@evolu/common 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.
- package/dist/src/Number.d.ts +66 -6
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +11 -3
- package/dist/src/Schedule.d.ts +2 -2
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Task.d.ts +2 -1
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +3 -3
- package/dist/src/Time.d.ts +70 -34
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +77 -1
- package/dist/src/Type.d.ts +456 -62
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +605 -46
- package/dist/src/Types.d.ts +0 -24
- package/dist/src/Types.d.ts.map +1 -1
- package/dist/src/intl/cs.d.ts +11 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +11 -1
- package/package.json +1 -1
- package/src/Number.ts +85 -14
- package/src/Schedule.ts +1 -1
- package/src/Task.ts +3 -4
- package/src/Time.ts +108 -62
- package/src/Type.ts +1196 -75
- package/src/Types.ts +0 -49
- package/src/intl/cs.ts +36 -1
package/src/Type.ts
CHANGED
|
@@ -36,10 +36,7 @@ import {
|
|
|
36
36
|
instance,
|
|
37
37
|
isInstance,
|
|
38
38
|
type CompileTimeError,
|
|
39
|
-
type Digit,
|
|
40
|
-
type Digit1To9,
|
|
41
39
|
type Instance,
|
|
42
|
-
type Int1To100,
|
|
43
40
|
type IsUnion,
|
|
44
41
|
type Literal,
|
|
45
42
|
type Simplify,
|
|
@@ -144,8 +141,8 @@ import {
|
|
|
144
141
|
* normalized value, it uses a `null` prototype so every string key remains
|
|
145
142
|
* ordinary data.
|
|
146
143
|
*
|
|
147
|
-
* Evolu Type
|
|
148
|
-
* to be enabled.
|
|
144
|
+
* Evolu Type requires TypeScript 7 or newer and expects the
|
|
145
|
+
* `exactOptionalPropertyTypes` compiler option to be enabled.
|
|
149
146
|
*
|
|
150
147
|
* Predefined Types intentionally use the names of corresponding JavaScript
|
|
151
148
|
* built-ins because they represent those familiar value categories. If an
|
|
@@ -863,7 +860,7 @@ const assertTypeOutput = <Error extends TypeError>(
|
|
|
863
860
|
};
|
|
864
861
|
|
|
865
862
|
/**
|
|
866
|
-
*
|
|
863
|
+
* Localized copies of selected {@link Type} declarations for every locale.
|
|
867
864
|
*
|
|
868
865
|
* Each locale supplies one formatter for every Type that can own a formatted
|
|
869
866
|
* error. Structural Types use their own formatter for structural failures and
|
|
@@ -2128,10 +2125,13 @@ type RuntimeEncoder = RuntimeOperation<unknown>;
|
|
|
2128
2125
|
|
|
2129
2126
|
declare const encoderSymbolType: unique symbol;
|
|
2130
2127
|
declare const fromSymbolType: unique symbol;
|
|
2128
|
+
declare const templateLiteralSyntaxSymbolType: unique symbol;
|
|
2131
2129
|
const encoderSymbol: typeof encoderSymbolType =
|
|
2132
2130
|
/*#__PURE__*/ globalThis.Symbol() as typeof encoderSymbolType;
|
|
2133
2131
|
const fromSymbol: typeof fromSymbolType =
|
|
2134
2132
|
/*#__PURE__*/ globalThis.Symbol() as typeof fromSymbolType;
|
|
2133
|
+
const templateLiteralSyntaxSymbol: typeof templateLiteralSyntaxSymbolType =
|
|
2134
|
+
/*#__PURE__*/ globalThis.Symbol() as typeof templateLiteralSyntaxSymbolType;
|
|
2135
2135
|
|
|
2136
2136
|
/**
|
|
2137
2137
|
* Type-erased {@link Type} used to traverse and invoke heterogeneous Type nodes
|
|
@@ -2154,6 +2154,7 @@ type RuntimeTypeNode = Omit<TypeNode, typeof customFromSymbol> & {
|
|
|
2154
2154
|
// assertion, avoiding repeated validation and preserving identity fast paths.
|
|
2155
2155
|
readonly [encoderSymbolType]: RuntimeEncoder;
|
|
2156
2156
|
readonly [getRuntimeTypeIssuesSymbolType]: RuntimeGetTypeIssues;
|
|
2157
|
+
readonly [templateLiteralSyntaxSymbolType]?: true;
|
|
2157
2158
|
};
|
|
2158
2159
|
|
|
2159
2160
|
const mapRuntimeResult =
|
|
@@ -2612,7 +2613,7 @@ interface ObjectTagOutputByName {
|
|
|
2612
2613
|
}
|
|
2613
2614
|
|
|
2614
2615
|
/**
|
|
2615
|
-
*
|
|
2616
|
+
* Realm-neutral {@link Type} trusting an object's reported tag.
|
|
2616
2617
|
*
|
|
2617
2618
|
* Predefined built-in tags expose their native Output type under the assumption
|
|
2618
2619
|
* that trusted code does not forge their tags. They do not verify native
|
|
@@ -2720,7 +2721,7 @@ export const Uint8Array = /*#__PURE__*/ objectTag("Uint8Array");
|
|
|
2720
2721
|
export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
|
|
2721
2722
|
|
|
2722
2723
|
/**
|
|
2723
|
-
*
|
|
2724
|
+
* Instance {@link Type} for one constructor.
|
|
2724
2725
|
*
|
|
2725
2726
|
* Membership uses the intrinsic prototype chain, so subclasses are accepted,
|
|
2726
2727
|
* equivalent constructors from other realms are rejected, and custom
|
|
@@ -2831,6 +2832,26 @@ type InstanceConstructorCompileTimeError = CompileTimeError<
|
|
|
2831
2832
|
* primitive through `from.parent`. The expected value must have one exact
|
|
2832
2833
|
* literal type. Validation uses `===`, so `-0` matches `0`.
|
|
2833
2834
|
*
|
|
2835
|
+
* In {@link templateLiteralParser}, use a string Literal Type when the literal
|
|
2836
|
+
* should be decoded into the Output Tuple. Use a raw string when it should only
|
|
2837
|
+
* frame the canonical string.
|
|
2838
|
+
*
|
|
2839
|
+
* ### Example
|
|
2840
|
+
*
|
|
2841
|
+
* ```ts
|
|
2842
|
+
* import { literal } from "@evolu/common";
|
|
2843
|
+
*
|
|
2844
|
+
* const Ready = literal("ready");
|
|
2845
|
+
*
|
|
2846
|
+
* expectTypeOf<typeof Ready.Output>().toEqualTypeOf<"ready">();
|
|
2847
|
+
* expectOk(Ready.fromUnknown("ready"), "ready");
|
|
2848
|
+
* expectErr(Ready.fromUnknown("pending"), {
|
|
2849
|
+
* type: "Literal",
|
|
2850
|
+
* expected: "ready",
|
|
2851
|
+
* value: "pending",
|
|
2852
|
+
* });
|
|
2853
|
+
* ```
|
|
2854
|
+
*
|
|
2834
2855
|
* @group Unions
|
|
2835
2856
|
*/
|
|
2836
2857
|
export const literal = <const Expected extends Literal>(
|
|
@@ -2867,7 +2888,10 @@ export const literal = <const Expected extends Literal>(
|
|
|
2867
2888
|
formatError,
|
|
2868
2889
|
)
|
|
2869
2890
|
: createRootType("Literal", validate, formatError),
|
|
2870
|
-
{
|
|
2891
|
+
{
|
|
2892
|
+
expected: literalExpected,
|
|
2893
|
+
[templateLiteralSyntaxSymbol]: true,
|
|
2894
|
+
},
|
|
2871
2895
|
) as unknown as LiteralType<Expected>;
|
|
2872
2896
|
};
|
|
2873
2897
|
|
|
@@ -2886,6 +2910,7 @@ export interface LiteralType<Expected extends Literal> extends Type<
|
|
|
2886
2910
|
>,
|
|
2887
2911
|
IdentityEncodingForParent<LiteralParent<Expected>>
|
|
2888
2912
|
> {
|
|
2913
|
+
readonly [templateLiteralSyntaxSymbol]: true;
|
|
2889
2914
|
readonly expected: Expected;
|
|
2890
2915
|
}
|
|
2891
2916
|
|
|
@@ -3072,7 +3097,7 @@ export function union(
|
|
|
3072
3097
|
from,
|
|
3073
3098
|
to,
|
|
3074
3099
|
getTypeIssues,
|
|
3075
|
-
{ members },
|
|
3100
|
+
{ members, [templateLiteralSyntaxSymbol]: true },
|
|
3076
3101
|
);
|
|
3077
3102
|
}
|
|
3078
3103
|
|
|
@@ -3116,7 +3141,7 @@ const createUnionValidation =
|
|
|
3116
3141
|
};
|
|
3117
3142
|
|
|
3118
3143
|
/**
|
|
3119
|
-
*
|
|
3144
|
+
* Union {@link Type} containing the supplied Type and `undefined`.
|
|
3120
3145
|
*
|
|
3121
3146
|
* This does not make an object property optional. It changes only the values
|
|
3122
3147
|
* accepted when the property is present.
|
|
@@ -3128,7 +3153,7 @@ export const undefinedOr = <ValueType extends TypeNode>(
|
|
|
3128
3153
|
): UnionType<readonly [ValueType, typeof Undefined]> => union(type, Undefined);
|
|
3129
3154
|
|
|
3130
3155
|
/**
|
|
3131
|
-
*
|
|
3156
|
+
* Union {@link Type} containing the supplied Type and `null`.
|
|
3132
3157
|
*
|
|
3133
3158
|
* @group Unions
|
|
3134
3159
|
*/
|
|
@@ -3137,7 +3162,7 @@ export const nullOr = <ValueType extends TypeNode>(
|
|
|
3137
3162
|
): UnionType<readonly [ValueType, typeof Null]> => union(type, Null);
|
|
3138
3163
|
|
|
3139
3164
|
/**
|
|
3140
|
-
*
|
|
3165
|
+
* Union {@link Type} containing the supplied Type, `null`, and `undefined`.
|
|
3141
3166
|
*
|
|
3142
3167
|
* @group Unions
|
|
3143
3168
|
*/
|
|
@@ -3194,6 +3219,7 @@ export interface UnionType<
|
|
|
3194
3219
|
CanonicalInputOf<Members[number]>,
|
|
3195
3220
|
AllTypesUseIdentityEncoding<Members[number]>
|
|
3196
3221
|
> {
|
|
3222
|
+
readonly [templateLiteralSyntaxSymbol]: true;
|
|
3197
3223
|
readonly [reflectedTypesSymbol]?: Members[number];
|
|
3198
3224
|
readonly members: Members;
|
|
3199
3225
|
}
|
|
@@ -3326,6 +3352,849 @@ interface UnionErrorValue<
|
|
|
3326
3352
|
readonly errors: NonEmptyReadonlyArray<MemberError>;
|
|
3327
3353
|
}
|
|
3328
3354
|
|
|
3355
|
+
/**
|
|
3356
|
+
* Template literal {@link Type} for validation and parsing.
|
|
3357
|
+
*
|
|
3358
|
+
* Parses and creates structured strings.
|
|
3359
|
+
*
|
|
3360
|
+
* Accepts the same template parts as {@link templateLiteral}: fixed string
|
|
3361
|
+
* literals and Types canonically encoded as strings. Instead of keeping Output
|
|
3362
|
+
* as a string, fixed literals define the framing and Output is a readonly Tuple
|
|
3363
|
+
* of the decoded Type parts. `to` encodes that Tuple back into the canonical
|
|
3364
|
+
* string represented by the parent Type. At least one Type part is required.
|
|
3365
|
+
*
|
|
3366
|
+
* When every capture uses identity encoding, the parent Output is the exact
|
|
3367
|
+
* TypeScript template literal type. A transforming capture makes it nominal;
|
|
3368
|
+
* create such strings with `to` or validate them with the parent Type.
|
|
3369
|
+
*
|
|
3370
|
+
* Deterministic framing is a core correctness guarantee. It preserves
|
|
3371
|
+
* reversibility and keeps capture boundaries unambiguous. Different capture
|
|
3372
|
+
* Tuples must never encode to the same string. The parser provides predictable
|
|
3373
|
+
* parsing without pathological backtracking and decodes each capture once, so
|
|
3374
|
+
* adversarial input cannot trigger exponential parser work. Fixed-width captures
|
|
3375
|
+
* may be adjacent, but only one variable-width capture is allowed. Declarations
|
|
3376
|
+
* that could join UTF-16 surrogate halves across parts are rejected during
|
|
3377
|
+
* construction.
|
|
3378
|
+
*
|
|
3379
|
+
* Keep capture unions reasonably small to avoid excessive compiler work.
|
|
3380
|
+
*
|
|
3381
|
+
* TypeScript template literal types can describe a fixed number of digit
|
|
3382
|
+
* positions, but not an arbitrarily long sequence of digits. Such grammars use
|
|
3383
|
+
* branded Types such as {@link DecimalString}; `templateLiteralParser` preserves
|
|
3384
|
+
* that exactness by requiring a validated branded capture when encoding.
|
|
3385
|
+
*
|
|
3386
|
+
* ### Example
|
|
3387
|
+
*
|
|
3388
|
+
* A template literal Type defines both a canonical string representation and
|
|
3389
|
+
* the structured data decoded from it:
|
|
3390
|
+
*
|
|
3391
|
+
* ```ts
|
|
3392
|
+
* import { templateLiteralParser, union } from "@evolu/common";
|
|
3393
|
+
*
|
|
3394
|
+
* const Language = union("en", "cs");
|
|
3395
|
+
* const Region = union("US", "CZ");
|
|
3396
|
+
*
|
|
3397
|
+
* // Define a Type for "en-US" | "en-CZ" | "cs-US" | "cs-CZ".
|
|
3398
|
+
* const SupportedLocale = templateLiteralParser(Language, "-", Region);
|
|
3399
|
+
*
|
|
3400
|
+
* // Output is the decoded language and region.
|
|
3401
|
+
* type SupportedLocale = typeof SupportedLocale.Output;
|
|
3402
|
+
* expectTypeOf<SupportedLocale>().toEqualTypeOf<
|
|
3403
|
+
* readonly ["en" | "cs", "US" | "CZ"]
|
|
3404
|
+
* >();
|
|
3405
|
+
*
|
|
3406
|
+
* // The parent Output is the canonical locale string.
|
|
3407
|
+
* type SupportedLocaleLiteral = typeof SupportedLocale.parent.Output;
|
|
3408
|
+
* expectTypeOf<SupportedLocaleLiteral>().toEqualTypeOf<
|
|
3409
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
3410
|
+
* >();
|
|
3411
|
+
*
|
|
3412
|
+
* // Parse an unknown string into structured data.
|
|
3413
|
+
* const result = SupportedLocale.fromUnknown("cs-CZ");
|
|
3414
|
+
* assert(result.ok);
|
|
3415
|
+
* const locale = result.value;
|
|
3416
|
+
* expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
|
|
3417
|
+
* expect(locale).toEqual(["cs", "CZ"]);
|
|
3418
|
+
* expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
|
|
3419
|
+
* type: "TemplateLiteral",
|
|
3420
|
+
* value: "cs/CZ",
|
|
3421
|
+
* });
|
|
3422
|
+
*
|
|
3423
|
+
* // Encode structured data into its canonical string.
|
|
3424
|
+
* const localeLiteral = SupportedLocale.to(locale);
|
|
3425
|
+
* expectTypeOf(localeLiteral).toEqualTypeOf<SupportedLocaleLiteral>();
|
|
3426
|
+
* expect(localeLiteral).toBe("cs-CZ");
|
|
3427
|
+
*
|
|
3428
|
+
* // Validate a string configuration value.
|
|
3429
|
+
* const configValue: unknown = "cs-CZ";
|
|
3430
|
+
* assert(SupportedLocale.parent.is(configValue));
|
|
3431
|
+
* expectTypeOf(configValue).toEqualTypeOf<SupportedLocaleLiteral>();
|
|
3432
|
+
* expect(SupportedLocale.parent.is("fr-CZ")).toBe(false);
|
|
3433
|
+
* ```
|
|
3434
|
+
*
|
|
3435
|
+
* `SupportedLocale` is structured data for application code.
|
|
3436
|
+
* `SupportedLocaleLiteral` is its canonical representation for configuration
|
|
3437
|
+
* and other APIs that require a string, such as URL parameters, environment
|
|
3438
|
+
* variables, and storage keys.
|
|
3439
|
+
*
|
|
3440
|
+
* Use branded captures for strings that TypeScript template literal types
|
|
3441
|
+
* cannot express exactly, such as arbitrary-length canonical decimals:
|
|
3442
|
+
*
|
|
3443
|
+
* ```ts
|
|
3444
|
+
* import {
|
|
3445
|
+
* NonNegativeDecimalString,
|
|
3446
|
+
* templateLiteralParser,
|
|
3447
|
+
* } from "@evolu/common";
|
|
3448
|
+
*
|
|
3449
|
+
* const DecimalText = templateLiteralParser(
|
|
3450
|
+
* "decimal:",
|
|
3451
|
+
* NonNegativeDecimalString,
|
|
3452
|
+
* );
|
|
3453
|
+
*
|
|
3454
|
+
* // DecimalText.to requires a validated NonNegativeDecimalString.
|
|
3455
|
+
* const zero = NonNegativeDecimalString.orThrow("0");
|
|
3456
|
+
*
|
|
3457
|
+
* expectOk(DecimalText.fromUnknown("decimal:0"), [zero]);
|
|
3458
|
+
* expect(DecimalText.to([zero])).toBe("decimal:0");
|
|
3459
|
+
* ```
|
|
3460
|
+
*
|
|
3461
|
+
* Capture Types (the Type arguments passed to `templateLiteralParser`) can use
|
|
3462
|
+
* transformations to decode substrings into non-string data:
|
|
3463
|
+
*
|
|
3464
|
+
* ```ts
|
|
3465
|
+
* import {
|
|
3466
|
+
* Int64FromInt64String,
|
|
3467
|
+
* templateLiteralParser,
|
|
3468
|
+
* } from "@evolu/common";
|
|
3469
|
+
*
|
|
3470
|
+
* const ItemId = templateLiteralParser("item-", Int64FromInt64String);
|
|
3471
|
+
* type ItemId = typeof ItemId.Output;
|
|
3472
|
+
* type ItemIdLiteral = typeof ItemId.parent.Output;
|
|
3473
|
+
*
|
|
3474
|
+
* // Decode the string into structured data.
|
|
3475
|
+
* const result = ItemId.fromUnknown("item-42");
|
|
3476
|
+
* assert(result.ok);
|
|
3477
|
+
* const itemId = result.value;
|
|
3478
|
+
* expectTypeOf(itemId).toEqualTypeOf<ItemId>();
|
|
3479
|
+
* expect(itemId).toEqual([42n]);
|
|
3480
|
+
*
|
|
3481
|
+
* // Encode the structured data into its canonical string.
|
|
3482
|
+
* const itemIdLiteral = ItemId.to(itemId);
|
|
3483
|
+
* expectTypeOf(itemIdLiteral).toEqualTypeOf<ItemIdLiteral>();
|
|
3484
|
+
* expect(itemIdLiteral).toBe("item-42");
|
|
3485
|
+
*
|
|
3486
|
+
* // TypeScript cannot prove from the literal alone that "42" is a valid Int64 encoding.
|
|
3487
|
+
* // @ts-expect-error Validate it with ItemId.parent or create it with ItemId.to.
|
|
3488
|
+
* const invalidItemIdLiteral: ItemIdLiteral = "item-42";
|
|
3489
|
+
* ```
|
|
3490
|
+
*
|
|
3491
|
+
* Fixed-width captures can be adjacent:
|
|
3492
|
+
*
|
|
3493
|
+
* ```ts
|
|
3494
|
+
* import { templateLiteralParser, union } from "@evolu/common";
|
|
3495
|
+
*
|
|
3496
|
+
* const Digit = union("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
|
|
3497
|
+
* const TwoDigits = templateLiteralParser(Digit, Digit);
|
|
3498
|
+
* type TwoDigits = typeof TwoDigits.Output;
|
|
3499
|
+
* type TwoDigitsLiteral = typeof TwoDigits.parent.Output;
|
|
3500
|
+
*
|
|
3501
|
+
* const twoDigits: TwoDigits = ["4", "2"];
|
|
3502
|
+
* const twoDigitsLiteral: TwoDigitsLiteral = "42";
|
|
3503
|
+
* // @ts-expect-error TwoDigitsLiteral requires exactly two digits.
|
|
3504
|
+
* const threeDigitsLiteral: TwoDigitsLiteral = "123";
|
|
3505
|
+
*
|
|
3506
|
+
* expectOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
|
|
3507
|
+
* expect(TwoDigits.to(twoDigits)).toBe(twoDigitsLiteral);
|
|
3508
|
+
* ```
|
|
3509
|
+
*
|
|
3510
|
+
* TypeScript rejects multiple variable-width captures because their encoded
|
|
3511
|
+
* boundaries would be ambiguous:
|
|
3512
|
+
*
|
|
3513
|
+
* ```ts
|
|
3514
|
+
* import { String, templateLiteralParser } from "@evolu/common";
|
|
3515
|
+
*
|
|
3516
|
+
* // @ts-expect-error At most one Type capture can have a variable-width string representation.
|
|
3517
|
+
* templateLiteralParser(String, ":", String);
|
|
3518
|
+
* ```
|
|
3519
|
+
*
|
|
3520
|
+
* This restriction keeps encoding reversible: different capture Tuples must
|
|
3521
|
+
* never produce the same string. A delimiter alone is not enough because it can
|
|
3522
|
+
* also occur inside a capture. Some formats could provide stronger guarantees,
|
|
3523
|
+
* such as captures that exclude a delimiter; support for those can be added
|
|
3524
|
+
* when concrete use cases justify the additional framing rules.
|
|
3525
|
+
*
|
|
3526
|
+
* @group Template literals
|
|
3527
|
+
*/
|
|
3528
|
+
export const templateLiteralParser = <const Parts extends TemplateLiteralParts>(
|
|
3529
|
+
...parts: {
|
|
3530
|
+
readonly [Index in keyof Parts]: ValidateTemplateLiteralPart<Parts[Index]>;
|
|
3531
|
+
} & TemplateLiteralValidation<Parts>
|
|
3532
|
+
): TemplateLiteralParserType<Parts> =>
|
|
3533
|
+
createTemplateLiteralParserType(parts as unknown as Parts);
|
|
3534
|
+
|
|
3535
|
+
const createTemplateLiteralParserType = <
|
|
3536
|
+
const Parts extends TemplateLiteralParts,
|
|
3537
|
+
>(
|
|
3538
|
+
templateParts: Parts,
|
|
3539
|
+
): TemplateLiteralParserType<Parts> => {
|
|
3540
|
+
const captureTypes = templateParts.filter(
|
|
3541
|
+
(part): part is TypeNode => typeof part !== "string",
|
|
3542
|
+
) as unknown as TemplateLiteralCaptureTypes<Parts>;
|
|
3543
|
+
const runtimeCaptureTypes =
|
|
3544
|
+
captureTypes as unknown as NonEmptyReadonlyArray<RuntimeTypeNode>;
|
|
3545
|
+
const output = tuple(...captureTypes);
|
|
3546
|
+
const runtimeOutput = output as TemplateLiteralCaptureTuple<Parts> &
|
|
3547
|
+
RuntimeTypeNode;
|
|
3548
|
+
const reflection = {
|
|
3549
|
+
output,
|
|
3550
|
+
parts: templateParts,
|
|
3551
|
+
[templateLiteralSyntaxSymbol]: true as const,
|
|
3552
|
+
};
|
|
3553
|
+
const parse = compileTemplateLiteralParser(templateParts);
|
|
3554
|
+
const decodeString = (
|
|
3555
|
+
value: string,
|
|
3556
|
+
options: ValidationOptions = firstValidationOptions,
|
|
3557
|
+
): Result<
|
|
3558
|
+
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3559
|
+
TemplateLiteralRuntimeParseError<Parts>
|
|
3560
|
+
> => {
|
|
3561
|
+
const parseResult = parse(value);
|
|
3562
|
+
if (!parseResult.ok) return parseResult;
|
|
3563
|
+
|
|
3564
|
+
const outputResult = validateTupleItems(
|
|
3565
|
+
parseResult.value,
|
|
3566
|
+
runtimeCaptureTypes,
|
|
3567
|
+
(capture, value, captureOptions) =>
|
|
3568
|
+
capture.fromUnknown(value, captureOptions),
|
|
3569
|
+
options,
|
|
3570
|
+
false,
|
|
3571
|
+
);
|
|
3572
|
+
return (outputResult.ok
|
|
3573
|
+
? outputResult
|
|
3574
|
+
: err({
|
|
3575
|
+
type: "TemplateLiteral",
|
|
3576
|
+
outputError: outputResult.error,
|
|
3577
|
+
})) as Result<
|
|
3578
|
+
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3579
|
+
TemplateLiteralRuntimeParseError<Parts>
|
|
3580
|
+
>;
|
|
3581
|
+
};
|
|
3582
|
+
const encodeCaptures = (
|
|
3583
|
+
captures: TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3584
|
+
): TemplateLiteralStringOutput<Parts> => {
|
|
3585
|
+
const encodedCaptures = runtimeOutput[encoderSymbol](
|
|
3586
|
+
captures as never,
|
|
3587
|
+
) as ReadonlyArray<string>;
|
|
3588
|
+
let value = "";
|
|
3589
|
+
let captureIndex = 0;
|
|
3590
|
+
|
|
3591
|
+
for (const part of templateParts) {
|
|
3592
|
+
value +=
|
|
3593
|
+
typeof part === "string" ? part : encodedCaptures[captureIndex++];
|
|
3594
|
+
}
|
|
3595
|
+
return value as TemplateLiteralStringOutput<Parts>;
|
|
3596
|
+
};
|
|
3597
|
+
const canonicalizeString = (
|
|
3598
|
+
value: string,
|
|
3599
|
+
options: ValidationOptions,
|
|
3600
|
+
): Result<
|
|
3601
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3602
|
+
TemplateLiteralRuntimeParseError<Parts>
|
|
3603
|
+
> => {
|
|
3604
|
+
const result = decodeString(value, options);
|
|
3605
|
+
return result.ok ? ok(encodeCaptures(result.value)) : result;
|
|
3606
|
+
};
|
|
3607
|
+
const validateCanonicalString = (
|
|
3608
|
+
value: unknown,
|
|
3609
|
+
options: ValidationOptions = firstValidationOptions,
|
|
3610
|
+
): Result<
|
|
3611
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3612
|
+
TypeOfError<"String"> | TemplateLiteralRuntimeParseError<Parts>
|
|
3613
|
+
> => {
|
|
3614
|
+
const stringResult = String.fromUnknown(value, options);
|
|
3615
|
+
if (!stringResult.ok) return stringResult;
|
|
3616
|
+
|
|
3617
|
+
const result = canonicalizeString(stringResult.value, options);
|
|
3618
|
+
if (!result.ok || result.value === stringResult.value) return result;
|
|
3619
|
+
|
|
3620
|
+
return err({ type: "TemplateLiteral", value: stringResult.value });
|
|
3621
|
+
};
|
|
3622
|
+
const getTypeIssues: RuntimeGetTypeIssues = (error, mode) => {
|
|
3623
|
+
if (error.type !== "TemplateLiteral") {
|
|
3624
|
+
return (String as unknown as RuntimeTypeNode)[getRuntimeTypeIssuesSymbol](
|
|
3625
|
+
error,
|
|
3626
|
+
mode,
|
|
3627
|
+
);
|
|
3628
|
+
}
|
|
3629
|
+
if ("outputError" in error) {
|
|
3630
|
+
return runtimeOutput[getRuntimeTypeIssuesSymbol](
|
|
3631
|
+
error.outputError as TypeError,
|
|
3632
|
+
mode,
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
return singleRuntimeTypeIssue(
|
|
3636
|
+
"TemplateLiteral",
|
|
3637
|
+
error,
|
|
3638
|
+
formatTemplateLiteralError as TypeErrorFormatter<TypeError>,
|
|
3639
|
+
);
|
|
3640
|
+
};
|
|
3641
|
+
const canonicalStringFromUnknown = (
|
|
3642
|
+
value: unknown,
|
|
3643
|
+
options: ValidationOptions = firstValidationOptions,
|
|
3644
|
+
): Result<
|
|
3645
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3646
|
+
TypeOfError<"String"> | TemplateLiteralRuntimeParseError<Parts>
|
|
3647
|
+
> => {
|
|
3648
|
+
const stringResult = String.fromUnknown(value, options);
|
|
3649
|
+
return stringResult.ok
|
|
3650
|
+
? canonicalizeString(stringResult.value, options)
|
|
3651
|
+
: stringResult;
|
|
3652
|
+
};
|
|
3653
|
+
const canonicalStringFrom = createFromOperation(
|
|
3654
|
+
(value, options = firstValidationOptions) =>
|
|
3655
|
+
canonicalizeString(value, options),
|
|
3656
|
+
);
|
|
3657
|
+
const stringType = createTypeNode<TemplateLiteralType<Parts>>(
|
|
3658
|
+
"TemplateLiteral",
|
|
3659
|
+
String,
|
|
3660
|
+
canonicalStringFromUnknown,
|
|
3661
|
+
(value) => validateCanonicalString(value, firstValidationOptions).ok,
|
|
3662
|
+
validateCanonicalString,
|
|
3663
|
+
canonicalStringFrom,
|
|
3664
|
+
identity,
|
|
3665
|
+
getTypeIssues,
|
|
3666
|
+
reflection,
|
|
3667
|
+
) as TemplateLiteralType<Parts> & RuntimeTypeNode;
|
|
3668
|
+
const fromUnknown = (
|
|
3669
|
+
value: unknown,
|
|
3670
|
+
options: ValidationOptions = firstValidationOptions,
|
|
3671
|
+
): Result<
|
|
3672
|
+
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3673
|
+
InferErrors<TemplateLiteralType<Parts>>
|
|
3674
|
+
> => {
|
|
3675
|
+
const stringResult = String.fromUnknown(value, options);
|
|
3676
|
+
if (!stringResult.ok) return stringResult;
|
|
3677
|
+
|
|
3678
|
+
// The internal parser has one broad signature, but a statically frameless
|
|
3679
|
+
// declaration cannot return its framing error.
|
|
3680
|
+
return decodeString(stringResult.value, options) as Result<
|
|
3681
|
+
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3682
|
+
InferErrors<TemplateLiteralType<Parts>>
|
|
3683
|
+
>;
|
|
3684
|
+
};
|
|
3685
|
+
const fromCanonicalString: RuntimeOperation<Result<unknown, TypeError>> = (
|
|
3686
|
+
value: never,
|
|
3687
|
+
options = firstValidationOptions,
|
|
3688
|
+
) => {
|
|
3689
|
+
String.from(value);
|
|
3690
|
+
const result = decodeString(value, options);
|
|
3691
|
+
if (!result.ok || encodeCaptures(result.value) !== value) {
|
|
3692
|
+
throw new Error("Expected TemplateLiteral.", {
|
|
3693
|
+
cause: result.ok
|
|
3694
|
+
? ({
|
|
3695
|
+
type: "TemplateLiteral",
|
|
3696
|
+
value,
|
|
3697
|
+
} satisfies TemplateLiteralError)
|
|
3698
|
+
: result.error,
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
return result;
|
|
3702
|
+
};
|
|
3703
|
+
const fromString: RuntimeOperation<Result<unknown, TypeError>> = (
|
|
3704
|
+
value: never,
|
|
3705
|
+
options = firstValidationOptions,
|
|
3706
|
+
) => {
|
|
3707
|
+
String.from(value);
|
|
3708
|
+
return decodeString(value, options);
|
|
3709
|
+
};
|
|
3710
|
+
fromCanonicalString.parent = fromString;
|
|
3711
|
+
const from = createFromOperation(fromCanonicalString);
|
|
3712
|
+
const type = createTypeNode<TemplateLiteralParserType<Parts>>(
|
|
3713
|
+
"TemplateLiteral",
|
|
3714
|
+
stringType,
|
|
3715
|
+
fromUnknown,
|
|
3716
|
+
runtimeOutput.is,
|
|
3717
|
+
runtimeOutput[outputValidationSymbol],
|
|
3718
|
+
from,
|
|
3719
|
+
encodeCaptures,
|
|
3720
|
+
getTypeIssues,
|
|
3721
|
+
reflection,
|
|
3722
|
+
);
|
|
3723
|
+
(type.from as RuntimeOperation<Result<unknown, TypeError>>).parent =
|
|
3724
|
+
fromCanonicalString;
|
|
3725
|
+
|
|
3726
|
+
return type;
|
|
3727
|
+
};
|
|
3728
|
+
|
|
3729
|
+
/** @group Template literals */
|
|
3730
|
+
export interface TemplateLiteralParserType<
|
|
3731
|
+
Parts extends TemplateLiteralParts,
|
|
3732
|
+
> extends Type<
|
|
3733
|
+
"TemplateLiteral",
|
|
3734
|
+
string,
|
|
3735
|
+
TemplateLiteralCaptureTuple<Parts>["Output"],
|
|
3736
|
+
never,
|
|
3737
|
+
TemplateLiteralType<Parts>,
|
|
3738
|
+
InferErrors<TemplateLiteralType<Parts>>,
|
|
3739
|
+
never,
|
|
3740
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3741
|
+
false
|
|
3742
|
+
> {
|
|
3743
|
+
readonly [templateLiteralSyntaxSymbol]: true;
|
|
3744
|
+
readonly [reflectedTypesSymbol]?: TemplateLiteralCaptureTuple<Parts>;
|
|
3745
|
+
readonly output: TemplateLiteralCaptureTuple<Parts>;
|
|
3746
|
+
readonly parts: Parts;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
/** @group Template literals */
|
|
3750
|
+
export interface TemplateLiteralType<
|
|
3751
|
+
Parts extends TemplateLiteralParts,
|
|
3752
|
+
> extends Type<
|
|
3753
|
+
"TemplateLiteral",
|
|
3754
|
+
string,
|
|
3755
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3756
|
+
TemplateLiteralParseError<Parts>,
|
|
3757
|
+
typeof String,
|
|
3758
|
+
TypeOfError<"String"> | TemplateLiteralParseError<Parts>,
|
|
3759
|
+
never,
|
|
3760
|
+
TemplateLiteralStringOutput<Parts>,
|
|
3761
|
+
true
|
|
3762
|
+
> {
|
|
3763
|
+
readonly [templateLiteralSyntaxSymbol]: true;
|
|
3764
|
+
readonly [reflectedTypesSymbol]?: TemplateLiteralCaptureTuple<Parts>;
|
|
3765
|
+
readonly output: TemplateLiteralCaptureTuple<Parts>;
|
|
3766
|
+
readonly parts: Parts;
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
/**
|
|
3770
|
+
* Template literal {@link Type} for validation.
|
|
3771
|
+
*
|
|
3772
|
+
* Creates a canonical string Type from fixed strings and string-encoded Types.
|
|
3773
|
+
*
|
|
3774
|
+
* Use this factory when Output should remain a string. Switch to
|
|
3775
|
+
* {@link templateLiteralParser} when the individual Type parts should be
|
|
3776
|
+
* decoded into a Tuple.
|
|
3777
|
+
*
|
|
3778
|
+
* ### Example
|
|
3779
|
+
*
|
|
3780
|
+
* ```ts
|
|
3781
|
+
* import { templateLiteral, union } from "@evolu/common";
|
|
3782
|
+
*
|
|
3783
|
+
* const Language = union("en", "cs");
|
|
3784
|
+
* const Region = union("US", "CZ");
|
|
3785
|
+
* const Locale = templateLiteral(Language, "-", Region);
|
|
3786
|
+
*
|
|
3787
|
+
* expectTypeOf<typeof Locale.Output>().toEqualTypeOf<
|
|
3788
|
+
* "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
|
|
3789
|
+
* >();
|
|
3790
|
+
* expectOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
|
|
3791
|
+
* expect(Locale.is("fr-CZ")).toBe(false);
|
|
3792
|
+
* ```
|
|
3793
|
+
*
|
|
3794
|
+
* @group Template literals
|
|
3795
|
+
*/
|
|
3796
|
+
export const templateLiteral = <const Parts extends TemplateLiteralParts>(
|
|
3797
|
+
...parts: {
|
|
3798
|
+
readonly [Index in keyof Parts]: ValidateTemplateLiteralPart<Parts[Index]>;
|
|
3799
|
+
} & TemplateLiteralValidation<Parts>
|
|
3800
|
+
): TemplateLiteralType<Parts> =>
|
|
3801
|
+
createTemplateLiteralParserType(parts as unknown as Parts).parent;
|
|
3802
|
+
|
|
3803
|
+
type TemplateLiteralParseError<Parts extends TemplateLiteralParts> =
|
|
3804
|
+
TransformError<
|
|
3805
|
+
"TemplateLiteral",
|
|
3806
|
+
TemplateLiteralIsFrameless<Parts> extends true
|
|
3807
|
+
? never
|
|
3808
|
+
: TemplateLiteralError,
|
|
3809
|
+
TemplateLiteralCaptureTupleError<Parts>
|
|
3810
|
+
>;
|
|
3811
|
+
|
|
3812
|
+
type TemplateLiteralRuntimeParseError<Parts extends TemplateLiteralParts> =
|
|
3813
|
+
TransformError<
|
|
3814
|
+
"TemplateLiteral",
|
|
3815
|
+
TemplateLiteralError,
|
|
3816
|
+
TemplateLiteralCaptureTupleError<Parts>
|
|
3817
|
+
>;
|
|
3818
|
+
|
|
3819
|
+
type TemplateLiteralCaptureTupleError<Parts extends TemplateLiteralParts> =
|
|
3820
|
+
TupleElementsError<
|
|
3821
|
+
TemplateLiteralCaptureFromStringError<
|
|
3822
|
+
TemplateLiteralCaptureTypes<Parts>[number]
|
|
3823
|
+
>
|
|
3824
|
+
>;
|
|
3825
|
+
|
|
3826
|
+
type TemplateLiteralCaptureFromStringError<T extends TypeNode> =
|
|
3827
|
+
T extends TypeNode
|
|
3828
|
+
? string extends RootType<T>["Output"]
|
|
3829
|
+
? TypeFromError<T>
|
|
3830
|
+
: InferErrors<T>
|
|
3831
|
+
: never;
|
|
3832
|
+
|
|
3833
|
+
/** @group Template literals */
|
|
3834
|
+
export interface TemplateLiteralError extends TypeError<"TemplateLiteral"> {
|
|
3835
|
+
readonly value: string;
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
const formatTemplateLiteralError: TypeErrorFormatter<TemplateLiteralError> =
|
|
3839
|
+
(error) =>
|
|
3840
|
+
`The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`;
|
|
3841
|
+
|
|
3842
|
+
declare const templateLiteralStringBrandSymbol: unique symbol;
|
|
3843
|
+
|
|
3844
|
+
interface TemplateLiteralStringBrand<Parts extends TemplateLiteralParts> {
|
|
3845
|
+
readonly [templateLiteralStringBrandSymbol]: Parts;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
type TemplateLiteralPart = string | TypeNode;
|
|
3849
|
+
|
|
3850
|
+
type TemplateLiteralParts = NonEmptyReadonlyArray<TemplateLiteralPart>;
|
|
3851
|
+
|
|
3852
|
+
type TemplateLiteralValidation<Parts extends TemplateLiteralParts> =
|
|
3853
|
+
number extends Parts["length"]
|
|
3854
|
+
? readonly [ValidationFailure<TemplateLiteralPartsTupleError>]
|
|
3855
|
+
: IsUnion<Parts["length"]> extends true
|
|
3856
|
+
? readonly [ValidationFailure<TemplateLiteralPartsTupleError>]
|
|
3857
|
+
: [Extract<Parts[number], TypeNode>] extends [never]
|
|
3858
|
+
? readonly [ValidationFailure<TemplateLiteralCaptureRequiredError>]
|
|
3859
|
+
: TemplateLiteralHasAmbiguousCaptures<Parts> extends true
|
|
3860
|
+
? readonly [ValidationFailure<TemplateLiteralAmbiguousCapturesError>]
|
|
3861
|
+
: unknown;
|
|
3862
|
+
|
|
3863
|
+
type TemplateLiteralPartsTupleError = CompileTimeError<
|
|
3864
|
+
"TemplateLiteral",
|
|
3865
|
+
"Parts must use one concrete finite non-empty tuple."
|
|
3866
|
+
>;
|
|
3867
|
+
|
|
3868
|
+
type TemplateLiteralCaptureRequiredError = CompileTimeError<
|
|
3869
|
+
"TemplateLiteral",
|
|
3870
|
+
"At least one part must be a Type capture."
|
|
3871
|
+
>;
|
|
3872
|
+
|
|
3873
|
+
type TemplateLiteralAmbiguousCapturesError = CompileTimeError<
|
|
3874
|
+
"TemplateLiteral",
|
|
3875
|
+
"At most one Type capture can have a variable-width string representation."
|
|
3876
|
+
>;
|
|
3877
|
+
|
|
3878
|
+
type TemplateLiteralCaptureTypes<
|
|
3879
|
+
Parts extends ReadonlyArray<TemplateLiteralPart>,
|
|
3880
|
+
Captures extends ReadonlyArray<TypeNode> = readonly [],
|
|
3881
|
+
> = Parts extends readonly [infer Head, ...infer Tail]
|
|
3882
|
+
? TemplateLiteralCaptureTypes<
|
|
3883
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3884
|
+
Head extends TypeNode ? readonly [...Captures, Head] : Captures
|
|
3885
|
+
>
|
|
3886
|
+
: Extract<Captures, NonEmptyReadonlyArray<TypeNode>>;
|
|
3887
|
+
|
|
3888
|
+
type TemplateLiteralCaptureTuple<Parts extends TemplateLiteralParts> =
|
|
3889
|
+
TupleType<TemplateLiteralCaptureTypes<Parts>>;
|
|
3890
|
+
|
|
3891
|
+
type TemplateLiteralCanonicalInput<
|
|
3892
|
+
Parts extends ReadonlyArray<TemplateLiteralPart>,
|
|
3893
|
+
Input extends string = "",
|
|
3894
|
+
> = Parts extends readonly [infer Head, ...infer Tail]
|
|
3895
|
+
? TemplateLiteralCanonicalInput<
|
|
3896
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3897
|
+
`${Input}${TemplateLiteralPartCanonicalInput<Extract<Head, TemplateLiteralPart>>}`
|
|
3898
|
+
>
|
|
3899
|
+
: Input;
|
|
3900
|
+
|
|
3901
|
+
type TemplateLiteralStringOutput<Parts extends TemplateLiteralParts> =
|
|
3902
|
+
AllTypesUseIdentityEncoding<Extract<Parts[number], TypeNode>> extends true
|
|
3903
|
+
? TemplateLiteralCanonicalInput<Parts>
|
|
3904
|
+
: TemplateLiteralCanonicalInput<Parts> & TemplateLiteralStringBrand<Parts>;
|
|
3905
|
+
|
|
3906
|
+
type TemplateLiteralPartCanonicalInput<Part extends TemplateLiteralPart> =
|
|
3907
|
+
Part extends string
|
|
3908
|
+
? Part
|
|
3909
|
+
: Part extends TypeNode
|
|
3910
|
+
? Extract<CanonicalInputOf<Part>, string>
|
|
3911
|
+
: never;
|
|
3912
|
+
|
|
3913
|
+
type ValidateTemplateLiteralPart<Part extends TemplateLiteralPart> =
|
|
3914
|
+
IsUnion<Part> extends false
|
|
3915
|
+
? Part extends string
|
|
3916
|
+
? ValidateLiteral<Part>
|
|
3917
|
+
: Part extends ConcreteTypeNode
|
|
3918
|
+
? IsTemplateLiteralPartType<Part> extends true
|
|
3919
|
+
? Part
|
|
3920
|
+
: TemplateLiteralPartCompileTimeError
|
|
3921
|
+
: TemplateLiteralPartCompileTimeError
|
|
3922
|
+
: TemplateLiteralPartCompileTimeError;
|
|
3923
|
+
|
|
3924
|
+
type IsTemplateLiteralPartType<T extends TypeNode> = [
|
|
3925
|
+
CanonicalInputOf<T>,
|
|
3926
|
+
] extends [never]
|
|
3927
|
+
? false
|
|
3928
|
+
: [CanonicalInputOf<T>] extends [string]
|
|
3929
|
+
? true
|
|
3930
|
+
: false;
|
|
3931
|
+
|
|
3932
|
+
type TemplateLiteralPartCompileTimeError = CompileTimeError<
|
|
3933
|
+
"TemplateLiteral",
|
|
3934
|
+
"Part must be a raw string literal or a Type canonically encoded as a string."
|
|
3935
|
+
>;
|
|
3936
|
+
|
|
3937
|
+
type TemplateLiteralHasAmbiguousCaptures<
|
|
3938
|
+
Parts extends ReadonlyArray<TemplateLiteralPart>,
|
|
3939
|
+
VariableCaptures extends ReadonlyArray<unknown> = readonly [],
|
|
3940
|
+
> = Parts extends readonly [infer Head, ...infer Tail]
|
|
3941
|
+
? Head extends TypeNode
|
|
3942
|
+
? [TemplateLiteralTypeWidth<Head>] extends [null]
|
|
3943
|
+
? VariableCaptures extends readonly [unknown]
|
|
3944
|
+
? true
|
|
3945
|
+
: TemplateLiteralHasAmbiguousCaptures<
|
|
3946
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3947
|
+
readonly [unknown]
|
|
3948
|
+
>
|
|
3949
|
+
: TemplateLiteralHasAmbiguousCaptures<
|
|
3950
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3951
|
+
VariableCaptures
|
|
3952
|
+
>
|
|
3953
|
+
: TemplateLiteralHasAmbiguousCaptures<
|
|
3954
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3955
|
+
VariableCaptures
|
|
3956
|
+
>
|
|
3957
|
+
: false;
|
|
3958
|
+
|
|
3959
|
+
type TemplateLiteralTypeWidth<T extends TypeNode> =
|
|
3960
|
+
T extends LiteralType<infer Expected extends string>
|
|
3961
|
+
? TemplateLiteralStringWidth<Expected>
|
|
3962
|
+
: T extends UnionType<infer Members>
|
|
3963
|
+
? NormalizeTemplateLiteralWidth<TemplateLiteralTypeWidth<Members[number]>>
|
|
3964
|
+
: T extends TemplateLiteralParserType<infer Parts>
|
|
3965
|
+
? TemplateLiteralPartsWidth<Parts>
|
|
3966
|
+
: T extends TemplateLiteralType<infer Parts>
|
|
3967
|
+
? TemplateLiteralPartsWidth<Parts>
|
|
3968
|
+
: T["parent"] extends infer Parent extends TypeNode
|
|
3969
|
+
? TemplateLiteralTypeWidth<Parent>
|
|
3970
|
+
: null;
|
|
3971
|
+
|
|
3972
|
+
type NormalizeTemplateLiteralWidth<Width> =
|
|
3973
|
+
IsUnion<Width> extends true
|
|
3974
|
+
? null
|
|
3975
|
+
: Width extends ReadonlyArray<unknown>
|
|
3976
|
+
? Width
|
|
3977
|
+
: null;
|
|
3978
|
+
|
|
3979
|
+
type TemplateLiteralPartsWidth<
|
|
3980
|
+
Parts extends ReadonlyArray<TemplateLiteralPart>,
|
|
3981
|
+
Width extends ReadonlyArray<unknown> = readonly [],
|
|
3982
|
+
> = Parts extends readonly [infer Head, ...infer Tail]
|
|
3983
|
+
? TemplateLiteralPartWidth<
|
|
3984
|
+
Extract<Head, TemplateLiteralPart>
|
|
3985
|
+
> extends infer PartWidth
|
|
3986
|
+
? [PartWidth] extends [ReadonlyArray<unknown>]
|
|
3987
|
+
? TemplateLiteralPartsWidth<
|
|
3988
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
3989
|
+
readonly [...Width, ...PartWidth]
|
|
3990
|
+
>
|
|
3991
|
+
: null
|
|
3992
|
+
: never
|
|
3993
|
+
: Width;
|
|
3994
|
+
|
|
3995
|
+
type TemplateLiteralPartWidth<Part extends TemplateLiteralPart> =
|
|
3996
|
+
Part extends string
|
|
3997
|
+
? TemplateLiteralStringWidth<Part>
|
|
3998
|
+
: Part extends TypeNode
|
|
3999
|
+
? TemplateLiteralTypeWidth<Part>
|
|
4000
|
+
: never;
|
|
4001
|
+
|
|
4002
|
+
type TemplateLiteralIsFrameless<
|
|
4003
|
+
Parts extends ReadonlyArray<TemplateLiteralPart>,
|
|
4004
|
+
HasVariableCapture extends boolean = false,
|
|
4005
|
+
> = Parts extends readonly [infer Head, ...infer Tail]
|
|
4006
|
+
? TemplateLiteralPartWidth<
|
|
4007
|
+
Extract<Head, TemplateLiteralPart>
|
|
4008
|
+
> extends infer Width
|
|
4009
|
+
? [Width] extends [null]
|
|
4010
|
+
? HasVariableCapture extends true
|
|
4011
|
+
? false
|
|
4012
|
+
: TemplateLiteralIsFrameless<
|
|
4013
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
4014
|
+
true
|
|
4015
|
+
>
|
|
4016
|
+
: [Width] extends [readonly []]
|
|
4017
|
+
? TemplateLiteralIsFrameless<
|
|
4018
|
+
Extract<Tail, ReadonlyArray<TemplateLiteralPart>>,
|
|
4019
|
+
HasVariableCapture
|
|
4020
|
+
>
|
|
4021
|
+
: false
|
|
4022
|
+
: false
|
|
4023
|
+
: HasVariableCapture;
|
|
4024
|
+
|
|
4025
|
+
type TemplateLiteralStringWidth<
|
|
4026
|
+
Value extends string,
|
|
4027
|
+
Width extends ReadonlyArray<unknown> = readonly [],
|
|
4028
|
+
> = string extends Value
|
|
4029
|
+
? null
|
|
4030
|
+
: Value extends ""
|
|
4031
|
+
? Width
|
|
4032
|
+
: Value extends `${infer _CodePoint}${infer Tail}`
|
|
4033
|
+
? TemplateLiteralStringWidth<Tail, readonly [...Width, unknown]>
|
|
4034
|
+
: null;
|
|
4035
|
+
|
|
4036
|
+
interface TemplateLiteralFraming {
|
|
4037
|
+
readonly width: number | null;
|
|
4038
|
+
readonly canBeEmpty: boolean;
|
|
4039
|
+
readonly canStartWithLowSurrogate: boolean;
|
|
4040
|
+
readonly canEndWithHighSurrogate: boolean;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
const compileTemplateLiteralParser = <Parts extends TemplateLiteralParts>(
|
|
4044
|
+
parts: Parts,
|
|
4045
|
+
): ((
|
|
4046
|
+
input: string,
|
|
4047
|
+
) => Result<
|
|
4048
|
+
TemplateLiteralCaptureTuple<Parts>["Input"],
|
|
4049
|
+
TemplateLiteralError
|
|
4050
|
+
>) => {
|
|
4051
|
+
let framing = emptyTemplateLiteralFraming;
|
|
4052
|
+
let fixedPartsWidth = 0;
|
|
4053
|
+
const compiledParts = parts.map((part) => {
|
|
4054
|
+
const partFraming = getTemplateLiteralPartFraming(part);
|
|
4055
|
+
framing = concatenateTemplateLiteralFraming(framing, partFraming);
|
|
4056
|
+
fixedPartsWidth += partFraming.width ?? 0;
|
|
4057
|
+
return [part, partFraming.width] as const;
|
|
4058
|
+
});
|
|
4059
|
+
|
|
4060
|
+
return (input) => {
|
|
4061
|
+
const inputCodePoints = globalThis.Array.from(input);
|
|
4062
|
+
const variableWidth = inputCodePoints.length - fixedPartsWidth;
|
|
4063
|
+
if (variableWidth < 0) {
|
|
4064
|
+
return err({ type: "TemplateLiteral", value: input });
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
const captures: Array<string> = [];
|
|
4068
|
+
let position = 0;
|
|
4069
|
+
|
|
4070
|
+
for (const [part, width] of compiledParts) {
|
|
4071
|
+
const partWidth = width ?? variableWidth;
|
|
4072
|
+
const value = inputCodePoints
|
|
4073
|
+
.slice(position, position + partWidth)
|
|
4074
|
+
.join("");
|
|
4075
|
+
|
|
4076
|
+
if (typeof part === "string") {
|
|
4077
|
+
if (value !== part) {
|
|
4078
|
+
return err({ type: "TemplateLiteral", value: input });
|
|
4079
|
+
}
|
|
4080
|
+
} else {
|
|
4081
|
+
captures.push(value);
|
|
4082
|
+
}
|
|
4083
|
+
position += partWidth;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
return position === inputCodePoints.length
|
|
4087
|
+
? ok(
|
|
4088
|
+
captures as unknown as TemplateLiteralCaptureTuple<Parts>["Input"],
|
|
4089
|
+
)
|
|
4090
|
+
: err({ type: "TemplateLiteral", value: input });
|
|
4091
|
+
};
|
|
4092
|
+
};
|
|
4093
|
+
|
|
4094
|
+
const emptyTemplateLiteralFraming: TemplateLiteralFraming = {
|
|
4095
|
+
width: 0,
|
|
4096
|
+
canBeEmpty: true,
|
|
4097
|
+
canStartWithLowSurrogate: false,
|
|
4098
|
+
canEndWithHighSurrogate: false,
|
|
4099
|
+
};
|
|
4100
|
+
|
|
4101
|
+
const unknownTemplateLiteralFraming: TemplateLiteralFraming = {
|
|
4102
|
+
width: null,
|
|
4103
|
+
canBeEmpty: true,
|
|
4104
|
+
canStartWithLowSurrogate: true,
|
|
4105
|
+
canEndWithHighSurrogate: true,
|
|
4106
|
+
};
|
|
4107
|
+
|
|
4108
|
+
const concatenateTemplateLiteralFraming = (
|
|
4109
|
+
left: TemplateLiteralFraming,
|
|
4110
|
+
right: TemplateLiteralFraming,
|
|
4111
|
+
): TemplateLiteralFraming => {
|
|
4112
|
+
assert(
|
|
4113
|
+
!(left.canEndWithHighSurrogate && right.canStartWithLowSurrogate),
|
|
4114
|
+
"A TemplateLiteral cannot form a Unicode surrogate pair across part boundaries.",
|
|
4115
|
+
);
|
|
4116
|
+
|
|
4117
|
+
return {
|
|
4118
|
+
width:
|
|
4119
|
+
left.width !== null && right.width !== null
|
|
4120
|
+
? left.width + right.width
|
|
4121
|
+
: null,
|
|
4122
|
+
canBeEmpty: left.canBeEmpty && right.canBeEmpty,
|
|
4123
|
+
canStartWithLowSurrogate:
|
|
4124
|
+
left.canStartWithLowSurrogate ||
|
|
4125
|
+
(left.canBeEmpty && right.canStartWithLowSurrogate),
|
|
4126
|
+
canEndWithHighSurrogate:
|
|
4127
|
+
right.canEndWithHighSurrogate ||
|
|
4128
|
+
(right.canBeEmpty && left.canEndWithHighSurrogate),
|
|
4129
|
+
};
|
|
4130
|
+
};
|
|
4131
|
+
|
|
4132
|
+
const getStringTemplateLiteralFraming = (
|
|
4133
|
+
value: string,
|
|
4134
|
+
): TemplateLiteralFraming => {
|
|
4135
|
+
const firstCodeUnit = value.charCodeAt(0);
|
|
4136
|
+
const lastCodeUnit = value.charCodeAt(value.length - 1);
|
|
4137
|
+
|
|
4138
|
+
return {
|
|
4139
|
+
width: globalThis.Array.from(value).length,
|
|
4140
|
+
canBeEmpty: value.length === 0,
|
|
4141
|
+
canStartWithLowSurrogate:
|
|
4142
|
+
firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff,
|
|
4143
|
+
canEndWithHighSurrogate: lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff,
|
|
4144
|
+
};
|
|
4145
|
+
};
|
|
4146
|
+
|
|
4147
|
+
const getTemplateLiteralPartFraming = (
|
|
4148
|
+
part: TemplateLiteralPart,
|
|
4149
|
+
): TemplateLiteralFraming => {
|
|
4150
|
+
if (typeof part === "string") return getStringTemplateLiteralFraming(part);
|
|
4151
|
+
|
|
4152
|
+
const type = part as RuntimeTypeNode;
|
|
4153
|
+
if (type[templateLiteralSyntaxSymbol] === true) {
|
|
4154
|
+
if (type.name === "Literal") {
|
|
4155
|
+
return getStringTemplateLiteralFraming(
|
|
4156
|
+
(type as unknown as LiteralType<string>).expected,
|
|
4157
|
+
);
|
|
4158
|
+
}
|
|
4159
|
+
if (type.name === "Union") {
|
|
4160
|
+
const memberFramings = (
|
|
4161
|
+
type as unknown as RuntimeUnionTypeNode
|
|
4162
|
+
).members.map(getTemplateLiteralPartFraming);
|
|
4163
|
+
const width = memberFramings[0].width;
|
|
4164
|
+
|
|
4165
|
+
return {
|
|
4166
|
+
width:
|
|
4167
|
+
width !== null &&
|
|
4168
|
+
memberFramings.every((framing) => framing.width === width)
|
|
4169
|
+
? width
|
|
4170
|
+
: null,
|
|
4171
|
+
canBeEmpty: memberFramings.some((framing) => framing.canBeEmpty),
|
|
4172
|
+
canStartWithLowSurrogate: memberFramings.some(
|
|
4173
|
+
(framing) => framing.canStartWithLowSurrogate,
|
|
4174
|
+
),
|
|
4175
|
+
canEndWithHighSurrogate: memberFramings.some(
|
|
4176
|
+
(framing) => framing.canEndWithHighSurrogate,
|
|
4177
|
+
),
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
if (type.name === "TemplateLiteral") {
|
|
4181
|
+
return (
|
|
4182
|
+
type as unknown as { readonly parts: TemplateLiteralParts }
|
|
4183
|
+
).parts.reduce(
|
|
4184
|
+
(framing, part) =>
|
|
4185
|
+
concatenateTemplateLiteralFraming(
|
|
4186
|
+
framing,
|
|
4187
|
+
getTemplateLiteralPartFraming(part),
|
|
4188
|
+
),
|
|
4189
|
+
emptyTemplateLiteralFraming,
|
|
4190
|
+
);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
if (type.parent === null) return unknownTemplateLiteralFraming;
|
|
4194
|
+
|
|
4195
|
+
return getTemplateLiteralPartFraming(type.parent);
|
|
4196
|
+
};
|
|
4197
|
+
|
|
3329
4198
|
/**
|
|
3330
4199
|
* Branded {@link Type}.
|
|
3331
4200
|
*
|
|
@@ -3809,7 +4678,7 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
|
|
|
3809
4678
|
export type CapitalizedString = typeof CapitalizedString.Output;
|
|
3810
4679
|
|
|
3811
4680
|
/**
|
|
3812
|
-
*
|
|
4681
|
+
* String {@link Brand} without surrounding whitespace.
|
|
3813
4682
|
*
|
|
3814
4683
|
* @group String
|
|
3815
4684
|
*/
|
|
@@ -3855,7 +4724,7 @@ export const trim = (value: string): TrimmedString =>
|
|
|
3855
4724
|
value.trim() as TrimmedString;
|
|
3856
4725
|
|
|
3857
4726
|
/**
|
|
3858
|
-
*
|
|
4727
|
+
* Minimum-length {@link Brand} requiring a value to have at least `min` items.
|
|
3859
4728
|
*
|
|
3860
4729
|
* @group String
|
|
3861
4730
|
* @group Collection
|
|
@@ -3905,7 +4774,7 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
|
|
|
3905
4774
|
export type NonEmptyTrimmedString = typeof NonEmptyTrimmedString.Output;
|
|
3906
4775
|
|
|
3907
4776
|
/**
|
|
3908
|
-
*
|
|
4777
|
+
* Maximum-length {@link Brand} requiring a value to have at most `max` items.
|
|
3909
4778
|
*
|
|
3910
4779
|
* @group String
|
|
3911
4780
|
* @group Collection
|
|
@@ -3958,7 +4827,7 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(
|
|
|
3958
4827
|
export type NonEmptyTrimmedString1000 = typeof NonEmptyTrimmedString1000.Output;
|
|
3959
4828
|
|
|
3960
4829
|
/**
|
|
3961
|
-
*
|
|
4830
|
+
* Exact-length {@link Brand} requiring a value to have exactly `exact` items.
|
|
3962
4831
|
*
|
|
3963
4832
|
* @group String
|
|
3964
4833
|
* @group Collection
|
|
@@ -3991,7 +4860,7 @@ export interface LengthError<
|
|
|
3991
4860
|
}
|
|
3992
4861
|
|
|
3993
4862
|
/**
|
|
3994
|
-
*
|
|
4863
|
+
* String {@link Brand} constrained by a regular expression.
|
|
3995
4864
|
*
|
|
3996
4865
|
* ### Example
|
|
3997
4866
|
*
|
|
@@ -4318,7 +5187,7 @@ export const createIdAsUuidv7 = <B extends string = never>(
|
|
|
4318
5187
|
};
|
|
4319
5188
|
|
|
4320
5189
|
/**
|
|
4321
|
-
*
|
|
5190
|
+
* Table-specific {@link Id} Type.
|
|
4322
5191
|
*
|
|
4323
5192
|
* @group String
|
|
4324
5193
|
*/
|
|
@@ -4475,7 +5344,7 @@ export const Int64FromInt64String = /*#__PURE__*/ transform(
|
|
|
4475
5344
|
);
|
|
4476
5345
|
|
|
4477
5346
|
/**
|
|
4478
|
-
*
|
|
5347
|
+
* Number {@link Brand} requiring a value greater than or equal to zero.
|
|
4479
5348
|
*
|
|
4480
5349
|
* @group Number
|
|
4481
5350
|
*/
|
|
@@ -4507,7 +5376,7 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
|
|
|
4507
5376
|
export type NonNegativeNumber = typeof NonNegativeNumber.Output;
|
|
4508
5377
|
|
|
4509
5378
|
/**
|
|
4510
|
-
*
|
|
5379
|
+
* Number {@link Brand} requiring a value greater than zero.
|
|
4511
5380
|
*
|
|
4512
5381
|
* @group Number
|
|
4513
5382
|
*/
|
|
@@ -4540,7 +5409,7 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
|
|
|
4540
5409
|
export type PositiveNumber = typeof PositiveNumber.Output;
|
|
4541
5410
|
|
|
4542
5411
|
/**
|
|
4543
|
-
*
|
|
5412
|
+
* Number {@link Brand} requiring a value less than or equal to zero.
|
|
4544
5413
|
*
|
|
4545
5414
|
* @group Number
|
|
4546
5415
|
*/
|
|
@@ -4572,7 +5441,7 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
|
|
|
4572
5441
|
export type NonPositiveNumber = typeof NonPositiveNumber.Output;
|
|
4573
5442
|
|
|
4574
5443
|
/**
|
|
4575
|
-
*
|
|
5444
|
+
* Number {@link Brand} requiring a value less than zero.
|
|
4576
5445
|
*
|
|
4577
5446
|
* @group Number
|
|
4578
5447
|
*/
|
|
@@ -4605,7 +5474,7 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
|
|
|
4605
5474
|
export type NegativeNumber = typeof NegativeNumber.Output;
|
|
4606
5475
|
|
|
4607
5476
|
/**
|
|
4608
|
-
*
|
|
5477
|
+
* Number {@link Brand} requiring a value other than `NaN`.
|
|
4609
5478
|
*
|
|
4610
5479
|
* @group Number
|
|
4611
5480
|
*/
|
|
@@ -4639,7 +5508,7 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
|
|
|
4639
5508
|
export type NonNaNNumber = typeof NonNaNNumber.Output;
|
|
4640
5509
|
|
|
4641
5510
|
/**
|
|
4642
|
-
*
|
|
5511
|
+
* Number {@link Brand} requiring a finite value.
|
|
4643
5512
|
*
|
|
4644
5513
|
* @group Number
|
|
4645
5514
|
*/
|
|
@@ -4741,13 +5610,6 @@ export type Int = typeof Int.Output;
|
|
|
4741
5610
|
export const NonNegativeInt = /*#__PURE__*/ nonNegative(Int);
|
|
4742
5611
|
export type NonNegativeInt = typeof NonNegativeInt.Output;
|
|
4743
5612
|
|
|
4744
|
-
/**
|
|
4745
|
-
* 0-100 as a literal, or any already-validated {@link NonNegativeInt}.
|
|
4746
|
-
*
|
|
4747
|
-
* @group Number
|
|
4748
|
-
*/
|
|
4749
|
-
export type Int0To100OrNonNegativeInt = 0 | Int1To100 | NonNegativeInt;
|
|
4750
|
-
|
|
4751
5613
|
/**
|
|
4752
5614
|
* Minimum {@link NonNegativeInt} value.
|
|
4753
5615
|
*
|
|
@@ -4766,13 +5628,6 @@ export const zeroNonNegativeInt = /*#__PURE__*/ NonNegativeInt.orThrow(0);
|
|
|
4766
5628
|
export const PositiveInt = /*#__PURE__*/ positive(NonNegativeInt);
|
|
4767
5629
|
export type PositiveInt = typeof PositiveInt.Output;
|
|
4768
5630
|
|
|
4769
|
-
/**
|
|
4770
|
-
* 1-100 as a literal, or any already-validated {@link PositiveInt}.
|
|
4771
|
-
*
|
|
4772
|
-
* @group Number
|
|
4773
|
-
*/
|
|
4774
|
-
export type Int1To100OrPositiveInt = Int1To100 | PositiveInt;
|
|
4775
|
-
|
|
4776
5631
|
/**
|
|
4777
5632
|
* Minimum {@link PositiveInt} value.
|
|
4778
5633
|
*
|
|
@@ -4808,7 +5663,7 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
|
|
|
4808
5663
|
export type NegativeInt = typeof NegativeInt.Output;
|
|
4809
5664
|
|
|
4810
5665
|
/**
|
|
4811
|
-
*
|
|
5666
|
+
* Number {@link Brand} requiring a value greater than `min`.
|
|
4812
5667
|
*
|
|
4813
5668
|
* @group Number
|
|
4814
5669
|
*/
|
|
@@ -4840,7 +5695,7 @@ export interface GreaterThanError<
|
|
|
4840
5695
|
}
|
|
4841
5696
|
|
|
4842
5697
|
/**
|
|
4843
|
-
*
|
|
5698
|
+
* Number {@link Brand} requiring a value greater than or equal to `min`.
|
|
4844
5699
|
*
|
|
4845
5700
|
* @group Number
|
|
4846
5701
|
*/
|
|
@@ -4880,7 +5735,7 @@ export interface GreaterThanOrEqualToError<
|
|
|
4880
5735
|
}
|
|
4881
5736
|
|
|
4882
5737
|
/**
|
|
4883
|
-
*
|
|
5738
|
+
* Number {@link Brand} requiring a value less than `max`.
|
|
4884
5739
|
*
|
|
4885
5740
|
* @group Number
|
|
4886
5741
|
*/
|
|
@@ -4923,7 +5778,7 @@ export const Age = /*#__PURE__*/ brand(
|
|
|
4923
5778
|
export type Age = typeof Age.Output;
|
|
4924
5779
|
|
|
4925
5780
|
/**
|
|
4926
|
-
*
|
|
5781
|
+
* Number {@link Brand} requiring a value less than or equal to `max`.
|
|
4927
5782
|
*
|
|
4928
5783
|
* @group Number
|
|
4929
5784
|
*/
|
|
@@ -4972,51 +5827,124 @@ export const Ratio = /*#__PURE__*/ brand(
|
|
|
4972
5827
|
export type Ratio = typeof Ratio.Output;
|
|
4973
5828
|
|
|
4974
5829
|
/**
|
|
4975
|
-
* Canonical string representation of a
|
|
5830
|
+
* Canonical string representation of a signed base-10 decimal value.
|
|
4976
5831
|
*
|
|
4977
5832
|
* Use this Type when a decimal value must remain exact instead of being
|
|
4978
5833
|
* converted to an IEEE-754 number. Equivalent values have one accepted
|
|
4979
|
-
* representation, so leading zeroes, trailing fractional zeroes,
|
|
4980
|
-
* exponent notation are rejected.
|
|
5834
|
+
* representation, so leading zeroes, trailing fractional zeroes, `-0`, plus
|
|
5835
|
+
* signs, and exponent notation are rejected.
|
|
4981
5836
|
*
|
|
4982
5837
|
* The decoded value remains a string. Arithmetic requires an explicit decimal
|
|
4983
5838
|
* or fixed-point representation.
|
|
4984
5839
|
*
|
|
5840
|
+
* TypeScript template literal types can describe a fixed number of digit
|
|
5841
|
+
* positions, but not the arbitrarily long integer and fractional parts accepted
|
|
5842
|
+
* here. `DecimalString` therefore uses a {@link Brand} so its TypeScript type
|
|
5843
|
+
* does not accept strings that have not been validated.
|
|
5844
|
+
*
|
|
5845
|
+
* Use these predefined Types or their corresponding factories to add sign
|
|
5846
|
+
* constraints to compatible decimal string Types:
|
|
5847
|
+
*
|
|
5848
|
+
* - {@link NonNegativeDecimalString} / {@link nonNegativeDecimalString}
|
|
5849
|
+
* - {@link PositiveDecimalString} / {@link positiveDecimalString}
|
|
5850
|
+
* - {@link NonPositiveDecimalString} / {@link nonPositiveDecimalString}
|
|
5851
|
+
* - {@link NegativeDecimalString} / {@link negativeDecimalString}
|
|
5852
|
+
*
|
|
4985
5853
|
* ### Example
|
|
4986
5854
|
*
|
|
4987
5855
|
* ```ts
|
|
4988
|
-
* import {
|
|
5856
|
+
* import { DecimalString } from "@evolu/common";
|
|
4989
5857
|
*
|
|
4990
|
-
* expectOk(
|
|
4991
|
-
* expectOk(
|
|
4992
|
-
* expectOk(
|
|
5858
|
+
* expectOk(DecimalString.fromUnknown("-10.25"), "-10.25");
|
|
5859
|
+
* expectOk(DecimalString.fromUnknown("0"), "0");
|
|
5860
|
+
* expectOk(DecimalString.fromUnknown("10.25"), "10.25");
|
|
4993
5861
|
*
|
|
4994
|
-
* expectErr(
|
|
4995
|
-
* type: "
|
|
4996
|
-
* value: "
|
|
4997
|
-
* });
|
|
4998
|
-
* expectErr(PositiveDecimalString.fromUnknown("0.30"), {
|
|
4999
|
-
* type: "PositiveDecimalString",
|
|
5000
|
-
* value: "0.30",
|
|
5862
|
+
* expectErr(DecimalString.fromUnknown("10.250"), {
|
|
5863
|
+
* type: "DecimalString",
|
|
5864
|
+
* value: "10.250",
|
|
5001
5865
|
* });
|
|
5002
5866
|
* ```
|
|
5003
5867
|
*
|
|
5004
5868
|
* @group Number
|
|
5005
5869
|
*/
|
|
5006
|
-
export const
|
|
5007
|
-
"
|
|
5870
|
+
export const DecimalString = /*#__PURE__*/ brand(
|
|
5871
|
+
"DecimalString",
|
|
5008
5872
|
String,
|
|
5009
5873
|
(value) =>
|
|
5010
|
-
/^(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9])$/.test(value)
|
|
5874
|
+
/^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))$/.test(value)
|
|
5011
5875
|
? ok()
|
|
5012
|
-
: err<
|
|
5013
|
-
type: "PositiveDecimalString",
|
|
5014
|
-
value,
|
|
5015
|
-
}),
|
|
5876
|
+
: err<DecimalStringError>({ type: "DecimalString", value }),
|
|
5016
5877
|
(error) =>
|
|
5017
|
-
`The value ${safelyStringifyUnknownValue(error.value)} must be a canonical
|
|
5878
|
+
`The value ${safelyStringifyUnknownValue(error.value)} must be a canonical decimal string.`,
|
|
5018
5879
|
);
|
|
5019
|
-
export type
|
|
5880
|
+
export type DecimalString = typeof DecimalString.Output;
|
|
5881
|
+
|
|
5882
|
+
/** @group Number */
|
|
5883
|
+
export interface DecimalStringError extends TypeError<"DecimalString"> {
|
|
5884
|
+
readonly value: string;
|
|
5885
|
+
}
|
|
5886
|
+
|
|
5887
|
+
/**
|
|
5888
|
+
* {@link DecimalString} Brand requiring a value greater than or equal to zero.
|
|
5889
|
+
*
|
|
5890
|
+
* @group Number
|
|
5891
|
+
*/
|
|
5892
|
+
export const nonNegativeDecimalString: BrandFactory<
|
|
5893
|
+
"NonNegativeDecimalString",
|
|
5894
|
+
DecimalString,
|
|
5895
|
+
NonNegativeDecimalStringError
|
|
5896
|
+
> = (parent) =>
|
|
5897
|
+
brand(
|
|
5898
|
+
"NonNegativeDecimalString",
|
|
5899
|
+
parent,
|
|
5900
|
+
(value) =>
|
|
5901
|
+
value[0] !== "-"
|
|
5902
|
+
? ok()
|
|
5903
|
+
: err<NonNegativeDecimalStringError>({
|
|
5904
|
+
type: "NonNegativeDecimalString",
|
|
5905
|
+
value,
|
|
5906
|
+
}),
|
|
5907
|
+
(error) =>
|
|
5908
|
+
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-negative decimal string.`,
|
|
5909
|
+
);
|
|
5910
|
+
|
|
5911
|
+
/** @group Number */
|
|
5912
|
+
export interface NonNegativeDecimalStringError extends TypeError<"NonNegativeDecimalString"> {
|
|
5913
|
+
readonly value: string;
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
/**
|
|
5917
|
+
* Non-negative {@link DecimalString}.
|
|
5918
|
+
*
|
|
5919
|
+
* @group Number
|
|
5920
|
+
*/
|
|
5921
|
+
export const NonNegativeDecimalString =
|
|
5922
|
+
/*#__PURE__*/ nonNegativeDecimalString(DecimalString);
|
|
5923
|
+
export type NonNegativeDecimalString = typeof NonNegativeDecimalString.Output;
|
|
5924
|
+
|
|
5925
|
+
/**
|
|
5926
|
+
* {@link DecimalString} Brand requiring a value greater than zero.
|
|
5927
|
+
*
|
|
5928
|
+
* @group Number
|
|
5929
|
+
*/
|
|
5930
|
+
export const positiveDecimalString: BrandFactory<
|
|
5931
|
+
"PositiveDecimalString",
|
|
5932
|
+
DecimalString,
|
|
5933
|
+
PositiveDecimalStringError
|
|
5934
|
+
> = (parent) =>
|
|
5935
|
+
brand(
|
|
5936
|
+
"PositiveDecimalString",
|
|
5937
|
+
parent,
|
|
5938
|
+
(value) =>
|
|
5939
|
+
value !== "0" && value[0] !== "-"
|
|
5940
|
+
? ok()
|
|
5941
|
+
: err<PositiveDecimalStringError>({
|
|
5942
|
+
type: "PositiveDecimalString",
|
|
5943
|
+
value,
|
|
5944
|
+
}),
|
|
5945
|
+
(error) =>
|
|
5946
|
+
`The value ${safelyStringifyUnknownValue(error.value)} must be a positive decimal string.`,
|
|
5947
|
+
);
|
|
5020
5948
|
|
|
5021
5949
|
/** @group Number */
|
|
5022
5950
|
export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalString"> {
|
|
@@ -5024,8 +5952,100 @@ export interface PositiveDecimalStringError extends TypeError<"PositiveDecimalSt
|
|
|
5024
5952
|
}
|
|
5025
5953
|
|
|
5026
5954
|
/**
|
|
5027
|
-
*
|
|
5028
|
-
*
|
|
5955
|
+
* Positive {@link DecimalString}.
|
|
5956
|
+
*
|
|
5957
|
+
* Also satisfies {@link NonNegativeDecimalString}, so it can be used wherever a
|
|
5958
|
+
* non-negative decimal string is required.
|
|
5959
|
+
*
|
|
5960
|
+
* @group Number
|
|
5961
|
+
*/
|
|
5962
|
+
export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(
|
|
5963
|
+
NonNegativeDecimalString,
|
|
5964
|
+
);
|
|
5965
|
+
export type PositiveDecimalString = typeof PositiveDecimalString.Output;
|
|
5966
|
+
|
|
5967
|
+
/**
|
|
5968
|
+
* {@link DecimalString} Brand requiring a value less than or equal to zero.
|
|
5969
|
+
*
|
|
5970
|
+
* @group Number
|
|
5971
|
+
*/
|
|
5972
|
+
export const nonPositiveDecimalString: BrandFactory<
|
|
5973
|
+
"NonPositiveDecimalString",
|
|
5974
|
+
DecimalString,
|
|
5975
|
+
NonPositiveDecimalStringError
|
|
5976
|
+
> = (parent) =>
|
|
5977
|
+
brand(
|
|
5978
|
+
"NonPositiveDecimalString",
|
|
5979
|
+
parent,
|
|
5980
|
+
(value) =>
|
|
5981
|
+
value === "0" || value[0] === "-"
|
|
5982
|
+
? ok()
|
|
5983
|
+
: err<NonPositiveDecimalStringError>({
|
|
5984
|
+
type: "NonPositiveDecimalString",
|
|
5985
|
+
value,
|
|
5986
|
+
}),
|
|
5987
|
+
(error) =>
|
|
5988
|
+
`The value ${safelyStringifyUnknownValue(error.value)} must be a non-positive decimal string.`,
|
|
5989
|
+
);
|
|
5990
|
+
|
|
5991
|
+
/** @group Number */
|
|
5992
|
+
export interface NonPositiveDecimalStringError extends TypeError<"NonPositiveDecimalString"> {
|
|
5993
|
+
readonly value: string;
|
|
5994
|
+
}
|
|
5995
|
+
|
|
5996
|
+
/**
|
|
5997
|
+
* Non-positive {@link DecimalString}.
|
|
5998
|
+
*
|
|
5999
|
+
* @group Number
|
|
6000
|
+
*/
|
|
6001
|
+
export const NonPositiveDecimalString =
|
|
6002
|
+
/*#__PURE__*/ nonPositiveDecimalString(DecimalString);
|
|
6003
|
+
export type NonPositiveDecimalString = typeof NonPositiveDecimalString.Output;
|
|
6004
|
+
|
|
6005
|
+
/**
|
|
6006
|
+
* {@link DecimalString} Brand requiring a value less than zero.
|
|
6007
|
+
*
|
|
6008
|
+
* @group Number
|
|
6009
|
+
*/
|
|
6010
|
+
export const negativeDecimalString: BrandFactory<
|
|
6011
|
+
"NegativeDecimalString",
|
|
6012
|
+
DecimalString,
|
|
6013
|
+
NegativeDecimalStringError
|
|
6014
|
+
> = (parent) =>
|
|
6015
|
+
brand(
|
|
6016
|
+
"NegativeDecimalString",
|
|
6017
|
+
parent,
|
|
6018
|
+
(value) =>
|
|
6019
|
+
value[0] === "-"
|
|
6020
|
+
? ok()
|
|
6021
|
+
: err<NegativeDecimalStringError>({
|
|
6022
|
+
type: "NegativeDecimalString",
|
|
6023
|
+
value,
|
|
6024
|
+
}),
|
|
6025
|
+
(error) =>
|
|
6026
|
+
`The value ${safelyStringifyUnknownValue(error.value)} must be a negative decimal string.`,
|
|
6027
|
+
);
|
|
6028
|
+
|
|
6029
|
+
/** @group Number */
|
|
6030
|
+
export interface NegativeDecimalStringError extends TypeError<"NegativeDecimalString"> {
|
|
6031
|
+
readonly value: string;
|
|
6032
|
+
}
|
|
6033
|
+
|
|
6034
|
+
/**
|
|
6035
|
+
* Negative {@link DecimalString}.
|
|
6036
|
+
*
|
|
6037
|
+
* Also satisfies {@link NonPositiveDecimalString}, so it can be used wherever a
|
|
6038
|
+
* non-positive decimal string is required.
|
|
6039
|
+
*
|
|
6040
|
+
* @group Number
|
|
6041
|
+
*/
|
|
6042
|
+
export const NegativeDecimalString = /*#__PURE__*/ negativeDecimalString(
|
|
6043
|
+
NonPositiveDecimalString,
|
|
6044
|
+
);
|
|
6045
|
+
export type NegativeDecimalString = typeof NegativeDecimalString.Output;
|
|
6046
|
+
|
|
6047
|
+
/**
|
|
6048
|
+
* Number {@link Brand} requiring an exact decimal multiple of `divisor`.
|
|
5029
6049
|
*
|
|
5030
6050
|
* The divisor must be one canonical positive decimal string literal because its
|
|
5031
6051
|
* exact value is encoded in the resulting Brand name. The declaration is
|
|
@@ -5175,7 +6195,7 @@ const decimalStringToParts = (value: string): DecimalParts => {
|
|
|
5175
6195
|
};
|
|
5176
6196
|
|
|
5177
6197
|
/**
|
|
5178
|
-
*
|
|
6198
|
+
* Number {@link Brand} requiring a value within an inclusive range.
|
|
5179
6199
|
*
|
|
5180
6200
|
* @group Number
|
|
5181
6201
|
*/
|
|
@@ -6465,6 +7485,106 @@ const validateTupleItems = (
|
|
|
6465
7485
|
checkStructure,
|
|
6466
7486
|
);
|
|
6467
7487
|
|
|
7488
|
+
/**
|
|
7489
|
+
* Decimal digit from `"0"` to `"9"`.
|
|
7490
|
+
*
|
|
7491
|
+
* @group String
|
|
7492
|
+
*/
|
|
7493
|
+
export const Digit = /*#__PURE__*/ union(
|
|
7494
|
+
"0",
|
|
7495
|
+
"1",
|
|
7496
|
+
"2",
|
|
7497
|
+
"3",
|
|
7498
|
+
"4",
|
|
7499
|
+
"5",
|
|
7500
|
+
"6",
|
|
7501
|
+
"7",
|
|
7502
|
+
"8",
|
|
7503
|
+
"9",
|
|
7504
|
+
);
|
|
7505
|
+
export type Digit = typeof Digit.Output;
|
|
7506
|
+
|
|
7507
|
+
/**
|
|
7508
|
+
* Decimal digit from `"1"` to `"9"`.
|
|
7509
|
+
*
|
|
7510
|
+
* @group String
|
|
7511
|
+
*/
|
|
7512
|
+
export const Digit1To9 = /*#__PURE__*/ union(
|
|
7513
|
+
"1",
|
|
7514
|
+
"2",
|
|
7515
|
+
"3",
|
|
7516
|
+
"4",
|
|
7517
|
+
"5",
|
|
7518
|
+
"6",
|
|
7519
|
+
"7",
|
|
7520
|
+
"8",
|
|
7521
|
+
"9",
|
|
7522
|
+
);
|
|
7523
|
+
export type Digit1To9 = typeof Digit1To9.Output;
|
|
7524
|
+
|
|
7525
|
+
/**
|
|
7526
|
+
* Decimal string from `"1"` to `"6"`.
|
|
7527
|
+
*
|
|
7528
|
+
* @group String
|
|
7529
|
+
*/
|
|
7530
|
+
export const Digit1To6 = /*#__PURE__*/ union("1", "2", "3", "4", "5", "6");
|
|
7531
|
+
export type Digit1To6 = typeof Digit1To6.Output;
|
|
7532
|
+
|
|
7533
|
+
/**
|
|
7534
|
+
* Decimal string from `"1"` to `"23"`.
|
|
7535
|
+
*
|
|
7536
|
+
* @group String
|
|
7537
|
+
*/
|
|
7538
|
+
export const Digit1To23 = /*#__PURE__*/ union(
|
|
7539
|
+
Digit1To9,
|
|
7540
|
+
/*#__PURE__*/ templateLiteral("1", Digit),
|
|
7541
|
+
/*#__PURE__*/ templateLiteral(
|
|
7542
|
+
"2",
|
|
7543
|
+
/*#__PURE__*/ union("0", "1", "2", "3"),
|
|
7544
|
+
),
|
|
7545
|
+
);
|
|
7546
|
+
export type Digit1To23 = typeof Digit1To23.Output;
|
|
7547
|
+
|
|
7548
|
+
/**
|
|
7549
|
+
* Decimal string from `"1"` to `"51"`.
|
|
7550
|
+
*
|
|
7551
|
+
* @group String
|
|
7552
|
+
*/
|
|
7553
|
+
export const Digit1To51 = /*#__PURE__*/ union(
|
|
7554
|
+
Digit1To9,
|
|
7555
|
+
/*#__PURE__*/ templateLiteral(
|
|
7556
|
+
/*#__PURE__*/ union("1", "2", "3", "4"),
|
|
7557
|
+
Digit,
|
|
7558
|
+
),
|
|
7559
|
+
/*#__PURE__*/ templateLiteral("5", /*#__PURE__*/ union("0", "1")),
|
|
7560
|
+
);
|
|
7561
|
+
export type Digit1To51 = typeof Digit1To51.Output;
|
|
7562
|
+
|
|
7563
|
+
/**
|
|
7564
|
+
* Decimal string from `"1"` to `"99"`.
|
|
7565
|
+
*
|
|
7566
|
+
* @group String
|
|
7567
|
+
*/
|
|
7568
|
+
export const Digit1To99 = /*#__PURE__*/ union(
|
|
7569
|
+
Digit1To9,
|
|
7570
|
+
/*#__PURE__*/ templateLiteral(Digit1To9, Digit),
|
|
7571
|
+
);
|
|
7572
|
+
export type Digit1To99 = typeof Digit1To99.Output;
|
|
7573
|
+
|
|
7574
|
+
/**
|
|
7575
|
+
* Decimal string from `"1"` to `"59"`.
|
|
7576
|
+
*
|
|
7577
|
+
* @group String
|
|
7578
|
+
*/
|
|
7579
|
+
export const Digit1To59 = /*#__PURE__*/ union(
|
|
7580
|
+
Digit1To9,
|
|
7581
|
+
/*#__PURE__*/ templateLiteral(
|
|
7582
|
+
/*#__PURE__*/ union("1", "2", "3", "4", "5"),
|
|
7583
|
+
Digit,
|
|
7584
|
+
),
|
|
7585
|
+
);
|
|
7586
|
+
export type Digit1To59 = typeof Digit1To59.Output;
|
|
7587
|
+
|
|
6468
7588
|
const createObjectRuntimeTypeIssues =
|
|
6469
7589
|
(
|
|
6470
7590
|
defaultFormatter: TypeErrorFormatter<TypeError>,
|
|
@@ -8489,7 +9609,7 @@ const createRecordPropertyError = <Error extends TypeError>(
|
|
|
8489
9609
|
});
|
|
8490
9610
|
|
|
8491
9611
|
/**
|
|
8492
|
-
*
|
|
9612
|
+
* Object {@link Type} with every property optional.
|
|
8493
9613
|
*
|
|
8494
9614
|
* No property is required, but every present property must still satisfy its
|
|
8495
9615
|
* Type.
|
|
@@ -8539,7 +9659,8 @@ export type PartialObjectProps<Props extends ObjectProps> = {
|
|
|
8539
9659
|
};
|
|
8540
9660
|
|
|
8541
9661
|
/**
|
|
8542
|
-
*
|
|
9662
|
+
* Object {@link Type} making every property whose Union Type includes
|
|
9663
|
+
* {@link Null} optional.
|
|
8543
9664
|
*
|
|
8544
9665
|
* The property retains its original Union Type, so consumers may omit it, set
|
|
8545
9666
|
* it to `null`, or provide any other member of that Union. Properties without
|
|
@@ -8610,7 +9731,7 @@ export type NullableToOptionalProps<Props extends ObjectProps> = {
|
|
|
8610
9731
|
};
|
|
8611
9732
|
|
|
8612
9733
|
/**
|
|
8613
|
-
*
|
|
9734
|
+
* Object {@link Type} without the selected declared properties.
|
|
8614
9735
|
*
|
|
8615
9736
|
* @group Objects
|
|
8616
9737
|
*/
|
|
@@ -8664,7 +9785,7 @@ type OmitKeyConcreteTypeError = CompileTimeError<
|
|
|
8664
9785
|
>;
|
|
8665
9786
|
|
|
8666
9787
|
/**
|
|
8667
|
-
*
|
|
9788
|
+
* {@link Result} {@link Type} for typed success and error values.
|
|
8668
9789
|
*
|
|
8669
9790
|
* Use this to validate Results crossing a storage, worker, API, or other
|
|
8670
9791
|
* serialization boundary. The operation returns an outer validation Result. Its
|
|
@@ -8958,7 +10079,7 @@ type TypedTypePropertyError = CompileTimeError<
|
|
|
8958
10079
|
>;
|
|
8959
10080
|
|
|
8960
10081
|
/**
|
|
8961
|
-
*
|
|
10082
|
+
* Producer-result {@link Type} for value, error, or done outcomes.
|
|
8962
10083
|
*
|
|
8963
10084
|
* The three outcomes are `Ok<Value>`, `Err<Error>`, and `Err<Typed<"Done"> & {
|
|
8964
10085
|
* done: Done }>`. This keeps normal completion distinct from failure while
|
|
@@ -10593,7 +11714,7 @@ export const JsonValueFromJson = /*#__PURE__*/ transform(
|
|
|
10593
11714
|
);
|
|
10594
11715
|
|
|
10595
11716
|
/**
|
|
10596
|
-
*
|
|
11717
|
+
* Branded {@link Json} Type and total conversions for another Type.
|
|
10597
11718
|
*
|
|
10598
11719
|
* Use this factory when a domain value must be stored as JSON text while its
|
|
10599
11720
|
* exact Type remains visible to TypeScript, such as a JSON column in an Evolu
|