@fgv/ts-utils 5.0.2 → 5.1.0-0

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 (104) hide show
  1. package/README.md +152 -84
  2. package/dist/index.js +2 -2
  3. package/dist/packlets/base/messageAggregator.js +6 -6
  4. package/dist/packlets/base/result.js +39 -39
  5. package/dist/packlets/base/utils.js +47 -0
  6. package/dist/packlets/collections/aggregatedResultMap.js +722 -0
  7. package/dist/packlets/collections/collector.js +9 -9
  8. package/dist/packlets/collections/collectorValidator.js +4 -4
  9. package/dist/packlets/collections/converters.js +39 -0
  10. package/dist/packlets/collections/convertingCollectorValidator.js +3 -3
  11. package/dist/packlets/collections/convertingResultMap.js +208 -0
  12. package/dist/packlets/collections/index.js +4 -0
  13. package/dist/packlets/collections/keyValueConverters.js +1 -4
  14. package/dist/packlets/collections/readOnlyConvertingResultMap.js +193 -0
  15. package/dist/packlets/collections/resultMapValidator.js +50 -6
  16. package/dist/packlets/collections/validatingConvertingResultMap.js +78 -0
  17. package/dist/packlets/conversion/advancedConverters.js +249 -0
  18. package/dist/packlets/conversion/baseConverter.js +23 -16
  19. package/dist/packlets/conversion/basicConverters.js +571 -0
  20. package/dist/packlets/conversion/converters.js +3 -617
  21. package/dist/packlets/conversion/defaultingConverter.js +21 -15
  22. package/dist/packlets/logging/bootLogger.js +122 -0
  23. package/dist/packlets/logging/index.js +1 -0
  24. package/dist/packlets/logging/logReporter.js +32 -1
  25. package/dist/packlets/logging/logger.js +25 -5
  26. package/dist/packlets/validation/classes.js +1 -0
  27. package/dist/packlets/validation/compositeId.js +60 -0
  28. package/dist/packlets/validation/field.js +1 -1
  29. package/dist/packlets/validation/genericValidator.js +24 -10
  30. package/dist/packlets/validation/object.js +3 -6
  31. package/dist/packlets/validation/validators.js +12 -0
  32. package/dist/ts-utils.d.ts +1633 -200
  33. package/dist/tsdoc-metadata.json +1 -1
  34. package/lib/index.d.ts +2 -2
  35. package/lib/index.js +2 -1
  36. package/lib/packlets/base/mapResults.d.ts +3 -3
  37. package/lib/packlets/base/messageAggregator.d.ts +6 -6
  38. package/lib/packlets/base/messageAggregator.js +6 -6
  39. package/lib/packlets/base/result.d.ts +74 -51
  40. package/lib/packlets/base/result.js +39 -39
  41. package/lib/packlets/base/utils.d.ts +42 -0
  42. package/lib/packlets/base/utils.js +52 -0
  43. package/lib/packlets/collections/aggregatedResultMap.d.ts +433 -0
  44. package/lib/packlets/collections/aggregatedResultMap.js +728 -0
  45. package/lib/packlets/collections/collector.d.ts +11 -10
  46. package/lib/packlets/collections/collector.js +9 -9
  47. package/lib/packlets/collections/collectorValidator.d.ts +12 -10
  48. package/lib/packlets/collections/collectorValidator.js +4 -4
  49. package/lib/packlets/collections/converters.d.ts +18 -0
  50. package/lib/packlets/collections/converters.js +42 -0
  51. package/lib/packlets/collections/convertingCollector.d.ts +4 -4
  52. package/lib/packlets/collections/convertingCollectorValidator.d.ts +10 -7
  53. package/lib/packlets/collections/convertingCollectorValidator.js +3 -3
  54. package/lib/packlets/collections/convertingResultMap.d.ts +176 -0
  55. package/lib/packlets/collections/convertingResultMap.js +213 -0
  56. package/lib/packlets/collections/index.d.ts +4 -0
  57. package/lib/packlets/collections/index.js +4 -0
  58. package/lib/packlets/collections/keyValueConverters.d.ts +1 -4
  59. package/lib/packlets/collections/keyValueConverters.js +1 -4
  60. package/lib/packlets/collections/readOnlyConvertingResultMap.d.ts +153 -0
  61. package/lib/packlets/collections/readOnlyConvertingResultMap.js +197 -0
  62. package/lib/packlets/collections/readonlyResultMap.d.ts +7 -7
  63. package/lib/packlets/collections/resultMap.d.ts +108 -2
  64. package/lib/packlets/collections/resultMapValidator.d.ts +45 -11
  65. package/lib/packlets/collections/resultMapValidator.js +52 -7
  66. package/lib/packlets/collections/validatingCollector.d.ts +5 -5
  67. package/lib/packlets/collections/validatingConvertingCollector.d.ts +3 -3
  68. package/lib/packlets/collections/validatingConvertingResultMap.d.ts +102 -0
  69. package/lib/packlets/collections/validatingConvertingResultMap.js +83 -0
  70. package/lib/packlets/collections/validatingResultMap.d.ts +1 -1
  71. package/lib/packlets/conversion/advancedConverters.d.ts +170 -0
  72. package/lib/packlets/conversion/advancedConverters.js +258 -0
  73. package/lib/packlets/conversion/baseConverter.d.ts +39 -23
  74. package/lib/packlets/conversion/baseConverter.js +23 -16
  75. package/lib/packlets/conversion/basicConverters.d.ts +470 -0
  76. package/lib/packlets/conversion/basicConverters.js +595 -0
  77. package/lib/packlets/conversion/converter.d.ts +7 -0
  78. package/lib/packlets/conversion/converters.d.ts +2 -535
  79. package/lib/packlets/conversion/converters.js +17 -639
  80. package/lib/packlets/conversion/defaultingConverter.d.ts +20 -16
  81. package/lib/packlets/conversion/defaultingConverter.js +21 -15
  82. package/lib/packlets/logging/bootLogger.d.ts +84 -0
  83. package/lib/packlets/logging/bootLogger.js +126 -0
  84. package/lib/packlets/logging/index.d.ts +1 -0
  85. package/lib/packlets/logging/index.js +1 -0
  86. package/lib/packlets/logging/logReporter.d.ts +18 -3
  87. package/lib/packlets/logging/logReporter.js +31 -0
  88. package/lib/packlets/logging/logger.d.ts +39 -6
  89. package/lib/packlets/logging/logger.js +26 -5
  90. package/lib/packlets/validation/classes.d.ts +1 -0
  91. package/lib/packlets/validation/classes.js +3 -1
  92. package/lib/packlets/validation/compositeId.d.ts +29 -0
  93. package/lib/packlets/validation/compositeId.js +64 -0
  94. package/lib/packlets/validation/field.d.ts +1 -1
  95. package/lib/packlets/validation/field.js +1 -1
  96. package/lib/packlets/validation/genericValidator.d.ts +15 -11
  97. package/lib/packlets/validation/genericValidator.js +24 -10
  98. package/lib/packlets/validation/object.d.ts +3 -6
  99. package/lib/packlets/validation/object.js +3 -6
  100. package/lib/packlets/validation/traits.d.ts +3 -3
  101. package/lib/packlets/validation/validator.d.ts +7 -0
  102. package/lib/packlets/validation/validators.d.ts +10 -0
  103. package/lib/packlets/validation/validators.js +13 -0
  104. package/package.json +14 -14
@@ -0,0 +1,728 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.AggregatedResultMap = exports.AggregatedResultMapBase = exports.AggregatedResultMapValidator = void 0;
25
+ const base_1 = require("../base");
26
+ const conversion_1 = require("../conversion");
27
+ const validation_1 = require("../validation");
28
+ const keyValueConverters_1 = require("./keyValueConverters");
29
+ const validatingResultMap_1 = require("./validatingResultMap");
30
+ /**
31
+ * A validator for weakly-typed access to an {@link AggregatedResultMap | aggregated result map}.
32
+ * @public
33
+ */
34
+ class AggregatedResultMapValidator {
35
+ /**
36
+ * Constructs a new {@link AggregatedResultMapValidator | aggregated result map validator}.
37
+ * @param map - The {@link AggregatedResultMap | aggregated result map} being validated.
38
+ * @param converters - The key-value converters for weakly-typed access.
39
+ */
40
+ constructor(map, converters) {
41
+ this._map = map;
42
+ this.converters = converters;
43
+ }
44
+ /** {@inheritDoc Collections.ResultMapValidator.map} */
45
+ get map() {
46
+ return this._map;
47
+ }
48
+ /** {@inheritDoc Collections.ResultMapValidator.get} */
49
+ get(key) {
50
+ return this.converters.convertKey(key).onSuccess((k) => {
51
+ return this._map.get(k);
52
+ });
53
+ }
54
+ /** {@inheritDoc Collections.ResultMapValidator.has} */
55
+ has(key) {
56
+ const result = this.converters.convertKey(key);
57
+ if (result.isFailure()) {
58
+ return false;
59
+ }
60
+ return this._map.has(result.value);
61
+ }
62
+ /** {@inheritDoc Collections.ResultMapValidator.add} */
63
+ add(key, value) {
64
+ return this.converters.convertEntry([key, value]).onSuccess(([vk, vv]) => {
65
+ return this._map.add(vk, vv);
66
+ });
67
+ }
68
+ /** {@inheritDoc Collections.ResultMapValidator.set} */
69
+ set(key, value) {
70
+ return this.converters.convertEntry([key, value]).onSuccess(([vk, vv]) => {
71
+ return this._map.set(vk, vv);
72
+ });
73
+ }
74
+ /** {@inheritDoc Collections.ResultMapValidator.update} */
75
+ update(key, value) {
76
+ return this.converters.convertEntry([key, value]).onSuccess(([vk, vv]) => {
77
+ return this._map.update(vk, vv);
78
+ });
79
+ }
80
+ /** {@inheritDoc Collections.ResultMapValidator.delete} */
81
+ delete(key) {
82
+ return this.converters.convertKey(key).onSuccess((k) => {
83
+ return this._map.delete(k);
84
+ });
85
+ }
86
+ }
87
+ exports.AggregatedResultMapValidator = AggregatedResultMapValidator;
88
+ /**
89
+ * Type guard to check if an {@link AggregatedResultMapEntry | aggregated result map entry} is mutable.
90
+ */
91
+ function isMutableEntry(entry) {
92
+ return entry.isMutable === true;
93
+ }
94
+ /**
95
+ * Base class for an aggregated result map that wraps a collection of {@link ValidatingResultMap} instances,
96
+ * keyed by collection ID. Items are accessed via composite IDs that combine the collection ID
97
+ * and item ID with a delimiter.
98
+ * @remarks Consumers should inherit from this class or use {@link AggregatedResultMap | AggregatedResultMap}
99
+ * for fully generic.
100
+ * @public
101
+ */
102
+ class AggregatedResultMapBase {
103
+ /**
104
+ * Protected constructor for derived classes.
105
+ * Use the {@link AggregatedResultMap.create | create static method} for safe construction with error handling.
106
+ * @param params - {@link IAggregatedResultMapConstructorParams | Parameters} for constructing the map.
107
+ * @throws If initialization fails (e.g., invalid collections).
108
+ * @public
109
+ */
110
+ constructor(params) {
111
+ var _a, _b, _c;
112
+ this._compositeIdValidator =
113
+ (_a = params.compositeIdValidator) !== null && _a !== void 0 ? _a : AggregatedResultMapBase._compositeIdValidator(params);
114
+ this._collectionIdConverter = params.collectionIdConverter;
115
+ this._itemIdConverter = params.itemIdConverter;
116
+ this._itemConverter = params.itemConverter;
117
+ this._metadataConverter = params.metadataConverter;
118
+ this._delimiter = (_b = params.separator) !== null && _b !== void 0 ? _b : '.';
119
+ // Build child key-value converters for item ID/value pairs
120
+ this._childKvConverters = new keyValueConverters_1.KeyValueConverters({
121
+ key: params.itemIdConverter,
122
+ value: params.itemConverter
123
+ });
124
+ // Build composite ID converter for parsing composite IDs
125
+ this._compositeIdConverter = conversion_1.Converters.compositeId(params.collectionIdConverter, this._delimiter, params.itemIdConverter);
126
+ // Build key-value converters for composite ID/item pairs
127
+ this._kvConverters = new keyValueConverters_1.KeyValueConverters({
128
+ key: this._compositeIdValidator,
129
+ value: params.itemConverter
130
+ });
131
+ // Build entry converter for collection entries
132
+ const entryConverter = AggregatedResultMapBase._entryConverter(params.collectionIdConverter, this._childKvConverters, params.metadataConverter);
133
+ // Build key-value converters for the collections map
134
+ this._collectionKvConverters = new keyValueConverters_1.KeyValueConverters({
135
+ key: params.collectionIdConverter,
136
+ value: entryConverter
137
+ });
138
+ // Convert all input collections to [id, entry] pairs for ValidatingResultMap
139
+ const initCollections = (_c = params.collections) !== null && _c !== void 0 ? _c : [];
140
+ const initEntries = initCollections.map((entry) => {
141
+ return [entry.id, entry];
142
+ });
143
+ // Build the inner ValidatingResultMap - throws on error
144
+ this._collections = new validatingResultMap_1.ValidatingResultMap({
145
+ converters: this._collectionKvConverters,
146
+ entries: initEntries
147
+ });
148
+ // Build the validator
149
+ this._validating = new AggregatedResultMapValidator(this, this._kvConverters);
150
+ }
151
+ // ==================== IReadOnlyValidatingResultMap implementation ====================
152
+ /**
153
+ * A validator for weakly-typed access to the map.
154
+ */
155
+ get validating() {
156
+ return this._validating;
157
+ }
158
+ /**
159
+ * The total number of items across all collections.
160
+ */
161
+ get size() {
162
+ let total = 0;
163
+ for (const entry of Array.from(this._collections.values())) {
164
+ total += entry.items.size;
165
+ }
166
+ return total;
167
+ }
168
+ /**
169
+ * Gets an item by its composite ID.
170
+ * @param key - The composite ID of the item.
171
+ * @returns `Success` with the item if found, `Failure` otherwise.
172
+ */
173
+ get(key) {
174
+ return this._splitAndLookup(key).onSuccess(({ entry, itemId }) => {
175
+ return entry.items.get(itemId);
176
+ });
177
+ }
178
+ /**
179
+ * Checks if an item exists by its composite ID.
180
+ * @param key - The composite ID of the item.
181
+ * @returns `true` if the item exists, `false` otherwise.
182
+ */
183
+ has(key) {
184
+ const result = this._splitAndLookup(key);
185
+ if (result.isFailure()) {
186
+ return false;
187
+ }
188
+ return result.value.entry.items.has(result.value.itemId);
189
+ }
190
+ /**
191
+ * Iterates over all entries in all collections.
192
+ */
193
+ *entries() {
194
+ for (const collectionEntry of Array.from(this._collections.values())) {
195
+ for (const [itemId, item] of Array.from(collectionEntry.items.entries())) {
196
+ // Validate the composite ID through the converter
197
+ const compositeId = this._compositeIdValidator
198
+ .convert(`${collectionEntry.id}${this._delimiter}${itemId}`)
199
+ .orThrow();
200
+ yield [compositeId, item];
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Iterates over all composite keys.
206
+ */
207
+ *keys() {
208
+ for (const [key] of Array.from(this.entries())) {
209
+ yield key;
210
+ }
211
+ }
212
+ /**
213
+ * Iterates over all values.
214
+ */
215
+ *values() {
216
+ for (const collectionEntry of Array.from(this._collections.values())) {
217
+ for (const value of Array.from(collectionEntry.items.values())) {
218
+ yield value;
219
+ }
220
+ }
221
+ }
222
+ /**
223
+ * Calls a callback for each entry.
224
+ * @param cb - The callback to call.
225
+ * @param thisArg - Optional `this` argument for the callback.
226
+ */
227
+ forEach(cb, thisArg) {
228
+ for (const [key, value] of Array.from(this.entries())) {
229
+ cb.call(thisArg, value, key, this);
230
+ }
231
+ }
232
+ /**
233
+ * Gets an iterator over the map entries.
234
+ */
235
+ [Symbol.iterator]() {
236
+ return this.entries();
237
+ }
238
+ // ==================== Mutating methods ====================
239
+ /**
240
+ * Sets an item by its composite ID. Creates the collection if it doesn't exist and is mutable.
241
+ * @param key - The composite ID of the item.
242
+ * @param value - The value to set.
243
+ * @returns `Success` with the value if set, `Failure` otherwise.
244
+ */
245
+ set(key, value) {
246
+ return this._splitCompositeId(key).onSuccess(({ collectionId, itemId }) => {
247
+ return this._getOrCreateMutableCollection(collectionId).onSuccess((collection) => {
248
+ return collection.set(itemId, value);
249
+ });
250
+ });
251
+ }
252
+ /**
253
+ * Adds an item by its composite ID. Fails if the item already exists.
254
+ * @param key - The composite ID of the item.
255
+ * @param value - The value to add.
256
+ * @returns `Success` with the value if added, `Failure` otherwise.
257
+ */
258
+ add(key, value) {
259
+ return this._splitCompositeId(key).onSuccess(({ collectionId, itemId }) => {
260
+ return this._getOrCreateMutableCollection(collectionId).onSuccess((collection) => {
261
+ return collection.add(itemId, value);
262
+ });
263
+ });
264
+ }
265
+ /**
266
+ * Updates an existing item by its composite ID. Fails if the item doesn't exist.
267
+ * @param key - The composite ID of the item.
268
+ * @param value - The new value.
269
+ * @returns `Success` with the value if updated, `Failure` otherwise.
270
+ */
271
+ update(key, value) {
272
+ return this._splitAndLookupMutable(key).onSuccess(({ entry, itemId }) => {
273
+ return entry.items.update(itemId, value);
274
+ });
275
+ }
276
+ /**
277
+ * Deletes an item by its composite ID.
278
+ * @param key - The composite ID of the item.
279
+ * @returns `Success` with the deleted value, `Failure` otherwise.
280
+ */
281
+ delete(key) {
282
+ return this._splitAndLookupMutable(key).onSuccess(({ entry, itemId }) => {
283
+ return entry.items.delete(itemId);
284
+ });
285
+ }
286
+ getOrAdd(key, valueOrFactory) {
287
+ return this._splitCompositeId(key).onSuccess(({ collectionId, itemId }) => {
288
+ return this._getOrCreateMutableCollection(collectionId).onSuccess((collection) => {
289
+ // Use type guard to check if valueOrFactory is a factory function
290
+ if (this._isFactory(valueOrFactory)) {
291
+ return collection.getOrAdd(itemId, () => valueOrFactory(key));
292
+ }
293
+ return collection.getOrAdd(itemId, valueOrFactory);
294
+ });
295
+ });
296
+ }
297
+ /**
298
+ * Clears all items from all mutable collections.
299
+ * Immutable collections are not affected.
300
+ */
301
+ clear() {
302
+ for (const entry of Array.from(this._collections.values())) {
303
+ if (isMutableEntry(entry)) {
304
+ entry.items.clear();
305
+ }
306
+ }
307
+ }
308
+ /**
309
+ * Returns a read-only view of this map.
310
+ */
311
+ toReadOnly() {
312
+ return this;
313
+ }
314
+ // ==================== Convenience methods for split IDs ====================
315
+ /**
316
+ * Composes a collection ID and item ID into a composite ID.
317
+ * @param collectionId - The collection ID.
318
+ * @param itemId - The item ID.
319
+ * @returns `Success` with the composite ID if valid, `Failure` otherwise.
320
+ */
321
+ composeId(collectionId, itemId) {
322
+ const rawId = `${collectionId}${this._delimiter}${itemId}`;
323
+ return this._compositeIdValidator.convert(rawId);
324
+ }
325
+ /**
326
+ * Adds an item using separate collection and item IDs.
327
+ * @param collectionId - The collection ID.
328
+ * @param itemId - The item ID.
329
+ * @param value - The value to add.
330
+ * @returns `Success` with the composite ID if added, `Failure` otherwise.
331
+ */
332
+ addToCollection(collectionId, itemId, value) {
333
+ return this._getOrCreateMutableCollection(collectionId).onSuccess((collection) => {
334
+ return collection.add(itemId, value).onSuccess(() => {
335
+ return this.composeId(collectionId, itemId).withDetail('invalid-key', 'added');
336
+ });
337
+ });
338
+ }
339
+ /**
340
+ * Sets an item using separate collection and item IDs.
341
+ * @param collectionId - The collection ID.
342
+ * @param itemId - The item ID.
343
+ * @param value - The value to set.
344
+ * @returns `Success` with the composite ID if set, `Failure` otherwise.
345
+ */
346
+ setInCollection(collectionId, itemId, value) {
347
+ return this._getOrCreateMutableCollection(collectionId).onSuccess((collection) => {
348
+ return collection.set(itemId, value).onSuccess((_, detail) => {
349
+ return this.composeId(collectionId, itemId).withDetail('invalid-key', detail);
350
+ });
351
+ });
352
+ }
353
+ /**
354
+ * Updates an item using separate collection and item IDs.
355
+ * @param collectionId - The collection ID.
356
+ * @param itemId - The item ID.
357
+ * @param value - The new value.
358
+ * @returns `Success` with the composite ID if updated, `Failure` otherwise.
359
+ */
360
+ updateInCollection(collectionId, itemId, value) {
361
+ return this._getMutableCollection(collectionId).onSuccess((collection) => {
362
+ return collection.update(itemId, value).onSuccess(() => {
363
+ return this.composeId(collectionId, itemId).withDetail('invalid-key', 'updated');
364
+ });
365
+ });
366
+ }
367
+ /**
368
+ * Deletes an item using separate collection and item IDs.
369
+ * @param collectionId - The collection ID.
370
+ * @param itemId - The item ID.
371
+ * @returns `Success` with the composite ID if deleted, `Failure` otherwise.
372
+ */
373
+ deleteFromCollection(collectionId, itemId) {
374
+ return this._getMutableCollection(collectionId).onSuccess((collection) => {
375
+ return collection.delete(itemId).onSuccess(() => {
376
+ return this.composeId(collectionId, itemId).withDetail('invalid-key', 'deleted');
377
+ });
378
+ });
379
+ }
380
+ // ==================== Collection-level methods ====================
381
+ /**
382
+ * Provides read-only access to the underlying collections map.
383
+ * Use `collections.has(id)` and `collections.get(id)` to check existence and retrieve collections.
384
+ */
385
+ get collections() {
386
+ return this._collections;
387
+ }
388
+ /**
389
+ * The number of collections.
390
+ */
391
+ get collectionCount() {
392
+ return this._collections.size;
393
+ }
394
+ /**
395
+ * Gets the metadata for a specific collection.
396
+ * @param collectionId - The collection ID.
397
+ * @returns `Success` with the metadata if found, `Failure` otherwise.
398
+ */
399
+ getCollectionMetadata(collectionId) {
400
+ return this._collections.get(collectionId).asResult.onSuccess((entry) => {
401
+ return (0, base_1.succeed)(entry.metadata);
402
+ });
403
+ }
404
+ /**
405
+ * Sets the metadata for a mutable collection.
406
+ * @param collectionId - The collection ID.
407
+ * @param metadata - The metadata to set.
408
+ * @returns `Success` if set, `Failure` otherwise.
409
+ */
410
+ setCollectionMetadata(collectionId, metadata) {
411
+ return this._collections.get(collectionId).asResult.onSuccess((entry) => {
412
+ if (!isMutableEntry(entry)) {
413
+ return (0, base_1.fail)(`Cannot modify metadata of immutable collection '${collectionId}'`);
414
+ }
415
+ entry.metadata = metadata;
416
+ return (0, base_1.succeed)(metadata);
417
+ });
418
+ }
419
+ /**
420
+ * Adds a new collection from a pre-built entry object.
421
+ * @param entry - The collection entry to add (JSON with items/entries, or pre-instantiated).
422
+ * @returns `Success` with the entry if added, `Failure` otherwise.
423
+ */
424
+ addCollectionEntry(entry) {
425
+ return AggregatedResultMapBase._entryConverter(this._collectionIdConverter, this._childKvConverters, this._metadataConverter)
426
+ .convert(entry)
427
+ .withFailureDetail('invalid-value')
428
+ .onSuccess((c) => this._collections.add(c.id, c));
429
+ }
430
+ /**
431
+ * Adds a new collection with the specified ID and optional initial entries.
432
+ * @param collectionId - The collection ID as a string (will be validated).
433
+ * @param items - Optional initial entries for the collection.
434
+ * @param options - Optional settings (isImmutable defaults to false).
435
+ * @returns `Success` with the validated collection ID if added, `Failure` otherwise.
436
+ */
437
+ addCollectionWithItems(collectionId, items, options) {
438
+ const isMutable = (options === null || options === void 0 ? void 0 : options.isImmutable) !== true;
439
+ const entries = items ? Array.from(items) : [];
440
+ return this._collectionIdConverter
441
+ .convert(collectionId)
442
+ .onSuccess((id) => this.addCollectionEntry({ isMutable, id, entries, metadata: options === null || options === void 0 ? void 0 : options.metadata }).asResult.onSuccess(() => base_1.Success.with(id)));
443
+ }
444
+ // ==================== Protected helpers ====================
445
+ /**
446
+ * Deletes a collection from the internal collections map.
447
+ * This is a protected method for use by derived classes that need to implement
448
+ * collection deletion functionality.
449
+ *
450
+ * @param collectionId - The collection ID to delete.
451
+ * @returns `Success` with the deleted entry if found, `Failure` otherwise.
452
+ * @remarks
453
+ * - Does NOT check if the collection is mutable - callers should validate this first.
454
+ * - Use `collections.get(id)` to check if a collection exists and its mutability before calling.
455
+ */
456
+ _deleteCollection(collectionId) {
457
+ // ValidatingResultMap extends ResultMap which has delete method
458
+ return this._collections.delete(collectionId);
459
+ }
460
+ // ==================== Private helpers ====================
461
+ /**
462
+ * Type guard to check if a value is a factory function.
463
+ */
464
+ _isFactory(valueOrFactory) {
465
+ return typeof valueOrFactory === 'function';
466
+ }
467
+ _splitCompositeId(key) {
468
+ return this._compositeIdConverter.convert(key).withDetail('invalid-key', 'success');
469
+ }
470
+ _splitAndLookup(key) {
471
+ return this._splitCompositeId(key).onSuccess(({ collectionId, itemId }) => {
472
+ return this._collections.get(collectionId).onSuccess((entry) => {
473
+ return (0, base_1.succeedWithDetail)({ entry, itemId }, 'success');
474
+ });
475
+ });
476
+ }
477
+ _splitAndLookupMutable(key) {
478
+ return this._splitAndLookup(key).onSuccess(({ entry, itemId }) => {
479
+ if (!isMutableEntry(entry)) {
480
+ return (0, base_1.failWithDetail)(`Cannot modify immutable collection '${entry.id}'`, 'failure');
481
+ }
482
+ return (0, base_1.succeedWithDetail)({ entry, itemId }, 'success');
483
+ });
484
+ }
485
+ /**
486
+ * Gets an existing mutable collection by ID.
487
+ * Returns failure if the collection doesn't exist or is immutable.
488
+ */
489
+ _getMutableCollection(collectionId) {
490
+ return this._collections.get(collectionId).onSuccess((entry) => {
491
+ if (!isMutableEntry(entry)) {
492
+ return (0, base_1.failWithDetail)(`Cannot modify immutable collection '${collectionId}'`, 'failure');
493
+ }
494
+ return (0, base_1.succeedWithDetail)(entry.items, 'exists');
495
+ });
496
+ }
497
+ /**
498
+ * Gets an existing mutable collection or creates a new one.
499
+ */
500
+ _getOrCreateMutableCollection(collectionId) {
501
+ const existing = this._collections.get(collectionId);
502
+ if (existing.isSuccess()) {
503
+ if (!isMutableEntry(existing.value)) {
504
+ return (0, base_1.failWithDetail)(`Cannot modify immutable collection '${collectionId}'`, 'failure');
505
+ }
506
+ return (0, base_1.succeedWithDetail)(existing.value.items, 'exists');
507
+ }
508
+ // Create a new mutable collection
509
+ const newItems = new validatingResultMap_1.ValidatingResultMap({
510
+ converters: this._childKvConverters
511
+ });
512
+ const newEntry = {
513
+ isMutable: true,
514
+ id: collectionId,
515
+ items: newItems
516
+ };
517
+ return this._collections.add(collectionId, newEntry).onSuccess(() => {
518
+ return (0, base_1.succeedWithDetail)(newItems, 'added');
519
+ });
520
+ }
521
+ // ==================== Static converter factories ====================
522
+ /**
523
+ * Constructs a composite id validator using other converters/validators and separator supplied in params.
524
+ * @param params - Constructor params.
525
+ * @returns The composite ID validator.
526
+ */
527
+ static _compositeIdValidator(params) {
528
+ var _a;
529
+ return validation_1.Validators.compositeId({
530
+ collectionId: conversion_1.Converters.asValidator(params.collectionIdConverter),
531
+ separator: (_a = params.separator) !== null && _a !== void 0 ? _a : '.',
532
+ itemId: conversion_1.Converters.asValidator(params.itemIdConverter)
533
+ });
534
+ }
535
+ /**
536
+ * Converts metadata from source, failing fast if metadata is present but no converter is configured.
537
+ * @param sourceMetadata - The metadata from the source (may be undefined).
538
+ * @param metadataConverter - Optional converter for metadata.
539
+ * @returns Success with converted metadata (or undefined), or Failure if metadata is present but disallowed.
540
+ */
541
+ static _convertMetadata(sourceMetadata, metadataConverter) {
542
+ if (sourceMetadata === undefined) {
543
+ return (0, base_1.succeed)(undefined);
544
+ }
545
+ if (!metadataConverter) {
546
+ return (0, base_1.fail)('metadata is not allowed when no metadataConverter is configured');
547
+ }
548
+ return metadataConverter.convert(sourceMetadata);
549
+ }
550
+ /**
551
+ * Extracts metadata from a source object if present.
552
+ */
553
+ static _extractSourceMetadata(from) {
554
+ return typeof from === 'object' && from !== null && 'metadata' in from ? from.metadata : undefined;
555
+ }
556
+ /**
557
+ * Creates a converter for pre-instantiated mutable entries.
558
+ * These are entries that already have a ValidatingResultMap as their items property.
559
+ */
560
+ static _instantiatedMutableConverter(collectionIdConverter, metadataConverter) {
561
+ const baseConverter = conversion_1.Converters.strictObject({
562
+ isMutable: conversion_1.Converters.literal(true),
563
+ id: collectionIdConverter,
564
+ items: conversion_1.Converters.isA('ValidatingResultMap', (t) => t instanceof validatingResultMap_1.ValidatingResultMap)
565
+ });
566
+ return conversion_1.Converters.generic((from) => {
567
+ return baseConverter.convert(from).onSuccess((base) => {
568
+ const sourceMetadata = AggregatedResultMapBase._extractSourceMetadata(from);
569
+ return AggregatedResultMapBase._convertMetadata(sourceMetadata, metadataConverter).onSuccess((metadata) => {
570
+ const entry = {
571
+ isMutable: true,
572
+ id: base.id,
573
+ items: base.items,
574
+ metadata
575
+ };
576
+ return (0, base_1.succeed)(entry);
577
+ });
578
+ });
579
+ });
580
+ }
581
+ /**
582
+ * Creates a converter for pre-instantiated read-only entries.
583
+ * These are entries that already have an IReadOnlyValidatingResultMap as their items property.
584
+ */
585
+ static _instantiatedReadonlyConverter(collectionIdConverter, metadataConverter) {
586
+ const baseConverter = conversion_1.Converters.strictObject({
587
+ isMutable: conversion_1.Converters.literal(false),
588
+ id: collectionIdConverter,
589
+ items: conversion_1.Converters.isA('IReadOnlyValidatingResultMap', (t) => t instanceof validatingResultMap_1.ValidatingResultMap)
590
+ });
591
+ return conversion_1.Converters.generic((from) => {
592
+ return baseConverter.convert(from).onSuccess((base) => {
593
+ const sourceMetadata = AggregatedResultMapBase._extractSourceMetadata(from);
594
+ return AggregatedResultMapBase._convertMetadata(sourceMetadata, metadataConverter).onSuccess((metadata) => {
595
+ const entry = {
596
+ isMutable: false,
597
+ id: base.id,
598
+ items: base.items,
599
+ metadata
600
+ };
601
+ return (0, base_1.succeed)(entry);
602
+ });
603
+ });
604
+ });
605
+ }
606
+ /**
607
+ * Creates a converter for JSON entries with an entries array.
608
+ * Converts the entries array to a ValidatingResultMap.
609
+ */
610
+ static _jsonWithEntriesConverter(collectionIdConverter, childKvConverters, metadataConverter) {
611
+ const entriesArrayConverter = conversion_1.Converters.isA('entries array', (t) => Array.isArray(t) && t.every((item) => Array.isArray(item) && item.length === 2));
612
+ const baseConverter = conversion_1.Converters.object({
613
+ isMutable: conversion_1.Converters.boolean,
614
+ id: collectionIdConverter,
615
+ entries: entriesArrayConverter
616
+ });
617
+ return conversion_1.Converters.generic((from) => {
618
+ return baseConverter.convert(from).onSuccess((json) => {
619
+ const sourceMetadata = AggregatedResultMapBase._extractSourceMetadata(from);
620
+ return childKvConverters.convertEntries(json.entries).onSuccess((convertedEntries) => {
621
+ const items = new validatingResultMap_1.ValidatingResultMap({
622
+ converters: childKvConverters,
623
+ entries: convertedEntries
624
+ });
625
+ return AggregatedResultMapBase._convertMetadata(sourceMetadata, metadataConverter).onSuccess((metadata) => {
626
+ if (json.isMutable) {
627
+ return (0, base_1.succeed)({
628
+ isMutable: true,
629
+ id: json.id,
630
+ items,
631
+ metadata
632
+ });
633
+ }
634
+ return (0, base_1.succeed)({
635
+ isMutable: false,
636
+ id: json.id,
637
+ items: items.toReadOnly(),
638
+ metadata
639
+ });
640
+ });
641
+ });
642
+ });
643
+ });
644
+ }
645
+ /**
646
+ * Creates a converter for JSON entries with an items object.
647
+ * Converts the items object to a ValidatingResultMap.
648
+ */
649
+ static _jsonWithItemsConverter(collectionIdConverter, childKvConverters, metadataConverter) {
650
+ const baseConverter = conversion_1.Converters.object({
651
+ isMutable: conversion_1.Converters.boolean,
652
+ id: collectionIdConverter,
653
+ items: conversion_1.Converters.generic((from) => {
654
+ if (typeof from === 'object' && from !== null && !Array.isArray(from)) {
655
+ return (0, base_1.succeed)(from);
656
+ }
657
+ return (0, base_1.fail)('expected an object');
658
+ })
659
+ });
660
+ return conversion_1.Converters.generic((from) => {
661
+ return baseConverter.convert(from).onSuccess((json) => {
662
+ const sourceMetadata = AggregatedResultMapBase._extractSourceMetadata(from);
663
+ const entries = Object.entries(json.items);
664
+ return childKvConverters.convertEntries(entries).onSuccess((convertedEntries) => {
665
+ const items = new validatingResultMap_1.ValidatingResultMap({
666
+ converters: childKvConverters,
667
+ entries: convertedEntries
668
+ });
669
+ return AggregatedResultMapBase._convertMetadata(sourceMetadata, metadataConverter).onSuccess((metadata) => {
670
+ if (json.isMutable) {
671
+ return (0, base_1.succeed)({
672
+ isMutable: true,
673
+ id: json.id,
674
+ items,
675
+ metadata
676
+ });
677
+ }
678
+ return (0, base_1.succeed)({
679
+ isMutable: false,
680
+ id: json.id,
681
+ items: items.toReadOnly(),
682
+ metadata
683
+ });
684
+ });
685
+ });
686
+ });
687
+ });
688
+ }
689
+ /**
690
+ * Creates a unified entry converter that handles all entry formats.
691
+ * Tries each format in order: instantiated mutable, instantiated readonly,
692
+ * JSON with entries array, JSON with items object.
693
+ */
694
+ static _entryConverter(collectionIdConverter, childKvConverters, metadataConverter) {
695
+ return conversion_1.Converters.oneOf([
696
+ AggregatedResultMapBase._instantiatedMutableConverter(collectionIdConverter, metadataConverter),
697
+ AggregatedResultMapBase._instantiatedReadonlyConverter(collectionIdConverter, metadataConverter),
698
+ AggregatedResultMapBase._jsonWithEntriesConverter(collectionIdConverter, childKvConverters, metadataConverter),
699
+ AggregatedResultMapBase._jsonWithItemsConverter(collectionIdConverter, childKvConverters, metadataConverter)
700
+ ]);
701
+ }
702
+ }
703
+ exports.AggregatedResultMapBase = AggregatedResultMapBase;
704
+ /**
705
+ * An aggregated result map that wraps a collection of {@link ValidatingResultMap | ValidatingResultMap} instances,
706
+ * keyed by collection ID. Items are accessed via composite IDs that combine the collection ID
707
+ * and item ID with a delimiter.
708
+ * @public
709
+ */
710
+ class AggregatedResultMap extends AggregatedResultMapBase {
711
+ /**
712
+ * Constructs a new {@link AggregatedResultMap | AggregatedResultMap}.
713
+ * @param params -
714
+ */
715
+ constructor(params) {
716
+ super(params);
717
+ }
718
+ /**
719
+ * Creates a new {@link AggregatedResultMap | AggregatedResultMap}.
720
+ * @param params - Parameters for constructing the map.
721
+ * @returns `Success` with the new map if successful, `Failure` otherwise.
722
+ */
723
+ static create(params) {
724
+ return (0, base_1.captureResult)(() => new AggregatedResultMap(params));
725
+ }
726
+ }
727
+ exports.AggregatedResultMap = AggregatedResultMap;
728
+ //# sourceMappingURL=aggregatedResultMap.js.map