@effect-app/infra 0.200.1 → 0.200.3

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/services/RepositoryBase.cjs +3 -0
  3. package/_cjs/services/RepositoryBase.cjs.map +1 -1
  4. package/_cjs/services/query/dsl.cjs +79 -0
  5. package/_cjs/services/query/dsl.cjs.map +1 -0
  6. package/_cjs/services/query/new-kid-interpreter.cjs +129 -0
  7. package/_cjs/services/query/new-kid-interpreter.cjs.map +1 -0
  8. package/_cjs/services/query.cjs +20 -200
  9. package/_cjs/services/query.cjs.map +1 -1
  10. package/_cjs/services/query.test.cjs +34 -0
  11. package/_cjs/services/query.test.cjs.map +1 -0
  12. package/dist/services/QueueMaker/memQueue.d.ts +1 -1
  13. package/dist/services/QueueMaker/sbqueue.d.ts +1 -1
  14. package/dist/services/RepositoryBase.d.ts +10 -10
  15. package/dist/services/RepositoryBase.d.ts.map +1 -1
  16. package/dist/services/RepositoryBase.js +4 -1
  17. package/dist/services/query/dsl.d.ts +127 -0
  18. package/dist/services/query/dsl.d.ts.map +1 -0
  19. package/dist/services/query/dsl.js +38 -0
  20. package/dist/services/query/new-kid-interpreter.d.ts +15 -0
  21. package/dist/services/query/new-kid-interpreter.d.ts.map +1 -0
  22. package/dist/services/query/new-kid-interpreter.js +110 -0
  23. package/dist/services/query.d.ts +2 -163
  24. package/dist/services/query.d.ts.map +1 -1
  25. package/dist/services/query.js +3 -157
  26. package/dist/services/query.test.d.ts.map +1 -0
  27. package/package.json +23 -3
  28. package/src/services/RepositoryBase.ts +13 -10
  29. package/src/services/query/dsl.ts +290 -0
  30. package/src/services/query/new-kid-interpreter.ts +169 -0
  31. package/src/services/query.test.ts +37 -0
  32. package/src/services/query.ts +2 -462
@@ -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
+ limit?: 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
+ desc?: boolean
124
+ ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
125
+ (field, desc) => (current) => new Order({ current, field, direction: desc ? "DESC" : "ASC" })
126
+
127
+ export const page: <TFieldValues extends FieldValues>(
128
+ page: { limit?: number; skip?: number }
129
+ ) => (current: Query<TFieldValues> | QueryWhere<TFieldValues> | QueryEnd<TFieldValues>) => QueryEnd<TFieldValues> =
130
+ ({ limit, skip }) => (current) =>
131
+ new Page({
132
+ current,
133
+ limit,
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,169 @@
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 { Effect, Match, Option, pipe, S, Unify } from "effect-app"
5
+ import type { NoInfer } from "effect/Types"
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, type QueryProjection } from "../query.js"
9
+ import type { Repository } from "../Repository.js"
10
+ import type { PersistenceModelType } from "../Store.js"
11
+ import type { FilterResult, QueryBuilder } from "../Store/filterApi/query.js"
12
+
13
+ const interpret = <TFieldValues extends FieldValues, A = TFieldValues, R = never>(_: QAll<TFieldValues, A, R>) => {
14
+ const values: FilterResult[] = []
15
+ const a = _ as Q<TFieldValues>
16
+
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" }[] = []
21
+
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
28
+ }
29
+
30
+ pipe(
31
+ a,
32
+ Match.valueTags({
33
+ value: () => {
34
+ // values.push(value)
35
+ },
36
+ where: ({ current, operation }) => {
37
+ upd(interpret(current))
38
+ values.push(
39
+ {
40
+ t: "where",
41
+ path: operation[0],
42
+ op: operation.length === 2 ? "eq" : operation[1],
43
+ value: operation.length === 2 ? operation[1] : operation[2]
44
+ }
45
+ )
46
+ },
47
+ and: ({ current, operation }) => {
48
+ upd(interpret(current))
49
+ if (typeof operation === "function") {
50
+ values.push(
51
+ { t: "and-scope", result: interpret(operation(make())).filter }
52
+ )
53
+ } else {
54
+ values.push(
55
+ {
56
+ t: "and",
57
+ path: operation[0],
58
+ op: operation.length === 2 ? "eq" : operation[1],
59
+ value: operation.length === 2 ? operation[1] : operation[2]
60
+ }
61
+ )
62
+ }
63
+ },
64
+ or: ({ current, operation }) => {
65
+ upd(interpret(current))
66
+ if (typeof operation === "function") {
67
+ values.push(
68
+ { t: "or-scope", result: interpret(operation(make())).filter }
69
+ )
70
+ } else {
71
+ values.push(
72
+ {
73
+ t: "or",
74
+ path: operation[0],
75
+ op: operation.length === 2 ? "eq" : operation[1],
76
+ value: operation.length === 2 ? operation[1] : operation[2]
77
+ }
78
+ )
79
+ }
80
+ },
81
+ order: ({ current }) => {
82
+ upd(interpret(current))
83
+ },
84
+ page: (v) => {
85
+ upd(interpret(v.current))
86
+ limit = v.limit
87
+ skip = v.skip
88
+ },
89
+ project: (v) => {
90
+ upd(interpret(v.current))
91
+ schema = v.schema
92
+ }
93
+ })
94
+ )
95
+
96
+ return {
97
+ filter: values,
98
+ limit,
99
+ skip,
100
+ schema,
101
+ order
102
+ }
103
+ }
104
+
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,
134
+ A,
135
+ R
136
+ >(repo: Repository<T, PM, Evt, ItemType>, q: QAll<PM, A, R>) => {
137
+ // TODO: Native interpreter for each db adapter, instead of the intermediate "new-kid" format
138
+ const a = interpret(q)
139
+ const schema = a.schema
140
+ let select: (keyof PM)[] = []
141
+ if (schema) {
142
+ let t = schema.ast
143
+ if (S.AST.isTransform(t)) {
144
+ t = t.from
145
+ }
146
+ if (S.AST.isTypeLiteral(t)) {
147
+ select = t.propertySignatures.map((_) => _.name) as any
148
+ }
149
+ }
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
@@ -0,0 +1,37 @@
1
+ import { Effect, flow, pipe, S } from "effect-app"
2
+ import { make, or, order, project, where } from "./query.js"
3
+ 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 }>(),
16
+ where("displayName", "Verona"),
17
+ or(flow(
18
+ where("displayName", "Riley")
19
+ // and("resource._tag", "Printer")
20
+ )),
21
+ order("displayName")
22
+ )
23
+ const q = pipe(
24
+ a,
25
+ // page(2, 1)
26
+ project(
27
+ // User.struct.pipe(S.pick("displayName"))
28
+ 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, _),
32
+ () => Effect.die(new Error("not implemented"))
33
+ )
34
+ )
35
+ )
36
+ console.log(q)
37
+ console.log(TestRepo.q2(() => q))