ts-fortress 8.1.0 → 8.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.
@@ -1,7 +1,13 @@
1
1
  import { type ArrayElement, type NonEmptyArray, type Primitive } from 'ts-type-forge';
2
2
  import { type Type } from '../type.mjs';
3
- export declare const enumType: <const Values extends NonEmptyArray<Primitive>>(values: Values, options?: Partial<Readonly<{
3
+ export declare function enumType<const Values extends NonEmptyArray<Primitive>>(values: Values, options?: Partial<Readonly<{
4
4
  typeName: string;
5
5
  defaultValue: ArrayElement<Values>;
6
- }>>) => Type<ArrayElement<Values>>;
6
+ allowAnyString: false;
7
+ }>>): Type<ArrayElement<Values>>;
8
+ export declare function enumType<const Values extends NonEmptyArray<string>>(values: Values, options: Readonly<{
9
+ allowAnyString: true;
10
+ typeName?: string;
11
+ defaultValue?: ArrayElement<Values> | (string & {});
12
+ }>): Type<ArrayElement<Values> | (string & {})>;
7
13
  //# sourceMappingURL=enum.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"enum.d.mts","sourceRoot":"","sources":["../../src/enum/enum.mts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,eAAO,MAAM,QAAQ,GAAI,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,SAAS,CAAC,EACpE,QAAQ,MAAM,EACd,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;CACpC,CAAC,CACH,KACA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CA6C3B,CAAC"}
1
+ {"version":3,"file":"enum.d.mts","sourceRoot":"","sources":["../../src/enum/enum.mts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAQxC,wBAAgB,QAAQ,CAAC,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,SAAS,CAAC,EACpE,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,cAAc,EAAE,KAAK,CAAC;CACvB,CAAC,CACH,GACA,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;AAM9B,wBAAgB,QAAQ,CAAC,KAAK,CAAC,MAAM,SAAS,aAAa,CAAC,MAAM,CAAC,EACjE,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,QAAQ,CAAC;IAChB,cAAc,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;CACrD,CAAC,GACD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC"}
@@ -1,9 +1,17 @@
1
- import { ISet, memoizeFunction, Result } from 'ts-data-forge';
1
+ import { isString, ISet, memoizeFunction, Result } from 'ts-data-forge';
2
2
  import { createAssertFn } from '../utils/create-assert-fn.mjs';
3
3
  import { createCastFn } from '../utils/create-cast-fn.mjs';
4
4
  import { createIsFn } from '../utils/create-is-fn.mjs';
5
+ import '@sindresorhus/is';
6
+ import '../other-types/unknown.mjs';
7
+ import { string } from '../primitives/string.mjs';
5
8
 
6
- const enumType = (values, options) => {
9
+ function enumType(values, options) {
10
+ if (options?.allowAnyString === true &&
11
+ values.every(isString) &&
12
+ isString(options.defaultValue)) {
13
+ return string(options.defaultValue);
14
+ }
7
15
  const valueSet = ISet.create(values);
8
16
  const typeName = options?.typeName ?? 'enum';
9
17
  const getDefaultValue = memoizeFunction(
@@ -29,7 +37,7 @@ const enumType = (values, options) => {
29
37
  const is = createIsFn(validate);
30
38
  const fill = (a) => (is(a) ? a : getDefaultValue());
31
39
  return {
32
- typeName: options?.typeName ?? 'enum',
40
+ typeName,
33
41
  get defaultValue() {
34
42
  return getDefaultValue();
35
43
  },
@@ -39,7 +47,7 @@ const enumType = (values, options) => {
39
47
  assertIs: createAssertFn(validate),
40
48
  cast: createCastFn(validate),
41
49
  };
42
- };
50
+ }
43
51
 
44
52
  export { enumType };
45
53
  //# sourceMappingURL=enum.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"enum.mjs","sources":["../../src/enum/enum.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;MAca,QAAQ,GAAG,CACtB,MAAc,EACd,OAKC,KAC6B;IAG9B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;AAEpC,IAAA,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,MAAM;IAE5C,MAAM,eAAe,GAAG,eAAe;;IAErC,MAAS,OAAO,EAAE,YAAY,IAAK,MAAM,CAAC,CAAC,CAA0B,CACtE;AAED,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC;;AAEtC,IAAA,QAAQ,CAAC,GAAG,CAAC,CAAc;AACzB;AACE,YAAA,MAAM,CAAC,EAAE,CAAC,CAAM;AAClB,UAAE,MAAM,CAAC,GAAG,CAAC;AACT,YAAA;AACE,gBAAA,IAAI,EAAE,EAAE;AACR,gBAAA,WAAW,EAAE,CAAC;AACd,gBAAA,YAAY,EAAE,QAAQ;gBACtB,QAAQ;AACR,gBAAA,OAAO,EAAE;AACP,oBAAA,IAAI,EAAE,MAAM;oBACZ,MAAM;AACP,iBAAA;AACwB,aAAA;AAC5B,SAAA,CAAC;AAER,IAAA,MAAM,EAAE,GAAG,UAAU,CAAI,QAAQ,CAAC;IAElC,MAAM,IAAI,GAAoB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,eAAe,EAAE,CAAC;IAEpE,OAAO;AACL,QAAA,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM;AACrC,QAAA,IAAI,YAAY,GAAA;YACd,OAAO,eAAe,EAAE;QAC1B,CAAC;QACD,IAAI;QACJ,QAAQ;QACR,EAAE;AACF,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;AAClC,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;KAC7B;AACH;;;;"}
1
+ {"version":3,"file":"enum.mjs","sources":["../../src/enum/enum.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;AAuCM,SAAU,QAAQ,CACtB,MAAc,EACd,OAMC,EAAA;AAED,IAAA,IACE,OAAO,EAAE,cAAc,KAAK,IAAI;AAChC,QAAA,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC;AACtB,QAAA,QAAQ,CAAC,OAAO,CAAC,YAAY,CAAC,EAC9B;AACA,QAAA,OAAO,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC;IACrC;IAIA,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;AAEpC,IAAA,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,MAAM;IAE5C,MAAM,eAAe,GAAG,eAAe;;IAErC,MAAS,OAAO,EAAE,YAAY,IAAK,MAAM,CAAC,CAAC,CAA0B,CACtE;AAED,IAAA,MAAM,QAAQ,GAAwB,CAAC,CAAC;;AAEtC,IAAA,QAAQ,CAAC,GAAG,CAAC,CAAc;AACzB;AACE,YAAA,MAAM,CAAC,EAAE,CAAC,CAAM;AAClB,UAAE,MAAM,CAAC,GAAG,CAAC;AACT,YAAA;AACE,gBAAA,IAAI,EAAE,EAAE;AACR,gBAAA,WAAW,EAAE,CAAC;AACd,gBAAA,YAAY,EAAE,QAAQ;gBACtB,QAAQ;AACR,gBAAA,OAAO,EAAE;AACP,oBAAA,IAAI,EAAE,MAAM;oBACZ,MAAM;AACP,iBAAA;AACwB,aAAA;AAC5B,SAAA,CAAC;AAER,IAAA,MAAM,EAAE,GAAG,UAAU,CAAI,QAAQ,CAAC;IAElC,MAAM,IAAI,GAAoB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,eAAe,EAAE,CAAC;IAEpE,OAAO;QACL,QAAQ;AACR,QAAA,IAAI,YAAY,GAAA;YACd,OAAO,eAAe,EAAE;QAC1B,CAAC;QACD,IAAI;QACJ,QAAQ;QACR,EAAE;AACF,QAAA,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC;AAClC,QAAA,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC;KAC7B;AACH;;;;"}
@@ -1,4 +1,6 @@
1
1
  import 'ts-data-forge';
2
+ import '@sindresorhus/is';
3
+ import '../other-types/unknown.mjs';
2
4
  import { intRange } from '../enum/int-range.mjs';
3
5
 
4
6
  const int8 = (defaultValue = 0) => intRange({
@@ -1 +1 @@
1
- {"version":3,"file":"int8.mjs","sources":["../../src/predefined/int8.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAIO,MAAM,IAAI,GAAG,CAAC,YAAA,GAAqB,CAAC,KACzC,QAAQ,CAAC;IACP,KAAK,EAAE,IAAI;AACX,IAAA,GAAG,EAAE,GAAG;IACR,YAAY;AACb,CAAA;;;;"}
1
+ {"version":3,"file":"int8.mjs","sources":["../../src/predefined/int8.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAIO,MAAM,IAAI,GAAG,CAAC,YAAA,GAAqB,CAAC,KACzC,QAAQ,CAAC;IACP,KAAK,EAAE,IAAI;AACX,IAAA,GAAG,EAAE,GAAG;IACR,YAAY;AACb,CAAA;;;;"}
@@ -1,4 +1,6 @@
1
1
  import 'ts-data-forge';
2
+ import '@sindresorhus/is';
3
+ import '../other-types/unknown.mjs';
2
4
  import { uintRange } from '../enum/uint-range.mjs';
3
5
 
4
6
  const uint8 = (defaultValue = 0) => uintRange({
@@ -1 +1 @@
1
- {"version":3,"file":"uint8.mjs","sources":["../../src/predefined/uint8.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAIO,MAAM,KAAK,GAAG,CAAC,YAAA,GAAsB,CAAC,KAC3C,SAAS,CAAC;AACR,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,GAAG,EAAE,GAAG;IACR,YAAY;AACb,CAAA;;;;"}
1
+ {"version":3,"file":"uint8.mjs","sources":["../../src/predefined/uint8.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;AAIO,MAAM,KAAK,GAAG,CAAC,YAAA,GAAsB,CAAC,KAC3C,SAAS,CAAC;AACR,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,GAAG,EAAE,GAAG;IACR,YAAY;AACb,CAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "8.1.0",
3
+ "version": "8.2.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
package/src/enum/enum.mts CHANGED
@@ -1,9 +1,10 @@
1
- import { ISet, memoizeFunction, Result } from 'ts-data-forge';
1
+ import { ISet, isString, memoizeFunction, Result } from 'ts-data-forge';
2
2
  import {
3
3
  type ArrayElement,
4
4
  type NonEmptyArray,
5
5
  type Primitive,
6
6
  } from 'ts-type-forge';
7
+ import { string } from '../primitives/index.mjs';
7
8
  import { type Type } from '../type.mjs';
8
9
  import {
9
10
  createAssertFn,
@@ -12,16 +13,49 @@ import {
12
13
  type ValidationError,
13
14
  } from '../utils/index.mjs';
14
15
 
15
- export const enumType = <const Values extends NonEmptyArray<Primitive>>(
16
+ export function enumType<const Values extends NonEmptyArray<Primitive>>(
16
17
  values: Values,
17
18
  options?: Partial<
18
19
  Readonly<{
19
20
  typeName: string;
20
21
  defaultValue: ArrayElement<Values>;
22
+ allowAnyString: false;
21
23
  }>
22
24
  >,
23
- ): Type<ArrayElement<Values>> => {
24
- type T = ArrayElement<Values>;
25
+ ): Type<ArrayElement<Values>>;
26
+
27
+ // When `allowAnyString` is on, the result type keeps autocomplete for the
28
+ // listed members via the `T | (string & {})` idiom while still accepting any
29
+ // other string, and at runtime the value is validated as a plain string
30
+ // (i.e. it behaves like `string()`).
31
+ export function enumType<const Values extends NonEmptyArray<string>>(
32
+ values: Values,
33
+ options: Readonly<{
34
+ allowAnyString: true;
35
+ typeName?: string;
36
+ defaultValue?: ArrayElement<Values> | (string & {});
37
+ }>,
38
+ ): Type<ArrayElement<Values> | (string & {})>;
39
+
40
+ export function enumType<const Values extends NonEmptyArray<Primitive>>(
41
+ values: Values,
42
+ options?: Partial<
43
+ Readonly<{
44
+ typeName: string;
45
+ defaultValue: ArrayElement<Values> | (string & {});
46
+ allowAnyString: boolean;
47
+ }>
48
+ >,
49
+ ): Type<ArrayElement<Values> | (string & {})> {
50
+ if (
51
+ options?.allowAnyString === true &&
52
+ values.every(isString) &&
53
+ isString(options.defaultValue)
54
+ ) {
55
+ return string(options.defaultValue);
56
+ }
57
+
58
+ type T = ArrayElement<Values> | (string & {});
25
59
 
26
60
  const valueSet = ISet.create(values);
27
61
 
@@ -55,7 +89,7 @@ export const enumType = <const Values extends NonEmptyArray<Primitive>>(
55
89
  const fill: Type<T>['fill'] = (a) => (is(a) ? a : getDefaultValue());
56
90
 
57
91
  return {
58
- typeName: options?.typeName ?? 'enum',
92
+ typeName,
59
93
  get defaultValue() {
60
94
  return getDefaultValue();
61
95
  },
@@ -65,4 +99,4 @@ export const enumType = <const Values extends NonEmptyArray<Primitive>>(
65
99
  assertIs: createAssertFn(validate),
66
100
  cast: createCastFn(validate),
67
101
  };
68
- };
102
+ }
@@ -108,3 +108,73 @@ describe(enumType, () => {
108
108
  });
109
109
  });
110
110
  });
111
+
112
+ describe('enumType with allowAnyString', () => {
113
+ const color = enumType(['red', 'green', 'blue'], {
114
+ allowAnyString: true,
115
+ defaultValue: '',
116
+ });
117
+
118
+ type Color = TypeOf<typeof color>;
119
+
120
+ // Autocomplete is preserved for the listed members via the open-union idiom,
121
+ // while any other string is still part of the type.
122
+ expectType<Color, 'red' | 'green' | 'blue' | (string & {})>('=');
123
+
124
+ expectType<typeof color.defaultValue, Color>('=');
125
+
126
+ describe('is', () => {
127
+ test('accepts a listed member', () => {
128
+ assert.isTrue(color.is('red'));
129
+ });
130
+
131
+ test('accepts any other string at runtime (behaves like string())', () => {
132
+ assert.isTrue(color.is('purple'));
133
+ });
134
+
135
+ test('rejects non-string values', () => {
136
+ assert.isFalse(color.is(42));
137
+ });
138
+ });
139
+
140
+ describe('validate', () => {
141
+ test('returns an unlisted string as-is', () => {
142
+ const result = color.validate('purple');
143
+
144
+ assert.isTrue(Result.isOk(result));
145
+
146
+ expect(Result.unwrapThrow(result)).toBe('purple');
147
+ });
148
+
149
+ test('rejects non-string values with a string-type error', () => {
150
+ const result = color.validate(42);
151
+
152
+ assert.isTrue(Result.isErr(result));
153
+
154
+ assert.deepStrictEqual(
155
+ validationErrorsToMessages(Result.unwrapErrThrow(result)),
156
+ [
157
+ 'Error: expected <string> type but <number> type value `42` was passed.',
158
+ ],
159
+ );
160
+ });
161
+ });
162
+
163
+ describe('default value and fill', () => {
164
+ test('uses the configured default value', () => {
165
+ expect(color.defaultValue).toBe('');
166
+ });
167
+
168
+ test('keeps any string', () => {
169
+ const x: unknown = 'anything';
170
+
171
+ expect(color.fill(x)).toBe('anything');
172
+ });
173
+
174
+ test('non-string falls back to the default value', () => {
175
+ const x: unknown = 123;
176
+
177
+ expect(color.fill(x)).toBe('');
178
+ });
179
+ });
180
+ });