@highstate/backend 0.9.15 → 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 +6158 -2178
  7. package/dist/index.js.map +1 -1
  8. package/dist/library/worker/main.js +47 -155
  9. package/dist/library/worker/main.js.map +1 -1
  10. package/dist/shared/index.js +159 -41
  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 +9 -42
  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 +129 -82
  52. package/src/orchestrator/operation-workset.ts +168 -77
  53. package/src/orchestrator/operation.ts +967 -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 -371
  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} +28 -7
  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 +28 -0
  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 +3 -2
  99. package/src/shared/resolvers/state.ts +2 -2
  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 +131 -79
  122. package/src/terminal/run.sh.ts +21 -11
  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-KTGKNSKM.js +0 -979
  129. package/dist/chunk-KTGKNSKM.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 -247
  139. package/src/shared/terminal.ts +0 -14
  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
@@ -34,13 +34,25 @@ export function isAbortError(error: unknown): boolean {
34
34
  return error instanceof Error && error.name === "AbortError"
35
35
  }
36
36
 
37
- const abortMessagePatterns = ["Operation aborted", "Command was killed with SIGINT"]
37
+ const abortMessagePatterns = [
38
+ "Operation aborted",
39
+ "This operation was aborted",
40
+ "Command was killed with SIGINT",
41
+ ]
38
42
 
39
43
  export function isAbortErrorLike(error: unknown): boolean {
44
+ if (isAbortError(error)) {
45
+ return true
46
+ }
47
+
40
48
  if (error instanceof Error) {
41
49
  return abortMessagePatterns.some(pattern => error.message.includes(pattern))
42
50
  }
43
51
 
52
+ if (typeof error === "string") {
53
+ return abortMessagePatterns.some(pattern => error.includes(pattern))
54
+ }
55
+
44
56
  return false
45
57
  }
46
58
 
@@ -61,3 +73,30 @@ export function errorToString(error: unknown): string {
61
73
 
62
74
  return JSON.stringify(error)
63
75
  }
76
+
77
+ export function valueToString(value: unknown): string {
78
+ if (typeof value === "string") {
79
+ return value
80
+ }
81
+
82
+ return JSON.stringify(value)
83
+ }
84
+
85
+ export function stringToValue(value: string): unknown {
86
+ try {
87
+ return JSON.parse(value)
88
+ } catch {
89
+ return value
90
+ }
91
+ }
92
+
93
+ export function waitForAbort(signal: AbortSignal): Promise<void> {
94
+ return new Promise(resolve => {
95
+ if (signal.aborted) {
96
+ resolve()
97
+ return
98
+ }
99
+
100
+ signal.addEventListener("abort", () => resolve(), { once: true })
101
+ })
102
+ }
package/src/config.ts CHANGED
@@ -1,28 +1,32 @@
1
1
  import { z } from "zod"
2
2
  import { libraryBackendConfig } from "./library"
3
3
  import { projectBackendConfig } from "./project"
4
- import { secretBackendConfig } from "./secret"
5
4
  import { terminalBackendConfig } from "./terminal"
6
5
  import { runnerBackendConfig } from "./runner"
7
- import { stateBackendConfig } from "./state"
8
- import { workspaceBackendConfig } from "./workspace"
6
+ import { stateBackendConfig, stateManagerConfig } from "./state"
7
+ import { artifactBackendConfig } from "./artifact"
8
+ import { pubSubBackendConfig } from "./pubsub"
9
+ import { lockBackendConfig } from "./lock"
10
+ import { hotStateBackendConfig } from "./hotstate"
11
+ import { workerBackendConfig, workerManagerConfig } from "./worker"
9
12
 
10
13
  const loggerConfig = z.object({
11
- HIGHSTATE_BACKEND_LOGGER_NAME: z.string().default("highstate-backend"),
12
-
13
- HIGHSTATE_BACKEND_LOGGER_LEVEL: z
14
- .enum(["fatal", "error", "warn", "info", "debug", "trace"])
15
- .default("info"),
14
+ HIGHSTATE_LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
16
15
  })
17
16
 
18
17
  const configSchema = z.object({
18
+ ...hotStateBackendConfig.shape,
19
+ ...pubSubBackendConfig.shape,
20
+ ...lockBackendConfig.shape,
19
21
  ...libraryBackendConfig.shape,
20
22
  ...projectBackendConfig.shape,
21
- ...secretBackendConfig.shape,
22
23
  ...stateBackendConfig.shape,
24
+ ...stateManagerConfig.shape,
23
25
  ...runnerBackendConfig.shape,
24
26
  ...terminalBackendConfig.shape,
25
- ...workspaceBackendConfig.shape,
27
+ ...workerBackendConfig.shape,
28
+ ...workerManagerConfig.shape,
29
+ ...artifactBackendConfig.shape,
26
30
  ...loggerConfig.shape,
27
31
  })
28
32
 
@@ -0,0 +1,48 @@
1
+ import type { z } from "zod"
2
+
3
+ export interface HotStateBackend {
4
+ /**
5
+ * Gets a value for a key.
6
+ */
7
+ get(key: string, schema: z.ZodType): Promise<unknown>
8
+
9
+ /**
10
+ * Checks if a key exists.
11
+ */
12
+ exists(key: string): Promise<boolean>
13
+
14
+ /**
15
+ * Sets a value for a key.
16
+ */
17
+ set(key: string, schema: z.ZodType, value: unknown): Promise<void>
18
+
19
+ /**
20
+ * Deletes a key.
21
+ */
22
+ delete(key: string): Promise<void>
23
+
24
+ /**
25
+ * Gets all key-value pairs from a hash set.
26
+ */
27
+ hgetall(key: string, schema: z.ZodType): Promise<Array<[string, unknown]>>
28
+
29
+ /**
30
+ * Checks if a field exists in a hash set.
31
+ */
32
+ hexists(key: string, field: string): Promise<boolean>
33
+
34
+ /**
35
+ * Sets a field in a hash set.
36
+ */
37
+ hset(key: string, field: string, schema: z.ZodType, value: unknown): Promise<void>
38
+
39
+ /**
40
+ * Gets a field from a hash set.
41
+ */
42
+ hget(key: string, field: string, schema: z.ZodType): Promise<unknown>
43
+
44
+ /**
45
+ * Deletes a field from a hash set.
46
+ */
47
+ hdel(key: string, field: string): Promise<void>
48
+ }
@@ -0,0 +1,17 @@
1
+ import type { HotStateBackend } from "./abstractions"
2
+ import { z } from "zod"
3
+ import { MemoryHotStateBackend } from "./memory"
4
+
5
+ export const hotStateBackendConfig = z.object({
6
+ HIGHSTATE_HOTSTATE_BACKEND_TYPE: z.enum(["memory"]).default("memory"),
7
+ })
8
+
9
+ export function createHotStateBackend(
10
+ config: z.infer<typeof hotStateBackendConfig>,
11
+ ): HotStateBackend {
12
+ switch (config.HIGHSTATE_HOTSTATE_BACKEND_TYPE) {
13
+ case "memory": {
14
+ return MemoryHotStateBackend.create()
15
+ }
16
+ }
17
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./abstractions"
2
2
  export * from "./factory"
3
+ export * from "./manager"
@@ -0,0 +1,192 @@
1
+ import type { HotStateBackend } from "./abstractions"
2
+ import { z } from "zod"
3
+ import { instanceLockSchema, instanceStateSchema, operationSchema } from "../shared"
4
+
5
+ export type DataMap = {
6
+ /**
7
+ * Master key for a project used to read and write encrypted data.
8
+ *
9
+ * If the key is not set, the project is considered locked.
10
+ */
11
+ "project-master-key": {
12
+ key: [projectId: string]
13
+ data: z.ZodType<Uint8Array>
14
+ }
15
+ }
16
+
17
+ const dataSchemas: Record<string, z.ZodType> = {
18
+ "project-master-key": z.instanceof(Uint8Array),
19
+ }
20
+
21
+ export type DataHashSetMap = {
22
+ "active-operations": {
23
+ key: [projectId: string]
24
+ data: typeof operationSchema
25
+ }
26
+ "instance-states": {
27
+ key: [projectId: string]
28
+ data: typeof instanceStateSchema
29
+ }
30
+ "instance-locks": {
31
+ key: [projectId: string]
32
+ data: typeof instanceLockSchema
33
+ }
34
+ "operation-logs": {
35
+ key: [operationId: string]
36
+ data: z.ZodString
37
+ }
38
+ }
39
+
40
+ const hashSetSchemas: Record<string, z.ZodType> = {
41
+ "active-operations": operationSchema,
42
+ "instance-states": instanceStateSchema,
43
+ "instance-locks": instanceLockSchema,
44
+ "operation-logs": z.string(),
45
+ }
46
+
47
+ type GenericKey =
48
+ | {
49
+ [K in keyof DataMap]: [type: K, ...DataMap[K]["key"]]
50
+ }[keyof DataMap]
51
+ | {
52
+ [K in keyof DataHashSetMap]: [type: K, ...DataHashSetMap[K]["key"]]
53
+ }[keyof DataHashSetMap]
54
+
55
+ export class HotStateManager {
56
+ constructor(private readonly hotStateBackend: HotStateBackend) {}
57
+
58
+ /**
59
+ * Gets a value for a key.
60
+ *
61
+ * @param key The key to get the value for.
62
+ * @param schema The Zod schema to validate the value.
63
+ * @returns The value or null if not found.
64
+ */
65
+ public async get<K extends keyof DataMap>(
66
+ key: [type: K, ...DataMap[K]["key"]],
67
+ ): Promise<z.infer<DataMap[K]["data"]> | null> {
68
+ const result = await this.hotStateBackend.get(key.join(":"), dataSchemas[key[0]])
69
+
70
+ return result as z.infer<DataMap[K]["data"]> | null
71
+ }
72
+
73
+ /**
74
+ * Checks if a key exists.
75
+ *
76
+ * @param key The key to check.
77
+ * @returns True if the key exists, false otherwise.
78
+ */
79
+ public async exists(key: GenericKey): Promise<boolean> {
80
+ return await this.hotStateBackend.exists(key.join(":"))
81
+ }
82
+
83
+ /**
84
+ * Sets a value for a key.
85
+ *
86
+ * @param key The key to set the value for.
87
+ * @param value The value to set.
88
+ */
89
+ public async set<K extends keyof DataMap>(
90
+ key: [type: K, ...DataMap[K]["key"]],
91
+ value: z.infer<DataMap[K]["data"]>,
92
+ ): Promise<void> {
93
+ return await this.hotStateBackend.set(key.join(":"), dataSchemas[key[0]], value)
94
+ }
95
+
96
+ /**
97
+ * Deletes a key.
98
+ *
99
+ * @param key The key to delete.
100
+ */
101
+ public async delete(key: GenericKey): Promise<void> {
102
+ return await this.hotStateBackend.delete(key.join(":"))
103
+ }
104
+
105
+ /**
106
+ * Gets all key-value pairs from a hash set.
107
+ *
108
+ * @param key The hash set key.
109
+ * @returns All key-value pairs in the hash set.
110
+ */
111
+ public async hgetall<K extends keyof DataHashSetMap>(
112
+ key: [type: K, ...DataHashSetMap[K]["key"]],
113
+ ): Promise<[string, z.infer<DataHashSetMap[K]["data"]>][]> {
114
+ const result = await this.hotStateBackend.hgetall(key.join(":"), hashSetSchemas[key[0]])
115
+
116
+ return result as [string, z.infer<DataHashSetMap[K]["data"]>][]
117
+ }
118
+
119
+ /**
120
+ * Checks if a field exists in a hash set.
121
+ *
122
+ * @param key The hash set key.
123
+ * @param field The field name.
124
+ * @returns True if the field exists, false otherwise.
125
+ */
126
+ public async hexists<K extends keyof DataHashSetMap>(
127
+ key: [type: K, ...DataHashSetMap[K]["key"]],
128
+ field: string,
129
+ ): Promise<boolean> {
130
+ return await this.hotStateBackend.hexists(key.join(":"), field)
131
+ }
132
+
133
+ /**
134
+ * Sets a field in a hash set.
135
+ *
136
+ * @param key The hash set key.
137
+ * @param field The field name.
138
+ * @param value The value to set.
139
+ */
140
+ public async hset<K extends keyof DataHashSetMap>(
141
+ key: [type: K, ...DataHashSetMap[K]["key"]],
142
+ field: string,
143
+ value: z.infer<DataHashSetMap[K]["data"]>,
144
+ ): Promise<void> {
145
+ return await this.hotStateBackend.hset(key.join(":"), field, hashSetSchemas[key[0]], value)
146
+ }
147
+
148
+ /**
149
+ * Sets multiple fields in a hash set.
150
+ *
151
+ * @param key The hash set key.
152
+ * @param entries An array of field-value pairs to set.
153
+ */
154
+ public async hmset<K extends keyof DataHashSetMap>(
155
+ key: [type: K, ...DataHashSetMap[K]["key"]],
156
+ entries: Array<[string, z.infer<DataHashSetMap[K]["data"]>]>,
157
+ ): Promise<void> {
158
+ const promises = entries.map(async ([field, value]) => this.hset(key, field, value))
159
+
160
+ await Promise.allSettled(promises)
161
+ }
162
+
163
+ /**
164
+ * Gets a field from a hash set.
165
+ *
166
+ * @param key The hash set key.
167
+ * @param field The field name.
168
+ * @returns The value or null if not found.
169
+ */
170
+ public async hget<K extends keyof DataHashSetMap>(
171
+ key: [type: K, ...DataHashSetMap[K]["key"]],
172
+ field: string,
173
+ ): Promise<z.infer<DataHashSetMap[K]["data"]> | null> {
174
+ const result = await this.hotStateBackend.hget(key.join(":"), field, hashSetSchemas[key[0]])
175
+
176
+ return result as z.infer<DataHashSetMap[K]["data"]> | null
177
+ }
178
+
179
+ /**
180
+ * Deletes a field from a hash set.
181
+ *
182
+ * @param key The hash set key.
183
+ * @param field The field name.
184
+ * @returns A promise that resolves when the field is deleted.
185
+ */
186
+ public async hdel<K extends keyof DataHashSetMap>(
187
+ key: [type: K, ...DataHashSetMap[K]["key"]],
188
+ field: string,
189
+ ): Promise<void> {
190
+ return await this.hotStateBackend.hdel(key.join(":"), field)
191
+ }
192
+ }
@@ -0,0 +1,100 @@
1
+ import type { z } from "zod"
2
+ import type { HotStateBackend } from "./abstractions"
3
+
4
+ export class MemoryHotStateBackend implements HotStateBackend {
5
+ private readonly storage = new Map<string, unknown>()
6
+
7
+ get<TSchema extends z.ZodSchema>(key: string): Promise<z.infer<TSchema> | null> {
8
+ const value = this.storage.get(key)
9
+ if (value === undefined || value instanceof Map) {
10
+ return Promise.resolve(null)
11
+ }
12
+ return Promise.resolve(value as z.infer<TSchema>)
13
+ }
14
+
15
+ exists(key: string): Promise<boolean> {
16
+ const exists = this.storage.has(key) && !(this.storage.get(key) instanceof Map)
17
+ return Promise.resolve(exists)
18
+ }
19
+
20
+ set<TSchema extends z.ZodSchema>(
21
+ key: string,
22
+ _schema: TSchema,
23
+ value: z.infer<TSchema>,
24
+ ): Promise<void> {
25
+ this.storage.set(key, value)
26
+ return Promise.resolve()
27
+ }
28
+
29
+ delete(key: string): Promise<void> {
30
+ this.storage.delete(key)
31
+ return Promise.resolve()
32
+ }
33
+
34
+ hgetall<TSchema extends z.ZodSchema>(key: string): Promise<Array<[string, z.infer<TSchema>]>> {
35
+ const value = this.storage.get(key)
36
+ if (!(value instanceof Map)) {
37
+ return Promise.resolve([])
38
+ }
39
+
40
+ const result: Array<[string, z.infer<TSchema>]> = []
41
+ for (const [field, fieldValue] of value.entries()) {
42
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
43
+ result.push([field, fieldValue as z.infer<TSchema>])
44
+ }
45
+ return Promise.resolve(result)
46
+ }
47
+
48
+ hexists(key: string, field: string): Promise<boolean> {
49
+ const hashSet = this.storage.get(key)
50
+ if (!(hashSet instanceof Map)) {
51
+ return Promise.resolve(false)
52
+ }
53
+
54
+ const exists = hashSet.has(field)
55
+ return Promise.resolve(exists)
56
+ }
57
+
58
+ hset<TSchema extends z.ZodSchema>(
59
+ key: string,
60
+ field: string,
61
+ _schema: TSchema,
62
+ value: z.infer<TSchema>,
63
+ ): Promise<void> {
64
+ let hashSet = this.storage.get(key)
65
+ if (!(hashSet instanceof Map)) {
66
+ hashSet = new Map<string, unknown>()
67
+ this.storage.set(key, hashSet)
68
+ }
69
+
70
+ ;(hashSet as Map<string, unknown>).set(field, value)
71
+ return Promise.resolve()
72
+ }
73
+
74
+ hget<TSchema extends z.ZodSchema>(key: string, field: string): Promise<z.infer<TSchema> | null> {
75
+ const hashSet = this.storage.get(key)
76
+ if (!(hashSet instanceof Map)) {
77
+ return Promise.resolve(null)
78
+ }
79
+
80
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
81
+ const value = hashSet.get(field)
82
+ return Promise.resolve(value === undefined ? null : (value as z.infer<TSchema>))
83
+ }
84
+
85
+ hdel(key: string, field: string): Promise<void> {
86
+ const hashSet = this.storage.get(key)
87
+ if (hashSet instanceof Map) {
88
+ hashSet.delete(field)
89
+ // If the hash set becomes empty, remove it from storage
90
+ if (hashSet.size === 0) {
91
+ this.storage.delete(key)
92
+ }
93
+ }
94
+ return Promise.resolve()
95
+ }
96
+
97
+ static create(): HotStateBackend {
98
+ return new MemoryHotStateBackend()
99
+ }
100
+ }
@@ -0,0 +1,101 @@
1
+ import type { Logger } from "pino"
2
+ import type { HotStateBackend } from "./abstractions"
3
+ import type { z } from "zod"
4
+
5
+ export class HotStateValidationDecorator implements HotStateBackend {
6
+ constructor(
7
+ private readonly hotstateBackend: HotStateBackend,
8
+ private readonly logger: Logger,
9
+ ) {}
10
+
11
+ async get(key: string, schema: z.ZodType): Promise<unknown> {
12
+ const value = await this.hotstateBackend.get(key, schema)
13
+
14
+ if (value === null) {
15
+ return null
16
+ }
17
+
18
+ try {
19
+ return schema.parse(value)
20
+ } catch (error) {
21
+ this.logger.error({ key, error }, `invalid value for key "${key}", returning null`)
22
+ return null
23
+ }
24
+ }
25
+
26
+ async exists(key: string): Promise<boolean> {
27
+ return this.hotstateBackend.exists(key)
28
+ }
29
+
30
+ async set(key: string, schema: z.ZodType, value: unknown): Promise<void> {
31
+ try {
32
+ schema.parse(value)
33
+ } catch (error) {
34
+ throw new Error(`Failed to validate value for key "${key}"`, { cause: error })
35
+ }
36
+
37
+ await this.hotstateBackend.set(key, schema, value)
38
+ }
39
+
40
+ async delete(key: string): Promise<void> {
41
+ return this.hotstateBackend.delete(key)
42
+ }
43
+
44
+ async hgetall(key: string, schema: z.ZodType): Promise<Array<[string, unknown]>> {
45
+ const entries = await this.hotstateBackend.hgetall(key, schema)
46
+ const validEntries: Array<[string, unknown]> = []
47
+
48
+ for (const [field, value] of entries) {
49
+ try {
50
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
51
+ const validValue = schema.parse(value)
52
+ validEntries.push([field, validValue])
53
+ } catch (error) {
54
+ this.logger.error(
55
+ { key, field, error },
56
+ `skipped invalid value for field "${field}" in hash "${key}"`,
57
+ )
58
+ }
59
+ }
60
+
61
+ return validEntries
62
+ }
63
+
64
+ async hexists(key: string, field: string): Promise<boolean> {
65
+ return this.hotstateBackend.hexists(key, field)
66
+ }
67
+
68
+ async hset(key: string, field: string, schema: z.ZodType, value: unknown): Promise<void> {
69
+ try {
70
+ schema.parse(value)
71
+ } catch (error) {
72
+ throw new Error(`Failed to validate value for field "${field}" in hash "${key}"`, {
73
+ cause: error,
74
+ })
75
+ }
76
+
77
+ await this.hotstateBackend.hset(key, field, schema, value)
78
+ }
79
+
80
+ async hget(key: string, field: string, schema: z.ZodType): Promise<unknown> {
81
+ const value = await this.hotstateBackend.hget(key, field, schema)
82
+
83
+ if (value === null) {
84
+ return null
85
+ }
86
+
87
+ try {
88
+ return schema.parse(value)
89
+ } catch (error) {
90
+ this.logger.error(
91
+ { key, field, error },
92
+ `invalid value for field "${field}" in hash "${key}", returning null`,
93
+ )
94
+ return null
95
+ }
96
+ }
97
+
98
+ async hdel(key: string, field: string): Promise<void> {
99
+ return this.hotstateBackend.hdel(key, field)
100
+ }
101
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
- export * from "./secret"
2
1
  export * from "./library"
3
2
  export * from "./config"
4
3
  export * from "./orchestrator"
5
4
  export * from "./terminal"
6
5
  export * from "./services"
6
+ export * from "./state"
7
+ export * from "./business"
@@ -3,22 +3,11 @@ import type { LibraryModel, LibraryUpdate, ResolvedInstanceInput } from "../shar
3
3
 
4
4
  export type ResolvedUnitSource = {
5
5
  unitType: string
6
- sourceHash: string
6
+ sourceHash: number
7
7
  projectPath: string
8
8
  allowedDependencies: string[]
9
9
  }
10
10
 
11
- export type ModuleEvaluationResult =
12
- | {
13
- success: true
14
- compositeInstances: CompositeInstance[]
15
- }
16
- | {
17
- success: false
18
- modulePath: string
19
- error: string
20
- }
21
-
22
11
  export type InstanceEvaluationResult =
23
12
  | {
24
13
  success: true
@@ -35,44 +24,42 @@ export interface LibraryBackend {
35
24
  /**
36
25
  * Loads the library.
37
26
  */
38
- loadLibrary(signal?: AbortSignal): Promise<LibraryModel>
27
+ loadLibrary(libraryId: string, signal?: AbortSignal): Promise<LibraryModel>
39
28
 
40
29
  /**
41
30
  * Watches the library for changes.
42
31
  */
43
- watchLibrary(signal?: AbortSignal): AsyncIterable<LibraryUpdate[]>
32
+ watchLibrary(libraryId: string, signal?: AbortSignal): AsyncIterable<LibraryUpdate[]>
44
33
 
45
34
  /**
46
35
  * Gets the resolved unit sources for the given unit types.
47
36
  *
48
37
  * If the packages for these units are not resolved, it will resolve them and include in watch list.
49
38
  */
50
- getResolvedUnitSources(unitTypes: string[]): Promise<ResolvedUnitSource[]>
39
+ getResolvedUnitSources(libraryId: string, unitTypes: string[]): Promise<ResolvedUnitSource[]>
51
40
 
52
41
  /**
53
42
  * Watches the resolved unit sources for changes.
54
43
  * Returns an async iterable that emits each resolved unit source whenever it changes.
55
44
  * Does not emit the resolved unit sources for units that have not changed even if the library was reloaded.
56
45
  */
57
- watchResolvedUnitSources(signal?: AbortSignal): AsyncIterable<ResolvedUnitSource>
46
+ watchResolvedUnitSources(
47
+ libraryId: string,
48
+ signal?: AbortSignal,
49
+ ): AsyncIterable<ResolvedUnitSource>
58
50
 
59
51
  /**
60
52
  * Evaluates the instances and returns the evaluated composite instances.
61
53
  *
54
+ * @param libraryId The library ID to use for evaluation.
62
55
  * @param allInstances The all instances of the project.
63
56
  * @param resolvedInputs The resolved inputs of the instances.
64
57
  * @param instanceIds The instance ids to evaluate.
65
58
  */
66
59
  evaluateCompositeInstances(
60
+ libraryId: string,
67
61
  allInstances: InstanceModel[],
68
62
  resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,
69
63
  instanceIds: string[],
70
64
  ): Promise<InstanceEvaluationResult[]>
71
-
72
- /**
73
- * Evaluates the modules and returns the evaluated instances.
74
- *
75
- * @param modulePaths The module paths to evaluate.
76
- */
77
- evaluateModules(modulePaths: string[]): Promise<ModuleEvaluationResult>
78
65
  }
@@ -4,7 +4,7 @@ import { z } from "zod"
4
4
  import { LocalLibraryBackend, localLibraryBackendConfig } from "./local"
5
5
 
6
6
  export const libraryBackendConfig = z.object({
7
- HIGHSTATE_BACKEND_LIBRARY_TYPE: z.enum(["local"]).default("local"),
7
+ HIGHSTATE_LIBRARY_BACKEND_TYPE: z.enum(["local"]).default("local"),
8
8
  ...localLibraryBackendConfig.shape,
9
9
  })
10
10
 
@@ -12,7 +12,7 @@ export async function createLibraryBackend(
12
12
  config: z.infer<typeof libraryBackendConfig>,
13
13
  logger: Logger,
14
14
  ): Promise<LibraryBackend> {
15
- switch (config.HIGHSTATE_BACKEND_LIBRARY_TYPE) {
15
+ switch (config.HIGHSTATE_LIBRARY_BACKEND_TYPE) {
16
16
  case "local": {
17
17
  return await LocalLibraryBackend.create(config, logger)
18
18
  }