@stacksjs/database 0.70.187 → 0.70.188
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/seeder.d.ts +8 -0
- package/dist/seeder.js +90 -24
- package/package.json +10 -10
package/dist/seeder.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ import type { Attribute, Model } from '@stacksjs/types';
|
|
|
5
5
|
* list stays a single source of truth.
|
|
6
6
|
*/
|
|
7
7
|
export declare function isProtectedModel(name: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Choose one consistent set of parent ids per record.
|
|
10
|
+
*
|
|
11
|
+
* Split out from the query above so the rule can be exercised without a
|
|
12
|
+
* database: given the parent rows, the same input always produces rows whose
|
|
13
|
+
* foreign keys agree with one another.
|
|
14
|
+
*/
|
|
15
|
+
export declare function chooseRelations(pools: { column: string, rows: Record<string, unknown>[] }[], count: number): Record<string, unknown>[];
|
|
8
16
|
/**
|
|
9
17
|
* Seeds the database from your models.
|
|
10
18
|
*
|
package/dist/seeder.js
CHANGED
|
@@ -87,7 +87,7 @@ function isPasswordField(fieldName, attr) {
|
|
|
87
87
|
return !0;
|
|
88
88
|
return !1;
|
|
89
89
|
}
|
|
90
|
-
async function generateRecord(attributes, modelName,
|
|
90
|
+
async function generateRecord(attributes, modelName, report = !1) {
|
|
91
91
|
const record = {};
|
|
92
92
|
for (const [fieldName, attr] of Object.entries(attributes)) {
|
|
93
93
|
const columnName = snakeCase(fieldName);
|
|
@@ -97,8 +97,8 @@ async function generateRecord(attributes, modelName, verbose = !1) {
|
|
|
97
97
|
value = attr.factory(faker);
|
|
98
98
|
} catch (err) {
|
|
99
99
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
100
|
-
if (
|
|
101
|
-
log.warn(` Factory failed for ${modelName}.${fieldName}: ${errorMsg}
|
|
100
|
+
if (report)
|
|
101
|
+
log.warn(` Factory failed for ${modelName}.${fieldName}: ${errorMsg} \u2014 seeding the default instead.`);
|
|
102
102
|
if (attr.default !== void 0)
|
|
103
103
|
value = attr.default;
|
|
104
104
|
else
|
|
@@ -113,8 +113,7 @@ async function generateRecord(attributes, modelName, verbose = !1) {
|
|
|
113
113
|
value = await hashMake(value, { algorithm: "bcrypt" });
|
|
114
114
|
} catch (err) {
|
|
115
115
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
116
|
-
|
|
117
|
-
log.warn(` Failed to hash password for ${modelName}.${fieldName}: ${errorMsg}`);
|
|
116
|
+
log.warn(` Failed to hash password for ${modelName}.${fieldName}: ${errorMsg}`);
|
|
118
117
|
}
|
|
119
118
|
record[columnName] = value;
|
|
120
119
|
}
|
|
@@ -140,11 +139,54 @@ function fixtureToColumns(fixture) {
|
|
|
140
139
|
out[snakeCase(key)] = value;
|
|
141
140
|
return out;
|
|
142
141
|
}
|
|
143
|
-
async function
|
|
144
|
-
|
|
142
|
+
async function existingRows(table) {
|
|
143
|
+
try {
|
|
144
|
+
return await db.selectFrom(table).selectAll().limit(500).execute();
|
|
145
|
+
} catch {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function relationColumns(model) {
|
|
150
|
+
const parents = parentModels(model);
|
|
151
|
+
if (parents.length === 0)
|
|
152
|
+
return [];
|
|
153
|
+
const pools = [];
|
|
154
|
+
for (const parent of parents) {
|
|
155
|
+
const column = `${snakeCase(parent)}_id`;
|
|
156
|
+
if (model.attributes[column] || model.attributes[parent])
|
|
157
|
+
continue;
|
|
158
|
+
const rows = await existingRows(`${snakeCase(parent)}s`);
|
|
159
|
+
if (rows.length > 0)
|
|
160
|
+
pools.push({ column, rows });
|
|
161
|
+
}
|
|
162
|
+
return chooseRelations(pools, model.count);
|
|
163
|
+
}
|
|
164
|
+
export function chooseRelations(pools, count) {
|
|
165
|
+
if (pools.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
const wanted = new Set(pools.map((pool) => pool.column)), specificity = (pool) => {
|
|
168
|
+
const sample = pool.rows[0] ?? {};
|
|
169
|
+
return [...wanted].filter((column) => column !== pool.column && (column in sample)).length;
|
|
170
|
+
}, ordered = [...pools].sort((a, b) => specificity(b) - specificity(a));
|
|
171
|
+
return Array.from({ length: count }, () => {
|
|
172
|
+
const row = {};
|
|
173
|
+
for (const pool of ordered) {
|
|
174
|
+
if (row[pool.column] != null)
|
|
175
|
+
continue;
|
|
176
|
+
const agrees = (candidate) => [...wanted].every((column) => row[column] == null || candidate[column] == null || candidate[column] === row[column]), candidates = pool.rows.filter(agrees), from = candidates.length > 0 ? candidates : pool.rows, chosen = from[Math.floor(Math.random() * from.length)];
|
|
177
|
+
row[pool.column] = chosen.id;
|
|
178
|
+
for (const column of wanted)
|
|
179
|
+
if (column !== pool.column && row[column] == null && chosen[column] != null)
|
|
180
|
+
row[column] = chosen[column];
|
|
181
|
+
}
|
|
182
|
+
return row;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function generateRecords(model) {
|
|
186
|
+
const records = [], relations = await relationColumns(model);
|
|
145
187
|
for (let i = 0;i < model.count; i++) {
|
|
146
|
-
const record = await generateRecord(model.attributes, model.name,
|
|
147
|
-
records.push(fixture ? { ...
|
|
188
|
+
const record = await generateRecord(model.attributes, model.name, i === 0), fixture = model.fixtures[i], withRelations = { ...record, ...relations[i] ?? {} };
|
|
189
|
+
records.push(fixture ? { ...withRelations, ...fixtureToColumns(fixture) } : withRelations);
|
|
148
190
|
}
|
|
149
191
|
return records;
|
|
150
192
|
}
|
|
@@ -180,7 +222,7 @@ async function seedModel(model, options) {
|
|
|
180
222
|
};
|
|
181
223
|
}
|
|
182
224
|
}
|
|
183
|
-
const records = await generateRecords(model
|
|
225
|
+
const records = await generateRecords(model);
|
|
184
226
|
if (records.length === 0)
|
|
185
227
|
return {
|
|
186
228
|
model: model.name,
|
|
@@ -189,12 +231,6 @@ async function seedModel(model, options) {
|
|
|
189
231
|
success: !0,
|
|
190
232
|
duration: Date.now() - startTime
|
|
191
233
|
};
|
|
192
|
-
if (options.fresh)
|
|
193
|
-
try {
|
|
194
|
-
await db.deleteFrom(model.table).execute();
|
|
195
|
-
if (options.verbose)
|
|
196
|
-
log.info(` Truncated table: ${model.table}`);
|
|
197
|
-
} catch {}
|
|
198
234
|
const batchSize = 100;
|
|
199
235
|
let inserted = 0;
|
|
200
236
|
for (let i = 0;i < records.length; i += batchSize) {
|
|
@@ -225,16 +261,44 @@ async function seedModel(model, options) {
|
|
|
225
261
|
};
|
|
226
262
|
}
|
|
227
263
|
}
|
|
264
|
+
function parentModels(model) {
|
|
265
|
+
const belongsTo = model.model.belongsTo;
|
|
266
|
+
if (Array.isArray(belongsTo))
|
|
267
|
+
return belongsTo.map((entry) => typeof entry === "string" ? entry : String(entry?.model ?? "")).filter(Boolean);
|
|
268
|
+
if (belongsTo && typeof belongsTo === "object")
|
|
269
|
+
return Object.values(belongsTo).map((value) => String(value?.model ?? value)).filter(Boolean);
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
async function clearTables(models, verbose) {
|
|
273
|
+
for (const model of [...models].reverse())
|
|
274
|
+
try {
|
|
275
|
+
await db.deleteFrom(model.table).execute();
|
|
276
|
+
if (verbose)
|
|
277
|
+
log.info(` Truncated table: ${model.table}`);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
280
|
+
if (/no such table|doesn't exist|does not exist/i.test(message))
|
|
281
|
+
continue;
|
|
282
|
+
throw Error(`Could not empty ${model.table} before seeding: ${message}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
228
285
|
function sortModelsByDependencies(models) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
286
|
+
const byName = new Map(models.map((model) => [model.name, model])), ordered = [], state = new Map, visit = (model) => {
|
|
287
|
+
const status = state.get(model.name);
|
|
288
|
+
if (status === "done" || status === "visiting")
|
|
289
|
+
return;
|
|
290
|
+
state.set(model.name, "visiting");
|
|
291
|
+
for (const parentName of parentModels(model)) {
|
|
292
|
+
const parent = byName.get(parentName);
|
|
293
|
+
if (parent && parent !== model)
|
|
294
|
+
visit(parent);
|
|
295
|
+
}
|
|
296
|
+
state.set(model.name, "done");
|
|
297
|
+
ordered.push(model);
|
|
233
298
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
});
|
|
299
|
+
for (const model of models)
|
|
300
|
+
visit(model);
|
|
301
|
+
return ordered;
|
|
238
302
|
}
|
|
239
303
|
export async function seed(config = {}) {
|
|
240
304
|
const startTime = Date.now();
|
|
@@ -277,6 +341,8 @@ export async function seed(config = {}) {
|
|
|
277
341
|
models = sortModelsByDependencies(models);
|
|
278
342
|
if (verbose)
|
|
279
343
|
log.info(`Found ${models.length} seedable model(s)`);
|
|
344
|
+
if (config.fresh)
|
|
345
|
+
await clearTables(models, verbose);
|
|
280
346
|
const results = [];
|
|
281
347
|
for (const model of models) {
|
|
282
348
|
if (verbose)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.188",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -60,15 +60,15 @@
|
|
|
60
60
|
"dynamodb-tooling": "^0.3.2"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@stacksjs/cli": "0.70.
|
|
64
|
-
"@stacksjs/config": "0.70.
|
|
65
|
-
"@stacksjs/logging": "0.70.
|
|
66
|
-
"@stacksjs/router": "0.70.
|
|
63
|
+
"@stacksjs/cli": "0.70.188",
|
|
64
|
+
"@stacksjs/config": "0.70.188",
|
|
65
|
+
"@stacksjs/logging": "0.70.188",
|
|
66
|
+
"@stacksjs/router": "0.70.188",
|
|
67
67
|
"better-dx": "^0.2.17",
|
|
68
|
-
"@stacksjs/path": "0.70.
|
|
69
|
-
"@stacksjs/query-builder": "0.70.
|
|
70
|
-
"@stacksjs/storage": "0.70.
|
|
71
|
-
"@stacksjs/strings": "0.70.
|
|
72
|
-
"@stacksjs/utils": "0.70.
|
|
68
|
+
"@stacksjs/path": "0.70.188",
|
|
69
|
+
"@stacksjs/query-builder": "0.70.188",
|
|
70
|
+
"@stacksjs/storage": "0.70.188",
|
|
71
|
+
"@stacksjs/strings": "0.70.188",
|
|
72
|
+
"@stacksjs/utils": "0.70.188"
|
|
73
73
|
}
|
|
74
74
|
}
|