@terraforge/core 0.0.29 → 0.0.31

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,12 +252,16 @@ type GetDataProps<T = State> = {
252
252
  type: string;
253
253
  state: T;
254
254
  };
255
- type RefreshStateProps<T = State> = {
255
+ type RefreshResourceProps<T = State> = {
256
256
  type: string;
257
- tag: 'resource' | 'data';
258
257
  priorInputState: T;
259
258
  priorOutputState: T;
260
- refreshedState: T;
259
+ };
260
+ type RefreshResourceResult<T = State> = {
261
+ kind: 'unchanged' | 'updated';
262
+ state: T;
263
+ } | {
264
+ kind: 'deleted';
261
265
  };
262
266
  interface Provider {
263
267
  ownResource(id: string): boolean;
@@ -282,7 +286,7 @@ interface Provider {
282
286
  getData?(props: GetDataProps): Promise<{
283
287
  state: State;
284
288
  }>;
285
- isRefreshStateEqual?(props: RefreshStateProps): Promise<boolean> | boolean;
289
+ refreshResource?(props: RefreshResourceProps): Promise<RefreshResourceResult | undefined>;
286
290
  destroy?(): Promise<void>;
287
291
  }
288
292
  //#endregion
@@ -540,8 +544,8 @@ type CustomResourceProvider = Partial<{
540
544
  state: State;
541
545
  requiresReplacement: boolean;
542
546
  }>;
543
- isRefreshStateEqual?(props: Omit<RefreshStateProps, 'type'>): Promise<boolean> | boolean;
547
+ refreshResource?(props: Omit<RefreshResourceProps, 'type'>): Promise<RefreshResourceResult<State> | undefined>;
544
548
  }>;
545
549
  declare const createCustomProvider: (providerId: string, resourceProviders: Record<string, CustomResourceProvider>) => Provider;
546
550
  //#endregion
547
- 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 RefreshStateProps, 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);
1068
+ newResourceState = await replaceResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1069
+ if (newResourceState.output) meta$1.resolve(newResourceState.output);
1012
1070
  }
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;
1071
+ else {
1072
+ newResourceState = await updateResource(node, appState.idempotentToken, nodeState.input, nodeState.output, input, opt);
1073
+ if (ignoreReplace) forcedUpdateDependents.delete(meta$1.urn);
1019
1074
  }
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
- }
1057
- }
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,55 +1122,55 @@ 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
+ return {
1136
+ urn,
1137
+ operation: "update",
1138
+ commit() {
1139
+ nodeState.output = state;
1140
+ }
1141
+ };
1142
+ };
1114
1143
  const refresh = async (app, opt) => {
1115
1144
  const appState = await opt.backend.state.get(app.urn);
1116
1145
  const queue = createConcurrencyQueue(opt.concurrency ?? 10);
1117
1146
  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);
1147
+ if (opt.filters && opt.filters.length > 0) filteredStacks = Object.values(appState?.stacks ?? {}).filter((stackState) => {
1148
+ return opt.filters.includes(stackState.name);
1149
+ });
1121
1150
  if (appState && filteredStacks.length > 0) {
1122
1151
  const filteredOperations = (await Promise.all(filteredStacks.map((stackState) => {
1123
1152
  return Promise.all(Object.entries(stackState.nodes).map(([_urn, nodeState]) => {
1124
1153
  const urn = _urn;
1125
1154
  return queue(async () => {
1126
1155
  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({
1133
- type: nodeState.type,
1134
- state: nodeState.output
1135
- });
1136
- const hasChanged = !result ? true : provider.isRefreshStateEqual ? !await provider.isRefreshStateEqual({
1156
+ if (nodeState.tag === "data") {
1157
+ const result = await provider.getData?.({
1158
+ type: nodeState.type,
1159
+ state: nodeState.output
1160
+ });
1161
+ if (!result) return createDeleteOperation(urn, stackState);
1162
+ if (compareState(result.state, nodeState.output)) return;
1163
+ return createUpdateOperation(urn, result.state, nodeState);
1164
+ }
1165
+ if (!provider.refreshResource) return;
1166
+ const refreshed = await provider.refreshResource({
1137
1167
  type: nodeState.type,
1138
- tag: nodeState.tag,
1139
1168
  priorInputState: nodeState.input,
1140
- priorOutputState: nodeState.output,
1141
- refreshedState: result.state
1142
- }) : !compareState(result.state, nodeState.output);
1143
- if (!result) return {
1144
- urn,
1145
- operation: "delete",
1146
- commit() {
1147
- delete stackState.nodes[urn];
1148
- }
1149
- };
1150
- else if (hasChanged) {
1151
- console.log(urn);
1152
- console.log(nodeState.output);
1153
- console.log(result?.state);
1154
- return {
1155
- urn,
1156
- operation: "update",
1157
- commit() {
1158
- nodeState.output = result.state;
1159
- if (nodeState.tag === "resource" && "version" in result) nodeState.version = result.version;
1160
- }
1161
- };
1162
- }
1169
+ priorOutputState: nodeState.output
1170
+ });
1171
+ if (!refreshed || refreshed.kind === "unchanged") return;
1172
+ if (refreshed.kind === "deleted") return createDeleteOperation(urn, stackState);
1173
+ return createUpdateOperation(urn, refreshed.state, nodeState);
1163
1174
  });
1164
1175
  }));
1165
1176
  }))).flat().filter((op) => !!op);
@@ -1687,61 +1698,62 @@ const createCustomResourceClass = (providerId, resourceType) => {
1687
1698
  //#region src/custom/provider.ts
1688
1699
  const createCustomProvider = (providerId, resourceProviders) => {
1689
1700
  const version = 1;
1701
+ const hasRefreshResource = Object.values(resourceProviders).some((provider$1) => !!provider$1.refreshResource);
1690
1702
  const getProvider = (type) => {
1691
- const provider = resourceProviders[type];
1692
- if (!provider) throw new Error(`The "${providerId}" provider doesn't support the "${type}" resource type.`);
1693
- return provider;
1703
+ const provider$1 = resourceProviders[type];
1704
+ if (!provider$1) throw new Error(`The "${providerId}" provider doesn't support the "${type}" resource type.`);
1705
+ return provider$1;
1694
1706
  };
1695
- return {
1707
+ const provider = {
1696
1708
  ownResource(id) {
1697
1709
  return id === `custom:${providerId}`;
1698
1710
  },
1699
1711
  async getResource({ type, ...props }) {
1700
- const provider = getProvider(type);
1701
- if (!provider.getResource) return {
1712
+ const provider$1 = getProvider(type);
1713
+ if (!provider$1.getResource) return {
1702
1714
  version,
1703
1715
  state: props.state
1704
1716
  };
1705
1717
  return {
1706
1718
  version,
1707
- state: await provider.getResource(props)
1719
+ state: await provider$1.getResource(props)
1708
1720
  };
1709
1721
  },
1710
1722
  async createResource({ type, ...props }) {
1711
- const provider = getProvider(type);
1712
- if (!provider.createResource) return {
1723
+ const provider$1 = getProvider(type);
1724
+ if (!provider$1.createResource) return {
1713
1725
  version,
1714
1726
  state: props.state
1715
1727
  };
1716
1728
  return {
1717
1729
  version,
1718
- state: await provider.createResource(props)
1730
+ state: await provider$1.createResource(props)
1719
1731
  };
1720
1732
  },
1721
1733
  async updateResource({ type, ...props }) {
1722
- const provider = getProvider(type);
1723
- if (!provider.updateResource) return {
1734
+ const provider$1 = getProvider(type);
1735
+ if (!provider$1.updateResource) return {
1724
1736
  version,
1725
1737
  state: props.proposedState
1726
1738
  };
1727
1739
  return {
1728
1740
  version,
1729
- state: await provider.updateResource(props)
1741
+ state: await provider$1.updateResource(props)
1730
1742
  };
1731
1743
  },
1732
1744
  async deleteResource({ type, ...props }) {
1733
1745
  await getProvider(type).deleteResource?.(props);
1734
1746
  },
1735
1747
  async planResourceChange({ type, ...props }) {
1736
- const provider = getProvider(type);
1737
- if (!provider.planResourceChange) return {
1748
+ const provider$1 = getProvider(type);
1749
+ if (!provider$1.planResourceChange) return {
1738
1750
  version,
1739
1751
  state: props.proposedState,
1740
1752
  requiresReplacement: false
1741
1753
  };
1742
1754
  return {
1743
1755
  version,
1744
- ...await provider.planResourceChange(props)
1756
+ ...await provider$1.planResourceChange(props)
1745
1757
  };
1746
1758
  },
1747
1759
  async getData({ type, ...props }) {
@@ -1749,11 +1761,12 @@ const createCustomProvider = (providerId, resourceProviders) => {
1749
1761
  version,
1750
1762
  state: await getProvider(type).getData?.(props) ?? {}
1751
1763
  };
1752
- },
1753
- async isRefreshStateEqual({ type, ...props }) {
1754
- return await getProvider(type).isRefreshStateEqual?.(props) ?? false;
1755
1764
  }
1756
1765
  };
1766
+ if (hasRefreshResource) provider.refreshResource = async ({ type, ...props }) => {
1767
+ return await getProvider(type).refreshResource?.(props);
1768
+ };
1769
+ return provider;
1757
1770
  };
1758
1771
 
1759
1772
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/core",
3
- "version": "0.0.29",
3
+ "version": "0.0.31",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",