@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.
- package/dist/chunk-RCB4AFGD.js +159 -0
- package/dist/chunk-RCB4AFGD.js.map +1 -0
- package/dist/chunk-WHALQHEZ.js +2017 -0
- package/dist/chunk-WHALQHEZ.js.map +1 -0
- package/dist/highstate.manifest.json +3 -3
- package/dist/index.js +6158 -2178
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +47 -155
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +159 -41
- package/package.json +25 -7
- package/src/artifact/abstractions.ts +46 -0
- package/src/artifact/encryption.ts +69 -0
- package/src/artifact/factory.ts +36 -0
- package/src/artifact/index.ts +3 -0
- package/src/artifact/local.ts +142 -0
- package/src/business/api-key.ts +65 -0
- package/src/business/artifact.ts +288 -0
- package/src/business/backend-unlock.ts +10 -0
- package/src/business/index.ts +9 -0
- package/src/business/instance-lock.ts +124 -0
- package/src/business/instance-state.ts +292 -0
- package/src/business/operation.ts +251 -0
- package/src/business/project-unlock.ts +242 -0
- package/src/business/secret.ts +187 -0
- package/src/business/worker.ts +161 -0
- package/src/common/index.ts +2 -1
- package/src/common/performance.ts +44 -0
- package/src/common/tree.ts +33 -0
- package/src/common/utils.ts +40 -1
- package/src/config.ts +14 -10
- package/src/hotstate/abstractions.ts +48 -0
- package/src/hotstate/factory.ts +17 -0
- package/src/{secret → hotstate}/index.ts +1 -0
- package/src/hotstate/manager.ts +192 -0
- package/src/hotstate/memory.ts +100 -0
- package/src/hotstate/validation.ts +101 -0
- package/src/index.ts +2 -1
- package/src/library/abstractions.ts +10 -23
- package/src/library/factory.ts +2 -2
- package/src/library/local.ts +89 -102
- package/src/library/worker/evaluator.ts +9 -42
- package/src/library/worker/loader.lite.ts +41 -0
- package/src/library/worker/main.ts +14 -65
- package/src/library/worker/protocol.ts +8 -24
- package/src/lock/abstractions.ts +6 -0
- package/src/lock/factory.ts +15 -0
- package/src/{workspace → lock}/index.ts +1 -0
- package/src/lock/manager.ts +82 -0
- package/src/lock/memory.ts +19 -0
- package/src/orchestrator/manager.ts +129 -82
- package/src/orchestrator/operation-workset.ts +168 -77
- package/src/orchestrator/operation.ts +967 -284
- package/src/project/abstractions.ts +20 -7
- package/src/project/factory.ts +1 -1
- package/src/project/index.ts +0 -1
- package/src/project/local.ts +73 -17
- package/src/project/manager.ts +272 -131
- package/src/pubsub/abstractions.ts +13 -0
- package/src/pubsub/factory.ts +19 -0
- package/src/pubsub/index.ts +3 -0
- package/src/pubsub/local.ts +36 -0
- package/src/pubsub/manager.ts +100 -0
- package/src/pubsub/validation.ts +33 -0
- package/src/runner/abstractions.ts +135 -68
- package/src/runner/artifact-env.ts +160 -0
- package/src/runner/factory.ts +20 -5
- package/src/runner/force-abort.ts +117 -0
- package/src/runner/local.ts +281 -371
- package/src/{common → runner}/pulumi.ts +86 -37
- package/src/services.ts +193 -35
- package/src/shared/index.ts +3 -11
- package/src/shared/models/backend/index.ts +3 -0
- package/src/shared/models/backend/project.ts +63 -0
- package/src/shared/models/backend/unlock-method.ts +20 -0
- package/src/shared/models/base.ts +151 -0
- package/src/shared/models/errors.ts +5 -0
- package/src/shared/models/index.ts +4 -0
- package/src/shared/models/project/api-key.ts +62 -0
- package/src/shared/models/project/artifact.ts +113 -0
- package/src/shared/models/project/component.ts +45 -0
- package/src/shared/models/project/index.ts +14 -0
- package/src/shared/{project.ts → models/project/instance.ts} +12 -0
- package/src/shared/models/project/lock.ts +91 -0
- package/src/shared/{operation.ts → models/project/operation.ts} +28 -7
- package/src/shared/models/project/page.ts +57 -0
- package/src/shared/models/project/secret.ts +112 -0
- package/src/shared/models/project/service-account.ts +22 -0
- package/src/shared/models/project/state.ts +432 -0
- package/src/shared/models/project/terminal.ts +99 -0
- package/src/shared/models/project/trigger.ts +56 -0
- package/src/shared/models/project/unlock-method.ts +31 -0
- package/src/shared/models/project/worker.ts +105 -0
- package/src/shared/resolvers/graph-resolver.ts +28 -0
- package/src/shared/resolvers/index.ts +5 -0
- package/src/shared/resolvers/input-hash.ts +53 -15
- package/src/shared/resolvers/input.ts +1 -9
- package/src/shared/resolvers/registry.ts +3 -2
- package/src/shared/resolvers/state.ts +2 -2
- package/src/shared/resolvers/validation.ts +61 -20
- package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
- package/src/shared/utils/hash.ts +6 -0
- package/src/shared/utils/index.ts +3 -0
- package/src/shared/utils/promise-tracker.ts +23 -0
- package/src/state/abstractions.ts +330 -101
- package/src/state/encryption.ts +59 -0
- package/src/state/factory.ts +3 -5
- package/src/state/index.ts +3 -0
- package/src/state/keyring.ts +22 -0
- package/src/state/local/backend.ts +299 -0
- package/src/state/local/collection.ts +342 -0
- package/src/state/local/index.ts +2 -0
- package/src/state/manager.ts +804 -18
- package/src/state/repository/index.ts +2 -0
- package/src/state/repository/repository.index.ts +193 -0
- package/src/state/repository/repository.ts +458 -0
- package/src/terminal/{shared.ts → abstractions.ts} +3 -3
- package/src/terminal/docker.ts +18 -14
- package/src/terminal/factory.ts +3 -3
- package/src/terminal/index.ts +1 -1
- package/src/terminal/manager.ts +131 -79
- package/src/terminal/run.sh.ts +21 -11
- package/src/worker/abstractions.ts +42 -0
- package/src/worker/docker.ts +83 -0
- package/src/worker/factory.ts +20 -0
- package/src/worker/index.ts +3 -0
- package/src/worker/manager.ts +139 -0
- package/dist/chunk-KTGKNSKM.js +0 -979
- package/dist/chunk-KTGKNSKM.js.map +0 -1
- package/dist/chunk-WXDYCRTT.js +0 -234
- package/dist/chunk-WXDYCRTT.js.map +0 -1
- package/src/library/worker/loader.ts +0 -114
- package/src/preferences/shared.ts +0 -1
- package/src/project/lock.ts +0 -39
- package/src/secret/abstractions.ts +0 -59
- package/src/secret/factory.ts +0 -22
- package/src/secret/local.ts +0 -152
- package/src/shared/state.ts +0 -247
- package/src/shared/terminal.ts +0 -14
- package/src/state/local.ts +0 -612
- package/src/workspace/abstractions.ts +0 -41
- package/src/workspace/factory.ts +0 -14
- package/src/workspace/local.ts +0 -54
- /package/src/shared/{library.ts → models/backend/library.ts} +0 -0
package/src/common/utils.ts
CHANGED
|
@@ -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 = [
|
|
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 {
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
+
}
|
|
@@ -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
|
@@ -3,22 +3,11 @@ import type { LibraryModel, LibraryUpdate, ResolvedInstanceInput } from "../shar
|
|
|
3
3
|
|
|
4
4
|
export type ResolvedUnitSource = {
|
|
5
5
|
unitType: string
|
|
6
|
-
sourceHash:
|
|
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(
|
|
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
|
}
|
package/src/library/factory.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
15
|
+
switch (config.HIGHSTATE_LIBRARY_BACKEND_TYPE) {
|
|
16
16
|
case "local": {
|
|
17
17
|
return await LocalLibraryBackend.create(config, logger)
|
|
18
18
|
}
|