@highstate/backend 0.7.8 → 0.7.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.
@@ -3,6 +3,7 @@ import type { ProjectBackend } from "../project"
3
3
  import type { StateBackend, StateManager } from "../state"
4
4
  import type { Logger } from "pino"
5
5
  import { isUnitModel, type ComponentModel, type InstanceModel } from "@highstate/contract"
6
+ import { unique } from "remeda"
6
7
  import {
7
8
  applyPartialInstanceState,
8
9
  createInputHashResolver,
@@ -366,14 +367,17 @@ export class OperationWorkset {
366
367
  logger: Logger,
367
368
  signal: AbortSignal,
368
369
  ): Promise<OperationWorkset> {
369
- const [library, unitSources, project, compositeInstances, states] = await Promise.all([
370
+ const [library, project, compositeInstances, states] = await Promise.all([
370
371
  libraryBackend.loadLibrary(signal),
371
- libraryBackend.getResolvedUnitSources(),
372
372
  projectBackend.getProject(operation.projectId, signal),
373
373
  stateBackend.getCompositeInstances(operation.projectId, signal),
374
374
  stateBackend.getAllInstanceStates(operation.projectId, signal),
375
375
  ])
376
376
 
377
+ const unitSources = await libraryBackend.getResolvedUnitSources(
378
+ unique(project.instances.map(i => i.type)),
379
+ )
380
+
377
381
  const workset = new OperationWorkset(
378
382
  operation,
379
383
  library,
@@ -8,6 +8,7 @@ import {
8
8
  type InstanceInput,
9
9
  type InstanceModel,
10
10
  } from "@highstate/contract"
11
+ import { stringify, parse } from "yaml"
11
12
  import {
12
13
  type HubModel,
13
14
  type HubModelPatch,
@@ -38,7 +39,7 @@ export class LocalProjectBackend implements ProjectBackend {
38
39
  try {
39
40
  const files = await readdir(this.projectsDir)
40
41
 
41
- return files.filter(file => file.endsWith(".json")).map(file => file.replace(/\.json$/, ""))
42
+ return files.filter(file => file.endsWith(".yaml")).map(file => file.replace(/\.yaml$/, ""))
42
43
  } catch (error) {
43
44
  throw new Error("Failed to get project names", { cause: error })
44
45
  }
@@ -345,7 +346,7 @@ export class LocalProjectBackend implements ProjectBackend {
345
346
  }
346
347
 
347
348
  private getProjectPath(projectId: string) {
348
- return `${this.projectsDir}/${projectId}.json`
349
+ return `${this.projectsDir}/${projectId}.yaml`
349
350
  }
350
351
 
351
352
  private async loadProject(projectId: string) {
@@ -354,7 +355,7 @@ export class LocalProjectBackend implements ProjectBackend {
354
355
  try {
355
356
  const content = await readFile(projectPath, "utf-8")
356
357
 
357
- return projectModelSchema.parse(JSON.parse(content))
358
+ return projectModelSchema.parse(parse(content))
358
359
  } catch (error) {
359
360
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
360
361
  return { instances: {}, hubs: {} }
@@ -366,7 +367,7 @@ export class LocalProjectBackend implements ProjectBackend {
366
367
 
367
368
  private async writeProject(projectId: string, project: z.infer<typeof projectModelSchema>) {
368
369
  const projectPath = this.getProjectPath(projectId)
369
- const content = JSON.stringify(project, undefined, 2)
370
+ const content = stringify(project, undefined, 2)
370
371
 
371
372
  await writeFile(projectPath, content)
372
373
  }
@@ -15,6 +15,7 @@ import {
15
15
  createInstanceState,
16
16
  type CompositeInstance,
17
17
  type ResolvedInstanceInput,
18
+ type HubModel,
18
19
  } from "../shared"
19
20
 
20
21
  type CompositeInstanceEvent =
@@ -31,6 +32,12 @@ type CompositeInstanceEvents = {
31
32
  [K in string]: [CompositeInstanceEvent]
32
33
  }
33
34
 
35
+ export type FullProjectModel = {
36
+ instances: InstanceModel[]
37
+ hubs: HubModel[]
38
+ compositeInstances: CompositeInstance[]
39
+ }
40
+
34
41
  export class ProjectManager {
35
42
  private constructor(
36
43
  private readonly projectBackend: ProjectBackend,
@@ -56,6 +63,35 @@ export class ProjectManager {
56
63
  }
57
64
  }
58
65
 
66
+ /**
67
+ * Loads the full info of a project, including instances, hubs, and composite instances.
68
+ *
69
+ * Also filters out instances that are not in the library.
70
+ *
71
+ * @param projectId The ID of the project to load.
72
+ */
73
+ async getProject(projectId: string): Promise<FullProjectModel> {
74
+ const [{ instances, hubs }, compositeInstances, library] = await Promise.all([
75
+ this.projectBackend.getProject(projectId),
76
+ this.stateBackend.getCompositeInstances(projectId),
77
+ this.libraryBackend.loadLibrary(),
78
+ ])
79
+
80
+ const filteredInstances = instances.filter(instance => instance.type in library.components)
81
+ const filteredCompositeInstances = compositeInstances
82
+ .filter(instance => instance.instance.type in library.components)
83
+ .map(instance => ({
84
+ ...instance,
85
+ children: instance.children.filter(child => child.type in library.components),
86
+ }))
87
+
88
+ return {
89
+ instances: filteredInstances,
90
+ hubs,
91
+ compositeInstances: filteredCompositeInstances,
92
+ }
93
+ }
94
+
59
95
  async createInstance(projectId: string, instance: InstanceModel): Promise<InstanceModel> {
60
96
  const createdInstance = await this.projectBackend.createInstance(projectId, instance)
61
97
  await this.updateCompositeInstance(projectId, createdInstance)
@@ -255,9 +291,11 @@ export class ProjectManager {
255
291
 
256
292
  let sourceHash: string | undefined
257
293
  if (isUnitModel(library.components[instance.type])) {
258
- const resolvedUnit = await this.libraryBackend.getResolvedUnitSource(instance.type)
294
+ const resolvedUnits = await this.libraryBackend.getResolvedUnitSources([instance.type])
295
+ const resolvedUnit = resolvedUnits.find(unit => unit.unitType === instance.type)
296
+
259
297
  if (!resolvedUnit) {
260
- throw new Error(`Resolved unit not found: ${instance.type}`)
298
+ throw new Error(`Resolved unit not found for type "${instance.type}"`)
261
299
  }
262
300
 
263
301
  sourceHash = resolvedUnit.sourceHash
@@ -1,5 +1,5 @@
1
1
  import type { ConfigMap, Stack } from "@pulumi/pulumi/automation"
2
- import type { LibraryBackend } from "../library"
2
+ import type { LibraryBackend, ResolvedUnitSource } from "../library"
3
3
  import { EventEmitter, on } from "node:events"
4
4
  import { resolve } from "node:path"
5
5
  import { getInstanceId } from "@highstate/contract"
@@ -238,10 +238,7 @@ export class LocalRunnerBackend implements RunnerBackend {
238
238
  const instanceId = LocalRunnerBackend.getInstanceId(options)
239
239
 
240
240
  try {
241
- const resolvedSource = await this.libraryBackend.getResolvedUnitSource(options.instanceType)
242
- if (!resolvedSource) {
243
- throw new Error(`Resolved unit source not found for ${options.instanceType}`)
244
- }
241
+ const resolvedSource = await this.getResolvedUnitSource(options.instanceType)
245
242
 
246
243
  await this.pulumiProjectHost.runLocal(
247
244
  {
@@ -366,7 +363,7 @@ export class LocalRunnerBackend implements RunnerBackend {
366
363
  const instanceId = LocalRunnerBackend.getInstanceId(options)
367
364
 
368
365
  try {
369
- const resolvedSource = await this.libraryBackend.getResolvedUnitSource(options.instanceType)
366
+ const resolvedSource = await this.getResolvedUnitSource(options.instanceType)
370
367
  if (!resolvedSource) {
371
368
  throw new Error(`Resolved unit source not found for ${options.instanceType}`)
372
369
  }
@@ -666,6 +663,17 @@ export class LocalRunnerBackend implements RunnerBackend {
666
663
  return true
667
664
  }
668
665
 
666
+ private async getResolvedUnitSource(instanceType: string): Promise<ResolvedUnitSource> {
667
+ const sources = await this.libraryBackend.getResolvedUnitSources([instanceType])
668
+ const source = sources.find(source => source.unitType === instanceType)
669
+
670
+ if (!source) {
671
+ throw new Error(`Resolved unit source not found for ${instanceType}`)
672
+ }
673
+
674
+ return source
675
+ }
676
+
669
677
  private static getStackName(options: RunnerBaseOptions) {
670
678
  return `${options.projectId}_${options.instanceName}`
671
679
  }
@@ -1,11 +0,0 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
- export {
9
- __require
10
- };
11
- //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,56 +0,0 @@
1
- // src/library/source-resolution-worker.ts
2
- import { parentPort, workerData } from "node:worker_threads";
3
- import { fileURLToPath } from "node:url";
4
- import { dirname, relative, resolve } from "node:path";
5
- import { readFile } from "node:fs/promises";
6
- import pino from "pino";
7
- import { resolve as importMetaResolve } from "import-meta-resolve";
8
- import { readPackageJSON, resolvePackageJSON } from "pkg-types";
9
- var { requests, logLevel } = workerData;
10
- var logger = pino({ name: "source-resolution-worker", level: logLevel ?? "silent" });
11
- var results = await Promise.all(
12
- requests.map((request) => resolveUnitSourceSafe(request.source, request.unitType))
13
- );
14
- parentPort.postMessage({
15
- type: "result",
16
- results: results.filter((result) => result !== null)
17
- });
18
- async function resolveUnitSourceSafe(source, unitType) {
19
- try {
20
- return await resolveUnitSource(source, unitType);
21
- } catch (error) {
22
- logger.error({ source, unitType, err: error }, "failed to resolve unit source");
23
- return null;
24
- }
25
- }
26
- async function resolveUnitSource(source, unitType) {
27
- const fullPath = source.path ? `${source.package}/${source.path}` : source.package;
28
- const url = importMetaResolve(fullPath, import.meta.url);
29
- const path = fileURLToPath(url);
30
- const projectPath = dirname(path);
31
- const packageJsonPath = await resolvePackageJSON(projectPath);
32
- const packageJson = await readPackageJSON(projectPath);
33
- const manifestPath = resolve(dirname(packageJsonPath), "dist", "highstate.manifest.json");
34
- let manifest;
35
- try {
36
- manifest = JSON.parse(await readFile(manifestPath, "utf8"));
37
- } catch (error) {
38
- logger.debug({ error }, "failed to read highstate manifest");
39
- }
40
- let relativePath = relative(dirname(packageJsonPath), path);
41
- relativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
42
- const sourceHash = manifest?.sourceHashes?.[relativePath];
43
- if (!sourceHash) {
44
- logger.warn({ unitType, relativePath, packageName: packageJson.name }, "source hash not found");
45
- }
46
- const allowedDependencies = Object.keys(packageJson.peerDependencies ?? {});
47
- logger.debug({ packageJson }, "package.json read");
48
- return {
49
- unitType,
50
- projectPath,
51
- packageJsonPath,
52
- sourceHash: sourceHash ?? "",
53
- allowedDependencies
54
- };
55
- }
56
- //# sourceMappingURL=source-resolution-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/library/source-resolution-worker.ts"],"sourcesContent":["import type { UnitSource } from \"@highstate/contract\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname, relative, resolve } from \"node:path\"\nimport { readFile } from \"node:fs/promises\"\nimport pino, { type Level } from \"pino\"\nimport { resolve as importMetaResolve } from \"import-meta-resolve\"\nimport { readPackageJSON, resolvePackageJSON } from \"pkg-types\"\n\nexport type SourceResolutionRequest = {\n unitType: string\n source: UnitSource\n}\n\nexport type SourceResolutionResult = {\n unitType: string\n projectPath: string\n packageJsonPath: string\n sourceHash: string\n allowedDependencies: string[]\n}\n\nexport type HighstateManifestJson = {\n sourceHashes?: Record<string, string>\n}\n\nconst { requests, logLevel } = workerData as {\n requests: SourceResolutionRequest[]\n logLevel?: Level\n}\n\nconst logger = pino({ name: \"source-resolution-worker\", level: logLevel ?? \"silent\" })\n\nconst results = await Promise.all(\n requests.map(request => resolveUnitSourceSafe(request.source, request.unitType)),\n)\n\nparentPort!.postMessage({\n type: \"result\",\n results: results.filter((result): result is SourceResolutionResult => result !== null),\n})\n\nasync function resolveUnitSourceSafe(\n source: UnitSource,\n unitType: string,\n): Promise<SourceResolutionResult | null> {\n try {\n return await resolveUnitSource(source, unitType)\n } catch (error) {\n logger.error({ source, unitType, err: error }, \"failed to resolve unit source\")\n return null\n }\n}\n\nasync function resolveUnitSource(\n source: UnitSource,\n unitType: string,\n): Promise<SourceResolutionResult> {\n const fullPath = source.path ? `${source.package}/${source.path}` : source.package\n\n const url = importMetaResolve(fullPath, import.meta.url)\n const path = fileURLToPath(url)\n const projectPath = dirname(path)\n\n const packageJsonPath = await resolvePackageJSON(projectPath)\n const packageJson = await readPackageJSON(projectPath)\n\n const manifestPath = resolve(dirname(packageJsonPath), \"dist\", \"highstate.manifest.json\")\n let manifest: HighstateManifestJson | undefined\n try {\n manifest = JSON.parse(await readFile(manifestPath, \"utf8\")) as HighstateManifestJson\n } catch (error) {\n logger.debug({ error }, \"failed to read highstate manifest\")\n }\n\n let relativePath = relative(dirname(packageJsonPath), path)\n relativePath = relativePath.startsWith(\".\") ? relativePath : `./${relativePath}`\n\n const sourceHash = manifest?.sourceHashes?.[relativePath]\n if (!sourceHash) {\n logger.warn({ unitType, relativePath, packageName: packageJson.name }, \"source hash not found\")\n }\n\n // only the peer dependencies of the package are allowed to auto-install when they are missing\n const allowedDependencies = Object.keys(packageJson.peerDependencies ?? {})\n\n logger.debug({ packageJson }, \"package.json read\")\n\n return {\n unitType,\n projectPath,\n packageJsonPath,\n sourceHash: sourceHash ?? \"\",\n allowedDependencies,\n }\n}\n"],"mappings":";AACA,SAAS,YAAY,kBAAkB;AACvC,SAAS,qBAAqB;AAC9B,SAAS,SAAS,UAAU,eAAe;AAC3C,SAAS,gBAAgB;AACzB,OAAO,UAA0B;AACjC,SAAS,WAAW,yBAAyB;AAC7C,SAAS,iBAAiB,0BAA0B;AAmBpD,IAAM,EAAE,UAAU,SAAS,IAAI;AAK/B,IAAM,SAAS,KAAK,EAAE,MAAM,4BAA4B,OAAO,YAAY,SAAS,CAAC;AAErF,IAAM,UAAU,MAAM,QAAQ;AAAA,EAC5B,SAAS,IAAI,aAAW,sBAAsB,QAAQ,QAAQ,QAAQ,QAAQ,CAAC;AACjF;AAEA,WAAY,YAAY;AAAA,EACtB,MAAM;AAAA,EACN,SAAS,QAAQ,OAAO,CAAC,WAA6C,WAAW,IAAI;AACvF,CAAC;AAED,eAAe,sBACb,QACA,UACwC;AACxC,MAAI;AACF,WAAO,MAAM,kBAAkB,QAAQ,QAAQ;AAAA,EACjD,SAAS,OAAO;AACd,WAAO,MAAM,EAAE,QAAQ,UAAU,KAAK,MAAM,GAAG,+BAA+B;AAC9E,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBACb,QACA,UACiC;AACjC,QAAM,WAAW,OAAO,OAAO,GAAG,OAAO,OAAO,IAAI,OAAO,IAAI,KAAK,OAAO;AAE3E,QAAM,MAAM,kBAAkB,UAAU,YAAY,GAAG;AACvD,QAAM,OAAO,cAAc,GAAG;AAC9B,QAAM,cAAc,QAAQ,IAAI;AAEhC,QAAM,kBAAkB,MAAM,mBAAmB,WAAW;AAC5D,QAAM,cAAc,MAAM,gBAAgB,WAAW;AAErD,QAAM,eAAe,QAAQ,QAAQ,eAAe,GAAG,QAAQ,yBAAyB;AACxF,MAAI;AACJ,MAAI;AACF,eAAW,KAAK,MAAM,MAAM,SAAS,cAAc,MAAM,CAAC;AAAA,EAC5D,SAAS,OAAO;AACd,WAAO,MAAM,EAAE,MAAM,GAAG,mCAAmC;AAAA,EAC7D;AAEA,MAAI,eAAe,SAAS,QAAQ,eAAe,GAAG,IAAI;AAC1D,iBAAe,aAAa,WAAW,GAAG,IAAI,eAAe,KAAK,YAAY;AAE9E,QAAM,aAAa,UAAU,eAAe,YAAY;AACxD,MAAI,CAAC,YAAY;AACf,WAAO,KAAK,EAAE,UAAU,cAAc,aAAa,YAAY,KAAK,GAAG,uBAAuB;AAAA,EAChG;AAGA,QAAM,sBAAsB,OAAO,KAAK,YAAY,oBAAoB,CAAC,CAAC;AAE1E,SAAO,MAAM,EAAE,YAAY,GAAG,mBAAmB;AAEjD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,cAAc;AAAA,IAC1B;AAAA,EACF;AACF;","names":[]}
@@ -1,96 +0,0 @@
1
- import type { UnitSource } from "@highstate/contract"
2
- import { parentPort, workerData } from "node:worker_threads"
3
- import { fileURLToPath } from "node:url"
4
- import { dirname, relative, resolve } from "node:path"
5
- import { readFile } from "node:fs/promises"
6
- import pino, { type Level } from "pino"
7
- import { resolve as importMetaResolve } from "import-meta-resolve"
8
- import { readPackageJSON, resolvePackageJSON } from "pkg-types"
9
-
10
- export type SourceResolutionRequest = {
11
- unitType: string
12
- source: UnitSource
13
- }
14
-
15
- export type SourceResolutionResult = {
16
- unitType: string
17
- projectPath: string
18
- packageJsonPath: string
19
- sourceHash: string
20
- allowedDependencies: string[]
21
- }
22
-
23
- export type HighstateManifestJson = {
24
- sourceHashes?: Record<string, string>
25
- }
26
-
27
- const { requests, logLevel } = workerData as {
28
- requests: SourceResolutionRequest[]
29
- logLevel?: Level
30
- }
31
-
32
- const logger = pino({ name: "source-resolution-worker", level: logLevel ?? "silent" })
33
-
34
- const results = await Promise.all(
35
- requests.map(request => resolveUnitSourceSafe(request.source, request.unitType)),
36
- )
37
-
38
- parentPort!.postMessage({
39
- type: "result",
40
- results: results.filter((result): result is SourceResolutionResult => result !== null),
41
- })
42
-
43
- async function resolveUnitSourceSafe(
44
- source: UnitSource,
45
- unitType: string,
46
- ): Promise<SourceResolutionResult | null> {
47
- try {
48
- return await resolveUnitSource(source, unitType)
49
- } catch (error) {
50
- logger.error({ source, unitType, err: error }, "failed to resolve unit source")
51
- return null
52
- }
53
- }
54
-
55
- async function resolveUnitSource(
56
- source: UnitSource,
57
- unitType: string,
58
- ): Promise<SourceResolutionResult> {
59
- const fullPath = source.path ? `${source.package}/${source.path}` : source.package
60
-
61
- const url = importMetaResolve(fullPath, import.meta.url)
62
- const path = fileURLToPath(url)
63
- const projectPath = dirname(path)
64
-
65
- const packageJsonPath = await resolvePackageJSON(projectPath)
66
- const packageJson = await readPackageJSON(projectPath)
67
-
68
- const manifestPath = resolve(dirname(packageJsonPath), "dist", "highstate.manifest.json")
69
- let manifest: HighstateManifestJson | undefined
70
- try {
71
- manifest = JSON.parse(await readFile(manifestPath, "utf8")) as HighstateManifestJson
72
- } catch (error) {
73
- logger.debug({ error }, "failed to read highstate manifest")
74
- }
75
-
76
- let relativePath = relative(dirname(packageJsonPath), path)
77
- relativePath = relativePath.startsWith(".") ? relativePath : `./${relativePath}`
78
-
79
- const sourceHash = manifest?.sourceHashes?.[relativePath]
80
- if (!sourceHash) {
81
- logger.warn({ unitType, relativePath, packageName: packageJson.name }, "source hash not found")
82
- }
83
-
84
- // only the peer dependencies of the package are allowed to auto-install when they are missing
85
- const allowedDependencies = Object.keys(packageJson.peerDependencies ?? {})
86
-
87
- logger.debug({ packageJson }, "package.json read")
88
-
89
- return {
90
- unitType,
91
- projectPath,
92
- packageJsonPath,
93
- sourceHash: sourceHash ?? "",
94
- allowedDependencies,
95
- }
96
- }