@terraforge/core 0.0.9 → 0.0.11

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/index.mjs ADDED
@@ -0,0 +1,1454 @@
1
+ import asyncOnExit from "async-on-exit";
2
+ import promiseLimit from "p-limit";
3
+ import { DirectedGraph } from "graphology";
4
+ import { topologicalGenerations, willCreateCycle } from "graphology-dag";
5
+ import { v5 } from "uuid";
6
+ import { get } from "get-wild";
7
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { lock } from "proper-lockfile";
10
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
11
+ import { DynamoDB } from "@aws-sdk/client-dynamodb";
12
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
13
+ import { createHash } from "node:crypto";
14
+
15
+ //#region src/node.ts
16
+ const nodeMetaSymbol = Symbol("metadata");
17
+ const isNode = (obj) => {
18
+ const meta = obj[nodeMetaSymbol];
19
+ return meta && typeof meta === "object" && meta !== null && "tag" in meta && typeof meta.tag === "string";
20
+ };
21
+ function getMeta(node) {
22
+ return node[nodeMetaSymbol];
23
+ }
24
+ const isResource = (obj) => {
25
+ return isNode(obj) && obj[nodeMetaSymbol].tag === "resource";
26
+ };
27
+ const isDataSource = (obj) => {
28
+ return isNode(obj) && obj[nodeMetaSymbol].tag === "data";
29
+ };
30
+
31
+ //#endregion
32
+ //#region src/group.ts
33
+ var Group = class Group {
34
+ children = [];
35
+ constructor(parent, type, name) {
36
+ this.parent = parent;
37
+ this.type = type;
38
+ this.name = name;
39
+ parent?.children.push(this);
40
+ }
41
+ get urn() {
42
+ return `${this.parent ? this.parent.urn : "urn"}:${this.type}:{${this.name}}`;
43
+ }
44
+ addChild(child) {
45
+ if (isNode(child)) {
46
+ const meta = getMeta(child);
47
+ if (this.children.filter((c) => isResource(c)).map((c) => getMeta(c)).find((c) => c.type === meta.type && c.logicalId === meta.logicalId)) throw new Error(`Duplicate node found: ${meta.type}:${meta.logicalId}`);
48
+ }
49
+ if (child instanceof Group) {
50
+ if (this.children.filter((c) => c instanceof Group).find((c) => c.type === child.type && c.name === child.name)) throw new Error(`Duplicate group found: ${child.type}:${child.name}`);
51
+ }
52
+ this.children.push(child);
53
+ }
54
+ add(...children) {
55
+ for (const child of children) this.addChild(child);
56
+ }
57
+ get nodes() {
58
+ return this.children.map((child) => {
59
+ if (child instanceof Group) return child.nodes;
60
+ if (isNode(child)) return child;
61
+ }).flat().filter((child) => !!child);
62
+ }
63
+ get resources() {
64
+ return this.nodes.filter((node) => isResource(node));
65
+ }
66
+ get dataSources() {
67
+ return this.nodes.filter((node) => isDataSource(node));
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ //#region src/stack.ts
73
+ var Stack = class extends Group {
74
+ dependencies = /* @__PURE__ */ new Set();
75
+ constructor(app, name) {
76
+ super(app, "stack", name);
77
+ this.app = app;
78
+ }
79
+ dependsOn(...stacks) {
80
+ for (const stack of stacks) {
81
+ if (stack.app !== this.app) throw new Error(`Stacks that belong to different apps can't be dependent on each other`);
82
+ this.dependencies.add(stack);
83
+ }
84
+ return this;
85
+ }
86
+ };
87
+ const findParentStack = (group) => {
88
+ if (group instanceof Stack) return group;
89
+ if (!group.parent) throw new Error("No stack found");
90
+ return findParentStack(group.parent);
91
+ };
92
+
93
+ //#endregion
94
+ //#region src/app.ts
95
+ var App = class extends Group {
96
+ constructor(name) {
97
+ super(void 0, "app", name);
98
+ this.name = name;
99
+ }
100
+ get stacks() {
101
+ return this.children.filter((child) => child instanceof Stack);
102
+ }
103
+ };
104
+
105
+ //#endregion
106
+ //#region src/future.ts
107
+ const IDLE = 0;
108
+ const PENDING = 1;
109
+ const RESOLVED = 2;
110
+ const REJECTED = 3;
111
+ var Future = class Future {
112
+ listeners = /* @__PURE__ */ new Set();
113
+ status = IDLE;
114
+ data;
115
+ error;
116
+ constructor(callback) {
117
+ this.callback = callback;
118
+ }
119
+ get [Symbol.toStringTag]() {
120
+ switch (this.status) {
121
+ case IDLE: return `<idle>`;
122
+ case PENDING: return `<pending>`;
123
+ case RESOLVED: return `${this.data}`;
124
+ case REJECTED: return `<rejected> ${this.error}`;
125
+ }
126
+ }
127
+ pipe(cb) {
128
+ return new Future((resolve$1, reject) => {
129
+ this.then((value) => {
130
+ Promise.resolve(cb(value)).then((value$1) => {
131
+ resolve$1(value$1);
132
+ }).catch(reject);
133
+ }, reject);
134
+ });
135
+ }
136
+ then(resolve$1, reject) {
137
+ if (this.status === RESOLVED) resolve$1(this.data);
138
+ else if (this.status === REJECTED) reject?.(this.error);
139
+ else {
140
+ this.listeners.add({
141
+ resolve: resolve$1,
142
+ reject
143
+ });
144
+ if (this.status === IDLE) {
145
+ this.status = PENDING;
146
+ this.callback((data) => {
147
+ if (this.status === PENDING) {
148
+ this.status = RESOLVED;
149
+ this.data = data;
150
+ this.listeners.forEach(({ resolve: resolve$2 }) => resolve$2(data));
151
+ this.listeners.clear();
152
+ }
153
+ }, (error) => {
154
+ if (this.status === PENDING) {
155
+ this.status = REJECTED;
156
+ this.error = error;
157
+ this.listeners.forEach(({ reject: reject$1 }) => reject$1?.(error));
158
+ this.listeners.clear();
159
+ }
160
+ });
161
+ }
162
+ }
163
+ }
164
+ };
165
+
166
+ //#endregion
167
+ //#region src/input.ts
168
+ const findInputDeps = (props) => {
169
+ const deps = [];
170
+ const find = (props$1) => {
171
+ if (props$1 instanceof Output) deps.push(...props$1.dependencies);
172
+ else if (Array.isArray(props$1)) props$1.map(find);
173
+ else if (props$1?.constructor === Object) Object.values(props$1).map(find);
174
+ };
175
+ find(props);
176
+ return deps;
177
+ };
178
+ const resolveInputs = async (inputs) => {
179
+ const unresolved = [];
180
+ const find = (props, parent, key) => {
181
+ if (props instanceof Output || props instanceof Future || props instanceof Promise) unresolved.push([parent, key]);
182
+ else if (Array.isArray(props)) props.map((value, index) => find(value, props, index));
183
+ else if (props?.constructor === Object) Object.entries(props).map(([key$1, value]) => find(value, props, key$1));
184
+ };
185
+ find(inputs, {}, "root");
186
+ const responses = await Promise.all(unresolved.map(async ([obj, key]) => {
187
+ const promise = obj[key];
188
+ let timeout;
189
+ const response = await Promise.race([promise, new Promise((_, reject) => {
190
+ timeout = setTimeout(() => {
191
+ if (promise instanceof Output) reject(/* @__PURE__ */ new Error(`Resolving Output<${[...promise.dependencies].map((d) => d.urn).join(", ")}> took too long.`));
192
+ else if (promise instanceof Future) reject(/* @__PURE__ */ new Error("Resolving Future took too long."));
193
+ else reject(/* @__PURE__ */ new Error("Resolving Promise took too long."));
194
+ }, 3e3);
195
+ })]);
196
+ clearTimeout(timeout);
197
+ return response;
198
+ }));
199
+ unresolved.forEach(([props, key], i) => {
200
+ props[key] = responses[i];
201
+ });
202
+ return inputs;
203
+ };
204
+
205
+ //#endregion
206
+ //#region src/output.ts
207
+ var Output = class Output extends Future {
208
+ constructor(dependencies, callback) {
209
+ super(callback);
210
+ this.dependencies = dependencies;
211
+ }
212
+ pipe(cb) {
213
+ return new Output(this.dependencies, (resolve$1, reject) => {
214
+ this.then((value) => {
215
+ Promise.resolve(cb(value)).then((value$1) => {
216
+ resolve$1(value$1);
217
+ }).catch(reject);
218
+ }, reject);
219
+ });
220
+ }
221
+ };
222
+ const deferredOutput = (cb) => {
223
+ return new Output(/* @__PURE__ */ new Set(), cb);
224
+ };
225
+ const output = (value) => {
226
+ return deferredOutput((resolve$1) => resolve$1(value));
227
+ };
228
+ const combine = (...inputs) => {
229
+ return new Output(new Set(findInputDeps(inputs)), (resolve$1, reject) => {
230
+ Promise.all(inputs).then((result) => {
231
+ resolve$1(result);
232
+ }, reject);
233
+ });
234
+ };
235
+ const resolve = (inputs, transformer) => {
236
+ return combine(...inputs).pipe((data) => {
237
+ return transformer(...data);
238
+ });
239
+ };
240
+ const interpolate = (literals, ...placeholders) => {
241
+ return combine(...placeholders).pipe((unwrapped) => {
242
+ const result = [];
243
+ for (let i = 0; i < unwrapped.length; i++) result.push(literals[i], unwrapped[i]);
244
+ result.push(literals.at(-1));
245
+ return result.join("");
246
+ });
247
+ };
248
+
249
+ //#endregion
250
+ //#region src/urn.ts
251
+ const createUrn = (tag, type, name, parentUrn) => {
252
+ return `${parentUrn ? parentUrn : "urn"}:${tag}:${type}:{${name}}`;
253
+ };
254
+
255
+ //#endregion
256
+ //#region src/meta.ts
257
+ const createMeta = (tag, provider, parent, type, logicalId, input, config) => {
258
+ const urn = createUrn(tag, type, logicalId, parent.urn);
259
+ const stack = findParentStack(parent);
260
+ let output$1;
261
+ return {
262
+ tag,
263
+ urn,
264
+ logicalId,
265
+ type,
266
+ stack,
267
+ provider,
268
+ input,
269
+ config,
270
+ get dependencies() {
271
+ const dependencies = /* @__PURE__ */ new Set();
272
+ const linkMetaDep = (dep) => {
273
+ if (dep.urn === urn) throw new Error("You can't depend on yourself");
274
+ dependencies.add(dep.urn);
275
+ };
276
+ for (const dep of findInputDeps(input)) linkMetaDep(dep);
277
+ for (const dep of config?.dependsOn ?? []) linkMetaDep(getMeta(dep));
278
+ return dependencies;
279
+ },
280
+ resolve(data) {
281
+ output$1 = data;
282
+ },
283
+ output(cb) {
284
+ return new Output(new Set([this]), (resolve$1) => {
285
+ if (!output$1) throw new Error(`Unresolved output for ${tag}: ${urn}`);
286
+ resolve$1(cb(output$1));
287
+ });
288
+ }
289
+ };
290
+ };
291
+
292
+ //#endregion
293
+ //#region src/debug.ts
294
+ let enabled = false;
295
+ const enableDebug = () => {
296
+ enabled = true;
297
+ };
298
+ const createDebugger = (group) => {
299
+ return (...args) => {
300
+ if (!enabled) return;
301
+ console.log();
302
+ console.log(`${group}:`, ...args);
303
+ console.log();
304
+ };
305
+ };
306
+
307
+ //#endregion
308
+ //#region src/workspace/exit.ts
309
+ const listeners = /* @__PURE__ */ new Set();
310
+ let listening = false;
311
+ const onExit = (cb) => {
312
+ listeners.add(cb);
313
+ if (!listening) {
314
+ listening = true;
315
+ asyncOnExit(async () => {
316
+ await Promise.allSettled([...listeners].map((cb$1) => cb$1()));
317
+ }, true);
318
+ }
319
+ return () => {
320
+ listeners.delete(cb);
321
+ if (listeners.size === 0) {
322
+ listening = false;
323
+ asyncOnExit.dispose();
324
+ }
325
+ };
326
+ };
327
+
328
+ //#endregion
329
+ //#region src/workspace/lock.ts
330
+ const lockApp = async (lockBackend, app, fn) => {
331
+ let releaseLock;
332
+ try {
333
+ releaseLock = await lockBackend.lock(app.urn);
334
+ } catch (error) {
335
+ throw new Error(`Already in progress: ${app.urn}`);
336
+ }
337
+ const releaseExit = onExit(async () => {
338
+ await releaseLock();
339
+ });
340
+ let result;
341
+ try {
342
+ result = await fn();
343
+ } catch (error) {
344
+ throw error;
345
+ } finally {
346
+ await releaseLock();
347
+ releaseExit();
348
+ }
349
+ return result;
350
+ };
351
+
352
+ //#endregion
353
+ //#region src/workspace/concurrency.ts
354
+ const createConcurrencyQueue = (concurrency) => {
355
+ const queue = promiseLimit(concurrency);
356
+ return (cb) => {
357
+ return queue(cb);
358
+ };
359
+ };
360
+
361
+ //#endregion
362
+ //#region src/workspace/entries.ts
363
+ const entries = (object) => {
364
+ return Object.entries(object);
365
+ };
366
+
367
+ //#endregion
368
+ //#region src/workspace/dependency.ts
369
+ var DependencyGraph = class {
370
+ graph = new DirectedGraph();
371
+ callbacks = /* @__PURE__ */ new Map();
372
+ add(urn, deps, callback) {
373
+ this.callbacks.set(urn, callback);
374
+ this.graph.mergeNode(urn);
375
+ for (const dep of deps) {
376
+ if (!dep) throw new Error(`Resource ${urn} has an undefined dependency.`);
377
+ if (willCreateCycle(this.graph, dep, urn)) throw new Error(`There is a circular dependency between ${urn} -> ${dep}`);
378
+ this.graph.mergeEdge(dep, urn);
379
+ }
380
+ }
381
+ validate() {
382
+ const nodes = this.graph.nodes();
383
+ for (const urn of nodes) if (!this.callbacks.has(urn)) {
384
+ const deps = this.graph.filterNodes((node) => {
385
+ return this.graph.areNeighbors(node, urn);
386
+ });
387
+ throw new Error(`The following resources ${deps.join(", ")} have a missing dependency: ${urn}`);
388
+ }
389
+ }
390
+ async run() {
391
+ this.validate();
392
+ const graph = topologicalGenerations(this.graph);
393
+ const errors = [];
394
+ for (const list of graph) {
395
+ const result = await Promise.allSettled(list.map((urn) => {
396
+ const callback = this.callbacks.get(urn);
397
+ if (!callback) return;
398
+ return callback();
399
+ }));
400
+ for (const entry of result) if (entry.status === "rejected") if (entry.reason instanceof Error) errors.push(entry.reason);
401
+ else errors.push(/* @__PURE__ */ new Error(`Unknown error: ${entry.reason}`));
402
+ if (errors.length > 0) break;
403
+ }
404
+ return errors;
405
+ }
406
+ };
407
+ const dependentsOn = (resources, dependency) => {
408
+ const dependents = [];
409
+ for (const [urn, resource] of entries(resources)) if (resource.dependencies.includes(dependency)) dependents.push(urn);
410
+ return dependents;
411
+ };
412
+ const findDependencyPaths = (value, dependencyUrn, path = []) => {
413
+ const paths = [];
414
+ const visit = (current, currentPath) => {
415
+ if (current instanceof Output) {
416
+ for (const dep of current.dependencies) if (dep.urn === dependencyUrn) {
417
+ paths.push(currentPath);
418
+ return;
419
+ }
420
+ return;
421
+ }
422
+ if (Array.isArray(current)) {
423
+ current.forEach((item, index) => {
424
+ visit(item, [...currentPath, index]);
425
+ });
426
+ return;
427
+ }
428
+ if (current && typeof current === "object") for (const [key, item] of Object.entries(current)) visit(item, [...currentPath, key]);
429
+ };
430
+ visit(value, path);
431
+ return paths;
432
+ };
433
+ const cloneState = (value) => JSON.parse(JSON.stringify(value));
434
+ const removeAtPath = (target, path) => {
435
+ if (path.length === 0) return;
436
+ let parent = target;
437
+ for (let i = 0; i < path.length - 1; i++) {
438
+ if (parent == null) return;
439
+ parent = parent[path[i]];
440
+ }
441
+ const last = path[path.length - 1];
442
+ if (Array.isArray(parent) && typeof last === "number") {
443
+ if (last >= 0 && last < parent.length) parent.splice(last, 1);
444
+ return;
445
+ }
446
+ if (parent && typeof parent === "object") delete parent[last];
447
+ };
448
+ const stripDependencyInputs = (input, metaInput, dependencyUrn) => {
449
+ const paths = findDependencyPaths(metaInput, dependencyUrn);
450
+ if (paths.length === 0) return input;
451
+ const detached = cloneState(input);
452
+ const sortedPaths = [...paths].sort((a, b) => {
453
+ if (a.length !== b.length) return b.length - a.length;
454
+ const aLast = a[a.length - 1];
455
+ const bLast = b[b.length - 1];
456
+ if (typeof aLast === "number" && typeof bLast === "number") return bLast - aLast;
457
+ return 0;
458
+ });
459
+ for (const path of sortedPaths) removeAtPath(detached, path);
460
+ return detached;
461
+ };
462
+ const allowsDependentReplace = (replaceOnChanges, dependencyPaths) => {
463
+ if (!replaceOnChanges || replaceOnChanges.length === 0) return false;
464
+ for (const path of dependencyPaths) {
465
+ const base = typeof path[0] === "string" ? path[0] : void 0;
466
+ if (!base) continue;
467
+ for (const replacePath of replaceOnChanges) if (replacePath === base || replacePath.startsWith(`${base}.`) || replacePath.startsWith(`${base}[`) || replacePath.startsWith(`${base}.*`)) return true;
468
+ }
469
+ return false;
470
+ };
471
+
472
+ //#endregion
473
+ //#region src/workspace/error.ts
474
+ var ResourceError = class ResourceError extends Error {
475
+ static wrap(urn, type, operation, error) {
476
+ if (error instanceof Error) return new ResourceError(urn, type, operation, error.message);
477
+ return new ResourceError(urn, type, operation, "Unknown Error");
478
+ }
479
+ constructor(urn, type, operation, message) {
480
+ super(message);
481
+ this.urn = urn;
482
+ this.type = type;
483
+ this.operation = operation;
484
+ }
485
+ };
486
+ var AppError = class extends Error {
487
+ constructor(app, issues, message) {
488
+ super(message);
489
+ this.app = app;
490
+ this.issues = issues;
491
+ }
492
+ };
493
+ var ResourceNotFound = class extends Error {};
494
+ var ResourceAlreadyExists = class extends Error {};
495
+
496
+ //#endregion
497
+ //#region src/workspace/state.ts
498
+ const compareState = (left, right) => {
499
+ const replacer = (_, value) => {
500
+ if (value !== null && value instanceof Object && !Array.isArray(value)) return Object.keys(value).sort().reduce((sorted, key) => {
501
+ sorted[key] = value[key];
502
+ return sorted;
503
+ }, {});
504
+ return value;
505
+ };
506
+ return JSON.stringify(left, replacer) === JSON.stringify(right, replacer);
507
+ };
508
+ const removeEmptyStackStates = (appState) => {
509
+ for (const [stackUrn, stackState] of entries(appState.stacks)) if (Object.keys(stackState.nodes).length === 0) delete appState.stacks[stackUrn];
510
+ };
511
+
512
+ //#endregion
513
+ //#region src/workspace/state/v1.ts
514
+ const v1 = (oldAppState) => {
515
+ const stacks = {};
516
+ for (const [urn, stack] of entries(oldAppState.stacks)) {
517
+ const nodes = {};
518
+ for (const [urn$1, resource] of entries(stack.resources)) nodes[urn$1] = {
519
+ ...resource,
520
+ tag: "resource"
521
+ };
522
+ stacks[urn] = {
523
+ name: stack.name,
524
+ dependencies: stack.dependencies,
525
+ nodes
526
+ };
527
+ }
528
+ return {
529
+ ...oldAppState,
530
+ stacks,
531
+ version: 1
532
+ };
533
+ };
534
+
535
+ //#endregion
536
+ //#region src/workspace/state/v2.ts
537
+ const v2 = (oldAppState) => {
538
+ const stacks = {};
539
+ for (const [urn, stack] of entries(oldAppState.stacks)) stacks[urn] = {
540
+ name: stack.name,
541
+ nodes: stack.nodes
542
+ };
543
+ return {
544
+ ...oldAppState,
545
+ stacks,
546
+ version: 2
547
+ };
548
+ };
549
+
550
+ //#endregion
551
+ //#region src/workspace/state/migrate.ts
552
+ const versions = [[1, v1], [2, v2]];
553
+ const migrateAppState = (oldState) => {
554
+ const version = "version" in oldState && oldState.version || 0;
555
+ for (const [v, migrate] of versions) if (v > version) oldState = migrate(oldState);
556
+ return oldState;
557
+ };
558
+
559
+ //#endregion
560
+ //#region src/provider.ts
561
+ const findProvider = (providers, id) => {
562
+ for (const provider of providers) if (provider.ownResource(id)) return provider;
563
+ throw new TypeError(`Can't find the "${id}" provider.`);
564
+ };
565
+
566
+ //#endregion
567
+ //#region src/workspace/token.ts
568
+ const createIdempotantToken = (appToken, urn, operation) => {
569
+ return v5(`${urn}-${operation}`, appToken);
570
+ };
571
+
572
+ //#endregion
573
+ //#region src/workspace/procedure/delete-resource.ts
574
+ const debug$7 = createDebugger("Delete");
575
+ const deleteResource = async (appToken, urn, state, opt) => {
576
+ debug$7(state.type);
577
+ debug$7(state);
578
+ if (state.lifecycle?.retainOnDelete) {
579
+ debug$7("retain", state.type);
580
+ return;
581
+ }
582
+ const idempotantToken = createIdempotantToken(appToken, urn, "delete");
583
+ const provider = findProvider(opt.providers, state.provider);
584
+ try {
585
+ await opt.hooks?.beforeResourceDelete?.({
586
+ urn,
587
+ type: state.type,
588
+ oldInput: state.input,
589
+ oldOutput: state.output
590
+ });
591
+ await provider.deleteResource({
592
+ type: state.type,
593
+ state: state.output,
594
+ idempotantToken
595
+ });
596
+ await opt.hooks?.afterResourceDelete?.({
597
+ urn,
598
+ type: state.type,
599
+ oldInput: state.input,
600
+ oldOutput: state.output
601
+ });
602
+ } catch (error) {
603
+ if (error instanceof ResourceNotFound) debug$7(state.type, "already deleted");
604
+ else throw ResourceError.wrap(urn, state.type, "delete", error);
605
+ }
606
+ };
607
+
608
+ //#endregion
609
+ //#region src/workspace/procedure/delete-app.ts
610
+ const deleteApp = async (app, opt) => {
611
+ const latestState = await opt.backend.state.get(app.urn);
612
+ if (!latestState) throw new AppError(app.name, [], `App already deleted: ${app.name}`);
613
+ const appState = migrateAppState(latestState);
614
+ if (opt.idempotentToken || !appState.idempotentToken) {
615
+ appState.idempotentToken = opt.idempotentToken ?? crypto.randomUUID();
616
+ await opt.backend.state.update(app.urn, appState);
617
+ }
618
+ let stackStates = Object.values(appState.stacks);
619
+ if (opt.filters && opt.filters.length > 0) stackStates = stackStates.filter((stackState) => opt.filters.includes(stackState.name));
620
+ const queue = createConcurrencyQueue(opt.concurrency ?? 10);
621
+ const graph = new DependencyGraph();
622
+ const allNodes = {};
623
+ for (const stackState of Object.values(appState.stacks)) for (const [urn, nodeState] of entries(stackState.nodes)) allNodes[urn] = nodeState;
624
+ for (const stackState of stackStates) for (const [urn, state] of entries(stackState.nodes)) graph.add(urn, dependentsOn(allNodes, urn), async () => {
625
+ if (state.tag === "resource") await queue(() => deleteResource(appState.idempotentToken, urn, state, opt));
626
+ delete stackState.nodes[urn];
627
+ });
628
+ const errors = await graph.run();
629
+ removeEmptyStackStates(appState);
630
+ delete appState.idempotentToken;
631
+ await opt.backend.state.update(app.urn, appState);
632
+ if (errors.length > 0) throw new AppError(app.name, [...new Set(errors)], "Deleting app failed.");
633
+ if (Object.keys(appState.stacks).length === 0) await opt.backend.state.delete(app.urn);
634
+ };
635
+
636
+ //#endregion
637
+ //#region src/workspace/replacement.ts
638
+ const requiresReplacement = (priorState, proposedState, replaceOnChanges) => {
639
+ for (const path of replaceOnChanges) {
640
+ const priorValue = get(priorState, path);
641
+ const proposedValue = get(proposedState, path);
642
+ if (path.includes("*") && Array.isArray(priorValue)) {
643
+ for (let i = 0; i < priorValue.length; i++) if (!compareState(priorValue[i], proposedValue[i])) return true;
644
+ }
645
+ if (!compareState(priorValue, proposedValue)) return true;
646
+ }
647
+ return false;
648
+ };
649
+
650
+ //#endregion
651
+ //#region src/workspace/procedure/create-resource.ts
652
+ const debug$6 = createDebugger("Create");
653
+ const createResource = async (resource, appToken, input, opt) => {
654
+ const meta = getMeta(resource);
655
+ const provider = findProvider(opt.providers, meta.provider);
656
+ const idempotantToken = createIdempotantToken(appToken, meta.urn, "create");
657
+ debug$6(meta.type);
658
+ debug$6(input);
659
+ let result;
660
+ try {
661
+ await opt.hooks?.beforeResourceCreate?.({
662
+ urn: resource.urn,
663
+ type: meta.type,
664
+ resource,
665
+ newInput: input
666
+ });
667
+ result = await provider.createResource({
668
+ type: meta.type,
669
+ state: input,
670
+ idempotantToken
671
+ });
672
+ await opt.hooks?.afterResourceCreate?.({
673
+ urn: resource.urn,
674
+ type: meta.type,
675
+ resource,
676
+ newInput: input,
677
+ newOutput: result.state
678
+ });
679
+ } catch (error) {
680
+ throw ResourceError.wrap(meta.urn, meta.type, "create", error);
681
+ }
682
+ return {
683
+ tag: "resource",
684
+ version: result.version,
685
+ type: meta.type,
686
+ provider: meta.provider,
687
+ input: meta.input,
688
+ output: result.state
689
+ };
690
+ };
691
+
692
+ //#endregion
693
+ //#region src/workspace/procedure/get-data-source.ts
694
+ const debug$5 = createDebugger("Data Source");
695
+ const getDataSource = async (dataSource, input, opt) => {
696
+ const provider = findProvider(opt.providers, dataSource.provider);
697
+ debug$5(dataSource.type);
698
+ if (!provider.getData) throw new Error(`Provider doesn't support data sources`);
699
+ let result;
700
+ try {
701
+ result = await provider.getData({
702
+ type: dataSource.type,
703
+ state: input
704
+ });
705
+ } catch (error) {
706
+ throw ResourceError.wrap(dataSource.urn, dataSource.type, "get", error);
707
+ }
708
+ return {
709
+ tag: "data",
710
+ type: dataSource.type,
711
+ provider: dataSource.provider,
712
+ input,
713
+ output: result.state
714
+ };
715
+ };
716
+
717
+ //#endregion
718
+ //#region src/workspace/procedure/import-resource.ts
719
+ const debug$4 = createDebugger("Import");
720
+ const importResource = async (resource, input, opt) => {
721
+ const meta = getMeta(resource);
722
+ const provider = findProvider(opt.providers, meta.provider);
723
+ debug$4(meta.type);
724
+ debug$4(input);
725
+ let result;
726
+ try {
727
+ result = await provider.getResource({
728
+ type: meta.type,
729
+ state: {
730
+ ...input,
731
+ id: meta.config?.import
732
+ }
733
+ });
734
+ } catch (error) {
735
+ throw ResourceError.wrap(meta.urn, meta.type, "import", error);
736
+ }
737
+ return {
738
+ tag: "resource",
739
+ version: result.version,
740
+ type: meta.type,
741
+ provider: meta.provider,
742
+ input: meta.input,
743
+ output: result.state
744
+ };
745
+ };
746
+
747
+ //#endregion
748
+ //#region src/workspace/procedure/replace-resource.ts
749
+ const debug$3 = createDebugger("Replace");
750
+ const replaceResource = async (resource, appToken, priorInputState, priorOutputState, proposedState, opt) => {
751
+ const meta = getMeta(resource);
752
+ const urn = meta.urn;
753
+ const type = meta.type;
754
+ const provider = findProvider(opt.providers, meta.provider);
755
+ const idempotantToken = createIdempotantToken(appToken, meta.urn, "replace");
756
+ debug$3(meta.type);
757
+ debug$3(proposedState);
758
+ if (meta.config?.retainOnDelete) debug$3("retain", type);
759
+ else try {
760
+ await opt.hooks?.beforeResourceDelete?.({
761
+ urn,
762
+ type,
763
+ oldInput: priorInputState,
764
+ oldOutput: priorOutputState
765
+ });
766
+ await provider.deleteResource({
767
+ type,
768
+ state: priorOutputState,
769
+ idempotantToken
770
+ });
771
+ await opt.hooks?.afterResourceDelete?.({
772
+ urn,
773
+ type,
774
+ oldInput: priorInputState,
775
+ oldOutput: priorOutputState
776
+ });
777
+ } catch (error) {
778
+ if (error instanceof ResourceNotFound) debug$3(type, "already deleted");
779
+ else throw ResourceError.wrap(urn, type, "replace", error);
780
+ }
781
+ let result;
782
+ try {
783
+ await opt.hooks?.beforeResourceCreate?.({
784
+ urn,
785
+ type,
786
+ resource,
787
+ newInput: proposedState
788
+ });
789
+ result = await provider.createResource({
790
+ type,
791
+ state: proposedState,
792
+ idempotantToken
793
+ });
794
+ await opt.hooks?.afterResourceCreate?.({
795
+ urn,
796
+ type,
797
+ resource,
798
+ newInput: proposedState,
799
+ newOutput: result.state
800
+ });
801
+ } catch (error) {
802
+ throw ResourceError.wrap(urn, type, "replace", error);
803
+ }
804
+ return {
805
+ version: result.version,
806
+ output: result.state
807
+ };
808
+ };
809
+
810
+ //#endregion
811
+ //#region src/workspace/procedure/update-resource.ts
812
+ const debug$2 = createDebugger("Update");
813
+ const updateResource = async (resource, appToken, priorInputState, priorOutputState, proposedState, opt) => {
814
+ const meta = getMeta(resource);
815
+ const provider = findProvider(opt.providers, meta.provider);
816
+ const idempotantToken = createIdempotantToken(appToken, meta.urn, "update");
817
+ let result;
818
+ debug$2(meta.type);
819
+ debug$2(proposedState);
820
+ try {
821
+ await opt.hooks?.beforeResourceUpdate?.({
822
+ urn: resource.urn,
823
+ type: meta.type,
824
+ resource,
825
+ newInput: proposedState,
826
+ oldInput: priorInputState,
827
+ oldOutput: priorOutputState
828
+ });
829
+ result = await provider.updateResource({
830
+ type: meta.type,
831
+ priorState: priorOutputState,
832
+ proposedState,
833
+ idempotantToken
834
+ });
835
+ await opt.hooks?.afterResourceUpdate?.({
836
+ urn: resource.urn,
837
+ type: meta.type,
838
+ resource,
839
+ newInput: proposedState,
840
+ oldInput: priorInputState,
841
+ newOutput: result.state,
842
+ oldOutput: priorOutputState
843
+ });
844
+ } catch (error) {
845
+ throw ResourceError.wrap(meta.urn, meta.type, "update", error);
846
+ }
847
+ return {
848
+ version: result.version,
849
+ output: result.state
850
+ };
851
+ };
852
+
853
+ //#endregion
854
+ //#region src/workspace/procedure/deploy-app.ts
855
+ const debug$1 = createDebugger("Deploy App");
856
+ const deployApp = async (app, opt) => {
857
+ debug$1(app.name, "start");
858
+ const appState = migrateAppState(await opt.backend.state.get(app.urn) ?? {
859
+ name: app.name,
860
+ stacks: {}
861
+ });
862
+ const releaseOnExit = onExit(async () => {
863
+ await opt.backend.state.update(app.urn, appState);
864
+ });
865
+ if (opt.idempotentToken || !appState.idempotentToken) {
866
+ appState.idempotentToken = opt.idempotentToken ?? crypto.randomUUID();
867
+ await opt.backend.state.update(app.urn, appState);
868
+ }
869
+ let stacks = app.stacks;
870
+ let filteredOutStacks = [];
871
+ if (opt.filters && opt.filters.length > 0) {
872
+ stacks = app.stacks.filter((stack) => opt.filters.includes(stack.name));
873
+ filteredOutStacks = app.stacks.filter((stack) => !opt.filters.includes(stack.name));
874
+ }
875
+ const nodeByUrn = /* @__PURE__ */ new Map();
876
+ const stackStates = /* @__PURE__ */ new Map();
877
+ const plannedDependents = /* @__PURE__ */ new Set();
878
+ const forcedUpdateDependents = /* @__PURE__ */ new Set();
879
+ for (const stack of stacks) {
880
+ const stackState = appState.stacks[stack.urn] = appState.stacks[stack.urn] ?? {
881
+ name: stack.name,
882
+ nodes: {}
883
+ };
884
+ stackStates.set(stack.urn, stackState);
885
+ for (const node of stack.nodes) nodeByUrn.set(getMeta(node).urn, node);
886
+ }
887
+ const queue = createConcurrencyQueue(opt.concurrency ?? 10);
888
+ const graph = new DependencyGraph();
889
+ const replacementDeletes = /* @__PURE__ */ new Map();
890
+ const allNodes = {};
891
+ for (const stackState of Object.values(appState.stacks)) for (const [urn, nodeState] of entries(stackState.nodes)) allNodes[urn] = nodeState;
892
+ for (const stack of filteredOutStacks) {
893
+ const stackState = appState.stacks[stack.urn];
894
+ if (stackState) for (const node of stack.nodes) {
895
+ const meta = getMeta(node);
896
+ const nodeState = stackState.nodes[meta.urn];
897
+ if (nodeState && nodeState.output) graph.add(meta.urn, [], async () => {
898
+ debug$1("hydrate", meta.urn);
899
+ meta.resolve(nodeState.output);
900
+ });
901
+ }
902
+ }
903
+ for (const [urn, stackState] of entries(appState.stacks)) {
904
+ const found = app.stacks.find((stack) => {
905
+ return stack.urn === urn;
906
+ });
907
+ const filtered = opt.filters ? opt.filters.find((filter) => filter === stackState.name) : true;
908
+ if (!found && filtered) for (const [urn$1, nodeState] of entries(stackState.nodes)) graph.add(urn$1, dependentsOn(allNodes, urn$1), async () => {
909
+ if (nodeState.tag === "resource") await queue(() => deleteResource(appState.idempotentToken, urn$1, nodeState, opt));
910
+ delete stackState.nodes[urn$1];
911
+ });
912
+ }
913
+ for (const stack of stacks) {
914
+ const stackState = stackStates.get(stack.urn);
915
+ for (const [urn, nodeState] of entries(stackState.nodes)) if (!stack.nodes.find((r) => getMeta(r).urn === urn)) graph.add(urn, dependentsOn(allNodes, urn), async () => {
916
+ if (nodeState.tag === "resource") await queue(() => deleteResource(appState.idempotentToken, urn, nodeState, opt));
917
+ delete stackState.nodes[urn];
918
+ });
919
+ for (const node of stack.nodes) {
920
+ const meta = getMeta(node);
921
+ const dependencies = [...meta.dependencies];
922
+ const partialNewResourceState = {
923
+ dependencies,
924
+ lifecycle: isResource(node) ? { retainOnDelete: getMeta(node).config?.retainOnDelete } : void 0
925
+ };
926
+ graph.add(meta.urn, dependencies, () => {
927
+ return queue(async () => {
928
+ let nodeState = stackState.nodes[meta.urn];
929
+ let input;
930
+ try {
931
+ input = await resolveInputs(meta.input);
932
+ } catch (error) {
933
+ throw ResourceError.wrap(meta.urn, meta.type, "resolve", error);
934
+ }
935
+ if (isDataSource(node)) {
936
+ const meta$1 = getMeta(node);
937
+ if (!nodeState) {
938
+ const dataSourceState = await getDataSource(meta$1, input, opt);
939
+ nodeState = stackState.nodes[meta$1.urn] = {
940
+ ...dataSourceState,
941
+ ...partialNewResourceState
942
+ };
943
+ } else if (!compareState(nodeState.input, input)) {
944
+ const dataSourceState = await getDataSource(meta$1, input, opt);
945
+ Object.assign(nodeState, {
946
+ ...dataSourceState,
947
+ ...partialNewResourceState
948
+ });
949
+ } else Object.assign(nodeState, partialNewResourceState);
950
+ }
951
+ if (isResource(node)) {
952
+ const meta$1 = getMeta(node);
953
+ if (!nodeState) if (meta$1.config?.import) {
954
+ const importedState = await importResource(node, input, opt);
955
+ const newResourceState = await updateResource(node, appState.idempotentToken, importedState.input, importedState.output, input, opt);
956
+ nodeState = stackState.nodes[meta$1.urn] = {
957
+ ...importedState,
958
+ ...newResourceState,
959
+ ...partialNewResourceState
960
+ };
961
+ } else {
962
+ const newResourceState = await createResource(node, appState.idempotentToken, input, opt);
963
+ nodeState = stackState.nodes[meta$1.urn] = {
964
+ ...newResourceState,
965
+ ...partialNewResourceState
966
+ };
967
+ }
968
+ else if (!compareState(nodeState.input, input)) {
969
+ let newResourceState;
970
+ const ignoreReplace = forcedUpdateDependents.has(meta$1.urn);
971
+ if (!ignoreReplace && requiresReplacement(nodeState.input, input, meta$1.config?.replaceOnChanges ?? [])) if (meta$1.config?.createBeforeReplace) {
972
+ const priorState = { ...nodeState };
973
+ newResourceState = await createResource(node, appState.idempotentToken, input, opt);
974
+ if (!meta$1.config?.retainOnDelete) replacementDeletes.set(meta$1.urn, priorState);
975
+ } else {
976
+ for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
977
+ if (!isResource(dependentNode)) continue;
978
+ const dependentMeta = getMeta(dependentNode);
979
+ if (!dependentMeta.dependencies.has(meta$1.urn)) continue;
980
+ if (plannedDependents.has(dependentUrn)) continue;
981
+ const dependentStackState = stackStates.get(dependentMeta.stack.urn);
982
+ const dependentState = dependentStackState?.nodes[dependentUrn];
983
+ if (!dependentStackState || !dependentState) continue;
984
+ const dependencyPaths = findDependencyPaths(dependentMeta.input, meta$1.urn);
985
+ if (dependencyPaths.length === 0) continue;
986
+ const detachedInput = stripDependencyInputs(dependentState.input, dependentMeta.input, meta$1.urn);
987
+ if (compareState(dependentState.input, detachedInput)) continue;
988
+ plannedDependents.add(dependentUrn);
989
+ let dependentRequiresReplacement = false;
990
+ const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
991
+ if (dependentProvider.planResourceChange) try {
992
+ dependentRequiresReplacement = (await dependentProvider.planResourceChange({
993
+ type: dependentMeta.type,
994
+ priorState: dependentState.output,
995
+ proposedState: detachedInput
996
+ })).requiresReplacement;
997
+ } catch (error) {
998
+ throw ResourceError.wrap(dependentMeta.urn, dependentMeta.type, "update", error);
999
+ }
1000
+ if (dependentRequiresReplacement) {
1001
+ if (!allowsDependentReplace(dependentMeta.config?.replaceOnChanges, dependencyPaths)) throw ResourceError.wrap(dependentMeta.urn, dependentMeta.type, "update", /* @__PURE__ */ new Error(`Replacing ${meta$1.urn} requires ${dependentMeta.urn} to set replaceOnChanges for its dependency fields.`));
1002
+ await deleteResource(appState.idempotentToken, dependentUrn, dependentState, opt);
1003
+ delete dependentStackState.nodes[dependentUrn];
1004
+ } else {
1005
+ const updated = await updateResource(dependentNode, appState.idempotentToken, dependentState.input, dependentState.output, detachedInput, opt);
1006
+ Object.assign(dependentState, {
1007
+ input: detachedInput,
1008
+ ...updated
1009
+ });
1010
+ forcedUpdateDependents.add(dependentUrn);
1011
+ }
1012
+ }
1013
+ newResourceState = await replaceResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1014
+ }
1015
+ else {
1016
+ newResourceState = await updateResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1017
+ if (ignoreReplace) forcedUpdateDependents.delete(meta$1.urn);
1018
+ }
1019
+ Object.assign(nodeState, {
1020
+ input,
1021
+ ...newResourceState,
1022
+ ...partialNewResourceState
1023
+ });
1024
+ } else Object.assign(nodeState, partialNewResourceState);
1025
+ }
1026
+ if (nodeState?.output) meta.resolve(nodeState.output);
1027
+ });
1028
+ });
1029
+ }
1030
+ }
1031
+ const errors = await graph.run();
1032
+ if (errors.length === 0 && replacementDeletes.size > 0) for (const [urn, nodeState] of replacementDeletes.entries()) try {
1033
+ await deleteResource(appState.idempotentToken, urn, nodeState, opt);
1034
+ } catch (error) {
1035
+ if (error instanceof Error) errors.push(error);
1036
+ else errors.push(/* @__PURE__ */ new Error(`${error}`));
1037
+ }
1038
+ removeEmptyStackStates(appState);
1039
+ delete appState.idempotentToken;
1040
+ await opt.backend.state.update(app.urn, appState);
1041
+ releaseOnExit();
1042
+ debug$1(app.name, "done");
1043
+ if (errors.length > 0) throw new AppError(app.name, [...new Set(errors)], "Deploying app failed.");
1044
+ if (Object.keys(appState.stacks).length === 0) await opt.backend.state.delete(app.urn);
1045
+ return appState;
1046
+ };
1047
+
1048
+ //#endregion
1049
+ //#region src/workspace/procedure/hydrate.ts
1050
+ const hydrate = async (app, opt) => {
1051
+ const appState = await opt.backend.state.get(app.urn);
1052
+ if (appState) for (const stack of app.stacks) {
1053
+ const stackState = appState.stacks[stack.urn];
1054
+ if (stackState) for (const node of stack.nodes) {
1055
+ const meta = getMeta(node);
1056
+ const nodeState = stackState.nodes[meta.urn];
1057
+ if (nodeState && nodeState.output) meta.resolve(nodeState.output);
1058
+ }
1059
+ }
1060
+ };
1061
+
1062
+ //#endregion
1063
+ //#region src/workspace/procedure/refresh.ts
1064
+ const refresh = async (app, opt) => {
1065
+ const appState = await opt.backend.state.get(app.urn);
1066
+ const queue = createConcurrencyQueue(opt.concurrency ?? 10);
1067
+ if (appState) {
1068
+ await Promise.all(Object.values(appState.stacks).map((stackState) => {
1069
+ return Promise.all(Object.values(stackState.nodes).map((nodeState) => {
1070
+ return queue(async () => {
1071
+ const provider = findProvider(opt.providers, nodeState.provider);
1072
+ if (nodeState.tag === "data") {
1073
+ const result = await provider.getData?.({
1074
+ type: nodeState.type,
1075
+ state: nodeState.output
1076
+ });
1077
+ if (result && !compareState(result.state, nodeState.output)) {
1078
+ nodeState.output = result.state;
1079
+ nodeState.input = result.state;
1080
+ }
1081
+ } else if (nodeState.tag === "resource") {
1082
+ const result = await provider.getResource({
1083
+ type: nodeState.type,
1084
+ state: nodeState.output
1085
+ });
1086
+ if (result && !compareState(result.state, nodeState.output)) {
1087
+ nodeState.output = result.state;
1088
+ nodeState.input = result.state;
1089
+ }
1090
+ }
1091
+ });
1092
+ }));
1093
+ }));
1094
+ await opt.backend.state.update(app.urn, appState);
1095
+ }
1096
+ };
1097
+
1098
+ //#endregion
1099
+ //#region src/workspace/workspace.ts
1100
+ var WorkSpace = class {
1101
+ constructor(props) {
1102
+ this.props = props;
1103
+ }
1104
+ /**
1105
+ * Deploy the entire app or use the filter option to deploy specific stacks inside your app.
1106
+ */
1107
+ deploy(app, options = {}) {
1108
+ return lockApp(this.props.backend.lock, app, async () => {
1109
+ try {
1110
+ await deployApp(app, {
1111
+ ...this.props,
1112
+ ...options
1113
+ });
1114
+ } finally {
1115
+ await this.destroyProviders();
1116
+ }
1117
+ });
1118
+ }
1119
+ /**
1120
+ * Delete the entire app or use the filter option to delete specific stacks inside your app.
1121
+ */
1122
+ delete(app, options = {}) {
1123
+ return lockApp(this.props.backend.lock, app, async () => {
1124
+ try {
1125
+ await deleteApp(app, {
1126
+ ...this.props,
1127
+ ...options
1128
+ });
1129
+ } finally {
1130
+ await this.destroyProviders();
1131
+ }
1132
+ });
1133
+ }
1134
+ /**
1135
+ * Hydrate the outputs of the resources & data-sources inside your app.
1136
+ */
1137
+ hydrate(app) {
1138
+ return hydrate(app, this.props);
1139
+ }
1140
+ /**
1141
+ * Refresh the state of the resources & data-sources inside your app.
1142
+ */
1143
+ refresh(app) {
1144
+ return lockApp(this.props.backend.lock, app, async () => {
1145
+ try {
1146
+ await refresh(app, this.props);
1147
+ } finally {
1148
+ await this.destroyProviders();
1149
+ }
1150
+ });
1151
+ }
1152
+ async destroyProviders() {
1153
+ await Promise.all(this.props.providers.map((p) => {
1154
+ return p.destroy?.();
1155
+ }));
1156
+ }
1157
+ };
1158
+
1159
+ //#endregion
1160
+ //#region src/backend/memory/state.ts
1161
+ var MemoryStateBackend = class {
1162
+ states = /* @__PURE__ */ new Map();
1163
+ async get(urn) {
1164
+ return this.states.get(urn);
1165
+ }
1166
+ async update(urn, state) {
1167
+ this.states.set(urn, state);
1168
+ }
1169
+ async delete(urn) {
1170
+ this.states.delete(urn);
1171
+ }
1172
+ clear() {
1173
+ this.states.clear();
1174
+ }
1175
+ };
1176
+
1177
+ //#endregion
1178
+ //#region src/backend/memory/lock.ts
1179
+ var MemoryLockBackend = class {
1180
+ locks = /* @__PURE__ */ new Map();
1181
+ async insecureReleaseLock(urn) {
1182
+ this.locks.delete(urn);
1183
+ }
1184
+ async locked(urn) {
1185
+ return this.locks.has(urn);
1186
+ }
1187
+ async lock(urn) {
1188
+ if (this.locks.has(urn)) throw new Error("Already locked");
1189
+ const id = Math.random();
1190
+ this.locks.set(urn, id);
1191
+ return async () => {
1192
+ if (this.locks.get(urn) === id) this.locks.delete(urn);
1193
+ };
1194
+ }
1195
+ clear() {
1196
+ this.locks.clear();
1197
+ }
1198
+ };
1199
+
1200
+ //#endregion
1201
+ //#region src/backend/file/state.ts
1202
+ const debug = createDebugger("State");
1203
+ var FileStateBackend = class {
1204
+ constructor(props) {
1205
+ this.props = props;
1206
+ }
1207
+ stateFile(urn) {
1208
+ return join(this.props.dir, `${urn}.json`);
1209
+ }
1210
+ async mkdir() {
1211
+ await mkdir(this.props.dir, { recursive: true });
1212
+ }
1213
+ async get(urn) {
1214
+ debug("get");
1215
+ let json;
1216
+ try {
1217
+ json = await readFile(join(this.stateFile(urn)), "utf8");
1218
+ } catch (error) {
1219
+ return;
1220
+ }
1221
+ return JSON.parse(json);
1222
+ }
1223
+ async update(urn, state) {
1224
+ debug("update");
1225
+ await this.mkdir();
1226
+ await writeFile(this.stateFile(urn), JSON.stringify(state, void 0, 2));
1227
+ }
1228
+ async delete(urn) {
1229
+ debug("delete");
1230
+ await this.mkdir();
1231
+ await rm(this.stateFile(urn));
1232
+ }
1233
+ };
1234
+
1235
+ //#endregion
1236
+ //#region src/backend/file/lock.ts
1237
+ var FileLockBackend = class {
1238
+ constructor(props) {
1239
+ this.props = props;
1240
+ }
1241
+ lockFile(urn) {
1242
+ return join(this.props.dir, `${urn}.lock`);
1243
+ }
1244
+ async mkdir() {
1245
+ await mkdir(this.props.dir, { recursive: true });
1246
+ }
1247
+ async insecureReleaseLock(urn) {
1248
+ if (await this.locked(urn)) await rm(this.lockFile(urn));
1249
+ }
1250
+ async locked(urn) {
1251
+ return (await stat(this.lockFile(urn))).isFile();
1252
+ }
1253
+ async lock(urn) {
1254
+ await this.mkdir();
1255
+ return lock(this.lockFile(urn), { realpath: false });
1256
+ }
1257
+ };
1258
+
1259
+ //#endregion
1260
+ //#region src/backend/aws/s3-state.ts
1261
+ var S3StateBackend = class {
1262
+ client;
1263
+ constructor(props) {
1264
+ this.props = props;
1265
+ this.client = new S3Client(props);
1266
+ }
1267
+ async get(urn) {
1268
+ let result;
1269
+ try {
1270
+ result = await this.client.send(new GetObjectCommand({
1271
+ Bucket: this.props.bucket,
1272
+ Key: `${urn}.state`
1273
+ }));
1274
+ } catch (error) {
1275
+ if (error instanceof S3ServiceException && error.name === "NoSuchKey") return;
1276
+ throw error;
1277
+ }
1278
+ if (!result.Body) return;
1279
+ const body = await result.Body.transformToString("utf8");
1280
+ return JSON.parse(body);
1281
+ }
1282
+ async update(urn, state) {
1283
+ await this.client.send(new PutObjectCommand({
1284
+ Bucket: this.props.bucket,
1285
+ Key: `${urn}.state`,
1286
+ Body: JSON.stringify(state)
1287
+ }));
1288
+ }
1289
+ async delete(urn) {
1290
+ await this.client.send(new DeleteObjectCommand({
1291
+ Bucket: this.props.bucket,
1292
+ Key: `${urn}.state`
1293
+ }));
1294
+ }
1295
+ };
1296
+
1297
+ //#endregion
1298
+ //#region src/backend/aws/dynamodb-lock.ts
1299
+ var DynamoLockBackend = class {
1300
+ client;
1301
+ constructor(props) {
1302
+ this.props = props;
1303
+ this.client = new DynamoDB(props);
1304
+ }
1305
+ async insecureReleaseLock(urn) {
1306
+ await this.client.updateItem({
1307
+ TableName: this.props.tableName,
1308
+ Key: marshall({ urn }),
1309
+ ExpressionAttributeNames: { "#lock": "lock" },
1310
+ UpdateExpression: "REMOVE #lock"
1311
+ });
1312
+ }
1313
+ async locked(urn) {
1314
+ const result = await this.client.getItem({
1315
+ TableName: this.props.tableName,
1316
+ Key: marshall({ urn })
1317
+ });
1318
+ if (!result.Item) return false;
1319
+ return typeof unmarshall(result.Item).lock === "number";
1320
+ }
1321
+ async lock(urn) {
1322
+ const id = Math.floor(Math.random() * 1e5);
1323
+ const props = {
1324
+ TableName: this.props.tableName,
1325
+ Key: marshall({ urn }),
1326
+ ExpressionAttributeNames: { "#lock": "lock" },
1327
+ ExpressionAttributeValues: { ":id": marshall(id) }
1328
+ };
1329
+ await this.client.updateItem({
1330
+ ...props,
1331
+ UpdateExpression: "SET #lock = :id",
1332
+ ConditionExpression: "attribute_not_exists(#lock)"
1333
+ });
1334
+ return async () => {
1335
+ await this.client.updateItem({
1336
+ ...props,
1337
+ UpdateExpression: "REMOVE #lock",
1338
+ ConditionExpression: "#lock = :id"
1339
+ });
1340
+ };
1341
+ }
1342
+ };
1343
+
1344
+ //#endregion
1345
+ //#region src/helpers.ts
1346
+ const file = (path, encoding = "utf8") => {
1347
+ return new Future(async (resolve$1, reject) => {
1348
+ try {
1349
+ resolve$1(await readFile(path, { encoding }));
1350
+ } catch (error) {
1351
+ reject(error);
1352
+ }
1353
+ });
1354
+ };
1355
+ const hash = (path, algo = "sha256") => {
1356
+ return file(path).pipe((file$1) => createHash(algo).update(file$1).digest("hex"));
1357
+ };
1358
+
1359
+ //#endregion
1360
+ //#region src/globals.ts
1361
+ globalThis.$resolve = resolve;
1362
+ globalThis.$combine = combine;
1363
+ globalThis.$interpolate = interpolate;
1364
+ globalThis.$hash = hash;
1365
+ globalThis.$file = file;
1366
+
1367
+ //#endregion
1368
+ //#region src/custom/resource.ts
1369
+ const createCustomResourceClass = (providerId, resourceType) => {
1370
+ return new Proxy(class {}, { construct(_, [parent, id, input, config]) {
1371
+ const meta = createMeta("resource", `custom:${providerId}`, parent, resourceType, id, input, config);
1372
+ const node = new Proxy({}, { get(_$1, key) {
1373
+ if (key === nodeMetaSymbol) return meta;
1374
+ if (key === "urn") return meta.urn;
1375
+ if (typeof key === "symbol") return;
1376
+ return meta.output((data) => data[key]);
1377
+ } });
1378
+ parent.add(node);
1379
+ return node;
1380
+ } });
1381
+ };
1382
+
1383
+ //#endregion
1384
+ //#region src/custom/provider.ts
1385
+ const createCustomProvider = (providerId, resourceProviders) => {
1386
+ const version = 1;
1387
+ const getProvider = (type) => {
1388
+ const provider = resourceProviders[type];
1389
+ if (!provider) throw new Error(`The "${providerId}" provider doesn't support the "${type}" resource type.`);
1390
+ return provider;
1391
+ };
1392
+ return {
1393
+ ownResource(id) {
1394
+ return id === `custom:${providerId}`;
1395
+ },
1396
+ async getResource({ type, ...props }) {
1397
+ const provider = getProvider(type);
1398
+ if (!provider.getResource) return {
1399
+ version,
1400
+ state: props.state
1401
+ };
1402
+ return {
1403
+ version,
1404
+ state: await provider.getResource(props)
1405
+ };
1406
+ },
1407
+ async createResource({ type, ...props }) {
1408
+ const provider = getProvider(type);
1409
+ if (!provider.createResource) return {
1410
+ version,
1411
+ state: props.state
1412
+ };
1413
+ return {
1414
+ version,
1415
+ state: await provider.createResource(props)
1416
+ };
1417
+ },
1418
+ async updateResource({ type, ...props }) {
1419
+ const provider = getProvider(type);
1420
+ if (!provider.updateResource) return {
1421
+ version,
1422
+ state: props.proposedState
1423
+ };
1424
+ return {
1425
+ version,
1426
+ state: await provider.updateResource(props)
1427
+ };
1428
+ },
1429
+ async deleteResource({ type, ...props }) {
1430
+ await getProvider(type).deleteResource?.(props);
1431
+ },
1432
+ async planResourceChange({ type, ...props }) {
1433
+ const provider = getProvider(type);
1434
+ if (!provider.planResourceChange) return {
1435
+ version,
1436
+ state: props.proposedState,
1437
+ requiresReplacement: false
1438
+ };
1439
+ return {
1440
+ version,
1441
+ ...await provider.planResourceChange(props)
1442
+ };
1443
+ },
1444
+ async getData({ type, ...props }) {
1445
+ return {
1446
+ version,
1447
+ state: await getProvider(type).getData?.(props) ?? {}
1448
+ };
1449
+ }
1450
+ };
1451
+ };
1452
+
1453
+ //#endregion
1454
+ export { App, AppError, DynamoLockBackend, FileLockBackend, FileStateBackend, Future, Group, MemoryLockBackend, MemoryStateBackend, Output, ResourceAlreadyExists, ResourceError, ResourceNotFound, S3StateBackend, Stack, WorkSpace, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };