@apisr/drizzle-model 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +319 -38
- package/ROADMAP.md +3 -0
- package/dist/core/dialect.mjs +56 -0
- package/dist/core/query/joins.mjs +334 -0
- package/dist/core/query/projection.mjs +78 -0
- package/dist/core/query/where.mjs +265 -0
- package/dist/core/result.mjs +215 -0
- package/dist/core/runtime.mjs +393 -0
- package/dist/core/transform.mjs +83 -0
- package/dist/index.d.mts +4 -4
- package/dist/model/builder.mjs +22 -2
- package/dist/model/config.d.mts +6 -6
- package/dist/model/format.d.mts +6 -2
- package/dist/model/index.d.mts +2 -2
- package/dist/model/methods/exclude.d.mts +1 -1
- package/dist/model/methods/return.d.mts +2 -2
- package/dist/model/methods/select.d.mts +1 -1
- package/dist/model/model.d.mts +10 -12
- package/dist/model/query/error.d.mts +4 -0
- package/dist/model/query/operations.d.mts +89 -39
- package/dist/model/query/operations.mjs +12 -0
- package/dist/model/result.d.mts +26 -10
- package/dist/types.d.mts +16 -1
- package/package.json +1 -1
- package/src/model/query/operations.ts +77 -39
- package/tests/base/esc-chainable.test.ts +159 -0
- package/tests/snippets/x-2.ts +30 -0
- package/dist/model/core/joins.mjs +0 -184
- package/dist/model/core/projection.mjs +0 -28
- package/dist/model/core/runtime.mjs +0 -198
- package/dist/model/core/thenable.mjs +0 -64
- package/dist/model/core/transform.mjs +0 -39
- package/dist/model/core/where.mjs +0 -130
- package/dist/model/core/with.mjs +0 -19
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { esc, modelBuilder } from "src/model";
|
|
2
|
+
import { db } from "../db";
|
|
3
|
+
import { relations } from "../relations";
|
|
4
|
+
import * as schema from "../schema";
|
|
5
|
+
|
|
6
|
+
const model = modelBuilder({
|
|
7
|
+
schema,
|
|
8
|
+
db,
|
|
9
|
+
relations,
|
|
10
|
+
dialect: "PostgreSQL",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// create model
|
|
14
|
+
const userModel = model("user", {});
|
|
15
|
+
|
|
16
|
+
// #1 syntax
|
|
17
|
+
await userModel
|
|
18
|
+
.where({
|
|
19
|
+
name: {
|
|
20
|
+
like: "A%",
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
.findFirst();
|
|
24
|
+
|
|
25
|
+
// #2 syntax
|
|
26
|
+
await userModel
|
|
27
|
+
.where({
|
|
28
|
+
name: esc.like("A%"),
|
|
29
|
+
})
|
|
30
|
+
.findFirst();
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { and, eq } from "drizzle-orm";
|
|
2
|
-
|
|
3
|
-
//#region src/model/core/joins.ts
|
|
4
|
-
function isDrizzleColumn(value) {
|
|
5
|
-
return !!value && typeof value === "object" && typeof value.getSQL === "function";
|
|
6
|
-
}
|
|
7
|
-
function getPrimaryKeyField(table) {
|
|
8
|
-
for (const [k, v] of Object.entries(table)) {
|
|
9
|
-
if (!isDrizzleColumn(v)) continue;
|
|
10
|
-
if (v.primary === true) return k;
|
|
11
|
-
if (v.config?.primaryKey === true) return k;
|
|
12
|
-
}
|
|
13
|
-
if ("id" in table) return "id";
|
|
14
|
-
return Object.keys(table).find((k) => isDrizzleColumn(table[k])) ?? "id";
|
|
15
|
-
}
|
|
16
|
-
function isAllNullRow(obj) {
|
|
17
|
-
if (!obj || typeof obj !== "object") return true;
|
|
18
|
-
for (const v of Object.values(obj)) if (v !== null && v !== void 0) return false;
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
async function aliasTable(table, aliasName, dialect) {
|
|
22
|
-
if (dialect === "PostgreSQL") {
|
|
23
|
-
const mod = await import("drizzle-orm/pg-core");
|
|
24
|
-
if (typeof mod.alias === "function") return mod.alias(table, aliasName);
|
|
25
|
-
}
|
|
26
|
-
if (dialect === "MySQL") {
|
|
27
|
-
const mod = await import("drizzle-orm/mysql-core");
|
|
28
|
-
if (typeof mod.alias === "function") return mod.alias(table, aliasName);
|
|
29
|
-
}
|
|
30
|
-
if (dialect === "SQLite") {
|
|
31
|
-
const mod = await import("drizzle-orm/sqlite-core");
|
|
32
|
-
if (typeof mod.alias === "function") return mod.alias(table, aliasName);
|
|
33
|
-
}
|
|
34
|
-
return table;
|
|
35
|
-
}
|
|
36
|
-
function buildJoinOn(node) {
|
|
37
|
-
const parts = node.sourceColumns.map((src, i) => {
|
|
38
|
-
const tgt = node.targetColumns[i];
|
|
39
|
-
const tgtKey = Object.entries(node.targetTable).find(([, v]) => v === tgt)?.[0];
|
|
40
|
-
return eq(tgtKey ? node.targetAliasTable[tgtKey] : tgt, src);
|
|
41
|
-
});
|
|
42
|
-
return parts.length === 1 ? parts[0] : and(...parts);
|
|
43
|
-
}
|
|
44
|
-
function buildSelectMapForTable(table) {
|
|
45
|
-
const out = {};
|
|
46
|
-
for (const [k, v] of Object.entries(table)) if (isDrizzleColumn(v)) out[k] = v;
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
async function executeWithJoins(args) {
|
|
50
|
-
const { db, schema, relations, baseTableName, baseTable, dialect, whereSql, withValue, limitOne } = args;
|
|
51
|
-
const usedAliasKeys = /* @__PURE__ */ new Set();
|
|
52
|
-
const buildNode = async (parent, currentTableName, currentTable, key, value, path) => {
|
|
53
|
-
const relMeta = relations[currentTableName]?.relations?.[key];
|
|
54
|
-
if (!relMeta) throw new Error(`Unknown relation '${key}' on table '${currentTableName}'.`);
|
|
55
|
-
const targetTableName = relMeta.targetTableName;
|
|
56
|
-
const targetTable = schema[targetTableName];
|
|
57
|
-
const aliasKeyBase = [...path, key].join("__");
|
|
58
|
-
let aliasKey = aliasKeyBase;
|
|
59
|
-
let idx = 1;
|
|
60
|
-
while (usedAliasKeys.has(aliasKey)) aliasKey = `${aliasKeyBase}_${idx++}`;
|
|
61
|
-
usedAliasKeys.add(aliasKey);
|
|
62
|
-
const needsAlias = targetTableName === currentTableName || usedAliasKeys.has(`table:${targetTableName}`);
|
|
63
|
-
usedAliasKeys.add(`table:${targetTableName}`);
|
|
64
|
-
const targetAliasTable = needsAlias ? await aliasTable(targetTable, aliasKey, dialect) : targetTable;
|
|
65
|
-
const node = {
|
|
66
|
-
path: [...path, key],
|
|
67
|
-
key,
|
|
68
|
-
relationType: relMeta.relationType,
|
|
69
|
-
sourceTableName: currentTableName,
|
|
70
|
-
targetTableName,
|
|
71
|
-
sourceTable: currentTable,
|
|
72
|
-
targetTable,
|
|
73
|
-
targetAliasTable,
|
|
74
|
-
aliasKey,
|
|
75
|
-
sourceColumns: relMeta.sourceColumns ?? [],
|
|
76
|
-
targetColumns: relMeta.targetColumns ?? [],
|
|
77
|
-
pkField: getPrimaryKeyField(targetAliasTable),
|
|
78
|
-
parent,
|
|
79
|
-
children: []
|
|
80
|
-
};
|
|
81
|
-
if (value && typeof value === "object" && value !== true) for (const [childKey, childVal] of Object.entries(value)) {
|
|
82
|
-
if (childVal !== true && (typeof childVal !== "object" || childVal == null)) continue;
|
|
83
|
-
const child = await buildNode(node, targetTableName, targetAliasTable, childKey, childVal, [...path, key]);
|
|
84
|
-
node.children.push(child);
|
|
85
|
-
}
|
|
86
|
-
return node;
|
|
87
|
-
};
|
|
88
|
-
const root = {
|
|
89
|
-
path: [],
|
|
90
|
-
key: "$root",
|
|
91
|
-
relationType: "one",
|
|
92
|
-
sourceTableName: baseTableName,
|
|
93
|
-
targetTableName: baseTableName,
|
|
94
|
-
sourceTable: baseTable,
|
|
95
|
-
targetTable: baseTable,
|
|
96
|
-
targetAliasTable: baseTable,
|
|
97
|
-
aliasKey: "$base",
|
|
98
|
-
sourceColumns: [],
|
|
99
|
-
targetColumns: [],
|
|
100
|
-
pkField: getPrimaryKeyField(baseTable),
|
|
101
|
-
children: []
|
|
102
|
-
};
|
|
103
|
-
for (const [key, value] of Object.entries(withValue)) {
|
|
104
|
-
if (value !== true && (typeof value !== "object" || value == null)) continue;
|
|
105
|
-
const child = await buildNode(void 0, baseTableName, baseTable, key, value, []);
|
|
106
|
-
root.children.push(child);
|
|
107
|
-
}
|
|
108
|
-
const nodes = [];
|
|
109
|
-
const walk = (n) => {
|
|
110
|
-
for (const c of n.children) {
|
|
111
|
-
nodes.push(c);
|
|
112
|
-
walk(c);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
walk(root);
|
|
116
|
-
const selectMap = { base: buildSelectMapForTable(baseTable) };
|
|
117
|
-
for (const n of nodes) selectMap[n.aliasKey] = buildSelectMapForTable(n.targetAliasTable);
|
|
118
|
-
let q = db.select(selectMap).from(baseTable);
|
|
119
|
-
if (whereSql) q = q.where(whereSql);
|
|
120
|
-
for (const n of nodes) {
|
|
121
|
-
const on = buildJoinOn(n);
|
|
122
|
-
q = q.leftJoin(n.targetAliasTable, on);
|
|
123
|
-
}
|
|
124
|
-
if (limitOne) q = q.limit(1);
|
|
125
|
-
const rows = await q;
|
|
126
|
-
const basePk = root.pkField;
|
|
127
|
-
const baseMap = /* @__PURE__ */ new Map();
|
|
128
|
-
const ensureManyContainer = (obj, key) => {
|
|
129
|
-
if (!Array.isArray(obj[key])) obj[key] = [];
|
|
130
|
-
};
|
|
131
|
-
const ensureOneContainer = (obj, key) => {
|
|
132
|
-
if (!(key in obj)) obj[key] = null;
|
|
133
|
-
};
|
|
134
|
-
const manyIndexByPath = /* @__PURE__ */ new Map();
|
|
135
|
-
for (const row of rows) {
|
|
136
|
-
const baseRow = row.base;
|
|
137
|
-
const baseId = baseRow[basePk];
|
|
138
|
-
if (baseId === void 0) continue;
|
|
139
|
-
const baseObj = (() => {
|
|
140
|
-
const existing = baseMap.get(baseId);
|
|
141
|
-
if (existing) return existing;
|
|
142
|
-
const created = { ...baseRow };
|
|
143
|
-
baseMap.set(baseId, created);
|
|
144
|
-
return created;
|
|
145
|
-
})();
|
|
146
|
-
for (const n of nodes) {
|
|
147
|
-
const data = row[n.aliasKey];
|
|
148
|
-
const relPath = n.path.join(".");
|
|
149
|
-
const parentPath = n.parent ? n.parent.path.join(".") : "";
|
|
150
|
-
let parentObj = baseObj;
|
|
151
|
-
if (parentPath) {
|
|
152
|
-
const parentIndex = manyIndexByPath.get(parentPath);
|
|
153
|
-
if (parentIndex && parentIndex.size) parentObj = Array.from(parentIndex.values()).at(-1);
|
|
154
|
-
else {
|
|
155
|
-
const parentKey = n.parent?.key;
|
|
156
|
-
parentObj = parentKey ? baseObj[parentKey] ?? baseObj : baseObj;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (isAllNullRow(data)) {
|
|
160
|
-
if (n.relationType === "one") ensureOneContainer(parentObj, n.key);
|
|
161
|
-
else ensureManyContainer(parentObj, n.key);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const pk = data[n.pkField];
|
|
165
|
-
if (n.relationType === "one") parentObj[n.key] = { ...data };
|
|
166
|
-
else {
|
|
167
|
-
ensureManyContainer(parentObj, n.key);
|
|
168
|
-
const indexKey = relPath;
|
|
169
|
-
if (!manyIndexByPath.has(indexKey)) manyIndexByPath.set(indexKey, /* @__PURE__ */ new Map());
|
|
170
|
-
const idxMap = manyIndexByPath.get(indexKey);
|
|
171
|
-
if (!idxMap.has(pk)) {
|
|
172
|
-
const obj = { ...data };
|
|
173
|
-
idxMap.set(pk, obj);
|
|
174
|
-
parentObj[n.key].push(obj);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const out = Array.from(baseMap.values());
|
|
180
|
-
return limitOne ? out[0] : out;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
//#endregion
|
|
184
|
-
export { executeWithJoins };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
//#region src/model/core/projection.ts
|
|
2
|
-
function isDrizzleColumn(value) {
|
|
3
|
-
return !!value && typeof value === "object" && typeof value.getSQL === "function";
|
|
4
|
-
}
|
|
5
|
-
function getTableColumnsMap(table) {
|
|
6
|
-
const out = {};
|
|
7
|
-
for (const [key, value] of Object.entries(table)) if (isDrizzleColumn(value)) out[key] = value;
|
|
8
|
-
return out;
|
|
9
|
-
}
|
|
10
|
-
function buildSelectProjection(table, select, exclude) {
|
|
11
|
-
const all = getTableColumnsMap(table);
|
|
12
|
-
if (select && typeof select === "object") {
|
|
13
|
-
const picked = {};
|
|
14
|
-
for (const [key, value] of Object.entries(select)) if (value === true && key in all) picked[key] = all[key];
|
|
15
|
-
if (Object.keys(picked).length) return { selectMap: picked };
|
|
16
|
-
return { selectMap: all };
|
|
17
|
-
}
|
|
18
|
-
if (exclude && typeof exclude === "object") {
|
|
19
|
-
const omitted = { ...all };
|
|
20
|
-
for (const [key, value] of Object.entries(exclude)) if (value === true) delete omitted[key];
|
|
21
|
-
if (Object.keys(omitted).length) return { selectMap: omitted };
|
|
22
|
-
return { selectMap: all };
|
|
23
|
-
}
|
|
24
|
-
return { selectMap: all };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
export { buildSelectProjection };
|