codify-plugin-lib 1.0.37 → 1.0.39

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.
@@ -1,25 +1,49 @@
1
- import { ParameterOperation, ResourceConfig, ResourceOperation } from 'codify-schemas';
1
+ import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterConfiguration } from './plan-types.js';
2
3
 
3
- export interface ParameterChange {
4
- name: string;
4
+ export interface ParameterChange<T extends StringIndexedObject> {
5
+ name: keyof T & string;
5
6
  operation: ParameterOperation;
6
7
  previousValue: any | null;
7
8
  newValue: any | null;
8
9
  }
9
10
 
10
- export class ChangeSet {
11
+ export class ChangeSet<T extends StringIndexedObject> {
11
12
  operation: ResourceOperation
12
- parameterChanges: Array<ParameterChange>
13
+ parameterChanges: Array<ParameterChange<T>>
13
14
 
14
15
  constructor(
15
16
  operation: ResourceOperation,
16
- parameterChanges: Array<ParameterChange>
17
+ parameterChanges: Array<ParameterChange<T>>
17
18
  ) {
18
19
  this.operation = operation;
19
20
  this.parameterChanges = parameterChanges;
20
21
  }
21
22
 
22
- static createForNullCurrentConfig(desiredConfig: ResourceConfig) {
23
+ get desiredParameters(): T {
24
+ return this.parameterChanges
25
+ .reduce((obj, pc) => ({
26
+ ...obj,
27
+ [pc.name]: pc.newValue,
28
+ }), {}) as T;
29
+ }
30
+
31
+ get currentParameters(): T {
32
+ return this.parameterChanges
33
+ .reduce((obj, pc) => ({
34
+ ...obj,
35
+ [pc.name]: pc.previousValue,
36
+ }), {}) as T;
37
+ }
38
+
39
+ // static create<T extends Record<string, unknown>>(prev: T, next: T, options: {
40
+ // statefulMode: boolean,
41
+ // }): ChangeSet {
42
+ // const parameterChanges = ChangeSet.calculateParameterChangeSet(prev, prev, options);
43
+ // const operation = ChangeSet.combineResourceOperations(prev, );
44
+ // }
45
+
46
+ static newCreate<T extends {}>(desiredConfig: T) {
23
47
  const parameterChangeSet = Object.entries(desiredConfig)
24
48
  .filter(([k,]) => k !== 'type' && k !== 'name')
25
49
  .map(([k, v]) => {
@@ -34,21 +58,67 @@ export class ChangeSet {
34
58
  return new ChangeSet(ResourceOperation.CREATE, parameterChangeSet);
35
59
  }
36
60
 
37
- static calculateParameterChangeSet(prev: ResourceConfig, next: ResourceConfig): ParameterChange[] {
38
- const parameterChangeSet = new Array<ParameterChange>();
61
+ static calculateParameterChangeSet<T extends StringIndexedObject>(
62
+ desired: T | null,
63
+ current: T | null,
64
+ options: { statefulMode: boolean, parameterConfigurations?: Record<keyof T, ParameterConfiguration> },
65
+ ): ParameterChange<T>[] {
66
+ if (options.statefulMode) {
67
+ return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterConfigurations);
68
+ } else {
69
+ return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterConfigurations);
70
+ }
71
+ }
39
72
 
40
- const filteredPrev = Object.fromEntries(
41
- Object.entries(prev)
42
- .filter(([k,]) => k !== 'type' && k !== 'name')
43
- );
73
+ static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation) {
74
+ const orderOfOperations = [
75
+ ResourceOperation.NOOP,
76
+ ResourceOperation.MODIFY,
77
+ ResourceOperation.RECREATE,
78
+ ResourceOperation.CREATE,
79
+ ResourceOperation.DESTROY,
80
+ ]
44
81
 
45
- const filteredNext = Object.fromEntries(
46
- Object.entries(next)
47
- .filter(([k,]) => k !== 'type' && k !== 'name')
48
- );
82
+ const indexPrev = orderOfOperations.indexOf(prev);
83
+ const indexNext = orderOfOperations.indexOf(next);
49
84
 
50
- for (const [k, v] of Object.entries(filteredPrev)) {
51
- if (!filteredNext[k]) {
85
+ return orderOfOperations[Math.max(indexPrev, indexNext)];
86
+ }
87
+
88
+ static isSame(
89
+ a: unknown,
90
+ b: unknown,
91
+ isEqual?: (a: unknown, b: unknown) => boolean,
92
+ ): boolean {
93
+ if (isEqual) {
94
+ return isEqual(a, b);
95
+ }
96
+
97
+ if (Array.isArray(a) && Array.isArray(b)) {
98
+ const sortedPrev = a.map((x) => x).sort();
99
+ const sortedNext = b.map((x) => x).sort();
100
+
101
+ return JSON.stringify(sortedPrev) === JSON.stringify(sortedNext);
102
+ }
103
+
104
+ return a === b;
105
+ }
106
+
107
+ // Explanation: Stateful mode means that codify maintains a stateful to keep track of resources it has added.
108
+ // When a resource is removed from a stateful config, it will be deleted from the system.
109
+ private static calculateStatefulModeChangeSet<T extends StringIndexedObject>(
110
+ desired: T | null,
111
+ current: T | null,
112
+ parameterConfigurations?: Record<keyof T, ParameterConfiguration>,
113
+ ): ParameterChange<T>[] {
114
+ const parameterChangeSet = new Array<ParameterChange<T>>();
115
+
116
+ const _desired = { ...desired };
117
+ const _current = { ...current };
118
+
119
+
120
+ for (const [k, v] of Object.entries(_current)) {
121
+ if (_desired[k] == null) {
52
122
  parameterChangeSet.push({
53
123
  name: k,
54
124
  previousValue: v,
@@ -56,39 +126,39 @@ export class ChangeSet {
56
126
  operation: ParameterOperation.REMOVE,
57
127
  })
58
128
 
59
- delete filteredPrev[k];
129
+ delete _current[k];
60
130
  continue;
61
131
  }
62
132
 
63
- if (!ChangeSet.isSame(filteredPrev[k], filteredNext[k])) {
133
+ if (!ChangeSet.isSame(_current[k], _desired[k], parameterConfigurations?.[k]?.isEqual)) {
64
134
  parameterChangeSet.push({
65
135
  name: k,
66
136
  previousValue: v,
67
- newValue: filteredNext[k],
137
+ newValue: _desired[k],
68
138
  operation: ParameterOperation.MODIFY,
69
139
  })
70
140
 
71
- delete filteredPrev[k];
72
- delete filteredNext[k];
141
+ delete _current[k];
142
+ delete _desired[k];
73
143
  continue;
74
144
  }
75
145
 
76
146
  parameterChangeSet.push({
77
147
  name: k,
78
148
  previousValue: v,
79
- newValue: filteredNext[k],
149
+ newValue: _desired[k],
80
150
  operation: ParameterOperation.NOOP,
81
151
  })
82
152
 
83
- delete filteredPrev[k];
84
- delete filteredNext[k];
153
+ delete _current[k];
154
+ delete _desired[k];
85
155
  }
86
156
 
87
- if (Object.keys(filteredPrev).length !== 0) {
157
+ if (Object.keys(_current).length !== 0) {
88
158
  throw Error('Diff algorithm error');
89
159
  }
90
160
 
91
- for (const [k, v] of Object.entries(filteredNext)) {
161
+ for (const [k, v] of Object.entries(_desired)) {
92
162
  parameterChangeSet.push({
93
163
  name: k,
94
164
  previousValue: null,
@@ -100,29 +170,50 @@ export class ChangeSet {
100
170
  return parameterChangeSet;
101
171
  }
102
172
 
103
- static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation) {
104
- const orderOfOperations = [
105
- ResourceOperation.NOOP,
106
- ResourceOperation.MODIFY,
107
- ResourceOperation.RECREATE,
108
- ResourceOperation.CREATE,
109
- ResourceOperation.DESTROY,
110
- ]
173
+ // Explanation: Stateful mode means that codify does not keep track of state. Resources in stateless mode can only
174
+ // be added by Codify and not destroyed.
175
+ private static calculateStatelessModeChangeSet<T extends StringIndexedObject>(
176
+ desired: T | null,
177
+ current: T | null,
178
+ parameterConfigurations?: Record<keyof T, ParameterConfiguration>,
179
+ ): ParameterChange<T>[] {
180
+ const parameterChangeSet = new Array<ParameterChange<T>>();
111
181
 
112
- const indexPrev = orderOfOperations.indexOf(prev);
113
- const indexNext = orderOfOperations.indexOf(next);
182
+ const _desired = { ...desired };
183
+ const _current = { ...current };
114
184
 
115
- return orderOfOperations[Math.max(indexPrev, indexNext)];
116
- }
185
+ for (const [k, v] of Object.entries(_desired)) {
186
+ if (_current[k] == null) {
187
+ parameterChangeSet.push({
188
+ name: k,
189
+ previousValue: null,
190
+ newValue: v,
191
+ operation: ParameterOperation.ADD,
192
+ });
117
193
 
118
- static isSame(a: unknown, b: unknown): boolean {
119
- if (Array.isArray(a) && Array.isArray(b)) {
120
- const sortedPrev = a.map((x) => x).sort();
121
- const sortedNext = b.map((x) => x).sort();
194
+ continue;
195
+ }
122
196
 
123
- return JSON.stringify(sortedPrev) === JSON.stringify(sortedNext);
197
+ if (!ChangeSet.isSame(_current[k], _desired[k], parameterConfigurations?.[k]?.isEqual)) {
198
+ parameterChangeSet.push({
199
+ name: k,
200
+ previousValue: _current[k],
201
+ newValue: _desired[k],
202
+ operation: ParameterOperation.MODIFY,
203
+ });
204
+
205
+ continue;
206
+ }
207
+
208
+ parameterChangeSet.push({
209
+ name: k,
210
+ previousValue: v,
211
+ newValue: v,
212
+ operation: ParameterOperation.NOOP,
213
+ })
124
214
  }
125
215
 
126
- return a === b;
216
+ return parameterChangeSet;
127
217
  }
218
+
128
219
  }
@@ -0,0 +1,26 @@
1
+ import { ResourceOperation } from 'codify-schemas';
2
+
3
+ /**
4
+ * Customize properties for specific parameters. This will alter the way the library process changes to the parameter.
5
+ */
6
+ export interface ParameterConfiguration {
7
+ /**
8
+ * Chose if the resource should be re-created or modified if this parameter is changed. Defaults to re-create.
9
+ */
10
+ planOperation?: ResourceOperation.MODIFY | ResourceOperation.RECREATE;
11
+ /**
12
+ * Customize the equality comparison for a parameter.
13
+ * @param a
14
+ * @param b
15
+ */
16
+ isEqual?: (a: any, b: any) => boolean;
17
+
18
+ isArrayElementEqual?: (a: any, b: any) => boolean;
19
+
20
+ isStatefulParameter?: boolean;
21
+ }
22
+
23
+ export interface PlanConfiguration<T> {
24
+ statefulMode: boolean;
25
+ parameterConfigurations?: Record<keyof T, ParameterConfiguration>;
26
+ }
@@ -1,38 +1,95 @@
1
- import { ChangeSet } from './change-set.js';
2
- import { ApplyRequestData, PlanResponseData, ResourceConfig, } from 'codify-schemas';
1
+ import { ChangeSet, ParameterChange } from './change-set.js';
2
+ import {
3
+ ApplyRequestData,
4
+ ParameterOperation,
5
+ PlanResponseData,
6
+ ResourceConfig,
7
+ ResourceOperation,
8
+ StringIndexedObject,
9
+ } from 'codify-schemas';
3
10
  import { randomUUID } from 'crypto';
11
+ import { ParameterConfiguration, PlanConfiguration } from './plan-types.js';
12
+ import { splitUserConfig } from '../utils/utils.js';
4
13
 
5
- export class Plan<T extends ResourceConfig> {
14
+ export class Plan<T extends StringIndexedObject> {
6
15
  id: string;
7
- changeSet: ChangeSet;
8
- resourceConfig: T
16
+ changeSet: ChangeSet<T>;
17
+ resourceMetadata: ResourceConfig
9
18
 
10
- constructor(id: string, changeSet: ChangeSet, resourceConfig: T) {
19
+ constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig) {
11
20
  this.id = id;
12
21
  this.changeSet = changeSet;
13
- this.resourceConfig = resourceConfig;
22
+ this.resourceMetadata = resourceMetadata;
14
23
  }
15
24
 
16
- static create<T extends ResourceConfig>(changeSet: ChangeSet, resourceConfig: T): Plan<T> {
25
+ static create<T extends StringIndexedObject>(
26
+ desiredConfig: Partial<T> & ResourceConfig,
27
+ currentConfig: Partial<T> & ResourceConfig | null,
28
+ configuration: PlanConfiguration<T>
29
+ ): Plan<T> {
30
+ const parameterConfigurations = configuration.parameterConfigurations ?? {} as Record<keyof T, ParameterConfiguration>;
31
+ const statefulParameterNames = new Set(
32
+ [...Object.entries(parameterConfigurations)]
33
+ .filter(([k, v]) => v.isStatefulParameter)
34
+ .map(([k, v]) => k)
35
+ );
36
+
37
+ const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
38
+ const { parameters: currentParameters } = currentConfig != null ? splitUserConfig(currentConfig) : { parameters: null };
39
+
40
+
41
+ // TODO: After adding in state files, need to calculate deletes here
42
+ // Where current config exists and state config exists but desired config doesn't
43
+
44
+ // Explanation: This calculates the change set of the parameters between the
45
+ // two configs and then passes it to ChangeSet to calculate the overall
46
+ // operation for the resource
47
+ const parameterChangeSet = ChangeSet.calculateParameterChangeSet(
48
+ desiredParameters,
49
+ currentParameters,
50
+ { statefulMode: configuration.statefulMode, parameterConfigurations }
51
+ );
52
+
53
+ let resourceOperation: ResourceOperation;
54
+ if (!currentConfig && desiredConfig) {
55
+ resourceOperation = ResourceOperation.CREATE;
56
+ } else if (currentConfig && !desiredConfig) {
57
+ resourceOperation = ResourceOperation.DESTROY;
58
+ } else {
59
+ resourceOperation = parameterChangeSet
60
+ .filter((change) => change.operation !== ParameterOperation.NOOP)
61
+ .reduce((operation: ResourceOperation, curr: ParameterChange<T>) => {
62
+ let newOperation: ResourceOperation;
63
+ if (statefulParameterNames.has(curr.name)) {
64
+ newOperation = ResourceOperation.MODIFY // All stateful parameters are modify only
65
+ } else if (parameterConfigurations[curr.name]?.planOperation) {
66
+ newOperation = parameterConfigurations[curr.name].planOperation!;
67
+ } else {
68
+ newOperation = ResourceOperation.RECREATE; // Default to Re-create. Should handle the majority of use cases
69
+ }
70
+ return ChangeSet.combineResourceOperations(operation, newOperation);
71
+ }, ResourceOperation.NOOP);
72
+ }
73
+
17
74
  return new Plan(
18
75
  randomUUID(),
19
- changeSet,
20
- resourceConfig,
21
- )
76
+ new ChangeSet<T>(resourceOperation, parameterChangeSet),
77
+ resourceMetadata,
78
+ );
22
79
  }
23
80
 
24
81
  getResourceType(): string {
25
- return this.resourceConfig.type;
82
+ return this.resourceMetadata.type
26
83
  }
27
84
 
28
- static fromResponse(data: ApplyRequestData['plan']): Plan<ResourceConfig> {
85
+ static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan']): Plan<T> {
29
86
  if (!data) {
30
87
  throw new Error('Data is empty');
31
88
  }
32
89
 
33
90
  return new Plan(
34
91
  randomUUID(),
35
- new ChangeSet(
92
+ new ChangeSet<T>(
36
93
  data.operation,
37
94
  data.parameters.map(value => ({
38
95
  ...value,
@@ -50,12 +107,26 @@ export class Plan<T extends ResourceConfig> {
50
107
  );
51
108
  }
52
109
 
110
+ get desiredConfig(): T {
111
+ return {
112
+ ...this.resourceMetadata,
113
+ ...this.changeSet.desiredParameters,
114
+ }
115
+ }
116
+
117
+ get currentConfig(): T {
118
+ return {
119
+ ...this.resourceMetadata,
120
+ ...this.changeSet.currentParameters,
121
+ }
122
+ }
123
+
53
124
  toResponse(): PlanResponseData {
54
125
  return {
55
126
  planId: this.id,
56
127
  operation: this.changeSet.operation,
57
- resourceName: this.resourceConfig.name,
58
- resourceType: this.resourceConfig.type,
128
+ resourceName: this.resourceMetadata.name,
129
+ resourceType: this.resourceMetadata.type,
59
130
  parameters: this.changeSet.parameterChanges,
60
131
  }
61
132
  }
@@ -27,30 +27,32 @@ export class Plugin {
27
27
  return {
28
28
  resourceDefinitions: [...this.resources.values()]
29
29
  .map((r) => ({
30
- type: r.getTypeId(),
30
+ type: r.typeId,
31
31
  dependencies: r.getDependencyTypeIds(),
32
32
  }))
33
33
  }
34
34
  }
35
35
 
36
36
  async validate(data: ValidateRequestData): Promise<ValidateResponseData> {
37
- const totalErrors = [];
37
+ const validationResults = [];
38
38
  for (const config of data.configs) {
39
39
  if (!this.resources.has(config.type)) {
40
40
  throw new Error(`Resource type not found: ${config.type}`);
41
41
  }
42
42
 
43
- const error = await this.resources.get(config.type)!.validate(config);
44
- if (error) {
45
- totalErrors.push(...error);
46
- }
43
+ const validateResult = await this.resources.get(config.type)!.validate(config);
44
+
45
+ validationResults.push({
46
+ ...validateResult,
47
+ resourceType: config.type,
48
+ resourceName: config.name,
49
+ });
47
50
  }
48
51
 
49
52
  await this.crossValidateResources(data.configs);
50
53
  return {
51
- isValid: true,
52
- errors: totalErrors,
53
- }
54
+ validationResults
55
+ };
54
56
  }
55
57
 
56
58
  async plan(data: PlanRequestData): Promise<PlanResponseData> {
@@ -0,0 +1,159 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { StatefulParameter, StatefulParameterConfiguration } from './stateful-parameter.js';
3
+ import { Plan } from './plan.js';
4
+ import { spy } from 'sinon';
5
+ import { ResourceOperation } from 'codify-schemas';
6
+ import { TestConfig, TestResource } from './resource.test.js';
7
+
8
+ class TestParameter extends StatefulParameter<TestConfig, string> {
9
+ constructor(configuration?: StatefulParameterConfiguration<TestConfig>) {
10
+ super(configuration ?? {
11
+ name: 'propA'
12
+ })
13
+ }
14
+
15
+ applyAdd(valueToAdd: string, plan: Plan<TestConfig>): Promise<void> {
16
+ return Promise.resolve();
17
+ }
18
+ applyModify(newValue: string, previousValue: string, allowDeletes: boolean, plan: Plan<TestConfig>): Promise<void> {
19
+ return Promise.resolve();
20
+ }
21
+ applyRemove(valueToRemove: string, plan: Plan<TestConfig>): Promise<void> {
22
+ return Promise.resolve();
23
+ }
24
+ async refresh(): Promise<string | null> {
25
+ return '';
26
+ }
27
+ }
28
+
29
+ describe('Resource parameters tests', () => {
30
+ it('supports the creation of stateful parameters', async () => {
31
+
32
+ const statefulParameter = new class extends TestParameter {
33
+ async refresh(): Promise<string | null> {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ const statefulParameterSpy = spy(statefulParameter);
39
+
40
+ const resource = new class extends TestResource {
41
+
42
+ constructor() {
43
+ super({
44
+ type: 'resource',
45
+ statefulParameters: [statefulParameterSpy],
46
+ });
47
+ }
48
+ }
49
+
50
+ const resourceSpy = spy(resource);
51
+ const result = await resourceSpy.apply(
52
+ Plan.create<TestConfig>(
53
+ { type: 'resource', propA: 'a', propB: 0, propC: 'b' },
54
+ null,
55
+ { statefulMode: false },
56
+ )
57
+ );
58
+
59
+ expect(statefulParameterSpy.applyAdd.calledOnce).to.be.true;
60
+ expect(resourceSpy.applyCreate.calledOnce).to.be.true;
61
+ })
62
+
63
+ it('supports the modification of stateful parameters', async () => {
64
+ const statefulParameter = new class extends TestParameter {
65
+ async refresh(): Promise<string | null> {
66
+ return 'b';
67
+ }
68
+ }
69
+
70
+ const statefulParameterSpy = spy(statefulParameter);
71
+
72
+ const resource = new class extends TestResource {
73
+
74
+ constructor() {
75
+ super({
76
+ type: 'resource',
77
+ statefulParameters: [statefulParameterSpy],
78
+ parameterConfigurations: {
79
+ propB: { planOperation: ResourceOperation.MODIFY },
80
+ }
81
+ });
82
+ }
83
+
84
+ async refresh(): Promise<Partial<TestConfig> | null> {
85
+ return { propB: -1, propC: 'b' }
86
+ }
87
+ }
88
+
89
+ const plan = await resource.plan({ type: 'resource', propA: 'a', propB: 0, propC: 'b' })
90
+
91
+ const resourceSpy = spy(resource);
92
+ const result = await resourceSpy.apply(plan);
93
+
94
+ expect(statefulParameterSpy.applyModify.calledOnce).to.be.true;
95
+ expect(resourceSpy.applyModify.calledOnce).to.be.true;
96
+ })
97
+
98
+ it('Filters array results in stateless mode to prevent modify from being called', async () => {
99
+ const statefulParameter = new class extends TestParameter {
100
+ async refresh(): Promise<any | null> {
101
+ return ['a', 'b', 'c', 'd']
102
+ }
103
+ }
104
+
105
+ const statefulParameterSpy = spy(statefulParameter);
106
+
107
+ const resource = new class extends TestResource {
108
+ constructor() {
109
+ super({
110
+ type: 'resource',
111
+ statefulParameters: [statefulParameterSpy],
112
+ });
113
+ }
114
+
115
+ async refresh(): Promise<Partial<TestConfig> | null> {
116
+ return null;
117
+ }
118
+ }
119
+
120
+ const plan = await resource.plan({ type: 'resource', propA: ['a', 'b'] } as any)
121
+
122
+ expect(plan).toMatchObject({
123
+ changeSet: {
124
+ operation: ResourceOperation.NOOP,
125
+ }
126
+ })
127
+ })
128
+
129
+ it('Filters array results in stateless mode to prevent modify from being called', async () => {
130
+ const statefulParameter = new class extends TestParameter {
131
+ async refresh(): Promise<any | null> {
132
+ return ['a', 'b']
133
+ }
134
+ }
135
+
136
+ const statefulParameterSpy = spy(statefulParameter);
137
+
138
+ const resource = new class extends TestResource {
139
+ constructor() {
140
+ super({
141
+ type: 'resource',
142
+ statefulParameters: [statefulParameterSpy],
143
+ });
144
+ }
145
+
146
+ async refresh(): Promise<Partial<TestConfig> | null> {
147
+ return null;
148
+ }
149
+ }
150
+
151
+ const plan = await resource.plan({ type: 'resource', propA: ['a', 'b', 'c', 'd'] } as any)
152
+
153
+ expect(plan).toMatchObject({
154
+ changeSet: {
155
+ operation: ResourceOperation.MODIFY,
156
+ }
157
+ })
158
+ })
159
+ })
@@ -0,0 +1,47 @@
1
+ import { StatefulParameter } from './stateful-parameter.js';
2
+ import { ResourceOperation, StringIndexedObject } from 'codify-schemas';
3
+ import { Resource } from './resource.js';
4
+
5
+ export type ErrorMessage = string;
6
+
7
+ /**
8
+ * Customize properties for specific parameters. This will alter the way the library process changes to the parameter.
9
+ */
10
+ export interface ResourceParameterConfiguration {
11
+ /**
12
+ * Chose if the resource should be re-created or modified if this parameter is changed. Defaults to re-create.
13
+ */
14
+ planOperation?: ResourceOperation.MODIFY | ResourceOperation.RECREATE;
15
+ /**
16
+ * Customize the equality comparison for a parameter.
17
+ * @param a
18
+ * @param b
19
+ */
20
+ isEqual?: (a: any, b: any) => boolean;
21
+ }
22
+
23
+ /**
24
+ * @param
25
+ */
26
+ export interface ResourceConfiguration<T extends StringIndexedObject> {
27
+ type: string;
28
+ /**
29
+ * If true, statefulParameter.applyRemove() will be called before resource destruction.
30
+ * Defaults to false.
31
+ */
32
+ callStatefulParameterRemoveOnDestroy?: boolean,
33
+ dependencies?: Resource<any>[];
34
+ statefulParameters?: Array<StatefulParameter<T, T[keyof T]>>;
35
+ parameterConfigurations?: Partial<Record<keyof T, ResourceParameterConfiguration>>
36
+ }
37
+
38
+ export interface ResourceDefinition {
39
+ [x: string]: {
40
+ type: string;
41
+ }
42
+ }
43
+
44
+ export interface ValidationResult {
45
+ isValid: boolean;
46
+ errors?: unknown[],
47
+ }