codify-plugin-lib 1.0.64 → 1.0.66

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.
@@ -59,8 +59,8 @@ export class ChangeSet {
59
59
  }
60
60
  static calculateStatefulModeChangeSet(desired, current, parameterOptions) {
61
61
  const parameterChangeSet = new Array();
62
- const _desired = { ...desired };
63
- const _current = { ...current };
62
+ const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
63
+ const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
64
64
  this.addDefaultValues(_desired, parameterOptions);
65
65
  for (const [k, v] of Object.entries(_current)) {
66
66
  if (_desired[k] == null) {
@@ -108,8 +108,8 @@ export class ChangeSet {
108
108
  }
109
109
  static calculateStatelessModeChangeSet(desired, current, parameterOptions) {
110
110
  const parameterChangeSet = new Array();
111
- const _desired = { ...desired };
112
- const _current = { ...current };
111
+ const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
112
+ const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
113
113
  this.addDefaultValues(_desired, parameterOptions);
114
114
  for (const [k, v] of Object.entries(_desired)) {
115
115
  if (_current[k] == null) {
@@ -1,4 +1,11 @@
1
+ import { Plan } from './plan.js';
2
+ import { StringIndexedObject } from 'codify-schemas';
1
3
  export declare class SudoError extends Error {
2
4
  command: string;
3
5
  constructor(command: string);
4
6
  }
7
+ export declare class ApplyValidationError<T extends StringIndexedObject> extends Error {
8
+ desiredPlan: Plan<T>;
9
+ validatedPlan: Plan<T>;
10
+ constructor(desiredPlan: Plan<T>, validatedPlan: Plan<T>);
11
+ }
@@ -5,3 +5,12 @@ export class SudoError extends Error {
5
5
  this.command = command;
6
6
  }
7
7
  }
8
+ export class ApplyValidationError extends Error {
9
+ desiredPlan;
10
+ validatedPlan;
11
+ constructor(desiredPlan, validatedPlan) {
12
+ super();
13
+ this.desiredPlan = desiredPlan;
14
+ this.validatedPlan = validatedPlan;
15
+ }
16
+ }
@@ -1,5 +1,5 @@
1
1
  export interface ParameterOptions {
2
- canModify?: boolean;
2
+ modifyOnChange?: boolean;
3
3
  isEqual?: (desired: any, current: any) => boolean;
4
4
  isElementEqual?: (desired: any, current: any) => boolean;
5
5
  default?: unknown;
@@ -8,8 +8,8 @@ export declare class Plan<T extends StringIndexedObject> {
8
8
  constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
9
9
  static create<T extends StringIndexedObject>(desiredParameters: Partial<T> | null, currentParameters: Partial<T> | null, resourceMetadata: ResourceConfig, options: PlanOptions<T>): Plan<T>;
10
10
  getResourceType(): string;
11
- static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues: Partial<Record<keyof T, unknown>>): Plan<T>;
12
- get desiredConfig(): T;
13
- get currentConfig(): T;
11
+ static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T>;
12
+ get desiredConfig(): T | null;
13
+ get currentConfig(): T | null;
14
14
  toResponse(): PlanResponseData;
15
15
  }
@@ -31,8 +31,8 @@ export class Plan {
31
31
  if (statefulParameterNames.has(curr.name)) {
32
32
  newOperation = ResourceOperation.MODIFY;
33
33
  }
34
- else if (parameterOptions[curr.name]?.canModify) {
35
- newOperation = parameterOptions[curr.name].canModify ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
34
+ else if (parameterOptions[curr.name]?.modifyOnChange) {
35
+ newOperation = parameterOptions[curr.name].modifyOnChange ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
36
36
  }
37
37
  else {
38
38
  newOperation = ResourceOperation.RECREATE;
@@ -55,7 +55,7 @@ export class Plan {
55
55
  name: data.resourceName,
56
56
  });
57
57
  function addDefaultValues() {
58
- Object.entries(defaultValues)
58
+ Object.entries(defaultValues ?? {})
59
59
  .forEach(([key, defaultValue]) => {
60
60
  const configValueExists = data
61
61
  .parameters
@@ -98,12 +98,18 @@ export class Plan {
98
98
  }
99
99
  }
100
100
  get desiredConfig() {
101
+ if (this.changeSet.operation === ResourceOperation.DESTROY) {
102
+ return null;
103
+ }
101
104
  return {
102
105
  ...this.resourceMetadata,
103
106
  ...this.changeSet.desiredParameters,
104
107
  };
105
108
  }
106
109
  get currentConfig() {
110
+ if (this.changeSet.operation === ResourceOperation.CREATE) {
111
+ return null;
112
+ }
107
113
  return {
108
114
  ...this.resourceMetadata,
109
115
  ...this.changeSet.currentParameters,
@@ -4,7 +4,8 @@ import { Plan } from './plan.js';
4
4
  export declare class Plugin {
5
5
  name: string;
6
6
  resources: Map<string, Resource<ResourceConfig>>;
7
- planStorage: Map<string, Plan<ResourceConfig>>;
7
+ planStorage: Map<string, Plan<any>>;
8
+ static create(name: string, resources: Resource<any>[]): Plugin;
8
9
  constructor(name: string, resources: Map<string, Resource<ResourceConfig>>);
9
10
  initialize(): Promise<InitializeResponseData>;
10
11
  validate(data: ValidateRequestData): Promise<ValidateResponseData>;
@@ -1,9 +1,15 @@
1
+ import { ResourceOperation } from 'codify-schemas';
1
2
  import { Plan } from './plan.js';
2
3
  import { splitUserConfig } from '../utils/utils.js';
4
+ import { ApplyValidationError } from './errors.js';
3
5
  export class Plugin {
4
6
  name;
5
7
  resources;
6
8
  planStorage;
9
+ static create(name, resources) {
10
+ const resourceMap = new Map(resources.map((r) => [r.typeId, r]));
11
+ return new Plugin(name, resourceMap);
12
+ }
7
13
  constructor(name, resources) {
8
14
  this.name = name;
9
15
  this.resources = resources;
@@ -58,6 +64,10 @@ export class Plugin {
58
64
  throw new Error('Malformed plan with resource that cannot be found');
59
65
  }
60
66
  await resource.apply(plan);
67
+ const validationPlan = await resource.plan(plan.desiredConfig, plan.currentConfig, true);
68
+ if (validationPlan.changeSet.operation !== ResourceOperation.NOOP) {
69
+ throw new ApplyValidationError(plan, validationPlan);
70
+ }
61
71
  }
62
72
  resolvePlan(data) {
63
73
  const { planId, plan: planRequest } = data;
@@ -1,6 +1,6 @@
1
1
  export type ErrorMessage = string;
2
2
  export interface ResourceParameterOptions {
3
- canModify?: boolean;
3
+ modifyOnChange?: boolean;
4
4
  isEqual?: (desired: any, current: any) => boolean;
5
5
  default?: unknown;
6
6
  }
@@ -24,7 +24,7 @@ export declare abstract class Resource<T extends StringIndexedObject> {
24
24
  protected constructor(options: ResourceOptions<T>);
25
25
  onInitialize(): Promise<void>;
26
26
  validateResource(parameters: unknown): Promise<ValidationResult>;
27
- plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>>;
27
+ plan(desiredConfig: Partial<T> & ResourceConfig | null, currentConfig?: Partial<T> & ResourceConfig | null, statefulMode?: boolean): Promise<Plan<T>>;
28
28
  apply(plan: Plan<T>): Promise<void>;
29
29
  private _applyCreate;
30
30
  private _applyModify;
@@ -32,8 +32,9 @@ export declare abstract class Resource<T extends StringIndexedObject> {
32
32
  private validateRefreshResults;
33
33
  private applyTransformParameters;
34
34
  private addDefaultValues;
35
- private refreshResourceParameters;
35
+ private refreshNonStatefulParameters;
36
36
  private refreshStatefulParameters;
37
+ private validatePlanInputs;
37
38
  validate(parameters: unknown): Promise<ValidationResult>;
38
39
  abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
39
40
  abstract applyCreate(plan: Plan<T>): Promise<void>;
@@ -49,21 +49,22 @@ export class Resource {
49
49
  }
50
50
  return this.validate(parameters);
51
51
  }
52
- async plan(desiredConfig) {
52
+ async plan(desiredConfig, currentConfig = null, statefulMode = false) {
53
+ this.validatePlanInputs(desiredConfig, currentConfig, statefulMode);
53
54
  const planOptions = {
54
- statefulMode: false,
55
+ statefulMode,
55
56
  parameterOptions: this.parameterOptions,
56
57
  };
57
58
  this.addDefaultValues(desiredConfig);
58
- const parsedConfig = new ConfigParser(desiredConfig, this.statefulParameters, this.transformParameters);
59
- const { parameters: desiredParameters, resourceMetadata, resourceParameters, statefulParameters, transformParameters, } = parsedConfig;
60
- await this.applyTransformParameters(transformParameters, resourceParameters);
61
- const currentParameters = await this.refreshResourceParameters(resourceParameters);
59
+ await this.applyTransformParameters(desiredConfig);
60
+ const parsedConfig = new ConfigParser(desiredConfig, currentConfig, this.statefulParameters, this.transformParameters);
61
+ const { desiredParameters, resourceMetadata, nonStatefulParameters, statefulParameters, } = parsedConfig;
62
+ const currentParameters = await this.refreshNonStatefulParameters(nonStatefulParameters);
62
63
  if (currentParameters == null) {
63
- return Plan.create({ ...resourceParameters, ...statefulParameters }, null, resourceMetadata, planOptions);
64
+ return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
64
65
  }
65
66
  const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
66
- return Plan.create({ ...resourceParameters, ...statefulParameters }, { ...currentParameters, ...statefulCurrentParameters }, resourceMetadata, planOptions);
67
+ return Plan.create(desiredParameters, { ...currentParameters, ...statefulCurrentParameters }, resourceMetadata, planOptions);
67
68
  }
68
69
  async apply(plan) {
69
70
  if (plan.getResourceType() !== this.typeId) {
@@ -151,20 +152,30 @@ Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
151
152
  Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
152
153
  }
153
154
  }
154
- async applyTransformParameters(transformParameters, desired) {
155
- const orderedEntries = [...Object.entries(transformParameters)]
155
+ async applyTransformParameters(desired) {
156
+ if (!desired) {
157
+ return;
158
+ }
159
+ const transformParameters = [...this.transformParameters.entries()]
156
160
  .sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA) - this.transformParameterOrder.get(keyB));
157
- for (const [key, value] of orderedEntries) {
158
- const transformedValue = await this.transformParameters.get(key).transform(value);
161
+ for (const [key, transformParameter] of transformParameters) {
162
+ if (desired[key] === undefined) {
163
+ continue;
164
+ }
165
+ const transformedValue = await transformParameter.transform(desired[key]);
159
166
  if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
160
167
  throw new Error(`Transform parameter ${key} is attempting to override existing values ${JSON.stringify(transformedValue, null, 2)}`);
161
168
  }
169
+ delete desired[key];
162
170
  Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
163
171
  desired[tvKey] = tvValue;
164
172
  });
165
173
  }
166
174
  }
167
175
  addDefaultValues(desired) {
176
+ if (!desired) {
177
+ return;
178
+ }
168
179
  Object.entries(this.defaultValues)
169
180
  .forEach(([key, defaultValue]) => {
170
181
  if (defaultValue !== undefined && desired[key] === undefined) {
@@ -172,7 +183,7 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
172
183
  }
173
184
  });
174
185
  }
175
- async refreshResourceParameters(resourceParameters) {
186
+ async refreshNonStatefulParameters(resourceParameters) {
176
187
  const entriesToRefresh = new Map(Object.entries(resourceParameters));
177
188
  const currentParameters = await this.refresh(entriesToRefresh);
178
189
  this.validateRefreshResults(currentParameters, entriesToRefresh);
@@ -204,6 +215,14 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
204
215
  }
205
216
  return currentParameters;
206
217
  }
218
+ validatePlanInputs(desired, current, statefulMode) {
219
+ if (!desired && !current) {
220
+ throw new Error('Desired config and current config cannot both be missing');
221
+ }
222
+ if (!statefulMode && !desired) {
223
+ throw new Error('Desired config must be provided in non-stateful mode');
224
+ }
225
+ }
207
226
  async validate(parameters) {
208
227
  return {
209
228
  isValid: true,
@@ -214,23 +233,46 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
214
233
  ;
215
234
  }
216
235
  class ConfigParser {
217
- config;
236
+ desiredConfig;
237
+ currentConfig;
218
238
  statefulParametersMap;
219
239
  transformParametersMap;
220
- constructor(config, statefulParameters, transformParameters) {
221
- this.config = config;
240
+ constructor(desiredConfig, currentConfig, statefulParameters, transformParameters) {
241
+ this.desiredConfig = desiredConfig;
242
+ this.currentConfig = currentConfig;
222
243
  this.statefulParametersMap = statefulParameters;
223
244
  this.transformParametersMap = transformParameters;
224
245
  }
225
246
  get resourceMetadata() {
226
- const { resourceMetadata } = splitUserConfig(this.config);
227
- return resourceMetadata;
247
+ const desiredMetadata = this.desiredConfig ? splitUserConfig(this.desiredConfig).resourceMetadata : undefined;
248
+ const currentMetadata = this.currentConfig ? splitUserConfig(this.currentConfig).resourceMetadata : undefined;
249
+ if (!desiredMetadata && !currentMetadata) {
250
+ throw new Error(`Unable to parse resource metadata from ${this.desiredConfig}, ${this.currentConfig}`);
251
+ }
252
+ if (currentMetadata && desiredMetadata && (Object.keys(desiredMetadata).length !== Object.keys(currentMetadata).length
253
+ || Object.entries(desiredMetadata).some(([key, value]) => currentMetadata[key] !== value))) {
254
+ throw new Error(`The metadata for the current config does not match the desired config.
255
+ Desired metadata:
256
+ ${JSON.stringify(desiredMetadata, null, 2)}
257
+
258
+ Current metadata:
259
+ ${JSON.stringify(currentMetadata, null, 2)}`);
260
+ }
261
+ return desiredMetadata ?? currentMetadata;
228
262
  }
229
- get parameters() {
230
- const { parameters } = splitUserConfig(this.config);
263
+ get desiredParameters() {
264
+ if (!this.desiredConfig) {
265
+ return null;
266
+ }
267
+ const { parameters } = splitUserConfig(this.desiredConfig);
231
268
  return parameters;
232
269
  }
233
- get resourceParameters() {
270
+ get parameters() {
271
+ const desiredParameters = this.desiredConfig ? splitUserConfig(this.desiredConfig).parameters : undefined;
272
+ const currentParameters = this.currentConfig ? splitUserConfig(this.currentConfig).parameters : undefined;
273
+ return { ...(desiredParameters ?? {}), ...(currentParameters ?? {}) };
274
+ }
275
+ get nonStatefulParameters() {
234
276
  const parameters = this.parameters;
235
277
  return Object.fromEntries([
236
278
  ...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
@@ -242,10 +284,4 @@ class ConfigParser {
242
284
  ...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
243
285
  ]);
244
286
  }
245
- get transformParameters() {
246
- const parameters = this.parameters;
247
- return Object.fromEntries([
248
- ...Object.entries(parameters).filter(([key]) => this.transformParametersMap.has(key)),
249
- ]);
250
- }
251
287
  }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,5 @@ export * from './entities/plan.js';
8
8
  export * from './entities/plan-types.js';
9
9
  export * from './entities/stateful-parameter.js';
10
10
  export * from './entities/errors.js';
11
- export * from './utils/test-utils.js';
12
11
  export * from './utils/utils.js';
13
12
  export declare function runPlugin(plugin: Plugin): Promise<void>;
package/dist/index.js CHANGED
@@ -8,7 +8,6 @@ export * from './entities/plan.js';
8
8
  export * from './entities/plan-types.js';
9
9
  export * from './entities/stateful-parameter.js';
10
10
  export * from './entities/errors.js';
11
- export * from './utils/test-utils.js';
12
11
  export * from './utils/utils.js';
13
12
  export async function runPlugin(plugin) {
14
13
  const messageHandler = new MessageHandler(plugin);
@@ -1,7 +1,7 @@
1
1
  import addFormats from 'ajv-formats';
2
2
  import { ApplyRequestDataSchema, ApplyResponseDataSchema, InitializeRequestDataSchema, InitializeResponseDataSchema, IpcMessageSchema, MessageStatus, PlanRequestDataSchema, PlanResponseDataSchema, ResourceSchema, ValidateRequestDataSchema, ValidateResponseDataSchema } from 'codify-schemas';
3
3
  import Ajv2020 from 'ajv/dist/2020.js';
4
- import { SudoError } from '../entities/errors.js';
4
+ import { ApplyValidationError, SudoError } from '../entities/errors.js';
5
5
  const SupportedRequests = {
6
6
  'initialize': {
7
7
  requestValidator: InitializeRequestDataSchema,
@@ -83,12 +83,26 @@ export class MessageHandler {
83
83
  }
84
84
  const cmd = message.cmd + '_Response';
85
85
  if (e instanceof SudoError) {
86
- process.send?.({
86
+ return process.send?.({
87
87
  cmd,
88
88
  status: MessageStatus.ERROR,
89
89
  data: `Plugin: '${this.plugin.name}'. Forbidden usage of sudo for command '${e.command}'. Please contact the plugin developer to fix this.`,
90
90
  });
91
91
  }
92
+ if (e instanceof ApplyValidationError) {
93
+ return process.send?.({
94
+ cmd,
95
+ status: MessageStatus.ERROR,
96
+ data: `Plugin: '${this.plugin.name}'. Apply validation was not successful (additional changes are needed to match the desired plan).
97
+
98
+ Validation plan:
99
+ ${JSON.stringify(e.validatedPlan, null, 2)},
100
+
101
+ User desired plan:
102
+ ${JSON.stringify(e.desiredPlan, null, 2)}
103
+ `
104
+ });
105
+ }
92
106
  const isDebug = process.env.DEBUG?.includes('*') ?? false;
93
107
  process.send?.({
94
108
  cmd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codify-plugin-lib",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -123,8 +123,8 @@ export class ChangeSet<T extends StringIndexedObject> {
123
123
  ): ParameterChange<T>[] {
124
124
  const parameterChangeSet = new Array<ParameterChange<T>>();
125
125
 
126
- const _desired = { ...desired };
127
- const _current = { ...current };
126
+ const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
127
+ const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
128
128
 
129
129
  this.addDefaultValues(_desired, parameterOptions);
130
130
 
@@ -190,8 +190,9 @@ export class ChangeSet<T extends StringIndexedObject> {
190
190
  ): ParameterChange<T>[] {
191
191
  const parameterChangeSet = new Array<ParameterChange<T>>();
192
192
 
193
- const _desired = { ...desired };
194
- const _current = { ...current };
193
+ const _desired = Object.fromEntries(Object.entries(desired ?? {}).filter(([, v]) => v != null));
194
+ const _current = Object.fromEntries(Object.entries(current ?? {}).filter(([, v]) => v != null));
195
+
195
196
 
196
197
  this.addDefaultValues(_desired, parameterOptions);
197
198
 
@@ -1,3 +1,6 @@
1
+ import { Plan } from './plan.js';
2
+ import { StringIndexedObject } from 'codify-schemas';
3
+
1
4
  export class SudoError extends Error {
2
5
  command: string;
3
6
 
@@ -6,3 +9,17 @@ export class SudoError extends Error {
6
9
  this.command = command;
7
10
  }
8
11
  }
12
+
13
+ export class ApplyValidationError<T extends StringIndexedObject> extends Error {
14
+ desiredPlan: Plan<T>;
15
+ validatedPlan: Plan<T>;
16
+
17
+ constructor(
18
+ desiredPlan: Plan<T>,
19
+ validatedPlan: Plan<T>
20
+ ) {
21
+ super();
22
+ this.desiredPlan = desiredPlan;
23
+ this.validatedPlan = validatedPlan;
24
+ }
25
+ }
@@ -5,7 +5,7 @@ export interface ParameterOptions {
5
5
  /**
6
6
  * Chose if the resource should be re-created or modified if this parameter is changed. Defaults to false (re-creates resource on change).
7
7
  */
8
- canModify?: boolean;
8
+ modifyOnChange?: boolean;
9
9
  /**
10
10
  * Customize the equality comparison for a parameter.
11
11
  * @param a
@@ -19,11 +19,7 @@ describe('Plan entity tests', () => {
19
19
  }]
20
20
  }, resource.defaultValues);
21
21
 
22
- expect(plan.currentConfig).toMatchObject({
23
- type: 'type',
24
- propA: null,
25
- propB: null,
26
- })
22
+ expect(plan.currentConfig).to.be.null;
27
23
 
28
24
  expect(plan.desiredConfig).toMatchObject({
29
25
  type: 'type',
@@ -56,11 +52,7 @@ describe('Plan entity tests', () => {
56
52
  propB: 'propBValue',
57
53
  })
58
54
 
59
- expect(plan.desiredConfig).toMatchObject({
60
- type: 'type',
61
- propA: null,
62
- propB: null,
63
- })
55
+ expect(plan.desiredConfig).to.be.null;
64
56
 
65
57
  expect(plan.changeSet.parameterChanges
66
58
  .every((pc) => pc.operation === ParameterOperation.REMOVE)
@@ -117,11 +109,7 @@ describe('Plan entity tests', () => {
117
109
  }]
118
110
  }, resource.defaultValues);
119
111
 
120
- expect(plan.currentConfig).toMatchObject({
121
- type: 'type',
122
- propA: null,
123
- propB: null,
124
- })
112
+ expect(plan.currentConfig).to.be.null
125
113
 
126
114
  expect(plan.desiredConfig).toMatchObject({
127
115
  type: 'type',
@@ -55,8 +55,8 @@ export class Plan<T extends StringIndexedObject> {
55
55
  let newOperation: ResourceOperation;
56
56
  if (statefulParameterNames.has(curr.name)) {
57
57
  newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
58
- } else if (parameterOptions[curr.name]?.canModify) {
59
- newOperation = parameterOptions[curr.name].canModify ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
58
+ } else if (parameterOptions[curr.name]?.modifyOnChange) {
59
+ newOperation = parameterOptions[curr.name].modifyOnChange ? ResourceOperation.MODIFY : ResourceOperation.RECREATE;
60
60
  } else {
61
61
  newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
62
62
  }
@@ -75,7 +75,7 @@ export class Plan<T extends StringIndexedObject> {
75
75
  return this.resourceMetadata.type
76
76
  }
77
77
 
78
- static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues: Partial<Record<keyof T, unknown>>): Plan<T> {
78
+ static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues?: Partial<Record<keyof T, unknown>>): Plan<T> {
79
79
  if (!data) {
80
80
  throw new Error('Data is empty');
81
81
  }
@@ -95,7 +95,7 @@ export class Plan<T extends StringIndexedObject> {
95
95
  );
96
96
 
97
97
  function addDefaultValues(): void {
98
- Object.entries(defaultValues)
98
+ Object.entries(defaultValues ?? {})
99
99
  .forEach(([key, defaultValue]) => {
100
100
  const configValueExists = data!
101
101
  .parameters
@@ -144,14 +144,22 @@ export class Plan<T extends StringIndexedObject> {
144
144
 
145
145
  }
146
146
 
147
- get desiredConfig(): T {
147
+ get desiredConfig(): T | null {
148
+ if (this.changeSet.operation === ResourceOperation.DESTROY) {
149
+ return null;
150
+ }
151
+
148
152
  return {
149
153
  ...this.resourceMetadata,
150
154
  ...this.changeSet.desiredParameters,
151
155
  }
152
156
  }
153
157
 
154
- get currentConfig(): T {
158
+ get currentConfig(): T | null {
159
+ if (this.changeSet.operation === ResourceOperation.CREATE) {
160
+ return null;
161
+ }
162
+
155
163
  return {
156
164
  ...this.resourceMetadata,
157
165
  ...this.changeSet.currentParameters,