@effect-app/infra 0.117.0 → 0.119.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/_cjs/filter.cjs +13 -13
  3. package/_cjs/filter.cjs.map +1 -1
  4. package/_cjs/services/Repository.cjs +4 -38
  5. package/_cjs/services/Repository.cjs.map +1 -1
  6. package/_cjs/services/RepositoryBase.cjs +39 -36
  7. package/_cjs/services/RepositoryBase.cjs.map +1 -1
  8. package/_cjs/services/Store/Cosmos/query.cjs +291 -0
  9. package/_cjs/services/Store/Cosmos/query.cjs.map +1 -0
  10. package/_cjs/services/Store/Cosmos.cjs +38 -183
  11. package/_cjs/services/Store/Cosmos.cjs.map +1 -1
  12. package/_cjs/services/Store/Cosmos.test.cjs +7 -7
  13. package/_cjs/services/Store/Cosmos.test.cjs.map +1 -1
  14. package/_cjs/services/Store/Memory.cjs +20 -17
  15. package/_cjs/services/Store/Memory.cjs.map +1 -1
  16. package/_cjs/services/Store/Redis.cjs +1 -1
  17. package/_cjs/services/Store/Redis.cjs.map +1 -1
  18. package/_cjs/services/Store/codeFilter.cjs +151 -0
  19. package/_cjs/services/Store/codeFilter.cjs.map +1 -0
  20. package/_cjs/services/Store/codeFilter.test.cjs +8 -8
  21. package/_cjs/services/Store/codeFilter.test.cjs.map +1 -1
  22. package/_cjs/services/Store/filterApi/proxy.cjs +40 -0
  23. package/_cjs/services/Store/filterApi/proxy.cjs.map +1 -0
  24. package/_cjs/services/Store/filterApi/query.cjs +173 -0
  25. package/_cjs/services/Store/filterApi/query.cjs.map +1 -0
  26. package/_cjs/services/Store/filterApi.test.cjs +164 -0
  27. package/_cjs/services/Store/filterApi.test.cjs.map +1 -0
  28. package/_cjs/services/Store/service.cjs.map +1 -1
  29. package/_cjs/services/Store/utils.cjs +8 -1
  30. package/_cjs/services/Store/utils.cjs.map +1 -1
  31. package/_src/filter.ts +13 -13
  32. package/_src/services/Repository.ts +124 -70
  33. package/_src/services/RepositoryBase.ts +26 -12
  34. package/_src/services/Store/Cosmos/query.ts +390 -0
  35. package/_src/services/Store/Cosmos.test.ts +6 -6
  36. package/_src/services/Store/Cosmos.ts +39 -245
  37. package/_src/services/Store/Memory.ts +21 -18
  38. package/_src/services/Store/Redis.ts +2 -3
  39. package/_src/services/Store/codeFilter.test.ts +8 -8
  40. package/_src/services/Store/codeFilter.ts +150 -0
  41. package/_src/services/Store/filterApi/proxy.ts +109 -0
  42. package/_src/services/Store/filterApi/query.ts +256 -0
  43. package/_src/services/Store/filterApi.test.ts +263 -0
  44. package/_src/services/Store/service.ts +17 -5
  45. package/_src/services/Store/utils.ts +10 -7
  46. package/dist/filter.d.ts +13 -13
  47. package/dist/filter.js +14 -14
  48. package/dist/services/Repository.d.ts +44 -104
  49. package/dist/services/Repository.d.ts.map +1 -1
  50. package/dist/services/Repository.js +6 -30
  51. package/dist/services/RepositoryBase.d.ts +11 -21
  52. package/dist/services/RepositoryBase.d.ts.map +1 -1
  53. package/dist/services/RepositoryBase.js +41 -37
  54. package/dist/services/Store/Cosmos/query.d.ts +61 -0
  55. package/dist/services/Store/Cosmos/query.d.ts.map +1 -0
  56. package/dist/services/Store/Cosmos/query.js +302 -0
  57. package/dist/services/Store/Cosmos.d.ts +3 -47
  58. package/dist/services/Store/Cosmos.d.ts.map +1 -1
  59. package/dist/services/Store/Cosmos.js +39 -193
  60. package/dist/services/Store/Memory.d.ts +2 -7
  61. package/dist/services/Store/Memory.d.ts.map +1 -1
  62. package/dist/services/Store/Memory.js +17 -15
  63. package/dist/services/Store/Redis.d.ts.map +1 -1
  64. package/dist/services/Store/Redis.js +2 -2
  65. package/dist/services/Store/codeFilter.d.ts +5 -0
  66. package/dist/services/Store/codeFilter.d.ts.map +1 -0
  67. package/dist/services/Store/codeFilter.js +140 -0
  68. package/dist/services/Store/filterApi/proxy.d.ts +41 -0
  69. package/dist/services/Store/filterApi/proxy.d.ts.map +1 -0
  70. package/dist/services/Store/filterApi/proxy.js +41 -0
  71. package/dist/services/Store/filterApi/query.d.ts +64 -0
  72. package/dist/services/Store/filterApi/query.d.ts.map +1 -0
  73. package/dist/services/Store/filterApi/query.js +134 -0
  74. package/dist/services/Store/filterApi.test.d.ts.map +1 -0
  75. package/dist/services/Store/service.d.ts +12 -5
  76. package/dist/services/Store/service.d.ts.map +1 -1
  77. package/dist/services/Store/service.js +1 -1
  78. package/dist/services/Store/utils.d.ts +7 -1
  79. package/dist/services/Store/utils.d.ts.map +1 -1
  80. package/dist/services/Store/utils.js +118 -115
  81. package/dist/services/simpledb/cosmosdb.d.ts +2 -2
  82. package/dist/services/simpledb/diskdb.d.ts +1 -1
  83. package/dist/services/simpledb/redisdb.d.ts +2 -2
  84. package/dist/services/simpledb/simpledb.d.ts +2 -2
  85. package/package.json +44 -4
@@ -0,0 +1,109 @@
1
+ import type { FieldValues } from "../../../filter/types.js"
2
+ import type { FieldPath } from "../../../filter/types/path/eager.js"
3
+
4
+ type NullOrUndefined<T, Fallback> = null extends T ? null : undefined extends T ? null : Fallback
5
+
6
+ export type InOps =
7
+ | "in"
8
+ | "notIn"
9
+
10
+ export type OtherOps =
11
+ | "endsWith"
12
+ | "startsWith"
13
+ | "notEndsWith"
14
+ | "notStartsWith"
15
+ | "contains"
16
+ | "notContains"
17
+ | "includes"
18
+ | "notIncludes"
19
+ | "eq"
20
+ | "neq"
21
+ | "gt"
22
+ | "gte"
23
+ | "lt"
24
+ | "lte"
25
+
26
+ // TODO: includes | notIncludes
27
+ export type Ops = OtherOps | InOps
28
+ type F<T extends FieldValues> = {
29
+ path: FieldPath<T>
30
+ op: OtherOps
31
+ value: string
32
+ }
33
+
34
+ type FIn<T extends FieldValues> = {
35
+ op: InOps
36
+ path: FieldPath<T>
37
+ value: readonly string[]
38
+ }
39
+
40
+ type G<T extends FieldValues, Val> = {
41
+ (value: Val): F<T>
42
+ startsWith: (value: string) => F<T>
43
+ notStartsWith: (value: string) => F<T>
44
+ endsWith: (value: string) => F<T>
45
+ notEndsWith: (value: string) => F<T>
46
+ contains: (value: string) => F<T>
47
+ notContains: (value: string) => F<T>
48
+ includes: (value: string) => F<T>
49
+ notIncludes: (value: string) => F<T>
50
+ in: (...value: readonly Val[]) => FIn<T>
51
+ notIn: (...value: readonly Val[]) => FIn<T>
52
+ eq: (value: Val) => F<T>
53
+ neq: (value: Val) => F<T>
54
+ gt: (value: Val) => F<T>
55
+ gte: (value: Val) => F<T>
56
+ lt: (value: Val) => F<T>
57
+ lte: (value: Val) => F<T>
58
+ }
59
+
60
+ export type Filter<T extends FieldValues, Ext = never> = {
61
+ [K in keyof T]-?: [T[K]] extends [Record<any, any> | undefined | null]
62
+ ? Filter<T[K], NullOrUndefined<T[K], Ext>> & G<T, T[K] | Ext>
63
+ : [T[K]] extends [Record<any, any>] ? Filter<T[K], NullOrUndefined<T[K], Ext>> & G<T, T[K] | Ext>
64
+ : G<T, T[K] | Ext>
65
+ }
66
+ const ops: Ops[] = [
67
+ "contains",
68
+ "startsWith",
69
+ "endsWith",
70
+ "notContains",
71
+ "notStartsWith",
72
+ "notEndsWith",
73
+ "in",
74
+ "notIn",
75
+ "eq",
76
+ "neq",
77
+ "gte",
78
+ "gt",
79
+ "lt",
80
+ "lte"
81
+ ]
82
+ export const makeProxy = (parentProp?: string): any =>
83
+ new Proxy(
84
+ Object.assign(() => {}, {
85
+ _proxies: {} as Record<string, any>
86
+ }),
87
+ {
88
+ apply(_target, _thisArg, argArray) {
89
+ return ({ op: "eq", value: argArray[0], path: parentProp })
90
+ },
91
+ get(target, prop) {
92
+ if (typeof prop !== "string") return undefined
93
+ if (target._proxies[prop]) {
94
+ return target._proxies[prop]
95
+ }
96
+
97
+ if (ops.includes(prop as any)) {
98
+ return (value: any) => ({ op: prop, path: parentProp, value })
99
+ }
100
+ let fullProp = prop
101
+ if (parentProp) {
102
+ fullProp = `${parentProp}.${prop}`
103
+ }
104
+ const p = makeProxy(fullProp)
105
+ target._proxies[prop] = p
106
+ return p
107
+ }
108
+ }
109
+ )
@@ -0,0 +1,256 @@
1
+ import type { FieldValues } from "../../../filter/types.js"
2
+ import type { FieldPath, FieldPathValue } from "../../../filter/types/path/eager.js"
3
+ import type { Filter, Ops } from "./proxy.js"
4
+ import { makeProxy } from "./proxy.js"
5
+
6
+ export type FilterScopes = {
7
+ t: "or-scope"
8
+ result: FilterResult[]
9
+ } | {
10
+ t: "and-scope"
11
+ result: FilterResult[]
12
+ } | {
13
+ t: "where-scope"
14
+ result: FilterResult[]
15
+ }
16
+
17
+ export type FilterR = {
18
+ op: Ops
19
+
20
+ path: string
21
+ value: string // ToDO: Value[]
22
+ }
23
+
24
+ export type FilterResult =
25
+ | {
26
+ t: "where"
27
+ } & FilterR
28
+ | {
29
+ t: "or"
30
+ } & FilterR
31
+ | {
32
+ t: "and"
33
+ } & FilterR
34
+ | FilterScopes
35
+
36
+ export const print = (state: readonly FilterResult[]) => {
37
+ let s = ""
38
+ let l = 0
39
+ const printN = (n: number) => {
40
+ return n === 0 ? "" : ReadonlyArray.range(1, n).map(() => " ").join("")
41
+ }
42
+ for (const e of state) {
43
+ switch (e.t) {
44
+ case "where":
45
+ s += `${e.path} ${e.op} ${e.value}`
46
+ break
47
+ case "or":
48
+ s += ` OR ${e.path} ${e.op} ${e.value}`
49
+ break
50
+ case "and":
51
+ s += ` AND ${e.path} ${e.op} ${e.value}`
52
+ break
53
+ case "or-scope": {
54
+ ;++l
55
+ s += ` OR (\n${printN(l + 1)}${print(e.result)}\n${printN(l)})`
56
+ ;--l
57
+ break
58
+ }
59
+ case "and-scope": {
60
+ ;++l
61
+ s += ` AND (\n${printN(l + 1)}${print(e.result)}\n${printN(l)})`
62
+ ;--l
63
+
64
+ break
65
+ }
66
+ case "where-scope": {
67
+ // ;++l
68
+ s += `(\n${printN(l + 1)}${print(e.result)}\n)`
69
+ // ;--l
70
+ break
71
+ }
72
+ }
73
+ }
74
+ return s
75
+ }
76
+
77
+ export const makeFilter = <TFieldValues extends FieldValues>() => {
78
+ // const recursive = {}
79
+ // const and = Object.assign(() => {}, recursive)
80
+ // const or = Object.assign(() => {}, recursive)
81
+ // const where = Object.assign(() => {}, recursive)
82
+ // recursive.and = and
83
+ // recursive.or = or
84
+ // recursive.where = where
85
+ const state: FilterResult[] = []
86
+ let current = state
87
+ const where = (...args: any[]) => {
88
+ if (typeof args[0] === "function") {
89
+ const mine: any[] = []
90
+ const previous = current
91
+ current = mine
92
+ const r = args[0](all)
93
+ current = previous
94
+ current.push({ t: "where-scope", result: mine })
95
+ return r
96
+ }
97
+ if (args.length === 1) {
98
+ current.push({ t: "where", ...args[0] })
99
+ return all
100
+ }
101
+ current.push({
102
+ t: "where",
103
+ op: args.length === 2 ? "eq" : args[1],
104
+ path: args[0],
105
+ value: args[args.length - 1]
106
+ })
107
+ return all
108
+ }
109
+ const and = (...args: any[]) => {
110
+ if (typeof args[0] === "function") {
111
+ const mine: any[] = []
112
+ const previous = current
113
+ current = mine
114
+ const r = args[0](all)
115
+ current = previous
116
+ current.push({ t: "and-scope", result: mine })
117
+ return r
118
+ }
119
+ if (args.length === 1) {
120
+ current.push({ t: "and", ...args[0] })
121
+ return all
122
+ }
123
+ current.push({
124
+ t: "and",
125
+ op: args.length === 2 ? "eq" : args[1],
126
+ path: args[0],
127
+ value: args[args.length - 1]
128
+ })
129
+ return all
130
+ }
131
+ const or = (...args: any[]) => {
132
+ if (typeof args[0] === "function") {
133
+ const mine: any[] = []
134
+ const previous = current
135
+ current = mine
136
+ const r = args[0](all)
137
+ current = previous
138
+ current.push({ t: "or-scope", result: mine })
139
+ return r
140
+ }
141
+ if (args.length === 1) {
142
+ current.push({ t: "or", ...args[0] })
143
+ return all
144
+ }
145
+ current.push({ t: "or", op: args.length === 2 ? "eq" : args[1], path: args[0], value: args[args.length - 1] })
146
+ return all
147
+ }
148
+ const fields = makeProxy() as Filter<TFieldValues>
149
+ const all = Object.assign(where, {
150
+ type: "new-kid",
151
+ and,
152
+ or,
153
+ build() {
154
+ return state
155
+ }
156
+ })
157
+ return { all, fields }
158
+ }
159
+
160
+ export const QueryBuilder = {
161
+ make: <TFieldValues extends FieldValues>(): (
162
+ fn: (f: FilterTest<TFieldValues>, fields: Filter<TFieldValues>) => QueryBuilder<TFieldValues>
163
+ ) => QueryBuilder<TFieldValues> => {
164
+ type F = ReturnType<typeof makeFilter<TFieldValues>>
165
+ return (
166
+ f: (
167
+ n: F["all"],
168
+ f: F["fields"]
169
+ ) => QueryBuilder<TFieldValues>
170
+ ) => {
171
+ const fil = makeFilter<TFieldValues>()
172
+ return f(fil.all, fil.fields)
173
+ }
174
+ }
175
+ }
176
+
177
+ export type Initial<TFieldValues extends FieldValues> = {
178
+ where: FilterTest<TFieldValues>
179
+ build(): FilterResult[]
180
+ }
181
+
182
+ export type Filts<TFieldValues extends FieldValues> = {
183
+ <
184
+ TFieldName extends FieldPath<TFieldValues>,
185
+ V extends FieldPathValue<TFieldValues, TFieldName>
186
+ >(
187
+ path: TFieldName,
188
+ value: V
189
+ ): QueryBuilder<TFieldValues>
190
+ <
191
+ TFieldName extends FieldPath<TFieldValues>,
192
+ V extends FieldPathValue<TFieldValues, TFieldName>
193
+ >(
194
+ path: TFieldName,
195
+ op: "neq",
196
+ value: V
197
+ ): QueryBuilder<TFieldValues>
198
+ <
199
+ TFieldName extends FieldPath<TFieldValues>,
200
+ V extends FieldPathValue<TFieldValues, TFieldName>
201
+ >(
202
+ path: TFieldName,
203
+ op: "gt" | "gte" | "lt" | "lte",
204
+ value: V // only numbers?
205
+ ): QueryBuilder<TFieldValues>
206
+ <
207
+ TFieldName extends FieldPath<TFieldValues>,
208
+ V extends FieldPathValue<TFieldValues, TFieldName>
209
+ >(
210
+ path: TFieldName,
211
+ op: "startsWith" | "endsWith" | "contains" | "notContains" | "notStartsWith" | "notEndsWith",
212
+ value: V // only strings?
213
+ ): QueryBuilder<TFieldValues>
214
+ <
215
+ TFieldName extends FieldPath<TFieldValues>,
216
+ V extends FieldPathValue<TFieldValues, TFieldName>
217
+ >(
218
+ path: TFieldName,
219
+ op: "in" | "notIn",
220
+ value: readonly V[]
221
+ ): QueryBuilder<TFieldValues>
222
+ }
223
+
224
+ // type Filter<TFieldValues extends FieldValues> = {
225
+ // (
226
+ // fb: (f: Filts<TFieldValues>) => QueryBuilder<TFieldValues>
227
+ // ): QueryBuilder<TFieldValues>
228
+ // } & Filts<TFieldValues>
229
+
230
+ export type FilterTest<TFieldValues extends FieldValues> = {
231
+ (
232
+ fb: (f: Filts<TFieldValues> & Initial<TFieldValues>) => QueryBuilder<TFieldValues>
233
+ ): QueryBuilder<TFieldValues>
234
+ <
235
+ TFieldName extends FieldPath<TFieldValues>,
236
+ V extends FieldPathValue<TFieldValues, TFieldName>
237
+ >(f: {
238
+ path: TFieldName
239
+ op: Ops
240
+ value: V
241
+ }): QueryBuilder<TFieldValues>
242
+ } & Filts<TFieldValues>
243
+ // const not = <A extends string>(s: A) => `!${s}`
244
+
245
+ // type FilterGroup<TFieldValues extends FieldValues> = (
246
+ // fb: (f: QueryBuilder<TFieldValues>) => QueryBuilder<TFieldValues>
247
+ // ) => QueryBuilder<TFieldValues>
248
+
249
+ export interface QueryBuilder<TFieldValues extends FieldValues> {
250
+ type: "new-kid"
251
+ // TODO: as overloads?
252
+ and: FilterTest<TFieldValues>
253
+ or: FilterTest<TFieldValues>
254
+ build(): FilterResult[]
255
+ // where: <Filters extends Record<TFieldName extends FieldPath<TFieldValues>, V extends Value< FieldPathValue<TFieldValues, TFieldName>>>(filter: Filters) => QueryBuilder<TFieldValues>
256
+ }
@@ -0,0 +1,263 @@
1
+ import { codeFilter3 } from "./codeFilter.js"
2
+ import { buildWhereCosmosQuery3 } from "./Cosmos/query.js"
3
+ import { print, QueryBuilder } from "./filterApi/query.js"
4
+
5
+ const MyEntity = {
6
+ query: QueryBuilder
7
+ .make<MyEntity>()
8
+ }
9
+
10
+ const removeWhitespace = (a: string) => a.replaceAll(/\s+/g, " ").replaceAll(/^\s+/g, "").replaceAll(/\s+$/g, "")
11
+
12
+ interface MyEntity {
13
+ id: string
14
+ name: string
15
+ bio: string | null
16
+ isActive: boolean
17
+ state: { _tag: "a"; val: number } | { _tag: "b"; val: string } | { _tag: "c"; noVal: boolean }
18
+ roles: readonly string[]
19
+ tag: "a" | "b" | "c"
20
+ age?: number
21
+ something: {
22
+ id: number
23
+ name: string
24
+ } | null
25
+ }
26
+
27
+ describe("works", () => {
28
+ const f = MyEntity.query((where, f) =>
29
+ where(f.something.id.eq(1))
30
+ .and(() =>
31
+ where(f.something.name.startsWith("a")) // or would we do "like", "a%"?
32
+ .or(f.tag.in("a", "b"))
33
+ .or(() =>
34
+ where(f.name.neq("Alfredo"))
35
+ .and(f.tag("c"))
36
+ )
37
+ )
38
+ // .and(f.state._tag("a"))
39
+ .and("state._tag", "a")
40
+ .and(f.isActive(true))
41
+ .and(f.age.gte(12))
42
+ )
43
+
44
+ const s = f.build()
45
+ console.log(JSON.stringify(s, undefined, 2))
46
+ it("print", () =>
47
+ expect(print(s)).toBe(
48
+ `something.id eq 1 AND (
49
+ something.name startsWith a OR tag in a OR (
50
+ name neq Alfredo AND tag eq c
51
+ )
52
+ ) AND isActive eq true AND age gte 12`
53
+ ))
54
+ const values: MyEntity[] = [{
55
+ id: "1",
56
+ name: "Patrick",
57
+ bio: "something",
58
+ age: 13,
59
+ isActive: true,
60
+ state: { _tag: "a", val: 1 },
61
+ roles: ["admin"],
62
+ tag: "c",
63
+ something: { id: 1, name: "abc" }
64
+ }, {
65
+ id: "1",
66
+ name: "Patrick",
67
+ bio: "something",
68
+ age: 11,
69
+ isActive: true,
70
+ state: { _tag: "b", val: "2" },
71
+ roles: ["admin"],
72
+ tag: "a",
73
+ something: { id: 1, name: "abc" }
74
+ }]
75
+
76
+ it("print codef", () => {
77
+ expect(values.filter(codeFilter3(s))).toStrictEqual([values[0]])
78
+ })
79
+
80
+ it("codeFilter1", () => {
81
+ expect(values.filter(codeFilter3(s))).toStrictEqual([values[0]])
82
+ })
83
+ it("codeFilter2", () => {
84
+ expect(values.filter(
85
+ codeFilter3(
86
+ MyEntity
87
+ .query((where, f) =>
88
+ where(f.something.id.eq(0))
89
+ .and(() =>
90
+ where(f.something.name.startsWith("a"))
91
+ .or(f.tag.in("a", "b"))
92
+ )
93
+ )
94
+ .build()
95
+ )
96
+ ))
97
+ .toStrictEqual([])
98
+
99
+ expect(values.filter(
100
+ codeFilter3(
101
+ MyEntity
102
+ .query((where, f) => where(f.something.id.eq(0)))
103
+ .build()
104
+ )
105
+ ))
106
+ .toStrictEqual([])
107
+
108
+ expect(values.filter(
109
+ codeFilter3(
110
+ MyEntity
111
+ .query((where, f) => where(f.something.id.eq(1)))
112
+ .build()
113
+ )
114
+ ))
115
+ .toStrictEqual(values)
116
+
117
+ expect(values.filter(
118
+ codeFilter3(
119
+ MyEntity
120
+ .query((where, f) => where(f.age.eq(11)))
121
+ .build()
122
+ )
123
+ ))
124
+ .toStrictEqual([values[1]])
125
+ })
126
+
127
+ it("cosmos", () => {
128
+ const r = buildWhereCosmosQuery3(s, "MyEntity", "marker")
129
+ expect(removeWhitespace(r.query)).toBe(removeWhitespace(`
130
+ SELECT *
131
+ FROM MyEntity f
132
+ WHERE f.id != @id AND f.something.id = @v0 AND (
133
+ STARTSWITH(f.something.name, @v1, true) OR ARRAY_CONTAINS(@v2, f.tag) OR (
134
+ LOWER(f.name) <> LOWER(@v3) AND LOWER(f.tag) = LOWER(@v4)
135
+ )
136
+ ) AND f.isActive = @v5 AND f.age >= @v6`))
137
+ expect(r.parameters).toEqual([
138
+ {
139
+ "name": "@id",
140
+ "value": "marker"
141
+ },
142
+ {
143
+ "name": "@v0",
144
+ "value": 1
145
+ },
146
+ {
147
+ "name": "@v1",
148
+ "value": "a"
149
+ },
150
+ {
151
+ "name": "@v2",
152
+ "value": "a"
153
+ },
154
+ {
155
+ "name": "@v3",
156
+ "value": "Alfredo"
157
+ },
158
+ {
159
+ "name": "@v4",
160
+ "value": "c"
161
+ },
162
+ {
163
+ "name": "@v5",
164
+ "value": true
165
+ },
166
+ {
167
+ "name": "@v6",
168
+ "value": 12
169
+ }
170
+ ])
171
+ })
172
+ })
173
+
174
+ // ref https://stackoverflow.com/questions/1241142/sql-logic-operator-precedence-and-and-or
175
+ describe("root-or", () => {
176
+ const f = MyEntity.query((where, f) =>
177
+ where(() =>
178
+ where(f.something.id(1))
179
+ .and(() =>
180
+ where(f.something.name.startsWith("a")) // or would we do "like", "a%"?
181
+ .or(f.tag.in("a", "b"))
182
+ .or(() =>
183
+ where(f.name.neq("Alfredo"))
184
+ .and(f.tag("c"))
185
+ )
186
+ )
187
+ .and(f.bio.contains("abc"))
188
+ .and(f.isActive(true))
189
+ .and(f.age.gte(12))
190
+ )
191
+ .or(f.name.startsWith("C"))
192
+ )
193
+
194
+ const s = f.build()
195
+ console.log(JSON.stringify(s, undefined, 2))
196
+
197
+ it("print", () => {
198
+ expect(print(s)).toBe(
199
+ `(
200
+ something.id eq 1 AND (
201
+ something.name startsWith a OR tag in a OR (
202
+ name neq Alfredo AND tag eq c
203
+ )
204
+ ) AND bio contains abc AND isActive eq true AND age gte 12
205
+ ) OR name startsWith C`
206
+ )
207
+ })
208
+ it("cosmos", () => {
209
+ const r = buildWhereCosmosQuery3(s, "MyEntity", "marker")
210
+ expect(removeWhitespace(r.query)).toBe(removeWhitespace(` SELECT *
211
+ FROM MyEntity f
212
+
213
+ WHERE f.id != @id AND (
214
+ f.something.id = @v0 AND (
215
+ STARTSWITH(f.something.name, @v1, true) OR ARRAY_CONTAINS(@v2, f.tag) OR (
216
+ LOWER(f.name) <> LOWER(@v3) AND LOWER(f.tag) = LOWER(@v4)
217
+ )
218
+ ) AND CONTAINS(f.bio, @v5, true) AND f.isActive = @v6 AND f.age >= @v7
219
+ ) OR STARTSWITH(f.name, @v8, true) `))
220
+ expect(r.parameters).toEqual([
221
+ {
222
+ "name": "@id",
223
+ "value": "marker"
224
+ },
225
+ {
226
+ "name": "@v0",
227
+ "value": 1
228
+ },
229
+ {
230
+ "name": "@v1",
231
+ "value": "a"
232
+ },
233
+ {
234
+ "name": "@v2",
235
+ "value": "a"
236
+ },
237
+ {
238
+ "name": "@v3",
239
+ "value": "Alfredo"
240
+ },
241
+ {
242
+ "name": "@v4",
243
+ "value": "c"
244
+ },
245
+ {
246
+ "name": "@v5",
247
+ "value": "abc"
248
+ },
249
+ {
250
+ "name": "@v6",
251
+ "value": true
252
+ },
253
+ {
254
+ "name": "@v7",
255
+ "value": 12
256
+ },
257
+ {
258
+ "name": "@v8",
259
+ "value": "C"
260
+ }
261
+ ])
262
+ })
263
+ })
@@ -4,6 +4,8 @@ import type { Parser, ParserEnv } from "@effect-app/schema/custom/Parser"
4
4
 
5
5
  import type { These } from "@effect-app/prelude/schema"
6
6
  import type { OptimisticConcurrencyException } from "../../errors.js"
7
+ import type { FieldValues } from "../../filter/types.js"
8
+ import type { QueryBuilder } from "./filterApi/query.js"
7
9
 
8
10
  export type StoreConfig<E> = {
9
11
  uniqueKeys?: UniqueKey[]
@@ -50,10 +52,11 @@ export type JoinFindFilter = {
50
52
  valueKey: string /* value paths of E[keys][valueKey] */
51
53
  value: any /* value path[valueKey] of E */
52
54
  }
53
- export type Filter<E> =
55
+ export type Filter<E extends FieldValues> =
54
56
  | JoinFindFilter
55
57
  | StoreWhereFilter
56
58
  | LegacyFilter<E>
59
+ | QueryBuilder<E>
57
60
 
58
61
  export type FilterJoinSelect = {
59
62
  type: "filter_join_select"
@@ -62,12 +65,21 @@ export type FilterJoinSelect = {
62
65
  value: any /* value path[valueKey] of E */
63
66
  }
64
67
 
68
+ export interface FilterArgs<PM extends PersistenceModelType<string>, U extends keyof PM = never> {
69
+ filter?: Filter<PM>
70
+ select?: readonly U[]
71
+ limit?: number
72
+ skip?: number
73
+ }
74
+
75
+ export type FilterFunc<PM extends PersistenceModelType<string>> = <U extends keyof PM = never>(
76
+ args: FilterArgs<PM, U>
77
+ ) => Effect<never, never, (U extends undefined ? PM : Pick<PM, U>)[]>
78
+
65
79
  export interface Store<PM extends PersistenceModelType<Id>, Id extends string> {
66
80
  all: Effect<never, never, PM[]>
67
- filter: (
68
- filter: Filter<PM>,
69
- cursor?: { limit?: number; skip?: number }
70
- ) => Effect<never, never, PM[]>
81
+ filter: FilterFunc<PM>
82
+ /** @deprecated */
71
83
  filterJoinSelect: <T extends object>(
72
84
  filter: FilterJoinSelect
73
85
  ) => Effect<never, never, (T & { _rootId: string })[]>