@highstate/backend 0.9.14 → 0.9.16

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.
Files changed (144) hide show
  1. package/dist/chunk-RCB4AFGD.js +159 -0
  2. package/dist/chunk-RCB4AFGD.js.map +1 -0
  3. package/dist/chunk-WHALQHEZ.js +2017 -0
  4. package/dist/chunk-WHALQHEZ.js.map +1 -0
  5. package/dist/highstate.manifest.json +3 -3
  6. package/dist/index.js +6146 -2174
  7. package/dist/index.js.map +1 -1
  8. package/dist/library/worker/main.js +51 -159
  9. package/dist/library/worker/main.js.map +1 -1
  10. package/dist/shared/index.js +159 -43
  11. package/package.json +25 -7
  12. package/src/artifact/abstractions.ts +46 -0
  13. package/src/artifact/encryption.ts +69 -0
  14. package/src/artifact/factory.ts +36 -0
  15. package/src/artifact/index.ts +3 -0
  16. package/src/artifact/local.ts +142 -0
  17. package/src/business/api-key.ts +65 -0
  18. package/src/business/artifact.ts +288 -0
  19. package/src/business/backend-unlock.ts +10 -0
  20. package/src/business/index.ts +9 -0
  21. package/src/business/instance-lock.ts +124 -0
  22. package/src/business/instance-state.ts +292 -0
  23. package/src/business/operation.ts +251 -0
  24. package/src/business/project-unlock.ts +242 -0
  25. package/src/business/secret.ts +187 -0
  26. package/src/business/worker.ts +161 -0
  27. package/src/common/index.ts +2 -1
  28. package/src/common/performance.ts +44 -0
  29. package/src/common/tree.ts +33 -0
  30. package/src/common/utils.ts +40 -1
  31. package/src/config.ts +14 -10
  32. package/src/hotstate/abstractions.ts +48 -0
  33. package/src/hotstate/factory.ts +17 -0
  34. package/src/{secret → hotstate}/index.ts +1 -0
  35. package/src/hotstate/manager.ts +192 -0
  36. package/src/hotstate/memory.ts +100 -0
  37. package/src/hotstate/validation.ts +101 -0
  38. package/src/index.ts +2 -1
  39. package/src/library/abstractions.ts +10 -23
  40. package/src/library/factory.ts +2 -2
  41. package/src/library/local.ts +89 -102
  42. package/src/library/worker/evaluator.ts +14 -47
  43. package/src/library/worker/loader.lite.ts +41 -0
  44. package/src/library/worker/main.ts +14 -65
  45. package/src/library/worker/protocol.ts +8 -24
  46. package/src/lock/abstractions.ts +6 -0
  47. package/src/lock/factory.ts +15 -0
  48. package/src/{workspace → lock}/index.ts +1 -0
  49. package/src/lock/manager.ts +82 -0
  50. package/src/lock/memory.ts +19 -0
  51. package/src/orchestrator/manager.ts +131 -82
  52. package/src/orchestrator/operation-workset.ts +188 -77
  53. package/src/orchestrator/operation.ts +975 -284
  54. package/src/project/abstractions.ts +20 -7
  55. package/src/project/factory.ts +1 -1
  56. package/src/project/index.ts +0 -1
  57. package/src/project/local.ts +73 -17
  58. package/src/project/manager.ts +272 -131
  59. package/src/pubsub/abstractions.ts +13 -0
  60. package/src/pubsub/factory.ts +19 -0
  61. package/src/pubsub/index.ts +3 -0
  62. package/src/pubsub/local.ts +36 -0
  63. package/src/pubsub/manager.ts +100 -0
  64. package/src/pubsub/validation.ts +33 -0
  65. package/src/runner/abstractions.ts +135 -68
  66. package/src/runner/artifact-env.ts +160 -0
  67. package/src/runner/factory.ts +20 -5
  68. package/src/runner/force-abort.ts +117 -0
  69. package/src/runner/local.ts +281 -372
  70. package/src/{common → runner}/pulumi.ts +86 -37
  71. package/src/services.ts +193 -35
  72. package/src/shared/index.ts +3 -11
  73. package/src/shared/models/backend/index.ts +3 -0
  74. package/src/shared/models/backend/project.ts +63 -0
  75. package/src/shared/models/backend/unlock-method.ts +20 -0
  76. package/src/shared/models/base.ts +151 -0
  77. package/src/shared/models/errors.ts +5 -0
  78. package/src/shared/models/index.ts +4 -0
  79. package/src/shared/models/project/api-key.ts +62 -0
  80. package/src/shared/models/project/artifact.ts +113 -0
  81. package/src/shared/models/project/component.ts +45 -0
  82. package/src/shared/models/project/index.ts +14 -0
  83. package/src/shared/{project.ts → models/project/instance.ts} +12 -0
  84. package/src/shared/models/project/lock.ts +91 -0
  85. package/src/shared/{operation.ts → models/project/operation.ts} +43 -8
  86. package/src/shared/models/project/page.ts +57 -0
  87. package/src/shared/models/project/secret.ts +112 -0
  88. package/src/shared/models/project/service-account.ts +22 -0
  89. package/src/shared/models/project/state.ts +432 -0
  90. package/src/shared/models/project/terminal.ts +99 -0
  91. package/src/shared/models/project/trigger.ts +56 -0
  92. package/src/shared/models/project/unlock-method.ts +31 -0
  93. package/src/shared/models/project/worker.ts +105 -0
  94. package/src/shared/resolvers/graph-resolver.ts +74 -13
  95. package/src/shared/resolvers/index.ts +5 -0
  96. package/src/shared/resolvers/input-hash.ts +53 -15
  97. package/src/shared/resolvers/input.ts +1 -9
  98. package/src/shared/resolvers/registry.ts +7 -2
  99. package/src/shared/resolvers/state.ts +12 -0
  100. package/src/shared/resolvers/validation.ts +61 -20
  101. package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
  102. package/src/shared/utils/hash.ts +6 -0
  103. package/src/shared/utils/index.ts +3 -0
  104. package/src/shared/utils/promise-tracker.ts +23 -0
  105. package/src/state/abstractions.ts +330 -101
  106. package/src/state/encryption.ts +59 -0
  107. package/src/state/factory.ts +3 -5
  108. package/src/state/index.ts +3 -0
  109. package/src/state/keyring.ts +22 -0
  110. package/src/state/local/backend.ts +299 -0
  111. package/src/state/local/collection.ts +342 -0
  112. package/src/state/local/index.ts +2 -0
  113. package/src/state/manager.ts +804 -18
  114. package/src/state/repository/index.ts +2 -0
  115. package/src/state/repository/repository.index.ts +193 -0
  116. package/src/state/repository/repository.ts +458 -0
  117. package/src/terminal/{shared.ts → abstractions.ts} +3 -3
  118. package/src/terminal/docker.ts +18 -14
  119. package/src/terminal/factory.ts +3 -3
  120. package/src/terminal/index.ts +1 -1
  121. package/src/terminal/manager.ts +134 -80
  122. package/src/terminal/run.sh.ts +22 -10
  123. package/src/worker/abstractions.ts +42 -0
  124. package/src/worker/docker.ts +83 -0
  125. package/src/worker/factory.ts +20 -0
  126. package/src/worker/index.ts +3 -0
  127. package/src/worker/manager.ts +139 -0
  128. package/dist/chunk-C2TJAQAD.js +0 -937
  129. package/dist/chunk-C2TJAQAD.js.map +0 -1
  130. package/dist/chunk-WXDYCRTT.js +0 -234
  131. package/dist/chunk-WXDYCRTT.js.map +0 -1
  132. package/src/library/worker/loader.ts +0 -114
  133. package/src/preferences/shared.ts +0 -1
  134. package/src/project/lock.ts +0 -39
  135. package/src/secret/abstractions.ts +0 -59
  136. package/src/secret/factory.ts +0 -22
  137. package/src/secret/local.ts +0 -152
  138. package/src/shared/state.ts +0 -270
  139. package/src/shared/terminal.ts +0 -13
  140. package/src/state/local.ts +0 -612
  141. package/src/workspace/abstractions.ts +0 -41
  142. package/src/workspace/factory.ts +0 -14
  143. package/src/workspace/local.ts +0 -54
  144. /package/src/shared/{library.ts → models/backend/library.ts} +0 -0
@@ -1,15 +1,10 @@
1
- import type {
2
- InstanceEvaluationResult,
3
- LibraryBackend,
4
- ModuleEvaluationResult,
5
- ResolvedUnitSource,
6
- } from "./abstractions"
1
+ import type { InstanceEvaluationResult, LibraryBackend, ResolvedUnitSource } from "./abstractions"
7
2
  import type { Logger } from "pino"
8
3
  import type {
9
4
  PackageResolutionResponse,
10
5
  PackageResolutionWorkerData,
11
6
  } from "./package-resolution-worker"
12
- import type { WorkerRequest, WorkerResponse } from "./worker/protocol"
7
+ import type { WorkerData, WorkerResponse } from "./worker/protocol"
13
8
  import { fileURLToPath } from "node:url"
14
9
  import { EventEmitter, on } from "node:events"
15
10
  import { Worker } from "node:worker_threads"
@@ -23,6 +18,7 @@ import { z } from "zod"
23
18
  import { readPackageJSON } from "pkg-types"
24
19
  import { runScript, installDependencies, addDependency } from "nypm"
25
20
  import { flatMap, groupBy, map, pipe, unique } from "remeda"
21
+ import { decode } from "@msgpack/msgpack"
26
22
  import { resolveMainLocalProject, stringArrayType } from "../common"
27
23
  import {
28
24
  diffLibraries,
@@ -32,8 +28,8 @@ import {
32
28
  } from "../shared"
33
29
 
34
30
  export const localLibraryBackendConfig = z.object({
35
- HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES: stringArrayType.default("@highstate/library"),
36
- HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS: stringArrayType.optional(),
31
+ HIGHSTATE_LIBRARY_BACKEND_LOCAL_PACKAGES: stringArrayType.default("@highstate/library"),
32
+ HIGHSTATE_LIBRARY_BACKEND_LOCAL_WATCH_PATHS: stringArrayType.optional(),
37
33
  })
38
34
 
39
35
  interface Events {
@@ -42,7 +38,7 @@ interface Events {
42
38
  }
43
39
 
44
40
  type HighstateManifestJson = {
45
- sourceHashes?: Record<string, string>
41
+ sourceHashes?: Record<string, number>
46
42
  }
47
43
 
48
44
  type LibraryPackage = {
@@ -59,7 +55,6 @@ export class LocalLibraryBackend implements LibraryBackend {
59
55
  private readonly eventEmitter = new EventEmitter<Events>()
60
56
 
61
57
  private library: LibraryModel | null = null
62
- private worker: Worker | null = null
63
58
 
64
59
  private readonly packages = new Map<string, LibraryPackage>()
65
60
  private readonly resolvedUnitSources = new Map<string, ResolvedUnitSource>()
@@ -88,13 +83,12 @@ export class LocalLibraryBackend implements LibraryBackend {
88
83
 
89
84
  async loadLibrary(): Promise<LibraryModel> {
90
85
  return await this.lock.acquire(async () => {
91
- const [library] = await this.getLibrary()
92
-
93
- return library
86
+ return await this.getLibrary()
94
87
  })
95
88
  }
96
89
 
97
- async *watchLibrary(signal?: AbortSignal): AsyncIterable<LibraryUpdate[]> {
90
+ async *watchLibrary(_libraryId: string, signal?: AbortSignal): AsyncIterable<LibraryUpdate[]> {
91
+ // For local backend, we ignore libraryId since there's only one library
98
92
  for await (const [library] of on(this.eventEmitter, "library", { signal })) {
99
93
  yield library
100
94
  }
@@ -106,9 +100,12 @@ export class LocalLibraryBackend implements LibraryBackend {
106
100
  })
107
101
  }
108
102
 
109
- async getResolvedUnitSources(unitTypes: string[]): Promise<ResolvedUnitSource[]> {
103
+ async getResolvedUnitSources(
104
+ _libraryId: string,
105
+ unitTypes: string[],
106
+ ): Promise<ResolvedUnitSource[]> {
110
107
  return await this.lock.acquire(async () => {
111
- const [library] = await this.getLibrary()
108
+ const library = await this.getLibrary()
112
109
 
113
110
  const units = unitTypes
114
111
  .map(type => library.components[type])
@@ -135,7 +132,11 @@ export class LocalLibraryBackend implements LibraryBackend {
135
132
  })
136
133
  }
137
134
 
138
- async *watchResolvedUnitSources(signal?: AbortSignal): AsyncIterable<ResolvedUnitSource> {
135
+ async *watchResolvedUnitSources(
136
+ _libraryId: string,
137
+ signal?: AbortSignal,
138
+ ): AsyncIterable<ResolvedUnitSource> {
139
+ // For local backend, we ignore libraryId since there's only one library
139
140
  for await (const [resolvedUnitSource] of on(this.eventEmitter, "resolvedUnitSource", {
140
141
  signal,
141
142
  })) {
@@ -144,49 +145,20 @@ export class LocalLibraryBackend implements LibraryBackend {
144
145
  }
145
146
 
146
147
  async evaluateCompositeInstances(
148
+ _libraryId: string,
147
149
  allInstances: InstanceModel[],
148
150
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
149
151
  instanceIds: string[],
150
152
  ): Promise<InstanceEvaluationResult[]> {
151
- return await this.lock.acquire(async () => {
152
- this.logger.info("evaluating %d composite instances", instanceIds.length)
153
-
154
- const [, worker] = await this.getLibrary()
155
- worker.postMessage({
156
- type: "evaluate-composite-instances",
157
- allInstances,
158
- resolvedInputs,
159
- instanceIds,
160
- } satisfies WorkerRequest)
161
-
162
- this.logger.debug("evaluation request sent")
163
-
164
- const { results } = await this.getResult(worker, "instance-evaluation-results")
165
- return results
166
- })
167
- }
168
-
169
- async evaluateModules(modulePaths: string[]): Promise<ModuleEvaluationResult> {
170
- return await this.lock.acquire(async () => {
171
- this.logger.info({ msg: "evaluating modules", modulePaths })
172
-
173
- const [, worker] = await this.getLibrary()
174
- worker.postMessage({
175
- type: "evaluate-modules",
176
- modulePaths,
177
- } satisfies WorkerRequest)
153
+ this.logger.info("evaluating %d composite instances", instanceIds.length)
178
154
 
179
- this.logger.debug("evaluation request sent")
180
-
181
- const { result } = await this.getResult(worker, "module-evaluation-result")
182
- return result
155
+ const worker = this.createLibraryWorker({
156
+ libraryModulePaths: this.libraryPackages,
157
+ allInstances,
158
+ resolvedInputs,
159
+ instanceIds,
183
160
  })
184
- }
185
161
 
186
- private async getResult<TType extends WorkerResponse["type"]>(
187
- worker: Worker,
188
- expectedType: TType,
189
- ): Promise<WorkerResponse & { type: TType }> {
190
162
  for await (const [event] of on(worker, "message")) {
191
163
  const eventData = event as WorkerResponse
192
164
 
@@ -194,69 +166,56 @@ export class LocalLibraryBackend implements LibraryBackend {
194
166
  throw new Error(`Worker error: ${eventData.error}`)
195
167
  }
196
168
 
197
- if (eventData.type !== expectedType) {
198
- throw new Error(
199
- `Unexpected response message type "${eventData.type}", expected "${expectedType}"`,
200
- )
201
- }
202
-
203
- return eventData as WorkerResponse & { type: TType }
169
+ this.logger.info("composite instances evaluated successfully")
170
+ return eventData.results
204
171
  }
205
172
 
206
173
  throw new Error("Worker ended without sending any response")
207
174
  }
208
175
 
209
- private async getLibrary(): Promise<[LibraryModel, Worker]> {
210
- if (this.library && this.worker) {
211
- return [this.library, this.worker]
176
+ private async getLibrary(): Promise<LibraryModel> {
177
+ if (this.library) {
178
+ return this.library
212
179
  }
213
180
 
214
181
  return await this.reloadLibrary()
215
182
  }
216
183
 
217
- private async reloadLibrary(): Promise<[LibraryModel, Worker]> {
184
+ private async reloadLibrary(): Promise<LibraryModel> {
218
185
  this.logger.info("reloading library")
219
186
 
220
187
  this.eventEmitter.emit("library", [{ type: "reload-started" }])
221
188
 
222
189
  await this.ensureLibraryPackagesLoaded(this.libraryPackages, true)
223
190
 
224
- this.worker = this.createLibraryWorker({
225
- modulePaths: this.libraryPackages,
226
- logLevel: "warn",
227
- })
191
+ const loadedPackages = this.packages
192
+ .values()
193
+ .filter(pkg => this.libraryPackages.includes(pkg.name))
228
194
 
229
- for await (const [event] of on(this.worker, "message")) {
230
- const eventData = event as {
231
- type: string
232
- library: LibraryModel
233
- error?: string
234
- }
195
+ const mergedLibrary: LibraryModel = { components: {}, entities: {} }
235
196
 
236
- if (eventData.type === "error") {
237
- throw new Error(`Worker error: ${eventData.error}`)
238
- }
197
+ for (const loadedPackage of loadedPackages) {
198
+ const libraryContent = await this.readLibraryContent(loadedPackage)
239
199
 
240
- if (eventData.type !== "library") {
241
- throw new Error(`Unexpected message type '${eventData.type}', expected 'library'`)
200
+ for (const [componentType, component] of Object.entries(libraryContent.components)) {
201
+ mergedLibrary.components[componentType] = component
242
202
  }
243
203
 
244
- const updates = diffLibraries(
245
- this.library ?? { components: {}, entities: {} },
246
- eventData.library,
247
- )
204
+ for (const [entityType, entity] of Object.entries(libraryContent.entities)) {
205
+ mergedLibrary.entities[entityType] = entity
206
+ }
207
+ }
248
208
 
249
- this.eventEmitter.emit("library", updates)
250
- this.library = eventData.library
209
+ const updates = diffLibraries(this.library ?? { components: {}, entities: {} }, mergedLibrary)
251
210
 
252
- this.logger.info("library reloaded")
211
+ this.eventEmitter.emit("library", updates)
212
+ this.library = mergedLibrary
253
213
 
254
- this.eventEmitter.emit("library", [{ type: "reload-completed" }])
214
+ this.logger.info("library reloaded")
255
215
 
256
- return [this.library, this.worker]
257
- }
216
+ this.eventEmitter.emit("library", [{ type: "reload-completed" }])
258
217
 
259
- throw new Error("Worker ended without sending library model")
218
+ return this.library
260
219
  }
261
220
 
262
221
  private async reloadUnitManifest(libraryPackage: LibraryPackage): Promise<void> {
@@ -347,9 +306,7 @@ export class LocalLibraryBackend implements LibraryBackend {
347
306
  this.logger.info(`rebuilding library package "${libraryPackage.name}" via build script`)
348
307
  await runScript("build", { cwd: libraryPackage.rootPath })
349
308
 
350
- if (this.libraryPackages.includes(libraryPackage.name)) {
351
- await this.reloadLibrary()
352
- } else {
309
+ if (!this.libraryPackages.includes(libraryPackage.name)) {
353
310
  await this.reloadUnitManifest(libraryPackage)
354
311
  }
355
312
 
@@ -361,7 +318,7 @@ export class LocalLibraryBackend implements LibraryBackend {
361
318
  const parsedName = LocalLibraryBackend.parseDependencyName(libraryPackage.name)
362
319
 
363
320
  const dependencyPackageNames = pipe(
364
- [packageJson.dependencies, packageJson.devDependencies, packageJson.peerDependencies],
321
+ [packageJson.dependencies, packageJson.peerDependencies],
365
322
  flatMap(deps => Object.keys(deps ?? {})),
366
323
  unique(),
367
324
  map(LocalLibraryBackend.parseDependencyName),
@@ -440,8 +397,27 @@ export class LocalLibraryBackend implements LibraryBackend {
440
397
  }
441
398
  }
442
399
 
400
+ private async readLibraryContent(libraryPackage: LibraryPackage): Promise<LibraryModel> {
401
+ const contentPath = resolve(libraryPackage.rootPath, "dist", "highstate.library.msgpack")
402
+
403
+ try {
404
+ const contentBuffer = await readFile(contentPath)
405
+ const content = decode(contentBuffer) as LibraryModel
406
+
407
+ return content
408
+ } catch (error) {
409
+ this.logger.debug(
410
+ { error },
411
+ `failed to read highstate library content of package: "%s"`,
412
+ libraryPackage.name,
413
+ )
414
+
415
+ return { components: {}, entities: {} }
416
+ }
417
+ }
418
+
443
419
  private async loadLibraryPackages(names: string[], installIfNotFound = false): Promise<void> {
444
- this.logger.info("loading library packages: %s", names.join(", "))
420
+ this.logger.debug("loading library packages: %s", names.join(", "))
445
421
 
446
422
  const missingPackages: string[] = []
447
423
  const packagesToUpdate: LibraryPackage[] = []
@@ -484,7 +460,10 @@ export class LocalLibraryBackend implements LibraryBackend {
484
460
  for (const libraryPackage of packagesToUpdate) {
485
461
  await this.updateLibraryPackageDependencies(libraryPackage)
486
462
 
487
- if (!this.libraryPackages.includes(libraryPackage.name)) {
463
+ if (
464
+ !this.libraryPackages.includes(libraryPackage.name) &&
465
+ libraryPackage.name !== "@highstate/contract"
466
+ ) {
488
467
  await this.reloadUnitManifest(libraryPackage)
489
468
  }
490
469
  }
@@ -500,13 +479,21 @@ export class LocalLibraryBackend implements LibraryBackend {
500
479
  await this.lock.acquire(async () => {
501
480
  const libraryPackage = this.packages.values().find(pkg => path.startsWith(pkg.rootPath))
502
481
 
503
- if (libraryPackage) {
504
- await this.rebuildLibraryPackage(libraryPackage)
482
+ if (!libraryPackage) {
483
+ return
484
+ }
485
+
486
+ const builtPackages = new Set<string>()
487
+ await this.rebuildLibraryPackage(libraryPackage, false, false, builtPackages)
488
+
489
+ if (this.libraryPackages.some(pkg => builtPackages.has(pkg))) {
490
+ this.logger.info("reloading library due to file change in package: %s", libraryPackage.name)
491
+ await this.reloadLibrary()
505
492
  }
506
493
  })
507
494
  }
508
495
 
509
- private createLibraryWorker(workerData: Record<string, unknown>) {
496
+ private createLibraryWorker(workerData: WorkerData): Worker {
510
497
  const workerPathUrl = importMetaResolve(`@highstate/backend/library-worker`, import.meta.url)
511
498
  const workerPath = fileURLToPath(workerPathUrl)
512
499
 
@@ -524,14 +511,14 @@ export class LocalLibraryBackend implements LibraryBackend {
524
511
  }
525
512
 
526
513
  static async create(config: z.infer<typeof localLibraryBackendConfig>, logger: Logger) {
527
- let watchPaths = config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_WATCH_PATHS
514
+ let watchPaths = config.HIGHSTATE_LIBRARY_BACKEND_LOCAL_WATCH_PATHS
528
515
  if (!watchPaths) {
529
516
  const [projectPath] = await resolveMainLocalProject()
530
517
  watchPaths = [resolve(projectPath, "packages")]
531
518
  }
532
519
 
533
520
  return new LocalLibraryBackend(
534
- config.HIGHSTATE_BACKEND_LIBRARY_LOCAL_PACKAGES,
521
+ config.HIGHSTATE_LIBRARY_BACKEND_LOCAL_PACKAGES,
535
522
  watchPaths,
536
523
  logger.child({ backend: "LibraryBackend", service: "LocalLibraryBackend" }),
537
524
  )
@@ -1,50 +1,17 @@
1
1
  import type { Logger } from "pino"
2
- import type { Library } from "./loader"
3
- import type { Jiti } from "jiti"
4
- import type { InstanceEvaluationResult, ModuleEvaluationResult } from "../abstractions"
2
+ import type { InstanceEvaluationResult } from "../abstractions"
5
3
  import type { ResolvedInstanceInput } from "../../shared"
6
- import { getCompositeInstances, resetEvaluation, type InstanceModel } from "@highstate/contract"
7
- import { BetterLock } from "better-lock"
4
+ import {
5
+ getCompositeInstances,
6
+ resetEvaluation,
7
+ type Component,
8
+ type InstanceModel,
9
+ } from "@highstate/contract"
8
10
  import { errorToString } from "../../common"
9
11
 
10
- const lock = new BetterLock()
11
-
12
- export function evaluateModules(
13
- jiti: Jiti,
14
- logger: Logger,
15
- modulePaths: string[],
16
- ): Promise<ModuleEvaluationResult> {
17
- return lock.acquire(async () => {
18
- resetEvaluation()
19
- let lastModulePath = ""
20
-
21
- try {
22
- for (const modulePath of modulePaths) {
23
- logger.info("loading module: %s", modulePath)
24
-
25
- lastModulePath = modulePath
26
- await jiti.import(modulePath)
27
-
28
- logger.debug("module loaded: %s", modulePath)
29
- }
30
-
31
- return {
32
- success: true,
33
- compositeInstances: getCompositeInstances(),
34
- }
35
- } catch (error) {
36
- return {
37
- success: false,
38
- modulePath: lastModulePath,
39
- error: errorToString(error),
40
- }
41
- }
42
- })
43
- }
44
-
45
12
  export function evaluateInstances(
46
13
  logger: Logger,
47
- library: Library,
14
+ components: Readonly<Record<string, Component>>,
48
15
  allInstances: InstanceModel[],
49
16
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
50
17
  instanceIds: string[],
@@ -53,19 +20,19 @@ export function evaluateInstances(
53
20
  const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))
54
21
 
55
22
  for (const instanceId of instanceIds ?? []) {
56
- const externalInstanceIds = new Set(allInstances.map(instance => instance.id))
57
- externalInstanceIds.delete(instanceId)
58
-
59
23
  try {
60
24
  logger.debug({ instanceId }, "evaluating top-level instance")
61
- resetEvaluation(externalInstanceIds)
25
+ resetEvaluation()
62
26
 
63
27
  evaluateInstance(instanceId)
64
28
 
65
29
  results.push({
66
30
  success: true,
67
31
  instanceId,
68
- compositeInstances: getCompositeInstances(),
32
+ compositeInstances: getCompositeInstances().filter(
33
+ instance =>
34
+ instanceId.includes(instance.instance.id) || !allInstancesMap.has(instance.instance.id),
35
+ ),
69
36
  })
70
37
  } catch (error) {
71
38
  results.push({
@@ -113,7 +80,7 @@ export function evaluateInstances(
113
80
  })
114
81
  }
115
82
 
116
- const component = library.components[instance.type]
83
+ const component = components[instance.type]
117
84
  if (!component) {
118
85
  throw new Error(`Component not found: ${instance.type}, required by instance: ${instanceId}`)
119
86
  }
@@ -0,0 +1,41 @@
1
+ import type { Logger } from "pino"
2
+ import { isComponent, type Component } from "@highstate/contract"
3
+
4
+ export async function loadComponents(
5
+ logger: Logger,
6
+ modulePaths: string[],
7
+ ): Promise<Readonly<Record<string, Component>>> {
8
+ const modules: Record<string, unknown> = {}
9
+ for (const modulePath of modulePaths) {
10
+ try {
11
+ logger.debug({ modulePath }, "loading module")
12
+ modules[modulePath] = await import(modulePath)
13
+
14
+ logger.debug({ modulePath }, "module loaded")
15
+ } catch (err) {
16
+ logger.error({ modulePath, err }, "module load failed")
17
+ }
18
+ }
19
+
20
+ const components: Record<string, Component> = {}
21
+
22
+ await _loadLibrary(modules, components)
23
+ logger.debug("library loaded with %s components", Object.keys(components).length)
24
+
25
+ return components
26
+ }
27
+
28
+ async function _loadLibrary(value: unknown, components: Record<string, Component>): Promise<void> {
29
+ if (isComponent(value)) {
30
+ components[value.model.type] = value
31
+ return
32
+ }
33
+
34
+ if (typeof value !== "object" || value === null) {
35
+ return
36
+ }
37
+
38
+ for (const key in value) {
39
+ await _loadLibrary((value as Record<string, unknown>)[key], components)
40
+ }
41
+ }
@@ -1,82 +1,31 @@
1
- import type { WorkerData, WorkerRequest, WorkerResponse } from "./protocol"
1
+ import type { WorkerData, WorkerResponse } from "./protocol"
2
2
  import { parentPort, workerData } from "node:worker_threads"
3
- import { createJiti, type Jiti } from "jiti"
4
3
  import { pino } from "pino"
5
- import { mapValues } from "remeda"
6
4
  import { errorToString } from "../../common"
7
- import { loadLibrary, type Library } from "./loader"
8
- import { evaluateInstances, evaluateModules } from "./evaluator"
5
+ import { evaluateInstances } from "./evaluator"
6
+ import { loadComponents } from "./loader.lite"
9
7
 
10
8
  const data = workerData as WorkerData
11
9
 
12
10
  const logger = pino({ name: "library-worker", level: data.logLevel })
13
- let jiti: Jiti
14
- let library: Library
15
11
 
16
12
  try {
17
- logger.info("worker started")
18
- logger.trace({ data }, "worker data")
13
+ const library = await loadComponents(logger, data.libraryModulePaths)
19
14
 
20
- jiti = createJiti(import.meta.filename)
21
- logger.debug({ filename: import.meta.filename }, "jiti created")
15
+ const results = evaluateInstances(
16
+ logger,
17
+ library,
18
+ data.allInstances,
19
+ data.resolvedInputs,
20
+ data.instanceIds,
21
+ )
22
22
 
23
- library = await loadLibrary(jiti, logger, data.modulePaths)
24
-
25
- parentPort!.postMessage({
26
- type: "library",
27
- library: {
28
- components: mapValues(library.components, component => component.model),
29
- entities: library.entities,
30
- },
31
- } satisfies WorkerResponse)
32
-
33
- logger.info("library loaded and sent")
23
+ parentPort!.postMessage({ type: "results", results } satisfies WorkerResponse)
34
24
  } catch (error) {
35
- logger.error({ error }, "failed to load library")
25
+ logger.error({ error }, "failed to evaluate")
36
26
 
37
27
  parentPort!.postMessage({
38
28
  type: "error",
39
29
  error: errorToString(error),
40
- })
30
+ } satisfies WorkerResponse)
41
31
  }
42
-
43
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
44
- parentPort!.on("message", async message => {
45
- try {
46
- const request = message as WorkerRequest
47
-
48
- switch (request.type) {
49
- case "evaluate-composite-instances": {
50
- const results = evaluateInstances(
51
- logger,
52
- library,
53
- request.allInstances,
54
- request.resolvedInputs,
55
- request.instanceIds,
56
- )
57
-
58
- parentPort!.postMessage({
59
- type: "instance-evaluation-results",
60
- results,
61
- } satisfies WorkerResponse)
62
- break
63
- }
64
- case "evaluate-modules": {
65
- const result = await evaluateModules(jiti, logger, request.modulePaths)
66
-
67
- parentPort!.postMessage({
68
- type: "module-evaluation-result",
69
- result,
70
- } satisfies WorkerResponse)
71
- break
72
- }
73
- }
74
- } catch (error) {
75
- logger.error({ error }, "failed to evaluate")
76
-
77
- parentPort!.postMessage({
78
- type: "error",
79
- error: errorToString(error),
80
- } satisfies WorkerResponse)
81
- }
82
- })
@@ -1,37 +1,21 @@
1
1
  import type { InstanceModel } from "@highstate/contract"
2
- import type { InstanceEvaluationResult, ModuleEvaluationResult } from "../abstractions"
3
- import type { LibraryModel, ResolvedInstanceInput } from "../../shared"
2
+ import type { InstanceEvaluationResult } from "../abstractions"
3
+ import type { ResolvedInstanceInput } from "../../shared"
4
4
 
5
5
  export type WorkerData = {
6
- modulePaths: string[]
6
+ libraryModulePaths: string[]
7
7
  logLevel?: string
8
- }
9
8
 
10
- export type WorkerRequest =
11
- | {
12
- type: "evaluate-composite-instances"
13
- allInstances: InstanceModel[]
14
- resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>
15
- instanceIds: string[]
16
- }
17
- | {
18
- type: "evaluate-modules"
19
- modulePaths: string[]
20
- }
9
+ allInstances: InstanceModel[]
10
+ resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>
11
+ instanceIds: string[]
12
+ }
21
13
 
22
14
  export type WorkerResponse =
23
15
  | {
24
- type: "library"
25
- library: LibraryModel
26
- }
27
- | {
28
- type: "instance-evaluation-results"
16
+ type: "results"
29
17
  results: InstanceEvaluationResult[]
30
18
  }
31
- | {
32
- type: "module-evaluation-result"
33
- result: ModuleEvaluationResult
34
- }
35
19
  | {
36
20
  type: "error"
37
21
  error: string
@@ -0,0 +1,6 @@
1
+ export interface LockBackend {
2
+ /**
3
+ * Acquires locks for the given keys and executes the function.
4
+ */
5
+ acquire<T>(keys: string[], fn: () => Promise<T> | T): Promise<T>
6
+ }
@@ -0,0 +1,15 @@
1
+ import type { LockBackend } from "./abstractions"
2
+ import { z } from "zod"
3
+ import { MemoryLockBackend } from "./memory"
4
+
5
+ export const lockBackendConfig = z.object({
6
+ HIGHSTATE_LOCK_BACKEND_TYPE: z.enum(["memory"]).default("memory"),
7
+ })
8
+
9
+ export function createLockBackend(config: z.infer<typeof lockBackendConfig>): LockBackend {
10
+ switch (config.HIGHSTATE_LOCK_BACKEND_TYPE) {
11
+ case "memory": {
12
+ return MemoryLockBackend.create()
13
+ }
14
+ }
15
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./abstractions"
2
2
  export * from "./factory"
3
+ export * from "./manager"