silgi 0.51.6 → 0.51.8

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 (53) hide show
  1. package/README.md +47 -0
  2. package/dist/adapters/_fetch-adapter.d.mts +6 -0
  3. package/dist/adapters/_fetch-adapter.mjs +14 -8
  4. package/dist/adapters/astro.mjs +1 -1
  5. package/dist/adapters/nextjs.mjs +1 -1
  6. package/dist/adapters/remix.mjs +1 -1
  7. package/dist/adapters/solidstart.mjs +1 -1
  8. package/dist/adapters/sveltekit.mjs +1 -1
  9. package/dist/client/client.d.mts +42 -4
  10. package/dist/client/client.mjs +42 -4
  11. package/dist/client/server.d.mts +27 -2
  12. package/dist/client/server.mjs +27 -2
  13. package/dist/compile.d.mts +10 -1
  14. package/dist/compile.mjs +13 -4
  15. package/dist/core/context-bridge.d.mts +49 -0
  16. package/dist/core/context-bridge.mjs +43 -7
  17. package/dist/core/context.d.mts +26 -0
  18. package/dist/core/ctx-symbols.mjs +21 -0
  19. package/dist/core/error.d.mts +183 -2
  20. package/dist/core/error.mjs +259 -16
  21. package/dist/core/handler.d.mts +15 -1
  22. package/dist/core/handler.mjs +33 -17
  23. package/dist/core/schema-converter.d.mts +131 -0
  24. package/dist/core/schema-converter.mjs +82 -0
  25. package/dist/core/serve.d.mts +2 -2
  26. package/dist/core/serve.mjs +9 -2
  27. package/dist/core/task.mjs +2 -2
  28. package/dist/index.d.mts +5 -2
  29. package/dist/index.mjs +4 -2
  30. package/dist/integrations/better-auth/index.d.mts +22 -1
  31. package/dist/integrations/better-auth/index.mjs +79 -11
  32. package/dist/integrations/drizzle/index.mjs +22 -5
  33. package/dist/integrations/zod/converter.d.mts +1 -1
  34. package/dist/integrations/zod/index.d.mts +29 -2
  35. package/dist/integrations/zod/index.mjs +60 -1
  36. package/dist/lazy.d.mts +40 -3
  37. package/dist/lazy.mjs +40 -3
  38. package/dist/map-input.mjs +1 -1
  39. package/dist/plugins/analytics/collector.d.mts +1 -1
  40. package/dist/plugins/analytics/trace.mjs +1 -1
  41. package/dist/plugins/analytics/types.d.mts +3 -3
  42. package/dist/plugins/analytics/utils.mjs +1 -4
  43. package/dist/plugins/analytics.d.mts +5 -3
  44. package/dist/plugins/analytics.mjs +16 -29
  45. package/dist/plugins/cache.mjs +1 -1
  46. package/dist/plugins/coerce.mjs +1 -1
  47. package/dist/scalar.d.mts +2 -1
  48. package/dist/scalar.mjs +9 -30
  49. package/dist/silgi.d.mts +165 -18
  50. package/dist/silgi.mjs +50 -12
  51. package/package.json +7 -3
  52. package/dist/core/trace-map.d.mts +0 -13
  53. package/dist/core/trace-map.mjs +0 -13
@@ -1,2 +1,29 @@
1
- import { CompositeSchemaConverter, ConvertOptions, JSONSchema, SchemaConverter, ZodSchemaConverter } from "./converter.mjs";
2
- export { CompositeSchemaConverter, type ConvertOptions, type JSONSchema, type SchemaConverter, ZodSchemaConverter };
1
+ import { SchemaConverter } from "../../core/schema-converter.mjs";
2
+ import { CompositeSchemaConverter, ConvertOptions, JSONSchema, ZodSchemaConverter } from "./converter.mjs";
3
+
4
+ //#region src/integrations/zod/index.d.ts
5
+ /**
6
+ * Pre-built Zod → JSON Schema converter for use with
7
+ * `silgi({ schemaConverters })`.
8
+ *
9
+ * @remarks
10
+ * Pass this to the `schemaConverters` option of `silgi()` to enable
11
+ * OpenAPI spec generation and analytics schema extraction for Zod
12
+ * schemas. Supports Zod v3 and v4.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { zodConverter } from 'silgi/zod'
17
+ * import { silgi } from 'silgi'
18
+ *
19
+ * const k = silgi({
20
+ * context: (req) => ({ db: getDB() }),
21
+ * schemaConverters: [zodConverter],
22
+ * })
23
+ * ```
24
+ *
25
+ * @category Schema
26
+ */
27
+ declare const zodConverter: SchemaConverter;
28
+ //#endregion
29
+ export { CompositeSchemaConverter, type ConvertOptions, type JSONSchema, type SchemaConverter, ZodSchemaConverter, zodConverter };
@@ -1,2 +1,61 @@
1
1
  import { CompositeSchemaConverter, ZodSchemaConverter } from "./converter.mjs";
2
- export { CompositeSchemaConverter, ZodSchemaConverter };
2
+ //#region src/integrations/zod/index.ts
3
+ /**
4
+ * Zod integration for Silgi — explicit injection model.
5
+ *
6
+ * @remarks
7
+ * **Breaking change:** the old side-effect import (`import 'silgi/zod'`)
8
+ * that auto-registered the Zod converter globally is removed. Import
9
+ * {@link zodConverter} and pass it explicitly to
10
+ * `silgi({ schemaConverters: [zodConverter] })`.
11
+ *
12
+ * Migration:
13
+ * ```ts
14
+ * // Before:
15
+ * import 'silgi/zod'
16
+ * const k = silgi({ context: ... })
17
+ *
18
+ * // After:
19
+ * import { zodConverter } from 'silgi/zod'
20
+ * const k = silgi({ context: ..., schemaConverters: [zodConverter] })
21
+ * ```
22
+ *
23
+ * Note: Zod v4 schemas expose a native `jsonSchema.input()` fast path, so
24
+ * most Zod v4 users do not strictly need this converter. Passing
25
+ * `zodConverter` is still recommended as a safety net for schemas that
26
+ * bypass the fast path.
27
+ *
28
+ * @category Schema
29
+ */
30
+ const _zodConverterInstance = new ZodSchemaConverter();
31
+ /**
32
+ * Pre-built Zod → JSON Schema converter for use with
33
+ * `silgi({ schemaConverters })`.
34
+ *
35
+ * @remarks
36
+ * Pass this to the `schemaConverters` option of `silgi()` to enable
37
+ * OpenAPI spec generation and analytics schema extraction for Zod
38
+ * schemas. Supports Zod v3 and v4.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { zodConverter } from 'silgi/zod'
43
+ * import { silgi } from 'silgi'
44
+ *
45
+ * const k = silgi({
46
+ * context: (req) => ({ db: getDB() }),
47
+ * schemaConverters: [zodConverter],
48
+ * })
49
+ * ```
50
+ *
51
+ * @category Schema
52
+ */
53
+ const zodConverter = {
54
+ vendor: "zod",
55
+ toJsonSchema(schema, opts) {
56
+ const [, json] = _zodConverterInstance.convert(schema, opts);
57
+ return json;
58
+ }
59
+ };
60
+ //#endregion
61
+ export { CompositeSchemaConverter, ZodSchemaConverter, zodConverter };
package/dist/lazy.d.mts CHANGED
@@ -9,14 +9,51 @@ interface LazyRouter {
9
9
  }
10
10
  /**
11
11
  * Wrap a dynamic import for lazy loading.
12
- * The module must export its router/procedure as `default`.
12
+ *
13
+ * @remarks
14
+ * The module must export its router or procedure as `default`. The
15
+ * returned handle is resolved on demand by {@link resolveLazy} and
16
+ * cached via module-local `WeakMap`s so concurrent resolutions share a
17
+ * single in-flight `Promise`.
18
+ *
19
+ * @param loader - A function returning `import('…')` — typically a
20
+ * dynamic import expression pointing at a module whose default export
21
+ * is a `RouterDef` or `ProcedureDef`.
22
+ * @returns A {@link LazyRouter} handle usable as a value inside
23
+ * `silgi.router({ … })`.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const appRouter = k.router({
28
+ * users: lazy(() => import('./routes/users.ts')),
29
+ * admin: lazy(() => import('./routes/admin.ts')),
30
+ * })
31
+ * ```
13
32
  */
14
33
  declare function lazy(loader: () => Promise<{
15
34
  default: RouterDef | ProcedureDef;
16
35
  }>): LazyRouter;
17
- /** Check if a value is a lazy router */
36
+ /**
37
+ * Type guard: `true` when `value` is a {@link LazyRouter} produced by
38
+ * {@link lazy}.
39
+ *
40
+ * @param value - Any value.
41
+ * @returns `true` when `value` carries the `__lazy` brand.
42
+ */
18
43
  declare function isLazy(value: unknown): value is LazyRouter;
19
- /** Resolve a lazy router (cached after first load, race-safe) */
44
+ /**
45
+ * Resolve a lazy router and cache the result.
46
+ *
47
+ * @remarks
48
+ * The resolved module is cached in a `WeakMap` keyed on the
49
+ * {@link LazyRouter} handle, so repeat calls return synchronously after
50
+ * the first. Concurrent calls share a single in-flight `Promise` to
51
+ * avoid duplicate imports — the race cache is cleared once the import
52
+ * settles.
53
+ *
54
+ * @param value - A handle returned by {@link lazy}.
55
+ * @returns The default export of the imported module.
56
+ */
20
57
  declare function resolveLazy(value: LazyRouter): Promise<RouterDef | ProcedureDef>;
21
58
  //#endregion
22
59
  export { LazyRouter, isLazy, lazy, resolveLazy };
package/dist/lazy.mjs CHANGED
@@ -3,7 +3,26 @@ const resolved = /* @__PURE__ */ new WeakMap();
3
3
  const loading = /* @__PURE__ */ new WeakMap();
4
4
  /**
5
5
  * Wrap a dynamic import for lazy loading.
6
- * The module must export its router/procedure as `default`.
6
+ *
7
+ * @remarks
8
+ * The module must export its router or procedure as `default`. The
9
+ * returned handle is resolved on demand by {@link resolveLazy} and
10
+ * cached via module-local `WeakMap`s so concurrent resolutions share a
11
+ * single in-flight `Promise`.
12
+ *
13
+ * @param loader - A function returning `import('…')` — typically a
14
+ * dynamic import expression pointing at a module whose default export
15
+ * is a `RouterDef` or `ProcedureDef`.
16
+ * @returns A {@link LazyRouter} handle usable as a value inside
17
+ * `silgi.router({ … })`.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const appRouter = k.router({
22
+ * users: lazy(() => import('./routes/users.ts')),
23
+ * admin: lazy(() => import('./routes/admin.ts')),
24
+ * })
25
+ * ```
7
26
  */
8
27
  function lazy(loader) {
9
28
  return {
@@ -11,11 +30,29 @@ function lazy(loader) {
11
30
  load: loader
12
31
  };
13
32
  }
14
- /** Check if a value is a lazy router */
33
+ /**
34
+ * Type guard: `true` when `value` is a {@link LazyRouter} produced by
35
+ * {@link lazy}.
36
+ *
37
+ * @param value - Any value.
38
+ * @returns `true` when `value` carries the `__lazy` brand.
39
+ */
15
40
  function isLazy(value) {
16
41
  return typeof value === "object" && value !== null && value.__lazy === true;
17
42
  }
18
- /** Resolve a lazy router (cached after first load, race-safe) */
43
+ /**
44
+ * Resolve a lazy router and cache the result.
45
+ *
46
+ * @remarks
47
+ * The resolved module is cached in a `WeakMap` keyed on the
48
+ * {@link LazyRouter} handle, so repeat calls return synchronously after
49
+ * the first. Concurrent calls share a single in-flight `Promise` to
50
+ * avoid duplicate imports — the race cache is cleared once the import
51
+ * settles.
52
+ *
53
+ * @param value - A handle returned by {@link lazy}.
54
+ * @returns The default export of the imported module.
55
+ */
19
56
  async function resolveLazy(value) {
20
57
  const cached = resolved.get(value);
21
58
  if (cached) return cached;
@@ -1,4 +1,4 @@
1
- import { RAW_INPUT } from "./compile.mjs";
1
+ import { RAW_INPUT } from "./core/ctx-symbols.mjs";
2
2
  //#region src/map-input.ts
3
3
  /**
4
4
  * mapInput — transform the input shape before a procedure runs.
@@ -15,7 +15,7 @@ declare class AnalyticsCollector {
15
15
  alertEngine: AlertEngine | null;
16
16
  /** Cost tracker */
17
17
  costTracker: CostTracker;
18
- constructor(options?: AnalyticsOptions);
18
+ constructor(options?: Omit<AnalyticsOptions, 'auth'>);
19
19
  /** Set procedure schemas extracted from router definition. */
20
20
  setProcedureSchemas(schemas: Map<string, {
21
21
  input?: Record<string, unknown>;
@@ -75,7 +75,7 @@ function guessKind(name) {
75
75
  * ```
76
76
  */
77
77
  async function trace(ctx, name, fn, opts) {
78
- const reqTrace = ctx.__analyticsTrace;
78
+ const reqTrace = ctx.trace;
79
79
  if (reqTrace) return reqTrace.trace(name, fn, opts);
80
80
  return fn();
81
81
  }
@@ -90,12 +90,12 @@ interface AnalyticsOptions {
90
90
  /** Time-series history in seconds (default: 120) */
91
91
  historySeconds?: number;
92
92
  /**
93
- * Protect dashboard access.
93
+ * Protect dashboard access. **Required** — the analytics dashboard exposes
94
+ * request bodies, headers, and stack traces, so authentication is mandatory.
94
95
  * - `string` — secret token checked against `Authorization: Bearer <token>` header or `?token=` query param
95
96
  * - `(req: Request) => boolean | Promise<boolean>` — custom auth function
96
- * - `undefined` — no auth (open access, NOT recommended in production)
97
97
  */
98
- auth?: string | ((req: Request) => boolean | Promise<boolean>);
98
+ auth: string | ((req: Request) => boolean | Promise<boolean>);
99
99
  /** Interval in ms between storage flushes (default: 5000) */
100
100
  flushInterval?: number;
101
101
  /** Days to retain entries in storage (default: 30). Entries older than this are pruned on flush. */
@@ -13,11 +13,8 @@ const SENSITIVE_HEADER_KEYS = new Set([
13
13
  "x-auth-token",
14
14
  "proxy-authorization"
15
15
  ]);
16
- function shouldRedactSensitiveData() {
17
- return process.env.NODE_ENV === "production";
18
- }
19
16
  function redactHeaderValue(key, value) {
20
- return shouldRedactSensitiveData() && SENSITIVE_HEADER_KEYS.has(key.toLowerCase()) ? REDACTED : value;
17
+ return SENSITIVE_HEADER_KEYS.has(key.toLowerCase()) ? REDACTED : value;
21
18
  }
22
19
  function round(n) {
23
20
  return Math.round(n * 100) / 100;
@@ -1,13 +1,15 @@
1
1
  import { RouterDef } from "../types.mjs";
2
2
  import { AnalyticsOptions, AnalyticsSnapshot, ErrorEntry, ProcedureCall, ProcedureSnapshot, RequestEntry, SpanKind, TaskExecution, TaskSnapshot, TraceSpan } from "./analytics/types.mjs";
3
+ import { SchemaRegistry } from "../core/schema-converter.mjs";
3
4
  import { AnalyticsCollector } from "./analytics/collector.mjs";
4
5
  import { RequestTrace, trace } from "./analytics/trace.mjs";
5
6
  import { analyticsAuthResponse, analyticsHTML, checkAnalyticsAuth, serveAnalyticsRoute } from "./analytics/routes.mjs";
6
7
  import { errorToMarkdown, requestToMarkdown } from "./analytics/export.mjs";
7
8
  import { sanitizeHeaders } from "./analytics/utils.mjs";
8
9
  import { RequestAccumulator } from "./analytics/accumulator.mjs";
9
- import { analyticsTraceMap } from "../core/trace-map.mjs";
10
+ import { SilgiHooks } from "../silgi.mjs";
10
11
  import { FetchHandler } from "../core/handler.mjs";
12
+ import { Hookable } from "hookable";
11
13
 
12
14
  //#region src/plugins/analytics.d.ts
13
15
  interface ProcedureSchemaInfo {
@@ -18,6 +20,6 @@ interface ProcedureSchemaInfo {
18
20
  * Wrap a fetch handler with analytics collection.
19
21
  * Intercepts analytics dashboard routes and instruments every request.
20
22
  */
21
- declare function wrapWithAnalytics(handler: FetchHandler, router: RouterDef | undefined, options?: AnalyticsOptions): FetchHandler;
23
+ declare function wrapWithAnalytics(handler: FetchHandler, router: RouterDef | undefined, options: AnalyticsOptions, registry?: SchemaRegistry, hooks?: Hookable<SilgiHooks>): FetchHandler;
22
24
  //#endregion
23
- export { AnalyticsCollector, type AnalyticsOptions, type AnalyticsSnapshot, type ErrorEntry, type ProcedureCall, ProcedureSchemaInfo, type ProcedureSnapshot, RequestAccumulator, type RequestEntry, RequestTrace, type SpanKind, type TaskExecution, type TaskSnapshot, type TraceSpan, analyticsAuthResponse, analyticsHTML, analyticsTraceMap, checkAnalyticsAuth, errorToMarkdown, requestToMarkdown, sanitizeHeaders, serveAnalyticsRoute, trace, wrapWithAnalytics };
25
+ export { AnalyticsCollector, type AnalyticsOptions, type AnalyticsSnapshot, type ErrorEntry, type ProcedureCall, ProcedureSchemaInfo, type ProcedureSnapshot, RequestAccumulator, type RequestEntry, RequestTrace, type SpanKind, type TaskExecution, type TaskSnapshot, type TraceSpan, analyticsAuthResponse, analyticsHTML, checkAnalyticsAuth, errorToMarkdown, requestToMarkdown, sanitizeHeaders, serveAnalyticsRoute, trace, wrapWithAnalytics };
@@ -1,8 +1,7 @@
1
1
  import { ValidationError } from "../core/schema.mjs";
2
2
  import { SilgiError, toSilgiError } from "../core/error.mjs";
3
- import { analyticsTraceMap } from "../core/trace-map.mjs";
4
3
  import { parseUrlPathname } from "../core/url.mjs";
5
- import { ZodSchemaConverter } from "../integrations/zod/converter.mjs";
4
+ import { schemaToJsonSchema } from "../core/schema-converter.mjs";
6
5
  import { generateRequestId } from "./analytics/request-id.mjs";
7
6
  import { isTrackedRequestPath, normalizeAnalyticsPath, round, sanitizeHeaders } from "./analytics/utils.mjs";
8
7
  import { RequestAccumulator } from "./analytics/accumulator.mjs";
@@ -84,29 +83,18 @@ function extractResponseError(output, status, fallback) {
84
83
  function isProcedureDef(value) {
85
84
  return typeof value === "object" && value !== null && "type" in value && "resolve" in value && typeof value.resolve === "function";
86
85
  }
87
- const _zodConverter = new ZodSchemaConverter();
88
- function schemaToJson(schema, strategy) {
86
+ function schemaToJson(schema, strategy, registry) {
89
87
  if (!schema) return void 0;
90
- const std = schema["~standard"];
91
- if (std?.jsonSchema?.input) try {
92
- const result = std.jsonSchema.input({ target: "draft-2020-12" });
93
- if (result && typeof result === "object") {
94
- const { $schema: _, ...rest } = result;
95
- return rest;
96
- }
97
- } catch {}
98
- if (_zodConverter.condition(schema)) try {
99
- const [, json] = _zodConverter.convert(schema, { strategy });
100
- return json;
101
- } catch {}
88
+ const json = schemaToJsonSchema(schema, strategy, registry);
89
+ return Object.keys(json).length > 0 ? json : void 0;
102
90
  }
103
- function extractProcedureSchemas(router) {
91
+ function extractProcedureSchemas(router, registry) {
104
92
  const schemas = /* @__PURE__ */ new Map();
105
93
  function walk(node, path) {
106
94
  if (isProcedureDef(node)) {
107
95
  const info = {};
108
- if (node.input) info.input = schemaToJson(node.input, "input");
109
- if (node.output) info.output = schemaToJson(node.output, "output");
96
+ if (node.input) info.input = schemaToJson(node.input, "input", registry);
97
+ if (node.output) info.output = schemaToJson(node.output, "output", registry);
110
98
  if (info.input || info.output) schemas.set(path.join("/"), info);
111
99
  return;
112
100
  }
@@ -119,12 +107,13 @@ function extractProcedureSchemas(router) {
119
107
  * Wrap a fetch handler with analytics collection.
120
108
  * Intercepts analytics dashboard routes and instruments every request.
121
109
  */
122
- function wrapWithAnalytics(handler, router, options = {}) {
110
+ function wrapWithAnalytics(handler, router, options, registry, hooks) {
123
111
  const collector = new AnalyticsCollector(options);
124
- const procedureSchemas = router ? extractProcedureSchemas(router) : void 0;
112
+ const procedureSchemas = router ? extractProcedureSchemas(router, registry) : void 0;
125
113
  if (procedureSchemas) collector.setProcedureSchemas(procedureSchemas);
126
114
  const dashboardHtml = analyticsHTML();
127
115
  const auth = options.auth;
116
+ if (!auth) throw new Error("[silgi analytics] `options.auth` is required. The analytics dashboard exposes request bodies, headers, and stack traces. Provide an auth token via `analytics: { auth: \"your-secret-token\" }` to protect it.");
128
117
  import("../core/task.mjs").then(({ setTaskAnalytics }) => {
129
118
  setTaskAnalytics((entry) => collector.recordTask({
130
119
  ...entry,
@@ -136,10 +125,8 @@ function wrapWithAnalytics(handler, router, options = {}) {
136
125
  const analyticsSub = normalizeAnalyticsPath(pathname);
137
126
  if (analyticsSub !== null) {
138
127
  const canonical = analyticsSub === "" ? "api/analytics" : `api/analytics/${analyticsSub}`;
139
- if (auth) {
140
- const authResult = checkAnalyticsAuth(request, auth);
141
- if (!(authResult instanceof Promise ? await authResult : authResult)) return analyticsAuthResponse(canonical);
142
- }
128
+ const authResult = checkAnalyticsAuth(request, auth);
129
+ if (!(authResult instanceof Promise ? await authResult : authResult)) return analyticsAuthResponse(canonical);
143
130
  return serveAnalyticsRoute(canonical, request, collector, dashboardHtml);
144
131
  }
145
132
  if (!isTrackedRequestPath(pathname) || collector.isIgnored(pathname)) return handler(request);
@@ -149,7 +136,9 @@ function wrapWithAnalytics(handler, router, options = {}) {
149
136
  const acc = new RequestAccumulator(request, collector, traceId, parentRequestId ?? void 0);
150
137
  const reqTrace = new RequestTrace();
151
138
  const t0 = performance.now();
152
- analyticsTraceMap.set(request, reqTrace);
139
+ if (hooks) hooks.hookOnce("request:prepare", (event) => {
140
+ if (event.request === request) event.ctx.trace = reqTrace;
141
+ });
153
142
  let response;
154
143
  try {
155
144
  response = await handler(request);
@@ -206,8 +195,6 @@ function wrapWithAnalytics(handler, router, options = {}) {
206
195
  spans: reqTrace.spans ?? []
207
196
  });
208
197
  throw error;
209
- } finally {
210
- analyticsTraceMap.delete(request);
211
198
  }
212
199
  const headers = new Headers(response.headers);
213
200
  headers.set("x-request-id", acc.requestId);
@@ -224,4 +211,4 @@ function wrapWithAnalytics(handler, router, options = {}) {
224
211
  };
225
212
  }
226
213
  //#endregion
227
- export { AnalyticsCollector, RequestAccumulator, RequestTrace, analyticsAuthResponse, analyticsHTML, analyticsTraceMap, checkAnalyticsAuth, errorToMarkdown, requestToMarkdown, sanitizeHeaders, serveAnalyticsRoute, trace, wrapWithAnalytics };
214
+ export { AnalyticsCollector, RequestAccumulator, RequestTrace, analyticsAuthResponse, analyticsHTML, checkAnalyticsAuth, errorToMarkdown, requestToMarkdown, sanitizeHeaders, serveAnalyticsRoute, trace, wrapWithAnalytics };
@@ -1,4 +1,4 @@
1
- import { RAW_INPUT } from "../compile.mjs";
1
+ import { RAW_INPUT } from "../core/ctx-symbols.mjs";
2
2
  import { useStorage as useStorage$1 } from "../core/storage.mjs";
3
3
  import { defineCachedFunction, setStorage, useStorage } from "ocache";
4
4
  import { hash } from "ohash";
@@ -1,4 +1,4 @@
1
- import { RAW_INPUT } from "../compile.mjs";
1
+ import { RAW_INPUT } from "../core/ctx-symbols.mjs";
2
2
  //#region src/plugins/coerce.ts
3
3
  /**
4
4
  * Smart coercion — convert string query parameters to proper types.
package/dist/scalar.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RouterDef } from "./types.mjs";
2
+ import { SchemaRegistry } from "./core/schema-converter.mjs";
2
3
  //#region src/scalar.d.ts
3
4
  interface ScalarOptions {
4
5
  title?: string;
@@ -43,7 +44,7 @@ interface ScalarOptions {
43
44
  */
44
45
  cdn?: 'cdn' | 'unpkg' | 'local' | (string & {});
45
46
  }
46
- declare function generateOpenAPI(router: RouterDef, options?: ScalarOptions, basePath?: string): Record<string, unknown>;
47
+ declare function generateOpenAPI(router: RouterDef, options?: ScalarOptions, basePath?: string, registry?: SchemaRegistry): Record<string, unknown>;
47
48
  declare function scalarHTML(specUrl: string, options?: ScalarOptions): string;
48
49
  //#endregion
49
50
  export { ScalarOptions, generateOpenAPI, scalarHTML };
package/dist/scalar.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ZodSchemaConverter } from "./integrations/zod/converter.mjs";
1
+ import { schemaToJsonSchema } from "./core/schema-converter.mjs";
2
2
  //#region src/scalar.ts
3
3
  /**
4
4
  * Scalar API Reference — v2 OpenAPI integration.
@@ -31,7 +31,8 @@ function toOpenAPIPath(raw) {
31
31
  pathParams
32
32
  };
33
33
  }
34
- function generateOpenAPI(router, options = {}, basePath = "") {
34
+ function generateOpenAPI(router, options = {}, basePath = "", registry) {
35
+ const schemaToJsonSchema$1 = (schema, strategy = "input") => schemaToJsonSchema(schema, strategy, registry);
35
36
  const paths = {};
36
37
  const tags = /* @__PURE__ */ new Map();
37
38
  collectProcedures(router, [], (path, proc) => {
@@ -74,7 +75,7 @@ function generateOpenAPI(router, options = {}, basePath = "") {
74
75
  if (route?.security === false) operation.security = [];
75
76
  else if (route?.security) operation.security = route.security.map((s) => ({ [s]: [] }));
76
77
  else if (options.security) operation.security = [{ auth: [] }];
77
- const inputSchema = proc.input ? schemaToJsonSchema(proc.input, "input") : null;
78
+ const inputSchema = proc.input ? schemaToJsonSchema$1(proc.input, "input") : null;
78
79
  const successStatus = route?.successStatus ?? 200;
79
80
  const successDesc = route?.successDescription ?? "Successful response";
80
81
  const guards = (proc.use ?? []).filter((m) => m.kind === "guard" && m.errors);
@@ -107,7 +108,7 @@ function generateOpenAPI(router, options = {}, basePath = "") {
107
108
  };
108
109
  if (params.length > 0) op.parameters = params;
109
110
  if (proc.type === "subscription") {
110
- const outputSchema = proc.output ? schemaToJsonSchema(proc.output, "output") : { type: "string" };
111
+ const outputSchema = proc.output ? schemaToJsonSchema$1(proc.output, "output") : { type: "string" };
111
112
  op.responses[String(successStatus)] = {
112
113
  description: "SSE event stream",
113
114
  content: { "text/event-stream": { schema: {
@@ -117,7 +118,7 @@ function generateOpenAPI(router, options = {}, basePath = "") {
117
118
  };
118
119
  } else if (proc.output) op.responses[String(successStatus)] = {
119
120
  description: successDesc,
120
- content: { "application/json": { schema: schemaToJsonSchema(proc.output, "output") } }
121
+ content: { "application/json": { schema: schemaToJsonSchema$1(proc.output, "output") } }
121
122
  };
122
123
  else op.responses[String(successStatus)] = { description: successDesc };
123
124
  if (proc.input) op.responses["400"] = {
@@ -154,7 +155,7 @@ function generateOpenAPI(router, options = {}, basePath = "") {
154
155
  const entry = { code };
155
156
  if (typeof def === "object") {
156
157
  if (def.message) entry.message = def.message;
157
- if (def.data) entry.schema = schemaToJsonSchema(def.data);
158
+ if (def.data) entry.schema = schemaToJsonSchema$1(def.data);
158
159
  }
159
160
  byStatus.get(status).push(entry);
160
161
  }
@@ -269,28 +270,6 @@ function collectProcedures(node, path, cb) {
269
270
  }
270
271
  if (typeof node === "object" && node !== null) for (const [key, child] of Object.entries(node)) collectProcedures(child, [...path, key], cb);
271
272
  }
272
- /**
273
- * Convert a Standard Schema to JSON Schema.
274
- *
275
- * Fast path: `~standard.jsonSchema.input()` (StandardJSONSchemaV1 implementors).
276
- * Fallback: vendor-specific converters (Zod v4 via ZodSchemaConverter).
277
- */
278
- const _zodConverter = new ZodSchemaConverter();
279
- function schemaToJsonSchema(schema, strategy = "input") {
280
- const std = schema["~standard"];
281
- if (std?.jsonSchema?.input) try {
282
- const result = std.jsonSchema.input({ target: "draft-2020-12" });
283
- if (result && typeof result === "object") {
284
- const { $schema: _, ...rest } = result;
285
- return rest;
286
- }
287
- } catch {}
288
- if (_zodConverter.condition(schema)) try {
289
- const [, jsonSchema] = _zodConverter.convert(schema, { strategy });
290
- return jsonSchema;
291
- } catch {}
292
- return {};
293
- }
294
273
  function objectSchemaToParams(schema) {
295
274
  if (schema.type !== "object" || !schema.properties) return [];
296
275
  const required = new Set(schema.required ?? []);
@@ -306,9 +285,9 @@ function objectSchemaToParams(schema) {
306
285
  * Wrap a fetch handler to serve Scalar API Reference at /api/reference and /api/openapi.json.
307
286
  * Scalar routes are intercepted before the handler — zero overhead for normal requests.
308
287
  */
309
- function wrapWithScalar(handler, routerDef, options = {}, prefix = "/api") {
288
+ function wrapWithScalar(handler, routerDef, options = {}, prefix = "/api", registry) {
310
289
  const normPrefix = (prefix.startsWith("/") ? prefix : "/" + prefix).replace(/\/+$/, "");
311
- const specJson = JSON.stringify(generateOpenAPI(routerDef, options, normPrefix));
290
+ const specJson = JSON.stringify(generateOpenAPI(routerDef, options, normPrefix, registry));
312
291
  const specHtml = scalarHTML(`${normPrefix}/openapi.json`, options);
313
292
  const openapiMatch = `${normPrefix.slice(1)}/openapi.json`;
314
293
  const referenceMatch = `${normPrefix.slice(1)}/reference`;