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.
- package/CHANGELOG.md +25 -0
- package/dist/Model/Repository/internal/internal.d.ts +1 -1
- package/dist/Model/Repository/validation.d.ts +7 -20
- package/dist/Model/Repository/validation.d.ts.map +1 -1
- package/dist/Model/query/dsl.d.ts +82 -35
- package/dist/Model/query/dsl.d.ts.map +1 -1
- package/dist/Model/query/dsl.js +77 -43
- package/dist/RequestContext.d.ts +9 -21
- package/dist/RequestContext.d.ts.map +1 -1
- package/dist/Schema/moreStrings.d.ts +41 -29
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +1 -4
- package/dist/Schema/numbers.d.ts +23 -17
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +1 -1
- package/dist/Schema/strings.d.ts +11 -8
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema/strings.js +1 -1
- package/dist/ids.d.ts +9 -3
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +5 -2
- package/package.json +1 -1
- package/src/Model/query/dsl.ts +206 -58
- package/src/Schema/moreStrings.ts +47 -35
- package/src/Schema/numbers.ts +28 -17
- package/src/Schema/strings.ts +15 -8
- package/src/ids.ts +6 -1
package/src/Model/query/dsl.ts
CHANGED
|
@@ -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
|
-
|
|
510
|
-
|
|
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: (
|
|
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: (
|
|
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:
|
|
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: {
|
|
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: (
|
|
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: (
|
|
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: (
|
|
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
|
|
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:
|
|
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
|
-
} = (
|
|
731
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
759
|
-
*
|
|
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:
|
|
773
|
-
* count:
|
|
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,
|
|
793
|
-
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
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<
|
|
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
|
|
183
|
-
|
|
184
|
-
|
|
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<
|
|
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:
|
|
300
|
+
readonly withConstructorDefault: S.withConstructorDefault<S.Codec<Type, string> & S.WithoutConstructorDefault>
|
|
295
301
|
}
|
|
296
302
|
|
|
297
|
-
export interface BrandedStringIdSchema<Id
|
|
298
|
-
|
|
299
|
-
|
|
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<
|
|
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
|
|
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(
|