@highstate/backend 0.9.15 → 0.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/dist/chunk-RCB4AFGD.js +159 -0
  2. package/dist/chunk-RCB4AFGD.js.map +1 -0
  3. package/dist/chunk-WHALQHEZ.js +2017 -0
  4. package/dist/chunk-WHALQHEZ.js.map +1 -0
  5. package/dist/highstate.manifest.json +3 -3
  6. package/dist/index.js +6158 -2178
  7. package/dist/index.js.map +1 -1
  8. package/dist/library/worker/main.js +47 -155
  9. package/dist/library/worker/main.js.map +1 -1
  10. package/dist/shared/index.js +159 -41
  11. package/package.json +25 -7
  12. package/src/artifact/abstractions.ts +46 -0
  13. package/src/artifact/encryption.ts +69 -0
  14. package/src/artifact/factory.ts +36 -0
  15. package/src/artifact/index.ts +3 -0
  16. package/src/artifact/local.ts +142 -0
  17. package/src/business/api-key.ts +65 -0
  18. package/src/business/artifact.ts +288 -0
  19. package/src/business/backend-unlock.ts +10 -0
  20. package/src/business/index.ts +9 -0
  21. package/src/business/instance-lock.ts +124 -0
  22. package/src/business/instance-state.ts +292 -0
  23. package/src/business/operation.ts +251 -0
  24. package/src/business/project-unlock.ts +242 -0
  25. package/src/business/secret.ts +187 -0
  26. package/src/business/worker.ts +161 -0
  27. package/src/common/index.ts +2 -1
  28. package/src/common/performance.ts +44 -0
  29. package/src/common/tree.ts +33 -0
  30. package/src/common/utils.ts +40 -1
  31. package/src/config.ts +14 -10
  32. package/src/hotstate/abstractions.ts +48 -0
  33. package/src/hotstate/factory.ts +17 -0
  34. package/src/{secret → hotstate}/index.ts +1 -0
  35. package/src/hotstate/manager.ts +192 -0
  36. package/src/hotstate/memory.ts +100 -0
  37. package/src/hotstate/validation.ts +101 -0
  38. package/src/index.ts +2 -1
  39. package/src/library/abstractions.ts +10 -23
  40. package/src/library/factory.ts +2 -2
  41. package/src/library/local.ts +89 -102
  42. package/src/library/worker/evaluator.ts +9 -42
  43. package/src/library/worker/loader.lite.ts +41 -0
  44. package/src/library/worker/main.ts +14 -65
  45. package/src/library/worker/protocol.ts +8 -24
  46. package/src/lock/abstractions.ts +6 -0
  47. package/src/lock/factory.ts +15 -0
  48. package/src/{workspace → lock}/index.ts +1 -0
  49. package/src/lock/manager.ts +82 -0
  50. package/src/lock/memory.ts +19 -0
  51. package/src/orchestrator/manager.ts +129 -82
  52. package/src/orchestrator/operation-workset.ts +168 -77
  53. package/src/orchestrator/operation.ts +967 -284
  54. package/src/project/abstractions.ts +20 -7
  55. package/src/project/factory.ts +1 -1
  56. package/src/project/index.ts +0 -1
  57. package/src/project/local.ts +73 -17
  58. package/src/project/manager.ts +272 -131
  59. package/src/pubsub/abstractions.ts +13 -0
  60. package/src/pubsub/factory.ts +19 -0
  61. package/src/pubsub/index.ts +3 -0
  62. package/src/pubsub/local.ts +36 -0
  63. package/src/pubsub/manager.ts +100 -0
  64. package/src/pubsub/validation.ts +33 -0
  65. package/src/runner/abstractions.ts +135 -68
  66. package/src/runner/artifact-env.ts +160 -0
  67. package/src/runner/factory.ts +20 -5
  68. package/src/runner/force-abort.ts +117 -0
  69. package/src/runner/local.ts +281 -371
  70. package/src/{common → runner}/pulumi.ts +86 -37
  71. package/src/services.ts +193 -35
  72. package/src/shared/index.ts +3 -11
  73. package/src/shared/models/backend/index.ts +3 -0
  74. package/src/shared/models/backend/project.ts +63 -0
  75. package/src/shared/models/backend/unlock-method.ts +20 -0
  76. package/src/shared/models/base.ts +151 -0
  77. package/src/shared/models/errors.ts +5 -0
  78. package/src/shared/models/index.ts +4 -0
  79. package/src/shared/models/project/api-key.ts +62 -0
  80. package/src/shared/models/project/artifact.ts +113 -0
  81. package/src/shared/models/project/component.ts +45 -0
  82. package/src/shared/models/project/index.ts +14 -0
  83. package/src/shared/{project.ts → models/project/instance.ts} +12 -0
  84. package/src/shared/models/project/lock.ts +91 -0
  85. package/src/shared/{operation.ts → models/project/operation.ts} +28 -7
  86. package/src/shared/models/project/page.ts +57 -0
  87. package/src/shared/models/project/secret.ts +112 -0
  88. package/src/shared/models/project/service-account.ts +22 -0
  89. package/src/shared/models/project/state.ts +432 -0
  90. package/src/shared/models/project/terminal.ts +99 -0
  91. package/src/shared/models/project/trigger.ts +56 -0
  92. package/src/shared/models/project/unlock-method.ts +31 -0
  93. package/src/shared/models/project/worker.ts +105 -0
  94. package/src/shared/resolvers/graph-resolver.ts +28 -0
  95. package/src/shared/resolvers/index.ts +5 -0
  96. package/src/shared/resolvers/input-hash.ts +53 -15
  97. package/src/shared/resolvers/input.ts +1 -9
  98. package/src/shared/resolvers/registry.ts +3 -2
  99. package/src/shared/resolvers/state.ts +2 -2
  100. package/src/shared/resolvers/validation.ts +61 -20
  101. package/src/shared/{async-batcher.ts → utils/async-batcher.ts} +13 -1
  102. package/src/shared/utils/hash.ts +6 -0
  103. package/src/shared/utils/index.ts +3 -0
  104. package/src/shared/utils/promise-tracker.ts +23 -0
  105. package/src/state/abstractions.ts +330 -101
  106. package/src/state/encryption.ts +59 -0
  107. package/src/state/factory.ts +3 -5
  108. package/src/state/index.ts +3 -0
  109. package/src/state/keyring.ts +22 -0
  110. package/src/state/local/backend.ts +299 -0
  111. package/src/state/local/collection.ts +342 -0
  112. package/src/state/local/index.ts +2 -0
  113. package/src/state/manager.ts +804 -18
  114. package/src/state/repository/index.ts +2 -0
  115. package/src/state/repository/repository.index.ts +193 -0
  116. package/src/state/repository/repository.ts +458 -0
  117. package/src/terminal/{shared.ts → abstractions.ts} +3 -3
  118. package/src/terminal/docker.ts +18 -14
  119. package/src/terminal/factory.ts +3 -3
  120. package/src/terminal/index.ts +1 -1
  121. package/src/terminal/manager.ts +131 -79
  122. package/src/terminal/run.sh.ts +21 -11
  123. package/src/worker/abstractions.ts +42 -0
  124. package/src/worker/docker.ts +83 -0
  125. package/src/worker/factory.ts +20 -0
  126. package/src/worker/index.ts +3 -0
  127. package/src/worker/manager.ts +139 -0
  128. package/dist/chunk-KTGKNSKM.js +0 -979
  129. package/dist/chunk-KTGKNSKM.js.map +0 -1
  130. package/dist/chunk-WXDYCRTT.js +0 -234
  131. package/dist/chunk-WXDYCRTT.js.map +0 -1
  132. package/src/library/worker/loader.ts +0 -114
  133. package/src/preferences/shared.ts +0 -1
  134. package/src/project/lock.ts +0 -39
  135. package/src/secret/abstractions.ts +0 -59
  136. package/src/secret/factory.ts +0 -22
  137. package/src/secret/local.ts +0 -152
  138. package/src/shared/state.ts +0 -247
  139. package/src/shared/terminal.ts +0 -14
  140. package/src/state/local.ts +0 -612
  141. package/src/workspace/abstractions.ts +0 -41
  142. package/src/workspace/factory.ts +0 -14
  143. package/src/workspace/local.ts +0 -54
  144. /package/src/shared/{library.ts → models/backend/library.ts} +0 -0
@@ -0,0 +1,151 @@
1
+ import { z } from "zod"
2
+
3
+ export const userObjectMetaSchema = z.object({
4
+ /**
5
+ * Human-readable name of the object.
6
+ *
7
+ * Used in UI components for better user experience.
8
+ */
9
+ title: z.string().optional(),
10
+
11
+ /**
12
+ * The title used globally for the object.
13
+ *
14
+ * For example, the title of an instance secret is "Password" which is okay
15
+ * to display in the instance secret list, but when the secret is displayed in a
16
+ * global secret list the name should be more descriptive, like "Proxmox Password".
17
+ */
18
+ globalTitle: z.string().optional(),
19
+
20
+ /**
21
+ * Description of the object.
22
+ *
23
+ * Provides additional context for users and developers.
24
+ */
25
+ description: z.string().optional(),
26
+
27
+ /**
28
+ * The color of the object.
29
+ *
30
+ * Used in UI components to visually distinguish objects.
31
+ */
32
+ color: z.string().optional(),
33
+
34
+ /**
35
+ * Primary icon identifier.
36
+ *
37
+ * Should reference a iconify icon name, like "mdi:server" or "gg:remote".
38
+ */
39
+ icon: z.string().optional(),
40
+
41
+ /**
42
+ * The color of the primary icon.
43
+ */
44
+ iconColor: z.string().optional(),
45
+
46
+ /**
47
+ * The secondary icon identifier.
48
+ *
49
+ * Used to provide additional context or actions related to the object.
50
+ *
51
+ * Should reference a iconify icon name, like "mdi:edit" or "mdi:delete".
52
+ */
53
+ secondaryIcon: z.string().optional(),
54
+
55
+ /**
56
+ * The color of the secondary icon.
57
+ */
58
+ secondaryIconColor: z.string().optional(),
59
+ })
60
+
61
+ export const objectMetaSchema = userObjectMetaSchema.extend({
62
+ /**
63
+ * Creation timestamp in milliseconds.
64
+ *
65
+ * Managed automatically by the system.
66
+ */
67
+ createdAt: z.number().optional(),
68
+
69
+ /**
70
+ * Last update timestamp in milliseconds.
71
+ *
72
+ * Managed automatically by the system.
73
+ */
74
+ updatedAt: z.number().optional(),
75
+
76
+ /**
77
+ * The optional version of the document to support optimistic concurrency control.
78
+ *
79
+ * Managed automatically by the system.
80
+ */
81
+ version: z.number().optional(),
82
+ })
83
+
84
+ export type UserObjectMeta = z.infer<typeof userObjectMetaSchema>
85
+ export type ObjectMeta = z.infer<typeof objectMetaSchema>
86
+
87
+ export function hasObjectMeta(value: unknown): value is { meta: ObjectMeta } {
88
+ return typeof value === "object" && value !== null && "meta" in value
89
+ }
90
+
91
+ export const collectionQuerySchema = z.object({
92
+ /**
93
+ * The search string to filter documents by display name, description, or other text fields.
94
+ *
95
+ * Not implemented yet.
96
+ */
97
+ search: z.string().optional(),
98
+
99
+ /**
100
+ * The skip count for pagination.
101
+ *
102
+ * Cannot be used with `cursor`.
103
+ */
104
+ skip: z.number().int().nonnegative().optional(),
105
+
106
+ /**
107
+ * The sorting order for the results.
108
+ *
109
+ * By default, "asc" is used.
110
+ */
111
+ sort: z.enum(["asc", "desc"]).default("asc"),
112
+
113
+ /**
114
+ * The ID of the document to start the query from.
115
+ *
116
+ * If "asc" sorting is used, this will return results after this ID.
117
+ * Otherwise, it will return results before this ID.
118
+ *
119
+ * Cannot be used with `skip`.
120
+ */
121
+ cursor: z.string().optional(),
122
+
123
+ /**
124
+ * The count of documents to return.
125
+ *
126
+ * Defaults to 20 if not specified.
127
+ * Maximum value is 100.
128
+ */
129
+ count: z.number().int().nonnegative().max(100).optional(),
130
+ })
131
+
132
+ export type CollectionQuery = z.infer<typeof collectionQuerySchema>
133
+
134
+ export type CollectionQueryResult<T> = {
135
+ /**
136
+ * The list of objects matching the query.
137
+ */
138
+ items: T[]
139
+
140
+ /**
141
+ * The total number of documents matching the query.
142
+ */
143
+ total: number
144
+
145
+ /**
146
+ * The cursor for the next page of results.
147
+ *
148
+ * If undefined, there are no more results.
149
+ */
150
+ nextCursor?: string
151
+ }
@@ -0,0 +1,5 @@
1
+ export class AccessError extends Error {
2
+ constructor(message: string) {
3
+ super(message)
4
+ }
5
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./base"
2
+ export * from "./backend"
3
+ export * from "./project"
4
+ export * from "./errors"
@@ -0,0 +1,62 @@
1
+ import { z } from "zod"
2
+ import { objectMetaSchema } from "../base"
3
+
4
+ export const instanceActionSchema = z.enum(["read", "write"])
5
+ export const serviceAccountActionSchema = z.enum(["full"])
6
+
7
+ export const projectAccessScope = z.discriminatedUnion("type", [
8
+ z.object({
9
+ type: z.literal("instance"),
10
+ actions: instanceActionSchema.array(),
11
+ instanceIds: z.string().array(),
12
+ }),
13
+ z.object({
14
+ type: z.literal("service-account"),
15
+ actions: serviceAccountActionSchema.array(),
16
+ serviceAccountIds: z.string().array(),
17
+ }),
18
+ ])
19
+
20
+ export const projectApiKeySchema = z.object({
21
+ id: z.string().ulid(),
22
+ meta: objectMetaSchema,
23
+
24
+ /**
25
+ * The 256-bit random token used to authenticate API requests.
26
+ */
27
+ token: z.string(),
28
+
29
+ scopes: z.array(projectAccessScope),
30
+ })
31
+
32
+ export type InstanceAction = z.infer<typeof instanceActionSchema>
33
+ export type ProjectAccessScope = z.infer<typeof projectAccessScope>
34
+ export type ProjectApiKey = z.infer<typeof projectApiKeySchema>
35
+
36
+ function hasAll<T>(array: T[], values: T[]): boolean {
37
+ return values.every(value => array.includes(value))
38
+ }
39
+
40
+ export function validateInstanceAccess(
41
+ apiKey: ProjectApiKey,
42
+ actions: InstanceAction[],
43
+ instanceIds: string[],
44
+ ): boolean {
45
+ return apiKey.scopes
46
+ .filter(scope => scope.type === "instance")
47
+ .some(scope => hasAll(scope.actions, actions) && hasAll(scope.instanceIds, instanceIds))
48
+ }
49
+
50
+ type ActionDescriptionMap<TType extends ProjectAccessScope["type"] = ProjectAccessScope["type"]> = {
51
+ [K in TType]: Record<(ProjectAccessScope & { type: K })["actions"][number], string>
52
+ }
53
+
54
+ export const projectAccessActionDescriptions: ActionDescriptionMap = {
55
+ "service-account": {
56
+ full: "Full access to the service account and all resources owned by it and granted to it.",
57
+ },
58
+ instance: {
59
+ read: "Read access to the instance, including its state all resources owned by it.",
60
+ write: "Write access to the instance, including its state and all resources owned by it.",
61
+ },
62
+ }
@@ -0,0 +1,113 @@
1
+ import { z } from "zod"
2
+ import {
3
+ HighstateSignature,
4
+ type File,
5
+ type FileContent,
6
+ type FileMeta,
7
+ type UnitArtifact,
8
+ } from "@highstate/contract"
9
+ import { objectMetaSchema, userObjectMetaSchema } from "../base"
10
+
11
+ export const artifactUsageSchema = z.discriminatedUnion("type", [
12
+ z.object({
13
+ type: z.literal("instance"),
14
+ instanceId: z.string(),
15
+ }),
16
+ z.object({
17
+ type: z.literal("terminal"),
18
+ terminalId: z.string(),
19
+ }),
20
+ z.object({
21
+ type: z.literal("service-account"),
22
+ serviceAccountId: z.string(),
23
+ }),
24
+ ])
25
+
26
+ export type ArtifactUsage = z.infer<typeof artifactUsageSchema>
27
+
28
+ export function compareArtifactUsage(a: ArtifactUsage, b: ArtifactUsage): boolean {
29
+ if (a.type === "instance" && b.type === "instance") {
30
+ return a.instanceId === b.instanceId
31
+ }
32
+
33
+ if (a.type === "terminal" && b.type === "terminal") {
34
+ return a.terminalId === b.terminalId
35
+ }
36
+
37
+ if (a.type === "service-account" && b.type === "service-account") {
38
+ return a.serviceAccountId === b.serviceAccountId
39
+ }
40
+
41
+ return false
42
+ }
43
+
44
+ /**
45
+ * File metadata schema.
46
+ */
47
+ export const fileMetaSchema = z.object({
48
+ name: z.string(),
49
+ size: z.number().optional(),
50
+ isBinary: z.boolean().optional(),
51
+ mode: z.number().optional(),
52
+ }) satisfies z.ZodType<FileMeta>
53
+
54
+ /**
55
+ * Complete artifact schema for database storage.
56
+ * Contains all artifact information.
57
+ */
58
+ export const artifactSchema = z.object({
59
+ /**
60
+ * The UUIDv7 of the artifact generated when the artifact with unique content is first stored.
61
+ */
62
+ id: z.string().uuid(),
63
+
64
+ meta: objectMetaSchema,
65
+
66
+ /**
67
+ * The SHA-256 hash of the artifact content before encryption.
68
+ */
69
+ hash: z.string(),
70
+
71
+ /**
72
+ * The list of usages of this artifact.
73
+ *
74
+ * When became empty, the artifact is garbage collected.
75
+ */
76
+ usages: artifactUsageSchema.array(),
77
+
78
+ /**
79
+ * The size of the unencrypted artifact archive in bytes.
80
+ */
81
+ size: z.number(),
82
+
83
+ /**
84
+ * The size of each chunk of the artifact which independently encrypted.
85
+ */
86
+ chunkSize: z.number(),
87
+ })
88
+
89
+ export type Artifact = z.infer<typeof artifactSchema>
90
+
91
+ /**
92
+ * Artifact schema for unit API.
93
+ */
94
+ export const unitArtifactSchema = z.object({
95
+ hash: z.string(),
96
+ meta: userObjectMetaSchema.optional(),
97
+ }) satisfies z.ZodType<UnitArtifact>
98
+
99
+ export const fileContentSchema = z.union([
100
+ z.object({
101
+ type: z.literal("embedded"),
102
+ value: z.string(),
103
+ }),
104
+ z.object({
105
+ type: z.literal("artifact"),
106
+ [HighstateSignature.Artifact]: unitArtifactSchema,
107
+ }),
108
+ ]) satisfies z.ZodType<FileContent>
109
+
110
+ export const fileSchema = z.object({
111
+ meta: fileMetaSchema,
112
+ content: fileContentSchema,
113
+ }) satisfies z.ZodType<File>
@@ -0,0 +1,45 @@
1
+ import { z } from "zod"
2
+
3
+ export const metaSchema = z.object({
4
+ displayName: z.string().optional(),
5
+ description: z.string().optional(),
6
+ color: z.string().optional(),
7
+ })
8
+
9
+ export const entitySchema = z.object({
10
+ type: z.string(),
11
+ schema: z.any(), // TSchema from TypeBox
12
+ meta: metaSchema,
13
+ definitionHash: z.string(),
14
+ })
15
+
16
+ export const componentMetaSchema = metaSchema.extend({
17
+ primaryIcon: z.string().optional(),
18
+ primaryIconColor: z.string().optional(),
19
+ secondaryIcon: z.string().optional(),
20
+ secondaryIconColor: z.string().optional(),
21
+ category: z.string().optional(),
22
+ defaultNamePrefix: z.string().optional(),
23
+ })
24
+
25
+ export const componentArgumentSchema = z.object({
26
+ schema: z.any(), // ArgumentValueSchema from TypeBox
27
+ required: z.boolean(),
28
+ meta: componentMetaSchema,
29
+ })
30
+
31
+ export const componentInputSchema = z.object({
32
+ type: z.string(),
33
+ required: z.boolean(),
34
+ multiple: z.boolean(),
35
+ meta: componentMetaSchema,
36
+ })
37
+
38
+ export const componentModelSchema = z.object({
39
+ type: z.string(),
40
+ args: z.record(componentArgumentSchema),
41
+ inputs: z.record(componentInputSchema),
42
+ outputs: z.record(componentInputSchema),
43
+ meta: componentMetaSchema,
44
+ definitionHash: z.string(),
45
+ })
@@ -0,0 +1,14 @@
1
+ export * from "./instance"
2
+ export * from "./state"
3
+ export * from "./operation"
4
+ export * from "./terminal"
5
+ export * from "./page"
6
+ export * from "./artifact"
7
+ export * from "./trigger"
8
+ export * from "./unlock-method"
9
+ export * from "./secret"
10
+ export * from "./lock"
11
+ export * from "./component"
12
+ export * from "./service-account"
13
+ export * from "./worker"
14
+ export * from "./api-key"
@@ -38,11 +38,23 @@ export const instanceModelSchema = z.object({
38
38
  export const compositeInstanceSchema = z.object({
39
39
  instance: instanceModelSchema,
40
40
  children: z.array(instanceModelSchema),
41
+ childCompositeInstanceIds: z.array(z.string()).optional(),
41
42
  inputHash: z.string().optional(),
42
43
  })
43
44
 
44
45
  export type CompositeInstance = z.infer<typeof compositeInstanceSchema>
45
46
 
47
+ export const compositeInstanceEventSchema = z.discriminatedUnion("type", [
48
+ z.object({
49
+ type: z.literal("updated"),
50
+ instance: compositeInstanceSchema,
51
+ }),
52
+ z.object({
53
+ type: z.literal("deleted"),
54
+ instanceId: z.string(),
55
+ }),
56
+ ])
57
+
46
58
  export const hubModelPatchSchema = z.object({
47
59
  position: positionSchema.optional(),
48
60
  inputs: z.array(instanceInputSchema).optional(),
@@ -0,0 +1,91 @@
1
+ import { z } from "zod"
2
+ import { objectMetaSchema } from "../base"
3
+
4
+ export const instanceLockSpecSchema = z.union([
5
+ z.object({
6
+ type: z.literal("operation"),
7
+
8
+ /**
9
+ * The operation ID. Will be used to fetch the operation and detect whether it is still running or lost.
10
+ */
11
+ operationId: z.string(),
12
+ }),
13
+ z.object({
14
+ type: z.literal("evaluation"),
15
+
16
+ /**
17
+ * The internal evaluation ID that is used to detect lost evaluations and restart them.
18
+ */
19
+ evaluationId: z.string(),
20
+ }),
21
+ z.object({
22
+ type: z.literal("custom"),
23
+
24
+ /**
25
+ * The ID of the party that holds the lock.
26
+ *
27
+ * Each locker must only manage the locks it has created.
28
+ */
29
+ lockerId: z.string(),
30
+
31
+ /**
32
+ * The ID of the lock provided by the locker.
33
+ *
34
+ * Must be unique across all locks in the locker system.
35
+ */
36
+ lockId: z.string(),
37
+
38
+ /**
39
+ * The optional payload provided by the locker.
40
+ *
41
+ * Can be used to store additional information about the lock,
42
+ * like the ID of the lock in the locker system,
43
+ * or any other metadata that is relevant to the lock.
44
+ */
45
+ payload: z.record(z.string()).optional(),
46
+ }),
47
+ ])
48
+
49
+ export const instanceLockSchema = z.object({
50
+ id: z.string(),
51
+ meta: objectMetaSchema,
52
+ spec: instanceLockSpecSchema,
53
+ })
54
+
55
+ export type InstanceLock = z.infer<typeof instanceLockSchema>
56
+ export type InstanceLockSpec = z.infer<typeof instanceLockSpecSchema>
57
+
58
+ /**
59
+ * Compares two instance lock specifications for equality.
60
+ *
61
+ * @param a The first instance lock specification to compare.
62
+ * @param b The second instance lock specification to compare.
63
+ *
64
+ * @returns True if both specifications are of the same type and have matching identifiers, false otherwise.
65
+ */
66
+ export function compareInstanceLockSpecs(a: InstanceLockSpec, b: InstanceLockSpec): boolean {
67
+ if (a.type === "operation" && b.type === "operation") {
68
+ return a.operationId === b.operationId
69
+ }
70
+
71
+ if (a.type === "evaluation" && b.type === "evaluation") {
72
+ return a.evaluationId === b.evaluationId
73
+ }
74
+
75
+ if (a.type === "custom" && b.type === "custom") {
76
+ return a.lockerId === b.lockerId && a.lockId === b.lockId
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+ export const instanceLockEventSchema = z.discriminatedUnion("type", [
83
+ z.object({
84
+ type: z.literal("locked"),
85
+ locks: z.array(instanceLockSchema),
86
+ }),
87
+ z.object({
88
+ type: z.literal("unlocked"),
89
+ instanceIds: z.array(z.string()),
90
+ }),
91
+ ])
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod"
2
+ import { objectMetaSchema } from "../base"
2
3
 
3
4
  export const operationTypeSchema = z.enum(["update", "preview", "destroy", "recreate", "refresh"])
4
5
 
@@ -8,6 +9,7 @@ export const operationStatusSchema = z.enum([
8
9
  "completed",
9
10
  "failed",
10
11
  "cancelled",
12
+ "failing",
11
13
  ])
12
14
 
13
15
  export const operationOptionsSchema = z.object({
@@ -81,18 +83,20 @@ export const operationOptionsSchema = z.object({
81
83
  refresh: z.boolean().default(false),
82
84
  })
83
85
 
84
- export const projectOperationRequestSchema = z.object({
86
+ export const operationRequestSchema = z.object({
85
87
  projectId: z.string(),
86
88
  type: operationTypeSchema,
87
89
  instanceIds: z.array(z.string()),
88
90
  options: operationOptionsSchema.partial().optional(),
89
91
  })
90
92
 
91
- export const projectOperationSchema = z.object({
93
+ export const operationSchema = z.object({
92
94
  id: z.string().uuid(),
95
+ meta: objectMetaSchema,
93
96
  status: operationStatusSchema,
94
97
 
95
- projectId: z.string(),
98
+ error: z.string().nullable(),
99
+
96
100
  type: operationTypeSchema,
97
101
 
98
102
  requestedInstanceIds: z.array(z.string()),
@@ -100,19 +104,36 @@ export const projectOperationSchema = z.object({
100
104
  instanceIdsToUpdate: z.array(z.string()).default(() => []),
101
105
  instanceIdsToDestroy: z.array(z.string()).default(() => []),
102
106
 
103
- options: operationOptionsSchema.default(() => ({})),
107
+ options: operationOptionsSchema,
104
108
 
105
- error: z.string().nullable(),
106
109
  startedAt: z.number(),
107
110
  completedAt: z.number().nullable(),
108
111
  })
109
112
 
110
113
  export type OperationType = z.infer<typeof operationTypeSchema>
111
114
  export type OperationStatus = z.infer<typeof operationStatusSchema>
112
- export type ProjectOperation = z.infer<typeof projectOperationSchema>
113
- export type ProjectOperationRequest = z.infer<typeof projectOperationRequestSchema>
115
+ export type Operation = z.infer<typeof operationSchema>
116
+ export type OperationRequest = z.infer<typeof operationRequestSchema>
114
117
  export type OperationOptions = z.infer<typeof operationOptionsSchema>
115
118
 
116
119
  export function isFinalOperationStatus(status: OperationStatus): boolean {
117
120
  return status === "completed" || status === "failed" || status === "cancelled"
118
121
  }
122
+
123
+ export const operationLogEntrySchema = z.tuple([
124
+ z.string().uuid(), // UUIDv7
125
+ z.string(), // The log message
126
+ ])
127
+
128
+ export type OperationLogEntry = z.infer<typeof operationLogEntrySchema>
129
+
130
+ export const operationEventSchema = z.discriminatedUnion("type", [
131
+ z.object({
132
+ type: z.literal("updated"),
133
+ operation: operationSchema,
134
+ }),
135
+ z.object({
136
+ type: z.literal("deleted"),
137
+ operationId: z.string(),
138
+ }),
139
+ ])
@@ -0,0 +1,57 @@
1
+ import { z } from "zod"
2
+ import { objectMetaSchema, userObjectMetaSchema } from "../base"
3
+ import { fileSchema } from "./artifact"
4
+
5
+ /**
6
+ * Page block schema for database storage and unit API.
7
+ */
8
+ export const pageBlockSchema = z.union([
9
+ z.object({
10
+ type: z.literal("markdown"),
11
+ content: z.string(),
12
+ }),
13
+ z.object({
14
+ type: z.literal("qr"),
15
+ content: z.string(),
16
+ showContent: z.coerce.boolean(),
17
+ language: z.string().optional(),
18
+ }),
19
+ fileSchema.extend({
20
+ type: z.literal("file"),
21
+ }),
22
+ ])
23
+
24
+ export type PageBlock = z.infer<typeof pageBlockSchema>
25
+
26
+ /**
27
+ * Page info for frontend display.
28
+ * Contains only basic information visible to the frontend.
29
+ */
30
+ export const pageSchema = z.object({
31
+ id: z.string(),
32
+ instanceId: z.string().optional(),
33
+ meta: objectMetaSchema,
34
+ })
35
+
36
+ export type Page = z.infer<typeof pageSchema>
37
+
38
+ /**
39
+ * The content of a page separated from the page info.
40
+ */
41
+ export const pageContentSchema = z.object({
42
+ id: z.string(),
43
+ content: z.array(pageBlockSchema),
44
+ })
45
+
46
+ export type PageContent = z.infer<typeof pageContentSchema>
47
+
48
+ /**
49
+ * Page schema for unit API.
50
+ * This is what units provide - excludes id, instanceId and some fields from meta since those are set by the system.
51
+ */
52
+ export const unitPageSchema = pageContentSchema.omit({ id: true }).extend({
53
+ name: z.string(),
54
+ meta: userObjectMetaSchema,
55
+ })
56
+
57
+ export type UnitPage = z.infer<typeof unitPageSchema>