@highstate/backend 0.9.9 → 0.9.11

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.
@@ -43,61 +43,6 @@ function errorToString(error) {
43
43
  }
44
44
  return JSON.stringify(error);
45
45
  }
46
- function createAsyncBatcher(fn, { waitMs = 100, maxWaitTimeMs = 1e3 } = {}) {
47
- let batch = [];
48
- let activeTimeout = null;
49
- let maxWaitTimeout = null;
50
- let firstCallTimestamp = null;
51
- async function processBatch() {
52
- if (batch.length === 0) return;
53
- const currentBatch = batch;
54
- batch = [];
55
- await fn(currentBatch);
56
- if (maxWaitTimeout) {
57
- clearTimeout(maxWaitTimeout);
58
- maxWaitTimeout = null;
59
- }
60
- firstCallTimestamp = null;
61
- }
62
- function schedule() {
63
- if (activeTimeout) clearTimeout(activeTimeout);
64
- activeTimeout = setTimeout(() => {
65
- activeTimeout = null;
66
- void processBatch();
67
- }, waitMs);
68
- if (!firstCallTimestamp) {
69
- firstCallTimestamp = Date.now();
70
- maxWaitTimeout = setTimeout(() => {
71
- if (activeTimeout) clearTimeout(activeTimeout);
72
- activeTimeout = null;
73
- void processBatch();
74
- }, maxWaitTimeMs);
75
- }
76
- }
77
- return {
78
- /**
79
- * Add an item to the batch.
80
- */
81
- call(item) {
82
- batch.push(item);
83
- schedule();
84
- },
85
- /**
86
- * Immediately flush the pending batch (if any).
87
- */
88
- async flush() {
89
- if (activeTimeout) {
90
- clearTimeout(activeTimeout);
91
- activeTimeout = null;
92
- }
93
- if (maxWaitTimeout) {
94
- clearTimeout(maxWaitTimeout);
95
- maxWaitTimeout = null;
96
- }
97
- await processBatch();
98
- }
99
- };
100
- }
101
46
 
102
47
  // src/common/pulumi.ts
103
48
  import { BetterLock } from "better-lock";
@@ -280,11 +225,10 @@ export {
280
225
  tryWrapAbortErrorLike,
281
226
  stringArrayType,
282
227
  errorToString,
283
- createAsyncBatcher,
284
228
  LocalPulumiHost,
285
229
  valueToString,
286
230
  stringToValue,
287
231
  updateResourceCount,
288
232
  resolveMainLocalProject
289
233
  };
290
- //# sourceMappingURL=chunk-EQ4LMS7B.js.map
234
+ //# sourceMappingURL=chunk-WXDYCRTT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/common/utils.ts","../src/common/pulumi.ts","../src/common/local.ts"],"sourcesContent":["import { z } from \"zod\"\n\nexport async function runWithRetryOnError<T>(\n runner: () => T | Promise<T>,\n tryHandleError: (error: unknown) => boolean | Promise<boolean>,\n maxRetries: number = 1,\n): Promise<T> {\n let lastError: unknown\n\n for (let i = 0; i < maxRetries + 1; i++) {\n try {\n return await runner()\n } catch (e) {\n lastError = e\n\n if (await tryHandleError(e)) {\n continue\n }\n\n throw e\n }\n }\n\n throw lastError\n}\n\nexport class AbortError extends Error {\n constructor(options?: ErrorOptions) {\n super(\"Operation aborted\", options)\n }\n}\n\nexport function isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\"\n}\n\nconst abortMessagePatterns = [\"Operation aborted\", \"Command was killed with SIGINT\"]\n\nexport function isAbortErrorLike(error: unknown): boolean {\n if (error instanceof Error) {\n return abortMessagePatterns.some(pattern => error.message.includes(pattern))\n }\n\n return false\n}\n\nexport function tryWrapAbortErrorLike(error: unknown): unknown {\n if (isAbortErrorLike(error)) {\n return new AbortError({ cause: error })\n }\n\n return error\n}\n\nexport const stringArrayType = z.string().transform(args => args.split(\",\").map(arg => arg.trim()))\n\nexport function errorToString(error: unknown): string {\n if (error instanceof Error) {\n return error.stack || error.message\n }\n\n return JSON.stringify(error)\n}\n","import type { ConfigMap, OpMap, OpType, Stack, WhoAmIResult } from \"@pulumi/pulumi/automation\"\nimport type { Logger } from \"pino\"\nimport { BetterLock } from \"better-lock\"\nimport { AbortError, runWithRetryOnError } from \"./utils\"\n\nexport type RunOptions = {\n projectId: string\n pulumiProjectName: string\n pulumiStackName: string\n envVars?: Record<string, string>\n}\n\nexport type RunLocalOptions = RunOptions & {\n projectPath: string\n stackConfig?: ConfigMap\n}\n\nexport class LocalPulumiHost {\n private lock = new BetterLock()\n\n private constructor(private readonly logger: Logger) {}\n\n async getCurrentUser(): Promise<WhoAmIResult | null> {\n const { LocalWorkspace } = await import(\"@pulumi/pulumi/automation/index.js\")\n const workspace = await LocalWorkspace.create({})\n\n try {\n return await workspace.whoAmI()\n } catch (error) {\n this.logger.error({ msg: \"failed to get current user\", error })\n\n return null\n }\n }\n\n async runEmpty<T>(\n options: RunOptions,\n fn: (stack: Stack) => Promise<T>,\n signal?: AbortSignal,\n ): Promise<T> {\n const { projectId, pulumiProjectName, pulumiStackName, envVars } = options\n\n return await this.lock.acquire(`${pulumiProjectName}.${pulumiStackName}`, async () => {\n const { LocalWorkspace } = await import(\"@pulumi/pulumi/automation/index.js\")\n\n const stack = await LocalWorkspace.createOrSelectStack(\n {\n projectName: pulumiProjectName,\n stackName: pulumiStackName,\n program: () => Promise.resolve(),\n },\n {\n projectSettings: {\n name: pulumiProjectName,\n runtime: \"nodejs\",\n },\n envVars: {\n PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),\n PULUMI_K8S_AWAIT_ALL: \"true\",\n ...envVars,\n },\n },\n )\n\n signal?.throwIfAborted()\n\n try {\n return await runWithRetryOnError(\n () => fn(stack),\n error => this.tryUnlockStack(stack, error),\n )\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"canceled\")) {\n throw new AbortError()\n }\n\n throw e\n }\n })\n }\n\n async runLocal<T>(\n options: RunLocalOptions,\n fn: (stack: Stack) => Promise<T>,\n signal?: AbortSignal,\n ): Promise<T> {\n const { projectId, pulumiProjectName, pulumiStackName, projectPath, stackConfig, envVars } =\n options\n\n return await this.lock.acquire(`${pulumiProjectName}.${pulumiStackName}`, async () => {\n const { LocalWorkspace } = await import(\"@pulumi/pulumi/automation/index.js\")\n\n const stack = await LocalWorkspace.createOrSelectStack(\n {\n stackName: pulumiStackName,\n workDir: projectPath,\n },\n {\n projectSettings: {\n name: pulumiProjectName,\n runtime: \"nodejs\",\n },\n stackSettings: stackConfig\n ? {\n [pulumiStackName]: {\n config: stackConfig,\n },\n }\n : undefined,\n envVars: {\n PULUMI_CONFIG_PASSPHRASE: this.getPassword(projectId),\n PULUMI_K8S_AWAIT_ALL: \"true\",\n ...envVars,\n },\n },\n )\n\n signal?.throwIfAborted()\n\n try {\n return await runWithRetryOnError(\n () => fn(stack),\n error => this.tryUnlockStack(stack, error),\n )\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"canceled\")) {\n throw new AbortError()\n }\n\n throw e\n }\n })\n }\n\n private sharedPassword: string = process.env.PULUMI_CONFIG_PASSPHRASE ?? \"\"\n private passwords = new Map<string, string>()\n\n hasPassword(projectId: string) {\n return !!this.sharedPassword || this.passwords.has(projectId)\n }\n\n setPassword(projectId: string, password: string) {\n this.passwords.set(projectId, password)\n }\n\n removePassword(projectId: string) {\n this.passwords.delete(projectId)\n }\n\n private getPassword(projectId: string) {\n return this.sharedPassword || this.passwords.get(projectId) || \"\"\n }\n\n async tryUnlockStack(stack: Stack, error: unknown) {\n if (error instanceof Error && error.message.includes(\"the stack is currently locked\")) {\n // TODO: kill the process if the hostname matches the current hostname\n\n this.logger.warn({ stackName: stack.name }, \"inlocking stack\")\n await stack.cancel()\n return true\n }\n\n return false\n }\n\n static create(logger: Logger) {\n return new LocalPulumiHost(logger.child({ service: \"LocalPulumiHost\" }))\n }\n}\n\nexport function valueToString(value: unknown): string {\n if (typeof value === \"string\") {\n return value\n }\n\n return JSON.stringify(value)\n}\n\nexport function stringToValue(value: string): unknown {\n try {\n return JSON.parse(value)\n } catch {\n return value\n }\n}\n\nexport function updateResourceCount(opType: OpType, currentCount: number): number {\n switch (opType) {\n case \"same\":\n case \"create\":\n case \"update\":\n case \"replace\":\n case \"create-replacement\":\n case \"import\":\n case \"import-replacement\":\n return currentCount + 1\n\n case \"delete\":\n case \"delete-replaced\":\n case \"discard\":\n case \"discard-replaced\":\n case \"remove-pending-replace\":\n return currentCount - 1\n\n case \"refresh\":\n case \"read-replacement\":\n case \"read\":\n return currentCount\n\n default:\n throw new Error(`Unknown operation type: ${opType as string}`)\n }\n}\n\nexport function calculateTotalResources(opMap: OpMap | undefined): number {\n if (!opMap) {\n return 0 // No operations imply no resources\n }\n\n let total = 0\n\n for (const [op, count] of Object.entries(opMap)) {\n const opType = op as OpType\n const value = count ?? 0\n\n total = updateResourceCount(opType, value)\n }\n\n return total\n}\n","import { basename } from \"node:path\"\nimport { findWorkspaceDir, readPackageJSON } from \"pkg-types\"\n\nexport async function resolveMainLocalProject(\n projectPath?: string,\n projectName?: string,\n): Promise<[projecPath: string, projectName: string]> {\n if (!projectPath) {\n projectPath = await findWorkspaceDir()\n }\n\n if (!projectName) {\n const packageJson = await readPackageJSON(projectPath)\n projectName = packageJson.name\n }\n\n if (!projectName) {\n projectName = basename(projectPath)\n }\n\n return [projectPath, projectName]\n}\n"],"mappings":"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","names":[]}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "sourceHashes": {
3
- "./dist/index.js": "bac9fe2cacea1c7b42619b6950c9516162da5dba571a6d8282fbe880645b7f97",
4
- "./dist/shared/index.js": "8d015e5f855592ea97cbcf2152042d87f82087b1576d4e667d0cc0e873e68927",
5
- "./dist/library/worker/main.js": "2acb09c0340ce9d3f6825ff0c7d2d8a08596f378aa41df211e464257f463497c",
3
+ "./dist/index.js": "65eb8a0ed4774e1bbd2a2a6176b0840b2fec9c515a620b90a963900a3cd5e155",
4
+ "./dist/shared/index.js": "0d314d61379d99c55057d507979d2f49119b0da831fbbc8681fccf05d99a4d5c",
5
+ "./dist/library/worker/main.js": "4e24e61811d3f605da65f70dc14cd2585f551e28773d96686cc188e66fe57650",
6
6
  "./dist/library/package-resolution-worker.js": "4bfa368ad35a64c109df9e5a468c057791164760fe166c4eb5d9a889dee4d7bc"
7
7
  }
8
8
  }
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
+ InputHashResolver,
3
+ InputResolver,
2
4
  applyPartialInstanceState,
3
5
  compositeInstanceSchema,
4
- createInputHashResolver,
5
- createInputResolver,
6
+ createAsyncBatcher,
6
7
  createInstanceState,
7
8
  createInstanceStatePatch,
8
9
  diffLibraries,
@@ -17,10 +18,9 @@ import {
17
18
  isFinalOperationStatus,
18
19
  projectOperationSchema,
19
20
  terminalSessionSchema
20
- } from "./chunk-NMGIUI6X.js";
21
+ } from "./chunk-DQDXRDUA.js";
21
22
  import {
22
23
  LocalPulumiHost,
23
- createAsyncBatcher,
24
24
  errorToString,
25
25
  isAbortError,
26
26
  isAbortErrorLike,
@@ -31,7 +31,7 @@ import {
31
31
  tryWrapAbortErrorLike,
32
32
  updateResourceCount,
33
33
  valueToString
34
- } from "./chunk-EQ4LMS7B.js";
34
+ } from "./chunk-WXDYCRTT.js";
35
35
 
36
36
  // src/secret/abstractions.ts
37
37
  var SecretAccessDeniedError = class extends Error {
@@ -950,17 +950,17 @@ var ProjectManager = class _ProjectManager {
950
950
  }
951
951
  async createInstance(projectId, instance) {
952
952
  const createdInstance = await this.projectBackend.createInstance(projectId, instance);
953
- await this.updateCompositeInstance(projectId, createdInstance);
953
+ await this.evaluateChangedCompositeInstances(projectId);
954
954
  return createdInstance;
955
955
  }
956
956
  async updateInstance(projectId, instanceId, patch) {
957
957
  const instance = await this.projectBackend.updateInstance(projectId, instanceId, patch);
958
- await this.updateCompositeInstance(projectId, instance);
958
+ await this.evaluateChangedCompositeInstances(projectId);
959
959
  return instance;
960
960
  }
961
961
  async renameInstance(projectId, instanceId, newName) {
962
962
  const instance = await this.projectBackend.renameInstance(projectId, instanceId, newName);
963
- await this.updateCompositeInstance(projectId, instance);
963
+ await this.evaluateChangedCompositeInstances(projectId);
964
964
  return instance;
965
965
  }
966
966
  async deleteInstance(projectId, instanceId) {
@@ -969,40 +969,27 @@ var ProjectManager = class _ProjectManager {
969
969
  this.stateBackend.clearCompositeInstances(projectId, [instanceId])
970
970
  ]);
971
971
  }
972
- async updateCompositeInstance(projectId, instance) {
973
- const { resolveInputHash, library } = await this.prepareInputHashResolver(projectId);
974
- const component = library.components[instance.type];
975
- if (!component) {
976
- return;
977
- }
978
- if (isUnitModel2(component)) {
979
- return;
980
- }
981
- const { inputHash: expectedInputHash } = await resolveInputHash(instance.id);
982
- const inputHash = await this.stateBackend.getCompositeInstanceInputHash(projectId, instance.id);
983
- if (inputHash !== expectedInputHash) {
984
- this.logger.info("re-evaluating instance since input hash has changed", {
985
- projectId,
986
- instanceId: instance.id
987
- });
988
- await this.evaluateCompositeInstances(projectId, [instance.id]);
989
- }
990
- }
991
- async evaluateCompositeInstances(projectId, instanceIds) {
972
+ async evaluateChangedCompositeInstances(projectId) {
973
+ const { inputHashResolver, instances, library, evaluatedInputHashes } = await this.prepareResolvers(projectId);
974
+ inputHashResolver.addAllNodesToWorkset();
975
+ await inputHashResolver.process();
976
+ const instanceIds = instances.filter((instance) => !isUnitModel2(library.components[instance.type])).filter(
977
+ (instance) => evaluatedInputHashes[instance.id] !== inputHashResolver.requireOutput(instance.id).inputHash
978
+ ).map((instance) => instance.id);
992
979
  await this.projectLockManager.getLock(projectId).lockInstances(instanceIds, async () => {
993
980
  this.logger.debug({ instanceIds }, "evaluating composite instances");
994
981
  for (const instanceId of instanceIds) {
995
982
  this.compositeInstanceEE.emit(projectId, { type: "evaluation-started", instanceId });
996
983
  }
997
984
  const [
998
- { instances, resolvedInputs, stateMap, resolveInputHash },
985
+ { instances: instances2, resolvedInputs, stateMap, inputHashResolver: inputHashResolver2 },
999
986
  topLevelCompositeChildrenIds
1000
987
  ] = await Promise.all([
1001
- this.prepareInputHashResolver(projectId),
988
+ this.prepareResolvers(projectId),
1002
989
  this.stateBackend.getTopLevelCompositeChildrenIds(projectId, instanceIds)
1003
990
  ]);
1004
991
  const results = await this.libraryBackend.evaluateCompositeInstances(
1005
- instances,
992
+ instances2,
1006
993
  resolvedInputs,
1007
994
  instanceIds
1008
995
  );
@@ -1012,15 +999,15 @@ var ProjectManager = class _ProjectManager {
1012
999
  newState.evaluationError = result.success ? null : result.error;
1013
1000
  return newState;
1014
1001
  });
1015
- const inputHashes = /* @__PURE__ */ new Map();
1016
- for (const instanceId of instanceIds) {
1017
- const { inputHash } = await resolveInputHash(instanceId);
1018
- inputHashes.set(instanceId, inputHash);
1019
- }
1002
+ inputHashResolver2.addAllNodesToWorkset();
1003
+ await inputHashResolver2.process();
1020
1004
  const compositeInstances = results.filter((result) => result.success).flatMap(
1021
1005
  (result) => result.compositeInstances.map((instance) => ({
1022
1006
  ...instance,
1023
- inputHash: inputHashes.get(instance.instance.id)
1007
+ inputHash: (
1008
+ // only store inputHash for top-level composite instances
1009
+ instance.instance.id === result.instanceId ? inputHashResolver2.requireOutput(instance.instance.id).inputHash : ""
1010
+ )
1024
1011
  }))
1025
1012
  );
1026
1013
  const newTopLevelCompositeChildrenIds = Object.fromEntries(
@@ -1047,6 +1034,16 @@ var ProjectManager = class _ProjectManager {
1047
1034
  ]);
1048
1035
  for (const state of newStates) {
1049
1036
  this.stateManager.emitStatePatch(projectId, state);
1037
+ if (state.evaluationError) {
1038
+ this.logger.error(
1039
+ { projectId, instanceId: state.id, error: state.evaluationError },
1040
+ "instance evaluation failed"
1041
+ );
1042
+ this.compositeInstanceEE.emit(projectId, {
1043
+ type: "failed",
1044
+ instanceId: state.id
1045
+ });
1046
+ }
1050
1047
  }
1051
1048
  for (const instance of compositeInstances) {
1052
1049
  this.compositeInstanceEE.emit(projectId, { type: "updated", instance });
@@ -1073,11 +1070,14 @@ var ProjectManager = class _ProjectManager {
1073
1070
  await Promise.all(promises);
1074
1071
  });
1075
1072
  }
1076
- async prepareInputHashResolver(projectId) {
1077
- const { instances, hubs } = await this.projectBackend.getProject(projectId);
1078
- const library = await this.libraryBackend.loadLibrary();
1073
+ async prepareResolvers(projectId) {
1074
+ const [{ instances, hubs }, states, library, evaluatedInputHashes] = await Promise.all([
1075
+ this.projectBackend.getProject(projectId),
1076
+ this.stateBackend.getAllInstanceStates(projectId),
1077
+ this.libraryBackend.loadLibrary(),
1078
+ this.stateBackend.getCompositeInstanceInputHashes(projectId)
1079
+ ]);
1079
1080
  const filteredInstances = instances.filter((instance) => instance.type in library.components);
1080
- const states = await this.stateBackend.getAllInstanceStates(projectId);
1081
1081
  const stateMap = new Map(states.map((state) => [state.id, state]));
1082
1082
  const inputResolverNodes = /* @__PURE__ */ new Map();
1083
1083
  for (const instance of filteredInstances) {
@@ -1090,11 +1090,13 @@ var ProjectManager = class _ProjectManager {
1090
1090
  for (const hub of hubs) {
1091
1091
  inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub });
1092
1092
  }
1093
- const resolveInputs = createInputResolver(inputResolverNodes, this.logger);
1093
+ const inputResolver = new InputResolver(inputResolverNodes, this.logger);
1094
+ inputResolver.addAllNodesToWorkset();
1094
1095
  const inputHashInputs = /* @__PURE__ */ new Map();
1095
1096
  const resolvedInputs = {};
1097
+ await inputResolver.process();
1096
1098
  for (const instance of filteredInstances) {
1097
- const output = await resolveInputs(`instance:${instance.id}`);
1099
+ const output = inputResolver.requireOutput(`instance:${instance.id}`);
1098
1100
  if (output.kind !== "instance") {
1099
1101
  throw new Error("Expected instance node");
1100
1102
  }
@@ -1118,13 +1120,15 @@ var ProjectManager = class _ProjectManager {
1118
1120
  });
1119
1121
  resolvedInputs[instance.id] = output.resolvedInputs;
1120
1122
  }
1121
- const resolveInputHash = createInputHashResolver(inputHashInputs, this.logger);
1123
+ const inputHashResolver = new InputHashResolver(inputHashInputs, this.logger);
1122
1124
  return {
1123
- resolveInputHash,
1125
+ inputHashInputs,
1126
+ inputHashResolver,
1124
1127
  library,
1125
- instances,
1128
+ instances: filteredInstances,
1126
1129
  stateMap,
1127
- resolvedInputs
1130
+ resolvedInputs,
1131
+ evaluatedInputHashes
1128
1132
  };
1129
1133
  }
1130
1134
  async watchLibraryChanges() {
@@ -1158,7 +1162,7 @@ var ProjectManager = class _ProjectManager {
1158
1162
  );
1159
1163
  const projects = await this.projectBackend.getProjectIds();
1160
1164
  for (const projectId of projects) {
1161
- const { resolveInputHash, instances } = await this.prepareInputHashResolver(projectId);
1165
+ const { instances } = await this.prepareResolvers(projectId);
1162
1166
  const filteredInstances = instances.filter(
1163
1167
  (instance) => changedComponents.has(instance.type) && library.components[instance.type] && !isUnitModel2(library.components[instance.type])
1164
1168
  );
@@ -1166,16 +1170,8 @@ var ProjectManager = class _ProjectManager {
1166
1170
  { projectId, filteredInstanceIds: filteredInstances.map((instance) => instance.id) },
1167
1171
  "updating composite instances for project"
1168
1172
  );
1169
- const inputHashMap = /* @__PURE__ */ new Map();
1170
- for (const instance of filteredInstances) {
1171
- const { inputHash } = await resolveInputHash(instance.id);
1172
- inputHashMap.set(instance.id, inputHash);
1173
- }
1174
1173
  try {
1175
- await this.evaluateCompositeInstances(
1176
- projectId,
1177
- filteredInstances.map((instance) => instance.id)
1178
- );
1174
+ await this.evaluateChangedCompositeInstances(projectId);
1179
1175
  } catch (error) {
1180
1176
  this.logger.error({ error }, "failed to evaluate composite instances");
1181
1177
  }
@@ -2296,10 +2292,13 @@ var LocalStateBackend = class _LocalStateBackend {
2296
2292
  const sublevel = this.getProjectCompositeInstancesSublevel(projectId);
2297
2293
  return this.getSublevelItem(sublevel, compositeInstanceSchema, instanceId);
2298
2294
  }
2299
- async getCompositeInstanceInputHash(projectId, instanceId) {
2295
+ async getCompositeInstanceInputHashes(projectId) {
2300
2296
  const sublevel = this.getProjectCompositeInstanceInputHashesSublevel(projectId);
2301
- const inputHash = await sublevel.get(instanceId);
2302
- return inputHash ?? null;
2297
+ const result = {};
2298
+ for await (const [key, value] of sublevel.iterator()) {
2299
+ result[key] = value;
2300
+ }
2301
+ return result;
2303
2302
  }
2304
2303
  async putCompositeInstances(projectId, instances) {
2305
2304
  this.validateArray(compositeInstanceSchema, instances);
@@ -2312,6 +2311,12 @@ var LocalStateBackend = class _LocalStateBackend {
2312
2311
  key: instance.instance.id,
2313
2312
  value: compositeInstanceSchema.parse(instance),
2314
2313
  sublevel
2314
+ },
2315
+ {
2316
+ type: "put",
2317
+ key: instance.instance.id,
2318
+ value: instance.inputHash ?? "",
2319
+ sublevel: inputHashesSublevel
2315
2320
  }
2316
2321
  ])
2317
2322
  );
@@ -2687,6 +2692,7 @@ import {
2687
2692
  parseInstanceId as parseInstanceId2
2688
2693
  } from "@highstate/contract";
2689
2694
  import { unique as unique2 } from "remeda";
2695
+ import { BetterLock as BetterLock3 } from "better-lock";
2690
2696
  var OperationWorkset = class _OperationWorkset {
2691
2697
  constructor(operation, library, stateManager, logger) {
2692
2698
  this.operation = operation;
@@ -2704,11 +2710,10 @@ var OperationWorkset = class _OperationWorkset {
2704
2710
  stateChildIdMap = /* @__PURE__ */ new Map();
2705
2711
  unitSourceHashMap = /* @__PURE__ */ new Map();
2706
2712
  inputResolver;
2707
- inputResolverInputs = /* @__PURE__ */ new Map();
2708
- inputResolverPromiseCache = /* @__PURE__ */ new Map();
2713
+ inputResolverNodes = /* @__PURE__ */ new Map();
2709
2714
  inputHashResolver;
2710
- inputHashResolverInputs = /* @__PURE__ */ new Map();
2711
- inputHashResolverPromiseCache = /* @__PURE__ */ new Map();
2715
+ inputHashNodes = /* @__PURE__ */ new Map();
2716
+ inputHashResolverLock = new BetterLock3();
2712
2717
  resolvedInstanceInputs = /* @__PURE__ */ new Map();
2713
2718
  getInstance(instanceId) {
2714
2719
  const instance = this.instanceMap.get(instanceId);
@@ -2874,7 +2879,7 @@ var OperationWorkset = class _OperationWorkset {
2874
2879
  }
2875
2880
  }
2876
2881
  const state = this.stateMap.get(instance.id);
2877
- const { inputHash: expectedInputHash } = await this.inputHashResolver(instance.id);
2882
+ const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(instance.id);
2878
2883
  if (this.operation.options.forceUpdateDependencies) {
2879
2884
  this.instanceIdsToUpdate.add(instanceId);
2880
2885
  return;
@@ -2900,7 +2905,7 @@ var OperationWorkset = class _OperationWorkset {
2900
2905
  continue;
2901
2906
  }
2902
2907
  const state = this.stateMap.get(child.id);
2903
- const { inputHash: expectedInputHash } = await this.inputHashResolver(child.id);
2908
+ const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(child.id);
2904
2909
  if (state?.status !== "created" || state.inputHash !== expectedInputHash) {
2905
2910
  this.instanceIdsToUpdate.add(child.id);
2906
2911
  }
@@ -2995,17 +3000,20 @@ var OperationWorkset = class _OperationWorkset {
2995
3000
  }
2996
3001
  }
2997
3002
  async getUpToDateInputHash(instance) {
2998
- const component = this.library.components[instance.type];
2999
- this.inputHashResolverInputs.set(instance.id, {
3000
- instance,
3001
- component,
3002
- resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
3003
- state: this.stateMap.get(instance.id),
3004
- sourceHash: this.getSourceHashIfApplicable(instance, component)
3003
+ return await this.inputHashResolverLock.acquire(async () => {
3004
+ const component = this.library.components[instance.type];
3005
+ this.inputHashNodes.set(instance.id, {
3006
+ instance,
3007
+ component,
3008
+ resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
3009
+ state: this.stateMap.get(instance.id),
3010
+ sourceHash: this.getSourceHashIfApplicable(instance, component)
3011
+ });
3012
+ this.inputHashResolver.invalidate(instance.id);
3013
+ await this.inputHashResolver.process();
3014
+ const { inputHash } = this.inputHashResolver.requireOutput(instance.id);
3015
+ return inputHash;
3005
3016
  });
3006
- this.inputHashResolverPromiseCache.delete(instance.id);
3007
- const { inputHash } = await this.inputHashResolver(instance.id);
3008
- return inputHash;
3009
3017
  }
3010
3018
  getLockInstanceIds() {
3011
3019
  return Array.from(this.instanceIdsToUpdate.union(this.instanceIdsToDestroy));
@@ -3017,18 +3025,12 @@ var OperationWorkset = class _OperationWorkset {
3017
3025
  stateBackend.getCompositeInstances(operation.projectId, signal),
3018
3026
  stateBackend.getAllInstanceStates(operation.projectId, signal)
3019
3027
  ]);
3020
- const unitSources = await libraryBackend.getResolvedUnitSources(
3021
- unique2(project.instances.map((i) => i.type))
3022
- );
3023
3028
  const workset = new _OperationWorkset(
3024
3029
  operation,
3025
3030
  library,
3026
3031
  stateManager,
3027
3032
  logger.child({ operationId: operation.id, service: "OperationWorkset" })
3028
3033
  );
3029
- for (const unitSource of unitSources) {
3030
- workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash);
3031
- }
3032
3034
  for (const instance of project.instances) {
3033
3035
  workset.addInstance(instance);
3034
3036
  }
@@ -3051,6 +3053,12 @@ var OperationWorkset = class _OperationWorkset {
3051
3053
  }
3052
3054
  }
3053
3055
  }
3056
+ const unitSources = await libraryBackend.getResolvedUnitSources(
3057
+ unique2(Array.from(workset.instanceMap.values().map((i) => i.type)))
3058
+ );
3059
+ for (const unitSource of unitSources) {
3060
+ workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash);
3061
+ }
3054
3062
  for (const state of states) {
3055
3063
  if (!workset.instanceMap.has(state.id)) {
3056
3064
  workset.logger.warn(
@@ -3061,29 +3069,26 @@ var OperationWorkset = class _OperationWorkset {
3061
3069
  workset.setState(state);
3062
3070
  }
3063
3071
  for (const instance of workset.instanceMap.values()) {
3064
- workset.inputResolverInputs.set(`instance:${instance.id}`, {
3072
+ workset.inputResolverNodes.set(`instance:${instance.id}`, {
3065
3073
  kind: "instance",
3066
3074
  instance,
3067
3075
  component: library.components[instance.type]
3068
3076
  });
3069
3077
  }
3070
3078
  for (const hub of project.hubs) {
3071
- workset.inputResolverInputs.set(`hub:${hub.id}`, { kind: "hub", hub });
3079
+ workset.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub });
3072
3080
  }
3073
- workset.inputResolver = createInputResolver(
3074
- //
3075
- workset.inputResolverInputs,
3076
- logger,
3077
- { promiseCache: workset.inputResolverPromiseCache }
3078
- );
3081
+ workset.inputResolver = new InputResolver(workset.inputResolverNodes, logger);
3082
+ workset.inputResolver.addAllNodesToWorkset();
3083
+ await workset.inputResolver.process();
3079
3084
  for (const instance of workset.instanceMap.values()) {
3080
- const output = await workset.inputResolver(`instance:${instance.id}`);
3085
+ const output = workset.inputResolver.requireOutput(`instance:${instance.id}`);
3081
3086
  if (output.kind !== "instance") {
3082
3087
  throw new Error("Unexpected output kind");
3083
3088
  }
3084
3089
  workset.resolvedInstanceInputs.set(instance.id, output.resolvedInputs);
3085
3090
  const component = workset.library.components[instance.type];
3086
- workset.inputHashResolverInputs.set(instance.id, {
3091
+ workset.inputHashNodes.set(instance.id, {
3087
3092
  instance,
3088
3093
  component,
3089
3094
  resolvedInputs: output.resolvedInputs,
@@ -3091,12 +3096,9 @@ var OperationWorkset = class _OperationWorkset {
3091
3096
  sourceHash: workset.getSourceHashIfApplicable(instance, component)
3092
3097
  });
3093
3098
  }
3094
- workset.inputHashResolver = createInputHashResolver(
3095
- //
3096
- workset.inputHashResolverInputs,
3097
- logger,
3098
- { promiseCache: workset.inputHashResolverPromiseCache }
3099
- );
3099
+ workset.inputHashResolver = new InputHashResolver(workset.inputHashNodes, logger);
3100
+ workset.inputHashResolver.addAllNodesToWorkset();
3101
+ await workset.inputHashResolver.process();
3100
3102
  if (operation.type !== "destroy") {
3101
3103
  await workset.calculateInstanceIdsToUpdate();
3102
3104
  }