@openfairygui/backend 0.2.0-alpha.8 → 0.2.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 +29 -8
- package/dist/index.cjs +1633 -27
- package/dist/index.d.cts +516 -4
- package/dist/index.d.mts +516 -4
- package/dist/index.mjs +1629 -23
- package/dist/node.cjs +1603 -9
- package/dist/node.d.cts +507 -2
- package/dist/node.d.mts +507 -2
- package/dist/node.mjs +1601 -6
- package/package.json +7 -6
- package/src/index.ts +18 -17
- package/src/node.ts +24 -8
- package/src/runtime/capabilities.ts +126 -0
- package/src/runtime/contracts.ts +518 -0
- package/src/runtime.ts +49 -623
- package/src/services/authoring-service.ts +242 -99
- package/src/services/cache-service.ts +1 -2
- package/src/services/context.ts +17 -8
- package/src/services/event-service.ts +1 -2
- package/src/services/job-service.ts +4 -5
- package/src/services/read-service.ts +1 -2
- package/src/services/runtime-service.ts +133 -27
- package/src/services/session-project-writer.ts +69 -0
- package/src/services/session-utils.ts +6 -3
- package/src/storage.ts +67 -27
- package/dist/runtime-DFatY9W0.mjs +0 -1417
- package/dist/runtime-GKzsXJdO.cjs +0 -1441
- package/dist/runtime-GyNVxAQ0.d.mts +0 -494
- package/dist/runtime-Jec6FcF5.d.cts +0 -494
- package/src/services/snapshot-utils.ts +0 -43
|
@@ -1,1441 +0,0 @@
|
|
|
1
|
-
require("./node.cjs");
|
|
2
|
-
let _openfairygui_core_uam = require("@openfairygui/core/uam");
|
|
3
|
-
let _openfairygui_core_project_io = require("@openfairygui/core/project-io");
|
|
4
|
-
let _openfairygui_functions_uam = require("@openfairygui/functions/uam");
|
|
5
|
-
//#region src/contracts.ts
|
|
6
|
-
const BACKEND_CONTRACT_VERSION = "1.1.0-p2";
|
|
7
|
-
const BACKEND_CAPABILITY_SCHEMA_VERSION = 2;
|
|
8
|
-
const BACKEND_COMPATIBILITY_POLICY = {
|
|
9
|
-
incompatibleChange: "requires contractVersion bump",
|
|
10
|
-
capabilitySchemaChange: "requires capabilitySchemaVersion bump",
|
|
11
|
-
additiveChange: "allowed without breaking existing consumers"
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/path-policy.ts
|
|
15
|
-
function normalizeComparablePath(value) {
|
|
16
|
-
const normalized = value.replace(/[/\\]+$/, "").replace(/\\/g, "/");
|
|
17
|
-
const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
|
|
18
|
-
const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
|
|
19
|
-
const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
|
|
20
|
-
const hasRoot = driveMatch ? true : remainder.startsWith("/");
|
|
21
|
-
const rawSegments = remainder.split("/").filter((segment) => segment.length > 0);
|
|
22
|
-
const segments = [];
|
|
23
|
-
for (const segment of rawSegments) {
|
|
24
|
-
if (segment === ".") continue;
|
|
25
|
-
if (segment === "..") {
|
|
26
|
-
if (segments.length > 0 && segments[segments.length - 1] !== "..") segments.pop();
|
|
27
|
-
else if (!hasRoot) segments.push("..");
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
segments.push(segment);
|
|
31
|
-
}
|
|
32
|
-
const joined = segments.join("/");
|
|
33
|
-
return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
|
|
34
|
-
}
|
|
35
|
-
function createRuntimePathPolicy() {
|
|
36
|
-
return {
|
|
37
|
-
canonicalization: "realpath+normalized-casefold",
|
|
38
|
-
sessionIdentity: "project-root",
|
|
39
|
-
saveTarget: "opened-project-only",
|
|
40
|
-
outputTargets: "deferred",
|
|
41
|
-
workspaceBoundary: "project-root-only"
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
async function resolveFairyPath(fileSystem, input) {
|
|
45
|
-
const resolvedInput = fileSystem.resolve(input);
|
|
46
|
-
const stat = await fileSystem.stat(resolvedInput);
|
|
47
|
-
if (stat.isFile() && resolvedInput.endsWith(".fairy")) return await fileSystem.resolvePath(resolvedInput);
|
|
48
|
-
if (stat.isDirectory()) {
|
|
49
|
-
const fairyFiles = (await fileSystem.readdir(resolvedInput)).filter((entry) => entry.endsWith(".fairy"));
|
|
50
|
-
if (fairyFiles.length === 1) return await fileSystem.resolvePath(fileSystem.join(resolvedInput, fairyFiles[0]));
|
|
51
|
-
if (fairyFiles.length > 1) throw new Error(`Multiple .fairy files found in ${resolvedInput}: ${fairyFiles.join(", ")}`);
|
|
52
|
-
throw new Error(`No .fairy file found in ${resolvedInput}`);
|
|
53
|
-
}
|
|
54
|
-
throw new Error(`Input is not a .fairy file or directory: ${resolvedInput}`);
|
|
55
|
-
}
|
|
56
|
-
async function resolveCanonicalProjectRoot(fileSystem, input) {
|
|
57
|
-
const fairyPath = await resolveFairyPath(fileSystem, input);
|
|
58
|
-
const canonicalProjectPath = await fileSystem.resolvePath(fileSystem.dirname(fairyPath));
|
|
59
|
-
return {
|
|
60
|
-
fairyPath,
|
|
61
|
-
canonicalProjectPath,
|
|
62
|
-
canonicalPathKey: normalizeComparablePath(canonicalProjectPath)
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
async function validateSaveTarget(fileSystem, openedFairyPath, targetPath) {
|
|
66
|
-
if (!targetPath) return null;
|
|
67
|
-
const attemptedPath = await fileSystem.resolvePath(fileSystem.resolve(targetPath));
|
|
68
|
-
const allowedPath = await fileSystem.resolvePath(openedFairyPath);
|
|
69
|
-
if (normalizeComparablePath(attemptedPath) === normalizeComparablePath(allowedPath)) return null;
|
|
70
|
-
return {
|
|
71
|
-
code: "path_policy_violation",
|
|
72
|
-
message: `Save target is restricted to the originally opened project file: ${allowedPath}`,
|
|
73
|
-
policy: "save_target",
|
|
74
|
-
attemptedPath,
|
|
75
|
-
allowedPath
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
//#endregion
|
|
79
|
-
//#region src/services/artifact-service.ts
|
|
80
|
-
function createArtifactCapabilities() {
|
|
81
|
-
const bridge = {
|
|
82
|
-
available: false,
|
|
83
|
-
requiredHost: "node",
|
|
84
|
-
executionBoundary: "external-bridge",
|
|
85
|
-
bridgeEntrypoint: "@openfairygui/backend/node",
|
|
86
|
-
reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
publish: false,
|
|
90
|
-
restore: false,
|
|
91
|
-
status: "bridge-required",
|
|
92
|
-
publishBridge: bridge,
|
|
93
|
-
restoreBridge: bridge
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
//#endregion
|
|
97
|
-
//#region src/services/context.ts
|
|
98
|
-
function diagnosticFromError(error) {
|
|
99
|
-
return {
|
|
100
|
-
code: error.code,
|
|
101
|
-
message: error.message,
|
|
102
|
-
severity: "error"
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
function randomId$1() {
|
|
106
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
107
|
-
}
|
|
108
|
-
function createMeta(stage, startedAt, options) {
|
|
109
|
-
return {
|
|
110
|
-
requestId: options?.requestId ?? randomId$1(),
|
|
111
|
-
sessionId: options?.sessionId,
|
|
112
|
-
revision: options?.revision,
|
|
113
|
-
durationMs: Math.max(0, Date.now() - startedAt),
|
|
114
|
-
warnings: options?.warnings ?? [],
|
|
115
|
-
diagnostics: options?.diagnostics ?? [],
|
|
116
|
-
stage,
|
|
117
|
-
contractVersion: BACKEND_CONTRACT_VERSION,
|
|
118
|
-
capabilitySchemaVersion: 2
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function success(stage, startedAt, data, options) {
|
|
122
|
-
return {
|
|
123
|
-
ok: true,
|
|
124
|
-
meta: createMeta(stage, startedAt, options),
|
|
125
|
-
data
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function failure(stage, startedAt, error, session, options) {
|
|
129
|
-
const diagnostics = options?.diagnostics ?? [diagnosticFromError(error)];
|
|
130
|
-
return {
|
|
131
|
-
ok: false,
|
|
132
|
-
meta: createMeta(stage, startedAt, {
|
|
133
|
-
...options,
|
|
134
|
-
diagnostics
|
|
135
|
-
}),
|
|
136
|
-
error,
|
|
137
|
-
session
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
//#endregion
|
|
141
|
-
//#region src/services/snapshot-utils.ts
|
|
142
|
-
function cloneJsonValue(value) {
|
|
143
|
-
if (value === void 0 || value === null) return value;
|
|
144
|
-
return JSON.parse(JSON.stringify(value));
|
|
145
|
-
}
|
|
146
|
-
function cloneEventSnapshot(event) {
|
|
147
|
-
return {
|
|
148
|
-
...event,
|
|
149
|
-
diagnostics: event.diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
150
|
-
payload: cloneJsonValue(event.payload)
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function cloneJobSnapshot(job) {
|
|
154
|
-
return {
|
|
155
|
-
...job,
|
|
156
|
-
diagnostics: job.diagnostics.map((diagnostic) => ({ ...diagnostic })),
|
|
157
|
-
progress: job.progress ? { ...job.progress } : void 0,
|
|
158
|
-
result: cloneJsonValue(job.result),
|
|
159
|
-
error: cloneJsonValue(job.error)
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function cloneCacheEntrySnapshot(entry) {
|
|
163
|
-
return {
|
|
164
|
-
...entry,
|
|
165
|
-
summary: {
|
|
166
|
-
...entry.summary,
|
|
167
|
-
diagnostics: entry.summary.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function cloneCapabilitiesSnapshot(capabilities) {
|
|
172
|
-
return cloneJsonValue(capabilities);
|
|
173
|
-
}
|
|
174
|
-
//#endregion
|
|
175
|
-
//#region src/services/session-utils.ts
|
|
176
|
-
function toSessionSnapshot(session, capabilities) {
|
|
177
|
-
return {
|
|
178
|
-
sessionId: session.sessionId,
|
|
179
|
-
canonicalProjectPath: session.canonicalProjectPath,
|
|
180
|
-
revision: session.revision,
|
|
181
|
-
lastSavedRevision: session.lastSavedRevision,
|
|
182
|
-
dirty: session.dirty,
|
|
183
|
-
lockHeld: session.lockHeld,
|
|
184
|
-
capabilities: cloneCapabilitiesSnapshot(capabilities)
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function createSessionNotFoundError(sessionId) {
|
|
188
|
-
return {
|
|
189
|
-
code: "session_not_found",
|
|
190
|
-
message: `Session was not found: ${sessionId}`,
|
|
191
|
-
sessionId
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function createStaleWriteError(session, expectedRevision) {
|
|
195
|
-
return {
|
|
196
|
-
code: "stale_write",
|
|
197
|
-
message: `Expected revision ${expectedRevision} does not match current revision ${session.revision}.`,
|
|
198
|
-
sessionId: session.sessionId,
|
|
199
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
200
|
-
expectedRevision,
|
|
201
|
-
actualRevision: session.revision
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
//#endregion
|
|
205
|
-
//#region src/services/authoring-service.ts
|
|
206
|
-
function createWriterFileSystem(fileSystem, committedPaths, failedPaths) {
|
|
207
|
-
async function trackWrite(targetPath, fn) {
|
|
208
|
-
try {
|
|
209
|
-
const result = await fn();
|
|
210
|
-
committedPaths.push(targetPath);
|
|
211
|
-
return result;
|
|
212
|
-
} catch (error) {
|
|
213
|
-
failedPaths.push(targetPath);
|
|
214
|
-
throw error;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
async readFile(filePath) {
|
|
219
|
-
return fileSystem.readFile(filePath);
|
|
220
|
-
},
|
|
221
|
-
async readFileRaw(filePath) {
|
|
222
|
-
return fileSystem.readFileRaw(filePath);
|
|
223
|
-
},
|
|
224
|
-
async writeFile(filePath, content) {
|
|
225
|
-
await trackWrite(filePath, async () => {
|
|
226
|
-
await fileSystem.mkdir(fileSystem.dirname(filePath), { recursive: true });
|
|
227
|
-
await fileSystem.writeFile(filePath, content);
|
|
228
|
-
});
|
|
229
|
-
},
|
|
230
|
-
async writeFileRaw(filePath, data) {
|
|
231
|
-
await trackWrite(filePath, async () => {
|
|
232
|
-
await fileSystem.mkdir(fileSystem.dirname(filePath), { recursive: true });
|
|
233
|
-
await fileSystem.writeFileRaw(filePath, data);
|
|
234
|
-
});
|
|
235
|
-
},
|
|
236
|
-
async mkdir(dirPath) {
|
|
237
|
-
await fileSystem.mkdir(dirPath, { recursive: true });
|
|
238
|
-
},
|
|
239
|
-
async readdir(dirPath) {
|
|
240
|
-
return fileSystem.readdir(dirPath);
|
|
241
|
-
},
|
|
242
|
-
async exists(filePath) {
|
|
243
|
-
try {
|
|
244
|
-
await fileSystem.stat(filePath);
|
|
245
|
-
return true;
|
|
246
|
-
} catch {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
join(...paths) {
|
|
251
|
-
return fileSystem.join(...paths);
|
|
252
|
-
},
|
|
253
|
-
dirname(filePath) {
|
|
254
|
-
return fileSystem.dirname(filePath);
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
function toBackendDiagnostics(error) {
|
|
259
|
-
return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
|
|
260
|
-
code: error.code,
|
|
261
|
-
message: error.message,
|
|
262
|
-
severity: "error",
|
|
263
|
-
operationKind: error.operationKind,
|
|
264
|
-
opIndex: error.opIndex,
|
|
265
|
-
opId: error.opId
|
|
266
|
-
}];
|
|
267
|
-
}
|
|
268
|
-
function createCapabilityUnavailableError$1(message) {
|
|
269
|
-
return {
|
|
270
|
-
code: "capability_unavailable",
|
|
271
|
-
message,
|
|
272
|
-
capability: "fileSystem",
|
|
273
|
-
requiredAdapter: "BackendFileSystem"
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
function validationDiagnostics(sessionProject) {
|
|
277
|
-
return (0, _openfairygui_core_uam.validateUamProject)(sessionProject).map((issue) => ({
|
|
278
|
-
code: "materialize_validation_failed",
|
|
279
|
-
message: issue.message,
|
|
280
|
-
severity: "error",
|
|
281
|
-
path: issue.path,
|
|
282
|
-
operationKind: "materializeSession"
|
|
283
|
-
}));
|
|
284
|
-
}
|
|
285
|
-
function toMaterializeSnapshot(session, capabilities, input) {
|
|
286
|
-
return {
|
|
287
|
-
...toSessionSnapshot(session, capabilities),
|
|
288
|
-
mode: "fullProject",
|
|
289
|
-
reason: input.reason,
|
|
290
|
-
materializeRevision: session.revision,
|
|
291
|
-
saveRevision: session.lastSavedRevision,
|
|
292
|
-
writtenPaths: [...input.writtenPaths],
|
|
293
|
-
skippedPaths: [...input.skippedPaths],
|
|
294
|
-
diagnostics: input.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
function storageCanonicalTarget(input) {
|
|
298
|
-
const canonicalProjectPath = input.canonicalProjectPath ?? (input.fileSystem.dirname(input.fairyPath) || ".");
|
|
299
|
-
return {
|
|
300
|
-
fileSystem: input.fileSystem,
|
|
301
|
-
fairyPath: input.fairyPath,
|
|
302
|
-
canonicalProjectPath,
|
|
303
|
-
canonicalPathKey: input.canonicalPathKey ?? normalizeComparablePath(canonicalProjectPath)
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
var AuthoringService = class {
|
|
307
|
-
constructor(context, cacheService, eventService) {
|
|
308
|
-
this.context = context;
|
|
309
|
-
this.cacheService = cacheService;
|
|
310
|
-
this.eventService = eventService;
|
|
311
|
-
}
|
|
312
|
-
async applyTransaction(input) {
|
|
313
|
-
const startedAt = Date.now();
|
|
314
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
315
|
-
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
316
|
-
if (input.expectedRevision !== session.revision) {
|
|
317
|
-
this.eventService.emit({
|
|
318
|
-
kind: "transaction.rejected",
|
|
319
|
-
sessionId: session.sessionId,
|
|
320
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
321
|
-
revision: session.revision
|
|
322
|
-
});
|
|
323
|
-
return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
324
|
-
sessionId: session.sessionId,
|
|
325
|
-
revision: session.revision
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
const result = (0, _openfairygui_functions_uam.applyUamTransactionApp)({
|
|
329
|
-
project: session.project,
|
|
330
|
-
operations: input.operations
|
|
331
|
-
});
|
|
332
|
-
if (result.ok === false) {
|
|
333
|
-
const diagnostics = toBackendDiagnostics(result.error);
|
|
334
|
-
this.eventService.emit({
|
|
335
|
-
kind: "transaction.rejected",
|
|
336
|
-
sessionId: session.sessionId,
|
|
337
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
338
|
-
revision: session.revision,
|
|
339
|
-
diagnostics
|
|
340
|
-
});
|
|
341
|
-
return failure("authoring", startedAt, result.error, toSessionSnapshot(session, this.context.capabilities), {
|
|
342
|
-
sessionId: session.sessionId,
|
|
343
|
-
revision: session.revision,
|
|
344
|
-
diagnostics
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
session.project = result.project;
|
|
348
|
-
session.revision += 1;
|
|
349
|
-
session.dirty = true;
|
|
350
|
-
const cacheEntry = this.cacheService.invalidateSession(session);
|
|
351
|
-
this.eventService.emit({
|
|
352
|
-
kind: "transaction.applied",
|
|
353
|
-
sessionId: session.sessionId,
|
|
354
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
355
|
-
revision: session.revision
|
|
356
|
-
});
|
|
357
|
-
this.eventService.emit({
|
|
358
|
-
kind: "cache.invalidated",
|
|
359
|
-
sessionId: session.sessionId,
|
|
360
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
361
|
-
revision: session.revision,
|
|
362
|
-
cacheRevision: cacheEntry.revision
|
|
363
|
-
});
|
|
364
|
-
return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
365
|
-
sessionId: session.sessionId,
|
|
366
|
-
revision: session.revision
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
async saveSession(input) {
|
|
370
|
-
if (input.force === true || input.mode === "materializeCleanSession") return this.materializeSession({
|
|
371
|
-
sessionId: input.sessionId,
|
|
372
|
-
expectedRevision: input.expectedRevision,
|
|
373
|
-
targetPath: input.targetPath,
|
|
374
|
-
fileSystem: input.fileSystem,
|
|
375
|
-
mode: "fullProject",
|
|
376
|
-
reason: "force_save"
|
|
377
|
-
});
|
|
378
|
-
const startedAt = Date.now();
|
|
379
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
380
|
-
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
381
|
-
const fileSystem = input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
382
|
-
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
383
|
-
sessionId: session.sessionId,
|
|
384
|
-
revision: session.revision
|
|
385
|
-
});
|
|
386
|
-
if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
387
|
-
sessionId: session.sessionId,
|
|
388
|
-
revision: session.revision
|
|
389
|
-
});
|
|
390
|
-
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
391
|
-
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
392
|
-
sessionId: session.sessionId,
|
|
393
|
-
revision: session.revision
|
|
394
|
-
});
|
|
395
|
-
if (!session.dirty) return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
396
|
-
sessionId: session.sessionId,
|
|
397
|
-
revision: session.revision
|
|
398
|
-
});
|
|
399
|
-
const committedPaths = [];
|
|
400
|
-
const failedPaths = [];
|
|
401
|
-
this.eventService.emit({
|
|
402
|
-
kind: "save.started",
|
|
403
|
-
sessionId: session.sessionId,
|
|
404
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
405
|
-
revision: session.revision
|
|
406
|
-
});
|
|
407
|
-
try {
|
|
408
|
-
await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(fileSystem, committedPaths, failedPaths)).write((0, _openfairygui_core_uam.materializeUamProject)(session.project), session.fairyPath);
|
|
409
|
-
session.lastSavedRevision = session.revision;
|
|
410
|
-
session.dirty = false;
|
|
411
|
-
const cacheEntry = this.cacheService.refreshSession(session);
|
|
412
|
-
this.eventService.emit({
|
|
413
|
-
kind: "save.completed",
|
|
414
|
-
sessionId: session.sessionId,
|
|
415
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
416
|
-
revision: session.revision
|
|
417
|
-
});
|
|
418
|
-
this.eventService.emit({
|
|
419
|
-
kind: "cache.updated",
|
|
420
|
-
sessionId: session.sessionId,
|
|
421
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
422
|
-
revision: session.revision,
|
|
423
|
-
cacheRevision: cacheEntry.revision
|
|
424
|
-
});
|
|
425
|
-
return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
426
|
-
sessionId: session.sessionId,
|
|
427
|
-
revision: session.revision
|
|
428
|
-
});
|
|
429
|
-
} catch (error) {
|
|
430
|
-
this.cacheService.invalidateSession(session);
|
|
431
|
-
this.eventService.emit({
|
|
432
|
-
kind: "save.failed",
|
|
433
|
-
sessionId: session.sessionId,
|
|
434
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
435
|
-
revision: session.revision
|
|
436
|
-
});
|
|
437
|
-
return failure("authoring", startedAt, {
|
|
438
|
-
code: "save_partial_failure",
|
|
439
|
-
message: error instanceof Error ? error.message : String(error),
|
|
440
|
-
sessionId: session.sessionId,
|
|
441
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
442
|
-
attemptedRevision: session.revision,
|
|
443
|
-
lastSavedRevision: session.lastSavedRevision,
|
|
444
|
-
committedPaths,
|
|
445
|
-
failedPaths,
|
|
446
|
-
diskMayBePartiallyUpdated: true
|
|
447
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
448
|
-
sessionId: session.sessionId,
|
|
449
|
-
revision: session.revision
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
async materializeSession(input) {
|
|
454
|
-
const startedAt = Date.now();
|
|
455
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
456
|
-
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
457
|
-
if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
458
|
-
sessionId: session.sessionId,
|
|
459
|
-
revision: session.revision
|
|
460
|
-
});
|
|
461
|
-
const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
|
|
462
|
-
const fileSystem = storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
463
|
-
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("materializeSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
464
|
-
sessionId: session.sessionId,
|
|
465
|
-
revision: session.revision
|
|
466
|
-
});
|
|
467
|
-
const fairyPath = storageTarget?.fairyPath ?? session.fairyPath;
|
|
468
|
-
const targetViolation = await validateSaveTarget(fileSystem, fairyPath, input.targetPath);
|
|
469
|
-
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
470
|
-
sessionId: session.sessionId,
|
|
471
|
-
revision: session.revision
|
|
472
|
-
});
|
|
473
|
-
if (storageTarget) {
|
|
474
|
-
const holderSessionId = this.context.sessionsByPath.get(storageTarget.canonicalPathKey);
|
|
475
|
-
if (holderSessionId && holderSessionId !== session.sessionId) return failure("authoring", startedAt, {
|
|
476
|
-
code: "lock_conflict",
|
|
477
|
-
kind: "in_process_session_exists",
|
|
478
|
-
message: `Project is already open in this backend runtime: ${storageTarget.canonicalProjectPath}`,
|
|
479
|
-
canonicalPathKey: storageTarget.canonicalPathKey,
|
|
480
|
-
holderSessionId
|
|
481
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
482
|
-
sessionId: session.sessionId,
|
|
483
|
-
revision: session.revision
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
const diagnostics = validationDiagnostics(session.project);
|
|
487
|
-
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
488
|
-
code: "materialize_validation_failed",
|
|
489
|
-
message: `UAM materialize validation failed with ${diagnostics.length} issue(s).`,
|
|
490
|
-
sessionId: session.sessionId,
|
|
491
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
492
|
-
issueCount: diagnostics.length,
|
|
493
|
-
diagnostics
|
|
494
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
495
|
-
sessionId: session.sessionId,
|
|
496
|
-
revision: session.revision,
|
|
497
|
-
diagnostics
|
|
498
|
-
});
|
|
499
|
-
let document;
|
|
500
|
-
try {
|
|
501
|
-
document = (0, _openfairygui_core_uam.materializeUamProject)(session.project);
|
|
502
|
-
} catch (error) {
|
|
503
|
-
const diagnosticsFromError = [{
|
|
504
|
-
code: "materialize_validation_failed",
|
|
505
|
-
message: error instanceof Error ? error.message : String(error),
|
|
506
|
-
severity: "error",
|
|
507
|
-
operationKind: "materializeSession"
|
|
508
|
-
}];
|
|
509
|
-
return failure("authoring", startedAt, {
|
|
510
|
-
code: "materialize_validation_failed",
|
|
511
|
-
message: error instanceof Error ? error.message : String(error),
|
|
512
|
-
sessionId: session.sessionId,
|
|
513
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
514
|
-
issueCount: diagnosticsFromError.length,
|
|
515
|
-
diagnostics: diagnosticsFromError
|
|
516
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
517
|
-
sessionId: session.sessionId,
|
|
518
|
-
revision: session.revision,
|
|
519
|
-
diagnostics: diagnosticsFromError
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
const writtenPaths = [];
|
|
523
|
-
const failedPaths = [];
|
|
524
|
-
const skippedPaths = [];
|
|
525
|
-
this.eventService.emit({
|
|
526
|
-
kind: "save.started",
|
|
527
|
-
sessionId: session.sessionId,
|
|
528
|
-
canonicalPathKey: storageTarget?.canonicalPathKey ?? session.canonicalPathKey,
|
|
529
|
-
revision: session.revision
|
|
530
|
-
});
|
|
531
|
-
try {
|
|
532
|
-
await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(fileSystem, writtenPaths, failedPaths)).write(document, fairyPath);
|
|
533
|
-
if (storageTarget) {
|
|
534
|
-
this.context.sessionsByPath.delete(session.canonicalPathKey);
|
|
535
|
-
session.fileSystem = storageTarget.fileSystem;
|
|
536
|
-
session.fairyPath = storageTarget.fairyPath;
|
|
537
|
-
session.canonicalProjectPath = storageTarget.canonicalProjectPath;
|
|
538
|
-
session.canonicalPathKey = storageTarget.canonicalPathKey;
|
|
539
|
-
this.context.sessionsByPath.set(session.canonicalPathKey, session.sessionId);
|
|
540
|
-
}
|
|
541
|
-
session.lastSavedRevision = session.revision;
|
|
542
|
-
session.dirty = false;
|
|
543
|
-
const cacheEntry = this.cacheService.refreshSession(session);
|
|
544
|
-
this.eventService.emit({
|
|
545
|
-
kind: "save.completed",
|
|
546
|
-
sessionId: session.sessionId,
|
|
547
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
548
|
-
revision: session.revision
|
|
549
|
-
});
|
|
550
|
-
this.eventService.emit({
|
|
551
|
-
kind: "cache.updated",
|
|
552
|
-
sessionId: session.sessionId,
|
|
553
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
554
|
-
revision: session.revision,
|
|
555
|
-
cacheRevision: cacheEntry.revision
|
|
556
|
-
});
|
|
557
|
-
return success("authoring", startedAt, toMaterializeSnapshot(session, this.context.capabilities, {
|
|
558
|
-
reason: input.reason,
|
|
559
|
-
writtenPaths,
|
|
560
|
-
skippedPaths,
|
|
561
|
-
diagnostics: []
|
|
562
|
-
}), {
|
|
563
|
-
sessionId: session.sessionId,
|
|
564
|
-
revision: session.revision
|
|
565
|
-
});
|
|
566
|
-
} catch (error) {
|
|
567
|
-
const diagnosticsFromError = [{
|
|
568
|
-
code: "write_failed",
|
|
569
|
-
message: error instanceof Error ? error.message : String(error),
|
|
570
|
-
severity: "error",
|
|
571
|
-
path: failedPaths[0],
|
|
572
|
-
operationKind: "materializeSession"
|
|
573
|
-
}];
|
|
574
|
-
this.cacheService.invalidateSession(session);
|
|
575
|
-
this.eventService.emit({
|
|
576
|
-
kind: "save.failed",
|
|
577
|
-
sessionId: session.sessionId,
|
|
578
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
579
|
-
revision: session.revision,
|
|
580
|
-
diagnostics: diagnosticsFromError
|
|
581
|
-
});
|
|
582
|
-
return failure("authoring", startedAt, {
|
|
583
|
-
code: "write_failed",
|
|
584
|
-
message: error instanceof Error ? error.message : String(error),
|
|
585
|
-
sessionId: session.sessionId,
|
|
586
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
587
|
-
attemptedRevision: session.revision,
|
|
588
|
-
lastSavedRevision: session.lastSavedRevision,
|
|
589
|
-
writtenPaths,
|
|
590
|
-
failedPaths,
|
|
591
|
-
skippedPaths,
|
|
592
|
-
diagnostics: diagnosticsFromError,
|
|
593
|
-
diskMayBePartiallyUpdated: true
|
|
594
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
595
|
-
sessionId: session.sessionId,
|
|
596
|
-
revision: session.revision,
|
|
597
|
-
diagnostics: diagnosticsFromError
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
//#endregion
|
|
603
|
-
//#region src/services/cache-service.ts
|
|
604
|
-
function createCacheEntry(session, valid) {
|
|
605
|
-
return {
|
|
606
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
607
|
-
sessionId: session.sessionId,
|
|
608
|
-
revision: session.revision,
|
|
609
|
-
lastSavedRevision: session.lastSavedRevision,
|
|
610
|
-
dirty: session.dirty,
|
|
611
|
-
valid,
|
|
612
|
-
indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
613
|
-
summary: {
|
|
614
|
-
packageCount: session.project.packages.length,
|
|
615
|
-
resourceCount: session.project.packages.reduce((total, pkg) => total + pkg.resources.length, 0),
|
|
616
|
-
diagnostics: []
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
var CacheService = class {
|
|
621
|
-
constructor(context) {
|
|
622
|
-
this.context = context;
|
|
623
|
-
}
|
|
624
|
-
getCacheSnapshot(input) {
|
|
625
|
-
const startedAt = Date.now();
|
|
626
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
627
|
-
if (!session || session.closed) return failure("read", startedAt, createSessionNotFoundError(input.sessionId));
|
|
628
|
-
const entry = this.context.cacheBySession.get(input.sessionId);
|
|
629
|
-
return success("read", startedAt, {
|
|
630
|
-
cacheRevision: entry?.revision ?? session.revision,
|
|
631
|
-
entries: entry ? [cloneCacheEntrySnapshot(entry)] : []
|
|
632
|
-
}, {
|
|
633
|
-
sessionId: session.sessionId,
|
|
634
|
-
revision: session.revision
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
refreshSession(session) {
|
|
638
|
-
const entry = createCacheEntry(session, true);
|
|
639
|
-
this.context.cacheBySession.set(session.sessionId, entry);
|
|
640
|
-
return entry;
|
|
641
|
-
}
|
|
642
|
-
invalidateSession(session) {
|
|
643
|
-
const entry = createCacheEntry(session, false);
|
|
644
|
-
this.context.cacheBySession.set(session.sessionId, entry);
|
|
645
|
-
return entry;
|
|
646
|
-
}
|
|
647
|
-
removeSession(sessionId) {
|
|
648
|
-
this.context.cacheBySession.delete(sessionId);
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
//#endregion
|
|
652
|
-
//#region src/services/event-service.ts
|
|
653
|
-
const DEFAULT_EVENT_RETENTION_LIMIT = 1e3;
|
|
654
|
-
var EventService = class {
|
|
655
|
-
constructor(context) {
|
|
656
|
-
this.context = context;
|
|
657
|
-
}
|
|
658
|
-
emit(event) {
|
|
659
|
-
const emitted = {
|
|
660
|
-
...event,
|
|
661
|
-
sequence: this.context.nextEventSequence(),
|
|
662
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
663
|
-
diagnostics: event.diagnostics ?? []
|
|
664
|
-
};
|
|
665
|
-
const sessionId = event.sessionId;
|
|
666
|
-
if (!sessionId) return emitted;
|
|
667
|
-
const events = this.context.eventsBySession.get(sessionId) ?? [];
|
|
668
|
-
events.push(emitted);
|
|
669
|
-
while (events.length > DEFAULT_EVENT_RETENTION_LIMIT) events.shift();
|
|
670
|
-
this.context.eventsBySession.set(sessionId, events);
|
|
671
|
-
return emitted;
|
|
672
|
-
}
|
|
673
|
-
getEvents(input) {
|
|
674
|
-
const startedAt = Date.now();
|
|
675
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
676
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
677
|
-
const events = this.context.eventsBySession.get(input.sessionId) ?? [];
|
|
678
|
-
const oldestSequence = events[0]?.sequence ?? (events.length === 0 ? 1 : 0);
|
|
679
|
-
const currentSequence = events.at(-1)?.sequence ?? 0;
|
|
680
|
-
const after = input.after === void 0 ? 0 : Number(input.after);
|
|
681
|
-
if (!Number.isInteger(after) || after < 0) return failure("runtime", startedAt, {
|
|
682
|
-
code: "event_cursor_invalid",
|
|
683
|
-
message: `Invalid event cursor: ${input.after}`,
|
|
684
|
-
sessionId: input.sessionId,
|
|
685
|
-
after: String(input.after)
|
|
686
|
-
});
|
|
687
|
-
if (events.length > 0 && after !== 0 && after < oldestSequence - 1) return failure("runtime", startedAt, {
|
|
688
|
-
code: "event_cursor_invalid",
|
|
689
|
-
message: `Event cursor has expired: ${after}`,
|
|
690
|
-
sessionId: input.sessionId,
|
|
691
|
-
after: String(input.after)
|
|
692
|
-
});
|
|
693
|
-
if (after > currentSequence) return failure("runtime", startedAt, {
|
|
694
|
-
code: "event_cursor_invalid",
|
|
695
|
-
message: `Unknown event cursor: ${after}`,
|
|
696
|
-
sessionId: input.sessionId,
|
|
697
|
-
after: String(input.after)
|
|
698
|
-
});
|
|
699
|
-
const filtered = events.filter((event) => event.sequence > after);
|
|
700
|
-
const limit = input.limit === void 0 ? filtered.length : Math.max(0, input.limit);
|
|
701
|
-
return success("runtime", startedAt, {
|
|
702
|
-
events: filtered.slice(0, limit).map(cloneEventSnapshot),
|
|
703
|
-
oldestSequence,
|
|
704
|
-
currentSequence,
|
|
705
|
-
cursorExpired: false
|
|
706
|
-
}, {
|
|
707
|
-
sessionId: session.sessionId,
|
|
708
|
-
revision: session.revision
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
removeSession(sessionId) {
|
|
712
|
-
this.context.eventsBySession.delete(sessionId);
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
//#endregion
|
|
716
|
-
//#region src/services/job-service.ts
|
|
717
|
-
const COMPLETED_JOB_RETENTION_LIMIT = 100;
|
|
718
|
-
const REFRESH_START_DELAY_MS = 0;
|
|
719
|
-
const REFRESH_COMPLETE_DELAY_MS = 50;
|
|
720
|
-
function isTerminal(status) {
|
|
721
|
-
return status === "completed" || status === "failed" || status === "cancelled";
|
|
722
|
-
}
|
|
723
|
-
var JobService = class {
|
|
724
|
-
constructor(context, cacheService, eventService) {
|
|
725
|
-
this.context = context;
|
|
726
|
-
this.cacheService = cacheService;
|
|
727
|
-
this.eventService = eventService;
|
|
728
|
-
}
|
|
729
|
-
cancellationRequests = /* @__PURE__ */ new Set();
|
|
730
|
-
getJobs(sessionId) {
|
|
731
|
-
return this.context.jobsBySession.get(sessionId) ?? [];
|
|
732
|
-
}
|
|
733
|
-
setJobs(sessionId, jobs) {
|
|
734
|
-
const retained = [];
|
|
735
|
-
let terminalCount = 0;
|
|
736
|
-
for (let index = jobs.length - 1; index >= 0; index -= 1) {
|
|
737
|
-
const job = jobs[index];
|
|
738
|
-
if (isTerminal(job.status)) {
|
|
739
|
-
if (terminalCount >= COMPLETED_JOB_RETENTION_LIMIT) continue;
|
|
740
|
-
terminalCount += 1;
|
|
741
|
-
}
|
|
742
|
-
retained.push(job);
|
|
743
|
-
}
|
|
744
|
-
this.context.jobsBySession.set(sessionId, retained.reverse());
|
|
745
|
-
}
|
|
746
|
-
refreshCache(input) {
|
|
747
|
-
const startedAt = Date.now();
|
|
748
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
749
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
750
|
-
const jobId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
751
|
-
const job = {
|
|
752
|
-
jobId,
|
|
753
|
-
kind: "cache.refresh",
|
|
754
|
-
status: "queued",
|
|
755
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
756
|
-
sessionId: session.sessionId,
|
|
757
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
758
|
-
revision: session.revision,
|
|
759
|
-
diagnostics: [],
|
|
760
|
-
progress: {
|
|
761
|
-
completed: 0,
|
|
762
|
-
total: 1,
|
|
763
|
-
message: input.reason ?? "manual"
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
const jobs = this.getJobs(session.sessionId);
|
|
767
|
-
jobs.push(job);
|
|
768
|
-
this.setJobs(session.sessionId, jobs);
|
|
769
|
-
this.eventService.emit({
|
|
770
|
-
kind: "job.created",
|
|
771
|
-
sessionId: session.sessionId,
|
|
772
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
773
|
-
revision: session.revision,
|
|
774
|
-
jobId
|
|
775
|
-
});
|
|
776
|
-
this.scheduleRefreshJob(session.sessionId, jobId);
|
|
777
|
-
return success("runtime", startedAt, cloneJobSnapshot(job), {
|
|
778
|
-
sessionId: session.sessionId,
|
|
779
|
-
revision: session.revision
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
getJob(input) {
|
|
783
|
-
const startedAt = Date.now();
|
|
784
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
785
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
786
|
-
const job = this.getJobs(input.sessionId).find((item) => item.jobId === input.jobId);
|
|
787
|
-
if (!job) return failure("runtime", startedAt, {
|
|
788
|
-
code: "job_not_found",
|
|
789
|
-
message: `Job was not found: ${input.jobId}`,
|
|
790
|
-
sessionId: input.sessionId,
|
|
791
|
-
jobId: input.jobId
|
|
792
|
-
}, void 0, {
|
|
793
|
-
sessionId: session.sessionId,
|
|
794
|
-
revision: session.revision
|
|
795
|
-
});
|
|
796
|
-
return success("runtime", startedAt, cloneJobSnapshot(job), {
|
|
797
|
-
sessionId: session.sessionId,
|
|
798
|
-
revision: session.revision
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
listJobs(input) {
|
|
802
|
-
const startedAt = Date.now();
|
|
803
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
804
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
805
|
-
let jobs = [...this.getJobs(input.sessionId)];
|
|
806
|
-
if (input.kind) jobs = jobs.filter((job) => job.kind === input.kind);
|
|
807
|
-
if (input.status) if (input.status === "active") jobs = jobs.filter((job) => !isTerminal(job.status));
|
|
808
|
-
else if (input.status === "terminal") jobs = jobs.filter((job) => isTerminal(job.status));
|
|
809
|
-
else jobs = jobs.filter((job) => job.status === input.status);
|
|
810
|
-
if (input.limit !== void 0) jobs = jobs.slice(0, Math.max(0, input.limit));
|
|
811
|
-
return success("runtime", startedAt, { jobs: jobs.map(cloneJobSnapshot) }, {
|
|
812
|
-
sessionId: session.sessionId,
|
|
813
|
-
revision: session.revision
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
cancelJob(input) {
|
|
817
|
-
const startedAt = Date.now();
|
|
818
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
819
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
820
|
-
const job = this.getJobs(input.sessionId).find((item) => item.jobId === input.jobId);
|
|
821
|
-
if (!job) return failure("runtime", startedAt, {
|
|
822
|
-
code: "job_not_found",
|
|
823
|
-
message: `Job was not found: ${input.jobId}`,
|
|
824
|
-
sessionId: input.sessionId,
|
|
825
|
-
jobId: input.jobId
|
|
826
|
-
}, void 0, {
|
|
827
|
-
sessionId: session.sessionId,
|
|
828
|
-
revision: session.revision
|
|
829
|
-
});
|
|
830
|
-
if (isTerminal(job.status)) return failure("runtime", startedAt, {
|
|
831
|
-
code: "job_not_cancellable",
|
|
832
|
-
message: `Job is already terminal: ${input.jobId}`,
|
|
833
|
-
sessionId: input.sessionId,
|
|
834
|
-
jobId: input.jobId,
|
|
835
|
-
status: job.status
|
|
836
|
-
}, void 0, {
|
|
837
|
-
sessionId: session.sessionId,
|
|
838
|
-
revision: session.revision
|
|
839
|
-
});
|
|
840
|
-
this.cancellationRequests.add(job.jobId);
|
|
841
|
-
this.eventService.emit({
|
|
842
|
-
kind: "job.cancelRequested",
|
|
843
|
-
sessionId: input.sessionId,
|
|
844
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
845
|
-
revision: session.revision,
|
|
846
|
-
jobId: job.jobId
|
|
847
|
-
});
|
|
848
|
-
return success("runtime", startedAt, cloneJobSnapshot(this.cancelRefreshJob(session.sessionId, job)), {
|
|
849
|
-
sessionId: session.sessionId,
|
|
850
|
-
revision: session.revision
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
removeSession(sessionId) {
|
|
854
|
-
for (const job of this.getJobs(sessionId)) this.cancellationRequests.delete(job.jobId);
|
|
855
|
-
this.context.jobsBySession.delete(sessionId);
|
|
856
|
-
}
|
|
857
|
-
replaceJob(sessionId, nextJob) {
|
|
858
|
-
const jobs = this.getJobs(sessionId).map((job) => job.jobId === nextJob.jobId ? nextJob : job);
|
|
859
|
-
this.setJobs(sessionId, jobs);
|
|
860
|
-
}
|
|
861
|
-
scheduleRefreshJob(sessionId, jobId) {
|
|
862
|
-
setTimeout(() => this.startRefreshJob(sessionId, jobId), REFRESH_START_DELAY_MS);
|
|
863
|
-
}
|
|
864
|
-
startRefreshJob(sessionId, jobId) {
|
|
865
|
-
const session = this.context.sessions.get(sessionId);
|
|
866
|
-
if (!session || session.closed) return;
|
|
867
|
-
const job = this.getJobs(sessionId).find((item) => item.jobId === jobId);
|
|
868
|
-
if (!job || isTerminal(job.status)) return;
|
|
869
|
-
if (this.cancellationRequests.has(jobId)) {
|
|
870
|
-
this.cancelRefreshJob(sessionId, job);
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
const running = {
|
|
874
|
-
...job,
|
|
875
|
-
status: "running",
|
|
876
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
877
|
-
progress: {
|
|
878
|
-
completed: 0,
|
|
879
|
-
total: 1,
|
|
880
|
-
message: "refreshing cache"
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
this.replaceJob(sessionId, running);
|
|
884
|
-
this.eventService.emit({
|
|
885
|
-
kind: "job.started",
|
|
886
|
-
sessionId,
|
|
887
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
888
|
-
revision: session.revision,
|
|
889
|
-
jobId
|
|
890
|
-
});
|
|
891
|
-
this.eventService.emit({
|
|
892
|
-
kind: "job.progress",
|
|
893
|
-
sessionId,
|
|
894
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
895
|
-
revision: session.revision,
|
|
896
|
-
jobId,
|
|
897
|
-
payload: running.progress
|
|
898
|
-
});
|
|
899
|
-
setTimeout(() => this.completeRefreshJob(sessionId, jobId), REFRESH_COMPLETE_DELAY_MS);
|
|
900
|
-
}
|
|
901
|
-
completeRefreshJob(sessionId, jobId) {
|
|
902
|
-
const session = this.context.sessions.get(sessionId);
|
|
903
|
-
if (!session || session.closed) return;
|
|
904
|
-
const job = this.getJobs(sessionId).find((item) => item.jobId === jobId);
|
|
905
|
-
if (!job || isTerminal(job.status)) return;
|
|
906
|
-
if (this.cancellationRequests.has(jobId)) {
|
|
907
|
-
this.cancelRefreshJob(sessionId, job);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
try {
|
|
911
|
-
const entry = this.cacheService.refreshSession(session);
|
|
912
|
-
const completed = {
|
|
913
|
-
...job,
|
|
914
|
-
status: "completed",
|
|
915
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
916
|
-
cacheRevision: entry.revision,
|
|
917
|
-
progress: {
|
|
918
|
-
completed: 1,
|
|
919
|
-
total: 1,
|
|
920
|
-
message: "cache refreshed"
|
|
921
|
-
},
|
|
922
|
-
result: { cacheRevision: entry.revision }
|
|
923
|
-
};
|
|
924
|
-
this.replaceJob(sessionId, completed);
|
|
925
|
-
this.eventService.emit({
|
|
926
|
-
kind: "job.completed",
|
|
927
|
-
sessionId,
|
|
928
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
929
|
-
revision: session.revision,
|
|
930
|
-
cacheRevision: entry.revision,
|
|
931
|
-
jobId
|
|
932
|
-
});
|
|
933
|
-
this.eventService.emit({
|
|
934
|
-
kind: "cache.updated",
|
|
935
|
-
sessionId,
|
|
936
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
937
|
-
revision: session.revision,
|
|
938
|
-
cacheRevision: entry.revision
|
|
939
|
-
});
|
|
940
|
-
} catch (error) {
|
|
941
|
-
const failed = {
|
|
942
|
-
...job,
|
|
943
|
-
status: "failed",
|
|
944
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
945
|
-
error: {
|
|
946
|
-
code: "cache_refresh_failed",
|
|
947
|
-
message: error instanceof Error ? error.message : "Cache refresh failed",
|
|
948
|
-
sessionId,
|
|
949
|
-
jobId
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
this.replaceJob(sessionId, failed);
|
|
953
|
-
this.eventService.emit({
|
|
954
|
-
kind: "job.failed",
|
|
955
|
-
sessionId,
|
|
956
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
957
|
-
revision: session.revision,
|
|
958
|
-
jobId,
|
|
959
|
-
diagnostics: failed.diagnostics
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
cancelRefreshJob(sessionId, job) {
|
|
964
|
-
const session = this.context.sessions.get(sessionId);
|
|
965
|
-
const cancelled = {
|
|
966
|
-
...job,
|
|
967
|
-
status: "cancelled",
|
|
968
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
969
|
-
error: {
|
|
970
|
-
code: "job_cancelled",
|
|
971
|
-
message: `Job was cancelled: ${job.jobId}`,
|
|
972
|
-
sessionId,
|
|
973
|
-
jobId: job.jobId
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
this.replaceJob(sessionId, cancelled);
|
|
977
|
-
this.cancellationRequests.delete(job.jobId);
|
|
978
|
-
this.eventService.emit({
|
|
979
|
-
kind: "job.cancelled",
|
|
980
|
-
sessionId,
|
|
981
|
-
canonicalPathKey: session?.canonicalPathKey ?? job.canonicalPathKey,
|
|
982
|
-
revision: session?.revision ?? job.revision,
|
|
983
|
-
jobId: job.jobId
|
|
984
|
-
});
|
|
985
|
-
return cancelled;
|
|
986
|
-
}
|
|
987
|
-
};
|
|
988
|
-
//#endregion
|
|
989
|
-
//#region src/services/read-service.ts
|
|
990
|
-
var ReadService = class {
|
|
991
|
-
constructor(context) {
|
|
992
|
-
this.context = context;
|
|
993
|
-
}
|
|
994
|
-
getCapabilities() {
|
|
995
|
-
return success("read", Date.now(), cloneCapabilitiesSnapshot(this.context.capabilities));
|
|
996
|
-
}
|
|
997
|
-
getSession(input) {
|
|
998
|
-
const startedAt = Date.now();
|
|
999
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
1000
|
-
if (!session || session.closed) return failure("read", startedAt, createSessionNotFoundError(input.sessionId));
|
|
1001
|
-
return success("read", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
1002
|
-
sessionId: session.sessionId,
|
|
1003
|
-
revision: session.revision
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
};
|
|
1007
|
-
//#endregion
|
|
1008
|
-
//#region src/services/runtime-service.ts
|
|
1009
|
-
function randomId() {
|
|
1010
|
-
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1011
|
-
}
|
|
1012
|
-
function createCapabilityUnavailableError(capability) {
|
|
1013
|
-
const artifactCapability = capability === "artifact.publish" || capability === "artifact.restore";
|
|
1014
|
-
return {
|
|
1015
|
-
code: "capability_unavailable",
|
|
1016
|
-
message: artifactCapability ? `${capability} requires the Node bridge boundary exposed by @openfairygui/backend/node.` : `${capability} requires an injected BackendFileSystem adapter.`,
|
|
1017
|
-
capability,
|
|
1018
|
-
requiredAdapter: capability === "fileSystem" ? "BackendFileSystem" : void 0,
|
|
1019
|
-
requiredHost: artifactCapability ? "node" : void 0,
|
|
1020
|
-
bridgeBoundary: artifactCapability ? "external-bridge" : void 0
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
function createProjectReaderFileSystem(fileSystem) {
|
|
1024
|
-
return {
|
|
1025
|
-
readFile(filePath) {
|
|
1026
|
-
return fileSystem.readFile(filePath);
|
|
1027
|
-
},
|
|
1028
|
-
readFileRaw(filePath) {
|
|
1029
|
-
return fileSystem.readFileRaw(filePath);
|
|
1030
|
-
},
|
|
1031
|
-
writeFile(filePath, content) {
|
|
1032
|
-
return fileSystem.writeFile(filePath, content);
|
|
1033
|
-
},
|
|
1034
|
-
writeFileRaw(filePath, data) {
|
|
1035
|
-
return fileSystem.writeFileRaw(filePath, data);
|
|
1036
|
-
},
|
|
1037
|
-
async mkdir(dirPath) {
|
|
1038
|
-
await fileSystem.mkdir(dirPath, { recursive: true });
|
|
1039
|
-
},
|
|
1040
|
-
readdir(dirPath) {
|
|
1041
|
-
return fileSystem.readdir(dirPath);
|
|
1042
|
-
},
|
|
1043
|
-
async exists(filePath) {
|
|
1044
|
-
try {
|
|
1045
|
-
await fileSystem.stat(filePath);
|
|
1046
|
-
return true;
|
|
1047
|
-
} catch {
|
|
1048
|
-
return false;
|
|
1049
|
-
}
|
|
1050
|
-
},
|
|
1051
|
-
join(...paths) {
|
|
1052
|
-
return fileSystem.join(...paths);
|
|
1053
|
-
},
|
|
1054
|
-
dirname(filePath) {
|
|
1055
|
-
return fileSystem.dirname(filePath);
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
var RuntimeService = class {
|
|
1060
|
-
constructor(context, cacheService, eventService, jobService) {
|
|
1061
|
-
this.context = context;
|
|
1062
|
-
this.cacheService = cacheService;
|
|
1063
|
-
this.eventService = eventService;
|
|
1064
|
-
this.jobService = jobService;
|
|
1065
|
-
}
|
|
1066
|
-
async openSession(input) {
|
|
1067
|
-
const startedAt = Date.now();
|
|
1068
|
-
if (!this.context.fileSystem) return failure("runtime", startedAt, createCapabilityUnavailableError("fileSystem"));
|
|
1069
|
-
const fileSystem = this.context.fileSystem;
|
|
1070
|
-
const { fairyPath, canonicalProjectPath, canonicalPathKey } = await resolveCanonicalProjectRoot(fileSystem, input.projectPath);
|
|
1071
|
-
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
1072
|
-
const lockFilePath = fileSystem.join(canonicalProjectPath, ".openfairygui.backend.lock");
|
|
1073
|
-
if (existingSessionId) return failure("runtime", startedAt, {
|
|
1074
|
-
code: "lock_conflict",
|
|
1075
|
-
kind: "in_process_session_exists",
|
|
1076
|
-
message: `Project is already open in this backend runtime: ${canonicalProjectPath}`,
|
|
1077
|
-
canonicalPathKey,
|
|
1078
|
-
holderSessionId: existingSessionId,
|
|
1079
|
-
lockFilePath
|
|
1080
|
-
});
|
|
1081
|
-
let advisoryLock = null;
|
|
1082
|
-
try {
|
|
1083
|
-
advisoryLock = await fileSystem.openExclusive(lockFilePath);
|
|
1084
|
-
await advisoryLock.writeFile(JSON.stringify(this.context.host?.lockMetadata?.({
|
|
1085
|
-
canonicalPathKey,
|
|
1086
|
-
canonicalProjectPath,
|
|
1087
|
-
lockFilePath
|
|
1088
|
-
}) ?? {
|
|
1089
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1090
|
-
canonicalPathKey
|
|
1091
|
-
}));
|
|
1092
|
-
await advisoryLock.close();
|
|
1093
|
-
const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath));
|
|
1094
|
-
const sessionId = randomId();
|
|
1095
|
-
const session = {
|
|
1096
|
-
sessionId,
|
|
1097
|
-
fairyPath,
|
|
1098
|
-
canonicalProjectPath,
|
|
1099
|
-
canonicalPathKey,
|
|
1100
|
-
lockFilePath,
|
|
1101
|
-
fileSystem,
|
|
1102
|
-
project,
|
|
1103
|
-
revision: 0,
|
|
1104
|
-
lastSavedRevision: 0,
|
|
1105
|
-
dirty: false,
|
|
1106
|
-
lockHeld: true,
|
|
1107
|
-
closed: false
|
|
1108
|
-
};
|
|
1109
|
-
this.context.sessions.set(sessionId, session);
|
|
1110
|
-
this.context.sessionsByPath.set(canonicalPathKey, sessionId);
|
|
1111
|
-
this.cacheService.refreshSession(session);
|
|
1112
|
-
this.eventService.emit({
|
|
1113
|
-
kind: "session.opened",
|
|
1114
|
-
sessionId,
|
|
1115
|
-
canonicalPathKey,
|
|
1116
|
-
revision: session.revision
|
|
1117
|
-
});
|
|
1118
|
-
return success("runtime", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
1119
|
-
sessionId: session.sessionId,
|
|
1120
|
-
revision: session.revision
|
|
1121
|
-
});
|
|
1122
|
-
} catch (error) {
|
|
1123
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST") return failure("runtime", startedAt, {
|
|
1124
|
-
code: "lock_conflict",
|
|
1125
|
-
kind: "advisory_lock_conflict",
|
|
1126
|
-
message: `Advisory lock already exists for project: ${canonicalProjectPath}`,
|
|
1127
|
-
canonicalPathKey,
|
|
1128
|
-
lockFilePath
|
|
1129
|
-
});
|
|
1130
|
-
if (advisoryLock) {
|
|
1131
|
-
await advisoryLock.close().catch(() => void 0);
|
|
1132
|
-
await fileSystem.unlink(lockFilePath).catch(() => void 0);
|
|
1133
|
-
}
|
|
1134
|
-
throw error;
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
openProjectSession(input) {
|
|
1138
|
-
const startedAt = Date.now();
|
|
1139
|
-
const sessionId = input.sessionId ?? randomId();
|
|
1140
|
-
const storage = input.storage;
|
|
1141
|
-
const memoryProjectPath = `memory://${sessionId}`;
|
|
1142
|
-
const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
|
|
1143
|
-
const canonicalPathKey = storage?.canonicalPathKey ?? input.canonicalPathKey ?? (storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
|
|
1144
|
-
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
1145
|
-
if (existingSessionId) return failure("runtime", startedAt, {
|
|
1146
|
-
code: "lock_conflict",
|
|
1147
|
-
kind: "in_process_session_exists",
|
|
1148
|
-
message: `Project is already open in this backend runtime: ${canonicalProjectPath}`,
|
|
1149
|
-
canonicalPathKey,
|
|
1150
|
-
holderSessionId: existingSessionId
|
|
1151
|
-
});
|
|
1152
|
-
const session = {
|
|
1153
|
-
sessionId,
|
|
1154
|
-
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
1155
|
-
canonicalProjectPath,
|
|
1156
|
-
canonicalPathKey,
|
|
1157
|
-
lockFilePath: "",
|
|
1158
|
-
fileSystem: storage?.fileSystem,
|
|
1159
|
-
project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
|
|
1160
|
-
revision: 0,
|
|
1161
|
-
lastSavedRevision: 0,
|
|
1162
|
-
dirty: false,
|
|
1163
|
-
lockHeld: false,
|
|
1164
|
-
closed: false
|
|
1165
|
-
};
|
|
1166
|
-
this.context.sessions.set(sessionId, session);
|
|
1167
|
-
this.context.sessionsByPath.set(canonicalPathKey, sessionId);
|
|
1168
|
-
this.cacheService.refreshSession(session);
|
|
1169
|
-
this.eventService.emit({
|
|
1170
|
-
kind: "session.opened",
|
|
1171
|
-
sessionId,
|
|
1172
|
-
canonicalPathKey,
|
|
1173
|
-
revision: session.revision
|
|
1174
|
-
});
|
|
1175
|
-
return success("runtime", startedAt, toSessionSnapshot(session, this.context.capabilities), {
|
|
1176
|
-
sessionId: session.sessionId,
|
|
1177
|
-
revision: session.revision
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
async closeSession(input) {
|
|
1181
|
-
const startedAt = Date.now();
|
|
1182
|
-
const session = this.context.sessions.get(input.sessionId);
|
|
1183
|
-
if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
|
|
1184
|
-
this.eventService.emit({
|
|
1185
|
-
kind: "session.closeRequested",
|
|
1186
|
-
sessionId: session.sessionId,
|
|
1187
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
1188
|
-
revision: session.revision
|
|
1189
|
-
});
|
|
1190
|
-
if (this.context.fileSystem && session.lockFilePath) await this.context.fileSystem.unlink(session.lockFilePath).catch(() => void 0);
|
|
1191
|
-
session.lockHeld = false;
|
|
1192
|
-
session.closed = true;
|
|
1193
|
-
this.context.sessions.delete(session.sessionId);
|
|
1194
|
-
this.context.sessionsByPath.delete(session.canonicalPathKey);
|
|
1195
|
-
this.cacheService.removeSession(session.sessionId);
|
|
1196
|
-
this.jobService.removeSession(session.sessionId);
|
|
1197
|
-
this.eventService.emit({
|
|
1198
|
-
kind: "session.closed",
|
|
1199
|
-
sessionId: session.sessionId,
|
|
1200
|
-
canonicalPathKey: session.canonicalPathKey,
|
|
1201
|
-
revision: session.revision
|
|
1202
|
-
});
|
|
1203
|
-
this.eventService.removeSession(session.sessionId);
|
|
1204
|
-
return success("runtime", startedAt, {
|
|
1205
|
-
sessionId: session.sessionId,
|
|
1206
|
-
closed: true
|
|
1207
|
-
}, {
|
|
1208
|
-
sessionId: session.sessionId,
|
|
1209
|
-
revision: session.revision
|
|
1210
|
-
});
|
|
1211
|
-
}
|
|
1212
|
-
};
|
|
1213
|
-
//#endregion
|
|
1214
|
-
//#region src/runtime.ts
|
|
1215
|
-
const BACKEND_METHODS = [
|
|
1216
|
-
"getCapabilities",
|
|
1217
|
-
"openSession",
|
|
1218
|
-
"openProjectSession",
|
|
1219
|
-
"getSession",
|
|
1220
|
-
"applyTransaction",
|
|
1221
|
-
"saveSession",
|
|
1222
|
-
"materializeSession",
|
|
1223
|
-
"closeSession",
|
|
1224
|
-
"getEvents",
|
|
1225
|
-
"getJob",
|
|
1226
|
-
"listJobs",
|
|
1227
|
-
"cancelJob",
|
|
1228
|
-
"getCacheSnapshot",
|
|
1229
|
-
"refreshCache"
|
|
1230
|
-
];
|
|
1231
|
-
const ARTIFACT_BRIDGE_CAPABILITY = {
|
|
1232
|
-
available: false,
|
|
1233
|
-
requiredHost: "node",
|
|
1234
|
-
executionBoundary: "external-bridge",
|
|
1235
|
-
bridgeEntrypoint: "@openfairygui/backend/node",
|
|
1236
|
-
reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
|
|
1237
|
-
};
|
|
1238
|
-
function createCapabilities() {
|
|
1239
|
-
return {
|
|
1240
|
-
contractVersion: BACKEND_CONTRACT_VERSION,
|
|
1241
|
-
capabilitySchemaVersion: 2,
|
|
1242
|
-
transactionKernelOwner: "@openfairygui/core",
|
|
1243
|
-
appSeamOwner: "@openfairygui/functions",
|
|
1244
|
-
runtimeOwner: "@openfairygui/backend",
|
|
1245
|
-
methods: BACKEND_METHODS,
|
|
1246
|
-
read: {
|
|
1247
|
-
capabilitySnapshot: true,
|
|
1248
|
-
sessionSnapshot: true
|
|
1249
|
-
},
|
|
1250
|
-
authoring: {
|
|
1251
|
-
applyTransaction: true,
|
|
1252
|
-
saveSession: true,
|
|
1253
|
-
resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
|
|
1254
|
-
nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
|
|
1255
|
-
gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
|
|
1256
|
-
transactionScope: {
|
|
1257
|
-
resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
|
|
1258
|
-
nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
|
|
1259
|
-
gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds]
|
|
1260
|
-
},
|
|
1261
|
-
unsupported: ["artifact.publish", "artifact.restore"]
|
|
1262
|
-
},
|
|
1263
|
-
artifact: createArtifactCapabilities(),
|
|
1264
|
-
manifest: {
|
|
1265
|
-
browserSafe: true,
|
|
1266
|
-
rootEntrypoint: "@openfairygui/backend",
|
|
1267
|
-
nodeEntrypoint: "@openfairygui/backend/node",
|
|
1268
|
-
adapters: {
|
|
1269
|
-
fileSystem: {
|
|
1270
|
-
injected: true,
|
|
1271
|
-
requiredFor: [
|
|
1272
|
-
"openSession",
|
|
1273
|
-
"saveSession",
|
|
1274
|
-
"materializeSession"
|
|
1275
|
-
]
|
|
1276
|
-
},
|
|
1277
|
-
projectStorage: {
|
|
1278
|
-
injected: true,
|
|
1279
|
-
browserSafe: true,
|
|
1280
|
-
requiredFor: [
|
|
1281
|
-
"openProjectSession.writeback",
|
|
1282
|
-
"saveSession",
|
|
1283
|
-
"materializeSession"
|
|
1284
|
-
],
|
|
1285
|
-
adapterFactory: "createBackendStorageFileSystem"
|
|
1286
|
-
},
|
|
1287
|
-
host: {
|
|
1288
|
-
injected: true,
|
|
1289
|
-
requiredFor: ["advisoryLockMetadata"]
|
|
1290
|
-
}
|
|
1291
|
-
},
|
|
1292
|
-
executionBoundaries: {
|
|
1293
|
-
projectSession: "in-process-browser-safe",
|
|
1294
|
-
fileBackedSession: "adapter-backed",
|
|
1295
|
-
artifactPublish: ARTIFACT_BRIDGE_CAPABILITY,
|
|
1296
|
-
artifactRestore: ARTIFACT_BRIDGE_CAPABILITY
|
|
1297
|
-
},
|
|
1298
|
-
diagnostics: {
|
|
1299
|
-
stableCodes: true,
|
|
1300
|
-
errorDiagnosticMirror: true
|
|
1301
|
-
}
|
|
1302
|
-
},
|
|
1303
|
-
compatibilityPolicy: BACKEND_COMPATIBILITY_POLICY,
|
|
1304
|
-
runtime: {
|
|
1305
|
-
sessionRuntime: true,
|
|
1306
|
-
advisoryLocking: true,
|
|
1307
|
-
coordinatedSave: true,
|
|
1308
|
-
atomicSave: false,
|
|
1309
|
-
staleRevisionProtection: true,
|
|
1310
|
-
pathPolicy: createRuntimePathPolicy(),
|
|
1311
|
-
events: {
|
|
1312
|
-
polling: true,
|
|
1313
|
-
subscriptions: false,
|
|
1314
|
-
retentionLimit: 1e3,
|
|
1315
|
-
sequenceScope: "runtime"
|
|
1316
|
-
},
|
|
1317
|
-
jobs: {
|
|
1318
|
-
inMemory: true,
|
|
1319
|
-
cooperativeCancel: true,
|
|
1320
|
-
persistent: false,
|
|
1321
|
-
supportedKinds: ["cache.refresh"],
|
|
1322
|
-
artifactJobs: false,
|
|
1323
|
-
completedRetentionLimit: 100
|
|
1324
|
-
},
|
|
1325
|
-
cache: {
|
|
1326
|
-
derivedReadOnly: true,
|
|
1327
|
-
keyedBy: "canonicalPathKey",
|
|
1328
|
-
sourceOfTruth: false,
|
|
1329
|
-
refreshMethod: "refreshCache"
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
var BackendRuntime = class {
|
|
1335
|
-
fileSystem;
|
|
1336
|
-
capabilities;
|
|
1337
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1338
|
-
sessionsByPath = /* @__PURE__ */ new Map();
|
|
1339
|
-
eventsBySession = /* @__PURE__ */ new Map();
|
|
1340
|
-
jobsBySession = /* @__PURE__ */ new Map();
|
|
1341
|
-
cacheBySession = /* @__PURE__ */ new Map();
|
|
1342
|
-
eventSequence = 0;
|
|
1343
|
-
context;
|
|
1344
|
-
readService;
|
|
1345
|
-
runtimeService;
|
|
1346
|
-
authoringService;
|
|
1347
|
-
cacheService;
|
|
1348
|
-
eventService;
|
|
1349
|
-
jobService;
|
|
1350
|
-
constructor(options = {}) {
|
|
1351
|
-
this.fileSystem = options.fileSystem;
|
|
1352
|
-
this.capabilities = createCapabilities();
|
|
1353
|
-
this.context = {
|
|
1354
|
-
fileSystem: this.fileSystem,
|
|
1355
|
-
host: options.host,
|
|
1356
|
-
capabilities: this.capabilities,
|
|
1357
|
-
sessions: this.sessions,
|
|
1358
|
-
sessionsByPath: this.sessionsByPath,
|
|
1359
|
-
eventsBySession: this.eventsBySession,
|
|
1360
|
-
jobsBySession: this.jobsBySession,
|
|
1361
|
-
cacheBySession: this.cacheBySession,
|
|
1362
|
-
nextEventSequence: () => {
|
|
1363
|
-
this.eventSequence += 1;
|
|
1364
|
-
return this.eventSequence;
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1367
|
-
this.readService = new ReadService(this.context);
|
|
1368
|
-
this.eventService = new EventService(this.context);
|
|
1369
|
-
this.cacheService = new CacheService(this.context);
|
|
1370
|
-
this.jobService = new JobService(this.context, this.cacheService, this.eventService);
|
|
1371
|
-
this.runtimeService = new RuntimeService(this.context, this.cacheService, this.eventService, this.jobService);
|
|
1372
|
-
this.authoringService = new AuthoringService(this.context, this.cacheService, this.eventService);
|
|
1373
|
-
}
|
|
1374
|
-
getCapabilities() {
|
|
1375
|
-
return this.readService.getCapabilities();
|
|
1376
|
-
}
|
|
1377
|
-
async openSession(input) {
|
|
1378
|
-
return this.runtimeService.openSession(input);
|
|
1379
|
-
}
|
|
1380
|
-
openProjectSession(input) {
|
|
1381
|
-
return this.runtimeService.openProjectSession(input);
|
|
1382
|
-
}
|
|
1383
|
-
getSession(input) {
|
|
1384
|
-
return this.readService.getSession(input);
|
|
1385
|
-
}
|
|
1386
|
-
async applyTransaction(input) {
|
|
1387
|
-
return this.authoringService.applyTransaction(input);
|
|
1388
|
-
}
|
|
1389
|
-
async saveSession(input) {
|
|
1390
|
-
return this.authoringService.saveSession(input);
|
|
1391
|
-
}
|
|
1392
|
-
async materializeSession(input) {
|
|
1393
|
-
return this.authoringService.materializeSession(input);
|
|
1394
|
-
}
|
|
1395
|
-
async closeSession(input) {
|
|
1396
|
-
return this.runtimeService.closeSession(input);
|
|
1397
|
-
}
|
|
1398
|
-
getEvents(input) {
|
|
1399
|
-
return this.eventService.getEvents(input);
|
|
1400
|
-
}
|
|
1401
|
-
getJob(input) {
|
|
1402
|
-
return this.jobService.getJob(input);
|
|
1403
|
-
}
|
|
1404
|
-
listJobs(input) {
|
|
1405
|
-
return this.jobService.listJobs(input);
|
|
1406
|
-
}
|
|
1407
|
-
cancelJob(input) {
|
|
1408
|
-
return this.jobService.cancelJob(input);
|
|
1409
|
-
}
|
|
1410
|
-
getCacheSnapshot(input) {
|
|
1411
|
-
return this.cacheService.getCacheSnapshot(input);
|
|
1412
|
-
}
|
|
1413
|
-
refreshCache(input) {
|
|
1414
|
-
return this.jobService.refreshCache(input);
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
|
-
//#endregion
|
|
1418
|
-
Object.defineProperty(exports, "BACKEND_CAPABILITY_SCHEMA_VERSION", {
|
|
1419
|
-
enumerable: true,
|
|
1420
|
-
get: function() {
|
|
1421
|
-
return BACKEND_CAPABILITY_SCHEMA_VERSION;
|
|
1422
|
-
}
|
|
1423
|
-
});
|
|
1424
|
-
Object.defineProperty(exports, "BACKEND_COMPATIBILITY_POLICY", {
|
|
1425
|
-
enumerable: true,
|
|
1426
|
-
get: function() {
|
|
1427
|
-
return BACKEND_COMPATIBILITY_POLICY;
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1430
|
-
Object.defineProperty(exports, "BACKEND_CONTRACT_VERSION", {
|
|
1431
|
-
enumerable: true,
|
|
1432
|
-
get: function() {
|
|
1433
|
-
return BACKEND_CONTRACT_VERSION;
|
|
1434
|
-
}
|
|
1435
|
-
});
|
|
1436
|
-
Object.defineProperty(exports, "BackendRuntime", {
|
|
1437
|
-
enumerable: true,
|
|
1438
|
-
get: function() {
|
|
1439
|
-
return BackendRuntime;
|
|
1440
|
-
}
|
|
1441
|
-
});
|