@fgv/ts-utils 5.0.1-9 → 5.0.1
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/index.js +32 -0
- package/dist/packlets/base/brand.js +23 -0
- package/dist/packlets/base/index.js +28 -0
- package/dist/packlets/base/mapResults.js +217 -0
- package/dist/packlets/base/messageAggregator.js +110 -0
- package/dist/packlets/base/normalize.js +132 -0
- package/dist/packlets/base/result.js +611 -0
- package/dist/packlets/base/utils.js +185 -0
- package/dist/packlets/collections/collectible.js +82 -0
- package/dist/packlets/collections/collector.js +189 -0
- package/dist/packlets/collections/collectorValidator.js +94 -0
- package/dist/packlets/collections/common.js +23 -0
- package/dist/packlets/collections/convertingCollector.js +118 -0
- package/dist/packlets/collections/convertingCollectorValidator.js +95 -0
- package/dist/packlets/collections/index.js +37 -0
- package/dist/packlets/collections/keyValueConverters.js +100 -0
- package/dist/packlets/collections/readonlyResultMap.js +23 -0
- package/dist/packlets/collections/resultMap.js +214 -0
- package/dist/packlets/collections/resultMapValidator.js +122 -0
- package/dist/packlets/collections/utils.js +31 -0
- package/dist/packlets/collections/validatingCollector.js +63 -0
- package/dist/packlets/collections/validatingConvertingCollector.js +64 -0
- package/dist/packlets/collections/validatingResultMap.js +57 -0
- package/dist/packlets/conversion/baseConverter.js +242 -0
- package/dist/packlets/conversion/converter.js +2 -0
- package/dist/packlets/conversion/converters.js +637 -0
- package/dist/packlets/conversion/defaultingConverter.js +149 -0
- package/dist/packlets/conversion/index.js +29 -0
- package/dist/packlets/conversion/objectConverter.js +141 -0
- package/dist/packlets/conversion/stringConverter.js +93 -0
- package/dist/packlets/hash/crcNormalizer.js +114 -0
- package/dist/packlets/hash/hashingNormalizer.js +92 -0
- package/dist/packlets/hash/index.js +24 -0
- package/dist/packlets/logging/index.js +24 -0
- package/dist/packlets/logging/logReporter.js +114 -0
- package/dist/packlets/logging/logger.js +258 -0
- package/dist/packlets/validation/array.js +64 -0
- package/dist/packlets/validation/boolean.js +52 -0
- package/dist/packlets/validation/classes.js +31 -0
- package/dist/packlets/validation/common.js +23 -0
- package/dist/packlets/validation/field.js +67 -0
- package/dist/packlets/validation/genericValidator.js +160 -0
- package/dist/packlets/validation/index.js +29 -0
- package/dist/packlets/validation/number.js +52 -0
- package/dist/packlets/validation/object.js +134 -0
- package/dist/packlets/validation/oneOf.js +58 -0
- package/dist/packlets/validation/string.js +52 -0
- package/dist/packlets/validation/traits.js +56 -0
- package/dist/packlets/validation/typeGuard.js +59 -0
- package/dist/packlets/validation/validator.js +23 -0
- package/dist/packlets/validation/validatorBase.js +37 -0
- package/dist/packlets/validation/validators.js +191 -0
- package/dist/test/helpers/jest/helpers/fsHelpers.js +139 -0
- package/dist/test/helpers/jest/helpers/index.js +2 -0
- package/dist/test/helpers/jest/index.js +17 -0
- package/dist/test/helpers/jest/matchers/index.js +14 -0
- package/dist/test/helpers/jest/matchers/toFail/index.js +23 -0
- package/dist/test/helpers/jest/matchers/toFail/predicate.js +5 -0
- package/dist/test/helpers/jest/matchers/toFailTest/index.js +28 -0
- package/dist/test/helpers/jest/matchers/toFailTest/predicate.js +8 -0
- package/dist/test/helpers/jest/matchers/toFailTestAndMatchSnapshot/index.js +24 -0
- package/dist/test/helpers/jest/matchers/toFailTestAndMatchSnapshot/predicate.js +11 -0
- package/dist/test/helpers/jest/matchers/toFailTestWith/index.js +29 -0
- package/dist/test/helpers/jest/matchers/toFailTestWith/predicate.js +26 -0
- package/dist/test/helpers/jest/matchers/toFailWith/index.js +27 -0
- package/dist/test/helpers/jest/matchers/toFailWith/predicate.js +16 -0
- package/dist/test/helpers/jest/matchers/toFailWithDetail/index.js +27 -0
- package/dist/test/helpers/jest/matchers/toFailWithDetail/predicate.js +22 -0
- package/dist/test/helpers/jest/matchers/toSucceed/index.js +23 -0
- package/dist/test/helpers/jest/matchers/toSucceed/predicate.js +5 -0
- package/dist/test/helpers/jest/matchers/toSucceedAndMatchInlineSnapshot/index.js +24 -0
- package/dist/test/helpers/jest/matchers/toSucceedAndMatchSnapshot/index.js +24 -0
- package/dist/test/helpers/jest/matchers/toSucceedAndSatisfy/index.js +53 -0
- package/dist/test/helpers/jest/matchers/toSucceedAndSatisfy/predicate.js +17 -0
- package/dist/test/helpers/jest/matchers/toSucceedWith/index.js +27 -0
- package/dist/test/helpers/jest/matchers/toSucceedWith/predicate.js +12 -0
- package/dist/test/helpers/jest/matchers/toSucceedWithDetail/index.js +27 -0
- package/dist/test/helpers/jest/matchers/toSucceedWithDetail/predicate.js +17 -0
- package/dist/test/helpers/jest/resolvers/cli.js +10 -0
- package/dist/test/helpers/jest/resolvers/ide.js +10 -0
- package/dist/test/helpers/jest/ts-utils.js +2 -0
- package/dist/test/helpers/jest/types/index.js +3 -0
- package/dist/test/helpers/jest/utils/matcherHelpers.js +47 -0
- package/dist/test/helpers/jest/utils/snapshotResolver.js +11 -0
- package/dist/test/unit/collections/helpers.js +106 -0
- package/dist/test/unit/hashTextEncodeCompat.js +23 -0
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/packlets/hash/crcNormalizer.js +42 -2
- package/package.json +12 -2
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2020 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { fail, succeed } from './result';
|
|
23
|
+
/**
|
|
24
|
+
* Helper type-guard function to report whether a specified key is present in
|
|
25
|
+
* a supplied object.
|
|
26
|
+
* @param key - The key to be tested.
|
|
27
|
+
* @param item - The object to be tested.
|
|
28
|
+
* @returns Returns `true` if the key is present, `false` otherwise.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export function isKeyOf(key, item) {
|
|
32
|
+
return item.hasOwnProperty(key);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Simple implicit pick function, which picks a set of properties from a supplied
|
|
36
|
+
* object. Ignores picked properties that do not exist regardless of type signature.
|
|
37
|
+
* @param from - The object from which keys are to be picked.
|
|
38
|
+
* @param include - The keys of the properties to be picked from `from`.
|
|
39
|
+
* @returns A new object containing the requested properties from `from`, where present.
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export function pick(from, include) {
|
|
43
|
+
const rtrn = {};
|
|
44
|
+
for (const key of include) {
|
|
45
|
+
if (key in from) {
|
|
46
|
+
rtrn[key] = from[key];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return rtrn;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Simple implicit omit function, which picks all of the properties from a supplied
|
|
53
|
+
* object except those specified for exclusion.
|
|
54
|
+
* @param from - The object from which keys are to be picked.
|
|
55
|
+
* @param exclude - The keys of the properties to be excluded from the returned object.
|
|
56
|
+
* @returns A new object containing all of the properties from `from` that were not
|
|
57
|
+
* explicitly excluded.
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
export function omit(from, exclude) {
|
|
61
|
+
const rtrn = {};
|
|
62
|
+
for (const entry of Object.entries(from).filter((e) => !exclude.includes(e[0]))) {
|
|
63
|
+
rtrn[entry[0]] = entry[1];
|
|
64
|
+
}
|
|
65
|
+
return rtrn;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Gets the value of a property specified by key from an arbitrary object,
|
|
69
|
+
* or a default value if the property does not exist.
|
|
70
|
+
* @param key - The key specifying the property to be retrieved.
|
|
71
|
+
* @param item - The object from which the property is to be retrieved.
|
|
72
|
+
* @param defaultValue - An optional default value to be returned if the property
|
|
73
|
+
* is not present (default `undefined`).
|
|
74
|
+
* @returns The value of the requested property, or the default value if the
|
|
75
|
+
* requested property does not exist.
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export function getValueOfPropertyOrDefault(key, item, defaultValue) {
|
|
79
|
+
return isKeyOf(key, item) ? item[key] : defaultValue;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Gets the type of a property specified by key from an arbitrary object.
|
|
83
|
+
* @param key - The key specifying the property to be tested.
|
|
84
|
+
* @param item - The object from which the property is to be tested.
|
|
85
|
+
* @returns The type of the requested property, or `undefined` if the
|
|
86
|
+
* property does not exist.
|
|
87
|
+
* @example
|
|
88
|
+
* Returns `'undefined'` (a string) if the property exists but has the value
|
|
89
|
+
* undefined but `undefined` (the literal) if the property does not exist.
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export function getTypeOfProperty(key, item) {
|
|
93
|
+
return isKeyOf(key, item) ? typeof item[key] : undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Applies a factory method to convert a `Record<TK, TS>` into a `Map<TK, TD>`.
|
|
97
|
+
* @param src - The `Record` to be converted.
|
|
98
|
+
* @param factory - The factory method used to convert elements.
|
|
99
|
+
* @returns {@link Success} with the resulting map on success, or {@link Failure} with a
|
|
100
|
+
* message if an error occurs.
|
|
101
|
+
* @public
|
|
102
|
+
*/
|
|
103
|
+
export function recordToMap(src, factory) {
|
|
104
|
+
const map = new Map();
|
|
105
|
+
for (const key in src) {
|
|
106
|
+
if (src[key] !== undefined) {
|
|
107
|
+
const itemResult = factory(key, src[key]);
|
|
108
|
+
if (itemResult.isSuccess()) {
|
|
109
|
+
map.set(key, itemResult.value);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return fail(`${key}: ${itemResult.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return succeed(map);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Applies a factory method to convert an optional `Record<TK, TS>` into a `Map<TK, TD>`, or `undefined`.
|
|
120
|
+
* @param src - The `Record` to be converted, or undefined.
|
|
121
|
+
* @param factory - The factory method used to convert elements.
|
|
122
|
+
* @returns {@link Success} with the resulting map if conversion succeeds, or {@link Success} with `undefined`
|
|
123
|
+
* if `src` is `undefined`. Returns {@link Failure} with a message if an error occurs.
|
|
124
|
+
* @public
|
|
125
|
+
*/
|
|
126
|
+
export function optionalRecordToMap(src, factory) {
|
|
127
|
+
return src === undefined ? succeed(undefined) : recordToMap(src, factory);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Applies a factory method to convert an optional `Record<TK, TS>` into a `Map<TK, TD>`
|
|
131
|
+
* @param src - The `Record` to be converted, or `undefined`.
|
|
132
|
+
* @param factory - The factory method used to convert elements.
|
|
133
|
+
* @returns {@link Success} with the resulting map (empty if `src` is `undefined`) if conversion succeeds.
|
|
134
|
+
* Returns {@link Failure} with a message if an error occurs.
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
137
|
+
export function optionalRecordToPossiblyEmptyMap(src, factory) {
|
|
138
|
+
return src === undefined ? succeed(new Map()) : recordToMap(src, factory);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Applies a factory method to convert a `ReadonlyMap<TK, TS>` into a `Record<TK, TD>`.
|
|
142
|
+
* @param src - The `Map` object to be converted.
|
|
143
|
+
* @param factory - The factory method used to convert elements.
|
|
144
|
+
* @returns {@link Success} with the resulting `Record<TK, TD>` if conversion succeeds, or
|
|
145
|
+
* {@link Failure} with an error message if an error occurs.
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
148
|
+
export function mapToRecord(src, factory) {
|
|
149
|
+
const record = {};
|
|
150
|
+
for (const kvp of src) {
|
|
151
|
+
if (kvp[1] !== undefined) {
|
|
152
|
+
const itemResult = factory(kvp[0], kvp[1]);
|
|
153
|
+
if (itemResult.isSuccess()) {
|
|
154
|
+
record[kvp[0]] = itemResult.value;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
return fail(`${kvp[0]}: ${itemResult.message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return succeed(record);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Applies a factory method to convert an optional `ReadonlyMap<string, TS>` into a `Record<string, TD>` or `undefined`.
|
|
165
|
+
* @param src - The `Map` object to be converted, or `undefined`.
|
|
166
|
+
* @param factory - The factory method used to convert elements.
|
|
167
|
+
* @returns {@link Success} with the resulting record if conversion succeeds, or {@link Success} with `undefined` if
|
|
168
|
+
* `src` is `undefined`. Returns {@link Failure} with a message if an error occurs.
|
|
169
|
+
* @public
|
|
170
|
+
*/
|
|
171
|
+
export function optionalMapToRecord(src, factory) {
|
|
172
|
+
return src === undefined ? succeed(undefined) : mapToRecord(src, factory);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Applies a factory method to convert an optional `ReadonlyMap<string, TS>` into a `Record<string, TD>`
|
|
176
|
+
* @param src - The `ReadonlyMap` object to be converted, or `undefined`.
|
|
177
|
+
* @param factory - The factory method used to convert elements.
|
|
178
|
+
* @returns {@link Success} with the resulting record (empty if `src` is `undefined`) if conversion succeeds.
|
|
179
|
+
* Returns {@link Failure} with a message if an error occurs.
|
|
180
|
+
* @public
|
|
181
|
+
*/
|
|
182
|
+
export function optionalMapToPossiblyEmptyRecord(src, factory) {
|
|
183
|
+
return src === undefined ? succeed({}) : mapToRecord(src, factory);
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { captureResult, fail, succeed } from '../base';
|
|
23
|
+
import { Converters } from '../conversion';
|
|
24
|
+
/**
|
|
25
|
+
* Simple implementation of {@link Collections.ICollectible | ICollectible} which does not allow the index to be
|
|
26
|
+
* changed once set.
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export class Collectible {
|
|
30
|
+
/**
|
|
31
|
+
* {@link Collections.ICollectible.index}
|
|
32
|
+
*/
|
|
33
|
+
get index() {
|
|
34
|
+
return this._index;
|
|
35
|
+
}
|
|
36
|
+
constructor(params) {
|
|
37
|
+
this.key = params.key;
|
|
38
|
+
if ('indexConverter' in params) {
|
|
39
|
+
this._indexConverter =
|
|
40
|
+
typeof params.indexConverter === 'function'
|
|
41
|
+
? Converters.generic(params.indexConverter)
|
|
42
|
+
: params.indexConverter;
|
|
43
|
+
if (params.index !== undefined) {
|
|
44
|
+
this._index = this._indexConverter.convert(params.index).orThrow();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
/* c8 ignore next 3 - coverage is having a bad day */
|
|
49
|
+
this._index = params.index;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
static createCollectible(params) {
|
|
53
|
+
return captureResult(() => new Collectible(params));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* {@link Collections.ICollectible.setIndex}
|
|
57
|
+
*/
|
|
58
|
+
setIndex(index) {
|
|
59
|
+
let converted;
|
|
60
|
+
if (this._indexConverter) {
|
|
61
|
+
const { value, message } = this._indexConverter.convert(index);
|
|
62
|
+
/* c8 ignore next 3 - there's a test for this but coverage is having a bad day */
|
|
63
|
+
if (message) {
|
|
64
|
+
return fail(message);
|
|
65
|
+
}
|
|
66
|
+
converted = value;
|
|
67
|
+
}
|
|
68
|
+
if (index === this.index) {
|
|
69
|
+
return succeed(this.index);
|
|
70
|
+
}
|
|
71
|
+
else if (this._index !== undefined) {
|
|
72
|
+
return fail(`index ${this.index} is immutable and cannot be changed to ${index}`);
|
|
73
|
+
}
|
|
74
|
+
/* c8 ignore next 3 - should be impossible */
|
|
75
|
+
if (converted === undefined) {
|
|
76
|
+
return fail(`index ${index} cannot be set on a Collectible without an index converter`);
|
|
77
|
+
}
|
|
78
|
+
this._index = converted;
|
|
79
|
+
return succeed(converted);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=collectible.js.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { captureResult, failWithDetail, fail, succeed, succeedWithDetail } from '../base';
|
|
23
|
+
/**
|
|
24
|
+
* A {@link Collections.Collector | Collector} that is a specialized collection
|
|
25
|
+
* which contains items of type {@link Collections.ICollectible | ICollectible},
|
|
26
|
+
* which have a unique key and a write-once index.
|
|
27
|
+
*
|
|
28
|
+
* Items are assigned an index sequentially as they are added to the collection.
|
|
29
|
+
* Once added, items are immutable - they cannot be removed or replaced.
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
export class Collector {
|
|
33
|
+
/**
|
|
34
|
+
* {@inheritdoc Collections.ResultMap.size}
|
|
35
|
+
*/
|
|
36
|
+
get size() {
|
|
37
|
+
return this._byIndex.length;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Constructs a new {@link Collections.Collector | Collector}.
|
|
41
|
+
* @param params - Optional {@link Collections.ICollectorConstructorParams | initialization parameters} used
|
|
42
|
+
* to construct the collector.
|
|
43
|
+
*/
|
|
44
|
+
constructor(params) {
|
|
45
|
+
var _a;
|
|
46
|
+
this._byKey = new Map();
|
|
47
|
+
this._byIndex = [];
|
|
48
|
+
for (const item of (_a = params === null || params === void 0 ? void 0 : params.items) !== null && _a !== void 0 ? _a : []) {
|
|
49
|
+
this.add(item).orThrow();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new {@link Collections.Collector | Collector} instance.
|
|
54
|
+
* @param params - Optional {@link Collections.ICollectorConstructorParams | initialization parameters} used
|
|
55
|
+
* to create the collector.
|
|
56
|
+
* @returns Returns {@link Success | Success} with the new collector if it was created successfully,
|
|
57
|
+
* or {@link Failure | Failure} with an error if the collector could not be created.
|
|
58
|
+
*/
|
|
59
|
+
static createCollector(params) {
|
|
60
|
+
return captureResult(() => new Collector(params));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Adds an item to the collection, failing if a different item with the same key already exists. Note
|
|
64
|
+
* that adding an object that is already in the collection again will succeed without updating the collection.
|
|
65
|
+
* @param item - The item to add.
|
|
66
|
+
* @returns Returns {@link DetailedSuccess | Success} with the item and detail `added` if it was added
|
|
67
|
+
* or detail `exists` if the item was already in the map. Returns {@link DetailedFailure | Failure} with
|
|
68
|
+
* an error message and appropriate detail if the item could not be added.
|
|
69
|
+
*/
|
|
70
|
+
add(item) {
|
|
71
|
+
const existing = this._byKey.get(item.key);
|
|
72
|
+
if (existing === item) {
|
|
73
|
+
return succeedWithDetail(item, 'exists');
|
|
74
|
+
}
|
|
75
|
+
else if (existing) {
|
|
76
|
+
return failWithDetail(`${item.key}: already exists`, 'exists');
|
|
77
|
+
}
|
|
78
|
+
const indexResult = item.setIndex(this._byIndex.length);
|
|
79
|
+
if (indexResult.isFailure()) {
|
|
80
|
+
return failWithDetail(`${item.key}: ${indexResult.message}`, 'invalid-index');
|
|
81
|
+
}
|
|
82
|
+
else if (item.index !== this._byIndex.length) {
|
|
83
|
+
return failWithDetail(`${item.key}: index mismatch - built item has ${item.index}`, 'invalid-index');
|
|
84
|
+
}
|
|
85
|
+
this._byKey.set(item.key, item);
|
|
86
|
+
this._byIndex.push(item);
|
|
87
|
+
return succeedWithDetail(item, 'added');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* {@inheritdoc Collections.ResultMap.entries}
|
|
91
|
+
*/
|
|
92
|
+
entries() {
|
|
93
|
+
return this._byKey.entries();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* {@inheritdoc Collections.ResultMap.forEach}
|
|
97
|
+
*/
|
|
98
|
+
forEach(callback, arg) {
|
|
99
|
+
for (const [key, value] of this._byKey.entries()) {
|
|
100
|
+
callback(value, key, this, arg);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* {@inheritdoc Collections.ResultMap.get}
|
|
105
|
+
*/
|
|
106
|
+
get(key) {
|
|
107
|
+
const item = this._byKey.get(key);
|
|
108
|
+
return item ? succeedWithDetail(item, 'exists') : failWithDetail(`${key}: not found`, 'not-found');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* {@inheritdoc Collections.IReadOnlyCollector.getAt}
|
|
112
|
+
*/
|
|
113
|
+
getAt(index) {
|
|
114
|
+
if (typeof index !== 'number') {
|
|
115
|
+
// Handle Symbol conversion safely
|
|
116
|
+
const indexStr = typeof index === 'symbol' ? index.toString() : String(index);
|
|
117
|
+
return fail(`${indexStr}: collector index must be a number.`);
|
|
118
|
+
}
|
|
119
|
+
// Check that the number is a finite integer (handles NaN, Infinity, and non-integers)
|
|
120
|
+
if (!Number.isFinite(index) || !Number.isInteger(index)) {
|
|
121
|
+
return fail(`${index}: collector index must be a finite integer.`);
|
|
122
|
+
}
|
|
123
|
+
if (index < 0 || index >= this._byIndex.length) {
|
|
124
|
+
return fail(`${index}: collector index out of range.`);
|
|
125
|
+
}
|
|
126
|
+
return succeed(this._byIndex[index]);
|
|
127
|
+
}
|
|
128
|
+
getOrAdd(keyOrItem, factory) {
|
|
129
|
+
const key = !this._isItem(keyOrItem) ? keyOrItem : keyOrItem.key;
|
|
130
|
+
const existing = this._byKey.get(key);
|
|
131
|
+
if (existing) {
|
|
132
|
+
return succeedWithDetail(existing, 'exists');
|
|
133
|
+
}
|
|
134
|
+
const itemResult = !this._isItem(keyOrItem)
|
|
135
|
+
? factory(keyOrItem, this._byIndex.length)
|
|
136
|
+
: succeed(keyOrItem);
|
|
137
|
+
if (itemResult.isFailure()) {
|
|
138
|
+
return failWithDetail(`${key}: ${itemResult.message}`, 'invalid-value');
|
|
139
|
+
}
|
|
140
|
+
const item = itemResult.value;
|
|
141
|
+
if (item.key !== key) {
|
|
142
|
+
return failWithDetail(`${key}: key mismatch - built item has ${key}`, 'invalid-key');
|
|
143
|
+
}
|
|
144
|
+
return itemResult.withFailureDetail('invalid-index').onSuccess((item) => {
|
|
145
|
+
return this.add(item);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* {@inheritdoc Collections.ResultMap.has}
|
|
150
|
+
*/
|
|
151
|
+
has(key) {
|
|
152
|
+
return this._byKey.has(key);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* {@inheritdoc Collections.ResultMap.keys}
|
|
156
|
+
*/
|
|
157
|
+
keys() {
|
|
158
|
+
return this._byKey.keys();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* {@inheritdoc Collections.ResultMap.values}
|
|
162
|
+
*/
|
|
163
|
+
values() {
|
|
164
|
+
return this._byKey.values();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* {@inheritdoc Collections.IReadOnlyCollector.valuesByIndex}
|
|
168
|
+
*/
|
|
169
|
+
valuesByIndex() {
|
|
170
|
+
return this._byIndex;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Gets a read-only version of this collector.
|
|
174
|
+
*/
|
|
175
|
+
toReadOnly() {
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Gets an iterator over the map entries.
|
|
180
|
+
* @returns An iterator over the map entries.
|
|
181
|
+
*/
|
|
182
|
+
[Symbol.iterator]() {
|
|
183
|
+
return this._byKey[Symbol.iterator]();
|
|
184
|
+
}
|
|
185
|
+
_isItem(keyOrItem) {
|
|
186
|
+
return typeof keyOrItem !== 'string';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=collector.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* A {@link Collections.Collector | Collector} wrapper which validates weakly-typed keys
|
|
24
|
+
* and values before calling the wrapped collector.
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export class CollectorValidator {
|
|
28
|
+
get map() {
|
|
29
|
+
return this._collector.toReadOnly();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Constructs a new {@link Collections.ConvertingCollectorValidator | ConvertingCollectorValidator}.
|
|
33
|
+
* @param params - Required parameters for constructing the collector validator.
|
|
34
|
+
*/
|
|
35
|
+
constructor(params) {
|
|
36
|
+
this._collector = params.collector;
|
|
37
|
+
this.converters = params.converters;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* {@inheritdoc Collections.Collector.add}
|
|
41
|
+
*/
|
|
42
|
+
add(item) {
|
|
43
|
+
return this._convertValue(item).onSuccess((i) => {
|
|
44
|
+
return this._collector.add(i);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* {@inheritdoc Collections.Collector.get}
|
|
49
|
+
*/
|
|
50
|
+
get(key) {
|
|
51
|
+
return this.converters.convertKey(key).onSuccess((k) => {
|
|
52
|
+
return this._collector.get(k);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
getOrAdd(keyOrItem, factoryCb) {
|
|
56
|
+
if (factoryCb === undefined) {
|
|
57
|
+
return this._convertValue(keyOrItem).onSuccess((item) => this._collector.getOrAdd(item));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return this.converters
|
|
61
|
+
.convertKey(keyOrItem)
|
|
62
|
+
.withDetail('invalid-key', 'success')
|
|
63
|
+
.onSuccess((key) => this._collector.getOrAdd(key, factoryCb));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* {@inheritdoc Collections.ResultMap.has}
|
|
68
|
+
*/
|
|
69
|
+
has(key) {
|
|
70
|
+
return this._collector.has(key);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* {@inheritdoc Collections.Collector.toReadOnly}
|
|
74
|
+
*/
|
|
75
|
+
toReadOnly() {
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Helper to convert a value, returning a {@link DetailedResult | DetailedResult}
|
|
80
|
+
* and formatting the error message.
|
|
81
|
+
* @param value - The value to convert.
|
|
82
|
+
* @returns {@link DetailedSuccess | DetailedSuccess} with the converted value
|
|
83
|
+
* and detail `success` if conversion is successful, or
|
|
84
|
+
* {@link DetailedFailure | DetailedFailure} with the error message and detail `invalid-value`
|
|
85
|
+
* if conversion fails.
|
|
86
|
+
*/
|
|
87
|
+
_convertValue(value) {
|
|
88
|
+
return this.converters
|
|
89
|
+
.convertValue(value)
|
|
90
|
+
.withErrorFormat((message) => `invalid item: ${message}`)
|
|
91
|
+
.withDetail('invalid-value', 'success');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=collectorValidator.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Erik Fortune
|
|
3
|
+
*
|
|
4
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
* in the Software without restriction, including without limitation the rights
|
|
7
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
* furnished to do so, subject to the following conditions:
|
|
10
|
+
*
|
|
11
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
* copies or substantial portions of the Software.
|
|
13
|
+
*
|
|
14
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
* SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
import { captureResult, failWithDetail, succeedWithDetail } from '../base';
|
|
23
|
+
import { Collector } from './collector';
|
|
24
|
+
/**
|
|
25
|
+
* A {@link Collector | collector} that collects {@link Collections.ICollectible | ICollectible} items,
|
|
26
|
+
* optionally converting them from a source representation to the target representation using a factory
|
|
27
|
+
* supplied at default or at the time of collection.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export class ConvertingCollector extends Collector {
|
|
31
|
+
/**
|
|
32
|
+
* Constructs a new {@link Collections.ConvertingCollector | ConvertingCollector}.
|
|
33
|
+
* @param params - Parameters for constructing the collector.
|
|
34
|
+
*/
|
|
35
|
+
constructor(params) {
|
|
36
|
+
var _a;
|
|
37
|
+
super();
|
|
38
|
+
this._factory = params.factory;
|
|
39
|
+
(_a = params.entries) === null || _a === void 0 ? void 0 : _a.forEach((entry) => {
|
|
40
|
+
this.getOrAdd(entry[0], entry[1]).orThrow();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new {@link Collections.ConvertingCollector | ConvertingCollector}.
|
|
45
|
+
* @param params - Required parameters for constructing the collector.
|
|
46
|
+
* @returns Returns {@link Success | Success} with the new collector if it is created, or {@link Failure | Failure}
|
|
47
|
+
* with an error if the collector cannot be created.
|
|
48
|
+
*/
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
static createConvertingCollector(params) {
|
|
51
|
+
return captureResult(() => new ConvertingCollector(params));
|
|
52
|
+
}
|
|
53
|
+
add(keyOrItem, itemOrCb) {
|
|
54
|
+
if (this._overloadIsItem(keyOrItem, itemOrCb)) {
|
|
55
|
+
return super.add(keyOrItem);
|
|
56
|
+
}
|
|
57
|
+
return this._buildItem(keyOrItem, itemOrCb)
|
|
58
|
+
.withFailureDetail('invalid-value')
|
|
59
|
+
.onSuccess((item) => {
|
|
60
|
+
return super.add(item);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
getOrAdd(keyOrItem, itemOrCb) {
|
|
64
|
+
if (this._overloadIsItem(keyOrItem, itemOrCb)) {
|
|
65
|
+
return super.getOrAdd(keyOrItem);
|
|
66
|
+
}
|
|
67
|
+
if (this.has(keyOrItem)) {
|
|
68
|
+
return this.get(keyOrItem);
|
|
69
|
+
}
|
|
70
|
+
return this._buildItem(keyOrItem, itemOrCb)
|
|
71
|
+
.withFailureDetail('invalid-value')
|
|
72
|
+
.onSuccess((item) => {
|
|
73
|
+
if (item.key !== keyOrItem) {
|
|
74
|
+
return failWithDetail(`${keyOrItem}: key mismatch - item has unexpected key ${item.key}`, 'invalid-key');
|
|
75
|
+
}
|
|
76
|
+
return succeedWithDetail(item, 'success');
|
|
77
|
+
})
|
|
78
|
+
.onSuccess((item) => {
|
|
79
|
+
return super.add(item);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Helper method for derived classes to determine if a supplied
|
|
84
|
+
* itemOrCb parameter is a factory callback.
|
|
85
|
+
* @param itemOrCb - Overloaded parameter is either `CollectibleKey<TITEM>` or
|
|
86
|
+
* a {@link Collections.CollectibleFactoryCallback | factory callback}.
|
|
87
|
+
* @returns Returns `true` if the parameter is a factory callback, `false` otherwise.
|
|
88
|
+
* @public
|
|
89
|
+
*/
|
|
90
|
+
_isFactoryCB(itemOrCb) {
|
|
91
|
+
return typeof itemOrCb === 'function';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Helper method for derived classes to determine if a supplied
|
|
95
|
+
* keyOrItem parameter is an item.
|
|
96
|
+
* @param keyOrItem - Overloaded parameter is either `CollectibleKey<TITEM>` or `TITEM`.
|
|
97
|
+
* @param itemOrCb - Overloaded parameter is either `TSRC`, a {@link Collections.CollectibleFactoryCallback | factory callback}
|
|
98
|
+
* or `undefined`.
|
|
99
|
+
* @returns Returns `true` if the parameter is an item, `false` otherwise.
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
_overloadIsItem(keyOrItem, itemOrCb) {
|
|
103
|
+
return itemOrCb === undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Helper method for derived classes to build an item from a key and a source representation using
|
|
107
|
+
* a default or supplied factory.
|
|
108
|
+
* @param key - The key of the item to build.
|
|
109
|
+
* @param itemOrCb - The source representation of the item to build, or a factory callback to create it.
|
|
110
|
+
* @returns Returns {@link Success | Success} with the item if it is built, or {@link Failure | Failure}
|
|
111
|
+
* with an error if the item cannot be built.
|
|
112
|
+
* @public
|
|
113
|
+
*/
|
|
114
|
+
_buildItem(key, itemOrCb) {
|
|
115
|
+
return this._isFactoryCB(itemOrCb) ? itemOrCb(key, this.size) : this._factory(key, this.size, itemOrCb);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=convertingCollector.js.map
|