arkormx 2.11.13 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +2893 -2809
- package/dist/{index-B9svndOx.d.cts → index-D7YII9Fu.d.cts} +55 -2
- package/dist/{index-zjj05F0G.d.mts → index-XrPjh7V8.d.mts} +55 -2
- package/dist/index.cjs +227 -128
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +272 -175
- package/dist/relationship/index.cjs +1 -1
- package/dist/relationship/index.d.cts +1 -1
- package/dist/relationship/index.d.mts +1 -1
- package/dist/relationship/index.mjs +1 -1
- package/dist/{relationship-DYMxYzUD.mjs → relationship-2jzLUHCB.mjs} +2745 -2729
- package/dist/{relationship-CBYK5LE3.cjs → relationship-BoQDV6gx.cjs} +2098 -2076
- package/package.json +1 -1
|
@@ -26,17 +26,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
28
|
let _h3ravel_collect_js = require("@h3ravel/collect.js");
|
|
29
|
+
let node_path = require("node:path");
|
|
30
|
+
let node_fs = require("node:fs");
|
|
31
|
+
let node_crypto = require("node:crypto");
|
|
29
32
|
let async_hooks = require("async_hooks");
|
|
30
33
|
let jiti = require("jiti");
|
|
31
34
|
let node_url = require("node:url");
|
|
32
|
-
let node_path = require("node:path");
|
|
33
35
|
let module$1 = require("module");
|
|
34
36
|
let fs = require("fs");
|
|
35
37
|
let url = require("url");
|
|
36
38
|
let path = require("path");
|
|
37
39
|
path = __toESM(path, 1);
|
|
38
|
-
let node_fs = require("node:fs");
|
|
39
|
-
let node_crypto = require("node:crypto");
|
|
40
40
|
let _h3ravel_support = require("@h3ravel/support");
|
|
41
41
|
let node_async_hooks = require("node:async_hooks");
|
|
42
42
|
let node_child_process = require("node:child_process");
|
|
@@ -92,1309 +92,981 @@ var RelationResolutionException = class extends ArkormException {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
//#endregion
|
|
95
|
-
//#region src/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
95
|
+
//#region src/helpers/migration-history.ts
|
|
96
|
+
const createEmptyAppliedMigrationsState = () => ({
|
|
97
|
+
version: 1,
|
|
98
|
+
migrations: [],
|
|
99
|
+
runs: []
|
|
100
|
+
});
|
|
101
|
+
const supportsDatabaseMigrationState = (adapter) => {
|
|
102
|
+
return typeof adapter?.readAppliedMigrationsState === "function" && typeof adapter?.writeAppliedMigrationsState === "function";
|
|
103
|
+
};
|
|
104
|
+
const resolveMigrationStateFilePath = (cwd, configuredPath) => {
|
|
105
|
+
if (configuredPath && configuredPath.trim().length > 0) return (0, node_path.resolve)(configuredPath);
|
|
106
|
+
return (0, node_path.join)(cwd, ".arkormx", "migrations.applied.json");
|
|
107
|
+
};
|
|
108
|
+
const buildMigrationIdentity = (filePath, className) => {
|
|
109
|
+
const fileName = filePath.split("/").pop()?.split("\\").pop() ?? filePath;
|
|
110
|
+
return `${fileName.slice(0, fileName.length - (0, node_path.extname)(fileName).length)}:${className}`;
|
|
111
|
+
};
|
|
112
|
+
const computeMigrationChecksum = (filePath) => {
|
|
113
|
+
if (!(0, node_fs.existsSync)(filePath)) return (0, node_crypto.createHash)("sha256").update(filePath).digest("hex");
|
|
114
|
+
const source = (0, node_fs.readFileSync)(filePath, "utf-8");
|
|
115
|
+
return (0, node_crypto.createHash)("sha256").update(source).digest("hex");
|
|
116
|
+
};
|
|
117
|
+
const readAppliedMigrationsState = (stateFilePath) => {
|
|
118
|
+
if (!(0, node_fs.existsSync)(stateFilePath)) return createEmptyAppliedMigrationsState();
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse((0, node_fs.readFileSync)(stateFilePath, "utf-8"));
|
|
121
|
+
if (!Array.isArray(parsed.migrations)) return createEmptyAppliedMigrationsState();
|
|
122
|
+
return {
|
|
123
|
+
version: 1,
|
|
124
|
+
migrations: parsed.migrations.filter((migration) => {
|
|
125
|
+
return typeof migration?.id === "string" && typeof migration?.file === "string" && typeof migration?.className === "string" && typeof migration?.appliedAt === "string" && (migration?.checksum === void 0 || typeof migration?.checksum === "string");
|
|
126
|
+
}),
|
|
127
|
+
runs: Array.isArray(parsed.runs) ? parsed.runs.filter((run) => {
|
|
128
|
+
return typeof run?.id === "string" && typeof run?.appliedAt === "string" && Array.isArray(run?.migrationIds) && run.migrationIds.every((item) => typeof item === "string");
|
|
129
|
+
}) : []
|
|
130
|
+
};
|
|
131
|
+
} catch {
|
|
132
|
+
return createEmptyAppliedMigrationsState();
|
|
126
133
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
};
|
|
135
|
+
const readAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
|
|
136
|
+
if (supportsDatabaseMigrationState(adapter)) return await adapter.readAppliedMigrationsState();
|
|
137
|
+
return readAppliedMigrationsState(stateFilePath);
|
|
138
|
+
};
|
|
139
|
+
const writeAppliedMigrationsState = (stateFilePath, state) => {
|
|
140
|
+
const directory = (0, node_path.dirname)(stateFilePath);
|
|
141
|
+
if (!(0, node_fs.existsSync)(directory)) (0, node_fs.mkdirSync)(directory, { recursive: true });
|
|
142
|
+
(0, node_fs.writeFileSync)(stateFilePath, JSON.stringify(state, null, 2));
|
|
143
|
+
};
|
|
144
|
+
const writeAppliedMigrationsStateToStore = async (adapter, stateFilePath, state) => {
|
|
145
|
+
if (supportsDatabaseMigrationState(adapter)) {
|
|
146
|
+
await adapter.writeAppliedMigrationsState(state);
|
|
147
|
+
return;
|
|
132
148
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
writeAppliedMigrationsState(stateFilePath, state);
|
|
150
|
+
};
|
|
151
|
+
const deleteAppliedMigrationsStateFromStore = async (adapter, stateFilePath) => {
|
|
152
|
+
if (supportsDatabaseMigrationState(adapter)) {
|
|
153
|
+
await adapter.writeAppliedMigrationsState(createEmptyAppliedMigrationsState());
|
|
154
|
+
return "database";
|
|
139
155
|
}
|
|
156
|
+
if (!(0, node_fs.existsSync)(stateFilePath)) return "missing-file";
|
|
157
|
+
writeAppliedMigrationsState(stateFilePath, createEmptyAppliedMigrationsState());
|
|
158
|
+
return "file";
|
|
159
|
+
};
|
|
160
|
+
const isMigrationApplied = (state, identity, checksum) => {
|
|
161
|
+
const matched = state.migrations.find((migration) => migration.id === identity);
|
|
162
|
+
if (!matched) return false;
|
|
163
|
+
if (checksum && matched.checksum) return matched.checksum === checksum;
|
|
164
|
+
if (checksum && !matched.checksum) return false;
|
|
165
|
+
return true;
|
|
166
|
+
};
|
|
167
|
+
const findAppliedMigration = (state, identity) => {
|
|
168
|
+
return state.migrations.find((migration) => migration.id === identity);
|
|
169
|
+
};
|
|
170
|
+
const markMigrationApplied = (state, entry) => {
|
|
171
|
+
const next = state.migrations.filter((migration) => migration.id !== entry.id);
|
|
172
|
+
next.push(entry);
|
|
173
|
+
return {
|
|
174
|
+
version: 1,
|
|
175
|
+
migrations: next,
|
|
176
|
+
runs: state.runs ?? []
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
const removeAppliedMigration = (state, identity) => {
|
|
180
|
+
return {
|
|
181
|
+
version: 1,
|
|
182
|
+
migrations: state.migrations.filter((migration) => migration.id !== identity),
|
|
183
|
+
runs: (state.runs ?? []).map((run) => ({
|
|
184
|
+
...run,
|
|
185
|
+
migrationIds: run.migrationIds.filter((id) => id !== identity)
|
|
186
|
+
})).filter((run) => run.migrationIds.length > 0)
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
const buildMigrationRunId = () => {
|
|
190
|
+
return `run_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
191
|
+
};
|
|
192
|
+
const markMigrationRun = (state, run) => {
|
|
193
|
+
const nextRuns = (state.runs ?? []).filter((existing) => existing.id !== run.id);
|
|
194
|
+
nextRuns.push(run);
|
|
195
|
+
return {
|
|
196
|
+
version: 1,
|
|
197
|
+
migrations: state.migrations,
|
|
198
|
+
runs: nextRuns
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const getLastMigrationRun = (state) => {
|
|
202
|
+
const runs = state.runs ?? [];
|
|
203
|
+
if (runs.length === 0) return void 0;
|
|
204
|
+
return runs.map((run, index) => ({
|
|
205
|
+
run,
|
|
206
|
+
index
|
|
207
|
+
})).sort((left, right) => {
|
|
208
|
+
const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
|
|
209
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
210
|
+
return right.index - left.index;
|
|
211
|
+
})[0]?.run;
|
|
212
|
+
};
|
|
213
|
+
const getLatestAppliedMigrations = (state, steps) => {
|
|
214
|
+
return state.migrations.map((migration, index) => ({
|
|
215
|
+
migration,
|
|
216
|
+
index
|
|
217
|
+
})).sort((left, right) => {
|
|
218
|
+
const appliedAtOrder = right.migration.appliedAt.localeCompare(left.migration.appliedAt);
|
|
219
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
220
|
+
return right.index - left.index;
|
|
221
|
+
}).slice(0, Math.max(0, steps)).map((entry) => entry.migration);
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Groups the applied migrations into batches, oldest batch first, each batch
|
|
225
|
+
* holding its migration ids in application order.
|
|
226
|
+
*
|
|
227
|
+
* A "batch" is one `migrate` run. When no runs were recorded (e.g. legacy state
|
|
228
|
+
* written before run tracking), each applied migration is treated as its own
|
|
229
|
+
* batch so `--step` still behaves predictably.
|
|
230
|
+
*
|
|
231
|
+
* @param state
|
|
232
|
+
* @returns
|
|
233
|
+
*/
|
|
234
|
+
const resolveAppliedBatches = (state) => {
|
|
235
|
+
const runs = state.runs ?? [];
|
|
236
|
+
if (runs.length > 0) return runs.map((run, index) => ({
|
|
237
|
+
run,
|
|
238
|
+
index
|
|
239
|
+
})).sort((left, right) => {
|
|
240
|
+
const appliedAtOrder = left.run.appliedAt.localeCompare(right.run.appliedAt);
|
|
241
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
242
|
+
return left.index - right.index;
|
|
243
|
+
}).map((entry) => entry.run.migrationIds);
|
|
244
|
+
return state.migrations.map((migration) => [migration.id]);
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Resolves the migrations belonging to the most recent `batches` batches, ordered
|
|
248
|
+
* for rollback — **reverse of the order they were applied** (most recent batch
|
|
249
|
+
* first, and within each batch the migration that ran `up()` last runs `down()`
|
|
250
|
+
* first), so a rollback exactly reverses how the migrations were applied.
|
|
251
|
+
*
|
|
252
|
+
* Defaults to a single batch (`migrate:rollback` with no `--step`); `--step=N`
|
|
253
|
+
* rolls back the last N batches.
|
|
254
|
+
*
|
|
255
|
+
* @param state
|
|
256
|
+
* @param batches
|
|
257
|
+
* @returns
|
|
258
|
+
*/
|
|
259
|
+
const getLastBatchMigrations = (state, batches = 1) => {
|
|
260
|
+
if (batches < 1) return [];
|
|
261
|
+
const allBatches = resolveAppliedBatches(state);
|
|
262
|
+
const selected = allBatches.slice(Math.max(0, allBatches.length - batches));
|
|
263
|
+
const byId = new Map(state.migrations.map((migration) => [migration.id, migration]));
|
|
264
|
+
return selected.reverse().flatMap((migrationIds) => [...migrationIds].reverse()).map((id) => byId.get(id)).filter((migration) => Boolean(migration));
|
|
140
265
|
};
|
|
141
266
|
|
|
142
267
|
//#endregion
|
|
143
|
-
//#region src/
|
|
268
|
+
//#region src/database/ForeignKeyBuilder.ts
|
|
144
269
|
/**
|
|
145
|
-
*
|
|
146
|
-
* a
|
|
270
|
+
* The ForeignKeyBuilder class provides a fluent interface for defining
|
|
271
|
+
* foreign key constraints in a migration. It allows you to specify
|
|
272
|
+
* the referenced table and column, as well as actions to take on
|
|
273
|
+
* delete and aliases for the relation.
|
|
147
274
|
*
|
|
148
275
|
* @author Legacy (3m1n3nc3)
|
|
149
|
-
* @since
|
|
276
|
+
* @since 0.2.2
|
|
150
277
|
*/
|
|
151
|
-
var
|
|
152
|
-
constructor(
|
|
153
|
-
this.
|
|
154
|
-
this.relations = relations;
|
|
278
|
+
var ForeignKeyBuilder = class {
|
|
279
|
+
constructor(foreignKey) {
|
|
280
|
+
this.foreignKey = foreignKey;
|
|
155
281
|
}
|
|
156
282
|
/**
|
|
157
|
-
*
|
|
283
|
+
* Defines the referenced table and column for this foreign key constraint.
|
|
158
284
|
*
|
|
285
|
+
* @param table
|
|
286
|
+
* @param column
|
|
159
287
|
* @returns
|
|
160
288
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await this.loadRelationNode(name, node);
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
async loadRelationNode(name, node) {
|
|
169
|
-
await this.loadRelation(name, node.constraint);
|
|
170
|
-
if (node.children.size === 0) return;
|
|
171
|
-
const relatedModels = this.collectLoadedRelationModels(name);
|
|
172
|
-
if (relatedModels.length === 0) return;
|
|
173
|
-
await new SetBasedEagerLoader(relatedModels, this.relationTreeToMap(node.children)).load();
|
|
289
|
+
references(table, column) {
|
|
290
|
+
this.foreignKey.referencesTable = table;
|
|
291
|
+
this.foreignKey.referencesColumn = column;
|
|
292
|
+
return this;
|
|
174
293
|
}
|
|
175
294
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
295
|
+
* Defines the action to take when a referenced record is deleted, such
|
|
296
|
+
* as "CASCADE", "SET NULL", or "RESTRICT".
|
|
178
297
|
*
|
|
179
|
-
* @param
|
|
180
|
-
* @
|
|
181
|
-
* @returns A promise that resolves when the relationship is loaded.
|
|
298
|
+
* @param action
|
|
299
|
+
* @returns
|
|
182
300
|
*/
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const metadata = resolver.call(this.models[0]).getMetadata();
|
|
187
|
-
switch (metadata.type) {
|
|
188
|
-
case "belongsTo":
|
|
189
|
-
await this.loadBelongsTo(name, resolver, metadata, constraint);
|
|
190
|
-
return;
|
|
191
|
-
case "belongsToMany":
|
|
192
|
-
await this.loadBelongsToMany(name, metadata, constraint);
|
|
193
|
-
return;
|
|
194
|
-
case "hasMany":
|
|
195
|
-
await this.loadHasMany(name, metadata, constraint);
|
|
196
|
-
return;
|
|
197
|
-
case "hasOne":
|
|
198
|
-
await this.loadHasOne(name, resolver, metadata, constraint);
|
|
199
|
-
return;
|
|
200
|
-
case "hasManyThrough":
|
|
201
|
-
await this.loadHasManyThrough(name, metadata, constraint);
|
|
202
|
-
return;
|
|
203
|
-
case "hasOneThrough":
|
|
204
|
-
await this.loadHasOneThrough(name, resolver, metadata, constraint);
|
|
205
|
-
return;
|
|
206
|
-
case "morphTo":
|
|
207
|
-
await this.loadMorphTo(name, metadata, constraint);
|
|
208
|
-
return;
|
|
209
|
-
case "morphOne":
|
|
210
|
-
await this.loadMorphOne(name, resolver, metadata, constraint);
|
|
211
|
-
return;
|
|
212
|
-
case "morphMany":
|
|
213
|
-
await this.loadMorphMany(name, metadata, constraint);
|
|
214
|
-
return;
|
|
215
|
-
case "morphToMany":
|
|
216
|
-
case "morphedByMany":
|
|
217
|
-
await this.loadPolymorphicManyToMany(name, metadata, constraint);
|
|
218
|
-
return;
|
|
219
|
-
default: await this.loadIndividually(name, resolver, constraint);
|
|
220
|
-
}
|
|
301
|
+
onDelete(action) {
|
|
302
|
+
this.foreignKey.onDelete = action;
|
|
303
|
+
return this;
|
|
221
304
|
}
|
|
222
305
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
306
|
+
* Defines an alias for the relation represented by this foreign key, which
|
|
307
|
+
* can be used in the ORM for more intuitive access to related models.
|
|
225
308
|
*
|
|
226
|
-
* @param name
|
|
227
|
-
* @returns
|
|
309
|
+
* @param name
|
|
310
|
+
* @returns
|
|
228
311
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const modelName = this.models[0].constructor?.name ?? "Model";
|
|
233
|
-
throw new RelationResolutionException(`Relation [${name}] is not defined on the model.`, {
|
|
234
|
-
operation: "eagerLoad",
|
|
235
|
-
model: modelName,
|
|
236
|
-
relation: name
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
return resolver;
|
|
240
|
-
}
|
|
241
|
-
buildRelationTree(relations) {
|
|
242
|
-
const tree = /* @__PURE__ */ new Map();
|
|
243
|
-
Object.entries(relations).forEach(([path, constraint]) => {
|
|
244
|
-
const segments = path.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
245
|
-
if (segments.length === 0) return;
|
|
246
|
-
let current = tree;
|
|
247
|
-
segments.forEach((segment, index) => {
|
|
248
|
-
const existing = current.get(segment) ?? {
|
|
249
|
-
constraint: void 0,
|
|
250
|
-
children: /* @__PURE__ */ new Map()
|
|
251
|
-
};
|
|
252
|
-
if (index === segments.length - 1 && constraint) existing.constraint = constraint;
|
|
253
|
-
current.set(segment, existing);
|
|
254
|
-
current = existing.children;
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
return tree;
|
|
258
|
-
}
|
|
259
|
-
relationTreeToMap(tree, prefix = "") {
|
|
260
|
-
return Array.from(tree.entries()).reduce((all, [name, node]) => {
|
|
261
|
-
const path = prefix ? `${prefix}.${name}` : name;
|
|
262
|
-
all[path] = node.constraint;
|
|
263
|
-
Object.assign(all, this.relationTreeToMap(node.children, path));
|
|
264
|
-
return all;
|
|
265
|
-
}, {});
|
|
266
|
-
}
|
|
267
|
-
collectLoadedRelationModels(name) {
|
|
268
|
-
return this.models.reduce((all, model) => {
|
|
269
|
-
const loaded = model.getAttribute(name);
|
|
270
|
-
if (loaded instanceof ArkormCollection) {
|
|
271
|
-
loaded.all().forEach((item) => {
|
|
272
|
-
if (this.isEagerLoadableModel(item)) all.push(item);
|
|
273
|
-
});
|
|
274
|
-
return all;
|
|
275
|
-
}
|
|
276
|
-
if (this.isEagerLoadableModel(loaded)) all.push(loaded);
|
|
277
|
-
return all;
|
|
278
|
-
}, []);
|
|
279
|
-
}
|
|
280
|
-
isEagerLoadableModel(value) {
|
|
281
|
-
return typeof value === "object" && value !== null && typeof value.getAttribute === "function" && typeof value.setLoadedRelation === "function";
|
|
312
|
+
alias(name) {
|
|
313
|
+
this.foreignKey.relationAlias = name;
|
|
314
|
+
return this;
|
|
282
315
|
}
|
|
283
316
|
/**
|
|
284
|
-
*
|
|
317
|
+
* Defines an alias for the inverse relation represented by this foreign key.
|
|
285
318
|
*
|
|
286
|
-
* @param name
|
|
287
|
-
* @
|
|
288
|
-
* @param metadata The metadata for the relationship.
|
|
289
|
-
* @param constraint An optional constraint to apply to the query.
|
|
290
|
-
* @returns A promise that resolves when the relationship is loaded.
|
|
319
|
+
* @param name
|
|
320
|
+
* @returns
|
|
291
321
|
*/
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
this.models.forEach((model) => {
|
|
296
|
-
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
297
|
-
});
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
let query = metadata.relatedModel.query().whereIn(metadata.ownerKey, keys);
|
|
301
|
-
query = this.applyConstraint(query, constraint);
|
|
302
|
-
const relatedModels = await this.getPartitionedModels(query, metadata.ownerKey, keys);
|
|
303
|
-
const relatedByOwnerKey = /* @__PURE__ */ new Map();
|
|
304
|
-
relatedModels.forEach((related) => {
|
|
305
|
-
const value = this.readModelAttribute(related, metadata.ownerKey);
|
|
306
|
-
if (value == null) return;
|
|
307
|
-
const lookupKey = this.toLookupKey(value);
|
|
308
|
-
if (!relatedByOwnerKey.has(lookupKey)) relatedByOwnerKey.set(lookupKey, related);
|
|
309
|
-
});
|
|
310
|
-
this.models.forEach((model) => {
|
|
311
|
-
const foreignValue = model.getAttribute(metadata.foreignKey);
|
|
312
|
-
const relationValue = foreignValue == null ? void 0 : relatedByOwnerKey.get(this.toLookupKey(foreignValue));
|
|
313
|
-
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
314
|
-
});
|
|
322
|
+
inverseAlias(name) {
|
|
323
|
+
this.foreignKey.inverseRelationAlias = name;
|
|
324
|
+
return this;
|
|
315
325
|
}
|
|
316
326
|
/**
|
|
317
|
-
*
|
|
327
|
+
* Defines an alias for the foreign key field itself, which can be
|
|
328
|
+
* used in the ORM for more intuitive access to the foreign key value.
|
|
318
329
|
*
|
|
319
|
-
* @param
|
|
320
|
-
* @param metadata
|
|
321
|
-
* @param constraint
|
|
330
|
+
* @param fieldName
|
|
322
331
|
* @returns
|
|
323
332
|
*/
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
as(fieldName) {
|
|
334
|
+
this.foreignKey.fieldAlias = fieldName;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/helpers/PrimaryKeyGenerationPlanner.ts
|
|
341
|
+
var PrimaryKeyGenerationPlanner = class {
|
|
342
|
+
static plan(column) {
|
|
343
|
+
if (!column.primary || column.default !== void 0) return void 0;
|
|
344
|
+
if (column.type === "uuid" || column.type === "string") return {
|
|
345
|
+
strategy: "uuid",
|
|
346
|
+
prismaDefault: "@default(uuid())",
|
|
347
|
+
databaseDefault: column.type === "uuid" ? "gen_random_uuid()" : "gen_random_uuid()::text",
|
|
348
|
+
runtimeFactory: "uuid"
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
static generate(generation) {
|
|
352
|
+
if (generation?.runtimeFactory === "uuid") return (0, node_crypto.randomUUID)();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/helpers/runtime-module-loader.ts
|
|
358
|
+
var RuntimeModuleLoader = class {
|
|
359
|
+
static async load(filePath, useDefault = false) {
|
|
360
|
+
const resolvedPath = (0, node_path.resolve)(filePath);
|
|
361
|
+
return await (0, jiti.createJiti)((0, node_url.pathToFileURL)(resolvedPath).href, {
|
|
362
|
+
interopDefault: false,
|
|
363
|
+
tsconfigPaths: true,
|
|
364
|
+
sourceMaps: true
|
|
365
|
+
}).import(resolvedPath, useDefault ? { default: true } : {});
|
|
349
366
|
}
|
|
350
367
|
/**
|
|
351
|
-
*
|
|
368
|
+
* Load many modules through a single shared jiti context, retrying files
|
|
369
|
+
* that fail until no further progress is made.
|
|
352
370
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
371
|
+
* A shared module cache resolves circular imports consistently: a file that
|
|
372
|
+
* fails because one of its dependencies was not yet cached (for example a
|
|
373
|
+
* trait whose module imports a model which imports the class that uses the
|
|
374
|
+
* trait) succeeds on a later pass, once a sibling load has populated that
|
|
375
|
+
* dependency. A fresh per-file context cannot do this, so the same set of
|
|
376
|
+
* files can load or fail depending purely on which file is the entry point.
|
|
377
|
+
*
|
|
378
|
+
* Each returned entry carries either the loaded module or the last error,
|
|
379
|
+
* so callers can surface genuine failures instead of silently dropping them.
|
|
380
|
+
*
|
|
381
|
+
* @param filePaths
|
|
382
|
+
* @param useDefault
|
|
356
383
|
* @returns
|
|
357
384
|
*/
|
|
358
|
-
async
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
});
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
const pivotRows = await this.createRelationTableLoader().selectRows({
|
|
367
|
-
table: metadata.throughTable,
|
|
368
|
-
where: this.buildBelongsToManyPivotWhere(metadata, parentKeys),
|
|
369
|
-
columns: this.getBelongsToManyPivotColumns(metadata).map((column) => ({ column }))
|
|
385
|
+
static async loadAll(filePaths, useDefault = false) {
|
|
386
|
+
const jiti$1 = (0, jiti.createJiti)(`${(0, node_url.pathToFileURL)((0, node_path.resolve)(".")).href}/`, {
|
|
387
|
+
interopDefault: false,
|
|
388
|
+
tsconfigPaths: true,
|
|
389
|
+
sourceMaps: true
|
|
370
390
|
});
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const relatedKeysByParent = /* @__PURE__ */ new Map();
|
|
389
|
-
const pivotByParentAndRelated = /* @__PURE__ */ new Map();
|
|
390
|
-
pivotRows.forEach((row) => {
|
|
391
|
-
const parentValue = row[metadata.foreignPivotKey];
|
|
392
|
-
const relatedValue = row[metadata.relatedPivotKey];
|
|
393
|
-
if (parentValue == null || relatedValue == null) return;
|
|
394
|
-
const bucket = relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
395
|
-
bucket.push(relatedValue);
|
|
396
|
-
relatedKeysByParent.set(this.toLookupKey(parentValue), bucket);
|
|
397
|
-
pivotByParentAndRelated.set(`${this.toLookupKey(parentValue)}:${this.toLookupKey(relatedValue)}`, row);
|
|
398
|
-
});
|
|
399
|
-
this.models.forEach((model) => {
|
|
400
|
-
const parentValue = model.getAttribute(metadata.parentKey);
|
|
401
|
-
const related = (parentValue == null ? [] : relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? []).reduce((all, relatedValue) => {
|
|
402
|
-
const candidate = relatedByKey.get(this.toLookupKey(relatedValue));
|
|
403
|
-
if (candidate) all.push(this.attachBelongsToManyPivot(metadata, candidate, pivotByParentAndRelated.get(`${this.toLookupKey(parentValue)}:${this.toLookupKey(relatedValue)}`)));
|
|
404
|
-
return all;
|
|
405
|
-
}, []);
|
|
406
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
async loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows) {
|
|
410
|
-
if (metadata.pivotWhere) {
|
|
411
|
-
await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
|
|
412
|
-
return true;
|
|
413
|
-
}
|
|
414
|
-
const pivotColumns = this.getBelongsToManyPivotColumns(metadata);
|
|
415
|
-
const pivotAliases = new Map(pivotColumns.map((column, index) => [column, `__arkorm_pivot_${index}`]));
|
|
416
|
-
const parentAlias = pivotAliases.get(metadata.foreignPivotKey);
|
|
417
|
-
const throughTable = metadata.throughTable;
|
|
418
|
-
const relatedTable = metadata.relatedModel.getTable();
|
|
419
|
-
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${metadata.foreignPivotKey}`, parentKeys).addSelect({ ...Object.fromEntries([...pivotAliases].map(([column, alias]) => [`${throughTable}.${column}`, alias])) }).getRowsForEagerLoad([`${throughTable}.${metadata.foreignPivotKey}`]);
|
|
420
|
-
if (!rows) {
|
|
421
|
-
await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
|
|
422
|
-
return true;
|
|
423
|
-
}
|
|
424
|
-
const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
|
|
425
|
-
const relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
|
|
426
|
-
const relatedByKey = /* @__PURE__ */ new Map();
|
|
427
|
-
relatedModels.forEach((related) => {
|
|
428
|
-
const key = this.readModelAttribute(related, metadata.relatedKey);
|
|
429
|
-
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
430
|
-
});
|
|
431
|
-
const relatedByParent = /* @__PURE__ */ new Map();
|
|
432
|
-
rows.forEach((row) => {
|
|
433
|
-
const parentKey = row[parentAlias];
|
|
434
|
-
const relatedKey = row[metadata.relatedKey];
|
|
435
|
-
if (parentKey == null || relatedKey == null) return;
|
|
436
|
-
const related = relatedByKey.get(this.toLookupKey(relatedKey));
|
|
437
|
-
if (!related) return;
|
|
438
|
-
const pivot = Object.fromEntries([...pivotAliases].map(([column, alias]) => [column, row[alias]]));
|
|
439
|
-
const bucket = relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
440
|
-
bucket.push(this.attachBelongsToManyPivot(metadata, related, pivot));
|
|
441
|
-
relatedByParent.set(this.toLookupKey(parentKey), bucket);
|
|
442
|
-
});
|
|
443
|
-
this.models.forEach((model) => {
|
|
444
|
-
const parentKey = model.getAttribute(metadata.parentKey);
|
|
445
|
-
const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
446
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
447
|
-
});
|
|
448
|
-
return true;
|
|
449
|
-
}
|
|
450
|
-
async loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows) {
|
|
451
|
-
const rowsByParent = /* @__PURE__ */ new Map();
|
|
452
|
-
pivotRows.forEach((row) => {
|
|
453
|
-
const parentKey = row[metadata.foreignPivotKey];
|
|
454
|
-
if (parentKey == null) return;
|
|
455
|
-
const bucket = rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
456
|
-
bucket.push(row);
|
|
457
|
-
rowsByParent.set(this.toLookupKey(parentKey), bucket);
|
|
458
|
-
});
|
|
459
|
-
await Promise.all(this.models.map(async (model) => {
|
|
460
|
-
const parentKey = model.getAttribute(metadata.parentKey);
|
|
461
|
-
const rows = parentKey == null ? [] : rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
462
|
-
const relatedIds = this.collectUniqueRowValues(rows, metadata.relatedPivotKey);
|
|
463
|
-
const related = relatedIds.length ? (await query.clone().whereIn(metadata.relatedKey, relatedIds).get()).all() : [];
|
|
464
|
-
const pivotByRelated = new Map(rows.map((row) => [this.toLookupKey(row[metadata.relatedPivotKey]), row]));
|
|
465
|
-
model.setLoadedRelation(name, new ArkormCollection(related.map((entry) => this.attachBelongsToManyPivot(metadata, entry, pivotByRelated.get(this.toLookupKey(this.readModelAttribute(entry, metadata.relatedKey)))))));
|
|
466
|
-
}));
|
|
467
|
-
}
|
|
468
|
-
buildBelongsToManyPivotWhere(metadata, parentKeys) {
|
|
469
|
-
const baseCondition = {
|
|
470
|
-
type: "comparison",
|
|
471
|
-
column: metadata.foreignPivotKey,
|
|
472
|
-
operator: "in",
|
|
473
|
-
value: parentKeys
|
|
474
|
-
};
|
|
475
|
-
if (!metadata.pivotWhere) return baseCondition;
|
|
476
|
-
return {
|
|
477
|
-
type: "group",
|
|
478
|
-
operator: "and",
|
|
479
|
-
conditions: [baseCondition, metadata.pivotWhere]
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
getBelongsToManyPivotColumns(metadata) {
|
|
483
|
-
return [
|
|
484
|
-
metadata.foreignPivotKey,
|
|
485
|
-
metadata.relatedPivotKey,
|
|
486
|
-
...metadata.pivotColumns ?? []
|
|
487
|
-
].filter((column, index, all) => all.indexOf(column) === index);
|
|
488
|
-
}
|
|
489
|
-
shouldAttachBelongsToManyPivot(metadata) {
|
|
490
|
-
return Boolean(metadata.pivotModel) || Boolean(metadata.pivotCreatedAtColumn) || Boolean(metadata.pivotUpdatedAtColumn) || (metadata.pivotColumns?.length ?? 0) > 0 || Boolean(metadata.pivotAccessor);
|
|
491
|
-
}
|
|
492
|
-
createBelongsToManyPivotRecord(metadata, row) {
|
|
493
|
-
const attributes = this.getBelongsToManyPivotColumns(metadata).reduce((all, column) => {
|
|
494
|
-
all[column] = row[column];
|
|
495
|
-
return all;
|
|
496
|
-
}, {});
|
|
497
|
-
if (!metadata.pivotModel) return attributes;
|
|
498
|
-
if (typeof metadata.pivotModel.hydrate === "function") return metadata.pivotModel.hydrate(attributes);
|
|
499
|
-
return new metadata.pivotModel(attributes);
|
|
500
|
-
}
|
|
501
|
-
attachBelongsToManyPivot(metadata, related, row) {
|
|
502
|
-
if (!row || !this.shouldAttachBelongsToManyPivot(metadata)) return related;
|
|
503
|
-
const rawReader = related;
|
|
504
|
-
if (typeof rawReader.getRawAttributes !== "function" || typeof rawReader.setAttribute !== "function") return related;
|
|
505
|
-
const cloned = metadata.relatedModel.hydrate(rawReader.getRawAttributes());
|
|
506
|
-
cloned.setAttribute(metadata.pivotAccessor ?? "pivot", this.createBelongsToManyPivotRecord(metadata, row));
|
|
507
|
-
return cloned;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Loads a "belongs to many" relationship for the set of models.
|
|
511
|
-
*
|
|
512
|
-
* @param name
|
|
513
|
-
* @param resolver
|
|
514
|
-
* @param metadata
|
|
515
|
-
* @param constraint
|
|
516
|
-
* @returns
|
|
517
|
-
*/
|
|
518
|
-
async loadHasOne(name, resolver, metadata, constraint) {
|
|
519
|
-
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
520
|
-
if (keys.length === 0) {
|
|
521
|
-
this.models.forEach((model) => {
|
|
522
|
-
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
523
|
-
});
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
|
|
527
|
-
query = this.applyConstraint(query, constraint);
|
|
528
|
-
const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
|
|
529
|
-
const relatedByForeignKey = /* @__PURE__ */ new Map();
|
|
530
|
-
relatedModels.forEach((related) => {
|
|
531
|
-
const value = this.readModelAttribute(related, metadata.foreignKey);
|
|
532
|
-
if (value == null) return;
|
|
533
|
-
const lookupKey = this.toLookupKey(value);
|
|
534
|
-
if (!relatedByForeignKey.has(lookupKey)) relatedByForeignKey.set(lookupKey, related);
|
|
535
|
-
});
|
|
536
|
-
this.models.forEach((model) => {
|
|
537
|
-
const localValue = model.getAttribute(metadata.localKey);
|
|
538
|
-
const relationValue = localValue == null ? void 0 : relatedByForeignKey.get(this.toLookupKey(localValue));
|
|
539
|
-
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Loads a "has many through" relationship for the set of models.
|
|
544
|
-
*
|
|
545
|
-
* @param name
|
|
546
|
-
* @param metadata
|
|
547
|
-
* @param constraint
|
|
548
|
-
* @returns
|
|
549
|
-
*/
|
|
550
|
-
async loadHasManyThrough(name, metadata, constraint) {
|
|
551
|
-
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
552
|
-
if (parentKeys.length === 0) {
|
|
553
|
-
this.models.forEach((model) => {
|
|
554
|
-
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
555
|
-
});
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
559
|
-
table: metadata.throughTable,
|
|
560
|
-
where: {
|
|
561
|
-
type: "comparison",
|
|
562
|
-
column: metadata.firstKey,
|
|
563
|
-
operator: "in",
|
|
564
|
-
value: parentKeys
|
|
391
|
+
const results = /* @__PURE__ */ new Map();
|
|
392
|
+
let pending = filePaths.map((file) => (0, node_path.resolve)(file));
|
|
393
|
+
while (pending.length > 0) {
|
|
394
|
+
const failed = [];
|
|
395
|
+
for (const file of pending) try {
|
|
396
|
+
const module = await jiti$1.import(file, useDefault ? { default: true } : {});
|
|
397
|
+
results.set(file, {
|
|
398
|
+
file,
|
|
399
|
+
module
|
|
400
|
+
});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
failed.push(file);
|
|
403
|
+
results.set(file, {
|
|
404
|
+
file,
|
|
405
|
+
module: null,
|
|
406
|
+
error
|
|
407
|
+
});
|
|
565
408
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (intermediateKeys.length === 0) {
|
|
569
|
-
this.models.forEach((model) => {
|
|
570
|
-
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
571
|
-
});
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
575
|
-
query = this.applyConstraint(query, constraint);
|
|
576
|
-
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
577
|
-
const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
|
|
578
|
-
this.models.forEach((model) => {
|
|
579
|
-
const parentKey = model.getAttribute(metadata.localKey);
|
|
580
|
-
const related = parentKey == null ? [] : limited.get(this.toLookupKey(parentKey)) ?? [];
|
|
581
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
582
|
-
});
|
|
583
|
-
return;
|
|
409
|
+
if (failed.length === pending.length) break;
|
|
410
|
+
pending = failed;
|
|
584
411
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const parentValue = row[metadata.firstKey];
|
|
597
|
-
const intermediateValue = row[metadata.secondLocalKey];
|
|
598
|
-
if (parentValue == null || intermediateValue == null) return;
|
|
599
|
-
const bucket = intermediateByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
600
|
-
bucket.push(intermediateValue);
|
|
601
|
-
intermediateByParent.set(this.toLookupKey(parentValue), bucket);
|
|
602
|
-
});
|
|
603
|
-
this.models.forEach((model) => {
|
|
604
|
-
const parentValue = model.getAttribute(metadata.localKey);
|
|
605
|
-
const related = (parentValue == null ? [] : intermediateByParent.get(this.toLookupKey(parentValue)) ?? []).flatMap((intermediateValue) => relatedByIntermediate.get(this.toLookupKey(intermediateValue)) ?? []);
|
|
606
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
412
|
+
return filePaths.map((file) => results.get((0, node_path.resolve)(file)));
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/Exceptions/UnsupportedAdapterFeatureException.ts
|
|
418
|
+
var UnsupportedAdapterFeatureException = class extends ArkormException {
|
|
419
|
+
constructor(message, context = {}) {
|
|
420
|
+
super(message, {
|
|
421
|
+
code: "UNSUPPORTED_ADAPTER_FEATURE",
|
|
422
|
+
...context
|
|
607
423
|
});
|
|
424
|
+
this.name = "UnsupportedAdapterFeatureException";
|
|
608
425
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Loads a "has one through" relationship for the set of models.
|
|
611
|
-
*
|
|
612
|
-
* @param name
|
|
613
|
-
* @param resolver
|
|
614
|
-
* @param metadata
|
|
615
|
-
* @param constraint
|
|
616
|
-
* @returns
|
|
617
|
-
*/
|
|
618
|
-
async loadHasOneThrough(name, resolver, metadata, constraint) {
|
|
619
|
-
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
620
|
-
if (parentKeys.length === 0) {
|
|
621
|
-
this.models.forEach((model) => {
|
|
622
|
-
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
623
|
-
});
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
627
|
-
table: metadata.throughTable,
|
|
628
|
-
where: {
|
|
629
|
-
type: "comparison",
|
|
630
|
-
column: metadata.firstKey,
|
|
631
|
-
operator: "in",
|
|
632
|
-
value: parentKeys
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
const intermediateKeys = this.collectUniqueRowValues(throughRows, metadata.secondLocalKey);
|
|
636
|
-
if (intermediateKeys.length === 0) {
|
|
637
|
-
this.models.forEach((model) => {
|
|
638
|
-
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
639
|
-
});
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
643
|
-
query = this.applyConstraint(query, constraint);
|
|
644
|
-
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
645
|
-
const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
|
|
646
|
-
this.models.forEach((model) => {
|
|
647
|
-
const parentKey = model.getAttribute(metadata.localKey);
|
|
648
|
-
const related = parentKey == null ? void 0 : limited.get(this.toLookupKey(parentKey))?.[0];
|
|
649
|
-
model.setLoadedRelation(name, related ?? this.resolveSingleDefault(resolver, model));
|
|
650
|
-
});
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
const relatedModels = (await query.get()).all();
|
|
654
|
-
const relatedByIntermediate = /* @__PURE__ */ new Map();
|
|
655
|
-
relatedModels.forEach((related) => {
|
|
656
|
-
const relatedValue = this.readModelAttribute(related, metadata.secondKey);
|
|
657
|
-
if (relatedValue == null) return;
|
|
658
|
-
const lookupKey = this.toLookupKey(relatedValue);
|
|
659
|
-
if (!relatedByIntermediate.has(lookupKey)) relatedByIntermediate.set(lookupKey, related);
|
|
660
|
-
});
|
|
661
|
-
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
662
|
-
throughRows.forEach((row) => {
|
|
663
|
-
const parentValue = row[metadata.firstKey];
|
|
664
|
-
const intermediateValue = row[metadata.secondLocalKey];
|
|
665
|
-
if (parentValue == null || intermediateValue == null) return;
|
|
666
|
-
const lookupKey = this.toLookupKey(parentValue);
|
|
667
|
-
if (!intermediateByParent.has(lookupKey)) intermediateByParent.set(lookupKey, intermediateValue);
|
|
668
|
-
});
|
|
669
|
-
this.models.forEach((model) => {
|
|
670
|
-
const parentValue = model.getAttribute(metadata.localKey);
|
|
671
|
-
const intermediateValue = parentValue == null ? void 0 : intermediateByParent.get(this.toLookupKey(parentValue));
|
|
672
|
-
const relationValue = intermediateValue == null ? void 0 : relatedByIntermediate.get(this.toLookupKey(intermediateValue));
|
|
673
|
-
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
async loadLimitedThrough(metadata, query, parentKeys, throughRows) {
|
|
677
|
-
const parentAlias = "__arkorm_parent_key";
|
|
678
|
-
const throughTable = metadata.throughTable;
|
|
679
|
-
const relatedTable = metadata.relatedModel.getTable();
|
|
680
|
-
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.secondKey)}`, "=", `${throughTable}.${metadata.secondLocalKey}`).whereIn(`${throughTable}.${metadata.firstKey}`, parentKeys).addSelect({ [`${throughTable}.${metadata.firstKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${metadata.firstKey}`]);
|
|
681
|
-
if (!rows) return await this.loadLimitedThroughIndividually(metadata, query, throughRows);
|
|
682
|
-
const relatedKey = metadata.relatedModel.getPrimaryKey();
|
|
683
|
-
const selectedIds = this.collectUniqueRowValues(rows, relatedKey);
|
|
684
|
-
const relatedModels = (await query.clone().withoutPagination().whereIn(relatedKey, selectedIds).get()).all();
|
|
685
|
-
const relatedByKey = /* @__PURE__ */ new Map();
|
|
686
|
-
relatedModels.forEach((related) => {
|
|
687
|
-
const key = this.readModelAttribute(related, relatedKey);
|
|
688
|
-
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
689
|
-
});
|
|
690
|
-
return rows.reduce((all, row) => {
|
|
691
|
-
const parentKey = row[parentAlias];
|
|
692
|
-
const key = row[relatedKey];
|
|
693
|
-
if (parentKey == null || key == null) return all;
|
|
694
|
-
const related = relatedByKey.get(this.toLookupKey(key));
|
|
695
|
-
if (!related) return all;
|
|
696
|
-
const lookup = this.toLookupKey(parentKey);
|
|
697
|
-
const bucket = all.get(lookup) ?? [];
|
|
698
|
-
bucket.push(related);
|
|
699
|
-
all.set(lookup, bucket);
|
|
700
|
-
return all;
|
|
701
|
-
}, /* @__PURE__ */ new Map());
|
|
702
|
-
}
|
|
703
|
-
async loadLimitedThroughIndividually(metadata, query, throughRows) {
|
|
704
|
-
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
705
|
-
throughRows.forEach((row) => {
|
|
706
|
-
const parentKey = row[metadata.firstKey];
|
|
707
|
-
const intermediateKey = row[metadata.secondLocalKey];
|
|
708
|
-
if (parentKey == null || intermediateKey == null) return;
|
|
709
|
-
const lookup = this.toLookupKey(parentKey);
|
|
710
|
-
const bucket = intermediateByParent.get(lookup) ?? [];
|
|
711
|
-
bucket.push(intermediateKey);
|
|
712
|
-
intermediateByParent.set(lookup, bucket);
|
|
713
|
-
});
|
|
714
|
-
const entries = await Promise.all([...intermediateByParent].map(async ([parentKey, intermediateKeys]) => {
|
|
715
|
-
return [parentKey, (await query.clone().whereIn(metadata.secondKey, intermediateKeys).get()).all()];
|
|
716
|
-
}));
|
|
717
|
-
return new Map(entries);
|
|
718
|
-
}
|
|
719
|
-
/**
|
|
720
|
-
* Loads an inverse polymorphic ("morph to") relationship for the set of
|
|
721
|
-
* models. Parents are grouped by their morph type, so each distinct type
|
|
722
|
-
* runs a single batched query instead of one query per parent row.
|
|
723
|
-
*
|
|
724
|
-
* @param name
|
|
725
|
-
* @param metadata
|
|
726
|
-
* @param constraint
|
|
727
|
-
* @returns
|
|
728
|
-
*/
|
|
729
|
-
async loadMorphTo(name, metadata, constraint) {
|
|
730
|
-
const idsByType = /* @__PURE__ */ new Map();
|
|
731
|
-
const seenByType = /* @__PURE__ */ new Map();
|
|
732
|
-
this.models.forEach((model) => {
|
|
733
|
-
const morphType = this.readModelAttribute(model, metadata.morphTypeColumn);
|
|
734
|
-
const morphId = this.readModelAttribute(model, metadata.morphIdColumn);
|
|
735
|
-
if (typeof morphType !== "string" || morphType.length === 0 || morphId == null) return;
|
|
736
|
-
const ids = idsByType.get(morphType) ?? [];
|
|
737
|
-
const seen = seenByType.get(morphType) ?? /* @__PURE__ */ new Set();
|
|
738
|
-
const lookupKey = this.toLookupKey(morphId);
|
|
739
|
-
if (!seen.has(lookupKey)) {
|
|
740
|
-
seen.add(lookupKey);
|
|
741
|
-
ids.push(morphId);
|
|
742
|
-
}
|
|
743
|
-
idsByType.set(morphType, ids);
|
|
744
|
-
seenByType.set(morphType, seen);
|
|
745
|
-
});
|
|
746
|
-
const relatedByTypeAndKey = /* @__PURE__ */ new Map();
|
|
747
|
-
await Promise.all(Array.from(idsByType.entries()).map(async ([morphType, ids]) => {
|
|
748
|
-
const relatedModel = metadata.resolveModel(morphType);
|
|
749
|
-
const ownerKey = metadata.ownerKey ?? relatedModel.getPrimaryKey();
|
|
750
|
-
let query = relatedModel.query().whereIn(ownerKey, ids);
|
|
751
|
-
query = this.applyConstraint(query, constraint);
|
|
752
|
-
(await this.getPartitionedModels(query, ownerKey, ids)).forEach((related) => {
|
|
753
|
-
const value = this.readModelAttribute(related, ownerKey);
|
|
754
|
-
if (value == null) return;
|
|
755
|
-
relatedByTypeAndKey.set(`${morphType}:${this.toLookupKey(value)}`, related);
|
|
756
|
-
});
|
|
757
|
-
}));
|
|
758
|
-
this.models.forEach((model) => {
|
|
759
|
-
const morphType = this.readModelAttribute(model, metadata.morphTypeColumn);
|
|
760
|
-
const morphId = this.readModelAttribute(model, metadata.morphIdColumn);
|
|
761
|
-
const relationValue = typeof morphType !== "string" || morphId == null ? null : relatedByTypeAndKey.get(`${morphType}:${this.toLookupKey(morphId)}`) ?? null;
|
|
762
|
-
model.setLoadedRelation(name, relationValue);
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Loads a polymorphic one-to-one ("morph one") relationship for the set of
|
|
767
|
-
* models. See {@link loadMorphChildren} for the batching strategy.
|
|
768
|
-
*
|
|
769
|
-
* @param name
|
|
770
|
-
* @param resolver
|
|
771
|
-
* @param metadata
|
|
772
|
-
* @param constraint
|
|
773
|
-
*/
|
|
774
|
-
async loadMorphOne(name, resolver, metadata, constraint) {
|
|
775
|
-
const relatedByKey = await this.loadMorphChildren(metadata, constraint);
|
|
776
|
-
this.models.forEach((model) => {
|
|
777
|
-
const bucket = relatedByKey.get(this.morphParentKey(model, metadata.localKey));
|
|
778
|
-
model.setLoadedRelation(name, bucket?.[0] ?? this.resolveSingleDefault(resolver, model));
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Loads a polymorphic one-to-many ("morph many") relationship for the set of
|
|
783
|
-
* models. See {@link loadMorphChildren} for the batching strategy.
|
|
784
|
-
*
|
|
785
|
-
* @param name
|
|
786
|
-
* @param metadata
|
|
787
|
-
* @param constraint
|
|
788
|
-
*/
|
|
789
|
-
async loadMorphMany(name, metadata, constraint) {
|
|
790
|
-
const relatedByKey = await this.loadMorphChildren(metadata, constraint);
|
|
791
|
-
this.models.forEach((model) => {
|
|
792
|
-
const bucket = relatedByKey.get(this.morphParentKey(model, metadata.localKey)) ?? [];
|
|
793
|
-
model.setLoadedRelation(name, new ArkormCollection(bucket));
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
async loadPolymorphicManyToMany(name, metadata, constraint) {
|
|
797
|
-
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
|
|
798
|
-
if (parentKeys.length === 0) {
|
|
799
|
-
this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
const parentPivotKey = metadata.type === "morphToMany" ? metadata.morphIdColumn : metadata.foreignPivotKey;
|
|
803
|
-
const morphType = metadata.type === "morphToMany" ? this.morphTypeOf(this.models[0]) : metadata.relatedModel.name;
|
|
804
|
-
const pivotRows = await this.createRelationTableLoader().selectRows({
|
|
805
|
-
table: metadata.throughTable,
|
|
806
|
-
where: {
|
|
807
|
-
type: "group",
|
|
808
|
-
operator: "and",
|
|
809
|
-
conditions: [{
|
|
810
|
-
type: "comparison",
|
|
811
|
-
column: parentPivotKey,
|
|
812
|
-
operator: "in",
|
|
813
|
-
value: parentKeys
|
|
814
|
-
}, {
|
|
815
|
-
type: "comparison",
|
|
816
|
-
column: metadata.morphTypeColumn,
|
|
817
|
-
operator: "=",
|
|
818
|
-
value: morphType
|
|
819
|
-
}]
|
|
820
|
-
},
|
|
821
|
-
columns: [parentPivotKey, metadata.relatedPivotKey].map((column) => ({ column }))
|
|
822
|
-
});
|
|
823
|
-
const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
|
|
824
|
-
if (relatedIds.length === 0) {
|
|
825
|
-
this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
|
|
829
|
-
query = this.applyConstraint(query, constraint);
|
|
830
|
-
let pairs = pivotRows.map((row) => ({
|
|
831
|
-
parent: row[parentPivotKey],
|
|
832
|
-
related: row[metadata.relatedPivotKey]
|
|
833
|
-
}));
|
|
834
|
-
let relatedModels;
|
|
835
|
-
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
836
|
-
const parentAlias = "__arkorm_parent_key";
|
|
837
|
-
const throughTable = metadata.throughTable;
|
|
838
|
-
const relatedTable = metadata.relatedModel.getTable();
|
|
839
|
-
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${parentPivotKey}`, parentKeys).where({ [`${throughTable}.${metadata.morphTypeColumn}`]: morphType }).addSelect({ [`${throughTable}.${parentPivotKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${parentPivotKey}`]);
|
|
840
|
-
if (rows) {
|
|
841
|
-
pairs = rows.map((row) => ({
|
|
842
|
-
parent: row[parentAlias],
|
|
843
|
-
related: row[metadata.relatedKey]
|
|
844
|
-
}));
|
|
845
|
-
const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
|
|
846
|
-
relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
|
|
847
|
-
} else {
|
|
848
|
-
await this.loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows);
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
} else relatedModels = (await query.get()).all();
|
|
852
|
-
const relatedByKey = /* @__PURE__ */ new Map();
|
|
853
|
-
relatedModels.forEach((related) => {
|
|
854
|
-
const key = this.readModelAttribute(related, metadata.relatedKey);
|
|
855
|
-
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
856
|
-
});
|
|
857
|
-
const relatedByParent = /* @__PURE__ */ new Map();
|
|
858
|
-
pairs.forEach(({ parent, related }) => {
|
|
859
|
-
if (parent == null || related == null) return;
|
|
860
|
-
const model = relatedByKey.get(this.toLookupKey(related));
|
|
861
|
-
if (!model) return;
|
|
862
|
-
const lookup = this.toLookupKey(parent);
|
|
863
|
-
const bucket = relatedByParent.get(lookup) ?? [];
|
|
864
|
-
bucket.push(model);
|
|
865
|
-
relatedByParent.set(lookup, bucket);
|
|
866
|
-
});
|
|
867
|
-
this.models.forEach((model) => {
|
|
868
|
-
const parentKey = model.getAttribute(metadata.parentKey);
|
|
869
|
-
const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
870
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
async loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows) {
|
|
874
|
-
await Promise.all(this.models.map(async (model) => {
|
|
875
|
-
const parentKey = model.getAttribute(metadata.parentKey);
|
|
876
|
-
const ids = pivotRows.filter((row) => this.toLookupKey(row[parentPivotKey]) === this.toLookupKey(parentKey)).map((row) => row[metadata.relatedPivotKey]);
|
|
877
|
-
const related = ids.length ? (await query.clone().whereIn(metadata.relatedKey, ids).get()).all() : [];
|
|
878
|
-
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
879
|
-
}));
|
|
880
|
-
}
|
|
881
|
-
/**
|
|
882
|
-
* Batch-loads the children for a morphOne/morphMany relationship. Parents are
|
|
883
|
-
* grouped by their own morph type (constructor name), so each distinct type
|
|
884
|
-
* runs a single query constrained by `idColumn IN (...) AND typeColumn = type`
|
|
885
|
-
* instead of one query per parent row. This also supports a heterogeneous
|
|
886
|
-
* parent set (e.g. the result of a nested load after a morphTo).
|
|
887
|
-
*
|
|
888
|
-
* @param metadata
|
|
889
|
-
* @param constraint
|
|
890
|
-
* @returns A map of `"{type}:{localKey}" -> related[]`.
|
|
891
|
-
*/
|
|
892
|
-
async loadMorphChildren(metadata, constraint) {
|
|
893
|
-
const keysByType = /* @__PURE__ */ new Map();
|
|
894
|
-
const seenByType = /* @__PURE__ */ new Map();
|
|
895
|
-
this.models.forEach((model) => {
|
|
896
|
-
const morphType = this.morphTypeOf(model);
|
|
897
|
-
const localValue = model.getAttribute(metadata.localKey);
|
|
898
|
-
if (morphType.length === 0 || localValue == null) return;
|
|
899
|
-
const keys = keysByType.get(morphType) ?? [];
|
|
900
|
-
const seen = seenByType.get(morphType) ?? /* @__PURE__ */ new Set();
|
|
901
|
-
const lookupKey = this.toLookupKey(localValue);
|
|
902
|
-
if (!seen.has(lookupKey)) {
|
|
903
|
-
seen.add(lookupKey);
|
|
904
|
-
keys.push(localValue);
|
|
905
|
-
}
|
|
906
|
-
keysByType.set(morphType, keys);
|
|
907
|
-
seenByType.set(morphType, seen);
|
|
908
|
-
});
|
|
909
|
-
const relatedByKey = /* @__PURE__ */ new Map();
|
|
910
|
-
await Promise.all(Array.from(keysByType.entries()).map(async ([morphType, keys]) => {
|
|
911
|
-
let query = metadata.relatedModel.query().whereIn(metadata.morphIdColumn, keys).where({ [metadata.morphTypeColumn]: morphType });
|
|
912
|
-
query = this.applyConstraint(query, constraint);
|
|
913
|
-
(await this.getPartitionedModels(query, metadata.morphIdColumn, keys)).forEach((related) => {
|
|
914
|
-
const value = this.readModelAttribute(related, metadata.morphIdColumn);
|
|
915
|
-
if (value == null) return;
|
|
916
|
-
const key = `${morphType}:${this.toLookupKey(value)}`;
|
|
917
|
-
const bucket = relatedByKey.get(key) ?? [];
|
|
918
|
-
bucket.push(related);
|
|
919
|
-
relatedByKey.set(key, bucket);
|
|
920
|
-
});
|
|
921
|
-
}));
|
|
922
|
-
return relatedByKey;
|
|
923
|
-
}
|
|
924
|
-
morphParentKey(model, localKey) {
|
|
925
|
-
return `${this.morphTypeOf(model)}:${this.toLookupKey(model.getAttribute(localKey))}`;
|
|
926
|
-
}
|
|
927
|
-
morphTypeOf(model) {
|
|
928
|
-
return model.constructor?.name ?? "";
|
|
929
|
-
}
|
|
930
|
-
/**
|
|
931
|
-
* Fallback method to load relationships individually for each model when the
|
|
932
|
-
* relationship type is not supported for set-based loading.
|
|
933
|
-
*
|
|
934
|
-
* @param name
|
|
935
|
-
* @param resolver
|
|
936
|
-
* @param constraint
|
|
937
|
-
*/
|
|
938
|
-
async loadIndividually(name, resolver, constraint) {
|
|
939
|
-
await Promise.all(this.models.map(async (model) => {
|
|
940
|
-
const relation = resolver.call(model);
|
|
941
|
-
if (constraint) relation.constrain(constraint);
|
|
942
|
-
model.setLoadedRelation(name, await relation.getResults());
|
|
943
|
-
}));
|
|
944
|
-
}
|
|
945
|
-
/**
|
|
946
|
-
* Applies an eager load constraint to a query if provided.
|
|
947
|
-
*
|
|
948
|
-
* @param query
|
|
949
|
-
* @param constraint
|
|
950
|
-
* @returns
|
|
951
|
-
*/
|
|
952
|
-
applyConstraint(query, constraint) {
|
|
953
|
-
if (!constraint) return query;
|
|
954
|
-
return constraint(query) ?? query;
|
|
955
|
-
}
|
|
956
|
-
async getPartitionedModels(query, partitionColumn, partitionValues) {
|
|
957
|
-
if (partitionValues.length <= 1) return (await query.get()).all();
|
|
958
|
-
const partitioned = await query.getForEagerLoad([partitionColumn]);
|
|
959
|
-
if (partitioned) return partitioned.all();
|
|
960
|
-
return (await Promise.all(partitionValues.map(async (value) => {
|
|
961
|
-
return (await query.clone().where({ [partitionColumn]: value }).get()).all();
|
|
962
|
-
}))).flat();
|
|
963
|
-
}
|
|
964
|
-
/**
|
|
965
|
-
* Collects unique values from the set of models based on a resolver function, which
|
|
966
|
-
* is used to extract the value from each model.
|
|
967
|
-
*
|
|
968
|
-
* @param resolve A function that takes a model and returns the value to be collected.
|
|
969
|
-
* @returns An array of unique values.
|
|
970
|
-
*/
|
|
971
|
-
collectUniqueKeys(resolve) {
|
|
972
|
-
const seen = /* @__PURE__ */ new Set();
|
|
973
|
-
const values = [];
|
|
974
|
-
this.models.forEach((model) => {
|
|
975
|
-
const value = resolve(model);
|
|
976
|
-
if (value == null) return;
|
|
977
|
-
const lookupKey = this.toLookupKey(value);
|
|
978
|
-
if (seen.has(lookupKey)) return;
|
|
979
|
-
seen.add(lookupKey);
|
|
980
|
-
values.push(value);
|
|
981
|
-
});
|
|
982
|
-
return values;
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Collects unique values from an array of database rows based on a specified key, which
|
|
986
|
-
* is used to extract the value from each row.
|
|
987
|
-
*
|
|
988
|
-
* @param rows An array of database rows.
|
|
989
|
-
* @param key The key to extract values from each row.
|
|
990
|
-
* @returns An array of unique values.
|
|
991
|
-
*/
|
|
992
|
-
collectUniqueRowValues(rows, key) {
|
|
993
|
-
const seen = /* @__PURE__ */ new Set();
|
|
994
|
-
const values = [];
|
|
995
|
-
rows.forEach((row) => {
|
|
996
|
-
const value = row[key];
|
|
997
|
-
if (value == null) return;
|
|
998
|
-
const lookupKey = this.toLookupKey(value);
|
|
999
|
-
if (seen.has(lookupKey)) return;
|
|
1000
|
-
seen.add(lookupKey);
|
|
1001
|
-
values.push(value);
|
|
1002
|
-
});
|
|
1003
|
-
return values;
|
|
1004
|
-
}
|
|
1005
|
-
/**
|
|
1006
|
-
* Loads a "belongs to many" relationship for the set of models.
|
|
1007
|
-
*
|
|
1008
|
-
* @returns
|
|
1009
|
-
*/
|
|
1010
|
-
createRelationTableLoader() {
|
|
1011
|
-
return new RelationTableLoader(this.resolveAdapter());
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Loads a "belongs to many" relationship for the set of models.
|
|
1015
|
-
*
|
|
1016
|
-
* @returns
|
|
1017
|
-
*/
|
|
1018
|
-
resolveAdapter() {
|
|
1019
|
-
const adapter = this.models[0].constructor.getAdapter?.();
|
|
1020
|
-
if (!adapter) throw new Error("Set-based eager loading requires a configured adapter.");
|
|
1021
|
-
return adapter;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Reads an attribute value from a model using the getAttribute method, which is used
|
|
1025
|
-
* to access model attributes in a way that is compatible with Arkorm's internal model structure.
|
|
1026
|
-
*
|
|
1027
|
-
* @param model The model to read the attribute from.
|
|
1028
|
-
* @param key The name of the attribute to read.
|
|
1029
|
-
* @returns
|
|
1030
|
-
*/
|
|
1031
|
-
readModelAttribute(model, key) {
|
|
1032
|
-
const readable = model;
|
|
1033
|
-
const value = readable.getAttribute?.(key);
|
|
1034
|
-
if (value !== void 0) return value;
|
|
1035
|
-
try {
|
|
1036
|
-
const attribute = Object.entries(readable.constructor?.getColumnMap?.() ?? {}).find(([, column]) => column === key)?.[0];
|
|
1037
|
-
return attribute ? readable.getAttribute?.(attribute) : value;
|
|
1038
|
-
} catch {
|
|
1039
|
-
return value;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Resolves the default result for a relationship when no related models are found.
|
|
1044
|
-
*
|
|
1045
|
-
* @param resolver
|
|
1046
|
-
* @param model
|
|
1047
|
-
* @returns
|
|
1048
|
-
*/
|
|
1049
|
-
resolveSingleDefault(resolver, model) {
|
|
1050
|
-
return resolver.call(model).resolveDefaultResult?.() ?? null;
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* Generates a unique lookup key for a given value, which is used to store and retrieve
|
|
1054
|
-
* values in maps during the eager loading process.
|
|
1055
|
-
*
|
|
1056
|
-
* @param value The value to generate a lookup key for.
|
|
1057
|
-
* @returns A unique string representing the value.
|
|
1058
|
-
*/
|
|
1059
|
-
toLookupKey(value) {
|
|
1060
|
-
if (value instanceof Date) return `date:${value.toISOString()}`;
|
|
1061
|
-
return `${typeof value}:${String(value)}`;
|
|
1062
|
-
}
|
|
1063
426
|
};
|
|
1064
|
-
|
|
1065
|
-
//#endregion
|
|
1066
|
-
//#region src/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
427
|
+
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region src/helpers/runtime-registry.ts
|
|
430
|
+
const createEmptyRegistry = () => ({
|
|
431
|
+
paths: {
|
|
432
|
+
models: [],
|
|
433
|
+
seeders: [],
|
|
434
|
+
migrations: [],
|
|
435
|
+
factories: []
|
|
436
|
+
},
|
|
437
|
+
migrations: [],
|
|
438
|
+
seeders: [],
|
|
439
|
+
models: [],
|
|
440
|
+
factories: []
|
|
441
|
+
});
|
|
442
|
+
const registry = createEmptyRegistry();
|
|
443
|
+
const pushUnique = (items, values) => {
|
|
444
|
+
values.forEach((value) => {
|
|
445
|
+
if (!items.includes(value)) items.push(value);
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
const normalizePathInput = (paths) => {
|
|
449
|
+
if (paths === void 0) return [];
|
|
450
|
+
return (Array.isArray(paths) ? paths : [paths]).filter((path) => typeof path === "string" && path.trim().length > 0);
|
|
451
|
+
};
|
|
452
|
+
const normalizeConstructors = (items) => items.flatMap((item) => Array.isArray(item) ? item : [item]).filter(Boolean);
|
|
453
|
+
/**
|
|
454
|
+
* Register additional runtime discovery paths without replacing configured paths.
|
|
455
|
+
*
|
|
456
|
+
* @param paths
|
|
457
|
+
*/
|
|
458
|
+
const registerPaths = (paths) => {
|
|
459
|
+
Object.entries(paths).forEach(([key, value]) => {
|
|
460
|
+
pushUnique(registry.paths[key], normalizePathInput(value));
|
|
461
|
+
});
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Register additional runtime discovery paths for migrations without replacing configured paths.
|
|
465
|
+
*
|
|
466
|
+
* @param paths
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
469
|
+
const loadMigrationsFrom = (paths) => registerPaths({ migrations: paths });
|
|
470
|
+
/**
|
|
471
|
+
* Register additional runtime discovery paths for seeders without replacing configured paths.
|
|
472
|
+
*
|
|
473
|
+
* @param paths
|
|
474
|
+
* @returns
|
|
475
|
+
*/
|
|
476
|
+
const loadSeedersFrom = (paths) => registerPaths({ seeders: paths });
|
|
477
|
+
/**
|
|
478
|
+
* Register additional runtime discovery paths for models without replacing configured paths.
|
|
479
|
+
*
|
|
480
|
+
* @param paths
|
|
481
|
+
* @returns
|
|
482
|
+
*/
|
|
483
|
+
const loadModelsFrom = (paths) => registerPaths({ models: paths });
|
|
484
|
+
/**
|
|
485
|
+
* Register additional runtime discovery paths for factories without replacing configured paths.
|
|
486
|
+
*
|
|
487
|
+
* @param paths
|
|
488
|
+
* @returns
|
|
489
|
+
*/
|
|
490
|
+
const loadFactoriesFrom = (paths) => registerPaths({ factories: paths });
|
|
491
|
+
/**
|
|
492
|
+
* Register migration constructors directly without relying on runtime discovery.
|
|
493
|
+
*
|
|
494
|
+
* @param migrations
|
|
495
|
+
*/
|
|
496
|
+
const registerMigrations = (...migrations) => {
|
|
497
|
+
pushUnique(registry.migrations, normalizeConstructors(migrations));
|
|
498
|
+
};
|
|
499
|
+
/**
|
|
500
|
+
* Register seeder constructors directly without relying on runtime discovery.
|
|
501
|
+
*
|
|
502
|
+
* @param seeders
|
|
503
|
+
*/
|
|
504
|
+
const registerSeeders = (...seeders) => {
|
|
505
|
+
pushUnique(registry.seeders, normalizeConstructors(seeders));
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* Register model constructors directly without relying on runtime discovery.
|
|
509
|
+
*
|
|
510
|
+
* @param models
|
|
511
|
+
*/
|
|
512
|
+
const registerModels = (...models) => {
|
|
513
|
+
pushUnique(registry.models, normalizeConstructors(models));
|
|
514
|
+
};
|
|
515
|
+
/**
|
|
516
|
+
* Register factory constructors or instances directly without relying on runtime discovery.
|
|
517
|
+
*
|
|
518
|
+
* @param factories
|
|
519
|
+
*/
|
|
520
|
+
const registerFactories = (...factories) => {
|
|
521
|
+
pushUnique(registry.factories, normalizeConstructors(factories));
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Get registered runtime discovery paths or registered constructors for a specific type.
|
|
525
|
+
*
|
|
526
|
+
* @param key
|
|
527
|
+
* @returns
|
|
528
|
+
*/
|
|
529
|
+
const getRegisteredPaths = (key) => {
|
|
530
|
+
if (key) return [...registry.paths[key]];
|
|
531
|
+
return {
|
|
532
|
+
models: [...registry.paths.models],
|
|
533
|
+
seeders: [...registry.paths.seeders],
|
|
534
|
+
migrations: [...registry.paths.migrations],
|
|
535
|
+
factories: [...registry.paths.factories]
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
/**
|
|
539
|
+
* Get registered migration constructors instances.
|
|
540
|
+
*
|
|
541
|
+
* @returns
|
|
542
|
+
*/
|
|
543
|
+
const getRegisteredMigrations = () => [...registry.migrations];
|
|
544
|
+
/**
|
|
545
|
+
* Get registered seeder constructors instances.
|
|
546
|
+
*
|
|
547
|
+
* @returns
|
|
548
|
+
*/
|
|
549
|
+
const getRegisteredSeeders = () => [...registry.seeders];
|
|
550
|
+
/**
|
|
551
|
+
* Get registered model constructors instances.
|
|
552
|
+
*
|
|
553
|
+
* @returns
|
|
554
|
+
*/
|
|
555
|
+
const getRegisteredModels = () => [...registry.models];
|
|
556
|
+
const resolveRegisteredModel = (modelName, context = {}) => {
|
|
557
|
+
const normalized = modelName.trim();
|
|
558
|
+
const model = registry.models.find((registered) => registered.name === normalized);
|
|
559
|
+
if (!model) throw new RelationResolutionException(`Model [${normalized}] is not registered. Register it with Arkorm.registerModels() or load it through configured model paths.`, {
|
|
560
|
+
operation: context.operation ?? "relationship.resolveModel",
|
|
561
|
+
model: normalized,
|
|
562
|
+
relation: context.relation
|
|
563
|
+
});
|
|
564
|
+
return model;
|
|
565
|
+
};
|
|
566
|
+
/**
|
|
567
|
+
* Get registered factory constructors or instances.
|
|
568
|
+
*
|
|
569
|
+
* @returns
|
|
570
|
+
*/
|
|
571
|
+
const getRegisteredFactories = () => [...registry.factories];
|
|
572
|
+
const resetRuntimeRegistryForTests = () => {
|
|
573
|
+
const empty = createEmptyRegistry();
|
|
574
|
+
registry.paths = empty.paths;
|
|
575
|
+
registry.migrations = empty.migrations;
|
|
576
|
+
registry.seeders = empty.seeders;
|
|
577
|
+
registry.models = empty.models;
|
|
578
|
+
registry.factories = empty.factories;
|
|
1075
579
|
};
|
|
1076
580
|
|
|
1077
581
|
//#endregion
|
|
1078
|
-
//#region src/helpers/runtime-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
582
|
+
//#region src/helpers/runtime-config.ts
|
|
583
|
+
const resolveDefaultStubsPath = () => {
|
|
584
|
+
let current = path.default.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
585
|
+
while (true) {
|
|
586
|
+
const packageJsonPath = path.default.join(current, "package.json");
|
|
587
|
+
const stubsPath = path.default.join(current, "stubs");
|
|
588
|
+
if ((0, fs.existsSync)(packageJsonPath) && (0, fs.existsSync)(stubsPath)) return stubsPath;
|
|
589
|
+
const parent = path.default.dirname(current);
|
|
590
|
+
if (parent === current) break;
|
|
591
|
+
current = parent;
|
|
1087
592
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
593
|
+
return path.default.join(process.cwd(), "stubs");
|
|
594
|
+
};
|
|
595
|
+
const baseConfig = {
|
|
596
|
+
naming: { case: "snake" },
|
|
597
|
+
features: {
|
|
598
|
+
persistedColumnMappings: true,
|
|
599
|
+
persistedEnums: true
|
|
600
|
+
},
|
|
601
|
+
paths: {
|
|
602
|
+
stubs: resolveDefaultStubsPath(),
|
|
603
|
+
seeders: path.default.join(process.cwd(), "database", "seeders"),
|
|
604
|
+
models: path.default.join(process.cwd(), "src", "models"),
|
|
605
|
+
migrations: path.default.join(process.cwd(), "database", "migrations"),
|
|
606
|
+
factories: path.default.join(process.cwd(), "database", "factories"),
|
|
607
|
+
buildOutput: path.default.join(process.cwd(), "dist")
|
|
608
|
+
},
|
|
609
|
+
outputExt: "ts"
|
|
610
|
+
};
|
|
611
|
+
const userConfig = {
|
|
612
|
+
...baseConfig,
|
|
613
|
+
naming: { ...baseConfig.naming ?? {} },
|
|
614
|
+
features: { ...baseConfig.features ?? {} },
|
|
615
|
+
paths: { ...baseConfig.paths ?? {} }
|
|
616
|
+
};
|
|
617
|
+
let runtimeConfigLoaded = false;
|
|
618
|
+
let runtimeConfigLoadingPromise;
|
|
619
|
+
let runtimeModelRegistrationPromise;
|
|
620
|
+
let runtimeModelRegistrationGeneration = 0;
|
|
621
|
+
let runtimeClientResolver;
|
|
622
|
+
let runtimeAdapter;
|
|
623
|
+
let runtimePaginationURLDriverFactory;
|
|
624
|
+
let runtimePaginationCurrentPageResolver;
|
|
625
|
+
let runtimeDebugHandler;
|
|
626
|
+
const transactionClientStorage = new async_hooks.AsyncLocalStorage();
|
|
627
|
+
const transactionAdapterStorage = new async_hooks.AsyncLocalStorage();
|
|
628
|
+
const defaultDebugHandler = (event) => {
|
|
629
|
+
const prefix = `[arkorm:${event.adapter}] ${event.operation}${event.target ? ` [${event.target}]` : ""}`;
|
|
630
|
+
const payload = {
|
|
631
|
+
phase: event.phase,
|
|
632
|
+
durationMs: event.durationMs,
|
|
633
|
+
inspection: event.inspection ?? void 0,
|
|
634
|
+
meta: event.meta,
|
|
635
|
+
error: event.error
|
|
636
|
+
};
|
|
637
|
+
if (event.phase === "error") {
|
|
638
|
+
console.error(prefix, payload);
|
|
639
|
+
return;
|
|
1134
640
|
}
|
|
641
|
+
console.debug(prefix, payload);
|
|
1135
642
|
};
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
643
|
+
const resolveDebugHandler = (debug) => {
|
|
644
|
+
if (debug === true) return defaultDebugHandler;
|
|
645
|
+
return typeof debug === "function" ? debug : void 0;
|
|
646
|
+
};
|
|
647
|
+
const mergeNamingConfig = (naming) => {
|
|
648
|
+
const defaults = baseConfig.naming ?? {};
|
|
649
|
+
const current = userConfig.naming ?? {};
|
|
650
|
+
const resolvedCase = naming?.case ?? naming?.modelTableCase ?? current.case ?? current.modelTableCase ?? defaults.case ?? defaults.modelTableCase ?? "snake";
|
|
651
|
+
return {
|
|
652
|
+
...defaults,
|
|
653
|
+
...current,
|
|
654
|
+
...naming ?? {},
|
|
655
|
+
case: resolvedCase
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
const mergePathConfig = (paths) => {
|
|
659
|
+
const defaults = baseConfig.paths ?? {};
|
|
660
|
+
const current = userConfig.paths ?? {};
|
|
661
|
+
const incoming = Object.entries(paths ?? {}).reduce((all, [key, value]) => {
|
|
662
|
+
if (typeof value === "string" && value.trim().length > 0) all[key] = path.default.isAbsolute(value) ? value : path.default.resolve(process.cwd(), value);
|
|
663
|
+
return all;
|
|
664
|
+
}, {});
|
|
665
|
+
return {
|
|
666
|
+
...defaults,
|
|
667
|
+
...current,
|
|
668
|
+
...incoming
|
|
669
|
+
};
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* Merge the feature configuration from the base defaults, user configuration, and provided options.
|
|
673
|
+
*
|
|
674
|
+
* @param features
|
|
675
|
+
* @returns
|
|
676
|
+
*/
|
|
677
|
+
const mergeFeatureConfig = (features) => {
|
|
678
|
+
const defaults = baseConfig.features ?? {};
|
|
679
|
+
const current = userConfig.features ?? {};
|
|
680
|
+
return {
|
|
681
|
+
...defaults,
|
|
682
|
+
...current,
|
|
683
|
+
...features ?? {}
|
|
684
|
+
};
|
|
685
|
+
};
|
|
686
|
+
const resolveRuntimeModelsDirectory = (directory) => {
|
|
687
|
+
if ((0, fs.existsSync)(directory)) return directory;
|
|
688
|
+
const buildOutput = userConfig.paths?.buildOutput;
|
|
689
|
+
if (typeof buildOutput !== "string" || buildOutput.trim().length === 0) return directory;
|
|
690
|
+
const relativeSource = path.default.relative(process.cwd(), directory);
|
|
691
|
+
if (!relativeSource || relativeSource.startsWith("..")) return directory;
|
|
692
|
+
const mappedDirectory = path.default.join(buildOutput, relativeSource);
|
|
693
|
+
return (0, fs.existsSync)(mappedDirectory) ? mappedDirectory : directory;
|
|
694
|
+
};
|
|
695
|
+
const collectRuntimeModelFiles = (directory) => {
|
|
696
|
+
if (!(0, fs.existsSync)(directory)) return [];
|
|
697
|
+
return (0, fs.readdirSync)(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
698
|
+
const entryPath = path.default.join(directory, entry.name);
|
|
699
|
+
if (entry.isDirectory()) return collectRuntimeModelFiles(entryPath);
|
|
700
|
+
if (!entry.isFile() || !/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i.test(entry.name)) return [];
|
|
701
|
+
if (/\.d\.(ts|mts|cts)$/i.test(entry.name)) return [];
|
|
702
|
+
return [entryPath];
|
|
703
|
+
});
|
|
704
|
+
};
|
|
705
|
+
const isModelConstructor = (value) => {
|
|
706
|
+
if (typeof value !== "function") return false;
|
|
707
|
+
const candidate = value;
|
|
708
|
+
return typeof candidate.query === "function" && typeof candidate.hydrate === "function" && typeof candidate.getTable === "function" && typeof candidate.getPrimaryKey === "function";
|
|
709
|
+
};
|
|
710
|
+
const registerConfiguredModels = async (generation) => {
|
|
711
|
+
const configured = userConfig.paths?.models;
|
|
712
|
+
const additional = getRegisteredPaths("models");
|
|
713
|
+
const files = [...typeof configured === "string" ? [configured] : [], ...additional].map((directory) => resolveRuntimeModelsDirectory(path.default.isAbsolute(directory) ? directory : path.default.resolve(process.cwd(), directory))).filter((directory, index, all) => all.indexOf(directory) === index).flatMap(collectRuntimeModelFiles);
|
|
714
|
+
const loaded = await RuntimeModuleLoader.loadAll(files);
|
|
715
|
+
loaded.forEach(({ file, error }) => {
|
|
716
|
+
if (error !== void 0) console.warn(`[arkorm] Failed to load model file for registration: ${file}\n ${error instanceof Error ? error.message : String(error)}\n Models in this file will not be registered (morphTo resolution may fail). This is often a circular import — avoid importing models at the top level of trait modules.`);
|
|
717
|
+
});
|
|
718
|
+
const models = loaded.flatMap(({ module }) => module ? Object.values(module).filter(isModelConstructor) : []);
|
|
719
|
+
if (generation === runtimeModelRegistrationGeneration) registerModels(models);
|
|
720
|
+
};
|
|
721
|
+
const scheduleConfiguredModelRegistration = () => {
|
|
722
|
+
const generation = runtimeModelRegistrationGeneration;
|
|
723
|
+
runtimeModelRegistrationPromise = (runtimeModelRegistrationPromise ?? Promise.resolve()).then(async () => await registerConfiguredModels(generation));
|
|
724
|
+
return runtimeModelRegistrationPromise;
|
|
725
|
+
};
|
|
726
|
+
const awaitConfiguredModelsRegistration = async () => {
|
|
727
|
+
if (runtimeModelRegistrationPromise) await runtimeModelRegistrationPromise;
|
|
728
|
+
};
|
|
729
|
+
/**
|
|
730
|
+
* Define the ArkORM runtime configuration. This function can be used to provide.
|
|
731
|
+
*
|
|
732
|
+
* @param config The ArkORM configuration object.
|
|
733
|
+
* @returns The same configuration object.
|
|
734
|
+
*/
|
|
735
|
+
const defineConfig = (config) => {
|
|
736
|
+
return config;
|
|
737
|
+
};
|
|
738
|
+
/**
|
|
739
|
+
* Bind a database adapter instance to an array of models that support adapter binding.
|
|
740
|
+
*
|
|
741
|
+
* @param adapter
|
|
742
|
+
* @param models
|
|
743
|
+
* @returns
|
|
744
|
+
*/
|
|
745
|
+
const bindAdapterToModels = (adapter, models) => {
|
|
746
|
+
models.forEach((model) => {
|
|
747
|
+
model.setAdapter(adapter);
|
|
748
|
+
});
|
|
749
|
+
return adapter;
|
|
1146
750
|
};
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
751
|
+
/**
|
|
752
|
+
* Get the user-provided ArkORM configuration.
|
|
753
|
+
*
|
|
754
|
+
* @param key Optional specific configuration key to retrieve. If omitted, the entire configuration object is returned.
|
|
755
|
+
* @returns The user-provided ArkORM configuration object.
|
|
756
|
+
*/
|
|
757
|
+
const getUserConfig = (key) => {
|
|
758
|
+
if (key) return userConfig[key];
|
|
759
|
+
return userConfig;
|
|
1150
760
|
};
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
761
|
+
/**
|
|
762
|
+
* Configure the ArkORM runtime with the provided runtime client resolver and
|
|
763
|
+
* adapter-first options.
|
|
764
|
+
*
|
|
765
|
+
* @param client
|
|
766
|
+
* @param options
|
|
767
|
+
*/
|
|
768
|
+
const configureArkormRuntime = (client, options = {}) => {
|
|
769
|
+
const resolvedClient = client ?? options.client;
|
|
770
|
+
const nextConfig = {
|
|
771
|
+
...userConfig,
|
|
772
|
+
naming: mergeNamingConfig(options.naming),
|
|
773
|
+
features: mergeFeatureConfig(options.features),
|
|
774
|
+
paths: mergePathConfig(options.paths)
|
|
775
|
+
};
|
|
776
|
+
nextConfig.client = resolvedClient;
|
|
777
|
+
nextConfig.prisma = resolvedClient;
|
|
778
|
+
if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
|
|
779
|
+
if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
|
|
780
|
+
if (options.boot !== void 0) nextConfig.boot = options.boot;
|
|
781
|
+
if (options.debug !== void 0) nextConfig.debug = options.debug;
|
|
782
|
+
if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
|
|
783
|
+
Object.assign(userConfig, { ...nextConfig });
|
|
784
|
+
runtimeClientResolver = resolvedClient;
|
|
785
|
+
runtimeAdapter = options.adapter;
|
|
786
|
+
runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
|
|
787
|
+
runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
|
|
788
|
+
runtimeDebugHandler = resolveDebugHandler(nextConfig.debug);
|
|
789
|
+
scheduleConfiguredModelRegistration();
|
|
790
|
+
const bootClient = resolveClient(resolvedClient);
|
|
791
|
+
options.boot?.({
|
|
792
|
+
client: bootClient,
|
|
793
|
+
prisma: bootClient,
|
|
794
|
+
bindAdapter: bindAdapterToModels
|
|
795
|
+
});
|
|
1154
796
|
};
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
797
|
+
/**
|
|
798
|
+
* Reset the ArkORM runtime configuration.
|
|
799
|
+
* This is primarily intended for testing purposes.
|
|
800
|
+
*/
|
|
801
|
+
const resetArkormRuntimeForTests = () => {
|
|
802
|
+
Object.assign(userConfig, {
|
|
803
|
+
...baseConfig,
|
|
804
|
+
naming: { ...baseConfig.naming ?? {} },
|
|
805
|
+
features: { ...baseConfig.features ?? {} },
|
|
806
|
+
paths: { ...baseConfig.paths ?? {} }
|
|
807
|
+
});
|
|
808
|
+
runtimeConfigLoaded = false;
|
|
809
|
+
runtimeConfigLoadingPromise = void 0;
|
|
810
|
+
runtimeModelRegistrationPromise = void 0;
|
|
811
|
+
runtimeModelRegistrationGeneration++;
|
|
812
|
+
runtimeClientResolver = void 0;
|
|
813
|
+
runtimeAdapter = void 0;
|
|
814
|
+
runtimePaginationURLDriverFactory = void 0;
|
|
815
|
+
runtimePaginationCurrentPageResolver = void 0;
|
|
816
|
+
runtimeDebugHandler = void 0;
|
|
817
|
+
resetPersistedColumnMappingsCache();
|
|
818
|
+
resetRuntimeRegistryForTests();
|
|
1159
819
|
};
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}) : []
|
|
1173
|
-
};
|
|
1174
|
-
} catch {
|
|
1175
|
-
return createEmptyAppliedMigrationsState();
|
|
1176
|
-
}
|
|
820
|
+
/**
|
|
821
|
+
* Resolve a runtime client instance from the provided resolver, which can be either
|
|
822
|
+
* a direct client instance or a function that returns a client instance.
|
|
823
|
+
*
|
|
824
|
+
* @param resolver
|
|
825
|
+
* @returns
|
|
826
|
+
*/
|
|
827
|
+
const resolveClient = (resolver) => {
|
|
828
|
+
if (!resolver) return void 0;
|
|
829
|
+
const client = typeof resolver === "function" ? resolver() : resolver;
|
|
830
|
+
if (!client || typeof client !== "object") return void 0;
|
|
831
|
+
return client;
|
|
1177
832
|
};
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
833
|
+
/**
|
|
834
|
+
* Resolve and apply the ArkORM configuration from an imported module.
|
|
835
|
+
* This function checks for a default export and falls back to the module itself, then validates
|
|
836
|
+
* the configuration object and applies it to the runtime if valid.
|
|
837
|
+
*
|
|
838
|
+
* @param imported
|
|
839
|
+
* @returns
|
|
840
|
+
*/
|
|
841
|
+
const resolveAndApplyConfig = (imported) => {
|
|
842
|
+
const config = imported?.default ?? imported;
|
|
843
|
+
if (!config || typeof config !== "object") return;
|
|
844
|
+
const runtimeClient = config.client ?? config.prisma;
|
|
845
|
+
configureArkormRuntime(runtimeClient, {
|
|
846
|
+
client: runtimeClient,
|
|
847
|
+
adapter: config.adapter,
|
|
848
|
+
boot: config.boot,
|
|
849
|
+
debug: config.debug,
|
|
850
|
+
naming: config.naming,
|
|
851
|
+
features: config.features,
|
|
852
|
+
pagination: config.pagination,
|
|
853
|
+
paths: config.paths,
|
|
854
|
+
outputExt: config.outputExt
|
|
855
|
+
});
|
|
856
|
+
runtimeConfigLoaded = true;
|
|
1181
857
|
};
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
858
|
+
/**
|
|
859
|
+
* Dynamically import a configuration file.
|
|
860
|
+
* A cache-busting query parameter is appended to ensure the latest version is loaded.
|
|
861
|
+
*
|
|
862
|
+
* @param configPath
|
|
863
|
+
* @returns A promise that resolves to the imported configuration module.
|
|
864
|
+
*/
|
|
865
|
+
const importConfigFile = (configPath) => {
|
|
866
|
+
return RuntimeModuleLoader.load(configPath);
|
|
1186
867
|
};
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
868
|
+
const loadRuntimeConfigSync = () => {
|
|
869
|
+
const require = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
870
|
+
const syncConfigPaths = [path.default.join(process.cwd(), "arkormx.config.cjs")];
|
|
871
|
+
for (const configPath of syncConfigPaths) {
|
|
872
|
+
if (!(0, fs.existsSync)(configPath)) continue;
|
|
873
|
+
try {
|
|
874
|
+
resolveAndApplyConfig(require(configPath));
|
|
875
|
+
return true;
|
|
876
|
+
} catch {
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
1191
879
|
}
|
|
1192
|
-
|
|
880
|
+
return false;
|
|
1193
881
|
};
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
882
|
+
/**
|
|
883
|
+
* Load the ArkORM configuration by searching for configuration files in the
|
|
884
|
+
* current working directory.
|
|
885
|
+
* @returns
|
|
886
|
+
*/
|
|
887
|
+
const loadArkormConfig = async () => {
|
|
888
|
+
if (runtimeConfigLoaded) {
|
|
889
|
+
await awaitConfiguredModelsRegistration();
|
|
890
|
+
return;
|
|
1198
891
|
}
|
|
1199
|
-
if (
|
|
1200
|
-
|
|
1201
|
-
|
|
892
|
+
if (runtimeConfigLoadingPromise) return await runtimeConfigLoadingPromise;
|
|
893
|
+
if (loadRuntimeConfigSync()) {
|
|
894
|
+
await awaitConfiguredModelsRegistration();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
runtimeConfigLoadingPromise = (async () => {
|
|
898
|
+
const configPaths = [path.default.join(process.cwd(), "arkormx.config.js"), path.default.join(process.cwd(), "arkormx.config.ts")];
|
|
899
|
+
for (const configPath of configPaths) {
|
|
900
|
+
if (!(0, fs.existsSync)(configPath)) continue;
|
|
901
|
+
try {
|
|
902
|
+
resolveAndApplyConfig(await importConfigFile(configPath));
|
|
903
|
+
await awaitConfiguredModelsRegistration();
|
|
904
|
+
return;
|
|
905
|
+
} catch {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
runtimeConfigLoaded = true;
|
|
910
|
+
await scheduleConfiguredModelRegistration();
|
|
911
|
+
await awaitConfiguredModelsRegistration();
|
|
912
|
+
})();
|
|
913
|
+
await runtimeConfigLoadingPromise;
|
|
1202
914
|
};
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
915
|
+
/**
|
|
916
|
+
* Ensure that the ArkORM configuration is loaded.
|
|
917
|
+
* This function can be called to trigger the loading process if it hasn't already been initiated.
|
|
918
|
+
* If the configuration is already loaded, it will return immediately.
|
|
919
|
+
*
|
|
920
|
+
* @returns
|
|
921
|
+
*/
|
|
922
|
+
const ensureArkormConfigLoading = () => {
|
|
923
|
+
if (runtimeConfigLoaded) return;
|
|
924
|
+
if (!runtimeConfigLoadingPromise) loadArkormConfig();
|
|
1209
925
|
};
|
|
1210
|
-
const
|
|
1211
|
-
return
|
|
926
|
+
const getDefaultStubsPath = () => {
|
|
927
|
+
return resolveDefaultStubsPath();
|
|
1212
928
|
};
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Get the runtime compatibility client.
|
|
931
|
+
* This function will trigger the loading of the ArkORM configuration if
|
|
932
|
+
* it hasn't already been loaded.
|
|
933
|
+
*
|
|
934
|
+
* @returns
|
|
935
|
+
*/
|
|
936
|
+
const getRuntimeClient = () => {
|
|
937
|
+
const activeTransactionClient = transactionClientStorage.getStore();
|
|
938
|
+
if (activeTransactionClient) return activeTransactionClient;
|
|
939
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
940
|
+
return resolveClient(runtimeClientResolver);
|
|
941
|
+
};
|
|
942
|
+
/**
|
|
943
|
+
* @deprecated Use getRuntimeClient instead.
|
|
944
|
+
*/
|
|
945
|
+
const getRuntimePrismaClient = getRuntimeClient;
|
|
946
|
+
/**
|
|
947
|
+
* Get the currently configured runtime adapter, if any.
|
|
948
|
+
*
|
|
949
|
+
* @returns
|
|
950
|
+
*/
|
|
951
|
+
const getRuntimeAdapter = () => {
|
|
952
|
+
const activeTransactionAdapter = transactionAdapterStorage.getStore();
|
|
953
|
+
if (activeTransactionAdapter) return activeTransactionAdapter;
|
|
954
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
955
|
+
return runtimeAdapter;
|
|
1221
956
|
};
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
957
|
+
/**
|
|
958
|
+
* Releases the database resources held by the configured runtime — the adapter's
|
|
959
|
+
* connection pool and/or the configured client. Intended for short-lived
|
|
960
|
+
* processes (the CLI) so the Node event loop drains and the process exits
|
|
961
|
+
* promptly instead of hanging on pool idle timeouts. Teardown failures are
|
|
962
|
+
* swallowed because the process is shutting down anyway.
|
|
963
|
+
*/
|
|
964
|
+
const disposeArkormRuntime = async () => {
|
|
965
|
+
let adapterDisposed = false;
|
|
966
|
+
try {
|
|
967
|
+
if (runtimeAdapter && typeof runtimeAdapter.dispose === "function") {
|
|
968
|
+
await runtimeAdapter.dispose();
|
|
969
|
+
adapterDisposed = true;
|
|
970
|
+
}
|
|
971
|
+
} catch {}
|
|
972
|
+
const client = getRuntimeClient();
|
|
973
|
+
if (!client) return;
|
|
974
|
+
try {
|
|
975
|
+
if (typeof client.$disconnect === "function") await client.$disconnect();
|
|
976
|
+
else if (!adapterDisposed && typeof client.destroy === "function") await client.destroy();
|
|
977
|
+
else if (!adapterDisposed && typeof client.end === "function") await client.end();
|
|
978
|
+
} catch {}
|
|
1231
979
|
};
|
|
1232
|
-
const
|
|
1233
|
-
return
|
|
980
|
+
const getActiveTransactionClient = () => {
|
|
981
|
+
return transactionClientStorage.getStore();
|
|
1234
982
|
};
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
nextRuns.push(run);
|
|
1238
|
-
return {
|
|
1239
|
-
version: 1,
|
|
1240
|
-
migrations: state.migrations,
|
|
1241
|
-
runs: nextRuns
|
|
1242
|
-
};
|
|
983
|
+
const getActiveTransactionAdapter = () => {
|
|
984
|
+
return transactionAdapterStorage.getStore();
|
|
1243
985
|
};
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
return runs.map((run, index) => ({
|
|
1248
|
-
run,
|
|
1249
|
-
index
|
|
1250
|
-
})).sort((left, right) => {
|
|
1251
|
-
const appliedAtOrder = right.run.appliedAt.localeCompare(left.run.appliedAt);
|
|
1252
|
-
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
1253
|
-
return right.index - left.index;
|
|
1254
|
-
})[0]?.run;
|
|
986
|
+
const isTransactionCapableClient = (value) => {
|
|
987
|
+
if (!value || typeof value !== "object") return false;
|
|
988
|
+
return typeof value.$transaction === "function";
|
|
1255
989
|
};
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
990
|
+
const runArkormTransaction = async (callback, options = {}, preferredAdapter) => {
|
|
991
|
+
const activeTransactionAdapter = transactionAdapterStorage.getStore();
|
|
992
|
+
const activeTransactionClient = transactionClientStorage.getStore();
|
|
993
|
+
if (activeTransactionAdapter || activeTransactionClient) return await callback({
|
|
994
|
+
adapter: activeTransactionAdapter,
|
|
995
|
+
client: activeTransactionClient
|
|
996
|
+
});
|
|
997
|
+
const adapter = preferredAdapter ?? getRuntimeAdapter();
|
|
998
|
+
if (adapter) return await adapter.transaction(async (transactionAdapter) => {
|
|
999
|
+
return await transactionAdapterStorage.run(transactionAdapter, async () => {
|
|
1000
|
+
return await callback({
|
|
1001
|
+
adapter: transactionAdapter,
|
|
1002
|
+
client: transactionClientStorage.getStore()
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
}, options);
|
|
1006
|
+
const client = getRuntimeClient();
|
|
1007
|
+
if (!client) throw new ArkormException("Cannot start a transaction without a configured runtime client or adapter.", {
|
|
1008
|
+
code: "CLIENT_NOT_CONFIGURED",
|
|
1009
|
+
operation: "transaction"
|
|
1010
|
+
});
|
|
1011
|
+
if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
|
|
1012
|
+
code: "TRANSACTION_NOT_SUPPORTED",
|
|
1013
|
+
operation: "transaction"
|
|
1014
|
+
});
|
|
1015
|
+
return await client.$transaction(async (transactionClient) => {
|
|
1016
|
+
return await transactionClientStorage.run(transactionClient, async () => {
|
|
1017
|
+
return await callback({ client: transactionClient });
|
|
1018
|
+
});
|
|
1019
|
+
}, options);
|
|
1265
1020
|
};
|
|
1266
1021
|
/**
|
|
1267
|
-
*
|
|
1268
|
-
* holding its migration ids in application order.
|
|
1269
|
-
*
|
|
1270
|
-
* A "batch" is one `migrate` run. When no runs were recorded (e.g. legacy state
|
|
1271
|
-
* written before run tracking), each applied migration is treated as its own
|
|
1272
|
-
* batch so `--step` still behaves predictably.
|
|
1022
|
+
* Get the configured pagination URL driver factory from runtime config.
|
|
1273
1023
|
*
|
|
1274
|
-
* @param state
|
|
1275
1024
|
* @returns
|
|
1276
1025
|
*/
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
run,
|
|
1281
|
-
index
|
|
1282
|
-
})).sort((left, right) => {
|
|
1283
|
-
const appliedAtOrder = left.run.appliedAt.localeCompare(right.run.appliedAt);
|
|
1284
|
-
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
1285
|
-
return left.index - right.index;
|
|
1286
|
-
}).map((entry) => entry.run.migrationIds);
|
|
1287
|
-
return state.migrations.map((migration) => [migration.id]);
|
|
1026
|
+
const getRuntimePaginationURLDriverFactory = () => {
|
|
1027
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
1028
|
+
return runtimePaginationURLDriverFactory;
|
|
1288
1029
|
};
|
|
1289
1030
|
/**
|
|
1290
|
-
*
|
|
1291
|
-
* for rollback — **reverse of the order they were applied** (most recent batch
|
|
1292
|
-
* first, and within each batch the migration that ran `up()` last runs `down()`
|
|
1293
|
-
* first), so a rollback exactly reverses how the migrations were applied.
|
|
1294
|
-
*
|
|
1295
|
-
* Defaults to a single batch (`migrate:rollback` with no `--step`); `--step=N`
|
|
1296
|
-
* rolls back the last N batches.
|
|
1031
|
+
* Get the configured current-page resolver from runtime config.
|
|
1297
1032
|
*
|
|
1298
|
-
* @param state
|
|
1299
|
-
* @param batches
|
|
1300
1033
|
* @returns
|
|
1301
1034
|
*/
|
|
1302
|
-
const
|
|
1303
|
-
if (
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1035
|
+
const getRuntimePaginationCurrentPageResolver = () => {
|
|
1036
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
1037
|
+
return runtimePaginationCurrentPageResolver;
|
|
1038
|
+
};
|
|
1039
|
+
const getRuntimeDebugHandler = () => {
|
|
1040
|
+
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
1041
|
+
return runtimeDebugHandler;
|
|
1042
|
+
};
|
|
1043
|
+
const emitRuntimeDebugEvent = (event) => {
|
|
1044
|
+
getRuntimeDebugHandler()?.(event);
|
|
1308
1045
|
};
|
|
1309
|
-
|
|
1310
|
-
//#endregion
|
|
1311
|
-
//#region src/database/ForeignKeyBuilder.ts
|
|
1312
1046
|
/**
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
* the referenced table and column, as well as actions to take on
|
|
1316
|
-
* delete and aliases for the relation.
|
|
1047
|
+
* Check if a given value matches Arkorm's query-schema contract
|
|
1048
|
+
* by verifying the presence of common delegate methods.
|
|
1317
1049
|
*
|
|
1318
|
-
* @
|
|
1319
|
-
* @
|
|
1050
|
+
* @param value The value to check.
|
|
1051
|
+
* @returns True if the value matches the query-schema contract, false otherwise.
|
|
1320
1052
|
*/
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
references(table, column) {
|
|
1333
|
-
this.foreignKey.referencesTable = table;
|
|
1334
|
-
this.foreignKey.referencesColumn = column;
|
|
1335
|
-
return this;
|
|
1336
|
-
}
|
|
1337
|
-
/**
|
|
1338
|
-
* Defines the action to take when a referenced record is deleted, such
|
|
1339
|
-
* as "CASCADE", "SET NULL", or "RESTRICT".
|
|
1340
|
-
*
|
|
1341
|
-
* @param action
|
|
1342
|
-
* @returns
|
|
1343
|
-
*/
|
|
1344
|
-
onDelete(action) {
|
|
1345
|
-
this.foreignKey.onDelete = action;
|
|
1346
|
-
return this;
|
|
1347
|
-
}
|
|
1348
|
-
/**
|
|
1349
|
-
* Defines an alias for the relation represented by this foreign key, which
|
|
1350
|
-
* can be used in the ORM for more intuitive access to related models.
|
|
1351
|
-
*
|
|
1352
|
-
* @param name
|
|
1353
|
-
* @returns
|
|
1354
|
-
*/
|
|
1355
|
-
alias(name) {
|
|
1356
|
-
this.foreignKey.relationAlias = name;
|
|
1357
|
-
return this;
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Defines an alias for the inverse relation represented by this foreign key.
|
|
1361
|
-
*
|
|
1362
|
-
* @param name
|
|
1363
|
-
* @returns
|
|
1364
|
-
*/
|
|
1365
|
-
inverseAlias(name) {
|
|
1366
|
-
this.foreignKey.inverseRelationAlias = name;
|
|
1367
|
-
return this;
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Defines an alias for the foreign key field itself, which can be
|
|
1371
|
-
* used in the ORM for more intuitive access to the foreign key value.
|
|
1372
|
-
*
|
|
1373
|
-
* @param fieldName
|
|
1374
|
-
* @returns
|
|
1375
|
-
*/
|
|
1376
|
-
as(fieldName) {
|
|
1377
|
-
this.foreignKey.fieldAlias = fieldName;
|
|
1378
|
-
return this;
|
|
1379
|
-
}
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
|
-
//#endregion
|
|
1383
|
-
//#region src/helpers/PrimaryKeyGenerationPlanner.ts
|
|
1384
|
-
var PrimaryKeyGenerationPlanner = class {
|
|
1385
|
-
static plan(column) {
|
|
1386
|
-
if (!column.primary || column.default !== void 0) return void 0;
|
|
1387
|
-
if (column.type === "uuid" || column.type === "string") return {
|
|
1388
|
-
strategy: "uuid",
|
|
1389
|
-
prismaDefault: "@default(uuid())",
|
|
1390
|
-
databaseDefault: column.type === "uuid" ? "gen_random_uuid()" : "gen_random_uuid()::text",
|
|
1391
|
-
runtimeFactory: "uuid"
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
static generate(generation) {
|
|
1395
|
-
if (generation?.runtimeFactory === "uuid") return (0, node_crypto.randomUUID)();
|
|
1396
|
-
}
|
|
1053
|
+
const isQuerySchemaLike = (value) => {
|
|
1054
|
+
if (!value || typeof value !== "object") return false;
|
|
1055
|
+
const candidate = value;
|
|
1056
|
+
return [
|
|
1057
|
+
"findMany",
|
|
1058
|
+
"findFirst",
|
|
1059
|
+
"create",
|
|
1060
|
+
"update",
|
|
1061
|
+
"delete",
|
|
1062
|
+
"count"
|
|
1063
|
+
].every((method) => typeof candidate[method] === "function");
|
|
1397
1064
|
};
|
|
1065
|
+
/**
|
|
1066
|
+
* @deprecated Use isQuerySchemaLike instead.
|
|
1067
|
+
*/
|
|
1068
|
+
const isDelegateLike = isQuerySchemaLike;
|
|
1069
|
+
loadArkormConfig();
|
|
1398
1070
|
|
|
1399
1071
|
//#endregion
|
|
1400
1072
|
//#region src/Expression.ts
|
|
@@ -3650,886 +3322,1230 @@ const normalizePersistedTimestampColumns = (value) => {
|
|
|
3650
3322
|
}, []);
|
|
3651
3323
|
return columns.length > 0 ? columns : void 0;
|
|
3652
3324
|
};
|
|
3653
|
-
const normalizePersistedColumnMappingsState = (state) => {
|
|
3654
|
-
return {
|
|
3655
|
-
version: 1,
|
|
3656
|
-
tables: Object.entries(state?.tables ?? {}).reduce((all, [tableName, tableMetadata]) => {
|
|
3657
|
-
if (tableName.trim().length === 0) return all;
|
|
3658
|
-
const normalized = normalizePersistedTableMetadata(tableMetadata);
|
|
3659
|
-
if (Object.keys(normalized.columns).length > 0 || Object.keys(normalized.enums).length > 0 || normalized.primaryKeyGeneration || normalized.timestampColumns?.length) all[tableName] = normalized;
|
|
3325
|
+
const normalizePersistedColumnMappingsState = (state) => {
|
|
3326
|
+
return {
|
|
3327
|
+
version: 1,
|
|
3328
|
+
tables: Object.entries(state?.tables ?? {}).reduce((all, [tableName, tableMetadata]) => {
|
|
3329
|
+
if (tableName.trim().length === 0) return all;
|
|
3330
|
+
const normalized = normalizePersistedTableMetadata(tableMetadata);
|
|
3331
|
+
if (Object.keys(normalized.columns).length > 0 || Object.keys(normalized.enums).length > 0 || normalized.primaryKeyGeneration || normalized.timestampColumns?.length) all[tableName] = normalized;
|
|
3332
|
+
return all;
|
|
3333
|
+
}, {})
|
|
3334
|
+
};
|
|
3335
|
+
};
|
|
3336
|
+
const buildPersistedFeatureDisabledError = (feature, table) => {
|
|
3337
|
+
return new ArkormException(`Table [${table}] requires ${feature === "persistedColumnMappings" ? "persisted column mappings" : "persisted enum metadata"}, but ${feature === "persistedColumnMappings" ? "features.persistedColumnMappings" : "features.persistedEnums"} is disabled in arkormx.config.*.`, {
|
|
3338
|
+
operation: "metadata.persisted",
|
|
3339
|
+
meta: {
|
|
3340
|
+
feature,
|
|
3341
|
+
table
|
|
3342
|
+
}
|
|
3343
|
+
});
|
|
3344
|
+
};
|
|
3345
|
+
const assertPersistedTableMetadataEnabled = (table, metadata, features, strict) => {
|
|
3346
|
+
if (!strict) return;
|
|
3347
|
+
if (!features.persistedColumnMappings && Object.keys(metadata.columns).length > 0) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
|
|
3348
|
+
if (!features.persistedEnums && Object.keys(metadata.enums).length > 0) throw buildPersistedFeatureDisabledError("persistedEnums", table);
|
|
3349
|
+
};
|
|
3350
|
+
const buildEnumUnionType = (values) => {
|
|
3351
|
+
return values.map((value) => {
|
|
3352
|
+
return `'${value.replace(/'/g, String.raw`\'`)}'`;
|
|
3353
|
+
}).join(" | ");
|
|
3354
|
+
};
|
|
3355
|
+
const resetPersistedColumnMappingsCache = () => {
|
|
3356
|
+
cachedColumnMappingsPath = void 0;
|
|
3357
|
+
cachedColumnMappingsState = void 0;
|
|
3358
|
+
};
|
|
3359
|
+
const readPersistedColumnMappingsState = (filePath) => {
|
|
3360
|
+
if (cachedColumnMappingsPath === filePath && cachedColumnMappingsState) return cachedColumnMappingsState;
|
|
3361
|
+
if (!(0, node_fs.existsSync)(filePath)) {
|
|
3362
|
+
const empty = createEmptyPersistedColumnMappingsState();
|
|
3363
|
+
cachedColumnMappingsPath = filePath;
|
|
3364
|
+
cachedColumnMappingsState = empty;
|
|
3365
|
+
return empty;
|
|
3366
|
+
}
|
|
3367
|
+
try {
|
|
3368
|
+
const normalized = normalizePersistedColumnMappingsState(JSON.parse((0, node_fs.readFileSync)(filePath, "utf-8")));
|
|
3369
|
+
cachedColumnMappingsPath = filePath;
|
|
3370
|
+
cachedColumnMappingsState = normalized;
|
|
3371
|
+
return normalized;
|
|
3372
|
+
} catch {
|
|
3373
|
+
const empty = createEmptyPersistedColumnMappingsState();
|
|
3374
|
+
cachedColumnMappingsPath = filePath;
|
|
3375
|
+
cachedColumnMappingsState = empty;
|
|
3376
|
+
return empty;
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
const writePersistedColumnMappingsState = (filePath, state) => {
|
|
3380
|
+
const normalized = normalizePersistedColumnMappingsState(state);
|
|
3381
|
+
const directory = (0, node_path.dirname)(filePath);
|
|
3382
|
+
if (!(0, node_fs.existsSync)(directory)) (0, node_fs.mkdirSync)(directory, { recursive: true });
|
|
3383
|
+
(0, node_fs.writeFileSync)(filePath, JSON.stringify(normalized, null, 2));
|
|
3384
|
+
cachedColumnMappingsPath = filePath;
|
|
3385
|
+
cachedColumnMappingsState = normalized;
|
|
3386
|
+
};
|
|
3387
|
+
const deletePersistedColumnMappingsState = (filePath) => {
|
|
3388
|
+
if ((0, node_fs.existsSync)(filePath)) (0, node_fs.rmSync)(filePath, { force: true });
|
|
3389
|
+
resetPersistedColumnMappingsCache();
|
|
3390
|
+
};
|
|
3391
|
+
const getPersistedTableMetadata = (table, options = {}) => {
|
|
3392
|
+
const metadata = readPersistedColumnMappingsState(resolveColumnMappingsFilePath(options.cwd ?? process.cwd(), options.configuredPath)).tables[table] ?? {
|
|
3393
|
+
columns: {},
|
|
3394
|
+
enums: {}
|
|
3395
|
+
};
|
|
3396
|
+
assertPersistedTableMetadataEnabled(table, metadata, options.features ?? resolvePersistedMetadataFeatures(), options.strict ?? false);
|
|
3397
|
+
return {
|
|
3398
|
+
columns: { ...metadata.columns },
|
|
3399
|
+
enums: Object.entries(metadata.enums).reduce((all, [columnName, values]) => {
|
|
3400
|
+
all[columnName] = [...values];
|
|
3401
|
+
return all;
|
|
3402
|
+
}, {}),
|
|
3403
|
+
primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
|
|
3404
|
+
timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
|
|
3405
|
+
};
|
|
3406
|
+
};
|
|
3407
|
+
const getPersistedColumnMap = (table, options = {}) => {
|
|
3408
|
+
return getPersistedTableMetadata(table, options).columns;
|
|
3409
|
+
};
|
|
3410
|
+
const getPersistedEnumMap = (table, options = {}) => {
|
|
3411
|
+
return getPersistedTableMetadata(table, options).enums;
|
|
3412
|
+
};
|
|
3413
|
+
const getPersistedPrimaryKeyGeneration = (table, options = {}) => {
|
|
3414
|
+
return getPersistedTableMetadata(table, options).primaryKeyGeneration;
|
|
3415
|
+
};
|
|
3416
|
+
const getPersistedTimestampColumns = (table, options = {}) => {
|
|
3417
|
+
return getPersistedTableMetadata(table, options).timestampColumns ?? [];
|
|
3418
|
+
};
|
|
3419
|
+
const applyMappedColumn = (tableColumns, column, features, table) => {
|
|
3420
|
+
if (typeof column.map === "string" && column.map.trim().length > 0 && column.map !== column.name) {
|
|
3421
|
+
if (!features.persistedColumnMappings) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
|
|
3422
|
+
tableColumns[column.name] = column.map;
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
delete tableColumns[column.name];
|
|
3426
|
+
};
|
|
3427
|
+
const applyEnumColumn = (tableEnums, column, features, table) => {
|
|
3428
|
+
const values = column.enumValues ?? [];
|
|
3429
|
+
if (column.type === "enum" && values.length > 0) {
|
|
3430
|
+
if (!features.persistedEnums) throw buildPersistedFeatureDisabledError("persistedEnums", table);
|
|
3431
|
+
tableEnums[column.name] = [...values];
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
delete tableEnums[column.name];
|
|
3435
|
+
};
|
|
3436
|
+
const removePersistedColumnMetadata = (tableMetadata, columnName) => {
|
|
3437
|
+
delete tableMetadata.columns[columnName];
|
|
3438
|
+
delete tableMetadata.enums[columnName];
|
|
3439
|
+
Object.entries(tableMetadata.columns).forEach(([attribute, mappedColumn]) => {
|
|
3440
|
+
if (mappedColumn === columnName) delete tableMetadata.columns[attribute];
|
|
3441
|
+
});
|
|
3442
|
+
if (tableMetadata.primaryKeyGeneration?.column === columnName) delete tableMetadata.primaryKeyGeneration;
|
|
3443
|
+
if (tableMetadata.timestampColumns) {
|
|
3444
|
+
tableMetadata.timestampColumns = tableMetadata.timestampColumns.filter((column) => column.column !== columnName);
|
|
3445
|
+
if (tableMetadata.timestampColumns.length === 0) delete tableMetadata.timestampColumns;
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
const applyPrimaryKeyGeneration = (tableMetadata, column) => {
|
|
3449
|
+
if (!column.primary || !column.primaryKeyGeneration) {
|
|
3450
|
+
if (tableMetadata.primaryKeyGeneration?.column === column.name) delete tableMetadata.primaryKeyGeneration;
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
tableMetadata.primaryKeyGeneration = {
|
|
3454
|
+
column: column.name,
|
|
3455
|
+
...column.primaryKeyGeneration
|
|
3456
|
+
};
|
|
3457
|
+
};
|
|
3458
|
+
const applyTimestampColumn = (tableMetadata, column) => {
|
|
3459
|
+
if (column.type !== "timestamp" || column.default !== "now()" && column.updatedAt !== true) {
|
|
3460
|
+
if (tableMetadata.timestampColumns) {
|
|
3461
|
+
tableMetadata.timestampColumns = tableMetadata.timestampColumns.filter((entry) => entry.column !== column.name);
|
|
3462
|
+
if (tableMetadata.timestampColumns.length === 0) delete tableMetadata.timestampColumns;
|
|
3463
|
+
}
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
const nextColumn = {
|
|
3467
|
+
column: column.name,
|
|
3468
|
+
...column.default === "now()" ? { default: "now()" } : {},
|
|
3469
|
+
...column.updatedAt ? { updatedAt: true } : {}
|
|
3470
|
+
};
|
|
3471
|
+
tableMetadata.timestampColumns = [...(tableMetadata.timestampColumns ?? []).filter((entry) => entry.column !== column.name), nextColumn];
|
|
3472
|
+
};
|
|
3473
|
+
const applyOperationsToPersistedColumnMappingsState = (state, operations, features = resolvePersistedMetadataFeatures()) => {
|
|
3474
|
+
const nextTables = Object.entries(state.tables).reduce((all, [table, metadata]) => {
|
|
3475
|
+
all[table] = {
|
|
3476
|
+
columns: { ...metadata.columns },
|
|
3477
|
+
enums: Object.entries(metadata.enums).reduce((nextEnums, [columnName, values]) => {
|
|
3478
|
+
nextEnums[columnName] = [...values];
|
|
3479
|
+
return nextEnums;
|
|
3480
|
+
}, {}),
|
|
3481
|
+
primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
|
|
3482
|
+
timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
|
|
3483
|
+
};
|
|
3484
|
+
return all;
|
|
3485
|
+
}, {});
|
|
3486
|
+
operations.forEach((operation) => {
|
|
3487
|
+
if (operation.type === "createTable") {
|
|
3488
|
+
const tableMetadata = nextTables[operation.table] ?? {
|
|
3489
|
+
columns: {},
|
|
3490
|
+
enums: {}
|
|
3491
|
+
};
|
|
3492
|
+
operation.columns.forEach((column) => {
|
|
3493
|
+
applyMappedColumn(tableMetadata.columns, column, features, operation.table);
|
|
3494
|
+
applyEnumColumn(tableMetadata.enums, column, features, operation.table);
|
|
3495
|
+
applyPrimaryKeyGeneration(tableMetadata, column);
|
|
3496
|
+
applyTimestampColumn(tableMetadata, column);
|
|
3497
|
+
});
|
|
3498
|
+
if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0 || tableMetadata.primaryKeyGeneration || tableMetadata.timestampColumns?.length) nextTables[operation.table] = tableMetadata;
|
|
3499
|
+
else delete nextTables[operation.table];
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
if (operation.type === "alterTable") {
|
|
3503
|
+
const tableMetadata = nextTables[operation.table] ?? {
|
|
3504
|
+
columns: {},
|
|
3505
|
+
enums: {}
|
|
3506
|
+
};
|
|
3507
|
+
operation.addColumns.forEach((column) => {
|
|
3508
|
+
applyMappedColumn(tableMetadata.columns, column, features, operation.table);
|
|
3509
|
+
applyEnumColumn(tableMetadata.enums, column, features, operation.table);
|
|
3510
|
+
applyPrimaryKeyGeneration(tableMetadata, column);
|
|
3511
|
+
applyTimestampColumn(tableMetadata, column);
|
|
3512
|
+
});
|
|
3513
|
+
operation.dropColumns.forEach((columnName) => {
|
|
3514
|
+
removePersistedColumnMetadata(tableMetadata, columnName);
|
|
3515
|
+
});
|
|
3516
|
+
if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0 || tableMetadata.primaryKeyGeneration || tableMetadata.timestampColumns?.length) nextTables[operation.table] = tableMetadata;
|
|
3517
|
+
else delete nextTables[operation.table];
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
delete nextTables[operation.table];
|
|
3521
|
+
});
|
|
3522
|
+
return {
|
|
3523
|
+
version: 1,
|
|
3524
|
+
tables: nextTables
|
|
3525
|
+
};
|
|
3526
|
+
};
|
|
3527
|
+
const rebuildPersistedColumnMappingsState = async (state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
|
|
3528
|
+
const availableByIdentity = new Map(availableMigrations.map(([migrationClass, file]) => [buildMigrationIdentity(file, migrationClass.name), migrationClass]));
|
|
3529
|
+
let nextState = createEmptyPersistedColumnMappingsState();
|
|
3530
|
+
const orderedMigrations = state.migrations.map((migration, index) => ({
|
|
3531
|
+
migration,
|
|
3532
|
+
index
|
|
3533
|
+
})).sort((left, right) => {
|
|
3534
|
+
const appliedAtOrder = left.migration.appliedAt.localeCompare(right.migration.appliedAt);
|
|
3535
|
+
if (appliedAtOrder !== 0) return appliedAtOrder;
|
|
3536
|
+
return left.index - right.index;
|
|
3537
|
+
});
|
|
3538
|
+
for (const { migration } of orderedMigrations) {
|
|
3539
|
+
const migrationClass = availableByIdentity.get(migration.id);
|
|
3540
|
+
if (!migrationClass) throw new ArkormException(`Unable to rebuild persisted column mappings because migration [${migration.id}] could not be resolved from the current migration files.`, {
|
|
3541
|
+
operation: "migration.columnMappings",
|
|
3542
|
+
meta: {
|
|
3543
|
+
migrationId: migration.id,
|
|
3544
|
+
file: migration.file,
|
|
3545
|
+
className: migration.className
|
|
3546
|
+
}
|
|
3547
|
+
});
|
|
3548
|
+
const operations = await getMigrationPlan(migrationClass, "up", { inert: true });
|
|
3549
|
+
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
3550
|
+
}
|
|
3551
|
+
return nextState;
|
|
3552
|
+
};
|
|
3553
|
+
const syncPersistedColumnMappingsFromState = async (cwd, state, availableMigrations, features = resolvePersistedMetadataFeatures()) => {
|
|
3554
|
+
const filePath = resolveColumnMappingsFilePath(cwd);
|
|
3555
|
+
const nextState = await rebuildPersistedColumnMappingsState(state, availableMigrations, features);
|
|
3556
|
+
if (Object.keys(nextState.tables).length === 0) {
|
|
3557
|
+
deletePersistedColumnMappingsState(filePath);
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
writePersistedColumnMappingsState(filePath, nextState);
|
|
3561
|
+
};
|
|
3562
|
+
const validatePersistedMetadataFeaturesForMigrations = async (migrations, features = resolvePersistedMetadataFeatures()) => {
|
|
3563
|
+
let nextState = createEmptyPersistedColumnMappingsState();
|
|
3564
|
+
for (const [migrationClass] of migrations) {
|
|
3565
|
+
const operations = await getMigrationPlan(migrationClass, "up", { inert: true });
|
|
3566
|
+
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
3569
|
+
const getPersistedEnumTsType = (values) => {
|
|
3570
|
+
return buildEnumUnionType(values);
|
|
3571
|
+
};
|
|
3572
|
+
|
|
3573
|
+
//#endregion
|
|
3574
|
+
//#region src/relationship/RelationTableLoader.ts
|
|
3575
|
+
/**
|
|
3576
|
+
* Utility class responsible for loading data from relation tables, which are used to
|
|
3577
|
+
* manage relationships between models in Arkorm.
|
|
3578
|
+
*
|
|
3579
|
+
* @author Legacy (3m1n3nc3)
|
|
3580
|
+
* @since 2.0.0-next.0
|
|
3581
|
+
*/
|
|
3582
|
+
var RelationTableLoader = class {
|
|
3583
|
+
constructor(adapter) {
|
|
3584
|
+
this.adapter = adapter;
|
|
3585
|
+
}
|
|
3586
|
+
buildTarget(table) {
|
|
3587
|
+
return {
|
|
3588
|
+
table,
|
|
3589
|
+
columns: getPersistedTableMetadata(table, { features: resolvePersistedMetadataFeatures(getUserConfig("features")) }).columns
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
async selectRows(spec) {
|
|
3593
|
+
return await this.adapter.select({
|
|
3594
|
+
target: this.buildTarget(spec.table),
|
|
3595
|
+
where: spec.where,
|
|
3596
|
+
columns: spec.columns,
|
|
3597
|
+
orderBy: spec.orderBy,
|
|
3598
|
+
limit: spec.limit,
|
|
3599
|
+
offset: spec.offset
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
async selectRow(spec) {
|
|
3603
|
+
return await this.adapter.selectOne({
|
|
3604
|
+
target: this.buildTarget(spec.table),
|
|
3605
|
+
where: spec.where,
|
|
3606
|
+
columns: spec.columns,
|
|
3607
|
+
orderBy: spec.orderBy,
|
|
3608
|
+
limit: spec.limit ?? 1,
|
|
3609
|
+
offset: spec.offset
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
async selectColumnValues(spec) {
|
|
3613
|
+
return (await this.selectRows({
|
|
3614
|
+
...spec.lookup,
|
|
3615
|
+
columns: [{ column: spec.column }]
|
|
3616
|
+
})).map((row) => row[spec.column]);
|
|
3617
|
+
}
|
|
3618
|
+
async selectColumnValue(spec) {
|
|
3619
|
+
return (await this.selectRow({
|
|
3620
|
+
...spec.lookup,
|
|
3621
|
+
columns: [{ column: spec.column }],
|
|
3622
|
+
limit: 1
|
|
3623
|
+
}))?.[spec.column] ?? null;
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
|
|
3627
|
+
//#endregion
|
|
3628
|
+
//#region src/relationship/SetBasedEagerLoader.ts
|
|
3629
|
+
/**
|
|
3630
|
+
* Utility class responsible for performing set-based eager loading of relationships for
|
|
3631
|
+
* a collection of models.
|
|
3632
|
+
*
|
|
3633
|
+
* @author Legacy (3m1n3nc3)
|
|
3634
|
+
* @since 2.0.0-next.2
|
|
3635
|
+
*/
|
|
3636
|
+
var SetBasedEagerLoader = class SetBasedEagerLoader {
|
|
3637
|
+
constructor(models, relations) {
|
|
3638
|
+
this.models = models;
|
|
3639
|
+
this.relations = relations;
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Performs eager loading of all specified relationships for the set of models.
|
|
3643
|
+
*
|
|
3644
|
+
* @returns
|
|
3645
|
+
*/
|
|
3646
|
+
async load() {
|
|
3647
|
+
if (this.models.length === 0) return;
|
|
3648
|
+
const relationTree = this.buildRelationTree(this.relations);
|
|
3649
|
+
await Promise.all(Array.from(relationTree.entries()).map(async ([name, node]) => {
|
|
3650
|
+
await this.loadRelationNode(name, node);
|
|
3651
|
+
}));
|
|
3652
|
+
}
|
|
3653
|
+
async loadRelationNode(name, node) {
|
|
3654
|
+
await this.loadRelation(name, node.constraint);
|
|
3655
|
+
if (node.children.size === 0) return;
|
|
3656
|
+
const relatedModels = this.collectLoadedRelationModels(name);
|
|
3657
|
+
if (relatedModels.length === 0) return;
|
|
3658
|
+
await new SetBasedEagerLoader(relatedModels, this.relationTreeToMap(node.children)).load();
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Loads a specific relationship for the set of models based on the relationship name
|
|
3662
|
+
* and an optional constraint.
|
|
3663
|
+
*
|
|
3664
|
+
* @param name The name of the relationship to load.
|
|
3665
|
+
* @param constraint An optional constraint to apply to the query.
|
|
3666
|
+
* @returns A promise that resolves when the relationship is loaded.
|
|
3667
|
+
*/
|
|
3668
|
+
async loadRelation(name, constraint) {
|
|
3669
|
+
const resolver = this.resolveRelationResolver(name);
|
|
3670
|
+
if (!resolver) return;
|
|
3671
|
+
const metadata = resolver.call(this.models[0]).getMetadata();
|
|
3672
|
+
switch (metadata.type) {
|
|
3673
|
+
case "belongsTo":
|
|
3674
|
+
await this.loadBelongsTo(name, resolver, metadata, constraint);
|
|
3675
|
+
return;
|
|
3676
|
+
case "belongsToMany":
|
|
3677
|
+
await this.loadBelongsToMany(name, metadata, constraint);
|
|
3678
|
+
return;
|
|
3679
|
+
case "hasMany":
|
|
3680
|
+
await this.loadHasMany(name, metadata, constraint);
|
|
3681
|
+
return;
|
|
3682
|
+
case "hasOne":
|
|
3683
|
+
await this.loadHasOne(name, resolver, metadata, constraint);
|
|
3684
|
+
return;
|
|
3685
|
+
case "hasManyThrough":
|
|
3686
|
+
await this.loadHasManyThrough(name, metadata, constraint);
|
|
3687
|
+
return;
|
|
3688
|
+
case "hasOneThrough":
|
|
3689
|
+
await this.loadHasOneThrough(name, resolver, metadata, constraint);
|
|
3690
|
+
return;
|
|
3691
|
+
case "morphTo":
|
|
3692
|
+
await this.loadMorphTo(name, metadata, constraint);
|
|
3693
|
+
return;
|
|
3694
|
+
case "morphOne":
|
|
3695
|
+
await this.loadMorphOne(name, resolver, metadata, constraint);
|
|
3696
|
+
return;
|
|
3697
|
+
case "morphMany":
|
|
3698
|
+
await this.loadMorphMany(name, metadata, constraint);
|
|
3699
|
+
return;
|
|
3700
|
+
case "morphToMany":
|
|
3701
|
+
case "morphedByMany":
|
|
3702
|
+
await this.loadPolymorphicManyToMany(name, metadata, constraint);
|
|
3703
|
+
return;
|
|
3704
|
+
default: await this.loadIndividually(name, resolver, constraint);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
/**
|
|
3708
|
+
* Resolves the relation resolver function for a given relationship name by inspecting
|
|
3709
|
+
* the first model in the set.
|
|
3710
|
+
*
|
|
3711
|
+
* @param name The name of the relationship to resolve.
|
|
3712
|
+
* @returns The relation resolver function or null if not found.
|
|
3713
|
+
*/
|
|
3714
|
+
resolveRelationResolver(name) {
|
|
3715
|
+
const resolver = this.models[0][name];
|
|
3716
|
+
if (typeof resolver !== "function") {
|
|
3717
|
+
const modelName = this.models[0].constructor?.name ?? "Model";
|
|
3718
|
+
throw new RelationResolutionException(`Relation [${name}] is not defined on the model.`, {
|
|
3719
|
+
operation: "eagerLoad",
|
|
3720
|
+
model: modelName,
|
|
3721
|
+
relation: name
|
|
3722
|
+
});
|
|
3723
|
+
}
|
|
3724
|
+
return resolver;
|
|
3725
|
+
}
|
|
3726
|
+
buildRelationTree(relations) {
|
|
3727
|
+
const tree = /* @__PURE__ */ new Map();
|
|
3728
|
+
Object.entries(relations).forEach(([path, constraint]) => {
|
|
3729
|
+
const segments = path.split(".").map((segment) => segment.trim()).filter((segment) => segment.length > 0);
|
|
3730
|
+
if (segments.length === 0) return;
|
|
3731
|
+
let current = tree;
|
|
3732
|
+
segments.forEach((segment, index) => {
|
|
3733
|
+
const existing = current.get(segment) ?? {
|
|
3734
|
+
constraint: void 0,
|
|
3735
|
+
children: /* @__PURE__ */ new Map()
|
|
3736
|
+
};
|
|
3737
|
+
if (index === segments.length - 1 && constraint) existing.constraint = constraint;
|
|
3738
|
+
current.set(segment, existing);
|
|
3739
|
+
current = existing.children;
|
|
3740
|
+
});
|
|
3741
|
+
});
|
|
3742
|
+
return tree;
|
|
3743
|
+
}
|
|
3744
|
+
relationTreeToMap(tree, prefix = "") {
|
|
3745
|
+
return Array.from(tree.entries()).reduce((all, [name, node]) => {
|
|
3746
|
+
const path = prefix ? `${prefix}.${name}` : name;
|
|
3747
|
+
all[path] = node.constraint;
|
|
3748
|
+
Object.assign(all, this.relationTreeToMap(node.children, path));
|
|
3749
|
+
return all;
|
|
3750
|
+
}, {});
|
|
3751
|
+
}
|
|
3752
|
+
collectLoadedRelationModels(name) {
|
|
3753
|
+
return this.models.reduce((all, model) => {
|
|
3754
|
+
const loaded = model.getAttribute(name);
|
|
3755
|
+
if (loaded instanceof ArkormCollection) {
|
|
3756
|
+
loaded.all().forEach((item) => {
|
|
3757
|
+
if (this.isEagerLoadableModel(item)) all.push(item);
|
|
3758
|
+
});
|
|
3759
|
+
return all;
|
|
3760
|
+
}
|
|
3761
|
+
if (this.isEagerLoadableModel(loaded)) all.push(loaded);
|
|
3762
|
+
return all;
|
|
3763
|
+
}, []);
|
|
3764
|
+
}
|
|
3765
|
+
isEagerLoadableModel(value) {
|
|
3766
|
+
return typeof value === "object" && value !== null && typeof value.getAttribute === "function" && typeof value.setLoadedRelation === "function";
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Loads a "belongs to" relationship for the set of models.
|
|
3770
|
+
*
|
|
3771
|
+
* @param name The name of the relationship to load.
|
|
3772
|
+
* @param resolver The relation resolver function.
|
|
3773
|
+
* @param metadata The metadata for the relationship.
|
|
3774
|
+
* @param constraint An optional constraint to apply to the query.
|
|
3775
|
+
* @returns A promise that resolves when the relationship is loaded.
|
|
3776
|
+
*/
|
|
3777
|
+
async loadBelongsTo(name, resolver, metadata, constraint) {
|
|
3778
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.foreignKey));
|
|
3779
|
+
if (keys.length === 0) {
|
|
3780
|
+
this.models.forEach((model) => {
|
|
3781
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
3782
|
+
});
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
let query = metadata.relatedModel.query().whereIn(metadata.ownerKey, keys);
|
|
3786
|
+
query = this.applyConstraint(query, constraint);
|
|
3787
|
+
const relatedModels = await this.getPartitionedModels(query, metadata.ownerKey, keys);
|
|
3788
|
+
const relatedByOwnerKey = /* @__PURE__ */ new Map();
|
|
3789
|
+
relatedModels.forEach((related) => {
|
|
3790
|
+
const value = this.readModelAttribute(related, metadata.ownerKey);
|
|
3791
|
+
if (value == null) return;
|
|
3792
|
+
const lookupKey = this.toLookupKey(value);
|
|
3793
|
+
if (!relatedByOwnerKey.has(lookupKey)) relatedByOwnerKey.set(lookupKey, related);
|
|
3794
|
+
});
|
|
3795
|
+
this.models.forEach((model) => {
|
|
3796
|
+
const foreignValue = model.getAttribute(metadata.foreignKey);
|
|
3797
|
+
const relationValue = foreignValue == null ? void 0 : relatedByOwnerKey.get(this.toLookupKey(foreignValue));
|
|
3798
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Loads a "has many" relationship for the set of models.
|
|
3803
|
+
*
|
|
3804
|
+
* @param name
|
|
3805
|
+
* @param metadata
|
|
3806
|
+
* @param constraint
|
|
3807
|
+
* @returns
|
|
3808
|
+
*/
|
|
3809
|
+
async loadHasMany(name, metadata, constraint) {
|
|
3810
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
3811
|
+
if (keys.length === 0) {
|
|
3812
|
+
this.models.forEach((model) => {
|
|
3813
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
3814
|
+
});
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
3817
|
+
let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
|
|
3818
|
+
query = this.applyConstraint(query, constraint);
|
|
3819
|
+
const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
|
|
3820
|
+
const relatedByForeignKey = /* @__PURE__ */ new Map();
|
|
3821
|
+
relatedModels.forEach((related) => {
|
|
3822
|
+
const value = this.readModelAttribute(related, metadata.foreignKey);
|
|
3823
|
+
if (value == null) return;
|
|
3824
|
+
const lookupKey = this.toLookupKey(value);
|
|
3825
|
+
const bucket = relatedByForeignKey.get(lookupKey) ?? [];
|
|
3826
|
+
bucket.push(related);
|
|
3827
|
+
relatedByForeignKey.set(lookupKey, bucket);
|
|
3828
|
+
});
|
|
3829
|
+
this.models.forEach((model) => {
|
|
3830
|
+
const localValue = model.getAttribute(metadata.localKey);
|
|
3831
|
+
const related = localValue == null ? [] : relatedByForeignKey.get(this.toLookupKey(localValue)) ?? [];
|
|
3832
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
3835
|
+
/**
|
|
3836
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
3837
|
+
*
|
|
3838
|
+
* @param name
|
|
3839
|
+
* @param metadata
|
|
3840
|
+
* @param constraint
|
|
3841
|
+
* @returns
|
|
3842
|
+
*/
|
|
3843
|
+
async loadBelongsToMany(name, metadata, constraint) {
|
|
3844
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
|
|
3845
|
+
if (parentKeys.length === 0) {
|
|
3846
|
+
this.models.forEach((model) => {
|
|
3847
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
3848
|
+
});
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
3851
|
+
const pivotRows = await this.createRelationTableLoader().selectRows({
|
|
3852
|
+
table: metadata.throughTable,
|
|
3853
|
+
where: this.buildBelongsToManyPivotWhere(metadata, parentKeys),
|
|
3854
|
+
columns: this.getBelongsToManyPivotColumns(metadata).map((column) => ({ column }))
|
|
3855
|
+
});
|
|
3856
|
+
const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
|
|
3857
|
+
if (relatedIds.length === 0) {
|
|
3858
|
+
this.models.forEach((model) => {
|
|
3859
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
3860
|
+
});
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
|
|
3864
|
+
query = this.applyConstraint(query, constraint);
|
|
3865
|
+
if (query.hasEagerLoadPagination() && parentKeys.length > 1 && await this.loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows)) return;
|
|
3866
|
+
const relatedModels = (await query.get()).all();
|
|
3867
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
3868
|
+
relatedModels.forEach((related) => {
|
|
3869
|
+
const relatedValue = this.readModelAttribute(related, metadata.relatedKey);
|
|
3870
|
+
if (relatedValue == null) return;
|
|
3871
|
+
relatedByKey.set(this.toLookupKey(relatedValue), related);
|
|
3872
|
+
});
|
|
3873
|
+
const relatedKeysByParent = /* @__PURE__ */ new Map();
|
|
3874
|
+
const pivotByParentAndRelated = /* @__PURE__ */ new Map();
|
|
3875
|
+
pivotRows.forEach((row) => {
|
|
3876
|
+
const parentValue = row[metadata.foreignPivotKey];
|
|
3877
|
+
const relatedValue = row[metadata.relatedPivotKey];
|
|
3878
|
+
if (parentValue == null || relatedValue == null) return;
|
|
3879
|
+
const bucket = relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
3880
|
+
bucket.push(relatedValue);
|
|
3881
|
+
relatedKeysByParent.set(this.toLookupKey(parentValue), bucket);
|
|
3882
|
+
pivotByParentAndRelated.set(`${this.toLookupKey(parentValue)}:${this.toLookupKey(relatedValue)}`, row);
|
|
3883
|
+
});
|
|
3884
|
+
this.models.forEach((model) => {
|
|
3885
|
+
const parentValue = model.getAttribute(metadata.parentKey);
|
|
3886
|
+
const related = (parentValue == null ? [] : relatedKeysByParent.get(this.toLookupKey(parentValue)) ?? []).reduce((all, relatedValue) => {
|
|
3887
|
+
const candidate = relatedByKey.get(this.toLookupKey(relatedValue));
|
|
3888
|
+
if (candidate) all.push(this.attachBelongsToManyPivot(metadata, candidate, pivotByParentAndRelated.get(`${this.toLookupKey(parentValue)}:${this.toLookupKey(relatedValue)}`)));
|
|
3889
|
+
return all;
|
|
3890
|
+
}, []);
|
|
3891
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
async loadLimitedBelongsToMany(name, metadata, query, parentKeys, pivotRows) {
|
|
3895
|
+
if (metadata.pivotWhere) {
|
|
3896
|
+
await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
|
|
3897
|
+
return true;
|
|
3898
|
+
}
|
|
3899
|
+
const pivotColumns = this.getBelongsToManyPivotColumns(metadata);
|
|
3900
|
+
const pivotAliases = new Map(pivotColumns.map((column, index) => [column, `__arkorm_pivot_${index}`]));
|
|
3901
|
+
const parentAlias = pivotAliases.get(metadata.foreignPivotKey);
|
|
3902
|
+
const throughTable = metadata.throughTable;
|
|
3903
|
+
const relatedTable = metadata.relatedModel.getTable();
|
|
3904
|
+
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${metadata.foreignPivotKey}`, parentKeys).addSelect({ ...Object.fromEntries([...pivotAliases].map(([column, alias]) => [`${throughTable}.${column}`, alias])) }).getRowsForEagerLoad([`${throughTable}.${metadata.foreignPivotKey}`]);
|
|
3905
|
+
if (!rows) {
|
|
3906
|
+
await this.loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows);
|
|
3907
|
+
return true;
|
|
3908
|
+
}
|
|
3909
|
+
const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
|
|
3910
|
+
const relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
|
|
3911
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
3912
|
+
relatedModels.forEach((related) => {
|
|
3913
|
+
const key = this.readModelAttribute(related, metadata.relatedKey);
|
|
3914
|
+
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
3915
|
+
});
|
|
3916
|
+
const relatedByParent = /* @__PURE__ */ new Map();
|
|
3917
|
+
rows.forEach((row) => {
|
|
3918
|
+
const parentKey = row[parentAlias];
|
|
3919
|
+
const relatedKey = row[metadata.relatedKey];
|
|
3920
|
+
if (parentKey == null || relatedKey == null) return;
|
|
3921
|
+
const related = relatedByKey.get(this.toLookupKey(relatedKey));
|
|
3922
|
+
if (!related) return;
|
|
3923
|
+
const pivot = Object.fromEntries([...pivotAliases].map(([column, alias]) => [column, row[alias]]));
|
|
3924
|
+
const bucket = relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
3925
|
+
bucket.push(this.attachBelongsToManyPivot(metadata, related, pivot));
|
|
3926
|
+
relatedByParent.set(this.toLookupKey(parentKey), bucket);
|
|
3927
|
+
});
|
|
3928
|
+
this.models.forEach((model) => {
|
|
3929
|
+
const parentKey = model.getAttribute(metadata.parentKey);
|
|
3930
|
+
const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
3931
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
3932
|
+
});
|
|
3933
|
+
return true;
|
|
3934
|
+
}
|
|
3935
|
+
async loadLimitedBelongsToManyIndividually(name, metadata, query, pivotRows) {
|
|
3936
|
+
const rowsByParent = /* @__PURE__ */ new Map();
|
|
3937
|
+
pivotRows.forEach((row) => {
|
|
3938
|
+
const parentKey = row[metadata.foreignPivotKey];
|
|
3939
|
+
if (parentKey == null) return;
|
|
3940
|
+
const bucket = rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
3941
|
+
bucket.push(row);
|
|
3942
|
+
rowsByParent.set(this.toLookupKey(parentKey), bucket);
|
|
3943
|
+
});
|
|
3944
|
+
await Promise.all(this.models.map(async (model) => {
|
|
3945
|
+
const parentKey = model.getAttribute(metadata.parentKey);
|
|
3946
|
+
const rows = parentKey == null ? [] : rowsByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
3947
|
+
const relatedIds = this.collectUniqueRowValues(rows, metadata.relatedPivotKey);
|
|
3948
|
+
const related = relatedIds.length ? (await query.clone().whereIn(metadata.relatedKey, relatedIds).get()).all() : [];
|
|
3949
|
+
const pivotByRelated = new Map(rows.map((row) => [this.toLookupKey(row[metadata.relatedPivotKey]), row]));
|
|
3950
|
+
model.setLoadedRelation(name, new ArkormCollection(related.map((entry) => this.attachBelongsToManyPivot(metadata, entry, pivotByRelated.get(this.toLookupKey(this.readModelAttribute(entry, metadata.relatedKey)))))));
|
|
3951
|
+
}));
|
|
3952
|
+
}
|
|
3953
|
+
buildBelongsToManyPivotWhere(metadata, parentKeys) {
|
|
3954
|
+
const baseCondition = {
|
|
3955
|
+
type: "comparison",
|
|
3956
|
+
column: metadata.foreignPivotKey,
|
|
3957
|
+
operator: "in",
|
|
3958
|
+
value: parentKeys
|
|
3959
|
+
};
|
|
3960
|
+
if (!metadata.pivotWhere) return baseCondition;
|
|
3961
|
+
return {
|
|
3962
|
+
type: "group",
|
|
3963
|
+
operator: "and",
|
|
3964
|
+
conditions: [baseCondition, metadata.pivotWhere]
|
|
3965
|
+
};
|
|
3966
|
+
}
|
|
3967
|
+
getBelongsToManyPivotColumns(metadata) {
|
|
3968
|
+
return [
|
|
3969
|
+
metadata.foreignPivotKey,
|
|
3970
|
+
metadata.relatedPivotKey,
|
|
3971
|
+
...metadata.pivotColumns ?? []
|
|
3972
|
+
].filter((column, index, all) => all.indexOf(column) === index);
|
|
3973
|
+
}
|
|
3974
|
+
shouldAttachBelongsToManyPivot(metadata) {
|
|
3975
|
+
return Boolean(metadata.pivotModel) || Boolean(metadata.pivotCreatedAtColumn) || Boolean(metadata.pivotUpdatedAtColumn) || (metadata.pivotColumns?.length ?? 0) > 0 || Boolean(metadata.pivotAccessor);
|
|
3976
|
+
}
|
|
3977
|
+
createBelongsToManyPivotRecord(metadata, row) {
|
|
3978
|
+
const attributes = this.getBelongsToManyPivotColumns(metadata).reduce((all, column) => {
|
|
3979
|
+
all[column] = row[column];
|
|
3660
3980
|
return all;
|
|
3661
|
-
}, {})
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3981
|
+
}, {});
|
|
3982
|
+
if (!metadata.pivotModel) return attributes;
|
|
3983
|
+
if (typeof metadata.pivotModel.hydrate === "function") return metadata.pivotModel.hydrate(attributes);
|
|
3984
|
+
return new metadata.pivotModel(attributes);
|
|
3985
|
+
}
|
|
3986
|
+
attachBelongsToManyPivot(metadata, related, row) {
|
|
3987
|
+
if (!row || !this.shouldAttachBelongsToManyPivot(metadata)) return related;
|
|
3988
|
+
const rawReader = related;
|
|
3989
|
+
if (typeof rawReader.getRawAttributes !== "function" || typeof rawReader.setAttribute !== "function") return related;
|
|
3990
|
+
const cloned = metadata.relatedModel.hydrate(rawReader.getRawAttributes());
|
|
3991
|
+
cloned.setAttribute(metadata.pivotAccessor ?? "pivot", this.createBelongsToManyPivotRecord(metadata, row));
|
|
3992
|
+
return cloned;
|
|
3993
|
+
}
|
|
3994
|
+
/**
|
|
3995
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
3996
|
+
*
|
|
3997
|
+
* @param name
|
|
3998
|
+
* @param resolver
|
|
3999
|
+
* @param metadata
|
|
4000
|
+
* @param constraint
|
|
4001
|
+
* @returns
|
|
4002
|
+
*/
|
|
4003
|
+
async loadHasOne(name, resolver, metadata, constraint) {
|
|
4004
|
+
const keys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
4005
|
+
if (keys.length === 0) {
|
|
4006
|
+
this.models.forEach((model) => {
|
|
4007
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
4008
|
+
});
|
|
4009
|
+
return;
|
|
3670
4010
|
}
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
const
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
const
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
const
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
4011
|
+
let query = metadata.relatedModel.query().whereIn(metadata.foreignKey, keys);
|
|
4012
|
+
query = this.applyConstraint(query, constraint);
|
|
4013
|
+
const relatedModels = await this.getPartitionedModels(query, metadata.foreignKey, keys);
|
|
4014
|
+
const relatedByForeignKey = /* @__PURE__ */ new Map();
|
|
4015
|
+
relatedModels.forEach((related) => {
|
|
4016
|
+
const value = this.readModelAttribute(related, metadata.foreignKey);
|
|
4017
|
+
if (value == null) return;
|
|
4018
|
+
const lookupKey = this.toLookupKey(value);
|
|
4019
|
+
if (!relatedByForeignKey.has(lookupKey)) relatedByForeignKey.set(lookupKey, related);
|
|
4020
|
+
});
|
|
4021
|
+
this.models.forEach((model) => {
|
|
4022
|
+
const localValue = model.getAttribute(metadata.localKey);
|
|
4023
|
+
const relationValue = localValue == null ? void 0 : relatedByForeignKey.get(this.toLookupKey(localValue));
|
|
4024
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Loads a "has many through" relationship for the set of models.
|
|
4029
|
+
*
|
|
4030
|
+
* @param name
|
|
4031
|
+
* @param metadata
|
|
4032
|
+
* @param constraint
|
|
4033
|
+
* @returns
|
|
4034
|
+
*/
|
|
4035
|
+
async loadHasManyThrough(name, metadata, constraint) {
|
|
4036
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
4037
|
+
if (parentKeys.length === 0) {
|
|
4038
|
+
this.models.forEach((model) => {
|
|
4039
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
4040
|
+
});
|
|
4041
|
+
return;
|
|
4042
|
+
}
|
|
4043
|
+
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
4044
|
+
table: metadata.throughTable,
|
|
4045
|
+
where: {
|
|
4046
|
+
type: "comparison",
|
|
4047
|
+
column: metadata.firstKey,
|
|
4048
|
+
operator: "in",
|
|
4049
|
+
value: parentKeys
|
|
4050
|
+
}
|
|
4051
|
+
});
|
|
4052
|
+
const intermediateKeys = this.collectUniqueRowValues(throughRows, metadata.secondLocalKey);
|
|
4053
|
+
if (intermediateKeys.length === 0) {
|
|
4054
|
+
this.models.forEach((model) => {
|
|
4055
|
+
model.setLoadedRelation(name, new ArkormCollection([]));
|
|
4056
|
+
});
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
4060
|
+
query = this.applyConstraint(query, constraint);
|
|
4061
|
+
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
4062
|
+
const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
|
|
4063
|
+
this.models.forEach((model) => {
|
|
4064
|
+
const parentKey = model.getAttribute(metadata.localKey);
|
|
4065
|
+
const related = parentKey == null ? [] : limited.get(this.toLookupKey(parentKey)) ?? [];
|
|
4066
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
4067
|
+
});
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4070
|
+
const relatedModels = (await query.get()).all();
|
|
4071
|
+
const relatedByIntermediate = /* @__PURE__ */ new Map();
|
|
4072
|
+
relatedModels.forEach((related) => {
|
|
4073
|
+
const relatedValue = this.readModelAttribute(related, metadata.secondKey);
|
|
4074
|
+
if (relatedValue == null) return;
|
|
4075
|
+
const bucket = relatedByIntermediate.get(this.toLookupKey(relatedValue)) ?? [];
|
|
4076
|
+
bucket.push(related);
|
|
4077
|
+
relatedByIntermediate.set(this.toLookupKey(relatedValue), bucket);
|
|
4078
|
+
});
|
|
4079
|
+
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
4080
|
+
throughRows.forEach((row) => {
|
|
4081
|
+
const parentValue = row[metadata.firstKey];
|
|
4082
|
+
const intermediateValue = row[metadata.secondLocalKey];
|
|
4083
|
+
if (parentValue == null || intermediateValue == null) return;
|
|
4084
|
+
const bucket = intermediateByParent.get(this.toLookupKey(parentValue)) ?? [];
|
|
4085
|
+
bucket.push(intermediateValue);
|
|
4086
|
+
intermediateByParent.set(this.toLookupKey(parentValue), bucket);
|
|
4087
|
+
});
|
|
4088
|
+
this.models.forEach((model) => {
|
|
4089
|
+
const parentValue = model.getAttribute(metadata.localKey);
|
|
4090
|
+
const related = (parentValue == null ? [] : intermediateByParent.get(this.toLookupKey(parentValue)) ?? []).flatMap((intermediateValue) => relatedByIntermediate.get(this.toLookupKey(intermediateValue)) ?? []);
|
|
4091
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
4092
|
+
});
|
|
3694
4093
|
}
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
4094
|
+
/**
|
|
4095
|
+
* Loads a "has one through" relationship for the set of models.
|
|
4096
|
+
*
|
|
4097
|
+
* @param name
|
|
4098
|
+
* @param resolver
|
|
4099
|
+
* @param metadata
|
|
4100
|
+
* @param constraint
|
|
4101
|
+
* @returns
|
|
4102
|
+
*/
|
|
4103
|
+
async loadHasOneThrough(name, resolver, metadata, constraint) {
|
|
4104
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.localKey));
|
|
4105
|
+
if (parentKeys.length === 0) {
|
|
4106
|
+
this.models.forEach((model) => {
|
|
4107
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
4108
|
+
});
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
4111
|
+
const throughRows = await this.createRelationTableLoader().selectRows({
|
|
4112
|
+
table: metadata.throughTable,
|
|
4113
|
+
where: {
|
|
4114
|
+
type: "comparison",
|
|
4115
|
+
column: metadata.firstKey,
|
|
4116
|
+
operator: "in",
|
|
4117
|
+
value: parentKeys
|
|
4118
|
+
}
|
|
4119
|
+
});
|
|
4120
|
+
const intermediateKeys = this.collectUniqueRowValues(throughRows, metadata.secondLocalKey);
|
|
4121
|
+
if (intermediateKeys.length === 0) {
|
|
4122
|
+
this.models.forEach((model) => {
|
|
4123
|
+
model.setLoadedRelation(name, this.resolveSingleDefault(resolver, model));
|
|
4124
|
+
});
|
|
4125
|
+
return;
|
|
4126
|
+
}
|
|
4127
|
+
let query = metadata.relatedModel.query().whereIn(metadata.secondKey, intermediateKeys);
|
|
4128
|
+
query = this.applyConstraint(query, constraint);
|
|
4129
|
+
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
4130
|
+
const limited = await this.loadLimitedThrough(metadata, query, parentKeys, throughRows);
|
|
4131
|
+
this.models.forEach((model) => {
|
|
4132
|
+
const parentKey = model.getAttribute(metadata.localKey);
|
|
4133
|
+
const related = parentKey == null ? void 0 : limited.get(this.toLookupKey(parentKey))?.[0];
|
|
4134
|
+
model.setLoadedRelation(name, related ?? this.resolveSingleDefault(resolver, model));
|
|
4135
|
+
});
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
4138
|
+
const relatedModels = (await query.get()).all();
|
|
4139
|
+
const relatedByIntermediate = /* @__PURE__ */ new Map();
|
|
4140
|
+
relatedModels.forEach((related) => {
|
|
4141
|
+
const relatedValue = this.readModelAttribute(related, metadata.secondKey);
|
|
4142
|
+
if (relatedValue == null) return;
|
|
4143
|
+
const lookupKey = this.toLookupKey(relatedValue);
|
|
4144
|
+
if (!relatedByIntermediate.has(lookupKey)) relatedByIntermediate.set(lookupKey, related);
|
|
4145
|
+
});
|
|
4146
|
+
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
4147
|
+
throughRows.forEach((row) => {
|
|
4148
|
+
const parentValue = row[metadata.firstKey];
|
|
4149
|
+
const intermediateValue = row[metadata.secondLocalKey];
|
|
4150
|
+
if (parentValue == null || intermediateValue == null) return;
|
|
4151
|
+
const lookupKey = this.toLookupKey(parentValue);
|
|
4152
|
+
if (!intermediateByParent.has(lookupKey)) intermediateByParent.set(lookupKey, intermediateValue);
|
|
4153
|
+
});
|
|
4154
|
+
this.models.forEach((model) => {
|
|
4155
|
+
const parentValue = model.getAttribute(metadata.localKey);
|
|
4156
|
+
const intermediateValue = parentValue == null ? void 0 : intermediateByParent.get(this.toLookupKey(parentValue));
|
|
4157
|
+
const relationValue = intermediateValue == null ? void 0 : relatedByIntermediate.get(this.toLookupKey(intermediateValue));
|
|
4158
|
+
model.setLoadedRelation(name, relationValue ?? this.resolveSingleDefault(resolver, model));
|
|
4159
|
+
});
|
|
3705
4160
|
}
|
|
3706
|
-
|
|
3707
|
-
const
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
const
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
4161
|
+
async loadLimitedThrough(metadata, query, parentKeys, throughRows) {
|
|
4162
|
+
const parentAlias = "__arkorm_parent_key";
|
|
4163
|
+
const throughTable = metadata.throughTable;
|
|
4164
|
+
const relatedTable = metadata.relatedModel.getTable();
|
|
4165
|
+
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.secondKey)}`, "=", `${throughTable}.${metadata.secondLocalKey}`).whereIn(`${throughTable}.${metadata.firstKey}`, parentKeys).addSelect({ [`${throughTable}.${metadata.firstKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${metadata.firstKey}`]);
|
|
4166
|
+
if (!rows) return await this.loadLimitedThroughIndividually(metadata, query, throughRows);
|
|
4167
|
+
const relatedKey = metadata.relatedModel.getPrimaryKey();
|
|
4168
|
+
const selectedIds = this.collectUniqueRowValues(rows, relatedKey);
|
|
4169
|
+
const relatedModels = (await query.clone().withoutPagination().whereIn(relatedKey, selectedIds).get()).all();
|
|
4170
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
4171
|
+
relatedModels.forEach((related) => {
|
|
4172
|
+
const key = this.readModelAttribute(related, relatedKey);
|
|
4173
|
+
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
4174
|
+
});
|
|
4175
|
+
return rows.reduce((all, row) => {
|
|
4176
|
+
const parentKey = row[parentAlias];
|
|
4177
|
+
const key = row[relatedKey];
|
|
4178
|
+
if (parentKey == null || key == null) return all;
|
|
4179
|
+
const related = relatedByKey.get(this.toLookupKey(key));
|
|
4180
|
+
if (!related) return all;
|
|
4181
|
+
const lookup = this.toLookupKey(parentKey);
|
|
4182
|
+
const bucket = all.get(lookup) ?? [];
|
|
4183
|
+
bucket.push(related);
|
|
4184
|
+
all.set(lookup, bucket);
|
|
3729
4185
|
return all;
|
|
3730
|
-
},
|
|
3731
|
-
primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
|
|
3732
|
-
timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
|
|
3733
|
-
};
|
|
3734
|
-
};
|
|
3735
|
-
const getPersistedColumnMap = (table, options = {}) => {
|
|
3736
|
-
return getPersistedTableMetadata(table, options).columns;
|
|
3737
|
-
};
|
|
3738
|
-
const getPersistedEnumMap = (table, options = {}) => {
|
|
3739
|
-
return getPersistedTableMetadata(table, options).enums;
|
|
3740
|
-
};
|
|
3741
|
-
const getPersistedPrimaryKeyGeneration = (table, options = {}) => {
|
|
3742
|
-
return getPersistedTableMetadata(table, options).primaryKeyGeneration;
|
|
3743
|
-
};
|
|
3744
|
-
const getPersistedTimestampColumns = (table, options = {}) => {
|
|
3745
|
-
return getPersistedTableMetadata(table, options).timestampColumns ?? [];
|
|
3746
|
-
};
|
|
3747
|
-
const applyMappedColumn = (tableColumns, column, features, table) => {
|
|
3748
|
-
if (typeof column.map === "string" && column.map.trim().length > 0 && column.map !== column.name) {
|
|
3749
|
-
if (!features.persistedColumnMappings) throw buildPersistedFeatureDisabledError("persistedColumnMappings", table);
|
|
3750
|
-
tableColumns[column.name] = column.map;
|
|
3751
|
-
return;
|
|
4186
|
+
}, /* @__PURE__ */ new Map());
|
|
3752
4187
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4188
|
+
async loadLimitedThroughIndividually(metadata, query, throughRows) {
|
|
4189
|
+
const intermediateByParent = /* @__PURE__ */ new Map();
|
|
4190
|
+
throughRows.forEach((row) => {
|
|
4191
|
+
const parentKey = row[metadata.firstKey];
|
|
4192
|
+
const intermediateKey = row[metadata.secondLocalKey];
|
|
4193
|
+
if (parentKey == null || intermediateKey == null) return;
|
|
4194
|
+
const lookup = this.toLookupKey(parentKey);
|
|
4195
|
+
const bucket = intermediateByParent.get(lookup) ?? [];
|
|
4196
|
+
bucket.push(intermediateKey);
|
|
4197
|
+
intermediateByParent.set(lookup, bucket);
|
|
4198
|
+
});
|
|
4199
|
+
const entries = await Promise.all([...intermediateByParent].map(async ([parentKey, intermediateKeys]) => {
|
|
4200
|
+
return [parentKey, (await query.clone().whereIn(metadata.secondKey, intermediateKeys).get()).all()];
|
|
4201
|
+
}));
|
|
4202
|
+
return new Map(entries);
|
|
3761
4203
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
4204
|
+
/**
|
|
4205
|
+
* Loads an inverse polymorphic ("morph to") relationship for the set of
|
|
4206
|
+
* models. Parents are grouped by their morph type, so each distinct type
|
|
4207
|
+
* runs a single batched query instead of one query per parent row.
|
|
4208
|
+
*
|
|
4209
|
+
* @param name
|
|
4210
|
+
* @param metadata
|
|
4211
|
+
* @param constraint
|
|
4212
|
+
* @returns
|
|
4213
|
+
*/
|
|
4214
|
+
async loadMorphTo(name, metadata, constraint) {
|
|
4215
|
+
const idsByType = /* @__PURE__ */ new Map();
|
|
4216
|
+
const seenByType = /* @__PURE__ */ new Map();
|
|
4217
|
+
this.models.forEach((model) => {
|
|
4218
|
+
const morphType = this.readModelAttribute(model, metadata.morphTypeColumn);
|
|
4219
|
+
const morphId = this.readModelAttribute(model, metadata.morphIdColumn);
|
|
4220
|
+
if (typeof morphType !== "string" || morphType.length === 0 || morphId == null) return;
|
|
4221
|
+
const ids = idsByType.get(morphType) ?? [];
|
|
4222
|
+
const seen = seenByType.get(morphType) ?? /* @__PURE__ */ new Set();
|
|
4223
|
+
const lookupKey = this.toLookupKey(morphId);
|
|
4224
|
+
if (!seen.has(lookupKey)) {
|
|
4225
|
+
seen.add(lookupKey);
|
|
4226
|
+
ids.push(morphId);
|
|
4227
|
+
}
|
|
4228
|
+
idsByType.set(morphType, ids);
|
|
4229
|
+
seenByType.set(morphType, seen);
|
|
4230
|
+
});
|
|
4231
|
+
const relatedByTypeAndKey = /* @__PURE__ */ new Map();
|
|
4232
|
+
await Promise.all(Array.from(idsByType.entries()).map(async ([morphType, ids]) => {
|
|
4233
|
+
const relatedModel = metadata.resolveModel(morphType);
|
|
4234
|
+
const ownerKey = metadata.ownerKey ?? relatedModel.getPrimaryKey();
|
|
4235
|
+
let query = relatedModel.query().whereIn(ownerKey, ids);
|
|
4236
|
+
query = this.applyConstraint(query, constraint);
|
|
4237
|
+
(await this.getPartitionedModels(query, ownerKey, ids)).forEach((related) => {
|
|
4238
|
+
const value = this.readModelAttribute(related, ownerKey);
|
|
4239
|
+
if (value == null) return;
|
|
4240
|
+
relatedByTypeAndKey.set(`${morphType}:${this.toLookupKey(value)}`, related);
|
|
4241
|
+
});
|
|
4242
|
+
}));
|
|
4243
|
+
this.models.forEach((model) => {
|
|
4244
|
+
const morphType = this.readModelAttribute(model, metadata.morphTypeColumn);
|
|
4245
|
+
const morphId = this.readModelAttribute(model, metadata.morphIdColumn);
|
|
4246
|
+
const relationValue = typeof morphType !== "string" || morphId == null ? null : relatedByTypeAndKey.get(`${morphType}:${this.toLookupKey(morphId)}`) ?? null;
|
|
4247
|
+
model.setLoadedRelation(name, relationValue);
|
|
4248
|
+
});
|
|
3774
4249
|
}
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
4250
|
+
/**
|
|
4251
|
+
* Loads a polymorphic one-to-one ("morph one") relationship for the set of
|
|
4252
|
+
* models. See {@link loadMorphChildren} for the batching strategy.
|
|
4253
|
+
*
|
|
4254
|
+
* @param name
|
|
4255
|
+
* @param resolver
|
|
4256
|
+
* @param metadata
|
|
4257
|
+
* @param constraint
|
|
4258
|
+
*/
|
|
4259
|
+
async loadMorphOne(name, resolver, metadata, constraint) {
|
|
4260
|
+
const relatedByKey = await this.loadMorphChildren(metadata, constraint);
|
|
4261
|
+
this.models.forEach((model) => {
|
|
4262
|
+
const bucket = relatedByKey.get(this.morphParentKey(model, metadata.localKey));
|
|
4263
|
+
model.setLoadedRelation(name, bucket?.[0] ?? this.resolveSingleDefault(resolver, model));
|
|
4264
|
+
});
|
|
3780
4265
|
}
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
4266
|
+
/**
|
|
4267
|
+
* Loads a polymorphic one-to-many ("morph many") relationship for the set of
|
|
4268
|
+
* models. See {@link loadMorphChildren} for the batching strategy.
|
|
4269
|
+
*
|
|
4270
|
+
* @param name
|
|
4271
|
+
* @param metadata
|
|
4272
|
+
* @param constraint
|
|
4273
|
+
*/
|
|
4274
|
+
async loadMorphMany(name, metadata, constraint) {
|
|
4275
|
+
const relatedByKey = await this.loadMorphChildren(metadata, constraint);
|
|
4276
|
+
this.models.forEach((model) => {
|
|
4277
|
+
const bucket = relatedByKey.get(this.morphParentKey(model, metadata.localKey)) ?? [];
|
|
4278
|
+
model.setLoadedRelation(name, new ArkormCollection(bucket));
|
|
4279
|
+
});
|
|
3793
4280
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
};
|
|
3799
|
-
tableMetadata.timestampColumns = [...(tableMetadata.timestampColumns ?? []).filter((entry) => entry.column !== column.name), nextColumn];
|
|
3800
|
-
};
|
|
3801
|
-
const applyOperationsToPersistedColumnMappingsState = (state, operations, features = resolvePersistedMetadataFeatures()) => {
|
|
3802
|
-
const nextTables = Object.entries(state.tables).reduce((all, [table, metadata]) => {
|
|
3803
|
-
all[table] = {
|
|
3804
|
-
columns: { ...metadata.columns },
|
|
3805
|
-
enums: Object.entries(metadata.enums).reduce((nextEnums, [columnName, values]) => {
|
|
3806
|
-
nextEnums[columnName] = [...values];
|
|
3807
|
-
return nextEnums;
|
|
3808
|
-
}, {}),
|
|
3809
|
-
primaryKeyGeneration: metadata.primaryKeyGeneration ? { ...metadata.primaryKeyGeneration } : void 0,
|
|
3810
|
-
timestampColumns: metadata.timestampColumns?.map((column) => ({ ...column }))
|
|
3811
|
-
};
|
|
3812
|
-
return all;
|
|
3813
|
-
}, {});
|
|
3814
|
-
operations.forEach((operation) => {
|
|
3815
|
-
if (operation.type === "createTable") {
|
|
3816
|
-
const tableMetadata = nextTables[operation.table] ?? {
|
|
3817
|
-
columns: {},
|
|
3818
|
-
enums: {}
|
|
3819
|
-
};
|
|
3820
|
-
operation.columns.forEach((column) => {
|
|
3821
|
-
applyMappedColumn(tableMetadata.columns, column, features, operation.table);
|
|
3822
|
-
applyEnumColumn(tableMetadata.enums, column, features, operation.table);
|
|
3823
|
-
applyPrimaryKeyGeneration(tableMetadata, column);
|
|
3824
|
-
applyTimestampColumn(tableMetadata, column);
|
|
3825
|
-
});
|
|
3826
|
-
if (Object.keys(tableMetadata.columns).length > 0 || Object.keys(tableMetadata.enums).length > 0 || tableMetadata.primaryKeyGeneration || tableMetadata.timestampColumns?.length) nextTables[operation.table] = tableMetadata;
|
|
3827
|
-
else delete nextTables[operation.table];
|
|
4281
|
+
async loadPolymorphicManyToMany(name, metadata, constraint) {
|
|
4282
|
+
const parentKeys = this.collectUniqueKeys((model) => model.getAttribute(metadata.parentKey));
|
|
4283
|
+
if (parentKeys.length === 0) {
|
|
4284
|
+
this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
|
|
3828
4285
|
return;
|
|
3829
4286
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
4287
|
+
const parentPivotKey = metadata.type === "morphToMany" ? metadata.morphIdColumn : metadata.foreignPivotKey;
|
|
4288
|
+
const morphType = metadata.type === "morphToMany" ? this.morphTypeOf(this.models[0]) : metadata.relatedModel.name;
|
|
4289
|
+
const pivotRows = await this.createRelationTableLoader().selectRows({
|
|
4290
|
+
table: metadata.throughTable,
|
|
4291
|
+
where: {
|
|
4292
|
+
type: "group",
|
|
4293
|
+
operator: "and",
|
|
4294
|
+
conditions: [{
|
|
4295
|
+
type: "comparison",
|
|
4296
|
+
column: parentPivotKey,
|
|
4297
|
+
operator: "in",
|
|
4298
|
+
value: parentKeys
|
|
4299
|
+
}, {
|
|
4300
|
+
type: "comparison",
|
|
4301
|
+
column: metadata.morphTypeColumn,
|
|
4302
|
+
operator: "=",
|
|
4303
|
+
value: morphType
|
|
4304
|
+
}]
|
|
4305
|
+
},
|
|
4306
|
+
columns: [parentPivotKey, metadata.relatedPivotKey].map((column) => ({ column }))
|
|
4307
|
+
});
|
|
4308
|
+
const relatedIds = this.collectUniqueRowValues(pivotRows, metadata.relatedPivotKey);
|
|
4309
|
+
if (relatedIds.length === 0) {
|
|
4310
|
+
this.models.forEach((model) => model.setLoadedRelation(name, new ArkormCollection([])));
|
|
3846
4311
|
return;
|
|
3847
4312
|
}
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
meta: {
|
|
3871
|
-
migrationId: migration.id,
|
|
3872
|
-
file: migration.file,
|
|
3873
|
-
className: migration.className
|
|
4313
|
+
let query = metadata.relatedModel.query().whereIn(metadata.relatedKey, relatedIds);
|
|
4314
|
+
query = this.applyConstraint(query, constraint);
|
|
4315
|
+
let pairs = pivotRows.map((row) => ({
|
|
4316
|
+
parent: row[parentPivotKey],
|
|
4317
|
+
related: row[metadata.relatedPivotKey]
|
|
4318
|
+
}));
|
|
4319
|
+
let relatedModels;
|
|
4320
|
+
if (query.hasEagerLoadPagination() && parentKeys.length > 1) {
|
|
4321
|
+
const parentAlias = "__arkorm_parent_key";
|
|
4322
|
+
const throughTable = metadata.throughTable;
|
|
4323
|
+
const relatedTable = metadata.relatedModel.getTable();
|
|
4324
|
+
const rows = await query.clone().innerJoin(throughTable, `${relatedTable}.${metadata.relatedModel.getColumnName(metadata.relatedKey)}`, "=", `${throughTable}.${metadata.relatedPivotKey}`).whereIn(`${throughTable}.${parentPivotKey}`, parentKeys).where({ [`${throughTable}.${metadata.morphTypeColumn}`]: morphType }).addSelect({ [`${throughTable}.${parentPivotKey}`]: parentAlias }).getRowsForEagerLoad([`${throughTable}.${parentPivotKey}`]);
|
|
4325
|
+
if (rows) {
|
|
4326
|
+
pairs = rows.map((row) => ({
|
|
4327
|
+
parent: row[parentAlias],
|
|
4328
|
+
related: row[metadata.relatedKey]
|
|
4329
|
+
}));
|
|
4330
|
+
const selectedIds = this.collectUniqueRowValues(rows, metadata.relatedKey);
|
|
4331
|
+
relatedModels = (await query.clone().withoutPagination().whereIn(metadata.relatedKey, selectedIds).get()).all();
|
|
4332
|
+
} else {
|
|
4333
|
+
await this.loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows);
|
|
4334
|
+
return;
|
|
3874
4335
|
}
|
|
4336
|
+
} else relatedModels = (await query.get()).all();
|
|
4337
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
4338
|
+
relatedModels.forEach((related) => {
|
|
4339
|
+
const key = this.readModelAttribute(related, metadata.relatedKey);
|
|
4340
|
+
if (key != null) relatedByKey.set(this.toLookupKey(key), related);
|
|
4341
|
+
});
|
|
4342
|
+
const relatedByParent = /* @__PURE__ */ new Map();
|
|
4343
|
+
pairs.forEach(({ parent, related }) => {
|
|
4344
|
+
if (parent == null || related == null) return;
|
|
4345
|
+
const model = relatedByKey.get(this.toLookupKey(related));
|
|
4346
|
+
if (!model) return;
|
|
4347
|
+
const lookup = this.toLookupKey(parent);
|
|
4348
|
+
const bucket = relatedByParent.get(lookup) ?? [];
|
|
4349
|
+
bucket.push(model);
|
|
4350
|
+
relatedByParent.set(lookup, bucket);
|
|
4351
|
+
});
|
|
4352
|
+
this.models.forEach((model) => {
|
|
4353
|
+
const parentKey = model.getAttribute(metadata.parentKey);
|
|
4354
|
+
const related = parentKey == null ? [] : relatedByParent.get(this.toLookupKey(parentKey)) ?? [];
|
|
4355
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
3875
4356
|
});
|
|
3876
|
-
const operations = await getMigrationPlan(migrationClass, "up", { inert: true });
|
|
3877
|
-
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
3878
4357
|
}
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
const
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4358
|
+
async loadPolymorphicManyToManyIndividually(name, metadata, query, parentPivotKey, pivotRows) {
|
|
4359
|
+
await Promise.all(this.models.map(async (model) => {
|
|
4360
|
+
const parentKey = model.getAttribute(metadata.parentKey);
|
|
4361
|
+
const ids = pivotRows.filter((row) => this.toLookupKey(row[parentPivotKey]) === this.toLookupKey(parentKey)).map((row) => row[metadata.relatedPivotKey]);
|
|
4362
|
+
const related = ids.length ? (await query.clone().whereIn(metadata.relatedKey, ids).get()).all() : [];
|
|
4363
|
+
model.setLoadedRelation(name, new ArkormCollection(related));
|
|
4364
|
+
}));
|
|
4365
|
+
}
|
|
4366
|
+
/**
|
|
4367
|
+
* Batch-loads the children for a morphOne/morphMany relationship. Parents are
|
|
4368
|
+
* grouped by their own morph type (constructor name), so each distinct type
|
|
4369
|
+
* runs a single query constrained by `idColumn IN (...) AND typeColumn = type`
|
|
4370
|
+
* instead of one query per parent row. This also supports a heterogeneous
|
|
4371
|
+
* parent set (e.g. the result of a nested load after a morphTo).
|
|
4372
|
+
*
|
|
4373
|
+
* @param metadata
|
|
4374
|
+
* @param constraint
|
|
4375
|
+
* @returns A map of `"{type}:{localKey}" -> related[]`.
|
|
4376
|
+
*/
|
|
4377
|
+
async loadMorphChildren(metadata, constraint) {
|
|
4378
|
+
const keysByType = /* @__PURE__ */ new Map();
|
|
4379
|
+
const seenByType = /* @__PURE__ */ new Map();
|
|
4380
|
+
this.models.forEach((model) => {
|
|
4381
|
+
const morphType = this.morphTypeOf(model);
|
|
4382
|
+
const localValue = model.getAttribute(metadata.localKey);
|
|
4383
|
+
if (morphType.length === 0 || localValue == null) return;
|
|
4384
|
+
const keys = keysByType.get(morphType) ?? [];
|
|
4385
|
+
const seen = seenByType.get(morphType) ?? /* @__PURE__ */ new Set();
|
|
4386
|
+
const lookupKey = this.toLookupKey(localValue);
|
|
4387
|
+
if (!seen.has(lookupKey)) {
|
|
4388
|
+
seen.add(lookupKey);
|
|
4389
|
+
keys.push(localValue);
|
|
4390
|
+
}
|
|
4391
|
+
keysByType.set(morphType, keys);
|
|
4392
|
+
seenByType.set(morphType, seen);
|
|
4393
|
+
});
|
|
4394
|
+
const relatedByKey = /* @__PURE__ */ new Map();
|
|
4395
|
+
await Promise.all(Array.from(keysByType.entries()).map(async ([morphType, keys]) => {
|
|
4396
|
+
let query = metadata.relatedModel.query().whereIn(metadata.morphIdColumn, keys).where({ [metadata.morphTypeColumn]: morphType });
|
|
4397
|
+
query = this.applyConstraint(query, constraint);
|
|
4398
|
+
(await this.getPartitionedModels(query, metadata.morphIdColumn, keys)).forEach((related) => {
|
|
4399
|
+
const value = this.readModelAttribute(related, metadata.morphIdColumn);
|
|
4400
|
+
if (value == null) return;
|
|
4401
|
+
const key = `${morphType}:${this.toLookupKey(value)}`;
|
|
4402
|
+
const bucket = relatedByKey.get(key) ?? [];
|
|
4403
|
+
bucket.push(related);
|
|
4404
|
+
relatedByKey.set(key, bucket);
|
|
4405
|
+
});
|
|
4406
|
+
}));
|
|
4407
|
+
return relatedByKey;
|
|
4408
|
+
}
|
|
4409
|
+
morphParentKey(model, localKey) {
|
|
4410
|
+
return `${this.morphTypeOf(model)}:${this.toLookupKey(model.getAttribute(localKey))}`;
|
|
3887
4411
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
const validatePersistedMetadataFeaturesForMigrations = async (migrations, features = resolvePersistedMetadataFeatures()) => {
|
|
3891
|
-
let nextState = createEmptyPersistedColumnMappingsState();
|
|
3892
|
-
for (const [migrationClass] of migrations) {
|
|
3893
|
-
const operations = await getMigrationPlan(migrationClass, "up", { inert: true });
|
|
3894
|
-
nextState = applyOperationsToPersistedColumnMappingsState(nextState, operations, features);
|
|
4412
|
+
morphTypeOf(model) {
|
|
4413
|
+
return model.constructor?.name ?? "";
|
|
3895
4414
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
models
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
migrations: [],
|
|
3911
|
-
seeders: [],
|
|
3912
|
-
models: [],
|
|
3913
|
-
factories: []
|
|
3914
|
-
});
|
|
3915
|
-
const registry = createEmptyRegistry();
|
|
3916
|
-
const pushUnique = (items, values) => {
|
|
3917
|
-
values.forEach((value) => {
|
|
3918
|
-
if (!items.includes(value)) items.push(value);
|
|
3919
|
-
});
|
|
3920
|
-
};
|
|
3921
|
-
const normalizePathInput = (paths) => {
|
|
3922
|
-
if (paths === void 0) return [];
|
|
3923
|
-
return (Array.isArray(paths) ? paths : [paths]).filter((path) => typeof path === "string" && path.trim().length > 0);
|
|
3924
|
-
};
|
|
3925
|
-
const normalizeConstructors = (items) => items.flatMap((item) => Array.isArray(item) ? item : [item]).filter(Boolean);
|
|
3926
|
-
/**
|
|
3927
|
-
* Register additional runtime discovery paths without replacing configured paths.
|
|
3928
|
-
*
|
|
3929
|
-
* @param paths
|
|
3930
|
-
*/
|
|
3931
|
-
const registerPaths = (paths) => {
|
|
3932
|
-
Object.entries(paths).forEach(([key, value]) => {
|
|
3933
|
-
pushUnique(registry.paths[key], normalizePathInput(value));
|
|
3934
|
-
});
|
|
3935
|
-
};
|
|
3936
|
-
/**
|
|
3937
|
-
* Register additional runtime discovery paths for migrations without replacing configured paths.
|
|
3938
|
-
*
|
|
3939
|
-
* @param paths
|
|
3940
|
-
* @returns
|
|
3941
|
-
*/
|
|
3942
|
-
const loadMigrationsFrom = (paths) => registerPaths({ migrations: paths });
|
|
3943
|
-
/**
|
|
3944
|
-
* Register additional runtime discovery paths for seeders without replacing configured paths.
|
|
3945
|
-
*
|
|
3946
|
-
* @param paths
|
|
3947
|
-
* @returns
|
|
3948
|
-
*/
|
|
3949
|
-
const loadSeedersFrom = (paths) => registerPaths({ seeders: paths });
|
|
3950
|
-
/**
|
|
3951
|
-
* Register additional runtime discovery paths for models without replacing configured paths.
|
|
3952
|
-
*
|
|
3953
|
-
* @param paths
|
|
3954
|
-
* @returns
|
|
3955
|
-
*/
|
|
3956
|
-
const loadModelsFrom = (paths) => registerPaths({ models: paths });
|
|
3957
|
-
/**
|
|
3958
|
-
* Register additional runtime discovery paths for factories without replacing configured paths.
|
|
3959
|
-
*
|
|
3960
|
-
* @param paths
|
|
3961
|
-
* @returns
|
|
3962
|
-
*/
|
|
3963
|
-
const loadFactoriesFrom = (paths) => registerPaths({ factories: paths });
|
|
3964
|
-
/**
|
|
3965
|
-
* Register migration constructors directly without relying on runtime discovery.
|
|
3966
|
-
*
|
|
3967
|
-
* @param migrations
|
|
3968
|
-
*/
|
|
3969
|
-
const registerMigrations = (...migrations) => {
|
|
3970
|
-
pushUnique(registry.migrations, normalizeConstructors(migrations));
|
|
3971
|
-
};
|
|
3972
|
-
/**
|
|
3973
|
-
* Register seeder constructors directly without relying on runtime discovery.
|
|
3974
|
-
*
|
|
3975
|
-
* @param seeders
|
|
3976
|
-
*/
|
|
3977
|
-
const registerSeeders = (...seeders) => {
|
|
3978
|
-
pushUnique(registry.seeders, normalizeConstructors(seeders));
|
|
3979
|
-
};
|
|
3980
|
-
/**
|
|
3981
|
-
* Register model constructors directly without relying on runtime discovery.
|
|
3982
|
-
*
|
|
3983
|
-
* @param models
|
|
3984
|
-
*/
|
|
3985
|
-
const registerModels = (...models) => {
|
|
3986
|
-
pushUnique(registry.models, normalizeConstructors(models));
|
|
3987
|
-
};
|
|
3988
|
-
/**
|
|
3989
|
-
* Register factory constructors or instances directly without relying on runtime discovery.
|
|
3990
|
-
*
|
|
3991
|
-
* @param factories
|
|
3992
|
-
*/
|
|
3993
|
-
const registerFactories = (...factories) => {
|
|
3994
|
-
pushUnique(registry.factories, normalizeConstructors(factories));
|
|
3995
|
-
};
|
|
3996
|
-
/**
|
|
3997
|
-
* Get registered runtime discovery paths or registered constructors for a specific type.
|
|
3998
|
-
*
|
|
3999
|
-
* @param key
|
|
4000
|
-
* @returns
|
|
4001
|
-
*/
|
|
4002
|
-
const getRegisteredPaths = (key) => {
|
|
4003
|
-
if (key) return [...registry.paths[key]];
|
|
4004
|
-
return {
|
|
4005
|
-
models: [...registry.paths.models],
|
|
4006
|
-
seeders: [...registry.paths.seeders],
|
|
4007
|
-
migrations: [...registry.paths.migrations],
|
|
4008
|
-
factories: [...registry.paths.factories]
|
|
4009
|
-
};
|
|
4010
|
-
};
|
|
4011
|
-
/**
|
|
4012
|
-
* Get registered migration constructors instances.
|
|
4013
|
-
*
|
|
4014
|
-
* @returns
|
|
4015
|
-
*/
|
|
4016
|
-
const getRegisteredMigrations = () => [...registry.migrations];
|
|
4017
|
-
/**
|
|
4018
|
-
* Get registered seeder constructors instances.
|
|
4019
|
-
*
|
|
4020
|
-
* @returns
|
|
4021
|
-
*/
|
|
4022
|
-
const getRegisteredSeeders = () => [...registry.seeders];
|
|
4023
|
-
/**
|
|
4024
|
-
* Get registered model constructors instances.
|
|
4025
|
-
*
|
|
4026
|
-
* @returns
|
|
4027
|
-
*/
|
|
4028
|
-
const getRegisteredModels = () => [...registry.models];
|
|
4029
|
-
/**
|
|
4030
|
-
* Get registered factory constructors or instances.
|
|
4031
|
-
*
|
|
4032
|
-
* @returns
|
|
4033
|
-
*/
|
|
4034
|
-
const getRegisteredFactories = () => [...registry.factories];
|
|
4035
|
-
const resetRuntimeRegistryForTests = () => {
|
|
4036
|
-
const empty = createEmptyRegistry();
|
|
4037
|
-
registry.paths = empty.paths;
|
|
4038
|
-
registry.migrations = empty.migrations;
|
|
4039
|
-
registry.seeders = empty.seeders;
|
|
4040
|
-
registry.models = empty.models;
|
|
4041
|
-
registry.factories = empty.factories;
|
|
4042
|
-
};
|
|
4043
|
-
|
|
4044
|
-
//#endregion
|
|
4045
|
-
//#region src/helpers/runtime-config.ts
|
|
4046
|
-
const resolveDefaultStubsPath = () => {
|
|
4047
|
-
let current = path.default.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
4048
|
-
while (true) {
|
|
4049
|
-
const packageJsonPath = path.default.join(current, "package.json");
|
|
4050
|
-
const stubsPath = path.default.join(current, "stubs");
|
|
4051
|
-
if ((0, fs.existsSync)(packageJsonPath) && (0, fs.existsSync)(stubsPath)) return stubsPath;
|
|
4052
|
-
const parent = path.default.dirname(current);
|
|
4053
|
-
if (parent === current) break;
|
|
4054
|
-
current = parent;
|
|
4415
|
+
/**
|
|
4416
|
+
* Fallback method to load relationships individually for each model when the
|
|
4417
|
+
* relationship type is not supported for set-based loading.
|
|
4418
|
+
*
|
|
4419
|
+
* @param name
|
|
4420
|
+
* @param resolver
|
|
4421
|
+
* @param constraint
|
|
4422
|
+
*/
|
|
4423
|
+
async loadIndividually(name, resolver, constraint) {
|
|
4424
|
+
await Promise.all(this.models.map(async (model) => {
|
|
4425
|
+
const relation = resolver.call(model);
|
|
4426
|
+
if (constraint) relation.constrain(constraint);
|
|
4427
|
+
model.setLoadedRelation(name, await relation.getResults());
|
|
4428
|
+
}));
|
|
4055
4429
|
}
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
};
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
phase: event.phase,
|
|
4095
|
-
durationMs: event.durationMs,
|
|
4096
|
-
inspection: event.inspection ?? void 0,
|
|
4097
|
-
meta: event.meta,
|
|
4098
|
-
error: event.error
|
|
4099
|
-
};
|
|
4100
|
-
if (event.phase === "error") {
|
|
4101
|
-
console.error(prefix, payload);
|
|
4102
|
-
return;
|
|
4430
|
+
/**
|
|
4431
|
+
* Applies an eager load constraint to a query if provided.
|
|
4432
|
+
*
|
|
4433
|
+
* @param query
|
|
4434
|
+
* @param constraint
|
|
4435
|
+
* @returns
|
|
4436
|
+
*/
|
|
4437
|
+
applyConstraint(query, constraint) {
|
|
4438
|
+
if (!constraint) return query;
|
|
4439
|
+
return constraint(query) ?? query;
|
|
4440
|
+
}
|
|
4441
|
+
async getPartitionedModels(query, partitionColumn, partitionValues) {
|
|
4442
|
+
if (partitionValues.length <= 1) return (await query.get()).all();
|
|
4443
|
+
const partitioned = await query.getForEagerLoad([partitionColumn]);
|
|
4444
|
+
if (partitioned) return partitioned.all();
|
|
4445
|
+
return (await Promise.all(partitionValues.map(async (value) => {
|
|
4446
|
+
return (await query.clone().where({ [partitionColumn]: value }).get()).all();
|
|
4447
|
+
}))).flat();
|
|
4448
|
+
}
|
|
4449
|
+
/**
|
|
4450
|
+
* Collects unique values from the set of models based on a resolver function, which
|
|
4451
|
+
* is used to extract the value from each model.
|
|
4452
|
+
*
|
|
4453
|
+
* @param resolve A function that takes a model and returns the value to be collected.
|
|
4454
|
+
* @returns An array of unique values.
|
|
4455
|
+
*/
|
|
4456
|
+
collectUniqueKeys(resolve) {
|
|
4457
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4458
|
+
const values = [];
|
|
4459
|
+
this.models.forEach((model) => {
|
|
4460
|
+
const value = resolve(model);
|
|
4461
|
+
if (value == null) return;
|
|
4462
|
+
const lookupKey = this.toLookupKey(value);
|
|
4463
|
+
if (seen.has(lookupKey)) return;
|
|
4464
|
+
seen.add(lookupKey);
|
|
4465
|
+
values.push(value);
|
|
4466
|
+
});
|
|
4467
|
+
return values;
|
|
4103
4468
|
}
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
}
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
*
|
|
4136
|
-
*
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
const mappedDirectory = path.default.join(buildOutput, relativeSource);
|
|
4156
|
-
return (0, fs.existsSync)(mappedDirectory) ? mappedDirectory : directory;
|
|
4157
|
-
};
|
|
4158
|
-
const collectRuntimeModelFiles = (directory) => {
|
|
4159
|
-
if (!(0, fs.existsSync)(directory)) return [];
|
|
4160
|
-
return (0, fs.readdirSync)(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
4161
|
-
const entryPath = path.default.join(directory, entry.name);
|
|
4162
|
-
if (entry.isDirectory()) return collectRuntimeModelFiles(entryPath);
|
|
4163
|
-
if (!entry.isFile() || !/\.(ts|tsx|mts|cts|js|mjs|cjs)$/i.test(entry.name)) return [];
|
|
4164
|
-
if (/\.d\.(ts|mts|cts)$/i.test(entry.name)) return [];
|
|
4165
|
-
return [entryPath];
|
|
4166
|
-
});
|
|
4167
|
-
};
|
|
4168
|
-
const isModelConstructor = (value) => {
|
|
4169
|
-
if (typeof value !== "function") return false;
|
|
4170
|
-
const candidate = value;
|
|
4171
|
-
return typeof candidate.query === "function" && typeof candidate.hydrate === "function" && typeof candidate.getTable === "function" && typeof candidate.getPrimaryKey === "function";
|
|
4172
|
-
};
|
|
4173
|
-
const registerConfiguredModels = async (generation) => {
|
|
4174
|
-
const configured = userConfig.paths?.models;
|
|
4175
|
-
const additional = getRegisteredPaths("models");
|
|
4176
|
-
const files = [...typeof configured === "string" ? [configured] : [], ...additional].map((directory) => resolveRuntimeModelsDirectory(path.default.isAbsolute(directory) ? directory : path.default.resolve(process.cwd(), directory))).filter((directory, index, all) => all.indexOf(directory) === index).flatMap(collectRuntimeModelFiles);
|
|
4177
|
-
const loaded = await RuntimeModuleLoader.loadAll(files);
|
|
4178
|
-
loaded.forEach(({ file, error }) => {
|
|
4179
|
-
if (error !== void 0) console.warn(`[arkorm] Failed to load model file for registration: ${file}\n ${error instanceof Error ? error.message : String(error)}\n Models in this file will not be registered (morphTo resolution may fail). This is often a circular import — avoid importing models at the top level of trait modules.`);
|
|
4180
|
-
});
|
|
4181
|
-
const models = loaded.flatMap(({ module }) => module ? Object.values(module).filter(isModelConstructor) : []);
|
|
4182
|
-
if (generation === runtimeModelRegistrationGeneration) registerModels(models);
|
|
4183
|
-
};
|
|
4184
|
-
const scheduleConfiguredModelRegistration = () => {
|
|
4185
|
-
const generation = runtimeModelRegistrationGeneration;
|
|
4186
|
-
runtimeModelRegistrationPromise = (runtimeModelRegistrationPromise ?? Promise.resolve()).then(async () => await registerConfiguredModels(generation));
|
|
4187
|
-
return runtimeModelRegistrationPromise;
|
|
4188
|
-
};
|
|
4189
|
-
const awaitConfiguredModelsRegistration = async () => {
|
|
4190
|
-
if (runtimeModelRegistrationPromise) await runtimeModelRegistrationPromise;
|
|
4191
|
-
};
|
|
4192
|
-
/**
|
|
4193
|
-
* Define the ArkORM runtime configuration. This function can be used to provide.
|
|
4194
|
-
*
|
|
4195
|
-
* @param config The ArkORM configuration object.
|
|
4196
|
-
* @returns The same configuration object.
|
|
4197
|
-
*/
|
|
4198
|
-
const defineConfig = (config) => {
|
|
4199
|
-
return config;
|
|
4200
|
-
};
|
|
4201
|
-
/**
|
|
4202
|
-
* Bind a database adapter instance to an array of models that support adapter binding.
|
|
4203
|
-
*
|
|
4204
|
-
* @param adapter
|
|
4205
|
-
* @param models
|
|
4206
|
-
* @returns
|
|
4207
|
-
*/
|
|
4208
|
-
const bindAdapterToModels = (adapter, models) => {
|
|
4209
|
-
models.forEach((model) => {
|
|
4210
|
-
model.setAdapter(adapter);
|
|
4211
|
-
});
|
|
4212
|
-
return adapter;
|
|
4213
|
-
};
|
|
4214
|
-
/**
|
|
4215
|
-
* Get the user-provided ArkORM configuration.
|
|
4216
|
-
*
|
|
4217
|
-
* @param key Optional specific configuration key to retrieve. If omitted, the entire configuration object is returned.
|
|
4218
|
-
* @returns The user-provided ArkORM configuration object.
|
|
4219
|
-
*/
|
|
4220
|
-
const getUserConfig = (key) => {
|
|
4221
|
-
if (key) return userConfig[key];
|
|
4222
|
-
return userConfig;
|
|
4223
|
-
};
|
|
4224
|
-
/**
|
|
4225
|
-
* Configure the ArkORM runtime with the provided runtime client resolver and
|
|
4226
|
-
* adapter-first options.
|
|
4227
|
-
*
|
|
4228
|
-
* @param client
|
|
4229
|
-
* @param options
|
|
4230
|
-
*/
|
|
4231
|
-
const configureArkormRuntime = (client, options = {}) => {
|
|
4232
|
-
const resolvedClient = client ?? options.client;
|
|
4233
|
-
const nextConfig = {
|
|
4234
|
-
...userConfig,
|
|
4235
|
-
naming: mergeNamingConfig(options.naming),
|
|
4236
|
-
features: mergeFeatureConfig(options.features),
|
|
4237
|
-
paths: mergePathConfig(options.paths)
|
|
4238
|
-
};
|
|
4239
|
-
nextConfig.client = resolvedClient;
|
|
4240
|
-
nextConfig.prisma = resolvedClient;
|
|
4241
|
-
if (options.pagination !== void 0) nextConfig.pagination = options.pagination;
|
|
4242
|
-
if (options.adapter !== void 0) nextConfig.adapter = options.adapter;
|
|
4243
|
-
if (options.boot !== void 0) nextConfig.boot = options.boot;
|
|
4244
|
-
if (options.debug !== void 0) nextConfig.debug = options.debug;
|
|
4245
|
-
if (options.outputExt !== void 0) nextConfig.outputExt = options.outputExt;
|
|
4246
|
-
Object.assign(userConfig, { ...nextConfig });
|
|
4247
|
-
runtimeClientResolver = resolvedClient;
|
|
4248
|
-
runtimeAdapter = options.adapter;
|
|
4249
|
-
runtimePaginationURLDriverFactory = nextConfig.pagination?.urlDriver;
|
|
4250
|
-
runtimePaginationCurrentPageResolver = nextConfig.pagination?.resolveCurrentPage;
|
|
4251
|
-
runtimeDebugHandler = resolveDebugHandler(nextConfig.debug);
|
|
4252
|
-
scheduleConfiguredModelRegistration();
|
|
4253
|
-
const bootClient = resolveClient(resolvedClient);
|
|
4254
|
-
options.boot?.({
|
|
4255
|
-
client: bootClient,
|
|
4256
|
-
prisma: bootClient,
|
|
4257
|
-
bindAdapter: bindAdapterToModels
|
|
4258
|
-
});
|
|
4259
|
-
};
|
|
4260
|
-
/**
|
|
4261
|
-
* Reset the ArkORM runtime configuration.
|
|
4262
|
-
* This is primarily intended for testing purposes.
|
|
4263
|
-
*/
|
|
4264
|
-
const resetArkormRuntimeForTests = () => {
|
|
4265
|
-
Object.assign(userConfig, {
|
|
4266
|
-
...baseConfig,
|
|
4267
|
-
naming: { ...baseConfig.naming ?? {} },
|
|
4268
|
-
features: { ...baseConfig.features ?? {} },
|
|
4269
|
-
paths: { ...baseConfig.paths ?? {} }
|
|
4270
|
-
});
|
|
4271
|
-
runtimeConfigLoaded = false;
|
|
4272
|
-
runtimeConfigLoadingPromise = void 0;
|
|
4273
|
-
runtimeModelRegistrationPromise = void 0;
|
|
4274
|
-
runtimeModelRegistrationGeneration++;
|
|
4275
|
-
runtimeClientResolver = void 0;
|
|
4276
|
-
runtimeAdapter = void 0;
|
|
4277
|
-
runtimePaginationURLDriverFactory = void 0;
|
|
4278
|
-
runtimePaginationCurrentPageResolver = void 0;
|
|
4279
|
-
runtimeDebugHandler = void 0;
|
|
4280
|
-
resetPersistedColumnMappingsCache();
|
|
4281
|
-
resetRuntimeRegistryForTests();
|
|
4282
|
-
};
|
|
4283
|
-
/**
|
|
4284
|
-
* Resolve a runtime client instance from the provided resolver, which can be either
|
|
4285
|
-
* a direct client instance or a function that returns a client instance.
|
|
4286
|
-
*
|
|
4287
|
-
* @param resolver
|
|
4288
|
-
* @returns
|
|
4289
|
-
*/
|
|
4290
|
-
const resolveClient = (resolver) => {
|
|
4291
|
-
if (!resolver) return void 0;
|
|
4292
|
-
const client = typeof resolver === "function" ? resolver() : resolver;
|
|
4293
|
-
if (!client || typeof client !== "object") return void 0;
|
|
4294
|
-
return client;
|
|
4295
|
-
};
|
|
4296
|
-
/**
|
|
4297
|
-
* Resolve and apply the ArkORM configuration from an imported module.
|
|
4298
|
-
* This function checks for a default export and falls back to the module itself, then validates
|
|
4299
|
-
* the configuration object and applies it to the runtime if valid.
|
|
4300
|
-
*
|
|
4301
|
-
* @param imported
|
|
4302
|
-
* @returns
|
|
4303
|
-
*/
|
|
4304
|
-
const resolveAndApplyConfig = (imported) => {
|
|
4305
|
-
const config = imported?.default ?? imported;
|
|
4306
|
-
if (!config || typeof config !== "object") return;
|
|
4307
|
-
const runtimeClient = config.client ?? config.prisma;
|
|
4308
|
-
configureArkormRuntime(runtimeClient, {
|
|
4309
|
-
client: runtimeClient,
|
|
4310
|
-
adapter: config.adapter,
|
|
4311
|
-
boot: config.boot,
|
|
4312
|
-
debug: config.debug,
|
|
4313
|
-
naming: config.naming,
|
|
4314
|
-
features: config.features,
|
|
4315
|
-
pagination: config.pagination,
|
|
4316
|
-
paths: config.paths,
|
|
4317
|
-
outputExt: config.outputExt
|
|
4318
|
-
});
|
|
4319
|
-
runtimeConfigLoaded = true;
|
|
4320
|
-
};
|
|
4321
|
-
/**
|
|
4322
|
-
* Dynamically import a configuration file.
|
|
4323
|
-
* A cache-busting query parameter is appended to ensure the latest version is loaded.
|
|
4324
|
-
*
|
|
4325
|
-
* @param configPath
|
|
4326
|
-
* @returns A promise that resolves to the imported configuration module.
|
|
4327
|
-
*/
|
|
4328
|
-
const importConfigFile = (configPath) => {
|
|
4329
|
-
return RuntimeModuleLoader.load(configPath);
|
|
4330
|
-
};
|
|
4331
|
-
const loadRuntimeConfigSync = () => {
|
|
4332
|
-
const require = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
4333
|
-
const syncConfigPaths = [path.default.join(process.cwd(), "arkormx.config.cjs")];
|
|
4334
|
-
for (const configPath of syncConfigPaths) {
|
|
4335
|
-
if (!(0, fs.existsSync)(configPath)) continue;
|
|
4469
|
+
/**
|
|
4470
|
+
* Collects unique values from an array of database rows based on a specified key, which
|
|
4471
|
+
* is used to extract the value from each row.
|
|
4472
|
+
*
|
|
4473
|
+
* @param rows An array of database rows.
|
|
4474
|
+
* @param key The key to extract values from each row.
|
|
4475
|
+
* @returns An array of unique values.
|
|
4476
|
+
*/
|
|
4477
|
+
collectUniqueRowValues(rows, key) {
|
|
4478
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4479
|
+
const values = [];
|
|
4480
|
+
rows.forEach((row) => {
|
|
4481
|
+
const value = row[key];
|
|
4482
|
+
if (value == null) return;
|
|
4483
|
+
const lookupKey = this.toLookupKey(value);
|
|
4484
|
+
if (seen.has(lookupKey)) return;
|
|
4485
|
+
seen.add(lookupKey);
|
|
4486
|
+
values.push(value);
|
|
4487
|
+
});
|
|
4488
|
+
return values;
|
|
4489
|
+
}
|
|
4490
|
+
/**
|
|
4491
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
4492
|
+
*
|
|
4493
|
+
* @returns
|
|
4494
|
+
*/
|
|
4495
|
+
createRelationTableLoader() {
|
|
4496
|
+
return new RelationTableLoader(this.resolveAdapter());
|
|
4497
|
+
}
|
|
4498
|
+
/**
|
|
4499
|
+
* Loads a "belongs to many" relationship for the set of models.
|
|
4500
|
+
*
|
|
4501
|
+
* @returns
|
|
4502
|
+
*/
|
|
4503
|
+
resolveAdapter() {
|
|
4504
|
+
const adapter = this.models[0].constructor.getAdapter?.();
|
|
4505
|
+
if (!adapter) throw new Error("Set-based eager loading requires a configured adapter.");
|
|
4506
|
+
return adapter;
|
|
4507
|
+
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Reads an attribute value from a model using the getAttribute method, which is used
|
|
4510
|
+
* to access model attributes in a way that is compatible with Arkorm's internal model structure.
|
|
4511
|
+
*
|
|
4512
|
+
* @param model The model to read the attribute from.
|
|
4513
|
+
* @param key The name of the attribute to read.
|
|
4514
|
+
* @returns
|
|
4515
|
+
*/
|
|
4516
|
+
readModelAttribute(model, key) {
|
|
4517
|
+
const readable = model;
|
|
4518
|
+
const value = readable.getAttribute?.(key);
|
|
4519
|
+
if (value !== void 0) return value;
|
|
4336
4520
|
try {
|
|
4337
|
-
|
|
4338
|
-
return
|
|
4521
|
+
const attribute = Object.entries(readable.constructor?.getColumnMap?.() ?? {}).find(([, column]) => column === key)?.[0];
|
|
4522
|
+
return attribute ? readable.getAttribute?.(attribute) : value;
|
|
4339
4523
|
} catch {
|
|
4340
|
-
|
|
4524
|
+
return value;
|
|
4341
4525
|
}
|
|
4342
4526
|
}
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
*
|
|
4347
|
-
*
|
|
4348
|
-
* @returns
|
|
4349
|
-
*/
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
await awaitConfiguredModelsRegistration();
|
|
4353
|
-
return;
|
|
4527
|
+
/**
|
|
4528
|
+
* Resolves the default result for a relationship when no related models are found.
|
|
4529
|
+
*
|
|
4530
|
+
* @param resolver
|
|
4531
|
+
* @param model
|
|
4532
|
+
* @returns
|
|
4533
|
+
*/
|
|
4534
|
+
resolveSingleDefault(resolver, model) {
|
|
4535
|
+
return resolver.call(model).resolveDefaultResult?.() ?? null;
|
|
4354
4536
|
}
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4537
|
+
/**
|
|
4538
|
+
* Generates a unique lookup key for a given value, which is used to store and retrieve
|
|
4539
|
+
* values in maps during the eager loading process.
|
|
4540
|
+
*
|
|
4541
|
+
* @param value The value to generate a lookup key for.
|
|
4542
|
+
* @returns A unique string representing the value.
|
|
4543
|
+
*/
|
|
4544
|
+
toLookupKey(value) {
|
|
4545
|
+
if (value instanceof Date) return `date:${value.toISOString()}`;
|
|
4546
|
+
return `${typeof value}:${String(value)}`;
|
|
4359
4547
|
}
|
|
4360
|
-
runtimeConfigLoadingPromise = (async () => {
|
|
4361
|
-
const configPaths = [path.default.join(process.cwd(), "arkormx.config.js"), path.default.join(process.cwd(), "arkormx.config.ts")];
|
|
4362
|
-
for (const configPath of configPaths) {
|
|
4363
|
-
if (!(0, fs.existsSync)(configPath)) continue;
|
|
4364
|
-
try {
|
|
4365
|
-
resolveAndApplyConfig(await importConfigFile(configPath));
|
|
4366
|
-
await awaitConfiguredModelsRegistration();
|
|
4367
|
-
return;
|
|
4368
|
-
} catch {
|
|
4369
|
-
continue;
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
runtimeConfigLoaded = true;
|
|
4373
|
-
await scheduleConfiguredModelRegistration();
|
|
4374
|
-
await awaitConfiguredModelsRegistration();
|
|
4375
|
-
})();
|
|
4376
|
-
await runtimeConfigLoadingPromise;
|
|
4377
|
-
};
|
|
4378
|
-
/**
|
|
4379
|
-
* Ensure that the ArkORM configuration is loaded.
|
|
4380
|
-
* This function can be called to trigger the loading process if it hasn't already been initiated.
|
|
4381
|
-
* If the configuration is already loaded, it will return immediately.
|
|
4382
|
-
*
|
|
4383
|
-
* @returns
|
|
4384
|
-
*/
|
|
4385
|
-
const ensureArkormConfigLoading = () => {
|
|
4386
|
-
if (runtimeConfigLoaded) return;
|
|
4387
|
-
if (!runtimeConfigLoadingPromise) loadArkormConfig();
|
|
4388
|
-
};
|
|
4389
|
-
const getDefaultStubsPath = () => {
|
|
4390
|
-
return resolveDefaultStubsPath();
|
|
4391
|
-
};
|
|
4392
|
-
/**
|
|
4393
|
-
* Get the runtime compatibility client.
|
|
4394
|
-
* This function will trigger the loading of the ArkORM configuration if
|
|
4395
|
-
* it hasn't already been loaded.
|
|
4396
|
-
*
|
|
4397
|
-
* @returns
|
|
4398
|
-
*/
|
|
4399
|
-
const getRuntimeClient = () => {
|
|
4400
|
-
const activeTransactionClient = transactionClientStorage.getStore();
|
|
4401
|
-
if (activeTransactionClient) return activeTransactionClient;
|
|
4402
|
-
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
4403
|
-
return resolveClient(runtimeClientResolver);
|
|
4404
|
-
};
|
|
4405
|
-
/**
|
|
4406
|
-
* @deprecated Use getRuntimeClient instead.
|
|
4407
|
-
*/
|
|
4408
|
-
const getRuntimePrismaClient = getRuntimeClient;
|
|
4409
|
-
/**
|
|
4410
|
-
* Get the currently configured runtime adapter, if any.
|
|
4411
|
-
*
|
|
4412
|
-
* @returns
|
|
4413
|
-
*/
|
|
4414
|
-
const getRuntimeAdapter = () => {
|
|
4415
|
-
const activeTransactionAdapter = transactionAdapterStorage.getStore();
|
|
4416
|
-
if (activeTransactionAdapter) return activeTransactionAdapter;
|
|
4417
|
-
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
4418
|
-
return runtimeAdapter;
|
|
4419
|
-
};
|
|
4420
|
-
/**
|
|
4421
|
-
* Releases the database resources held by the configured runtime — the adapter's
|
|
4422
|
-
* connection pool and/or the configured client. Intended for short-lived
|
|
4423
|
-
* processes (the CLI) so the Node event loop drains and the process exits
|
|
4424
|
-
* promptly instead of hanging on pool idle timeouts. Teardown failures are
|
|
4425
|
-
* swallowed because the process is shutting down anyway.
|
|
4426
|
-
*/
|
|
4427
|
-
const disposeArkormRuntime = async () => {
|
|
4428
|
-
let adapterDisposed = false;
|
|
4429
|
-
try {
|
|
4430
|
-
if (runtimeAdapter && typeof runtimeAdapter.dispose === "function") {
|
|
4431
|
-
await runtimeAdapter.dispose();
|
|
4432
|
-
adapterDisposed = true;
|
|
4433
|
-
}
|
|
4434
|
-
} catch {}
|
|
4435
|
-
const client = getRuntimeClient();
|
|
4436
|
-
if (!client) return;
|
|
4437
|
-
try {
|
|
4438
|
-
if (typeof client.$disconnect === "function") await client.$disconnect();
|
|
4439
|
-
else if (!adapterDisposed && typeof client.destroy === "function") await client.destroy();
|
|
4440
|
-
else if (!adapterDisposed && typeof client.end === "function") await client.end();
|
|
4441
|
-
} catch {}
|
|
4442
|
-
};
|
|
4443
|
-
const getActiveTransactionClient = () => {
|
|
4444
|
-
return transactionClientStorage.getStore();
|
|
4445
|
-
};
|
|
4446
|
-
const getActiveTransactionAdapter = () => {
|
|
4447
|
-
return transactionAdapterStorage.getStore();
|
|
4448
|
-
};
|
|
4449
|
-
const isTransactionCapableClient = (value) => {
|
|
4450
|
-
if (!value || typeof value !== "object") return false;
|
|
4451
|
-
return typeof value.$transaction === "function";
|
|
4452
|
-
};
|
|
4453
|
-
const runArkormTransaction = async (callback, options = {}, preferredAdapter) => {
|
|
4454
|
-
const activeTransactionAdapter = transactionAdapterStorage.getStore();
|
|
4455
|
-
const activeTransactionClient = transactionClientStorage.getStore();
|
|
4456
|
-
if (activeTransactionAdapter || activeTransactionClient) return await callback({
|
|
4457
|
-
adapter: activeTransactionAdapter,
|
|
4458
|
-
client: activeTransactionClient
|
|
4459
|
-
});
|
|
4460
|
-
const adapter = preferredAdapter ?? getRuntimeAdapter();
|
|
4461
|
-
if (adapter) return await adapter.transaction(async (transactionAdapter) => {
|
|
4462
|
-
return await transactionAdapterStorage.run(transactionAdapter, async () => {
|
|
4463
|
-
return await callback({
|
|
4464
|
-
adapter: transactionAdapter,
|
|
4465
|
-
client: transactionClientStorage.getStore()
|
|
4466
|
-
});
|
|
4467
|
-
});
|
|
4468
|
-
}, options);
|
|
4469
|
-
const client = getRuntimeClient();
|
|
4470
|
-
if (!client) throw new ArkormException("Cannot start a transaction without a configured runtime client or adapter.", {
|
|
4471
|
-
code: "CLIENT_NOT_CONFIGURED",
|
|
4472
|
-
operation: "transaction"
|
|
4473
|
-
});
|
|
4474
|
-
if (!isTransactionCapableClient(client)) throw new UnsupportedAdapterFeatureException("Transactions are not supported by the current adapter.", {
|
|
4475
|
-
code: "TRANSACTION_NOT_SUPPORTED",
|
|
4476
|
-
operation: "transaction"
|
|
4477
|
-
});
|
|
4478
|
-
return await client.$transaction(async (transactionClient) => {
|
|
4479
|
-
return await transactionClientStorage.run(transactionClient, async () => {
|
|
4480
|
-
return await callback({ client: transactionClient });
|
|
4481
|
-
});
|
|
4482
|
-
}, options);
|
|
4483
|
-
};
|
|
4484
|
-
/**
|
|
4485
|
-
* Get the configured pagination URL driver factory from runtime config.
|
|
4486
|
-
*
|
|
4487
|
-
* @returns
|
|
4488
|
-
*/
|
|
4489
|
-
const getRuntimePaginationURLDriverFactory = () => {
|
|
4490
|
-
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
4491
|
-
return runtimePaginationURLDriverFactory;
|
|
4492
|
-
};
|
|
4493
|
-
/**
|
|
4494
|
-
* Get the configured current-page resolver from runtime config.
|
|
4495
|
-
*
|
|
4496
|
-
* @returns
|
|
4497
|
-
*/
|
|
4498
|
-
const getRuntimePaginationCurrentPageResolver = () => {
|
|
4499
|
-
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
4500
|
-
return runtimePaginationCurrentPageResolver;
|
|
4501
|
-
};
|
|
4502
|
-
const getRuntimeDebugHandler = () => {
|
|
4503
|
-
if (!runtimeConfigLoaded) loadRuntimeConfigSync();
|
|
4504
|
-
return runtimeDebugHandler;
|
|
4505
|
-
};
|
|
4506
|
-
const emitRuntimeDebugEvent = (event) => {
|
|
4507
|
-
getRuntimeDebugHandler()?.(event);
|
|
4508
|
-
};
|
|
4509
|
-
/**
|
|
4510
|
-
* Check if a given value matches Arkorm's query-schema contract
|
|
4511
|
-
* by verifying the presence of common delegate methods.
|
|
4512
|
-
*
|
|
4513
|
-
* @param value The value to check.
|
|
4514
|
-
* @returns True if the value matches the query-schema contract, false otherwise.
|
|
4515
|
-
*/
|
|
4516
|
-
const isQuerySchemaLike = (value) => {
|
|
4517
|
-
if (!value || typeof value !== "object") return false;
|
|
4518
|
-
const candidate = value;
|
|
4519
|
-
return [
|
|
4520
|
-
"findMany",
|
|
4521
|
-
"findFirst",
|
|
4522
|
-
"create",
|
|
4523
|
-
"update",
|
|
4524
|
-
"delete",
|
|
4525
|
-
"count"
|
|
4526
|
-
].every((method) => typeof candidate[method] === "function");
|
|
4527
4548
|
};
|
|
4528
|
-
/**
|
|
4529
|
-
* @deprecated Use isQuerySchemaLike instead.
|
|
4530
|
-
*/
|
|
4531
|
-
const isDelegateLike = isQuerySchemaLike;
|
|
4532
|
-
loadArkormConfig();
|
|
4533
4549
|
|
|
4534
4550
|
//#endregion
|
|
4535
4551
|
//#region src/URLDriver.ts
|
|
@@ -7893,6 +7909,12 @@ Object.defineProperty(exports, 'resolvePrismaType', {
|
|
|
7893
7909
|
return resolvePrismaType;
|
|
7894
7910
|
}
|
|
7895
7911
|
});
|
|
7912
|
+
Object.defineProperty(exports, 'resolveRegisteredModel', {
|
|
7913
|
+
enumerable: true,
|
|
7914
|
+
get: function () {
|
|
7915
|
+
return resolveRegisteredModel;
|
|
7916
|
+
}
|
|
7917
|
+
});
|
|
7896
7918
|
Object.defineProperty(exports, 'runArkormTransaction', {
|
|
7897
7919
|
enumerable: true,
|
|
7898
7920
|
get: function () {
|