@spikard/wasm 0.6.2 → 0.7.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/README.md +356 -524
- package/dist/chunk-YO5KBN4A.mjs +2268 -0
- package/dist/chunk-YO5KBN4A.mjs.map +1 -0
- package/dist/index.d.mts +365 -0
- package/dist/index.d.ts +365 -0
- package/dist/index.js +2270 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node.d.mts +9 -0
- package/dist/node.d.ts +9 -0
- package/dist/node.js +2401 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +120 -0
- package/dist/node.mjs.map +1 -0
- package/{dist-node → dist}/package.json +1 -1
- package/{dist-node → dist}/spikard_wasm.js +11 -8
- package/{dist-node → dist}/spikard_wasm_bg.wasm +0 -0
- package/package.json +54 -46
- package/dist-bundler/package.json +0 -34
- package/dist-bundler/spikard_wasm.d.ts +0 -28
- package/dist-bundler/spikard_wasm.js +0 -5
- package/dist-bundler/spikard_wasm_bg.js +0 -914
- package/dist-bundler/spikard_wasm_bg.wasm +0 -0
- package/dist-bundler/spikard_wasm_bg.wasm.d.ts +0 -26
- package/dist-node/README.md +0 -739
- package/dist-node/spikard_wasm.d.ts +0 -28
- package/dist-node/spikard_wasm_bg.wasm.d.ts +0 -26
- package/dist-web/README.md +0 -739
- package/dist-web/package.json +0 -32
- package/dist-web/spikard_wasm.d.ts +0 -79
- package/dist-web/spikard_wasm.js +0 -921
- package/dist-web/spikard_wasm_bg.wasm +0 -0
- package/dist-web/spikard_wasm_bg.wasm.d.ts +0 -26
- /package/{dist-bundler → dist}/README.md +0 -0
package/dist/node.js
ADDED
|
@@ -0,0 +1,2401 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var zlib = require('zlib');
|
|
6
|
+
var fflate = require('fflate');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var fs__default = /*#__PURE__*/_interopDefault(fs);
|
|
11
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/streaming.ts
|
|
20
|
+
var STREAM_HANDLE_PROP = /* @__PURE__ */ Symbol.for("spikard.streaming.handle");
|
|
21
|
+
var StreamingResponse = class {
|
|
22
|
+
statusCode;
|
|
23
|
+
headers;
|
|
24
|
+
[STREAM_HANDLE_PROP];
|
|
25
|
+
__spikard_streaming__;
|
|
26
|
+
constructor(stream, init) {
|
|
27
|
+
this[STREAM_HANDLE_PROP] = toAsyncIterator(stream);
|
|
28
|
+
this.statusCode = init?.statusCode ?? 200;
|
|
29
|
+
this.headers = init?.headers ?? {};
|
|
30
|
+
this.__spikard_streaming__ = true;
|
|
31
|
+
}
|
|
32
|
+
async collect() {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
for await (const chunk of this[STREAM_HANDLE_PROP]) {
|
|
35
|
+
chunks.push(normalizeChunk(chunk));
|
|
36
|
+
}
|
|
37
|
+
return concatChunks(chunks);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function isStreamingResponse(value) {
|
|
41
|
+
return Boolean(value) && value instanceof StreamingResponse;
|
|
42
|
+
}
|
|
43
|
+
function toAsyncIterator(source) {
|
|
44
|
+
if (!source || typeof source !== "object") {
|
|
45
|
+
throw new TypeError("StreamingResponse requires an async iterator or generator");
|
|
46
|
+
}
|
|
47
|
+
if (typeof source.next === "function") {
|
|
48
|
+
const iterator = source;
|
|
49
|
+
if (typeof iterator[Symbol.asyncIterator] === "function") {
|
|
50
|
+
return iterator;
|
|
51
|
+
}
|
|
52
|
+
return wrapIterator(iterator);
|
|
53
|
+
}
|
|
54
|
+
if (typeof source[Symbol.asyncIterator] === "function") {
|
|
55
|
+
return source[Symbol.asyncIterator]();
|
|
56
|
+
}
|
|
57
|
+
throw new TypeError("StreamingResponse requires an async iterator or generator");
|
|
58
|
+
}
|
|
59
|
+
function wrapIterator(iterator) {
|
|
60
|
+
return {
|
|
61
|
+
next: iterator.next.bind(iterator),
|
|
62
|
+
...iterator.throw ? { throw: iterator.throw.bind(iterator) } : {},
|
|
63
|
+
...iterator.return ? { return: iterator.return.bind(iterator) } : {},
|
|
64
|
+
[Symbol.asyncIterator]() {
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function normalizeChunk(chunk) {
|
|
70
|
+
if (typeof chunk === "string") {
|
|
71
|
+
return new TextEncoder().encode(chunk);
|
|
72
|
+
}
|
|
73
|
+
if (chunk instanceof Uint8Array) {
|
|
74
|
+
return chunk;
|
|
75
|
+
}
|
|
76
|
+
if (ArrayBuffer.isView(chunk)) {
|
|
77
|
+
const view = chunk;
|
|
78
|
+
return new Uint8Array(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
|
|
79
|
+
}
|
|
80
|
+
if (chunk instanceof ArrayBuffer) {
|
|
81
|
+
return new Uint8Array(chunk);
|
|
82
|
+
}
|
|
83
|
+
if (chunk == null) {
|
|
84
|
+
return new Uint8Array();
|
|
85
|
+
}
|
|
86
|
+
return new TextEncoder().encode(typeof chunk === "object" ? JSON.stringify(chunk) : String(chunk));
|
|
87
|
+
}
|
|
88
|
+
function concatChunks(chunks) {
|
|
89
|
+
if (chunks.length === 1) {
|
|
90
|
+
return chunks[0] ?? new Uint8Array();
|
|
91
|
+
}
|
|
92
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
93
|
+
const result = new Uint8Array(totalLength);
|
|
94
|
+
let offset = 0;
|
|
95
|
+
for (const chunk of chunks) {
|
|
96
|
+
result.set(chunk, offset);
|
|
97
|
+
offset += chunk.length;
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
var globalAny = globalThis;
|
|
102
|
+
var wasmBindingsPromise = null;
|
|
103
|
+
async function loadWasmBindings() {
|
|
104
|
+
if (wasmBindingsPromise) {
|
|
105
|
+
return wasmBindingsPromise;
|
|
106
|
+
}
|
|
107
|
+
wasmBindingsPromise = (async () => {
|
|
108
|
+
const distPath = "./spikard_wasm.js";
|
|
109
|
+
const runtimePath = "../runtime/spikard_wasm.js";
|
|
110
|
+
const webFallback = "../../../crates/spikard-wasm/dist-web/spikard_wasm.js";
|
|
111
|
+
const nodeFallback = "../../../crates/spikard-wasm/dist-node/spikard_wasm.js";
|
|
112
|
+
const preferNode = isNodeLikeEnvironment();
|
|
113
|
+
const candidates = preferNode ? [distPath, nodeFallback, runtimePath, webFallback] : [distPath, runtimePath, webFallback, nodeFallback];
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
try {
|
|
116
|
+
return await import(candidate);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw new Error("Failed to load WASM bindings (dist, runtime, dist-web, dist-node).");
|
|
121
|
+
})();
|
|
122
|
+
return wasmBindingsPromise;
|
|
123
|
+
}
|
|
124
|
+
var defaultNativeClientFactory = async (routesJson, handlers, config, lifecycleHooks, dependencies) => {
|
|
125
|
+
const bindings = await loadWasmBindings();
|
|
126
|
+
if (typeof bindings.default === "function") {
|
|
127
|
+
await bindings.default();
|
|
128
|
+
}
|
|
129
|
+
await Promise.resolve(bindings.init());
|
|
130
|
+
return new bindings.TestClient(routesJson, handlers, config, lifecycleHooks, dependencies);
|
|
131
|
+
};
|
|
132
|
+
var nativeClientFactory = defaultNativeClientFactory;
|
|
133
|
+
var textDecoder = new TextDecoder();
|
|
134
|
+
var textEncoder = new TextEncoder();
|
|
135
|
+
var RAW_REQUEST_KEY = "__spikard_raw_request__";
|
|
136
|
+
var ABORT_SIGNAL_KEY = "__spikard_abort_signal__";
|
|
137
|
+
var EMPTY_HOOKS = {
|
|
138
|
+
onRequest: [],
|
|
139
|
+
preValidation: [],
|
|
140
|
+
preHandler: [],
|
|
141
|
+
onResponse: [],
|
|
142
|
+
onError: []
|
|
143
|
+
};
|
|
144
|
+
var gunzipImplementation = null;
|
|
145
|
+
function __setGunzipImplementation(fn) {
|
|
146
|
+
gunzipImplementation = fn;
|
|
147
|
+
}
|
|
148
|
+
var TestResponse = class {
|
|
149
|
+
constructor(status, headersMap, body) {
|
|
150
|
+
this.status = status;
|
|
151
|
+
this.headersMap = headersMap;
|
|
152
|
+
this.bodyBytes = toUint8Array(body);
|
|
153
|
+
}
|
|
154
|
+
bodyBytes;
|
|
155
|
+
decodedBody = null;
|
|
156
|
+
get statusCode() {
|
|
157
|
+
return this.status;
|
|
158
|
+
}
|
|
159
|
+
headers() {
|
|
160
|
+
return { ...this.headersMap };
|
|
161
|
+
}
|
|
162
|
+
text() {
|
|
163
|
+
return textDecoder.decode(this.getDecodedBody());
|
|
164
|
+
}
|
|
165
|
+
json() {
|
|
166
|
+
const body = this.getDecodedBody();
|
|
167
|
+
if (body.length === 0) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const textValue = textDecoder.decode(body);
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(textValue);
|
|
173
|
+
} catch {
|
|
174
|
+
return textValue;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
bytes() {
|
|
178
|
+
const decoded = this.getDecodedBody();
|
|
179
|
+
const bufferCtor = globalAny.Buffer;
|
|
180
|
+
if (bufferCtor) {
|
|
181
|
+
return bufferCtor.from(decoded);
|
|
182
|
+
}
|
|
183
|
+
return decoded.slice();
|
|
184
|
+
}
|
|
185
|
+
raw() {
|
|
186
|
+
return this.bodyBytes.slice();
|
|
187
|
+
}
|
|
188
|
+
getDecodedBody() {
|
|
189
|
+
if (this.decodedBody) {
|
|
190
|
+
return this.decodedBody;
|
|
191
|
+
}
|
|
192
|
+
const encoding = this.getHeaderValue("content-encoding");
|
|
193
|
+
if (encoding && encoding.toLowerCase() === "gzip") {
|
|
194
|
+
this.decodedBody = gunzipBytes(this.bodyBytes);
|
|
195
|
+
return this.decodedBody;
|
|
196
|
+
}
|
|
197
|
+
this.decodedBody = this.bodyBytes;
|
|
198
|
+
return this.decodedBody;
|
|
199
|
+
}
|
|
200
|
+
getHeaderValue(name) {
|
|
201
|
+
for (const [key, value] of Object.entries(this.headersMap)) {
|
|
202
|
+
if (key.toLowerCase() === name.toLowerCase()) {
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var TestClient = class {
|
|
210
|
+
routes;
|
|
211
|
+
websocketRoutes;
|
|
212
|
+
websocketHandlers;
|
|
213
|
+
nativeClientPromise;
|
|
214
|
+
constructor(app) {
|
|
215
|
+
if (!app || !Array.isArray(app.routes)) {
|
|
216
|
+
throw new Error("Invalid Spikard app: missing routes");
|
|
217
|
+
}
|
|
218
|
+
this.routes = app.routes;
|
|
219
|
+
this.websocketRoutes = app.websocketRoutes ?? [];
|
|
220
|
+
const httpHandlers = app.handlers ?? {};
|
|
221
|
+
this.websocketHandlers = app.websocketHandlers ?? httpHandlers;
|
|
222
|
+
const routesJson = JSON.stringify(app.routes);
|
|
223
|
+
const lifecycleHooks = normalizeLifecycleHooks(app.lifecycleHooks);
|
|
224
|
+
const wrappedHandlers = wrapHandlers(httpHandlers);
|
|
225
|
+
const dependencies = app.dependencies ?? null;
|
|
226
|
+
const nativeLifecycleHooks = createNativeLifecycleHooks(lifecycleHooks);
|
|
227
|
+
this.nativeClientPromise = (async () => {
|
|
228
|
+
const resolvedApp = await withStaticManifest(app);
|
|
229
|
+
const configString = resolvedApp.config && Object.keys(resolvedApp.config).length > 0 ? JSON.stringify(resolvedApp.config) : null;
|
|
230
|
+
return nativeClientFactory(routesJson, wrappedHandlers, configString, nativeLifecycleHooks, dependencies);
|
|
231
|
+
})();
|
|
232
|
+
}
|
|
233
|
+
async get(path2, headers) {
|
|
234
|
+
const native = await this.nativeClientPromise;
|
|
235
|
+
const snapshot = await this.dispatchWithHeaders(native, "GET", path2, headers);
|
|
236
|
+
return this.responseFromNative(snapshot);
|
|
237
|
+
}
|
|
238
|
+
async delete(path2, headers) {
|
|
239
|
+
const native = await this.nativeClientPromise;
|
|
240
|
+
const snapshot = await this.dispatchWithHeaders(native, "DELETE", path2, headers);
|
|
241
|
+
return this.responseFromNative(snapshot);
|
|
242
|
+
}
|
|
243
|
+
async head(path2, headers) {
|
|
244
|
+
const native = await this.nativeClientPromise;
|
|
245
|
+
const snapshot = await this.dispatchWithHeaders(native, "HEAD", path2, headers);
|
|
246
|
+
return this.responseFromNative(snapshot);
|
|
247
|
+
}
|
|
248
|
+
async options(path2, headers) {
|
|
249
|
+
const native = await this.nativeClientPromise;
|
|
250
|
+
const snapshot = await this.dispatchWithHeaders(native, "OPTIONS", path2, headers);
|
|
251
|
+
return this.responseFromNative(snapshot);
|
|
252
|
+
}
|
|
253
|
+
async trace(path2, headers) {
|
|
254
|
+
const native = await this.nativeClientPromise;
|
|
255
|
+
const snapshot = await this.dispatchWithHeaders(native, "TRACE", path2, headers);
|
|
256
|
+
return this.responseFromNative(snapshot);
|
|
257
|
+
}
|
|
258
|
+
async post(path2, options) {
|
|
259
|
+
const native = await this.nativeClientPromise;
|
|
260
|
+
const snapshot = await native.post(path2, this.buildNativeOptions(options));
|
|
261
|
+
return this.responseFromNative(snapshot);
|
|
262
|
+
}
|
|
263
|
+
async put(path2, options) {
|
|
264
|
+
const native = await this.nativeClientPromise;
|
|
265
|
+
const snapshot = await native.put(path2, this.buildNativeOptions(options));
|
|
266
|
+
return this.responseFromNative(snapshot);
|
|
267
|
+
}
|
|
268
|
+
async patch(path2, options) {
|
|
269
|
+
const native = await this.nativeClientPromise;
|
|
270
|
+
const snapshot = await native.patch(path2, this.buildNativeOptions(options));
|
|
271
|
+
return this.responseFromNative(snapshot);
|
|
272
|
+
}
|
|
273
|
+
async websocketConnect(path2) {
|
|
274
|
+
await this.nativeClientPromise;
|
|
275
|
+
const route2 = this.findWebSocketRoute(path2);
|
|
276
|
+
if (!route2) {
|
|
277
|
+
throw new Error(`WebSocket route not found for ${path2}`);
|
|
278
|
+
}
|
|
279
|
+
const handler = this.websocketHandlers[route2.metadata.handler_name];
|
|
280
|
+
if (!handler) {
|
|
281
|
+
throw new Error(`Handler ${route2.metadata.handler_name} not registered`);
|
|
282
|
+
}
|
|
283
|
+
return WebSocketTestConnection.connect(handler);
|
|
284
|
+
}
|
|
285
|
+
responseFromNative(snapshot) {
|
|
286
|
+
const rawHeaders = normalizeRecord(snapshot.headers);
|
|
287
|
+
const normalizedHeaders = lowerCaseHeaderKeys(rawHeaders);
|
|
288
|
+
const bodyBytes = toUint8Array(snapshot.body);
|
|
289
|
+
const unwrapped = tryUnwrapStructuredSnapshot(bodyBytes);
|
|
290
|
+
if (unwrapped) {
|
|
291
|
+
const flattened = flattenStructuredResponse(unwrapped);
|
|
292
|
+
const status = flattened.status ?? flattened.statusCode ?? snapshot.status;
|
|
293
|
+
const headers = lowerCaseHeaderKeys({
|
|
294
|
+
...normalizeRecord(flattened.headers ?? {}),
|
|
295
|
+
...normalizedHeaders
|
|
296
|
+
});
|
|
297
|
+
const body = flattened.body ?? null;
|
|
298
|
+
return new TestResponse(status, headers, encodeBodyBytes(body));
|
|
299
|
+
}
|
|
300
|
+
return new TestResponse(snapshot.status, normalizedHeaders, bodyBytes);
|
|
301
|
+
}
|
|
302
|
+
dispatchWithHeaders(native, method, path2, headers) {
|
|
303
|
+
const normalizedHeaders = normalizeHeaderInput(headers);
|
|
304
|
+
if (!normalizedHeaders) {
|
|
305
|
+
switch (method) {
|
|
306
|
+
case "GET":
|
|
307
|
+
return native.get(path2, null);
|
|
308
|
+
case "DELETE":
|
|
309
|
+
return native.delete(path2, null);
|
|
310
|
+
case "HEAD":
|
|
311
|
+
return native.head(path2, null);
|
|
312
|
+
case "OPTIONS":
|
|
313
|
+
return native.options(path2, null);
|
|
314
|
+
case "TRACE":
|
|
315
|
+
return native.trace(path2, null);
|
|
316
|
+
default:
|
|
317
|
+
return native.get(path2, null);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const maybeHandleRequest = native.handle_request;
|
|
321
|
+
if (typeof maybeHandleRequest === "function") {
|
|
322
|
+
return native.handle_request(JSON.stringify({ method, path: path2, headers: normalizedHeaders }));
|
|
323
|
+
}
|
|
324
|
+
switch (method) {
|
|
325
|
+
case "GET":
|
|
326
|
+
return native.get(path2, normalizedHeaders);
|
|
327
|
+
case "DELETE":
|
|
328
|
+
return native.delete(path2, normalizedHeaders);
|
|
329
|
+
case "HEAD":
|
|
330
|
+
return native.head(path2, normalizedHeaders);
|
|
331
|
+
case "OPTIONS":
|
|
332
|
+
return native.options(path2, normalizedHeaders);
|
|
333
|
+
case "TRACE":
|
|
334
|
+
return native.trace(path2, normalizedHeaders);
|
|
335
|
+
default:
|
|
336
|
+
return native.get(path2, normalizedHeaders);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
buildNativeOptions(options) {
|
|
340
|
+
if (!options) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const native = {};
|
|
344
|
+
const headers = this.buildHeaders(options);
|
|
345
|
+
if (headers) {
|
|
346
|
+
native.headers = headers;
|
|
347
|
+
}
|
|
348
|
+
if (options.multipart) {
|
|
349
|
+
native.multipart = {
|
|
350
|
+
fields: options.multipart.fields ?? {},
|
|
351
|
+
files: options.multipart.files ?? []
|
|
352
|
+
};
|
|
353
|
+
return native;
|
|
354
|
+
}
|
|
355
|
+
if (options.form && typeof options.form === "object") {
|
|
356
|
+
native.form = options.form;
|
|
357
|
+
return native;
|
|
358
|
+
}
|
|
359
|
+
if (typeof options.form === "string") {
|
|
360
|
+
native.formRaw = options.form;
|
|
361
|
+
return native;
|
|
362
|
+
}
|
|
363
|
+
if (options.formRaw) {
|
|
364
|
+
native.formRaw = options.formRaw;
|
|
365
|
+
return native;
|
|
366
|
+
}
|
|
367
|
+
if ("json" in options) {
|
|
368
|
+
native.json = options.json == null ? null : normalizeJsonValue(options.json);
|
|
369
|
+
}
|
|
370
|
+
if (options.binary) {
|
|
371
|
+
native.binary = options.binary;
|
|
372
|
+
}
|
|
373
|
+
return Object.keys(native).length === 0 ? null : native;
|
|
374
|
+
}
|
|
375
|
+
buildHeaders(options) {
|
|
376
|
+
if (!options?.headers || Object.keys(options.headers).length === 0) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
return normalizeHeaderInput(options.headers);
|
|
380
|
+
}
|
|
381
|
+
findRoute(method, targetPath) {
|
|
382
|
+
for (const metadata of this.routes) {
|
|
383
|
+
if (!methodsMatch(metadata.method, method)) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
const params = matchPath(metadata.path, targetPath);
|
|
387
|
+
if (params) {
|
|
388
|
+
return { metadata, params };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return void 0;
|
|
392
|
+
}
|
|
393
|
+
findWebSocketRoute(targetPath) {
|
|
394
|
+
for (const metadata of this.websocketRoutes ?? []) {
|
|
395
|
+
const params = matchPath(metadata.path, targetPath);
|
|
396
|
+
if (params) {
|
|
397
|
+
return { metadata, params };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return this.findRoute("GET", targetPath);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
function normalizeHeaderInput(headers) {
|
|
404
|
+
if (!headers) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
const normalized = {};
|
|
408
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
409
|
+
normalized[key] = String(value);
|
|
410
|
+
}
|
|
411
|
+
return normalized;
|
|
412
|
+
}
|
|
413
|
+
async function withStaticManifest(app) {
|
|
414
|
+
const config = app.config;
|
|
415
|
+
if (!config || !config.staticFiles || config.staticFiles.length === 0) {
|
|
416
|
+
return app;
|
|
417
|
+
}
|
|
418
|
+
if (Array.isArray(config.__wasmStaticManifest) && config.__wasmStaticManifest.length > 0) {
|
|
419
|
+
return app;
|
|
420
|
+
}
|
|
421
|
+
const manifest = await buildStaticManifest(config.staticFiles);
|
|
422
|
+
if (manifest.length === 0) {
|
|
423
|
+
return app;
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
...app,
|
|
427
|
+
config: {
|
|
428
|
+
...config,
|
|
429
|
+
__wasmStaticManifest: manifest
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function isNodeLikeEnvironment() {
|
|
434
|
+
const processValue = globalThis["process"];
|
|
435
|
+
if (!processValue || typeof processValue !== "object") {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
const versionsValue = processValue["versions"];
|
|
439
|
+
if (!versionsValue || typeof versionsValue !== "object") {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
return typeof versionsValue["node"] === "string";
|
|
443
|
+
}
|
|
444
|
+
function hasDeno() {
|
|
445
|
+
return typeof globalThis["Deno"] === "object";
|
|
446
|
+
}
|
|
447
|
+
function normalizeRoute(route2) {
|
|
448
|
+
let normalized = route2.replaceAll("\\", "/");
|
|
449
|
+
while (normalized.includes("//")) {
|
|
450
|
+
normalized = normalized.replaceAll("//", "/");
|
|
451
|
+
}
|
|
452
|
+
if (!normalized.startsWith("/")) {
|
|
453
|
+
normalized = `/${normalized}`;
|
|
454
|
+
}
|
|
455
|
+
return normalized;
|
|
456
|
+
}
|
|
457
|
+
function contentTypeForPath(path2) {
|
|
458
|
+
const lower = path2.toLowerCase();
|
|
459
|
+
if (lower.endsWith(".html")) {
|
|
460
|
+
return "text/html";
|
|
461
|
+
}
|
|
462
|
+
if (lower.endsWith(".txt")) {
|
|
463
|
+
return "text/plain";
|
|
464
|
+
}
|
|
465
|
+
if (lower.endsWith(".css")) {
|
|
466
|
+
return "text/css";
|
|
467
|
+
}
|
|
468
|
+
if (lower.endsWith(".js") || lower.endsWith(".mjs")) {
|
|
469
|
+
return "application/javascript";
|
|
470
|
+
}
|
|
471
|
+
if (lower.endsWith(".json")) {
|
|
472
|
+
return "application/json";
|
|
473
|
+
}
|
|
474
|
+
if (lower.endsWith(".svg")) {
|
|
475
|
+
return "image/svg+xml";
|
|
476
|
+
}
|
|
477
|
+
if (lower.endsWith(".png")) {
|
|
478
|
+
return "image/png";
|
|
479
|
+
}
|
|
480
|
+
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) {
|
|
481
|
+
return "image/jpeg";
|
|
482
|
+
}
|
|
483
|
+
return "application/octet-stream";
|
|
484
|
+
}
|
|
485
|
+
function buildStaticHeaders(filePath, cacheControl) {
|
|
486
|
+
const headers = {
|
|
487
|
+
"content-type": contentTypeForPath(filePath)
|
|
488
|
+
};
|
|
489
|
+
if (cacheControl) {
|
|
490
|
+
headers["cache-control"] = cacheControl;
|
|
491
|
+
}
|
|
492
|
+
return headers;
|
|
493
|
+
}
|
|
494
|
+
async function buildStaticManifest(configs) {
|
|
495
|
+
if (isNodeLikeEnvironment()) {
|
|
496
|
+
const fs2 = await import('fs');
|
|
497
|
+
const path2 = await import('path');
|
|
498
|
+
const manifest = [];
|
|
499
|
+
for (const config of configs) {
|
|
500
|
+
if (!config.directory || !config.routePrefix) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (!fs2.existsSync(config.directory)) {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const stack = [config.directory];
|
|
507
|
+
while (stack.length > 0) {
|
|
508
|
+
const current = stack.pop();
|
|
509
|
+
if (!current) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const stats = fs2.statSync(current);
|
|
513
|
+
if (stats.isDirectory()) {
|
|
514
|
+
for (const child of fs2.readdirSync(current)) {
|
|
515
|
+
stack.push(path2.join(current, child));
|
|
516
|
+
}
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
519
|
+
const relative = path2.relative(config.directory, current).split(path2.sep).join("/");
|
|
520
|
+
let prefix = config.routePrefix;
|
|
521
|
+
while (prefix.endsWith("/")) {
|
|
522
|
+
prefix = prefix.slice(0, -1);
|
|
523
|
+
}
|
|
524
|
+
const route2 = normalizeRoute(`${prefix}/${relative}`);
|
|
525
|
+
const headers = buildStaticHeaders(current, config.cacheControl ?? null);
|
|
526
|
+
const body = bufferToBase64(new Uint8Array(fs2.readFileSync(current)));
|
|
527
|
+
manifest.push({ route: route2, headers, body });
|
|
528
|
+
}
|
|
529
|
+
if (config.indexFile ?? true) {
|
|
530
|
+
const indexPath = path2.join(config.directory, "index.html");
|
|
531
|
+
if (fs2.existsSync(indexPath)) {
|
|
532
|
+
const headers = buildStaticHeaders(indexPath, config.cacheControl ?? null);
|
|
533
|
+
const body = bufferToBase64(new Uint8Array(fs2.readFileSync(indexPath)));
|
|
534
|
+
const prefix = normalizeRoute(config.routePrefix);
|
|
535
|
+
manifest.push({ route: prefix, headers: { ...headers }, body });
|
|
536
|
+
if (!prefix.endsWith("/")) {
|
|
537
|
+
manifest.push({ route: `${prefix}/`, headers: { ...headers }, body });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return manifest;
|
|
543
|
+
}
|
|
544
|
+
if (hasDeno()) {
|
|
545
|
+
const deno = globalThis["Deno"];
|
|
546
|
+
const manifest = [];
|
|
547
|
+
for (const config of configs) {
|
|
548
|
+
if (!config.directory || !config.routePrefix) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
const root = config.directory.replaceAll("\\", "/").replace(/\/+$/, "");
|
|
552
|
+
const stack = [root];
|
|
553
|
+
while (stack.length > 0) {
|
|
554
|
+
const current = stack.pop();
|
|
555
|
+
if (!current) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
for await (const entry of deno.readDir(current)) {
|
|
559
|
+
const entryPath = `${current}/${entry.name}`;
|
|
560
|
+
if (entry.isDirectory) {
|
|
561
|
+
stack.push(entryPath);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (!entry.isFile) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const relative = entryPath.startsWith(`${root}/`) ? entryPath.slice(root.length + 1) : entry.name;
|
|
568
|
+
let prefix = config.routePrefix;
|
|
569
|
+
while (prefix.endsWith("/")) {
|
|
570
|
+
prefix = prefix.slice(0, -1);
|
|
571
|
+
}
|
|
572
|
+
const route2 = normalizeRoute(`${prefix}/${relative}`);
|
|
573
|
+
const headers = buildStaticHeaders(entryPath, config.cacheControl ?? null);
|
|
574
|
+
const body = bufferToBase64(await deno.readFile(entryPath));
|
|
575
|
+
manifest.push({ route: route2, headers, body });
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (config.indexFile ?? true) {
|
|
579
|
+
const indexPath = `${root}/index.html`;
|
|
580
|
+
try {
|
|
581
|
+
const bytes = await deno.readFile(indexPath);
|
|
582
|
+
const headers = buildStaticHeaders(indexPath, config.cacheControl ?? null);
|
|
583
|
+
const body = bufferToBase64(bytes);
|
|
584
|
+
const prefix = normalizeRoute(config.routePrefix);
|
|
585
|
+
manifest.push({ route: prefix, headers: { ...headers }, body });
|
|
586
|
+
if (!prefix.endsWith("/")) {
|
|
587
|
+
manifest.push({ route: `${prefix}/`, headers: { ...headers }, body });
|
|
588
|
+
}
|
|
589
|
+
} catch {
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return manifest;
|
|
594
|
+
}
|
|
595
|
+
return [];
|
|
596
|
+
}
|
|
597
|
+
var WebSocketServerSocketImpl = class {
|
|
598
|
+
constructor(enqueue, onClose) {
|
|
599
|
+
this.enqueue = enqueue;
|
|
600
|
+
this.onClose = onClose;
|
|
601
|
+
}
|
|
602
|
+
closed = false;
|
|
603
|
+
async sendText(message) {
|
|
604
|
+
this.ensureOpen();
|
|
605
|
+
this.enqueue({ kind: "text", payload: message });
|
|
606
|
+
}
|
|
607
|
+
async sendJson(payload) {
|
|
608
|
+
this.ensureOpen();
|
|
609
|
+
this.enqueue({ kind: "json", payload });
|
|
610
|
+
}
|
|
611
|
+
async sendBytes(payload) {
|
|
612
|
+
this.ensureOpen();
|
|
613
|
+
this.enqueue({ kind: "binary", payload: toUint8Array(payload) });
|
|
614
|
+
}
|
|
615
|
+
async close(code, reason) {
|
|
616
|
+
if (this.closed) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
this.closed = true;
|
|
620
|
+
await this.onClose(code, reason);
|
|
621
|
+
}
|
|
622
|
+
async broadcast(payload) {
|
|
623
|
+
if (typeof payload === "string") {
|
|
624
|
+
await this.sendText(payload);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
await this.sendJson(payload);
|
|
628
|
+
}
|
|
629
|
+
isClosed() {
|
|
630
|
+
return this.closed;
|
|
631
|
+
}
|
|
632
|
+
ensureOpen() {
|
|
633
|
+
if (this.closed) {
|
|
634
|
+
throw new Error("WebSocket connection is closed");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
var WebSocketTestConnection = class _WebSocketTestConnection {
|
|
639
|
+
constructor(handler) {
|
|
640
|
+
this.handler = handler;
|
|
641
|
+
this.socket = new WebSocketServerSocketImpl(
|
|
642
|
+
(message) => {
|
|
643
|
+
this.pending.push(message);
|
|
644
|
+
},
|
|
645
|
+
async (code, reason) => {
|
|
646
|
+
this.closed = true;
|
|
647
|
+
if (isWebSocketHandler(this.handler) && this.handler.onClose) {
|
|
648
|
+
await this.handler.onClose(this.socket, code, reason);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
pending = [];
|
|
654
|
+
socket;
|
|
655
|
+
closed = false;
|
|
656
|
+
static async connect(handler) {
|
|
657
|
+
const connection = new _WebSocketTestConnection(handler);
|
|
658
|
+
if (isWebSocketHandler(handler) && handler.onOpen) {
|
|
659
|
+
await handler.onOpen(connection.socket);
|
|
660
|
+
}
|
|
661
|
+
return connection;
|
|
662
|
+
}
|
|
663
|
+
async sendJson(payload) {
|
|
664
|
+
this.ensureOpen();
|
|
665
|
+
await this.dispatchMessage(payload);
|
|
666
|
+
}
|
|
667
|
+
async sendText(payload) {
|
|
668
|
+
this.ensureOpen();
|
|
669
|
+
await this.dispatchMessage(payload);
|
|
670
|
+
}
|
|
671
|
+
async sendBytes(payload) {
|
|
672
|
+
this.ensureOpen();
|
|
673
|
+
await this.dispatchMessage(payload);
|
|
674
|
+
}
|
|
675
|
+
async receiveJson() {
|
|
676
|
+
const message = this.shiftMessage();
|
|
677
|
+
if (message.kind === "json") {
|
|
678
|
+
return message.payload;
|
|
679
|
+
}
|
|
680
|
+
if (message.kind === "binary") {
|
|
681
|
+
const text = textDecoder.decode(message.payload);
|
|
682
|
+
const parsed2 = safeParseJson(text);
|
|
683
|
+
if (parsed2 === null) {
|
|
684
|
+
throw new Error("WebSocket binary message is not valid JSON");
|
|
685
|
+
}
|
|
686
|
+
return parsed2;
|
|
687
|
+
}
|
|
688
|
+
const parsed = safeParseJson(message.payload);
|
|
689
|
+
if (parsed === null) {
|
|
690
|
+
throw new Error("WebSocket text message is not valid JSON");
|
|
691
|
+
}
|
|
692
|
+
return parsed;
|
|
693
|
+
}
|
|
694
|
+
async receiveText() {
|
|
695
|
+
const message = this.shiftMessage();
|
|
696
|
+
if (message.kind === "text") {
|
|
697
|
+
return message.payload;
|
|
698
|
+
}
|
|
699
|
+
if (message.kind === "binary") {
|
|
700
|
+
return textDecoder.decode(message.payload);
|
|
701
|
+
}
|
|
702
|
+
return JSON.stringify(message.payload);
|
|
703
|
+
}
|
|
704
|
+
async receiveBytes() {
|
|
705
|
+
const message = this.shiftMessage();
|
|
706
|
+
if (message.kind === "binary") {
|
|
707
|
+
return message.payload;
|
|
708
|
+
}
|
|
709
|
+
if (message.kind === "text") {
|
|
710
|
+
return textEncoder.encode(message.payload);
|
|
711
|
+
}
|
|
712
|
+
return textEncoder.encode(JSON.stringify(message.payload));
|
|
713
|
+
}
|
|
714
|
+
async close(code, reason) {
|
|
715
|
+
if (this.closed) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
this.closed = true;
|
|
719
|
+
this.pending.length = 0;
|
|
720
|
+
await this.socket.close(code, reason);
|
|
721
|
+
}
|
|
722
|
+
async dispatchMessage(payload) {
|
|
723
|
+
const result = await (isWebSocketHandler(this.handler) ? this.handler.onMessage?.(this.socket, payload) : this.handler(payload));
|
|
724
|
+
if (result === void 0) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (isStreamingResponse(result)) {
|
|
728
|
+
throw new Error("WebSocket handlers cannot return streaming responses");
|
|
729
|
+
}
|
|
730
|
+
const message = normalizeWebSocketResult(result);
|
|
731
|
+
if (message) {
|
|
732
|
+
this.pending.push(message);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
shiftMessage() {
|
|
736
|
+
if (this.pending.length === 0) {
|
|
737
|
+
throw new Error("No WebSocket messages available");
|
|
738
|
+
}
|
|
739
|
+
const message = this.pending.shift();
|
|
740
|
+
if (!message) {
|
|
741
|
+
throw new Error("No WebSocket messages available");
|
|
742
|
+
}
|
|
743
|
+
return message;
|
|
744
|
+
}
|
|
745
|
+
ensureOpen() {
|
|
746
|
+
if (this.closed) {
|
|
747
|
+
throw new Error("WebSocket connection is closed");
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function isWebSocketHandler(handler) {
|
|
752
|
+
if (typeof handler !== "object" || handler === null) {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
const candidate = handler;
|
|
756
|
+
return typeof candidate.onMessage === "function" || typeof candidate.onOpen === "function" || typeof candidate.onClose === "function";
|
|
757
|
+
}
|
|
758
|
+
function normalizeWebSocketResult(result) {
|
|
759
|
+
if (result === null || result === void 0) {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
if (typeof result === "string") {
|
|
763
|
+
const parsed = safeParseJson(result);
|
|
764
|
+
if (parsed === null) {
|
|
765
|
+
return { kind: "text", payload: result };
|
|
766
|
+
}
|
|
767
|
+
return { kind: "json", payload: parsed };
|
|
768
|
+
}
|
|
769
|
+
if (isBinaryLike(result)) {
|
|
770
|
+
return { kind: "binary", payload: toUint8Array(result) };
|
|
771
|
+
}
|
|
772
|
+
return { kind: "json", payload: result };
|
|
773
|
+
}
|
|
774
|
+
function safeParseJson(text) {
|
|
775
|
+
try {
|
|
776
|
+
return JSON.parse(text);
|
|
777
|
+
} catch {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function isBinaryLike(value) {
|
|
782
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
783
|
+
}
|
|
784
|
+
function normalizeRecord(record) {
|
|
785
|
+
if (!record) {
|
|
786
|
+
return {};
|
|
787
|
+
}
|
|
788
|
+
if (isMapLike(record)) {
|
|
789
|
+
return Object.fromEntries(record.entries());
|
|
790
|
+
}
|
|
791
|
+
return record;
|
|
792
|
+
}
|
|
793
|
+
function normalizeValueRecord(record) {
|
|
794
|
+
if (!record) {
|
|
795
|
+
return {};
|
|
796
|
+
}
|
|
797
|
+
if (record instanceof Map) {
|
|
798
|
+
return Object.fromEntries(record.entries());
|
|
799
|
+
}
|
|
800
|
+
return record;
|
|
801
|
+
}
|
|
802
|
+
function isMapLike(record) {
|
|
803
|
+
return Boolean(record) && record instanceof Map;
|
|
804
|
+
}
|
|
805
|
+
function normalizeLifecycleHooks(hooks) {
|
|
806
|
+
if (!hooks) {
|
|
807
|
+
return EMPTY_HOOKS;
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
onRequest: [...hooks.onRequest ?? []],
|
|
811
|
+
preValidation: [...hooks.preValidation ?? []],
|
|
812
|
+
preHandler: [...hooks.preHandler ?? []],
|
|
813
|
+
onResponse: [...hooks.onResponse ?? []],
|
|
814
|
+
onError: [...hooks.onError ?? []]
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function hasLifecycleHooks(hooks) {
|
|
818
|
+
return hooks.onRequest.length > 0 || hooks.preValidation.length > 0 || hooks.preHandler.length > 0 || hooks.onResponse.length > 0 || hooks.onError.length > 0;
|
|
819
|
+
}
|
|
820
|
+
function createNativeLifecycleHooks(hooks) {
|
|
821
|
+
if (!hasLifecycleHooks(hooks)) {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
onRequest: hooks.onRequest.map((hook) => wrapNativeRequestHook(hook)),
|
|
826
|
+
preValidation: hooks.preValidation.map((hook) => wrapNativeRequestHook(hook)),
|
|
827
|
+
preHandler: hooks.preHandler.map((hook) => wrapNativeRequestHook(hook)),
|
|
828
|
+
onResponse: hooks.onResponse.map((hook) => wrapNativeResponseHook(hook)),
|
|
829
|
+
onError: hooks.onError.map((hook) => wrapNativeResponseHook(hook))
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function wrapNativeRequestHook(hook) {
|
|
833
|
+
return async (payload) => {
|
|
834
|
+
const wrapped = maybeWrapRequestPayload(payload);
|
|
835
|
+
if (!isWasmRequest(wrapped)) {
|
|
836
|
+
throw new Error("Invalid lifecycle request payload");
|
|
837
|
+
}
|
|
838
|
+
const request = wrapped;
|
|
839
|
+
const result = await hook(request);
|
|
840
|
+
const maybeRequest = ensureWasmRequest(result);
|
|
841
|
+
if (maybeRequest) {
|
|
842
|
+
return { type: "request", payload: serializeWasmRequest(maybeRequest) };
|
|
843
|
+
}
|
|
844
|
+
const response = await normalizeStructuredResponse(result);
|
|
845
|
+
return { type: "response", payload: response };
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function wrapNativeResponseHook(hook) {
|
|
849
|
+
return async (payload) => {
|
|
850
|
+
const current = await normalizeStructuredResponse(payload);
|
|
851
|
+
const result = await hook(current);
|
|
852
|
+
const maybeRequest = ensureWasmRequest(result);
|
|
853
|
+
if (maybeRequest) {
|
|
854
|
+
return { type: "response", payload: current };
|
|
855
|
+
}
|
|
856
|
+
const updated = await normalizeStructuredResponse(result, current);
|
|
857
|
+
return { type: "response", payload: updated };
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function serializeWasmRequest(request) {
|
|
861
|
+
return request.toPayload();
|
|
862
|
+
}
|
|
863
|
+
function normalizeJsonValue(value) {
|
|
864
|
+
if (value instanceof Map) {
|
|
865
|
+
const result = {};
|
|
866
|
+
for (const [key, entry] of value.entries()) {
|
|
867
|
+
result[key] = normalizeJsonValue(entry);
|
|
868
|
+
}
|
|
869
|
+
return result;
|
|
870
|
+
}
|
|
871
|
+
if (Array.isArray(value)) {
|
|
872
|
+
return value.map((entry) => normalizeJsonValue(entry));
|
|
873
|
+
}
|
|
874
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
875
|
+
return value;
|
|
876
|
+
}
|
|
877
|
+
if (typeof value === "bigint") {
|
|
878
|
+
return value.toString();
|
|
879
|
+
}
|
|
880
|
+
if (typeof value === "object" && value) {
|
|
881
|
+
const result = {};
|
|
882
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
883
|
+
result[key] = normalizeJsonValue(entry);
|
|
884
|
+
}
|
|
885
|
+
return result;
|
|
886
|
+
}
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
function lowerCaseHeaderKeys(headers) {
|
|
890
|
+
const lowered = {};
|
|
891
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
892
|
+
lowered[key.toLowerCase()] = value;
|
|
893
|
+
}
|
|
894
|
+
return lowered;
|
|
895
|
+
}
|
|
896
|
+
function tryUnwrapStructuredSnapshot(body) {
|
|
897
|
+
if (body.length === 0) {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
const text = textDecoder.decode(body);
|
|
901
|
+
try {
|
|
902
|
+
const parsed = JSON.parse(text);
|
|
903
|
+
if (!isStructuredResponseLike(parsed)) {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
return parsed;
|
|
907
|
+
} catch {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function flattenStructuredResponse(response) {
|
|
912
|
+
let current = response;
|
|
913
|
+
let mergedHeaders = normalizeRecord(current.headers ?? {});
|
|
914
|
+
while (current.body && typeof current.body === "object" && isStructuredResponseLike(current.body)) {
|
|
915
|
+
const next = current.body;
|
|
916
|
+
mergedHeaders = {
|
|
917
|
+
...mergedHeaders,
|
|
918
|
+
...normalizeRecord(next.headers ?? {})
|
|
919
|
+
};
|
|
920
|
+
current = next;
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
...current,
|
|
924
|
+
headers: mergedHeaders
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function encodeBodyBytes(body) {
|
|
928
|
+
if (body == null) {
|
|
929
|
+
return new Uint8Array();
|
|
930
|
+
}
|
|
931
|
+
if (typeof body === "string") {
|
|
932
|
+
return textEncoder.encode(body);
|
|
933
|
+
}
|
|
934
|
+
return textEncoder.encode(JSON.stringify(body));
|
|
935
|
+
}
|
|
936
|
+
function toUint8Array(value) {
|
|
937
|
+
if (value instanceof Uint8Array) {
|
|
938
|
+
return value;
|
|
939
|
+
}
|
|
940
|
+
if (value instanceof ArrayBuffer) {
|
|
941
|
+
return new Uint8Array(value);
|
|
942
|
+
}
|
|
943
|
+
if (ArrayBuffer.isView(value)) {
|
|
944
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
945
|
+
}
|
|
946
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) {
|
|
947
|
+
throw new Error("Blob payloads are not supported synchronously");
|
|
948
|
+
}
|
|
949
|
+
if (isArrayLike(value)) {
|
|
950
|
+
return Uint8Array.from(value);
|
|
951
|
+
}
|
|
952
|
+
throw new Error("Unsupported binary payload");
|
|
953
|
+
}
|
|
954
|
+
function isArrayLike(value) {
|
|
955
|
+
if (typeof value === "object" && value !== null) {
|
|
956
|
+
const length = value.length;
|
|
957
|
+
return typeof length === "number";
|
|
958
|
+
}
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
function gunzipBytes(data) {
|
|
962
|
+
if (gunzipImplementation) {
|
|
963
|
+
try {
|
|
964
|
+
return gunzipImplementation(data);
|
|
965
|
+
} catch {
|
|
966
|
+
return data;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
try {
|
|
970
|
+
return fflate.gunzipSync(data);
|
|
971
|
+
} catch {
|
|
972
|
+
return data;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
function bufferToBase64(bytes) {
|
|
976
|
+
const bufferCtor = globalAny.Buffer;
|
|
977
|
+
if (bufferCtor) {
|
|
978
|
+
return bufferCtor.from(bytes).toString("base64");
|
|
979
|
+
}
|
|
980
|
+
let binary = "";
|
|
981
|
+
for (const byte of bytes) {
|
|
982
|
+
binary += String.fromCharCode(byte);
|
|
983
|
+
}
|
|
984
|
+
if (typeof btoa === "function") {
|
|
985
|
+
return btoa(binary);
|
|
986
|
+
}
|
|
987
|
+
throw new Error("Base64 encoding is not supported in this environment");
|
|
988
|
+
}
|
|
989
|
+
function isAbortSignalLike(value) {
|
|
990
|
+
if (!value || typeof value !== "object") {
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
const candidate = value;
|
|
994
|
+
return typeof candidate.aborted === "boolean" && typeof candidate.addEventListener === "function" && typeof candidate.removeEventListener === "function";
|
|
995
|
+
}
|
|
996
|
+
function wrapHandlers(handlers) {
|
|
997
|
+
const wrapped = {};
|
|
998
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
999
|
+
const looksLikeStringHandler = handler.toString().includes("JSON.parse");
|
|
1000
|
+
wrapped[name] = async (request) => {
|
|
1001
|
+
const rawJson = request && typeof request === "object" && RAW_REQUEST_KEY in request ? String(request[RAW_REQUEST_KEY] ?? "") : JSON.stringify(request);
|
|
1002
|
+
const maybeSignal = request && typeof request === "object" && ABORT_SIGNAL_KEY in request ? request[ABORT_SIGNAL_KEY] : void 0;
|
|
1003
|
+
const context = isAbortSignalLike(maybeSignal) ? { signal: maybeSignal } : void 0;
|
|
1004
|
+
const normalizedRequest = maybeWrapRequestPayload(request);
|
|
1005
|
+
return looksLikeStringHandler ? handler(rawJson, context) : handler(normalizedRequest, context);
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
return wrapped;
|
|
1009
|
+
}
|
|
1010
|
+
var WasmRequest = class {
|
|
1011
|
+
method;
|
|
1012
|
+
path;
|
|
1013
|
+
queryString;
|
|
1014
|
+
headers;
|
|
1015
|
+
params;
|
|
1016
|
+
pathParams;
|
|
1017
|
+
query;
|
|
1018
|
+
files;
|
|
1019
|
+
rawJson;
|
|
1020
|
+
bodyKind;
|
|
1021
|
+
bodyJson;
|
|
1022
|
+
formData;
|
|
1023
|
+
textBody;
|
|
1024
|
+
bodyMetadata = null;
|
|
1025
|
+
bodyDirty = false;
|
|
1026
|
+
_body;
|
|
1027
|
+
constructor(payload, rawJson) {
|
|
1028
|
+
this.rawJson = rawJson;
|
|
1029
|
+
this.method = payload.method;
|
|
1030
|
+
this.path = payload.path;
|
|
1031
|
+
this.params = normalizeValueRecord(payload.params);
|
|
1032
|
+
this.pathParams = normalizeValueRecord(payload.pathParams);
|
|
1033
|
+
this.query = normalizeValueRecord(payload.query);
|
|
1034
|
+
this.headers = normalizeRecord(payload.headers);
|
|
1035
|
+
this.queryString = buildQueryString(payload.rawQuery);
|
|
1036
|
+
const normalizedBody = payload.body == null ? null : normalizeJsonValue(payload.body);
|
|
1037
|
+
let metadata = payload.__spikard_body_metadata__ ?? null;
|
|
1038
|
+
if ((!metadata || metadata.kind === "none") && normalizedBody != null) {
|
|
1039
|
+
metadata = buildBodyMetadata(normalizedBody);
|
|
1040
|
+
}
|
|
1041
|
+
if (metadata) {
|
|
1042
|
+
metadata.kind = normalizeRequestBodyKind(metadata.kind);
|
|
1043
|
+
}
|
|
1044
|
+
this.bodyMetadata = metadata ?? null;
|
|
1045
|
+
const applied = applyBodyMetadata(this.bodyMetadata, normalizedBody);
|
|
1046
|
+
this.bodyKind = applied.kind;
|
|
1047
|
+
this.bodyJson = applied.jsonValue;
|
|
1048
|
+
this.formData = applied.formValue;
|
|
1049
|
+
this.textBody = applied.textValue;
|
|
1050
|
+
if (applied.files !== void 0) {
|
|
1051
|
+
this.files = applied.files;
|
|
1052
|
+
}
|
|
1053
|
+
this._body = applied.buffer;
|
|
1054
|
+
}
|
|
1055
|
+
get body() {
|
|
1056
|
+
return this._body;
|
|
1057
|
+
}
|
|
1058
|
+
set body(value) {
|
|
1059
|
+
this._body = value;
|
|
1060
|
+
this.bodyDirty = true;
|
|
1061
|
+
}
|
|
1062
|
+
json() {
|
|
1063
|
+
let value;
|
|
1064
|
+
if (this.bodyKind === "json" && this.bodyJson !== void 0) {
|
|
1065
|
+
value = this.bodyJson;
|
|
1066
|
+
} else if (this.bodyKind === "text" && this.textBody !== void 0) {
|
|
1067
|
+
try {
|
|
1068
|
+
value = JSON.parse(this.textBody);
|
|
1069
|
+
} catch {
|
|
1070
|
+
throw new Error("Request body is not valid JSON");
|
|
1071
|
+
}
|
|
1072
|
+
} else {
|
|
1073
|
+
throw new Error("Request body is not JSON");
|
|
1074
|
+
}
|
|
1075
|
+
return normalizeJsonValue(value);
|
|
1076
|
+
}
|
|
1077
|
+
form() {
|
|
1078
|
+
if (this.formData) {
|
|
1079
|
+
return { ...this.formData };
|
|
1080
|
+
}
|
|
1081
|
+
throw new Error("Request body is not form data");
|
|
1082
|
+
}
|
|
1083
|
+
toPayload() {
|
|
1084
|
+
const bodyValue = this.buildBodyValue();
|
|
1085
|
+
const metadata = this.getOrBuildBodyMetadata(bodyValue);
|
|
1086
|
+
const payload = {
|
|
1087
|
+
method: this.method,
|
|
1088
|
+
path: this.path,
|
|
1089
|
+
pathParams: { ...this.pathParams },
|
|
1090
|
+
params: { ...this.params },
|
|
1091
|
+
query: { ...this.query },
|
|
1092
|
+
rawQuery: buildRawQueryMap(this.queryString),
|
|
1093
|
+
headers: { ...this.headers },
|
|
1094
|
+
cookies: {},
|
|
1095
|
+
body: bodyValue
|
|
1096
|
+
};
|
|
1097
|
+
if (metadata) {
|
|
1098
|
+
payload.__spikard_body_metadata__ = metadata;
|
|
1099
|
+
}
|
|
1100
|
+
return payload;
|
|
1101
|
+
}
|
|
1102
|
+
buildBodyValue() {
|
|
1103
|
+
const currentBody = this.body;
|
|
1104
|
+
if (!currentBody) {
|
|
1105
|
+
switch (this.bodyKind) {
|
|
1106
|
+
case "json":
|
|
1107
|
+
return this.bodyJson ?? null;
|
|
1108
|
+
case "text":
|
|
1109
|
+
return this.textBody ?? null;
|
|
1110
|
+
case "form":
|
|
1111
|
+
return this.formData ? { __spikard_form__: this.formData } : null;
|
|
1112
|
+
case "multipart":
|
|
1113
|
+
return this.formData || this.files ? {
|
|
1114
|
+
__spikard_multipart__: {
|
|
1115
|
+
fields: this.formData ?? {},
|
|
1116
|
+
files: this.files ?? []
|
|
1117
|
+
}
|
|
1118
|
+
} : null;
|
|
1119
|
+
case "binary":
|
|
1120
|
+
if (this.bodyMetadata?.rawBase64) {
|
|
1121
|
+
return { __spikard_base64__: this.bodyMetadata.rawBase64 };
|
|
1122
|
+
}
|
|
1123
|
+
return null;
|
|
1124
|
+
default:
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (currentBody instanceof Uint8Array) {
|
|
1129
|
+
return { __spikard_base64__: bufferToBase64(currentBody) };
|
|
1130
|
+
}
|
|
1131
|
+
return normalizeJsonValue(currentBody);
|
|
1132
|
+
}
|
|
1133
|
+
getOrBuildBodyMetadata(bodyValue) {
|
|
1134
|
+
if (!this.bodyDirty && this.bodyMetadata) {
|
|
1135
|
+
return this.bodyMetadata;
|
|
1136
|
+
}
|
|
1137
|
+
const metadata = buildBodyMetadata(bodyValue ?? null);
|
|
1138
|
+
if (metadata) {
|
|
1139
|
+
metadata.kind = normalizeRequestBodyKind(metadata.kind);
|
|
1140
|
+
}
|
|
1141
|
+
this.bodyMetadata = metadata;
|
|
1142
|
+
this.bodyDirty = false;
|
|
1143
|
+
return metadata;
|
|
1144
|
+
}
|
|
1145
|
+
toString() {
|
|
1146
|
+
return this.rawJson;
|
|
1147
|
+
}
|
|
1148
|
+
valueOf() {
|
|
1149
|
+
return this.rawJson;
|
|
1150
|
+
}
|
|
1151
|
+
[Symbol.toPrimitive]() {
|
|
1152
|
+
return this.rawJson;
|
|
1153
|
+
}
|
|
1154
|
+
toJSON() {
|
|
1155
|
+
try {
|
|
1156
|
+
return JSON.parse(this.rawJson);
|
|
1157
|
+
} catch {
|
|
1158
|
+
return {
|
|
1159
|
+
method: this.method,
|
|
1160
|
+
path: this.path
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
function isWasmRequest(value) {
|
|
1166
|
+
return value instanceof WasmRequest;
|
|
1167
|
+
}
|
|
1168
|
+
function maybeWrapRequestPayload(payload) {
|
|
1169
|
+
if (!payload || typeof payload !== "object") {
|
|
1170
|
+
return payload;
|
|
1171
|
+
}
|
|
1172
|
+
if (!("method" in payload) || !("path" in payload)) {
|
|
1173
|
+
return payload;
|
|
1174
|
+
}
|
|
1175
|
+
const candidate = payload;
|
|
1176
|
+
const rawJson = typeof candidate[RAW_REQUEST_KEY] === "string" ? candidate[RAW_REQUEST_KEY] : JSON.stringify(candidate);
|
|
1177
|
+
return new WasmRequest(candidate, rawJson);
|
|
1178
|
+
}
|
|
1179
|
+
function buildQueryString(raw) {
|
|
1180
|
+
if (!raw) {
|
|
1181
|
+
return "";
|
|
1182
|
+
}
|
|
1183
|
+
const queryRecord = raw instanceof Map ? Object.fromEntries(raw.entries()) : raw;
|
|
1184
|
+
const params = new URLSearchParams();
|
|
1185
|
+
for (const [key, values] of Object.entries(queryRecord)) {
|
|
1186
|
+
for (const value of values ?? []) {
|
|
1187
|
+
params.append(key, value);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return params.toString();
|
|
1191
|
+
}
|
|
1192
|
+
function buildRawQueryMap(queryString) {
|
|
1193
|
+
if (!queryString) {
|
|
1194
|
+
return {};
|
|
1195
|
+
}
|
|
1196
|
+
const params = new URLSearchParams(queryString);
|
|
1197
|
+
const record = {};
|
|
1198
|
+
for (const [key, value] of params.entries()) {
|
|
1199
|
+
if (!record[key]) {
|
|
1200
|
+
record[key] = [];
|
|
1201
|
+
}
|
|
1202
|
+
record[key].push(value);
|
|
1203
|
+
}
|
|
1204
|
+
return record;
|
|
1205
|
+
}
|
|
1206
|
+
function base64ToUint8Array(base64) {
|
|
1207
|
+
const bufferCtor = globalAny.Buffer;
|
|
1208
|
+
if (bufferCtor) {
|
|
1209
|
+
const buf = bufferCtor.from(base64, "base64");
|
|
1210
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof atob === "function") {
|
|
1213
|
+
const binary = atob(base64);
|
|
1214
|
+
const bytes = new Uint8Array(binary.length);
|
|
1215
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
1216
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1217
|
+
}
|
|
1218
|
+
return bytes;
|
|
1219
|
+
}
|
|
1220
|
+
throw new Error("Base64 decoding is not supported in this environment");
|
|
1221
|
+
}
|
|
1222
|
+
function decodeFormValues(values) {
|
|
1223
|
+
const form = {};
|
|
1224
|
+
for (const [key, value] of Object.entries(values)) {
|
|
1225
|
+
if (value == null) {
|
|
1226
|
+
form[key] = "";
|
|
1227
|
+
} else if (typeof value === "string") {
|
|
1228
|
+
form[key] = value;
|
|
1229
|
+
} else {
|
|
1230
|
+
form[key] = JSON.stringify(value);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return form;
|
|
1234
|
+
}
|
|
1235
|
+
function buildBuffer(bytes) {
|
|
1236
|
+
if (!bytes) {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
const bufferCtor = globalAny.Buffer;
|
|
1240
|
+
if (bufferCtor) {
|
|
1241
|
+
const buf = bufferCtor.from(bytes);
|
|
1242
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
1243
|
+
}
|
|
1244
|
+
return bytes;
|
|
1245
|
+
}
|
|
1246
|
+
function applyBodyMetadata(metadata, payloadBody) {
|
|
1247
|
+
const kind = normalizeRequestBodyKind(metadata?.kind);
|
|
1248
|
+
let buffer = null;
|
|
1249
|
+
if (metadata?.rawBase64) {
|
|
1250
|
+
buffer = buildBuffer(base64ToUint8Array(metadata.rawBase64));
|
|
1251
|
+
}
|
|
1252
|
+
let jsonValue;
|
|
1253
|
+
let formValue;
|
|
1254
|
+
let textValue;
|
|
1255
|
+
const files = metadata?.files;
|
|
1256
|
+
switch (kind) {
|
|
1257
|
+
case "json":
|
|
1258
|
+
jsonValue = metadata?.json ?? payloadBody;
|
|
1259
|
+
if (!buffer && payloadBody != null) {
|
|
1260
|
+
const text = JSON.stringify(payloadBody);
|
|
1261
|
+
buffer = buildBuffer(textEncoder.encode(text));
|
|
1262
|
+
}
|
|
1263
|
+
break;
|
|
1264
|
+
case "form":
|
|
1265
|
+
case "multipart":
|
|
1266
|
+
formValue = metadata?.form ?? void 0;
|
|
1267
|
+
break;
|
|
1268
|
+
case "text":
|
|
1269
|
+
textValue = metadata?.text ?? void 0;
|
|
1270
|
+
if (!buffer && textValue !== void 0) {
|
|
1271
|
+
buffer = buildBuffer(textEncoder.encode(textValue));
|
|
1272
|
+
}
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
kind,
|
|
1277
|
+
buffer,
|
|
1278
|
+
jsonValue,
|
|
1279
|
+
formValue,
|
|
1280
|
+
textValue,
|
|
1281
|
+
files
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function buildBodyMetadata(body) {
|
|
1285
|
+
if (body == null) {
|
|
1286
|
+
return { kind: "none" };
|
|
1287
|
+
}
|
|
1288
|
+
if (typeof body === "string") {
|
|
1289
|
+
const bytes = textEncoder.encode(body);
|
|
1290
|
+
return {
|
|
1291
|
+
kind: "text",
|
|
1292
|
+
text: body,
|
|
1293
|
+
rawBase64: bufferToBase64(bytes)
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
if (typeof body === "number" || typeof body === "boolean") {
|
|
1297
|
+
const text = JSON.stringify(body);
|
|
1298
|
+
return {
|
|
1299
|
+
kind: "json",
|
|
1300
|
+
json: body,
|
|
1301
|
+
rawBase64: bufferToBase64(textEncoder.encode(text))
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
if (typeof body === "object") {
|
|
1305
|
+
const payload = body;
|
|
1306
|
+
if (typeof payload.__spikard_base64__ === "string") {
|
|
1307
|
+
return {
|
|
1308
|
+
kind: "binary",
|
|
1309
|
+
rawBase64: payload.__spikard_base64__
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
if (payload.__spikard_form__ && typeof payload.__spikard_form__ === "object") {
|
|
1313
|
+
const formSource = normalizeValueRecord(
|
|
1314
|
+
payload.__spikard_form__
|
|
1315
|
+
);
|
|
1316
|
+
const formValue = decodeFormValues(formSource);
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "form",
|
|
1319
|
+
form: formValue,
|
|
1320
|
+
rawBase64: bufferToBase64(textEncoder.encode(new URLSearchParams(formValue).toString()))
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
if (payload.__spikard_multipart__ && typeof payload.__spikard_multipart__ === "object") {
|
|
1324
|
+
const multipart = payload.__spikard_multipart__;
|
|
1325
|
+
const fields = normalizeValueRecord(multipart.fields);
|
|
1326
|
+
const formValue = decodeFormValues(fields);
|
|
1327
|
+
return {
|
|
1328
|
+
kind: "multipart",
|
|
1329
|
+
form: formValue,
|
|
1330
|
+
files: multipart.files ?? [],
|
|
1331
|
+
rawBase64: bufferToBase64(textEncoder.encode(new URLSearchParams(formValue).toString()))
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
return {
|
|
1335
|
+
kind: "json",
|
|
1336
|
+
json: normalizeJsonValue(payload),
|
|
1337
|
+
rawBase64: bufferToBase64(textEncoder.encode(JSON.stringify(payload)))
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
kind: "json",
|
|
1342
|
+
json: body,
|
|
1343
|
+
rawBase64: bufferToBase64(textEncoder.encode(JSON.stringify(body)))
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
function normalizeRequestBodyKind(value) {
|
|
1347
|
+
switch ((value ?? "none").toLowerCase()) {
|
|
1348
|
+
case "json":
|
|
1349
|
+
return "json";
|
|
1350
|
+
case "form":
|
|
1351
|
+
return "form";
|
|
1352
|
+
case "multipart":
|
|
1353
|
+
return "multipart";
|
|
1354
|
+
case "binary":
|
|
1355
|
+
return "binary";
|
|
1356
|
+
case "text":
|
|
1357
|
+
return "text";
|
|
1358
|
+
default:
|
|
1359
|
+
return "none";
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function ensureWasmRequest(value) {
|
|
1363
|
+
if (value instanceof WasmRequest) {
|
|
1364
|
+
return value;
|
|
1365
|
+
}
|
|
1366
|
+
if (value && typeof value === "object" && "method" in value && "path" in value) {
|
|
1367
|
+
const rawJson = typeof value[RAW_REQUEST_KEY] === "string" ? value[RAW_REQUEST_KEY] : JSON.stringify(value);
|
|
1368
|
+
return new WasmRequest(value, rawJson);
|
|
1369
|
+
}
|
|
1370
|
+
return null;
|
|
1371
|
+
}
|
|
1372
|
+
async function normalizeStructuredResponse(value, defaultResponse) {
|
|
1373
|
+
if (typeof value === "string") {
|
|
1374
|
+
try {
|
|
1375
|
+
const parsed = JSON.parse(value);
|
|
1376
|
+
return normalizeStructuredResponse(parsed);
|
|
1377
|
+
} catch {
|
|
1378
|
+
return {
|
|
1379
|
+
status: 200,
|
|
1380
|
+
headers: {},
|
|
1381
|
+
body: value
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (value instanceof WasmRequest) {
|
|
1386
|
+
return defaultResponse ?? {
|
|
1387
|
+
status: 200,
|
|
1388
|
+
headers: {},
|
|
1389
|
+
body: null
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
if (isStructuredResponseLike(value)) {
|
|
1393
|
+
const headers = normalizeRecord(value.headers ?? {});
|
|
1394
|
+
const status = value.status ?? value.statusCode ?? 200;
|
|
1395
|
+
const body = value.body === void 0 ? null : typeof value.body === "object" && value.body !== null ? value.body : value.body;
|
|
1396
|
+
return {
|
|
1397
|
+
status,
|
|
1398
|
+
headers,
|
|
1399
|
+
body
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
status: 200,
|
|
1404
|
+
headers: {},
|
|
1405
|
+
body: value == null ? null : value
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function isStructuredResponseLike(value) {
|
|
1409
|
+
if (!value || typeof value !== "object") {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
const hasStatus = "status" in value || "statusCode" in value;
|
|
1413
|
+
if (!hasStatus) {
|
|
1414
|
+
return false;
|
|
1415
|
+
}
|
|
1416
|
+
return "headers" in value || "body" in value;
|
|
1417
|
+
}
|
|
1418
|
+
function matchPath(template, actual) {
|
|
1419
|
+
const params = {};
|
|
1420
|
+
const actualPath = actual.split("?")[0] ?? actual;
|
|
1421
|
+
const templateSegments = template.split("/").filter(Boolean);
|
|
1422
|
+
const actualSegments = actualPath.split("/").filter(Boolean);
|
|
1423
|
+
if (templateSegments.length !== actualSegments.length) {
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
for (let i = 0; i < templateSegments.length; i += 1) {
|
|
1427
|
+
const templateSegment = templateSegments[i];
|
|
1428
|
+
const actualSegment = actualSegments[i];
|
|
1429
|
+
if (templateSegment === void 0 || actualSegment === void 0) {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
if (templateSegment.startsWith("{") && templateSegment.endsWith("}")) {
|
|
1433
|
+
const key = templateSegment.slice(1, -1);
|
|
1434
|
+
params[key] = decodeURIComponent(actualSegment);
|
|
1435
|
+
} else if (templateSegment !== actualSegment) {
|
|
1436
|
+
return null;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return params;
|
|
1440
|
+
}
|
|
1441
|
+
function methodsMatch(routeMethod, actualMethod) {
|
|
1442
|
+
return routeMethod.toUpperCase() === actualMethod.toUpperCase();
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/server.ts
|
|
1446
|
+
function createFetchHandler(app) {
|
|
1447
|
+
const client = new TestClient(app);
|
|
1448
|
+
return async (request) => {
|
|
1449
|
+
const websocketResponse = await maybeHandleWebSocketUpgrade(app, request);
|
|
1450
|
+
if (websocketResponse) {
|
|
1451
|
+
return websocketResponse;
|
|
1452
|
+
}
|
|
1453
|
+
const url = new URL(request.url);
|
|
1454
|
+
const path2 = `${url.pathname}${url.search}`;
|
|
1455
|
+
const headers = headersToRecord(request.headers);
|
|
1456
|
+
const method = request.method.toUpperCase();
|
|
1457
|
+
const options = await buildRequestOptions(request);
|
|
1458
|
+
let response;
|
|
1459
|
+
switch (method) {
|
|
1460
|
+
case "GET":
|
|
1461
|
+
response = await client.get(path2, headers);
|
|
1462
|
+
break;
|
|
1463
|
+
case "DELETE":
|
|
1464
|
+
response = await client.delete(path2, headers);
|
|
1465
|
+
break;
|
|
1466
|
+
case "HEAD":
|
|
1467
|
+
response = await client.head(path2, headers);
|
|
1468
|
+
break;
|
|
1469
|
+
case "OPTIONS":
|
|
1470
|
+
response = await client.options(path2, headers);
|
|
1471
|
+
break;
|
|
1472
|
+
case "TRACE":
|
|
1473
|
+
response = await client.trace(path2, headers);
|
|
1474
|
+
break;
|
|
1475
|
+
case "POST":
|
|
1476
|
+
response = await client.post(path2, mergeHeaders(options, headers));
|
|
1477
|
+
break;
|
|
1478
|
+
case "PUT":
|
|
1479
|
+
response = await client.put(path2, mergeHeaders(options, headers));
|
|
1480
|
+
break;
|
|
1481
|
+
case "PATCH":
|
|
1482
|
+
response = await client.patch(path2, mergeHeaders(options, headers));
|
|
1483
|
+
break;
|
|
1484
|
+
default:
|
|
1485
|
+
return new Response("Method Not Allowed", { status: 405 });
|
|
1486
|
+
}
|
|
1487
|
+
return responseToFetch(response);
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
function runServer(app, config = {}) {
|
|
1491
|
+
const handler = createFetchHandler(app);
|
|
1492
|
+
const options = resolveServerOptions(config);
|
|
1493
|
+
if (isBun()) {
|
|
1494
|
+
const bun = globalThis.Bun;
|
|
1495
|
+
bun.serve({
|
|
1496
|
+
fetch: (req, server) => {
|
|
1497
|
+
const upgradeResult = maybeUpgradeBunWebSocket(req, server, app);
|
|
1498
|
+
if (upgradeResult) {
|
|
1499
|
+
return upgradeResult;
|
|
1500
|
+
}
|
|
1501
|
+
return handler(req);
|
|
1502
|
+
},
|
|
1503
|
+
websocket: createBunWebSocketHandlers(),
|
|
1504
|
+
hostname: options.host ?? "0.0.0.0",
|
|
1505
|
+
port: options.port ?? 0
|
|
1506
|
+
});
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const globalAny2 = globalThis;
|
|
1510
|
+
if (globalAny2.Deno && typeof globalAny2.Deno.serve === "function") {
|
|
1511
|
+
globalAny2.Deno.serve(
|
|
1512
|
+
{
|
|
1513
|
+
hostname: options.host ?? "0.0.0.0",
|
|
1514
|
+
port: options.port ?? 0
|
|
1515
|
+
},
|
|
1516
|
+
handler
|
|
1517
|
+
);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
if (typeof globalAny2.addEventListener === "function") {
|
|
1521
|
+
globalAny2.addEventListener(
|
|
1522
|
+
"fetch",
|
|
1523
|
+
(event) => {
|
|
1524
|
+
event.respondWith(handler(event.request));
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
throw new Error("Unsupported runtime: unable to start WASM HTTP server");
|
|
1530
|
+
}
|
|
1531
|
+
function isBun() {
|
|
1532
|
+
const globalAny2 = globalThis;
|
|
1533
|
+
return typeof globalAny2.Bun === "object" && typeof globalAny2.Bun?.serve === "function";
|
|
1534
|
+
}
|
|
1535
|
+
function headersToRecord(headers) {
|
|
1536
|
+
const record = {};
|
|
1537
|
+
for (const [key, value] of headers.entries()) {
|
|
1538
|
+
record[key] = value;
|
|
1539
|
+
}
|
|
1540
|
+
return record;
|
|
1541
|
+
}
|
|
1542
|
+
async function buildRequestOptions(request) {
|
|
1543
|
+
const method = request.method.toUpperCase();
|
|
1544
|
+
if (method === "GET" || method === "HEAD") {
|
|
1545
|
+
return void 0;
|
|
1546
|
+
}
|
|
1547
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
1548
|
+
if (contentType.includes("application/json")) {
|
|
1549
|
+
const text = await request.text();
|
|
1550
|
+
if (!text) {
|
|
1551
|
+
return { json: null };
|
|
1552
|
+
}
|
|
1553
|
+
try {
|
|
1554
|
+
return { json: JSON.parse(text) };
|
|
1555
|
+
} catch {
|
|
1556
|
+
return { json: text };
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
1560
|
+
const text = await request.text();
|
|
1561
|
+
return text ? { formRaw: text } : void 0;
|
|
1562
|
+
}
|
|
1563
|
+
if (contentType.includes("multipart/form-data") && typeof request.formData === "function") {
|
|
1564
|
+
const formData = await request.formData();
|
|
1565
|
+
const fields = {};
|
|
1566
|
+
const files = [];
|
|
1567
|
+
for (const [key, value] of formData.entries()) {
|
|
1568
|
+
if (typeof value === "string") {
|
|
1569
|
+
fields[key] = value;
|
|
1570
|
+
} else {
|
|
1571
|
+
const buffer2 = new Uint8Array(await value.arrayBuffer());
|
|
1572
|
+
const file = {
|
|
1573
|
+
name: key,
|
|
1574
|
+
content: bufferToBase642(buffer2),
|
|
1575
|
+
...value.name ? { filename: value.name } : {},
|
|
1576
|
+
...value.type ? { contentType: value.type } : {}
|
|
1577
|
+
};
|
|
1578
|
+
files.push(file);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return { multipart: { fields, files } };
|
|
1582
|
+
}
|
|
1583
|
+
const buffer = new Uint8Array(await request.arrayBuffer());
|
|
1584
|
+
if (buffer.length === 0) {
|
|
1585
|
+
return void 0;
|
|
1586
|
+
}
|
|
1587
|
+
return { binary: bufferToBase642(buffer) };
|
|
1588
|
+
}
|
|
1589
|
+
function mergeHeaders(options, headers) {
|
|
1590
|
+
if (options) {
|
|
1591
|
+
return {
|
|
1592
|
+
...options,
|
|
1593
|
+
headers: {
|
|
1594
|
+
...options.headers,
|
|
1595
|
+
...headers
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
return { headers };
|
|
1600
|
+
}
|
|
1601
|
+
function responseToFetch(response) {
|
|
1602
|
+
const headers = new Headers(response.headers());
|
|
1603
|
+
return new Response(response.raw(), {
|
|
1604
|
+
status: response.statusCode,
|
|
1605
|
+
headers
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
function bufferToBase642(bytes) {
|
|
1609
|
+
const globalAny2 = globalThis;
|
|
1610
|
+
if (globalAny2.Buffer) {
|
|
1611
|
+
return globalAny2.Buffer.from(bytes).toString("base64");
|
|
1612
|
+
}
|
|
1613
|
+
let binary = "";
|
|
1614
|
+
for (const byte of bytes) {
|
|
1615
|
+
binary += String.fromCharCode(byte);
|
|
1616
|
+
}
|
|
1617
|
+
if (typeof btoa === "function") {
|
|
1618
|
+
return btoa(binary);
|
|
1619
|
+
}
|
|
1620
|
+
throw new Error("Base64 encoding is not supported in this runtime");
|
|
1621
|
+
}
|
|
1622
|
+
function resolveServerOptions(config) {
|
|
1623
|
+
if ("host" in config || "port" in config) {
|
|
1624
|
+
const options = config;
|
|
1625
|
+
const resolved = {};
|
|
1626
|
+
if (options.host !== void 0) {
|
|
1627
|
+
resolved.host = options.host;
|
|
1628
|
+
}
|
|
1629
|
+
if (options.port !== void 0) {
|
|
1630
|
+
resolved.port = options.port;
|
|
1631
|
+
}
|
|
1632
|
+
return resolved;
|
|
1633
|
+
}
|
|
1634
|
+
return {};
|
|
1635
|
+
}
|
|
1636
|
+
async function maybeHandleWebSocketUpgrade(app, request) {
|
|
1637
|
+
if (!isWebSocketUpgrade(request)) {
|
|
1638
|
+
return null;
|
|
1639
|
+
}
|
|
1640
|
+
const url = new URL(request.url);
|
|
1641
|
+
const match = findWebSocketRoute(app, url.pathname);
|
|
1642
|
+
if (!match) {
|
|
1643
|
+
return new Response("WebSocket route not found", { status: 404 });
|
|
1644
|
+
}
|
|
1645
|
+
const denoUpgrade = getDenoUpgrade();
|
|
1646
|
+
if (denoUpgrade) {
|
|
1647
|
+
const { socket, response } = denoUpgrade(request);
|
|
1648
|
+
startWebSocketSession(socket, match.handler);
|
|
1649
|
+
return response;
|
|
1650
|
+
}
|
|
1651
|
+
const webSocketPair = getWebSocketPair();
|
|
1652
|
+
if (webSocketPair) {
|
|
1653
|
+
const pair = new webSocketPair();
|
|
1654
|
+
const [clientSocket, serverSocket] = pair;
|
|
1655
|
+
const serverSocketWithAccept = serverSocket;
|
|
1656
|
+
serverSocketWithAccept.accept?.();
|
|
1657
|
+
startWebSocketSession(serverSocket, match.handler);
|
|
1658
|
+
const responseInit = {
|
|
1659
|
+
status: 101,
|
|
1660
|
+
webSocket: clientSocket
|
|
1661
|
+
};
|
|
1662
|
+
return new Response(null, responseInit);
|
|
1663
|
+
}
|
|
1664
|
+
return new Response("WebSocket upgrade not supported in this runtime", { status: 501 });
|
|
1665
|
+
}
|
|
1666
|
+
function buildWebSocketRegistry(app) {
|
|
1667
|
+
return {
|
|
1668
|
+
routes: app.websocketRoutes ?? [],
|
|
1669
|
+
handlers: app.websocketHandlers ?? app.handlers ?? {}
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
function findWebSocketRoute(app, path2) {
|
|
1673
|
+
const registry = buildWebSocketRegistry(app);
|
|
1674
|
+
const routeMatch = findRouteByPath(registry.routes, path2);
|
|
1675
|
+
if (routeMatch) {
|
|
1676
|
+
const handler = registry.handlers[routeMatch.metadata.handler_name];
|
|
1677
|
+
if (handler) {
|
|
1678
|
+
return { metadata: routeMatch.metadata, handler };
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const fallback = findRouteByPath(app.routes ?? [], path2);
|
|
1682
|
+
if (fallback) {
|
|
1683
|
+
const handler = registry.handlers[fallback.metadata.handler_name];
|
|
1684
|
+
if (handler) {
|
|
1685
|
+
return { metadata: fallback.metadata, handler };
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return null;
|
|
1689
|
+
}
|
|
1690
|
+
function findRouteByPath(routes, targetPath) {
|
|
1691
|
+
for (const metadata of routes) {
|
|
1692
|
+
if (metadata.method !== "GET") {
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
const params = matchPath2(metadata.path, targetPath);
|
|
1696
|
+
if (params) {
|
|
1697
|
+
return { metadata, params };
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
function isWebSocketUpgrade(request) {
|
|
1703
|
+
const upgrade = request.headers.get("upgrade");
|
|
1704
|
+
if (!upgrade || upgrade.toLowerCase() !== "websocket") {
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1707
|
+
const connection = request.headers.get("connection")?.toLowerCase();
|
|
1708
|
+
return !connection || connection.includes("upgrade");
|
|
1709
|
+
}
|
|
1710
|
+
function getDenoUpgrade() {
|
|
1711
|
+
const denoGlobal = globalThis.Deno;
|
|
1712
|
+
return denoGlobal?.upgradeWebSocket ?? null;
|
|
1713
|
+
}
|
|
1714
|
+
function getWebSocketPair() {
|
|
1715
|
+
const pairCtor = globalThis.WebSocketPair;
|
|
1716
|
+
return pairCtor ?? null;
|
|
1717
|
+
}
|
|
1718
|
+
function startWebSocketSession(socket, handler) {
|
|
1719
|
+
const runtimeSocket = createRuntimeSocket(socket);
|
|
1720
|
+
if (isWebSocketHandler2(handler) && handler.onOpen) {
|
|
1721
|
+
void handler.onOpen(runtimeSocket);
|
|
1722
|
+
}
|
|
1723
|
+
socket.addEventListener("message", (event) => {
|
|
1724
|
+
void handleWebSocketMessage(runtimeSocket, handler, event.data).catch(() => {
|
|
1725
|
+
closeSocket(runtimeSocket, 1011, "WebSocket handler error");
|
|
1726
|
+
});
|
|
1727
|
+
});
|
|
1728
|
+
socket.addEventListener("close", (event) => {
|
|
1729
|
+
if (isWebSocketHandler2(handler) && handler.onClose) {
|
|
1730
|
+
void handler.onClose(runtimeSocket, event.code, event.reason);
|
|
1731
|
+
}
|
|
1732
|
+
runtimeSocket.close(event.code, event.reason);
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
async function handleWebSocketMessage(socket, handler, data) {
|
|
1736
|
+
const payload = decodeWebSocketMessage(data);
|
|
1737
|
+
const result = isWebSocketHandler2(handler) ? await handler.onMessage?.(socket, payload) : await handler(payload);
|
|
1738
|
+
if (result === void 0) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (isStreamingResponse(result)) {
|
|
1742
|
+
throw new Error("WebSocket handlers cannot return streaming responses");
|
|
1743
|
+
}
|
|
1744
|
+
await sendWebSocketResult(socket, result);
|
|
1745
|
+
}
|
|
1746
|
+
async function sendWebSocketResult(socket, result) {
|
|
1747
|
+
if (result === null || result === void 0) {
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
if (typeof result === "string") {
|
|
1751
|
+
await socket.sendText(result);
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
if (isBinaryLike2(result)) {
|
|
1755
|
+
await socket.sendBytes?.(result);
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
await socket.sendJson(result);
|
|
1759
|
+
}
|
|
1760
|
+
function decodeWebSocketMessage(data) {
|
|
1761
|
+
if (typeof data === "string") {
|
|
1762
|
+
const parsed = safeParseJson2(data);
|
|
1763
|
+
return parsed ?? data;
|
|
1764
|
+
}
|
|
1765
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
|
1766
|
+
return data;
|
|
1767
|
+
}
|
|
1768
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
1769
|
+
return data;
|
|
1770
|
+
}
|
|
1771
|
+
return data;
|
|
1772
|
+
}
|
|
1773
|
+
function safeParseJson2(text) {
|
|
1774
|
+
try {
|
|
1775
|
+
return JSON.parse(text);
|
|
1776
|
+
} catch {
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
function isBinaryLike2(value) {
|
|
1781
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
|
|
1782
|
+
return true;
|
|
1783
|
+
}
|
|
1784
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
1785
|
+
}
|
|
1786
|
+
function createRuntimeSocket(socket) {
|
|
1787
|
+
let closed = false;
|
|
1788
|
+
return {
|
|
1789
|
+
sendText(message) {
|
|
1790
|
+
if (closed) {
|
|
1791
|
+
throw new Error("WebSocket connection is closed");
|
|
1792
|
+
}
|
|
1793
|
+
socket.send(message);
|
|
1794
|
+
},
|
|
1795
|
+
sendJson(payload) {
|
|
1796
|
+
if (closed) {
|
|
1797
|
+
throw new Error("WebSocket connection is closed");
|
|
1798
|
+
}
|
|
1799
|
+
socket.send(JSON.stringify(payload));
|
|
1800
|
+
},
|
|
1801
|
+
sendBytes(payload) {
|
|
1802
|
+
if (closed) {
|
|
1803
|
+
throw new Error("WebSocket connection is closed");
|
|
1804
|
+
}
|
|
1805
|
+
socket.send(payload);
|
|
1806
|
+
},
|
|
1807
|
+
close(code, reason) {
|
|
1808
|
+
if (closed) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
closed = true;
|
|
1812
|
+
socket.close(code, reason);
|
|
1813
|
+
},
|
|
1814
|
+
isClosed() {
|
|
1815
|
+
return closed || socket.readyState === 3;
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
function closeSocket(socket, code, reason) {
|
|
1820
|
+
try {
|
|
1821
|
+
socket.close(code, reason);
|
|
1822
|
+
} catch {
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function isWebSocketHandler2(handler) {
|
|
1826
|
+
if (typeof handler !== "object" || handler === null) {
|
|
1827
|
+
return false;
|
|
1828
|
+
}
|
|
1829
|
+
const candidate = handler;
|
|
1830
|
+
return typeof candidate.onOpen === "function" || typeof candidate.onMessage === "function" || typeof candidate.onClose === "function";
|
|
1831
|
+
}
|
|
1832
|
+
function matchPath2(template, actual) {
|
|
1833
|
+
const templateParts = template.split("/").filter(Boolean);
|
|
1834
|
+
const actualParts = actual.split("/").filter(Boolean);
|
|
1835
|
+
if (templateParts.length !== actualParts.length) {
|
|
1836
|
+
return null;
|
|
1837
|
+
}
|
|
1838
|
+
const params = {};
|
|
1839
|
+
for (let i = 0; i < templateParts.length; i++) {
|
|
1840
|
+
const templatePart = templateParts[i];
|
|
1841
|
+
const actualPart = actualParts[i];
|
|
1842
|
+
if (!templatePart || actualPart === void 0) {
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
if (templatePart.startsWith(":")) {
|
|
1846
|
+
params[templatePart.slice(1)] = decodeURIComponent(actualPart);
|
|
1847
|
+
} else if (templatePart !== actualPart) {
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return params;
|
|
1852
|
+
}
|
|
1853
|
+
function maybeUpgradeBunWebSocket(request, server, app) {
|
|
1854
|
+
if (!server || !isWebSocketUpgrade(request)) {
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
const url = new URL(request.url);
|
|
1858
|
+
const match = findWebSocketRoute(app, url.pathname);
|
|
1859
|
+
if (!match) {
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
const upgraded = server.upgrade(request, { data: { handler: match.handler, path: url.pathname } });
|
|
1863
|
+
if (upgraded) {
|
|
1864
|
+
return new Response(null, { status: 101 });
|
|
1865
|
+
}
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
function createBunWebSocketHandlers() {
|
|
1869
|
+
return {
|
|
1870
|
+
open(ws) {
|
|
1871
|
+
const handler = ws.data?.handler;
|
|
1872
|
+
if (!handler) {
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
const runtimeSocket = createBunRuntimeSocket(ws);
|
|
1876
|
+
ws.data = { handler, socket: runtimeSocket };
|
|
1877
|
+
if (isWebSocketHandler2(handler) && handler.onOpen) {
|
|
1878
|
+
void handler.onOpen(runtimeSocket);
|
|
1879
|
+
}
|
|
1880
|
+
},
|
|
1881
|
+
async message(ws, message) {
|
|
1882
|
+
const handler = ws.data?.handler;
|
|
1883
|
+
const runtimeSocket = ws.data?.socket;
|
|
1884
|
+
if (!handler || !runtimeSocket) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
try {
|
|
1888
|
+
await handleWebSocketMessage(runtimeSocket, handler, message);
|
|
1889
|
+
} catch {
|
|
1890
|
+
closeSocket(runtimeSocket, 1011, "WebSocket handler error");
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
close(ws, code, reason) {
|
|
1894
|
+
const handler = ws.data?.handler;
|
|
1895
|
+
const runtimeSocket = ws.data?.socket;
|
|
1896
|
+
if (!handler || !runtimeSocket) {
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (isWebSocketHandler2(handler) && handler.onClose) {
|
|
1900
|
+
void handler.onClose(runtimeSocket, code, reason);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
function createBunRuntimeSocket(ws) {
|
|
1906
|
+
let closed = false;
|
|
1907
|
+
return {
|
|
1908
|
+
sendText(message) {
|
|
1909
|
+
if (closed) {
|
|
1910
|
+
throw new Error("WebSocket connection is closed");
|
|
1911
|
+
}
|
|
1912
|
+
ws.send(message);
|
|
1913
|
+
},
|
|
1914
|
+
sendJson(payload) {
|
|
1915
|
+
if (closed) {
|
|
1916
|
+
throw new Error("WebSocket connection is closed");
|
|
1917
|
+
}
|
|
1918
|
+
ws.send(JSON.stringify(payload));
|
|
1919
|
+
},
|
|
1920
|
+
sendBytes(payload) {
|
|
1921
|
+
if (closed) {
|
|
1922
|
+
throw new Error("WebSocket connection is closed");
|
|
1923
|
+
}
|
|
1924
|
+
ws.send(payload);
|
|
1925
|
+
},
|
|
1926
|
+
close(code, reason) {
|
|
1927
|
+
if (closed) {
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
closed = true;
|
|
1931
|
+
ws.close?.(code, reason);
|
|
1932
|
+
},
|
|
1933
|
+
isClosed() {
|
|
1934
|
+
return closed;
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/app.ts
|
|
1940
|
+
var Spikard = class {
|
|
1941
|
+
routes = [];
|
|
1942
|
+
handlers = {};
|
|
1943
|
+
websocketRoutes = [];
|
|
1944
|
+
websocketHandlers = {};
|
|
1945
|
+
lifecycleHooks = {
|
|
1946
|
+
onRequest: [],
|
|
1947
|
+
preValidation: [],
|
|
1948
|
+
preHandler: [],
|
|
1949
|
+
onResponse: [],
|
|
1950
|
+
onError: []
|
|
1951
|
+
};
|
|
1952
|
+
dependencies = {};
|
|
1953
|
+
/**
|
|
1954
|
+
* Add a route to the application
|
|
1955
|
+
*
|
|
1956
|
+
* @param metadata - Route configuration metadata
|
|
1957
|
+
* @param handler - Handler function (sync or async)
|
|
1958
|
+
*/
|
|
1959
|
+
addRoute(metadata, handler) {
|
|
1960
|
+
this.routes.push(metadata);
|
|
1961
|
+
this.handlers[metadata.handler_name] = handler;
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* Register a WebSocket route (message-based)
|
|
1965
|
+
*/
|
|
1966
|
+
websocket(path2, handler, options = {}) {
|
|
1967
|
+
const handlerName = options.handlerName ?? `ws_${this.websocketRoutes.length}_${path2}`.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
1968
|
+
const route2 = {
|
|
1969
|
+
method: "GET",
|
|
1970
|
+
path: path2,
|
|
1971
|
+
handler_name: handlerName,
|
|
1972
|
+
request_schema: options.messageSchema,
|
|
1973
|
+
response_schema: options.responseSchema,
|
|
1974
|
+
is_async: true
|
|
1975
|
+
};
|
|
1976
|
+
this.websocketRoutes.push(route2);
|
|
1977
|
+
this.websocketHandlers[handlerName] = handler;
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Run the server
|
|
1981
|
+
*
|
|
1982
|
+
* @param options - Server configuration
|
|
1983
|
+
*/
|
|
1984
|
+
run(options = {}) {
|
|
1985
|
+
runServer(this, options);
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Register a dependency value or factory
|
|
1989
|
+
*
|
|
1990
|
+
* Dependencies are normalized to `snake_case` so fixtures and cross-language
|
|
1991
|
+
* bindings stay aligned.
|
|
1992
|
+
*/
|
|
1993
|
+
provide(key, valueOrFactory, options) {
|
|
1994
|
+
const normalizedKey = normalizeDependencyKey(key);
|
|
1995
|
+
const isFactory = typeof valueOrFactory === "function";
|
|
1996
|
+
const dependsOn = options?.dependsOn?.map((depKey) => normalizeDependencyKey(depKey)) ?? (isFactory ? inferFactoryDependencies(valueOrFactory) : []);
|
|
1997
|
+
this.dependencies[normalizedKey] = {
|
|
1998
|
+
isFactory,
|
|
1999
|
+
value: isFactory ? void 0 : valueOrFactory,
|
|
2000
|
+
factory: isFactory ? wrapFactory(valueOrFactory) : void 0,
|
|
2001
|
+
dependsOn,
|
|
2002
|
+
singleton: options?.singleton ?? false,
|
|
2003
|
+
cacheable: options?.cacheable ?? !isFactory
|
|
2004
|
+
};
|
|
2005
|
+
return this;
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Register an onRequest lifecycle hook
|
|
2009
|
+
*
|
|
2010
|
+
* Runs before routing. Can inspect/modify the request or short-circuit with a response.
|
|
2011
|
+
*
|
|
2012
|
+
* @param hook - Async function that receives a request and returns either:
|
|
2013
|
+
* - The (possibly modified) request to continue processing
|
|
2014
|
+
* - A Response object to short-circuit the request pipeline
|
|
2015
|
+
* @returns The hook function (for decorator usage)
|
|
2016
|
+
*
|
|
2017
|
+
* @example
|
|
2018
|
+
* ```typescript
|
|
2019
|
+
* app.onRequest(async (request) => {
|
|
2020
|
+
* console.log(`Request: ${request.method} ${request.path}`);
|
|
2021
|
+
* return request;
|
|
2022
|
+
* });
|
|
2023
|
+
* ```
|
|
2024
|
+
*/
|
|
2025
|
+
onRequest(hook) {
|
|
2026
|
+
this.lifecycleHooks.onRequest.push(hook);
|
|
2027
|
+
return hook;
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Register a preValidation lifecycle hook
|
|
2031
|
+
*
|
|
2032
|
+
* Runs after routing but before validation. Useful for rate limiting.
|
|
2033
|
+
*
|
|
2034
|
+
* @param hook - Async function that receives a request and returns either:
|
|
2035
|
+
* - The (possibly modified) request to continue processing
|
|
2036
|
+
* - A Response object to short-circuit the request pipeline
|
|
2037
|
+
* @returns The hook function (for decorator usage)
|
|
2038
|
+
*
|
|
2039
|
+
* @example
|
|
2040
|
+
* ```typescript
|
|
2041
|
+
* app.preValidation(async (request) => {
|
|
2042
|
+
* if (tooManyRequests()) {
|
|
2043
|
+
* return { error: "Rate limit exceeded", status: 429 };
|
|
2044
|
+
* }
|
|
2045
|
+
* return request;
|
|
2046
|
+
* });
|
|
2047
|
+
* ```
|
|
2048
|
+
*/
|
|
2049
|
+
preValidation(hook) {
|
|
2050
|
+
this.lifecycleHooks.preValidation.push(hook);
|
|
2051
|
+
return hook;
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Register a preHandler lifecycle hook
|
|
2055
|
+
*
|
|
2056
|
+
* Runs after validation but before the handler. Ideal for authentication/authorization.
|
|
2057
|
+
*
|
|
2058
|
+
* @param hook - Async function that receives a request and returns either:
|
|
2059
|
+
* - The (possibly modified) request to continue processing
|
|
2060
|
+
* - A Response object to short-circuit the request pipeline
|
|
2061
|
+
* @returns The hook function (for decorator usage)
|
|
2062
|
+
*
|
|
2063
|
+
* @example
|
|
2064
|
+
* ```typescript
|
|
2065
|
+
* app.preHandler(async (request) => {
|
|
2066
|
+
* if (!validToken(request.headers.authorization)) {
|
|
2067
|
+
* return { error: "Unauthorized", status: 401 };
|
|
2068
|
+
* }
|
|
2069
|
+
* return request;
|
|
2070
|
+
* });
|
|
2071
|
+
* ```
|
|
2072
|
+
*/
|
|
2073
|
+
preHandler(hook) {
|
|
2074
|
+
this.lifecycleHooks.preHandler.push(hook);
|
|
2075
|
+
return hook;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Register an onResponse lifecycle hook
|
|
2079
|
+
*
|
|
2080
|
+
* Runs after the handler executes. Can modify the response.
|
|
2081
|
+
*
|
|
2082
|
+
* @param hook - Async function that receives a response and returns the (possibly modified) response
|
|
2083
|
+
* @returns The hook function (for decorator usage)
|
|
2084
|
+
*
|
|
2085
|
+
* @example
|
|
2086
|
+
* ```typescript
|
|
2087
|
+
* app.onResponse(async (response) => {
|
|
2088
|
+
* response.headers["X-Frame-Options"] = "DENY";
|
|
2089
|
+
* return response;
|
|
2090
|
+
* });
|
|
2091
|
+
* ```
|
|
2092
|
+
*/
|
|
2093
|
+
onResponse(hook) {
|
|
2094
|
+
this.lifecycleHooks.onResponse.push(hook);
|
|
2095
|
+
return hook;
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Register an onError lifecycle hook
|
|
2099
|
+
*
|
|
2100
|
+
* Runs when an error occurs. Can customize error responses.
|
|
2101
|
+
*
|
|
2102
|
+
* @param hook - Async function that receives an error response and returns a (possibly modified) response
|
|
2103
|
+
* @returns The hook function (for decorator usage)
|
|
2104
|
+
*
|
|
2105
|
+
* @example
|
|
2106
|
+
* ```typescript
|
|
2107
|
+
* app.onError(async (response) => {
|
|
2108
|
+
* response.headers["Content-Type"] = "application/json";
|
|
2109
|
+
* return response;
|
|
2110
|
+
* });
|
|
2111
|
+
* ```
|
|
2112
|
+
*/
|
|
2113
|
+
onError(hook) {
|
|
2114
|
+
this.lifecycleHooks.onError.push(hook);
|
|
2115
|
+
return hook;
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* Get all registered lifecycle hooks
|
|
2119
|
+
*
|
|
2120
|
+
* @returns Dictionary of hook lists by type
|
|
2121
|
+
*/
|
|
2122
|
+
getLifecycleHooks() {
|
|
2123
|
+
return {
|
|
2124
|
+
onRequest: [...this.lifecycleHooks.onRequest],
|
|
2125
|
+
preValidation: [...this.lifecycleHooks.preValidation],
|
|
2126
|
+
preHandler: [...this.lifecycleHooks.preHandler],
|
|
2127
|
+
onResponse: [...this.lifecycleHooks.onResponse],
|
|
2128
|
+
onError: [...this.lifecycleHooks.onError]
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
};
|
|
2132
|
+
function normalizeDependencyKey(key) {
|
|
2133
|
+
if (key.startsWith("__spikard_")) {
|
|
2134
|
+
return key;
|
|
2135
|
+
}
|
|
2136
|
+
const trimmed = key.replace(/^_+/, "");
|
|
2137
|
+
if (trimmed.includes("_")) {
|
|
2138
|
+
return trimmed.toLowerCase();
|
|
2139
|
+
}
|
|
2140
|
+
return trimmed.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
|
|
2141
|
+
}
|
|
2142
|
+
function inferFactoryDependencies(factory) {
|
|
2143
|
+
const params = getFactoryParameterNames(factory);
|
|
2144
|
+
return params.map((name) => normalizeDependencyKey(name));
|
|
2145
|
+
}
|
|
2146
|
+
function getFactoryParameterNames(factory) {
|
|
2147
|
+
const source = factory.toString().trim();
|
|
2148
|
+
const openParen = source.indexOf("(");
|
|
2149
|
+
if (openParen === -1) {
|
|
2150
|
+
return [];
|
|
2151
|
+
}
|
|
2152
|
+
const closeParen = source.indexOf(")", openParen + 1);
|
|
2153
|
+
if (closeParen === -1) {
|
|
2154
|
+
return [];
|
|
2155
|
+
}
|
|
2156
|
+
const raw = source.slice(openParen + 1, closeParen).trim();
|
|
2157
|
+
if (!raw) {
|
|
2158
|
+
return [];
|
|
2159
|
+
}
|
|
2160
|
+
return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map(
|
|
2161
|
+
(entry) => entry.replace(/^\.\.\./, "").split("=")[0]?.trim() ?? ""
|
|
2162
|
+
).filter((entry) => entry.length > 0 && !entry.startsWith("{") && !entry.startsWith("["));
|
|
2163
|
+
}
|
|
2164
|
+
var DI_REQUEST_ID_KEY = "__spikard_request_id__";
|
|
2165
|
+
var globalDi = globalThis;
|
|
2166
|
+
function registerDependencyGenerator(requestId, generator) {
|
|
2167
|
+
let registry = globalDi.__spikard_di_generators__;
|
|
2168
|
+
if (!registry) {
|
|
2169
|
+
registry = {};
|
|
2170
|
+
globalDi.__spikard_di_generators__ = registry;
|
|
2171
|
+
}
|
|
2172
|
+
let generators = registry[requestId];
|
|
2173
|
+
if (!generators) {
|
|
2174
|
+
generators = [];
|
|
2175
|
+
registry[requestId] = generators;
|
|
2176
|
+
}
|
|
2177
|
+
generators.push(generator);
|
|
2178
|
+
}
|
|
2179
|
+
async function cleanupDependencyGenerators(requestId) {
|
|
2180
|
+
const registry = globalDi.__spikard_di_generators__;
|
|
2181
|
+
if (!registry) {
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const generators = registry[requestId];
|
|
2185
|
+
if (!generators || generators.length === 0) {
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
for (let idx = generators.length - 1; idx >= 0; idx -= 1) {
|
|
2189
|
+
const generator = generators[idx];
|
|
2190
|
+
if (!generator) {
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2193
|
+
try {
|
|
2194
|
+
if (typeof generator.return === "function") {
|
|
2195
|
+
await generator.return(void 0);
|
|
2196
|
+
}
|
|
2197
|
+
} catch {
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
delete registry[requestId];
|
|
2201
|
+
}
|
|
2202
|
+
if (!globalDi.__spikard_di_cleanup__) {
|
|
2203
|
+
globalDi.__spikard_di_cleanup__ = cleanupDependencyGenerators;
|
|
2204
|
+
}
|
|
2205
|
+
function wrapFactory(factory) {
|
|
2206
|
+
return async (dependenciesJson) => {
|
|
2207
|
+
const depsUnknown = JSON.parse(dependenciesJson);
|
|
2208
|
+
const deps = typeof depsUnknown === "object" && depsUnknown ? depsUnknown : {};
|
|
2209
|
+
const requestId = typeof deps[DI_REQUEST_ID_KEY] === "string" ? deps[DI_REQUEST_ID_KEY] : null;
|
|
2210
|
+
const paramNames = getFactoryParameterNames(factory);
|
|
2211
|
+
const args = paramNames.map((name) => deps[normalizeDependencyKey(name)]);
|
|
2212
|
+
const result = await factory(...args);
|
|
2213
|
+
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
2214
|
+
const generator = result;
|
|
2215
|
+
const first = await generator.next();
|
|
2216
|
+
const value = first.value;
|
|
2217
|
+
if (requestId) {
|
|
2218
|
+
registerDependencyGenerator(requestId, generator);
|
|
2219
|
+
} else {
|
|
2220
|
+
try {
|
|
2221
|
+
if (typeof generator.return === "function") {
|
|
2222
|
+
await generator.return(void 0);
|
|
2223
|
+
}
|
|
2224
|
+
} catch {
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
return JSON.stringify(value ?? null);
|
|
2228
|
+
}
|
|
2229
|
+
return JSON.stringify(result ?? null);
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
// src/background.ts
|
|
2234
|
+
var background_exports = {};
|
|
2235
|
+
__export(background_exports, {
|
|
2236
|
+
run: () => run
|
|
2237
|
+
});
|
|
2238
|
+
function run(work) {
|
|
2239
|
+
void Promise.resolve().then(() => work());
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// src/routing.ts
|
|
2243
|
+
function route(path2, options = {}) {
|
|
2244
|
+
return (handler) => {
|
|
2245
|
+
const methods = options.methods ? Array.isArray(options.methods) ? options.methods : [options.methods] : ["GET"];
|
|
2246
|
+
const metadata = {
|
|
2247
|
+
method: methods.join(","),
|
|
2248
|
+
path: path2,
|
|
2249
|
+
handler_name: handler.name || "anonymous",
|
|
2250
|
+
...options.bodySchema !== void 0 && { request_schema: options.bodySchema },
|
|
2251
|
+
...options.responseSchema !== void 0 && { response_schema: options.responseSchema },
|
|
2252
|
+
...options.parameterSchema !== void 0 && { parameter_schema: options.parameterSchema },
|
|
2253
|
+
...options.cors !== void 0 && { cors: options.cors },
|
|
2254
|
+
is_async: true
|
|
2255
|
+
};
|
|
2256
|
+
handler.__route_metadata__ = metadata;
|
|
2257
|
+
return handler;
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
function get(path2, options = {}) {
|
|
2261
|
+
return route(path2, { ...options, methods: ["GET"] });
|
|
2262
|
+
}
|
|
2263
|
+
function post(path2, options = {}) {
|
|
2264
|
+
return route(path2, { ...options, methods: ["POST"] });
|
|
2265
|
+
}
|
|
2266
|
+
function put(path2, options = {}) {
|
|
2267
|
+
return route(path2, { ...options, methods: ["PUT"] });
|
|
2268
|
+
}
|
|
2269
|
+
function del(path2, options = {}) {
|
|
2270
|
+
return route(path2, { ...options, methods: ["DELETE"] });
|
|
2271
|
+
}
|
|
2272
|
+
function patch(path2, options = {}) {
|
|
2273
|
+
return route(path2, { ...options, methods: ["PATCH"] });
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// src/node.ts
|
|
2277
|
+
__setGunzipImplementation((bytes) => new Uint8Array(zlib.gunzipSync(bytes)));
|
|
2278
|
+
var NodeTestClient = class extends TestClient {
|
|
2279
|
+
constructor(app) {
|
|
2280
|
+
super(withStaticManifest2(app));
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
var TestClient2 = NodeTestClient;
|
|
2284
|
+
function withStaticManifest2(app) {
|
|
2285
|
+
const config = app.config;
|
|
2286
|
+
if (!config || !config.staticFiles || config.staticFiles.length === 0) {
|
|
2287
|
+
return app;
|
|
2288
|
+
}
|
|
2289
|
+
const manifest = buildStaticManifest2(config.staticFiles);
|
|
2290
|
+
if (manifest.length === 0) {
|
|
2291
|
+
return app;
|
|
2292
|
+
}
|
|
2293
|
+
return {
|
|
2294
|
+
...app,
|
|
2295
|
+
config: {
|
|
2296
|
+
...config,
|
|
2297
|
+
__wasmStaticManifest: manifest
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
function buildStaticManifest2(configs) {
|
|
2302
|
+
const manifest = [];
|
|
2303
|
+
for (const config of configs) {
|
|
2304
|
+
if (!config.directory || !config.routePrefix) {
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
if (!fs__default.default.existsSync(config.directory)) {
|
|
2308
|
+
continue;
|
|
2309
|
+
}
|
|
2310
|
+
const files = listFiles(config.directory);
|
|
2311
|
+
for (const filePath of files) {
|
|
2312
|
+
const relative = path__default.default.relative(config.directory, filePath).split(path__default.default.sep).join("/");
|
|
2313
|
+
let normalizedPrefix = config.routePrefix;
|
|
2314
|
+
while (normalizedPrefix.endsWith("/")) {
|
|
2315
|
+
normalizedPrefix = normalizedPrefix.slice(0, -1);
|
|
2316
|
+
}
|
|
2317
|
+
const route2 = normalizeRoute2(`${normalizedPrefix}/${relative}`);
|
|
2318
|
+
const headers = buildStaticHeaders2(filePath, config.cacheControl);
|
|
2319
|
+
const body = fs__default.default.readFileSync(filePath).toString("base64");
|
|
2320
|
+
manifest.push({ route: route2, headers, body });
|
|
2321
|
+
}
|
|
2322
|
+
if (config.indexFile ?? true) {
|
|
2323
|
+
const indexPath = path__default.default.join(config.directory, "index.html");
|
|
2324
|
+
if (fs__default.default.existsSync(indexPath)) {
|
|
2325
|
+
const headers = buildStaticHeaders2(indexPath, config.cacheControl);
|
|
2326
|
+
const body = fs__default.default.readFileSync(indexPath).toString("base64");
|
|
2327
|
+
const prefix = normalizeRoute2(config.routePrefix);
|
|
2328
|
+
manifest.push({ route: prefix, headers: { ...headers }, body });
|
|
2329
|
+
if (!prefix.endsWith("/")) {
|
|
2330
|
+
manifest.push({ route: `${prefix}/`, headers: { ...headers }, body });
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return manifest;
|
|
2336
|
+
}
|
|
2337
|
+
function listFiles(root) {
|
|
2338
|
+
const entries = [];
|
|
2339
|
+
const stack = [root];
|
|
2340
|
+
while (stack.length > 0) {
|
|
2341
|
+
const current = stack.pop();
|
|
2342
|
+
if (!current) {
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
const stats = fs__default.default.statSync(current);
|
|
2346
|
+
if (stats.isDirectory()) {
|
|
2347
|
+
for (const child of fs__default.default.readdirSync(current)) {
|
|
2348
|
+
stack.push(path__default.default.join(current, child));
|
|
2349
|
+
}
|
|
2350
|
+
} else {
|
|
2351
|
+
entries.push(current);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return entries;
|
|
2355
|
+
}
|
|
2356
|
+
function buildStaticHeaders2(filePath, cacheControl) {
|
|
2357
|
+
const headers = {
|
|
2358
|
+
"content-type": lookupContentType(filePath)
|
|
2359
|
+
};
|
|
2360
|
+
if (cacheControl) {
|
|
2361
|
+
headers["cache-control"] = cacheControl;
|
|
2362
|
+
}
|
|
2363
|
+
return headers;
|
|
2364
|
+
}
|
|
2365
|
+
function lookupContentType(filePath) {
|
|
2366
|
+
const ext = path__default.default.extname(filePath).toLowerCase();
|
|
2367
|
+
switch (ext) {
|
|
2368
|
+
case ".html":
|
|
2369
|
+
case ".htm":
|
|
2370
|
+
return "text/html";
|
|
2371
|
+
case ".txt":
|
|
2372
|
+
return "text/plain";
|
|
2373
|
+
case ".json":
|
|
2374
|
+
return "application/json";
|
|
2375
|
+
case ".js":
|
|
2376
|
+
return "application/javascript";
|
|
2377
|
+
case ".css":
|
|
2378
|
+
return "text/css";
|
|
2379
|
+
default:
|
|
2380
|
+
return "application/octet-stream";
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
function normalizeRoute2(route2) {
|
|
2384
|
+
const normalized = route2.replace(/\/+/g, "/");
|
|
2385
|
+
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
exports.Spikard = Spikard;
|
|
2389
|
+
exports.StreamingResponse = StreamingResponse;
|
|
2390
|
+
exports.TestClient = TestClient2;
|
|
2391
|
+
exports.background = background_exports;
|
|
2392
|
+
exports.createFetchHandler = createFetchHandler;
|
|
2393
|
+
exports.del = del;
|
|
2394
|
+
exports.get = get;
|
|
2395
|
+
exports.patch = patch;
|
|
2396
|
+
exports.post = post;
|
|
2397
|
+
exports.put = put;
|
|
2398
|
+
exports.route = route;
|
|
2399
|
+
exports.runServer = runServer;
|
|
2400
|
+
//# sourceMappingURL=node.js.map
|
|
2401
|
+
//# sourceMappingURL=node.js.map
|