silgi 0.1.0-beta.4 → 0.1.0-beta.6
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/adapters/_fetch-adapter.d.mts +18 -0
- package/dist/adapters/_fetch-adapter.mjs +53 -0
- package/dist/adapters/astro.d.mts +4 -6
- package/dist/adapters/astro.mjs +23 -16
- package/dist/adapters/aws-lambda.d.mts +15 -4
- package/dist/adapters/aws-lambda.mjs +40 -33
- package/dist/adapters/express.mjs +66 -24
- package/dist/adapters/message-port.d.mts +6 -1
- package/dist/adapters/message-port.mjs +24 -21
- package/dist/adapters/nestjs.mjs +5 -21
- package/dist/adapters/nextjs.d.mts +3 -10
- package/dist/adapters/nextjs.mjs +18 -19
- package/dist/adapters/remix.d.mts +4 -6
- package/dist/adapters/remix.mjs +22 -16
- package/dist/adapters/solidstart.d.mts +3 -5
- package/dist/adapters/solidstart.mjs +20 -21
- package/dist/adapters/sveltekit.d.mts +3 -7
- package/dist/adapters/sveltekit.mjs +19 -22
- package/dist/callable.d.mts +2 -0
- package/dist/callable.mjs +4 -4
- package/dist/caller.mjs +90 -0
- package/dist/client/adapters/fetch/index.mjs +14 -2
- package/dist/client/adapters/ofetch/index.d.mts +16 -2
- package/dist/client/adapters/ofetch/index.mjs +6 -7
- package/dist/client/client.mjs +1 -1
- package/dist/client/index.d.mts +1 -2
- package/dist/client/index.mjs +1 -2
- package/dist/client/plugins/batch.mjs +2 -2
- package/dist/client/plugins/circuit-breaker.d.mts +24 -0
- package/dist/client/plugins/circuit-breaker.mjs +60 -0
- package/dist/client/plugins/index.d.mts +3 -1
- package/dist/client/plugins/index.mjs +3 -1
- package/dist/client/plugins/retry.d.mts +23 -2
- package/dist/client/plugins/retry.mjs +51 -7
- package/dist/client/plugins/timeout.d.mts +10 -0
- package/dist/client/plugins/timeout.mjs +14 -0
- package/dist/codec/devalue.d.mts +2 -2
- package/dist/codec/devalue.mjs +4 -3
- package/dist/codec/msgpack.d.mts +3 -6
- package/dist/codec/msgpack.mjs +4 -18
- package/dist/codec/sanitize.mjs +38 -0
- package/dist/compile.d.mts +12 -20
- package/dist/compile.mjs +123 -96
- package/dist/core/codec.mjs +67 -0
- package/dist/core/dispatch.mjs +62 -0
- package/dist/core/handler.d.mts +6 -0
- package/dist/core/handler.mjs +94 -516
- package/dist/core/input.mjs +49 -0
- package/dist/core/router-utils.mjs +10 -4
- package/dist/core/schema.d.mts +2 -1
- package/dist/core/schema.mjs +11 -4
- package/dist/core/serve.d.mts +51 -0
- package/dist/core/serve.mjs +47 -9
- package/dist/core/sse.d.mts +5 -3
- package/dist/core/sse.mjs +21 -18
- package/dist/core/utils.mjs +3 -0
- package/dist/index.d.mts +5 -4
- package/dist/index.mjs +3 -3
- package/dist/integrations/ai/index.mjs +4 -3
- package/dist/integrations/react/index.mjs +2 -3
- package/dist/integrations/tanstack-query/ssr.d.mts +10 -1
- package/dist/integrations/tanstack-query/ssr.mjs +15 -2
- package/dist/lazy.d.mts +0 -2
- package/dist/lazy.mjs +14 -7
- package/dist/map-input.mjs +25 -2
- package/dist/plugins/analytics.d.mts +17 -1
- package/dist/plugins/analytics.mjs +195 -17
- package/dist/plugins/batch-server.mjs +5 -0
- package/dist/plugins/body-limit.d.mts +4 -1
- package/dist/plugins/body-limit.mjs +8 -3
- package/dist/plugins/cache.mjs +18 -6
- package/dist/plugins/coerce.d.mts +5 -2
- package/dist/plugins/coerce.mjs +29 -5
- package/dist/plugins/cookies.d.mts +8 -38
- package/dist/plugins/cookies.mjs +21 -40
- package/dist/plugins/cors.d.mts +8 -4
- package/dist/plugins/cors.mjs +12 -6
- package/dist/plugins/file-upload.mjs +11 -9
- package/dist/plugins/index.d.mts +1 -3
- package/dist/plugins/index.mjs +2 -4
- package/dist/plugins/ratelimit.d.mts +2 -0
- package/dist/plugins/ratelimit.mjs +11 -0
- package/dist/plugins/signing.mjs +4 -1
- package/dist/scalar.d.mts +0 -4
- package/dist/scalar.mjs +128 -147
- package/dist/silgi.d.mts +17 -14
- package/dist/silgi.mjs +34 -7
- package/dist/types.d.mts +24 -1
- package/dist/ws.d.mts +54 -8
- package/dist/ws.mjs +86 -18
- package/lib/dashboard/index.html +43 -43
- package/package.json +13 -14
- package/dist/adapters/fastify.d.mts +0 -15
- package/dist/adapters/fastify.mjs +0 -78
- package/dist/analyze.mjs +0 -26
- package/dist/client/merge.d.mts +0 -28
- package/dist/client/merge.mjs +0 -30
- package/dist/fast-stringify.mjs +0 -125
- package/dist/plugins/compression.d.mts +0 -19
- package/dist/plugins/compression.mjs +0 -23
- package/dist/plugins/custom-serializer.d.mts +0 -57
- package/dist/plugins/custom-serializer.mjs +0 -40
- package/dist/route/add.mjs +0 -240
- package/dist/route/compiler.mjs +0 -373
- package/dist/route/context.mjs +0 -12
- package/dist/route/types.d.mts +0 -11
- package/dist/route/utils.mjs +0 -17
package/dist/adapters/remix.mjs
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
|
+
import { createFetchAdapter } from "./_fetch-adapter.mjs";
|
|
1
2
|
//#region src/adapters/remix.ts
|
|
2
3
|
/**
|
|
4
|
+
* Remix adapter — use Silgi with Remix action/loader routes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // app/routes/rpc.$.tsx
|
|
9
|
+
* import { silgiRemix } from "silgi/remix"
|
|
10
|
+
* import { appRouter } from "~/server/rpc"
|
|
11
|
+
*
|
|
12
|
+
* const handler = silgiRemix(appRouter, {
|
|
13
|
+
* prefix: "/rpc",
|
|
14
|
+
* context: (req) => ({ db: getDB() }),
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* export const action = handler
|
|
18
|
+
* export const loader = handler
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
3
22
|
* Create a Remix action/loader handler.
|
|
4
|
-
*
|
|
23
|
+
* Remix passes { request, params } — we extract request and delegate.
|
|
5
24
|
*/
|
|
6
25
|
function silgiRemix(router, options = {}) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
return async ({ request }) => {
|
|
10
|
-
if (!_handler) {
|
|
11
|
-
const { silgi } = await import("../silgi.mjs");
|
|
12
|
-
_handler = silgi({ context: options.context ?? (() => ({})) }).handler(router);
|
|
13
|
-
}
|
|
14
|
-
const url = new URL(request.url);
|
|
15
|
-
let pathname = url.pathname;
|
|
16
|
-
if (pathname.startsWith(prefix)) {
|
|
17
|
-
pathname = pathname.slice(prefix.length);
|
|
18
|
-
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
19
|
-
}
|
|
20
|
-
return _handler(new Request(new URL(pathname + url.search, url.origin), request));
|
|
21
|
-
};
|
|
26
|
+
const handler = createFetchAdapter(router, options, "/rpc");
|
|
27
|
+
return ({ request }) => handler(request);
|
|
22
28
|
}
|
|
23
29
|
//#endregion
|
|
24
30
|
export { silgiRemix };
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { FetchAdapterConfigWithEvent } from "./_fetch-adapter.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/adapters/solidstart.d.ts
|
|
4
|
-
interface SolidStartAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
-
context?: (event: any) => TCtx | Promise<TCtx>;
|
|
6
|
-
prefix?: string;
|
|
7
|
-
}
|
|
5
|
+
interface SolidStartAdapterOptions<TCtx extends Record<string, unknown>> extends FetchAdapterConfigWithEvent<TCtx> {}
|
|
8
6
|
/**
|
|
9
7
|
* Create a SolidStart API route handler.
|
|
10
8
|
* SolidStart uses Fetch API events — uses Silgi's handler().
|
|
11
9
|
*/
|
|
12
|
-
declare function silgiSolidStart<TCtx extends Record<string, unknown>>(router: RouterDef, options?: SolidStartAdapterOptions<TCtx>): (event: any) => Promise<Response>;
|
|
10
|
+
declare function silgiSolidStart<TCtx extends Record<string, unknown>>(router: RouterDef, options?: SolidStartAdapterOptions<TCtx>): (event: any) => Response | Promise<Response>;
|
|
13
11
|
//#endregion
|
|
14
12
|
export { SolidStartAdapterOptions, silgiSolidStart };
|
|
@@ -1,30 +1,29 @@
|
|
|
1
|
+
import { createEventFetchAdapter } from "./_fetch-adapter.mjs";
|
|
1
2
|
//#region src/adapters/solidstart.ts
|
|
2
3
|
/**
|
|
4
|
+
* SolidStart adapter — use Silgi with SolidStart API routes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // src/routes/api/rpc/[...path].ts
|
|
9
|
+
* import { silgiSolidStart } from "silgi/solidstart"
|
|
10
|
+
* import { appRouter } from "~/server/rpc"
|
|
11
|
+
*
|
|
12
|
+
* const handler = silgiSolidStart(appRouter, {
|
|
13
|
+
* prefix: "/api/rpc",
|
|
14
|
+
* context: (event) => ({ db: getDB() }),
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* export const GET = handler
|
|
18
|
+
* export const POST = handler
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
3
22
|
* Create a SolidStart API route handler.
|
|
4
23
|
* SolidStart uses Fetch API events — uses Silgi's handler().
|
|
5
24
|
*/
|
|
6
25
|
function silgiSolidStart(router, options = {}) {
|
|
7
|
-
|
|
8
|
-
let _handler = null;
|
|
9
|
-
let _currentEvent = null;
|
|
10
|
-
return async (event) => {
|
|
11
|
-
_currentEvent = event;
|
|
12
|
-
if (!_handler) {
|
|
13
|
-
const { silgi } = await import("../silgi.mjs");
|
|
14
|
-
_handler = silgi({ context: (_req) => {
|
|
15
|
-
if (options.context) return options.context(_currentEvent);
|
|
16
|
-
return {};
|
|
17
|
-
} }).handler(router);
|
|
18
|
-
}
|
|
19
|
-
const request = event.request ?? event;
|
|
20
|
-
const url = new URL(request.url);
|
|
21
|
-
let pathname = url.pathname;
|
|
22
|
-
if (pathname.startsWith(prefix)) {
|
|
23
|
-
pathname = pathname.slice(prefix.length);
|
|
24
|
-
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
25
|
-
}
|
|
26
|
-
return _handler(new Request(new URL(pathname + url.search, url.origin), request));
|
|
27
|
-
};
|
|
26
|
+
return createEventFetchAdapter(router, options, "/api/rpc", (event) => event.request ?? event);
|
|
28
27
|
}
|
|
29
28
|
//#endregion
|
|
30
29
|
export { silgiSolidStart };
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { FetchAdapterConfigWithEvent } from "./_fetch-adapter.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/adapters/sveltekit.d.ts
|
|
4
|
-
interface SvelteKitAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
-
/** Context factory — receives the SvelteKit RequestEvent */
|
|
6
|
-
context?: (event: any) => TCtx | Promise<TCtx>;
|
|
7
|
-
/** Route prefix to strip. Default: "/api/rpc" */
|
|
8
|
-
prefix?: string;
|
|
9
|
-
}
|
|
5
|
+
interface SvelteKitAdapterOptions<TCtx extends Record<string, unknown>> extends FetchAdapterConfigWithEvent<TCtx> {}
|
|
10
6
|
/**
|
|
11
7
|
* Create a SvelteKit request handler.
|
|
12
8
|
*
|
|
13
9
|
* SvelteKit passes a RequestEvent with `.request` (standard Request).
|
|
14
10
|
* The handler uses Silgi's handler() for full protocol support.
|
|
15
11
|
*/
|
|
16
|
-
declare function silgiSvelteKit<TCtx extends Record<string, unknown>>(router: RouterDef, options?: SvelteKitAdapterOptions<TCtx>): (event: any) => Promise<Response>;
|
|
12
|
+
declare function silgiSvelteKit<TCtx extends Record<string, unknown>>(router: RouterDef, options?: SvelteKitAdapterOptions<TCtx>): (event: any) => Response | Promise<Response>;
|
|
17
13
|
//#endregion
|
|
18
14
|
export { SvelteKitAdapterOptions, silgiSvelteKit };
|
|
@@ -1,33 +1,30 @@
|
|
|
1
|
+
import { createEventFetchAdapter } from "./_fetch-adapter.mjs";
|
|
1
2
|
//#region src/adapters/sveltekit.ts
|
|
2
3
|
/**
|
|
4
|
+
* SvelteKit adapter — use Silgi with SvelteKit API routes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // src/routes/api/rpc/[...path]/+server.ts
|
|
9
|
+
* import { silgiSvelteKit } from "silgi/sveltekit"
|
|
10
|
+
* import { appRouter } from "$lib/server/rpc"
|
|
11
|
+
*
|
|
12
|
+
* const handler = silgiSvelteKit(appRouter, {
|
|
13
|
+
* context: (event) => ({ db: getDB(), user: event.locals.user }),
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* export const GET = handler
|
|
17
|
+
* export const POST = handler
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
3
21
|
* Create a SvelteKit request handler.
|
|
4
22
|
*
|
|
5
23
|
* SvelteKit passes a RequestEvent with `.request` (standard Request).
|
|
6
24
|
* The handler uses Silgi's handler() for full protocol support.
|
|
7
25
|
*/
|
|
8
26
|
function silgiSvelteKit(router, options = {}) {
|
|
9
|
-
|
|
10
|
-
let _handler = null;
|
|
11
|
-
let _currentEvent = null;
|
|
12
|
-
return async (event) => {
|
|
13
|
-
_currentEvent = event;
|
|
14
|
-
if (!_handler) {
|
|
15
|
-
const { silgi } = await import("../silgi.mjs");
|
|
16
|
-
_handler = silgi({ context: (_req) => {
|
|
17
|
-
if (options.context) return options.context(_currentEvent);
|
|
18
|
-
return {};
|
|
19
|
-
} }).handler(router);
|
|
20
|
-
}
|
|
21
|
-
const req = event.request;
|
|
22
|
-
const url = new URL(req.url);
|
|
23
|
-
let pathname = url.pathname;
|
|
24
|
-
if (pathname.startsWith(prefix)) {
|
|
25
|
-
pathname = pathname.slice(prefix.length);
|
|
26
|
-
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
27
|
-
}
|
|
28
|
-
const rewritten = new Request(new URL(pathname + url.search, url.origin), req);
|
|
29
|
-
return _handler(rewritten);
|
|
30
|
-
};
|
|
27
|
+
return createEventFetchAdapter(router, options, "/api/rpc", (event) => event.request);
|
|
31
28
|
}
|
|
32
29
|
//#endregion
|
|
33
30
|
export { silgiSvelteKit };
|
package/dist/callable.d.mts
CHANGED
|
@@ -4,6 +4,8 @@ import { ErrorDef, ProcedureDef } from "./types.mjs";
|
|
|
4
4
|
interface CallableOptions<TCtx extends Record<string, unknown>> {
|
|
5
5
|
/** Context factory — called on every invocation */
|
|
6
6
|
context: () => TCtx | Promise<TCtx>;
|
|
7
|
+
/** Default timeout in ms. Default: 30000. Set null to disable. */
|
|
8
|
+
timeout?: number | null;
|
|
7
9
|
}
|
|
8
10
|
type CallableFn<TInput, TOutput> = undefined extends TInput ? (input?: TInput) => Promise<TOutput> : (input: TInput) => Promise<TOutput>;
|
|
9
11
|
/**
|
package/dist/callable.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compileProcedure } from "./compile.mjs";
|
|
2
|
+
import { applyContext } from "./core/dispatch.mjs";
|
|
2
3
|
//#region src/callable.ts
|
|
3
4
|
/**
|
|
4
5
|
* callable() — turn a procedure into a directly invocable function.
|
|
@@ -29,13 +30,12 @@ import { compileProcedure } from "./compile.mjs";
|
|
|
29
30
|
function callable(procedure, options) {
|
|
30
31
|
const handler = compileProcedure(procedure);
|
|
31
32
|
const contextFactory = options.context;
|
|
32
|
-
const
|
|
33
|
+
const defaultTimeout = options.timeout !== void 0 ? options.timeout : 3e4;
|
|
33
34
|
return (async (input) => {
|
|
34
35
|
const ctx = await contextFactory();
|
|
35
36
|
const ctxObj = Object.create(null);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return handler(ctxObj, input, signal);
|
|
37
|
+
applyContext(ctxObj, ctx);
|
|
38
|
+
return handler(ctxObj, input, defaultTimeout !== null ? AbortSignal.timeout(defaultTimeout) : new AbortController().signal);
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
//#endregion
|
package/dist/caller.mjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { routerCache } from "./core/router-utils.mjs";
|
|
2
|
+
import { compileRouter, createContext, releaseContext } from "./compile.mjs";
|
|
3
|
+
import { applyContext } from "./core/dispatch.mjs";
|
|
4
|
+
//#region src/caller.ts
|
|
5
|
+
/**
|
|
6
|
+
* createCaller — call procedures directly without HTTP.
|
|
7
|
+
*
|
|
8
|
+
* Compiles the router, creates context, and runs the pipeline
|
|
9
|
+
* for each procedure call. Perfect for testing and server-side usage.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const caller = s.createCaller(appRouter)
|
|
14
|
+
*
|
|
15
|
+
* // Call procedures directly
|
|
16
|
+
* const users = await caller.users.list({ limit: 10 })
|
|
17
|
+
* const user = await caller.users.get({ id: 1 })
|
|
18
|
+
*
|
|
19
|
+
* // With custom context override
|
|
20
|
+
* const adminCaller = s.createCaller(appRouter, {
|
|
21
|
+
* contextOverride: { user: { id: 1, role: 'admin' } },
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Create a direct caller for a router — no HTTP, no serialization.
|
|
27
|
+
*
|
|
28
|
+
* Returns a proxy that mirrors the router's nested structure.
|
|
29
|
+
* Calling a leaf procedure invokes the compiled pipeline directly.
|
|
30
|
+
*/
|
|
31
|
+
function createCaller(routerDef, contextFactory, options) {
|
|
32
|
+
let compiledRouter = routerCache.get(routerDef);
|
|
33
|
+
if (!compiledRouter) {
|
|
34
|
+
compiledRouter = compileRouter(routerDef);
|
|
35
|
+
routerCache.set(routerDef, compiledRouter);
|
|
36
|
+
}
|
|
37
|
+
const router = compiledRouter;
|
|
38
|
+
const defaultTimeout = options?.timeout !== void 0 ? options.timeout : 3e4;
|
|
39
|
+
function createMockRequest(extraHeaders) {
|
|
40
|
+
const headers = new Headers(options?.headers);
|
|
41
|
+
if (extraHeaders) for (const [k, v] of Object.entries(extraHeaders)) headers.set(k, v);
|
|
42
|
+
return new Request("http://localhost/__caller", { headers });
|
|
43
|
+
}
|
|
44
|
+
async function resolveContext(perCallContext) {
|
|
45
|
+
const ctx = createContext();
|
|
46
|
+
if (contextFactory) {
|
|
47
|
+
const result = contextFactory(createMockRequest());
|
|
48
|
+
applyContext(ctx, result instanceof Promise ? await result : result);
|
|
49
|
+
}
|
|
50
|
+
if (options?.contextOverride) applyContext(ctx, options.contextOverride);
|
|
51
|
+
if (perCallContext) applyContext(ctx, perCallContext);
|
|
52
|
+
return ctx;
|
|
53
|
+
}
|
|
54
|
+
function createProxy(segments) {
|
|
55
|
+
const cache = /* @__PURE__ */ new Map();
|
|
56
|
+
return new Proxy(() => {}, {
|
|
57
|
+
get(_target, prop) {
|
|
58
|
+
if (typeof prop === "symbol") return void 0;
|
|
59
|
+
if (prop === "then" || prop === "toJSON" || prop === "toString" || prop === "$$typeof") return;
|
|
60
|
+
let sub = cache.get(prop);
|
|
61
|
+
if (!sub) {
|
|
62
|
+
sub = createProxy([...segments, prop]);
|
|
63
|
+
cache.set(prop, sub);
|
|
64
|
+
}
|
|
65
|
+
return sub;
|
|
66
|
+
},
|
|
67
|
+
apply(_target, _thisArg, args) {
|
|
68
|
+
const path = "/" + segments.join("/");
|
|
69
|
+
const input = args[0];
|
|
70
|
+
const callOptions = args[1];
|
|
71
|
+
return (async () => {
|
|
72
|
+
const match = router("", path);
|
|
73
|
+
if (!match) throw new Error(`Procedure not found: ${path}`);
|
|
74
|
+
const ctx = await resolveContext(callOptions?.context);
|
|
75
|
+
if (match.params) ctx.params = match.params;
|
|
76
|
+
const signal = callOptions?.signal ?? (defaultTimeout !== null ? AbortSignal.timeout(defaultTimeout) : void 0);
|
|
77
|
+
try {
|
|
78
|
+
const result = match.data.handler(ctx, input, signal);
|
|
79
|
+
return result instanceof Promise ? await result : result;
|
|
80
|
+
} finally {
|
|
81
|
+
releaseContext(ctx);
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return createProxy([]);
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { createCaller };
|
|
@@ -44,8 +44,20 @@ var RPCLink = class {
|
|
|
44
44
|
body,
|
|
45
45
|
signal: options.signal
|
|
46
46
|
});
|
|
47
|
-
const
|
|
48
|
-
|
|
47
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
48
|
+
let responseBody;
|
|
49
|
+
if (contentType.includes("msgpack")) {
|
|
50
|
+
const { decode } = await import("../../../codec/msgpack.mjs");
|
|
51
|
+
const buf = new Uint8Array(await response.arrayBuffer());
|
|
52
|
+
responseBody = buf.length > 0 ? decode(buf) : void 0;
|
|
53
|
+
} else if (contentType.includes("x-devalue")) {
|
|
54
|
+
const { decode } = await import("../../../codec/devalue.mjs");
|
|
55
|
+
const text = await response.text();
|
|
56
|
+
responseBody = text ? decode(text) : void 0;
|
|
57
|
+
} else {
|
|
58
|
+
const responseText = await response.text();
|
|
59
|
+
responseBody = responseText ? parseEmptyableJSON(responseText) : void 0;
|
|
60
|
+
}
|
|
49
61
|
if (isErrorStatus(response.status)) {
|
|
50
62
|
if (isSilgiErrorJSON(responseBody)) throw fromSilgiErrorJSON(responseBody);
|
|
51
63
|
throw new SilgiError("INTERNAL_SERVER_ERROR", {
|
|
@@ -13,9 +13,23 @@ interface SilgiLinkOptions<TClientContext extends ClientContext = ClientContext>
|
|
|
13
13
|
retryDelay?: number | ((ctx: FetchContext) => number);
|
|
14
14
|
/** Timeout in ms (default: 30000) */
|
|
15
15
|
timeout?: number;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Wire protocol for request/response encoding.
|
|
18
|
+
*
|
|
19
|
+
* - `'json'` — default, standard JSON
|
|
20
|
+
* - `'messagepack'` — 2-4x faster, ~50% smaller payloads
|
|
21
|
+
* - `'devalue'` — preserves Date, Map, Set, BigInt, circular refs
|
|
22
|
+
*
|
|
23
|
+
* @default 'json'
|
|
24
|
+
*/
|
|
25
|
+
protocol?: 'json' | 'messagepack' | 'devalue';
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `protocol: 'messagepack'` instead.
|
|
28
|
+
*/
|
|
17
29
|
binary?: boolean;
|
|
18
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `protocol: 'devalue'` instead.
|
|
32
|
+
*/
|
|
19
33
|
devalue?: boolean;
|
|
20
34
|
/** ofetch interceptors */
|
|
21
35
|
onRequest?: FetchOptions['onRequest'];
|
|
@@ -26,17 +26,16 @@ function createLink(options) {
|
|
|
26
26
|
const defaultTimeout = options.timeout ?? 3e4;
|
|
27
27
|
const defaultRetry = options.retry;
|
|
28
28
|
const defaultRetryDelay = options.retryDelay ?? 0;
|
|
29
|
-
const
|
|
30
|
-
const useDevalue = options.devalue ?? false;
|
|
29
|
+
const resolvedProtocol = options.protocol ?? (options.binary ? "messagepack" : void 0) ?? (options.devalue ? "devalue" : void 0) ?? "json";
|
|
31
30
|
return { async call(path, input, callOptions) {
|
|
32
31
|
const url = `${baseUrl}/${path.map(encodeURIComponent).join("/")}`;
|
|
33
32
|
const headers = { ...typeof options.headers === "function" ? options.headers(callOptions) : options.headers };
|
|
34
33
|
let body;
|
|
35
|
-
if (
|
|
34
|
+
if (resolvedProtocol === "messagepack") {
|
|
36
35
|
headers["content-type"] = MSGPACK_CONTENT_TYPE;
|
|
37
36
|
headers["accept"] = MSGPACK_CONTENT_TYPE;
|
|
38
37
|
body = input !== void 0 && input !== null ? encode(input) : void 0;
|
|
39
|
-
} else if (
|
|
38
|
+
} else if (resolvedProtocol === "devalue") {
|
|
40
39
|
const { encode: devalueEncode, DEVALUE_CONTENT_TYPE } = await import("../../../codec/devalue.mjs");
|
|
41
40
|
headers["content-type"] = DEVALUE_CONTENT_TYPE;
|
|
42
41
|
headers["accept"] = DEVALUE_CONTENT_TYPE;
|
|
@@ -56,7 +55,7 @@ function createLink(options) {
|
|
|
56
55
|
onResponse: options.onResponse,
|
|
57
56
|
onRequestError: options.onRequestError,
|
|
58
57
|
onResponseError: options.onResponseError,
|
|
59
|
-
...
|
|
58
|
+
...resolvedProtocol === "messagepack" ? { responseType: "arrayBuffer" } : resolvedProtocol === "devalue" ? { responseType: "text" } : { parseResponse(text) {
|
|
60
59
|
if (!text) return void 0;
|
|
61
60
|
try {
|
|
62
61
|
return JSON.parse(text);
|
|
@@ -66,8 +65,8 @@ function createLink(options) {
|
|
|
66
65
|
} }
|
|
67
66
|
});
|
|
68
67
|
let decoded;
|
|
69
|
-
if (
|
|
70
|
-
else if (
|
|
68
|
+
if (resolvedProtocol === "messagepack") decoded = decode(new Uint8Array(data));
|
|
69
|
+
else if (resolvedProtocol === "devalue") {
|
|
71
70
|
const { decode: devalueDecode } = await import("../../../codec/devalue.mjs");
|
|
72
71
|
decoded = data ? devalueDecode(data) : void 0;
|
|
73
72
|
} else decoded = data;
|
package/dist/client/client.mjs
CHANGED
|
@@ -23,7 +23,7 @@ function createClientProxy(link, path) {
|
|
|
23
23
|
if (typeof prop !== "string") return void 0;
|
|
24
24
|
let cached = cache.get(prop);
|
|
25
25
|
if (!cached) {
|
|
26
|
-
cached = createClientProxy(link,
|
|
26
|
+
cached = createClientProxy(link, [...path, prop]);
|
|
27
27
|
cache.set(prop, cached);
|
|
28
28
|
}
|
|
29
29
|
return cached;
|
package/dist/client/index.d.mts
CHANGED
|
@@ -2,6 +2,5 @@ import { SilgiError, SilgiErrorCode, SilgiErrorJSON, isDefinedError } from "../c
|
|
|
2
2
|
import { Client, ClientContext, ClientLink, ClientOptions, ClientRest, InferClientInputs, InferClientOutputs, NestedClient } from "./types.mjs";
|
|
3
3
|
import { SafeResult, createClient, safe } from "./client.mjs";
|
|
4
4
|
import { DynamicLink, LinkSelector } from "./dynamic-link.mjs";
|
|
5
|
-
import { mergeClients } from "./merge.mjs";
|
|
6
5
|
import { ClientInterceptors, withInterceptors } from "./interceptor.mjs";
|
|
7
|
-
export { type Client, type ClientContext, type ClientInterceptors, type ClientLink, type ClientOptions, type ClientRest, DynamicLink, type InferClientInputs, type InferClientOutputs, type LinkSelector, type NestedClient, type SafeResult, SilgiError, type SilgiErrorCode, type SilgiErrorJSON, createClient, isDefinedError,
|
|
6
|
+
export { type Client, type ClientContext, type ClientInterceptors, type ClientLink, type ClientOptions, type ClientRest, DynamicLink, type InferClientInputs, type InferClientOutputs, type LinkSelector, type NestedClient, type SafeResult, SilgiError, type SilgiErrorCode, type SilgiErrorJSON, createClient, isDefinedError, safe, withInterceptors };
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SilgiError, isDefinedError } from "../core/error.mjs";
|
|
2
2
|
import { createClient, safe } from "./client.mjs";
|
|
3
3
|
import { DynamicLink } from "./dynamic-link.mjs";
|
|
4
|
-
import { mergeClients } from "./merge.mjs";
|
|
5
4
|
import { withInterceptors } from "./interceptor.mjs";
|
|
6
|
-
export { DynamicLink, SilgiError, createClient, isDefinedError,
|
|
5
|
+
export { DynamicLink, SilgiError, createClient, isDefinedError, safe, withInterceptors };
|
|
@@ -31,7 +31,7 @@ var BatchLink = class {
|
|
|
31
31
|
if (batch.length === 0) return;
|
|
32
32
|
const chunks = [];
|
|
33
33
|
for (let i = 0; i < batch.length; i += this.#maxSize) chunks.push(batch.slice(i, i + this.#maxSize));
|
|
34
|
-
|
|
34
|
+
await Promise.all(chunks.map((chunk) => this.#sendChunk(chunk)));
|
|
35
35
|
}
|
|
36
36
|
async #sendChunk(chunk) {
|
|
37
37
|
const batchBody = chunk.map((call) => ({
|
|
@@ -40,7 +40,7 @@ var BatchLink = class {
|
|
|
40
40
|
body: call.input
|
|
41
41
|
}));
|
|
42
42
|
try {
|
|
43
|
-
const responses = await this.#link.call([this.#batchPath.slice(1)], batchBody, { signal: chunk[0]
|
|
43
|
+
const responses = await this.#link.call([this.#batchPath.slice(1)], batchBody, { signal: chunk.length === 1 ? chunk[0].options.signal : AbortSignal.any(chunk.map((c) => c.options.signal).filter(Boolean)) });
|
|
44
44
|
if (!Array.isArray(responses)) {
|
|
45
45
|
for (const call of chunk) call.reject(/* @__PURE__ */ new Error("Invalid batch response"));
|
|
46
46
|
return;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ClientContext, ClientLink } from "../types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/client/plugins/circuit-breaker.d.ts
|
|
4
|
+
type CircuitState = 'closed' | 'open' | 'half-open';
|
|
5
|
+
declare class CircuitBreakerOpenError extends Error {
|
|
6
|
+
readonly state: CircuitState;
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
interface CircuitBreakerOptions {
|
|
10
|
+
/** Number of consecutive failures before opening (default: 5) */
|
|
11
|
+
failureThreshold?: number;
|
|
12
|
+
/** Time in ms to wait before moving to half-open (default: 30000) */
|
|
13
|
+
resetTimeout?: number;
|
|
14
|
+
/** Called when circuit state changes */
|
|
15
|
+
onStateChange?: (state: CircuitState, info: {
|
|
16
|
+
failures: number;
|
|
17
|
+
}) => void;
|
|
18
|
+
}
|
|
19
|
+
declare function withCircuitBreaker<TClientContext extends ClientContext>(link: ClientLink<TClientContext>, options?: CircuitBreakerOptions): ClientLink<TClientContext> & {
|
|
20
|
+
getState: () => CircuitState;
|
|
21
|
+
reset: () => void;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { CircuitBreakerOpenError, CircuitBreakerOptions, CircuitState, withCircuitBreaker };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/client/plugins/circuit-breaker.ts
|
|
2
|
+
var CircuitBreakerOpenError = class extends Error {
|
|
3
|
+
state = "open";
|
|
4
|
+
constructor() {
|
|
5
|
+
super("Circuit breaker is open — requests are blocked. Try again later.");
|
|
6
|
+
this.name = "CircuitBreakerOpenError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function withCircuitBreaker(link, options = {}) {
|
|
10
|
+
const threshold = options.failureThreshold ?? 5;
|
|
11
|
+
const resetTimeout = options.resetTimeout ?? 3e4;
|
|
12
|
+
let state = "closed";
|
|
13
|
+
let failures = 0;
|
|
14
|
+
let openedAt = 0;
|
|
15
|
+
let probeSent = false;
|
|
16
|
+
function setState(newState) {
|
|
17
|
+
if (state !== newState) {
|
|
18
|
+
state = newState;
|
|
19
|
+
if (newState !== "half-open") probeSent = false;
|
|
20
|
+
options.onStateChange?.(state, { failures });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function recordSuccess() {
|
|
24
|
+
failures = 0;
|
|
25
|
+
setState("closed");
|
|
26
|
+
}
|
|
27
|
+
function recordFailure() {
|
|
28
|
+
failures++;
|
|
29
|
+
if (failures >= threshold) {
|
|
30
|
+
openedAt = Date.now();
|
|
31
|
+
setState("open");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
async call(path, input, callOptions) {
|
|
36
|
+
if (state === "open") if (Date.now() - openedAt >= resetTimeout) setState("half-open");
|
|
37
|
+
else throw new CircuitBreakerOpenError();
|
|
38
|
+
if (state === "half-open") {
|
|
39
|
+
if (probeSent) throw new CircuitBreakerOpenError();
|
|
40
|
+
probeSent = true;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const result = await link.call(path, input, callOptions);
|
|
44
|
+
recordSuccess();
|
|
45
|
+
return result;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
recordFailure();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
getState: () => state,
|
|
52
|
+
reset: () => {
|
|
53
|
+
failures = 0;
|
|
54
|
+
probeSent = false;
|
|
55
|
+
setState("closed");
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { CircuitBreakerOpenError, withCircuitBreaker };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { RetryOptions, withRetry } from "./retry.mjs";
|
|
2
|
+
import { CircuitBreakerOpenError, CircuitBreakerOptions, CircuitState, withCircuitBreaker } from "./circuit-breaker.mjs";
|
|
3
|
+
import { TimeoutOptions, withTimeout } from "./timeout.mjs";
|
|
2
4
|
import { BatchLink, BatchLinkOptions } from "./batch.mjs";
|
|
3
5
|
import { DedupeOptions, withDedupe } from "./dedupe.mjs";
|
|
4
6
|
import { CSRFLinkOptions, withCSRF } from "./csrf.mjs";
|
|
5
|
-
export { BatchLink, type BatchLinkOptions, type CSRFLinkOptions, type DedupeOptions, type RetryOptions, withCSRF, withDedupe, withRetry };
|
|
7
|
+
export { BatchLink, type BatchLinkOptions, type CSRFLinkOptions, CircuitBreakerOpenError, type CircuitBreakerOptions, type CircuitState, type DedupeOptions, type RetryOptions, type TimeoutOptions, withCSRF, withCircuitBreaker, withDedupe, withRetry, withTimeout };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { withRetry } from "./retry.mjs";
|
|
2
|
+
import { CircuitBreakerOpenError, withCircuitBreaker } from "./circuit-breaker.mjs";
|
|
3
|
+
import { withTimeout } from "./timeout.mjs";
|
|
2
4
|
import { BatchLink } from "./batch.mjs";
|
|
3
5
|
import { withDedupe } from "./dedupe.mjs";
|
|
4
6
|
import { withCSRF } from "./csrf.mjs";
|
|
5
|
-
export { BatchLink, withCSRF, withDedupe, withRetry };
|
|
7
|
+
export { BatchLink, CircuitBreakerOpenError, withCSRF, withCircuitBreaker, withDedupe, withRetry, withTimeout };
|
|
@@ -2,9 +2,30 @@ import { ClientContext, ClientLink } from "../types.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/client/plugins/retry.d.ts
|
|
4
4
|
interface RetryOptions {
|
|
5
|
+
/** Maximum number of retry attempts (default: 3) */
|
|
5
6
|
maxRetries?: number;
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Base delay in ms for exponential backoff (default: 1000).
|
|
9
|
+
* Actual delay: `baseDelay * 2^attempt + jitter`
|
|
10
|
+
* Or pass a function: `(attempt: number) => delayMs`
|
|
11
|
+
*/
|
|
12
|
+
baseDelay?: number | ((attempt: number) => number);
|
|
13
|
+
/** Add random jitter 0-25% to prevent thundering herd (default: true) */
|
|
14
|
+
jitter?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* HTTP status codes to retry on (default: [408, 429, 500, 502, 503, 504]).
|
|
17
|
+
* Network errors (no status) are always retried unless shouldRetry returns false.
|
|
18
|
+
*/
|
|
19
|
+
retryOn?: number[];
|
|
20
|
+
/** Custom retry predicate — return false to stop retrying */
|
|
21
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
22
|
+
/** Called before each retry attempt */
|
|
23
|
+
onRetry?: (info: {
|
|
24
|
+
attempt: number;
|
|
25
|
+
delay: number;
|
|
26
|
+
error: unknown;
|
|
27
|
+
path: readonly string[];
|
|
28
|
+
}) => void;
|
|
8
29
|
}
|
|
9
30
|
declare function withRetry<TClientContext extends ClientContext>(link: ClientLink<TClientContext>, options?: RetryOptions): ClientLink<TClientContext>;
|
|
10
31
|
//#endregion
|