stitchkit 0.54.0 → 0.56.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 +4 -1
- package/dist/browser/socket-io.d.ts +25 -7
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +14 -9
- package/dist/contract/errors.d.ts +7 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/file-ref.d.ts +13 -0
- package/dist/contract/file-ref.d.ts.map +1 -0
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +21 -17
- package/dist/files/boundary.d.ts +55 -0
- package/dist/files/boundary.d.ts.map +1 -0
- package/dist/files.d.ts +3 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +16 -0
- package/dist/index-1bx83sw4.js +4 -0
- package/dist/{index-pfqjb5xy.js → index-28cqssm3.js} +30 -5
- package/dist/index-557by2db.js +34 -0
- package/dist/index-5r13htq1.js +283 -0
- package/dist/index-6k1937bx.js +20 -0
- package/dist/index-6y759j86.js +1031 -0
- package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
- package/dist/index-bfcpjw20.js +363 -0
- package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
- package/dist/{index-7vb44hy8.js → index-escqg10p.js} +193 -751
- package/dist/{index-173tvqcp.js → index-j3dem06f.js} +9 -114
- package/dist/{index-gvha32jh.js → index-jcc611vh.js} +2 -2
- package/dist/{index-z7761jk2.js → index-n4nfa7gh.js} +16 -22
- package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
- package/dist/index-sbdmyz75.js +18 -0
- package/dist/{index-b15rtha1.js → index-sm2tjx06.js} +98 -3
- package/dist/{index-yez419px.js → index-xy8fmh6w.js} +99 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +188 -40
- package/dist/node.js +31 -27
- package/dist/observability/index.js +28 -25
- package/dist/react.js +2 -2
- package/dist/realtime/contract.d.ts +9 -0
- package/dist/realtime/contract.d.ts.map +1 -1
- package/dist/realtime/index.d.ts +2 -1
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/request.d.ts +21 -0
- package/dist/realtime/request.d.ts.map +1 -0
- package/dist/realtime/socket.d.ts +3 -0
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/remote.d.ts +9 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +79 -0
- package/dist/server/context-contribution.d.ts +18 -0
- package/dist/server/context-contribution.d.ts.map +1 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +136 -88
- package/dist/server/lifecycle.d.ts +4 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/middleware/auth.d.ts +64 -9
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +180 -0
- package/dist/testing/surface-conformance.d.ts.map +1 -0
- package/dist/testing/surface-manifest.d.ts +255 -0
- package/dist/testing/surface-manifest.d.ts.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +714 -3
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/async-operation.d.ts +360 -0
- package/dist/tools/async-operation.d.ts.map +1 -0
- package/dist/tools/cli-args.d.ts +1 -1
- package/dist/tools/cli-args.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-wait.d.ts +11 -2
- package/dist/tools/cli-wait.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +2 -0
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts +6 -5
- package/dist/tools/define-download-tool.d.ts.map +1 -1
- package/dist/tools/define-upload-tool.d.ts +3 -1
- package/dist/tools/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/define-wait-tool.d.ts +1 -1
- package/dist/tools/define-wait-tool.d.ts.map +1 -1
- package/dist/tools/download-core.d.ts +5 -10
- package/dist/tools/download-core.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +101 -0
- package/dist/tools/internal/surface-projector.d.ts.map +1 -0
- package/dist/tools/lifecycle.d.ts +4 -0
- package/dist/tools/lifecycle.d.ts.map +1 -0
- package/dist/tools/managed-file-error.d.ts +6 -0
- package/dist/tools/managed-file-error.d.ts.map +1 -0
- package/dist/tools/mcp-prepare.d.ts +3 -21
- package/dist/tools/mcp-prepare.d.ts.map +1 -1
- package/dist/tools/mcp-round-policy.d.ts +9 -0
- package/dist/tools/mcp-round-policy.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +0 -4
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts +5 -4
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts +4 -2
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +5 -9
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -0
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/upload-core.d.ts +3 -2
- package/dist/tools/upload-core.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +4 -3
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools/wait-core.d.ts +6 -1
- package/dist/tools/wait-core.d.ts.map +1 -1
- package/dist/tools.d.ts +3 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +705 -599
- package/llms-full.txt +727 -34
- package/package.json +10 -2
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
1
|
// src/observability/trace.ts
|
|
5
2
|
var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
|
|
6
3
|
function randomHex(bytes) {
|
|
@@ -149,4 +146,4 @@ function mapObject(source, mapper) {
|
|
|
149
146
|
return result;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
export {
|
|
149
|
+
export { parseTrailingWildcard, typedEntries, isRecord, callRuntimeHandler, transportResult, mapObject, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, resolvePropagationContext };
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isWithinDir
|
|
3
|
+
} from "./index-sbdmyz75.js";
|
|
4
|
+
import {
|
|
5
|
+
ManagedFilePathSchema,
|
|
6
|
+
ManagedFileRefSchema
|
|
7
|
+
} from "./index-6k1937bx.js";
|
|
8
|
+
|
|
9
|
+
// src/files/boundary.ts
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { constants } from "node:fs";
|
|
12
|
+
import { link, mkdir, open, realpath, rename, stat, unlink } from "node:fs/promises";
|
|
13
|
+
import { basename, dirname, resolve } from "node:path";
|
|
14
|
+
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
15
|
+
var DEFAULT_INSPECTION_BYTES = 64 * 1024;
|
|
16
|
+
var DEFAULT_INSPECTION_TIMEOUT_MS = 15000;
|
|
17
|
+
var NOFOLLOW_FLAG = constants.O_NOFOLLOW ?? 0;
|
|
18
|
+
var ManagedFileInspectionSchema = ManagedFileRefSchema.pick({
|
|
19
|
+
mediaType: true,
|
|
20
|
+
name: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
class ManagedFileError extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.name = "ManagedFileError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function positiveLimit(value, fallback, name) {
|
|
32
|
+
const resolved = value ?? fallback;
|
|
33
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
34
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
function filePath(path) {
|
|
39
|
+
const parsed = ManagedFilePathSchema.safeParse(path);
|
|
40
|
+
if (!parsed.success) {
|
|
41
|
+
throw new ManagedFileError("FILE_INVALID_PATH", "invalid managed-file path");
|
|
42
|
+
}
|
|
43
|
+
return parsed.data;
|
|
44
|
+
}
|
|
45
|
+
function errorCode(error) {
|
|
46
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
47
|
+
return;
|
|
48
|
+
return typeof error.code === "string" ? error.code : undefined;
|
|
49
|
+
}
|
|
50
|
+
function ioError(message, error) {
|
|
51
|
+
if (error instanceof ManagedFileError)
|
|
52
|
+
return error;
|
|
53
|
+
return new ManagedFileError("FILE_IO_ERROR", message, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
async function realpathOrNull(path, message) {
|
|
56
|
+
try {
|
|
57
|
+
return await realpath(path);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (errorCode(error) === "ENOENT")
|
|
60
|
+
return null;
|
|
61
|
+
throw ioError(message, error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function bindRoot(config) {
|
|
65
|
+
const requested = resolve(config.root);
|
|
66
|
+
let canonical = await realpathOrNull(requested, "failed to resolve managed-file root");
|
|
67
|
+
if (canonical === null && config.createRoot) {
|
|
68
|
+
const parent = await realpathOrNull(dirname(requested), "failed to resolve managed-file root parent");
|
|
69
|
+
if (parent === null) {
|
|
70
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root parent must exist");
|
|
71
|
+
}
|
|
72
|
+
const parentInfo = await stat(parent).catch((error) => {
|
|
73
|
+
throw ioError("failed to inspect managed-file root parent", error);
|
|
74
|
+
});
|
|
75
|
+
if (!parentInfo.isDirectory()) {
|
|
76
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file root parent must be a directory");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await mkdir(requested, { mode: 448 });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (errorCode(error) !== "EEXIST") {
|
|
82
|
+
if (errorCode(error) === "ENOENT") {
|
|
83
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root parent must exist");
|
|
84
|
+
}
|
|
85
|
+
throw ioError("failed to create managed-file root", error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
canonical = await realpathOrNull(requested, "failed to resolve managed-file root");
|
|
89
|
+
}
|
|
90
|
+
if (canonical === null) {
|
|
91
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root must exist");
|
|
92
|
+
}
|
|
93
|
+
const info = await stat(canonical).catch((error) => {
|
|
94
|
+
throw ioError("failed to inspect managed-file root", error);
|
|
95
|
+
});
|
|
96
|
+
if (!info.isDirectory()) {
|
|
97
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file root must be a directory");
|
|
98
|
+
}
|
|
99
|
+
return canonical;
|
|
100
|
+
}
|
|
101
|
+
async function existingParent(root, target) {
|
|
102
|
+
const parent = await realpathOrNull(dirname(target), "failed to resolve managed-file parent directory");
|
|
103
|
+
if (parent === null) {
|
|
104
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file parent directory not found");
|
|
105
|
+
}
|
|
106
|
+
if (!isWithinDir(root, parent)) {
|
|
107
|
+
throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
|
|
108
|
+
}
|
|
109
|
+
const info = await stat(parent).catch((error) => {
|
|
110
|
+
throw ioError("failed to inspect managed-file parent directory", error);
|
|
111
|
+
});
|
|
112
|
+
if (!info.isDirectory()) {
|
|
113
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file parent is not a directory");
|
|
114
|
+
}
|
|
115
|
+
return parent;
|
|
116
|
+
}
|
|
117
|
+
function abortReason(signal) {
|
|
118
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
119
|
+
}
|
|
120
|
+
function raceWithSignal(operation, signal) {
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
123
|
+
let settled = false;
|
|
124
|
+
const settle = (complete) => {
|
|
125
|
+
if (settled)
|
|
126
|
+
return;
|
|
127
|
+
settled = true;
|
|
128
|
+
signal.removeEventListener("abort", onAbort);
|
|
129
|
+
complete();
|
|
130
|
+
};
|
|
131
|
+
const onAbort = () => settle(() => rejectPromise(abortReason(signal)));
|
|
132
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
if (signal.aborted)
|
|
134
|
+
onAbort();
|
|
135
|
+
operation.then((value) => settle(() => resolvePromise(value)), (error) => settle(() => rejectPromise(error)));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function inspectFile(inspector, input, timeoutMs, outerSignal) {
|
|
139
|
+
if (!inspector)
|
|
140
|
+
return {};
|
|
141
|
+
outerSignal?.throwIfAborted();
|
|
142
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
143
|
+
const signal = outerSignal ? AbortSignal.any([outerSignal, timeoutSignal]) : timeoutSignal;
|
|
144
|
+
try {
|
|
145
|
+
const inspected = await raceWithSignal(Promise.resolve().then(() => inspector({ ...input, signal })), signal);
|
|
146
|
+
return ManagedFileInspectionSchema.parse(inspected);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (outerSignal?.aborted)
|
|
149
|
+
throw abortReason(outerSignal);
|
|
150
|
+
throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: error });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function inspectedRef(path, size, inspection, fallback = {}) {
|
|
154
|
+
const mediaType = inspection.mediaType ?? fallback.mediaType;
|
|
155
|
+
const name = inspection.name ?? fallback.name;
|
|
156
|
+
const parsed = ManagedFileRefSchema.safeParse({
|
|
157
|
+
path,
|
|
158
|
+
size,
|
|
159
|
+
...mediaType ? { mediaType } : {},
|
|
160
|
+
...name ? { name } : {}
|
|
161
|
+
});
|
|
162
|
+
if (!parsed.success) {
|
|
163
|
+
throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: parsed.error });
|
|
164
|
+
}
|
|
165
|
+
return parsed.data;
|
|
166
|
+
}
|
|
167
|
+
async function readHandle(handle, maxBytes, signal) {
|
|
168
|
+
const chunks = [];
|
|
169
|
+
let total = 0;
|
|
170
|
+
while (true) {
|
|
171
|
+
signal?.throwIfAborted();
|
|
172
|
+
const capacity = Math.min(64 * 1024, maxBytes + 1 - total);
|
|
173
|
+
if (capacity <= 0) {
|
|
174
|
+
throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
|
|
175
|
+
}
|
|
176
|
+
const chunk = new Uint8Array(capacity);
|
|
177
|
+
const { bytesRead } = await handle.read(chunk, 0, capacity);
|
|
178
|
+
if (bytesRead === 0)
|
|
179
|
+
break;
|
|
180
|
+
total += bytesRead;
|
|
181
|
+
if (total > maxBytes) {
|
|
182
|
+
throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
|
|
183
|
+
}
|
|
184
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
185
|
+
}
|
|
186
|
+
const bytes = new Uint8Array(total);
|
|
187
|
+
let offset = 0;
|
|
188
|
+
for (const chunk of chunks) {
|
|
189
|
+
bytes.set(chunk, offset);
|
|
190
|
+
offset += chunk.byteLength;
|
|
191
|
+
}
|
|
192
|
+
return bytes;
|
|
193
|
+
}
|
|
194
|
+
async function writeChunk(handle, chunk) {
|
|
195
|
+
let offset = 0;
|
|
196
|
+
while (offset < chunk.byteLength) {
|
|
197
|
+
const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset);
|
|
198
|
+
if (bytesWritten === 0)
|
|
199
|
+
throw new Error("zero-byte managed-file write");
|
|
200
|
+
offset += bytesWritten;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function writeSource(handle, source, maxBytes, inspectionBytes, signal) {
|
|
204
|
+
let size = 0;
|
|
205
|
+
const prefixChunks = [];
|
|
206
|
+
let prefixSize = 0;
|
|
207
|
+
const consume = async (chunk) => {
|
|
208
|
+
signal?.throwIfAborted();
|
|
209
|
+
size += chunk.byteLength;
|
|
210
|
+
if (size > maxBytes) {
|
|
211
|
+
throw new ManagedFileError("FILE_TOO_LARGE", `file exceeds the ${maxBytes}-byte cap`);
|
|
212
|
+
}
|
|
213
|
+
if (prefixSize < inspectionBytes) {
|
|
214
|
+
const kept = chunk.subarray(0, inspectionBytes - prefixSize);
|
|
215
|
+
prefixChunks.push(kept);
|
|
216
|
+
prefixSize += kept.byteLength;
|
|
217
|
+
}
|
|
218
|
+
await writeChunk(handle, chunk);
|
|
219
|
+
};
|
|
220
|
+
if (source instanceof Uint8Array) {
|
|
221
|
+
await consume(source);
|
|
222
|
+
} else {
|
|
223
|
+
const reader = source.getReader();
|
|
224
|
+
try {
|
|
225
|
+
while (true) {
|
|
226
|
+
const result = await reader.read();
|
|
227
|
+
if (result.done)
|
|
228
|
+
break;
|
|
229
|
+
await consume(result.value);
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
await reader.cancel(error).catch(() => {
|
|
233
|
+
return;
|
|
234
|
+
});
|
|
235
|
+
throw error;
|
|
236
|
+
} finally {
|
|
237
|
+
reader.releaseLock();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const prefix = new Uint8Array(prefixSize);
|
|
241
|
+
let offset = 0;
|
|
242
|
+
for (const chunk of prefixChunks) {
|
|
243
|
+
prefix.set(chunk, offset);
|
|
244
|
+
offset += chunk.byteLength;
|
|
245
|
+
}
|
|
246
|
+
return { size, prefix };
|
|
247
|
+
}
|
|
248
|
+
async function createManagedFileBoundary(config) {
|
|
249
|
+
const root = await bindRoot(config);
|
|
250
|
+
const maxReadBytes = positiveLimit(config.maxReadBytes, DEFAULT_MAX_BYTES, "maxReadBytes");
|
|
251
|
+
const maxWriteBytes = positiveLimit(config.maxWriteBytes, DEFAULT_MAX_BYTES, "maxWriteBytes");
|
|
252
|
+
const inspectionBytes = positiveLimit(config.inspectionBytes, DEFAULT_INSPECTION_BYTES, "inspectionBytes");
|
|
253
|
+
const inspectionTimeoutMs = positiveLimit(config.inspectionTimeoutMs, DEFAULT_INSPECTION_TIMEOUT_MS, "inspectionTimeoutMs");
|
|
254
|
+
const targetFor = (path) => {
|
|
255
|
+
const relative = filePath(path);
|
|
256
|
+
const target = resolve(root, ...relative.split("/"));
|
|
257
|
+
if (!isWithinDir(root, target)) {
|
|
258
|
+
throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
|
|
259
|
+
}
|
|
260
|
+
return { path: relative, target };
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
async read(path, options = {}) {
|
|
264
|
+
const resolved = targetFor(path);
|
|
265
|
+
options.signal?.throwIfAborted();
|
|
266
|
+
const realTarget = await realpathOrNull(resolved.target, "failed to resolve managed file");
|
|
267
|
+
if (realTarget === null) {
|
|
268
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed file not found");
|
|
269
|
+
}
|
|
270
|
+
if (!isWithinDir(root, realTarget)) {
|
|
271
|
+
throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
|
|
272
|
+
}
|
|
273
|
+
const maxBytes = positiveLimit(options.maxBytes, maxReadBytes, "maxBytes");
|
|
274
|
+
let handle;
|
|
275
|
+
try {
|
|
276
|
+
handle = await open(realTarget, constants.O_RDONLY | NOFOLLOW_FLAG);
|
|
277
|
+
const info = await handle.stat();
|
|
278
|
+
if (!info.isFile()) {
|
|
279
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed path is not a regular file");
|
|
280
|
+
}
|
|
281
|
+
const bytes = await readHandle(handle, maxBytes, options.signal);
|
|
282
|
+
const inspection = await inspectFile(config.inspect, {
|
|
283
|
+
prefix: bytes.slice(0, inspectionBytes),
|
|
284
|
+
name: basename(resolved.path)
|
|
285
|
+
}, inspectionTimeoutMs, options.signal);
|
|
286
|
+
return {
|
|
287
|
+
ref: inspectedRef(resolved.path, bytes.byteLength, inspection),
|
|
288
|
+
bytes
|
|
289
|
+
};
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (options.signal?.aborted && error === options.signal.reason)
|
|
292
|
+
throw error;
|
|
293
|
+
if (errorCode(error) === "ENOENT") {
|
|
294
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed file not found");
|
|
295
|
+
}
|
|
296
|
+
throw ioError("failed to read managed file", error);
|
|
297
|
+
} finally {
|
|
298
|
+
await handle?.close().catch(() => {
|
|
299
|
+
return;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
async write(path, source, options = {}) {
|
|
304
|
+
options.signal?.throwIfAborted();
|
|
305
|
+
const resolved = targetFor(path);
|
|
306
|
+
const parent = await existingParent(root, resolved.target);
|
|
307
|
+
const maxBytes = positiveLimit(options.maxBytes, maxWriteBytes, "maxBytes");
|
|
308
|
+
const temporary = resolve(parent, `.stitchkit-${randomUUID()}.tmp`);
|
|
309
|
+
let handle;
|
|
310
|
+
let committed = false;
|
|
311
|
+
let size = 0;
|
|
312
|
+
try {
|
|
313
|
+
handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | NOFOLLOW_FLAG, 384);
|
|
314
|
+
const written = await writeSource(handle, source, maxBytes, inspectionBytes, options.signal);
|
|
315
|
+
size = written.size;
|
|
316
|
+
if (options.durable)
|
|
317
|
+
await handle.sync();
|
|
318
|
+
await handle.close();
|
|
319
|
+
handle = undefined;
|
|
320
|
+
const inspection = await inspectFile(config.inspect, {
|
|
321
|
+
prefix: written.prefix,
|
|
322
|
+
...options.mediaType ? { declaredMediaType: options.mediaType } : {},
|
|
323
|
+
name: options.name ?? basename(resolved.path)
|
|
324
|
+
}, inspectionTimeoutMs, options.signal);
|
|
325
|
+
options.signal?.throwIfAborted();
|
|
326
|
+
if (options.replace) {
|
|
327
|
+
await rename(temporary, resolved.target);
|
|
328
|
+
committed = true;
|
|
329
|
+
} else {
|
|
330
|
+
try {
|
|
331
|
+
await link(temporary, resolved.target);
|
|
332
|
+
committed = true;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (errorCode(error) === "EEXIST") {
|
|
335
|
+
throw new ManagedFileError("FILE_EXISTS", "managed file already exists");
|
|
336
|
+
}
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return inspectedRef(resolved.path, size, inspection, {
|
|
341
|
+
mediaType: options.mediaType,
|
|
342
|
+
name: options.name
|
|
343
|
+
});
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (options.signal?.aborted && error === options.signal.reason)
|
|
346
|
+
throw error;
|
|
347
|
+
throw ioError("failed to write managed file", error);
|
|
348
|
+
} finally {
|
|
349
|
+
await handle?.close().catch(() => {
|
|
350
|
+
return;
|
|
351
|
+
});
|
|
352
|
+
if (!options.replace || !committed) {
|
|
353
|
+
await unlink(temporary).catch((error) => {
|
|
354
|
+
if (errorCode(error) !== "ENOENT")
|
|
355
|
+
config.onCleanupError?.(error);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export { ManagedFileError, createManagedFileBoundary };
|
|
@@ -1,20 +1,3 @@
|
|
|
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
1
|
// src/tools/json-schema.ts
|
|
19
2
|
import { z } from "zod";
|
|
20
3
|
function toJsonSchema(schema, io, unrepresentable = "throw", target = "draft-2020-12") {
|
|
@@ -60,4 +43,4 @@ function jsonSchemaFields(jsonSchema) {
|
|
|
60
43
|
return fields;
|
|
61
44
|
}
|
|
62
45
|
|
|
63
|
-
export {
|
|
46
|
+
export { toJsonSchema, jsonSchemaFields };
|