@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.
Files changed (120) hide show
  1. package/dist/{chunk-V2NILDHS.js → chunk-52MY2TCE.js} +347 -19
  2. package/dist/chunk-52MY2TCE.js.map +1 -0
  3. package/dist/{chunk-I7BWSAN6.js → chunk-UAWBPTDW.js} +3 -3
  4. package/dist/{chunk-I7BWSAN6.js.map → chunk-UAWBPTDW.js.map} +1 -1
  5. package/dist/highstate.manifest.json +4 -4
  6. package/dist/index.js +4159 -785
  7. package/dist/index.js.map +1 -1
  8. package/dist/library/worker/main.js +5 -2
  9. package/dist/library/worker/main.js.map +1 -1
  10. package/dist/shared/index.js +2 -2
  11. package/package.json +7 -7
  12. package/prisma/backend/_schema/object.prisma +12 -0
  13. package/prisma/backend/sqlite/migrations/20260222113554_add_object_tracking/migration.sql +7 -0
  14. package/prisma/project/artifact.prisma +3 -0
  15. package/prisma/project/entity.prisma +125 -0
  16. package/prisma/project/instance.prisma +6 -0
  17. package/prisma/project/migrations/20260301210131_add_entity_tracking/migration.sql +70 -0
  18. package/prisma/project/migrations/20260302212734_add_resource_hooks_flag/migration.sql +1 -0
  19. package/prisma/project/operation.prisma +3 -0
  20. package/src/business/artifact.test.ts +22 -2
  21. package/src/business/artifact.ts +7 -1
  22. package/src/business/entity-snapshot.test.ts +684 -0
  23. package/src/business/entity-snapshot.ts +904 -0
  24. package/src/business/evaluation.test.ts +56 -0
  25. package/src/business/evaluation.ts +102 -22
  26. package/src/business/global-search.test.ts +344 -0
  27. package/src/business/global-search.ts +902 -0
  28. package/src/business/index.ts +4 -0
  29. package/src/business/instance-lock.ts +58 -74
  30. package/src/business/instance-state.test.ts +15 -1
  31. package/src/business/instance-state.ts +37 -14
  32. package/src/business/object-ref-index.test.ts +140 -0
  33. package/src/business/object-ref-index.ts +193 -0
  34. package/src/business/operation.test.ts +15 -1
  35. package/src/business/operation.ts +4 -0
  36. package/src/business/project-model.ts +154 -13
  37. package/src/business/project-unlock.ts +25 -2
  38. package/src/business/project.ts +9 -0
  39. package/src/business/secret.test.ts +35 -2
  40. package/src/business/secret.ts +32 -9
  41. package/src/business/settings.ts +761 -0
  42. package/src/business/unit-output.test.ts +477 -0
  43. package/src/business/unit-output.ts +461 -0
  44. package/src/business/worker.ts +55 -4
  45. package/src/database/_generated/backend/postgresql/browser.ts +6 -0
  46. package/src/database/_generated/backend/postgresql/client.ts +6 -0
  47. package/src/database/_generated/backend/postgresql/internal/class.ts +23 -5
  48. package/src/database/_generated/backend/postgresql/internal/prismaNamespace.ts +89 -5
  49. package/src/database/_generated/backend/postgresql/internal/prismaNamespaceBrowser.ts +9 -0
  50. package/src/database/_generated/backend/postgresql/models/Object.ts +1076 -0
  51. package/src/database/_generated/backend/postgresql/models.ts +1 -0
  52. package/src/database/_generated/backend/sqlite/browser.ts +6 -0
  53. package/src/database/_generated/backend/sqlite/client.ts +6 -0
  54. package/src/database/_generated/backend/sqlite/internal/class.ts +23 -5
  55. package/src/database/_generated/backend/sqlite/internal/prismaNamespace.ts +89 -5
  56. package/src/database/_generated/backend/sqlite/internal/prismaNamespaceBrowser.ts +9 -0
  57. package/src/database/_generated/backend/sqlite/models/Object.ts +1074 -0
  58. package/src/database/_generated/backend/sqlite/models.ts +1 -0
  59. package/src/database/_generated/project/browser.ts +23 -0
  60. package/src/database/_generated/project/client.ts +23 -0
  61. package/src/database/_generated/project/commonInputTypes.ts +87 -53
  62. package/src/database/_generated/project/enums.ts +8 -0
  63. package/src/database/_generated/project/internal/class.ts +53 -5
  64. package/src/database/_generated/project/internal/prismaNamespace.ts +367 -13
  65. package/src/database/_generated/project/internal/prismaNamespaceBrowser.ts +48 -1
  66. package/src/database/_generated/project/models/Artifact.ts +199 -11
  67. package/src/database/_generated/project/models/Entity.ts +1274 -0
  68. package/src/database/_generated/project/models/EntitySnapshot.ts +2389 -0
  69. package/src/database/_generated/project/models/EntitySnapshotContent.ts +1260 -0
  70. package/src/database/_generated/project/models/EntitySnapshotReference.ts +1449 -0
  71. package/src/database/_generated/project/models/InstanceState.ts +361 -1
  72. package/src/database/_generated/project/models/Operation.ts +148 -3
  73. package/src/database/_generated/project/models/OperationLog.ts +0 -4
  74. package/src/database/_generated/project/models.ts +4 -0
  75. package/src/database/migration.ts +3 -0
  76. package/src/library/worker/evaluator.ts +7 -1
  77. package/src/orchestrator/manager.ts +7 -0
  78. package/src/orchestrator/operation-context.captured-outputs.test.ts +118 -0
  79. package/src/orchestrator/operation-context.ts +154 -16
  80. package/src/orchestrator/operation-plan.destroy.test.md +33 -12
  81. package/src/orchestrator/operation-plan.destroy.test.ts +140 -2
  82. package/src/orchestrator/operation-plan.fixtures.ts +2 -0
  83. package/src/orchestrator/operation-plan.md +4 -1
  84. package/src/orchestrator/operation-plan.ts +286 -92
  85. package/src/orchestrator/operation-plan.update.test.md +286 -11
  86. package/src/orchestrator/operation-plan.update.test.ts +656 -5
  87. package/src/orchestrator/operation-workset.ts +72 -22
  88. package/src/orchestrator/operation.cancel.test.ts +4 -0
  89. package/src/orchestrator/operation.composite.test.ts +341 -0
  90. package/src/orchestrator/operation.destroy.test.ts +4 -0
  91. package/src/orchestrator/operation.output-validation.failure.test.ts +124 -0
  92. package/src/orchestrator/operation.preview.test.ts +4 -0
  93. package/src/orchestrator/operation.refresh.test.ts +4 -0
  94. package/src/orchestrator/operation.test-utils.ts +52 -13
  95. package/src/orchestrator/operation.ts +228 -68
  96. package/src/orchestrator/operation.update.failure.test.ts +4 -0
  97. package/src/orchestrator/operation.update.skip.test.ts +110 -0
  98. package/src/orchestrator/operation.update.test.ts +4 -0
  99. package/src/orchestrator/plan-test-builder.ts +1 -0
  100. package/src/orchestrator/unit-input-values.test.ts +450 -0
  101. package/src/orchestrator/unit-input-values.ts +281 -0
  102. package/src/pubsub/manager.ts +3 -0
  103. package/src/runner/abstractions.ts +23 -54
  104. package/src/runner/local.ts +109 -85
  105. package/src/services.ts +52 -1
  106. package/src/shared/models/prisma.ts +1 -0
  107. package/src/shared/models/project/entity.ts +121 -0
  108. package/src/shared/models/project/index.ts +1 -0
  109. package/src/shared/models/project/operation.ts +61 -3
  110. package/src/shared/models/project/state.ts +10 -0
  111. package/src/shared/models/project/worker.ts +7 -0
  112. package/src/shared/resolvers/effective-output-type.test.ts +494 -0
  113. package/src/shared/resolvers/effective-output-type.ts +162 -0
  114. package/src/shared/resolvers/index.ts +1 -0
  115. package/src/shared/resolvers/input.ts +59 -9
  116. package/src/shared/utils/index.ts +1 -0
  117. package/src/shared/utils/stable-json.ts +41 -0
  118. package/src/terminal/manager.ts +6 -0
  119. package/src/worker/manager.ts +97 -1
  120. package/dist/chunk-V2NILDHS.js.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { errorToString } from '../../chunk-X2WG3WGL.js';
2
- import '../../chunk-I7BWSAN6.js';
2
+ import '../../chunk-UAWBPTDW.js';
3
3
  import { workerData, parentPort } from 'node:worker_threads';
4
4
  import { pino } from 'pino';
5
5
  import { InstanceNameConflictError, getRuntimeInstances, parseArgumentValue, isComponent } from '@highstate/contract';
6
6
  import { mapValues } from 'remeda';
7
7
 
8
+ function toCloneSafeInstanceModel(instance) {
9
+ return JSON.parse(JSON.stringify(instance));
10
+ }
8
11
  function evaluateProject(logger2, components, allInstances, resolvedInputs) {
9
12
  const allInstancesMap = new Map(allInstances.map((instance) => [instance.id, instance]));
10
13
  const instanceErrors = {};
@@ -24,7 +27,7 @@ function evaluateProject(logger2, components, allInstances, resolvedInputs) {
24
27
  }
25
28
  return {
26
29
  success: true,
27
- virtualInstances: getRuntimeInstances().map((instance) => instance.instance).filter((instance) => instance.kind === "composite" || !allInstancesMap.has(instance.id)),
30
+ virtualInstances: getRuntimeInstances().map((instance) => toCloneSafeInstanceModel(instance.instance)).filter((instance) => instance.kind === "composite" || !allInstancesMap.has(instance.id)),
28
31
  topLevelErrors
29
32
  };
30
33
  function evaluateInstance(instanceId) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/library/worker/evaluator.ts","../../../src/library/worker/loader.lite.ts","../../../src/library/worker/main.ts"],"names":["logger","error","input"],"mappings":"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","file":"main.js","sourcesContent":["import type { Logger } from \"pino\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport type { ProjectEvaluationResult } from \"../abstractions\"\nimport {\n type Component,\n getRuntimeInstances,\n type InstanceModel,\n InstanceNameConflictError,\n parseArgumentValue,\n} from \"@highstate/contract\"\nimport { mapValues } from \"remeda\"\nimport { errorToString } from \"../../common\"\n\nexport function evaluateProject(\n logger: Logger,\n components: Readonly<Record<string, Component>>,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n): ProjectEvaluationResult {\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n const instanceErrors: Record<string, unknown> = {}\n const topLevelErrors: Record<string, string> = {}\n\n const instanceOutputs = new Map<string, Record<string, unknown>>()\n\n for (const instance of allInstances) {\n try {\n evaluateInstance(instance.id)\n } catch (error) {\n if (error instanceof InstanceNameConflictError) {\n // fail the whole evaluation if there's a name conflict\n return {\n success: false,\n error: error.message,\n }\n }\n }\n }\n\n return {\n success: true,\n\n virtualInstances: getRuntimeInstances()\n .map(instance => instance.instance)\n // only include top-level composite instances and their children\n .filter(instance => instance.kind === \"composite\" || !allInstancesMap.has(instance.id)),\n\n topLevelErrors,\n }\n\n function evaluateInstance(instanceId: InstanceModel[\"id\"]): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n if (outputs) {\n return outputs\n }\n\n // do not evaluate instance if it has an error, just rethrow it\n const error = instanceErrors[instanceId]\n if (error) {\n // eslint-disable-next-line @typescript-eslint/only-throw-error\n throw error\n }\n\n const instance = allInstancesMap.get(instanceId)\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n try {\n outputs = _evaluateInstance(instance)\n\n instanceOutputs.set(instanceId, outputs)\n return outputs\n } catch (error) {\n if (instance.kind === \"composite\" || !allInstancesMap.has(instance.id)) {\n topLevelErrors[instance.id] = errorToString(error)\n }\n\n instanceErrors[instanceId] = error\n throw error\n }\n }\n\n function _evaluateInstance(instance: InstanceModel): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n\n logger.debug(`evaluating instance \"%s\"`, instance.id)\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instance.id}`)\n }\n\n const parsedArgs = mapValues(instance.args ?? {}, rawValue => parseArgumentValue(rawValue))\n\n return component({\n name: instance.name,\n args: parsedArgs as Record<string, never>,\n inputs: inputs as Record<string, never>,\n })\n }\n}\n","import type { Logger } from \"pino\"\nimport { type Component, isComponent } from \"@highstate/contract\"\n\nexport async function loadComponents(\n logger: Logger,\n modulePaths: string[],\n): Promise<Readonly<Record<string, Component>>> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await import(modulePath)\n\n logger.debug({ modulePath }, \"module loaded\")\n } catch (err) {\n logger.error({ modulePath, err }, \"module load failed\")\n }\n }\n\n const components: Record<string, Component> = {}\n\n await _loadLibrary(modules, components)\n logger.debug(\"library loaded with %s components\", Object.keys(components).length)\n\n return components\n}\n\nasync function _loadLibrary(value: unknown, components: Record<string, Component>): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n if (\"_zod\" in value) {\n // this is a zod schema, we can skip it\n return\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n await _loadLibrary(item, components)\n }\n\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components)\n }\n}\n","import type { WorkerData } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { pino } from \"pino\"\nimport { errorToString } from \"../../common\"\nimport { evaluateProject } from \"./evaluator\"\nimport { loadComponents } from \"./loader.lite\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\" })\n\ntry {\n const library = await loadComponents(logger, data.libraryModulePaths)\n const result = evaluateProject(logger, library, data.allInstances, data.resolvedInputs)\n\n parentPort?.postMessage(result)\n} catch (error) {\n logger.error({ error }, \"failed to evaluate project\")\n\n parentPort?.postMessage({\n success: false,\n error: errorToString(error),\n })\n}\n"]}
1
+ {"version":3,"sources":["../../../src/library/worker/evaluator.ts","../../../src/library/worker/loader.lite.ts","../../../src/library/worker/main.ts"],"names":["logger","error","input"],"mappings":"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","file":"main.js","sourcesContent":["import type { Logger } from \"pino\"\nimport type { ResolvedInstanceInput } from \"../../shared\"\nimport type { ProjectEvaluationResult } from \"../abstractions\"\nimport {\n type Component,\n getRuntimeInstances,\n type InstanceModel,\n InstanceNameConflictError,\n parseArgumentValue,\n} from \"@highstate/contract\"\nimport { mapValues } from \"remeda\"\nimport { errorToString } from \"../../common\"\n\nfunction toCloneSafeInstanceModel(instance: InstanceModel): InstanceModel {\n // Runtime instances may contain Proxy-based accessors in outputs/resolvedOutputs.\n // Serialize to plain data so worker thread postMessage can structured-clone it.\n return JSON.parse(JSON.stringify(instance)) as InstanceModel\n}\n\nexport function evaluateProject(\n logger: Logger,\n components: Readonly<Record<string, Component>>,\n allInstances: InstanceModel[],\n resolvedInputs: Record<string, Record<string, ResolvedInstanceInput[]>>,\n): ProjectEvaluationResult {\n const allInstancesMap = new Map(allInstances.map(instance => [instance.id, instance]))\n\n const instanceErrors: Record<string, unknown> = {}\n const topLevelErrors: Record<string, string> = {}\n\n const instanceOutputs = new Map<string, Record<string, unknown>>()\n\n for (const instance of allInstances) {\n try {\n evaluateInstance(instance.id)\n } catch (error) {\n if (error instanceof InstanceNameConflictError) {\n // fail the whole evaluation if there's a name conflict\n return {\n success: false,\n error: error.message,\n }\n }\n }\n }\n\n return {\n success: true,\n\n virtualInstances: getRuntimeInstances()\n .map(instance => toCloneSafeInstanceModel(instance.instance))\n // only include top-level composite instances and their children\n .filter(instance => instance.kind === \"composite\" || !allInstancesMap.has(instance.id)),\n\n topLevelErrors,\n }\n\n function evaluateInstance(instanceId: InstanceModel[\"id\"]): Record<string, unknown> {\n let outputs = instanceOutputs.get(instanceId)\n if (outputs) {\n return outputs\n }\n\n // do not evaluate instance if it has an error, just rethrow it\n const error = instanceErrors[instanceId]\n if (error) {\n // eslint-disable-next-line @typescript-eslint/only-throw-error\n throw error\n }\n\n const instance = allInstancesMap.get(instanceId)\n if (!instance) {\n throw new Error(`Instance not found: ${instanceId}`)\n }\n\n try {\n outputs = _evaluateInstance(instance)\n\n instanceOutputs.set(instanceId, outputs)\n return outputs\n } catch (error) {\n if (instance.kind === \"composite\" || !allInstancesMap.has(instance.id)) {\n topLevelErrors[instance.id] = errorToString(error)\n }\n\n instanceErrors[instanceId] = error\n throw error\n }\n }\n\n function _evaluateInstance(instance: InstanceModel): Record<string, unknown> {\n const inputs: Record<string, unknown> = {}\n\n logger.debug(`evaluating instance \"%s\"`, instance.id)\n\n for (const [inputName, input] of Object.entries(resolvedInputs[instance.id] ?? {})) {\n inputs[inputName] = input.map(input => {\n const evaluated = evaluateInstance(input.input.instanceId)\n\n return evaluated[input.input.output]\n })\n }\n\n const component = components[instance.type]\n if (!component) {\n throw new Error(`Component not found: ${instance.type}, required by instance: ${instance.id}`)\n }\n\n const parsedArgs = mapValues(instance.args ?? {}, rawValue => parseArgumentValue(rawValue))\n\n return component({\n name: instance.name,\n args: parsedArgs as Record<string, never>,\n inputs: inputs as Record<string, never>,\n })\n }\n}\n","import type { Logger } from \"pino\"\nimport { type Component, isComponent } from \"@highstate/contract\"\n\nexport async function loadComponents(\n logger: Logger,\n modulePaths: string[],\n): Promise<Readonly<Record<string, Component>>> {\n const modules: Record<string, unknown> = {}\n for (const modulePath of modulePaths) {\n try {\n logger.debug({ modulePath }, \"loading module\")\n modules[modulePath] = await import(modulePath)\n\n logger.debug({ modulePath }, \"module loaded\")\n } catch (err) {\n logger.error({ modulePath, err }, \"module load failed\")\n }\n }\n\n const components: Record<string, Component> = {}\n\n await _loadLibrary(modules, components)\n logger.debug(\"library loaded with %s components\", Object.keys(components).length)\n\n return components\n}\n\nasync function _loadLibrary(value: unknown, components: Record<string, Component>): Promise<void> {\n if (isComponent(value)) {\n components[value.model.type] = value\n return\n }\n\n if (typeof value !== \"object\" || value === null) {\n return\n }\n\n if (\"_zod\" in value) {\n // this is a zod schema, we can skip it\n return\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n await _loadLibrary(item, components)\n }\n\n return\n }\n\n for (const key in value) {\n await _loadLibrary((value as Record<string, unknown>)[key], components)\n }\n}\n","import type { WorkerData } from \"./protocol\"\nimport { parentPort, workerData } from \"node:worker_threads\"\nimport { pino } from \"pino\"\nimport { errorToString } from \"../../common\"\nimport { evaluateProject } from \"./evaluator\"\nimport { loadComponents } from \"./loader.lite\"\n\nconst data = workerData as WorkerData\n\nconst logger = pino({ name: \"library-worker\" })\n\ntry {\n const library = await loadComponents(logger, data.libraryModulePaths)\n const result = evaluateProject(logger, library, data.allInstances, data.resolvedInputs)\n\n parentPort?.postMessage(result)\n} catch (error) {\n logger.error({ error }, \"failed to evaluate project\")\n\n parentPort?.postMessage({\n success: false,\n error: errorToString(error),\n })\n}\n"]}
@@ -1,4 +1,4 @@
1
- export { AccessError, BackendError, BackendUnlockMethodNotFoundError, CannotDeleteLastBackendUnlockMethodError, CannotDeleteLastUnlockMethodError, GraphResolver, InputHashResolver, InputResolver, InstanceLockLostError, InstanceLockedError, InstanceNotFoundError, InstanceStateNotFoundError, InvalidInstanceKindError, MAX_WORKER_START_ATTEMPTS, OperationNotFoundError, ProjectLockedError, ProjectNotFoundError, PromiseTracker, SystemSecretNames, ValidationResolver, WorkerVersionNotFoundError, apiKeyMetaSchema, apiKeyOutputSchema, apiKeyQuerySchema, applyLibraryUpdate, artifactOutputSchema, artifactQuerySchema, backendUnlockMethodInputSchema, backendUnlockMethodMetaSchema, codebaseLibrary, codebaseProjectModelStorage, collectionQueryResult, collectionQuerySchema, createAsyncBatcher, databaseProjectModelStorage, diffLibraries, extractDigestFromImage, finalInstanceOperationStatuses, finalOperationStatuses, forSchema, getAllDependents, getMatchedInjectionInstanceInputs, getResolvedHubInputs, getResolvedInjectionInstanceInputs, getResolvedInstanceInputs, getResolvedInstanceOutputs, getWorkerIdentity, globalProjectSpace, hasObjectMeta, hostPulumiBackend, instanceCustomStatusInputSchema, instanceLockEventSchema, instanceLockOutputSchema, instanceStateEventSchema, int32ToBytes, isFinalOperationStatus, isInstanceDeployed, isTransientInstanceOperationStatus, isTransientOperationStatus, isVirtualGhostInstance, librarySpecSchema, operationEventSchema, operationLaunchInputSchema, operationMetaSchema, operationOptionsSchema, operationOutputSchema, operationPhaseInstanceSchema, operationPhaseSchema, operationPhaseTypeSchema, operationPlanInputSchema, operationStatusSchema, operationTypeSchema, pageDetailsOutputSchema, pageOutputSchema, pageQuerySchema, projectInputSchema, projectModelEventSchema, projectModelStorageSpecSchema, projectOutputSchema, projectUnlockStateSchema, projectUnlockSuiteSchema, pulumiBackendSpecSchema, resolverFactories, secretOutputSchema, secretQuerySchema, serviceAccountOutputSchema, serviceAccountQuerySchema, stableInstanceInputSchema, terminalDetailsOutputSchema, terminalOutputSchema, terminalQuerySchema, terminalSessionOutputSchema, terminalStatusSchema, toApiKeyOutput, toPageOutput, toSecretOutput, toTerminalDetailsOutput, toTerminalOutput, toTerminalSessionOutput, toWorkerOutput, toWorkerVersionOutput, triggerOutputSchema, triggerQuerySchema, unlockMethodInputSchema, unlockMethodMetaSchema, unlockMethodOutputSchema, unlockMethodType, waitAll, workerOutputSchema, workerQuerySchema, workerUnitRegistrationEventSchema, workerVersionOutputSchema, workerVersionStatusSchema } from '../chunk-V2NILDHS.js';
2
- import '../chunk-I7BWSAN6.js';
1
+ export { AccessError, BackendError, BackendUnlockMethodNotFoundError, CannotDeleteLastBackendUnlockMethodError, CannotDeleteLastUnlockMethodError, GraphResolver, InputHashResolver, InputResolver, InstanceLockLostError, InstanceLockedError, InstanceNotFoundError, InstanceStateNotFoundError, InvalidInstanceKindError, MAX_WORKER_START_ATTEMPTS, OperationNotFoundError, ProjectLockedError, ProjectNotFoundError, PromiseTracker, SystemSecretNames, ValidationResolver, WorkerVersionNotFoundError, apiKeyMetaSchema, apiKeyOutputSchema, apiKeyQuerySchema, applyLibraryUpdate, artifactOutputSchema, artifactQuerySchema, backendUnlockMethodInputSchema, backendUnlockMethodMetaSchema, codebaseLibrary, codebaseProjectModelStorage, collectionQueryResult, collectionQuerySchema, createAsyncBatcher, databaseProjectModelStorage, diffLibraries, entityDetailsOutputSchema, entityOutputSchema, entityQuerySchema, entityReferenceOutputSchema, entitySnapshotDetailsOutputSchema, entitySnapshotListItemOutputSchema, entitySnapshotOutputSchema, extractDigestFromImage, finalInstanceOperationStatuses, finalOperationStatuses, forSchema, getAllDependents, getMatchedInjectionInstanceInputs, getResolvedHubInputs, getResolvedInjectionInstanceInputs, getResolvedInstanceInputs, getResolvedInstanceOutputs, getWorkerIdentity, globalProjectSpace, hasObjectMeta, hostPulumiBackend, instanceCustomStatusInputSchema, instanceLockEventSchema, instanceLockOutputSchema, instanceStateEventSchema, int32ToBytes, isFinalOperationStatus, isInstanceDeployed, isTransientInstanceOperationStatus, isTransientOperationStatus, isVirtualGhostInstance, librarySpecSchema, operationEventSchema, operationLaunchInputSchema, operationMetaSchema, operationOptionsSchema, operationOutputSchema, operationPhaseInstanceSchema, operationPhaseSchema, operationPhaseTypeSchema, operationPlanInputSchema, operationStatusSchema, operationTypeSchema, pageDetailsOutputSchema, pageOutputSchema, pageQuerySchema, projectInputSchema, projectModelEventSchema, projectModelStorageSpecSchema, projectOutputSchema, projectUnlockStateSchema, projectUnlockSuiteSchema, pulumiBackendSpecSchema, resolveEffectiveOutputType, resolverFactories, secretOutputSchema, secretQuerySchema, serviceAccountOutputSchema, serviceAccountQuerySchema, stableInstanceInputSchema, stableJsonStringify, terminalDetailsOutputSchema, terminalOutputSchema, terminalQuerySchema, terminalSessionOutputSchema, terminalStatusSchema, toApiKeyOutput, toCommonEntityMeta, toPageOutput, toSecretOutput, toTerminalDetailsOutput, toTerminalOutput, toTerminalSessionOutput, toWorkerOutput, toWorkerVersionOutput, triggerOutputSchema, triggerQuerySchema, unlockMethodInputSchema, unlockMethodMetaSchema, unlockMethodOutputSchema, unlockMethodType, waitAll, workerOutputSchema, workerQuerySchema, workerUnitRegistrationEventSchema, workerVersionOutputSchema, workerVersionStatusEventSchema, workerVersionStatusSchema } from '../chunk-52MY2TCE.js';
2
+ import '../chunk-UAWBPTDW.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -34,10 +34,10 @@
34
34
  "@noble/ciphers": "^1.3.0",
35
35
  "@noble/hashes": "^1.8.0",
36
36
  "@paralleldrive/cuid2": "^2.2.2",
37
- "@prisma/adapter-libsql": "7.3.0",
38
- "@prisma/client": "7.3.0",
39
- "@prisma/driver-adapter-utils": "7.3.0",
40
- "@pulumi/pulumi": "3.198.0",
37
+ "@prisma/adapter-libsql": "7.4.1",
38
+ "@prisma/client": "7.4.1",
39
+ "@prisma/driver-adapter-utils": "7.4.1",
40
+ "@pulumi/pulumi": "3.220.0",
41
41
  "age-encryption": "^0.2.3",
42
42
  "ajv": "^8.17.1",
43
43
  "ansi-colors": "^4.1.3",
@@ -57,14 +57,14 @@
57
57
  "p-queue": "^8.0.0",
58
58
  "pino": "^9.6.0",
59
59
  "pkg-types": "^1.2.1",
60
- "prisma": "7.3.0",
60
+ "prisma": "7.4.1",
61
61
  "remeda": "^2.21.0",
62
62
  "ulid": "^3.0.1",
63
63
  "uuid": "^11.1.0",
64
64
  "watcher": "^2.3.1",
65
65
  "yaml": "^2.7.1",
66
66
  "zod": "^4.0.5",
67
- "@highstate/contract": "0.19.1"
67
+ "@highstate/contract": "0.20.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@biomejs/biome": "2.2.0",
@@ -0,0 +1,12 @@
1
+ /// The object allows to track arbitrary object across multiple projects and search them globally by their IDs.
2
+ /// This also allow to correlate different entities across different projects.
3
+ model Object {
4
+ /// The CUIDv2(d) of the object.
5
+ id String
6
+
7
+ /// The ID of the project that knows this object.
8
+ /// Multiple projects can reference the same object, but each project can only reference an object once.
9
+ projectId String
10
+
11
+ @@id([id, projectId])
12
+ }
@@ -0,0 +1,7 @@
1
+ -- CreateTable
2
+ CREATE TABLE "Object" (
3
+ "id" TEXT NOT NULL,
4
+ "projectId" TEXT NOT NULL,
5
+
6
+ PRIMARY KEY ("id", "projectId")
7
+ );
@@ -49,4 +49,7 @@ model Artifact {
49
49
 
50
50
  /// The pages using this artifact.
51
51
  pages Page[]
52
+
53
+ /// The entity snapshots referenced this artifact.
54
+ entitySnapshots EntitySnapshot[]
52
55
  }
@@ -0,0 +1,125 @@
1
+ /// This model represents and instance of Highstate entity produced by one or many component instances one or many times.
2
+ /// Entity tracks EntitySnapshots by their unique IDs allowing to correlate them across different operations and instances.
3
+ /// Entities also can be tracked globally across different projects by referencing them in the Object model at the backend level.
4
+ model Entity {
5
+ /// The CUIDv2 or CUIDv2d of the entity.
6
+ ///
7
+ /// The ID is calculated by the backend as CUIDv2d(entityType, identity), where identity is a user-provided string value that is expected to be globally unique for each entity of the same type.
8
+ id String @id
9
+
10
+ /// The type of the entity.
11
+ type String
12
+
13
+ /// The identity of the entity.
14
+ identity String
15
+
16
+ /// The snapshots of the entity.
17
+ snapshots EntitySnapshot[]
18
+ }
19
+
20
+ /// This model represents an immutable snapshot of an entity at a certain point of time
21
+ /// provide by some component instance during an operation.
22
+ model EntitySnapshot {
23
+ /// The CUIDv2 of the entity snapshot.
24
+ id String @id @default(cuid(2))
25
+
26
+ /// The SHA-256 hash of the entity snapshot content.
27
+ /// The content is stored separately in the EntitySnapshotContent model and can be shared between different snapshots with the same content hash.
28
+ contentHash String
29
+
30
+ /// The ID of the entity this snapshot belongs to.
31
+ entityId String
32
+
33
+ /// The ID of the operation that created this snapshot.
34
+ operationId String
35
+
36
+ /// The ID of the instance state produced this entity snapshot.
37
+ stateId String
38
+
39
+ /// The name of the instance outputs where this entity were referenced (including nested entities).
40
+ ///
41
+ /// ![string[]]
42
+ referencedInOutputs Json
43
+
44
+ /// The name of the outputs that exported this entity directly.
45
+ ///
46
+ /// ![string[]]
47
+ exportedInOutputs Json
48
+
49
+ /// The time when the entity snapshot was created.
50
+ createdAt DateTime @default(now())
51
+
52
+ /// The content of the entity snapshot.
53
+ content EntitySnapshotContent @relation(fields: [contentHash], references: [hash])
54
+
55
+ /// The entity this snapshot belongs to.
56
+ entity Entity @relation(fields: [entityId], references: [id])
57
+
58
+ /// The operation that created this snapshot.
59
+ operation Operation @relation(fields: [operationId], references: [id])
60
+
61
+ /// The instance state that produced this entity snapshot.
62
+ state InstanceState @relation(fields: [stateId], references: [id])
63
+
64
+ /// The snapshots of entities referenced by this entity snapshot.
65
+ /// For explicit references (specified manually by their IDs) the last snapshot of the referenced entity will be used.
66
+ /// For implicit references (collected via includes) the same snapshot of the same operation will be used.
67
+ references EntitySnapshotReference[] @relation("EntitySnapshotReferences")
68
+
69
+ /// The snapshots of entities that reference this entity snapshot.
70
+ referencedBy EntitySnapshotReference[] @relation("EntitySnapshotReferencedBy")
71
+
72
+ /// The artifacts referenced by this entity snapshot.
73
+ artifacts Artifact[]
74
+
75
+ @@index([entityId, createdAt(sort: Desc)])
76
+ @@index([operationId])
77
+ @@index([stateId, createdAt(sort: Desc)])
78
+ }
79
+
80
+ enum EntityReferenceKind {
81
+ explicit
82
+ inclusion
83
+ }
84
+
85
+ model EntitySnapshotReference {
86
+ /// The CUIDv2 of the entity snapshot relation.
87
+ fromId String
88
+
89
+ /// The CUIDv2 of the referenced entity snapshot.
90
+ toId String
91
+
92
+ /// The kind of the reference, which can be either explicit or inclusion (implicit).
93
+ kind EntityReferenceKind
94
+
95
+ /// The group of the references.
96
+ /// It can be either the exlicit group name provided by the entity explicit reference,
97
+ /// or name of the inclusion field of the parent entity for implicit references.
98
+ group String
99
+
100
+ /// The entity snapshot that holds the reference.
101
+ from EntitySnapshot @relation("EntitySnapshotReferences", fields: [fromId], references: [id])
102
+
103
+ /// The entity snapshot that is referenced.
104
+ to EntitySnapshot @relation("EntitySnapshotReferencedBy", fields: [toId], references: [id])
105
+
106
+ @@id([fromId, toId, kind, group])
107
+ @@index([toId])
108
+ @@index([fromId])
109
+ }
110
+
111
+ model EntitySnapshotContent {
112
+ /// The SHA-256 hash of the entity snapshot content.
113
+ hash String @id
114
+
115
+ /// The metadata of the entity at the time of the snapshot.
116
+ ///
117
+ /// [EntityMeta]
118
+ meta Json?
119
+
120
+ /// The content of the entity snapshot, which is opaque to the backend.
121
+ content Json
122
+
123
+ /// The entity snapshots that have this content.
124
+ snapshots EntitySnapshot[]
125
+ }
@@ -125,6 +125,9 @@ model InstanceState {
125
125
  /// [InstanceStatusFields]
126
126
  statusFields Json?
127
127
 
128
+ /// Whether the instance has resource hooks and requires running program on destroy to properly clean up resources.
129
+ hasResourceHooks Boolean @default(false)
130
+
128
131
  /// The parent instance.
129
132
  parent InstanceState? @relation("InstanceHierarchy", fields: [parentId], references: [id])
130
133
 
@@ -166,4 +169,7 @@ model InstanceState {
166
169
 
167
170
  /// The user viewports associated with this instance.
168
171
  userViewports UserCompositeViewport[]
172
+
173
+ /// The entity snapshots associated with this instance.
174
+ entitySnapshots EntitySnapshot[]
169
175
  }
@@ -0,0 +1,70 @@
1
+ -- CreateTable
2
+ CREATE TABLE "Entity" (
3
+ "id" TEXT NOT NULL PRIMARY KEY,
4
+ "type" TEXT NOT NULL,
5
+ "identity" TEXT NOT NULL
6
+ );
7
+
8
+ -- CreateTable
9
+ CREATE TABLE "EntitySnapshot" (
10
+ "id" TEXT NOT NULL PRIMARY KEY,
11
+ "contentHash" TEXT NOT NULL,
12
+ "entityId" TEXT NOT NULL,
13
+ "operationId" TEXT NOT NULL,
14
+ "stateId" TEXT NOT NULL,
15
+ "referencedInOutputs" JSONB NOT NULL,
16
+ "exportedInOutputs" JSONB NOT NULL,
17
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+ CONSTRAINT "EntitySnapshot_contentHash_fkey" FOREIGN KEY ("contentHash") REFERENCES "EntitySnapshotContent" ("hash") ON DELETE RESTRICT ON UPDATE CASCADE,
19
+ CONSTRAINT "EntitySnapshot_entityId_fkey" FOREIGN KEY ("entityId") REFERENCES "Entity" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
20
+ CONSTRAINT "EntitySnapshot_operationId_fkey" FOREIGN KEY ("operationId") REFERENCES "Operation" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
21
+ CONSTRAINT "EntitySnapshot_stateId_fkey" FOREIGN KEY ("stateId") REFERENCES "InstanceState" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
22
+ );
23
+
24
+ -- CreateTable
25
+ CREATE TABLE "EntitySnapshotReference" (
26
+ "fromId" TEXT NOT NULL,
27
+ "toId" TEXT NOT NULL,
28
+ "kind" TEXT NOT NULL,
29
+ "group" TEXT NOT NULL,
30
+
31
+ PRIMARY KEY ("fromId", "toId", "kind", "group"),
32
+ CONSTRAINT "EntitySnapshotReference_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "EntitySnapshot" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
33
+ CONSTRAINT "EntitySnapshotReference_toId_fkey" FOREIGN KEY ("toId") REFERENCES "EntitySnapshot" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
34
+ );
35
+
36
+ -- CreateTable
37
+ CREATE TABLE "EntitySnapshotContent" (
38
+ "hash" TEXT NOT NULL PRIMARY KEY,
39
+ "meta" JSONB,
40
+ "content" JSONB NOT NULL
41
+ );
42
+
43
+ -- CreateTable
44
+ CREATE TABLE "_ArtifactToEntitySnapshot" (
45
+ "A" TEXT NOT NULL,
46
+ "B" TEXT NOT NULL,
47
+ CONSTRAINT "_ArtifactToEntitySnapshot_A_fkey" FOREIGN KEY ("A") REFERENCES "Artifact" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
48
+ CONSTRAINT "_ArtifactToEntitySnapshot_B_fkey" FOREIGN KEY ("B") REFERENCES "EntitySnapshot" ("id") ON DELETE CASCADE ON UPDATE CASCADE
49
+ );
50
+
51
+ -- CreateIndex
52
+ CREATE INDEX "EntitySnapshot_entityId_createdAt_idx" ON "EntitySnapshot"("entityId", "createdAt" DESC);
53
+
54
+ -- CreateIndex
55
+ CREATE INDEX "EntitySnapshot_operationId_idx" ON "EntitySnapshot"("operationId");
56
+
57
+ -- CreateIndex
58
+ CREATE INDEX "EntitySnapshot_stateId_createdAt_idx" ON "EntitySnapshot"("stateId", "createdAt" DESC);
59
+
60
+ -- CreateIndex
61
+ CREATE INDEX "EntitySnapshotReference_toId_idx" ON "EntitySnapshotReference"("toId");
62
+
63
+ -- CreateIndex
64
+ CREATE INDEX "EntitySnapshotReference_fromId_idx" ON "EntitySnapshotReference"("fromId");
65
+
66
+ -- CreateIndex
67
+ CREATE UNIQUE INDEX "_ArtifactToEntitySnapshot_AB_unique" ON "_ArtifactToEntitySnapshot"("A", "B");
68
+
69
+ -- CreateIndex
70
+ CREATE INDEX "_ArtifactToEntitySnapshot_B_index" ON "_ArtifactToEntitySnapshot"("B");
@@ -0,0 +1 @@
1
+ ALTER TABLE "InstanceState" ADD COLUMN "hasResourceHooks" BOOLEAN NOT NULL DEFAULT false;
@@ -83,6 +83,9 @@ model Operation {
83
83
 
84
84
  /// The logs of the operation.
85
85
  logs OperationLog[]
86
+
87
+ /// The entity snapshots created by this operation.
88
+ entitySnapshots EntitySnapshot[]
86
89
  }
87
90
 
88
91
  model InstanceOperationState {
@@ -1,4 +1,5 @@
1
1
  import type { ArtifactBackend } from "../artifact"
2
+ import type { ObjectRefIndexService } from "./object-ref-index"
2
3
  import { createId } from "@paralleldrive/cuid2"
3
4
  import { describe, type MockedObject, vi } from "vitest"
4
5
  import { test } from "../test-utils"
@@ -10,6 +11,7 @@ const createContent = async function* (text: string): AsyncIterable<Uint8Array>
10
11
 
11
12
  const artifactTest = test.extend<{
12
13
  artifactBackend: MockedObject<ArtifactBackend>
14
+ objectRefIndexService: MockedObject<ObjectRefIndexService>
13
15
  artifactService: ArtifactService
14
16
  }>({
15
17
  artifactBackend: async ({}, use) => {
@@ -22,10 +24,19 @@ const artifactTest = test.extend<{
22
24
  await use(artifactBackend)
23
25
  },
24
26
 
25
- artifactService: async ({ database, artifactBackend, logger }, use) => {
27
+ objectRefIndexService: async ({}, use) => {
28
+ const objectRefIndexService = vi.mockObject({
29
+ track: vi.fn().mockResolvedValue(undefined),
30
+ } as unknown as ObjectRefIndexService)
31
+
32
+ await use(objectRefIndexService)
33
+ },
34
+
35
+ artifactService: async ({ database, artifactBackend, objectRefIndexService, logger }, use) => {
26
36
  const service = new ArtifactService(
27
37
  database,
28
38
  artifactBackend,
39
+ objectRefIndexService,
29
40
  logger.child({ service: "ArtifactService" }),
30
41
  )
31
42
 
@@ -36,7 +47,14 @@ const artifactTest = test.extend<{
36
47
  describe("store", () => {
37
48
  artifactTest(
38
49
  "stores new artifact successfully",
39
- async ({ artifactService, projectDatabase, project, artifactBackend, expect }) => {
50
+ async ({
51
+ artifactService,
52
+ projectDatabase,
53
+ project,
54
+ artifactBackend,
55
+ objectRefIndexService,
56
+ expect,
57
+ }) => {
40
58
  // arrange
41
59
  const hash = createId()
42
60
  const size = 1024
@@ -72,6 +90,8 @@ describe("store", () => {
72
90
  expect.any(Object),
73
91
  )
74
92
 
93
+ expect(objectRefIndexService.track).toHaveBeenCalledWith(project.id, [artifact.id])
94
+
75
95
  // verify artifact was created with reference
76
96
  const dbArtifact = await projectDatabase.artifact.findUnique({
77
97
  where: { id: artifact.id },
@@ -2,6 +2,7 @@ import type { CommonObjectMeta } from "@highstate/contract"
2
2
  import type { Logger } from "pino"
3
3
  import type { ArtifactBackend } from "../artifact"
4
4
  import type { Artifact, DatabaseManager, ProjectTransaction } from "../database"
5
+ import type { ObjectRefIndexService } from "./object-ref-index"
5
6
  import { createId } from "@paralleldrive/cuid2"
6
7
 
7
8
  export const artifactChunkSize = 1024 * 1024 // 1 MB
@@ -13,6 +14,7 @@ export class ArtifactService {
13
14
  constructor(
14
15
  private readonly database: DatabaseManager,
15
16
  private readonly artifactBackend: ArtifactBackend,
17
+ private readonly objectRefIndexService: ObjectRefIndexService,
16
18
  private readonly logger: Logger,
17
19
  ) {}
18
20
 
@@ -53,7 +55,7 @@ export class ArtifactService {
53
55
  await this.artifactBackend.store(projectId, artifactId, artifactChunkSize, content)
54
56
  }
55
57
 
56
- return await database.$transaction(async tx => {
58
+ const artifact = await database.$transaction(async tx => {
57
59
  // create or update the main artifact record
58
60
  const artifact = await tx.artifact.upsert({
59
61
  where: { id: artifactId },
@@ -73,6 +75,10 @@ export class ArtifactService {
73
75
 
74
76
  return artifact
75
77
  })
78
+
79
+ await this.objectRefIndexService.track(projectId, [artifact.id])
80
+
81
+ return artifact
76
82
  }
77
83
 
78
84
  /**