@openfairygui/backend 0.2.0-alpha.7 → 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 CHANGED
@@ -85,8 +85,15 @@ const opened = runtime.openProjectSession({
85
85
  });
86
86
  if (!opened.ok) throw new Error(opened.error.message);
87
87
 
88
- const saved = await runtime.saveSession({ sessionId: opened.data.sessionId });
89
- if (!saved.ok) throw new Error(saved.error.message);
88
+ const bootstrapped = await runtime.materializeSession({
89
+ sessionId: opened.data.sessionId,
90
+ expectedRevision: opened.data.revision,
91
+ mode: 'fullProject',
92
+ reason: 'workspace_bootstrap',
93
+ });
94
+ if (!bootstrapped.ok) throw new Error(bootstrapped.error.message);
95
+
96
+ console.log(bootstrapped.data.writtenPaths);
90
97
  ```
91
98
 
92
99
  Node file-backed session:
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_runtime = require("./runtime-YK3ACpuQ.cjs");
2
+ const require_runtime = require("./runtime-GKzsXJdO.cjs");
3
3
  //#region src/storage.ts
4
4
  var StorageFileStat = class {
5
5
  constructor(kind) {
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as BackendStage, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobProgress, D as BackendResult, E as BackendProjectSessionStorage, F as GetCacheSnapshotInput, G as SessionNotFoundError, H as RefreshCacheInput, I as GetEventsInput, J as BACKEND_COMPATIBILITY_POLICY, K as SessionStaleWriteError, L as GetEventsSnapshot, M as CacheRefreshFailedError, N as CancelJobInput, O as BackendRuntime, P as EventCursorInvalidError, Q as BackendResponseMeta, R as GetJobInput, S as BackendJobNotFoundError, T as BackendJobStatus, U as SavePartialFailureError, V as OpenProjectSessionInput, W as SaveSessionInput, X as BackendDiagnostic, Y as BACKEND_CONTRACT_VERSION, Z as BackendMessage, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, o as BackendCapabilities, p as BackendFileHandle, q as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BackendArtifactBridgeCapability, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-DziTGugy.cjs";
1
+ import { $ as BACKEND_CONTRACT_VERSION, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobProgress, D as BackendResult, E as BackendProjectSessionStorage, F as GetCacheSnapshotInput, G as OpenProjectSessionInput, H as MaterializeSessionSnapshot, I as GetEventsInput, J as SaveSessionInput, K as RefreshCacheInput, L as GetEventsSnapshot, M as CacheRefreshFailedError, N as CancelJobInput, O as BackendRuntime, P as EventCursorInvalidError, Q as BACKEND_COMPATIBILITY_POLICY, R as GetJobInput, S as BackendJobNotFoundError, T as BackendJobStatus, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as BACKEND_CAPABILITY_SCHEMA_VERSION, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, et as BackendDiagnostic, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, nt as BackendResponseMeta, o as BackendCapabilities, p as BackendFileHandle, q as SavePartialFailureError, r as BackendArtifactBridgeCapability, rt as BackendStage, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, tt as BackendMessage, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-Jec6FcF5.cjs";
2
2
  import { FileSystem } from "@openfairygui/core/project-io";
3
3
 
4
4
  //#region src/storage.d.ts
@@ -30,4 +30,4 @@ interface BackendAsyncStorageAdapter {
30
30
  type BackendStorageFileSystem = BackendFileSystem & FileSystem;
31
31
  declare function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapter): BackendStorageFileSystem;
32
32
  //#endregion
33
- export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileHandle, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type InProcessLockConflictError, type ListJobsInput, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, createBackendStorageFileSystem };
33
+ export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileHandle, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, createBackendStorageFileSystem };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as BackendStage, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobProgress, D as BackendResult, E as BackendProjectSessionStorage, F as GetCacheSnapshotInput, G as SessionNotFoundError, H as RefreshCacheInput, I as GetEventsInput, J as BACKEND_COMPATIBILITY_POLICY, K as SessionStaleWriteError, L as GetEventsSnapshot, M as CacheRefreshFailedError, N as CancelJobInput, O as BackendRuntime, P as EventCursorInvalidError, Q as BackendResponseMeta, R as GetJobInput, S as BackendJobNotFoundError, T as BackendJobStatus, U as SavePartialFailureError, V as OpenProjectSessionInput, W as SaveSessionInput, X as BackendDiagnostic, Y as BACKEND_CONTRACT_VERSION, Z as BackendMessage, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, o as BackendCapabilities, p as BackendFileHandle, q as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BackendArtifactBridgeCapability, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-Bydxr_SN.mjs";
1
+ import { $ as BACKEND_CONTRACT_VERSION, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobProgress, D as BackendResult, E as BackendProjectSessionStorage, F as GetCacheSnapshotInput, G as OpenProjectSessionInput, H as MaterializeSessionSnapshot, I as GetEventsInput, J as SaveSessionInput, K as RefreshCacheInput, L as GetEventsSnapshot, M as CacheRefreshFailedError, N as CancelJobInput, O as BackendRuntime, P as EventCursorInvalidError, Q as BACKEND_COMPATIBILITY_POLICY, R as GetJobInput, S as BackendJobNotFoundError, T as BackendJobStatus, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as BACKEND_CAPABILITY_SCHEMA_VERSION, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, et as BackendDiagnostic, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, nt as BackendResponseMeta, o as BackendCapabilities, p as BackendFileHandle, q as SavePartialFailureError, r as BackendArtifactBridgeCapability, rt as BackendStage, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, tt as BackendMessage, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-GyNVxAQ0.mjs";
2
2
  import { FileSystem } from "@openfairygui/core/project-io";
3
3
 
4
4
  //#region src/storage.d.ts
@@ -30,4 +30,4 @@ interface BackendAsyncStorageAdapter {
30
30
  type BackendStorageFileSystem = BackendFileSystem & FileSystem;
31
31
  declare function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapter): BackendStorageFileSystem;
32
32
  //#endregion
33
- export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileHandle, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type InProcessLockConflictError, type ListJobsInput, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, createBackendStorageFileSystem };
33
+ export { type AdvisoryLockConflictError, type ApplySessionTransactionInput, BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_COMPATIBILITY_POLICY, BACKEND_CONTRACT_VERSION, type BackendArtifactBridgeCapability, type BackendAsyncStorageAdapter, type BackendCacheEntry, type BackendCacheSnapshot, type BackendCapabilities, type BackendCapabilityManifest, type BackendCapabilityUnavailableError, type BackendDiagnostic, type BackendError, type BackendEvent, type BackendEventKind, type BackendFailure, type BackendFileHandle, type BackendFileStat, type BackendFileSystem, type BackendHostAdapter, type BackendJobErrors, type BackendJobKind, type BackendJobListSnapshot, type BackendJobListStatusFilter, type BackendJobNotCancellableError, type BackendJobNotFoundError, type BackendJobProgress, type BackendJobSnapshot, type BackendJobStatus, type BackendMessage, type BackendProjectSessionStorage, type BackendResponseMeta, type BackendResult, BackendRuntime, type BackendRuntimeOptions, type BackendSessionSnapshot, type BackendStage, type BackendStorageFileSystem, type BackendStorageStatLike, type BackendSuccess, type CacheRefreshFailedError, type CancelJobInput, type EventCursorInvalidError, type GetCacheSnapshotInput, type GetEventsInput, type GetEventsSnapshot, type GetJobInput, type InProcessLockConflictError, type ListJobsInput, type MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, type OpenProjectSessionInput, type RefreshCacheInput, type SavePartialFailureError, type SaveSessionInput, type SessionNotFoundError, type SessionStaleWriteError, createBackendStorageFileSystem };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { i as BACKEND_CONTRACT_VERSION, n as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BACKEND_COMPATIBILITY_POLICY, t as BackendRuntime } from "./runtime-Cn0O-wZt.mjs";
1
+ import { i as BACKEND_CONTRACT_VERSION, n as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BACKEND_COMPATIBILITY_POLICY, t as BackendRuntime } from "./runtime-DFatY9W0.mjs";
2
2
  //#region src/storage.ts
3
3
  var StorageFileStat = class {
4
4
  constructor(kind) {
package/dist/node.cjs CHANGED
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- const require_runtime = require("./runtime-YK3ACpuQ.cjs");
24
+ const require_runtime = require("./runtime-GKzsXJdO.cjs");
25
25
  let node_fs_promises = require("node:fs/promises");
26
26
  node_fs_promises = __toESM(node_fs_promises);
27
27
  let node_path = require("node:path");
package/dist/node.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-DziTGugy.cjs";
1
+ import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-Jec6FcF5.cjs";
2
2
 
3
3
  //#region src/node.d.ts
4
4
  declare function createNodeBackendFileSystem(): BackendFileSystem;
package/dist/node.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-Bydxr_SN.mjs";
1
+ import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-GyNVxAQ0.mjs";
2
2
 
3
3
  //#region src/node.d.ts
4
4
  declare function createNodeBackendFileSystem(): BackendFileSystem;
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BackendRuntime } from "./runtime-Cn0O-wZt.mjs";
1
+ import { t as BackendRuntime } from "./runtime-DFatY9W0.mjs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  //#region src/node.ts
@@ -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,16 +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
380
  const fileSystem = input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
335
- if (!fileSystem) return failure("authoring", startedAt, {
336
- code: "capability_unavailable",
337
- message: "saveSession requires an injected BackendFileSystem adapter.",
338
- capability: "fileSystem",
339
- requiredAdapter: "BackendFileSystem"
340
- }, toSessionSnapshot(session, this.context.capabilities), {
381
+ if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
341
382
  sessionId: session.sessionId,
342
383
  revision: session.revision
343
384
  });
@@ -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
@@ -1029,6 +1218,7 @@ const BACKEND_METHODS = [
1029
1218
  "getSession",
1030
1219
  "applyTransaction",
1031
1220
  "saveSession",
1221
+ "materializeSession",
1032
1222
  "closeSession",
1033
1223
  "getEvents",
1034
1224
  "getJob",
@@ -1077,12 +1267,20 @@ function createCapabilities() {
1077
1267
  adapters: {
1078
1268
  fileSystem: {
1079
1269
  injected: true,
1080
- requiredFor: ["openSession", "saveSession"]
1270
+ requiredFor: [
1271
+ "openSession",
1272
+ "saveSession",
1273
+ "materializeSession"
1274
+ ]
1081
1275
  },
1082
1276
  projectStorage: {
1083
1277
  injected: true,
1084
1278
  browserSafe: true,
1085
- requiredFor: ["openProjectSession.writeback", "saveSession"],
1279
+ requiredFor: [
1280
+ "openProjectSession.writeback",
1281
+ "saveSession",
1282
+ "materializeSession"
1283
+ ],
1086
1284
  adapterFactory: "createBackendStorageFileSystem"
1087
1285
  },
1088
1286
  host: {
@@ -1190,6 +1388,9 @@ var BackendRuntime = class {
1190
1388
  async saveSession(input) {
1191
1389
  return this.authoringService.saveSession(input);
1192
1390
  }
1391
+ async materializeSession(input) {
1392
+ return this.authoringService.materializeSession(input);
1393
+ }
1193
1394
  async closeSession(input) {
1194
1395
  return this.runtimeService.closeSession(input);
1195
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,16 +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
381
  const fileSystem = input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
336
- if (!fileSystem) return failure("authoring", startedAt, {
337
- code: "capability_unavailable",
338
- message: "saveSession requires an injected BackendFileSystem adapter.",
339
- capability: "fileSystem",
340
- requiredAdapter: "BackendFileSystem"
341
- }, toSessionSnapshot(session, this.context.capabilities), {
382
+ if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
342
383
  sessionId: session.sessionId,
343
384
  revision: session.revision
344
385
  });
@@ -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
@@ -1030,6 +1219,7 @@ const BACKEND_METHODS = [
1030
1219
  "getSession",
1031
1220
  "applyTransaction",
1032
1221
  "saveSession",
1222
+ "materializeSession",
1033
1223
  "closeSession",
1034
1224
  "getEvents",
1035
1225
  "getJob",
@@ -1078,12 +1268,20 @@ function createCapabilities() {
1078
1268
  adapters: {
1079
1269
  fileSystem: {
1080
1270
  injected: true,
1081
- requiredFor: ["openSession", "saveSession"]
1271
+ requiredFor: [
1272
+ "openSession",
1273
+ "saveSession",
1274
+ "materializeSession"
1275
+ ]
1082
1276
  },
1083
1277
  projectStorage: {
1084
1278
  injected: true,
1085
1279
  browserSafe: true,
1086
- requiredFor: ["openProjectSession.writeback", "saveSession"],
1280
+ requiredFor: [
1281
+ "openProjectSession.writeback",
1282
+ "saveSession",
1283
+ "materializeSession"
1284
+ ],
1087
1285
  adapterFactory: "createBackendStorageFileSystem"
1088
1286
  },
1089
1287
  host: {
@@ -1191,6 +1389,9 @@ var BackendRuntime = class {
1191
1389
  async saveSession(input) {
1192
1390
  return this.authoringService.saveSession(input);
1193
1391
  }
1392
+ async materializeSession(input) {
1393
+ return this.authoringService.materializeSession(input);
1394
+ }
1194
1395
  async closeSession(input) {
1195
1396
  return this.runtimeService.closeSession(input);
1196
1397
  }