effect-app 4.0.0-beta.256 → 4.0.0-beta.258

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
 
@@ -104,9 +104,7 @@ export const NonEmptyString80: NonEmptyString80Schema = nonEmptyString.pipe(
104
104
  /**
105
105
  * A string that is at least 1 character long and a maximum of 100.
106
106
  */
107
- export interface NonEmptyString100Brand
108
- extends Simplify<SchemaB.Brand<"NonEmptyString100"> & NonEmptyString255Brand>
109
- {}
107
+ export interface NonEmptyString100Brand extends Simplify<SchemaB.Brand<"NonEmptyString100"> & NonEmptyString255Brand> {}
110
108
 
111
109
  /**
112
110
  * A string that is at least 1 character long and a maximum of 100.
package/src/ids.ts CHANGED
@@ -24,7 +24,12 @@ export const RequestId = extendM(
24
24
  Object
25
25
  .assign(Object.create(NonEmptyString255) as {}, NonEmptyString255 as unknown as Codec<NonEmptyString255, string>),
26
26
  (s) => {
27
- const make = StringId.make as () => NonEmptyString255
27
+ function make(): NonEmptyString255
28
+ function make(input: string, options?: S.MakeOptions): NonEmptyString255
29
+ function make(input?: string, options?: S.MakeOptions): NonEmptyString255 {
30
+ const id = input === undefined ? StringId.make() : StringId.make(input, options)
31
+ return id as NonEmptyString255
32
+ }
28
33
  return ({
29
34
  make,
30
35
  /**