@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { TestTracer } from "../../common"
|
|
2
|
+
import {
|
|
3
|
+
type StateBackend,
|
|
4
|
+
type StateBatch,
|
|
5
|
+
type StateSnapshot,
|
|
6
|
+
type StateCollection,
|
|
7
|
+
type CollectionMap,
|
|
8
|
+
} from "../abstractions"
|
|
9
|
+
import { MemoryStateCollection, MemoryStateBatch, MemoryStateSnapshot } from "./collection"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A memory-based state backend implementation for testing purposes.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryStateBackend implements StateBackend {
|
|
15
|
+
private collections = new Map<string, MemoryStateCollection>()
|
|
16
|
+
private batchIdCounter = 0
|
|
17
|
+
private snapshotIdCounter = 0
|
|
18
|
+
|
|
19
|
+
constructor(private readonly tracer: TestTracer) {}
|
|
20
|
+
|
|
21
|
+
getEncryptedBackendMasterKey(): Promise<string | undefined> {
|
|
22
|
+
throw new Error("Method not implemented.")
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setEncryptedBackendMasterKey(): Promise<void> {
|
|
26
|
+
throw new Error("Method not implemented.")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getEncryptedBackendNamespace(): Promise<Uint8Array | undefined> {
|
|
30
|
+
throw new Error("Method not implemented.")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setEncryptedBackendNamespace(): Promise<void> {
|
|
34
|
+
throw new Error("Method not implemented.")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
createStateBatch(): StateBatch {
|
|
38
|
+
this.batchIdCounter += 1
|
|
39
|
+
return new MemoryStateBatch(this.batchIdCounter, this.tracer)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
createStateSnapshot(): StateSnapshot {
|
|
43
|
+
this.snapshotIdCounter += 1
|
|
44
|
+
// Create a snapshot of all existing collections
|
|
45
|
+
const allCollections = Array.from(this.collections.values())
|
|
46
|
+
return new MemoryStateSnapshot(allCollections, this.snapshotIdCounter, this.tracer)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
createCollection<TName extends keyof CollectionMap>(
|
|
50
|
+
name: TName,
|
|
51
|
+
params: CollectionMap[TName],
|
|
52
|
+
): StateCollection {
|
|
53
|
+
const fullName = [name, ...Object.values(params)].join(":")
|
|
54
|
+
|
|
55
|
+
const existingCollection = this.collections.get(fullName)
|
|
56
|
+
if (existingCollection) {
|
|
57
|
+
return existingCollection
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const collection = new MemoryStateCollection(
|
|
61
|
+
name,
|
|
62
|
+
params,
|
|
63
|
+
Promise.resolve(fullName), // this should be a UUIDv5 namespace, but for testing we will use the full name
|
|
64
|
+
this.tracer,
|
|
65
|
+
)
|
|
66
|
+
this.collections.set(fullName, collection)
|
|
67
|
+
|
|
68
|
+
return collection
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { CollectionQueryResult } from "../../shared"
|
|
2
|
+
import { linkTraceEntry, type TestTracer, type TraceEntry } from "../../common"
|
|
3
|
+
import {
|
|
4
|
+
CollectionBackend,
|
|
5
|
+
type StateCollection,
|
|
6
|
+
type StateBatch,
|
|
7
|
+
type StateSnapshot,
|
|
8
|
+
type CollectionQuery,
|
|
9
|
+
} from "../abstractions"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A memory-based state collection implementation for testing purposes.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryStateCollection implements StateCollection {
|
|
15
|
+
private data = new Map<string, Uint8Array>()
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
readonly name: string,
|
|
19
|
+
readonly params: Record<string, string>,
|
|
20
|
+
readonly namespace: Promise<string>,
|
|
21
|
+
readonly tracer: TestTracer,
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
get(id: string, snapshot?: StateSnapshot): Promise<Uint8Array | undefined> {
|
|
25
|
+
if (snapshot) {
|
|
26
|
+
const memorySnapshot = snapshot as unknown as MemoryStateSnapshot
|
|
27
|
+
const snapshotData = memorySnapshot.getCollection(this)
|
|
28
|
+
|
|
29
|
+
return Promise.resolve(snapshotData.get(id))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Promise.resolve(this.data.get(id))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getMany(ids: string[], snapshot?: StateSnapshot): Promise<(Uint8Array | undefined)[]> {
|
|
36
|
+
const source = snapshot
|
|
37
|
+
? (snapshot as unknown as MemoryStateSnapshot).getCollection(this)
|
|
38
|
+
: this.data
|
|
39
|
+
|
|
40
|
+
return Promise.resolve(ids.map(id => source.get(id)))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getAll(snapshot?: StateSnapshot): Promise<[string, Uint8Array][]> {
|
|
44
|
+
const source = snapshot
|
|
45
|
+
? (snapshot as unknown as MemoryStateSnapshot).getCollection(this)
|
|
46
|
+
: this.data
|
|
47
|
+
|
|
48
|
+
return Promise.resolve(Array.from(source.entries()))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
52
|
+
async query(
|
|
53
|
+
query: CollectionQuery,
|
|
54
|
+
snapshot?: StateSnapshot,
|
|
55
|
+
): Promise<CollectionQueryResult<[string, Uint8Array]>> {
|
|
56
|
+
const source = snapshot
|
|
57
|
+
? (snapshot as unknown as MemoryStateSnapshot).getCollection(this)
|
|
58
|
+
: this.data
|
|
59
|
+
|
|
60
|
+
let entries = Array.from(source.entries())
|
|
61
|
+
|
|
62
|
+
// apply cursor filtering
|
|
63
|
+
if (query.cursor) {
|
|
64
|
+
if (query.sort === "desc") {
|
|
65
|
+
entries = entries.filter(([id]) => id < query.cursor!)
|
|
66
|
+
} else {
|
|
67
|
+
entries = entries.filter(([id]) => id > query.cursor!)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// apply sorting
|
|
72
|
+
entries.sort(([a], [b]) => (query.sort === "desc" ? b.localeCompare(a) : a.localeCompare(b)))
|
|
73
|
+
|
|
74
|
+
// apply limit
|
|
75
|
+
const count = Math.min(query.count ?? 20, 100)
|
|
76
|
+
const limitedEntries = entries.slice(0, count)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
items: limitedEntries,
|
|
80
|
+
total: source.size,
|
|
81
|
+
nextCursor:
|
|
82
|
+
limitedEntries.length > 0 ? limitedEntries[limitedEntries.length - 1][0] : undefined,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getTotalCount(snapshot?: StateSnapshot): Promise<number> {
|
|
87
|
+
const source = snapshot
|
|
88
|
+
? (snapshot as unknown as MemoryStateSnapshot).getCollection(this)
|
|
89
|
+
: this.data
|
|
90
|
+
|
|
91
|
+
return Promise.resolve(source.size)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
95
|
+
async *iterate(snapshot?: StateSnapshot): AsyncIterable<[string, Uint8Array]> {
|
|
96
|
+
const source = snapshot
|
|
97
|
+
? (snapshot as unknown as MemoryStateSnapshot).getCollection(this)
|
|
98
|
+
: this.data
|
|
99
|
+
|
|
100
|
+
for (const entry of source.entries()) {
|
|
101
|
+
yield entry
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
put(id: string, value: Uint8Array, batch?: StateBatch): Promise<void> {
|
|
106
|
+
if (batch) {
|
|
107
|
+
const memoryBatch = batch as MemoryStateBatch
|
|
108
|
+
memoryBatch[CollectionBackend].push({ type: "put", collection: this, id, value })
|
|
109
|
+
return Promise.resolve()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.data.set(id, value)
|
|
113
|
+
return Promise.resolve()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
putMany(entries: Array<[string, Uint8Array]>, batch?: StateBatch): Promise<void> {
|
|
117
|
+
if (batch) {
|
|
118
|
+
const memoryBatch = batch as MemoryStateBatch
|
|
119
|
+
for (const [id, value] of entries) {
|
|
120
|
+
memoryBatch[CollectionBackend].push({ type: "put", collection: this, id, value })
|
|
121
|
+
}
|
|
122
|
+
return Promise.resolve()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const [id, value] of entries) {
|
|
126
|
+
this.data.set(id, value)
|
|
127
|
+
}
|
|
128
|
+
return Promise.resolve()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
delete(id: string, batch?: StateBatch): Promise<void> {
|
|
132
|
+
if (batch) {
|
|
133
|
+
const memoryBatch = batch as MemoryStateBatch
|
|
134
|
+
memoryBatch[CollectionBackend].push({ type: "delete", collection: this, id })
|
|
135
|
+
return Promise.resolve()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.data.delete(id)
|
|
139
|
+
return Promise.resolve()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
deleteMany(ids: string[], batch?: StateBatch): Promise<void> {
|
|
143
|
+
if (batch) {
|
|
144
|
+
const memoryBatch = batch as MemoryStateBatch
|
|
145
|
+
for (const id of ids) {
|
|
146
|
+
memoryBatch[CollectionBackend].push({ type: "delete", collection: this, id })
|
|
147
|
+
}
|
|
148
|
+
return Promise.resolve()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const id of ids) {
|
|
152
|
+
this.data.delete(id)
|
|
153
|
+
}
|
|
154
|
+
return Promise.resolve()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
clear(): Promise<void> {
|
|
158
|
+
this.data.clear()
|
|
159
|
+
return Promise.resolve()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface MemoryBatchOperation {
|
|
164
|
+
type: "put" | "delete"
|
|
165
|
+
collection: MemoryStateCollection
|
|
166
|
+
id: string
|
|
167
|
+
value?: Uint8Array
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class StateBatchEntry implements TraceEntry {
|
|
171
|
+
constructor(
|
|
172
|
+
readonly id: number,
|
|
173
|
+
private readonly opEntries: TraceEntry[],
|
|
174
|
+
) {}
|
|
175
|
+
|
|
176
|
+
render(): string {
|
|
177
|
+
return `💾 write ` + this.opEntries.map(linkTraceEntry).join(", ")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class StateSnapshotEntry implements TraceEntry {
|
|
182
|
+
constructor(
|
|
183
|
+
readonly id: number,
|
|
184
|
+
readonly usageEntries: TraceEntry[],
|
|
185
|
+
) {}
|
|
186
|
+
|
|
187
|
+
render(): string {
|
|
188
|
+
if (this.usageEntries.length === 0) {
|
|
189
|
+
return `📸 snapshot`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return `📸 snapshot, used by ` + this.usageEntries.map(linkTraceEntry).join(", ")
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class MemoryStateBatch implements StateBatch {
|
|
197
|
+
[CollectionBackend]: MemoryBatchOperation[] = []
|
|
198
|
+
|
|
199
|
+
constructor(
|
|
200
|
+
readonly id: number,
|
|
201
|
+
private readonly tracer: TestTracer,
|
|
202
|
+
) {}
|
|
203
|
+
|
|
204
|
+
// for testing purposes
|
|
205
|
+
readonly opEntries: TraceEntry[] = []
|
|
206
|
+
traceEntry?: TraceEntry
|
|
207
|
+
|
|
208
|
+
disposed = false
|
|
209
|
+
|
|
210
|
+
write(): Promise<void> {
|
|
211
|
+
if (this.disposed) {
|
|
212
|
+
return Promise.resolve()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
for (const operation of this[CollectionBackend]) {
|
|
216
|
+
if (operation.type === "put") {
|
|
217
|
+
operation.collection["data"].set(operation.id, operation.value!)
|
|
218
|
+
} else if (operation.type === "delete") {
|
|
219
|
+
operation.collection["data"].delete(operation.id)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.traceEntry = new StateBatchEntry(this.tracer.nextEntryId(), this.opEntries)
|
|
224
|
+
this.tracer.addEntry(this.traceEntry)
|
|
225
|
+
|
|
226
|
+
this.disposed = true
|
|
227
|
+
return Promise.resolve()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
[Symbol.asyncDispose](): Promise<void> {
|
|
231
|
+
if (this.disposed) {
|
|
232
|
+
return Promise.resolve()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.disposed = true
|
|
236
|
+
return Promise.resolve()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export class MemoryStateSnapshot implements StateSnapshot {
|
|
241
|
+
[CollectionBackend] = new Map<MemoryStateCollection, Map<string, Uint8Array>>()
|
|
242
|
+
|
|
243
|
+
readonly traceEntry: TraceEntry
|
|
244
|
+
readonly usageEntries: TraceEntry[] = []
|
|
245
|
+
|
|
246
|
+
constructor(
|
|
247
|
+
collections: MemoryStateCollection[],
|
|
248
|
+
readonly id: number,
|
|
249
|
+
readonly tracer: TestTracer,
|
|
250
|
+
) {
|
|
251
|
+
// create snapshots of all collections at this point in time
|
|
252
|
+
for (const collection of collections) {
|
|
253
|
+
this[CollectionBackend].set(collection, new Map(collection["data"]))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.traceEntry = new StateSnapshotEntry(tracer.nextEntryId(), this.usageEntries)
|
|
257
|
+
tracer.addEntry(this.traceEntry)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
getCollection(collection: MemoryStateCollection): Map<string, Uint8Array> {
|
|
261
|
+
const snapshot = this[CollectionBackend].get(collection)
|
|
262
|
+
|
|
263
|
+
return snapshot ?? new Map<string, Uint8Array>()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
[Symbol.asyncDispose](): Promise<void> {
|
|
267
|
+
this[CollectionBackend].clear()
|
|
268
|
+
return Promise.resolve()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { Logger } from "pino"
|
|
2
|
+
import type { StateRepository } from "./repository"
|
|
3
|
+
import type { CollectionQuery, StateSnapshot } from "../abstractions"
|
|
4
|
+
import type { CollectionQueryResult } from "../../shared"
|
|
5
|
+
import type { StateManager } from "../manager"
|
|
6
|
+
import { type z } from "zod"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The index value that indicates an object key is the same as the index key.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: DO NOT use this feature when the index repository has key hashing enabled.
|
|
12
|
+
*/
|
|
13
|
+
export const SAME_KEY = ""
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* StateIndexRepository provides read-only operations that combine two StateRepository instances.
|
|
17
|
+
*
|
|
18
|
+
* The index repository maps index keys to object keys, allowing indirect access to objects in the main repository.
|
|
19
|
+
*/
|
|
20
|
+
export class StateIndexRepository<TSchema extends z.ZodType> {
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly stateManager: StateManager,
|
|
23
|
+
public readonly repository: StateRepository<TSchema>,
|
|
24
|
+
public readonly indexRepository: StateRepository<z.ZodString>,
|
|
25
|
+
private readonly logger: Logger,
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Retrieves a single item by its index key.
|
|
30
|
+
*/
|
|
31
|
+
async get(indexKey: string): Promise<z.infer<TSchema> | undefined> {
|
|
32
|
+
const snapshot = this.stateManager.snapshot()
|
|
33
|
+
|
|
34
|
+
// fetch the object key from the index repository
|
|
35
|
+
const objectKey = await this.indexRepository.get(indexKey, snapshot)
|
|
36
|
+
if (!objectKey) {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// if index value is empty string, use the index key itself
|
|
41
|
+
const resolvedObjectId = objectKey === SAME_KEY ? indexKey : objectKey
|
|
42
|
+
|
|
43
|
+
const item = await this.repository.get(resolvedObjectId, snapshot)
|
|
44
|
+
if (item === undefined) {
|
|
45
|
+
this.logger.warn({ indexKey, resolvedObjectId }, "index points to non-existent object")
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return item
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves multiple items by their index keys, returning an array of records.
|
|
54
|
+
*
|
|
55
|
+
* NOTE: the keys of the records are the index keys, not the object keys.
|
|
56
|
+
*/
|
|
57
|
+
async getMany(indexKeys: string[]): Promise<[string, z.infer<TSchema>][]> {
|
|
58
|
+
if (indexKeys.length === 0) return []
|
|
59
|
+
|
|
60
|
+
const snapshot = this.stateManager.snapshot()
|
|
61
|
+
|
|
62
|
+
// get all index records for the provided keys
|
|
63
|
+
const indexRecords = await this.indexRepository.getMany(indexKeys, snapshot)
|
|
64
|
+
if (indexRecords.length === 0) return []
|
|
65
|
+
|
|
66
|
+
// resolve index records to get the actual objects
|
|
67
|
+
return this.resolveIndexEntries(indexRecords, snapshot)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Retrieves multiple items by their index keys, returning a record of objects.
|
|
72
|
+
*
|
|
73
|
+
* NOTE: the keys of the record are the index keys, not the object keys.
|
|
74
|
+
*/
|
|
75
|
+
async getManyRecord(indexKeys: string[]): Promise<Record<string, z.infer<TSchema> | undefined>> {
|
|
76
|
+
const records = await this.getMany(indexKeys)
|
|
77
|
+
|
|
78
|
+
return Object.fromEntries(records)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Retrieves multiple items by their index keys, returning an array of objects.
|
|
83
|
+
*/
|
|
84
|
+
async getManyItems(indexKeys: string[]): Promise<z.infer<TSchema>[]> {
|
|
85
|
+
const records = await this.getMany(indexKeys)
|
|
86
|
+
|
|
87
|
+
return Object.values(records)
|
|
88
|
+
.filter(item => item !== null)
|
|
89
|
+
.map(([, item]) => item)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Retrieves all items that have index entries, returning an array of records.
|
|
94
|
+
*
|
|
95
|
+
* NOTE: the keys of the records are the index keys, not the object keys.
|
|
96
|
+
*/
|
|
97
|
+
async getAll(): Promise<Array<[string, z.infer<TSchema>]>> {
|
|
98
|
+
const snapshot = this.stateManager.snapshot()
|
|
99
|
+
|
|
100
|
+
// get all index records
|
|
101
|
+
const indexEntries = await this.indexRepository.getAll(snapshot)
|
|
102
|
+
if (indexEntries.length === 0) return []
|
|
103
|
+
|
|
104
|
+
// resolve index entries to get the actual objects
|
|
105
|
+
return this.resolveIndexEntries(indexEntries, snapshot)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Retrieves all items that have index entries, returning a record of objects.
|
|
110
|
+
*
|
|
111
|
+
* NOTE: the keys of the record are the index keys, not the object keys.
|
|
112
|
+
*/
|
|
113
|
+
async getAllRecord(): Promise<Record<string, z.infer<TSchema> | null>> {
|
|
114
|
+
const allEntries = await this.getAll()
|
|
115
|
+
|
|
116
|
+
// convert the array of entries to a record
|
|
117
|
+
return Object.fromEntries(allEntries)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Retrieves all items that have index entries.
|
|
122
|
+
* Iterates through the index and returns all matched objects.
|
|
123
|
+
*/
|
|
124
|
+
async getAllItems(): Promise<z.infer<TSchema>[]> {
|
|
125
|
+
const allEntries = await this.getAll()
|
|
126
|
+
|
|
127
|
+
return allEntries.map(([, item]) => item)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Queries the index repository and retrieves items from the main repository.
|
|
132
|
+
*/
|
|
133
|
+
async query(query: CollectionQuery): Promise<CollectionQueryResult<[string, z.infer<TSchema>]>> {
|
|
134
|
+
const snapshot = this.stateManager.snapshot()
|
|
135
|
+
|
|
136
|
+
// first, resolve the query to get the index keys
|
|
137
|
+
const result = await this.indexRepository.query(query, snapshot)
|
|
138
|
+
|
|
139
|
+
// then resolve the index entries to get the actual objects
|
|
140
|
+
const items = await this.resolveIndexEntries(result.items, snapshot)
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
total: result.total,
|
|
144
|
+
items,
|
|
145
|
+
nextCursor: result.nextCursor,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Queries the index repository and retrieves items from the main repository, returning an array of objects.
|
|
151
|
+
*/
|
|
152
|
+
async queryItems(query: CollectionQuery): Promise<CollectionQueryResult<z.infer<TSchema>>> {
|
|
153
|
+
const result = await this.query(query)
|
|
154
|
+
|
|
155
|
+
// extract the items from the result
|
|
156
|
+
return {
|
|
157
|
+
total: result.total,
|
|
158
|
+
items: result.items.map(([, item]) => item),
|
|
159
|
+
nextCursor: result.nextCursor,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private async resolveIndexEntries(
|
|
164
|
+
indexEntries: [string, string][],
|
|
165
|
+
snapshot: StateSnapshot,
|
|
166
|
+
): Promise<[string, z.infer<TSchema>][]> {
|
|
167
|
+
// collect all object keys that were found
|
|
168
|
+
const objectKeys: string[] = []
|
|
169
|
+
for (const [indexKey, objectId] of indexEntries) {
|
|
170
|
+
// if index value is empty string, use the index key itself
|
|
171
|
+
const resolvedObjectId = objectId === SAME_KEY ? indexKey : objectId
|
|
172
|
+
objectKeys.push(resolvedObjectId)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// get objects from main repository
|
|
176
|
+
const objectRecord = await this.repository.getManyRecord(objectKeys, snapshot)
|
|
177
|
+
const result: [string, z.infer<TSchema>][] = []
|
|
178
|
+
|
|
179
|
+
for (const [indexKey, objectKey] of indexEntries) {
|
|
180
|
+
const resolvedObjectId = objectKey === SAME_KEY ? indexKey : objectKey
|
|
181
|
+
const item = objectRecord[resolvedObjectId]
|
|
182
|
+
|
|
183
|
+
if (item === undefined) {
|
|
184
|
+
this.logger.warn({ indexKey, resolvedObjectId }, "index points to non-existent object")
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
result.push([indexKey, item])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
}
|