stitchkit 0.54.0 → 0.55.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 (100) hide show
  1. package/README.md +4 -1
  2. package/dist/browser/socket-io.d.ts +11 -1
  3. package/dist/browser/socket-io.d.ts.map +1 -1
  4. package/dist/cli.js +14 -9
  5. package/dist/contract/file-ref.d.ts +13 -0
  6. package/dist/contract/file-ref.d.ts.map +1 -0
  7. package/dist/contract/index.d.ts +1 -0
  8. package/dist/contract/index.d.ts.map +1 -1
  9. package/dist/contract/index.js +21 -17
  10. package/dist/files/boundary.d.ts +52 -0
  11. package/dist/files/boundary.d.ts.map +1 -0
  12. package/dist/files.d.ts +3 -0
  13. package/dist/files.d.ts.map +1 -0
  14. package/dist/files.js +16 -0
  15. package/dist/{index-7vb44hy8.js → index-0gv7k0ra.js} +127 -92
  16. package/dist/index-1bx83sw4.js +4 -0
  17. package/dist/index-1k16zv57.js +269 -0
  18. package/dist/index-6k1937bx.js +20 -0
  19. package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
  20. package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
  21. package/dist/index-h60df7rj.js +110 -0
  22. package/dist/{index-yez419px.js → index-js7yexng.js} +19 -6
  23. package/dist/index-k4dftwf7.js +66 -0
  24. package/dist/{index-z7761jk2.js → index-npye5kt7.js} +16 -22
  25. package/dist/{index-b15rtha1.js → index-pzc32v75.js} +61 -3
  26. package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
  27. package/dist/{index-gvha32jh.js → index-qx84bqzk.js} +2 -2
  28. package/dist/{index-pfqjb5xy.js → index-rgd18hx3.js} +23 -5
  29. package/dist/index-sbdmyz75.js +18 -0
  30. package/dist/{index-173tvqcp.js → index-v4bbq3p4.js} +9 -114
  31. package/dist/index.js +162 -35
  32. package/dist/node.js +30 -27
  33. package/dist/observability/index.js +28 -25
  34. package/dist/react.js +2 -2
  35. package/dist/realtime/contract.d.ts +9 -0
  36. package/dist/realtime/contract.d.ts.map +1 -1
  37. package/dist/realtime/index.d.ts +2 -1
  38. package/dist/realtime/index.d.ts.map +1 -1
  39. package/dist/realtime/request.d.ts +21 -0
  40. package/dist/realtime/request.d.ts.map +1 -0
  41. package/dist/realtime/socket.d.ts +3 -0
  42. package/dist/realtime/socket.d.ts.map +1 -1
  43. package/dist/remote.d.ts +9 -0
  44. package/dist/remote.d.ts.map +1 -0
  45. package/dist/remote.js +79 -0
  46. package/dist/server/context-contribution.d.ts +10 -0
  47. package/dist/server/context-contribution.d.ts.map +1 -0
  48. package/dist/server/context.d.ts.map +1 -1
  49. package/dist/server/index.d.ts +2 -1
  50. package/dist/server/index.d.ts.map +1 -1
  51. package/dist/server/index.js +133 -88
  52. package/dist/server/lifecycle.d.ts +4 -0
  53. package/dist/server/lifecycle.d.ts.map +1 -0
  54. package/dist/server/middleware/auth.d.ts +35 -7
  55. package/dist/server/middleware/auth.d.ts.map +1 -1
  56. package/dist/server/types.d.ts +1 -1
  57. package/dist/server/types.d.ts.map +1 -1
  58. package/dist/testing/surface-conformance.d.ts +47 -0
  59. package/dist/testing/surface-conformance.d.ts.map +1 -0
  60. package/dist/testing/surface-manifest.d.ts +156 -0
  61. package/dist/testing/surface-manifest.d.ts.map +1 -0
  62. package/dist/testing.d.ts +2 -0
  63. package/dist/testing.d.ts.map +1 -1
  64. package/dist/testing.js +366 -3
  65. package/dist/tools/agent.d.ts.map +1 -1
  66. package/dist/tools/async-operation.d.ts +168 -0
  67. package/dist/tools/async-operation.d.ts.map +1 -0
  68. package/dist/tools/cli-args.d.ts +1 -1
  69. package/dist/tools/cli-args.d.ts.map +1 -1
  70. package/dist/tools/cli-format.d.ts +2 -2
  71. package/dist/tools/cli-format.d.ts.map +1 -1
  72. package/dist/tools/cli-wait.d.ts +11 -2
  73. package/dist/tools/cli-wait.d.ts.map +1 -1
  74. package/dist/tools/cli.d.ts +2 -0
  75. package/dist/tools/cli.d.ts.map +1 -1
  76. package/dist/tools/define-download-tool.d.ts +6 -5
  77. package/dist/tools/define-download-tool.d.ts.map +1 -1
  78. package/dist/tools/define-upload-tool.d.ts +3 -1
  79. package/dist/tools/define-upload-tool.d.ts.map +1 -1
  80. package/dist/tools/define-wait-tool.d.ts +1 -1
  81. package/dist/tools/define-wait-tool.d.ts.map +1 -1
  82. package/dist/tools/download-core.d.ts +5 -10
  83. package/dist/tools/download-core.d.ts.map +1 -1
  84. package/dist/tools/lifecycle.d.ts +4 -0
  85. package/dist/tools/lifecycle.d.ts.map +1 -0
  86. package/dist/tools/mount-download.d.ts +5 -4
  87. package/dist/tools/mount-download.d.ts.map +1 -1
  88. package/dist/tools/mount-upload.d.ts +4 -2
  89. package/dist/tools/mount-upload.d.ts.map +1 -1
  90. package/dist/tools/upload-core.d.ts +3 -2
  91. package/dist/tools/upload-core.d.ts.map +1 -1
  92. package/dist/tools/view-file.d.ts +4 -3
  93. package/dist/tools/view-file.d.ts.map +1 -1
  94. package/dist/tools/wait-core.d.ts +6 -1
  95. package/dist/tools/wait-core.d.ts.map +1 -1
  96. package/dist/tools.d.ts +3 -2
  97. package/dist/tools.d.ts.map +1 -1
  98. package/dist/tools.js +385 -244
  99. package/llms-full.txt +417 -27
  100. package/package.json +10 -2
@@ -0,0 +1,269 @@
1
+ import {
2
+ isWithinDir
3
+ } from "./index-sbdmyz75.js";
4
+ import {
5
+ ManagedFilePathSchema,
6
+ ManagedFileRefSchema
7
+ } from "./index-6k1937bx.js";
8
+
9
+ // src/files/boundary.ts
10
+ import { randomUUID } from "node:crypto";
11
+ import { constants } from "node:fs";
12
+ import { link, open, realpath, rename, stat, unlink } from "node:fs/promises";
13
+ import { basename, dirname, resolve } from "node:path";
14
+ var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
15
+ var DEFAULT_INSPECTION_BYTES = 64 * 1024;
16
+ var NOFOLLOW_FLAG = constants.O_NOFOLLOW ?? 0;
17
+
18
+ class ManagedFileError extends Error {
19
+ code;
20
+ constructor(code, message, options) {
21
+ super(message, options);
22
+ this.code = code;
23
+ this.name = "ManagedFileError";
24
+ }
25
+ }
26
+ function positiveLimit(value, fallback, name) {
27
+ const resolved = value ?? fallback;
28
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
29
+ throw new TypeError(`${name} must be a positive safe integer`);
30
+ }
31
+ return resolved;
32
+ }
33
+ function filePath(path) {
34
+ const parsed = ManagedFilePathSchema.safeParse(path);
35
+ if (!parsed.success) {
36
+ throw new ManagedFileError("FILE_INVALID_PATH", "invalid managed-file path");
37
+ }
38
+ return parsed.data;
39
+ }
40
+ function errorCode(error) {
41
+ if (typeof error !== "object" || error === null || !("code" in error))
42
+ return;
43
+ return typeof error.code === "string" ? error.code : undefined;
44
+ }
45
+ function ioError(message, error) {
46
+ if (error instanceof ManagedFileError)
47
+ return error;
48
+ return new ManagedFileError("FILE_IO_ERROR", message, { cause: error });
49
+ }
50
+ async function existingParent(root, target) {
51
+ const parent = await realpath(dirname(target)).catch(() => null);
52
+ if (parent === null) {
53
+ throw new ManagedFileError("FILE_NOT_FOUND", "managed-file parent directory not found");
54
+ }
55
+ if (!isWithinDir(root, parent)) {
56
+ throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
57
+ }
58
+ const info = await stat(parent).catch(() => null);
59
+ if (!info?.isDirectory()) {
60
+ throw new ManagedFileError("FILE_NOT_FOUND", "managed-file parent directory not found");
61
+ }
62
+ return parent;
63
+ }
64
+ async function readHandle(handle, maxBytes, signal) {
65
+ const chunks = [];
66
+ let total = 0;
67
+ while (true) {
68
+ signal?.throwIfAborted();
69
+ const capacity = Math.min(64 * 1024, maxBytes + 1 - total);
70
+ if (capacity <= 0) {
71
+ throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
72
+ }
73
+ const chunk = new Uint8Array(capacity);
74
+ const { bytesRead } = await handle.read(chunk, 0, capacity);
75
+ if (bytesRead === 0)
76
+ break;
77
+ total += bytesRead;
78
+ if (total > maxBytes) {
79
+ throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
80
+ }
81
+ chunks.push(chunk.subarray(0, bytesRead));
82
+ }
83
+ const bytes = new Uint8Array(total);
84
+ let offset = 0;
85
+ for (const chunk of chunks) {
86
+ bytes.set(chunk, offset);
87
+ offset += chunk.byteLength;
88
+ }
89
+ return bytes;
90
+ }
91
+ async function writeChunk(handle, chunk) {
92
+ let offset = 0;
93
+ while (offset < chunk.byteLength) {
94
+ const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset);
95
+ if (bytesWritten === 0)
96
+ throw new Error("zero-byte managed-file write");
97
+ offset += bytesWritten;
98
+ }
99
+ }
100
+ async function writeSource(handle, source, maxBytes, inspectionBytes, signal) {
101
+ let size = 0;
102
+ const prefixChunks = [];
103
+ let prefixSize = 0;
104
+ const consume = async (chunk) => {
105
+ signal?.throwIfAborted();
106
+ size += chunk.byteLength;
107
+ if (size > maxBytes) {
108
+ throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
109
+ }
110
+ if (prefixSize < inspectionBytes) {
111
+ const kept = chunk.subarray(0, inspectionBytes - prefixSize);
112
+ prefixChunks.push(kept);
113
+ prefixSize += kept.byteLength;
114
+ }
115
+ await writeChunk(handle, chunk);
116
+ };
117
+ if (source instanceof Uint8Array) {
118
+ await consume(source);
119
+ } else {
120
+ const reader = source.getReader();
121
+ try {
122
+ while (true) {
123
+ const result = await reader.read();
124
+ if (result.done)
125
+ break;
126
+ await consume(result.value);
127
+ }
128
+ } catch (error) {
129
+ await reader.cancel(error).catch(() => {
130
+ return;
131
+ });
132
+ throw error;
133
+ } finally {
134
+ reader.releaseLock();
135
+ }
136
+ }
137
+ const prefix = new Uint8Array(prefixSize);
138
+ let offset = 0;
139
+ for (const chunk of prefixChunks) {
140
+ prefix.set(chunk, offset);
141
+ offset += chunk.byteLength;
142
+ }
143
+ return { size, prefix };
144
+ }
145
+ async function createManagedFileBoundary(config) {
146
+ const root = await realpath(resolve(config.root)).catch(() => null);
147
+ if (root === null || !(await stat(root).catch(() => null))?.isDirectory()) {
148
+ throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root must exist");
149
+ }
150
+ const maxReadBytes = positiveLimit(config.maxReadBytes, DEFAULT_MAX_BYTES, "maxReadBytes");
151
+ const maxWriteBytes = positiveLimit(config.maxWriteBytes, DEFAULT_MAX_BYTES, "maxWriteBytes");
152
+ const inspectionBytes = positiveLimit(config.inspectionBytes, DEFAULT_INSPECTION_BYTES, "inspectionBytes");
153
+ const targetFor = (path) => {
154
+ const relative = filePath(path);
155
+ const target = resolve(root, ...relative.split("/"));
156
+ if (!isWithinDir(root, target)) {
157
+ throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
158
+ }
159
+ return { path: relative, target };
160
+ };
161
+ return {
162
+ async read(path, options = {}) {
163
+ const resolved = targetFor(path);
164
+ const realTarget = await realpath(resolved.target).catch(() => null);
165
+ if (realTarget === null) {
166
+ throw new ManagedFileError("FILE_NOT_FOUND", "managed file not found");
167
+ }
168
+ if (!isWithinDir(root, realTarget)) {
169
+ throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
170
+ }
171
+ const maxBytes = positiveLimit(options.maxBytes, maxReadBytes, "maxBytes");
172
+ let handle;
173
+ try {
174
+ handle = await open(realTarget, constants.O_RDONLY | NOFOLLOW_FLAG);
175
+ const info = await handle.stat();
176
+ if (!info.isFile()) {
177
+ throw new ManagedFileError("FILE_NOT_REGULAR", "managed path is not a regular file");
178
+ }
179
+ const bytes = await readHandle(handle, maxBytes, options.signal);
180
+ return {
181
+ ref: ManagedFileRefSchema.parse({ path: resolved.path, size: bytes.byteLength }),
182
+ bytes
183
+ };
184
+ } catch (error) {
185
+ if (options.signal?.aborted && error === options.signal.reason)
186
+ throw error;
187
+ if (errorCode(error) === "ENOENT") {
188
+ throw new ManagedFileError("FILE_NOT_FOUND", "managed file not found");
189
+ }
190
+ throw ioError("failed to read managed file", error);
191
+ } finally {
192
+ await handle?.close().catch(() => {
193
+ return;
194
+ });
195
+ }
196
+ },
197
+ async write(path, source, options = {}) {
198
+ options.signal?.throwIfAborted();
199
+ const resolved = targetFor(path);
200
+ const parent = await existingParent(root, resolved.target);
201
+ const maxBytes = positiveLimit(options.maxBytes, maxWriteBytes, "maxBytes");
202
+ const temporary = resolve(parent, `.stitchkit-${randomUUID()}.tmp`);
203
+ let handle;
204
+ let committed = false;
205
+ let size = 0;
206
+ try {
207
+ handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | NOFOLLOW_FLAG, 384);
208
+ const written = await writeSource(handle, source, maxBytes, inspectionBytes, options.signal);
209
+ size = written.size;
210
+ if (options.durable)
211
+ await handle.sync();
212
+ await handle.close();
213
+ handle = undefined;
214
+ let inspection = {};
215
+ if (config.inspect) {
216
+ try {
217
+ inspection = await config.inspect({
218
+ prefix: written.prefix,
219
+ declaredMediaType: options.mediaType,
220
+ name: options.name ?? basename(resolved.path)
221
+ });
222
+ } catch (error) {
223
+ throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: error });
224
+ }
225
+ }
226
+ options.signal?.throwIfAborted();
227
+ if (options.replace) {
228
+ await rename(temporary, resolved.target);
229
+ committed = true;
230
+ } else {
231
+ try {
232
+ await link(temporary, resolved.target);
233
+ committed = true;
234
+ } catch (error) {
235
+ if (errorCode(error) === "EEXIST") {
236
+ throw new ManagedFileError("FILE_EXISTS", "managed file already exists");
237
+ }
238
+ throw error;
239
+ }
240
+ }
241
+ const mediaType = inspection.mediaType ?? options.mediaType;
242
+ const name = inspection.name ?? options.name;
243
+ const ref = ManagedFileRefSchema.parse({
244
+ path: resolved.path,
245
+ size,
246
+ ...mediaType ? { mediaType } : {},
247
+ ...name ? { name } : {}
248
+ });
249
+ return ref;
250
+ } catch (error) {
251
+ if (options.signal?.aborted && error === options.signal.reason)
252
+ throw error;
253
+ throw ioError("failed to write managed file", error);
254
+ } finally {
255
+ await handle?.close().catch(() => {
256
+ return;
257
+ });
258
+ if (!options.replace || !committed) {
259
+ await unlink(temporary).catch((error) => {
260
+ if (errorCode(error) !== "ENOENT")
261
+ config.onCleanupError?.(error);
262
+ });
263
+ }
264
+ }
265
+ }
266
+ };
267
+ }
268
+
269
+ export { ManagedFileError, createManagedFileBoundary };
@@ -0,0 +1,20 @@
1
+ // src/contract/file-ref.ts
2
+ import { z } from "zod";
3
+ function isManagedFilePath(value) {
4
+ if (value.length === 0 || value.includes("\x00") || value.includes("\\"))
5
+ return false;
6
+ if (value.startsWith("/") || value.startsWith("//") || /^[A-Za-z]:/.test(value)) {
7
+ return false;
8
+ }
9
+ const parts = value.split("/");
10
+ return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
11
+ }
12
+ var ManagedFilePathSchema = z.string().refine(isManagedFilePath, "expected a canonical relative managed-file path");
13
+ var ManagedFileRefSchema = z.object({
14
+ path: ManagedFilePathSchema,
15
+ size: z.number().int().nonnegative(),
16
+ mediaType: z.string().min(1).optional(),
17
+ name: z.string().min(1).optional()
18
+ });
19
+
20
+ export { ManagedFilePathSchema, ManagedFileRefSchema };
@@ -1,6 +1,3 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
1
  // src/observability/trace.ts
5
2
  var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
6
3
  function randomHex(bytes) {
@@ -149,4 +146,4 @@ function mapObject(source, mapper) {
149
146
  return result;
150
147
  }
151
148
 
152
- export { __require, parseTrailingWildcard, typedEntries, isRecord, callRuntimeHandler, transportResult, mapObject, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, resolvePropagationContext };
149
+ export { parseTrailingWildcard, typedEntries, isRecord, callRuntimeHandler, transportResult, mapObject, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, resolvePropagationContext };
@@ -1,20 +1,3 @@
1
- // src/internal/within-dir.ts
2
- import { realpath } from "node:fs/promises";
3
- import { sep } from "node:path";
4
- function isWithinDir(root, target) {
5
- const base = root.endsWith(sep) ? root.slice(0, -sep.length) : root;
6
- return target === root || target === base || target.startsWith(base + sep);
7
- }
8
- async function realPathWithinDir(root, target) {
9
- const [realRoot, realTarget] = await Promise.all([
10
- realpath(root).catch(() => null),
11
- realpath(target).catch(() => null)
12
- ]);
13
- if (realRoot === null || realTarget === null)
14
- return null;
15
- return isWithinDir(realRoot, realTarget) ? realTarget : null;
16
- }
17
-
18
1
  // src/tools/json-schema.ts
19
2
  import { z } from "zod";
20
3
  function toJsonSchema(schema, io, unrepresentable = "throw", target = "draft-2020-12") {
@@ -60,4 +43,4 @@ function jsonSchemaFields(jsonSchema) {
60
43
  return fields;
61
44
  }
62
45
 
63
- export { isWithinDir, realPathWithinDir, toJsonSchema, jsonSchemaFields };
46
+ export { toJsonSchema, jsonSchemaFields };
@@ -0,0 +1,110 @@
1
+ // src/contract/errors.ts
2
+ var APP_ERROR_BRAND = Symbol.for("stitchkit.AppError");
3
+
4
+ class AppError extends Error {
5
+ code;
6
+ status;
7
+ details;
8
+ hint;
9
+ traceId;
10
+ constructor(code, message, status = 500, details, hint, traceId) {
11
+ super(message ?? code);
12
+ this.code = code;
13
+ this.status = status;
14
+ this.details = details;
15
+ this.hint = hint;
16
+ this.traceId = traceId;
17
+ this.name = "AppError";
18
+ Object.defineProperty(this, APP_ERROR_BRAND, { value: true });
19
+ }
20
+ static is(err) {
21
+ return typeof err === "object" && err !== null && APP_ERROR_BRAND in err;
22
+ }
23
+ toJSON() {
24
+ return {
25
+ error: {
26
+ code: this.code,
27
+ message: this.message,
28
+ ...this.details && { details: this.details },
29
+ ...this.hint && { hint: this.hint }
30
+ }
31
+ };
32
+ }
33
+ }
34
+ function notFound(message = "Not found") {
35
+ throw new AppError("NOT_FOUND", message, 404);
36
+ }
37
+ function badRequest(message, details) {
38
+ throw new AppError("BAD_REQUEST", message, 400, details);
39
+ }
40
+ function unauthorized(message = "Unauthorized") {
41
+ throw new AppError("UNAUTHORIZED", message, 401);
42
+ }
43
+ function forbidden(message = "Forbidden") {
44
+ throw new AppError("FORBIDDEN", message, 403);
45
+ }
46
+ function conflict(message = "Conflict", details) {
47
+ throw new AppError("CONFLICT", message, 409, details);
48
+ }
49
+ function rateLimited(message = "Too many requests") {
50
+ throw new AppError("RATE_LIMITED", message, 429);
51
+ }
52
+ var STITCH_ERROR_STATUS = {
53
+ BAD_REQUEST: 400,
54
+ UNAUTHORIZED: 401,
55
+ FORBIDDEN: 403,
56
+ NOT_FOUND: 404,
57
+ METHOD_NOT_ALLOWED: 405,
58
+ CONFLICT: 409,
59
+ RATE_LIMITED: 429,
60
+ VALIDATION_ERROR: 400,
61
+ REALTIME_CONTRACT_VIOLATION: 500,
62
+ INTERNAL_SERVER_ERROR: 500
63
+ };
64
+ function isStitchErrorCode(code) {
65
+ return Object.hasOwn(STITCH_ERROR_STATUS, code);
66
+ }
67
+ function appError(code, message, details) {
68
+ throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
69
+ }
70
+
71
+ // src/contract/define.ts
72
+ import { z } from "zod";
73
+
74
+ // src/internal/safe-json.ts
75
+ function isUnsafeKey(key) {
76
+ return key === "__proto__";
77
+ }
78
+ function safeJsonParse(text) {
79
+ return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
80
+ }
81
+
82
+ // src/contract/define.ts
83
+ function mergeMeta(contractMeta, endpointMeta) {
84
+ if (!contractMeta)
85
+ return endpointMeta;
86
+ if (!endpointMeta)
87
+ return { ...contractMeta };
88
+ return { ...contractMeta, ...endpointMeta };
89
+ }
90
+ // src/contract/errors-factory.ts
91
+ import { z as z2 } from "zod";
92
+ // src/contract/pagination.ts
93
+ import { z as z3 } from "zod";
94
+
95
+ // src/internal/base64url.ts
96
+ function bytesToBase64Url(bytes) {
97
+ let binary = "";
98
+ for (const byte of bytes)
99
+ binary += String.fromCharCode(byte);
100
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
101
+ }
102
+ function base64UrlToBytes(segment) {
103
+ if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) {
104
+ throw new Error("invalid base64url segment");
105
+ }
106
+ const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
107
+ const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
108
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
109
+ }
110
+ export { isUnsafeKey, safeJsonParse, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, bytesToBase64Url, base64UrlToBytes };
@@ -1,3 +1,6 @@
1
+ import {
2
+ mergeContextContribution
3
+ } from "./index-pzc32v75.js";
1
4
  import {
2
5
  base64UrlToBytes,
3
6
  bytesToBase64Url,
@@ -5,10 +8,10 @@ import {
5
8
  isUnsafeKey,
6
9
  safeJsonParse,
7
10
  unauthorized
8
- } from "./index-173tvqcp.js";
11
+ } from "./index-h60df7rj.js";
9
12
  import {
10
13
  isRecord
11
- } from "./index-bmnhqtya.js";
14
+ } from "./index-7eq9va6e.js";
12
15
 
13
16
  // src/server/middleware/cookies.ts
14
17
  function parseCookies(header) {
@@ -151,7 +154,11 @@ function extractToken(req, cookieName) {
151
154
  return null;
152
155
  }
153
156
  function isThenable(value) {
154
- return "then" in value && typeof value.then === "function";
157
+ try {
158
+ return "then" in value && typeof value.then === "function";
159
+ } catch {
160
+ return false;
161
+ }
155
162
  }
156
163
  function createAuthHook(config) {
157
164
  const onAnonymous = config.onAnonymous ?? (() => unauthorized());
@@ -169,10 +176,10 @@ function createAuthHook(config) {
169
176
  const rule = typeof entry === "object" ? entry.rule : entry;
170
177
  if (typeof entry === "object" && entry.inject && identity !== null) {
171
178
  const fields = entry.inject(identity, ctx);
172
- if (isThenable(fields)) {
179
+ if (typeof fields === "object" && fields !== null && isThenable(fields)) {
173
180
  throw new Error(`[stitchkit] auth: the inject of scope "${scope}" must be synchronous — an async inject merges a Promise, not fields`);
174
181
  }
175
- Object.assign(ctx, fields);
182
+ mergeContextContribution(ctx, fields, scope);
176
183
  }
177
184
  if (rule === "public")
178
185
  return;
@@ -182,8 +189,14 @@ function createAuthHook(config) {
182
189
  }
183
190
  if (rule === "authenticated")
184
191
  return;
185
- if (!await rule(identity, ctx))
192
+ const result = await rule(identity, ctx);
193
+ if (result === false) {
186
194
  onForbidden();
195
+ return;
196
+ }
197
+ if (result === true)
198
+ return;
199
+ mergeContextContribution(ctx, result, scope);
187
200
  }
188
201
  return auth;
189
202
  }
@@ -0,0 +1,66 @@
1
+ // src/tools/names.ts
2
+ var SINGULAR_EXCEPTIONS = new Set([
3
+ "analytics",
4
+ "status",
5
+ "stats",
6
+ "settings",
7
+ "media",
8
+ "progress",
9
+ "news"
10
+ ]);
11
+ var TOOL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
12
+ function normalizeService(value) {
13
+ return value.replace(/[^a-zA-Z0-9_]/g, "_");
14
+ }
15
+ function normalizeMethod(value) {
16
+ return value.replace(/[^a-zA-Z0-9_-]/g, "_");
17
+ }
18
+ function singularize(name) {
19
+ if (SINGULAR_EXCEPTIONS.has(name))
20
+ return name;
21
+ if (name.endsWith("ies"))
22
+ return `${name.slice(0, -3)}y`;
23
+ if (name.endsWith("s") && !name.endsWith("ss"))
24
+ return name.slice(0, -1);
25
+ return name;
26
+ }
27
+ function singularizeTail(name) {
28
+ const cut = name.lastIndexOf("_");
29
+ if (cut === -1)
30
+ return singularize(name);
31
+ return `${name.slice(0, cut + 1)}${singularize(name.slice(cut + 1))}`;
32
+ }
33
+ function hasUsableChars(value) {
34
+ return /[a-zA-Z0-9]/.test(value);
35
+ }
36
+ function assertToolName(name, serviceName, key) {
37
+ const where = `service "${serviceName}", method "${key}"`;
38
+ if (!TOOL_NAME_RE.test(name)) {
39
+ const why = name.length > 64 ? `is ${name.length} characters (max 64) — set an explicit \`toolName\`` : "must match [a-zA-Z0-9_-]";
40
+ throw new Error(`Tool name "${name}" (${where}) ${why}`);
41
+ }
42
+ }
43
+ function toToolName(serviceName, methodName) {
44
+ const normalized = normalizeService(serviceName);
45
+ const singular = singularizeTail(normalized);
46
+ const method = normalizeMethod(methodName);
47
+ if (method === "list")
48
+ return `list_${normalized}`;
49
+ if (method === "get")
50
+ return `get_${singular}`;
51
+ if (method === "create")
52
+ return `create_${singular}`;
53
+ if (method === "update")
54
+ return `update_${singular}`;
55
+ if (method === "delete")
56
+ return `delete_${singular}`;
57
+ const snake = method.replace(/([A-Z])/g, "_$1").toLowerCase();
58
+ return `${snake}_${singular}`.replace(/^_/, "");
59
+ }
60
+ function assertUniqueToolName(name, taken, surface) {
61
+ if (taken) {
62
+ throw new Error(`Duplicate ${surface} "${name}" across mounted operations`);
63
+ }
64
+ }
65
+
66
+ export { hasUsableChars, assertToolName, toToolName, assertUniqueToolName };
@@ -1,39 +1,44 @@
1
1
  import {
2
2
  DEFAULT_PROCESS_SIGNALS,
3
+ RUNTIME_CONTEXT_RESERVED_KEYS,
3
4
  assertCorsConfig,
4
5
  corsHeaders,
5
6
  corsPreflightResponse,
6
7
  defaultSignalSource,
7
8
  guardSignalCallback,
8
9
  reportSignalError
9
- } from "./index-b15rtha1.js";
10
+ } from "./index-pzc32v75.js";
10
11
  import {
11
- AppError,
12
- badRequest,
13
12
  errorCode,
14
13
  extractIp,
15
14
  getClientInfo,
16
15
  getRequestContext,
17
- isUnsafeKey,
18
16
  normalizeError,
19
17
  parseQueryParams,
20
18
  recordedErrorMessage,
21
19
  resolveSocketIp,
22
20
  resolveTraceId,
23
21
  runWithRequestContext,
24
- safeJsonParse,
25
22
  setRequestEndpoint,
26
23
  setRequestError,
27
24
  validateDeclaredOutput,
28
25
  zodIssues
29
- } from "./index-173tvqcp.js";
26
+ } from "./index-v4bbq3p4.js";
27
+ import {
28
+ AppError,
29
+ badRequest,
30
+ isUnsafeKey,
31
+ safeJsonParse
32
+ } from "./index-h60df7rj.js";
30
33
  import {
31
- __require,
32
34
  isRecord,
33
35
  parseTrailingWildcard,
34
36
  resolveTraceContext,
35
37
  transportResult
36
- } from "./index-bmnhqtya.js";
38
+ } from "./index-7eq9va6e.js";
39
+ import {
40
+ __require
41
+ } from "./index-1bx83sw4.js";
37
42
 
38
43
  // src/server/multipart.ts
39
44
  var DEFAULT_MAX_REQUEST_BYTES = 25 * 1024 * 1024;
@@ -500,19 +505,6 @@ async function readRequestText(req, maxBytes) {
500
505
  }
501
506
 
502
507
  // src/server/context.ts
503
- var RESERVED_KEYS = new Set([
504
- "params",
505
- "input",
506
- "source",
507
- "req",
508
- "url",
509
- "headers",
510
- "rawBody",
511
- "traceId",
512
- "spanId",
513
- "ipAddress",
514
- "userAgent"
515
- ]);
516
508
  function parseJsonBody(req, text) {
517
509
  if (text.trim() === "")
518
510
  return {};
@@ -528,7 +520,7 @@ function parseJsonBody(req, text) {
528
520
  function buildBaseContext(req, url, pathParams, traceId, clientIp) {
529
521
  const safePathParams = {};
530
522
  for (const [k, v] of Object.entries(pathParams)) {
531
- if (!RESERVED_KEYS.has(k) && !isUnsafeKey(k))
523
+ if (!RUNTIME_CONTEXT_RESERVED_KEYS.has(k) && !isUnsafeKey(k))
532
524
  safePathParams[k] = v;
533
525
  }
534
526
  return {
@@ -538,6 +530,7 @@ function buildBaseContext(req, url, pathParams, traceId, clientIp) {
538
530
  req,
539
531
  url,
540
532
  headers: req.headers,
533
+ signal: req.signal,
541
534
  ...safePathParams,
542
535
  traceId,
543
536
  ...getClientInfo(req, clientIp)
@@ -585,6 +578,7 @@ function buildErrorContext(req, url, traceId, clientIp) {
585
578
  req,
586
579
  url,
587
580
  headers: req.headers,
581
+ signal: req.signal,
588
582
  traceId,
589
583
  ...getClientInfo(req, clientIp)
590
584
  };