@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.
- package/dist/chunk-NAAIDR4U.js +8499 -0
- package/dist/chunk-NAAIDR4U.js.map +1 -0
- package/dist/chunk-OU5OQBLB.js +74 -0
- package/dist/chunk-OU5OQBLB.js.map +1 -0
- package/dist/chunk-Y7DXREVO.js +1745 -0
- package/dist/chunk-Y7DXREVO.js.map +1 -0
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +7227 -2501
- package/dist/index.js.map +1 -1
- package/dist/library/package-resolution-worker.js +7 -5
- package/dist/library/package-resolution-worker.js.map +1 -1
- package/dist/library/worker/main.js +76 -185
- package/dist/library/worker/main.js.map +1 -1
- package/dist/magic-string.es-5ABAC4JN.js +1292 -0
- package/dist/magic-string.es-5ABAC4JN.js.map +1 -0
- package/dist/shared/index.js +3 -98
- package/dist/shared/index.js.map +1 -1
- package/package.json +31 -10
- package/src/artifact/abstractions.ts +46 -0
- package/src/artifact/encryption.ts +109 -0
- package/src/artifact/factory.ts +36 -0
- package/src/artifact/index.ts +3 -0
- package/src/artifact/local.ts +138 -0
- package/src/business/__traces__/secret/update-instance-secrets/create-and-delete-secrets-simultaneously.md +356 -0
- package/src/business/__traces__/secret/update-instance-secrets/create-new-secrets-for-instance.md +274 -0
- package/src/business/__traces__/secret/update-instance-secrets/delete-existing-secrets.md +223 -0
- package/src/business/__traces__/secret/update-instance-secrets/no-op-when-no-changes.md +147 -0
- package/src/business/__traces__/secret/update-instance-secrets/update-existing-secrets.md +280 -0
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration-when-other-exists.md +360 -0
- package/src/business/__traces__/worker/update-unit-registrations/add-new-unit-registration.md +215 -0
- package/src/business/__traces__/worker/update-unit-registrations/create-multiple-workers-with-different-identities.md +427 -0
- package/src/business/__traces__/worker/update-unit-registrations/handle-nonexistent-registration-id-gracefully.md +217 -0
- package/src/business/__traces__/worker/update-unit-registrations/no-op-when-no-changes.md +132 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-changes.md +454 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-when-image-version-changes.md +426 -0
- package/src/business/__traces__/worker/update-unit-registrations/recreate-worker-with-same-identity-reuses-service-account.md +372 -0
- package/src/business/__traces__/worker/update-unit-registrations/remove-one-of-multiple-unit-registrations.md +383 -0
- package/src/business/__traces__/worker/update-unit-registrations/remove-unit-registration.md +245 -0
- package/src/business/__traces__/worker/update-unit-registrations/update-existing-unit-registration-when-params-change.md +174 -0
- package/src/business/__traces__/worker/update-unit-registrations/update-params-and-image-simultaneously.md +432 -0
- package/src/business/__traces__/worker/update-unit-registrations/worker-with-multiple-registrations-not-deleted-when-one-removed.md +220 -0
- package/src/business/api-key.ts +65 -0
- package/src/business/artifact.ts +289 -0
- package/src/business/backend-unlock.ts +10 -0
- package/src/business/index.ts +10 -0
- package/src/business/instance-lock.ts +125 -0
- package/src/business/instance-state.ts +434 -0
- package/src/business/operation.ts +251 -0
- package/src/business/project-unlock.ts +260 -0
- package/src/business/project.ts +299 -0
- package/src/business/secret.test.ts +178 -0
- package/src/business/secret.ts +281 -0
- package/src/business/worker.test.ts +614 -0
- package/src/business/worker.ts +398 -0
- package/src/common/clock.ts +18 -0
- package/src/common/index.ts +5 -1
- package/src/common/performance.ts +44 -0
- package/src/common/random.ts +68 -0
- package/src/common/test/index.ts +2 -0
- package/src/common/test/render.ts +98 -0
- package/src/common/test/tracer.ts +359 -0
- package/src/common/tree.ts +33 -0
- package/src/common/utils.ts +40 -1
- package/src/config.ts +19 -11
- 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 +100 -0
- package/src/index.ts +2 -1
- package/src/library/abstractions.ts +24 -28
- package/src/library/factory.ts +2 -2
- package/src/library/local.ts +91 -111
- package/src/library/worker/evaluator.ts +36 -73
- package/src/library/worker/loader.lite.ts +54 -0
- package/src/library/worker/main.ts +15 -66
- package/src/library/worker/protocol.ts +6 -33
- package/src/lock/abstractions.ts +6 -0
- package/src/lock/factory.ts +15 -0
- package/src/lock/index.ts +4 -0
- package/src/lock/manager.ts +97 -0
- package/src/lock/memory.ts +19 -0
- package/src/lock/test.ts +108 -0
- package/src/orchestrator/manager.ts +118 -90
- package/src/orchestrator/operation-workset.ts +181 -93
- package/src/orchestrator/operation.ts +1021 -283
- package/src/project/abstractions.ts +27 -38
- package/src/project/evaluation.ts +248 -0
- package/src/project/factory.ts +1 -1
- package/src/project/index.ts +1 -2
- package/src/project/local.ts +107 -103
- package/src/pubsub/abstractions.ts +13 -0
- package/src/pubsub/factory.ts +19 -0
- package/src/{workspace → pubsub}/index.ts +1 -0
- package/src/pubsub/local.ts +36 -0
- package/src/pubsub/manager.ts +108 -0
- package/src/pubsub/validation.ts +33 -0
- package/src/runner/abstractions.ts +155 -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 +292 -372
- package/src/{common → runner}/pulumi.ts +89 -37
- package/src/services.ts +251 -40
- package/src/shared/index.ts +3 -11
- package/src/shared/models/backend/index.ts +3 -0
- package/src/shared/{library.ts → models/backend/library.ts} +4 -4
- package/src/shared/models/backend/project.ts +82 -0
- package/src/shared/models/backend/unlock-method.ts +20 -0
- package/src/shared/models/base.ts +68 -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 +65 -0
- package/src/shared/models/project/artifact.ts +83 -0
- package/src/shared/models/project/index.ts +13 -0
- package/src/shared/models/project/lock.ts +91 -0
- package/src/shared/models/project/model.ts +14 -0
- package/src/shared/{operation.ts → models/project/operation.ts} +29 -8
- package/src/shared/models/project/page.ts +57 -0
- package/src/shared/models/project/secret.ts +98 -0
- package/src/shared/models/project/service-account.ts +22 -0
- package/src/shared/models/project/state.ts +449 -0
- package/src/shared/models/project/terminal.ts +98 -0
- package/src/shared/models/project/trigger.ts +56 -0
- package/src/shared/models/project/unlock-method.ts +38 -0
- package/src/shared/models/project/worker.ts +107 -0
- package/src/shared/resolvers/graph-resolver.ts +61 -18
- package/src/shared/resolvers/index.ts +5 -0
- package/src/shared/resolvers/input-hash.ts +53 -15
- package/src/shared/resolvers/input.ts +47 -13
- package/src/shared/resolvers/registry.ts +3 -2
- package/src/shared/resolvers/state.ts +2 -2
- package/src/shared/resolvers/validation.ts +82 -25
- package/src/shared/utils/args.ts +25 -0
- 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 +4 -0
- package/src/shared/utils/promise-tracker.ts +23 -0
- package/src/state/abstractions.ts +199 -131
- package/src/state/encryption.ts +98 -0
- package/src/state/factory.ts +3 -5
- package/src/state/index.ts +4 -0
- package/src/state/keyring.ts +22 -0
- package/src/state/local/backend.ts +106 -0
- package/src/state/local/collection.ts +361 -0
- package/src/state/local/index.ts +2 -0
- package/src/state/manager.ts +875 -18
- package/src/state/memory/backend.ts +70 -0
- package/src/state/memory/collection.ts +270 -0
- package/src/state/memory/index.ts +2 -0
- package/src/state/repository/index.ts +2 -0
- package/src/state/repository/repository.index.ts +193 -0
- package/src/state/repository/repository.ts +507 -0
- package/src/state/test.ts +457 -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/unlock/abstractions.ts +49 -0
- package/src/unlock/index.ts +2 -0
- package/src/unlock/memory.ts +32 -0
- 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 +167 -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/project/manager.ts +0 -433
- package/src/secret/abstractions.ts +0 -59
- package/src/secret/factory.ts +0 -22
- package/src/secret/local.ts +0 -152
- package/src/shared/project.ts +0 -62
- 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
|
@@ -3,10 +3,22 @@ export type AsyncBatcherOptions = {
|
|
|
3
3
|
maxWaitTimeMs?: number
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export type AsyncBatcher<T> = {
|
|
7
|
+
/**
|
|
8
|
+
* Add an item to the batch.
|
|
9
|
+
*/
|
|
10
|
+
call(item: T): void
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Immediately flush the pending batch (if any).
|
|
14
|
+
*/
|
|
15
|
+
flush(): Promise<void>
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
export function createAsyncBatcher<T>(
|
|
7
19
|
fn: (items: T[]) => Promise<void> | void,
|
|
8
20
|
{ waitMs = 100, maxWaitTimeMs = 1000 }: AsyncBatcherOptions = {},
|
|
9
|
-
) {
|
|
21
|
+
): AsyncBatcher<T> {
|
|
10
22
|
let batch: T[] = []
|
|
11
23
|
let activeTimeout: NodeJS.Timeout | null = null
|
|
12
24
|
let maxWaitTimeout: NodeJS.Timeout | null = null
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class PromiseTracker {
|
|
2
|
+
private readonly trackedPromises = new Set<Promise<unknown>>()
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tracks a promise to ensure its rejection is handled inside the tracker's scope.
|
|
6
|
+
*/
|
|
7
|
+
track<T>(promise: Promise<T>): void {
|
|
8
|
+
this.trackedPromises.add(promise)
|
|
9
|
+
|
|
10
|
+
void promise.finally(() => this.trackedPromises.delete(promise))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Waits for all tracked promises to resolve or reject.
|
|
15
|
+
*/
|
|
16
|
+
async waitForAll(): Promise<void> {
|
|
17
|
+
if (this.trackedPromises.size === 0) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await Promise.allSettled(this.trackedPromises)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,221 +1,289 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type InstanceState,
|
|
4
|
-
type ProjectOperation,
|
|
5
|
-
type TerminalSession,
|
|
6
|
-
} from "../shared"
|
|
1
|
+
import type { KeyObfuscationBackend } from "./encryption"
|
|
2
|
+
import { AccessError, type CollectionQueryResult } from "../shared"
|
|
7
3
|
|
|
8
|
-
export type
|
|
9
|
-
export type TerminalHistoryEntry = [sessionId: string, entry: string]
|
|
10
|
-
|
|
11
|
-
export interface StateBackend {
|
|
4
|
+
export type CollectionQuery = {
|
|
12
5
|
/**
|
|
13
|
-
*
|
|
6
|
+
* The search string to filter documents by display name, descriptiion, or other text fields.
|
|
7
|
+
*
|
|
8
|
+
* Not implemented yet.
|
|
14
9
|
*/
|
|
15
|
-
|
|
10
|
+
search?: string
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
19
|
-
* If `beforeOperationId` is provided, returns the operations before the specified operation ID.
|
|
20
|
-
* The page size is fixed to 10.
|
|
13
|
+
* The ID of the document to start the query from.
|
|
21
14
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
15
|
+
* If "asc" sorting is used, this will return results after this ID.
|
|
16
|
+
* Otherwise, it will return results before this ID.
|
|
24
17
|
*/
|
|
25
|
-
|
|
18
|
+
cursor?: string
|
|
26
19
|
|
|
27
20
|
/**
|
|
28
|
-
*
|
|
29
|
-
* Actual states should be retrieved from the runner (and they are still may not be up-to-date).
|
|
21
|
+
* The count of documents to return.
|
|
30
22
|
*
|
|
31
|
-
*
|
|
23
|
+
* Defaults to 20 if not specified.
|
|
24
|
+
* Maximum value is 100.
|
|
32
25
|
*/
|
|
33
|
-
|
|
26
|
+
count?: number
|
|
34
27
|
|
|
35
28
|
/**
|
|
36
|
-
*
|
|
29
|
+
* The sorting order for the results.
|
|
37
30
|
*
|
|
38
|
-
*
|
|
39
|
-
* @param instanceId The ID of the instance.
|
|
31
|
+
* By default, "asc" is used.
|
|
40
32
|
*/
|
|
41
|
-
|
|
33
|
+
sort?: "asc" | "desc"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const CollectionBackend = Symbol("CollectionBackend")
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The batch interface for write operations.
|
|
40
|
+
*
|
|
41
|
+
* It ensures that all changes made within a batch are written atomically.
|
|
42
|
+
*/
|
|
43
|
+
export type StateBatch = {
|
|
44
|
+
[CollectionBackend]: unknown
|
|
45
|
+
[Symbol.asyncDispose](): Promise<void>
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
* Writes all changes made in this batch as a single atomic operation.
|
|
49
|
+
*/
|
|
50
|
+
write(): Promise<void>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The snapshot interface for read operations.
|
|
55
|
+
*
|
|
56
|
+
* It ensure the consistency of read operations performed within a single snapshot.
|
|
57
|
+
*/
|
|
58
|
+
export type StateSnapshot = {
|
|
59
|
+
[CollectionBackend]: unknown
|
|
60
|
+
[Symbol.asyncDispose](): Promise<void>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Low-level storage abstraction that operates on raw binary data.
|
|
65
|
+
* This interface provides basic CRUD operations without any knowledge of data structure or validation.
|
|
66
|
+
*/
|
|
67
|
+
export interface StateCollection {
|
|
68
|
+
/**
|
|
69
|
+
* The name of the collection received from the state manager.
|
|
48
70
|
*/
|
|
49
|
-
|
|
71
|
+
readonly name: string
|
|
50
72
|
|
|
51
73
|
/**
|
|
52
|
-
*
|
|
53
|
-
* The logs are not included.
|
|
54
|
-
*
|
|
55
|
-
* @param operationId The ID of the operation.
|
|
74
|
+
* The parameters received from the state manager.
|
|
56
75
|
*/
|
|
57
|
-
|
|
76
|
+
readonly params: Record<string, string>
|
|
58
77
|
|
|
59
78
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @param operationId The ID of the operation.
|
|
63
|
-
* @param instanceId The ID of the instance.
|
|
79
|
+
* The UUIDv5 namespace for this collection which combines the collection name and parameters + project namespace if applicable.
|
|
64
80
|
*/
|
|
65
|
-
|
|
81
|
+
readonly namespace: Promise<string>
|
|
66
82
|
|
|
67
83
|
/**
|
|
68
|
-
*
|
|
69
|
-
* The operation must have a unique ID sortable chronologically.
|
|
70
|
-
* Also, the operation must have the project ID.
|
|
84
|
+
* Retrieves a single item by its ID.
|
|
71
85
|
*
|
|
72
|
-
* @param
|
|
86
|
+
* @param id The ID of the item to retrieve.
|
|
87
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
73
88
|
*/
|
|
74
|
-
|
|
89
|
+
get(id: string, snapshot?: StateSnapshot): Promise<Uint8Array | undefined>
|
|
75
90
|
|
|
76
91
|
/**
|
|
77
|
-
*
|
|
92
|
+
* Retrieves multiple items by their IDs.
|
|
93
|
+
* Returns a record where keys are IDs and values are the data (or undefined if not found).
|
|
94
|
+
* The order of the results matches the order of the input IDs.
|
|
78
95
|
*
|
|
79
|
-
* @param
|
|
80
|
-
* @param
|
|
81
|
-
* @param states The instance states to put.
|
|
96
|
+
* @param ids An array of IDs to retrieve.
|
|
97
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
82
98
|
*/
|
|
83
|
-
|
|
84
|
-
projectId: string,
|
|
85
|
-
operationId: string,
|
|
86
|
-
states: InstanceState[],
|
|
87
|
-
): Promise<void>
|
|
99
|
+
getMany(ids: string[], snapshot?: StateSnapshot): Promise<(Uint8Array | undefined)[]>
|
|
88
100
|
|
|
89
101
|
/**
|
|
90
|
-
*
|
|
102
|
+
* Retrieves all items in the collection as an array of tuples.
|
|
103
|
+
* Use carefully, as this may return a large amount of data.
|
|
91
104
|
*
|
|
92
|
-
* @param
|
|
93
|
-
* @param states The instance states to put.
|
|
105
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
94
106
|
*/
|
|
95
|
-
|
|
107
|
+
getAll(snapshot?: StateSnapshot): Promise<[string, Uint8Array][]>
|
|
96
108
|
|
|
97
109
|
/**
|
|
98
|
-
*
|
|
110
|
+
* Retrieves items matching a query.
|
|
99
111
|
*
|
|
100
|
-
* @param
|
|
101
|
-
* @param
|
|
112
|
+
* @param query The query object containing search, cursor, skip, count, and sort options.
|
|
113
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
102
114
|
*/
|
|
103
|
-
|
|
115
|
+
query(
|
|
116
|
+
query: CollectionQuery,
|
|
117
|
+
snapshot?: StateSnapshot,
|
|
118
|
+
): Promise<CollectionQueryResult<[string, Uint8Array]>>
|
|
104
119
|
|
|
105
120
|
/**
|
|
106
|
-
*
|
|
121
|
+
* Retrieves the total count of items in the collection.
|
|
122
|
+
* This is useful for pagination and understanding the size of the collection.
|
|
107
123
|
*
|
|
108
|
-
* @param
|
|
124
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
109
125
|
*/
|
|
110
|
-
|
|
126
|
+
getTotalCount(snapshot?: StateSnapshot): Promise<number>
|
|
111
127
|
|
|
112
128
|
/**
|
|
113
|
-
*
|
|
129
|
+
* Returns an async iterator over all items in the collection.
|
|
130
|
+
* This is more memory-efficient than getAll() for large collections.
|
|
114
131
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* @param projectId The ID of the project.
|
|
118
|
-
* @param instanceIds The IDs of the top-level composite instances.
|
|
132
|
+
* @param snapshot Optional snapshot for consistent reads.
|
|
119
133
|
*/
|
|
120
|
-
|
|
121
|
-
projectId: string,
|
|
122
|
-
instanceIds: string[],
|
|
123
|
-
signal?: AbortSignal,
|
|
124
|
-
): Promise<Record<string, string[]>>
|
|
134
|
+
iterate(snapshot?: StateSnapshot): AsyncIterable<[string, Uint8Array]>
|
|
125
135
|
|
|
126
136
|
/**
|
|
127
|
-
*
|
|
137
|
+
* Stores a single item with the given ID.
|
|
128
138
|
*
|
|
129
|
-
* @param
|
|
130
|
-
* @param
|
|
139
|
+
* @param id The ID of the item to store.
|
|
140
|
+
* @param value The binary data to store.
|
|
141
|
+
* @param batch Optional batch for atomic writes.
|
|
131
142
|
*/
|
|
132
|
-
|
|
133
|
-
projectId: string,
|
|
134
|
-
childrenIds: Record<string, string[]>,
|
|
135
|
-
): Promise<void>
|
|
143
|
+
put(id: string, value: Uint8Array, batch?: StateBatch): Promise<void>
|
|
136
144
|
|
|
137
145
|
/**
|
|
138
|
-
*
|
|
146
|
+
* Stores multiple items in a batch operation.
|
|
139
147
|
*
|
|
140
|
-
* @param
|
|
141
|
-
* @param
|
|
148
|
+
* @param entries An array of key-value pairs where keys are IDs and values are binary data.
|
|
149
|
+
* @param batch Optional batch for atomic writes.
|
|
142
150
|
*/
|
|
143
|
-
|
|
151
|
+
putMany(entries: Array<[string, Uint8Array]>, batch?: StateBatch): Promise<void>
|
|
144
152
|
|
|
145
153
|
/**
|
|
146
|
-
*
|
|
154
|
+
* Deletes a single item by its ID.
|
|
147
155
|
*
|
|
148
|
-
* @param
|
|
156
|
+
* @param id The ID of the item to delete.
|
|
157
|
+
* @param batch Optional batch for atomic writes.
|
|
149
158
|
*/
|
|
150
|
-
|
|
159
|
+
delete(id: string, batch?: StateBatch): Promise<void>
|
|
151
160
|
|
|
152
161
|
/**
|
|
153
|
-
*
|
|
162
|
+
* Deletes multiple items by their IDs.
|
|
154
163
|
*
|
|
155
|
-
* @param
|
|
156
|
-
* @param
|
|
164
|
+
* @param ids An array of IDs to delete.
|
|
165
|
+
* @param batch Optional batch for atomic writes.
|
|
157
166
|
*/
|
|
158
|
-
|
|
167
|
+
deleteMany(ids: string[], batch?: StateBatch): Promise<void>
|
|
159
168
|
|
|
160
169
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @param projectId The ID of the project.
|
|
164
|
-
* @param instanceIds The IDs of the instances to clear.
|
|
170
|
+
* Deletes all items in the collection.
|
|
165
171
|
*/
|
|
166
|
-
|
|
172
|
+
clear(): Promise<void>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export type CollectionMap = {
|
|
176
|
+
// Backend System
|
|
177
|
+
projects: Record<never, never>
|
|
178
|
+
"project-name-index": Record<never, never>
|
|
179
|
+
"project-master-keys": Record<never, never>
|
|
180
|
+
"project-namespaces": Record<never, never>
|
|
181
|
+
"project-unlock-suites": Record<never, never>
|
|
182
|
+
"user-layouts": Record<never, never>
|
|
183
|
+
|
|
184
|
+
// Instances
|
|
185
|
+
"instance-states": { projectId: string }
|
|
186
|
+
"instance-locks": { projectId: string }
|
|
187
|
+
"virtual-instances": { projectId: string }
|
|
167
188
|
|
|
189
|
+
// Operations
|
|
190
|
+
operations: { projectId: string }
|
|
191
|
+
"active-operations": { projectId: string }
|
|
192
|
+
"operation-logs": { projectId: string; operationId: string }
|
|
193
|
+
"instance-log-index": { projectId: string; operationId: string; instanceId: string }
|
|
194
|
+
|
|
195
|
+
// Secrets
|
|
196
|
+
secrets: { projectId: string }
|
|
197
|
+
"secret-content": { projectId: string }
|
|
198
|
+
"secret-index": { projectId: string }
|
|
199
|
+
|
|
200
|
+
// Artifacts
|
|
201
|
+
artifacts: { projectId: string }
|
|
202
|
+
"artifact-hash-index": { projectId: string }
|
|
203
|
+
|
|
204
|
+
// Terminals
|
|
205
|
+
terminals: { projectId: string }
|
|
206
|
+
"terminal-specs": { projectId: string }
|
|
207
|
+
"terminal-sessions": { projectId: string }
|
|
208
|
+
"active-terminal-sessions": { projectId: string }
|
|
209
|
+
"instance-terminal-sessions": { projectId: string; instanceId: string }
|
|
210
|
+
"terminal-session-lines": { projectId: string; sessionId: string }
|
|
211
|
+
|
|
212
|
+
// Pages
|
|
213
|
+
pages: { projectId: string }
|
|
214
|
+
"page-contents": { projectId: string }
|
|
215
|
+
|
|
216
|
+
// Triggers
|
|
217
|
+
triggers: { projectId: string }
|
|
218
|
+
|
|
219
|
+
// Authentication
|
|
220
|
+
"unlock-methods": { projectId: string }
|
|
221
|
+
|
|
222
|
+
// Service Accounts
|
|
223
|
+
"service-accounts": { projectId: string }
|
|
224
|
+
|
|
225
|
+
// API Keys
|
|
226
|
+
"api-keys": { projectId: string }
|
|
227
|
+
"api-key-token-index": { projectId: string }
|
|
228
|
+
|
|
229
|
+
// Workers
|
|
230
|
+
workers: { projectId: string }
|
|
231
|
+
"worker-registrations": { projectId: string }
|
|
232
|
+
"worker-registration-index": { projectId: string; workerId: string }
|
|
233
|
+
"worker-logs": { projectId: string; workerId: string }
|
|
234
|
+
|
|
235
|
+
// Viewports
|
|
236
|
+
"project-viewports": { projectId: string }
|
|
237
|
+
"instance-viewports": { projectId: string; instanceId: string }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface StateBackend {
|
|
168
241
|
/**
|
|
169
|
-
*
|
|
242
|
+
* Returns the encrypted master key used for the encryption of backend-wide data.
|
|
170
243
|
*
|
|
171
|
-
*
|
|
172
|
-
* @param instanceId The ID of the instance.
|
|
244
|
+
* The key is AGE-armored string.
|
|
173
245
|
*/
|
|
174
|
-
|
|
246
|
+
getEncryptedBackendMasterKey(): Promise<string | undefined>
|
|
175
247
|
|
|
176
248
|
/**
|
|
177
|
-
*
|
|
249
|
+
* Sets the encrypted master key used for the encryption of backend-wide data.
|
|
250
|
+
*
|
|
251
|
+
* @param encryptedMasterKey The encrypted master key to set, as an AGE-armored string.
|
|
178
252
|
*/
|
|
179
|
-
|
|
253
|
+
setEncryptedBackendMasterKey(encryptedMasterKey: string): Promise<void>
|
|
180
254
|
|
|
181
255
|
/**
|
|
182
|
-
*
|
|
256
|
+
* Returns the encrypted namespace used for the backend.
|
|
183
257
|
*/
|
|
184
|
-
|
|
258
|
+
getEncryptedBackendNamespace(): Promise<Uint8Array | undefined>
|
|
185
259
|
|
|
186
260
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* @param projectId The ID of the project.
|
|
190
|
-
* @param instanceId The ID of the instance.
|
|
191
|
-
* @param sessionId The ID of the session.
|
|
261
|
+
* Sets the static identifier for the backend.
|
|
192
262
|
*/
|
|
193
|
-
|
|
194
|
-
projectId: string,
|
|
195
|
-
instanceId: string,
|
|
196
|
-
sessionId: string,
|
|
197
|
-
): Promise<TerminalSession | null>
|
|
263
|
+
setEncryptedBackendNamespace(encryptedNamespace: Uint8Array): Promise<void>
|
|
198
264
|
|
|
199
265
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* @param projectId The ID of the project.
|
|
203
|
-
* @param instanceId The ID of the instance.
|
|
204
|
-
* @param session The terminal session to put.
|
|
266
|
+
* Creates a batch for performing atomic write operations.
|
|
205
267
|
*/
|
|
206
|
-
|
|
268
|
+
createStateBatch(): StateBatch
|
|
207
269
|
|
|
208
270
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* @param sessionId The ID of the session.
|
|
271
|
+
* Creates a snapshot for consistent read operations.
|
|
212
272
|
*/
|
|
213
|
-
|
|
273
|
+
createStateSnapshot(): StateSnapshot
|
|
214
274
|
|
|
215
275
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* @param entries The entries to append.
|
|
276
|
+
* Creates a collection by name with the appropriate parameters.
|
|
219
277
|
*/
|
|
220
|
-
|
|
278
|
+
createCollection<TName extends keyof CollectionMap>(
|
|
279
|
+
name: TName,
|
|
280
|
+
params: CollectionMap[TName],
|
|
281
|
+
keyObfuscationBackend: KeyObfuscationBackend,
|
|
282
|
+
): StateCollection
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export class ProjectLockedError extends AccessError {
|
|
286
|
+
constructor(projectId: string) {
|
|
287
|
+
super(`The project with ID "${projectId}" is locked, decryption is not possible.`)
|
|
288
|
+
}
|
|
221
289
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js"
|
|
2
|
+
import { managedNonce } from "@noble/ciphers/webcrypto.js"
|
|
3
|
+
import { v5 as uuidv5 } from "uuid"
|
|
4
|
+
|
|
5
|
+
export interface EncryptionBackend {
|
|
6
|
+
/**
|
|
7
|
+
* Encrypts the given binary data using the configured master key.
|
|
8
|
+
*
|
|
9
|
+
* @param data The binary data to encrypt, represented as a Uint8Array.
|
|
10
|
+
*/
|
|
11
|
+
encrypt(data: Uint8Array): Promise<Uint8Array>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Decrypts the given binary data using the configured master key.
|
|
15
|
+
*
|
|
16
|
+
* @param data The binary data to decrypt, represented as a Uint8Array.
|
|
17
|
+
*/
|
|
18
|
+
decrypt(data: Uint8Array): Promise<Uint8Array>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The no-op encryption backend that does not perform any encryption or decryption and simply
|
|
23
|
+
* returns the data as-is.
|
|
24
|
+
*/
|
|
25
|
+
export class PassThroughEncryptionBackend implements EncryptionBackend {
|
|
26
|
+
static readonly instance = new PassThroughEncryptionBackend()
|
|
27
|
+
|
|
28
|
+
encrypt(data: Uint8Array): Promise<Uint8Array> {
|
|
29
|
+
return Promise.resolve(data)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
decrypt(data: Uint8Array): Promise<Uint8Array> {
|
|
33
|
+
return Promise.resolve(data)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The MasterKeyEncryptionBackend encryption backend that uses the noble-ciphers library to encrypt and decrypt data.
|
|
39
|
+
*
|
|
40
|
+
* It requires a master key to be provided, which is used for the actual encryption and decryption operations.
|
|
41
|
+
* The master key should be a 32-byte Uint8Array.
|
|
42
|
+
* This backend uses XChaCha20-Poly1305 for encryption and decryption.
|
|
43
|
+
* The nonce is 24 bytes long and is prepended to the ciphertext.
|
|
44
|
+
*/
|
|
45
|
+
export class MasterKeyEncryptionBackend implements EncryptionBackend {
|
|
46
|
+
constructor(private readonly masterKeyProvider: () => Promise<Uint8Array>) {}
|
|
47
|
+
|
|
48
|
+
async encrypt(data: Uint8Array): Promise<Uint8Array> {
|
|
49
|
+
const chacha = managedNonce(xchacha20poly1305)(await this.masterKeyProvider())
|
|
50
|
+
const encryptedData = chacha.encrypt(data)
|
|
51
|
+
|
|
52
|
+
return Promise.resolve(encryptedData)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async decrypt(data: Uint8Array): Promise<Uint8Array> {
|
|
56
|
+
const chacha = managedNonce(xchacha20poly1305)(await this.masterKeyProvider())
|
|
57
|
+
const decryptedData = chacha.decrypt(data)
|
|
58
|
+
|
|
59
|
+
return Promise.resolve(decryptedData)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface KeyObfuscationBackend {
|
|
64
|
+
/**
|
|
65
|
+
* Returns the obfuscated key for the given key.
|
|
66
|
+
*
|
|
67
|
+
* @param key The key to obfuscate, represented as a string.
|
|
68
|
+
*/
|
|
69
|
+
obfuscateKey(key: string): Promise<string>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The PassThroughKeyObfuscationBackend does not obfuscate the key and returns it as-is.
|
|
74
|
+
*
|
|
75
|
+
* This is useful for cases where no obfuscation is needed.
|
|
76
|
+
*/
|
|
77
|
+
export class PassThroughKeyObfuscationBackend implements KeyObfuscationBackend {
|
|
78
|
+
static readonly instance = new PassThroughKeyObfuscationBackend()
|
|
79
|
+
|
|
80
|
+
obfuscateKey(key: string): Promise<string> {
|
|
81
|
+
return Promise.resolve(key)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The NamespaceKeyObfuscationBackend obfuscates keys using a namespace derived from a hash function.
|
|
87
|
+
*
|
|
88
|
+
* It uses UUIDv5 to generate a consistent obfuscated key based on the provided key and namespace.
|
|
89
|
+
*/
|
|
90
|
+
export class NamespaceKeyObfuscationBackend implements KeyObfuscationBackend {
|
|
91
|
+
constructor(private readonly hashNamespaceProvider: () => Promise<string>) {}
|
|
92
|
+
|
|
93
|
+
async obfuscateKey(key: string): Promise<string> {
|
|
94
|
+
const namespace = await this.hashNamespaceProvider()
|
|
95
|
+
|
|
96
|
+
return uuidv5(key, namespace)
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/state/factory.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import type { StateBackend } from "./abstractions"
|
|
2
2
|
import type { Logger } from "pino"
|
|
3
|
-
import type { LocalPulumiHost } from "../common"
|
|
4
3
|
import { z } from "zod"
|
|
5
4
|
import { LocalStateBackend, localStateBackendConfig } from "./local"
|
|
6
5
|
|
|
7
6
|
export const stateBackendConfig = z.object({
|
|
8
|
-
|
|
7
|
+
HIGHSTATE_STATE_BACKEND_TYPE: z.enum(["local"]).default("local"),
|
|
9
8
|
...localStateBackendConfig.shape,
|
|
10
9
|
})
|
|
11
10
|
|
|
12
11
|
export function createStateBackend(
|
|
13
12
|
config: z.infer<typeof stateBackendConfig>,
|
|
14
|
-
localPulumiHost: LocalPulumiHost,
|
|
15
13
|
logger: Logger,
|
|
16
14
|
): Promise<StateBackend> {
|
|
17
|
-
switch (config.
|
|
15
|
+
switch (config.HIGHSTATE_STATE_BACKEND_TYPE) {
|
|
18
16
|
case "local": {
|
|
19
|
-
return LocalStateBackend.create(config,
|
|
17
|
+
return LocalStateBackend.create(config, logger)
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
}
|
package/src/state/index.ts
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Logger } from "pino"
|
|
2
|
+
import { AsyncEntry, findCredentialsAsync } from "@napi-rs/keyring"
|
|
3
|
+
import { generateIdentity } from "age-encryption"
|
|
4
|
+
|
|
5
|
+
const serviceName = "io.highstate.backend"
|
|
6
|
+
const accountName = "identity"
|
|
7
|
+
|
|
8
|
+
export async function getOrCreateBackendIdentity(logger: Logger): Promise<string> {
|
|
9
|
+
const credentials = await findCredentialsAsync(serviceName)
|
|
10
|
+
const entry = credentials.find(entry => entry.account === accountName)
|
|
11
|
+
|
|
12
|
+
if (entry) {
|
|
13
|
+
logger.debug("found existing backend identity in keyring")
|
|
14
|
+
return entry.password
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const newIdentity = await generateIdentity()
|
|
18
|
+
await new AsyncEntry(serviceName, accountName).setPassword(newIdentity)
|
|
19
|
+
|
|
20
|
+
logger.info("created new backend identity and stored it in the OS keyring")
|
|
21
|
+
return newIdentity
|
|
22
|
+
}
|