@stacksjs/database 0.70.163 → 0.70.165
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -3
- package/dist/migrations.js +1 -1
- package/dist/seeder.d.ts +7 -17
- package/dist/seeder.js +0 -4
- package/dist/types.js +1 -1
- package/package.json +10 -10
- package/dist/class-seeder.d.ts +0 -65
- package/dist/class-seeder.js +0 -116
- package/dist/factory.d.ts +0 -41
- package/dist/factory.js +0 -51
- package/dist/seed-scaffold.d.ts +0 -34
- package/dist/seed-scaffold.js +0 -144
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ For help, discussion about best practices, or any other conversation that would
|
|
|
50
50
|
|
|
51
51
|
For casual chit-chat with others using this package:
|
|
52
52
|
|
|
53
|
-
[Join the Stacks Discord Server](https://
|
|
53
|
+
[Join the Stacks Discord Server](https://stacksjs.com/discord)
|
|
54
54
|
|
|
55
55
|
## 📄 License
|
|
56
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -10,8 +10,6 @@ export type {
|
|
|
10
10
|
PostgresConfig,
|
|
11
11
|
SqliteConfig,
|
|
12
12
|
} from './driver-config';
|
|
13
|
-
export type { GenerateOptions } from './factory';
|
|
14
|
-
export type { ScaffoldOptions, ScaffoldResult } from './seed-scaffold';
|
|
15
13
|
export type { DeclaredFK, FkAuditResult, FkOrphan, FkOrphanReport, LiveFK } from './fk-audit';
|
|
16
14
|
export type { DeclaredUnique, LiveUniqueIndex, UniqueAuditResult } from './unique-audit';
|
|
17
15
|
export type {
|
|
@@ -83,22 +81,10 @@ export * from './types';
|
|
|
83
81
|
export * from './migrations';
|
|
84
82
|
// Query logger DI hook (router calls setQueryTracker on init)
|
|
85
83
|
export { setQueryTracker, logQuery } from './query-logger';
|
|
86
|
-
// Class-based seeders (supplements the model-attribute auto-seeder)
|
|
87
|
-
export { Seeder, runClassSeeders } from './class-seeder';
|
|
88
84
|
// Zero-downtime migration helpers
|
|
89
85
|
export { addColumnSafely, backfillInBatches, renameColumnSafely } from './safe-migrations';
|
|
90
86
|
// Seeding
|
|
91
87
|
export * from './seeder';
|
|
92
|
-
// stacksjs/stacks#1919 — public factory API. The canonical replacement
|
|
93
|
-
// for the legacy `useSeeder` trait + auto-walker. Class seeders call
|
|
94
|
-
// `factory.generate(Model, opts)` explicitly so there's one
|
|
95
|
-
// orchestration layer per table, no double-fire on tables that have
|
|
96
|
-
// both a `useSeeder` trait and a class seeder file.
|
|
97
|
-
export { factory, generate as factoryGenerate } from './factory';
|
|
98
|
-
// `buddy seed:scaffold` codemod — generates class-seeder files for
|
|
99
|
-
// every model with a `useSeeder` trait, easing the migration off the
|
|
100
|
-
// auto-walker.
|
|
101
|
-
export { scaffoldClassSeedersFromModels, renderSeederFile } from './seed-scaffold';
|
|
102
88
|
// Driver utilities
|
|
103
89
|
export * from './drivers/index';
|
|
104
90
|
// Custom migrations (jobs, errors, etc.)
|
package/dist/index.js
CHANGED
|
@@ -17,11 +17,8 @@ export * from "./utils";
|
|
|
17
17
|
export * from "./types";
|
|
18
18
|
export * from "./migrations";
|
|
19
19
|
export { setQueryTracker, logQuery } from "./query-logger";
|
|
20
|
-
export { Seeder, runClassSeeders } from "./class-seeder";
|
|
21
20
|
export { addColumnSafely, backfillInBatches, renameColumnSafely } from "./safe-migrations";
|
|
22
21
|
export * from "./seeder";
|
|
23
|
-
export { factory, generate as factoryGenerate } from "./factory";
|
|
24
|
-
export { scaffoldClassSeedersFromModels, renderSeederFile } from "./seed-scaffold";
|
|
25
22
|
export * from "./drivers";
|
|
26
23
|
export * from "./custom";
|
|
27
24
|
export * from "./auth-tables";
|
package/dist/migrations.js
CHANGED
|
@@ -297,7 +297,7 @@ async function countAppliedMigrations() {
|
|
|
297
297
|
}
|
|
298
298
|
async function writeMigrateMarker(appliedCount) {
|
|
299
299
|
try {
|
|
300
|
-
const fs = await import("node:fs/promises"), dir = path.
|
|
300
|
+
const fs = await import("node:fs/promises"), dir = path.frameworkRuntimePath();
|
|
301
301
|
await fs.mkdir(dir, { recursive: !0 });
|
|
302
302
|
const file = `${dir}/last-migrate-result.json`, body = JSON.stringify({
|
|
303
303
|
appliedCount,
|
package/dist/seeder.d.ts
CHANGED
|
@@ -6,24 +6,14 @@ import type { Attribute, Model } from '@stacksjs/types';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function isProtectedModel(name: string): boolean;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Main seeding function
|
|
17
|
-
* Seeds the database using model factory functions
|
|
18
|
-
* Loads models from both framework defaults and user-defined models,
|
|
19
|
-
* with user models taking precedence.
|
|
9
|
+
* Seeds the database from your models.
|
|
10
|
+
*
|
|
11
|
+
* Walks every model that declares a `useSeeder` trait and fills its table
|
|
12
|
+
* using the per-attribute `factory: faker => …` declarations. Models are
|
|
13
|
+
* loaded from `app/Models/` and, with `includeDefaults`, the framework's
|
|
14
|
+
* built-in models too - user models win on a name collision.
|
|
20
15
|
*
|
|
21
|
-
*
|
|
22
|
-
* longer invoked by `./buddy seed`. Migrate each `useSeeder` trait to
|
|
23
|
-
* a class seeder via `./buddy seed:scaffold`, then call
|
|
24
|
-
* `factory.generate(Model, opts)` from inside each seeder. This
|
|
25
|
-
* function remains exported for programmatic back-compat but is
|
|
26
|
-
* scheduled for removal.
|
|
16
|
+
* This is what `./buddy seed` runs.
|
|
27
17
|
*/
|
|
28
18
|
export declare function seed(config?: SeederConfig): Promise<SeedSummary>;
|
|
29
19
|
/**
|
package/dist/seeder.js
CHANGED
|
@@ -148,9 +148,6 @@ async function generateRecords(model, verbose = !1) {
|
|
|
148
148
|
}
|
|
149
149
|
return records;
|
|
150
150
|
}
|
|
151
|
-
export function seedModelDirect(model, options) {
|
|
152
|
-
return seedModel(model, options);
|
|
153
|
-
}
|
|
154
151
|
async function seedModel(model, options) {
|
|
155
152
|
const startTime = Date.now();
|
|
156
153
|
try {
|
|
@@ -259,7 +256,6 @@ export async function seed(config = {}) {
|
|
|
259
256
|
duration: Date.now() - startTime
|
|
260
257
|
};
|
|
261
258
|
}
|
|
262
|
-
log.warn(`[seed] The \`useSeeder\` trait + auto-walker is deprecated (stacksjs/stacks#1919, #1929). Run \`./buddy seed:scaffold\` to generate a class seeder per \`useSeeder\` model AND strip the trait from the model in one pass. The walker + trait are scheduled for removal in the next major. Affected: ${models.map((m) => m.name).join(", ")}`);
|
|
263
259
|
if (config.only && config.only.length > 0)
|
|
264
260
|
models = models.filter((m) => config.only.includes(m.name));
|
|
265
261
|
if (config.except && config.except.length > 0)
|
package/dist/types.js
CHANGED
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.165",
|
|
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.165",
|
|
64
|
+
"@stacksjs/config": "0.70.165",
|
|
65
|
+
"@stacksjs/logging": "0.70.165",
|
|
66
|
+
"@stacksjs/router": "0.70.165",
|
|
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.165",
|
|
69
|
+
"@stacksjs/query-builder": "0.70.165",
|
|
70
|
+
"@stacksjs/storage": "0.70.165",
|
|
71
|
+
"@stacksjs/strings": "0.70.165",
|
|
72
|
+
"@stacksjs/utils": "0.70.165"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/dist/class-seeder.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Topologically sort seeders by their declared `dependencies`. Ties
|
|
3
|
-
* (and dependency-free seeders) come out in alphabetical order so the
|
|
4
|
-
* result is deterministic across filesystems and runs.
|
|
5
|
-
*
|
|
6
|
-
* Unknown dependency names are dropped from the graph with a warning —
|
|
7
|
-
* they may refer to model-factory seeders that ran earlier in the
|
|
8
|
-
* `buddy seed` pipeline, or be stale references from a rename. The run
|
|
9
|
-
* doesn't fail because of them.
|
|
10
|
-
*
|
|
11
|
-
* Cycles throw with the offending class names in the error message.
|
|
12
|
-
*
|
|
13
|
-
* Exported for testing.
|
|
14
|
-
*/
|
|
15
|
-
export declare function topoSortSeeders(seeders: Array<{ name: string, dependencies?: string[] }>): string[];
|
|
16
|
-
/*.ts`; an explicit `--class` filters to one.
|
|
17
|
-
*
|
|
18
|
-
* Ordering:
|
|
19
|
-
* 1. Files matching `*.ts` (excluding `_*.ts`) are imported.
|
|
20
|
-
* 2. If any seeder declares `dependencies`, the runnable set is
|
|
21
|
-
* topologically sorted (alphabetical tie-break).
|
|
22
|
-
* 3. Otherwise, the alphabetical order from `Array.sort()` wins —
|
|
23
|
-
* cheaper than the topo path and predictable across filesystems.
|
|
24
|
-
*
|
|
25
|
-
* Class-name filtering via `options.class` short-circuits both paths
|
|
26
|
-
* and runs only that one seeder. Cross-seeder dependencies are NOT
|
|
27
|
-
* resolved transitively in that mode — the caller takes responsibility
|
|
28
|
-
* for whatever prereqs are needed.
|
|
29
|
-
*
|
|
30
|
-
* See stacksjs/stacks#1855 for the original report of unsorted FS
|
|
31
|
-
* iteration producing zero-row seed runs.
|
|
32
|
-
*/
|
|
33
|
-
export declare function runClassSeeders(options?: RunOptions): Promise<{ ran: string[], skipped: string[] }>;
|
|
34
|
-
declare interface RunOptions {
|
|
35
|
-
class?: string
|
|
36
|
-
dir?: string
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Base class for class-based seeders. Subclass this and implement
|
|
40
|
-
* `async run()`. Seeders may call `this.call()` to invoke other
|
|
41
|
-
* seeders, mirroring Laravel's nested-seeder pattern.
|
|
42
|
-
*
|
|
43
|
-
* Cross-seeder ordering can be declared explicitly via `dependencies`:
|
|
44
|
-
*
|
|
45
|
-
* ```ts
|
|
46
|
-
* export default class JudgeSeeder extends Seeder {
|
|
47
|
-
* dependencies = ['CourtHouseSeeder']
|
|
48
|
-
* async run() { ... }
|
|
49
|
-
* }
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* The class name of each dependency is matched against the class names
|
|
53
|
-
* `runClassSeeders` discovered in the seeders directory. Unknown
|
|
54
|
-
* dependency names are warned about but don't fail the run (they may
|
|
55
|
-
* refer to a model-factory seeder run earlier in `buddy seed`).
|
|
56
|
-
*
|
|
57
|
-
* When no `dependencies` are declared, seeders run in alphabetical
|
|
58
|
-
* order — predictable across filesystems and good enough for projects
|
|
59
|
-
* that name seeders by data flow (CourtHouse → Judge → Review).
|
|
60
|
-
*/
|
|
61
|
-
export declare abstract class Seeder {
|
|
62
|
-
dependencies?: string[];
|
|
63
|
-
abstract run(): Promise<void> | void;
|
|
64
|
-
protected call(other: new () => Seeder): Promise<void>;
|
|
65
|
-
}
|
package/dist/class-seeder.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { log } from "@stacksjs/logging";
|
|
2
|
-
import { path } from "@stacksjs/path";
|
|
3
|
-
import { fs } from "@stacksjs/storage";
|
|
4
|
-
|
|
5
|
-
export class Seeder {
|
|
6
|
-
dependencies;
|
|
7
|
-
async call(other) {
|
|
8
|
-
await new other().run();
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function topoSortSeeders(seeders) {
|
|
12
|
-
const known = new Set(seeders.map((s) => s.name)), effectiveDeps = new Map;
|
|
13
|
-
for (const s of seeders) {
|
|
14
|
-
const deps = new Set;
|
|
15
|
-
for (const dep of s.dependencies ?? []) {
|
|
16
|
-
if (dep === s.name)
|
|
17
|
-
continue;
|
|
18
|
-
if (!known.has(dep)) {
|
|
19
|
-
log.warn(`[seeder] ${s.name} depends on '${dep}' but no seeder by that name was discovered \u2014 ignoring`);
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
deps.add(dep);
|
|
23
|
-
}
|
|
24
|
-
effectiveDeps.set(s.name, deps);
|
|
25
|
-
}
|
|
26
|
-
const indegree = new Map, successors = new Map;
|
|
27
|
-
for (const s of seeders) {
|
|
28
|
-
indegree.set(s.name, effectiveDeps.get(s.name).size);
|
|
29
|
-
successors.set(s.name, new Set);
|
|
30
|
-
}
|
|
31
|
-
for (const s of seeders)
|
|
32
|
-
for (const dep of effectiveDeps.get(s.name))
|
|
33
|
-
successors.get(dep).add(s.name);
|
|
34
|
-
const ready = seeders.filter((s) => indegree.get(s.name) === 0).map((s) => s.name).sort(), result = [];
|
|
35
|
-
while (ready.length > 0) {
|
|
36
|
-
const next = ready.shift();
|
|
37
|
-
result.push(next);
|
|
38
|
-
const newlyReady = [];
|
|
39
|
-
for (const succ of successors.get(next)) {
|
|
40
|
-
const left = (indegree.get(succ) ?? 0) - 1;
|
|
41
|
-
indegree.set(succ, left);
|
|
42
|
-
if (left === 0)
|
|
43
|
-
newlyReady.push(succ);
|
|
44
|
-
}
|
|
45
|
-
if (newlyReady.length > 0) {
|
|
46
|
-
ready.push(...newlyReady);
|
|
47
|
-
ready.sort();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (result.length !== seeders.length) {
|
|
51
|
-
const unresolved = seeders.map((s) => s.name).filter((n) => !result.includes(n));
|
|
52
|
-
throw Error(`[seeder] Cycle in seeder \`dependencies\` among: ${unresolved.join(", ")}`);
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
export async function runClassSeeders(options = {}) {
|
|
57
|
-
try {
|
|
58
|
-
const { injectGlobalAutoImports } = await import("@stacksjs/server");
|
|
59
|
-
await injectGlobalAutoImports();
|
|
60
|
-
} catch {}
|
|
61
|
-
const dir = options.dir ?? path.projectPath("database/seeders"), ran = [], skipped = [];
|
|
62
|
-
if (!fs.existsSync(dir)) {
|
|
63
|
-
log.info(`[seeder] No class seeders directory at ${dir}`);
|
|
64
|
-
return { ran, skipped };
|
|
65
|
-
}
|
|
66
|
-
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".ts") && !f.startsWith("_")).sort(), loaded = [];
|
|
67
|
-
for (const file of files) {
|
|
68
|
-
const className = file.replace(/\.ts$/, "");
|
|
69
|
-
try {
|
|
70
|
-
const mod = await import(`${dir}/${file}`), Klass = mod.default ?? mod[className];
|
|
71
|
-
if (!Klass) {
|
|
72
|
-
log.warn(`[seeder] ${file} has no default export`);
|
|
73
|
-
skipped.push(className);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const inst = new Klass;
|
|
77
|
-
if (typeof inst.run !== "function") {
|
|
78
|
-
log.warn(`[seeder] ${className} does not implement run()`);
|
|
79
|
-
skipped.push(className);
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
loaded.push({ className, inst });
|
|
83
|
-
} catch (err) {
|
|
84
|
-
log.error(`[seeder] ${className} failed to load:`, err);
|
|
85
|
-
skipped.push(className);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const declaresDeps = loaded.some((l) => (l.inst.dependencies?.length ?? 0) > 0);
|
|
89
|
-
let order;
|
|
90
|
-
if (declaresDeps)
|
|
91
|
-
try {
|
|
92
|
-
order = topoSortSeeders(loaded.map((l) => ({ name: l.className, dependencies: l.inst.dependencies })));
|
|
93
|
-
} catch (err) {
|
|
94
|
-
log.error(err instanceof Error ? err.message : String(err));
|
|
95
|
-
return { ran, skipped: [...skipped, ...loaded.map((l) => l.className)] };
|
|
96
|
-
}
|
|
97
|
-
else
|
|
98
|
-
order = loaded.map((l) => l.className);
|
|
99
|
-
const byName = new Map(loaded.map((l) => [l.className, l.inst]));
|
|
100
|
-
for (const className of order) {
|
|
101
|
-
if (options.class && className !== options.class) {
|
|
102
|
-
skipped.push(className);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const inst = byName.get(className);
|
|
106
|
-
try {
|
|
107
|
-
log.info(`[seeder] Running ${className}\u2026`);
|
|
108
|
-
await inst.run();
|
|
109
|
-
ran.push(className);
|
|
110
|
-
} catch (err) {
|
|
111
|
-
log.error(`[seeder] ${className} failed:`, err);
|
|
112
|
-
skipped.push(className);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return { ran, skipped };
|
|
116
|
-
}
|
package/dist/factory.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Attribute, Model } from '@stacksjs/types';
|
|
2
|
-
import type { SeedResult } from './seeder';
|
|
3
|
-
/**
|
|
4
|
-
* Build the internal `SeederModel`-shaped payload that `seedModelDirect`
|
|
5
|
-
* expects from a public-API call. Pure function — exported separately
|
|
6
|
-
* so tests can assert the override-precedence rules without touching
|
|
7
|
-
* the database.
|
|
8
|
-
*
|
|
9
|
-
* Precedence (lowest → highest): per-attribute `factory` output →
|
|
10
|
-
* global `options.with` → per-row `options.rows[i]`. All keys are
|
|
11
|
-
* snake-cased before insert so callers can use the model's camelCase
|
|
12
|
-
* attribute names without thinking about column naming.
|
|
13
|
-
*/
|
|
14
|
-
export declare function buildSeederPayload(modelInput: unknown, options?: GenerateOptions): {
|
|
15
|
-
name: string
|
|
16
|
-
table: string
|
|
17
|
-
count: number
|
|
18
|
-
fixtures: Array<Record<string, unknown>>
|
|
19
|
-
attributes: Record<string, Attribute>
|
|
20
|
-
model: Model
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Generate factory rows for a model and insert them. Designed to be
|
|
24
|
-
* called from a class seeder.
|
|
25
|
-
*
|
|
26
|
-
* Honours the model's per-attribute `factory: faker => …` declarations
|
|
27
|
-
* — exactly the same code path as the legacy auto-walker — but without
|
|
28
|
-
* the implicit "every model with `useSeeder` fires on every run"
|
|
29
|
-
* coupling. See stacksjs/stacks#1919 for the rationale.
|
|
30
|
-
*/
|
|
31
|
-
export declare function generate(modelInput: unknown, options?: GenerateOptions): Promise<SeedResult>;
|
|
32
|
-
export declare const factory: {
|
|
33
|
-
generate: typeof generate
|
|
34
|
-
};
|
|
35
|
-
export declare interface GenerateOptions {
|
|
36
|
-
count?: number
|
|
37
|
-
fresh?: boolean
|
|
38
|
-
verbose?: boolean
|
|
39
|
-
with?: Record<string, unknown>
|
|
40
|
-
rows?: Array<Record<string, unknown>>
|
|
41
|
-
}
|
package/dist/factory.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { log } from "@stacksjs/logging";
|
|
2
|
-
import { seedModelDirect } from "./seeder";
|
|
3
|
-
function resolveDefinition(input) {
|
|
4
|
-
if (input && typeof input === "object") {
|
|
5
|
-
const obj = input;
|
|
6
|
-
if (obj._definition && typeof obj._definition === "object" && "name" in obj._definition)
|
|
7
|
-
return obj._definition;
|
|
8
|
-
if ("name" in obj && (("attributes" in obj) || ("table" in obj) || ("traits" in obj)))
|
|
9
|
-
return obj;
|
|
10
|
-
}
|
|
11
|
-
throw Error("factory.generate: expected a Stacks model (the default export of app/Models/*.ts, or a defineModel() return value). Got something without a `.name` field.");
|
|
12
|
-
}
|
|
13
|
-
function snakeCase(str) {
|
|
14
|
-
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replace(/(\d)([A-Za-z])/g, "$1_$2").toLowerCase();
|
|
15
|
-
}
|
|
16
|
-
function snakeCaseKeys(input) {
|
|
17
|
-
const out = {};
|
|
18
|
-
for (const [key, value] of Object.entries(input))
|
|
19
|
-
out[snakeCase(key)] = value;
|
|
20
|
-
return out;
|
|
21
|
-
}
|
|
22
|
-
export function buildSeederPayload(modelInput, options = {}) {
|
|
23
|
-
const def = resolveDefinition(modelInput), name = def.name, attributes = def.attributes ?? {}, useSeederConfig = def.traits?.useSeeder, seederDefault = useSeederConfig && typeof useSeederConfig === "object" ? useSeederConfig : void 0, count = options.count ?? seederDefault?.count ?? 10, globalOverrides = options.with ? snakeCaseKeys(options.with) : void 0, perRow = options.rows ?? seederDefault?.fixtures ?? [], fixtureCount = Math.max(count, perRow.length), fixtures = [];
|
|
24
|
-
for (let i = 0;i < fixtureCount; i++) {
|
|
25
|
-
const row = perRow[i];
|
|
26
|
-
if (!globalOverrides && !row)
|
|
27
|
-
continue;
|
|
28
|
-
fixtures[i] = { ...globalOverrides ?? {}, ...row ? snakeCaseKeys(row) : {} };
|
|
29
|
-
}
|
|
30
|
-
const table = def.table ?? `${snakeCase(name)}s`;
|
|
31
|
-
return { name, table, count: fixtureCount, fixtures, attributes, model: def };
|
|
32
|
-
}
|
|
33
|
-
export async function generate(modelInput, options = {}) {
|
|
34
|
-
const payload = buildSeederPayload(modelInput, options);
|
|
35
|
-
try {
|
|
36
|
-
return await seedModelDirect({
|
|
37
|
-
...payload,
|
|
38
|
-
filePath: ""
|
|
39
|
-
}, {
|
|
40
|
-
fresh: options.fresh,
|
|
41
|
-
verbose: options.verbose ?? !1
|
|
42
|
-
});
|
|
43
|
-
} catch (err) {
|
|
44
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
45
|
-
log.error(`[factory] generate(${payload.name}) failed: ${message}`);
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export const factory = {
|
|
50
|
-
generate
|
|
51
|
-
};
|
package/dist/seed-scaffold.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Remove a single `useSeeder` / `seedable` object-property from model
|
|
3
|
-
* source text (stacksjs/stacks#1929). Brace-aware (balances nested
|
|
4
|
-
* `{}` and skips string literals) and conservative: only strips the
|
|
5
|
-
* documented value shapes (`true`, `false`, or a `{ … }` object). For
|
|
6
|
-
* anything else (an identifier, a function call, a spread) it returns
|
|
7
|
-
* `changed: false` so the caller can flag it for manual cleanup
|
|
8
|
-
* instead of risking a mangled file.
|
|
9
|
-
*
|
|
10
|
-
* Exported for unit tests.
|
|
11
|
-
*/
|
|
12
|
-
export declare function stripUseSeederTrait(source: string): { source: string, changed: boolean, skipped: boolean };
|
|
13
|
-
/**
|
|
14
|
-
* Walk the configured models directory, find every model whose
|
|
15
|
-
* `traits.useSeeder` is truthy, and write a class-seeder file for it.
|
|
16
|
-
* Returns a structured report so the CLI command can render a summary
|
|
17
|
-
* without re-parsing log lines.
|
|
18
|
-
*/
|
|
19
|
-
export declare function scaffoldClassSeedersFromModels(options?: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
20
|
-
/** Pure renderer — exported for unit tests. */
|
|
21
|
-
export declare function renderSeederFile(modelName: string, modelImportPath: string, count: number): string;
|
|
22
|
-
export declare interface ScaffoldOptions {
|
|
23
|
-
modelsDir?: string
|
|
24
|
-
seedersDir?: string
|
|
25
|
-
force?: boolean
|
|
26
|
-
dryRun?: boolean
|
|
27
|
-
}
|
|
28
|
-
export declare interface ScaffoldResult {
|
|
29
|
-
generated: Array<{ model: string, file: string }>
|
|
30
|
-
skipped: Array<{ model: string, file: string, reason: 'already-exists' | 'no-useseeder' }>
|
|
31
|
-
errors: Array<{ model: string, error: string }>
|
|
32
|
-
strippedTrait: Array<{ model: string, file: string }>
|
|
33
|
-
traitStripSkipped: Array<{ model: string, file: string }>
|
|
34
|
-
}
|
package/dist/seed-scaffold.js
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { log } from "@stacksjs/logging";
|
|
2
|
-
import { path } from "@stacksjs/path";
|
|
3
|
-
import { fs } from "@stacksjs/storage";
|
|
4
|
-
export function stripUseSeederTrait(source) {
|
|
5
|
-
let out = source, changed = !1, skipped = !1;
|
|
6
|
-
for (const name of ["useSeeder", "seedable"]) {
|
|
7
|
-
const m = new RegExp(`\\b${name}\\s*:`).exec(out);
|
|
8
|
-
if (!m)
|
|
9
|
-
continue;
|
|
10
|
-
const keyStart = m.index;
|
|
11
|
-
let i = keyStart + m[0].length;
|
|
12
|
-
while (i < out.length && /\s/.test(out[i]))
|
|
13
|
-
i++;
|
|
14
|
-
if (out[i] === "{") {
|
|
15
|
-
let depth = 0, inStr = null;
|
|
16
|
-
for (;i < out.length; i++) {
|
|
17
|
-
const ch = out[i];
|
|
18
|
-
if (inStr) {
|
|
19
|
-
if (ch === "\\") {
|
|
20
|
-
i++;
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
if (ch === inStr)
|
|
24
|
-
inStr = null;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
28
|
-
inStr = ch;
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
if (ch === "{")
|
|
32
|
-
depth++;
|
|
33
|
-
else if (ch === "}") {
|
|
34
|
-
depth--;
|
|
35
|
-
if (depth === 0) {
|
|
36
|
-
i++;
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
} else if (out.startsWith("true", i) || out.startsWith("false", i))
|
|
42
|
-
i += out.startsWith("true", i) ? 4 : 5;
|
|
43
|
-
else {
|
|
44
|
-
skipped = !0;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
let end = i;
|
|
48
|
-
while (end < out.length && (out[end] === " " || out[end] === "\t"))
|
|
49
|
-
end++;
|
|
50
|
-
if (out[end] === ",")
|
|
51
|
-
end++;
|
|
52
|
-
while (end < out.length && (out[end] === " " || out[end] === "\t"))
|
|
53
|
-
end++;
|
|
54
|
-
if (out[end] === "/" && out[end + 1] === "/")
|
|
55
|
-
while (end < out.length && out[end] !== `
|
|
56
|
-
`)
|
|
57
|
-
end++;
|
|
58
|
-
let start = keyStart;
|
|
59
|
-
while (start > 0 && (out[start - 1] === " " || out[start - 1] === "\t"))
|
|
60
|
-
start--;
|
|
61
|
-
if (start > 0 && out[start - 1] === `
|
|
62
|
-
` && out[end] === `
|
|
63
|
-
`)
|
|
64
|
-
end++;
|
|
65
|
-
out = out.slice(0, start) + out.slice(end);
|
|
66
|
-
changed = !0;
|
|
67
|
-
}
|
|
68
|
-
return { source: out, changed, skipped: skipped && !changed };
|
|
69
|
-
}
|
|
70
|
-
const SEEDER_TEMPLATE = (modelName, modelImportPath, count) => `import { factory, Seeder } from '@stacksjs/database'
|
|
71
|
-
import ${modelName} from '${modelImportPath}'
|
|
72
|
-
|
|
73
|
-
export default class ${modelName}Seeder extends Seeder {
|
|
74
|
-
async run(): Promise<void> {
|
|
75
|
-
await factory.generate(${modelName}, { count: ${count} })
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
`;
|
|
79
|
-
function relativeModelImport(seedersDir, modelFilePath) {
|
|
80
|
-
const noExt = path.relative(seedersDir, modelFilePath).replace(/\\/g, "/").replace(/\.ts$/, "");
|
|
81
|
-
return noExt.startsWith(".") ? noExt : `./${noExt}`;
|
|
82
|
-
}
|
|
83
|
-
export async function scaffoldClassSeedersFromModels(options = {}) {
|
|
84
|
-
const modelsDir = options.modelsDir ?? path.userModelsPath(), seedersDir = options.seedersDir ?? path.projectPath("database/seeders"), result = { generated: [], skipped: [], errors: [], strippedTrait: [], traitStripSkipped: [] };
|
|
85
|
-
if (!fs.existsSync(modelsDir)) {
|
|
86
|
-
log.warn(`[seed:scaffold] No models directory at ${modelsDir}`);
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
if (!options.dryRun && !fs.existsSync(seedersDir))
|
|
90
|
-
fs.mkdirSync(seedersDir, { recursive: !0 });
|
|
91
|
-
const entries = fs.readdirSync(modelsDir, { withFileTypes: !0 });
|
|
92
|
-
for (const entry of entries) {
|
|
93
|
-
if (!entry.isFile() || !entry.name.endsWith(".ts"))
|
|
94
|
-
continue;
|
|
95
|
-
if (entry.name.startsWith("_") || entry.name.startsWith("index"))
|
|
96
|
-
continue;
|
|
97
|
-
const modelFilePath = path.join(modelsDir, entry.name);
|
|
98
|
-
let modelDef;
|
|
99
|
-
try {
|
|
100
|
-
const module = await import(modelFilePath);
|
|
101
|
-
modelDef = module.default || module;
|
|
102
|
-
} catch (err) {
|
|
103
|
-
result.errors.push({ model: entry.name, error: err.message });
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (!modelDef || !modelDef.name) {
|
|
107
|
-
result.errors.push({ model: entry.name, error: "missing default export with `name` field" });
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const useSeeder = modelDef.traits?.useSeeder ?? modelDef.traits?.seedable;
|
|
111
|
-
if (!useSeeder) {
|
|
112
|
-
result.skipped.push({ model: modelDef.name, file: "", reason: "no-useseeder" });
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const count = typeof useSeeder === "object" && "count" in useSeeder ? useSeeder.count : 10, seederFileName = `${modelDef.name}Seeder.ts`, seederFilePath = path.join(seedersDir, seederFileName);
|
|
116
|
-
if (fs.existsSync(seederFilePath) && !options.force)
|
|
117
|
-
result.skipped.push({ model: modelDef.name, file: seederFilePath, reason: "already-exists" });
|
|
118
|
-
else {
|
|
119
|
-
const importPath = relativeModelImport(seedersDir, modelFilePath), content = SEEDER_TEMPLATE(modelDef.name, importPath, count);
|
|
120
|
-
if (options.dryRun)
|
|
121
|
-
log.info(`[seed:scaffold] would write ${seederFilePath}`);
|
|
122
|
-
else
|
|
123
|
-
fs.writeFileSync(seederFilePath, content, "utf-8");
|
|
124
|
-
result.generated.push({ model: modelDef.name, file: seederFilePath });
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
const modelSource = fs.readFileSync(modelFilePath, "utf-8"), { source: stripped, changed, skipped } = stripUseSeederTrait(modelSource);
|
|
128
|
-
if (changed) {
|
|
129
|
-
if (options.dryRun)
|
|
130
|
-
log.info(`[seed:scaffold] would strip useSeeder trait from ${modelFilePath}`);
|
|
131
|
-
else
|
|
132
|
-
fs.writeFileSync(modelFilePath, stripped, "utf-8");
|
|
133
|
-
result.strippedTrait.push({ model: modelDef.name, file: modelFilePath });
|
|
134
|
-
} else if (skipped)
|
|
135
|
-
result.traitStripSkipped.push({ model: modelDef.name, file: modelFilePath });
|
|
136
|
-
} catch (err) {
|
|
137
|
-
result.errors.push({ model: modelDef.name, error: `trait strip failed: ${err.message}` });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
export function renderSeederFile(modelName, modelImportPath, count) {
|
|
143
|
-
return SEEDER_TEMPLATE(modelName, modelImportPath, count);
|
|
144
|
-
}
|