@highstate/backend 0.7.8 → 0.7.10

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "sourceHashes": {
3
- "./dist/index.js": "e5a4106585f6ef91330ece00fcd73d51b7bad213ae36b46e9d98c13eb398d65f",
4
- "./dist/shared/index.js": "7356c923eb12cbe487a811f5dae2d7ce79452ee8d4cbe856c5bc0beb9752d738",
5
- "./dist/library/worker/main.js": "5ba7f6868be5fc81df24c54ffb1acafbe50762216c4261d631d041ece1d5a572",
6
- "./dist/library/source-resolution-worker.js": "4589af58d254b32b8a9708528839e1da9450b5ebe8d74cd8d5cf713e52c1304b"
3
+ "./dist/index.js": "e5fc1ecef50b0c434ff63c631362ecbc196b32f1b7c0d92a16b1511df017ad6c",
4
+ "./dist/shared/index.js": "ce531a480a772514e20f94ec9116c98580dba419c8f5507bb85b6cef40a5c1ae",
5
+ "./dist/library/worker/main.js": "11bbed95f67a1756408bcbea93b87af1ed913ebbf7dbd35978027b0b3e219c05",
6
+ "./dist/library/package-resolution-worker.js": "4bfa368ad35a64c109df9e5a468c057791164760fe166c4eb5d9a889dee4d7bc"
7
7
  }
8
8
  }
package/dist/index.js CHANGED
@@ -32,7 +32,6 @@ import {
32
32
  updateResourceCount,
33
33
  valueToString
34
34
  } from "./chunk-EQ4LMS7B.js";
35
- import "./chunk-DGUM43GV.js";
36
35
 
37
36
  // src/secret/abstractions.ts
38
37
  var SecretAccessDeniedError = class extends Error {
@@ -173,49 +172,43 @@ import { z as z4 } from "zod";
173
172
  import { fileURLToPath } from "node:url";
174
173
  import { EventEmitter, on } from "node:events";
175
174
  import { Worker } from "node:worker_threads";
176
- import { basename, dirname, relative, resolve } from "node:path";
175
+ import { resolve } from "node:path";
177
176
  import { readFile } from "node:fs/promises";
178
177
  import { isUnitModel } from "@highstate/contract";
179
178
  import Watcher from "watcher";
180
179
  import { BetterLock } from "better-lock";
181
180
  import { resolve as importMetaResolve } from "import-meta-resolve";
182
181
  import { z as z3 } from "zod";
183
- import { readPackageJSON, resolvePackageJSON } from "pkg-types";
182
+ import { readPackageJSON } from "pkg-types";
183
+ import { runScript, installDependencies, addDependency } from "nypm";
184
+ import { flatMap, groupBy, map, pipe, unique } from "remeda";
184
185
  var localLibraryBackendConfig = z3.object({
185
- HIGHSTATE_BACKEND_LIBRARY_LOCAL_MODULES: stringArrayType.default("@highstate/library"),
186
- HIGHSTATE_BACKEND_LIBRARY_LOCAL_SOURCE_BASE_PATH: z3.string().optional(),
187
- HIGHSTATE_BACKEND_LIBRARY_LOCAL_EXTRA_SOURCE_WATCH_PATHS: stringArrayType.default("")
186
+ HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES: stringArrayType.default("@highstate/library"),
187
+ HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS: stringArrayType.optional()
188
188
  });
189
189
  var LocalLibraryBackend = class _LocalLibraryBackend {
190
- constructor(modulePaths, sourceBasePath, extraSourceWatchPaths, logger) {
191
- this.modulePaths = modulePaths;
192
- this.sourceBasePath = sourceBasePath;
190
+ constructor(libraryPackages, watchPaths, logger) {
191
+ this.libraryPackages = libraryPackages;
193
192
  this.logger = logger;
194
- this.libraryWatcher = new Watcher(modulePaths, { recursive: true, ignoreInitial: true });
195
- this.libraryWatcher.on("all", (event, path) => {
196
- const prefixPath = modulePaths.find((modulePath) => path.startsWith(modulePath));
197
- this.logger.info({ msg: "library event", event, path: relative(prefixPath, path) });
198
- void this.lock.acquire(() => this.updateLibrary());
199
- });
200
- this.sourceWatcher = new Watcher([sourceBasePath, ...extraSourceWatchPaths], {
193
+ this.watcher = new Watcher(watchPaths, {
201
194
  recursive: true,
202
195
  ignoreInitial: true,
203
- ignore: /\.git|node_modules/
196
+ ignore: /\.git|node_modules|dist/
204
197
  });
205
- this.sourceWatcher.on("all", (_, path) => {
206
- if (!path.endsWith("highstate.manifest.json")) {
198
+ this.watcher.on("all", (event, path) => {
199
+ this.logger.debug({ event, path }, "library event");
200
+ if (!path.endsWith(".json") && !path.endsWith(".ts")) {
207
201
  return;
208
202
  }
209
- void this.updateUnitSourceHashes(path);
203
+ void this.handleFileEvent(path);
210
204
  });
211
- this.logger.debug({ msg: "initialized", modulePaths });
212
205
  }
213
- libraryWatcher;
214
- sourceWatcher;
206
+ watcher;
215
207
  lock = new BetterLock();
216
208
  eventEmitter = new EventEmitter();
217
209
  library = null;
218
210
  worker = null;
211
+ packages = /* @__PURE__ */ new Map();
219
212
  resolvedUnitSources = /* @__PURE__ */ new Map();
220
213
  async loadLibrary() {
221
214
  return await this.lock.acquire(async () => {
@@ -228,22 +221,27 @@ var LocalLibraryBackend = class _LocalLibraryBackend {
228
221
  yield library;
229
222
  }
230
223
  }
231
- async getResolvedUnitSources() {
232
- return await this.lock.acquire(async () => {
233
- const [library] = await this.getLibrary();
234
- if (!this.resolvedUnitSources.size) {
235
- await this.syncUnitSources(library);
236
- }
224
+ getLoadedResolvedUnitSources() {
225
+ return this.lock.acquire(() => {
237
226
  return Array.from(this.resolvedUnitSources.values());
238
227
  });
239
228
  }
240
- async getResolvedUnitSource(unitType) {
229
+ async getResolvedUnitSources(unitTypes) {
241
230
  return await this.lock.acquire(async () => {
242
231
  const [library] = await this.getLibrary();
243
- if (!this.resolvedUnitSources.size) {
244
- await this.syncUnitSources(library);
232
+ const units = unitTypes.map((type) => library.components[type]).filter(isUnitModel);
233
+ const packageNames = Object.keys(groupBy(units, (unit) => unit.source.package));
234
+ await this.ensureLibraryPackagesLoaded(packageNames, true);
235
+ const result = [];
236
+ for (const unitType of unitTypes) {
237
+ const resolvedUnitSource = this.resolvedUnitSources.get(unitType);
238
+ if (resolvedUnitSource) {
239
+ result.push(resolvedUnitSource);
240
+ } else {
241
+ this.logger.warn(`resolved unit source not found for unit: "%s"`, unitType);
242
+ }
245
243
  }
246
- return this.resolvedUnitSources.get(unitType) ?? null;
244
+ return result;
247
245
  });
248
246
  }
249
247
  async *watchResolvedUnitSources(signal) {
@@ -253,63 +251,6 @@ var LocalLibraryBackend = class _LocalLibraryBackend {
253
251
  yield resolvedUnitSource;
254
252
  }
255
253
  }
256
- async syncUnitSources(library) {
257
- const unitsToResolve = /* @__PURE__ */ new Map();
258
- for (const component of Object.values(library.components)) {
259
- if (!isUnitModel(component)) {
260
- continue;
261
- }
262
- const existingResolvedSource = this.resolvedUnitSources.get(component.type);
263
- const expectedSource = JSON.stringify(component.source);
264
- if (existingResolvedSource?.serializedSource !== expectedSource) {
265
- unitsToResolve.set(component.type, component);
266
- }
267
- }
268
- await this.runSourceResolution(unitsToResolve);
269
- }
270
- async runSourceResolution(units) {
271
- const workerPathUrl = importMetaResolve(
272
- `@highstate/backend/source-resolution-worker`,
273
- import.meta.url
274
- );
275
- const workerPath = fileURLToPath(workerPathUrl);
276
- const worker = new Worker(workerPath, {
277
- workerData: {
278
- requests: Array.from(units.values()).map((unit) => ({
279
- unitType: unit.type,
280
- source: unit.source
281
- })),
282
- sourceBasePath: this.sourceBasePath,
283
- logLevel: "error"
284
- }
285
- });
286
- for await (const [event] of on(worker, "message")) {
287
- const eventData = event;
288
- if (eventData.type !== "result") {
289
- throw new Error(`Unexpected message type '${eventData.type}', expected 'result'`);
290
- }
291
- for (const result of eventData.results) {
292
- const unit = units.get(result.unitType);
293
- if (!unit) {
294
- this.logger.warn("unit not found for resolved source", { unitType: result.unitType });
295
- continue;
296
- }
297
- const resolvedSource = {
298
- unitType: result.unitType,
299
- serializedSource: JSON.stringify(unit.source),
300
- projectPath: result.projectPath,
301
- packageJsonPath: result.packageJsonPath,
302
- allowedDependencies: result.allowedDependencies,
303
- sourceHash: result.sourceHash
304
- };
305
- this.resolvedUnitSources.set(result.unitType, resolvedSource);
306
- this.eventEmitter.emit("resolvedUnitSource", resolvedSource);
307
- }
308
- this.logger.info("unit sources synced");
309
- return;
310
- }
311
- throw new Error("Worker ended without sending the result");
312
- }
313
254
  async evaluateCompositeInstances(allInstances, resolvedInputs, instanceIds) {
314
255
  return await this.lock.acquire(async () => {
315
256
  this.logger.info("evaluating %d composite instances", instanceIds.length);
@@ -357,11 +298,14 @@ var LocalLibraryBackend = class _LocalLibraryBackend {
357
298
  if (this.library && this.worker) {
358
299
  return [this.library, this.worker];
359
300
  }
360
- return await this.updateLibrary();
301
+ return await this.reloadLibrary();
361
302
  }
362
- async updateLibrary() {
363
- this.logger.info("creating library worker");
364
- this.worker = this.createWorker({ modulePaths: this.modulePaths, logLevel: "silent" });
303
+ async reloadLibrary() {
304
+ this.logger.info("reloading library");
305
+ this.worker = this.createLibraryWorker({
306
+ modulePaths: this.libraryPackages,
307
+ logLevel: "silent"
308
+ });
365
309
  for await (const [event] of on(this.worker, "message")) {
366
310
  const eventData = event;
367
311
  if (eventData.type === "error") {
@@ -377,79 +321,213 @@ var LocalLibraryBackend = class _LocalLibraryBackend {
377
321
  this.eventEmitter.emit("library", updates);
378
322
  this.library = eventData.library;
379
323
  this.logger.info("library reloaded");
380
- await this.syncUnitSources(eventData.library);
381
324
  return [this.library, this.worker];
382
325
  }
383
326
  throw new Error("Worker ended without sending library model");
384
327
  }
385
- createWorker(workerData) {
386
- const workerPathUrl = importMetaResolve(`@highstate/backend/library-worker`, import.meta.url);
387
- const workerPath = fileURLToPath(workerPathUrl);
388
- return new Worker(workerPath, { workerData });
389
- }
390
- async updateUnitSourceHashes(path) {
391
- const packageJsonPath = await resolvePackageJSON(path);
392
- const packageJson = await readPackageJSON(path);
393
- const library = await this.loadLibrary();
394
- const manifestPath = resolve(dirname(packageJsonPath), "dist", "highstate.manifest.json");
395
- let manifest;
396
- try {
397
- manifest = JSON.parse(await readFile(manifestPath, "utf8"));
398
- } catch (error) {
399
- this.logger.debug(
400
- { error },
401
- `failed to read highstate manifest for package "%s"`,
402
- packageJson.name
328
+ async reloadUnitManifest(libraryPackage) {
329
+ const library = this.library;
330
+ if (!library) {
331
+ this.logger.warn(
332
+ `library not loaded, cannot reload unit manifest for package: "%s"`,
333
+ libraryPackage.name
403
334
  );
335
+ return;
404
336
  }
337
+ const manifest = await this.readLibraryPackageManifest(libraryPackage);
338
+ const packageJson = await readPackageJSON(libraryPackage.rootPath);
405
339
  for (const unit of Object.values(library.components)) {
406
340
  if (!isUnitModel(unit)) {
407
341
  continue;
408
342
  }
409
- if (unit.source.package !== packageJson.name) {
343
+ if (unit.source.package !== libraryPackage.name) {
410
344
  continue;
411
345
  }
412
- const relativePath = unit.source.path ? `./dist/${unit.source.path}/index.js` : `./dist/index.js`;
413
- const sourceHash = manifest?.sourceHashes?.[relativePath];
346
+ const relativePath = unit.source.path ? `./dist/${unit.source.path}` : `./dist`;
347
+ const sourceHash = manifest?.sourceHashes?.[`${relativePath}/index.js`];
414
348
  if (!sourceHash) {
415
349
  this.logger.warn(`source hash not found for unit: "%s"`, unit.type);
416
350
  continue;
417
351
  }
418
352
  const resolvedSource = this.resolvedUnitSources.get(unit.type);
419
- if (!resolvedSource) {
420
- this.logger.warn(`resolved source not found for unit: "%s"`, unit.type);
421
- continue;
422
- }
423
353
  const newResolvedSource = {
424
- ...resolvedSource,
425
- sourceHash
354
+ unitType: unit.type,
355
+ sourceHash,
356
+ projectPath: resolve(libraryPackage.rootPath, relativePath),
357
+ allowedDependencies: Object.keys(packageJson.peerDependencies ?? {})
426
358
  };
359
+ if (resolvedSource?.sourceHash === newResolvedSource.sourceHash && resolvedSource?.projectPath === newResolvedSource.projectPath) {
360
+ continue;
361
+ }
427
362
  this.resolvedUnitSources.set(unit.type, newResolvedSource);
428
363
  this.eventEmitter.emit("resolvedUnitSource", newResolvedSource);
429
- this.logger.info(`updated source hash for unit: "%s"`, unit.type);
364
+ this.logger.debug(`updated source for unit: "%s"`, unit.type);
430
365
  }
431
366
  }
367
+ async ensureLibraryPackagesLoaded(names, installIfNotFound = false) {
368
+ const packagesToLoad = names.filter((name) => !this.packages.has(name));
369
+ if (packagesToLoad.length > 0) {
370
+ await this.loadLibraryPackages(packagesToLoad, installIfNotFound);
371
+ }
372
+ }
373
+ async rebuildLibraryPackage(libraryPackage, installDeps = false, updateDeps = false, rebuiltPackages = /* @__PURE__ */ new Set()) {
374
+ if (rebuiltPackages.has(libraryPackage.name)) {
375
+ return;
376
+ }
377
+ rebuiltPackages.add(libraryPackage.name);
378
+ if (installDeps) {
379
+ this.logger.info(`installing dependencies for package "${libraryPackage.name}"`);
380
+ await installDependencies({ cwd: libraryPackage.rootPath });
381
+ }
382
+ if (updateDeps) {
383
+ await this.updateLibraryPackageDependencies(libraryPackage);
384
+ }
385
+ this.logger.info(`rebuilding library package "${libraryPackage.name}" via build script`);
386
+ await runScript("build", { cwd: libraryPackage.rootPath });
387
+ if (this.libraryPackages.includes(libraryPackage.name)) {
388
+ await this.reloadLibrary();
389
+ } else {
390
+ await this.reloadUnitManifest(libraryPackage);
391
+ }
392
+ await this.rebuildLibraryPackageDependents(libraryPackage, rebuiltPackages);
393
+ }
394
+ async updateLibraryPackageDependencies(libraryPackage) {
395
+ const packageJson = await readPackageJSON(libraryPackage.rootPath);
396
+ const parsedName = _LocalLibraryBackend.parseDependencyName(libraryPackage.name);
397
+ const dependencyPackageNames = pipe(
398
+ [packageJson.dependencies, packageJson.devDependencies, packageJson.peerDependencies],
399
+ flatMap((deps) => Object.keys(deps ?? {})),
400
+ unique(),
401
+ map(_LocalLibraryBackend.parseDependencyName)
402
+ );
403
+ const sameScopeDependencies = dependencyPackageNames.filter(
404
+ (dep) => dep.scope === parsedName.scope && dep.name !== parsedName.name
405
+ );
406
+ await this.ensureLibraryPackagesLoaded(sameScopeDependencies.map((dep) => dep.name));
407
+ for (const dependency of sameScopeDependencies) {
408
+ const dependencyPackage = this.packages.get(dependency.name);
409
+ if (!dependencyPackage) {
410
+ this.logger.warn(`dependency package not found for graph update: "%s"`, dependency.name);
411
+ continue;
412
+ }
413
+ libraryPackage.dependencies.add(dependency.name);
414
+ dependencyPackage.dependents.add(libraryPackage.name);
415
+ }
416
+ }
417
+ async rebuildLibraryPackageDependents(libraryPackage, rebuiltPackages = /* @__PURE__ */ new Set()) {
418
+ const promises = [];
419
+ for (const dependent of libraryPackage.dependents) {
420
+ const dependentPackage = this.packages.get(dependent);
421
+ if (!dependentPackage) {
422
+ this.logger.warn(`dependent package not found for rebuild: "%s"`, dependent);
423
+ continue;
424
+ }
425
+ promises.push(this.rebuildLibraryPackage(dependentPackage, false, false, rebuiltPackages));
426
+ }
427
+ await Promise.all(promises);
428
+ }
429
+ static parseDependencyName(dependency) {
430
+ if (dependency.startsWith("@")) {
431
+ const parts = dependency.split("/");
432
+ return {
433
+ name: dependency,
434
+ scope: parts[0]
435
+ };
436
+ }
437
+ return {
438
+ name: dependency,
439
+ scope: null
440
+ };
441
+ }
442
+ async readLibraryPackageManifest(libraryPackage) {
443
+ const manifestPath = resolve(libraryPackage.rootPath, "dist", "highstate.manifest.json");
444
+ try {
445
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
446
+ return manifest;
447
+ } catch (error) {
448
+ this.logger.debug(
449
+ { error },
450
+ `failed to read highstate manifest of package: "%s"`,
451
+ libraryPackage.name
452
+ );
453
+ return void 0;
454
+ }
455
+ }
456
+ async loadLibraryPackages(names, installIfNotFound = false) {
457
+ this.logger.info("loading library packages: %s", names.join(", "));
458
+ const missingPackages = [];
459
+ const packagesToUpdate = [];
460
+ const worker = this.createPackageResolutionWorker({ packageNames: names });
461
+ for await (const [event] of on(worker, "message")) {
462
+ const eventData = event;
463
+ if (eventData.type !== "result") {
464
+ continue;
465
+ }
466
+ for (const result of eventData.results) {
467
+ if (result.type === "success") {
468
+ const libraryPackage = {
469
+ name: result.packageName,
470
+ rootPath: result.packageRootPath,
471
+ dependencies: /* @__PURE__ */ new Set(),
472
+ dependents: /* @__PURE__ */ new Set()
473
+ };
474
+ this.packages.set(result.packageName, libraryPackage);
475
+ packagesToUpdate.push(libraryPackage);
476
+ this.logger.info(`loaded library package: "%s"`, result.packageName);
477
+ } else if (result.type === "not-found") {
478
+ missingPackages.push(result.packageName);
479
+ } else {
480
+ this.logger.error(
481
+ `failed to load library package "%s": %s`,
482
+ result.packageName,
483
+ result.error
484
+ );
485
+ }
486
+ }
487
+ break;
488
+ }
489
+ for (const libraryPackage of packagesToUpdate) {
490
+ await this.updateLibraryPackageDependencies(libraryPackage);
491
+ if (!this.libraryPackages.includes(libraryPackage.name)) {
492
+ await this.reloadUnitManifest(libraryPackage);
493
+ }
494
+ }
495
+ if (installIfNotFound && missingPackages.length > 0) {
496
+ this.logger.info("installing missing library packages: %s", missingPackages.join(", "));
497
+ await addDependency(missingPackages);
498
+ await this.loadLibraryPackages(missingPackages);
499
+ }
500
+ }
501
+ async handleFileEvent(path) {
502
+ await this.lock.acquire(async () => {
503
+ const libraryPackage = this.packages.values().find((pkg) => path.startsWith(pkg.rootPath));
504
+ if (libraryPackage) {
505
+ await this.rebuildLibraryPackage(libraryPackage);
506
+ }
507
+ });
508
+ }
509
+ createLibraryWorker(workerData) {
510
+ const workerPathUrl = importMetaResolve(`@highstate/backend/library-worker`, import.meta.url);
511
+ const workerPath = fileURLToPath(workerPathUrl);
512
+ return new Worker(workerPath, { workerData });
513
+ }
514
+ createPackageResolutionWorker(workerData) {
515
+ const workerPathUrl = importMetaResolve(
516
+ `@highstate/backend/package-resolution-worker`,
517
+ import.meta.url
518
+ );
519
+ const workerPath = fileURLToPath(workerPathUrl);
520
+ return new Worker(workerPath, { workerData });
521
+ }
432
522
  static async create(config, logger) {
433
- const modulePaths = [];
434
- for (const module of config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_MODULES) {
435
- const url = importMetaResolve(module, import.meta.url);
436
- let path = fileURLToPath(url);
437
- if (basename(path).includes(".")) {
438
- path = dirname(path);
439
- }
440
- modulePaths.push(path);
441
- }
442
- let sourceBasePath = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_SOURCE_BASE_PATH;
443
- const extraSourceWatchPaths = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_EXTRA_SOURCE_WATCH_PATHS;
444
- if (!sourceBasePath) {
523
+ let watchPaths = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS;
524
+ if (!watchPaths) {
445
525
  const [projectPath] = await resolveMainLocalProject();
446
- sourceBasePath = resolve(projectPath, "units");
447
- extraSourceWatchPaths.push(projectPath);
526
+ watchPaths = [resolve(projectPath, "packages")];
448
527
  }
449
528
  return new _LocalLibraryBackend(
450
- modulePaths,
451
- sourceBasePath,
452
- extraSourceWatchPaths,
529
+ config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES,
530
+ watchPaths,
453
531
  logger.child({ backend: "LibraryBackend", service: "LocalLibraryBackend" })
454
532
  );
455
533
  }
@@ -481,6 +559,7 @@ import { z as z5 } from "zod";
481
559
  import {
482
560
  getInstanceId
483
561
  } from "@highstate/contract";
562
+ import { stringify, parse } from "yaml";
484
563
  var localProjectBackendConfig = z5.object({
485
564
  HIGHSTATE_BACKEND_PROJECT_PROJECTS_DIR: z5.string().optional()
486
565
  });
@@ -495,7 +574,7 @@ var LocalProjectBackend = class _LocalProjectBackend {
495
574
  async getProjectIds() {
496
575
  try {
497
576
  const files = await readdir(this.projectsDir);
498
- return files.filter((file) => file.endsWith(".json")).map((file) => file.replace(/\.json$/, ""));
577
+ return files.filter((file) => file.endsWith(".yaml")).map((file) => file.replace(/\.yaml$/, ""));
499
578
  } catch (error) {
500
579
  throw new Error("Failed to get project names", { cause: error });
501
580
  }
@@ -732,13 +811,13 @@ var LocalProjectBackend = class _LocalProjectBackend {
732
811
  }
733
812
  }
734
813
  getProjectPath(projectId) {
735
- return `${this.projectsDir}/${projectId}.json`;
814
+ return `${this.projectsDir}/${projectId}.yaml`;
736
815
  }
737
816
  async loadProject(projectId) {
738
817
  const projectPath = this.getProjectPath(projectId);
739
818
  try {
740
819
  const content = await readFile2(projectPath, "utf-8");
741
- return projectModelSchema.parse(JSON.parse(content));
820
+ return projectModelSchema.parse(parse(content));
742
821
  } catch (error) {
743
822
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
744
823
  return { instances: {}, hubs: {} };
@@ -748,7 +827,7 @@ var LocalProjectBackend = class _LocalProjectBackend {
748
827
  }
749
828
  async writeProject(projectId, project) {
750
829
  const projectPath = this.getProjectPath(projectId);
751
- const content = JSON.stringify(project, void 0, 2);
830
+ const content = stringify(project, void 0, 2);
752
831
  await writeFile(projectPath, content);
753
832
  }
754
833
  async withInstance(projectId, instanceId, callback) {
@@ -842,6 +921,30 @@ var ProjectManager = class _ProjectManager {
842
921
  yield children;
843
922
  }
844
923
  }
924
+ /**
925
+ * Loads the full info of a project, including instances, hubs, and composite instances.
926
+ *
927
+ * Also filters out instances that are not in the library.
928
+ *
929
+ * @param projectId The ID of the project to load.
930
+ */
931
+ async getProject(projectId) {
932
+ const [{ instances, hubs }, compositeInstances, library] = await Promise.all([
933
+ this.projectBackend.getProject(projectId),
934
+ this.stateBackend.getCompositeInstances(projectId),
935
+ this.libraryBackend.loadLibrary()
936
+ ]);
937
+ const filteredInstances = instances.filter((instance) => instance.type in library.components);
938
+ const filteredCompositeInstances = compositeInstances.filter((instance) => instance.instance.type in library.components).map((instance) => ({
939
+ ...instance,
940
+ children: instance.children.filter((child) => child.type in library.components)
941
+ }));
942
+ return {
943
+ instances: filteredInstances,
944
+ hubs,
945
+ compositeInstances: filteredCompositeInstances
946
+ };
947
+ }
845
948
  async createInstance(projectId, instance) {
846
949
  const createdInstance = await this.projectBackend.createInstance(projectId, instance);
847
950
  await this.updateCompositeInstance(projectId, createdInstance);
@@ -986,9 +1089,10 @@ var ProjectManager = class _ProjectManager {
986
1089
  }
987
1090
  let sourceHash;
988
1091
  if (isUnitModel2(library.components[instance.type])) {
989
- const resolvedUnit = await this.libraryBackend.getResolvedUnitSource(instance.type);
1092
+ const resolvedUnits = await this.libraryBackend.getResolvedUnitSources([instance.type]);
1093
+ const resolvedUnit = resolvedUnits.find((unit) => unit.unitType === instance.type);
990
1094
  if (!resolvedUnit) {
991
- throw new Error(`Resolved unit not found: ${instance.type}`);
1095
+ throw new Error(`Resolved unit not found for type "${instance.type}"`);
992
1096
  }
993
1097
  sourceHash = resolvedUnit.sourceHash;
994
1098
  }
@@ -1088,7 +1192,7 @@ import { z as z7 } from "zod";
1088
1192
  import spawn from "nano-spawn";
1089
1193
 
1090
1194
  // src/terminal/run.sh.ts
1091
- var runScript = `set -e -o pipefail
1195
+ var runScript2 = `set -e -o pipefail
1092
1196
  read -r data
1093
1197
 
1094
1198
  # Extract env and files as key-value pairs, and command as an array
@@ -1143,7 +1247,7 @@ var DockerTerminalBackend = class _DockerTerminalBackend {
1143
1247
  const hsTempDir = resolve3(tmpdir(), "highstate");
1144
1248
  await mkdir2(hsTempDir, { recursive: true });
1145
1249
  const runScriptPath = resolve3(hsTempDir, "run.sh");
1146
- await writeFile2(runScriptPath, runScript, { mode: 493 });
1250
+ await writeFile2(runScriptPath, runScript2, { mode: 493 });
1147
1251
  const args = [
1148
1252
  "run",
1149
1253
  "-i",
@@ -1630,10 +1734,7 @@ var LocalRunnerBackend = class _LocalRunnerBackend {
1630
1734
  async updateWorker(options, configMap, preview) {
1631
1735
  const instanceId = _LocalRunnerBackend.getInstanceId(options);
1632
1736
  try {
1633
- const resolvedSource = await this.libraryBackend.getResolvedUnitSource(options.instanceType);
1634
- if (!resolvedSource) {
1635
- throw new Error(`Resolved unit source not found for ${options.instanceType}`);
1636
- }
1737
+ const resolvedSource = await this.getResolvedUnitSource(options.instanceType);
1637
1738
  await this.pulumiProjectHost.runLocal(
1638
1739
  {
1639
1740
  projectId: options.projectId,
@@ -1737,7 +1838,7 @@ var LocalRunnerBackend = class _LocalRunnerBackend {
1737
1838
  async destroyWorker(options) {
1738
1839
  const instanceId = _LocalRunnerBackend.getInstanceId(options);
1739
1840
  try {
1740
- const resolvedSource = await this.libraryBackend.getResolvedUnitSource(options.instanceType);
1841
+ const resolvedSource = await this.getResolvedUnitSource(options.instanceType);
1741
1842
  if (!resolvedSource) {
1742
1843
  throw new Error(`Resolved unit source not found for ${options.instanceType}`);
1743
1844
  }
@@ -1980,6 +2081,14 @@ var LocalRunnerBackend = class _LocalRunnerBackend {
1980
2081
  await ensureDependencyInstalled(packageName);
1981
2082
  return true;
1982
2083
  }
2084
+ async getResolvedUnitSource(instanceType) {
2085
+ const sources = await this.libraryBackend.getResolvedUnitSources([instanceType]);
2086
+ const source = sources.find((source2) => source2.unitType === instanceType);
2087
+ if (!source) {
2088
+ throw new Error(`Resolved unit source not found for ${instanceType}`);
2089
+ }
2090
+ return source;
2091
+ }
1983
2092
  static getStackName(options) {
1984
2093
  return `${options.projectId}_${options.instanceName}`;
1985
2094
  }
@@ -2538,6 +2647,7 @@ import { mapValues as mapValues3 } from "remeda";
2538
2647
 
2539
2648
  // src/orchestrator/operation-workset.ts
2540
2649
  import { isUnitModel as isUnitModel3 } from "@highstate/contract";
2650
+ import { unique as unique2 } from "remeda";
2541
2651
  var OperationWorkset = class _OperationWorkset {
2542
2652
  constructor(operation, library, stateManager, logger) {
2543
2653
  this.operation = operation;
@@ -2788,13 +2898,15 @@ var OperationWorkset = class _OperationWorkset {
2788
2898
  return Array.from(instanceIds);
2789
2899
  }
2790
2900
  static async load(operation, projectBackend, libraryBackend, stateBackend, stateManager, logger, signal) {
2791
- const [library, unitSources, project, compositeInstances, states] = await Promise.all([
2901
+ const [library, project, compositeInstances, states] = await Promise.all([
2792
2902
  libraryBackend.loadLibrary(signal),
2793
- libraryBackend.getResolvedUnitSources(),
2794
2903
  projectBackend.getProject(operation.projectId, signal),
2795
2904
  stateBackend.getCompositeInstances(operation.projectId, signal),
2796
2905
  stateBackend.getAllInstanceStates(operation.projectId, signal)
2797
2906
  ]);
2907
+ const unitSources = await libraryBackend.getResolvedUnitSources(
2908
+ unique2(project.instances.map((i) => i.type))
2909
+ );
2798
2910
  const workset = new _OperationWorkset(
2799
2911
  operation,
2800
2912
  library,