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.
Files changed (75) hide show
  1. package/dist/browser/client.d.ts.map +1 -1
  2. package/dist/browser/http.d.ts +9 -0
  3. package/dist/browser/http.d.ts.map +1 -1
  4. package/dist/browser/socket-io.d.ts.map +1 -1
  5. package/dist/cli.js +3 -3
  6. package/dist/contract/errors-factory.d.ts +18 -0
  7. package/dist/contract/errors-factory.d.ts.map +1 -0
  8. package/dist/contract/factory.d.ts +36 -0
  9. package/dist/contract/factory.d.ts.map +1 -0
  10. package/dist/contract/index.d.ts +2 -0
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +6 -1
  13. package/dist/contract/pagination.d.ts.map +1 -1
  14. package/dist/index-809wc1tt.js +18 -0
  15. package/dist/{index-tm7dqzxc.js → index-c7nyw0yt.js} +9 -9
  16. package/dist/{index-fwqnkc90.js → index-dzx781tm.js} +5 -41
  17. package/dist/index-khwedj16.js +40 -0
  18. package/dist/{index-ktn76xz0.js → index-ng2v2ts4.js} +17 -1
  19. package/dist/index-tje0q6gp.js +44 -0
  20. package/dist/{index-6k6y7wat.js → index-wpbn133x.js} +13 -50
  21. package/dist/{index-yzac2zbh.js → index-x62gnfsk.js} +50 -9
  22. package/dist/{index-akv8rk4w.js → index-xmncnw8z.js} +2 -2
  23. package/dist/{index-yv93m4yr.js → index-ynnh9x6h.js} +11 -29
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +141 -73
  27. package/dist/internal/base64url.d.ts +16 -0
  28. package/dist/internal/base64url.d.ts.map +1 -0
  29. package/dist/node.js +6 -4
  30. package/dist/observability/index.js +9 -7
  31. package/dist/react/entity-cache.d.ts +54 -0
  32. package/dist/react/entity-cache.d.ts.map +1 -0
  33. package/dist/react.d.ts +1 -0
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +74 -0
  36. package/dist/server/create.d.ts.map +1 -1
  37. package/dist/server/error-hook.d.ts +56 -0
  38. package/dist/server/error-hook.d.ts.map +1 -0
  39. package/dist/server/index.d.ts +2 -1
  40. package/dist/server/index.d.ts.map +1 -1
  41. package/dist/server/index.js +33 -7
  42. package/dist/server/middleware/auth.d.ts.map +1 -1
  43. package/dist/server/middleware/cors.d.ts +13 -0
  44. package/dist/server/middleware/cors.d.ts.map +1 -1
  45. package/dist/server/middleware/pkce.d.ts +0 -6
  46. package/dist/server/middleware/pkce.d.ts.map +1 -1
  47. package/dist/server/router.d.ts +0 -2
  48. package/dist/server/router.d.ts.map +1 -1
  49. package/dist/server/types.d.ts +0 -8
  50. package/dist/server/types.d.ts.map +1 -1
  51. package/dist/tools/agent.d.ts +2 -2
  52. package/dist/tools/agent.d.ts.map +1 -1
  53. package/dist/tools/cli-format.d.ts +2 -2
  54. package/dist/tools/cli-format.d.ts.map +1 -1
  55. package/dist/tools/cli.d.ts +2 -2
  56. package/dist/tools/cli.d.ts.map +1 -1
  57. package/dist/tools/execute.d.ts +7 -0
  58. package/dist/tools/execute.d.ts.map +1 -1
  59. package/dist/tools/list-names.d.ts +33 -0
  60. package/dist/tools/list-names.d.ts.map +1 -0
  61. package/dist/tools/mcp.d.ts +3 -3
  62. package/dist/tools/mcp.d.ts.map +1 -1
  63. package/dist/tools/mount.d.ts +3 -3
  64. package/dist/tools/mount.d.ts.map +1 -1
  65. package/dist/tools/oauth-metadata.d.ts.map +1 -1
  66. package/dist/tools/oauth-provider.d.ts.map +1 -1
  67. package/dist/tools/tool-logger.d.ts +31 -0
  68. package/dist/tools/tool-logger.d.ts.map +1 -0
  69. package/dist/tools/transports.d.ts +28 -0
  70. package/dist/tools/transports.d.ts.map +1 -0
  71. package/dist/tools.d.ts +4 -1
  72. package/dist/tools.d.ts.map +1 -1
  73. package/dist/tools.js +114 -25
  74. package/llms-full.txt +307 -7
  75. 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-ktn76xz0.js";
6
+ } from "./index-ng2v2ts4.js";
5
7
  import {
6
8
  isRecord,
7
9
  isUnsafeKey,
8
10
  safeJsonParse
9
- } from "./index-tm7dqzxc.js";
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(decodeBase64Url(headerB64)));
87
- payloadRaw = safeJsonParse(decoder.decode(decodeBase64Url(payloadB64)));
88
- signature = decodeBase64Url(signatureB64);
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 = encodeBase64Url(encoder.encode(JSON.stringify({ alg: "HS256", typ: "JWT" })));
148
- const payloadB64 = encodeBase64Url(encoder.encode(JSON.stringify(claims)));
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 = encodeBase64Url(new Uint8Array(signature));
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 base64UrlEncode(new Uint8Array(digest));
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
@@ -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;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,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-yzac2zbh.js";
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/internal/typed.ts
25
- function typedEntries(value) {
26
- return Object.entries(value);
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 isRecord(value) {
29
- return typeof value === "object" && value !== null && !Array.isArray(value);
44
+ function createTraceContext() {
45
+ return { traceId: randomHex(16), spanId: randomHex(8) };
30
46
  }
31
- function mapObject(source, mapper) {
32
- const result = {};
33
- for (const [key, value] of typedEntries(source)) {
34
- const mapped = mapper(key, value);
35
- if (mapped !== undefined)
36
- result[key] = mapped;
37
- }
38
- return result;
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.post(url, formData, withTimeout(undefined, endpoint.timeout));
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 searchParams = new URLSearchParams;
310
- for (const [k, v] of Object.entries(remaining)) {
311
- if (v === undefined || v === null)
312
- continue;
313
- if (isParamArray(v)) {
314
- for (const item of v)
315
- searchParams.append(k, String(item));
316
- } else if (typeof v !== "object") {
317
- searchParams.set(k, String(v));
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
- socket = io(config.url, {
484
- path: config.path ?? "/socket.io/",
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-6k6y7wat.js";
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-ktn76xz0.js";
17
- import"./index-fwqnkc90.js";
18
- import"./index-tm7dqzxc.js";
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-fwqnkc90.js";
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-tm7dqzxc.js";
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
@@ -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"}