silgi 0.0.14 → 0.1.0-beta.10
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/_fetch-adapter.d.mts +18 -0
- package/dist/adapters/_fetch-adapter.mjs +53 -0
- package/dist/adapters/astro.d.mts +15 -0
- package/dist/adapters/astro.mjs +31 -0
- package/dist/adapters/aws-lambda.d.mts +42 -0
- package/dist/adapters/aws-lambda.mjs +92 -0
- package/dist/adapters/express.d.mts +16 -0
- package/dist/adapters/express.mjs +120 -0
- package/dist/adapters/message-port.d.mts +42 -0
- package/dist/adapters/message-port.mjs +132 -0
- package/dist/adapters/nestjs.d.mts +25 -0
- package/dist/adapters/nestjs.mjs +75 -0
- package/dist/adapters/nextjs.d.mts +14 -0
- package/dist/adapters/nextjs.mjs +29 -0
- package/dist/adapters/peer.d.mts +27 -0
- package/dist/adapters/peer.mjs +36 -0
- package/dist/adapters/remix.d.mts +15 -0
- package/dist/adapters/remix.mjs +30 -0
- package/dist/adapters/solidstart.d.mts +12 -0
- package/dist/adapters/solidstart.mjs +29 -0
- package/dist/adapters/sveltekit.d.mts +14 -0
- package/dist/adapters/sveltekit.mjs +30 -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 +55 -0
- package/dist/builder.mjs +70 -0
- package/dist/callable.d.mts +19 -0
- package/dist/callable.mjs +42 -0
- package/dist/caller.mjs +90 -0
- package/dist/client/adapters/fetch/index.d.mts +19 -0
- package/dist/client/adapters/fetch/index.mjs +82 -0
- package/dist/client/adapters/ofetch/index.d.mts +57 -0
- package/dist/client/adapters/ofetch/index.mjs +100 -0
- package/dist/client/adapters/websocket/index.d.mts +20 -0
- package/dist/client/adapters/websocket/index.mjs +101 -0
- package/dist/client/client.d.mts +37 -0
- package/dist/client/client.mjs +80 -0
- package/dist/client/consume.d.mts +50 -0
- package/dist/client/consume.mjs +66 -0
- package/dist/client/dynamic-link.d.mts +16 -0
- package/dist/client/dynamic-link.mjs +19 -0
- package/dist/client/index.d.mts +6 -0
- package/dist/client/index.mjs +5 -0
- package/dist/client/interceptor.d.mts +31 -0
- package/dist/client/interceptor.mjs +34 -0
- package/dist/client/openapi.d.mts +29 -0
- package/dist/client/openapi.mjs +89 -0
- package/dist/client/plugins/batch.d.mts +26 -0
- package/dist/client/plugins/batch.mjs +64 -0
- package/dist/client/plugins/circuit-breaker.d.mts +24 -0
- package/dist/client/plugins/circuit-breaker.mjs +60 -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 +8 -0
- package/dist/client/plugins/index.mjs +8 -0
- package/dist/client/plugins/otel.d.mts +12 -0
- package/dist/client/plugins/otel.mjs +27 -0
- package/dist/client/plugins/retry.d.mts +34 -0
- package/dist/client/plugins/retry.mjs +79 -0
- package/dist/client/plugins/timeout.d.mts +10 -0
- package/dist/client/plugins/timeout.mjs +14 -0
- package/dist/client/server.d.mts +16 -0
- package/dist/client/server.mjs +62 -0
- package/dist/client/types.d.mts +29 -0
- package/dist/codec/devalue.d.mts +21 -0
- package/dist/codec/devalue.mjs +33 -0
- package/dist/codec/msgpack.d.mts +18 -0
- package/dist/codec/msgpack.mjs +45 -0
- package/dist/codec/sanitize.mjs +38 -0
- package/dist/compile.d.mts +46 -0
- package/dist/compile.mjs +332 -0
- package/dist/contract.d.mts +36 -0
- package/dist/contract.mjs +40 -0
- package/dist/core/codec.mjs +67 -0
- package/dist/core/dispatch.mjs +62 -0
- package/dist/core/error.d.mts +104 -0
- package/dist/core/error.mjs +139 -0
- package/dist/core/handler.d.mts +6 -0
- package/dist/core/handler.mjs +148 -0
- package/dist/core/input.mjs +49 -0
- package/dist/core/iterator.d.mts +17 -0
- package/dist/core/iterator.mjs +79 -0
- package/dist/core/router-utils.d.mts +25 -0
- package/dist/core/router-utils.mjs +98 -0
- package/dist/core/schema.d.mts +20 -0
- package/dist/core/schema.mjs +33 -0
- package/dist/core/serve.d.mts +51 -0
- package/dist/core/serve.mjs +76 -0
- package/dist/core/sse.d.mts +18 -0
- package/dist/core/sse.mjs +98 -0
- package/dist/core/storage.d.mts +20 -0
- package/dist/core/storage.mjs +61 -0
- package/dist/core/task.d.mts +62 -0
- package/dist/core/task.mjs +165 -0
- package/dist/core/utils.mjs +24 -0
- package/dist/index.d.mts +18 -37
- package/dist/index.mjs +15 -7
- package/dist/integrations/ai/index.d.mts +25 -0
- package/dist/integrations/ai/index.mjs +117 -0
- package/dist/integrations/better-auth/index.d.mts +61 -0
- package/dist/integrations/better-auth/index.mjs +332 -0
- package/dist/integrations/drizzle/index.d.mts +27 -0
- package/dist/integrations/drizzle/index.mjs +286 -0
- package/dist/integrations/hey-api/index.d.mts +2 -0
- package/dist/integrations/hey-api/index.mjs +2 -0
- package/dist/integrations/hey-api/to-client.d.mts +20 -0
- package/dist/integrations/hey-api/to-client.mjs +39 -0
- package/dist/integrations/pinia-colada/general-utils.d.mts +13 -0
- package/dist/integrations/pinia-colada/general-utils.mjs +9 -0
- package/dist/integrations/pinia-colada/index.d.mts +6 -0
- package/dist/integrations/pinia-colada/index.mjs +5 -0
- package/dist/integrations/pinia-colada/key.d.mts +11 -0
- package/dist/integrations/pinia-colada/key.mjs +11 -0
- package/dist/integrations/pinia-colada/procedure-utils.d.mts +25 -0
- package/dist/integrations/pinia-colada/procedure-utils.mjs +33 -0
- package/dist/integrations/pinia-colada/router-utils.d.mts +17 -0
- package/dist/integrations/pinia-colada/router-utils.mjs +30 -0
- package/dist/integrations/pinia-colada/types.d.mts +25 -0
- package/dist/integrations/react/index.d.mts +83 -0
- package/dist/integrations/react/index.mjs +196 -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 +60 -0
- package/dist/integrations/tanstack-query/ssr.mjs +102 -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 +22 -0
- package/dist/lazy.mjs +34 -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 +47 -0
- package/dist/plugins/analytics.d.mts +236 -0
- package/dist/plugins/analytics.mjs +816 -0
- package/dist/plugins/batch-server.d.mts +20 -0
- package/dist/plugins/batch-server.mjs +91 -0
- package/dist/plugins/body-limit.d.mts +19 -0
- package/dist/plugins/body-limit.mjs +49 -0
- package/dist/plugins/cache.d.mts +170 -0
- package/dist/plugins/cache.mjs +212 -0
- package/dist/plugins/coerce.d.mts +24 -0
- package/dist/plugins/coerce.mjs +70 -0
- package/dist/plugins/cookies.d.mts +14 -0
- package/dist/plugins/cookies.mjs +48 -0
- package/dist/plugins/cors.d.mts +43 -0
- package/dist/plugins/cors.mjs +62 -0
- package/dist/plugins/file-upload.d.mts +38 -0
- package/dist/plugins/file-upload.mjs +102 -0
- package/dist/plugins/index.d.mts +14 -0
- package/dist/plugins/index.mjs +14 -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 +53 -0
- package/dist/plugins/ratelimit.mjs +92 -0
- package/dist/plugins/signing.d.mts +41 -0
- package/dist/plugins/signing.mjs +118 -0
- package/dist/plugins/strict-get.d.mts +10 -0
- package/dist/plugins/strict-get.mjs +33 -0
- package/dist/scalar.d.mts +49 -0
- package/dist/scalar.mjs +311 -0
- package/dist/silgi.d.mts +144 -0
- package/dist/silgi.mjs +156 -0
- package/dist/trpc-interop.d.mts +22 -0
- package/dist/trpc-interop.mjs +68 -0
- package/dist/types.d.mts +108 -0
- package/dist/ws.d.mts +88 -0
- package/dist/ws.mjs +205 -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 +324 -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,139 @@
|
|
|
1
|
+
//#region src/core/error.ts
|
|
2
|
+
/**
|
|
3
|
+
* SilgiError — unified RPC error with cross-realm instanceof.
|
|
4
|
+
*/
|
|
5
|
+
const COMMON_ERRORS = /* @__PURE__ */ Object.freeze({
|
|
6
|
+
BAD_REQUEST: {
|
|
7
|
+
status: 400,
|
|
8
|
+
message: "Bad Request"
|
|
9
|
+
},
|
|
10
|
+
UNAUTHORIZED: {
|
|
11
|
+
status: 401,
|
|
12
|
+
message: "Unauthorized"
|
|
13
|
+
},
|
|
14
|
+
FORBIDDEN: {
|
|
15
|
+
status: 403,
|
|
16
|
+
message: "Forbidden"
|
|
17
|
+
},
|
|
18
|
+
NOT_FOUND: {
|
|
19
|
+
status: 404,
|
|
20
|
+
message: "Not Found"
|
|
21
|
+
},
|
|
22
|
+
METHOD_NOT_ALLOWED: {
|
|
23
|
+
status: 405,
|
|
24
|
+
message: "Method Not Allowed"
|
|
25
|
+
},
|
|
26
|
+
NOT_ACCEPTABLE: {
|
|
27
|
+
status: 406,
|
|
28
|
+
message: "Not Acceptable"
|
|
29
|
+
},
|
|
30
|
+
CONFLICT: {
|
|
31
|
+
status: 409,
|
|
32
|
+
message: "Conflict"
|
|
33
|
+
},
|
|
34
|
+
GONE: {
|
|
35
|
+
status: 410,
|
|
36
|
+
message: "Gone"
|
|
37
|
+
},
|
|
38
|
+
UNPROCESSABLE_CONTENT: {
|
|
39
|
+
status: 422,
|
|
40
|
+
message: "Unprocessable Content"
|
|
41
|
+
},
|
|
42
|
+
PRECONDITION_REQUIRED: {
|
|
43
|
+
status: 428,
|
|
44
|
+
message: "Precondition Required"
|
|
45
|
+
},
|
|
46
|
+
TOO_MANY_REQUESTS: {
|
|
47
|
+
status: 429,
|
|
48
|
+
message: "Too Many Requests"
|
|
49
|
+
},
|
|
50
|
+
CLIENT_CLOSED_REQUEST: {
|
|
51
|
+
status: 499,
|
|
52
|
+
message: "Client Closed Request"
|
|
53
|
+
},
|
|
54
|
+
INTERNAL_SERVER_ERROR: {
|
|
55
|
+
status: 500,
|
|
56
|
+
message: "Internal Server Error"
|
|
57
|
+
},
|
|
58
|
+
NOT_IMPLEMENTED: {
|
|
59
|
+
status: 501,
|
|
60
|
+
message: "Not Implemented"
|
|
61
|
+
},
|
|
62
|
+
BAD_GATEWAY: {
|
|
63
|
+
status: 502,
|
|
64
|
+
message: "Bad Gateway"
|
|
65
|
+
},
|
|
66
|
+
SERVICE_UNAVAILABLE: {
|
|
67
|
+
status: 503,
|
|
68
|
+
message: "Service Unavailable"
|
|
69
|
+
},
|
|
70
|
+
GATEWAY_TIMEOUT: {
|
|
71
|
+
status: 504,
|
|
72
|
+
message: "Gateway Timeout"
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const REGISTRY_KEY = Symbol.for("silgi.error.registry");
|
|
76
|
+
const registry = globalThis[REGISTRY_KEY] ??= /* @__PURE__ */ new WeakSet();
|
|
77
|
+
var SilgiError = class extends Error {
|
|
78
|
+
code;
|
|
79
|
+
status;
|
|
80
|
+
data;
|
|
81
|
+
defined;
|
|
82
|
+
constructor(code, options = {}) {
|
|
83
|
+
const defaults = COMMON_ERRORS[code];
|
|
84
|
+
const message = options.message ?? defaults?.message ?? code;
|
|
85
|
+
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
86
|
+
this.code = code;
|
|
87
|
+
this.status = options.status ?? defaults?.status ?? 500;
|
|
88
|
+
this.data = options.data;
|
|
89
|
+
this.defined = options.defined ?? false;
|
|
90
|
+
this.name = "SilgiError";
|
|
91
|
+
}
|
|
92
|
+
toJSON() {
|
|
93
|
+
return {
|
|
94
|
+
defined: this.defined,
|
|
95
|
+
code: this.code,
|
|
96
|
+
status: this.status,
|
|
97
|
+
message: this.message,
|
|
98
|
+
data: this.data
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
static {
|
|
102
|
+
registry.add(this);
|
|
103
|
+
}
|
|
104
|
+
static [Symbol.hasInstance](instance) {
|
|
105
|
+
if (typeof instance !== "object" || instance === null) return false;
|
|
106
|
+
let proto = Object.getPrototypeOf(instance);
|
|
107
|
+
while (proto) {
|
|
108
|
+
if (proto.constructor && registry.has(proto.constructor)) return true;
|
|
109
|
+
proto = Object.getPrototypeOf(proto);
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function isDefinedError(error) {
|
|
115
|
+
return error instanceof SilgiError && error.defined === true;
|
|
116
|
+
}
|
|
117
|
+
function toSilgiError(error) {
|
|
118
|
+
if (error instanceof SilgiError) return error;
|
|
119
|
+
return new SilgiError("INTERNAL_SERVER_ERROR", {
|
|
120
|
+
message: "Internal server error",
|
|
121
|
+
cause: error
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function isErrorStatus(status) {
|
|
125
|
+
return status >= 400;
|
|
126
|
+
}
|
|
127
|
+
function isSilgiErrorJSON(json) {
|
|
128
|
+
return typeof json === "object" && json !== null && "code" in json && "status" in json && typeof json.code === "string";
|
|
129
|
+
}
|
|
130
|
+
function fromSilgiErrorJSON(json) {
|
|
131
|
+
return new SilgiError(json.code, {
|
|
132
|
+
status: json.status,
|
|
133
|
+
message: json.message,
|
|
134
|
+
data: json.data,
|
|
135
|
+
defined: json.defined
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//#endregion
|
|
139
|
+
export { SilgiError, fromSilgiErrorJSON, isDefinedError, isErrorStatus, isSilgiErrorJSON, toSilgiError };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { routerCache } from "./router-utils.mjs";
|
|
2
|
+
import { compileRouter, createContext, releaseContext } from "../compile.mjs";
|
|
3
|
+
import { applyContext } from "./dispatch.mjs";
|
|
4
|
+
import { analyticsTraceMap } from "../plugins/analytics.mjs";
|
|
5
|
+
import { detectResponseFormat, encodeResponse, makeErrorResponse } from "./codec.mjs";
|
|
6
|
+
import { parseInput } from "./input.mjs";
|
|
7
|
+
import { iteratorToEventStream } from "./sse.mjs";
|
|
8
|
+
//#region src/core/handler.ts
|
|
9
|
+
/**
|
|
10
|
+
* Fetch API handler — single unified request handler.
|
|
11
|
+
*
|
|
12
|
+
* Orchestrates: routing → context → input parsing → pipeline → response encoding.
|
|
13
|
+
* Each concern lives in its own module (codec.ts, input.ts, sse.ts).
|
|
14
|
+
*
|
|
15
|
+
* Analytics / Scalar are NOT here — they wrap the handler externally
|
|
16
|
+
* (see wrapWithAnalytics / wrapWithScalar in their respective modules).
|
|
17
|
+
*/
|
|
18
|
+
/** Wrap a stream to release pooled context on completion or cancellation. */
|
|
19
|
+
function wrapStreamWithRelease(source, ctx) {
|
|
20
|
+
let released = false;
|
|
21
|
+
const release = () => {
|
|
22
|
+
if (!released) {
|
|
23
|
+
released = true;
|
|
24
|
+
releaseContext(ctx);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const reader = source.getReader();
|
|
28
|
+
return new ReadableStream({
|
|
29
|
+
async pull(controller) {
|
|
30
|
+
try {
|
|
31
|
+
const { done, value } = await reader.read();
|
|
32
|
+
if (done) {
|
|
33
|
+
release();
|
|
34
|
+
controller.close();
|
|
35
|
+
} else controller.enqueue(value);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
release();
|
|
38
|
+
controller.error(err);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
cancel() {
|
|
42
|
+
release();
|
|
43
|
+
reader.cancel();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function makeResponse(output, route, format, ctx) {
|
|
48
|
+
if (output instanceof Response) {
|
|
49
|
+
releaseContext(ctx);
|
|
50
|
+
return output;
|
|
51
|
+
}
|
|
52
|
+
if (output instanceof ReadableStream) return new Response(wrapStreamWithRelease(output, ctx), { headers: { "content-type": "application/octet-stream" } });
|
|
53
|
+
if (output && typeof output === "object" && Symbol.asyncIterator in output) {
|
|
54
|
+
const stream = iteratorToEventStream(output);
|
|
55
|
+
return new Response(wrapStreamWithRelease(stream, ctx), { headers: {
|
|
56
|
+
"content-type": "text/event-stream",
|
|
57
|
+
"cache-control": "no-cache"
|
|
58
|
+
} });
|
|
59
|
+
}
|
|
60
|
+
releaseContext(ctx);
|
|
61
|
+
const cacheHeaders = route.cacheControl ? { "cache-control": route.cacheControl } : void 0;
|
|
62
|
+
if (format !== "json") return encodeResponse(output, 200, format, cacheHeaders);
|
|
63
|
+
return new Response(JSON.stringify(output), { headers: cacheHeaders ? {
|
|
64
|
+
"content-type": "application/json",
|
|
65
|
+
...cacheHeaders
|
|
66
|
+
} : { "content-type": "application/json" } });
|
|
67
|
+
}
|
|
68
|
+
function createFetchHandler(routerDef, contextFactory, hooks) {
|
|
69
|
+
let compiledRouter = routerCache.get(routerDef);
|
|
70
|
+
if (!compiledRouter) {
|
|
71
|
+
compiledRouter = compileRouter(routerDef);
|
|
72
|
+
routerCache.set(routerDef, compiledRouter);
|
|
73
|
+
}
|
|
74
|
+
const jsonHeaders = { "content-type": "application/json" };
|
|
75
|
+
const notFoundBody = JSON.stringify({
|
|
76
|
+
code: "NOT_FOUND",
|
|
77
|
+
status: 404,
|
|
78
|
+
message: "Procedure not found"
|
|
79
|
+
});
|
|
80
|
+
function callHook(name, event) {
|
|
81
|
+
if (!hooks) return;
|
|
82
|
+
try {
|
|
83
|
+
const result = hooks.callHook(name, event);
|
|
84
|
+
if (result instanceof Promise) result.catch(() => {});
|
|
85
|
+
} catch {}
|
|
86
|
+
}
|
|
87
|
+
return async function handleRequest(request) {
|
|
88
|
+
const url = request.url;
|
|
89
|
+
const pathStart = url.indexOf("/", url.indexOf("//") + 2);
|
|
90
|
+
const qMark = url.indexOf("?", pathStart);
|
|
91
|
+
const fullPath = qMark === -1 ? url.slice(pathStart) : url.slice(pathStart, qMark);
|
|
92
|
+
const pathname = fullPath.length > 1 ? fullPath.slice(1) : "";
|
|
93
|
+
const match = compiledRouter(request.method, fullPath);
|
|
94
|
+
if (!match) return new Response(notFoundBody, {
|
|
95
|
+
status: 404,
|
|
96
|
+
headers: jsonHeaders
|
|
97
|
+
});
|
|
98
|
+
const route = match.data;
|
|
99
|
+
const reqMethod = request.method;
|
|
100
|
+
if (route.method !== "*" && reqMethod !== route.method && reqMethod !== "OPTIONS") {
|
|
101
|
+
if (!(reqMethod === "GET" && route.method === "POST")) return new Response(JSON.stringify({
|
|
102
|
+
code: "METHOD_NOT_ALLOWED",
|
|
103
|
+
status: 405,
|
|
104
|
+
message: `Method ${reqMethod} not allowed`
|
|
105
|
+
}), {
|
|
106
|
+
status: 405,
|
|
107
|
+
headers: {
|
|
108
|
+
...jsonHeaders,
|
|
109
|
+
allow: route.method
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const format = detectResponseFormat(request);
|
|
114
|
+
const ctx = createContext();
|
|
115
|
+
let rawInput;
|
|
116
|
+
try {
|
|
117
|
+
const baseCtxResult = contextFactory(request);
|
|
118
|
+
applyContext(ctx, baseCtxResult instanceof Promise ? await baseCtxResult : baseCtxResult);
|
|
119
|
+
if (match.params) ctx.params = match.params;
|
|
120
|
+
const reqTrace = analyticsTraceMap.get(request);
|
|
121
|
+
if (reqTrace) ctx.__analyticsTrace = reqTrace;
|
|
122
|
+
if (!route.passthrough) rawInput = await parseInput(request, url, qMark);
|
|
123
|
+
callHook("request", {
|
|
124
|
+
path: pathname,
|
|
125
|
+
input: rawInput
|
|
126
|
+
});
|
|
127
|
+
const pipelineResult = route.handler(ctx, rawInput, request.signal);
|
|
128
|
+
const output = pipelineResult instanceof Promise ? await pipelineResult : pipelineResult;
|
|
129
|
+
callHook("response", {
|
|
130
|
+
path: pathname,
|
|
131
|
+
output,
|
|
132
|
+
durationMs: 0
|
|
133
|
+
});
|
|
134
|
+
const response = makeResponse(output, route, format, ctx);
|
|
135
|
+
return response instanceof Promise ? await response : response;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
releaseContext(ctx);
|
|
138
|
+
callHook("error", {
|
|
139
|
+
path: pathname,
|
|
140
|
+
error
|
|
141
|
+
});
|
|
142
|
+
const errorResponse = makeErrorResponse(error, format);
|
|
143
|
+
return errorResponse instanceof Promise ? await errorResponse : errorResponse;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
export { createFetchHandler };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { SilgiError } from "./error.mjs";
|
|
2
|
+
//#region src/core/input.ts
|
|
3
|
+
/**
|
|
4
|
+
* Request input parsing — JSON, MessagePack, devalue, query string.
|
|
5
|
+
*/
|
|
6
|
+
let _msgpack;
|
|
7
|
+
let _devalue;
|
|
8
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
9
|
+
/** Max allowed size for GET ?data= parameter (bytes). Prevents JSON bomb via URL. */
|
|
10
|
+
const MAX_QUERY_DATA_LENGTH = 8192;
|
|
11
|
+
/** Parse request input from body or query string */
|
|
12
|
+
async function parseInput(request, url, qMark) {
|
|
13
|
+
if (request.method === "GET" || !request.body) {
|
|
14
|
+
if (qMark !== -1) {
|
|
15
|
+
const searchStr = url.slice(qMark + 1);
|
|
16
|
+
const dataIdx = searchStr.indexOf("data=");
|
|
17
|
+
if (dataIdx !== -1) {
|
|
18
|
+
const valueStart = dataIdx + 5;
|
|
19
|
+
const valueEnd = searchStr.indexOf("&", valueStart);
|
|
20
|
+
const encoded = valueEnd === -1 ? searchStr.slice(valueStart) : searchStr.slice(valueStart, valueEnd);
|
|
21
|
+
if (encoded.length > MAX_QUERY_DATA_LENGTH) throw new SilgiError("BAD_REQUEST", { message: "Query data parameter too large" });
|
|
22
|
+
return JSON.parse(decodeURIComponent(encoded));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const ct = request.headers.get("content-type");
|
|
28
|
+
if (ct) {
|
|
29
|
+
if (ct.includes("msgpack")) {
|
|
30
|
+
_msgpack ??= await import("../codec/msgpack.mjs");
|
|
31
|
+
const buf = new Uint8Array(await request.arrayBuffer());
|
|
32
|
+
return buf.length > 0 ? _msgpack.decode(buf) : void 0;
|
|
33
|
+
}
|
|
34
|
+
if (ct.includes("x-devalue")) {
|
|
35
|
+
_devalue ??= await import("../codec/devalue.mjs");
|
|
36
|
+
const text = await request.text();
|
|
37
|
+
return text ? _devalue.decode(text) : void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (isBun) try {
|
|
41
|
+
return await request.json();
|
|
42
|
+
} catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const text = await request.text();
|
|
46
|
+
return text ? JSON.parse(text) : void 0;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { parseInput };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/core/iterator.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Async iterator utilities for streaming support.
|
|
4
|
+
*/
|
|
5
|
+
type CleanupReason = 'return' | 'throw' | 'next' | 'dispose';
|
|
6
|
+
declare class AsyncIteratorClass<TYield, TReturn = void> implements AsyncGenerator<TYield, TReturn> {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(nextFn: () => Promise<IteratorResult<TYield, TReturn>>, cleanup?: (reason: CleanupReason) => Promise<void>);
|
|
9
|
+
next(): Promise<IteratorResult<TYield, TReturn>>;
|
|
10
|
+
return(value?: TReturn | PromiseLike<TReturn>): Promise<IteratorResult<TYield, TReturn>>;
|
|
11
|
+
throw(error?: unknown): Promise<IteratorResult<TYield, TReturn>>;
|
|
12
|
+
[Symbol.asyncIterator](): this;
|
|
13
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
declare function mapAsyncIterator<TIn, TOut, TReturn = void>(source: AsyncIterableIterator<TIn>, transform: (value: TIn) => Promise<TOut> | TOut, transformError?: (error: unknown) => unknown): AsyncIteratorClass<TOut, TReturn>;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { AsyncIteratorClass, mapAsyncIterator };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { sequential } from "./utils.mjs";
|
|
2
|
+
//#region src/core/iterator.ts
|
|
3
|
+
/**
|
|
4
|
+
* Async iterator utilities for streaming support.
|
|
5
|
+
*/
|
|
6
|
+
var AsyncIteratorClass = class {
|
|
7
|
+
#nextFn;
|
|
8
|
+
#cleanup;
|
|
9
|
+
#isDone = false;
|
|
10
|
+
#cleanupCalled = false;
|
|
11
|
+
constructor(nextFn, cleanup) {
|
|
12
|
+
this.#nextFn = sequential(nextFn);
|
|
13
|
+
this.#cleanup = cleanup;
|
|
14
|
+
}
|
|
15
|
+
async #doCleanup(reason) {
|
|
16
|
+
if (this.#cleanupCalled) return;
|
|
17
|
+
this.#cleanupCalled = true;
|
|
18
|
+
await this.#cleanup?.(reason);
|
|
19
|
+
}
|
|
20
|
+
async next() {
|
|
21
|
+
if (this.#isDone) return {
|
|
22
|
+
done: true,
|
|
23
|
+
value: void 0
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
const result = await this.#nextFn();
|
|
27
|
+
if (result.done) {
|
|
28
|
+
this.#isDone = true;
|
|
29
|
+
await this.#doCleanup("next");
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
this.#isDone = true;
|
|
34
|
+
await this.#doCleanup("throw");
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async return(value) {
|
|
39
|
+
this.#isDone = true;
|
|
40
|
+
await this.#doCleanup("return");
|
|
41
|
+
return {
|
|
42
|
+
done: true,
|
|
43
|
+
value
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async throw(error) {
|
|
47
|
+
this.#isDone = true;
|
|
48
|
+
await this.#doCleanup("throw");
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
[Symbol.asyncIterator]() {
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
async [Symbol.asyncDispose]() {
|
|
55
|
+
this.#isDone = true;
|
|
56
|
+
await this.#doCleanup("dispose");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function mapAsyncIterator(source, transform, transformError) {
|
|
60
|
+
return new AsyncIteratorClass(async () => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await source.next();
|
|
63
|
+
if (result.done) return {
|
|
64
|
+
done: true,
|
|
65
|
+
value: await transform(result.value)
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
done: false,
|
|
69
|
+
value: await transform(result.value)
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw transformError ? transformError(error) : error;
|
|
73
|
+
}
|
|
74
|
+
}, async () => {
|
|
75
|
+
await source.return?.();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { AsyncIteratorClass, mapAsyncIterator };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { compileRouter } from "../compile.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/core/router-utils.d.ts
|
|
4
|
+
/** Extracted route metadata — safe for client bundles (no server code) */
|
|
5
|
+
interface ExtractedRoute {
|
|
6
|
+
path: string;
|
|
7
|
+
method: string;
|
|
8
|
+
}
|
|
9
|
+
/** Nested route map — mirrors the router tree structure with only route metadata */
|
|
10
|
+
type ExtractedRoutes = {
|
|
11
|
+
[key: string]: ExtractedRoute | ExtractedRoutes;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Extract route metadata from a router definition.
|
|
15
|
+
* Returns a lightweight nested object safe for client bundles — no resolve functions,
|
|
16
|
+
* no guards, no schemas, no server code.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // server/routes.ts — import this on the client
|
|
20
|
+
* export const routes = extractRoutes(appRouter)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function extractRoutes(def: unknown, prefix?: string[]): ExtractedRoutes;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ExtractedRoute, ExtractedRoutes, extractRoutes };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import "../compile.mjs";
|
|
2
|
+
//#region src/core/router-utils.ts
|
|
3
|
+
const routerCache = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function isProcedureDef(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && "type" in value && "resolve" in value && typeof value.resolve === "function";
|
|
6
|
+
}
|
|
7
|
+
function assignPaths(def, prefix = []) {
|
|
8
|
+
const result = {};
|
|
9
|
+
for (const [key, value] of Object.entries(def)) {
|
|
10
|
+
const currentPath = [...prefix, key];
|
|
11
|
+
if (isProcedureDef(value)) if (!value.route) result[key] = {
|
|
12
|
+
...value,
|
|
13
|
+
route: { path: "/" + currentPath.join("/") }
|
|
14
|
+
};
|
|
15
|
+
else result[key] = value;
|
|
16
|
+
else if (typeof value === "object" && value !== null) result[key] = assignPaths(value, currentPath);
|
|
17
|
+
else result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract route metadata from a router definition.
|
|
23
|
+
* Returns a lightweight nested object safe for client bundles — no resolve functions,
|
|
24
|
+
* no guards, no schemas, no server code.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* // server/routes.ts — import this on the client
|
|
28
|
+
* export const routes = extractRoutes(appRouter)
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function extractRoutes(def, prefix = []) {
|
|
32
|
+
const result = {};
|
|
33
|
+
if (def == null || typeof def !== "object") return result;
|
|
34
|
+
for (const [key, value] of Object.entries(def)) if (isProcedureDef(value)) {
|
|
35
|
+
const route = value.route;
|
|
36
|
+
if (route?.path) result[key] = {
|
|
37
|
+
path: route.path,
|
|
38
|
+
method: (route.method ?? "POST").toUpperCase()
|
|
39
|
+
};
|
|
40
|
+
} else if (typeof value === "object" && value !== null) {
|
|
41
|
+
const nested = extractRoutes(value, [...prefix, key]);
|
|
42
|
+
if (Object.keys(nested).length > 0) result[key] = nested;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/** Check if a value is an extracted route leaf (has path + method, not a nested object) */
|
|
47
|
+
function isExtractedRoute(value) {
|
|
48
|
+
return typeof value === "object" && value !== null && "path" in value && "method" in value && !("resolve" in value);
|
|
49
|
+
}
|
|
50
|
+
/** Walk a route tree by path segments and return the route metadata. Works with both full routers and extracted routes. */
|
|
51
|
+
function resolveRoute(routes, path) {
|
|
52
|
+
let current = routes;
|
|
53
|
+
for (const segment of path) {
|
|
54
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
55
|
+
current = current[segment];
|
|
56
|
+
}
|
|
57
|
+
if (isExtractedRoute(current)) return current;
|
|
58
|
+
if (isProcedureDef(current)) {
|
|
59
|
+
const route = current.route;
|
|
60
|
+
if (!route?.path) return void 0;
|
|
61
|
+
return {
|
|
62
|
+
path: route.path,
|
|
63
|
+
method: (route.method ?? "POST").toUpperCase()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Substitute :param placeholders in a route path with values from input.
|
|
69
|
+
* Returns the resolved URL path and the input with used params removed.
|
|
70
|
+
*/
|
|
71
|
+
function substituteParams(routePath, input) {
|
|
72
|
+
if (input == null || typeof input !== "object" || Array.isArray(input)) return {
|
|
73
|
+
url: routePath,
|
|
74
|
+
remainingInput: input
|
|
75
|
+
};
|
|
76
|
+
const obj = input;
|
|
77
|
+
const used = /* @__PURE__ */ new Set();
|
|
78
|
+
const url = routePath.replace(/:([a-zA-Z_]\w*)/g, (_match, name) => {
|
|
79
|
+
const val = obj[name];
|
|
80
|
+
if (val !== void 0) {
|
|
81
|
+
used.add(name);
|
|
82
|
+
return encodeURIComponent(String(val));
|
|
83
|
+
}
|
|
84
|
+
return `:${name}`;
|
|
85
|
+
});
|
|
86
|
+
if (used.size === 0) return {
|
|
87
|
+
url,
|
|
88
|
+
remainingInput: input
|
|
89
|
+
};
|
|
90
|
+
const remaining = {};
|
|
91
|
+
for (const key of Object.keys(obj)) if (!used.has(key)) remaining[key] = obj[key];
|
|
92
|
+
return {
|
|
93
|
+
url,
|
|
94
|
+
remainingInput: Object.keys(remaining).length > 0 ? remaining : void 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { assignPaths, extractRoutes, isProcedureDef, resolveRoute, routerCache, substituteParams };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
|
|
3
|
+
//#region src/core/schema.d.ts
|
|
4
|
+
type Schema<TInput = unknown, TOutput = unknown> = StandardSchemaV1<TInput, TOutput>;
|
|
5
|
+
type AnySchema = Schema<any, any>;
|
|
6
|
+
type InferSchemaInput<T extends AnySchema> = T extends Schema<infer I, unknown> ? I : never;
|
|
7
|
+
type InferSchemaOutput<T extends AnySchema> = T extends Schema<unknown, infer O> ? O : never;
|
|
8
|
+
type SchemaIssue = StandardSchemaV1.Issue;
|
|
9
|
+
declare class ValidationError extends Error {
|
|
10
|
+
readonly issues: readonly SchemaIssue[];
|
|
11
|
+
constructor(options: {
|
|
12
|
+
message?: string;
|
|
13
|
+
issues: readonly SchemaIssue[];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Sync fast-path: Zod 4 validate() returns sync result — avoid Promise allocation */
|
|
17
|
+
declare function validateSchema(schema: AnySchema, value: unknown): unknown;
|
|
18
|
+
declare function type<TInput, TOutput = TInput>(...args: TInput extends TOutput ? TOutput extends TInput ? [map?: (input: TInput) => TOutput] : [map: (input: TInput) => TOutput] : [map: (input: TInput) => TOutput]): Schema<TInput, TOutput>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { AnySchema, InferSchemaInput, InferSchemaOutput, Schema, ValidationError, type, validateSchema };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/core/schema.ts
|
|
2
|
+
var ValidationError = class extends Error {
|
|
3
|
+
issues;
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(options.message ?? "Validation failed");
|
|
6
|
+
this.name = "ValidationError";
|
|
7
|
+
this.issues = options.issues;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
/** Sync fast-path: Zod 4 validate() returns sync result — avoid Promise allocation */
|
|
11
|
+
function validateSchema(schema, value) {
|
|
12
|
+
const result = schema["~standard"].validate(value);
|
|
13
|
+
if (typeof result?.then !== "function") {
|
|
14
|
+
if ("issues" in result && result.issues) throw new ValidationError({ issues: result.issues });
|
|
15
|
+
return result.value;
|
|
16
|
+
}
|
|
17
|
+
return result.then((r) => {
|
|
18
|
+
if ("issues" in r && r.issues) throw new ValidationError({ issues: r.issues });
|
|
19
|
+
return r.value;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function type(...args) {
|
|
23
|
+
const mapFn = args[0];
|
|
24
|
+
return { "~standard": {
|
|
25
|
+
version: 1,
|
|
26
|
+
vendor: "silgi",
|
|
27
|
+
validate(value) {
|
|
28
|
+
return { value: mapFn ? mapFn(value) : value };
|
|
29
|
+
}
|
|
30
|
+
} };
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { ValidationError, type, validateSchema };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AnalyticsOptions } from "../plugins/analytics.mjs";
|
|
2
|
+
import { ScalarOptions } from "../scalar.mjs";
|
|
3
|
+
import { WSAdapterOptions } from "../ws.mjs";
|
|
4
|
+
import { Hookable } from "hookable";
|
|
5
|
+
|
|
6
|
+
//#region src/core/serve.d.ts
|
|
7
|
+
interface SilgiServer {
|
|
8
|
+
/** Server URL (e.g. "http://127.0.0.1:3000") */
|
|
9
|
+
readonly url: string;
|
|
10
|
+
/** Configured port */
|
|
11
|
+
readonly port: number;
|
|
12
|
+
/** Configured hostname */
|
|
13
|
+
readonly hostname: string;
|
|
14
|
+
/**
|
|
15
|
+
* Gracefully shut down the server.
|
|
16
|
+
*
|
|
17
|
+
* By default waits for in-flight requests to complete.
|
|
18
|
+
* Pass `true` to forcefully terminate all active connections immediately.
|
|
19
|
+
*/
|
|
20
|
+
close(forceCloseConnections?: boolean): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
interface ServeOptions {
|
|
23
|
+
port?: number;
|
|
24
|
+
hostname?: string;
|
|
25
|
+
/** Enable Scalar API Reference UI at /reference and /openapi.json */
|
|
26
|
+
scalar?: boolean | ScalarOptions;
|
|
27
|
+
/** Enable analytics dashboard at /analytics */
|
|
28
|
+
analytics?: boolean | AnalyticsOptions;
|
|
29
|
+
/** Enable WebSocket RPC (requires crossws) */
|
|
30
|
+
ws?: boolean | WSAdapterOptions;
|
|
31
|
+
/** Enable HTTP/2 (requires cert + key for TLS) */
|
|
32
|
+
http2?: {
|
|
33
|
+
cert: string;
|
|
34
|
+
key: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Graceful shutdown on SIGINT/SIGTERM signals.
|
|
38
|
+
*
|
|
39
|
+
* - `true` (default): enables graceful shutdown with srvx defaults
|
|
40
|
+
* - `false`: disables automatic signal handling
|
|
41
|
+
* - `object`: fine-tune timeouts
|
|
42
|
+
*
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
gracefulShutdown?: boolean | {
|
|
46
|
+
/** Max time (ms) to wait for in-flight requests before force-closing */timeout?: number; /** Max time (ms) after graceful period before process.exit */
|
|
47
|
+
forceTimeout?: number;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { ServeOptions, SilgiServer };
|