codify-plugin-lib 1.0.75 → 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 (76) 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/entities/plugin.d.ts +1 -1
  5. package/dist/entities/plugin.js +5 -5
  6. package/dist/entities/resource-options.d.ts +6 -6
  7. package/dist/entities/resource-options.js +7 -9
  8. package/dist/entities/resource.d.ts +2 -3
  9. package/dist/entities/resource.js +2 -2
  10. package/dist/errors.d.ts +4 -0
  11. package/dist/errors.js +7 -0
  12. package/dist/index.d.ts +10 -10
  13. package/dist/index.js +9 -9
  14. package/dist/messages/handlers.d.ts +1 -1
  15. package/dist/messages/handlers.js +25 -24
  16. package/dist/plan/change-set.d.ts +37 -0
  17. package/dist/plan/change-set.js +146 -0
  18. package/dist/plan/plan-types.d.ts +23 -0
  19. package/dist/plan/plan-types.js +1 -0
  20. package/dist/plan/plan.d.ts +59 -0
  21. package/dist/plan/plan.js +228 -0
  22. package/dist/plugin/plugin.d.ts +17 -0
  23. package/dist/plugin/plugin.js +83 -0
  24. package/dist/resource/config-parser.d.ts +14 -0
  25. package/dist/resource/config-parser.js +48 -0
  26. package/dist/resource/parsed-resource-settings.d.ts +26 -0
  27. package/dist/resource/parsed-resource-settings.js +126 -0
  28. package/dist/resource/resource-controller.d.ts +30 -0
  29. package/dist/resource/resource-controller.js +247 -0
  30. package/dist/resource/resource-settings.d.ts +149 -0
  31. package/dist/resource/resource-settings.js +9 -0
  32. package/dist/resource/resource.d.ts +137 -0
  33. package/dist/resource/resource.js +44 -0
  34. package/dist/resource/stateful-parameter.d.ts +164 -0
  35. package/dist/resource/stateful-parameter.js +94 -0
  36. package/dist/utils/utils.d.ts +19 -3
  37. package/dist/utils/utils.js +52 -3
  38. package/package.json +6 -4
  39. package/src/index.ts +10 -11
  40. package/src/messages/handlers.test.ts +21 -42
  41. package/src/messages/handlers.ts +28 -27
  42. package/src/plan/change-set.test.ts +220 -0
  43. package/src/plan/change-set.ts +225 -0
  44. package/src/plan/plan-types.ts +27 -0
  45. package/src/{entities → plan}/plan.test.ts +35 -29
  46. package/src/plan/plan.ts +353 -0
  47. package/src/{entities → plugin}/plugin.test.ts +14 -13
  48. package/src/{entities → plugin}/plugin.ts +32 -27
  49. package/src/resource/config-parser.ts +77 -0
  50. package/src/{entities/resource-options.test.ts → resource/parsed-resource-settings.test.ts} +8 -7
  51. package/src/resource/parsed-resource-settings.ts +179 -0
  52. package/src/{entities/resource-stateful-mode.test.ts → resource/resource-controller-stateful-mode.test.ts} +36 -39
  53. package/src/{entities/resource.test.ts → resource/resource-controller.test.ts} +116 -176
  54. package/src/resource/resource-controller.ts +340 -0
  55. package/src/resource/resource-settings.test.ts +494 -0
  56. package/src/resource/resource-settings.ts +192 -0
  57. package/src/resource/resource.ts +149 -0
  58. package/src/resource/stateful-parameter.test.ts +93 -0
  59. package/src/resource/stateful-parameter.ts +217 -0
  60. package/src/utils/test-utils.test.ts +87 -0
  61. package/src/utils/utils.test.ts +2 -2
  62. package/src/utils/utils.ts +51 -5
  63. package/tsconfig.json +0 -1
  64. package/vitest.config.ts +10 -0
  65. package/src/entities/change-set.test.ts +0 -155
  66. package/src/entities/change-set.ts +0 -244
  67. package/src/entities/plan-types.ts +0 -44
  68. package/src/entities/plan.ts +0 -178
  69. package/src/entities/resource-options.ts +0 -156
  70. package/src/entities/resource-parameters.test.ts +0 -604
  71. package/src/entities/resource-types.ts +0 -31
  72. package/src/entities/resource.ts +0 -471
  73. package/src/entities/stateful-parameter.test.ts +0 -114
  74. package/src/entities/stateful-parameter.ts +0 -92
  75. package/src/entities/transform-parameter.ts +0 -13
  76. /package/src/{entities/errors.ts → errors.ts} +0 -0
@@ -0,0 +1,228 @@
1
+ import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
2
+ import { v4 as uuidV4 } from 'uuid';
3
+ import { ChangeSet } from './change-set.js';
4
+ /**
5
+ * A plan represents a set of actions that after taken will turn the current resource into the desired one.
6
+ * A plan consists of list of parameter level changes (ADD, REMOVE, MODIFY or NO-OP) as well as a resource level
7
+ * operation (CREATE, DESTROY, MODIFY, RE-CREATE, NO-OP).
8
+ */
9
+ export class Plan {
10
+ id;
11
+ changeSet;
12
+ coreParameters;
13
+ constructor(id, changeSet, resourceMetadata) {
14
+ this.id = id;
15
+ this.changeSet = changeSet;
16
+ this.coreParameters = resourceMetadata;
17
+ }
18
+ /**
19
+ * The desired config that a plan will achieve after executing all the actions.
20
+ */
21
+ get desiredConfig() {
22
+ if (this.changeSet.operation === ResourceOperation.DESTROY) {
23
+ return null;
24
+ }
25
+ return {
26
+ ...this.coreParameters,
27
+ ...this.changeSet.desiredParameters,
28
+ };
29
+ }
30
+ /**
31
+ * The current config that the plan is changing.
32
+ */
33
+ get currentConfig() {
34
+ if (this.changeSet.operation === ResourceOperation.CREATE) {
35
+ return null;
36
+ }
37
+ return {
38
+ ...this.coreParameters,
39
+ ...this.changeSet.currentParameters,
40
+ };
41
+ }
42
+ /**
43
+ * When multiples of the same resource are allowed, this matching function will match a given config with one of the
44
+ * existing configs on the system. For example if there are multiple versions of Android Studios installed, we can use
45
+ * the application name and location to match it to our desired configs name and location.
46
+ *
47
+ * @param params
48
+ * @private
49
+ */
50
+ static matchCurrentParameters(params) {
51
+ const { desiredParameters, currentParametersArray, stateParameters, settings, statefulMode } = params;
52
+ if (!settings.allowMultiple) {
53
+ return currentParametersArray?.[0] ?? null;
54
+ }
55
+ if (!currentParametersArray) {
56
+ return null;
57
+ }
58
+ if (statefulMode) {
59
+ return stateParameters
60
+ ? settings.allowMultiple.matcher(stateParameters, currentParametersArray)
61
+ : null;
62
+ }
63
+ return settings.allowMultiple.matcher(desiredParameters, currentParametersArray);
64
+ }
65
+ /**
66
+ * The type (id) of the resource
67
+ *
68
+ * @return string
69
+ */
70
+ getResourceType() {
71
+ return this.coreParameters.type;
72
+ }
73
+ static calculate(params) {
74
+ const { desiredParameters, currentParametersArray, stateParameters, coreParameters, settings, statefulMode } = params;
75
+ const currentParameters = Plan.matchCurrentParameters({
76
+ desiredParameters,
77
+ currentParametersArray,
78
+ stateParameters,
79
+ settings,
80
+ statefulMode
81
+ });
82
+ const filteredCurrentParameters = Plan.filterCurrentParams({
83
+ desiredParameters,
84
+ currentParameters,
85
+ stateParameters,
86
+ settings,
87
+ statefulMode
88
+ });
89
+ // Empty
90
+ if (!filteredCurrentParameters && !desiredParameters) {
91
+ return new Plan(uuidV4(), ChangeSet.empty(), coreParameters);
92
+ }
93
+ // CREATE
94
+ if (!filteredCurrentParameters && desiredParameters) {
95
+ return new Plan(uuidV4(), ChangeSet.create(desiredParameters), coreParameters);
96
+ }
97
+ // DESTROY
98
+ if (filteredCurrentParameters && !desiredParameters) {
99
+ return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters), coreParameters);
100
+ }
101
+ // NO-OP, MODIFY or RE-CREATE
102
+ const changeSet = ChangeSet.calculateModification(desiredParameters, filteredCurrentParameters, settings.parameterSettings);
103
+ return new Plan(uuidV4(), changeSet, coreParameters);
104
+ }
105
+ /**
106
+ * Only keep relevant params for the plan. We don't want to change settings that were not already
107
+ * defined.
108
+ *
109
+ * 1. In stateless mode, filter current by desired. We only want to know about settings that the user has specified
110
+ * 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
111
+ * or wants to set. If a parameter is not specified then it's not managed by Codify.
112
+ */
113
+ static filterCurrentParams(params) {
114
+ const { desiredParameters: desired, currentParameters: current, stateParameters: state, settings, statefulMode } = params;
115
+ if (!current) {
116
+ return null;
117
+ }
118
+ const filteredCurrent = filterCurrent();
119
+ if (!filteredCurrent) {
120
+ return null;
121
+ }
122
+ // For stateful mode, we're done after filtering by the keys of desired + state. Stateless mode
123
+ // requires additional filtering for stateful parameter arrays and objects.
124
+ if (statefulMode) {
125
+ return filteredCurrent;
126
+ }
127
+ // TODO: Add object handling here in addition to arrays in the future
128
+ const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
129
+ .filter(([k, v]) => isArrayStatefulParameter(k, v))
130
+ .map(([k, v]) => [k, filterArrayStatefulParameter(k, v)]));
131
+ return { ...filteredCurrent, ...arrayStatefulParameters };
132
+ function filterCurrent() {
133
+ if (!current) {
134
+ return null;
135
+ }
136
+ if (statefulMode) {
137
+ const keys = new Set([...Object.keys(state ?? {}), ...Object.keys(desired ?? {})]);
138
+ return Object.fromEntries(Object.entries(current)
139
+ .filter(([k]) => keys.has(k)));
140
+ }
141
+ // Stateless mode
142
+ const keys = new Set(Object.keys(desired ?? {}));
143
+ return Object.fromEntries(Object.entries(current)
144
+ .filter(([k]) => keys.has(k)));
145
+ }
146
+ function isArrayStatefulParameter(k, v) {
147
+ return settings.parameterSettings?.[k]?.type === 'stateful'
148
+ && settings.parameterSettings[k].definition.getSettings().type === 'array'
149
+ && Array.isArray(v);
150
+ }
151
+ function filterArrayStatefulParameter(k, v) {
152
+ const desiredArray = desired[k];
153
+ const matcher = settings.parameterSettings[k]
154
+ .definition
155
+ .getSettings()
156
+ .isElementEqual;
157
+ return v.filter((cv) => desiredArray.find((dv) => (matcher ?? ((a, b) => a === b))(dv, cv)));
158
+ }
159
+ }
160
+ // TODO: This needs to be revisited. I don't think this is valid anymore.
161
+ // 1. For all scenarios, there shouldn't be an apply without a plan beforehand
162
+ // 2. Even if there was (maybe for testing reasons), the plan values should not be adjusted
163
+ static fromResponse(data, defaultValues) {
164
+ if (!data) {
165
+ throw new Error('Data is empty');
166
+ }
167
+ addDefaultValues();
168
+ return new Plan(uuidV4(), new ChangeSet(data.operation, data.parameters), {
169
+ type: data.resourceType,
170
+ name: data.resourceName,
171
+ });
172
+ function addDefaultValues() {
173
+ Object.entries(defaultValues ?? {})
174
+ .forEach(([key, defaultValue]) => {
175
+ const configValueExists = data
176
+ .parameters
177
+ .some((p) => p.name === key);
178
+ // Only set default values if the value does not exist in the config
179
+ if (configValueExists) {
180
+ return;
181
+ }
182
+ switch (data.operation) {
183
+ case ResourceOperation.CREATE: {
184
+ data.parameters.push({
185
+ name: key,
186
+ operation: ParameterOperation.ADD,
187
+ previousValue: null,
188
+ newValue: defaultValue,
189
+ });
190
+ break;
191
+ }
192
+ case ResourceOperation.DESTROY: {
193
+ data.parameters.push({
194
+ name: key,
195
+ operation: ParameterOperation.REMOVE,
196
+ previousValue: defaultValue,
197
+ newValue: null,
198
+ });
199
+ break;
200
+ }
201
+ case ResourceOperation.MODIFY:
202
+ case ResourceOperation.RECREATE:
203
+ case ResourceOperation.NOOP: {
204
+ data.parameters.push({
205
+ name: key,
206
+ operation: ParameterOperation.NOOP,
207
+ previousValue: defaultValue,
208
+ newValue: defaultValue,
209
+ });
210
+ break;
211
+ }
212
+ }
213
+ });
214
+ }
215
+ }
216
+ /**
217
+ * Convert the plan to a JSON response object
218
+ */
219
+ toResponse() {
220
+ return {
221
+ planId: this.id,
222
+ operation: this.changeSet.operation,
223
+ resourceName: this.coreParameters.name,
224
+ resourceType: this.coreParameters.type,
225
+ parameters: this.changeSet.parameterChanges,
226
+ };
227
+ }
228
+ }
@@ -0,0 +1,17 @@
1
+ import { ApplyRequestData, InitializeResponseData, PlanRequestData, PlanResponseData, ResourceConfig, ValidateRequestData, ValidateResponseData } from 'codify-schemas';
2
+ import { Plan } from '../plan/plan.js';
3
+ import { Resource } from '../resource/resource.js';
4
+ import { ResourceController } from '../resource/resource-controller.js';
5
+ export declare class Plugin {
6
+ name: string;
7
+ resourceControllers: Map<string, ResourceController<ResourceConfig>>;
8
+ planStorage: Map<string, Plan<any>>;
9
+ constructor(name: string, resourceControllers: Map<string, ResourceController<ResourceConfig>>);
10
+ static create(name: string, resources: Resource<any>[]): Plugin;
11
+ initialize(): Promise<InitializeResponseData>;
12
+ validate(data: ValidateRequestData): Promise<ValidateResponseData>;
13
+ plan(data: PlanRequestData): Promise<PlanResponseData>;
14
+ apply(data: ApplyRequestData): Promise<void>;
15
+ private resolvePlan;
16
+ protected crossValidateResources(configs: ResourceConfig[]): Promise<void>;
17
+ }
@@ -0,0 +1,83 @@
1
+ import { Plan } from '../plan/plan.js';
2
+ import { ResourceController } from '../resource/resource-controller.js';
3
+ import { splitUserConfig } from '../utils/utils.js';
4
+ export class Plugin {
5
+ name;
6
+ resourceControllers;
7
+ planStorage;
8
+ constructor(name, resourceControllers) {
9
+ this.name = name;
10
+ this.resourceControllers = resourceControllers;
11
+ this.planStorage = new Map();
12
+ }
13
+ static create(name, resources) {
14
+ const controllers = resources
15
+ .map((resource) => new ResourceController(resource));
16
+ const controllersMap = new Map(controllers.map((r) => [r.typeId, r]));
17
+ return new Plugin(name, controllersMap);
18
+ }
19
+ async initialize() {
20
+ for (const controller of this.resourceControllers.values()) {
21
+ await controller.initialize();
22
+ }
23
+ return {
24
+ resourceDefinitions: [...this.resourceControllers.values()]
25
+ .map((r) => ({
26
+ dependencies: r.dependencies,
27
+ type: r.typeId,
28
+ }))
29
+ };
30
+ }
31
+ async validate(data) {
32
+ const validationResults = [];
33
+ for (const config of data.configs) {
34
+ if (!this.resourceControllers.has(config.type)) {
35
+ throw new Error(`Resource type not found: ${config.type}`);
36
+ }
37
+ const { parameters, coreParameters } = splitUserConfig(config);
38
+ const validation = await this.resourceControllers
39
+ .get(config.type)
40
+ .validate(parameters, coreParameters);
41
+ validationResults.push(validation);
42
+ }
43
+ await this.crossValidateResources(data.configs);
44
+ return {
45
+ resourceValidations: validationResults
46
+ };
47
+ }
48
+ async plan(data) {
49
+ const type = data.desired?.type ?? data.state?.type;
50
+ if (!type || !this.resourceControllers.has(type)) {
51
+ throw new Error(`Resource type not found: ${type}`);
52
+ }
53
+ const plan = await this.resourceControllers.get(type).plan(data.desired ?? null, data.state ?? null, data.isStateful);
54
+ this.planStorage.set(plan.id, plan);
55
+ return plan.toResponse();
56
+ }
57
+ async apply(data) {
58
+ if (!data.planId && !data.plan) {
59
+ throw new Error('For applies either plan or planId must be supplied');
60
+ }
61
+ const plan = this.resolvePlan(data);
62
+ const resource = this.resourceControllers.get(plan.getResourceType());
63
+ if (!resource) {
64
+ throw new Error('Malformed plan with resource that cannot be found');
65
+ }
66
+ await resource.apply(plan);
67
+ }
68
+ resolvePlan(data) {
69
+ const { plan: planRequest, planId } = data;
70
+ if (planId) {
71
+ if (!this.planStorage.has(planId)) {
72
+ throw new Error(`Plan with id: ${planId} was not found`);
73
+ }
74
+ return this.planStorage.get(planId);
75
+ }
76
+ if (!planRequest?.resourceType || !this.resourceControllers.has(planRequest.resourceType)) {
77
+ throw new Error('Malformed plan. Resource type must be supplied or resource type was not found');
78
+ }
79
+ const resource = this.resourceControllers.get(planRequest.resourceType);
80
+ return Plan.fromResponse(planRequest, resource.parsedSettings.defaultValues);
81
+ }
82
+ async crossValidateResources(configs) { }
83
+ }
@@ -0,0 +1,14 @@
1
+ import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
2
+ import { StatefulParameter } from './stateful-parameter.js';
3
+ export declare class ConfigParser<T extends StringIndexedObject> {
4
+ private readonly desiredConfig;
5
+ private readonly stateConfig;
6
+ private statefulParametersMap;
7
+ constructor(desiredConfig: Partial<T> & ResourceConfig | null, stateConfig: Partial<T> & ResourceConfig | null, statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>);
8
+ get coreParameters(): ResourceConfig;
9
+ get desiredParameters(): Partial<T> | null;
10
+ get stateParameters(): Partial<T> | null;
11
+ get allParameters(): Partial<T>;
12
+ get allNonStatefulParameters(): Partial<T>;
13
+ get allStatefulParameters(): Partial<T>;
14
+ }
@@ -0,0 +1,48 @@
1
+ import { splitUserConfig } from '../utils/utils.js';
2
+ export class ConfigParser {
3
+ desiredConfig;
4
+ stateConfig;
5
+ statefulParametersMap;
6
+ constructor(desiredConfig, stateConfig, statefulParameters) {
7
+ this.desiredConfig = desiredConfig;
8
+ this.stateConfig = stateConfig;
9
+ this.statefulParametersMap = statefulParameters;
10
+ }
11
+ get coreParameters() {
12
+ const desiredCoreParameters = this.desiredConfig ? splitUserConfig(this.desiredConfig).coreParameters : undefined;
13
+ const currentCoreParameters = this.stateConfig ? splitUserConfig(this.stateConfig).coreParameters : undefined;
14
+ if (!desiredCoreParameters && !currentCoreParameters) {
15
+ throw new Error(`Unable to parse resource core parameters from:
16
+
17
+ Desired: ${JSON.stringify(this.desiredConfig, null, 2)}
18
+
19
+ Current: ${JSON.stringify(this.stateConfig, null, 2)}`);
20
+ }
21
+ return desiredCoreParameters ?? currentCoreParameters;
22
+ }
23
+ get desiredParameters() {
24
+ if (!this.desiredConfig) {
25
+ return null;
26
+ }
27
+ const { parameters } = splitUserConfig(this.desiredConfig);
28
+ return parameters;
29
+ }
30
+ get stateParameters() {
31
+ if (!this.stateConfig) {
32
+ return null;
33
+ }
34
+ const { parameters } = splitUserConfig(this.stateConfig);
35
+ return parameters;
36
+ }
37
+ get allParameters() {
38
+ return { ...this.desiredParameters, ...this.stateParameters };
39
+ }
40
+ get allNonStatefulParameters() {
41
+ const { allParameters, statefulParametersMap, } = this;
42
+ return Object.fromEntries(Object.entries(allParameters).filter(([key]) => !statefulParametersMap.has(key)));
43
+ }
44
+ get allStatefulParameters() {
45
+ const { allParameters, statefulParametersMap } = this;
46
+ return Object.fromEntries(Object.entries(allParameters).filter(([key]) => statefulParametersMap.has(key)));
47
+ }
48
+ }
@@ -0,0 +1,26 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { ParameterSetting, ResourceSettings } from './resource-settings.js';
3
+ import { StatefulParameter as StatefulParameterImpl } from './stateful-parameter.js';
4
+ export declare class ParsedResourceSettings<T extends StringIndexedObject> implements ResourceSettings<T> {
5
+ private cache;
6
+ id: string;
7
+ schema?: unknown;
8
+ allowMultiple?: {
9
+ matcher: (desired: Partial<T>, current: Partial<T>[]) => Partial<T>;
10
+ } | undefined;
11
+ removeStatefulParametersBeforeDestroy?: boolean | undefined;
12
+ dependencies?: string[] | undefined;
13
+ inputTransformation?: ((desired: Partial<T>) => unknown) | undefined;
14
+ private settings;
15
+ constructor(settings: ResourceSettings<T>);
16
+ get typeId(): string;
17
+ get statefulParameters(): Map<keyof T, StatefulParameterImpl<T, T[keyof T]>>;
18
+ get parameterSettings(): Record<keyof T, ParameterSetting>;
19
+ get defaultValues(): Partial<Record<keyof T, unknown>>;
20
+ get inputTransformations(): Partial<Record<keyof T, (a: unknown) => unknown>>;
21
+ get statefulParameterOrder(): Map<keyof T, number>;
22
+ private validateSettings;
23
+ private validateParameterEqualsFn;
24
+ private resolveEqualsFn;
25
+ private getFromCacheOrCreate;
26
+ }
@@ -0,0 +1,126 @@
1
+ import { areArraysEqual } from '../utils/utils.js';
2
+ import { ParameterEqualsDefaults } from './resource-settings.js';
3
+ export class ParsedResourceSettings {
4
+ cache = new Map();
5
+ id;
6
+ schema;
7
+ allowMultiple;
8
+ removeStatefulParametersBeforeDestroy;
9
+ dependencies;
10
+ inputTransformation;
11
+ settings;
12
+ constructor(settings) {
13
+ this.settings = settings;
14
+ this.id = settings.id;
15
+ this.schema = settings.schema;
16
+ this.allowMultiple = settings.allowMultiple;
17
+ this.removeStatefulParametersBeforeDestroy = settings.removeStatefulParametersBeforeDestroy;
18
+ this.dependencies = settings.dependencies;
19
+ this.inputTransformation = settings.inputTransformation;
20
+ this.validateSettings();
21
+ }
22
+ get typeId() {
23
+ return this.id;
24
+ }
25
+ get statefulParameters() {
26
+ return this.getFromCacheOrCreate('statefulParameters', () => {
27
+ const statefulParameters = Object.entries(this.settings.parameterSettings ?? {})
28
+ .filter(([, p]) => p?.type === 'stateful')
29
+ .map(([k, v]) => [k, v.definition]);
30
+ return new Map(statefulParameters);
31
+ });
32
+ }
33
+ get parameterSettings() {
34
+ return this.getFromCacheOrCreate('parameterSetting', () => {
35
+ const settings = Object.entries(this.settings.parameterSettings ?? {})
36
+ .map(([k, v]) => [k, v])
37
+ .map(([k, v]) => {
38
+ v.isEqual = this.resolveEqualsFn(v, k);
39
+ return [k, v];
40
+ });
41
+ return Object.fromEntries(settings);
42
+ });
43
+ }
44
+ get defaultValues() {
45
+ return this.getFromCacheOrCreate('defaultValues', () => {
46
+ if (!this.settings.parameterSettings) {
47
+ return {};
48
+ }
49
+ const defaultValues = Object.fromEntries(Object.entries(this.settings.parameterSettings)
50
+ .filter(([, v]) => v.default !== undefined)
51
+ .map(([k, v]) => [k, v.default]));
52
+ const statefulParameterDefaultValues = Object.fromEntries(Object.entries(this.settings.parameterSettings)
53
+ .filter(([, v]) => v?.type === 'stateful')
54
+ .filter(([, v]) => v.definition.getSettings().default !== undefined)
55
+ .map(([k, v]) => [k, v.definition.getSettings().default]));
56
+ return { ...defaultValues, ...statefulParameterDefaultValues };
57
+ });
58
+ }
59
+ get inputTransformations() {
60
+ return this.getFromCacheOrCreate('inputTransformations', () => {
61
+ if (!this.settings.parameterSettings) {
62
+ return {};
63
+ }
64
+ return Object.fromEntries(Object.entries(this.settings.parameterSettings)
65
+ .filter(([, v]) => v.inputTransformation !== undefined)
66
+ .map(([k, v]) => [k, v.inputTransformation]));
67
+ });
68
+ }
69
+ get statefulParameterOrder() {
70
+ return this.getFromCacheOrCreate('stateParameterOrder', () => {
71
+ const entries = Object.entries(this.settings.parameterSettings ?? {})
72
+ .filter(([, v]) => v?.type === 'stateful')
73
+ .map(([k, v]) => [k, v]);
74
+ const orderedEntries = entries.filter(([, v]) => v.order !== undefined);
75
+ const unorderedEntries = entries.filter(([, v]) => v.order === undefined);
76
+ orderedEntries.sort((a, b) => a[1].order - b[1].order);
77
+ const resultArray = [
78
+ ...orderedEntries.map(([k]) => k),
79
+ ...unorderedEntries.map(([k]) => k)
80
+ ];
81
+ return new Map(resultArray.map((key, idx) => [key, idx]));
82
+ });
83
+ }
84
+ validateSettings() {
85
+ // validate parameter settings
86
+ if (this.settings.parameterSettings) {
87
+ for (const [k, v] of Object.entries(this.settings.parameterSettings)) {
88
+ if (!v) {
89
+ throw new Error(`Resource: ${this.id}. Parameter setting ${k} was left undefined`);
90
+ }
91
+ this.validateParameterEqualsFn(v, k);
92
+ }
93
+ }
94
+ if (this.allowMultiple
95
+ && Object.values(this.parameterSettings).some((v) => v.type === 'stateful')) {
96
+ throw new Error(`Resource: ${this.id}. Stateful parameters are not allowed if multiples of a resource exist`);
97
+ }
98
+ }
99
+ validateParameterEqualsFn(parameter, key) {
100
+ if (parameter.type === 'stateful') {
101
+ const nestedSettings = parameter.definition.getSettings();
102
+ if (nestedSettings.type === 'stateful') {
103
+ throw new Error(`Nested stateful parameters are not allowed for ${key}`);
104
+ }
105
+ this.validateParameterEqualsFn(nestedSettings, key);
106
+ }
107
+ // The rest of the types have defaults set already
108
+ }
109
+ resolveEqualsFn(parameter, key) {
110
+ if (parameter.type === 'array') {
111
+ return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter);
112
+ }
113
+ if (parameter.type === 'stateful') {
114
+ return this.resolveEqualsFn(parameter.definition.getSettings(), key);
115
+ }
116
+ return parameter.isEqual ?? ParameterEqualsDefaults[parameter.type] ?? (((a, b) => a === b));
117
+ }
118
+ getFromCacheOrCreate(key, create) {
119
+ if (this.cache.has(key)) {
120
+ return this.cache.get(key);
121
+ }
122
+ const result = create();
123
+ this.cache.set(key, result);
124
+ return result;
125
+ }
126
+ }
@@ -0,0 +1,30 @@
1
+ import { Ajv, ValidateFunction } from 'ajv';
2
+ import { ResourceConfig, StringIndexedObject, ValidateResponseData } from 'codify-schemas';
3
+ import { Plan } from '../plan/plan.js';
4
+ import { ParsedResourceSettings } from './parsed-resource-settings.js';
5
+ import { Resource } from './resource.js';
6
+ import { ResourceSettings } from './resource-settings.js';
7
+ export declare class ResourceController<T extends StringIndexedObject> {
8
+ readonly resource: Resource<T>;
9
+ readonly settings: ResourceSettings<T>;
10
+ readonly parsedSettings: ParsedResourceSettings<T>;
11
+ readonly typeId: string;
12
+ readonly dependencies: string[];
13
+ protected ajv?: Ajv;
14
+ protected schemaValidator?: ValidateFunction;
15
+ constructor(resource: Resource<T>);
16
+ initialize(): Promise<void>;
17
+ validate(parameters: Partial<T>, resourceMetaData: ResourceConfig): Promise<ValidateResponseData['resourceValidations'][0]>;
18
+ plan(desiredConfig: Partial<T> & ResourceConfig | null, stateConfig?: Partial<T> & ResourceConfig | null, statefulMode?: boolean): Promise<Plan<T>>;
19
+ apply(plan: Plan<T>): Promise<void>;
20
+ private applyCreate;
21
+ private applyModify;
22
+ private applyDestroy;
23
+ private validateRefreshResults;
24
+ private applyTransformParameters;
25
+ private addDefaultValues;
26
+ private refreshNonStatefulParameters;
27
+ private refreshStatefulParameters;
28
+ private validatePlanInputs;
29
+ private getSortedStatefulParameterChanges;
30
+ }