@effect-app/infra 0.118.0 → 0.120.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 +20 -0
- package/_cjs/services/Emailer/Sendgrid.cjs +1 -1
- package/_cjs/services/Emailer/Sendgrid.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/Disk.cjs +1 -1
- package/_cjs/services/Store/Disk.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 +2 -2
- 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/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/index.cjs +1 -1
- package/_cjs/services/Store/index.cjs.map +1 -1
- 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/services/Emailer/Sendgrid.ts +0 -1
- 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 +5 -5
- package/_src/services/Store/Cosmos.ts +39 -245
- package/_src/services/Store/Disk.ts +0 -1
- package/_src/services/Store/Memory.ts +21 -18
- package/_src/services/Store/Redis.ts +3 -4
- 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/index.ts +0 -1
- package/_src/services/Store/service.ts +17 -5
- package/_src/services/Store/utils.ts +10 -7
- package/dist/services/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/services/Emailer/Sendgrid.js +2 -2
- 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/Disk.d.ts.map +1 -1
- package/dist/services/Store/Disk.js +2 -2
- 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 +3 -3
- 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/index.d.ts.map +1 -1
- package/dist/services/Store/index.js +2 -2
- 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/package.json +48 -8
|
@@ -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 })[]>
|
|
@@ -5,6 +5,7 @@ import { OptimisticConcurrencyException } from "../../errors.js"
|
|
|
5
5
|
import type { Filter, FilterJoinSelect, PersistenceModelType, SupportedValues2 } from "./service.js"
|
|
6
6
|
|
|
7
7
|
import objectHash from "object-hash"
|
|
8
|
+
import { codeFilter3_ } from "./codeFilter.js"
|
|
8
9
|
|
|
9
10
|
export const makeETag = <E extends PersistenceModelType<Id>, Id extends string>(
|
|
10
11
|
{ _etag, ...e }: E
|
|
@@ -38,7 +39,9 @@ export const makeUpdateETag =
|
|
|
38
39
|
|
|
39
40
|
export function codeFilter<E extends { id: string }, NE extends E>(filter: Filter<NE>) {
|
|
40
41
|
return (x: E) =>
|
|
41
|
-
filter.type === "
|
|
42
|
+
filter.type === "new-kid"
|
|
43
|
+
? codeFilter3_(filter.build(), x) ? Option(x as unknown as NE) : Option.none
|
|
44
|
+
: filter.type === "startsWith"
|
|
42
45
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
46
|
? lowercaseIfString((x as any)[filter.by]).startsWith(filter.value.toLowerCase())
|
|
44
47
|
? Option(x as unknown as NE)
|
|
@@ -184,38 +187,38 @@ export function codeFilterJoinSelect<E extends { id: string }, NE>(
|
|
|
184
187
|
.flatMap((_) => _)
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
function lowercaseIfString<T>(val: T) {
|
|
190
|
+
export function lowercaseIfString<T>(val: T) {
|
|
188
191
|
if (typeof val === "string") {
|
|
189
192
|
return val.toLowerCase()
|
|
190
193
|
}
|
|
191
194
|
return val
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
function compareCaseInsensitive(valA: unknown, valB: unknown) {
|
|
197
|
+
export function compareCaseInsensitive(valA: unknown, valB: unknown) {
|
|
195
198
|
return typeof valB === "string" && typeof valA === "string"
|
|
196
199
|
? valA.toLowerCase() === valB.toLowerCase()
|
|
197
200
|
: valA === valB
|
|
198
201
|
}
|
|
199
202
|
|
|
200
|
-
function ltCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
203
|
+
export function ltCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
201
204
|
return typeof valB === "string" && typeof valA === "string"
|
|
202
205
|
? valA.toLowerCase() < valB.toLowerCase()
|
|
203
206
|
: valA < valB
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
function lteCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
209
|
+
export function lteCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
207
210
|
return typeof valB === "string" && typeof valA === "string"
|
|
208
211
|
? valA.toLowerCase() <= valB.toLowerCase()
|
|
209
212
|
: valA <= valB
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
function gtCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
215
|
+
export function gtCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
213
216
|
return typeof valB === "string" && typeof valA === "string"
|
|
214
217
|
? valA.toLowerCase() > valB.toLowerCase()
|
|
215
218
|
: valA > valB
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
function gteCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
221
|
+
export function gteCaseInsensitive(valA: SupportedValues2, valB: SupportedValues2) {
|
|
219
222
|
return typeof valB === "string" && typeof valA === "string"
|
|
220
223
|
? valA.toLowerCase() >= valB.toLowerCase()
|
|
221
224
|
: valA >= valB
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sendgrid.d.ts","sourceRoot":"","sources":["../../../_src/services/Emailer/Sendgrid.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAA;AAG3E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAwB,cAAc,EAAE,MAAM,cAAc,CAAA;;;;;AAoDlF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"Sendgrid.d.ts","sourceRoot":"","sources":["../../../_src/services/Emailer/Sendgrid.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAA;AAG3E,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,KAAK,EAAE,QAAQ,EAAwB,cAAc,EAAE,MAAM,cAAc,CAAA;;;;;AAoDlF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,0FAI1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,OAAO,SAUpC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAMnB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,SAAS,WAO1C"}
|
|
@@ -42,7 +42,7 @@ const makeLiveSendgrid = ({ apiKey, defaultFrom, defaultReplyTo, realMail, subje
|
|
|
42
42
|
* @tsplus static Emailer.Ops LiveSendgrid
|
|
43
43
|
*/
|
|
44
44
|
export function LiveSendgrid(config) {
|
|
45
|
-
return tsplus_module_3.effect(Emailer)(tsplus_module_2.flatMap(
|
|
45
|
+
return tsplus_module_3.effect(Emailer)(tsplus_module_2.flatMap(config, makeLiveSendgrid));
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* @hidden
|
|
@@ -98,4 +98,4 @@ function renderMailData(md) {
|
|
|
98
98
|
}
|
|
99
99
|
return md.email;
|
|
100
100
|
}
|
|
101
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
101
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU2VuZGdyaWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9fc3JjL3NlcnZpY2VzL0VtYWlsZXIvU2VuZGdyaWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQ0EsT0FBTyxNQUFNLE1BQU0sZ0JBQWdCLENBQUE7QUFDbkMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQTtBQUM5QixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sY0FBYyxDQUFBO0FBR3RDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsY0FBYyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQWtCLEVBQUUsRUFBRSxDQUM1RyxxQkFBWSxHQUFHLEVBQUU7SUFDZixNQUFNLENBQUMsU0FBUyx1QkFBQyxNQUFNLEVBQU8sQ0FBQTtJQUU5QixPQUFPO1FBQ0wsUUFBUSxDQUFDLElBQTBCO1lBQ2pDLE9BQU8sb0JBQVcsUUFBUSxDQUFDLEVBQUMsQ0FBQztnQkFDM0IsTUFBTSxHQUFHLEdBQWE7b0JBQ3BCLEdBQUcsSUFBSTtvQkFDUCxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksSUFBSSxXQUFXO29CQUM5QixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDO2lCQUNsRSxDQUFBO2dCQUNELE1BQU0sTUFBTSxHQUFHLGFBQWEsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFBO2dCQUV2QyxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUE7Z0JBQ2hDLE1BQU0sV0FBVyxHQUFHLEVBQUUsR0FBRyxZQUFZLEVBQUUsT0FBTyxFQUFFLEdBQUcsYUFBYSxHQUFHLFlBQVksQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFBO2dCQUMzRixLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsNkJB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
|