@openfairygui/backend 0.2.0-alpha.8 → 0.2.0

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/dist/node.cjs CHANGED
@@ -21,11 +21,1590 @@ 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-GKzsXJdO.cjs");
25
24
  let node_fs_promises = require("node:fs/promises");
26
25
  node_fs_promises = __toESM(node_fs_promises);
27
26
  let node_path = require("node:path");
28
27
  node_path = __toESM(node_path);
28
+ let _openfairygui_core_uam = require("@openfairygui/core/uam");
29
+ let _openfairygui_functions_uam = require("@openfairygui/functions/uam");
30
+ let _openfairygui_core_project_io = require("@openfairygui/core/project-io");
31
+ //#region src/path-policy.ts
32
+ function normalizeComparablePath(value) {
33
+ const normalized = value.replace(/[/\\]+$/, "").replace(/\\/g, "/");
34
+ const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
35
+ const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
36
+ const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
37
+ const hasRoot = driveMatch ? true : remainder.startsWith("/");
38
+ const rawSegments = remainder.split("/").filter((segment) => segment.length > 0);
39
+ const segments = [];
40
+ for (const segment of rawSegments) {
41
+ if (segment === ".") continue;
42
+ if (segment === "..") {
43
+ if (segments.length > 0 && segments[segments.length - 1] !== "..") segments.pop();
44
+ else if (!hasRoot) segments.push("..");
45
+ continue;
46
+ }
47
+ segments.push(segment);
48
+ }
49
+ const joined = segments.join("/");
50
+ return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
51
+ }
52
+ function createRuntimePathPolicy() {
53
+ return {
54
+ canonicalization: "realpath+normalized-casefold",
55
+ sessionIdentity: "project-root",
56
+ saveTarget: "opened-project-only",
57
+ outputTargets: "deferred",
58
+ workspaceBoundary: "project-root-only"
59
+ };
60
+ }
61
+ async function resolveFairyPath(fileSystem, input) {
62
+ const resolvedInput = fileSystem.resolve(input);
63
+ const stat = await fileSystem.stat(resolvedInput);
64
+ if (stat.isFile() && resolvedInput.endsWith(".fairy")) return await fileSystem.resolvePath(resolvedInput);
65
+ if (stat.isDirectory()) {
66
+ const fairyFiles = (await fileSystem.readdir(resolvedInput)).filter((entry) => entry.endsWith(".fairy"));
67
+ if (fairyFiles.length === 1) return await fileSystem.resolvePath(fileSystem.join(resolvedInput, fairyFiles[0]));
68
+ if (fairyFiles.length > 1) throw new Error(`Multiple .fairy files found in ${resolvedInput}: ${fairyFiles.join(", ")}`);
69
+ throw new Error(`No .fairy file found in ${resolvedInput}`);
70
+ }
71
+ throw new Error(`Input is not a .fairy file or directory: ${resolvedInput}`);
72
+ }
73
+ async function resolveCanonicalProjectRoot(fileSystem, input) {
74
+ const fairyPath = await resolveFairyPath(fileSystem, input);
75
+ const canonicalProjectPath = await fileSystem.resolvePath(fileSystem.dirname(fairyPath));
76
+ return {
77
+ fairyPath,
78
+ canonicalProjectPath,
79
+ canonicalPathKey: normalizeComparablePath(canonicalProjectPath)
80
+ };
81
+ }
82
+ async function validateSaveTarget(fileSystem, openedFairyPath, targetPath) {
83
+ if (!targetPath) return null;
84
+ const attemptedPath = await fileSystem.resolvePath(fileSystem.resolve(targetPath));
85
+ const allowedPath = await fileSystem.resolvePath(openedFairyPath);
86
+ if (normalizeComparablePath(attemptedPath) === normalizeComparablePath(allowedPath)) return null;
87
+ return {
88
+ code: "path_policy_violation",
89
+ message: `Save target is restricted to the originally opened project file: ${allowedPath}`,
90
+ policy: "save_target",
91
+ attemptedPath,
92
+ allowedPath
93
+ };
94
+ }
95
+ //#endregion
96
+ //#region src/contracts.ts
97
+ const BACKEND_CONTRACT_VERSION = "1.1.0-p2";
98
+ const BACKEND_COMPATIBILITY_POLICY = {
99
+ incompatibleChange: "requires contractVersion bump",
100
+ capabilitySchemaChange: "requires capabilitySchemaVersion bump",
101
+ additiveChange: "allowed without breaking existing consumers"
102
+ };
103
+ //#endregion
104
+ //#region src/services/context.ts
105
+ function diagnosticFromError(error) {
106
+ return {
107
+ code: error.code,
108
+ message: error.message,
109
+ severity: "error"
110
+ };
111
+ }
112
+ function randomId$1() {
113
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
114
+ }
115
+ function createMeta(stage, startedAt, options) {
116
+ return {
117
+ requestId: options?.requestId ?? randomId$1(),
118
+ sessionId: options?.sessionId,
119
+ revision: options?.revision,
120
+ durationMs: Math.max(0, Date.now() - startedAt),
121
+ warnings: options?.warnings ?? [],
122
+ diagnostics: options?.diagnostics ?? [],
123
+ stage,
124
+ contractVersion: BACKEND_CONTRACT_VERSION,
125
+ capabilitySchemaVersion: 2
126
+ };
127
+ }
128
+ function success(stage, startedAt, data, options) {
129
+ return {
130
+ ok: true,
131
+ meta: createMeta(stage, startedAt, options),
132
+ data
133
+ };
134
+ }
135
+ function failure(stage, startedAt, error, session, options) {
136
+ const diagnostics = options?.diagnostics ?? [diagnosticFromError(error)];
137
+ return {
138
+ ok: false,
139
+ meta: createMeta(stage, startedAt, {
140
+ ...options,
141
+ diagnostics
142
+ }),
143
+ error,
144
+ session
145
+ };
146
+ }
147
+ //#endregion
148
+ //#region src/services/session-project-writer.ts
149
+ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
150
+ async function trackWrite(targetPath, write) {
151
+ try {
152
+ const result = await write();
153
+ writtenPaths.push(targetPath);
154
+ return result;
155
+ } catch (error) {
156
+ failedPaths.push(targetPath);
157
+ throw error;
158
+ }
159
+ }
160
+ return {
161
+ readFile: (path) => fileSystem.readFile(path),
162
+ readFileRaw: (path) => fileSystem.readFileRaw(path),
163
+ writeFile: (path, content) => trackWrite(path, async () => {
164
+ await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
165
+ await fileSystem.writeFile(path, content);
166
+ }),
167
+ writeFileRaw: (path, data) => trackWrite(path, async () => {
168
+ await fileSystem.mkdir(fileSystem.dirname(path), { recursive: true });
169
+ await fileSystem.writeFileRaw(path, data);
170
+ }),
171
+ mkdir: (path) => fileSystem.mkdir(path, { recursive: true }),
172
+ readdir: (path) => fileSystem.readdir(path),
173
+ async exists(path) {
174
+ try {
175
+ await fileSystem.stat(path);
176
+ return true;
177
+ } catch {
178
+ return false;
179
+ }
180
+ },
181
+ join: (...paths) => fileSystem.join(...paths),
182
+ dirname: (path) => fileSystem.dirname(path),
183
+ unlink: (path) => trackWrite(path, () => fileSystem.unlink(path)),
184
+ rmdir: (path) => trackWrite(path, () => fileSystem.rmdir(path))
185
+ };
186
+ }
187
+ async function writeSessionProject(input) {
188
+ await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, {
189
+ staleSourceFiles: input.staleSourceFiles,
190
+ staleResourceFolders: input.staleResourceFolders,
191
+ staleBranchDirectories: input.staleBranchDirectories
192
+ });
193
+ }
194
+ //#endregion
195
+ //#region src/services/session-utils.ts
196
+ function toSessionSnapshot(session, capabilities) {
197
+ return {
198
+ sessionId: session.sessionId,
199
+ canonicalProjectPath: session.canonicalProjectPath,
200
+ revision: session.revision,
201
+ lastSavedRevision: session.lastSavedRevision,
202
+ dirty: session.dirty,
203
+ uamFidelity: session.uamFidelity,
204
+ lockHeld: session.lockHeld,
205
+ capabilities: structuredClone(capabilities)
206
+ };
207
+ }
208
+ function createSessionNotFoundError(sessionId) {
209
+ return {
210
+ code: "session_not_found",
211
+ message: `Session was not found: ${sessionId}`,
212
+ sessionId
213
+ };
214
+ }
215
+ function createStaleWriteError(session, expectedRevision) {
216
+ return {
217
+ code: "stale_write",
218
+ message: `Expected revision ${expectedRevision} does not match current revision ${session.revision}.`,
219
+ sessionId: session.sessionId,
220
+ canonicalPathKey: session.canonicalPathKey,
221
+ expectedRevision,
222
+ actualRevision: session.revision
223
+ };
224
+ }
225
+ //#endregion
226
+ //#region src/services/authoring-service.ts
227
+ function sourceFileKey(source) {
228
+ return [
229
+ source.branch,
230
+ source.packageName,
231
+ source.path,
232
+ source.fileName
233
+ ].join("\0");
234
+ }
235
+ function resourceFolderKey(folder) {
236
+ return [
237
+ folder.branch,
238
+ folder.packageName,
239
+ folder.path
240
+ ].join("\0");
241
+ }
242
+ function branchDirectoryKey(directory) {
243
+ return [directory.branch, directory.packageName ?? ""].join("\0");
244
+ }
245
+ function recordStaleProjectFiles(session, previousProject, nextProject) {
246
+ if (!session.fileSystem) return;
247
+ for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(previousProject, nextProject)) session.pendingStaleSourceFiles.set(sourceFileKey(source), source);
248
+ for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(nextProject, previousProject)) session.pendingStaleSourceFiles.delete(sourceFileKey(source));
249
+ for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(previousProject, nextProject)) session.pendingStaleResourceFolders.set(resourceFolderKey(folder), folder);
250
+ for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(nextProject, previousProject)) session.pendingStaleResourceFolders.delete(resourceFolderKey(folder));
251
+ for (const directory of (0, _openfairygui_core_uam.staleBranchDirectories)(previousProject, nextProject)) session.pendingStaleBranchDirectories.set(branchDirectoryKey(directory), directory);
252
+ for (const directory of (0, _openfairygui_core_uam.staleBranchDirectories)(nextProject, previousProject)) session.pendingStaleBranchDirectories.delete(branchDirectoryKey(directory));
253
+ }
254
+ function toBackendDiagnostics(error) {
255
+ return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
256
+ code: error.code,
257
+ message: error.message,
258
+ severity: "error",
259
+ operationKind: error.operationKind,
260
+ opIndex: error.opIndex,
261
+ opId: error.opId
262
+ }];
263
+ }
264
+ function createCapabilityUnavailableError$1(message) {
265
+ return {
266
+ code: "capability_unavailable",
267
+ message,
268
+ capability: "fileSystem",
269
+ requiredAdapter: "BackendFileSystem"
270
+ };
271
+ }
272
+ function createUamFidelityUnsupportedError(session) {
273
+ return {
274
+ code: "uam_fidelity_unsupported",
275
+ message: "The source project contains formal properties that the current UAM cannot preserve.",
276
+ sessionId: session.sessionId,
277
+ canonicalPathKey: session.canonicalPathKey
278
+ };
279
+ }
280
+ function validationDiagnostics(sessionProject) {
281
+ return (0, _openfairygui_core_uam.validateUamProject)(sessionProject).map((issue) => ({
282
+ code: "materialize_validation_failed",
283
+ message: issue.message,
284
+ severity: "error",
285
+ path: issue.path,
286
+ operationKind: "materializeSession"
287
+ }));
288
+ }
289
+ function toMaterializeSnapshot(session, capabilities, input) {
290
+ return {
291
+ ...toSessionSnapshot(session, capabilities),
292
+ mode: "fullProject",
293
+ reason: input.reason,
294
+ materializeRevision: session.revision,
295
+ saveRevision: session.lastSavedRevision,
296
+ writtenPaths: [...input.writtenPaths],
297
+ skippedPaths: [...input.skippedPaths],
298
+ diagnostics: input.diagnostics.map((diagnostic) => ({ ...diagnostic }))
299
+ };
300
+ }
301
+ function storageCanonicalTarget(input) {
302
+ const canonicalProjectPath = input.canonicalProjectPath ?? (input.fileSystem.dirname(input.fairyPath) || ".");
303
+ return {
304
+ fileSystem: input.fileSystem,
305
+ fairyPath: input.fairyPath,
306
+ canonicalProjectPath,
307
+ canonicalPathKey: input.canonicalPathKey ?? normalizeComparablePath(canonicalProjectPath)
308
+ };
309
+ }
310
+ function detachSharedByteViews(value, seen = /* @__PURE__ */ new WeakSet()) {
311
+ if (!value || typeof value !== "object" || seen.has(value)) return;
312
+ seen.add(value);
313
+ for (const [key, child] of Object.entries(value)) {
314
+ if (child instanceof Uint8Array) {
315
+ if (typeof SharedArrayBuffer !== "undefined" && child.buffer instanceof SharedArrayBuffer) value[key] = new Uint8Array(child);
316
+ continue;
317
+ }
318
+ detachSharedByteViews(child, seen);
319
+ }
320
+ }
321
+ var AuthoringService = class {
322
+ sessionOperations = /* @__PURE__ */ new Map();
323
+ constructor(context, cacheService, eventService) {
324
+ this.context = context;
325
+ this.cacheService = cacheService;
326
+ this.eventService = eventService;
327
+ }
328
+ async runSessionExclusive(sessionId, operation) {
329
+ const previous = this.sessionOperations.get(sessionId) ?? Promise.resolve();
330
+ let release = () => void 0;
331
+ const current = new Promise((resolve) => {
332
+ release = resolve;
333
+ });
334
+ const tail = previous.then(() => current);
335
+ this.sessionOperations.set(sessionId, tail);
336
+ await previous;
337
+ try {
338
+ return await operation();
339
+ } finally {
340
+ release();
341
+ if (this.sessionOperations.get(sessionId) === tail) this.sessionOperations.delete(sessionId);
342
+ }
343
+ }
344
+ async applyTransaction(input) {
345
+ const queuedInput = structuredClone(input);
346
+ detachSharedByteViews(queuedInput);
347
+ return this.runSessionExclusive(queuedInput.sessionId, () => this.applyTransactionExclusive(queuedInput));
348
+ }
349
+ async applyTransactionExclusive(input) {
350
+ const startedAt = Date.now();
351
+ const session = this.context.sessions.get(input.sessionId);
352
+ if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
353
+ if (input.expectedRevision !== session.revision) {
354
+ this.eventService.emit({
355
+ kind: "transaction.rejected",
356
+ sessionId: session.sessionId,
357
+ canonicalPathKey: session.canonicalPathKey,
358
+ revision: session.revision
359
+ });
360
+ return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
361
+ sessionId: session.sessionId,
362
+ revision: session.revision
363
+ });
364
+ }
365
+ const result = await (0, _openfairygui_functions_uam.applyUamTransactionAppAsync)({
366
+ project: session.project,
367
+ operations: input.operations
368
+ });
369
+ if (this.context.sessions.get(input.sessionId) !== session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
370
+ if (result.ok === false) {
371
+ const diagnostics = toBackendDiagnostics(result.error);
372
+ this.eventService.emit({
373
+ kind: "transaction.rejected",
374
+ sessionId: session.sessionId,
375
+ canonicalPathKey: session.canonicalPathKey,
376
+ revision: session.revision,
377
+ diagnostics
378
+ });
379
+ return failure("authoring", startedAt, result.error, toSessionSnapshot(session, this.context.capabilities), {
380
+ sessionId: session.sessionId,
381
+ revision: session.revision,
382
+ diagnostics
383
+ });
384
+ }
385
+ recordStaleProjectFiles(session, session.project, result.project);
386
+ session.project = result.project;
387
+ session.revision += 1;
388
+ session.dirty = true;
389
+ const cacheEntry = this.cacheService.invalidateSession(session);
390
+ this.eventService.emit({
391
+ kind: "transaction.applied",
392
+ sessionId: session.sessionId,
393
+ canonicalPathKey: session.canonicalPathKey,
394
+ revision: session.revision
395
+ });
396
+ this.eventService.emit({
397
+ kind: "cache.invalidated",
398
+ sessionId: session.sessionId,
399
+ canonicalPathKey: session.canonicalPathKey,
400
+ revision: session.revision,
401
+ cacheRevision: cacheEntry.revision
402
+ });
403
+ return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
404
+ sessionId: session.sessionId,
405
+ revision: session.revision
406
+ });
407
+ }
408
+ async saveSession(input) {
409
+ if (input.force === true || input.mode === "materializeCleanSession") return this.materializeSession({
410
+ sessionId: input.sessionId,
411
+ expectedRevision: input.expectedRevision,
412
+ targetPath: input.targetPath,
413
+ fileSystem: input.fileSystem,
414
+ mode: "fullProject",
415
+ reason: "force_save"
416
+ });
417
+ return this.runSessionExclusive(input.sessionId, () => this.saveSessionExclusive(input));
418
+ }
419
+ async saveSessionExclusive(input) {
420
+ const startedAt = Date.now();
421
+ const session = this.context.sessions.get(input.sessionId);
422
+ if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
423
+ const fileSystem = session.fileSystem ?? input.fileSystem ?? this.context.fileSystem;
424
+ if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("saveSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
425
+ sessionId: session.sessionId,
426
+ revision: session.revision
427
+ });
428
+ if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
429
+ sessionId: session.sessionId,
430
+ revision: session.revision
431
+ });
432
+ const targetViolation = await validateSaveTarget(fileSystem, session.fairyPath, input.targetPath);
433
+ if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
434
+ sessionId: session.sessionId,
435
+ revision: session.revision
436
+ });
437
+ if (!session.dirty) return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
438
+ sessionId: session.sessionId,
439
+ revision: session.revision
440
+ });
441
+ if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
442
+ sessionId: session.sessionId,
443
+ revision: session.revision
444
+ });
445
+ const committedPaths = [];
446
+ const failedPaths = [];
447
+ this.eventService.emit({
448
+ kind: "save.started",
449
+ sessionId: session.sessionId,
450
+ canonicalPathKey: session.canonicalPathKey,
451
+ revision: session.revision
452
+ });
453
+ try {
454
+ await writeSessionProject({
455
+ fileSystem,
456
+ document: (0, _openfairygui_core_uam.materializeUamProject)(session.project),
457
+ fairyPath: session.fairyPath,
458
+ staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
459
+ staleResourceFolders: [...session.pendingStaleResourceFolders.values()],
460
+ staleBranchDirectories: [...session.pendingStaleBranchDirectories.values()],
461
+ writtenPaths: committedPaths,
462
+ failedPaths
463
+ });
464
+ session.fileSystem ??= fileSystem;
465
+ session.pendingStaleSourceFiles.clear();
466
+ session.pendingStaleResourceFolders.clear();
467
+ session.pendingStaleBranchDirectories.clear();
468
+ (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
469
+ session.lastSavedRevision = session.revision;
470
+ session.dirty = false;
471
+ const cacheEntry = this.cacheService.refreshSession(session);
472
+ this.eventService.emit({
473
+ kind: "save.completed",
474
+ sessionId: session.sessionId,
475
+ canonicalPathKey: session.canonicalPathKey,
476
+ revision: session.revision
477
+ });
478
+ this.eventService.emit({
479
+ kind: "cache.updated",
480
+ sessionId: session.sessionId,
481
+ canonicalPathKey: session.canonicalPathKey,
482
+ revision: session.revision,
483
+ cacheRevision: cacheEntry.revision
484
+ });
485
+ return success("authoring", startedAt, toSessionSnapshot(session, this.context.capabilities), {
486
+ sessionId: session.sessionId,
487
+ revision: session.revision
488
+ });
489
+ } catch (error) {
490
+ this.cacheService.invalidateSession(session);
491
+ this.eventService.emit({
492
+ kind: "save.failed",
493
+ sessionId: session.sessionId,
494
+ canonicalPathKey: session.canonicalPathKey,
495
+ revision: session.revision
496
+ });
497
+ return failure("authoring", startedAt, {
498
+ code: "save_partial_failure",
499
+ message: error instanceof Error ? error.message : String(error),
500
+ sessionId: session.sessionId,
501
+ canonicalPathKey: session.canonicalPathKey,
502
+ attemptedRevision: session.revision,
503
+ lastSavedRevision: session.lastSavedRevision,
504
+ committedPaths,
505
+ failedPaths,
506
+ diskMayBePartiallyUpdated: true
507
+ }, toSessionSnapshot(session, this.context.capabilities), {
508
+ sessionId: session.sessionId,
509
+ revision: session.revision
510
+ });
511
+ }
512
+ }
513
+ async materializeSession(input) {
514
+ return this.runSessionExclusive(input.sessionId, () => this.materializeSessionExclusive(input));
515
+ }
516
+ async materializeSessionExclusive(input) {
517
+ const startedAt = Date.now();
518
+ const session = this.context.sessions.get(input.sessionId);
519
+ if (!session || session.closed) return failure("authoring", startedAt, createSessionNotFoundError(input.sessionId));
520
+ if (input.expectedRevision !== void 0 && input.expectedRevision !== session.revision) return failure("authoring", startedAt, createStaleWriteError(session, input.expectedRevision), toSessionSnapshot(session, this.context.capabilities), {
521
+ sessionId: session.sessionId,
522
+ revision: session.revision
523
+ });
524
+ const storageTarget = input.storage ? storageCanonicalTarget(input.storage) : null;
525
+ const fileSystem = storageTarget?.fileSystem ?? input.fileSystem ?? session.fileSystem ?? this.context.fileSystem;
526
+ if (!fileSystem) return failure("authoring", startedAt, createCapabilityUnavailableError$1("materializeSession requires an injected BackendFileSystem adapter."), toSessionSnapshot(session, this.context.capabilities), {
527
+ sessionId: session.sessionId,
528
+ revision: session.revision
529
+ });
530
+ const fairyPath = storageTarget?.fairyPath ?? session.fairyPath;
531
+ const targetViolation = await validateSaveTarget(fileSystem, fairyPath, input.targetPath);
532
+ if (targetViolation) return failure("authoring", startedAt, targetViolation, toSessionSnapshot(session, this.context.capabilities), {
533
+ sessionId: session.sessionId,
534
+ revision: session.revision
535
+ });
536
+ if (storageTarget) {
537
+ const holderSessionId = this.context.sessionsByPath.get(storageTarget.canonicalPathKey);
538
+ if (holderSessionId && holderSessionId !== session.sessionId) return failure("authoring", startedAt, {
539
+ code: "lock_conflict",
540
+ kind: "in_process_session_exists",
541
+ message: `Project is already open in this backend runtime: ${storageTarget.canonicalProjectPath}`,
542
+ canonicalPathKey: storageTarget.canonicalPathKey,
543
+ holderSessionId
544
+ }, toSessionSnapshot(session, this.context.capabilities), {
545
+ sessionId: session.sessionId,
546
+ revision: session.revision
547
+ });
548
+ }
549
+ if (session.uamFidelity === "unsupported") return failure("authoring", startedAt, createUamFidelityUnsupportedError(session), toSessionSnapshot(session, this.context.capabilities), {
550
+ sessionId: session.sessionId,
551
+ revision: session.revision
552
+ });
553
+ const diagnostics = validationDiagnostics(session.project);
554
+ if (diagnostics.length > 0) return failure("authoring", startedAt, {
555
+ code: "materialize_validation_failed",
556
+ message: `UAM materialize validation failed with ${diagnostics.length} issue(s).`,
557
+ sessionId: session.sessionId,
558
+ canonicalPathKey: session.canonicalPathKey,
559
+ issueCount: diagnostics.length,
560
+ diagnostics
561
+ }, toSessionSnapshot(session, this.context.capabilities), {
562
+ sessionId: session.sessionId,
563
+ revision: session.revision,
564
+ diagnostics
565
+ });
566
+ let document;
567
+ try {
568
+ document = (0, _openfairygui_core_uam.materializeUamProject)(session.project);
569
+ } catch (error) {
570
+ const diagnosticsFromError = [{
571
+ code: "materialize_validation_failed",
572
+ message: error instanceof Error ? error.message : String(error),
573
+ severity: "error",
574
+ operationKind: "materializeSession"
575
+ }];
576
+ return failure("authoring", startedAt, {
577
+ code: "materialize_validation_failed",
578
+ message: error instanceof Error ? error.message : String(error),
579
+ sessionId: session.sessionId,
580
+ canonicalPathKey: session.canonicalPathKey,
581
+ issueCount: diagnosticsFromError.length,
582
+ diagnostics: diagnosticsFromError
583
+ }, toSessionSnapshot(session, this.context.capabilities), {
584
+ sessionId: session.sessionId,
585
+ revision: session.revision,
586
+ diagnostics: diagnosticsFromError
587
+ });
588
+ }
589
+ const writtenPaths = [];
590
+ const failedPaths = [];
591
+ const skippedPaths = [];
592
+ this.eventService.emit({
593
+ kind: "save.started",
594
+ sessionId: session.sessionId,
595
+ canonicalPathKey: storageTarget?.canonicalPathKey ?? session.canonicalPathKey,
596
+ revision: session.revision
597
+ });
598
+ try {
599
+ const isSessionStorageTarget = fileSystem === session.fileSystem && fairyPath === session.fairyPath;
600
+ await writeSessionProject({
601
+ fileSystem,
602
+ document,
603
+ fairyPath,
604
+ staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
605
+ staleResourceFolders: isSessionStorageTarget ? [...session.pendingStaleResourceFolders.values()] : [],
606
+ staleBranchDirectories: isSessionStorageTarget ? [...session.pendingStaleBranchDirectories.values()] : [],
607
+ writtenPaths,
608
+ failedPaths
609
+ });
610
+ if (isSessionStorageTarget) {
611
+ session.pendingStaleSourceFiles.clear();
612
+ session.pendingStaleResourceFolders.clear();
613
+ session.pendingStaleBranchDirectories.clear();
614
+ }
615
+ if (storageTarget && !isSessionStorageTarget) {
616
+ session.pendingStaleSourceFiles.clear();
617
+ session.pendingStaleResourceFolders.clear();
618
+ session.pendingStaleBranchDirectories.clear();
619
+ }
620
+ if (isSessionStorageTarget || storageTarget) (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
621
+ if (storageTarget) {
622
+ this.context.sessionsByPath.delete(session.canonicalPathKey);
623
+ session.fileSystem = storageTarget.fileSystem;
624
+ session.fairyPath = storageTarget.fairyPath;
625
+ session.canonicalProjectPath = storageTarget.canonicalProjectPath;
626
+ session.canonicalPathKey = storageTarget.canonicalPathKey;
627
+ this.context.sessionsByPath.set(session.canonicalPathKey, session.sessionId);
628
+ }
629
+ session.lastSavedRevision = session.revision;
630
+ session.dirty = false;
631
+ const cacheEntry = this.cacheService.refreshSession(session);
632
+ this.eventService.emit({
633
+ kind: "save.completed",
634
+ sessionId: session.sessionId,
635
+ canonicalPathKey: session.canonicalPathKey,
636
+ revision: session.revision
637
+ });
638
+ this.eventService.emit({
639
+ kind: "cache.updated",
640
+ sessionId: session.sessionId,
641
+ canonicalPathKey: session.canonicalPathKey,
642
+ revision: session.revision,
643
+ cacheRevision: cacheEntry.revision
644
+ });
645
+ return success("authoring", startedAt, toMaterializeSnapshot(session, this.context.capabilities, {
646
+ reason: input.reason,
647
+ writtenPaths,
648
+ skippedPaths,
649
+ diagnostics: []
650
+ }), {
651
+ sessionId: session.sessionId,
652
+ revision: session.revision
653
+ });
654
+ } catch (error) {
655
+ const diagnosticsFromError = [{
656
+ code: "write_failed",
657
+ message: error instanceof Error ? error.message : String(error),
658
+ severity: "error",
659
+ path: failedPaths[0],
660
+ operationKind: "materializeSession"
661
+ }];
662
+ this.cacheService.invalidateSession(session);
663
+ this.eventService.emit({
664
+ kind: "save.failed",
665
+ sessionId: session.sessionId,
666
+ canonicalPathKey: session.canonicalPathKey,
667
+ revision: session.revision,
668
+ diagnostics: diagnosticsFromError
669
+ });
670
+ return failure("authoring", startedAt, {
671
+ code: "write_failed",
672
+ message: error instanceof Error ? error.message : String(error),
673
+ sessionId: session.sessionId,
674
+ canonicalPathKey: session.canonicalPathKey,
675
+ attemptedRevision: session.revision,
676
+ lastSavedRevision: session.lastSavedRevision,
677
+ writtenPaths,
678
+ failedPaths,
679
+ skippedPaths,
680
+ diagnostics: diagnosticsFromError,
681
+ diskMayBePartiallyUpdated: true
682
+ }, toSessionSnapshot(session, this.context.capabilities), {
683
+ sessionId: session.sessionId,
684
+ revision: session.revision,
685
+ diagnostics: diagnosticsFromError
686
+ });
687
+ }
688
+ }
689
+ };
690
+ //#endregion
691
+ //#region src/services/cache-service.ts
692
+ function createCacheEntry(session, valid) {
693
+ return {
694
+ canonicalPathKey: session.canonicalPathKey,
695
+ sessionId: session.sessionId,
696
+ revision: session.revision,
697
+ lastSavedRevision: session.lastSavedRevision,
698
+ dirty: session.dirty,
699
+ valid,
700
+ indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
701
+ summary: {
702
+ packageCount: session.project.packages.length,
703
+ resourceCount: session.project.packages.reduce((total, pkg) => total + pkg.resources.length, 0),
704
+ diagnostics: []
705
+ }
706
+ };
707
+ }
708
+ var CacheService = class {
709
+ constructor(context) {
710
+ this.context = context;
711
+ }
712
+ getCacheSnapshot(input) {
713
+ const startedAt = Date.now();
714
+ const session = this.context.sessions.get(input.sessionId);
715
+ if (!session || session.closed) return failure("read", startedAt, createSessionNotFoundError(input.sessionId));
716
+ const entry = this.context.cacheBySession.get(input.sessionId);
717
+ return success("read", startedAt, {
718
+ cacheRevision: entry?.revision ?? session.revision,
719
+ entries: entry ? [structuredClone(entry)] : []
720
+ }, {
721
+ sessionId: session.sessionId,
722
+ revision: session.revision
723
+ });
724
+ }
725
+ refreshSession(session) {
726
+ const entry = createCacheEntry(session, true);
727
+ this.context.cacheBySession.set(session.sessionId, entry);
728
+ return entry;
729
+ }
730
+ invalidateSession(session) {
731
+ const entry = createCacheEntry(session, false);
732
+ this.context.cacheBySession.set(session.sessionId, entry);
733
+ return entry;
734
+ }
735
+ removeSession(sessionId) {
736
+ this.context.cacheBySession.delete(sessionId);
737
+ }
738
+ };
739
+ //#endregion
740
+ //#region src/services/event-service.ts
741
+ const DEFAULT_EVENT_RETENTION_LIMIT = 1e3;
742
+ var EventService = class {
743
+ constructor(context) {
744
+ this.context = context;
745
+ }
746
+ emit(event) {
747
+ const emitted = {
748
+ ...event,
749
+ sequence: this.context.nextEventSequence(),
750
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
751
+ diagnostics: event.diagnostics ?? []
752
+ };
753
+ const sessionId = event.sessionId;
754
+ if (!sessionId) return emitted;
755
+ const events = this.context.eventsBySession.get(sessionId) ?? [];
756
+ events.push(emitted);
757
+ while (events.length > DEFAULT_EVENT_RETENTION_LIMIT) events.shift();
758
+ this.context.eventsBySession.set(sessionId, events);
759
+ return emitted;
760
+ }
761
+ getEvents(input) {
762
+ const startedAt = Date.now();
763
+ const session = this.context.sessions.get(input.sessionId);
764
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
765
+ const events = this.context.eventsBySession.get(input.sessionId) ?? [];
766
+ const oldestSequence = events[0]?.sequence ?? (events.length === 0 ? 1 : 0);
767
+ const currentSequence = events.at(-1)?.sequence ?? 0;
768
+ const after = input.after === void 0 ? 0 : Number(input.after);
769
+ if (!Number.isInteger(after) || after < 0) return failure("runtime", startedAt, {
770
+ code: "event_cursor_invalid",
771
+ message: `Invalid event cursor: ${input.after}`,
772
+ sessionId: input.sessionId,
773
+ after: String(input.after)
774
+ });
775
+ if (events.length > 0 && after !== 0 && after < oldestSequence - 1) return failure("runtime", startedAt, {
776
+ code: "event_cursor_invalid",
777
+ message: `Event cursor has expired: ${after}`,
778
+ sessionId: input.sessionId,
779
+ after: String(input.after)
780
+ });
781
+ if (after > currentSequence) return failure("runtime", startedAt, {
782
+ code: "event_cursor_invalid",
783
+ message: `Unknown event cursor: ${after}`,
784
+ sessionId: input.sessionId,
785
+ after: String(input.after)
786
+ });
787
+ const filtered = events.filter((event) => event.sequence > after);
788
+ const limit = input.limit === void 0 ? filtered.length : Math.max(0, input.limit);
789
+ return success("runtime", startedAt, {
790
+ events: filtered.slice(0, limit).map((event) => structuredClone(event)),
791
+ oldestSequence,
792
+ currentSequence,
793
+ cursorExpired: false
794
+ }, {
795
+ sessionId: session.sessionId,
796
+ revision: session.revision
797
+ });
798
+ }
799
+ removeSession(sessionId) {
800
+ this.context.eventsBySession.delete(sessionId);
801
+ }
802
+ };
803
+ //#endregion
804
+ //#region src/services/job-service.ts
805
+ const COMPLETED_JOB_RETENTION_LIMIT = 100;
806
+ const REFRESH_START_DELAY_MS = 0;
807
+ const REFRESH_COMPLETE_DELAY_MS = 50;
808
+ function isTerminal(status) {
809
+ return status === "completed" || status === "failed" || status === "cancelled";
810
+ }
811
+ var JobService = class {
812
+ constructor(context, cacheService, eventService) {
813
+ this.context = context;
814
+ this.cacheService = cacheService;
815
+ this.eventService = eventService;
816
+ }
817
+ cancellationRequests = /* @__PURE__ */ new Set();
818
+ getJobs(sessionId) {
819
+ return this.context.jobsBySession.get(sessionId) ?? [];
820
+ }
821
+ setJobs(sessionId, jobs) {
822
+ const retained = [];
823
+ let terminalCount = 0;
824
+ for (let index = jobs.length - 1; index >= 0; index -= 1) {
825
+ const job = jobs[index];
826
+ if (isTerminal(job.status)) {
827
+ if (terminalCount >= COMPLETED_JOB_RETENTION_LIMIT) continue;
828
+ terminalCount += 1;
829
+ }
830
+ retained.push(job);
831
+ }
832
+ this.context.jobsBySession.set(sessionId, retained.reverse());
833
+ }
834
+ refreshCache(input) {
835
+ const startedAt = Date.now();
836
+ const session = this.context.sessions.get(input.sessionId);
837
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
838
+ const jobId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
839
+ const job = {
840
+ jobId,
841
+ kind: "cache.refresh",
842
+ status: "queued",
843
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
844
+ sessionId: session.sessionId,
845
+ canonicalPathKey: session.canonicalPathKey,
846
+ revision: session.revision,
847
+ diagnostics: [],
848
+ progress: {
849
+ completed: 0,
850
+ total: 1,
851
+ message: input.reason ?? "manual"
852
+ }
853
+ };
854
+ const jobs = this.getJobs(session.sessionId);
855
+ jobs.push(job);
856
+ this.setJobs(session.sessionId, jobs);
857
+ this.eventService.emit({
858
+ kind: "job.created",
859
+ sessionId: session.sessionId,
860
+ canonicalPathKey: session.canonicalPathKey,
861
+ revision: session.revision,
862
+ jobId
863
+ });
864
+ this.scheduleRefreshJob(session.sessionId, jobId);
865
+ return success("runtime", startedAt, structuredClone(job), {
866
+ sessionId: session.sessionId,
867
+ revision: session.revision
868
+ });
869
+ }
870
+ getJob(input) {
871
+ const startedAt = Date.now();
872
+ const session = this.context.sessions.get(input.sessionId);
873
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
874
+ const job = this.getJobs(input.sessionId).find((item) => item.jobId === input.jobId);
875
+ if (!job) return failure("runtime", startedAt, {
876
+ code: "job_not_found",
877
+ message: `Job was not found: ${input.jobId}`,
878
+ sessionId: input.sessionId,
879
+ jobId: input.jobId
880
+ }, void 0, {
881
+ sessionId: session.sessionId,
882
+ revision: session.revision
883
+ });
884
+ return success("runtime", startedAt, structuredClone(job), {
885
+ sessionId: session.sessionId,
886
+ revision: session.revision
887
+ });
888
+ }
889
+ listJobs(input) {
890
+ const startedAt = Date.now();
891
+ const session = this.context.sessions.get(input.sessionId);
892
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
893
+ let jobs = [...this.getJobs(input.sessionId)];
894
+ if (input.kind) jobs = jobs.filter((job) => job.kind === input.kind);
895
+ if (input.status) if (input.status === "active") jobs = jobs.filter((job) => !isTerminal(job.status));
896
+ else if (input.status === "terminal") jobs = jobs.filter((job) => isTerminal(job.status));
897
+ else jobs = jobs.filter((job) => job.status === input.status);
898
+ if (input.limit !== void 0) jobs = jobs.slice(0, Math.max(0, input.limit));
899
+ return success("runtime", startedAt, { jobs: jobs.map((job) => structuredClone(job)) }, {
900
+ sessionId: session.sessionId,
901
+ revision: session.revision
902
+ });
903
+ }
904
+ cancelJob(input) {
905
+ const startedAt = Date.now();
906
+ const session = this.context.sessions.get(input.sessionId);
907
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
908
+ const job = this.getJobs(input.sessionId).find((item) => item.jobId === input.jobId);
909
+ if (!job) return failure("runtime", startedAt, {
910
+ code: "job_not_found",
911
+ message: `Job was not found: ${input.jobId}`,
912
+ sessionId: input.sessionId,
913
+ jobId: input.jobId
914
+ }, void 0, {
915
+ sessionId: session.sessionId,
916
+ revision: session.revision
917
+ });
918
+ if (isTerminal(job.status)) return failure("runtime", startedAt, {
919
+ code: "job_not_cancellable",
920
+ message: `Job is already terminal: ${input.jobId}`,
921
+ sessionId: input.sessionId,
922
+ jobId: input.jobId,
923
+ status: job.status
924
+ }, void 0, {
925
+ sessionId: session.sessionId,
926
+ revision: session.revision
927
+ });
928
+ this.cancellationRequests.add(job.jobId);
929
+ this.eventService.emit({
930
+ kind: "job.cancelRequested",
931
+ sessionId: input.sessionId,
932
+ canonicalPathKey: session.canonicalPathKey,
933
+ revision: session.revision,
934
+ jobId: job.jobId
935
+ });
936
+ const cancelled = this.cancelRefreshJob(session.sessionId, job);
937
+ return success("runtime", startedAt, structuredClone(cancelled), {
938
+ sessionId: session.sessionId,
939
+ revision: session.revision
940
+ });
941
+ }
942
+ removeSession(sessionId) {
943
+ for (const job of this.getJobs(sessionId)) this.cancellationRequests.delete(job.jobId);
944
+ this.context.jobsBySession.delete(sessionId);
945
+ }
946
+ replaceJob(sessionId, nextJob) {
947
+ const jobs = this.getJobs(sessionId).map((job) => job.jobId === nextJob.jobId ? nextJob : job);
948
+ this.setJobs(sessionId, jobs);
949
+ }
950
+ scheduleRefreshJob(sessionId, jobId) {
951
+ setTimeout(() => this.startRefreshJob(sessionId, jobId), REFRESH_START_DELAY_MS);
952
+ }
953
+ startRefreshJob(sessionId, jobId) {
954
+ const session = this.context.sessions.get(sessionId);
955
+ if (!session || session.closed) return;
956
+ const job = this.getJobs(sessionId).find((item) => item.jobId === jobId);
957
+ if (!job || isTerminal(job.status)) return;
958
+ if (this.cancellationRequests.has(jobId)) {
959
+ this.cancelRefreshJob(sessionId, job);
960
+ return;
961
+ }
962
+ const running = {
963
+ ...job,
964
+ status: "running",
965
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
966
+ progress: {
967
+ completed: 0,
968
+ total: 1,
969
+ message: "refreshing cache"
970
+ }
971
+ };
972
+ this.replaceJob(sessionId, running);
973
+ this.eventService.emit({
974
+ kind: "job.started",
975
+ sessionId,
976
+ canonicalPathKey: session.canonicalPathKey,
977
+ revision: session.revision,
978
+ jobId
979
+ });
980
+ this.eventService.emit({
981
+ kind: "job.progress",
982
+ sessionId,
983
+ canonicalPathKey: session.canonicalPathKey,
984
+ revision: session.revision,
985
+ jobId,
986
+ payload: running.progress
987
+ });
988
+ setTimeout(() => this.completeRefreshJob(sessionId, jobId), REFRESH_COMPLETE_DELAY_MS);
989
+ }
990
+ completeRefreshJob(sessionId, jobId) {
991
+ const session = this.context.sessions.get(sessionId);
992
+ if (!session || session.closed) return;
993
+ const job = this.getJobs(sessionId).find((item) => item.jobId === jobId);
994
+ if (!job || isTerminal(job.status)) return;
995
+ if (this.cancellationRequests.has(jobId)) {
996
+ this.cancelRefreshJob(sessionId, job);
997
+ return;
998
+ }
999
+ try {
1000
+ const entry = this.cacheService.refreshSession(session);
1001
+ const completed = {
1002
+ ...job,
1003
+ status: "completed",
1004
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1005
+ cacheRevision: entry.revision,
1006
+ progress: {
1007
+ completed: 1,
1008
+ total: 1,
1009
+ message: "cache refreshed"
1010
+ },
1011
+ result: { cacheRevision: entry.revision }
1012
+ };
1013
+ this.replaceJob(sessionId, completed);
1014
+ this.eventService.emit({
1015
+ kind: "job.completed",
1016
+ sessionId,
1017
+ canonicalPathKey: session.canonicalPathKey,
1018
+ revision: session.revision,
1019
+ cacheRevision: entry.revision,
1020
+ jobId
1021
+ });
1022
+ this.eventService.emit({
1023
+ kind: "cache.updated",
1024
+ sessionId,
1025
+ canonicalPathKey: session.canonicalPathKey,
1026
+ revision: session.revision,
1027
+ cacheRevision: entry.revision
1028
+ });
1029
+ } catch (error) {
1030
+ const failed = {
1031
+ ...job,
1032
+ status: "failed",
1033
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1034
+ error: {
1035
+ code: "cache_refresh_failed",
1036
+ message: error instanceof Error ? error.message : "Cache refresh failed",
1037
+ sessionId,
1038
+ jobId
1039
+ }
1040
+ };
1041
+ this.replaceJob(sessionId, failed);
1042
+ this.eventService.emit({
1043
+ kind: "job.failed",
1044
+ sessionId,
1045
+ canonicalPathKey: session.canonicalPathKey,
1046
+ revision: session.revision,
1047
+ jobId,
1048
+ diagnostics: failed.diagnostics
1049
+ });
1050
+ }
1051
+ }
1052
+ cancelRefreshJob(sessionId, job) {
1053
+ const session = this.context.sessions.get(sessionId);
1054
+ const cancelled = {
1055
+ ...job,
1056
+ status: "cancelled",
1057
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
1058
+ error: {
1059
+ code: "job_cancelled",
1060
+ message: `Job was cancelled: ${job.jobId}`,
1061
+ sessionId,
1062
+ jobId: job.jobId
1063
+ }
1064
+ };
1065
+ this.replaceJob(sessionId, cancelled);
1066
+ this.cancellationRequests.delete(job.jobId);
1067
+ this.eventService.emit({
1068
+ kind: "job.cancelled",
1069
+ sessionId,
1070
+ canonicalPathKey: session?.canonicalPathKey ?? job.canonicalPathKey,
1071
+ revision: session?.revision ?? job.revision,
1072
+ jobId: job.jobId
1073
+ });
1074
+ return cancelled;
1075
+ }
1076
+ };
1077
+ //#endregion
1078
+ //#region src/services/read-service.ts
1079
+ var ReadService = class {
1080
+ constructor(context) {
1081
+ this.context = context;
1082
+ }
1083
+ getCapabilities() {
1084
+ return success("read", Date.now(), structuredClone(this.context.capabilities));
1085
+ }
1086
+ getSession(input) {
1087
+ const startedAt = Date.now();
1088
+ const session = this.context.sessions.get(input.sessionId);
1089
+ if (!session || session.closed) return failure("read", startedAt, createSessionNotFoundError(input.sessionId));
1090
+ return success("read", startedAt, toSessionSnapshot(session, this.context.capabilities), {
1091
+ sessionId: session.sessionId,
1092
+ revision: session.revision
1093
+ });
1094
+ }
1095
+ };
1096
+ //#endregion
1097
+ //#region src/services/runtime-service.ts
1098
+ function randomId() {
1099
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1100
+ }
1101
+ function createCapabilityUnavailableError(capability) {
1102
+ const artifactCapability = capability === "artifact.publish" || capability === "artifact.restore";
1103
+ return {
1104
+ code: "capability_unavailable",
1105
+ message: artifactCapability ? `${capability} requires the Node bridge boundary exposed by @openfairygui/backend/node.` : `${capability} requires an injected BackendFileSystem adapter.`,
1106
+ capability,
1107
+ requiredAdapter: capability === "fileSystem" ? "BackendFileSystem" : void 0,
1108
+ requiredHost: artifactCapability ? "node" : void 0,
1109
+ bridgeBoundary: artifactCapability ? "external-bridge" : void 0
1110
+ };
1111
+ }
1112
+ function createProjectReaderFileSystem(fileSystem) {
1113
+ return {
1114
+ readFile(filePath) {
1115
+ return fileSystem.readFile(filePath);
1116
+ },
1117
+ readFileRaw(filePath) {
1118
+ return fileSystem.readFileRaw(filePath);
1119
+ },
1120
+ writeFile(filePath, content) {
1121
+ return fileSystem.writeFile(filePath, content);
1122
+ },
1123
+ writeFileRaw(filePath, data) {
1124
+ return fileSystem.writeFileRaw(filePath, data);
1125
+ },
1126
+ async mkdir(dirPath) {
1127
+ await fileSystem.mkdir(dirPath, { recursive: true });
1128
+ },
1129
+ readdir(dirPath) {
1130
+ return fileSystem.readdir(dirPath);
1131
+ },
1132
+ async exists(filePath) {
1133
+ try {
1134
+ await fileSystem.stat(filePath);
1135
+ return true;
1136
+ } catch {
1137
+ return false;
1138
+ }
1139
+ },
1140
+ join(...paths) {
1141
+ return fileSystem.join(...paths);
1142
+ },
1143
+ dirname(filePath) {
1144
+ return fileSystem.dirname(filePath);
1145
+ }
1146
+ };
1147
+ }
1148
+ function createCaptureFileSystem(files, directories) {
1149
+ const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
1150
+ return {
1151
+ async readFile(filePath) {
1152
+ const value = files.get(normalize(filePath));
1153
+ if (typeof value !== "string") throw new Error(`Captured text file was not found: ${filePath}`);
1154
+ return value;
1155
+ },
1156
+ async readFileRaw(filePath) {
1157
+ const value = files.get(normalize(filePath));
1158
+ if (!(value instanceof Uint8Array)) throw new Error(`Captured binary file was not found: ${filePath}`);
1159
+ return value.slice();
1160
+ },
1161
+ async writeFile(filePath, content) {
1162
+ files.set(normalize(filePath), content);
1163
+ },
1164
+ async writeFileRaw(filePath, data) {
1165
+ files.set(normalize(filePath), data.slice());
1166
+ },
1167
+ async mkdir(dirPath) {
1168
+ directories.add(normalize(dirPath));
1169
+ },
1170
+ async readdir() {
1171
+ return [];
1172
+ },
1173
+ async exists(filePath) {
1174
+ return files.has(normalize(filePath));
1175
+ },
1176
+ join(...paths) {
1177
+ return normalize(paths.filter(Boolean).join("/"));
1178
+ },
1179
+ dirname(filePath) {
1180
+ const normalized = normalize(filePath);
1181
+ const separator = normalized.lastIndexOf("/");
1182
+ return separator < 0 ? "" : normalized.slice(0, separator);
1183
+ },
1184
+ async unlink(filePath) {
1185
+ files.delete(normalize(filePath));
1186
+ }
1187
+ };
1188
+ }
1189
+ function capturedFilesEqual(left, right) {
1190
+ if (left.size !== right.size) return false;
1191
+ for (const [filePath, leftValue] of left) {
1192
+ const rightValue = right.get(filePath);
1193
+ if (typeof leftValue === "string") {
1194
+ if (leftValue !== rightValue) return false;
1195
+ continue;
1196
+ }
1197
+ if (!(rightValue instanceof Uint8Array) || leftValue.length !== rightValue.length) return false;
1198
+ for (let index = 0; index < leftValue.length; index += 1) if (leftValue[index] !== rightValue[index]) return false;
1199
+ }
1200
+ return true;
1201
+ }
1202
+ function capturedDirectoriesEqual(left, right) {
1203
+ return left.size === right.size && [...left].every((directory) => right.has(directory));
1204
+ }
1205
+ async function hasFullUamFidelity(document, project) {
1206
+ const sourceFiles = /* @__PURE__ */ new Map();
1207
+ const materializedFiles = /* @__PURE__ */ new Map();
1208
+ const sourceDirectories = /* @__PURE__ */ new Set();
1209
+ const materializedDirectories = /* @__PURE__ */ new Set();
1210
+ try {
1211
+ await Promise.all([new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(sourceFiles, sourceDirectories)).write(document, "Project.fairy"), new _openfairygui_core_project_io.ProjectWriter(createCaptureFileSystem(materializedFiles, materializedDirectories)).write((0, _openfairygui_core_uam.materializeUamProject)(project), "Project.fairy")]);
1212
+ } catch {
1213
+ return false;
1214
+ }
1215
+ return capturedFilesEqual(sourceFiles, materializedFiles) && capturedDirectoriesEqual(sourceDirectories, materializedDirectories);
1216
+ }
1217
+ var RuntimeService = class {
1218
+ constructor(context, cacheService, eventService, jobService) {
1219
+ this.context = context;
1220
+ this.cacheService = cacheService;
1221
+ this.eventService = eventService;
1222
+ this.jobService = jobService;
1223
+ }
1224
+ async openSession(input) {
1225
+ const startedAt = Date.now();
1226
+ if (!this.context.fileSystem) return failure("runtime", startedAt, createCapabilityUnavailableError("fileSystem"));
1227
+ const fileSystem = this.context.fileSystem;
1228
+ const { fairyPath, canonicalProjectPath, canonicalPathKey } = await resolveCanonicalProjectRoot(fileSystem, input.projectPath);
1229
+ const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
1230
+ const lockFilePath = fileSystem.join(canonicalProjectPath, ".openfairygui.backend.lock");
1231
+ if (existingSessionId) return failure("runtime", startedAt, {
1232
+ code: "lock_conflict",
1233
+ kind: "in_process_session_exists",
1234
+ message: `Project is already open in this backend runtime: ${canonicalProjectPath}`,
1235
+ canonicalPathKey,
1236
+ holderSessionId: existingSessionId,
1237
+ lockFilePath
1238
+ });
1239
+ let sessionLock = null;
1240
+ try {
1241
+ sessionLock = await fileSystem.acquireSessionLock(lockFilePath);
1242
+ await sessionLock.writeMetadata(JSON.stringify(this.context.host?.lockMetadata?.({
1243
+ canonicalPathKey,
1244
+ canonicalProjectPath,
1245
+ lockFilePath
1246
+ }) ?? {
1247
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1248
+ canonicalPathKey
1249
+ }));
1250
+ const document = await new _openfairygui_core_project_io.ProjectReader(createProjectReaderFileSystem(fileSystem)).read(fairyPath, { hydrateResourceBytes: true });
1251
+ const project = (0, _openfairygui_core_uam.liftDocumentToUamProject)(document);
1252
+ const sessionId = randomId();
1253
+ const session = {
1254
+ sessionId,
1255
+ fairyPath,
1256
+ canonicalProjectPath,
1257
+ canonicalPathKey,
1258
+ lockFilePath,
1259
+ sessionLock,
1260
+ fileSystem,
1261
+ project,
1262
+ uamFidelity: await hasFullUamFidelity(document, project) ? "full" : "unsupported",
1263
+ revision: 0,
1264
+ lastSavedRevision: 0,
1265
+ pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1266
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1267
+ pendingStaleBranchDirectories: /* @__PURE__ */ new Map(),
1268
+ dirty: false,
1269
+ lockHeld: true,
1270
+ closed: false
1271
+ };
1272
+ this.context.sessions.set(sessionId, session);
1273
+ this.context.sessionsByPath.set(canonicalPathKey, sessionId);
1274
+ this.cacheService.refreshSession(session);
1275
+ this.eventService.emit({
1276
+ kind: "session.opened",
1277
+ sessionId,
1278
+ canonicalPathKey,
1279
+ revision: session.revision
1280
+ });
1281
+ return success("runtime", startedAt, toSessionSnapshot(session, this.context.capabilities), {
1282
+ sessionId: session.sessionId,
1283
+ revision: session.revision
1284
+ });
1285
+ } catch (error) {
1286
+ if (sessionLock) await sessionLock.release().catch(() => void 0);
1287
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST") return failure("runtime", startedAt, {
1288
+ code: "lock_conflict",
1289
+ kind: "advisory_lock_conflict",
1290
+ message: `Advisory lock already exists for project: ${canonicalProjectPath}`,
1291
+ canonicalPathKey,
1292
+ lockFilePath
1293
+ });
1294
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOTSUP") return failure("runtime", startedAt, {
1295
+ ...createCapabilityUnavailableError("fileSystem"),
1296
+ message: error instanceof Error ? error.message : String(error)
1297
+ });
1298
+ throw error;
1299
+ }
1300
+ }
1301
+ openProjectSession(input) {
1302
+ const startedAt = Date.now();
1303
+ const sessionId = input.sessionId ?? randomId();
1304
+ const storage = input.storage;
1305
+ const memoryProjectPath = `memory://${sessionId}`;
1306
+ const canonicalProjectPath = storage?.canonicalProjectPath ?? input.canonicalProjectPath ?? (storage ? storage.fileSystem.dirname(storage.fairyPath) || "." : memoryProjectPath);
1307
+ const canonicalPathKey = storage?.canonicalPathKey ?? input.canonicalPathKey ?? (storage ? normalizeComparablePath(canonicalProjectPath) : canonicalProjectPath.toLowerCase());
1308
+ const existingSessionId = this.context.sessionsByPath.get(canonicalPathKey);
1309
+ if (existingSessionId) return failure("runtime", startedAt, {
1310
+ code: "lock_conflict",
1311
+ kind: "in_process_session_exists",
1312
+ message: `Project is already open in this backend runtime: ${canonicalProjectPath}`,
1313
+ canonicalPathKey,
1314
+ holderSessionId: existingSessionId
1315
+ });
1316
+ const session = {
1317
+ sessionId,
1318
+ fairyPath: storage?.fairyPath ?? canonicalProjectPath,
1319
+ canonicalProjectPath,
1320
+ canonicalPathKey,
1321
+ lockFilePath: "",
1322
+ sessionLock: null,
1323
+ fileSystem: storage?.fileSystem,
1324
+ project: (0, _openfairygui_core_uam.normalizeUamProject)(input.project),
1325
+ uamFidelity: "full",
1326
+ revision: 0,
1327
+ lastSavedRevision: 0,
1328
+ pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1329
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1330
+ pendingStaleBranchDirectories: /* @__PURE__ */ new Map(),
1331
+ dirty: false,
1332
+ lockHeld: false,
1333
+ closed: false
1334
+ };
1335
+ this.context.sessions.set(sessionId, session);
1336
+ this.context.sessionsByPath.set(canonicalPathKey, sessionId);
1337
+ this.cacheService.refreshSession(session);
1338
+ this.eventService.emit({
1339
+ kind: "session.opened",
1340
+ sessionId,
1341
+ canonicalPathKey,
1342
+ revision: session.revision
1343
+ });
1344
+ return success("runtime", startedAt, toSessionSnapshot(session, this.context.capabilities), {
1345
+ sessionId: session.sessionId,
1346
+ revision: session.revision
1347
+ });
1348
+ }
1349
+ async closeSession(input) {
1350
+ const startedAt = Date.now();
1351
+ const session = this.context.sessions.get(input.sessionId);
1352
+ if (!session || session.closed) return failure("runtime", startedAt, createSessionNotFoundError(input.sessionId));
1353
+ this.eventService.emit({
1354
+ kind: "session.closeRequested",
1355
+ sessionId: session.sessionId,
1356
+ canonicalPathKey: session.canonicalPathKey,
1357
+ revision: session.revision
1358
+ });
1359
+ await session.sessionLock?.release().catch(() => void 0);
1360
+ session.sessionLock = null;
1361
+ session.lockHeld = false;
1362
+ session.closed = true;
1363
+ this.context.sessions.delete(session.sessionId);
1364
+ this.context.sessionsByPath.delete(session.canonicalPathKey);
1365
+ this.cacheService.removeSession(session.sessionId);
1366
+ this.jobService.removeSession(session.sessionId);
1367
+ this.eventService.emit({
1368
+ kind: "session.closed",
1369
+ sessionId: session.sessionId,
1370
+ canonicalPathKey: session.canonicalPathKey,
1371
+ revision: session.revision
1372
+ });
1373
+ this.eventService.removeSession(session.sessionId);
1374
+ return success("runtime", startedAt, {
1375
+ sessionId: session.sessionId,
1376
+ closed: true
1377
+ }, {
1378
+ sessionId: session.sessionId,
1379
+ revision: session.revision
1380
+ });
1381
+ }
1382
+ };
1383
+ //#endregion
1384
+ //#region src/services/artifact-service.ts
1385
+ function createArtifactCapabilities() {
1386
+ const bridge = {
1387
+ available: false,
1388
+ requiredHost: "node",
1389
+ executionBoundary: "external-bridge",
1390
+ bridgeEntrypoint: "@openfairygui/backend/node",
1391
+ reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
1392
+ };
1393
+ return {
1394
+ publish: false,
1395
+ restore: false,
1396
+ status: "bridge-required",
1397
+ publishBridge: bridge,
1398
+ restoreBridge: bridge
1399
+ };
1400
+ }
1401
+ //#endregion
1402
+ //#region src/runtime/capabilities.ts
1403
+ const BACKEND_METHODS = [
1404
+ "getCapabilities",
1405
+ "openSession",
1406
+ "openProjectSession",
1407
+ "getSession",
1408
+ "applyTransaction",
1409
+ "saveSession",
1410
+ "materializeSession",
1411
+ "closeSession",
1412
+ "getEvents",
1413
+ "getJob",
1414
+ "listJobs",
1415
+ "cancelJob",
1416
+ "getCacheSnapshot",
1417
+ "refreshCache"
1418
+ ];
1419
+ const ARTIFACT_BRIDGE_CAPABILITY = {
1420
+ available: false,
1421
+ requiredHost: "node",
1422
+ executionBoundary: "external-bridge",
1423
+ bridgeEntrypoint: "@openfairygui/backend/node",
1424
+ reason: "publish/restore require explicit Node-hosted filesystem and artifact execution."
1425
+ };
1426
+ function createCapabilities() {
1427
+ return {
1428
+ contractVersion: BACKEND_CONTRACT_VERSION,
1429
+ capabilitySchemaVersion: 2,
1430
+ transactionKernelOwner: "@openfairygui/core",
1431
+ appSeamOwner: "@openfairygui/functions",
1432
+ runtimeOwner: "@openfairygui/backend",
1433
+ methods: BACKEND_METHODS,
1434
+ read: {
1435
+ capabilitySnapshot: true,
1436
+ sessionSnapshot: true
1437
+ },
1438
+ authoring: {
1439
+ applyTransaction: true,
1440
+ saveSession: true,
1441
+ resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.resourceKinds],
1442
+ nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.nodeKinds],
1443
+ gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_MATERIALIZATION_SCOPE.gearKinds],
1444
+ transactionScope: {
1445
+ resourceKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.resourceKinds],
1446
+ nodeKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.nodeKinds],
1447
+ gearKinds: [..._openfairygui_core_uam.UAM_SUPPORTED_TRANSACTION_SCOPE.gearKinds]
1448
+ },
1449
+ unsupported: ["artifact.publish", "artifact.restore"]
1450
+ },
1451
+ artifact: createArtifactCapabilities(),
1452
+ manifest: {
1453
+ browserSafe: true,
1454
+ rootEntrypoint: "@openfairygui/backend",
1455
+ nodeEntrypoint: "@openfairygui/backend/node",
1456
+ adapters: {
1457
+ fileSystem: {
1458
+ injected: true,
1459
+ requiredFor: [
1460
+ "openSession",
1461
+ "saveSession",
1462
+ "materializeSession"
1463
+ ]
1464
+ },
1465
+ projectStorage: {
1466
+ injected: true,
1467
+ browserSafe: true,
1468
+ requiredFor: [
1469
+ "openProjectSession.writeback",
1470
+ "saveSession",
1471
+ "materializeSession"
1472
+ ],
1473
+ adapterFactory: "createBackendStorageFileSystem"
1474
+ },
1475
+ host: {
1476
+ injected: true,
1477
+ requiredFor: ["advisoryLockMetadata"]
1478
+ }
1479
+ },
1480
+ executionBoundaries: {
1481
+ projectSession: "in-process-browser-safe",
1482
+ fileBackedSession: "adapter-backed",
1483
+ artifactPublish: ARTIFACT_BRIDGE_CAPABILITY,
1484
+ artifactRestore: ARTIFACT_BRIDGE_CAPABILITY
1485
+ },
1486
+ diagnostics: {
1487
+ stableCodes: true,
1488
+ errorDiagnosticMirror: true
1489
+ }
1490
+ },
1491
+ compatibilityPolicy: BACKEND_COMPATIBILITY_POLICY,
1492
+ runtime: {
1493
+ sessionRuntime: true,
1494
+ advisoryLocking: true,
1495
+ coordinatedSave: true,
1496
+ atomicSave: false,
1497
+ staleRevisionProtection: true,
1498
+ pathPolicy: createRuntimePathPolicy(),
1499
+ events: {
1500
+ polling: true,
1501
+ subscriptions: false,
1502
+ retentionLimit: 1e3,
1503
+ sequenceScope: "runtime"
1504
+ },
1505
+ jobs: {
1506
+ inMemory: true,
1507
+ cooperativeCancel: true,
1508
+ persistent: false,
1509
+ supportedKinds: ["cache.refresh"],
1510
+ artifactJobs: false,
1511
+ completedRetentionLimit: 100
1512
+ },
1513
+ cache: {
1514
+ derivedReadOnly: true,
1515
+ keyedBy: "canonicalPathKey",
1516
+ sourceOfTruth: false,
1517
+ refreshMethod: "refreshCache"
1518
+ }
1519
+ }
1520
+ };
1521
+ }
1522
+ //#endregion
1523
+ //#region src/runtime.ts
1524
+ var BackendRuntime = class {
1525
+ fileSystem;
1526
+ capabilities;
1527
+ sessions = /* @__PURE__ */ new Map();
1528
+ sessionsByPath = /* @__PURE__ */ new Map();
1529
+ eventsBySession = /* @__PURE__ */ new Map();
1530
+ jobsBySession = /* @__PURE__ */ new Map();
1531
+ cacheBySession = /* @__PURE__ */ new Map();
1532
+ eventSequence = 0;
1533
+ context;
1534
+ readService;
1535
+ runtimeService;
1536
+ authoringService;
1537
+ cacheService;
1538
+ eventService;
1539
+ jobService;
1540
+ constructor(options = {}) {
1541
+ this.fileSystem = options.fileSystem;
1542
+ this.capabilities = createCapabilities();
1543
+ this.context = {
1544
+ fileSystem: this.fileSystem,
1545
+ host: options.host,
1546
+ capabilities: this.capabilities,
1547
+ sessions: this.sessions,
1548
+ sessionsByPath: this.sessionsByPath,
1549
+ eventsBySession: this.eventsBySession,
1550
+ jobsBySession: this.jobsBySession,
1551
+ cacheBySession: this.cacheBySession,
1552
+ nextEventSequence: () => {
1553
+ this.eventSequence += 1;
1554
+ return this.eventSequence;
1555
+ }
1556
+ };
1557
+ this.readService = new ReadService(this.context);
1558
+ this.eventService = new EventService(this.context);
1559
+ this.cacheService = new CacheService(this.context);
1560
+ this.jobService = new JobService(this.context, this.cacheService, this.eventService);
1561
+ this.runtimeService = new RuntimeService(this.context, this.cacheService, this.eventService, this.jobService);
1562
+ this.authoringService = new AuthoringService(this.context, this.cacheService, this.eventService);
1563
+ }
1564
+ getCapabilities() {
1565
+ return this.readService.getCapabilities();
1566
+ }
1567
+ async openSession(input) {
1568
+ return this.runtimeService.openSession(input);
1569
+ }
1570
+ openProjectSession(input) {
1571
+ return this.runtimeService.openProjectSession(input);
1572
+ }
1573
+ getSession(input) {
1574
+ return this.readService.getSession(input);
1575
+ }
1576
+ async applyTransaction(input) {
1577
+ return this.authoringService.applyTransaction(input);
1578
+ }
1579
+ async saveSession(input) {
1580
+ return this.authoringService.saveSession(input);
1581
+ }
1582
+ async materializeSession(input) {
1583
+ return this.authoringService.materializeSession(input);
1584
+ }
1585
+ async closeSession(input) {
1586
+ return this.runtimeService.closeSession(input);
1587
+ }
1588
+ getEvents(input) {
1589
+ return this.eventService.getEvents(input);
1590
+ }
1591
+ getJob(input) {
1592
+ return this.jobService.getJob(input);
1593
+ }
1594
+ listJobs(input) {
1595
+ return this.jobService.listJobs(input);
1596
+ }
1597
+ cancelJob(input) {
1598
+ return this.jobService.cancelJob(input);
1599
+ }
1600
+ getCacheSnapshot(input) {
1601
+ return this.cacheService.getCacheSnapshot(input);
1602
+ }
1603
+ refreshCache(input) {
1604
+ return this.jobService.refreshCache(input);
1605
+ }
1606
+ };
1607
+ //#endregion
29
1608
  //#region src/node.ts
30
1609
  function createNodeBackendFileSystem() {
31
1610
  return {
@@ -58,20 +1637,36 @@ function createNodeBackendFileSystem() {
58
1637
  return node_path.default.resolve(filePath);
59
1638
  }
60
1639
  },
61
- async openExclusive(filePath) {
1640
+ async acquireSessionLock(filePath) {
62
1641
  const handle = await node_fs_promises.default.open(filePath, "wx");
1642
+ let closed = false;
1643
+ let released = false;
1644
+ const closeHandle = async () => {
1645
+ if (closed) return;
1646
+ await handle.close();
1647
+ closed = true;
1648
+ };
63
1649
  return {
64
- writeFile(content) {
65
- return handle.writeFile(content, "utf-8");
1650
+ async writeMetadata(content) {
1651
+ await handle.writeFile(content, "utf-8");
1652
+ await closeHandle();
66
1653
  },
67
- close() {
68
- return handle.close();
1654
+ async release() {
1655
+ if (released) return;
1656
+ await closeHandle();
1657
+ await node_fs_promises.default.unlink(filePath).catch((error) => {
1658
+ if (error.code !== "ENOENT") throw error;
1659
+ });
1660
+ released = true;
69
1661
  }
70
1662
  };
71
1663
  },
72
1664
  unlink(filePath) {
73
1665
  return node_fs_promises.default.unlink(filePath);
74
1666
  },
1667
+ rmdir(dirPath) {
1668
+ return node_fs_promises.default.rmdir(dirPath);
1669
+ },
75
1670
  join(...paths) {
76
1671
  return node_path.default.join(...paths);
77
1672
  },
@@ -93,15 +1688,14 @@ function createNodeBackendHostAdapter() {
93
1688
  } };
94
1689
  }
95
1690
  function createNodeBackendRuntime(options = {}) {
96
- return new require_runtime.BackendRuntime({
1691
+ return new BackendRuntime({
97
1692
  ...options,
98
1693
  fileSystem: options.fileSystem ?? createNodeBackendFileSystem(),
99
1694
  host: options.host ?? createNodeBackendHostAdapter()
100
1695
  });
101
1696
  }
102
1697
  //#endregion
103
- exports.BackendRuntime = require_runtime.BackendRuntime;
104
- exports.__toESM = __toESM;
1698
+ exports.BackendRuntime = BackendRuntime;
105
1699
  exports.createNodeBackendFileSystem = createNodeBackendFileSystem;
106
1700
  exports.createNodeBackendHostAdapter = createNodeBackendHostAdapter;
107
1701
  exports.createNodeBackendRuntime = createNodeBackendRuntime;