@warlock.js/core 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/esm/ai/src/agent/agent.mjs +126 -7
- package/esm/ai/src/agent/agent.mjs.map +1 -1
- package/esm/ai/src/agent/signature.mjs +57 -0
- package/esm/ai/src/agent/signature.mjs.map +1 -0
- package/esm/ai/src/agent/snapshot.mjs +101 -0
- package/esm/ai/src/agent/snapshot.mjs.map +1 -0
- package/esm/ai/src/ai.mjs +37 -1
- package/esm/ai/src/ai.mjs.map +1 -1
- package/esm/ai/src/errors/agent-drift-error.mjs +31 -0
- package/esm/ai/src/errors/agent-drift-error.mjs.map +1 -0
- package/esm/ai/src/errors/index.mjs +2 -0
- package/esm/ai/src/errors/planner-drift-error.mjs +33 -0
- package/esm/ai/src/errors/planner-drift-error.mjs.map +1 -0
- package/esm/ai/src/image/image-cost.mjs +55 -0
- package/esm/ai/src/image/image-cost.mjs.map +1 -0
- package/esm/ai/src/image/image.mjs +112 -0
- package/esm/ai/src/image/image.mjs.map +1 -0
- package/esm/ai/src/image/index.mjs +4 -0
- package/esm/ai/src/index.mjs +20 -0
- package/esm/ai/src/planner/planner-run.mjs +161 -6
- package/esm/ai/src/planner/planner-run.mjs.map +1 -1
- package/esm/ai/src/planner/planner.mjs +25 -1
- package/esm/ai/src/planner/planner.mjs.map +1 -1
- package/esm/ai/src/planner/snapshot.mjs +95 -0
- package/esm/ai/src/planner/snapshot.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/bm25.mjs +51 -0
- package/esm/ai/src/rag/hybrid/bm25.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/hybrid-rank.mjs +29 -0
- package/esm/ai/src/rag/hybrid/hybrid-rank.mjs.map +1 -0
- package/esm/ai/src/rag/hybrid/rrf.mjs +30 -0
- package/esm/ai/src/rag/hybrid/rrf.mjs.map +1 -0
- package/esm/ai/src/rag/index.mjs +11 -0
- package/esm/ai/src/rag/loaders/errors.mjs +25 -0
- package/esm/ai/src/rag/loaders/errors.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/index.mjs +7 -0
- package/esm/ai/src/rag/loaders/load-html.mjs +138 -0
- package/esm/ai/src/rag/loaders/load-html.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-pdf.mjs +150 -0
- package/esm/ai/src/rag/loaders/load-pdf.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-text.mjs +60 -0
- package/esm/ai/src/rag/loaders/load-text.mjs.map +1 -0
- package/esm/ai/src/rag/loaders/load-web.mjs +89 -0
- package/esm/ai/src/rag/loaders/load-web.mjs.map +1 -0
- package/esm/ai/src/rag/store/pg-vector-store.mjs +328 -0
- package/esm/ai/src/rag/store/pg-vector-store.mjs.map +1 -0
- package/esm/ai/src/rag/transforms/multi-query.mjs +41 -0
- package/esm/ai/src/rag/transforms/multi-query.mjs.map +1 -0
- package/esm/ai/src/speech/index.mjs +3 -0
- package/esm/ai/src/speech/speech.mjs +122 -0
- package/esm/ai/src/speech/speech.mjs.map +1 -0
- package/esm/ai/src/supervisor/entries.mjs +2 -2
- package/esm/ai/src/supervisor/entries.mjs.map +1 -1
- package/esm/ai/src/transcribe/audio-input.mjs +84 -0
- package/esm/ai/src/transcribe/audio-input.mjs.map +1 -0
- package/esm/ai/src/transcribe/index.mjs +4 -0
- package/esm/ai/src/transcribe/transcribe.mjs +127 -0
- package/esm/ai/src/transcribe/transcribe.mjs.map +1 -0
- package/esm/ai-openai/src/image.mjs +5 -0
- package/esm/ai-openai/src/index.mjs +3 -0
- package/esm/ai-openai/src/sdk.mjs +3 -0
- package/esm/ai-openai/src/speech.mjs +5 -0
- package/esm/ai-openai/src/transcription.mjs +6 -0
- package/esm/ai-openai/src/utils/index.mjs +1 -0
- package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
- package/esm/bootstrap.d.mts.map +1 -1
- package/esm/bootstrap.mjs +3 -1
- package/esm/bootstrap.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/config.check.mjs +42 -0
- package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs +28 -0
- package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs +35 -0
- package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/index.mjs +25 -0
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +90 -0
- package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +69 -0
- package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs +30 -0
- package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs +28 -0
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -0
- package/esm/cli/commands/doctor/format-report.mjs +44 -0
- package/esm/cli/commands/doctor/format-report.mjs.map +1 -0
- package/esm/cli/commands/doctor/run-checks.mjs +50 -0
- package/esm/cli/commands/doctor/run-checks.mjs.map +1 -0
- package/esm/cli/commands/doctor.command.mjs +29 -0
- package/esm/cli/commands/doctor.command.mjs.map +1 -0
- package/esm/cli/commands/generate/templates/stubs.mjs +6 -8
- package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
- package/esm/cli/commands/routes/format-routes-table.mjs +129 -0
- package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -0
- package/esm/cli/commands/routes/route-row.mjs +92 -0
- package/esm/cli/commands/routes/route-row.mjs.map +1 -0
- package/esm/cli/commands/routes/routes-command.action.mjs +43 -0
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -0
- package/esm/cli/commands/routes.command.mjs +51 -0
- package/esm/cli/commands/routes.command.mjs.map +1 -0
- package/esm/cli/commands/seed.command.mjs +5 -0
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/framework-cli-commands.mjs +4 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/ai-connector.mjs +2 -2
- package/esm/connectors/ai-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +9 -7
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/database/index.d.mts +2 -1
- package/esm/database/index.mjs +1 -0
- package/esm/database/seed-command-action.mjs +44 -4
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/database/seeds/seed-records-table-migration.mjs +36 -0
- package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -0
- package/esm/database/seeds/seeder.d.mts +22 -5
- package/esm/database/seeds/seeder.d.mts.map +1 -1
- package/esm/database/seeds/seeder.errors.d.mts +22 -0
- package/esm/database/seeds/seeder.errors.d.mts.map +1 -0
- package/esm/database/seeds/seeder.errors.mjs +29 -0
- package/esm/database/seeds/seeder.errors.mjs.map +1 -0
- package/esm/database/seeds/seeder.mjs.map +1 -1
- package/esm/database/seeds/seeders.manager.mjs +90 -11
- package/esm/database/seeds/seeders.manager.mjs.map +1 -1
- package/esm/database/seeds/types.d.mts +117 -1
- package/esm/database/seeds/types.d.mts.map +1 -1
- package/esm/database/seeds/utils.mjs +6 -1
- package/esm/database/seeds/utils.mjs.map +1 -1
- package/esm/dev-server/module-loader.d.mts +12 -0
- package/esm/dev-server/module-loader.d.mts.map +1 -1
- package/esm/dev-server/module-loader.mjs +30 -2
- package/esm/dev-server/module-loader.mjs.map +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/health.d.mts +14 -0
- package/esm/http/health.d.mts.map +1 -1
- package/esm/http/health.mjs +16 -0
- package/esm/http/health.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/response.mjs +5 -5
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.mjs +4 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +4 -3
- package/esm/index.mjs +2 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +28 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs +39 -0
- package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
- package/esm/repositories/contracts/index.d.mts +1 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts +87 -1
- package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/repositories/index.d.mts +1 -1
- package/esm/repositories/repository.manager.d.mts +99 -1
- package/esm/repositories/repository.manager.d.mts.map +1 -1
- package/esm/repositories/repository.manager.mjs +128 -0
- package/esm/repositories/repository.manager.mjs.map +1 -1
- package/esm/router/router.d.mts +9 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +21 -1
- package/esm/router/router.mjs.map +1 -1
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +427 -45
- package/llms.txt +4 -2
- package/package.json +9 -9
- package/skills/README.md +65 -0
- package/skills/health-checks/SKILL.md +13 -0
- package/skills/use-repository/SKILL.md +38 -1
- package/skills/warlock-doctor/SKILL.md +135 -0
- package/skills/warlock-routes/SKILL.md +91 -0
- package/skills/write-seeder/SKILL.md +143 -45
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: write-seeder
|
|
3
|
-
description: 'Author a seed file under `src/app/<module>/seeds/<name>.ts` using the `seeder()` factory — `name`, `dependsOn`, `once`, `order`, `batchSize`, `run()`. Auto-discovered by `warlock seed`; tracked in a `seeds` table. Triggers: `seeder`, `Seeder`, `SeedResult`, `SeedersManager`, `warlock seed`, `--fresh`, `--list`, `--path`; "seed default roles", "one-time data migration", "auto-discovered seeds", "order seeds by dependency"; typical import `import { seeder } from "@warlock.js/core"`. Skip: module folder layout — `@warlock.js/core/create-module/SKILL.md`; repository CRUD — `@warlock.js/core/use-repository/SKILL.md`; CLI flags — `@warlock.js/core/write-cli-command/SKILL.md`; competing patterns: hand-rolled `node scripts/seed.js`, `typeorm-seeding`.'
|
|
3
|
+
description: 'Author a seed file under `src/app/<module>/seeds/<name>.ts` using the `seeder()` factory — `name`, `dependsOn`, `once`, `order`, `batchSize`, `run({ track, now, batchSize })`. Auto-discovered by `warlock seed`; tracked in a `seeds` table; per-record refs in `seed_records` so `warlock seed --drop` can undo a seed. Triggers: `seeder`, `Seeder`, `SeedResult`, `SeedContext`, `SeedClock`, `track`, `now`, `batchSize`, `SeedersManager`, `warlock seed`, `--fresh`, `--drop`, `--list`, `--path`; "seed default roles", "undo a seed", "one-time data migration", "auto-discovered seeds", "order seeds by dependency", "deterministic seed timestamps", "inject a seed clock"; typical import `import { seeder } from "@warlock.js/core"`. Skip: module folder layout — `@warlock.js/core/create-module/SKILL.md`; repository CRUD — `@warlock.js/core/use-repository/SKILL.md`; CLI flags — `@warlock.js/core/write-cli-command/SKILL.md`; competing patterns: hand-rolled `node scripts/seed.js`, `typeorm-seeding`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Warlock — write a seeder
|
|
@@ -18,22 +18,21 @@ export default seeder({
|
|
|
18
18
|
description: "Insert admin / member / viewer roles",
|
|
19
19
|
once: true,
|
|
20
20
|
order: 1,
|
|
21
|
-
async run() {
|
|
22
|
-
let recordsCreated = 0;
|
|
23
|
-
|
|
21
|
+
async run({ track }) {
|
|
24
22
|
for (const slug of ["admin", "member", "viewer"]) {
|
|
25
23
|
const existing = await Role.first({ slug });
|
|
26
24
|
if (existing) continue;
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// track() records the created row in `seed_records` (same transaction)
|
|
27
|
+
// AND returns its argument so you can chain inline.
|
|
28
|
+
track(await Role.create({ slug, name: slug.toUpperCase() }));
|
|
30
29
|
}
|
|
31
|
-
|
|
32
|
-
return { recordsCreated };
|
|
33
30
|
},
|
|
34
31
|
});
|
|
35
32
|
```
|
|
36
33
|
|
|
34
|
+
`recordsCreated` is **auto-derived from the track count** — you no longer have to count by hand or return a `SeedResult`.
|
|
35
|
+
|
|
37
36
|
Run them:
|
|
38
37
|
|
|
39
38
|
```bash
|
|
@@ -41,10 +40,14 @@ yarn warlock seed # discover + run all
|
|
|
41
40
|
yarn warlock seed --list # show registry, don't run
|
|
42
41
|
yarn warlock seed --path=src/app/roles/seeds/default-roles.seed.ts # one file
|
|
43
42
|
yarn warlock seed --fresh # truncate every table first, then run all
|
|
43
|
+
yarn warlock seed --drop # undo every tracked record, reset the log
|
|
44
|
+
yarn warlock seed --drop=default-roles # undo just one seeder's records
|
|
44
45
|
```
|
|
45
46
|
|
|
46
47
|
`--fresh` truncates **every** table in the DB (`datasource.driver.truncateTable(table, { cascade: true })`), including the `seeds` tracking table. After `--fresh`, `once: true` seeds will run again.
|
|
47
48
|
|
|
49
|
+
`--drop` is the surgical alternative: it deletes only the rows a seeder **tracked**, in reverse run-order, then resets the matching `seeds`-log rows so `once: true` seeds re-run. See [Undo — `seed_records` + `--drop`](#undo--seed_records----drop).
|
|
50
|
+
|
|
48
51
|
## The `Seeder` type
|
|
49
52
|
|
|
50
53
|
```ts
|
|
@@ -56,15 +59,69 @@ type Seeder = {
|
|
|
56
59
|
once?: boolean; // skip if a row exists in the `seeds` table
|
|
57
60
|
order?: number; // sort key — lower runs first
|
|
58
61
|
batchSize?: number; // documented; not enforced by the manager
|
|
59
|
-
run(): Promise<SeedResult | void>;
|
|
62
|
+
run(ctx: SeedContext): Promise<SeedResult | void>; // your work
|
|
60
63
|
};
|
|
61
64
|
|
|
65
|
+
type SeedContext = {
|
|
66
|
+
track: Track; // register created records for --drop
|
|
67
|
+
now: SeedClock; // injectable clock — () => Date, default () => new Date()
|
|
68
|
+
batchSize: number; // from the seeder's batchSize (0 when unset)
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type SeedClock = () => Date;
|
|
72
|
+
|
|
62
73
|
type SeedResult = {
|
|
63
|
-
recordsCreated: number; //
|
|
74
|
+
recordsCreated: number; // OPTIONAL fallback when nothing was tracked
|
|
64
75
|
};
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
|
|
78
|
+
`run` now receives a `{ track, now, batchSize }` context. Declaring the parameter is optional — an existing **zero-arg `run()` keeps working unchanged** (it just ignores the context). Returning a `SeedResult` is also optional: the manager prefers the track count and only falls back to `result.recordsCreated` when you tracked nothing. A seed that neither tracks nor returns still counts as run (the `seeds` row is inserted with `recordsCreated: 0`).
|
|
79
|
+
|
|
80
|
+
## `now` — the injectable clock
|
|
81
|
+
|
|
82
|
+
`now` is a `SeedClock` (`() => Date`) the seed should read **instead of** an ambient `new Date()` / `dayjs()`. It defaults to `() => new Date()`, and the `SeedersManager` reads the **same clock** for its own seeds-log timestamps (`createdAt` / `firstRunAt` / `lastRunAt`) — so seed data and the tracking row never disagree.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
async run({ track, now }) {
|
|
86
|
+
// every timestamp in the run reads from one clock
|
|
87
|
+
track(await User.create({ name: "Admin", created_at: now() }));
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Inject a fixed clock for deterministic tests or historical back-fills — pass `clock` to `SeedersManager` (`new SeedersManager({ clock: () => new Date("2020-01-01") })`) or, programmatically, to `seedCommandAction(options, { clock })`. Pinning the clock makes every timestamp in the run — seed data *and* the `seeds` log — resolve to that one value.
|
|
92
|
+
|
|
93
|
+
## `batchSize` — bulk-insert hint
|
|
94
|
+
|
|
95
|
+
`batchSize` surfaces the seeder's own `batchSize` field into the run context (`0` when unset), so a seed can forward it straight to `Model.createMany(rows, { batchSize })` without re-reading its config:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
async run({ track, batchSize }) {
|
|
99
|
+
const rows = buildManyRows();
|
|
100
|
+
track(await User.createMany(rows, { batchSize })); // chunked insert
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The manager doesn't act on `batchSize` itself — it only hands the value through; the chunking happens inside `createMany`.
|
|
105
|
+
|
|
106
|
+
## `track` — register created records
|
|
107
|
+
|
|
108
|
+
`track` is how a seed tells the framework which rows it created, so `--drop` can undo them. It has three forms, and **every form returns its first argument** so you can wrap a `create()` call inline:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
track(model) // single model — reads model.getTableName() + model.id
|
|
112
|
+
track([modelA, modelB]) // bulk — array of models
|
|
113
|
+
track("legacy_table", id) // raw escape hatch — table name + id, no model
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
async run({ track }) {
|
|
118
|
+
const role = track(await Role.create({ slug: "admin" })); // chain inline
|
|
119
|
+
const users = track(await User.createMany([...])); // bulk
|
|
120
|
+
track("audit_log", auditId); // raw
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Tracked refs are written to the `seed_records` table **inside the same transaction** the seed runs in — if the seed throws, the refs roll back with the data. Only the **last run's** refs are kept: re-running a seed drops its prior refs first, so undo stays bounded to what currently exists.
|
|
68
125
|
|
|
69
126
|
## Auto-discovery
|
|
70
127
|
|
|
@@ -108,6 +165,34 @@ Skip-on-`once`:
|
|
|
108
165
|
|
|
109
166
|
Use `once: true` for irreversible "starter" data (the framework's default `roles`, fixed enum tables) and leave it off for repeatable seeds (dev fixtures you re-run after rolling the DB).
|
|
110
167
|
|
|
168
|
+
## Undo — `seed_records` + `--drop`
|
|
169
|
+
|
|
170
|
+
Every record you `track()` is written to a `seed_records` table (created on first run via `SeedRecordsTableMigration`):
|
|
171
|
+
|
|
172
|
+
| Column | Meaning |
|
|
173
|
+
| ---------- | ---------------------------------------- |
|
|
174
|
+
| `seeder` | Owning seeder's `name` |
|
|
175
|
+
| `table` | Table the tracked record lives in |
|
|
176
|
+
| `recordId` | Primary key of the tracked record |
|
|
177
|
+
| `runAt` | Wall time the reference was recorded |
|
|
178
|
+
|
|
179
|
+
`warlock seed --drop` reads those refs and undoes the seed:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
yarn warlock seed --drop # undo every tracked record across all seeders
|
|
183
|
+
yarn warlock seed --drop=default-roles # undo just one seeder's records
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
What it does, inside a single transaction:
|
|
187
|
+
|
|
188
|
+
1. Deletes the tracked records in **reverse run-order**, and within each seed in **reverse insertion-order** (highest `seed_records.id` first — so children created after their parents are removed first).
|
|
189
|
+
2. Clears the `seed_records` refs it acted on.
|
|
190
|
+
3. Resets the matching `seeds`-log rows, so a `once: true` seed **re-runs** on the next `warlock seed`.
|
|
191
|
+
|
|
192
|
+
Only **tracked** rows are deleted — records you created without `track()` (or rows added by hand) are untouched. A seed whose `run()` never calls `track()` can't be undone with `--drop`; it has nothing to delete and its log row stays.
|
|
193
|
+
|
|
194
|
+
`--drop` vs `--fresh`: `--fresh` is the blunt reset (truncates *every* table); `--drop` is surgical (only what a seed tracked). Reach for `--drop` when you want to re-run one seeder without nuking the whole DB.
|
|
195
|
+
|
|
111
196
|
## Ordering
|
|
112
197
|
|
|
113
198
|
```ts
|
|
@@ -126,19 +211,34 @@ seeder({
|
|
|
126
211
|
|
|
127
212
|
The manager sorts by `order` ascending — lower runs first. Seeds without an `order` get sorted to the end (`Number.MAX_SAFE_INTEGER`).
|
|
128
213
|
|
|
129
|
-
|
|
214
|
+
### `dependsOn` — topological order
|
|
215
|
+
|
|
216
|
+
`dependsOn: ["user-roles"]` is **resolved** by the manager: it topologically sorts seeders so every dependency runs before its dependents, *layered over* the numeric `order` tie-break. Among seeders whose dependencies are already satisfied, the lowest `order` (then registration order) runs first.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
seeder({ name: "roles", order: 20, async run() { /* ... */ } });
|
|
220
|
+
seeder({ name: "admin-user", dependsOn: ["roles"], order: 10, async run() { /* ... */ } });
|
|
221
|
+
// → roles runs FIRST despite its higher order, because admin-user depends on it.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Two ways it fails loudly:
|
|
225
|
+
|
|
226
|
+
- **Unknown dependency** — `dependsOn` names a seeder that isn't registered (or is `enabled: false`, since disabled seeds are filtered out *before* resolution) → `UnknownSeederDependencyError`.
|
|
227
|
+
- **Cycle** — `a → b → a` → `SeederDependencyCycleError` (the message includes the cycle path).
|
|
228
|
+
|
|
229
|
+
Use `dependsOn` to express *hard* ordering constraints and `order` for soft sequencing within a dependency layer.
|
|
130
230
|
|
|
131
231
|
## Transactions
|
|
132
232
|
|
|
133
|
-
By default, each seed runs inside a transaction:
|
|
233
|
+
By default, each seed runs inside a transaction. The manager wraps `run({ track })` **and** the write of its tracked `seed_records` refs in one transaction:
|
|
134
234
|
|
|
135
235
|
```ts
|
|
136
236
|
const result = withTransaction
|
|
137
|
-
? await transaction(
|
|
138
|
-
: await
|
|
237
|
+
? await transaction(runSeeder) // run() + seed_records writes, atomically
|
|
238
|
+
: await runSeeder();
|
|
139
239
|
```
|
|
140
240
|
|
|
141
|
-
If your `run()` throws, the transaction rolls back. The CLI takes `--transaction` to flip this — pass `--transaction=false` for large seeds where you want each insert committed individually (avoid the rollback-on-failure cost; accept partial state on error).
|
|
241
|
+
If your `run()` throws, the transaction rolls back — including the `seed_records` refs, so a failed seed leaves no dangling track rows. The CLI takes `--transaction` to flip this — pass `--transaction=false` for large seeds where you want each insert committed individually (avoid the rollback-on-failure cost; accept partial state on error).
|
|
142
242
|
|
|
143
243
|
Within a single seed, multiple model `create()` / `insert()` calls share the same transaction — partial-on-error doesn't apply unless you opt out.
|
|
144
244
|
|
|
@@ -154,7 +254,7 @@ export default seeder({
|
|
|
154
254
|
name: "default-currencies",
|
|
155
255
|
description: "USD / EUR / SAR / EGP currencies",
|
|
156
256
|
order: 5,
|
|
157
|
-
async run() {
|
|
257
|
+
async run({ track }) {
|
|
158
258
|
const seeds = [
|
|
159
259
|
{ code: "USD", name: "US Dollar", decimals: 2 },
|
|
160
260
|
{ code: "EUR", name: "Euro", decimals: 2 },
|
|
@@ -162,15 +262,11 @@ export default seeder({
|
|
|
162
262
|
{ code: "EGP", name: "Egyptian Pound", decimals: 2 },
|
|
163
263
|
];
|
|
164
264
|
|
|
165
|
-
let recordsCreated = 0;
|
|
166
265
|
for (const data of seeds) {
|
|
167
266
|
const existing = await Currency.first({ code: data.code });
|
|
168
267
|
if (existing) continue;
|
|
169
|
-
await Currency.create(data);
|
|
170
|
-
recordsCreated++;
|
|
268
|
+
track(await Currency.create(data)); // count auto-derived from track()
|
|
171
269
|
}
|
|
172
|
-
|
|
173
|
-
return { recordsCreated };
|
|
174
270
|
},
|
|
175
271
|
});
|
|
176
272
|
```
|
|
@@ -188,23 +284,23 @@ export default seeder({
|
|
|
188
284
|
name: "default-admin",
|
|
189
285
|
description: "Insert the seed admin user",
|
|
190
286
|
once: true, // never re-run
|
|
191
|
-
|
|
192
|
-
async run() {
|
|
287
|
+
dependsOn: ["default-roles"], // roles must exist first
|
|
288
|
+
async run({ track }) {
|
|
193
289
|
const adminRole = await Role.first({ slug: "admin" });
|
|
194
|
-
if (!adminRole) throw new Error("admin role missing — run
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
290
|
+
if (!adminRole) throw new Error("admin role missing — run the roles seed first");
|
|
291
|
+
|
|
292
|
+
track(
|
|
293
|
+
await User.create({
|
|
294
|
+
email: "admin@example.com",
|
|
295
|
+
password: "change-me-immediately",
|
|
296
|
+
role_id: adminRole.id,
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
203
299
|
},
|
|
204
300
|
});
|
|
205
301
|
```
|
|
206
302
|
|
|
207
|
-
Combining `once: true` + `
|
|
303
|
+
Combining `once: true` + `dependsOn` is the canonical "starter data" pattern.
|
|
208
304
|
|
|
209
305
|
### Bulk dev fixtures (no `once`, large batch)
|
|
210
306
|
|
|
@@ -217,16 +313,15 @@ export default seeder({
|
|
|
217
313
|
description: "100 sample products for dev",
|
|
218
314
|
enabled: process.env.NODE_ENV === "development",
|
|
219
315
|
order: 100,
|
|
220
|
-
async run() {
|
|
221
|
-
let recordsCreated = 0;
|
|
316
|
+
async run({ track }) {
|
|
222
317
|
for (let i = 0; i < 100; i++) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
318
|
+
track(
|
|
319
|
+
await Product.create({
|
|
320
|
+
name: `Sample ${i}`,
|
|
321
|
+
price: Math.random() * 100,
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
228
324
|
}
|
|
229
|
-
return { recordsCreated };
|
|
230
325
|
},
|
|
231
326
|
});
|
|
232
327
|
```
|
|
@@ -256,17 +351,20 @@ warlock seed # run all auto-discovered seeds
|
|
|
256
351
|
warlock seed --list # show registry (name, order, enabled), don't run
|
|
257
352
|
warlock seed --path=<file> # run one file by absolute or relative path
|
|
258
353
|
warlock seed --fresh # truncate every table first, then run all
|
|
354
|
+
warlock seed --drop # undo every tracked record, reset the seeds-log
|
|
355
|
+
warlock seed --drop=<name> # undo just one seeder's tracked records
|
|
259
356
|
warlock seed --transaction # (default true) — pass --transaction=false to skip wrapping
|
|
260
357
|
```
|
|
261
358
|
|
|
262
|
-
`--fresh` is **destructive** — it truncates all tables, including the `seeds` tracking table. Treat it as "reset the dev DB," not "run pending seeds."
|
|
359
|
+
`--fresh` is **destructive** — it truncates all tables, including the `seeds` tracking table. Treat it as "reset the dev DB," not "run pending seeds." `--drop` is the surgical undo — only tracked rows, scoped to one seeder if you name it.
|
|
263
360
|
|
|
264
361
|
## Gotchas
|
|
265
362
|
|
|
266
363
|
- **The seed file must default-export.** A named export gets the discovery error at boot. Wrap with `export default seeder({...})`.
|
|
267
364
|
- **`once: true` only works if the seed run succeeds.** A throw rolls back the transaction including the `seeds` table insert — the next run will retry.
|
|
268
|
-
-
|
|
269
|
-
- **`
|
|
365
|
+
- **`--drop` only undoes what you `track()`.** A seed that never calls `track()` has nothing in `seed_records`, so `--drop` can't reverse it. Track every row you want to be able to undo.
|
|
366
|
+
- **`dependsOn` is resolved** (topological sort over `order`). An unknown dependency throws `UnknownSeederDependencyError`; a cycle throws `SeederDependencyCycleError`. A `dependsOn` pointing at a `enabled: false` seeder errors, because disabled seeds are filtered out before resolution.
|
|
367
|
+
- **`batchSize` is surfaced into the run context, not acted on by the manager.** Set `batchSize` on the seeder and the manager hands the value through as `run({ batchSize })` (defaulting to `0`); forward it to `Model.createMany(rows, { batchSize })` to get chunked inserts. The manager itself never chunks — it only relays the value.
|
|
270
368
|
- **`--fresh` truncates `cascade: true`.** Foreign-key chains delete with their parents. If you have data outside the seeded scope that you want to keep, do NOT run `--fresh`.
|
|
271
369
|
- **Seeds aren't migrations.** Schema changes go in `migrations/`. Seeds populate data into a schema that already exists. Mixing the two (creating a table in a seed) confuses the lifecycle.
|
|
272
370
|
- **`enabled: false` skips the seed but it's still in the registry.** `warlock seed --list` shows it as disabled. Re-enable by flipping the flag, not by deleting the file.
|