@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.
- package/dist/{chunk-NMGIUI6X.js → chunk-DQDXRDUA.js} +228 -96
- package/dist/chunk-DQDXRDUA.js.map +1 -0
- package/dist/{chunk-EQ4LMS7B.js → chunk-WXDYCRTT.js} +1 -57
- package/dist/chunk-WXDYCRTT.js.map +1 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/index.js +117 -118
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +1 -1
- package/dist/shared/index.js +11 -13
- package/package.json +3 -3
- package/src/common/utils.ts +0 -74
- package/src/library/abstractions.ts +0 -5
- package/src/orchestrator/operation-workset.ts +64 -59
- package/src/orchestrator/operation.ts +3 -19
- package/src/project/manager.ts +66 -61
- package/src/shared/async-batcher.ts +73 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/resolvers/graph-resolver.ts +146 -79
- package/src/shared/resolvers/input-hash.ts +22 -17
- package/src/shared/resolvers/input.ts +29 -26
- package/src/shared/resolvers/registry.ts +19 -9
- package/src/shared/resolvers/validation.ts +12 -18
- package/src/state/abstractions.ts +2 -3
- package/src/state/local.ts +13 -6
- package/src/terminal/manager.ts +2 -2
- package/dist/chunk-EQ4LMS7B.js.map +0 -1
- package/dist/chunk-NMGIUI6X.js.map +0 -1
package/dist/shared/index.js
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
import {
|
2
|
-
|
2
|
+
GraphResolver,
|
3
|
+
InputHashResolver,
|
4
|
+
InputResolver,
|
5
|
+
ValidationResolver,
|
3
6
|
applyLibraryUpdate,
|
4
7
|
applyPartialInstanceState,
|
5
8
|
buildDependentInstanceStateMap,
|
6
9
|
compositeInstanceSchema,
|
7
|
-
|
8
|
-
createInputHashResolver,
|
9
|
-
createInputResolver,
|
10
|
+
createAsyncBatcher,
|
10
11
|
createInstanceState,
|
11
12
|
createInstanceStatePatch,
|
12
|
-
createValidationResolver,
|
13
|
-
defineGraphResolver,
|
14
13
|
diffLibraries,
|
15
14
|
getAllDependentInstanceIds,
|
16
15
|
getMatchedInjectionInstanceInputs,
|
@@ -48,20 +47,19 @@ import {
|
|
48
47
|
projectOperationSchema,
|
49
48
|
resolverFactories,
|
50
49
|
terminalSessionSchema
|
51
|
-
} from "../chunk-
|
50
|
+
} from "../chunk-DQDXRDUA.js";
|
52
51
|
export {
|
53
|
-
|
52
|
+
GraphResolver,
|
53
|
+
InputHashResolver,
|
54
|
+
InputResolver,
|
55
|
+
ValidationResolver,
|
54
56
|
applyLibraryUpdate,
|
55
57
|
applyPartialInstanceState,
|
56
58
|
buildDependentInstanceStateMap,
|
57
59
|
compositeInstanceSchema,
|
58
|
-
|
59
|
-
createInputHashResolver,
|
60
|
-
createInputResolver,
|
60
|
+
createAsyncBatcher,
|
61
61
|
createInstanceState,
|
62
62
|
createInstanceStatePatch,
|
63
|
-
createValidationResolver,
|
64
|
-
defineGraphResolver,
|
65
63
|
diffLibraries,
|
66
64
|
getAllDependentInstanceIds,
|
67
65
|
getMatchedInjectionInstanceInputs,
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@highstate/backend",
|
3
|
-
"version": "0.9.
|
3
|
+
"version": "0.9.10",
|
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.10",
|
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": "aacf8837fdf40f2bb2f83d4c11b35a358e26ec33"
|
69
69
|
}
|
package/src/common/utils.ts
CHANGED
@@ -61,77 +61,3 @@ export function errorToString(error: unknown): string {
|
|
61
61
|
|
62
62
|
return JSON.stringify(error)
|
63
63
|
}
|
64
|
-
|
65
|
-
export type AsyncBatcherOptions = {
|
66
|
-
waitMs?: number
|
67
|
-
maxWaitTimeMs?: number
|
68
|
-
}
|
69
|
-
|
70
|
-
export function createAsyncBatcher<T>(
|
71
|
-
fn: (items: T[]) => Promise<void>,
|
72
|
-
{ waitMs = 100, maxWaitTimeMs = 1000 }: AsyncBatcherOptions = {},
|
73
|
-
) {
|
74
|
-
let batch: T[] = []
|
75
|
-
let activeTimeout: NodeJS.Timeout | null = null
|
76
|
-
let maxWaitTimeout: NodeJS.Timeout | null = null
|
77
|
-
let firstCallTimestamp: number | null = null
|
78
|
-
|
79
|
-
async function processBatch() {
|
80
|
-
if (batch.length === 0) return
|
81
|
-
|
82
|
-
const currentBatch = batch
|
83
|
-
batch = [] // Reset batch before async call
|
84
|
-
|
85
|
-
await fn(currentBatch)
|
86
|
-
|
87
|
-
// Clear max wait timer since batch has been processed
|
88
|
-
if (maxWaitTimeout) {
|
89
|
-
clearTimeout(maxWaitTimeout)
|
90
|
-
maxWaitTimeout = null
|
91
|
-
}
|
92
|
-
firstCallTimestamp = null
|
93
|
-
}
|
94
|
-
|
95
|
-
function schedule() {
|
96
|
-
if (activeTimeout) clearTimeout(activeTimeout)
|
97
|
-
activeTimeout = setTimeout(() => {
|
98
|
-
activeTimeout = null
|
99
|
-
void processBatch()
|
100
|
-
}, waitMs)
|
101
|
-
|
102
|
-
// Ensure batch is executed within maxWaitTimeMs
|
103
|
-
if (!firstCallTimestamp) {
|
104
|
-
firstCallTimestamp = Date.now()
|
105
|
-
maxWaitTimeout = setTimeout(() => {
|
106
|
-
if (activeTimeout) clearTimeout(activeTimeout)
|
107
|
-
activeTimeout = null
|
108
|
-
void processBatch()
|
109
|
-
}, maxWaitTimeMs)
|
110
|
-
}
|
111
|
-
}
|
112
|
-
|
113
|
-
return {
|
114
|
-
/**
|
115
|
-
* Add an item to the batch.
|
116
|
-
*/
|
117
|
-
call(item: T): void {
|
118
|
-
batch.push(item)
|
119
|
-
schedule()
|
120
|
-
},
|
121
|
-
|
122
|
-
/**
|
123
|
-
* Immediately flush the pending batch (if any).
|
124
|
-
*/
|
125
|
-
async flush(): Promise<void> {
|
126
|
-
if (activeTimeout) {
|
127
|
-
clearTimeout(activeTimeout)
|
128
|
-
activeTimeout = null
|
129
|
-
}
|
130
|
-
if (maxWaitTimeout) {
|
131
|
-
clearTimeout(maxWaitTimeout)
|
132
|
-
maxWaitTimeout = null
|
133
|
-
}
|
134
|
-
await processBatch()
|
135
|
-
},
|
136
|
-
}
|
137
|
-
}
|
@@ -42,11 +42,6 @@ export interface LibraryBackend {
|
|
42
42
|
*/
|
43
43
|
watchLibrary(signal?: AbortSignal): AsyncIterable<LibraryUpdate[]>
|
44
44
|
|
45
|
-
/**
|
46
|
-
* Gets the currently loaded resolved unit sources.
|
47
|
-
*/
|
48
|
-
getLoadedResolvedUnitSources(): Promise<ResolvedUnitSource[]>
|
49
|
-
|
50
45
|
/**
|
51
46
|
* Gets the resolved unit sources for the given unit types.
|
52
47
|
*
|
@@ -9,19 +9,18 @@ import {
|
|
9
9
|
type InstanceModel,
|
10
10
|
} from "@highstate/contract"
|
11
11
|
import { unique } from "remeda"
|
12
|
+
import { BetterLock } from "better-lock"
|
12
13
|
import {
|
13
14
|
applyPartialInstanceState,
|
14
|
-
createInputHashResolver,
|
15
|
-
createInputResolver,
|
16
15
|
createInstanceState,
|
17
16
|
createInstanceStatePatch,
|
18
|
-
|
19
|
-
|
20
|
-
type
|
21
|
-
type
|
22
|
-
type InputResolverOutput,
|
17
|
+
InputHashResolver,
|
18
|
+
InputResolver,
|
19
|
+
type InputHashNode,
|
20
|
+
type InputResolverNode,
|
23
21
|
type InstanceState,
|
24
22
|
type InstanceStateUpdate,
|
23
|
+
type InstanceStatus,
|
25
24
|
type LibraryModel,
|
26
25
|
type ProjectOperation,
|
27
26
|
type ResolvedInstanceInput,
|
@@ -43,17 +42,12 @@ export class OperationWorkset {
|
|
43
42
|
|
44
43
|
private readonly unitSourceHashMap = new Map<string, string>()
|
45
44
|
|
46
|
-
private inputResolver!:
|
47
|
-
private readonly
|
48
|
-
private readonly inputResolverPromiseCache = new Map<string, Promise<InputResolverOutput>>()
|
45
|
+
private inputResolver!: InputResolver
|
46
|
+
private readonly inputResolverNodes = new Map<string, InputResolverNode>()
|
49
47
|
|
50
|
-
private inputHashResolver!:
|
51
|
-
private readonly
|
52
|
-
|
53
|
-
private readonly inputHashResolverPromiseCache = new Map<
|
54
|
-
string,
|
55
|
-
Promise<InputHashResolverOutput>
|
56
|
-
>()
|
48
|
+
private inputHashResolver!: InputHashResolver
|
49
|
+
private readonly inputHashNodes = new Map<string, InputHashNode>()
|
50
|
+
private readonly inputHashResolverLock = new BetterLock()
|
57
51
|
|
58
52
|
public readonly resolvedInstanceInputs = new Map<
|
59
53
|
string,
|
@@ -96,12 +90,12 @@ export class OperationWorkset {
|
|
96
90
|
return this.instanceIdsToUpdate.has(instanceId)
|
97
91
|
}
|
98
92
|
|
99
|
-
public updateState(update: InstanceStateUpdate): InstanceState {
|
93
|
+
public updateState(update: InstanceStateUpdate, phase: OperationPhase): InstanceState {
|
100
94
|
const finalState = applyPartialInstanceState(this.stateMap, update)
|
101
95
|
this.stateManager.emitStatePatch(this.operation.projectId, createInstanceStatePatch(update))
|
102
96
|
|
103
97
|
if (finalState.parentId) {
|
104
|
-
this.recalculateCompositeInstanceState(finalState.parentId)
|
98
|
+
this.recalculateCompositeInstanceState(finalState.parentId, phase)
|
105
99
|
}
|
106
100
|
|
107
101
|
return finalState
|
@@ -202,7 +196,7 @@ export class OperationWorkset {
|
|
202
196
|
.filter((state): state is InstanceState => !!state)
|
203
197
|
}
|
204
198
|
|
205
|
-
private recalculateCompositeInstanceState(instanceId: string): void {
|
199
|
+
private recalculateCompositeInstanceState(instanceId: string, phase: OperationPhase): void {
|
206
200
|
const state = this.stateMap.get(instanceId) ?? createInstanceState(instanceId)
|
207
201
|
let currentResourceCount = 0
|
208
202
|
let totalResourceCount = 0
|
@@ -222,6 +216,7 @@ export class OperationWorkset {
|
|
222
216
|
|
223
217
|
const updatedState = {
|
224
218
|
...state,
|
219
|
+
status: OperationWorkset.getStatusByOperationPhase(phase),
|
225
220
|
currentResourceCount,
|
226
221
|
totalResourceCount,
|
227
222
|
}
|
@@ -230,7 +225,7 @@ export class OperationWorkset {
|
|
230
225
|
this.stateManager.emitStatePatch(this.operation.projectId, updatedState)
|
231
226
|
|
232
227
|
if (state.parentId) {
|
233
|
-
this.recalculateCompositeInstanceState(state.parentId)
|
228
|
+
this.recalculateCompositeInstanceState(state.parentId, phase)
|
234
229
|
}
|
235
230
|
}
|
236
231
|
|
@@ -279,7 +274,7 @@ export class OperationWorkset {
|
|
279
274
|
}
|
280
275
|
|
281
276
|
const state = this.stateMap.get(instance.id)
|
282
|
-
const { inputHash: expectedInputHash } =
|
277
|
+
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(instance.id)
|
283
278
|
|
284
279
|
if (this.operation.options.forceUpdateDependencies) {
|
285
280
|
this.instanceIdsToUpdate.add(instanceId)
|
@@ -315,7 +310,7 @@ export class OperationWorkset {
|
|
315
310
|
}
|
316
311
|
|
317
312
|
const state = this.stateMap.get(child.id)
|
318
|
-
const { inputHash: expectedInputHash } =
|
313
|
+
const { inputHash: expectedInputHash } = this.inputHashResolver.requireOutput(child.id)
|
319
314
|
|
320
315
|
if (state?.status !== "created" || state.inputHash !== expectedInputHash) {
|
321
316
|
this.instanceIdsToUpdate.add(child.id)
|
@@ -431,21 +426,35 @@ export class OperationWorkset {
|
|
431
426
|
return undefined
|
432
427
|
}
|
433
428
|
|
429
|
+
private static getStatusByOperationPhase(phase: OperationPhase): InstanceStatus {
|
430
|
+
switch (phase) {
|
431
|
+
case "update":
|
432
|
+
return "updating"
|
433
|
+
case "destroy":
|
434
|
+
return "destroying"
|
435
|
+
case "refresh":
|
436
|
+
return "refreshing"
|
437
|
+
}
|
438
|
+
}
|
439
|
+
|
434
440
|
public async getUpToDateInputHash(instance: InstanceModel): Promise<string> {
|
435
|
-
|
436
|
-
|
437
|
-
this.inputHashResolverInputs.set(instance.id, {
|
438
|
-
instance,
|
439
|
-
component,
|
440
|
-
resolvedInputs: this.resolvedInstanceInputs.get(instance.id)!,
|
441
|
-
state: this.stateMap.get(instance.id),
|
442
|
-
sourceHash: this.getSourceHashIfApplicable(instance, component),
|
443
|
-
})
|
441
|
+
return await this.inputHashResolverLock.acquire(async () => {
|
442
|
+
const component = this.library.components[instance.type]
|
444
443
|
|
445
|
-
|
444
|
+
this.inputHashNodes.set(instance.id, {
|
445
|
+
instance,
|
446
|
+
component,
|
447
|
+
resolvedInputs: this.resolvedInstanceInputs.get(instance.id)!,
|
448
|
+
state: this.stateMap.get(instance.id),
|
449
|
+
sourceHash: this.getSourceHashIfApplicable(instance, component),
|
450
|
+
})
|
446
451
|
|
447
|
-
|
448
|
-
|
452
|
+
this.inputHashResolver.invalidate(instance.id)
|
453
|
+
await this.inputHashResolver.process()
|
454
|
+
|
455
|
+
const { inputHash } = this.inputHashResolver.requireOutput(instance.id)
|
456
|
+
return inputHash
|
457
|
+
})
|
449
458
|
}
|
450
459
|
|
451
460
|
public getLockInstanceIds(): string[] {
|
@@ -468,10 +477,6 @@ export class OperationWorkset {
|
|
468
477
|
stateBackend.getAllInstanceStates(operation.projectId, signal),
|
469
478
|
])
|
470
479
|
|
471
|
-
const unitSources = await libraryBackend.getResolvedUnitSources(
|
472
|
-
unique(project.instances.map(i => i.type)),
|
473
|
-
)
|
474
|
-
|
475
480
|
const workset = new OperationWorkset(
|
476
481
|
operation,
|
477
482
|
library,
|
@@ -479,10 +484,6 @@ export class OperationWorkset {
|
|
479
484
|
logger.child({ operationId: operation.id, service: "OperationWorkset" }),
|
480
485
|
)
|
481
486
|
|
482
|
-
for (const unitSource of unitSources) {
|
483
|
-
workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash)
|
484
|
-
}
|
485
|
-
|
486
487
|
// prepare instances
|
487
488
|
for (const instance of project.instances) {
|
488
489
|
workset.addInstance(instance)
|
@@ -510,6 +511,14 @@ export class OperationWorkset {
|
|
510
511
|
}
|
511
512
|
}
|
512
513
|
|
514
|
+
const unitSources = await libraryBackend.getResolvedUnitSources(
|
515
|
+
unique(Array.from(workset.instanceMap.values().map(i => i.type))),
|
516
|
+
)
|
517
|
+
|
518
|
+
for (const unitSource of unitSources) {
|
519
|
+
workset.unitSourceHashMap.set(unitSource.unitType, unitSource.sourceHash)
|
520
|
+
}
|
521
|
+
|
513
522
|
for (const state of states) {
|
514
523
|
if (!workset.instanceMap.has(state.id)) {
|
515
524
|
workset.logger.warn(
|
@@ -523,7 +532,7 @@ export class OperationWorkset {
|
|
523
532
|
|
524
533
|
// prepare input resolver
|
525
534
|
for (const instance of workset.instanceMap.values()) {
|
526
|
-
workset.
|
535
|
+
workset.inputResolverNodes.set(`instance:${instance.id}`, {
|
527
536
|
kind: "instance",
|
528
537
|
instance,
|
529
538
|
component: library.components[instance.type],
|
@@ -531,19 +540,17 @@ export class OperationWorkset {
|
|
531
540
|
}
|
532
541
|
|
533
542
|
for (const hub of project.hubs) {
|
534
|
-
workset.
|
543
|
+
workset.inputResolverNodes.set(`hub:${hub.id}`, { kind: "hub", hub })
|
535
544
|
}
|
536
545
|
|
537
|
-
workset.inputResolver =
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
{ promiseCache: workset.inputResolverPromiseCache },
|
542
|
-
)
|
546
|
+
workset.inputResolver = new InputResolver(workset.inputResolverNodes, logger)
|
547
|
+
workset.inputResolver.addAllNodesToWorkset()
|
548
|
+
|
549
|
+
await workset.inputResolver.process()
|
543
550
|
|
544
551
|
// resolve inputs for all instances and pass outputs to input hash resolver
|
545
552
|
for (const instance of workset.instanceMap.values()) {
|
546
|
-
const output =
|
553
|
+
const output = workset.inputResolver.requireOutput(`instance:${instance.id}`)
|
547
554
|
if (output.kind !== "instance") {
|
548
555
|
throw new Error("Unexpected output kind")
|
549
556
|
}
|
@@ -552,7 +559,7 @@ export class OperationWorkset {
|
|
552
559
|
|
553
560
|
const component = workset.library.components[instance.type]
|
554
561
|
|
555
|
-
workset.
|
562
|
+
workset.inputHashNodes.set(instance.id, {
|
556
563
|
instance,
|
557
564
|
component,
|
558
565
|
resolvedInputs: output.resolvedInputs,
|
@@ -562,12 +569,10 @@ export class OperationWorkset {
|
|
562
569
|
}
|
563
570
|
|
564
571
|
// prepare input hash resolver
|
565
|
-
workset.inputHashResolver =
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
{ promiseCache: workset.inputHashResolverPromiseCache },
|
570
|
-
)
|
572
|
+
workset.inputHashResolver = new InputHashResolver(workset.inputHashNodes, logger)
|
573
|
+
workset.inputHashResolver.addAllNodesToWorkset()
|
574
|
+
|
575
|
+
await workset.inputHashResolver.process()
|
571
576
|
|
572
577
|
if (operation.type !== "destroy") {
|
573
578
|
await workset.calculateInstanceIdsToUpdate()
|
@@ -11,13 +11,12 @@ import { mapValues } from "remeda"
|
|
11
11
|
import {
|
12
12
|
type InstanceState,
|
13
13
|
type InstanceStateUpdate,
|
14
|
-
type InstanceStatus,
|
15
14
|
type ProjectOperation,
|
16
15
|
type InstanceTriggerInvocation,
|
17
16
|
createInstanceState,
|
17
|
+
createAsyncBatcher,
|
18
18
|
} from "../shared"
|
19
19
|
import {
|
20
|
-
createAsyncBatcher,
|
21
20
|
errorToString,
|
22
21
|
isAbortError,
|
23
22
|
isAbortErrorLike,
|
@@ -220,7 +219,7 @@ export class RuntimeOperation {
|
|
220
219
|
...state,
|
221
220
|
parentId: instance?.parentId,
|
222
221
|
latestOperationId: this.operation.id,
|
223
|
-
status:
|
222
|
+
status: "pending",
|
224
223
|
error: null,
|
225
224
|
})
|
226
225
|
|
@@ -575,7 +574,7 @@ export class RuntimeOperation {
|
|
575
574
|
return
|
576
575
|
}
|
577
576
|
|
578
|
-
const state = this.workset.updateState(patch)
|
577
|
+
const state = this.workset.updateState(patch, this.currentPhase)
|
579
578
|
|
580
579
|
// do not persist anyting for preview operations
|
581
580
|
if (this.operation.type !== "preview") {
|
@@ -604,21 +603,6 @@ export class RuntimeOperation {
|
|
604
603
|
return instancePromise
|
605
604
|
}
|
606
605
|
|
607
|
-
private getStatusByOperationType(): InstanceStatus {
|
608
|
-
switch (this.operation.type) {
|
609
|
-
case "update":
|
610
|
-
return "updating"
|
611
|
-
case "preview":
|
612
|
-
return "previewing"
|
613
|
-
case "recreate":
|
614
|
-
return "updating"
|
615
|
-
case "destroy":
|
616
|
-
return "destroying"
|
617
|
-
case "refresh":
|
618
|
-
return "refreshing"
|
619
|
-
}
|
620
|
-
}
|
621
|
-
|
622
606
|
private getInstanceDependencies(instance: InstanceModel): InstanceModel[] {
|
623
607
|
const dependencies: InstanceModel[] = []
|
624
608
|
const instanceInputs = this.workset.resolvedInstanceInputs.get(instance.id) ?? {}
|