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.
- package/README.md +5 -4
- package/dist/browser/cancellation.d.ts +14 -0
- package/dist/browser/cancellation.d.ts.map +1 -0
- package/dist/browser/client-multipart.d.ts +3 -1
- package/dist/browser/client-multipart.d.ts.map +1 -1
- package/dist/browser/client.d.ts +1 -0
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/socket-io.d.ts +3 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +3 -4
- package/dist/contract/define.d.ts +59 -15
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +1 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +2 -2
- package/dist/{index-w0mec79h.js → index-44xysy8r.js} +697 -301
- package/dist/{index-r6czv7yt.js → index-45dz4m51.js} +50 -3
- package/dist/index-8ekq6res.js +181 -0
- package/dist/{index-yh0p5ddn.js → index-ee621cmy.js} +39 -19
- package/dist/{index-mvjyq9h9.js → index-kp8xamqp.js} +309 -144
- package/dist/{index-36f1xkdb.js → index-nrytvb30.js} +5 -4
- package/dist/{index-6jypn22c.js → index-r1qp4rve.js} +9 -1
- package/dist/{index-s4qsmgwe.js → index-tss6bk5c.js} +4 -1
- package/dist/index-yxpe3phd.js +63 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +315 -134
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/secure-fetch.d.ts +23 -3
- package/dist/internal/secure-fetch.d.ts.map +1 -1
- package/dist/internal/typed.d.ts +7 -0
- package/dist/internal/typed.d.ts.map +1 -1
- package/dist/internal/within-dir.d.ts +6 -0
- package/dist/internal/within-dir.d.ts.map +1 -1
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/node.js +3 -4
- package/dist/observability/audit.d.ts +22 -3
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/event.d.ts +7 -0
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +130 -134
- package/dist/observability/sanitize.d.ts +8 -1
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/observability/trace.d.ts +3 -1
- package/dist/observability/trace.d.ts.map +1 -1
- package/dist/react/cache-bridge.d.ts +4 -0
- package/dist/react/cache-bridge.d.ts.map +1 -1
- package/dist/react.js +46 -5
- package/dist/realtime/contract.d.ts +4 -1
- package/dist/realtime/contract.d.ts.map +1 -1
- package/dist/realtime/rejection.d.ts +10 -0
- package/dist/realtime/rejection.d.ts.map +1 -0
- package/dist/realtime/socket.d.ts +14 -1
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/server/cache.d.ts +5 -1
- package/dist/server/cache.d.ts.map +1 -1
- package/dist/server/context.d.ts +8 -5
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +1 -0
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/file.d.ts +6 -0
- package/dist/server/file.d.ts.map +1 -1
- package/dist/server/implement.d.ts +33 -2
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -6
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +133 -14
- package/dist/server/middleware/auth.d.ts +7 -4
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +13 -18
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/realtime.d.ts +2 -0
- package/dist/server/realtime.d.ts.map +1 -1
- package/dist/server/router.d.ts +0 -8
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/types.d.ts +50 -22
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/cli-args.d.ts +11 -3
- package/dist/tools/cli-args.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +5 -0
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/coerce.d.ts.map +1 -1
- package/dist/tools/json-schema.d.ts.map +1 -1
- package/dist/tools/mcp-handler.d.ts +4 -3
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mcp-prepare.d.ts +95 -0
- package/dist/tools/mcp-prepare.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +4 -0
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mcp-stdio.d.ts +2 -1
- package/dist/tools/mcp-stdio.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +2 -78
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts +5 -0
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/native-mcp.d.ts +1 -1
- package/dist/tools/native-mcp.d.ts.map +1 -1
- package/dist/tools/oauth-provider.d.ts +20 -1
- package/dist/tools/oauth-provider.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts +1 -1
- package/dist/tools/tool-logger.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +9 -0
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +2 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +505 -278
- package/llms-full.txt +473 -85
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/dist/index-frfyw9fa.js +0 -33
- 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-
|
|
20
|
+
} from "./index-45dz4m51.js";
|
|
21
21
|
import {
|
|
22
22
|
isRecord,
|
|
23
23
|
mapObject,
|
|
24
|
+
transportResult,
|
|
24
25
|
typedEntries
|
|
25
|
-
} from "./index-
|
|
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 = /^
|
|
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
|
-
|
|
233
|
-
const
|
|
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}
|
|
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:
|
|
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:
|
|
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
|
-
|
|
379
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
399
|
-
|
|
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
|
|
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 =
|
|
540
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
555
|
-
const
|
|
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
|
|
562
|
-
const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input &&
|
|
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
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
709
|
-
|
|
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
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
|
|
735
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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,
|
|
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"}
|