@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.
Files changed (89) hide show
  1. package/dist/index.js +32 -0
  2. package/dist/packlets/base/brand.js +23 -0
  3. package/dist/packlets/base/index.js +28 -0
  4. package/dist/packlets/base/mapResults.js +217 -0
  5. package/dist/packlets/base/messageAggregator.js +110 -0
  6. package/dist/packlets/base/normalize.js +132 -0
  7. package/dist/packlets/base/result.js +611 -0
  8. package/dist/packlets/base/utils.js +185 -0
  9. package/dist/packlets/collections/collectible.js +82 -0
  10. package/dist/packlets/collections/collector.js +189 -0
  11. package/dist/packlets/collections/collectorValidator.js +94 -0
  12. package/dist/packlets/collections/common.js +23 -0
  13. package/dist/packlets/collections/convertingCollector.js +118 -0
  14. package/dist/packlets/collections/convertingCollectorValidator.js +95 -0
  15. package/dist/packlets/collections/index.js +37 -0
  16. package/dist/packlets/collections/keyValueConverters.js +100 -0
  17. package/dist/packlets/collections/readonlyResultMap.js +23 -0
  18. package/dist/packlets/collections/resultMap.js +214 -0
  19. package/dist/packlets/collections/resultMapValidator.js +122 -0
  20. package/dist/packlets/collections/utils.js +31 -0
  21. package/dist/packlets/collections/validatingCollector.js +63 -0
  22. package/dist/packlets/collections/validatingConvertingCollector.js +64 -0
  23. package/dist/packlets/collections/validatingResultMap.js +57 -0
  24. package/dist/packlets/conversion/baseConverter.js +242 -0
  25. package/dist/packlets/conversion/converter.js +2 -0
  26. package/dist/packlets/conversion/converters.js +637 -0
  27. package/dist/packlets/conversion/defaultingConverter.js +149 -0
  28. package/dist/packlets/conversion/index.js +29 -0
  29. package/dist/packlets/conversion/objectConverter.js +141 -0
  30. package/dist/packlets/conversion/stringConverter.js +93 -0
  31. package/dist/packlets/hash/crcNormalizer.js +114 -0
  32. package/dist/packlets/hash/hashingNormalizer.js +92 -0
  33. package/dist/packlets/hash/index.js +24 -0
  34. package/dist/packlets/logging/index.js +24 -0
  35. package/dist/packlets/logging/logReporter.js +114 -0
  36. package/dist/packlets/logging/logger.js +258 -0
  37. package/dist/packlets/validation/array.js +64 -0
  38. package/dist/packlets/validation/boolean.js +52 -0
  39. package/dist/packlets/validation/classes.js +31 -0
  40. package/dist/packlets/validation/common.js +23 -0
  41. package/dist/packlets/validation/field.js +67 -0
  42. package/dist/packlets/validation/genericValidator.js +160 -0
  43. package/dist/packlets/validation/index.js +29 -0
  44. package/dist/packlets/validation/number.js +52 -0
  45. package/dist/packlets/validation/object.js +134 -0
  46. package/dist/packlets/validation/oneOf.js +58 -0
  47. package/dist/packlets/validation/string.js +52 -0
  48. package/dist/packlets/validation/traits.js +56 -0
  49. package/dist/packlets/validation/typeGuard.js +59 -0
  50. package/dist/packlets/validation/validator.js +23 -0
  51. package/dist/packlets/validation/validatorBase.js +37 -0
  52. package/dist/packlets/validation/validators.js +191 -0
  53. package/dist/test/helpers/jest/helpers/fsHelpers.js +139 -0
  54. package/dist/test/helpers/jest/helpers/index.js +2 -0
  55. package/dist/test/helpers/jest/index.js +17 -0
  56. package/dist/test/helpers/jest/matchers/index.js +14 -0
  57. package/dist/test/helpers/jest/matchers/toFail/index.js +23 -0
  58. package/dist/test/helpers/jest/matchers/toFail/predicate.js +5 -0
  59. package/dist/test/helpers/jest/matchers/toFailTest/index.js +28 -0
  60. package/dist/test/helpers/jest/matchers/toFailTest/predicate.js +8 -0
  61. package/dist/test/helpers/jest/matchers/toFailTestAndMatchSnapshot/index.js +24 -0
  62. package/dist/test/helpers/jest/matchers/toFailTestAndMatchSnapshot/predicate.js +11 -0
  63. package/dist/test/helpers/jest/matchers/toFailTestWith/index.js +29 -0
  64. package/dist/test/helpers/jest/matchers/toFailTestWith/predicate.js +26 -0
  65. package/dist/test/helpers/jest/matchers/toFailWith/index.js +27 -0
  66. package/dist/test/helpers/jest/matchers/toFailWith/predicate.js +16 -0
  67. package/dist/test/helpers/jest/matchers/toFailWithDetail/index.js +27 -0
  68. package/dist/test/helpers/jest/matchers/toFailWithDetail/predicate.js +22 -0
  69. package/dist/test/helpers/jest/matchers/toSucceed/index.js +23 -0
  70. package/dist/test/helpers/jest/matchers/toSucceed/predicate.js +5 -0
  71. package/dist/test/helpers/jest/matchers/toSucceedAndMatchInlineSnapshot/index.js +24 -0
  72. package/dist/test/helpers/jest/matchers/toSucceedAndMatchSnapshot/index.js +24 -0
  73. package/dist/test/helpers/jest/matchers/toSucceedAndSatisfy/index.js +53 -0
  74. package/dist/test/helpers/jest/matchers/toSucceedAndSatisfy/predicate.js +17 -0
  75. package/dist/test/helpers/jest/matchers/toSucceedWith/index.js +27 -0
  76. package/dist/test/helpers/jest/matchers/toSucceedWith/predicate.js +12 -0
  77. package/dist/test/helpers/jest/matchers/toSucceedWithDetail/index.js +27 -0
  78. package/dist/test/helpers/jest/matchers/toSucceedWithDetail/predicate.js +17 -0
  79. package/dist/test/helpers/jest/resolvers/cli.js +10 -0
  80. package/dist/test/helpers/jest/resolvers/ide.js +10 -0
  81. package/dist/test/helpers/jest/ts-utils.js +2 -0
  82. package/dist/test/helpers/jest/types/index.js +3 -0
  83. package/dist/test/helpers/jest/utils/matcherHelpers.js +47 -0
  84. package/dist/test/helpers/jest/utils/snapshotResolver.js +11 -0
  85. package/dist/test/unit/collections/helpers.js +106 -0
  86. package/dist/test/unit/hashTextEncodeCompat.js +23 -0
  87. package/dist/tsdoc-metadata.json +1 -1
  88. package/lib/packlets/hash/crcNormalizer.js +42 -2
  89. 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