@highstate/backend 0.19.1 → 0.20.0
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-V2NILDHS.js → chunk-52MY2TCE.js} +347 -19
- package/dist/chunk-52MY2TCE.js.map +1 -0
- package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
- package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
- package/dist/highstate.manifest.json +4 -4
- package/dist/index.js +4159 -785
- package/dist/index.js.map +1 -1
- package/dist/library/worker/main.js +5 -2
- package/dist/library/worker/main.js.map +1 -1
- package/dist/shared/index.js +2 -2
- package/package.json +7 -7
- package/prisma/backend/_schema/object.prisma +12 -0
- package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
- package/prisma/project/artifact.prisma +3 -0
- package/prisma/project/entity.prisma +125 -0
- package/prisma/project/instance.prisma +6 -0
- package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
- package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
- package/prisma/project/operation.prisma +3 -0
- package/src/business/artifact.test.ts +22 -2
- package/src/business/artifact.ts +7 -1
- package/src/business/entity-snapshot.test.ts +684 -0
- package/src/business/entity-snapshot.ts +904 -0
- package/src/business/evaluation.test.ts +56 -0
- package/src/business/evaluation.ts +102 -22
- package/src/business/global-search.test.ts +344 -0
- package/src/business/global-search.ts +902 -0
- package/src/business/index.ts +4 -0
- package/src/business/instance-lock.ts +58 -74
- package/src/business/instance-state.test.ts +15 -1
- package/src/business/instance-state.ts +37 -14
- package/src/business/object-ref-index.test.ts +140 -0
- package/src/business/object-ref-index.ts +193 -0
- package/src/business/operation.test.ts +15 -1
- package/src/business/operation.ts +4 -0
- package/src/business/project-model.ts +154 -13
- package/src/business/project-unlock.ts +25 -2
- package/src/business/project.ts +9 -0
- package/src/business/secret.test.ts +35 -2
- package/src/business/secret.ts +32 -9
- package/src/business/settings.ts +761 -0
- package/src/business/unit-output.test.ts +477 -0
- package/src/business/unit-output.ts +461 -0
- package/src/business/worker.ts +55 -4
- package/src/database/_generated/backend/postgresql/browser.ts +6 -0
- package/src/database/_generated/backend/postgresql/client.ts +6 -0
- package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
- package/src/database/_generated/backend/postgresql/models.ts +1 -0
- package/src/database/_generated/backend/sqlite/browser.ts +6 -0
- package/src/database/_generated/backend/sqlite/client.ts +6 -0
- package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
- package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
- package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
- package/src/database/_generated/backend/sqlite/models.ts +1 -0
- package/src/database/_generated/project/browser.ts +23 -0
- package/src/database/_generated/project/client.ts +23 -0
- package/src/database/_generated/project/commonInputTypes.ts +87 -53
- package/src/database/_generated/project/enums.ts +8 -0
- package/src/database/_generated/project/internal/class.ts +53 -5
- package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
- package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
- package/src/database/_generated/project/models/Artifact.ts +199 -11
- package/src/database/_generated/project/models/Entity.ts +1274 -0
- package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
- package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
- package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
- package/src/database/_generated/project/models/InstanceState.ts +361 -1
- package/src/database/_generated/project/models/Operation.ts +148 -3
- package/src/database/_generated/project/models/OperationLog.ts +0 -4
- package/src/database/_generated/project/models.ts +4 -0
- package/src/database/migration.ts +3 -0
- package/src/library/worker/evaluator.ts +7 -1
- package/src/orchestrator/manager.ts +7 -0
- package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
- package/src/orchestrator/operation-context.ts +154 -16
- package/src/orchestrator/operation-plan.destroy.test.md +33 -12
- package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
- package/src/orchestrator/operation-plan.fixtures.ts +2 -0
- package/src/orchestrator/operation-plan.md +4 -1
- package/src/orchestrator/operation-plan.ts +286 -92
- package/src/orchestrator/operation-plan.update.test.md +286 -11
- package/src/orchestrator/operation-plan.update.test.ts +656 -5
- package/src/orchestrator/operation-workset.ts +72 -22
- package/src/orchestrator/operation.cancel.test.ts +4 -0
- package/src/orchestrator/operation.composite.test.ts +341 -0
- package/src/orchestrator/operation.destroy.test.ts +4 -0
- package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
- package/src/orchestrator/operation.preview.test.ts +4 -0
- package/src/orchestrator/operation.refresh.test.ts +4 -0
- package/src/orchestrator/operation.test-utils.ts +52 -13
- package/src/orchestrator/operation.ts +228 -68
- package/src/orchestrator/operation.update.failure.test.ts +4 -0
- package/src/orchestrator/operation.update.skip.test.ts +110 -0
- package/src/orchestrator/operation.update.test.ts +4 -0
- package/src/orchestrator/plan-test-builder.ts +1 -0
- package/src/orchestrator/unit-input-values.test.ts +450 -0
- package/src/orchestrator/unit-input-values.ts +281 -0
- package/src/pubsub/manager.ts +3 -0
- package/src/runner/abstractions.ts +23 -54
- package/src/runner/local.ts +109 -85
- package/src/services.ts +52 -1
- package/src/shared/models/prisma.ts +1 -0
- package/src/shared/models/project/entity.ts +121 -0
- package/src/shared/models/project/index.ts +1 -0
- package/src/shared/models/project/operation.ts +61 -3
- package/src/shared/models/project/state.ts +10 -0
- package/src/shared/models/project/worker.ts +7 -0
- package/src/shared/resolvers/effective-output-type.test.ts +494 -0
- package/src/shared/resolvers/effective-output-type.ts +162 -0
- package/src/shared/resolvers/index.ts +1 -0
- package/src/shared/resolvers/input.ts +59 -9
- package/src/shared/utils/index.ts +1 -0
- package/src/shared/utils/stable-json.ts +41 -0
- package/src/terminal/manager.ts +6 -0
- package/src/worker/manager.ts +97 -1
- package/dist/chunk-V2NILDHS.js.map +0 -1
package/src/business/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from "./api-key"
|
|
2
2
|
export * from "./artifact"
|
|
3
3
|
export * from "./backend-unlock"
|
|
4
|
+
export * from "./entity-snapshot"
|
|
5
|
+
export * from "./global-search"
|
|
4
6
|
export * from "./instance-lock"
|
|
5
7
|
export * from "./instance-state"
|
|
8
|
+
export * from "./object-ref-index"
|
|
6
9
|
export * from "./operation"
|
|
7
10
|
export * from "./project"
|
|
8
11
|
export * from "./project-model"
|
|
@@ -11,4 +14,5 @@ export * from "./secret"
|
|
|
11
14
|
export * from "./settings"
|
|
12
15
|
export * from "./terminal-session"
|
|
13
16
|
export * from "./unit-extra"
|
|
17
|
+
export * from "./unit-output"
|
|
14
18
|
export * from "./worker"
|
|
@@ -260,79 +260,59 @@ export class InstanceLockService {
|
|
|
260
260
|
let remainingStateIds = [...stateIds]
|
|
261
261
|
const lockedStateIds: string[] = []
|
|
262
262
|
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
while (remainingStateIds.length > 0) {
|
|
264
|
+
if (abortSignal?.aborted) {
|
|
265
|
+
throw new Error("Lock operation was aborted")
|
|
266
|
+
}
|
|
265
267
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
this.logger.debug(
|
|
269
|
+
{
|
|
270
|
+
projectId,
|
|
271
|
+
remainingCount: remainingStateIds.length,
|
|
272
|
+
lockedCount: lockedStateIds.length,
|
|
273
|
+
},
|
|
274
|
+
"attempting to lock %s remaining instances",
|
|
275
|
+
remainingStateIds.length,
|
|
276
|
+
)
|
|
271
277
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
// try to acquire locks on remaining instances using the same token
|
|
279
|
+
const [_, newlyLockedStateIds] = await this.tryLockInstances(
|
|
280
|
+
projectId,
|
|
281
|
+
remainingStateIds,
|
|
282
|
+
lockMeta,
|
|
283
|
+
action,
|
|
284
|
+
allowPartialLock,
|
|
285
|
+
token,
|
|
286
|
+
)
|
|
277
287
|
|
|
288
|
+
if (newlyLockedStateIds.length === 0) {
|
|
289
|
+
// no instances were locked, wait for unlock events
|
|
278
290
|
this.logger.debug(
|
|
279
|
-
{
|
|
280
|
-
|
|
281
|
-
remainingCount: remainingStateIds.length,
|
|
282
|
-
lockedCount: lockedStateIds.length,
|
|
283
|
-
},
|
|
284
|
-
"attempting to lock %s remaining instances",
|
|
291
|
+
{ projectId, remainingCount: remainingStateIds.length },
|
|
292
|
+
"waiting for unlock events for %s remaining instances",
|
|
285
293
|
remainingStateIds.length,
|
|
286
294
|
)
|
|
287
295
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
remainingStateIds,
|
|
292
|
-
lockMeta,
|
|
293
|
-
action,
|
|
294
|
-
allowPartialLock,
|
|
295
|
-
token,
|
|
296
|
-
)
|
|
296
|
+
await this.waitForUnlockEvent(projectId, remainingStateIds, abortSignal, eventWaitTime)
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
297
299
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{ projectId, remainingCount: remainingStateIds.length },
|
|
302
|
-
"waiting for unlock events for %s remaining instances",
|
|
303
|
-
remainingStateIds.length,
|
|
304
|
-
)
|
|
300
|
+
// remove newly locked instances from remaining list
|
|
301
|
+
remainingStateIds = remainingStateIds.filter(id => !newlyLockedStateIds.includes(id))
|
|
302
|
+
lockedStateIds.push(...newlyLockedStateIds)
|
|
305
303
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// remove newly locked instances from remaining list
|
|
317
|
-
remainingStateIds = remainingStateIds.filter(id => !newlyLockedStateIds.includes(id))
|
|
318
|
-
lockedStateIds.push(...newlyLockedStateIds)
|
|
319
|
-
|
|
320
|
-
// if partial locking is not allowed, we should have all instances by now
|
|
321
|
-
if (!allowPartialLock && remainingStateIds.length > 0) {
|
|
322
|
-
this.logger.error(
|
|
323
|
-
{ projectId, remaining: remainingStateIds.length },
|
|
324
|
-
"partial lock not allowed but %s instances remain unlocked",
|
|
325
|
-
remainingStateIds.length,
|
|
326
|
-
)
|
|
327
|
-
throw new Error("Failed to acquire all required locks")
|
|
328
|
-
}
|
|
304
|
+
// if partial locking is not allowed, we should have all instances by now
|
|
305
|
+
if (!allowPartialLock && remainingStateIds.length > 0) {
|
|
306
|
+
this.logger.error(
|
|
307
|
+
{ projectId, remaining: remainingStateIds.length },
|
|
308
|
+
"partial lock not allowed but %s instances remain unlocked",
|
|
309
|
+
remainingStateIds.length,
|
|
310
|
+
)
|
|
311
|
+
throw new Error("Failed to acquire all required locks")
|
|
329
312
|
}
|
|
330
|
-
|
|
331
|
-
return [token, lockedStateIds]
|
|
332
|
-
} finally {
|
|
333
|
-
// clean up event subscription
|
|
334
|
-
subscriptionController.abort()
|
|
335
313
|
}
|
|
314
|
+
|
|
315
|
+
return [token, lockedStateIds]
|
|
336
316
|
}
|
|
337
317
|
|
|
338
318
|
/**
|
|
@@ -341,32 +321,36 @@ export class InstanceLockService {
|
|
|
341
321
|
*
|
|
342
322
|
* @param projectId The project ID to monitor for events.
|
|
343
323
|
* @param stateIds The state IDs we're waiting to become available.
|
|
344
|
-
* @param eventIterable The async iterable for event subscription.
|
|
345
324
|
* @param abortSignal Optional abort signal to interrupt waiting.
|
|
346
325
|
* @param eventWaitTime Time in milliseconds to wait before timing out and retrying.
|
|
347
326
|
*/
|
|
348
327
|
private async waitForUnlockEvent(
|
|
349
328
|
projectId: string,
|
|
350
329
|
stateIds: string[],
|
|
351
|
-
eventIterable: AsyncIterable<InstanceLockEvent>,
|
|
352
330
|
abortSignal?: AbortSignal,
|
|
353
331
|
eventWaitTime = 60000,
|
|
354
332
|
): Promise<void> {
|
|
355
|
-
const
|
|
333
|
+
const waitController = new AbortController()
|
|
334
|
+
const eventIterable = await this.pubsubManager.subscribe(
|
|
335
|
+
["instance-lock", projectId],
|
|
336
|
+
waitController.signal,
|
|
337
|
+
)
|
|
356
338
|
|
|
357
339
|
// combine abort signals
|
|
358
340
|
if (abortSignal?.aborted) {
|
|
359
341
|
throw new Error("Lock operation was aborted")
|
|
360
342
|
}
|
|
361
343
|
|
|
362
|
-
const abortHandler = () =>
|
|
344
|
+
const abortHandler = () => waitController.abort()
|
|
345
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
346
|
+
|
|
363
347
|
abortSignal?.addEventListener("abort", abortHandler)
|
|
364
348
|
|
|
365
349
|
try {
|
|
366
350
|
await Promise.race([
|
|
367
351
|
// timeout promise - triggers retry attempt, does not abort
|
|
368
352
|
new Promise<void>(resolve => {
|
|
369
|
-
setTimeout(() => {
|
|
353
|
+
timeoutId = setTimeout(() => {
|
|
370
354
|
this.logger.debug(
|
|
371
355
|
{ projectId, eventWaitTime },
|
|
372
356
|
"unlock wait timed out after %s ms, will retry",
|
|
@@ -377,7 +361,7 @@ export class InstanceLockService {
|
|
|
377
361
|
}),
|
|
378
362
|
|
|
379
363
|
// event listener promise
|
|
380
|
-
this.listenForUnlockEvents(projectId, stateIds, eventIterable
|
|
364
|
+
this.listenForUnlockEvents(projectId, stateIds, eventIterable),
|
|
381
365
|
|
|
382
366
|
// abort promise - only this can interrupt the operation
|
|
383
367
|
new Promise<void>((_, reject) => {
|
|
@@ -389,7 +373,12 @@ export class InstanceLockService {
|
|
|
389
373
|
}),
|
|
390
374
|
])
|
|
391
375
|
} finally {
|
|
392
|
-
|
|
376
|
+
waitController.abort()
|
|
377
|
+
|
|
378
|
+
if (timeoutId) {
|
|
379
|
+
clearTimeout(timeoutId)
|
|
380
|
+
}
|
|
381
|
+
|
|
393
382
|
abortSignal?.removeEventListener("abort", abortHandler)
|
|
394
383
|
}
|
|
395
384
|
}
|
|
@@ -400,18 +389,13 @@ export class InstanceLockService {
|
|
|
400
389
|
* @param projectId The project ID to monitor for events.
|
|
401
390
|
* @param stateIds The state IDs we're waiting to become available.
|
|
402
391
|
* @param eventIterable The async iterable for event subscription.
|
|
403
|
-
* @param signal Abort signal to stop listening.
|
|
404
392
|
*/
|
|
405
393
|
private async listenForUnlockEvents(
|
|
406
394
|
projectId: string,
|
|
407
395
|
stateIds: string[],
|
|
408
396
|
eventIterable: AsyncIterable<InstanceLockEvent>,
|
|
409
|
-
signal: AbortSignal,
|
|
410
397
|
): Promise<void> {
|
|
411
398
|
for await (const event of eventIterable) {
|
|
412
|
-
if (signal.aborted) {
|
|
413
|
-
break
|
|
414
|
-
}
|
|
415
399
|
if (event.type !== "unlocked") {
|
|
416
400
|
continue // only interested in unlock events
|
|
417
401
|
}
|
|
@@ -2,6 +2,7 @@ import type { InstanceId, InstanceModel } from "@highstate/contract"
|
|
|
2
2
|
import type { ArtifactService } from "../artifact"
|
|
3
3
|
import type { PubSubManager } from "../pubsub"
|
|
4
4
|
import type { RunnerBackend } from "../runner"
|
|
5
|
+
import type { ObjectRefIndexService } from "./object-ref-index"
|
|
5
6
|
import type { ProjectService } from "./project"
|
|
6
7
|
import type { SecretService } from "./secret"
|
|
7
8
|
import type { UnitExtraService } from "./unit-extra"
|
|
@@ -21,6 +22,7 @@ const instanceStateTest = test.extend<{
|
|
|
21
22
|
artifactService: MockedObject<ArtifactService>
|
|
22
23
|
unitExtraService: MockedObject<UnitExtraService>
|
|
23
24
|
secretService: MockedObject<SecretService>
|
|
25
|
+
objectRefIndexService: MockedObject<ObjectRefIndexService>
|
|
24
26
|
instanceStateService: InstanceStateService
|
|
25
27
|
}>({
|
|
26
28
|
pubsubManager: async ({}, use) => {
|
|
@@ -52,6 +54,7 @@ const instanceStateTest = test.extend<{
|
|
|
52
54
|
workerService: async ({}, use) => {
|
|
53
55
|
const workerService = vi.mockObject({
|
|
54
56
|
cleanupWorkerUsageAndSync: vi.fn().mockResolvedValue(undefined),
|
|
57
|
+
updateUnitRegistrations: vi.fn().mockResolvedValue([]),
|
|
55
58
|
} as unknown as WorkerService)
|
|
56
59
|
|
|
57
60
|
await use(workerService)
|
|
@@ -76,10 +79,19 @@ const instanceStateTest = test.extend<{
|
|
|
76
79
|
},
|
|
77
80
|
|
|
78
81
|
secretService: async ({}, use) => {
|
|
79
|
-
const secretService = vi.mockObject({
|
|
82
|
+
const secretService = vi.mockObject({
|
|
83
|
+
updateInstanceSecretsCore: vi.fn().mockResolvedValue({ secretNames: [], secretIds: [] }),
|
|
84
|
+
} as unknown as SecretService)
|
|
80
85
|
await use(secretService)
|
|
81
86
|
},
|
|
82
87
|
|
|
88
|
+
objectRefIndexService: async ({}, use) => {
|
|
89
|
+
const objectRefIndexService = vi.mockObject({
|
|
90
|
+
track: vi.fn().mockResolvedValue(undefined),
|
|
91
|
+
} as unknown as ObjectRefIndexService)
|
|
92
|
+
await use(objectRefIndexService)
|
|
93
|
+
},
|
|
94
|
+
|
|
83
95
|
instanceStateService: async (
|
|
84
96
|
{
|
|
85
97
|
database,
|
|
@@ -89,6 +101,7 @@ const instanceStateTest = test.extend<{
|
|
|
89
101
|
artifactService,
|
|
90
102
|
unitExtraService,
|
|
91
103
|
secretService,
|
|
104
|
+
objectRefIndexService,
|
|
92
105
|
logger,
|
|
93
106
|
},
|
|
94
107
|
use,
|
|
@@ -101,6 +114,7 @@ const instanceStateTest = test.extend<{
|
|
|
101
114
|
artifactService,
|
|
102
115
|
unitExtraService,
|
|
103
116
|
secretService,
|
|
117
|
+
objectRefIndexService,
|
|
104
118
|
logger.child({ service: "InstanceStateService" }),
|
|
105
119
|
)
|
|
106
120
|
|
|
@@ -10,6 +10,7 @@ import type { ArtifactService } from "../artifact"
|
|
|
10
10
|
import type { SecretService, UnitExtraService, WorkerService } from "../business"
|
|
11
11
|
import type { PubSubManager } from "../pubsub"
|
|
12
12
|
import type { RunnerBackend } from "../runner"
|
|
13
|
+
import type { ObjectRefIndexService } from "./object-ref-index"
|
|
13
14
|
import { type InstanceId, parseInstanceId } from "@highstate/contract"
|
|
14
15
|
import { isNonNullish, omit } from "remeda"
|
|
15
16
|
import {
|
|
@@ -103,6 +104,7 @@ export type InstanceStatePatch = Pick<
|
|
|
103
104
|
| "resolvedInputs"
|
|
104
105
|
| "currentResourceCount"
|
|
105
106
|
| "exportedArtifactIds"
|
|
107
|
+
| "hasResourceHooks"
|
|
106
108
|
>
|
|
107
109
|
|
|
108
110
|
export type UpdateOperationStateOptions = {
|
|
@@ -220,6 +222,7 @@ export class InstanceStateService {
|
|
|
220
222
|
private readonly artifactService: ArtifactService,
|
|
221
223
|
private readonly unitExtraService: UnitExtraService,
|
|
222
224
|
private readonly secretService: SecretService,
|
|
225
|
+
private readonly objectRefIndexService: ObjectRefIndexService,
|
|
223
226
|
private readonly logger: Logger,
|
|
224
227
|
) {}
|
|
225
228
|
|
|
@@ -667,6 +670,7 @@ export class InstanceStateService {
|
|
|
667
670
|
|
|
668
671
|
const result = await database.$transaction(async tx => {
|
|
669
672
|
let unitExtraData = null
|
|
673
|
+
let unitExtraTrackingIds: string[] = []
|
|
670
674
|
|
|
671
675
|
// update operation state
|
|
672
676
|
const updatedOperationState = await tx.instanceOperationState.update({
|
|
@@ -690,20 +694,35 @@ export class InstanceStateService {
|
|
|
690
694
|
|
|
691
695
|
// update unit-specific data if provided
|
|
692
696
|
if (unitExtra) {
|
|
693
|
-
const [pageIds, terminalIds, triggerIds,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
697
|
+
const [pageIds, terminalIds, triggerIds, secretUpdate, workerObjectIds] = await Promise.all(
|
|
698
|
+
[
|
|
699
|
+
this.unitExtraService.processUnitPages(tx, stateId, unitExtra.pages),
|
|
700
|
+
this.unitExtraService.processUnitTerminals(tx, stateId, unitExtra.terminals),
|
|
701
|
+
this.unitExtraService.processUnitTriggers(tx, stateId, unitExtra.triggers),
|
|
702
|
+
this.secretService.updateInstanceSecretsCore(
|
|
703
|
+
tx,
|
|
704
|
+
project.libraryId,
|
|
705
|
+
stateId,
|
|
706
|
+
unitExtra.secrets,
|
|
707
|
+
),
|
|
708
|
+
this.workerService.updateUnitRegistrations(tx, projectId, stateId, unitExtra.workers),
|
|
709
|
+
],
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
unitExtraData = {
|
|
713
|
+
pageIds,
|
|
714
|
+
terminalIds,
|
|
715
|
+
triggerIds,
|
|
716
|
+
secretNames: secretUpdate.secretNames,
|
|
717
|
+
}
|
|
705
718
|
|
|
706
|
-
|
|
719
|
+
unitExtraTrackingIds = [
|
|
720
|
+
...pageIds,
|
|
721
|
+
...terminalIds,
|
|
722
|
+
...triggerIds,
|
|
723
|
+
...secretUpdate.secretIds,
|
|
724
|
+
...workerObjectIds,
|
|
725
|
+
]
|
|
707
726
|
|
|
708
727
|
if (unitExtra.artifactIds !== undefined) {
|
|
709
728
|
await this.unitExtraService.pruneInstanceArtifacts(tx, stateId, unitExtra.artifactIds)
|
|
@@ -718,9 +737,13 @@ export class InstanceStateService {
|
|
|
718
737
|
})
|
|
719
738
|
}
|
|
720
739
|
|
|
721
|
-
return { updatedOperationState, unitExtraData }
|
|
740
|
+
return { updatedOperationState, unitExtraData, unitExtraTrackingIds }
|
|
722
741
|
})
|
|
723
742
|
|
|
743
|
+
if (result.unitExtraTrackingIds.length > 0) {
|
|
744
|
+
await this.objectRefIndexService.track(projectId, result.unitExtraTrackingIds)
|
|
745
|
+
}
|
|
746
|
+
|
|
724
747
|
if (options.unitExtra?.artifactIds !== undefined) {
|
|
725
748
|
await this.artifactService.collectGarbage(projectId)
|
|
726
749
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { createId } from "@paralleldrive/cuid2"
|
|
2
|
+
import { describe, expect } from "vitest"
|
|
3
|
+
import { test } from "../test-utils/services"
|
|
4
|
+
import { ObjectRefIndexService } from "./object-ref-index"
|
|
5
|
+
|
|
6
|
+
describe(ObjectRefIndexService.name, () => {
|
|
7
|
+
test("track inserts unique ids and ignores blanks", async ({ database, project, logger }) => {
|
|
8
|
+
const service = new ObjectRefIndexService(database, logger)
|
|
9
|
+
|
|
10
|
+
const id1 = createId()
|
|
11
|
+
const id2 = createId()
|
|
12
|
+
|
|
13
|
+
await service.track(project.id, [id1, ` ${id1} `, "", " ", id2])
|
|
14
|
+
await service.track(project.id, [id1, id2])
|
|
15
|
+
|
|
16
|
+
const refs = await database.backend.object.findMany({
|
|
17
|
+
where: { projectId: project.id },
|
|
18
|
+
select: { id: true },
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
expect(refs.map(r => r.id).sort()).toEqual([id1, id2].sort())
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("syncProject indexes curated object ids", async ({
|
|
25
|
+
database,
|
|
26
|
+
projectDatabase,
|
|
27
|
+
project,
|
|
28
|
+
logger,
|
|
29
|
+
}) => {
|
|
30
|
+
const service = new ObjectRefIndexService(database, logger)
|
|
31
|
+
|
|
32
|
+
const operation = await projectDatabase.operation.create({
|
|
33
|
+
data: {
|
|
34
|
+
meta: { title: "op" },
|
|
35
|
+
type: "update",
|
|
36
|
+
options: {},
|
|
37
|
+
requestedInstanceIds: [],
|
|
38
|
+
},
|
|
39
|
+
select: { id: true },
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const state = await projectDatabase.instanceState.create({
|
|
43
|
+
data: {
|
|
44
|
+
instanceId: `component.v1:${createId()}`,
|
|
45
|
+
kind: "unit",
|
|
46
|
+
status: "undeployed",
|
|
47
|
+
source: "resident",
|
|
48
|
+
},
|
|
49
|
+
select: { id: true },
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const artifact = await projectDatabase.artifact.create({
|
|
53
|
+
data: {
|
|
54
|
+
meta: { title: "artifact" },
|
|
55
|
+
hash: `sha256:${createId()}`,
|
|
56
|
+
size: 1,
|
|
57
|
+
chunkSize: 1,
|
|
58
|
+
},
|
|
59
|
+
select: { id: true },
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const page = await projectDatabase.page.create({
|
|
63
|
+
data: {
|
|
64
|
+
meta: { title: "page" },
|
|
65
|
+
content: [],
|
|
66
|
+
},
|
|
67
|
+
select: { id: true },
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const secret = await projectDatabase.secret.create({
|
|
71
|
+
data: {
|
|
72
|
+
meta: { title: "secret" },
|
|
73
|
+
content: { value: "x" },
|
|
74
|
+
},
|
|
75
|
+
select: { id: true },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const serviceAccount = await projectDatabase.serviceAccount.create({
|
|
79
|
+
data: {
|
|
80
|
+
meta: { title: "sa" },
|
|
81
|
+
},
|
|
82
|
+
select: { id: true },
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const apiKey = await projectDatabase.apiKey.create({
|
|
86
|
+
data: {
|
|
87
|
+
meta: { title: "key" },
|
|
88
|
+
serviceAccountId: serviceAccount.id,
|
|
89
|
+
token: createId(),
|
|
90
|
+
},
|
|
91
|
+
select: { id: true },
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
await service.syncProject(project.id)
|
|
95
|
+
|
|
96
|
+
const refs = await database.backend.object.findMany({
|
|
97
|
+
where: { projectId: project.id },
|
|
98
|
+
select: { id: true },
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const refIds = new Set(refs.map(r => r.id))
|
|
102
|
+
expect(refIds).toContain(operation.id)
|
|
103
|
+
expect(refIds).toContain(state.id)
|
|
104
|
+
expect(refIds).toContain(artifact.id)
|
|
105
|
+
expect(refIds).toContain(page.id)
|
|
106
|
+
expect(refIds).toContain(secret.id)
|
|
107
|
+
expect(refIds).toContain(serviceAccount.id)
|
|
108
|
+
expect(refIds).toContain(apiKey.id)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("syncProject prunes stale object ids", async ({
|
|
112
|
+
database,
|
|
113
|
+
projectDatabase,
|
|
114
|
+
project,
|
|
115
|
+
logger,
|
|
116
|
+
}) => {
|
|
117
|
+
const service = new ObjectRefIndexService(database, logger)
|
|
118
|
+
|
|
119
|
+
const operation = await projectDatabase.operation.create({
|
|
120
|
+
data: {
|
|
121
|
+
meta: { title: "op" },
|
|
122
|
+
type: "update",
|
|
123
|
+
options: {},
|
|
124
|
+
requestedInstanceIds: [],
|
|
125
|
+
},
|
|
126
|
+
select: { id: true },
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
await service.syncProject(project.id)
|
|
130
|
+
await projectDatabase.operation.delete({ where: { id: operation.id } })
|
|
131
|
+
await service.syncProject(project.id)
|
|
132
|
+
|
|
133
|
+
const refs = await database.backend.object.findMany({
|
|
134
|
+
where: { projectId: project.id, id: operation.id },
|
|
135
|
+
select: { id: true },
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
expect(refs).toHaveLength(0)
|
|
139
|
+
})
|
|
140
|
+
})
|