silgi 0.52.2 → 0.53.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.
- package/dist/adapters/aws-lambda.mjs +1 -1
- package/dist/broker/redis.mjs +1 -2
- package/dist/builder.mjs +35 -7
- package/dist/caller.mjs +67 -50
- package/dist/client/plugins/retry.mjs +9 -4
- package/dist/compile.d.mts +17 -10
- package/dist/compile.mjs +161 -144
- package/dist/core/ctx-symbols.mjs +18 -1
- package/dist/core/handler.d.mts +3 -3
- package/dist/core/handler.mjs +94 -83
- package/dist/core/input.mjs +116 -37
- 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 +36 -8
- package/dist/core/task.mjs +210 -90
- package/dist/core/url.mjs +19 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/integrations/better-auth/index.mjs +11 -2
- package/dist/lazy.mjs +3 -0
- package/dist/map-input.d.mts +8 -6
- package/dist/map-input.mjs +8 -6
- package/dist/plugins/analytics/routes.mjs +25 -13
- package/dist/plugins/cache.d.mts +62 -126
- package/dist/plugins/cache.mjs +146 -134
- package/dist/plugins/coerce.d.mts +3 -2
- package/dist/plugins/coerce.mjs +25 -8
- package/dist/scalar.d.mts +24 -13
- package/dist/scalar.mjs +292 -201
- package/dist/silgi.d.mts +35 -0
- package/dist/silgi.mjs +177 -103
- package/dist/ws.d.mts +26 -27
- package/dist/ws.mjs +128 -89
- package/package.json +2 -4
|
@@ -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
|
}
|
package/dist/core/serve.mjs
CHANGED
|
@@ -3,19 +3,156 @@ import { createFetchHandler, wrapHandler } from "./handler.mjs";
|
|
|
3
3
|
import { _createWSHooks } from "../ws.mjs";
|
|
4
4
|
import { serve } from "srvx";
|
|
5
5
|
//#region src/core/serve.ts
|
|
6
|
+
/**
|
|
7
|
+
* `serve()` orchestrator
|
|
8
|
+
* ------------------------
|
|
9
|
+
*
|
|
10
|
+
* Builds a Node/Bun/Deno HTTP server from a silgi router. The heavy
|
|
11
|
+
* lifting (the compiled Fetch handler, analytics/Scalar wrappers) is
|
|
12
|
+
* already done elsewhere; this module stitches them together with the
|
|
13
|
+
* runtime-specific WebSocket upgrade plumbing and hands off to `srvx`.
|
|
14
|
+
*
|
|
15
|
+
* The only per-runtime work that lives here is mounting WebSocket hooks
|
|
16
|
+
* for subscriptions:
|
|
17
|
+
*
|
|
18
|
+
* - **Bun** — inject crossws into `serve({ bun: { websocket } })`
|
|
19
|
+
* and intercept upgrade requests at the Fetch layer.
|
|
20
|
+
* - **Deno** — intercept upgrade requests at the Fetch layer and
|
|
21
|
+
* call the crossws Deno adapter directly.
|
|
22
|
+
* - **Node** — after srvx exposes the `http.Server`, attach crossws
|
|
23
|
+
* via `server.on('upgrade', …)`.
|
|
24
|
+
*
|
|
25
|
+
* Everything else is shared: URL resolution, graceful shutdown, hook
|
|
26
|
+
* firing, and the startup banner.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Detect the current JavaScript runtime from well-known globals.
|
|
30
|
+
*
|
|
31
|
+
* Written as a plain function rather than reading a module-global so
|
|
32
|
+
* the check re-evaluates when the module is imported into a different
|
|
33
|
+
* runtime (e.g. a test that spawns a Bun child process).
|
|
34
|
+
*/
|
|
6
35
|
function detectRuntime() {
|
|
7
36
|
if (typeof globalThis.Bun !== "undefined") return "bun";
|
|
8
37
|
if (typeof globalThis.Deno !== "undefined") return "deno";
|
|
9
38
|
return "node";
|
|
10
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Walk the router tree looking for a subscription. We stop at the first
|
|
42
|
+
* hit because we only need to decide whether to wire up WS at all — we
|
|
43
|
+
* do not need an inventory.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors the helper in `silgi.ts`; kept here so `core/serve.ts` has no
|
|
46
|
+
* runtime dependency on the top-level instance module.
|
|
47
|
+
*/
|
|
11
48
|
function routerHasSubscription(def) {
|
|
12
49
|
if (!def || typeof def !== "object") return false;
|
|
13
50
|
if (def.type === "subscription") return true;
|
|
14
|
-
for (const
|
|
51
|
+
for (const child of Object.values(def)) if (routerHasSubscription(child)) return true;
|
|
15
52
|
return false;
|
|
16
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Translate our `gracefulShutdown` option into the shape srvx expects.
|
|
56
|
+
*
|
|
57
|
+
* srvx uses `gracefulTimeout`, we expose `timeout` — the rename keeps
|
|
58
|
+
* the public API readable without leaking srvx vocabulary.
|
|
59
|
+
*/
|
|
60
|
+
function resolveShutdown(option) {
|
|
61
|
+
if (option === void 0 || typeof option === "boolean") return option ?? true;
|
|
62
|
+
return {
|
|
63
|
+
gracefulTimeout: option.timeout,
|
|
64
|
+
forceTimeout: option.forceTimeout
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the WS wiring for the current runtime.
|
|
69
|
+
*
|
|
70
|
+
* Everything stays lazy: when no subscriptions exist or WS is
|
|
71
|
+
* explicitly disabled, we return `{ fetch: httpHandler }` and never
|
|
72
|
+
* import the crossws adapters.
|
|
73
|
+
*/
|
|
74
|
+
async function wireWebSocket(routerDef, httpHandler, enabled, wsOpts, runtime, bunServerRef) {
|
|
75
|
+
if (!enabled) return { fetch: httpHandler };
|
|
76
|
+
const hooksObj = _createWSHooks(routerDef, wsOpts);
|
|
77
|
+
if (runtime === "bun") {
|
|
78
|
+
const bunAdapter = (await import("crossws/adapters/bun")).default;
|
|
79
|
+
const adapter = bunAdapter({ hooks: hooksObj });
|
|
80
|
+
return {
|
|
81
|
+
bunWebsocket: adapter.websocket,
|
|
82
|
+
fetch: (async (req) => {
|
|
83
|
+
if (req.headers.get("upgrade") === "websocket" && bunServerRef.current) {
|
|
84
|
+
const res = await adapter.handleUpgrade(req, bunServerRef.current);
|
|
85
|
+
if (res) return res;
|
|
86
|
+
}
|
|
87
|
+
return httpHandler(req);
|
|
88
|
+
})
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (runtime === "deno") {
|
|
92
|
+
const denoAdapter = (await import("crossws/adapters/deno")).default;
|
|
93
|
+
const adapter = denoAdapter({ hooks: hooksObj });
|
|
94
|
+
return { fetch: (async (req) => {
|
|
95
|
+
if (req.headers.get("upgrade") === "websocket") return adapter.handleUpgrade(req, {});
|
|
96
|
+
return httpHandler(req);
|
|
97
|
+
}) };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
fetch: httpHandler,
|
|
101
|
+
attachNode: async (httpServer) => {
|
|
102
|
+
const { attachWebSocket } = await import("../ws.mjs");
|
|
103
|
+
await attachWebSocket(httpServer, routerDef, wsOpts);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Compute the final server URL from srvx's output.
|
|
109
|
+
*
|
|
110
|
+
* srvx usually populates `server.url` itself, but when the caller
|
|
111
|
+
* requests port `0` (pick-any-free) or when HTTP/2 is on, we have to
|
|
112
|
+
* piece it together from the runtime-specific socket info. Trailing
|
|
113
|
+
* slashes are stripped so `${url}/api/foo` always produces exactly one
|
|
114
|
+
* separator.
|
|
115
|
+
*/
|
|
116
|
+
function resolveUrl(server, requestedPort, hostname, http2) {
|
|
117
|
+
let port = requestedPort;
|
|
118
|
+
if (server.node?.server) {
|
|
119
|
+
const addr = server.node.server.address();
|
|
120
|
+
if (addr && typeof addr === "object") port = addr.port;
|
|
121
|
+
} else if (server.bun?.server) port = server.bun.server.port ?? requestedPort;
|
|
122
|
+
const protocol = http2 ? "https" : "http";
|
|
123
|
+
const raw = server.url || `${protocol}://${hostname}:${port}`;
|
|
124
|
+
return {
|
|
125
|
+
url: raw.endsWith("/") ? raw.slice(0, -1) : raw,
|
|
126
|
+
port
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Print the startup banner.
|
|
131
|
+
*
|
|
132
|
+
* Side-effect-y and intentionally not bypassable — a server starting
|
|
133
|
+
* silently is a surprising default; `silent: true` on srvx suppresses
|
|
134
|
+
* *its* banner, but silgi still wants to show where it bound.
|
|
135
|
+
*/
|
|
136
|
+
function printBanner(url, hostname, port, runtime, options, wsEnabled) {
|
|
137
|
+
console.log(`\nSilgi server running at ${url}`);
|
|
138
|
+
if (options?.http2) console.log(` HTTP/2 enabled (with HTTP/1.1 fallback)`);
|
|
139
|
+
if (wsEnabled) console.log(` WebSocket RPC at ws://${hostname}:${port}/_ws (${runtime})`);
|
|
140
|
+
if (options?.scalar) console.log(` Scalar API Reference at ${url}/api/reference`);
|
|
141
|
+
if (options?.analytics) console.log(` Analytics dashboard at ${url}/api/analytics`);
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Build and start the HTTP (and optionally WebSocket) server.
|
|
146
|
+
*
|
|
147
|
+
* The function is intentionally long because the steps are strictly
|
|
148
|
+
* ordered: WS wiring has to happen before srvx starts (Bun needs its
|
|
149
|
+
* websocket handler at construction time); the `http.Server` only
|
|
150
|
+
* exists once srvx returns (Node attaches WS there); and the banner
|
|
151
|
+
* wants real bound port info. Splitting further would hide the
|
|
152
|
+
* ordering more than it would simplify anything.
|
|
153
|
+
*/
|
|
17
154
|
async function createServeHandler(routerDef, contextFactory, hooks, options, schemaRegistry, bridge) {
|
|
18
|
-
const
|
|
155
|
+
const requestedPort = options?.port ?? 3e3;
|
|
19
156
|
const hostname = options?.hostname ?? "127.0.0.1";
|
|
20
157
|
const prefix = options?.basePath ? normalizePrefix(options.basePath) : void 0;
|
|
21
158
|
const httpHandler = wrapHandler(createFetchHandler(routerDef, contextFactory, hooks, prefix, bridge), routerDef, options ? {
|
|
@@ -26,89 +163,42 @@ async function createServeHandler(routerDef, contextFactory, hooks, options, sch
|
|
|
26
163
|
schemaRegistry,
|
|
27
164
|
hooks
|
|
28
165
|
}, prefix);
|
|
29
|
-
const shutdownOpt = options?.gracefulShutdown ?? true;
|
|
30
|
-
let gracefulShutdown;
|
|
31
|
-
if (typeof shutdownOpt === "object") gracefulShutdown = {
|
|
32
|
-
gracefulTimeout: shutdownOpt.timeout,
|
|
33
|
-
forceTimeout: shutdownOpt.forceTimeout
|
|
34
|
-
};
|
|
35
|
-
else gracefulShutdown = shutdownOpt;
|
|
36
|
-
const wsExplicitlyDisabled = options?.ws === false;
|
|
37
|
-
const wsOpts = typeof options?.ws === "object" ? options.ws : void 0;
|
|
38
|
-
const wsEnabled = !wsExplicitlyDisabled && routerHasSubscription(routerDef);
|
|
39
166
|
const runtime = detectRuntime();
|
|
40
|
-
|
|
41
|
-
|
|
167
|
+
const wsEnabled = !(options?.ws === false) && routerHasSubscription(routerDef);
|
|
168
|
+
const wsOpts = typeof options?.ws === "object" ? options.ws : void 0;
|
|
42
169
|
const bunServerRef = { current: void 0 };
|
|
43
|
-
|
|
44
|
-
if (wsEnabled) {
|
|
45
|
-
const hooksObj = _createWSHooks(routerDef, wsOpts);
|
|
46
|
-
if (runtime === "bun") {
|
|
47
|
-
const bunAdapter = (await import("crossws/adapters/bun")).default;
|
|
48
|
-
const adapter = bunAdapter({ hooks: hooksObj });
|
|
49
|
-
bunWebsocket = adapter.websocket;
|
|
50
|
-
fetchHandler = (async (req) => {
|
|
51
|
-
if (req.headers.get("upgrade") === "websocket" && bunServerRef.current) {
|
|
52
|
-
const res = await adapter.handleUpgrade(req, bunServerRef.current);
|
|
53
|
-
if (res) return res;
|
|
54
|
-
}
|
|
55
|
-
return httpHandler(req);
|
|
56
|
-
});
|
|
57
|
-
} else if (runtime === "deno") {
|
|
58
|
-
const denoAdapter = (await import("crossws/adapters/deno")).default;
|
|
59
|
-
const adapter = denoAdapter({ hooks: hooksObj });
|
|
60
|
-
fetchHandler = (async (req) => {
|
|
61
|
-
if (req.headers.get("upgrade") === "websocket") return adapter.handleUpgrade(req, {});
|
|
62
|
-
return httpHandler(req);
|
|
63
|
-
});
|
|
64
|
-
} else nodeAttach = async (httpServer) => {
|
|
65
|
-
const { attachWebSocket } = await import("../ws.mjs");
|
|
66
|
-
await attachWebSocket(httpServer, routerDef, wsOpts);
|
|
67
|
-
};
|
|
68
|
-
}
|
|
170
|
+
const wiring = await wireWebSocket(routerDef, httpHandler, wsEnabled, wsOpts, runtime, bunServerRef);
|
|
69
171
|
const server = await serve({
|
|
70
|
-
port,
|
|
172
|
+
port: requestedPort,
|
|
71
173
|
hostname,
|
|
72
|
-
fetch:
|
|
73
|
-
gracefulShutdown,
|
|
174
|
+
fetch: wiring.fetch,
|
|
175
|
+
gracefulShutdown: resolveShutdown(options?.gracefulShutdown),
|
|
74
176
|
silent: true,
|
|
75
|
-
...options?.http2
|
|
177
|
+
...options?.http2 ? { tls: {
|
|
76
178
|
cert: options.http2.cert,
|
|
77
179
|
key: options.http2.key
|
|
78
|
-
} },
|
|
79
|
-
...bunWebsocket ? { bun: { websocket: bunWebsocket } } : {}
|
|
180
|
+
} } : {},
|
|
181
|
+
...wiring.bunWebsocket ? { bun: { websocket: wiring.bunWebsocket } } : {}
|
|
80
182
|
});
|
|
81
183
|
await server.ready();
|
|
82
184
|
if (runtime === "bun" && server.bun?.server) bunServerRef.current = server.bun.server;
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const addr = server.node.server.address();
|
|
87
|
-
if (addr && typeof addr === "object") resolvedPort = addr.port;
|
|
88
|
-
} else if (server.bun?.server) resolvedPort = server.bun.server.port ?? port;
|
|
89
|
-
const protocol = options?.http2 ? "https" : "http";
|
|
90
|
-
const rawUrl = server.url || `${protocol}://${hostname}:${resolvedPort}`;
|
|
91
|
-
const url = rawUrl.endsWith("/") ? rawUrl.slice(0, -1) : rawUrl;
|
|
92
|
-
console.log(`\nSilgi server running at ${url}`);
|
|
93
|
-
if (options?.http2) console.log(` HTTP/2 enabled (with HTTP/1.1 fallback)`);
|
|
94
|
-
if (wsEnabled) console.log(` WebSocket RPC at ws://${hostname}:${resolvedPort}/_ws (${runtime})`);
|
|
95
|
-
if (options?.scalar) console.log(` Scalar API Reference at ${url}/api/reference`);
|
|
96
|
-
if (options?.analytics) console.log(` Analytics dashboard at ${url}/api/analytics`);
|
|
97
|
-
console.log();
|
|
185
|
+
if (wiring.attachNode && server.node?.server) await wiring.attachNode(server.node.server);
|
|
186
|
+
const { url, port } = resolveUrl(server, requestedPort, hostname, Boolean(options?.http2));
|
|
187
|
+
printBanner(url, hostname, port, runtime, options, wsEnabled);
|
|
98
188
|
await hooks.callHook("serve:start", {
|
|
99
189
|
url,
|
|
100
|
-
port
|
|
190
|
+
port,
|
|
101
191
|
hostname
|
|
102
192
|
});
|
|
103
193
|
return {
|
|
104
194
|
url,
|
|
105
|
-
port
|
|
195
|
+
port,
|
|
106
196
|
hostname,
|
|
107
197
|
async close(forceCloseConnections = false) {
|
|
108
198
|
await server.close(forceCloseConnections);
|
|
109
199
|
await hooks.callHook("serve:stop", {
|
|
110
200
|
url,
|
|
111
|
-
port
|
|
201
|
+
port,
|
|
112
202
|
hostname
|
|
113
203
|
});
|
|
114
204
|
}
|
package/dist/core/sse.d.mts
CHANGED
|
@@ -4,15 +4,14 @@ interface EventMeta {
|
|
|
4
4
|
retry?: number;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Attach SSE
|
|
7
|
+
* Attach SSE `id` / `retry` metadata to a yielded value.
|
|
8
8
|
*
|
|
9
|
-
* Only
|
|
10
|
-
*
|
|
9
|
+
* Only object-shaped values can carry metadata; primitives cannot be
|
|
10
|
+
* keyed in the `WeakMap` and are returned unchanged. Wrap primitives
|
|
11
|
+
* in a one-field object when you need metadata on them.
|
|
11
12
|
*/
|
|
12
13
|
declare function withEventMeta<T>(value: T, meta: EventMeta): T;
|
|
13
|
-
/**
|
|
14
|
-
* Read SSE metadata from a value (if attached).
|
|
15
|
-
*/
|
|
14
|
+
/** Read SSE metadata previously attached via `withEventMeta`. */
|
|
16
15
|
declare function getEventMeta(value: unknown): EventMeta | undefined;
|
|
17
16
|
//#endregion
|
|
18
17
|
export { EventMeta, getEventMeta, withEventMeta };
|