@highstate/backend 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-C2TJAQAD.js → chunk-KTGKNSKM.js} +63 -21
- package/dist/chunk-KTGKNSKM.js.map +1 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/index.js +38 -46
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +4 -4
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +1 -3
- package/package.json +3 -3
- package/src/library/worker/evaluator.ts +5 -5
- package/src/orchestrator/manager.ts +2 -0
- package/src/orchestrator/operation-workset.ts +20 -0
- package/src/orchestrator/operation.ts +8 -0
- package/src/runner/local.ts +2 -3
- package/src/shared/operation.ts +15 -1
- package/src/shared/resolvers/graph-resolver.ts +46 -13
- package/src/shared/resolvers/registry.ts +5 -1
- package/src/shared/resolvers/state.ts +12 -0
- package/src/shared/state.ts +2 -25
- package/src/shared/terminal.ts +2 -1
- package/src/state/local.ts +1 -1
- package/src/terminal/manager.ts +5 -3
- package/src/terminal/run.sh.ts +2 -0
- package/dist/chunk-C2TJAQAD.js.map +0 -1
@@ -113,16 +113,16 @@ function evaluateInstances(logger2, library2, allInstances, resolvedInputs, inst
|
|
113
113
|
const results = [];
|
114
114
|
const allInstancesMap = new Map(allInstances.map((instance) => [instance.id, instance]));
|
115
115
|
for (const instanceId of instanceIds ?? []) {
|
116
|
-
const externalInstanceIds = new Set(allInstances.map((instance) => instance.id));
|
117
|
-
externalInstanceIds.delete(instanceId);
|
118
116
|
try {
|
119
117
|
logger2.debug({ instanceId }, "evaluating top-level instance");
|
120
|
-
resetEvaluation(
|
118
|
+
resetEvaluation();
|
121
119
|
evaluateInstance(instanceId);
|
122
120
|
results.push({
|
123
121
|
success: true,
|
124
122
|
instanceId,
|
125
|
-
compositeInstances: getCompositeInstances()
|
123
|
+
compositeInstances: getCompositeInstances().filter(
|
124
|
+
(instance) => instanceId.includes(instance.instance.id) || !allInstancesMap.has(instance.instance.id)
|
125
|
+
)
|
126
126
|
});
|
127
127
|
} catch (error) {
|
128
128
|
results.push({
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../../../src/library/worker/main.ts","../../../src/library/worker/loader.ts","../../../src/library/worker/evaluator.ts"],"sourcesContent":["import type { WorkerData, WorkerRequest, WorkerResponse } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { createJiti, type Jiti } from \"jiti\"\nimport { pino } from \"pino\"\nimport { mapValues } from \"remeda\"\nimport { errorToString } from \"../../common\"\nimport { loadLibrary, type Library } from \"./loader\"\nimport { evaluateInstances, evaluateModules } from \"./evaluator\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\", level: data.logLevel })\nlet jiti: Jiti\nlet library: Library\n\ntry {\n logger.info(\"worker started\")\n logger.trace({ data }, \"worker data\")\n\n jiti = createJiti(import.meta.filename)\n logger.debug({ filename: import.meta.filename }, \"jiti created\")\n\n library = await loadLibrary(jiti, logger, data.modulePaths)\n\n parentPort!.postMessage({\n type: \"library\",\n library: {\n components: mapValues(library.components, component => component.model),\n entities: library.entities,\n },\n } satisfies WorkerResponse)\n\n logger.info(\"library loaded and sent\")\n} catch (error) {\n logger.error({ error }, \"failed to load library\")\n\n parentPort!.postMessage({\n type: \"error\",\n error: errorToString(error),\n })\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nparentPort!.on(\"message\", async message => {\n try {\n const request = message as WorkerRequest\n\n switch (request.type) {\n case \"evaluate-composite-instances\": {\n const results = evaluateInstances(\n logger,\n library,\n request.allInstances,\n request.resolvedInputs,\n request.instanceIds,\n )\n\n parentPort!.postMessage({\n type: \"instance-evaluation-results\",\n results,\n } satisfies WorkerResponse)\n break\n }\n case \"evaluate-modules\": {\n const result = await evaluateModules(jiti, logger, request.modulePaths)\n\n parentPort!.postMessage({\n type: \"module-evaluation-result\",\n result,\n } satisfies WorkerResponse)\n break\n }\n }\n } catch (error) {\n logger.error({ error }, \"failed to evaluate\")\n\n parentPort!.postMessage({\n type: \"error\",\n error: errorToString(error),\n } satisfies WorkerResponse)\n }\n})\n","import type { Logger } from \"pino\"\nimport type { Jiti } from \"jiti\"\nimport Module from \"node:module\"\nimport {\n type Component,\n type Entity,\n isComponent,\n isEntity,\n isUnitModel,\n originalCreate,\n} from \"@highstate/contract\"\nimport { serializeFunction } from \"@pulumi/pulumi/runtime/index.js\"\nimport { sha256 } from \"crypto-hash\"\n\ndeclare module \"module\" {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Module {\n function _load(request: unknown, parent: unknown, isMain: boolean): unknown\n }\n}\n\nconst originalLoad = Module._load\n\nModule._load = function (request: unknown, parent: unknown, isMain: boolean) {\n if (request === \"trace_events\") {\n return {}\n }\n\n return originalLoad(request, parent, isMain)\n}\n\nexport type Library = Readonly<{\n components: Readonly<Record<string, Component>>\n entities: Readonly<Record<string, Entity>>\n}>\n\nexport async function loadLibrary(\n jiti: Jiti,\n logger: Logger,\n modulePaths: string[],\n): Promise<Library> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await jiti.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 const entities: Record<string, Entity> = {}\n\n await _loadLibrary(modules, components, entities)\n\n logger.info(\n {\n componentCount: Object.keys(components).length,\n entityCount: Object.keys(entities).length,\n },\n \"library loaded\",\n )\n\n logger.trace({ components, entities }, \"library content\")\n\n return { components, entities }\n}\n\nasync function _loadLibrary(\n value: unknown,\n components: Record<string, Component>,\n entities: Record<string, Entity>,\n): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n value.model.definitionHash = await calculateComponentDefinitionHash(value)\n\n for (const entity of value.entities.values()) {\n entity.definitionHash ??= await calculateEntityDefinitionHash(entity)\n }\n return\n }\n\n if (isEntity(value)) {\n entities[value.type] = value\n entities[value.type].definitionHash ??= await calculateEntityDefinitionHash(value)\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components, entities)\n }\n}\n\nasync function calculateComponentDefinitionHash(component: Component): Promise<string> {\n if (isUnitModel(component.model)) {\n return await sha256(JSON.stringify(component.model))\n }\n\n const serializedCreate = await serializeFunction(component[originalCreate])\n\n return await sha256(JSON.stringify(component.model) + serializedCreate.text)\n}\n\nasync function calculateEntityDefinitionHash(entity: Entity): Promise<string> {\n return await sha256(JSON.stringify(entity))\n}\n","import type { Logger } from \"pino\"\nimport type { Library } from \"./loader\"\nimport type { Jiti } from \"jiti\"\nimport type { InstanceEvaluationResult, ModuleEvaluationResult } from \"../abstractions\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport { getCompositeInstances, resetEvaluation, type InstanceModel } from \"@highstate/contract\"\nimport { BetterLock } from \"better-lock\"\nimport { errorToString } from \"../../common\"\n\nconst lock = new BetterLock()\n\nexport function evaluateModules(\n jiti: Jiti,\n logger: Logger,\n modulePaths: string[],\n): Promise<ModuleEvaluationResult> {\n return lock.acquire(async () => {\n resetEvaluation()\n let lastModulePath = \"\"\n\n try {\n for (const modulePath of modulePaths) {\n logger.info(\"loading module: %s\", modulePath)\n\n lastModulePath = modulePath\n await jiti.import(modulePath)\n\n logger.debug(\"module loaded: %s\", modulePath)\n }\n\n return {\n success: true,\n compositeInstances: getCompositeInstances(),\n }\n } catch (error) {\n return {\n success: false,\n modulePath: lastModulePath,\n error: errorToString(error),\n }\n }\n })\n}\n\nexport function evaluateInstances(\n logger: Logger,\n library: Library,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n instanceIds: string[],\n): InstanceEvaluationResult[] {\n const results: InstanceEvaluationResult[] = []\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n for (const instanceId of instanceIds ?? []) {\n const externalInstanceIds = new Set(allInstances.map(instance => instance.id))\n externalInstanceIds.delete(instanceId)\n\n try {\n logger.debug({ instanceId }, \"evaluating top-level instance\")\n resetEvaluation(externalInstanceIds)\n\n evaluateInstance(instanceId)\n\n results.push({\n success: true,\n instanceId,\n compositeInstances: getCompositeInstances(),\n })\n } catch (error) {\n results.push({\n success: false,\n instanceId,\n error: errorToString(error),\n })\n }\n }\n\n return results\n\n function evaluateInstance(\n instanceId: string,\n instanceOutputs: Map<string, Record<string, unknown>> = new Map(),\n ): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n\n if (!outputs) {\n outputs = _evaluateInstance(instanceId, instanceOutputs)\n instanceOutputs.set(instanceId, outputs)\n }\n\n return outputs\n }\n\n function _evaluateInstance(\n instanceId: string,\n instanceOutputs: Map<string, Record<string, unknown>>,\n ): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n const instance = allInstancesMap.get(instanceId)\n\n logger.info(\"evaluating instance\", { instanceId })\n\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instanceId] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId, instanceOutputs)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = library.components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instanceId}`)\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"],"mappings":"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","names":["jiti","logger","jiti","logger","library","input"]}
|
1
|
+
{"version":3,"sources":["../../../src/library/worker/main.ts","../../../src/library/worker/loader.ts","../../../src/library/worker/evaluator.ts"],"sourcesContent":["import type { WorkerData, WorkerRequest, WorkerResponse } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { createJiti, type Jiti } from \"jiti\"\nimport { pino } from \"pino\"\nimport { mapValues } from \"remeda\"\nimport { errorToString } from \"../../common\"\nimport { loadLibrary, type Library } from \"./loader\"\nimport { evaluateInstances, evaluateModules } from \"./evaluator\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\", level: data.logLevel })\nlet jiti: Jiti\nlet library: Library\n\ntry {\n logger.info(\"worker started\")\n logger.trace({ data }, \"worker data\")\n\n jiti = createJiti(import.meta.filename)\n logger.debug({ filename: import.meta.filename }, \"jiti created\")\n\n library = await loadLibrary(jiti, logger, data.modulePaths)\n\n parentPort!.postMessage({\n type: \"library\",\n library: {\n components: mapValues(library.components, component => component.model),\n entities: library.entities,\n },\n } satisfies WorkerResponse)\n\n logger.info(\"library loaded and sent\")\n} catch (error) {\n logger.error({ error }, \"failed to load library\")\n\n parentPort!.postMessage({\n type: \"error\",\n error: errorToString(error),\n })\n}\n\n// eslint-disable-next-line @typescript-eslint/no-misused-promises\nparentPort!.on(\"message\", async message => {\n try {\n const request = message as WorkerRequest\n\n switch (request.type) {\n case \"evaluate-composite-instances\": {\n const results = evaluateInstances(\n logger,\n library,\n request.allInstances,\n request.resolvedInputs,\n request.instanceIds,\n )\n\n parentPort!.postMessage({\n type: \"instance-evaluation-results\",\n results,\n } satisfies WorkerResponse)\n break\n }\n case \"evaluate-modules\": {\n const result = await evaluateModules(jiti, logger, request.modulePaths)\n\n parentPort!.postMessage({\n type: \"module-evaluation-result\",\n result,\n } satisfies WorkerResponse)\n break\n }\n }\n } catch (error) {\n logger.error({ error }, \"failed to evaluate\")\n\n parentPort!.postMessage({\n type: \"error\",\n error: errorToString(error),\n } satisfies WorkerResponse)\n }\n})\n","import type { Logger } from \"pino\"\nimport type { Jiti } from \"jiti\"\nimport Module from \"node:module\"\nimport {\n type Component,\n type Entity,\n isComponent,\n isEntity,\n isUnitModel,\n originalCreate,\n} from \"@highstate/contract\"\nimport { serializeFunction } from \"@pulumi/pulumi/runtime/index.js\"\nimport { sha256 } from \"crypto-hash\"\n\ndeclare module \"module\" {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Module {\n function _load(request: unknown, parent: unknown, isMain: boolean): unknown\n }\n}\n\nconst originalLoad = Module._load\n\nModule._load = function (request: unknown, parent: unknown, isMain: boolean) {\n if (request === \"trace_events\") {\n return {}\n }\n\n return originalLoad(request, parent, isMain)\n}\n\nexport type Library = Readonly<{\n components: Readonly<Record<string, Component>>\n entities: Readonly<Record<string, Entity>>\n}>\n\nexport async function loadLibrary(\n jiti: Jiti,\n logger: Logger,\n modulePaths: string[],\n): Promise<Library> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await jiti.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 const entities: Record<string, Entity> = {}\n\n await _loadLibrary(modules, components, entities)\n\n logger.info(\n {\n componentCount: Object.keys(components).length,\n entityCount: Object.keys(entities).length,\n },\n \"library loaded\",\n )\n\n logger.trace({ components, entities }, \"library content\")\n\n return { components, entities }\n}\n\nasync function _loadLibrary(\n value: unknown,\n components: Record<string, Component>,\n entities: Record<string, Entity>,\n): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n value.model.definitionHash = await calculateComponentDefinitionHash(value)\n\n for (const entity of value.entities.values()) {\n entity.definitionHash ??= await calculateEntityDefinitionHash(entity)\n }\n return\n }\n\n if (isEntity(value)) {\n entities[value.type] = value\n entities[value.type].definitionHash ??= await calculateEntityDefinitionHash(value)\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components, entities)\n }\n}\n\nasync function calculateComponentDefinitionHash(component: Component): Promise<string> {\n if (isUnitModel(component.model)) {\n return await sha256(JSON.stringify(component.model))\n }\n\n const serializedCreate = await serializeFunction(component[originalCreate])\n\n return await sha256(JSON.stringify(component.model) + serializedCreate.text)\n}\n\nasync function calculateEntityDefinitionHash(entity: Entity): Promise<string> {\n return await sha256(JSON.stringify(entity))\n}\n","import type { Logger } from \"pino\"\nimport type { Library } from \"./loader\"\nimport type { Jiti } from \"jiti\"\nimport type { InstanceEvaluationResult, ModuleEvaluationResult } from \"../abstractions\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport { getCompositeInstances, resetEvaluation, type InstanceModel } from \"@highstate/contract\"\nimport { BetterLock } from \"better-lock\"\nimport { errorToString } from \"../../common\"\n\nconst lock = new BetterLock()\n\nexport function evaluateModules(\n jiti: Jiti,\n logger: Logger,\n modulePaths: string[],\n): Promise<ModuleEvaluationResult> {\n return lock.acquire(async () => {\n resetEvaluation()\n let lastModulePath = \"\"\n\n try {\n for (const modulePath of modulePaths) {\n logger.info(\"loading module: %s\", modulePath)\n\n lastModulePath = modulePath\n await jiti.import(modulePath)\n\n logger.debug(\"module loaded: %s\", modulePath)\n }\n\n return {\n success: true,\n compositeInstances: getCompositeInstances(),\n }\n } catch (error) {\n return {\n success: false,\n modulePath: lastModulePath,\n error: errorToString(error),\n }\n }\n })\n}\n\nexport function evaluateInstances(\n logger: Logger,\n library: Library,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n instanceIds: string[],\n): InstanceEvaluationResult[] {\n const results: InstanceEvaluationResult[] = []\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n for (const instanceId of instanceIds ?? []) {\n try {\n logger.debug({ instanceId }, \"evaluating top-level instance\")\n resetEvaluation()\n\n evaluateInstance(instanceId)\n\n results.push({\n success: true,\n instanceId,\n compositeInstances: getCompositeInstances().filter(\n instance =>\n instanceId.includes(instance.instance.id) || !allInstancesMap.has(instance.instance.id),\n ),\n })\n } catch (error) {\n results.push({\n success: false,\n instanceId,\n error: errorToString(error),\n })\n }\n }\n\n return results\n\n function evaluateInstance(\n instanceId: string,\n instanceOutputs: Map<string, Record<string, unknown>> = new Map(),\n ): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n\n if (!outputs) {\n outputs = _evaluateInstance(instanceId, instanceOutputs)\n instanceOutputs.set(instanceId, outputs)\n }\n\n return outputs\n }\n\n function _evaluateInstance(\n instanceId: string,\n instanceOutputs: Map<string, Record<string, unknown>>,\n ): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n const instance = allInstancesMap.get(instanceId)\n\n logger.info(\"evaluating instance\", { instanceId })\n\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instanceId] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId, instanceOutputs)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = library.components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instanceId}`)\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"],"mappings":"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","names":["jiti","logger","jiti","logger","library","input"]}
|
package/dist/shared/index.js
CHANGED
@@ -5,7 +5,6 @@ import {
|
|
5
5
|
ValidationResolver,
|
6
6
|
applyLibraryUpdate,
|
7
7
|
applyPartialInstanceState,
|
8
|
-
buildDependentInstanceStateMap,
|
9
8
|
compositeInstanceSchema,
|
10
9
|
createAsyncBatcher,
|
11
10
|
createInstanceState,
|
@@ -47,7 +46,7 @@ import {
|
|
47
46
|
projectOperationSchema,
|
48
47
|
resolverFactories,
|
49
48
|
terminalSessionSchema
|
50
|
-
} from "../chunk-
|
49
|
+
} from "../chunk-KTGKNSKM.js";
|
51
50
|
export {
|
52
51
|
GraphResolver,
|
53
52
|
InputHashResolver,
|
@@ -55,7 +54,6 @@ export {
|
|
55
54
|
ValidationResolver,
|
56
55
|
applyLibraryUpdate,
|
57
56
|
applyPartialInstanceState,
|
58
|
-
buildDependentInstanceStateMap,
|
59
57
|
compositeInstanceSchema,
|
60
58
|
createAsyncBatcher,
|
61
59
|
createInstanceState,
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@highstate/backend",
|
3
|
-
"version": "0.9.
|
3
|
+
"version": "0.9.15",
|
4
4
|
"type": "module",
|
5
5
|
"files": [
|
6
6
|
"dist",
|
@@ -26,7 +26,7 @@
|
|
26
26
|
"build": "highstate build"
|
27
27
|
},
|
28
28
|
"dependencies": {
|
29
|
-
"@highstate/contract": "^0.9.
|
29
|
+
"@highstate/contract": "^0.9.15",
|
30
30
|
"@types/node": "^22.10.1",
|
31
31
|
"ajv": "^8.17.1",
|
32
32
|
"better-lock": "^3.2.0",
|
@@ -65,5 +65,5 @@
|
|
65
65
|
"rollup": "^4.28.1",
|
66
66
|
"typescript": "^5.7.2"
|
67
67
|
},
|
68
|
-
"gitHead": "
|
68
|
+
"gitHead": "f61b9905d4cd50511b03331411f42595403ebc06"
|
69
69
|
}
|
@@ -53,19 +53,19 @@ export function evaluateInstances(
|
|
53
53
|
const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))
|
54
54
|
|
55
55
|
for (const instanceId of instanceIds ?? []) {
|
56
|
-
const externalInstanceIds = new Set(allInstances.map(instance => instance.id))
|
57
|
-
externalInstanceIds.delete(instanceId)
|
58
|
-
|
59
56
|
try {
|
60
57
|
logger.debug({ instanceId }, "evaluating top-level instance")
|
61
|
-
resetEvaluation(
|
58
|
+
resetEvaluation()
|
62
59
|
|
63
60
|
evaluateInstance(instanceId)
|
64
61
|
|
65
62
|
results.push({
|
66
63
|
success: true,
|
67
64
|
instanceId,
|
68
|
-
compositeInstances: getCompositeInstances()
|
65
|
+
compositeInstances: getCompositeInstances().filter(
|
66
|
+
instance =>
|
67
|
+
instanceId.includes(instance.instance.id) || !allInstancesMap.has(instance.instance.id),
|
68
|
+
),
|
69
69
|
})
|
70
70
|
} catch (error) {
|
71
71
|
results.push({
|
@@ -84,6 +84,8 @@ export class OperationManager {
|
|
84
84
|
deleteUnreachableResources: request.options?.deleteUnreachableResources ?? false,
|
85
85
|
forceDeleteState: request.options?.forceDeleteState ?? false,
|
86
86
|
refresh: request.options?.refresh ?? false,
|
87
|
+
allowPartialCompositeInstanceUpdates:
|
88
|
+
request.options?.allowPartialCompositeInstanceUpdates ?? false,
|
87
89
|
},
|
88
90
|
error: null,
|
89
91
|
startedAt: Date.now(),
|
@@ -327,6 +327,16 @@ export class OperationWorkset {
|
|
327
327
|
}
|
328
328
|
}
|
329
329
|
|
330
|
+
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
331
|
+
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
332
|
+
for (const instanceId of this.instanceIdsToUpdate) {
|
333
|
+
const children = this.instanceChildrenMap.get(instanceId) ?? []
|
334
|
+
for (const child of children) {
|
335
|
+
this.instanceIdsToUpdate.add(child.id)
|
336
|
+
}
|
337
|
+
}
|
338
|
+
}
|
339
|
+
|
330
340
|
this.operation.instanceIdsToUpdate = Array.from(this.instanceIdsToUpdate)
|
331
341
|
}
|
332
342
|
|
@@ -412,6 +422,16 @@ export class OperationWorkset {
|
|
412
422
|
}
|
413
423
|
}
|
414
424
|
|
425
|
+
// 4. if "allowPartialCompositeInstanceUpdates" is not set, include all children of the composite instances
|
426
|
+
if (!this.operation.options.allowPartialCompositeInstanceUpdates) {
|
427
|
+
for (const instanceId of this.instanceIdsToDestroy) {
|
428
|
+
const children = this.stateChildIdMap.get(instanceId) ?? []
|
429
|
+
for (const childId of children) {
|
430
|
+
this.instanceIdsToDestroy.add(childId)
|
431
|
+
}
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
415
435
|
this.operation.instanceIdsToDestroy = Array.from(this.instanceIdsToDestroy)
|
416
436
|
}
|
417
437
|
|
@@ -430,6 +430,14 @@ export class RuntimeOperation {
|
|
430
430
|
const dependents = this.workset.getDependentStates(state.id)
|
431
431
|
|
432
432
|
for (const dependent of dependents) {
|
433
|
+
if (
|
434
|
+
!this.operation.options.destroyDependentInstances &&
|
435
|
+
!this.operation.instanceIdsToDestroy.includes(dependent.id)
|
436
|
+
) {
|
437
|
+
// skip dependents that are not affected by the operation
|
438
|
+
continue
|
439
|
+
}
|
440
|
+
|
433
441
|
dependentPromises.push(this.getInstancePromiseForOperation(dependent.id))
|
434
442
|
}
|
435
443
|
|
package/src/runner/local.ts
CHANGED
@@ -13,6 +13,7 @@ import {
|
|
13
13
|
instanceFileSchema,
|
14
14
|
instancePageSchema,
|
15
15
|
instanceStatusFieldSchema,
|
16
|
+
instanceTerminalMetaSchema,
|
16
17
|
instanceTerminalSchema,
|
17
18
|
instanceTriggerSchema,
|
18
19
|
type InstancePageBlock,
|
@@ -606,9 +607,7 @@ export class LocalRunnerBackend implements RunnerBackend {
|
|
606
607
|
}
|
607
608
|
|
608
609
|
if (outputs["$terminals"]) {
|
609
|
-
|
610
|
-
|
611
|
-
patch.terminals = terminals.map(terminal => pick(terminal, ["name", "title", "description"]))
|
610
|
+
patch.terminals = z.array(instanceTerminalMetaSchema).parse(outputs["$terminals"].value)
|
612
611
|
} else {
|
613
612
|
patch.terminals = []
|
614
613
|
}
|
package/src/shared/operation.ts
CHANGED
@@ -15,6 +15,7 @@ export const operationOptionsSchema = z.object({
|
|
15
15
|
* Whether to force update all dependencies of the instances even if they are not changed.
|
16
16
|
*
|
17
17
|
* Only applicable for `update`, `preview`, `recreate`, and `refresh` operations.
|
18
|
+
*
|
18
19
|
* By default, `false`.
|
19
20
|
*/
|
20
21
|
forceUpdateDependencies: z.boolean().default(false),
|
@@ -23,6 +24,7 @@ export const operationOptionsSchema = z.object({
|
|
23
24
|
* Whether to force update all children of the composite instances even if they are not changed.
|
24
25
|
*
|
25
26
|
* Only applicable for `update`, `preview`, `recreate`, and `refresh` operations.
|
27
|
+
*
|
26
28
|
* By default, `false`.
|
27
29
|
*/
|
28
30
|
forceUpdateChildren: z.boolean().default(false),
|
@@ -30,7 +32,8 @@ export const operationOptionsSchema = z.object({
|
|
30
32
|
/**
|
31
33
|
* Whether to destroy all dependents of the instances when destroying them.
|
32
34
|
*
|
33
|
-
* Only applicable for `destroy
|
35
|
+
* Only applicable for `destroy` and `recreate` operations.
|
36
|
+
*
|
34
37
|
* By default, `true`.
|
35
38
|
*/
|
36
39
|
destroyDependentInstances: z.boolean().default(true),
|
@@ -39,6 +42,7 @@ export const operationOptionsSchema = z.object({
|
|
39
42
|
* Whether to invoke destroy triggers when destroying the instances.
|
40
43
|
*
|
41
44
|
* Only applicable for `destroy`.
|
45
|
+
*
|
42
46
|
* By default, `true`.
|
43
47
|
*/
|
44
48
|
invokeDestroyTriggers: z.boolean().default(true),
|
@@ -59,6 +63,16 @@ export const operationOptionsSchema = z.object({
|
|
59
63
|
*/
|
60
64
|
forceDeleteState: z.boolean().default(false),
|
61
65
|
|
66
|
+
/**
|
67
|
+
* Whether to allow partial updates of composite instances when updating, destroying or recreating them.
|
68
|
+
*
|
69
|
+
* If `true`, the operation will only update/destroy composite instance children that are directly referenced by other affected instances.
|
70
|
+
* If `false`, the operation will update/destroy all children at of the composite instances at all levels if at least one of them is referenced by affected instances.
|
71
|
+
*
|
72
|
+
* By default, `false`.
|
73
|
+
*/
|
74
|
+
allowPartialCompositeInstanceUpdates: z.boolean().default(false),
|
75
|
+
|
62
76
|
/**
|
63
77
|
* Whether to refresh the state before running the operation.
|
64
78
|
*
|
@@ -5,6 +5,7 @@ export type ResolverOutputHandler<TOutput> = (id: string, value: TOutput) => voi
|
|
5
5
|
|
6
6
|
export abstract class GraphResolver<TNode, TOutput> {
|
7
7
|
private readonly workset: Set<string> = new Set()
|
8
|
+
private readonly dependencyMap: Map<string, string[]> = new Map()
|
8
9
|
private readonly dependentMap: Map<string, Set<string>> = new Map()
|
9
10
|
private readonly outputMap: Map<string, TOutput> = new Map()
|
10
11
|
|
@@ -52,6 +53,30 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
52
53
|
*/
|
53
54
|
protected abstract processNode(node: TNode, logger: Logger): TOutput | Promise<TOutput>
|
54
55
|
|
56
|
+
/**
|
57
|
+
* Gets all identifiers of the nodes that depend on the given node directly or indirectly.
|
58
|
+
*/
|
59
|
+
getAllDependents(nodeId: string): string[] {
|
60
|
+
const result = new Set<string>()
|
61
|
+
const stack: string[] = [nodeId]
|
62
|
+
|
63
|
+
while (stack.length > 0) {
|
64
|
+
const dependents = this.dependentMap.get(stack.pop()!)
|
65
|
+
if (!dependents) {
|
66
|
+
continue
|
67
|
+
}
|
68
|
+
|
69
|
+
for (const dependentId of dependents) {
|
70
|
+
if (!result.has(dependentId)) {
|
71
|
+
result.add(dependentId)
|
72
|
+
stack.push(dependentId)
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
return Array.from(result)
|
78
|
+
}
|
79
|
+
|
55
80
|
/**
|
56
81
|
* Invalidates the node and all nodes that depend on it.
|
57
82
|
*
|
@@ -82,9 +107,6 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
82
107
|
stack.push(dependentId)
|
83
108
|
}
|
84
109
|
}
|
85
|
-
|
86
|
-
// clear the dependent map for the node
|
87
|
-
this.dependentMap.delete(nodeId)
|
88
110
|
}
|
89
111
|
}
|
90
112
|
|
@@ -94,9 +116,15 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
94
116
|
* The abort signal of the previous operation must be called before calling this method again.
|
95
117
|
*/
|
96
118
|
async process(signal?: AbortSignal): Promise<void> {
|
119
|
+
type StackItem = {
|
120
|
+
nodeId: string
|
121
|
+
resolved: boolean
|
122
|
+
dependencies: string[]
|
123
|
+
}
|
124
|
+
|
97
125
|
while (this.workset.size > 0) {
|
98
126
|
const rootNodeId = this.workset.values().next().value!
|
99
|
-
const stack = [{ nodeId: rootNodeId, resolved: false, dependencies: []
|
127
|
+
const stack: StackItem[] = [{ nodeId: rootNodeId, resolved: false, dependencies: [] }]
|
100
128
|
|
101
129
|
while (stack.length > 0) {
|
102
130
|
const stackItem = stack[stack.length - 1]
|
@@ -126,14 +154,6 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
126
154
|
continue
|
127
155
|
}
|
128
156
|
|
129
|
-
// if (stack.some(item => item.nodeId === depId)) {
|
130
|
-
// this.logger.warn(
|
131
|
-
// { depId, nodeId, stack },
|
132
|
-
// "dependency is already in the stack, looks like a circular dependency, skipping",
|
133
|
-
// )
|
134
|
-
// continue
|
135
|
-
// }
|
136
|
-
|
137
157
|
if (!this.outputMap.has(depId)) {
|
138
158
|
stack.push({ nodeId: depId, resolved: false, dependencies: [] })
|
139
159
|
hasUnresolvedDependencies = true
|
@@ -155,7 +175,19 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
155
175
|
return
|
156
176
|
}
|
157
177
|
|
158
|
-
//
|
178
|
+
// remove all dependent nodes
|
179
|
+
const oldDependencies = this.dependencyMap.get(nodeId) ?? []
|
180
|
+
for (const depId of oldDependencies) {
|
181
|
+
const dependantSet = this.dependentMap.get(depId)
|
182
|
+
if (dependantSet) {
|
183
|
+
dependantSet.delete(nodeId)
|
184
|
+
if (dependantSet.size === 0) {
|
185
|
+
this.dependentMap.delete(depId)
|
186
|
+
}
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
// add the new dependencies
|
159
191
|
for (const depId of stackItem.dependencies) {
|
160
192
|
let dependantSet = this.dependentMap.get(depId)
|
161
193
|
if (!dependantSet) {
|
@@ -168,6 +200,7 @@ export abstract class GraphResolver<TNode, TOutput> {
|
|
168
200
|
|
169
201
|
this.outputMap.set(nodeId, output)
|
170
202
|
this.outputHandler?.(nodeId, output)
|
203
|
+
this.dependencyMap.set(nodeId, stackItem.dependencies)
|
171
204
|
|
172
205
|
stack.pop()
|
173
206
|
}
|
@@ -1,21 +1,25 @@
|
|
1
1
|
import type { Logger } from "pino"
|
2
2
|
import type { GraphResolver, ResolverOutputHandler } from "./graph-resolver"
|
3
|
+
import type { InstanceState } from "../state"
|
4
|
+
import { StateResolver } from "./state"
|
3
5
|
import { InputResolver, type InputResolverNode, type InputResolverOutput } from "./input"
|
4
6
|
import { InputHashResolver, type InputHashNode, type InputHashOutput } from "./input-hash"
|
5
7
|
import { ValidationResolver, type ValidationNode, type ValidationOutput } from "./validation"
|
6
8
|
|
7
|
-
export type GraphResolverType =
|
9
|
+
export type GraphResolverType = keyof GraphResolverMap
|
8
10
|
|
9
11
|
export type GraphResolverMap = {
|
10
12
|
InputResolver: [InputResolverNode, InputResolverOutput]
|
11
13
|
InputHashResolver: [InputHashNode, InputHashOutput]
|
12
14
|
ValidationResolver: [ValidationNode, ValidationOutput]
|
15
|
+
StateResolver: [InstanceState, void]
|
13
16
|
}
|
14
17
|
|
15
18
|
export const resolverFactories = {
|
16
19
|
InputResolver,
|
17
20
|
InputHashResolver,
|
18
21
|
ValidationResolver,
|
22
|
+
StateResolver,
|
19
23
|
} as Record<
|
20
24
|
GraphResolverType,
|
21
25
|
new (
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import type { InstanceState } from "../state"
|
2
|
+
import { GraphResolver } from "./graph-resolver"
|
3
|
+
|
4
|
+
export class StateResolver extends GraphResolver<InstanceState, void> {
|
5
|
+
protected getNodeDependencies(node: InstanceState): string[] {
|
6
|
+
return node.dependencyIds
|
7
|
+
}
|
8
|
+
|
9
|
+
protected processNode(): void {
|
10
|
+
return
|
11
|
+
}
|
12
|
+
}
|
package/src/shared/state.ts
CHANGED
@@ -72,6 +72,7 @@ export const instanceTerminalMetaSchema = z.object({
|
|
72
72
|
name: z.string(),
|
73
73
|
title: z.string(),
|
74
74
|
description: z.string().optional(),
|
75
|
+
icon: z.string().optional(),
|
75
76
|
})
|
76
77
|
|
77
78
|
export const instanceTerminalFileSchema = z.object({
|
@@ -147,6 +148,7 @@ export const instanceStateUpdateSchema = z
|
|
147
148
|
|
148
149
|
export type InstanceStatusFieldValue = z.infer<typeof instanceStatusFieldValueSchema>
|
149
150
|
export type InstanceStatusField = z.infer<typeof instanceStatusFieldSchema>
|
151
|
+
export type InstanceTerminalMeta = z.infer<typeof instanceTerminalMetaSchema>
|
150
152
|
export type InstanceTerminal = z.infer<typeof instanceTerminalSchema>
|
151
153
|
|
152
154
|
export type InstanceStatus = z.infer<typeof instanceStatusSchema>
|
@@ -212,31 +214,6 @@ export function createInstanceStatePatch(update: InstanceStateUpdate): Partial<I
|
|
212
214
|
return omit(update, ["secrets", "logLine"])
|
213
215
|
}
|
214
216
|
|
215
|
-
/**
|
216
|
-
* Builds a map where instance id -> instance state ids that directly depend on the instance.
|
217
|
-
*
|
218
|
-
* @param instanceStates The instance states to build the map from.
|
219
|
-
*/
|
220
|
-
export function buildDependentInstanceStateMap(
|
221
|
-
instanceStates: Map<string, InstanceState>,
|
222
|
-
): Map<string, string[]> {
|
223
|
-
const dependentMap = new Map<string, string[]>()
|
224
|
-
|
225
|
-
for (const state of instanceStates.values()) {
|
226
|
-
for (const dependency of state.dependencyIds) {
|
227
|
-
let dependents = dependentMap.get(dependency)
|
228
|
-
if (!dependents) {
|
229
|
-
dependents = []
|
230
|
-
dependentMap.set(dependency, dependents)
|
231
|
-
}
|
232
|
-
|
233
|
-
dependents.push(state.id)
|
234
|
-
}
|
235
|
-
}
|
236
|
-
|
237
|
-
return dependentMap
|
238
|
-
}
|
239
|
-
|
240
217
|
/**
|
241
218
|
* Gets all instance ids that depend on the given instance id (including recursively).
|
242
219
|
* The instance id itself is not included in the result.
|
package/src/shared/terminal.ts
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import { z } from "zod"
|
2
2
|
|
3
3
|
export const terminalSessionSchema = z.object({
|
4
|
-
id: z.string().
|
4
|
+
id: z.string().uuid(),
|
5
5
|
projectId: z.string(),
|
6
6
|
instanceId: z.string(),
|
7
7
|
terminalName: z.string(),
|
8
8
|
terminalTitle: z.string(),
|
9
|
+
terminalIcon: z.string().optional(),
|
9
10
|
createdAt: z.coerce.date(),
|
10
11
|
finishedAt: z.coerce.date().optional(),
|
11
12
|
})
|
package/src/state/local.ts
CHANGED
@@ -326,7 +326,7 @@ export class LocalStateBackend implements StateBackend {
|
|
326
326
|
async getTerminalSessions(projectId: string, instanceId: string): Promise<TerminalSession[]> {
|
327
327
|
const sublevel = this.getTerminalSessionsSublevel(projectId, instanceId)
|
328
328
|
|
329
|
-
return await this.getAllSublevelItems(sublevel, terminalSessionSchema)
|
329
|
+
return await this.getAllSublevelItems(sublevel, terminalSessionSchema, 20, { reverse: true })
|
330
330
|
}
|
331
331
|
|
332
332
|
async getLastTerminalSession(
|
package/src/terminal/manager.ts
CHANGED
@@ -5,7 +5,7 @@ import type { RunnerBackend } from "../runner"
|
|
5
5
|
import { PassThrough, type Stream, type Writable } from "node:stream"
|
6
6
|
import { EventEmitter, on } from "node:events"
|
7
7
|
import { parseInstanceId } from "@highstate/contract"
|
8
|
-
import {
|
8
|
+
import { uuidv7 } from "uuidv7"
|
9
9
|
import { type InstanceTerminal, type TerminalSession, createAsyncBatcher } from "../shared"
|
10
10
|
import { isAbortErrorLike } from "../common"
|
11
11
|
|
@@ -97,11 +97,12 @@ export class TerminalManager {
|
|
97
97
|
}
|
98
98
|
|
99
99
|
const terminalSession: TerminalSession = {
|
100
|
-
id:
|
100
|
+
id: uuidv7(),
|
101
101
|
projectId,
|
102
102
|
instanceId,
|
103
103
|
terminalName,
|
104
104
|
terminalTitle: factory.title,
|
105
|
+
terminalIcon: factory.icon,
|
105
106
|
createdAt: new Date(),
|
106
107
|
}
|
107
108
|
|
@@ -117,10 +118,11 @@ export class TerminalManager {
|
|
117
118
|
projectId: string,
|
118
119
|
instanceId: string,
|
119
120
|
terminalName: string,
|
121
|
+
newSession = false,
|
120
122
|
): Promise<TerminalSession> {
|
121
123
|
const existingSession = this.existingSessions.get(`${projectId}:${instanceId}.${terminalName}`)
|
122
124
|
|
123
|
-
if (existingSession) {
|
125
|
+
if (existingSession && !newSession) {
|
124
126
|
return existingSession
|
125
127
|
}
|
126
128
|
|
package/src/terminal/run.sh.ts
CHANGED
@@ -19,6 +19,8 @@ for key in "\${filesKeys[@]}"; do
|
|
19
19
|
isBinary=$(jq -r ".files[\\"$key\\"].isBinary // false" <<<"$data")
|
20
20
|
content=$(jq -r ".files[\\"$key\\"].content" <<<"$data")
|
21
21
|
mode=$(jq -r ".files[\\"$key\\"].mode // 0" <<<"$data")
|
22
|
+
|
23
|
+
mkdir -p "$(dirname "$key")"
|
22
24
|
|
23
25
|
if [ "$isBinary" = "true" ]; then
|
24
26
|
echo "$content" | base64 -d > "$key"
|