silgi 0.53.0 → 0.53.1
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/builder.mjs +32 -6
- package/dist/caller.mjs +65 -55
- package/dist/compile.d.mts +15 -8
- package/dist/compile.mjs +157 -142
- package/dist/core/handler.d.mts +3 -3
- package/dist/core/handler.mjs +69 -73
- package/dist/core/input.mjs +95 -33
- package/dist/core/schema-converter.d.mts +68 -63
- package/dist/core/schema-converter.mjs +85 -56
- package/dist/core/serve.d.mts +18 -17
- package/dist/core/serve.mjs +154 -64
- package/dist/core/sse.d.mts +5 -6
- package/dist/core/sse.mjs +86 -46
- package/dist/core/task.d.mts +15 -4
- package/dist/core/task.mjs +160 -76
- package/dist/plugins/cache.d.mts +62 -126
- package/dist/plugins/cache.mjs +146 -128
- package/dist/scalar.d.mts +24 -13
- package/dist/scalar.mjs +292 -201
- package/dist/silgi.mjs +160 -117
- package/dist/ws.d.mts +26 -27
- package/dist/ws.mjs +126 -87
- package/package.json +1 -1
package/dist/builder.mjs
CHANGED
|
@@ -12,6 +12,25 @@ import { createTaskFromProcedure } from "./core/task.mjs";
|
|
|
12
12
|
* // ← autocomplete suggests id, title, body
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* A `ProcBuilder` plays two roles in its lifetime:
|
|
17
|
+
*
|
|
18
|
+
* 1. While the user is chaining methods it is a *builder* — each `$x()`
|
|
19
|
+
* mutates a slot and returns `this`.
|
|
20
|
+
* 2. Once `$resolve()` is called, the *same instance* is returned typed
|
|
21
|
+
* as `ProcedureDef`. This works because `isProcedureDef` (see
|
|
22
|
+
* `core/router-utils.ts`) only checks for `type` and a callable
|
|
23
|
+
* `resolve`, both of which we now have.
|
|
24
|
+
*
|
|
25
|
+
* Using one object for both roles avoids copying the slots into a fresh
|
|
26
|
+
* frozen record at the end — callers hold the object directly, so the
|
|
27
|
+
* shape they see is the same object we wrote into.
|
|
28
|
+
*
|
|
29
|
+
* All slots default to `null` rather than an empty array / object so
|
|
30
|
+
* that downstream code can branch on presence without caring about
|
|
31
|
+
* length, and so that we do not allocate sentinels the user never ends
|
|
32
|
+
* up needing.
|
|
33
|
+
*/
|
|
15
34
|
var ProcBuilder = class {
|
|
16
35
|
type;
|
|
17
36
|
input = null;
|
|
@@ -21,14 +40,21 @@ var ProcBuilder = class {
|
|
|
21
40
|
resolve = null;
|
|
22
41
|
route = null;
|
|
23
42
|
meta = null;
|
|
43
|
+
/**
|
|
44
|
+
* Underscore-prefixed slots are framework-internal. They are set by
|
|
45
|
+
* `createProcedureBuilder` when the builder is constructed through a
|
|
46
|
+
* `silgi({...})` instance and are threaded through to `$task()` so
|
|
47
|
+
* that background tasks share the instance's context factory and
|
|
48
|
+
* root wraps.
|
|
49
|
+
*/
|
|
24
50
|
_contextFactory = null;
|
|
25
51
|
_rootWrapsGetter = null;
|
|
26
52
|
constructor(type) {
|
|
27
53
|
this.type = type;
|
|
28
54
|
}
|
|
29
55
|
$use(...middleware) {
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
this.use ??= [];
|
|
57
|
+
this.use.push(...middleware);
|
|
32
58
|
return this;
|
|
33
59
|
}
|
|
34
60
|
$input(schema) {
|
|
@@ -63,10 +89,10 @@ var ProcBuilder = class {
|
|
|
63
89
|
}
|
|
64
90
|
};
|
|
65
91
|
function createProcedureBuilder(type, contextFactory, rootWrapsGetter) {
|
|
66
|
-
const
|
|
67
|
-
if (contextFactory)
|
|
68
|
-
if (rootWrapsGetter)
|
|
69
|
-
return
|
|
92
|
+
const builder = new ProcBuilder(type);
|
|
93
|
+
if (contextFactory) builder._contextFactory = contextFactory;
|
|
94
|
+
if (rootWrapsGetter) builder._rootWrapsGetter = rootWrapsGetter;
|
|
95
|
+
return builder;
|
|
70
96
|
}
|
|
71
97
|
//#endregion
|
|
72
98
|
export { createProcedureBuilder };
|
package/dist/caller.mjs
CHANGED
|
@@ -1,96 +1,106 @@
|
|
|
1
1
|
import { routerCache } from "./core/router-utils.mjs";
|
|
2
|
-
import { compileRouter
|
|
2
|
+
import { compileRouter } from "./compile.mjs";
|
|
3
3
|
import { applyContext } from "./core/dispatch.mjs";
|
|
4
4
|
//#region src/caller.ts
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Direct caller
|
|
7
|
+
* --------------
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* `createCaller` returns a proxy that mirrors the router's nested shape.
|
|
10
|
+
* Calling a leaf procedure invokes the compiled pipeline directly — no
|
|
11
|
+
* HTTP, no body serialization, no response encoding. This is what tests
|
|
12
|
+
* and server-side orchestration code use.
|
|
10
13
|
*
|
|
11
14
|
* @example
|
|
12
|
-
*
|
|
13
|
-
*
|
|
15
|
+
* const caller = s.createCaller(appRouter)
|
|
16
|
+
* const users = await caller.users.list({ limit: 10 })
|
|
17
|
+
* const user = await caller.users.get({ id: 1 })
|
|
14
18
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* // With custom context override
|
|
20
|
-
* const adminCaller = s.createCaller(appRouter, {
|
|
21
|
-
* contextOverride: { user: { id: 1, role: 'admin' } },
|
|
22
|
-
* })
|
|
23
|
-
* ```
|
|
19
|
+
* // Override the context for this caller (e.g. for admin tests):
|
|
20
|
+
* const admin = s.createCaller(appRouter, {
|
|
21
|
+
* contextOverride: { user: { id: 1, role: 'admin' } },
|
|
22
|
+
* })
|
|
24
23
|
*/
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
25
|
+
* Placeholder signal for callers that opt out of timeouts (`timeout: null`)
|
|
26
|
+
* and do not pass their own signal. We still hand the pipeline a real
|
|
27
|
+
* `AbortSignal` so that user code doing `signal.addEventListener('abort', …)`
|
|
28
|
+
* does not crash on `undefined` — this signal just never fires.
|
|
30
29
|
*/
|
|
31
|
-
const
|
|
30
|
+
const NEVER_ABORTS = new AbortController().signal;
|
|
32
31
|
/**
|
|
33
|
-
*
|
|
32
|
+
* Build a direct caller for a router.
|
|
34
33
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* The returned value is a proxy that mirrors the router tree: accessing
|
|
35
|
+
* `caller.users.list` returns another proxy; calling it at the leaf
|
|
36
|
+
* dispatches to the compiled pipeline for that procedure.
|
|
37
37
|
*/
|
|
38
38
|
function createCaller(routerDef, contextFactory, options) {
|
|
39
|
-
let
|
|
40
|
-
if (!
|
|
41
|
-
|
|
42
|
-
routerCache.set(routerDef,
|
|
39
|
+
let compiled = routerCache.get(routerDef);
|
|
40
|
+
if (!compiled) {
|
|
41
|
+
compiled = compileRouter(routerDef);
|
|
42
|
+
routerCache.set(routerDef, compiled);
|
|
43
43
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
const defaultTimeoutMs = options?.timeout !== void 0 ? options.timeout : 3e4;
|
|
45
|
+
/**
|
|
46
|
+
* Construct a mock `Request` to feed into the user's context factory.
|
|
47
|
+
* Tests rarely care about the URL — only the headers matter, because
|
|
48
|
+
* that is the surface most factories actually read.
|
|
49
|
+
*/
|
|
50
|
+
const mockRequest = (extraHeaders) => {
|
|
47
51
|
const headers = new Headers(options?.headers);
|
|
48
|
-
if (extraHeaders) for (const [
|
|
52
|
+
if (extraHeaders) for (const [key, value] of Object.entries(extraHeaders)) headers.set(key, value);
|
|
49
53
|
return new Request("http://localhost/__caller", { headers });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Build a fresh context for a single call. Layers are applied in the
|
|
57
|
+
* order they would be on the HTTP path: context factory → caller-level
|
|
58
|
+
* `contextOverride` → per-call override.
|
|
59
|
+
*/
|
|
60
|
+
const buildContext = async (perCallContext) => {
|
|
61
|
+
const ctx = Object.create(null);
|
|
62
|
+
if (contextFactory) applyContext(ctx, await contextFactory(mockRequest()));
|
|
57
63
|
if (options?.contextOverride) applyContext(ctx, options.contextOverride);
|
|
58
64
|
if (perCallContext) applyContext(ctx, perCallContext);
|
|
59
65
|
return ctx;
|
|
60
|
-
}
|
|
61
|
-
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Build a proxy node for the current `segments` path. Each property
|
|
69
|
+
* access descends one level; each function call dispatches to the
|
|
70
|
+
* compiled pipeline.
|
|
71
|
+
*
|
|
72
|
+
* The `cache` map ensures repeated property access (e.g.
|
|
73
|
+
* `caller.users.list`) returns the same proxy object so equality
|
|
74
|
+
* checks and `.bind` in user tests stay stable.
|
|
75
|
+
*/
|
|
76
|
+
const createProxy = (segments) => {
|
|
62
77
|
const cache = /* @__PURE__ */ new Map();
|
|
63
78
|
return new Proxy(() => {}, {
|
|
64
79
|
get(_target, prop) {
|
|
65
80
|
if (typeof prop === "symbol") return void 0;
|
|
66
81
|
if (prop === "then" || prop === "toJSON" || prop === "toString" || prop === "$$typeof") return;
|
|
67
|
-
let
|
|
68
|
-
if (!
|
|
69
|
-
|
|
70
|
-
cache.set(prop,
|
|
82
|
+
let child = cache.get(prop);
|
|
83
|
+
if (!child) {
|
|
84
|
+
child = createProxy([...segments, prop]);
|
|
85
|
+
cache.set(prop, child);
|
|
71
86
|
}
|
|
72
|
-
return
|
|
87
|
+
return child;
|
|
73
88
|
},
|
|
74
89
|
apply(_target, _thisArg, args) {
|
|
75
90
|
const path = "/" + segments.join("/");
|
|
76
91
|
const input = args[0];
|
|
77
92
|
const callOptions = args[1];
|
|
78
93
|
return (async () => {
|
|
79
|
-
const match =
|
|
94
|
+
const match = compiled("", path);
|
|
80
95
|
if (!match) throw new Error(`Procedure not found: ${path}`);
|
|
81
|
-
const ctx = await
|
|
96
|
+
const ctx = await buildContext(callOptions?.context);
|
|
82
97
|
if (match.params) ctx.params = match.params;
|
|
83
|
-
const signal = callOptions?.signal ?? (
|
|
84
|
-
|
|
85
|
-
const result = match.data.handler(ctx, input, signal);
|
|
86
|
-
return result instanceof Promise ? await result : result;
|
|
87
|
-
} finally {
|
|
88
|
-
releaseContext(ctx);
|
|
89
|
-
}
|
|
98
|
+
const signal = callOptions?.signal ?? (defaultTimeoutMs !== null ? AbortSignal.timeout(defaultTimeoutMs) : NEVER_ABORTS);
|
|
99
|
+
return await match.data.handler(ctx, input, signal);
|
|
90
100
|
})();
|
|
91
101
|
}
|
|
92
102
|
});
|
|
93
|
-
}
|
|
103
|
+
};
|
|
94
104
|
return createProxy([]);
|
|
95
105
|
}
|
|
96
106
|
//#endregion
|
package/dist/compile.d.mts
CHANGED
|
@@ -2,8 +2,9 @@ import { ProcedureDef, WrapDef } from "./types.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/compile.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Compiled
|
|
6
|
-
*
|
|
5
|
+
* Compiled request handler. May return a sync value or a `Promise` —
|
|
6
|
+
* adapters branch on `instanceof Promise` for the fast path when the
|
|
7
|
+
* resolver and guards were all synchronous.
|
|
7
8
|
*/
|
|
8
9
|
type CompiledHandler = (ctx: Record<string, unknown>, rawInput: unknown, signal: AbortSignal) => unknown | Promise<unknown>;
|
|
9
10
|
/**
|
|
@@ -39,15 +40,21 @@ type CompiledRouterFn = (method: string, path: string) => MatchedRoute<CompiledR
|
|
|
39
40
|
*/
|
|
40
41
|
declare function compileRouter(def: Record<string, unknown>): CompiledRouterFn;
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
-
* `releaseContext` unless ownership has been transferred elsewhere
|
|
44
|
-
* (e.g. to a streaming Response) — in that case the new owner is
|
|
45
|
-
* expected to call `releaseContext` when the stream ends.
|
|
43
|
+
* Disposable wrapper around the pipeline context.
|
|
46
44
|
*
|
|
47
|
-
*
|
|
45
|
+
* Adapters use `using ctx = createContext()` so the context is released
|
|
46
|
+
* automatically at scope exit — unless ownership has been transferred
|
|
47
|
+
* elsewhere (e.g. to a streaming `Response` that keeps reading from
|
|
48
|
+
* `ctx` after the handler returns). In that case the handler calls
|
|
49
|
+
* `detachContext(ctx)` and the new owner is responsible for cleanup.
|
|
48
50
|
*/
|
|
49
51
|
type PooledContext = Record<string, unknown> & Disposable;
|
|
50
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Acquire a context object — from the pool when one is available,
|
|
54
|
+
* otherwise a fresh null-prototype object. Null-prototype keeps user
|
|
55
|
+
* keys from colliding with `Object.prototype` members and avoids a
|
|
56
|
+
* prototype-chain walk on every property lookup.
|
|
57
|
+
*/
|
|
51
58
|
declare function createContext(): PooledContext;
|
|
52
59
|
//#endregion
|
|
53
60
|
export { compileProcedure, compileRouter, createContext };
|