@fuzdev/fuz_app 0.148.0 → 0.149.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +2 -2
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +22 -2
- package/dist/auth/CLAUDE.md +3 -2
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +29 -8
- package/dist/auth/audit_emitter.d.ts +14 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +5 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +5 -3
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +90 -35
- package/dist/schema_meta.d.ts +60 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +64 -0
- package/dist/testing/CLAUDE.md +162 -61
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +27 -4
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
|
@@ -131,6 +131,18 @@ export const ERROR_ROLE_NOT_WEB_GRANTABLE = 'role_not_web_grantable';
|
|
|
131
131
|
export const ERROR_ROLE_GRANT_NOT_FOUND = 'role_grant_not_found';
|
|
132
132
|
/** Query parameter `event_type` is not a valid audit event type. */
|
|
133
133
|
export const ERROR_INVALID_EVENT_TYPE = 'invalid_event_type';
|
|
134
|
+
// --- Declared input reasons (paged reads) ---
|
|
135
|
+
// each is a field's `invalid_reason` (`schema_meta.ts`): every refusal of the
|
|
136
|
+
// field carries it, whatever the fault — a wrong type or an unsafe integer
|
|
137
|
+
// included; each is named `<field>_invalid`
|
|
138
|
+
/** `limit` on a paged read refused — out of `[1, max]`, or not an integer. */
|
|
139
|
+
export const ERROR_LIMIT_INVALID = 'limit_invalid';
|
|
140
|
+
/** `offset` on a paged read refused — negative, or not a safe integer. */
|
|
141
|
+
export const ERROR_OFFSET_INVALID = 'offset_invalid';
|
|
142
|
+
/** `audit_log_list`'s `since_seq` cursor refused — negative, or not a safe integer. */
|
|
143
|
+
export const ERROR_SINCE_SEQ_INVALID = 'since_seq_invalid';
|
|
144
|
+
/** `audit_log_list`'s `outcome` filter refused — not `'success'` or `'failure'`. */
|
|
145
|
+
export const ERROR_OUTCOME_INVALID = 'outcome_invalid';
|
|
134
146
|
// --- DB table browser ---
|
|
135
147
|
/** DELETE blocked by a foreign key constraint. */
|
|
136
148
|
export const ERROR_FOREIGN_KEY_VIOLATION = 'foreign_key_violation';
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Provides error types, named constructors, and HTTP status mapping
|
|
5
5
|
* for the throw/catch error pattern used by `apply_route_specs`.
|
|
6
6
|
* Core error codes (5 standard + 10 general application). Domain-specific
|
|
7
|
-
* codes stay in consumers — add by casting `as JsonrpcErrorCode
|
|
7
|
+
* codes stay in consumers — add by casting `as JsonrpcErrorCode`, and
|
|
8
|
+
* declare each one's HTTP status with `register_jsonrpc_error_code`.
|
|
8
9
|
*
|
|
9
10
|
* `JsonrpcErrorCode` and `JsonrpcErrorObject` types are Zod-inferred
|
|
10
11
|
* from `http/jsonrpc.ts` — this module re-uses those as the single source
|
|
@@ -26,7 +27,8 @@ export type JsonrpcErrorName = 'parse_error' | 'invalid_request' | 'method_not_f
|
|
|
26
27
|
* Standard JSON-RPC error codes (5) plus general application codes (10).
|
|
27
28
|
*
|
|
28
29
|
* Extensible — consumers add domain-specific codes to their own objects
|
|
29
|
-
* by casting `as JsonrpcErrorCode
|
|
30
|
+
* by casting `as JsonrpcErrorCode`, and declare each one's HTTP status with
|
|
31
|
+
* `register_jsonrpc_error_code`. Application codes use the -32000 to
|
|
30
32
|
* -32099 range reserved by the JSON-RPC spec.
|
|
31
33
|
*
|
|
32
34
|
* Frozen with `Object.freeze` to convert accidental mutation (test
|
|
@@ -99,27 +101,49 @@ export declare const jsonrpc_errors: {
|
|
|
99
101
|
readonly request_cancelled: (...args: any[]) => ThrownJsonrpcError;
|
|
100
102
|
};
|
|
101
103
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
104
|
+
* Declare a consumer JSON-RPC error code and the HTTP status it answers, so
|
|
105
|
+
* `jsonrpc_error_code_to_http_status` maps it instead of falling back to 500.
|
|
106
|
+
*
|
|
107
|
+
* Call once at module load, beside the code's declaration — the module's
|
|
108
|
+
* import is then a side effect, so list it in the package's `sideEffects`.
|
|
109
|
+
* Registering the same code again with the same status is a no-op, so a
|
|
110
|
+
* module evaluated twice (a duplicated dependency, a test reload) is fine.
|
|
111
|
+
* A registered code joins neither `HTTP_STATUS_TO_JSONRPC_ERROR_CODE` (a bare
|
|
112
|
+
* status carries no consumer meaning, e.g. a proxy's envelope-less 502) nor
|
|
113
|
+
* `JSONRPC_ERROR_CODE_TO_NAME` (its name lives in the consumer's own
|
|
114
|
+
* taxonomy), matching the Rust spine's `ConsumerErrorCode`, which carries a
|
|
115
|
+
* code and a status only.
|
|
116
|
+
*
|
|
117
|
+
* @param code - a new code in the -32099 to -32000 server-error range
|
|
118
|
+
* @param http_status - the 4xx or 5xx status the code answers
|
|
119
|
+
* @throws Error when `code` is not an integer in -32099 to -32000, is a code
|
|
120
|
+
* `JSONRPC_ERROR_CODES` defines, or is already registered with another
|
|
121
|
+
* status, or when `http_status` is not an integer in 400 to 599
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* export const JSONRPC_ERROR_CODES = {
|
|
125
|
+
* ...BASE_JSONRPC_ERROR_CODES,
|
|
126
|
+
* ai_provider_error: -32020 as JsonrpcErrorCode
|
|
127
|
+
* } as const;
|
|
128
|
+
* register_jsonrpc_error_code(JSONRPC_ERROR_CODES.ai_provider_error, 502);
|
|
129
|
+
* ```
|
|
108
130
|
*/
|
|
109
|
-
export declare const
|
|
131
|
+
export declare const register_jsonrpc_error_code: (code: JsonrpcErrorCode, http_status: number) => void;
|
|
110
132
|
/**
|
|
111
|
-
* Maps HTTP status codes to JSON-RPC error codes (reverse mapping)
|
|
133
|
+
* Maps HTTP status codes to JSON-RPC error codes (reverse mapping), over the
|
|
134
|
+
* base set only — built once at module load, before any consumer code is
|
|
135
|
+
* registered, and frozen.
|
|
112
136
|
*
|
|
113
137
|
* When multiple error codes map to the same HTTP status (e.g. parse_error
|
|
114
138
|
* and invalid_request both map to 400), the last one wins. Use for
|
|
115
139
|
* best-effort HTTP → JSON-RPC translation.
|
|
116
140
|
*/
|
|
117
|
-
export declare const HTTP_STATUS_TO_JSONRPC_ERROR_CODE: Record<number, JsonrpcErrorCode
|
|
141
|
+
export declare const HTTP_STATUS_TO_JSONRPC_ERROR_CODE: Readonly<Record<number, JsonrpcErrorCode>>;
|
|
118
142
|
/**
|
|
119
143
|
* Map a JSON-RPC error code to an HTTP status code.
|
|
120
144
|
*
|
|
121
|
-
* Returns 500 for unrecognized codes (consumer
|
|
122
|
-
*
|
|
145
|
+
* Returns 500 for unrecognized codes (a consumer code never passed to
|
|
146
|
+
* `register_jsonrpc_error_code` answers internal server error). The return
|
|
123
147
|
* is narrowed to Hono's `ContentfulStatusCode` so call sites can
|
|
124
148
|
* pass the result to `c.json(body, status)` without `as any` —
|
|
125
149
|
* 499 (nginx "client closed request") is non-standard and gets
|
|
@@ -140,8 +164,9 @@ export declare const http_status_to_jsonrpc_error_code: (status: number) => Json
|
|
|
140
164
|
* without inventing a separate vocabulary. Built once at module load
|
|
141
165
|
* from the canonical `JSONRPC_ERROR_CODES` map so the two cannot drift.
|
|
142
166
|
*
|
|
143
|
-
* Consumer-defined codes outside the standard taxonomy are not present
|
|
144
|
-
* `
|
|
167
|
+
* Consumer-defined codes outside the standard taxonomy are not present —
|
|
168
|
+
* registering one with `register_jsonrpc_error_code` gives it a status, not
|
|
169
|
+
* a name; `jsonrpc_error_code_to_name` falls back to `'internal_error'` so the
|
|
145
170
|
* REST shape always carries some reason rather than `undefined`.
|
|
146
171
|
*/
|
|
147
172
|
export declare const JSONRPC_ERROR_CODE_TO_NAME: Readonly<Record<number, JsonrpcErrorName>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc_errors.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/jsonrpc_errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"jsonrpc_errors.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/jsonrpc_errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,OAAO,EAQN,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,MAAM,cAAc,CAAC;AAEtB,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AAErD,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GACzB,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,WAAW,GACX,WAAW,GACX,UAAU,GACV,kBAAkB,GAClB,cAAc,GACd,qBAAqB,GACrB,SAAS,GACT,gBAAgB,GAChB,mBAAmB,CAAC;AAEvB;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,EA4C1B,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EAAE,OAAO,CAAC,KAAG,CAAC,GAAG,SAAsC,CAAC;AAElF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,EAmG7B,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,kBAAkB,CAAC,CAAC,CAAC;AAEtF;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC;gBAEH,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY;CAK3F;AAWD;;;;GAIG;AACH,eAAO,MAAM,cAAc;8CAXQ,kBAAkB;kDAAlB,kBAAkB;mDAAlB,kBAAkB;iDAAlB,kBAAkB;iDAAlB,kBAAkB;kDAAlB,kBAAkB;4CAAlB,kBAAkB;4CAAlB,kBAAkB;2CAAlB,kBAAkB;mDAAlB,kBAAkB;+CAAlB,kBAAkB;sDAAlB,kBAAkB;0CAAlB,kBAAkB;iDAAlB,kBAAkB;oDAAlB,kBAAkB;CA2BqC,CAAC;AAqC3F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,gBAAgB,EAAE,aAAa,MAAM,KAAG,IA0BzF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAQvF,CAAC;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,iCAAiC,GAAI,MAAM,gBAAgB,KAAG,oBACC,CAAC;AAE7E;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,GAAI,QAAQ,MAAM,KAAG,gBACa,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAMjF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GAAI,MAAM,gBAAgB,KAAG,gBACL,CAAC"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Provides error types, named constructors, and HTTP status mapping
|
|
5
5
|
* for the throw/catch error pattern used by `apply_route_specs`.
|
|
6
6
|
* Core error codes (5 standard + 10 general application). Domain-specific
|
|
7
|
-
* codes stay in consumers — add by casting `as JsonrpcErrorCode
|
|
7
|
+
* codes stay in consumers — add by casting `as JsonrpcErrorCode`, and
|
|
8
|
+
* declare each one's HTTP status with `register_jsonrpc_error_code`.
|
|
8
9
|
*
|
|
9
10
|
* `JsonrpcErrorCode` and `JsonrpcErrorObject` types are Zod-inferred
|
|
10
11
|
* from `http/jsonrpc.ts` — this module re-uses those as the single source
|
|
@@ -17,14 +18,15 @@
|
|
|
17
18
|
* @module
|
|
18
19
|
*/
|
|
19
20
|
import { DEV } from 'esm-env';
|
|
20
|
-
import { JSONRPC_PARSE_ERROR, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_INVALID_PARAMS, JSONRPC_INTERNAL_ERROR } from './jsonrpc.js';
|
|
21
|
+
import { JSONRPC_PARSE_ERROR, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_INVALID_PARAMS, JSONRPC_INTERNAL_ERROR, JSONRPC_SERVER_ERROR_START, JSONRPC_SERVER_ERROR_END } from './jsonrpc.js';
|
|
21
22
|
/** Default message for unknown errors. */
|
|
22
23
|
export const UNKNOWN_ERROR_MESSAGE = 'unknown error';
|
|
23
24
|
/**
|
|
24
25
|
* Standard JSON-RPC error codes (5) plus general application codes (10).
|
|
25
26
|
*
|
|
26
27
|
* Extensible — consumers add domain-specific codes to their own objects
|
|
27
|
-
* by casting `as JsonrpcErrorCode
|
|
28
|
+
* by casting `as JsonrpcErrorCode`, and declare each one's HTTP status with
|
|
29
|
+
* `register_jsonrpc_error_code`. Application codes use the -32000 to
|
|
28
30
|
* -32099 range reserved by the JSON-RPC spec.
|
|
29
31
|
*
|
|
30
32
|
* Frozen with `Object.freeze` to convert accidental mutation (test
|
|
@@ -225,54 +227,106 @@ export const jsonrpc_errors = {
|
|
|
225
227
|
};
|
|
226
228
|
// --- HTTP status mapping ---
|
|
227
229
|
/**
|
|
228
|
-
*
|
|
230
|
+
* The HTTP status each JSON-RPC error code answers — the base set, plus each
|
|
231
|
+
* consumer code `register_jsonrpc_error_code` adds.
|
|
229
232
|
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* lookup function reads at call time, so mutation is the supported
|
|
233
|
-
* extension mechanism.
|
|
233
|
+
* Module-private so a consumer can't remap a base code or skip the checks
|
|
234
|
+
* registration makes; read it through `jsonrpc_error_code_to_http_status`.
|
|
234
235
|
*/
|
|
235
|
-
|
|
236
|
-
[
|
|
237
|
-
[
|
|
238
|
-
[
|
|
239
|
-
[
|
|
240
|
-
[
|
|
241
|
-
[
|
|
242
|
-
[
|
|
243
|
-
[
|
|
244
|
-
[
|
|
245
|
-
[
|
|
246
|
-
// queue_overflow shares 429 with rate_limited — listed first so
|
|
247
|
-
// map
|
|
248
|
-
|
|
249
|
-
[
|
|
250
|
-
[
|
|
251
|
-
[
|
|
252
|
-
[
|
|
236
|
+
const jsonrpc_error_code_http_statuses = new Map([
|
|
237
|
+
[JSONRPC_PARSE_ERROR, 400],
|
|
238
|
+
[JSONRPC_INVALID_REQUEST, 400],
|
|
239
|
+
[JSONRPC_METHOD_NOT_FOUND, 404],
|
|
240
|
+
[JSONRPC_INVALID_PARAMS, 400],
|
|
241
|
+
[JSONRPC_INTERNAL_ERROR, 500],
|
|
242
|
+
[JSONRPC_ERROR_CODES.unauthenticated, 401],
|
|
243
|
+
[JSONRPC_ERROR_CODES.forbidden, 403],
|
|
244
|
+
[JSONRPC_ERROR_CODES.not_found, 404],
|
|
245
|
+
[JSONRPC_ERROR_CODES.conflict, 409],
|
|
246
|
+
[JSONRPC_ERROR_CODES.validation_error, 422],
|
|
247
|
+
// queue_overflow shares 429 with rate_limited — listed first so the
|
|
248
|
+
// last-wins reverse map resolves 429 to rate_limited (a policy) rather
|
|
249
|
+
// than a full buffer
|
|
250
|
+
[JSONRPC_ERROR_CODES.queue_overflow, 429],
|
|
251
|
+
[JSONRPC_ERROR_CODES.rate_limited, 429],
|
|
252
|
+
[JSONRPC_ERROR_CODES.service_unavailable, 503],
|
|
253
|
+
[JSONRPC_ERROR_CODES.timeout, 504],
|
|
254
|
+
[JSONRPC_ERROR_CODES.request_cancelled, 499] // nginx "client closed request"
|
|
255
|
+
]);
|
|
256
|
+
/** Every code `JSONRPC_ERROR_CODES` defines — the numbers a consumer code may not reuse. */
|
|
257
|
+
const BASE_JSONRPC_ERROR_CODE_SET = new Set(Object.values(JSONRPC_ERROR_CODES));
|
|
258
|
+
/**
|
|
259
|
+
* Declare a consumer JSON-RPC error code and the HTTP status it answers, so
|
|
260
|
+
* `jsonrpc_error_code_to_http_status` maps it instead of falling back to 500.
|
|
261
|
+
*
|
|
262
|
+
* Call once at module load, beside the code's declaration — the module's
|
|
263
|
+
* import is then a side effect, so list it in the package's `sideEffects`.
|
|
264
|
+
* Registering the same code again with the same status is a no-op, so a
|
|
265
|
+
* module evaluated twice (a duplicated dependency, a test reload) is fine.
|
|
266
|
+
* A registered code joins neither `HTTP_STATUS_TO_JSONRPC_ERROR_CODE` (a bare
|
|
267
|
+
* status carries no consumer meaning, e.g. a proxy's envelope-less 502) nor
|
|
268
|
+
* `JSONRPC_ERROR_CODE_TO_NAME` (its name lives in the consumer's own
|
|
269
|
+
* taxonomy), matching the Rust spine's `ConsumerErrorCode`, which carries a
|
|
270
|
+
* code and a status only.
|
|
271
|
+
*
|
|
272
|
+
* @param code - a new code in the -32099 to -32000 server-error range
|
|
273
|
+
* @param http_status - the 4xx or 5xx status the code answers
|
|
274
|
+
* @throws Error when `code` is not an integer in -32099 to -32000, is a code
|
|
275
|
+
* `JSONRPC_ERROR_CODES` defines, or is already registered with another
|
|
276
|
+
* status, or when `http_status` is not an integer in 400 to 599
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* export const JSONRPC_ERROR_CODES = {
|
|
280
|
+
* ...BASE_JSONRPC_ERROR_CODES,
|
|
281
|
+
* ai_provider_error: -32020 as JsonrpcErrorCode
|
|
282
|
+
* } as const;
|
|
283
|
+
* register_jsonrpc_error_code(JSONRPC_ERROR_CODES.ai_provider_error, 502);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export const register_jsonrpc_error_code = (code, http_status) => {
|
|
287
|
+
if (!Number.isInteger(code) ||
|
|
288
|
+
code < JSONRPC_SERVER_ERROR_END ||
|
|
289
|
+
code > JSONRPC_SERVER_ERROR_START) {
|
|
290
|
+
throw new Error(`a consumer JSON-RPC error code must be an integer in ${JSONRPC_SERVER_ERROR_END} to ${JSONRPC_SERVER_ERROR_START}, got ${code}`);
|
|
291
|
+
}
|
|
292
|
+
if (BASE_JSONRPC_ERROR_CODE_SET.has(code)) {
|
|
293
|
+
throw new Error(`a consumer JSON-RPC error code may not reuse a base-set code, got ${code}`);
|
|
294
|
+
}
|
|
295
|
+
if (!Number.isInteger(http_status) || http_status < 400 || http_status > 599) {
|
|
296
|
+
throw new Error(`a consumer JSON-RPC error code must answer a 4xx or 5xx status, got ${http_status} for ${code}`);
|
|
297
|
+
}
|
|
298
|
+
const registered = jsonrpc_error_code_http_statuses.get(code);
|
|
299
|
+
if (registered === undefined) {
|
|
300
|
+
jsonrpc_error_code_http_statuses.set(code, http_status);
|
|
301
|
+
}
|
|
302
|
+
else if (registered !== http_status) {
|
|
303
|
+
throw new Error(`JSON-RPC error code ${code} is already registered with status ${registered}, not ${http_status}`);
|
|
304
|
+
}
|
|
253
305
|
};
|
|
254
306
|
/**
|
|
255
|
-
* Maps HTTP status codes to JSON-RPC error codes (reverse mapping)
|
|
307
|
+
* Maps HTTP status codes to JSON-RPC error codes (reverse mapping), over the
|
|
308
|
+
* base set only — built once at module load, before any consumer code is
|
|
309
|
+
* registered, and frozen.
|
|
256
310
|
*
|
|
257
311
|
* When multiple error codes map to the same HTTP status (e.g. parse_error
|
|
258
312
|
* and invalid_request both map to 400), the last one wins. Use for
|
|
259
313
|
* best-effort HTTP → JSON-RPC translation.
|
|
260
314
|
*/
|
|
261
|
-
export const HTTP_STATUS_TO_JSONRPC_ERROR_CODE = Object.
|
|
315
|
+
export const HTTP_STATUS_TO_JSONRPC_ERROR_CODE = Object.freeze(Object.fromEntries(Array.from(jsonrpc_error_code_http_statuses, ([code, status]) => [
|
|
262
316
|
status,
|
|
263
|
-
|
|
264
|
-
]));
|
|
317
|
+
code
|
|
318
|
+
])));
|
|
265
319
|
/**
|
|
266
320
|
* Map a JSON-RPC error code to an HTTP status code.
|
|
267
321
|
*
|
|
268
|
-
* Returns 500 for unrecognized codes (consumer
|
|
269
|
-
*
|
|
322
|
+
* Returns 500 for unrecognized codes (a consumer code never passed to
|
|
323
|
+
* `register_jsonrpc_error_code` answers internal server error). The return
|
|
270
324
|
* is narrowed to Hono's `ContentfulStatusCode` so call sites can
|
|
271
325
|
* pass the result to `c.json(body, status)` without `as any` —
|
|
272
326
|
* 499 (nginx "client closed request") is non-standard and gets
|
|
273
327
|
* absorbed by the cast here rather than at every dispatcher branch.
|
|
274
328
|
*/
|
|
275
|
-
export const jsonrpc_error_code_to_http_status = (code) => (
|
|
329
|
+
export const jsonrpc_error_code_to_http_status = (code) => (jsonrpc_error_code_http_statuses.get(code) ?? 500);
|
|
276
330
|
/**
|
|
277
331
|
* Map an HTTP status code to a JSON-RPC error code.
|
|
278
332
|
*
|
|
@@ -287,8 +341,9 @@ export const http_status_to_jsonrpc_error_code = (status) => HTTP_STATUS_TO_JSON
|
|
|
287
341
|
* without inventing a separate vocabulary. Built once at module load
|
|
288
342
|
* from the canonical `JSONRPC_ERROR_CODES` map so the two cannot drift.
|
|
289
343
|
*
|
|
290
|
-
* Consumer-defined codes outside the standard taxonomy are not present
|
|
291
|
-
* `
|
|
344
|
+
* Consumer-defined codes outside the standard taxonomy are not present —
|
|
345
|
+
* registering one with `register_jsonrpc_error_code` gives it a status, not
|
|
346
|
+
* a name; `jsonrpc_error_code_to_name` falls back to `'internal_error'` so the
|
|
292
347
|
* REST shape always carries some reason rather than `undefined`.
|
|
293
348
|
*/
|
|
294
349
|
export const JSONRPC_ERROR_CODE_TO_NAME = Object.freeze(Object.fromEntries(Object.entries(JSONRPC_ERROR_CODES).map(([name, code]) => [code, name])));
|
package/dist/schema_meta.d.ts
CHANGED
|
@@ -25,6 +25,29 @@ export interface SchemaFieldMeta {
|
|
|
25
25
|
* schema itself, so a wrapper (`.optional()`, `.extend()`) drops it.
|
|
26
26
|
*/
|
|
27
27
|
empty_input_handler_guarded?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The `error.data.reason` an action input refuses this field with. On a
|
|
30
|
+
* field of an action's **input** schema, every `invalid_params` whose first
|
|
31
|
+
* Zod issue lands on the field — missing, wrong type, `null`, out of range,
|
|
32
|
+
* malformed, or a fault anywhere inside it — carries this reason, where an
|
|
33
|
+
* undeclared field's refusal carries none. The message stays the generic
|
|
34
|
+
* `invalid params`. A body with faults on several fields answers the first
|
|
35
|
+
* issue's reason in Zod's order, which a twin checking in another order
|
|
36
|
+
* need not match, so the reason is defined for a single-fault body only —
|
|
37
|
+
* except that an issue on the input itself (an unknown key) anywhere among
|
|
38
|
+
* the issues means no reason, as the Rust twin refuses unknown keys first.
|
|
39
|
+
* Declared reasons are named `<field>_invalid`.
|
|
40
|
+
*
|
|
41
|
+
* The declaration is the one source of the reason: the dispatcher reads it
|
|
42
|
+
* (`perform_action`), the adversarial suites expect it, and the Rust twin
|
|
43
|
+
* owes the same reason on every refusal of the field. Read through the
|
|
44
|
+
* field's optional, nullable, default, prefault, transform and pipe layers
|
|
45
|
+
* (not `.readonly()`, `.catch()` or `z.lazy()`), so it may sit on any of
|
|
46
|
+
* them; a declaration on a nested field overrides its parent's for a fault
|
|
47
|
+
* inside it, and an object-level refine whose issue names the field takes
|
|
48
|
+
* its reason too. See `resolve_issues_invalid_reason`.
|
|
49
|
+
*/
|
|
50
|
+
invalid_reason?: string;
|
|
28
51
|
}
|
|
29
52
|
/**
|
|
30
53
|
* Whether `schema` carries `empty_input_handler_guarded: true` in its `.meta()`.
|
|
@@ -33,4 +56,41 @@ export interface SchemaFieldMeta {
|
|
|
33
56
|
* @returns whether its handler refuses the `{}` the schema admits
|
|
34
57
|
*/
|
|
35
58
|
export declare const is_empty_input_handler_guarded: (schema: z.ZodType) => boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The `invalid_reason` a schema declares, read through its wrappers, the
|
|
61
|
+
* outermost declaration winning.
|
|
62
|
+
*
|
|
63
|
+
* @param schema - a field schema, possibly wrapped
|
|
64
|
+
* @returns the declared reason, or `undefined` when no layer declares one
|
|
65
|
+
*/
|
|
66
|
+
export declare const schema_invalid_reason: (schema: z.ZodType) => string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* The `error.data.reason` an input validation failure at `path` carries: the
|
|
69
|
+
* `invalid_reason` of the deepest field along `path` that declares one.
|
|
70
|
+
*
|
|
71
|
+
* Walks object fields only, each through its wrappers: a segment that is not
|
|
72
|
+
* a key of an object schema (an array index, a union member's key, an unknown
|
|
73
|
+
* key) ends the walk, so a fault inside a declared field still resolves to
|
|
74
|
+
* that field's reason. An empty path — a whole-input fault such as an
|
|
75
|
+
* unrecognized key on the input object — resolves to none, as does a schema
|
|
76
|
+
* that is not an object.
|
|
77
|
+
*
|
|
78
|
+
* @param input_schema - an action's input schema
|
|
79
|
+
* @param path - the failing issue's path (`ZodError.issues[0].path`)
|
|
80
|
+
* @returns the declared reason, or `undefined`
|
|
81
|
+
*/
|
|
82
|
+
export declare const resolve_invalid_reason: (input_schema: z.ZodType, path: ReadonlyArray<PropertyKey>) => string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* The `error.data.reason` a failed input parse answers: the declared reason
|
|
85
|
+
* of the first issue's field (`resolve_invalid_reason`), or none when any
|
|
86
|
+
* issue sits on the input itself. Such an issue is an unknown key on the
|
|
87
|
+
* input object (or a non-object input), which the Rust twin refuses before
|
|
88
|
+
* reading any field, so a stale client sending an extra key gets the same
|
|
89
|
+
* answer from both spines whatever else its body gets wrong.
|
|
90
|
+
*
|
|
91
|
+
* @param input_schema - an action's input schema
|
|
92
|
+
* @param issues - the failed parse's issues, in Zod's order
|
|
93
|
+
* @returns the declared reason, or `undefined`
|
|
94
|
+
*/
|
|
95
|
+
export declare const resolve_issues_invalid_reason: (input_schema: z.ZodType, issues: ReadonlyArray<z.core.$ZodIssue>) => string | undefined;
|
|
36
96
|
//# sourceMappingURL=schema_meta.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema_meta.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/schema_meta.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"schema_meta.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/schema_meta.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,OACkB,CAAC;AAEtF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,CAAC,CAAC,OAAO,KAAG,MAAM,GAAG,SAMlE,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GAClC,cAAc,CAAC,CAAC,OAAO,EACvB,MAAM,aAAa,CAAC,WAAW,CAAC,KAC9B,MAAM,GAAG,SAYX,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,6BAA6B,GACzC,cAAc,CAAC,CAAC,OAAO,EACvB,QAAQ,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KACrC,MAAM,GAAG,SAIX,CAAC"}
|
package/dist/schema_meta.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { zod_get_schema_layers, zod_unwrap_to_object } from './zod.js';
|
|
9
10
|
/**
|
|
10
11
|
* Whether `schema` carries `empty_input_handler_guarded: true` in its `.meta()`.
|
|
11
12
|
*
|
|
@@ -13,3 +14,66 @@
|
|
|
13
14
|
* @returns whether its handler refuses the `{}` the schema admits
|
|
14
15
|
*/
|
|
15
16
|
export const is_empty_input_handler_guarded = (schema) => schema.meta()?.empty_input_handler_guarded === true;
|
|
17
|
+
/**
|
|
18
|
+
* The `invalid_reason` a schema declares, read through its wrappers, the
|
|
19
|
+
* outermost declaration winning.
|
|
20
|
+
*
|
|
21
|
+
* @param schema - a field schema, possibly wrapped
|
|
22
|
+
* @returns the declared reason, or `undefined` when no layer declares one
|
|
23
|
+
*/
|
|
24
|
+
export const schema_invalid_reason = (schema) => {
|
|
25
|
+
for (const layer of zod_get_schema_layers(schema)) {
|
|
26
|
+
const reason = layer.meta()?.invalid_reason;
|
|
27
|
+
if (reason !== undefined)
|
|
28
|
+
return reason;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The `error.data.reason` an input validation failure at `path` carries: the
|
|
34
|
+
* `invalid_reason` of the deepest field along `path` that declares one.
|
|
35
|
+
*
|
|
36
|
+
* Walks object fields only, each through its wrappers: a segment that is not
|
|
37
|
+
* a key of an object schema (an array index, a union member's key, an unknown
|
|
38
|
+
* key) ends the walk, so a fault inside a declared field still resolves to
|
|
39
|
+
* that field's reason. An empty path — a whole-input fault such as an
|
|
40
|
+
* unrecognized key on the input object — resolves to none, as does a schema
|
|
41
|
+
* that is not an object.
|
|
42
|
+
*
|
|
43
|
+
* @param input_schema - an action's input schema
|
|
44
|
+
* @param path - the failing issue's path (`ZodError.issues[0].path`)
|
|
45
|
+
* @returns the declared reason, or `undefined`
|
|
46
|
+
*/
|
|
47
|
+
export const resolve_invalid_reason = (input_schema, path) => {
|
|
48
|
+
let reason;
|
|
49
|
+
let schema = input_schema;
|
|
50
|
+
for (const segment of path) {
|
|
51
|
+
if (typeof segment !== 'string')
|
|
52
|
+
break;
|
|
53
|
+
const shape = zod_unwrap_to_object(schema)?.shape;
|
|
54
|
+
if (!shape || !Object.hasOwn(shape, segment))
|
|
55
|
+
break;
|
|
56
|
+
const field = shape[segment];
|
|
57
|
+
reason = schema_invalid_reason(field) ?? reason;
|
|
58
|
+
schema = field;
|
|
59
|
+
}
|
|
60
|
+
return reason;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The `error.data.reason` a failed input parse answers: the declared reason
|
|
64
|
+
* of the first issue's field (`resolve_invalid_reason`), or none when any
|
|
65
|
+
* issue sits on the input itself. Such an issue is an unknown key on the
|
|
66
|
+
* input object (or a non-object input), which the Rust twin refuses before
|
|
67
|
+
* reading any field, so a stale client sending an extra key gets the same
|
|
68
|
+
* answer from both spines whatever else its body gets wrong.
|
|
69
|
+
*
|
|
70
|
+
* @param input_schema - an action's input schema
|
|
71
|
+
* @param issues - the failed parse's issues, in Zod's order
|
|
72
|
+
* @returns the declared reason, or `undefined`
|
|
73
|
+
*/
|
|
74
|
+
export const resolve_issues_invalid_reason = (input_schema, issues) => {
|
|
75
|
+
const first = issues[0];
|
|
76
|
+
if (!first || issues.some((issue) => issue.path.length === 0))
|
|
77
|
+
return undefined;
|
|
78
|
+
return resolve_invalid_reason(input_schema, first.path);
|
|
79
|
+
};
|