silgi 0.0.13 → 0.1.0-beta.1

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 +52 -0
  72. package/dist/compile.mjs +304 -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 +315 -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,67 @@
1
+ //#region src/plugins/cookies.ts
2
+ /**
3
+ * Cookie helpers — parse, set, and delete cookies.
4
+ *
5
+ * Lightweight utilities for working with cookies in Silgi handlers.
6
+ * No dependencies. Works with both serve() and handler().
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { getCookie, setCookie, deleteCookie } from "silgi/cookies"
11
+ *
12
+ * const auth = k.guard((ctx) => {
13
+ * const token = getCookie(ctx.headers, "session")
14
+ * if (!token) throw new SilgiError("UNAUTHORIZED")
15
+ * return { sessionToken: token }
16
+ * })
17
+ * ```
18
+ */
19
+ /** Parse a specific cookie from a headers object or cookie string. */
20
+ function getCookie(headers, name) {
21
+ const cookieStr = typeof headers === "string" ? headers : headers.cookie ?? headers.Cookie ?? "";
22
+ if (!cookieStr) return void 0;
23
+ const prefix = `${name}=`;
24
+ const cookies = cookieStr.split(";");
25
+ for (let i = 0; i < cookies.length; i++) {
26
+ const c = cookies[i].trim();
27
+ if (c.startsWith(prefix)) return decodeURIComponent(c.slice(prefix.length));
28
+ }
29
+ }
30
+ /** Parse all cookies from a headers object or cookie string. */
31
+ function parseCookies(headers) {
32
+ const cookieStr = typeof headers === "string" ? headers : headers.cookie ?? headers.Cookie ?? "";
33
+ if (!cookieStr) return {};
34
+ const result = {};
35
+ const cookies = cookieStr.split(";");
36
+ for (let i = 0; i < cookies.length; i++) {
37
+ const c = cookies[i].trim();
38
+ const eq = c.indexOf("=");
39
+ if (eq === -1) continue;
40
+ const key = c.slice(0, eq);
41
+ const value = c.slice(eq + 1);
42
+ result[key] = decodeURIComponent(value);
43
+ }
44
+ return result;
45
+ }
46
+ /** Serialize a Set-Cookie header value. */
47
+ function setCookie(name, value, options = {}) {
48
+ const { path = "/", httpOnly = true, secure, sameSite = "lax", maxAge, expires, domain } = options;
49
+ let cookie = `${name}=${encodeURIComponent(value)}`;
50
+ if (path) cookie += `; Path=${path}`;
51
+ if (domain) cookie += `; Domain=${domain}`;
52
+ if (maxAge !== void 0) cookie += `; Max-Age=${maxAge}`;
53
+ if (expires) cookie += `; Expires=${expires.toUTCString()}`;
54
+ if (httpOnly) cookie += "; HttpOnly";
55
+ if (secure ?? (sameSite === "none" || typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV === "production")) cookie += "; Secure";
56
+ cookie += `; SameSite=${sameSite.charAt(0).toUpperCase() + sameSite.slice(1)}`;
57
+ return cookie;
58
+ }
59
+ /** Create a Set-Cookie header that deletes a cookie. */
60
+ function deleteCookie(name, options = {}) {
61
+ return setCookie(name, "", {
62
+ ...options,
63
+ maxAge: 0
64
+ });
65
+ }
66
+ //#endregion
67
+ export { deleteCookie, getCookie, parseCookies, setCookie };
@@ -0,0 +1,39 @@
1
+ //#region src/plugins/cors.d.ts
2
+ /**
3
+ * CORS plugin — v2 hook-based.
4
+ *
5
+ * Adds CORS headers to every response via lifecycle hooks.
6
+ * Handles preflight OPTIONS requests automatically.
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 hooks 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
+ declare function corsHeaders(options?: CORSOptions, requestOrigin?: string): Record<string, string>;
38
+ //#endregion
39
+ export { CORSOptions, cors, corsHeaders };
@@ -0,0 +1,56 @@
1
+ //#region src/plugins/cors.ts
2
+ /**
3
+ * Create CORS hooks 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
+ function corsHeaders(options = {}, requestOrigin) {
28
+ const origin = options.origin ?? "*";
29
+ const methods = options.methods ?? [
30
+ "GET",
31
+ "POST",
32
+ "PUT",
33
+ "PATCH",
34
+ "DELETE",
35
+ "OPTIONS"
36
+ ];
37
+ const allowedHeaders = options.allowedHeaders ?? ["Content-Type", "Authorization"];
38
+ const headers = {
39
+ "access-control-allow-methods": methods.join(", "),
40
+ "access-control-allow-headers": allowedHeaders.join(", ")
41
+ };
42
+ if (typeof origin === "string") headers["access-control-allow-origin"] = origin;
43
+ else if (Array.isArray(origin)) {
44
+ headers["access-control-allow-origin"] = requestOrigin && origin.includes(requestOrigin) ? requestOrigin : "";
45
+ headers["vary"] = "Origin";
46
+ } else if (typeof origin === "function" && requestOrigin) {
47
+ headers["access-control-allow-origin"] = origin(requestOrigin) ? requestOrigin : "";
48
+ headers["vary"] = "Origin";
49
+ }
50
+ if (options.credentials) headers["access-control-allow-credentials"] = "true";
51
+ if (options.maxAge !== void 0) headers["access-control-max-age"] = String(options.maxAge);
52
+ if (options.exposedHeaders) headers["access-control-expose-headers"] = options.exposedHeaders.join(", ");
53
+ return headers;
54
+ }
55
+ //#endregion
56
+ export { cors, corsHeaders };
@@ -0,0 +1,57 @@
1
+ //#region src/plugins/custom-serializer.d.ts
2
+ /**
3
+ * Custom JSON serializers — extend serialization for custom types.
4
+ *
5
+ * Register custom type handlers that run during JSON stringify/parse.
6
+ * Works with both serve() responses and client deserialization.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { createSerializer } from "silgi/plugins"
11
+ *
12
+ * const serializer = createSerializer()
13
+ * .register("Date", {
14
+ * test: (v) => v instanceof Date,
15
+ * serialize: (v) => v.toISOString(),
16
+ * deserialize: (v) => new Date(v),
17
+ * })
18
+ * .register("BigInt", {
19
+ * test: (v) => typeof v === "bigint",
20
+ * serialize: (v) => v.toString(),
21
+ * deserialize: (v) => BigInt(v),
22
+ * })
23
+ *
24
+ * // Use as JSON replacer/reviver
25
+ * JSON.stringify(data, serializer.replacer)
26
+ * JSON.parse(text, serializer.reviver)
27
+ * ```
28
+ */
29
+ interface TypeHandler<T = unknown> {
30
+ /** Test if a value is this type */
31
+ test: (value: unknown) => boolean;
32
+ /** Convert to JSON-safe value */
33
+ serialize: (value: T) => unknown;
34
+ /** Convert back from JSON-safe value */
35
+ deserialize: (value: unknown) => T;
36
+ }
37
+ interface Serializer {
38
+ /** Register a custom type handler. Returns self for chaining. */
39
+ register<T>(tag: string, handler: TypeHandler<T>): Serializer;
40
+ /** JSON.stringify replacer function */
41
+ replacer: (key: string, value: unknown) => unknown;
42
+ /** JSON.parse reviver function */
43
+ reviver: (key: string, value: unknown) => unknown;
44
+ /** Stringify with custom types */
45
+ stringify: (value: unknown) => string;
46
+ /** Parse with custom types */
47
+ parse: (text: string) => unknown;
48
+ }
49
+ /**
50
+ * Create a custom serializer with support for non-JSON types.
51
+ *
52
+ * Values are wrapped as `{ __$type: "Tag", __$value: serialized }`
53
+ * during stringify and unwrapped during parse.
54
+ */
55
+ declare function createSerializer(): Serializer;
56
+ //#endregion
57
+ export { Serializer, TypeHandler, createSerializer };
@@ -0,0 +1,40 @@
1
+ //#region src/plugins/custom-serializer.ts
2
+ const TYPE_TAG = "__$type";
3
+ const VALUE_TAG = "__$value";
4
+ /**
5
+ * Create a custom serializer with support for non-JSON types.
6
+ *
7
+ * Values are wrapped as `{ __$type: "Tag", __$value: serialized }`
8
+ * during stringify and unwrapped during parse.
9
+ */
10
+ function createSerializer() {
11
+ const handlers = /* @__PURE__ */ new Map();
12
+ const replacer = (_key, value) => {
13
+ for (const [tag, handler] of handlers) if (handler.test(value)) return {
14
+ [TYPE_TAG]: tag,
15
+ [VALUE_TAG]: handler.serialize(value)
16
+ };
17
+ return value;
18
+ };
19
+ const reviver = (_key, value) => {
20
+ if (typeof value === "object" && value !== null && TYPE_TAG in value && VALUE_TAG in value) {
21
+ const tag = value[TYPE_TAG];
22
+ const handler = handlers.get(tag);
23
+ if (handler) return handler.deserialize(value[VALUE_TAG]);
24
+ }
25
+ return value;
26
+ };
27
+ const serializer = {
28
+ register(tag, handler) {
29
+ handlers.set(tag, handler);
30
+ return serializer;
31
+ },
32
+ replacer,
33
+ reviver,
34
+ stringify: (value) => JSON.stringify(value, replacer),
35
+ parse: (text) => JSON.parse(text, reviver)
36
+ };
37
+ return serializer;
38
+ }
39
+ //#endregion
40
+ export { createSerializer };
@@ -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,100 @@
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 (value instanceof File) files.push({
75
+ name: value.name,
76
+ size: value.size,
77
+ type: value.type,
78
+ arrayBuffer: () => value.arrayBuffer(),
79
+ text: () => value.text(),
80
+ stream: () => value.stream()
81
+ });
82
+ else fields[key] = value;
83
+ return {
84
+ files,
85
+ fields
86
+ };
87
+ }
88
+ function matchesMimeType(actual, patterns) {
89
+ for (const pattern of patterns) {
90
+ if (pattern === "*" || pattern === "*/*") return true;
91
+ if (pattern === actual) return true;
92
+ if (pattern.endsWith("/*")) {
93
+ const prefix = pattern.slice(0, -2);
94
+ if (actual.startsWith(prefix + "/")) return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ //#endregion
100
+ export { fileGuard, parseMultipart };
@@ -0,0 +1,16 @@
1
+ import { AnalyticsCollector, AnalyticsOptions, AnalyticsSnapshot, ErrorEntry, ProcedureSnapshot, RequestTrace, TraceSpan, analyticsHTML, errorToMarkdown, trace } from "./analytics.mjs";
2
+ import { CookieOptions, deleteCookie, getCookie, parseCookies, setCookie } from "./cookies.mjs";
3
+ import { CORSOptions, cors, corsHeaders } from "./cors.mjs";
4
+ import { Span, Tracer, otelWrap } from "./otel.mjs";
5
+ import { Logger, LoggingOptions, loggingHooks } from "./pino.mjs";
6
+ import { MemoryRateLimiter, MemoryRateLimiterOptions, RateLimitGuardOptions, RateLimitResult, RateLimiter, rateLimitGuard } from "./ratelimit.mjs";
7
+ import { CompressionOptions, compressionWrap } from "./compression.mjs";
8
+ import { BodyLimitOptions, bodyLimitGuard } from "./body-limit.mjs";
9
+ import { strictGetGuard } from "./strict-get.mjs";
10
+ import { decrypt, encrypt, sign, unsign } from "./signing.mjs";
11
+ import { coerceGuard, coerceObject, coerceValue } from "./coerce.mjs";
12
+ import { BatchHandlerOptions, createBatchHandler } from "./batch-server.mjs";
13
+ import { MemoryPubSub, PubSubBackend, Publisher, createPublisher } from "./pubsub.mjs";
14
+ import { Serializer, TypeHandler, createSerializer } from "./custom-serializer.mjs";
15
+ import { FileGuardOptions, UploadedFile, fileGuard, parseMultipart } from "./file-upload.mjs";
16
+ export { AnalyticsCollector, type AnalyticsOptions, type AnalyticsSnapshot, type BatchHandlerOptions, type BodyLimitOptions, type CORSOptions, type CompressionOptions, 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 Serializer, type Span, type TraceSpan, type Tracer, type TypeHandler, type UploadedFile, analyticsHTML, bodyLimitGuard, coerceGuard, coerceObject, coerceValue, compressionWrap, cors, corsHeaders, createBatchHandler, createPublisher, createSerializer, decrypt, deleteCookie, encrypt, errorToMarkdown, fileGuard, getCookie, loggingHooks, otelWrap, parseCookies, parseMultipart, rateLimitGuard, setCookie, sign, strictGetGuard, trace, unsign };
@@ -0,0 +1,16 @@
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 { compressionWrap } from "./compression.mjs";
7
+ import { bodyLimitGuard } from "./body-limit.mjs";
8
+ import { strictGetGuard } from "./strict-get.mjs";
9
+ import { deleteCookie, getCookie, parseCookies, setCookie } from "./cookies.mjs";
10
+ import { decrypt, encrypt, sign, unsign } from "./signing.mjs";
11
+ import { coerceGuard, coerceObject, coerceValue } from "./coerce.mjs";
12
+ import { createBatchHandler } from "./batch-server.mjs";
13
+ import { MemoryPubSub, createPublisher } from "./pubsub.mjs";
14
+ import { createSerializer } from "./custom-serializer.mjs";
15
+ import { fileGuard, parseMultipart } from "./file-upload.mjs";
16
+ export { AnalyticsCollector, MemoryPubSub, MemoryRateLimiter, RequestTrace, analyticsHTML, bodyLimitGuard, coerceGuard, coerceObject, coerceValue, compressionWrap, cors, corsHeaders, createBatchHandler, createPublisher, createSerializer, 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 };