@fgv/ts-utils 5.0.1-9 → 5.0.2-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 (92) 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 +638 -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/ts-utils.d.ts +4 -4
  88. package/dist/tsdoc-metadata.json +1 -1
  89. package/lib/packlets/base/result.d.ts +4 -4
  90. package/lib/packlets/conversion/converters.js +10 -9
  91. package/lib/packlets/hash/crcNormalizer.js +42 -2
  92. package/package.json +16 -2
@@ -0,0 +1,24 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { matcherHint } from 'jest-matcher-utils';
3
+ import { toMatchInlineSnapshot } from 'jest-snapshot';
4
+ import { printReceivedResult } from '../../utils/matcherHelpers';
5
+ const matcherName = 'toSucceedAndMatchInlineSnapshot';
6
+ export default {
7
+ toSucceedAndMatchInlineSnapshot: function (received, snapshot) {
8
+ const context = this;
9
+ if (received.isFailure()) {
10
+ return {
11
+ pass: false,
12
+ message: () => {
13
+ return [
14
+ matcherHint(`${matcherName}`, 'callback'),
15
+ 'Expected:\n Callback to succeed with a result that matches the snapshot',
16
+ printReceivedResult(received)
17
+ ].join('\n');
18
+ }
19
+ };
20
+ }
21
+ return toMatchInlineSnapshot.call(context, received.value, {}, snapshot);
22
+ }
23
+ };
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,24 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { matcherHint } from 'jest-matcher-utils';
3
+ import { toMatchSnapshot } from 'jest-snapshot';
4
+ import { printReceivedResult } from '../../utils/matcherHelpers';
5
+ const matcherName = 'toSucceedAndMatchSnapshot';
6
+ export default {
7
+ toSucceedAndMatchSnapshot: function (received) {
8
+ const context = this;
9
+ if (received.isFailure()) {
10
+ return {
11
+ pass: false,
12
+ message: () => {
13
+ return [
14
+ matcherHint(`${matcherName}`, 'callback'),
15
+ 'Expected:\n Callback to succeed with a result that matches the snapshot',
16
+ printReceivedResult(received)
17
+ ].join('\n');
18
+ }
19
+ };
20
+ }
21
+ return toMatchSnapshot.call(context, received.value, 'toSucceedAndMatchSnapshot');
22
+ }
23
+ };
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,53 @@
1
+ import { printExpectedResult, printReceivedResult } from '../../utils/matcherHelpers';
2
+ import { matcherName, predicate } from './predicate';
3
+ import { matcherHint } from 'jest-matcher-utils';
4
+ function passMessage(received) {
5
+ const expected = 'successful callback';
6
+ const got = [printReceivedResult(received)];
7
+ got.push(' Callback returned true');
8
+ return () => [
9
+ matcherHint(`.not.${matcherName}`, 'result', 'callback'),
10
+ printExpectedResult('success', false, expected),
11
+ ...got
12
+ ].join('\n');
13
+ }
14
+ function failMessage(received, cbResult) {
15
+ const expected = 'successful callback';
16
+ const got = [printReceivedResult(received)];
17
+ /* c8 ignore else */
18
+ if (cbResult.isFailure()) {
19
+ got.push(cbResult.message);
20
+ }
21
+ else if (cbResult.value === false) {
22
+ got.push(' Callback returned false');
23
+ }
24
+ else if (cbResult.value === undefined) {
25
+ /* c8 ignore else */
26
+ got.push(' Callback was not invoked');
27
+ }
28
+ else {
29
+ /* c8 ignore next */
30
+ throw new Error('Internal error: toSucceedAndSatisfy.failMessage passed success with true');
31
+ }
32
+ return () => [
33
+ matcherHint(`${matcherName}`, 'result', 'callback'),
34
+ printExpectedResult('success', true, expected),
35
+ ...got
36
+ ].join('\n');
37
+ }
38
+ export default {
39
+ toSucceedAndSatisfy: function (received, test) {
40
+ // For the normal (not '.not') case, we do not want to capture exceptions
41
+ // so that the IDE can display exactly the line on which the failure case.
42
+ // For the .not case, we want to swallow exceptions or expect failures since
43
+ // we're just testing failure and not the reason.
44
+ const capture = this.isNot;
45
+ const cbResult = predicate(received, test, !!capture);
46
+ const pass = cbResult.isSuccess() && cbResult.value === true;
47
+ if (pass) {
48
+ return { pass: true, message: passMessage(received) };
49
+ }
50
+ return { pass: false, message: failMessage(received, cbResult) };
51
+ }
52
+ };
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import { captureResult, fail, succeed } from '../../ts-utils';
2
+ export const matcherName = 'toSucceedAndSatisfy';
3
+ // If the result is successful and callback does not throw, returns Success with the callback return value, or
4
+ // true if the callback returns undefined
5
+ // If the result is successful but the callback throws, returns Failure with the thrown error
6
+ // If the result is failure, returns Success with undefined
7
+ export function predicate(received, cb, capture) {
8
+ if (received.isSuccess()) {
9
+ const cbResult = capture ? captureResult(() => cb(received.value)) : succeed(cb(received.value));
10
+ if (cbResult.isSuccess()) {
11
+ return succeed(cbResult.value === true || cbResult.value === undefined);
12
+ }
13
+ return fail(` Callback failed with:\n ${cbResult.message}`);
14
+ }
15
+ return succeed(undefined);
16
+ }
17
+ //# sourceMappingURL=predicate.js.map
@@ -0,0 +1,27 @@
1
+ import { printExpectedResult, printReceivedResult } from '../../utils/matcherHelpers';
2
+ import { matcherName, predicate } from './predicate';
3
+ import { matcherHint } from 'jest-matcher-utils';
4
+ function passMessage(received, expected) {
5
+ return () => [
6
+ matcherHint(`.not.${matcherName}`),
7
+ printExpectedResult('success', false, expected),
8
+ printReceivedResult(received)
9
+ ].join('\n');
10
+ }
11
+ function failMessage(received, expected) {
12
+ return () => [
13
+ matcherHint(`${matcherName}`),
14
+ printExpectedResult('success', true, expected),
15
+ printReceivedResult(received)
16
+ ].join('\n');
17
+ }
18
+ export default {
19
+ toSucceedWith: function (received, expected) {
20
+ const pass = predicate(received, expected);
21
+ if (pass) {
22
+ return { pass: true, message: passMessage(received, expected) };
23
+ }
24
+ return { pass: false, message: failMessage(received, expected) };
25
+ }
26
+ };
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ import { equals } from '@jest/expect-utils';
2
+ export const matcherName = 'toSucceedWith';
3
+ export function predicate(received, expected) {
4
+ if (received.isSuccess()) {
5
+ if (typeof received.value === 'string' && expected instanceof RegExp) {
6
+ return expected.test(received.value);
7
+ }
8
+ return equals(received.value, expected);
9
+ }
10
+ return false;
11
+ }
12
+ //# sourceMappingURL=predicate.js.map
@@ -0,0 +1,27 @@
1
+ import { printExpectedDetailedResult, printReceivedDetailedResult } from '../../utils/matcherHelpers';
2
+ import { matcherName, predicate } from './predicate';
3
+ import { matcherHint } from 'jest-matcher-utils';
4
+ function passMessage(received, expected, expectedDetail) {
5
+ return () => [
6
+ matcherHint(`.not.${matcherName}`),
7
+ printExpectedDetailedResult('success', false, expected, expectedDetail),
8
+ printReceivedDetailedResult(received)
9
+ ].join('\n');
10
+ }
11
+ function failMessage(received, expected, expectedDetail) {
12
+ return () => [
13
+ matcherHint(`${matcherName}`),
14
+ printExpectedDetailedResult('success', true, expected, expectedDetail),
15
+ printReceivedDetailedResult(received)
16
+ ].join('\n');
17
+ }
18
+ export default {
19
+ toSucceedWithDetail: function (received, expected, detail) {
20
+ const pass = predicate(received, expected, detail);
21
+ if (pass) {
22
+ return { pass: true, message: passMessage(received, expected, detail) };
23
+ }
24
+ return { pass: false, message: failMessage(received, expected, detail) };
25
+ }
26
+ };
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import { equals } from '@jest/expect-utils';
2
+ export const matcherName = 'toSucceedWithDetail';
3
+ export function predicate(received, expected, detail) {
4
+ if (received.isSuccess()) {
5
+ let pass = false;
6
+ if (typeof received.value === 'string' && expected instanceof RegExp) {
7
+ pass = expected.test(received.value);
8
+ }
9
+ else {
10
+ pass = equals(received.value, expected);
11
+ }
12
+ pass = pass && equals(received.detail, detail);
13
+ return pass;
14
+ }
15
+ return false;
16
+ }
17
+ //# sourceMappingURL=predicate.js.map
@@ -0,0 +1,10 @@
1
+ import * as Resolver from '../utils/snapshotResolver';
2
+ export const CLI_SNAPSHOT_FOLDER = '__cli__';
3
+ export function resolveSnapshotPath(testPath, ext) {
4
+ return Resolver.resolveSnapshotPath(testPath, ext, CLI_SNAPSHOT_FOLDER);
5
+ }
6
+ export function resolveTestPath(snapshotPath, ext) {
7
+ return Resolver.resolveTestPath(snapshotPath, ext);
8
+ }
9
+ export const testPathForConsistencyCheck = Resolver.testPathForConsistencyCheck;
10
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1,10 @@
1
+ import * as Resolver from '../utils/snapshotResolver';
2
+ export const CLI_SNAPSHOT_FOLDER = '__ide__';
3
+ export function resolveSnapshotPath(testPath, ext) {
4
+ return Resolver.resolveSnapshotPath(testPath, ext, CLI_SNAPSHOT_FOLDER);
5
+ }
6
+ export function resolveTestPath(snapshotPath, ext) {
7
+ return Resolver.resolveTestPath(snapshotPath, ext);
8
+ }
9
+ export const testPathForConsistencyCheck = Resolver.testPathForConsistencyCheck;
10
+ //# sourceMappingURL=ide.js.map
@@ -0,0 +1,2 @@
1
+ export * from '../../../index';
2
+ //# sourceMappingURL=ts-utils.js.map
@@ -0,0 +1,3 @@
1
+ /// <reference types="jest"/>
2
+ export {};
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,47 @@
1
+ import { printExpected, printReceived } from 'jest-matcher-utils';
2
+ function printExpectedValue(outcome, expected) {
3
+ return expected !== undefined ? ` ${outcome} with ${printExpected(expected)}` : ` ${outcome}`;
4
+ }
5
+ export function printExpectedResult(expect, isNot, expected) {
6
+ return [
7
+ 'Expected:',
8
+ isNot
9
+ ? expect === 'success'
10
+ ? printExpectedValue('Success', expected)
11
+ : printExpectedValue('Failure', expected)
12
+ : expect === 'success'
13
+ ? printExpectedValue('Not success', expected)
14
+ : printExpectedValue('Not failure', expected)
15
+ ].join('\n');
16
+ }
17
+ export function printExpectedDetailedResult(expect, isNot, expectedMessage, expectedDetail) {
18
+ /* c8 ignore next */
19
+ return [
20
+ 'Expected:',
21
+ isNot
22
+ ? expect === 'success'
23
+ ? printExpectedValue('Success', expectedMessage)
24
+ : printExpectedValue('Failure', expectedMessage)
25
+ : expect === 'success'
26
+ ? printExpectedValue('Not success', expectedMessage)
27
+ : printExpectedValue('Not failure', expectedMessage),
28
+ ` Detail: "${printExpected(expectedDetail)}"`
29
+ ].join('\n');
30
+ }
31
+ export function printReceivedResult(received) {
32
+ return [
33
+ 'Received:',
34
+ received.isSuccess()
35
+ ? ` Success with ${printReceived(received.value)}`
36
+ : ` Failure with "${received.message}"`
37
+ ].join('\n');
38
+ }
39
+ export function printReceivedDetailedResult(received) {
40
+ return [
41
+ 'Received:',
42
+ received.isSuccess()
43
+ ? ` Success with "${printReceived(received.value)}"\n Detail: "${printReceived(received.detail)}"`
44
+ : ` Failure with "${received.message}"\n Detail: "${printReceived(received.detail)}"`
45
+ ].join('\n');
46
+ }
47
+ //# sourceMappingURL=matcherHelpers.js.map
@@ -0,0 +1,11 @@
1
+ import path from 'path';
2
+ export function resolveSnapshotPath(testPath, snapshotExtension, snapshotFolderName) {
3
+ const snapshotPath = path.join(path.join(path.dirname(testPath), '__snapshots__', snapshotFolderName), path.basename(testPath) + snapshotExtension);
4
+ return snapshotPath;
5
+ }
6
+ export function resolveTestPath(snapshotFilePath, snapshotExtension) {
7
+ const testPath = path.join(path.dirname(path.dirname(path.dirname(snapshotFilePath))), path.basename(snapshotFilePath, snapshotExtension));
8
+ return testPath;
9
+ }
10
+ export const testPathForConsistencyCheck = path.posix.join('consistency_check', '__tests__', 'subdir', 'example.test.js');
11
+ //# sourceMappingURL=snapshotResolver.js.map
@@ -0,0 +1,106 @@
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, succeed } from '../../../packlets/base';
23
+ import { Collectible, ConvertingCollector } from '../../../packlets/collections';
24
+ import { Converters } from '../../../packlets/conversion';
25
+ export const testThingKey = Converters.string
26
+ .withConstraint((s) => /^thing\d{1,4}$/.test(s))
27
+ .withFormattedError((val) => `${val} is invalid TestThingKey`)
28
+ .withBrand('TestThingKey');
29
+ export const testThingIndex = Converters.number
30
+ .withConstraint((n) => n >= 0)
31
+ .withBrand('TestThingIndex');
32
+ export const testThing = Converters.strictObject({
33
+ str: Converters.string.optional(),
34
+ num: Converters.number.optional(),
35
+ bool: Converters.boolean.optional()
36
+ });
37
+ export class BrandedCollectibleTestThing extends Collectible {
38
+ constructor(thing, key, index) {
39
+ super({ key, index, indexConverter: testThingIndex });
40
+ this.str = thing.str;
41
+ this.num = thing.num;
42
+ this.bool = thing.bool;
43
+ }
44
+ static create(thing, key, index) {
45
+ return testThingKey.convert(key).onSuccess((convertedKey) => {
46
+ return testThingIndex.convert(index).onSuccess((convertedIndex) => {
47
+ return captureResult(() => new BrandedCollectibleTestThing(thing, convertedKey, convertedIndex));
48
+ });
49
+ });
50
+ }
51
+ }
52
+ export class CollectibleTestThing extends Collectible {
53
+ constructor(thing, key, index) {
54
+ super({ key, index, indexConverter: Converters.number });
55
+ this.str = thing.str;
56
+ this.num = thing.num;
57
+ this.bool = thing.bool;
58
+ }
59
+ static create(thing, key, index) {
60
+ return captureResult(() => new CollectibleTestThing(thing, key, index));
61
+ }
62
+ }
63
+ export class BrokenCollectibleTestThing extends CollectibleTestThing {
64
+ constructor(thing, key, index) {
65
+ super(thing, key, index ? (index + 1) : undefined);
66
+ }
67
+ setIndex(index) {
68
+ this._index = index + 1;
69
+ return succeed(this.index);
70
+ }
71
+ }
72
+ export class TestCollector extends ConvertingCollector {
73
+ constructor(things) {
74
+ const entries = things
75
+ ? { entries: things.map((thing, index) => [`thing${index}`, thing]) }
76
+ : {};
77
+ const params = Object.assign({ factory: TestCollector._factory }, entries);
78
+ super(params);
79
+ }
80
+ static _factory(key, index, item) {
81
+ return succeed(new CollectibleTestThing(item, key, index));
82
+ }
83
+ }
84
+ export function getTestThings() {
85
+ const things = [
86
+ { str: 'thing0', num: 0, bool: false },
87
+ { str: 'thing1', num: 1, bool: true },
88
+ { str: 'thing2', num: 2, bool: false },
89
+ { str: 'thing3', num: 3, bool: true },
90
+ { str: 'thing4', num: 4, bool: false }
91
+ ];
92
+ const collectibles = things.map((thing, index) => new CollectibleTestThing(thing, `thing${index}`, index));
93
+ return { things, collectibles };
94
+ }
95
+ export function getBrandedTestThings() {
96
+ const things = [
97
+ { str: 'thing0', num: 0, bool: false },
98
+ { str: 'thing1', num: 1, bool: true },
99
+ { str: 'thing2', num: 2, bool: false },
100
+ { str: 'thing3', num: 3, bool: true },
101
+ { str: 'thing4', num: 4, bool: false }
102
+ ];
103
+ const collectibles = things.map((thing, index) => BrandedCollectibleTestThing.create(thing, `thing${index}`, index).orThrow());
104
+ return { things, collectibles };
105
+ }
106
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Copyright (c) 2025 Erik Fortune
3
+ */
4
+ import '../helpers/jest';
5
+ import { Crc32Normalizer } from '../../packlets/hash';
6
+ describe('Crc32Normalizer TextEncoder/Buffer compatibility', () => {
7
+ test('falls back to a non-TextEncoder path when TextEncoder is unavailable', () => {
8
+ const expected = Crc32Normalizer.crc32Hash(['hello']);
9
+ // eslint-disable-next-line @typescript-eslint/naming-convention
10
+ const g = globalThis;
11
+ const originalTextEncoder = g.TextEncoder;
12
+ try {
13
+ delete g.TextEncoder;
14
+ const actual = Crc32Normalizer.crc32Hash(['hello']);
15
+ expect(actual).toEqual(expected);
16
+ }
17
+ finally {
18
+ // Restore environment
19
+ g.TextEncoder = originalTextEncoder;
20
+ }
21
+ });
22
+ });
23
+ //# sourceMappingURL=hashTextEncodeCompat.js.map
@@ -1060,7 +1060,7 @@ declare class Collectible<TKEY extends string = string, TINDEX extends number =
1060
1060
  * failure details in addition to the error message.
1061
1061
  * @public
1062
1062
  */
1063
- export declare class DetailedFailure<T, TD> extends Failure<T> {
1063
+ export declare class DetailedFailure<out T, out TD> extends Failure<T> {
1064
1064
  /**
1065
1065
  * @internal
1066
1066
  */
@@ -1154,7 +1154,7 @@ declare class Collectible<TKEY extends string = string, TINDEX extends number =
1154
1154
  * details in addition to the error message.
1155
1155
  * @public
1156
1156
  */
1157
- export declare class DetailedSuccess<T, TD> extends Success<T> {
1157
+ export declare class DetailedSuccess<out T, out TD> extends Success<T> {
1158
1158
  /**
1159
1159
  * @internal
1160
1160
  */
@@ -1323,7 +1323,7 @@ declare class Collectible<TKEY extends string = string, TINDEX extends number =
1323
1323
  * Reports a failed {@link IResult | result} from some operation, with an error message.
1324
1324
  * @public
1325
1325
  */
1326
- export declare class Failure<T> implements IResult<T> {
1326
+ export declare class Failure<out T> implements IResult<T> {
1327
1327
  /**
1328
1328
  * {@inheritdoc IResult.success}
1329
1329
  */
@@ -4161,7 +4161,7 @@ declare class Collectible<TKEY extends string = string, TINDEX extends number =
4161
4161
  * corresponding value.
4162
4162
  * @public
4163
4163
  */
4164
- export declare class Success<T> implements IResult<T> {
4164
+ export declare class Success<out T> implements IResult<T> {
4165
4165
  /**
4166
4166
  * {@inheritdoc IResult.success}
4167
4167
  */
@@ -5,7 +5,7 @@
5
5
  "toolPackages": [
6
6
  {
7
7
  "packageName": "@microsoft/api-extractor",
8
- "packageVersion": "7.53.3"
8
+ "packageVersion": "7.54.0"
9
9
  }
10
10
  ]
11
11
  }
@@ -298,7 +298,7 @@ export interface IResult<T> {
298
298
  * corresponding value.
299
299
  * @public
300
300
  */
301
- export declare class Success<T> implements IResult<T> {
301
+ export declare class Success<out T> implements IResult<T> {
302
302
  /**
303
303
  * {@inheritdoc IResult.success}
304
304
  */
@@ -394,7 +394,7 @@ export declare class Success<T> implements IResult<T> {
394
394
  * Reports a failed {@link IResult | result} from some operation, with an error message.
395
395
  * @public
396
396
  */
397
- export declare class Failure<T> implements IResult<T> {
397
+ export declare class Failure<out T> implements IResult<T> {
398
398
  /**
399
399
  * {@inheritdoc IResult.success}
400
400
  */
@@ -551,7 +551,7 @@ export type DetailedFailureContinuation<T, TD> = (message: string, detail?: TD)
551
551
  * details in addition to the error message.
552
552
  * @public
553
553
  */
554
- export declare class DetailedSuccess<T, TD> extends Success<T> {
554
+ export declare class DetailedSuccess<out T, out TD> extends Success<T> {
555
555
  /**
556
556
  * @internal
557
557
  */
@@ -619,7 +619,7 @@ export declare class DetailedSuccess<T, TD> extends Success<T> {
619
619
  * failure details in addition to the error message.
620
620
  * @public
621
621
  */
622
- export declare class DetailedFailure<T, TD> extends Failure<T> {
622
+ export declare class DetailedFailure<out T, out TD> extends Failure<T> {
623
623
  /**
624
624
  * @internal
625
625
  */
@@ -130,14 +130,7 @@ exports.number = new baseConverter_1.BaseConverter((from) => {
130
130
  }
131
131
  return (0, base_1.succeed)(from);
132
132
  });
133
- /**
134
- * A {@link Converter | Converter} which converts `unknown` to `boolean`.
135
- * @remarks
136
- * Boolean values or the case-insensitive strings `'true'` and `'false'` succeed.
137
- * Anything else fails.
138
- * @public
139
- */
140
- exports.boolean = new baseConverter_1.BaseConverter((from) => {
133
+ const booleanConverter = new baseConverter_1.BaseConverter((from) => {
141
134
  if (typeof from === 'boolean') {
142
135
  return (0, base_1.succeed)(from);
143
136
  }
@@ -151,6 +144,14 @@ exports.boolean = new baseConverter_1.BaseConverter((from) => {
151
144
  }
152
145
  return (0, base_1.fail)(`Not a boolean: ${JSON.stringify(from)}`);
153
146
  });
147
+ /**
148
+ * A {@link Converter | Converter} which converts `unknown` to `boolean`.
149
+ * @remarks
150
+ * Boolean values or the case-insensitive strings `'true'` and `'false'` succeed.
151
+ * Anything else fails.
152
+ * @public
153
+ */
154
+ exports.boolean = booleanConverter;
154
155
  /**
155
156
  * A {@link Converter | Converter} which converts an optional `string` value. Values of type
156
157
  * `string` are returned. Anything else returns {@link Success | Success} with value `undefined`.
@@ -231,7 +232,7 @@ exports.optionalNumber = exports.number.optional();
231
232
  * with value `undefined`.
232
233
  * @public
233
234
  */
234
- exports.optionalBoolean = exports.boolean.optional();
235
+ exports.optionalBoolean = booleanConverter.optional();
235
236
  /**
236
237
  * A helper function to create a {@link Converter | Converter} for polymorphic values.
237
238
  * Returns a converter which invokes the wrapped converters in sequence, returning the
@@ -23,7 +23,47 @@
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.Crc32Normalizer = void 0;
25
25
  const hashingNormalizer_1 = require("./hashingNormalizer");
26
- const textEncoder = new TextEncoder();
26
+ function encodeUtf8(input) {
27
+ // Prefer built-in TextEncoder when available (browsers and modern Node)
28
+ // @ts-ignore - global TextEncoder may be unavailable in some environments
29
+ const GlobalTextEncoder = typeof TextEncoder !== 'undefined' ? TextEncoder : undefined;
30
+ if (GlobalTextEncoder) {
31
+ return new GlobalTextEncoder().encode(input);
32
+ }
33
+ // Very small manual UTF-8 encoder as a last resort
34
+ /* eslint-disable no-bitwise */
35
+ const bytes = [];
36
+ for (let i = 0; i < input.length; i++) {
37
+ let codePoint = input.charCodeAt(i);
38
+ if (codePoint >= 0xd800 && codePoint <= 0xdbff && i + 1 < input.length) {
39
+ const next = input.charCodeAt(i + 1);
40
+ if (next >= 0xdc00 && next <= 0xdfff) {
41
+ codePoint = ((codePoint - 0xd800) << 10) + (next - 0xdc00) + 0x10000;
42
+ i++;
43
+ }
44
+ }
45
+ if (codePoint < 0x80) {
46
+ bytes.push(codePoint);
47
+ }
48
+ else if (codePoint < 0x800) {
49
+ bytes.push(0xc0 | (codePoint >> 6));
50
+ bytes.push(0x80 | (codePoint & 0x3f));
51
+ }
52
+ else if (codePoint < 0x10000) {
53
+ bytes.push(0xe0 | (codePoint >> 12));
54
+ bytes.push(0x80 | ((codePoint >> 6) & 0x3f));
55
+ bytes.push(0x80 | (codePoint & 0x3f));
56
+ }
57
+ else {
58
+ bytes.push(0xf0 | (codePoint >> 18));
59
+ bytes.push(0x80 | ((codePoint >> 12) & 0x3f));
60
+ bytes.push(0x80 | ((codePoint >> 6) & 0x3f));
61
+ bytes.push(0x80 | (codePoint & 0x3f));
62
+ }
63
+ }
64
+ /* eslint-enable no-bitwise */
65
+ return new Uint8Array(bytes);
66
+ }
27
67
  const POLYNOMIAL = 0xedb88320;
28
68
  const crc32Table = [];
29
69
  function crc32(bytes, crc = 0xffffffff) {
@@ -69,7 +109,7 @@ class Crc32Normalizer extends hashingNormalizer_1.HashingNormalizer {
69
109
  super(Crc32Normalizer.crc32Hash);
70
110
  }
71
111
  static crc32Hash(parts) {
72
- return crc32(textEncoder.encode(parts.join('|')))
112
+ return crc32(encodeUtf8(parts.join('|')))
73
113
  .toString(16)
74
114
  .padStart(8, '0');
75
115
  }
package/package.json CHANGED
@@ -1,9 +1,18 @@
1
1
  {
2
2
  "name": "@fgv/ts-utils",
3
- "version": "5.0.1-9",
3
+ "version": "5.0.2-0",
4
4
  "description": "Assorted Typescript Utilities",
5
5
  "main": "lib/index.js",
6
+ "module": "dist/index.js",
6
7
  "types": "dist/ts-utils.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/ts-utils.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./lib/index.js",
13
+ "default": "./lib/index.js"
14
+ }
15
+ },
7
16
  "sideEffects": false,
8
17
  "keywords": [
9
18
  "typescript",
@@ -43,7 +52,12 @@
43
52
  "@rushstack/eslint-config": "4.5.3",
44
53
  "@types/heft-jest": "1.0.6",
45
54
  "@rushstack/heft-jest-plugin": "1.1.3",
46
- "eslint-plugin-tsdoc": "~0.4.0"
55
+ "eslint-plugin-tsdoc": "~0.4.0",
56
+ "@fgv/heft-dual-rig": "0.1.0"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/ErikFortune/fgv.git"
47
61
  },
48
62
  "scripts": {
49
63
  "build": "heft build --clean",