fix: expose client fetch and signed adjustments
This commit is contained in:
@@ -235,7 +235,7 @@
|
|||||||
},
|
},
|
||||||
"packages/auth": {
|
"packages/auth": {
|
||||||
"name": "@wrnexus/auth",
|
"name": "@wrnexus/auth",
|
||||||
"version": "0.8.17",
|
"version": "0.8.18",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/authz": "workspace:*",
|
"@wrnexus/authz": "workspace:*",
|
||||||
"@wrnexus/captcha": "workspace:*",
|
"@wrnexus/captcha": "workspace:*",
|
||||||
@@ -342,7 +342,7 @@
|
|||||||
},
|
},
|
||||||
"packages/csr": {
|
"packages/csr": {
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.31",
|
"version": "0.8.32",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "workspace:*",
|
"@wrnexus/core": "workspace:*",
|
||||||
},
|
},
|
||||||
@@ -501,7 +501,7 @@
|
|||||||
},
|
},
|
||||||
"packages/metering": {
|
"packages/metering": {
|
||||||
"name": "@wrnexus/metering",
|
"name": "@wrnexus/metering",
|
||||||
"version": "0.8.2",
|
"version": "0.8.3",
|
||||||
},
|
},
|
||||||
"packages/mobile": {
|
"packages/mobile": {
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/auth",
|
"name": "@wrnexus/auth",
|
||||||
"version": "0.8.17",
|
"version": "0.8.18",
|
||||||
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
|
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@@ -1672,6 +1672,11 @@ export function createAuthEngine(options: AuthEngineOptions): AuthEngine {
|
|||||||
(typeof metadata.deviceId === "string" ? metadata.deviceId : undefined),
|
(typeof metadata.deviceId === "string" ? metadata.deviceId : undefined),
|
||||||
trusted: Boolean(rememberedDevice) || input.session?.trusted === true,
|
trusted: Boolean(rememberedDevice) || input.session?.trusted === true,
|
||||||
fingerprint: typeof metadata.fingerprint === "string" ? metadata.fingerprint : undefined,
|
fingerprint: typeof metadata.fingerprint === "string" ? metadata.fingerprint : undefined,
|
||||||
|
metadata: {
|
||||||
|
...(input.session?.metadata ?? {}),
|
||||||
|
mfaVerifiedAt: now(),
|
||||||
|
mfaMethod: input.method,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
user.lastLoginAt = now();
|
user.lastLoginAt = now();
|
||||||
user.updatedAt = now();
|
user.updatedAt = now();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.8.31",
|
"version": "0.8.32",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ export { ACTION_RUNTIME } from "./action-runtime.ts";
|
|||||||
export { createApiClient } from "./api-client.ts";
|
export { createApiClient } from "./api-client.ts";
|
||||||
export type { ApiClientOptions, ApiRequest } from "./api-client.ts";
|
export type { ApiClientOptions, ApiRequest } from "./api-client.ts";
|
||||||
|
|
||||||
|
/** Public client transport used by `.wrn` client functions for dynamic URLs. */
|
||||||
|
export function useFetch<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
methodOrOptions?:
|
||||||
|
string | { method?: string; query?: unknown; params?: unknown; body?: unknown; data?: unknown },
|
||||||
|
input?: unknown,
|
||||||
|
): Promise<T> {
|
||||||
|
const client = (
|
||||||
|
globalThis as typeof globalThis & { useFetch?: (...args: unknown[]) => Promise<T> }
|
||||||
|
).useFetch;
|
||||||
|
if (!client)
|
||||||
|
return Promise.reject(new Error("WRN-CSR: useFetch is only available in the browser runtime"));
|
||||||
|
return client(path, methodOrOptions, input);
|
||||||
|
}
|
||||||
|
|
||||||
const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const;
|
const CONTROLLER_SECTIONS = ["PRIMARY", "UI", "PIN"] as const;
|
||||||
|
|
||||||
function runtimeForMode(development: boolean): string {
|
function runtimeForMode(development: boolean): string {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/metering",
|
"name": "@wrnexus/metering",
|
||||||
"version": "0.8.2",
|
"version": "0.8.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ export function defineMeter(options: MeterOptions) {
|
|||||||
Number.isSafeInteger(units) && units > 0
|
Number.isSafeInteger(units) && units > 0
|
||||||
? null
|
? null
|
||||||
: { ok: false, reason: `${label} must be a positive whole number` };
|
: { ok: false, reason: `${label} must be a positive whole number` };
|
||||||
|
const validSigned = (units: number): MeterResult | null =>
|
||||||
|
Number.isSafeInteger(units) && units !== 0
|
||||||
|
? null
|
||||||
|
: { ok: false, reason: `${label} adjustment must be a non-zero whole number` };
|
||||||
const fault = (error: unknown, operation: string): MeterResult => {
|
const fault = (error: unknown, operation: string): MeterResult => {
|
||||||
options.onFault?.(error, operation);
|
options.onFault?.(error, operation);
|
||||||
return {
|
return {
|
||||||
@@ -138,7 +142,15 @@ export function defineMeter(options: MeterOptions) {
|
|||||||
add("purchase", subjectId, units, reason, reference),
|
add("purchase", subjectId, units, reason, reference),
|
||||||
refund: (subjectId: string, units: number, reason: string, reference = "") =>
|
refund: (subjectId: string, units: number, reason: string, reference = "") =>
|
||||||
add("refund", subjectId, units, reason, reference),
|
add("refund", subjectId, units, reason, reference),
|
||||||
adjust: (subjectId: string, units: number, reason: string, reference = "") =>
|
async adjust(subjectId: string, units: number, reason: string, reference = "") {
|
||||||
add("adjust", subjectId, units, reason, reference),
|
const invalid = validSigned(units);
|
||||||
|
if (invalid) return invalid;
|
||||||
|
try {
|
||||||
|
await options.store.write(subjectId, units, "adjust", reason, reference);
|
||||||
|
return { ok: true } as const;
|
||||||
|
} catch (error) {
|
||||||
|
return fault(error, "adjust");
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,3 +40,27 @@ test("meter distinguishes refusals from storage faults", async () => {
|
|||||||
fault: true,
|
fault: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("adjust accepts signed non-zero amounts while other additions stay positive", async () => {
|
||||||
|
const writes: number[] = [];
|
||||||
|
const meter = defineMeter({
|
||||||
|
store: {
|
||||||
|
balance: async () => 0,
|
||||||
|
reserve: async () => true,
|
||||||
|
write: async (_subject, units) => {
|
||||||
|
writes.push(units);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(await meter.adjust("u1", -25, "correction")).toEqual({ ok: true });
|
||||||
|
expect(await meter.adjust("u1", 10, "correction")).toEqual({ ok: true });
|
||||||
|
expect(await meter.adjust("u1", 0, "correction")).toEqual({
|
||||||
|
ok: false,
|
||||||
|
reason: "units adjustment must be a non-zero whole number",
|
||||||
|
});
|
||||||
|
expect(await meter.grant("u1", -1, "invalid")).toEqual({
|
||||||
|
ok: false,
|
||||||
|
reason: "units must be a positive whole number",
|
||||||
|
});
|
||||||
|
expect(writes).toEqual([-25, 10]);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user