stitchkit 0.76.2 → 0.78.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 (74) hide show
  1. package/dist/agent-runtime-coding-tools.js +1 -1
  2. package/dist/agent-runtime-harness.js +6 -6
  3. package/dist/agent-runtime.js +11 -11
  4. package/dist/application/decisions.d.ts +89 -0
  5. package/dist/application/decisions.d.ts.map +1 -0
  6. package/dist/application/kernel.d.ts +35 -1
  7. package/dist/application/kernel.d.ts.map +1 -1
  8. package/dist/application/keyspace.d.ts +11 -1
  9. package/dist/application/keyspace.d.ts.map +1 -1
  10. package/dist/application/schedule.d.ts.map +1 -1
  11. package/dist/application/server-resource.d.ts.map +1 -1
  12. package/dist/application-diagnostic-journal.d.ts +21 -0
  13. package/dist/application-diagnostic-journal.d.ts.map +1 -0
  14. package/dist/application-diagnostic-journal.js +714 -0
  15. package/dist/application-opentelemetry.js +92 -5
  16. package/dist/application-schemas.d.ts +15 -0
  17. package/dist/application-schemas.d.ts.map +1 -0
  18. package/dist/application-schemas.js +26 -0
  19. package/dist/application.d.ts +3 -3
  20. package/dist/application.d.ts.map +1 -1
  21. package/dist/application.js +1832 -844
  22. package/dist/cli.js +7 -7
  23. package/dist/contract/index.js +9 -7
  24. package/dist/files/boundary.d.ts.map +1 -1
  25. package/dist/files.js +1 -1
  26. package/dist/{index-3z73fh2c.js → index-1ckavx4h.js} +49 -1
  27. package/dist/{index-s2rchahr.js → index-31k1ns1s.js} +4 -4
  28. package/dist/{index-m668wzyc.js → index-4ntp63ps.js} +4 -4
  29. package/dist/{index-1rxswfbv.js → index-56fm73re.js} +1 -1
  30. package/dist/{index-ezmn6ac6.js → index-5r0ak8fr.js} +3 -3
  31. package/dist/{index-f6n5n7nz.js → index-7z69kyrh.js} +10 -87
  32. package/dist/{index-k2zczx1g.js → index-93xp5tgg.js} +1 -1
  33. package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
  34. package/dist/index-anyqnqcv.js +79 -0
  35. package/dist/{index-7qy2ex0m.js → index-cwp38wxn.js} +1 -1
  36. package/dist/{index-35z5h2ty.js → index-dmfn9m4x.js} +125 -4
  37. package/dist/index-h4pj6fta.js +87 -0
  38. package/dist/{index-jqtsc9mj.js → index-j6mfaw26.js} +2 -2
  39. package/dist/{index-58jzmnn4.js → index-k45qqrdh.js} +513 -39
  40. package/dist/{index-8eywc9zv.js → index-kw12fsbc.js} +0 -1
  41. package/dist/index-n1g2xm6a.js +203 -0
  42. package/dist/{index-t8qyvrvg.js → index-n7apa6sy.js} +12 -12
  43. package/dist/{index-s4c8wy8m.js → index-qaajjfp6.js} +8 -8
  44. package/dist/{index-nemjkxjp.js → index-s5tn4kdv.js} +14 -10
  45. package/dist/index-wfq8zerm.js +92 -0
  46. package/dist/index.js +53 -1215
  47. package/dist/internal/deadline.d.ts +17 -0
  48. package/dist/internal/deadline.d.ts.map +1 -0
  49. package/dist/internal/decision.d.ts +31 -0
  50. package/dist/internal/decision.d.ts.map +1 -0
  51. package/dist/live/events.d.ts +8 -13
  52. package/dist/live/events.d.ts.map +1 -1
  53. package/dist/live.d.ts +1 -1
  54. package/dist/live.d.ts.map +1 -1
  55. package/dist/live.js +12 -89
  56. package/dist/node.js +5 -6
  57. package/dist/observability/context.d.ts.map +1 -1
  58. package/dist/observability/index.js +3 -3
  59. package/dist/primitives.js +2 -1
  60. package/dist/remote.js +3 -6
  61. package/dist/server/event-bus.d.ts +3 -2
  62. package/dist/server/event-bus.d.ts.map +1 -1
  63. package/dist/server/index.js +31 -30
  64. package/dist/testing.js +1562 -87
  65. package/dist/tool-invoker.js +6 -6
  66. package/dist/tools.js +66 -23
  67. package/llms-full.txt +276 -6
  68. package/package.json +12 -4
  69. package/dist/index-3cwck0rm.js +0 -897
  70. package/dist/index-7b188kmz.js +0 -49
  71. package/dist/index-a916km3r.js +0 -45
  72. package/dist/index-hvftzz91.js +0 -721
  73. package/dist/{index-da1aqnhb.js → index-aewy3x5j.js} +3 -3
  74. package/dist/{index-nt1mp8km.js → index-efm026pw.js} +3 -3
@@ -1,19 +1,71 @@
1
- import {
2
- DEFAULT_STREAM_LINE_BYTES,
3
- inputIsQuery,
4
- parseSSE,
5
- readBoundedUtf8Lines
6
- } from "./index-1rxswfbv.js";
7
1
  import {
8
2
  ContractStreamFrameSchema,
9
3
  DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
10
4
  parseTrailingWildcard
11
- } from "./index-vj3vvpaa.js";
5
+ } from "./index-7z69kyrh.js";
12
6
  import {
13
7
  isRecord,
14
8
  mapObject,
9
+ transportResult,
15
10
  typedEntries
16
- } from "./index-qyrqwr4c.js";
11
+ } from "./index-ksp6e2ye.js";
12
+
13
+ // src/internal/random-hex.ts
14
+ function randomHex(bytes) {
15
+ const arr = new Uint8Array(bytes);
16
+ crypto.getRandomValues(arr);
17
+ let hex = "";
18
+ for (const byte of arr)
19
+ hex += byte.toString(16).padStart(2, "0");
20
+ return hex;
21
+ }
22
+
23
+ // src/observability/trace.ts
24
+ var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
25
+ function createTraceContext() {
26
+ return { traceId: randomHex(16), spanId: randomHex(8) };
27
+ }
28
+ function parseTraceparent(header) {
29
+ if (!header)
30
+ return null;
31
+ const match = TRACEPARENT_RE.exec(header.trim());
32
+ if (!match?.[1] || !match[2] || !match[3] || !match[4] || match[5] === undefined)
33
+ return null;
34
+ const version = match[1].toLowerCase();
35
+ if (version === "ff")
36
+ return null;
37
+ const suffix = match[5];
38
+ if (version === "00" ? suffix !== "" : suffix !== "" && !/^(?:-[0-9a-f]{2,})+$/i.test(suffix)) {
39
+ return null;
40
+ }
41
+ const traceId = match[2].toLowerCase();
42
+ const parentSpanId = match[3].toLowerCase();
43
+ const traceFlags = match[4].toLowerCase();
44
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
45
+ return null;
46
+ return { traceId, spanId: randomHex(8), parentSpanId, traceFlags };
47
+ }
48
+ function formatTraceparent(ctx) {
49
+ return `00-${ctx.traceId}-${ctx.spanId}-${ctx.traceFlags ?? "01"}`;
50
+ }
51
+ function childSpan(parent) {
52
+ return {
53
+ traceId: parent.traceId,
54
+ spanId: randomHex(8),
55
+ parentSpanId: parent.spanId,
56
+ ...parent.tracestate !== undefined && { tracestate: parent.tracestate },
57
+ ...parent.baggage !== undefined && { baggage: parent.baggage },
58
+ ...parent.traceFlags !== undefined && { traceFlags: parent.traceFlags }
59
+ };
60
+ }
61
+ var encoder = new TextEncoder;
62
+
63
+ // src/browser/http.ts
64
+ import ky, {
65
+ isHTTPError,
66
+ isNetworkError,
67
+ isTimeoutError
68
+ } from "ky";
17
69
 
18
70
  // src/browser/cancellation.ts
19
71
  class RequestCancellationError extends Error {
@@ -72,13 +124,6 @@ function cancellationError(cause) {
72
124
  return new RequestCancellationError(cause);
73
125
  }
74
126
 
75
- // src/browser/http.ts
76
- import ky, {
77
- isHTTPError,
78
- isNetworkError,
79
- isTimeoutError
80
- } from "ky";
81
-
82
127
  // src/browser/request-id.ts
83
128
  var REQUEST_ID_HEADER = "x-request-id";
84
129
  function responseTraceId(response) {
@@ -114,6 +159,15 @@ class ApiError extends Error {
114
159
  function refuseLocally(path, message) {
115
160
  return new ApiError("VALIDATION_ERROR", 0, { issues: [{ path, code: "invalid_type", message }] }, message);
116
161
  }
162
+ function shouldRetryBunNetworkError(error) {
163
+ if (ApiError.is(error) || isHTTPError(error) || isNetworkError(error) || isTimeoutError(error) || error instanceof RequestCancellationError || error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) {
164
+ return;
165
+ }
166
+ if (!(error instanceof Error))
167
+ return;
168
+ const code = Object.getOwnPropertyDescriptor(error, "code");
169
+ return code && "value" in code && code.value === "ConnectionRefused" ? true : undefined;
170
+ }
117
171
  function parseApiErrorBody(body) {
118
172
  if (!isRecord(body) || !isRecord(body.error))
119
173
  return null;
@@ -127,6 +181,329 @@ function parseApiErrorBody(body) {
127
181
  hint: typeof error.hint === "string" ? error.hint : undefined
128
182
  };
129
183
  }
184
+ function createRetryAwareFetch(transportFetch, unix) {
185
+ const runtimeFetch = transportFetch;
186
+ let attempt = 0;
187
+ return (input, init) => {
188
+ attempt += 1;
189
+ if (unix === undefined && attempt === 1) {
190
+ return runtimeFetch(input, init);
191
+ }
192
+ if (!(input instanceof Request)) {
193
+ if (unix === undefined)
194
+ return runtimeFetch(input, init);
195
+ const unixInit = { ...init, unix };
196
+ return runtimeFetch(input, unixInit);
197
+ }
198
+ const streamedBody = input.body ? { body: input.body, duplex: "half" } : {};
199
+ const materialized = {
200
+ ...init,
201
+ ...unix !== undefined && { unix },
202
+ method: input.method,
203
+ headers: input.headers,
204
+ ...streamedBody,
205
+ cache: input.cache,
206
+ credentials: input.credentials,
207
+ integrity: input.integrity,
208
+ keepalive: input.keepalive,
209
+ mode: input.mode,
210
+ redirect: input.redirect,
211
+ referrer: input.referrer,
212
+ referrerPolicy: input.referrerPolicy,
213
+ signal: input.signal
214
+ };
215
+ return runtimeFetch(input.url, materialized);
216
+ };
217
+ }
218
+ function createHttpClient(config) {
219
+ if (config.fetch && config.unix) {
220
+ throw new TypeError("HttpClientConfig.fetch and unix are mutually exclusive");
221
+ }
222
+ if (config.unix !== undefined) {
223
+ if (!config.unix.startsWith("/") || config.unix.includes("\x00")) {
224
+ throw new TypeError("HttpClientConfig.unix must be an absolute Unix socket path");
225
+ }
226
+ if (typeof Reflect.get(globalThis, "Bun") !== "object") {
227
+ throw new TypeError("HttpClientConfig.unix requires Bun; on Bun or Node use createUnixClientTransport().fetch for an explicit portable transport");
228
+ }
229
+ }
230
+ let ssrCookies = null;
231
+ let isLoggedOut = false;
232
+ const listeners = new Set;
233
+ const suppressUnauthorizedFor = config.suppressUnauthorizedFor ?? [];
234
+ const parseError = config.parseError ?? parseApiErrorBody;
235
+ function emit(event) {
236
+ for (const fn of listeners) {
237
+ try {
238
+ fn(event);
239
+ } catch {}
240
+ }
241
+ }
242
+ const client = ky.create({
243
+ prefix: config.baseUrl,
244
+ credentials: config.credentials ?? "include",
245
+ timeout: false,
246
+ retry: {
247
+ limit: config.retry?.limit ?? 2,
248
+ methods: config.retry?.methods ?? ["get"],
249
+ statusCodes: config.retry?.statusCodes ?? [],
250
+ shouldRetry: ({ error }) => shouldRetryBunNetworkError(error)
251
+ },
252
+ hooks: {
253
+ beforeRequest: [
254
+ ({ request: request2 }) => {
255
+ if (ssrCookies) {
256
+ request2.headers.set("Cookie", ssrCookies);
257
+ }
258
+ const extra = typeof config.headers === "function" ? config.headers() : config.headers;
259
+ if (extra) {
260
+ for (const [key, value] of Object.entries(extra)) {
261
+ request2.headers.set(key, value);
262
+ }
263
+ }
264
+ if (config.trace && !request2.headers.has("traceparent")) {
265
+ request2.headers.set("traceparent", formatTraceparent(createTraceContext()));
266
+ }
267
+ }
268
+ ],
269
+ afterResponse: [
270
+ async ({ request: request2, response }) => {
271
+ if (response.status === 401) {
272
+ const url = new URL(request2.url).pathname;
273
+ if (!isLoggedOut && !suppressUnauthorizedFor.some((matches) => matches(url))) {
274
+ isLoggedOut = true;
275
+ emit({ type: "unauthorized" });
276
+ }
277
+ }
278
+ if (!response.ok) {
279
+ const body = await response.clone().json().catch(() => null);
280
+ if (body) {
281
+ const parsed = parseError(body);
282
+ if (parsed) {
283
+ throw new ApiError(parsed.code, response.status, parsed.details, parsed.message, parsed.hint, responseTraceId(response));
284
+ }
285
+ }
286
+ }
287
+ }
288
+ ]
289
+ }
290
+ });
291
+ async function request(method, url, data, options = {}) {
292
+ const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
293
+ const kyOptions = {
294
+ fetch: createRetryAwareFetch(config.fetch ?? globalThis.fetch.bind(globalThis), config.unix),
295
+ timeout: false,
296
+ signal: cancellation.signal
297
+ };
298
+ if (options.params) {
299
+ const searchParams = new URLSearchParams;
300
+ for (const [key, value] of Object.entries(options.params)) {
301
+ if (value === undefined)
302
+ continue;
303
+ if (Array.isArray(value)) {
304
+ for (const item of value)
305
+ searchParams.append(key, String(item));
306
+ } else {
307
+ searchParams.set(key, String(value));
308
+ }
309
+ }
310
+ if (searchParams.size > 0) {
311
+ kyOptions.searchParams = searchParams;
312
+ }
313
+ }
314
+ if (data instanceof FormData) {
315
+ kyOptions.body = data;
316
+ } else if (data !== undefined) {
317
+ kyOptions.json = data;
318
+ }
319
+ try {
320
+ return await cancellation.run(async () => {
321
+ if (options.responseType === "blob") {
322
+ return transportResult(await client[method](url, kyOptions).blob());
323
+ }
324
+ if (options.responseType === "response") {
325
+ return transportResult(await client[method](url, kyOptions));
326
+ }
327
+ const response = await client[method](url, kyOptions);
328
+ if (options.responseType === "void") {
329
+ const text = await response.text();
330
+ if (text.length > 0) {
331
+ throw new Error("Server returned data for an endpoint with no output contract");
332
+ }
333
+ }
334
+ if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
335
+ return transportResult(undefined);
336
+ }
337
+ return await response.json();
338
+ });
339
+ } catch (error) {
340
+ if (error instanceof RequestCancellationError) {
341
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
342
+ }
343
+ if (ApiError.is(error))
344
+ throw error;
345
+ emit({ type: "network_error" });
346
+ const response = isHTTPError(error) ? error.response : undefined;
347
+ const status = response?.status ?? 0;
348
+ const msg = error instanceof Error ? error.message : undefined;
349
+ throw new ApiError("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, msg, undefined, responseTraceId(response), { cause: error });
350
+ }
351
+ }
352
+ return {
353
+ baseUrl: config.baseUrl,
354
+ get: (url, options) => request("get", url, undefined, options),
355
+ head: (url, options) => request("head", url, undefined, options),
356
+ post: (url, data, options) => request("post", url, data, options),
357
+ put: (url, data, options) => request("put", url, data, options),
358
+ patch: (url, data, options) => request("patch", url, data, options),
359
+ delete: (url, options) => request("delete", url, undefined, options),
360
+ setServerContext(cookies) {
361
+ ssrCookies = cookies;
362
+ },
363
+ subscribe(listener) {
364
+ listeners.add(listener);
365
+ return () => listeners.delete(listener);
366
+ },
367
+ logout() {
368
+ isLoggedOut = true;
369
+ emit({ type: "logout" });
370
+ },
371
+ resetLogoutState() {
372
+ isLoggedOut = false;
373
+ }
374
+ };
375
+ }
376
+
377
+ // src/internal/errors.ts
378
+ import { z } from "zod";
379
+ function issuePath(path) {
380
+ return path.length > 0 ? path.map(String).join(".") : "(root)";
381
+ }
382
+ function formatZodError(error) {
383
+ const issues = error.issues.slice(0, 5);
384
+ const lines = issues.map((issue) => `${issuePath(issue.path)}: ${issue.message}`);
385
+ const suffix = error.issues.length > 5 ? `
386
+ ...and ${error.issues.length - 5} more issues` : "";
387
+ return lines.join(`
388
+ `) + suffix;
389
+ }
390
+ function zodIssues(error) {
391
+ return error.issues.map((issue) => ({
392
+ path: issuePath(issue.path),
393
+ code: issue.code,
394
+ message: issue.message
395
+ }));
396
+ }
397
+
398
+ // src/internal/bounded-lines.ts
399
+ var DEFAULT_STREAM_LINE_BYTES = 1024 * 1024;
400
+ function lineLimit(value) {
401
+ const resolved = value ?? DEFAULT_STREAM_LINE_BYTES;
402
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
403
+ throw new TypeError("maxLineBytes must be a positive safe integer");
404
+ }
405
+ return resolved;
406
+ }
407
+ function joinBytes(left, right) {
408
+ if (left.byteLength === 0)
409
+ return right.slice();
410
+ const result = new Uint8Array(left.byteLength + right.byteLength);
411
+ result.set(left);
412
+ result.set(right, left.byteLength);
413
+ return result;
414
+ }
415
+ async function* readBoundedUtf8Lines(response, maxLineBytes, requireFinalNewline = false) {
416
+ const reader = response.body?.getReader();
417
+ if (!reader)
418
+ return;
419
+ const limit = lineLimit(maxLineBytes);
420
+ const decoder = new TextDecoder("utf-8", { fatal: true });
421
+ let pending = new Uint8Array;
422
+ try {
423
+ for (;; ) {
424
+ const chunk = await reader.read();
425
+ if (chunk.done)
426
+ break;
427
+ let start = 0;
428
+ for (let index = 0;index < chunk.value.byteLength; index += 1) {
429
+ if (chunk.value[index] !== 10)
430
+ continue;
431
+ const lineBytes = joinBytes(pending, chunk.value.slice(start, index));
432
+ if (lineBytes.byteLength > limit) {
433
+ throw new RangeError(`Stream line exceeds the ${limit} byte limit`);
434
+ }
435
+ yield decoder.decode(lineBytes);
436
+ pending = new Uint8Array;
437
+ start = index + 1;
438
+ }
439
+ pending = joinBytes(pending, chunk.value.slice(start));
440
+ if (pending.byteLength > limit) {
441
+ throw new RangeError(`Stream line exceeds the ${limit} byte limit`);
442
+ }
443
+ }
444
+ if (pending.byteLength > 0) {
445
+ if (requireFinalNewline) {
446
+ throw new SyntaxError("Stream ended with an unterminated final line");
447
+ }
448
+ yield decoder.decode(pending);
449
+ }
450
+ } finally {
451
+ await reader.cancel().catch(() => {
452
+ return;
453
+ });
454
+ reader.releaseLock();
455
+ }
456
+ }
457
+
458
+ // src/server/stream.ts
459
+ async function* parseSSE(response, options) {
460
+ try {
461
+ for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES)) {
462
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
463
+ if (!line.startsWith("data:"))
464
+ continue;
465
+ const data = line.slice(5).replace(/^ /, "");
466
+ if (data === "[DONE]")
467
+ return;
468
+ try {
469
+ yield JSON.parse(data);
470
+ } catch (error) {
471
+ const failure = error instanceof Error ? error : new Error(String(error));
472
+ if (!options?.onParseError)
473
+ throw failure;
474
+ options.onParseError(data, failure);
475
+ }
476
+ }
477
+ } catch (error) {
478
+ const failure = error instanceof Error ? error : new Error(String(error));
479
+ if (!options?.onParseError)
480
+ throw failure;
481
+ options.onParseError("", failure);
482
+ }
483
+ }
484
+ // src/browser/stream.ts
485
+ function parseFailure(raw, error, onParseError) {
486
+ const failure = error instanceof Error ? error : new Error(String(error));
487
+ if (!onParseError)
488
+ throw failure;
489
+ onParseError(raw, failure);
490
+ }
491
+ async function* parseNDJSON(response, options) {
492
+ try {
493
+ for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES, options?.finalLine === "require-newline")) {
494
+ const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
495
+ if (line === "")
496
+ continue;
497
+ try {
498
+ yield JSON.parse(line);
499
+ } catch (error) {
500
+ parseFailure(line, error, options?.onParseError);
501
+ }
502
+ }
503
+ } catch (error) {
504
+ parseFailure("", error, options?.onParseError);
505
+ }
506
+ }
130
507
 
131
508
  // src/browser/client-multipart.ts
132
509
  function isFileDescriptor(value) {
@@ -178,6 +555,11 @@ function buildMultipartForm(descriptor, values) {
178
555
  return formData;
179
556
  }
180
557
 
558
+ // src/internal/http-input.ts
559
+ function inputIsQuery(method) {
560
+ return method === "GET" || method === "DELETE";
561
+ }
562
+
181
563
  // src/browser/client-url.ts
182
564
  function isParamArray(value) {
183
565
  return Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number");
@@ -217,6 +599,52 @@ function resolvePathPrefix(config, args) {
217
599
  }
218
600
  return config.pathPrefix(args);
219
601
  }
602
+ function escapeRegex(value) {
603
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
604
+ }
605
+ function decodePathSegment(value) {
606
+ try {
607
+ return decodeURIComponent(value);
608
+ } catch {
609
+ return value;
610
+ }
611
+ }
612
+ function createClientRouteMatcher(endpoint, contractPrefix, config) {
613
+ if (typeof config?.pathPrefix === "function" && (!config.stripPrefixKeys || config.stripPrefixKeys.length === 0)) {
614
+ throw new Error("Dynamic pathPrefix matchers require stripPrefixKeys");
615
+ }
616
+ const markerByKey = {};
617
+ for (const [index, key] of (config?.stripPrefixKeys ?? []).entries()) {
618
+ markerByKey[key] = `__stitch_scope_${index}__`;
619
+ }
620
+ const pathPrefix = resolvePathPrefix(config, markerByKey);
621
+ const route = [pathPrefix, contractPrefix, endpoint.path === "/" ? "" : endpoint.path].filter(Boolean).join("/");
622
+ const patternSegments = route.split("/").filter(Boolean);
623
+ const wildcard = patternSegments.at(-1)?.startsWith("*") === true;
624
+ const fixedCount = wildcard ? patternSegments.length - 1 : patternSegments.length;
625
+ const markers = Object.values(markerByKey);
626
+ return (pathname) => {
627
+ const actualSegments = pathname.split("/").filter(Boolean).map(decodePathSegment);
628
+ if (wildcard ? actualSegments.length < fixedCount : actualSegments.length !== fixedCount) {
629
+ return false;
630
+ }
631
+ for (let index = 0;index < fixedCount; index += 1) {
632
+ const pattern = patternSegments[index];
633
+ const actual = actualSegments[index];
634
+ if (!pattern || actual === undefined)
635
+ return false;
636
+ if (pattern.startsWith(":"))
637
+ continue;
638
+ let source = escapeRegex(pattern);
639
+ for (const marker of markers) {
640
+ source = source.replaceAll(escapeRegex(marker), "[^/]+");
641
+ }
642
+ if (!new RegExp(`^${source}$`).test(actual))
643
+ return false;
644
+ }
645
+ return true;
646
+ };
647
+ }
220
648
  function extractParamNames(path) {
221
649
  const matches = path.match(/:(\w+)/g);
222
650
  const names = matches ? matches.map((match) => match.slice(1)) : [];
@@ -290,29 +718,6 @@ function joinClientBaseUrl(baseUrl, relativeUrl) {
290
718
  const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
291
719
  return `${base}${path}`;
292
720
  }
293
- // src/browser/stream.ts
294
- function parseFailure(raw, error, onParseError) {
295
- const failure = error instanceof Error ? error : new Error(String(error));
296
- if (!onParseError)
297
- throw failure;
298
- onParseError(raw, failure);
299
- }
300
- async function* parseNDJSON(response, options) {
301
- try {
302
- for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES, options?.finalLine === "require-newline")) {
303
- const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
304
- if (line === "")
305
- continue;
306
- try {
307
- yield JSON.parse(line);
308
- } catch (error) {
309
- parseFailure(line, error, options?.onParseError);
310
- }
311
- }
312
- } catch (error) {
313
- parseFailure("", error, options?.onParseError);
314
- }
315
- }
316
721
 
317
722
  // src/browser/contract-stream.ts
318
723
  function sourceFor(response, descriptor) {
@@ -448,6 +853,25 @@ function withOutput(endpoint, result, abortStream) {
448
853
  return schema.parse(value);
449
854
  });
450
855
  }
856
+ function contractEndpointMatchers(contract, endpointNames, contractConfig) {
857
+ const selected = endpointNames ? new Set(endpointNames) : null;
858
+ const matchers = [];
859
+ for (const [key, endpoint] of typedEntries(contract.endpoints)) {
860
+ if (selected && !selected.has(key))
861
+ continue;
862
+ if (endpoint.expose && !endpoint.expose.includes("HTTP")) {
863
+ if (selected) {
864
+ throw new Error(`Cannot create an HTTP route matcher for non-HTTP endpoint: ${String(key)}`);
865
+ }
866
+ continue;
867
+ }
868
+ matchers.push(createClientRouteMatcher(endpoint, contract.meta.prefix, contractConfig));
869
+ }
870
+ if (selected && matchers.length !== selected.size) {
871
+ throw new Error("Cannot create an HTTP route matcher for an unknown endpoint");
872
+ }
873
+ return matchers;
874
+ }
451
875
  function createClient(contract, configOrClient, contractConfig) {
452
876
  const client = {};
453
877
  const makeExecutor = isHttpAdapter(configOrClient) ? (endpoint) => createHttpExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig);
@@ -461,6 +885,34 @@ function createClient(contract, configOrClient, contractConfig) {
461
885
  function createClients(contracts, configOrClient, contractConfig) {
462
886
  return mapObject(contracts, (_key, contract) => createClient(contract, configOrClient, contractConfig));
463
887
  }
888
+ function buildScopedRegistry(contracts, scopeConfigs, surfaceName, build) {
889
+ const registry = {};
890
+ for (const [namespace, value] of Object.entries(contracts)) {
891
+ const list = Array.isArray(value) ? value : [value];
892
+ const surface = {};
893
+ for (const contract of list) {
894
+ const scope = contract.meta.scope;
895
+ if (!scope) {
896
+ throw new Error(`Contract in ${surfaceName} namespace "${namespace}" has no scope`);
897
+ }
898
+ const scopeConfig = scopeConfigs[scope];
899
+ if (!scopeConfig) {
900
+ throw new Error(`Missing ${surfaceName} config for scope: ${scope}`);
901
+ }
902
+ for (const [methodName, method] of Object.entries(build(contract, scopeConfig))) {
903
+ if (Object.hasOwn(surface, methodName)) {
904
+ throw new Error(`${surfaceName[0]?.toUpperCase()}${surfaceName.slice(1)} namespace "${namespace}" has duplicate method: ${methodName}`);
905
+ }
906
+ surface[methodName] = method;
907
+ }
908
+ }
909
+ registry[namespace] = surface;
910
+ }
911
+ return registry;
912
+ }
913
+ function createScopedClients(contracts, configOrClient, scopeConfigs) {
914
+ return buildScopedRegistry(contracts, scopeConfigs, "client", (contract, config) => createClient(contract, configOrClient, config));
915
+ }
464
916
  function isHttpAdapter(value) {
465
917
  return typeof value === "object" && "get" in value && typeof value.get === "function";
466
918
  }
@@ -633,5 +1085,27 @@ async function throwForErrorResponse(res, config, fallbackBody) {
633
1085
  }
634
1086
  throw new ApiError("HTTP_ERROR", res.status, { body }, undefined, undefined, responseTraceId(res));
635
1087
  }
1088
+ function createUrlBuilder(contract, source, contractConfig) {
1089
+ const builder = {};
1090
+ for (const [key, endpoint] of typedEntries(contract.endpoints)) {
1091
+ if (endpoint.expose && !endpoint.expose.includes("HTTP"))
1092
+ continue;
1093
+ setClientMethod(builder, key, (args) => {
1094
+ const plan = planClientRequest(endpoint, contract.meta.prefix, args ?? {}, contractConfig);
1095
+ if (endpoint.method !== "GET" && endpoint.method !== "DELETE" && Object.keys(plan.remainingArgs).length > 0) {
1096
+ const fields = Object.keys(plan.remainingArgs).join(", ");
1097
+ throw new Error(`URL builder for ${endpoint.method} ${endpoint.path} received non-URL fields: ${fields}`);
1098
+ }
1099
+ return joinClientBaseUrl(source.baseUrl, plan.relativeUrl);
1100
+ });
1101
+ }
1102
+ return builder;
1103
+ }
1104
+ function createUrlBuilders(contracts, source, contractConfig) {
1105
+ return mapObject(contracts, (_key, contract) => createUrlBuilder(contract, source, contractConfig));
1106
+ }
1107
+ function createScopedUrlBuilders(contracts, source, scopeConfigs) {
1108
+ return buildScopedRegistry(contracts, scopeConfigs, "URL builder", (contract, config) => createUrlBuilder(contract, source, config));
1109
+ }
636
1110
 
637
- export { ApiError, createClient, createClients };
1111
+ export { randomHex, createTraceContext, parseTraceparent, formatTraceparent, childSpan, ApiError, createHttpClient, formatZodError, zodIssues, parseSSE, parseNDJSON, contractEndpointMatchers, createClient, createClients, createScopedClients, createUrlBuilder, createUrlBuilders, createScopedUrlBuilders };
@@ -96,5 +96,4 @@ var ApplicationShutdownResultSchema = z.object({
96
96
  resources: z.array(ApplicationResourceShutdownSchema).readonly(),
97
97
  durationMs: z.number().nonnegative()
98
98
  }).readonly();
99
-
100
99
  export { ApplicationIdSchema, ApplicationLifecycleSchema, ApplicationHealthSchema, ManagedResourceStateSchema, ApplicationAdmissionSnapshotSchema, ManagedResourceSnapshotSchema, ApplicationSnapshotSchema, ApplicationStatusProjectionSchema, projectApplicationStatus, ApplicationResourceShutdownSchema, ApplicationShutdownResultSchema };