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.
Files changed (210) hide show
  1. package/README.md +102 -1
  2. package/dist/_virtual/_rolldown/runtime.mjs +5 -0
  3. package/dist/adapters/_fetch-adapter.d.mts +18 -0
  4. package/dist/adapters/_fetch-adapter.mjs +53 -0
  5. package/dist/adapters/astro.d.mts +15 -0
  6. package/dist/adapters/astro.mjs +31 -0
  7. package/dist/adapters/aws-lambda.d.mts +42 -0
  8. package/dist/adapters/aws-lambda.mjs +92 -0
  9. package/dist/adapters/express.d.mts +16 -0
  10. package/dist/adapters/express.mjs +120 -0
  11. package/dist/adapters/message-port.d.mts +42 -0
  12. package/dist/adapters/message-port.mjs +132 -0
  13. package/dist/adapters/nestjs.d.mts +25 -0
  14. package/dist/adapters/nestjs.mjs +75 -0
  15. package/dist/adapters/nextjs.d.mts +14 -0
  16. package/dist/adapters/nextjs.mjs +29 -0
  17. package/dist/adapters/peer.d.mts +27 -0
  18. package/dist/adapters/peer.mjs +36 -0
  19. package/dist/adapters/remix.d.mts +15 -0
  20. package/dist/adapters/remix.mjs +30 -0
  21. package/dist/adapters/solidstart.d.mts +12 -0
  22. package/dist/adapters/solidstart.mjs +29 -0
  23. package/dist/adapters/sveltekit.d.mts +14 -0
  24. package/dist/adapters/sveltekit.mjs +30 -0
  25. package/dist/broker/index.d.mts +62 -0
  26. package/dist/broker/index.mjs +153 -0
  27. package/dist/broker/nats.d.mts +33 -0
  28. package/dist/broker/nats.mjs +31 -0
  29. package/dist/broker/redis.d.mts +51 -0
  30. package/dist/broker/redis.mjs +92 -0
  31. package/dist/builder.d.mts +55 -0
  32. package/dist/builder.mjs +70 -0
  33. package/dist/callable.d.mts +19 -0
  34. package/dist/callable.mjs +42 -0
  35. package/dist/caller.mjs +90 -0
  36. package/dist/client/adapters/fetch/index.d.mts +19 -0
  37. package/dist/client/adapters/fetch/index.mjs +82 -0
  38. package/dist/client/adapters/ofetch/index.d.mts +57 -0
  39. package/dist/client/adapters/ofetch/index.mjs +100 -0
  40. package/dist/client/adapters/websocket/index.d.mts +20 -0
  41. package/dist/client/adapters/websocket/index.mjs +101 -0
  42. package/dist/client/client.d.mts +37 -0
  43. package/dist/client/client.mjs +80 -0
  44. package/dist/client/consume.d.mts +50 -0
  45. package/dist/client/consume.mjs +66 -0
  46. package/dist/client/dynamic-link.d.mts +16 -0
  47. package/dist/client/dynamic-link.mjs +19 -0
  48. package/dist/client/index.d.mts +6 -0
  49. package/dist/client/index.mjs +5 -0
  50. package/dist/client/interceptor.d.mts +31 -0
  51. package/dist/client/interceptor.mjs +34 -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 +26 -0
  55. package/dist/client/plugins/batch.mjs +64 -0
  56. package/dist/client/plugins/circuit-breaker.d.mts +24 -0
  57. package/dist/client/plugins/circuit-breaker.mjs +60 -0
  58. package/dist/client/plugins/csrf.d.mts +13 -0
  59. package/dist/client/plugins/csrf.mjs +20 -0
  60. package/dist/client/plugins/dedupe.d.mts +10 -0
  61. package/dist/client/plugins/dedupe.mjs +28 -0
  62. package/dist/client/plugins/index.d.mts +8 -0
  63. package/dist/client/plugins/index.mjs +8 -0
  64. package/dist/client/plugins/otel.d.mts +12 -0
  65. package/dist/client/plugins/otel.mjs +27 -0
  66. package/dist/client/plugins/retry.d.mts +34 -0
  67. package/dist/client/plugins/retry.mjs +79 -0
  68. package/dist/client/plugins/timeout.d.mts +10 -0
  69. package/dist/client/plugins/timeout.mjs +14 -0
  70. package/dist/client/server.d.mts +16 -0
  71. package/dist/client/server.mjs +62 -0
  72. package/dist/client/types.d.mts +29 -0
  73. package/dist/codec/devalue.d.mts +21 -0
  74. package/dist/codec/devalue.mjs +33 -0
  75. package/dist/codec/msgpack.d.mts +18 -0
  76. package/dist/codec/msgpack.mjs +45 -0
  77. package/dist/codec/sanitize.mjs +38 -0
  78. package/dist/compile.d.mts +46 -0
  79. package/dist/compile.mjs +332 -0
  80. package/dist/contract.d.mts +36 -0
  81. package/dist/contract.mjs +40 -0
  82. package/dist/core/codec.mjs +67 -0
  83. package/dist/core/dispatch.mjs +62 -0
  84. package/dist/core/error.d.mts +104 -0
  85. package/dist/core/error.mjs +139 -0
  86. package/dist/core/handler.d.mts +6 -0
  87. package/dist/core/handler.mjs +148 -0
  88. package/dist/core/input.mjs +49 -0
  89. package/dist/core/iterator.d.mts +17 -0
  90. package/dist/core/iterator.mjs +79 -0
  91. package/dist/core/router-utils.d.mts +25 -0
  92. package/dist/core/router-utils.mjs +98 -0
  93. package/dist/core/schema.d.mts +20 -0
  94. package/dist/core/schema.mjs +33 -0
  95. package/dist/core/serve.d.mts +51 -0
  96. package/dist/core/serve.mjs +76 -0
  97. package/dist/core/sse.d.mts +18 -0
  98. package/dist/core/sse.mjs +98 -0
  99. package/dist/core/storage.d.mts +20 -0
  100. package/dist/core/storage.mjs +61 -0
  101. package/dist/core/task.d.mts +62 -0
  102. package/dist/core/task.mjs +165 -0
  103. package/dist/core/utils.mjs +24 -0
  104. package/dist/index.d.mts +18 -37
  105. package/dist/index.mjs +15 -7
  106. package/dist/integrations/ai/index.d.mts +25 -0
  107. package/dist/integrations/ai/index.mjs +117 -0
  108. package/dist/integrations/better-auth/index.d.mts +61 -0
  109. package/dist/integrations/better-auth/index.mjs +332 -0
  110. package/dist/integrations/drizzle/index.d.mts +27 -0
  111. package/dist/integrations/drizzle/index.mjs +286 -0
  112. package/dist/integrations/hey-api/index.d.mts +2 -0
  113. package/dist/integrations/hey-api/index.mjs +2 -0
  114. package/dist/integrations/hey-api/to-client.d.mts +20 -0
  115. package/dist/integrations/hey-api/to-client.mjs +39 -0
  116. package/dist/integrations/pinia-colada/general-utils.d.mts +13 -0
  117. package/dist/integrations/pinia-colada/general-utils.mjs +9 -0
  118. package/dist/integrations/pinia-colada/index.d.mts +6 -0
  119. package/dist/integrations/pinia-colada/index.mjs +5 -0
  120. package/dist/integrations/pinia-colada/key.d.mts +11 -0
  121. package/dist/integrations/pinia-colada/key.mjs +11 -0
  122. package/dist/integrations/pinia-colada/procedure-utils.d.mts +25 -0
  123. package/dist/integrations/pinia-colada/procedure-utils.mjs +33 -0
  124. package/dist/integrations/pinia-colada/router-utils.d.mts +17 -0
  125. package/dist/integrations/pinia-colada/router-utils.mjs +30 -0
  126. package/dist/integrations/pinia-colada/types.d.mts +25 -0
  127. package/dist/integrations/react/index.d.mts +83 -0
  128. package/dist/integrations/react/index.mjs +196 -0
  129. package/dist/integrations/tanstack-query/index.d.mts +120 -0
  130. package/dist/integrations/tanstack-query/index.mjs +100 -0
  131. package/dist/integrations/tanstack-query/ssr.d.mts +60 -0
  132. package/dist/integrations/tanstack-query/ssr.mjs +102 -0
  133. package/dist/integrations/zod/converter.d.mts +75 -0
  134. package/dist/integrations/zod/converter.mjs +345 -0
  135. package/dist/integrations/zod/index.d.mts +2 -0
  136. package/dist/integrations/zod/index.mjs +2 -0
  137. package/dist/lazy.d.mts +22 -0
  138. package/dist/lazy.mjs +34 -0
  139. package/dist/lifecycle.d.mts +36 -0
  140. package/dist/lifecycle.mjs +46 -0
  141. package/dist/map-input.d.mts +17 -0
  142. package/dist/map-input.mjs +47 -0
  143. package/dist/plugins/analytics.d.mts +236 -0
  144. package/dist/plugins/analytics.mjs +816 -0
  145. package/dist/plugins/batch-server.d.mts +20 -0
  146. package/dist/plugins/batch-server.mjs +91 -0
  147. package/dist/plugins/body-limit.d.mts +19 -0
  148. package/dist/plugins/body-limit.mjs +49 -0
  149. package/dist/plugins/cache.d.mts +170 -0
  150. package/dist/plugins/cache.mjs +212 -0
  151. package/dist/plugins/coerce.d.mts +24 -0
  152. package/dist/plugins/coerce.mjs +70 -0
  153. package/dist/plugins/cookies.d.mts +14 -0
  154. package/dist/plugins/cookies.mjs +48 -0
  155. package/dist/plugins/cors.d.mts +43 -0
  156. package/dist/plugins/cors.mjs +62 -0
  157. package/dist/plugins/file-upload.d.mts +38 -0
  158. package/dist/plugins/file-upload.mjs +102 -0
  159. package/dist/plugins/index.d.mts +14 -0
  160. package/dist/plugins/index.mjs +14 -0
  161. package/dist/plugins/otel.d.mts +35 -0
  162. package/dist/plugins/otel.mjs +40 -0
  163. package/dist/plugins/pino.d.mts +60 -0
  164. package/dist/plugins/pino.mjs +42 -0
  165. package/dist/plugins/pubsub.d.mts +50 -0
  166. package/dist/plugins/pubsub.mjs +53 -0
  167. package/dist/plugins/ratelimit.d.mts +53 -0
  168. package/dist/plugins/ratelimit.mjs +92 -0
  169. package/dist/plugins/signing.d.mts +41 -0
  170. package/dist/plugins/signing.mjs +118 -0
  171. package/dist/plugins/strict-get.d.mts +10 -0
  172. package/dist/plugins/strict-get.mjs +33 -0
  173. package/dist/scalar.d.mts +49 -0
  174. package/dist/scalar.mjs +311 -0
  175. package/dist/silgi.d.mts +144 -0
  176. package/dist/silgi.mjs +156 -0
  177. package/dist/trpc-interop.d.mts +22 -0
  178. package/dist/trpc-interop.mjs +68 -0
  179. package/dist/types.d.mts +108 -0
  180. package/dist/ws.d.mts +88 -0
  181. package/dist/ws.mjs +205 -0
  182. package/lib/dashboard/index.html +123 -0
  183. package/lib/ocache.d.mts +1 -0
  184. package/lib/ocache.mjs +1 -0
  185. package/lib/ofetch.d.mts +1 -0
  186. package/lib/ofetch.mjs +1 -0
  187. package/lib/srvx.d.mts +1 -0
  188. package/lib/srvx.mjs +1 -0
  189. package/lib/unstorage.d.mts +1 -0
  190. package/lib/unstorage.mjs +1 -0
  191. package/package.json +324 -65
  192. package/bin/silgi.mjs +0 -3
  193. package/dist/chunks/generate.mjs +0 -933
  194. package/dist/chunks/init.mjs +0 -21
  195. package/dist/cli/config.d.mts +0 -19
  196. package/dist/cli/config.d.ts +0 -19
  197. package/dist/cli/config.mjs +0 -5
  198. package/dist/cli/index.d.mts +0 -2
  199. package/dist/cli/index.d.ts +0 -2
  200. package/dist/cli/index.mjs +0 -119
  201. package/dist/index.d.ts +0 -37
  202. package/dist/plugins/openapi.d.mts +0 -138
  203. package/dist/plugins/openapi.d.ts +0 -138
  204. package/dist/plugins/openapi.mjs +0 -204
  205. package/dist/plugins/scalar.d.mts +0 -14
  206. package/dist/plugins/scalar.d.ts +0 -14
  207. package/dist/plugins/scalar.mjs +0 -66
  208. package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
  209. package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
  210. package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
@@ -0,0 +1,48 @@
1
+ import { parse, parseSetCookie, serialize, splitSetCookieString } from "cookie-es";
2
+ //#region src/plugins/cookies.ts
3
+ /**
4
+ * Cookie helpers — thin wrapper over cookie-es (unjs).
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { getCookie, setCookie, deleteCookie } from "silgi/cookies"
9
+ *
10
+ * const auth = k.guard((ctx) => {
11
+ * const token = getCookie(ctx.headers, "session")
12
+ * if (!token) throw new SilgiError("UNAUTHORIZED")
13
+ * return { sessionToken: token }
14
+ * })
15
+ * ```
16
+ */
17
+ /** Parse a specific cookie value by name. */
18
+ function getCookie(headers, name) {
19
+ const str = typeof headers === "string" ? headers : headers.cookie ?? headers.Cookie ?? "";
20
+ if (!str) return void 0;
21
+ return parse(str)[name];
22
+ }
23
+ /** Parse all cookies into a key-value object. */
24
+ function parseCookies(headers) {
25
+ const str = typeof headers === "string" ? headers : headers.cookie ?? headers.Cookie ?? "";
26
+ if (!str) return {};
27
+ return parse(str);
28
+ }
29
+ /** Serialize a Set-Cookie header. Defaults: path="/", httpOnly, sameSite=lax. */
30
+ function setCookie(name, value, options = {}) {
31
+ const merged = {
32
+ path: "/",
33
+ httpOnly: true,
34
+ sameSite: "lax",
35
+ ...options
36
+ };
37
+ if (merged.sameSite === "none") merged.secure = true;
38
+ return serialize(name, value, merged);
39
+ }
40
+ /** Delete a cookie by setting maxAge=0. */
41
+ function deleteCookie(name, options = {}) {
42
+ return setCookie(name, "", {
43
+ ...options,
44
+ maxAge: 0
45
+ });
46
+ }
47
+ //#endregion
48
+ export { deleteCookie, getCookie, parseCookies, parseSetCookie, setCookie, splitSetCookieString };
@@ -0,0 +1,43 @@
1
+ //#region src/plugins/cors.d.ts
2
+ /**
3
+ * CORS plugin — response header helper.
4
+ *
5
+ * Returns CORS headers to merge into responses.
6
+ * Handles preflight OPTIONS requests automatically via the handler.
7
+ */
8
+ interface CORSOptions {
9
+ origin?: string | string[] | ((origin: string) => boolean);
10
+ methods?: string[];
11
+ allowedHeaders?: string[];
12
+ exposedHeaders?: string[];
13
+ credentials?: boolean;
14
+ maxAge?: number;
15
+ }
16
+ /**
17
+ * Create CORS headers config for silgi().
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { cors } from "silgi/cors"
22
+ *
23
+ * const k = silgi({
24
+ * context: (req) => ({}),
25
+ * hooks: cors({ origin: "https://app.example.com", credentials: true }),
26
+ * })
27
+ * ```
28
+ */
29
+ declare function cors(options?: CORSOptions): {
30
+ headers: Record<string, string>;
31
+ options: CORSOptions;
32
+ };
33
+ /**
34
+ * CORS header map — use in custom serve() or middleware.
35
+ * Returns headers object to merge into responses.
36
+ *
37
+ * For dynamic origins (array/function), omits Access-Control-Allow-Origin
38
+ * entirely when the request origin is not allowed — this is the correct
39
+ * behavior per the CORS spec (browsers reject missing header).
40
+ */
41
+ declare function corsHeaders(options?: CORSOptions, requestOrigin?: string): Record<string, string>;
42
+ //#endregion
43
+ export { CORSOptions, cors, corsHeaders };
@@ -0,0 +1,62 @@
1
+ //#region src/plugins/cors.ts
2
+ /**
3
+ * Create CORS headers config for silgi().
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { cors } from "silgi/cors"
8
+ *
9
+ * const k = silgi({
10
+ * context: (req) => ({}),
11
+ * hooks: cors({ origin: "https://app.example.com", credentials: true }),
12
+ * })
13
+ * ```
14
+ */
15
+ function cors(options = {}) {
16
+ const origin = options.origin ?? "*";
17
+ if (options.credentials && typeof origin === "string" && origin === "*") throw new Error("[silgi/cors] Cannot use credentials: true with origin: \"*\". Set an explicit origin.");
18
+ return {
19
+ headers: corsHeaders(options),
20
+ options
21
+ };
22
+ }
23
+ /**
24
+ * CORS header map — use in custom serve() or middleware.
25
+ * Returns headers object to merge into responses.
26
+ *
27
+ * For dynamic origins (array/function), omits Access-Control-Allow-Origin
28
+ * entirely when the request origin is not allowed — this is the correct
29
+ * behavior per the CORS spec (browsers reject missing header).
30
+ */
31
+ function corsHeaders(options = {}, requestOrigin) {
32
+ const origin = options.origin ?? "*";
33
+ const methods = options.methods ?? [
34
+ "GET",
35
+ "POST",
36
+ "PUT",
37
+ "PATCH",
38
+ "DELETE",
39
+ "OPTIONS"
40
+ ];
41
+ const allowedHeaders = options.allowedHeaders ?? ["Content-Type", "Authorization"];
42
+ const headers = {
43
+ "access-control-allow-methods": methods.join(", "),
44
+ "access-control-allow-headers": allowedHeaders.join(", ")
45
+ };
46
+ if (typeof origin === "string") {
47
+ headers["access-control-allow-origin"] = origin;
48
+ if (origin !== "*") headers["vary"] = "Origin";
49
+ } else if (Array.isArray(origin)) {
50
+ if (requestOrigin && origin.includes(requestOrigin)) headers["access-control-allow-origin"] = requestOrigin;
51
+ headers["vary"] = "Origin";
52
+ } else if (typeof origin === "function") {
53
+ if (requestOrigin && origin(requestOrigin)) headers["access-control-allow-origin"] = requestOrigin;
54
+ headers["vary"] = "Origin";
55
+ }
56
+ if (options.credentials) headers["access-control-allow-credentials"] = "true";
57
+ if (options.maxAge !== void 0) headers["access-control-max-age"] = String(options.maxAge);
58
+ if (options.exposedHeaders) headers["access-control-expose-headers"] = options.exposedHeaders.join(", ");
59
+ return headers;
60
+ }
61
+ //#endregion
62
+ export { cors, corsHeaders };
@@ -0,0 +1,38 @@
1
+ import { GuardDef } from "../types.mjs";
2
+
3
+ //#region src/plugins/file-upload.d.ts
4
+ interface FileGuardOptions {
5
+ /** Maximum file size in bytes. Default: 10MB */
6
+ maxFileSize?: number;
7
+ /** Allowed MIME types (supports wildcards like "image/*"). Default: all */
8
+ allowedTypes?: string[];
9
+ /** Maximum number of files. Default: 1 */
10
+ maxFiles?: number;
11
+ /** Form field name for the file. Default: "file" */
12
+ fieldName?: string;
13
+ }
14
+ interface UploadedFile {
15
+ name: string;
16
+ size: number;
17
+ type: string;
18
+ arrayBuffer(): Promise<ArrayBuffer>;
19
+ text(): Promise<string>;
20
+ stream(): ReadableStream<Uint8Array>;
21
+ }
22
+ /**
23
+ * Guard that parses multipart file uploads from the request.
24
+ *
25
+ * Adds `ctx.file` (single) or `ctx.files` (multiple) to the context.
26
+ * Validates file size and MIME type before the procedure runs.
27
+ */
28
+ declare function fileGuard(options?: FileGuardOptions): GuardDef<Record<string, unknown>>;
29
+ /**
30
+ * Parse multipart form data from a Request.
31
+ * Returns files and fields separately.
32
+ */
33
+ declare function parseMultipart(request: Request): Promise<{
34
+ files: UploadedFile[];
35
+ fields: Record<string, string>;
36
+ }>;
37
+ //#endregion
38
+ export { FileGuardOptions, UploadedFile, fileGuard, parseMultipart };
@@ -0,0 +1,102 @@
1
+ import { SilgiError } from "../core/error.mjs";
2
+ //#region src/plugins/file-upload.ts
3
+ /**
4
+ * File upload/download — type-safe File/Blob handling.
5
+ *
6
+ * Parse multipart/form-data requests and pass File objects
7
+ * to procedure handlers. Works with serve() and handler().
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { fileInput, fileGuard } from "silgi/plugins"
12
+ *
13
+ * const uploadAvatar = k
14
+ * .$use(fileGuard({ maxFileSize: 5 * 1024 * 1024, allowedTypes: ["image/*"] }))
15
+ * .$resolve(async ({ ctx }) => {
16
+ * const file = ctx.file
17
+ * const buffer = await file.arrayBuffer()
18
+ * return { name: file.name, size: file.size, type: file.type }
19
+ * })
20
+ * ```
21
+ */
22
+ /**
23
+ * Guard that parses multipart file uploads from the request.
24
+ *
25
+ * Adds `ctx.file` (single) or `ctx.files` (multiple) to the context.
26
+ * Validates file size and MIME type before the procedure runs.
27
+ */
28
+ function fileGuard(options = {}) {
29
+ const { maxFileSize = 10 * 1024 * 1024, allowedTypes, maxFiles = 1, fieldName: _fieldName = "file" } = options;
30
+ return {
31
+ kind: "guard",
32
+ fn: (ctx) => {
33
+ const files = ctx.__files;
34
+ if (!files || files.length === 0) throw new SilgiError("BAD_REQUEST", {
35
+ status: 400,
36
+ message: "No file uploaded"
37
+ });
38
+ if (files.length > maxFiles) throw new SilgiError("BAD_REQUEST", {
39
+ status: 400,
40
+ message: `Too many files: ${files.length} (max ${maxFiles})`
41
+ });
42
+ for (const file of files) {
43
+ if (file.size > maxFileSize) throw new SilgiError("PAYLOAD_TOO_LARGE", {
44
+ status: 413,
45
+ message: `File too large: ${file.size} bytes (max ${maxFileSize})`,
46
+ data: {
47
+ maxFileSize,
48
+ actualSize: file.size,
49
+ fileName: file.name
50
+ }
51
+ });
52
+ if (allowedTypes && !matchesMimeType(file.type, allowedTypes)) throw new SilgiError("BAD_REQUEST", {
53
+ status: 400,
54
+ message: `File type not allowed: ${file.type}`,
55
+ data: {
56
+ allowedTypes,
57
+ actualType: file.type,
58
+ fileName: file.name
59
+ }
60
+ });
61
+ }
62
+ return maxFiles === 1 ? { file: files[0] } : { files };
63
+ }
64
+ };
65
+ }
66
+ /**
67
+ * Parse multipart form data from a Request.
68
+ * Returns files and fields separately.
69
+ */
70
+ async function parseMultipart(request) {
71
+ const formData = await request.formData();
72
+ const files = [];
73
+ const fields = {};
74
+ for (const [key, value] of formData.entries()) if (typeof value !== "string") {
75
+ const file = value;
76
+ files.push({
77
+ name: file.name,
78
+ size: file.size,
79
+ type: file.type,
80
+ arrayBuffer: () => file.arrayBuffer(),
81
+ text: () => file.text(),
82
+ stream: () => file.stream()
83
+ });
84
+ } else fields[key] = value;
85
+ return {
86
+ files,
87
+ fields
88
+ };
89
+ }
90
+ function matchesMimeType(actual, patterns) {
91
+ for (const pattern of patterns) {
92
+ if (pattern === "*" || pattern === "*/*") return true;
93
+ if (pattern === actual) return true;
94
+ if (pattern.endsWith("/*")) {
95
+ const prefix = pattern.slice(0, -2);
96
+ if (actual.startsWith(prefix + "/")) return true;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+ //#endregion
102
+ export { fileGuard, parseMultipart };
@@ -0,0 +1,14 @@
1
+ import { AnalyticsCollector, AnalyticsOptions, AnalyticsSnapshot, ErrorEntry, ProcedureSnapshot, RequestTrace, TraceSpan, analyticsHTML, errorToMarkdown, trace } from "./analytics.mjs";
2
+ import { Span, Tracer, otelWrap } from "./otel.mjs";
3
+ import { CookieOptions, deleteCookie, getCookie, parseCookies, setCookie } from "./cookies.mjs";
4
+ import { CORSOptions, cors, corsHeaders } from "./cors.mjs";
5
+ import { Logger, LoggingOptions, loggingHooks } from "./pino.mjs";
6
+ import { MemoryRateLimiter, MemoryRateLimiterOptions, RateLimitGuardOptions, RateLimitResult, RateLimiter, rateLimitGuard } from "./ratelimit.mjs";
7
+ import { BodyLimitOptions, bodyLimitGuard } from "./body-limit.mjs";
8
+ import { strictGetGuard } from "./strict-get.mjs";
9
+ import { decrypt, encrypt, sign, unsign } from "./signing.mjs";
10
+ import { coerceGuard, coerceObject, coerceValue } from "./coerce.mjs";
11
+ import { BatchHandlerOptions, createBatchHandler } from "./batch-server.mjs";
12
+ import { MemoryPubSub, PubSubBackend, Publisher, createPublisher } from "./pubsub.mjs";
13
+ import { FileGuardOptions, UploadedFile, fileGuard, parseMultipart } from "./file-upload.mjs";
14
+ export { AnalyticsCollector, type AnalyticsOptions, type AnalyticsSnapshot, type BatchHandlerOptions, type BodyLimitOptions, type CORSOptions, type CookieOptions, type ErrorEntry, type FileGuardOptions, type Logger, type LoggingOptions, MemoryPubSub, MemoryRateLimiter, type MemoryRateLimiterOptions, type ProcedureSnapshot, type PubSubBackend, type Publisher, type RateLimitGuardOptions, type RateLimitResult, type RateLimiter, RequestTrace, type Span, type TraceSpan, type Tracer, type UploadedFile, analyticsHTML, bodyLimitGuard, coerceGuard, coerceObject, coerceValue, cors, corsHeaders, createBatchHandler, createPublisher, decrypt, deleteCookie, encrypt, errorToMarkdown, fileGuard, getCookie, loggingHooks, otelWrap, parseCookies, parseMultipart, rateLimitGuard, setCookie, sign, strictGetGuard, trace, unsign };
@@ -0,0 +1,14 @@
1
+ import { AnalyticsCollector, RequestTrace, analyticsHTML, errorToMarkdown, trace } from "./analytics.mjs";
2
+ import { cors, corsHeaders } from "./cors.mjs";
3
+ import { otelWrap } from "./otel.mjs";
4
+ import { loggingHooks } from "./pino.mjs";
5
+ import { MemoryRateLimiter, rateLimitGuard } from "./ratelimit.mjs";
6
+ import { bodyLimitGuard } from "./body-limit.mjs";
7
+ import { strictGetGuard } from "./strict-get.mjs";
8
+ import { deleteCookie, getCookie, parseCookies, setCookie } from "./cookies.mjs";
9
+ import { decrypt, encrypt, sign, unsign } from "./signing.mjs";
10
+ import { coerceGuard, coerceObject, coerceValue } from "./coerce.mjs";
11
+ import { createBatchHandler } from "./batch-server.mjs";
12
+ import { MemoryPubSub, createPublisher } from "./pubsub.mjs";
13
+ import { fileGuard, parseMultipart } from "./file-upload.mjs";
14
+ export { AnalyticsCollector, MemoryPubSub, MemoryRateLimiter, RequestTrace, analyticsHTML, bodyLimitGuard, coerceGuard, coerceObject, coerceValue, cors, corsHeaders, createBatchHandler, createPublisher, decrypt, deleteCookie, encrypt, errorToMarkdown, fileGuard, getCookie, loggingHooks, otelWrap, parseCookies, parseMultipart, rateLimitGuard, setCookie, sign, strictGetGuard, trace, unsign };
@@ -0,0 +1,35 @@
1
+ import { WrapDef } from "../types.mjs";
2
+
3
+ //#region src/plugins/otel.d.ts
4
+ interface Span {
5
+ setAttribute(key: string, value: string | number | boolean): void;
6
+ setStatus(status: {
7
+ code: number;
8
+ message?: string;
9
+ }): void;
10
+ addEvent(name: string, attributes?: Record<string, unknown>): void;
11
+ end(): void;
12
+ }
13
+ interface Tracer {
14
+ startSpan(name: string, options?: {
15
+ attributes?: Record<string, string | number | boolean>;
16
+ }): Span;
17
+ }
18
+ /**
19
+ * Create an OTel tracing wrap middleware.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { otelWrap } from "silgi/otel"
24
+ * import { trace } from "@opentelemetry/api"
25
+ *
26
+ * const tracing = otelWrap(trace.getTracer("my-service"))
27
+ *
28
+ * const proc = k
29
+ * .$use(tracing)
30
+ * .$resolve(({ ctx }) => ctx.db.find())
31
+ * ```
32
+ */
33
+ declare function otelWrap(tracer: Tracer, spanName?: string): WrapDef;
34
+ //#endregion
35
+ export { Span, Tracer, otelWrap };
@@ -0,0 +1,40 @@
1
+ //#region src/plugins/otel.ts
2
+ /**
3
+ * Create an OTel tracing wrap middleware.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { otelWrap } from "silgi/otel"
8
+ * import { trace } from "@opentelemetry/api"
9
+ *
10
+ * const tracing = otelWrap(trace.getTracer("my-service"))
11
+ *
12
+ * const proc = k
13
+ * .$use(tracing)
14
+ * .$resolve(({ ctx }) => ctx.db.find())
15
+ * ```
16
+ */
17
+ function otelWrap(tracer, spanName = "rpc.call") {
18
+ return {
19
+ kind: "wrap",
20
+ fn: async (_ctx, next) => {
21
+ const span = tracer.startSpan(spanName, { attributes: { "rpc.system": "silgi" } });
22
+ try {
23
+ const result = await next();
24
+ span.setStatus({ code: 0 });
25
+ return result;
26
+ } catch (error) {
27
+ span.setStatus({
28
+ code: 2,
29
+ message: String(error)
30
+ });
31
+ span.addEvent("exception", { "exception.message": error instanceof Error ? error.message : String(error) });
32
+ throw error;
33
+ } finally {
34
+ span.end();
35
+ }
36
+ }
37
+ };
38
+ }
39
+ //#endregion
40
+ export { otelWrap };
@@ -0,0 +1,60 @@
1
+ //#region src/plugins/pino.d.ts
2
+ /**
3
+ * Pino logging plugin — v2 hook-based.
4
+ *
5
+ * Logs request lifecycle events via silgi hooks.
6
+ */
7
+ interface Logger {
8
+ child(bindings: Record<string, unknown>): Logger;
9
+ info(obj: Record<string, unknown>, msg?: string): void;
10
+ error(obj: Record<string, unknown>, msg?: string): void;
11
+ warn(obj: Record<string, unknown>, msg?: string): void;
12
+ debug(obj: Record<string, unknown>, msg?: string): void;
13
+ }
14
+ interface LoggingOptions {
15
+ /** The root logger instance */
16
+ logger: Logger;
17
+ /** Log request received events (default: true) */
18
+ logRequests?: boolean;
19
+ /** Log response events (default: true) */
20
+ logResponses?: boolean;
21
+ }
22
+ /**
23
+ * Create logging hooks for silgi().
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import pino from "pino"
28
+ * import { loggingHooks } from "silgi/pino"
29
+ *
30
+ * const k = silgi({
31
+ * context: (req) => ({}),
32
+ * hooks: loggingHooks({ logger: pino() }),
33
+ * })
34
+ * ```
35
+ */
36
+ declare function loggingHooks(options: LoggingOptions): {
37
+ error: ({
38
+ path,
39
+ error
40
+ }: {
41
+ path: string;
42
+ error: unknown;
43
+ }) => void;
44
+ response?: (({
45
+ path,
46
+ durationMs
47
+ }: {
48
+ path: string;
49
+ durationMs: number;
50
+ }) => void) | undefined;
51
+ request?: (({
52
+ path,
53
+ input
54
+ }: {
55
+ path: string;
56
+ input: unknown;
57
+ }) => void) | undefined;
58
+ };
59
+ //#endregion
60
+ export { Logger, LoggingOptions, loggingHooks };
@@ -0,0 +1,42 @@
1
+ //#region src/plugins/pino.ts
2
+ /**
3
+ * Create logging hooks for silgi().
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import pino from "pino"
8
+ * import { loggingHooks } from "silgi/pino"
9
+ *
10
+ * const k = silgi({
11
+ * context: (req) => ({}),
12
+ * hooks: loggingHooks({ logger: pino() }),
13
+ * })
14
+ * ```
15
+ */
16
+ function loggingHooks(options) {
17
+ const { logger } = options;
18
+ const logRequests = options.logRequests ?? true;
19
+ const logResponses = options.logResponses ?? true;
20
+ return {
21
+ ...logRequests && { request: ({ path, input }) => {
22
+ logger.info({
23
+ path,
24
+ hasInput: input !== void 0
25
+ }, "request received");
26
+ } },
27
+ ...logResponses && { response: ({ path, durationMs }) => {
28
+ logger.info({
29
+ path,
30
+ durationMs: Math.round(durationMs * 100) / 100
31
+ }, "response sent");
32
+ } },
33
+ error: ({ path, error }) => {
34
+ logger.error({
35
+ path,
36
+ error: error instanceof Error ? error.message : String(error)
37
+ }, "request error");
38
+ }
39
+ };
40
+ }
41
+ //#endregion
42
+ export { loggingHooks };
@@ -0,0 +1,50 @@
1
+ //#region src/plugins/pubsub.d.ts
2
+ /**
3
+ * Publisher/PubSub — event pub/sub with pluggable backends.
4
+ *
5
+ * Publish events from any procedure, subscribe from SSE/WebSocket.
6
+ * Built-in memory adapter. Redis/Upstash adapters plug in via interface.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { createPublisher, MemoryPubSub } from "silgi/plugins"
11
+ *
12
+ * const pubsub = createPublisher(new MemoryPubSub())
13
+ *
14
+ * // Publish from a mutation
15
+ * const createUser = k.$resolve(async ({ input, ctx }) => {
16
+ * const user = await ctx.db.users.create(input)
17
+ * await pubsub.publish("user:created", user)
18
+ * return user
19
+ * })
20
+ *
21
+ * // Subscribe via SSE
22
+ * const onUserCreated = k.subscription(async function* () {
23
+ * yield* pubsub.subscribe("user:created")
24
+ * })
25
+ * ```
26
+ */
27
+ interface PubSubBackend {
28
+ publish(channel: string, data: unknown): Promise<void>;
29
+ subscribe(channel: string, callback: (data: unknown) => void): () => void;
30
+ }
31
+ declare class MemoryPubSub implements PubSubBackend {
32
+ #private;
33
+ publish(channel: string, data: unknown): Promise<void>;
34
+ subscribe(channel: string, callback: (data: unknown) => void): () => void;
35
+ }
36
+ interface Publisher {
37
+ /** Publish an event to a channel */
38
+ publish(channel: string, data: unknown): Promise<void>;
39
+ /** Subscribe to a channel — returns an async iterable for use in subscriptions */
40
+ subscribe<T = unknown>(channel: string): AsyncGenerator<T, void, unknown>;
41
+ }
42
+ /**
43
+ * Create a publisher from a PubSub backend.
44
+ *
45
+ * The publisher exposes `publish()` for mutations and `subscribe()`
46
+ * as an async generator for SSE/WebSocket subscriptions.
47
+ */
48
+ declare function createPublisher(backend: PubSubBackend): Publisher;
49
+ //#endregion
50
+ export { MemoryPubSub, PubSubBackend, Publisher, createPublisher };
@@ -0,0 +1,53 @@
1
+ //#region src/plugins/pubsub.ts
2
+ var MemoryPubSub = class {
3
+ #listeners = /* @__PURE__ */ new Map();
4
+ async publish(channel, data) {
5
+ const listeners = this.#listeners.get(channel);
6
+ if (!listeners) return;
7
+ for (const cb of listeners) cb(data);
8
+ }
9
+ subscribe(channel, callback) {
10
+ let set = this.#listeners.get(channel);
11
+ if (!set) {
12
+ set = /* @__PURE__ */ new Set();
13
+ this.#listeners.set(channel, set);
14
+ }
15
+ set.add(callback);
16
+ return () => {
17
+ set.delete(callback);
18
+ if (set.size === 0) this.#listeners.delete(channel);
19
+ };
20
+ }
21
+ };
22
+ /**
23
+ * Create a publisher from a PubSub backend.
24
+ *
25
+ * The publisher exposes `publish()` for mutations and `subscribe()`
26
+ * as an async generator for SSE/WebSocket subscriptions.
27
+ */
28
+ function createPublisher(backend) {
29
+ return {
30
+ publish: (channel, data) => backend.publish(channel, data),
31
+ async *subscribe(channel) {
32
+ const queue = [];
33
+ let resolve = null;
34
+ const unsubscribe = backend.subscribe(channel, (data) => {
35
+ queue.push(data);
36
+ if (resolve) {
37
+ resolve();
38
+ resolve = null;
39
+ }
40
+ });
41
+ try {
42
+ while (true) if (queue.length > 0) yield queue.shift();
43
+ else await new Promise((r) => {
44
+ resolve = r;
45
+ });
46
+ } finally {
47
+ unsubscribe();
48
+ }
49
+ }
50
+ };
51
+ }
52
+ //#endregion
53
+ export { MemoryPubSub, createPublisher };
@@ -0,0 +1,53 @@
1
+ import { GuardDef } from "../types.mjs";
2
+
3
+ //#region src/plugins/ratelimit.d.ts
4
+ interface RateLimitResult {
5
+ success: boolean;
6
+ limit: number;
7
+ remaining: number;
8
+ reset: number;
9
+ }
10
+ interface RateLimiter {
11
+ limit(key: string): Promise<RateLimitResult>;
12
+ }
13
+ interface MemoryRateLimiterOptions {
14
+ /** Maximum requests per window */
15
+ limit: number;
16
+ /** Window duration in milliseconds */
17
+ windowMs: number;
18
+ }
19
+ declare class MemoryRateLimiter implements RateLimiter {
20
+ #private;
21
+ constructor(options: MemoryRateLimiterOptions);
22
+ limit(key: string): Promise<RateLimitResult>;
23
+ /** Remove entries with no active timestamps to prevent unbounded Map growth */
24
+ evict(): void;
25
+ }
26
+ interface RateLimitGuardOptions {
27
+ /** The rate limiter instance */
28
+ limiter: RateLimiter;
29
+ /** Extract rate limit key from context */
30
+ keyFn: (ctx: Record<string, unknown>) => string | Promise<string>;
31
+ /** Custom error message */
32
+ message?: string;
33
+ }
34
+ /**
35
+ * Create a rate limiting guard.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * import { rateLimitGuard, MemoryRateLimiter } from "silgi/ratelimit"
40
+ *
41
+ * const rateLimit = rateLimitGuard({
42
+ * limiter: new MemoryRateLimiter({ limit: 100, windowMs: 60_000 }),
43
+ * keyFn: (ctx) => (ctx as any).ip ?? "anonymous",
44
+ * })
45
+ *
46
+ * const proc = k
47
+ * .$use(rateLimit)
48
+ * .$resolve(() => ({ ok: true }))
49
+ * ```
50
+ */
51
+ declare function rateLimitGuard(options: RateLimitGuardOptions): GuardDef<any, any>;
52
+ //#endregion
53
+ export { MemoryRateLimiter, MemoryRateLimiterOptions, RateLimitGuardOptions, RateLimitResult, RateLimiter, rateLimitGuard };