@openfairygui/backend 0.2.0-alpha.1 → 0.2.0-alpha.13
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/LICENSE +21 -0
- 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-DLWLghmH.mjs → runtime-DFatY9W0.mjs} +244 -19
- package/dist/{runtime-BXbt135W.cjs → runtime-GKzsXJdO.cjs} +251 -26
- package/dist/{runtime-C5JY_kRI.d.cts → runtime-GyNVxAQ0.d.mts} +81 -11
- package/dist/{runtime-C5ZsWMnH.d.mts → runtime-Jec6FcF5.d.cts} +81 -11
- package/package.json +65 -65
- package/src/contracts.ts +8 -0
- package/src/index.ts +12 -0
- package/src/runtime.ts +156 -29
- package/src/services/authoring-service.ts +462 -46
- package/src/services/context.ts +2 -1
- package/src/services/runtime-service.ts +62 -25
- package/src/storage.ts +192 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { UAM_SUPPORTED_MATERIALIZATION_SCOPE, UAM_SUPPORTED_TRANSACTION_SCOPE, liftDocumentToUamProject, materializeUamProject, normalizeUamProject, validateUamProject } from "@openfairygui/core/uam";
|
|
2
|
+
import { ProjectReader, ProjectWriter } from "@openfairygui/core/project-io";
|
|
3
|
+
import { applyUamTransactionApp } from "@openfairygui/functions/uam";
|
|
3
4
|
//#region src/contracts.ts
|
|
4
5
|
const BACKEND_CONTRACT_VERSION = "1.1.0-p2";
|
|
5
6
|
const BACKEND_CAPABILITY_SCHEMA_VERSION = 2;
|
|
@@ -253,6 +254,54 @@ function createWriterFileSystem(fileSystem, committedPaths, failedPaths) {
|
|
|
253
254
|
}
|
|
254
255
|
};
|
|
255
256
|
}
|
|
257
|
+
function toBackendDiagnostics(error) {
|
|
258
|
+
return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
|
|
259
|
+
code: error.code,
|
|
260
|
+
message: error.message,
|
|
261
|
+
severity: "error",
|
|
262
|
+
operationKind: error.operationKind,
|
|
263
|
+
opIndex: error.opIndex,
|
|
264
|
+
opId: error.opId
|
|
265
|
+
}];
|
|
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
|
+
}
|
|
256
305
|
var AuthoringService = class {
|
|
257
306
|
constructor(context, cacheService, eventService) {
|
|
258
307
|
this.context = context;
|
|
@@ -280,20 +329,18 @@ var AuthoringService = class {
|
|
|
280
329
|
operations: input.operations
|
|
281
330
|
});
|
|
282
331
|
if (result.ok === false) {
|
|
332
|
+
const diagnostics = toBackendDiagnostics(result.error);
|
|
283
333
|
this.eventService.emit({
|
|
284
334
|
kind: "transaction.rejected",
|
|
285
335
|
sessionId: session.sessionId,
|
|
286
336
|
canonicalPathKey: session.canonicalPathKey,
|
|
287
337
|
revision: session.revision,
|
|
288
|
-
diagnostics
|
|
289
|
-
code: result.error.code,
|
|
290
|
-
message: issue.message,
|
|
291
|
-
severity: "error"
|
|
292
|
-
})) ?? []
|
|
338
|
+
diagnostics
|
|
293
339
|
});
|
|
294
340
|
return failure("authoring", startedAt, result.error, toSessionSnapshot(session, this.context.capabilities), {
|
|
295
341
|
sessionId: session.sessionId,
|
|
296
|
-
revision: session.revision
|
|
342
|
+
revision: session.revision,
|
|
343
|
+
diagnostics
|
|
297
344
|
});
|
|
298
345
|
}
|
|
299
346
|
session.project = result.project;
|
|
@@ -319,15 +366,19 @@ var AuthoringService = class {
|
|
|
319
366
|
});
|
|
320
367
|
}
|
|
321
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
|
+
});
|
|
322
377
|
const startedAt = Date.now();
|
|
323
378
|
const session = this.context.sessions.get(input.sessionId);
|
|
324
379
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
message: "saveSession requires an injected BackendFileSystem adapter.",
|
|
328
|
-
capability: "fileSystem",
|
|
329
|
-
requiredAdapter: "BackendFileSystem"
|
|
330
|
-
}, 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), {
|
|
331
382
|
sessionId: session.sessionId,
|
|
332
383
|
revision: session.revision
|
|
333
384
|
});
|
|
@@ -335,7 +386,6 @@ var AuthoringService = class {
|
|
|
335
386
|
sessionId: session.sessionId,
|
|
336
387
|
revision: session.revision
|
|
337
388
|
});
|
|
338
|
-
const fileSystem = this.context.fileSystem;
|
|
339
389
|
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
340
390
|
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
341
391
|
sessionId: session.sessionId,
|
|
@@ -399,6 +449,154 @@ var AuthoringService = class {
|
|
|
399
449
|
});
|
|
400
450
|
}
|
|
401
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
|
+
}
|
|
402
600
|
};
|
|
403
601
|
//#endregion
|
|
404
602
|
//#region src/services/cache-service.ts
|
|
@@ -899,6 +1097,7 @@ var RuntimeService = class {
|
|
|
899
1097
|
canonicalProjectPath,
|
|
900
1098
|
canonicalPathKey,
|
|
901
1099
|
lockFilePath,
|
|
1100
|
+
fileSystem,
|
|
902
1101
|
project,
|
|
903
1102
|
revision: 0,
|
|
904
1103
|
lastSavedRevision: 0,
|
|
@@ -937,8 +1136,10 @@ var RuntimeService = class {
|
|
|
937
1136
|
openProjectSession(input) {
|
|
938
1137
|
const startedAt = Date.now();
|
|
939
1138
|
const sessionId = input.sessionId ?? randomId();
|
|
940
|
-
const
|
|
941
|
-
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());
|
|
942
1143
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
943
1144
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
944
1145
|
code: "lock_conflict",
|
|
@@ -949,10 +1150,11 @@ var RuntimeService = class {
|
|
|
949
1150
|
});
|
|
950
1151
|
const session = {
|
|
951
1152
|
sessionId,
|
|
952
|
-
fairyPath: canonicalProjectPath,
|
|
1153
|
+
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
953
1154
|
canonicalProjectPath,
|
|
954
1155
|
canonicalPathKey,
|
|
955
1156
|
lockFilePath: "",
|
|
1157
|
+
fileSystem: storage?.fileSystem,
|
|
956
1158
|
project: normalizeUamProject(input.project),
|
|
957
1159
|
revision: 0,
|
|
958
1160
|
lastSavedRevision: 0,
|
|
@@ -1016,6 +1218,7 @@ const BACKEND_METHODS = [
|
|
|
1016
1218
|
"getSession",
|
|
1017
1219
|
"applyTransaction",
|
|
1018
1220
|
"saveSession",
|
|
1221
|
+
"materializeSession",
|
|
1019
1222
|
"closeSession",
|
|
1020
1223
|
"getEvents",
|
|
1021
1224
|
"getJob",
|
|
@@ -1049,6 +1252,11 @@ function createCapabilities() {
|
|
|
1049
1252
|
resourceKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
|
|
1050
1253
|
nodeKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
|
|
1051
1254
|
gearKinds: [...UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
|
|
1255
|
+
transactionScope: {
|
|
1256
|
+
resourceKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
|
|
1257
|
+
nodeKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
|
|
1258
|
+
gearKinds: [...UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds]
|
|
1259
|
+
},
|
|
1052
1260
|
unsupported: ["artifact.publish", "artifact.restore"]
|
|
1053
1261
|
},
|
|
1054
1262
|
artifact: createArtifactCapabilities(),
|
|
@@ -1059,7 +1267,21 @@ function createCapabilities() {
|
|
|
1059
1267
|
adapters: {
|
|
1060
1268
|
fileSystem: {
|
|
1061
1269
|
injected: true,
|
|
1062
|
-
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"
|
|
1063
1285
|
},
|
|
1064
1286
|
host: {
|
|
1065
1287
|
injected: true,
|
|
@@ -1166,6 +1388,9 @@ var BackendRuntime = class {
|
|
|
1166
1388
|
async saveSession(input) {
|
|
1167
1389
|
return this.authoringService.saveSession(input);
|
|
1168
1390
|
}
|
|
1391
|
+
async materializeSession(input) {
|
|
1392
|
+
return this.authoringService.materializeSession(input);
|
|
1393
|
+
}
|
|
1169
1394
|
async closeSession(input) {
|
|
1170
1395
|
return this.runtimeService.closeSession(input);
|
|
1171
1396
|
}
|