effect-app 4.0.0-beta.255 → 4.0.0-beta.257

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.
@@ -506,9 +506,42 @@ export const project: {
506
506
  ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
507
507
  } = (schema: any, mode = "transform") => (current: any) => new Project({ current, schema, mode } as any)
508
508
 
509
- export const relation = <TFieldValues extends FieldValues>(
510
- path: FieldPath<TFieldValues>
509
+ /**
510
+ * Element type of an array-valued field path on `TFieldValues`. Falls back to
511
+ * `FieldValues` when the path does not resolve to an array of structs so that
512
+ * `FieldPath<...>` stays defined (it just degrades to `string`).
513
+ *
514
+ * Uses `Extract` so that when `P` defaults to the full `FieldPath<TFieldValues>`
515
+ * union, only the array-valued branches contribute to the element type.
516
+ */
517
+ export type RelationElement<
518
+ TFieldValues extends FieldValues,
519
+ P extends FieldPath<TFieldValues>
520
+ > = Extract<FieldPathValue<TFieldValues, P>, ReadonlyArray<unknown>> extends ReadonlyArray<infer E>
521
+ ? (E extends FieldValues ? E : FieldValues)
522
+ : FieldValues
523
+
524
+ export const relation = <
525
+ TFieldValues extends FieldValues,
526
+ const P extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
527
+ >(
528
+ path: P
511
529
  ) => ({
530
+ /**
531
+ * Typed math-expression builder bound to the relation's element scope:
532
+ * `relation("items").expr.field("weight")` constrains the field path to
533
+ * `FieldPath<RelationElement<TFieldValues, P>>`.
534
+ */
535
+ expr: {
536
+ field: (field: FieldPath<RelationElement<TFieldValues, P>>): ComputedProjectionMathExpression => ({
537
+ _tag: "field",
538
+ field: field as string
539
+ }),
540
+ mul: (
541
+ left: ComputedProjectionMathExpression,
542
+ right: ComputedProjectionMathExpression
543
+ ): ComputedProjectionMathExpression => ({ _tag: "mul", left, right })
544
+ },
512
545
  length: (): ComputedProjectionExpression => ({
513
546
  _tag: "relation-length",
514
547
  path: path as string
@@ -540,31 +573,37 @@ export const relation = <TFieldValues extends FieldValues>(
540
573
  path: path as string,
541
574
  operation
542
575
  }),
543
- distinctCount: (field: string, operation?: ComputedProjectionOperation): ComputedProjectionExpression =>
576
+ distinctCount: (
577
+ field: FieldPath<RelationElement<TFieldValues, P>>,
578
+ operation?: ComputedProjectionOperation
579
+ ): ComputedProjectionExpression =>
544
580
  operation
545
581
  ? {
546
582
  _tag: "relation-distinct-count",
547
583
  path: path as string,
548
- field,
584
+ field: field as string,
549
585
  operation
550
586
  }
551
587
  : {
552
588
  _tag: "relation-distinct-count",
553
589
  path: path as string,
554
- field
590
+ field: field as string
555
591
  },
556
- sum: (field: string, operation?: ComputedProjectionOperation): ComputedProjectionExpression =>
592
+ sum: (
593
+ field: FieldPath<RelationElement<TFieldValues, P>>,
594
+ operation?: ComputedProjectionOperation
595
+ ): ComputedProjectionExpression =>
557
596
  operation
558
597
  ? {
559
598
  _tag: "relation-sum",
560
599
  path: path as string,
561
- field,
600
+ field: field as string,
562
601
  operation
563
602
  }
564
603
  : {
565
604
  _tag: "relation-sum",
566
605
  path: path as string,
567
- field
606
+ field: field as string
568
607
  },
569
608
  sumExpr: (
570
609
  expression: ComputedProjectionMathExpression,
@@ -584,7 +623,7 @@ export const relation = <TFieldValues extends FieldValues>(
584
623
  },
585
624
  sumExprBy: (
586
625
  expression: ComputedProjectionMathExpression,
587
- options: { unit: string },
626
+ options: { unit: FieldPath<RelationElement<TFieldValues, P>> },
588
627
  operation?: ComputedProjectionOperation
589
628
  ): ComputedProjectionExpression =>
590
629
  operation
@@ -592,18 +631,22 @@ export const relation = <TFieldValues extends FieldValues>(
592
631
  _tag: "relation-sum-expr-by",
593
632
  path: path as string,
594
633
  expression,
595
- unit: options.unit,
634
+ unit: options.unit as string,
596
635
  operation
597
636
  }
598
637
  : {
599
638
  _tag: "relation-sum-expr-by",
600
639
  path: path as string,
601
640
  expression,
602
- unit: options.unit
641
+ unit: options.unit as string
603
642
  },
604
643
  sumExprNormalized: (
605
644
  expression: ComputedProjectionMathExpression,
606
- options: { unit: string; toBase: string; factors: Readonly<Record<string, number>> },
645
+ options: {
646
+ unit: FieldPath<RelationElement<TFieldValues, P>>
647
+ toBase: string
648
+ factors: Readonly<Record<string, number>>
649
+ },
607
650
  operation?: ComputedProjectionOperation
608
651
  ): ComputedProjectionExpression =>
609
652
  operation
@@ -611,7 +654,7 @@ export const relation = <TFieldValues extends FieldValues>(
611
654
  _tag: "relation-sum-expr-normalized",
612
655
  path: path as string,
613
656
  expression,
614
- unit: options.unit,
657
+ unit: options.unit as string,
615
658
  toBase: options.toBase,
616
659
  factors: options.factors,
617
660
  operation
@@ -620,77 +663,96 @@ export const relation = <TFieldValues extends FieldValues>(
620
663
  _tag: "relation-sum-expr-normalized",
621
664
  path: path as string,
622
665
  expression,
623
- unit: options.unit,
666
+ unit: options.unit as string,
624
667
  toBase: options.toBase,
625
668
  factors: options.factors
626
669
  },
627
- collect: (field: string, operation?: ComputedProjectionOperation): ComputedProjectionExpression =>
670
+ collect: (
671
+ field: FieldPath<RelationElement<TFieldValues, P>>,
672
+ operation?: ComputedProjectionOperation
673
+ ): ComputedProjectionExpression =>
628
674
  operation
629
675
  ? {
630
676
  _tag: "relation-collect",
631
677
  path: path as string,
632
- field,
678
+ field: field as string,
633
679
  distinct: false,
634
680
  operation
635
681
  }
636
682
  : {
637
683
  _tag: "relation-collect",
638
684
  path: path as string,
639
- field,
685
+ field: field as string,
640
686
  distinct: false
641
687
  },
642
- collectDistinct: (field: string, operation?: ComputedProjectionOperation): ComputedProjectionExpression =>
688
+ collectDistinct: (
689
+ field: FieldPath<RelationElement<TFieldValues, P>>,
690
+ operation?: ComputedProjectionOperation
691
+ ): ComputedProjectionExpression =>
643
692
  operation
644
693
  ? {
645
694
  _tag: "relation-collect",
646
695
  path: path as string,
647
- field,
696
+ field: field as string,
648
697
  distinct: true,
649
698
  operation
650
699
  }
651
700
  : {
652
701
  _tag: "relation-collect",
653
702
  path: path as string,
654
- field,
703
+ field: field as string,
655
704
  distinct: true
656
705
  },
657
- collectFields: (fields: readonly string[], operation?: ComputedProjectionOperation): ComputedProjectionExpression =>
706
+ collectFields: (
707
+ fields: readonly FieldPath<RelationElement<TFieldValues, P>>[],
708
+ operation?: ComputedProjectionOperation
709
+ ): ComputedProjectionExpression =>
658
710
  operation
659
711
  ? {
660
712
  _tag: "relation-collect-fields",
661
713
  path: path as string,
662
- fields,
714
+ fields: fields as readonly string[],
663
715
  distinct: false,
664
716
  operation
665
717
  }
666
718
  : {
667
719
  _tag: "relation-collect-fields",
668
720
  path: path as string,
669
- fields,
721
+ fields: fields as readonly string[],
670
722
  distinct: false
671
723
  },
672
724
  collectDistinctFields: (
673
- fields: readonly string[],
725
+ fields: readonly FieldPath<RelationElement<TFieldValues, P>>[],
674
726
  operation?: ComputedProjectionOperation
675
727
  ): ComputedProjectionExpression =>
676
728
  operation
677
729
  ? {
678
730
  _tag: "relation-collect-fields",
679
731
  path: path as string,
680
- fields,
732
+ fields: fields as readonly string[],
681
733
  distinct: true,
682
734
  operation
683
735
  }
684
736
  : {
685
737
  _tag: "relation-collect-fields",
686
738
  path: path as string,
687
- fields,
739
+ fields: fields as readonly string[],
688
740
  distinct: true
689
741
  }
690
742
  })
691
743
 
744
+ /**
745
+ * Untyped math-expression builder. Field paths are not statically validated —
746
+ * prefer the scope-bound `relation(path).expr` builder when the element type
747
+ * is known, since it constrains the field argument to `FieldPath<E>`.
748
+ *
749
+ * The generic parameter is accepted for symmetry with the scoped builder so
750
+ * callers may opt into a tighter check via `expr.field<E>("x")`.
751
+ */
692
752
  export const expr = {
693
- field: (field: string): ComputedProjectionMathExpression => ({ _tag: "field", field }),
753
+ field: <T extends FieldValues = FieldValues>(
754
+ field: FieldPath<T>
755
+ ): ComputedProjectionMathExpression => ({ _tag: "field", field: field as string }),
694
756
  mul: (
695
757
  left: ComputedProjectionMathExpression,
696
758
  right: ComputedProjectionMathExpression
@@ -699,7 +761,49 @@ export const expr = {
699
761
 
700
762
  export const computed = <T extends ComputedProjectionMap>(value: T): T => value
701
763
 
764
+ /**
765
+ * Helpers passed to the inline-builder form of {@link projectComputed}. The
766
+ * `relation` factory is bound to the source row shape so paths/fields inside
767
+ * the projection are validated against the document shape inferred from the
768
+ * pipe.
769
+ */
770
+ export interface ComputedHelpers<TFieldValues extends FieldValues> {
771
+ relation: <const P extends FieldPath<TFieldValues>>(path: P) => ReturnType<typeof relation<TFieldValues, P>>
772
+ }
773
+
774
+ const makeComputedHelpers = <TFieldValues extends FieldValues>(): ComputedHelpers<TFieldValues> => ({
775
+ relation: (path) => relation<TFieldValues, typeof path>(path)
776
+ })
777
+
702
778
  export const projectComputed: {
779
+ <
780
+ Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
781
+ I extends Record<string, unknown>,
782
+ A = ExtractFieldValuesRefined<Q>,
783
+ R = never,
784
+ E extends boolean = ExtractExclusiveness<Q>
785
+ >(
786
+ schema: S.Codec<Option.Option<A>, I, R>,
787
+ build: (helpers: ComputedHelpers<ExtractFieldValuesRefined<Q>>) => ComputedProjectionMap,
788
+ mode: "collect"
789
+ ): (
790
+ current: Q
791
+ ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
792
+
793
+ <
794
+ Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
795
+ I extends Record<string, unknown>,
796
+ A = ExtractFieldValuesRefined<Q>,
797
+ R = never,
798
+ E extends boolean = ExtractExclusiveness<Q>
799
+ >(
800
+ schema: S.Codec<A, I, R>,
801
+ build: (helpers: ComputedHelpers<ExtractFieldValuesRefined<Q>>) => ComputedProjectionMap,
802
+ mode?: "project"
803
+ ): (
804
+ current: Q
805
+ ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
806
+
703
807
  <
704
808
  Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
705
809
  I extends Record<string, unknown>,
@@ -727,40 +831,62 @@ export const projectComputed: {
727
831
  ): (
728
832
  current: Q
729
833
  ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
730
- } = (schema: any, computedProjection: ComputedProjectionMap, mode = "project") => (current: any) =>
731
- new Project({ current, schema, mode, computed: computedProjection } as any)
834
+ } = (
835
+ schema: any,
836
+ mapOrBuild:
837
+ | ComputedProjectionMap
838
+ | ((helpers: ComputedHelpers<FieldValues>) => ComputedProjectionMap),
839
+ mode = "project"
840
+ ) =>
841
+ (current: any) => {
842
+ const computedProjection = typeof mapOrBuild === "function"
843
+ ? mapOrBuild(makeComputedHelpers())
844
+ : mapOrBuild
845
+ return new Project({ current, schema, mode, computed: computedProjection } as any)
846
+ }
847
+
848
+ /**
849
+ * Builder shape returned by {@link agg}. Field-taking methods are constrained
850
+ * to `FieldPath<TFieldValues>` so paths are validated against the document
851
+ * shape.
852
+ */
853
+ export interface AggBuilder<TFieldValues extends FieldValues> {
854
+ field: (path: FieldPath<TFieldValues>) => AggregateExpression
855
+ count: () => AggregateExpression
856
+ countWhen: (operation: ComputedProjectionOperation) => AggregateExpression
857
+ sum: (field: FieldPath<TFieldValues>) => AggregateExpression
858
+ min: (field: FieldPath<TFieldValues>) => AggregateExpression
859
+ max: (field: FieldPath<TFieldValues>) => AggregateExpression
860
+ }
861
+
862
+ const makeAggBuilder = <TFieldValues extends FieldValues>(): AggBuilder<TFieldValues> => ({
863
+ field: (path) => ({ _tag: "agg-field", path: path as string }),
864
+ count: () => ({ _tag: "agg-count" }),
865
+ countWhen: (operation) => ({ _tag: "agg-count-when", operation }),
866
+ sum: (field) => ({ _tag: "agg-sum", field: field as string }),
867
+ min: (field) => ({ _tag: "agg-min", field: field as string }),
868
+ max: (field) => ({ _tag: "agg-max", field: field as string })
869
+ })
732
870
 
733
871
  /**
734
- * DSL helpers for building aggregate expressions used with {@link aggregate}.
872
+ * Scope-bound aggregate-expression builder factory. Invoke with the source
873
+ * document field-value shape to get a builder whose `field`/`sum`/`min`/`max`
874
+ * arguments are constrained to `FieldPath<TFieldValues>`.
735
875
  *
736
- * - `agg.field(path)` references a document field for GROUP BY (with optional output alias)
737
- * - `agg.count()` — COUNT(*) across all rows in the group
738
- * - `agg.countWhen(op)` COUNT of rows matching the filter operation
739
- * - `agg.sum(field)` SUM of a numeric document field
740
- * - `agg.min(field)` / `agg.max(field)` — MIN/MAX of a document field
876
+ * Prefer the inline callback form of {@link aggregate} (`aggregate(schema,
877
+ * ($) => ({...}))`)there the source shape is inferred from the pipe so no
878
+ * explicit type argument is needed. This factory is the escape hatch when
879
+ * the builder is constructed outside the pipe.
741
880
  */
742
- export const agg = {
743
- field: (path: string): AggregateExpression => ({ _tag: "agg-field", path }),
744
- count: (): AggregateExpression => ({ _tag: "agg-count" }),
745
- countWhen: (operation: ComputedProjectionOperation): AggregateExpression => ({
746
- _tag: "agg-count-when",
747
- operation
748
- }),
749
- sum: (field: string): AggregateExpression => ({ _tag: "agg-sum", field }),
750
- min: (field: string): AggregateExpression => ({ _tag: "agg-min", field }),
751
- max: (field: string): AggregateExpression => ({ _tag: "agg-max", field })
752
- } as const
881
+ export const agg = <TFieldValues extends FieldValues = FieldValues>(): AggBuilder<TFieldValues> =>
882
+ makeAggBuilder<TFieldValues>()
753
883
 
754
884
  /**
755
885
  * Attach an aggregate projection to a query, performing GROUP BY + aggregate functions at the
756
886
  * database level instead of fetching all rows and grouping in memory.
757
887
  *
758
- * The `aggregateMap` maps each output field name to either:
759
- * - `agg.field(path)` a group-by field (document path output alias)
760
- * - `agg.count()` / `agg.countWhen(op)` / `agg.sum(f)` / `agg.min(f)` / `agg.max(f)` — an aggregate
761
- *
762
- * The output is decoded directly with `schema` (no PM reverse-mapping, no etag tracking).
763
- * Decode failures surface as `S.SchemaError`.
888
+ * Pass a builder callback to get a typed `agg` bound to the source row shape
889
+ * (inferred from the pipe no explicit generic needed):
764
890
  *
765
891
  * @example
766
892
  * ```ts
@@ -768,15 +894,33 @@ export const agg = {
768
894
  * where("status", "active"),
769
895
  * aggregate(
770
896
  * S.Struct({ city: S.String, count: S.Number }),
771
- * {
772
- * city: agg.field("address.city"),
773
- * count: agg.countWhen((q) => q.pipe(where("active", true)))
774
- * }
897
+ * ($) => ({
898
+ * city: $.field("address.city"),
899
+ * count: $.countWhen((q) => q.pipe(where("active", true)))
900
+ * })
775
901
  * )
776
902
  * )
777
903
  * ```
904
+ *
905
+ * A plain {@link AggregateMap} is also accepted for the rare case where the
906
+ * map is built outside the pipe (loses path inference).
907
+ *
908
+ * The output is decoded directly with `schema` (no PM reverse-mapping, no etag tracking).
909
+ * Decode failures surface as `S.SchemaError`.
778
910
  */
779
911
  export const aggregate: {
912
+ <
913
+ Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
914
+ I extends Record<string, unknown>,
915
+ A = ExtractFieldValuesRefined<Q>,
916
+ R = never,
917
+ E extends boolean = ExtractExclusiveness<Q>
918
+ >(
919
+ schema: S.Codec<A, I, R>,
920
+ build: (agg: AggBuilder<ExtractFieldValuesRefined<Q>>) => AggregateMap
921
+ ): (
922
+ current: Q
923
+ ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
780
924
  <
781
925
  Q extends Query<any> | QueryWhere<any, any, any> | QueryEnd<any, "one" | "many", any>,
782
926
  I extends Record<string, unknown>,
@@ -789,8 +933,12 @@ export const aggregate: {
789
933
  ): (
790
934
  current: Q
791
935
  ) => QueryProjection<ExtractFieldValuesRefined<Q>, A, R, ExtractTType<Q>, E>
792
- } = (schema: any, aggregateMap: AggregateMap) => (current: any) =>
793
- new Project({ current, schema, mode: "aggregate", aggregateMap } as any)
936
+ } = (schema: any, mapOrBuild: AggregateMap | ((agg: AggBuilder<FieldValues>) => AggregateMap)) => (current: any) => {
937
+ const aggregateMap = typeof mapOrBuild === "function"
938
+ ? mapOrBuild(makeAggBuilder())
939
+ : mapOrBuild
940
+ return new Project({ current, schema, mode: "aggregate", aggregateMap } as any)
941
+ }
794
942
 
795
943
  type GetArV<T> = T extends readonly (infer R)[] ? R : never
796
944
 
@@ -10,26 +10,25 @@
10
10
  */
11
11
  import type { Refinement } from "effect-app/Function"
12
12
  import { extendM } from "effect-app/utils"
13
+ import type * as B from "effect/Brand"
13
14
  import * as Effect from "effect/Effect"
14
15
  import { pipe } from "effect/Function"
15
16
  import * as S from "effect/Schema"
17
+ import type * as SchemaAST from "effect/SchemaAST"
16
18
  import type { Simplify } from "effect/Types"
17
19
  import { customRandom, nanoid, urlAlphabet } from "nanoid"
18
20
  import validator from "validator"
19
- import { fromBrand, nominal } from "./brand.js"
20
- import { withDefaultMake, type WithDefaults } from "./ext.js"
21
- import { type B } from "./schema.js"
21
+ import { type BrandedSchema, fromBrand, nominal } from "./brand.js"
22
+ import { withDefaultMake } from "./ext.js"
23
+ import { type B as SchemaB } from "./schema.js"
22
24
  import type { NonEmptyString255Brand, NonEmptyStringBrand } from "./strings.js"
23
25
 
24
- type BrandedStringSchema<A extends string> = S.Codec<A, string> & WithDefaults<S.Codec<A, string>>
25
- type ConstructorDefaultBaseSchema<A> = S.Codec<A, string> & S.WithoutConstructorDefault
26
- type WithConstructorDefaultSchema<A> = S.withConstructorDefault<ConstructorDefaultBaseSchema<A>>
27
26
  const nonEmptyString = S.NonEmptyString
28
27
 
29
28
  /**
30
29
  * A string that is at least 1 character long and a maximum of 50.
31
30
  */
32
- export interface NonEmptyString50Brand extends Simplify<B.Brand<"NonEmptyString50"> & NonEmptyString64Brand> {}
31
+ export interface NonEmptyString50Brand extends Simplify<SchemaB.Brand<"NonEmptyString50"> & NonEmptyString64Brand> {}
33
32
 
34
33
  /**
35
34
  * A string that is at least 1 character long and a maximum of 50.
@@ -39,7 +38,9 @@ export type NonEmptyString50 = string & NonEmptyString50Brand
39
38
  /**
40
39
  * A string that is at least 1 character long and a maximum of 50.
41
40
  */
42
- export interface NonEmptyString50Schema extends BrandedStringSchema<NonEmptyString50> {}
41
+ export interface NonEmptyString50Schema extends BrandedSchema<S.NonEmptyString, NonEmptyString50> {
42
+ (i: string, options?: SchemaAST.ParseOptions): NonEmptyString50
43
+ }
43
44
  export const NonEmptyString50: NonEmptyString50Schema = nonEmptyString.pipe(
44
45
  S.check(S.isMaxLength(50)),
45
46
  fromBrand<NonEmptyString50>(nominal<NonEmptyString50>(), {
@@ -52,7 +53,7 @@ export const NonEmptyString50: NonEmptyString50Schema = nonEmptyString.pipe(
52
53
  /**
53
54
  * A string that is at least 1 character long and a maximum of 64.
54
55
  */
55
- export interface NonEmptyString64Brand extends Simplify<B.Brand<"NonEmptyString64"> & NonEmptyString80Brand> {}
56
+ export interface NonEmptyString64Brand extends Simplify<SchemaB.Brand<"NonEmptyString64"> & NonEmptyString80Brand> {}
56
57
 
57
58
  /**
58
59
  * A string that is at least 1 character long and a maximum of 64.
@@ -62,7 +63,9 @@ export type NonEmptyString64 = string & NonEmptyString64Brand
62
63
  /**
63
64
  * A string that is at least 1 character long and a maximum of 64.
64
65
  */
65
- export interface NonEmptyString64Schema extends BrandedStringSchema<NonEmptyString64> {}
66
+ export interface NonEmptyString64Schema extends BrandedSchema<S.NonEmptyString, NonEmptyString64> {
67
+ (i: string, options?: SchemaAST.ParseOptions): NonEmptyString64
68
+ }
66
69
  export const NonEmptyString64: NonEmptyString64Schema = nonEmptyString.pipe(
67
70
  S.check(S.isMaxLength(64)),
68
71
  fromBrand<NonEmptyString64>(nominal<NonEmptyString64>(), {
@@ -75,7 +78,7 @@ export const NonEmptyString64: NonEmptyString64Schema = nonEmptyString.pipe(
75
78
  /**
76
79
  * A string that is at least 1 character long and a maximum of 80.
77
80
  */
78
- export interface NonEmptyString80Brand extends Simplify<B.Brand<"NonEmptyString80"> & NonEmptyString100Brand> {}
81
+ export interface NonEmptyString80Brand extends Simplify<SchemaB.Brand<"NonEmptyString80"> & NonEmptyString100Brand> {}
79
82
 
80
83
  /**
81
84
  * A string that is at least 1 character long and a maximum of 80.
@@ -86,7 +89,9 @@ export type NonEmptyString80 = string & NonEmptyString80Brand
86
89
  * A string that is at least 1 character long and a maximum of 80.
87
90
  */
88
91
 
89
- export interface NonEmptyString80Schema extends BrandedStringSchema<NonEmptyString80> {}
92
+ export interface NonEmptyString80Schema extends BrandedSchema<S.NonEmptyString, NonEmptyString80> {
93
+ (i: string, options?: SchemaAST.ParseOptions): NonEmptyString80
94
+ }
90
95
  export const NonEmptyString80: NonEmptyString80Schema = nonEmptyString.pipe(
91
96
  S.check(S.isMaxLength(80)),
92
97
  fromBrand<NonEmptyString80>(nominal<NonEmptyString80>(), {
@@ -99,7 +104,7 @@ export const NonEmptyString80: NonEmptyString80Schema = nonEmptyString.pipe(
99
104
  /**
100
105
  * A string that is at least 1 character long and a maximum of 100.
101
106
  */
102
- export interface NonEmptyString100Brand extends Simplify<B.Brand<"NonEmptyString100"> & NonEmptyString255Brand> {}
107
+ export interface NonEmptyString100Brand extends Simplify<SchemaB.Brand<"NonEmptyString100"> & NonEmptyString255Brand> {}
103
108
 
104
109
  /**
105
110
  * A string that is at least 1 character long and a maximum of 100.
@@ -109,7 +114,9 @@ export type NonEmptyString100 = string & NonEmptyString100Brand
109
114
  /**
110
115
  * A string that is at least 1 character long and a maximum of 100.
111
116
  */
112
- export interface NonEmptyString100Schema extends BrandedStringSchema<NonEmptyString100> {}
117
+ export interface NonEmptyString100Schema extends BrandedSchema<S.NonEmptyString, NonEmptyString100> {
118
+ (i: string, options?: SchemaAST.ParseOptions): NonEmptyString100
119
+ }
113
120
  export const NonEmptyString100: NonEmptyString100Schema = nonEmptyString.pipe(
114
121
  S.check(S.isMaxLength(100)),
115
122
  fromBrand<NonEmptyString100>(nominal<NonEmptyString100>(), {
@@ -122,7 +129,7 @@ export const NonEmptyString100: NonEmptyString100Schema = nonEmptyString.pipe(
122
129
  /**
123
130
  * A string that is at least 3 character long and a maximum of 255.
124
131
  */
125
- export interface Min3String255Brand extends Simplify<B.Brand<"Min3String255"> & NonEmptyString255Brand> {}
132
+ export interface Min3String255Brand extends Simplify<SchemaB.Brand<"Min3String255"> & NonEmptyString255Brand> {}
126
133
 
127
134
  /**
128
135
  * A string that is at least 3 character long and a maximum of 255.
@@ -132,7 +139,9 @@ export type Min3String255 = string & Min3String255Brand
132
139
  /**
133
140
  * A string that is at least 3 character long and a maximum of 255.
134
141
  */
135
- export interface Min3String255Schema extends BrandedStringSchema<Min3String255> {}
142
+ export interface Min3String255Schema extends BrandedSchema<S.String, Min3String255> {
143
+ (i: string, options?: SchemaAST.ParseOptions): Min3String255
144
+ }
136
145
  export const Min3String255: Min3String255Schema = pipe(
137
146
  S.String,
138
147
  S.check(S.isMinLength(3), S.isMaxLength(255)),
@@ -146,7 +155,7 @@ export const Min3String255: Min3String255Schema = pipe(
146
155
  /**
147
156
  * A string that is at least 6 characters long and a maximum of 50.
148
157
  */
149
- export interface StringIdBrand extends Simplify<B.Brand<"StringId"> & NonEmptyString50Brand> {}
158
+ export interface StringIdBrand extends Simplify<SchemaB.Brand<"StringId"> & NonEmptyString50Brand> {}
150
159
 
151
160
  /**
152
161
  * A string that is at least 6 characters long and a maximum of 50.
@@ -157,7 +166,6 @@ const minLength = 6
157
166
  const maxLength = 50
158
167
  const size = 21
159
168
  const length = 10 * size
160
- /** Base `StringId` codec (without constructor default extensions). */
161
169
  const StringIdSchemaBase = pipe(
162
170
  S.String,
163
171
  S.check(S.isMinLength(minLength), S.isMaxLength(maxLength)),
@@ -179,9 +187,13 @@ const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
179
187
  * `.withConstructorDefault` => fresh `nanoid()` (construction-only; not
180
188
  * applied during decode — see file-level note).
181
189
  */
182
- export interface StringIdSchema extends BrandedStringSchema<StringId> {
183
- readonly make: (s?: string) => StringId
184
- readonly withConstructorDefault: WithConstructorDefaultSchema<StringId>
190
+ export interface StringIdSchema extends BrandedSchema<S.String, StringId> {
191
+ (i: string, options?: SchemaAST.ParseOptions): StringId
192
+ /** Generate fresh `nanoid()`-shaped `StringId`. */
193
+ make(): StringId
194
+ /** Construct a `StringId` from a known string (validated via decodeSync). */
195
+ make(input: string, options?: S.MakeOptions): StringId
196
+ readonly withConstructorDefault: S.withConstructorDefault<BrandedSchema<S.String, StringId>>
185
197
  }
186
198
  export const StringId: StringIdSchema = extendM(
187
199
  StringIdSchemaBase,
@@ -198,10 +210,6 @@ export const StringId: StringIdSchema = extendM(
198
210
  )
199
211
  .pipe(withDefaultMake)
200
212
 
201
- // const prefixedStringIdUnsafe = (prefix: string) => StringId(prefix + StringId.make())
202
-
203
- // const prefixedStringIdUnsafeThunk = (prefix: string) => () => prefixedStringIdUnsafe(prefix)
204
-
205
213
  /**
206
214
  * Build a `StringId` schema whose values are required to start with a fixed
207
215
  * `prefix` (joined with `separator`, default `-`).
@@ -255,7 +263,7 @@ export function prefixedStringId<Type extends StringId>() {
255
263
  * file-level note.
256
264
  */
257
265
  withConstructorDefault: schema.pipe(
258
- S.withConstructorDefault<ConstructorDefaultBaseSchema<Type>>(
266
+ S.withConstructorDefault<S.Codec<Type, string> & S.WithoutConstructorDefault>(
259
267
  Effect.sync(make)
260
268
  )
261
269
  )
@@ -270,9 +278,7 @@ export function prefixedStringId<Type extends StringId>() {
270
278
  * Exposes `.withConstructorDefault` that mints a fresh `nanoid()`-shaped id.
271
279
  * Construction-only — not applied during decode; see file-level note.
272
280
  */
273
- export const brandedStringId = <
274
- Id
275
- >(): BrandedStringIdSchema<Id> =>
281
+ export const brandedStringId = <Id extends string & B.Brand<any>>(): BrandedStringIdSchema<Id> =>
276
282
  withDefaultMake(
277
283
  Object.assign(Object.create(StringId), StringId) as BrandedStringIdSchema<Id>
278
284
  )
@@ -291,15 +297,19 @@ export interface PrefixedStringUtils<
291
297
  * field is omitted from `.make(...)` input. NOT applied during decode —
292
298
  * cannot be used to JIT-migrate database fields. See file-level note.
293
299
  */
294
- readonly withConstructorDefault: WithConstructorDefaultSchema<Type>
300
+ readonly withConstructorDefault: S.withConstructorDefault<S.Codec<Type, string> & S.WithoutConstructorDefault>
295
301
  }
296
302
 
297
- export interface BrandedStringIdSchema<Id> extends S.Codec<Id, string>, WithDefaults<S.Codec<Id, string>> {
298
- readonly withConstructorDefault: WithConstructorDefaultSchema<Id>
299
- readonly make: () => Id
303
+ export interface BrandedStringIdSchema<Id extends string & B.Brand<any>> extends BrandedSchema<S.String, Id> {
304
+ (i: string, options?: SchemaAST.ParseOptions): Id
305
+ /** Generate fresh `nanoid()`-shaped id (inherited from `StringId`). */
306
+ make(): Id
307
+ /** Construct an `Id` from a known string (validated via decodeSync). */
308
+ make(input: string, options?: S.MakeOptions): Id
309
+ readonly withConstructorDefault: S.withConstructorDefault<BrandedSchema<S.String, Id>>
300
310
  }
301
311
 
302
- export interface UrlBrand extends Simplify<B.Brand<"Url"> & NonEmptyStringBrand> {}
312
+ export interface UrlBrand extends Simplify<SchemaB.Brand<"Url"> & NonEmptyStringBrand> {}
303
313
 
304
314
  export type Url = string & UrlBrand
305
315
 
@@ -307,7 +317,9 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
307
317
  return validator.default.isURL(s, { require_tld: false })
308
318
  }
309
319
 
310
- export interface UrlSchema extends BrandedStringSchema<Url> {}
320
+ export interface UrlSchema extends S.refine<Url, S.String> {
321
+ (i: string, options?: SchemaAST.ParseOptions): Url
322
+ }
311
323
  export const Url: UrlSchema = S
312
324
  .String
313
325
  .pipe(