@highstate/backend 0.9.15 → 0.9.18

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 (187) hide show
  1. package/dist/chunk-NAAIDR4U.js +8499 -0
  2. package/dist/chunk-NAAIDR4U.js.map +1 -0
  3. package/dist/chunk-OU5OQBLB.js +74 -0
  4. package/dist/chunk-OU5OQBLB.js.map +1 -0
  5. package/dist/chunk-Y7DXREVO.js +1745 -0
  6. package/dist/chunk-Y7DXREVO.js.map +1 -0
  7. package/dist/highstate.manifest.json +4 -4
  8. package/dist/index.js +7227 -2501
  9. package/dist/index.js.map +1 -1
  10. package/dist/library/package-resolution-worker.js +7 -5
  11. package/dist/library/package-resolution-worker.js.map +1 -1
  12. package/dist/library/worker/main.js +76 -185
  13. package/dist/library/worker/main.js.map +1 -1
  14. package/dist/magic-string.es-5ABAC4JN.js +1292 -0
  15. package/dist/magic-string.es-5ABAC4JN.js.map +1 -0
  16. package/dist/shared/index.js +3 -98
  17. package/dist/shared/index.js.map +1 -1
  18. package/package.json +31 -10
  19. package/src/artifact/abstractions.ts +46 -0
  20. package/src/artifact/encryption.ts +109 -0
  21. package/src/artifact/factory.ts +36 -0
  22. package/src/artifact/index.ts +3 -0
  23. package/src/artifact/local.ts +138 -0
  24. package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +356 -0
  25. package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +274 -0
  26. package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +223 -0
  27. package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +147 -0
  28. package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +280 -0
  29. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +360 -0
  30. package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +215 -0
  31. package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +427 -0
  32. package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +217 -0
  33. package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +132 -0
  34. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +454 -0
  35. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +426 -0
  36. package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +372 -0
  37. package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +383 -0
  38. package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +245 -0
  39. package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +174 -0
  40. package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +432 -0
  41. package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +220 -0
  42. package/src/business/api-key.ts +65 -0
  43. package/src/business/artifact.ts +289 -0
  44. package/src/business/backend-unlock.ts +10 -0
  45. package/src/business/index.ts +10 -0
  46. package/src/business/instance-lock.ts +125 -0
  47. package/src/business/instance-state.ts +434 -0
  48. package/src/business/operation.ts +251 -0
  49. package/src/business/project-unlock.ts +260 -0
  50. package/src/business/project.ts +299 -0
  51. package/src/business/secret.test.ts +178 -0
  52. package/src/business/secret.ts +281 -0
  53. package/src/business/worker.test.ts +614 -0
  54. package/src/business/worker.ts +398 -0
  55. package/src/common/clock.ts +18 -0
  56. package/src/common/index.ts +5 -1
  57. package/src/common/performance.ts +44 -0
  58. package/src/common/random.ts +68 -0
  59. package/src/common/test/index.ts +2 -0
  60. package/src/common/test/render.ts +98 -0
  61. package/src/common/test/tracer.ts +359 -0
  62. package/src/common/tree.ts +33 -0
  63. package/src/common/utils.ts +40 -1
  64. package/src/config.ts +19 -11
  65. package/src/hotstate/abstractions.ts +48 -0
  66. package/src/hotstate/factory.ts +17 -0
  67. package/src/{secret → hotstate}/index.ts +1 -0
  68. package/src/hotstate/manager.ts +192 -0
  69. package/src/hotstate/memory.ts +100 -0
  70. package/src/hotstate/validation.ts +100 -0
  71. package/src/index.ts +2 -1
  72. package/src/library/abstractions.ts +24 -28
  73. package/src/library/factory.ts +2 -2
  74. package/src/library/local.ts +91 -111
  75. package/src/library/worker/evaluator.ts +36 -73
  76. package/src/library/worker/loader.lite.ts +54 -0
  77. package/src/library/worker/main.ts +15 -66
  78. package/src/library/worker/protocol.ts +6 -33
  79. package/src/lock/abstractions.ts +6 -0
  80. package/src/lock/factory.ts +15 -0
  81. package/src/lock/index.ts +4 -0
  82. package/src/lock/manager.ts +97 -0
  83. package/src/lock/memory.ts +19 -0
  84. package/src/lock/test.ts +108 -0
  85. package/src/orchestrator/manager.ts +118 -90
  86. package/src/orchestrator/operation-workset.ts +181 -93
  87. package/src/orchestrator/operation.ts +1021 -283
  88. package/src/project/abstractions.ts +27 -38
  89. package/src/project/evaluation.ts +248 -0
  90. package/src/project/factory.ts +1 -1
  91. package/src/project/index.ts +1 -2
  92. package/src/project/local.ts +107 -103
  93. package/src/pubsub/abstractions.ts +13 -0
  94. package/src/pubsub/factory.ts +19 -0
  95. package/src/{workspace → pubsub}/index.ts +1 -0
  96. package/src/pubsub/local.ts +36 -0
  97. package/src/pubsub/manager.ts +108 -0
  98. package/src/pubsub/validation.ts +33 -0
  99. package/src/runner/abstractions.ts +155 -68
  100. package/src/runner/artifact-env.ts +160 -0
  101. package/src/runner/factory.ts +20 -5
  102. package/src/runner/force-abort.ts +117 -0
  103. package/src/runner/local.ts +292 -372
  104. package/src/{common → runner}/pulumi.ts +89 -37
  105. package/src/services.ts +251 -40
  106. package/src/shared/index.ts +3 -11
  107. package/src/shared/models/backend/index.ts +3 -0
  108. package/src/shared/{library.ts → models/backend/library.ts} +4 -4
  109. package/src/shared/models/backend/project.ts +82 -0
  110. package/src/shared/models/backend/unlock-method.ts +20 -0
  111. package/src/shared/models/base.ts +68 -0
  112. package/src/shared/models/errors.ts +5 -0
  113. package/src/shared/models/index.ts +4 -0
  114. package/src/shared/models/project/api-key.ts +65 -0
  115. package/src/shared/models/project/artifact.ts +83 -0
  116. package/src/shared/models/project/index.ts +13 -0
  117. package/src/shared/models/project/lock.ts +91 -0
  118. package/src/shared/models/project/model.ts +14 -0
  119. package/src/shared/{operation.ts → models/project/operation.ts} +29 -8
  120. package/src/shared/models/project/page.ts +57 -0
  121. package/src/shared/models/project/secret.ts +98 -0
  122. package/src/shared/models/project/service-account.ts +22 -0
  123. package/src/shared/models/project/state.ts +449 -0
  124. package/src/shared/models/project/terminal.ts +98 -0
  125. package/src/shared/models/project/trigger.ts +56 -0
  126. package/src/shared/models/project/unlock-method.ts +38 -0
  127. package/src/shared/models/project/worker.ts +107 -0
  128. package/src/shared/resolvers/graph-resolver.ts +61 -18
  129. package/src/shared/resolvers/index.ts +5 -0
  130. package/src/shared/resolvers/input-hash.ts +53 -15
  131. package/src/shared/resolvers/input.ts +47 -13
  132. package/src/shared/resolvers/registry.ts +3 -2
  133. package/src/shared/resolvers/state.ts +2 -2
  134. package/src/shared/resolvers/validation.ts +82 -25
  135. package/src/shared/utils/args.ts +25 -0
  136. package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
  137. package/src/shared/utils/hash.ts +6 -0
  138. package/src/shared/utils/index.ts +4 -0
  139. package/src/shared/utils/promise-tracker.ts +23 -0
  140. package/src/state/abstractions.ts +199 -131
  141. package/src/state/encryption.ts +98 -0
  142. package/src/state/factory.ts +3 -5
  143. package/src/state/index.ts +4 -0
  144. package/src/state/keyring.ts +22 -0
  145. package/src/state/local/backend.ts +106 -0
  146. package/src/state/local/collection.ts +361 -0
  147. package/src/state/local/index.ts +2 -0
  148. package/src/state/manager.ts +875 -18
  149. package/src/state/memory/backend.ts +70 -0
  150. package/src/state/memory/collection.ts +270 -0
  151. package/src/state/memory/index.ts +2 -0
  152. package/src/state/repository/index.ts +2 -0
  153. package/src/state/repository/repository.index.ts +193 -0
  154. package/src/state/repository/repository.ts +507 -0
  155. package/src/state/test.ts +457 -0
  156. package/src/terminal/{shared.ts → abstractions.ts} +3 -3
  157. package/src/terminal/docker.ts +18 -14
  158. package/src/terminal/factory.ts +3 -3
  159. package/src/terminal/index.ts +1 -1
  160. package/src/terminal/manager.ts +131 -79
  161. package/src/terminal/run.sh.ts +21 -11
  162. package/src/unlock/abstractions.ts +49 -0
  163. package/src/unlock/index.ts +2 -0
  164. package/src/unlock/memory.ts +32 -0
  165. package/src/worker/abstractions.ts +42 -0
  166. package/src/worker/docker.ts +83 -0
  167. package/src/worker/factory.ts +20 -0
  168. package/src/worker/index.ts +3 -0
  169. package/src/worker/manager.ts +167 -0
  170. package/dist/chunk-KTGKNSKM.js +0 -979
  171. package/dist/chunk-KTGKNSKM.js.map +0 -1
  172. package/dist/chunk-WXDYCRTT.js +0 -234
  173. package/dist/chunk-WXDYCRTT.js.map +0 -1
  174. package/src/library/worker/loader.ts +0 -114
  175. package/src/preferences/shared.ts +0 -1
  176. package/src/project/lock.ts +0 -39
  177. package/src/project/manager.ts +0 -433
  178. package/src/secret/abstractions.ts +0 -59
  179. package/src/secret/factory.ts +0 -22
  180. package/src/secret/local.ts +0 -152
  181. package/src/shared/project.ts +0 -62
  182. package/src/shared/state.ts +0 -247
  183. package/src/shared/terminal.ts +0 -14
  184. package/src/state/local.ts +0 -612
  185. package/src/workspace/abstractions.ts +0 -41
  186. package/src/workspace/factory.ts +0 -14
  187. package/src/workspace/local.ts +0 -54
@@ -0,0 +1,359 @@
1
+ import { PassThrough } from "stream"
2
+ import { basename } from "node:path"
3
+ import pino, { levels } from "pino"
4
+ import { test as baseTest, type RunnerTask } from "vitest"
5
+ import { isPromise, omit } from "remeda"
6
+ import * as md from "ts-markdown-builder"
7
+ import { ReproducibleClockProvider, type ClockProvider } from "../clock"
8
+ import { ReproducibleRandomProvider, type RandomProvider } from "../random"
9
+ import { renderTraceEntry } from "./render"
10
+
11
+ export interface TraceEntry {
12
+ readonly id: number
13
+ title?: string
14
+ render(): string
15
+ }
16
+
17
+ export function linkTraceEntry(entry: TraceEntry): string {
18
+ const anchorId = `#trace-${entry.id}`
19
+
20
+ if (entry.title) {
21
+ return md.link(anchorId, `${entry.id}. ${entry.title}`)
22
+ }
23
+
24
+ return md.link(anchorId, entry.id.toString())
25
+ }
26
+
27
+ export type TestPhase = "arrange" | "act" | "assert"
28
+
29
+ class CallTraceEntry implements TraceEntry {
30
+ resolvedEntry?: TraceEntry
31
+ result?: unknown
32
+ error?: unknown
33
+
34
+ constructor(
35
+ readonly id: number,
36
+ private readonly serviceName: string,
37
+ private readonly methodName: string,
38
+ private readonly args: unknown[],
39
+ result?: unknown,
40
+ error?: unknown,
41
+ ) {
42
+ this.result = result
43
+ this.error = error
44
+ }
45
+
46
+ get title(): string {
47
+ return `${this.serviceName}.${this.methodName}`
48
+ }
49
+
50
+ render(): string {
51
+ if (this.resolvedEntry) {
52
+ // deferred completion - show promise link with ➡️
53
+ return renderTraceEntry({
54
+ icon: "➡️",
55
+ title: this.title,
56
+ fields: {
57
+ args: {
58
+ value: this.args,
59
+ alwaysRender: true,
60
+ },
61
+ result: {
62
+ value: `${md.code("Promise")}, resolved at ${linkTraceEntry(this.resolvedEntry)}`,
63
+ raw: true,
64
+ },
65
+ },
66
+ })
67
+ }
68
+
69
+ return renderTraceEntry({
70
+ icon: "✅",
71
+ title: this.title,
72
+ fields: {
73
+ args: {
74
+ value: this.args,
75
+ alwaysRender: true,
76
+ },
77
+ result: {
78
+ value: this.result,
79
+ },
80
+ error: {
81
+ value: this.error,
82
+ },
83
+ },
84
+ })
85
+ }
86
+ }
87
+
88
+ class CallResultEntry implements TraceEntry {
89
+ constructor(
90
+ readonly id: number,
91
+ private readonly callEntry: TraceEntry,
92
+ private readonly result?: unknown,
93
+ private readonly error?: unknown,
94
+ ) {}
95
+
96
+ render(): string {
97
+ return renderTraceEntry({
98
+ icon: "↩️",
99
+ title: linkTraceEntry(this.callEntry),
100
+ fields: {
101
+ result: {
102
+ value: this.result,
103
+ },
104
+ error: {
105
+ value: this.error,
106
+ },
107
+ },
108
+ })
109
+ }
110
+ }
111
+
112
+ const logLevelIcons: Record<string, string> = {
113
+ error: "❌",
114
+ warn: "⚠️",
115
+ info: "ℹ️",
116
+ debug: "🔍",
117
+ trace: "🔬",
118
+ }
119
+
120
+ class LogTraceEntry implements TraceEntry {
121
+ constructor(
122
+ readonly id: number,
123
+ private readonly level: string,
124
+ private readonly message: string,
125
+ private readonly data?: Record<string, unknown>,
126
+ private readonly error?: unknown,
127
+ ) {}
128
+
129
+ render(): string {
130
+ return renderTraceEntry({
131
+ icon: logLevelIcons[this.level] ?? "ℹ️",
132
+ title: this.level,
133
+ code: this.message,
134
+ fields: {
135
+ data: {
136
+ value: this.data,
137
+ },
138
+ error: {
139
+ value: this.error,
140
+ },
141
+ },
142
+ })
143
+ }
144
+ }
145
+
146
+ export class TestTracer {
147
+ private readonly traces: Record<TestPhase, TraceEntry[]> = {
148
+ arrange: [],
149
+ act: [],
150
+ assert: [],
151
+ }
152
+
153
+ private _entryCount = 0
154
+ private _currentPhase: TestPhase = "arrange"
155
+
156
+ setPhase(phase: TestPhase) {
157
+ this._currentPhase = phase
158
+ }
159
+
160
+ readonly logger = pino({ level: "debug" }, this.createLogStream())
161
+
162
+ createServiceMock<TService extends object>(name: string, impl: Partial<TService> = {}): TService {
163
+ return new Proxy(impl as TService, {
164
+ get: (target, prop) => {
165
+ // we assume that all get calls via the proxy are method calls
166
+
167
+ return this.createServiceMethodMock(
168
+ name,
169
+ prop as string,
170
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
171
+ target[prop as keyof TService] as Function,
172
+ )
173
+ },
174
+ })
175
+ }
176
+
177
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
178
+ private createServiceMethodMock(serviceName: string, name: string, base: Function = () => {}) {
179
+ return new Proxy(base, {
180
+ apply: (target, thisArg, args) => {
181
+ try {
182
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
183
+ const result = target.apply(thisArg, args)
184
+
185
+ if (!isPromise(result)) {
186
+ // record synchronous call
187
+ this.addEntry(new CallTraceEntry(this.nextEntryId(), serviceName, name, args, result))
188
+
189
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
190
+ return result
191
+ }
192
+
193
+ // create async call trace entry
194
+ const callEntry = new CallTraceEntry(this.nextEntryId(), serviceName, name, args)
195
+ this.addEntry(callEntry)
196
+
197
+ // wrap the promise to track resolution
198
+ const wrappedPromise = (result as Promise<unknown>).then(
199
+ (resolvedValue: unknown) => {
200
+ if (this.nextEntryId() === callEntry.id + 1) {
201
+ // immediate completion (no other traces added in between)
202
+
203
+ callEntry.result = resolvedValue
204
+ return resolvedValue
205
+ }
206
+
207
+ // deferred completion - create a separate result entry
208
+ const resultEntry = new CallResultEntry(this.nextEntryId(), callEntry, resolvedValue)
209
+
210
+ this.addEntry(resultEntry)
211
+ callEntry.resolvedEntry = resultEntry
212
+
213
+ return resolvedValue
214
+ },
215
+ (rejectedError: unknown) => {
216
+ if (this.nextEntryId() === callEntry.id + 1) {
217
+ // immediate error (no other traces added in between)
218
+
219
+ callEntry.error = rejectedError
220
+ throw rejectedError
221
+ }
222
+
223
+ // deferred error - create a separate result entry
224
+ const resultEntry = new CallResultEntry(
225
+ this.nextEntryId(),
226
+ callEntry,
227
+ undefined,
228
+ rejectedError,
229
+ )
230
+
231
+ this.addEntry(resultEntry)
232
+ callEntry.resolvedEntry = resultEntry
233
+
234
+ throw rejectedError
235
+ },
236
+ )
237
+
238
+ return wrappedPromise
239
+ } catch (error) {
240
+ // record synchronous error
241
+ const callEntry = new CallTraceEntry(
242
+ this.nextEntryId(),
243
+ serviceName,
244
+ name,
245
+ args,
246
+ undefined,
247
+ error,
248
+ )
249
+
250
+ this.addEntry(callEntry)
251
+ }
252
+ },
253
+ })
254
+ }
255
+
256
+ nextEntryId(): number {
257
+ return this._entryCount + 1
258
+ }
259
+
260
+ addEntry(entry: TraceEntry) {
261
+ this._entryCount += 1
262
+ this.traces[this._currentPhase].push(entry)
263
+ }
264
+
265
+ private createLogStream() {
266
+ const stream = new PassThrough()
267
+
268
+ stream.on("data", data => {
269
+ const { level, msg, error, ...other } = JSON.parse(String(data)) as {
270
+ msg: string
271
+ level: number
272
+ error?: string
273
+ }
274
+
275
+ const levelLabel = levels.labels[level]
276
+ const logEntry = new LogTraceEntry(
277
+ this.nextEntryId(),
278
+ levelLabel,
279
+ msg,
280
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
281
+ omit(other as any, ["time", "pid", "hostname"]),
282
+ error,
283
+ )
284
+
285
+ this.addEntry(logEntry)
286
+ })
287
+
288
+ return stream
289
+ }
290
+
291
+ render(): string {
292
+ const blocks = Object.entries(this.traces).flatMap(([phase, traces]) => {
293
+ if (traces.length === 0) {
294
+ return []
295
+ }
296
+
297
+ return [
298
+ `## ${phase}\n`,
299
+ ...traces.map(trace => {
300
+ const anchorId = `trace-${trace.id}`
301
+
302
+ return `### <a id="${anchorId}"></a> ${trace.id}. ${trace.render()}`
303
+ }),
304
+ ]
305
+ })
306
+
307
+ return md.joinBlocks(blocks)
308
+ }
309
+ }
310
+
311
+ export type TestBaseFixtures = {
312
+ tracer: TestTracer
313
+ clock: ClockProvider
314
+ random: RandomProvider
315
+ }
316
+
317
+ function camelCaseToKebabCase(str: string): string {
318
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
319
+ }
320
+
321
+ function getFullName(test: RunnerTask): string {
322
+ let result = camelCaseToKebabCase(test.name)
323
+
324
+ while (test.suite) {
325
+ test = test.suite
326
+ result = `${camelCaseToKebabCase(test.name)}/${result}`
327
+ }
328
+
329
+ return result.replaceAll(" ", "-")
330
+ }
331
+
332
+ export const test = baseTest.extend<TestBaseFixtures>({
333
+ tracer: async ({ task, expect }, use) => {
334
+ const tracer = new TestTracer()
335
+
336
+ await use(tracer)
337
+
338
+ const fileName = basename(task.file.name).replace(/\.test\.ts$/, "")
339
+ const taskName = getFullName(task)
340
+ const rendered = tracer.render()
341
+
342
+ await expect(rendered).toMatchFileSnapshot(`./__traces__/${fileName}/${taskName}.md`)
343
+ },
344
+
345
+ random: async ({ task }, use) => {
346
+ const random = new ReproducibleRandomProvider(Buffer.from(task.id))
347
+
348
+ await use(random)
349
+ },
350
+
351
+ // eslint-disable-next-line no-empty-pattern
352
+ clock: async ({}, use) => {
353
+ const clock = new ReproducibleClockProvider()
354
+
355
+ await use(clock)
356
+ },
357
+ })
358
+
359
+ export const testBase = test
@@ -0,0 +1,33 @@
1
+ export type TreeNode = {
2
+ text: string
3
+ children: TreeNode[]
4
+ }
5
+
6
+ /**
7
+ * Renders a tree structure similar to the Linux tree command output.
8
+ * Uses box-drawing characters to create visual hierarchy.
9
+ */
10
+ export function renderTree(node: TreeNode): string {
11
+ const lines: string[] = []
12
+
13
+ function renderNode(node: TreeNode, prefix: string = "", isLast: boolean = true): void {
14
+ // Add current node
15
+ lines.push(prefix + (isLast ? "└── " : "├── ") + node.text)
16
+
17
+ // Add children
18
+ const childPrefix = prefix + (isLast ? " " : "│ ")
19
+ node.children.forEach((child, index) => {
20
+ const isLastChild = index === node.children.length - 1
21
+ renderNode(child, childPrefix, isLastChild)
22
+ })
23
+ }
24
+
25
+ // Start with root node (no prefix)
26
+ lines.push(node.text)
27
+ node.children.forEach((child, index) => {
28
+ const isLastChild = index === node.children.length - 1
29
+ renderNode(child, "", isLastChild)
30
+ })
31
+
32
+ return lines.join("\n")
33
+ }
@@ -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
 
@@ -36,5 +40,9 @@ export async function loadConfig(
36
40
  await import("dotenv/config")
37
41
  }
38
42
 
39
- return configSchema.parse(env)
43
+ try {
44
+ return configSchema.parse(env)
45
+ } catch (error) {
46
+ throw new Error("Failed to parse backend configuration", { cause: error })
47
+ }
40
48
  }
@@ -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"