@fuzdev/fuz_app 0.4.0 → 0.6.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.
Files changed (38) hide show
  1. package/dist/actions/action_bridge.d.ts +3 -3
  2. package/dist/actions/action_bridge.d.ts.map +1 -1
  3. package/dist/actions/action_bridge.js +5 -4
  4. package/dist/actions/action_rpc.d.ts +89 -0
  5. package/dist/actions/action_rpc.d.ts.map +1 -0
  6. package/dist/actions/action_rpc.js +248 -0
  7. package/dist/actions/action_spec.d.ts +8 -8
  8. package/dist/actions/action_spec.d.ts.map +1 -1
  9. package/dist/actions/action_spec.js +2 -2
  10. package/dist/http/db_routes.d.ts.map +1 -1
  11. package/dist/http/db_routes.js +4 -2
  12. package/dist/http/jsonrpc.d.ts +62 -0
  13. package/dist/http/jsonrpc.d.ts.map +1 -0
  14. package/dist/http/jsonrpc.js +49 -0
  15. package/dist/http/jsonrpc_errors.d.ts +132 -0
  16. package/dist/http/jsonrpc_errors.d.ts.map +1 -0
  17. package/dist/http/jsonrpc_errors.js +197 -0
  18. package/dist/http/route_spec.d.ts +2 -1
  19. package/dist/http/route_spec.d.ts.map +1 -1
  20. package/dist/http/route_spec.js +43 -7
  21. package/dist/http/schema_helpers.d.ts +3 -3
  22. package/dist/http/schema_helpers.d.ts.map +1 -1
  23. package/dist/http/schema_helpers.js +5 -10
  24. package/dist/http/surface.d.ts +25 -0
  25. package/dist/http/surface.d.ts.map +1 -1
  26. package/dist/http/surface.js +16 -1
  27. package/dist/server/app_server.d.ts +3 -1
  28. package/dist/server/app_server.d.ts.map +1 -1
  29. package/dist/server/app_server.js +2 -1
  30. package/dist/testing/adversarial_input.d.ts.map +1 -1
  31. package/dist/testing/adversarial_input.js +22 -7
  32. package/dist/testing/stubs.d.ts +3 -1
  33. package/dist/testing/stubs.d.ts.map +1 -1
  34. package/dist/testing/stubs.js +2 -1
  35. package/dist/testing/surface_invariants.d.ts +4 -0
  36. package/dist/testing/surface_invariants.d.ts.map +1 -1
  37. package/dist/testing/surface_invariants.js +4 -0
  38. package/package.json +1 -1
@@ -39,7 +39,7 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
39
39
  * Derive a `RouteSpec` from an `ActionSpec` and options.
40
40
  *
41
41
  * Only `request_response` actions (which require non-null `auth`) can become routes.
42
- * `remote_notification` actions (auth null) should use `event_spec_from_action`.
42
+ * `remote_notification` actions (auth null) should use `create_action_event_spec`.
43
43
  * `local_call` actions are not for HTTP transport.
44
44
  *
45
45
  * Error schemas are transport-specific (keyed by HTTP status codes) and belong
@@ -51,7 +51,7 @@ export declare const derive_http_method: (side_effects: ActionSideEffects) => Ro
51
51
  * @returns a `RouteSpec` ready for `apply_route_specs`
52
52
  * @throws if `spec.auth` is null
53
53
  */
54
- export declare const route_spec_from_action: (spec: ActionSpec, options: ActionRouteOptions) => RouteSpec;
54
+ export declare const create_action_route_spec: (spec: ActionSpec, options: ActionRouteOptions) => RouteSpec;
55
55
  /**
56
56
  * Derive an `SseEventSpec` from an `ActionSpec`.
57
57
  *
@@ -61,5 +61,5 @@ export declare const route_spec_from_action: (spec: ActionSpec, options: ActionR
61
61
  * @param options - optional SSE-specific options (channel)
62
62
  * @returns an `SseEventSpec` ready for `create_validated_broadcaster`
63
63
  */
64
- export declare const event_spec_from_action: (spec: ActionSpec, options?: ActionEventOptions) => SseEventSpec;
64
+ export declare const create_action_event_spec: (spec: ActionSpec, options?: ActionEventOptions) => SseEventSpec;
65
65
  //# sourceMappingURL=action_bridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+IAA+I;IAC/I,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,KAAG,SAKtD,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAkBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,YAYF,CAAC"}
1
+ {"version":3,"file":"action_bridge.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,UAAU,EAAE,UAAU,IAAI,cAAc,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,uGAAuG;IACvG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACrB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,+IAA+I;IAC/I,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,8GAA8G;IAC9G,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,GAAI,MAAM,cAAc,KAAG,SAKtD,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,GAAI,cAAc,iBAAiB,KAAG,WAEpE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,SAAS,kBAAkB,KACzB,SAmBF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,GACpC,MAAM,UAAU,EAChB,UAAU,kBAAkB,KAC1B,YAYF,CAAC"}
@@ -19,13 +19,13 @@ export const map_action_auth = (auth) => {
19
19
  };
20
20
  /** Derive the default HTTP method from side effects. */
21
21
  export const derive_http_method = (side_effects) => {
22
- return side_effects === true ? 'POST' : 'GET';
22
+ return side_effects ? 'POST' : 'GET';
23
23
  };
24
24
  /**
25
25
  * Derive a `RouteSpec` from an `ActionSpec` and options.
26
26
  *
27
27
  * Only `request_response` actions (which require non-null `auth`) can become routes.
28
- * `remote_notification` actions (auth null) should use `event_spec_from_action`.
28
+ * `remote_notification` actions (auth null) should use `create_action_event_spec`.
29
29
  * `local_call` actions are not for HTTP transport.
30
30
  *
31
31
  * Error schemas are transport-specific (keyed by HTTP status codes) and belong
@@ -37,7 +37,7 @@ export const derive_http_method = (side_effects) => {
37
37
  * @returns a `RouteSpec` ready for `apply_route_specs`
38
38
  * @throws if `spec.auth` is null
39
39
  */
40
- export const route_spec_from_action = (spec, options) => {
40
+ export const create_action_route_spec = (spec, options) => {
41
41
  if (spec.auth === null) {
42
42
  throw new Error(`Cannot derive route spec from action '${spec.method}': auth is null (only request_response actions with non-null auth can become routes)`);
43
43
  }
@@ -52,6 +52,7 @@ export const route_spec_from_action = (spec, options) => {
52
52
  input: spec.input,
53
53
  output: spec.output,
54
54
  ...(options.errors ? { errors: options.errors } : {}),
55
+ transaction: spec.side_effects,
55
56
  };
56
57
  };
57
58
  /**
@@ -63,7 +64,7 @@ export const route_spec_from_action = (spec, options) => {
63
64
  * @param options - optional SSE-specific options (channel)
64
65
  * @returns an `SseEventSpec` ready for `create_validated_broadcaster`
65
66
  */
66
- export const event_spec_from_action = (spec, options) => {
67
+ export const create_action_event_spec = (spec, options) => {
67
68
  if (spec.kind !== 'remote_notification') {
68
69
  throw new Error(`Cannot derive event spec from action '${spec.method}': kind is '${spec.kind}' (must be 'remote_notification')`);
69
70
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Single JSON-RPC 2.0 endpoint from action specs.
3
+ *
4
+ * `create_rpc_endpoint` produces `RouteSpec[]` (GET + POST on one path)
5
+ * with an internal dispatcher. Method name lives in the JSON-RPC envelope
6
+ * (POST body or GET query string), not the URL. Auth is checked per-action
7
+ * inside the dispatcher.
8
+ *
9
+ * Handler signature: `(input: TInput, ctx: ActionContext) => TOutput`
10
+ * where `ActionContext` provides auth identity, DB, and framework context.
11
+ *
12
+ * @module
13
+ */
14
+ import type { Logger } from '@fuzdev/fuz_util/log.js';
15
+ import type { RequestResponseActionSpec } from './action_spec.js';
16
+ import { type RouteSpec } from '../http/route_spec.js';
17
+ import { type RequestContext } from '../auth/request_context.js';
18
+ import type { Db } from '../db/db.js';
19
+ /**
20
+ * Per-request context provided to RPC action handlers.
21
+ *
22
+ * Extends `RouteContext` with auth identity and logger.
23
+ * `auth` is `RequestContext | null` — handlers for authenticated
24
+ * actions can narrow via the auth middleware guarantee.
25
+ */
26
+ export interface ActionContext {
27
+ /** The authenticated identity, or `null` for public routes. */
28
+ auth: RequestContext | null;
29
+ /** Transaction-scoped for mutations, pool-level for reads. */
30
+ db: Db;
31
+ /** Always pool-level — for fire-and-forget effects that outlive the transaction. */
32
+ background_db: Db;
33
+ /** Fire-and-forget side effects — push here for post-response flushing. */
34
+ pending_effects: Array<Promise<void>>;
35
+ /** Logger instance. */
36
+ log: Logger;
37
+ }
38
+ /**
39
+ * Handler function for an RPC action.
40
+ *
41
+ * Receives validated input and an `ActionContext` with per-request deps.
42
+ * Returns the output value (serialized to JSON by the wrapper).
43
+ */
44
+ export type ActionHandler<TInput = any, TOutput = any> = (input: TInput, ctx: ActionContext) => TOutput | Promise<TOutput>;
45
+ /**
46
+ * An RPC action — combines an action spec with its handler.
47
+ *
48
+ * The spec defines the contract (method, auth, schemas, side effects).
49
+ * The handler implements the behavior.
50
+ */
51
+ export interface RpcAction {
52
+ spec: RequestResponseActionSpec;
53
+ handler: ActionHandler;
54
+ }
55
+ /** Options for `create_rpc_endpoint`. */
56
+ export interface CreateRpcEndpointOptions {
57
+ /** Mount path for the endpoint (e.g., `/api/rpc`). */
58
+ path: string;
59
+ /** RPC actions to serve. */
60
+ actions: Array<RpcAction>;
61
+ /** Logger instance for handler context. */
62
+ log: Logger;
63
+ }
64
+ /**
65
+ * Single JSON-RPC 2.0 endpoint — the canonical RPC transport binding.
66
+ *
67
+ * Returns two `RouteSpec` entries (GET + POST on the same path) for
68
+ * `apply_route_specs`. The internal dispatcher handles:
69
+ *
70
+ * 1. **Parse envelope** — POST: JSON body as `JsonrpcRequest`. GET: `method`
71
+ * and `params` from query string.
72
+ * 2. **Lookup method** — find the `RpcAction` by method name.
73
+ * 3. **Auth check** — verify identity against the action's `auth` requirement.
74
+ * 4. **Validate params** — parse input against the action's `input` schema.
75
+ * 5. **Dispatch** — acquire DB handle (transaction for mutations, pool for reads),
76
+ * construct `ActionContext`, call handler, return JSON-RPC response.
77
+ *
78
+ * GET is restricted to `side_effects: false` actions (cacheable reads).
79
+ * All errors use JSON-RPC format: `{jsonrpc, id, error: {code, message, data?}}`.
80
+ *
81
+ * The RouteSpecs use `auth: {type: 'none'}` because auth is checked per-action
82
+ * inside the dispatcher, and `transaction: false` because transaction scope
83
+ * is per-action (mutations get a transaction, reads get pool).
84
+ *
85
+ * @param options - endpoint path, actions, and logger
86
+ * @returns route specs (GET + POST) ready for `apply_route_specs`
87
+ */
88
+ export declare const create_rpc_endpoint: (options: CreateRpcEndpointOptions) => Array<RouteSpec>;
89
+ //# sourceMappingURL=action_rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_rpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAoB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAgC,KAAK,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAWpC;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC7B,+DAA+D;IAC/D,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,8DAA8D;IAC9D,EAAE,EAAE,EAAE,CAAC;IACP,oFAAoF;IACpF,aAAa,EAAE,EAAE,CAAC;IAClB,2EAA2E;IAC3E,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,CACxD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,KACd,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,aAAa,CAAC;CACvB;AAED,yCAAyC;AACzC,MAAM,WAAW,wBAAwB;IACxC,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;CACZ;AA4CD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,wBAAwB,KAAG,KAAK,CAAC,SAAS,CA6NtF,CAAC"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Single JSON-RPC 2.0 endpoint from action specs.
3
+ *
4
+ * `create_rpc_endpoint` produces `RouteSpec[]` (GET + POST on one path)
5
+ * with an internal dispatcher. Method name lives in the JSON-RPC envelope
6
+ * (POST body or GET query string), not the URL. Auth is checked per-action
7
+ * inside the dispatcher.
8
+ *
9
+ * Handler signature: `(input: TInput, ctx: ActionContext) => TOutput`
10
+ * where `ActionContext` provides auth identity, DB, and framework context.
11
+ *
12
+ * @module
13
+ */
14
+ import { z } from 'zod';
15
+ import { DEV } from 'esm-env';
16
+ import {} from '../http/route_spec.js';
17
+ import { get_request_context, has_role } from '../auth/request_context.js';
18
+ import { is_null_schema } from '../http/schema_helpers.js';
19
+ import { JSONRPC_VERSION, JsonrpcRequest } from '../http/jsonrpc.js';
20
+ import { jsonrpc_error_messages, jsonrpc_error_code_to_http_status, JSONRPC_ERROR_CODES, ThrownJsonrpcError, } from '../http/jsonrpc_errors.js';
21
+ /**
22
+ * Format a JSON-RPC error response.
23
+ *
24
+ * @param id - the request id (null if unknown)
25
+ * @param error - the error object
26
+ * @returns a JSON-RPC error response object
27
+ */
28
+ const jsonrpc_error_response = (id, error) => ({
29
+ jsonrpc: JSONRPC_VERSION,
30
+ id,
31
+ error,
32
+ });
33
+ /**
34
+ * Check auth for an action spec against the request context.
35
+ *
36
+ * @param auth - the action's auth requirement
37
+ * @param request_context - the resolved identity (null if unauthenticated)
38
+ * @returns an error json if auth fails, or null if authorized
39
+ */
40
+ const check_action_auth = (auth, request_context) => {
41
+ if (auth === 'public')
42
+ return null;
43
+ if (!request_context)
44
+ return jsonrpc_error_messages.unauthenticated();
45
+ if (auth === 'authenticated')
46
+ return null;
47
+ if (auth === 'keeper') {
48
+ // keeper requires the keeper role
49
+ if (!has_role(request_context, 'keeper'))
50
+ return jsonrpc_error_messages.forbidden();
51
+ return null;
52
+ }
53
+ // role check
54
+ if (!has_role(request_context, auth.role)) {
55
+ return jsonrpc_error_messages.forbidden(`requires role: ${auth.role}`);
56
+ }
57
+ return null;
58
+ };
59
+ /**
60
+ * Single JSON-RPC 2.0 endpoint — the canonical RPC transport binding.
61
+ *
62
+ * Returns two `RouteSpec` entries (GET + POST on the same path) for
63
+ * `apply_route_specs`. The internal dispatcher handles:
64
+ *
65
+ * 1. **Parse envelope** — POST: JSON body as `JsonrpcRequest`. GET: `method`
66
+ * and `params` from query string.
67
+ * 2. **Lookup method** — find the `RpcAction` by method name.
68
+ * 3. **Auth check** — verify identity against the action's `auth` requirement.
69
+ * 4. **Validate params** — parse input against the action's `input` schema.
70
+ * 5. **Dispatch** — acquire DB handle (transaction for mutations, pool for reads),
71
+ * construct `ActionContext`, call handler, return JSON-RPC response.
72
+ *
73
+ * GET is restricted to `side_effects: false` actions (cacheable reads).
74
+ * All errors use JSON-RPC format: `{jsonrpc, id, error: {code, message, data?}}`.
75
+ *
76
+ * The RouteSpecs use `auth: {type: 'none'}` because auth is checked per-action
77
+ * inside the dispatcher, and `transaction: false` because transaction scope
78
+ * is per-action (mutations get a transaction, reads get pool).
79
+ *
80
+ * @param options - endpoint path, actions, and logger
81
+ * @returns route specs (GET + POST) ready for `apply_route_specs`
82
+ */
83
+ export const create_rpc_endpoint = (options) => {
84
+ const { path: endpoint_path, actions, log } = options;
85
+ // build action lookup map
86
+ const action_map = new Map();
87
+ for (const action of actions) {
88
+ if (action_map.has(action.spec.method)) {
89
+ throw new Error(`Duplicate RPC action method: ${action.spec.method}`);
90
+ }
91
+ action_map.set(action.spec.method, action);
92
+ }
93
+ /**
94
+ * Core dispatcher — shared by GET and POST handlers.
95
+ *
96
+ * @param c - Hono context
97
+ * @param route - route context with db and pending_effects
98
+ * @param method_name - the JSON-RPC method name
99
+ * @param raw_params - the raw params (parsed from body or query string)
100
+ * @param id - the request id
101
+ * @param restrict_to_reads - true for GET requests (reject side_effects actions)
102
+ * @returns a Response
103
+ */
104
+ const dispatch = async (c, route, method_name, raw_params, id, restrict_to_reads) => {
105
+ // step 2: lookup method
106
+ const action = action_map.get(method_name);
107
+ if (!action) {
108
+ const error = jsonrpc_error_response(id, jsonrpc_error_messages.method_not_found(method_name));
109
+ return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.method_not_found));
110
+ }
111
+ // GET restriction: only side_effects:false actions
112
+ if (restrict_to_reads && action.spec.side_effects) {
113
+ const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_request({
114
+ reason: `method '${method_name}' has side effects and must use POST`,
115
+ }));
116
+ return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.invalid_request));
117
+ }
118
+ // step 3: auth check
119
+ const request_context = get_request_context(c);
120
+ const auth_error = check_action_auth(action.spec.auth, request_context);
121
+ if (auth_error) {
122
+ const error = jsonrpc_error_response(id, auth_error);
123
+ return c.json(error, jsonrpc_error_code_to_http_status(auth_error.code));
124
+ }
125
+ // step 4: validate params
126
+ const params = raw_params ?? (is_null_schema(action.spec.input) ? null : undefined);
127
+ const parse_result = action.spec.input.safeParse(params);
128
+ if (!parse_result.success) {
129
+ const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_params('invalid params', {
130
+ issues: parse_result.error.issues,
131
+ }));
132
+ return c.json(error, jsonrpc_error_code_to_http_status(JSONRPC_ERROR_CODES.invalid_params));
133
+ }
134
+ // step 5: dispatch — transaction for mutations, pool for reads
135
+ const use_transaction = action.spec.side_effects;
136
+ const execute = async (db) => {
137
+ const action_context = {
138
+ auth: request_context,
139
+ db,
140
+ background_db: route.background_db,
141
+ pending_effects: route.pending_effects,
142
+ log,
143
+ };
144
+ const output = await action.handler(parse_result.data, action_context);
145
+ // DEV-only output validation
146
+ if (DEV) {
147
+ const output_result = action.spec.output.safeParse(output);
148
+ if (!output_result.success) {
149
+ log.warn(`RPC output schema mismatch: ${method_name}`, output_result.error.issues);
150
+ }
151
+ }
152
+ return c.json({ jsonrpc: JSONRPC_VERSION, id, result: output });
153
+ };
154
+ // error handling wraps the transaction boundary so handler throws
155
+ // cause rollback before the error is formatted as a JSON-RPC response
156
+ try {
157
+ if (use_transaction) {
158
+ return await route.db.transaction((tx) => execute(tx));
159
+ }
160
+ return await execute(route.db);
161
+ }
162
+ catch (err) {
163
+ if (err instanceof ThrownJsonrpcError) {
164
+ const status = jsonrpc_error_code_to_http_status(err.code);
165
+ const error_json = { code: err.code, message: err.message };
166
+ if (err.data !== undefined)
167
+ error_json.data = err.data;
168
+ return c.json(jsonrpc_error_response(id, error_json), status);
169
+ }
170
+ // generic error
171
+ log.error(`Unhandled RPC handler error: ${method_name}`, err);
172
+ const message = DEV && err instanceof Error ? err.message : 'internal server error';
173
+ return c.json(jsonrpc_error_response(id, jsonrpc_error_messages.internal_error(message)), 500);
174
+ }
175
+ };
176
+ // POST handler — parse JSON-RPC envelope from body
177
+ const post_handler = async (c, route) => {
178
+ // step 1: parse envelope
179
+ let body;
180
+ try {
181
+ body = await c.req.json();
182
+ }
183
+ catch {
184
+ const error = jsonrpc_error_response(null, jsonrpc_error_messages.parse_error());
185
+ return c.json(error, 400);
186
+ }
187
+ const envelope = JsonrpcRequest.safeParse(body);
188
+ if (!envelope.success) {
189
+ // try to extract id even from an invalid envelope
190
+ const raw_id = typeof body === 'object' && body !== null && 'id' in body
191
+ ? body.id
192
+ : null;
193
+ const id = typeof raw_id === 'string' || typeof raw_id === 'number' ? raw_id : null;
194
+ const error = jsonrpc_error_response(id, jsonrpc_error_messages.invalid_request({ issues: envelope.error.issues }));
195
+ return c.json(error, 400);
196
+ }
197
+ return dispatch(c, route, envelope.data.method, envelope.data.params, envelope.data.id, false);
198
+ };
199
+ // GET handler — extract method and params from query string
200
+ const get_handler = async (c, route) => {
201
+ // step 1: parse from query string
202
+ const method_name = c.req.query('method');
203
+ if (!method_name) {
204
+ const error = jsonrpc_error_response(null, jsonrpc_error_messages.invalid_request({ reason: 'missing method query parameter' }));
205
+ return c.json(error, 400);
206
+ }
207
+ const id_raw = c.req.query('id');
208
+ if (!id_raw) {
209
+ const error = jsonrpc_error_response(null, jsonrpc_error_messages.invalid_request({ reason: 'missing id query parameter' }));
210
+ return c.json(error, 400);
211
+ }
212
+ // parse params from query string (optional — null input schemas need no params)
213
+ const params_raw = c.req.query('params');
214
+ let params;
215
+ if (params_raw !== undefined) {
216
+ try {
217
+ params = JSON.parse(params_raw);
218
+ }
219
+ catch {
220
+ const error = jsonrpc_error_response(id_raw, jsonrpc_error_messages.invalid_params('params query parameter is not valid JSON'));
221
+ return c.json(error, 400);
222
+ }
223
+ }
224
+ return dispatch(c, route, method_name, params, id_raw, true);
225
+ };
226
+ return [
227
+ {
228
+ method: 'POST',
229
+ path: endpoint_path,
230
+ auth: { type: 'none' }, // per-action auth inside dispatcher
231
+ handler: post_handler,
232
+ description: `JSON-RPC 2.0 endpoint — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
233
+ input: z.null(), // dispatcher owns body parsing; rpc_endpoints surface has the real schemas
234
+ output: z.any(), // varies by method
235
+ transaction: false, // per-action inside dispatcher
236
+ },
237
+ {
238
+ method: 'GET',
239
+ path: endpoint_path,
240
+ auth: { type: 'none' }, // per-action auth inside dispatcher
241
+ handler: get_handler,
242
+ description: `JSON-RPC 2.0 endpoint (cacheable reads) — ${actions.length} method${actions.length === 1 ? '' : 's'}`,
243
+ input: z.null(), // params from query string, validated by dispatcher
244
+ output: z.any(), // varies by method
245
+ transaction: false, // per-action inside dispatcher
246
+ },
247
+ ];
248
+ };
@@ -29,7 +29,7 @@ export declare const ActionAuth: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.
29
29
  role: z.ZodString;
30
30
  }, z.core.$strict>]>;
31
31
  export type ActionAuth = z.infer<typeof ActionAuth>;
32
- export declare const ActionSideEffects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
32
+ export declare const ActionSideEffects: z.ZodBoolean;
33
33
  export type ActionSideEffects = z.infer<typeof ActionSideEffects>;
34
34
  export declare const ActionSpec: z.ZodObject<{
35
35
  method: z.ZodString;
@@ -46,7 +46,7 @@ export declare const ActionSpec: z.ZodObject<{
46
46
  auth: z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"authenticated">, z.ZodLiteral<"keeper">, z.ZodObject<{
47
47
  role: z.ZodString;
48
48
  }, z.core.$strict>]>>;
49
- side_effects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
49
+ side_effects: z.ZodBoolean;
50
50
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
51
51
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
52
52
  async: z.ZodBoolean;
@@ -60,7 +60,7 @@ export declare const RequestResponseActionSpec: z.ZodObject<{
60
60
  frontend: "frontend";
61
61
  backend: "backend";
62
62
  }>;
63
- side_effects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
63
+ side_effects: z.ZodBoolean;
64
64
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
65
65
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
66
66
  description: z.ZodString;
@@ -82,7 +82,7 @@ export declare const RemoteNotificationActionSpec: z.ZodObject<{
82
82
  description: z.ZodString;
83
83
  kind: z.ZodDefault<z.ZodLiteral<"remote_notification">>;
84
84
  auth: z.ZodDefault<z.ZodNull>;
85
- side_effects: z.ZodDefault<z.ZodNullable<z.ZodLiteral<true>>>;
85
+ side_effects: z.ZodDefault<z.ZodLiteral<true>>;
86
86
  output: z.ZodCustom<z.ZodVoid, z.ZodVoid>;
87
87
  async: z.ZodDefault<z.ZodLiteral<true>>;
88
88
  }, z.core.$strict>;
@@ -98,7 +98,7 @@ export declare const LocalCallActionSpec: z.ZodObject<{
98
98
  frontend: "frontend";
99
99
  backend: "backend";
100
100
  }>;
101
- side_effects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
101
+ side_effects: z.ZodBoolean;
102
102
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
103
103
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
104
104
  async: z.ZodBoolean;
@@ -114,7 +114,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
114
114
  frontend: "frontend";
115
115
  backend: "backend";
116
116
  }>;
117
- side_effects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
117
+ side_effects: z.ZodBoolean;
118
118
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
119
119
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
120
120
  description: z.ZodString;
@@ -134,7 +134,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
134
134
  description: z.ZodString;
135
135
  kind: z.ZodDefault<z.ZodLiteral<"remote_notification">>;
136
136
  auth: z.ZodDefault<z.ZodNull>;
137
- side_effects: z.ZodDefault<z.ZodNullable<z.ZodLiteral<true>>>;
137
+ side_effects: z.ZodDefault<z.ZodLiteral<true>>;
138
138
  output: z.ZodCustom<z.ZodVoid, z.ZodVoid>;
139
139
  async: z.ZodDefault<z.ZodLiteral<true>>;
140
140
  }, z.core.$strict>, z.ZodObject<{
@@ -144,7 +144,7 @@ export declare const ActionSpecUnion: z.ZodUnion<readonly [z.ZodObject<{
144
144
  frontend: "frontend";
145
145
  backend: "backend";
146
146
  }>;
147
- side_effects: z.ZodUnion<readonly [z.ZodLiteral<true>, z.ZodNull]>;
147
+ side_effects: z.ZodBoolean;
148
148
  input: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
149
149
  output: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
150
150
  async: z.ZodBoolean;
@@ -1 +1 @@
1
- {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,UAAU;;oBAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,sDAAuC,CAAC;AACtE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;kBAUrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"action_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/action_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,UAAU;;;;EAAoE,CAAC;AAC5F,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,eAAe;;;;EAA0C,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,UAAU;;oBAKrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;kBAUrB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;kBAIpC,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;kBAMvC,CAAC;AACH,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAExF;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;kBAG9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,eAKoB,CAAC;AAE9E,eAAO,MAAM,gBAAgB;;;;;;;;;;EAU3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -21,7 +21,7 @@ export const ActionAuth = z.union([
21
21
  z.literal('keeper'),
22
22
  z.strictObject({ role: z.string() }),
23
23
  ]);
24
- export const ActionSideEffects = z.union([z.literal(true), z.null()]);
24
+ export const ActionSideEffects = z.boolean();
25
25
  export const ActionSpec = z.strictObject({
26
26
  method: z.string(),
27
27
  kind: ActionKind,
@@ -41,7 +41,7 @@ export const RequestResponseActionSpec = ActionSpec.extend({
41
41
  export const RemoteNotificationActionSpec = ActionSpec.extend({
42
42
  kind: z.literal('remote_notification').default('remote_notification'),
43
43
  auth: z.null().default(null),
44
- side_effects: z.literal(true).nullable().default(true),
44
+ side_effects: z.literal(true).default(true),
45
45
  output: z.custom((v) => v instanceof z.ZodVoid),
46
46
  async: z.literal(true).default(true),
47
47
  });
@@ -1 +1 @@
1
- {"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CAqN9E,CAAC"}
1
+ {"version":3,"file":"db_routes.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/db_routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAmB,KAAK,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,SAAS,cAAc,KAAG,KAAK,CAAC,SAAS,CAuN9E,CAAC"}
@@ -57,7 +57,9 @@ export const create_db_route_specs = (options) => {
57
57
  auth: { type: 'keeper' },
58
58
  description: 'List public tables with row counts',
59
59
  input: z.null(),
60
- output: z.looseObject({ tables: z.array(z.object({ name: z.string(), row_count: z.number() })) }),
60
+ output: z.looseObject({
61
+ tables: z.array(z.strictObject({ name: z.string(), row_count: z.number() })),
62
+ }),
61
63
  handler: async (c, route) => {
62
64
  const table_names = await route.db.query(`SELECT table_name FROM information_schema.tables
63
65
  WHERE table_schema = 'public'
@@ -83,7 +85,7 @@ export const create_db_route_specs = (options) => {
83
85
  input: z.null(),
84
86
  errors: { 404: z.looseObject({ error: z.literal(ERROR_TABLE_NOT_FOUND) }) },
85
87
  output: z.looseObject({
86
- columns: z.array(z.object({ column_name: z.string(), data_type: z.string(), is_nullable: z.string() })),
88
+ columns: z.array(z.strictObject({ column_name: z.string(), data_type: z.string(), is_nullable: z.string() })),
87
89
  rows: z.array(z.record(z.string(), z.unknown())),
88
90
  total: z.number(),
89
91
  offset: z.number(),
@@ -0,0 +1,62 @@
1
+ /**
2
+ * JSON-RPC 2.0 envelope schemas for the single RPC endpoint dispatcher.
3
+ *
4
+ * Minimal subset extracted from zzz's `jsonrpc.ts` — only what the
5
+ * `create_rpc_endpoint` dispatcher needs to parse incoming requests
6
+ * and format outgoing responses. Full JSON-RPC schemas (batching,
7
+ * MCP extensions, notification types) remain in zzz.
8
+ *
9
+ * Following MCP, params and result are object-only (no positional arrays).
10
+ *
11
+ * @source https://github.com/modelcontextprotocol/typescript-sdk
12
+ * @see https://www.jsonrpc.org/specification
13
+ * @module
14
+ */
15
+ import { z } from 'zod';
16
+ export declare const JSONRPC_VERSION = "2.0";
17
+ /** A uniquely identifying id for a request in JSON-RPC. Like MCP, excludes null. */
18
+ export declare const JsonrpcRequestId: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
19
+ export type JsonrpcRequestId = z.infer<typeof JsonrpcRequestId>;
20
+ /** A JSON-RPC method name. */
21
+ export declare const JsonrpcMethod: z.ZodString;
22
+ export type JsonrpcMethod = z.infer<typeof JsonrpcMethod>;
23
+ /** Request params — loose object to allow additional properties. */
24
+ export declare const JsonrpcRequestParams: z.ZodObject<{}, z.core.$loose>;
25
+ export type JsonrpcRequestParams = z.infer<typeof JsonrpcRequestParams>;
26
+ /** Result — loose object to allow additional properties. */
27
+ export declare const JsonrpcResult: z.ZodObject<{}, z.core.$loose>;
28
+ export type JsonrpcResult = z.infer<typeof JsonrpcResult>;
29
+ /** A request that expects a response. */
30
+ export declare const JsonrpcRequest: z.ZodObject<{
31
+ jsonrpc: z.ZodLiteral<"2.0">;
32
+ id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
33
+ method: z.ZodString;
34
+ params: z.ZodOptional<z.ZodObject<{}, z.core.$loose>>;
35
+ }, z.core.$loose>;
36
+ export type JsonrpcRequest = z.infer<typeof JsonrpcRequest>;
37
+ /** A successful (non-error) response to a request. */
38
+ export declare const JsonrpcResponse: z.ZodObject<{
39
+ jsonrpc: z.ZodLiteral<"2.0">;
40
+ id: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
41
+ result: z.ZodObject<{}, z.core.$loose>;
42
+ }, z.core.$loose>;
43
+ export type JsonrpcResponse = z.infer<typeof JsonrpcResponse>;
44
+ /** Error object within a JSON-RPC error response. */
45
+ export declare const JsonrpcErrorObject: z.ZodObject<{
46
+ code: z.ZodNumber;
47
+ message: z.ZodString;
48
+ data: z.ZodOptional<z.ZodUnknown>;
49
+ }, z.core.$loose>;
50
+ export type JsonrpcErrorObject = z.infer<typeof JsonrpcErrorObject>;
51
+ /** A response that indicates an error occurred. */
52
+ export declare const JsonrpcErrorResponse: z.ZodObject<{
53
+ jsonrpc: z.ZodLiteral<"2.0">;
54
+ id: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
55
+ error: z.ZodObject<{
56
+ code: z.ZodNumber;
57
+ message: z.ZodString;
58
+ data: z.ZodOptional<z.ZodUnknown>;
59
+ }, z.core.$loose>;
60
+ }, z.core.$loose>;
61
+ export type JsonrpcErrorResponse = z.infer<typeof JsonrpcErrorResponse>;
62
+ //# sourceMappingURL=jsonrpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonrpc.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,iDAAoC,CAAC;AAClE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE,8BAA8B;AAC9B,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,gCAAoB,CAAC;AACtD,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,4DAA4D;AAC5D,eAAO,MAAM,aAAa,gCAAoB,CAAC;AAC/C,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,yCAAyC;AACzC,eAAO,MAAM,cAAc;;;;;iBAKzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,sDAAsD;AACtD,eAAO,MAAM,eAAe;;;;iBAI1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,qDAAqD;AACrD,eAAO,MAAM,kBAAkB;;;;iBAI7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEpE,mDAAmD;AACnD,eAAO,MAAM,oBAAoB;;;;;;;;iBAI/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}