@effect-app/infra 0.200.2 → 0.201.0

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.
@@ -1,3 +1,6 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
1
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
5
  /* eslint-disable @typescript-eslint/ban-types */
3
6
  // import type { ParserEnv } from "@effect-app/schema/custom/Parser"
@@ -23,13 +26,14 @@ import { makeFilters } from "@effect-app/infra/filter"
23
26
  import type { ParseResult, Schema } from "@effect-app/schema"
24
27
  import { NonNegativeInt } from "@effect-app/schema"
25
28
  import type { Context, NonEmptyArray, NonEmptyReadonlyArray } from "effect-app"
26
- 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"
27
30
  import { runTerm } from "effect-app/Pure"
28
31
  import type { FixEnv, PureEnv } from "effect-app/Pure"
29
32
  import { assignTag } from "effect-app/service"
33
+ import type { NoInfer } from "effect/Types"
30
34
  import { type InvalidStateError, NotFoundError, type OptimisticConcurrencyException } from "../errors.js"
31
35
  import type { FieldValues } from "../filter/types.js"
32
- import { make as makeQuery, page, query } from "./query.js"
36
+ import { make as makeQuery, page } from "./query.js"
33
37
  import type { QAll, Query, QueryEnd, QueryProjection, QueryWhere } from "./query.js"
34
38
  import * as Q2 from "./query.js"
35
39
  import { ContextMapContainer } from "./Store/ContextMapContainer.js"
@@ -99,17 +103,17 @@ export abstract class RepositoryBaseC<
99
103
  /** @deprecated use q2 */
100
104
  abstract readonly mapped: Mapped<PM, Omit<PM, "_etag">>
101
105
  abstract readonly q2: {
102
- <A, R, From extends FieldValues>(
103
- q: (initial: Query<Omit<PM, "_etag">>) => QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
104
- ): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
105
106
  <R = never>(
106
107
  q: (initial: Query<Omit<PM, "_etag">>) => QAll<Omit<PM, "_etag">, T, R>
107
108
  ): Effect.Effect<readonly T[], never, R>
109
+ <A, R, From extends FieldValues>(
110
+ q: (initial: Query<Omit<PM, "_etag">>) => QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
111
+ ): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
108
112
 
113
+ <R = never>(q: QAll<Omit<PM, "_etag">, T, R>): Effect.Effect<readonly T[], never, R>
109
114
  <A, R, From extends FieldValues>(
110
115
  q: QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
111
116
  ): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
112
- <R = never>(q: QAll<Omit<PM, "_etag">, T, R>): Effect.Effect<readonly T[], never, R>
113
117
  }
114
118
  }
115
119
 
@@ -195,7 +199,7 @@ export class RepositoryBaseC3<
195
199
  q: (q: Query<Omit<PM, "_etag">>) => Query<Omit<PM, "_etag">> | QueryEnd<Omit<PM, "_etag">>,
196
200
  pure: Effect<A, E2, FixEnv<R2, Evt, T, T2>>
197
201
  ) =>
198
- this.q2(flow(q, page({ limit: 1 }))).pipe(
202
+ this.q2(flow(q, page({ take: 1 }))).pipe(
199
203
  Effect.map(ReadonlyArray.toNonEmptyArray),
200
204
  Effect.flatMap(Effect.mapError(() => new NotFoundError({ type: this.itemType, id: q }))),
201
205
  Effect.andThen((_) => saveWithPure_(this, _[0], pure))
@@ -206,7 +210,7 @@ export class RepositoryBaseC3<
206
210
  pure: Effect<A, E2, FixEnv<R2, Evt, readonly T[], readonly T2[]>>,
207
211
  batchSize?: number
208
212
  ) =>
209
- this.q2(flow(q, page({ limit: 1 }))).pipe(
213
+ this.q2(flow(q, page({ take: 1 }))).pipe(
210
214
  Effect.andThen((_) =>
211
215
  batchSize === undefined
212
216
  ? saveManyWithPure_(this, _, pure)
@@ -782,6 +786,50 @@ export function makeRepo<
782
786
  })
783
787
  }
784
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
+
785
833
  const r: Repository<T, PM, Evt, ItemType> = {
786
834
  /**
787
835
  * @internal
@@ -1105,17 +1153,17 @@ export interface RepoFunctions<T extends { id: unknown }, PM extends { id: strin
1105
1153
  }
1106
1154
 
1107
1155
  readonly q2: {
1108
- <A, R, From extends FieldValues>(
1109
- q: (initial: Query<Omit<PM, "_etag">>) => QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
1110
- ): Effect.Effect<readonly A[], S.ParseResult.ParseError, Service | R>
1111
1156
  <R = never>(
1112
1157
  q: (initial: Query<Omit<PM, "_etag">>) => QAll<Omit<PM, "_etag">, T, R>
1113
1158
  ): Effect.Effect<readonly T[], never, Service | R>
1159
+ <A, R, From extends FieldValues>(
1160
+ q: (initial: Query<Omit<PM, "_etag">>) => QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
1161
+ ): Effect.Effect<readonly A[], S.ParseResult.ParseError, Service | R>
1114
1162
 
1163
+ <R = never>(q: QAll<Omit<PM, "_etag">, T, R>): Effect.Effect<readonly T[], never, Service | R>
1115
1164
  <A, R, From extends FieldValues>(
1116
1165
  q: QueryProjection<Omit<PM, "_etag"> extends From ? From : never, A, R>
1117
1166
  ): Effect.Effect<readonly A[], S.ParseResult.ParseError, Service | R>
1118
- <R = never>(q: QAll<Omit<PM, "_etag">, T, R>): Effect.Effect<readonly T[], never, Service | R>
1119
1167
  }
1120
1168
  }
1121
1169
 
@@ -0,0 +1,290 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ import type { FieldValues } from "@effect-app/infra/filter/types"
5
+ import type { FieldPath, FieldPathValue } from "@effect-app/infra/filter/types/path/eager"
6
+ import type { Ops } from "@effect-app/infra/services/Store/filterApi/proxy"
7
+ import type { S } from "effect-app"
8
+ import { Data } from "effect-app"
9
+ import type { Covariant } from "effect/Types"
10
+
11
+ export type QAll<TFieldValues extends FieldValues, A = TFieldValues, R = never> =
12
+ | Query<TFieldValues>
13
+ | QueryWhere<TFieldValues>
14
+ | QueryEnd<TFieldValues>
15
+ | QueryProjection<TFieldValues, A, R>
16
+
17
+ export const QId = Symbol()
18
+ export type QId = typeof QId
19
+
20
+ export interface QueryTogether<
21
+ out TFieldValues extends FieldValues,
22
+ out T extends "initial" | "where" | "end" | "projection" = "initial",
23
+ out A = TFieldValues,
24
+ out R = never
25
+ > {
26
+ readonly [QId]: {
27
+ readonly _TFieldValues: Covariant<TFieldValues>
28
+ readonly _T: Covariant<T>
29
+ readonly _A: Covariant<A>
30
+ readonly _R: Covariant<R>
31
+ }
32
+ }
33
+
34
+ export type Query<TFieldValues extends FieldValues> = QueryTogether<TFieldValues, "initial">
35
+ export type QueryWhere<TFieldValues extends FieldValues> = QueryTogether<TFieldValues, "where">
36
+
37
+ export type QueryEnd<TFieldValues extends FieldValues> = QueryTogether<TFieldValues, "end">
38
+
39
+ export type QueryProjection<TFieldValues extends FieldValues, A = TFieldValues, R = never> = QueryTogether<
40
+ TFieldValues,
41
+ "projection",
42
+ A,
43
+ R
44
+ >
45
+
46
+ export type Q<TFieldValues extends FieldValues> =
47
+ | Initial<TFieldValues>
48
+ | Where<TFieldValues>
49
+ | And<TFieldValues>
50
+ | Or<TFieldValues>
51
+ | Order<TFieldValues, any>
52
+ | Page<TFieldValues>
53
+ | Project<any, TFieldValues, any>
54
+
55
+ export class Initial<TFieldValues extends FieldValues> extends Data.TaggedClass("value")<{ value: "initial" }>
56
+ implements Query<TFieldValues>
57
+ {
58
+ readonly [QId]!: any
59
+ constructor() {
60
+ super({ value: "initial" as const })
61
+ }
62
+ }
63
+
64
+ export class Where<TFieldValues extends FieldValues> extends Data.TaggedClass("where")<{
65
+ current: Query<TFieldValues>
66
+ operation: [string, Ops, any] | [string, any]
67
+ }> implements QueryWhere<TFieldValues> {
68
+ readonly [QId]!: any
69
+ }
70
+
71
+ export class And<TFieldValues extends FieldValues> extends Data.TaggedClass("and")<{
72
+ current: Query<TFieldValues>
73
+ operation: [string, Ops, any] | [string, any] | ((q: Query<TFieldValues>) => QueryWhere<TFieldValues>)
74
+ }> implements QueryWhere<TFieldValues> {
75
+ readonly [QId]!: any
76
+ }
77
+
78
+ export class Or<TFieldValues extends FieldValues> extends Data.TaggedClass("or")<{
79
+ current: Query<TFieldValues>
80
+ operation: [string, Ops, any] | [string, any] | ((q: Query<TFieldValues>) => QueryWhere<TFieldValues>)
81
+ }> implements QueryWhere<TFieldValues> {
82
+ readonly [QId]!: any
83
+ }
84
+
85
+ export class Page<TFieldValues extends FieldValues> extends Data.TaggedClass("page")<{
86
+ current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>
87
+ take?: number | undefined
88
+ skip?: number | undefined
89
+ }> implements QueryEnd<TFieldValues> {
90
+ readonly [QId]!: any
91
+ }
92
+
93
+ export class Order<TFieldValues extends FieldValues, TFieldName extends FieldPath<TFieldValues>>
94
+ extends Data.TaggedClass("order")<{
95
+ current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>
96
+ field: TFieldName
97
+ direction: "ASC" | "DESC"
98
+ }>
99
+ implements QueryEnd<TFieldValues>
100
+ {
101
+ readonly [QId]!: any
102
+ }
103
+
104
+ export class Project<A, TFieldValues extends FieldValues, R> extends Data.TaggedClass("project")<{
105
+ current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>
106
+ schema: S.Schema<A, TFieldValues, R>
107
+ }> implements QueryProjection<TFieldValues, A, R> {
108
+ readonly [QId]!: any
109
+ }
110
+
111
+ export const make: <TFieldValues extends FieldValues>() => Query<TFieldValues> = () => new Initial()
112
+
113
+ export const where: FilterWhere = (...operation: any[]) => (current: any) => new Where({ current, operation } as any)
114
+
115
+ export const and: FilterContinuation = (...operation: any[]) => (current: any) =>
116
+ new And({ current, operation: typeof operation[0] === "function" ? operation[0] : operation } as any)
117
+
118
+ export const or: FilterContinuation = (...operation: any[]) => (current: any) =>
119
+ new Or({ current, operation: typeof operation[0] === "function" ? operation[0] : operation } as any)
120
+
121
+ export const order: <TFieldValues extends FieldValues, TFieldName extends FieldPath<TFieldValues>>(
122
+ field: TFieldName,
123
+ direction?: "ASC" | "DESC"
124
+ ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
125
+ (field, direction = "ASC") => (current) => new Order({ current, field, direction })
126
+
127
+ export const page: <TFieldValues extends FieldValues>(
128
+ page: { skip?: number; take?: number }
129
+ ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
130
+ ({ skip, take }) => (current) =>
131
+ new Page({
132
+ current,
133
+ take,
134
+ skip
135
+ })
136
+
137
+ export const project: <TFieldValues extends FieldValues, A = FieldValues, R = never>(
138
+ schema: S.Schema<A, TFieldValues, R>
139
+ ) => (
140
+ current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>
141
+ ) => QueryProjection<TFieldValues, A, R> = (schema) => (current) => new Project({ current, schema })
142
+
143
+ export type FilterWheres = {
144
+ <
145
+ TFieldValues extends FieldValues,
146
+ TFieldName extends FieldPath<TFieldValues>,
147
+ V extends FieldPathValue<TFieldValues, TFieldName>
148
+ >(
149
+ path: TFieldName,
150
+ value: V
151
+ ): (
152
+ current: Query<TFieldValues>
153
+ ) => QueryWhere<TFieldValues>
154
+ <
155
+ TFieldValues extends FieldValues,
156
+ TFieldName extends FieldPath<TFieldValues>,
157
+ V extends FieldPathValue<TFieldValues, TFieldName>
158
+ >(
159
+ path: TFieldName,
160
+ op: "neq",
161
+ value: V
162
+ ): (
163
+ current: Query<TFieldValues>
164
+ ) => QueryWhere<TFieldValues>
165
+ <
166
+ TFieldValues extends FieldValues,
167
+ TFieldName extends FieldPath<TFieldValues>,
168
+ V extends FieldPathValue<TFieldValues, TFieldName>
169
+ >(
170
+ path: TFieldName,
171
+ op: "gt" | "gte" | "lt" | "lte",
172
+ value: V // only numbers?
173
+ ): (
174
+ current: Query<TFieldValues>
175
+ ) => QueryWhere<TFieldValues>
176
+ <
177
+ TFieldValues extends FieldValues,
178
+ TFieldName extends FieldPath<TFieldValues>,
179
+ V extends FieldPathValue<TFieldValues, TFieldName>
180
+ >(
181
+ path: TFieldName,
182
+ op: "startsWith" | "endsWith" | "contains" | "notContains" | "notStartsWith" | "notEndsWith",
183
+ value: V // only strings?
184
+ ): (
185
+ current: Query<TFieldValues>
186
+ ) => QueryWhere<TFieldValues>
187
+ <
188
+ TFieldValues extends FieldValues,
189
+ TFieldName extends FieldPath<TFieldValues>,
190
+ V extends FieldPathValue<TFieldValues, TFieldName>
191
+ >(
192
+ path: TFieldName,
193
+ op: "in" | "notIn",
194
+ value: readonly V[]
195
+ ): (
196
+ current: Query<TFieldValues>
197
+ ) => QueryWhere<TFieldValues>
198
+ }
199
+
200
+ export type FilterWhere = {
201
+ <
202
+ TFieldValues extends FieldValues,
203
+ TFieldName extends FieldPath<TFieldValues>,
204
+ V extends FieldPathValue<TFieldValues, TFieldName>
205
+ >(f: {
206
+ path: TFieldName
207
+ op: Ops
208
+ value: V
209
+ }): (
210
+ current: Query<TFieldValues>
211
+ ) => QueryWhere<TFieldValues>
212
+ } & FilterWheres
213
+
214
+ export type FilterContinuations = {
215
+ <
216
+ TFieldValues extends FieldValues,
217
+ TFieldName extends FieldPath<TFieldValues>,
218
+ V extends FieldPathValue<TFieldValues, TFieldName>
219
+ >(
220
+ path: TFieldName,
221
+ value: V
222
+ ): (
223
+ current: QueryWhere<TFieldValues>
224
+ ) => QueryWhere<TFieldValues>
225
+ <
226
+ TFieldValues extends FieldValues,
227
+ TFieldName extends FieldPath<TFieldValues>,
228
+ V extends FieldPathValue<TFieldValues, TFieldName>
229
+ >(
230
+ path: TFieldName,
231
+ op: "neq",
232
+ value: V
233
+ ): (
234
+ current: QueryWhere<TFieldValues>
235
+ ) => QueryWhere<TFieldValues>
236
+ <
237
+ TFieldValues extends FieldValues,
238
+ TFieldName extends FieldPath<TFieldValues>,
239
+ V extends FieldPathValue<TFieldValues, TFieldName>
240
+ >(
241
+ path: TFieldName,
242
+ op: "gt" | "gte" | "lt" | "lte",
243
+ value: V // only numbers?
244
+ ): (
245
+ current: QueryWhere<TFieldValues>
246
+ ) => QueryWhere<TFieldValues>
247
+ <
248
+ TFieldValues extends FieldValues,
249
+ TFieldName extends FieldPath<TFieldValues>,
250
+ V extends FieldPathValue<TFieldValues, TFieldName>
251
+ >(
252
+ path: TFieldName,
253
+ op: "startsWith" | "endsWith" | "contains" | "notContains" | "notStartsWith" | "notEndsWith",
254
+ value: V // only strings?
255
+ ): (
256
+ current: QueryWhere<TFieldValues>
257
+ ) => QueryWhere<TFieldValues>
258
+ <
259
+ TFieldValues extends FieldValues,
260
+ TFieldName extends FieldPath<TFieldValues>,
261
+ V extends FieldPathValue<TFieldValues, TFieldName>
262
+ >(
263
+ path: TFieldName,
264
+ op: "in" | "notIn",
265
+ value: readonly V[]
266
+ ): (
267
+ current: QueryWhere<TFieldValues>
268
+ ) => QueryWhere<TFieldValues>
269
+ }
270
+
271
+ export type FilterContinuation = {
272
+ <TFieldValues extends FieldValues>(
273
+ fb: (
274
+ current: Query<TFieldValues>
275
+ ) => QueryWhere<TFieldValues>
276
+ ): (
277
+ current: QueryWhere<TFieldValues>
278
+ ) => QueryWhere<TFieldValues>
279
+ <
280
+ TFieldValues extends FieldValues,
281
+ TFieldName extends FieldPath<TFieldValues>,
282
+ V extends FieldPathValue<TFieldValues, TFieldName>
283
+ >(f: {
284
+ path: TFieldName
285
+ op: Ops
286
+ value: V
287
+ }): (
288
+ current: QueryWhere<TFieldValues>
289
+ ) => QueryWhere<TFieldValues>
290
+ } & FilterContinuations
@@ -0,0 +1,143 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
+ import { toNonEmptyArray } from "@effect-app/core/Array"
4
+ import { Match, Option, pipe, S } from "effect-app"
5
+ import { dropUndefinedT } from "effect-app/utils"
6
+ import type { FieldValues } from "../../filter/types.js"
7
+ import type { FieldPath } from "../../filter/types/path/eager.js"
8
+ import { make, type Q, type QAll } from "../query.js"
9
+ import type { FilterResult, QueryBuilder } from "../Store/filterApi/query.js"
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
+
19
+ const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never>(_: QAll<TFieldValues, A, R>) => {
20
+ const a = _ as Q<TFieldValues>
21
+
22
+ const data: Result<TFieldValues, any, any> = {
23
+ filter: [],
24
+ schema: undefined,
25
+ limit: undefined,
26
+ skip: undefined,
27
+ order: []
28
+ }
29
+
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
38
+ }
39
+
40
+ pipe(
41
+ a,
42
+ Match.valueTags({
43
+ value: () => {
44
+ // data.filter.push(value)
45
+ },
46
+ where: ({ current, operation }) => {
47
+ upd(interpret(current))
48
+ data.filter.push(
49
+ {
50
+ t: "where",
51
+ path: operation[0],
52
+ op: operation.length === 2 ? "eq" : operation[1],
53
+ value: operation.length === 2 ? operation[1] : operation[2]
54
+ }
55
+ )
56
+ },
57
+ and: ({ current, operation }) => {
58
+ upd(interpret(current))
59
+ if (typeof operation === "function") {
60
+ data.filter.push(
61
+ { t: "and-scope", result: interpret(operation(make())).filter }
62
+ )
63
+ } else {
64
+ data.filter.push(
65
+ {
66
+ t: "and",
67
+ path: operation[0],
68
+ op: operation.length === 2 ? "eq" : operation[1],
69
+ value: operation.length === 2 ? operation[1] : operation[2]
70
+ }
71
+ )
72
+ }
73
+ },
74
+ or: ({ current, operation }) => {
75
+ upd(interpret(current))
76
+ if (typeof operation === "function") {
77
+ data.filter.push(
78
+ { t: "or-scope", result: interpret(operation(make())).filter }
79
+ )
80
+ } else {
81
+ data.filter.push(
82
+ {
83
+ t: "or",
84
+ path: operation[0],
85
+ op: operation.length === 2 ? "eq" : operation[1],
86
+ value: operation.length === 2 ? operation[1] : operation[2]
87
+ }
88
+ )
89
+ }
90
+ },
91
+ order: ({ current, direction, field }) => {
92
+ upd(interpret(current))
93
+ data.order.push({ key: field, direction })
94
+ },
95
+ page: (v) => {
96
+ upd(interpret(v.current))
97
+ data.limit = v.take
98
+ data.skip = v.skip
99
+ },
100
+ project: (v) => {
101
+ upd(interpret(v.current))
102
+ data.schema = v.schema
103
+ }
104
+ })
105
+ )
106
+
107
+ return data
108
+ }
109
+
110
+ export const toFilter = <
111
+ TFieldValues extends FieldValues,
112
+ A,
113
+ R
114
+ >(
115
+ q: QAll<TFieldValues, A, R>
116
+ ) => {
117
+ // TODO: Native interpreter for each db adapter, instead of the intermediate "new-kid" format
118
+ const a = interpret(q)
119
+ const schema = a.schema
120
+ let select: (keyof TFieldValues)[] = []
121
+ if (schema) {
122
+ let t = schema.ast
123
+ if (S.AST.isTransform(t)) {
124
+ t = t.from
125
+ }
126
+ if (S.AST.isTypeLiteral(t)) {
127
+ select = t.propertySignatures.map((_) => _.name) as any
128
+ }
129
+ }
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
+ }
@@ -0,0 +1,83 @@
1
+ import { Effect, flow, pipe, S } from "effect-app"
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"
6
+ import { RepositoryDefaultImpl } from "./RepositoryBase.js"
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()
21
+ where("displayName", "Verona"),
22
+ or(flow(
23
+ where("displayName", "Riley"),
24
+ and("n", "gt", "2021-01-01T00:00:00Z") // TODO: work with To type translation, so Date?
25
+ )),
26
+ order("displayName"),
27
+ page({ take: 10 }),
28
+ project(
29
+ S.transformOrFail(
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, _),
33
+ () => Effect.die(new Error("not implemented"))
34
+ )
35
+ )
36
+ )
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
+ })