@highstate/backend 0.4.3 → 0.4.5

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.
@@ -0,0 +1,512 @@
1
+ import { z } from 'zod';
2
+ import { omit, unique, fromEntries } from 'remeda';
3
+ import { sha256 } from 'crypto-hash';
4
+
5
+ const positionSchema = z.object({
6
+ x: z.number(),
7
+ y: z.number()
8
+ });
9
+ const instanceInputSchema = z.object({
10
+ instanceId: z.string(),
11
+ output: z.string()
12
+ });
13
+ const hubInstanceInputSchema = z.object({
14
+ hubId: z.string()
15
+ });
16
+ const instanceModelPatchSchema = z.object({
17
+ args: z.record(z.unknown()).optional(),
18
+ inputs: z.record(z.array(instanceInputSchema)).optional(),
19
+ hubInputs: z.record(z.array(hubInstanceInputSchema)).optional(),
20
+ injectionInputs: z.array(hubInstanceInputSchema).optional(),
21
+ position: positionSchema.optional()
22
+ });
23
+ const instanceModelSchema = z.object({
24
+ id: z.string(),
25
+ type: z.string(),
26
+ name: z.string(),
27
+ ...instanceModelPatchSchema.shape,
28
+ parentId: z.string().optional(),
29
+ outputs: z.record(z.array(instanceInputSchema)).optional()
30
+ });
31
+ const compositeInstanceSchema = z.object({
32
+ instance: instanceModelSchema,
33
+ children: z.array(instanceModelSchema)
34
+ });
35
+ const hubModelPatchSchema = z.object({
36
+ position: positionSchema.optional(),
37
+ inputs: z.array(instanceInputSchema).optional(),
38
+ injectionInputs: z.array(hubInstanceInputSchema).optional()
39
+ });
40
+ const hubModelSchema = z.object({
41
+ id: z.string().nanoid(),
42
+ position: positionSchema,
43
+ inputs: z.array(instanceInputSchema).optional(),
44
+ injectionInputs: z.array(hubInstanceInputSchema).optional()
45
+ });
46
+
47
+ const instanceStatusSchema = z.enum([
48
+ "not_created",
49
+ "updating",
50
+ "destroying",
51
+ "refreshing",
52
+ "created",
53
+ "error",
54
+ "pending",
55
+ "unknown"
56
+ ]);
57
+ const instanceStatusFieldSchema = z.object({
58
+ name: z.string(),
59
+ value: z.string().optional(),
60
+ displayName: z.string().optional(),
61
+ sensitive: z.boolean().optional(),
62
+ url: z.string().optional()
63
+ });
64
+ const instanceFileMetaSchema = z.object({
65
+ name: z.string(),
66
+ contentType: z.string(),
67
+ isBinary: z.boolean().optional()
68
+ });
69
+ const instanceFileSchema = z.object({
70
+ ...instanceFileMetaSchema.shape,
71
+ content: z.string()
72
+ });
73
+ const instancePageBlockSchema = z.union([
74
+ z.object({
75
+ type: z.literal("markdown"),
76
+ content: z.string()
77
+ }),
78
+ z.object({
79
+ type: z.literal("qr"),
80
+ content: z.string(),
81
+ showContent: z.coerce.boolean(),
82
+ language: z.string().optional()
83
+ }),
84
+ z.object({
85
+ type: z.literal("file"),
86
+ fileMeta: instanceFileMetaSchema
87
+ })
88
+ ]);
89
+ const instancePageMetaSchema = z.object({
90
+ name: z.string(),
91
+ title: z.string()
92
+ });
93
+ const instancePageSchema = z.object({
94
+ ...instancePageMetaSchema.shape,
95
+ content: z.array(instancePageBlockSchema)
96
+ });
97
+ const instanceTerminalMetaSchema = z.object({
98
+ name: z.string(),
99
+ title: z.string(),
100
+ description: z.string().optional()
101
+ });
102
+ const instanceTerminalFileSchema = z.object({
103
+ content: z.string().optional(),
104
+ isBinary: z.boolean().optional()
105
+ });
106
+ const instanceTerminalSchema = z.object({
107
+ ...instanceTerminalMetaSchema.shape,
108
+ image: z.string(),
109
+ command: z.string().array(),
110
+ cwd: z.string().optional(),
111
+ env: z.record(z.string()).optional(),
112
+ files: z.record(instanceTerminalFileSchema).optional()
113
+ });
114
+ const instanceTriggerSpecSchema = z.union([
115
+ z.object({
116
+ type: z.literal("before-destroy")
117
+ }),
118
+ z.object({
119
+ type: z.literal("schedule"),
120
+ schedule: z.string()
121
+ })
122
+ ]);
123
+ const instanceTriggerSchema = z.object({
124
+ name: z.string(),
125
+ title: z.string(),
126
+ description: z.string().optional(),
127
+ spec: instanceTriggerSpecSchema
128
+ });
129
+ const instanceTriggerInvocationSchema = z.object({
130
+ name: z.string()
131
+ });
132
+ const instanceStateSchema = z.object({
133
+ id: z.string(),
134
+ parentId: z.string().nullable().default(null),
135
+ status: instanceStatusSchema,
136
+ latestOperationId: z.string().nullable().default(null),
137
+ currentResourceCount: z.number().nullable().default(null),
138
+ totalResourceCount: z.number().nullable().default(null),
139
+ inputHash: z.string().nullable().default(null),
140
+ outputHash: z.string().nullable().default(null),
141
+ error: z.string().nullable().default(null),
142
+ statusFields: z.array(instanceStatusFieldSchema).default(() => []),
143
+ files: z.array(instanceFileMetaSchema).default(() => []),
144
+ pages: z.array(instancePageMetaSchema).default(() => []),
145
+ terminals: z.array(instanceTerminalMetaSchema).default(() => []),
146
+ triggers: z.array(instanceTriggerSchema).default(() => [])
147
+ });
148
+ const instanceStatePatchSchema = z.object({
149
+ ...instanceStateSchema.shape,
150
+ // secrets updated by the unit
151
+ secrets: z.record(z.string()).nullable(),
152
+ // log line emitted by the unit
153
+ logLine: z.string().nullable()
154
+ }).partial();
155
+ function createInstanceState(id, status = "not_created", fields = {}) {
156
+ return {
157
+ id,
158
+ parentId: null,
159
+ status,
160
+ latestOperationId: null,
161
+ currentResourceCount: null,
162
+ totalResourceCount: null,
163
+ inputHash: null,
164
+ outputHash: null,
165
+ error: null,
166
+ statusFields: [],
167
+ files: [],
168
+ pages: [],
169
+ terminals: [],
170
+ triggers: [],
171
+ ...fields
172
+ };
173
+ }
174
+ function applyPartialInstanceState(states, patch) {
175
+ if (!patch.id) {
176
+ throw new Error("The ID of the instance state is required.");
177
+ }
178
+ let state = states.get(patch.id);
179
+ if (!state) {
180
+ state = createInstanceState(patch.id, "unknown");
181
+ states.set(patch.id, state);
182
+ }
183
+ if (patch.status !== void 0) {
184
+ state.status = patch.status;
185
+ }
186
+ if (patch.latestOperationId !== void 0) {
187
+ state.latestOperationId = patch.latestOperationId;
188
+ }
189
+ if (patch.currentResourceCount !== void 0) {
190
+ state.currentResourceCount = patch.currentResourceCount;
191
+ }
192
+ if (patch.totalResourceCount !== void 0) {
193
+ state.totalResourceCount = patch.totalResourceCount;
194
+ }
195
+ if (patch.inputHash !== void 0) {
196
+ state.inputHash = patch.inputHash;
197
+ }
198
+ if (patch.outputHash !== void 0) {
199
+ state.outputHash = patch.outputHash;
200
+ }
201
+ if (patch.error !== void 0) {
202
+ state.error = patch.error;
203
+ }
204
+ if (patch.statusFields !== void 0) {
205
+ state.statusFields = patch.statusFields;
206
+ }
207
+ if (patch.files !== void 0) {
208
+ state.files = patch.files;
209
+ }
210
+ if (patch.pages !== void 0) {
211
+ state.pages = patch.pages;
212
+ }
213
+ if (patch.terminals !== void 0) {
214
+ state.terminals = patch.terminals;
215
+ }
216
+ if (patch.triggers !== void 0) {
217
+ state.triggers = patch.triggers;
218
+ }
219
+ return state;
220
+ }
221
+ function createInstanceStateFrontendPatch(patch) {
222
+ return omit(patch, ["secrets", "logLine"]);
223
+ }
224
+
225
+ const operationTypeSchema = z.enum(["update", "destroy", "recreate", "refresh", "evaluate"]);
226
+ const operationStatusSchema = z.enum([
227
+ "pending",
228
+ "evaluating",
229
+ "running",
230
+ "completed",
231
+ "failed",
232
+ "cancelled"
233
+ ]);
234
+ const projectOperationSchema = z.object({
235
+ id: z.string().uuid(),
236
+ projectId: z.string(),
237
+ type: operationTypeSchema,
238
+ status: operationStatusSchema,
239
+ instanceIds: z.array(z.string()),
240
+ error: z.string().nullable(),
241
+ startedAt: z.number(),
242
+ completedAt: z.number().nullable()
243
+ });
244
+ const projectOperationRequestSchema = z.object({
245
+ projectId: z.string(),
246
+ type: operationTypeSchema,
247
+ instanceIds: z.array(z.string())
248
+ });
249
+ function isFinalOperationStatus(status) {
250
+ return status === "completed" || status === "failed" || status === "cancelled";
251
+ }
252
+
253
+ class CircularDependencyError extends Error {
254
+ constructor(path) {
255
+ super(`Circular dependency detected: ${path.join(" -> ")}`);
256
+ this.name = "CircularDependencyError";
257
+ }
258
+ }
259
+ function createDefaultGraphResolverBackend() {
260
+ const promiseCache = /* @__PURE__ */ new Map();
261
+ return {
262
+ promiseCache,
263
+ setOutput() {
264
+ },
265
+ setDependencies() {
266
+ }
267
+ };
268
+ }
269
+ function defineGraphResolver(options) {
270
+ return (nodes, logger, backend) => {
271
+ backend ??= createDefaultGraphResolverBackend();
272
+ const resolver = (itemId, dependencyChain) => {
273
+ logger.trace({ itemId }, "resolving item");
274
+ const existingPromise = backend.promiseCache.get(itemId);
275
+ if (existingPromise) {
276
+ return existingPromise;
277
+ }
278
+ if (dependencyChain.includes(itemId)) {
279
+ throw new CircularDependencyError([...dependencyChain, itemId]);
280
+ }
281
+ const item = nodes.get(itemId);
282
+ if (!item) {
283
+ return Promise.resolve(void 0);
284
+ }
285
+ const resolve = async () => {
286
+ const dependencies = unique(options.getNodeDependencies(item));
287
+ backend.setDependencies(itemId, dependencies);
288
+ logger.trace({ itemId, dependencies }, "resolving item dependencies");
289
+ const resolvedDependencies = /* @__PURE__ */ new Map();
290
+ const newChain = [...dependencyChain, itemId];
291
+ for (const depId of dependencies) {
292
+ const depResult = await resolver(depId, newChain);
293
+ if (depResult !== void 0) {
294
+ resolvedDependencies.set(depId, depResult);
295
+ }
296
+ }
297
+ return await options.process(item, resolvedDependencies, logger);
298
+ };
299
+ const promise = resolve().then((result) => {
300
+ if (backend.promiseCache.get(itemId) === promise) {
301
+ backend.setOutput(itemId, result);
302
+ }
303
+ return result;
304
+ });
305
+ backend.promiseCache.set(itemId, promise);
306
+ return promise;
307
+ };
308
+ return (id) => resolver(id, []);
309
+ };
310
+ }
311
+
312
+ const createInputResolver = defineGraphResolver({
313
+ getNodeId(node) {
314
+ if (node.kind === "hub") {
315
+ return `hub:${node.hub.id}`;
316
+ }
317
+ return `instance:${node.instance.id}`;
318
+ },
319
+ getNodeDependencies(node) {
320
+ const dependencies = [];
321
+ if (node.kind === "hub") {
322
+ for (const input of node.hub.inputs ?? []) {
323
+ dependencies.push(`instance:${input.instanceId}`);
324
+ }
325
+ for (const input of node.hub.injectionInputs ?? []) {
326
+ dependencies.push(`hub:${input.hubId}`);
327
+ }
328
+ return dependencies;
329
+ }
330
+ for (const inputs of Object.values(node.instance.hubInputs ?? {})) {
331
+ for (const input of inputs) {
332
+ dependencies.push(`hub:${input.hubId}`);
333
+ }
334
+ }
335
+ for (const input of node.instance.injectionInputs ?? []) {
336
+ dependencies.push(`hub:${input.hubId}`);
337
+ }
338
+ return dependencies;
339
+ },
340
+ process(node, dependencies, logger) {
341
+ const getHubOutput = (input) => {
342
+ const output = dependencies.get(`hub:${input.hubId}`);
343
+ if (!output) {
344
+ throw new Error("Hub not found");
345
+ }
346
+ if (output.kind !== "hub") {
347
+ throw new Error("Expected hub node");
348
+ }
349
+ return output;
350
+ };
351
+ const getInstanceOutput = (input) => {
352
+ const output = dependencies.get(`instance:${input.instanceId}`);
353
+ if (!output) {
354
+ throw new Error("Instance not found");
355
+ }
356
+ if (output.kind !== "instance") {
357
+ throw new Error("Expected instance node");
358
+ }
359
+ return output;
360
+ };
361
+ if (node.kind === "hub") {
362
+ const hubResult = /* @__PURE__ */ new Map();
363
+ const addHubResult = (input) => {
364
+ hubResult.set(`${input.input.instanceId}:${input.input.output}`, input);
365
+ };
366
+ for (const input of node.hub.inputs ?? []) {
367
+ const { component } = getInstanceOutput(input);
368
+ const componentInput = component.outputs[input.output];
369
+ if (!componentInput) {
370
+ logger.warn({ msg: "output not found in the component", input, component });
371
+ continue;
372
+ }
373
+ addHubResult({ input, type: componentInput.type });
374
+ }
375
+ for (const injectionInput of node.hub.injectionInputs ?? []) {
376
+ const { resolvedInputs: resolvedInputs2 } = getHubOutput(injectionInput);
377
+ for (const input of resolvedInputs2) {
378
+ addHubResult(input);
379
+ }
380
+ }
381
+ return {
382
+ kind: "hub",
383
+ resolvedInputs: Array.from(hubResult.values())
384
+ };
385
+ }
386
+ const resolvedInputsMap = /* @__PURE__ */ new Map();
387
+ const addInstanceResult = (inputName, input) => {
388
+ let inputs = resolvedInputsMap.get(inputName);
389
+ if (!inputs) {
390
+ inputs = /* @__PURE__ */ new Map();
391
+ resolvedInputsMap.set(inputName, inputs);
392
+ }
393
+ inputs.set(`${input.input.instanceId}:${input.input.output}`, input);
394
+ };
395
+ for (const [inputName, inputs] of Object.entries(node.instance.inputs ?? {})) {
396
+ for (const input of inputs) {
397
+ const componentInput = node.component.inputs[inputName];
398
+ if (!componentInput) {
399
+ logger.warn({ msg: "input not found in the component", input, component: node.component });
400
+ continue;
401
+ }
402
+ addInstanceResult(inputName, { input, type: componentInput.type });
403
+ }
404
+ }
405
+ const injectionInputs = /* @__PURE__ */ new Map();
406
+ const matchedInjectionInputs = /* @__PURE__ */ new Map();
407
+ for (const injectionInput of node.instance.injectionInputs ?? []) {
408
+ const { resolvedInputs: resolvedInputs2 } = getHubOutput(injectionInput);
409
+ for (const input of resolvedInputs2) {
410
+ injectionInputs.set(`${input.input.instanceId}:${input.input.output}`, input);
411
+ }
412
+ }
413
+ for (const [inputName, componentInput] of Object.entries(node.component.inputs ?? {})) {
414
+ const allInputs = new Map(injectionInputs);
415
+ const hubInputs = node.instance.hubInputs?.[inputName] ?? [];
416
+ for (const hubInput of hubInputs) {
417
+ const { resolvedInputs: resolvedInputs2 } = getHubOutput(hubInput);
418
+ for (const input of resolvedInputs2) {
419
+ allInputs.set(`${input.input.instanceId}:${input.input.output}`, input);
420
+ }
421
+ }
422
+ for (const input of allInputs.values()) {
423
+ if (input.type === componentInput.type) {
424
+ addInstanceResult(inputName, input);
425
+ const key = `${input.input.instanceId}:${input.input.output}`;
426
+ if (injectionInputs.has(key)) {
427
+ matchedInjectionInputs.set(key, input);
428
+ }
429
+ }
430
+ }
431
+ }
432
+ const resolvedInputs = fromEntries(
433
+ Array.from(resolvedInputsMap.entries()).map(([inputName, inputs]) => [
434
+ inputName,
435
+ Array.from(inputs.values())
436
+ ])
437
+ );
438
+ return {
439
+ kind: "instance",
440
+ component: node.component,
441
+ resolvedInputs,
442
+ resolvedInjectionInputs: Array.from(injectionInputs.values()),
443
+ matchedInjectionInputs: Array.from(matchedInjectionInputs.values())
444
+ };
445
+ }
446
+ });
447
+ function getResolvedHubInputs(output) {
448
+ if (output.kind !== "hub") {
449
+ throw new Error("Expected hub node");
450
+ }
451
+ return output.resolvedInputs;
452
+ }
453
+ function getResolvedInstanceInputs(output) {
454
+ if (output.kind !== "instance") {
455
+ throw new Error("Expected instance node");
456
+ }
457
+ return output.resolvedInputs;
458
+ }
459
+ function getResolvedInjectionInstanceInputs(output) {
460
+ if (output.kind !== "instance") {
461
+ throw new Error("Expected instance node");
462
+ }
463
+ return output.resolvedInjectionInputs;
464
+ }
465
+ function getMatchedInjectionInstanceInputs(output) {
466
+ if (output.kind !== "instance") {
467
+ throw new Error("Expected instance node");
468
+ }
469
+ return output.matchedInjectionInputs;
470
+ }
471
+
472
+ const createInputHashResolver = defineGraphResolver({
473
+ getNodeId: (node) => node.instance.id,
474
+ getNodeDependencies({ resolvedInputs }) {
475
+ const dependencies = [];
476
+ for (const inputs of Object.values(resolvedInputs ?? {})) {
477
+ for (const input of inputs) {
478
+ dependencies.push(input.input.instanceId);
479
+ }
480
+ }
481
+ return dependencies;
482
+ },
483
+ async process({ instance, resolvedInputs, sourceHash, state }, dependencies) {
484
+ let sink = JSON.stringify(instance.args ?? {});
485
+ if (sourceHash) {
486
+ sink += sourceHash;
487
+ }
488
+ if (state?.outputHash) {
489
+ sink += state.outputHash;
490
+ }
491
+ const sortedInputs = Object.entries(resolvedInputs).sort(([a], [b]) => a.localeCompare(b));
492
+ for (const [inputKey, inputs] of sortedInputs) {
493
+ if (Object.keys(inputs).length === 0) {
494
+ continue;
495
+ }
496
+ sink += inputKey;
497
+ const instanceIds = inputs.map((input) => input.input.instanceId);
498
+ instanceIds.sort();
499
+ for (const instanceId of instanceIds) {
500
+ sink += dependencies.get(instanceId);
501
+ }
502
+ }
503
+ return await sha256(sink);
504
+ }
505
+ });
506
+
507
+ const terminalSessionSchema = z.object({
508
+ id: z.string().uuid(),
509
+ terminalName: z.string()
510
+ });
511
+
512
+ export { instancePageMetaSchema as A, instanceTerminalMetaSchema as B, instanceTerminalFileSchema as C, instanceTriggerSpecSchema as D, instanceTriggerInvocationSchema as E, instanceStatePatchSchema as F, operationTypeSchema as G, operationStatusSchema as H, projectOperationRequestSchema as I, CircularDependencyError as J, createDefaultGraphResolverBackend as K, getResolvedHubInputs as L, getResolvedInstanceInputs as M, getResolvedInjectionInstanceInputs as N, getMatchedInjectionInstanceInputs as O, createInputHashResolver as a, createInstanceState as b, createInputResolver as c, instanceTerminalSchema as d, instancePageSchema as e, instanceFileSchema as f, instanceStatusFieldSchema as g, hubModelSchema as h, instanceModelSchema as i, instanceTriggerSchema as j, compositeInstanceSchema as k, instanceStateSchema as l, isFinalOperationStatus as m, applyPartialInstanceState as n, createInstanceStateFrontendPatch as o, projectOperationSchema as p, defineGraphResolver as q, positionSchema as r, instanceInputSchema as s, terminalSessionSchema as t, hubInstanceInputSchema as u, instanceModelPatchSchema as v, hubModelPatchSchema as w, instanceStatusSchema as x, instanceFileMetaSchema as y, instancePageBlockSchema as z };