@fgv/ts-utils 5.0.2-0 → 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 (105) hide show
  1. package/CHANGELOG.json +21 -0
  2. package/README.md +152 -84
  3. package/dist/index.js +2 -2
  4. package/dist/packlets/base/messageAggregator.js +6 -6
  5. package/dist/packlets/base/result.js +39 -39
  6. package/dist/packlets/base/utils.js +47 -0
  7. package/dist/packlets/collections/aggregatedResultMap.js +722 -0
  8. package/dist/packlets/collections/collector.js +9 -9
  9. package/dist/packlets/collections/collectorValidator.js +4 -4
  10. package/dist/packlets/collections/converters.js +39 -0
  11. package/dist/packlets/collections/convertingCollectorValidator.js +3 -3
  12. package/dist/packlets/collections/convertingResultMap.js +208 -0
  13. package/dist/packlets/collections/index.js +4 -0
  14. package/dist/packlets/collections/keyValueConverters.js +1 -4
  15. package/dist/packlets/collections/readOnlyConvertingResultMap.js +193 -0
  16. package/dist/packlets/collections/resultMapValidator.js +50 -6
  17. package/dist/packlets/collections/validatingConvertingResultMap.js +78 -0
  18. package/dist/packlets/conversion/advancedConverters.js +249 -0
  19. package/dist/packlets/conversion/baseConverter.js +23 -16
  20. package/dist/packlets/conversion/basicConverters.js +571 -0
  21. package/dist/packlets/conversion/converters.js +3 -617
  22. package/dist/packlets/conversion/defaultingConverter.js +21 -15
  23. package/dist/packlets/logging/bootLogger.js +122 -0
  24. package/dist/packlets/logging/index.js +1 -0
  25. package/dist/packlets/logging/logReporter.js +32 -1
  26. package/dist/packlets/logging/logger.js +25 -5
  27. package/dist/packlets/validation/classes.js +1 -0
  28. package/dist/packlets/validation/compositeId.js +60 -0
  29. package/dist/packlets/validation/field.js +1 -1
  30. package/dist/packlets/validation/genericValidator.js +24 -10
  31. package/dist/packlets/validation/object.js +3 -6
  32. package/dist/packlets/validation/validators.js +12 -0
  33. package/dist/ts-utils.d.ts +1633 -200
  34. package/dist/tsdoc-metadata.json +1 -1
  35. package/lib/index.d.ts +2 -2
  36. package/lib/index.js +2 -1
  37. package/lib/packlets/base/mapResults.d.ts +3 -3
  38. package/lib/packlets/base/messageAggregator.d.ts +6 -6
  39. package/lib/packlets/base/messageAggregator.js +6 -6
  40. package/lib/packlets/base/result.d.ts +74 -51
  41. package/lib/packlets/base/result.js +39 -39
  42. package/lib/packlets/base/utils.d.ts +42 -0
  43. package/lib/packlets/base/utils.js +52 -0
  44. package/lib/packlets/collections/aggregatedResultMap.d.ts +433 -0
  45. package/lib/packlets/collections/aggregatedResultMap.js +728 -0
  46. package/lib/packlets/collections/collector.d.ts +11 -10
  47. package/lib/packlets/collections/collector.js +9 -9
  48. package/lib/packlets/collections/collectorValidator.d.ts +12 -10
  49. package/lib/packlets/collections/collectorValidator.js +4 -4
  50. package/lib/packlets/collections/converters.d.ts +18 -0
  51. package/lib/packlets/collections/converters.js +42 -0
  52. package/lib/packlets/collections/convertingCollector.d.ts +4 -4
  53. package/lib/packlets/collections/convertingCollectorValidator.d.ts +10 -7
  54. package/lib/packlets/collections/convertingCollectorValidator.js +3 -3
  55. package/lib/packlets/collections/convertingResultMap.d.ts +176 -0
  56. package/lib/packlets/collections/convertingResultMap.js +213 -0
  57. package/lib/packlets/collections/index.d.ts +4 -0
  58. package/lib/packlets/collections/index.js +4 -0
  59. package/lib/packlets/collections/keyValueConverters.d.ts +1 -4
  60. package/lib/packlets/collections/keyValueConverters.js +1 -4
  61. package/lib/packlets/collections/readOnlyConvertingResultMap.d.ts +153 -0
  62. package/lib/packlets/collections/readOnlyConvertingResultMap.js +197 -0
  63. package/lib/packlets/collections/readonlyResultMap.d.ts +7 -7
  64. package/lib/packlets/collections/resultMap.d.ts +108 -2
  65. package/lib/packlets/collections/resultMapValidator.d.ts +45 -11
  66. package/lib/packlets/collections/resultMapValidator.js +52 -7
  67. package/lib/packlets/collections/validatingCollector.d.ts +5 -5
  68. package/lib/packlets/collections/validatingConvertingCollector.d.ts +3 -3
  69. package/lib/packlets/collections/validatingConvertingResultMap.d.ts +102 -0
  70. package/lib/packlets/collections/validatingConvertingResultMap.js +83 -0
  71. package/lib/packlets/collections/validatingResultMap.d.ts +1 -1
  72. package/lib/packlets/conversion/advancedConverters.d.ts +170 -0
  73. package/lib/packlets/conversion/advancedConverters.js +258 -0
  74. package/lib/packlets/conversion/baseConverter.d.ts +39 -23
  75. package/lib/packlets/conversion/baseConverter.js +23 -16
  76. package/lib/packlets/conversion/basicConverters.d.ts +470 -0
  77. package/lib/packlets/conversion/basicConverters.js +595 -0
  78. package/lib/packlets/conversion/converter.d.ts +7 -0
  79. package/lib/packlets/conversion/converters.d.ts +2 -535
  80. package/lib/packlets/conversion/converters.js +17 -639
  81. package/lib/packlets/conversion/defaultingConverter.d.ts +20 -16
  82. package/lib/packlets/conversion/defaultingConverter.js +21 -15
  83. package/lib/packlets/logging/bootLogger.d.ts +84 -0
  84. package/lib/packlets/logging/bootLogger.js +126 -0
  85. package/lib/packlets/logging/index.d.ts +1 -0
  86. package/lib/packlets/logging/index.js +1 -0
  87. package/lib/packlets/logging/logReporter.d.ts +18 -3
  88. package/lib/packlets/logging/logReporter.js +31 -0
  89. package/lib/packlets/logging/logger.d.ts +39 -6
  90. package/lib/packlets/logging/logger.js +26 -5
  91. package/lib/packlets/validation/classes.d.ts +1 -0
  92. package/lib/packlets/validation/classes.js +3 -1
  93. package/lib/packlets/validation/compositeId.d.ts +29 -0
  94. package/lib/packlets/validation/compositeId.js +64 -0
  95. package/lib/packlets/validation/field.d.ts +1 -1
  96. package/lib/packlets/validation/field.js +1 -1
  97. package/lib/packlets/validation/genericValidator.d.ts +15 -11
  98. package/lib/packlets/validation/genericValidator.js +24 -10
  99. package/lib/packlets/validation/object.d.ts +3 -6
  100. package/lib/packlets/validation/object.js +3 -6
  101. package/lib/packlets/validation/traits.d.ts +3 -3
  102. package/lib/packlets/validation/validator.d.ts +7 -0
  103. package/lib/packlets/validation/validators.d.ts +10 -0
  104. package/lib/packlets/validation/validators.js +13 -0
  105. package/package.json +14 -14
@@ -27,13 +27,13 @@ import { succeed } from '../base';
27
27
  */
28
28
  export class GenericDefaultingConverter {
29
29
  /**
30
- * {@inheritdoc Converter.isOptional}
30
+ * {@inheritDoc Converter.isOptional}
31
31
  */
32
32
  get isOptional() {
33
33
  return this._converter.isOptional;
34
34
  }
35
35
  /**
36
- * {@inheritdoc Converter.isOptional}
36
+ * {@inheritDoc Converter.isOptional}
37
37
  */
38
38
  get brand() {
39
39
  return this._converter.brand;
@@ -48,13 +48,13 @@ export class GenericDefaultingConverter {
48
48
  this._converter = converter;
49
49
  }
50
50
  /**
51
- * {@inheritdoc Converter.convert}
51
+ * {@inheritDoc Converter.convert}
52
52
  */
53
53
  convert(from, ctx) {
54
54
  return this._applyDefault(this._converter.convert(from, ctx));
55
55
  }
56
56
  /**
57
- * {@inheritdoc Converter.convertOptional}
57
+ * {@inheritDoc Converter.convertOptional}
58
58
  */
59
59
  convertOptional(from, context, onError) {
60
60
  const converted = this._converter.convertOptional(from, context, onError);
@@ -64,7 +64,7 @@ export class GenericDefaultingConverter {
64
64
  return converted;
65
65
  }
66
66
  /**
67
- * {@inheritdoc Converter.optional}
67
+ * {@inheritDoc Converter.optional}
68
68
  */
69
69
  optional(onError) {
70
70
  // need to let the inner converter do its optional thing first or our default
@@ -72,61 +72,61 @@ export class GenericDefaultingConverter {
72
72
  return this._converter.optional(onError).withAction((result) => this._applyDefault(result));
73
73
  }
74
74
  /**
75
- * {@inheritdoc Converter.map}
75
+ * {@inheritDoc Converter.map}
76
76
  */
77
77
  map(mapper) {
78
78
  return this._converter.withAction((result) => this._applyDefault(result)).map(mapper);
79
79
  }
80
80
  /**
81
- * {@inheritdoc Converter.mapConvert}
81
+ * {@inheritDoc Converter.mapConvert}
82
82
  */
83
83
  mapConvert(mapConverter) {
84
84
  return this._converter.withAction((result) => this._applyDefault(result)).mapConvert(mapConverter);
85
85
  }
86
86
  /**
87
- * {@inheritdoc Converter.mapItems}
87
+ * {@inheritDoc Converter.mapItems}
88
88
  */
89
89
  mapItems(mapper) {
90
90
  return this._converter.withAction((result) => this._applyDefault(result)).mapItems(mapper);
91
91
  }
92
92
  /**
93
- * {@inheritdoc Converter.mapConvertItems}
93
+ * {@inheritDoc Converter.mapConvertItems}
94
94
  */
95
95
  mapConvertItems(mapConverter) {
96
96
  return this._converter.withAction((result) => this._applyDefault(result)).mapConvertItems(mapConverter);
97
97
  }
98
98
  /**
99
- * {@inheritdoc Converter.withAction}
99
+ * {@inheritDoc Converter.withAction}
100
100
  */
101
101
  withAction(action) {
102
102
  return this._converter.withAction((result) => this._applyDefault(result)).withAction(action);
103
103
  }
104
104
  /**
105
- * {@inheritdoc Converter.withTypeGuard}
105
+ * {@inheritDoc Converter.withTypeGuard}
106
106
  */
107
107
  withTypeGuard(guard, message) {
108
108
  return this._converter.withAction((result) => this._applyDefault(result)).withTypeGuard(guard);
109
109
  }
110
110
  /**
111
- * {@inheritdoc Converter.withItemTypeGuard}
111
+ * {@inheritDoc Converter.withItemTypeGuard}
112
112
  */
113
113
  withItemTypeGuard(guard, message) {
114
114
  return this._converter.withAction((result) => this._applyDefault(result)).withItemTypeGuard(guard);
115
115
  }
116
116
  /**
117
- * {@inheritdoc Converter.withConstraint}
117
+ * {@inheritDoc Converter.withConstraint}
118
118
  */
119
119
  withConstraint(constraint, options) {
120
120
  return this._converter.withAction((result) => this._applyDefault(result)).withConstraint(constraint);
121
121
  }
122
122
  /**
123
- * {@inheritdoc Converter.withBrand}
123
+ * {@inheritDoc Converter.withBrand}
124
124
  */
125
125
  withBrand(brand) {
126
126
  return this._converter.withAction((result) => this._applyDefault(result)).withBrand(brand);
127
127
  }
128
128
  /**
129
- * {@inheritdoc Converter.withFormattedError}
129
+ * {@inheritDoc Converter.withFormattedError}
130
130
  */
131
131
  withFormattedError(formatter) {
132
132
  // formatter should never actually be invoked for a defaulting converter
@@ -142,6 +142,12 @@ export class GenericDefaultingConverter {
142
142
  withDefault(dflt) {
143
143
  return new GenericDefaultingConverter(this._converter, dflt);
144
144
  }
145
+ /**
146
+ * {@inheritDoc Converter.or}
147
+ */
148
+ or(__converter) {
149
+ return this;
150
+ }
145
151
  _applyDefault(converted) {
146
152
  return converted.success ? converted : succeed(this.defaultValue);
147
153
  }
@@ -0,0 +1,122 @@
1
+ import { InMemoryLogger, isDetailLogger } from './logger';
2
+ /**
3
+ * A logger that buffers log entries during startup, then replays them
4
+ * to a real logger once it becomes available.
5
+ *
6
+ * @remarks
7
+ * During application bootstrap, the real logger (e.g. one backed by
8
+ * toast notifications) may not be available yet. `BootLogger` solves
9
+ * this by:
10
+ * 1. Buffering all log calls in memory during the boot phase.
11
+ * 2. When {@link BootLogger.ready | ready()} is called with the real
12
+ * logger, replaying all buffered entries and then forwarding all
13
+ * subsequent calls directly.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Create early, before the real logger exists
18
+ * const bootLogger = new BootLogger('detail');
19
+ * bootLogger.info('Starting up...');
20
+ *
21
+ * // Later, when the real logger is available
22
+ * bootLogger.ready(realLogger);
23
+ * // 'Starting up...' is replayed to realLogger
24
+ * // All future calls go directly to realLogger
25
+ * ```
26
+ *
27
+ * @public
28
+ */
29
+ export class BootLogger {
30
+ /**
31
+ * Creates a new boot logger.
32
+ * @param logLevel - The log level for the initial in-memory buffer phase.
33
+ * Defaults to 'detail' to capture everything during boot.
34
+ */
35
+ constructor(logLevel) {
36
+ this._buffer = [];
37
+ this._isReady = false;
38
+ this._delegate = new InMemoryLogger(logLevel !== null && logLevel !== void 0 ? logLevel : 'detail');
39
+ }
40
+ /**
41
+ * {@inheritDoc Logging.ILogger.logLevel}
42
+ */
43
+ get logLevel() {
44
+ return this._delegate.logLevel;
45
+ }
46
+ /**
47
+ * Whether the boot logger has been connected to a real logger.
48
+ */
49
+ get isReady() {
50
+ return this._isReady;
51
+ }
52
+ /**
53
+ * Connects this boot logger to a real logger.
54
+ * All buffered entries are replayed to the new logger in order,
55
+ * and all subsequent calls are forwarded directly.
56
+ * @param logger - The real logger to forward to.
57
+ */
58
+ ready(logger) {
59
+ // Replay buffered entries
60
+ for (const entry of this._buffer) {
61
+ logger.log(entry.level, entry.message, ...entry.parameters);
62
+ }
63
+ // Switch to the real logger and discard the buffer
64
+ this._delegate = logger;
65
+ this._buffer = [];
66
+ this._isReady = true;
67
+ }
68
+ /**
69
+ * {@inheritDoc Logging.ILogger.log}
70
+ */
71
+ log(level, message, ...parameters) {
72
+ if (!this._isReady) {
73
+ this._buffer.push({ level, message, parameters });
74
+ }
75
+ return this._delegate.log(level, message, ...parameters);
76
+ }
77
+ /**
78
+ * {@inheritDoc Logging.ILogger.detail}
79
+ */
80
+ detail(message, ...parameters) {
81
+ return this.log('detail', message, ...parameters);
82
+ }
83
+ /**
84
+ * {@inheritDoc Logging.ILogger.info}
85
+ */
86
+ info(message, ...parameters) {
87
+ return this.log('info', message, ...parameters);
88
+ }
89
+ /**
90
+ * {@inheritDoc Logging.ILogger.warn}
91
+ */
92
+ warn(message, ...parameters) {
93
+ return this.log('warning', message, ...parameters);
94
+ }
95
+ /**
96
+ * {@inheritDoc Logging.ILogger.error}
97
+ */
98
+ error(message, ...parameters) {
99
+ return this.log('error', message, ...parameters);
100
+ }
101
+ /**
102
+ * {@inheritDoc Logging.IDetailLogger.errorWithDetail}
103
+ */
104
+ errorWithDetail(message, detail) {
105
+ if (isDetailLogger(this._delegate)) {
106
+ return this._delegate.errorWithDetail(message, detail);
107
+ }
108
+ this.log('detail', detail);
109
+ return this.log('error', message);
110
+ }
111
+ /**
112
+ * {@inheritDoc Logging.IDetailLogger.warnWithDetail}
113
+ */
114
+ warnWithDetail(message, detail) {
115
+ if (isDetailLogger(this._delegate)) {
116
+ return this._delegate.warnWithDetail(message, detail);
117
+ }
118
+ this.log('detail', detail);
119
+ return this.log('warning', message);
120
+ }
121
+ }
122
+ //# sourceMappingURL=bootLogger.js.map
@@ -19,6 +19,7 @@
19
19
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  * SOFTWARE.
21
21
  */
22
+ export * from './bootLogger';
22
23
  export * from './logger';
23
24
  export * from './logReporter';
24
25
  //# sourceMappingURL=index.js.map
@@ -19,7 +19,8 @@
19
19
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
20
  * SOFTWARE.
21
21
  */
22
- import { NoOpLogger, stringifyLogValue } from './logger';
22
+ import { isDetailLogger, NoOpLogger, stringifyLogValue } from './logger';
23
+ import { captureResult } from '../base';
23
24
  /**
24
25
  * Abstract base class which wraps an existing {@link Logging.ILogger | ILogger} to implement
25
26
  * both {@link Logging.ILogger | ILogger} and {@link IResultReporter | IResultReporter}.
@@ -38,6 +39,16 @@ export class LogReporter {
38
39
  this._valueFormatter = (_b = params.valueFormatter) !== null && _b !== void 0 ? _b : LogReporter.tryFormatObject;
39
40
  this._messageFormatter = (_c = params.messageFormatter) !== null && _c !== void 0 ? _c : ((message, __detail) => message);
40
41
  }
42
+ /**
43
+ * Creates a default {@link Logging.LogReporter | LogReporter} with a {@link Logging.NoOpLogger | NoOpLogger} with the
44
+ * supplied parameters, returning both the logger and reporter.
45
+ * @param logger - Optional logger to use; defaults to a new {@link Logging.NoOpLogger | NoOpLogger} if not provided.
46
+ * @returns
47
+ */
48
+ static createDefault(logger) {
49
+ logger = logger !== null && logger !== void 0 ? logger : new NoOpLogger();
50
+ return captureResult(() => new LogReporter({ logger }));
51
+ }
41
52
  /**
42
53
  * {@inheritDoc Logging.ILogger.logLevel}
43
54
  */
@@ -68,6 +79,26 @@ export class LogReporter {
68
79
  error(message, ...parameters) {
69
80
  return this.logger.error(message, ...parameters);
70
81
  }
82
+ /**
83
+ * {@inheritDoc Logging.IDetailLogger.errorWithDetail}
84
+ */
85
+ errorWithDetail(message, detail) {
86
+ if (isDetailLogger(this.logger)) {
87
+ return this.logger.errorWithDetail(message, detail);
88
+ }
89
+ this.logger.detail(detail);
90
+ return this.logger.error(message);
91
+ }
92
+ /**
93
+ * {@inheritDoc Logging.IDetailLogger.warnWithDetail}
94
+ */
95
+ warnWithDetail(message, detail) {
96
+ if (isDetailLogger(this.logger)) {
97
+ return this.logger.warnWithDetail(message, detail);
98
+ }
99
+ this.logger.detail(detail);
100
+ return this.logger.warn(message);
101
+ }
71
102
  /**
72
103
  * {@inheritDoc Logging.ILogger.log}
73
104
  */
@@ -70,7 +70,17 @@ export function stringifyLogValue(value, maxLength) {
70
70
  return str;
71
71
  }
72
72
  /**
73
- * Abstract base class which implements {@link Logging.ILogger | ILogger}.
73
+ * Type guard that checks whether a logger implements {@link IDetailLogger}.
74
+ * @param logger - The logger to check.
75
+ * @returns `true` if the logger implements `IDetailLogger`.
76
+ * @public
77
+ */
78
+ export function isDetailLogger(logger) {
79
+ return (typeof logger.errorWithDetail === 'function' &&
80
+ typeof logger.warnWithDetail === 'function');
81
+ }
82
+ /**
83
+ * Abstract base class which implements {@link Logging.IDetailLogger | IDetailLogger}.
74
84
  * @public
75
85
  */
76
86
  export class LoggerBase {
@@ -105,6 +115,20 @@ export class LoggerBase {
105
115
  error(message, ...parameters) {
106
116
  return this.log('error', message, ...parameters);
107
117
  }
118
+ /**
119
+ * {@inheritDoc Logging.IDetailLogger.errorWithDetail}
120
+ */
121
+ errorWithDetail(message, detail) {
122
+ this.detail(detail);
123
+ return this.error(message);
124
+ }
125
+ /**
126
+ * {@inheritDoc Logging.IDetailLogger.warnWithDetail}
127
+ */
128
+ warnWithDetail(message, detail) {
129
+ this.detail(detail);
130
+ return this.warn(message);
131
+ }
108
132
  /**
109
133
  * {@inheritDoc Logging.ILogger.log}
110
134
  */
@@ -188,10 +212,6 @@ export class InMemoryLogger extends LoggerBase {
188
212
  }
189
213
  /**
190
214
  * {@inheritDoc Logging.LoggerBase._suppressLog}
191
- * @param level - The level of the message.
192
- * @param message - The message to suppress.
193
- * @param parameters - The parameters to suppress.
194
- * @returns `Success` with `undefined` if the message is suppressed.
195
215
  * @internal
196
216
  */
197
217
  _suppressLog(level, message, ...parameters) {
@@ -22,6 +22,7 @@
22
22
  /* c8 ignore start */
23
23
  export { ArrayValidator } from './array';
24
24
  export { BooleanValidator } from './boolean';
25
+ export { CompositeIdValidator } from './compositeId';
25
26
  export { NumberValidator } from './number';
26
27
  export { ObjectValidator } from './object';
27
28
  export { OneOfValidator } from './oneOf';
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright (c) 2026 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 { Failure, fail } from '../base';
23
+ import { ValidatorBase } from './validatorBase';
24
+ /**
25
+ * An in-place {@link Validation.Validator | Validator} for a strongly-typed composite ID.
26
+ * @public
27
+ */
28
+ export class CompositeIdValidator extends ValidatorBase {
29
+ /**
30
+ * Constructs a new {@link Validation.Classes.StringValidator | StringValidator}.
31
+ * @param params - Optional {@link Validation.Classes.StringValidatorConstructorParams | init params}
32
+ * for the new {@link Validation.Classes.StringValidator | StringValidator}.
33
+ */
34
+ constructor(params) {
35
+ /* c8 ignore next */
36
+ super(Object.assign({}, params));
37
+ const { collectionId, separator, itemId } = params;
38
+ this._collectionIdValidator = collectionId.withFormattedError((value, err) => `${value}: invalid composite collection ID (${err}).`);
39
+ this._itemIdValidator = itemId.withFormattedError((value, err) => `${value}: invalid composite item ID (${err}).`);
40
+ this._separator = separator;
41
+ }
42
+ _validate(value, context) {
43
+ if (typeof value !== 'string') {
44
+ return fail(`${value}: invalid non-string composite ID.`);
45
+ }
46
+ const parts = value.split(this._separator);
47
+ if (parts.length < 2) {
48
+ return fail(`${value}: invalid composite ID - separator '${this._separator}' not found.`);
49
+ }
50
+ else if (parts.length > 2) {
51
+ return fail(`${value}: invalid composite ID - multiple separators '${this._separator}.' found.`);
52
+ }
53
+ const [collectionId, itemId] = parts;
54
+ const result = this._collectionIdValidator
55
+ .validate(collectionId, context)
56
+ .onSuccess(() => this._itemIdValidator.validate(itemId, context));
57
+ return result.isSuccess() ? true : Failure.with(`${value}: invalid composite ID - ${result.message}`);
58
+ }
59
+ }
60
+ //# sourceMappingURL=compositeId.js.map
@@ -43,7 +43,7 @@ export class FieldValidator extends ValidatorBase {
43
43
  this._fieldOptions = options !== null && options !== void 0 ? options : {};
44
44
  }
45
45
  /**
46
- * {@inheritdoc Validation.ValidatorBase._validate}
46
+ * {@inheritDoc Validation.ValidatorBase._validate}
47
47
  */
48
48
  _validate(from, context, self) {
49
49
  if (typeof from === 'object' && !Array.isArray(from) && from !== null) {
@@ -41,19 +41,19 @@ export class GenericValidator {
41
41
  this.traits = new ValidatorTraits(params.traits);
42
42
  }
43
43
  /**
44
- * {@inheritdoc Validation.Validator.isOptional}
44
+ * {@inheritDoc Validation.Validator.isOptional}
45
45
  */
46
46
  get isOptional() {
47
47
  return this.traits.isOptional;
48
48
  }
49
49
  /**
50
- * {@inheritdoc Validation.Validator.brand}
50
+ * {@inheritDoc Validation.Validator.brand}
51
51
  */
52
52
  get brand() {
53
53
  return this.traits.brand;
54
54
  }
55
55
  /**
56
- * {@inheritdoc Validation.Validator.validate}
56
+ * {@inheritDoc Validation.Validator.validate}
57
57
  */
58
58
  validate(from, context) {
59
59
  const result = this._validator(from, this._context(context), this);
@@ -63,7 +63,7 @@ export class GenericValidator {
63
63
  return result;
64
64
  }
65
65
  /**
66
- * {@inheritdoc Validation.Validator.convert}
66
+ * {@inheritDoc Validation.Validator.convert}
67
67
  */
68
68
  convert(from, context) {
69
69
  const result = this._validator(from, this._context(context), this);
@@ -73,19 +73,19 @@ export class GenericValidator {
73
73
  return result;
74
74
  }
75
75
  /**
76
- * {@inheritdoc Validation.Validator.validateOptional}
76
+ * {@inheritDoc Validation.Validator.validateOptional}
77
77
  */
78
78
  validateOptional(from, context) {
79
79
  return from === undefined ? succeed(undefined) : this.validate(from, context);
80
80
  }
81
81
  /**
82
- * {@inheritdoc Validation.Validator.guard}
82
+ * {@inheritDoc Validation.Validator.guard}
83
83
  */
84
84
  guard(from, context) {
85
85
  return this._validator(from, this._context(context), this) === true;
86
86
  }
87
87
  /**
88
- * {@inheritdoc Validation.Validator.optional}
88
+ * {@inheritDoc Validation.Validator.optional}
89
89
  */
90
90
  optional() {
91
91
  return new GenericValidator({
@@ -96,7 +96,7 @@ export class GenericValidator {
96
96
  });
97
97
  }
98
98
  /**
99
- * {@inheritdoc Validation.Validator.withConstraint}
99
+ * {@inheritDoc Validation.Validator.withConstraint}
100
100
  */
101
101
  withConstraint(constraint, trait) {
102
102
  trait = trait !== null && trait !== void 0 ? trait : { type: 'function' };
@@ -118,7 +118,7 @@ export class GenericValidator {
118
118
  });
119
119
  }
120
120
  /**
121
- * {@inheritdoc Validation.Validator.brand}
121
+ * {@inheritDoc Validation.Validator.brand}
122
122
  */
123
123
  withBrand(brand) {
124
124
  if (this.brand) {
@@ -132,7 +132,7 @@ export class GenericValidator {
132
132
  });
133
133
  }
134
134
  /**
135
- * {@inheritdoc Validation.Validator.withFormattedError}
135
+ * {@inheritDoc Validation.Validator.withFormattedError}
136
136
  */
137
137
  withFormattedError(formatter) {
138
138
  return new GenericValidator({
@@ -147,6 +147,20 @@ export class GenericValidator {
147
147
  }
148
148
  });
149
149
  }
150
+ /**
151
+ * {@inheritDoc Validation.Validator.or}
152
+ */
153
+ or(other) {
154
+ return new GenericValidator({
155
+ validator: (from, context, __self) => {
156
+ if (this._validator(from, this._context(context), this) === true) {
157
+ return true;
158
+ }
159
+ const otherResult = other.validate(from, this._context(context));
160
+ return otherResult.isSuccess() ? true : otherResult;
161
+ }
162
+ });
163
+ }
150
164
  /**
151
165
  * Gets a default or explicit context.
152
166
  * @param explicitContext - Optional explicit context.
@@ -33,10 +33,7 @@ import { FieldValidator } from './field';
33
33
  export class ObjectValidator extends ValidatorBase {
34
34
  /**
35
35
  * Constructs a new {@link Validation.Classes.ObjectValidator | ObjectValidator<T>}.
36
- * @param fields - A {@link Validation.Classes.FieldValidators | FieldValidators<T>} containing
37
- * a {@link Validation.Validator | Validator} for each field.
38
- * @param options - An optional {@link Validation.Classes.ObjectValidatorOptions} to configure
39
- * validation.
36
+ * @param params - Construction parameters including field validators and options.
40
37
  */
41
38
  constructor(params) {
42
39
  var _a;
@@ -92,7 +89,7 @@ export class ObjectValidator extends ValidatorBase {
92
89
  /**
93
90
  * Creates a new {@link Validation.Classes.ObjectValidator | ObjectValidator} derived from this one but with
94
91
  * new optional properties as specified by a supplied array of `keyof T`.
95
- * @param addOptionalProperties - The keys to be made optional.
92
+ * @param addOptionalFields - The keys to be made optional.
96
93
  * @returns A new {@link Validation.Classes.ObjectValidator | ObjectValidator} with the additional optional
97
94
  * source properties.
98
95
  */
@@ -103,7 +100,7 @@ export class ObjectValidator extends ValidatorBase {
103
100
  });
104
101
  }
105
102
  /**
106
- * {@inheritdoc Validation.ValidatorBase._validate}
103
+ * {@inheritDoc Validation.ValidatorBase._validate}
107
104
  * @internal
108
105
  */
109
106
  _validate(from, context, self) {
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { fail, isKeyOf, MessageAggregator } from '../base';
23
23
  import { ArrayValidator } from './array';
24
+ import { CompositeIdValidator } from './compositeId';
24
25
  import { ObjectValidator } from './object';
25
26
  import { TypeGuardValidator } from './typeGuard';
26
27
  import { BooleanValidator } from './boolean';
@@ -152,6 +153,17 @@ export function literal(value) {
152
153
  }
153
154
  });
154
155
  }
156
+ /** Helper function to create a {@link Validation.Validator | Validator} which validates a
157
+ * strongly-typed composite ID.
158
+ * @param params - {@link Validation.Classes.CompositeIdValidatorConstructorParams | params}
159
+ * used to construct the validator.
160
+ * @returns A new {@link Validation.Validator | Validator} which validates the desired
161
+ * composite ID in place.
162
+ * @public
163
+ */
164
+ export function compositeId(params) {
165
+ return new CompositeIdValidator(params);
166
+ }
155
167
  /**
156
168
  * Helper function to create a {@link Validation.Validator | Validator} which validates one
157
169
  * of several possible validated values.