@warlock.js/cascade 5.30.0 → 5.31.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -5
  3. package/cjs/index.cjs +0 -1
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/contracts/database-restorer.contract.d.mts.map +1 -1
  6. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  7. package/esm/data-source/data-source-registry.mjs.map +1 -1
  8. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  9. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  10. package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
  11. package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
  12. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  13. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  14. package/esm/drivers/mongodb/mongodb-query-operations.d.mts.map +1 -1
  15. package/esm/drivers/mongodb/mongodb-query-operations.mjs.map +1 -1
  16. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  17. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  18. package/esm/drivers/postgres/naming.mjs.map +1 -1
  19. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  20. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  21. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  22. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  23. package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
  24. package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
  25. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  26. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  27. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  28. package/esm/drivers/postgres/postgres-query-parser.mjs +0 -1
  29. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  30. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  31. package/esm/drivers/postgres/postgres-ttl-purge.mjs.map +1 -1
  32. package/esm/drivers/postgres/types.d.mts.map +1 -1
  33. package/esm/errors/unsupported-query-operation.error.mjs.map +1 -1
  34. package/esm/expressions/column-expressions.d.mts.map +1 -1
  35. package/esm/expressions/column-expressions.mjs.map +1 -1
  36. package/esm/migration/foreign-key-builder.mjs.map +1 -1
  37. package/esm/migration/parse-created-at.mjs.map +1 -1
  38. package/esm/migration/sql-grammar.mjs.map +1 -1
  39. package/esm/model/generate-model-id.mjs.map +1 -1
  40. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  41. package/esm/model/methods/dirty-methods.mjs.map +1 -1
  42. package/esm/model/methods/hydration-methods.mjs.map +1 -1
  43. package/esm/model/methods/query-methods.mjs.map +1 -1
  44. package/esm/model/methods/scope-methods.mjs.map +1 -1
  45. package/esm/model/methods/static-event-methods.mjs.map +1 -1
  46. package/esm/model/methods/write-methods.mjs.map +1 -1
  47. package/esm/model/model.d.mts.map +1 -1
  48. package/esm/model/model.mjs.map +1 -1
  49. package/esm/model/register-model.d.mts.map +1 -1
  50. package/esm/model/register-model.mjs.map +1 -1
  51. package/esm/operations/migrations.d.mts.map +1 -1
  52. package/esm/operations/migrations.mjs.map +1 -1
  53. package/esm/query-builder/query-builder.d.mts.map +1 -1
  54. package/esm/query-builder/query-builder.mjs.map +1 -1
  55. package/esm/relations/key-conventions.mjs.map +1 -1
  56. package/esm/relations/pivot-operations.d.mts.map +1 -1
  57. package/esm/relations/pivot-operations.mjs.map +1 -1
  58. package/esm/relations/relation-loader.d.mts.map +1 -1
  59. package/esm/relations/relation-loader.mjs.map +1 -1
  60. package/esm/remover/database-remover.d.mts.map +1 -1
  61. package/esm/remover/database-remover.mjs.map +1 -1
  62. package/esm/sync/sync-context.d.mts.map +1 -1
  63. package/esm/sync/sync-context.mjs.map +1 -1
  64. package/esm/sync/sync-fanout.mjs.map +1 -1
  65. package/esm/sync/sync-manager.d.mts.map +1 -1
  66. package/esm/sync/sync-manager.mjs.map +1 -1
  67. package/esm/utils/database-writer.utils.d.mts.map +1 -1
  68. package/esm/utils/database-writer.utils.mjs.map +1 -1
  69. package/esm/utils/is-valid-date-value.mjs.map +1 -1
  70. package/esm/writer/database-writer.d.mts.map +1 -1
  71. package/esm/writer/database-writer.mjs.map +1 -1
  72. package/llms-full.txt +541 -434
  73. package/package.json +5 -5
  74. package/skills/aggregate-data/SKILL.md +28 -17
  75. package/skills/alter-migration/SKILL.md +325 -233
  76. package/skills/cascade-basics/SKILL.md +19 -19
  77. package/skills/configure-delete-strategy/SKILL.md +11 -11
  78. package/skills/define-model/SKILL.md +19 -17
  79. package/skills/define-relations/SKILL.md +13 -18
  80. package/skills/index.md +3 -0
  81. package/skills/manage-transactions/SKILL.md +18 -12
  82. package/skills/paginate-results/SKILL.md +6 -8
  83. package/skills/perform-atomic-ops/SKILL.md +26 -21
  84. package/skills/query-data/SKILL.md +27 -33
  85. package/skills/run-cascade-cli/SKILL.md +11 -10
  86. package/skills/search-by-vector/SKILL.md +8 -5
  87. package/skills/subscribe-to-model-events/SKILL.md +17 -17
  88. package/skills/track-changes/SKILL.md +9 -9
  89. package/skills/write-migration/SKILL.md +4 -4
@@ -17,15 +17,19 @@ Use `Migration.alter` to evolve an existing table — adding or dropping columns
17
17
  import { Migration, text } from "@warlock.js/cascade";
18
18
  import { User } from "../user.model";
19
19
 
20
- export default Migration.alter(User, {
21
- add: {
22
- phone: text().nullable(),
20
+ export default Migration.alter(
21
+ User,
22
+ {
23
+ add: {
24
+ phone: text().nullable(),
25
+ },
23
26
  },
24
- }, {
25
- down() {
26
- this.dropColumn("phone");
27
+ {
28
+ down() {
29
+ this.dropColumn("phone");
30
+ },
27
31
  },
28
- });
32
+ );
29
33
  ```
30
34
 
31
35
  ---
@@ -55,21 +59,25 @@ import { Migration, text, integer, bool, uuid } from "@warlock.js/cascade";
55
59
  import { Organization } from "app/organizations/models/organization";
56
60
  import { User } from "../user.model";
57
61
 
58
- export default Migration.alter(User, {
59
- add: {
60
- phone: text().nullable(),
61
- login_attempts: integer().notNullable().default(0),
62
- is_verified: bool().notNullable().default(false),
63
- organization_id: uuid().references(Organization.table).onDelete("cascade").notNullable(),
62
+ export default Migration.alter(
63
+ User,
64
+ {
65
+ add: {
66
+ phone: text().nullable(),
67
+ login_attempts: integer().notNullable().default(0),
68
+ is_verified: bool().notNullable().default(false),
69
+ organization_id: uuid().references(Organization.table).onDelete("cascade").notNullable(),
70
+ },
64
71
  },
65
- }, {
66
- down() {
67
- this.dropColumn("phone");
68
- this.dropColumn("login_attempts");
69
- this.dropColumn("is_verified");
70
- this.dropColumn("organization_id");
72
+ {
73
+ down() {
74
+ this.dropColumn("phone");
75
+ this.dropColumn("login_attempts");
76
+ this.dropColumn("is_verified");
77
+ this.dropColumn("organization_id");
78
+ },
71
79
  },
72
- });
80
+ );
73
81
  ```
74
82
 
75
83
  Column helpers (`text`, `integer`, `bool`, `uuid`, `double`, `json`, `timestamp`, `date`, …) are imported from `@warlock.js/cascade` and support the same modifier chain as in `Migration.create`.
@@ -77,31 +85,39 @@ Column helpers (`text`, `integer`, `bool`, `uuid`, `double`, `json`, `timestamp`
77
85
  ### `drop` — remove columns
78
86
 
79
87
  ```ts
80
- export default Migration.alter(User, {
81
- drop: ["legacy_token", "old_avatar_url"],
82
- }, {
83
- down() {
84
- // re-add with the original definition if rollback is meaningful
85
- this.text("legacy_token").nullable();
86
- this.text("old_avatar_url").nullable();
88
+ export default Migration.alter(
89
+ User,
90
+ {
91
+ drop: ["legacy_token", "old_avatar_url"],
87
92
  },
88
- });
93
+ {
94
+ down() {
95
+ // re-add with the original definition if rollback is meaningful
96
+ this.text("legacy_token").nullable();
97
+ this.text("old_avatar_url").nullable();
98
+ },
99
+ },
100
+ );
89
101
  ```
90
102
 
91
103
  ### `rename` — rename columns
92
104
 
93
105
  ```ts
94
- export default Migration.alter(Post, {
95
- rename: {
96
- body: "content",
97
- thumb_url: "thumbnail_url",
106
+ export default Migration.alter(
107
+ Post,
108
+ {
109
+ rename: {
110
+ body: "content",
111
+ thumb_url: "thumbnail_url",
112
+ },
98
113
  },
99
- }, {
100
- down() {
101
- this.renameColumn("content", "body");
102
- this.renameColumn("thumbnail_url", "thumb_url");
114
+ {
115
+ down() {
116
+ this.renameColumn("content", "body");
117
+ this.renameColumn("thumbnail_url", "thumb_url");
118
+ },
103
119
  },
104
- });
120
+ );
105
121
  ```
106
122
 
107
123
  ### `modify` — change column type or nullability
@@ -110,17 +126,21 @@ export default Migration.alter(Post, {
110
126
  import { Migration, string } from "@warlock.js/cascade";
111
127
  import { User } from "../user.model";
112
128
 
113
- export default Migration.alter(User, {
114
- modify: {
115
- // widen email from text to varchar(320) and keep not-null
116
- email: string(320).notNullable(),
129
+ export default Migration.alter(
130
+ User,
131
+ {
132
+ modify: {
133
+ // widen email from text to varchar(320) and keep not-null
134
+ email: string(320).notNullable(),
135
+ },
117
136
  },
118
- }, {
119
- down() {
120
- // revert to unbounded text
121
- this.text("email").notNullable();
137
+ {
138
+ down() {
139
+ // revert to unbounded text
140
+ this.text("email").notNullable();
141
+ },
122
142
  },
123
- });
143
+ );
124
144
  ```
125
145
 
126
146
  > `modify` re-declares the column. Include every modifier you want to keep (`.notNullable()`, `.default(…)`, etc.) — the declaration replaces the old one entirely.
@@ -132,29 +152,37 @@ export default Migration.alter(User, {
132
152
  ### Regular indexes — `addIndex` / `dropIndex`
133
153
 
134
154
  ```ts
135
- export default Migration.alter(Order, {
136
- // single column
137
- addIndex: [{ columns: "status" }],
138
- }, {
139
- down() {
140
- this.dropIndex("status");
155
+ export default Migration.alter(
156
+ Order,
157
+ {
158
+ // single column
159
+ addIndex: [{ columns: "status" }],
141
160
  },
142
- });
161
+ {
162
+ down() {
163
+ this.dropIndex("status");
164
+ },
165
+ },
166
+ );
143
167
  ```
144
168
 
145
169
  ```ts
146
170
  // composite index with an explicit name
147
- export default Migration.alter(Order, {
148
- addIndex: [
149
- { columns: ["organization_id", "status"], name: "idx_org_status" },
150
- { columns: "created_at" },
151
- ],
152
- }, {
153
- down() {
154
- this.dropIndex("idx_org_status");
155
- this.dropIndex("created_at");
171
+ export default Migration.alter(
172
+ Order,
173
+ {
174
+ addIndex: [
175
+ { columns: ["organization_id", "status"], name: "idx_org_status" },
176
+ { columns: "created_at" },
177
+ ],
156
178
  },
157
- });
179
+ {
180
+ down() {
181
+ this.dropIndex("idx_org_status");
182
+ this.dropIndex("created_at");
183
+ },
184
+ },
185
+ );
158
186
  ```
159
187
 
160
188
  `dropIndex` accepts a name string or a columns array:
@@ -168,63 +196,79 @@ export default Migration.alter(Order, {
168
196
  `options.concurrently: true` builds the index without locking the table (Postgres):
169
197
 
170
198
  ```ts
171
- addIndex: [{ columns: "email", options: { concurrently: true } }]
199
+ addIndex: [{ columns: "email", options: { concurrently: true } }];
172
200
  ```
173
201
 
174
202
  ### Unique constraints — `addUnique` / `dropUnique`
175
203
 
176
204
  ```ts
177
205
  // promote an indexed column to unique
178
- export default Migration.alter(User, {
179
- addUnique: [{ columns: "email" }],
180
- }, {
181
- down() {
182
- this.dropUnique("email");
206
+ export default Migration.alter(
207
+ User,
208
+ {
209
+ addUnique: [{ columns: "email" }],
183
210
  },
184
- });
211
+ {
212
+ down() {
213
+ this.dropUnique("email");
214
+ },
215
+ },
216
+ );
185
217
  ```
186
218
 
187
219
  Composite unique constraint:
188
220
 
189
221
  ```ts
190
- export default Migration.alter(Membership, {
191
- addUnique: [{ columns: ["organization_id", "user_id"], name: "uq_org_member" }],
192
- }, {
193
- down() {
194
- this.dropUnique(["organization_id", "user_id"]);
222
+ export default Migration.alter(
223
+ Membership,
224
+ {
225
+ addUnique: [{ columns: ["organization_id", "user_id"], name: "uq_org_member" }],
195
226
  },
196
- });
227
+ {
228
+ down() {
229
+ this.dropUnique(["organization_id", "user_id"]);
230
+ },
231
+ },
232
+ );
197
233
  ```
198
234
 
199
235
  Downgrade a unique to a plain index (the pattern used in this session):
200
236
 
201
237
  ```ts
202
- export default Migration.alter(User, {
203
- dropUnique: [["email"]],
204
- addIndex: [{ columns: "email", name: "users_email_index" }],
205
- }, {
206
- down() {
207
- this.dropIndex("users_email_index");
208
- this.unique("email");
238
+ export default Migration.alter(
239
+ User,
240
+ {
241
+ dropUnique: [["email"]],
242
+ addIndex: [{ columns: "email", name: "users_email_index" }],
209
243
  },
210
- });
244
+ {
245
+ down() {
246
+ this.dropIndex("users_email_index");
247
+ this.unique("email");
248
+ },
249
+ },
250
+ );
211
251
  ```
212
252
 
213
253
  ### Expression indexes — `addExpressionIndex` (Postgres)
214
254
 
215
255
  ```ts
216
- export default Migration.alter(User, {
217
- addExpressionIndex: [
218
- { expressions: "lower(email)", name: "idx_email_ci" },
219
- { expressions: "lower(name)", name: "idx_name_ci" },
220
- { expressions: ["year(created_at)", "status"] },
221
- ],
222
- }, {
223
- down() {
224
- this.dropIndex("idx_email_ci");
225
- this.dropIndex("idx_name_ci");
256
+ export default Migration.alter(
257
+ User,
258
+ {
259
+ addExpressionIndex: [
260
+ { expressions: "lower(email)", name: "idx_email_ci" },
261
+ { expressions: "lower(name)", name: "idx_name_ci" },
262
+ { expressions: ["year(created_at)", "status"] },
263
+ ],
226
264
  },
227
- });
265
+ {
266
+ down() {
267
+ this.dropIndex("idx_email_ci");
268
+ this.dropIndex("idx_name_ci");
269
+ },
270
+ },
271
+ );
228
272
  ```
229
273
 
230
274
  ---
@@ -234,30 +278,38 @@ export default Migration.alter(User, {
234
278
  ### Full-text — `addFullText` / `dropFullText`
235
279
 
236
280
  ```ts
237
- export default Migration.alter(Article, {
238
- addFullText: [
239
- {
240
- columns: ["title", "body"],
241
- options: { name: "ft_article_search", language: "english" },
281
+ export default Migration.alter(
282
+ Article,
283
+ {
284
+ addFullText: [
285
+ {
286
+ columns: ["title", "body"],
287
+ options: { name: "ft_article_search", language: "english" },
288
+ },
289
+ ],
290
+ },
291
+ {
292
+ down() {
293
+ this.dropFullText("ft_article_search");
242
294
  },
243
- ],
244
- }, {
245
- down() {
246
- this.dropFullText("ft_article_search");
247
295
  },
248
- });
296
+ );
249
297
  ```
250
298
 
251
299
  ### Geo-spatial — `addGeoIndex` / `dropGeoIndex`
252
300
 
253
301
  ```ts
254
- export default Migration.alter(Store, {
255
- addGeoIndex: [{ column: "location" }],
256
- }, {
257
- down() {
258
- this.dropGeoIndex("location");
302
+ export default Migration.alter(
303
+ Store,
304
+ {
305
+ addGeoIndex: [{ column: "location" }],
259
306
  },
260
- });
307
+ {
308
+ down() {
309
+ this.dropGeoIndex("location");
310
+ },
311
+ },
312
+ );
261
313
  ```
262
314
 
263
315
  Options: `{ name?, type?: "2dsphere" | "2d", min?, max? }`
@@ -265,16 +317,22 @@ Options: `{ name?, type?: "2dsphere" | "2d", min?, max? }`
265
317
  ### Vector (AI embeddings) — `addVectorIndex` / `dropVectorIndex`
266
318
 
267
319
  ```ts
268
- export default Migration.alter(Document, {
269
- addVectorIndex: [{
270
- column: "embedding",
271
- options: { dimensions: 1536, similarity: "cosine", name: "idx_doc_embedding" },
272
- }],
273
- }, {
274
- down() {
275
- this.dropVectorIndex("embedding");
320
+ export default Migration.alter(
321
+ Document,
322
+ {
323
+ addVectorIndex: [
324
+ {
325
+ column: "embedding",
326
+ options: { dimensions: 1536, similarity: "cosine", name: "idx_doc_embedding" },
327
+ },
328
+ ],
276
329
  },
277
- });
330
+ {
331
+ down() {
332
+ this.dropVectorIndex("embedding");
333
+ },
334
+ },
335
+ );
278
336
  ```
279
337
 
280
338
  Similarity options: `"cosine"` | `"euclidean"` | `"dotProduct"`.
@@ -284,13 +342,17 @@ Similarity options: `"cosine"` | `"euclidean"` | `"dotProduct"`.
284
342
  Expires documents automatically after a fixed duration:
285
343
 
286
344
  ```ts
287
- export default Migration.alter(Session, {
288
- addTTLIndex: [{ column: "created_at", expireAfterSeconds: 86400 }], // 24 h
289
- }, {
290
- down() {
291
- this.dropTTLIndex("created_at");
345
+ export default Migration.alter(
346
+ Session,
347
+ {
348
+ addTTLIndex: [{ column: "created_at", expireAfterSeconds: 86400 }], // 24 h
292
349
  },
293
- });
350
+ {
351
+ down() {
352
+ this.dropTTLIndex("created_at");
353
+ },
354
+ },
355
+ );
294
356
  ```
295
357
 
296
358
  ---
@@ -306,17 +368,21 @@ import { Migration } from "@warlock.js/cascade";
306
368
  import { Team } from "app/teams/models/team";
307
369
  import { User } from "../user.model";
308
370
 
309
- export default Migration.alter(User, {
310
- addForeign: [
311
- { column: "team_id", references: Team, onDelete: "cascade" },
312
- { column: "owner_id", references: "users", on: "id", onDelete: "setNull" },
313
- ],
314
- }, {
315
- down() {
316
- this.dropForeign("team_id", Team.table);
317
- this.dropForeign("owner_id", "users");
371
+ export default Migration.alter(
372
+ User,
373
+ {
374
+ addForeign: [
375
+ { column: "team_id", references: Team, onDelete: "cascade" },
376
+ { column: "owner_id", references: "users", on: "id", onDelete: "setNull" },
377
+ ],
318
378
  },
319
- });
379
+ {
380
+ down() {
381
+ this.dropForeign("team_id", Team.table);
382
+ this.dropForeign("owner_id", "users");
383
+ },
384
+ },
385
+ );
320
386
  ```
321
387
 
322
388
  `onDelete` / `onUpdate` values: `"cascade"` | `"restrict"` | `"setNull"` | `"noAction"`. Both default to `"restrict"` when omitted.
@@ -324,23 +390,27 @@ export default Migration.alter(User, {
324
390
  Drop by constraint name when you named it manually:
325
391
 
326
392
  ```ts
327
- dropForeign: [{ columnOrConstraint: "fk_my_custom_name" }]
393
+ dropForeign: [{ columnOrConstraint: "fk_my_custom_name" }];
328
394
  ```
329
395
 
330
396
  ### CHECK constraints — `addCheck` / `dropCheck`
331
397
 
332
398
  ```ts
333
- export default Migration.alter(Product, {
334
- addCheck: [
335
- { name: "price_positive", expression: "price_cents >= 0" },
336
- { name: "stock_non_negative", expression: "stock >= 0" },
337
- ],
338
- }, {
339
- down() {
340
- this.dropCheck("price_positive");
341
- this.dropCheck("stock_non_negative");
399
+ export default Migration.alter(
400
+ Product,
401
+ {
402
+ addCheck: [
403
+ { name: "price_positive", expression: "price_cents >= 0" },
404
+ { name: "stock_non_negative", expression: "stock >= 0" },
405
+ ],
342
406
  },
343
- });
407
+ {
408
+ down() {
409
+ this.dropCheck("price_positive");
410
+ this.dropCheck("stock_non_negative");
411
+ },
412
+ },
413
+ );
344
414
  ```
345
415
 
346
416
  ---
@@ -350,16 +420,20 @@ export default Migration.alter(Product, {
350
420
  Use `raw` when you need a one-off SQL statement alongside declarative changes (e.g. backfilling a new column in the same migration when the table is small):
351
421
 
352
422
  ```ts
353
- export default Migration.alter(User, {
354
- add: {
355
- full_name: text().nullable(),
423
+ export default Migration.alter(
424
+ User,
425
+ {
426
+ add: {
427
+ full_name: text().nullable(),
428
+ },
429
+ raw: `UPDATE users SET full_name = first_name || ' ' || last_name`,
356
430
  },
357
- raw: `UPDATE users SET full_name = first_name || ' ' || last_name`,
358
- }, {
359
- down() {
360
- this.dropColumn("full_name");
431
+ {
432
+ down() {
433
+ this.dropColumn("full_name");
434
+ },
361
435
  },
362
- });
436
+ );
363
437
  ```
364
438
 
365
439
  Pass an array for multiple statements:
@@ -368,7 +442,7 @@ Pass an array for multiple statements:
368
442
  raw: [
369
443
  `UPDATE users SET status = 'active' WHERE status IS NULL`,
370
444
  `UPDATE users SET role = 'member' WHERE role IS NULL`,
371
- ]
445
+ ];
372
446
  ```
373
447
 
374
448
  ---
@@ -377,24 +451,24 @@ raw: [
377
451
 
378
452
  `Migration.alter` **never** auto-infers rollback. Supply `down()` in options whenever you want `cascade migrate:rollback` to work. Inside `down()`, `this` is a full `Migration` instance — every class-form builder is available:
379
453
 
380
- | Operation | Class-form call in `down()` |
381
- |---|---|
382
- | Drop an added column | `this.dropColumn("name")` |
383
- | Re-add a dropped column | `this.text("name").nullable()` |
384
- | Undo a rename | `this.renameColumn("new", "old")` |
385
- | Undo a column modify | `this.text("col").notNullable()` |
386
- | Drop a plain index (by name) | `this.dropIndex("idx_name")` |
387
- | Drop a plain index (by columns) | `this.dropIndex(["col_a", "col_b"])` |
388
- | Drop a unique constraint | `this.dropUnique("email")` or `this.dropUnique(["a", "b"])` |
389
- | Add a unique constraint back | `this.unique("email")` |
390
- | Add a plain index back | `this.index("email", "idx_name")` |
391
- | Drop a foreign key | `this.dropForeign("col", "ref_table")` |
392
- | Drop a CHECK constraint | `this.dropCheck("constraint_name")` |
393
- | Drop full-text index | `this.dropFullText("ft_name")` |
394
- | Drop geo index | `this.dropGeoIndex("column")` |
395
- | Drop vector index | `this.dropVectorIndex("column")` |
396
- | Drop TTL index | `this.dropTTLIndex("column")` |
397
- | Raw SQL | `this.raw("SQL statement")` |
454
+ | Operation | Class-form call in `down()` |
455
+ | ------------------------------- | ----------------------------------------------------------- |
456
+ | Drop an added column | `this.dropColumn("name")` |
457
+ | Re-add a dropped column | `this.text("name").nullable()` |
458
+ | Undo a rename | `this.renameColumn("new", "old")` |
459
+ | Undo a column modify | `this.text("col").notNullable()` |
460
+ | Drop a plain index (by name) | `this.dropIndex("idx_name")` |
461
+ | Drop a plain index (by columns) | `this.dropIndex(["col_a", "col_b"])` |
462
+ | Drop a unique constraint | `this.dropUnique("email")` or `this.dropUnique(["a", "b"])` |
463
+ | Add a unique constraint back | `this.unique("email")` |
464
+ | Add a plain index back | `this.index("email", "idx_name")` |
465
+ | Drop a foreign key | `this.dropForeign("col", "ref_table")` |
466
+ | Drop a CHECK constraint | `this.dropCheck("constraint_name")` |
467
+ | Drop full-text index | `this.dropFullText("ft_name")` |
468
+ | Drop geo index | `this.dropGeoIndex("column")` |
469
+ | Drop vector index | `this.dropVectorIndex("column")` |
470
+ | Drop TTL index | `this.dropTTLIndex("column")` |
471
+ | Raw SQL | `this.raw("SQL statement")` |
398
472
 
399
473
  If a migration is genuinely irreversible, throw inside `down()` so accidental rollbacks fail loudly:
400
474
 
@@ -410,28 +484,32 @@ If a migration is genuinely irreversible, throw inside `down()` so accidental ro
410
484
 
411
485
  ## Options (`MigrationAlterOptions`)
412
486
 
413
- | Key | Type | Purpose |
414
- |---|---|---|
415
- | `down` | `(this: Migration) => void \| Promise<void>` | Rollback logic (not auto-inferred) |
416
- | `up` | `(this: Migration) => void \| Promise<void>` | Extra imperative logic to run after declarative changes |
417
- | `order` | `number` | Execution order override (default `0`) |
418
- | `createdAt` | `string` | ISO timestamp override for migration name ordering |
419
- | `transactional` | `boolean` | Wrap the migration in a DB transaction |
487
+ | Key | Type | Purpose |
488
+ | --------------- | -------------------------------------------- | ------------------------------------------------------- |
489
+ | `down` | `(this: Migration) => void \| Promise<void>` | Rollback logic (not auto-inferred) |
490
+ | `up` | `(this: Migration) => void \| Promise<void>` | Extra imperative logic to run after declarative changes |
491
+ | `order` | `number` | Execution order override (default `0`) |
492
+ | `createdAt` | `string` | ISO timestamp override for migration name ordering |
493
+ | `transactional` | `boolean` | Wrap the migration in a DB transaction |
420
494
 
421
495
  The `up` hook runs **after** the declarative schema changes, making it useful for data backfills or conditional DDL that depends on the added schema:
422
496
 
423
497
  ```ts
424
- export default Migration.alter(User, {
425
- add: { verified_at: timestamp().nullable() },
426
- }, {
427
- up() {
428
- // backfill: mark existing confirmed users as verified
429
- this.raw(`UPDATE users SET verified_at = updated_at WHERE status = 'confirmed'`);
498
+ export default Migration.alter(
499
+ User,
500
+ {
501
+ add: { verified_at: timestamp().nullable() },
430
502
  },
431
- down() {
432
- this.dropColumn("verified_at");
503
+ {
504
+ up() {
505
+ // backfill: mark existing confirmed users as verified
506
+ this.raw(`UPDATE users SET verified_at = updated_at WHERE status = 'confirmed'`);
507
+ },
508
+ down() {
509
+ this.dropColumn("verified_at");
510
+ },
433
511
  },
434
- });
512
+ );
435
513
  ```
436
514
 
437
515
  ---
@@ -445,48 +523,58 @@ import { Migration, uuid } from "@warlock.js/cascade";
445
523
  import { Team } from "app/teams/models/team";
446
524
  import { User } from "../user.model";
447
525
 
448
- export default Migration.alter(User, {
449
- add: {
450
- team_id: uuid().nullable(),
526
+ export default Migration.alter(
527
+ User,
528
+ {
529
+ add: {
530
+ team_id: uuid().nullable(),
531
+ },
532
+ addForeign: [{ column: "team_id", references: Team, onDelete: "setNull" }],
533
+ addIndex: [{ columns: "team_id" }],
451
534
  },
452
- addForeign: [
453
- { column: "team_id", references: Team, onDelete: "setNull" },
454
- ],
455
- addIndex: [{ columns: "team_id" }],
456
- }, {
457
- down() {
458
- this.dropForeign("team_id", Team.table);
459
- this.dropIndex("team_id");
460
- this.dropColumn("team_id");
535
+ {
536
+ down() {
537
+ this.dropForeign("team_id", Team.table);
538
+ this.dropIndex("team_id");
539
+ this.dropColumn("team_id");
540
+ },
461
541
  },
462
- });
542
+ );
463
543
  ```
464
544
 
465
545
  ### Swap unique → plain index
466
546
 
467
547
  ```ts
468
- export default Migration.alter(User, {
469
- dropUnique: [["email"]],
470
- addIndex: [{ columns: "email", name: "users_email_index" }],
471
- }, {
472
- down() {
473
- this.dropIndex("users_email_index");
474
- this.unique("email");
548
+ export default Migration.alter(
549
+ User,
550
+ {
551
+ dropUnique: [["email"]],
552
+ addIndex: [{ columns: "email", name: "users_email_index" }],
475
553
  },
476
- });
554
+ {
555
+ down() {
556
+ this.dropIndex("users_email_index");
557
+ this.unique("email");
558
+ },
559
+ },
560
+ );
477
561
  ```
478
562
 
479
563
  ### Rename + widen a column
480
564
 
481
565
  ```ts
482
- export default Migration.alter(Post, {
483
- rename: { body: "content" },
484
- modify: { content: text().notNullable() },
485
- }, {
486
- down() {
487
- this.renameColumn("content", "body");
566
+ export default Migration.alter(
567
+ Post,
568
+ {
569
+ rename: { body: "content" },
570
+ modify: { content: text().notNullable() },
488
571
  },
489
- });
572
+ {
573
+ down() {
574
+ this.renameColumn("content", "body");
575
+ },
576
+ },
577
+ );
490
578
  ```
491
579
 
492
580
  ### Multiple operations in one migration
@@ -494,22 +582,26 @@ export default Migration.alter(Post, {
494
582
  Combine freely — all operations run in a single `up()` call:
495
583
 
496
584
  ```ts
497
- export default Migration.alter(Product, {
498
- add: { sku: text().unique().notNullable() },
499
- drop: ["old_code"],
500
- rename: { price: "price_cents" },
501
- modify: { price_cents: integer().notNullable() },
502
- addIndex: [{ columns: ["category_id", "status"], name: "idx_cat_status" }],
503
- addCheck: [{ name: "price_non_negative", expression: "price_cents >= 0" }],
504
- }, {
505
- down() {
506
- this.dropCheck("price_non_negative");
507
- this.dropIndex("idx_cat_status");
508
- this.renameColumn("price_cents", "price");
509
- this.text("old_code").nullable();
510
- this.dropColumn("sku");
585
+ export default Migration.alter(
586
+ Product,
587
+ {
588
+ add: { sku: text().unique().notNullable() },
589
+ drop: ["old_code"],
590
+ rename: { price: "price_cents" },
591
+ modify: { price_cents: integer().notNullable() },
592
+ addIndex: [{ columns: ["category_id", "status"], name: "idx_cat_status" }],
593
+ addCheck: [{ name: "price_non_negative", expression: "price_cents >= 0" }],
511
594
  },
512
- });
595
+ {
596
+ down() {
597
+ this.dropCheck("price_non_negative");
598
+ this.dropIndex("idx_cat_status");
599
+ this.renameColumn("price_cents", "price");
600
+ this.text("old_code").nullable();
601
+ this.dropColumn("sku");
602
+ },
603
+ },
604
+ );
513
605
  ```
514
606
 
515
607
  ---