@highstate/backend 0.9.31 → 0.9.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.
@@ -53,7 +53,7 @@ function evaluateProject(logger2, components, allInstances, resolvedInputs) {
53
53
  }
54
54
  function _evaluateInstance(instance) {
55
55
  const inputs = {};
56
- logger2.debug("evaluating instance", { instanceId: instance.id });
56
+ logger2.debug(`evaluating instance "%s"`, instance.id);
57
57
  for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {
58
58
  inputs[inputName] = input.map((input2) => {
59
59
  const evaluated = evaluateInstance(input2.input.instanceId);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/library/worker/evaluator.ts","../../../src/library/worker/loader.lite.ts","../../../src/library/worker/main.ts"],"names":["logger","error","input"],"mappings":"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","file":"main.js","sourcesContent":["import type { Logger } from \"pino\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport type { ProjectEvaluationResult } from \"../abstractions\"\nimport {\n type Component,\n getRuntimeInstances,\n type InstanceModel,\n InstanceNameConflictError,\n} from \"@highstate/contract\"\nimport { errorToString } from \"../../common\"\n\nexport function evaluateProject(\n logger: Logger,\n components: Readonly<Record<string, Component>>,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n): ProjectEvaluationResult {\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n const instanceErrors: Record<string, unknown> = {}\n const topLevelErrors: Record<string, string> = {}\n\n const instanceOutputs = new Map<string, Record<string, unknown>>()\n\n for (const instance of allInstances) {\n try {\n evaluateInstance(instance.id)\n } catch (error) {\n if (error instanceof InstanceNameConflictError) {\n // fail the whole evaluation if there's a name conflict\n return {\n success: false,\n error: error.message,\n }\n }\n }\n }\n\n return {\n success: true,\n\n virtualInstances: getRuntimeInstances()\n .map(instance => instance.instance)\n // only include top-level composite instances and their children\n .filter(instance => instance.kind === \"composite\" || !allInstancesMap.has(instance.id)),\n\n topLevelErrors,\n }\n\n function evaluateInstance(instanceId: InstanceModel[\"id\"]): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n if (outputs) {\n return outputs\n }\n\n // do not evaluate instance if it has an error, just rethrow it\n const error = instanceErrors[instanceId]\n if (error) {\n // eslint-disable-next-line @typescript-eslint/only-throw-error\n throw error\n }\n\n const instance = allInstancesMap.get(instanceId)\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n try {\n outputs = _evaluateInstance(instance)\n\n instanceOutputs.set(instanceId, outputs)\n return outputs\n } catch (error) {\n if (instance.kind === \"composite\" || !allInstancesMap.has(instance.id)) {\n topLevelErrors[instance.id] = errorToString(error)\n }\n\n instanceErrors[instanceId] = error\n throw error\n }\n }\n\n function _evaluateInstance(instance: InstanceModel): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n\n logger.debug(\"evaluating instance\", { instanceId: instance.id })\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instance.id}`)\n }\n\n return component({\n name: instance.name,\n args: instance.args as Record<string, never>,\n inputs: inputs as Record<string, never>,\n })\n }\n}\n","import type { Logger } from \"pino\"\nimport { type Component, isComponent } from \"@highstate/contract\"\n\nexport async function loadComponents(\n logger: Logger,\n modulePaths: string[],\n): Promise<Readonly<Record<string, Component>>> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await import(modulePath)\n\n logger.debug({ modulePath }, \"module loaded\")\n } catch (err) {\n logger.error({ modulePath, err }, \"module load failed\")\n }\n }\n\n const components: Record<string, Component> = {}\n\n await _loadLibrary(modules, components)\n logger.debug(\"library loaded with %s components\", Object.keys(components).length)\n\n return components\n}\n\nasync function _loadLibrary(value: unknown, components: Record<string, Component>): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n if (\"_zod\" in value) {\n // this is a zod schema, we can skip it\n return\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n await _loadLibrary(item, components)\n }\n\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components)\n }\n}\n","import type { WorkerData } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { pino } from \"pino\"\nimport { errorToString } from \"../../common\"\nimport { evaluateProject } from \"./evaluator\"\nimport { loadComponents } from \"./loader.lite\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\" })\n\ntry {\n const library = await loadComponents(logger, data.libraryModulePaths)\n const result = evaluateProject(logger, library, data.allInstances, data.resolvedInputs)\n\n parentPort?.postMessage(result)\n} catch (error) {\n logger.error({ error }, \"failed to evaluate project\")\n\n parentPort?.postMessage({\n success: false,\n error: errorToString(error),\n })\n}\n"]}
1
+ {"version":3,"sources":["../../../src/library/worker/evaluator.ts","../../../src/library/worker/loader.lite.ts","../../../src/library/worker/main.ts"],"names":["logger","error","input"],"mappings":";;;;;;AAWO,SAAS,eAAA,CACdA,OAAAA,EACA,UAAA,EACA,YAAA,EACA,cAAA,EACyB;AACzB,EAAA,MAAM,eAAA,GAAkB,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,CAAA,QAAA,KAAY,CAAC,QAAA,CAAS,EAAA,EAAI,QAAQ,CAAC,CAAC,CAAA;AAErF,EAAA,MAAM,iBAA0C,EAAC;AACjD,EAAA,MAAM,iBAAyC,EAAC;AAEhD,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAqC;AAEjE,EAAA,KAAA,MAAW,YAAY,YAAA,EAAc;AACnC,IAAA,IAAI;AACF,MAAA,gBAAA,CAAiB,SAAS,EAAE,CAAA;AAAA,IAC9B,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiB,yBAAA,EAA2B;AAE9C,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,KAAA;AAAA,UACT,OAAO,KAAA,CAAM;AAAA,SACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,IAAA;AAAA,IAET,kBAAkB,mBAAA,EAAoB,CACnC,IAAI,CAAA,QAAA,KAAY,QAAA,CAAS,QAAQ,CAAA,CAEjC,MAAA,CAAO,CAAA,QAAA,KAAY,QAAA,CAAS,SAAS,WAAA,IAAe,CAAC,gBAAgB,GAAA,CAAI,QAAA,CAAS,EAAE,CAAC,CAAA;AAAA,IAExF;AAAA,GACF;AAEA,EAAA,SAAS,iBAAiB,UAAA,EAA0D;AAClF,IAAA,IAAI,OAAA,GAAU,eAAA,CAAgB,GAAA,CAAI,UAAU,CAAA;AAC5C,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,OAAO,OAAA;AAAA,IACT;AAGA,IAAA,MAAM,KAAA,GAAQ,eAAe,UAAU,CAAA;AACvC,IAAA,IAAI,KAAA,EAAO;AAET,MAAA,MAAM,KAAA;AAAA,IACR;AAEA,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,GAAA,CAAI,UAAU,CAAA;AAC/C,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,UAAU,CAAA,CAAE,CAAA;AAAA,IACrD;AAEA,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,kBAAkB,QAAQ,CAAA;AAEpC,MAAA,eAAA,CAAgB,GAAA,CAAI,YAAY,OAAO,CAAA;AACvC,MAAA,OAAO,OAAA;AAAA,IACT,SAASC,MAAAA,EAAO;AACd,MAAA,IAAI,QAAA,CAAS,SAAS,WAAA,IAAe,CAAC,gBAAgB,GAAA,CAAI,QAAA,CAAS,EAAE,CAAA,EAAG;AACtE,QAAA,cAAA,CAAe,QAAA,CAAS,EAAE,CAAA,GAAI,aAAA,CAAcA,MAAK,CAAA;AAAA,MACnD;AAEA,MAAA,cAAA,CAAe,UAAU,CAAA,GAAIA,MAAAA;AAC7B,MAAA,MAAMA,MAAAA;AAAA,IACR;AAAA,EACF;AAEA,EAAA,SAAS,kBAAkB,QAAA,EAAkD;AAC3E,IAAA,MAAM,SAAkC,EAAC;AAEzC,IAAAD,OAAAA,CAAO,KAAA,CAAM,CAAA,wBAAA,CAAA,EAA4B,QAAA,CAAS,EAAE,CAAA;AAEpD,IAAA,KAAA,MAAW,CAAC,SAAA,EAAW,KAAK,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,cAAA,CAAe,QAAA,CAAS,EAAE,CAAA,IAAK,EAAE,CAAA,EAAG;AAClF,MAAA,MAAA,CAAO,SAAS,CAAA,GAAI,KAAA,CAAM,GAAA,CAAI,CAAAE,MAAAA,KAAS;AACrC,QAAA,MAAM,SAAA,GAAY,gBAAA,CAAiBA,MAAAA,CAAM,KAAA,CAAM,UAAU,CAAA;AAEzD,QAAA,OAAO,SAAA,CAAUA,MAAAA,CAAM,KAAA,CAAM,MAAM,CAAA;AAAA,MACrC,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,MAAM,SAAA,GAAY,UAAA,CAAW,QAAA,CAAS,IAAI,CAAA;AAC1C,IAAA,IAAI,CAAC,SAAA,EAAW;AACd,MAAA,MAAM,IAAI,MAAM,CAAA,qBAAA,EAAwB,QAAA,CAAS,IAAI,CAAA,wBAAA,EAA2B,QAAA,CAAS,EAAE,CAAA,CAAE,CAAA;AAAA,IAC/F;AAEA,IAAA,OAAO,SAAA,CAAU;AAAA,MACf,MAAM,QAAA,CAAS,IAAA;AAAA,MACf,MAAM,QAAA,CAAS,IAAA;AAAA,MACf;AAAA,KACD,CAAA;AAAA,EACH;AACF;ACvGA,eAAsB,cAAA,CACpBF,SACA,WAAA,EAC8C;AAC9C,EAAA,MAAM,UAAmC,EAAC;AAC1C,EAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AACpC,IAAA,IAAI;AACF,MAAAA,OAAAA,CAAO,KAAA,CAAM,EAAE,UAAA,IAAc,gBAAgB,CAAA;AAC7C,MAAA,OAAA,CAAQ,UAAU,CAAA,GAAI,MAAM,OAAO,UAAA,CAAA;AAEnC,MAAAA,OAAAA,CAAO,KAAA,CAAM,EAAE,UAAA,IAAc,eAAe,CAAA;AAAA,IAC9C,SAAS,GAAA,EAAK;AACZ,MAAAA,QAAO,KAAA,CAAM,EAAE,UAAA,EAAY,GAAA,IAAO,oBAAoB,CAAA;AAAA,IACxD;AAAA,EACF;AAEA,EAAA,MAAM,aAAwC,EAAC;AAE/C,EAAA,MAAM,YAAA,CAAa,SAAS,UAAU,CAAA;AACtC,EAAAA,QAAO,KAAA,CAAM,mCAAA,EAAqC,OAAO,IAAA,CAAK,UAAU,EAAE,MAAM,CAAA;AAEhF,EAAA,OAAO,UAAA;AACT;AAEA,eAAe,YAAA,CAAa,OAAgB,UAAA,EAAsD;AAChG,EAAA,IAAI,WAAA,CAAY,KAAK,CAAA,EAAG;AACtB,IAAA,UAAA,CAAW,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA;AAC/B,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAC/C,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,UAAU,KAAA,EAAO;AAEnB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,YAAA,CAAa,MAAM,UAAU,CAAA;AAAA,IACrC;AAEA,IAAA;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,OAAO,KAAA,EAAO;AACvB,IAAA,MAAM,YAAA,CAAc,KAAA,CAAkC,GAAG,CAAA,EAAG,UAAU,CAAA;AAAA,EACxE;AACF;;;AC9CA,IAAM,IAAA,GAAO,UAAA;AAEb,IAAM,MAAA,GAAS,IAAA,CAAK,EAAE,IAAA,EAAM,kBAAkB,CAAA;AAE9C,IAAI;AACF,EAAA,MAAM,OAAA,GAAU,MAAM,cAAA,CAAe,MAAA,EAAQ,KAAK,kBAAkB,CAAA;AACpE,EAAA,MAAM,SAAS,eAAA,CAAgB,MAAA,EAAQ,SAAS,IAAA,CAAK,YAAA,EAAc,KAAK,cAAc,CAAA;AAEtF,EAAA,UAAA,EAAY,YAAY,MAAM,CAAA;AAChC,CAAA,CAAA,OAAS,KAAA,EAAO;AACd,EAAA,MAAA,CAAO,KAAA,CAAM,EAAE,KAAA,EAAM,EAAG,4BAA4B,CAAA;AAEpD,EAAA,UAAA,EAAY,WAAA,CAAY;AAAA,IACtB,OAAA,EAAS,KAAA;AAAA,IACT,KAAA,EAAO,cAAc,KAAK;AAAA,GAC3B,CAAA;AACH","file":"main.js","sourcesContent":["import type { Logger } from \"pino\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport type { ProjectEvaluationResult } from \"../abstractions\"\nimport {\n type Component,\n getRuntimeInstances,\n type InstanceModel,\n InstanceNameConflictError,\n} from \"@highstate/contract\"\nimport { errorToString } from \"../../common\"\n\nexport function evaluateProject(\n logger: Logger,\n components: Readonly<Record<string, Component>>,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n): ProjectEvaluationResult {\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n const instanceErrors: Record<string, unknown> = {}\n const topLevelErrors: Record<string, string> = {}\n\n const instanceOutputs = new Map<string, Record<string, unknown>>()\n\n for (const instance of allInstances) {\n try {\n evaluateInstance(instance.id)\n } catch (error) {\n if (error instanceof InstanceNameConflictError) {\n // fail the whole evaluation if there's a name conflict\n return {\n success: false,\n error: error.message,\n }\n }\n }\n }\n\n return {\n success: true,\n\n virtualInstances: getRuntimeInstances()\n .map(instance => instance.instance)\n // only include top-level composite instances and their children\n .filter(instance => instance.kind === \"composite\" || !allInstancesMap.has(instance.id)),\n\n topLevelErrors,\n }\n\n function evaluateInstance(instanceId: InstanceModel[\"id\"]): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n if (outputs) {\n return outputs\n }\n\n // do not evaluate instance if it has an error, just rethrow it\n const error = instanceErrors[instanceId]\n if (error) {\n // eslint-disable-next-line @typescript-eslint/only-throw-error\n throw error\n }\n\n const instance = allInstancesMap.get(instanceId)\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n try {\n outputs = _evaluateInstance(instance)\n\n instanceOutputs.set(instanceId, outputs)\n return outputs\n } catch (error) {\n if (instance.kind === \"composite\" || !allInstancesMap.has(instance.id)) {\n topLevelErrors[instance.id] = errorToString(error)\n }\n\n instanceErrors[instanceId] = error\n throw error\n }\n }\n\n function _evaluateInstance(instance: InstanceModel): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n\n logger.debug(`evaluating instance \"%s\"`, instance.id)\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instance.id}`)\n }\n\n return component({\n name: instance.name,\n args: instance.args as Record<string, never>,\n inputs: inputs as Record<string, never>,\n })\n }\n}\n","import type { Logger } from \"pino\"\nimport { type Component, isComponent } from \"@highstate/contract\"\n\nexport async function loadComponents(\n logger: Logger,\n modulePaths: string[],\n): Promise<Readonly<Record<string, Component>>> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await import(modulePath)\n\n logger.debug({ modulePath }, \"module loaded\")\n } catch (err) {\n logger.error({ modulePath, err }, \"module load failed\")\n }\n }\n\n const components: Record<string, Component> = {}\n\n await _loadLibrary(modules, components)\n logger.debug(\"library loaded with %s components\", Object.keys(components).length)\n\n return components\n}\n\nasync function _loadLibrary(value: unknown, components: Record<string, Component>): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n if (\"_zod\" in value) {\n // this is a zod schema, we can skip it\n return\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n await _loadLibrary(item, components)\n }\n\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components)\n }\n}\n","import type { WorkerData } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { pino } from \"pino\"\nimport { errorToString } from \"../../common\"\nimport { evaluateProject } from \"./evaluator\"\nimport { loadComponents } from \"./loader.lite\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\" })\n\ntry {\n const library = await loadComponents(logger, data.libraryModulePaths)\n const result = evaluateProject(logger, library, data.allInstances, data.resolvedInputs)\n\n parentPort?.postMessage(result)\n} catch (error) {\n logger.error({ error }, \"failed to evaluate project\")\n\n parentPort?.postMessage({\n success: false,\n error: errorToString(error),\n })\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { AccessError, BackendError, BackendUnlockMethodNotFoundError, CannotDeleteLastBackendUnlockMethodError, CannotDeleteLastUnlockMethodError, GraphResolver, InputHashResolver, InputResolver, InstanceLockLostError, InstanceLockedError, InstanceNotFoundError, InstanceStateNotFoundError, InvalidInstanceKindError, MAX_WORKER_START_ATTEMPTS, OperationNotFoundError, ProjectLockedError, ProjectNotFoundError, PromiseTracker, SystemSecretNames, ValidationResolver, WorkerVersionNotFoundError, apiKeyMetaSchema, apiKeyOutputSchema, apiKeyQuerySchema, applyLibraryUpdate, artifactOutputSchema, artifactQuerySchema, backendUnlockMethodInputSchema, backendUnlockMethodMetaSchema, codebaseLibrary, codebaseProjectModelStorage, collectionQueryResult, collectionQuerySchema, createAsyncBatcher, databaseProjectModelStorage, diffLibraries, extractDigestFromImage, finalInstanceOperationStatuses, finalOperationStatuses, forSchema, getAllDependents, getMatchedInjectionInstanceInputs, getResolvedHubInputs, getResolvedInjectionInstanceInputs, getResolvedInstanceInputs, getResolvedInstanceOutputs, getWorkerIdentity, globalProjectSpace, hasObjectMeta, hostPulumiBackend, instanceCustomStatusInputSchema, instanceLockEventSchema, instanceLockOutputSchema, instanceStateEventSchema, int32ToBytes, isFinalOperationStatus, isInstanceDeployed, isTransientInstanceOperationStatus, isTransientOperationStatus, isVirtualGhostInstance, librarySpecSchema, operationEventSchema, operationLaunchInputSchema, operationMetaSchema, operationOptionsSchema, operationOutputSchema, operationPhaseInstanceSchema, operationPhaseSchema, operationPhaseTypeSchema, operationPlanInputSchema, operationStatusSchema, operationTypeSchema, pageDetailsOutputSchema, pageOutputSchema, pageQuerySchema, projectInputSchema, projectModelEventSchema, projectModelStorageSpecSchema, projectOutputSchema, projectUnlockStateSchema, projectUnlockSuiteSchema, pulumiBackendSpecSchema, resolverFactories, secretOutputSchema, secretQuerySchema, serviceAccountOutputSchema, serviceAccountQuerySchema, terminalDetailsOutputSchema, terminalOutputSchema, terminalQuerySchema, terminalSessionOutputSchema, terminalStatusSchema, toApiKeyOutput, toPageOutput, toSecretOutput, toTerminalDetailsOutput, toTerminalOutput, toTerminalSessionOutput, toWorkerOutput, toWorkerVersionOutput, triggerOutputSchema, triggerQuerySchema, unlockMethodInputSchema, unlockMethodMetaSchema, unlockMethodOutputSchema, unlockMethodType, waitAll, workerOutputSchema, workerQuerySchema, workerUnitRegistrationEventSchema, workerVersionOutputSchema, workerVersionStatusSchema } from '../chunk-QSHSXLO2.js';
1
+ export { AccessError, BackendError, BackendUnlockMethodNotFoundError, CannotDeleteLastBackendUnlockMethodError, CannotDeleteLastUnlockMethodError, GraphResolver, InputHashResolver, InputResolver, InstanceLockLostError, InstanceLockedError, InstanceNotFoundError, InstanceStateNotFoundError, InvalidInstanceKindError, MAX_WORKER_START_ATTEMPTS, OperationNotFoundError, ProjectLockedError, ProjectNotFoundError, PromiseTracker, SystemSecretNames, ValidationResolver, WorkerVersionNotFoundError, apiKeyMetaSchema, apiKeyOutputSchema, apiKeyQuerySchema, applyLibraryUpdate, artifactOutputSchema, artifactQuerySchema, backendUnlockMethodInputSchema, backendUnlockMethodMetaSchema, codebaseLibrary, codebaseProjectModelStorage, collectionQueryResult, collectionQuerySchema, createAsyncBatcher, databaseProjectModelStorage, diffLibraries, extractDigestFromImage, finalInstanceOperationStatuses, finalOperationStatuses, forSchema, getAllDependents, getMatchedInjectionInstanceInputs, getResolvedHubInputs, getResolvedInjectionInstanceInputs, getResolvedInstanceInputs, getResolvedInstanceOutputs, getWorkerIdentity, globalProjectSpace, hasObjectMeta, hostPulumiBackend, instanceCustomStatusInputSchema, instanceLockEventSchema, instanceLockOutputSchema, instanceStateEventSchema, int32ToBytes, isFinalOperationStatus, isInstanceDeployed, isTransientInstanceOperationStatus, isTransientOperationStatus, isVirtualGhostInstance, librarySpecSchema, operationEventSchema, operationLaunchInputSchema, operationMetaSchema, operationOptionsSchema, operationOutputSchema, operationPhaseInstanceSchema, operationPhaseSchema, operationPhaseTypeSchema, operationPlanInputSchema, operationStatusSchema, operationTypeSchema, pageDetailsOutputSchema, pageOutputSchema, pageQuerySchema, projectInputSchema, projectModelEventSchema, projectModelStorageSpecSchema, projectOutputSchema, projectUnlockStateSchema, projectUnlockSuiteSchema, pulumiBackendSpecSchema, resolverFactories, secretOutputSchema, secretQuerySchema, serviceAccountOutputSchema, serviceAccountQuerySchema, stableInstanceInputSchema, terminalDetailsOutputSchema, terminalOutputSchema, terminalQuerySchema, terminalSessionOutputSchema, terminalStatusSchema, toApiKeyOutput, toPageOutput, toSecretOutput, toTerminalDetailsOutput, toTerminalOutput, toTerminalSessionOutput, toWorkerOutput, toWorkerVersionOutput, triggerOutputSchema, triggerQuerySchema, unlockMethodInputSchema, unlockMethodMetaSchema, unlockMethodOutputSchema, unlockMethodType, waitAll, workerOutputSchema, workerQuerySchema, workerUnitRegistrationEventSchema, workerVersionOutputSchema, workerVersionStatusSchema } from '../chunk-ZRKVL3EL.js';
2
2
  import '../chunk-I7BWSAN6.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend",
3
- "version": "0.9.31",
3
+ "version": "0.9.32",
4
4
  "type": "module",
5
5
  "highstate": {
6
6
  "sourceHash": {
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@aws-crypto/crc32": "^5.2.0",
44
- "@highstate/contract": "^0.9.31",
44
+ "@highstate/contract": "^0.9.32",
45
45
  "@msgpack/msgpack": "^3.1.2",
46
46
  "@napi-rs/keyring": "^1.1.8",
47
47
  "@noble/ciphers": "^1.3.0",
@@ -75,7 +75,7 @@
75
75
  "zod": "^4.0.5"
76
76
  },
77
77
  "peerDependencies": {
78
- "@pulumi/pulumi": "^3.163.0",
78
+ "@pulumi/pulumi": "^3.198.0",
79
79
  "classic-level": "^2.0.0"
80
80
  },
81
81
  "peerDependenciesMeta": {
@@ -98,5 +98,5 @@
98
98
  "type-fest": "^4.41.0",
99
99
  "vitest": "^3.2.4"
100
100
  },
101
- "gitHead": "49effdd2cb069da42997c005be031fcdac3ead7d"
101
+ "gitHead": "00a7c1c40e556daeecfa9e5f3f6a1e60d88911ee"
102
102
  }
@@ -12,6 +12,7 @@ import {
12
12
  type InstanceModel,
13
13
  type InstanceModelPatch,
14
14
  isUnitModel,
15
+ parseInstanceId,
15
16
  } from "@highstate/contract"
16
17
  import { createId } from "@paralleldrive/cuid2"
17
18
  import { createProjectLogger } from "../common"
@@ -166,6 +167,65 @@ export class ProjectService {
166
167
  where: { id: state.id },
167
168
  data: { instanceId: instance.id },
168
169
  })
170
+
171
+ // if the instance is composite, we also need to rename all child instance states
172
+ // we will replace all old instance name occurrences with the new names
173
+ // this will not work in general, since child IDs may be arbitrary strings:
174
+ // e.g. we have parent with name "foo" and child with name pattern "{parent}-foo"
175
+ // if we rename parent to "bar", the child should be renamed to "bar-foo",
176
+ // but will be renamed to "bar-bar" with this approach, leading to child recreation
177
+ // it is not ideal, but this is the best we can do in the current evaluation model
178
+
179
+ if (instance.kind !== "composite") {
180
+ return
181
+ }
182
+
183
+ const [, oldName] = parseInstanceId(instanceId)
184
+
185
+ // TODO: use proper SQL to do this in the database instead of in memory
186
+ const allStates = await tx.instanceState.findMany({
187
+ where: { source: "virtual" },
188
+ select: { id: true, parentId: true, instanceId: true },
189
+ })
190
+
191
+ const stateMap = new Map<string, { parentId: string | null }>()
192
+ for (const s of allStates) {
193
+ stateMap.set(s.instanceId, s)
194
+ }
195
+
196
+ const isChild = (s: { parentId: string | null }) => {
197
+ while (s.parentId) {
198
+ if (s.parentId === state.id) {
199
+ return true
200
+ }
201
+
202
+ const parent = stateMap.get(s.parentId)
203
+ if (!parent) {
204
+ break
205
+ }
206
+
207
+ s = parent
208
+ }
209
+
210
+ return false
211
+ }
212
+
213
+ for (const childState of allStates) {
214
+ if (!isChild(childState)) {
215
+ // reduce the scope of replacement to only child instances to reduce risk of incorrect renames
216
+ continue
217
+ }
218
+
219
+ const newChildInstanceId = childState.instanceId.replace(oldName, newName)
220
+ if (newChildInstanceId === childState.instanceId) {
221
+ continue
222
+ }
223
+
224
+ await tx.instanceState.update({
225
+ where: { id: childState.id },
226
+ data: { instanceId: newChildInstanceId },
227
+ })
228
+ }
169
229
  })
170
230
 
171
231
  await this.pubsubManager.publish(["project-model", projectId], {
@@ -83,7 +83,7 @@ export function evaluateProject(
83
83
  function _evaluateInstance(instance: InstanceModel): Record<string, unknown> {
84
84
  const inputs: Record<string, unknown> = {}
85
85
 
86
- logger.debug("evaluating instance", { instanceId: instance.id })
86
+ logger.debug(`evaluating instance "%s"`, instance.id)
87
87
 
88
88
  for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {
89
89
  inputs[inputName] = input.map(input => {
@@ -4,11 +4,12 @@ import type { LibraryBackend } from "../library"
4
4
  import {
5
5
  type ComponentModel,
6
6
  type InstanceId,
7
+ type InstanceInput,
7
8
  type InstanceModel,
8
9
  isUnitModel,
9
10
  } from "@highstate/contract"
10
11
  import { BetterLock } from "better-lock"
11
- import { unique } from "remeda"
12
+ import { mapValues, unique } from "remeda"
12
13
  import {
13
14
  type InputHashNode,
14
15
  type InputHashOutput,
@@ -20,12 +21,14 @@ import {
20
21
  type LibraryModel,
21
22
  type ProjectOutput,
22
23
  type ResolvedInstanceInput,
24
+ type StableInstanceInput,
23
25
  } from "../shared"
24
26
 
25
27
  export class OperationContext {
26
28
  private readonly instanceMap = new Map<InstanceId, InstanceModel>()
27
29
  private readonly instanceChildrenMap = new Map<InstanceId, InstanceModel[]>()
28
30
 
31
+ private readonly stateIdMap = new Map<string, InstanceId>()
29
32
  private readonly stateMap = new Map<InstanceId, InstanceState>()
30
33
  private readonly dependentStateIdMap = new Map<InstanceId, Set<InstanceId>>()
31
34
  private readonly stateChildIdMap = new Map<InstanceId, InstanceId[]>()
@@ -79,6 +82,7 @@ export class OperationContext {
79
82
 
80
83
  public setState(state: InstanceState): void {
81
84
  this.stateMap.set(state.instanceId, state)
85
+ this.stateIdMap.set(state.id, state.instanceId)
82
86
 
83
87
  if (state.parentInstanceId) {
84
88
  let children = this.stateChildIdMap.get(state.parentInstanceId)
@@ -94,14 +98,35 @@ export class OperationContext {
94
98
  if (state.resolvedInputs) {
95
99
  for (const inputGroup of Object.values(state.resolvedInputs)) {
96
100
  for (const input of inputGroup) {
97
- if (input.instanceId) {
98
- this.addDependentState(state.instanceId, input.instanceId)
101
+ const instanceId = this.stateIdMap.get(input.stateId)
102
+ if (!instanceId) {
103
+ this.logger.warn(
104
+ `cannot add dependent state for unknown input state ID: ${input.stateId}`,
105
+ )
106
+ continue
99
107
  }
108
+
109
+ this.addDependentState(state.instanceId, instanceId)
100
110
  }
101
111
  }
102
112
  }
103
113
  }
104
114
 
115
+ public serializeResolvedInputs(
116
+ resolvedInputs: Record<string, InstanceInput[]>,
117
+ ): Record<string, StableInstanceInput[]> {
118
+ return mapValues(resolvedInputs, inputs =>
119
+ inputs.map(input => {
120
+ const state = this.getState(input.instanceId)
121
+
122
+ return {
123
+ stateId: state.id,
124
+ output: input.output,
125
+ }
126
+ }),
127
+ )
128
+ }
129
+
105
130
  private addDependentState(instanceId: InstanceId, dependencyId: InstanceId): void {
106
131
  let dependentStates = this.dependentStateIdMap.get(dependencyId)
107
132
 
@@ -134,15 +159,17 @@ export class OperationContext {
134
159
  }
135
160
 
136
161
  public getDependencies(instanceId: InstanceId): InstanceModel[] {
137
- const dependencies: InstanceModel[] = []
138
162
  const resolvedInputs = this.resolvedInstanceInputs.get(instanceId)
163
+ if (!resolvedInputs) {
164
+ return []
165
+ }
139
166
 
140
- if (resolvedInputs) {
141
- for (const inputGroup of Object.values(resolvedInputs)) {
142
- for (const resolvedInput of inputGroup) {
143
- if (resolvedInput.input.instanceId && resolvedInput.input.instanceId !== instanceId) {
144
- dependencies.push(this.getInstance(resolvedInput.input.instanceId))
145
- }
167
+ const dependencies: InstanceModel[] = []
168
+
169
+ for (const inputGroup of Object.values(resolvedInputs)) {
170
+ for (const resolvedInput of inputGroup) {
171
+ if (resolvedInput.input.instanceId && resolvedInput.input.instanceId !== instanceId) {
172
+ dependencies.push(this.getInstance(resolvedInput.input.instanceId))
146
173
  }
147
174
  }
148
175
  }
@@ -84,6 +84,7 @@ export const operationPlanTest = test.extend<{
84
84
  status: "pending",
85
85
  options: {
86
86
  forceUpdateDependencies: false,
87
+ ignoreDependencies: false,
87
88
  forceUpdateChildren: false,
88
89
  destroyDependentInstances: true,
89
90
  invokeDestroyTriggers: true,
@@ -35,6 +35,12 @@ export function createOperationPlan(
35
35
  requestedInstanceIds: string[],
36
36
  options: OperationOptions,
37
37
  ): OperationPhase[] {
38
+ if (options.forceUpdateDependencies && options.ignoreDependencies) {
39
+ throw new Error(
40
+ "Operation options are invalid: forceUpdateDependencies and ignoreDependencies cannot both be enabled.",
41
+ )
42
+ }
43
+
38
44
  // initialize work state
39
45
  const workState: WorkState = {
40
46
  included: new Map(),
@@ -195,6 +201,10 @@ function processUpdateInclusions(
195
201
  if (workState.included.has(instance.id)) {
196
202
  const dependencies = context.getDependencies(instance.id)
197
203
  for (const depInstance of dependencies) {
204
+ if (options.ignoreDependencies) {
205
+ continue
206
+ }
207
+
198
208
  const shouldInclude = options.forceUpdateDependencies || isOutdated(depInstance, context)
199
209
 
200
210
  if (shouldInclude && !workState.included.has(depInstance.id)) {
@@ -237,6 +247,10 @@ function processRefreshInclusions(
237
247
  if (workState.included.has(instance.id)) {
238
248
  const dependencies = context.getDependencies(instance.id)
239
249
  for (const depInstance of dependencies) {
250
+ if (options.ignoreDependencies) {
251
+ continue
252
+ }
253
+
240
254
  const shouldInclude = options.forceUpdateDependencies
241
255
 
242
256
  if (shouldInclude && !workState.included.has(depInstance.id)) {
@@ -48,6 +48,47 @@ describe("OperationPlan - Update Operations", () => {
48
48
  },
49
49
  )
50
50
 
51
+ operationPlanTest(
52
+ "1a. should ignore dependencies when option enabled",
53
+ async ({ testBuilder, expect }) => {
54
+ // arrange
55
+ const { context, operation } = await testBuilder()
56
+ .unit("A")
57
+ .unit("B")
58
+ .unit("C")
59
+ .depends("C", "B")
60
+ .depends("B", "A")
61
+ .states({ A: "upToDate", B: "changed", C: "upToDate" })
62
+ .options({ ignoreDependencies: true })
63
+ .request("update", "C")
64
+ .build()
65
+
66
+ // act
67
+ const plan = createOperationPlan(
68
+ context,
69
+ operation.type,
70
+ operation.requestedInstanceIds,
71
+ operation.options,
72
+ )
73
+
74
+ // assert
75
+ expect(plan).toMatchInlineSnapshot(`
76
+ [
77
+ {
78
+ "instances": [
79
+ {
80
+ "id": "component.v1:C",
81
+ "message": "explicitly requested",
82
+ "parentId": undefined,
83
+ },
84
+ ],
85
+ "type": "update",
86
+ },
87
+ ]
88
+ `)
89
+ },
90
+ )
91
+
51
92
  operationPlanTest(
52
93
  "2. should not propagate beyond compositional inclusion",
53
94
  async ({ testBuilder, expect }) => {
@@ -158,6 +199,35 @@ describe("OperationPlan - Update Operations", () => {
158
199
  },
159
200
  )
160
201
 
202
+ operationPlanTest(
203
+ "3a. should reject conflicting dependency options",
204
+ async ({ testBuilder, expect }) => {
205
+ // arrange
206
+ const { context, operation } = await testBuilder()
207
+ .unit("A")
208
+ .unit("B")
209
+ .unit("C")
210
+ .depends("C", "B")
211
+ .depends("B", "A")
212
+ .states({ A: "upToDate", B: "upToDate", C: "upToDate" })
213
+ .options({ forceUpdateDependencies: true, ignoreDependencies: true })
214
+ .request("update", "C")
215
+ .build()
216
+
217
+ // act & assert
218
+ expect(() =>
219
+ createOperationPlan(
220
+ context,
221
+ operation.type,
222
+ operation.requestedInstanceIds,
223
+ operation.options,
224
+ ),
225
+ ).toThrowErrorMatchingInlineSnapshot(
226
+ "[Error: Operation options are invalid: forceUpdateDependencies and ignoreDependencies cannot both be enabled.]",
227
+ )
228
+ },
229
+ )
230
+
161
231
  operationPlanTest(
162
232
  "4. should include outdated children of substantive composite",
163
233
  async ({ testBuilder, expect }) => {
@@ -120,18 +120,20 @@ export class OperationWorkset {
120
120
  inputs => inputs.map(input => input.input),
121
121
  )
122
122
 
123
+ const serializedResolvedInputs = this.context.serializeResolvedInputs(resolvedInputs)
124
+
123
125
  return [
124
126
  {
125
127
  stateId: state.id,
126
128
  operationId: this.operationId,
127
129
  status: "pending",
128
130
  model: instance,
129
- resolvedInputs,
131
+ resolvedInputs: serializedResolvedInputs,
130
132
  },
131
133
  {
132
134
  status: state.status === "undeployed" ? "attempted" : state.status,
133
135
  model: instance,
134
- resolvedInputs,
136
+ resolvedInputs: serializedResolvedInputs,
135
137
  },
136
138
  ]
137
139
  }),
@@ -209,14 +209,6 @@ export class PlanTestBuilder {
209
209
  const instances = Array.from(this.instances.values())
210
210
  const states = Array.from(this.stateMap.values())
211
211
 
212
- // Copy resolvedInputs from instances to states for dependency tracking
213
- states.forEach(state => {
214
- const instance = instances.find(i => i.id === state.instanceId)
215
- if (instance?.resolvedInputs) {
216
- state.resolvedInputs = instance.resolvedInputs
217
- }
218
- })
219
-
220
212
  // get requested instance IDs
221
213
  const requestedInstanceIds = this.requestedInstanceNames.map(name => {
222
214
  const instance = this.instances.get(name)
@@ -234,6 +226,14 @@ export class PlanTestBuilder {
234
226
  // create context with instances and initial states
235
227
  const context = await this.createContext(instances, states)
236
228
 
229
+ // copy resolvedInputs from instances to states for dependency tracking
230
+ states.forEach(state => {
231
+ const instance = instances.find(i => i.id === state.instanceId)
232
+ if (instance?.resolvedInputs) {
233
+ state.resolvedInputs = context.serializeResolvedInputs(instance.resolvedInputs)
234
+ }
235
+ })
236
+
237
237
  // update "upToDate" states with correct input hashes from context
238
238
  const updatedStates = states.map(state => {
239
239
  const stateEntry = Array.from(this.stateMap.entries()).find(
@@ -111,7 +111,12 @@ export class LocalPulumiHost {
111
111
  {
112
112
  projectSettings: {
113
113
  name: pulumiProjectName,
114
- runtime: "nodejs",
114
+ runtime: {
115
+ name: "nodejs",
116
+ options: {
117
+ nodeargs: "--no-deprecation",
118
+ },
119
+ },
115
120
  main: "index.js",
116
121
  },
117
122
  stackSettings: stackConfig
@@ -24,7 +24,7 @@ declare global {
24
24
  type InstanceModel = contract.InstanceModel
25
25
 
26
26
  type InstanceArgs = Record<string, unknown>
27
- type InstanceResolvedInputs = Record<string, contract.InstanceInput[]>
27
+ type InstanceResolvedInputs = Record<string, shared.StableInstanceInput[]>
28
28
  type UnlockMethodMeta = shared.UnlockMethodMeta
29
29
  type ProjectUnlockSuite = shared.ProjectUnlockSuite
30
30
 
@@ -61,6 +61,20 @@ export const operationOptionsSchema = z
61
61
  */
62
62
  forceUpdateDependencies: z.boolean().default(false),
63
63
 
64
+ /**
65
+ * Ignore dependencies and operate only on explicitly requested instances.
66
+ *
67
+ * **Operation Behavior Impact:**
68
+ * - skips dependency inclusion even when dependencies are failed or undeployed;
69
+ * - caller must explicitly include every prerequisite instance to avoid failures;
70
+ * - complements on-demand or targeted updates where dependency safety is managed externally.
71
+ *
72
+ * **Usage with other options:**
73
+ * - mutually exclusive with `forceUpdateDependencies`;
74
+ * - independent of child/composite inclusion options.
75
+ */
76
+ ignoreDependencies: z.boolean().default(false),
77
+
64
78
  /**
65
79
  * Force update all children of composite instances regardless of their state.
66
80
  *
@@ -8,6 +8,18 @@ import type {
8
8
  } from "../../../database"
9
9
  import { z } from "zod"
10
10
 
11
+ export const stableInstanceInputSchema = z.object({
12
+ stateId: z.string(),
13
+ output: z.string(),
14
+ })
15
+
16
+ /**
17
+ * The instance input that references state IDs instead of instance IDs.
18
+ *
19
+ * This provides a stable reference to an instance output that is not affected by instance ID changes.
20
+ */
21
+ export type StableInstanceInput = z.infer<typeof stableInstanceInputSchema>
22
+
11
23
  /**
12
24
  * The instance state aggregate including all related states.
13
25
  */
@@ -45,6 +45,9 @@ export class InputHashResolver extends GraphResolver<InputHashNode, InputHashOut
45
45
  }: InputHashNode): InputHashOutput {
46
46
  const inputHashSink: Uint8Array[] = []
47
47
 
48
+ // 0. include the instance id to reflect renames
49
+ inputHashSink.push(Buffer.from(instance.id))
50
+
48
51
  // 1. include the component definition hash
49
52
  inputHashSink.push(int32ToBytes(component.definitionHash))
50
53
 
@@ -1,9 +1,7 @@
1
1
  import type { Logger } from "pino"
2
- import type { InstanceState } from "../models/project"
3
2
  import type { DependentSetHandler, GraphResolver, ResolverOutputHandler } from "./graph-resolver"
4
3
  import { InputResolver, type InputResolverNode, type InputResolverOutput } from "./input"
5
4
  import { type InputHashNode, type InputHashOutput, InputHashResolver } from "./input-hash"
6
- import { StateResolver } from "./state"
7
5
  import { type ValidationNode, type ValidationOutput, ValidationResolver } from "./validation"
8
6
 
9
7
  export type GraphResolverType = keyof GraphResolverMap
@@ -12,15 +10,12 @@ export type GraphResolverMap = {
12
10
  InputResolver: [InputResolverNode, InputResolverOutput]
13
11
  InputHashResolver: [InputHashNode, InputHashOutput]
14
12
  ValidationResolver: [ValidationNode, ValidationOutput]
15
- // biome-ignore lint/suspicious/noConfusingVoidType: it is return type
16
- StateResolver: [InstanceState, void]
17
13
  }
18
14
 
19
15
  export const resolverFactories = {
20
16
  InputResolver,
21
17
  InputHashResolver,
22
18
  ValidationResolver,
23
- StateResolver,
24
19
  } as Record<
25
20
  GraphResolverType,
26
21
  new (