@openfairygui/backend 0.2.0-alpha.29 → 0.2.0-alpha.30

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/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_runtime = require("./runtime-BxbnisWJ.cjs");
2
+ const require_runtime = require("./runtime-CcBLI-J7.cjs");
3
3
  //#region src/storage.ts
4
4
  var StorageFileStat = class {
5
5
  constructor(kind) {
@@ -72,6 +72,7 @@ async function inferStat(storage, filePath) {
72
72
  }
73
73
  function createBackendStorageFileSystem(storage) {
74
74
  if (typeof storage.unlink !== "function") throw createPathError("ENOTSUP", "Storage adapter must provide unlink() for project resource lifecycle writes.");
75
+ if (typeof storage.rmdir !== "function") throw createPathError("ENOTSUP", "Storage adapter must provide rmdir() for project resource folder lifecycle writes.");
75
76
  const lockedPaths = /* @__PURE__ */ new Set();
76
77
  const fileSystem = {
77
78
  stat(filePath) {
@@ -131,6 +132,9 @@ function createBackendStorageFileSystem(storage) {
131
132
  lockedPaths.delete(resolved);
132
133
  return storage.unlink(resolved);
133
134
  },
135
+ rmdir(dirPath) {
136
+ return storage.rmdir(fileSystem.resolve(dirPath));
137
+ },
134
138
  join(...paths) {
135
139
  return storage.join ? storage.join(...paths) : joinStoragePath(...paths);
136
140
  },
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as BACKEND_COMPATIBILITY_POLICY, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobNotFoundError, D as BackendProjectSessionStorage, E as BackendJobStatus, 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 BackendResult, P as EventCursorInvalidError, Q as BACKEND_CAPABILITY_SCHEMA_VERSION, R as GetJobInput, S as BackendJobNotCancellableError, T as BackendJobSnapshot, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as UamFidelityUnsupportedError, _ as BackendHostAdapter, a as BackendCacheEntry, b as BackendJobListSnapshot, c as BackendCapabilityManifest, d as BackendEvent, et as BACKEND_CONTRACT_VERSION, f as BackendEventKind, g as BackendFileSystem, h as BackendFileStat, i as BackendArtifactBridgeCapability, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendCapabilityUnavailableError, m as BackendFileHandle, n as AdvisoryLockConflictError, nt as BackendMessage, o as BackendCacheSnapshot, p as BackendFailure, q as SavePartialFailureError, r as ApplySessionTransactionInput, rt as BackendResponseMeta, s as BackendCapabilities, t as BackendRuntime, tt as BackendDiagnostic, u as BackendError, v as BackendJobErrors, w as BackendJobProgress, x as BackendJobListStatusFilter, y as BackendJobKind, z as InProcessLockConflictError } from "./runtime-BB_3Ws5y.cjs";
1
+ import { $ as BACKEND_COMPATIBILITY_POLICY, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobNotFoundError, D as BackendProjectSessionStorage, E as BackendJobStatus, 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 BackendResult, P as EventCursorInvalidError, Q as BACKEND_CAPABILITY_SCHEMA_VERSION, R as GetJobInput, S as BackendJobNotCancellableError, T as BackendJobSnapshot, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as UamFidelityUnsupportedError, _ as BackendHostAdapter, a as BackendCacheEntry, b as BackendJobListSnapshot, c as BackendCapabilityManifest, d as BackendEvent, et as BACKEND_CONTRACT_VERSION, f as BackendEventKind, g as BackendFileSystem, h as BackendFileStat, i as BackendArtifactBridgeCapability, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendCapabilityUnavailableError, m as BackendFileHandle, n as AdvisoryLockConflictError, nt as BackendMessage, o as BackendCacheSnapshot, p as BackendFailure, q as SavePartialFailureError, r as ApplySessionTransactionInput, rt as BackendResponseMeta, s as BackendCapabilities, t as BackendRuntime, tt as BackendDiagnostic, u as BackendError, v as BackendJobErrors, w as BackendJobProgress, x as BackendJobListStatusFilter, y as BackendJobKind, z as InProcessLockConflictError } from "./runtime-Wu6vGZ-j.cjs";
2
2
  import { FileSystem } from "@openfairygui/core/project-io";
3
3
 
4
4
  //#region src/storage.d.ts
@@ -23,6 +23,7 @@ interface BackendAsyncStorageAdapter {
23
23
  resolvePath?(filePath: string): Promise<string>;
24
24
  openExclusive?(filePath: string): Promise<BackendFileHandle>;
25
25
  unlink(filePath: string): Promise<void>;
26
+ rmdir(dirPath: string): Promise<void>;
26
27
  join?(...paths: string[]): string;
27
28
  dirname?(filePath: string): string;
28
29
  resolve?(...paths: string[]): string;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as BACKEND_COMPATIBILITY_POLICY, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobNotFoundError, D as BackendProjectSessionStorage, E as BackendJobStatus, 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 BackendResult, P as EventCursorInvalidError, Q as BACKEND_CAPABILITY_SCHEMA_VERSION, R as GetJobInput, S as BackendJobNotCancellableError, T as BackendJobSnapshot, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as UamFidelityUnsupportedError, _ as BackendHostAdapter, a as BackendCacheEntry, b as BackendJobListSnapshot, c as BackendCapabilityManifest, d as BackendEvent, et as BACKEND_CONTRACT_VERSION, f as BackendEventKind, g as BackendFileSystem, h as BackendFileStat, i as BackendArtifactBridgeCapability, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendCapabilityUnavailableError, m as BackendFileHandle, n as AdvisoryLockConflictError, nt as BackendMessage, o as BackendCacheSnapshot, p as BackendFailure, q as SavePartialFailureError, r as ApplySessionTransactionInput, rt as BackendResponseMeta, s as BackendCapabilities, t as BackendRuntime, tt as BackendDiagnostic, u as BackendError, v as BackendJobErrors, w as BackendJobProgress, x as BackendJobListStatusFilter, y as BackendJobKind, z as InProcessLockConflictError } from "./runtime-B4SwHTRC.mjs";
1
+ import { $ as BACKEND_COMPATIBILITY_POLICY, A as BackendSessionSnapshot, B as ListJobsInput, C as BackendJobNotFoundError, D as BackendProjectSessionStorage, E as BackendJobStatus, 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 BackendResult, P as EventCursorInvalidError, Q as BACKEND_CAPABILITY_SCHEMA_VERSION, R as GetJobInput, S as BackendJobNotCancellableError, T as BackendJobSnapshot, U as MaterializeValidationFailedError, V as MaterializeSessionInput, W as MaterializeWriteFailedError, X as SessionStaleWriteError, Y as SessionNotFoundError, Z as UamFidelityUnsupportedError, _ as BackendHostAdapter, a as BackendCacheEntry, b as BackendJobListSnapshot, c as BackendCapabilityManifest, d as BackendEvent, et as BACKEND_CONTRACT_VERSION, f as BackendEventKind, g as BackendFileSystem, h as BackendFileStat, i as BackendArtifactBridgeCapability, it as BackendStage, j as BackendSuccess, k as BackendRuntimeOptions, l as BackendCapabilityUnavailableError, m as BackendFileHandle, n as AdvisoryLockConflictError, nt as BackendMessage, o as BackendCacheSnapshot, p as BackendFailure, q as SavePartialFailureError, r as ApplySessionTransactionInput, rt as BackendResponseMeta, s as BackendCapabilities, t as BackendRuntime, tt as BackendDiagnostic, u as BackendError, v as BackendJobErrors, w as BackendJobProgress, x as BackendJobListStatusFilter, y as BackendJobKind, z as InProcessLockConflictError } from "./runtime-BQOEw22M.mjs";
2
2
  import { FileSystem } from "@openfairygui/core/project-io";
3
3
 
4
4
  //#region src/storage.d.ts
@@ -23,6 +23,7 @@ interface BackendAsyncStorageAdapter {
23
23
  resolvePath?(filePath: string): Promise<string>;
24
24
  openExclusive?(filePath: string): Promise<BackendFileHandle>;
25
25
  unlink(filePath: string): Promise<void>;
26
+ rmdir(dirPath: string): Promise<void>;
26
27
  join?(...paths: string[]): string;
27
28
  dirname?(filePath: string): string;
28
29
  resolve?(...paths: string[]): string;
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-griAoBRA.mjs";
1
+ import { i as BACKEND_CONTRACT_VERSION, n as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BACKEND_COMPATIBILITY_POLICY, t as BackendRuntime } from "./runtime-NiBgr7PF.mjs";
2
2
  //#region src/storage.ts
3
3
  var StorageFileStat = class {
4
4
  constructor(kind) {
@@ -71,6 +71,7 @@ async function inferStat(storage, filePath) {
71
71
  }
72
72
  function createBackendStorageFileSystem(storage) {
73
73
  if (typeof storage.unlink !== "function") throw createPathError("ENOTSUP", "Storage adapter must provide unlink() for project resource lifecycle writes.");
74
+ if (typeof storage.rmdir !== "function") throw createPathError("ENOTSUP", "Storage adapter must provide rmdir() for project resource folder lifecycle writes.");
74
75
  const lockedPaths = /* @__PURE__ */ new Set();
75
76
  const fileSystem = {
76
77
  stat(filePath) {
@@ -130,6 +131,9 @@ function createBackendStorageFileSystem(storage) {
130
131
  lockedPaths.delete(resolved);
131
132
  return storage.unlink(resolved);
132
133
  },
134
+ rmdir(dirPath) {
135
+ return storage.rmdir(fileSystem.resolve(dirPath));
136
+ },
133
137
  join(...paths) {
134
138
  return storage.join ? storage.join(...paths) : joinStoragePath(...paths);
135
139
  },
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-BxbnisWJ.cjs");
24
+ const require_runtime = require("./runtime-CcBLI-J7.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");
@@ -72,6 +72,9 @@ function createNodeBackendFileSystem() {
72
72
  unlink(filePath) {
73
73
  return node_fs_promises.default.unlink(filePath);
74
74
  },
75
+ rmdir(dirPath) {
76
+ return node_fs_promises.default.rmdir(dirPath);
77
+ },
75
78
  join(...paths) {
76
79
  return node_path.default.join(...paths);
77
80
  },
package/dist/node.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as BackendHostAdapter, g as BackendFileSystem, h as BackendFileStat, k as BackendRuntimeOptions, m as BackendFileHandle, t as BackendRuntime } from "./runtime-BB_3Ws5y.cjs";
1
+ import { _ as BackendHostAdapter, g as BackendFileSystem, h as BackendFileStat, k as BackendRuntimeOptions, m as BackendFileHandle, t as BackendRuntime } from "./runtime-Wu6vGZ-j.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 { _ as BackendHostAdapter, g as BackendFileSystem, h as BackendFileStat, k as BackendRuntimeOptions, m as BackendFileHandle, t as BackendRuntime } from "./runtime-B4SwHTRC.mjs";
1
+ import { _ as BackendHostAdapter, g as BackendFileSystem, h as BackendFileStat, k as BackendRuntimeOptions, m as BackendFileHandle, t as BackendRuntime } from "./runtime-BQOEw22M.mjs";
2
2
 
3
3
  //#region src/node.d.ts
4
4
  declare function createNodeBackendFileSystem(): BackendFileSystem;
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BackendRuntime } from "./runtime-griAoBRA.mjs";
1
+ import { t as BackendRuntime } from "./runtime-NiBgr7PF.mjs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  //#region src/node.ts
@@ -47,6 +47,9 @@ function createNodeBackendFileSystem() {
47
47
  unlink(filePath) {
48
48
  return fs.unlink(filePath);
49
49
  },
50
+ rmdir(dirPath) {
51
+ return fs.rmdir(dirPath);
52
+ },
50
53
  join(...paths) {
51
54
  return path.join(...paths);
52
55
  },
@@ -70,6 +70,7 @@ interface BackendFileSystem {
70
70
  resolvePath(filePath: string): Promise<string>;
71
71
  openExclusive(filePath: string): Promise<BackendFileHandle>;
72
72
  unlink(filePath: string): Promise<void>;
73
+ rmdir(dirPath: string): Promise<void>;
73
74
  join(...paths: string[]): string;
74
75
  dirname(filePath: string): string;
75
76
  resolve(...paths: string[]): string;
@@ -155,11 +155,15 @@ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
155
155
  },
156
156
  join: (...paths) => fileSystem.join(...paths),
157
157
  dirname: (path) => fileSystem.dirname(path),
158
- unlink: (path) => trackWrite(path, () => fileSystem.unlink(path))
158
+ unlink: (path) => trackWrite(path, () => fileSystem.unlink(path)),
159
+ rmdir: (path) => trackWrite(path, () => fileSystem.rmdir(path))
159
160
  };
160
161
  }
161
162
  async function writeSessionProject(input) {
162
- await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, { staleSourceFiles: input.staleSourceFiles });
163
+ await new _openfairygui_core_project_io.ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, {
164
+ staleSourceFiles: input.staleSourceFiles,
165
+ staleResourceFolders: input.staleResourceFolders
166
+ });
163
167
  }
164
168
  //#endregion
165
169
  //#region src/services/snapshot-utils.ts
@@ -238,6 +242,7 @@ function projectSourceFiles(project) {
238
242
  fileName: "package.xml"
239
243
  });
240
244
  const branches = /* @__PURE__ */ new Set();
245
+ for (const folder of pkg.folders) if (folder.branch) branches.add(folder.branch);
241
246
  for (const resource of pkg.resources) {
242
247
  if (resource.branch) branches.add(resource.branch);
243
248
  const fileName = resource.kind === "component" ? `${resource.name}.xml` : resource.fileName ?? (resource.kind === "image" ? "" : resource.file) ?? "";
@@ -266,6 +271,22 @@ function sourceFileKey(source) {
266
271
  source.fileName
267
272
  ].join("\0");
268
273
  }
274
+ function projectResourceFolders(project) {
275
+ const result = /* @__PURE__ */ new Map();
276
+ for (const pkg of project.packages) for (const folder of pkg.folders) result.set(`${pkg.id}/${folder.branch}/${folder.path}`, {
277
+ packageName: pkg.name,
278
+ branch: folder.branch,
279
+ path: folder.path
280
+ });
281
+ return result;
282
+ }
283
+ function resourceFolderKey(folder) {
284
+ return [
285
+ folder.branch,
286
+ folder.packageName,
287
+ folder.path
288
+ ].join("\0");
289
+ }
269
290
  function recordStaleProjectFiles(session, previousProject, nextProject) {
270
291
  if (!session.fileSystem) return;
271
292
  const previousSources = projectSourceFiles(previousProject);
@@ -275,6 +296,13 @@ function recordStaleProjectFiles(session, previousProject, nextProject) {
275
296
  if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
276
297
  }
277
298
  for (const key of nextSourceKeys) session.pendingStaleSourceFiles.delete(key);
299
+ const previousFolders = projectResourceFolders(previousProject);
300
+ const nextFolderKeys = new Set([...projectResourceFolders(nextProject).values()].map(resourceFolderKey));
301
+ for (const folder of previousFolders.values()) {
302
+ const key = resourceFolderKey(folder);
303
+ if (!nextFolderKeys.has(key)) session.pendingStaleResourceFolders.set(key, folder);
304
+ }
305
+ for (const key of nextFolderKeys) session.pendingStaleResourceFolders.delete(key);
278
306
  }
279
307
  function toBackendDiagnostics(error) {
280
308
  return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
@@ -467,11 +495,13 @@ var AuthoringService = class {
467
495
  document: (0, _openfairygui_core_uam.materializeUamProject)(session.project),
468
496
  fairyPath: session.fairyPath,
469
497
  staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
498
+ staleResourceFolders: [...session.pendingStaleResourceFolders.values()],
470
499
  writtenPaths: committedPaths,
471
500
  failedPaths
472
501
  });
473
502
  session.fileSystem ??= fileSystem;
474
503
  session.pendingStaleSourceFiles.clear();
504
+ session.pendingStaleResourceFolders.clear();
475
505
  (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
476
506
  session.lastSavedRevision = session.revision;
477
507
  session.dirty = false;
@@ -609,11 +639,18 @@ var AuthoringService = class {
609
639
  document,
610
640
  fairyPath,
611
641
  staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
642
+ staleResourceFolders: isSessionStorageTarget ? [...session.pendingStaleResourceFolders.values()] : [],
612
643
  writtenPaths,
613
644
  failedPaths
614
645
  });
615
- if (isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
616
- if (storageTarget && !isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
646
+ if (isSessionStorageTarget) {
647
+ session.pendingStaleSourceFiles.clear();
648
+ session.pendingStaleResourceFolders.clear();
649
+ }
650
+ if (storageTarget && !isSessionStorageTarget) {
651
+ session.pendingStaleSourceFiles.clear();
652
+ session.pendingStaleResourceFolders.clear();
653
+ }
617
654
  if (isSessionStorageTarget || storageTarget) (0, _openfairygui_core_uam.commitUamProjectSourcePaths)(session.project);
618
655
  if (storageTarget) {
619
656
  this.context.sessionsByPath.delete(session.canonicalPathKey);
@@ -1141,7 +1178,7 @@ function createProjectReaderFileSystem(fileSystem) {
1141
1178
  }
1142
1179
  };
1143
1180
  }
1144
- function createCaptureFileSystem(files) {
1181
+ function createCaptureFileSystem(files, directories) {
1145
1182
  const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
1146
1183
  return {
1147
1184
  async readFile(filePath) {
@@ -1160,7 +1197,9 @@ function createCaptureFileSystem(files) {
1160
1197
  async writeFileRaw(filePath, data) {
1161
1198
  files.set(normalize(filePath), data.slice());
1162
1199
  },
1163
- async mkdir() {},
1200
+ async mkdir(dirPath) {
1201
+ directories.add(normalize(dirPath));
1202
+ },
1164
1203
  async readdir() {
1165
1204
  return [];
1166
1205
  },
@@ -1193,15 +1232,20 @@ function capturedFilesEqual(left, right) {
1193
1232
  }
1194
1233
  return true;
1195
1234
  }
1235
+ function capturedDirectoriesEqual(left, right) {
1236
+ return left.size === right.size && [...left].every((directory) => right.has(directory));
1237
+ }
1196
1238
  async function hasFullUamFidelity(document, project) {
1197
1239
  const sourceFiles = /* @__PURE__ */ new Map();
1198
1240
  const materializedFiles = /* @__PURE__ */ new Map();
1241
+ const sourceDirectories = /* @__PURE__ */ new Set();
1242
+ const materializedDirectories = /* @__PURE__ */ new Set();
1199
1243
  try {
1200
- 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")]);
1244
+ 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")]);
1201
1245
  } catch {
1202
1246
  return false;
1203
1247
  }
1204
- return capturedFilesEqual(sourceFiles, materializedFiles);
1248
+ return capturedFilesEqual(sourceFiles, materializedFiles) && capturedDirectoriesEqual(sourceDirectories, materializedDirectories);
1205
1249
  }
1206
1250
  var RuntimeService = class {
1207
1251
  constructor(context, cacheService, eventService, jobService) {
@@ -1252,6 +1296,7 @@ var RuntimeService = class {
1252
1296
  revision: 0,
1253
1297
  lastSavedRevision: 0,
1254
1298
  pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1299
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1255
1300
  dirty: false,
1256
1301
  lockHeld: true,
1257
1302
  closed: false
@@ -1311,6 +1356,7 @@ var RuntimeService = class {
1311
1356
  revision: 0,
1312
1357
  lastSavedRevision: 0,
1313
1358
  pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1359
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1314
1360
  dirty: false,
1315
1361
  lockHeld: false,
1316
1362
  closed: false
@@ -154,11 +154,15 @@ function createWriterFileSystem(fileSystem, writtenPaths, failedPaths) {
154
154
  },
155
155
  join: (...paths) => fileSystem.join(...paths),
156
156
  dirname: (path) => fileSystem.dirname(path),
157
- unlink: (path) => trackWrite(path, () => fileSystem.unlink(path))
157
+ unlink: (path) => trackWrite(path, () => fileSystem.unlink(path)),
158
+ rmdir: (path) => trackWrite(path, () => fileSystem.rmdir(path))
158
159
  };
159
160
  }
160
161
  async function writeSessionProject(input) {
161
- await new ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, { staleSourceFiles: input.staleSourceFiles });
162
+ await new ProjectWriter(createWriterFileSystem(input.fileSystem, input.writtenPaths, input.failedPaths)).write(input.document, input.fairyPath, {
163
+ staleSourceFiles: input.staleSourceFiles,
164
+ staleResourceFolders: input.staleResourceFolders
165
+ });
162
166
  }
163
167
  //#endregion
164
168
  //#region src/services/snapshot-utils.ts
@@ -237,6 +241,7 @@ function projectSourceFiles(project) {
237
241
  fileName: "package.xml"
238
242
  });
239
243
  const branches = /* @__PURE__ */ new Set();
244
+ for (const folder of pkg.folders) if (folder.branch) branches.add(folder.branch);
240
245
  for (const resource of pkg.resources) {
241
246
  if (resource.branch) branches.add(resource.branch);
242
247
  const fileName = resource.kind === "component" ? `${resource.name}.xml` : resource.fileName ?? (resource.kind === "image" ? "" : resource.file) ?? "";
@@ -265,6 +270,22 @@ function sourceFileKey(source) {
265
270
  source.fileName
266
271
  ].join("\0");
267
272
  }
273
+ function projectResourceFolders(project) {
274
+ const result = /* @__PURE__ */ new Map();
275
+ for (const pkg of project.packages) for (const folder of pkg.folders) result.set(`${pkg.id}/${folder.branch}/${folder.path}`, {
276
+ packageName: pkg.name,
277
+ branch: folder.branch,
278
+ path: folder.path
279
+ });
280
+ return result;
281
+ }
282
+ function resourceFolderKey(folder) {
283
+ return [
284
+ folder.branch,
285
+ folder.packageName,
286
+ folder.path
287
+ ].join("\0");
288
+ }
268
289
  function recordStaleProjectFiles(session, previousProject, nextProject) {
269
290
  if (!session.fileSystem) return;
270
291
  const previousSources = projectSourceFiles(previousProject);
@@ -274,6 +295,13 @@ function recordStaleProjectFiles(session, previousProject, nextProject) {
274
295
  if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
275
296
  }
276
297
  for (const key of nextSourceKeys) session.pendingStaleSourceFiles.delete(key);
298
+ const previousFolders = projectResourceFolders(previousProject);
299
+ const nextFolderKeys = new Set([...projectResourceFolders(nextProject).values()].map(resourceFolderKey));
300
+ for (const folder of previousFolders.values()) {
301
+ const key = resourceFolderKey(folder);
302
+ if (!nextFolderKeys.has(key)) session.pendingStaleResourceFolders.set(key, folder);
303
+ }
304
+ for (const key of nextFolderKeys) session.pendingStaleResourceFolders.delete(key);
277
305
  }
278
306
  function toBackendDiagnostics(error) {
279
307
  return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
@@ -466,11 +494,13 @@ var AuthoringService = class {
466
494
  document: materializeUamProject(session.project),
467
495
  fairyPath: session.fairyPath,
468
496
  staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
497
+ staleResourceFolders: [...session.pendingStaleResourceFolders.values()],
469
498
  writtenPaths: committedPaths,
470
499
  failedPaths
471
500
  });
472
501
  session.fileSystem ??= fileSystem;
473
502
  session.pendingStaleSourceFiles.clear();
503
+ session.pendingStaleResourceFolders.clear();
474
504
  commitUamProjectSourcePaths(session.project);
475
505
  session.lastSavedRevision = session.revision;
476
506
  session.dirty = false;
@@ -608,11 +638,18 @@ var AuthoringService = class {
608
638
  document,
609
639
  fairyPath,
610
640
  staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
641
+ staleResourceFolders: isSessionStorageTarget ? [...session.pendingStaleResourceFolders.values()] : [],
611
642
  writtenPaths,
612
643
  failedPaths
613
644
  });
614
- if (isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
615
- if (storageTarget && !isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
645
+ if (isSessionStorageTarget) {
646
+ session.pendingStaleSourceFiles.clear();
647
+ session.pendingStaleResourceFolders.clear();
648
+ }
649
+ if (storageTarget && !isSessionStorageTarget) {
650
+ session.pendingStaleSourceFiles.clear();
651
+ session.pendingStaleResourceFolders.clear();
652
+ }
616
653
  if (isSessionStorageTarget || storageTarget) commitUamProjectSourcePaths(session.project);
617
654
  if (storageTarget) {
618
655
  this.context.sessionsByPath.delete(session.canonicalPathKey);
@@ -1140,7 +1177,7 @@ function createProjectReaderFileSystem(fileSystem) {
1140
1177
  }
1141
1178
  };
1142
1179
  }
1143
- function createCaptureFileSystem(files) {
1180
+ function createCaptureFileSystem(files, directories) {
1144
1181
  const normalize = (filePath) => filePath.replace(/\\/g, "/").replace(/\/+/g, "/");
1145
1182
  return {
1146
1183
  async readFile(filePath) {
@@ -1159,7 +1196,9 @@ function createCaptureFileSystem(files) {
1159
1196
  async writeFileRaw(filePath, data) {
1160
1197
  files.set(normalize(filePath), data.slice());
1161
1198
  },
1162
- async mkdir() {},
1199
+ async mkdir(dirPath) {
1200
+ directories.add(normalize(dirPath));
1201
+ },
1163
1202
  async readdir() {
1164
1203
  return [];
1165
1204
  },
@@ -1192,15 +1231,20 @@ function capturedFilesEqual(left, right) {
1192
1231
  }
1193
1232
  return true;
1194
1233
  }
1234
+ function capturedDirectoriesEqual(left, right) {
1235
+ return left.size === right.size && [...left].every((directory) => right.has(directory));
1236
+ }
1195
1237
  async function hasFullUamFidelity(document, project) {
1196
1238
  const sourceFiles = /* @__PURE__ */ new Map();
1197
1239
  const materializedFiles = /* @__PURE__ */ new Map();
1240
+ const sourceDirectories = /* @__PURE__ */ new Set();
1241
+ const materializedDirectories = /* @__PURE__ */ new Set();
1198
1242
  try {
1199
- await Promise.all([new ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, "Project.fairy"), new ProjectWriter(createCaptureFileSystem(materializedFiles)).write(materializeUamProject(project), "Project.fairy")]);
1243
+ await Promise.all([new ProjectWriter(createCaptureFileSystem(sourceFiles, sourceDirectories)).write(document, "Project.fairy"), new ProjectWriter(createCaptureFileSystem(materializedFiles, materializedDirectories)).write(materializeUamProject(project), "Project.fairy")]);
1200
1244
  } catch {
1201
1245
  return false;
1202
1246
  }
1203
- return capturedFilesEqual(sourceFiles, materializedFiles);
1247
+ return capturedFilesEqual(sourceFiles, materializedFiles) && capturedDirectoriesEqual(sourceDirectories, materializedDirectories);
1204
1248
  }
1205
1249
  var RuntimeService = class {
1206
1250
  constructor(context, cacheService, eventService, jobService) {
@@ -1251,6 +1295,7 @@ var RuntimeService = class {
1251
1295
  revision: 0,
1252
1296
  lastSavedRevision: 0,
1253
1297
  pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1298
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1254
1299
  dirty: false,
1255
1300
  lockHeld: true,
1256
1301
  closed: false
@@ -1310,6 +1355,7 @@ var RuntimeService = class {
1310
1355
  revision: 0,
1311
1356
  lastSavedRevision: 0,
1312
1357
  pendingStaleSourceFiles: /* @__PURE__ */ new Map(),
1358
+ pendingStaleResourceFolders: /* @__PURE__ */ new Map(),
1313
1359
  dirty: false,
1314
1360
  lockHeld: false,
1315
1361
  closed: false
@@ -70,6 +70,7 @@ interface BackendFileSystem {
70
70
  resolvePath(filePath: string): Promise<string>;
71
71
  openExclusive(filePath: string): Promise<BackendFileHandle>;
72
72
  unlink(filePath: string): Promise<void>;
73
+ rmdir(dirPath: string): Promise<void>;
73
74
  join(...paths: string[]): string;
74
75
  dirname(filePath: string): string;
75
76
  resolve(...paths: string[]): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/backend",
3
- "version": "0.2.0-alpha.29",
3
+ "version": "0.2.0-alpha.30",
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/core": "0.2.0-alpha.29",
56
- "@openfairygui/functions": "0.2.0-alpha.29"
55
+ "@openfairygui/core": "0.2.0-alpha.30",
56
+ "@openfairygui/functions": "0.2.0-alpha.30"
57
57
  },
58
58
  "devDependencies": {
59
59
  "ava": "^7.0.0",
package/src/node.ts CHANGED
@@ -54,6 +54,9 @@ export function createNodeBackendFileSystem(): BackendFileSystem {
54
54
  unlink(filePath: string): Promise<void> {
55
55
  return fs.unlink(filePath);
56
56
  },
57
+ rmdir(dirPath: string): Promise<void> {
58
+ return fs.rmdir(dirPath);
59
+ },
57
60
  join(...paths: string[]): string {
58
61
  return path.join(...paths);
59
62
  },
@@ -30,6 +30,7 @@ export interface BackendFileSystem {
30
30
  resolvePath(filePath: string): Promise<string>;
31
31
  openExclusive(filePath: string): Promise<BackendFileHandle>;
32
32
  unlink(filePath: string): Promise<void>;
33
+ rmdir(dirPath: string): Promise<void>;
33
34
  join(...paths: string[]): string;
34
35
  dirname(filePath: string): string;
35
36
  resolve(...paths: string[]): string;
@@ -1,4 +1,4 @@
1
- import type { ProjectSourceFile } from '@openfairygui/core/project-io';
1
+ import type { ProjectResourceFolder, ProjectSourceFile } from '@openfairygui/core/project-io';
2
2
  import {
3
3
  commitUamProjectSourcePaths,
4
4
  materializeUamProject,
@@ -41,6 +41,9 @@ function projectSourceFiles(project: UamProject): Map<string, ProjectSourceFile>
41
41
  fileName: 'package.xml',
42
42
  });
43
43
  const branches = new Set<string>();
44
+ for (const folder of pkg.folders) {
45
+ if (folder.branch) branches.add(folder.branch);
46
+ }
44
47
  for (const resource of pkg.resources) {
45
48
  if (resource.branch) branches.add(resource.branch);
46
49
  const fileName = resource.kind === 'component'
@@ -70,6 +73,24 @@ function sourceFileKey(source: ProjectSourceFile): string {
70
73
  return [source.branch, source.packageName, source.path, source.fileName].join('\0');
71
74
  }
72
75
 
76
+ function projectResourceFolders(project: UamProject): Map<string, ProjectResourceFolder> {
77
+ const result = new Map<string, ProjectResourceFolder>();
78
+ for (const pkg of project.packages) {
79
+ for (const folder of pkg.folders) {
80
+ result.set(`${pkg.id}/${folder.branch}/${folder.path}`, {
81
+ packageName: pkg.name,
82
+ branch: folder.branch,
83
+ path: folder.path,
84
+ });
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+
90
+ function resourceFolderKey(folder: ProjectResourceFolder): string {
91
+ return [folder.branch, folder.packageName, folder.path].join('\0');
92
+ }
93
+
73
94
  function recordStaleProjectFiles(
74
95
  session: Parameters<typeof toSessionSnapshot>[0],
75
96
  previousProject: UamProject,
@@ -85,6 +106,15 @@ function recordStaleProjectFiles(
85
106
  for (const key of nextSourceKeys) {
86
107
  session.pendingStaleSourceFiles.delete(key);
87
108
  }
109
+ const previousFolders = projectResourceFolders(previousProject);
110
+ const nextFolderKeys = new Set([...projectResourceFolders(nextProject).values()].map(resourceFolderKey));
111
+ for (const folder of previousFolders.values()) {
112
+ const key = resourceFolderKey(folder);
113
+ if (!nextFolderKeys.has(key)) session.pendingStaleResourceFolders.set(key, folder);
114
+ }
115
+ for (const key of nextFolderKeys) {
116
+ session.pendingStaleResourceFolders.delete(key);
117
+ }
88
118
  }
89
119
 
90
120
  function toBackendDiagnostics(error: ApplyUamTransactionAppError): BackendDiagnostic[] {
@@ -411,11 +441,13 @@ export class AuthoringService {
411
441
  document: materializeUamProject(session.project),
412
442
  fairyPath: session.fairyPath,
413
443
  staleSourceFiles: [...session.pendingStaleSourceFiles.values()],
444
+ staleResourceFolders: [...session.pendingStaleResourceFolders.values()],
414
445
  writtenPaths: committedPaths,
415
446
  failedPaths,
416
447
  });
417
448
  session.fileSystem ??= fileSystem;
418
449
  session.pendingStaleSourceFiles.clear();
450
+ session.pendingStaleResourceFolders.clear();
419
451
  commitUamProjectSourcePaths(session.project);
420
452
  session.lastSavedRevision = session.revision;
421
453
  session.dirty = false;
@@ -650,11 +682,18 @@ export class AuthoringService {
650
682
  document,
651
683
  fairyPath,
652
684
  staleSourceFiles: isSessionStorageTarget ? [...session.pendingStaleSourceFiles.values()] : [],
685
+ staleResourceFolders: isSessionStorageTarget ? [...session.pendingStaleResourceFolders.values()] : [],
653
686
  writtenPaths,
654
687
  failedPaths,
655
688
  });
656
- if (isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
657
- if (storageTarget && !isSessionStorageTarget) session.pendingStaleSourceFiles.clear();
689
+ if (isSessionStorageTarget) {
690
+ session.pendingStaleSourceFiles.clear();
691
+ session.pendingStaleResourceFolders.clear();
692
+ }
693
+ if (storageTarget && !isSessionStorageTarget) {
694
+ session.pendingStaleSourceFiles.clear();
695
+ session.pendingStaleResourceFolders.clear();
696
+ }
658
697
  if (isSessionStorageTarget || storageTarget) commitUamProjectSourcePaths(session.project);
659
698
  if (storageTarget) {
660
699
  this.context.sessionsByPath.delete(session.canonicalPathKey);
@@ -32,6 +32,8 @@ export interface BackendSessionState {
32
32
  lastSavedRevision: number;
33
33
  /** Package-controlled files deferred until a successful replacement project write. */
34
34
  pendingStaleSourceFiles: Map<string, import('@openfairygui/core/project-io').ProjectSourceFile>;
35
+ /** Empty resource directories deferred until a successful replacement project write. */
36
+ pendingStaleResourceFolders: Map<string, import('@openfairygui/core/project-io').ProjectResourceFolder>;
35
37
  dirty: boolean;
36
38
  lockHeld: boolean;
37
39
  closed: boolean;
@@ -79,7 +79,10 @@ function createProjectReaderFileSystem(fileSystem: NonNullable<BackendContext['f
79
79
  };
80
80
  }
81
81
 
82
- function createCaptureFileSystem(files: Map<string, string | Uint8Array>): FileSystem {
82
+ function createCaptureFileSystem(
83
+ files: Map<string, string | Uint8Array>,
84
+ directories: Set<string>,
85
+ ): FileSystem {
83
86
  const normalize = (filePath: string): string => filePath.replace(/\\/g, '/').replace(/\/+/g, '/');
84
87
  return {
85
88
  async readFile(filePath: string): Promise<string> {
@@ -98,7 +101,9 @@ function createCaptureFileSystem(files: Map<string, string | Uint8Array>): FileS
98
101
  async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
99
102
  files.set(normalize(filePath), data.slice());
100
103
  },
101
- async mkdir(): Promise<void> {},
104
+ async mkdir(dirPath: string): Promise<void> {
105
+ directories.add(normalize(dirPath));
106
+ },
102
107
  async readdir(): Promise<string[]> {
103
108
  return [];
104
109
  },
@@ -135,16 +140,22 @@ function capturedFilesEqual(left: Map<string, string | Uint8Array>, right: Map<s
135
140
  return true;
136
141
  }
137
142
 
143
+ function capturedDirectoriesEqual(left: Set<string>, right: Set<string>): boolean {
144
+ return left.size === right.size && [...left].every((directory) => right.has(directory));
145
+ }
146
+
138
147
  async function hasFullUamFidelity(
139
148
  document: Awaited<ReturnType<ProjectReader['read']>>,
140
149
  project: UamProject,
141
150
  ): Promise<boolean> {
142
151
  const sourceFiles = new Map<string, string | Uint8Array>();
143
152
  const materializedFiles = new Map<string, string | Uint8Array>();
153
+ const sourceDirectories = new Set<string>();
154
+ const materializedDirectories = new Set<string>();
144
155
  try {
145
156
  await Promise.all([
146
- new ProjectWriter(createCaptureFileSystem(sourceFiles)).write(document, 'Project.fairy'),
147
- new ProjectWriter(createCaptureFileSystem(materializedFiles)).write(
157
+ new ProjectWriter(createCaptureFileSystem(sourceFiles, sourceDirectories)).write(document, 'Project.fairy'),
158
+ new ProjectWriter(createCaptureFileSystem(materializedFiles, materializedDirectories)).write(
148
159
  materializeUamProject(project),
149
160
  'Project.fairy',
150
161
  ),
@@ -152,7 +163,8 @@ async function hasFullUamFidelity(
152
163
  } catch {
153
164
  return false;
154
165
  }
155
- return capturedFilesEqual(sourceFiles, materializedFiles);
166
+ return capturedFilesEqual(sourceFiles, materializedFiles)
167
+ && capturedDirectoriesEqual(sourceDirectories, materializedDirectories);
156
168
  }
157
169
 
158
170
  export class RuntimeService {
@@ -225,6 +237,7 @@ export class RuntimeService {
225
237
  revision: 0,
226
238
  lastSavedRevision: 0,
227
239
  pendingStaleSourceFiles: new Map(),
240
+ pendingStaleResourceFolders: new Map(),
228
241
  dirty: false,
229
242
  lockHeld: true,
230
243
  closed: false,
@@ -292,6 +305,7 @@ export class RuntimeService {
292
305
  revision: 0,
293
306
  lastSavedRevision: 0,
294
307
  pendingStaleSourceFiles: new Map(),
308
+ pendingStaleResourceFolders: new Map(),
295
309
  dirty: false,
296
310
  lockHeld: false,
297
311
  closed: false,
@@ -44,6 +44,7 @@ function createWriterFileSystem(
44
44
  join: (...paths) => fileSystem.join(...paths),
45
45
  dirname: (path) => fileSystem.dirname(path),
46
46
  unlink: (path) => trackWrite(path, () => fileSystem.unlink(path)),
47
+ rmdir: (path) => trackWrite(path, () => fileSystem.rmdir(path)),
47
48
  };
48
49
  }
49
50
 
@@ -52,6 +53,7 @@ export async function writeSessionProject(input: {
52
53
  document: Document;
53
54
  fairyPath: string;
54
55
  staleSourceFiles: ProjectSourceFile[];
56
+ staleResourceFolders: import('@openfairygui/core/project-io').ProjectResourceFolder[];
55
57
  writtenPaths: string[];
56
58
  failedPaths: string[];
57
59
  }): Promise<void> {
@@ -60,5 +62,6 @@ export async function writeSessionProject(input: {
60
62
  );
61
63
  await writer.write(input.document, input.fairyPath, {
62
64
  staleSourceFiles: input.staleSourceFiles,
65
+ staleResourceFolders: input.staleResourceFolders,
63
66
  });
64
67
  }
package/src/storage.ts CHANGED
@@ -22,6 +22,7 @@ export interface BackendAsyncStorageAdapter {
22
22
  resolvePath?(filePath: string): Promise<string>;
23
23
  openExclusive?(filePath: string): Promise<BackendFileHandle>;
24
24
  unlink(filePath: string): Promise<void>;
25
+ rmdir(dirPath: string): Promise<void>;
25
26
  join?(...paths: string[]): string;
26
27
  dirname?(filePath: string): string;
27
28
  resolve?(...paths: string[]): string;
@@ -117,6 +118,9 @@ export function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapt
117
118
  if (typeof storage.unlink !== 'function') {
118
119
  throw createPathError('ENOTSUP', 'Storage adapter must provide unlink() for project resource lifecycle writes.');
119
120
  }
121
+ if (typeof storage.rmdir !== 'function') {
122
+ throw createPathError('ENOTSUP', 'Storage adapter must provide rmdir() for project resource folder lifecycle writes.');
123
+ }
120
124
  const lockedPaths = new Set<string>();
121
125
 
122
126
  const fileSystem: BackendStorageFileSystem = {
@@ -179,6 +183,9 @@ export function createBackendStorageFileSystem(storage: BackendAsyncStorageAdapt
179
183
  lockedPaths.delete(resolved);
180
184
  return storage.unlink(resolved);
181
185
  },
186
+ rmdir(dirPath: string): Promise<void> {
187
+ return storage.rmdir(fileSystem.resolve(dirPath));
188
+ },
182
189
  join(...paths: string[]): string {
183
190
  return storage.join ? storage.join(...paths) : joinStoragePath(...paths);
184
191
  },