stitchkit 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/browser/client-url.d.ts +2 -0
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/client.d.ts +25 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +5 -6
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/contract/define.d.ts +36 -12
- 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-czmqks7r.js → index-6jypn22c.js} +1 -1
- package/dist/{index-p3kwf73n.js → index-7rbzbnnf.js} +28 -15
- package/dist/{index-bgdd42pt.js → index-92gs1m5b.js} +1 -1
- package/dist/{index-mzx0an0s.js → index-fyfk537k.js} +50 -5
- package/dist/{index-x4wbc8sz.js → index-xax049k6.js} +73 -5
- package/dist/{index-n5t4gnfz.js → index-y5scd1cr.js} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -17
- package/dist/internal/route-pattern.d.ts +7 -0
- package/dist/internal/route-pattern.d.ts.map +1 -0
- package/dist/node.js +3 -3
- package/dist/observability/index.js +1 -1
- package/dist/react/entity-cache.d.ts +56 -42
- package/dist/react/entity-cache.d.ts.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +98 -46
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/index.js +10 -9
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/router.d.ts +2 -0
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/socket-io.d.ts +1 -1
- package/dist/server/types.d.ts +4 -6
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +3 -0
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/invoker.d.ts +33 -0
- package/dist/tools/invoker.d.ts.map +1 -0
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/names.d.ts +1 -1
- package/dist/tools/names.d.ts.map +1 -1
- package/dist/tools/native-mcp.d.ts +6 -33
- package/dist/tools/native-mcp.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +58 -0
- package/dist/tools/runtime-tool.d.ts.map +1 -0
- package/dist/tools.d.ts +3 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +181 -82
- package/llms-full.txt +417 -89
- package/package.json +1 -1
|
@@ -2,11 +2,66 @@ import {
|
|
|
2
2
|
mapObject
|
|
3
3
|
} from "./index-809wc1tt.js";
|
|
4
4
|
|
|
5
|
+
// src/contract/define.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
// src/internal/route-pattern.ts
|
|
9
|
+
var PARAM_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
10
|
+
function parseTrailingWildcard(path) {
|
|
11
|
+
const segments = path.split("/").filter(Boolean);
|
|
12
|
+
const paramNames = new Set;
|
|
13
|
+
let wildcard = null;
|
|
14
|
+
for (const [segmentIndex, segment] of segments.entries()) {
|
|
15
|
+
if (segment.startsWith(":")) {
|
|
16
|
+
const name2 = segment.slice(1);
|
|
17
|
+
if (!PARAM_IDENTIFIER.test(name2)) {
|
|
18
|
+
throw new Error(`Invalid route parameter name "${name2}" in path "${path}"`);
|
|
19
|
+
}
|
|
20
|
+
if (paramNames.has(name2)) {
|
|
21
|
+
throw new Error(`Duplicate route parameter name "${name2}" in path "${path}"`);
|
|
22
|
+
}
|
|
23
|
+
paramNames.add(name2);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!segment.startsWith("*")) {
|
|
27
|
+
if (segment.includes("*")) {
|
|
28
|
+
throw new Error(`Wildcard must occupy its own segment in path "${path}"`);
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const name = segment.slice(1);
|
|
33
|
+
if (!PARAM_IDENTIFIER.test(name)) {
|
|
34
|
+
throw new Error(`Trailing wildcard in path "${path}" must be named, for example "/*filePath"`);
|
|
35
|
+
}
|
|
36
|
+
if (segmentIndex !== segments.length - 1) {
|
|
37
|
+
throw new Error(`Wildcard "*${name}" must be the final segment in path "${path}"`);
|
|
38
|
+
}
|
|
39
|
+
if (wildcard) {
|
|
40
|
+
throw new Error(`Path "${path}" contains more than one wildcard`);
|
|
41
|
+
}
|
|
42
|
+
if (paramNames.has(name)) {
|
|
43
|
+
throw new Error(`Duplicate route parameter name "${name}" in path "${path}"`);
|
|
44
|
+
}
|
|
45
|
+
wildcard = { name, segmentIndex };
|
|
46
|
+
}
|
|
47
|
+
return wildcard;
|
|
48
|
+
}
|
|
49
|
+
|
|
5
50
|
// src/contract/define.ts
|
|
6
51
|
var ALL_TRANSPORTS = ["HTTP", "MCP", "AGENT", "CLI"];
|
|
7
52
|
function defineContract(meta, endpoints) {
|
|
8
53
|
const toolTransports = new Map;
|
|
9
54
|
for (const [key, ep] of Object.entries(endpoints)) {
|
|
55
|
+
const wildcard = parseTrailingWildcard(ep.path);
|
|
56
|
+
if (wildcard) {
|
|
57
|
+
if (!ep.params) {
|
|
58
|
+
throw new Error(`Contract "${meta.prefix}": endpoint "${key}" wildcard "${wildcard.name}" requires a params schema field`);
|
|
59
|
+
}
|
|
60
|
+
const paramsJson = z.toJSONSchema(ep.params, { io: "input" });
|
|
61
|
+
if (!paramsJson.properties || !(wildcard.name in paramsJson.properties)) {
|
|
62
|
+
throw new Error(`Contract "${meta.prefix}": endpoint "${key}" params schema is missing wildcard field "${wildcard.name}"`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
10
65
|
if (ep.desc.trim() === "") {
|
|
11
66
|
throw new Error(`Contract "${meta.prefix}": endpoint "${key}" has an empty desc`);
|
|
12
67
|
}
|
|
@@ -15,6 +70,8 @@ function defineContract(meta, endpoints) {
|
|
|
15
70
|
}
|
|
16
71
|
if (ep.rawResponse)
|
|
17
72
|
assertRawEndpoint(meta.prefix, key, ep);
|
|
73
|
+
if (ep.method === "HEAD")
|
|
74
|
+
assertHeadEndpoint(meta.prefix, key, ep);
|
|
18
75
|
if (ep.rawBody)
|
|
19
76
|
assertRawBodyEndpoint(meta.prefix, key, ep);
|
|
20
77
|
if ("responseMeta" in ep)
|
|
@@ -55,6 +112,17 @@ function assertRawEndpoint(prefix, key, ep) {
|
|
|
55
112
|
throw new Error(`${where} is HTTP-only — remove ${nonHttp.join(", ")} from expose`);
|
|
56
113
|
}
|
|
57
114
|
}
|
|
115
|
+
function assertHeadEndpoint(prefix, key, ep) {
|
|
116
|
+
const where = `Contract "${prefix}": HEAD endpoint "${key}"`;
|
|
117
|
+
if (!ep.rawResponse)
|
|
118
|
+
throw new Error(`${where} must declare rawResponse: true`);
|
|
119
|
+
if (ep.input)
|
|
120
|
+
throw new Error(`${where} cannot declare an input schema`);
|
|
121
|
+
if (ep.multipart)
|
|
122
|
+
throw new Error(`${where} cannot be multipart`);
|
|
123
|
+
if (ep.rawBody)
|
|
124
|
+
throw new Error(`${where} cannot retain a raw body`);
|
|
125
|
+
}
|
|
58
126
|
function assertRawBodyEndpoint(prefix, key, ep) {
|
|
59
127
|
const where = `Contract "${prefix}": rawBody endpoint "${key}"`;
|
|
60
128
|
if (!ep.input)
|
|
@@ -194,7 +262,7 @@ function createContractFactory() {
|
|
|
194
262
|
};
|
|
195
263
|
}
|
|
196
264
|
// src/contract/pagination.ts
|
|
197
|
-
import { z } from "zod";
|
|
265
|
+
import { z as z2 } from "zod";
|
|
198
266
|
|
|
199
267
|
// src/internal/base64url.ts
|
|
200
268
|
function bytesToBase64Url(bytes) {
|
|
@@ -214,9 +282,9 @@ function base64UrlToBytes(segment) {
|
|
|
214
282
|
|
|
215
283
|
// src/contract/pagination.ts
|
|
216
284
|
function paginatedSchema(itemSchema) {
|
|
217
|
-
return
|
|
218
|
-
items:
|
|
219
|
-
nextCursor:
|
|
285
|
+
return z2.object({
|
|
286
|
+
items: z2.array(itemSchema),
|
|
287
|
+
nextCursor: z2.string().nullable()
|
|
220
288
|
});
|
|
221
289
|
}
|
|
222
290
|
function toBase64Url(str) {
|
|
@@ -238,4 +306,4 @@ function decodeCursor(cursor, schema) {
|
|
|
238
306
|
return null;
|
|
239
307
|
}
|
|
240
308
|
}
|
|
241
|
-
export { ALL_TRANSPORTS, defineContract, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, defineErrors, createContractFactory, paginatedSchema, encodeCursor, decodeCursor };
|
|
309
|
+
export { parseTrailingWildcard, ALL_TRANSPORTS, defineContract, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, defineErrors, createContractFactory, paginatedSchema, encodeCursor, decodeCursor };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type ClientConfig, type ContractClientConfig, createClient, createClients, createUrlBuilder, createUrlBuilders, type PathPrefixArgs, type UrlBuilderConfig, } from './browser/client';
|
|
2
|
-
export { ApiError, type ApiEvent, type ApiEventListener, type ConfiguredHttpClient, createHttpClient, type HeaderProvider, type HttpClient, type HttpClientConfig, type RequestOptions, } from './browser/http';
|
|
1
|
+
export { type ClientConfig, type ClientContract, type ClientRegistryValue, type ContractClientConfig, contractEndpointMatchers, createClient, createClients, createScopedClients, createUrlBuilder, createUrlBuilders, type PathPrefixArgs, type RegistryScope, type ScopeClientConfigs, type ScopedClientRegistry, type UrlBuilderConfig, } from './browser/client';
|
|
2
|
+
export { ApiError, type ApiEvent, type ApiEventListener, type ConfiguredHttpClient, createHttpClient, type HeaderProvider, type HttpClient, type HttpClientConfig, type RequestOptions, type UnauthorizedMatcher, } from './browser/http';
|
|
3
3
|
export type { SocketEventMap, SocketIOClient, SocketIOClientConfig, } from './browser/socket-io';
|
|
4
4
|
export { createSocketIOClient } from './browser/socket-io';
|
|
5
5
|
export { type ParseSSEOptions, parseSSE } from './browser/stream';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClE,cAAc,YAAY,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
isStitchErrorCode,
|
|
15
15
|
notFound,
|
|
16
16
|
paginatedSchema,
|
|
17
|
+
parseTrailingWildcard,
|
|
17
18
|
rateLimited,
|
|
18
19
|
unauthorized
|
|
19
|
-
} from "./index-
|
|
20
|
+
} from "./index-xax049k6.js";
|
|
20
21
|
import {
|
|
21
22
|
isRecord,
|
|
22
23
|
mapObject,
|
|
@@ -86,14 +87,62 @@ function resolvePathPrefix(config, args) {
|
|
|
86
87
|
}
|
|
87
88
|
return config.pathPrefix(args);
|
|
88
89
|
}
|
|
90
|
+
function escapeRegex(value) {
|
|
91
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
92
|
+
}
|
|
93
|
+
function decodePathSegment(value) {
|
|
94
|
+
try {
|
|
95
|
+
return decodeURIComponent(value);
|
|
96
|
+
} catch {
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createClientRouteMatcher(endpoint, contractPrefix, config) {
|
|
101
|
+
if (typeof config?.pathPrefix === "function" && (!config.stripPrefixKeys || config.stripPrefixKeys.length === 0)) {
|
|
102
|
+
throw new Error("Dynamic pathPrefix matchers require stripPrefixKeys");
|
|
103
|
+
}
|
|
104
|
+
const markerByKey = {};
|
|
105
|
+
for (const [index, key] of (config?.stripPrefixKeys ?? []).entries()) {
|
|
106
|
+
markerByKey[key] = `__stitch_scope_${index}__`;
|
|
107
|
+
}
|
|
108
|
+
const pathPrefix = resolvePathPrefix(config, markerByKey);
|
|
109
|
+
const route = [pathPrefix, contractPrefix, endpoint.path === "/" ? "" : endpoint.path].filter(Boolean).join("/");
|
|
110
|
+
const patternSegments = route.split("/").filter(Boolean);
|
|
111
|
+
const wildcard = patternSegments.at(-1)?.startsWith("*") === true;
|
|
112
|
+
const fixedCount = wildcard ? patternSegments.length - 1 : patternSegments.length;
|
|
113
|
+
const markers = Object.values(markerByKey);
|
|
114
|
+
return (pathname) => {
|
|
115
|
+
const actualSegments = pathname.split("/").filter(Boolean).map(decodePathSegment);
|
|
116
|
+
if (wildcard ? actualSegments.length < fixedCount : actualSegments.length !== fixedCount) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
for (let index = 0;index < fixedCount; index += 1) {
|
|
120
|
+
const pattern = patternSegments[index];
|
|
121
|
+
const actual = actualSegments[index];
|
|
122
|
+
if (!pattern || actual === undefined)
|
|
123
|
+
return false;
|
|
124
|
+
if (pattern.startsWith(":"))
|
|
125
|
+
continue;
|
|
126
|
+
let source = escapeRegex(pattern);
|
|
127
|
+
for (const marker of markers) {
|
|
128
|
+
source = source.replaceAll(escapeRegex(marker), "[^/]+");
|
|
129
|
+
}
|
|
130
|
+
if (!new RegExp(`^${source}$`).test(actual))
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
89
136
|
function extractParamNames(path) {
|
|
90
137
|
const matches = path.match(/:(\w+)/g);
|
|
91
138
|
const names = matches ? matches.map((match) => match.slice(1)) : [];
|
|
92
|
-
|
|
93
|
-
|
|
139
|
+
const wildcard = parseTrailingWildcard(path);
|
|
140
|
+
if (wildcard)
|
|
141
|
+
names.push(wildcard.name);
|
|
94
142
|
return names;
|
|
95
143
|
}
|
|
96
144
|
function fillPathParams(path, args) {
|
|
145
|
+
const wildcard = parseTrailingWildcard(path);
|
|
97
146
|
let filled = path.replace(/:(\w+)/g, (_, key) => {
|
|
98
147
|
const value = args[key];
|
|
99
148
|
if (value === undefined || value === null) {
|
|
@@ -101,14 +150,14 @@ function fillPathParams(path, args) {
|
|
|
101
150
|
}
|
|
102
151
|
return encodeURIComponent(String(value));
|
|
103
152
|
});
|
|
104
|
-
if (!
|
|
153
|
+
if (!wildcard)
|
|
105
154
|
return filled;
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
throw new Error(
|
|
155
|
+
const wildcardValue = args[wildcard.name];
|
|
156
|
+
if (wildcardValue === undefined || wildcardValue === null) {
|
|
157
|
+
throw new Error(`Missing path param: ${wildcard.name}`);
|
|
109
158
|
}
|
|
110
|
-
const remainder = String(
|
|
111
|
-
filled = `${filled.slice(0, -1)}${remainder}`;
|
|
159
|
+
const remainder = String(wildcardValue).split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
160
|
+
filled = `${filled.slice(0, -(wildcard.name.length + 1))}${remainder}`;
|
|
112
161
|
return filled;
|
|
113
162
|
}
|
|
114
163
|
function stripConsumedArgs(args, path, scopeKeys) {
|
|
@@ -232,7 +281,7 @@ function createHttpClient(config) {
|
|
|
232
281
|
let ssrCookies = null;
|
|
233
282
|
let isLoggedOut = false;
|
|
234
283
|
const listeners = new Set;
|
|
235
|
-
const
|
|
284
|
+
const suppressUnauthorizedFor = config.suppressUnauthorizedFor ?? [];
|
|
236
285
|
const parseError = config.parseError ?? parseApiErrorBody;
|
|
237
286
|
function emit(event) {
|
|
238
287
|
for (const fn of listeners) {
|
|
@@ -271,7 +320,7 @@ function createHttpClient(config) {
|
|
|
271
320
|
async ({ request: request2, response }) => {
|
|
272
321
|
if (response.status === 401) {
|
|
273
322
|
const url = new URL(request2.url).pathname;
|
|
274
|
-
if (!isLoggedOut && !
|
|
323
|
+
if (!isLoggedOut && !suppressUnauthorizedFor.some((matches) => matches(url))) {
|
|
275
324
|
isLoggedOut = true;
|
|
276
325
|
emit({ type: "unauthorized" });
|
|
277
326
|
}
|
|
@@ -341,6 +390,7 @@ function createHttpClient(config) {
|
|
|
341
390
|
return {
|
|
342
391
|
baseUrl: config.baseUrl,
|
|
343
392
|
get: (url, options) => request("get", url, undefined, options),
|
|
393
|
+
head: (url, options) => request("head", url, undefined, options),
|
|
344
394
|
post: (url, data, options) => request("post", url, data, options),
|
|
345
395
|
put: (url, data, options) => request("put", url, data, options),
|
|
346
396
|
patch: (url, data, options) => request("patch", url, data, options),
|
|
@@ -379,6 +429,25 @@ function withOutput(endpoint, result) {
|
|
|
379
429
|
return result;
|
|
380
430
|
return result.then((value) => value === undefined ? undefined : schema.parse(value));
|
|
381
431
|
}
|
|
432
|
+
function contractEndpointMatchers(contract, endpointNames, contractConfig) {
|
|
433
|
+
const selected = endpointNames ? new Set(endpointNames) : null;
|
|
434
|
+
const matchers = [];
|
|
435
|
+
for (const [key, endpoint] of typedEntries(contract.endpoints)) {
|
|
436
|
+
if (selected && !selected.has(key))
|
|
437
|
+
continue;
|
|
438
|
+
if (endpoint.expose && !endpoint.expose.includes("HTTP")) {
|
|
439
|
+
if (selected) {
|
|
440
|
+
throw new Error(`Cannot create an HTTP route matcher for non-HTTP endpoint: ${String(key)}`);
|
|
441
|
+
}
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
matchers.push(createClientRouteMatcher(endpoint, contract.meta.prefix, contractConfig));
|
|
445
|
+
}
|
|
446
|
+
if (selected && matchers.length !== selected.size) {
|
|
447
|
+
throw new Error("Cannot create an HTTP route matcher for an unknown endpoint");
|
|
448
|
+
}
|
|
449
|
+
return matchers;
|
|
450
|
+
}
|
|
382
451
|
function createClient(contract, configOrClient, contractConfig) {
|
|
383
452
|
const client = {};
|
|
384
453
|
const makeMethod = isHttpAdapter(configOrClient) ? (endpoint) => createHttpMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig);
|
|
@@ -392,6 +461,32 @@ function createClient(contract, configOrClient, contractConfig) {
|
|
|
392
461
|
function createClients(contracts, configOrClient, contractConfig) {
|
|
393
462
|
return mapObject(contracts, (_key, contract) => createClient(contract, configOrClient, contractConfig));
|
|
394
463
|
}
|
|
464
|
+
function createScopedClients(contracts, configOrClient, scopeConfigs) {
|
|
465
|
+
const registry = {};
|
|
466
|
+
for (const [namespace, value] of Object.entries(contracts)) {
|
|
467
|
+
const list = Array.isArray(value) ? value : [value];
|
|
468
|
+
const client = {};
|
|
469
|
+
for (const contract of list) {
|
|
470
|
+
const scope = contract.meta.scope;
|
|
471
|
+
if (!scope) {
|
|
472
|
+
throw new Error(`Contract in client namespace "${namespace}" has no scope`);
|
|
473
|
+
}
|
|
474
|
+
const scopeConfig = Object.entries(scopeConfigs).find(([configuredScope]) => configuredScope === scope)?.[1];
|
|
475
|
+
if (!scopeConfig) {
|
|
476
|
+
throw new Error(`Missing client config for scope: ${scope}`);
|
|
477
|
+
}
|
|
478
|
+
const scoped = createClient(contract, configOrClient, scopeConfig);
|
|
479
|
+
for (const [methodName, method] of Object.entries(scoped)) {
|
|
480
|
+
if (Object.hasOwn(client, methodName)) {
|
|
481
|
+
throw new Error(`Client namespace "${namespace}" has duplicate method: ${methodName}`);
|
|
482
|
+
}
|
|
483
|
+
client[methodName] = method;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
registry[namespace] = client;
|
|
487
|
+
}
|
|
488
|
+
return registry;
|
|
489
|
+
}
|
|
395
490
|
function isHttpAdapter(value) {
|
|
396
491
|
return typeof value === "object" && "get" in value && typeof value.get === "function";
|
|
397
492
|
}
|
|
@@ -408,7 +503,7 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
408
503
|
if (!isMultipartFile(file)) {
|
|
409
504
|
throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
|
|
410
505
|
}
|
|
411
|
-
if (httpMethod === "get" || httpMethod === "delete") {
|
|
506
|
+
if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
|
|
412
507
|
throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
|
|
413
508
|
}
|
|
414
509
|
const formData = new FormData;
|
|
@@ -416,8 +511,8 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
416
511
|
appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
|
|
417
512
|
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
|
|
418
513
|
}
|
|
419
|
-
if (httpMethod === "get") {
|
|
420
|
-
return withOutput(endpoint, client
|
|
514
|
+
if (httpMethod === "get" || httpMethod === "head") {
|
|
515
|
+
return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(undefined, endpoint)));
|
|
421
516
|
}
|
|
422
517
|
if (httpMethod === "delete") {
|
|
423
518
|
return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
|
|
@@ -434,7 +529,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
434
529
|
...typeof config.headers === "function" ? config.headers() : config.headers
|
|
435
530
|
};
|
|
436
531
|
const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
|
|
437
|
-
const hasBody = endpoint.method !== "GET" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
|
|
532
|
+
const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
|
|
438
533
|
if (hasBody)
|
|
439
534
|
headers["Content-Type"] = "application/json";
|
|
440
535
|
if (endpoint.multipart && args) {
|
|
@@ -494,12 +589,14 @@ async function throwForErrorResponse(res, config, fallbackBody) {
|
|
|
494
589
|
function createUrlBuilder(contract, source, contractConfig) {
|
|
495
590
|
const builder = {};
|
|
496
591
|
for (const [key, endpoint] of typedEntries(contract.endpoints)) {
|
|
497
|
-
if (endpoint.method !== "GET" || endpoint.multipart)
|
|
498
|
-
continue;
|
|
499
592
|
if (endpoint.expose && !endpoint.expose.includes("HTTP"))
|
|
500
593
|
continue;
|
|
501
594
|
setClientMethod(builder, key, (args) => {
|
|
502
595
|
const plan = planClientRequest(endpoint, contract.meta.prefix, args ?? {}, contractConfig);
|
|
596
|
+
if (endpoint.method !== "GET" && endpoint.method !== "DELETE" && Object.keys(plan.remainingArgs).length > 0) {
|
|
597
|
+
const fields = Object.keys(plan.remainingArgs).join(", ");
|
|
598
|
+
throw new Error(`URL builder for ${endpoint.method} ${endpoint.path} received non-URL fields: ${fields}`);
|
|
599
|
+
}
|
|
503
600
|
return joinClientBaseUrl(source.baseUrl, plan.relativeUrl);
|
|
504
601
|
});
|
|
505
602
|
}
|
|
@@ -718,11 +815,13 @@ export {
|
|
|
718
815
|
createUrlBuilder,
|
|
719
816
|
createTraceContext,
|
|
720
817
|
createSocketIOClient,
|
|
818
|
+
createScopedClients,
|
|
721
819
|
createRetainedTopics,
|
|
722
820
|
createHttpClient,
|
|
723
821
|
createContractFactory,
|
|
724
822
|
createClients,
|
|
725
823
|
createClient,
|
|
824
|
+
contractEndpointMatchers,
|
|
726
825
|
conflict,
|
|
727
826
|
childSpan,
|
|
728
827
|
badRequest,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface TrailingWildcard {
|
|
2
|
+
name: string;
|
|
3
|
+
segmentIndex: number;
|
|
4
|
+
}
|
|
5
|
+
/** Parse and validate the route's single named terminal wildcard, if present. */
|
|
6
|
+
export declare function parseTrailingWildcard(path: string): TrailingWildcard | null;
|
|
7
|
+
//# sourceMappingURL=route-pattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-pattern.d.ts","sourceRoot":"","sources":["../../src/internal/route-pattern.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA2C3E"}
|
package/dist/node.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
createImplement,
|
|
4
4
|
createSocketIOServer,
|
|
5
5
|
implement
|
|
6
|
-
} from "./index-
|
|
7
|
-
import"./index-
|
|
6
|
+
} from "./index-7rbzbnnf.js";
|
|
7
|
+
import"./index-6jypn22c.js";
|
|
8
8
|
import"./index-x3fcszf8.js";
|
|
9
9
|
import {
|
|
10
10
|
AppError,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
notFound,
|
|
16
16
|
rateLimited,
|
|
17
17
|
unauthorized
|
|
18
|
-
} from "./index-
|
|
18
|
+
} from "./index-fyfk537k.js";
|
|
19
19
|
// src/server/node.ts
|
|
20
20
|
import { serve } from "srvx";
|
|
21
21
|
async function serveNode(config) {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
setRequestError,
|
|
17
17
|
setRequestUser,
|
|
18
18
|
wrapInRequestContext
|
|
19
|
-
} from "../index-
|
|
19
|
+
} from "../index-fyfk537k.js";
|
|
20
20
|
|
|
21
21
|
// src/observability/sanitize.ts
|
|
22
22
|
var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
|
|
@@ -1,54 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Declarative CRUD cache handlers for `createCacheBridge
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* from a small config.
|
|
6
|
-
*
|
|
7
|
-
* It patches stitchkit's own `Paginated<T>` list envelope (plain or a TanStack
|
|
8
|
-
* `InfiniteData` of it) and the entity's detail query. It does **not** flatten
|
|
9
|
-
* pages or expose a `useAllX` surface — flattening stays in the component (a
|
|
10
|
-
* deliberate boundary): this only keeps the cache correct.
|
|
11
|
-
*
|
|
12
|
-
* ```ts
|
|
13
|
-
* const handlers = createEntityCacheHandlers<Widget>({
|
|
14
|
-
* getId: (w) => w.id,
|
|
15
|
-
* listKey: ['widgets'],
|
|
16
|
-
* detailKey: (id) => ['widgets', id],
|
|
17
|
-
* });
|
|
18
|
-
* createCacheBridge({ socket, queryClient, handlers: {
|
|
19
|
-
* widgetCreated: handlers.created,
|
|
20
|
-
* widgetUpdated: handlers.updated,
|
|
21
|
-
* widgetDeleted: handlers.deleted,
|
|
22
|
-
* }});
|
|
23
|
-
* ```
|
|
2
|
+
* Declarative CRUD cache handlers for `createCacheBridge`. One config projects
|
|
3
|
+
* a full event entity into the cached list item and patches plain, paginated or
|
|
4
|
+
* infinite TanStack Query data without changing its envelope metadata.
|
|
24
5
|
*/
|
|
25
6
|
import type { QueryKey } from '@tanstack/react-query';
|
|
26
7
|
import type { CacheBridgeHandler } from './cache-bridge';
|
|
27
|
-
/** The `deleted` event may carry the whole entity or
|
|
28
|
-
export type DeletedPayload<
|
|
8
|
+
/** The `deleted` event may carry the whole entity or only its id. */
|
|
9
|
+
export type DeletedPayload<TData> = TData | {
|
|
29
10
|
id: string;
|
|
30
11
|
};
|
|
12
|
+
/** Typed event passed to dynamic list/detail query-key selectors. */
|
|
13
|
+
export type EntityCacheEvent<TData> = {
|
|
14
|
+
type: 'created';
|
|
15
|
+
entity: TData;
|
|
16
|
+
id: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'updated';
|
|
19
|
+
entity: TData;
|
|
20
|
+
id: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'deleted';
|
|
23
|
+
payload: DeletedPayload<TData>;
|
|
24
|
+
id: string;
|
|
25
|
+
};
|
|
26
|
+
/** A static cache key/prefix or an event-aware key factory. */
|
|
27
|
+
export type EntityCacheKey<TData> = QueryKey | ((event: EntityCacheEvent<TData>) => QueryKey);
|
|
28
|
+
export type EntityCacheListShape = 'array' | 'paginated' | 'infinite-array' | 'infinite-paginated';
|
|
29
|
+
/** List envelope, scoped key and explicit CRUD policies. */
|
|
30
|
+
export interface EntityCacheListConfig<TData, TListItem> {
|
|
31
|
+
/** Static query-key prefix or an event-aware scoped key factory. */
|
|
32
|
+
key: EntityCacheKey<TData>;
|
|
33
|
+
/** Edge/page used for a create or an inserted missing update. */
|
|
34
|
+
createAt: 'start' | 'end';
|
|
35
|
+
/** Explicit behavior when an update's id is absent from every cached page. */
|
|
36
|
+
updateMissing: 'skip' | 'insert';
|
|
37
|
+
/** Backend-equivalent ordering for each affected logical item array. */
|
|
38
|
+
compare?: (left: TListItem, right: TListItem) => number;
|
|
39
|
+
/** Cached data envelope; no runtime shape inference is performed. */
|
|
40
|
+
shape: EntityCacheListShape;
|
|
41
|
+
}
|
|
31
42
|
/** Config for `createEntityCacheHandlers`. */
|
|
32
|
-
export interface EntityCacheConfig<
|
|
33
|
-
/**
|
|
34
|
-
getId: (entity:
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
43
|
+
export interface EntityCacheConfig<TData, TListItem = TData> {
|
|
44
|
+
/** Canonical id from a full created/updated entity. */
|
|
45
|
+
getId: (entity: TData) => string;
|
|
46
|
+
/** Canonical id from the projected item stored in list caches. */
|
|
47
|
+
getListItemId: (item: TListItem) => string;
|
|
48
|
+
/** Project a full mutation entity into the list cache's item type. */
|
|
49
|
+
toListItem: (entity: TData) => TListItem;
|
|
50
|
+
/** Read a deleted id. Default: a string `.id`, otherwise `getId(payload)`. */
|
|
51
|
+
getDeletedId?: (payload: DeletedPayload<TData>) => string;
|
|
52
|
+
/** List envelope, key/prefix, insertion policy and optional ordering. */
|
|
53
|
+
list: EntityCacheListConfig<TData, TListItem>;
|
|
54
|
+
/** Static detail key or event-aware key factory. Omit to skip detail updates. */
|
|
55
|
+
detailKey?: EntityCacheKey<TData>;
|
|
41
56
|
}
|
|
42
|
-
/** The three handlers to wire onto a `createCacheBridge`
|
|
43
|
-
export interface EntityCacheHandlers<
|
|
44
|
-
created: CacheBridgeHandler<
|
|
45
|
-
updated: CacheBridgeHandler<
|
|
46
|
-
deleted: CacheBridgeHandler<DeletedPayload<
|
|
57
|
+
/** The three handlers to wire onto a `createCacheBridge` handlers map. */
|
|
58
|
+
export interface EntityCacheHandlers<TData> {
|
|
59
|
+
created: CacheBridgeHandler<TData>;
|
|
60
|
+
updated: CacheBridgeHandler<TData>;
|
|
61
|
+
deleted: CacheBridgeHandler<DeletedPayload<TData>>;
|
|
47
62
|
}
|
|
48
63
|
/**
|
|
49
|
-
* Build created
|
|
50
|
-
*
|
|
51
|
-
* the entity's detail key when available, else the list key.
|
|
64
|
+
* Build created/updated/deleted cache handlers over one declared list shape.
|
|
65
|
+
* Every event resolves its own scoped keys and applies the same fresh-echo gate.
|
|
52
66
|
*/
|
|
53
|
-
export declare function createEntityCacheHandlers<
|
|
67
|
+
export declare function createEntityCacheHandlers<TData, TListItem = TData>(config: EntityCacheConfig<TData, TListItem>): EntityCacheHandlers<TData>;
|
|
54
68
|
//# sourceMappingURL=entity-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-cache.d.ts","sourceRoot":"","sources":["../../src/react/entity-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"entity-cache.d.ts","sourceRoot":"","sources":["../../src/react/entity-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGpE,OAAO,KAAK,EAAsB,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAE7E,qEAAqE;AACrE,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI,KAAK,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,qEAAqE;AACrE,MAAM,MAAM,gBAAgB,CAAC,KAAK,IAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE,+DAA+D;AAC/D,MAAM,MAAM,cAAc,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC;AAE9F,MAAM,MAAM,oBAAoB,GAC5B,OAAO,GACP,WAAW,GACX,gBAAgB,GAChB,oBAAoB,CAAC;AAEzB,4DAA4D;AAC5D,MAAM,WAAW,qBAAqB,CAAC,KAAK,EAAE,SAAS;IACrD,oEAAoE;IACpE,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,8EAA8E;IAC9E,aAAa,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,MAAM,CAAC;IACxD,qEAAqE;IACrE,KAAK,EAAE,oBAAoB,CAAC;CAC7B;AAED,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK;IACzD,uDAAuD;IACvD,KAAK,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,CAAC;IACjC,kEAAkE;IAClE,aAAa,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IAC3C,sEAAsE;IACtE,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC;IACzC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC;IAC1D,yEAAyE;IACzE,IAAI,EAAE,qBAAqB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC9C,iFAAiF;IACjF,SAAS,CAAC,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;CACnC;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB,CAAC,KAAK;IACxC,OAAO,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,OAAO,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,OAAO,EAAE,kBAAkB,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;CACpD;AAiJD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,GAAG,KAAK,EAChE,MAAM,EAAE,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC,GAC1C,mBAAmB,CAAC,KAAK,CAAC,CAkD5B"}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { type CacheBridge, type CacheBridgeConfig, type CacheBridgeContext, type CacheBridgeHandler, type CacheBridgeHandlers, type CacheBridgeSocket, createCacheBridge, } from './react/cache-bridge';
|
|
2
2
|
export { type CursorQueryConfig, createCursorQuery } from './react/cursor-query';
|
|
3
|
-
export { createEntityCacheHandlers, type DeletedPayload, type EntityCacheConfig, type EntityCacheHandlers, } from './react/entity-cache';
|
|
3
|
+
export { createEntityCacheHandlers, type DeletedPayload, type EntityCacheConfig, type EntityCacheEvent, type EntityCacheHandlers, type EntityCacheKey, type EntityCacheListConfig, type EntityCacheListShape, } from './react/entity-cache';
|
|
4
4
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EACL,yBAAyB,EACzB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EACL,yBAAyB,EACzB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC"}
|