stitchkit 0.77.0 → 0.79.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/dist/agent-runtime-browser.js +53 -581
- package/dist/agent-runtime-coding-tools.js +6 -5
- package/dist/agent-runtime-harness.js +20 -93
- package/dist/agent-runtime-openrouter.js +1 -1
- package/dist/agent-runtime.js +65 -61
- package/dist/application/decisions.d.ts +28 -25
- package/dist/application/decisions.d.ts.map +1 -1
- package/dist/application/kernel.d.ts +2 -0
- package/dist/application/kernel.d.ts.map +1 -1
- package/dist/application/keyspace.d.ts +11 -1
- package/dist/application/keyspace.d.ts.map +1 -1
- package/dist/application/schedule.d.ts.map +1 -1
- package/dist/application/schemas.d.ts +2 -0
- package/dist/application/schemas.d.ts.map +1 -1
- package/dist/application/server-resource.d.ts.map +1 -1
- package/dist/application/watch-hub.d.ts.map +1 -1
- package/dist/application-diagnostic-journal.d.ts +21 -0
- package/dist/application-diagnostic-journal.d.ts.map +1 -0
- package/dist/application-diagnostic-journal.js +632 -0
- package/dist/application-opentelemetry.js +3 -2
- package/dist/application-schemas.js +14 -98
- package/dist/application.d.ts +1 -2
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +155 -882
- package/dist/cli.js +13 -11
- package/dist/contract/index.js +15 -10
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files.js +1 -1
- package/dist/{index-7b188kmz.js → index-2qzskm8h.js} +15 -1
- package/dist/{index-k2zczx1g.js → index-2rwwmr7v.js} +3 -3
- package/dist/{index-8eywc9zv.js → index-3gye9wzb.js} +6 -3
- package/dist/index-4fpa9dy2.js +51 -0
- package/dist/{index-7qy2ex0m.js → index-5mtc969c.js} +2 -2
- package/dist/index-5s3zajp8.js +192 -0
- package/dist/{index-3cwck0rm.js → index-6s7n2v50.js} +60 -374
- package/dist/{index-s4c8wy8m.js → index-758zx2ha.js} +11 -90
- package/dist/{index-qyrqwr4c.js → index-77fekveh.js} +4 -1
- package/dist/{index-58jzmnn4.js → index-7ag81v2f.js} +355 -34
- package/dist/index-7gnkny4z.js +326 -0
- package/dist/index-7zbps32p.js +89 -0
- package/dist/index-85vfqd7m.js +135 -0
- package/dist/{index-m668wzyc.js → index-9s6z8vgf.js} +17 -141
- package/dist/{index-bfcpjw20.js → index-actkayzn.js} +7 -3
- package/dist/index-b9aefrqd.js +20 -0
- package/dist/index-dre2ywck.js +48 -0
- package/dist/{index-nemjkxjp.js → index-dwf1f2ap.js} +16 -12
- package/dist/{index-wwst0td5.js → index-dx9xehwt.js} +1 -1
- package/dist/{index-ezmn6ac6.js → index-dxesg10d.js} +4 -4
- package/dist/{index-aczggrty.js → index-dxjjv399.js} +2 -2
- package/dist/{index-jqtsc9mj.js → index-egaabxkz.js} +3 -3
- package/dist/{index-vc1b0b1b.js → index-evcnmsc3.js} +49 -8
- package/dist/{index-nt1mp8km.js → index-fxz0v7vg.js} +84 -84
- package/dist/index-h4pj6fta.js +87 -0
- package/dist/{index-1rxswfbv.js → index-hb5pf3s4.js} +1 -1
- package/dist/{index-t8qyvrvg.js → index-k9ay6v6d.js} +21 -272
- package/dist/index-kzfs85xp.js +9 -0
- package/dist/{index-vbf2p6me.js → index-m9kzax52.js} +91 -89
- package/dist/{index-22by16v6.js → index-mgtxfe6b.js} +1 -1
- package/dist/index-p7hgdyhb.js +22 -0
- package/dist/index-pq95qkbf.js +0 -0
- package/dist/index-t0z5p5w6.js +318 -0
- package/dist/{index-s2rchahr.js → index-vwp69871.js} +13 -11
- package/dist/index-wc4x00bn.js +103 -0
- package/dist/{index-da1aqnhb.js → index-xcetq6j3.js} +8 -6
- package/dist/{index-f6n5n7nz.js → index-y01f4223.js} +33 -126
- package/dist/{index-35z5h2ty.js → index-ywd0y6m1.js} +18 -218
- package/dist/index.js +65 -1588
- package/dist/internal/deadline.d.ts +17 -0
- package/dist/internal/deadline.d.ts.map +1 -0
- package/dist/live.js +15 -89
- package/dist/node.js +17 -15
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/index.js +7 -6
- package/dist/primitives.js +5 -2
- package/dist/react.js +1 -1
- package/dist/remote.js +7 -6
- package/dist/server/event-bus.d.ts.map +1 -1
- package/dist/server/index.js +26 -38
- package/dist/telegram.js +1 -1
- package/dist/testing.js +86 -97
- package/dist/tool-invoker.js +11 -9
- package/dist/tools/async-operation-contract.d.ts +61 -0
- package/dist/tools/async-operation-contract.d.ts.map +1 -0
- package/dist/tools/async-operation.d.ts +2 -47
- package/dist/tools/async-operation.d.ts.map +1 -1
- package/dist/tools/view-file-contract.d.ts +98 -0
- package/dist/tools/view-file-contract.d.ts.map +1 -0
- package/dist/tools/view-file.d.ts +4 -83
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools-contract.d.ts +29 -0
- package/dist/tools-contract.d.ts.map +1 -0
- package/dist/tools-contract.js +24 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +59 -95
- package/llms-full.txt +146 -14
- package/package.json +14 -7
- package/dist/index-ksp6e2ye.js +0 -24
- package/dist/index-vj3vvpaa.js +0 -307
|
@@ -3,17 +3,29 @@ import {
|
|
|
3
3
|
inputIsQuery,
|
|
4
4
|
parseSSE,
|
|
5
5
|
readBoundedUtf8Lines
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-hb5pf3s4.js";
|
|
7
|
+
import {
|
|
8
|
+
createTraceContext,
|
|
9
|
+
formatTraceparent
|
|
10
|
+
} from "./index-fxz0v7vg.js";
|
|
7
11
|
import {
|
|
8
12
|
ContractStreamFrameSchema,
|
|
9
13
|
DEFAULT_CONTRACT_STREAM_FRAME_BYTES,
|
|
10
14
|
parseTrailingWildcard
|
|
11
|
-
} from "./index-
|
|
15
|
+
} from "./index-y01f4223.js";
|
|
12
16
|
import {
|
|
13
17
|
isRecord,
|
|
14
18
|
mapObject,
|
|
19
|
+
transportResult,
|
|
15
20
|
typedEntries
|
|
16
|
-
} from "./index-
|
|
21
|
+
} from "./index-77fekveh.js";
|
|
22
|
+
|
|
23
|
+
// src/browser/http.ts
|
|
24
|
+
import ky, {
|
|
25
|
+
isHTTPError,
|
|
26
|
+
isNetworkError,
|
|
27
|
+
isTimeoutError
|
|
28
|
+
} from "ky";
|
|
17
29
|
|
|
18
30
|
// src/browser/cancellation.ts
|
|
19
31
|
class RequestCancellationError extends Error {
|
|
@@ -72,13 +84,6 @@ function cancellationError(cause) {
|
|
|
72
84
|
return new RequestCancellationError(cause);
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
// src/browser/http.ts
|
|
76
|
-
import ky, {
|
|
77
|
-
isHTTPError,
|
|
78
|
-
isNetworkError,
|
|
79
|
-
isTimeoutError
|
|
80
|
-
} from "ky";
|
|
81
|
-
|
|
82
87
|
// src/browser/request-id.ts
|
|
83
88
|
var REQUEST_ID_HEADER = "x-request-id";
|
|
84
89
|
function responseTraceId(response) {
|
|
@@ -114,6 +119,15 @@ class ApiError extends Error {
|
|
|
114
119
|
function refuseLocally(path, message) {
|
|
115
120
|
return new ApiError("VALIDATION_ERROR", 0, { issues: [{ path, code: "invalid_type", message }] }, message);
|
|
116
121
|
}
|
|
122
|
+
function shouldRetryBunNetworkError(error) {
|
|
123
|
+
if (ApiError.is(error) || isHTTPError(error) || isNetworkError(error) || isTimeoutError(error) || error instanceof RequestCancellationError || error instanceof DOMException && (error.name === "AbortError" || error.name === "TimeoutError")) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!(error instanceof Error))
|
|
127
|
+
return;
|
|
128
|
+
const code = Object.getOwnPropertyDescriptor(error, "code");
|
|
129
|
+
return code && "value" in code && code.value === "ConnectionRefused" ? true : undefined;
|
|
130
|
+
}
|
|
117
131
|
function parseApiErrorBody(body) {
|
|
118
132
|
if (!isRecord(body) || !isRecord(body.error))
|
|
119
133
|
return null;
|
|
@@ -127,6 +141,221 @@ function parseApiErrorBody(body) {
|
|
|
127
141
|
hint: typeof error.hint === "string" ? error.hint : undefined
|
|
128
142
|
};
|
|
129
143
|
}
|
|
144
|
+
function createRetryAwareFetch(transportFetch, unix) {
|
|
145
|
+
const runtimeFetch = transportFetch;
|
|
146
|
+
let attempt = 0;
|
|
147
|
+
return (input, init) => {
|
|
148
|
+
attempt += 1;
|
|
149
|
+
if (unix === undefined && attempt === 1) {
|
|
150
|
+
return runtimeFetch(input, init);
|
|
151
|
+
}
|
|
152
|
+
if (!(input instanceof Request)) {
|
|
153
|
+
if (unix === undefined)
|
|
154
|
+
return runtimeFetch(input, init);
|
|
155
|
+
const unixInit = { ...init, unix };
|
|
156
|
+
return runtimeFetch(input, unixInit);
|
|
157
|
+
}
|
|
158
|
+
const streamedBody = input.body ? { body: input.body, duplex: "half" } : {};
|
|
159
|
+
const materialized = {
|
|
160
|
+
...init,
|
|
161
|
+
...unix !== undefined && { unix },
|
|
162
|
+
method: input.method,
|
|
163
|
+
headers: input.headers,
|
|
164
|
+
...streamedBody,
|
|
165
|
+
cache: input.cache,
|
|
166
|
+
credentials: input.credentials,
|
|
167
|
+
integrity: input.integrity,
|
|
168
|
+
keepalive: input.keepalive,
|
|
169
|
+
mode: input.mode,
|
|
170
|
+
redirect: input.redirect,
|
|
171
|
+
referrer: input.referrer,
|
|
172
|
+
referrerPolicy: input.referrerPolicy,
|
|
173
|
+
signal: input.signal
|
|
174
|
+
};
|
|
175
|
+
return runtimeFetch(input.url, materialized);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function createHttpClient(config) {
|
|
179
|
+
if (config.fetch && config.unix) {
|
|
180
|
+
throw new TypeError("HttpClientConfig.fetch and unix are mutually exclusive");
|
|
181
|
+
}
|
|
182
|
+
if (config.unix !== undefined) {
|
|
183
|
+
if (!config.unix.startsWith("/") || config.unix.includes("\x00")) {
|
|
184
|
+
throw new TypeError("HttpClientConfig.unix must be an absolute Unix socket path");
|
|
185
|
+
}
|
|
186
|
+
if (typeof Reflect.get(globalThis, "Bun") !== "object") {
|
|
187
|
+
throw new TypeError("HttpClientConfig.unix requires Bun; on Bun or Node use createUnixClientTransport().fetch for an explicit portable transport");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let ssrCookies = null;
|
|
191
|
+
let isLoggedOut = false;
|
|
192
|
+
const listeners = new Set;
|
|
193
|
+
const suppressUnauthorizedFor = config.suppressUnauthorizedFor ?? [];
|
|
194
|
+
const parseError = config.parseError ?? parseApiErrorBody;
|
|
195
|
+
function emit(event) {
|
|
196
|
+
for (const fn of listeners) {
|
|
197
|
+
try {
|
|
198
|
+
fn(event);
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const client = ky.create({
|
|
203
|
+
prefix: config.baseUrl,
|
|
204
|
+
credentials: config.credentials ?? "include",
|
|
205
|
+
timeout: false,
|
|
206
|
+
retry: {
|
|
207
|
+
limit: config.retry?.limit ?? 2,
|
|
208
|
+
methods: config.retry?.methods ?? ["get"],
|
|
209
|
+
statusCodes: config.retry?.statusCodes ?? [],
|
|
210
|
+
shouldRetry: ({ error }) => shouldRetryBunNetworkError(error)
|
|
211
|
+
},
|
|
212
|
+
hooks: {
|
|
213
|
+
beforeRequest: [
|
|
214
|
+
({ request: request2 }) => {
|
|
215
|
+
if (ssrCookies) {
|
|
216
|
+
request2.headers.set("Cookie", ssrCookies);
|
|
217
|
+
}
|
|
218
|
+
const extra = typeof config.headers === "function" ? config.headers() : config.headers;
|
|
219
|
+
if (extra) {
|
|
220
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
221
|
+
request2.headers.set(key, value);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (config.trace && !request2.headers.has("traceparent")) {
|
|
225
|
+
request2.headers.set("traceparent", formatTraceparent(createTraceContext()));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
afterResponse: [
|
|
230
|
+
async ({ request: request2, response }) => {
|
|
231
|
+
if (response.status === 401) {
|
|
232
|
+
const url = new URL(request2.url).pathname;
|
|
233
|
+
if (!isLoggedOut && !suppressUnauthorizedFor.some((matches) => matches(url))) {
|
|
234
|
+
isLoggedOut = true;
|
|
235
|
+
emit({ type: "unauthorized" });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
const body = await response.clone().json().catch(() => null);
|
|
240
|
+
if (body) {
|
|
241
|
+
const parsed = parseError(body);
|
|
242
|
+
if (parsed) {
|
|
243
|
+
throw new ApiError(parsed.code, response.status, parsed.details, parsed.message, parsed.hint, responseTraceId(response));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
async function request(method, url, data, options = {}) {
|
|
252
|
+
const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
|
|
253
|
+
const kyOptions = {
|
|
254
|
+
fetch: createRetryAwareFetch(config.fetch ?? globalThis.fetch.bind(globalThis), config.unix),
|
|
255
|
+
timeout: false,
|
|
256
|
+
signal: cancellation.signal
|
|
257
|
+
};
|
|
258
|
+
if (options.params) {
|
|
259
|
+
const searchParams = new URLSearchParams;
|
|
260
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
261
|
+
if (value === undefined)
|
|
262
|
+
continue;
|
|
263
|
+
if (Array.isArray(value)) {
|
|
264
|
+
for (const item of value)
|
|
265
|
+
searchParams.append(key, String(item));
|
|
266
|
+
} else {
|
|
267
|
+
searchParams.set(key, String(value));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (searchParams.size > 0) {
|
|
271
|
+
kyOptions.searchParams = searchParams;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (data instanceof FormData) {
|
|
275
|
+
kyOptions.body = data;
|
|
276
|
+
} else if (data !== undefined) {
|
|
277
|
+
kyOptions.json = data;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
return await cancellation.run(async () => {
|
|
281
|
+
if (options.responseType === "blob") {
|
|
282
|
+
return transportResult(await client[method](url, kyOptions).blob());
|
|
283
|
+
}
|
|
284
|
+
if (options.responseType === "response") {
|
|
285
|
+
return transportResult(await client[method](url, kyOptions));
|
|
286
|
+
}
|
|
287
|
+
const response = await client[method](url, kyOptions);
|
|
288
|
+
if (options.responseType === "void") {
|
|
289
|
+
const text = await response.text();
|
|
290
|
+
if (text.length > 0) {
|
|
291
|
+
throw new Error("Server returned data for an endpoint with no output contract");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (options.responseType === "void" || response.status === 204 || response.headers.get("content-length") === "0") {
|
|
295
|
+
return transportResult(undefined);
|
|
296
|
+
}
|
|
297
|
+
return await response.json();
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (error instanceof RequestCancellationError) {
|
|
301
|
+
throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
|
|
302
|
+
}
|
|
303
|
+
if (ApiError.is(error))
|
|
304
|
+
throw error;
|
|
305
|
+
emit({ type: "network_error" });
|
|
306
|
+
const response = isHTTPError(error) ? error.response : undefined;
|
|
307
|
+
const status = response?.status ?? 0;
|
|
308
|
+
const msg = error instanceof Error ? error.message : undefined;
|
|
309
|
+
throw new ApiError("UNKNOWN_ERROR", status, msg ? { message: msg } : undefined, msg, undefined, responseTraceId(response), { cause: error });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
baseUrl: config.baseUrl,
|
|
314
|
+
get: (url, options) => request("get", url, undefined, options),
|
|
315
|
+
head: (url, options) => request("head", url, undefined, options),
|
|
316
|
+
post: (url, data, options) => request("post", url, data, options),
|
|
317
|
+
put: (url, data, options) => request("put", url, data, options),
|
|
318
|
+
patch: (url, data, options) => request("patch", url, data, options),
|
|
319
|
+
delete: (url, options) => request("delete", url, undefined, options),
|
|
320
|
+
setServerContext(cookies) {
|
|
321
|
+
ssrCookies = cookies;
|
|
322
|
+
},
|
|
323
|
+
subscribe(listener) {
|
|
324
|
+
listeners.add(listener);
|
|
325
|
+
return () => listeners.delete(listener);
|
|
326
|
+
},
|
|
327
|
+
logout() {
|
|
328
|
+
isLoggedOut = true;
|
|
329
|
+
emit({ type: "logout" });
|
|
330
|
+
},
|
|
331
|
+
resetLogoutState() {
|
|
332
|
+
isLoggedOut = false;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// src/browser/stream.ts
|
|
337
|
+
function parseFailure(raw, error, onParseError) {
|
|
338
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
339
|
+
if (!onParseError)
|
|
340
|
+
throw failure;
|
|
341
|
+
onParseError(raw, failure);
|
|
342
|
+
}
|
|
343
|
+
async function* parseNDJSON(response, options) {
|
|
344
|
+
try {
|
|
345
|
+
for await (const rawLine of readBoundedUtf8Lines(response, options?.maxLineBytes ?? DEFAULT_STREAM_LINE_BYTES, options?.finalLine === "require-newline")) {
|
|
346
|
+
const line = (rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine).trim();
|
|
347
|
+
if (line === "")
|
|
348
|
+
continue;
|
|
349
|
+
try {
|
|
350
|
+
yield JSON.parse(line);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
parseFailure(line, error, options?.onParseError);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
parseFailure("", error, options?.onParseError);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
130
359
|
|
|
131
360
|
// src/browser/client-multipart.ts
|
|
132
361
|
function isFileDescriptor(value) {
|
|
@@ -217,6 +446,52 @@ function resolvePathPrefix(config, args) {
|
|
|
217
446
|
}
|
|
218
447
|
return config.pathPrefix(args);
|
|
219
448
|
}
|
|
449
|
+
function escapeRegex(value) {
|
|
450
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
451
|
+
}
|
|
452
|
+
function decodePathSegment(value) {
|
|
453
|
+
try {
|
|
454
|
+
return decodeURIComponent(value);
|
|
455
|
+
} catch {
|
|
456
|
+
return value;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function createClientRouteMatcher(endpoint, contractPrefix, config) {
|
|
460
|
+
if (typeof config?.pathPrefix === "function" && (!config.stripPrefixKeys || config.stripPrefixKeys.length === 0)) {
|
|
461
|
+
throw new Error("Dynamic pathPrefix matchers require stripPrefixKeys");
|
|
462
|
+
}
|
|
463
|
+
const markerByKey = {};
|
|
464
|
+
for (const [index, key] of (config?.stripPrefixKeys ?? []).entries()) {
|
|
465
|
+
markerByKey[key] = `__stitch_scope_${index}__`;
|
|
466
|
+
}
|
|
467
|
+
const pathPrefix = resolvePathPrefix(config, markerByKey);
|
|
468
|
+
const route = [pathPrefix, contractPrefix, endpoint.path === "/" ? "" : endpoint.path].filter(Boolean).join("/");
|
|
469
|
+
const patternSegments = route.split("/").filter(Boolean);
|
|
470
|
+
const wildcard = patternSegments.at(-1)?.startsWith("*") === true;
|
|
471
|
+
const fixedCount = wildcard ? patternSegments.length - 1 : patternSegments.length;
|
|
472
|
+
const markers = Object.values(markerByKey);
|
|
473
|
+
return (pathname) => {
|
|
474
|
+
const actualSegments = pathname.split("/").filter(Boolean).map(decodePathSegment);
|
|
475
|
+
if (wildcard ? actualSegments.length < fixedCount : actualSegments.length !== fixedCount) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
for (let index = 0;index < fixedCount; index += 1) {
|
|
479
|
+
const pattern = patternSegments[index];
|
|
480
|
+
const actual = actualSegments[index];
|
|
481
|
+
if (!pattern || actual === undefined)
|
|
482
|
+
return false;
|
|
483
|
+
if (pattern.startsWith(":"))
|
|
484
|
+
continue;
|
|
485
|
+
let source = escapeRegex(pattern);
|
|
486
|
+
for (const marker of markers) {
|
|
487
|
+
source = source.replaceAll(escapeRegex(marker), "[^/]+");
|
|
488
|
+
}
|
|
489
|
+
if (!new RegExp(`^${source}$`).test(actual))
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
};
|
|
494
|
+
}
|
|
220
495
|
function extractParamNames(path) {
|
|
221
496
|
const matches = path.match(/:(\w+)/g);
|
|
222
497
|
const names = matches ? matches.map((match) => match.slice(1)) : [];
|
|
@@ -290,29 +565,6 @@ function joinClientBaseUrl(baseUrl, relativeUrl) {
|
|
|
290
565
|
const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
|
|
291
566
|
return `${base}${path}`;
|
|
292
567
|
}
|
|
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
568
|
|
|
317
569
|
// src/browser/contract-stream.ts
|
|
318
570
|
function sourceFor(response, descriptor) {
|
|
@@ -448,6 +700,25 @@ function withOutput(endpoint, result, abortStream) {
|
|
|
448
700
|
return schema.parse(value);
|
|
449
701
|
});
|
|
450
702
|
}
|
|
703
|
+
function contractEndpointMatchers(contract, endpointNames, contractConfig) {
|
|
704
|
+
const selected = endpointNames ? new Set(endpointNames) : null;
|
|
705
|
+
const matchers = [];
|
|
706
|
+
for (const [key, endpoint] of typedEntries(contract.endpoints)) {
|
|
707
|
+
if (selected && !selected.has(key))
|
|
708
|
+
continue;
|
|
709
|
+
if (endpoint.expose && !endpoint.expose.includes("HTTP")) {
|
|
710
|
+
if (selected) {
|
|
711
|
+
throw new Error(`Cannot create an HTTP route matcher for non-HTTP endpoint: ${String(key)}`);
|
|
712
|
+
}
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
matchers.push(createClientRouteMatcher(endpoint, contract.meta.prefix, contractConfig));
|
|
716
|
+
}
|
|
717
|
+
if (selected && matchers.length !== selected.size) {
|
|
718
|
+
throw new Error("Cannot create an HTTP route matcher for an unknown endpoint");
|
|
719
|
+
}
|
|
720
|
+
return matchers;
|
|
721
|
+
}
|
|
451
722
|
function createClient(contract, configOrClient, contractConfig) {
|
|
452
723
|
const client = {};
|
|
453
724
|
const makeExecutor = isHttpAdapter(configOrClient) ? (endpoint) => createHttpExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig);
|
|
@@ -461,6 +732,34 @@ function createClient(contract, configOrClient, contractConfig) {
|
|
|
461
732
|
function createClients(contracts, configOrClient, contractConfig) {
|
|
462
733
|
return mapObject(contracts, (_key, contract) => createClient(contract, configOrClient, contractConfig));
|
|
463
734
|
}
|
|
735
|
+
function buildScopedRegistry(contracts, scopeConfigs, surfaceName, build) {
|
|
736
|
+
const registry = {};
|
|
737
|
+
for (const [namespace, value] of Object.entries(contracts)) {
|
|
738
|
+
const list = Array.isArray(value) ? value : [value];
|
|
739
|
+
const surface = {};
|
|
740
|
+
for (const contract of list) {
|
|
741
|
+
const scope = contract.meta.scope;
|
|
742
|
+
if (!scope) {
|
|
743
|
+
throw new Error(`Contract in ${surfaceName} namespace "${namespace}" has no scope`);
|
|
744
|
+
}
|
|
745
|
+
const scopeConfig = scopeConfigs[scope];
|
|
746
|
+
if (!scopeConfig) {
|
|
747
|
+
throw new Error(`Missing ${surfaceName} config for scope: ${scope}`);
|
|
748
|
+
}
|
|
749
|
+
for (const [methodName, method] of Object.entries(build(contract, scopeConfig))) {
|
|
750
|
+
if (Object.hasOwn(surface, methodName)) {
|
|
751
|
+
throw new Error(`${surfaceName[0]?.toUpperCase()}${surfaceName.slice(1)} namespace "${namespace}" has duplicate method: ${methodName}`);
|
|
752
|
+
}
|
|
753
|
+
surface[methodName] = method;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
registry[namespace] = surface;
|
|
757
|
+
}
|
|
758
|
+
return registry;
|
|
759
|
+
}
|
|
760
|
+
function createScopedClients(contracts, configOrClient, scopeConfigs) {
|
|
761
|
+
return buildScopedRegistry(contracts, scopeConfigs, "client", (contract, config) => createClient(contract, configOrClient, config));
|
|
762
|
+
}
|
|
464
763
|
function isHttpAdapter(value) {
|
|
465
764
|
return typeof value === "object" && "get" in value && typeof value.get === "function";
|
|
466
765
|
}
|
|
@@ -633,5 +932,27 @@ async function throwForErrorResponse(res, config, fallbackBody) {
|
|
|
633
932
|
}
|
|
634
933
|
throw new ApiError("HTTP_ERROR", res.status, { body }, undefined, undefined, responseTraceId(res));
|
|
635
934
|
}
|
|
935
|
+
function createUrlBuilder(contract, source, contractConfig) {
|
|
936
|
+
const builder = {};
|
|
937
|
+
for (const [key, endpoint] of typedEntries(contract.endpoints)) {
|
|
938
|
+
if (endpoint.expose && !endpoint.expose.includes("HTTP"))
|
|
939
|
+
continue;
|
|
940
|
+
setClientMethod(builder, key, (args) => {
|
|
941
|
+
const plan = planClientRequest(endpoint, contract.meta.prefix, args ?? {}, contractConfig);
|
|
942
|
+
if (endpoint.method !== "GET" && endpoint.method !== "DELETE" && Object.keys(plan.remainingArgs).length > 0) {
|
|
943
|
+
const fields = Object.keys(plan.remainingArgs).join(", ");
|
|
944
|
+
throw new Error(`URL builder for ${endpoint.method} ${endpoint.path} received non-URL fields: ${fields}`);
|
|
945
|
+
}
|
|
946
|
+
return joinClientBaseUrl(source.baseUrl, plan.relativeUrl);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
return builder;
|
|
950
|
+
}
|
|
951
|
+
function createUrlBuilders(contracts, source, contractConfig) {
|
|
952
|
+
return mapObject(contracts, (_key, contract) => createUrlBuilder(contract, source, contractConfig));
|
|
953
|
+
}
|
|
954
|
+
function createScopedUrlBuilders(contracts, source, scopeConfigs) {
|
|
955
|
+
return buildScopedRegistry(contracts, scopeConfigs, "URL builder", (contract, config) => createUrlBuilder(contract, source, config));
|
|
956
|
+
}
|
|
636
957
|
|
|
637
|
-
export { ApiError, createClient, createClients };
|
|
958
|
+
export { ApiError, createHttpClient, parseNDJSON, contractEndpointMatchers, createClient, createClients, createScopedClients, createUrlBuilder, createUrlBuilders, createScopedUrlBuilders };
|