silgi 0.1.0-beta.5 → 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 +11 -3
- package/dist/adapters/aws-lambda.mjs +27 -41
- package/dist/adapters/express.mjs +18 -31
- 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.mjs +2 -2
- package/dist/caller.mjs +4 -11
- 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/index.d.mts +1 -2
- package/dist/client/index.mjs +1 -2
- package/dist/codec/devalue.mjs +1 -27
- package/dist/codec/msgpack.mjs +1 -22
- package/dist/codec/sanitize.mjs +38 -0
- package/dist/compile.mjs +15 -5
- package/dist/core/dispatch.mjs +62 -0
- package/dist/core/handler.d.mts +6 -0
- package/dist/core/handler.mjs +49 -241
- package/dist/core/input.mjs +4 -0
- package/dist/core/serve.d.mts +51 -0
- package/dist/core/serve.mjs +46 -8
- package/dist/core/sse.mjs +7 -2
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +1 -1
- package/dist/integrations/ai/index.mjs +4 -3
- package/dist/integrations/react/index.mjs +1 -2
- package/dist/integrations/tanstack-query/ssr.d.mts +10 -1
- package/dist/integrations/tanstack-query/ssr.mjs +15 -2
- package/dist/plugins/analytics.d.mts +16 -1
- package/dist/plugins/analytics.mjs +191 -1
- package/dist/plugins/cookies.mjs +5 -7
- package/dist/plugins/cors.mjs +6 -4
- package/dist/plugins/index.d.mts +1 -3
- package/dist/plugins/index.mjs +2 -4
- package/dist/scalar.d.mts +0 -1
- package/dist/scalar.mjs +19 -1
- package/dist/silgi.d.mts +10 -13
- package/dist/silgi.mjs +23 -3
- package/dist/ws.d.mts +44 -1
- package/dist/ws.mjs +38 -5
- package/package.json +2 -1
- package/dist/client/merge.d.mts +0 -28
- package/dist/client/merge.mjs +0 -30
- 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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/adapters/_fetch-adapter.d.ts
|
|
2
|
+
interface FetchAdapterConfig<TCtx extends Record<string, unknown>> {
|
|
3
|
+
/** Route prefix to strip. Default: "/api/rpc" */
|
|
4
|
+
prefix?: string;
|
|
5
|
+
/** Context factory — receives the Request (or framework event via eventMap). */
|
|
6
|
+
context?: (req: Request) => TCtx | Promise<TCtx>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* For adapters where the context factory needs access to a framework event
|
|
10
|
+
* (SvelteKit RequestEvent, SolidStart event), use this extended config.
|
|
11
|
+
*/
|
|
12
|
+
interface FetchAdapterConfigWithEvent<TCtx extends Record<string, unknown>, TEvent = any> {
|
|
13
|
+
prefix?: string;
|
|
14
|
+
/** Context factory — receives the framework event, not raw Request. */
|
|
15
|
+
context?: (event: TEvent) => TCtx | Promise<TCtx>;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { FetchAdapterConfig, FetchAdapterConfigWithEvent };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createFetchHandler } from "../core/handler.mjs";
|
|
2
|
+
//#region src/adapters/_fetch-adapter.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shared factory for fetch-passthrough adapters.
|
|
5
|
+
*
|
|
6
|
+
* Next.js, Astro, Remix, SvelteKit, and SolidStart all do the same thing:
|
|
7
|
+
* strip URL prefix → rewrite request → dispatch to fetch handler.
|
|
8
|
+
*
|
|
9
|
+
* This module eliminates the duplication. Each adapter file becomes a thin
|
|
10
|
+
* wrapper that extracts the framework-specific Request and calls this factory.
|
|
11
|
+
*/
|
|
12
|
+
/** Strip prefix from request URL and create a rewritten Request. */
|
|
13
|
+
function rewriteRequest(request, prefix) {
|
|
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 new Request(new URL(pathname + url.search, url.origin), request);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a fetch-passthrough adapter that strips a prefix and delegates to handler.
|
|
24
|
+
* Used by adapters that receive a standard Request (Next.js, Astro, Remix).
|
|
25
|
+
*/
|
|
26
|
+
function createFetchAdapter(router, options, defaultPrefix) {
|
|
27
|
+
const prefix = options.prefix ?? defaultPrefix;
|
|
28
|
+
const handler = createFetchHandler(router, options.context ?? (() => ({})));
|
|
29
|
+
return (request) => {
|
|
30
|
+
return handler(rewriteRequest(request, prefix));
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a fetch-passthrough adapter for frameworks that pass an event object
|
|
35
|
+
* with a `.request` property (SvelteKit, SolidStart).
|
|
36
|
+
* Uses a WeakMap to safely pass the event into the context factory per-request.
|
|
37
|
+
*/
|
|
38
|
+
function createEventFetchAdapter(router, options, defaultPrefix, extractRequest) {
|
|
39
|
+
const prefix = options.prefix ?? defaultPrefix;
|
|
40
|
+
const requestEventMap = /* @__PURE__ */ new WeakMap();
|
|
41
|
+
const handler = createFetchHandler(router, (_req) => {
|
|
42
|
+
const eventRef = requestEventMap.get(_req);
|
|
43
|
+
if (options.context && eventRef) return options.context(eventRef);
|
|
44
|
+
return {};
|
|
45
|
+
});
|
|
46
|
+
return (event) => {
|
|
47
|
+
const rewritten = rewriteRequest(extractRequest(event), prefix);
|
|
48
|
+
requestEventMap.set(rewritten, event);
|
|
49
|
+
return handler(rewritten);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { createEventFetchAdapter, createFetchAdapter };
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { FetchAdapterConfig } from "./_fetch-adapter.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/adapters/astro.d.ts
|
|
4
|
-
interface AstroAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
-
context?: (request: Request) => TCtx | Promise<TCtx>;
|
|
6
|
-
prefix?: string;
|
|
7
|
-
}
|
|
5
|
+
interface AstroAdapterOptions<TCtx extends Record<string, unknown>> extends FetchAdapterConfig<TCtx> {}
|
|
8
6
|
/**
|
|
9
7
|
* Create an Astro API route handler.
|
|
10
|
-
* Astro passes { request
|
|
8
|
+
* Astro passes { request, params } — we extract request and delegate.
|
|
11
9
|
*/
|
|
12
10
|
declare function silgiAstro<TCtx extends Record<string, unknown>>(router: RouterDef, options?: AstroAdapterOptions<TCtx>): (ctx: {
|
|
13
11
|
request: Request;
|
|
14
12
|
params: Record<string, string>;
|
|
15
|
-
}) => Promise<Response>;
|
|
13
|
+
}) => Response | Promise<Response>;
|
|
16
14
|
//#endregion
|
|
17
15
|
export { AstroAdapterOptions, silgiAstro };
|
package/dist/adapters/astro.mjs
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
|
+
import { createFetchAdapter } from "./_fetch-adapter.mjs";
|
|
1
2
|
//#region src/adapters/astro.ts
|
|
2
3
|
/**
|
|
4
|
+
* Astro adapter — use Silgi with Astro API routes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // src/pages/api/rpc/[...path].ts
|
|
9
|
+
* import { silgiAstro } from "silgi/astro"
|
|
10
|
+
* import { appRouter } from "~/server/rpc"
|
|
11
|
+
*
|
|
12
|
+
* const handler = silgiAstro(appRouter, {
|
|
13
|
+
* prefix: "/api/rpc",
|
|
14
|
+
* context: (req) => ({ db: getDB() }),
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* export const GET = handler
|
|
18
|
+
* export const POST = handler
|
|
19
|
+
* export const ALL = handler
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
3
23
|
* Create an Astro API route handler.
|
|
4
|
-
* Astro passes { request
|
|
24
|
+
* Astro passes { request, params } — we extract request and delegate.
|
|
5
25
|
*/
|
|
6
26
|
function silgiAstro(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
|
-
};
|
|
27
|
+
const handler = createFetchAdapter(router, options, "/api/rpc");
|
|
28
|
+
return ({ request }) => handler(request);
|
|
22
29
|
}
|
|
23
30
|
//#endregion
|
|
24
31
|
export { silgiAstro };
|
|
@@ -8,12 +8,20 @@ interface LambdaAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
|
8
8
|
prefix?: string;
|
|
9
9
|
}
|
|
10
10
|
interface LambdaEvent {
|
|
11
|
-
httpMethod
|
|
12
|
-
path
|
|
11
|
+
httpMethod?: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
version?: string;
|
|
14
|
+
rawPath?: string;
|
|
15
|
+
requestContext?: {
|
|
16
|
+
http?: {
|
|
17
|
+
method: string;
|
|
18
|
+
path: string;
|
|
19
|
+
};
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
13
22
|
body: string | null;
|
|
14
23
|
headers: Record<string, string>;
|
|
15
24
|
queryStringParameters: Record<string, string> | null;
|
|
16
|
-
requestContext?: Record<string, unknown>;
|
|
17
25
|
isBase64Encoded?: boolean;
|
|
18
26
|
}
|
|
19
27
|
interface LambdaContext {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
-
import { ValidationError } from "../core/schema.mjs";
|
|
3
1
|
import { compileRouter } from "../compile.mjs";
|
|
2
|
+
import { buildContext, isMethodAllowed, parseQueryData, serializeError } from "../core/dispatch.mjs";
|
|
4
3
|
//#region src/adapters/aws-lambda.ts
|
|
5
4
|
/**
|
|
6
5
|
* AWS Lambda adapter — deploy Silgi as a Lambda function.
|
|
@@ -22,14 +21,17 @@ import { compileRouter } from "../compile.mjs";
|
|
|
22
21
|
function silgiLambda(router, options = {}) {
|
|
23
22
|
const flatRouter = compileRouter(router);
|
|
24
23
|
const prefix = options.prefix ?? "";
|
|
24
|
+
const JSON_HDR = { "content-type": "application/json" };
|
|
25
25
|
return async (event, context) => {
|
|
26
|
-
|
|
26
|
+
const isV2 = event.version === "2.0";
|
|
27
|
+
const method = isV2 ? event.requestContext?.http?.method ?? "GET" : event.httpMethod ?? "GET";
|
|
28
|
+
let pathname = isV2 ? event.rawPath ?? "/" : event.path ?? "/";
|
|
27
29
|
if (prefix && pathname.startsWith(prefix)) pathname = pathname.slice(prefix.length);
|
|
28
30
|
if (pathname.startsWith("/")) pathname = pathname.slice(1);
|
|
29
|
-
const match = flatRouter(
|
|
31
|
+
const match = flatRouter(method, "/" + pathname);
|
|
30
32
|
if (!match) return {
|
|
31
33
|
statusCode: 404,
|
|
32
|
-
headers:
|
|
34
|
+
headers: JSON_HDR,
|
|
33
35
|
body: JSON.stringify({
|
|
34
36
|
code: "NOT_FOUND",
|
|
35
37
|
status: 404,
|
|
@@ -37,14 +39,20 @@ function silgiLambda(router, options = {}) {
|
|
|
37
39
|
})
|
|
38
40
|
};
|
|
39
41
|
const route = match.data;
|
|
42
|
+
if (!isMethodAllowed(method, route.method)) return {
|
|
43
|
+
statusCode: 405,
|
|
44
|
+
headers: {
|
|
45
|
+
...JSON_HDR,
|
|
46
|
+
allow: route.method
|
|
47
|
+
},
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
code: "METHOD_NOT_ALLOWED",
|
|
50
|
+
status: 405,
|
|
51
|
+
message: `Method ${method} not allowed`
|
|
52
|
+
})
|
|
53
|
+
};
|
|
40
54
|
try {
|
|
41
|
-
const ctx =
|
|
42
|
-
if (match.params) ctx.params = match.params;
|
|
43
|
-
if (options.context) {
|
|
44
|
-
const baseCtx = await options.context(event);
|
|
45
|
-
const keys = Object.keys(baseCtx);
|
|
46
|
-
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
47
|
-
}
|
|
55
|
+
const ctx = buildContext(options.context ? await options.context(event) : void 0, match.params);
|
|
48
56
|
let input;
|
|
49
57
|
if (event.body) {
|
|
50
58
|
const body = event.isBase64Encoded ? Buffer.from(event.body, "base64").toString("utf-8") : event.body;
|
|
@@ -53,7 +61,7 @@ function silgiLambda(router, options = {}) {
|
|
|
53
61
|
} catch {
|
|
54
62
|
return {
|
|
55
63
|
statusCode: 400,
|
|
56
|
-
headers:
|
|
64
|
+
headers: JSON_HDR,
|
|
57
65
|
body: JSON.stringify({
|
|
58
66
|
code: "BAD_REQUEST",
|
|
59
67
|
status: 400,
|
|
@@ -61,43 +69,21 @@ function silgiLambda(router, options = {}) {
|
|
|
61
69
|
})
|
|
62
70
|
};
|
|
63
71
|
}
|
|
64
|
-
} else if (event.queryStringParameters?.data)
|
|
65
|
-
input = JSON.parse(event.queryStringParameters.data);
|
|
66
|
-
} catch {
|
|
67
|
-
return {
|
|
68
|
-
statusCode: 400,
|
|
69
|
-
headers: { "content-type": "application/json" },
|
|
70
|
-
body: JSON.stringify({
|
|
71
|
-
code: "BAD_REQUEST",
|
|
72
|
-
status: 400,
|
|
73
|
-
message: "Invalid JSON in data parameter"
|
|
74
|
-
})
|
|
75
|
-
};
|
|
76
|
-
}
|
|
72
|
+
} else if (event.queryStringParameters?.data) input = parseQueryData(event.queryStringParameters.data);
|
|
77
73
|
const timeoutMs = context?.getRemainingTimeInMillis ? context.getRemainingTimeInMillis() - 500 : 3e4;
|
|
78
74
|
const signal = AbortSignal.timeout(Math.max(timeoutMs, 1e3));
|
|
79
75
|
const output = await route.handler(ctx, input, signal);
|
|
80
76
|
return {
|
|
81
77
|
statusCode: 200,
|
|
82
|
-
headers:
|
|
78
|
+
headers: JSON_HDR,
|
|
83
79
|
body: JSON.stringify(output)
|
|
84
80
|
};
|
|
85
81
|
} catch (error) {
|
|
86
|
-
|
|
87
|
-
statusCode: 400,
|
|
88
|
-
headers: { "content-type": "application/json" },
|
|
89
|
-
body: JSON.stringify({
|
|
90
|
-
code: "BAD_REQUEST",
|
|
91
|
-
status: 400,
|
|
92
|
-
message: error.message,
|
|
93
|
-
data: { issues: error.issues }
|
|
94
|
-
})
|
|
95
|
-
};
|
|
96
|
-
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
82
|
+
const body = serializeError(error);
|
|
97
83
|
return {
|
|
98
|
-
statusCode:
|
|
99
|
-
headers:
|
|
100
|
-
body: JSON.stringify(
|
|
84
|
+
statusCode: body.status,
|
|
85
|
+
headers: JSON_HDR,
|
|
86
|
+
body: JSON.stringify(body)
|
|
101
87
|
};
|
|
102
88
|
}
|
|
103
89
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
-
import { ValidationError } from "../core/schema.mjs";
|
|
3
1
|
import { compileRouter } from "../compile.mjs";
|
|
2
|
+
import { buildContext, isMethodAllowed, serializeError } from "../core/dispatch.mjs";
|
|
4
3
|
import { iteratorToEventStream } from "../core/sse.mjs";
|
|
5
4
|
//#region src/adapters/express.ts
|
|
6
5
|
/**
|
|
@@ -32,24 +31,17 @@ function silgiExpress(router, options = {}) {
|
|
|
32
31
|
const match = flatRouter(req.method, "/" + pathname);
|
|
33
32
|
if (!match) return next();
|
|
34
33
|
const route = match.data;
|
|
35
|
-
|
|
36
|
-
if (route.method !== "*" && method !== route.method && method !== "OPTIONS" && !(method === "GET" && route.method === "POST")) {
|
|
34
|
+
if (!isMethodAllowed(req.method, route.method)) {
|
|
37
35
|
res.status(405).set("allow", route.method).json({
|
|
38
36
|
code: "METHOD_NOT_ALLOWED",
|
|
39
37
|
status: 405,
|
|
40
|
-
message: `Method ${method} not allowed`
|
|
38
|
+
message: `Method ${req.method} not allowed`
|
|
41
39
|
});
|
|
42
40
|
return;
|
|
43
41
|
}
|
|
44
42
|
const handle = async () => {
|
|
45
43
|
try {
|
|
46
|
-
const ctx =
|
|
47
|
-
if (match.params) ctx.params = match.params;
|
|
48
|
-
if (options.context) {
|
|
49
|
-
const baseCtx = await options.context(req);
|
|
50
|
-
const keys = Object.keys(baseCtx);
|
|
51
|
-
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
52
|
-
}
|
|
44
|
+
const ctx = buildContext(options.context ? await options.context(req) : void 0, match.params);
|
|
53
45
|
let input;
|
|
54
46
|
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") input = req.body;
|
|
55
47
|
else if (req.query?.data) if (typeof req.query.data === "string") try {
|
|
@@ -76,7 +68,8 @@ function silgiExpress(router, options = {}) {
|
|
|
76
68
|
} else if (output instanceof ReadableStream) {
|
|
77
69
|
res.setHeader("content-type", "application/octet-stream");
|
|
78
70
|
const reader = output.getReader();
|
|
79
|
-
|
|
71
|
+
req.on("close", () => reader.cancel());
|
|
72
|
+
try {
|
|
80
73
|
while (true) {
|
|
81
74
|
const { done, value } = await reader.read();
|
|
82
75
|
if (done) {
|
|
@@ -85,14 +78,16 @@ function silgiExpress(router, options = {}) {
|
|
|
85
78
|
}
|
|
86
79
|
res.write(value);
|
|
87
80
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
81
|
+
} finally {
|
|
82
|
+
reader.releaseLock();
|
|
83
|
+
}
|
|
90
84
|
} else if (output && typeof output === "object" && Symbol.asyncIterator in output) {
|
|
91
85
|
const stream = iteratorToEventStream(output);
|
|
92
86
|
res.setHeader("content-type", "text/event-stream");
|
|
93
87
|
res.setHeader("cache-control", "no-cache");
|
|
94
88
|
const reader = stream.getReader();
|
|
95
|
-
|
|
89
|
+
req.on("close", () => reader.cancel());
|
|
90
|
+
try {
|
|
96
91
|
while (true) {
|
|
97
92
|
const { done, value } = await reader.read();
|
|
98
93
|
if (done) {
|
|
@@ -101,30 +96,22 @@ function silgiExpress(router, options = {}) {
|
|
|
101
96
|
}
|
|
102
97
|
res.write(value);
|
|
103
98
|
}
|
|
104
|
-
}
|
|
105
|
-
|
|
99
|
+
} finally {
|
|
100
|
+
reader.releaseLock();
|
|
101
|
+
}
|
|
106
102
|
} else res.json(output);
|
|
107
103
|
} finally {
|
|
108
104
|
req.removeListener("close", onClose);
|
|
109
105
|
}
|
|
110
106
|
} catch (error) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
code: "BAD_REQUEST",
|
|
114
|
-
status: 400,
|
|
115
|
-
message: error.message,
|
|
116
|
-
data: { issues: error.issues }
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
121
|
-
res.status(e.status).json(e.toJSON());
|
|
107
|
+
const body = serializeError(error);
|
|
108
|
+
res.status(body.status).json(body);
|
|
122
109
|
}
|
|
123
110
|
};
|
|
124
111
|
handle().catch((error) => {
|
|
125
112
|
if (!res.headersSent) {
|
|
126
|
-
const
|
|
127
|
-
res.status(
|
|
113
|
+
const body = serializeError(error);
|
|
114
|
+
res.status(body.status).json(body);
|
|
128
115
|
}
|
|
129
116
|
});
|
|
130
117
|
};
|
|
@@ -30,8 +30,13 @@ declare class MessagePortLink<TCtx extends ClientContext = ClientContext> implem
|
|
|
30
30
|
addEventListener(type: 'message', handler: (event: {
|
|
31
31
|
data: unknown;
|
|
32
32
|
}) => void): void;
|
|
33
|
+
removeEventListener?(type: 'message', handler: (event: {
|
|
34
|
+
data: unknown;
|
|
35
|
+
}) => void): void;
|
|
33
36
|
});
|
|
34
|
-
call(path: readonly string[], input: unknown,
|
|
37
|
+
call(path: readonly string[], input: unknown, options: ClientOptions<TCtx>): Promise<unknown>;
|
|
38
|
+
/** Reject all pending calls and stop listening. */
|
|
39
|
+
dispose(): void;
|
|
35
40
|
}
|
|
36
41
|
//#endregion
|
|
37
42
|
export { MessagePortAdapterOptions, MessagePortLink, silgiMessagePort };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SilgiError
|
|
2
|
-
import { ValidationError } from "../core/schema.mjs";
|
|
1
|
+
import { SilgiError } from "../core/error.mjs";
|
|
3
2
|
import { compileRouter } from "../compile.mjs";
|
|
3
|
+
import { buildContext, serializeError } from "../core/dispatch.mjs";
|
|
4
4
|
//#region src/adapters/message-port.ts
|
|
5
5
|
/**
|
|
6
6
|
* Message Port adapter — use Silgi over MessagePort/MessageChannel.
|
|
@@ -51,15 +51,9 @@ function silgiMessagePort(router, port, options = {}) {
|
|
|
51
51
|
}
|
|
52
52
|
const route = match.data;
|
|
53
53
|
try {
|
|
54
|
-
const ctx =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const baseCtx = await options.context();
|
|
58
|
-
const keys = Object.keys(baseCtx);
|
|
59
|
-
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
60
|
-
}
|
|
61
|
-
const signal = new AbortController().signal;
|
|
62
|
-
const result = await route.handler(ctx, msg.input, signal);
|
|
54
|
+
const ctx = buildContext(options.context ? await options.context() : void 0, match.params);
|
|
55
|
+
const ac = new AbortController();
|
|
56
|
+
const result = await route.handler(ctx, msg.input, ac.signal);
|
|
63
57
|
port.postMessage({
|
|
64
58
|
__silgi: true,
|
|
65
59
|
__type: "response",
|
|
@@ -67,17 +61,11 @@ function silgiMessagePort(router, port, options = {}) {
|
|
|
67
61
|
result
|
|
68
62
|
});
|
|
69
63
|
} catch (error) {
|
|
70
|
-
const e = error instanceof ValidationError ? {
|
|
71
|
-
code: "BAD_REQUEST",
|
|
72
|
-
status: 400,
|
|
73
|
-
message: error.message,
|
|
74
|
-
data: { issues: error.issues }
|
|
75
|
-
} : error instanceof SilgiError ? error.toJSON() : toSilgiError(error).toJSON();
|
|
76
64
|
port.postMessage({
|
|
77
65
|
__silgi: true,
|
|
78
66
|
__type: "response",
|
|
79
67
|
id: msg.id,
|
|
80
|
-
error:
|
|
68
|
+
error: serializeError(error)
|
|
81
69
|
});
|
|
82
70
|
}
|
|
83
71
|
};
|
|
@@ -92,9 +80,10 @@ var MessagePortLink = class {
|
|
|
92
80
|
#port;
|
|
93
81
|
#pending = /* @__PURE__ */ new Map();
|
|
94
82
|
#nextId = 1;
|
|
83
|
+
#messageHandler;
|
|
95
84
|
constructor(port) {
|
|
96
85
|
this.#port = port;
|
|
97
|
-
|
|
86
|
+
this.#messageHandler = (event) => {
|
|
98
87
|
const msg = event.data;
|
|
99
88
|
if (!msg || typeof msg !== "object" || !msg.__silgi || msg.__type !== "response") return;
|
|
100
89
|
const pending = this.#pending.get(msg.id);
|
|
@@ -106,15 +95,23 @@ var MessagePortLink = class {
|
|
|
106
95
|
data: msg.error.data
|
|
107
96
|
}));
|
|
108
97
|
else pending.resolve(msg.result);
|
|
109
|
-
}
|
|
98
|
+
};
|
|
99
|
+
port.addEventListener("message", this.#messageHandler);
|
|
110
100
|
}
|
|
111
|
-
call(path, input,
|
|
101
|
+
call(path, input, options) {
|
|
112
102
|
return new Promise((resolve, reject) => {
|
|
113
103
|
const id = String(this.#nextId++);
|
|
114
104
|
this.#pending.set(id, {
|
|
115
105
|
resolve,
|
|
116
106
|
reject
|
|
117
107
|
});
|
|
108
|
+
if (options.signal) options.signal.addEventListener("abort", () => {
|
|
109
|
+
const pending = this.#pending.get(id);
|
|
110
|
+
if (pending) {
|
|
111
|
+
this.#pending.delete(id);
|
|
112
|
+
pending.reject(new DOMException("Aborted", "AbortError"));
|
|
113
|
+
}
|
|
114
|
+
}, { once: true });
|
|
118
115
|
this.#port.postMessage({
|
|
119
116
|
__silgi: true,
|
|
120
117
|
__type: "request",
|
|
@@ -124,6 +121,12 @@ var MessagePortLink = class {
|
|
|
124
121
|
});
|
|
125
122
|
});
|
|
126
123
|
}
|
|
124
|
+
/** Reject all pending calls and stop listening. */
|
|
125
|
+
dispose() {
|
|
126
|
+
for (const [, pending] of this.#pending) pending.reject(new DOMException("Link disposed", "AbortError"));
|
|
127
|
+
this.#pending.clear();
|
|
128
|
+
this.#port.removeEventListener?.("message", this.#messageHandler);
|
|
129
|
+
}
|
|
127
130
|
};
|
|
128
131
|
//#endregion
|
|
129
132
|
export { MessagePortLink, silgiMessagePort };
|
package/dist/adapters/nestjs.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { SilgiError, toSilgiError } from "../core/error.mjs";
|
|
2
|
-
import { ValidationError } from "../core/schema.mjs";
|
|
3
1
|
import { compileRouter } from "../compile.mjs";
|
|
2
|
+
import { buildContext, parseQueryData, serializeError } from "../core/dispatch.mjs";
|
|
4
3
|
//#region src/adapters/nestjs.ts
|
|
5
4
|
/**
|
|
6
5
|
* NestJS adapter — register Silgi as a NestJS controller.
|
|
@@ -47,32 +46,17 @@ function silgiNestHandler(router, options = {}) {
|
|
|
47
46
|
}
|
|
48
47
|
const route = match.data;
|
|
49
48
|
try {
|
|
50
|
-
const ctx =
|
|
51
|
-
if (match.params) ctx.params = match.params;
|
|
52
|
-
if (options.context) {
|
|
53
|
-
const baseCtx = await options.context(req);
|
|
54
|
-
const keys = Object.keys(baseCtx);
|
|
55
|
-
for (let i = 0; i < keys.length; i++) ctx[keys[i]] = baseCtx[keys[i]];
|
|
56
|
-
}
|
|
49
|
+
const ctx = buildContext(options.context ? await options.context(req) : void 0, match.params);
|
|
57
50
|
let input;
|
|
58
51
|
if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") input = req.body;
|
|
59
|
-
else if (req.query?.data) input = typeof req.query.data === "string" ?
|
|
52
|
+
else if (req.query?.data) input = typeof req.query.data === "string" ? parseQueryData(req.query.data) : req.query.data;
|
|
60
53
|
const ac = new AbortController();
|
|
61
54
|
req.on?.("close", () => ac.abort());
|
|
62
55
|
const output = await route.handler(ctx, input, ac.signal);
|
|
63
56
|
res.json(output);
|
|
64
57
|
} catch (error) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
code: "BAD_REQUEST",
|
|
68
|
-
status: 400,
|
|
69
|
-
message: error.message,
|
|
70
|
-
data: { issues: error.issues }
|
|
71
|
-
});
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const e = error instanceof SilgiError ? error : toSilgiError(error);
|
|
75
|
-
res.status(e.status).json(e.toJSON());
|
|
58
|
+
const body = serializeError(error);
|
|
59
|
+
res.status(body.status).json(body);
|
|
76
60
|
}
|
|
77
61
|
};
|
|
78
62
|
}
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { FetchAdapterConfig } from "./_fetch-adapter.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/adapters/nextjs.d.ts
|
|
4
|
-
interface NextjsAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
-
/** Context factory — receives the Next.js Request */
|
|
6
|
-
context?: (req: Request) => TCtx | Promise<TCtx>;
|
|
7
|
-
/** Route prefix to strip. Default: "/api/rpc" */
|
|
8
|
-
prefix?: string;
|
|
9
|
-
}
|
|
5
|
+
interface NextjsAdapterOptions<TCtx extends Record<string, unknown>> extends FetchAdapterConfig<TCtx> {}
|
|
10
6
|
/**
|
|
11
7
|
* Create a Next.js App Router route handler.
|
|
12
8
|
*
|
|
13
9
|
* Uses Silgi's handler() internally — full Fetch API support
|
|
14
10
|
* including content negotiation (JSON, MessagePack, devalue).
|
|
15
|
-
*
|
|
16
|
-
* The handler strips the prefix from the URL path before dispatching
|
|
17
|
-
* to the Silgi router.
|
|
18
11
|
*/
|
|
19
|
-
declare function silgiNextjs<TCtx extends Record<string, unknown>>(router: RouterDef, options?: NextjsAdapterOptions<TCtx>): (req: Request) => Promise<Response>;
|
|
12
|
+
declare function silgiNextjs<TCtx extends Record<string, unknown>>(router: RouterDef, options?: NextjsAdapterOptions<TCtx>): (req: Request) => Response | Promise<Response>;
|
|
20
13
|
//#endregion
|
|
21
14
|
export { NextjsAdapterOptions, silgiNextjs };
|
package/dist/adapters/nextjs.mjs
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
|
+
import { createFetchAdapter } from "./_fetch-adapter.mjs";
|
|
1
2
|
//#region src/adapters/nextjs.ts
|
|
2
3
|
/**
|
|
4
|
+
* Next.js adapter — use Silgi with App Router API routes.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // app/api/rpc/[...path]/route.ts
|
|
9
|
+
* import { silgiNextjs } from "silgi/nextjs"
|
|
10
|
+
* import { appRouter } from "~/server/rpc"
|
|
11
|
+
*
|
|
12
|
+
* const handler = silgiNextjs(appRouter, {
|
|
13
|
+
* context: (req) => ({ db: getDB() }),
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* export { handler as GET, handler as POST }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
3
20
|
* Create a Next.js App Router route handler.
|
|
4
21
|
*
|
|
5
22
|
* Uses Silgi's handler() internally — full Fetch API support
|
|
6
23
|
* including content negotiation (JSON, MessagePack, devalue).
|
|
7
|
-
*
|
|
8
|
-
* The handler strips the prefix from the URL path before dispatching
|
|
9
|
-
* to the Silgi router.
|
|
10
24
|
*/
|
|
11
25
|
function silgiNextjs(router, options = {}) {
|
|
12
|
-
|
|
13
|
-
let _handler = null;
|
|
14
|
-
return async (req) => {
|
|
15
|
-
if (!_handler) {
|
|
16
|
-
const { silgi } = await import("../silgi.mjs");
|
|
17
|
-
_handler = silgi({ context: options.context ?? (() => ({})) }).handler(router);
|
|
18
|
-
}
|
|
19
|
-
const url = new URL(req.url);
|
|
20
|
-
let pathname = url.pathname;
|
|
21
|
-
if (pathname.startsWith(prefix)) {
|
|
22
|
-
pathname = pathname.slice(prefix.length);
|
|
23
|
-
if (!pathname.startsWith("/")) pathname = "/" + pathname;
|
|
24
|
-
}
|
|
25
|
-
const rewritten = new Request(new URL(pathname + url.search, url.origin), req);
|
|
26
|
-
return _handler(rewritten);
|
|
27
|
-
};
|
|
26
|
+
return createFetchAdapter(router, options, "/api/rpc");
|
|
28
27
|
}
|
|
29
28
|
//#endregion
|
|
30
29
|
export { silgiNextjs };
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { RouterDef } from "../types.mjs";
|
|
2
|
+
import { FetchAdapterConfig } from "./_fetch-adapter.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/adapters/remix.d.ts
|
|
4
|
-
interface RemixAdapterOptions<TCtx extends Record<string, unknown>> {
|
|
5
|
-
context?: (request: Request) => TCtx | Promise<TCtx>;
|
|
6
|
-
prefix?: string;
|
|
7
|
-
}
|
|
5
|
+
interface RemixAdapterOptions<TCtx extends Record<string, unknown>> extends FetchAdapterConfig<TCtx> {}
|
|
8
6
|
/**
|
|
9
7
|
* Create a Remix action/loader handler.
|
|
10
|
-
*
|
|
8
|
+
* Remix passes { request, params } — we extract request and delegate.
|
|
11
9
|
*/
|
|
12
10
|
declare function silgiRemix<TCtx extends Record<string, unknown>>(router: RouterDef, options?: RemixAdapterOptions<TCtx>): (args: {
|
|
13
11
|
request: Request;
|
|
14
12
|
params: Record<string, string>;
|
|
15
|
-
}) => Promise<Response>;
|
|
13
|
+
}) => Response | Promise<Response>;
|
|
16
14
|
//#endregion
|
|
17
15
|
export { RemixAdapterOptions, silgiRemix };
|