@terraforge/core 0.0.28 → 0.0.30

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.mts CHANGED
@@ -252,6 +252,17 @@ type GetDataProps<T = State> = {
252
252
  type: string;
253
253
  state: T;
254
254
  };
255
+ type RefreshResourceProps<T = State> = {
256
+ type: string;
257
+ priorInputState: T;
258
+ priorOutputState: T;
259
+ };
260
+ type RefreshResourceResult<T = State> = {
261
+ kind: 'unchanged' | 'updated';
262
+ state: T;
263
+ } | {
264
+ kind: 'deleted';
265
+ };
255
266
  interface Provider {
256
267
  ownResource(id: string): boolean;
257
268
  getResource(props: GetProps): Promise<{
@@ -275,6 +286,7 @@ interface Provider {
275
286
  getData?(props: GetDataProps): Promise<{
276
287
  state: State;
277
288
  }>;
289
+ refreshResource?(props: RefreshResourceProps): Promise<RefreshResourceResult | undefined>;
278
290
  destroy?(): Promise<void>;
279
291
  }
280
292
  //#endregion
@@ -532,7 +544,8 @@ type CustomResourceProvider = Partial<{
532
544
  state: State;
533
545
  requiresReplacement: boolean;
534
546
  }>;
547
+ refreshResource?(props: Omit<RefreshResourceProps, 'type'>): Promise<RefreshResourceResult<State> | undefined>;
535
548
  }>;
536
549
  declare const createCustomProvider: (providerId: string, resourceProviders: Record<string, CustomResourceProvider>) => Provider;
537
550
  //#endregion
538
- export { ActivityLogBackend, App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoActivityLogBackend, DynamoLockBackend, FileActivityLogBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, LockBackend, Log, LogProps, MemoryActivityLogBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type PlanProps, type ProcedureOptions, type Provider, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, type ResourceStatus, type ResourceStatusInfo, S3StateBackend, Stack, type StackStatusInfo, type State, StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
551
+ export { ActivityLogBackend, App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoActivityLogBackend, DynamoLockBackend, FileActivityLogBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, LockBackend, Log, LogProps, MemoryActivityLogBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type PlanProps, type ProcedureOptions, type Provider, type RefreshResourceProps, type RefreshResourceResult, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, type ResourceStatus, type ResourceStatusInfo, S3StateBackend, Stack, type StackStatusInfo, type State, StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
package/dist/index.mjs CHANGED
@@ -965,6 +965,7 @@ const deployApp = async (app, opt) => {
965
965
  }
966
966
  if (isResource(node)) {
967
967
  const meta$1 = getMeta(node);
968
+ const provider = findProvider(opt.providers, meta$1.provider);
968
969
  if (!nodeState) if (meta$1.config?.import) {
969
970
  const importedState = await importResource(node, input, opt);
970
971
  const newResourceState = await updateResource(node, appState.idempotentToken, importedState.input, importedState.output, input, opt);
@@ -980,94 +981,104 @@ const deployApp = async (app, opt) => {
980
981
  ...partialNewResourceState
981
982
  };
982
983
  }
983
- else if (!compareState(nodeState.input, input)) {
984
- let newResourceState;
985
- const ignoreReplace = forcedUpdateDependents.has(meta$1.urn);
986
- if (!ignoreReplace && requiresReplacement(nodeState.input, input, meta$1.config?.replaceOnChanges ?? [])) if (meta$1.config?.createBeforeReplace) {
987
- meta$1.resolve(input);
988
- try {
984
+ else {
985
+ const inputChanged = !compareState(nodeState.input, input);
986
+ const plannedChange = provider.planResourceChange ? await provider.planResourceChange({
987
+ type: meta$1.type,
988
+ priorState: nodeState.output,
989
+ proposedState: input
990
+ }) : void 0;
991
+ const hasDrift = !inputChanged && !!provider.refreshResource && !!plannedChange && !compareState(plannedChange.state, nodeState.output);
992
+ if (!inputChanged && !hasDrift) Object.assign(nodeState, partialNewResourceState);
993
+ else {
994
+ let newResourceState;
995
+ const ignoreReplace = forcedUpdateDependents.has(meta$1.urn);
996
+ if (!ignoreReplace && (requiresReplacement(nodeState.input, input, meta$1.config?.replaceOnChanges ?? []) || !inputChanged && (plannedChange?.requiresReplacement ?? false))) if (meta$1.config?.createBeforeReplace) {
997
+ meta$1.resolve(input);
998
+ try {
999
+ for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
1000
+ if (!isResource(dependentNode)) continue;
1001
+ const dependentMeta = getMeta(dependentNode);
1002
+ if (!dependentMeta.dependencies.has(meta$1.urn)) continue;
1003
+ const dependentStackState = stackStates.get(dependentMeta.stack.urn);
1004
+ const dependentState = dependentStackState?.nodes[dependentUrn];
1005
+ if (!dependentStackState || !dependentState) continue;
1006
+ const dependencyPaths = findDependencyPaths(dependentMeta.input, meta$1.urn);
1007
+ if (dependencyPaths.length === 0) continue;
1008
+ const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
1009
+ if (dependentProvider.planResourceChange) {
1010
+ const dependentProposedInput = await resolveInputs(dependentMeta.input);
1011
+ if ((await dependentProvider.planResourceChange({
1012
+ type: dependentMeta.type,
1013
+ priorState: dependentState.output,
1014
+ proposedState: dependentProposedInput
1015
+ })).requiresReplacement) {
1016
+ 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.`));
1017
+ }
1018
+ }
1019
+ }
1020
+ } finally {
1021
+ meta$1.resolve(nodeState.output);
1022
+ }
1023
+ const priorState = { ...nodeState };
1024
+ newResourceState = await createResource(node, appState.idempotentToken, input, opt);
1025
+ if (newResourceState.output) meta$1.resolve(newResourceState.output);
1026
+ if (!meta$1.config?.retainOnDelete) {
1027
+ appState.pendingDeletes ??= {};
1028
+ appState.pendingDeletes[meta$1.urn] = priorState;
1029
+ }
1030
+ } else {
989
1031
  for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
990
1032
  if (!isResource(dependentNode)) continue;
991
1033
  const dependentMeta = getMeta(dependentNode);
992
1034
  if (!dependentMeta.dependencies.has(meta$1.urn)) continue;
1035
+ if (plannedDependents.has(dependentUrn)) continue;
993
1036
  const dependentStackState = stackStates.get(dependentMeta.stack.urn);
994
1037
  const dependentState = dependentStackState?.nodes[dependentUrn];
995
1038
  if (!dependentStackState || !dependentState) continue;
996
1039
  const dependencyPaths = findDependencyPaths(dependentMeta.input, meta$1.urn);
997
1040
  if (dependencyPaths.length === 0) continue;
1041
+ const detachedInput = stripDependencyInputs(dependentState.input, dependentMeta.input, meta$1.urn);
1042
+ if (compareState(dependentState.input, detachedInput)) continue;
1043
+ plannedDependents.add(dependentUrn);
1044
+ let dependentRequiresReplacement = false;
998
1045
  const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
999
- if (dependentProvider.planResourceChange) {
1000
- const dependentProposedInput = await resolveInputs(dependentMeta.input);
1001
- if ((await dependentProvider.planResourceChange({
1046
+ if (dependentProvider.planResourceChange) try {
1047
+ dependentRequiresReplacement = (await dependentProvider.planResourceChange({
1002
1048
  type: dependentMeta.type,
1003
1049
  priorState: dependentState.output,
1004
- proposedState: dependentProposedInput
1005
- })).requiresReplacement) {
1006
- 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.`));
1007
- }
1050
+ proposedState: detachedInput
1051
+ })).requiresReplacement;
1052
+ } catch (error) {
1053
+ throw ResourceError.wrap(dependentMeta.urn, dependentMeta.type, "update", error);
1054
+ }
1055
+ if (dependentRequiresReplacement) {
1056
+ 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.`));
1057
+ await deleteResource(appState.idempotentToken, dependentUrn, dependentState, opt);
1058
+ delete dependentStackState.nodes[dependentUrn];
1059
+ } else {
1060
+ const updated = await updateResource(dependentNode, appState.idempotentToken, dependentState.input, dependentState.output, detachedInput, opt);
1061
+ Object.assign(dependentState, {
1062
+ input: detachedInput,
1063
+ ...updated
1064
+ });
1065
+ forcedUpdateDependents.add(dependentUrn);
1008
1066
  }
1009
1067
  }
1010
- } finally {
1011
- meta$1.resolve(nodeState.output);
1012
- }
1013
- const priorState = { ...nodeState };
1014
- newResourceState = await createResource(node, appState.idempotentToken, input, opt);
1015
- if (newResourceState.output) meta$1.resolve(newResourceState.output);
1016
- if (!meta$1.config?.retainOnDelete) {
1017
- appState.pendingDeletes ??= {};
1018
- appState.pendingDeletes[meta$1.urn] = priorState;
1068
+ newResourceState = await replaceResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1069
+ if (newResourceState.output) meta$1.resolve(newResourceState.output);
1019
1070
  }
1020
- } else {
1021
- for (const [dependentUrn, dependentNode] of nodeByUrn.entries()) {
1022
- if (!isResource(dependentNode)) continue;
1023
- const dependentMeta = getMeta(dependentNode);
1024
- if (!dependentMeta.dependencies.has(meta$1.urn)) continue;
1025
- if (plannedDependents.has(dependentUrn)) continue;
1026
- const dependentStackState = stackStates.get(dependentMeta.stack.urn);
1027
- const dependentState = dependentStackState?.nodes[dependentUrn];
1028
- if (!dependentStackState || !dependentState) continue;
1029
- const dependencyPaths = findDependencyPaths(dependentMeta.input, meta$1.urn);
1030
- if (dependencyPaths.length === 0) continue;
1031
- const detachedInput = stripDependencyInputs(dependentState.input, dependentMeta.input, meta$1.urn);
1032
- if (compareState(dependentState.input, detachedInput)) continue;
1033
- plannedDependents.add(dependentUrn);
1034
- let dependentRequiresReplacement = false;
1035
- const dependentProvider = findProvider(opt.providers, dependentMeta.provider);
1036
- if (dependentProvider.planResourceChange) try {
1037
- dependentRequiresReplacement = (await dependentProvider.planResourceChange({
1038
- type: dependentMeta.type,
1039
- priorState: dependentState.output,
1040
- proposedState: detachedInput
1041
- })).requiresReplacement;
1042
- } catch (error) {
1043
- throw ResourceError.wrap(dependentMeta.urn, dependentMeta.type, "update", error);
1044
- }
1045
- if (dependentRequiresReplacement) {
1046
- 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.`));
1047
- await deleteResource(appState.idempotentToken, dependentUrn, dependentState, opt);
1048
- delete dependentStackState.nodes[dependentUrn];
1049
- } else {
1050
- const updated = await updateResource(dependentNode, appState.idempotentToken, dependentState.input, dependentState.output, detachedInput, opt);
1051
- Object.assign(dependentState, {
1052
- input: detachedInput,
1053
- ...updated
1054
- });
1055
- forcedUpdateDependents.add(dependentUrn);
1056
- }
1071
+ else {
1072
+ newResourceState = await updateResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1073
+ if (ignoreReplace) forcedUpdateDependents.delete(meta$1.urn);
1057
1074
  }
1058
- newResourceState = await replaceResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1059
- if (newResourceState.output) meta$1.resolve(newResourceState.output);
1075
+ Object.assign(nodeState, {
1076
+ input,
1077
+ ...newResourceState,
1078
+ ...partialNewResourceState
1079
+ });
1060
1080
  }
1061
- else {
1062
- newResourceState = await updateResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1063
- if (ignoreReplace) forcedUpdateDependents.delete(meta$1.urn);
1064
- }
1065
- Object.assign(nodeState, {
1066
- input,
1067
- ...newResourceState,
1068
- ...partialNewResourceState
1069
- });
1070
- } else Object.assign(nodeState, partialNewResourceState);
1081
+ }
1071
1082
  }
1072
1083
  if (nodeState?.output) meta.resolve(nodeState.output);
1073
1084
  });
@@ -1111,48 +1122,58 @@ const hydrate = async (app, opt) => {
1111
1122
 
1112
1123
  //#endregion
1113
1124
  //#region src/workspace/procedure/refresh.ts
1125
+ const createDeleteOperation = (urn, stackState) => {
1126
+ return {
1127
+ urn,
1128
+ operation: "delete",
1129
+ commit() {
1130
+ delete stackState.nodes[urn];
1131
+ }
1132
+ };
1133
+ };
1134
+ const createUpdateOperation = (urn, state, nodeState) => {
1135
+ console.log(urn);
1136
+ console.log(nodeState.output);
1137
+ console.log(state);
1138
+ return {
1139
+ urn,
1140
+ operation: "update",
1141
+ commit() {
1142
+ nodeState.output = state;
1143
+ }
1144
+ };
1145
+ };
1114
1146
  const refresh = async (app, opt) => {
1115
1147
  const appState = await opt.backend.state.get(app.urn);
1116
1148
  const queue = createConcurrencyQueue(opt.concurrency ?? 10);
1117
1149
  let filteredStacks = Object.values(appState?.stacks ?? {});
1118
- if (opt.filters && opt.filters.length > 0) filteredStacks = Object.entries(appState?.stacks ?? {}).filter(([stackName]) => {
1119
- return opt.filters.includes(stackName);
1120
- }).map(([_, state]) => state);
1150
+ if (opt.filters && opt.filters.length > 0) filteredStacks = Object.values(appState?.stacks ?? {}).filter((stackState) => {
1151
+ return opt.filters.includes(stackState.name);
1152
+ });
1121
1153
  if (appState && filteredStacks.length > 0) {
1122
1154
  const filteredOperations = (await Promise.all(filteredStacks.map((stackState) => {
1123
1155
  return Promise.all(Object.entries(stackState.nodes).map(([_urn, nodeState]) => {
1124
1156
  const urn = _urn;
1125
1157
  return queue(async () => {
1126
1158
  const provider = findProvider(opt.providers, nodeState.provider);
1127
- let result;
1128
- if (nodeState.tag === "data") result = await provider.getData?.({
1129
- type: nodeState.type,
1130
- state: nodeState.output
1131
- });
1132
- else result = await provider.getResource({
1159
+ if (nodeState.tag === "data") {
1160
+ const result = await provider.getData?.({
1161
+ type: nodeState.type,
1162
+ state: nodeState.output
1163
+ });
1164
+ if (!result) return createDeleteOperation(urn, stackState);
1165
+ if (compareState(result.state, nodeState.output)) return;
1166
+ return createUpdateOperation(urn, result.state, nodeState);
1167
+ }
1168
+ if (!provider.refreshResource) return;
1169
+ const refreshed = await provider.refreshResource({
1133
1170
  type: nodeState.type,
1134
- state: nodeState.output
1171
+ priorInputState: nodeState.input,
1172
+ priorOutputState: nodeState.output
1135
1173
  });
1136
- if (!result) return {
1137
- urn,
1138
- operation: "delete",
1139
- commit() {
1140
- delete stackState.nodes[urn];
1141
- }
1142
- };
1143
- else if (!compareState(result.state, nodeState.output)) {
1144
- console.log(urn);
1145
- console.log(nodeState.output);
1146
- console.log(result?.state);
1147
- return {
1148
- urn,
1149
- operation: "update",
1150
- commit() {
1151
- nodeState.input = result.state;
1152
- nodeState.output = result.state;
1153
- }
1154
- };
1155
- }
1174
+ if (!refreshed || refreshed.kind === "unchanged") return;
1175
+ if (refreshed.kind === "deleted") return createDeleteOperation(urn, stackState);
1176
+ return createUpdateOperation(urn, refreshed.state, nodeState);
1156
1177
  });
1157
1178
  }));
1158
1179
  }))).flat().filter((op) => !!op);
@@ -1680,61 +1701,62 @@ const createCustomResourceClass = (providerId, resourceType) => {
1680
1701
  //#region src/custom/provider.ts
1681
1702
  const createCustomProvider = (providerId, resourceProviders) => {
1682
1703
  const version = 1;
1704
+ const hasRefreshResource = Object.values(resourceProviders).some((provider$1) => !!provider$1.refreshResource);
1683
1705
  const getProvider = (type) => {
1684
- const provider = resourceProviders[type];
1685
- if (!provider) throw new Error(`The "${providerId}" provider doesn't support the "${type}" resource type.`);
1686
- return provider;
1706
+ const provider$1 = resourceProviders[type];
1707
+ if (!provider$1) throw new Error(`The "${providerId}" provider doesn't support the "${type}" resource type.`);
1708
+ return provider$1;
1687
1709
  };
1688
- return {
1710
+ const provider = {
1689
1711
  ownResource(id) {
1690
1712
  return id === `custom:${providerId}`;
1691
1713
  },
1692
1714
  async getResource({ type, ...props }) {
1693
- const provider = getProvider(type);
1694
- if (!provider.getResource) return {
1715
+ const provider$1 = getProvider(type);
1716
+ if (!provider$1.getResource) return {
1695
1717
  version,
1696
1718
  state: props.state
1697
1719
  };
1698
1720
  return {
1699
1721
  version,
1700
- state: await provider.getResource(props)
1722
+ state: await provider$1.getResource(props)
1701
1723
  };
1702
1724
  },
1703
1725
  async createResource({ type, ...props }) {
1704
- const provider = getProvider(type);
1705
- if (!provider.createResource) return {
1726
+ const provider$1 = getProvider(type);
1727
+ if (!provider$1.createResource) return {
1706
1728
  version,
1707
1729
  state: props.state
1708
1730
  };
1709
1731
  return {
1710
1732
  version,
1711
- state: await provider.createResource(props)
1733
+ state: await provider$1.createResource(props)
1712
1734
  };
1713
1735
  },
1714
1736
  async updateResource({ type, ...props }) {
1715
- const provider = getProvider(type);
1716
- if (!provider.updateResource) return {
1737
+ const provider$1 = getProvider(type);
1738
+ if (!provider$1.updateResource) return {
1717
1739
  version,
1718
1740
  state: props.proposedState
1719
1741
  };
1720
1742
  return {
1721
1743
  version,
1722
- state: await provider.updateResource(props)
1744
+ state: await provider$1.updateResource(props)
1723
1745
  };
1724
1746
  },
1725
1747
  async deleteResource({ type, ...props }) {
1726
1748
  await getProvider(type).deleteResource?.(props);
1727
1749
  },
1728
1750
  async planResourceChange({ type, ...props }) {
1729
- const provider = getProvider(type);
1730
- if (!provider.planResourceChange) return {
1751
+ const provider$1 = getProvider(type);
1752
+ if (!provider$1.planResourceChange) return {
1731
1753
  version,
1732
1754
  state: props.proposedState,
1733
1755
  requiresReplacement: false
1734
1756
  };
1735
1757
  return {
1736
1758
  version,
1737
- ...await provider.planResourceChange(props)
1759
+ ...await provider$1.planResourceChange(props)
1738
1760
  };
1739
1761
  },
1740
1762
  async getData({ type, ...props }) {
@@ -1744,6 +1766,10 @@ const createCustomProvider = (providerId, resourceProviders) => {
1744
1766
  };
1745
1767
  }
1746
1768
  };
1769
+ if (hasRefreshResource) provider.refreshResource = async ({ type, ...props }) => {
1770
+ return await getProvider(type).refreshResource?.(props);
1771
+ };
1772
+ return provider;
1747
1773
  };
1748
1774
 
1749
1775
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/core",
3
- "version": "0.0.28",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",