@highstate/backend 0.9.8 → 0.9.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.
@@ -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": "2ccc50f29dbff43b4367fc8651bad5755599ad1ef2367b2d44c692e70d16d0f8",
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);
@@ -2732,11 +2737,11 @@ var OperationWorkset = class _OperationWorkset {
2732
2737
  }
2733
2738
  return this.instanceIdsToUpdate.has(instanceId);
2734
2739
  }
2735
- updateState(update) {
2740
+ updateState(update, phase) {
2736
2741
  const finalState = applyPartialInstanceState(this.stateMap, update);
2737
2742
  this.stateManager.emitStatePatch(this.operation.projectId, createInstanceStatePatch(update));
2738
2743
  if (finalState.parentId) {
2739
- this.recalculateCompositeInstanceState(finalState.parentId);
2744
+ this.recalculateCompositeInstanceState(finalState.parentId, phase);
2740
2745
  }
2741
2746
  return finalState;
2742
2747
  }
@@ -2812,7 +2817,7 @@ var OperationWorkset = class _OperationWorkset {
2812
2817
  }
2813
2818
  return Array.from(dependentStateIds).map((id) => this.stateMap.get(id)).filter((state) => !!state);
2814
2819
  }
2815
- recalculateCompositeInstanceState(instanceId) {
2820
+ recalculateCompositeInstanceState(instanceId, phase) {
2816
2821
  const state = this.stateMap.get(instanceId) ?? createInstanceState(instanceId);
2817
2822
  let currentResourceCount = 0;
2818
2823
  let totalResourceCount = 0;
@@ -2828,13 +2833,14 @@ var OperationWorkset = class _OperationWorkset {
2828
2833
  }
2829
2834
  const updatedState = {
2830
2835
  ...state,
2836
+ status: _OperationWorkset.getStatusByOperationPhase(phase),
2831
2837
  currentResourceCount,
2832
2838
  totalResourceCount
2833
2839
  };
2834
2840
  this.stateMap.set(instanceId, updatedState);
2835
2841
  this.stateManager.emitStatePatch(this.operation.projectId, updatedState);
2836
2842
  if (state.parentId) {
2837
- this.recalculateCompositeInstanceState(state.parentId);
2843
+ this.recalculateCompositeInstanceState(state.parentId, phase);
2838
2844
  }
2839
2845
  }
2840
2846
  addInstance(instance) {
@@ -2873,7 +2879,7 @@ var OperationWorkset = class _OperationWorkset {
2873
2879
  }
2874
2880
  }
2875
2881
  const state = this.stateMap.get(instance.id);
2876
- const { inputHash: expectedInputHash } = await this.inputHashResolver(instance.id);
2882
+ const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(instance.id);
2877
2883
  if (this.operation.options.forceUpdateDependencies) {
2878
2884
  this.instanceIdsToUpdate.add(instanceId);
2879
2885
  return;
@@ -2899,7 +2905,7 @@ var OperationWorkset = class _OperationWorkset {
2899
2905
  continue;
2900
2906
  }
2901
2907
  const state = this.stateMap.get(child.id);
2902
- const { inputHash: expectedInputHash } = await this.inputHashResolver(child.id);
2908
+ const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(child.id);
2903
2909
  if (state?.status !== "created" || state.inputHash !== expectedInputHash) {
2904
2910
  this.instanceIdsToUpdate.add(child.id);
2905
2911
  }
@@ -2983,18 +2989,31 @@ var OperationWorkset = class _OperationWorkset {
2983
2989
  }
2984
2990
  return void 0;
2985
2991
  }
2992
+ static getStatusByOperationPhase(phase) {
2993
+ switch (phase) {
2994
+ case "update":
2995
+ return "updating";
2996
+ case "destroy":
2997
+ return "destroying";
2998
+ case "refresh":
2999
+ return "refreshing";
3000
+ }
3001
+ }
2986
3002
  async getUpToDateInputHash(instance) {
2987
- const component = this.library.components[instance.type];
2988
- this.inputHashResolverInputs.set(instance.id, {
2989
- instance,
2990
- component,
2991
- resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
2992
- state: this.stateMap.get(instance.id),
2993
- 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;
2994
3016
  });
2995
- this.inputHashResolverPromiseCache.delete(instance.id);
2996
- const { inputHash } = await this.inputHashResolver(instance.id);
2997
- return inputHash;
2998
3017
  }
2999
3018
  getLockInstanceIds() {
3000
3019
  return Array.from(this.instanceIdsToUpdate.union(this.instanceIdsToDestroy));
@@ -3006,18 +3025,12 @@ var OperationWorkset = class _OperationWorkset {
3006
3025
  stateBackend.getCompositeInstances(operation.projectId, signal),
3007
3026
  stateBackend.getAllInstanceStates(operation.projectId, signal)
3008
3027
  ]);
3009
- const unitSources = await libraryBackend.getResolvedUnitSources(
3010
- unique2(project.instances.map((i) => i.type))
3011
- );
3012
3028
  const workset = new _OperationWorkset(
3013
3029
  operation,
3014
3030
  library,
3015
3031
  stateManager,
3016
3032
  logger.child({ operationId: operation.id, service: "OperationWorkset" })
3017
3033
  );
3018
- for (const unitSource of unitSources) {
3019
- workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash);
3020
- }
3021
3034
  for (const instance of project.instances) {
3022
3035
  workset.addInstance(instance);
3023
3036
  }
@@ -3040,6 +3053,12 @@ var OperationWorkset = class _OperationWorkset {
3040
3053
  }
3041
3054
  }
3042
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
+ }
3043
3062
  for (const state of states) {
3044
3063
  if (!workset.instanceMap.has(state.id)) {
3045
3064
  workset.logger.warn(
@@ -3050,29 +3069,26 @@ var OperationWorkset = class _OperationWorkset {
3050
3069
  workset.setState(state);
3051
3070
  }
3052
3071
  for (const instance of workset.instanceMap.values()) {
3053
- workset.inputResolverInputs.set(`instance:${instance.id}`, {
3072
+ workset.inputResolverNodes.set(`instance:${instance.id}`, {
3054
3073
  kind: "instance",
3055
3074
  instance,
3056
3075
  component: library.components[instance.type]
3057
3076
  });
3058
3077
  }
3059
3078
  for (const hub of project.hubs) {
3060
- workset.inputResolverInputs.set(`hub:${hub.id}`, { kind: "hub", hub });
3079
+ workset.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub });
3061
3080
  }
3062
- workset.inputResolver = createInputResolver(
3063
- //
3064
- workset.inputResolverInputs,
3065
- logger,
3066
- { promiseCache: workset.inputResolverPromiseCache }
3067
- );
3081
+ workset.inputResolver = new InputResolver(workset.inputResolverNodes, logger);
3082
+ workset.inputResolver.addAllNodesToWorkset();
3083
+ await workset.inputResolver.process();
3068
3084
  for (const instance of workset.instanceMap.values()) {
3069
- const output = await workset.inputResolver(`instance:${instance.id}`);
3085
+ const output = workset.inputResolver.requireOutput(`instance:${instance.id}`);
3070
3086
  if (output.kind !== "instance") {
3071
3087
  throw new Error("Unexpected output kind");
3072
3088
  }
3073
3089
  workset.resolvedInstanceInputs.set(instance.id, output.resolvedInputs);
3074
3090
  const component = workset.library.components[instance.type];
3075
- workset.inputHashResolverInputs.set(instance.id, {
3091
+ workset.inputHashNodes.set(instance.id, {
3076
3092
  instance,
3077
3093
  component,
3078
3094
  resolvedInputs: output.resolvedInputs,
@@ -3080,12 +3096,9 @@ var OperationWorkset = class _OperationWorkset {
3080
3096
  sourceHash: workset.getSourceHashIfApplicable(instance, component)
3081
3097
  });
3082
3098
  }
3083
- workset.inputHashResolver = createInputHashResolver(
3084
- //
3085
- workset.inputHashResolverInputs,
3086
- logger,
3087
- { promiseCache: workset.inputHashResolverPromiseCache }
3088
- );
3099
+ workset.inputHashResolver = new InputHashResolver(workset.inputHashNodes, logger);
3100
+ workset.inputHashResolver.addAllNodesToWorkset();
3101
+ await workset.inputHashResolver.process();
3089
3102
  if (operation.type !== "destroy") {
3090
3103
  await workset.calculateInstanceIdsToUpdate();
3091
3104
  }
@@ -3248,7 +3261,7 @@ var RuntimeOperation = class {
3248
3261
  ...state,
3249
3262
  parentId: instance?.parentId,
3250
3263
  latestOperationId: this.operation.id,
3251
- status: this.getStatusByOperationType(),
3264
+ status: "pending",
3252
3265
  error: null
3253
3266
  });
3254
3267
  const children = this.workset.getAffectedCompositeChildren(instanceId, this.currentPhase);
@@ -3515,7 +3528,7 @@ var RuntimeOperation = class {
3515
3528
  }
3516
3529
  return;
3517
3530
  }
3518
- const state = this.workset.updateState(patch);
3531
+ const state = this.workset.updateState(patch, this.currentPhase);
3519
3532
  if (this.operation.type !== "preview") {
3520
3533
  this.persistStates.call(state);
3521
3534
  if (patch.secrets) {
@@ -3533,20 +3546,6 @@ var RuntimeOperation = class {
3533
3546
  this.instancePromiseMap.set(instanceId, instancePromise);
3534
3547
  return instancePromise;
3535
3548
  }
3536
- getStatusByOperationType() {
3537
- switch (this.operation.type) {
3538
- case "update":
3539
- return "updating";
3540
- case "preview":
3541
- return "previewing";
3542
- case "recreate":
3543
- return "updating";
3544
- case "destroy":
3545
- return "destroying";
3546
- case "refresh":
3547
- return "refreshing";
3548
- }
3549
- }
3550
3549
  getInstanceDependencies(instance) {
3551
3550
  const dependencies = [];
3552
3551
  const instanceInputs = this.workset.resolvedInstanceInputs.get(instance.id) ?? {};