@openfairygui/backend 0.2.0-alpha.2 → 0.2.0-alpha.21
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 +48 -2
- package/dist/index.cjs +146 -1
- package/dist/index.d.cts +33 -2
- package/dist/index.d.mts +33 -2
- package/dist/index.mjs +146 -2
- package/dist/node.cjs +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.mjs +1 -1
- package/dist/{runtime-OO1sHuCl.d.mts → runtime-B4SwHTRC.d.mts} +89 -10
- package/dist/{runtime-qcSlD_9-.d.cts → runtime-BB_3Ws5y.d.cts} +90 -11
- package/dist/{runtime-BPWzx-Tv.mjs → runtime-CqGK_D9A.mjs} +483 -87
- package/dist/{runtime-BL9Pkfc7.cjs → runtime-D3bShb1X.cjs} +482 -86
- package/package.json +5 -4
- package/src/contracts.ts +8 -0
- package/src/index.ts +25 -12
- package/src/runtime/capabilities.ts +126 -0
- package/src/runtime/contracts.ts +512 -0
- package/src/runtime.ts +67 -540
- package/src/services/authoring-service.ts +520 -80
- package/src/services/context.ts +12 -8
- package/src/services/runtime-service.ts +108 -12
- package/src/services/session-project-writer.ts +64 -0
- package/src/services/session-utils.ts +5 -1
- package/src/storage.ts +194 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require("./node.cjs");
|
|
2
2
|
let _openfairygui_core_uam = require("@openfairygui/core/uam");
|
|
3
|
-
let _openfairygui_core_project_io = require("@openfairygui/core/project-io");
|
|
4
3
|
let _openfairygui_functions_uam = require("@openfairygui/functions/uam");
|
|
4
|
+
let _openfairygui_core_project_io = require("@openfairygui/core/project-io");
|
|
5
5
|
//#region src/contracts.ts
|
|
6
6
|
const BACKEND_CONTRACT_VERSION = "1.1.0-p2";
|
|
7
7
|
const BACKEND_CAPABILITY_SCHEMA_VERSION = 2;
|
|
@@ -76,24 +76,6 @@ async function validateSaveTarget(fileSystem, openedFairyPath, targetPath) {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
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
79
|
//#region src/services/context.ts
|
|
98
80
|
function diagnosticFromError(error) {
|
|
99
81
|
return {
|
|
@@ -138,6 +120,48 @@ function failure(stage, startedAt, error, session, options) {
|
|
|
138
120
|
};
|
|
139
121
|
}
|
|
140
122
|
//#endregion
|
|
123
|
+
//#region src/services/session-project-writer.ts
|
|
124
|
+
function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
|
|
125
|
+
async function trackWrite(targetPath, write) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await write();
|
|
128
|
+
writtenPaths.push(targetPath);
|
|
129
|
+
return result;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
failedPaths.push(targetPath);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
readFile: (path) => fileSystem.readFile(path),
|
|
137
|
+
readFileRaw: (path) => fileSystem.readFileRaw(path),
|
|
138
|
+
writeFile: (path, content) => trackWrite(path, async () => {
|
|
139
|
+
await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
|
|
140
|
+
await fileSystem.writeFile(path, content);
|
|
141
|
+
}),
|
|
142
|
+
writeFileRaw: (path, data) => trackWrite(path, async () => {
|
|
143
|
+
await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
|
|
144
|
+
await fileSystem.writeFileRaw(path, data);
|
|
145
|
+
}),
|
|
146
|
+
mkdir: (path) => fileSystem.mkdir(path, { recursive: true }),
|
|
147
|
+
readdir: (path) => fileSystem.readdir(path),
|
|
148
|
+
async exists(path) {
|
|
149
|
+
try {
|
|
150
|
+
await fileSystem.stat(path);
|
|
151
|
+
return true;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
join: (...paths) => fileSystem.join(...paths),
|
|
157
|
+
dirname: (path) => fileSystem.dirname(path),
|
|
158
|
+
unlink: (path) => trackWrite(path, () => fileSystem.unlink(path))
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function writeSessionProject(input) {
|
|
162
|
+
await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, { staleSourceFiles: input.staleSourceFiles });
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
141
165
|
//#region src/services/snapshot-utils.ts
|
|
142
166
|
function cloneJsonValue(value) {
|
|
143
167
|
if (value === void 0 || value === null) return value;
|
|
@@ -180,6 +204,7 @@ function toSessionSnapshot(session, capabilities) {
|
|
|
180
204
|
revision: session.revision,
|
|
181
205
|
lastSavedRevision: session.lastSavedRevision,
|
|
182
206
|
dirty: session.dirty,
|
|
207
|
+
uamFidelity: session.uamFidelity,
|
|
183
208
|
lockHeld: session.lockHeld,
|
|
184
209
|
capabilities: cloneCapabilitiesSnapshot(capabilities)
|
|
185
210
|
};
|
|
@@ -203,65 +228,137 @@ function createStaleWriteError(session, expectedRevision) {
|
|
|
203
228
|
}
|
|
204
229
|
//#endregion
|
|
205
230
|
//#region src/services/authoring-service.ts
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
function projectSourceFiles(project) {
|
|
232
|
+
const result = /* @__PURE__ */ new Map();
|
|
233
|
+
for (const pkg of project.packages) {
|
|
234
|
+
result.set(`${pkg.id}/package.xml`, {
|
|
235
|
+
packageName: pkg.name,
|
|
236
|
+
branch: "",
|
|
237
|
+
path: "",
|
|
238
|
+
fileName: "package.xml"
|
|
239
|
+
});
|
|
240
|
+
const branches = /* @__PURE__ */ new Set();
|
|
241
|
+
for (const resource of pkg.resources) {
|
|
242
|
+
if (resource.branch) branches.add(resource.branch);
|
|
243
|
+
const fileName = resource.kind === "component" ? `${resource.name}.xml` : resource.fileName ?? resource.file ?? "";
|
|
244
|
+
if (!fileName) continue;
|
|
245
|
+
result.set(`${pkg.id}/${resource.id}`, {
|
|
246
|
+
packageName: pkg.name,
|
|
247
|
+
branch: resource.branch,
|
|
248
|
+
path: resource.path,
|
|
249
|
+
fileName
|
|
250
|
+
});
|
|
215
251
|
}
|
|
252
|
+
for (const branch of branches) result.set(`${pkg.id}/branch/${branch}`, {
|
|
253
|
+
packageName: pkg.name,
|
|
254
|
+
branch,
|
|
255
|
+
path: "",
|
|
256
|
+
fileName: "package_branch.xml"
|
|
257
|
+
});
|
|
216
258
|
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
function sourceFileKey(source) {
|
|
262
|
+
return [
|
|
263
|
+
source.branch,
|
|
264
|
+
source.packageName,
|
|
265
|
+
source.path,
|
|
266
|
+
source.fileName
|
|
267
|
+
].join("\0");
|
|
268
|
+
}
|
|
269
|
+
function recordStaleProjectFiles(session, previousProject, nextProject) {
|
|
270
|
+
if (!session.fileSystem) return;
|
|
271
|
+
const previousSources = projectSourceFiles(previousProject);
|
|
272
|
+
const nextSourceKeys = new Set([...projectSourceFiles(nextProject).values()].map(sourceFileKey));
|
|
273
|
+
for (const source of previousSources.values()) {
|
|
274
|
+
const key = sourceFileKey(source);
|
|
275
|
+
if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
|
|
276
|
+
}
|
|
277
|
+
for (const key of nextSourceKeys) session.pendingStaleSourceFiles.delete(key);
|
|
278
|
+
}
|
|
279
|
+
function toBackendDiagnostics(error) {
|
|
280
|
+
return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
|
|
281
|
+
code: error.code,
|
|
282
|
+
message: error.message,
|
|
283
|
+
severity: "error",
|
|
284
|
+
operationKind: error.operationKind,
|
|
285
|
+
opIndex: error.opIndex,
|
|
286
|
+
opId: error.opId
|
|
287
|
+
}];
|
|
288
|
+
}
|
|
289
|
+
function createCapabilityUnavailableError$1(message) {
|
|
217
290
|
return {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
291
|
+
code: "capability_unavailable",
|
|
292
|
+
message,
|
|
293
|
+
capability: "fileSystem",
|
|
294
|
+
requiredAdapter: "BackendFileSystem"
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function createUamFidelityUnsupportedError(session) {
|
|
298
|
+
return {
|
|
299
|
+
code: "uam_fidelity_unsupported",
|
|
300
|
+
message: "The source project contains formal properties that the current UAM cannot preserve.",
|
|
301
|
+
sessionId: session.sessionId,
|
|
302
|
+
canonicalPathKey: session.canonicalPathKey
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function validationDiagnostics(sessionProject) {
|
|
306
|
+
return (0, _openfairygui_core_uam.validateUamProject)(sessionProject).map((issue) => ({
|
|
307
|
+
code: "materialize_validation_failed",
|
|
308
|
+
message: issue.message,
|
|
309
|
+
severity: "error",
|
|
310
|
+
path: issue.path,
|
|
311
|
+
operationKind: "materializeSession"
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
function toMaterializeSnapshot(session, capabilities, input) {
|
|
315
|
+
return {
|
|
316
|
+
...toSessionSnapshot(session, capabilities),
|
|
317
|
+
mode: "fullProject",
|
|
318
|
+
reason: input.reason,
|
|
319
|
+
materializeRevision: session.revision,
|
|
320
|
+
saveRevision: session.lastSavedRevision,
|
|
321
|
+
writtenPaths: [...input.writtenPaths],
|
|
322
|
+
skippedPaths: [...input.skippedPaths],
|
|
323
|
+
diagnostics: input.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function storageCanonicalTarget(input) {
|
|
327
|
+
const canonicalProjectPath = input.canonicalProjectPath ?? (input.fileSystem.dirname(input.fairyPath) || ".");
|
|
328
|
+
return {
|
|
329
|
+
fileSystem: input.fileSystem,
|
|
330
|
+
fairyPath: input.fairyPath,
|
|
331
|
+
canonicalProjectPath,
|
|
332
|
+
canonicalPathKey: input.canonicalPathKey ?? normalizeComparablePath(canonicalProjectPath)
|
|
256
333
|
};
|
|
257
334
|
}
|
|
258
335
|
var AuthoringService = class {
|
|
336
|
+
sessionOperations = /* @__PURE__ */ new Map();
|
|
259
337
|
constructor(context, cacheService, eventService) {
|
|
260
338
|
this.context = context;
|
|
261
339
|
this.cacheService = cacheService;
|
|
262
340
|
this.eventService = eventService;
|
|
263
341
|
}
|
|
342
|
+
async runSessionExclusive(sessionId, operation) {
|
|
343
|
+
const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
|
|
344
|
+
let release = () => void 0;
|
|
345
|
+
const current = new Promise((resolve) => {
|
|
346
|
+
release = resolve;
|
|
347
|
+
});
|
|
348
|
+
const tail = previous.then(() => current);
|
|
349
|
+
this.sessionOperations.set(sessionId, tail);
|
|
350
|
+
await previous;
|
|
351
|
+
try {
|
|
352
|
+
return await operation();
|
|
353
|
+
} finally {
|
|
354
|
+
release();
|
|
355
|
+
if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
264
358
|
async applyTransaction(input) {
|
|
359
|
+
return this.runSessionExclusive(input.sessionId, () => this.applyTransactionExclusive(input));
|
|
360
|
+
}
|
|
361
|
+
async applyTransactionExclusive(input) {
|
|
265
362
|
const startedAt = Date.now();
|
|
266
363
|
const session = this.context.sessions.get(input.sessionId);
|
|
267
364
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -282,22 +379,21 @@ var AuthoringService = class {
|
|
|
282
379
|
operations: input.operations
|
|
283
380
|
});
|
|
284
381
|
if (result.ok === false) {
|
|
382
|
+
const diagnostics = toBackendDiagnostics(result.error);
|
|
285
383
|
this.eventService.emit({
|
|
286
384
|
kind: "transaction.rejected",
|
|
287
385
|
sessionId: session.sessionId,
|
|
288
386
|
canonicalPathKey: session.canonicalPathKey,
|
|
289
387
|
revision: session.revision,
|
|
290
|
-
diagnostics
|
|
291
|
-
code: result.error.code,
|
|
292
|
-
message: issue.message,
|
|
293
|
-
severity: "error"
|
|
294
|
-
})) ?? []
|
|
388
|
+
diagnostics
|
|
295
389
|
});
|
|
296
390
|
return failure("authoring", startedAt, result.error, toSessionSnapshot(session, this.context.capabilities), {
|
|
297
391
|
sessionId: session.sessionId,
|
|
298
|
-
revision: session.revision
|
|
392
|
+
revision: session.revision,
|
|
393
|
+
diagnostics
|
|
299
394
|
});
|
|
300
395
|
}
|
|
396
|
+
recordStaleProjectFiles(session, session.project, result.project);
|
|
301
397
|
session.project = result.project;
|
|
302
398
|
session.revision += 1;
|
|
303
399
|
session.dirty = true;
|
|
@@ -321,15 +417,22 @@ var AuthoringService = class {
|
|
|
321
417
|
});
|
|
322
418
|
}
|
|
323
419
|
async saveSession(input) {
|
|
420
|
+
if (input.force === true || input.mode === "materializeCleanSession") return this.materializeSession({
|
|
421
|
+
sessionId: input.sessionId,
|
|
422
|
+
expectedRevision: input.expectedRevision,
|
|
423
|
+
targetPath: input.targetPath,
|
|
424
|
+
fileSystem: input.fileSystem,
|
|
425
|
+
mode: "fullProject",
|
|
426
|
+
reason: "force_save"
|
|
427
|
+
});
|
|
428
|
+
return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
|
|
429
|
+
}
|
|
430
|
+
async saveSessionExclusive(input) {
|
|
324
431
|
const startedAt = Date.now();
|
|
325
432
|
const session = this.context.sessions.get(input.sessionId);
|
|
326
433
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
message: "saveSession requires an injected BackendFileSystem adapter.",
|
|
330
|
-
capability: "fileSystem",
|
|
331
|
-
requiredAdapter: "BackendFileSystem"
|
|
332
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
434
|
+
const fileSystem = session.fileSystem ?? input.fileSystem ?? this.context.fileSystem;
|
|
435
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
333
436
|
sessionId: session.sessionId,
|
|
334
437
|
revision: session.revision
|
|
335
438
|
});
|
|
@@ -337,7 +440,6 @@ var AuthoringService = class {
|
|
|
337
440
|
sessionId: session.sessionId,
|
|
338
441
|
revision: session.revision
|
|
339
442
|
});
|
|
340
|
-
const fileSystem = this.context.fileSystem;
|
|
341
443
|
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
342
444
|
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
343
445
|
sessionId: session.sessionId,
|
|
@@ -347,6 +449,10 @@ var AuthoringService = class {
|
|
|
347
449
|
sessionId: session.sessionId,
|
|
348
450
|
revision: session.revision
|
|
349
451
|
});
|
|
452
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
453
|
+
sessionId: session.sessionId,
|
|
454
|
+
revision: session.revision
|
|
455
|
+
});
|
|
350
456
|
const committedPaths = [];
|
|
351
457
|
const failedPaths = [];
|
|
352
458
|
this.eventService.emit({
|
|
@@ -356,7 +462,17 @@ var AuthoringService = class {
|
|
|
356
462
|
revision: session.revision
|
|
357
463
|
});
|
|
358
464
|
try {
|
|
359
|
-
await
|
|
465
|
+
await writeSessionProject({
|
|
466
|
+
fileSystem,
|
|
467
|
+
document: (0, _openfairygui_core_uam.materializeUamProject)(session.project),
|
|
468
|
+
fairyPath: session.fairyPath,
|
|
469
|
+
staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
|
|
470
|
+
writtenPaths: committedPaths,
|
|
471
|
+
failedPaths
|
|
472
|
+
});
|
|
473
|
+
session.fileSystem ??= fileSystem;
|
|
474
|
+
session.pendingStaleSourceFiles.clear();
|
|
475
|
+
(0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
|
|
360
476
|
session.lastSavedRevision = session.revision;
|
|
361
477
|
session.dirty = false;
|
|
362
478
|
const cacheEntry = this.cacheService.refreshSession(session);
|
|
@@ -401,6 +517,172 @@ var AuthoringService = class {
|
|
|
401
517
|
});
|
|
402
518
|
}
|
|
403
519
|
}
|
|
520
|
+
async materializeSession(input) {
|
|
521
|
+
return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
|
|
522
|
+
}
|
|
523
|
+
async materializeSessionExclusive(input) {
|
|
524
|
+
const startedAt = Date.now();
|
|
525
|
+
const session = this.context.sessions.get(input.sessionId);
|
|
526
|
+
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
527
|
+
if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
528
|
+
sessionId: session.sessionId,
|
|
529
|
+
revision: session.revision
|
|
530
|
+
});
|
|
531
|
+
const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
|
|
532
|
+
const fileSystem = storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
533
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("materializeSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
534
|
+
sessionId: session.sessionId,
|
|
535
|
+
revision: session.revision
|
|
536
|
+
});
|
|
537
|
+
const fairyPath = storageTarget?.fairyPath ?? session.fairyPath;
|
|
538
|
+
const targetViolation = await validateSaveTarget(fileSystem, fairyPath, input.targetPath);
|
|
539
|
+
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
540
|
+
sessionId: session.sessionId,
|
|
541
|
+
revision: session.revision
|
|
542
|
+
});
|
|
543
|
+
if (storageTarget) {
|
|
544
|
+
const holderSessionId = this.context.sessionsByPath.get(storageTarget.canonicalPathKey);
|
|
545
|
+
if (holderSessionId && holderSessionId !== session.sessionId) return failure("authoring", startedAt, {
|
|
546
|
+
code: "lock_conflict",
|
|
547
|
+
kind: "in_process_session_exists",
|
|
548
|
+
message: `Project is already open in this backend runtime: ${storageTarget.canonicalProjectPath}`,
|
|
549
|
+
canonicalPathKey: storageTarget.canonicalPathKey,
|
|
550
|
+
holderSessionId
|
|
551
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
552
|
+
sessionId: session.sessionId,
|
|
553
|
+
revision: session.revision
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
557
|
+
sessionId: session.sessionId,
|
|
558
|
+
revision: session.revision
|
|
559
|
+
});
|
|
560
|
+
const diagnostics = validationDiagnostics(session.project);
|
|
561
|
+
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
562
|
+
code: "materialize_validation_failed",
|
|
563
|
+
message: `UAM materialize validation failed with ${diagnostics.length} issue(s).`,
|
|
564
|
+
sessionId: session.sessionId,
|
|
565
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
566
|
+
issueCount: diagnostics.length,
|
|
567
|
+
diagnostics
|
|
568
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
569
|
+
sessionId: session.sessionId,
|
|
570
|
+
revision: session.revision,
|
|
571
|
+
diagnostics
|
|
572
|
+
});
|
|
573
|
+
let document;
|
|
574
|
+
try {
|
|
575
|
+
document = (0, _openfairygui_core_uam.materializeUamProject)(session.project);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
const diagnosticsFromError = [{
|
|
578
|
+
code: "materialize_validation_failed",
|
|
579
|
+
message: error instanceof Error ? error.message : String(error),
|
|
580
|
+
severity: "error",
|
|
581
|
+
operationKind: "materializeSession"
|
|
582
|
+
}];
|
|
583
|
+
return failure("authoring", startedAt, {
|
|
584
|
+
code: "materialize_validation_failed",
|
|
585
|
+
message: error instanceof Error ? error.message : String(error),
|
|
586
|
+
sessionId: session.sessionId,
|
|
587
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
588
|
+
issueCount: diagnosticsFromError.length,
|
|
589
|
+
diagnostics: diagnosticsFromError
|
|
590
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
591
|
+
sessionId: session.sessionId,
|
|
592
|
+
revision: session.revision,
|
|
593
|
+
diagnostics: diagnosticsFromError
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
const writtenPaths = [];
|
|
597
|
+
const failedPaths = [];
|
|
598
|
+
const skippedPaths = [];
|
|
599
|
+
this.eventService.emit({
|
|
600
|
+
kind: "save.started",
|
|
601
|
+
sessionId: session.sessionId,
|
|
602
|
+
canonicalPathKey: storageTarget?.canonicalPathKey ?? session.canonicalPathKey,
|
|
603
|
+
revision: session.revision
|
|
604
|
+
});
|
|
605
|
+
try {
|
|
606
|
+
const isSessionStorageTarget = fileSystem === session.fileSystem && fairyPath === session.fairyPath;
|
|
607
|
+
await writeSessionProject({
|
|
608
|
+
fileSystem,
|
|
609
|
+
document,
|
|
610
|
+
fairyPath,
|
|
611
|
+
staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
|
|
612
|
+
writtenPaths,
|
|
613
|
+
failedPaths
|
|
614
|
+
});
|
|
615
|
+
if (isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
|
|
616
|
+
if (storageTarget && !isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
|
|
617
|
+
if (isSessionStorageTarget || storageTarget) (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
|
|
618
|
+
if (storageTarget) {
|
|
619
|
+
this.context.sessionsByPath.delete(session.canonicalPathKey);
|
|
620
|
+
session.fileSystem = storageTarget.fileSystem;
|
|
621
|
+
session.fairyPath = storageTarget.fairyPath;
|
|
622
|
+
session.canonicalProjectPath = storageTarget.canonicalProjectPath;
|
|
623
|
+
session.canonicalPathKey = storageTarget.canonicalPathKey;
|
|
624
|
+
this.context.sessionsByPath.set(session.canonicalPathKey, session.sessionId);
|
|
625
|
+
}
|
|
626
|
+
session.lastSavedRevision = session.revision;
|
|
627
|
+
session.dirty = false;
|
|
628
|
+
const cacheEntry = this.cacheService.refreshSession(session);
|
|
629
|
+
this.eventService.emit({
|
|
630
|
+
kind: "save.completed",
|
|
631
|
+
sessionId: session.sessionId,
|
|
632
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
633
|
+
revision: session.revision
|
|
634
|
+
});
|
|
635
|
+
this.eventService.emit({
|
|
636
|
+
kind: "cache.updated",
|
|
637
|
+
sessionId: session.sessionId,
|
|
638
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
639
|
+
revision: session.revision,
|
|
640
|
+
cacheRevision: cacheEntry.revision
|
|
641
|
+
});
|
|
642
|
+
return success("authoring", startedAt, toMaterializeSnapshot(session, this.context.capabilities, {
|
|
643
|
+
reason: input.reason,
|
|
644
|
+
writtenPaths,
|
|
645
|
+
skippedPaths,
|
|
646
|
+
diagnostics: []
|
|
647
|
+
}), {
|
|
648
|
+
sessionId: session.sessionId,
|
|
649
|
+
revision: session.revision
|
|
650
|
+
});
|
|
651
|
+
} catch (error) {
|
|
652
|
+
const diagnosticsFromError = [{
|
|
653
|
+
code: "write_failed",
|
|
654
|
+
message: error instanceof Error ? error.message : String(error),
|
|
655
|
+
severity: "error",
|
|
656
|
+
path: failedPaths[0],
|
|
657
|
+
operationKind: "materializeSession"
|
|
658
|
+
}];
|
|
659
|
+
this.cacheService.invalidateSession(session);
|
|
660
|
+
this.eventService.emit({
|
|
661
|
+
kind: "save.failed",
|
|
662
|
+
sessionId: session.sessionId,
|
|
663
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
664
|
+
revision: session.revision,
|
|
665
|
+
diagnostics: diagnosticsFromError
|
|
666
|
+
});
|
|
667
|
+
return failure("authoring", startedAt, {
|
|
668
|
+
code: "write_failed",
|
|
669
|
+
message: error instanceof Error ? error.message : String(error),
|
|
670
|
+
sessionId: session.sessionId,
|
|
671
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
672
|
+
attemptedRevision: session.revision,
|
|
673
|
+
lastSavedRevision: session.lastSavedRevision,
|
|
674
|
+
writtenPaths,
|
|
675
|
+
failedPaths,
|
|
676
|
+
skippedPaths,
|
|
677
|
+
diagnostics: diagnosticsFromError,
|
|
678
|
+
diskMayBePartiallyUpdated: true
|
|
679
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
680
|
+
sessionId: session.sessionId,
|
|
681
|
+
revision: session.revision,
|
|
682
|
+
diagnostics: diagnosticsFromError
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
404
686
|
};
|
|
405
687
|
//#endregion
|
|
406
688
|
//#region src/services/cache-service.ts
|
|
@@ -859,6 +1141,68 @@ function createProjectReaderFileSystem(fileSystem) {
|
|
|
859
1141
|
}
|
|
860
1142
|
};
|
|
861
1143
|
}
|
|
1144
|
+
function createCaptureFileSystem(files) {
|
|
1145
|
+
const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
1146
|
+
return {
|
|
1147
|
+
async readFile(filePath) {
|
|
1148
|
+
const value = files.get(normalize(filePath));
|
|
1149
|
+
if (typeof value !== "string") throw new Error(`Captured text file was not found: ${filePath}`);
|
|
1150
|
+
return value;
|
|
1151
|
+
},
|
|
1152
|
+
async readFileRaw(filePath) {
|
|
1153
|
+
const value = files.get(normalize(filePath));
|
|
1154
|
+
if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
|
|
1155
|
+
return value.slice();
|
|
1156
|
+
},
|
|
1157
|
+
async writeFile(filePath, content) {
|
|
1158
|
+
files.set(normalize(filePath), content);
|
|
1159
|
+
},
|
|
1160
|
+
async writeFileRaw(filePath, data) {
|
|
1161
|
+
files.set(normalize(filePath), data.slice());
|
|
1162
|
+
},
|
|
1163
|
+
async mkdir() {},
|
|
1164
|
+
async readdir() {
|
|
1165
|
+
return [];
|
|
1166
|
+
},
|
|
1167
|
+
async exists(filePath) {
|
|
1168
|
+
return files.has(normalize(filePath));
|
|
1169
|
+
},
|
|
1170
|
+
join(...paths) {
|
|
1171
|
+
return normalize(paths.filter(Boolean).join("/"));
|
|
1172
|
+
},
|
|
1173
|
+
dirname(filePath) {
|
|
1174
|
+
const normalized = normalize(filePath);
|
|
1175
|
+
const separator = normalized.lastIndexOf("/");
|
|
1176
|
+
return separator < 0 ? "" : normalized.slice(0, separator);
|
|
1177
|
+
},
|
|
1178
|
+
async unlink(filePath) {
|
|
1179
|
+
files.delete(normalize(filePath));
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
function capturedFilesEqual(left, right) {
|
|
1184
|
+
if (left.size !== right.size) return false;
|
|
1185
|
+
for (const [filePath, leftValue] of left) {
|
|
1186
|
+
const rightValue = right.get(filePath);
|
|
1187
|
+
if (typeof leftValue === "string") {
|
|
1188
|
+
if (leftValue !== rightValue) return false;
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
|
|
1192
|
+
for (let index = 0; index < leftValue.length; index += 1) if (leftValue[index] !== rightValue[index]) return false;
|
|
1193
|
+
}
|
|
1194
|
+
return true;
|
|
1195
|
+
}
|
|
1196
|
+
async function hasFullUamFidelity(document, project) {
|
|
1197
|
+
const sourceFiles = /* @__PURE__ */ new Map();
|
|
1198
|
+
const materializedFiles = /* @__PURE__ */ new Map();
|
|
1199
|
+
try {
|
|
1200
|
+
await Promise.all([new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, "Project.fairy"), new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(materializedFiles)).write((0, _openfairygui_core_uam.materializeUamProject)(project), "Project.fairy")]);
|
|
1201
|
+
} catch {
|
|
1202
|
+
return false;
|
|
1203
|
+
}
|
|
1204
|
+
return capturedFilesEqual(sourceFiles, materializedFiles);
|
|
1205
|
+
}
|
|
862
1206
|
var RuntimeService = class {
|
|
863
1207
|
constructor(context, cacheService, eventService, jobService) {
|
|
864
1208
|
this.context = context;
|
|
@@ -893,7 +1237,8 @@ var RuntimeService = class {
|
|
|
893
1237
|
canonicalPathKey
|
|
894
1238
|
}));
|
|
895
1239
|
await advisoryLock.close();
|
|
896
|
-
const
|
|
1240
|
+
const document = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath, { hydrateResourceBytes: true });
|
|
1241
|
+
const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(document);
|
|
897
1242
|
const sessionId = randomId();
|
|
898
1243
|
const session = {
|
|
899
1244
|
sessionId,
|
|
@@ -901,9 +1246,12 @@ var RuntimeService = class {
|
|
|
901
1246
|
canonicalProjectPath,
|
|
902
1247
|
canonicalPathKey,
|
|
903
1248
|
lockFilePath,
|
|
1249
|
+
fileSystem,
|
|
904
1250
|
project,
|
|
1251
|
+
uamFidelity: await hasFullUamFidelity(document, project) ? "full" : "unsupported",
|
|
905
1252
|
revision: 0,
|
|
906
1253
|
lastSavedRevision: 0,
|
|
1254
|
+
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
907
1255
|
dirty: false,
|
|
908
1256
|
lockHeld: true,
|
|
909
1257
|
closed: false
|
|
@@ -939,8 +1287,10 @@ var RuntimeService = class {
|
|
|
939
1287
|
openProjectSession(input) {
|
|
940
1288
|
const startedAt = Date.now();
|
|
941
1289
|
const sessionId = input.sessionId ?? randomId();
|
|
942
|
-
const
|
|
943
|
-
const
|
|
1290
|
+
const storage = input.storage;
|
|
1291
|
+
const memoryProjectPath = `memory://${sessionId}`;
|
|
1292
|
+
const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
|
|
1293
|
+
const canonicalPathKey = storage?.canonicalPathKey ?? input.canonicalPathKey ?? (storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
|
|
944
1294
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
945
1295
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
946
1296
|
code: "lock_conflict",
|
|
@@ -951,13 +1301,16 @@ var RuntimeService = class {
|
|
|
951
1301
|
});
|
|
952
1302
|
const session = {
|
|
953
1303
|
sessionId,
|
|
954
|
-
fairyPath: canonicalProjectPath,
|
|
1304
|
+
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
955
1305
|
canonicalProjectPath,
|
|
956
1306
|
canonicalPathKey,
|
|
957
1307
|
lockFilePath: "",
|
|
1308
|
+
fileSystem: storage?.fileSystem,
|
|
958
1309
|
project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
|
|
1310
|
+
uamFidelity: "full",
|
|
959
1311
|
revision: 0,
|
|
960
1312
|
lastSavedRevision: 0,
|
|
1313
|
+
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
961
1314
|
dirty: false,
|
|
962
1315
|
lockHeld: false,
|
|
963
1316
|
closed: false
|
|
@@ -1010,7 +1363,25 @@ var RuntimeService = class {
|
|
|
1010
1363
|
}
|
|
1011
1364
|
};
|
|
1012
1365
|
//#endregion
|
|
1013
|
-
//#region src/
|
|
1366
|
+
//#region src/services/artifact-service.ts
|
|
1367
|
+
function createArtifactCapabilities() {
|
|
1368
|
+
const bridge = {
|
|
1369
|
+
available: false,
|
|
1370
|
+
requiredHost: "node",
|
|
1371
|
+
executionBoundary: "external-bridge",
|
|
1372
|
+
bridgeEntrypoint: "@openfairygui/backend/node",
|
|
1373
|
+
reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
|
|
1374
|
+
};
|
|
1375
|
+
return {
|
|
1376
|
+
publish: false,
|
|
1377
|
+
restore: false,
|
|
1378
|
+
status: "bridge-required",
|
|
1379
|
+
publishBridge: bridge,
|
|
1380
|
+
restoreBridge: bridge
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
//#endregion
|
|
1384
|
+
//#region src/runtime/capabilities.ts
|
|
1014
1385
|
const BACKEND_METHODS = [
|
|
1015
1386
|
"getCapabilities",
|
|
1016
1387
|
"openSession",
|
|
@@ -1018,6 +1389,7 @@ const BACKEND_METHODS = [
|
|
|
1018
1389
|
"getSession",
|
|
1019
1390
|
"applyTransaction",
|
|
1020
1391
|
"saveSession",
|
|
1392
|
+
"materializeSession",
|
|
1021
1393
|
"closeSession",
|
|
1022
1394
|
"getEvents",
|
|
1023
1395
|
"getJob",
|
|
@@ -1051,6 +1423,11 @@ function createCapabilities() {
|
|
|
1051
1423
|
resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
|
|
1052
1424
|
nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
|
|
1053
1425
|
gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
|
|
1426
|
+
transactionScope: {
|
|
1427
|
+
resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
|
|
1428
|
+
nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
|
|
1429
|
+
gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds]
|
|
1430
|
+
},
|
|
1054
1431
|
unsupported: ["artifact.publish", "artifact.restore"]
|
|
1055
1432
|
},
|
|
1056
1433
|
artifact: createArtifactCapabilities(),
|
|
@@ -1061,7 +1438,21 @@ function createCapabilities() {
|
|
|
1061
1438
|
adapters: {
|
|
1062
1439
|
fileSystem: {
|
|
1063
1440
|
injected: true,
|
|
1064
|
-
requiredFor: [
|
|
1441
|
+
requiredFor: [
|
|
1442
|
+
"openSession",
|
|
1443
|
+
"saveSession",
|
|
1444
|
+
"materializeSession"
|
|
1445
|
+
]
|
|
1446
|
+
},
|
|
1447
|
+
projectStorage: {
|
|
1448
|
+
injected: true,
|
|
1449
|
+
browserSafe: true,
|
|
1450
|
+
requiredFor: [
|
|
1451
|
+
"openProjectSession.writeback",
|
|
1452
|
+
"saveSession",
|
|
1453
|
+
"materializeSession"
|
|
1454
|
+
],
|
|
1455
|
+
adapterFactory: "createBackendStorageFileSystem"
|
|
1065
1456
|
},
|
|
1066
1457
|
host: {
|
|
1067
1458
|
injected: true,
|
|
@@ -1110,6 +1501,8 @@ function createCapabilities() {
|
|
|
1110
1501
|
}
|
|
1111
1502
|
};
|
|
1112
1503
|
}
|
|
1504
|
+
//#endregion
|
|
1505
|
+
//#region src/runtime.ts
|
|
1113
1506
|
var BackendRuntime = class {
|
|
1114
1507
|
fileSystem;
|
|
1115
1508
|
capabilities;
|
|
@@ -1168,6 +1561,9 @@ var BackendRuntime = class {
|
|
|
1168
1561
|
async saveSession(input) {
|
|
1169
1562
|
return this.authoringService.saveSession(input);
|
|
1170
1563
|
}
|
|
1564
|
+
async materializeSession(input) {
|
|
1565
|
+
return this.authoringService.materializeSession(input);
|
|
1566
|
+
}
|
|
1171
1567
|
async closeSession(input) {
|
|
1172
1568
|
return this.runtimeService.closeSession(input);
|
|
1173
1569
|
}
|