@openfairygui/backend 0.3.0 → 0.3.1
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 -2
- package/dist/index.cjs +89 -29
- package/dist/index.d.cts +30 -7
- package/dist/index.d.mts +30 -7
- package/dist/index.mjs +89 -29
- package/dist/node.cjs +277 -44
- package/dist/node.d.cts +29 -6
- package/dist/node.d.mts +29 -6
- package/dist/node.mjs +276 -44
- package/package.json +8 -5
- package/src/index.ts +3 -0
- package/src/node.ts +213 -14
- package/src/path-policy.ts +18 -0
- package/src/runtime/capabilities.ts +2 -2
- package/src/runtime/contracts.ts +35 -3
- package/src/runtime.ts +27 -8
- package/src/services/authoring-service.ts +3 -3
- package/src/services/context.ts +1 -0
- package/src/services/runtime-service.ts +53 -14
- package/src/services/session-project-writer.ts +31 -13
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ It owns:
|
|
|
10
10
|
|
|
11
11
|
- project/session lifecycle
|
|
12
12
|
- revisioned request handling
|
|
13
|
-
- coordinated
|
|
13
|
+
- coordinated save semantics, with atomic staged directory swaps on the Node adapter
|
|
14
14
|
- browser-safe project sessions
|
|
15
15
|
- browser-safe async project storage adapter
|
|
16
16
|
- adapter-backed file sessions and backend-local session locking
|
|
@@ -39,7 +39,9 @@ and browser editors can inject an async storage adapter for OPFS, IndexedDB, ZIP
|
|
|
39
39
|
or File System Access API bridges. Storage adapters must implement `unlink()` so resource rename/move/remove
|
|
40
40
|
can clean up stale source files. Existing browser projects use a session-lifetime Web Lock: a live peer tab
|
|
41
41
|
receives `lock_conflict`, while reload or abrupt document termination releases ownership without leaving a
|
|
42
|
-
persistent `.openfairygui.backend.lock` marker.
|
|
42
|
+
persistent `.openfairygui.backend.lock` marker. The Node adapter instead keeps a token-protected lock beside
|
|
43
|
+
the project directory, recovers only valid same-host stale ownership, and rejects project-directory symbolic
|
|
44
|
+
links. When Web Locks are unavailable, the storage adapter must
|
|
43
45
|
provide `acquireSessionLock()` with the same atomic cross-context and owner-termination semantics. The default
|
|
44
46
|
Node filesystem/runtime lives under `@openfairygui/backend/node` and retains its advisory lock file behavior.
|
|
45
47
|
Adapter-backed `openSession` hydrates primary resource bytes so browser-safe transactions can rename/move
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,16 @@ function createRuntimePathPolicy() {
|
|
|
42
42
|
workspaceBoundary: "project-root-only"
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
async function assertProjectPathContained(fileSystem, projectRoot, targetPath) {
|
|
46
|
+
const [resolvedRoot, resolvedTarget] = await Promise.all([fileSystem.resolvePath(projectRoot), fileSystem.resolvePath(targetPath)]);
|
|
47
|
+
const root = normalizeComparablePath(resolvedRoot);
|
|
48
|
+
const target = normalizeComparablePath(resolvedTarget);
|
|
49
|
+
if (root === "." && !target.startsWith("/") && !/^[a-z]:\//i.test(target)) return;
|
|
50
|
+
if (target === root || target.startsWith(`${root}/`)) return;
|
|
51
|
+
const error = /* @__PURE__ */ new Error(`Project path escapes the opened root: ${targetPath}`);
|
|
52
|
+
error.code = "EACCES";
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
45
55
|
async function resolveFairyPath(fileSystem, input) {
|
|
46
56
|
const resolvedInput = fileSystem.resolve(input);
|
|
47
57
|
const stat = await fileSystem.stat(resolvedInput);
|
|
@@ -122,10 +132,14 @@ function failure(stage, startedAt, error, session, options) {
|
|
|
122
132
|
}
|
|
123
133
|
//#endregion
|
|
124
134
|
//#region src/services/session-project-writer.ts
|
|
125
|
-
function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
|
|
135
|
+
function createWriterFileSystem(fileSystem, projectRoot, writtenPaths, failedPaths) {
|
|
136
|
+
const contained = async (targetPath, operation) => {
|
|
137
|
+
await assertProjectPathContained(fileSystem, projectRoot, targetPath);
|
|
138
|
+
return operation();
|
|
139
|
+
};
|
|
126
140
|
async function trackWrite(targetPath, write) {
|
|
127
141
|
try {
|
|
128
|
-
const result = await write
|
|
142
|
+
const result = await contained(targetPath, write);
|
|
129
143
|
writtenPaths.push(targetPath);
|
|
130
144
|
return result;
|
|
131
145
|
} catch (error) {
|
|
@@ -134,8 +148,8 @@ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
|
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
return {
|
|
137
|
-
readFile: (path) => fileSystem.readFile(path),
|
|
138
|
-
readFileRaw: (path) => fileSystem.readFileRaw(path),
|
|
151
|
+
readFile: (path) => contained(path, () => fileSystem.readFile(path)),
|
|
152
|
+
readFileRaw: (path) => contained(path, () => fileSystem.readFileRaw(path)),
|
|
139
153
|
writeFile: (path, content) => trackWrite(path, async () => {
|
|
140
154
|
await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
|
|
141
155
|
await fileSystem.writeFile(path, content);
|
|
@@ -144,9 +158,10 @@ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
|
|
|
144
158
|
await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
|
|
145
159
|
await fileSystem.writeFileRaw(path, data);
|
|
146
160
|
}),
|
|
147
|
-
mkdir: (path) => fileSystem.mkdir(path, { recursive: true }),
|
|
148
|
-
readdir: (path) => fileSystem.readdir(path),
|
|
161
|
+
mkdir: (path) => contained(path, () => fileSystem.mkdir(path, { recursive: true })),
|
|
162
|
+
readdir: (path) => contained(path, () => fileSystem.readdir(path)),
|
|
149
163
|
async exists(path) {
|
|
164
|
+
await assertProjectPathContained(fileSystem, projectRoot, path);
|
|
150
165
|
try {
|
|
151
166
|
await fileSystem.stat(path);
|
|
152
167
|
return true;
|
|
@@ -161,11 +176,21 @@ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
|
|
|
161
176
|
};
|
|
162
177
|
}
|
|
163
178
|
async function writeSessionProject(input) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
const projectRoot = input.fileSystem.dirname(input.fairyPath);
|
|
180
|
+
const write = async (fileSystem) => {
|
|
181
|
+
await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(fileSystem, projectRoot, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, {
|
|
182
|
+
staleSourceFiles: input.staleSourceFiles,
|
|
183
|
+
staleResourceFolders: input.staleResourceFolders,
|
|
184
|
+
staleBranchDirectories: input.staleBranchDirectories
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
if (!input.fileSystem.runProjectWriteTransaction) return write(input.fileSystem);
|
|
188
|
+
try {
|
|
189
|
+
await input.fileSystem.runProjectWriteTransaction(projectRoot, write);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
input.writtenPaths.length = 0;
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
169
194
|
}
|
|
170
195
|
//#endregion
|
|
171
196
|
//#region src/services/session-utils.ts
|
|
@@ -479,7 +504,7 @@ var AuthoringService = class {
|
|
|
479
504
|
lastSavedRevision: session.lastSavedRevision,
|
|
480
505
|
committedPaths,
|
|
481
506
|
failedPaths,
|
|
482
|
-
diskMayBePartiallyUpdated:
|
|
507
|
+
diskMayBePartiallyUpdated: !fileSystem.runProjectWriteTransaction
|
|
483
508
|
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
484
509
|
sessionId: session.sessionId,
|
|
485
510
|
revision: session.revision
|
|
@@ -654,7 +679,7 @@ var AuthoringService = class {
|
|
|
654
679
|
failedPaths,
|
|
655
680
|
skippedPaths,
|
|
656
681
|
diagnostics: diagnosticsFromError,
|
|
657
|
-
diskMayBePartiallyUpdated:
|
|
682
|
+
diskMayBePartiallyUpdated: !fileSystem.runProjectWriteTransaction
|
|
658
683
|
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
659
684
|
sessionId: session.sessionId,
|
|
660
685
|
revision: session.revision,
|
|
@@ -1143,7 +1168,7 @@ var ReadService = class {
|
|
|
1143
1168
|
//#endregion
|
|
1144
1169
|
//#region src/services/runtime-service.ts
|
|
1145
1170
|
function randomId() {
|
|
1146
|
-
return
|
|
1171
|
+
return crypto.randomUUID();
|
|
1147
1172
|
}
|
|
1148
1173
|
function createCapabilityUnavailableError(capability) {
|
|
1149
1174
|
const artifactCapability = capability === "artifact.publish" || capability === "artifact.restore";
|
|
@@ -1156,29 +1181,33 @@ function createCapabilityUnavailableError(capability) {
|
|
|
1156
1181
|
bridgeBoundary: artifactCapability ? "external-bridge" : void 0
|
|
1157
1182
|
};
|
|
1158
1183
|
}
|
|
1159
|
-
function createProjectReaderFileSystem(fileSystem) {
|
|
1184
|
+
function createProjectReaderFileSystem(fileSystem, projectRoot) {
|
|
1185
|
+
const contained = async (filePath, operation) => {
|
|
1186
|
+
await assertProjectPathContained(fileSystem, projectRoot, filePath);
|
|
1187
|
+
return operation();
|
|
1188
|
+
};
|
|
1160
1189
|
return {
|
|
1161
1190
|
readFile(filePath) {
|
|
1162
|
-
return fileSystem.readFile(filePath);
|
|
1191
|
+
return contained(filePath, () => fileSystem.readFile(filePath));
|
|
1163
1192
|
},
|
|
1164
1193
|
readFileRaw(filePath) {
|
|
1165
|
-
return fileSystem.readFileRaw(filePath);
|
|
1194
|
+
return contained(filePath, () => fileSystem.readFileRaw(filePath));
|
|
1166
1195
|
},
|
|
1167
1196
|
writeFile(filePath, content) {
|
|
1168
|
-
return fileSystem.writeFile(filePath, content);
|
|
1197
|
+
return contained(filePath, () => fileSystem.writeFile(filePath, content));
|
|
1169
1198
|
},
|
|
1170
1199
|
writeFileRaw(filePath, data) {
|
|
1171
|
-
return fileSystem.writeFileRaw(filePath, data);
|
|
1200
|
+
return contained(filePath, () => fileSystem.writeFileRaw(filePath, data));
|
|
1172
1201
|
},
|
|
1173
1202
|
async mkdir(dirPath) {
|
|
1174
|
-
await fileSystem.mkdir(dirPath, { recursive: true });
|
|
1203
|
+
await contained(dirPath, () => fileSystem.mkdir(dirPath, { recursive: true }));
|
|
1175
1204
|
},
|
|
1176
1205
|
readdir(dirPath) {
|
|
1177
|
-
return fileSystem.readdir(dirPath);
|
|
1206
|
+
return contained(dirPath, () => fileSystem.readdir(dirPath));
|
|
1178
1207
|
},
|
|
1179
1208
|
async exists(filePath) {
|
|
1180
1209
|
try {
|
|
1181
|
-
await fileSystem.stat(filePath);
|
|
1210
|
+
await contained(filePath, () => fileSystem.stat(filePath));
|
|
1182
1211
|
return true;
|
|
1183
1212
|
} catch {
|
|
1184
1213
|
return false;
|
|
@@ -1272,9 +1301,25 @@ var RuntimeService = class {
|
|
|
1272
1301
|
const startedAt = Date.now();
|
|
1273
1302
|
if (!this.context.fileSystem) return failure("runtime", startedAt, createCapabilityUnavailableError("fileSystem"));
|
|
1274
1303
|
const fileSystem = this.context.fileSystem;
|
|
1304
|
+
if (this.context.allowedProjectRoots?.length) {
|
|
1305
|
+
let allowed = false;
|
|
1306
|
+
for (const root of this.context.allowedProjectRoots) try {
|
|
1307
|
+
await assertProjectPathContained(fileSystem, root, input.projectPath);
|
|
1308
|
+
allowed = true;
|
|
1309
|
+
break;
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (error.code !== "EACCES") throw error;
|
|
1312
|
+
}
|
|
1313
|
+
if (!allowed) return failure("runtime", startedAt, {
|
|
1314
|
+
code: "project_root_not_allowed",
|
|
1315
|
+
message: "Project path is outside the configured allowed roots.",
|
|
1316
|
+
projectPath: input.projectPath
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1275
1319
|
const { fairyPath, canonicalProjectPath, canonicalPathKey } = await resolveCanonicalProjectRoot(fileSystem, input.projectPath);
|
|
1320
|
+
await fileSystem.validateProjectRoot?.(canonicalProjectPath);
|
|
1276
1321
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
1277
|
-
const lockFilePath = fileSystem.join(canonicalProjectPath, ".openfairygui.backend.lock");
|
|
1322
|
+
const lockFilePath = fileSystem.getSessionLockPath?.(canonicalProjectPath) ?? fileSystem.join(canonicalProjectPath, ".openfairygui.backend.lock");
|
|
1278
1323
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
1279
1324
|
code: "lock_conflict",
|
|
1280
1325
|
kind: "in_process_session_exists",
|
|
@@ -1294,7 +1339,7 @@ var RuntimeService = class {
|
|
|
1294
1339
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1295
1340
|
canonicalPathKey
|
|
1296
1341
|
}));
|
|
1297
|
-
const read = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).readDetailed(fairyPath, { hydrateResourceBytes: true });
|
|
1342
|
+
const read = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem, canonicalProjectPath)).readDetailed(fairyPath, { hydrateResourceBytes: true });
|
|
1298
1343
|
if (!read.document) throw new Error(read.diagnostics[0]?.message ?? `Unable to read project: ${fairyPath}`);
|
|
1299
1344
|
const document = read.document;
|
|
1300
1345
|
const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(document);
|
|
@@ -1352,6 +1397,11 @@ var RuntimeService = class {
|
|
|
1352
1397
|
openProjectSession(input) {
|
|
1353
1398
|
const startedAt = Date.now();
|
|
1354
1399
|
const sessionId = input.sessionId ?? randomId();
|
|
1400
|
+
if (this.context.sessions.has(sessionId)) return failure("runtime", startedAt, {
|
|
1401
|
+
code: "session_id_conflict",
|
|
1402
|
+
message: `Session id is already in use: ${sessionId}`,
|
|
1403
|
+
sessionId
|
|
1404
|
+
});
|
|
1355
1405
|
const storage = input.storage;
|
|
1356
1406
|
const memoryProjectPath = `memory://${sessionId}`;
|
|
1357
1407
|
const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
|
|
@@ -1478,7 +1528,7 @@ const ARTIFACT_BRIDGE_CAPABILITY = {
|
|
|
1478
1528
|
bridgeEntrypoint: "@openfairygui/backend/node",
|
|
1479
1529
|
reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
|
|
1480
1530
|
};
|
|
1481
|
-
function createCapabilities() {
|
|
1531
|
+
function createCapabilities(atomicSave = false) {
|
|
1482
1532
|
return {
|
|
1483
1533
|
contractVersion: BACKEND_CONTRACT_VERSION,
|
|
1484
1534
|
capabilitySchemaVersion: 3,
|
|
@@ -1550,7 +1600,7 @@ function createCapabilities() {
|
|
|
1550
1600
|
sessionRuntime: true,
|
|
1551
1601
|
advisoryLocking: true,
|
|
1552
1602
|
coordinatedSave: true,
|
|
1553
|
-
atomicSave
|
|
1603
|
+
atomicSave,
|
|
1554
1604
|
staleRevisionProtection: true,
|
|
1555
1605
|
pathPolicy: createRuntimePathPolicy(),
|
|
1556
1606
|
events: {
|
|
@@ -1596,10 +1646,11 @@ var BackendRuntime = class {
|
|
|
1596
1646
|
jobService;
|
|
1597
1647
|
constructor(options = {}) {
|
|
1598
1648
|
this.fileSystem = options.fileSystem;
|
|
1599
|
-
this.capabilities = createCapabilities();
|
|
1649
|
+
this.capabilities = createCapabilities(Boolean(options.fileSystem?.runProjectWriteTransaction));
|
|
1600
1650
|
this.context = {
|
|
1601
1651
|
fileSystem: this.fileSystem,
|
|
1602
1652
|
host: options.host,
|
|
1653
|
+
allowedProjectRoots: options.allowedProjectRoots,
|
|
1603
1654
|
capabilities: this.capabilities,
|
|
1604
1655
|
sessions: this.sessions,
|
|
1605
1656
|
sessionsByPath: this.sessionsByPath,
|
|
@@ -1622,7 +1673,16 @@ var BackendRuntime = class {
|
|
|
1622
1673
|
return this.readService.getCapabilities();
|
|
1623
1674
|
}
|
|
1624
1675
|
async openSession(input) {
|
|
1625
|
-
|
|
1676
|
+
const startedAt = Date.now();
|
|
1677
|
+
try {
|
|
1678
|
+
return await this.runtimeService.openSession(input);
|
|
1679
|
+
} catch {
|
|
1680
|
+
return failure("runtime", startedAt, {
|
|
1681
|
+
code: "project_open_failed",
|
|
1682
|
+
message: "Unable to open project.",
|
|
1683
|
+
projectPath: input.projectPath
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1626
1686
|
}
|
|
1627
1687
|
openProjectSession(input) {
|
|
1628
1688
|
return this.runtimeService.openProjectSession(input);
|
|
@@ -1646,7 +1706,7 @@ var BackendRuntime = class {
|
|
|
1646
1706
|
return this.authoringService.materializeSession(input);
|
|
1647
1707
|
}
|
|
1648
1708
|
async closeSession(input) {
|
|
1649
|
-
return this.runtimeService.closeSession(input);
|
|
1709
|
+
return this.authoringService.runSessionExclusive(input.sessionId, () => this.runtimeService.closeSession(input));
|
|
1650
1710
|
}
|
|
1651
1711
|
getEvents(input) {
|
|
1652
1712
|
return this.eventService.getEvents(input);
|
package/dist/index.d.cts
CHANGED
|
@@ -73,6 +73,12 @@ interface BackendFileSystem {
|
|
|
73
73
|
recursive?: boolean;
|
|
74
74
|
}): Promise<void>;
|
|
75
75
|
resolvePath(filePath: string): Promise<string>;
|
|
76
|
+
/** Optional host validation before a project is read. Node rejects links anywhere in the project tree. */
|
|
77
|
+
validateProjectRoot?(projectRoot: string): Promise<void>;
|
|
78
|
+
/** Optional host-specific lock location. Node keeps it beside the project so directory swaps do not move it. */
|
|
79
|
+
getSessionLockPath?(canonicalProjectPath: string): string;
|
|
80
|
+
/** Runs project writes against a staged copy and commits them as one directory swap. */
|
|
81
|
+
runProjectWriteTransaction?(projectRoot: string, write: (stagedFileSystem: BackendFileSystem) => Promise<void>): Promise<void>;
|
|
76
82
|
acquireSessionLock(lockPath: string): Promise<BackendSessionLock>;
|
|
77
83
|
unlink(filePath: string): Promise<void>;
|
|
78
84
|
rmdir(dirPath: string): Promise<void>;
|
|
@@ -164,7 +170,7 @@ interface BackendCapabilities {
|
|
|
164
170
|
sessionRuntime: true;
|
|
165
171
|
advisoryLocking: true;
|
|
166
172
|
coordinatedSave: true;
|
|
167
|
-
atomicSave:
|
|
173
|
+
atomicSave: boolean;
|
|
168
174
|
staleRevisionProtection: true;
|
|
169
175
|
pathPolicy: {
|
|
170
176
|
canonicalization: 'realpath+normalized-casefold';
|
|
@@ -298,6 +304,11 @@ interface AdvisoryLockConflictError {
|
|
|
298
304
|
holderSessionId?: string;
|
|
299
305
|
lockFilePath: string;
|
|
300
306
|
}
|
|
307
|
+
interface SessionIdConflictError {
|
|
308
|
+
code: 'session_id_conflict';
|
|
309
|
+
message: string;
|
|
310
|
+
sessionId: string;
|
|
311
|
+
}
|
|
301
312
|
interface SavePartialFailureError {
|
|
302
313
|
code: 'save_partial_failure';
|
|
303
314
|
message: string;
|
|
@@ -307,7 +318,7 @@ interface SavePartialFailureError {
|
|
|
307
318
|
lastSavedRevision: number;
|
|
308
319
|
committedPaths: string[];
|
|
309
320
|
failedPaths: string[];
|
|
310
|
-
diskMayBePartiallyUpdated:
|
|
321
|
+
diskMayBePartiallyUpdated: boolean;
|
|
311
322
|
}
|
|
312
323
|
interface UamFidelityUnsupportedError {
|
|
313
324
|
code: 'uam_fidelity_unsupported';
|
|
@@ -334,7 +345,7 @@ interface MaterializeWriteFailedError {
|
|
|
334
345
|
failedPaths: string[];
|
|
335
346
|
skippedPaths: string[];
|
|
336
347
|
diagnostics: BackendDiagnostic[];
|
|
337
|
-
diskMayBePartiallyUpdated:
|
|
348
|
+
diskMayBePartiallyUpdated: boolean;
|
|
338
349
|
}
|
|
339
350
|
type BackendEventKind = 'session.opened' | 'transaction.applied' | 'transaction.rejected' | 'save.started' | 'save.completed' | 'save.failed' | 'session.closeRequested' | 'session.closed' | 'cache.invalidated' | 'cache.updated' | 'job.created' | 'job.started' | 'job.progress' | 'job.cancelRequested' | 'job.cancelled' | 'job.completed' | 'job.failed';
|
|
340
351
|
interface BackendEvent {
|
|
@@ -467,7 +478,17 @@ interface RefreshCacheInput {
|
|
|
467
478
|
sessionId: string;
|
|
468
479
|
reason?: 'manual' | 'session_open' | 'after_save';
|
|
469
480
|
}
|
|
470
|
-
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
481
|
+
type BackendError = SessionNotFoundError | SessionIdConflictError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ProjectRootNotAllowedError | ProjectOpenFailedError | ApplyUamTransactionAppError;
|
|
482
|
+
interface ProjectRootNotAllowedError {
|
|
483
|
+
code: 'project_root_not_allowed';
|
|
484
|
+
message: string;
|
|
485
|
+
projectPath: string;
|
|
486
|
+
}
|
|
487
|
+
interface ProjectOpenFailedError {
|
|
488
|
+
code: 'project_open_failed';
|
|
489
|
+
message: string;
|
|
490
|
+
projectPath: string;
|
|
491
|
+
}
|
|
471
492
|
interface ApplySessionTransactionInput {
|
|
472
493
|
sessionId: string;
|
|
473
494
|
expectedRevision: number;
|
|
@@ -514,6 +535,8 @@ interface MaterializeSessionInput {
|
|
|
514
535
|
interface BackendRuntimeOptions {
|
|
515
536
|
fileSystem?: BackendFileSystem;
|
|
516
537
|
host?: BackendHostAdapter;
|
|
538
|
+
/** Canonical filesystem roots available to file-backed sessions. Omit for unrestricted library use. */
|
|
539
|
+
allowedProjectRoots?: readonly string[];
|
|
517
540
|
}
|
|
518
541
|
//#endregion
|
|
519
542
|
//#region src/runtime.d.ts
|
|
@@ -537,8 +560,8 @@ declare class BackendRuntime {
|
|
|
537
560
|
getCapabilities(): BackendSuccess<BackendCapabilities>;
|
|
538
561
|
openSession(input: {
|
|
539
562
|
projectPath: string;
|
|
540
|
-
}): Promise<BackendResult<BackendSessionSnapshot, InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError>>;
|
|
541
|
-
openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot>;
|
|
563
|
+
}): Promise<BackendResult<BackendSessionSnapshot, InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError | ProjectRootNotAllowedError | ProjectOpenFailedError>>;
|
|
564
|
+
openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot, InProcessLockConflictError | SessionIdConflictError>;
|
|
542
565
|
getSession(input: {
|
|
543
566
|
sessionId: string;
|
|
544
567
|
}): BackendResult<BackendSessionSnapshot, SessionNotFoundError>;
|
|
@@ -596,4 +619,4 @@ interface BackendAsyncStorageAdapter {
|
|
|
596
619
|
type BackendStorageFileSystem = BackendFileSystem & FileSystem;
|
|
597
620
|
declare function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapter): BackendStorageFileSystem;
|
|
598
621
|
//#endregion
|
|
599
|
-
export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectOutline, type BackendProjectOutlinePackage, type BackendProjectOutlineResource, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionLock, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type GetProjectOutlineInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, type UamFidelityUnsupportedError, type ValidateSessionInput, createBackendStorageFileSystem };
|
|
622
|
+
export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectOutline, type BackendProjectOutlinePackage, type BackendProjectOutlineResource, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionLock, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type GetProjectOutlineInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type ProjectOpenFailedError, type ProjectRootNotAllowedError, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionIdConflictError, type SessionNotFoundError, type SessionStaleWriteError, type UamFidelityUnsupportedError, type ValidateSessionInput, createBackendStorageFileSystem };
|
package/dist/index.d.mts
CHANGED
|
@@ -73,6 +73,12 @@ interface BackendFileSystem {
|
|
|
73
73
|
recursive?: boolean;
|
|
74
74
|
}): Promise<void>;
|
|
75
75
|
resolvePath(filePath: string): Promise<string>;
|
|
76
|
+
/** Optional host validation before a project is read. Node rejects links anywhere in the project tree. */
|
|
77
|
+
validateProjectRoot?(projectRoot: string): Promise<void>;
|
|
78
|
+
/** Optional host-specific lock location. Node keeps it beside the project so directory swaps do not move it. */
|
|
79
|
+
getSessionLockPath?(canonicalProjectPath: string): string;
|
|
80
|
+
/** Runs project writes against a staged copy and commits them as one directory swap. */
|
|
81
|
+
runProjectWriteTransaction?(projectRoot: string, write: (stagedFileSystem: BackendFileSystem) => Promise<void>): Promise<void>;
|
|
76
82
|
acquireSessionLock(lockPath: string): Promise<BackendSessionLock>;
|
|
77
83
|
unlink(filePath: string): Promise<void>;
|
|
78
84
|
rmdir(dirPath: string): Promise<void>;
|
|
@@ -164,7 +170,7 @@ interface BackendCapabilities {
|
|
|
164
170
|
sessionRuntime: true;
|
|
165
171
|
advisoryLocking: true;
|
|
166
172
|
coordinatedSave: true;
|
|
167
|
-
atomicSave:
|
|
173
|
+
atomicSave: boolean;
|
|
168
174
|
staleRevisionProtection: true;
|
|
169
175
|
pathPolicy: {
|
|
170
176
|
canonicalization: 'realpath+normalized-casefold';
|
|
@@ -298,6 +304,11 @@ interface AdvisoryLockConflictError {
|
|
|
298
304
|
holderSessionId?: string;
|
|
299
305
|
lockFilePath: string;
|
|
300
306
|
}
|
|
307
|
+
interface SessionIdConflictError {
|
|
308
|
+
code: 'session_id_conflict';
|
|
309
|
+
message: string;
|
|
310
|
+
sessionId: string;
|
|
311
|
+
}
|
|
301
312
|
interface SavePartialFailureError {
|
|
302
313
|
code: 'save_partial_failure';
|
|
303
314
|
message: string;
|
|
@@ -307,7 +318,7 @@ interface SavePartialFailureError {
|
|
|
307
318
|
lastSavedRevision: number;
|
|
308
319
|
committedPaths: string[];
|
|
309
320
|
failedPaths: string[];
|
|
310
|
-
diskMayBePartiallyUpdated:
|
|
321
|
+
diskMayBePartiallyUpdated: boolean;
|
|
311
322
|
}
|
|
312
323
|
interface UamFidelityUnsupportedError {
|
|
313
324
|
code: 'uam_fidelity_unsupported';
|
|
@@ -334,7 +345,7 @@ interface MaterializeWriteFailedError {
|
|
|
334
345
|
failedPaths: string[];
|
|
335
346
|
skippedPaths: string[];
|
|
336
347
|
diagnostics: BackendDiagnostic[];
|
|
337
|
-
diskMayBePartiallyUpdated:
|
|
348
|
+
diskMayBePartiallyUpdated: boolean;
|
|
338
349
|
}
|
|
339
350
|
type BackendEventKind = 'session.opened' | 'transaction.applied' | 'transaction.rejected' | 'save.started' | 'save.completed' | 'save.failed' | 'session.closeRequested' | 'session.closed' | 'cache.invalidated' | 'cache.updated' | 'job.created' | 'job.started' | 'job.progress' | 'job.cancelRequested' | 'job.cancelled' | 'job.completed' | 'job.failed';
|
|
340
351
|
interface BackendEvent {
|
|
@@ -467,7 +478,17 @@ interface RefreshCacheInput {
|
|
|
467
478
|
sessionId: string;
|
|
468
479
|
reason?: 'manual' | 'session_open' | 'after_save';
|
|
469
480
|
}
|
|
470
|
-
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
481
|
+
type BackendError = SessionNotFoundError | SessionIdConflictError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ProjectRootNotAllowedError | ProjectOpenFailedError | ApplyUamTransactionAppError;
|
|
482
|
+
interface ProjectRootNotAllowedError {
|
|
483
|
+
code: 'project_root_not_allowed';
|
|
484
|
+
message: string;
|
|
485
|
+
projectPath: string;
|
|
486
|
+
}
|
|
487
|
+
interface ProjectOpenFailedError {
|
|
488
|
+
code: 'project_open_failed';
|
|
489
|
+
message: string;
|
|
490
|
+
projectPath: string;
|
|
491
|
+
}
|
|
471
492
|
interface ApplySessionTransactionInput {
|
|
472
493
|
sessionId: string;
|
|
473
494
|
expectedRevision: number;
|
|
@@ -514,6 +535,8 @@ interface MaterializeSessionInput {
|
|
|
514
535
|
interface BackendRuntimeOptions {
|
|
515
536
|
fileSystem?: BackendFileSystem;
|
|
516
537
|
host?: BackendHostAdapter;
|
|
538
|
+
/** Canonical filesystem roots available to file-backed sessions. Omit for unrestricted library use. */
|
|
539
|
+
allowedProjectRoots?: readonly string[];
|
|
517
540
|
}
|
|
518
541
|
//#endregion
|
|
519
542
|
//#region src/runtime.d.ts
|
|
@@ -537,8 +560,8 @@ declare class BackendRuntime {
|
|
|
537
560
|
getCapabilities(): BackendSuccess<BackendCapabilities>;
|
|
538
561
|
openSession(input: {
|
|
539
562
|
projectPath: string;
|
|
540
|
-
}): Promise<BackendResult<BackendSessionSnapshot, InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError>>;
|
|
541
|
-
openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot>;
|
|
563
|
+
}): Promise<BackendResult<BackendSessionSnapshot, InProcessLockConflictError | AdvisoryLockConflictError | BackendCapabilityUnavailableError | ProjectRootNotAllowedError | ProjectOpenFailedError>>;
|
|
564
|
+
openProjectSession(input: OpenProjectSessionInput): BackendResult<BackendSessionSnapshot, InProcessLockConflictError | SessionIdConflictError>;
|
|
542
565
|
getSession(input: {
|
|
543
566
|
sessionId: string;
|
|
544
567
|
}): BackendResult<BackendSessionSnapshot, SessionNotFoundError>;
|
|
@@ -596,4 +619,4 @@ interface BackendAsyncStorageAdapter {
|
|
|
596
619
|
type BackendStorageFileSystem = BackendFileSystem & FileSystem;
|
|
597
620
|
declare function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapter): BackendStorageFileSystem;
|
|
598
621
|
//#endregion
|
|
599
|
-
export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectOutline, type BackendProjectOutlinePackage, type BackendProjectOutlineResource, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionLock, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type GetProjectOutlineInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, type UamFidelityUnsupportedError, type ValidateSessionInput, createBackendStorageFileSystem };
|
|
622
|
+
export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectOutline, type BackendProjectOutlinePackage, type BackendProjectOutlineResource, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionLock, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type GetProjectOutlineInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type ProjectOpenFailedError, type ProjectRootNotAllowedError, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionIdConflictError, type SessionNotFoundError, type SessionStaleWriteError, type UamFidelityUnsupportedError, type ValidateSessionInput, createBackendStorageFileSystem };
|