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