@openfairygui/backend 0.2.0-alpha.14 → 0.2.0-alpha.15
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 +3 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- 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-GyNVxAQ0.d.mts → runtime-B6UjcyIt.d.cts} +11 -4
- package/dist/{runtime-Jec6FcF5.d.cts → runtime-BihiQW-H.d.mts} +11 -4
- package/dist/{runtime-DqApG30C.cjs → runtime-C5N7GLsG.cjs} +109 -1
- package/dist/{runtime-B9z0n2yF.mjs → runtime-CxH7aT_v.mjs} +109 -1
- package/package.json +3 -3
- package/src/index.ts +17 -16
- package/src/runtime.ts +17 -2
- package/src/services/authoring-service.ts +150 -42
- package/src/services/context.ts +9 -8
- package/src/services/runtime-service.ts +101 -15
- package/src/services/session-utils.ts +5 -1
package/README.md
CHANGED
|
@@ -39,6 +39,9 @@ can clean up stale source files. The default Node filesystem/runtime lives under
|
|
|
39
39
|
File-backed `openSession` hydrates primary resource bytes so browser-safe transactions can rename/move
|
|
40
40
|
assets or add/replace/remove binary resources. `saveSession` writes replacement bytes before it removes
|
|
41
41
|
stale source files, preserving the prior file when a write fails.
|
|
42
|
+
It also compares the source project with a UAM round trip through `ProjectWriter`; sessions with
|
|
43
|
+
unrepresented persisted properties expose `uamFidelity: 'unsupported'`, and write attempts fail with
|
|
44
|
+
`uam_fidelity_unsupported`. Transactions, saves, and materialization are serialized per session.
|
|
42
45
|
|
|
43
46
|
## Relationship to other packages
|
|
44
47
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_runtime = require("./runtime-
|
|
2
|
+
const require_runtime = require("./runtime-C5N7GLsG.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
|
|
1
|
+
import { $ as BACKEND_COMPATIBILITY_POLICY, 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_CAPABILITY_SCHEMA_VERSION, 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 UamFidelityUnsupportedError, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, et as BACKEND_CONTRACT_VERSION, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, nt as BackendMessage, o as BackendCapabilities, p as BackendFileHandle, q as SavePartialFailureError, r as BackendArtifactBridgeCapability, rt as BackendResponseMeta, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, tt as BackendDiagnostic, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-B6UjcyIt.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 MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, 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, type UamFidelityUnsupportedError, createBackendStorageFileSystem };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BACKEND_COMPATIBILITY_POLICY, 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_CAPABILITY_SCHEMA_VERSION, 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 UamFidelityUnsupportedError, _ as BackendJobErrors, a as BackendCacheSnapshot, b as BackendJobListStatusFilter, c as BackendCapabilityUnavailableError, d as BackendEventKind, et as BACKEND_CONTRACT_VERSION, f as BackendFailure, g as BackendHostAdapter, h as BackendFileSystem, i as BackendCacheEntry, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendError, m as BackendFileStat, n as ApplySessionTransactionInput, nt as BackendMessage, o as BackendCapabilities, p as BackendFileHandle, q as SavePartialFailureError, r as BackendArtifactBridgeCapability, rt as BackendResponseMeta, s as BackendCapabilityManifest, t as AdvisoryLockConflictError, tt as BackendDiagnostic, u as BackendEvent, v as BackendJobKind, w as BackendJobSnapshot, x as BackendJobNotCancellableError, y as BackendJobListSnapshot, z as InProcessLockConflictError } from "./runtime-BihiQW-H.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 MaterializeSessionInput, type MaterializeSessionSnapshot, type MaterializeValidationFailedError, type MaterializeWriteFailedError, 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, type UamFidelityUnsupportedError, 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-
|
|
1
|
+
import { i as BACKEND_CONTRACT_VERSION, n as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BACKEND_COMPATIBILITY_POLICY, t as BackendRuntime } from "./runtime-CxH7aT_v.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-
|
|
24
|
+
const require_runtime = require("./runtime-C5N7GLsG.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-
|
|
1
|
+
import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-B6UjcyIt.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-
|
|
1
|
+
import { O as BackendRuntime, g as BackendHostAdapter, h as BackendFileSystem, k as BackendRuntimeOptions, m as BackendFileStat, p as BackendFileHandle } from "./runtime-BihiQW-H.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/node.d.ts
|
|
4
4
|
declare function createNodeBackendFileSystem(): BackendFileSystem;
|
package/dist/node.mjs
CHANGED
|
@@ -193,6 +193,7 @@ interface BackendSessionSnapshot {
|
|
|
193
193
|
revision: number;
|
|
194
194
|
lastSavedRevision: number;
|
|
195
195
|
dirty: boolean;
|
|
196
|
+
uamFidelity: 'full' | 'unsupported';
|
|
196
197
|
lockHeld: boolean;
|
|
197
198
|
capabilities: BackendCapabilities;
|
|
198
199
|
}
|
|
@@ -257,6 +258,12 @@ interface SavePartialFailureError {
|
|
|
257
258
|
failedPaths: string[];
|
|
258
259
|
diskMayBePartiallyUpdated: true;
|
|
259
260
|
}
|
|
261
|
+
interface UamFidelityUnsupportedError {
|
|
262
|
+
code: 'uam_fidelity_unsupported';
|
|
263
|
+
message: string;
|
|
264
|
+
sessionId: string;
|
|
265
|
+
canonicalPathKey: string;
|
|
266
|
+
}
|
|
260
267
|
interface MaterializeValidationFailedError {
|
|
261
268
|
code: 'materialize_validation_failed';
|
|
262
269
|
message: string;
|
|
@@ -409,7 +416,7 @@ interface RefreshCacheInput {
|
|
|
409
416
|
sessionId: string;
|
|
410
417
|
reason?: 'manual' | 'session_open' | 'after_save';
|
|
411
418
|
}
|
|
412
|
-
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
419
|
+
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
413
420
|
interface ApplySessionTransactionInput {
|
|
414
421
|
sessionId: string;
|
|
415
422
|
expectedRevision: number;
|
|
@@ -475,8 +482,8 @@ declare class BackendRuntime {
|
|
|
475
482
|
sessionId: string;
|
|
476
483
|
}): BackendResult<BackendSessionSnapshot, SessionNotFoundError>;
|
|
477
484
|
applyTransaction(input: ApplySessionTransactionInput): Promise<BackendResult<BackendSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError>>;
|
|
478
|
-
saveSession(input: SaveSessionInput): Promise<BackendResult<BackendSessionSnapshot | MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | SavePartialFailureError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
479
|
-
materializeSession(input: MaterializeSessionInput): Promise<BackendResult<MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
485
|
+
saveSession(input: SaveSessionInput): Promise<BackendResult<BackendSessionSnapshot | MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
486
|
+
materializeSession(input: MaterializeSessionInput): Promise<BackendResult<MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
480
487
|
closeSession(input: {
|
|
481
488
|
sessionId: string;
|
|
482
489
|
}): Promise<BackendResult<{
|
|
@@ -491,4 +498,4 @@ declare class BackendRuntime {
|
|
|
491
498
|
refreshCache(input: RefreshCacheInput): BackendResult<BackendJobSnapshot, SessionNotFoundError>;
|
|
492
499
|
}
|
|
493
500
|
//#endregion
|
|
494
|
-
export {
|
|
501
|
+
export { BACKEND_COMPATIBILITY_POLICY as $, BackendSessionSnapshot as A, ListJobsInput as B, BackendJobProgress as C, BackendResult as D, BackendProjectSessionStorage as E, GetCacheSnapshotInput as F, OpenProjectSessionInput as G, MaterializeSessionSnapshot as H, GetEventsInput as I, SaveSessionInput as J, RefreshCacheInput as K, GetEventsSnapshot as L, CacheRefreshFailedError as M, CancelJobInput as N, BackendRuntime as O, EventCursorInvalidError as P, BACKEND_CAPABILITY_SCHEMA_VERSION as Q, GetJobInput as R, BackendJobNotFoundError as S, BackendJobStatus as T, MaterializeValidationFailedError as U, MaterializeSessionInput as V, MaterializeWriteFailedError as W, SessionStaleWriteError as X, SessionNotFoundError as Y, UamFidelityUnsupportedError as Z, BackendJobErrors as _, BackendCacheSnapshot as a, BackendJobListStatusFilter as b, BackendCapabilityUnavailableError as c, BackendEventKind as d, BACKEND_CONTRACT_VERSION as et, BackendFailure as f, BackendHostAdapter as g, BackendFileSystem as h, BackendCacheEntry as i, BackendStage as it, BackendSuccess as j, BackendRuntimeOptions as k, BackendError as l, BackendFileStat as m, ApplySessionTransactionInput as n, BackendMessage as nt, BackendCapabilities as o, BackendFileHandle as p, SavePartialFailureError as q, BackendArtifactBridgeCapability as r, BackendResponseMeta as rt, BackendCapabilityManifest as s, AdvisoryLockConflictError as t, BackendDiagnostic as tt, BackendEvent as u, BackendJobKind as v, BackendJobSnapshot as w, BackendJobNotCancellableError as x, BackendJobListSnapshot as y, InProcessLockConflictError as z };
|
|
@@ -193,6 +193,7 @@ interface BackendSessionSnapshot {
|
|
|
193
193
|
revision: number;
|
|
194
194
|
lastSavedRevision: number;
|
|
195
195
|
dirty: boolean;
|
|
196
|
+
uamFidelity: 'full' | 'unsupported';
|
|
196
197
|
lockHeld: boolean;
|
|
197
198
|
capabilities: BackendCapabilities;
|
|
198
199
|
}
|
|
@@ -257,6 +258,12 @@ interface SavePartialFailureError {
|
|
|
257
258
|
failedPaths: string[];
|
|
258
259
|
diskMayBePartiallyUpdated: true;
|
|
259
260
|
}
|
|
261
|
+
interface UamFidelityUnsupportedError {
|
|
262
|
+
code: 'uam_fidelity_unsupported';
|
|
263
|
+
message: string;
|
|
264
|
+
sessionId: string;
|
|
265
|
+
canonicalPathKey: string;
|
|
266
|
+
}
|
|
260
267
|
interface MaterializeValidationFailedError {
|
|
261
268
|
code: 'materialize_validation_failed';
|
|
262
269
|
message: string;
|
|
@@ -409,7 +416,7 @@ interface RefreshCacheInput {
|
|
|
409
416
|
sessionId: string;
|
|
410
417
|
reason?: 'manual' | 'session_open' | 'after_save';
|
|
411
418
|
}
|
|
412
|
-
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
419
|
+
type BackendError = SessionNotFoundError | SessionStaleWriteError | InProcessLockConflictError | AdvisoryLockConflictError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | EventCursorInvalidError | BackendJobNotFoundError | BackendJobNotCancellableError | BackendJobCancelledError | CacheRefreshFailedError | BackendCapabilityUnavailableError | ApplyUamTransactionAppError;
|
|
413
420
|
interface ApplySessionTransactionInput {
|
|
414
421
|
sessionId: string;
|
|
415
422
|
expectedRevision: number;
|
|
@@ -475,8 +482,8 @@ declare class BackendRuntime {
|
|
|
475
482
|
sessionId: string;
|
|
476
483
|
}): BackendResult<BackendSessionSnapshot, SessionNotFoundError>;
|
|
477
484
|
applyTransaction(input: ApplySessionTransactionInput): Promise<BackendResult<BackendSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError>>;
|
|
478
|
-
saveSession(input: SaveSessionInput): Promise<BackendResult<BackendSessionSnapshot | MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | SavePartialFailureError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
479
|
-
materializeSession(input: MaterializeSessionInput): Promise<BackendResult<MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
485
|
+
saveSession(input: SaveSessionInput): Promise<BackendResult<BackendSessionSnapshot | MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | SavePartialFailureError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
486
|
+
materializeSession(input: MaterializeSessionInput): Promise<BackendResult<MaterializeSessionSnapshot, SessionNotFoundError | SessionStaleWriteError | UamFidelityUnsupportedError | MaterializeValidationFailedError | MaterializeWriteFailedError | PathPolicyViolationError | InProcessLockConflictError | BackendCapabilityUnavailableError>>;
|
|
480
487
|
closeSession(input: {
|
|
481
488
|
sessionId: string;
|
|
482
489
|
}): Promise<BackendResult<{
|
|
@@ -491,4 +498,4 @@ declare class BackendRuntime {
|
|
|
491
498
|
refreshCache(input: RefreshCacheInput): BackendResult<BackendJobSnapshot, SessionNotFoundError>;
|
|
492
499
|
}
|
|
493
500
|
//#endregion
|
|
494
|
-
export {
|
|
501
|
+
export { BACKEND_COMPATIBILITY_POLICY as $, BackendSessionSnapshot as A, ListJobsInput as B, BackendJobProgress as C, BackendResult as D, BackendProjectSessionStorage as E, GetCacheSnapshotInput as F, OpenProjectSessionInput as G, MaterializeSessionSnapshot as H, GetEventsInput as I, SaveSessionInput as J, RefreshCacheInput as K, GetEventsSnapshot as L, CacheRefreshFailedError as M, CancelJobInput as N, BackendRuntime as O, EventCursorInvalidError as P, BACKEND_CAPABILITY_SCHEMA_VERSION as Q, GetJobInput as R, BackendJobNotFoundError as S, BackendJobStatus as T, MaterializeValidationFailedError as U, MaterializeSessionInput as V, MaterializeWriteFailedError as W, SessionStaleWriteError as X, SessionNotFoundError as Y, UamFidelityUnsupportedError as Z, BackendJobErrors as _, BackendCacheSnapshot as a, BackendJobListStatusFilter as b, BackendCapabilityUnavailableError as c, BackendEventKind as d, BACKEND_CONTRACT_VERSION as et, BackendFailure as f, BackendHostAdapter as g, BackendFileSystem as h, BackendCacheEntry as i, BackendStage as it, BackendSuccess as j, BackendRuntimeOptions as k, BackendError as l, BackendFileStat as m, ApplySessionTransactionInput as n, BackendMessage as nt, BackendCapabilities as o, BackendFileHandle as p, SavePartialFailureError as q, BackendArtifactBridgeCapability as r, BackendResponseMeta as rt, BackendCapabilityManifest as s, AdvisoryLockConflictError as t, BackendDiagnostic as tt, BackendEvent as u, BackendJobKind as v, BackendJobSnapshot as w, BackendJobNotCancellableError as x, BackendJobListSnapshot as y, InProcessLockConflictError as z };
|
|
@@ -180,6 +180,7 @@ function toSessionSnapshot(session, capabilities) {
|
|
|
180
180
|
revision: session.revision,
|
|
181
181
|
lastSavedRevision: session.lastSavedRevision,
|
|
182
182
|
dirty: session.dirty,
|
|
183
|
+
uamFidelity: session.uamFidelity,
|
|
183
184
|
lockHeld: session.lockHeld,
|
|
184
185
|
capabilities: cloneCapabilitiesSnapshot(capabilities)
|
|
185
186
|
};
|
|
@@ -309,6 +310,14 @@ function createCapabilityUnavailableError$1(message) {
|
|
|
309
310
|
requiredAdapter: "BackendFileSystem"
|
|
310
311
|
};
|
|
311
312
|
}
|
|
313
|
+
function createUamFidelityUnsupportedError(session) {
|
|
314
|
+
return {
|
|
315
|
+
code: "uam_fidelity_unsupported",
|
|
316
|
+
message: "The source project contains formal properties that the current UAM cannot preserve.",
|
|
317
|
+
sessionId: session.sessionId,
|
|
318
|
+
canonicalPathKey: session.canonicalPathKey
|
|
319
|
+
};
|
|
320
|
+
}
|
|
312
321
|
function validationDiagnostics(sessionProject) {
|
|
313
322
|
return (0, _openfairygui_core_uam.validateUamProject)(sessionProject).map((issue) => ({
|
|
314
323
|
code: "materialize_validation_failed",
|
|
@@ -340,12 +349,32 @@ function storageCanonicalTarget(input) {
|
|
|
340
349
|
};
|
|
341
350
|
}
|
|
342
351
|
var AuthoringService = class {
|
|
352
|
+
sessionOperations = /* @__PURE__ */ new Map();
|
|
343
353
|
constructor(context, cacheService, eventService) {
|
|
344
354
|
this.context = context;
|
|
345
355
|
this.cacheService = cacheService;
|
|
346
356
|
this.eventService = eventService;
|
|
347
357
|
}
|
|
358
|
+
async runSessionExclusive(sessionId, operation) {
|
|
359
|
+
const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
|
|
360
|
+
let release = () => void 0;
|
|
361
|
+
const current = new Promise((resolve) => {
|
|
362
|
+
release = resolve;
|
|
363
|
+
});
|
|
364
|
+
const tail = previous.then(() => current);
|
|
365
|
+
this.sessionOperations.set(sessionId, tail);
|
|
366
|
+
await previous;
|
|
367
|
+
try {
|
|
368
|
+
return await operation();
|
|
369
|
+
} finally {
|
|
370
|
+
release();
|
|
371
|
+
if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
348
374
|
async applyTransaction(input) {
|
|
375
|
+
return this.runSessionExclusive(input.sessionId, () => this.applyTransactionExclusive(input));
|
|
376
|
+
}
|
|
377
|
+
async applyTransactionExclusive(input) {
|
|
349
378
|
const startedAt = Date.now();
|
|
350
379
|
const session = this.context.sessions.get(input.sessionId);
|
|
351
380
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -412,6 +441,9 @@ var AuthoringService = class {
|
|
|
412
441
|
mode: "fullProject",
|
|
413
442
|
reason: "force_save"
|
|
414
443
|
});
|
|
444
|
+
return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
|
|
445
|
+
}
|
|
446
|
+
async saveSessionExclusive(input) {
|
|
415
447
|
const startedAt = Date.now();
|
|
416
448
|
const session = this.context.sessions.get(input.sessionId);
|
|
417
449
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -433,6 +465,10 @@ var AuthoringService = class {
|
|
|
433
465
|
sessionId: session.sessionId,
|
|
434
466
|
revision: session.revision
|
|
435
467
|
});
|
|
468
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
469
|
+
sessionId: session.sessionId,
|
|
470
|
+
revision: session.revision
|
|
471
|
+
});
|
|
436
472
|
const committedPaths = [];
|
|
437
473
|
const failedPaths = [];
|
|
438
474
|
this.eventService.emit({
|
|
@@ -491,6 +527,9 @@ var AuthoringService = class {
|
|
|
491
527
|
}
|
|
492
528
|
}
|
|
493
529
|
async materializeSession(input) {
|
|
530
|
+
return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
|
|
531
|
+
}
|
|
532
|
+
async materializeSessionExclusive(input) {
|
|
494
533
|
const startedAt = Date.now();
|
|
495
534
|
const session = this.context.sessions.get(input.sessionId);
|
|
496
535
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -523,6 +562,10 @@ var AuthoringService = class {
|
|
|
523
562
|
revision: session.revision
|
|
524
563
|
});
|
|
525
564
|
}
|
|
565
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
566
|
+
sessionId: session.sessionId,
|
|
567
|
+
revision: session.revision
|
|
568
|
+
});
|
|
526
569
|
const diagnostics = validationDiagnostics(session.project);
|
|
527
570
|
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
528
571
|
code: "materialize_validation_failed",
|
|
@@ -1101,6 +1144,68 @@ function createProjectReaderFileSystem(fileSystem) {
|
|
|
1101
1144
|
}
|
|
1102
1145
|
};
|
|
1103
1146
|
}
|
|
1147
|
+
function createCaptureFileSystem(files) {
|
|
1148
|
+
const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
1149
|
+
return {
|
|
1150
|
+
async readFile(filePath) {
|
|
1151
|
+
const value = files.get(normalize(filePath));
|
|
1152
|
+
if (typeof value !== "string") throw new Error(`Captured text file was not found: ${filePath}`);
|
|
1153
|
+
return value;
|
|
1154
|
+
},
|
|
1155
|
+
async readFileRaw(filePath) {
|
|
1156
|
+
const value = files.get(normalize(filePath));
|
|
1157
|
+
if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
|
|
1158
|
+
return value.slice();
|
|
1159
|
+
},
|
|
1160
|
+
async writeFile(filePath, content) {
|
|
1161
|
+
files.set(normalize(filePath), content);
|
|
1162
|
+
},
|
|
1163
|
+
async writeFileRaw(filePath, data) {
|
|
1164
|
+
files.set(normalize(filePath), data.slice());
|
|
1165
|
+
},
|
|
1166
|
+
async mkdir() {},
|
|
1167
|
+
async readdir() {
|
|
1168
|
+
return [];
|
|
1169
|
+
},
|
|
1170
|
+
async exists(filePath) {
|
|
1171
|
+
return files.has(normalize(filePath));
|
|
1172
|
+
},
|
|
1173
|
+
join(...paths) {
|
|
1174
|
+
return normalize(paths.filter(Boolean).join("/"));
|
|
1175
|
+
},
|
|
1176
|
+
dirname(filePath) {
|
|
1177
|
+
const normalized = normalize(filePath);
|
|
1178
|
+
const separator = normalized.lastIndexOf("/");
|
|
1179
|
+
return separator < 0 ? "" : normalized.slice(0, separator);
|
|
1180
|
+
},
|
|
1181
|
+
async unlink(filePath) {
|
|
1182
|
+
files.delete(normalize(filePath));
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function capturedFilesEqual(left, right) {
|
|
1187
|
+
if (left.size !== right.size) return false;
|
|
1188
|
+
for (const [filePath, leftValue] of left) {
|
|
1189
|
+
const rightValue = right.get(filePath);
|
|
1190
|
+
if (typeof leftValue === "string") {
|
|
1191
|
+
if (leftValue !== rightValue) return false;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
|
|
1195
|
+
for (let index = 0; index < leftValue.length; index += 1) if (leftValue[index] !== rightValue[index]) return false;
|
|
1196
|
+
}
|
|
1197
|
+
return true;
|
|
1198
|
+
}
|
|
1199
|
+
async function hasFullUamFidelity(document, project) {
|
|
1200
|
+
const sourceFiles = /* @__PURE__ */ new Map();
|
|
1201
|
+
const materializedFiles = /* @__PURE__ */ new Map();
|
|
1202
|
+
try {
|
|
1203
|
+
await Promise.all([new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, "Project.fairy"), new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(materializedFiles)).write((0, _openfairygui_core_uam.materializeUamProject)(project), "Project.fairy")]);
|
|
1204
|
+
} catch {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
return capturedFilesEqual(sourceFiles, materializedFiles);
|
|
1208
|
+
}
|
|
1104
1209
|
var RuntimeService = class {
|
|
1105
1210
|
constructor(context, cacheService, eventService, jobService) {
|
|
1106
1211
|
this.context = context;
|
|
@@ -1135,7 +1240,8 @@ var RuntimeService = class {
|
|
|
1135
1240
|
canonicalPathKey
|
|
1136
1241
|
}));
|
|
1137
1242
|
await advisoryLock.close();
|
|
1138
|
-
const
|
|
1243
|
+
const document = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath, { hydrateResourceBytes: true });
|
|
1244
|
+
const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(document);
|
|
1139
1245
|
const sessionId = randomId();
|
|
1140
1246
|
const session = {
|
|
1141
1247
|
sessionId,
|
|
@@ -1145,6 +1251,7 @@ var RuntimeService = class {
|
|
|
1145
1251
|
lockFilePath,
|
|
1146
1252
|
fileSystem,
|
|
1147
1253
|
project,
|
|
1254
|
+
uamFidelity: await hasFullUamFidelity(document, project) ? "full" : "unsupported",
|
|
1148
1255
|
revision: 0,
|
|
1149
1256
|
lastSavedRevision: 0,
|
|
1150
1257
|
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1203,6 +1310,7 @@ var RuntimeService = class {
|
|
|
1203
1310
|
lockFilePath: "",
|
|
1204
1311
|
fileSystem: storage?.fileSystem,
|
|
1205
1312
|
project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
|
|
1313
|
+
uamFidelity: "full",
|
|
1206
1314
|
revision: 0,
|
|
1207
1315
|
lastSavedRevision: 0,
|
|
1208
1316
|
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
@@ -179,6 +179,7 @@ function toSessionSnapshot(session, capabilities) {
|
|
|
179
179
|
revision: session.revision,
|
|
180
180
|
lastSavedRevision: session.lastSavedRevision,
|
|
181
181
|
dirty: session.dirty,
|
|
182
|
+
uamFidelity: session.uamFidelity,
|
|
182
183
|
lockHeld: session.lockHeld,
|
|
183
184
|
capabilities: cloneCapabilitiesSnapshot(capabilities)
|
|
184
185
|
};
|
|
@@ -308,6 +309,14 @@ function createCapabilityUnavailableError$1(message) {
|
|
|
308
309
|
requiredAdapter: "BackendFileSystem"
|
|
309
310
|
};
|
|
310
311
|
}
|
|
312
|
+
function createUamFidelityUnsupportedError(session) {
|
|
313
|
+
return {
|
|
314
|
+
code: "uam_fidelity_unsupported",
|
|
315
|
+
message: "The source project contains formal properties that the current UAM cannot preserve.",
|
|
316
|
+
sessionId: session.sessionId,
|
|
317
|
+
canonicalPathKey: session.canonicalPathKey
|
|
318
|
+
};
|
|
319
|
+
}
|
|
311
320
|
function validationDiagnostics(sessionProject) {
|
|
312
321
|
return validateUamProject(sessionProject).map((issue) => ({
|
|
313
322
|
code: "materialize_validation_failed",
|
|
@@ -339,12 +348,32 @@ function storageCanonicalTarget(input) {
|
|
|
339
348
|
};
|
|
340
349
|
}
|
|
341
350
|
var AuthoringService = class {
|
|
351
|
+
sessionOperations = /* @__PURE__ */ new Map();
|
|
342
352
|
constructor(context, cacheService, eventService) {
|
|
343
353
|
this.context = context;
|
|
344
354
|
this.cacheService = cacheService;
|
|
345
355
|
this.eventService = eventService;
|
|
346
356
|
}
|
|
357
|
+
async runSessionExclusive(sessionId, operation) {
|
|
358
|
+
const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
|
|
359
|
+
let release = () => void 0;
|
|
360
|
+
const current = new Promise((resolve) => {
|
|
361
|
+
release = resolve;
|
|
362
|
+
});
|
|
363
|
+
const tail = previous.then(() => current);
|
|
364
|
+
this.sessionOperations.set(sessionId, tail);
|
|
365
|
+
await previous;
|
|
366
|
+
try {
|
|
367
|
+
return await operation();
|
|
368
|
+
} finally {
|
|
369
|
+
release();
|
|
370
|
+
if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
347
373
|
async applyTransaction(input) {
|
|
374
|
+
return this.runSessionExclusive(input.sessionId, () => this.applyTransactionExclusive(input));
|
|
375
|
+
}
|
|
376
|
+
async applyTransactionExclusive(input) {
|
|
348
377
|
const startedAt = Date.now();
|
|
349
378
|
const session = this.context.sessions.get(input.sessionId);
|
|
350
379
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -411,6 +440,9 @@ var AuthoringService = class {
|
|
|
411
440
|
mode: "fullProject",
|
|
412
441
|
reason: "force_save"
|
|
413
442
|
});
|
|
443
|
+
return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
|
|
444
|
+
}
|
|
445
|
+
async saveSessionExclusive(input) {
|
|
414
446
|
const startedAt = Date.now();
|
|
415
447
|
const session = this.context.sessions.get(input.sessionId);
|
|
416
448
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -432,6 +464,10 @@ var AuthoringService = class {
|
|
|
432
464
|
sessionId: session.sessionId,
|
|
433
465
|
revision: session.revision
|
|
434
466
|
});
|
|
467
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
468
|
+
sessionId: session.sessionId,
|
|
469
|
+
revision: session.revision
|
|
470
|
+
});
|
|
435
471
|
const committedPaths = [];
|
|
436
472
|
const failedPaths = [];
|
|
437
473
|
this.eventService.emit({
|
|
@@ -490,6 +526,9 @@ var AuthoringService = class {
|
|
|
490
526
|
}
|
|
491
527
|
}
|
|
492
528
|
async materializeSession(input) {
|
|
529
|
+
return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
|
|
530
|
+
}
|
|
531
|
+
async materializeSessionExclusive(input) {
|
|
493
532
|
const startedAt = Date.now();
|
|
494
533
|
const session = this.context.sessions.get(input.sessionId);
|
|
495
534
|
if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
|
|
@@ -522,6 +561,10 @@ var AuthoringService = class {
|
|
|
522
561
|
revision: session.revision
|
|
523
562
|
});
|
|
524
563
|
}
|
|
564
|
+
if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
|
|
565
|
+
sessionId: session.sessionId,
|
|
566
|
+
revision: session.revision
|
|
567
|
+
});
|
|
525
568
|
const diagnostics = validationDiagnostics(session.project);
|
|
526
569
|
if (diagnostics.length > 0) return failure("authoring", startedAt, {
|
|
527
570
|
code: "materialize_validation_failed",
|
|
@@ -1100,6 +1143,68 @@ function createProjectReaderFileSystem(fileSystem) {
|
|
|
1100
1143
|
}
|
|
1101
1144
|
};
|
|
1102
1145
|
}
|
|
1146
|
+
function createCaptureFileSystem(files) {
|
|
1147
|
+
const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
1148
|
+
return {
|
|
1149
|
+
async readFile(filePath) {
|
|
1150
|
+
const value = files.get(normalize(filePath));
|
|
1151
|
+
if (typeof value !== "string") throw new Error(`Captured text file was not found: ${filePath}`);
|
|
1152
|
+
return value;
|
|
1153
|
+
},
|
|
1154
|
+
async readFileRaw(filePath) {
|
|
1155
|
+
const value = files.get(normalize(filePath));
|
|
1156
|
+
if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
|
|
1157
|
+
return value.slice();
|
|
1158
|
+
},
|
|
1159
|
+
async writeFile(filePath, content) {
|
|
1160
|
+
files.set(normalize(filePath), content);
|
|
1161
|
+
},
|
|
1162
|
+
async writeFileRaw(filePath, data) {
|
|
1163
|
+
files.set(normalize(filePath), data.slice());
|
|
1164
|
+
},
|
|
1165
|
+
async mkdir() {},
|
|
1166
|
+
async readdir() {
|
|
1167
|
+
return [];
|
|
1168
|
+
},
|
|
1169
|
+
async exists(filePath) {
|
|
1170
|
+
return files.has(normalize(filePath));
|
|
1171
|
+
},
|
|
1172
|
+
join(...paths) {
|
|
1173
|
+
return normalize(paths.filter(Boolean).join("/"));
|
|
1174
|
+
},
|
|
1175
|
+
dirname(filePath) {
|
|
1176
|
+
const normalized = normalize(filePath);
|
|
1177
|
+
const separator = normalized.lastIndexOf("/");
|
|
1178
|
+
return separator < 0 ? "" : normalized.slice(0, separator);
|
|
1179
|
+
},
|
|
1180
|
+
async unlink(filePath) {
|
|
1181
|
+
files.delete(normalize(filePath));
|
|
1182
|
+
}
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function capturedFilesEqual(left, right) {
|
|
1186
|
+
if (left.size !== right.size) return false;
|
|
1187
|
+
for (const [filePath, leftValue] of left) {
|
|
1188
|
+
const rightValue = right.get(filePath);
|
|
1189
|
+
if (typeof leftValue === "string") {
|
|
1190
|
+
if (leftValue !== rightValue) return false;
|
|
1191
|
+
continue;
|
|
1192
|
+
}
|
|
1193
|
+
if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
|
|
1194
|
+
for (let index = 0; index < leftValue.length; index += 1) if (leftValue[index] !== rightValue[index]) return false;
|
|
1195
|
+
}
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
async function hasFullUamFidelity(document, project) {
|
|
1199
|
+
const sourceFiles = /* @__PURE__ */ new Map();
|
|
1200
|
+
const materializedFiles = /* @__PURE__ */ new Map();
|
|
1201
|
+
try {
|
|
1202
|
+
await Promise.all([new ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, "Project.fairy"), new ProjectWriter(createCaptureFileSystem(materializedFiles)).write(materializeUamProject(project), "Project.fairy")]);
|
|
1203
|
+
} catch {
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
return capturedFilesEqual(sourceFiles, materializedFiles);
|
|
1207
|
+
}
|
|
1103
1208
|
var RuntimeService = class {
|
|
1104
1209
|
constructor(context, cacheService, eventService, jobService) {
|
|
1105
1210
|
this.context = context;
|
|
@@ -1134,7 +1239,8 @@ var RuntimeService = class {
|
|
|
1134
1239
|
canonicalPathKey
|
|
1135
1240
|
}));
|
|
1136
1241
|
await advisoryLock.close();
|
|
1137
|
-
const
|
|
1242
|
+
const document = await new ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath, { hydrateResourceBytes: true });
|
|
1243
|
+
const project = liftDocumentToUamProject(document);
|
|
1138
1244
|
const sessionId = randomId();
|
|
1139
1245
|
const session = {
|
|
1140
1246
|
sessionId,
|
|
@@ -1144,6 +1250,7 @@ var RuntimeService = class {
|
|
|
1144
1250
|
lockFilePath,
|
|
1145
1251
|
fileSystem,
|
|
1146
1252
|
project,
|
|
1253
|
+
uamFidelity: await hasFullUamFidelity(document, project) ? "full" : "unsupported",
|
|
1147
1254
|
revision: 0,
|
|
1148
1255
|
lastSavedRevision: 0,
|
|
1149
1256
|
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1202,6 +1309,7 @@ var RuntimeService = class {
|
|
|
1202
1309
|
lockFilePath: "",
|
|
1203
1310
|
fileSystem: storage?.fileSystem,
|
|
1204
1311
|
project: normalizeUamProject(input.project),
|
|
1312
|
+
uamFidelity: "full",
|
|
1205
1313
|
revision: 0,
|
|
1206
1314
|
lastSavedRevision: 0,
|
|
1207
1315
|
pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfairygui/backend",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.15",
|
|
4
4
|
"description": "FairyGUI Headless Authoring SDK — stateful backend runtime and session services.",
|
|
5
5
|
"author": "OpenFairyGUI Contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"runtime"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@openfairygui/
|
|
56
|
-
"@openfairygui/
|
|
55
|
+
"@openfairygui/core": "0.2.0-alpha.15",
|
|
56
|
+
"@openfairygui/functions": "0.2.0-alpha.15"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"ava": "^7.0.0",
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
2
|
+
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
3
|
+
BACKEND_COMPATIBILITY_POLICY,
|
|
4
|
+
BACKEND_CONTRACT_VERSION,
|
|
5
|
+
type BackendDiagnostic,
|
|
6
|
+
type BackendMessage,
|
|
7
|
+
type BackendResponseMeta,
|
|
8
|
+
type BackendStage,
|
|
9
|
+
} from './contracts.js';
|
|
10
|
+
export {
|
|
3
11
|
type AdvisoryLockConflictError,
|
|
4
12
|
type ApplySessionTransactionInput,
|
|
5
13
|
type BackendArtifactBridgeCapability,
|
|
6
14
|
type BackendCacheEntry,
|
|
7
15
|
type BackendCacheSnapshot,
|
|
16
|
+
type BackendCapabilities,
|
|
8
17
|
type BackendCapabilityManifest,
|
|
9
18
|
type BackendCapabilityUnavailableError,
|
|
10
|
-
type BackendCapabilities,
|
|
11
19
|
type BackendError,
|
|
12
20
|
type BackendEvent,
|
|
13
21
|
type BackendEventKind,
|
|
@@ -16,6 +24,7 @@ export {
|
|
|
16
24
|
type BackendFileStat,
|
|
17
25
|
type BackendFileSystem,
|
|
18
26
|
type BackendHostAdapter,
|
|
27
|
+
type BackendJobErrors,
|
|
19
28
|
type BackendJobKind,
|
|
20
29
|
type BackendJobListSnapshot,
|
|
21
30
|
type BackendJobListStatusFilter,
|
|
@@ -24,11 +33,12 @@ export {
|
|
|
24
33
|
type BackendJobProgress,
|
|
25
34
|
type BackendJobSnapshot,
|
|
26
35
|
type BackendJobStatus,
|
|
27
|
-
type
|
|
36
|
+
type BackendProjectSessionStorage,
|
|
28
37
|
type BackendResult,
|
|
38
|
+
BackendRuntime,
|
|
39
|
+
type BackendRuntimeOptions,
|
|
29
40
|
type BackendSessionSnapshot,
|
|
30
41
|
type BackendSuccess,
|
|
31
|
-
type BackendRuntimeOptions,
|
|
32
42
|
type CacheRefreshFailedError,
|
|
33
43
|
type CancelJobInput,
|
|
34
44
|
type EventCursorInvalidError,
|
|
@@ -43,25 +53,16 @@ export {
|
|
|
43
53
|
type MaterializeValidationFailedError,
|
|
44
54
|
type MaterializeWriteFailedError,
|
|
45
55
|
type OpenProjectSessionInput,
|
|
46
|
-
type BackendProjectSessionStorage,
|
|
47
56
|
type RefreshCacheInput,
|
|
48
|
-
type SaveSessionInput,
|
|
49
57
|
type SavePartialFailureError,
|
|
58
|
+
type SaveSessionInput,
|
|
50
59
|
type SessionNotFoundError,
|
|
51
60
|
type SessionStaleWriteError,
|
|
61
|
+
type UamFidelityUnsupportedError,
|
|
52
62
|
} from './runtime.js';
|
|
53
63
|
export {
|
|
54
|
-
createBackendStorageFileSystem,
|
|
55
64
|
type BackendAsyncStorageAdapter,
|
|
56
65
|
type BackendStorageFileSystem,
|
|
57
66
|
type BackendStorageStatLike,
|
|
67
|
+
createBackendStorageFileSystem,
|
|
58
68
|
} from './storage.js';
|
|
59
|
-
export {
|
|
60
|
-
type BackendDiagnostic,
|
|
61
|
-
type BackendMessage,
|
|
62
|
-
type BackendResponseMeta,
|
|
63
|
-
type BackendStage,
|
|
64
|
-
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
65
|
-
BACKEND_COMPATIBILITY_POLICY,
|
|
66
|
-
BACKEND_CONTRACT_VERSION,
|
|
67
|
-
} from './contracts.js';
|
package/src/runtime.ts
CHANGED
|
@@ -181,6 +181,7 @@ export interface BackendSessionSnapshot {
|
|
|
181
181
|
revision: number;
|
|
182
182
|
lastSavedRevision: number;
|
|
183
183
|
dirty: boolean;
|
|
184
|
+
uamFidelity: 'full' | 'unsupported';
|
|
184
185
|
lockHeld: boolean;
|
|
185
186
|
capabilities: BackendCapabilities;
|
|
186
187
|
}
|
|
@@ -255,6 +256,13 @@ export interface SavePartialFailureError {
|
|
|
255
256
|
diskMayBePartiallyUpdated: true;
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
export interface UamFidelityUnsupportedError {
|
|
260
|
+
code: 'uam_fidelity_unsupported';
|
|
261
|
+
message: string;
|
|
262
|
+
sessionId: string;
|
|
263
|
+
canonicalPathKey: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
258
266
|
export interface MaterializeValidationFailedError {
|
|
259
267
|
code: 'materialize_validation_failed';
|
|
260
268
|
message: string;
|
|
@@ -458,6 +466,7 @@ export type BackendError =
|
|
|
458
466
|
| InProcessLockConflictError
|
|
459
467
|
| AdvisoryLockConflictError
|
|
460
468
|
| SavePartialFailureError
|
|
469
|
+
| UamFidelityUnsupportedError
|
|
461
470
|
| MaterializeValidationFailedError
|
|
462
471
|
| MaterializeWriteFailedError
|
|
463
472
|
| PathPolicyViolationError
|
|
@@ -704,12 +713,15 @@ export class BackendRuntime {
|
|
|
704
713
|
return this.authoringService.applyTransaction(input);
|
|
705
714
|
}
|
|
706
715
|
|
|
707
|
-
public async saveSession(
|
|
716
|
+
public async saveSession(
|
|
717
|
+
input: SaveSessionInput,
|
|
718
|
+
): Promise<
|
|
708
719
|
BackendResult<
|
|
709
720
|
BackendSessionSnapshot | MaterializeSessionSnapshot,
|
|
710
721
|
| SessionNotFoundError
|
|
711
722
|
| SessionStaleWriteError
|
|
712
723
|
| SavePartialFailureError
|
|
724
|
+
| UamFidelityUnsupportedError
|
|
713
725
|
| MaterializeValidationFailedError
|
|
714
726
|
| MaterializeWriteFailedError
|
|
715
727
|
| PathPolicyViolationError
|
|
@@ -720,11 +732,14 @@ export class BackendRuntime {
|
|
|
720
732
|
return this.authoringService.saveSession(input);
|
|
721
733
|
}
|
|
722
734
|
|
|
723
|
-
public async materializeSession(
|
|
735
|
+
public async materializeSession(
|
|
736
|
+
input: MaterializeSessionInput,
|
|
737
|
+
): Promise<
|
|
724
738
|
BackendResult<
|
|
725
739
|
MaterializeSessionSnapshot,
|
|
726
740
|
| SessionNotFoundError
|
|
727
741
|
| SessionStaleWriteError
|
|
742
|
+
| UamFidelityUnsupportedError
|
|
728
743
|
| MaterializeValidationFailedError
|
|
729
744
|
| MaterializeWriteFailedError
|
|
730
745
|
| PathPolicyViolationError
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type FileSystem, type ProjectSourceFile, ProjectWriter } from '@openfairygui/core/project-io';
|
|
2
|
+
import {
|
|
3
|
+
commitUamProjectSourcePaths,
|
|
4
|
+
materializeUamProject,
|
|
5
|
+
type UamProject,
|
|
6
|
+
validateUamProject,
|
|
7
|
+
} from '@openfairygui/core/uam';
|
|
8
|
+
import { type ApplyUamTransactionAppError, applyUamTransactionApp } from '@openfairygui/functions/uam';
|
|
9
|
+
import type { BackendDiagnostic } from '../contracts.js';
|
|
10
|
+
import { normalizeComparablePath, type PathPolicyViolationError, validateSaveTarget } from '../path-policy.js';
|
|
7
11
|
import type {
|
|
8
12
|
ApplySessionTransactionInput,
|
|
9
13
|
BackendCapabilityUnavailableError,
|
|
@@ -15,13 +19,15 @@ import type {
|
|
|
15
19
|
MaterializeSessionSnapshot,
|
|
16
20
|
MaterializeValidationFailedError,
|
|
17
21
|
MaterializeWriteFailedError,
|
|
18
|
-
SaveSessionInput,
|
|
19
22
|
SavePartialFailureError,
|
|
23
|
+
SaveSessionInput,
|
|
20
24
|
SessionNotFoundError,
|
|
21
25
|
SessionStaleWriteError,
|
|
26
|
+
UamFidelityUnsupportedError,
|
|
22
27
|
} from '../runtime.js';
|
|
23
|
-
import type {
|
|
24
|
-
import {
|
|
28
|
+
import type { CacheService } from './cache-service.js';
|
|
29
|
+
import { type BackendContext, failure, success } from './context.js';
|
|
30
|
+
import type { EventService } from './event-service.js';
|
|
25
31
|
import { createSessionNotFoundError, createStaleWriteError, toSessionSnapshot } from './session-utils.js';
|
|
26
32
|
|
|
27
33
|
function createWriterFileSystem(
|
|
@@ -92,10 +98,12 @@ function projectAssetSourceFiles(project: UamProject): Map<string, ProjectSource
|
|
|
92
98
|
if (resource.kind === 'component') continue;
|
|
93
99
|
const fileName = resource.fileName ?? resource.file ?? '';
|
|
94
100
|
if (!fileName) continue;
|
|
95
|
-
result.set(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
result.set(`${pkg.id}/${resource.id}`, {
|
|
102
|
+
packageName: pkg.name,
|
|
103
|
+
branch: resource.branch,
|
|
104
|
+
path: resource.path,
|
|
105
|
+
fileName,
|
|
106
|
+
});
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
return result;
|
|
@@ -126,15 +134,15 @@ function toBackendDiagnostics(error: ApplyUamTransactionAppError): BackendDiagno
|
|
|
126
134
|
return error.diagnostics.length > 0
|
|
127
135
|
? error.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
128
136
|
: [
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
{
|
|
138
|
+
code: error.code,
|
|
139
|
+
message: error.message,
|
|
140
|
+
severity: 'error',
|
|
141
|
+
operationKind: error.operationKind,
|
|
142
|
+
opIndex: error.opIndex,
|
|
143
|
+
opId: error.opId,
|
|
144
|
+
},
|
|
145
|
+
];
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
function createCapabilityUnavailableError(message: string): BackendCapabilityUnavailableError {
|
|
@@ -146,6 +154,17 @@ function createCapabilityUnavailableError(message: string): BackendCapabilityUna
|
|
|
146
154
|
};
|
|
147
155
|
}
|
|
148
156
|
|
|
157
|
+
function createUamFidelityUnsupportedError(
|
|
158
|
+
session: Parameters<typeof toSessionSnapshot>[0],
|
|
159
|
+
): UamFidelityUnsupportedError {
|
|
160
|
+
return {
|
|
161
|
+
code: 'uam_fidelity_unsupported',
|
|
162
|
+
message: 'The source project contains formal properties that the current UAM cannot preserve.',
|
|
163
|
+
sessionId: session.sessionId,
|
|
164
|
+
canonicalPathKey: session.canonicalPathKey,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
149
168
|
function validationDiagnostics(sessionProject: Parameters<typeof validateUamProject>[0]): BackendDiagnostic[] {
|
|
150
169
|
const issues = validateUamProject(sessionProject);
|
|
151
170
|
return issues.map((issue) => ({
|
|
@@ -195,12 +214,31 @@ function storageCanonicalTarget(input: NonNullable<MaterializeSessionInput['stor
|
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
export class AuthoringService {
|
|
217
|
+
private readonly sessionOperations = new Map<string, Promise<void>>();
|
|
218
|
+
|
|
198
219
|
public constructor(
|
|
199
220
|
private readonly context: BackendContext,
|
|
200
221
|
private readonly cacheService: CacheService,
|
|
201
222
|
private readonly eventService: EventService,
|
|
202
223
|
) {}
|
|
203
224
|
|
|
225
|
+
private async runSessionExclusive<T>(sessionId: string, operation: () => Promise<T>): Promise<T> {
|
|
226
|
+
const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
|
|
227
|
+
let release = (): void => undefined;
|
|
228
|
+
const current = new Promise<void>((resolve) => {
|
|
229
|
+
release = resolve;
|
|
230
|
+
});
|
|
231
|
+
const tail = previous.then(() => current);
|
|
232
|
+
this.sessionOperations.set(sessionId, tail);
|
|
233
|
+
await previous;
|
|
234
|
+
try {
|
|
235
|
+
return await operation();
|
|
236
|
+
} finally {
|
|
237
|
+
release();
|
|
238
|
+
if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
204
242
|
public async applyTransaction(
|
|
205
243
|
input: ApplySessionTransactionInput,
|
|
206
244
|
): Promise<
|
|
@@ -208,6 +246,17 @@ export class AuthoringService {
|
|
|
208
246
|
BackendSessionSnapshot,
|
|
209
247
|
SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError
|
|
210
248
|
>
|
|
249
|
+
> {
|
|
250
|
+
return this.runSessionExclusive(input.sessionId, () => this.applyTransactionExclusive(input));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private async applyTransactionExclusive(
|
|
254
|
+
input: ApplySessionTransactionInput,
|
|
255
|
+
): Promise<
|
|
256
|
+
BackendResult<
|
|
257
|
+
BackendSessionSnapshot,
|
|
258
|
+
SessionNotFoundError | SessionStaleWriteError | ApplyUamTransactionAppError
|
|
259
|
+
>
|
|
211
260
|
> {
|
|
212
261
|
const startedAt = Date.now();
|
|
213
262
|
const session = this.context.sessions.get(input.sessionId);
|
|
@@ -284,12 +333,15 @@ export class AuthoringService {
|
|
|
284
333
|
});
|
|
285
334
|
}
|
|
286
335
|
|
|
287
|
-
public async saveSession(
|
|
336
|
+
public async saveSession(
|
|
337
|
+
input: SaveSessionInput,
|
|
338
|
+
): Promise<
|
|
288
339
|
BackendResult<
|
|
289
340
|
BackendSessionSnapshot | MaterializeSessionSnapshot,
|
|
290
341
|
| SessionNotFoundError
|
|
291
342
|
| SessionStaleWriteError
|
|
292
343
|
| SavePartialFailureError
|
|
344
|
+
| UamFidelityUnsupportedError
|
|
293
345
|
| MaterializeValidationFailedError
|
|
294
346
|
| MaterializeWriteFailedError
|
|
295
347
|
| PathPolicyViolationError
|
|
@@ -307,6 +359,25 @@ export class AuthoringService {
|
|
|
307
359
|
reason: 'force_save',
|
|
308
360
|
});
|
|
309
361
|
}
|
|
362
|
+
return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async saveSessionExclusive(
|
|
366
|
+
input: SaveSessionInput,
|
|
367
|
+
): Promise<
|
|
368
|
+
BackendResult<
|
|
369
|
+
BackendSessionSnapshot | MaterializeSessionSnapshot,
|
|
370
|
+
| SessionNotFoundError
|
|
371
|
+
| SessionStaleWriteError
|
|
372
|
+
| SavePartialFailureError
|
|
373
|
+
| UamFidelityUnsupportedError
|
|
374
|
+
| MaterializeValidationFailedError
|
|
375
|
+
| MaterializeWriteFailedError
|
|
376
|
+
| PathPolicyViolationError
|
|
377
|
+
| InProcessLockConflictError
|
|
378
|
+
| BackendCapabilityUnavailableError
|
|
379
|
+
>
|
|
380
|
+
> {
|
|
310
381
|
const startedAt = Date.now();
|
|
311
382
|
const session = this.context.sessions.get(input.sessionId);
|
|
312
383
|
if (!session || session.closed) {
|
|
@@ -317,9 +388,7 @@ export class AuthoringService {
|
|
|
317
388
|
return failure(
|
|
318
389
|
'authoring',
|
|
319
390
|
startedAt,
|
|
320
|
-
createCapabilityUnavailableError(
|
|
321
|
-
'saveSession requires an injected BackendFileSystem adapter.',
|
|
322
|
-
),
|
|
391
|
+
createCapabilityUnavailableError('saveSession requires an injected BackendFileSystem adapter.'),
|
|
323
392
|
toSessionSnapshot(session, this.context.capabilities),
|
|
324
393
|
{
|
|
325
394
|
sessionId: session.sessionId,
|
|
@@ -358,6 +427,18 @@ export class AuthoringService {
|
|
|
358
427
|
revision: session.revision,
|
|
359
428
|
});
|
|
360
429
|
}
|
|
430
|
+
if (session.uamFidelity === 'unsupported') {
|
|
431
|
+
return failure(
|
|
432
|
+
'authoring',
|
|
433
|
+
startedAt,
|
|
434
|
+
createUamFidelityUnsupportedError(session),
|
|
435
|
+
toSessionSnapshot(session, this.context.capabilities),
|
|
436
|
+
{
|
|
437
|
+
sessionId: session.sessionId,
|
|
438
|
+
revision: session.revision,
|
|
439
|
+
},
|
|
440
|
+
);
|
|
441
|
+
}
|
|
361
442
|
|
|
362
443
|
const committedPaths: string[] = [];
|
|
363
444
|
const failedPaths: string[] = [];
|
|
@@ -426,11 +507,32 @@ export class AuthoringService {
|
|
|
426
507
|
}
|
|
427
508
|
}
|
|
428
509
|
|
|
429
|
-
public async materializeSession(
|
|
510
|
+
public async materializeSession(
|
|
511
|
+
input: MaterializeSessionInput,
|
|
512
|
+
): Promise<
|
|
513
|
+
BackendResult<
|
|
514
|
+
MaterializeSessionSnapshot,
|
|
515
|
+
| SessionNotFoundError
|
|
516
|
+
| SessionStaleWriteError
|
|
517
|
+
| UamFidelityUnsupportedError
|
|
518
|
+
| MaterializeValidationFailedError
|
|
519
|
+
| MaterializeWriteFailedError
|
|
520
|
+
| PathPolicyViolationError
|
|
521
|
+
| InProcessLockConflictError
|
|
522
|
+
| BackendCapabilityUnavailableError
|
|
523
|
+
>
|
|
524
|
+
> {
|
|
525
|
+
return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
private async materializeSessionExclusive(
|
|
529
|
+
input: MaterializeSessionInput,
|
|
530
|
+
): Promise<
|
|
430
531
|
BackendResult<
|
|
431
532
|
MaterializeSessionSnapshot,
|
|
432
533
|
| SessionNotFoundError
|
|
433
534
|
| SessionStaleWriteError
|
|
535
|
+
| UamFidelityUnsupportedError
|
|
434
536
|
| MaterializeValidationFailedError
|
|
435
537
|
| MaterializeWriteFailedError
|
|
436
538
|
| PathPolicyViolationError
|
|
@@ -457,14 +559,13 @@ export class AuthoringService {
|
|
|
457
559
|
}
|
|
458
560
|
|
|
459
561
|
const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
|
|
460
|
-
const fileSystem =
|
|
562
|
+
const fileSystem =
|
|
563
|
+
storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
|
|
461
564
|
if (!fileSystem) {
|
|
462
565
|
return failure(
|
|
463
566
|
'authoring',
|
|
464
567
|
startedAt,
|
|
465
|
-
createCapabilityUnavailableError(
|
|
466
|
-
'materializeSession requires an injected BackendFileSystem adapter.',
|
|
467
|
-
),
|
|
568
|
+
createCapabilityUnavailableError('materializeSession requires an injected BackendFileSystem adapter.'),
|
|
468
569
|
toSessionSnapshot(session, this.context.capabilities),
|
|
469
570
|
{
|
|
470
571
|
sessionId: session.sessionId,
|
|
@@ -510,6 +611,19 @@ export class AuthoringService {
|
|
|
510
611
|
}
|
|
511
612
|
}
|
|
512
613
|
|
|
614
|
+
if (session.uamFidelity === 'unsupported') {
|
|
615
|
+
return failure(
|
|
616
|
+
'authoring',
|
|
617
|
+
startedAt,
|
|
618
|
+
createUamFidelityUnsupportedError(session),
|
|
619
|
+
toSessionSnapshot(session, this.context.capabilities),
|
|
620
|
+
{
|
|
621
|
+
sessionId: session.sessionId,
|
|
622
|
+
revision: session.revision,
|
|
623
|
+
},
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
513
627
|
const diagnostics = validationDiagnostics(session.project);
|
|
514
628
|
if (diagnostics.length > 0) {
|
|
515
629
|
const error: MaterializeValidationFailedError = {
|
|
@@ -520,17 +634,11 @@ export class AuthoringService {
|
|
|
520
634
|
issueCount: diagnostics.length,
|
|
521
635
|
diagnostics,
|
|
522
636
|
};
|
|
523
|
-
return failure(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
{
|
|
529
|
-
sessionId: session.sessionId,
|
|
530
|
-
revision: session.revision,
|
|
531
|
-
diagnostics,
|
|
532
|
-
},
|
|
533
|
-
);
|
|
637
|
+
return failure('authoring', startedAt, error, toSessionSnapshot(session, this.context.capabilities), {
|
|
638
|
+
sessionId: session.sessionId,
|
|
639
|
+
revision: session.revision,
|
|
640
|
+
diagnostics,
|
|
641
|
+
});
|
|
534
642
|
}
|
|
535
643
|
|
|
536
644
|
let document: ReturnType<typeof materializeUamProject>;
|
package/src/services/context.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
3
|
+
BACKEND_CONTRACT_VERSION,
|
|
4
|
+
type BackendDiagnostic,
|
|
5
|
+
type BackendMessage,
|
|
6
|
+
type BackendResponseMeta,
|
|
7
|
+
type BackendStage,
|
|
8
|
+
} from '../contracts.js';
|
|
1
9
|
import type {
|
|
2
10
|
BackendCacheEntry,
|
|
3
11
|
BackendCapabilities,
|
|
@@ -10,14 +18,6 @@ import type {
|
|
|
10
18
|
BackendSessionSnapshot,
|
|
11
19
|
BackendSuccess,
|
|
12
20
|
} from '../runtime.js';
|
|
13
|
-
import {
|
|
14
|
-
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
15
|
-
BACKEND_CONTRACT_VERSION,
|
|
16
|
-
type BackendDiagnostic,
|
|
17
|
-
type BackendMessage,
|
|
18
|
-
type BackendResponseMeta,
|
|
19
|
-
type BackendStage,
|
|
20
|
-
} from '../contracts.js';
|
|
21
21
|
|
|
22
22
|
export interface BackendSessionState {
|
|
23
23
|
sessionId: string;
|
|
@@ -27,6 +27,7 @@ export interface BackendSessionState {
|
|
|
27
27
|
lockFilePath: string;
|
|
28
28
|
fileSystem?: BackendFileSystem;
|
|
29
29
|
project: import('@openfairygui/core/uam').UamProject;
|
|
30
|
+
uamFidelity: 'full' | 'unsupported';
|
|
30
31
|
revision: number;
|
|
31
32
|
lastSavedRevision: number;
|
|
32
33
|
/** Source files deferred until a successful replacement project write. */
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import { ProjectReader,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type FileSystem, ProjectReader, ProjectWriter } from '@openfairygui/core/project-io';
|
|
2
|
+
import {
|
|
3
|
+
liftDocumentToUamProject,
|
|
4
|
+
materializeUamProject,
|
|
5
|
+
normalizeUamProject,
|
|
6
|
+
type UamProject,
|
|
7
|
+
} from '@openfairygui/core/uam';
|
|
8
|
+
import { normalizeComparablePath, resolveCanonicalProjectRoot } from '../path-policy.js';
|
|
7
9
|
import type {
|
|
8
10
|
AdvisoryLockConflictError,
|
|
11
|
+
BackendCapabilityUnavailableError,
|
|
9
12
|
BackendFileHandle,
|
|
10
13
|
BackendResult,
|
|
11
14
|
BackendSessionSnapshot,
|
|
12
|
-
BackendCapabilityUnavailableError,
|
|
13
15
|
InProcessLockConflictError,
|
|
14
16
|
OpenProjectSessionInput,
|
|
15
17
|
SessionNotFoundError,
|
|
16
18
|
} from '../runtime.js';
|
|
17
|
-
import {
|
|
19
|
+
import type { CacheService } from './cache-service.js';
|
|
20
|
+
import { type BackendContext, type BackendSessionState, failure, success } from './context.js';
|
|
21
|
+
import type { EventService } from './event-service.js';
|
|
22
|
+
import type { JobService } from './job-service.js';
|
|
18
23
|
import { createSessionNotFoundError, toSessionSnapshot } from './session-utils.js';
|
|
19
24
|
|
|
20
25
|
function randomId(): string {
|
|
@@ -74,6 +79,82 @@ function createProjectReaderFileSystem(fileSystem: NonNullable<BackendContext['f
|
|
|
74
79
|
};
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
function createCaptureFileSystem(files: Map<string, string | Uint8Array>): FileSystem {
|
|
83
|
+
const normalize = (filePath: string): string => filePath.replace(/\\/g, '/').replace(/\/+/g, '/');
|
|
84
|
+
return {
|
|
85
|
+
async readFile(filePath: string): Promise<string> {
|
|
86
|
+
const value = files.get(normalize(filePath));
|
|
87
|
+
if (typeof value !== 'string') throw new Error(`Captured text file was not found: ${filePath}`);
|
|
88
|
+
return value;
|
|
89
|
+
},
|
|
90
|
+
async readFileRaw(filePath: string): Promise<Uint8Array> {
|
|
91
|
+
const value = files.get(normalize(filePath));
|
|
92
|
+
if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
|
|
93
|
+
return value.slice();
|
|
94
|
+
},
|
|
95
|
+
async writeFile(filePath: string, content: string): Promise<void> {
|
|
96
|
+
files.set(normalize(filePath), content);
|
|
97
|
+
},
|
|
98
|
+
async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
99
|
+
files.set(normalize(filePath), data.slice());
|
|
100
|
+
},
|
|
101
|
+
async mkdir(): Promise<void> {},
|
|
102
|
+
async readdir(): Promise<string[]> {
|
|
103
|
+
return [];
|
|
104
|
+
},
|
|
105
|
+
async exists(filePath: string): Promise<boolean> {
|
|
106
|
+
return files.has(normalize(filePath));
|
|
107
|
+
},
|
|
108
|
+
join(...paths: string[]): string {
|
|
109
|
+
return normalize(paths.filter(Boolean).join('/'));
|
|
110
|
+
},
|
|
111
|
+
dirname(filePath: string): string {
|
|
112
|
+
const normalized = normalize(filePath);
|
|
113
|
+
const separator = normalized.lastIndexOf('/');
|
|
114
|
+
return separator < 0 ? '' : normalized.slice(0, separator);
|
|
115
|
+
},
|
|
116
|
+
async unlink(filePath: string): Promise<void> {
|
|
117
|
+
files.delete(normalize(filePath));
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function capturedFilesEqual(left: Map<string, string | Uint8Array>, right: Map<string, string | Uint8Array>): boolean {
|
|
123
|
+
if (left.size !== right.size) return false;
|
|
124
|
+
for (const [filePath, leftValue] of left) {
|
|
125
|
+
const rightValue = right.get(filePath);
|
|
126
|
+
if (typeof leftValue === 'string') {
|
|
127
|
+
if (leftValue !== rightValue) return false;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
|
|
131
|
+
for (let index = 0; index < leftValue.length; index += 1) {
|
|
132
|
+
if (leftValue[index] !== rightValue[index]) return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function hasFullUamFidelity(
|
|
139
|
+
document: Awaited<ReturnType<ProjectReader['read']>>,
|
|
140
|
+
project: UamProject,
|
|
141
|
+
): Promise<boolean> {
|
|
142
|
+
const sourceFiles = new Map<string, string | Uint8Array>();
|
|
143
|
+
const materializedFiles = new Map<string, string | Uint8Array>();
|
|
144
|
+
try {
|
|
145
|
+
await Promise.all([
|
|
146
|
+
new ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, 'Project.fairy'),
|
|
147
|
+
new ProjectWriter(createCaptureFileSystem(materializedFiles)).write(
|
|
148
|
+
materializeUamProject(project),
|
|
149
|
+
'Project.fairy',
|
|
150
|
+
),
|
|
151
|
+
]);
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return capturedFilesEqual(sourceFiles, materializedFiles);
|
|
156
|
+
}
|
|
157
|
+
|
|
77
158
|
export class RuntimeService {
|
|
78
159
|
public constructor(
|
|
79
160
|
private readonly context: BackendContext,
|
|
@@ -129,7 +210,8 @@ export class RuntimeService {
|
|
|
129
210
|
await advisoryLock.close();
|
|
130
211
|
|
|
131
212
|
const reader = new ProjectReader(createProjectReaderFileSystem(fileSystem));
|
|
132
|
-
const
|
|
213
|
+
const document = await reader.read(fairyPath, { hydrateResourceBytes: true });
|
|
214
|
+
const project = liftDocumentToUamProject(document);
|
|
133
215
|
const sessionId = randomId();
|
|
134
216
|
const session: BackendSessionState = {
|
|
135
217
|
sessionId,
|
|
@@ -139,6 +221,7 @@ export class RuntimeService {
|
|
|
139
221
|
lockFilePath,
|
|
140
222
|
fileSystem,
|
|
141
223
|
project,
|
|
224
|
+
uamFidelity: (await hasFullUamFidelity(document, project)) ? 'full' : 'unsupported',
|
|
142
225
|
revision: 0,
|
|
143
226
|
lastSavedRevision: 0,
|
|
144
227
|
pendingStaleSourceFiles: new Map(),
|
|
@@ -178,12 +261,14 @@ export class RuntimeService {
|
|
|
178
261
|
const sessionId = input.sessionId ?? randomId();
|
|
179
262
|
const storage = input.storage;
|
|
180
263
|
const memoryProjectPath = `memory://${sessionId}`;
|
|
181
|
-
const canonicalProjectPath =
|
|
182
|
-
??
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
264
|
+
const canonicalProjectPath =
|
|
265
|
+
storage?.canonicalProjectPath ??
|
|
266
|
+
input.canonicalProjectPath ??
|
|
267
|
+
(storage ? storage.fileSystem.dirname(storage.fairyPath) || '.' : memoryProjectPath);
|
|
268
|
+
const canonicalPathKey =
|
|
269
|
+
storage?.canonicalPathKey ??
|
|
270
|
+
input.canonicalPathKey ??
|
|
271
|
+
(storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
|
|
187
272
|
const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
|
|
188
273
|
if (existingSessionId) {
|
|
189
274
|
return failure('runtime', startedAt, {
|
|
@@ -203,6 +288,7 @@ export class RuntimeService {
|
|
|
203
288
|
lockFilePath: '',
|
|
204
289
|
fileSystem: storage?.fileSystem,
|
|
205
290
|
project: normalizeUamProject(input.project),
|
|
291
|
+
uamFidelity: 'full',
|
|
206
292
|
revision: 0,
|
|
207
293
|
lastSavedRevision: 0,
|
|
208
294
|
pendingStaleSourceFiles: new Map(),
|
|
@@ -7,13 +7,17 @@ import type {
|
|
|
7
7
|
import type { BackendSessionState } from './context.js';
|
|
8
8
|
import { cloneCapabilitiesSnapshot } from './snapshot-utils.js';
|
|
9
9
|
|
|
10
|
-
export function toSessionSnapshot(
|
|
10
|
+
export function toSessionSnapshot(
|
|
11
|
+
session: BackendSessionState,
|
|
12
|
+
capabilities: BackendCapabilities,
|
|
13
|
+
): BackendSessionSnapshot {
|
|
11
14
|
return {
|
|
12
15
|
sessionId: session.sessionId,
|
|
13
16
|
canonicalProjectPath: session.canonicalProjectPath,
|
|
14
17
|
revision: session.revision,
|
|
15
18
|
lastSavedRevision: session.lastSavedRevision,
|
|
16
19
|
dirty: session.dirty,
|
|
20
|
+
uamFidelity: session.uamFidelity,
|
|
17
21
|
lockHeld: session.lockHeld,
|
|
18
22
|
capabilities: cloneCapabilitiesSnapshot(capabilities),
|
|
19
23
|
};
|