stitchkit 0.45.0 → 0.46.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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/http.d.ts.map +1 -1
  3. package/dist/browser/socket-io.d.ts +3 -1
  4. package/dist/browser/socket-io.d.ts.map +1 -1
  5. package/dist/cli.js +3 -4
  6. package/dist/contract/errors.d.ts +1 -0
  7. package/dist/contract/errors.d.ts.map +1 -1
  8. package/dist/contract/index.js +2 -2
  9. package/dist/{index-yh0p5ddn.js → index-5s8b7z6q.js} +31 -11
  10. package/dist/{index-mvjyq9h9.js → index-62pqb23z.js} +309 -144
  11. package/dist/index-c40tkxcd.js +181 -0
  12. package/dist/{index-r6czv7yt.js → index-pwyedf7b.js} +4 -3
  13. package/dist/{index-6jypn22c.js → index-r1qp4rve.js} +9 -1
  14. package/dist/{index-s4qsmgwe.js → index-tss6bk5c.js} +4 -1
  15. package/dist/{index-36f1xkdb.js → index-w1s873ng.js} +2 -2
  16. package/dist/index-yxpe3phd.js +63 -0
  17. package/dist/{index-w0mec79h.js → index-zwqty9zf.js} +261 -281
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +129 -51
  21. package/dist/internal/errors.d.ts.map +1 -1
  22. package/dist/internal/secure-fetch.d.ts +23 -3
  23. package/dist/internal/secure-fetch.d.ts.map +1 -1
  24. package/dist/internal/typed.d.ts +7 -0
  25. package/dist/internal/typed.d.ts.map +1 -1
  26. package/dist/internal/within-dir.d.ts +6 -0
  27. package/dist/internal/within-dir.d.ts.map +1 -1
  28. package/dist/logger.d.ts +8 -0
  29. package/dist/logger.d.ts.map +1 -0
  30. package/dist/node.js +3 -4
  31. package/dist/observability/audit.d.ts.map +1 -1
  32. package/dist/observability/event.d.ts +7 -0
  33. package/dist/observability/event.d.ts.map +1 -1
  34. package/dist/observability/index.js +54 -125
  35. package/dist/observability/sanitize.d.ts +8 -1
  36. package/dist/observability/sanitize.d.ts.map +1 -1
  37. package/dist/observability/trace.d.ts +3 -1
  38. package/dist/observability/trace.d.ts.map +1 -1
  39. package/dist/react/cache-bridge.d.ts +4 -0
  40. package/dist/react/cache-bridge.d.ts.map +1 -1
  41. package/dist/react.js +46 -5
  42. package/dist/realtime/contract.d.ts +4 -1
  43. package/dist/realtime/contract.d.ts.map +1 -1
  44. package/dist/realtime/rejection.d.ts +10 -0
  45. package/dist/realtime/rejection.d.ts.map +1 -0
  46. package/dist/realtime/socket.d.ts +14 -1
  47. package/dist/realtime/socket.d.ts.map +1 -1
  48. package/dist/server/cache.d.ts +5 -1
  49. package/dist/server/cache.d.ts.map +1 -1
  50. package/dist/server/error-hook.d.ts +1 -0
  51. package/dist/server/error-hook.d.ts.map +1 -1
  52. package/dist/server/file.d.ts +6 -0
  53. package/dist/server/file.d.ts.map +1 -1
  54. package/dist/server/implement.d.ts.map +1 -1
  55. package/dist/server/index.d.ts +2 -3
  56. package/dist/server/index.d.ts.map +1 -1
  57. package/dist/server/index.js +108 -11
  58. package/dist/server/middleware/cors.d.ts.map +1 -1
  59. package/dist/server/openapi.d.ts.map +1 -1
  60. package/dist/server/realtime.d.ts +2 -0
  61. package/dist/server/realtime.d.ts.map +1 -1
  62. package/dist/server/router.d.ts +0 -8
  63. package/dist/server/router.d.ts.map +1 -1
  64. package/dist/server/stream.d.ts.map +1 -1
  65. package/dist/server/types.d.ts +2 -7
  66. package/dist/server/types.d.ts.map +1 -1
  67. package/dist/tools/cli-args.d.ts +11 -3
  68. package/dist/tools/cli-args.d.ts.map +1 -1
  69. package/dist/tools/cli.d.ts +5 -0
  70. package/dist/tools/cli.d.ts.map +1 -1
  71. package/dist/tools/coerce.d.ts.map +1 -1
  72. package/dist/tools/json-schema.d.ts.map +1 -1
  73. package/dist/tools/mcp-handler.d.ts +4 -3
  74. package/dist/tools/mcp-handler.d.ts.map +1 -1
  75. package/dist/tools/mcp-prepare.d.ts +95 -0
  76. package/dist/tools/mcp-prepare.d.ts.map +1 -0
  77. package/dist/tools/mcp-round.d.ts +4 -0
  78. package/dist/tools/mcp-round.d.ts.map +1 -1
  79. package/dist/tools/mcp-stdio.d.ts +2 -1
  80. package/dist/tools/mcp-stdio.d.ts.map +1 -1
  81. package/dist/tools/mcp.d.ts +2 -78
  82. package/dist/tools/mcp.d.ts.map +1 -1
  83. package/dist/tools/mount-download.d.ts +5 -0
  84. package/dist/tools/mount-download.d.ts.map +1 -1
  85. package/dist/tools/native-mcp.d.ts +1 -1
  86. package/dist/tools/native-mcp.d.ts.map +1 -1
  87. package/dist/tools/oauth-provider.d.ts +20 -1
  88. package/dist/tools/oauth-provider.d.ts.map +1 -1
  89. package/dist/tools/tool-logger.d.ts +1 -1
  90. package/dist/tools/tool-logger.d.ts.map +1 -1
  91. package/dist/tools/view-file.d.ts +9 -0
  92. package/dist/tools/view-file.d.ts.map +1 -1
  93. package/dist/tools.d.ts +2 -1
  94. package/dist/tools.d.ts.map +1 -1
  95. package/dist/tools.js +342 -214
  96. package/llms-full.txt +107 -19
  97. package/llms.txt +1 -1
  98. package/package.json +2 -2
  99. package/dist/index-frfyw9fa.js +0 -33
  100. package/dist/index-x3fcszf8.js +0 -8
@@ -0,0 +1,181 @@
1
+ import {
2
+ isRecord,
3
+ isUnsafeKey
4
+ } from "./index-5s8b7z6q.js";
5
+
6
+ // src/observability/sanitize.ts
7
+ var SENSITIVE_WORDS = new Set([
8
+ "password",
9
+ "passwords",
10
+ "passwd",
11
+ "pwd",
12
+ "secret",
13
+ "secrets",
14
+ "token",
15
+ "tokens",
16
+ "apikey",
17
+ "auth",
18
+ "authorization",
19
+ "bearer",
20
+ "cookie",
21
+ "cookies",
22
+ "credential",
23
+ "credentials"
24
+ ]);
25
+ var SENSITIVE_WORD_PAIRS = new Set([
26
+ "apikey",
27
+ "privatekey",
28
+ "initdata",
29
+ "sessionid",
30
+ "sessionkey"
31
+ ]);
32
+ var SENSITIVE_EXACT = new Set(["session"]);
33
+ function splitKeyWords(key) {
34
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 0);
35
+ }
36
+ function isSensitiveKeyDefault(key) {
37
+ const words = splitKeyWords(key);
38
+ if (words.length === 1 && SENSITIVE_EXACT.has(words[0] ?? ""))
39
+ return true;
40
+ for (let index = 0;index < words.length; index += 1) {
41
+ const word = words[index] ?? "";
42
+ if (SENSITIVE_WORDS.has(word))
43
+ return true;
44
+ const next = words[index + 1];
45
+ if (next !== undefined && SENSITIVE_WORD_PAIRS.has(word + next))
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ var DEFAULT_MAX_BYTES = 16000;
51
+ var DEFAULT_MAX_DEPTH = 20;
52
+ var MASK = "[redacted]";
53
+ function redact(value, options = {}) {
54
+ const custom = options.sensitiveKeys;
55
+ const isSensitive = custom === undefined ? isSensitiveKeyDefault : (key) => {
56
+ custom.lastIndex = 0;
57
+ return custom.test(key);
58
+ };
59
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
60
+ const ancestors = new Set;
61
+ const walk = (input, depth) => {
62
+ if (depth > maxDepth)
63
+ return "[max depth]";
64
+ if (input === null || input === undefined)
65
+ return null;
66
+ if (typeof input === "string" || typeof input === "number" || typeof input === "boolean") {
67
+ return input;
68
+ }
69
+ if (input instanceof Uint8Array || input instanceof ArrayBuffer) {
70
+ return { _type: "binary", size: input.byteLength };
71
+ }
72
+ if (input instanceof Blob) {
73
+ return { _type: "blob", size: input.size, mime: input.type };
74
+ }
75
+ if (typeof FormData !== "undefined" && input instanceof FormData) {
76
+ return { _type: "formdata", keys: [...input.keys()] };
77
+ }
78
+ if (input instanceof Map) {
79
+ if (ancestors.has(input))
80
+ return "[circular]";
81
+ ancestors.add(input);
82
+ const result = [...input.entries()].map(([key, value2]) => [
83
+ walk(key, depth + 1),
84
+ walk(value2, depth + 1)
85
+ ]);
86
+ ancestors.delete(input);
87
+ return { _type: "map", entries: result };
88
+ }
89
+ if (input instanceof Set) {
90
+ if (ancestors.has(input))
91
+ return "[circular]";
92
+ ancestors.add(input);
93
+ const values = [...input].map((item) => walk(item, depth + 1));
94
+ ancestors.delete(input);
95
+ return { _type: "set", values };
96
+ }
97
+ if (input instanceof Error) {
98
+ return {
99
+ _type: "error",
100
+ name: input.name,
101
+ message: input.message,
102
+ ...input.stack !== undefined && { stack: input.stack }
103
+ };
104
+ }
105
+ if (Array.isArray(input)) {
106
+ if (ancestors.has(input))
107
+ return "[circular]";
108
+ ancestors.add(input);
109
+ const result = input.map((item) => walk(item, depth + 1));
110
+ ancestors.delete(input);
111
+ return result;
112
+ }
113
+ if (isRecord(input)) {
114
+ if (ancestors.has(input))
115
+ return "[circular]";
116
+ ancestors.add(input);
117
+ const out = {};
118
+ for (const key of Object.keys(input)) {
119
+ if (isUnsafeKey(key))
120
+ continue;
121
+ if (isSensitive(key)) {
122
+ out[key] = MASK;
123
+ continue;
124
+ }
125
+ try {
126
+ out[key] = walk(Reflect.get(input, key), depth + 1);
127
+ } catch {
128
+ out[key] = "[unreadable]";
129
+ }
130
+ }
131
+ ancestors.delete(input);
132
+ return out;
133
+ }
134
+ return String(input);
135
+ };
136
+ return walk(value, 0);
137
+ }
138
+ function truncatePreview(value, maxBytes = DEFAULT_MAX_BYTES) {
139
+ const json = JSON.stringify(value);
140
+ const bytes = new TextEncoder().encode(json);
141
+ if (bytes.byteLength <= maxBytes)
142
+ return value;
143
+ let end = Math.max(0, maxBytes);
144
+ while (end > 0 && ((bytes[end] ?? 0) & 192) === 128)
145
+ end -= 1;
146
+ const preview = new TextDecoder().decode(bytes.subarray(0, end));
147
+ return {
148
+ _truncated: true,
149
+ _originalBytes: bytes.byteLength,
150
+ preview: `${preview}…`
151
+ };
152
+ }
153
+ function sanitizePayload(value, options = {}) {
154
+ if (value === undefined || value === null)
155
+ return null;
156
+ try {
157
+ return truncatePreview(redact(value, options), options.maxBytes ?? DEFAULT_MAX_BYTES);
158
+ } catch {
159
+ return "[unserializable]";
160
+ }
161
+ }
162
+ function measureSize(value) {
163
+ if (value === undefined || value === null) {
164
+ return { resultSize: null, responseBytes: 0 };
165
+ }
166
+ let responseBytes = 0;
167
+ try {
168
+ responseBytes = new TextEncoder().encode(JSON.stringify(value)).byteLength;
169
+ } catch {
170
+ return { resultSize: null, responseBytes: 0, unserializable: true };
171
+ }
172
+ if (Array.isArray(value)) {
173
+ return { resultSize: value.length, responseBytes };
174
+ }
175
+ if (isRecord(value) && Array.isArray(value.items) && "nextCursor" in value) {
176
+ return { resultSize: value.items.length, responseBytes };
177
+ }
178
+ return { resultSize: null, responseBytes };
179
+ }
180
+
181
+ export { redact, truncatePreview, sanitizePayload, measureSize };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  mapObjectTypeBoundary
3
- } from "./index-s4qsmgwe.js";
3
+ } from "./index-tss6bk5c.js";
4
4
 
5
5
  // src/contract/define.ts
6
6
  import { z } from "zod";
@@ -228,10 +228,11 @@ var STITCH_ERROR_STATUS = {
228
228
  CONFLICT: 409,
229
229
  RATE_LIMITED: 429,
230
230
  VALIDATION_ERROR: 400,
231
+ REALTIME_CONTRACT_VIOLATION: 500,
231
232
  INTERNAL_SERVER_ERROR: 500
232
233
  };
233
234
  function isStitchErrorCode(code) {
234
- return code in STITCH_ERROR_STATUS;
235
+ return Object.hasOwn(STITCH_ERROR_STATUS, code);
235
236
  }
236
237
  function appError(code, message, details) {
237
238
  throw new AppError(code, message, isStitchErrorCode(code) ? STITCH_ERROR_STATUS[code] : 500, details);
@@ -342,4 +343,4 @@ function decodeCursor(cursor, schema) {
342
343
  return null;
343
344
  }
344
345
  }
345
- export { parseTrailingWildcard, ALL_TRANSPORTS, defineContract, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, defineErrors, createContractFactory, paginatedSchema, encodeCursor, decodeCursor };
346
+ export { parseTrailingWildcard, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, ALL_TRANSPORTS, defineContract, defineErrors, createContractFactory, paginatedSchema, encodeCursor, decodeCursor };
@@ -5,9 +5,17 @@ function assertCorsConfig(config) {
5
5
  if (config.credentials && (config.origin === undefined || config.origin === "*")) {
6
6
  throw new Error("[stitchkit] cors: `credentials: true` cannot be combined with a wildcard origin. " + "Set `origin` to an explicit string or list.");
7
7
  }
8
+ if (config.origin === undefined) {
9
+ throw new Error("[stitchkit] cors: `origin` is required. Pass an explicit origin (or list), or '*' to deliberately allow every origin — omit `cors` entirely to emit no CORS headers.");
10
+ }
11
+ if (Array.isArray(config.origin) && config.origin.length === 0) {
12
+ throw new Error("[stitchkit] cors: `origin` cannot be an empty list. Pass explicit origins, or '*' to deliberately allow every origin.");
13
+ }
8
14
  }
9
15
  function resolveOrigin(config, requestOrigin) {
10
- if (config.origin === undefined || config.origin === "*") {
16
+ if (config.origin === undefined)
17
+ return;
18
+ if (config.origin === "*") {
11
19
  return "*";
12
20
  }
13
21
  if (Array.isArray(config.origin)) {
@@ -5,6 +5,9 @@ function typedEntries(value) {
5
5
  function isRecord(value) {
6
6
  return typeof value === "object" && value !== null && !Array.isArray(value);
7
7
  }
8
+ function transportResult(value) {
9
+ return value;
10
+ }
8
11
  function mapObject(source, mapper) {
9
12
  const result = {};
10
13
  for (const [key, value] of typedEntries(source)) {
@@ -18,4 +21,4 @@ function mapObjectTypeBoundary(source, mapper) {
18
21
  return mapObject(source, (key, value) => mapper(key, value));
19
22
  }
20
23
 
21
- export { typedEntries, isRecord, mapObject, mapObjectTypeBoundary };
24
+ export { typedEntries, isRecord, transportResult, mapObject, mapObjectTypeBoundary };
@@ -6,7 +6,7 @@ import {
6
6
  isUnsafeKey,
7
7
  safeJsonParse,
8
8
  unauthorized
9
- } from "./index-yh0p5ddn.js";
9
+ } from "./index-5s8b7z6q.js";
10
10
 
11
11
  // src/server/middleware/cookies.ts
12
12
  function parseCookies(header) {
@@ -157,7 +157,7 @@ function createAuthHook(config) {
157
157
  const scope = endpoint.scope ?? config.defaultScope;
158
158
  if (!scope)
159
159
  return;
160
- const rule = config.rules[scope];
160
+ const rule = Object.hasOwn(config.rules, scope) ? config.rules[scope] : undefined;
161
161
  if (!rule) {
162
162
  throw new Error(`[stitchkit] auth: no rule for scope "${scope}"`);
163
163
  }
@@ -0,0 +1,63 @@
1
+ // src/internal/within-dir.ts
2
+ import { realpath } from "node:fs/promises";
3
+ import { sep } from "node:path";
4
+ function isWithinDir(root, target) {
5
+ const base = root.endsWith(sep) ? root.slice(0, -sep.length) : root;
6
+ return target === root || target === base || target.startsWith(base + sep);
7
+ }
8
+ async function realPathWithinDir(root, target) {
9
+ const [realRoot, realTarget] = await Promise.all([
10
+ realpath(root).catch(() => null),
11
+ realpath(target).catch(() => null)
12
+ ]);
13
+ if (realRoot === null || realTarget === null)
14
+ return null;
15
+ return isWithinDir(realRoot, realTarget) ? realTarget : null;
16
+ }
17
+
18
+ // src/tools/json-schema.ts
19
+ import { z } from "zod";
20
+ function toJsonSchema(schema, io, unrepresentable = "throw", target = "draft-2020-12") {
21
+ return z.toJSONSchema(schema, {
22
+ io,
23
+ target,
24
+ unrepresentable,
25
+ cycles: "ref"
26
+ });
27
+ }
28
+ function isObjectNode(value) {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30
+ }
31
+ function jsonSchemaFields(jsonSchema) {
32
+ const properties = jsonSchema.properties;
33
+ if (!isObjectNode(properties)) {
34
+ const conjunctive = Array.isArray(jsonSchema.allOf);
35
+ const members = conjunctive ? jsonSchema.allOf : Array.isArray(jsonSchema.oneOf) ? jsonSchema.oneOf : Array.isArray(jsonSchema.anyOf) ? jsonSchema.anyOf : [];
36
+ const byName = new Map;
37
+ for (const member of Array.isArray(members) ? members : []) {
38
+ if (!isObjectNode(member))
39
+ continue;
40
+ for (const field of jsonSchemaFields(member)) {
41
+ const existing = byName.get(field.name);
42
+ const required2 = existing ? conjunctive ? existing.required || field.required : existing.required && field.required : field.required;
43
+ byName.set(field.name, existing ? { ...field, required: required2 } : field);
44
+ }
45
+ }
46
+ return [...byName.values()];
47
+ }
48
+ const required = new Set(Array.isArray(jsonSchema.required) ? jsonSchema.required.filter((k) => typeof k === "string") : []);
49
+ const fields = [];
50
+ for (const [name, raw] of Object.entries(properties)) {
51
+ const schema = isObjectNode(raw) ? raw : {};
52
+ const description = typeof schema.description === "string" ? schema.description : undefined;
53
+ fields.push({
54
+ name,
55
+ schema,
56
+ required: required.has(name),
57
+ ...description && { description }
58
+ });
59
+ }
60
+ return fields;
61
+ }
62
+
63
+ export { isWithinDir, realPathWithinDir, toJsonSchema, jsonSchemaFields };