stitchkit 0.37.0 → 0.38.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/browser/client-url.d.ts +11 -0
- package/dist/browser/client-url.d.ts.map +1 -0
- package/dist/browser/client.d.ts +16 -4
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +5 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/contract/define.d.ts +75 -1
- package/dist/contract/define.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 +1 -1
- package/dist/{index-xq45akyd.js → index-bgdd42pt.js} +1 -1
- package/dist/{index-s6yhmg1k.js → index-n5t4gnfz.js} +3 -1
- package/dist/{index-4pvtq6h2.js → index-p3kwf73n.js} +84 -23
- package/dist/{index-g3jrbd0z.js → index-x4wbc8sz.js} +28 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -114
- package/dist/node.js +2 -2
- package/dist/observability/index.js +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.js +19 -5
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/response-metadata.d.ts +9 -0
- package/dist/server/response-metadata.d.ts.map +1 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/types.d.ts +10 -2
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools.js +128 -114
- package/llms-full.txt +165 -10
- package/package.json +1 -1
- /package/dist/{index-e497hxcy.js → index-mzx0an0s.js} +0 -0
package/dist/tools.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
inputIsQuery,
|
|
3
3
|
signJwt,
|
|
4
4
|
verifyPkce
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-bgdd42pt.js";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_CORS_ALLOW_HEADERS
|
|
8
8
|
} from "./index-czmqks7r.js";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
readCapped,
|
|
25
25
|
toolResultFromError,
|
|
26
26
|
writeDownload
|
|
27
|
-
} from "./index-
|
|
27
|
+
} from "./index-n5t4gnfz.js";
|
|
28
28
|
import {
|
|
29
29
|
toJsonSchema
|
|
30
30
|
} from "./index-frfyw9fa.js";
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
isRecord,
|
|
39
39
|
mergeMeta,
|
|
40
40
|
typedEntries
|
|
41
|
-
} from "./index-
|
|
41
|
+
} from "./index-mzx0an0s.js";
|
|
42
42
|
|
|
43
43
|
// src/tools/agent.ts
|
|
44
44
|
import { jsonSchema, tool } from "ai";
|
|
@@ -1235,6 +1235,117 @@ function appendFormFields(formData, values, skipKeys) {
|
|
|
1235
1235
|
}
|
|
1236
1236
|
}
|
|
1237
1237
|
|
|
1238
|
+
// src/browser/client-url.ts
|
|
1239
|
+
function isParamArray(value) {
|
|
1240
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number");
|
|
1241
|
+
}
|
|
1242
|
+
function collectQueryParams(args, endpoint) {
|
|
1243
|
+
const params = {};
|
|
1244
|
+
let hasParams = false;
|
|
1245
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1246
|
+
if (value === undefined || value === null)
|
|
1247
|
+
continue;
|
|
1248
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
|
|
1249
|
+
params[key] = value;
|
|
1250
|
+
hasParams = true;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
|
|
1254
|
+
throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
|
|
1255
|
+
}
|
|
1256
|
+
return hasParams ? params : undefined;
|
|
1257
|
+
}
|
|
1258
|
+
function hasStringKeys(args, keys) {
|
|
1259
|
+
for (const key of keys) {
|
|
1260
|
+
if (typeof args[key] !== "string")
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
function resolvePathPrefix(config, args) {
|
|
1266
|
+
if (!config?.pathPrefix)
|
|
1267
|
+
return "";
|
|
1268
|
+
if (typeof config.pathPrefix === "string")
|
|
1269
|
+
return config.pathPrefix;
|
|
1270
|
+
const keys = config.stripPrefixKeys ?? [];
|
|
1271
|
+
if (!hasStringKeys(args, keys)) {
|
|
1272
|
+
const missing = keys.find((key) => typeof args[key] !== "string");
|
|
1273
|
+
throw new Error(`Missing path prefix key: ${missing}`);
|
|
1274
|
+
}
|
|
1275
|
+
return config.pathPrefix(args);
|
|
1276
|
+
}
|
|
1277
|
+
function extractParamNames(path) {
|
|
1278
|
+
const matches = path.match(/:(\w+)/g);
|
|
1279
|
+
const names = matches ? matches.map((match) => match.slice(1)) : [];
|
|
1280
|
+
if (path.endsWith("/*"))
|
|
1281
|
+
names.push("*");
|
|
1282
|
+
return names;
|
|
1283
|
+
}
|
|
1284
|
+
function fillPathParams(path, args) {
|
|
1285
|
+
let filled = path.replace(/:(\w+)/g, (_, key) => {
|
|
1286
|
+
const value = args[key];
|
|
1287
|
+
if (value === undefined || value === null) {
|
|
1288
|
+
throw new Error(`Missing path param: ${key}`);
|
|
1289
|
+
}
|
|
1290
|
+
return encodeURIComponent(String(value));
|
|
1291
|
+
});
|
|
1292
|
+
if (!filled.endsWith("/*"))
|
|
1293
|
+
return filled;
|
|
1294
|
+
const wildcard = args["*"];
|
|
1295
|
+
if (wildcard === undefined || wildcard === null) {
|
|
1296
|
+
throw new Error("Missing path param: *");
|
|
1297
|
+
}
|
|
1298
|
+
const remainder = String(wildcard).split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
1299
|
+
filled = `${filled.slice(0, -1)}${remainder}`;
|
|
1300
|
+
return filled;
|
|
1301
|
+
}
|
|
1302
|
+
function stripConsumedArgs(args, path, scopeKeys) {
|
|
1303
|
+
const consumed = new Set(scopeKeys);
|
|
1304
|
+
for (const name of extractParamNames(path))
|
|
1305
|
+
consumed.add(name);
|
|
1306
|
+
const remaining = {};
|
|
1307
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1308
|
+
if (!consumed.has(key) && value !== undefined)
|
|
1309
|
+
remaining[key] = value;
|
|
1310
|
+
}
|
|
1311
|
+
return remaining;
|
|
1312
|
+
}
|
|
1313
|
+
function appendQuery(relativeUrl, params) {
|
|
1314
|
+
if (!params)
|
|
1315
|
+
return relativeUrl;
|
|
1316
|
+
const search = new URLSearchParams;
|
|
1317
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1318
|
+
if (Array.isArray(value)) {
|
|
1319
|
+
for (const item of value)
|
|
1320
|
+
search.append(key, String(item));
|
|
1321
|
+
} else {
|
|
1322
|
+
search.set(key, String(value));
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return search.size > 0 ? `${relativeUrl}?${search}` : relativeUrl;
|
|
1326
|
+
}
|
|
1327
|
+
function planClientRequest(endpoint, contractPrefix, args, config) {
|
|
1328
|
+
let pathPrefix = resolvePathPrefix(config, args);
|
|
1329
|
+
if (pathPrefix && !pathPrefix.endsWith("/"))
|
|
1330
|
+
pathPrefix += "/";
|
|
1331
|
+
if (pathPrefix.startsWith("/"))
|
|
1332
|
+
pathPrefix = pathPrefix.slice(1);
|
|
1333
|
+
const endpointPath = endpoint.path === "/" ? "" : endpoint.path;
|
|
1334
|
+
let relativeUrl = fillPathParams(`${pathPrefix}${contractPrefix}${endpointPath}`, args);
|
|
1335
|
+
if (relativeUrl.endsWith("/"))
|
|
1336
|
+
relativeUrl = relativeUrl.slice(0, -1);
|
|
1337
|
+
const remainingArgs = stripConsumedArgs(args, endpoint.path, config?.stripPrefixKeys ?? []);
|
|
1338
|
+
if (inputIsQuery(endpoint.method)) {
|
|
1339
|
+
relativeUrl = appendQuery(relativeUrl, collectQueryParams(remainingArgs, endpoint));
|
|
1340
|
+
}
|
|
1341
|
+
return { relativeUrl, remainingArgs };
|
|
1342
|
+
}
|
|
1343
|
+
function joinClientBaseUrl(baseUrl, relativeUrl) {
|
|
1344
|
+
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
1345
|
+
const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
|
|
1346
|
+
return `${base}${path}`;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1238
1349
|
// src/browser/http.ts
|
|
1239
1350
|
import ky, { isHTTPError } from "ky";
|
|
1240
1351
|
class ApiError extends Error {
|
|
@@ -1285,25 +1396,6 @@ function withOutput(endpoint, result) {
|
|
|
1285
1396
|
return result;
|
|
1286
1397
|
return result.then((value) => value === undefined ? undefined : schema.parse(value));
|
|
1287
1398
|
}
|
|
1288
|
-
function isParamArray(value) {
|
|
1289
|
-
return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
|
|
1290
|
-
}
|
|
1291
|
-
function collectQueryParams(args, skipKeys, endpoint) {
|
|
1292
|
-
const params = {};
|
|
1293
|
-
let hasParams = false;
|
|
1294
|
-
for (const [key, value] of Object.entries(args)) {
|
|
1295
|
-
if (skipKeys.has(key) || value === undefined || value === null)
|
|
1296
|
-
continue;
|
|
1297
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
|
|
1298
|
-
params[key] = value;
|
|
1299
|
-
hasParams = true;
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
|
|
1303
|
-
throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
|
|
1304
|
-
}
|
|
1305
|
-
return hasParams ? params : undefined;
|
|
1306
|
-
}
|
|
1307
1399
|
function createClient(contract, configOrClient, contractConfig) {
|
|
1308
1400
|
const client = {};
|
|
1309
1401
|
const makeMethod = isHttpAdapter(configOrClient) ? (endpoint) => createHttpMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig);
|
|
@@ -1322,27 +1414,9 @@ function setClientMethod(target, key, method) {
|
|
|
1322
1414
|
}
|
|
1323
1415
|
function createHttpMethod(endpoint, prefix, client, config) {
|
|
1324
1416
|
const httpMethod = endpoint.method.toLowerCase();
|
|
1325
|
-
const isGet = httpMethod === "get";
|
|
1326
|
-
const paramNames = extractParamNames(endpoint.path);
|
|
1327
|
-
const prefixKeys = new Set([...config?.stripPrefixKeys ?? [], ...paramNames]);
|
|
1328
1417
|
return (...args) => {
|
|
1329
1418
|
const firstArg = args[0] ?? {};
|
|
1330
|
-
|
|
1331
|
-
if (config?.pathPrefix) {
|
|
1332
|
-
pathPrefixStr = typeof config.pathPrefix === "function" ? config.pathPrefix(firstArg) : config.pathPrefix;
|
|
1333
|
-
if (pathPrefixStr && !pathPrefixStr.endsWith("/"))
|
|
1334
|
-
pathPrefixStr += "/";
|
|
1335
|
-
}
|
|
1336
|
-
let url = `${pathPrefixStr}${prefix}${endpoint.path}`;
|
|
1337
|
-
for (const name of paramNames) {
|
|
1338
|
-
const value = firstArg[name];
|
|
1339
|
-
if (value === undefined || value === null) {
|
|
1340
|
-
throw new Error(`Missing path param: ${name}`);
|
|
1341
|
-
}
|
|
1342
|
-
url = url.replace(`:${name}`, encodeURIComponent(String(value)));
|
|
1343
|
-
}
|
|
1344
|
-
if (url.endsWith("/"))
|
|
1345
|
-
url = url.slice(0, -1);
|
|
1419
|
+
const plan = planClientRequest(endpoint, prefix, firstArg, config);
|
|
1346
1420
|
if (endpoint.multipart) {
|
|
1347
1421
|
const file = firstArg[endpoint.multipart];
|
|
1348
1422
|
if (!isMultipartFile(file)) {
|
|
@@ -1353,59 +1427,28 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
1353
1427
|
}
|
|
1354
1428
|
const formData = new FormData;
|
|
1355
1429
|
appendMultipartFile(formData, endpoint.multipart, file);
|
|
1356
|
-
appendFormFields(formData,
|
|
1357
|
-
return withOutput(endpoint, client[httpMethod](
|
|
1430
|
+
appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
|
|
1431
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
|
|
1358
1432
|
}
|
|
1359
|
-
if (
|
|
1360
|
-
|
|
1361
|
-
return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint)));
|
|
1433
|
+
if (httpMethod === "get") {
|
|
1434
|
+
return withOutput(endpoint, client.get(plan.relativeUrl, withTimeout(undefined, endpoint)));
|
|
1362
1435
|
}
|
|
1363
1436
|
if (httpMethod === "delete") {
|
|
1364
|
-
|
|
1365
|
-
return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint)));
|
|
1366
|
-
}
|
|
1367
|
-
const payload = {};
|
|
1368
|
-
for (const [key, value] of Object.entries(firstArg)) {
|
|
1369
|
-
if (!prefixKeys.has(key) && value !== undefined) {
|
|
1370
|
-
payload[key] = value;
|
|
1371
|
-
}
|
|
1437
|
+
return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
|
|
1372
1438
|
}
|
|
1373
|
-
return withOutput(endpoint, client[httpMethod](
|
|
1439
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
|
|
1374
1440
|
};
|
|
1375
1441
|
}
|
|
1376
1442
|
function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
1377
|
-
const prefixKeys = new Set(contractConfig?.stripPrefixKeys ?? []);
|
|
1378
1443
|
return async (args) => {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
pathPrefixStr = typeof contractConfig.pathPrefix === "function" ? contractConfig.pathPrefix(args ?? {}) : contractConfig.pathPrefix;
|
|
1382
|
-
if (pathPrefixStr && !pathPrefixStr.endsWith("/"))
|
|
1383
|
-
pathPrefixStr += "/";
|
|
1384
|
-
}
|
|
1385
|
-
let url = buildFetchUrl(config.baseUrl, prefix, endpoint.path, args, pathPrefixStr);
|
|
1444
|
+
const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
|
|
1445
|
+
const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
|
|
1386
1446
|
const headers = {
|
|
1387
1447
|
Accept: "application/json",
|
|
1388
1448
|
...typeof config.headers === "function" ? config.headers() : config.headers
|
|
1389
1449
|
};
|
|
1390
1450
|
const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
|
|
1391
|
-
const
|
|
1392
|
-
const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
|
|
1393
|
-
if (isQuery && args) {
|
|
1394
|
-
const remaining = stripParams(args, endpoint.path, prefixKeys);
|
|
1395
|
-
const params = collectQueryParams(remaining, new Set, endpoint);
|
|
1396
|
-
if (params) {
|
|
1397
|
-
const searchParams = new URLSearchParams;
|
|
1398
|
-
for (const [k, v] of Object.entries(params)) {
|
|
1399
|
-
if (Array.isArray(v)) {
|
|
1400
|
-
for (const item of v)
|
|
1401
|
-
searchParams.append(k, String(item));
|
|
1402
|
-
} else {
|
|
1403
|
-
searchParams.set(k, String(v));
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
url += `?${searchParams}`;
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1451
|
+
const hasBody = endpoint.method !== "GET" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
|
|
1409
1452
|
if (hasBody)
|
|
1410
1453
|
headers["Content-Type"] = "application/json";
|
|
1411
1454
|
if (endpoint.multipart && args) {
|
|
@@ -1415,7 +1458,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
1415
1458
|
}
|
|
1416
1459
|
const formData = new FormData;
|
|
1417
1460
|
appendMultipartFile(formData, endpoint.multipart, file);
|
|
1418
|
-
appendFormFields(formData,
|
|
1461
|
+
appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
|
|
1419
1462
|
const res2 = await fetch(url, {
|
|
1420
1463
|
method: endpoint.method,
|
|
1421
1464
|
headers,
|
|
@@ -1439,7 +1482,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
1439
1482
|
credentials: config.credentials,
|
|
1440
1483
|
signal,
|
|
1441
1484
|
...hasBody && {
|
|
1442
|
-
body: JSON.stringify(
|
|
1485
|
+
body: JSON.stringify(plan.remainingArgs)
|
|
1443
1486
|
}
|
|
1444
1487
|
});
|
|
1445
1488
|
if (!res.ok) {
|
|
@@ -1462,36 +1505,6 @@ async function throwForErrorResponse(res, config, fallbackBody) {
|
|
|
1462
1505
|
}
|
|
1463
1506
|
throw new ApiError("HTTP_ERROR", res.status, { body });
|
|
1464
1507
|
}
|
|
1465
|
-
function extractParamNames(path) {
|
|
1466
|
-
const matches = path.match(/:(\w+)/g);
|
|
1467
|
-
return matches ? matches.map((m) => m.slice(1)) : [];
|
|
1468
|
-
}
|
|
1469
|
-
function buildFetchUrl(baseUrl, prefix, path, args, pathPrefix = "") {
|
|
1470
|
-
let fullPath = `/${pathPrefix}${prefix}${path === "/" ? "" : path}`;
|
|
1471
|
-
if (args) {
|
|
1472
|
-
fullPath = fullPath.replace(/:(\w+)/g, (_, key) => {
|
|
1473
|
-
const val = args[key];
|
|
1474
|
-
if (val === undefined || val === null) {
|
|
1475
|
-
throw new Error(`Missing path param: ${key}`);
|
|
1476
|
-
}
|
|
1477
|
-
return encodeURIComponent(String(val));
|
|
1478
|
-
});
|
|
1479
|
-
}
|
|
1480
|
-
return `${baseUrl}${fullPath}`;
|
|
1481
|
-
}
|
|
1482
|
-
function stripParams(args, path, extra) {
|
|
1483
|
-
const skip = new Set(extra);
|
|
1484
|
-
for (const match of path.matchAll(/:(\w+)/g)) {
|
|
1485
|
-
if (match[1])
|
|
1486
|
-
skip.add(match[1]);
|
|
1487
|
-
}
|
|
1488
|
-
const result = {};
|
|
1489
|
-
for (const [k, v] of Object.entries(args)) {
|
|
1490
|
-
if (!skip.has(k))
|
|
1491
|
-
result[k] = v;
|
|
1492
|
-
}
|
|
1493
|
-
return result;
|
|
1494
|
-
}
|
|
1495
1508
|
|
|
1496
1509
|
// src/tools/remote.ts
|
|
1497
1510
|
var HTTP_ONLY = Object.freeze(["HTTP"]);
|
|
@@ -1514,7 +1527,7 @@ function implementRemote(contract, http, options) {
|
|
|
1514
1527
|
serviceName: contract.meta.prefix,
|
|
1515
1528
|
key,
|
|
1516
1529
|
toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
|
|
1517
|
-
expose: endpoint.rawResponse || endpoint.rawBody ? HTTP_ONLY : endpoint.expose,
|
|
1530
|
+
expose: endpoint.rawResponse || endpoint.rawBody || endpoint.responseMeta ? HTTP_ONLY : endpoint.expose,
|
|
1518
1531
|
ui: "ui" in endpoint ? endpoint.ui : undefined,
|
|
1519
1532
|
annotations: "annotations" in endpoint ? endpoint.annotations : undefined,
|
|
1520
1533
|
meta: mergeMeta(contract.meta.meta, endpoint.meta),
|
|
@@ -1527,6 +1540,7 @@ function implementRemote(contract, http, options) {
|
|
|
1527
1540
|
idempotent: endpoint.idempotent,
|
|
1528
1541
|
rawResponse: endpoint.rawResponse,
|
|
1529
1542
|
rawBody: endpoint.rawBody,
|
|
1543
|
+
responseMeta: endpoint.responseMeta,
|
|
1530
1544
|
contentType: "contentType" in endpoint ? endpoint.contentType : undefined,
|
|
1531
1545
|
handler: async (ctx) => {
|
|
1532
1546
|
const call = client[key];
|
package/llms-full.txt
CHANGED
|
@@ -230,7 +230,7 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
230
230
|
| Field | Required | Purpose |
|
|
231
231
|
|-------|----------|---------|
|
|
232
232
|
| `method` | yes | `GET` · `POST` · `PUT` · `PATCH` · `DELETE` |
|
|
233
|
-
| `path` | yes | route path under the contract `prefix`; `:name` marks a path param |
|
|
233
|
+
| `path` | yes | route path under the contract `prefix`; `:name` marks a path param and a terminal `/*` captures the remaining path |
|
|
234
234
|
| `desc` | yes | human description — also the MCP / agent tool description |
|
|
235
235
|
| `params` | no | Zod schema for **path params** (`:id`, …) |
|
|
236
236
|
| `input` | no | Zod schema for the **request body** (or query, for GET/DELETE) |
|
|
@@ -245,6 +245,7 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
245
245
|
| `meta` | no | opaque app metadata — read in hooks / on tool mounts, never in OpenAPI ([below](#endpoint-metadata-meta)) |
|
|
246
246
|
| `rawResponse` | no | the handler returns the `Response` itself — a download, a file, an SSE stream. HTTP-only, never a tool, no `output`. See [Raw-response endpoints](./server.md#raw-response-endpoints) |
|
|
247
247
|
| `rawBody` | no | retain original JSON text for a signed HTTP webhook. See [Signed JSON webhooks](./server.md#signed-json-webhooks) |
|
|
248
|
+
| `responseMeta` | no | make a typed-data endpoint HTTP-only, optionally declare its success status and expose `ctx.response.headers` — [Typed JSON response metadata](./server.md#typed-json-response-metadata) |
|
|
248
249
|
| `contentType` | no | documented response media type of a `rawResponse` endpoint (OpenAPI only) |
|
|
249
250
|
|
|
250
251
|
## `params` vs `input` vs `output`
|
|
@@ -253,7 +254,10 @@ The three schemas are distinct on purpose:
|
|
|
253
254
|
|
|
254
255
|
- **`params`** — values in the URL path. `path: '/:id'` ⇒
|
|
255
256
|
`params: z.object({ id: z.string() })`. The client takes them from the call
|
|
256
|
-
argument and substitutes them into the URL.
|
|
257
|
+
argument and substitutes them into the URL. A terminal wildcard is the
|
|
258
|
+
quoted `'*'` field: `path: '/:slug/*'` with
|
|
259
|
+
`params: z.object({ slug: z.string(), '*': z.string() })` matches both
|
|
260
|
+
`/foo/page` and `/foo/a/b`; the handler receives `'page'` or `'a/b'`.
|
|
257
261
|
- **`input`** — the request payload. For `POST` / `PUT` / `PATCH` it is the JSON
|
|
258
262
|
body; for `GET` / `DELETE` it is the query string. The handler reads it as
|
|
259
263
|
`ctx.input`.
|
|
@@ -267,6 +271,9 @@ the body.
|
|
|
267
271
|
```ts
|
|
268
272
|
// path: '/:id', params: { id }, input: { text }
|
|
269
273
|
await api.update({ id: '1', text: 'new' }) // PUT /users/1 body: { text: 'new' }
|
|
274
|
+
|
|
275
|
+
// path: '/:slug/*', params: { slug, '*': remainder }
|
|
276
|
+
await api.app({ slug: 'foo', '*': 'a/b' }) // GET /apps/foo/a/b
|
|
270
277
|
```
|
|
271
278
|
|
|
272
279
|
### Input vs. output types
|
|
@@ -317,10 +324,12 @@ tools. Narrow it with `expose`:
|
|
|
317
324
|
- `expose: ['MCP', 'AGENT']` — a tool only; no HTTP route.
|
|
318
325
|
- omit `expose` — all transports.
|
|
319
326
|
|
|
320
|
-
Tool transports (`MCP`, `AGENT`) skip
|
|
327
|
+
Tool transports (`MCP`, `AGENT`) skip three kinds of endpoint automatically:
|
|
321
328
|
`multipart` (a file upload is not a tool call) and
|
|
322
329
|
[`rawResponse`](./server.md#raw-response-endpoints) (its answer is bytes, which
|
|
323
|
-
a tool result cannot carry — it would serialize to `{}`)
|
|
330
|
+
a tool result cannot carry — it would serialize to `{}`), plus
|
|
331
|
+
[`responseMeta`](./server.md#typed-json-response-metadata) (outbound HTTP
|
|
332
|
+
headers have no meaning on a tool call).
|
|
324
333
|
|
|
325
334
|
## `toolName`
|
|
326
335
|
|
|
@@ -764,6 +773,17 @@ prefix param in the schema, use a non-strict `z.object` (extra keys are dropped
|
|
|
764
773
|
from `ctx.params`, but `ctx.tenantId` still works), or read the param off the
|
|
765
774
|
context root.
|
|
766
775
|
|
|
776
|
+
**Trailing wildcard.** A contract path may end in `/*`. `/app/:slug/*` matches
|
|
777
|
+
both `/app/foo` and nested paths such as `/app/foo/a/b`; the collected params are
|
|
778
|
+
`{ slug: 'foo', '*': '' }` and `{ slug: 'foo', '*': 'a/b' }` respectively. Put
|
|
779
|
+
the quoted `'*'` field in the endpoint's `params` schema to keep it in typed
|
|
780
|
+
`ctx.params`. Each captured segment is URL-decoded before the remainder is
|
|
781
|
+
joined, so encoded spaces and reserved characters reach the handler as their
|
|
782
|
+
semantic values while `/` remains the segment boundary. Static and named-param
|
|
783
|
+
routes are matched before a catch-all, so a
|
|
784
|
+
more specific endpoint wins regardless of declaration order. The same matcher
|
|
785
|
+
drives `405 Allow` resolution.
|
|
786
|
+
|
|
767
787
|
### Scope-driven mounting (`scopePrefixes`)
|
|
768
788
|
|
|
769
789
|
With several scopes, hand-partitioning services into `groups` duplicates the
|
|
@@ -796,7 +816,7 @@ createServer({
|
|
|
796
816
|
hooks: {
|
|
797
817
|
onRequest(req) { /* logging, global rate limit — may return a Response to short-circuit */ },
|
|
798
818
|
beforeHandle(ctx, endpoint) { /* auth, scope checks — throw to reject */ },
|
|
799
|
-
afterHandle(ctx, result, ep) { /* transform the result
|
|
819
|
+
afterHandle(ctx, result, ep) { /* transform the result data */ },
|
|
800
820
|
onError(ctx, error, ep) { /* custom error response — return a Response */ },
|
|
801
821
|
},
|
|
802
822
|
})
|
|
@@ -849,6 +869,46 @@ not retain the text. `maxJsonBodyBytes` may also be set once on `createServer` /
|
|
|
849
869
|
`createHandler`; a route value wins. Both limits are opt-in and abort an
|
|
850
870
|
oversized stream before it is fully buffered. → ADR 0051
|
|
851
871
|
|
|
872
|
+
## Typed JSON response metadata
|
|
873
|
+
|
|
874
|
+
A JSON endpoint that must attach dynamic HTTP headers while preserving typed
|
|
875
|
+
output declares `responseMeta`. The handler still returns ordinary data:
|
|
876
|
+
|
|
877
|
+
```ts
|
|
878
|
+
const auth = defineContract({ prefix: 'auth' }, {
|
|
879
|
+
complete: {
|
|
880
|
+
method: 'POST', path: '/complete', desc: 'Complete authentication',
|
|
881
|
+
input: CompleteAuthSchema,
|
|
882
|
+
output: AuthUserSchema,
|
|
883
|
+
responseMeta: { status: 200 },
|
|
884
|
+
},
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
complete: async ({ input, response }) => {
|
|
888
|
+
const result = await authenticate(input.token)
|
|
889
|
+
response.headers.append('Set-Cookie', session.set(result.sessionId))
|
|
890
|
+
response.headers.append('Set-Cookie', preferences.set(result.preferencesId))
|
|
891
|
+
return result.user
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
`ctx.response.headers` is a fresh Web Fetch `Headers` bag per request. `append`
|
|
896
|
+
preserves repeated `Set-Cookie` values on Bun and Node. The endpoint is
|
|
897
|
+
HTTP-only, but its typed client method still resolves to `AuthUser` — not
|
|
898
|
+
`Response` — and the final data still passes group/global `afterHandle` and the
|
|
899
|
+
declared `output` schema exactly once.
|
|
900
|
+
|
|
901
|
+
`responseMeta.status` is static contract metadata and OpenAPI publishes the same
|
|
902
|
+
2xx code. Without it, data keeps status `200` and no-data keeps `204`. Bodyless
|
|
903
|
+
`204`/`205` cannot be combined with `output`. Redirects, streams, files and
|
|
904
|
+
handler-owned status/body logic remain [`rawResponse: true`](#raw-response-endpoints).
|
|
905
|
+
|
|
906
|
+
Collected headers are merged only after the complete success pipeline. A
|
|
907
|
+
handler, hook or output-validation failure discards them. `Content-Type`,
|
|
908
|
+
`Content-Length`, `x-request-id` and every `Access-Control-*` header remain
|
|
909
|
+
framework-owned; trying to set one fails loudly with the endpoint identity.
|
|
910
|
+
→ [ADR 0052](../decisions/0052-typed-json-response-metadata.md)
|
|
911
|
+
|
|
852
912
|
## Raw-response endpoints
|
|
853
913
|
|
|
854
914
|
An endpoint that answers with **bytes rather than data** — a PDF download, a
|
|
@@ -891,6 +951,10 @@ const name = res.headers.get('Content-Disposition')
|
|
|
891
951
|
const blob = await res.blob()
|
|
892
952
|
```
|
|
893
953
|
|
|
954
|
+
When the browser should navigate or assign the endpoint directly to `src`, use
|
|
955
|
+
[`createUrlBuilder`](./client.md#contract-url-builders). Raw-response GET methods
|
|
956
|
+
are included, while mutation and multipart methods are intentionally absent.
|
|
957
|
+
|
|
894
958
|
Cross-origin, remember that those headers are readable only because CORS exposes
|
|
895
959
|
them — see [`cors.exposeHeaders`](#serving-files--range-requests).
|
|
896
960
|
|
|
@@ -902,6 +966,11 @@ client — which is what you want for an OAuth redirect or a non-JSON webhook. A
|
|
|
902
966
|
signed JSON webhook can stay validated through
|
|
903
967
|
[`rawBody: true`](#signed-json-webhooks).
|
|
904
968
|
|
|
969
|
+
If an endpoint returns typed JSON and only needs an additional status/header,
|
|
970
|
+
use [`responseMeta`](#typed-json-response-metadata), not `rawResponse`: the raw
|
|
971
|
+
variant deliberately transfers response ownership and changes the client result
|
|
972
|
+
to `Response`.
|
|
973
|
+
|
|
905
974
|
⚠️ **Delete the old raw route when you move an endpoint into the contract.** Raw
|
|
906
975
|
routes are matched **first**, so a leftover one keeps serving the bytes and the
|
|
907
976
|
contract endpoint — with its auth gate — never runs. stitchkit warns at startup
|
|
@@ -1135,6 +1204,14 @@ createServer({ services: [users, orders], rawRoutes: [openApiRoute('/openapi.jso
|
|
|
1135
1204
|
|
|
1136
1205
|
Only HTTP-exposed methods appear (an MCP/agent-only tool is skipped).
|
|
1137
1206
|
|
|
1207
|
+
OpenAPI 3.1 has no standard multi-segment path parameter. For a contract path
|
|
1208
|
+
ending in `/*`, Stitchkit keeps the literal runtime path, omits `*` from the
|
|
1209
|
+
standard `in: path` parameter list, and emits
|
|
1210
|
+
`x-stitchkit-trailing-wildcard` on the operation with its parameter name,
|
|
1211
|
+
schema and semantics. A generic OpenAPI client therefore cannot invent
|
|
1212
|
+
catch-all expansion; use Stitchkit's typed client or teach the generator that
|
|
1213
|
+
extension.
|
|
1214
|
+
|
|
1138
1215
|
### Curating the spec — `includeMethod`
|
|
1139
1216
|
|
|
1140
1217
|
To publish a **subset** — a public spec that advertises only some methods
|
|
@@ -1194,6 +1271,10 @@ import { createHttpClient } from 'stitchkit'
|
|
|
1194
1271
|
const http = createHttpClient({ baseUrl: '/api' })
|
|
1195
1272
|
```
|
|
1196
1273
|
|
|
1274
|
+
The returned `ConfiguredHttpClient` keeps that `baseUrl` as a readonly public
|
|
1275
|
+
field. Besides executing requests, it can therefore seed contract URL builders
|
|
1276
|
+
without repeating transport configuration.
|
|
1277
|
+
|
|
1197
1278
|
### `HttpClientConfig`
|
|
1198
1279
|
|
|
1199
1280
|
| Field | Default | Purpose |
|
|
@@ -1239,6 +1320,8 @@ Each call takes one argument object. The client routes each field by the
|
|
|
1239
1320
|
contract:
|
|
1240
1321
|
|
|
1241
1322
|
- a **path param** (`:id`) is substituted into the URL,
|
|
1323
|
+
- a terminal wildcard (`/*`) consumes the `'*'` field and preserves its path
|
|
1324
|
+
segments (`{ '*': 'a/b' }` → `/a/b`, not `/%2Fa%2Fb` or a query field),
|
|
1242
1325
|
- for `GET` / `DELETE`, the remaining fields become the **query string**
|
|
1243
1326
|
(arrays become repeated keys),
|
|
1244
1327
|
- for `POST` / `PUT` / `PATCH`, they become the **JSON body**,
|
|
@@ -1258,7 +1341,53 @@ await api.posts.create({ title: 'Hi' })
|
|
|
1258
1341
|
```
|
|
1259
1342
|
|
|
1260
1343
|
`createClients` builds one typed client per contract from a registry — list the
|
|
1261
|
-
contracts once, get the whole API typed.
|
|
1344
|
+
contracts once, get the whole API typed. It accepts the same optional scoped
|
|
1345
|
+
config as `createClient`, so a whole registry can share one resource prefix:
|
|
1346
|
+
|
|
1347
|
+
```ts
|
|
1348
|
+
const tenantApi = createClients({ users, posts }, http, {
|
|
1349
|
+
stripPrefixKeys: ['tenantId'],
|
|
1350
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}`,
|
|
1351
|
+
})
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
Every method now requires `tenantId`, and the callback sees it as a `string`.
|
|
1355
|
+
The batch form delegates to the same single-contract client runtime, including
|
|
1356
|
+
HTTP exposure filtering, multipart, raw responses and output validation.
|
|
1357
|
+
|
|
1358
|
+
## Contract URL builders
|
|
1359
|
+
|
|
1360
|
+
Browser-native consumers such as `<img src>`, downloads and navigation need a
|
|
1361
|
+
URL, not a fetched response. `createUrlBuilder` derives those URLs from the same
|
|
1362
|
+
contract path planner used by both typed-client transports:
|
|
1363
|
+
|
|
1364
|
+
```ts
|
|
1365
|
+
import { createUrlBuilder, createUrlBuilders } from 'stitchkit'
|
|
1366
|
+
|
|
1367
|
+
const mediaUrls = createUrlBuilder(media, http, {
|
|
1368
|
+
stripPrefixKeys: ['tenantId'],
|
|
1369
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}`,
|
|
1370
|
+
})
|
|
1371
|
+
|
|
1372
|
+
const src = mediaUrls.file({
|
|
1373
|
+
tenantId: 't_123',
|
|
1374
|
+
fileId: 'f_456',
|
|
1375
|
+
thumbnail: true,
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
const urls = createUrlBuilders({ media, exports }, http)
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
Only HTTP-exposed, non-multipart `GET` endpoints appear on a URL builder. Raw
|
|
1382
|
+
response GET endpoints are included, so downloads and streams stay
|
|
1383
|
+
contract-driven. Path and scoped-prefix keys are consumed by the path; remaining
|
|
1384
|
+
GET input becomes the query string, including repeated keys for arrays.
|
|
1385
|
+
|
|
1386
|
+
Building a URL is synchronous and performs no request, auth event, header
|
|
1387
|
+
resolution or output validation. A `ConfiguredHttpClient` created by
|
|
1388
|
+
`createHttpClient` supplies its base URL; custom transports can pass an explicit
|
|
1389
|
+
`{ baseUrl: 'https://api.example.com' }` instead. Relative bases produce relative
|
|
1390
|
+
URLs.
|
|
1262
1391
|
|
|
1263
1392
|
## `ApiError`
|
|
1264
1393
|
|
|
@@ -1329,7 +1458,7 @@ segment to every URL — the client half of a multi-tenant API
|
|
|
1329
1458
|
interface ContractClientConfig {
|
|
1330
1459
|
/** Prepended to every request URL. A function is called per request with the
|
|
1331
1460
|
* call's argument object, so the prefix can depend on the arguments. */
|
|
1332
|
-
pathPrefix?: string | ((args:
|
|
1461
|
+
pathPrefix?: string | ((args: { [K in ConsumedKey]: string }) => string)
|
|
1333
1462
|
/** Argument keys consumed by `pathPrefix` — stripped from the query/body so
|
|
1334
1463
|
* they are not also sent there (the endpoint's own path `:params` are
|
|
1335
1464
|
* stripped automatically; list any *extra* keys here). */
|
|
@@ -1341,8 +1470,8 @@ A per-tenant client — `tenantId` goes into the URL, not the body:
|
|
|
1341
1470
|
|
|
1342
1471
|
```ts
|
|
1343
1472
|
const widgets = createClient(widgetsContract, http, {
|
|
1344
|
-
pathPrefix: (args) => `tenants/${args.tenantId}/`,
|
|
1345
1473
|
stripPrefixKeys: ['tenantId'],
|
|
1474
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}/`,
|
|
1346
1475
|
})
|
|
1347
1476
|
|
|
1348
1477
|
widgets.list({ tenantId: 't_123' }) // GET /tenants/t_123/widgets
|
|
@@ -2744,6 +2873,20 @@ session.clear() // → a Set-Cookie value that expires it
|
|
|
2744
2873
|
config is not repeated at every call site. `parseCookies(header)` and
|
|
2745
2874
|
`serializeCookie(name, value, opts)` are the lower-level primitives.
|
|
2746
2875
|
|
|
2876
|
+
To set a cookie from a schema-validated JSON endpoint without losing its typed
|
|
2877
|
+
client result, declare [`responseMeta`](./server.md#typed-json-response-metadata)
|
|
2878
|
+
and append the generated value:
|
|
2879
|
+
|
|
2880
|
+
```ts
|
|
2881
|
+
complete: async ({ response }) => {
|
|
2882
|
+
response.headers.append('Set-Cookie', session.set('abc123'))
|
|
2883
|
+
return authenticatedUser
|
|
2884
|
+
}
|
|
2885
|
+
```
|
|
2886
|
+
|
|
2887
|
+
Append once per cookie; Stitchkit preserves separate `Set-Cookie` fields through
|
|
2888
|
+
both Bun and Node. Cookie/session policy remains application logic.
|
|
2889
|
+
|
|
2747
2890
|
## The error model
|
|
2748
2891
|
|
|
2749
2892
|
One error type, `AppError`, is shared by the contract, the server and the
|
|
@@ -3609,7 +3752,7 @@ keeps `tenantId` out of the body/query
|
|
|
3609
3752
|
|
|
3610
3753
|
```ts
|
|
3611
3754
|
const widgetsApi = createClient(widgets, http, {
|
|
3612
|
-
pathPrefix: (
|
|
3755
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}/`,
|
|
3613
3756
|
stripPrefixKeys: ['tenantId'],
|
|
3614
3757
|
})
|
|
3615
3758
|
|
|
@@ -3908,12 +4051,17 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
3908
4051
|
| Export | Kind | Summary |
|
|
3909
4052
|
|--------|------|---------|
|
|
3910
4053
|
| `createClient` | function | build a typed client from a contract — [guide](../guide/client.md#createclient) |
|
|
3911
|
-
| `createClients` | function | build one typed client per contract from a registry |
|
|
4054
|
+
| `createClients` | function | build one exact typed client per contract from a registry; accepts the same scoped config and transports as `createClient` |
|
|
4055
|
+
| `createUrlBuilder` | function | build synchronous browser-native URLs for one contract's HTTP GET endpoints — [guide](../guide/client.md#contract-url-builders) |
|
|
4056
|
+
| `createUrlBuilders` | function | build one exact URL builder per contract in a registry |
|
|
4057
|
+
| `UrlBuilderConfig` | _type_ | explicit `{ baseUrl }` source for a URL builder |
|
|
3912
4058
|
| `ClientConfig` | _type_ | config for `createClient`'s bare-fetch mode (2nd arg, no `HttpClient`) |
|
|
3913
4059
|
| `ContractClientConfig` | _type_ | per-tenant / resource-scoped client config — dynamic `pathPrefix` + `stripPrefixKeys` ([guide](../guide/client.md#contractclientconfig--per-tenant--resource-scoped-clients)) |
|
|
4060
|
+
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
3914
4061
|
| `createHttpClient` | function | the Ky-based HTTP transport — [guide](../guide/client.md#createhttpclient) |
|
|
3915
4062
|
| `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` |
|
|
3916
4063
|
| `HttpClient` | _type_ | the transport interface `createClient` builds on |
|
|
4064
|
+
| `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
|
|
3917
4065
|
| `HttpClientConfig` | _type_ | config for `createHttpClient` |
|
|
3918
4066
|
| `RequestOptions` | _type_ | per-call options — params, timeout, response type |
|
|
3919
4067
|
| `HeaderProvider` | _type_ | static or per-request headers |
|
|
@@ -3965,6 +4113,10 @@ from the root `stitchkit`.
|
|
|
3965
4113
|
| `ContractDef` | _type_ | a defined contract |
|
|
3966
4114
|
| `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` and `meta` (a default every endpoint shallow-merges over) |
|
|
3967
4115
|
| `EndpointDef` | _type_ | a single endpoint definition |
|
|
4116
|
+
| `EndpointResponseMeta` | _type_ | static success metadata declared by an HTTP-only typed-data endpoint |
|
|
4117
|
+
| `ResponseMetadata` | _type_ | per-request outbound collector exposed as `ctx.response` only for a `responseMeta` endpoint |
|
|
4118
|
+
| `HttpSuccessStatus` | _type_ | supported declared 2xx success statuses |
|
|
4119
|
+
| `BodyHttpSuccessStatus` | _type_ | supported 2xx statuses excluding bodyless 204/205 |
|
|
3968
4120
|
| `HttpMethod` | _type_ | `GET \| POST \| PUT \| PATCH \| DELETE` |
|
|
3969
4121
|
| `Transport` | _type_ | `HTTP \| MCP \| AGENT \| CLI` |
|
|
3970
4122
|
| `TransportSource` | _type_ | `http \| mcp \| agent \| cli` — the value of `ctx.source` |
|
|
@@ -3975,6 +4127,9 @@ from the root `stitchkit`.
|
|
|
3975
4127
|
| `TypedHttpClient` | _type_ | the typed client, HTTP endpoints only (`= ScopedHttpClient<C, unknown>`) |
|
|
3976
4128
|
| `ScopedHttpClient` | _type_ | a client whose `stripPrefixKeys` become required args ([guide](../guide/multi-tenant.md)) |
|
|
3977
4129
|
| `ScopedEndpointFn` | _type_ | one method's signature with the consumed keys folded in |
|
|
4130
|
+
| `TypedUrlBuilder` | _type_ | one contract's HTTP, non-multipart GET endpoints as synchronous URL functions |
|
|
4131
|
+
| `ScopedUrlBuilder` | _type_ | a URL builder whose scoped-prefix keys are required method arguments |
|
|
4132
|
+
| `ScopedUrlFn` | _type_ | one URL method's signature with scoped-prefix keys folded in |
|
|
3978
4133
|
| `MultipartFile` | _type_ | a `multipart` file field — `Blob \| FileDescriptor` |
|
|
3979
4134
|
| `FileDescriptor` | _type_ | a React Native / Expo file — `{ uri, name, type }` |
|
|
3980
4135
|
| `EndpointToolAnnotations` | _type_ | MCP behavioural hints on an endpoint (`readOnlyHint` / `destructiveHint` / `title`) |
|
package/package.json
CHANGED
|
File without changes
|