stitchkit 0.45.0 → 0.47.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 (122) hide show
  1. package/README.md +5 -4
  2. package/dist/browser/cancellation.d.ts +14 -0
  3. package/dist/browser/cancellation.d.ts.map +1 -0
  4. package/dist/browser/client-multipart.d.ts +3 -1
  5. package/dist/browser/client-multipart.d.ts.map +1 -1
  6. package/dist/browser/client.d.ts +1 -0
  7. package/dist/browser/client.d.ts.map +1 -1
  8. package/dist/browser/http.d.ts +1 -0
  9. package/dist/browser/http.d.ts.map +1 -1
  10. package/dist/browser/socket-io.d.ts +3 -1
  11. package/dist/browser/socket-io.d.ts.map +1 -1
  12. package/dist/cli.js +3 -4
  13. package/dist/contract/define.d.ts +59 -15
  14. package/dist/contract/define.d.ts.map +1 -1
  15. package/dist/contract/errors.d.ts +1 -0
  16. package/dist/contract/errors.d.ts.map +1 -1
  17. package/dist/contract/index.d.ts +1 -1
  18. package/dist/contract/index.d.ts.map +1 -1
  19. package/dist/contract/index.js +2 -2
  20. package/dist/{index-w0mec79h.js → index-44xysy8r.js} +697 -301
  21. package/dist/{index-r6czv7yt.js → index-45dz4m51.js} +50 -3
  22. package/dist/index-8ekq6res.js +181 -0
  23. package/dist/{index-yh0p5ddn.js → index-ee621cmy.js} +39 -19
  24. package/dist/{index-mvjyq9h9.js → index-kp8xamqp.js} +309 -144
  25. package/dist/{index-36f1xkdb.js → index-nrytvb30.js} +5 -4
  26. package/dist/{index-6jypn22c.js → index-r1qp4rve.js} +9 -1
  27. package/dist/{index-s4qsmgwe.js → index-tss6bk5c.js} +4 -1
  28. package/dist/index-yxpe3phd.js +63 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +315 -134
  32. package/dist/internal/errors.d.ts.map +1 -1
  33. package/dist/internal/secure-fetch.d.ts +23 -3
  34. package/dist/internal/secure-fetch.d.ts.map +1 -1
  35. package/dist/internal/typed.d.ts +7 -0
  36. package/dist/internal/typed.d.ts.map +1 -1
  37. package/dist/internal/within-dir.d.ts +6 -0
  38. package/dist/internal/within-dir.d.ts.map +1 -1
  39. package/dist/logger.d.ts +8 -0
  40. package/dist/logger.d.ts.map +1 -0
  41. package/dist/node.js +3 -4
  42. package/dist/observability/audit.d.ts +22 -3
  43. package/dist/observability/audit.d.ts.map +1 -1
  44. package/dist/observability/event.d.ts +7 -0
  45. package/dist/observability/event.d.ts.map +1 -1
  46. package/dist/observability/index.d.ts +1 -1
  47. package/dist/observability/index.d.ts.map +1 -1
  48. package/dist/observability/index.js +130 -134
  49. package/dist/observability/sanitize.d.ts +8 -1
  50. package/dist/observability/sanitize.d.ts.map +1 -1
  51. package/dist/observability/trace.d.ts +3 -1
  52. package/dist/observability/trace.d.ts.map +1 -1
  53. package/dist/react/cache-bridge.d.ts +4 -0
  54. package/dist/react/cache-bridge.d.ts.map +1 -1
  55. package/dist/react.js +46 -5
  56. package/dist/realtime/contract.d.ts +4 -1
  57. package/dist/realtime/contract.d.ts.map +1 -1
  58. package/dist/realtime/rejection.d.ts +10 -0
  59. package/dist/realtime/rejection.d.ts.map +1 -0
  60. package/dist/realtime/socket.d.ts +14 -1
  61. package/dist/realtime/socket.d.ts.map +1 -1
  62. package/dist/server/cache.d.ts +5 -1
  63. package/dist/server/cache.d.ts.map +1 -1
  64. package/dist/server/context.d.ts +8 -5
  65. package/dist/server/context.d.ts.map +1 -1
  66. package/dist/server/create.d.ts.map +1 -1
  67. package/dist/server/error-hook.d.ts +1 -0
  68. package/dist/server/error-hook.d.ts.map +1 -1
  69. package/dist/server/file.d.ts +6 -0
  70. package/dist/server/file.d.ts.map +1 -1
  71. package/dist/server/implement.d.ts +33 -2
  72. package/dist/server/implement.d.ts.map +1 -1
  73. package/dist/server/index.d.ts +5 -6
  74. package/dist/server/index.d.ts.map +1 -1
  75. package/dist/server/index.js +133 -14
  76. package/dist/server/middleware/auth.d.ts +7 -4
  77. package/dist/server/middleware/auth.d.ts.map +1 -1
  78. package/dist/server/middleware/cors.d.ts.map +1 -1
  79. package/dist/server/multipart.d.ts +13 -18
  80. package/dist/server/multipart.d.ts.map +1 -1
  81. package/dist/server/openapi.d.ts.map +1 -1
  82. package/dist/server/realtime.d.ts +2 -0
  83. package/dist/server/realtime.d.ts.map +1 -1
  84. package/dist/server/router.d.ts +0 -8
  85. package/dist/server/router.d.ts.map +1 -1
  86. package/dist/server/stream.d.ts.map +1 -1
  87. package/dist/server/types.d.ts +50 -22
  88. package/dist/server/types.d.ts.map +1 -1
  89. package/dist/tools/cli-args.d.ts +11 -3
  90. package/dist/tools/cli-args.d.ts.map +1 -1
  91. package/dist/tools/cli.d.ts +5 -0
  92. package/dist/tools/cli.d.ts.map +1 -1
  93. package/dist/tools/coerce.d.ts.map +1 -1
  94. package/dist/tools/json-schema.d.ts.map +1 -1
  95. package/dist/tools/mcp-handler.d.ts +4 -3
  96. package/dist/tools/mcp-handler.d.ts.map +1 -1
  97. package/dist/tools/mcp-prepare.d.ts +95 -0
  98. package/dist/tools/mcp-prepare.d.ts.map +1 -0
  99. package/dist/tools/mcp-round.d.ts +4 -0
  100. package/dist/tools/mcp-round.d.ts.map +1 -1
  101. package/dist/tools/mcp-stdio.d.ts +2 -1
  102. package/dist/tools/mcp-stdio.d.ts.map +1 -1
  103. package/dist/tools/mcp.d.ts +2 -78
  104. package/dist/tools/mcp.d.ts.map +1 -1
  105. package/dist/tools/mount-download.d.ts +5 -0
  106. package/dist/tools/mount-download.d.ts.map +1 -1
  107. package/dist/tools/native-mcp.d.ts +1 -1
  108. package/dist/tools/native-mcp.d.ts.map +1 -1
  109. package/dist/tools/oauth-provider.d.ts +20 -1
  110. package/dist/tools/oauth-provider.d.ts.map +1 -1
  111. package/dist/tools/tool-logger.d.ts +1 -1
  112. package/dist/tools/tool-logger.d.ts.map +1 -1
  113. package/dist/tools/view-file.d.ts +9 -0
  114. package/dist/tools/view-file.d.ts.map +1 -1
  115. package/dist/tools.d.ts +2 -1
  116. package/dist/tools.d.ts.map +1 -1
  117. package/dist/tools.js +505 -278
  118. package/llms-full.txt +473 -85
  119. package/llms.txt +1 -1
  120. package/package.json +2 -2
  121. package/dist/index-frfyw9fa.js +0 -33
  122. package/dist/index-x3fcszf8.js +0 -8
package/dist/index.js CHANGED
@@ -17,12 +17,70 @@ import {
17
17
  parseTrailingWildcard,
18
18
  rateLimited,
19
19
  unauthorized
20
- } from "./index-r6czv7yt.js";
20
+ } from "./index-45dz4m51.js";
21
21
  import {
22
22
  isRecord,
23
23
  mapObject,
24
+ transportResult,
24
25
  typedEntries
25
- } from "./index-s4qsmgwe.js";
26
+ } from "./index-tss6bk5c.js";
27
+
28
+ // src/browser/cancellation.ts
29
+ class RequestCancellationError extends Error {
30
+ cause;
31
+ constructor(cause) {
32
+ super(cause === "caller" ? "Request was aborted" : "Request timed out");
33
+ this.cause = cause;
34
+ this.name = "RequestCancellationError";
35
+ }
36
+ }
37
+ function createRequestCancellation(caller, timeoutMs) {
38
+ if (!caller && timeoutMs === undefined) {
39
+ return { signal: undefined, run: (operation) => operation() };
40
+ }
41
+ const controller = new AbortController;
42
+ let cause;
43
+ let timer;
44
+ const abortFromCaller = () => {
45
+ if (cause)
46
+ return;
47
+ cause = "caller";
48
+ controller.abort(caller?.reason);
49
+ };
50
+ if (caller?.aborted)
51
+ abortFromCaller();
52
+ else
53
+ caller?.addEventListener("abort", abortFromCaller, { once: true });
54
+ if (timeoutMs !== undefined) {
55
+ timer = setTimeout(() => {
56
+ if (cause)
57
+ return;
58
+ cause = "timeout";
59
+ controller.abort(new DOMException("Request timed out", "TimeoutError"));
60
+ }, timeoutMs);
61
+ }
62
+ return {
63
+ signal: controller.signal,
64
+ async run(operation) {
65
+ try {
66
+ if (cause === "caller")
67
+ throw cancellationError(cause);
68
+ return await operation(controller.signal);
69
+ } catch (error) {
70
+ if (cause)
71
+ throw cancellationError(cause);
72
+ throw error;
73
+ } finally {
74
+ if (timer !== undefined)
75
+ clearTimeout(timer);
76
+ caller?.removeEventListener("abort", abortFromCaller);
77
+ }
78
+ }
79
+ };
80
+ }
81
+ function cancellationError(cause) {
82
+ return new RequestCancellationError(cause);
83
+ }
26
84
 
27
85
  // src/browser/client-multipart.ts
28
86
  function isFileDescriptor(value) {
@@ -42,6 +100,35 @@ function appendFormFields(formData, values, skipKeys) {
42
100
  formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
43
101
  }
44
102
  }
103
+ function buildMultipartForm(descriptor, values) {
104
+ const formData = new FormData;
105
+ const fileFields = new Set(Object.keys(descriptor.files));
106
+ for (const [field, policy] of Object.entries(descriptor.files)) {
107
+ const value = values[field];
108
+ if (value === undefined) {
109
+ if (policy.required !== false)
110
+ throw new Error(`Missing multipart file field: ${field}`);
111
+ continue;
112
+ }
113
+ if (policy.multiple === true) {
114
+ if (!Array.isArray(value) || value.length === 0) {
115
+ throw new Error(`Multipart file field "${field}" must be a non-empty array`);
116
+ }
117
+ for (const file of value) {
118
+ if (!isMultipartFile(file)) {
119
+ throw new Error(`Invalid multipart file field: ${field}`);
120
+ }
121
+ appendMultipartFile(formData, field, file);
122
+ }
123
+ continue;
124
+ }
125
+ if (!isMultipartFile(value))
126
+ throw new Error(`Invalid multipart file field: ${field}`);
127
+ appendMultipartFile(formData, field, value);
128
+ }
129
+ appendFormFields(formData, values, fileFields);
130
+ return formData;
131
+ }
45
132
 
46
133
  // src/internal/http-input.ts
47
134
  function inputIsQuery(method) {
@@ -211,7 +298,7 @@ function joinClientBaseUrl(baseUrl, relativeUrl) {
211
298
  import ky, { isHTTPError } from "ky";
212
299
 
213
300
  // src/observability/trace.ts
214
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
301
+ var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
215
302
  function randomHex(bytes) {
216
303
  const arr = new Uint8Array(bytes);
217
304
  crypto.getRandomValues(arr);
@@ -227,16 +314,24 @@ function parseTraceparent(header) {
227
314
  if (!header)
228
315
  return null;
229
316
  const match = TRACEPARENT_RE.exec(header.trim());
230
- if (!match?.[1] || !match[2])
317
+ if (!match?.[1] || !match[2] || !match[3] || !match[4] || match[5] === undefined)
318
+ return null;
319
+ const version = match[1].toLowerCase();
320
+ if (version === "ff")
321
+ return null;
322
+ const suffix = match[5];
323
+ if (version === "00" ? suffix !== "" : suffix !== "" && !/^(?:-[0-9a-f]{2,})+$/i.test(suffix)) {
231
324
  return null;
232
- const traceId = match[1].toLowerCase();
233
- const parentSpanId = match[2].toLowerCase();
325
+ }
326
+ const traceId = match[2].toLowerCase();
327
+ const parentSpanId = match[3].toLowerCase();
328
+ const traceFlags = match[4].toLowerCase();
234
329
  if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
235
330
  return null;
236
- return { traceId, spanId: randomHex(8), parentSpanId };
331
+ return { traceId, spanId: randomHex(8), parentSpanId, traceFlags };
237
332
  }
238
333
  function formatTraceparent(ctx) {
239
- return `00-${ctx.traceId}-${ctx.spanId}-01`;
334
+ return `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags ?? "01"}`;
240
335
  }
241
336
  function childSpan(parent) {
242
337
  return {
@@ -244,7 +339,8 @@ function childSpan(parent) {
244
339
  spanId: randomHex(8),
245
340
  parentSpanId: parent.spanId,
246
341
  ...parent.tracestate !== undefined && { tracestate: parent.tracestate },
247
- ...parent.baggage !== undefined && { baggage: parent.baggage }
342
+ ...parent.baggage !== undefined && { baggage: parent.baggage },
343
+ ...parent.traceFlags !== undefined && { traceFlags: parent.traceFlags }
248
344
  };
249
345
  }
250
346
  var encoder = new TextEncoder;
@@ -304,7 +400,7 @@ function createHttpClient(config) {
304
400
  const client = ky.create({
305
401
  prefix: config.baseUrl,
306
402
  credentials: config.credentials ?? "include",
307
- timeout: config.timeout ?? 30000,
403
+ timeout: false,
308
404
  retry: {
309
405
  limit: config.retry?.limit ?? 2,
310
406
  methods: config.retry?.methods ?? ["get"],
@@ -350,8 +446,10 @@ function createHttpClient(config) {
350
446
  }
351
447
  });
352
448
  async function request(method, url, data, options = {}) {
449
+ const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
353
450
  const kyOptions = {
354
- timeout: options.timeout
451
+ timeout: false,
452
+ signal: cancellation.signal
355
453
  };
356
454
  if (options.params) {
357
455
  const searchParams = new URLSearchParams;
@@ -375,31 +473,33 @@ function createHttpClient(config) {
375
473
  kyOptions.json = data;
376
474
  }
377
475
  try {
378
- if (options.responseType === "blob") {
379
- return client[method](url, kyOptions).blob();
380
- }
381
- if (options.responseType === "response") {
382
- return await client[method](url, kyOptions);
383
- }
384
- const response = await client[method](url, kyOptions);
385
- if (options.responseType === "void") {
386
- const text = await response.text();
387
- if (text.length > 0) {
388
- throw new Error("Server returned data for an endpoint with no output contract");
476
+ return await cancellation.run(async () => {
477
+ if (options.responseType === "blob") {
478
+ return transportResult(await client[method](url, kyOptions).blob());
389
479
  }
390
- }
391
- if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
392
- return;
393
- }
394
- return response.json();
480
+ if (options.responseType === "response") {
481
+ return transportResult(await client[method](url, kyOptions));
482
+ }
483
+ const response = await client[method](url, kyOptions);
484
+ if (options.responseType === "void") {
485
+ const text = await response.text();
486
+ if (text.length > 0) {
487
+ throw new Error("Server returned data for an endpoint with no output contract");
488
+ }
489
+ }
490
+ if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
491
+ return transportResult(undefined);
492
+ }
493
+ return await response.json();
494
+ });
395
495
  } catch (error) {
496
+ if (error instanceof RequestCancellationError) {
497
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
498
+ }
396
499
  if (ApiError.is(error))
397
500
  throw error;
398
- const isAbort = error instanceof Error && error.name === "AbortError";
399
- if (!isAbort) {
400
- emit({ type: "network_error" });
401
- }
402
- const response = !isAbort && isHTTPError(error) ? error.response : undefined;
501
+ emit({ type: "network_error" });
502
+ const response = isHTTPError(error) ? error.response : undefined;
403
503
  const status = response?.status ?? 0;
404
504
  const msg = error instanceof Error ? error.message : undefined;
405
505
  throw new ApiError("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, undefined, undefined, responseTraceId(response));
@@ -526,95 +626,108 @@ function setClientMethod(target, key, method) {
526
626
  function createHttpMethod(endpoint, prefix, client, config) {
527
627
  const httpMethod = endpoint.method.toLowerCase();
528
628
  return (...args) => {
529
- const firstArg = args[0] ?? {};
629
+ const { requestArgs: firstArg, options } = splitClientCall(endpoint, args, config);
530
630
  const plan = planClientRequest(endpoint, prefix, firstArg, config);
531
631
  if (endpoint.multipart) {
532
- const file = firstArg[endpoint.multipart];
533
- if (!isMultipartFile(file)) {
534
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
535
- }
536
632
  if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
537
633
  throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
538
634
  }
539
- const formData = new FormData;
540
- appendMultipartFile(formData, endpoint.multipart, file);
541
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
542
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
635
+ const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
636
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
543
637
  }
544
638
  if (httpMethod === "get" || httpMethod === "head") {
545
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(undefined, endpoint)));
639
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
546
640
  }
547
641
  if (httpMethod === "delete") {
548
- return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
642
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
549
643
  }
550
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
644
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
551
645
  };
552
646
  }
553
647
  function createFetchMethod(endpoint, prefix, config, contractConfig) {
554
- return async (args) => {
555
- const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
648
+ return async (...callArgs) => {
649
+ const { requestArgs, options } = splitClientCall(endpoint, callArgs, contractConfig);
650
+ const plan = planClientRequest(endpoint, prefix, requestArgs, contractConfig);
556
651
  const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
557
652
  const headers = {
558
653
  Accept: "application/json",
559
654
  ...typeof config.headers === "function" ? config.headers() : config.headers
560
655
  };
561
- const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
562
- const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
656
+ const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
657
+ const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
563
658
  if (hasBody)
564
659
  headers["Content-Type"] = "application/json";
565
- if (endpoint.multipart && args) {
566
- const file = args[endpoint.multipart];
567
- if (!isMultipartFile(file)) {
568
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
569
- }
570
- const formData = new FormData;
571
- appendMultipartFile(formData, endpoint.multipart, file);
572
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
573
- const res2 = await fetch(url, {
574
- method: endpoint.method,
575
- headers,
576
- credentials: config.credentials,
577
- body: formData,
578
- signal
579
- });
580
- if (!res2.ok) {
581
- await throwForErrorResponse(res2, config, null);
582
- }
583
- if (endpoint.rawResponse)
584
- return res2;
585
- if (!endpoint.output) {
586
- const text = await res2.text();
587
- if (text.length > 0) {
588
- throw new Error("Server returned data for an endpoint with no output contract");
660
+ try {
661
+ return await cancellation.run(async (signal) => {
662
+ const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
663
+ const res = await fetch(url, {
664
+ method: endpoint.method,
665
+ headers,
666
+ credentials: config.credentials,
667
+ signal,
668
+ ...body !== undefined && { body }
669
+ });
670
+ if (!res.ok) {
671
+ await throwForErrorResponse(res, config, { error: res.statusText });
589
672
  }
590
- return;
591
- }
592
- return endpoint.output.parse(await res2.json());
593
- }
594
- const res = await fetch(url, {
595
- method: endpoint.method,
596
- headers,
597
- credentials: config.credentials,
598
- signal,
599
- ...hasBody && {
600
- body: JSON.stringify(plan.remainingArgs)
601
- }
602
- });
603
- if (!res.ok) {
604
- await throwForErrorResponse(res, config, { error: res.statusText });
605
- }
606
- if (endpoint.rawResponse)
607
- return res;
608
- if (!endpoint.output) {
609
- const text = await res.text();
610
- if (text.length > 0) {
611
- throw new Error("Server returned data for an endpoint with no output contract");
673
+ if (endpoint.rawResponse)
674
+ return res;
675
+ if (!endpoint.output) {
676
+ const text = await res.text();
677
+ if (text.length > 0) {
678
+ throw new Error("Server returned data for an endpoint with no output contract");
679
+ }
680
+ return;
681
+ }
682
+ return endpoint.output.parse(await res.json());
683
+ });
684
+ } catch (error) {
685
+ if (error instanceof RequestCancellationError) {
686
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
612
687
  }
613
- return;
688
+ if (ApiError.is(error))
689
+ throw error;
690
+ const message = error instanceof Error ? error.message : undefined;
691
+ throw new ApiError("UNKNOWN_ERROR", 0, message ? { message } : undefined, message);
614
692
  }
615
- return endpoint.output.parse(await res.json());
616
693
  };
617
694
  }
695
+ function endpointHasArguments(endpoint) {
696
+ return Boolean(endpoint.params || endpoint.input || endpoint.multipart);
697
+ }
698
+ function splitClientCall(endpoint, args, contractConfig) {
699
+ const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
700
+ if (!endpointHasArguments(endpoint) && !hasScopedArguments) {
701
+ return {
702
+ requestArgs: {},
703
+ options: readClientRequestOptions(args[0])
704
+ };
705
+ }
706
+ const requestArgs = args[0];
707
+ if (requestArgs !== undefined && !isRecord(requestArgs)) {
708
+ throw new TypeError("Endpoint arguments must be an object");
709
+ }
710
+ return {
711
+ requestArgs: requestArgs ?? {},
712
+ options: readClientRequestOptions(args[1])
713
+ };
714
+ }
715
+ function readClientRequestOptions(value) {
716
+ if (value === undefined)
717
+ return;
718
+ if (!isRecord(value))
719
+ throw new TypeError("Client request options must be an object");
720
+ const signal = value.signal;
721
+ if (signal === undefined)
722
+ return {};
723
+ if (!isAbortSignal(signal)) {
724
+ throw new TypeError("Client request signal must be an AbortSignal");
725
+ }
726
+ return { signal };
727
+ }
728
+ function isAbortSignal(value) {
729
+ return typeof value === "object" && value !== null && "aborted" in value && typeof value.aborted === "boolean" && "addEventListener" in value && typeof value.addEventListener === "function" && "removeEventListener" in value && typeof value.removeEventListener === "function";
730
+ }
618
731
  async function throwForErrorResponse(res, config, fallbackBody) {
619
732
  const body = await res.json().catch(() => fallbackBody);
620
733
  config.onError?.(res.status, body);
@@ -646,9 +759,40 @@ function createUrlBuilders(contracts, source, contractConfig) {
646
759
  function createScopedUrlBuilders(contracts, source, scopeConfigs) {
647
760
  return buildScopedRegistry(contracts, scopeConfigs, "URL builder", (contract, config) => createUrlBuilder(contract, source, config));
648
761
  }
649
- // src/realtime/socket.ts
762
+ // src/realtime/rejection.ts
763
+ import { z as z2 } from "zod";
764
+
765
+ // src/internal/errors.ts
650
766
  import { z } from "zod";
651
- var AcknowledgementCallbackSchema = z.function();
767
+ function issuePath(path) {
768
+ return path.length > 0 ? path.map(String).join(".") : "(root)";
769
+ }
770
+ function zodIssues(error) {
771
+ return error.issues.map((issue) => ({
772
+ path: issuePath(issue.path),
773
+ code: issue.code,
774
+ message: issue.message
775
+ }));
776
+ }
777
+
778
+ // src/realtime/rejection.ts
779
+ function realtimeContractViolation(options) {
780
+ const issues = options.cause instanceof z2.ZodError ? zodIssues(options.cause) : undefined;
781
+ const reason = options.reason.replaceAll("-", " ");
782
+ const error = new AppError("REALTIME_CONTRACT_VIOLATION", `Realtime event "${options.event}" (${options.direction}, ${options.phase}): ${reason}`, 500, {
783
+ event: options.event,
784
+ direction: options.direction,
785
+ phase: options.phase,
786
+ reason: options.reason,
787
+ fault: options.fault,
788
+ ...issues !== undefined && { issues }
789
+ });
790
+ if (options.cause !== undefined)
791
+ error.cause = options.cause;
792
+ return { ...options, error };
793
+ }
794
+
795
+ // src/realtime/socket.ts
652
796
  function method(target, name) {
653
797
  const value = Reflect.get(target, name);
654
798
  if (typeof value !== "function") {
@@ -667,15 +811,26 @@ function splitWireArguments(definition, args) {
667
811
  acknowledgement: (...callbackArgs) => Reflect.apply(acknowledgement, undefined, callbackArgs)
668
812
  };
669
813
  }
670
- function eventDefinition(registry, event) {
814
+ function eventDefinition(registry, event, direction) {
671
815
  const definition = registry[event];
672
- if (!definition)
673
- throw new Error(`Unknown realtime event "${event}"`);
816
+ if (!definition) {
817
+ throw realtimeContractViolation({
818
+ event,
819
+ direction,
820
+ phase: "arguments",
821
+ reason: "unknown-event",
822
+ fault: "local"
823
+ }).error;
824
+ }
674
825
  return definition;
675
826
  }
676
- function reportRejected(rejected, hook) {
827
+ function reportRejected(rejected, hook, logger) {
677
828
  if (!hook) {
678
- console.error(`[stitchkit] rejected realtime ${rejected.direction} event "${rejected.event}" (${rejected.phase})`, rejected.error);
829
+ if (logger) {
830
+ logger.warn(rejected.error.message, rejected.error.details);
831
+ return;
832
+ }
833
+ console.warn(`[stitchkit] rejected realtime ${rejected.direction} event "${rejected.event}" (${rejected.phase})`, rejected.error);
679
834
  return;
680
835
  }
681
836
  Promise.resolve(hook(rejected)).catch((error) => {
@@ -689,36 +844,35 @@ function createValidatedRealtimeSocket({
689
844
  inboundDirection,
690
845
  outboundDirection,
691
846
  onRejected,
847
+ logger,
692
848
  subscribe
693
849
  }) {
694
- const onTarget = method(target, "on");
695
- const offTarget = subscribe ? undefined : method(target, "off");
696
850
  const emitTarget = method(target, "emit");
697
851
  return {
698
852
  on: (event, handler) => {
699
- const definition = eventDefinition(inbound, event);
853
+ const definition = eventDefinition(inbound, event, inboundDirection);
700
854
  const wrapped = (...wireArgs) => {
701
855
  const { values, acknowledgement } = splitWireArguments(definition, wireArgs);
702
856
  const parsed = definition.args.safeParse(values);
703
857
  if (!parsed.success) {
704
- reportRejected({
858
+ reportRejected(realtimeContractViolation({
705
859
  event,
706
860
  direction: inboundDirection,
707
861
  phase: "arguments",
708
- error: parsed.error
709
- }, onRejected);
862
+ reason: "invalid-arguments",
863
+ fault: "peer",
864
+ cause: parsed.error
865
+ }), onRejected, logger);
710
866
  return;
711
867
  }
712
868
  if (definition.ack && !acknowledgement) {
713
- const callback = AcknowledgementCallbackSchema.safeParse(undefined);
714
- if (!callback.success) {
715
- reportRejected({
716
- event,
717
- direction: inboundDirection,
718
- phase: "acknowledgement",
719
- error: callback.error
720
- }, onRejected);
721
- }
869
+ reportRejected(realtimeContractViolation({
870
+ event,
871
+ direction: inboundDirection,
872
+ phase: "acknowledgement",
873
+ reason: "missing-acknowledgement",
874
+ fault: "peer"
875
+ }), onRejected, logger);
722
876
  return;
723
877
  }
724
878
  const applicationArgs = [...parsed.data];
@@ -727,12 +881,14 @@ function createValidatedRealtimeSocket({
727
881
  const ack = definition.ack?.safeParse(value);
728
882
  if (!ack?.success) {
729
883
  if (ack) {
730
- reportRejected({
884
+ reportRejected(realtimeContractViolation({
731
885
  event,
732
886
  direction: outboundDirection,
733
887
  phase: "acknowledgement",
734
- error: ack.error
735
- }, onRejected);
888
+ reason: "invalid-acknowledgement-value",
889
+ fault: "local",
890
+ cause: ack.error
891
+ }), onRejected, logger);
736
892
  }
737
893
  return;
738
894
  }
@@ -743,30 +899,50 @@ function createValidatedRealtimeSocket({
743
899
  };
744
900
  if (subscribe)
745
901
  return subscribe(event, wrapped);
902
+ const onTarget = method(target, "on");
903
+ const offTarget = method(target, "off");
746
904
  onTarget(event, wrapped);
747
905
  return () => {
748
- offTarget?.(event, wrapped);
906
+ offTarget(event, wrapped);
749
907
  };
750
908
  },
751
909
  emit: (event, ...args) => {
752
- const definition = eventDefinition(outbound, event);
910
+ const definition = eventDefinition(outbound, event, outboundDirection);
753
911
  const { values, acknowledgement } = splitWireArguments(definition, args);
754
- const parsed = definition.args.parse(values);
912
+ const parsed = definition.args.safeParse(values);
913
+ if (!parsed.success) {
914
+ throw realtimeContractViolation({
915
+ event,
916
+ direction: outboundDirection,
917
+ phase: "arguments",
918
+ reason: "invalid-arguments",
919
+ fault: "local",
920
+ cause: parsed.error
921
+ }).error;
922
+ }
755
923
  if (definition.ack && !acknowledgement) {
756
- AcknowledgementCallbackSchema.parse(undefined);
924
+ throw realtimeContractViolation({
925
+ event,
926
+ direction: outboundDirection,
927
+ phase: "acknowledgement",
928
+ reason: "missing-acknowledgement",
929
+ fault: "local"
930
+ }).error;
757
931
  }
758
- const wireArgs = [...parsed];
932
+ const wireArgs = [...parsed.data];
759
933
  if (definition.ack && acknowledgement) {
760
934
  wireArgs.push((value) => {
761
935
  const ack = definition.ack?.safeParse(value);
762
936
  if (!ack?.success) {
763
937
  if (ack) {
764
- reportRejected({
938
+ reportRejected(realtimeContractViolation({
765
939
  event,
766
940
  direction: inboundDirection,
767
941
  phase: "acknowledgement",
768
- error: ack.error
769
- }, onRejected);
942
+ reason: "invalid-acknowledgement-value",
943
+ fault: "peer",
944
+ cause: ack.error
945
+ }), onRejected, logger);
770
946
  }
771
947
  return;
772
948
  }
@@ -823,7 +999,7 @@ function toIoAuth(auth) {
823
999
  Promise.resolve().then(auth).then(cb, () => cb({}));
824
1000
  };
825
1001
  }
826
- function createRealtimeClient(contract, { onRejected, ...config }) {
1002
+ function createRealtimeClient(contract, { onRejected, logger, ...config }) {
827
1003
  const transport = createSocketIOClient(config);
828
1004
  const events = createValidatedRealtimeSocket({
829
1005
  target: transport,
@@ -832,6 +1008,7 @@ function createRealtimeClient(contract, { onRejected, ...config }) {
832
1008
  inboundDirection: "client-inbound",
833
1009
  outboundDirection: "client-outbound",
834
1010
  onRejected,
1011
+ logger,
835
1012
  subscribe: (event, handler) => {
836
1013
  const subscribe = Reflect.get(transport, "on");
837
1014
  if (typeof subscribe !== "function") {
@@ -906,6 +1083,9 @@ function createSocketIOClient(config) {
906
1083
  }, serverDisconnectDelay);
907
1084
  }
908
1085
  });
1086
+ socket.io.on("reconnect_failed", () => {
1087
+ desiredConnected = false;
1088
+ });
909
1089
  if (retained) {
910
1090
  for (const name of retainNames) {
911
1091
  socket.on(name, (payload) => retained.record(name, payload));
@@ -920,9 +1100,13 @@ function createSocketIOClient(config) {
920
1100
  return socket?.connected ?? false;
921
1101
  },
922
1102
  connect() {
923
- if (socket || desiredConnected)
1103
+ if (socket?.connected || desiredConnected)
924
1104
  return;
925
1105
  desiredConnected = true;
1106
+ if (socket) {
1107
+ socket.connect();
1108
+ return;
1109
+ }
926
1110
  loadIo().then(openSocket);
927
1111
  },
928
1112
  disconnect() {
@@ -966,9 +1150,6 @@ function createSocketIOClient(config) {
966
1150
  }
967
1151
  };
968
1152
  }
969
- // src/internal/errors.ts
970
- import { z as z2 } from "zod";
971
-
972
1153
  // src/server/stream.ts
973
1154
  async function* parseSSE(response, options) {
974
1155
  const reader = response.body?.getReader();
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAMxD;AAED,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,eAAe,EAAE,CAM9D;AAKD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,MAAM,EAAE,OAAO,GACd,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAgBrD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,EACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe9D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,IAAI,EAAE,OAAO,EACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe9D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/internal/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAQvC,wBAAgB,cAAc,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAMxD;AAED,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,eAAe,EAAE,CAM9D;AAKD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAI1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,MAAM,EAAE,OAAO,GACd,MAAM,GAAG,SAAS,CAYpB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,CAyBrD;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,EACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe9D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,IAAI,EAAE,OAAO,EACb,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAe9D"}