stitchkit 0.54.0 → 0.56.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 (120) hide show
  1. package/README.md +4 -1
  2. package/dist/browser/socket-io.d.ts +25 -7
  3. package/dist/browser/socket-io.d.ts.map +1 -1
  4. package/dist/cli.js +14 -9
  5. package/dist/contract/errors.d.ts +7 -0
  6. package/dist/contract/errors.d.ts.map +1 -1
  7. package/dist/contract/file-ref.d.ts +13 -0
  8. package/dist/contract/file-ref.d.ts.map +1 -0
  9. package/dist/contract/index.d.ts +1 -0
  10. package/dist/contract/index.d.ts.map +1 -1
  11. package/dist/contract/index.js +21 -17
  12. package/dist/files/boundary.d.ts +55 -0
  13. package/dist/files/boundary.d.ts.map +1 -0
  14. package/dist/files.d.ts +3 -0
  15. package/dist/files.d.ts.map +1 -0
  16. package/dist/files.js +16 -0
  17. package/dist/index-1bx83sw4.js +4 -0
  18. package/dist/{index-pfqjb5xy.js → index-28cqssm3.js} +30 -5
  19. package/dist/index-557by2db.js +34 -0
  20. package/dist/index-5r13htq1.js +283 -0
  21. package/dist/index-6k1937bx.js +20 -0
  22. package/dist/index-6y759j86.js +1031 -0
  23. package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
  24. package/dist/index-bfcpjw20.js +363 -0
  25. package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
  26. package/dist/{index-7vb44hy8.js → index-escqg10p.js} +193 -751
  27. package/dist/{index-173tvqcp.js → index-j3dem06f.js} +9 -114
  28. package/dist/{index-gvha32jh.js → index-jcc611vh.js} +2 -2
  29. package/dist/{index-z7761jk2.js → index-n4nfa7gh.js} +16 -22
  30. package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
  31. package/dist/index-sbdmyz75.js +18 -0
  32. package/dist/{index-b15rtha1.js → index-sm2tjx06.js} +98 -3
  33. package/dist/{index-yez419px.js → index-xy8fmh6w.js} +99 -7
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +188 -40
  37. package/dist/node.js +31 -27
  38. package/dist/observability/index.js +28 -25
  39. package/dist/react.js +2 -2
  40. package/dist/realtime/contract.d.ts +9 -0
  41. package/dist/realtime/contract.d.ts.map +1 -1
  42. package/dist/realtime/index.d.ts +2 -1
  43. package/dist/realtime/index.d.ts.map +1 -1
  44. package/dist/realtime/request.d.ts +21 -0
  45. package/dist/realtime/request.d.ts.map +1 -0
  46. package/dist/realtime/socket.d.ts +3 -0
  47. package/dist/realtime/socket.d.ts.map +1 -1
  48. package/dist/remote.d.ts +9 -0
  49. package/dist/remote.d.ts.map +1 -0
  50. package/dist/remote.js +79 -0
  51. package/dist/server/context-contribution.d.ts +18 -0
  52. package/dist/server/context-contribution.d.ts.map +1 -0
  53. package/dist/server/context.d.ts.map +1 -1
  54. package/dist/server/index.d.ts +2 -1
  55. package/dist/server/index.d.ts.map +1 -1
  56. package/dist/server/index.js +136 -88
  57. package/dist/server/lifecycle.d.ts +4 -0
  58. package/dist/server/lifecycle.d.ts.map +1 -0
  59. package/dist/server/middleware/auth.d.ts +64 -9
  60. package/dist/server/middleware/auth.d.ts.map +1 -1
  61. package/dist/server/types.d.ts +1 -1
  62. package/dist/server/types.d.ts.map +1 -1
  63. package/dist/testing/surface-conformance.d.ts +180 -0
  64. package/dist/testing/surface-conformance.d.ts.map +1 -0
  65. package/dist/testing/surface-manifest.d.ts +255 -0
  66. package/dist/testing/surface-manifest.d.ts.map +1 -0
  67. package/dist/testing.d.ts +2 -0
  68. package/dist/testing.d.ts.map +1 -1
  69. package/dist/testing.js +714 -3
  70. package/dist/tools/agent.d.ts.map +1 -1
  71. package/dist/tools/async-operation.d.ts +360 -0
  72. package/dist/tools/async-operation.d.ts.map +1 -0
  73. package/dist/tools/cli-args.d.ts +1 -1
  74. package/dist/tools/cli-args.d.ts.map +1 -1
  75. package/dist/tools/cli-format.d.ts +2 -2
  76. package/dist/tools/cli-format.d.ts.map +1 -1
  77. package/dist/tools/cli-wait.d.ts +11 -2
  78. package/dist/tools/cli-wait.d.ts.map +1 -1
  79. package/dist/tools/cli.d.ts +2 -0
  80. package/dist/tools/cli.d.ts.map +1 -1
  81. package/dist/tools/define-download-tool.d.ts +6 -5
  82. package/dist/tools/define-download-tool.d.ts.map +1 -1
  83. package/dist/tools/define-upload-tool.d.ts +3 -1
  84. package/dist/tools/define-upload-tool.d.ts.map +1 -1
  85. package/dist/tools/define-wait-tool.d.ts +1 -1
  86. package/dist/tools/define-wait-tool.d.ts.map +1 -1
  87. package/dist/tools/download-core.d.ts +5 -10
  88. package/dist/tools/download-core.d.ts.map +1 -1
  89. package/dist/tools/internal/surface-projector.d.ts +101 -0
  90. package/dist/tools/internal/surface-projector.d.ts.map +1 -0
  91. package/dist/tools/lifecycle.d.ts +4 -0
  92. package/dist/tools/lifecycle.d.ts.map +1 -0
  93. package/dist/tools/managed-file-error.d.ts +6 -0
  94. package/dist/tools/managed-file-error.d.ts.map +1 -0
  95. package/dist/tools/mcp-prepare.d.ts +3 -21
  96. package/dist/tools/mcp-prepare.d.ts.map +1 -1
  97. package/dist/tools/mcp-round-policy.d.ts +9 -0
  98. package/dist/tools/mcp-round-policy.d.ts.map +1 -0
  99. package/dist/tools/mcp-round.d.ts +0 -4
  100. package/dist/tools/mcp-round.d.ts.map +1 -1
  101. package/dist/tools/mount-download.d.ts +5 -4
  102. package/dist/tools/mount-download.d.ts.map +1 -1
  103. package/dist/tools/mount-upload.d.ts +4 -2
  104. package/dist/tools/mount-upload.d.ts.map +1 -1
  105. package/dist/tools/mount.d.ts +5 -9
  106. package/dist/tools/mount.d.ts.map +1 -1
  107. package/dist/tools/runtime-tool.d.ts.map +1 -1
  108. package/dist/tools/surface.d.ts +7 -0
  109. package/dist/tools/surface.d.ts.map +1 -1
  110. package/dist/tools/upload-core.d.ts +3 -2
  111. package/dist/tools/upload-core.d.ts.map +1 -1
  112. package/dist/tools/view-file.d.ts +4 -3
  113. package/dist/tools/view-file.d.ts.map +1 -1
  114. package/dist/tools/wait-core.d.ts +6 -1
  115. package/dist/tools/wait-core.d.ts.map +1 -1
  116. package/dist/tools.d.ts +3 -2
  117. package/dist/tools.d.ts.map +1 -1
  118. package/dist/tools.js +705 -599
  119. package/llms-full.txt +727 -34
  120. package/package.json +10 -2
@@ -1,119 +1,14 @@
1
+ import {
2
+ AppError,
3
+ isUnsafeKey
4
+ } from "./index-5r13htq1.js";
1
5
  import {
2
6
  isRecord,
3
7
  resolveTraceContext
4
- } from "./index-bmnhqtya.js";
5
-
6
- // src/contract/errors.ts
7
- var APP_ERROR_BRAND = Symbol.for("stitchkit.AppError");
8
-
9
- class AppError extends Error {
10
- code;
11
- status;
12
- details;
13
- hint;
14
- traceId;
15
- constructor(code, message, status = 500, details, hint, traceId) {
16
- super(message ?? code);
17
- this.code = code;
18
- this.status = status;
19
- this.details = details;
20
- this.hint = hint;
21
- this.traceId = traceId;
22
- this.name = "AppError";
23
- Object.defineProperty(this, APP_ERROR_BRAND, { value: true });
24
- }
25
- static is(err) {
26
- return typeof err === "object" && err !== null && APP_ERROR_BRAND in err;
27
- }
28
- toJSON() {
29
- return {
30
- error: {
31
- code: this.code,
32
- message: this.message,
33
- ...this.details && { details: this.details },
34
- ...this.hint && { hint: this.hint }
35
- }
36
- };
37
- }
38
- }
39
- function notFound(message = "Not found") {
40
- throw new AppError("NOT_FOUND", message, 404);
41
- }
42
- function badRequest(message, details) {
43
- throw new AppError("BAD_REQUEST", message, 400, details);
44
- }
45
- function unauthorized(message = "Unauthorized") {
46
- throw new AppError("UNAUTHORIZED", message, 401);
47
- }
48
- function forbidden(message = "Forbidden") {
49
- throw new AppError("FORBIDDEN", message, 403);
50
- }
51
- function conflict(message = "Conflict", details) {
52
- throw new AppError("CONFLICT", message, 409, details);
53
- }
54
- function rateLimited(message = "Too many requests") {
55
- throw new AppError("RATE_LIMITED", message, 429);
56
- }
57
- var STITCH_ERROR_STATUS = {
58
- BAD_REQUEST: 400,
59
- UNAUTHORIZED: 401,
60
- FORBIDDEN: 403,
61
- NOT_FOUND: 404,
62
- METHOD_NOT_ALLOWED: 405,
63
- CONFLICT: 409,
64
- RATE_LIMITED: 429,
65
- VALIDATION_ERROR: 400,
66
- REALTIME_CONTRACT_VIOLATION: 500,
67
- INTERNAL_SERVER_ERROR: 500
68
- };
69
- function isStitchErrorCode(code) {
70
- return Object.hasOwn(STITCH_ERROR_STATUS, code);
71
- }
72
- function appError(code, message, details) {
73
- throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
74
- }
75
-
76
- // src/contract/define.ts
77
- import { z } from "zod";
78
-
79
- // src/internal/safe-json.ts
80
- function isUnsafeKey(key) {
81
- return key === "__proto__";
82
- }
83
- function safeJsonParse(text) {
84
- return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
85
- }
86
-
87
- // src/contract/define.ts
88
- function mergeMeta(contractMeta, endpointMeta) {
89
- if (!contractMeta)
90
- return endpointMeta;
91
- if (!endpointMeta)
92
- return { ...contractMeta };
93
- return { ...contractMeta, ...endpointMeta };
94
- }
95
- // src/contract/errors-factory.ts
96
- import { z as z2 } from "zod";
97
- // src/contract/pagination.ts
98
- import { z as z3 } from "zod";
8
+ } from "./index-7eq9va6e.js";
99
9
 
100
- // src/internal/base64url.ts
101
- function bytesToBase64Url(bytes) {
102
- let binary = "";
103
- for (const byte of bytes)
104
- binary += String.fromCharCode(byte);
105
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
106
- }
107
- function base64UrlToBytes(segment) {
108
- if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) {
109
- throw new Error("invalid base64url segment");
110
- }
111
- const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
112
- const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
113
- return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
114
- }
115
10
  // src/internal/errors.ts
116
- import { z as z4 } from "zod";
11
+ import { z } from "zod";
117
12
  function issuePath(path) {
118
13
  return path.length > 0 ? path.map(String).join(".") : "(root)";
119
14
  }
@@ -136,7 +31,7 @@ var MAX_DETAIL_ISSUES = 20;
136
31
  function errorCode(err) {
137
32
  if (AppError.is(err))
138
33
  return err.code;
139
- if (err instanceof z4.ZodError)
34
+ if (err instanceof z.ZodError)
140
35
  return "VALIDATION_ERROR";
141
36
  return;
142
37
  }
@@ -156,7 +51,7 @@ function normalizeError(err) {
156
51
  }
157
52
  return err;
158
53
  }
159
- if (err instanceof z4.ZodError) {
54
+ if (err instanceof z.ZodError) {
160
55
  return new AppError("VALIDATION_ERROR", formatZodError(err), 400, {
161
56
  issues: zodIssues(err).slice(0, MAX_DETAIL_ISSUES)
162
57
  });
@@ -322,4 +217,4 @@ function wrapInRequestContext(handler, options = {}) {
322
217
  };
323
218
  }
324
219
 
325
- export { isUnsafeKey, safeJsonParse, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateDeclaredOutput, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
220
+ export { formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateDeclaredOutput, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  isUnsafeKey
3
- } from "./index-173tvqcp.js";
3
+ } from "./index-5r13htq1.js";
4
4
  import {
5
5
  isRecord
6
- } from "./index-bmnhqtya.js";
6
+ } from "./index-7eq9va6e.js";
7
7
 
8
8
  // src/observability/sanitize.ts
9
9
  var SENSITIVE_WORDS = new Set([
@@ -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-sm2tjx06.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-j3dem06f.js";
27
+ import {
28
+ AppError,
29
+ badRequest,
30
+ isUnsafeKey,
31
+ safeJsonParse
32
+ } from "./index-5r13htq1.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
  };
@@ -6,7 +6,7 @@ import {
6
6
  mapObject,
7
7
  parseTrailingWildcard,
8
8
  typedEntries
9
- } from "./index-bmnhqtya.js";
9
+ } from "./index-7eq9va6e.js";
10
10
 
11
11
  // src/browser/cancellation.ts
12
12
  class RequestCancellationError extends Error {
@@ -0,0 +1,18 @@
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
+ export { isWithinDir, realPathWithinDir };
@@ -1,12 +1,13 @@
1
1
  import {
2
+ isUnsafeKey,
2
3
  mergeMeta
3
- } from "./index-173tvqcp.js";
4
+ } from "./index-5r13htq1.js";
4
5
  import {
5
6
  callRuntimeHandler,
6
7
  isRecord,
7
8
  transportResult,
8
9
  typedEntries
9
- } from "./index-bmnhqtya.js";
10
+ } from "./index-7eq9va6e.js";
10
11
 
11
12
  // src/server/middleware/cors.ts
12
13
  var DEFAULT_CORS_ALLOW_HEADERS = "Content-Type, Authorization, X-Trace-Id, traceparent, tracestate";
@@ -246,6 +247,100 @@ function createImplementRegistry() {
246
247
  return (contracts, handlers) => transportResult(bindRegistry(contracts, handlers));
247
248
  }
248
249
 
250
+ // src/server/context-contribution.ts
251
+ var RUNTIME_CONTEXT_RESERVED_KEYS = new Set([
252
+ "params",
253
+ "input",
254
+ "files",
255
+ "rawBody",
256
+ "source",
257
+ "req",
258
+ "url",
259
+ "headers",
260
+ "traceId",
261
+ "spanId",
262
+ "ipAddress",
263
+ "userAgent",
264
+ "signal",
265
+ "mcp"
266
+ ]);
267
+ function contributionError(scope, reason) {
268
+ return new TypeError(`[stitchkit] auth: invalid context contribution for scope ${JSON.stringify(scope)}: ${reason}`);
269
+ }
270
+ function descriptorsEqual(left, right) {
271
+ if (!left || !right)
272
+ return left === right;
273
+ if (left.enumerable !== right.enumerable || left.configurable !== right.configurable || left.writable !== right.writable) {
274
+ return false;
275
+ }
276
+ if ("value" in left || "value" in right) {
277
+ return "value" in left && "value" in right && Object.is(left.value, right.value);
278
+ }
279
+ return left.get === right.get && left.set === right.set;
280
+ }
281
+ function contextContributionDelta(original, shadow, scope) {
282
+ if (Object.getPrototypeOf(shadow) !== Object.getPrototypeOf(original)) {
283
+ throw contributionError(scope, "context prototype changes are not supported");
284
+ }
285
+ const originalKeys = Reflect.ownKeys(original);
286
+ const shadowKeys = Reflect.ownKeys(shadow);
287
+ const shadowKeySet = new Set(shadowKeys);
288
+ for (const key of originalKeys) {
289
+ if (!shadowKeySet.has(key)) {
290
+ const printable = typeof key === "string" ? JSON.stringify(key) : "a symbol key";
291
+ throw contributionError(scope, `context key ${printable} may not be deleted`);
292
+ }
293
+ }
294
+ const contribution = Object.create(null);
295
+ for (const key of shadowKeys) {
296
+ const originalDescriptor = Object.getOwnPropertyDescriptor(original, key);
297
+ const shadowDescriptor = Object.getOwnPropertyDescriptor(shadow, key);
298
+ if (!shadowDescriptor || descriptorsEqual(originalDescriptor, shadowDescriptor))
299
+ continue;
300
+ Object.defineProperty(contribution, key, shadowDescriptor);
301
+ }
302
+ return contribution;
303
+ }
304
+ function prepareContextContribution(value, scope) {
305
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
306
+ throw contributionError(scope, "expected a plain record");
307
+ }
308
+ let prototype;
309
+ let descriptors;
310
+ let keys;
311
+ try {
312
+ prototype = Object.getPrototypeOf(value);
313
+ descriptors = Object.getOwnPropertyDescriptors(value);
314
+ keys = Reflect.ownKeys(value);
315
+ } catch {
316
+ throw contributionError(scope, "record inspection failed");
317
+ }
318
+ if (prototype !== Object.prototype && prototype !== null) {
319
+ throw contributionError(scope, "expected Object.prototype or null prototype");
320
+ }
321
+ const prepared = Object.create(null);
322
+ for (const key of keys) {
323
+ if (typeof key !== "string") {
324
+ throw contributionError(scope, "symbol keys are not supported");
325
+ }
326
+ if (isUnsafeKey(key)) {
327
+ throw contributionError(scope, `unsafe key ${JSON.stringify(key)}`);
328
+ }
329
+ if (RUNTIME_CONTEXT_RESERVED_KEYS.has(key)) {
330
+ throw contributionError(scope, `reserved key ${JSON.stringify(key)}`);
331
+ }
332
+ const descriptor = descriptors[key];
333
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
334
+ throw contributionError(scope, `key ${JSON.stringify(key)} must be enumerable data`);
335
+ }
336
+ prepared[key] = descriptor.value;
337
+ }
338
+ return prepared;
339
+ }
340
+ function mergeContextContribution(ctx, value, scope) {
341
+ Object.assign(ctx, prepareContextContribution(value, scope));
342
+ }
343
+
249
344
  // src/server/process-signal-common.ts
250
345
  var DEFAULT_PROCESS_SIGNALS = ["SIGINT", "SIGTERM"];
251
346
  var defaultSignalSource = {
@@ -275,4 +370,4 @@ function guardSignalCallback(run, phase, onError) {
275
370
  }
276
371
  }
277
372
 
278
- export { DEFAULT_CORS_ALLOW_HEADERS, DEFAULT_CORS_EXPOSE_HEADERS, assertCorsConfig, corsHeaders, corsPreflightResponse, defineMultipartStream, createMultipartStream, contractOnlyService, implement, createImplement, createScopedImplement, createScopedImplementRegistry, implementRegistry, createImplementRegistry, DEFAULT_PROCESS_SIGNALS, defaultSignalSource, reportSignalError, guardSignalCallback };
373
+ export { RUNTIME_CONTEXT_RESERVED_KEYS, contextContributionDelta, prepareContextContribution, mergeContextContribution, DEFAULT_CORS_ALLOW_HEADERS, DEFAULT_CORS_EXPOSE_HEADERS, assertCorsConfig, corsHeaders, corsPreflightResponse, defineMultipartStream, createMultipartStream, contractOnlyService, implement, createImplement, createScopedImplement, createScopedImplementRegistry, implementRegistry, createImplementRegistry, DEFAULT_PROCESS_SIGNALS, defaultSignalSource, reportSignalError, guardSignalCallback };
@@ -1,3 +1,8 @@
1
+ import {
2
+ contextContributionDelta,
3
+ mergeContextContribution,
4
+ prepareContextContribution
5
+ } from "./index-sm2tjx06.js";
1
6
  import {
2
7
  base64UrlToBytes,
3
8
  bytesToBase64Url,
@@ -5,10 +10,10 @@ import {
5
10
  isUnsafeKey,
6
11
  safeJsonParse,
7
12
  unauthorized
8
- } from "./index-173tvqcp.js";
13
+ } from "./index-5r13htq1.js";
9
14
  import {
10
15
  isRecord
11
- } from "./index-bmnhqtya.js";
16
+ } from "./index-7eq9va6e.js";
12
17
 
13
18
  // src/server/middleware/cookies.ts
14
19
  function parseCookies(header) {
@@ -151,7 +156,22 @@ function extractToken(req, cookieName) {
151
156
  return null;
152
157
  }
153
158
  function isThenable(value) {
154
- return "then" in value && typeof value.then === "function";
159
+ try {
160
+ return "then" in value && typeof value.then === "function";
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+ var scopedAuthHookBrand = Symbol("stitchkit.scoped-auth-hook");
166
+ var authOwnershipPlans = new WeakMap;
167
+ function createShadowContext(ctx) {
168
+ const shadow = {
169
+ params: ctx.params,
170
+ input: ctx.input,
171
+ source: ctx.source
172
+ };
173
+ Object.defineProperties(shadow, Object.getOwnPropertyDescriptors(ctx));
174
+ return shadow;
155
175
  }
156
176
  function createAuthHook(config) {
157
177
  const onAnonymous = config.onAnonymous ?? (() => unauthorized());
@@ -169,10 +189,10 @@ function createAuthHook(config) {
169
189
  const rule = typeof entry === "object" ? entry.rule : entry;
170
190
  if (typeof entry === "object" && entry.inject && identity !== null) {
171
191
  const fields = entry.inject(identity, ctx);
172
- if (isThenable(fields)) {
192
+ if (typeof fields === "object" && fields !== null && isThenable(fields)) {
173
193
  throw new Error(`[stitchkit] auth: the inject of scope "${scope}" must be synchronous — an async inject merges a Promise, not fields`);
174
194
  }
175
- Object.assign(ctx, fields);
195
+ mergeContextContribution(ctx, fields, scope);
176
196
  }
177
197
  if (rule === "public")
178
198
  return;
@@ -182,11 +202,83 @@ function createAuthHook(config) {
182
202
  }
183
203
  if (rule === "authenticated")
184
204
  return;
185
- if (!await rule(identity, ctx))
205
+ const result = await rule(identity, ctx);
206
+ if (result === false) {
186
207
  onForbidden();
208
+ return;
209
+ }
210
+ if (result === true)
211
+ return;
212
+ mergeContextContribution(ctx, result, scope);
187
213
  }
214
+ auth[scopedAuthHookBrand] = () => {
215
+ return;
216
+ };
217
+ authOwnershipPlans.set(auth, {
218
+ defaultScope: config.defaultScope,
219
+ hook: auth,
220
+ scopes: new Set(Object.keys(config.rules))
221
+ });
188
222
  return auth;
189
223
  }
224
+ function composeAuthHooks(config) {
225
+ const plans = [];
226
+ for (const hook of config.hooks) {
227
+ const plan = authOwnershipPlans.get(hook);
228
+ if (!plan) {
229
+ throw new TypeError("[stitchkit] auth: composeAuthHooks accepts only hooks created by createAuthHook or composeAuthHooks");
230
+ }
231
+ if (plan.defaultScope !== undefined && plan.defaultScope !== config.defaultScope) {
232
+ throw new TypeError(`[stitchkit] auth: child default scope ${JSON.stringify(plan.defaultScope)} must match the composite default scope`);
233
+ }
234
+ plans.push(plan);
235
+ }
236
+ const defaultScope = config.defaultScope;
237
+ if (defaultScope !== undefined && !plans.some((plan) => plan.scopes.has(defaultScope))) {
238
+ throw new TypeError(`[stitchkit] auth: composite default scope ${JSON.stringify(defaultScope)} has no owner`);
239
+ }
240
+ const ownedScopes = new Set;
241
+ for (const plan of plans) {
242
+ for (const scope of plan.scopes)
243
+ ownedScopes.add(scope);
244
+ }
245
+ async function composed(ctx, endpoint) {
246
+ const scope = endpoint.scope ?? config.defaultScope;
247
+ if (!scope)
248
+ return;
249
+ const owners = plans.filter((plan) => plan.scopes.has(scope));
250
+ if (owners.length === 0) {
251
+ throw new Error(`[stitchkit] auth: no rule for scope "${scope}"`);
252
+ }
253
+ const scopedEndpoint = { ...endpoint, scope };
254
+ const contribution = Object.create(null);
255
+ const contributionOwners = new Set;
256
+ for (const owner of owners) {
257
+ ctx.signal?.throwIfAborted();
258
+ const shadow = createShadowContext(ctx);
259
+ await owner.hook(shadow, scopedEndpoint);
260
+ const ownerContribution = prepareContextContribution(contextContributionDelta(ctx, shadow, scope), scope);
261
+ for (const key of Object.keys(ownerContribution)) {
262
+ if (contributionOwners.has(key)) {
263
+ throw new TypeError(`[stitchkit] auth: multiple owners contributed context key ${JSON.stringify(key)} for scope ${JSON.stringify(scope)}`);
264
+ }
265
+ contributionOwners.add(key);
266
+ }
267
+ Object.assign(contribution, ownerContribution);
268
+ }
269
+ ctx.signal?.throwIfAborted();
270
+ mergeContextContribution(ctx, contribution, scope);
271
+ }
272
+ composed[scopedAuthHookBrand] = () => {
273
+ return;
274
+ };
275
+ authOwnershipPlans.set(composed, {
276
+ defaultScope: config.defaultScope,
277
+ hook: composed,
278
+ scopes: ownedScopes
279
+ });
280
+ return composed;
281
+ }
190
282
  function createBearerResolver(config) {
191
283
  return async (req) => {
192
284
  const token = extractToken(req);
@@ -208,4 +300,4 @@ async function verifyPkce(verifier, challenge) {
208
300
  return derived === challenge;
209
301
  }
210
302
 
211
- export { parseCookies, serializeCookie, defineCookie, verifyJwt, signJwt, extractToken, createAuthHook, createBearerResolver, deriveCodeChallenge, verifyPkce };
303
+ export { parseCookies, serializeCookie, defineCookie, verifyJwt, signJwt, extractToken, createAuthHook, composeAuthHooks, createBearerResolver, deriveCodeChallenge, verifyPkce };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { type ClientConfig, type ClientContract, type ClientFetch, type ClientRegistryValue, type ContractClientConfig, contractEndpointMatchers, createClient, createClients, createScopedClients, createScopedUrlBuilders, createUrlBuilder, createUrlBuilders, type PathPrefixArgs, type RegistryScope, type ScopeClientConfigs, type ScopedClientRegistry, type ScopedUrlBuilderRegistry, type UrlBuilderConfig, } from './browser/client';
2
2
  export { ApiError, type ApiEvent, type ApiEventListener, type ConfiguredHttpClient, createHttpClient, type HeaderProvider, type HttpClient, type HttpClientConfig, type RequestOptions, type UnauthorizedMatcher, } from './browser/http';
3
- export type { RealtimeClient, RealtimeClientOptions, SocketEventMap, SocketIOClient, SocketIOClientConfig, } from './browser/socket-io';
4
- export { createRealtimeClient, createSocketIOClient } from './browser/socket-io';
3
+ export type { BindRealtimeClientOptions, BoundRealtimeClient, RealtimeClient, RealtimeClientOptions, RealtimeClientTransport, SocketEventMap, SocketIOClient, SocketIOClientConfig, } from './browser/socket-io';
4
+ export { bindRealtimeClient, createRealtimeClient, createSocketIOClient, } from './browser/socket-io';
5
5
  export { type ParseSSEOptions, parseSSE } from './browser/stream';
6
6
  export * from './contract';
7
7
  export type { StitchLogger } from './logger';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClE,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,yBAAyB,EACzB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClE,cAAc,YAAY,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}