codify-plugin-lib 1.0.80 → 1.0.81
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.
- package/dist/plan/change-set.d.ts +1 -1
- package/dist/plan/change-set.js +5 -9
- package/dist/plan/plan.d.ts +1 -1
- package/dist/plan/plan.js +9 -10
- package/dist/resource/resource-settings.d.ts +2 -2
- package/dist/utils/utils.d.ts +1 -6
- package/dist/utils/utils.js +2 -43
- package/package.json +1 -1
- package/src/plan/change-set.test.ts +27 -27
- package/src/plan/change-set.ts +8 -14
- package/src/plan/plan.test.ts +4 -5
- package/src/plan/plan.ts +13 -18
- package/src/resource/parsed-resource-settings.ts +1 -1
- package/src/resource/resource-controller.test.ts +3 -3
- package/src/resource/resource-settings.test.ts +1 -32
- package/src/resource/resource-settings.ts +2 -2
- package/src/resource/stateful-parameter.test.ts +14 -25
- package/src/utils/utils.ts +2 -57
|
@@ -30,7 +30,7 @@ export declare class ChangeSet<T extends StringIndexedObject> {
|
|
|
30
30
|
static empty<T extends StringIndexedObject>(): ChangeSet<T>;
|
|
31
31
|
static create<T extends StringIndexedObject>(desired: Partial<T>): ChangeSet<T>;
|
|
32
32
|
static destroy<T extends StringIndexedObject>(current: Partial<T>): ChangeSet<T>;
|
|
33
|
-
static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParameterSetting>>):
|
|
33
|
+
static calculateModification<T extends StringIndexedObject>(desired: Partial<T>, current: Partial<T>, parameterSettings?: Partial<Record<keyof T, ParameterSetting>>): ChangeSet<T>;
|
|
34
34
|
/**
|
|
35
35
|
* Calculates the differences between the desired and current parameters,
|
|
36
36
|
* and returns a list of parameter changes that describe what needs to be added,
|
package/dist/plan/change-set.js
CHANGED
|
@@ -45,8 +45,8 @@ export class ChangeSet {
|
|
|
45
45
|
}));
|
|
46
46
|
return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
|
|
47
47
|
}
|
|
48
|
-
static
|
|
49
|
-
const pc =
|
|
48
|
+
static calculateModification(desired, current, parameterSettings = {}) {
|
|
49
|
+
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
|
|
50
50
|
const statefulParameterKeys = new Set(Object.entries(parameterSettings)
|
|
51
51
|
.filter(([, v]) => v?.type === 'stateful')
|
|
52
52
|
.map(([k]) => k));
|
|
@@ -77,7 +77,7 @@ export class ChangeSet {
|
|
|
77
77
|
* @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
|
|
78
78
|
* @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
|
|
79
79
|
*/
|
|
80
|
-
static
|
|
80
|
+
static calculateParameterChanges(desiredParameters, currentParameters, parameterOptions) {
|
|
81
81
|
const parameterChangeSet = new Array();
|
|
82
82
|
// Filter out null and undefined values or else the diff below will not work
|
|
83
83
|
const desired = Object.fromEntries(Object.entries(desiredParameters).filter(([, v]) => v !== null && v !== undefined));
|
|
@@ -93,7 +93,7 @@ export class ChangeSet {
|
|
|
93
93
|
delete current[k];
|
|
94
94
|
continue;
|
|
95
95
|
}
|
|
96
|
-
if (!
|
|
96
|
+
if (!ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
97
97
|
parameterChangeSet.push({
|
|
98
98
|
name: k,
|
|
99
99
|
previousValue: v ?? null,
|
|
@@ -138,12 +138,8 @@ export class ChangeSet {
|
|
|
138
138
|
const indexNext = orderOfOperations.indexOf(next);
|
|
139
139
|
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
140
140
|
}
|
|
141
|
-
static
|
|
141
|
+
static isSame(desired, current, setting) {
|
|
142
142
|
switch (setting?.type) {
|
|
143
|
-
case 'stateful': {
|
|
144
|
-
const statefulSetting = setting.definition.getSettings();
|
|
145
|
-
return ChangeSet.isSame(desired, current, statefulSetting);
|
|
146
|
-
}
|
|
147
143
|
case 'array': {
|
|
148
144
|
const arrayParameter = setting;
|
|
149
145
|
return areArraysEqual(arrayParameter, desired, current);
|
package/dist/plan/plan.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare class Plan<T extends StringIndexedObject> {
|
|
|
41
41
|
coreParameters: ResourceConfig;
|
|
42
42
|
settings: ParsedResourceSettings<T>;
|
|
43
43
|
statefulMode: boolean;
|
|
44
|
-
}):
|
|
44
|
+
}): Plan<T>;
|
|
45
45
|
/**
|
|
46
46
|
* Only keep relevant params for the plan. We don't want to change settings that were not already
|
|
47
47
|
* defined.
|
package/dist/plan/plan.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ParameterOperation, ResourceOperation, } from 'codify-schemas';
|
|
2
2
|
import { v4 as uuidV4 } from 'uuid';
|
|
3
|
-
import { asyncFilter, asyncIncludes, asyncMap } from '../utils/utils.js';
|
|
4
3
|
import { ChangeSet } from './change-set.js';
|
|
5
4
|
/**
|
|
6
5
|
* A plan represents a set of actions that after taken will turn the current resource into the desired one.
|
|
@@ -71,7 +70,7 @@ export class Plan {
|
|
|
71
70
|
getResourceType() {
|
|
72
71
|
return this.coreParameters.type;
|
|
73
72
|
}
|
|
74
|
-
static
|
|
73
|
+
static calculate(params) {
|
|
75
74
|
const { desiredParameters, currentParametersArray, stateParameters, coreParameters, settings, statefulMode } = params;
|
|
76
75
|
const currentParameters = Plan.matchCurrentParameters({
|
|
77
76
|
desiredParameters,
|
|
@@ -80,7 +79,7 @@ export class Plan {
|
|
|
80
79
|
settings,
|
|
81
80
|
statefulMode
|
|
82
81
|
});
|
|
83
|
-
const filteredCurrentParameters =
|
|
82
|
+
const filteredCurrentParameters = Plan.filterCurrentParams({
|
|
84
83
|
desiredParameters,
|
|
85
84
|
currentParameters,
|
|
86
85
|
stateParameters,
|
|
@@ -100,7 +99,7 @@ export class Plan {
|
|
|
100
99
|
return new Plan(uuidV4(), ChangeSet.destroy(filteredCurrentParameters), coreParameters);
|
|
101
100
|
}
|
|
102
101
|
// NO-OP, MODIFY or RE-CREATE
|
|
103
|
-
const changeSet =
|
|
102
|
+
const changeSet = ChangeSet.calculateModification(desiredParameters, filteredCurrentParameters, settings.parameterSettings);
|
|
104
103
|
return new Plan(uuidV4(), changeSet, coreParameters);
|
|
105
104
|
}
|
|
106
105
|
/**
|
|
@@ -111,7 +110,7 @@ export class Plan {
|
|
|
111
110
|
* 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
|
|
112
111
|
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
113
112
|
*/
|
|
114
|
-
static
|
|
113
|
+
static filterCurrentParams(params) {
|
|
115
114
|
const { desiredParameters: desired, currentParameters: current, stateParameters: state, settings, statefulMode } = params;
|
|
116
115
|
if (!current) {
|
|
117
116
|
return null;
|
|
@@ -126,8 +125,9 @@ export class Plan {
|
|
|
126
125
|
return filteredCurrent;
|
|
127
126
|
}
|
|
128
127
|
// TODO: Add object handling here in addition to arrays in the future
|
|
129
|
-
const arrayStatefulParameters = Object.fromEntries(
|
|
130
|
-
.filter(([k, v]) => isArrayStatefulParameter(k, v))
|
|
128
|
+
const arrayStatefulParameters = Object.fromEntries(Object.entries(filteredCurrent)
|
|
129
|
+
.filter(([k, v]) => isArrayStatefulParameter(k, v))
|
|
130
|
+
.map(([k, v]) => [k, filterArrayStatefulParameter(k, v)]));
|
|
131
131
|
return { ...filteredCurrent, ...arrayStatefulParameters };
|
|
132
132
|
function filterCurrent() {
|
|
133
133
|
if (!current) {
|
|
@@ -148,14 +148,13 @@ export class Plan {
|
|
|
148
148
|
&& settings.parameterSettings[k].definition.getSettings().type === 'array'
|
|
149
149
|
&& Array.isArray(v);
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
function filterArrayStatefulParameter(k, v) {
|
|
152
152
|
const desiredArray = desired[k];
|
|
153
153
|
const matcher = settings.parameterSettings[k]
|
|
154
154
|
.definition
|
|
155
155
|
.getSettings()
|
|
156
156
|
.isElementEqual;
|
|
157
|
-
|
|
158
|
-
return asyncFilter(v, async (cv) => asyncIncludes(desiredArray, async (dv) => eq(dv, cv)));
|
|
157
|
+
return v.filter((cv) => desiredArray.find((dv) => (matcher ?? ((a, b) => a === b))(dv, cv)));
|
|
159
158
|
}
|
|
160
159
|
}
|
|
161
160
|
// TODO: This needs to be revisited. I don't think this is valid anymore.
|
|
@@ -97,7 +97,7 @@ export interface DefaultParameterSetting {
|
|
|
97
97
|
*
|
|
98
98
|
* @return Return true if equal
|
|
99
99
|
*/
|
|
100
|
-
isEqual?: (desired: any, current: any) =>
|
|
100
|
+
isEqual?: (desired: any, current: any) => boolean;
|
|
101
101
|
/**
|
|
102
102
|
* Chose if the resource can be modified instead of re-created when there is a change to this parameter.
|
|
103
103
|
* Defaults to false (re-create).
|
|
@@ -123,7 +123,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
|
123
123
|
*
|
|
124
124
|
* @return Return true if desired is equivalent to current.
|
|
125
125
|
*/
|
|
126
|
-
isElementEqual?: (desired: any, current: any) =>
|
|
126
|
+
isElementEqual?: (desired: any, current: any) => boolean;
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
129
|
* Stateful parameter type specific settings. A stateful parameter is a sub-resource that can hold its own
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -37,10 +37,5 @@ export declare function splitUserConfig<T extends StringIndexedObject>(config: R
|
|
|
37
37
|
};
|
|
38
38
|
export declare function setsEqual(set1: Set<unknown>, set2: Set<unknown>): boolean;
|
|
39
39
|
export declare function untildify(pathWithTilde: string): string;
|
|
40
|
-
export declare function areArraysEqual(parameter: ArrayParameterSetting, desired: unknown, current: unknown):
|
|
41
|
-
export declare function asyncFilter<T>(arr: T[], filter: (a: T) => Promise<boolean> | boolean): Promise<T[]>;
|
|
42
|
-
export declare function asyncMap<T, R>(arr: T[], map: (a: T) => Promise<R> | R): Promise<R[]>;
|
|
43
|
-
export declare function asyncFindIndex<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<number>;
|
|
44
|
-
export declare function asyncFind<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<T | undefined>;
|
|
45
|
-
export declare function asyncIncludes<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<boolean>;
|
|
40
|
+
export declare function areArraysEqual(parameter: ArrayParameterSetting, desired: unknown, current: unknown): boolean;
|
|
46
41
|
export {};
|
package/dist/utils/utils.js
CHANGED
|
@@ -72,7 +72,7 @@ const homeDirectory = os.homedir();
|
|
|
72
72
|
export function untildify(pathWithTilde) {
|
|
73
73
|
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
74
74
|
}
|
|
75
|
-
export
|
|
75
|
+
export function areArraysEqual(parameter, desired, current) {
|
|
76
76
|
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
77
77
|
throw new Error(`A non-array value:
|
|
78
78
|
|
|
@@ -88,11 +88,10 @@ Was provided even though type array was specified.
|
|
|
88
88
|
}
|
|
89
89
|
const desiredCopy = [...desired];
|
|
90
90
|
const currentCopy = [...current];
|
|
91
|
-
const eq = parameter.isElementEqual ?? ((a, b) => a === b);
|
|
92
91
|
// Algorithm for to check equality between two un-ordered; un-hashable arrays using
|
|
93
92
|
// an isElementEqual method. Time: O(n^2)
|
|
94
93
|
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
95
|
-
const idx =
|
|
94
|
+
const idx = currentCopy.findIndex((e2) => (parameter.isElementEqual ?? ((a, b) => a === b))(desiredCopy[counter], e2));
|
|
96
95
|
if (idx === -1) {
|
|
97
96
|
return false;
|
|
98
97
|
}
|
|
@@ -101,43 +100,3 @@ Was provided even though type array was specified.
|
|
|
101
100
|
}
|
|
102
101
|
return currentCopy.length === 0;
|
|
103
102
|
}
|
|
104
|
-
export async function asyncFilter(arr, filter) {
|
|
105
|
-
const result = [];
|
|
106
|
-
for (const element of arr) {
|
|
107
|
-
if (await filter(element)) {
|
|
108
|
-
result.push(element);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
}
|
|
113
|
-
export async function asyncMap(arr, map) {
|
|
114
|
-
const result = [];
|
|
115
|
-
for (const element of arr) {
|
|
116
|
-
result.push(await map(element));
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
export async function asyncFindIndex(arr, eq) {
|
|
121
|
-
for (const [counter, element] of arr.entries()) {
|
|
122
|
-
if (await eq(element)) {
|
|
123
|
-
return counter;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return -1;
|
|
127
|
-
}
|
|
128
|
-
export async function asyncFind(arr, eq) {
|
|
129
|
-
for (const element of arr) {
|
|
130
|
-
if (await eq(element)) {
|
|
131
|
-
return element;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
export async function asyncIncludes(arr, eq) {
|
|
137
|
-
for (const element of arr) {
|
|
138
|
-
if (await eq(element)) {
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@ import { ChangeSet } from './change-set.js';
|
|
|
2
2
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
describe('Change set tests',
|
|
6
|
-
it ('Correctly diffs two resource configs (modify)',
|
|
5
|
+
describe('Change set tests', () => {
|
|
6
|
+
it ('Correctly diffs two resource configs (modify)', () => {
|
|
7
7
|
const after = {
|
|
8
8
|
propA: 'before',
|
|
9
9
|
propB: 'before'
|
|
@@ -14,14 +14,14 @@ describe('Change set tests', async () => {
|
|
|
14
14
|
propB: 'after'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const cs =
|
|
17
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
18
18
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
19
19
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
20
20
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.MODIFY);
|
|
21
21
|
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
it ('Correctly diffs two resource configs (add)',
|
|
24
|
+
it ('Correctly diffs two resource configs (add)', () => {
|
|
25
25
|
const after = {
|
|
26
26
|
propA: 'before',
|
|
27
27
|
propB: 'after'
|
|
@@ -31,7 +31,7 @@ describe('Change set tests', async () => {
|
|
|
31
31
|
propA: 'after',
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const cs =
|
|
34
|
+
const cs = ChangeSet.calculateModification(after, before,);
|
|
35
35
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
36
36
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
37
37
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.ADD);
|
|
@@ -39,7 +39,7 @@ describe('Change set tests', async () => {
|
|
|
39
39
|
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
-
it ('Correctly diffs two resource configs (remove)',
|
|
42
|
+
it ('Correctly diffs two resource configs (remove)', () => {
|
|
43
43
|
const after = {
|
|
44
44
|
propA: 'after',
|
|
45
45
|
}
|
|
@@ -49,14 +49,14 @@ describe('Change set tests', async () => {
|
|
|
49
49
|
propB: 'before'
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const cs =
|
|
52
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
53
53
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
54
54
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
55
55
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
56
56
|
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
-
it ('Correctly diffs two resource configs (no-op)',
|
|
59
|
+
it ('Correctly diffs two resource configs (no-op)', () => {
|
|
60
60
|
const after = {
|
|
61
61
|
propA: 'prop',
|
|
62
62
|
}
|
|
@@ -65,7 +65,7 @@ describe('Change set tests', async () => {
|
|
|
65
65
|
propA: 'prop',
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const cs =
|
|
68
|
+
const cs = ChangeSet.calculateModification(after, before);
|
|
69
69
|
expect(cs.parameterChanges.length).to.eq(1);
|
|
70
70
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
71
71
|
expect(cs.operation).to.eq(ResourceOperation.NOOP)
|
|
@@ -95,7 +95,7 @@ describe('Change set tests', async () => {
|
|
|
95
95
|
expect(cs.operation).to.eq(ResourceOperation.DESTROY)
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
-
it ('handles simple arrays',
|
|
98
|
+
it ('handles simple arrays', () => {
|
|
99
99
|
const before = {
|
|
100
100
|
propA: ['a', 'b', 'c'],
|
|
101
101
|
}
|
|
@@ -104,13 +104,13 @@ describe('Change set tests', async () => {
|
|
|
104
104
|
propA: ['b', 'a', 'c'],
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
const cs =
|
|
107
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
|
|
108
108
|
expect(cs.parameterChanges.length).to.eq(1);
|
|
109
109
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
110
110
|
expect(cs.operation).to.eq(ResourceOperation.NOOP)
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
-
it('handles simple arrays 2',
|
|
113
|
+
it('handles simple arrays 2', () => {
|
|
114
114
|
const after = {
|
|
115
115
|
propA: ['a', 'b', 'c'],
|
|
116
116
|
}
|
|
@@ -119,13 +119,13 @@ describe('Change set tests', async () => {
|
|
|
119
119
|
propA: ['b', 'a'],
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
const cs =
|
|
122
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { type: 'array' } });
|
|
123
123
|
expect(cs.parameterChanges.length).to.eq(1);
|
|
124
124
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
125
125
|
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
126
126
|
})
|
|
127
127
|
|
|
128
|
-
it('determines the order of operations with canModify 1',
|
|
128
|
+
it('determines the order of operations with canModify 1', () => {
|
|
129
129
|
const after = {
|
|
130
130
|
propA: 'after',
|
|
131
131
|
}
|
|
@@ -135,14 +135,14 @@ describe('Change set tests', async () => {
|
|
|
135
135
|
propB: 'before'
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
const cs =
|
|
138
|
+
const cs = ChangeSet.calculateModification(after, before, { propA: { canModify: true } });
|
|
139
139
|
expect(cs.parameterChanges.length).to.eq(2);
|
|
140
140
|
expect(cs.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
141
141
|
expect(cs.parameterChanges[1].operation).to.eq(ParameterOperation.REMOVE);
|
|
142
142
|
expect(cs.operation).to.eq(ResourceOperation.RECREATE)
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
-
it('determines the order of operations with canModify 2',
|
|
145
|
+
it('determines the order of operations with canModify 2', () => {
|
|
146
146
|
const after = {
|
|
147
147
|
propA: 'after',
|
|
148
148
|
}
|
|
@@ -152,7 +152,7 @@ describe('Change set tests', async () => {
|
|
|
152
152
|
propB: 'before'
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
const cs =
|
|
155
|
+
const cs = ChangeSet.calculateModification<any>(after, before, {
|
|
156
156
|
propA: { canModify: true },
|
|
157
157
|
propB: { canModify: true }
|
|
158
158
|
});
|
|
@@ -163,38 +163,38 @@ describe('Change set tests', async () => {
|
|
|
163
163
|
})
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
it('correctly determines array equality',
|
|
166
|
+
it('correctly determines array equality', () => {
|
|
167
167
|
const arrA = ['a', 'b', 'd'];
|
|
168
168
|
const arrB = ['a', 'b', 'd'];
|
|
169
169
|
|
|
170
|
-
const result =
|
|
170
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
171
171
|
|
|
172
172
|
expect(result.operation).to.eq(ResourceOperation.NOOP);
|
|
173
173
|
})
|
|
174
174
|
|
|
175
|
-
it('correctly determines array equality 2',
|
|
175
|
+
it('correctly determines array equality 2', () => {
|
|
176
176
|
const arrA = ['a', 'b'];
|
|
177
177
|
const arrB = ['a', 'b', 'd'];
|
|
178
178
|
|
|
179
|
-
const result =
|
|
179
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
180
180
|
|
|
181
181
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
182
182
|
})
|
|
183
183
|
|
|
184
|
-
it('correctly determines array equality 3',
|
|
184
|
+
it('correctly determines array equality 3', () => {
|
|
185
185
|
const arrA = ['b', 'a', 'd'];
|
|
186
186
|
const arrB = ['a', 'b', 'd'];
|
|
187
187
|
|
|
188
|
-
const result =
|
|
188
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, { propA: { type: 'array' } })
|
|
189
189
|
|
|
190
190
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.NOOP);
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
-
it('correctly determines array equality 4',
|
|
193
|
+
it('correctly determines array equality 4', () => {
|
|
194
194
|
const arrA = [{ key1: 'a' }, { key1: 'a' }, { key1: 'a' }];
|
|
195
195
|
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
196
196
|
|
|
197
|
-
const result =
|
|
197
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
|
|
198
198
|
propA: {
|
|
199
199
|
type: 'array',
|
|
200
200
|
isElementEqual: (a, b) => a.key1 === b.key1
|
|
@@ -204,11 +204,11 @@ describe('Change set tests', async () => {
|
|
|
204
204
|
expect(result.parameterChanges[0].operation).to.eq(ParameterOperation.MODIFY);
|
|
205
205
|
})
|
|
206
206
|
|
|
207
|
-
it('correctly determines array equality 5',
|
|
207
|
+
it('correctly determines array equality 5', () => {
|
|
208
208
|
const arrA = [{ key1: 'b' }, { key1: 'a' }, { key1: 'a' }];
|
|
209
209
|
const arrB = [{ key1: 'a' }, { key1: 'a' }, { key1: 'b' }];
|
|
210
210
|
|
|
211
|
-
const result =
|
|
211
|
+
const result = ChangeSet.calculateModification({ propA: arrA }, { propA: arrB }, {
|
|
212
212
|
propA: {
|
|
213
213
|
type: 'array',
|
|
214
214
|
isElementEqual: (a, b) => a.key1 === b.key1
|
package/src/plan/change-set.ts
CHANGED
|
@@ -85,12 +85,12 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
85
85
|
return new ChangeSet(ResourceOperation.DESTROY, parameterChanges);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
static
|
|
88
|
+
static calculateModification<T extends StringIndexedObject>(
|
|
89
89
|
desired: Partial<T>,
|
|
90
90
|
current: Partial<T>,
|
|
91
91
|
parameterSettings: Partial<Record<keyof T, ParameterSetting>> = {},
|
|
92
|
-
):
|
|
93
|
-
const pc =
|
|
92
|
+
): ChangeSet<T> {
|
|
93
|
+
const pc = ChangeSet.calculateParameterChanges(desired, current, parameterSettings);
|
|
94
94
|
|
|
95
95
|
const statefulParameterKeys = new Set(
|
|
96
96
|
Object.entries(parameterSettings)
|
|
@@ -126,11 +126,11 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
126
126
|
* @param {Partial<Record<keyof T, ParameterSetting>>} [parameterOptions] - Optional settings used when comparing parameters.
|
|
127
127
|
* @return {ParameterChange<T>[]} A list of changes required to transition from the current state to the desired state.
|
|
128
128
|
*/
|
|
129
|
-
private static
|
|
129
|
+
private static calculateParameterChanges<T extends StringIndexedObject>(
|
|
130
130
|
desiredParameters: Partial<T>,
|
|
131
131
|
currentParameters: Partial<T>,
|
|
132
132
|
parameterOptions?: Partial<Record<keyof T, ParameterSetting>>,
|
|
133
|
-
):
|
|
133
|
+
): ParameterChange<T>[] {
|
|
134
134
|
const parameterChangeSet = new Array<ParameterChange<T>>();
|
|
135
135
|
|
|
136
136
|
// Filter out null and undefined values or else the diff below will not work
|
|
@@ -155,7 +155,7 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
155
155
|
continue;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
if (!
|
|
158
|
+
if (!ChangeSet.isSame(desired[k], current[k], parameterOptions?.[k])) {
|
|
159
159
|
parameterChangeSet.push({
|
|
160
160
|
name: k,
|
|
161
161
|
previousValue: v ?? null,
|
|
@@ -210,18 +210,12 @@ export class ChangeSet<T extends StringIndexedObject> {
|
|
|
210
210
|
return orderOfOperations[Math.max(indexPrev, indexNext)];
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
private static
|
|
213
|
+
private static isSame(
|
|
214
214
|
desired: unknown,
|
|
215
215
|
current: unknown,
|
|
216
216
|
setting?: ParameterSetting,
|
|
217
|
-
):
|
|
217
|
+
): boolean {
|
|
218
218
|
switch (setting?.type) {
|
|
219
|
-
case 'stateful': {
|
|
220
|
-
const statefulSetting = (setting as StatefulParameterSetting).definition.getSettings()
|
|
221
|
-
|
|
222
|
-
return ChangeSet.isSame(desired, current, statefulSetting as ParameterSetting);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
219
|
case 'array': {
|
|
226
220
|
const arrayParameter = setting as ArrayParameterSetting;
|
|
227
221
|
return areArraysEqual(arrayParameter, desired, current)
|
package/src/plan/plan.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { Plan } from './plan.js';
|
|
3
3
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
4
|
-
import { TestConfig,
|
|
4
|
+
import { TestConfig, TestResource } from '../utils/test-utils.test.js';
|
|
5
5
|
import { ResourceController } from '../resource/resource-controller.js';
|
|
6
6
|
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
|
|
7
7
|
import { ResourceSettings } from '../resource/resource-settings.js';
|
|
@@ -128,8 +128,8 @@ describe('Plan entity tests', () => {
|
|
|
128
128
|
).to.be.true;
|
|
129
129
|
})
|
|
130
130
|
|
|
131
|
-
it('Returns the original resource names',
|
|
132
|
-
const plan =
|
|
131
|
+
it('Returns the original resource names', () => {
|
|
132
|
+
const plan = Plan.calculate<TestConfig>({
|
|
133
133
|
desiredParameters: { propA: 'propA' },
|
|
134
134
|
currentParametersArray: [{ propA: 'propA2' }],
|
|
135
135
|
stateParameters: null,
|
|
@@ -147,8 +147,7 @@ describe('Plan entity tests', () => {
|
|
|
147
147
|
operation: ResourceOperation.RECREATE
|
|
148
148
|
})
|
|
149
149
|
})
|
|
150
|
-
})
|
|
151
|
-
|
|
150
|
+
})
|
|
152
151
|
|
|
153
152
|
function createTestResource() {
|
|
154
153
|
return new class extends TestResource {
|
package/src/plan/plan.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { v4 as uuidV4 } from 'uuid';
|
|
|
10
10
|
|
|
11
11
|
import { ParsedResourceSettings } from '../resource/parsed-resource-settings.js';
|
|
12
12
|
import { ArrayParameterSetting, ResourceSettings, StatefulParameterSetting } from '../resource/resource-settings.js';
|
|
13
|
-
import { asyncFilter, asyncIncludes, asyncMap } from '../utils/utils.js';
|
|
14
13
|
import { ChangeSet } from './change-set.js';
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -106,14 +105,14 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
106
105
|
return this.coreParameters.type
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
static
|
|
108
|
+
static calculate<T extends StringIndexedObject>(params: {
|
|
110
109
|
desiredParameters: Partial<T> | null,
|
|
111
110
|
currentParametersArray: Partial<T>[] | null,
|
|
112
111
|
stateParameters: Partial<T> | null,
|
|
113
112
|
coreParameters: ResourceConfig,
|
|
114
113
|
settings: ParsedResourceSettings<T>,
|
|
115
114
|
statefulMode: boolean,
|
|
116
|
-
}):
|
|
115
|
+
}): Plan<T> {
|
|
117
116
|
const {
|
|
118
117
|
desiredParameters,
|
|
119
118
|
currentParametersArray,
|
|
@@ -131,7 +130,7 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
131
130
|
statefulMode
|
|
132
131
|
});
|
|
133
132
|
|
|
134
|
-
const filteredCurrentParameters =
|
|
133
|
+
const filteredCurrentParameters = Plan.filterCurrentParams<T>({
|
|
135
134
|
desiredParameters,
|
|
136
135
|
currentParameters,
|
|
137
136
|
stateParameters,
|
|
@@ -167,7 +166,7 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
// NO-OP, MODIFY or RE-CREATE
|
|
170
|
-
const changeSet =
|
|
169
|
+
const changeSet = ChangeSet.calculateModification(
|
|
171
170
|
desiredParameters!,
|
|
172
171
|
filteredCurrentParameters!,
|
|
173
172
|
settings.parameterSettings,
|
|
@@ -188,13 +187,13 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
188
187
|
* 2. In stateful mode, filter current by state and desired. We only know about the settings the user has previously set
|
|
189
188
|
* or wants to set. If a parameter is not specified then it's not managed by Codify.
|
|
190
189
|
*/
|
|
191
|
-
private static
|
|
190
|
+
private static filterCurrentParams<T extends StringIndexedObject>(params: {
|
|
192
191
|
desiredParameters: Partial<T> | null,
|
|
193
192
|
currentParameters: Partial<T> | null,
|
|
194
193
|
stateParameters: Partial<T> | null,
|
|
195
194
|
settings: ResourceSettings<T>,
|
|
196
195
|
statefulMode: boolean,
|
|
197
|
-
}):
|
|
196
|
+
}): Partial<T> | null {
|
|
198
197
|
const {
|
|
199
198
|
desiredParameters: desired,
|
|
200
199
|
currentParameters: current,
|
|
@@ -220,12 +219,10 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
220
219
|
|
|
221
220
|
// TODO: Add object handling here in addition to arrays in the future
|
|
222
221
|
const arrayStatefulParameters = Object.fromEntries(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
);
|
|
222
|
+
Object.entries(filteredCurrent)
|
|
223
|
+
.filter(([k, v]) => isArrayStatefulParameter(k, v))
|
|
224
|
+
.map(([k, v]) => [k, filterArrayStatefulParameter(k, v)])
|
|
225
|
+
)
|
|
229
226
|
|
|
230
227
|
return { ...filteredCurrent, ...arrayStatefulParameters }
|
|
231
228
|
|
|
@@ -256,17 +253,15 @@ export class Plan<T extends StringIndexedObject> {
|
|
|
256
253
|
&& Array.isArray(v)
|
|
257
254
|
}
|
|
258
255
|
|
|
259
|
-
|
|
256
|
+
function filterArrayStatefulParameter(k: string, v: unknown[]): unknown[] {
|
|
260
257
|
const desiredArray = desired![k] as unknown[];
|
|
261
258
|
const matcher = ((settings.parameterSettings![k] as StatefulParameterSetting)
|
|
262
259
|
.definition
|
|
263
260
|
.getSettings() as ArrayParameterSetting)
|
|
264
261
|
.isElementEqual;
|
|
265
262
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return asyncFilter(v, async (cv) =>
|
|
269
|
-
asyncIncludes(desiredArray, async (dv) => eq(dv, cv))
|
|
263
|
+
return v.filter((cv) =>
|
|
264
|
+
desiredArray.find((dv) => (matcher ?? ((a: any, b: any) => a === b))(dv, cv))
|
|
270
265
|
)
|
|
271
266
|
}
|
|
272
267
|
}
|
|
@@ -154,7 +154,7 @@ export class ParsedResourceSettings<T extends StringIndexedObject> implements Re
|
|
|
154
154
|
// The rest of the types have defaults set already
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
private resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) =>
|
|
157
|
+
private resolveEqualsFn(parameter: ParameterSetting, key: string): (desired: unknown, current: unknown) => boolean {
|
|
158
158
|
if (parameter.type === 'array') {
|
|
159
159
|
return parameter.isEqual ?? areArraysEqual.bind(areArraysEqual, parameter as ArrayParameterSetting)
|
|
160
160
|
}
|
|
@@ -99,7 +99,7 @@ describe('Resource tests', () => {
|
|
|
99
99
|
const resourceSpy = spy(resource);
|
|
100
100
|
|
|
101
101
|
await controllerSpy.apply(
|
|
102
|
-
|
|
102
|
+
testPlan({
|
|
103
103
|
desired: { propA: 'a', propB: 0 },
|
|
104
104
|
})
|
|
105
105
|
)
|
|
@@ -116,7 +116,7 @@ describe('Resource tests', () => {
|
|
|
116
116
|
const resourceSpy = spy(resource);
|
|
117
117
|
|
|
118
118
|
await controllerSpy.apply(
|
|
119
|
-
|
|
119
|
+
testPlan({
|
|
120
120
|
current: [{ propA: 'a', propB: 0 }],
|
|
121
121
|
state: { propA: 'a', propB: 0 },
|
|
122
122
|
statefulMode: true,
|
|
@@ -135,7 +135,7 @@ describe('Resource tests', () => {
|
|
|
135
135
|
const resourceSpy = spy(resource);
|
|
136
136
|
|
|
137
137
|
await controllerSpy.apply(
|
|
138
|
-
|
|
138
|
+
testPlan({
|
|
139
139
|
desired: { propA: 'a', propB: 0 },
|
|
140
140
|
current: [{ propA: 'b', propB: -1 }],
|
|
141
141
|
statefulMode: true
|
|
@@ -77,7 +77,7 @@ describe('Resource parameter tests', () => {
|
|
|
77
77
|
const resourceSpy = spy(resource);
|
|
78
78
|
|
|
79
79
|
await controller.apply(
|
|
80
|
-
|
|
80
|
+
testPlan<TestConfig>({
|
|
81
81
|
desired: { propA: 'a', propB: 0, propC: 'c' }
|
|
82
82
|
})
|
|
83
83
|
);
|
|
@@ -491,35 +491,4 @@ describe('Resource parameter tests', () => {
|
|
|
491
491
|
|
|
492
492
|
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
493
493
|
})
|
|
494
|
-
|
|
495
|
-
it('Works with async equals methods', async () => {
|
|
496
|
-
const resource = new class extends TestResource {
|
|
497
|
-
getSettings(): ResourceSettings<TestConfig> {
|
|
498
|
-
return {
|
|
499
|
-
id: 'type',
|
|
500
|
-
parameterSettings: {
|
|
501
|
-
propA: {
|
|
502
|
-
isEqual: async (desired, current) => {
|
|
503
|
-
console.log(desired, current)
|
|
504
|
-
await sleep(500);
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
const controller = new ResourceController(resource);
|
|
513
|
-
|
|
514
|
-
const plan = await controller.plan({ type: 'type', propA: 'abc' } as any);
|
|
515
|
-
|
|
516
|
-
console.log(JSON.stringify(plan, null, 2));
|
|
517
|
-
|
|
518
|
-
expect(plan.toResponse().operation).to.equal(ResourceOperation.NOOP);
|
|
519
|
-
})
|
|
520
494
|
})
|
|
521
|
-
|
|
522
|
-
function sleep(ms: number) {
|
|
523
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
524
|
-
}
|
|
525
|
-
|
|
@@ -127,7 +127,7 @@ export interface DefaultParameterSetting {
|
|
|
127
127
|
*
|
|
128
128
|
* @return Return true if equal
|
|
129
129
|
*/
|
|
130
|
-
isEqual?: (desired: any, current: any) =>
|
|
130
|
+
isEqual?: (desired: any, current: any) => boolean;
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Chose if the resource can be modified instead of re-created when there is a change to this parameter.
|
|
@@ -156,7 +156,7 @@ export interface ArrayParameterSetting extends DefaultParameterSetting {
|
|
|
156
156
|
*
|
|
157
157
|
* @return Return true if desired is equivalent to current.
|
|
158
158
|
*/
|
|
159
|
-
isElementEqual?: (desired: any, current: any) =>
|
|
159
|
+
isElementEqual?: (desired: any, current: any) => boolean
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { spy } from 'sinon';
|
|
3
3
|
import { ParameterOperation, ResourceOperation } from 'codify-schemas';
|
|
4
|
-
import { TestArrayStatefulParameter, TestConfig, testPlan,
|
|
5
|
-
import { ArrayParameterSetting,
|
|
6
|
-
import { ResourceController } from './resource-controller.js';
|
|
4
|
+
import { TestArrayStatefulParameter, TestConfig, testPlan, TestStatefulParameter } from '../utils/test-utils.test.js';
|
|
5
|
+
import { ArrayParameterSetting, ParameterSetting } from './resource-settings.js';
|
|
7
6
|
|
|
8
7
|
describe('Stateful parameter tests', () => {
|
|
9
8
|
it('addItem is called the correct number of times', async () => {
|
|
10
|
-
const plan =
|
|
9
|
+
const plan = testPlan<TestConfig>({
|
|
11
10
|
desired: { propZ: ['a', 'b', 'c'] },
|
|
12
11
|
});
|
|
13
12
|
|
|
@@ -22,7 +21,7 @@ describe('Stateful parameter tests', () => {
|
|
|
22
21
|
})
|
|
23
22
|
|
|
24
23
|
it('applyRemoveItem is called the correct number of times', async () => {
|
|
25
|
-
const plan =
|
|
24
|
+
const plan = testPlan<TestConfig>({
|
|
26
25
|
desired: null,
|
|
27
26
|
current: [{ propZ: ['a', 'b', 'c'] }],
|
|
28
27
|
state: { propZ: ['a', 'b', 'c'] },
|
|
@@ -41,7 +40,7 @@ describe('Stateful parameter tests', () => {
|
|
|
41
40
|
|
|
42
41
|
it('In stateless mode only applyAddItem is called only for modifies', async () => {
|
|
43
42
|
const parameter = new TestArrayStatefulParameter()
|
|
44
|
-
const plan =
|
|
43
|
+
const plan = testPlan<TestConfig>({
|
|
45
44
|
desired: { propZ: ['a', 'c', 'd', 'e', 'f'] }, // b to remove, d, e, f to add
|
|
46
45
|
current: [{ propZ: ['a', 'b', 'c'] }],
|
|
47
46
|
settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: parameter } } },
|
|
@@ -72,7 +71,7 @@ describe('Stateful parameter tests', () => {
|
|
|
72
71
|
}
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
const plan =
|
|
74
|
+
const plan = testPlan<TestConfig>({
|
|
76
75
|
desired: { propZ: ['9.12', '9.13'] }, // b to remove, d, e, f to add
|
|
77
76
|
current: [{ propZ: ['9.12.9'] }],
|
|
78
77
|
settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: testParameter } } }
|
|
@@ -92,31 +91,21 @@ describe('Stateful parameter tests', () => {
|
|
|
92
91
|
expect(testParameter.removeItem.called).to.be.false;
|
|
93
92
|
})
|
|
94
93
|
|
|
95
|
-
it('
|
|
96
|
-
const testParameter = spy(new class extends
|
|
97
|
-
getSettings():
|
|
94
|
+
it('isEqual works with type defaults', () => {
|
|
95
|
+
const testParameter = spy(new class extends TestStatefulParameter {
|
|
96
|
+
getSettings(): ParameterSetting {
|
|
98
97
|
return {
|
|
99
|
-
type: '
|
|
100
|
-
isElementEqual: async (desired, current) => {
|
|
101
|
-
console.log(desired, current)
|
|
102
|
-
await sleep(50);
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
98
|
+
type: 'version',
|
|
105
99
|
}
|
|
106
100
|
}
|
|
107
101
|
});
|
|
108
102
|
|
|
109
|
-
const plan =
|
|
110
|
-
desired: { propZ:
|
|
111
|
-
current: [{ propZ:
|
|
103
|
+
const plan = testPlan<TestConfig>({
|
|
104
|
+
desired: { propZ: '20' },
|
|
105
|
+
current: [{ propZ: '20.17.0' }],
|
|
112
106
|
settings: { id: 'type', parameterSettings: { propZ: { type: 'stateful', definition: testParameter } } }
|
|
113
107
|
});
|
|
114
108
|
|
|
115
|
-
expect(plan.
|
|
109
|
+
expect(plan.changeSet.operation).to.eq(ResourceOperation.NOOP);
|
|
116
110
|
})
|
|
117
111
|
})
|
|
118
|
-
|
|
119
|
-
function sleep(ms: number) {
|
|
120
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
121
|
-
}
|
|
122
|
-
|
package/src/utils/utils.ts
CHANGED
|
@@ -111,7 +111,7 @@ export function untildify(pathWithTilde: string) {
|
|
|
111
111
|
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export
|
|
114
|
+
export function areArraysEqual(parameter: ArrayParameterSetting, desired: unknown, current: unknown) {
|
|
115
115
|
if (!Array.isArray(desired) || !Array.isArray(current)) {
|
|
116
116
|
throw new Error(`A non-array value:
|
|
117
117
|
|
|
@@ -130,12 +130,10 @@ Was provided even though type array was specified.
|
|
|
130
130
|
const desiredCopy = [...desired];
|
|
131
131
|
const currentCopy = [...current];
|
|
132
132
|
|
|
133
|
-
const eq = parameter.isElementEqual ?? ((a, b) => a === b);
|
|
134
|
-
|
|
135
133
|
// Algorithm for to check equality between two un-ordered; un-hashable arrays using
|
|
136
134
|
// an isElementEqual method. Time: O(n^2)
|
|
137
135
|
for (let counter = desiredCopy.length - 1; counter >= 0; counter--) {
|
|
138
|
-
const idx =
|
|
136
|
+
const idx = currentCopy.findIndex((e2) => (parameter.isElementEqual ?? ((a, b) => a === b))(desiredCopy[counter], e2))
|
|
139
137
|
|
|
140
138
|
if (idx === -1) {
|
|
141
139
|
return false;
|
|
@@ -147,56 +145,3 @@ Was provided even though type array was specified.
|
|
|
147
145
|
|
|
148
146
|
return currentCopy.length === 0;
|
|
149
147
|
}
|
|
150
|
-
|
|
151
|
-
export async function asyncFilter<T>(arr: T[], filter: (a: T) => Promise<boolean> | boolean): Promise<T[]> {
|
|
152
|
-
const result = [];
|
|
153
|
-
|
|
154
|
-
for (const element of arr) {
|
|
155
|
-
if (await filter(element)) {
|
|
156
|
-
result.push(element);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return result;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export async function asyncMap<T, R>(arr: T[], map: (a: T) => Promise<R> | R): Promise<R[]> {
|
|
164
|
-
const result: R[] = [];
|
|
165
|
-
|
|
166
|
-
for (const element of arr) {
|
|
167
|
-
result.push(await map(element));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
export async function asyncFindIndex<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<number> {
|
|
175
|
-
for (const [counter, element] of arr.entries()) {
|
|
176
|
-
if (await eq(element)) {
|
|
177
|
-
return counter;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return -1;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export async function asyncFind<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<T | undefined> {
|
|
185
|
-
for (const element of arr) {
|
|
186
|
-
if (await eq(element)) {
|
|
187
|
-
return element;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export async function asyncIncludes<T>(arr: T[], eq: (a: T) => Promise<boolean> | boolean): Promise<boolean> {
|
|
195
|
-
for (const element of arr) {
|
|
196
|
-
if (await eq(element)) {
|
|
197
|
-
return true
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return false;
|
|
202
|
-
}
|