stitchkit 0.3.0 → 0.5.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 (99) hide show
  1. package/README.md +11 -10
  2. package/dist/browser/client.d.ts.map +1 -1
  3. package/dist/browser/socket-io.d.ts +17 -0
  4. package/dist/browser/socket-io.d.ts.map +1 -1
  5. package/dist/cli.d.ts +23 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +21 -0
  8. package/dist/contract/define.d.ts +45 -2
  9. package/dist/contract/define.d.ts.map +1 -1
  10. package/dist/contract/index.d.ts +1 -1
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +2 -1
  13. package/dist/index-0ed3bx43.js +33 -0
  14. package/dist/index-0ma1eqv4.js +1007 -0
  15. package/dist/index-1cf8jkhf.js +219 -0
  16. package/dist/index-37x76zdn.js +4 -0
  17. package/dist/index-48ffdxgk.js +6 -0
  18. package/dist/{index-4kz3aqpx.js → index-78q1qm7v.js} +4 -1
  19. package/dist/{index-kckky6zw.js → index-afzt3nmx.js} +1 -1
  20. package/dist/{index-4b1j82gp.js → index-d4rwrjbc.js} +1 -1
  21. package/dist/{index-dpj1jcys.js → index-kgjnt3hn.js} +157 -4
  22. package/dist/{index-0t460v7k.js → index-vhdvv00d.js} +1 -1
  23. package/dist/index-x3fcszf8.js +8 -0
  24. package/dist/index.js +16 -4
  25. package/dist/internal/http-input.d.ts +13 -0
  26. package/dist/internal/http-input.d.ts.map +1 -0
  27. package/dist/internal/secure-fetch.d.ts +27 -0
  28. package/dist/internal/secure-fetch.d.ts.map +1 -0
  29. package/dist/internal/within-dir.d.ts.map +1 -1
  30. package/dist/node.d.ts +10 -1
  31. package/dist/node.d.ts.map +1 -1
  32. package/dist/node.js +38 -7
  33. package/dist/observability/audit.d.ts.map +1 -1
  34. package/dist/observability/event.d.ts +4 -0
  35. package/dist/observability/event.d.ts.map +1 -1
  36. package/dist/observability/index.js +3 -0
  37. package/dist/react.js +2 -0
  38. package/dist/server/implement.d.ts.map +1 -1
  39. package/dist/server/index.d.ts +5 -2
  40. package/dist/server/index.d.ts.map +1 -1
  41. package/dist/server/index.js +172 -216
  42. package/dist/server/middleware/auth.d.ts +17 -0
  43. package/dist/server/middleware/auth.d.ts.map +1 -1
  44. package/dist/server/middleware/pkce.d.ts +17 -0
  45. package/dist/server/middleware/pkce.d.ts.map +1 -0
  46. package/dist/server/node.d.ts +9 -0
  47. package/dist/server/node.d.ts.map +1 -1
  48. package/dist/server/openapi.d.ts +52 -0
  49. package/dist/server/openapi.d.ts.map +1 -0
  50. package/dist/server/socket-io.d.ts +60 -15
  51. package/dist/server/socket-io.d.ts.map +1 -1
  52. package/dist/server/types.d.ts +11 -1
  53. package/dist/server/types.d.ts.map +1 -1
  54. package/dist/server/websocket.d.ts +73 -0
  55. package/dist/server/websocket.d.ts.map +1 -0
  56. package/dist/tools/cli-args.d.ts +68 -0
  57. package/dist/tools/cli-args.d.ts.map +1 -0
  58. package/dist/tools/cli-format.d.ts +39 -0
  59. package/dist/tools/cli-format.d.ts.map +1 -0
  60. package/dist/tools/cli-wait.d.ts +45 -0
  61. package/dist/tools/cli-wait.d.ts.map +1 -0
  62. package/dist/tools/cli.d.ts +77 -0
  63. package/dist/tools/cli.d.ts.map +1 -0
  64. package/dist/tools/coerce.d.ts.map +1 -1
  65. package/dist/tools/json-schema.d.ts +29 -4
  66. package/dist/tools/json-schema.d.ts.map +1 -1
  67. package/dist/tools/mcp-app.d.ts +59 -0
  68. package/dist/tools/mcp-app.d.ts.map +1 -0
  69. package/dist/tools/mcp-handler.d.ts +18 -0
  70. package/dist/tools/mcp-handler.d.ts.map +1 -1
  71. package/dist/tools/mcp.d.ts +9 -0
  72. package/dist/tools/mcp.d.ts.map +1 -1
  73. package/dist/tools/mount-download.d.ts +30 -0
  74. package/dist/tools/mount-download.d.ts.map +1 -0
  75. package/dist/tools/mount-upload.d.ts +19 -0
  76. package/dist/tools/mount-upload.d.ts.map +1 -0
  77. package/dist/tools/mount-wait.d.ts +36 -0
  78. package/dist/tools/mount-wait.d.ts.map +1 -0
  79. package/dist/tools/mount.d.ts +6 -2
  80. package/dist/tools/mount.d.ts.map +1 -1
  81. package/dist/tools/native-result.d.ts +17 -0
  82. package/dist/tools/native-result.d.ts.map +1 -0
  83. package/dist/tools/oauth-metadata.d.ts +28 -0
  84. package/dist/tools/oauth-metadata.d.ts.map +1 -0
  85. package/dist/tools/oauth-provider.d.ts +85 -0
  86. package/dist/tools/oauth-provider.d.ts.map +1 -0
  87. package/dist/tools/remote.d.ts +2 -2
  88. package/dist/tools/remote.d.ts.map +1 -1
  89. package/dist/tools/toolkit.d.ts +51 -0
  90. package/dist/tools/toolkit.d.ts.map +1 -0
  91. package/dist/tools/view-file.d.ts +12 -0
  92. package/dist/tools/view-file.d.ts.map +1 -1
  93. package/dist/tools/wait-core.d.ts +33 -0
  94. package/dist/tools/wait-core.d.ts.map +1 -0
  95. package/dist/tools.d.ts +12 -1
  96. package/dist/tools.d.ts.map +1 -1
  97. package/dist/tools.js +587 -421
  98. package/package.json +22 -9
  99. package/dist/index-q6ja2qwq.js +0 -7
@@ -0,0 +1,219 @@
1
+ import {
2
+ forbidden,
3
+ unauthorized
4
+ } from "./index-afzt3nmx.js";
5
+ import {
6
+ isUnsafeKey,
7
+ safeJsonParse
8
+ } from "./index-kzfs85xp.js";
9
+ import {
10
+ isRecord
11
+ } from "./index-809wc1tt.js";
12
+
13
+ // src/server/middleware/cookies.ts
14
+ function parseCookies(header) {
15
+ if (!header)
16
+ return {};
17
+ const cookies = {};
18
+ for (const pair of header.split(";")) {
19
+ const eq = pair.indexOf("=");
20
+ if (eq === -1)
21
+ continue;
22
+ const key = pair.slice(0, eq).trim();
23
+ if (!key || isUnsafeKey(key))
24
+ continue;
25
+ const raw = pair.slice(eq + 1).trim();
26
+ try {
27
+ cookies[key] = decodeURIComponent(raw);
28
+ } catch {
29
+ cookies[key] = raw;
30
+ }
31
+ }
32
+ return cookies;
33
+ }
34
+ function serializeCookie(name, value, opts = {}) {
35
+ let cookie = `${name}=${encodeURIComponent(value)}`;
36
+ if (opts.maxAge !== undefined)
37
+ cookie += `; Max-Age=${opts.maxAge}`;
38
+ if (opts.path)
39
+ cookie += `; Path=${opts.path}`;
40
+ if (opts.domain)
41
+ cookie += `; Domain=${opts.domain}`;
42
+ if (opts.secure)
43
+ cookie += "; Secure";
44
+ if (opts.httpOnly)
45
+ cookie += "; HttpOnly";
46
+ if (opts.sameSite)
47
+ cookie += `; SameSite=${opts.sameSite}`;
48
+ return cookie;
49
+ }
50
+ function defineCookie(config) {
51
+ const { name, ...options } = config;
52
+ return {
53
+ get(req) {
54
+ return parseCookies(req.headers.get("cookie"))[name];
55
+ },
56
+ set(value) {
57
+ return serializeCookie(name, value, options);
58
+ },
59
+ clear() {
60
+ return serializeCookie(name, "", { ...options, maxAge: 0 });
61
+ }
62
+ };
63
+ }
64
+
65
+ // src/server/middleware/auth.ts
66
+ var MAX_TOKEN_BYTES = 8192;
67
+ function decodeBase64Url(segment) {
68
+ if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) {
69
+ throw new Error("invalid base64url segment");
70
+ }
71
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
72
+ const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
73
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
74
+ }
75
+ async function verifyJwt(token, secret, options = {}) {
76
+ if (!secret)
77
+ throw new Error("verifyJwt: a non-empty secret is required");
78
+ if (token.length > MAX_TOKEN_BYTES)
79
+ throw unauthorized("Token too large");
80
+ const [headerB64, payloadB64, signatureB64] = token.split(".");
81
+ if (!headerB64 || !payloadB64 || !signatureB64)
82
+ throw unauthorized("Invalid token format");
83
+ let header;
84
+ let payloadRaw;
85
+ let signature;
86
+ try {
87
+ const decoder = new TextDecoder;
88
+ header = JSON.parse(decoder.decode(decodeBase64Url(headerB64)));
89
+ payloadRaw = safeJsonParse(decoder.decode(decodeBase64Url(payloadB64)));
90
+ signature = decodeBase64Url(signatureB64);
91
+ } catch {
92
+ throw unauthorized("Malformed token");
93
+ }
94
+ if (!isRecord(payloadRaw))
95
+ throw unauthorized("Malformed token");
96
+ const payload = payloadRaw;
97
+ if (header.alg !== "HS256")
98
+ throw unauthorized("Unsupported token algorithm");
99
+ const encoder = new TextEncoder;
100
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
101
+ const data = encoder.encode(`${headerB64}.${payloadB64}`);
102
+ const valid = await crypto.subtle.verify("HMAC", key, signature, data);
103
+ if (!valid)
104
+ throw unauthorized("Invalid token signature");
105
+ const leeway = options.leewaySeconds ?? 60;
106
+ const now = Date.now() / 1000;
107
+ if ("exp" in payload) {
108
+ if (typeof payload.exp !== "number")
109
+ throw unauthorized("Malformed token");
110
+ if (payload.exp < now - leeway)
111
+ throw unauthorized("Token expired");
112
+ }
113
+ if ("nbf" in payload) {
114
+ if (typeof payload.nbf !== "number")
115
+ throw unauthorized("Malformed token");
116
+ if (payload.nbf > now + leeway)
117
+ throw unauthorized("Token not yet valid");
118
+ }
119
+ if (options.issuer !== undefined && payload.iss !== options.issuer) {
120
+ throw unauthorized("Token issuer mismatch");
121
+ }
122
+ if (options.audience !== undefined) {
123
+ const aud = payload.aud;
124
+ const ok = Array.isArray(aud) ? aud.includes(options.audience) : aud === options.audience;
125
+ if (!ok)
126
+ throw unauthorized("Token audience mismatch");
127
+ }
128
+ return payload;
129
+ }
130
+ function encodeBase64Url(bytes) {
131
+ let binary = "";
132
+ for (const byte of bytes)
133
+ binary += String.fromCharCode(byte);
134
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
135
+ }
136
+ async function signJwt(payload, secret, options = {}) {
137
+ if (!secret)
138
+ throw new Error("signJwt: a non-empty secret is required");
139
+ const now = Math.floor(Date.now() / 1000);
140
+ const claims = {
141
+ ...payload,
142
+ iat: now,
143
+ ...options.expiresInSec !== undefined && { exp: now + options.expiresInSec },
144
+ ...options.issuer !== undefined && { iss: options.issuer },
145
+ ...options.audience !== undefined && { aud: options.audience },
146
+ ...options.subject !== undefined && { sub: options.subject }
147
+ };
148
+ const encoder = new TextEncoder;
149
+ const headerB64 = encodeBase64Url(encoder.encode(JSON.stringify({ alg: "HS256", typ: "JWT" })));
150
+ const payloadB64 = encodeBase64Url(encoder.encode(JSON.stringify(claims)));
151
+ const signingInput = `${headerB64}.${payloadB64}`;
152
+ const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
153
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(signingInput));
154
+ const signatureB64 = encodeBase64Url(new Uint8Array(signature));
155
+ return `${signingInput}.${signatureB64}`;
156
+ }
157
+ function extractToken(req, cookieName) {
158
+ const authHeader = req.headers.get("authorization");
159
+ if (authHeader?.startsWith("Bearer "))
160
+ return authHeader.slice(7);
161
+ if (cookieName) {
162
+ const cookies = parseCookies(req.headers.get("cookie"));
163
+ return cookies[cookieName] ?? null;
164
+ }
165
+ return null;
166
+ }
167
+ function createAuthHook(config) {
168
+ const onAnonymous = config.onAnonymous ?? (() => unauthorized());
169
+ const onForbidden = config.onForbidden ?? (() => forbidden());
170
+ return async (ctx, endpoint) => {
171
+ const identity = ctx.source === "http" ? await config.resolve(ctx) : await config.resolveFromContext?.(ctx) ?? null;
172
+ config.inject?.(ctx, identity);
173
+ const scope = endpoint.scope ?? config.defaultScope;
174
+ if (!scope)
175
+ return;
176
+ const rule = config.rules[scope];
177
+ if (!rule) {
178
+ throw new Error(`[stitchkit] auth: no rule for scope "${scope}"`);
179
+ }
180
+ if (rule === "public")
181
+ return;
182
+ if (!identity) {
183
+ onAnonymous();
184
+ return;
185
+ }
186
+ if (rule === "authenticated")
187
+ return;
188
+ if (!await rule(identity, ctx))
189
+ onForbidden();
190
+ };
191
+ }
192
+ function createBearerResolver(config) {
193
+ return async (req) => {
194
+ const token = extractToken(req);
195
+ if (!token)
196
+ return null;
197
+ return config.lookup(token, req);
198
+ };
199
+ }
200
+
201
+ // src/server/middleware/pkce.ts
202
+ function base64UrlEncode(bytes) {
203
+ let binary = "";
204
+ for (const byte of bytes)
205
+ binary += String.fromCharCode(byte);
206
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
207
+ }
208
+ async function deriveCodeChallenge(verifier) {
209
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
210
+ return base64UrlEncode(new Uint8Array(digest));
211
+ }
212
+ async function verifyPkce(verifier, challenge) {
213
+ if (!verifier || !challenge)
214
+ return false;
215
+ const derived = await deriveCodeChallenge(verifier);
216
+ return derived === challenge;
217
+ }
218
+
219
+ export { parseCookies, serializeCookie, defineCookie, verifyJwt, signJwt, extractToken, createAuthHook, createBearerResolver, deriveCodeChallenge, verifyPkce };
@@ -0,0 +1,4 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ export { __require };
@@ -0,0 +1,6 @@
1
+ // src/internal/http-input.ts
2
+ function inputIsQuery(method) {
3
+ return method === "GET" || method === "DELETE";
4
+ }
5
+
6
+ export { inputIsQuery };
@@ -1,3 +1,6 @@
1
+ import {
2
+ inputIsQuery
3
+ } from "./index-48ffdxgk.js";
1
4
  import {
2
5
  isRecord,
3
6
  mapObject,
@@ -261,7 +264,7 @@ function createFetchMethod(endpoint, prefix, config) {
261
264
  Accept: "application/json",
262
265
  ...typeof config.headers === "function" ? config.headers() : config.headers
263
266
  };
264
- const isQuery = endpoint.method === "GET" || endpoint.method === "DELETE";
267
+ const isQuery = inputIsQuery(endpoint.method);
265
268
  const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
266
269
  if (isQuery && args) {
267
270
  const remaining = stripParams(args, endpoint.path);
@@ -1,5 +1,5 @@
1
1
  // src/contract/define.ts
2
- var ALL_TRANSPORTS = ["HTTP", "MCP", "AGENT"];
2
+ var ALL_TRANSPORTS = ["HTTP", "MCP", "AGENT", "CLI"];
3
3
  function defineContract(meta, endpoints) {
4
4
  const toolTransports = new Map;
5
5
  for (const [key, ep] of Object.entries(endpoints)) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  normalizeError
3
- } from "./index-0t460v7k.js";
3
+ } from "./index-vhdvv00d.js";
4
4
 
5
5
  // src/server/stream.ts
6
6
  function streamSSE(generator) {
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  isWithinDir
3
- } from "./index-q6ja2qwq.js";
3
+ } from "./index-x3fcszf8.js";
4
4
  import {
5
5
  normalizeError,
6
6
  validateHandlerOutput
7
- } from "./index-0t460v7k.js";
7
+ } from "./index-vhdvv00d.js";
8
8
  import {
9
9
  AppError,
10
10
  badRequest
11
- } from "./index-kckky6zw.js";
11
+ } from "./index-afzt3nmx.js";
12
12
  import {
13
13
  extractIp,
14
14
  getClientInfo,
@@ -20,6 +20,12 @@ import {
20
20
  isUnsafeKey,
21
21
  safeJsonParse
22
22
  } from "./index-kzfs85xp.js";
23
+ import {
24
+ typedEntries
25
+ } from "./index-809wc1tt.js";
26
+ import {
27
+ __require
28
+ } from "./index-37x76zdn.js";
23
29
 
24
30
  // src/server/multipart.ts
25
31
  var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
@@ -674,4 +680,151 @@ function json(data, status, cors, req) {
674
680
  return Response.json(data, { status, headers: corsHeaders2(cors, req) });
675
681
  }
676
682
 
677
- export { parseMultipart, corsHeaders, corsPreflightResponse, staticRoute, createHandler, createServer };
683
+ // src/server/implement.ts
684
+ function implement(contract, handlers) {
685
+ const methods = {};
686
+ const groupScope = contract.meta.scope ?? "public";
687
+ for (const [key, endpoint] of typedEntries(contract.endpoints)) {
688
+ const typedHandler = handlers[key];
689
+ methods[String(key)] = {
690
+ method: endpoint.method,
691
+ path: endpoint.path,
692
+ desc: endpoint.desc,
693
+ toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
694
+ expose: endpoint.expose,
695
+ scope: endpoint.scope ?? groupScope,
696
+ paramsSchema: endpoint.params,
697
+ inputSchema: endpoint.input,
698
+ outputSchema: endpoint.output,
699
+ multipart: endpoint.multipart,
700
+ ui: "ui" in endpoint ? endpoint.ui : undefined,
701
+ annotations: "annotations" in endpoint ? endpoint.annotations : undefined,
702
+ meta: endpoint.meta,
703
+ handler: (ctx) => typedHandler(ctx)
704
+ };
705
+ }
706
+ return {
707
+ name: contract.meta.prefix,
708
+ prefix: contract.meta.prefix,
709
+ scope: groupScope,
710
+ methods
711
+ };
712
+ }
713
+ function createImplement() {
714
+ return (contract, handlers) => implement(contract, handlers);
715
+ }
716
+
717
+ // src/server/websocket.ts
718
+ function webSocketLane(lane) {
719
+ const { match, handlers } = lane;
720
+ return {
721
+ match,
722
+ handlers: {
723
+ message(ws, message) {
724
+ if (match(ws))
725
+ handlers.message(ws, message);
726
+ },
727
+ open(ws) {
728
+ if (match(ws))
729
+ handlers.open?.(ws);
730
+ },
731
+ close(ws, code, reason) {
732
+ if (match(ws))
733
+ handlers.close?.(ws, code, reason);
734
+ },
735
+ drain(ws) {
736
+ if (match(ws))
737
+ handlers.drain?.(ws);
738
+ },
739
+ ping(ws, data) {
740
+ if (match(ws))
741
+ handlers.ping?.(ws, data);
742
+ },
743
+ pong(ws, data) {
744
+ if (match(ws))
745
+ handlers.pong?.(ws, data);
746
+ }
747
+ }
748
+ };
749
+ }
750
+ function composeWebSocketHandlers(lanes, config) {
751
+ const pick = (ws) => lanes.find((lane) => lane.match(ws));
752
+ return {
753
+ ...config,
754
+ message(ws, message) {
755
+ pick(ws)?.handlers.message(ws, message);
756
+ },
757
+ open(ws) {
758
+ pick(ws)?.handlers.open?.(ws);
759
+ },
760
+ close(ws, code, reason) {
761
+ pick(ws)?.handlers.close?.(ws, code, reason);
762
+ },
763
+ drain(ws) {
764
+ pick(ws)?.handlers.drain?.(ws);
765
+ },
766
+ ping(ws, data) {
767
+ pick(ws)?.handlers.ping?.(ws, data);
768
+ },
769
+ pong(ws, data) {
770
+ pick(ws)?.handlers.pong?.(ws, data);
771
+ }
772
+ };
773
+ }
774
+
775
+ // src/server/socket-io.ts
776
+ var noop = () => {};
777
+ async function createSocketIOServer(config) {
778
+ const path = config.path ?? "/socket.io/";
779
+ const onBun = "Bun" in globalThis;
780
+ const transports = config.transports ?? (onBun ? ["websocket", "polling"] : ["websocket"]);
781
+ const { Server } = await import("socket.io");
782
+ const io = new Server({
783
+ path,
784
+ cors: {
785
+ origin: config.cors.origin,
786
+ credentials: config.cors.credentials ?? true,
787
+ methods: ["GET", "POST"]
788
+ },
789
+ transports,
790
+ pingTimeout: config.pingTimeout ?? 20000,
791
+ pingInterval: config.pingInterval ?? 1e4
792
+ });
793
+ if (onBun) {
794
+ const { Server: Engine } = await import("@socket.io/bun-engine");
795
+ const engine = new Engine({ path });
796
+ io.bind(engine);
797
+ const { websocket } = engine.handler();
798
+ const route = {
799
+ method: "ALL",
800
+ path: `${path.replace(/\/+$/, "")}/*`,
801
+ handler: (req, ctx) => {
802
+ if (!ctx.server) {
803
+ throw new Error("[stitchkit] createSocketIOServer route needs a running Bun server — mount it via createServer.");
804
+ }
805
+ return engine.handleRequest(req, ctx.server);
806
+ }
807
+ };
808
+ return { io, websocket, route, attach: noop };
809
+ }
810
+ return {
811
+ io,
812
+ attach: (server) => io.attach(server),
813
+ websocket: { open: noop, message: noop, close: noop, maxPayloadLength: 0 },
814
+ route: {
815
+ method: "ALL",
816
+ path: `${path.replace(/\/+$/, "")}/*`,
817
+ handler: () => {
818
+ throw new Error("[stitchkit] On Node, Socket.IO attaches via serveNode({ socket }) — this route is not mounted.");
819
+ }
820
+ }
821
+ };
822
+ }
823
+ function socketIoLane(websocket) {
824
+ return webSocketLane({
825
+ match: (_ws) => true,
826
+ handlers: websocket
827
+ });
828
+ }
829
+
830
+ export { parseMultipart, corsHeaders, corsPreflightResponse, staticRoute, createHandler, createServer, implement, createImplement, webSocketLane, composeWebSocketHandlers, createSocketIOServer, socketIoLane };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  AppError
3
- } from "./index-kckky6zw.js";
3
+ } from "./index-afzt3nmx.js";
4
4
 
5
5
  // src/internal/errors.ts
6
6
  import { z } from "zod";
@@ -0,0 +1,8 @@
1
+ // src/internal/within-dir.ts
2
+ import { sep } from "node:path";
3
+ function isWithinDir(root, target) {
4
+ const base = root.endsWith(sep) ? root.slice(0, -sep.length) : root;
5
+ return target === root || target === base || target.startsWith(base + sep);
6
+ }
7
+
8
+ export { isWithinDir };
package/dist/index.js CHANGED
@@ -3,11 +3,12 @@ import {
3
3
  createClient,
4
4
  createClients,
5
5
  createHttpClient
6
- } from "./index-4kz3aqpx.js";
6
+ } from "./index-78q1qm7v.js";
7
7
  import {
8
8
  parseSSE
9
- } from "./index-4b1j82gp.js";
10
- import"./index-0t460v7k.js";
9
+ } from "./index-d4rwrjbc.js";
10
+ import"./index-48ffdxgk.js";
11
+ import"./index-vhdvv00d.js";
11
12
  import {
12
13
  ALL_TRANSPORTS,
13
14
  AppError,
@@ -20,10 +21,18 @@ import {
20
21
  paginatedSchema,
21
22
  rateLimited,
22
23
  unauthorized
23
- } from "./index-kckky6zw.js";
24
+ } from "./index-afzt3nmx.js";
24
25
  import"./index-809wc1tt.js";
26
+ import"./index-37x76zdn.js";
25
27
  // src/browser/socket-io.ts
26
28
  import { io } from "socket.io-client";
29
+ function toIoAuth(auth) {
30
+ if (typeof auth !== "function")
31
+ return auth;
32
+ return (cb) => {
33
+ Promise.resolve().then(auth).then(cb, () => cb({}));
34
+ };
35
+ }
27
36
  function createSocketIOClient(config) {
28
37
  let socket = null;
29
38
  const connectionListeners = new Set;
@@ -42,6 +51,9 @@ function createSocketIOClient(config) {
42
51
  socket = io(config.url, {
43
52
  path: config.path ?? "/socket.io/",
44
53
  withCredentials: config.withCredentials ?? true,
54
+ auth: toIoAuth(config.auth),
55
+ ...config.query && { query: config.query },
56
+ ...config.extraHeaders && { extraHeaders: config.extraHeaders },
45
57
  transports: config.transports ?? ["websocket", "polling"],
46
58
  autoConnect: false,
47
59
  reconnection: true,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Where a method's input travels over HTTP — the single source of truth for the
3
+ * convention shared by the typed client (what it SENDS) and the OpenAPI
4
+ * generator (what it DOCUMENTS), so the two cannot drift. GET and DELETE carry
5
+ * their input as query parameters; the body verbs (POST / PUT / PATCH) carry it
6
+ * as a request body.
7
+ *
8
+ * The server runtime is a tolerant superset of this: it additionally accepts a
9
+ * DELETE *body* when the request carries an `application/json` content-type. The
10
+ * canonical form a client uses and the spec documents is the query form.
11
+ */
12
+ export declare function inputIsQuery(method: string): boolean;
13
+ //# sourceMappingURL=http-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-input.d.ts","sourceRoot":"","sources":["../../src/internal/http-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD"}
@@ -0,0 +1,27 @@
1
+ /** True for a loopback / private / link-local / ULA / CGNAT IP literal. */
2
+ export declare function isPrivateIp(ip: string): boolean;
3
+ /**
4
+ * Refuse a URL that is not `http(s)`, has no host, or whose host is — or
5
+ * resolves to — a private/internal address. Called on the initial URL and on
6
+ * every redirect hop, so a public host cannot `302` to an internal address or
7
+ * to a `file:` / `gopher:` / `data:` scheme.
8
+ */
9
+ export declare function assertPublicUrl(url: URL): Promise<void>;
10
+ /**
11
+ * Fetch a URL with the SSRF guard enforced on the initial URL and on every
12
+ * redirect hop — a public host can otherwise `302` to an internal address or a
13
+ * non-http(s) scheme.
14
+ *
15
+ * Note: the DNS resolution here and the resolution `fetch` performs are
16
+ * separate, so a hostile resolver retains a narrow rebinding window. The
17
+ * per-hop re-check is the larger and fully-closed hole; a static private DNS
18
+ * record is rejected outright.
19
+ */
20
+ export declare function fetchGuarded(start: URL, allowPrivate: boolean): Promise<Response>;
21
+ /**
22
+ * Read a response body into a buffer, aborting (and returning `null`) if it
23
+ * exceeds `max` bytes — the size cap that keeps a hostile or unbounded stream
24
+ * from OOM-ing the process. A missing body yields an empty buffer.
25
+ */
26
+ export declare function readCapped(res: Response, max: number): Promise<Buffer | null>;
27
+ //# sourceMappingURL=secure-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-fetch.d.ts","sourceRoot":"","sources":["../../src/internal/secure-fetch.ts"],"names":[],"mappings":"AA8CA,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAkC/C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B7D;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAavF;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"within-dir.d.ts","sourceRoot":"","sources":["../../src/internal/within-dir.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjE"}
1
+ {"version":3,"file":"within-dir.d.ts","sourceRoot":"","sources":["../../src/internal/within-dir.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQjE"}
package/dist/node.d.ts CHANGED
@@ -1,4 +1,13 @@
1
+ /**
2
+ * `stitchkit/node` — the Node entrypoint. Everything a Node app needs without
3
+ * touching the Bun-named `createServer`: the Fetch-clean `createHandler`, the
4
+ * srvx-backed `serveNode`, `implement`, the error helpers and (lazily-loaded,
5
+ * Node-safe) `createSocketIOServer`.
6
+ */
7
+ export { AppError, appError, badRequest, conflict, forbidden, notFound, rateLimited, unauthorized, } from './contract';
1
8
  export { createHandler } from './server/create';
9
+ export { createImplement, implement } from './server/implement';
2
10
  export { type NodeServerConfig, type NodeServerHandle, serveNode } from './server/node';
3
- export type { HandlerConfig } from './server/types';
11
+ export { createSocketIOServer, type SocketIOServerConfig, type SocketIOServerHandle, } from './server/socket-io';
12
+ export type { HandlerConfig, RawRoute, RawRouteContext, ServiceDef, } from './server/types';
4
13
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,QAAQ,EACR,eAAe,EACf,UAAU,GACX,MAAM,gBAAgB,CAAC"}
package/dist/node.js CHANGED
@@ -1,19 +1,39 @@
1
1
  import {
2
- createHandler
3
- } from "./index-dpj1jcys.js";
4
- import"./index-q6ja2qwq.js";
5
- import"./index-0t460v7k.js";
6
- import"./index-kckky6zw.js";
2
+ createHandler,
3
+ createImplement,
4
+ createSocketIOServer,
5
+ implement
6
+ } from "./index-kgjnt3hn.js";
7
+ import"./index-x3fcszf8.js";
8
+ import"./index-vhdvv00d.js";
9
+ import {
10
+ AppError,
11
+ appError,
12
+ badRequest,
13
+ conflict,
14
+ forbidden,
15
+ notFound,
16
+ rateLimited,
17
+ unauthorized
18
+ } from "./index-afzt3nmx.js";
7
19
  import"./index-mwmpw6j1.js";
8
20
  import"./index-kzfs85xp.js";
9
21
  import"./index-809wc1tt.js";
22
+ import"./index-37x76zdn.js";
10
23
  // src/server/node.ts
11
24
  import { serve } from "srvx";
12
25
  async function serveNode(config) {
13
- const { port = 3000, hostname, ...handlerConfig } = config;
26
+ const { port = 3000, hostname, socket, ...handlerConfig } = config;
14
27
  const handler = createHandler(handlerConfig);
15
28
  const server = serve({ port, hostname, fetch: handler });
16
29
  await server.ready();
30
+ if (socket) {
31
+ const nodeServer = server.node?.server;
32
+ if (!nodeServer || !("maxRequestsPerSocket" in nodeServer)) {
33
+ throw new Error("[stitchkit] serveNode: expected a node:http.Server for Socket.IO (none / http2).");
34
+ }
35
+ socket.attach(nodeServer);
36
+ }
17
37
  const listenUrl = server.url ?? `http://${hostname ?? "localhost"}:${port}`;
18
38
  const resolvedPort = Number(new URL(listenUrl).port) || port;
19
39
  const resolvedHost = hostname ?? "localhost";
@@ -24,6 +44,17 @@ async function serveNode(config) {
24
44
  };
25
45
  }
26
46
  export {
47
+ unauthorized,
27
48
  serveNode,
28
- createHandler
49
+ rateLimited,
50
+ notFound,
51
+ implement,
52
+ forbidden,
53
+ createSocketIOServer,
54
+ createImplement,
55
+ createHandler,
56
+ conflict,
57
+ badRequest,
58
+ appError,
59
+ AppError
29
60
  };
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,IAAI,EAAE,CAAC,CAAC,EACN,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,KACpD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;CACzB;AAmBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CA6F9D"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/observability/audit.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,kBAAkB,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAe,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAC;AAGhF,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;IAC1C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,IAAI,EAAE,CAAC,CAAC,EACN,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,KACpD,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD;;;OAGG;IACH,QAAQ,EAAE,aAAa,CAAC;CACzB;AAmBD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CA+F9D"}
@@ -39,6 +39,10 @@ export interface RequestEvent {
39
39
  responseBytes: number;
40
40
  /** Resolved user id, when authenticated. */
41
41
  userId?: string;
42
+ /** How the caller authenticated, when the app records it on the context. */
43
+ authMethod?: string;
44
+ /** Client id the call was made under (e.g. an OAuth client), when recorded. */
45
+ clientId?: string;
42
46
  /** Client IP. */
43
47
  ipAddress?: string;
44
48
  /** Client user-agent. */