@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.
- package/CHANGELOG.md +16 -0
- package/_cjs/services/RepositoryBase.cjs +10 -3
- package/_cjs/services/RepositoryBase.cjs.map +1 -1
- package/_cjs/services/query/dsl.cjs +79 -0
- package/_cjs/services/query/dsl.cjs.map +1 -0
- package/_cjs/services/query/new-kid-interpreter.cjs +134 -0
- package/_cjs/services/query/new-kid-interpreter.cjs.map +1 -0
- package/_cjs/services/query.cjs +20 -200
- package/_cjs/services/query.cjs.map +1 -1
- package/_cjs/services/query.test.cjs +71 -0
- package/_cjs/services/query.test.cjs.map +1 -0
- package/dist/services/RepositoryBase.d.ts +10 -10
- package/dist/services/RepositoryBase.d.ts.map +1 -1
- package/dist/services/RepositoryBase.js +14 -5
- package/dist/services/query/dsl.d.ts +127 -0
- package/dist/services/query/dsl.d.ts.map +1 -0
- package/dist/services/query/dsl.js +38 -0
- package/dist/services/query/new-kid-interpreter.d.ts +19 -0
- package/dist/services/query/new-kid-interpreter.d.ts.map +1 -0
- package/dist/services/query/new-kid-interpreter.js +108 -0
- package/dist/services/query.d.ts +2 -163
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +3 -157
- package/dist/services/query.test.d.ts.map +1 -0
- package/package.json +23 -3
- package/src/services/RepositoryBase.ts +60 -12
- package/src/services/query/dsl.ts +290 -0
- package/src/services/query/new-kid-interpreter.ts +143 -0
- package/src/services/query.test.ts +83 -0
- package/src/services/query.ts +2 -462
|
@@ -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
|
|
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({
|
|
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({
|
|
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
|
+
})
|