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

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-CcBLI-J7.cjs");
2
+ const require_runtime = require("./runtime-DtcaaVfy.cjs");
3
3
  //#region src/storage.ts
4
4
  var StorageFileStat = class {
5
5
  constructor(kind) {
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-NiBgr7PF.mjs";
1
+ import { i as BACKEND_CONTRACT_VERSION, n as BACKEND_CAPABILITY_SCHEMA_VERSION, r as BACKEND_COMPATIBILITY_POLICY, t as BackendRuntime } from "./runtime-BDkZmTFN.mjs";
2
2
  //#region src/storage.ts
3
3
  var StorageFileStat = class {
4
4
  constructor(kind) {
package/dist/node.cjs CHANGED
@@ -21,7 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- const require_runtime = require("./runtime-CcBLI-J7.cjs");
24
+ const require_runtime = require("./runtime-DtcaaVfy.cjs");
25
25
  let node_fs_promises = require("node:fs/promises");
26
26
  node_fs_promises = __toESM(node_fs_promises);
27
27
  let node_path = require("node:path");
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BackendRuntime } from "./runtime-NiBgr7PF.mjs";
1
+ import { t as BackendRuntime } from "./runtime-BDkZmTFN.mjs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  //#region src/node.ts
@@ -1,4 +1,4 @@
1
- import { UAM_SUPPORTED_MATERIALIZATION_SCOPE, UAM_SUPPORTED_TRANSACTION_SCOPE, commitUamProjectSourcePaths, liftDocumentToUamProject, materializeUamProject, normalizeUamProject, validateUamProject } from "@openfairygui/core/uam";
1
+ import { UAM_SUPPORTED_MATERIALIZATION_SCOPE, UAM_SUPPORTED_TRANSACTION_SCOPE, commitUamProjectSourcePaths, liftDocumentToUamProject, materializeUamProject, normalizeUamProject, staleResourceFolders, staleSourceFiles, validateUamProject } from "@openfairygui/core/uam";
2
2
  import { applyUamTransactionApp } from "@openfairygui/functions/uam";
3
3
  import { ProjectReader, ProjectWriter } from "@openfairygui/core/project-io";
4
4
  //#region src/contracts.ts
@@ -165,40 +165,6 @@ async function writeSessionProject(input) {
165
165
  });
166
166
  }
167
167
  //#endregion
168
- //#region src/services/snapshot-utils.ts
169
- function cloneJsonValue(value) {
170
- if (value === void 0 || value === null) return value;
171
- return JSON.parse(JSON.stringify(value));
172
- }
173
- function cloneEventSnapshot(event) {
174
- return {
175
- ...event,
176
- diagnostics: event.diagnostics.map((diagnostic) => ({ ...diagnostic })),
177
- payload: cloneJsonValue(event.payload)
178
- };
179
- }
180
- function cloneJobSnapshot(job) {
181
- return {
182
- ...job,
183
- diagnostics: job.diagnostics.map((diagnostic) => ({ ...diagnostic })),
184
- progress: job.progress ? { ...job.progress } : void 0,
185
- result: cloneJsonValue(job.result),
186
- error: cloneJsonValue(job.error)
187
- };
188
- }
189
- function cloneCacheEntrySnapshot(entry) {
190
- return {
191
- ...entry,
192
- summary: {
193
- ...entry.summary,
194
- diagnostics: entry.summary.diagnostics.map((diagnostic) => ({ ...diagnostic }))
195
- }
196
- };
197
- }
198
- function cloneCapabilitiesSnapshot(capabilities) {
199
- return cloneJsonValue(capabilities);
200
- }
201
- //#endregion
202
168
  //#region src/services/session-utils.ts
203
169
  function toSessionSnapshot(session, capabilities) {
204
170
  return {
@@ -209,7 +175,7 @@ function toSessionSnapshot(session, capabilities) {
209
175
  dirty: session.dirty,
210
176
  uamFidelity: session.uamFidelity,
211
177
  lockHeld: session.lockHeld,
212
- capabilities: cloneCapabilitiesSnapshot(capabilities)
178
+ capabilities: structuredClone(capabilities)
213
179
  };
214
180
  }
215
181
  function createSessionNotFoundError(sessionId) {
@@ -231,37 +197,6 @@ function createStaleWriteError(session, expectedRevision) {
231
197
  }
232
198
  //#endregion
233
199
  //#region src/services/authoring-service.ts
234
- function projectSourceFiles(project) {
235
- const result = /* @__PURE__ */ new Map();
236
- for (const pkg of project.packages) {
237
- result.set(`${pkg.id}/package.xml`, {
238
- packageName: pkg.name,
239
- branch: "",
240
- path: "",
241
- fileName: "package.xml"
242
- });
243
- const branches = /* @__PURE__ */ new Set();
244
- for (const folder of pkg.folders) if (folder.branch) branches.add(folder.branch);
245
- for (const resource of pkg.resources) {
246
- if (resource.branch) branches.add(resource.branch);
247
- const fileName = resource.kind === "component" ? `${resource.name}.xml` : resource.fileName ?? (resource.kind === "image" ? "" : resource.file) ?? "";
248
- if (!fileName) continue;
249
- result.set(`${pkg.id}/${resource.id}`, {
250
- packageName: pkg.name,
251
- branch: resource.branch,
252
- path: resource.path,
253
- fileName
254
- });
255
- }
256
- for (const branch of branches) result.set(`${pkg.id}/branch/${branch}`, {
257
- packageName: pkg.name,
258
- branch,
259
- path: "",
260
- fileName: "package_branch.xml"
261
- });
262
- }
263
- return result;
264
- }
265
200
  function sourceFileKey(source) {
266
201
  return [
267
202
  source.branch,
@@ -270,15 +205,6 @@ function sourceFileKey(source) {
270
205
  source.fileName
271
206
  ].join("\0");
272
207
  }
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
208
  function resourceFolderKey(folder) {
283
209
  return [
284
210
  folder.branch,
@@ -288,20 +214,10 @@ function resourceFolderKey(folder) {
288
214
  }
289
215
  function recordStaleProjectFiles(session, previousProject, nextProject) {
290
216
  if (!session.fileSystem) return;
291
- const previousSources = projectSourceFiles(previousProject);
292
- const nextSourceKeys = new Set([...projectSourceFiles(nextProject).values()].map(sourceFileKey));
293
- for (const source of previousSources.values()) {
294
- const key = sourceFileKey(source);
295
- if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
296
- }
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);
217
+ for (const source of staleSourceFiles(previousProject, nextProject)) session.pendingStaleSourceFiles.set(sourceFileKey(source), source);
218
+ for (const source of staleSourceFiles(nextProject, previousProject)) session.pendingStaleSourceFiles.delete(sourceFileKey(source));
219
+ for (const folder of staleResourceFolders(previousProject, nextProject)) session.pendingStaleResourceFolders.set(resourceFolderKey(folder), folder);
220
+ for (const folder of staleResourceFolders(nextProject, previousProject)) session.pendingStaleResourceFolders.delete(resourceFolderKey(folder));
305
221
  }
306
222
  function toBackendDiagnostics(error) {
307
223
  return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
@@ -749,7 +665,7 @@ var CacheService = class {
749
665
  const entry = this.context.cacheBySession.get(input.sessionId);
750
666
  return success("read", startedAt, {
751
667
  cacheRevision: entry?.revision ?? session.revision,
752
- entries: entry ? [cloneCacheEntrySnapshot(entry)] : []
668
+ entries: entry ? [structuredClone(entry)] : []
753
669
  }, {
754
670
  sessionId: session.sessionId,
755
671
  revision: session.revision
@@ -820,7 +736,7 @@ var EventService = class {
820
736
  const filtered = events.filter((event) => event.sequence > after);
821
737
  const limit = input.limit === void 0 ? filtered.length : Math.max(0, input.limit);
822
738
  return success("runtime", startedAt, {
823
- events: filtered.slice(0, limit).map(cloneEventSnapshot),
739
+ events: filtered.slice(0, limit).map((event) => structuredClone(event)),
824
740
  oldestSequence,
825
741
  currentSequence,
826
742
  cursorExpired: false
@@ -895,7 +811,7 @@ var JobService = class {
895
811
  jobId
896
812
  });
897
813
  this.scheduleRefreshJob(session.sessionId, jobId);
898
- return success("runtime", startedAt, cloneJobSnapshot(job), {
814
+ return success("runtime", startedAt, structuredClone(job), {
899
815
  sessionId: session.sessionId,
900
816
  revision: session.revision
901
817
  });
@@ -914,7 +830,7 @@ var JobService = class {
914
830
  sessionId: session.sessionId,
915
831
  revision: session.revision
916
832
  });
917
- return success("runtime", startedAt, cloneJobSnapshot(job), {
833
+ return success("runtime", startedAt, structuredClone(job), {
918
834
  sessionId: session.sessionId,
919
835
  revision: session.revision
920
836
  });
@@ -929,7 +845,7 @@ var JobService = class {
929
845
  else if (input.status === "terminal") jobs = jobs.filter((job) => isTerminal(job.status));
930
846
  else jobs = jobs.filter((job) => job.status === input.status);
931
847
  if (input.limit !== void 0) jobs = jobs.slice(0, Math.max(0, input.limit));
932
- return success("runtime", startedAt, { jobs: jobs.map(cloneJobSnapshot) }, {
848
+ return success("runtime", startedAt, { jobs: jobs.map((job) => structuredClone(job)) }, {
933
849
  sessionId: session.sessionId,
934
850
  revision: session.revision
935
851
  });
@@ -966,7 +882,8 @@ var JobService = class {
966
882
  revision: session.revision,
967
883
  jobId: job.jobId
968
884
  });
969
- return success("runtime", startedAt, cloneJobSnapshot(this.cancelRefreshJob(session.sessionId, job)), {
885
+ const cancelled = this.cancelRefreshJob(session.sessionId, job);
886
+ return success("runtime", startedAt, structuredClone(cancelled), {
970
887
  sessionId: session.sessionId,
971
888
  revision: session.revision
972
889
  });
@@ -1113,7 +1030,7 @@ var ReadService = class {
1113
1030
  this.context = context;
1114
1031
  }
1115
1032
  getCapabilities() {
1116
- return success("read", Date.now(), cloneCapabilitiesSnapshot(this.context.capabilities));
1033
+ return success("read", Date.now(), structuredClone(this.context.capabilities));
1117
1034
  }
1118
1035
  getSession(input) {
1119
1036
  const startedAt = Date.now();
@@ -166,40 +166,6 @@ async function writeSessionProject(input) {
166
166
  });
167
167
  }
168
168
  //#endregion
169
- //#region src/services/snapshot-utils.ts
170
- function cloneJsonValue(value) {
171
- if (value === void 0 || value === null) return value;
172
- return JSON.parse(JSON.stringify(value));
173
- }
174
- function cloneEventSnapshot(event) {
175
- return {
176
- ...event,
177
- diagnostics: event.diagnostics.map((diagnostic) => ({ ...diagnostic })),
178
- payload: cloneJsonValue(event.payload)
179
- };
180
- }
181
- function cloneJobSnapshot(job) {
182
- return {
183
- ...job,
184
- diagnostics: job.diagnostics.map((diagnostic) => ({ ...diagnostic })),
185
- progress: job.progress ? { ...job.progress } : void 0,
186
- result: cloneJsonValue(job.result),
187
- error: cloneJsonValue(job.error)
188
- };
189
- }
190
- function cloneCacheEntrySnapshot(entry) {
191
- return {
192
- ...entry,
193
- summary: {
194
- ...entry.summary,
195
- diagnostics: entry.summary.diagnostics.map((diagnostic) => ({ ...diagnostic }))
196
- }
197
- };
198
- }
199
- function cloneCapabilitiesSnapshot(capabilities) {
200
- return cloneJsonValue(capabilities);
201
- }
202
- //#endregion
203
169
  //#region src/services/session-utils.ts
204
170
  function toSessionSnapshot(session, capabilities) {
205
171
  return {
@@ -210,7 +176,7 @@ function toSessionSnapshot(session, capabilities) {
210
176
  dirty: session.dirty,
211
177
  uamFidelity: session.uamFidelity,
212
178
  lockHeld: session.lockHeld,
213
- capabilities: cloneCapabilitiesSnapshot(capabilities)
179
+ capabilities: structuredClone(capabilities)
214
180
  };
215
181
  }
216
182
  function createSessionNotFoundError(sessionId) {
@@ -232,37 +198,6 @@ function createStaleWriteError(session, expectedRevision) {
232
198
  }
233
199
  //#endregion
234
200
  //#region src/services/authoring-service.ts
235
- function projectSourceFiles(project) {
236
- const result = /* @__PURE__ */ new Map();
237
- for (const pkg of project.packages) {
238
- result.set(`${pkg.id}/package.xml`, {
239
- packageName: pkg.name,
240
- branch: "",
241
- path: "",
242
- fileName: "package.xml"
243
- });
244
- const branches = /* @__PURE__ */ new Set();
245
- for (const folder of pkg.folders) if (folder.branch) branches.add(folder.branch);
246
- for (const resource of pkg.resources) {
247
- if (resource.branch) branches.add(resource.branch);
248
- const fileName = resource.kind === "component" ? `${resource.name}.xml` : resource.fileName ?? (resource.kind === "image" ? "" : resource.file) ?? "";
249
- if (!fileName) continue;
250
- result.set(`${pkg.id}/${resource.id}`, {
251
- packageName: pkg.name,
252
- branch: resource.branch,
253
- path: resource.path,
254
- fileName
255
- });
256
- }
257
- for (const branch of branches) result.set(`${pkg.id}/branch/${branch}`, {
258
- packageName: pkg.name,
259
- branch,
260
- path: "",
261
- fileName: "package_branch.xml"
262
- });
263
- }
264
- return result;
265
- }
266
201
  function sourceFileKey(source) {
267
202
  return [
268
203
  source.branch,
@@ -271,15 +206,6 @@ function sourceFileKey(source) {
271
206
  source.fileName
272
207
  ].join("\0");
273
208
  }
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
209
  function resourceFolderKey(folder) {
284
210
  return [
285
211
  folder.branch,
@@ -289,20 +215,10 @@ function resourceFolderKey(folder) {
289
215
  }
290
216
  function recordStaleProjectFiles(session, previousProject, nextProject) {
291
217
  if (!session.fileSystem) return;
292
- const previousSources = projectSourceFiles(previousProject);
293
- const nextSourceKeys = new Set([...projectSourceFiles(nextProject).values()].map(sourceFileKey));
294
- for (const source of previousSources.values()) {
295
- const key = sourceFileKey(source);
296
- if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
297
- }
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);
218
+ for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(previousProject, nextProject)) session.pendingStaleSourceFiles.set(sourceFileKey(source), source);
219
+ for (const source of (0, _openfairygui_core_uam.staleSourceFiles)(nextProject, previousProject)) session.pendingStaleSourceFiles.delete(sourceFileKey(source));
220
+ for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(previousProject, nextProject)) session.pendingStaleResourceFolders.set(resourceFolderKey(folder), folder);
221
+ for (const folder of (0, _openfairygui_core_uam.staleResourceFolders)(nextProject, previousProject)) session.pendingStaleResourceFolders.delete(resourceFolderKey(folder));
306
222
  }
307
223
  function toBackendDiagnostics(error) {
308
224
  return error.diagnostics.length > 0 ? error.diagnostics.map((diagnostic) => ({ ...diagnostic })) : [{
@@ -750,7 +666,7 @@ var CacheService = class {
750
666
  const entry = this.context.cacheBySession.get(input.sessionId);
751
667
  return success("read", startedAt, {
752
668
  cacheRevision: entry?.revision ?? session.revision,
753
- entries: entry ? [cloneCacheEntrySnapshot(entry)] : []
669
+ entries: entry ? [structuredClone(entry)] : []
754
670
  }, {
755
671
  sessionId: session.sessionId,
756
672
  revision: session.revision
@@ -821,7 +737,7 @@ var EventService = class {
821
737
  const filtered = events.filter((event) => event.sequence > after);
822
738
  const limit = input.limit === void 0 ? filtered.length : Math.max(0, input.limit);
823
739
  return success("runtime", startedAt, {
824
- events: filtered.slice(0, limit).map(cloneEventSnapshot),
740
+ events: filtered.slice(0, limit).map((event) => structuredClone(event)),
825
741
  oldestSequence,
826
742
  currentSequence,
827
743
  cursorExpired: false
@@ -896,7 +812,7 @@ var JobService = class {
896
812
  jobId
897
813
  });
898
814
  this.scheduleRefreshJob(session.sessionId, jobId);
899
- return success("runtime", startedAt, cloneJobSnapshot(job), {
815
+ return success("runtime", startedAt, structuredClone(job), {
900
816
  sessionId: session.sessionId,
901
817
  revision: session.revision
902
818
  });
@@ -915,7 +831,7 @@ var JobService = class {
915
831
  sessionId: session.sessionId,
916
832
  revision: session.revision
917
833
  });
918
- return success("runtime", startedAt, cloneJobSnapshot(job), {
834
+ return success("runtime", startedAt, structuredClone(job), {
919
835
  sessionId: session.sessionId,
920
836
  revision: session.revision
921
837
  });
@@ -930,7 +846,7 @@ var JobService = class {
930
846
  else if (input.status === "terminal") jobs = jobs.filter((job) => isTerminal(job.status));
931
847
  else jobs = jobs.filter((job) => job.status === input.status);
932
848
  if (input.limit !== void 0) jobs = jobs.slice(0, Math.max(0, input.limit));
933
- return success("runtime", startedAt, { jobs: jobs.map(cloneJobSnapshot) }, {
849
+ return success("runtime", startedAt, { jobs: jobs.map((job) => structuredClone(job)) }, {
934
850
  sessionId: session.sessionId,
935
851
  revision: session.revision
936
852
  });
@@ -967,7 +883,8 @@ var JobService = class {
967
883
  revision: session.revision,
968
884
  jobId: job.jobId
969
885
  });
970
- return success("runtime", startedAt, cloneJobSnapshot(this.cancelRefreshJob(session.sessionId, job)), {
886
+ const cancelled = this.cancelRefreshJob(session.sessionId, job);
887
+ return success("runtime", startedAt, structuredClone(cancelled), {
971
888
  sessionId: session.sessionId,
972
889
  revision: session.revision
973
890
  });
@@ -1114,7 +1031,7 @@ var ReadService = class {
1114
1031
  this.context = context;
1115
1032
  }
1116
1033
  getCapabilities() {
1117
- return success("read", Date.now(), cloneCapabilitiesSnapshot(this.context.capabilities));
1034
+ return success("read", Date.now(), structuredClone(this.context.capabilities));
1118
1035
  }
1119
1036
  getSession(input) {
1120
1037
  const startedAt = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/backend",
3
- "version": "0.2.0-alpha.30",
3
+ "version": "0.2.0-alpha.32",
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.30",
56
- "@openfairygui/functions": "0.2.0-alpha.30"
55
+ "@openfairygui/core": "0.2.0-alpha.32",
56
+ "@openfairygui/functions": "0.2.0-alpha.32"
57
57
  },
58
58
  "devDependencies": {
59
59
  "ava": "^7.0.0",
@@ -2,6 +2,8 @@ import type { ProjectResourceFolder, ProjectSourceFile } from '@openfairygui/cor
2
2
  import {
3
3
  commitUamProjectSourcePaths,
4
4
  materializeUamProject,
5
+ staleResourceFolders,
6
+ staleSourceFiles,
5
7
  type UamProject,
6
8
  validateUamProject,
7
9
  } from '@openfairygui/core/uam';
@@ -31,62 +33,10 @@ import type { EventService } from './event-service.js';
31
33
  import { writeSessionProject } from './session-project-writer.js';
32
34
  import { createSessionNotFoundError, createStaleWriteError, toSessionSnapshot } from './session-utils.js';
33
35
 
34
- function projectSourceFiles(project: UamProject): Map<string, ProjectSourceFile> {
35
- const result = new Map<string, ProjectSourceFile>();
36
- for (const pkg of project.packages) {
37
- result.set(`${pkg.id}/package.xml`, {
38
- packageName: pkg.name,
39
- branch: '',
40
- path: '',
41
- fileName: 'package.xml',
42
- });
43
- const branches = new Set<string>();
44
- for (const folder of pkg.folders) {
45
- if (folder.branch) branches.add(folder.branch);
46
- }
47
- for (const resource of pkg.resources) {
48
- if (resource.branch) branches.add(resource.branch);
49
- const fileName = resource.kind === 'component'
50
- ? `${resource.name}.xml`
51
- : resource.fileName ?? (resource.kind === 'image' ? '' : resource.file) ?? '';
52
- if (!fileName) continue;
53
- result.set(`${pkg.id}/${resource.id}`, {
54
- packageName: pkg.name,
55
- branch: resource.branch,
56
- path: resource.path,
57
- fileName,
58
- });
59
- }
60
- for (const branch of branches) {
61
- result.set(`${pkg.id}/branch/${branch}`, {
62
- packageName: pkg.name,
63
- branch,
64
- path: '',
65
- fileName: 'package_branch.xml',
66
- });
67
- }
68
- }
69
- return result;
70
- }
71
-
72
36
  function sourceFileKey(source: ProjectSourceFile): string {
73
37
  return [source.branch, source.packageName, source.path, source.fileName].join('\0');
74
38
  }
75
39
 
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
40
  function resourceFolderKey(folder: ProjectResourceFolder): string {
91
41
  return [folder.branch, folder.packageName, folder.path].join('\0');
92
42
  }
@@ -97,23 +47,17 @@ function recordStaleProjectFiles(
97
47
  nextProject: UamProject,
98
48
  ): void {
99
49
  if (!session.fileSystem) return;
100
- const previousSources = projectSourceFiles(previousProject);
101
- const nextSourceKeys = new Set([...projectSourceFiles(nextProject).values()].map(sourceFileKey));
102
- for (const source of previousSources.values()) {
103
- const key = sourceFileKey(source);
104
- if (!nextSourceKeys.has(key)) session.pendingStaleSourceFiles.set(key, source);
50
+ for (const source of staleSourceFiles(previousProject, nextProject)) {
51
+ session.pendingStaleSourceFiles.set(sourceFileKey(source), source);
105
52
  }
106
- for (const key of nextSourceKeys) {
107
- session.pendingStaleSourceFiles.delete(key);
53
+ for (const source of staleSourceFiles(nextProject, previousProject)) {
54
+ session.pendingStaleSourceFiles.delete(sourceFileKey(source));
108
55
  }
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);
56
+ for (const folder of staleResourceFolders(previousProject, nextProject)) {
57
+ session.pendingStaleResourceFolders.set(resourceFolderKey(folder), folder);
114
58
  }
115
- for (const key of nextFolderKeys) {
116
- session.pendingStaleResourceFolders.delete(key);
59
+ for (const folder of staleResourceFolders(nextProject, previousProject)) {
60
+ session.pendingStaleResourceFolders.delete(resourceFolderKey(folder));
117
61
  }
118
62
  }
119
63
 
@@ -7,7 +7,6 @@ import type {
7
7
  GetCacheSnapshotInput,
8
8
  SessionNotFoundError,
9
9
  } from '../runtime.js';
10
- import { cloneCacheEntrySnapshot } from './snapshot-utils.js';
11
10
 
12
11
  function createCacheEntry(session: BackendSessionState, valid: boolean): BackendCacheEntry {
13
12
  return {
@@ -38,7 +37,7 @@ export class CacheService {
38
37
  const entry = this.context.cacheBySession.get(input.sessionId);
39
38
  return success('read', startedAt, {
40
39
  cacheRevision: entry?.revision ?? session.revision,
41
- entries: entry ? [cloneCacheEntrySnapshot(entry)] : [],
40
+ entries: entry ? [structuredClone(entry)] : [],
42
41
  }, { sessionId: session.sessionId, revision: session.revision });
43
42
  }
44
43
 
@@ -8,7 +8,6 @@ import type {
8
8
  GetEventsSnapshot,
9
9
  SessionNotFoundError,
10
10
  } from '../runtime.js';
11
- import { cloneEventSnapshot } from './snapshot-utils.js';
12
11
 
13
12
  const DEFAULT_EVENT_RETENTION_LIMIT = 1000;
14
13
 
@@ -71,7 +70,7 @@ export class EventService {
71
70
  const filtered = events.filter((event) => event.sequence > after);
72
71
  const limit = input.limit === undefined ? filtered.length : Math.max(0, input.limit);
73
72
  return success('runtime', startedAt, {
74
- events: filtered.slice(0, limit).map(cloneEventSnapshot),
73
+ events: filtered.slice(0, limit).map((event) => structuredClone(event)),
75
74
  oldestSequence,
76
75
  currentSequence,
77
76
  cursorExpired: false,
@@ -2,7 +2,6 @@ import { failure, success, type BackendContext } from './context.js';
2
2
  import type { CacheService } from './cache-service.js';
3
3
  import type { EventService } from './event-service.js';
4
4
  import { createSessionNotFoundError } from './session-utils.js';
5
- import { cloneJobSnapshot } from './snapshot-utils.js';
6
5
  import type {
7
6
  BackendJobListSnapshot,
8
7
  BackendJobNotCancellableError,
@@ -76,7 +75,7 @@ export class JobService {
76
75
  this.eventService.emit({ kind: 'job.created', sessionId: session.sessionId, canonicalPathKey: session.canonicalPathKey, revision: session.revision, jobId });
77
76
  this.scheduleRefreshJob(session.sessionId, jobId);
78
77
 
79
- return success('runtime', startedAt, cloneJobSnapshot(job), { sessionId: session.sessionId, revision: session.revision });
78
+ return success('runtime', startedAt, structuredClone(job), { sessionId: session.sessionId, revision: session.revision });
80
79
  }
81
80
 
82
81
  public getJob(input: GetJobInput): BackendResult<BackendJobSnapshot, SessionNotFoundError | BackendJobNotFoundError> {
@@ -92,7 +91,7 @@ export class JobService {
92
91
  jobId: input.jobId,
93
92
  }, undefined, { sessionId: session.sessionId, revision: session.revision });
94
93
  }
95
- return success('runtime', startedAt, cloneJobSnapshot(job), { sessionId: session.sessionId, revision: session.revision });
94
+ return success('runtime', startedAt, structuredClone(job), { sessionId: session.sessionId, revision: session.revision });
96
95
  }
97
96
 
98
97
  public listJobs(input: ListJobsInput): BackendResult<BackendJobListSnapshot, SessionNotFoundError> {
@@ -107,7 +106,7 @@ export class JobService {
107
106
  else jobs = jobs.filter((job) => job.status === input.status);
108
107
  }
109
108
  if (input.limit !== undefined) jobs = jobs.slice(0, Math.max(0, input.limit));
110
- return success('runtime', startedAt, { jobs: jobs.map(cloneJobSnapshot) }, { sessionId: session.sessionId, revision: session.revision });
109
+ return success('runtime', startedAt, { jobs: jobs.map((job) => structuredClone(job)) }, { sessionId: session.sessionId, revision: session.revision });
111
110
  }
112
111
 
113
112
  public cancelJob(input: CancelJobInput): BackendResult<BackendJobSnapshot, SessionNotFoundError | BackendJobNotFoundError | BackendJobNotCancellableError> {
@@ -135,7 +134,7 @@ export class JobService {
135
134
  this.cancellationRequests.add(job.jobId);
136
135
  this.eventService.emit({ kind: 'job.cancelRequested', sessionId: input.sessionId, canonicalPathKey: session.canonicalPathKey, revision: session.revision, jobId: job.jobId });
137
136
  const cancelled = this.cancelRefreshJob(session.sessionId, job);
138
- return success('runtime', startedAt, cloneJobSnapshot(cancelled), { sessionId: session.sessionId, revision: session.revision });
137
+ return success('runtime', startedAt, structuredClone(cancelled), { sessionId: session.sessionId, revision: session.revision });
139
138
  }
140
139
 
141
140
  public removeSession(sessionId: string): void {
@@ -1,13 +1,12 @@
1
1
  import { failure, success, type BackendContext } from './context.js';
2
2
  import type { BackendCapabilities, BackendResult, BackendSessionSnapshot, SessionNotFoundError } from '../runtime.js';
3
3
  import { createSessionNotFoundError, toSessionSnapshot } from './session-utils.js';
4
- import { cloneCapabilitiesSnapshot } from './snapshot-utils.js';
5
4
 
6
5
  export class ReadService {
7
6
  public constructor(private readonly context: BackendContext) {}
8
7
 
9
8
  public getCapabilities(): BackendResult<BackendCapabilities> {
10
- return success('read', Date.now(), cloneCapabilitiesSnapshot(this.context.capabilities));
9
+ return success('read', Date.now(), structuredClone(this.context.capabilities));
11
10
  }
12
11
 
13
12
  public getSession(input: { sessionId: string }): BackendResult<BackendSessionSnapshot, SessionNotFoundError> {
@@ -5,7 +5,6 @@ import type {
5
5
  SessionStaleWriteError,
6
6
  } from '../runtime.js';
7
7
  import type { BackendSessionState } from './context.js';
8
- import { cloneCapabilitiesSnapshot } from './snapshot-utils.js';
9
8
 
10
9
  export function toSessionSnapshot(
11
10
  session: BackendSessionState,
@@ -19,7 +18,7 @@ export function toSessionSnapshot(
19
18
  dirty: session.dirty,
20
19
  uamFidelity: session.uamFidelity,
21
20
  lockHeld: session.lockHeld,
22
- capabilities: cloneCapabilitiesSnapshot(capabilities),
21
+ capabilities: structuredClone(capabilities),
23
22
  };
24
23
  }
25
24
 
@@ -1,43 +0,0 @@
1
- import type {
2
- BackendCacheEntry,
3
- BackendCapabilities,
4
- BackendEvent,
5
- BackendJobSnapshot,
6
- } from '../runtime.js';
7
-
8
- function cloneJsonValue<T>(value: T): T {
9
- if (value === undefined || value === null) return value;
10
- return JSON.parse(JSON.stringify(value)) as T;
11
- }
12
-
13
- export function cloneEventSnapshot(event: BackendEvent): BackendEvent {
14
- return {
15
- ...event,
16
- diagnostics: event.diagnostics.map((diagnostic) => ({ ...diagnostic })),
17
- payload: cloneJsonValue(event.payload),
18
- };
19
- }
20
-
21
- export function cloneJobSnapshot(job: BackendJobSnapshot): BackendJobSnapshot {
22
- return {
23
- ...job,
24
- diagnostics: job.diagnostics.map((diagnostic) => ({ ...diagnostic })),
25
- progress: job.progress ? { ...job.progress } : undefined,
26
- result: cloneJsonValue(job.result),
27
- error: cloneJsonValue(job.error),
28
- };
29
- }
30
-
31
- export function cloneCacheEntrySnapshot(entry: BackendCacheEntry): BackendCacheEntry {
32
- return {
33
- ...entry,
34
- summary: {
35
- ...entry.summary,
36
- diagnostics: entry.summary.diagnostics.map((diagnostic) => ({ ...diagnostic })),
37
- },
38
- };
39
- }
40
-
41
- export function cloneCapabilitiesSnapshot(capabilities: BackendCapabilities): BackendCapabilities {
42
- return cloneJsonValue(capabilities);
43
- }