stitchkit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +10 -7
  2. package/dist/contract/define.d.ts.map +1 -1
  3. package/dist/contract/errors.d.ts +4 -3
  4. package/dist/contract/errors.d.ts.map +1 -1
  5. package/dist/contract/index.js +1 -1
  6. package/dist/{index-gfzn1n4n.js → index-a35v22fh.js} +1 -1
  7. package/dist/{index-7wfkbvss.js → index-kckky6zw.js} +6 -2
  8. package/dist/index-v2z2v3mq.js +587 -0
  9. package/dist/index.js +2 -2
  10. package/dist/node.d.ts +4 -0
  11. package/dist/node.d.ts.map +1 -0
  12. package/dist/node.js +26 -0
  13. package/dist/server/create.d.ts +3 -3
  14. package/dist/server/create.d.ts.map +1 -1
  15. package/dist/server/index.d.ts +1 -1
  16. package/dist/server/index.d.ts.map +1 -1
  17. package/dist/server/index.js +10 -576
  18. package/dist/server/node.d.ts +12 -0
  19. package/dist/server/node.d.ts.map +1 -0
  20. package/dist/server/types.d.ts +24 -21
  21. package/dist/server/types.d.ts.map +1 -1
  22. package/dist/tools/agent.d.ts +15 -2
  23. package/dist/tools/agent.d.ts.map +1 -1
  24. package/dist/tools/coerce.d.ts +8 -0
  25. package/dist/tools/coerce.d.ts.map +1 -0
  26. package/dist/tools/execute.d.ts +26 -2
  27. package/dist/tools/execute.d.ts.map +1 -1
  28. package/dist/tools/flatten.d.ts +15 -0
  29. package/dist/tools/flatten.d.ts.map +1 -0
  30. package/dist/tools/json-schema.d.ts +18 -0
  31. package/dist/tools/json-schema.d.ts.map +1 -0
  32. package/dist/tools/manifest.d.ts +13 -0
  33. package/dist/tools/manifest.d.ts.map +1 -0
  34. package/dist/tools/mcp-handler.d.ts.map +1 -1
  35. package/dist/tools/mcp.d.ts +53 -3
  36. package/dist/tools/mcp.d.ts.map +1 -1
  37. package/dist/tools/mount.d.ts +18 -6
  38. package/dist/tools/mount.d.ts.map +1 -1
  39. package/dist/tools/schema.d.ts +19 -2
  40. package/dist/tools/schema.d.ts.map +1 -1
  41. package/dist/tools.d.ts +6 -2
  42. package/dist/tools.d.ts.map +1 -1
  43. package/dist/tools.js +337 -154
  44. package/package.json +15 -4
@@ -3,8 +3,14 @@ import {
3
3
  streamSSE
4
4
  } from "../index-n7bmdwmz.js";
5
5
  import {
6
- normalizeError
7
- } from "../index-gfzn1n4n.js";
6
+ corsHeaders,
7
+ corsPreflightResponse,
8
+ createHandler,
9
+ createServer,
10
+ parseMultipart,
11
+ staticRoute
12
+ } from "../index-v2z2v3mq.js";
13
+ import"../index-a35v22fh.js";
8
14
  import {
9
15
  AppError,
10
16
  appError,
@@ -14,12 +20,11 @@ import {
14
20
  notFound,
15
21
  rateLimited,
16
22
  unauthorized
17
- } from "../index-7wfkbvss.js";
23
+ } from "../index-kckky6zw.js";
18
24
  import {
19
25
  extractIp,
20
26
  generateTraceId,
21
27
  getClientInfo,
22
- parseQueryParams,
23
28
  resolveTraceId
24
29
  } from "../index-ke4mx4ea.js";
25
30
  // src/server/swept-map.ts
@@ -79,577 +84,6 @@ function cacheHeaders(maxAge, scope = "public") {
79
84
  "Cache-Control": `${scope}, max-age=${maxAge}`
80
85
  };
81
86
  }
82
- // src/server/multipart.ts
83
- var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
84
- async function parseMultipart(req, fileField, fieldsSchema, maxBytes = DEFAULT_MAX_UPLOAD_BYTES) {
85
- const declared = Number(req.headers.get("content-length") ?? 0);
86
- if (declared > maxBytes) {
87
- badRequest(`Upload exceeds the ${Math.round(maxBytes / 1024 / 1024)} MB limit`);
88
- }
89
- const formData = await req.formData();
90
- const file = formData.get(fileField);
91
- if (!file || !(file instanceof File)) {
92
- badRequest(`Missing file field: ${fileField}`);
93
- }
94
- if (file.size > maxBytes) {
95
- badRequest(`Upload exceeds the ${Math.round(maxBytes / 1024 / 1024)} MB limit`);
96
- }
97
- const fields = {};
98
- for (const [key, value] of formData.entries()) {
99
- if (key === fileField)
100
- continue;
101
- if (typeof value === "string") {
102
- try {
103
- fields[key] = JSON.parse(value);
104
- } catch {
105
- fields[key] = value;
106
- }
107
- }
108
- }
109
- const parsed = fieldsSchema ? fieldsSchema.parse(fields) : fields;
110
- return { file, fields: parsed };
111
- }
112
-
113
- // src/server/context.ts
114
- var RESERVED_KEYS = new Set([
115
- "params",
116
- "input",
117
- "source",
118
- "req",
119
- "url",
120
- "headers",
121
- "traceId",
122
- "spanId",
123
- "ipAddress",
124
- "userAgent"
125
- ]);
126
- async function readJsonBody(req) {
127
- const text = await req.text();
128
- if (text.trim() === "")
129
- return {};
130
- try {
131
- return JSON.parse(text);
132
- } catch {
133
- badRequest("Invalid JSON body");
134
- }
135
- }
136
- async function buildContext(req, url, method, pathParams, traceId) {
137
- const parsedParams = method.paramsSchema ? method.paramsSchema.parse(pathParams) : undefined;
138
- let parsedInput;
139
- let file;
140
- if (method.multipart) {
141
- const multipart = await parseMultipart(req, method.multipart, method.inputSchema);
142
- parsedInput = multipart.fields;
143
- file = multipart.file;
144
- } else if (method.inputSchema) {
145
- if (req.method === "GET") {
146
- parsedInput = method.inputSchema.parse(parseQueryParams(url));
147
- } else if (req.method === "DELETE") {
148
- const ct = req.headers.get("content-type");
149
- if (ct?.includes("application/json")) {
150
- parsedInput = method.inputSchema.parse(await readJsonBody(req));
151
- } else {
152
- parsedInput = method.inputSchema.parse(parseQueryParams(url));
153
- }
154
- } else {
155
- parsedInput = method.inputSchema.parse(await readJsonBody(req));
156
- }
157
- }
158
- const safePathParams = {};
159
- for (const [k, v] of Object.entries(pathParams)) {
160
- if (!RESERVED_KEYS.has(k))
161
- safePathParams[k] = v;
162
- }
163
- return {
164
- params: parsedParams,
165
- input: parsedInput,
166
- ...file && { file },
167
- source: "http",
168
- req,
169
- url,
170
- headers: req.headers,
171
- ...safePathParams,
172
- traceId,
173
- ...getClientInfo(req)
174
- };
175
- }
176
- function buildErrorContext(req, url, traceId) {
177
- return {
178
- params: undefined,
179
- input: undefined,
180
- source: "http",
181
- req,
182
- url,
183
- headers: req.headers,
184
- traceId,
185
- ...getClientInfo(req)
186
- };
187
- }
188
-
189
- // src/server/logger.ts
190
- var c = {
191
- reset: "\x1B[0m",
192
- dim: "\x1B[2m",
193
- red: "\x1B[31m",
194
- green: "\x1B[32m",
195
- yellow: "\x1B[33m",
196
- blue: "\x1B[34m",
197
- magenta: "\x1B[35m",
198
- cyan: "\x1B[36m",
199
- gray: "\x1B[90m"
200
- };
201
- var METHOD_COLOR = {
202
- GET: c.green,
203
- POST: c.yellow,
204
- PUT: c.blue,
205
- PATCH: c.cyan,
206
- DELETE: c.red,
207
- OPTIONS: c.gray
208
- };
209
- var isProd = false;
210
- function timestamp() {
211
- const now = new Date;
212
- const t = now.toLocaleTimeString("en-US", {
213
- hour12: false,
214
- hour: "2-digit",
215
- minute: "2-digit",
216
- second: "2-digit"
217
- });
218
- return `${t}.${now.getMilliseconds().toString().padStart(3, "0")}`;
219
- }
220
- function elapsedMs(startTime) {
221
- return Number(process.hrtime.bigint() - startTime) / 1e6;
222
- }
223
- function levelForStatus(status) {
224
- if (status >= 500)
225
- return "error";
226
- if (status >= 400)
227
- return "warn";
228
- return "info";
229
- }
230
- function buildLogFields(method, path, status, durationMs, traceId) {
231
- return { traceId, method, path, status, durationMs };
232
- }
233
- function formatMs(ms) {
234
- if (ms >= 1000)
235
- return `${(ms / 1000).toFixed(2)}s`;
236
- if (ms >= 1)
237
- return `${Math.round(ms)}ms`;
238
- return `${Math.round(ms * 1000)}µs`;
239
- }
240
- function durationColor(ms) {
241
- if (ms >= 1000)
242
- return c.red;
243
- if (ms > 300)
244
- return c.yellow;
245
- return c.green;
246
- }
247
- function statusColor(status) {
248
- if (status >= 500)
249
- return c.red;
250
- if (status >= 400)
251
- return c.yellow;
252
- if (status >= 300)
253
- return c.cyan;
254
- return c.green;
255
- }
256
- function ipLabel(ip) {
257
- if (!ip || ip === "::1" || ip === "127.0.0.1")
258
- return `${c.magenta}local${c.reset}`;
259
- return `${c.dim}${ip}${c.reset}`;
260
- }
261
- var SKIP_PREFIXES = ["/_bun/", "/_bundle", "/favicon"];
262
- function shouldLog(pathname, method) {
263
- if (method === "OPTIONS")
264
- return false;
265
- return !SKIP_PREFIXES.some((prefix) => pathname.startsWith(prefix));
266
- }
267
- function logIncoming(req, pathname, traceId) {
268
- const log = { traceId, startTime: process.hrtime.bigint() };
269
- if (!isProd) {
270
- const mc = METHOD_COLOR[req.method] ?? c.dim;
271
- console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${traceId}${c.reset} ${c.cyan}→${c.reset} ${pathname} ${ipLabel(extractIp(req))}`);
272
- }
273
- return log;
274
- }
275
- function logOutgoing(req, pathname, status, log) {
276
- const ms = elapsedMs(log.startTime);
277
- if (isProd) {
278
- console.log(JSON.stringify({
279
- ts: new Date().toISOString(),
280
- level: levelForStatus(status),
281
- msg: `${req.method} ${pathname} ${status}`,
282
- ...buildLogFields(req.method, pathname, status, Math.round(ms), log.traceId),
283
- ip: extractIp(req) || undefined
284
- }));
285
- return;
286
- }
287
- const mc = METHOD_COLOR[req.method] ?? c.dim;
288
- console.log(`${c.gray}[${timestamp()}]${c.reset} ${mc}${req.method}${c.reset} ${c.dim}${log.traceId}${c.reset} ${c.cyan}←${c.reset} ${pathname} ${statusColor(status)}${status}${c.reset} ${durationColor(ms)}${formatMs(ms)}${c.reset} ${ipLabel(extractIp(req))}`);
289
- }
290
-
291
- // src/server/middleware/cors.ts
292
- function corsHeaders(config, requestOrigin) {
293
- let allowOrigin;
294
- if (config.origin === undefined || config.origin === "*") {
295
- allowOrigin = config.credentials ? requestOrigin ?? undefined : "*";
296
- } else if (Array.isArray(config.origin)) {
297
- allowOrigin = requestOrigin && config.origin.includes(requestOrigin) ? requestOrigin : undefined;
298
- } else {
299
- allowOrigin = config.origin;
300
- }
301
- const headers = {
302
- "Access-Control-Allow-Methods": config.methods ?? "GET, POST, PUT, PATCH, DELETE, OPTIONS",
303
- "Access-Control-Allow-Headers": config.headers ?? "Content-Type, Authorization, X-Trace-Id"
304
- };
305
- if (allowOrigin !== undefined) {
306
- headers["Access-Control-Allow-Origin"] = allowOrigin;
307
- }
308
- if (config.credentials) {
309
- headers["Access-Control-Allow-Credentials"] = "true";
310
- }
311
- const variesByOrigin = Array.isArray(config.origin) || (config.origin === undefined || config.origin === "*") && Boolean(config.credentials);
312
- if (variesByOrigin) {
313
- headers.Vary = "Origin";
314
- }
315
- return headers;
316
- }
317
- function corsPreflightResponse(config, req) {
318
- return new Response(null, {
319
- status: 204,
320
- headers: corsHeaders(config, req.headers.get("origin"))
321
- });
322
- }
323
-
324
- // src/server/router.ts
325
- import { resolve, sep } from "node:path";
326
- function joinPath(...parts) {
327
- const joined = parts.filter(Boolean).map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/");
328
- return `/${joined}`;
329
- }
330
- function matchSegments(patternSegments, requestSegments) {
331
- if (patternSegments.length !== requestSegments.length)
332
- return null;
333
- const params = {};
334
- for (const [i, pattern] of patternSegments.entries()) {
335
- const actual = requestSegments[i];
336
- if (actual === undefined)
337
- return null;
338
- if (pattern.startsWith(":")) {
339
- params[pattern.slice(1)] = decodeURIComponent(actual);
340
- } else if (pattern !== actual) {
341
- return null;
342
- }
343
- }
344
- return params;
345
- }
346
- function buildRouteMap(groups) {
347
- const map = new Map;
348
- for (const { prefix, service, hooks } of groups) {
349
- for (const [, method] of Object.entries(service.methods)) {
350
- if (method.expose && !method.expose.includes("HTTP"))
351
- continue;
352
- const servicePath = joinPath("/", service.prefix, method.path === "/" ? "" : method.path);
353
- const fullPath = prefix ? joinPath(prefix, servicePath) : servicePath;
354
- const segments = fullPath.split("/").filter(Boolean);
355
- const entries = map.get(method.method) ?? [];
356
- entries.push({ method, service, pattern: fullPath, segments, groupHooks: hooks });
357
- map.set(method.method, entries);
358
- }
359
- }
360
- for (const [, entries] of map) {
361
- entries.sort((a, b) => {
362
- const len = Math.min(a.segments.length, b.segments.length);
363
- for (let i = 0;i < len; i++) {
364
- const aIsParam = a.segments[i]?.startsWith(":");
365
- const bIsParam = b.segments[i]?.startsWith(":");
366
- if (aIsParam !== bIsParam)
367
- return aIsParam ? 1 : -1;
368
- }
369
- return a.segments.length - b.segments.length;
370
- });
371
- }
372
- return map;
373
- }
374
- function matchRoute(routeMap, httpMethod, pathname) {
375
- const entries = routeMap.get(httpMethod);
376
- if (!entries)
377
- return null;
378
- const requestSegments = pathname.split("/").filter(Boolean);
379
- for (const entry of entries) {
380
- const pathParams = matchSegments(entry.segments, requestSegments);
381
- if (pathParams) {
382
- return {
383
- method: entry.method,
384
- service: entry.service,
385
- pathParams,
386
- groupHooks: entry.groupHooks
387
- };
388
- }
389
- }
390
- return null;
391
- }
392
- function allowedMethods(routeMap, pathname) {
393
- const requestSegments = pathname.split("/").filter(Boolean);
394
- const methods = [];
395
- for (const [method, entries] of routeMap) {
396
- for (const entry of entries) {
397
- if (matchSegments(entry.segments, requestSegments)) {
398
- methods.push(method);
399
- break;
400
- }
401
- }
402
- }
403
- return methods;
404
- }
405
- function validateRoutes(routeMap) {
406
- for (const [method, entries] of routeMap) {
407
- const seen = new Map;
408
- for (const entry of entries) {
409
- const normalized = entry.segments.map((s) => s.startsWith(":") ? ":param" : s).join("/");
410
- const key = `${method} /${normalized}`;
411
- const existing = seen.get(key);
412
- if (existing) {
413
- throw new Error(`Duplicate route: ${method} ${entry.pattern} conflicts with ${existing}`);
414
- }
415
- seen.set(key, entry.pattern);
416
- }
417
- }
418
- }
419
- function matchRawRoute(rawRoutes, httpMethod, pathname) {
420
- for (const route of rawRoutes) {
421
- if (route.method !== "ALL" && route.method !== httpMethod)
422
- continue;
423
- if (route.path.endsWith("/*")) {
424
- const prefix = route.path.slice(0, -2);
425
- if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
426
- return { route, params: {} };
427
- }
428
- continue;
429
- }
430
- if (route.path.includes("/:")) {
431
- const routeSegs = route.path.split("/").filter(Boolean);
432
- const pathSegs = pathname.split("/").filter(Boolean);
433
- const params = matchSegments(routeSegs, pathSegs);
434
- if (params)
435
- return { route, params };
436
- continue;
437
- }
438
- if (route.path === pathname)
439
- return { route, params: {} };
440
- }
441
- return null;
442
- }
443
- function staticRoute(prefix, dir) {
444
- const cleanPrefix = prefix.replace(/\/+$/, "");
445
- const cleanDir = dir.replace(/\/+$/, "");
446
- return {
447
- method: "GET",
448
- path: `${cleanPrefix}/*`,
449
- handler: async (req) => {
450
- const pathname = new URL(req.url).pathname;
451
- const rel = pathname.slice(cleanPrefix.length).replace(/^\/+/, "");
452
- const root = resolve(cleanDir);
453
- const target = resolve(root, rel);
454
- if (target !== root && !target.startsWith(root + sep)) {
455
- return new Response("Forbidden", { status: 403 });
456
- }
457
- const file = Bun.file(target);
458
- if (!await file.exists()) {
459
- return new Response("Not found", { status: 404 });
460
- }
461
- return new Response(file);
462
- }
463
- };
464
- }
465
-
466
- // src/server/create.ts
467
- function createHandler(config) {
468
- const { cors, hooks, logging = false } = config;
469
- const customLogger = typeof logging === "object" ? logging : null;
470
- const useDefaultLog = logging === true;
471
- const resolveId = config.traceId ?? resolveTraceId;
472
- const routeMap = buildRouteMap(normalizeGroups(config));
473
- validateRoutes(routeMap);
474
- async function dispatch(req, url, traceId, server) {
475
- const shouldLogRequest = logging && shouldLog(url.pathname, req.method);
476
- let reqLog;
477
- if (shouldLogRequest && useDefaultLog) {
478
- reqLog = logIncoming(req, url.pathname, traceId);
479
- }
480
- if (shouldLogRequest && customLogger) {
481
- customLogger.debug(`${req.method} ${url.pathname}`, {
482
- traceId,
483
- method: req.method,
484
- path: url.pathname,
485
- ip: extractIp(req) || undefined
486
- });
487
- reqLog = { traceId, startTime: process.hrtime.bigint() };
488
- }
489
- const logDone = (status) => {
490
- if (!reqLog)
491
- return;
492
- if (useDefaultLog)
493
- logOutgoing(req, url.pathname, status, reqLog);
494
- if (customLogger) {
495
- const durationMs = Math.round(elapsedMs(reqLog.startTime));
496
- const level = levelForStatus(status);
497
- customLogger[level](`${req.method} ${url.pathname} ${status} ${durationMs}ms`, buildLogFields(req.method, url.pathname, status, durationMs, reqLog.traceId));
498
- }
499
- };
500
- const respondError = async (err, errCtx, endpoint) => {
501
- if (hooks?.onError) {
502
- try {
503
- const response = await hooks.onError(errCtx ?? buildErrorContext(req, url, traceId), err, endpoint);
504
- if (response instanceof Response) {
505
- const withCors = applyCors(response, cors, req);
506
- logDone(withCors.status);
507
- return withCors;
508
- }
509
- } catch {}
510
- }
511
- const appErr = normalizeError(err);
512
- logDone(appErr.status);
513
- return json(appErr.toJSON(), appErr.status, cors, req);
514
- };
515
- if (cors && req.method === "OPTIONS") {
516
- const res = corsPreflightResponse(cors, req);
517
- logDone(204);
518
- return res;
519
- }
520
- if (hooks?.onRequest) {
521
- const earlyResponse = await hooks.onRequest(req);
522
- if (earlyResponse instanceof Response) {
523
- logDone(earlyResponse.status);
524
- return earlyResponse;
525
- }
526
- }
527
- if (config.rawRoutes) {
528
- const rawMatch = matchRawRoute(config.rawRoutes, req.method, url.pathname);
529
- if (rawMatch) {
530
- try {
531
- const res = await rawMatch.route.handler(req, {
532
- params: rawMatch.params,
533
- server
534
- });
535
- const withCors = applyCors(res, cors, req);
536
- logDone(withCors.status);
537
- return withCors;
538
- } catch (err) {
539
- return respondError(err);
540
- }
541
- }
542
- }
543
- const match = matchRoute(routeMap, req.method, url.pathname);
544
- if (!match) {
545
- const allow = allowedMethods(routeMap, url.pathname);
546
- if (allow.length > 0) {
547
- const res = await respondError(new AppError("METHOD_NOT_ALLOWED", `Method ${req.method} not allowed`, 405));
548
- res.headers.set("Allow", allow.join(", "));
549
- return res;
550
- }
551
- return respondError(new AppError("NOT_FOUND", "Not found", 404));
552
- }
553
- const { method, pathParams, groupHooks } = match;
554
- let ctx;
555
- try {
556
- ctx = await buildContext(req, url, method, pathParams, traceId);
557
- if (hooks?.beforeHandle) {
558
- await hooks.beforeHandle(ctx, method);
559
- }
560
- if (groupHooks?.beforeHandle) {
561
- await groupHooks.beforeHandle(ctx, method);
562
- }
563
- let result = await method.handler(ctx);
564
- if (groupHooks?.afterHandle) {
565
- const transformed = await groupHooks.afterHandle(ctx, result, method);
566
- if (transformed !== undefined)
567
- result = transformed;
568
- }
569
- if (hooks?.afterHandle) {
570
- const transformed = await hooks.afterHandle(ctx, result, method);
571
- if (transformed !== undefined)
572
- result = transformed;
573
- }
574
- if (method.outputSchema) {
575
- result = method.outputSchema.parse(result);
576
- }
577
- if (result === undefined || result === null) {
578
- logDone(204);
579
- return new Response(null, { status: 204, headers: corsHeaders2(cors, req) });
580
- }
581
- logDone(200);
582
- return json(result, 200, cors, req);
583
- } catch (err) {
584
- return respondError(err, ctx, method);
585
- }
586
- }
587
- return async (req, server) => {
588
- const url = new URL(req.url);
589
- const traceId = resolveId(req);
590
- const response = await dispatch(req, url, traceId, server);
591
- if (!response.headers.has("x-request-id")) {
592
- response.headers.set("x-request-id", traceId);
593
- }
594
- return response;
595
- };
596
- }
597
- function createServer(config) {
598
- const { routes, websocket, development, bun: bunExtra, port = 3000, hostname } = config;
599
- const fetch = createHandler(config);
600
- return websocket ? Bun.serve({
601
- ...bunExtra,
602
- ...routes && { routes },
603
- ...development && { development },
604
- port,
605
- hostname,
606
- websocket,
607
- fetch
608
- }) : Bun.serve({
609
- ...bunExtra,
610
- ...development && { development },
611
- port,
612
- hostname,
613
- fetch
614
- });
615
- }
616
- function normalizeGroups(config) {
617
- const result = [];
618
- if (config.services) {
619
- for (const service of config.services) {
620
- result.push({ prefix: "", service });
621
- }
622
- }
623
- if (config.groups) {
624
- for (const group of config.groups) {
625
- for (const service of group.services) {
626
- result.push({ prefix: group.pathPrefix ?? "", service, hooks: group.hooks });
627
- }
628
- }
629
- }
630
- return result;
631
- }
632
- function corsHeaders2(cors, req) {
633
- if (!cors)
634
- return {};
635
- return corsHeaders(cors, req.headers.get("origin"));
636
- }
637
- function applyCors(res, cors, req) {
638
- const extra = corsHeaders2(cors, req);
639
- if (Object.keys(extra).length === 0)
640
- return res;
641
- const headers = new Headers(res.headers);
642
- for (const [key, value] of Object.entries(extra))
643
- headers.set(key, value);
644
- return new Response(res.body, {
645
- status: res.status,
646
- statusText: res.statusText,
647
- headers
648
- });
649
- }
650
- function json(data, status, cors, req) {
651
- return Response.json(data, { status, headers: corsHeaders2(cors, req) });
652
- }
653
87
  // src/server/event-bus.ts
654
88
  function createEventBus() {
655
89
  const subscriptions = new Map;
@@ -785,7 +219,7 @@ function defineCookie(config) {
785
219
  function decodeBase64Url(segment) {
786
220
  const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
787
221
  const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
788
- return Uint8Array.from(atob(padded), (c2) => c2.charCodeAt(0));
222
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
789
223
  }
790
224
  async function verifyJwt(token, secret) {
791
225
  const [headerB64, payloadB64, signatureB64] = token.split(".");
@@ -0,0 +1,12 @@
1
+ import type { HandlerConfig } from './types';
2
+ export interface NodeServerConfig extends HandlerConfig {
3
+ port?: number;
4
+ hostname?: string;
5
+ }
6
+ export interface NodeServerHandle {
7
+ url: string;
8
+ port: number;
9
+ close(closeActive?: boolean): Promise<void>;
10
+ }
11
+ export declare function serveNode(config: NodeServerConfig): Promise<NodeServerHandle>;
12
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/server/node.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgBnF"}
@@ -73,40 +73,43 @@ export interface RawRoute {
73
73
  */
74
74
  handler: (req: Request, ctx: RawRouteContext) => Response | Promise<Response>;
75
75
  }
76
- type BunServeOptions = Parameters<typeof Bun.serve>[0];
77
- type BunWebSocketHandlers = BunServeOptions extends {
78
- websocket?: infer T;
79
- } ? T : never;
80
- type BunRoutes = BunServeOptions extends {
81
- routes?: infer T;
82
- } ? T : never;
83
- type BunDevelopmentOptions = BunServeOptions extends {
84
- development?: infer T;
85
- } ? T : never;
86
- export type ServerPassthrough = Omit<BunServeOptions, 'fetch' | 'port' | 'hostname' | 'unix' | 'routes' | 'websocket' | 'development'>;
87
76
  export interface StitchLogger {
88
77
  info(msg: string, data?: Record<string, unknown>): void;
89
78
  warn(msg: string, data?: Record<string, unknown>): void;
90
79
  error(msg: string, data?: Record<string, unknown>): void;
91
80
  debug(msg: string, data?: Record<string, unknown>): void;
92
81
  }
93
- export interface ServerConfig {
82
+ /**
83
+ * Runtime-neutral handler config — everything `createHandler` needs.
84
+ * No Bun globals, no Bun types. This is the portability seam.
85
+ */
86
+ export interface HandlerConfig {
94
87
  services?: ServiceDef[];
95
88
  groups?: RouteGroup[];
96
- /** Non-contract routes — auth, webhooks, static, socket.io. */
97
89
  rawRoutes?: RawRoute[];
98
- port?: number;
99
- hostname?: string;
100
90
  cors?: CorsConfig;
101
91
  hooks?: LifecycleHooks;
102
92
  logging?: boolean | StitchLogger;
103
- /**
104
- * Resolve the per-request trace id. Default: a trusted `x-request-id` /
105
- * `x-trace-id` header, else a generated id. Override to reuse an id the
106
- * project already owns (e.g. an `AsyncLocalStorage` request context) so
107
- * request logs and application logs share one id.
108
- */
109
93
  traceId?: (req: Request) => string;
94
+ }
95
+ type BunServeOptions = Parameters<typeof Bun.serve>[0];
96
+ type BunWebSocketHandlers = BunServeOptions extends {
97
+ websocket?: infer T;
98
+ } ? T : never;
99
+ type BunRoutes = BunServeOptions extends {
100
+ routes?: infer T;
101
+ } ? T : never;
102
+ type BunDevelopmentOptions = BunServeOptions extends {
103
+ development?: infer T;
104
+ } ? T : never;
105
+ export type ServerPassthrough = Omit<BunServeOptions, 'fetch' | 'port' | 'hostname' | 'unix' | 'routes' | 'websocket' | 'development'>;
106
+ /**
107
+ * Full config for `createServer` — extends `HandlerConfig` with Bun-specific
108
+ * options (`Bun.serve` routes, websocket, development, passthrough).
109
+ */
110
+ export interface BunServerConfig extends HandlerConfig {
111
+ port?: number;
112
+ hostname?: string;
110
113
  routes?: BunRoutes;
111
114
  websocket?: BunWebSocketHandlers;
112
115
  development?: BunDevelopmentOptions;