@buenojs/bueno 0.8.3 → 0.8.5
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/README.md +136 -16
- package/dist/cli/{index.js → bin.js} +3036 -1421
- package/dist/container/index.js +250 -0
- package/dist/context/index.js +219 -0
- package/dist/database/index.js +493 -0
- package/dist/frontend/index.js +7697 -0
- package/dist/health/index.js +364 -0
- package/dist/i18n/index.js +345 -0
- package/dist/index.js +11043 -6482
- package/dist/jobs/index.js +819 -0
- package/dist/lock/index.js +367 -0
- package/dist/logger/index.js +281 -0
- package/dist/metrics/index.js +289 -0
- package/dist/middleware/index.js +77 -0
- package/dist/migrations/index.js +571 -0
- package/dist/modules/index.js +3346 -0
- package/dist/notification/index.js +484 -0
- package/dist/observability/index.js +331 -0
- package/dist/openapi/index.js +776 -0
- package/dist/orm/index.js +1356 -0
- package/dist/router/index.js +886 -0
- package/dist/rpc/index.js +691 -0
- package/dist/schema/index.js +400 -0
- package/dist/telemetry/index.js +595 -0
- package/dist/template/index.js +640 -0
- package/dist/templates/index.js +640 -0
- package/dist/testing/index.js +1111 -0
- package/dist/types/index.js +60 -0
- package/package.json +121 -27
- package/src/cache/index.ts +2 -1
- package/src/cli/bin.ts +2 -2
- package/src/cli/commands/build.ts +183 -165
- package/src/cli/commands/dev.ts +96 -89
- package/src/cli/commands/generate.ts +142 -111
- package/src/cli/commands/help.ts +20 -16
- package/src/cli/commands/index.ts +3 -6
- package/src/cli/commands/migration.ts +124 -105
- package/src/cli/commands/new.ts +392 -438
- package/src/cli/commands/start.ts +81 -79
- package/src/cli/core/args.ts +68 -50
- package/src/cli/core/console.ts +89 -95
- package/src/cli/core/index.ts +4 -4
- package/src/cli/core/prompt.ts +65 -62
- package/src/cli/core/spinner.ts +23 -20
- package/src/cli/index.ts +46 -38
- package/src/cli/templates/database/index.ts +61 -0
- package/src/cli/templates/database/mysql.ts +14 -0
- package/src/cli/templates/database/none.ts +16 -0
- package/src/cli/templates/database/postgresql.ts +14 -0
- package/src/cli/templates/database/sqlite.ts +14 -0
- package/src/cli/templates/deploy.ts +29 -26
- package/src/cli/templates/docker.ts +41 -30
- package/src/cli/templates/frontend/index.ts +63 -0
- package/src/cli/templates/frontend/none.ts +17 -0
- package/src/cli/templates/frontend/react.ts +140 -0
- package/src/cli/templates/frontend/solid.ts +134 -0
- package/src/cli/templates/frontend/svelte.ts +131 -0
- package/src/cli/templates/frontend/vue.ts +130 -0
- package/src/cli/templates/generators/index.ts +339 -0
- package/src/cli/templates/generators/types.ts +56 -0
- package/src/cli/templates/index.ts +35 -2
- package/src/cli/templates/project/api.ts +81 -0
- package/src/cli/templates/project/default.ts +140 -0
- package/src/cli/templates/project/fullstack.ts +111 -0
- package/src/cli/templates/project/index.ts +95 -0
- package/src/cli/templates/project/minimal.ts +45 -0
- package/src/cli/templates/project/types.ts +94 -0
- package/src/cli/templates/project/website.ts +263 -0
- package/src/cli/utils/fs.ts +55 -41
- package/src/cli/utils/index.ts +3 -2
- package/src/cli/utils/strings.ts +47 -33
- package/src/cli/utils/version.ts +47 -0
- package/src/config/env-validation.ts +100 -0
- package/src/config/env.ts +169 -41
- package/src/config/index.ts +28 -20
- package/src/config/loader.ts +25 -16
- package/src/config/merge.ts +21 -10
- package/src/config/types.ts +545 -25
- package/src/config/validation.ts +215 -7
- package/src/container/forward-ref.ts +22 -22
- package/src/container/index.ts +34 -12
- package/src/context/index.ts +11 -1
- package/src/database/index.ts +7 -190
- package/src/database/orm/builder.ts +457 -0
- package/src/database/orm/casts/index.ts +130 -0
- package/src/database/orm/casts/types.ts +25 -0
- package/src/database/orm/compiler.ts +304 -0
- package/src/database/orm/hooks/index.ts +114 -0
- package/src/database/orm/index.ts +61 -0
- package/src/database/orm/model-registry.ts +59 -0
- package/src/database/orm/model.ts +821 -0
- package/src/database/orm/relationships/base.ts +146 -0
- package/src/database/orm/relationships/belongs-to-many.ts +179 -0
- package/src/database/orm/relationships/belongs-to.ts +56 -0
- package/src/database/orm/relationships/has-many.ts +45 -0
- package/src/database/orm/relationships/has-one.ts +41 -0
- package/src/database/orm/relationships/index.ts +11 -0
- package/src/database/orm/scopes/index.ts +55 -0
- package/src/events/__tests__/event-system.test.ts +235 -0
- package/src/events/config.ts +238 -0
- package/src/events/example-usage.ts +185 -0
- package/src/events/index.ts +278 -0
- package/src/events/manager.ts +385 -0
- package/src/events/registry.ts +182 -0
- package/src/events/types.ts +124 -0
- package/src/frontend/api-routes.ts +65 -23
- package/src/frontend/bundler.ts +76 -34
- package/src/frontend/console-client.ts +2 -2
- package/src/frontend/console-stream.ts +94 -38
- package/src/frontend/dev-server.ts +94 -46
- package/src/frontend/file-router.ts +61 -19
- package/src/frontend/frameworks/index.ts +37 -10
- package/src/frontend/frameworks/react.ts +10 -8
- package/src/frontend/frameworks/solid.ts +11 -9
- package/src/frontend/frameworks/svelte.ts +15 -9
- package/src/frontend/frameworks/vue.ts +13 -11
- package/src/frontend/hmr-client.ts +12 -10
- package/src/frontend/hmr.ts +146 -103
- package/src/frontend/index.ts +14 -5
- package/src/frontend/islands.ts +41 -22
- package/src/frontend/isr.ts +59 -37
- package/src/frontend/layout.ts +36 -21
- package/src/frontend/ssr/react.ts +74 -27
- package/src/frontend/ssr/solid.ts +54 -20
- package/src/frontend/ssr/svelte.ts +48 -14
- package/src/frontend/ssr/vue.ts +50 -18
- package/src/frontend/ssr.ts +83 -39
- package/src/frontend/types.ts +91 -56
- package/src/health/index.ts +21 -9
- package/src/i18n/engine.ts +305 -0
- package/src/i18n/index.ts +38 -0
- package/src/i18n/loader.ts +218 -0
- package/src/i18n/middleware.ts +164 -0
- package/src/i18n/negotiator.ts +162 -0
- package/src/i18n/types.ts +158 -0
- package/src/index.ts +179 -27
- package/src/jobs/drivers/memory.ts +315 -0
- package/src/jobs/drivers/redis.ts +459 -0
- package/src/jobs/index.ts +30 -0
- package/src/jobs/queue.ts +281 -0
- package/src/jobs/types.ts +295 -0
- package/src/jobs/worker.ts +380 -0
- package/src/logger/index.ts +1 -3
- package/src/logger/transports/index.ts +62 -22
- package/src/metrics/index.ts +25 -16
- package/src/migrations/index.ts +9 -0
- package/src/modules/filters.ts +13 -17
- package/src/modules/guards.ts +49 -26
- package/src/modules/index.ts +409 -298
- package/src/modules/interceptors.ts +58 -20
- package/src/modules/lazy.ts +11 -19
- package/src/modules/lifecycle.ts +15 -7
- package/src/modules/metadata.ts +15 -5
- package/src/modules/pipes.ts +94 -72
- package/src/notification/channels/base.ts +68 -0
- package/src/notification/channels/email.ts +105 -0
- package/src/notification/channels/push.ts +104 -0
- package/src/notification/channels/sms.ts +105 -0
- package/src/notification/channels/whatsapp.ts +104 -0
- package/src/notification/index.ts +48 -0
- package/src/notification/service.ts +354 -0
- package/src/notification/types.ts +344 -0
- package/src/observability/__tests__/observability.test.ts +483 -0
- package/src/observability/breadcrumbs.ts +114 -0
- package/src/observability/index.ts +136 -0
- package/src/observability/interceptor.ts +85 -0
- package/src/observability/service.ts +303 -0
- package/src/observability/trace.ts +37 -0
- package/src/observability/types.ts +196 -0
- package/src/openapi/__tests__/decorators.test.ts +335 -0
- package/src/openapi/__tests__/document-builder.test.ts +285 -0
- package/src/openapi/__tests__/route-scanner.test.ts +334 -0
- package/src/openapi/__tests__/schema-generator.test.ts +275 -0
- package/src/openapi/decorators.ts +328 -0
- package/src/openapi/document-builder.ts +274 -0
- package/src/openapi/index.ts +112 -0
- package/src/openapi/metadata.ts +112 -0
- package/src/openapi/route-scanner.ts +289 -0
- package/src/openapi/schema-generator.ts +256 -0
- package/src/openapi/swagger-module.ts +166 -0
- package/src/openapi/types.ts +398 -0
- package/src/orm/index.ts +10 -0
- package/src/rpc/index.ts +3 -1
- package/src/schema/index.ts +9 -0
- package/src/security/index.ts +15 -6
- package/src/ssg/index.ts +9 -8
- package/src/telemetry/index.ts +76 -22
- package/src/template/index.ts +7 -0
- package/src/templates/engine.ts +224 -0
- package/src/templates/index.ts +9 -0
- package/src/templates/loader.ts +331 -0
- package/src/templates/renderers/markdown.ts +212 -0
- package/src/templates/renderers/simple.ts +269 -0
- package/src/templates/types.ts +154 -0
- package/src/testing/index.ts +100 -27
- package/src/types/optional-deps.d.ts +347 -187
- package/src/validation/index.ts +92 -2
- package/src/validation/schemas.ts +536 -0
- package/tests/integration/fullstack.test.ts +4 -4
- package/tests/unit/database.test.ts +2 -72
- package/tests/unit/env-validation.test.ts +166 -0
- package/tests/unit/events.test.ts +910 -0
- package/tests/unit/i18n.test.ts +455 -0
- package/tests/unit/jobs.test.ts +493 -0
- package/tests/unit/notification.test.ts +988 -0
- package/tests/unit/observability.test.ts +453 -0
- package/tests/unit/orm/builder.test.ts +323 -0
- package/tests/unit/orm/casts.test.ts +179 -0
- package/tests/unit/orm/compiler.test.ts +220 -0
- package/tests/unit/orm/eager-loading.test.ts +285 -0
- package/tests/unit/orm/hooks.test.ts +191 -0
- package/tests/unit/orm/model.test.ts +373 -0
- package/tests/unit/orm/relationships.test.ts +303 -0
- package/tests/unit/orm/scopes.test.ts +74 -0
- package/tests/unit/templates-simple.test.ts +53 -0
- package/tests/unit/templates.test.ts +454 -0
- package/tests/unit/validation.test.ts +18 -24
- package/tsconfig.json +11 -3
|
@@ -0,0 +1,1356 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
13
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
15
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
16
|
+
else
|
|
17
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
18
|
+
if (d = decorators[i])
|
|
19
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
20
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
21
|
+
};
|
|
22
|
+
var __legacyMetadataTS = (k, v) => {
|
|
23
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
24
|
+
return Reflect.metadata(k, v);
|
|
25
|
+
};
|
|
26
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
27
|
+
var __require = import.meta.require;
|
|
28
|
+
|
|
29
|
+
// src/database/orm/compiler.ts
|
|
30
|
+
class QueryCompiler {
|
|
31
|
+
dialect;
|
|
32
|
+
paramIndex = 0;
|
|
33
|
+
params = [];
|
|
34
|
+
constructor(dialect) {
|
|
35
|
+
this.dialect = dialect;
|
|
36
|
+
}
|
|
37
|
+
reset() {
|
|
38
|
+
this.paramIndex = 0;
|
|
39
|
+
this.params = [];
|
|
40
|
+
}
|
|
41
|
+
addParam(value) {
|
|
42
|
+
this.params.push(value);
|
|
43
|
+
if (this.dialect === "postgresql") {
|
|
44
|
+
return `$${++this.paramIndex}`;
|
|
45
|
+
}
|
|
46
|
+
this.paramIndex++;
|
|
47
|
+
return "?";
|
|
48
|
+
}
|
|
49
|
+
compileSelect(state) {
|
|
50
|
+
this.reset();
|
|
51
|
+
const parts = [];
|
|
52
|
+
const cols = state.selects.length > 0 ? state.selects.join(", ") : "*";
|
|
53
|
+
const distinct = state.distinct ? "DISTINCT " : "";
|
|
54
|
+
parts.push(`SELECT ${distinct}${cols}`);
|
|
55
|
+
const alias = state.alias ? ` AS ${state.alias}` : "";
|
|
56
|
+
parts.push(`FROM ${state.table}${alias}`);
|
|
57
|
+
for (const j of state.joins) {
|
|
58
|
+
parts.push(`${j.type} JOIN ${j.table} ON ${j.on}`);
|
|
59
|
+
}
|
|
60
|
+
if (state.wheres.length > 0) {
|
|
61
|
+
parts.push(`WHERE ${this.compileWheres(state.wheres)}`);
|
|
62
|
+
}
|
|
63
|
+
if (state.groupBys.length > 0) {
|
|
64
|
+
parts.push(`GROUP BY ${state.groupBys.join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
if (state.havings.length > 0) {
|
|
67
|
+
parts.push(`HAVING ${state.havings.join(" AND ")}`);
|
|
68
|
+
}
|
|
69
|
+
if (state.orders.length > 0) {
|
|
70
|
+
const orderStr = state.orders.map((o) => `${o.column} ${o.direction}`).join(", ");
|
|
71
|
+
parts.push(`ORDER BY ${orderStr}`);
|
|
72
|
+
}
|
|
73
|
+
if (state.limitVal !== undefined) {
|
|
74
|
+
parts.push(`LIMIT ${this.addParam(state.limitVal)}`);
|
|
75
|
+
}
|
|
76
|
+
if (state.offsetVal !== undefined) {
|
|
77
|
+
parts.push(`OFFSET ${this.addParam(state.offsetVal)}`);
|
|
78
|
+
}
|
|
79
|
+
if (state.lockMode === "update")
|
|
80
|
+
parts.push("FOR UPDATE");
|
|
81
|
+
if (state.lockMode === "share")
|
|
82
|
+
parts.push("FOR SHARE");
|
|
83
|
+
return { sql: parts.join(" "), params: [...this.params] };
|
|
84
|
+
}
|
|
85
|
+
compileWheres(wheres) {
|
|
86
|
+
return wheres.map((w, i) => {
|
|
87
|
+
const prefix = i === 0 ? "" : `${w.type.toUpperCase()} `;
|
|
88
|
+
if (w.raw !== undefined) {
|
|
89
|
+
if (w.rawParams) {
|
|
90
|
+
for (const p of w.rawParams) {
|
|
91
|
+
this.params.push(p);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return `${prefix}(${w.raw})`;
|
|
95
|
+
}
|
|
96
|
+
if (w.nested) {
|
|
97
|
+
return `${prefix}(${this.compileWheres(w.nested)})`;
|
|
98
|
+
}
|
|
99
|
+
if (w.operator === "IN" && Array.isArray(w.value)) {
|
|
100
|
+
const placeholders = w.value.map((v) => this.addParam(v)).join(", ");
|
|
101
|
+
return `${prefix}${w.column} IN (${placeholders})`;
|
|
102
|
+
}
|
|
103
|
+
if (w.operator === "NOT IN" && Array.isArray(w.value)) {
|
|
104
|
+
const placeholders = w.value.map((v) => this.addParam(v)).join(", ");
|
|
105
|
+
return `${prefix}${w.column} NOT IN (${placeholders})`;
|
|
106
|
+
}
|
|
107
|
+
if (w.operator === "IS NULL") {
|
|
108
|
+
return `${prefix}${w.column} IS NULL`;
|
|
109
|
+
}
|
|
110
|
+
if (w.operator === "IS NOT NULL") {
|
|
111
|
+
return `${prefix}${w.column} IS NOT NULL`;
|
|
112
|
+
}
|
|
113
|
+
if (w.operator === "BETWEEN") {
|
|
114
|
+
const [min, max] = w.value;
|
|
115
|
+
return `${prefix}${w.column} BETWEEN ${this.addParam(min)} AND ${this.addParam(max)}`;
|
|
116
|
+
}
|
|
117
|
+
return `${prefix}${w.column} ${w.operator} ${this.addParam(w.value)}`;
|
|
118
|
+
}).join(" ");
|
|
119
|
+
}
|
|
120
|
+
compileInsert(table, data) {
|
|
121
|
+
this.reset();
|
|
122
|
+
const keys = Object.keys(data);
|
|
123
|
+
const columns = keys.join(", ");
|
|
124
|
+
const placeholders = keys.map((k) => this.addParam(data[k])).join(", ");
|
|
125
|
+
const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
|
|
126
|
+
return {
|
|
127
|
+
sql: `INSERT INTO ${table} (${columns}) VALUES (${placeholders})${returning}`,
|
|
128
|
+
params: [...this.params]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
compileBatchInsert(table, rows) {
|
|
132
|
+
this.reset();
|
|
133
|
+
if (rows.length === 0) {
|
|
134
|
+
return { sql: "", params: [] };
|
|
135
|
+
}
|
|
136
|
+
const keys = Object.keys(rows[0]);
|
|
137
|
+
const columns = keys.join(", ");
|
|
138
|
+
const valueRows = rows.map((row) => {
|
|
139
|
+
const placeholders = keys.map((k) => this.addParam(row[k])).join(", ");
|
|
140
|
+
return `(${placeholders})`;
|
|
141
|
+
});
|
|
142
|
+
const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
|
|
143
|
+
return {
|
|
144
|
+
sql: `INSERT INTO ${table} (${columns}) VALUES ${valueRows.join(", ")}${returning}`,
|
|
145
|
+
params: [...this.params]
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
compileUpdate(state, data) {
|
|
149
|
+
this.reset();
|
|
150
|
+
const sets = Object.entries(data).map(([col, val]) => `${col} = ${this.addParam(val)}`).join(", ");
|
|
151
|
+
const whereStr = state.wheres.length > 0 ? ` WHERE ${this.compileWheres(state.wheres)}` : "";
|
|
152
|
+
const returning = this.dialect === "postgresql" ? " RETURNING *" : "";
|
|
153
|
+
return {
|
|
154
|
+
sql: `UPDATE ${state.table} SET ${sets}${whereStr}${returning}`,
|
|
155
|
+
params: [...this.params]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
compileDelete(state) {
|
|
159
|
+
this.reset();
|
|
160
|
+
const whereStr = state.wheres.length > 0 ? ` WHERE ${this.compileWheres(state.wheres)}` : "";
|
|
161
|
+
return {
|
|
162
|
+
sql: `DELETE FROM ${state.table}${whereStr}`,
|
|
163
|
+
params: [...this.params]
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
compileCount(state, column = "*") {
|
|
167
|
+
this.reset();
|
|
168
|
+
const countCol = column === "*" ? "COUNT(*)" : `COUNT(${column})`;
|
|
169
|
+
const parts = [`SELECT ${countCol} AS count`];
|
|
170
|
+
const alias = state.alias ? ` AS ${state.alias}` : "";
|
|
171
|
+
parts.push(`FROM ${state.table}${alias}`);
|
|
172
|
+
for (const j of state.joins) {
|
|
173
|
+
parts.push(`${j.type} JOIN ${j.table} ON ${j.on}`);
|
|
174
|
+
}
|
|
175
|
+
if (state.wheres.length > 0) {
|
|
176
|
+
parts.push(`WHERE ${this.compileWheres(state.wheres)}`);
|
|
177
|
+
}
|
|
178
|
+
return { sql: parts.join(" "), params: [...this.params] };
|
|
179
|
+
}
|
|
180
|
+
compileExists(state) {
|
|
181
|
+
const selectCompiled = this.compileSelect({
|
|
182
|
+
...state,
|
|
183
|
+
selects: ["1"],
|
|
184
|
+
limitVal: 1
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
sql: `SELECT EXISTS(${selectCompiled.sql}) as exists`,
|
|
188
|
+
params: selectCompiled.params
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/database/orm/builder.ts
|
|
194
|
+
class OrmQueryBuilder {
|
|
195
|
+
state;
|
|
196
|
+
compiler;
|
|
197
|
+
db;
|
|
198
|
+
rowTransformer;
|
|
199
|
+
constructor(db, table, dialect) {
|
|
200
|
+
this.db = db;
|
|
201
|
+
this.compiler = new QueryCompiler(dialect || db.getDriver());
|
|
202
|
+
this.state = {
|
|
203
|
+
table,
|
|
204
|
+
selects: [],
|
|
205
|
+
wheres: [],
|
|
206
|
+
orders: [],
|
|
207
|
+
joins: [],
|
|
208
|
+
groupBys: [],
|
|
209
|
+
havings: [],
|
|
210
|
+
distinct: false
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
clone() {
|
|
214
|
+
const cloned = new OrmQueryBuilder(this.db, this.state.table);
|
|
215
|
+
cloned.state = {
|
|
216
|
+
...this.state,
|
|
217
|
+
wheres: [...this.state.wheres],
|
|
218
|
+
orders: [...this.state.orders],
|
|
219
|
+
joins: [...this.state.joins],
|
|
220
|
+
selects: [...this.state.selects],
|
|
221
|
+
groupBys: [...this.state.groupBys],
|
|
222
|
+
havings: [...this.state.havings]
|
|
223
|
+
};
|
|
224
|
+
cloned.compiler = this.compiler;
|
|
225
|
+
cloned.rowTransformer = this.rowTransformer;
|
|
226
|
+
return cloned;
|
|
227
|
+
}
|
|
228
|
+
setRowTransformer(fn) {
|
|
229
|
+
this.rowTransformer = fn;
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
select(...columns) {
|
|
233
|
+
this.state.selects = columns;
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
addSelect(...columns) {
|
|
237
|
+
this.state.selects.push(...columns);
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
distinct() {
|
|
241
|
+
this.state.distinct = true;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
where(column, operatorOrValue, value) {
|
|
245
|
+
const [operator, val] = value === undefined ? ["=", operatorOrValue] : [String(operatorOrValue), value];
|
|
246
|
+
this.state.wheres.push({
|
|
247
|
+
type: "and",
|
|
248
|
+
column,
|
|
249
|
+
operator,
|
|
250
|
+
value: val
|
|
251
|
+
});
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
orWhere(column, operatorOrValue, value) {
|
|
255
|
+
const [operator, val] = value === undefined ? ["=", operatorOrValue] : [String(operatorOrValue), value];
|
|
256
|
+
this.state.wheres.push({
|
|
257
|
+
type: "or",
|
|
258
|
+
column,
|
|
259
|
+
operator,
|
|
260
|
+
value: val
|
|
261
|
+
});
|
|
262
|
+
return this;
|
|
263
|
+
}
|
|
264
|
+
whereRaw(sql, params) {
|
|
265
|
+
this.state.wheres.push({
|
|
266
|
+
type: "and",
|
|
267
|
+
raw: sql,
|
|
268
|
+
rawParams: params
|
|
269
|
+
});
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
whereIn(column, values) {
|
|
273
|
+
this.state.wheres.push({
|
|
274
|
+
type: "and",
|
|
275
|
+
column,
|
|
276
|
+
operator: "IN",
|
|
277
|
+
value: values
|
|
278
|
+
});
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
whereNotIn(column, values) {
|
|
282
|
+
this.state.wheres.push({
|
|
283
|
+
type: "and",
|
|
284
|
+
column,
|
|
285
|
+
operator: "NOT IN",
|
|
286
|
+
value: values
|
|
287
|
+
});
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
whereNull(column) {
|
|
291
|
+
this.state.wheres.push({
|
|
292
|
+
type: "and",
|
|
293
|
+
column,
|
|
294
|
+
operator: "IS NULL"
|
|
295
|
+
});
|
|
296
|
+
return this;
|
|
297
|
+
}
|
|
298
|
+
whereNotNull(column) {
|
|
299
|
+
this.state.wheres.push({
|
|
300
|
+
type: "and",
|
|
301
|
+
column,
|
|
302
|
+
operator: "IS NOT NULL"
|
|
303
|
+
});
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
whereBetween(column, min, max) {
|
|
307
|
+
this.state.wheres.push({
|
|
308
|
+
type: "and",
|
|
309
|
+
column,
|
|
310
|
+
operator: "BETWEEN",
|
|
311
|
+
value: [min, max]
|
|
312
|
+
});
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
join(table, on, type = "INNER") {
|
|
316
|
+
this.state.joins.push({ type, table, on });
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
leftJoin(table, on) {
|
|
320
|
+
return this.join(table, on, "LEFT");
|
|
321
|
+
}
|
|
322
|
+
rightJoin(table, on) {
|
|
323
|
+
return this.join(table, on, "RIGHT");
|
|
324
|
+
}
|
|
325
|
+
crossJoin(table) {
|
|
326
|
+
this.state.joins.push({ type: "CROSS", table, on: "" });
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
groupBy(...columns) {
|
|
330
|
+
this.state.groupBys.push(...columns);
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
having(raw, params) {
|
|
334
|
+
this.state.havings.push(raw);
|
|
335
|
+
if (params) {}
|
|
336
|
+
return this;
|
|
337
|
+
}
|
|
338
|
+
orderBy(column, direction = "ASC") {
|
|
339
|
+
this.state.orders.push({ column, direction });
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
orderByDesc(column) {
|
|
343
|
+
return this.orderBy(column, "DESC");
|
|
344
|
+
}
|
|
345
|
+
limit(n) {
|
|
346
|
+
this.state.limitVal = n;
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
offset(n) {
|
|
350
|
+
this.state.offsetVal = n;
|
|
351
|
+
return this;
|
|
352
|
+
}
|
|
353
|
+
lockForShare() {
|
|
354
|
+
this.state.lockMode = "share";
|
|
355
|
+
return this;
|
|
356
|
+
}
|
|
357
|
+
lockForUpdate() {
|
|
358
|
+
this.state.lockMode = "update";
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
async get() {
|
|
362
|
+
const { sql, params } = this.compiler.compileSelect(this.state);
|
|
363
|
+
const rows = await this.db.raw(sql, params);
|
|
364
|
+
return rows.map((row) => this.rowTransformer ? this.rowTransformer(row) : row);
|
|
365
|
+
}
|
|
366
|
+
async first() {
|
|
367
|
+
const results = await this.limit(1).get();
|
|
368
|
+
return results.length > 0 ? results[0] : null;
|
|
369
|
+
}
|
|
370
|
+
async firstOrFail() {
|
|
371
|
+
const result = await this.first();
|
|
372
|
+
if (!result) {
|
|
373
|
+
throw new Error(`No record found for query`);
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
async find(id) {
|
|
378
|
+
return this.where("id", id).first();
|
|
379
|
+
}
|
|
380
|
+
async findOrFail(id) {
|
|
381
|
+
const result = await this.find(id);
|
|
382
|
+
if (!result) {
|
|
383
|
+
throw new Error(`Record with id ${id} not found`);
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
async count(column = "*") {
|
|
388
|
+
const { sql, params } = this.compiler.compileCount(this.state, column);
|
|
389
|
+
const rows = await this.db.raw(sql, params);
|
|
390
|
+
return Number(rows[0]?.count ?? 0);
|
|
391
|
+
}
|
|
392
|
+
async exists() {
|
|
393
|
+
const count = await this.count();
|
|
394
|
+
return count > 0;
|
|
395
|
+
}
|
|
396
|
+
async pluck(column) {
|
|
397
|
+
const results = await this.select(String(column)).get();
|
|
398
|
+
return results.map((row) => row[column]);
|
|
399
|
+
}
|
|
400
|
+
async value(column) {
|
|
401
|
+
const result = await this.select(String(column)).first();
|
|
402
|
+
return result ? result[column] : null;
|
|
403
|
+
}
|
|
404
|
+
async paginate(page, limit) {
|
|
405
|
+
const offset = (page - 1) * limit;
|
|
406
|
+
const [data, total] = await Promise.all([
|
|
407
|
+
this.clone().offset(offset).limit(limit).get(),
|
|
408
|
+
this.clone().count()
|
|
409
|
+
]);
|
|
410
|
+
return {
|
|
411
|
+
data,
|
|
412
|
+
total,
|
|
413
|
+
page,
|
|
414
|
+
limit,
|
|
415
|
+
totalPages: Math.ceil(total / limit)
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async insert(data) {
|
|
419
|
+
const { sql, params } = this.compiler.compileInsert(this.state.table, data);
|
|
420
|
+
if (this.db.getDriver() === "postgresql") {
|
|
421
|
+
const rows = await this.db.raw(sql, params);
|
|
422
|
+
return this.rowTransformer ? this.rowTransformer(rows[0]) : rows[0];
|
|
423
|
+
}
|
|
424
|
+
await this.db.raw(sql, params);
|
|
425
|
+
const lastId = await this.getLastInsertId();
|
|
426
|
+
const row = await this.db.raw(`SELECT * FROM ${this.state.table} WHERE id = ?`, [lastId]);
|
|
427
|
+
return this.rowTransformer ? this.rowTransformer(row[0]) : row[0];
|
|
428
|
+
}
|
|
429
|
+
async insertMany(items) {
|
|
430
|
+
const results = [];
|
|
431
|
+
for (const item of items) {
|
|
432
|
+
const result = await this.insert(item);
|
|
433
|
+
results.push(result);
|
|
434
|
+
}
|
|
435
|
+
return results;
|
|
436
|
+
}
|
|
437
|
+
async update(data) {
|
|
438
|
+
const { sql, params } = this.compiler.compileUpdate(this.state, data);
|
|
439
|
+
if (this.db.getDriver() === "postgresql") {
|
|
440
|
+
const rows = await this.db.raw(sql, params);
|
|
441
|
+
return rows.length;
|
|
442
|
+
}
|
|
443
|
+
await this.db.raw(sql, params);
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
async delete() {
|
|
447
|
+
const { sql, params } = this.compiler.compileDelete(this.state);
|
|
448
|
+
await this.db.raw(sql, params);
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
async getLastInsertId() {
|
|
452
|
+
const driver = this.db.getDriver();
|
|
453
|
+
if (driver === "sqlite") {
|
|
454
|
+
const row = await this.db.raw("SELECT last_insert_rowid() as id");
|
|
455
|
+
return row[0].id;
|
|
456
|
+
}
|
|
457
|
+
if (driver === "mysql") {
|
|
458
|
+
const row = await this.db.raw("SELECT LAST_INSERT_ID() as id");
|
|
459
|
+
return row[0].id;
|
|
460
|
+
}
|
|
461
|
+
throw new Error("Unexpected driver");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function query(db, table) {
|
|
465
|
+
return new OrmQueryBuilder(db, table);
|
|
466
|
+
}
|
|
467
|
+
// src/database/orm/casts/index.ts
|
|
468
|
+
var builtInCasts = {
|
|
469
|
+
json: {
|
|
470
|
+
get(value) {
|
|
471
|
+
if (typeof value === "string") {
|
|
472
|
+
try {
|
|
473
|
+
return JSON.parse(value);
|
|
474
|
+
} catch {
|
|
475
|
+
return value;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return value;
|
|
479
|
+
},
|
|
480
|
+
set(value) {
|
|
481
|
+
if (typeof value === "string")
|
|
482
|
+
return value;
|
|
483
|
+
return JSON.stringify(value);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
boolean: {
|
|
487
|
+
get(value) {
|
|
488
|
+
if (value === null || value === undefined)
|
|
489
|
+
return null;
|
|
490
|
+
if (typeof value === "boolean")
|
|
491
|
+
return value;
|
|
492
|
+
if (typeof value === "number")
|
|
493
|
+
return value !== 0;
|
|
494
|
+
if (typeof value === "string")
|
|
495
|
+
return value !== "0" && value !== "false";
|
|
496
|
+
return Boolean(value);
|
|
497
|
+
},
|
|
498
|
+
set(value) {
|
|
499
|
+
return value ? 1 : 0;
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
integer: {
|
|
503
|
+
get(value) {
|
|
504
|
+
return Number(value);
|
|
505
|
+
},
|
|
506
|
+
set(value) {
|
|
507
|
+
return Number(value);
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
float: {
|
|
511
|
+
get(value) {
|
|
512
|
+
return Number(value);
|
|
513
|
+
},
|
|
514
|
+
set(value) {
|
|
515
|
+
return Number(value);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
date: {
|
|
519
|
+
get(value) {
|
|
520
|
+
if (!value)
|
|
521
|
+
return null;
|
|
522
|
+
if (value instanceof Date)
|
|
523
|
+
return value;
|
|
524
|
+
return new Date(String(value));
|
|
525
|
+
},
|
|
526
|
+
set(value) {
|
|
527
|
+
if (value instanceof Date) {
|
|
528
|
+
return value.toISOString().split("T")[0];
|
|
529
|
+
}
|
|
530
|
+
return String(value);
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
datetime: {
|
|
534
|
+
get(value) {
|
|
535
|
+
if (!value)
|
|
536
|
+
return null;
|
|
537
|
+
if (value instanceof Date)
|
|
538
|
+
return value;
|
|
539
|
+
return new Date(String(value));
|
|
540
|
+
},
|
|
541
|
+
set(value) {
|
|
542
|
+
if (value instanceof Date) {
|
|
543
|
+
return value.toISOString();
|
|
544
|
+
}
|
|
545
|
+
return String(value);
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
timestamp: {
|
|
549
|
+
get(value) {
|
|
550
|
+
if (!value)
|
|
551
|
+
return null;
|
|
552
|
+
if (value instanceof Date)
|
|
553
|
+
return value;
|
|
554
|
+
if (typeof value === "number")
|
|
555
|
+
return new Date(value);
|
|
556
|
+
return new Date(Number(value));
|
|
557
|
+
},
|
|
558
|
+
set(value) {
|
|
559
|
+
if (value instanceof Date) {
|
|
560
|
+
return value.getTime();
|
|
561
|
+
}
|
|
562
|
+
return Number(value);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
class CastRegistry {
|
|
568
|
+
static deserialize(castDef, value) {
|
|
569
|
+
if (typeof castDef === "string") {
|
|
570
|
+
const castObj = builtInCasts[castDef];
|
|
571
|
+
if (!castObj) {
|
|
572
|
+
throw new Error(`Unknown cast: ${castDef}`);
|
|
573
|
+
}
|
|
574
|
+
return castObj.get(value);
|
|
575
|
+
}
|
|
576
|
+
return castDef.get(value);
|
|
577
|
+
}
|
|
578
|
+
static serialize(castDef, value) {
|
|
579
|
+
if (typeof castDef === "string") {
|
|
580
|
+
const castObj = builtInCasts[castDef];
|
|
581
|
+
if (!castObj) {
|
|
582
|
+
throw new Error(`Unknown cast: ${castDef}`);
|
|
583
|
+
}
|
|
584
|
+
return castObj.set(value);
|
|
585
|
+
}
|
|
586
|
+
return castDef.set(value);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/database/orm/model-registry.ts
|
|
591
|
+
var registry = new Map;
|
|
592
|
+
var defaultDb = null;
|
|
593
|
+
function setDefaultDatabase(db) {
|
|
594
|
+
defaultDb = db;
|
|
595
|
+
}
|
|
596
|
+
function getDefaultDatabase() {
|
|
597
|
+
if (!defaultDb) {
|
|
598
|
+
throw new Error("No default database configured. Call setDefaultDatabase() before using models.");
|
|
599
|
+
}
|
|
600
|
+
return defaultDb;
|
|
601
|
+
}
|
|
602
|
+
function registerModelDatabase(modelName, db) {
|
|
603
|
+
registry.set(modelName, db);
|
|
604
|
+
}
|
|
605
|
+
function getModelDatabase(modelName) {
|
|
606
|
+
return registry.get(modelName) ?? getDefaultDatabase();
|
|
607
|
+
}
|
|
608
|
+
function clearModelDatabaseRegistry() {
|
|
609
|
+
registry.clear();
|
|
610
|
+
}
|
|
611
|
+
function clearDefaultDatabase() {
|
|
612
|
+
defaultDb = null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/database/orm/relationships/base.ts
|
|
616
|
+
class Relationship {
|
|
617
|
+
parentModel;
|
|
618
|
+
relatedClass;
|
|
619
|
+
foreignKey;
|
|
620
|
+
localKey;
|
|
621
|
+
query;
|
|
622
|
+
db;
|
|
623
|
+
constructor(parentModel, relatedClass, foreignKey, localKey = "id") {
|
|
624
|
+
this.parentModel = parentModel;
|
|
625
|
+
this.relatedClass = relatedClass;
|
|
626
|
+
this.foreignKey = foreignKey;
|
|
627
|
+
this.localKey = localKey;
|
|
628
|
+
this.db = getModelDatabase(relatedClass.name);
|
|
629
|
+
this.query = new OrmQueryBuilder(this.db, relatedClass.table);
|
|
630
|
+
this.addConstraints();
|
|
631
|
+
}
|
|
632
|
+
initConstraints() {
|
|
633
|
+
this.query = new OrmQueryBuilder(this.db, this.relatedClass.table);
|
|
634
|
+
this.addConstraints();
|
|
635
|
+
}
|
|
636
|
+
resetQuery() {
|
|
637
|
+
this.query = new OrmQueryBuilder(this.db, this.relatedClass.table);
|
|
638
|
+
}
|
|
639
|
+
where(column, operator, value) {
|
|
640
|
+
this.query.where(column, operator, value);
|
|
641
|
+
return this;
|
|
642
|
+
}
|
|
643
|
+
orWhere(column, operator, value) {
|
|
644
|
+
this.query.orWhere(column, operator, value);
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
orderBy(column, direction) {
|
|
648
|
+
this.query.orderBy(column, direction);
|
|
649
|
+
return this;
|
|
650
|
+
}
|
|
651
|
+
limit(n) {
|
|
652
|
+
this.query.limit(n);
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
async get() {
|
|
656
|
+
const rows = await this.query.get();
|
|
657
|
+
return this.relatedClass.hydrate(rows);
|
|
658
|
+
}
|
|
659
|
+
async first() {
|
|
660
|
+
const rows = await this.query.limit(1).get();
|
|
661
|
+
if (rows.length === 0)
|
|
662
|
+
return null;
|
|
663
|
+
return this.relatedClass.hydrate([rows[0]])[0];
|
|
664
|
+
}
|
|
665
|
+
async count() {
|
|
666
|
+
return this.query.count();
|
|
667
|
+
}
|
|
668
|
+
async exists() {
|
|
669
|
+
return this.query.exists();
|
|
670
|
+
}
|
|
671
|
+
async create(data) {
|
|
672
|
+
const model_data = {
|
|
673
|
+
...data,
|
|
674
|
+
[this.foreignKey]: this.parentModel.getAttribute(this.localKey)
|
|
675
|
+
};
|
|
676
|
+
return this.relatedClass.create(model_data);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// src/database/orm/relationships/has-one.ts
|
|
680
|
+
class HasOne extends Relationship {
|
|
681
|
+
addConstraints() {
|
|
682
|
+
const parentId = this.parentModel.getAttribute(this.localKey);
|
|
683
|
+
this.query.where(this.foreignKey, parentId);
|
|
684
|
+
}
|
|
685
|
+
addEagerConstraints(parents) {
|
|
686
|
+
const ids = parents.map((p) => p.getAttribute(this.localKey));
|
|
687
|
+
this.query.whereIn(this.foreignKey, ids);
|
|
688
|
+
}
|
|
689
|
+
match(parents, results, relation) {
|
|
690
|
+
const grouped = new Map;
|
|
691
|
+
for (const result of results) {
|
|
692
|
+
const key = result.getAttribute(this.foreignKey);
|
|
693
|
+
grouped.set(key, result);
|
|
694
|
+
}
|
|
695
|
+
for (const parent of parents) {
|
|
696
|
+
const key = parent.getAttribute(this.localKey);
|
|
697
|
+
const related = grouped.get(key) ?? null;
|
|
698
|
+
parent._relations.set(relation, related);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async getResults() {
|
|
702
|
+
return this.first();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
// src/database/orm/relationships/has-many.ts
|
|
706
|
+
class HasMany extends Relationship {
|
|
707
|
+
addConstraints() {
|
|
708
|
+
const parentId = this.parentModel.getAttribute(this.localKey);
|
|
709
|
+
this.query.where(this.foreignKey, parentId);
|
|
710
|
+
}
|
|
711
|
+
addEagerConstraints(parents) {
|
|
712
|
+
const ids = parents.map((p) => p.getAttribute(this.localKey));
|
|
713
|
+
this.query.whereIn(this.foreignKey, ids);
|
|
714
|
+
}
|
|
715
|
+
match(parents, results, relation) {
|
|
716
|
+
const grouped = new Map;
|
|
717
|
+
for (const result of results) {
|
|
718
|
+
const key = result.getAttribute(this.foreignKey);
|
|
719
|
+
if (!grouped.has(key)) {
|
|
720
|
+
grouped.set(key, []);
|
|
721
|
+
}
|
|
722
|
+
grouped.get(key).push(result);
|
|
723
|
+
}
|
|
724
|
+
for (const parent of parents) {
|
|
725
|
+
const key = parent.getAttribute(this.localKey);
|
|
726
|
+
const related = grouped.get(key) ?? [];
|
|
727
|
+
parent._relations.set(relation, related);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async getResults() {
|
|
731
|
+
return this.get();
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// src/database/orm/relationships/belongs-to.ts
|
|
735
|
+
class BelongsTo extends Relationship {
|
|
736
|
+
constructor(parentModel, relatedClass, foreignKey, ownerKey = "id") {
|
|
737
|
+
super(parentModel, relatedClass, foreignKey, ownerKey);
|
|
738
|
+
}
|
|
739
|
+
addConstraints() {
|
|
740
|
+
const parentForeignId = this.parentModel.getAttribute(this.foreignKey);
|
|
741
|
+
this.query.where(this.localKey, parentForeignId);
|
|
742
|
+
}
|
|
743
|
+
addEagerConstraints(parents) {
|
|
744
|
+
const ids = parents.map((p) => p.getAttribute(this.foreignKey));
|
|
745
|
+
this.query.whereIn(this.localKey, ids);
|
|
746
|
+
}
|
|
747
|
+
match(parents, results, relation) {
|
|
748
|
+
const grouped = new Map;
|
|
749
|
+
for (const result of results) {
|
|
750
|
+
const key = result.getAttribute(this.localKey);
|
|
751
|
+
grouped.set(key, result);
|
|
752
|
+
}
|
|
753
|
+
for (const parent of parents) {
|
|
754
|
+
const key = parent.getAttribute(this.foreignKey);
|
|
755
|
+
const related = grouped.get(key) ?? null;
|
|
756
|
+
parent._relations.set(relation, related);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
async getResults() {
|
|
760
|
+
return this.first();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// src/database/orm/relationships/belongs-to-many.ts
|
|
764
|
+
class BelongsToMany extends Relationship {
|
|
765
|
+
pivotTable;
|
|
766
|
+
foreignPivotKey;
|
|
767
|
+
relatedPivotKey;
|
|
768
|
+
parentKey;
|
|
769
|
+
relatedKey;
|
|
770
|
+
pivotData = new Set;
|
|
771
|
+
constructor(parentModel, relatedClass, pivotTable, foreignPivotKey, relatedPivotKey, parentKey = "id", relatedKey = "id") {
|
|
772
|
+
super(parentModel, relatedClass, foreignPivotKey, parentKey);
|
|
773
|
+
this.pivotTable = pivotTable;
|
|
774
|
+
this.foreignPivotKey = foreignPivotKey;
|
|
775
|
+
this.relatedPivotKey = relatedPivotKey;
|
|
776
|
+
this.parentKey = parentKey;
|
|
777
|
+
this.relatedKey = relatedKey;
|
|
778
|
+
this.initConstraints();
|
|
779
|
+
}
|
|
780
|
+
addConstraints() {
|
|
781
|
+
if (!this.pivotTable)
|
|
782
|
+
return;
|
|
783
|
+
const parentId = this.parentModel.getAttribute(this.parentKey);
|
|
784
|
+
this.query.join(this.pivotTable, `${this.pivotTable}.${this.relatedPivotKey} = ${this.relatedClass.table}.${this.relatedKey}`);
|
|
785
|
+
this.query.where(`${this.pivotTable}.${this.foreignPivotKey}`, parentId);
|
|
786
|
+
}
|
|
787
|
+
addEagerConstraints(parents) {
|
|
788
|
+
const ids = parents.map((p) => p.getAttribute(this.parentKey));
|
|
789
|
+
this.query.whereIn(`${this.pivotTable}.${this.foreignPivotKey}`, ids);
|
|
790
|
+
}
|
|
791
|
+
match(parents, results, relation) {
|
|
792
|
+
const grouped = new Map;
|
|
793
|
+
for (const result of results) {
|
|
794
|
+
const parentId = result.getAttribute(this.foreignPivotKey);
|
|
795
|
+
if (!grouped.has(parentId)) {
|
|
796
|
+
grouped.set(parentId, []);
|
|
797
|
+
}
|
|
798
|
+
grouped.get(parentId).push(result);
|
|
799
|
+
}
|
|
800
|
+
for (const parent of parents) {
|
|
801
|
+
const key = parent.getAttribute(this.parentKey);
|
|
802
|
+
const related = grouped.get(key) ?? [];
|
|
803
|
+
parent._relations.set(relation, related);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
withPivot(...columns) {
|
|
807
|
+
for (const col of columns) {
|
|
808
|
+
this.pivotData.add(col);
|
|
809
|
+
}
|
|
810
|
+
return this;
|
|
811
|
+
}
|
|
812
|
+
async attach(ids, pivotData) {
|
|
813
|
+
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
814
|
+
const parentId = this.parentModel.getAttribute(this.parentKey);
|
|
815
|
+
const db = getModelDatabase(this.relatedClass.name);
|
|
816
|
+
for (const id of idArray) {
|
|
817
|
+
const data = {
|
|
818
|
+
[this.foreignPivotKey]: parentId,
|
|
819
|
+
[this.relatedPivotKey]: id,
|
|
820
|
+
...pivotData
|
|
821
|
+
};
|
|
822
|
+
const columns = Object.keys(data).join(", ");
|
|
823
|
+
const placeholders = Object.keys(data).map(() => "?").join(", ");
|
|
824
|
+
const values = Object.values(data);
|
|
825
|
+
await db.raw(`INSERT INTO ${this.pivotTable} (${columns}) VALUES (${placeholders})`, values);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async detach(ids) {
|
|
829
|
+
const parentId = this.parentModel.getAttribute(this.parentKey);
|
|
830
|
+
const db = getModelDatabase(this.relatedClass.name);
|
|
831
|
+
let builder = new OrmQueryBuilder(db, this.pivotTable).where(this.foreignPivotKey, parentId);
|
|
832
|
+
if (ids) {
|
|
833
|
+
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
834
|
+
builder = builder.whereIn(this.relatedPivotKey, idArray);
|
|
835
|
+
}
|
|
836
|
+
await builder.delete();
|
|
837
|
+
}
|
|
838
|
+
async sync(ids, detaching = true) {
|
|
839
|
+
if (detaching) {
|
|
840
|
+
await this.detach();
|
|
841
|
+
}
|
|
842
|
+
await this.attach(ids);
|
|
843
|
+
}
|
|
844
|
+
async toggle(ids) {
|
|
845
|
+
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
846
|
+
const attached = await this.get();
|
|
847
|
+
const attachedIds = attached.map((m) => m.getAttribute(this.relatedKey));
|
|
848
|
+
const toAttach = idArray.filter((id) => !attachedIds.includes(id));
|
|
849
|
+
const toDetach = attachedIds.filter((id) => idArray.includes(id));
|
|
850
|
+
await Promise.all([this.attach(toAttach), this.detach(toDetach)]);
|
|
851
|
+
}
|
|
852
|
+
async updateExistingPivot(id, data) {
|
|
853
|
+
const parentId = this.parentModel.getAttribute(this.parentKey);
|
|
854
|
+
const db = getModelDatabase(this.relatedClass.name);
|
|
855
|
+
await new OrmQueryBuilder(db, this.pivotTable).where(this.foreignPivotKey, parentId).where(this.relatedPivotKey, id).update(data);
|
|
856
|
+
}
|
|
857
|
+
async getResults() {
|
|
858
|
+
return this.get();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
// src/database/orm/model.ts
|
|
862
|
+
class ModelNotFoundError extends Error {
|
|
863
|
+
constructor(message) {
|
|
864
|
+
super(message);
|
|
865
|
+
this.name = "ModelNotFoundError";
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
class ModelOperationAbortedError extends Error {
|
|
870
|
+
constructor(message) {
|
|
871
|
+
super(message);
|
|
872
|
+
this.name = "ModelOperationAbortedError";
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
class Model {
|
|
877
|
+
static table;
|
|
878
|
+
static primaryKey = "id";
|
|
879
|
+
static timestamps = true;
|
|
880
|
+
static createdAtColumn = "created_at";
|
|
881
|
+
static updatedAtColumn = "updated_at";
|
|
882
|
+
static softDeletes = false;
|
|
883
|
+
static deletedAtColumn = "deleted_at";
|
|
884
|
+
static fillable = [];
|
|
885
|
+
static guarded = [];
|
|
886
|
+
static casts = {};
|
|
887
|
+
static scopeRegistry = new Map;
|
|
888
|
+
static hookRegistry = new Map;
|
|
889
|
+
_attributes = {};
|
|
890
|
+
_original = {};
|
|
891
|
+
_relations = new Map;
|
|
892
|
+
_exists = false;
|
|
893
|
+
_isDirty = false;
|
|
894
|
+
constructor(attributes) {
|
|
895
|
+
if (attributes) {
|
|
896
|
+
this.fill(attributes);
|
|
897
|
+
}
|
|
898
|
+
return new Proxy(this, {
|
|
899
|
+
get: (target, prop) => {
|
|
900
|
+
if (prop.startsWith("_")) {
|
|
901
|
+
return target[prop];
|
|
902
|
+
}
|
|
903
|
+
if (target._relations.has(prop)) {
|
|
904
|
+
return target._relations.get(prop);
|
|
905
|
+
}
|
|
906
|
+
if (typeof target[prop] === "function") {
|
|
907
|
+
return target[prop];
|
|
908
|
+
}
|
|
909
|
+
return target.getAttribute(prop);
|
|
910
|
+
},
|
|
911
|
+
set: (target, prop, value) => {
|
|
912
|
+
if (prop.startsWith("_") || prop in Object.getPrototypeOf(target)) {
|
|
913
|
+
target[prop] = value;
|
|
914
|
+
return true;
|
|
915
|
+
}
|
|
916
|
+
target.setAttribute(prop, value);
|
|
917
|
+
return true;
|
|
918
|
+
},
|
|
919
|
+
has: (target, prop) => {
|
|
920
|
+
if (prop.startsWith("_") || prop in Object.getPrototypeOf(target)) {
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
if (target._relations.has(prop)) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
return target._attributes.hasOwnProperty(prop);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
static query() {
|
|
931
|
+
return new ModelQueryBuilder(this);
|
|
932
|
+
}
|
|
933
|
+
static async find(id) {
|
|
934
|
+
return this.query().where("id", id).first();
|
|
935
|
+
}
|
|
936
|
+
static async findOrFail(id) {
|
|
937
|
+
const result = await this.find(id);
|
|
938
|
+
if (!result) {
|
|
939
|
+
throw new ModelNotFoundError(`${this.name} with id ${id} not found`);
|
|
940
|
+
}
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
static where(column, operator, value) {
|
|
944
|
+
return this.query().where(column, operator, value);
|
|
945
|
+
}
|
|
946
|
+
static async all() {
|
|
947
|
+
return this.query().get();
|
|
948
|
+
}
|
|
949
|
+
static async create(data) {
|
|
950
|
+
const instance = new this;
|
|
951
|
+
instance.fill(data);
|
|
952
|
+
await instance.save();
|
|
953
|
+
return instance;
|
|
954
|
+
}
|
|
955
|
+
static async firstOrCreate(conditions, values) {
|
|
956
|
+
let instance = await this.query();
|
|
957
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
958
|
+
instance = instance.where(key, value);
|
|
959
|
+
}
|
|
960
|
+
const found = await instance.first();
|
|
961
|
+
if (found)
|
|
962
|
+
return found;
|
|
963
|
+
const create_data = { ...conditions, ...values };
|
|
964
|
+
return this.create(create_data);
|
|
965
|
+
}
|
|
966
|
+
static async updateOrCreate(conditions, values) {
|
|
967
|
+
let query2 = this.query();
|
|
968
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
969
|
+
query2 = query2.where(key, value);
|
|
970
|
+
}
|
|
971
|
+
const found = await query2.first();
|
|
972
|
+
if (found) {
|
|
973
|
+
await found.fill(values).save();
|
|
974
|
+
return found;
|
|
975
|
+
}
|
|
976
|
+
return this.create({ ...conditions, ...values });
|
|
977
|
+
}
|
|
978
|
+
getAttribute(key) {
|
|
979
|
+
return this._attributes[key];
|
|
980
|
+
}
|
|
981
|
+
setAttribute(key, value) {
|
|
982
|
+
this._attributes[key] = value;
|
|
983
|
+
this._isDirty = true;
|
|
984
|
+
}
|
|
985
|
+
fill(data) {
|
|
986
|
+
const guarded = this.constructor.guarded;
|
|
987
|
+
const fillable = this.constructor.fillable;
|
|
988
|
+
for (const [key, value] of Object.entries(data)) {
|
|
989
|
+
if (guarded.includes("*"))
|
|
990
|
+
break;
|
|
991
|
+
if (guarded.includes(key))
|
|
992
|
+
continue;
|
|
993
|
+
if (fillable.length > 0 && !fillable.includes(key))
|
|
994
|
+
continue;
|
|
995
|
+
this.setAttribute(key, value);
|
|
996
|
+
}
|
|
997
|
+
return this;
|
|
998
|
+
}
|
|
999
|
+
forceFill(data) {
|
|
1000
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1001
|
+
this.setAttribute(key, value);
|
|
1002
|
+
}
|
|
1003
|
+
return this;
|
|
1004
|
+
}
|
|
1005
|
+
toJSON() {
|
|
1006
|
+
return { ...this._attributes };
|
|
1007
|
+
}
|
|
1008
|
+
toObject() {
|
|
1009
|
+
return { ...this._attributes };
|
|
1010
|
+
}
|
|
1011
|
+
isDirty(key) {
|
|
1012
|
+
if (key) {
|
|
1013
|
+
return this._attributes[key] !== this._original[key];
|
|
1014
|
+
}
|
|
1015
|
+
return this._isDirty;
|
|
1016
|
+
}
|
|
1017
|
+
isClean(key) {
|
|
1018
|
+
return !this.isDirty(key);
|
|
1019
|
+
}
|
|
1020
|
+
getDirty() {
|
|
1021
|
+
const dirty = {};
|
|
1022
|
+
for (const [key, value] of Object.entries(this._attributes)) {
|
|
1023
|
+
if (value !== this._original[key]) {
|
|
1024
|
+
dirty[key] = value;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return dirty;
|
|
1028
|
+
}
|
|
1029
|
+
getOriginal(key) {
|
|
1030
|
+
if (key) {
|
|
1031
|
+
return this._original[key];
|
|
1032
|
+
}
|
|
1033
|
+
return { ...this._original };
|
|
1034
|
+
}
|
|
1035
|
+
async save() {
|
|
1036
|
+
const modelClass = this.constructor;
|
|
1037
|
+
const db = getModelDatabase(modelClass.name);
|
|
1038
|
+
if (this._exists) {
|
|
1039
|
+
if (!this.isDirty())
|
|
1040
|
+
return;
|
|
1041
|
+
const dirty = this.getDirty();
|
|
1042
|
+
if (modelClass.timestamps) {
|
|
1043
|
+
const now = new Date().toISOString();
|
|
1044
|
+
dirty[modelClass.updatedAtColumn] = now;
|
|
1045
|
+
this._attributes[modelClass.updatedAtColumn] = now;
|
|
1046
|
+
}
|
|
1047
|
+
const builder = new OrmQueryBuilder(db, modelClass.table);
|
|
1048
|
+
builder.where(modelClass.primaryKey, this.getAttribute(modelClass.primaryKey));
|
|
1049
|
+
await builder.update(dirty);
|
|
1050
|
+
this._original = { ...this._attributes };
|
|
1051
|
+
this._isDirty = false;
|
|
1052
|
+
} else {
|
|
1053
|
+
const data = { ...this._attributes };
|
|
1054
|
+
if (modelClass.timestamps) {
|
|
1055
|
+
const now = new Date().toISOString();
|
|
1056
|
+
data[modelClass.createdAtColumn] = now;
|
|
1057
|
+
data[modelClass.updatedAtColumn] = now;
|
|
1058
|
+
}
|
|
1059
|
+
const builder = new OrmQueryBuilder(db, modelClass.table);
|
|
1060
|
+
const result = await builder.insert(data);
|
|
1061
|
+
this._attributes = result;
|
|
1062
|
+
this._original = { ...result };
|
|
1063
|
+
this._exists = true;
|
|
1064
|
+
this._isDirty = false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
async delete() {
|
|
1068
|
+
const modelClass = this.constructor;
|
|
1069
|
+
const db = getModelDatabase(modelClass.name);
|
|
1070
|
+
if (modelClass.softDeletes) {
|
|
1071
|
+
this.setAttribute(modelClass.deletedAtColumn, new Date().toISOString());
|
|
1072
|
+
await this.save();
|
|
1073
|
+
} else {
|
|
1074
|
+
const builder = new OrmQueryBuilder(db, modelClass.table);
|
|
1075
|
+
builder.where(modelClass.primaryKey, this.getAttribute(modelClass.primaryKey));
|
|
1076
|
+
await builder.delete();
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
async restore() {
|
|
1080
|
+
const modelClass = this.constructor;
|
|
1081
|
+
if (!modelClass.softDeletes) {
|
|
1082
|
+
throw new Error(`Model ${modelClass.name} does not use soft deletes`);
|
|
1083
|
+
}
|
|
1084
|
+
this.setAttribute(modelClass.deletedAtColumn, null);
|
|
1085
|
+
await this.save();
|
|
1086
|
+
}
|
|
1087
|
+
async refresh() {
|
|
1088
|
+
const modelClass = this.constructor;
|
|
1089
|
+
const id = this.getAttribute(modelClass.primaryKey);
|
|
1090
|
+
const fresh = await modelClass.query().find(id);
|
|
1091
|
+
if (fresh) {
|
|
1092
|
+
this._attributes = fresh._attributes;
|
|
1093
|
+
this._original = { ...fresh._attributes };
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
async fresh() {
|
|
1097
|
+
const modelClass = this.constructor;
|
|
1098
|
+
const id = this.getAttribute(modelClass.primaryKey);
|
|
1099
|
+
return modelClass.query().find(id);
|
|
1100
|
+
}
|
|
1101
|
+
hasOne(relatedClass, foreignKey, localKey) {
|
|
1102
|
+
return new HasOne(this, relatedClass, foreignKey, localKey ?? "id");
|
|
1103
|
+
}
|
|
1104
|
+
hasMany(relatedClass, foreignKey, localKey) {
|
|
1105
|
+
return new HasMany(this, relatedClass, foreignKey, localKey ?? "id");
|
|
1106
|
+
}
|
|
1107
|
+
belongsTo(relatedClass, foreignKey, ownerKey) {
|
|
1108
|
+
return new BelongsTo(this, relatedClass, foreignKey, ownerKey ?? "id");
|
|
1109
|
+
}
|
|
1110
|
+
belongsToMany(relatedClass, pivotTable, foreignPivotKey, relatedPivotKey, parentKey, relatedKey) {
|
|
1111
|
+
return new BelongsToMany(this, relatedClass, pivotTable, foreignPivotKey, relatedPivotKey, parentKey ?? "id", relatedKey ?? "id");
|
|
1112
|
+
}
|
|
1113
|
+
static hydrate(rows) {
|
|
1114
|
+
return rows.map((row) => {
|
|
1115
|
+
const instance = new this;
|
|
1116
|
+
instance._attributes = {};
|
|
1117
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1118
|
+
const castDef = this.casts[key];
|
|
1119
|
+
instance._attributes[key] = castDef ? CastRegistry.deserialize(castDef, value) : value;
|
|
1120
|
+
}
|
|
1121
|
+
instance._original = { ...instance._attributes };
|
|
1122
|
+
instance._exists = true;
|
|
1123
|
+
return instance;
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
static on(hookName, callback) {
|
|
1127
|
+
if (!Model.hookRegistry.has(this.name)) {
|
|
1128
|
+
Model.hookRegistry.set(this.name, new Map);
|
|
1129
|
+
}
|
|
1130
|
+
const registry2 = Model.hookRegistry.get(this.name);
|
|
1131
|
+
if (!registry2.has(hookName)) {
|
|
1132
|
+
registry2.set(hookName, []);
|
|
1133
|
+
}
|
|
1134
|
+
registry2.get(hookName).push(callback);
|
|
1135
|
+
}
|
|
1136
|
+
static getHookCallbacks(hookName) {
|
|
1137
|
+
const registry2 = Model.hookRegistry.get(this.name);
|
|
1138
|
+
return registry2?.get(hookName) ?? [];
|
|
1139
|
+
}
|
|
1140
|
+
static booting() {}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
class ModelQueryBuilder extends OrmQueryBuilder {
|
|
1144
|
+
modelClass;
|
|
1145
|
+
eagerLoads = new Map;
|
|
1146
|
+
constructor(modelClass) {
|
|
1147
|
+
const db = getModelDatabase(modelClass.name);
|
|
1148
|
+
super(db, modelClass.table);
|
|
1149
|
+
this.modelClass = modelClass;
|
|
1150
|
+
}
|
|
1151
|
+
with(relation, callback) {
|
|
1152
|
+
this.eagerLoads.set(relation, callback ?? null);
|
|
1153
|
+
return this;
|
|
1154
|
+
}
|
|
1155
|
+
async get() {
|
|
1156
|
+
const rows = await super.get();
|
|
1157
|
+
const models = this.modelClass.hydrate(rows);
|
|
1158
|
+
for (const [relation, callback] of this.eagerLoads) {
|
|
1159
|
+
await this.loadRelation(models, relation, callback);
|
|
1160
|
+
}
|
|
1161
|
+
return models;
|
|
1162
|
+
}
|
|
1163
|
+
async first() {
|
|
1164
|
+
const rows = await OrmQueryBuilder.prototype.get.call(this);
|
|
1165
|
+
if (rows.length === 0)
|
|
1166
|
+
return null;
|
|
1167
|
+
const row = rows[0];
|
|
1168
|
+
const models = this.modelClass.hydrate([row]);
|
|
1169
|
+
for (const [relation, callback] of this.eagerLoads) {
|
|
1170
|
+
await this.loadRelation(models, relation, callback ?? undefined);
|
|
1171
|
+
}
|
|
1172
|
+
return models[0] ?? null;
|
|
1173
|
+
}
|
|
1174
|
+
async paginate(page, limit) {
|
|
1175
|
+
const offset = (page - 1) * limit;
|
|
1176
|
+
const [data, total] = await Promise.all([
|
|
1177
|
+
this.clone().offset(offset).limit(limit).get(),
|
|
1178
|
+
this.clone().count()
|
|
1179
|
+
]);
|
|
1180
|
+
return {
|
|
1181
|
+
data,
|
|
1182
|
+
total,
|
|
1183
|
+
page,
|
|
1184
|
+
limit,
|
|
1185
|
+
totalPages: Math.ceil(total / limit)
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async firstOrCreate(conditions, values) {
|
|
1189
|
+
const query2 = new ModelQueryBuilder(this.modelClass);
|
|
1190
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
1191
|
+
query2.where(key, value);
|
|
1192
|
+
}
|
|
1193
|
+
const found = await query2.first();
|
|
1194
|
+
if (found)
|
|
1195
|
+
return found;
|
|
1196
|
+
const createData = { ...conditions, ...values };
|
|
1197
|
+
return this.modelClass.create(createData);
|
|
1198
|
+
}
|
|
1199
|
+
async updateOrCreate(conditions, values) {
|
|
1200
|
+
const query2 = new ModelQueryBuilder(this.modelClass);
|
|
1201
|
+
for (const [key, value] of Object.entries(conditions)) {
|
|
1202
|
+
query2.where(key, value);
|
|
1203
|
+
}
|
|
1204
|
+
const found = await query2.first();
|
|
1205
|
+
if (found) {
|
|
1206
|
+
found.fill(values);
|
|
1207
|
+
await found.save();
|
|
1208
|
+
return found;
|
|
1209
|
+
}
|
|
1210
|
+
const createData = { ...conditions, ...values };
|
|
1211
|
+
return this.modelClass.create(createData);
|
|
1212
|
+
}
|
|
1213
|
+
async loadRelation(models, relation, callback) {
|
|
1214
|
+
if (models.length === 0)
|
|
1215
|
+
return;
|
|
1216
|
+
const dotIndex = relation.indexOf(".");
|
|
1217
|
+
if (dotIndex !== -1) {
|
|
1218
|
+
const head = relation.substring(0, dotIndex);
|
|
1219
|
+
const tail = relation.substring(dotIndex + 1);
|
|
1220
|
+
await this.loadRelation(models, head, undefined);
|
|
1221
|
+
const intermediates = models.flatMap((m) => m._relations.get(head) ?? []);
|
|
1222
|
+
if (intermediates.length === 0)
|
|
1223
|
+
return;
|
|
1224
|
+
const intermediateModelClass = intermediates[0].constructor;
|
|
1225
|
+
const intermediateBuilder = new ModelQueryBuilder(intermediateModelClass);
|
|
1226
|
+
await intermediateBuilder.loadRelation(intermediates, tail, callback);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const representative = models[0];
|
|
1230
|
+
const relationMethod = representative[relation];
|
|
1231
|
+
if (!relationMethod || typeof relationMethod !== "function") {
|
|
1232
|
+
throw new Error(`Relation "${relation}" not found on ${this.modelClass.name}`);
|
|
1233
|
+
}
|
|
1234
|
+
const relationship = relationMethod.call(representative);
|
|
1235
|
+
relationship.resetQuery();
|
|
1236
|
+
relationship.addEagerConstraints(models);
|
|
1237
|
+
if (callback) {
|
|
1238
|
+
callback(relationship.query);
|
|
1239
|
+
}
|
|
1240
|
+
const rawRows = await relationship.query.get();
|
|
1241
|
+
const related = relationship.relatedClass.hydrate(rawRows);
|
|
1242
|
+
relationship.match(models, related, relation);
|
|
1243
|
+
}
|
|
1244
|
+
static createScoped(modelClass) {
|
|
1245
|
+
const builder = new ModelQueryBuilder(modelClass);
|
|
1246
|
+
return new Proxy(builder, {
|
|
1247
|
+
get: (target, prop) => {
|
|
1248
|
+
if (typeof prop === "string" && prop.startsWith("scope")) {
|
|
1249
|
+
const methodName = `scope${prop.charAt(5).toUpperCase()}${prop.slice(6)}`;
|
|
1250
|
+
if (typeof modelClass[methodName] === "function") {
|
|
1251
|
+
return (...args) => {
|
|
1252
|
+
return modelClass[methodName](target, ...args);
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return target[prop];
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// src/database/orm/scopes/index.ts
|
|
1262
|
+
class ScopeRegistry {
|
|
1263
|
+
globalScopes = new Map;
|
|
1264
|
+
addGlobalScope(name, scope) {
|
|
1265
|
+
this.globalScopes.set(name, scope);
|
|
1266
|
+
}
|
|
1267
|
+
removeGlobalScope(name) {
|
|
1268
|
+
this.globalScopes.delete(name);
|
|
1269
|
+
}
|
|
1270
|
+
getGlobalScopes() {
|
|
1271
|
+
return Array.from(this.globalScopes.values());
|
|
1272
|
+
}
|
|
1273
|
+
clearGlobalScopes() {
|
|
1274
|
+
this.globalScopes.clear();
|
|
1275
|
+
}
|
|
1276
|
+
hasGlobalScope(name) {
|
|
1277
|
+
return this.globalScopes.has(name);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
class SoftDeleteScope {
|
|
1282
|
+
apply(query2) {
|
|
1283
|
+
query2.whereNull("deleted_at");
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
// src/database/orm/hooks/index.ts
|
|
1287
|
+
class HookRunner {
|
|
1288
|
+
modelClass;
|
|
1289
|
+
hooks = new Map;
|
|
1290
|
+
constructor(modelClass) {
|
|
1291
|
+
this.modelClass = modelClass;
|
|
1292
|
+
this.initializeHooks();
|
|
1293
|
+
}
|
|
1294
|
+
initializeHooks() {}
|
|
1295
|
+
on(hookName, callback) {
|
|
1296
|
+
if (!this.hooks.has(hookName)) {
|
|
1297
|
+
this.hooks.set(hookName, []);
|
|
1298
|
+
}
|
|
1299
|
+
this.hooks.get(hookName).push(callback);
|
|
1300
|
+
}
|
|
1301
|
+
async run(hookName, model) {
|
|
1302
|
+
const callbacks = this.hooks.get(hookName) ?? [];
|
|
1303
|
+
for (const callback of callbacks) {
|
|
1304
|
+
const result = await callback(model);
|
|
1305
|
+
if (result === false) {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return true;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
class Model2 {
|
|
1314
|
+
static hookRegistry = new Map;
|
|
1315
|
+
static getHookRegistry(modelName) {
|
|
1316
|
+
if (!Model2.hookRegistry.has(modelName)) {
|
|
1317
|
+
Model2.hookRegistry.set(modelName, new Map);
|
|
1318
|
+
}
|
|
1319
|
+
return Model2.hookRegistry.get(modelName);
|
|
1320
|
+
}
|
|
1321
|
+
static on(hookName, callback) {
|
|
1322
|
+
const registry2 = Model2.getHookRegistry(this.name);
|
|
1323
|
+
if (!registry2.has(hookName)) {
|
|
1324
|
+
registry2.set(hookName, []);
|
|
1325
|
+
}
|
|
1326
|
+
registry2.get(hookName).push(callback);
|
|
1327
|
+
}
|
|
1328
|
+
static getHookCallbacks(hookName) {
|
|
1329
|
+
const registry2 = Model2.getHookRegistry(this.name);
|
|
1330
|
+
return registry2.get(hookName) ?? [];
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
export {
|
|
1334
|
+
setDefaultDatabase,
|
|
1335
|
+
registerModelDatabase,
|
|
1336
|
+
query,
|
|
1337
|
+
getModelDatabase,
|
|
1338
|
+
getDefaultDatabase,
|
|
1339
|
+
clearModelDatabaseRegistry,
|
|
1340
|
+
clearDefaultDatabase,
|
|
1341
|
+
SoftDeleteScope,
|
|
1342
|
+
ScopeRegistry,
|
|
1343
|
+
Relationship,
|
|
1344
|
+
QueryCompiler,
|
|
1345
|
+
OrmQueryBuilder,
|
|
1346
|
+
ModelQueryBuilder,
|
|
1347
|
+
ModelOperationAbortedError,
|
|
1348
|
+
ModelNotFoundError,
|
|
1349
|
+
Model,
|
|
1350
|
+
HookRunner,
|
|
1351
|
+
HasOne,
|
|
1352
|
+
HasMany,
|
|
1353
|
+
CastRegistry,
|
|
1354
|
+
BelongsToMany,
|
|
1355
|
+
BelongsTo
|
|
1356
|
+
};
|