@apisr/drizzle-model 2.0.0 → 2.0.2
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 +26 -0
- package/README.md +471 -0
- package/TODO.md +5 -5
- package/package.json +4 -3
- package/src/core/query/joins.ts +70 -3
- package/src/core/result.ts +12 -6
- package/src/core/runtime.ts +39 -15
- package/src/model/query/operations.ts +77 -39
- package/src/model/result.ts +5 -2
- package/tests/base/esc-chainable.test.ts +159 -0
- package/tests/base/relations.test.ts +593 -0
- package/tests/base/upsert.test.ts +3 -3
- package/tests/snippets/x-2.ts +28 -0
package/src/core/result.ts
CHANGED
|
@@ -22,13 +22,13 @@ export type SafeResult<T> =
|
|
|
22
22
|
|
|
23
23
|
/** Accumulated state for a query operation (findMany / findFirst). */
|
|
24
24
|
export interface QueryState {
|
|
25
|
-
/**
|
|
25
|
+
/** SQL SELECT blacklist — columns to omit from the query. */
|
|
26
26
|
exclude?: AnyRecord;
|
|
27
27
|
/** When `true`, formatting is skipped. */
|
|
28
28
|
raw?: boolean;
|
|
29
29
|
/** When `true`, result is wrapped in `{ data, error }`. */
|
|
30
30
|
safe?: boolean;
|
|
31
|
-
/**
|
|
31
|
+
/** SQL SELECT whitelist — columns to include in the query. */
|
|
32
32
|
select?: AnyRecord;
|
|
33
33
|
/** Compiled where clause. */
|
|
34
34
|
where?: unknown;
|
|
@@ -154,9 +154,12 @@ export class QueryResult<T> extends ThenableResult<T> {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
157
|
+
* Controls which columns appear in the SQL SELECT clause (whitelist).
|
|
158
158
|
*
|
|
159
|
-
*
|
|
159
|
+
* This affects the query itself, not just the result.
|
|
160
|
+
* Equivalent to `db.select({ col: table.col }).from(table)`.
|
|
161
|
+
*
|
|
162
|
+
* @param value - A map of `{ columnName: true }`.
|
|
160
163
|
* @returns A new `QueryResult` with the `.select()` state applied.
|
|
161
164
|
*/
|
|
162
165
|
select(value: AnyRecord): QueryResult<T> {
|
|
@@ -164,9 +167,12 @@ export class QueryResult<T> extends ThenableResult<T> {
|
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
/**
|
|
167
|
-
*
|
|
170
|
+
* Controls which columns are excluded from the SQL SELECT clause (blacklist).
|
|
171
|
+
*
|
|
172
|
+
* This affects the query itself, not just the result.
|
|
173
|
+
* All columns except the listed ones will be fetched.
|
|
168
174
|
*
|
|
169
|
-
* @param value - A map of `{
|
|
175
|
+
* @param value - A map of `{ columnName: true }`.
|
|
170
176
|
* @returns A new `QueryResult` with the `.exclude()` state applied.
|
|
171
177
|
*/
|
|
172
178
|
exclude(value: AnyRecord): QueryResult<T> {
|
package/src/core/runtime.ts
CHANGED
|
@@ -105,6 +105,16 @@ export class ModelRuntime {
|
|
|
105
105
|
return this.config.options.format;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/** The current where clause, exposed for relation descriptors. */
|
|
109
|
+
get $where(): unknown {
|
|
110
|
+
return this.currentWhere;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The table name this model is bound to, exposed for relation descriptors. */
|
|
114
|
+
get $tableName(): string {
|
|
115
|
+
return this.config.tableName;
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
// ---------------------------------------------------------------------------
|
|
109
119
|
// Public: filtering
|
|
110
120
|
// ---------------------------------------------------------------------------
|
|
@@ -127,16 +137,26 @@ export class ModelRuntime {
|
|
|
127
137
|
// ---------------------------------------------------------------------------
|
|
128
138
|
|
|
129
139
|
/**
|
|
130
|
-
* Returns the
|
|
140
|
+
* Returns a relation descriptor carrying the model's where clause
|
|
141
|
+
* and the nested relation includes.
|
|
131
142
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
143
|
+
* Used in `.with()` to filter a relation and load nested relations:
|
|
144
|
+
* ```ts
|
|
145
|
+
* userModel.findMany().with({
|
|
146
|
+
* posts: postModel.where({ ... }).include({ comments: true }),
|
|
147
|
+
* });
|
|
148
|
+
* ```
|
|
134
149
|
*
|
|
135
|
-
* @param value - The relation include descriptor.
|
|
136
|
-
* @returns
|
|
150
|
+
* @param value - The nested relation include descriptor.
|
|
151
|
+
* @returns A model relation descriptor consumed by the join executor.
|
|
137
152
|
*/
|
|
138
153
|
include(value: unknown): unknown {
|
|
139
|
-
return
|
|
154
|
+
return {
|
|
155
|
+
__modelRelation: true,
|
|
156
|
+
whereValue: this.currentWhere,
|
|
157
|
+
tableName: this.config.tableName,
|
|
158
|
+
with: value,
|
|
159
|
+
};
|
|
140
160
|
}
|
|
141
161
|
|
|
142
162
|
/**
|
|
@@ -182,7 +202,8 @@ export class ModelRuntime {
|
|
|
182
202
|
/**
|
|
183
203
|
* Returns a thenable that resolves to an array of matching rows.
|
|
184
204
|
*
|
|
185
|
-
* Supports chaining: `.select()`, `.exclude()
|
|
205
|
+
* Supports chaining: `.select()`, `.exclude()` (SQL SELECT),
|
|
206
|
+
* `.with()` (relations), `.raw()`, `.safe()`.
|
|
186
207
|
*
|
|
187
208
|
* @returns A {@link QueryResult} that can be awaited or further chained.
|
|
188
209
|
*/
|
|
@@ -202,6 +223,8 @@ export class ModelRuntime {
|
|
|
202
223
|
baseTable: table,
|
|
203
224
|
whereSql,
|
|
204
225
|
withValue: qState.with as AnyRecord,
|
|
226
|
+
select: qState.select,
|
|
227
|
+
exclude: qState.exclude,
|
|
205
228
|
limitOne: false,
|
|
206
229
|
});
|
|
207
230
|
} else {
|
|
@@ -234,7 +257,8 @@ export class ModelRuntime {
|
|
|
234
257
|
/**
|
|
235
258
|
* Returns a thenable that resolves to the first matching row (or `undefined`).
|
|
236
259
|
*
|
|
237
|
-
* Supports chaining: `.select()`, `.exclude()
|
|
260
|
+
* Supports chaining: `.select()`, `.exclude()` (SQL SELECT),
|
|
261
|
+
* `.with()` (relations), `.raw()`, `.safe()`.
|
|
238
262
|
*
|
|
239
263
|
* @returns A {@link QueryResult} that can be awaited or further chained.
|
|
240
264
|
*/
|
|
@@ -254,6 +278,8 @@ export class ModelRuntime {
|
|
|
254
278
|
baseTable: table,
|
|
255
279
|
whereSql,
|
|
256
280
|
withValue: qState.with as AnyRecord,
|
|
281
|
+
select: qState.select,
|
|
282
|
+
exclude: qState.exclude,
|
|
257
283
|
limitOne: true,
|
|
258
284
|
});
|
|
259
285
|
} else {
|
|
@@ -529,7 +555,11 @@ export class ModelRuntime {
|
|
|
529
555
|
}
|
|
530
556
|
|
|
531
557
|
/**
|
|
532
|
-
* Applies
|
|
558
|
+
* Applies post-query transforms to the query result.
|
|
559
|
+
*
|
|
560
|
+
* Note: `.select()` and `.exclude()` are handled at the SQL level
|
|
561
|
+
* (via {@link ProjectionBuilder}) and are NOT applied here.
|
|
562
|
+
* Only format transforms are applied post-query.
|
|
533
563
|
*/
|
|
534
564
|
private applyPostQueryTransforms(
|
|
535
565
|
result: unknown,
|
|
@@ -537,12 +567,6 @@ export class ModelRuntime {
|
|
|
537
567
|
): unknown {
|
|
538
568
|
let out = result;
|
|
539
569
|
|
|
540
|
-
if (qState.select) {
|
|
541
|
-
out = this.transformer.applySelect(out, qState.select);
|
|
542
|
-
}
|
|
543
|
-
if (qState.exclude) {
|
|
544
|
-
out = this.transformer.applyExclude(out, qState.exclude);
|
|
545
|
-
}
|
|
546
570
|
if (!qState.raw) {
|
|
547
571
|
out = this.transformer.applyFormat(
|
|
548
572
|
out,
|
|
@@ -17,59 +17,59 @@ export type EscapedValue<T> =
|
|
|
17
17
|
|
|
18
18
|
type OpValue<T> = T | SQL | EscapedValue<T>;
|
|
19
19
|
|
|
20
|
-
export
|
|
20
|
+
export interface ColumnOpsBase<T> {
|
|
21
21
|
eq?: OpValue<T>;
|
|
22
22
|
equal?: OpValue<T>;
|
|
23
|
-
not?: OpValue<T>;
|
|
24
23
|
in?: OpValue<T>[];
|
|
25
|
-
nin?: OpValue<T>[];
|
|
26
24
|
isNull?: boolean;
|
|
27
|
-
|
|
25
|
+
nin?: OpValue<T>[];
|
|
26
|
+
not?: OpValue<T>;
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export interface NumberOps {
|
|
30
|
+
between?: [OpValue<number>, OpValue<number>];
|
|
30
31
|
gt?: OpValue<number>;
|
|
31
32
|
gte?: OpValue<number>;
|
|
32
33
|
lt?: OpValue<number>;
|
|
33
34
|
lte?: OpValue<number>;
|
|
34
|
-
between?: [OpValue<number>, OpValue<number>];
|
|
35
35
|
notBetween?: [OpValue<number>, OpValue<number>];
|
|
36
|
-
}
|
|
36
|
+
}
|
|
37
37
|
|
|
38
|
-
export
|
|
39
|
-
like?: OpValue<string>;
|
|
40
|
-
ilike?: OpValue<string>;
|
|
41
|
-
startsWith?: OpValue<string>;
|
|
42
|
-
endsWith?: OpValue<string>;
|
|
38
|
+
export interface StringOps {
|
|
43
39
|
contains?: OpValue<string>;
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
endsWith?: OpValue<string>;
|
|
41
|
+
ilike?: OpValue<string>;
|
|
46
42
|
length?: NumberOps;
|
|
47
|
-
|
|
43
|
+
like?: OpValue<string>;
|
|
44
|
+
notRegex?: OpValue<string>;
|
|
45
|
+
regex?: OpValue<string>;
|
|
46
|
+
startsWith?: OpValue<string>;
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
export
|
|
50
|
-
isTrue?: boolean;
|
|
49
|
+
export interface BoolOps {
|
|
51
50
|
isFalse?: boolean;
|
|
52
|
-
|
|
51
|
+
isTrue?: boolean;
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
export
|
|
55
|
-
before?: OpValue<Date | string>;
|
|
54
|
+
export interface DateOps {
|
|
56
55
|
after?: OpValue<Date | string>;
|
|
57
|
-
|
|
58
|
-
notOn?: OpValue<Date | string>;
|
|
56
|
+
before?: OpValue<Date | string>;
|
|
59
57
|
between?: [OpValue<Date | string>, OpValue<Date | string>];
|
|
60
|
-
|
|
58
|
+
notOn?: OpValue<Date | string>;
|
|
59
|
+
on?: OpValue<Date | string>;
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
export
|
|
62
|
+
export interface JsonOps<T> {
|
|
63
63
|
has?: T;
|
|
64
|
-
hasAny?: T[];
|
|
65
64
|
hasAll?: T[];
|
|
65
|
+
hasAny?: T[];
|
|
66
66
|
len?: NumberOps;
|
|
67
|
-
}
|
|
67
|
+
}
|
|
68
68
|
|
|
69
|
-
export
|
|
70
|
-
or?: ColumnValue<TColumn>[];
|
|
69
|
+
export interface LogicalOps<TColumn extends Column> {
|
|
71
70
|
and?: ColumnValue<TColumn>[];
|
|
72
|
-
|
|
71
|
+
or?: ColumnValue<TColumn>[];
|
|
72
|
+
}
|
|
73
73
|
|
|
74
74
|
export type TypeOps<T> = T extends number
|
|
75
75
|
? NumberOps
|
|
@@ -103,29 +103,40 @@ export type ColumnValue<
|
|
|
103
103
|
* - Drizzle ORM operators should be used directly
|
|
104
104
|
* - complex types (e.g. Date, objects, custom classes) need safe handling
|
|
105
105
|
*
|
|
106
|
-
* There are
|
|
106
|
+
* There are three supported forms:
|
|
107
107
|
*
|
|
108
108
|
* 1) Implicit equality (default behavior):
|
|
109
109
|
* ```ts
|
|
110
110
|
* where({ name: esc("Alex") })
|
|
111
111
|
* ```
|
|
112
|
-
* Compiles to:
|
|
113
|
-
* ```ts
|
|
114
|
-
* {
|
|
115
|
-
* eq: "Alex"
|
|
116
|
-
* }
|
|
117
|
-
* // In drizzle: eq(column, "Alex")
|
|
118
|
-
* ```
|
|
119
112
|
*
|
|
120
113
|
* 2) Explicit operator (Drizzle-style):
|
|
121
114
|
* ```ts
|
|
122
115
|
* where({ age: esc(gte, 18) })
|
|
123
116
|
* ```
|
|
124
|
-
*
|
|
117
|
+
*
|
|
118
|
+
* 3) Chainable operator methods (recommended):
|
|
125
119
|
* ```ts
|
|
126
|
-
*
|
|
120
|
+
* where({ name: esc.like("%Alex%") })
|
|
121
|
+
* where({ age: esc.gte(18) })
|
|
122
|
+
* where({ status: esc.in(["active", "pending"]) })
|
|
123
|
+
* where({ price: esc.between(10, 100) })
|
|
127
124
|
* ```
|
|
128
125
|
*
|
|
126
|
+
* Available chainable methods:
|
|
127
|
+
* - `esc.eq(value)` — equality
|
|
128
|
+
* - `esc.not(value)` — inequality
|
|
129
|
+
* - `esc.gt(value)` — greater than
|
|
130
|
+
* - `esc.gte(value)` — greater than or equal
|
|
131
|
+
* - `esc.lt(value)` — less than
|
|
132
|
+
* - `esc.lte(value)` — less than or equal
|
|
133
|
+
* - `esc.like(pattern)` — SQL LIKE pattern matching
|
|
134
|
+
* - `esc.ilike(pattern)` — case-insensitive LIKE
|
|
135
|
+
* - `esc.in(values)` — value in array
|
|
136
|
+
* - `esc.nin(values)` — value not in array
|
|
137
|
+
* - `esc.between(min, max)` — value between range
|
|
138
|
+
* - `esc.notBetween(min, max)` — value not between range
|
|
139
|
+
*
|
|
129
140
|
* The column is injected later during query compilation.
|
|
130
141
|
* `esc` does NOT execute the operator immediately.
|
|
131
142
|
*
|
|
@@ -169,3 +180,30 @@ export function esc<T>(arg1: any, arg2?: any): EscapedValue<T> {
|
|
|
169
180
|
equal: arg1,
|
|
170
181
|
};
|
|
171
182
|
}
|
|
183
|
+
|
|
184
|
+
// Chainable operator methods - return DSL objects
|
|
185
|
+
esc.eq = <T>(value: T) => ({ eq: value });
|
|
186
|
+
|
|
187
|
+
esc.not = <T>(value: T) => ({ not: value });
|
|
188
|
+
|
|
189
|
+
esc.gt = <T>(value: T) => ({ gt: value });
|
|
190
|
+
|
|
191
|
+
esc.gte = <T>(value: T) => ({ gte: value });
|
|
192
|
+
|
|
193
|
+
esc.lt = <T>(value: T) => ({ lt: value });
|
|
194
|
+
|
|
195
|
+
esc.lte = <T>(value: T) => ({ lte: value });
|
|
196
|
+
|
|
197
|
+
esc.like = (pattern: string) => ({ like: pattern });
|
|
198
|
+
|
|
199
|
+
esc.ilike = (pattern: string) => ({ ilike: pattern });
|
|
200
|
+
|
|
201
|
+
esc.in = <T>(values: T[]) => ({ in: values });
|
|
202
|
+
|
|
203
|
+
esc.nin = <T>(values: T[]) => ({ nin: values });
|
|
204
|
+
|
|
205
|
+
esc.between = <T>(min: T, max: T) => ({ between: [min, max] as [T, T] });
|
|
206
|
+
|
|
207
|
+
esc.notBetween = <T>(min: T, max: T) => ({
|
|
208
|
+
notBetween: [min, max] as [T, T],
|
|
209
|
+
});
|
package/src/model/result.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
TableRelationalConfig,
|
|
3
3
|
TablesRelationalConfig,
|
|
4
4
|
} from "drizzle-orm/relations";
|
|
5
|
+
import type { SimplifyDeep } from "type-fest";
|
|
5
6
|
import type {
|
|
6
7
|
ApplyArrayIfArray,
|
|
7
8
|
InferArrayItem,
|
|
@@ -49,7 +50,9 @@ export interface ModelQueryResult<
|
|
|
49
50
|
TWithSafe,
|
|
50
51
|
ApplyArrayIfArray<
|
|
51
52
|
TResult,
|
|
52
|
-
|
|
53
|
+
SimplifyDeep<
|
|
54
|
+
ModelFormatResult<InferArrayItem<TResult>, TFormat, TTable>
|
|
55
|
+
>
|
|
53
56
|
>
|
|
54
57
|
>
|
|
55
58
|
> {
|
|
@@ -148,7 +151,7 @@ export interface ModelInMutableResult<
|
|
|
148
151
|
// >(
|
|
149
152
|
// value?: TConfig["dialect"] extends ReturningIdDialects ? never : TValue
|
|
150
153
|
// ): Omit<ModelMutateResult<TResult, TConfig, TResultType>, "with">;
|
|
151
|
-
$return: MethodReturnResult<TConfig>;
|
|
154
|
+
// $return: MethodReturnResult<TConfig>;
|
|
152
155
|
|
|
153
156
|
omit<TValue extends MethodExcludeValue<TConfig["tableOutput"]>>(
|
|
154
157
|
value: TValue
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { model } from "tests/base";
|
|
3
|
+
import { esc } from "@/model";
|
|
4
|
+
|
|
5
|
+
const userModel = model("user", {});
|
|
6
|
+
|
|
7
|
+
function uid(): string {
|
|
8
|
+
return `${Date.now()}-${Math.random()}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
describe("esc chainable methods", () => {
|
|
12
|
+
let testUserId: number;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
const user = await userModel
|
|
16
|
+
.insert({
|
|
17
|
+
name: "Esc Test User",
|
|
18
|
+
email: `${uid()}@esc.com`,
|
|
19
|
+
age: 25,
|
|
20
|
+
})
|
|
21
|
+
.returnFirst();
|
|
22
|
+
testUserId = user.id;
|
|
23
|
+
|
|
24
|
+
await userModel.insert({
|
|
25
|
+
name: "Alice",
|
|
26
|
+
email: `${uid()}@esc.com`,
|
|
27
|
+
age: 30,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await userModel.insert({
|
|
31
|
+
name: "Bob",
|
|
32
|
+
email: `${uid()}@esc.com`,
|
|
33
|
+
age: 20,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("esc.eq() - equality", async () => {
|
|
38
|
+
const users = await userModel.where({ id: esc.eq(testUserId) }).findMany();
|
|
39
|
+
|
|
40
|
+
expect(users).toBeArray();
|
|
41
|
+
expect(users.length).toBe(1);
|
|
42
|
+
expect(users[0]?.id).toBe(testUserId);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("esc.not() - inequality", async () => {
|
|
46
|
+
const users = await userModel
|
|
47
|
+
.where({ id: esc.not(testUserId) })
|
|
48
|
+
.findMany();
|
|
49
|
+
|
|
50
|
+
expect(users).toBeArray();
|
|
51
|
+
for (const user of users) {
|
|
52
|
+
expect(user.id).not.toBe(testUserId);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("esc.gt() - greater than", async () => {
|
|
57
|
+
const users = await userModel.where({ age: esc.gt(25) }).findMany();
|
|
58
|
+
|
|
59
|
+
expect(users).toBeArray();
|
|
60
|
+
for (const user of users) {
|
|
61
|
+
expect(user.age).toBeGreaterThan(25);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("esc.gte() - greater than or equal", async () => {
|
|
66
|
+
const users = await userModel.where({ age: esc.gte(25) }).findMany();
|
|
67
|
+
|
|
68
|
+
expect(users).toBeArray();
|
|
69
|
+
for (const user of users) {
|
|
70
|
+
expect(user.age).toBeGreaterThanOrEqual(25);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("esc.lt() - less than", async () => {
|
|
75
|
+
const users = await userModel.where({ age: esc.lt(25) }).findMany();
|
|
76
|
+
|
|
77
|
+
expect(users).toBeArray();
|
|
78
|
+
for (const user of users) {
|
|
79
|
+
expect(user.age).toBeLessThan(25);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("esc.lte() - less than or equal", async () => {
|
|
84
|
+
const users = await userModel.where({ age: esc.lte(25) }).findMany();
|
|
85
|
+
|
|
86
|
+
expect(users).toBeArray();
|
|
87
|
+
for (const user of users) {
|
|
88
|
+
expect(user.age).toBeLessThanOrEqual(25);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("esc.like() - pattern matching", async () => {
|
|
93
|
+
const users = await userModel.where({ name: esc.like("Esc%") }).findMany();
|
|
94
|
+
|
|
95
|
+
expect(users).toBeArray();
|
|
96
|
+
expect(users.length).toBeGreaterThan(0);
|
|
97
|
+
for (const user of users) {
|
|
98
|
+
expect(user.name.startsWith("Esc")).toBe(true);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("esc.ilike() - case-insensitive pattern matching", async () => {
|
|
103
|
+
const users = await userModel
|
|
104
|
+
.where({ name: esc.ilike("%alice%") })
|
|
105
|
+
.findMany();
|
|
106
|
+
|
|
107
|
+
expect(users).toBeArray();
|
|
108
|
+
expect(users.length).toBeGreaterThan(0);
|
|
109
|
+
for (const user of users) {
|
|
110
|
+
expect(user.name.toLowerCase()).toContain("alice");
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("esc.in() - value in array", async () => {
|
|
115
|
+
const users = await userModel
|
|
116
|
+
.where({ name: esc.in(["Alice", "Bob"]) })
|
|
117
|
+
.findMany();
|
|
118
|
+
|
|
119
|
+
expect(users).toBeArray();
|
|
120
|
+
expect(users.length).toBeGreaterThan(0);
|
|
121
|
+
for (const user of users) {
|
|
122
|
+
expect(["Alice", "Bob"]).toContain(user.name);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("esc.nin() - value not in array", async () => {
|
|
127
|
+
const users = await userModel
|
|
128
|
+
.where({ name: esc.nin(["Alice", "Bob"]) })
|
|
129
|
+
.findMany();
|
|
130
|
+
|
|
131
|
+
expect(users).toBeArray();
|
|
132
|
+
for (const user of users) {
|
|
133
|
+
expect(["Alice", "Bob"]).not.toContain(user.name);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("esc.between() - value in range", async () => {
|
|
138
|
+
const users = await userModel
|
|
139
|
+
.where({ age: esc.between(20, 30) })
|
|
140
|
+
.findMany();
|
|
141
|
+
|
|
142
|
+
expect(users).toBeArray();
|
|
143
|
+
for (const user of users) {
|
|
144
|
+
expect(user.age).toBeGreaterThanOrEqual(20);
|
|
145
|
+
expect(user.age).toBeLessThanOrEqual(30);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("esc.notBetween() - value outside range", async () => {
|
|
150
|
+
const users = await userModel
|
|
151
|
+
.where({ age: esc.notBetween(21, 29) })
|
|
152
|
+
.findMany();
|
|
153
|
+
|
|
154
|
+
expect(users).toBeArray();
|
|
155
|
+
for (const user of users) {
|
|
156
|
+
expect(user.age < 21 || user.age > 29).toBe(true);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|