stitchkit 0.17.0 → 0.19.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.d.ts.map +1 -1
- package/dist/browser/http.d.ts +9 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/contract/errors-factory.d.ts +18 -0
- package/dist/contract/errors-factory.d.ts.map +1 -0
- package/dist/contract/factory.d.ts +36 -0
- package/dist/contract/factory.d.ts.map +1 -0
- package/dist/contract/index.d.ts +2 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +6 -1
- package/dist/contract/pagination.d.ts.map +1 -1
- package/dist/index-809wc1tt.js +18 -0
- package/dist/{index-tm7dqzxc.js → index-c7nyw0yt.js} +9 -9
- package/dist/{index-fwqnkc90.js → index-dzx781tm.js} +5 -41
- package/dist/index-khwedj16.js +40 -0
- package/dist/{index-ktn76xz0.js → index-ng2v2ts4.js} +17 -1
- package/dist/index-tje0q6gp.js +44 -0
- package/dist/{index-6k6y7wat.js → index-wpbn133x.js} +13 -50
- package/dist/{index-yzac2zbh.js → index-x62gnfsk.js} +50 -9
- package/dist/{index-akv8rk4w.js → index-xmncnw8z.js} +2 -2
- package/dist/{index-yv93m4yr.js → index-ynnh9x6h.js} +11 -29
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -73
- package/dist/internal/base64url.d.ts +16 -0
- package/dist/internal/base64url.d.ts.map +1 -0
- package/dist/node.js +6 -4
- package/dist/observability/index.js +9 -7
- package/dist/react/entity-cache.d.ts +54 -0
- package/dist/react/entity-cache.d.ts.map +1 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +74 -0
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +56 -0
- package/dist/server/error-hook.d.ts.map +1 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +33 -7
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +13 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/middleware/pkce.d.ts +0 -6
- package/dist/server/middleware/pkce.d.ts.map +1 -1
- package/dist/server/router.d.ts +0 -2
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/types.d.ts +0 -8
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +2 -2
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli-format.d.ts +2 -2
- package/dist/tools/cli-format.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +2 -2
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +7 -0
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +33 -0
- package/dist/tools/list-names.d.ts.map +1 -0
- package/dist/tools/mcp.d.ts +3 -3
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +3 -3
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/oauth-metadata.d.ts.map +1 -1
- package/dist/tools/oauth-provider.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts +31 -0
- package/dist/tools/tool-logger.d.ts.map +1 -0
- package/dist/tools/transports.d.ts +28 -0
- package/dist/tools/transports.d.ts.map +1 -0
- package/dist/tools.d.ts +4 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +114 -25
- package/llms-full.txt +307 -7
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
+
base64UrlToBytes,
|
|
3
|
+
bytesToBase64Url,
|
|
2
4
|
forbidden,
|
|
3
5
|
unauthorized
|
|
4
|
-
} from "./index-
|
|
6
|
+
} from "./index-ng2v2ts4.js";
|
|
5
7
|
import {
|
|
6
8
|
isRecord,
|
|
7
9
|
isUnsafeKey,
|
|
8
10
|
safeJsonParse
|
|
9
|
-
} from "./index-
|
|
11
|
+
} from "./index-c7nyw0yt.js";
|
|
10
12
|
|
|
11
13
|
// src/server/middleware/cookies.ts
|
|
12
14
|
function parseCookies(header) {
|
|
@@ -62,14 +64,6 @@ function defineCookie(config) {
|
|
|
62
64
|
|
|
63
65
|
// src/server/middleware/auth.ts
|
|
64
66
|
var MAX_TOKEN_BYTES = 8192;
|
|
65
|
-
function decodeBase64Url(segment) {
|
|
66
|
-
if (!/^[A-Za-z0-9_-]*$/.test(segment) || segment.length % 4 === 1) {
|
|
67
|
-
throw new Error("invalid base64url segment");
|
|
68
|
-
}
|
|
69
|
-
const b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
|
|
70
|
-
const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, "=");
|
|
71
|
-
return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
72
|
-
}
|
|
73
67
|
async function verifyJwt(token, secret, options = {}) {
|
|
74
68
|
if (!secret)
|
|
75
69
|
throw new Error("verifyJwt: a non-empty secret is required");
|
|
@@ -83,9 +77,9 @@ async function verifyJwt(token, secret, options = {}) {
|
|
|
83
77
|
let signature;
|
|
84
78
|
try {
|
|
85
79
|
const decoder = new TextDecoder;
|
|
86
|
-
header = JSON.parse(decoder.decode(
|
|
87
|
-
payloadRaw = safeJsonParse(decoder.decode(
|
|
88
|
-
signature =
|
|
80
|
+
header = JSON.parse(decoder.decode(base64UrlToBytes(headerB64)));
|
|
81
|
+
payloadRaw = safeJsonParse(decoder.decode(base64UrlToBytes(payloadB64)));
|
|
82
|
+
signature = base64UrlToBytes(signatureB64);
|
|
89
83
|
} catch {
|
|
90
84
|
throw unauthorized("Malformed token");
|
|
91
85
|
}
|
|
@@ -125,12 +119,6 @@ async function verifyJwt(token, secret, options = {}) {
|
|
|
125
119
|
}
|
|
126
120
|
return payload;
|
|
127
121
|
}
|
|
128
|
-
function encodeBase64Url(bytes) {
|
|
129
|
-
let binary = "";
|
|
130
|
-
for (const byte of bytes)
|
|
131
|
-
binary += String.fromCharCode(byte);
|
|
132
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
133
|
-
}
|
|
134
122
|
async function signJwt(payload, secret, options = {}) {
|
|
135
123
|
if (!secret)
|
|
136
124
|
throw new Error("signJwt: a non-empty secret is required");
|
|
@@ -144,12 +132,12 @@ async function signJwt(payload, secret, options = {}) {
|
|
|
144
132
|
...options.subject !== undefined && { sub: options.subject }
|
|
145
133
|
};
|
|
146
134
|
const encoder = new TextEncoder;
|
|
147
|
-
const headerB64 =
|
|
148
|
-
const payloadB64 =
|
|
135
|
+
const headerB64 = bytesToBase64Url(encoder.encode(JSON.stringify({ alg: "HS256", typ: "JWT" })));
|
|
136
|
+
const payloadB64 = bytesToBase64Url(encoder.encode(JSON.stringify(claims)));
|
|
149
137
|
const signingInput = `${headerB64}.${payloadB64}`;
|
|
150
138
|
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
151
139
|
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(signingInput));
|
|
152
|
-
const signatureB64 =
|
|
140
|
+
const signatureB64 = bytesToBase64Url(new Uint8Array(signature));
|
|
153
141
|
return `${signingInput}.${signatureB64}`;
|
|
154
142
|
}
|
|
155
143
|
function extractToken(req, cookieName) {
|
|
@@ -197,15 +185,9 @@ function createBearerResolver(config) {
|
|
|
197
185
|
}
|
|
198
186
|
|
|
199
187
|
// src/server/middleware/pkce.ts
|
|
200
|
-
function base64UrlEncode(bytes) {
|
|
201
|
-
let binary = "";
|
|
202
|
-
for (const byte of bytes)
|
|
203
|
-
binary += String.fromCharCode(byte);
|
|
204
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
205
|
-
}
|
|
206
188
|
async function deriveCodeChallenge(verifier) {
|
|
207
189
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
208
|
-
return
|
|
190
|
+
return bytesToBase64Url(new Uint8Array(digest));
|
|
209
191
|
}
|
|
210
192
|
async function verifyPkce(verifier, challenge) {
|
|
211
193
|
if (!verifier || !challenge)
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export type { SocketEventMap, SocketIOClient, SocketIOClientConfig, } from './br
|
|
|
4
4
|
export { createSocketIOClient } from './browser/socket-io';
|
|
5
5
|
export { type ParseSSEOptions, parseSSE } from './browser/stream';
|
|
6
6
|
export * from './contract';
|
|
7
|
+
export { childSpan, createTraceContext, formatTraceparent, parseTraceparent, type TraceContext, } from './observability/trace';
|
|
7
8
|
export { createRetainedTopics, type RetainedTopics } from './retained';
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
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,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,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;
|
|
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,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,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
|
@@ -5,8 +5,10 @@ import {
|
|
|
5
5
|
appError,
|
|
6
6
|
badRequest,
|
|
7
7
|
conflict,
|
|
8
|
+
createContractFactory,
|
|
8
9
|
decodeCursor,
|
|
9
10
|
defineContract,
|
|
11
|
+
defineErrors,
|
|
10
12
|
encodeCursor,
|
|
11
13
|
forbidden,
|
|
12
14
|
isStitchErrorCode,
|
|
@@ -14,32 +16,58 @@ import {
|
|
|
14
16
|
paginatedSchema,
|
|
15
17
|
rateLimited,
|
|
16
18
|
unauthorized
|
|
17
|
-
} from "./index-
|
|
19
|
+
} from "./index-x62gnfsk.js";
|
|
20
|
+
import {
|
|
21
|
+
isRecord,
|
|
22
|
+
mapObject,
|
|
23
|
+
typedEntries
|
|
24
|
+
} from "./index-809wc1tt.js";
|
|
18
25
|
|
|
19
26
|
// src/internal/http-input.ts
|
|
20
27
|
function inputIsQuery(method) {
|
|
21
28
|
return method === "GET" || method === "DELETE";
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
// src/
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
// src/browser/http.ts
|
|
32
|
+
import ky, { isHTTPError } from "ky";
|
|
33
|
+
|
|
34
|
+
// src/observability/trace.ts
|
|
35
|
+
var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
|
|
36
|
+
function randomHex(bytes) {
|
|
37
|
+
const arr = new Uint8Array(bytes);
|
|
38
|
+
crypto.getRandomValues(arr);
|
|
39
|
+
let hex = "";
|
|
40
|
+
for (const byte of arr)
|
|
41
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
42
|
+
return hex;
|
|
27
43
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
44
|
+
function createTraceContext() {
|
|
45
|
+
return { traceId: randomHex(16), spanId: randomHex(8) };
|
|
30
46
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
function parseTraceparent(header) {
|
|
48
|
+
if (!header)
|
|
49
|
+
return null;
|
|
50
|
+
const match = TRACEPARENT_RE.exec(header.trim());
|
|
51
|
+
if (!match?.[1] || !match[2])
|
|
52
|
+
return null;
|
|
53
|
+
const traceId = match[1].toLowerCase();
|
|
54
|
+
const parentSpanId = match[2].toLowerCase();
|
|
55
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
|
|
56
|
+
return null;
|
|
57
|
+
return { traceId, spanId: randomHex(8), parentSpanId };
|
|
58
|
+
}
|
|
59
|
+
function formatTraceparent(ctx) {
|
|
60
|
+
return `00-${ctx.traceId}-${ctx.spanId}-01`;
|
|
61
|
+
}
|
|
62
|
+
function childSpan(parent) {
|
|
63
|
+
return {
|
|
64
|
+
traceId: parent.traceId,
|
|
65
|
+
spanId: randomHex(8),
|
|
66
|
+
parentSpanId: parent.spanId
|
|
67
|
+
};
|
|
39
68
|
}
|
|
40
69
|
|
|
41
70
|
// src/browser/http.ts
|
|
42
|
-
import ky, { isHTTPError } from "ky";
|
|
43
71
|
class ApiError extends Error {
|
|
44
72
|
code;
|
|
45
73
|
status;
|
|
@@ -104,6 +132,9 @@ function createHttpClient(config) {
|
|
|
104
132
|
request2.headers.set(key, value);
|
|
105
133
|
}
|
|
106
134
|
}
|
|
135
|
+
if (config.trace && !request2.headers.has("traceparent")) {
|
|
136
|
+
request2.headers.set("traceparent", formatTraceparent(createTraceContext()));
|
|
137
|
+
}
|
|
107
138
|
}
|
|
108
139
|
],
|
|
109
140
|
afterResponse: [
|
|
@@ -203,10 +234,16 @@ function withTimeout(options, timeout) {
|
|
|
203
234
|
return options;
|
|
204
235
|
return { ...options, timeout };
|
|
205
236
|
}
|
|
237
|
+
function withOutput(endpoint, result) {
|
|
238
|
+
const schema = endpoint.output;
|
|
239
|
+
if (!schema)
|
|
240
|
+
return result;
|
|
241
|
+
return result.then((value) => value === undefined ? undefined : schema.parse(value));
|
|
242
|
+
}
|
|
206
243
|
function isParamArray(value) {
|
|
207
244
|
return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
|
|
208
245
|
}
|
|
209
|
-
function collectQueryParams(args, skipKeys) {
|
|
246
|
+
function collectQueryParams(args, skipKeys, endpoint) {
|
|
210
247
|
const params = {};
|
|
211
248
|
let hasParams = false;
|
|
212
249
|
for (const [key, value] of Object.entries(args)) {
|
|
@@ -215,7 +252,10 @@ function collectQueryParams(args, skipKeys) {
|
|
|
215
252
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
|
|
216
253
|
params[key] = value;
|
|
217
254
|
hasParams = true;
|
|
255
|
+
continue;
|
|
218
256
|
}
|
|
257
|
+
const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
|
|
258
|
+
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).");
|
|
219
259
|
}
|
|
220
260
|
return hasParams ? params : undefined;
|
|
221
261
|
}
|
|
@@ -266,18 +306,21 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
266
306
|
if (!isMultipartFile(file)) {
|
|
267
307
|
throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
|
|
268
308
|
}
|
|
309
|
+
if (httpMethod === "get" || httpMethod === "delete") {
|
|
310
|
+
throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
|
|
311
|
+
}
|
|
269
312
|
const formData = new FormData;
|
|
270
313
|
appendMultipartFile(formData, endpoint.multipart, file);
|
|
271
314
|
appendFormFields(formData, firstArg, new Set([...prefixKeys, endpoint.multipart]));
|
|
272
|
-
return client
|
|
315
|
+
return withOutput(endpoint, client[httpMethod](url, formData, withTimeout(undefined, endpoint.timeout)));
|
|
273
316
|
}
|
|
274
317
|
if (isGet) {
|
|
275
|
-
const params = collectQueryParams(firstArg, prefixKeys);
|
|
276
|
-
return client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
|
|
318
|
+
const params = collectQueryParams(firstArg, prefixKeys, endpoint);
|
|
319
|
+
return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout)));
|
|
277
320
|
}
|
|
278
321
|
if (httpMethod === "delete") {
|
|
279
|
-
const params = collectQueryParams(firstArg, prefixKeys);
|
|
280
|
-
return client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
|
|
322
|
+
const params = collectQueryParams(firstArg, prefixKeys, endpoint);
|
|
323
|
+
return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout)));
|
|
281
324
|
}
|
|
282
325
|
const payload = {};
|
|
283
326
|
for (const [key, value] of Object.entries(firstArg)) {
|
|
@@ -285,7 +328,7 @@ function createHttpMethod(endpoint, prefix, client, config) {
|
|
|
285
328
|
payload[key] = value;
|
|
286
329
|
}
|
|
287
330
|
}
|
|
288
|
-
return client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint.timeout));
|
|
331
|
+
return withOutput(endpoint, client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint.timeout)));
|
|
289
332
|
};
|
|
290
333
|
}
|
|
291
334
|
function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
@@ -302,23 +345,24 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
302
345
|
Accept: "application/json",
|
|
303
346
|
...typeof config.headers === "function" ? config.headers() : config.headers
|
|
304
347
|
};
|
|
348
|
+
const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
|
|
305
349
|
const isQuery = inputIsQuery(endpoint.method);
|
|
306
350
|
const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
|
|
307
351
|
if (isQuery && args) {
|
|
308
352
|
const remaining = stripParams(args, endpoint.path, prefixKeys);
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
353
|
+
const params = collectQueryParams(remaining, new Set, endpoint);
|
|
354
|
+
if (params) {
|
|
355
|
+
const searchParams = new URLSearchParams;
|
|
356
|
+
for (const [k, v] of Object.entries(params)) {
|
|
357
|
+
if (Array.isArray(v)) {
|
|
358
|
+
for (const item of v)
|
|
359
|
+
searchParams.append(k, String(item));
|
|
360
|
+
} else {
|
|
361
|
+
searchParams.set(k, String(v));
|
|
362
|
+
}
|
|
318
363
|
}
|
|
319
|
-
}
|
|
320
|
-
if (searchParams.size > 0)
|
|
321
364
|
url += `?${searchParams}`;
|
|
365
|
+
}
|
|
322
366
|
}
|
|
323
367
|
if (hasBody)
|
|
324
368
|
headers["Content-Type"] = "application/json";
|
|
@@ -334,7 +378,8 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
334
378
|
method: endpoint.method,
|
|
335
379
|
headers,
|
|
336
380
|
credentials: config.credentials,
|
|
337
|
-
body: formData
|
|
381
|
+
body: formData,
|
|
382
|
+
signal
|
|
338
383
|
});
|
|
339
384
|
if (!res2.ok) {
|
|
340
385
|
await throwForErrorResponse(res2, config, null);
|
|
@@ -348,6 +393,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
|
|
|
348
393
|
method: endpoint.method,
|
|
349
394
|
headers,
|
|
350
395
|
credentials: config.credentials,
|
|
396
|
+
signal,
|
|
351
397
|
...hasBody && {
|
|
352
398
|
body: JSON.stringify(stripParams(hasBody, endpoint.path, prefixKeys))
|
|
353
399
|
}
|
|
@@ -417,9 +463,6 @@ function stripParams(args, path, extra) {
|
|
|
417
463
|
}
|
|
418
464
|
return result;
|
|
419
465
|
}
|
|
420
|
-
// src/browser/socket-io.ts
|
|
421
|
-
import { io } from "socket.io-client";
|
|
422
|
-
|
|
423
466
|
// src/retained.ts
|
|
424
467
|
function createRetainedTopics() {
|
|
425
468
|
const last = {};
|
|
@@ -447,6 +490,17 @@ function createRetainedTopics() {
|
|
|
447
490
|
}
|
|
448
491
|
|
|
449
492
|
// src/browser/socket-io.ts
|
|
493
|
+
var SOCKET_IO_CLIENT = "socket.io-client";
|
|
494
|
+
var ioLoader = null;
|
|
495
|
+
function loadIo() {
|
|
496
|
+
if (!ioLoader) {
|
|
497
|
+
ioLoader = import(SOCKET_IO_CLIENT).then((mod) => mod.io, (cause) => {
|
|
498
|
+
ioLoader = null;
|
|
499
|
+
throw new Error('stitchkit: createSocketIOClient needs the "socket.io-client" peer — install it (e.g. `bun add socket.io-client`).', { cause });
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return ioLoader;
|
|
503
|
+
}
|
|
450
504
|
function toIoAuth(auth) {
|
|
451
505
|
if (typeof auth !== "function")
|
|
452
506
|
return auth;
|
|
@@ -456,6 +510,7 @@ function toIoAuth(auth) {
|
|
|
456
510
|
}
|
|
457
511
|
function createSocketIOClient(config) {
|
|
458
512
|
let socket = null;
|
|
513
|
+
let desiredConnected = false;
|
|
459
514
|
let reconnectTimer = null;
|
|
460
515
|
const serverDisconnectDelay = config.reconnectOnServerDisconnect ?? 1000;
|
|
461
516
|
const connectionListeners = new Set;
|
|
@@ -473,50 +528,57 @@ function createSocketIOClient(config) {
|
|
|
473
528
|
reconnectTimer = null;
|
|
474
529
|
}
|
|
475
530
|
}
|
|
531
|
+
function openSocket(io) {
|
|
532
|
+
if (!desiredConnected || socket)
|
|
533
|
+
return;
|
|
534
|
+
socket = io(config.url, {
|
|
535
|
+
path: config.path ?? "/socket.io/",
|
|
536
|
+
withCredentials: config.withCredentials ?? true,
|
|
537
|
+
auth: toIoAuth(config.auth),
|
|
538
|
+
...config.query && { query: config.query },
|
|
539
|
+
...config.extraHeaders && { extraHeaders: config.extraHeaders },
|
|
540
|
+
transports: config.transports ?? ["websocket", "polling"],
|
|
541
|
+
autoConnect: false,
|
|
542
|
+
reconnection: true,
|
|
543
|
+
reconnectionAttempts: config.reconnectionAttempts ?? Infinity,
|
|
544
|
+
reconnectionDelay: config.reconnectionDelay ?? 1000,
|
|
545
|
+
reconnectionDelayMax: config.reconnectionDelayMax ?? 5000,
|
|
546
|
+
timeout: config.timeout ?? 20000
|
|
547
|
+
});
|
|
548
|
+
socket.on("connect", () => notifyConnection(true));
|
|
549
|
+
socket.on("disconnect", (reason) => {
|
|
550
|
+
notifyConnection(false, reason);
|
|
551
|
+
if (reason === "io server disconnect" && serverDisconnectDelay !== false) {
|
|
552
|
+
const current = socket;
|
|
553
|
+
clearReconnectTimer();
|
|
554
|
+
reconnectTimer = setTimeout(() => {
|
|
555
|
+
reconnectTimer = null;
|
|
556
|
+
if (socket === current && current && !current.connected)
|
|
557
|
+
current.connect();
|
|
558
|
+
}, serverDisconnectDelay);
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
if (retained) {
|
|
562
|
+
for (const name of retainNames) {
|
|
563
|
+
socket.on(name, (payload) => retained.record(name, payload));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
for (const attach of subscriptions)
|
|
567
|
+
attach(socket);
|
|
568
|
+
socket.connect();
|
|
569
|
+
}
|
|
476
570
|
return {
|
|
477
571
|
get connected() {
|
|
478
572
|
return socket?.connected ?? false;
|
|
479
573
|
},
|
|
480
574
|
connect() {
|
|
481
|
-
if (socket)
|
|
575
|
+
if (socket || desiredConnected)
|
|
482
576
|
return;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
withCredentials: config.withCredentials ?? true,
|
|
486
|
-
auth: toIoAuth(config.auth),
|
|
487
|
-
...config.query && { query: config.query },
|
|
488
|
-
...config.extraHeaders && { extraHeaders: config.extraHeaders },
|
|
489
|
-
transports: config.transports ?? ["websocket", "polling"],
|
|
490
|
-
autoConnect: false,
|
|
491
|
-
reconnection: true,
|
|
492
|
-
reconnectionAttempts: config.reconnectionAttempts ?? Infinity,
|
|
493
|
-
reconnectionDelay: config.reconnectionDelay ?? 1000,
|
|
494
|
-
reconnectionDelayMax: config.reconnectionDelayMax ?? 5000,
|
|
495
|
-
timeout: config.timeout ?? 20000
|
|
496
|
-
});
|
|
497
|
-
socket.on("connect", () => notifyConnection(true));
|
|
498
|
-
socket.on("disconnect", (reason) => {
|
|
499
|
-
notifyConnection(false, reason);
|
|
500
|
-
if (reason === "io server disconnect" && serverDisconnectDelay !== false) {
|
|
501
|
-
const current = socket;
|
|
502
|
-
clearReconnectTimer();
|
|
503
|
-
reconnectTimer = setTimeout(() => {
|
|
504
|
-
reconnectTimer = null;
|
|
505
|
-
if (socket === current && current && !current.connected)
|
|
506
|
-
current.connect();
|
|
507
|
-
}, serverDisconnectDelay);
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
if (retained) {
|
|
511
|
-
for (const name of retainNames) {
|
|
512
|
-
socket.on(name, (payload) => retained.record(name, payload));
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
for (const attach of subscriptions)
|
|
516
|
-
attach(socket);
|
|
517
|
-
socket.connect();
|
|
577
|
+
desiredConnected = true;
|
|
578
|
+
loadIo().then(openSocket);
|
|
518
579
|
},
|
|
519
580
|
disconnect() {
|
|
581
|
+
desiredConnected = false;
|
|
520
582
|
clearReconnectTimer();
|
|
521
583
|
if (!socket)
|
|
522
584
|
return;
|
|
@@ -596,20 +658,26 @@ async function* parseSSE(response, options) {
|
|
|
596
658
|
export {
|
|
597
659
|
unauthorized,
|
|
598
660
|
rateLimited,
|
|
661
|
+
parseTraceparent,
|
|
599
662
|
parseSSE,
|
|
600
663
|
paginatedSchema,
|
|
601
664
|
notFound,
|
|
602
665
|
isStitchErrorCode,
|
|
666
|
+
formatTraceparent,
|
|
603
667
|
forbidden,
|
|
604
668
|
encodeCursor,
|
|
669
|
+
defineErrors,
|
|
605
670
|
defineContract,
|
|
606
671
|
decodeCursor,
|
|
672
|
+
createTraceContext,
|
|
607
673
|
createSocketIOClient,
|
|
608
674
|
createRetainedTopics,
|
|
609
675
|
createHttpClient,
|
|
676
|
+
createContractFactory,
|
|
610
677
|
createClients,
|
|
611
678
|
createClient,
|
|
612
679
|
conflict,
|
|
680
|
+
childSpan,
|
|
613
681
|
badRequest,
|
|
614
682
|
appError,
|
|
615
683
|
STITCH_ERROR_STATUS,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* base64url over raw bytes — the one codec shared by cursor pagination, JWT
|
|
3
|
+
* segments, OAuth-token minting and PKCE. `btoa`/`atob` based (not Node
|
|
4
|
+
* `Buffer`), so it works identically in the server, the browser and the typed
|
|
5
|
+
* client. Kept byte-level: string callers compose with `TextEncoder` /
|
|
6
|
+
* `TextDecoder` themselves (pagination round-trips UTF-8; JWT/PKCE are binary).
|
|
7
|
+
*/
|
|
8
|
+
/** Encode raw bytes as an unpadded base64url string. */
|
|
9
|
+
export declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
10
|
+
/**
|
|
11
|
+
* Decode a base64url string to bytes. A character outside the alphabet, or a
|
|
12
|
+
* length that no base64 string can have (`% 4 === 1`), is malformed — reject it
|
|
13
|
+
* before `atob` rather than emit silent garbage.
|
|
14
|
+
*/
|
|
15
|
+
export declare function base64UrlToBytes(segment: string): Uint8Array<ArrayBuffer>;
|
|
16
|
+
//# sourceMappingURL=base64url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../src/internal/base64url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wDAAwD;AACxD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAI1D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAOzE"}
|
package/dist/node.js
CHANGED
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
createImplement,
|
|
4
4
|
createSocketIOServer,
|
|
5
5
|
implement
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-wpbn133x.js";
|
|
7
|
+
import"./index-tje0q6gp.js";
|
|
7
8
|
import {
|
|
8
9
|
AppError,
|
|
9
10
|
appError,
|
|
@@ -13,9 +14,10 @@ import {
|
|
|
13
14
|
notFound,
|
|
14
15
|
rateLimited,
|
|
15
16
|
unauthorized
|
|
16
|
-
} from "./index-
|
|
17
|
-
import"./index-
|
|
18
|
-
import"./index-
|
|
17
|
+
} from "./index-ng2v2ts4.js";
|
|
18
|
+
import"./index-dzx781tm.js";
|
|
19
|
+
import"./index-khwedj16.js";
|
|
20
|
+
import"./index-c7nyw0yt.js";
|
|
19
21
|
// src/server/node.ts
|
|
20
22
|
import { serve } from "srvx";
|
|
21
23
|
async function serveNode(config) {
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
childSpan,
|
|
3
|
-
createTraceContext,
|
|
4
|
-
formatTraceparent,
|
|
5
2
|
getRequestContext,
|
|
6
3
|
getTraceId,
|
|
7
4
|
getUserId,
|
|
8
|
-
parseTraceparent,
|
|
9
|
-
resolveTraceContext,
|
|
10
5
|
runWithRequestContext,
|
|
11
6
|
setRequestDimensions,
|
|
12
7
|
setRequestEndpoint,
|
|
13
8
|
setRequestError,
|
|
14
9
|
setRequestUser,
|
|
15
10
|
wrapInRequestContext
|
|
16
|
-
} from "../index-
|
|
11
|
+
} from "../index-dzx781tm.js";
|
|
12
|
+
import {
|
|
13
|
+
childSpan,
|
|
14
|
+
createTraceContext,
|
|
15
|
+
formatTraceparent,
|
|
16
|
+
parseTraceparent,
|
|
17
|
+
resolveTraceContext
|
|
18
|
+
} from "../index-khwedj16.js";
|
|
17
19
|
import {
|
|
18
20
|
isRecord,
|
|
19
21
|
isUnsafeKey
|
|
20
|
-
} from "../index-
|
|
22
|
+
} from "../index-c7nyw0yt.js";
|
|
21
23
|
|
|
22
24
|
// src/observability/sanitize.ts
|
|
23
25
|
var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative CRUD cache handlers for `createCacheBridge` — the created /
|
|
3
|
+
* updated / deleted socket events of one entity, patched into its list and
|
|
4
|
+
* detail queries. Every project hand-rolls this same updater; this builds it
|
|
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
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type { QueryKey } from '@tanstack/react-query';
|
|
26
|
+
import type { CacheBridgeHandler } from './cache-bridge';
|
|
27
|
+
/** The `deleted` event may carry the whole entity or just its id. */
|
|
28
|
+
export type DeletedPayload<T> = T | {
|
|
29
|
+
id: string;
|
|
30
|
+
};
|
|
31
|
+
/** Config for `createEntityCacheHandlers`. */
|
|
32
|
+
export interface EntityCacheConfig<T> {
|
|
33
|
+
/** Read the entity's id — the identity used to match, replace and remove. */
|
|
34
|
+
getId: (entity: T) => string;
|
|
35
|
+
/** Read the id from a `deleted` payload (entity or `{ id }`). Default `getId` / `.id`. */
|
|
36
|
+
getDeletedId?: (payload: DeletedPayload<T>) => string;
|
|
37
|
+
/** Query key (or prefix) of the list(s) to patch — matched by partial equality. */
|
|
38
|
+
listKey: QueryKey;
|
|
39
|
+
/** Build the detail query key for an id. Omit to skip detail-cache updates. */
|
|
40
|
+
detailKey?: (id: string) => QueryKey;
|
|
41
|
+
}
|
|
42
|
+
/** The three handlers to wire onto a `createCacheBridge` `handlers` map. */
|
|
43
|
+
export interface EntityCacheHandlers<T> {
|
|
44
|
+
created: CacheBridgeHandler<T>;
|
|
45
|
+
updated: CacheBridgeHandler<T>;
|
|
46
|
+
deleted: CacheBridgeHandler<DeletedPayload<T>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build created / updated / deleted cache handlers for one entity. Each skips a
|
|
50
|
+
* stale socket echo of a change the client just made (`ctx.isFresh`), keyed on
|
|
51
|
+
* the entity's detail key when available, else the list key.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createEntityCacheHandlers<T>(config: EntityCacheConfig<T>): EntityCacheHandlers<T>;
|
|
54
|
+
//# sourceMappingURL=entity-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-cache.d.ts","sourceRoot":"","sources":["../../src/react/entity-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGpE,OAAO,KAAK,EAAsB,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAK7E,qEAAqE;AACrE,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,6EAA6E;IAC7E,KAAK,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IAC7B,0FAA0F;IAC1F,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC;IACtD,mFAAmF;IACnF,OAAO,EAAE,QAAQ,CAAC;IAClB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,QAAQ,CAAC;CACtC;AAED,4EAA4E;AAC5E,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAC/B,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC;IAC/B,OAAO,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;CAChD;AAyBD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC3B,mBAAmB,CAAC,CAAC,CAAC,CAuCxB"}
|
package/dist/react.d.ts
CHANGED
|
@@ -1,3 +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
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"}
|
|
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,GACzB,MAAM,sBAAsB,CAAC"}
|