@warlock.js/cascade 4.6.0 → 4.7.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 (61) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/cjs/index.cjs +372 -56
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/contracts/database-driver.contract.d.mts +8 -0
  5. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  6. package/esm/contracts/index.d.mts +1 -1
  7. package/esm/contracts/query-builder.contract.d.mts +36 -1
  8. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  9. package/esm/drivers/mongodb/mongodb-driver.d.mts +5 -0
  10. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  11. package/esm/drivers/mongodb/mongodb-driver.mjs +10 -4
  12. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  13. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +4 -0
  14. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  15. package/esm/drivers/mongodb/mongodb-migration-driver.mjs +5 -2
  16. package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
  17. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +15 -0
  18. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  19. package/esm/drivers/mongodb/mongodb-query-builder.mjs +24 -0
  20. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  21. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +16 -0
  22. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  23. package/esm/drivers/mongodb/mongodb-query-parser.mjs +33 -1
  24. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  25. package/esm/drivers/postgres/postgres-driver.d.mts +69 -13
  26. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  27. package/esm/drivers/postgres/postgres-driver.mjs +155 -27
  28. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  29. package/esm/drivers/postgres/postgres-query-builder.d.mts +14 -3
  30. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  31. package/esm/drivers/postgres/postgres-query-builder.mjs +44 -8
  32. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  33. package/esm/drivers/postgres/postgres-query-parser.d.mts +6 -1
  34. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  35. package/esm/drivers/postgres/postgres-query-parser.mjs +13 -0
  36. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  37. package/esm/drivers/postgres/postgres-sql-serializer.mjs +15 -4
  38. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  39. package/esm/index.d.mts +2 -2
  40. package/esm/migration/migration-runner.d.mts.map +1 -1
  41. package/esm/migration/migration-runner.mjs +25 -3
  42. package/esm/migration/migration-runner.mjs.map +1 -1
  43. package/esm/migration/migration.d.mts +6 -3
  44. package/esm/migration/migration.d.mts.map +1 -1
  45. package/esm/migration/migration.mjs +6 -3
  46. package/esm/migration/migration.mjs.map +1 -1
  47. package/esm/model/methods/scope-methods.mjs +18 -4
  48. package/esm/model/methods/scope-methods.mjs.map +1 -1
  49. package/esm/model/model.d.mts +6 -0
  50. package/esm/model/model.d.mts.map +1 -1
  51. package/esm/model/model.mjs +6 -0
  52. package/esm/model/model.mjs.map +1 -1
  53. package/esm/query-builder/query-builder.d.mts +12 -1
  54. package/esm/query-builder/query-builder.d.mts.map +1 -1
  55. package/esm/query-builder/query-builder.mjs +19 -0
  56. package/esm/query-builder/query-builder.mjs.map +1 -1
  57. package/llms-full.txt +41 -7
  58. package/llms.txt +1 -1
  59. package/package.json +4 -4
  60. package/skills/README.md +1 -1
  61. package/skills/manage-transactions/SKILL.md +41 -7
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: manage-transactions
3
- description: 'Wrap multi-statement work in `transaction(async () => {...})` — rollback on throw, commit on resolve, optional `isolation` level (Postgres), per-`dataSource` scope. Also the home for transaction-aware raw SQL (`Model.raw` / `DataSource.raw` → `RawQueryResult`) and the Postgres `nativeArrayColumns` connection option. Postgres native; MongoDB requires replica set. Triggers: `transaction`, `isolation`, `SERIALIZABLE`, `READ COMMITTED`, nested savepoints, `Model.raw`, `DataSource.raw`, raw SQL, `RawQueryResult`, `nativeArrayColumns`, `JSONB[]`, `TEXT[]`; "wrap two writes atomically", "transfer balance between accounts", "rollback on error", "MongoDB replica set transactions", "run raw SQL", "native array column"; typical import `import { transaction } from "@warlock.js/cascade"`. Skip: single-row atomic ops without a transaction — `@warlock.js/cascade/perform-atomic-ops/SKILL.md`; per-source scope — `@warlock.js/cascade/manage-data-sources/SKILL.md`; competing patterns `mongoose.startSession`, `pg` `BEGIN` manually, `prisma.$transaction`, `typeorm` `QueryRunner`.'
3
+ description: 'Wrap multi-statement work in `transaction(async () => {...})` — rollback on throw, commit on resolve, optional `isolation` level (Postgres), per-`dataSource` scope. Also the home for row locking (`lockForUpdate({ skipLocked })` → `SELECT ... FOR UPDATE [SKIP LOCKED | NOWAIT]`, Postgres-only), transaction-aware raw SQL (`Model.raw` / `DataSource.raw` → `RawQueryResult`) and Postgres native-array column handling (`JSONB[]` / `TEXT[]` / `INTEGER[]` auto-detected via schema introspection on connect; `nativeArrayColumns` is an optional override). Postgres native; MongoDB requires replica set. Triggers: `transaction`, `isolation`, `SERIALIZABLE`, `READ COMMITTED`, nested transaction, flat nesting, nested savepoints, `lockForUpdate`, `skipLocked`, `FOR UPDATE`, `SKIP LOCKED`, `NOWAIT`, row lock, pessimistic lock, queue claim, `Model.raw`, `DataSource.raw`, raw SQL, `RawQueryResult`, `nativeArrayColumns`, `JSONB[]`, `TEXT[]`; "wrap two writes atomically", "transfer balance between accounts", "rollback on error", "MongoDB replica set transactions", "lock rows so workers don''t double-process", "claim jobs from a table", "run raw SQL", "native array column", "malformed array literal", "array column not saving", "nested transaction not visible", "foreign key violation on insert inside transaction", "service transaction inside seeder"; typical import `import { transaction } from "@warlock.js/cascade"`. Skip: single-row atomic ops without a transaction — `@warlock.js/cascade/perform-atomic-ops/SKILL.md`; per-source scope — `@warlock.js/cascade/manage-data-sources/SKILL.md`; competing patterns `mongoose.startSession`, `pg` `BEGIN` manually, `prisma.$transaction`, `typeorm` `QueryRunner`.'
4
4
  ---
5
5
 
6
6
  # Use transactions
@@ -68,7 +68,9 @@ Postgres has no such requirement — transactions work out of the box.
68
68
 
69
69
  ## Nesting
70
70
 
71
- The function-shaped `transaction(fn)` is **not** nestable — calling it inside an already-open transaction is not supported. For nested scope on Postgres, drop to the manual API (`driver.beginTransaction()`) and use savepoints explicitly. For most app code, keep a single top-level `transaction(fn)` and let any inner failure abort the whole flow.
71
+ `transaction(fn)` **flat-nests**: calling it inside an already-open transaction JOINS the outer one instead of opening a second, independent transaction. The inner block runs on the **same** session — so it sees the outer's uncommitted writes — and the **outermost** `transaction()` owns commit/rollback. A service that opens its own `transaction()` for standalone atomicity therefore also works unchanged when called inside a larger transaction — e.g. a seeder that creates a row, then calls a service that opens a transaction to insert a child referencing it (an independent inner transaction couldn't see the parent's uncommitted row, and the child insert would fail its foreign key).
72
+
73
+ A throw anywhere inside unwinds the **whole** outer transaction — Postgres aborts a transaction on the first error, so there is no automatic per-block savepoint. For independent partial rollback of an inner block, drop to the manual API (`driver.beginTransaction()`) and use savepoints explicitly.
72
74
 
73
75
  ## Explicit rollback
74
76
 
@@ -97,6 +99,36 @@ await transaction(async () => {
97
99
 
98
100
  On `SERIALIZABLE`, Postgres may abort with a serialization failure when concurrent transactions conflict — wrap in retry logic at the caller.
99
101
 
102
+ ## Row locking — `lockForUpdate({ skipLocked })` (Postgres)
103
+
104
+ Chain `lockForUpdate()` on any query to emit `SELECT ... FOR UPDATE`: the matched rows are locked until the transaction ends, so no other transaction can modify or lock them. Only meaningful **inside** a transaction — outside one the lock releases immediately.
105
+
106
+ `{ skipLocked: true }` adds `SKIP LOCKED` — rows another transaction already holds are silently skipped instead of waited on. That is the standard concurrent queue-claim shape: N workers each grab *different* pending rows with no double-processing and no blocking.
107
+
108
+ ```ts
109
+ import { transaction } from "@warlock.js/cascade";
110
+
111
+ // Worker loop — each concurrent worker claims a disjoint batch:
112
+ const jobs = await transaction(async () => {
113
+ const claimed = await Job.query()
114
+ .where("status", "pending")
115
+ .orderBy("id", "asc")
116
+ .limit(10)
117
+ .lockForUpdate({ skipLocked: true })
118
+ .get();
119
+
120
+ for (const job of claimed) {
121
+ await job.merge({ status: "processing" }).save();
122
+ }
123
+
124
+ return claimed;
125
+ });
126
+ ```
127
+
128
+ `{ noWait: true }` errors immediately when a matching row is locked (instead of waiting or skipping) — mutually exclusive with `skipLocked`.
129
+
130
+ **Postgres-only.** MongoDB has no row-level SELECT locking — the MongoDB driver **throws** on `lockForUpdate()`. On Mongo, claim atomically instead (`findOneAndUpdate` with a reservation filter — see `@warlock.js/cascade/perform-atomic-ops/SKILL.md`).
131
+
100
132
  ## Outside the transaction
101
133
 
102
134
  Once the callback returns, the transaction is committed. Subsequent calls — including reads — see the committed state. Don't try to "share" a model instance between inside-transaction and outside contexts; reload outside if you need fresh state.
@@ -125,21 +157,23 @@ const { rows: counts } = await dataSource.raw<{ count: number }>(
125
157
 
126
158
  `Model.raw<T>(sql, params?)` and `DataSource.raw<T>(sql, params?)` both return `Promise<RawQueryResult<T>>` — `{ rows: T[]; rowCount: number }`, **not** hydrated model instances. MongoDB drivers **throw**: there is no raw SQL on Mongo.
127
159
 
128
- ### `nativeArrayColumns` (Postgres connection option)
160
+ ### Native-array columns (`JSONB[]` / `TEXT[]` / `INTEGER[]`)
161
+
162
+ From a value alone the serializer can't tell a `json` / `jsonb` column (which needs JSON text) from a genuine **native-array** column (`arrayJson()` → `JSONB[]`, `arrayText()` → `TEXT[]`, …, which needs the raw JS array so node-pg emits a `{...}` literal). Guess wrong and Postgres rejects the insert with *"malformed array literal"*.
129
163
 
130
- The Postgres serializer can't see the table schema, so by default it `JSON.stringify`s every non-vector array (and plain object) to bind it as JSON text the correct form for a `json` / `jsonb` column. For a genuine **native-array** column (`JSONB[]` / `TEXT[]` / `INTEGER[]`, e.g. from `arrayJson()` / `arrayText()`) that's wrong: node-pg must receive the raw JS array so it emits a `{...}` array literal. List those columns in the connection's `nativeArrayColumns` to opt them out of JSON-text encoding:
164
+ **This is handled automatically.** On connect the driver introspects `information_schema` once and remembers which columns are native arrays **per table**, so `arrayText()` / `arrayJson()` columns just work with **no configuration**. Because it's per-table, the same column name being `TEXT[]` in one table and `jsonb` in another is encoded correctly for each; the pgvector all-number array form is still preserved.
165
+
166
+ `nativeArrayColumns` remains as an optional, table-agnostic **override** — for a column created outside a migration, or an environment where `information_schema` can't be read:
131
167
 
132
168
  ```ts
133
169
  connectToDatabase({
134
170
  name: "default",
135
171
  driver: "postgres",
136
172
  // ...
137
- nativeArrayColumns: ["tags", "category_ids"], // genuine TEXT[] / INTEGER[] columns
173
+ nativeArrayColumns: ["tags", "category_ids"], // optional manual override
138
174
  });
139
175
  ```
140
176
 
141
- The pgvector all-number array form is preserved automatically — you only need this for non-vector native arrays.
142
-
143
177
  ## Side effects after commit — the outbox pattern
144
178
 
145
179
  For "side effects must only happen if the transaction succeeded" (publish to a queue, send an email, write to a search index), don't run them inside the transaction. Use the outbox pattern: write a row to an outbox table inside the transaction, dispatch from the outbox in a separate worker after commit.