@openfairygui/backend 0.2.0-alpha.3 → 0.2.0-alpha.31
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 +150 -1
- package/dist/index.d.cts +34 -2
- package/dist/index.d.mts +34 -2
- package/dist/index.mjs +150 -2
- package/dist/node.cjs +4 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/node.mjs +4 -1
- package/dist/{runtime-BG5qOVkJ.mjs → runtime-BDkZmTFN.mjs} +480 -126
- package/dist/{runtime-IrXqqmko.d.cts → runtime-BQOEw22M.d.mts} +85 -10
- package/dist/{runtime-jKuQmcqM.cjs → runtime-DtcaaVfy.cjs} +479 -125
- package/dist/{runtime-u1Bq_ysz.d.mts → runtime-Wu6vGZ-j.d.cts} +86 -11
- package/package.json +5 -4
- package/src/contracts.ts +8 -0
- package/src/index.ts +25 -12
- package/src/node.ts +3 -0
- package/src/runtime/capabilities.ts +126 -0
- package/src/runtime/contracts.ts +513 -0
- package/src/runtime.ts +67 -551
- package/src/services/authoring-service.ts +504 -81
- package/src/services/cache-service.ts +1 -2
- package/src/services/context.ts +14 -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 +122 -12
- package/src/services/session-project-writer.ts +67 -0
- package/src/services/session-utils.ts +6 -3
- package/src/storage.ts +201 -0
- package/src/services/snapshot-utils.ts +0 -43
|
@@ -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,38 +120,50 @@ function failure(stage, startedAt, error, session, options) {
|
|
|
138
120
|
};
|
|
139
121
|
}
|
|
140
122
|
//#endregion
|
|
141
|
-
//#region src/services/
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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 }))
|
|
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;
|
|
168
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
|
+
rmdir: (path) => trackWrite(path, () => fileSystem.rmdir(path))
|
|
169
160
|
};
|
|
170
161
|
}
|
|
171
|
-
function
|
|
172
|
-
|
|
162
|
+
async function writeSessionProject(input) {
|
|
163
|
+
await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, {
|
|
164
|
+
staleSourceFiles: input.staleSourceFiles,
|
|
165
|
+
staleResourceFolders: input.staleResourceFolders
|
|
166
|
+
});
|
|
173
167
|
}
|
|
174
168
|
//#endregion
|
|
175
169
|
//#region src/services/session-utils.ts
|
|
@@ -180,8 +174,9 @@ function toSessionSnapshot(session, capabilities) {
|
|
|
180
174
|
revision: session.revision,
|
|
181
175
|
lastSavedRevision: session.lastSavedRevision,
|
|
182
176
|
dirty: session.dirty,
|
|
177
|
+
uamFidelity: session.uamFidelity,
|
|
183
178
|
lockHeld: session.lockHeld,
|
|
184
|
-
capabilities:
|
|
179
|
+
capabilities: structuredClone(capabilities)
|
|
185
180
|
};
|
|
186
181
|
}
|
|
187
182
|
function createSessionNotFoundError(sessionId) {
|
|
@@ -203,65 +198,111 @@ function createStaleWriteError(session, expectedRevision) {
|
|
|
203
198
|
}
|
|
204
199
|
//#endregion
|
|
205
200
|
//#region src/services/authoring-service.ts
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
201
|
+
function sourceFileKey(source) {
|
|
202
|
+
return [
|
|
203
|
+
source.branch,
|
|
204
|
+
source.packageName,
|
|
205
|
+
source.path,
|
|
206
|
+
source.fileName
|
|
207
|
+
].join("\0");
|
|
208
|
+
}
|
|
209
|
+
function resourceFolderKey(folder) {
|
|
210
|
+
return [
|
|
211
|
+
folder.branch,
|
|
212
|
+
folder.packageName,
|
|
213
|
+
folder.path
|
|
214
|
+
].join("\0");
|
|
215
|
+
}
|
|
216
|
+
function recordStaleProjectFiles(session, previousProject, nextProject) {
|
|
217
|
+
if (!session.fileSystem) return;
|
|
218
|
+
for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(previousProject, nextProject)) session.pendingStaleSourceFiles.set(sourceFileKey(source), source);
|
|
219
|
+
for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(nextProject, previousProject)) session.pendingStaleSourceFiles.delete(sourceFileKey(source));
|
|
220
|
+
for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(previousProject, nextProject)) session.pendingStaleResourceFolders.set(resourceFolderKey(folder), folder);
|
|
221
|
+
for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(nextProject, previousProject)) session.pendingStaleResourceFolders.delete(resourceFolderKey(folder));
|
|
222
|
+
}
|
|
223
|
+
function toBackendDiagnostics(error) {
|
|
224
|
+
return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
|
|
225
|
+
code: error.code,
|
|
226
|
+
message: error.message,
|
|
227
|
+
severity: "error",
|
|
228
|
+
operationKind: error.operationKind,
|
|
229
|
+
opIndex: error.opIndex,
|
|
230
|
+
opId: error.opId
|
|
231
|
+
}];
|
|
232
|
+
}
|
|
233
|
+
function createCapabilityUnavailableError$1(message) {
|
|
217
234
|
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
|
-
|
|
235
|
+
code: "capability_unavailable",
|
|
236
|
+
message,
|
|
237
|
+
capability: "fileSystem",
|
|
238
|
+
requiredAdapter: "BackendFileSystem"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function createUamFidelityUnsupportedError(session) {
|
|
242
|
+
return {
|
|
243
|
+
code: "uam_fidelity_unsupported",
|
|
244
|
+
message: "The source project contains formal properties that the current UAM cannot preserve.",
|
|
245
|
+
sessionId: session.sessionId,
|
|
246
|
+
canonicalPathKey: session.canonicalPathKey
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function validationDiagnostics(sessionProject) {
|
|
250
|
+
return (0, _openfairygui_core_uam.validateUamProject)(sessionProject).map((issue) => ({
|
|
251
|
+
code: "materialize_validation_failed",
|
|
252
|
+
message: issue.message,
|
|
253
|
+
severity: "error",
|
|
254
|
+
path: issue.path,
|
|
255
|
+
operationKind: "materializeSession"
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
function toMaterializeSnapshot(session, capabilities, input) {
|
|
259
|
+
return {
|
|
260
|
+
...toSessionSnapshot(session, capabilities),
|
|
261
|
+
mode: "fullProject",
|
|
262
|
+
reason: input.reason,
|
|
263
|
+
materializeRevision: session.revision,
|
|
264
|
+
saveRevision: session.lastSavedRevision,
|
|
265
|
+
writtenPaths: [...input.writtenPaths],
|
|
266
|
+
skippedPaths: [...input.skippedPaths],
|
|
267
|
+
diagnostics: input.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function storageCanonicalTarget(input) {
|
|
271
|
+
const canonicalProjectPath = input.canonicalProjectPath ?? (input.fileSystem.dirname(input.fairyPath) || ".");
|
|
272
|
+
return {
|
|
273
|
+
fileSystem: input.fileSystem,
|
|
274
|
+
fairyPath: input.fairyPath,
|
|
275
|
+
canonicalProjectPath,
|
|
276
|
+
canonicalPathKey: input.canonicalPathKey ?? normalizeComparablePath(canonicalProjectPath)
|
|
256
277
|
};
|
|
257
278
|
}
|
|
258
279
|
var AuthoringService = class {
|
|
280
|
+
sessionOperations = /* @__PURE__ */ new Map();
|
|
259
281
|
constructor(context, cacheService, eventService) {
|
|
260
282
|
this.context = context;
|
|
261
283
|
this.cacheService = cacheService;
|
|
262
284
|
this.eventService = eventService;
|
|
263
285
|
}
|
|
286
|
+
async runSessionExclusive(sessionId, operation) {
|
|
287
|
+
const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
|
|
288
|
+
let release = () => void 0;
|
|
289
|
+
const current = new Promise((resolve) => {
|
|
290
|
+
release = resolve;
|
|
291
|
+
});
|
|
292
|
+
const tail = previous.then(() => current);
|
|
293
|
+
this.sessionOperations.set(sessionId, tail);
|
|
294
|
+
await previous;
|
|
295
|
+
try {
|
|
296
|
+
return await operation();
|
|
297
|
+
} finally {
|
|
298
|
+
release();
|
|
299
|
+
if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
264
302
|
async applyTransaction(input) {
|
|
303
|
+
return this.runSessionExclusive(input.sessionId, () => this.applyTransactionExclusive(input));
|
|
304
|
+
}
|
|
305
|
+
async applyTransactionExclusive(input) {
|
|
265
306
|
const startedAt = Date.now();
|
|
266
307
|
const session = this.context.sessions.get(input.sessionId);
|
|
267
308
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -282,22 +323,21 @@ var AuthoringService = class {
|
|
|
282
323
|
operations: input.operations
|
|
283
324
|
});
|
|
284
325
|
if (result.ok === false) {
|
|
326
|
+
const diagnostics = toBackendDiagnostics(result.error);
|
|
285
327
|
this.eventService.emit({
|
|
286
328
|
kind: "transaction.rejected",
|
|
287
329
|
sessionId: session.sessionId,
|
|
288
330
|
canonicalPathKey: session.canonicalPathKey,
|
|
289
331
|
revision: session.revision,
|
|
290
|
-
diagnostics
|
|
291
|
-
code: result.error.code,
|
|
292
|
-
message: issue.message,
|
|
293
|
-
severity: "error"
|
|
294
|
-
})) ?? []
|
|
332
|
+
diagnostics
|
|
295
333
|
});
|
|
296
334
|
return failure("authoring", startedAt, result.error, toSessionSnapshot(session, this.context.capabilities), {
|
|
297
335
|
sessionId: session.sessionId,
|
|
298
|
-
revision: session.revision
|
|
336
|
+
revision: session.revision,
|
|
337
|
+
diagnostics
|
|
299
338
|
});
|
|
300
339
|
}
|
|
340
|
+
recordStaleProjectFiles(session, session.project, result.project);
|
|
301
341
|
session.project = result.project;
|
|
302
342
|
session.revision += 1;
|
|
303
343
|
session.dirty = true;
|
|
@@ -321,15 +361,22 @@ var AuthoringService = class {
|
|
|
321
361
|
});
|
|
322
362
|
}
|
|
323
363
|
async saveSession(input) {
|
|
364
|
+
if (input.force === true || input.mode === "materializeCleanSession") return this.materializeSession({
|
|
365
|
+
sessionId: input.sessionId,
|
|
366
|
+
expectedRevision: input.expectedRevision,
|
|
367
|
+
targetPath: input.targetPath,
|
|
368
|
+
fileSystem: input.fileSystem,
|
|
369
|
+
mode: "fullProject",
|
|
370
|
+
reason: "force_save"
|
|
371
|
+
});
|
|
372
|
+
return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
|
|
373
|
+
}
|
|
374
|
+
async saveSessionExclusive(input) {
|
|
324
375
|
const startedAt = Date.now();
|
|
325
376
|
const session = this.context.sessions.get(input.sessionId);
|
|
326
377
|
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), {
|
|
378
|
+
const fileSystem = session.fileSystem ?? input.fileSystem ?? this.context.fileSystem;
|
|
379
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
333
380
|
sessionId: session.sessionId,
|
|
334
381
|
revision: session.revision
|
|
335
382
|
});
|
|
@@ -337,7 +384,6 @@ var AuthoringService = class {
|
|
|
337
384
|
sessionId: session.sessionId,
|
|
338
385
|
revision: session.revision
|
|
339
386
|
});
|
|
340
|
-
const fileSystem = this.context.fileSystem;
|
|
341
387
|
const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
|
|
342
388
|
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
343
389
|
sessionId: session.sessionId,
|
|
@@ -347,6 +393,10 @@ var AuthoringService = class {
|
|
|
347
393
|
sessionId: session.sessionId,
|
|
348
394
|
revision: session.revision
|
|
349
395
|
});
|
|
396
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
397
|
+
sessionId: session.sessionId,
|
|
398
|
+
revision: session.revision
|
|
399
|
+
});
|
|
350
400
|
const committedPaths = [];
|
|
351
401
|
const failedPaths = [];
|
|
352
402
|
this.eventService.emit({
|
|
@@ -356,7 +406,19 @@ var AuthoringService = class {
|
|
|
356
406
|
revision: session.revision
|
|
357
407
|
});
|
|
358
408
|
try {
|
|
359
|
-
await
|
|
409
|
+
await writeSessionProject({
|
|
410
|
+
fileSystem,
|
|
411
|
+
document: (0, _openfairygui_core_uam.materializeUamProject)(session.project),
|
|
412
|
+
fairyPath: session.fairyPath,
|
|
413
|
+
staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
|
|
414
|
+
staleResourceFolders: [...session.pendingStaleResourceFolders.values()],
|
|
415
|
+
writtenPaths: committedPaths,
|
|
416
|
+
failedPaths
|
|
417
|
+
});
|
|
418
|
+
session.fileSystem ??= fileSystem;
|
|
419
|
+
session.pendingStaleSourceFiles.clear();
|
|
420
|
+
session.pendingStaleResourceFolders.clear();
|
|
421
|
+
(0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
|
|
360
422
|
session.lastSavedRevision = session.revision;
|
|
361
423
|
session.dirty = false;
|
|
362
424
|
const cacheEntry = this.cacheService.refreshSession(session);
|
|
@@ -401,6 +463,179 @@ var AuthoringService = class {
|
|
|
401
463
|
});
|
|
402
464
|
}
|
|
403
465
|
}
|
|
466
|
+
async materializeSession(input) {
|
|
467
|
+
return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
|
|
468
|
+
}
|
|
469
|
+
async materializeSessionExclusive(input) {
|
|
470
|
+
const startedAt = Date.now();
|
|
471
|
+
const session = this.context.sessions.get(input.sessionId);
|
|
472
|
+
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
473
|
+
if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
|
|
474
|
+
sessionId: session.sessionId,
|
|
475
|
+
revision: session.revision
|
|
476
|
+
});
|
|
477
|
+
const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
|
|
478
|
+
const fileSystem = storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
479
|
+
if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("materializeSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
|
|
480
|
+
sessionId: session.sessionId,
|
|
481
|
+
revision: session.revision
|
|
482
|
+
});
|
|
483
|
+
const fairyPath = storageTarget?.fairyPath ?? session.fairyPath;
|
|
484
|
+
const targetViolation = await validateSaveTarget(fileSystem, fairyPath, input.targetPath);
|
|
485
|
+
if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
|
|
486
|
+
sessionId: session.sessionId,
|
|
487
|
+
revision: session.revision
|
|
488
|
+
});
|
|
489
|
+
if (storageTarget) {
|
|
490
|
+
const holderSessionId = this.context.sessionsByPath.get(storageTarget.canonicalPathKey);
|
|
491
|
+
if (holderSessionId && holderSessionId !== session.sessionId) return failure("authoring", startedAt, {
|
|
492
|
+
code: "lock_conflict",
|
|
493
|
+
kind: "in_process_session_exists",
|
|
494
|
+
message: `Project is already open in this backend runtime: ${storageTarget.canonicalProjectPath}`,
|
|
495
|
+
canonicalPathKey: storageTarget.canonicalPathKey,
|
|
496
|
+
holderSessionId
|
|
497
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
498
|
+
sessionId: session.sessionId,
|
|
499
|
+
revision: session.revision
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
503
|
+
sessionId: session.sessionId,
|
|
504
|
+
revision: session.revision
|
|
505
|
+
});
|
|
506
|
+
const diagnostics = validationDiagnostics(session.project);
|
|
507
|
+
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
508
|
+
code: "materialize_validation_failed",
|
|
509
|
+
message: `UAM materialize validation failed with ${diagnostics.length} issue(s).`,
|
|
510
|
+
sessionId: session.sessionId,
|
|
511
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
512
|
+
issueCount: diagnostics.length,
|
|
513
|
+
diagnostics
|
|
514
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
515
|
+
sessionId: session.sessionId,
|
|
516
|
+
revision: session.revision,
|
|
517
|
+
diagnostics
|
|
518
|
+
});
|
|
519
|
+
let document;
|
|
520
|
+
try {
|
|
521
|
+
document = (0, _openfairygui_core_uam.materializeUamProject)(session.project);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
const diagnosticsFromError = [{
|
|
524
|
+
code: "materialize_validation_failed",
|
|
525
|
+
message: error instanceof Error ? error.message : String(error),
|
|
526
|
+
severity: "error",
|
|
527
|
+
operationKind: "materializeSession"
|
|
528
|
+
}];
|
|
529
|
+
return failure("authoring", startedAt, {
|
|
530
|
+
code: "materialize_validation_failed",
|
|
531
|
+
message: error instanceof Error ? error.message : String(error),
|
|
532
|
+
sessionId: session.sessionId,
|
|
533
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
534
|
+
issueCount: diagnosticsFromError.length,
|
|
535
|
+
diagnostics: diagnosticsFromError
|
|
536
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
537
|
+
sessionId: session.sessionId,
|
|
538
|
+
revision: session.revision,
|
|
539
|
+
diagnostics: diagnosticsFromError
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
const writtenPaths = [];
|
|
543
|
+
const failedPaths = [];
|
|
544
|
+
const skippedPaths = [];
|
|
545
|
+
this.eventService.emit({
|
|
546
|
+
kind: "save.started",
|
|
547
|
+
sessionId: session.sessionId,
|
|
548
|
+
canonicalPathKey: storageTarget?.canonicalPathKey ?? session.canonicalPathKey,
|
|
549
|
+
revision: session.revision
|
|
550
|
+
});
|
|
551
|
+
try {
|
|
552
|
+
const isSessionStorageTarget = fileSystem === session.fileSystem && fairyPath === session.fairyPath;
|
|
553
|
+
await writeSessionProject({
|
|
554
|
+
fileSystem,
|
|
555
|
+
document,
|
|
556
|
+
fairyPath,
|
|
557
|
+
staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
|
|
558
|
+
staleResourceFolders: isSessionStorageTarget ? [...session.pendingStaleResourceFolders.values()] : [],
|
|
559
|
+
writtenPaths,
|
|
560
|
+
failedPaths
|
|
561
|
+
});
|
|
562
|
+
if (isSessionStorageTarget) {
|
|
563
|
+
session.pendingStaleSourceFiles.clear();
|
|
564
|
+
session.pendingStaleResourceFolders.clear();
|
|
565
|
+
}
|
|
566
|
+
if (storageTarget && !isSessionStorageTarget) {
|
|
567
|
+
session.pendingStaleSourceFiles.clear();
|
|
568
|
+
session.pendingStaleResourceFolders.clear();
|
|
569
|
+
}
|
|
570
|
+
if (isSessionStorageTarget || storageTarget) (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
|
|
571
|
+
if (storageTarget) {
|
|
572
|
+
this.context.sessionsByPath.delete(session.canonicalPathKey);
|
|
573
|
+
session.fileSystem = storageTarget.fileSystem;
|
|
574
|
+
session.fairyPath = storageTarget.fairyPath;
|
|
575
|
+
session.canonicalProjectPath = storageTarget.canonicalProjectPath;
|
|
576
|
+
session.canonicalPathKey = storageTarget.canonicalPathKey;
|
|
577
|
+
this.context.sessionsByPath.set(session.canonicalPathKey, session.sessionId);
|
|
578
|
+
}
|
|
579
|
+
session.lastSavedRevision = session.revision;
|
|
580
|
+
session.dirty = false;
|
|
581
|
+
const cacheEntry = this.cacheService.refreshSession(session);
|
|
582
|
+
this.eventService.emit({
|
|
583
|
+
kind: "save.completed",
|
|
584
|
+
sessionId: session.sessionId,
|
|
585
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
586
|
+
revision: session.revision
|
|
587
|
+
});
|
|
588
|
+
this.eventService.emit({
|
|
589
|
+
kind: "cache.updated",
|
|
590
|
+
sessionId: session.sessionId,
|
|
591
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
592
|
+
revision: session.revision,
|
|
593
|
+
cacheRevision: cacheEntry.revision
|
|
594
|
+
});
|
|
595
|
+
return success("authoring", startedAt, toMaterializeSnapshot(session, this.context.capabilities, {
|
|
596
|
+
reason: input.reason,
|
|
597
|
+
writtenPaths,
|
|
598
|
+
skippedPaths,
|
|
599
|
+
diagnostics: []
|
|
600
|
+
}), {
|
|
601
|
+
sessionId: session.sessionId,
|
|
602
|
+
revision: session.revision
|
|
603
|
+
});
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const diagnosticsFromError = [{
|
|
606
|
+
code: "write_failed",
|
|
607
|
+
message: error instanceof Error ? error.message : String(error),
|
|
608
|
+
severity: "error",
|
|
609
|
+
path: failedPaths[0],
|
|
610
|
+
operationKind: "materializeSession"
|
|
611
|
+
}];
|
|
612
|
+
this.cacheService.invalidateSession(session);
|
|
613
|
+
this.eventService.emit({
|
|
614
|
+
kind: "save.failed",
|
|
615
|
+
sessionId: session.sessionId,
|
|
616
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
617
|
+
revision: session.revision,
|
|
618
|
+
diagnostics: diagnosticsFromError
|
|
619
|
+
});
|
|
620
|
+
return failure("authoring", startedAt, {
|
|
621
|
+
code: "write_failed",
|
|
622
|
+
message: error instanceof Error ? error.message : String(error),
|
|
623
|
+
sessionId: session.sessionId,
|
|
624
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
625
|
+
attemptedRevision: session.revision,
|
|
626
|
+
lastSavedRevision: session.lastSavedRevision,
|
|
627
|
+
writtenPaths,
|
|
628
|
+
failedPaths,
|
|
629
|
+
skippedPaths,
|
|
630
|
+
diagnostics: diagnosticsFromError,
|
|
631
|
+
diskMayBePartiallyUpdated: true
|
|
632
|
+
}, toSessionSnapshot(session, this.context.capabilities), {
|
|
633
|
+
sessionId: session.sessionId,
|
|
634
|
+
revision: session.revision,
|
|
635
|
+
diagnostics: diagnosticsFromError
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
404
639
|
};
|
|
405
640
|
//#endregion
|
|
406
641
|
//#region src/services/cache-service.ts
|
|
@@ -431,7 +666,7 @@ var CacheService = class {
|
|
|
431
666
|
const entry = this.context.cacheBySession.get(input.sessionId);
|
|
432
667
|
return success("read", startedAt, {
|
|
433
668
|
cacheRevision: entry?.revision ?? session.revision,
|
|
434
|
-
entries: entry ? [
|
|
669
|
+
entries: entry ? [structuredClone(entry)] : []
|
|
435
670
|
}, {
|
|
436
671
|
sessionId: session.sessionId,
|
|
437
672
|
revision: session.revision
|
|
@@ -502,7 +737,7 @@ var EventService = class {
|
|
|
502
737
|
const filtered = events.filter((event) => event.sequence > after);
|
|
503
738
|
const limit = input.limit === void 0 ? filtered.length : Math.max(0, input.limit);
|
|
504
739
|
return success("runtime", startedAt, {
|
|
505
|
-
events: filtered.slice(0, limit).map(
|
|
740
|
+
events: filtered.slice(0, limit).map((event) => structuredClone(event)),
|
|
506
741
|
oldestSequence,
|
|
507
742
|
currentSequence,
|
|
508
743
|
cursorExpired: false
|
|
@@ -577,7 +812,7 @@ var JobService = class {
|
|
|
577
812
|
jobId
|
|
578
813
|
});
|
|
579
814
|
this.scheduleRefreshJob(session.sessionId, jobId);
|
|
580
|
-
return success("runtime", startedAt,
|
|
815
|
+
return success("runtime", startedAt, structuredClone(job), {
|
|
581
816
|
sessionId: session.sessionId,
|
|
582
817
|
revision: session.revision
|
|
583
818
|
});
|
|
@@ -596,7 +831,7 @@ var JobService = class {
|
|
|
596
831
|
sessionId: session.sessionId,
|
|
597
832
|
revision: session.revision
|
|
598
833
|
});
|
|
599
|
-
return success("runtime", startedAt,
|
|
834
|
+
return success("runtime", startedAt, structuredClone(job), {
|
|
600
835
|
sessionId: session.sessionId,
|
|
601
836
|
revision: session.revision
|
|
602
837
|
});
|
|
@@ -611,7 +846,7 @@ var JobService = class {
|
|
|
611
846
|
else if (input.status === "terminal") jobs = jobs.filter((job) => isTerminal(job.status));
|
|
612
847
|
else jobs = jobs.filter((job) => job.status === input.status);
|
|
613
848
|
if (input.limit !== void 0) jobs = jobs.slice(0, Math.max(0, input.limit));
|
|
614
|
-
return success("runtime", startedAt, { jobs: jobs.map(
|
|
849
|
+
return success("runtime", startedAt, { jobs: jobs.map((job) => structuredClone(job)) }, {
|
|
615
850
|
sessionId: session.sessionId,
|
|
616
851
|
revision: session.revision
|
|
617
852
|
});
|
|
@@ -648,7 +883,8 @@ var JobService = class {
|
|
|
648
883
|
revision: session.revision,
|
|
649
884
|
jobId: job.jobId
|
|
650
885
|
});
|
|
651
|
-
|
|
886
|
+
const cancelled = this.cancelRefreshJob(session.sessionId, job);
|
|
887
|
+
return success("runtime", startedAt, structuredClone(cancelled), {
|
|
652
888
|
sessionId: session.sessionId,
|
|
653
889
|
revision: session.revision
|
|
654
890
|
});
|
|
@@ -795,7 +1031,7 @@ var ReadService = class {
|
|
|
795
1031
|
this.context = context;
|
|
796
1032
|
}
|
|
797
1033
|
getCapabilities() {
|
|
798
|
-
return success("read", Date.now(),
|
|
1034
|
+
return success("read", Date.now(), structuredClone(this.context.capabilities));
|
|
799
1035
|
}
|
|
800
1036
|
getSession(input) {
|
|
801
1037
|
const startedAt = Date.now();
|
|
@@ -859,6 +1095,75 @@ function createProjectReaderFileSystem(fileSystem) {
|
|
|
859
1095
|
}
|
|
860
1096
|
};
|
|
861
1097
|
}
|
|
1098
|
+
function createCaptureFileSystem(files, directories) {
|
|
1099
|
+
const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
1100
|
+
return {
|
|
1101
|
+
async readFile(filePath) {
|
|
1102
|
+
const value = files.get(normalize(filePath));
|
|
1103
|
+
if (typeof value !== "string") throw new Error(`Captured text file was not found: ${filePath}`);
|
|
1104
|
+
return value;
|
|
1105
|
+
},
|
|
1106
|
+
async readFileRaw(filePath) {
|
|
1107
|
+
const value = files.get(normalize(filePath));
|
|
1108
|
+
if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
|
|
1109
|
+
return value.slice();
|
|
1110
|
+
},
|
|
1111
|
+
async writeFile(filePath, content) {
|
|
1112
|
+
files.set(normalize(filePath), content);
|
|
1113
|
+
},
|
|
1114
|
+
async writeFileRaw(filePath, data) {
|
|
1115
|
+
files.set(normalize(filePath), data.slice());
|
|
1116
|
+
},
|
|
1117
|
+
async mkdir(dirPath) {
|
|
1118
|
+
directories.add(normalize(dirPath));
|
|
1119
|
+
},
|
|
1120
|
+
async readdir() {
|
|
1121
|
+
return [];
|
|
1122
|
+
},
|
|
1123
|
+
async exists(filePath) {
|
|
1124
|
+
return files.has(normalize(filePath));
|
|
1125
|
+
},
|
|
1126
|
+
join(...paths) {
|
|
1127
|
+
return normalize(paths.filter(Boolean).join("/"));
|
|
1128
|
+
},
|
|
1129
|
+
dirname(filePath) {
|
|
1130
|
+
const normalized = normalize(filePath);
|
|
1131
|
+
const separator = normalized.lastIndexOf("/");
|
|
1132
|
+
return separator < 0 ? "" : normalized.slice(0, separator);
|
|
1133
|
+
},
|
|
1134
|
+
async unlink(filePath) {
|
|
1135
|
+
files.delete(normalize(filePath));
|
|
1136
|
+
}
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function capturedFilesEqual(left, right) {
|
|
1140
|
+
if (left.size !== right.size) return false;
|
|
1141
|
+
for (const [filePath, leftValue] of left) {
|
|
1142
|
+
const rightValue = right.get(filePath);
|
|
1143
|
+
if (typeof leftValue === "string") {
|
|
1144
|
+
if (leftValue !== rightValue) return false;
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
|
|
1148
|
+
for (let index = 0; index < leftValue.length; index += 1) if (leftValue[index] !== rightValue[index]) return false;
|
|
1149
|
+
}
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
function capturedDirectoriesEqual(left, right) {
|
|
1153
|
+
return left.size === right.size && [...left].every((directory) => right.has(directory));
|
|
1154
|
+
}
|
|
1155
|
+
async function hasFullUamFidelity(document, project) {
|
|
1156
|
+
const sourceFiles = /* @__PURE__ */ new Map();
|
|
1157
|
+
const materializedFiles = /* @__PURE__ */ new Map();
|
|
1158
|
+
const sourceDirectories = /* @__PURE__ */ new Set();
|
|
1159
|
+
const materializedDirectories = /* @__PURE__ */ new Set();
|
|
1160
|
+
try {
|
|
1161
|
+
await Promise.all([new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(sourceFiles, sourceDirectories)).write(document, "Project.fairy"), new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(materializedFiles, materializedDirectories)).write((0, _openfairygui_core_uam.materializeUamProject)(project), "Project.fairy")]);
|
|
1162
|
+
} catch {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
return capturedFilesEqual(sourceFiles, materializedFiles) && capturedDirectoriesEqual(sourceDirectories, materializedDirectories);
|
|
1166
|
+
}
|
|
862
1167
|
var RuntimeService = class {
|
|
863
1168
|
constructor(context, cacheService, eventService, jobService) {
|
|
864
1169
|
this.context = context;
|
|
@@ -893,7 +1198,8 @@ var RuntimeService = class {
|
|
|
893
1198
|
canonicalPathKey
|
|
894
1199
|
}));
|
|
895
1200
|
await advisoryLock.close();
|
|
896
|
-
const
|
|
1201
|
+
const document = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath, { hydrateResourceBytes: true });
|
|
1202
|
+
const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(document);
|
|
897
1203
|
const sessionId = randomId();
|
|
898
1204
|
const session = {
|
|
899
1205
|
sessionId,
|
|
@@ -901,9 +1207,13 @@ var RuntimeService = class {
|
|
|
901
1207
|
canonicalProjectPath,
|
|
902
1208
|
canonicalPathKey,
|
|
903
1209
|
lockFilePath,
|
|
1210
|
+
fileSystem,
|
|
904
1211
|
project,
|
|
1212
|
+
uamFidelity: await hasFullUamFidelity(document, project) ? "full" : "unsupported",
|
|
905
1213
|
revision: 0,
|
|
906
1214
|
lastSavedRevision: 0,
|
|
1215
|
+
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
1216
|
+
pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
|
|
907
1217
|
dirty: false,
|
|
908
1218
|
lockHeld: true,
|
|
909
1219
|
closed: false
|
|
@@ -939,8 +1249,10 @@ var RuntimeService = class {
|
|
|
939
1249
|
openProjectSession(input) {
|
|
940
1250
|
const startedAt = Date.now();
|
|
941
1251
|
const sessionId = input.sessionId ?? randomId();
|
|
942
|
-
const
|
|
943
|
-
const
|
|
1252
|
+
const storage = input.storage;
|
|
1253
|
+
const memoryProjectPath = `memory://${sessionId}`;
|
|
1254
|
+
const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
|
|
1255
|
+
const canonicalPathKey = storage?.canonicalPathKey ?? input.canonicalPathKey ?? (storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
|
|
944
1256
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
945
1257
|
if (existingSessionId) return failure("runtime", startedAt, {
|
|
946
1258
|
code: "lock_conflict",
|
|
@@ -951,13 +1263,17 @@ var RuntimeService = class {
|
|
|
951
1263
|
});
|
|
952
1264
|
const session = {
|
|
953
1265
|
sessionId,
|
|
954
|
-
fairyPath: canonicalProjectPath,
|
|
1266
|
+
fairyPath: storage?.fairyPath ?? canonicalProjectPath,
|
|
955
1267
|
canonicalProjectPath,
|
|
956
1268
|
canonicalPathKey,
|
|
957
1269
|
lockFilePath: "",
|
|
1270
|
+
fileSystem: storage?.fileSystem,
|
|
958
1271
|
project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
|
|
1272
|
+
uamFidelity: "full",
|
|
959
1273
|
revision: 0,
|
|
960
1274
|
lastSavedRevision: 0,
|
|
1275
|
+
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
1276
|
+
pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
|
|
961
1277
|
dirty: false,
|
|
962
1278
|
lockHeld: false,
|
|
963
1279
|
closed: false
|
|
@@ -1010,7 +1326,25 @@ var RuntimeService = class {
|
|
|
1010
1326
|
}
|
|
1011
1327
|
};
|
|
1012
1328
|
//#endregion
|
|
1013
|
-
//#region src/
|
|
1329
|
+
//#region src/services/artifact-service.ts
|
|
1330
|
+
function createArtifactCapabilities() {
|
|
1331
|
+
const bridge = {
|
|
1332
|
+
available: false,
|
|
1333
|
+
requiredHost: "node",
|
|
1334
|
+
executionBoundary: "external-bridge",
|
|
1335
|
+
bridgeEntrypoint: "@openfairygui/backend/node",
|
|
1336
|
+
reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
|
|
1337
|
+
};
|
|
1338
|
+
return {
|
|
1339
|
+
publish: false,
|
|
1340
|
+
restore: false,
|
|
1341
|
+
status: "bridge-required",
|
|
1342
|
+
publishBridge: bridge,
|
|
1343
|
+
restoreBridge: bridge
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region src/runtime/capabilities.ts
|
|
1014
1348
|
const BACKEND_METHODS = [
|
|
1015
1349
|
"getCapabilities",
|
|
1016
1350
|
"openSession",
|
|
@@ -1018,6 +1352,7 @@ const BACKEND_METHODS = [
|
|
|
1018
1352
|
"getSession",
|
|
1019
1353
|
"applyTransaction",
|
|
1020
1354
|
"saveSession",
|
|
1355
|
+
"materializeSession",
|
|
1021
1356
|
"closeSession",
|
|
1022
1357
|
"getEvents",
|
|
1023
1358
|
"getJob",
|
|
@@ -1066,7 +1401,21 @@ function createCapabilities() {
|
|
|
1066
1401
|
adapters: {
|
|
1067
1402
|
fileSystem: {
|
|
1068
1403
|
injected: true,
|
|
1069
|
-
requiredFor: [
|
|
1404
|
+
requiredFor: [
|
|
1405
|
+
"openSession",
|
|
1406
|
+
"saveSession",
|
|
1407
|
+
"materializeSession"
|
|
1408
|
+
]
|
|
1409
|
+
},
|
|
1410
|
+
projectStorage: {
|
|
1411
|
+
injected: true,
|
|
1412
|
+
browserSafe: true,
|
|
1413
|
+
requiredFor: [
|
|
1414
|
+
"openProjectSession.writeback",
|
|
1415
|
+
"saveSession",
|
|
1416
|
+
"materializeSession"
|
|
1417
|
+
],
|
|
1418
|
+
adapterFactory: "createBackendStorageFileSystem"
|
|
1070
1419
|
},
|
|
1071
1420
|
host: {
|
|
1072
1421
|
injected: true,
|
|
@@ -1115,6 +1464,8 @@ function createCapabilities() {
|
|
|
1115
1464
|
}
|
|
1116
1465
|
};
|
|
1117
1466
|
}
|
|
1467
|
+
//#endregion
|
|
1468
|
+
//#region src/runtime.ts
|
|
1118
1469
|
var BackendRuntime = class {
|
|
1119
1470
|
fileSystem;
|
|
1120
1471
|
capabilities;
|
|
@@ -1173,6 +1524,9 @@ var BackendRuntime = class {
|
|
|
1173
1524
|
async saveSession(input) {
|
|
1174
1525
|
return this.authoringService.saveSession(input);
|
|
1175
1526
|
}
|
|
1527
|
+
async materializeSession(input) {
|
|
1528
|
+
return this.authoringService.materializeSession(input);
|
|
1529
|
+
}
|
|
1176
1530
|
async closeSession(input) {
|
|
1177
1531
|
return this.runtimeService.closeSession(input);
|
|
1178
1532
|
}
|