@warlock.js/cascade 5.24.0 → 5.26.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 (93) hide show
  1. package/CHANGELOG.md +30 -5
  2. package/README.md +101 -101
  3. package/bin/cascade.js +0 -0
  4. package/cjs/index.cjs +307 -88
  5. package/cjs/index.cjs.map +1 -1
  6. package/esm/contracts/database-driver.contract.d.mts +22 -1
  7. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  8. package/esm/contracts/index.d.mts +1 -1
  9. package/esm/contracts/query-builder.contract.d.mts +7 -0
  10. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  11. package/esm/data-source/data-source-registry.d.mts +8 -6
  12. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  13. package/esm/data-source/data-source-registry.mjs +7 -1
  14. package/esm/data-source/data-source-registry.mjs.map +1 -1
  15. package/esm/drivers/mongodb/mongodb-driver.d.mts +13 -0
  16. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  17. package/esm/drivers/mongodb/mongodb-driver.mjs +71 -2
  18. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  19. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +5 -0
  20. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  21. package/esm/drivers/mongodb/mongodb-query-builder.mjs +11 -1
  22. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  23. package/esm/drivers/postgres/postgres-driver.d.mts +9 -0
  24. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  25. package/esm/drivers/postgres/postgres-driver.mjs +55 -7
  26. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  27. package/esm/drivers/postgres/postgres-query-builder.d.mts +5 -0
  28. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  29. package/esm/drivers/postgres/postgres-query-builder.mjs +15 -0
  30. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  31. package/esm/drivers/postgres/postgres-query-parser.d.mts +1 -4
  32. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  33. package/esm/drivers/postgres/postgres-query-parser.mjs +4 -5
  34. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  35. package/esm/errors/unsafe-filter.error.mjs.map +1 -1
  36. package/esm/errors/unsafe-raw-expression.error.mjs.map +1 -1
  37. package/esm/errors/unsupported-lock-for-update.error.d.mts +11 -0
  38. package/esm/errors/unsupported-lock-for-update.error.d.mts.map +1 -0
  39. package/esm/errors/unsupported-lock-for-update.error.mjs +16 -0
  40. package/esm/errors/unsupported-lock-for-update.error.mjs.map +1 -0
  41. package/esm/events/model-events.mjs.map +1 -1
  42. package/esm/index.d.mts +4 -3
  43. package/esm/index.mjs +2 -1
  44. package/esm/migration/index.d.mts +1 -1
  45. package/esm/migration/migration-order.mjs +6 -3
  46. package/esm/migration/migration-order.mjs.map +1 -1
  47. package/esm/migration/migration-runner.d.mts +5 -3
  48. package/esm/migration/migration-runner.d.mts.map +1 -1
  49. package/esm/migration/migration-runner.mjs +3 -2
  50. package/esm/migration/migration-runner.mjs.map +1 -1
  51. package/esm/migration/migration.d.mts +11 -1
  52. package/esm/migration/migration.d.mts.map +1 -1
  53. package/esm/migration/migration.mjs +45 -2
  54. package/esm/migration/migration.mjs.map +1 -1
  55. package/esm/migration/sql-grammar.mjs.map +1 -1
  56. package/esm/model/generate-model-id.mjs +23 -0
  57. package/esm/model/generate-model-id.mjs.map +1 -0
  58. package/esm/model/methods/delete-methods.mjs.map +1 -1
  59. package/esm/model/methods/meta-methods.mjs.map +1 -1
  60. package/esm/model/methods/query-methods.mjs +38 -11
  61. package/esm/model/methods/query-methods.mjs.map +1 -1
  62. package/esm/model/methods/write-methods.mjs.map +1 -1
  63. package/esm/model/model.d.mts +2 -2
  64. package/esm/model/model.d.mts.map +1 -1
  65. package/esm/model/model.mjs +1 -1
  66. package/esm/model/model.mjs.map +1 -1
  67. package/esm/query-builder/query-builder.d.mts +5 -0
  68. package/esm/query-builder/query-builder.d.mts.map +1 -1
  69. package/esm/query-builder/query-builder.mjs +16 -1
  70. package/esm/query-builder/query-builder.mjs.map +1 -1
  71. package/esm/utils/sanitize-filter.mjs.map +1 -1
  72. package/esm/validation/plugins/database-rules-plugin.mjs.map +1 -1
  73. package/esm/writer/database-writer.d.mts +0 -33
  74. package/esm/writer/database-writer.d.mts.map +1 -1
  75. package/esm/writer/database-writer.mjs +3 -52
  76. package/esm/writer/database-writer.mjs.map +1 -1
  77. package/llms-full.txt +2911 -2864
  78. package/package.json +5 -5
  79. package/skills/aggregate-data/SKILL.md +165 -165
  80. package/skills/alter-migration/SKILL.md +531 -531
  81. package/skills/configure-delete-strategy/SKILL.md +130 -130
  82. package/skills/define-model/SKILL.md +205 -205
  83. package/skills/define-relations/SKILL.md +171 -171
  84. package/skills/manage-data-sources/SKILL.md +177 -140
  85. package/skills/manage-transactions/SKILL.md +215 -205
  86. package/skills/paginate-results/SKILL.md +122 -122
  87. package/skills/perform-atomic-ops/SKILL.md +156 -156
  88. package/skills/query-data/SKILL.md +213 -213
  89. package/skills/run-cascade-cli/SKILL.md +125 -125
  90. package/skills/search-by-vector/SKILL.md +127 -127
  91. package/skills/subscribe-to-model-events/SKILL.md +148 -148
  92. package/skills/track-changes/SKILL.md +127 -127
  93. package/skills/write-migration/SKILL.md +206 -206
@@ -1,140 +1,177 @@
1
- ---
2
- name: manage-data-sources
3
- description: 'Configure multiple databases — register each via `connectToDatabase({ name, driver, database, isDefault })`, assign a model with `static dataSource = "name"`, route a migration with `dataSource` on the migration class, inspect via `dataSourceRegistry.get(name)` / `getAllDataSources()`. The first (or `isDefault: true`) source is the default. Triggers: `connectToDatabase`, `dataSourceRegistry`, `dataSourceRegistry.get`, `getAllDataSources`, `static dataSource`; "multi-database app", "per-tenant DB", "analytics on separate DB"; typical import `import { connectToDatabase, dataSourceRegistry } from "@warlock.js/cascade"`. Skip: per-source migrations — `@warlock.js/cascade/write-migration/SKILL.md`; transaction scope — `@warlock.js/cascade/manage-transactions/SKILL.md`; competing patterns `mongoose.createConnection`, `typeorm` `DataSource`, `prisma` multi-schema.'
4
- ---
5
-
6
- # Manage data sources
7
-
8
- Most apps run against a single database, and Cascade's defaults assume that. When you need multiple — analytics on a separate DB, per-tenant DBs — register each one and bind models to the right source.
9
-
10
- ## Register sources at boot
11
-
12
- `connectToDatabase()` builds the driver, registers the data source, and connects. Call it once per database:
13
-
14
- ```ts
15
- import { connectToDatabase } from "@warlock.js/cascade";
16
-
17
- await connectToDatabase({
18
- name: "primary",
19
- driver: "postgres",
20
- database: "app",
21
- host: "localhost",
22
- port: 5432,
23
- username: "app",
24
- password: "secret",
25
- isDefault: true, // the default source for models that don't pin one
26
- });
27
-
28
- await connectToDatabase({
29
- name: "analytics",
30
- driver: "postgres",
31
- database: "analytics",
32
- host: "analytics-host",
33
- });
34
-
35
- await connectToDatabase({
36
- name: "logs",
37
- driver: "mongodb",
38
- database: "logs",
39
- uri: "mongodb://localhost:27017",
40
- });
41
- ```
42
-
43
- The first source registered becomes the default unless another passes `isDefault: true`. (Lower-level: `dataSourceRegistry.register({ name, driver, isDefault })` if you build the driver yourself — `register` takes a `DataSourceOptions` object whose `driver` is a `DriverContract` instance, and constructs the `DataSource` for you.)
44
-
45
- ## Assign a model to a source
46
-
47
- ```ts
48
- @RegisterModel()
49
- export class AnalyticsEvent extends Model<AnalyticsEventSchema> {
50
- public static table = "analytics_events";
51
- public static schema = analyticsEventSchema;
52
- public static dataSource = "analytics"; // ← goes to the analytics DB
53
- }
54
-
55
- @RegisterModel()
56
- export class LogEntry extends Model<LogEntrySchema> {
57
- public static table = "logs";
58
- public static schema = logEntrySchema;
59
- public static dataSource = "logs"; // ← goes to MongoDB
60
- }
61
- ```
62
-
63
- `static dataSource` takes the registered name (or a `DataSource` instance). Models without it use the default. Same query API; different storage. There is no per-query `Model.using(name)` override — the binding lives on the class.
64
-
65
- ## Migrations per data source
66
-
67
- A migration class can pin its target source:
68
-
69
- ```ts
70
- import { Migration } from "@warlock.js/cascade";
71
-
72
- export default class CreateEventsTable extends Migration {
73
- public readonly table = "analytics_events";
74
- public readonly dataSource = "analytics";
75
-
76
- public up(): void {
77
- this.createTable();
78
- this.id();
79
- this.string("type");
80
- this.timestamps();
81
- }
82
-
83
- public down(): void {
84
- this.dropTable();
85
- }
86
- }
87
- ```
88
-
89
- See [`@warlock.js/cascade/write-migration/SKILL.md`](@warlock.js/cascade/write-migration/SKILL.md) for the migration shape.
90
-
91
- ## Transactions and data sources
92
-
93
- The top-level `transaction(fn)` helper runs on the **default** source's driver — it does not take a source name. To transact against a non-default source, reach for that source's driver directly (`dataSourceRegistry.get("analytics").driver.transaction(...)`). Transactions can't span two sources; coordinate at the application level (saga / outbox) if you need that.
94
-
95
- ```ts
96
- await transaction(async () => {
97
- // runs on the default source
98
- await Order.create({ ... });
99
- await OrderItem.createMany(items);
100
- });
101
- ```
102
-
103
- ## Multi-tenant per-tenant database
104
-
105
- For strict tenant isolation, register a source per tenant once, then bind at the model layer (or resolve the model class per tenant):
106
-
107
- ```ts
108
- async function ensureTenantSource(tenantId: string) {
109
- try {
110
- dataSourceRegistry.get(tenantId); // throws if not registered
111
- } catch {
112
- const config = await loadTenantConfig(tenantId);
113
- await connectToDatabase({ name: tenantId, ...config, isDefault: false });
114
- }
115
- }
116
- ```
117
-
118
- Register each tenant source once and reuse it — re-registering the same name re-creates the source.
119
-
120
- ## Inspection
121
-
122
- ```ts
123
- dataSourceRegistry.get("analytics"); // the DataSource instance (throws if missing)
124
- dataSourceRegistry.get(); // the default DataSource
125
- dataSourceRegistry.getAllDataSources(); // DataSource[] — every registered source
126
- ```
127
-
128
- There is no `has(name)` / `list()` / `setDefault()` — guard with a `try/catch` around `get(name)`, iterate `getAllDataSources()`, and set the default via `isDefault` at registration.
129
-
130
- ## Things NOT to do
131
-
132
- - Don't call `dataSourceRegistry.register("name", config)` — `register` takes a single `DataSourceOptions` object (`{ name, driver, isDefault }`, `driver` being a built `DriverContract`); for the common case use `connectToDatabase({ name, ... })`.
133
- - Don't reach for `Model.using(name)` / `setDefault` / `has` / `list` — they don't exist. Bind via `static dataSource`, inspect via `get` / `getAllDataSources`.
134
- - Don't span a transaction across two data sources. Use a saga / outbox pattern.
135
- - Don't write to a read replica. Most replicas reject writes; the data is overwritten on the next replication.
136
-
137
- ## See also
138
-
139
- - [`@warlock.js/cascade/write-migration/SKILL.md`](@warlock.js/cascade/write-migration/SKILL.md) — per-source migrations
140
- - [`@warlock.js/cascade/manage-transactions/SKILL.md`](@warlock.js/cascade/manage-transactions/SKILL.md) — transactions run on the default source
1
+ ---
2
+ name: manage-data-sources
3
+ description: 'Configure multiple databases — register each via `connectToDatabase({ name, driver, database, isDefault })`, assign a model with `static dataSource = "name"`, route a migration with `dataSource` on the migration class, inspect via `dataSourceRegistry.get(name)` / `getAllDataSources()`. The first (or `isDefault: true`) source is the default. Triggers: `connectToDatabase`, `dataSourceRegistry`, `dataSourceRegistry.get`, `getAllDataSources`, `static dataSource`; "multi-database app", "per-tenant DB", "analytics on separate DB"; typical import `import { connectToDatabase, dataSourceRegistry } from "@warlock.js/cascade"`. Skip: per-source migrations — `@warlock.js/cascade/write-migration/SKILL.md`; transaction scope — `@warlock.js/cascade/manage-transactions/SKILL.md`; competing patterns `mongoose.createConnection`, `typeorm` `DataSource`, `prisma` multi-schema.'
4
+ ---
5
+
6
+ # Manage data sources
7
+
8
+ Most apps run against a single database, and Cascade's defaults assume that. When you need multiple — analytics on a separate DB, per-tenant DBs — register each one and bind models to the right source.
9
+
10
+ ## Register sources at boot
11
+
12
+ `connectToDatabase()` builds the driver, registers the data source, and connects. Call it once per database:
13
+
14
+ ```ts
15
+ import { connectToDatabase } from "@warlock.js/cascade";
16
+
17
+ await connectToDatabase({
18
+ name: "primary",
19
+ driver: "postgres",
20
+ database: "app",
21
+ host: "localhost",
22
+ port: 5432,
23
+ username: "app",
24
+ password: "secret",
25
+ isDefault: true, // the default source for models that don't pin one
26
+ });
27
+
28
+ await connectToDatabase({
29
+ name: "analytics",
30
+ driver: "postgres",
31
+ database: "analytics",
32
+ host: "analytics-host",
33
+ });
34
+
35
+ await connectToDatabase({
36
+ name: "logs",
37
+ driver: "mongodb",
38
+ database: "logs",
39
+ uri: "mongodb://localhost:27017",
40
+ });
41
+ ```
42
+
43
+ The first source registered becomes the default unless another passes `isDefault: true`. (Lower-level: `dataSourceRegistry.register({ name, driver, isDefault })` if you build the driver yourself — `register` takes a `DataSourceOptions` object whose `driver` is a `DriverContract` instance, and constructs the `DataSource` for you.)
44
+
45
+ ## Assign a model to a source
46
+
47
+ ```ts
48
+ @RegisterModel()
49
+ export class AnalyticsEvent extends Model<AnalyticsEventSchema> {
50
+ public static table = "analytics_events";
51
+ public static schema = analyticsEventSchema;
52
+ public static dataSource = "analytics"; // ← goes to the analytics DB
53
+ }
54
+
55
+ @RegisterModel()
56
+ export class LogEntry extends Model<LogEntrySchema> {
57
+ public static table = "logs";
58
+ public static schema = logEntrySchema;
59
+ public static dataSource = "logs"; // ← goes to MongoDB
60
+ }
61
+ ```
62
+
63
+ `static dataSource` takes the registered name (or a `DataSource` instance). Models without it use the default. Same query API; different storage. There is no per-query `Model.using(name)` override — the binding lives on the class.
64
+
65
+ ## Migrations per data source
66
+
67
+ A migration class can pin its target source:
68
+
69
+ ```ts
70
+ import { Migration } from "@warlock.js/cascade";
71
+
72
+ export default class CreateEventsTable extends Migration {
73
+ public readonly table = "analytics_events";
74
+ public readonly dataSource = "analytics";
75
+
76
+ public up(): void {
77
+ this.createTable();
78
+ this.id();
79
+ this.string("type");
80
+ this.timestamps();
81
+ }
82
+
83
+ public down(): void {
84
+ this.dropTable();
85
+ }
86
+ }
87
+ ```
88
+
89
+ See [`@warlock.js/cascade/write-migration/SKILL.md`](@warlock.js/cascade/write-migration/SKILL.md) for the migration shape.
90
+
91
+ ## Transactions and data sources
92
+
93
+ The top-level `transaction(fn)` helper runs on the **default** source's driver — it does not take a source name. To transact against a non-default source, reach for that source's driver directly (`dataSourceRegistry.get("analytics").driver.transaction(...)`). Transactions can't span two sources; coordinate at the application level (saga / outbox) if you need that.
94
+
95
+ ```ts
96
+ await transaction(async () => {
97
+ // runs on the default source
98
+ await Order.create({ ... });
99
+ await OrderItem.createMany(items);
100
+ });
101
+ ```
102
+
103
+ ## Multi-tenant per-tenant database
104
+
105
+ For strict tenant isolation, register a source per tenant once, then bind at the model layer (or resolve the model class per tenant):
106
+
107
+ ```ts
108
+ async function ensureTenantSource(tenantId: string) {
109
+ try {
110
+ dataSourceRegistry.get(tenantId); // throws if not registered
111
+ } catch {
112
+ const config = await loadTenantConfig(tenantId);
113
+ await connectToDatabase({ name: tenantId, ...config, isDefault: false });
114
+ }
115
+ }
116
+ ```
117
+
118
+ Register each tenant source once and reuse it — re-registering the same name re-creates the source.
119
+
120
+ ## Inspection
121
+
122
+ ```ts
123
+ dataSourceRegistry.get("analytics"); // the DataSource instance (throws if missing)
124
+ dataSourceRegistry.get(); // the default DataSource
125
+ dataSourceRegistry.getAllDataSources(); // DataSource[] — every registered source
126
+ ```
127
+
128
+ There is no `has(name)` / `list()` / `setDefault()` — guard with a `try/catch` around `get(name)`, iterate `getAllDataSources()`, and set the default via `isDefault` at registration.
129
+
130
+ ## Observe database queries
131
+
132
+ Subscribe once at the registry to observe settled queries from every registered
133
+ source. Keep the listener reference to remove it later:
134
+
135
+ ```ts
136
+ import { dataSourceRegistry, type QueryEvent } from "@warlock.js/cascade";
137
+ import { log } from "@warlock.js/logger";
138
+
139
+ const onQuery = (event: QueryEvent) => {
140
+ if (event.durationMs > 200) {
141
+ log.warn("database", "slow-query", event);
142
+ }
143
+ };
144
+
145
+ dataSourceRegistry.on("query", onQuery);
146
+ // Later:
147
+ dataSourceRegistry.off("query", onQuery);
148
+ ```
149
+
150
+ `QueryEvent` has `connection`, `driver` (`"postgres"` or `"mongodb"`),
151
+ `durationMs`, and `startedAt`; SQL events may include `sql`, `bindings`, and
152
+ `rowCount`, while Mongo events may include `collection`, `command`, and
153
+ `pipeline`. It also carries `error` when the command failed. Registry events
154
+ use the registered data-source name as `connection`.
155
+
156
+ For one source only, subscribe to its driver with the same listener lifecycle:
157
+
158
+ ```ts
159
+ const source = dataSourceRegistry.get("analytics");
160
+ source.driver.on("query", onQuery);
161
+ source.driver.off("query", onQuery);
162
+ ```
163
+
164
+ A throwing listener never fails the query. Mongo command monitoring is enabled
165
+ unless `clientOptions.monitorCommands` is explicitly set.
166
+
167
+ ## Things NOT to do
168
+
169
+ - Don't call `dataSourceRegistry.register("name", config)` — `register` takes a single `DataSourceOptions` object (`{ name, driver, isDefault }`, `driver` being a built `DriverContract`); for the common case use `connectToDatabase({ name, ... })`.
170
+ - Don't reach for `Model.using(name)` / `setDefault` / `has` / `list` — they don't exist. Bind via `static dataSource`, inspect via `get` / `getAllDataSources`.
171
+ - Don't span a transaction across two data sources. Use a saga / outbox pattern.
172
+ - Don't write to a read replica. Most replicas reject writes; the data is overwritten on the next replication.
173
+
174
+ ## See also
175
+
176
+ - [`@warlock.js/cascade/write-migration/SKILL.md`](@warlock.js/cascade/write-migration/SKILL.md) — per-source migrations
177
+ - [`@warlock.js/cascade/manage-transactions/SKILL.md`](@warlock.js/cascade/manage-transactions/SKILL.md) — transactions run on the default source