silgi 0.0.14 → 0.1.0-beta.2

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.
Files changed (186) hide show
  1. package/README.md +102 -1
  2. package/dist/_virtual/_rolldown/runtime.mjs +5 -0
  3. package/dist/adapters/astro.d.mts +17 -0
  4. package/dist/adapters/astro.mjs +24 -0
  5. package/dist/adapters/aws-lambda.d.mts +31 -0
  6. package/dist/adapters/aws-lambda.mjs +85 -0
  7. package/dist/adapters/elysia.d.mts +17 -0
  8. package/dist/adapters/elysia.mjs +76 -0
  9. package/dist/adapters/express.d.mts +16 -0
  10. package/dist/adapters/express.mjs +78 -0
  11. package/dist/adapters/fastify.d.mts +15 -0
  12. package/dist/adapters/fastify.mjs +78 -0
  13. package/dist/adapters/message-port.d.mts +37 -0
  14. package/dist/adapters/message-port.mjs +129 -0
  15. package/dist/adapters/nestjs.d.mts +25 -0
  16. package/dist/adapters/nestjs.mjs +91 -0
  17. package/dist/adapters/nextjs.d.mts +21 -0
  18. package/dist/adapters/nextjs.mjs +30 -0
  19. package/dist/adapters/peer.d.mts +27 -0
  20. package/dist/adapters/peer.mjs +36 -0
  21. package/dist/adapters/remix.d.mts +17 -0
  22. package/dist/adapters/remix.mjs +24 -0
  23. package/dist/adapters/solidstart.d.mts +14 -0
  24. package/dist/adapters/solidstart.mjs +30 -0
  25. package/dist/adapters/sveltekit.d.mts +18 -0
  26. package/dist/adapters/sveltekit.mjs +33 -0
  27. package/dist/analyze.mjs +26 -0
  28. package/dist/broker/index.d.mts +62 -0
  29. package/dist/broker/index.mjs +153 -0
  30. package/dist/broker/nats.d.mts +33 -0
  31. package/dist/broker/nats.mjs +31 -0
  32. package/dist/broker/redis.d.mts +51 -0
  33. package/dist/broker/redis.mjs +92 -0
  34. package/dist/builder.d.mts +36 -0
  35. package/dist/builder.mjs +51 -0
  36. package/dist/callable.d.mts +17 -0
  37. package/dist/callable.mjs +42 -0
  38. package/dist/client/adapters/fetch/index.d.mts +17 -0
  39. package/dist/client/adapters/fetch/index.mjs +61 -0
  40. package/dist/client/adapters/ofetch/index.d.mts +41 -0
  41. package/dist/client/adapters/ofetch/index.mjs +92 -0
  42. package/dist/client/client.d.mts +29 -0
  43. package/dist/client/client.mjs +54 -0
  44. package/dist/client/dynamic-link.d.mts +15 -0
  45. package/dist/client/dynamic-link.mjs +16 -0
  46. package/dist/client/index.d.mts +7 -0
  47. package/dist/client/index.mjs +6 -0
  48. package/dist/client/interceptor.d.mts +31 -0
  49. package/dist/client/interceptor.mjs +34 -0
  50. package/dist/client/merge.d.mts +28 -0
  51. package/dist/client/merge.mjs +30 -0
  52. package/dist/client/openapi.d.mts +29 -0
  53. package/dist/client/openapi.mjs +89 -0
  54. package/dist/client/plugins/batch.d.mts +20 -0
  55. package/dist/client/plugins/batch.mjs +64 -0
  56. package/dist/client/plugins/csrf.d.mts +13 -0
  57. package/dist/client/plugins/csrf.mjs +20 -0
  58. package/dist/client/plugins/dedupe.d.mts +10 -0
  59. package/dist/client/plugins/dedupe.mjs +28 -0
  60. package/dist/client/plugins/index.d.mts +5 -0
  61. package/dist/client/plugins/index.mjs +5 -0
  62. package/dist/client/plugins/retry.d.mts +11 -0
  63. package/dist/client/plugins/retry.mjs +21 -0
  64. package/dist/client/server.d.mts +16 -0
  65. package/dist/client/server.mjs +60 -0
  66. package/dist/client/types.d.mts +29 -0
  67. package/dist/codec/devalue.d.mts +21 -0
  68. package/dist/codec/devalue.mjs +32 -0
  69. package/dist/codec/msgpack.d.mts +21 -0
  70. package/dist/codec/msgpack.mjs +59 -0
  71. package/dist/compile.d.mts +54 -0
  72. package/dist/compile.mjs +305 -0
  73. package/dist/contract.d.mts +36 -0
  74. package/dist/contract.mjs +40 -0
  75. package/dist/core/error.d.mts +104 -0
  76. package/dist/core/error.mjs +139 -0
  77. package/dist/core/handler.mjs +546 -0
  78. package/dist/core/iterator.d.mts +17 -0
  79. package/dist/core/iterator.mjs +79 -0
  80. package/dist/core/router-utils.mjs +16 -0
  81. package/dist/core/schema.d.mts +19 -0
  82. package/dist/core/schema.mjs +26 -0
  83. package/dist/core/serve.mjs +38 -0
  84. package/dist/core/sse.d.mts +16 -0
  85. package/dist/core/sse.mjs +95 -0
  86. package/dist/core/storage.d.mts +21 -0
  87. package/dist/core/storage.mjs +63 -0
  88. package/dist/core/utils.mjs +21 -0
  89. package/dist/fast-stringify.mjs +125 -0
  90. package/dist/index.d.mts +15 -37
  91. package/dist/index.mjs +13 -7
  92. package/dist/integrations/ai/index.d.mts +25 -0
  93. package/dist/integrations/ai/index.mjs +116 -0
  94. package/dist/integrations/react/index.d.mts +83 -0
  95. package/dist/integrations/react/index.mjs +197 -0
  96. package/dist/integrations/tanstack-query/index.d.mts +120 -0
  97. package/dist/integrations/tanstack-query/index.mjs +100 -0
  98. package/dist/integrations/tanstack-query/ssr.d.mts +51 -0
  99. package/dist/integrations/tanstack-query/ssr.mjs +89 -0
  100. package/dist/integrations/zod/converter.d.mts +75 -0
  101. package/dist/integrations/zod/converter.mjs +345 -0
  102. package/dist/integrations/zod/index.d.mts +2 -0
  103. package/dist/integrations/zod/index.mjs +2 -0
  104. package/dist/lazy.d.mts +24 -0
  105. package/dist/lazy.mjs +27 -0
  106. package/dist/lifecycle.d.mts +36 -0
  107. package/dist/lifecycle.mjs +46 -0
  108. package/dist/map-input.d.mts +17 -0
  109. package/dist/map-input.mjs +24 -0
  110. package/dist/plugins/analytics.d.mts +168 -0
  111. package/dist/plugins/analytics.mjs +459 -0
  112. package/dist/plugins/batch-server.d.mts +20 -0
  113. package/dist/plugins/batch-server.mjs +86 -0
  114. package/dist/plugins/body-limit.d.mts +16 -0
  115. package/dist/plugins/body-limit.mjs +44 -0
  116. package/dist/plugins/cache.d.mts +170 -0
  117. package/dist/plugins/cache.mjs +200 -0
  118. package/dist/plugins/coerce.d.mts +21 -0
  119. package/dist/plugins/coerce.mjs +46 -0
  120. package/dist/plugins/compression.d.mts +19 -0
  121. package/dist/plugins/compression.mjs +23 -0
  122. package/dist/plugins/cookies.d.mts +44 -0
  123. package/dist/plugins/cookies.mjs +67 -0
  124. package/dist/plugins/cors.d.mts +39 -0
  125. package/dist/plugins/cors.mjs +56 -0
  126. package/dist/plugins/custom-serializer.d.mts +57 -0
  127. package/dist/plugins/custom-serializer.mjs +40 -0
  128. package/dist/plugins/file-upload.d.mts +38 -0
  129. package/dist/plugins/file-upload.mjs +100 -0
  130. package/dist/plugins/index.d.mts +16 -0
  131. package/dist/plugins/index.mjs +16 -0
  132. package/dist/plugins/otel.d.mts +35 -0
  133. package/dist/plugins/otel.mjs +40 -0
  134. package/dist/plugins/pino.d.mts +60 -0
  135. package/dist/plugins/pino.mjs +42 -0
  136. package/dist/plugins/pubsub.d.mts +50 -0
  137. package/dist/plugins/pubsub.mjs +53 -0
  138. package/dist/plugins/ratelimit.d.mts +51 -0
  139. package/dist/plugins/ratelimit.mjs +81 -0
  140. package/dist/plugins/signing.d.mts +41 -0
  141. package/dist/plugins/signing.mjs +115 -0
  142. package/dist/plugins/strict-get.d.mts +10 -0
  143. package/dist/plugins/strict-get.mjs +33 -0
  144. package/dist/route/add.mjs +240 -0
  145. package/dist/route/compiler.mjs +373 -0
  146. package/dist/route/context.mjs +12 -0
  147. package/dist/route/types.d.mts +11 -0
  148. package/dist/route/utils.mjs +17 -0
  149. package/dist/scalar.d.mts +53 -0
  150. package/dist/scalar.mjs +330 -0
  151. package/dist/silgi.d.mts +139 -0
  152. package/dist/silgi.mjs +113 -0
  153. package/dist/trpc-interop.d.mts +22 -0
  154. package/dist/trpc-interop.mjs +68 -0
  155. package/dist/types.d.mts +82 -0
  156. package/dist/ws.d.mts +42 -0
  157. package/dist/ws.mjs +137 -0
  158. package/lib/dashboard/index.html +123 -0
  159. package/lib/ocache.d.mts +1 -0
  160. package/lib/ocache.mjs +1 -0
  161. package/lib/ofetch.d.mts +1 -0
  162. package/lib/ofetch.mjs +1 -0
  163. package/lib/srvx.d.mts +1 -0
  164. package/lib/srvx.mjs +1 -0
  165. package/lib/unstorage.d.mts +1 -0
  166. package/lib/unstorage.mjs +1 -0
  167. package/package.json +291 -65
  168. package/bin/silgi.mjs +0 -3
  169. package/dist/chunks/generate.mjs +0 -933
  170. package/dist/chunks/init.mjs +0 -21
  171. package/dist/cli/config.d.mts +0 -19
  172. package/dist/cli/config.d.ts +0 -19
  173. package/dist/cli/config.mjs +0 -5
  174. package/dist/cli/index.d.mts +0 -2
  175. package/dist/cli/index.d.ts +0 -2
  176. package/dist/cli/index.mjs +0 -119
  177. package/dist/index.d.ts +0 -37
  178. package/dist/plugins/openapi.d.mts +0 -138
  179. package/dist/plugins/openapi.d.ts +0 -138
  180. package/dist/plugins/openapi.mjs +0 -204
  181. package/dist/plugins/scalar.d.mts +0 -14
  182. package/dist/plugins/scalar.d.ts +0 -14
  183. package/dist/plugins/scalar.mjs +0 -66
  184. package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
  185. package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
  186. package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
@@ -0,0 +1,330 @@
1
+ //#region src/scalar.ts
2
+ /**
3
+ * Generate OpenAPI 3.1.0 document from a v2 RouterDef.
4
+ */
5
+ function generateOpenAPI(router, options = {}) {
6
+ const paths = {};
7
+ const tags = /* @__PURE__ */ new Map();
8
+ collectProcedures(router, [], (path, proc) => {
9
+ const route = proc.route;
10
+ const httpPath = (route?.path ?? "/" + path.join("/")).replace(/\/\*\*$/, "/{path}").replace(/\/\*\*/g, "/{path}");
11
+ const rawMethod = route?.method?.toLowerCase() ?? "post";
12
+ const methods = rawMethod === "*" ? [
13
+ "get",
14
+ "put",
15
+ "post",
16
+ "delete",
17
+ "options",
18
+ "head",
19
+ "patch",
20
+ "trace"
21
+ ] : [rawMethod];
22
+ const operationId = path.join("_");
23
+ if (path.length > 1) {
24
+ const tagName = path[0];
25
+ if (!tags.has(tagName)) tags.set(tagName, {});
26
+ }
27
+ let description = route?.description;
28
+ if (route?.ws) {
29
+ const wsNote = "Also available over WebSocket (`ws://`). Send `{ id, path: \"" + path.join("/") + "\", input }` as JSON.";
30
+ description = description ? `${description}\n\n${wsNote}` : wsNote;
31
+ }
32
+ const operation = {
33
+ operationId,
34
+ tags: path.length > 1 ? [path[0]] : void 0,
35
+ summary: route?.summary,
36
+ description,
37
+ deprecated: route?.deprecated || void 0,
38
+ responses: {}
39
+ };
40
+ if (!operation.summary) delete operation.summary;
41
+ if (!operation.description) delete operation.description;
42
+ if (!operation.deprecated) delete operation.deprecated;
43
+ if (options.security) operation.security = [{ auth: [] }];
44
+ const inputSchema = proc.input ? zodToJsonSchema(proc.input) : null;
45
+ const successStatus = route?.successStatus ?? 200;
46
+ const successDesc = route?.successDescription ?? "Successful response";
47
+ const guards = (proc.use ?? []).filter((m) => m.kind === "guard" && m.errors);
48
+ let allErrors = proc.errors ? { ...proc.errors } : null;
49
+ for (const guard of guards) {
50
+ const ge = guard.errors;
51
+ if (ge) allErrors = allErrors ? {
52
+ ...allErrors,
53
+ ...ge
54
+ } : { ...ge };
55
+ }
56
+ paths[httpPath] ??= {};
57
+ for (const method of methods) {
58
+ const op = {
59
+ ...operation,
60
+ responses: {}
61
+ };
62
+ if (methods.length > 1) op.operationId = `${operationId}_${method}`;
63
+ if (inputSchema) if (method === "get") op.parameters = objectSchemaToParams(inputSchema);
64
+ else op.requestBody = {
65
+ required: true,
66
+ content: { "application/json": { schema: inputSchema } }
67
+ };
68
+ if (proc.output) {
69
+ const schema = zodToJsonSchema(proc.output);
70
+ op.responses[String(successStatus)] = {
71
+ description: successDesc,
72
+ content: { "application/json": { schema } }
73
+ };
74
+ } else op.responses[String(successStatus)] = { description: successDesc };
75
+ if (allErrors) {
76
+ const byStatus = /* @__PURE__ */ new Map();
77
+ for (const [code, def] of Object.entries(allErrors)) {
78
+ const status = typeof def === "number" ? def : def.status;
79
+ if (!byStatus.has(status)) byStatus.set(status, []);
80
+ const entry = { code };
81
+ if (typeof def === "object" && def.data) entry.schema = zodToJsonSchema(def.data);
82
+ byStatus.get(status).push(entry);
83
+ }
84
+ for (const [status, errors] of byStatus) {
85
+ const errorSchemas = errors.map((e) => {
86
+ const s = {
87
+ type: "object",
88
+ properties: {
89
+ code: {
90
+ const: e.code,
91
+ type: "string"
92
+ },
93
+ status: {
94
+ const: status,
95
+ type: "integer"
96
+ },
97
+ message: { type: "string" }
98
+ },
99
+ required: [
100
+ "code",
101
+ "status",
102
+ "message"
103
+ ]
104
+ };
105
+ if (e.schema) {
106
+ s.properties.data = e.schema;
107
+ s.required.push("data");
108
+ }
109
+ return s;
110
+ });
111
+ op.responses[String(status)] = {
112
+ description: errors.map((e) => e.code).join(" | "),
113
+ content: { "application/json": { schema: errorSchemas.length === 1 ? errorSchemas[0] : { oneOf: errorSchemas } } }
114
+ };
115
+ }
116
+ }
117
+ if (proc.type === "subscription") op.responses[String(successStatus)] = {
118
+ description: "SSE event stream",
119
+ content: { "text/event-stream": { schema: { type: "string" } } }
120
+ };
121
+ paths[httpPath][method] = op;
122
+ }
123
+ });
124
+ const doc = {
125
+ openapi: "3.1.0",
126
+ info: {
127
+ title: options.title ?? "Silgi API",
128
+ version: options.version ?? "1.0.0",
129
+ ...options.description ? { description: options.description } : {},
130
+ ...options.contact ? { contact: options.contact } : {},
131
+ ...options.license ? { license: options.license } : {}
132
+ },
133
+ paths
134
+ };
135
+ if (options.servers?.length) doc.servers = options.servers;
136
+ if (options.externalDocs) doc.externalDocs = options.externalDocs;
137
+ if (tags.size > 0) doc.tags = [...tags.entries()].map(([name, meta]) => ({
138
+ name,
139
+ ...meta.description ? { description: meta.description } : {}
140
+ }));
141
+ if (options.security) {
142
+ const scheme = { type: options.security.type };
143
+ if (options.security.type === "http") {
144
+ scheme.scheme = options.security.scheme ?? "bearer";
145
+ if (options.security.bearerFormat) scheme.bearerFormat = options.security.bearerFormat;
146
+ } else if (options.security.type === "apiKey") {
147
+ scheme.in = options.security.in ?? "header";
148
+ scheme.name = options.security.name ?? "x-api-key";
149
+ }
150
+ if (options.security.description) scheme.description = options.security.description;
151
+ doc.components = { securitySchemes: { auth: scheme } };
152
+ }
153
+ return doc;
154
+ }
155
+ const SCALAR_CDN_SOURCES = {
156
+ cdn: "https://cdn.jsdelivr.net/npm/@scalar/api-reference",
157
+ unpkg: "https://unpkg.com/@scalar/api-reference",
158
+ local: "/__silgi/scalar.js"
159
+ };
160
+ function scalarHTML(specUrl, options = {}) {
161
+ const title = escapeHtml(options.title ?? "Silgi API");
162
+ const safeUrl = escapeHtml(specUrl);
163
+ const cdnOption = options.cdn ?? "cdn";
164
+ return `<!DOCTYPE html>
165
+ <html>
166
+ <head>
167
+ <title>${title} — Scalar</title>
168
+ <meta charset="utf-8" />
169
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
170
+ </head>
171
+ <body>
172
+ <script id="api-reference" data-url="${safeUrl}"><\/script>
173
+ <script src="${escapeHtml(SCALAR_CDN_SOURCES[cdnOption] ?? cdnOption)}"><\/script>
174
+ </body>
175
+ </html>`;
176
+ }
177
+ function escapeHtml(s) {
178
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
179
+ }
180
+ function isProcedureDef(value) {
181
+ return typeof value === "object" && value !== null && "type" in value && "resolve" in value && typeof value.resolve === "function";
182
+ }
183
+ function collectProcedures(node, path, cb) {
184
+ if (isProcedureDef(node)) {
185
+ cb(path, node);
186
+ return;
187
+ }
188
+ if (typeof node === "object" && node !== null) for (const [key, child] of Object.entries(node)) collectProcedures(child, [...path, key], cb);
189
+ }
190
+ /**
191
+ * Convert a Zod / Standard Schema to JSON Schema.
192
+ */
193
+ function zodToJsonSchema(schema) {
194
+ const zod = schema._zod ?? schema._def;
195
+ if (!zod) return {};
196
+ return convertZodDef(zod.def ?? zod);
197
+ }
198
+ function convertZodDef(def) {
199
+ if (!def) return {};
200
+ switch (def.type ?? def.typeName) {
201
+ case "string": return applyStringChecks({ type: "string" }, def.checks);
202
+ case "number":
203
+ case "float": return applyNumberChecks({ type: "number" }, def.checks);
204
+ case "int": return applyNumberChecks({ type: "integer" }, def.checks);
205
+ case "boolean": return { type: "boolean" };
206
+ case "bigint": return {
207
+ type: "integer",
208
+ format: "int64"
209
+ };
210
+ case "date": return {
211
+ type: "string",
212
+ format: "date-time"
213
+ };
214
+ case "object": {
215
+ const schema = {
216
+ type: "object",
217
+ properties: {},
218
+ required: []
219
+ };
220
+ if (def.shape) for (const [key, fieldSchema] of Object.entries(def.shape)) {
221
+ schema.properties[key] = zodToJsonSchema(fieldSchema);
222
+ const fz = fieldSchema?._zod?.def ?? fieldSchema?._def;
223
+ if (!(fz?.type === "optional" || fz?.typeName === "ZodOptional" || fz?.optional)) schema.required.push(key);
224
+ }
225
+ if (!schema.required.length) delete schema.required;
226
+ if (def.description) schema.description = def.description;
227
+ return schema;
228
+ }
229
+ case "array": return {
230
+ type: "array",
231
+ ...def.element ? { items: zodToJsonSchema(def.element) } : {}
232
+ };
233
+ case "tuple": return {
234
+ type: "array",
235
+ prefixItems: (def.items ?? []).map((item) => zodToJsonSchema(item))
236
+ };
237
+ case "record": return {
238
+ type: "object",
239
+ additionalProperties: def.valueType ? zodToJsonSchema(def.valueType) : true
240
+ };
241
+ case "map": return {
242
+ type: "object",
243
+ description: "Map (serialized as object)"
244
+ };
245
+ case "set": return {
246
+ type: "array",
247
+ uniqueItems: true,
248
+ ...def.valueType ? { items: zodToJsonSchema(def.valueType) } : {}
249
+ };
250
+ case "optional": return zodToJsonSchema(def.innerType ?? def.inner);
251
+ case "nullable": return { anyOf: [zodToJsonSchema(def.innerType ?? def.inner), { type: "null" }] };
252
+ case "default": {
253
+ const inner = zodToJsonSchema(def.innerType ?? def.inner);
254
+ const defaultVal = typeof def.defaultValue === "function" ? def.defaultValue() : def.default;
255
+ return {
256
+ ...inner,
257
+ default: defaultVal
258
+ };
259
+ }
260
+ case "enum": return {
261
+ type: "string",
262
+ enum: def.values ?? def.entries
263
+ };
264
+ case "nativeEnum": return { enum: Object.values(def.values ?? {}).filter((v) => typeof v !== "number" || !def.values[v]) };
265
+ case "literal": return { const: def.value };
266
+ case "union": return { anyOf: (def.options ?? def.members ?? []).map((o) => zodToJsonSchema(o)) };
267
+ case "discriminatedUnion": return { oneOf: (def.options ?? []).map((o) => zodToJsonSchema(o)) };
268
+ case "intersection": return { allOf: [zodToJsonSchema(def.left), zodToJsonSchema(def.right)] };
269
+ case "pipe":
270
+ case "transform": return zodToJsonSchema(def.in ?? def.innerType ?? def.input);
271
+ case "lazy": return def.getter ? zodToJsonSchema(def.getter()) : {};
272
+ case "any":
273
+ case "unknown": return {};
274
+ case "void":
275
+ case "undefined": return { type: "null" };
276
+ case "never": return { not: {} };
277
+ case "null": return { type: "null" };
278
+ default: return {};
279
+ }
280
+ }
281
+ function applyStringChecks(schema, checks) {
282
+ if (!checks) return schema;
283
+ for (const c of checks) {
284
+ const k = c.kind ?? c.type;
285
+ if (k === "min" || k === "min_length") schema.minLength = c.value ?? c.minimum;
286
+ if (k === "max" || k === "max_length") schema.maxLength = c.value ?? c.maximum;
287
+ if (k === "length") {
288
+ schema.minLength = c.value;
289
+ schema.maxLength = c.value;
290
+ }
291
+ if (k === "email" || c.format === "email") schema.format = "email";
292
+ if (k === "url") schema.format = "uri";
293
+ if (k === "uuid") schema.format = "uuid";
294
+ if (k === "cuid") schema.format = "cuid";
295
+ if (k === "ulid") schema.format = "ulid";
296
+ if (k === "datetime" || k === "iso_datetime") schema.format = "date-time";
297
+ if (k === "ip") schema.format = "ipv4";
298
+ if (k === "regex") schema.pattern = String(c.value ?? c.regex);
299
+ if (k === "includes") schema.pattern = c.value;
300
+ if (k === "startsWith") schema.pattern = `^${c.value}`;
301
+ if (k === "endsWith") schema.pattern = `${c.value}$`;
302
+ }
303
+ return schema;
304
+ }
305
+ function applyNumberChecks(schema, checks) {
306
+ if (!checks) return schema;
307
+ for (const c of checks) {
308
+ const k = c.kind ?? c.type;
309
+ if (k === "min" || k === "minimum" || k === "gte") schema.minimum = c.value ?? c.minimum;
310
+ if (k === "max" || k === "maximum" || k === "lte") schema.maximum = c.value ?? c.maximum;
311
+ if (k === "int") schema.type = "integer";
312
+ if (k === "positive") schema.minimum = 0;
313
+ if (k === "negative") schema.maximum = 0;
314
+ if (k === "multipleOf") schema.multipleOf = c.value;
315
+ }
316
+ return schema;
317
+ }
318
+ function objectSchemaToParams(schema) {
319
+ if (schema.type !== "object" || !schema.properties) return [];
320
+ const required = new Set(schema.required ?? []);
321
+ return Object.entries(schema.properties).map(([name, propSchema]) => ({
322
+ name,
323
+ in: "query",
324
+ required: required.has(name),
325
+ schema: propSchema,
326
+ ...propSchema.description ? { description: propSchema.description } : {}
327
+ }));
328
+ }
329
+ //#endregion
330
+ export { generateOpenAPI, scalarHTML };
@@ -0,0 +1,139 @@
1
+ import { AnySchema, InferSchemaInput, InferSchemaOutput } from "./core/schema.mjs";
2
+ import { ErrorDef, GuardDef, GuardFn, ProcedureDef, ResolveContext, RouterDef, WrapDef, WrapFn } from "./types.mjs";
3
+ import { ProcedureBuilder } from "./builder.mjs";
4
+ import { StorageConfig, useStorage } from "./core/storage.mjs";
5
+ import { AnalyticsOptions } from "./plugins/analytics.mjs";
6
+ import { ScalarOptions } from "./scalar.mjs";
7
+ import { Hookable } from "hookable";
8
+
9
+ //#region src/silgi.d.ts
10
+ interface SilgiHooks {
11
+ /** Called before a request is processed */
12
+ request: (event: {
13
+ path: string;
14
+ input: unknown;
15
+ }) => void;
16
+ /** Called after a successful response */
17
+ response: (event: {
18
+ path: string;
19
+ output: unknown;
20
+ durationMs: number;
21
+ }) => void;
22
+ /** Called when an error occurs */
23
+ error: (event: {
24
+ path: string;
25
+ error: unknown;
26
+ }) => void;
27
+ /** Called when the server starts */
28
+ 'serve:start': (event: {
29
+ url: string;
30
+ port: number;
31
+ hostname: string;
32
+ }) => void;
33
+ }
34
+ interface SilgiConfig<TCtx extends Record<string, unknown>> {
35
+ context: (req: Request) => TCtx | Promise<TCtx>;
36
+ /** Register lifecycle hooks */
37
+ hooks?: Partial<{ [K in keyof SilgiHooks]: SilgiHooks[K] | SilgiHooks[K][] }>;
38
+ /**
39
+ * Storage configuration — mount drivers by path prefix.
40
+ *
41
+ * ```ts
42
+ * import redisDriver from 'unstorage/drivers/redis'
43
+ * import memoryDriver from 'unstorage/drivers/memory'
44
+ *
45
+ * storage: {
46
+ * cache: redisDriver({ url: 'redis://localhost' }),
47
+ * sessions: memoryDriver(),
48
+ * }
49
+ * ```
50
+ *
51
+ * Or pass a pre-built unstorage instance:
52
+ * ```ts
53
+ * storage: myStorageInstance
54
+ * ```
55
+ */
56
+ storage?: StorageConfig;
57
+ }
58
+ interface SilgiInstance<TBaseCtx extends Record<string, unknown>> {
59
+ /** Register a lifecycle hook */
60
+ hook: Hookable<SilgiHooks>['hook'];
61
+ /** Remove a lifecycle hook */
62
+ removeHook: Hookable<SilgiHooks>['removeHook'];
63
+ /** Access storage with optional prefix — uses configured mounts */
64
+ useStorage: typeof useStorage;
65
+ /** Create a guard middleware (flat, zero-closure) */
66
+ guard: GuardFactory<TBaseCtx>;
67
+ /** Create a wrap middleware (onion, before+after) */
68
+ wrap: (fn: WrapFn<TBaseCtx>) => WrapDef<TBaseCtx>;
69
+ /** Start a builder — resolve only */
70
+ $resolve: ProcedureBuilder<'query', TBaseCtx>['$resolve'];
71
+ /** Start a builder — set input schema */
72
+ $input: ProcedureBuilder<'query', TBaseCtx>['$input'];
73
+ /** Start a builder — add middleware */
74
+ $use: ProcedureBuilder<'query', TBaseCtx>['$use'];
75
+ /** Start a builder — set output schema */
76
+ $output: ProcedureBuilder<'query', TBaseCtx>['$output'];
77
+ /** Start a builder — set errors */
78
+ $errors: ProcedureBuilder<'query', TBaseCtx>['$errors'];
79
+ /** Start a builder — set route metadata */
80
+ $route: ProcedureBuilder<'query', TBaseCtx>['$route'];
81
+ /** Start a builder — set custom metadata */
82
+ $meta: ProcedureBuilder<'query', TBaseCtx>['$meta'];
83
+ /** Define a subscription (SSE stream) */
84
+ subscription: SubscriptionFactory<TBaseCtx>;
85
+ /** Assemble router and compile pipelines */
86
+ router: <T extends RouterDef>(def: T) => T;
87
+ /** Create a Fetch API handler: (Request) => Response */
88
+ handler: (router: RouterDef, options?: {
89
+ /** Enable Scalar API Reference UI at /reference and /openapi.json */scalar?: boolean | ScalarOptions; /** Enable analytics dashboard at /analytics */
90
+ analytics?: boolean | AnalyticsOptions;
91
+ }) => (request: Request) => Response | Promise<Response>;
92
+ /** Create & start a Node.js HTTP server */
93
+ serve: (router: RouterDef, options?: {
94
+ port?: number;
95
+ hostname?: string; /** Enable Scalar API Reference UI at /reference and /openapi.json */
96
+ scalar?: boolean | ScalarOptions; /** Enable analytics dashboard at /analytics */
97
+ analytics?: boolean | AnalyticsOptions; /** Enable WebSocket RPC (requires crossws) */
98
+ ws?: boolean; /** Enable HTTP/2 (requires cert + key for TLS) */
99
+ http2?: {
100
+ cert: string;
101
+ key: string;
102
+ };
103
+ }) => void;
104
+ }
105
+ interface GuardConfig<TBaseCtx, TReturn extends Record<string, unknown> | void, TErrors extends ErrorDef> {
106
+ errors?: TErrors;
107
+ fn: GuardFn<TBaseCtx, TReturn>;
108
+ }
109
+ interface GuardFactory<TBaseCtx> {
110
+ /** Simple guard: guard(fn) */
111
+ <TReturn extends Record<string, unknown> | void>(fn: GuardFn<TBaseCtx, TReturn>): GuardDef<TBaseCtx, TReturn, {}>;
112
+ /** Guard with typed errors: guard({ errors, fn }) */
113
+ <TReturn extends Record<string, unknown> | void, TErrors extends ErrorDef>(config: GuardConfig<TBaseCtx, TReturn, TErrors>): GuardDef<TBaseCtx, TReturn, TErrors>;
114
+ }
115
+ interface SubscriptionFactory<TBaseCtx extends Record<string, unknown>> {
116
+ /** Builder: `subscription()` — returns chainable builder */
117
+ (): ProcedureBuilder<'subscription', TBaseCtx>;
118
+ /** Short: `subscription(resolve)` */
119
+ <TOutput>(resolve: (opts: ResolveContext<TBaseCtx, undefined, {}>) => AsyncIterableIterator<TOutput>): ProcedureDef<'subscription', undefined, TOutput, {}>;
120
+ /** Short: `subscription(input, resolve)` */
121
+ <TSchema extends AnySchema, TOutput>(input: TSchema, resolve: (opts: ResolveContext<TBaseCtx, InferSchemaOutput<TSchema>, {}>) => AsyncIterableIterator<TOutput>): ProcedureDef<'subscription', InferSchemaInput<TSchema>, TOutput, {}>;
122
+ }
123
+ /**
124
+ * Create a Silgi RPC instance with typed context.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * const k = silgi({
129
+ * context: (req) => ({ db: getDB(), user: getUser(req) }),
130
+ * hooks: {
131
+ * request: ({ path }) => console.log(`-> ${path}`),
132
+ * },
133
+ * })
134
+ * const { query, mutation, guard, wrap, router, serve } = k
135
+ * ```
136
+ */
137
+ declare function silgi<TBaseCtx extends Record<string, unknown>>(config: SilgiConfig<TBaseCtx>): SilgiInstance<TBaseCtx>;
138
+ //#endregion
139
+ export { SilgiConfig, SilgiInstance, silgi };
package/dist/silgi.mjs ADDED
@@ -0,0 +1,113 @@
1
+ import { createProcedureBuilder } from "./builder.mjs";
2
+ import { compileRouter } from "./compile.mjs";
3
+ import { assignPaths, routerCache } from "./core/router-utils.mjs";
4
+ import { createFetchHandler } from "./core/handler.mjs";
5
+ import { createHooks } from "hookable";
6
+ //#region src/silgi.ts
7
+ /**
8
+ * silgi() — the main entry point.
9
+ *
10
+ * Creates a Silgi instance with typed context.
11
+ * All procedure/middleware factories are methods on this instance,
12
+ * so context type flows automatically.
13
+ *
14
+ * Usage:
15
+ * const k = silgi({ context: (req) => ({ db, headers }) })
16
+ * export const { query, mutation, guard, wrap, router, handler } = k
17
+ */
18
+ function createProcedure(type, ...args) {
19
+ if (args.length === 0) return createProcedureBuilder(type);
20
+ if (args.length === 1 && typeof args[0] === "function") return {
21
+ type,
22
+ input: null,
23
+ output: null,
24
+ errors: null,
25
+ use: null,
26
+ resolve: args[0],
27
+ route: null,
28
+ meta: null
29
+ };
30
+ if (args.length === 2 && typeof args[1] === "function") return {
31
+ type,
32
+ input: args[0],
33
+ output: null,
34
+ errors: null,
35
+ use: null,
36
+ resolve: args[1],
37
+ route: null,
38
+ meta: null
39
+ };
40
+ throw new TypeError(`Invalid arguments for ${type}()`);
41
+ }
42
+ /**
43
+ * Create a Silgi RPC instance with typed context.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const k = silgi({
48
+ * context: (req) => ({ db: getDB(), user: getUser(req) }),
49
+ * hooks: {
50
+ * request: ({ path }) => console.log(`-> ${path}`),
51
+ * },
52
+ * })
53
+ * const { query, mutation, guard, wrap, router, serve } = k
54
+ * ```
55
+ */
56
+ function silgi(config) {
57
+ const contextFactory = config.context;
58
+ const hooks = createHooks();
59
+ if (config.hooks) {
60
+ for (const [name, fn] of Object.entries(config.hooks)) if (Array.isArray(fn)) for (const f of fn) hooks.hook(name, f);
61
+ else if (fn) hooks.hook(name, fn);
62
+ }
63
+ if (config.storage) import("./core/storage.mjs").then((m) => m.initStorage(config.storage));
64
+ return {
65
+ hook: hooks.hook,
66
+ removeHook: hooks.removeHook.bind(hooks),
67
+ useStorage: (...args) => {
68
+ return import("./core/storage.mjs").then((m) => m.useStorage(...args));
69
+ },
70
+ guard: (fnOrConfig) => {
71
+ if (typeof fnOrConfig === "function") return {
72
+ kind: "guard",
73
+ fn: fnOrConfig
74
+ };
75
+ return {
76
+ kind: "guard",
77
+ fn: fnOrConfig.fn,
78
+ errors: fnOrConfig.errors
79
+ };
80
+ },
81
+ wrap: (fn) => ({
82
+ kind: "wrap",
83
+ fn
84
+ }),
85
+ $resolve: ((fn) => createProcedure("query", fn)),
86
+ $input: ((schema) => createProcedureBuilder("query").$input(schema)),
87
+ $use: ((...middleware) => createProcedureBuilder("query").$use(...middleware)),
88
+ $output: ((schema) => createProcedureBuilder("query").$output(schema)),
89
+ $errors: ((errors) => createProcedureBuilder("query").$errors(errors)),
90
+ $route: ((route) => createProcedureBuilder("query").$route(route)),
91
+ $meta: ((meta) => createProcedureBuilder("query").$meta(meta)),
92
+ subscription: ((...args) => createProcedure("subscription", ...args)),
93
+ router: (def) => {
94
+ assignPaths(def);
95
+ const flat = compileRouter(def);
96
+ routerCache.set(def, flat);
97
+ return def;
98
+ },
99
+ handler: (routerDef, options) => {
100
+ let fetchHandler;
101
+ return (request) => {
102
+ fetchHandler ??= createFetchHandler(routerDef, contextFactory, hooks, options);
103
+ return fetchHandler(request);
104
+ };
105
+ },
106
+ serve: async (routerDef, options) => {
107
+ const { createServeHandler } = await import("./core/serve.mjs");
108
+ return createServeHandler(routerDef, contextFactory, hooks, options);
109
+ }
110
+ };
111
+ }
112
+ //#endregion
113
+ export { silgi };
@@ -0,0 +1,22 @@
1
+ import { RouterDef } from "./types.mjs";
2
+
3
+ //#region src/trpc-interop.d.ts
4
+ /**
5
+ * Convert a tRPC router to a Silgi RouterDef.
6
+ *
7
+ * Walks the tRPC router's `_def.procedures` and wraps each one as
8
+ * a Silgi ProcedureDef that calls the tRPC procedure's resolver.
9
+ *
10
+ * Supports:
11
+ * - tRPC v10 and v11 routers
12
+ * - Queries, mutations, and subscriptions
13
+ * - Input schemas (passed through as-is)
14
+ * - Middleware (runs inside tRPC, not Silgi's pipeline)
15
+ *
16
+ * Does NOT support:
17
+ * - Converting tRPC middleware to Silgi guards/wraps
18
+ * - tRPC context factories (use Silgi's context instead)
19
+ */
20
+ declare function fromTRPC(trpcRouter: unknown): RouterDef;
21
+ //#endregion
22
+ export { fromTRPC };
@@ -0,0 +1,68 @@
1
+ //#region src/trpc-interop.ts
2
+ /**
3
+ * Convert a tRPC router to a Silgi RouterDef.
4
+ *
5
+ * Walks the tRPC router's `_def.procedures` and wraps each one as
6
+ * a Silgi ProcedureDef that calls the tRPC procedure's resolver.
7
+ *
8
+ * Supports:
9
+ * - tRPC v10 and v11 routers
10
+ * - Queries, mutations, and subscriptions
11
+ * - Input schemas (passed through as-is)
12
+ * - Middleware (runs inside tRPC, not Silgi's pipeline)
13
+ *
14
+ * Does NOT support:
15
+ * - Converting tRPC middleware to Silgi guards/wraps
16
+ * - tRPC context factories (use Silgi's context instead)
17
+ */
18
+ function fromTRPC(trpcRouter) {
19
+ if (!trpcRouter || typeof trpcRouter !== "object") throw new Error("fromTRPC: expected a tRPC router object");
20
+ const router = trpcRouter;
21
+ return walkTRPCRouter(router._def?.procedures ?? router);
22
+ }
23
+ function walkTRPCRouter(node) {
24
+ const result = {};
25
+ for (const [key, value] of Object.entries(node)) {
26
+ if (!value || typeof value !== "object") continue;
27
+ const proc = value;
28
+ if (isTRPCProcedure(proc)) result[key] = convertProcedure(proc);
29
+ else if (isTRPCRouter(proc)) result[key] = walkTRPCRouter(proc._def?.procedures ?? proc);
30
+ else result[key] = walkTRPCRouter(proc);
31
+ }
32
+ return result;
33
+ }
34
+ function isTRPCProcedure(value) {
35
+ const def = value._def;
36
+ if (!def) return false;
37
+ return def.type === "query" || def.type === "mutation" || def.type === "subscription";
38
+ }
39
+ function isTRPCRouter(value) {
40
+ return !!value._def?.procedures;
41
+ }
42
+ function convertProcedure(trpcProc) {
43
+ const def = trpcProc._def;
44
+ const type = def.type === "subscription" ? "subscription" : def.type === "mutation" ? "mutation" : "query";
45
+ return {
46
+ type,
47
+ input: def.inputs?.[0] ?? null,
48
+ output: null,
49
+ errors: null,
50
+ use: null,
51
+ resolve: async ({ input, ctx }) => {
52
+ if (typeof def.resolver === "function") return def.resolver({
53
+ input,
54
+ ctx,
55
+ type
56
+ });
57
+ if (typeof trpcProc.call === "function") return trpcProc.call({
58
+ input,
59
+ ctx
60
+ });
61
+ throw new Error(`Cannot resolve tRPC procedure: ${JSON.stringify(Object.keys(def))}`);
62
+ },
63
+ route: null,
64
+ meta: { __trpc: true }
65
+ };
66
+ }
67
+ //#endregion
68
+ export { fromTRPC };