@effect-app/infra 0.200.3 → 0.201.1

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.
@@ -26,13 +26,14 @@ import { makeFilters } from "@effect-app/infra/filter"
26
26
  import type { ParseResult, Schema } from "@effect-app/schema"
27
27
  import { NonNegativeInt } from "@effect-app/schema"
28
28
  import type { Context, NonEmptyArray, NonEmptyReadonlyArray } from "effect-app"
29
- import { Chunk, Effect, flow, Option, pipe, PubSub, ReadonlyArray, S } from "effect-app"
29
+ import { Chunk, Effect, flow, Option, pipe, PubSub, ReadonlyArray, S, Unify } from "effect-app"
30
30
  import { runTerm } from "effect-app/Pure"
31
31
  import type { FixEnv, PureEnv } from "effect-app/Pure"
32
32
  import { assignTag } from "effect-app/service"
33
+ import type { NoInfer } from "effect/Types"
33
34
  import { type InvalidStateError, NotFoundError, type OptimisticConcurrencyException } from "../errors.js"
34
35
  import type { FieldValues } from "../filter/types.js"
35
- import { make as makeQuery, page, query } from "./query.js"
36
+ import { make as makeQuery, page } from "./query.js"
36
37
  import type { QAll, Query, QueryEnd, QueryProjection, QueryWhere } from "./query.js"
37
38
  import * as Q2 from "./query.js"
38
39
  import { ContextMapContainer } from "./Store/ContextMapContainer.js"
@@ -198,7 +199,7 @@ export class RepositoryBaseC3<
198
199
  q: (q: Query<Omit<PM, "_etag">>) => Query<Omit<PM, "_etag">> | QueryEnd<Omit<PM, "_etag">>,
199
200
  pure: Effect<A, E2, FixEnv<R2, Evt, T, T2>>
200
201
  ) =>
201
- this.q2(flow(q, page({ limit: 1 }))).pipe(
202
+ this.q2(flow(q, page({ take: 1 }))).pipe(
202
203
  Effect.map(ReadonlyArray.toNonEmptyArray),
203
204
  Effect.flatMap(Effect.mapError(() => new NotFoundError({ type: this.itemType, id: q }))),
204
205
  Effect.andThen((_) => saveWithPure_(this, _[0], pure))
@@ -209,7 +210,7 @@ export class RepositoryBaseC3<
209
210
  pure: Effect<A, E2, FixEnv<R2, Evt, readonly T[], readonly T2[]>>,
210
211
  batchSize?: number
211
212
  ) =>
212
- this.q2(flow(q, page({ limit: 1 }))).pipe(
213
+ this.q2(flow(q, page({ take: 1 }))).pipe(
213
214
  Effect.andThen((_) =>
214
215
  batchSize === undefined
215
216
  ? saveManyWithPure_(this, _, pure)
@@ -785,6 +786,50 @@ export function makeRepo<
785
786
  })
786
787
  }
787
788
 
789
+ const query: {
790
+ <
791
+ T extends { id: unknown },
792
+ PM extends PersistenceModelType<string>,
793
+ Evt,
794
+ ItemType extends string,
795
+ A,
796
+ R,
797
+ From extends FieldValues
798
+ >(
799
+ repo: Repository<T, PM, Evt, ItemType>,
800
+ q: QueryProjection<PM extends From ? From : never, A, R>
801
+ ): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
802
+ <
803
+ T extends { id: unknown },
804
+ PM extends PersistenceModelType<string>,
805
+ Evt,
806
+ ItemType extends string,
807
+ A,
808
+ R
809
+ >(
810
+ repo: Repository<T, PM, Evt, ItemType>,
811
+ q: QAll<NoInfer<PM>, A, R>
812
+ ): Effect.Effect<readonly A[], never, R>
813
+ } = (<
814
+ T extends { id: unknown },
815
+ PM extends PersistenceModelType<string>,
816
+ Evt,
817
+ ItemType extends string,
818
+ A,
819
+ R
820
+ >(repo: Repository<T, PM, Evt, ItemType>, q: QAll<PM, A, R>) => {
821
+ const a = Q2.toFilter(q)
822
+ return Effect.flatMap(
823
+ repo.utils.filter(a),
824
+ (_) =>
825
+ Unify.unify(
826
+ a.schema
827
+ ? repo.utils.parseMany2(_, a.schema as any)
828
+ : repo.utils.parseMany(_)
829
+ )
830
+ )
831
+ }) as any
832
+
788
833
  const r: Repository<T, PM, Evt, ItemType> = {
789
834
  /**
790
835
  * @internal
@@ -84,7 +84,7 @@ export class Or<TFieldValues extends FieldValues> extends Data.TaggedClass("or")
84
84
 
85
85
  export class Page<TFieldValues extends FieldValues> extends Data.TaggedClass("page")<{
86
86
  current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>
87
- limit?: number | undefined
87
+ take?: number | undefined
88
88
  skip?: number | undefined
89
89
  }> implements QueryEnd<TFieldValues> {
90
90
  readonly [QId]!: any
@@ -120,17 +120,17 @@ export const or: FilterContinuation = (...operation: any[]) => (current: any) =>
120
120
 
121
121
  export const order: <TFieldValues extends FieldValues, TFieldName extends FieldPath<TFieldValues>>(
122
122
  field: TFieldName,
123
- desc?: boolean
123
+ direction?: "ASC" | "DESC"
124
124
  ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
125
- (field, desc) => (current) => new Order({ current, field, direction: desc ? "DESC" : "ASC" })
125
+ (field, direction = "ASC") => (current) => new Order({ current, field, direction })
126
126
 
127
127
  export const page: <TFieldValues extends FieldValues>(
128
- page: { limit?: number; skip?: number }
128
+ page: { skip?: number; take?: number }
129
129
  ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
130
- ({ limit, skip }) => (current) =>
130
+ ({ skip, take }) => (current) =>
131
131
  new Page({
132
132
  current,
133
- limit,
133
+ take,
134
134
  skip
135
135
  })
136
136
 
@@ -1,41 +1,51 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
3
  import { toNonEmptyArray } from "@effect-app/core/Array"
4
- import { Effect, Match, Option, pipe, S, Unify } from "effect-app"
5
- import type { NoInfer } from "effect/Types"
4
+ import { Match, Option, pipe, S } from "effect-app"
5
+ import { dropUndefinedT } from "effect-app/utils"
6
6
  import type { FieldValues } from "../../filter/types.js"
7
7
  import type { FieldPath } from "../../filter/types/path/eager.js"
8
- import { make, type Q, type QAll, type QueryProjection } from "../query.js"
9
- import type { Repository } from "../Repository.js"
10
- import type { PersistenceModelType } from "../Store.js"
8
+ import { make, type Q, type QAll } from "../query.js"
11
9
  import type { FilterResult, QueryBuilder } from "../Store/filterApi/query.js"
12
10
 
11
+ type Result<TFieldValues extends FieldValues, A = TFieldValues, R = never> = {
12
+ filter: FilterResult[]
13
+ schema: S.Schema<A, TFieldValues, R> | undefined
14
+ limit: number | undefined
15
+ skip: number | undefined
16
+ order: { key: FieldPath<TFieldValues>; direction: "ASC" | "DESC" }[]
17
+ }
18
+
13
19
  const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never>(_: QAll<TFieldValues, A, R>) => {
14
- const values: FilterResult[] = []
15
20
  const a = _ as Q<TFieldValues>
16
21
 
17
- let schema: S.Schema<A, TFieldValues, R> | undefined = undefined as any
18
- let limit: number | undefined = undefined as any
19
- let skip: number | undefined = undefined as any
20
- const order: { field: FieldPath<TFieldValues>; direction: "ASC" | "DESC" }[] = []
22
+ const data: Result<TFieldValues, any, any> = {
23
+ filter: [],
24
+ schema: undefined,
25
+ limit: undefined,
26
+ skip: undefined,
27
+ order: []
28
+ }
21
29
 
22
- const upd = (v: { schema: any; limit: any; skip: any; filter: readonly FilterResult[]; order: any[] }) => {
23
- values.push(...v.filter)
24
- order.push(...v.order)
25
- limit = v.limit
26
- skip = v.skip
27
- schema = v.schema
30
+ const upd = (
31
+ v: Result<TFieldValues, any, any>
32
+ ) => {
33
+ data.filter.push(...v.filter)
34
+ data.order.push(...v.order)
35
+ data.limit = v.limit
36
+ data.skip = v.skip
37
+ data.schema = v.schema
28
38
  }
29
39
 
30
40
  pipe(
31
41
  a,
32
42
  Match.valueTags({
33
43
  value: () => {
34
- // values.push(value)
44
+ // data.filter.push(value)
35
45
  },
36
46
  where: ({ current, operation }) => {
37
47
  upd(interpret(current))
38
- values.push(
48
+ data.filter.push(
39
49
  {
40
50
  t: "where",
41
51
  path: operation[0],
@@ -47,11 +57,11 @@ const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never
47
57
  and: ({ current, operation }) => {
48
58
  upd(interpret(current))
49
59
  if (typeof operation === "function") {
50
- values.push(
60
+ data.filter.push(
51
61
  { t: "and-scope", result: interpret(operation(make())).filter }
52
62
  )
53
63
  } else {
54
- values.push(
64
+ data.filter.push(
55
65
  {
56
66
  t: "and",
57
67
  path: operation[0],
@@ -64,11 +74,11 @@ const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never
64
74
  or: ({ current, operation }) => {
65
75
  upd(interpret(current))
66
76
  if (typeof operation === "function") {
67
- values.push(
77
+ data.filter.push(
68
78
  { t: "or-scope", result: interpret(operation(make())).filter }
69
79
  )
70
80
  } else {
71
- values.push(
81
+ data.filter.push(
72
82
  {
73
83
  t: "or",
74
84
  path: operation[0],
@@ -78,66 +88,36 @@ const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never
78
88
  )
79
89
  }
80
90
  },
81
- order: ({ current }) => {
91
+ order: ({ current, direction, field }) => {
82
92
  upd(interpret(current))
93
+ data.order.push({ key: field, direction })
83
94
  },
84
95
  page: (v) => {
85
96
  upd(interpret(v.current))
86
- limit = v.limit
87
- skip = v.skip
97
+ data.limit = v.take
98
+ data.skip = v.skip
88
99
  },
89
100
  project: (v) => {
90
101
  upd(interpret(v.current))
91
- schema = v.schema
102
+ data.schema = v.schema
92
103
  }
93
104
  })
94
105
  )
95
106
 
96
- return {
97
- filter: values,
98
- limit,
99
- skip,
100
- schema,
101
- order
102
- }
107
+ return data
103
108
  }
104
109
 
105
- export const query: {
106
- <
107
- T extends { id: unknown },
108
- PM extends PersistenceModelType<string>,
109
- Evt,
110
- ItemType extends string,
111
- A,
112
- R,
113
- From extends FieldValues
114
- >(
115
- repo: Repository<T, PM, Evt, ItemType>,
116
- q: QueryProjection<PM extends From ? From : never, A, R>
117
- ): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
118
- <
119
- T extends { id: unknown },
120
- PM extends PersistenceModelType<string>,
121
- Evt,
122
- ItemType extends string,
123
- A,
124
- R
125
- >(
126
- repo: Repository<T, PM, Evt, ItemType>,
127
- q: QAll<NoInfer<PM>, A, R>
128
- ): Effect.Effect<readonly T[], never, R>
129
- } = (<
130
- T extends { id: unknown },
131
- PM extends PersistenceModelType<string>,
132
- Evt,
133
- ItemType extends string,
110
+ export const toFilter = <
111
+ TFieldValues extends FieldValues,
134
112
  A,
135
113
  R
136
- >(repo: Repository<T, PM, Evt, ItemType>, q: QAll<PM, A, R>) => {
114
+ >(
115
+ q: QAll<TFieldValues, A, R>
116
+ ) => {
137
117
  // TODO: Native interpreter for each db adapter, instead of the intermediate "new-kid" format
138
118
  const a = interpret(q)
139
119
  const schema = a.schema
140
- let select: (keyof PM)[] = []
120
+ let select: (keyof TFieldValues)[] = []
141
121
  if (schema) {
142
122
  let t = schema.ast
143
123
  if (S.AST.isTransform(t)) {
@@ -147,23 +127,17 @@ export const query: {
147
127
  select = t.propertySignatures.map((_) => _.name) as any
148
128
  }
149
129
  }
150
- return Effect.flatMap(
151
- repo.utils.filter({
152
- limit: a.limit,
153
- skip: a.skip,
154
- select: Option.getOrUndefined(toNonEmptyArray(select)),
155
- filter: a.filter.length
156
- ? {
157
- type: "new-kid" as const,
158
- build: () => a.filter
159
- } as QueryBuilder<any>
160
- : undefined
161
- }),
162
- (_) =>
163
- Unify.unify(
164
- schema
165
- ? repo.utils.parseMany2(_, schema as any)
166
- : repo.utils.parseMany(_)
167
- )
168
- )
169
- }) as any
130
+ return dropUndefinedT({
131
+ limit: a.limit,
132
+ skip: a.skip,
133
+ select: Option.getOrUndefined(toNonEmptyArray(select)),
134
+ schema,
135
+ order: Option.getOrUndefined(toNonEmptyArray(a.order)),
136
+ filter: a.filter.length
137
+ ? {
138
+ type: "new-kid" as const,
139
+ build: () => a.filter
140
+ } as QueryBuilder<any>
141
+ : undefined
142
+ })
143
+ }
@@ -1,37 +1,83 @@
1
1
  import { Effect, flow, pipe, S } from "effect-app"
2
- import { make, or, order, project, where } from "./query.js"
2
+ import { TagClassMakeId } from "effect-app/service"
3
+ import { pick } from "effect-app/utils"
4
+ import { inspect } from "util"
5
+ import { and, make, or, order, page, project, toFilter, where } from "./query.js"
3
6
  import { RepositoryDefaultImpl } from "./RepositoryBase.js"
4
- import { RequestContextContainer } from "./RequestContextContainer.js"
5
-
6
- const s = S.struct({ id: S.string, displayName: S.string, n: S.Date })
7
- type s = S.Schema.To<typeof s>
8
- type sfrom = S.Schema.From<typeof s>
9
- export class TestRepo extends RepositoryDefaultImpl<TestRepo>()<sfrom & { _etag: string | undefined }, never>()(
10
- "test",
11
- s
12
- ) {}
13
-
14
- const a = pipe(
15
- make<{ id: string; displayName: string; n: Date }>(),
7
+ import { memFilter } from "./Store/Memory.js"
8
+
9
+ export class s
10
+ extends S.Class<s>()({ id: S.StringId.withDefault, displayName: S.NonEmptyString255, n: S.Date.withDefault })
11
+ {}
12
+ export declare namespace s {
13
+ export type From = S.Schema.From<typeof s>
14
+ }
15
+
16
+ const MakeSomeService = Effect.succeed({ a: 1 })
17
+ export class SomeService extends TagClassMakeId("SomeService", MakeSomeService)<SomeService>() {}
18
+
19
+ const q = pipe(
20
+ make<s.From>(), // provided automatically inside Repo.q2()
16
21
  where("displayName", "Verona"),
17
22
  or(flow(
18
- where("displayName", "Riley")
19
- // and("resource._tag", "Printer")
23
+ where("displayName", "Riley"),
24
+ and("n", "gt", "2021-01-01T00:00:00Z") // TODO: work with To type translation, so Date?
20
25
  )),
21
- order("displayName")
22
- )
23
- const q = pipe(
24
- a,
25
- // page(2, 1)
26
+ order("displayName"),
27
+ page({ take: 10 }),
26
28
  project(
27
- // User.struct.pipe(S.pick("displayName"))
28
29
  S.transformOrFail(
29
- S.struct({ displayName: S.string }), // for projection performance benefit, this should be limited to the fields interested
30
- S.struct({ displayName: S.string }),
31
- (_) => Effect.andThen(RequestContextContainer, _),
30
+ S.struct({ displayName: S.string }), // for projection performance benefit, this should be limited to the fields interested, and leads to SELECT fields
31
+ S.struct(pick(s.fields, "displayName")),
32
+ (_) => Effect.andThen(SomeService, _),
32
33
  () => Effect.die(new Error("not implemented"))
33
34
  )
34
35
  )
35
36
  )
36
- console.log(q)
37
- console.log(TestRepo.q2(() => q))
37
+
38
+ it("works", () => {
39
+ console.log("raw", inspect(q, undefined, 25))
40
+ const interpreted = toFilter(q)
41
+ console.log("interpreted", inspect(interpreted, undefined, 25))
42
+ const filtersBuilt = interpreted.filter.build()
43
+ console.log("filtersBuilt", inspect(filtersBuilt, undefined, 25))
44
+ const items = [
45
+ new s({ displayName: S.NonEmptyString255("Verona"), n: new Date("2020-01-01T00:00:00Z") }),
46
+ new s({ displayName: S.NonEmptyString255("Riley") }),
47
+ new s({ displayName: S.NonEmptyString255("Riley"), n: new Date("2020-01-01T00:00:00Z") })
48
+ ]
49
+ .map((_) => S.encodeSync(s)(_))
50
+
51
+ const processed = memFilter(interpreted)(items)
52
+
53
+ expect(processed).toEqual(items.slice(0, 2).toReversed().map(({ displayName }) => ({ displayName })))
54
+ })
55
+
56
+ it.skip("works with repo", () => {
57
+ class TestRepo extends RepositoryDefaultImpl<TestRepo>()<s.From & { _etag: string | undefined }, never>()(
58
+ "test",
59
+ s
60
+ ) {}
61
+ const q1 = TestRepo.q2(() => q)
62
+
63
+ // same as above, but with the `flow` helper
64
+ const q2 = TestRepo.q2(flow(
65
+ where("displayName", "Verona"),
66
+ or(flow(
67
+ where("displayName", "Riley"),
68
+ and("n", "gt", "2021-01-01T00:00:00Z") // TODO: work with To type translation, so Date?
69
+ )),
70
+ order("displayName"),
71
+ page({ take: 10 }),
72
+ project(
73
+ S.transformOrFail(
74
+ S.struct({ displayName: S.string }), // for projection performance benefit, this should be limited to the fields interested, and leads to SELECT fields
75
+ S.struct(pick(s.fields, "displayName")),
76
+ (_) => Effect.andThen(SomeService, _),
77
+ () => Effect.die(new Error("not implemented"))
78
+ )
79
+ )
80
+ ))
81
+
82
+ expect([q1, q2]).toEqual([q1, q1])
83
+ })