@effect-app/infra 4.0.0-beta.305 → 4.0.0-beta.307

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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @effect-app/infra
2
2
 
3
+ ## 4.0.0-beta.307
4
+
5
+ ### Patch Changes
6
+
7
+ - 40585ca: Keep tag-aware `ProjectableFromDomain` for `projectComputed` only; restore loose key-presence guard for `project()` so view DTOs with reshaped fields keep typechecking.
8
+ - ad0ede6: Keep tag-aware `ProjectableGuard` on both `project()` and `projectComputed` (no loose key-only paper-over).
9
+
10
+ Hardening:
11
+
12
+ - single-literal tags allow dual same-tag domain variants (KeysOfUnion of matched members)
13
+ - multi-tag / string tags still require keys on every matched member
14
+ - optional projection/domain keys checked by key presence only (not optional-assignability)
15
+
16
+ Call sites must project domain-owned fields per tagged state.
17
+
18
+ - Updated dependencies [40585ca]
19
+ - Updated dependencies [ad0ede6]
20
+ - effect-app@4.0.0-beta.307
21
+
22
+ ## 4.0.0-beta.306
23
+
24
+ ### Patch Changes
25
+
26
+ - 0fe925d: Tag-aware `ProjectableFromDomain` for `projectComputed`: projection Encoded fields must exist on the matching domain tagged state (or be computed). Prevents Overview.List SchemaErrors when cancel states omit workflow lock fields like `activeRequest`.
27
+ - Updated dependencies [0fe925d]
28
+ - effect-app@4.0.0-beta.306
29
+
3
30
  ## 4.0.0-beta.305
4
31
 
5
32
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-app/infra",
3
- "version": "4.0.0-beta.305",
3
+ "version": "4.0.0-beta.307",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -15,7 +15,7 @@
15
15
  "proper-lockfile": "^4.1.2",
16
16
  "pure-rand": "8.4.0",
17
17
  "query-string": "^9.4.0",
18
- "effect-app": "4.0.0-beta.305"
18
+ "effect-app": "4.0.0-beta.307"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@azure/cosmos": "^4.9.3",
@@ -4,7 +4,7 @@
4
4
  import * as Context from "effect-app/Context"
5
5
  import * as Effect from "effect-app/Effect"
6
6
  import * as Layer from "effect-app/Layer"
7
- import { and, computed, count, expr, make, one, or, order, page, project, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "effect-app/Model/query"
7
+ import { and, computed, count, expr, make, one, or, order, page, project, type ProjectableFromDomain, projectComputed, type QueryEnd, type QueryProjection, type QueryWhere, relation, toFilter, where } from "effect-app/Model/query"
8
8
  import { makeRepo } from "effect-app/Model/Repository"
9
9
  import { RepositoryRegistryLive } from "effect-app/Model/Repository/Registry"
10
10
  import * as Option from "effect-app/Option"
@@ -662,9 +662,11 @@ it("projectComputed constrains projection schema to encoded repo fields and comp
662
662
  )
663
663
 
664
664
  make<Encoded>().pipe(
665
+ // @ts-expect-error missingField is neither an encoded repo field nor a computed projection
665
666
  projectComputed(S.Struct({ missingField: S.String }), computed({}))
666
667
  )
667
668
 
669
+ // Key presence only (not Encoded value types) — name: number is still projectable.
668
670
  make<Encoded>().pipe(
669
671
  projectComputed(
670
672
  S.Struct({ name: S.Number }),
@@ -672,6 +674,8 @@ it("projectComputed constrains projection schema to encoded repo fields and comp
672
674
  )
673
675
  )
674
676
 
677
+ // Computed field *value* types are not yet constrained against the expression
678
+ // result (only key presence). Keeping this as a documentation call site.
675
679
  make<Encoded>().pipe(
676
680
  projectComputed(
677
681
  S.Struct({ itemCount: S.String }),
@@ -737,12 +741,131 @@ it("projectComputed supports union projection schemas", () => {
737
741
  ])
738
742
  })
739
743
 
744
+ /**
745
+ * MACS-SCANNER-API-KS class of bug: domain cancel omits pack/print ownership
746
+ * (`activeRequest`); a hand-built overview projection that still requires it on
747
+ * every branch must fail at the `projectComputed` call site, not in prod decode.
748
+ */
749
+ it("projectComputed rejects state-owned fields required on the wrong tagged branch", () => {
750
+ const domain = S.Union([
751
+ S.Struct({
752
+ _tag: S.Literal("packing"),
753
+ id: S.String,
754
+ activeRequest: S.NullOr(S.String),
755
+ items: S.Array(S.Struct({ articleId: S.String }))
756
+ }),
757
+ S.Struct({
758
+ _tag: S.Literal("cancelled"),
759
+ id: S.String,
760
+ items: S.Array(S.Struct({ articleId: S.String }))
761
+ })
762
+ ])
763
+ type DomainEnc = S.Codec.Encoded<typeof domain>
764
+
765
+ // Good: activeRequest only on packing; cancel omits it; articleCount is computed.
766
+ make<DomainEnc>().pipe(
767
+ projectComputed(
768
+ S.Union([
769
+ S.Struct({
770
+ _tag: S.Literal("packing"),
771
+ id: S.String,
772
+ activeRequest: S.NullOr(S.String),
773
+ articleCount: S.Number
774
+ }),
775
+ S.Struct({
776
+ _tag: S.Literal("cancelled"),
777
+ id: S.String,
778
+ articleCount: S.Number
779
+ })
780
+ ]),
781
+ computed({
782
+ articleCount: relation<DomainEnc>("items").count()
783
+ })
784
+ )
785
+ )
786
+
787
+ make<DomainEnc>().pipe(
788
+ projectComputed(
789
+ S.Union([
790
+ S.Struct({
791
+ _tag: S.Literal("packing"),
792
+ id: S.String,
793
+ activeRequest: S.NullOr(S.String),
794
+ articleCount: S.Number
795
+ }),
796
+ S.Struct({
797
+ _tag: S.Literal("cancelled"),
798
+ id: S.String,
799
+ activeRequest: S.NullOr(S.String),
800
+ articleCount: S.Number
801
+ })
802
+ ]),
803
+ // @ts-expect-error activeRequest is not on domain cancelled — cannot project it there
804
+ computed({
805
+ articleCount: relation<DomainEnc>("items").count()
806
+ })
807
+ )
808
+ )
809
+ })
810
+
811
+ it("ProjectableFromDomain distributes over tagged union Encoded", () => {
812
+ type DomainEnc =
813
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
814
+ | { readonly _tag: "cancelled"; readonly id: string }
815
+
816
+ type Good =
817
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null; readonly n: number }
818
+ | { readonly _tag: "cancelled"; readonly id: string; readonly n: number }
819
+
820
+ type Bad =
821
+ | { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
822
+ | { readonly _tag: "cancelled"; readonly id: string; readonly activeRequest: string | null }
823
+
824
+ type GoodCheck = ProjectableFromDomain<Good, DomainEnc, "n">
825
+ type BadCheck = ProjectableFromDomain<Bad, DomainEnc>
826
+
827
+ const _good: GoodCheck = undefined as unknown
828
+ // @ts-expect-error cancelled branch requires activeRequest not present on domain cancelled
829
+ const _bad: BadCheck = undefined as unknown
830
+ void _good
831
+ void _bad
832
+ })
833
+
834
+ it("ProjectableFromDomain allows dual same-tag domain variants", () => {
835
+ // EasyLife packing/closed: two domain shapes share `_tag` with different fields.
836
+ type DomainEnc =
837
+ | { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
838
+ | { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
839
+ | { readonly _tag: "initial"; readonly id: string }
840
+
841
+ type Good =
842
+ | { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
843
+ | { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
844
+
845
+ // Flat multi-tag + state-only field must still fail (batchId not on initial).
846
+ type BadFlat = {
847
+ readonly _tag: "packing" | "initial"
848
+ readonly id: string
849
+ readonly packages: readonly string[]
850
+ }
851
+
852
+ type GoodCheck = ProjectableFromDomain<Good, DomainEnc>
853
+ type BadFlatCheck = ProjectableFromDomain<BadFlat, DomainEnc>
854
+
855
+ const _good: GoodCheck = undefined as unknown
856
+ // @ts-expect-error packages is not on domain initial; multi-tag flat intersection rejects it
857
+ const _badFlat: BadFlatCheck = undefined as unknown
858
+ void _good
859
+ void _badFlat
860
+ })
861
+
740
862
  it("projection schema with computed fields fails without computed map", () => {
741
863
  const baseSchema = S.Struct({
742
864
  id: S.String,
743
865
  items: S.Array(S.Struct({ value: S.Number }))
744
866
  })
745
867
  const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
868
+ // @ts-expect-error missing computed keys are rejected; also not projectable from domain alone
746
869
  projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
747
870
  )
748
871
  expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")