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
@@ -1,12 +1,13 @@
1
1
  import {
2
+ isUnsafeKey,
2
3
  mergeMeta
3
- } from "./index-173tvqcp.js";
4
+ } from "./index-h60df7rj.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,63 @@ 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 mergeContextContribution(ctx, value, scope) {
271
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
272
+ throw contributionError(scope, "expected a plain record");
273
+ }
274
+ let prototype;
275
+ let descriptors;
276
+ let keys;
277
+ try {
278
+ prototype = Object.getPrototypeOf(value);
279
+ descriptors = Object.getOwnPropertyDescriptors(value);
280
+ keys = Reflect.ownKeys(value);
281
+ } catch {
282
+ throw contributionError(scope, "record inspection failed");
283
+ }
284
+ if (prototype !== Object.prototype && prototype !== null) {
285
+ throw contributionError(scope, "expected Object.prototype or null prototype");
286
+ }
287
+ const prepared = Object.create(null);
288
+ for (const key of keys) {
289
+ if (typeof key !== "string") {
290
+ throw contributionError(scope, "symbol keys are not supported");
291
+ }
292
+ if (isUnsafeKey(key)) {
293
+ throw contributionError(scope, `unsafe key ${JSON.stringify(key)}`);
294
+ }
295
+ if (RUNTIME_CONTEXT_RESERVED_KEYS.has(key)) {
296
+ throw contributionError(scope, `reserved key ${JSON.stringify(key)}`);
297
+ }
298
+ const descriptor = descriptors[key];
299
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
300
+ throw contributionError(scope, `key ${JSON.stringify(key)} must be enumerable data`);
301
+ }
302
+ prepared[key] = descriptor.value;
303
+ }
304
+ Object.assign(ctx, prepared);
305
+ }
306
+
249
307
  // src/server/process-signal-common.ts
250
308
  var DEFAULT_PROCESS_SIGNALS = ["SIGINT", "SIGTERM"];
251
309
  var defaultSignalSource = {
@@ -275,4 +333,4 @@ function guardSignalCallback(run, phase, onError) {
275
333
  }
276
334
  }
277
335
 
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 };
336
+ export { RUNTIME_CONTEXT_RESERVED_KEYS, 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 };
@@ -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 {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  isUnsafeKey
3
- } from "./index-173tvqcp.js";
3
+ } from "./index-h60df7rj.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([
@@ -350,8 +350,26 @@ function createContractFactory(config) {
350
350
  };
351
351
  return { defineContract: explicit };
352
352
  }
353
- // src/contract/pagination.ts
353
+ // src/contract/file-ref.ts
354
354
  import { z as z3 } from "zod";
355
+ function isManagedFilePath(value) {
356
+ if (value.length === 0 || value.includes("\x00") || value.includes("\\"))
357
+ return false;
358
+ if (value.startsWith("/") || value.startsWith("//") || /^[A-Za-z]:/.test(value)) {
359
+ return false;
360
+ }
361
+ const parts = value.split("/");
362
+ return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
363
+ }
364
+ var ManagedFilePathSchema = z3.string().refine(isManagedFilePath, "expected a canonical relative managed-file path");
365
+ var ManagedFileRefSchema = z3.object({
366
+ path: ManagedFilePathSchema,
367
+ size: z3.number().int().nonnegative(),
368
+ mediaType: z3.string().min(1).optional(),
369
+ name: z3.string().min(1).optional()
370
+ });
371
+ // src/contract/pagination.ts
372
+ import { z as z4 } from "zod";
355
373
 
356
374
  // src/internal/base64url.ts
357
375
  function bytesToBase64Url(bytes) {
@@ -371,9 +389,9 @@ function base64UrlToBytes(segment) {
371
389
 
372
390
  // src/contract/pagination.ts
373
391
  function paginatedSchema(itemSchema) {
374
- return z3.object({
375
- items: z3.array(itemSchema),
376
- nextCursor: z3.string().nullable()
392
+ return z4.object({
393
+ items: z4.array(itemSchema),
394
+ nextCursor: z4.string().nullable()
377
395
  });
378
396
  }
379
397
  function toBase64Url(str) {
@@ -395,4 +413,4 @@ function decodeCursor(cursor, schema) {
395
413
  return null;
396
414
  }
397
415
  }
398
- export { parseTrailingWildcard, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, ALL_TRANSPORTS, defineContract, defineErrors, createContractFactory, paginatedSchema, encodeCursor, decodeCursor };
416
+ export { parseTrailingWildcard, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, ALL_TRANSPORTS, defineContract, defineErrors, createContractFactory, ManagedFilePathSchema, ManagedFileRefSchema, paginatedSchema, encodeCursor, decodeCursor };
@@ -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,119 +1,14 @@
1
+ import {
2
+ AppError,
3
+ isUnsafeKey
4
+ } from "./index-h60df7rj.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 };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ALL_TRANSPORTS,
3
3
  AppError,
4
+ ManagedFilePathSchema,
5
+ ManagedFileRefSchema,
4
6
  STITCH_ERROR_STATUS,
5
7
  appError,
6
8
  badRequest,
@@ -17,7 +19,7 @@ import {
17
19
  parseTrailingWildcard,
18
20
  rateLimited,
19
21
  unauthorized
20
- } from "./index-pfqjb5xy.js";
22
+ } from "./index-rgd18hx3.js";
21
23
  import {
22
24
  isRecord,
23
25
  mapObject,
@@ -806,6 +808,39 @@ function createUrlBuilders(contracts, source, contractConfig) {
806
808
  function createScopedUrlBuilders(contracts, source, scopeConfigs) {
807
809
  return buildScopedRegistry(contracts, scopeConfigs, "URL builder", (contract, config) => createUrlBuilder(contract, source, config));
808
810
  }
811
+ // src/realtime/request.ts
812
+ class RealtimeRequestTimeoutError extends Error {
813
+ code = "REALTIME_REQUEST_TIMEOUT";
814
+ event;
815
+ timeoutMs;
816
+ constructor(event, timeoutMs) {
817
+ super(`Realtime request "${event}" timed out after ${timeoutMs}ms`);
818
+ this.name = "RealtimeRequestTimeoutError";
819
+ this.event = event;
820
+ this.timeoutMs = timeoutMs;
821
+ }
822
+ }
823
+
824
+ class RealtimeRequestDisconnectedError extends Error {
825
+ code = "REALTIME_REQUEST_DISCONNECTED";
826
+ event;
827
+ constructor(event) {
828
+ super(`Realtime request "${event}" was interrupted by disconnect`);
829
+ this.name = "RealtimeRequestDisconnectedError";
830
+ this.event = event;
831
+ }
832
+ }
833
+
834
+ class RealtimeRequestInvalidAcknowledgementError extends Error {
835
+ code = "REALTIME_REQUEST_INVALID_ACKNOWLEDGEMENT";
836
+ event;
837
+ constructor(event, cause) {
838
+ super(`Realtime request "${event}" received an invalid acknowledgement`, { cause });
839
+ this.name = "RealtimeRequestInvalidAcknowledgementError";
840
+ this.event = event;
841
+ }
842
+ }
843
+
809
844
  // src/realtime/rejection.ts
810
845
  import { z as z2 } from "zod";
811
846
 
@@ -884,6 +919,45 @@ function reportRejected(rejected, hook, logger) {
884
919
  console.error("[stitchkit] realtime rejection hook failed", error);
885
920
  });
886
921
  }
922
+ function parseRealtimeRequestArguments(registry, event, direction, args) {
923
+ const definition = eventDefinition(registry, event, direction);
924
+ const parsed = definition.args.safeParse(args);
925
+ if (!parsed.success) {
926
+ throw realtimeContractViolation({
927
+ event,
928
+ direction,
929
+ phase: "arguments",
930
+ reason: "invalid-arguments",
931
+ fault: "local",
932
+ cause: parsed.error
933
+ }).error;
934
+ }
935
+ if (!definition.ack) {
936
+ throw realtimeContractViolation({
937
+ event,
938
+ direction,
939
+ phase: "acknowledgement",
940
+ reason: "missing-acknowledgement",
941
+ fault: "local"
942
+ }).error;
943
+ }
944
+ return parsed.data;
945
+ }
946
+ function parseRealtimeRequestAcknowledgement(schema, event, direction, value, hook, logger) {
947
+ const parsed = schema.safeParse(value);
948
+ if (parsed.success)
949
+ return parsed.data;
950
+ const rejected = realtimeContractViolation({
951
+ event,
952
+ direction,
953
+ phase: "acknowledgement",
954
+ reason: "invalid-acknowledgement-value",
955
+ fault: "peer",
956
+ cause: parsed.error
957
+ });
958
+ reportRejected(rejected, hook, logger);
959
+ throw new RealtimeRequestInvalidAcknowledgementError(event, rejected.error);
960
+ }
887
961
  function createValidatedRealtimeSocket({
888
962
  target,
889
963
  inbound,
@@ -1070,6 +1144,25 @@ function createRealtimeClient(contract, { onRejected, logger, ...config }) {
1070
1144
  };
1071
1145
  }
1072
1146
  });
1147
+ async function request(event, ...args) {
1148
+ const options = args.at(-1);
1149
+ if (!options || typeof options !== "object" || !("timeoutMs" in options)) {
1150
+ throw new TypeError(`Realtime request "${event}" requires { timeoutMs }`);
1151
+ }
1152
+ const timeoutMs = options.timeoutMs;
1153
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1154
+ throw new RangeError(`Realtime request "${event}" timeoutMs must be finite and > 0`);
1155
+ }
1156
+ const values = args.slice(0, -1);
1157
+ const parsedArgs = parseRealtimeRequestArguments(contract.clientToServer, event, "client-outbound", values);
1158
+ const definition = contract.clientToServer[event];
1159
+ if (!definition?.ack) {
1160
+ throw new Error(`Realtime request "${event}" has no acknowledgement schema`);
1161
+ }
1162
+ const value = await transport.emitWithAck(event, parsedArgs, { timeoutMs });
1163
+ const acknowledgement = parseRealtimeRequestAcknowledgement(definition.ack, event, "client-inbound", value, onRejected, logger);
1164
+ return acknowledgement;
1165
+ }
1073
1166
  return {
1074
1167
  ...events,
1075
1168
  connect: transport.connect,
@@ -1077,6 +1170,7 @@ function createRealtimeClient(contract, { onRejected, logger, ...config }) {
1077
1170
  get connected() {
1078
1171
  return transport.connected;
1079
1172
  },
1173
+ request,
1080
1174
  onConnectionChange: transport.onConnectionChange
1081
1175
  };
1082
1176
  }
@@ -1084,6 +1178,7 @@ function createSocketIOClient(config) {
1084
1178
  let socket = null;
1085
1179
  let desiredConnected = false;
1086
1180
  let reconnectTimer = null;
1181
+ const pendingRequestDisconnects = new Set;
1087
1182
  const serverDisconnectDelay = config.reconnectOnServerDisconnect ?? 1000;
1088
1183
  const connectionListeners = new Set;
1089
1184
  const subscriptions = new Set;
@@ -1169,6 +1264,8 @@ function createSocketIOClient(config) {
1169
1264
  disconnect() {
1170
1265
  desiredConnected = false;
1171
1266
  clearReconnectTimer();
1267
+ for (const reject of [...pendingRequestDisconnects])
1268
+ reject();
1172
1269
  if (!socket)
1173
1270
  return;
1174
1271
  const wasConnected = socket.connected;
@@ -1204,6 +1301,31 @@ function createSocketIOClient(config) {
1204
1301
  socket.emit(name, ...args);
1205
1302
  return true;
1206
1303
  },
1304
+ emitWithAck(event, args, options) {
1305
+ const active = socket;
1306
+ if (!active?.connected) {
1307
+ return Promise.reject(new RealtimeRequestDisconnectedError(event));
1308
+ }
1309
+ return new Promise((resolve, reject) => {
1310
+ let settled = false;
1311
+ const finish = (result) => {
1312
+ if (settled)
1313
+ return;
1314
+ settled = true;
1315
+ pendingRequestDisconnects.delete(onDisconnect);
1316
+ active.off("disconnect", onDisconnect);
1317
+ result();
1318
+ };
1319
+ const onDisconnect = () => {
1320
+ finish(() => reject(new RealtimeRequestDisconnectedError(event)));
1321
+ };
1322
+ pendingRequestDisconnects.add(onDisconnect);
1323
+ active.on("disconnect", onDisconnect);
1324
+ active.timeout(options.timeoutMs).emitWithAck(event, ...args).then((value) => finish(() => resolve(value)), () => {
1325
+ finish(() => reject(active.connected ? new RealtimeRequestTimeoutError(event, options.timeoutMs) : new RealtimeRequestDisconnectedError(event)));
1326
+ });
1327
+ });
1328
+ },
1207
1329
  onConnectionChange(listener) {
1208
1330
  connectionListeners.add(listener);
1209
1331
  return () => connectionListeners.delete(listener);
@@ -1249,39 +1371,44 @@ function defineRealtimeContract(contract) {
1249
1371
  return contract;
1250
1372
  }
1251
1373
  export {
1252
- unauthorized,
1253
- rateLimited,
1254
- parseTraceparent,
1255
- parseSSE,
1256
- paginatedSchema,
1257
- notFound,
1258
- isStitchErrorCode,
1259
- formatTraceparent,
1260
- forbidden,
1261
- encodeCursor,
1262
- defineRealtimeContract,
1263
- defineErrors,
1264
- defineContract,
1265
- decodeCursor,
1266
- createUrlBuilders,
1267
- createUrlBuilder,
1268
- createTraceContext,
1269
- createSocketIOClient,
1270
- createScopedUrlBuilders,
1271
- createScopedClients,
1272
- createRetainedTopics,
1273
- createRealtimeClient,
1274
- createHttpClient,
1275
- createContractFactory,
1276
- createClients,
1277
- createClient,
1278
- contractEndpointMatchers,
1279
- conflict,
1280
- childSpan,
1281
- badRequest,
1282
- appError,
1283
- STITCH_ERROR_STATUS,
1284
- AppError,
1374
+ ALL_TRANSPORTS,
1285
1375
  ApiError,
1286
- ALL_TRANSPORTS
1376
+ AppError,
1377
+ ManagedFilePathSchema,
1378
+ ManagedFileRefSchema,
1379
+ RealtimeRequestDisconnectedError,
1380
+ RealtimeRequestInvalidAcknowledgementError,
1381
+ RealtimeRequestTimeoutError,
1382
+ STITCH_ERROR_STATUS,
1383
+ appError,
1384
+ badRequest,
1385
+ childSpan,
1386
+ conflict,
1387
+ contractEndpointMatchers,
1388
+ createClient,
1389
+ createClients,
1390
+ createContractFactory,
1391
+ createHttpClient,
1392
+ createRealtimeClient,
1393
+ createRetainedTopics,
1394
+ createScopedClients,
1395
+ createScopedUrlBuilders,
1396
+ createSocketIOClient,
1397
+ createTraceContext,
1398
+ createUrlBuilder,
1399
+ createUrlBuilders,
1400
+ decodeCursor,
1401
+ defineContract,
1402
+ defineErrors,
1403
+ defineRealtimeContract,
1404
+ encodeCursor,
1405
+ forbidden,
1406
+ formatTraceparent,
1407
+ isStitchErrorCode,
1408
+ notFound,
1409
+ paginatedSchema,
1410
+ parseSSE,
1411
+ parseTraceparent,
1412
+ rateLimited,
1413
+ unauthorized
1287
1414
  };
package/dist/node.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  createHandler,
9
9
  createServerLifecycle,
10
10
  createSocketIOServer
11
- } from "./index-z7761jk2.js";
11
+ } from "./index-npye5kt7.js";
12
12
  import {
13
13
  createImplement,
14
14
  createImplementRegistry,
@@ -17,7 +17,8 @@ import {
17
17
  createScopedImplementRegistry,
18
18
  implement,
19
19
  implementRegistry
20
- } from "./index-b15rtha1.js";
20
+ } from "./index-pzc32v75.js";
21
+ import"./index-v4bbq3p4.js";
21
22
  import {
22
23
  AppError,
23
24
  appError,
@@ -27,8 +28,10 @@ import {
27
28
  notFound,
28
29
  rateLimited,
29
30
  unauthorized
30
- } from "./index-173tvqcp.js";
31
- import"./index-bmnhqtya.js";
31
+ } from "./index-h60df7rj.js";
32
+ import"./index-6k1937bx.js";
33
+ import"./index-7eq9va6e.js";
34
+ import"./index-1bx83sw4.js";
32
35
  // src/server/node.ts
33
36
  import { serve } from "srvx/node";
34
37
  async function serveNode(config) {
@@ -139,28 +142,28 @@ async function createNodeSocketIOServer(config) {
139
142
  return createSocketIOServer(config);
140
143
  }
141
144
  export {
142
- unauthorized,
143
- serveNode,
144
- rateLimited,
145
- notFound,
146
- implementRegistry,
147
- implement,
148
- forbidden,
149
- createNodeSocketIOServer as createSocketIOServer,
150
- createScopedImplementRegistry,
151
- createScopedImplement,
152
- createMultipartStream,
153
- createImplementRegistry,
154
- createImplement,
155
- createHandler,
156
- conflict,
157
- bindRealtimeServer,
158
- bindProcessSignals,
159
- badRequest,
160
- appError,
161
- ShutdownStatusSchema,
162
- ShutdownStateSchema,
163
- ShutdownResultSchema,
145
+ AppError,
164
146
  ShutdownOptionsSchema,
165
- AppError
147
+ ShutdownResultSchema,
148
+ ShutdownStateSchema,
149
+ ShutdownStatusSchema,
150
+ appError,
151
+ badRequest,
152
+ bindProcessSignals,
153
+ bindRealtimeServer,
154
+ conflict,
155
+ createHandler,
156
+ createImplement,
157
+ createImplementRegistry,
158
+ createMultipartStream,
159
+ createScopedImplement,
160
+ createScopedImplementRegistry,
161
+ createNodeSocketIOServer as createSocketIOServer,
162
+ forbidden,
163
+ implement,
164
+ implementRegistry,
165
+ notFound,
166
+ rateLimited,
167
+ serveNode,
168
+ unauthorized
166
169
  };