@terraforge/core 0.0.6 → 0.0.7

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.d.ts CHANGED
@@ -58,12 +58,13 @@ declare const isDataSource: (obj: object) => obj is DataSource;
58
58
  type ResourceConfig = Config & {
59
59
  /** Import an existing resource instead of creating a new resource. */
60
60
  import?: string;
61
- /** If true the resource will be retained in the backing cloud provider during a Pulumi delete operation. */
61
+ /** If true the resource will be retained in the backing cloud provider during a delete operation. */
62
62
  retainOnDelete?: boolean;
63
- /** Override the default create-after-delete behavior when replacing a resource. */
64
63
  /** If set, the provider’s Delete method will not be called for this resource if the specified resource is being deleted as well. */
65
64
  /** Declare that changes to certain properties should be treated as forcing a replacement. */
66
65
  replaceOnChanges?: string[];
66
+ /** If true, create the replacement before deleting the existing resource. */
67
+ createBeforeReplace?: boolean;
67
68
  };
68
69
  type ResourceMeta<I extends State = State, O extends State = State> = Meta<'resource', I, O, ResourceConfig>;
69
70
  type Resource<I extends State = State, O extends State = State> = O & {
package/dist/index.js CHANGED
@@ -420,6 +420,9 @@ var DependencyGraph = class {
420
420
  this.callbacks.set(urn, callback);
421
421
  this.graph.mergeNode(urn);
422
422
  for (const dep of deps) {
423
+ if (!dep) {
424
+ throw new Error(`Resource ${urn} has an undefined dependency.`);
425
+ }
423
426
  if (willCreateCycle(this.graph, dep, urn)) {
424
427
  throw new Error(`There is a circular dependency between ${urn} -> ${dep}`);
425
428
  }
@@ -1030,6 +1033,7 @@ var deployApp = async (app, opt) => {
1030
1033
  }
1031
1034
  const queue = createConcurrencyQueue(opt.concurrency ?? 10);
1032
1035
  const graph = new DependencyGraph();
1036
+ const replacementDeletes = /* @__PURE__ */ new Map();
1033
1037
  const allNodes = {};
1034
1038
  for (const stackState of Object.values(appState.stacks)) {
1035
1039
  for (const [urn, nodeState] of entries(stackState.nodes)) {
@@ -1177,99 +1181,107 @@ var deployApp = async (app, opt) => {
1177
1181
  let newResourceState;
1178
1182
  const ignoreReplace = forcedUpdateDependents.has(meta2.urn);
1179
1183
  if (!ignoreReplace && requiresReplacement(nodeState.input, input, meta2.config?.replaceOnChanges ?? [])) {
1180
- for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
1181
- if (!isResource(dependentNode)) {
1182
- continue;
1183
- }
1184
- const dependentMeta = getMeta(dependentNode);
1185
- if (!dependentMeta.dependencies.has(meta2.urn)) {
1186
- continue;
1187
- }
1188
- if (plannedDependents.has(dependentUrn)) {
1189
- continue;
1190
- }
1191
- const dependentStackState = stackStates.get(dependentMeta.stack.urn);
1192
- const dependentState = dependentStackState?.nodes[dependentUrn];
1193
- if (!dependentStackState || !dependentState) {
1194
- continue;
1195
- }
1196
- const dependencyPaths = findDependencyPaths(dependentMeta.input, meta2.urn);
1197
- if (dependencyPaths.length === 0) {
1198
- continue;
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);
1199
1189
  }
1200
- const detachedInput = stripDependencyInputs(
1201
- dependentState.input,
1202
- dependentMeta.input,
1203
- meta2.urn
1204
- );
1205
- if (compareState(dependentState.input, detachedInput)) {
1206
- continue;
1207
- }
1208
- plannedDependents.add(dependentUrn);
1209
- let dependentRequiresReplacement = false;
1210
- const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
1211
- if (dependentProvider.planResourceChange) {
1212
- try {
1213
- const dependentPlan = await dependentProvider.planResourceChange({
1214
- type: dependentMeta.type,
1215
- priorState: dependentState.output,
1216
- proposedState: detachedInput
1217
- });
1218
- dependentRequiresReplacement = dependentPlan.requiresReplacement;
1219
- } catch (error) {
1220
- throw ResourceError.wrap(
1221
- dependentMeta.urn,
1222
- dependentMeta.type,
1223
- "update",
1224
- error
1225
- );
1190
+ } else {
1191
+ for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
1192
+ if (!isResource(dependentNode)) {
1193
+ continue;
1226
1194
  }
1227
- }
1228
- if (dependentRequiresReplacement) {
1229
- if (!allowsDependentReplace(
1230
- dependentMeta.config?.replaceOnChanges,
1231
- dependencyPaths
1232
- )) {
1233
- throw ResourceError.wrap(
1234
- dependentMeta.urn,
1235
- dependentMeta.type,
1236
- "update",
1237
- new Error(
1238
- `Replacing ${meta2.urn} requires ${dependentMeta.urn} to set replaceOnChanges for its dependency fields.`
1239
- )
1240
- );
1195
+ const dependentMeta = getMeta(dependentNode);
1196
+ if (!dependentMeta.dependencies.has(meta2.urn)) {
1197
+ continue;
1241
1198
  }
1242
- await deleteResource(
1243
- appState.idempotentToken,
1244
- dependentUrn,
1245
- dependentState,
1246
- opt
1247
- );
1248
- delete dependentStackState.nodes[dependentUrn];
1249
- } else {
1250
- const updated = await updateResource(
1251
- dependentNode,
1252
- appState.idempotentToken,
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(
1253
1212
  dependentState.input,
1254
- dependentState.output,
1255
- detachedInput,
1256
- opt
1213
+ dependentMeta.input,
1214
+ meta2.urn
1257
1215
  );
1258
- Object.assign(dependentState, {
1259
- input: detachedInput,
1260
- ...updated
1261
- });
1262
- forcedUpdateDependents.add(dependentUrn);
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
+ }
1263
1275
  }
1276
+ newResourceState = await replaceResource(
1277
+ node,
1278
+ appState.idempotentToken,
1279
+ nodeState.input,
1280
+ nodeState.output,
1281
+ input,
1282
+ opt
1283
+ );
1264
1284
  }
1265
- newResourceState = await replaceResource(
1266
- node,
1267
- appState.idempotentToken,
1268
- nodeState.input,
1269
- nodeState.output,
1270
- input,
1271
- opt
1272
- );
1273
1285
  } else {
1274
1286
  newResourceState = await updateResource(
1275
1287
  node,
@@ -1300,6 +1312,19 @@ var deployApp = async (app, opt) => {
1300
1312
  }
1301
1313
  }
1302
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
+ }
1303
1328
  removeEmptyStackStates(appState);
1304
1329
  delete appState.idempotentToken;
1305
1330
  await opt.backend.state.update(app.urn, appState);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/core",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",