@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.
- package/CHANGELOG.md +12 -0
- package/_cjs/filter.cjs +13 -13
- package/_cjs/filter.cjs.map +1 -1
- package/_cjs/services/Repository.cjs +4 -38
- package/_cjs/services/Repository.cjs.map +1 -1
- package/_cjs/services/RepositoryBase.cjs +39 -36
- package/_cjs/services/RepositoryBase.cjs.map +1 -1
- package/_cjs/services/Store/Cosmos/query.cjs +291 -0
- package/_cjs/services/Store/Cosmos/query.cjs.map +1 -0
- package/_cjs/services/Store/Cosmos.cjs +38 -183
- package/_cjs/services/Store/Cosmos.cjs.map +1 -1
- package/_cjs/services/Store/Cosmos.test.cjs +7 -7
- package/_cjs/services/Store/Cosmos.test.cjs.map +1 -1
- package/_cjs/services/Store/Memory.cjs +20 -17
- package/_cjs/services/Store/Memory.cjs.map +1 -1
- package/_cjs/services/Store/Redis.cjs +1 -1
- package/_cjs/services/Store/Redis.cjs.map +1 -1
- package/_cjs/services/Store/codeFilter.cjs +151 -0
- package/_cjs/services/Store/codeFilter.cjs.map +1 -0
- package/_cjs/services/Store/codeFilter.test.cjs +8 -8
- package/_cjs/services/Store/codeFilter.test.cjs.map +1 -1
- package/_cjs/services/Store/filterApi/proxy.cjs +40 -0
- package/_cjs/services/Store/filterApi/proxy.cjs.map +1 -0
- package/_cjs/services/Store/filterApi/query.cjs +173 -0
- package/_cjs/services/Store/filterApi/query.cjs.map +1 -0
- package/_cjs/services/Store/filterApi.test.cjs +164 -0
- package/_cjs/services/Store/filterApi.test.cjs.map +1 -0
- package/_cjs/services/Store/service.cjs.map +1 -1
- package/_cjs/services/Store/utils.cjs +8 -1
- package/_cjs/services/Store/utils.cjs.map +1 -1
- package/_src/filter.ts +13 -13
- package/_src/services/Repository.ts +124 -70
- package/_src/services/RepositoryBase.ts +26 -12
- package/_src/services/Store/Cosmos/query.ts +390 -0
- package/_src/services/Store/Cosmos.test.ts +6 -6
- package/_src/services/Store/Cosmos.ts +39 -245
- package/_src/services/Store/Memory.ts +21 -18
- package/_src/services/Store/Redis.ts +2 -3
- package/_src/services/Store/codeFilter.test.ts +8 -8
- package/_src/services/Store/codeFilter.ts +150 -0
- package/_src/services/Store/filterApi/proxy.ts +109 -0
- package/_src/services/Store/filterApi/query.ts +256 -0
- package/_src/services/Store/filterApi.test.ts +263 -0
- package/_src/services/Store/service.ts +17 -5
- package/_src/services/Store/utils.ts +10 -7
- package/dist/filter.d.ts +13 -13
- package/dist/filter.js +14 -14
- package/dist/services/Repository.d.ts +44 -104
- package/dist/services/Repository.d.ts.map +1 -1
- package/dist/services/Repository.js +6 -30
- package/dist/services/RepositoryBase.d.ts +11 -21
- package/dist/services/RepositoryBase.d.ts.map +1 -1
- package/dist/services/RepositoryBase.js +41 -37
- package/dist/services/Store/Cosmos/query.d.ts +61 -0
- package/dist/services/Store/Cosmos/query.d.ts.map +1 -0
- package/dist/services/Store/Cosmos/query.js +302 -0
- package/dist/services/Store/Cosmos.d.ts +3 -47
- package/dist/services/Store/Cosmos.d.ts.map +1 -1
- package/dist/services/Store/Cosmos.js +39 -193
- package/dist/services/Store/Memory.d.ts +2 -7
- package/dist/services/Store/Memory.d.ts.map +1 -1
- package/dist/services/Store/Memory.js +17 -15
- package/dist/services/Store/Redis.d.ts.map +1 -1
- package/dist/services/Store/Redis.js +2 -2
- package/dist/services/Store/codeFilter.d.ts +5 -0
- package/dist/services/Store/codeFilter.d.ts.map +1 -0
- package/dist/services/Store/codeFilter.js +140 -0
- package/dist/services/Store/filterApi/proxy.d.ts +41 -0
- package/dist/services/Store/filterApi/proxy.d.ts.map +1 -0
- package/dist/services/Store/filterApi/proxy.js +41 -0
- package/dist/services/Store/filterApi/query.d.ts +64 -0
- package/dist/services/Store/filterApi/query.d.ts.map +1 -0
- package/dist/services/Store/filterApi/query.js +134 -0
- package/dist/services/Store/filterApi.test.d.ts.map +1 -0
- package/dist/services/Store/service.d.ts +12 -5
- package/dist/services/Store/service.d.ts.map +1 -1
- package/dist/services/Store/service.js +1 -1
- package/dist/services/Store/utils.d.ts +7 -1
- package/dist/services/Store/utils.d.ts.map +1 -1
- package/dist/services/Store/utils.js +118 -115
- package/dist/services/simpledb/cosmosdb.d.ts +2 -2
- package/dist/services/simpledb/diskdb.d.ts +1 -1
- package/dist/services/simpledb/redisdb.d.ts +2 -2
- package/dist/services/simpledb/simpledb.d.ts +2 -2
- 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
|
-
|
|
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 })[]>
|