@terraforge/core 0.0.29 → 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,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,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({
1133
- type: nodeState.type,
1134
- state: nodeState.output
1135
- });
1136
- const hasChanged = !result ? true : provider.isRefreshStateEqual ? !await provider.isRefreshStateEqual({
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({
1137
1170
  type: nodeState.type,
1138
- tag: nodeState.tag,
1139
1171
  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
- }
1172
+ priorOutputState: nodeState.output
1173
+ });
1174
+ if (!refreshed || refreshed.kind === "unchanged") return;
1175
+ if (refreshed.kind === "deleted") return createDeleteOperation(urn, stackState);
1176
+ return createUpdateOperation(urn, refreshed.state, nodeState);
1163
1177
  });
1164
1178
  }));
1165
1179
  }))).flat().filter((op) => !!op);
@@ -1687,61 +1701,62 @@ const createCustomResourceClass = (providerId, resourceType) => {
1687
1701
  //#region src/custom/provider.ts
1688
1702
  const createCustomProvider = (providerId, resourceProviders) => {
1689
1703
  const version = 1;
1704
+ const hasRefreshResource = Object.values(resourceProviders).some((provider$1) => !!provider$1.refreshResource);
1690
1705
  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;
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;
1694
1709
  };
1695
- return {
1710
+ const provider = {
1696
1711
  ownResource(id) {
1697
1712
  return id === `custom:${providerId}`;
1698
1713
  },
1699
1714
  async getResource({ type, ...props }) {
1700
- const provider = getProvider(type);
1701
- if (!provider.getResource) return {
1715
+ const provider$1 = getProvider(type);
1716
+ if (!provider$1.getResource) return {
1702
1717
  version,
1703
1718
  state: props.state
1704
1719
  };
1705
1720
  return {
1706
1721
  version,
1707
- state: await provider.getResource(props)
1722
+ state: await provider$1.getResource(props)
1708
1723
  };
1709
1724
  },
1710
1725
  async createResource({ type, ...props }) {
1711
- const provider = getProvider(type);
1712
- if (!provider.createResource) return {
1726
+ const provider$1 = getProvider(type);
1727
+ if (!provider$1.createResource) return {
1713
1728
  version,
1714
1729
  state: props.state
1715
1730
  };
1716
1731
  return {
1717
1732
  version,
1718
- state: await provider.createResource(props)
1733
+ state: await provider$1.createResource(props)
1719
1734
  };
1720
1735
  },
1721
1736
  async updateResource({ type, ...props }) {
1722
- const provider = getProvider(type);
1723
- if (!provider.updateResource) return {
1737
+ const provider$1 = getProvider(type);
1738
+ if (!provider$1.updateResource) return {
1724
1739
  version,
1725
1740
  state: props.proposedState
1726
1741
  };
1727
1742
  return {
1728
1743
  version,
1729
- state: await provider.updateResource(props)
1744
+ state: await provider$1.updateResource(props)
1730
1745
  };
1731
1746
  },
1732
1747
  async deleteResource({ type, ...props }) {
1733
1748
  await getProvider(type).deleteResource?.(props);
1734
1749
  },
1735
1750
  async planResourceChange({ type, ...props }) {
1736
- const provider = getProvider(type);
1737
- if (!provider.planResourceChange) return {
1751
+ const provider$1 = getProvider(type);
1752
+ if (!provider$1.planResourceChange) return {
1738
1753
  version,
1739
1754
  state: props.proposedState,
1740
1755
  requiresReplacement: false
1741
1756
  };
1742
1757
  return {
1743
1758
  version,
1744
- ...await provider.planResourceChange(props)
1759
+ ...await provider$1.planResourceChange(props)
1745
1760
  };
1746
1761
  },
1747
1762
  async getData({ type, ...props }) {
@@ -1749,11 +1764,12 @@ const createCustomProvider = (providerId, resourceProviders) => {
1749
1764
  version,
1750
1765
  state: await getProvider(type).getData?.(props) ?? {}
1751
1766
  };
1752
- },
1753
- async isRefreshStateEqual({ type, ...props }) {
1754
- return await getProvider(type).isRefreshStateEqual?.(props) ?? false;
1755
1767
  }
1756
1768
  };
1769
+ if (hasRefreshResource) provider.refreshResource = async ({ type, ...props }) => {
1770
+ return await getProvider(type).refreshResource?.(props);
1771
+ };
1772
+ return provider;
1757
1773
  };
1758
1774
 
1759
1775
  //#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.30",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",