codify-plugin-lib 1.0.49 → 1.0.51

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,5 +1,5 @@
1
1
  import { ParameterOperation, ResourceOperation, StringIndexedObject } from 'codify-schemas';
2
- import { ParameterConfiguration } from './plan-types.js';
2
+ import { ParameterOptions } from './plan-types.js';
3
3
  export interface ParameterChange<T extends StringIndexedObject> {
4
4
  name: keyof T & string;
5
5
  operation: ParameterOperation;
@@ -14,10 +14,10 @@ export declare class ChangeSet<T extends StringIndexedObject> {
14
14
  get currentParameters(): T;
15
15
  static calculateParameterChangeSet<T extends StringIndexedObject>(desired: T | null, current: T | null, options: {
16
16
  statefulMode: boolean;
17
- parameterConfigurations?: Record<keyof T, ParameterConfiguration>;
17
+ parameterOptions?: Record<keyof T, ParameterOptions>;
18
18
  }): ParameterChange<T>[];
19
19
  static combineResourceOperations(prev: ResourceOperation, next: ResourceOperation): ResourceOperation;
20
- static isSame(desired: unknown, current: unknown, configuration?: ParameterConfiguration): boolean;
20
+ static isSame(desired: unknown, current: unknown, options?: ParameterOptions): boolean;
21
21
  private static calculateStatefulModeChangeSet;
22
22
  private static calculateStatelessModeChangeSet;
23
23
  }
@@ -22,10 +22,10 @@ export class ChangeSet {
22
22
  }
23
23
  static calculateParameterChangeSet(desired, current, options) {
24
24
  if (options.statefulMode) {
25
- return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterConfigurations);
25
+ return ChangeSet.calculateStatefulModeChangeSet(desired, current, options.parameterOptions);
26
26
  }
27
27
  else {
28
- return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterConfigurations);
28
+ return ChangeSet.calculateStatelessModeChangeSet(desired, current, options.parameterOptions);
29
29
  }
30
30
  }
31
31
  static combineResourceOperations(prev, next) {
@@ -40,9 +40,9 @@ export class ChangeSet {
40
40
  const indexNext = orderOfOperations.indexOf(next);
41
41
  return orderOfOperations[Math.max(indexPrev, indexNext)];
42
42
  }
43
- static isSame(desired, current, configuration) {
44
- if (configuration?.isEqual) {
45
- return configuration.isEqual(desired, current);
43
+ static isSame(desired, current, options) {
44
+ if (options?.isEqual) {
45
+ return options.isEqual(desired, current);
46
46
  }
47
47
  if (Array.isArray(desired) && Array.isArray(current)) {
48
48
  const sortedDesired = desired.map((x) => x).sort();
@@ -50,14 +50,14 @@ export class ChangeSet {
50
50
  if (sortedDesired.length !== sortedCurrent.length) {
51
51
  return false;
52
52
  }
53
- if (configuration?.isElementEqual) {
54
- return sortedDesired.every((value, index) => configuration.isElementEqual(value, sortedCurrent[index]));
53
+ if (options?.isElementEqual) {
54
+ return sortedDesired.every((value, index) => options.isElementEqual(value, sortedCurrent[index]));
55
55
  }
56
56
  return JSON.stringify(sortedDesired) === JSON.stringify(sortedCurrent);
57
57
  }
58
58
  return desired === current;
59
59
  }
60
- static calculateStatefulModeChangeSet(desired, current, parameterConfigurations) {
60
+ static calculateStatefulModeChangeSet(desired, current, parameterOptions) {
61
61
  const parameterChangeSet = new Array();
62
62
  const _desired = { ...desired };
63
63
  const _current = { ...current };
@@ -72,7 +72,7 @@ export class ChangeSet {
72
72
  delete _current[k];
73
73
  continue;
74
74
  }
75
- if (!ChangeSet.isSame(_desired[k], _current[k], parameterConfigurations?.[k])) {
75
+ if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
76
76
  parameterChangeSet.push({
77
77
  name: k,
78
78
  previousValue: v,
@@ -105,7 +105,7 @@ export class ChangeSet {
105
105
  }
106
106
  return parameterChangeSet;
107
107
  }
108
- static calculateStatelessModeChangeSet(desired, current, parameterConfigurations) {
108
+ static calculateStatelessModeChangeSet(desired, current, parameterOptions) {
109
109
  const parameterChangeSet = new Array();
110
110
  const _desired = { ...desired };
111
111
  const _current = { ...current };
@@ -119,7 +119,7 @@ export class ChangeSet {
119
119
  });
120
120
  continue;
121
121
  }
122
- if (!ChangeSet.isSame(_desired[k], _current[k], parameterConfigurations?.[k])) {
122
+ if (!ChangeSet.isSame(_desired[k], _current[k], parameterOptions?.[k])) {
123
123
  parameterChangeSet.push({
124
124
  name: k,
125
125
  previousValue: _current[k],
@@ -1,11 +1,11 @@
1
1
  import { ResourceOperation } from 'codify-schemas';
2
- export interface ParameterConfiguration {
2
+ export interface ParameterOptions {
3
3
  planOperation?: ResourceOperation.MODIFY | ResourceOperation.RECREATE;
4
4
  isEqual?: (desired: any, current: any) => boolean;
5
5
  isElementEqual?: (desired: any, current: any) => boolean;
6
6
  isStatefulParameter?: boolean;
7
7
  }
8
- export interface PlanConfiguration<T> {
8
+ export interface PlanOptions<T> {
9
9
  statefulMode: boolean;
10
- parameterConfigurations?: Record<keyof T, ParameterConfiguration>;
10
+ parameterOptions?: Record<keyof T, ParameterOptions>;
11
11
  }
@@ -1,12 +1,12 @@
1
1
  import { ChangeSet } from './change-set.js';
2
2
  import { ApplyRequestData, PlanResponseData, ResourceConfig, StringIndexedObject } from 'codify-schemas';
3
- import { PlanConfiguration } from './plan-types.js';
3
+ import { PlanOptions } from './plan-types.js';
4
4
  export declare class Plan<T extends StringIndexedObject> {
5
5
  id: string;
6
6
  changeSet: ChangeSet<T>;
7
7
  resourceMetadata: ResourceConfig;
8
8
  constructor(id: string, changeSet: ChangeSet<T>, resourceMetadata: ResourceConfig);
9
- static create<T extends StringIndexedObject>(desiredParameters: Partial<T> | null, currentParameters: Partial<T> | null, resourceMetadata: ResourceConfig, configuration: PlanConfiguration<T>): Plan<T>;
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
11
  static fromResponse<T extends ResourceConfig>(data: ApplyRequestData['plan'], defaultValues: Partial<Record<keyof T, unknown>>): Plan<T>;
12
12
  get desiredConfig(): T;
@@ -10,12 +10,12 @@ export class Plan {
10
10
  this.changeSet = changeSet;
11
11
  this.resourceMetadata = resourceMetadata;
12
12
  }
13
- static create(desiredParameters, currentParameters, resourceMetadata, configuration) {
14
- const parameterConfigurations = configuration.parameterConfigurations ?? {};
15
- const statefulParameterNames = new Set([...Object.entries(parameterConfigurations)]
13
+ static create(desiredParameters, currentParameters, resourceMetadata, options) {
14
+ const parameterOptions = options.parameterOptions ?? {};
15
+ const statefulParameterNames = new Set([...Object.entries(parameterOptions)]
16
16
  .filter(([k, v]) => v.isStatefulParameter)
17
17
  .map(([k, v]) => k));
18
- const parameterChangeSet = ChangeSet.calculateParameterChangeSet(desiredParameters, currentParameters, { statefulMode: configuration.statefulMode, parameterConfigurations });
18
+ const parameterChangeSet = ChangeSet.calculateParameterChangeSet(desiredParameters, currentParameters, { statefulMode: options.statefulMode, parameterOptions });
19
19
  let resourceOperation;
20
20
  if (!currentParameters && desiredParameters) {
21
21
  resourceOperation = ResourceOperation.CREATE;
@@ -31,8 +31,8 @@ export class Plan {
31
31
  if (statefulParameterNames.has(curr.name)) {
32
32
  newOperation = ResourceOperation.MODIFY;
33
33
  }
34
- else if (parameterConfigurations[curr.name]?.planOperation) {
35
- newOperation = parameterConfigurations[curr.name].planOperation;
34
+ else if (parameterOptions[curr.name]?.planOperation) {
35
+ newOperation = parameterOptions[curr.name].planOperation;
36
36
  }
37
37
  else {
38
38
  newOperation = ResourceOperation.RECREATE;
@@ -26,7 +26,7 @@ export class Plugin {
26
26
  throw new Error(`Resource type not found: ${config.type}`);
27
27
  }
28
28
  const { parameters } = splitUserConfig(config);
29
- const validateResult = await this.resources.get(config.type).validate(parameters);
29
+ const validateResult = await this.resources.get(config.type).validateResource(parameters);
30
30
  validationResults.push({
31
31
  ...validateResult,
32
32
  resourceType: config.type,
@@ -0,0 +1,31 @@
1
+ import { StringIndexedObject } from 'codify-schemas';
2
+ import { StatefulParameter } from './stateful-parameter.js';
3
+ import { TransformParameter } from './transform-parameter.js';
4
+ import { ResourceParameterOptions } from './resource-types.js';
5
+ import { ParameterOptions } from './plan-types.js';
6
+ export interface ResourceOptions<T extends StringIndexedObject> {
7
+ type: string;
8
+ schema?: unknown;
9
+ callStatefulParameterRemoveOnDestroy?: boolean;
10
+ dependencies?: string[];
11
+ parameterOptions?: Partial<Record<keyof T, ResourceParameterOptions | ResourceStatefulParameterOptions<T> | ResourceTransformParameterOptions<T>>>;
12
+ }
13
+ export interface ResourceStatefulParameterOptions<T extends StringIndexedObject> {
14
+ statefulParameter: StatefulParameter<T, T[keyof T]>;
15
+ order?: number;
16
+ }
17
+ export interface ResourceTransformParameterOptions<T extends StringIndexedObject> {
18
+ transformParameter: TransformParameter<T>;
19
+ order?: number;
20
+ }
21
+ export declare class ResourceOptionsParser<T extends StringIndexedObject> {
22
+ private options;
23
+ constructor(options: ResourceOptions<T>);
24
+ get statefulParameters(): Map<keyof T, StatefulParameter<T, T[keyof T]>>;
25
+ get transformParameters(): Map<keyof T, TransformParameter<T>>;
26
+ get resourceParameters(): Map<keyof T, ResourceParameterOptions>;
27
+ get changeSetParameterOptions(): Record<keyof T, ParameterOptions>;
28
+ get defaultValues(): Partial<Record<keyof T, unknown>>;
29
+ get statefulParameterOrder(): Map<keyof T, number>;
30
+ get transformParameterOrder(): Map<keyof T, number>;
31
+ }
@@ -0,0 +1,78 @@
1
+ export class ResourceOptionsParser {
2
+ options;
3
+ constructor(options) {
4
+ this.options = options;
5
+ }
6
+ get statefulParameters() {
7
+ const statefulParameters = Object.entries(this.options.parameterOptions ?? {})
8
+ .filter(([, p]) => p?.hasOwnProperty('statefulParameter'))
9
+ .map(([k, v]) => [k, v])
10
+ .map(([k, v]) => [k, v.statefulParameter]);
11
+ return new Map(statefulParameters);
12
+ }
13
+ get transformParameters() {
14
+ const transformParameters = Object.entries(this.options.parameterOptions ?? {})
15
+ .filter(([, p]) => p?.hasOwnProperty('transformParameter'))
16
+ .map(([k, v]) => [k, v])
17
+ .map(([k, v]) => [k, v.transformParameter]);
18
+ return new Map(transformParameters);
19
+ }
20
+ get resourceParameters() {
21
+ const resourceParameters = Object.entries(this.options.parameterOptions ?? {})
22
+ .filter(([, p]) => !(p?.hasOwnProperty('statefulParameter') || p?.hasOwnProperty('transformParameter')))
23
+ .map(([k, v]) => [k, v]);
24
+ return new Map(resourceParameters);
25
+ }
26
+ get changeSetParameterOptions() {
27
+ const resourceParameters = Object.fromEntries([...this.resourceParameters.entries()]
28
+ .map(([name, value]) => ([name, { ...value, isStatefulParameter: false }])));
29
+ const statefulParameters = [...this.statefulParameters.entries()]
30
+ ?.reduce((obj, sp) => {
31
+ return {
32
+ ...obj,
33
+ [sp[0]]: {
34
+ ...sp[1].options,
35
+ isStatefulParameter: true,
36
+ }
37
+ };
38
+ }, {});
39
+ return {
40
+ ...resourceParameters,
41
+ ...statefulParameters,
42
+ };
43
+ }
44
+ get defaultValues() {
45
+ if (!this.options.parameterOptions) {
46
+ return {};
47
+ }
48
+ return Object.fromEntries([...this.resourceParameters.entries()]
49
+ .filter(([, rp]) => rp.defaultValue !== undefined)
50
+ .map(([name, rp]) => [name, rp.defaultValue]));
51
+ }
52
+ get statefulParameterOrder() {
53
+ const entries = Object.entries(this.options.parameterOptions ?? {})
54
+ .filter(([, v]) => v?.hasOwnProperty('statefulParameter'))
55
+ .map(([k, v]) => [k, v]);
56
+ const orderedEntries = entries.filter(([, v]) => v.order !== undefined);
57
+ const unorderedEntries = entries.filter(([, v]) => v.order === undefined);
58
+ orderedEntries.sort((a, b) => a[1].order - b[1].order);
59
+ const resultArray = [
60
+ ...orderedEntries.map(([k]) => k),
61
+ ...unorderedEntries.map(([k]) => k)
62
+ ];
63
+ return new Map(resultArray.map((key, idx) => [key, idx]));
64
+ }
65
+ get transformParameterOrder() {
66
+ const entries = Object.entries(this.options.parameterOptions ?? {})
67
+ .filter(([, v]) => v?.hasOwnProperty('transformParameter'))
68
+ .map(([k, v]) => [k, v]);
69
+ const orderedEntries = entries.filter(([, v]) => v.order !== undefined);
70
+ const unorderedEntries = entries.filter(([, v]) => v.order === undefined);
71
+ orderedEntries.sort((a, b) => a[1].order - b[1].order);
72
+ const resultArray = [
73
+ ...orderedEntries.map(([k]) => k),
74
+ ...unorderedEntries.map(([k]) => k)
75
+ ];
76
+ return new Map(resultArray.map((key, idx) => [key, idx]));
77
+ }
78
+ }
@@ -1,18 +1,10 @@
1
- import { StatefulParameter } from './stateful-parameter.js';
2
- import { ResourceOperation, StringIndexedObject } from 'codify-schemas';
1
+ import { ResourceOperation } from 'codify-schemas';
3
2
  export type ErrorMessage = string;
4
- export interface ResourceParameterConfiguration {
3
+ export interface ResourceParameterOptions {
5
4
  planOperation?: ResourceOperation.MODIFY | ResourceOperation.RECREATE;
6
5
  isEqual?: (desired: any, current: any) => boolean;
7
6
  defaultValue?: unknown;
8
7
  }
9
- export interface ResourceConfiguration<T extends StringIndexedObject> {
10
- type: string;
11
- callStatefulParameterRemoveOnDestroy?: boolean;
12
- dependencies?: string[];
13
- statefulParameters?: Array<StatefulParameter<T, T[keyof T]>>;
14
- parameterConfigurations?: Partial<Record<keyof T, ResourceParameterConfiguration>>;
15
- }
16
8
  export interface ResourceDefinition {
17
9
  [x: string]: {
18
10
  type: string;
@@ -1,28 +1,39 @@
1
1
  import { ResourceConfig, StringIndexedObject } from 'codify-schemas';
2
2
  import { Plan } from './plan.js';
3
3
  import { StatefulParameter } from './stateful-parameter.js';
4
- import { ResourceConfiguration, ValidationResult } from './resource-types.js';
5
- import { ParameterConfiguration } from './plan-types.js';
4
+ import { ResourceParameterOptions, ValidationResult } from './resource-types.js';
5
+ import { ParameterOptions } from './plan-types.js';
6
+ import { TransformParameter } from './transform-parameter.js';
7
+ import { ResourceOptions } from './resource-options.js';
8
+ import Ajv from 'ajv';
9
+ import { ValidateFunction } from 'ajv/dist/2020.js';
6
10
  export declare abstract class Resource<T extends StringIndexedObject> {
7
11
  readonly typeId: string;
8
12
  readonly statefulParameters: Map<keyof T, StatefulParameter<T, T[keyof T]>>;
13
+ readonly transformParameters: Map<keyof T, TransformParameter<T>>;
14
+ readonly resourceParameters: Map<keyof T, ResourceParameterOptions>;
15
+ readonly statefulParameterOrder: Map<keyof T, number>;
16
+ readonly transformParameterOrder: Map<keyof T, number>;
9
17
  readonly dependencies: string[];
10
- readonly parameterConfigurations: Record<keyof T, ParameterConfiguration>;
11
- readonly configuration: ResourceConfiguration<T>;
18
+ readonly parameterOptions: Record<keyof T, ParameterOptions>;
19
+ readonly options: ResourceOptions<T>;
12
20
  readonly defaultValues: Partial<Record<keyof T, unknown>>;
13
- protected constructor(configuration: ResourceConfiguration<T>);
21
+ protected ajv?: Ajv.default;
22
+ protected schemaValidator?: ValidateFunction;
23
+ protected constructor(options: ResourceOptions<T>);
14
24
  onInitialize(): Promise<void>;
25
+ validateResource(parameters: unknown): Promise<ValidationResult>;
15
26
  plan(desiredConfig: Partial<T> & ResourceConfig): Promise<Plan<T>>;
16
27
  apply(plan: Plan<T>): Promise<void>;
17
28
  private _applyCreate;
18
29
  private _applyModify;
19
30
  private _applyDestroy;
20
- private initializeParameterConfigurations;
21
- private initializeDefaultValues;
22
- private validateResourceConfiguration;
23
31
  private validateRefreshResults;
32
+ private applyTransformParameters;
24
33
  private addDefaultValues;
25
- abstract validate(parameters: unknown): Promise<ValidationResult>;
34
+ private refreshResourceParameters;
35
+ private refreshStatefulParameters;
36
+ validate(parameters: unknown): Promise<ValidationResult>;
26
37
  abstract refresh(keys: Map<keyof T, T[keyof T]>): Promise<Partial<T> | null>;
27
38
  abstract applyCreate(plan: Plan<T>): Promise<void>;
28
39
  applyModify(parameterName: keyof T, newValue: unknown, previousValue: unknown, allowDeletes: boolean, plan: Plan<T>): Promise<void>;
@@ -1,59 +1,69 @@
1
1
  import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
2
2
  import { Plan } from './plan.js';
3
3
  import { setsEqual, splitUserConfig } from '../utils/utils.js';
4
+ import { ResourceOptionsParser } from './resource-options.js';
5
+ import Ajv2020 from 'ajv/dist/2020.js';
4
6
  export class Resource {
5
7
  typeId;
6
8
  statefulParameters;
9
+ transformParameters;
10
+ resourceParameters;
11
+ statefulParameterOrder;
12
+ transformParameterOrder;
7
13
  dependencies;
8
- parameterConfigurations;
9
- configuration;
14
+ parameterOptions;
15
+ options;
10
16
  defaultValues;
11
- constructor(configuration) {
12
- this.validateResourceConfiguration(configuration);
13
- this.typeId = configuration.type;
14
- this.statefulParameters = new Map(configuration.statefulParameters?.map((sp) => [sp.name, sp]));
15
- this.parameterConfigurations = this.initializeParameterConfigurations(configuration);
16
- this.defaultValues = this.initializeDefaultValues(configuration);
17
- this.dependencies = configuration.dependencies ?? [];
18
- this.configuration = configuration;
17
+ ajv;
18
+ schemaValidator;
19
+ constructor(options) {
20
+ this.typeId = options.type;
21
+ this.dependencies = options.dependencies ?? [];
22
+ this.options = options;
23
+ if (this.options.schema) {
24
+ this.ajv = new Ajv2020.default({
25
+ strict: true,
26
+ strictRequired: false,
27
+ });
28
+ this.schemaValidator = this.ajv.compile(this.options.schema);
29
+ }
30
+ const parser = new ResourceOptionsParser(options);
31
+ this.statefulParameters = parser.statefulParameters;
32
+ this.transformParameters = parser.transformParameters;
33
+ this.resourceParameters = parser.resourceParameters;
34
+ this.parameterOptions = parser.changeSetParameterOptions;
35
+ this.defaultValues = parser.defaultValues;
36
+ this.statefulParameterOrder = parser.statefulParameterOrder;
37
+ this.transformParameterOrder = parser.transformParameterOrder;
19
38
  }
20
39
  async onInitialize() { }
40
+ async validateResource(parameters) {
41
+ if (this.schemaValidator) {
42
+ const isValid = this.schemaValidator(parameters);
43
+ if (!isValid) {
44
+ return {
45
+ isValid: false,
46
+ errors: this.schemaValidator?.errors ?? [],
47
+ };
48
+ }
49
+ }
50
+ return this.validate(parameters);
51
+ }
21
52
  async plan(desiredConfig) {
22
- const planConfiguration = {
53
+ const planOptions = {
23
54
  statefulMode: false,
24
- parameterConfigurations: this.parameterConfigurations,
55
+ parameterOptions: this.parameterOptions,
25
56
  };
26
- const { resourceMetadata, parameters: desiredParameters } = splitUserConfig(desiredConfig);
27
- this.addDefaultValues(desiredParameters);
28
- const resourceParameters = Object.fromEntries([
29
- ...Object.entries(desiredParameters).filter(([key]) => !this.statefulParameters.has(key)),
30
- ]);
31
- const statefulParameters = [...this.statefulParameters.values()]
32
- .filter((sp) => desiredParameters[sp.name] !== undefined);
33
- const entriesToRefresh = new Map(Object.entries(resourceParameters));
34
- const currentParameters = await this.refresh(entriesToRefresh);
57
+ const parsedConfig = new ConfigParser(desiredConfig, this.statefulParameters, this.transformParameters);
58
+ const { parameters: desiredParameters, resourceMetadata, resourceParameters, statefulParameters } = parsedConfig;
59
+ this.addDefaultValues(resourceParameters);
60
+ await this.applyTransformParameters(resourceParameters);
61
+ const currentParameters = await this.refreshResourceParameters(resourceParameters);
35
62
  if (currentParameters == null) {
36
- return Plan.create(desiredParameters, null, resourceMetadata, planConfiguration);
37
- }
38
- this.validateRefreshResults(currentParameters, entriesToRefresh);
39
- for (const statefulParameter of statefulParameters) {
40
- const desiredValue = desiredParameters[statefulParameter.name];
41
- let currentValue = await statefulParameter.refresh(desiredValue ?? null) ?? undefined;
42
- if (Array.isArray(currentValue)
43
- && Array.isArray(desiredValue)
44
- && !planConfiguration.statefulMode
45
- && !statefulParameter.configuration.disableStatelessModeArrayFiltering) {
46
- currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
47
- const pc = planConfiguration?.parameterConfigurations?.[statefulParameter.name];
48
- if (pc && pc.isElementEqual) {
49
- return pc.isElementEqual(d, c);
50
- }
51
- return d === c;
52
- }));
53
- }
54
- currentParameters[statefulParameter.name] = currentValue;
63
+ return Plan.create(desiredParameters, null, resourceMetadata, planOptions);
55
64
  }
56
- return Plan.create(desiredParameters, currentParameters, resourceMetadata, planConfiguration);
65
+ const statefulCurrentParameters = await this.refreshStatefulParameters(statefulParameters, planOptions.statefulMode);
66
+ return Plan.create({ ...resourceParameters, ...statefulParameters }, { ...currentParameters, ...statefulCurrentParameters }, resourceMetadata, planOptions);
57
67
  }
58
68
  async apply(plan) {
59
69
  if (plan.getResourceType() !== this.typeId) {
@@ -78,7 +88,8 @@ export class Resource {
78
88
  async _applyCreate(plan) {
79
89
  await this.applyCreate(plan);
80
90
  const statefulParameterChanges = plan.changeSet.parameterChanges
81
- .filter((pc) => this.statefulParameters.has(pc.name));
91
+ .filter((pc) => this.statefulParameters.has(pc.name))
92
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
82
93
  for (const parameterChange of statefulParameterChanges) {
83
94
  const statefulParameter = this.statefulParameters.get(parameterChange.name);
84
95
  await statefulParameter.applyAdd(parameterChange.newValue, plan);
@@ -95,7 +106,8 @@ export class Resource {
95
106
  await this.applyModify(pc.name, pc.newValue, pc.previousValue, false, plan);
96
107
  }
97
108
  const statefulParameterChanges = parameterChanges
98
- .filter((pc) => this.statefulParameters.has(pc.name));
109
+ .filter((pc) => this.statefulParameters.has(pc.name))
110
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
99
111
  for (const parameterChange of statefulParameterChanges) {
100
112
  const statefulParameter = this.statefulParameters.get(parameterChange.name);
101
113
  switch (parameterChange.operation) {
@@ -115,9 +127,10 @@ export class Resource {
115
127
  }
116
128
  }
117
129
  async _applyDestroy(plan) {
118
- if (this.configuration.callStatefulParameterRemoveOnDestroy) {
130
+ if (this.options.callStatefulParameterRemoveOnDestroy) {
119
131
  const statefulParameterChanges = plan.changeSet.parameterChanges
120
- .filter((pc) => this.statefulParameters.has(pc.name));
132
+ .filter((pc) => this.statefulParameters.has(pc.name))
133
+ .sort((a, b) => this.statefulParameterOrder.get(a.name) - this.statefulParameterOrder.get(b.name));
121
134
  for (const parameterChange of statefulParameterChanges) {
122
135
  const statefulParameter = this.statefulParameters.get(parameterChange.name);
123
136
  await statefulParameter.applyRemove(parameterChange.previousValue, plan);
@@ -125,42 +138,6 @@ export class Resource {
125
138
  }
126
139
  await this.applyDestroy(plan);
127
140
  }
128
- initializeParameterConfigurations(resourceConfiguration) {
129
- const resourceParameters = Object.fromEntries(Object.entries(resourceConfiguration.parameterConfigurations ?? {})
130
- ?.map(([name, value]) => ([name, { ...value, isStatefulParameter: false }])));
131
- const statefulParameters = resourceConfiguration.statefulParameters
132
- ?.reduce((obj, sp) => {
133
- return {
134
- ...obj,
135
- [sp.name]: {
136
- ...sp.configuration,
137
- isStatefulParameter: true,
138
- }
139
- };
140
- }, {}) ?? {};
141
- return {
142
- ...resourceParameters,
143
- ...statefulParameters,
144
- };
145
- }
146
- initializeDefaultValues(resourceConfiguration) {
147
- if (!resourceConfiguration.parameterConfigurations) {
148
- return {};
149
- }
150
- return Object.fromEntries(Object.entries(resourceConfiguration.parameterConfigurations)
151
- .filter((p) => p[1]?.defaultValue !== undefined)
152
- .map((config) => [config[0], config[1].defaultValue]));
153
- }
154
- validateResourceConfiguration(data) {
155
- if (data.parameterConfigurations && data.statefulParameters) {
156
- const parameters = [...Object.keys(data.parameterConfigurations)];
157
- const statefulParameterSet = new Set(data.statefulParameters.map((sp) => sp.name));
158
- const intersection = parameters.some((p) => statefulParameterSet.has(p));
159
- if (intersection) {
160
- throw new Error(`Resource ${this.typeId} cannot declare a parameter as both stateful and non-stateful`);
161
- }
162
- }
163
- }
164
141
  validateRefreshResults(refresh, desiredMap) {
165
142
  if (!refresh) {
166
143
  return;
@@ -168,12 +145,28 @@ export class Resource {
168
145
  const desiredKeys = new Set(desiredMap.keys());
169
146
  const refreshKeys = new Set(Object.keys(refresh));
170
147
  if (!setsEqual(desiredKeys, refreshKeys)) {
171
- throw new Error(`Resource ${this.configuration.type}
148
+ throw new Error(`Resource ${this.typeId}
172
149
  refresh() must return back exactly the keys that were provided
173
150
  Missing: ${[...desiredKeys].filter((k) => !refreshKeys.has(k))};
174
151
  Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
175
152
  }
176
153
  }
154
+ async applyTransformParameters(desired) {
155
+ const orderedEntries = [...this.transformParameters.entries()]
156
+ .sort(([keyA], [keyB]) => this.transformParameterOrder.get(keyA) - this.transformParameterOrder.get(keyB));
157
+ for (const [key, tp] of orderedEntries) {
158
+ if (desired[key] !== null) {
159
+ const transformedValue = await tp.transform(desired[key]);
160
+ if (Object.keys(transformedValue).some((k) => desired[k] !== undefined)) {
161
+ throw new Error(`Transform parameter ${key} is attempting to override existing value ${desired[key]}`);
162
+ }
163
+ Object.entries(transformedValue).forEach(([tvKey, tvValue]) => {
164
+ desired[tvKey] = tvValue;
165
+ });
166
+ delete desired[key];
167
+ }
168
+ }
169
+ }
177
170
  addDefaultValues(desired) {
178
171
  Object.entries(this.defaultValues)
179
172
  .forEach(([key, defaultValue]) => {
@@ -182,6 +175,80 @@ Additional: ${[...refreshKeys].filter(k => !desiredKeys.has(k))};`);
182
175
  }
183
176
  });
184
177
  }
178
+ async refreshResourceParameters(resourceParameters) {
179
+ const entriesToRefresh = new Map(Object.entries(resourceParameters));
180
+ const currentParameters = await this.refresh(entriesToRefresh);
181
+ this.validateRefreshResults(currentParameters, entriesToRefresh);
182
+ return currentParameters;
183
+ }
184
+ async refreshStatefulParameters(statefulParametersConfig, isStatefulMode) {
185
+ const currentParameters = {};
186
+ const sortedEntries = Object.entries(statefulParametersConfig)
187
+ .sort(([key1], [key2]) => this.statefulParameterOrder.get(key1) - this.statefulParameterOrder.get(key2));
188
+ for (const [key, desiredValue] of sortedEntries) {
189
+ const statefulParameter = this.statefulParameters.get(key);
190
+ if (!statefulParameter) {
191
+ throw new Error(`Stateful parameter ${key} was not found`);
192
+ }
193
+ let currentValue = await statefulParameter.refresh(desiredValue ?? null);
194
+ if (Array.isArray(currentValue)
195
+ && Array.isArray(desiredValue)
196
+ && !isStatefulMode
197
+ && !statefulParameter.options.disableStatelessModeArrayFiltering) {
198
+ currentValue = currentValue.filter((c) => desiredValue?.some((d) => {
199
+ const parameterOptions = statefulParameter.options;
200
+ if (parameterOptions && parameterOptions.isElementEqual) {
201
+ return parameterOptions.isElementEqual(d, c);
202
+ }
203
+ return d === c;
204
+ }));
205
+ }
206
+ currentParameters[key] = currentValue;
207
+ }
208
+ return currentParameters;
209
+ }
210
+ async validate(parameters) {
211
+ return {
212
+ isValid: true,
213
+ };
214
+ }
215
+ ;
185
216
  async applyModify(parameterName, newValue, previousValue, allowDeletes, plan) { }
186
217
  ;
187
218
  }
219
+ class ConfigParser {
220
+ config;
221
+ statefulParametersMap;
222
+ transformParametersMap;
223
+ constructor(config, statefulParameters, transformParameters) {
224
+ this.config = config;
225
+ this.statefulParametersMap = statefulParameters;
226
+ this.transformParametersMap = transformParameters;
227
+ }
228
+ get resourceMetadata() {
229
+ const { resourceMetadata } = splitUserConfig(this.config);
230
+ return resourceMetadata;
231
+ }
232
+ get parameters() {
233
+ const { parameters } = splitUserConfig(this.config);
234
+ return parameters;
235
+ }
236
+ get resourceParameters() {
237
+ const parameters = this.parameters;
238
+ return Object.fromEntries([
239
+ ...Object.entries(parameters).filter(([key]) => !(this.statefulParametersMap.has(key) || this.transformParametersMap.has(key))),
240
+ ]);
241
+ }
242
+ get statefulParameters() {
243
+ const parameters = this.parameters;
244
+ return Object.fromEntries([
245
+ ...Object.entries(parameters).filter(([key]) => this.statefulParametersMap.has(key)),
246
+ ]);
247
+ }
248
+ get transformParameters() {
249
+ const parameters = this.parameters;
250
+ return Object.fromEntries([
251
+ ...Object.entries(parameters).filter(([key]) => this.transformParametersMap.has(key)),
252
+ ]);
253
+ }
254
+ }