silgi 0.53.0 → 0.53.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder.mjs +32 -6
- package/dist/caller.mjs +65 -55
- package/dist/compile.d.mts +15 -8
- package/dist/compile.mjs +157 -142
- package/dist/core/handler.d.mts +3 -3
- package/dist/core/handler.mjs +69 -73
- package/dist/core/input.mjs +95 -33
- package/dist/core/schema-converter.d.mts +68 -63
- package/dist/core/schema-converter.mjs +85 -56
- package/dist/core/serve.d.mts +18 -17
- package/dist/core/serve.mjs +154 -64
- package/dist/core/sse.d.mts +5 -6
- package/dist/core/sse.mjs +86 -46
- package/dist/core/task.d.mts +15 -4
- package/dist/core/task.mjs +160 -76
- package/dist/plugins/cache.d.mts +62 -126
- package/dist/plugins/cache.mjs +146 -128
- package/dist/scalar.d.mts +24 -13
- package/dist/scalar.mjs +292 -201
- package/dist/silgi.mjs +160 -117
- package/dist/ws.d.mts +26 -27
- package/dist/ws.mjs +126 -87
- package/package.json +1 -1
package/dist/core/input.mjs
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { SilgiError } from "./error.mjs";
|
|
2
2
|
//#region src/core/input.ts
|
|
3
3
|
/**
|
|
4
|
-
* Request input parsing
|
|
4
|
+
* Request input parsing
|
|
5
|
+
* -----------------------
|
|
6
|
+
*
|
|
7
|
+
* Pulls the RPC input payload out of an incoming `Request` and returns
|
|
8
|
+
* it in its decoded form. The shape of the input depends on how the
|
|
9
|
+
* client chose to send it:
|
|
10
|
+
*
|
|
11
|
+
* - `GET` / no-body — JSON-encoded, URL-escaped, on `?data=` query.
|
|
12
|
+
* - `content-type: application/msgpack` — binary, via the msgpack codec.
|
|
13
|
+
* - `content-type: application/x-devalue` — text, via devalue.
|
|
14
|
+
* - anything else (typically JSON) — JSON body.
|
|
15
|
+
*
|
|
16
|
+
* Empty bodies always resolve to `undefined` so that a procedure with
|
|
17
|
+
* no input, or one whose schema allows `undefined`, works without the
|
|
18
|
+
* client having to send a payload at all. Malformed non-empty bodies
|
|
19
|
+
* throw `BAD_REQUEST` with a message the client can show to a user.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The msgpack and devalue codecs are pulled in on first use so that a
|
|
23
|
+
* handler that only ever sees JSON never pays the cost of loading
|
|
24
|
+
* them. `Promise`-cached at module scope: once the first request
|
|
25
|
+
* triggers the import, subsequent calls share the resolved module.
|
|
26
|
+
*
|
|
27
|
+
* Module-global is intentional and safe here — the cached value is a
|
|
28
|
+
* reference to an immutable ES module, not user data. Two silgi
|
|
29
|
+
* instances in the same process legitimately share it.
|
|
5
30
|
*/
|
|
6
|
-
let
|
|
7
|
-
let
|
|
8
|
-
/** Max
|
|
31
|
+
let msgpackModule;
|
|
32
|
+
let devalueModule;
|
|
33
|
+
/** Max bytes permitted in the `?data=` query param. Shields against JSON-bomb payloads in a URL. */
|
|
9
34
|
const MAX_QUERY_DATA_LENGTH = 8192;
|
|
10
35
|
/**
|
|
11
36
|
* Find the value of the `data=` query parameter by key, not by substring.
|
|
12
37
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* wrong value. This scans for `data=` at a parameter
|
|
16
|
-
*
|
|
38
|
+
* A naive `searchStr.indexOf('data=')` matches `userdata=`, `mydata=`,
|
|
39
|
+
* or any other key that merely ends in `data`, and silently returns
|
|
40
|
+
* the wrong value. This scans for `data=` only at a parameter
|
|
41
|
+
* boundary — i.e. at the start of the search string, or right after
|
|
42
|
+
* an `&`.
|
|
17
43
|
*/
|
|
18
44
|
function findDataParam(searchStr) {
|
|
19
45
|
let i = 0;
|
|
@@ -29,31 +55,47 @@ function findDataParam(searchStr) {
|
|
|
29
55
|
}
|
|
30
56
|
return null;
|
|
31
57
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Decode the `?data=` query param as JSON. Returns `undefined` when
|
|
60
|
+
* the query is missing or has no `data=` field. Throws `BAD_REQUEST`
|
|
61
|
+
* when the payload is oversized or unparsable.
|
|
62
|
+
*/
|
|
63
|
+
function decodeQueryInput(url, qMark) {
|
|
64
|
+
if (qMark === -1) return void 0;
|
|
65
|
+
const encoded = findDataParam(url.slice(qMark + 1));
|
|
66
|
+
if (encoded === null) return void 0;
|
|
67
|
+
if (encoded.length > MAX_QUERY_DATA_LENGTH) throw new SilgiError("BAD_REQUEST", { message: "Query data parameter too large" });
|
|
68
|
+
return JSON.parse(decodeURIComponent(encoded));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Decode a MessagePack-encoded request body. Empty bodies resolve to
|
|
72
|
+
* `undefined` (procedures with no input work without a payload).
|
|
73
|
+
*/
|
|
74
|
+
async function decodeMsgpackBody(request) {
|
|
75
|
+
msgpackModule ??= await import("../codec/msgpack.mjs");
|
|
76
|
+
const buf = new Uint8Array(await request.arrayBuffer());
|
|
77
|
+
return buf.length > 0 ? msgpackModule.decode(buf) : void 0;
|
|
78
|
+
}
|
|
79
|
+
/** Decode a devalue-encoded request body. Empty bodies resolve to `undefined`. */
|
|
80
|
+
async function decodeDevalueBody(request) {
|
|
81
|
+
devalueModule ??= await import("../codec/devalue.mjs");
|
|
82
|
+
const text = await request.text();
|
|
83
|
+
return text ? devalueModule.decode(text) : void 0;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Decode a JSON-encoded request body.
|
|
87
|
+
*
|
|
88
|
+
* Empty bodies resolve to `undefined` so the input schema sees the
|
|
89
|
+
* same value whether or not the client sent a body at all. Malformed
|
|
90
|
+
* non-empty bodies throw `BAD_REQUEST`.
|
|
91
|
+
*
|
|
92
|
+
* Why we `text()` first and then `JSON.parse` — instead of
|
|
93
|
+
* `request.json()`: Bun's `request.json()` is a fast path, but it
|
|
94
|
+
* throws a generic `SyntaxError` for **both** empty and malformed
|
|
95
|
+
* bodies, so we cannot tell them apart. Reading text first keeps the
|
|
96
|
+
* two cases distinct (and Bun's `text()` is also fast).
|
|
97
|
+
*/
|
|
98
|
+
async function decodeJsonBody(request) {
|
|
57
99
|
const text = await request.text();
|
|
58
100
|
if (!text) return void 0;
|
|
59
101
|
try {
|
|
@@ -62,5 +104,25 @@ async function parseInput(request, url, qMark) {
|
|
|
62
104
|
throw new SilgiError("BAD_REQUEST", { message: "Malformed JSON body" });
|
|
63
105
|
}
|
|
64
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Decode the input payload off a Fetch `Request`.
|
|
109
|
+
*
|
|
110
|
+
* @param request The incoming request.
|
|
111
|
+
* @param url The full request URL (reused by the caller — we avoid
|
|
112
|
+
* re-parsing it here).
|
|
113
|
+
* @param qMark Byte offset of the `?` in `url`, or `-1` when absent.
|
|
114
|
+
*
|
|
115
|
+
* @returns The decoded input value, or `undefined` when the request
|
|
116
|
+
* carries no payload.
|
|
117
|
+
*/
|
|
118
|
+
async function parseInput(request, url, qMark) {
|
|
119
|
+
if (request.method === "GET" || !request.body) return decodeQueryInput(url, qMark);
|
|
120
|
+
const contentType = request.headers.get("content-type");
|
|
121
|
+
if (contentType) {
|
|
122
|
+
if (contentType.includes("msgpack")) return decodeMsgpackBody(request);
|
|
123
|
+
if (contentType.includes("x-devalue")) return decodeDevalueBody(request);
|
|
124
|
+
}
|
|
125
|
+
return decodeJsonBody(request);
|
|
126
|
+
}
|
|
65
127
|
//#endregion
|
|
66
128
|
export { parseInput };
|
|
@@ -2,7 +2,9 @@ import { AnySchema } from "./schema.mjs";
|
|
|
2
2
|
|
|
3
3
|
//#region src/core/schema-converter.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* JSON Schema subset used
|
|
5
|
+
* JSON Schema subset used across silgi's OpenAPI and analytics output.
|
|
6
|
+
* Intentionally broad (`[key: string]: unknown`) so library-specific
|
|
7
|
+
* fields (e.g. Zod's `x-native-type`) pass through untouched.
|
|
6
8
|
*
|
|
7
9
|
* @category Schema
|
|
8
10
|
*/
|
|
@@ -22,54 +24,68 @@ interface JSONSchema {
|
|
|
22
24
|
default?: unknown;
|
|
23
25
|
[key: string]: unknown;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* JSON Schema dialect passed through to the schema library. Matches the
|
|
29
|
+
* `target` field of the Standard JSON Schema spec. Unknown strings are
|
|
30
|
+
* allowed so new dialects can be threaded through without a silgi
|
|
31
|
+
* release; libraries that do not recognise the value should throw and
|
|
32
|
+
* the conversion falls back to an empty schema.
|
|
33
|
+
*
|
|
34
|
+
* @category Schema
|
|
35
|
+
*/
|
|
36
|
+
type JSONSchemaTarget = 'draft-2020-12' | 'draft-07' | 'openapi-3.0' | (string & {});
|
|
25
37
|
/**
|
|
26
38
|
* Options passed to a converter's `toJsonSchema` method.
|
|
27
39
|
*
|
|
28
40
|
* @category Schema
|
|
29
41
|
*/
|
|
30
42
|
interface ConvertOptions {
|
|
43
|
+
/** `'input'` for pre-transform types, `'output'` for post-transform. */
|
|
31
44
|
strategy: 'input' | 'output';
|
|
45
|
+
/** JSON Schema dialect to target. Defaults to `'draft-2020-12'`. */
|
|
46
|
+
target?: JSONSchemaTarget;
|
|
47
|
+
/** Opaque options the converter may forward to its underlying library. */
|
|
48
|
+
libraryOptions?: Record<string, unknown>;
|
|
32
49
|
}
|
|
33
50
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
51
|
+
* Fallback converter for a schema library that has not adopted the
|
|
52
|
+
* Standard JSON Schema extension yet. Pass instances via
|
|
53
|
+
* `silgi({ schemaConverters: [...] })`.
|
|
36
54
|
*
|
|
37
55
|
* @remarks
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
56
|
+
* Libraries that *do* implement the extension (Zod v4.2+, ArkType
|
|
57
|
+
* v2.1.28+, Valibot v1.2+) are handled without a converter — silgi
|
|
58
|
+
* calls their native `~standard.jsonSchema` directly. Write a converter
|
|
59
|
+
* only when you need to support a vendor that has not yet adopted the
|
|
60
|
+
* spec.
|
|
41
61
|
*
|
|
42
62
|
* @example
|
|
43
|
-
*
|
|
44
|
-
* import type { SchemaConverter } from 'silgi'
|
|
63
|
+
* import type { SchemaConverter } from 'silgi'
|
|
45
64
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* ```
|
|
65
|
+
* const myConverter: SchemaConverter = {
|
|
66
|
+
* vendor: 'my-lib',
|
|
67
|
+
* toJsonSchema(schema, opts) {
|
|
68
|
+
* return { type: 'string' }
|
|
69
|
+
* },
|
|
70
|
+
* }
|
|
53
71
|
*
|
|
54
72
|
* @category Schema
|
|
55
73
|
*/
|
|
56
74
|
interface SchemaConverter {
|
|
57
|
-
/**
|
|
75
|
+
/** Matches the `~standard.vendor` reported by the schema library. */
|
|
58
76
|
vendor: string;
|
|
59
77
|
/**
|
|
60
|
-
* Convert a schema to a JSON Schema object.
|
|
61
|
-
*
|
|
62
|
-
* @param schema - The schema to convert.
|
|
63
|
-
* @param opts - Conversion options including `strategy` (`'input'` | `'output'`).
|
|
64
|
-
* @returns A JSON Schema object. Return `{}` for unsupported/unknown schemas.
|
|
78
|
+
* Convert a schema to a JSON Schema object. Return `{}` for schemas
|
|
79
|
+
* the converter does not understand.
|
|
65
80
|
*/
|
|
66
81
|
toJsonSchema(schema: AnySchema, opts: ConvertOptions): JSONSchema;
|
|
67
82
|
}
|
|
68
83
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
84
|
+
* Per-instance mapping of vendor string → fallback converter. Built by
|
|
85
|
+
* {@link createSchemaRegistry} and threaded through the handler pipeline
|
|
86
|
+
* to the scalar and analytics wrappers. Using `Map` gives O(1) lookup
|
|
87
|
+
* and keyed-by-vendor semantics that match the spec's own extension
|
|
88
|
+
* contract.
|
|
73
89
|
*
|
|
74
90
|
* @category Schema
|
|
75
91
|
*/
|
|
@@ -77,55 +93,44 @@ type SchemaRegistry = Map<string, SchemaConverter>;
|
|
|
77
93
|
/**
|
|
78
94
|
* Build a {@link SchemaRegistry} from an array of converters.
|
|
79
95
|
*
|
|
80
|
-
* @param converters - Array of {@link SchemaConverter} objects, each
|
|
81
|
-
* declaring their own `vendor`.
|
|
82
|
-
* @returns A `Map<string, SchemaConverter>` keyed by `converter.vendor`.
|
|
83
|
-
*
|
|
84
96
|
* @example
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* import { createSchemaRegistry } from 'silgi'
|
|
88
|
-
*
|
|
89
|
-
* const registry = createSchemaRegistry([zodConverter])
|
|
90
|
-
* ```
|
|
97
|
+
* import { zodConverter } from 'silgi/zod'
|
|
98
|
+
* const registry = createSchemaRegistry([zodConverter])
|
|
91
99
|
*
|
|
92
100
|
* @category Schema
|
|
93
101
|
*/
|
|
94
102
|
declare function createSchemaRegistry(converters?: SchemaConverter[]): SchemaRegistry;
|
|
95
103
|
/**
|
|
96
|
-
* Convert any Standard Schema to JSON Schema.
|
|
97
|
-
*
|
|
98
|
-
* @
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* {@link createSchemaRegistry}. When omitted the function still handles
|
|
114
|
-
* schemas that expose the native `jsonSchema.input()` fast path.
|
|
115
|
-
* @returns A JSON Schema object. Returns `{}` when conversion is not possible.
|
|
104
|
+
* Convert any Standard Schema to a JSON Schema object.
|
|
105
|
+
*
|
|
106
|
+
* @param schema The schema to convert.
|
|
107
|
+
* @param strategy `'input'` (default) for pre-transform types, `'output'`
|
|
108
|
+
* for post-transform. Matters for schemas that coerce
|
|
109
|
+
* (e.g. `z.coerce.number()` takes a string and yields a
|
|
110
|
+
* number — input and output schemas differ).
|
|
111
|
+
* @param registry Optional fallback registry built by
|
|
112
|
+
* {@link createSchemaRegistry}. Omit to rely solely on
|
|
113
|
+
* the native Standard JSON Schema extension.
|
|
114
|
+
* @param options Extra knobs: `target` dialect (default
|
|
115
|
+
* `'draft-2020-12'`), opaque `libraryOptions`
|
|
116
|
+
* forwarded to the underlying library.
|
|
117
|
+
*
|
|
118
|
+
* @returns A JSON Schema object. `{}` when the schema cannot be
|
|
119
|
+
* converted (silent fallback — analytics / OpenAPI output
|
|
120
|
+
* still renders, just without schema detail for that field).
|
|
116
121
|
*
|
|
117
122
|
* @example
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* import { createSchemaRegistry, schemaToJsonSchema } from 'silgi'
|
|
121
|
-
* import { z } from 'zod'
|
|
123
|
+
* import { zodConverter } from 'silgi/zod'
|
|
124
|
+
* import { createSchemaRegistry, schemaToJsonSchema } from 'silgi'
|
|
122
125
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* ```
|
|
126
|
+
* const registry = createSchemaRegistry([zodConverter])
|
|
127
|
+
* const json = schemaToJsonSchema(MySchema, 'input', registry)
|
|
126
128
|
*
|
|
127
129
|
* @category Schema
|
|
128
130
|
*/
|
|
129
|
-
declare function schemaToJsonSchema(schema: AnySchema, strategy?: 'input' | 'output', registry?: SchemaRegistry
|
|
131
|
+
declare function schemaToJsonSchema(schema: AnySchema, strategy?: 'input' | 'output', registry?: SchemaRegistry, options?: {
|
|
132
|
+
target?: JSONSchemaTarget;
|
|
133
|
+
libraryOptions?: Record<string, unknown>;
|
|
134
|
+
}): JSONSchema;
|
|
130
135
|
//#endregion
|
|
131
136
|
export { ConvertOptions, JSONSchema, SchemaConverter, SchemaRegistry, createSchemaRegistry, schemaToJsonSchema };
|
|
@@ -2,17 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Build a {@link SchemaRegistry} from an array of converters.
|
|
4
4
|
*
|
|
5
|
-
* @param converters - Array of {@link SchemaConverter} objects, each
|
|
6
|
-
* declaring their own `vendor`.
|
|
7
|
-
* @returns A `Map<string, SchemaConverter>` keyed by `converter.vendor`.
|
|
8
|
-
*
|
|
9
5
|
* @example
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* import { createSchemaRegistry } from 'silgi'
|
|
13
|
-
*
|
|
14
|
-
* const registry = createSchemaRegistry([zodConverter])
|
|
15
|
-
* ```
|
|
6
|
+
* import { zodConverter } from 'silgi/zod'
|
|
7
|
+
* const registry = createSchemaRegistry([zodConverter])
|
|
16
8
|
*
|
|
17
9
|
* @category Schema
|
|
18
10
|
*/
|
|
@@ -21,62 +13,99 @@ function createSchemaRegistry(converters = []) {
|
|
|
21
13
|
for (const converter of converters) map.set(converter.vendor, converter);
|
|
22
14
|
return map;
|
|
23
15
|
}
|
|
24
|
-
const _warnedVendors = /* @__PURE__ */ new Set();
|
|
25
16
|
/**
|
|
26
|
-
*
|
|
17
|
+
* Vendor names we've already warned about. Module-scoped so the
|
|
18
|
+
* warning fires at most once per vendor per process lifetime — across
|
|
19
|
+
* every silgi instance in the same process. The set only holds bounded
|
|
20
|
+
* vendor strings (no schema data, no user input), so sharing it
|
|
21
|
+
* globally is safe.
|
|
22
|
+
*/
|
|
23
|
+
const warnedVendors = /* @__PURE__ */ new Set();
|
|
24
|
+
/** Read the `~standard` slot off an AnySchema in one typed step. */
|
|
25
|
+
const readStandardSlot = (schema) => schema["~standard"];
|
|
26
|
+
/**
|
|
27
|
+
* Call the Standard JSON Schema extension on a schema that exposes it.
|
|
28
|
+
* Returns `null` when the extension does not handle this strategy or
|
|
29
|
+
* throws (in which case the caller falls back to the registry).
|
|
27
30
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
* We strip the `$schema` meta field because it is a JSON Schema marker,
|
|
32
|
+
* not a member of the schema itself — silgi never propagates it.
|
|
33
|
+
*/
|
|
34
|
+
function callNativeJsonSchema(std, opts) {
|
|
35
|
+
const generator = opts.strategy === "output" ? std.jsonSchema?.output : std.jsonSchema?.input;
|
|
36
|
+
if (!generator) return null;
|
|
37
|
+
try {
|
|
38
|
+
const result = generator({
|
|
39
|
+
target: opts.target,
|
|
40
|
+
libraryOptions: opts.libraryOptions
|
|
41
|
+
});
|
|
42
|
+
if (!result || typeof result !== "object") return null;
|
|
43
|
+
const { $schema: _ignored, ...rest } = result;
|
|
44
|
+
return rest;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Warn once per vendor when a registry was passed but had no match. */
|
|
50
|
+
function warnMissingConverter(vendor) {
|
|
51
|
+
if (warnedVendors.has(vendor)) return;
|
|
52
|
+
warnedVendors.add(vendor);
|
|
53
|
+
console.warn(`[silgi] No schema converter registered for vendor "${vendor}". Pass schemaConverters: [${vendor}Converter] to silgi() to enable OpenAPI / analytics schema generation.`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert any Standard Schema to a JSON Schema object.
|
|
38
57
|
*
|
|
39
|
-
* @param schema
|
|
40
|
-
* @param strategy
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
58
|
+
* @param schema The schema to convert.
|
|
59
|
+
* @param strategy `'input'` (default) for pre-transform types, `'output'`
|
|
60
|
+
* for post-transform. Matters for schemas that coerce
|
|
61
|
+
* (e.g. `z.coerce.number()` takes a string and yields a
|
|
62
|
+
* number — input and output schemas differ).
|
|
63
|
+
* @param registry Optional fallback registry built by
|
|
64
|
+
* {@link createSchemaRegistry}. Omit to rely solely on
|
|
65
|
+
* the native Standard JSON Schema extension.
|
|
66
|
+
* @param options Extra knobs: `target` dialect (default
|
|
67
|
+
* `'draft-2020-12'`), opaque `libraryOptions`
|
|
68
|
+
* forwarded to the underlying library.
|
|
69
|
+
*
|
|
70
|
+
* @returns A JSON Schema object. `{}` when the schema cannot be
|
|
71
|
+
* converted (silent fallback — analytics / OpenAPI output
|
|
72
|
+
* still renders, just without schema detail for that field).
|
|
46
73
|
*
|
|
47
74
|
* @example
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* import { createSchemaRegistry, schemaToJsonSchema } from 'silgi'
|
|
51
|
-
* import { z } from 'zod'
|
|
75
|
+
* import { zodConverter } from 'silgi/zod'
|
|
76
|
+
* import { createSchemaRegistry, schemaToJsonSchema } from 'silgi'
|
|
52
77
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* ```
|
|
78
|
+
* const registry = createSchemaRegistry([zodConverter])
|
|
79
|
+
* const json = schemaToJsonSchema(MySchema, 'input', registry)
|
|
56
80
|
*
|
|
57
81
|
* @category Schema
|
|
58
82
|
*/
|
|
59
|
-
function schemaToJsonSchema(schema, strategy = "input", registry) {
|
|
60
|
-
const std = schema
|
|
61
|
-
if (std
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
function schemaToJsonSchema(schema, strategy = "input", registry, options = {}) {
|
|
84
|
+
const std = readStandardSlot(schema);
|
|
85
|
+
if (!std) return {};
|
|
86
|
+
const target = options.target ?? "draft-2020-12";
|
|
87
|
+
const native = callNativeJsonSchema(std, {
|
|
88
|
+
strategy,
|
|
89
|
+
target,
|
|
90
|
+
libraryOptions: options.libraryOptions
|
|
91
|
+
});
|
|
92
|
+
if (native !== null) return native;
|
|
93
|
+
const vendor = typeof std.vendor === "string" ? std.vendor : void 0;
|
|
94
|
+
if (!vendor || !registry) return {};
|
|
95
|
+
const converter = registry.get(vendor);
|
|
96
|
+
if (!converter) {
|
|
97
|
+
warnMissingConverter(vendor);
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return converter.toJsonSchema(schema, {
|
|
102
|
+
strategy,
|
|
103
|
+
target,
|
|
104
|
+
libraryOptions: options.libraryOptions
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
return {};
|
|
78
108
|
}
|
|
79
|
-
return {};
|
|
80
109
|
}
|
|
81
110
|
//#endregion
|
|
82
111
|
export { createSchemaRegistry, schemaToJsonSchema };
|
package/dist/core/serve.d.mts
CHANGED
|
@@ -5,52 +5,53 @@ import { Hookable } from "hookable";
|
|
|
5
5
|
|
|
6
6
|
//#region src/core/serve.d.ts
|
|
7
7
|
interface SilgiServer {
|
|
8
|
-
/**
|
|
8
|
+
/** Full server URL, e.g. `http://127.0.0.1:3000`. */
|
|
9
9
|
readonly url: string;
|
|
10
|
-
/**
|
|
10
|
+
/** Port the server actually bound to (may differ from requested when `0`). */
|
|
11
11
|
readonly port: number;
|
|
12
|
-
/**
|
|
12
|
+
/** Hostname the server bound to. */
|
|
13
13
|
readonly hostname: string;
|
|
14
14
|
/**
|
|
15
|
-
* Gracefully shut
|
|
15
|
+
* Gracefully shut the server down.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Waits for in-flight requests by default. Pass `true` to drop
|
|
18
|
+
* active connections immediately.
|
|
19
19
|
*/
|
|
20
20
|
close(forceCloseConnections?: boolean): Promise<void>;
|
|
21
21
|
}
|
|
22
22
|
interface ServeOptions {
|
|
23
|
-
/** URL path prefix (e.g.
|
|
23
|
+
/** URL path prefix (e.g. `/api`). Requests outside the prefix 404. */
|
|
24
24
|
basePath?: string;
|
|
25
25
|
port?: number;
|
|
26
26
|
hostname?: string;
|
|
27
|
-
/**
|
|
27
|
+
/** Mount the Scalar API Reference UI at `/api/reference`. */
|
|
28
28
|
scalar?: boolean | ScalarOptions;
|
|
29
|
-
/**
|
|
29
|
+
/** Mount the analytics dashboard at `/api/analytics` (requires `auth`). */
|
|
30
30
|
analytics?: AnalyticsOptions;
|
|
31
31
|
/**
|
|
32
32
|
* WebSocket RPC configuration.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
* Pass `false` to disable, or an options object to
|
|
34
|
+
* Auto-enabled when the router contains any subscription procedure.
|
|
35
|
+
* Pass `false` to disable, or an options object to tune crossws
|
|
36
|
+
* (compression, keepalive, maxPayload).
|
|
36
37
|
*/
|
|
37
38
|
ws?: false | WSAdapterOptions;
|
|
38
|
-
/**
|
|
39
|
+
/** TLS material for HTTP/2. When set, the server serves HTTPS. */
|
|
39
40
|
http2?: {
|
|
40
41
|
cert: string;
|
|
41
42
|
key: string;
|
|
42
43
|
};
|
|
43
44
|
/**
|
|
44
|
-
* Graceful shutdown on SIGINT/SIGTERM
|
|
45
|
+
* Graceful shutdown on `SIGINT` / `SIGTERM`.
|
|
45
46
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* - `true` — enable with srvx defaults (recommended).
|
|
48
|
+
* - `false` — disable automatic signal handling.
|
|
49
|
+
* - object — fine-tune timeouts.
|
|
49
50
|
*
|
|
50
51
|
* @default true
|
|
51
52
|
*/
|
|
52
53
|
gracefulShutdown?: boolean | {
|
|
53
|
-
/** Max
|
|
54
|
+
/** Max ms to wait for in-flight requests before force-closing. */timeout?: number; /** Max ms after graceful period before `process.exit`. */
|
|
54
55
|
forceTimeout?: number;
|
|
55
56
|
};
|
|
56
57
|
}
|