codify-plugin-lib 1.0.76 → 1.0.77

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.
Files changed (70) hide show
  1. package/.eslintrc.json +11 -4
  2. package/.github/workflows/release.yaml +19 -0
  3. package/.github/workflows/unit-test-ci.yaml +19 -0
  4. package/dist/errors.d.ts +4 -0
  5. package/dist/errors.js +7 -0
  6. package/dist/index.d.ts +10 -10
  7. package/dist/index.js +9 -9
  8. package/dist/messages/handlers.d.ts +1 -1
  9. package/dist/messages/handlers.js +2 -1
  10. package/dist/plan/change-set.d.ts +37 -0
  11. package/dist/plan/change-set.js +146 -0
  12. package/dist/plan/plan-types.d.ts +23 -0
  13. package/dist/plan/plan-types.js +1 -0
  14. package/dist/plan/plan.d.ts +59 -0
  15. package/dist/plan/plan.js +228 -0
  16. package/dist/plugin/plugin.d.ts +17 -0
  17. package/dist/plugin/plugin.js +83 -0
  18. package/dist/resource/config-parser.d.ts +14 -0
  19. package/dist/resource/config-parser.js +48 -0
  20. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  21. package/dist/resource/parsed-resource-settings.js +126 -0
  22. package/dist/resource/resource-controller.d.ts +30 -0
  23. package/dist/resource/resource-controller.js +247 -0
  24. package/dist/resource/resource-settings.d.ts +149 -0
  25. package/dist/resource/resource-settings.js +9 -0
  26. package/dist/resource/resource.d.ts +137 -0
  27. package/dist/resource/resource.js +44 -0
  28. package/dist/resource/stateful-parameter.d.ts +164 -0
  29. package/dist/resource/stateful-parameter.js +94 -0
  30. package/dist/utils/utils.d.ts +19 -3
  31. package/dist/utils/utils.js +52 -3
  32. package/package.json +5 -3
  33. package/src/index.ts +10 -11
  34. package/src/messages/handlers.test.ts +10 -37
  35. package/src/messages/handlers.ts +2 -2
  36. package/src/plan/change-set.test.ts +220 -0
  37. package/src/plan/change-set.ts +225 -0
  38. package/src/plan/plan-types.ts +27 -0
  39. package/src/{entities → plan}/plan.test.ts +35 -29
  40. package/src/plan/plan.ts +353 -0
  41. package/src/{entities → plugin}/plugin.test.ts +14 -13
  42. package/src/{entities → plugin}/plugin.ts +28 -24
  43. package/src/resource/config-parser.ts +77 -0
  44. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  45. package/src/resource/parsed-resource-settings.ts +179 -0
  46. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  47. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  48. package/src/resource/resource-controller.ts +340 -0
  49. package/src/resource/resource-settings.test.ts +494 -0
  50. package/src/resource/resource-settings.ts +192 -0
  51. package/src/resource/resource.ts +149 -0
  52. package/src/resource/stateful-parameter.test.ts +93 -0
  53. package/src/resource/stateful-parameter.ts +217 -0
  54. package/src/utils/test-utils.test.ts +87 -0
  55. package/src/utils/utils.test.ts +2 -2
  56. package/src/utils/utils.ts +51 -5
  57. package/tsconfig.json +0 -1
  58. package/vitest.config.ts +10 -0
  59. package/src/entities/change-set.test.ts +0 -155
  60. package/src/entities/change-set.ts +0 -244
  61. package/src/entities/plan-types.ts +0 -44
  62. package/src/entities/plan.ts +0 -178
  63. package/src/entities/resource-options.ts +0 -155
  64. package/src/entities/resource-parameters.test.ts +0 -604
  65. package/src/entities/resource-types.ts +0 -31
  66. package/src/entities/resource.ts +0 -470
  67. package/src/entities/stateful-parameter.test.ts +0 -114
  68. package/src/entities/stateful-parameter.ts +0 -92
  69. package/src/entities/transform-parameter.ts +0 -13
  70. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,77 @@
1
+ import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { splitUserConfig } from '../utils/utils.js';
4
+ import { StatefulParameter } from './stateful-parameter.js';
5
+
6
+ export class ConfigParser<T extends StringIndexedObject> {
7
+ private readonly desiredConfig: Partial<T> & ResourceConfig | null;
8
+ private readonly stateConfig: Partial<T> & ResourceConfig | null;
9
+ private statefulParametersMap: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
10
+
11
+ constructor(
12
+ desiredConfig: Partial<T> & ResourceConfig | null,
13
+ stateConfig: Partial<T> & ResourceConfig | null,
14
+ statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>,
15
+ ) {
16
+ this.desiredConfig = desiredConfig;
17
+ this.stateConfig = stateConfig
18
+ this.statefulParametersMap = statefulParameters;
19
+ }
20
+
21
+ get coreParameters(): ResourceConfig {
22
+ const desiredCoreParameters = this.desiredConfig ? splitUserConfig(this.desiredConfig).coreParameters : undefined;
23
+ const currentCoreParameters = this.stateConfig ? splitUserConfig(this.stateConfig).coreParameters : undefined;
24
+
25
+ if (!desiredCoreParameters && !currentCoreParameters) {
26
+ throw new Error(`Unable to parse resource core parameters from:
27
+
28
+ Desired: ${JSON.stringify(this.desiredConfig, null, 2)}
29
+
30
+ Current: ${JSON.stringify(this.stateConfig, null, 2)}`)
31
+ }
32
+
33
+ return desiredCoreParameters ?? currentCoreParameters!;
34
+ }
35
+
36
+ get desiredParameters(): Partial<T> | null {
37
+ if (!this.desiredConfig) {
38
+ return null;
39
+ }
40
+
41
+ const { parameters } = splitUserConfig(this.desiredConfig);
42
+ return parameters;
43
+ }
44
+
45
+ get stateParameters(): Partial<T> | null {
46
+ if (!this.stateConfig) {
47
+ return null;
48
+ }
49
+
50
+ const { parameters } = splitUserConfig(this.stateConfig);
51
+ return parameters;
52
+ }
53
+
54
+
55
+ get allParameters(): Partial<T> {
56
+ return { ...this.desiredParameters, ...this.stateParameters } as Partial<T>;
57
+ }
58
+
59
+ get allNonStatefulParameters(): Partial<T> {
60
+ const {
61
+ allParameters,
62
+ statefulParametersMap,
63
+ } = this;
64
+
65
+ return Object.fromEntries(
66
+ Object.entries(allParameters).filter(([key]) => !statefulParametersMap.has(key))
67
+ ) as Partial<T>;
68
+ }
69
+
70
+ get allStatefulParameters(): Partial<T> {
71
+ const { allParameters, statefulParametersMap } = this;
72
+
73
+ return Object.fromEntries(
74
+ Object.entries(allParameters).filter(([key]) => statefulParametersMap.has(key))
75
+ ) as Partial<T>;
76
+ }
77
+ }
@@ -1,20 +1,21 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { ResourceOptions, ResourceOptionsParser } from './resource-options.js';
3
- import { TestConfig } from './resource.test.js';
2
+ import { ResourceSettings } from './resource-settings.js';
3
+ import { ParsedResourceSettings } from './parsed-resource-settings.js';
4
+ import { TestConfig } from '../utils/test-utils.test.js';
4
5
 
5
6
  describe('Resource options parser tests', () => {
6
7
  it('Parses default values from options', () => {
7
- const option: ResourceOptions<TestConfig> = {
8
- type: 'typeId',
9
- parameterOptions: {
8
+ const option: ResourceSettings<TestConfig> = {
9
+ id: 'typeId',
10
+ parameterSettings: {
10
11
  propA: { default: 'propA' },
11
12
  propB: { default: 'propB' },
12
13
  propC: { isEqual: () => true },
13
- propD: { },
14
+ propD: {},
14
15
  }
15
16
  }
16
17
 
17
- const result = new ResourceOptionsParser(option);
18
+ const result = new ParsedResourceSettings(option);
18
19
  expect(result.defaultValues).to.deep.eq({
19
20
  propA: 'propA',
20
21
  propB: 'propB'
@@ -0,0 +1,179 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+
3
+ import { areArraysEqual } from '../utils/utils.js';
4
+ import {
5
+ ArrayParameterSetting,
6
+ ParameterEqualsDefaults,
7
+ ParameterSetting,
8
+ ParameterSettingType,
9
+ ResourceSettings,
10
+ StatefulParameterSetting
11
+ } from './resource-settings.js';
12
+ import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js'
13
+
14
+ export class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
15
+ private cache = new Map<string, unknown>();
16
+ id!: string;
17
+ schema?: unknown;
18
+ allowMultiple?: { matcher: (desired: Partial<T>, current: Partial<T>[]) => Partial<T>; } | undefined;
19
+ removeStatefulParametersBeforeDestroy?: boolean | undefined;
20
+ dependencies?: string[] | undefined;
21
+ inputTransformation?: ((desired: Partial<T>) => unknown) | undefined;
22
+ private settings: ResourceSettings<T>;
23
+
24
+ constructor(settings: ResourceSettings<T>) {
25
+ this.settings = settings;
26
+ this.id = settings.id;
27
+ this.schema = settings.schema;
28
+ this.allowMultiple = settings.allowMultiple;
29
+ this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
30
+ this.dependencies = settings.dependencies;
31
+ this.inputTransformation = settings.inputTransformation;
32
+
33
+ this.validateSettings();
34
+ }
35
+
36
+ get typeId(): string {
37
+ return this.id;
38
+ }
39
+
40
+ get statefulParameters(): Map<keyof T, StatefulParameterImpl<T, T[keyof T]>> {
41
+ return this.getFromCacheOrCreate('statefulParameters', () => {
42
+
43
+ const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
44
+ .filter(([, p]) => p?.type === 'stateful')
45
+ .map(([k, v]) => [k, (v as StatefulParameterSetting).definition] as const)
46
+
47
+ return new Map(statefulParameters) as Map<keyof T, StatefulParameterImpl<T, T[keyof T]>>;
48
+ })
49
+ }
50
+
51
+ get parameterSettings(): Record<keyof T, ParameterSetting> {
52
+ return this.getFromCacheOrCreate('parameterSetting', () => {
53
+
54
+ const settings = Object.entries(this.settings.parameterSettings ?? {})
55
+ .map(([k, v]) => [k, v!] as const)
56
+ .map(([k, v]) => {
57
+ v.isEqual = this.resolveEqualsFn(v, k);
58
+
59
+ return [k, v];
60
+ })
61
+
62
+ return Object.fromEntries(settings);
63
+ });
64
+ }
65
+
66
+ get defaultValues(): Partial<Record<keyof T, unknown>> {
67
+ return this.getFromCacheOrCreate('defaultValues', () => {
68
+
69
+ if (!this.settings.parameterSettings) {
70
+ return {};
71
+ }
72
+
73
+ const defaultValues = Object.fromEntries(
74
+ Object.entries(this.settings.parameterSettings)
75
+ .filter(([, v]) => v!.default !== undefined)
76
+ .map(([k, v]) => [k, v!.default] as const)
77
+ )
78
+
79
+ const statefulParameterDefaultValues = Object.fromEntries(
80
+ Object.entries(this.settings.parameterSettings)
81
+ .filter(([, v]) => v?.type === 'stateful')
82
+ .filter(([, v]) => (v as StatefulParameterSetting).definition.getSettings().default !== undefined)
83
+ .map(([k, v]) => [k, (v as StatefulParameterSetting).definition.getSettings().default] as const)
84
+ )
85
+
86
+ return { ...defaultValues, ...statefulParameterDefaultValues } as Partial<Record<keyof T, unknown>>;
87
+ });
88
+ }
89
+
90
+ get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>> {
91
+ return this.getFromCacheOrCreate('inputTransformations', () => {
92
+ if (!this.settings.parameterSettings) {
93
+ return {};
94
+ }
95
+
96
+ return Object.fromEntries(
97
+ Object.entries(this.settings.parameterSettings)
98
+ .filter(([, v]) => v!.inputTransformation !== undefined)
99
+ .map(([k, v]) => [k, v!.inputTransformation!] as const)
100
+ ) as Record<keyof T, (a: unknown) => unknown>;
101
+ });
102
+ }
103
+
104
+ get statefulParameterOrder(): Map<keyof T, number> {
105
+ return this.getFromCacheOrCreate('stateParameterOrder', () => {
106
+
107
+ const entries = Object.entries(this.settings.parameterSettings ?? {})
108
+ .filter(([, v]) => v?.type === 'stateful')
109
+ .map(([k, v]) => [k, v as StatefulParameterSetting] as const)
110
+
111
+ const orderedEntries = entries.filter(([, v]) => v.order !== undefined)
112
+ const unorderedEntries = entries.filter(([, v]) => v.order === undefined)
113
+
114
+ orderedEntries.sort((a, b) => a[1].order! - b[1].order!);
115
+
116
+ const resultArray = [
117
+ ...orderedEntries.map(([k]) => k),
118
+ ...unorderedEntries.map(([k]) => k)
119
+ ]
120
+
121
+ return new Map(resultArray.map((key, idx) => [key, idx]));
122
+ });
123
+ }
124
+
125
+ private validateSettings(): void {
126
+ // validate parameter settings
127
+ if (this.settings.parameterSettings) {
128
+ for (const [k, v] of Object.entries(this.settings.parameterSettings)) {
129
+ if (!v) {
130
+ throw new Error(`Resource: ${this.id}. Parameter setting ${k} was left undefined`);
131
+ }
132
+
133
+ this.validateParameterEqualsFn(v, k);
134
+ }
135
+ }
136
+
137
+ if (this.allowMultiple
138
+ && Object.values(this.parameterSettings).some((v) => v.type === 'stateful')) {
139
+ throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed if multiples of a resource exist`)
140
+ }
141
+ }
142
+
143
+ private validateParameterEqualsFn(parameter: ParameterSetting, key: string): void {
144
+ if (parameter.type === 'stateful') {
145
+ const nestedSettings = (parameter as StatefulParameterSetting).definition.getSettings();
146
+
147
+ if (nestedSettings.type === 'stateful') {
148
+ throw new Error(`Nested stateful parameters are not allowed for ${key}`);
149
+ }
150
+
151
+ this.validateParameterEqualsFn(nestedSettings, key);
152
+ }
153
+
154
+ // The rest of the types have defaults set already
155
+ }
156
+
157
+ private resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
158
+ if (parameter.type === 'array') {
159
+ return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
160
+ }
161
+
162
+ if (parameter.type === 'stateful') {
163
+ return this.resolveEqualsFn((parameter as StatefulParameterSetting).definition.getSettings(), key)
164
+ }
165
+
166
+ return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type as ParameterSettingType] ?? (((a, b) => a === b));
167
+ }
168
+
169
+ private getFromCacheOrCreate<T2>(key: string, create: () => T2): T2 {
170
+ if (this.cache.has(key)) {
171
+ return this.cache.get(key) as T2
172
+ }
173
+
174
+ const result = create();
175
+
176
+ this.cache.set(key, result)
177
+ return result;
178
+ }
179
+ }
@@ -1,17 +1,13 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { TestConfig, TestResource } from './resource.test.js';
3
2
  import { ParameterOperation, ResourceOperation } from 'codify-schemas';
4
- import { TestParameter } from './resource-parameters.test.js';
5
- import { StatefulParameter } from './stateful-parameter.js';
3
+ import { TestConfig, TestResource, TestStatefulParameter } from '../utils/test-utils.test.js';
4
+ import { ResourceSettings } from './resource-settings.js';
5
+ import { ResourceController } from './resource-controller.js';
6
6
 
7
7
 
8
8
  describe('Resource tests for stateful plans', () => {
9
9
  it('Supports delete operations ', async () => {
10
10
  const resource = new class extends TestResource {
11
- constructor() {
12
- super({ type: 'resource' });
13
- }
14
-
15
11
  async refresh(parameters: Partial<TestConfig>): Promise<Partial<TestConfig> | null> {
16
12
  return {
17
13
  propA: 'propADifferent',
@@ -21,10 +17,11 @@ describe('Resource tests for stateful plans', () => {
21
17
  }
22
18
  }
23
19
 
24
- const plan = await resource.plan(
20
+ const controller = new ResourceController(resource);
21
+ const plan = await controller.plan(
25
22
  null,
26
23
  {
27
- type: 'resource',
24
+ type: 'type',
28
25
  propA: 'propA',
29
26
  propB: 10,
30
27
  propC: 'propC',
@@ -41,6 +38,12 @@ describe('Resource tests for stateful plans', () => {
41
38
  newValue: null,
42
39
  operation: ParameterOperation.REMOVE
43
40
  },
41
+ {
42
+ name: 'propB',
43
+ previousValue: null,
44
+ newValue: null,
45
+ operation: ParameterOperation.REMOVE
46
+ },
44
47
  {
45
48
  name: "propC",
46
49
  previousValue: "propCDifferent",
@@ -49,24 +52,21 @@ describe('Resource tests for stateful plans', () => {
49
52
  },
50
53
  ]
51
54
  },
52
- resourceMetadata: {
53
- type: 'resource'
55
+ coreParameters: {
56
+ type: 'type'
54
57
  }
55
58
  })
56
59
  })
57
60
 
58
61
  it('Supports create operations', async () => {
59
62
  const resource = new class extends TestResource {
60
- constructor() {
61
- super({ type: 'resource' });
62
- }
63
-
64
63
  async refresh(): Promise<Partial<TestConfig> | null> {
65
64
  return null;
66
65
  }
67
66
  }
68
67
 
69
- const plan = await resource.plan(
68
+ const controller = new ResourceController(resource);
69
+ const plan = await controller.plan(
70
70
  {
71
71
  type: 'resource',
72
72
  propA: 'propA',
@@ -101,7 +101,7 @@ describe('Resource tests for stateful plans', () => {
101
101
  },
102
102
  ]
103
103
  },
104
- resourceMetadata: {
104
+ coreParameters: {
105
105
  type: 'resource'
106
106
  }
107
107
  })
@@ -109,28 +109,24 @@ describe('Resource tests for stateful plans', () => {
109
109
 
110
110
  it('Supports re-create operations', async () => {
111
111
  const resource = new class extends TestResource {
112
- constructor() {
113
- super({ type: 'resource' });
114
- }
115
-
116
112
  async refresh(): Promise<Partial<TestConfig> | null> {
117
113
  return {
118
114
  propA: 'propA',
119
115
  propC: 'propC',
120
- propB: undefined
121
116
  };
122
117
  }
123
118
  }
124
119
 
125
- const plan = await resource.plan(
120
+ const controller = new ResourceController(resource)
121
+ const plan = await controller.plan(
126
122
  {
127
- type: 'resource',
123
+ type: 'type',
128
124
  propA: 'propA',
129
125
  propB: 10,
130
126
  propC: 'propC',
131
127
  },
132
128
  {
133
- type: 'resource',
129
+ type: 'type',
134
130
  propA: 'propA',
135
131
  propC: 'propC'
136
132
  },
@@ -161,27 +157,27 @@ describe('Resource tests for stateful plans', () => {
161
157
  },
162
158
  ])
163
159
  },
164
- resourceMetadata: {
165
- type: 'resource'
160
+ coreParameters: {
161
+ type: 'type'
166
162
  }
167
163
  })
168
164
  })
169
165
 
170
166
  it('Supports stateful parameters', async () => {
171
- const statefulParameter = new class extends TestParameter {
167
+ const statefulParameter = new class extends TestStatefulParameter {
172
168
  async refresh(): Promise<string | null> {
173
169
  return null;
174
170
  }
175
171
  }
176
172
 
177
173
  const resource = new class extends TestResource {
178
- constructor() {
179
- super({
180
- type: 'resource',
181
- parameterOptions: {
182
- propD: { statefulParameter: statefulParameter as StatefulParameter<TestConfig, string> },
174
+ getSettings(): ResourceSettings<TestConfig> {
175
+ return {
176
+ id: 'type',
177
+ parameterSettings: {
178
+ propD: { type: 'stateful', definition: statefulParameter },
183
179
  }
184
- });
180
+ };
185
181
  }
186
182
 
187
183
  async refresh(): Promise<Partial<TestConfig> | null> {
@@ -193,16 +189,17 @@ describe('Resource tests for stateful plans', () => {
193
189
  }
194
190
  }
195
191
 
196
- const plan = await resource.plan(
192
+ const controller = new ResourceController(resource);
193
+ const plan = await controller.plan(
197
194
  {
198
- type: 'resource',
195
+ type: 'type',
199
196
  propA: 'propA',
200
197
  propB: 10,
201
198
  propC: 'propC',
202
199
  propD: 'propD'
203
200
  },
204
201
  {
205
- type: 'resource',
202
+ type: 'type',
206
203
  propA: 'propA',
207
204
  propC: 'propC'
208
205
  },
@@ -239,8 +236,8 @@ describe('Resource tests for stateful plans', () => {
239
236
  },
240
237
  ])
241
238
  },
242
- resourceMetadata: {
243
- type: 'resource'
239
+ coreParameters: {
240
+ type: 'type'
244
241
  }
245
242
  })
246
243
  })