@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.
- package/dist/actions/action_bridge.d.ts +3 -3
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +5 -4
- package/dist/actions/action_rpc.d.ts +89 -0
- package/dist/actions/action_rpc.d.ts.map +1 -0
- package/dist/actions/action_rpc.js +248 -0
- package/dist/actions/action_spec.d.ts +8 -8
- package/dist/actions/action_spec.d.ts.map +1 -1
- package/dist/actions/action_spec.js +2 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +4 -2
- package/dist/http/jsonrpc.d.ts +62 -0
- package/dist/http/jsonrpc.d.ts.map +1 -0
- package/dist/http/jsonrpc.js +49 -0
- package/dist/http/jsonrpc_errors.d.ts +132 -0
- package/dist/http/jsonrpc_errors.d.ts.map +1 -0
- package/dist/http/jsonrpc_errors.js +197 -0
- package/dist/http/route_spec.d.ts +2 -1
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +43 -7
- package/dist/http/schema_helpers.d.ts +3 -3
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +5 -10
- package/dist/http/surface.d.ts +25 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +16 -1
- package/dist/server/app_server.d.ts +3 -1
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +2 -1
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +22 -7
- package/dist/testing/stubs.d.ts +3 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +2 -1
- package/dist/testing/surface_invariants.d.ts +4 -0
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +4 -0
- 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 `
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
|
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.
|
|
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).
|
|
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,
|
|
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"}
|
package/dist/http/db_routes.js
CHANGED
|
@@ -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({
|
|
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.
|
|
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"}
|