silgi 0.0.13 → 0.1.0-beta.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/README.md +102 -1
- package/dist/_virtual/_rolldown/runtime.mjs +5 -0
- package/dist/adapters/astro.d.mts +17 -0
- package/dist/adapters/astro.mjs +24 -0
- package/dist/adapters/aws-lambda.d.mts +31 -0
- package/dist/adapters/aws-lambda.mjs +85 -0
- package/dist/adapters/elysia.d.mts +17 -0
- package/dist/adapters/elysia.mjs +76 -0
- package/dist/adapters/express.d.mts +16 -0
- package/dist/adapters/express.mjs +78 -0
- package/dist/adapters/fastify.d.mts +15 -0
- package/dist/adapters/fastify.mjs +78 -0
- package/dist/adapters/message-port.d.mts +37 -0
- package/dist/adapters/message-port.mjs +129 -0
- package/dist/adapters/nestjs.d.mts +25 -0
- package/dist/adapters/nestjs.mjs +91 -0
- package/dist/adapters/nextjs.d.mts +21 -0
- package/dist/adapters/nextjs.mjs +30 -0
- package/dist/adapters/peer.d.mts +27 -0
- package/dist/adapters/peer.mjs +36 -0
- package/dist/adapters/remix.d.mts +17 -0
- package/dist/adapters/remix.mjs +24 -0
- package/dist/adapters/solidstart.d.mts +14 -0
- package/dist/adapters/solidstart.mjs +30 -0
- package/dist/adapters/sveltekit.d.mts +18 -0
- package/dist/adapters/sveltekit.mjs +33 -0
- package/dist/analyze.mjs +26 -0
- package/dist/broker/index.d.mts +62 -0
- package/dist/broker/index.mjs +153 -0
- package/dist/broker/nats.d.mts +33 -0
- package/dist/broker/nats.mjs +31 -0
- package/dist/broker/redis.d.mts +51 -0
- package/dist/broker/redis.mjs +92 -0
- package/dist/builder.d.mts +36 -0
- package/dist/builder.mjs +51 -0
- package/dist/callable.d.mts +17 -0
- package/dist/callable.mjs +42 -0
- package/dist/client/adapters/fetch/index.d.mts +17 -0
- package/dist/client/adapters/fetch/index.mjs +61 -0
- package/dist/client/adapters/ofetch/index.d.mts +41 -0
- package/dist/client/adapters/ofetch/index.mjs +92 -0
- package/dist/client/client.d.mts +29 -0
- package/dist/client/client.mjs +54 -0
- package/dist/client/dynamic-link.d.mts +15 -0
- package/dist/client/dynamic-link.mjs +16 -0
- package/dist/client/index.d.mts +7 -0
- package/dist/client/index.mjs +6 -0
- package/dist/client/interceptor.d.mts +31 -0
- package/dist/client/interceptor.mjs +34 -0
- package/dist/client/merge.d.mts +28 -0
- package/dist/client/merge.mjs +30 -0
- package/dist/client/openapi.d.mts +29 -0
- package/dist/client/openapi.mjs +89 -0
- package/dist/client/plugins/batch.d.mts +20 -0
- package/dist/client/plugins/batch.mjs +64 -0
- package/dist/client/plugins/csrf.d.mts +13 -0
- package/dist/client/plugins/csrf.mjs +20 -0
- package/dist/client/plugins/dedupe.d.mts +10 -0
- package/dist/client/plugins/dedupe.mjs +28 -0
- package/dist/client/plugins/index.d.mts +5 -0
- package/dist/client/plugins/index.mjs +5 -0
- package/dist/client/plugins/retry.d.mts +11 -0
- package/dist/client/plugins/retry.mjs +21 -0
- package/dist/client/server.d.mts +16 -0
- package/dist/client/server.mjs +60 -0
- package/dist/client/types.d.mts +29 -0
- package/dist/codec/devalue.d.mts +21 -0
- package/dist/codec/devalue.mjs +32 -0
- package/dist/codec/msgpack.d.mts +21 -0
- package/dist/codec/msgpack.mjs +59 -0
- package/dist/compile.d.mts +52 -0
- package/dist/compile.mjs +304 -0
- package/dist/contract.d.mts +36 -0
- package/dist/contract.mjs +40 -0
- package/dist/core/error.d.mts +104 -0
- package/dist/core/error.mjs +139 -0
- package/dist/core/handler.mjs +546 -0
- package/dist/core/iterator.d.mts +17 -0
- package/dist/core/iterator.mjs +79 -0
- package/dist/core/router-utils.mjs +16 -0
- package/dist/core/schema.d.mts +19 -0
- package/dist/core/schema.mjs +26 -0
- package/dist/core/serve.mjs +38 -0
- package/dist/core/sse.d.mts +16 -0
- package/dist/core/sse.mjs +95 -0
- package/dist/core/storage.d.mts +21 -0
- package/dist/core/storage.mjs +63 -0
- package/dist/core/utils.mjs +21 -0
- package/dist/fast-stringify.mjs +125 -0
- package/dist/index.d.mts +15 -37
- package/dist/index.mjs +13 -7
- package/dist/integrations/ai/index.d.mts +25 -0
- package/dist/integrations/ai/index.mjs +116 -0
- package/dist/integrations/react/index.d.mts +83 -0
- package/dist/integrations/react/index.mjs +197 -0
- package/dist/integrations/tanstack-query/index.d.mts +120 -0
- package/dist/integrations/tanstack-query/index.mjs +100 -0
- package/dist/integrations/tanstack-query/ssr.d.mts +51 -0
- package/dist/integrations/tanstack-query/ssr.mjs +89 -0
- package/dist/integrations/zod/converter.d.mts +75 -0
- package/dist/integrations/zod/converter.mjs +345 -0
- package/dist/integrations/zod/index.d.mts +2 -0
- package/dist/integrations/zod/index.mjs +2 -0
- package/dist/lazy.d.mts +24 -0
- package/dist/lazy.mjs +27 -0
- package/dist/lifecycle.d.mts +36 -0
- package/dist/lifecycle.mjs +46 -0
- package/dist/map-input.d.mts +17 -0
- package/dist/map-input.mjs +24 -0
- package/dist/plugins/analytics.d.mts +168 -0
- package/dist/plugins/analytics.mjs +459 -0
- package/dist/plugins/batch-server.d.mts +20 -0
- package/dist/plugins/batch-server.mjs +86 -0
- package/dist/plugins/body-limit.d.mts +16 -0
- package/dist/plugins/body-limit.mjs +44 -0
- package/dist/plugins/cache.d.mts +170 -0
- package/dist/plugins/cache.mjs +200 -0
- package/dist/plugins/coerce.d.mts +21 -0
- package/dist/plugins/coerce.mjs +46 -0
- package/dist/plugins/compression.d.mts +19 -0
- package/dist/plugins/compression.mjs +23 -0
- package/dist/plugins/cookies.d.mts +44 -0
- package/dist/plugins/cookies.mjs +67 -0
- package/dist/plugins/cors.d.mts +39 -0
- package/dist/plugins/cors.mjs +56 -0
- package/dist/plugins/custom-serializer.d.mts +57 -0
- package/dist/plugins/custom-serializer.mjs +40 -0
- package/dist/plugins/file-upload.d.mts +38 -0
- package/dist/plugins/file-upload.mjs +100 -0
- package/dist/plugins/index.d.mts +16 -0
- package/dist/plugins/index.mjs +16 -0
- package/dist/plugins/otel.d.mts +35 -0
- package/dist/plugins/otel.mjs +40 -0
- package/dist/plugins/pino.d.mts +60 -0
- package/dist/plugins/pino.mjs +42 -0
- package/dist/plugins/pubsub.d.mts +50 -0
- package/dist/plugins/pubsub.mjs +53 -0
- package/dist/plugins/ratelimit.d.mts +51 -0
- package/dist/plugins/ratelimit.mjs +81 -0
- package/dist/plugins/signing.d.mts +41 -0
- package/dist/plugins/signing.mjs +115 -0
- package/dist/plugins/strict-get.d.mts +10 -0
- package/dist/plugins/strict-get.mjs +33 -0
- package/dist/route/add.mjs +240 -0
- package/dist/route/compiler.mjs +373 -0
- package/dist/route/context.mjs +12 -0
- package/dist/route/types.d.mts +11 -0
- package/dist/route/utils.mjs +17 -0
- package/dist/scalar.d.mts +53 -0
- package/dist/scalar.mjs +315 -0
- package/dist/silgi.d.mts +139 -0
- package/dist/silgi.mjs +113 -0
- package/dist/trpc-interop.d.mts +22 -0
- package/dist/trpc-interop.mjs +68 -0
- package/dist/types.d.mts +82 -0
- package/dist/ws.d.mts +42 -0
- package/dist/ws.mjs +137 -0
- package/lib/dashboard/index.html +123 -0
- package/lib/ocache.d.mts +1 -0
- package/lib/ocache.mjs +1 -0
- package/lib/ofetch.d.mts +1 -0
- package/lib/ofetch.mjs +1 -0
- package/lib/srvx.d.mts +1 -0
- package/lib/srvx.mjs +1 -0
- package/lib/unstorage.d.mts +1 -0
- package/lib/unstorage.mjs +1 -0
- package/package.json +291 -65
- package/bin/silgi.mjs +0 -3
- package/dist/chunks/generate.mjs +0 -933
- package/dist/chunks/init.mjs +0 -21
- package/dist/cli/config.d.mts +0 -19
- package/dist/cli/config.d.ts +0 -19
- package/dist/cli/config.mjs +0 -5
- package/dist/cli/index.d.mts +0 -2
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.mjs +0 -119
- package/dist/index.d.ts +0 -37
- package/dist/plugins/openapi.d.mts +0 -138
- package/dist/plugins/openapi.d.ts +0 -138
- package/dist/plugins/openapi.mjs +0 -204
- package/dist/plugins/scalar.d.mts +0 -14
- package/dist/plugins/scalar.d.ts +0 -14
- package/dist/plugins/scalar.mjs +0 -66
- package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
- package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
- package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/client/plugins/retry.ts
|
|
2
|
+
function withRetry(link, options = {}) {
|
|
3
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
4
|
+
const getDelay = typeof options.retryDelay === "function" ? options.retryDelay : () => options.retryDelay ?? 1e3;
|
|
5
|
+
const shouldRetry = options.shouldRetry ?? (() => true);
|
|
6
|
+
return { async call(path, input, callOptions) {
|
|
7
|
+
let lastError;
|
|
8
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
9
|
+
return await link.call(path, input, callOptions);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
lastError = error;
|
|
12
|
+
if (attempt === maxRetries || !shouldRetry(error)) throw error;
|
|
13
|
+
if (callOptions.signal?.aborted) throw error;
|
|
14
|
+
const delay = getDelay(attempt);
|
|
15
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
16
|
+
}
|
|
17
|
+
throw lastError;
|
|
18
|
+
} };
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { withRetry };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InferClient, RouterDef } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/client/server.d.ts
|
|
4
|
+
interface ServerClientOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
+
/** Context factory — called for every procedure call */
|
|
6
|
+
context: () => TCtx | Promise<TCtx>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a type-safe client that calls procedures directly in-process.
|
|
10
|
+
*
|
|
11
|
+
* No HTTP, no serialization, no network — just compiled pipeline execution.
|
|
12
|
+
* Uses the same compiled handlers as serve() and handler().
|
|
13
|
+
*/
|
|
14
|
+
declare function createServerClient<TRouter extends RouterDef, TCtx extends Record<string, unknown>>(router: TRouter, options: ServerClientOptions<TCtx>): InferClient<TRouter>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ServerClientOptions, createServerClient };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { compileRouter } from "../compile.mjs";
|
|
2
|
+
//#region src/client/server.ts
|
|
3
|
+
/**
|
|
4
|
+
* Server-side client — call procedures directly without HTTP.
|
|
5
|
+
*
|
|
6
|
+
* Useful for SSR, server components, and testing where you want the
|
|
7
|
+
* same typed client interface but without network overhead.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createServerClient } from "silgi/client/server"
|
|
12
|
+
*
|
|
13
|
+
* const client = createServerClient(appRouter, {
|
|
14
|
+
* context: () => ({ db: getDB() }),
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // Same typed API as the HTTP client — but runs in-process
|
|
18
|
+
* const users = await client.users.list({ limit: 10 })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Create a type-safe client that calls procedures directly in-process.
|
|
23
|
+
*
|
|
24
|
+
* No HTTP, no serialization, no network — just compiled pipeline execution.
|
|
25
|
+
* Uses the same compiled handlers as serve() and handler().
|
|
26
|
+
*/
|
|
27
|
+
function createServerClient(router, options) {
|
|
28
|
+
return createServerProxy(compileRouter(router), options.context, []);
|
|
29
|
+
}
|
|
30
|
+
function createServerProxy(flatRouter, contextFactory, path) {
|
|
31
|
+
const cache = /* @__PURE__ */ new Map();
|
|
32
|
+
const callProcedure = async (input) => {
|
|
33
|
+
const key = path.join("/");
|
|
34
|
+
const route = flatRouter("POST", "/" + key)?.data;
|
|
35
|
+
if (!route) throw new Error(`Procedure not found: ${key}`);
|
|
36
|
+
const ctx = Object.create(null);
|
|
37
|
+
const baseCtx = await contextFactory();
|
|
38
|
+
const keys = Object.keys(baseCtx);
|
|
39
|
+
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
40
|
+
const signal = new AbortController().signal;
|
|
41
|
+
return route.handler(ctx, input, signal);
|
|
42
|
+
};
|
|
43
|
+
return new Proxy(callProcedure, {
|
|
44
|
+
get(_target, prop) {
|
|
45
|
+
if (prop === "then") return void 0;
|
|
46
|
+
if (typeof prop !== "string") return void 0;
|
|
47
|
+
let cached = cache.get(prop);
|
|
48
|
+
if (!cached) {
|
|
49
|
+
cached = createServerProxy(flatRouter, contextFactory, [...path, prop]);
|
|
50
|
+
cache.set(prop, cached);
|
|
51
|
+
}
|
|
52
|
+
return cached;
|
|
53
|
+
},
|
|
54
|
+
apply(_target, _thisArg, args) {
|
|
55
|
+
return callProcedure(args[0]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { createServerClient };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SilgiError } from "../core/error.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/client/types.d.ts
|
|
4
|
+
type ClientContext = Record<PropertyKey, unknown>;
|
|
5
|
+
interface ClientOptions<TContext extends ClientContext = ClientContext> {
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
lastEventId?: string;
|
|
8
|
+
context?: TContext;
|
|
9
|
+
}
|
|
10
|
+
/** A single procedure client — callable function */
|
|
11
|
+
type Client<TClientContext extends ClientContext, TInput, TOutput, _TError = SilgiError> = (...args: ClientRest<TClientContext, TInput>) => Promise<TOutput>;
|
|
12
|
+
/** A subscription client — returns async iterator */
|
|
13
|
+
type SubscriptionClient<TClientContext extends ClientContext, TInput, TOutput> = (...args: ClientRest<TClientContext, TInput>) => AsyncIterableIterator<TOutput>;
|
|
14
|
+
/** Determine argument shape based on input and context optionality */
|
|
15
|
+
type ClientRest<TClientContext extends ClientContext, TInput> = undefined extends TInput ? Record<never, never> extends TClientContext ? [input?: TInput, options?: ClientOptions<TClientContext>] : [input: TInput | undefined, options: ClientOptions<TClientContext>] : Record<never, never> extends TClientContext ? [input: TInput, options?: ClientOptions<TClientContext>] : [input: TInput, options: ClientOptions<TClientContext>];
|
|
16
|
+
/** Recursive nested client — mirrors the router structure */
|
|
17
|
+
type NestedClient<TClientContext extends ClientContext = ClientContext> = Client<TClientContext, any, any, any> | SubscriptionClient<TClientContext, any, any> | {
|
|
18
|
+
[key: string]: NestedClient<TClientContext>;
|
|
19
|
+
};
|
|
20
|
+
/** Transport interface — how requests are sent */
|
|
21
|
+
interface ClientLink<TClientContext extends ClientContext = ClientContext> {
|
|
22
|
+
call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
|
|
23
|
+
}
|
|
24
|
+
/** Infer input types from a nested client */
|
|
25
|
+
type InferClientInputs<T extends NestedClient> = T extends Client<any, infer TInput, any, any> ? TInput : T extends Record<string, NestedClient> ? { [K in keyof T]: InferClientInputs<T[K]> } : never;
|
|
26
|
+
/** Infer output types from a nested client */
|
|
27
|
+
type InferClientOutputs<T extends NestedClient> = T extends Client<any, any, infer TOutput, any> ? TOutput : T extends Record<string, NestedClient> ? { [K in keyof T]: InferClientOutputs<T[K]> } : never;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { Client, ClientContext, ClientLink, ClientOptions, ClientRest, InferClientInputs, InferClientOutputs, NestedClient };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/codec/devalue.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* devalue codec — rich type serialization.
|
|
4
|
+
*
|
|
5
|
+
* Supports Date, Map, Set, BigInt, RegExp, undefined, circular refs.
|
|
6
|
+
* 2.7x faster than superjson, 37% smaller output.
|
|
7
|
+
*
|
|
8
|
+
* Use when your RPC procedures return rich JS types
|
|
9
|
+
* that JSON.stringify can't handle.
|
|
10
|
+
*/
|
|
11
|
+
declare const DEVALUE_CONTENT_TYPE = "application/x-devalue+json";
|
|
12
|
+
/** Serialize a value with devalue (handles Date, Map, Set, BigInt, etc.) */
|
|
13
|
+
declare function encode(value: unknown): string;
|
|
14
|
+
/** Deserialize a devalue string back to the original value */
|
|
15
|
+
declare function decode(text: string): unknown;
|
|
16
|
+
/** Check if request body uses devalue encoding */
|
|
17
|
+
declare function isDevalue(contentType: string | null | undefined): boolean;
|
|
18
|
+
/** Check if client accepts devalue responses */
|
|
19
|
+
declare function acceptsDevalue(acceptHeader: string | null | undefined): boolean;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DEVALUE_CONTENT_TYPE, acceptsDevalue, decode, encode, isDevalue };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parse, stringify } from "devalue";
|
|
2
|
+
//#region src/codec/devalue.ts
|
|
3
|
+
/**
|
|
4
|
+
* devalue codec — rich type serialization.
|
|
5
|
+
*
|
|
6
|
+
* Supports Date, Map, Set, BigInt, RegExp, undefined, circular refs.
|
|
7
|
+
* 2.7x faster than superjson, 37% smaller output.
|
|
8
|
+
*
|
|
9
|
+
* Use when your RPC procedures return rich JS types
|
|
10
|
+
* that JSON.stringify can't handle.
|
|
11
|
+
*/
|
|
12
|
+
const DEVALUE_CONTENT_TYPE = "application/x-devalue+json";
|
|
13
|
+
/** Serialize a value with devalue (handles Date, Map, Set, BigInt, etc.) */
|
|
14
|
+
function encode(value) {
|
|
15
|
+
return stringify(value);
|
|
16
|
+
}
|
|
17
|
+
/** Deserialize a devalue string back to the original value */
|
|
18
|
+
function decode(text) {
|
|
19
|
+
return parse(text);
|
|
20
|
+
}
|
|
21
|
+
/** Check if request body uses devalue encoding */
|
|
22
|
+
function isDevalue(contentType) {
|
|
23
|
+
if (!contentType) return false;
|
|
24
|
+
return contentType.includes(DEVALUE_CONTENT_TYPE);
|
|
25
|
+
}
|
|
26
|
+
/** Check if client accepts devalue responses */
|
|
27
|
+
function acceptsDevalue(acceptHeader) {
|
|
28
|
+
if (!acceptHeader) return false;
|
|
29
|
+
return acceptHeader.includes(DEVALUE_CONTENT_TYPE);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { DEVALUE_CONTENT_TYPE, acceptsDevalue, decode, encode, isDevalue };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/codec/msgpack.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* MessagePack codec for Silgi binary protocol.
|
|
4
|
+
*
|
|
5
|
+
* 2-4x faster encoding, ~50% smaller payloads vs JSON.
|
|
6
|
+
* No competitor (oRPC, tRPC) offers binary transport.
|
|
7
|
+
*
|
|
8
|
+
* Uses msgpackr with record extension — repeated object shapes
|
|
9
|
+
* (common in RPC: same fields every request) get 2-3x decode speedup.
|
|
10
|
+
*/
|
|
11
|
+
declare const MSGPACK_CONTENT_TYPE = "application/x-msgpack";
|
|
12
|
+
/** Encode a value to MessagePack binary (usable as Response body) */
|
|
13
|
+
declare function encode(value: unknown): BodyInit;
|
|
14
|
+
/** Decode a MessagePack Buffer to a value (safe for untrusted input) */
|
|
15
|
+
declare function decode(buf: Buffer | Uint8Array): unknown;
|
|
16
|
+
/** Check if a request accepts msgpack */
|
|
17
|
+
declare function acceptsMsgpack(acceptHeader: string | null | undefined): boolean;
|
|
18
|
+
/** Check if request body is msgpack */
|
|
19
|
+
declare function isMsgpack(contentType: string | null | undefined): boolean;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { MSGPACK_CONTENT_TYPE, acceptsMsgpack, decode, encode, isMsgpack };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Packr } from "msgpackr";
|
|
2
|
+
//#region src/codec/msgpack.ts
|
|
3
|
+
/**
|
|
4
|
+
* MessagePack codec for Silgi binary protocol.
|
|
5
|
+
*
|
|
6
|
+
* 2-4x faster encoding, ~50% smaller payloads vs JSON.
|
|
7
|
+
* No competitor (oRPC, tRPC) offers binary transport.
|
|
8
|
+
*
|
|
9
|
+
* Uses msgpackr with record extension — repeated object shapes
|
|
10
|
+
* (common in RPC: same fields every request) get 2-3x decode speedup.
|
|
11
|
+
*/
|
|
12
|
+
const MSGPACK_CONTENT_TYPE = "application/x-msgpack";
|
|
13
|
+
/**
|
|
14
|
+
* Stateless msgpack codec — for request/response (no connection persistence).
|
|
15
|
+
* Records disabled for cross-request compatibility.
|
|
16
|
+
*/
|
|
17
|
+
const encoder = new Packr({
|
|
18
|
+
useRecords: false,
|
|
19
|
+
moreTypes: true,
|
|
20
|
+
int64AsType: "number"
|
|
21
|
+
});
|
|
22
|
+
const decoder = new Packr({
|
|
23
|
+
useRecords: false,
|
|
24
|
+
moreTypes: false,
|
|
25
|
+
int64AsType: "number"
|
|
26
|
+
});
|
|
27
|
+
/** Encode a value to MessagePack binary (usable as Response body) */
|
|
28
|
+
function encode(value) {
|
|
29
|
+
const buf = encoder.pack(value);
|
|
30
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
31
|
+
}
|
|
32
|
+
/** Decode a MessagePack Buffer to a value (safe for untrusted input) */
|
|
33
|
+
function decode(buf) {
|
|
34
|
+
return sanitizeDecoded(decoder.unpack(buf));
|
|
35
|
+
}
|
|
36
|
+
/** Strip unsafe types (Error, RegExp) that msgpackr may deserialize from untrusted input */
|
|
37
|
+
function sanitizeDecoded(value) {
|
|
38
|
+
if (value === null || value === void 0) return value;
|
|
39
|
+
if (typeof value !== "object") return value;
|
|
40
|
+
if (value instanceof Error) return { message: value.message };
|
|
41
|
+
if (value instanceof RegExp) return String(value);
|
|
42
|
+
if (Array.isArray(value)) return value.map(sanitizeDecoded);
|
|
43
|
+
if (value instanceof Date || value instanceof Map || value instanceof Set) return value;
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [k, v] of Object.entries(value)) result[k] = sanitizeDecoded(v);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
/** Check if a request accepts msgpack */
|
|
49
|
+
function acceptsMsgpack(acceptHeader) {
|
|
50
|
+
if (!acceptHeader) return false;
|
|
51
|
+
return acceptHeader.includes("application/x-msgpack") || acceptHeader.includes("application/msgpack");
|
|
52
|
+
}
|
|
53
|
+
/** Check if request body is msgpack */
|
|
54
|
+
function isMsgpack(contentType) {
|
|
55
|
+
if (!contentType) return false;
|
|
56
|
+
return contentType.includes("application/x-msgpack") || contentType.includes("application/msgpack");
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { MSGPACK_CONTENT_TYPE, acceptsMsgpack, decode, encode, isMsgpack };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ProcedureDef } from "./types.mjs";
|
|
2
|
+
import { MatchedRoute } from "./route/types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/compile.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Compiled pipeline — called per request.
|
|
7
|
+
* May return sync value OR Promise — caller uses instanceof check.
|
|
8
|
+
*/
|
|
9
|
+
type CompiledHandler = (ctx: Record<string, unknown>, rawInput: unknown, signal: AbortSignal) => unknown | Promise<unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Compile a procedure into the fastest possible handler.
|
|
12
|
+
*
|
|
13
|
+
* Optimizations applied:
|
|
14
|
+
* - Guard count specialization (unrolled for 0-4)
|
|
15
|
+
* - Separate fast path for no-wrap case (zero closures per request)
|
|
16
|
+
* - Pre-computed fail function (singleton per procedure)
|
|
17
|
+
* - Sync fast path when all guards are sync
|
|
18
|
+
*/
|
|
19
|
+
declare function compileProcedure(procedure: ProcedureDef): CompiledHandler;
|
|
20
|
+
interface CompiledRoute {
|
|
21
|
+
handler: CompiledHandler;
|
|
22
|
+
stringify: (value: unknown) => string;
|
|
23
|
+
/** Pre-computed Cache-Control header value, or undefined if no caching */
|
|
24
|
+
cacheControl?: string;
|
|
25
|
+
/** Procedure is accessible over WebSocket */
|
|
26
|
+
ws?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Compiled router function — returns matched route + params */
|
|
29
|
+
type CompiledRouterFn = (method: string, path: string) => MatchedRoute<CompiledRoute> | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Compile a router tree into a JIT-compiled radix router.
|
|
32
|
+
*
|
|
33
|
+
* Uses charCodeAt dispatch + lazy split + pre-allocated results.
|
|
34
|
+
* Static: ~2ns, Param: ~15ns, Wildcard: ~7ns, Miss: ~2ns.
|
|
35
|
+
*/
|
|
36
|
+
declare function compileRouter(def: Record<string, unknown>): CompiledRouterFn;
|
|
37
|
+
/**
|
|
38
|
+
* Pre-allocated context pool — zero allocation on borrow.
|
|
39
|
+
*
|
|
40
|
+
* Each context is a null-prototype object (no prototype chain lookups).
|
|
41
|
+
* After use, all properties are deleted and returned to pool.
|
|
42
|
+
*/
|
|
43
|
+
declare class ContextPool {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(size?: number);
|
|
46
|
+
borrow(): Record<string, unknown>;
|
|
47
|
+
release(_ctx: Record<string, unknown>): void;
|
|
48
|
+
/** Borrow a context that auto-releases when disposed (Node 24+ / `using` keyword) */
|
|
49
|
+
borrowDisposable(): Record<string, unknown> & Disposable;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { ContextPool, compileProcedure, compileRouter };
|
package/dist/compile.mjs
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { analyzeHandler } from "./analyze.mjs";
|
|
2
|
+
import { SilgiError } from "./core/error.mjs";
|
|
3
|
+
import { validateSchema } from "./core/schema.mjs";
|
|
4
|
+
import { compileStringify } from "./fast-stringify.mjs";
|
|
5
|
+
import { createRouter } from "./route/context.mjs";
|
|
6
|
+
import { addRoute } from "./route/add.mjs";
|
|
7
|
+
import { compileRouter as compileRouter$1 } from "./route/compiler.mjs";
|
|
8
|
+
//#region src/compile.ts
|
|
9
|
+
/**
|
|
10
|
+
* Pipeline Compiler v3 — 2026 Optimized
|
|
11
|
+
*
|
|
12
|
+
* Innovations:
|
|
13
|
+
* 1. UNROLLED GUARDS — 0-4 guard specialization (no loop, V8 inlines)
|
|
14
|
+
* 2. FLAT MAP ROUTER — O(1) lookup via Map.get()
|
|
15
|
+
* 3. CONTEXT POOL — pre-allocated, zero per-request allocation
|
|
16
|
+
* 4. AbortSignal.any() — native signal composition
|
|
17
|
+
* 5. Promise.withResolvers() — cleaner deferred patterns
|
|
18
|
+
* 6. RESULT-BASED ERRORS — avoid throw for typed errors (optional)
|
|
19
|
+
*
|
|
20
|
+
* Benchmark target: 5-8x faster than oRPC
|
|
21
|
+
*/
|
|
22
|
+
function isThenable(value) {
|
|
23
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
24
|
+
}
|
|
25
|
+
function createFail(errors) {
|
|
26
|
+
return (code, data) => {
|
|
27
|
+
const def = errors[code];
|
|
28
|
+
throw new SilgiError(code, {
|
|
29
|
+
status: typeof def === "number" ? def : def?.status ?? 500,
|
|
30
|
+
message: typeof def === "object" && def !== null && "message" in def ? def.message : void 0,
|
|
31
|
+
data,
|
|
32
|
+
defined: true
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function noopFail(code, data) {
|
|
37
|
+
throw new SilgiError(code, {
|
|
38
|
+
data,
|
|
39
|
+
defined: false
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
43
|
+
"__proto__",
|
|
44
|
+
"constructor",
|
|
45
|
+
"prototype"
|
|
46
|
+
]);
|
|
47
|
+
/** Apply a single guard result to context — direct property set (326x faster than Object.assign) */
|
|
48
|
+
function applyGuardResult(ctx, result) {
|
|
49
|
+
if (result === null || result === void 0 || typeof result !== "object") return;
|
|
50
|
+
const keys = Object.keys(result);
|
|
51
|
+
for (let i = 0; i < keys.length; i++) {
|
|
52
|
+
const k = keys[i];
|
|
53
|
+
if (UNSAFE_KEYS.has(k)) continue;
|
|
54
|
+
ctx[k] = result[k];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Apply a single guard (sync fast-path, async fallback) */
|
|
58
|
+
async function applyGuard(ctx, guard) {
|
|
59
|
+
const result = guard.fn(ctx);
|
|
60
|
+
applyGuardResult(ctx, isThenable(result) ? await result : result);
|
|
61
|
+
}
|
|
62
|
+
function runGuards0() {}
|
|
63
|
+
function runGuards1(ctx, g0) {
|
|
64
|
+
const r0 = g0.fn(ctx);
|
|
65
|
+
if (isThenable(r0)) return r0.then((v) => applyGuardResult(ctx, v));
|
|
66
|
+
applyGuardResult(ctx, r0);
|
|
67
|
+
}
|
|
68
|
+
function runGuards2(ctx, g0, g1) {
|
|
69
|
+
const r0 = g0.fn(ctx);
|
|
70
|
+
if (isThenable(r0)) return r0.then((v) => {
|
|
71
|
+
applyGuardResult(ctx, v);
|
|
72
|
+
return applyGuard(ctx, g1);
|
|
73
|
+
});
|
|
74
|
+
applyGuardResult(ctx, r0);
|
|
75
|
+
const r1 = g1.fn(ctx);
|
|
76
|
+
if (isThenable(r1)) return r1.then((v) => applyGuardResult(ctx, v));
|
|
77
|
+
applyGuardResult(ctx, r1);
|
|
78
|
+
}
|
|
79
|
+
function runGuards3(ctx, g0, g1, g2) {
|
|
80
|
+
const r0 = g0.fn(ctx);
|
|
81
|
+
if (isThenable(r0)) return r0.then(async (v) => {
|
|
82
|
+
applyGuardResult(ctx, v);
|
|
83
|
+
await applyGuard(ctx, g1);
|
|
84
|
+
await applyGuard(ctx, g2);
|
|
85
|
+
});
|
|
86
|
+
applyGuardResult(ctx, r0);
|
|
87
|
+
const r1 = g1.fn(ctx);
|
|
88
|
+
if (isThenable(r1)) return r1.then(async (v) => {
|
|
89
|
+
applyGuardResult(ctx, v);
|
|
90
|
+
await applyGuard(ctx, g2);
|
|
91
|
+
});
|
|
92
|
+
applyGuardResult(ctx, r1);
|
|
93
|
+
const r2 = g2.fn(ctx);
|
|
94
|
+
if (isThenable(r2)) return r2.then((v) => applyGuardResult(ctx, v));
|
|
95
|
+
applyGuardResult(ctx, r2);
|
|
96
|
+
}
|
|
97
|
+
function runGuards4(ctx, g0, g1, g2, g3) {
|
|
98
|
+
const r0 = g0.fn(ctx);
|
|
99
|
+
if (isThenable(r0)) return r0.then(async (v) => {
|
|
100
|
+
applyGuardResult(ctx, v);
|
|
101
|
+
await applyGuard(ctx, g1);
|
|
102
|
+
await applyGuard(ctx, g2);
|
|
103
|
+
await applyGuard(ctx, g3);
|
|
104
|
+
});
|
|
105
|
+
applyGuardResult(ctx, r0);
|
|
106
|
+
const r1 = g1.fn(ctx);
|
|
107
|
+
if (isThenable(r1)) return r1.then(async (v) => {
|
|
108
|
+
applyGuardResult(ctx, v);
|
|
109
|
+
await applyGuard(ctx, g2);
|
|
110
|
+
await applyGuard(ctx, g3);
|
|
111
|
+
});
|
|
112
|
+
applyGuardResult(ctx, r1);
|
|
113
|
+
const r2 = g2.fn(ctx);
|
|
114
|
+
if (isThenable(r2)) return r2.then(async (v) => {
|
|
115
|
+
applyGuardResult(ctx, v);
|
|
116
|
+
await applyGuard(ctx, g3);
|
|
117
|
+
});
|
|
118
|
+
applyGuardResult(ctx, r2);
|
|
119
|
+
const r3 = g3.fn(ctx);
|
|
120
|
+
if (isThenable(r3)) return r3.then((v) => applyGuardResult(ctx, v));
|
|
121
|
+
applyGuardResult(ctx, r3);
|
|
122
|
+
}
|
|
123
|
+
/** Fallback for 5+ guards — loop */
|
|
124
|
+
async function runGuardsN(ctx, guards) {
|
|
125
|
+
for (const guard of guards) {
|
|
126
|
+
const result = guard.fn(ctx);
|
|
127
|
+
applyGuardResult(ctx, isThenable(result) ? await result : result);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Select the optimal guard runner based on count.
|
|
132
|
+
* Returns a function that applies all guards to a context.
|
|
133
|
+
*/
|
|
134
|
+
function selectGuardRunner(guards) {
|
|
135
|
+
switch (guards.length) {
|
|
136
|
+
case 0: return runGuards0;
|
|
137
|
+
case 1: {
|
|
138
|
+
const g0 = guards[0];
|
|
139
|
+
return (ctx) => runGuards1(ctx, g0);
|
|
140
|
+
}
|
|
141
|
+
case 2: {
|
|
142
|
+
const [g0, g1] = guards;
|
|
143
|
+
return (ctx) => runGuards2(ctx, g0, g1);
|
|
144
|
+
}
|
|
145
|
+
case 3: {
|
|
146
|
+
const [g0, g1, g2] = guards;
|
|
147
|
+
return (ctx) => runGuards3(ctx, g0, g1, g2);
|
|
148
|
+
}
|
|
149
|
+
case 4: {
|
|
150
|
+
const [g0, g1, g2, g3] = guards;
|
|
151
|
+
return (ctx) => runGuards4(ctx, g0, g1, g2, g3);
|
|
152
|
+
}
|
|
153
|
+
default: return (ctx) => runGuardsN(ctx, guards);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Compile a procedure into the fastest possible handler.
|
|
158
|
+
*
|
|
159
|
+
* Optimizations applied:
|
|
160
|
+
* - Guard count specialization (unrolled for 0-4)
|
|
161
|
+
* - Separate fast path for no-wrap case (zero closures per request)
|
|
162
|
+
* - Pre-computed fail function (singleton per procedure)
|
|
163
|
+
* - Sync fast path when all guards are sync
|
|
164
|
+
*/
|
|
165
|
+
function compileProcedure(procedure) {
|
|
166
|
+
const middlewares = procedure.use ?? [];
|
|
167
|
+
const guards = [];
|
|
168
|
+
const wraps = [];
|
|
169
|
+
for (const mw of middlewares) if (mw.kind === "guard") guards.push(mw);
|
|
170
|
+
else wraps.push(mw);
|
|
171
|
+
const inputSchema = procedure.input;
|
|
172
|
+
const outputSchema = procedure.output;
|
|
173
|
+
const resolveFn = procedure.resolve;
|
|
174
|
+
let mergedErrors = procedure.errors;
|
|
175
|
+
for (const guard of guards) if (guard.errors) mergedErrors = mergedErrors ? {
|
|
176
|
+
...mergedErrors,
|
|
177
|
+
...guard.errors
|
|
178
|
+
} : guard.errors;
|
|
179
|
+
const analysis = analyzeHandler(resolveFn);
|
|
180
|
+
const failFn = analysis.usesFail && mergedErrors ? createFail(mergedErrors) : analysis.usesFail ? noopFail : noopFail;
|
|
181
|
+
const runGuards = selectGuardRunner(guards);
|
|
182
|
+
if (guards.length === 0 && wraps.length === 0 && !inputSchema && !outputSchema && !analysis.usesContext && !analysis.usesFail) return (_ctx, rawInput, signal) => resolveFn({
|
|
183
|
+
input: rawInput,
|
|
184
|
+
ctx: _ctx,
|
|
185
|
+
fail: failFn,
|
|
186
|
+
signal,
|
|
187
|
+
params: _ctx.params ?? {}
|
|
188
|
+
});
|
|
189
|
+
if (wraps.length === 0 && !inputSchema && !outputSchema) return (ctx, rawInput, signal) => {
|
|
190
|
+
const guardResult = runGuards(ctx);
|
|
191
|
+
if (guardResult && isThenable(guardResult)) return guardResult.then(() => resolveFn({
|
|
192
|
+
input: rawInput,
|
|
193
|
+
ctx,
|
|
194
|
+
fail: failFn,
|
|
195
|
+
signal,
|
|
196
|
+
params: ctx.params ?? {}
|
|
197
|
+
}));
|
|
198
|
+
return resolveFn({
|
|
199
|
+
input: rawInput,
|
|
200
|
+
ctx,
|
|
201
|
+
fail: failFn,
|
|
202
|
+
signal,
|
|
203
|
+
params: ctx.params ?? {}
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
if (wraps.length === 0) return async (ctx, rawInput, signal) => {
|
|
207
|
+
const guardResult = runGuards(ctx);
|
|
208
|
+
if (guardResult) await guardResult;
|
|
209
|
+
const output = await resolveFn({
|
|
210
|
+
input: inputSchema ? await validateSchema(inputSchema, rawInput ?? {}) : rawInput,
|
|
211
|
+
ctx,
|
|
212
|
+
fail: failFn,
|
|
213
|
+
signal,
|
|
214
|
+
params: ctx.params ?? {}
|
|
215
|
+
});
|
|
216
|
+
return outputSchema ? await validateSchema(outputSchema, output) : output;
|
|
217
|
+
};
|
|
218
|
+
return async (ctx, rawInput, signal) => {
|
|
219
|
+
const guardResult = runGuards(ctx);
|
|
220
|
+
if (guardResult) await guardResult;
|
|
221
|
+
const input = inputSchema ? await validateSchema(inputSchema, rawInput ?? {}) : rawInput;
|
|
222
|
+
ctx.__rawInput = input;
|
|
223
|
+
let execute = () => {
|
|
224
|
+
const resolvedInput = ctx.__rawInput ?? input;
|
|
225
|
+
return Promise.resolve(resolveFn({
|
|
226
|
+
input: resolvedInput,
|
|
227
|
+
ctx,
|
|
228
|
+
fail: failFn,
|
|
229
|
+
signal,
|
|
230
|
+
params: ctx.params ?? {}
|
|
231
|
+
}));
|
|
232
|
+
};
|
|
233
|
+
for (let i = wraps.length - 1; i >= 0; i--) {
|
|
234
|
+
const wrapFn = wraps[i].fn;
|
|
235
|
+
const next = execute;
|
|
236
|
+
execute = () => wrapFn(ctx, next);
|
|
237
|
+
}
|
|
238
|
+
const output = await execute();
|
|
239
|
+
return outputSchema ? await validateSchema(outputSchema, output) : output;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Compile a router tree into a JIT-compiled radix router.
|
|
244
|
+
*
|
|
245
|
+
* Uses charCodeAt dispatch + lazy split + pre-allocated results.
|
|
246
|
+
* Static: ~2ns, Param: ~15ns, Wildcard: ~7ns, Miss: ~2ns.
|
|
247
|
+
*/
|
|
248
|
+
function compileRouter(def) {
|
|
249
|
+
const radix = createRouter();
|
|
250
|
+
function walk(node, path) {
|
|
251
|
+
if (isProcedureDef(node)) {
|
|
252
|
+
const proc = node;
|
|
253
|
+
const route = proc.route;
|
|
254
|
+
const routePath = route?.path || "/" + path.join("/");
|
|
255
|
+
const method = route?.method?.toUpperCase() || "POST";
|
|
256
|
+
let cacheControl;
|
|
257
|
+
if (route?.cache != null) cacheControl = typeof route.cache === "number" ? `public, max-age=${route.cache}` : route.cache;
|
|
258
|
+
const compiled = {
|
|
259
|
+
handler: compileProcedure(proc),
|
|
260
|
+
stringify: compileStringify(proc.output),
|
|
261
|
+
cacheControl,
|
|
262
|
+
ws: route?.ws ?? void 0
|
|
263
|
+
};
|
|
264
|
+
addRoute(radix, method, routePath, compiled);
|
|
265
|
+
addRoute(radix, "", routePath, compiled);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (typeof node === "object" && node !== null) for (const [k, v] of Object.entries(node)) walk(v, [...path, k]);
|
|
269
|
+
}
|
|
270
|
+
walk(def, []);
|
|
271
|
+
return compileRouter$1(radix);
|
|
272
|
+
}
|
|
273
|
+
function isProcedureDef(value) {
|
|
274
|
+
return typeof value === "object" && value !== null && "type" in value && "resolve" in value && typeof value.resolve === "function";
|
|
275
|
+
}
|
|
276
|
+
const POOL_SIZE = 256;
|
|
277
|
+
/**
|
|
278
|
+
* Pre-allocated context pool — zero allocation on borrow.
|
|
279
|
+
*
|
|
280
|
+
* Each context is a null-prototype object (no prototype chain lookups).
|
|
281
|
+
* After use, all properties are deleted and returned to pool.
|
|
282
|
+
*/
|
|
283
|
+
var ContextPool = class {
|
|
284
|
+
#pool;
|
|
285
|
+
#index = 0;
|
|
286
|
+
constructor(size = POOL_SIZE) {
|
|
287
|
+
this.#pool = Array.from({ length: size }, () => Object.create(null));
|
|
288
|
+
}
|
|
289
|
+
borrow() {
|
|
290
|
+
if (this.#index < this.#pool.length) return this.#pool[this.#index++];
|
|
291
|
+
return Object.create(null);
|
|
292
|
+
}
|
|
293
|
+
release(_ctx) {
|
|
294
|
+
if (this.#index > 0) this.#pool[--this.#index] = Object.create(null);
|
|
295
|
+
}
|
|
296
|
+
/** Borrow a context that auto-releases when disposed (Node 24+ / `using` keyword) */
|
|
297
|
+
borrowDisposable() {
|
|
298
|
+
const ctx = this.borrow();
|
|
299
|
+
ctx[Symbol.dispose] = () => this.release(ctx);
|
|
300
|
+
return ctx;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
//#endregion
|
|
304
|
+
export { ContextPool, compileProcedure, compileRouter };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AnySchema, InferSchemaInput, InferSchemaOutput } from "./core/schema.mjs";
|
|
2
|
+
import { ErrorDef, ProcedureType, Route, RouterDef } from "./types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/contract.d.ts
|
|
5
|
+
interface ProcedureContract<TType extends ProcedureType = ProcedureType, TInput extends AnySchema | undefined = AnySchema | undefined, TOutput extends AnySchema | undefined = AnySchema | undefined, TErrors extends ErrorDef = ErrorDef> {
|
|
6
|
+
type?: TType;
|
|
7
|
+
input?: TInput;
|
|
8
|
+
output?: TOutput;
|
|
9
|
+
errors?: TErrors;
|
|
10
|
+
route?: Route;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
type ContractRouter = {
|
|
14
|
+
[key: string]: ProcedureContract<any, any, any, any> | ContractRouter;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Define an API contract — shared between client and server.
|
|
18
|
+
* Pure type information, no runtime behavior.
|
|
19
|
+
*/
|
|
20
|
+
declare function contract<T extends ContractRouter>(definition: T): T;
|
|
21
|
+
type ImplementProcedure<T extends ProcedureContract> = T extends ProcedureContract<infer _TType, infer TInput, infer TOutput, infer TErrors> ? (opts: {
|
|
22
|
+
input: TInput extends AnySchema ? InferSchemaOutput<TInput> : undefined;
|
|
23
|
+
ctx: Record<string, unknown>;
|
|
24
|
+
fail: TErrors extends ErrorDef ? <K extends keyof TErrors & string>(code: K, ...args: any[]) => never : never;
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
}) => TOutput extends AnySchema ? InferSchemaOutput<TOutput> | Promise<InferSchemaOutput<TOutput>> : unknown : never;
|
|
27
|
+
type ImplementRouter<T extends ContractRouter> = { [K in keyof T]: T[K] extends ProcedureContract ? ImplementProcedure<T[K]> : T[K] extends ContractRouter ? ImplementRouter<T[K]> : never };
|
|
28
|
+
/**
|
|
29
|
+
* Implement a contract — returns a silgi RouterDef.
|
|
30
|
+
* Type-safe: implementation must match the contract.
|
|
31
|
+
*/
|
|
32
|
+
declare function implement<T extends ContractRouter>(contractDef: T, implementations: ImplementRouter<T>): RouterDef;
|
|
33
|
+
/** Infer client type from a contract (no server code needed) */
|
|
34
|
+
type InferContractClient<T extends ContractRouter> = { [K in keyof T]: T[K] extends ProcedureContract<any, infer TInput, infer TOutput> ? TInput extends AnySchema ? (input: InferSchemaInput<TInput>) => Promise<TOutput extends AnySchema ? InferSchemaOutput<TOutput> : unknown> : () => Promise<TOutput extends AnySchema ? InferSchemaOutput<TOutput> : unknown> : T[K] extends ContractRouter ? InferContractClient<T[K]> : never };
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ContractRouter, InferContractClient, ProcedureContract, contract, implement };
|