@openfairygui/backend 0.2.0-alpha.6 → 0.2.0-alpha.8
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 +40 -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-CqARWlcn.mjs → runtime-DFatY9W0.mjs} +223 -12
- package/dist/{runtime--ytGfQCF.cjs → runtime-GKzsXJdO.cjs} +222 -11
- package/dist/{runtime-BPuZITUC.d.mts → runtime-GyNVxAQ0.d.mts} +66 -9
- package/dist/{runtime-DlM27Izm.d.cts → runtime-Jec6FcF5.d.cts} +66 -9
- package/package.json +3 -3
- package/src/index.ts +12 -0
- package/src/runtime.ts +98 -8
- package/src/services/authoring-service.ts +318 -16
- package/src/services/context.ts +1 -0
- package/src/services/runtime-service.ts +12 -4
- package/src/storage.ts +192 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UAM_SUPPORTED_MATERIALIZATION_SCOPE, UAM_SUPPORTED_TRANSACTION_SCOPE, liftDocumentToUamProject, materializeUamProject, normalizeUamProject } from "@openfairygui/core/uam";
|
|
1
|
+
import { UAM_SUPPORTED_MATERIALIZATION_SCOPE, UAM_SUPPORTED_TRANSACTION_SCOPE, liftDocumentToUamProject, materializeUamProject, normalizeUamProject, validateUamProject } from "@openfairygui/core/uam";
|
|
2
2
|
import { ProjectReader, ProjectWriter } from "@openfairygui/core/project-io";
|
|
3
3
|
import { applyUamTransactionApp } from "@openfairygui/functions/uam";
|
|
4
4
|
//#region src/contracts.ts
|
|
@@ -264,6 +264,44 @@ function toBackendDiagnostics(error) {
|
|
|
264
264
|
opId: error.opId
|
|
265
265
|
}];
|
|
266
266
|
}
|
|
267
|
+
function createCapabilityUnavailableError$1(message) {
|
|
268
|
+
return {
|
|
269
|
+
code: "capability_unavailable",
|
|
270
|
+
message,
|
|
271
|
+
capability: "fileSystem",
|
|
272
|
+
requiredAdapter: "BackendFileSystem"
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function validationDiagnostics(sessionProject) {
|
|
276
|
+
return validateUamProject(sessionProject).map((issue) => ({
|
|
277
|
+
code: "materialize_validation_failed",
|
|
278
|
+
message: issue.message,
|
|
279
|
+
severity: "error",
|
|
280
|
+
path: issue.path,
|
|
281
|
+
operationKind: "materializeSession"
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
function toMaterializeSnapshot(session, capabilities, input) {
|
|
285
|
+
return {
|
|
286
|
+
...toSessionSnapshot(session, capabilities),
|
|
287
|
+
mode: "fullProject",
|
|
288
|
+
reason: input.reason,
|
|
289
|
+
materializeRevision: session.revision,
|
|
290
|
+
saveRevision: session.lastSavedRevision,
|
|
291
|
+
writtenPaths: [...input.writtenPaths],
|
|
292
|
+
skippedPaths: [...input.skippedPaths],
|
|
293
|
+
diagnostics: input.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function storageCanonicalTarget(input) {
|
|
297
|
+
const canonicalProjectPath = input.canonicalProjectPath ?? (input.fileSystem.dirname(input.fairyPath) || ".");
|
|
298
|
+
return {
|
|
299
|
+
fileSystem: input.fileSystem,
|
|
300
|
+
fairyPath: input.fairyPath,
|
|
301
|
+
canonicalProjectPath,
|
|
302
|
+
canonicalPathKey: input.canonicalPathKey ?? normalizeComparablePath(canonicalProjectPath)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
267
305
|
var AuthoringService = class {
|
|
268
306
|
constructor(context, cacheService, eventService) {
|
|
269
307
|
this.context = context;
|
|
@@ -328,15 +366,19 @@ var AuthoringService = class {
|
|
|
328
366
|
});
|
|
329
367
|
}
|
|
330
368
|
async saveSession(input) {
|
|
369
|
+
if (input.force === true || input.mode === "materializeCleanSession") return this.materializeSession({
|
|
370
|
+
sessionId: input.sessionId,
|
|
371
|
+
expectedRevision: input.expectedRevision,
|
|
372
|
+
targetPath: input.targetPath,
|
|
373
|
+
fileSystem: input.fileSystem,
|
|
374
|
+
mode: "fullProject",
|
|
375
|
+
reason: "force_save"
|
|
376
|
+
});
|
|
331
377
|
const startedAt = Date.now();
|
|
332
378
|
const session = this.context.sessions.get(input.sessionId);
|
|
333
379
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
message: "saveSession requires an injected BackendFileSystem adapter.",
|
|
337
|
-
capability: "fileSystem",
|
|
338
|
-
requiredAdapter: "BackendFileSystem"
|
|
339
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
380
|
+
const fileSystem = input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
381
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
340
382
|
sessionId: session.sessionId,
|
|
341
383
|
revision: session.revision
|
|
342
384
|
});
|
|
@@ -344,7 +386,6 @@ var AuthoringService = class {
|
|
|
344
386
|
sessionId: session.sessionId,
|
|
345
387
|
revision: session.revision
|
|
346
388
|
});
|
|
347
|
-
const fileSystem = this.context.fileSystem;
|
|
348
389
|
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
349
390
|
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
350
391
|
sessionId: session.sessionId,
|
|
@@ -408,6 +449,154 @@ var AuthoringService = class {
|
|
|
408
449
|
});
|
|
409
450
|
}
|
|
410
451
|
}
|
|
452
|
+
async materializeSession(input) {
|
|
453
|
+
const startedAt = Date.now();
|
|
454
|
+
const session = this.context.sessions.get(input.sessionId);
|
|
455
|
+
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
456
|
+
if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
457
|
+
sessionId: session.sessionId,
|
|
458
|
+
revision: session.revision
|
|
459
|
+
});
|
|
460
|
+
const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
|
|
461
|
+
const fileSystem = storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
462
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("materializeSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
463
|
+
sessionId: session.sessionId,
|
|
464
|
+
revision: session.revision
|
|
465
|
+
});
|
|
466
|
+
const fairyPath = storageTarget?.fairyPath ?? session.fairyPath;
|
|
467
|
+
const targetViolation = await validateSaveTarget(fileSystem, fairyPath, input.targetPath);
|
|
468
|
+
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
469
|
+
sessionId: session.sessionId,
|
|
470
|
+
revision: session.revision
|
|
471
|
+
});
|
|
472
|
+
if (storageTarget) {
|
|
473
|
+
const holderSessionId = this.context.sessionsByPath.get(storageTarget.canonicalPathKey);
|
|
474
|
+
if (holderSessionId && holderSessionId !== session.sessionId) return failure("authoring", startedAt, {
|
|
475
|
+
code: "lock_conflict",
|
|
476
|
+
kind: "in_process_session_exists",
|
|
477
|
+
message: `Project is already open in this backend runtime: ${storageTarget.canonicalProjectPath}`,
|
|
478
|
+
canonicalPathKey: storageTarget.canonicalPathKey,
|
|
479
|
+
holderSessionId
|
|
480
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
481
|
+
sessionId: session.sessionId,
|
|
482
|
+
revision: session.revision
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const diagnostics = validationDiagnostics(session.project);
|
|
486
|
+
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
487
|
+
code: "materialize_validation_failed",
|
|
488
|
+
message: `UAM materialize validation failed with ${diagnostics.length} issue(s).`,
|
|
489
|
+
sessionId: session.sessionId,
|
|
490
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
491
|
+
issueCount: diagnostics.length,
|
|
492
|
+
diagnostics
|
|
493
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
494
|
+
sessionId: session.sessionId,
|
|
495
|
+
revision: session.revision,
|
|
496
|
+
diagnostics
|
|
497
|
+
});
|
|
498
|
+
let document;
|
|
499
|
+
try {
|
|
500
|
+
document = materializeUamProject(session.project);
|
|
501
|
+
} catch (error) {
|
|
502
|
+
const diagnosticsFromError = [{
|
|
503
|
+
code: "materialize_validation_failed",
|
|
504
|
+
message: error instanceof Error ? error.message : String(error),
|
|
505
|
+
severity: "error",
|
|
506
|
+
operationKind: "materializeSession"
|
|
507
|
+
}];
|
|
508
|
+
return failure("authoring", startedAt, {
|
|
509
|
+
code: "materialize_validation_failed",
|
|
510
|
+
message: error instanceof Error ? error.message : String(error),
|
|
511
|
+
sessionId: session.sessionId,
|
|
512
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
513
|
+
issueCount: diagnosticsFromError.length,
|
|
514
|
+
diagnostics: diagnosticsFromError
|
|
515
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
516
|
+
sessionId: session.sessionId,
|
|
517
|
+
revision: session.revision,
|
|
518
|
+
diagnostics: diagnosticsFromError
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
const writtenPaths = [];
|
|
522
|
+
const failedPaths = [];
|
|
523
|
+
const skippedPaths = [];
|
|
524
|
+
this.eventService.emit({
|
|
525
|
+
kind: "save.started",
|
|
526
|
+
sessionId: session.sessionId,
|
|
527
|
+
canonicalPathKey: storageTarget?.canonicalPathKey ?? session.canonicalPathKey,
|
|
528
|
+
revision: session.revision
|
|
529
|
+
});
|
|
530
|
+
try {
|
|
531
|
+
await new ProjectWriter(createWriterFileSystem(fileSystem, writtenPaths, failedPaths)).write(document, fairyPath);
|
|
532
|
+
if (storageTarget) {
|
|
533
|
+
this.context.sessionsByPath.delete(session.canonicalPathKey);
|
|
534
|
+
session.fileSystem = storageTarget.fileSystem;
|
|
535
|
+
session.fairyPath = storageTarget.fairyPath;
|
|
536
|
+
session.canonicalProjectPath = storageTarget.canonicalProjectPath;
|
|
537
|
+
session.canonicalPathKey = storageTarget.canonicalPathKey;
|
|
538
|
+
this.context.sessionsByPath.set(session.canonicalPathKey, session.sessionId);
|
|
539
|
+
}
|
|
540
|
+
session.lastSavedRevision = session.revision;
|
|
541
|
+
session.dirty = false;
|
|
542
|
+
const cacheEntry = this.cacheService.refreshSession(session);
|
|
543
|
+
this.eventService.emit({
|
|
544
|
+
kind: "save.completed",
|
|
545
|
+
sessionId: session.sessionId,
|
|
546
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
547
|
+
revision: session.revision
|
|
548
|
+
});
|
|
549
|
+
this.eventService.emit({
|
|
550
|
+
kind: "cache.updated",
|
|
551
|
+
sessionId: session.sessionId,
|
|
552
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
553
|
+
revision: session.revision,
|
|
554
|
+
cacheRevision: cacheEntry.revision
|
|
555
|
+
});
|
|
556
|
+
return success("authoring", startedAt, toMaterializeSnapshot(session, this.context.capabilities, {
|
|
557
|
+
reason: input.reason,
|
|
558
|
+
writtenPaths,
|
|
559
|
+
skippedPaths,
|
|
560
|
+
diagnostics: []
|
|
561
|
+
}), {
|
|
562
|
+
sessionId: session.sessionId,
|
|
563
|
+
revision: session.revision
|
|
564
|
+
});
|
|
565
|
+
} catch (error) {
|
|
566
|
+
const diagnosticsFromError = [{
|
|
567
|
+
code: "write_failed",
|
|
568
|
+
message: error instanceof Error ? error.message : String(error),
|
|
569
|
+
severity: "error",
|
|
570
|
+
path: failedPaths[0],
|
|
571
|
+
operationKind: "materializeSession"
|
|
572
|
+
}];
|
|
573
|
+
this.cacheService.invalidateSession(session);
|
|
574
|
+
this.eventService.emit({
|
|
575
|
+
kind: "save.failed",
|
|
576
|
+
sessionId: session.sessionId,
|
|
577
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
578
|
+
revision: session.revision,
|
|
579
|
+
diagnostics: diagnosticsFromError
|
|
580
|
+
});
|
|
581
|
+
return failure("authoring", startedAt, {
|
|
582
|
+
code: "write_failed",
|
|
583
|
+
message: error instanceof Error ? error.message : String(error),
|
|
584
|
+
sessionId: session.sessionId,
|
|
585
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
586
|
+
attemptedRevision: session.revision,
|
|
587
|
+
lastSavedRevision: session.lastSavedRevision,
|
|
588
|
+
writtenPaths,
|
|
589
|
+
failedPaths,
|
|
590
|
+
skippedPaths,
|
|
591
|
+
diagnostics: diagnosticsFromError,
|
|
592
|
+
diskMayBePartiallyUpdated: true
|
|
593
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
594
|
+
sessionId: session.sessionId,
|
|
595
|
+
revision: session.revision,
|
|
596
|
+
diagnostics: diagnosticsFromError
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
411
600
|
};
|
|
412
601
|
//#endregion
|
|
413
602
|
//#region src/services/cache-service.ts
|
|
@@ -908,6 +1097,7 @@ var RuntimeService = class {
|
|
|
908
1097
|
canonicalProjectPath,
|
|
909
1098
|
canonicalPathKey,
|
|
910
1099
|
lockFilePath,
|
|
1100
|
+
fileSystem,
|
|
911
1101
|
project,
|
|
912
1102
|
revision: 0,
|
|
913
1103
|
lastSavedRevision: 0,
|
|
@@ -946,8 +1136,10 @@ var RuntimeService = class {
|
|
|
946
1136
|
openProjectSession(input) {
|
|
947
1137
|
const startedAt = Date.now();
|
|
948
1138
|
const sessionId = input.sessionId ?? randomId();
|
|
949
|
-
const
|
|
950
|
-
const
|
|
1139
|
+
const storage = input.storage;
|
|
1140
|
+
const memoryProjectPath = `memory://${sessionId}`;
|
|
1141
|
+
const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
|
|
1142
|
+
const canonicalPathKey = storage?.canonicalPathKey ?? input.canonicalPathKey ?? (storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
|
|
951
1143
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
952
1144
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
953
1145
|
code: "lock_conflict",
|
|
@@ -958,10 +1150,11 @@ var RuntimeService = class {
|
|
|
958
1150
|
});
|
|
959
1151
|
const session = {
|
|
960
1152
|
sessionId,
|
|
961
|
-
fairyPath: canonicalProjectPath,
|
|
1153
|
+
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
962
1154
|
canonicalProjectPath,
|
|
963
1155
|
canonicalPathKey,
|
|
964
1156
|
lockFilePath: "",
|
|
1157
|
+
fileSystem: storage?.fileSystem,
|
|
965
1158
|
project: normalizeUamProject(input.project),
|
|
966
1159
|
revision: 0,
|
|
967
1160
|
lastSavedRevision: 0,
|
|
@@ -1025,6 +1218,7 @@ const BACKEND_METHODS = [
|
|
|
1025
1218
|
"getSession",
|
|
1026
1219
|
"applyTransaction",
|
|
1027
1220
|
"saveSession",
|
|
1221
|
+
"materializeSession",
|
|
1028
1222
|
"closeSession",
|
|
1029
1223
|
"getEvents",
|
|
1030
1224
|
"getJob",
|
|
@@ -1073,7 +1267,21 @@ function createCapabilities() {
|
|
|
1073
1267
|
adapters: {
|
|
1074
1268
|
fileSystem: {
|
|
1075
1269
|
injected: true,
|
|
1076
|
-
requiredFor: [
|
|
1270
|
+
requiredFor: [
|
|
1271
|
+
"openSession",
|
|
1272
|
+
"saveSession",
|
|
1273
|
+
"materializeSession"
|
|
1274
|
+
]
|
|
1275
|
+
},
|
|
1276
|
+
projectStorage: {
|
|
1277
|
+
injected: true,
|
|
1278
|
+
browserSafe: true,
|
|
1279
|
+
requiredFor: [
|
|
1280
|
+
"openProjectSession.writeback",
|
|
1281
|
+
"saveSession",
|
|
1282
|
+
"materializeSession"
|
|
1283
|
+
],
|
|
1284
|
+
adapterFactory: "createBackendStorageFileSystem"
|
|
1077
1285
|
},
|
|
1078
1286
|
host: {
|
|
1079
1287
|
injected: true,
|
|
@@ -1180,6 +1388,9 @@ var BackendRuntime = class {
|
|
|
1180
1388
|
async saveSession(input) {
|
|
1181
1389
|
return this.authoringService.saveSession(input);
|
|
1182
1390
|
}
|
|
1391
|
+
async materializeSession(input) {
|
|
1392
|
+
return this.authoringService.materializeSession(input);
|
|
1393
|
+
}
|
|
1183
1394
|
async closeSession(input) {
|
|
1184
1395
|
return this.runtimeService.closeSession(input);
|
|
1185
1396
|
}
|
|
@@ -265,6 +265,44 @@ function toBackendDiagnostics(error) {
|
|
|
265
265
|
opId: error.opId
|
|
266
266
|
}];
|
|
267
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
|
+
}
|
|
268
306
|
var AuthoringService = class {
|
|
269
307
|
constructor(context, cacheService, eventService) {
|
|
270
308
|
this.context = context;
|
|
@@ -329,15 +367,19 @@ var AuthoringService = class {
|
|
|
329
367
|
});
|
|
330
368
|
}
|
|
331
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
|
+
});
|
|
332
378
|
const startedAt = Date.now();
|
|
333
379
|
const session = this.context.sessions.get(input.sessionId);
|
|
334
380
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
message: "saveSession requires an injected BackendFileSystem adapter.",
|
|
338
|
-
capability: "fileSystem",
|
|
339
|
-
requiredAdapter: "BackendFileSystem"
|
|
340
|
-
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
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), {
|
|
341
383
|
sessionId: session.sessionId,
|
|
342
384
|
revision: session.revision
|
|
343
385
|
});
|
|
@@ -345,7 +387,6 @@ var AuthoringService = class {
|
|
|
345
387
|
sessionId: session.sessionId,
|
|
346
388
|
revision: session.revision
|
|
347
389
|
});
|
|
348
|
-
const fileSystem = this.context.fileSystem;
|
|
349
390
|
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
350
391
|
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
351
392
|
sessionId: session.sessionId,
|
|
@@ -409,6 +450,154 @@ var AuthoringService = class {
|
|
|
409
450
|
});
|
|
410
451
|
}
|
|
411
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
|
+
}
|
|
412
601
|
};
|
|
413
602
|
//#endregion
|
|
414
603
|
//#region src/services/cache-service.ts
|
|
@@ -909,6 +1098,7 @@ var RuntimeService = class {
|
|
|
909
1098
|
canonicalProjectPath,
|
|
910
1099
|
canonicalPathKey,
|
|
911
1100
|
lockFilePath,
|
|
1101
|
+
fileSystem,
|
|
912
1102
|
project,
|
|
913
1103
|
revision: 0,
|
|
914
1104
|
lastSavedRevision: 0,
|
|
@@ -947,8 +1137,10 @@ var RuntimeService = class {
|
|
|
947
1137
|
openProjectSession(input) {
|
|
948
1138
|
const startedAt = Date.now();
|
|
949
1139
|
const sessionId = input.sessionId ?? randomId();
|
|
950
|
-
const
|
|
951
|
-
const
|
|
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());
|
|
952
1144
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
953
1145
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
954
1146
|
code: "lock_conflict",
|
|
@@ -959,10 +1151,11 @@ var RuntimeService = class {
|
|
|
959
1151
|
});
|
|
960
1152
|
const session = {
|
|
961
1153
|
sessionId,
|
|
962
|
-
fairyPath: canonicalProjectPath,
|
|
1154
|
+
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
963
1155
|
canonicalProjectPath,
|
|
964
1156
|
canonicalPathKey,
|
|
965
1157
|
lockFilePath: "",
|
|
1158
|
+
fileSystem: storage?.fileSystem,
|
|
966
1159
|
project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
|
|
967
1160
|
revision: 0,
|
|
968
1161
|
lastSavedRevision: 0,
|
|
@@ -1026,6 +1219,7 @@ const BACKEND_METHODS = [
|
|
|
1026
1219
|
"getSession",
|
|
1027
1220
|
"applyTransaction",
|
|
1028
1221
|
"saveSession",
|
|
1222
|
+
"materializeSession",
|
|
1029
1223
|
"closeSession",
|
|
1030
1224
|
"getEvents",
|
|
1031
1225
|
"getJob",
|
|
@@ -1074,7 +1268,21 @@ function createCapabilities() {
|
|
|
1074
1268
|
adapters: {
|
|
1075
1269
|
fileSystem: {
|
|
1076
1270
|
injected: true,
|
|
1077
|
-
requiredFor: [
|
|
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"
|
|
1078
1286
|
},
|
|
1079
1287
|
host: {
|
|
1080
1288
|
injected: true,
|
|
@@ -1181,6 +1389,9 @@ var BackendRuntime = class {
|
|
|
1181
1389
|
async saveSession(input) {
|
|
1182
1390
|
return this.authoringService.saveSession(input);
|
|
1183
1391
|
}
|
|
1392
|
+
async materializeSession(input) {
|
|
1393
|
+
return this.authoringService.materializeSession(input);
|
|
1394
|
+
}
|
|
1184
1395
|
async closeSession(input) {
|
|
1185
1396
|
return this.runtimeService.closeSession(input);
|
|
1186
1397
|
}
|