@warlock.js/cascade 5.23.2 → 5.25.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.
- package/CHANGELOG.md +352 -329
- package/README.md +101 -101
- package/bin/cascade.js +0 -0
- package/cjs/index.cjs +208 -69
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/database-driver.contract.d.mts +22 -1
- package/esm/contracts/database-driver.contract.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +1 -1
- package/esm/data-source/data-source-registry.d.mts +8 -6
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +7 -1
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +13 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +71 -2
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +2 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +9 -0
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +61 -5
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +1 -4
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +4 -5
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +5 -0
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/errors/unsafe-filter.error.mjs.map +1 -1
- package/esm/errors/unsafe-raw-expression.error.mjs.map +1 -1
- package/esm/errors/unsupported-lock-for-update.error.d.mts +11 -0
- package/esm/errors/unsupported-lock-for-update.error.d.mts.map +1 -0
- package/esm/errors/unsupported-lock-for-update.error.mjs +16 -0
- package/esm/errors/unsupported-lock-for-update.error.mjs.map +1 -0
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/sql-grammar.mjs.map +1 -1
- package/esm/model/generate-model-id.mjs +23 -0
- package/esm/model/generate-model-id.mjs.map +1 -0
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +25 -2
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/query-builder/query-builder.mjs +1 -1
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/utils/sanitize-filter.mjs.map +1 -1
- package/esm/validation/plugins/database-rules-plugin.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +0 -33
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +4 -52
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +2835 -2783
- package/package.json +5 -5
- package/skills/aggregate-data/SKILL.md +165 -165
- package/skills/alter-migration/SKILL.md +531 -531
- package/skills/configure-delete-strategy/SKILL.md +130 -130
- package/skills/define-model/SKILL.md +205 -205
- package/skills/define-relations/SKILL.md +171 -171
- package/skills/manage-data-sources/SKILL.md +177 -140
- package/skills/manage-transactions/SKILL.md +233 -208
- package/skills/paginate-results/SKILL.md +122 -122
- package/skills/perform-atomic-ops/SKILL.md +156 -156
- package/skills/query-data/SKILL.md +213 -213
- package/skills/run-cascade-cli/SKILL.md +125 -125
- package/skills/search-by-vector/SKILL.md +127 -127
- package/skills/subscribe-to-model-events/SKILL.md +148 -148
- package/skills/track-changes/SKILL.md +127 -127
- package/skills/write-migration/SKILL.md +206 -206
package/README.md
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
# cascade
|
|
2
|
-
|
|
3
|
-
A powerful, model-first TypeScript ORM for **MongoDB and PostgreSQL** on `Node.js`.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
Cascade is a unified ORM for MongoDB and PostgreSQL — one model-first API runs against both engines. Define a model once (its `@warlock.js/seal` schema doubles as the TypeScript type, the runtime validator, and the DB shape) and query straight off the class. It's fast, type-safe, and built for real-world apps.
|
|
8
|
-
|
|
9
|
-
Some but not all of the features:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- **Easy to use:** `Cascade` is very easy to use, it's just a wrapper around MongoDB driver.
|
|
13
|
-
- **Supports multiple connections:** You can perform multiple connections to different MongoDB connections and use each one of them separately.
|
|
14
|
-
- **Supports multiple databases:** `Cascade` supports multiple databases, you can connect to multiple databases at the same time.
|
|
15
|
-
- **Powerful Aggregate framework:** `Cascade` has a powerful aggregate framework that helps you to perform complex queries.
|
|
16
|
-
- **Basic CRUD operations:** `Cascade` supports basic CRUD operations, you can perform create, read, update and delete operations.
|
|
17
|
-
- **Events Driven:** `Cascade` is events driven, you can listen to events and perform actions, for example before creating, updating or deleting a document.
|
|
18
|
-
- **Powerful Models:** `Cascade` has a powerful models system, a Model is a collection manager document based, it manages a collection's document easily with many utilities.
|
|
19
|
-
- **Learning curve:** `Cascade` has a very small learning curve, you can learn it in few minutes.
|
|
20
|
-
- **Pagination support:** `Cascade` supports pagination, you can paginate your results easily.
|
|
21
|
-
- **Output formatting:** `Cascade` supports output formatting, you can format your output easily when model is sent as a response.
|
|
22
|
-
- **Auto incremented id:** `Cascade` supports auto incremented id, you can use it as a primary key for your documents.
|
|
23
|
-
- **Random or sequential id:** `Cascade` supports random or sequential id.
|
|
24
|
-
- **Recycle Bin:** Reduce collection documents by removing the document entirely from the collection, but move it to a separate collection trash.
|
|
25
|
-
- **Migration system:** `Cascade` has a migration system, you can create migrations and run them easily.
|
|
26
|
-
- **Data casting:** You can cast your data to a specific type or using custom casting.
|
|
27
|
-
- **Embedded documents:** `Cascade` supports single and multiple embedded documents, you can embed documents inside other documents.
|
|
28
|
-
- **Syncing Models**: Auto update documents when model's data is updated or deleted.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
## Server-only
|
|
32
|
-
|
|
33
|
-
`@warlock.js/cascade`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
|
|
34
|
-
|
|
35
|
-
## Installation
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
npm i @warlock.js/cascade
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Using yarn:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
yarn add @warlock.js/cascade
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
Using pnpm:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
pnpm add @warlock.js/cascade
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## Peek inside cascade
|
|
55
|
-
|
|
56
|
-
Here is a simple example of defining a User model:
|
|
57
|
-
|
|
58
|
-
```ts title="src/models/user.ts"
|
|
59
|
-
import { Model, RegisterModel } from "@warlock.js/cascade";
|
|
60
|
-
|
|
61
|
-
@RegisterModel()
|
|
62
|
-
export class User extends Model {
|
|
63
|
-
/**
|
|
64
|
-
* The table / collection name.
|
|
65
|
-
* Must be defined explicitly.
|
|
66
|
-
*/
|
|
67
|
-
public static table = "users";
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
A quick example of creating a user:
|
|
72
|
-
|
|
73
|
-
```ts title="src/controllers/users.ts"
|
|
74
|
-
import { User } from "src/models/user";
|
|
75
|
-
|
|
76
|
-
export async function createUser() {
|
|
77
|
-
const user = await User.create({
|
|
78
|
-
name: "Hasan Zohdy",
|
|
79
|
-
email: "hassanzohdy@gmail.com",
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
console.log(user.data);
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Outputs something similar to:
|
|
87
|
-
|
|
88
|
-
```json
|
|
89
|
-
{
|
|
90
|
-
"id": 1231412,
|
|
91
|
-
"_id": "fagtrw43qwedasjoijwq",
|
|
92
|
-
"name": "Hasan Zohdy",
|
|
93
|
-
"email": "hassanzohdy@gmail.com",
|
|
94
|
-
"createdAt": "2023-06-01 00:00:00",
|
|
95
|
-
"updatedAt": "2023-06-01 00:00:00"
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
## Documentation
|
|
101
|
-
|
|
1
|
+
# cascade
|
|
2
|
+
|
|
3
|
+
A powerful, model-first TypeScript ORM for **MongoDB and PostgreSQL** on `Node.js`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
Cascade is a unified ORM for MongoDB and PostgreSQL — one model-first API runs against both engines. Define a model once (its `@warlock.js/seal` schema doubles as the TypeScript type, the runtime validator, and the DB shape) and query straight off the class. It's fast, type-safe, and built for real-world apps.
|
|
8
|
+
|
|
9
|
+
Some but not all of the features:
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
- **Easy to use:** `Cascade` is very easy to use, it's just a wrapper around MongoDB driver.
|
|
13
|
+
- **Supports multiple connections:** You can perform multiple connections to different MongoDB connections and use each one of them separately.
|
|
14
|
+
- **Supports multiple databases:** `Cascade` supports multiple databases, you can connect to multiple databases at the same time.
|
|
15
|
+
- **Powerful Aggregate framework:** `Cascade` has a powerful aggregate framework that helps you to perform complex queries.
|
|
16
|
+
- **Basic CRUD operations:** `Cascade` supports basic CRUD operations, you can perform create, read, update and delete operations.
|
|
17
|
+
- **Events Driven:** `Cascade` is events driven, you can listen to events and perform actions, for example before creating, updating or deleting a document.
|
|
18
|
+
- **Powerful Models:** `Cascade` has a powerful models system, a Model is a collection manager document based, it manages a collection's document easily with many utilities.
|
|
19
|
+
- **Learning curve:** `Cascade` has a very small learning curve, you can learn it in few minutes.
|
|
20
|
+
- **Pagination support:** `Cascade` supports pagination, you can paginate your results easily.
|
|
21
|
+
- **Output formatting:** `Cascade` supports output formatting, you can format your output easily when model is sent as a response.
|
|
22
|
+
- **Auto incremented id:** `Cascade` supports auto incremented id, you can use it as a primary key for your documents.
|
|
23
|
+
- **Random or sequential id:** `Cascade` supports random or sequential id.
|
|
24
|
+
- **Recycle Bin:** Reduce collection documents by removing the document entirely from the collection, but move it to a separate collection trash.
|
|
25
|
+
- **Migration system:** `Cascade` has a migration system, you can create migrations and run them easily.
|
|
26
|
+
- **Data casting:** You can cast your data to a specific type or using custom casting.
|
|
27
|
+
- **Embedded documents:** `Cascade` supports single and multiple embedded documents, you can embed documents inside other documents.
|
|
28
|
+
- **Syncing Models**: Auto update documents when model's data is updated or deleted.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Server-only
|
|
32
|
+
|
|
33
|
+
`@warlock.js/cascade`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm i @warlock.js/cascade
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Using yarn:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
yarn add @warlock.js/cascade
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Using pnpm:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm add @warlock.js/cascade
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Peek inside cascade
|
|
55
|
+
|
|
56
|
+
Here is a simple example of defining a User model:
|
|
57
|
+
|
|
58
|
+
```ts title="src/models/user.ts"
|
|
59
|
+
import { Model, RegisterModel } from "@warlock.js/cascade";
|
|
60
|
+
|
|
61
|
+
@RegisterModel()
|
|
62
|
+
export class User extends Model {
|
|
63
|
+
/**
|
|
64
|
+
* The table / collection name.
|
|
65
|
+
* Must be defined explicitly.
|
|
66
|
+
*/
|
|
67
|
+
public static table = "users";
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A quick example of creating a user:
|
|
72
|
+
|
|
73
|
+
```ts title="src/controllers/users.ts"
|
|
74
|
+
import { User } from "src/models/user";
|
|
75
|
+
|
|
76
|
+
export async function createUser() {
|
|
77
|
+
const user = await User.create({
|
|
78
|
+
name: "Hasan Zohdy",
|
|
79
|
+
email: "hassanzohdy@gmail.com",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(user.data);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Outputs something similar to:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"id": 1231412,
|
|
91
|
+
"_id": "fagtrw43qwedasjoijwq",
|
|
92
|
+
"name": "Hasan Zohdy",
|
|
93
|
+
"email": "hassanzohdy@gmail.com",
|
|
94
|
+
"createdAt": "2023-06-01 00:00:00",
|
|
95
|
+
"updatedAt": "2023-06-01 00:00:00"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Documentation
|
|
101
|
+
|
|
102
102
|
To see the full documentation, please visit [Cascade Documentation](https://warlock.js.org/docs/cascade/getting-started/introduction)
|
package/bin/cascade.js
CHANGED
|
File without changes
|
package/cjs/index.cjs
CHANGED
|
@@ -317,7 +317,7 @@ var DataSourceRegistry = class {
|
|
|
317
317
|
defaultSource;
|
|
318
318
|
defaultIsExplicit = false;
|
|
319
319
|
events = new node_events.EventEmitter();
|
|
320
|
-
/** Replaced sources; their forwarded driver events are ignored
|
|
320
|
+
/** Replaced sources; their forwarded driver events are ignored. */
|
|
321
321
|
detached = /* @__PURE__ */ new WeakSet();
|
|
322
322
|
/** In-flight `getOrRegister` factories, so concurrent callers share one creation. */
|
|
323
323
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -368,6 +368,12 @@ var DataSourceRegistry = class {
|
|
|
368
368
|
source.driver.on("disconnected", () => {
|
|
369
369
|
if (!this.detached.has(source)) this.events.emit("disconnected", source);
|
|
370
370
|
});
|
|
371
|
+
source.driver.on("query", (event) => {
|
|
372
|
+
if (!this.detached.has(source)) this.events.emit("query", {
|
|
373
|
+
...event,
|
|
374
|
+
connection: source.name
|
|
375
|
+
});
|
|
376
|
+
});
|
|
371
377
|
return source;
|
|
372
378
|
}
|
|
373
379
|
/**
|
|
@@ -670,6 +676,20 @@ var UnsupportedLeanOperationError = class UnsupportedLeanOperationError extends
|
|
|
670
676
|
}
|
|
671
677
|
};
|
|
672
678
|
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region ../cascade/src/errors/unsupported-lock-for-update.error.ts
|
|
681
|
+
/**
|
|
682
|
+
* Error thrown when `lockForUpdate()` is requested on MongoDB, whose document
|
|
683
|
+
* model has no row-level `SELECT ... FOR UPDATE` equivalent.
|
|
684
|
+
*/
|
|
685
|
+
var UnsupportedLockForUpdateError = class UnsupportedLockForUpdateError extends Error {
|
|
686
|
+
constructor() {
|
|
687
|
+
super("lockForUpdate() is unsupported on MongoDB. MongoDB has no row-level SELECT locking. Use a conditional atomic update instead, e.g. Model.atomic({ id, leaseUntil: { $lt: now } }, { $set: { status: \"processing\" } }, { trustedFilter: true }).");
|
|
688
|
+
this.name = "UnsupportedLockForUpdateError";
|
|
689
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, UnsupportedLockForUpdateError);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
673
693
|
//#endregion
|
|
674
694
|
//#region ../cascade/src/errors/unsupported-query-operation.error.ts
|
|
675
695
|
/**
|
|
@@ -4045,6 +4065,27 @@ function offModelEvent(model, event, listener) {
|
|
|
4045
4065
|
model.events.off(event, listener);
|
|
4046
4066
|
}
|
|
4047
4067
|
|
|
4068
|
+
//#endregion
|
|
4069
|
+
//#region ../cascade/src/model/generate-model-id.ts
|
|
4070
|
+
/**
|
|
4071
|
+
* Generate the next model id using the model's configured sequence options.
|
|
4072
|
+
*
|
|
4073
|
+
* This is shared by normal inserts and MongoDB upserts so both write paths
|
|
4074
|
+
* resolve random and configured sequence settings identically.
|
|
4075
|
+
*/
|
|
4076
|
+
async function generateModelId(ModelClass) {
|
|
4077
|
+
const idGenerator = ModelClass.getDataSource().idGenerator;
|
|
4078
|
+
if (!idGenerator) return;
|
|
4079
|
+
const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
|
|
4080
|
+
const initialId = ModelClass.initialId ? ModelClass.initialId : ModelClass.randomInitialId ? typeof ModelClass.randomInitialId === "function" ? ModelClass.randomInitialId() : randomInt(1e4, 499999) : 1;
|
|
4081
|
+
const incrementIdBy = ModelClass.randomIncrement ? typeof ModelClass.randomIncrement === "function" ? ModelClass.randomIncrement() : randomInt(1, 10) : ModelClass.incrementIdBy || 1;
|
|
4082
|
+
return await idGenerator.generateNextId({
|
|
4083
|
+
table: ModelClass.table,
|
|
4084
|
+
initialId,
|
|
4085
|
+
incrementIdBy
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4048
4089
|
//#endregion
|
|
4049
4090
|
//#region ../cascade/src/validation/database-writer-validation-error.ts
|
|
4050
4091
|
/**
|
|
@@ -4589,6 +4630,7 @@ var DatabaseWriter = class {
|
|
|
4589
4630
|
else {
|
|
4590
4631
|
const validated = result.data;
|
|
4591
4632
|
for (const column of Object.keys(dataToValidate)) if (column in validated) this.model.set(column, validated[column]);
|
|
4633
|
+
else if (dataToValidate[column] === null && column in validationSchema.schema) this.model.set(column, null);
|
|
4592
4634
|
else this.unvalidatedColumns.add(column);
|
|
4593
4635
|
}
|
|
4594
4636
|
if (!options.skipEvents) await this.model.emitEvent("validated", { result });
|
|
@@ -4707,15 +4749,8 @@ var DatabaseWriter = class {
|
|
|
4707
4749
|
*/
|
|
4708
4750
|
async generateNextId() {
|
|
4709
4751
|
if (!this.ctor.autoGenerateId || this.model.get("id")) return;
|
|
4710
|
-
const
|
|
4711
|
-
if (
|
|
4712
|
-
const initialId = this.resolveInitialId();
|
|
4713
|
-
const incrementIdBy = this.resolveIncrementBy();
|
|
4714
|
-
const id = await idGenerator.generateNextId({
|
|
4715
|
-
table: this.table,
|
|
4716
|
-
initialId,
|
|
4717
|
-
incrementIdBy
|
|
4718
|
-
});
|
|
4752
|
+
const id = await generateModelId(this.ctor);
|
|
4753
|
+
if (id === void 0) return;
|
|
4719
4754
|
this.model.set("id", id);
|
|
4720
4755
|
}
|
|
4721
4756
|
/**
|
|
@@ -4762,49 +4797,6 @@ var DatabaseWriter = class {
|
|
|
4762
4797
|
return operations;
|
|
4763
4798
|
}
|
|
4764
4799
|
/**
|
|
4765
|
-
* Resolve the initial ID from model configuration.
|
|
4766
|
-
*
|
|
4767
|
-
* Priority:
|
|
4768
|
-
* 1. Model.initialId (explicit value)
|
|
4769
|
-
* 2. Model.randomInitialId (random or function)
|
|
4770
|
-
* 3. Default: 1
|
|
4771
|
-
*
|
|
4772
|
-
* @returns The initial ID value
|
|
4773
|
-
* @private
|
|
4774
|
-
*/
|
|
4775
|
-
resolveInitialId() {
|
|
4776
|
-
if (this.ctor.initialId) return this.ctor.initialId;
|
|
4777
|
-
if (this.ctor.randomInitialId) return typeof this.ctor.randomInitialId === "function" ? this.ctor.randomInitialId() : this.randomInt(1e4, 499999);
|
|
4778
|
-
return 1;
|
|
4779
|
-
}
|
|
4780
|
-
/**
|
|
4781
|
-
* Resolve the increment value from model configuration.
|
|
4782
|
-
*
|
|
4783
|
-
* Priority:
|
|
4784
|
-
* 1. Model.incrementIdBy (explicit value)
|
|
4785
|
-
* 2. Model.randomIncrement (random or function)
|
|
4786
|
-
* 3. Default: 1
|
|
4787
|
-
*
|
|
4788
|
-
* @returns The increment value
|
|
4789
|
-
* @private
|
|
4790
|
-
*/
|
|
4791
|
-
resolveIncrementBy() {
|
|
4792
|
-
if (this.ctor.randomIncrement) return typeof this.ctor.randomIncrement === "function" ? this.ctor.randomIncrement() : this.randomInt(1, 10);
|
|
4793
|
-
if (this.ctor.incrementIdBy) return this.ctor.incrementIdBy;
|
|
4794
|
-
return 1;
|
|
4795
|
-
}
|
|
4796
|
-
/**
|
|
4797
|
-
* Generate a random integer between min and max (inclusive).
|
|
4798
|
-
*
|
|
4799
|
-
* @param min - Minimum value
|
|
4800
|
-
* @param max - Maximum value
|
|
4801
|
-
* @returns Random integer
|
|
4802
|
-
* @private
|
|
4803
|
-
*/
|
|
4804
|
-
randomInt(min, max) {
|
|
4805
|
-
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
4806
|
-
}
|
|
4807
|
-
/**
|
|
4808
4800
|
* Trigger sync operations after successful save.
|
|
4809
4801
|
*
|
|
4810
4802
|
* Emits a model.updated event that ModelSyncOperation listens to.
|
|
@@ -5318,6 +5310,26 @@ function toDriverOptions(options) {
|
|
|
5318
5310
|
return rest;
|
|
5319
5311
|
}
|
|
5320
5312
|
/**
|
|
5313
|
+
* Add the same generated `id` that normal Mongo inserts receive to an upsert's
|
|
5314
|
+
* insert branch. `$setOnInsert` keeps an existing document's id immutable.
|
|
5315
|
+
*/
|
|
5316
|
+
async function withAutoGeneratedUpsertId(ModelClass, filter, operations, options) {
|
|
5317
|
+
if (!options?.upsert || !ModelClass.autoGenerateId || ModelClass.getDriver().name !== "mongodb" || Array.isArray(operations)) return operations;
|
|
5318
|
+
const filterId = filter.id;
|
|
5319
|
+
const filterConstrainsId = Object.hasOwn(filter, "id") && (filterId === null || typeof filterId !== "object" || !Array.isArray(filterId) && Object.hasOwn(filterId, "$eq"));
|
|
5320
|
+
const updateSuppliesId = Object.hasOwn(operations.$set ?? {}, "id") || Object.hasOwn(operations.$setOnInsert ?? {}, "id");
|
|
5321
|
+
if (filterConstrainsId || updateSuppliesId) return operations;
|
|
5322
|
+
const id = await generateModelId(ModelClass);
|
|
5323
|
+
if (id === void 0) return operations;
|
|
5324
|
+
return {
|
|
5325
|
+
...operations,
|
|
5326
|
+
$setOnInsert: {
|
|
5327
|
+
...operations.$setOnInsert,
|
|
5328
|
+
id
|
|
5329
|
+
}
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
/**
|
|
5321
5333
|
* Pin a driver-level write to the rows the model's global scopes (tenant, soft
|
|
5322
5334
|
* delete) allow. The driver write APIs take a raw filter and never see scopes,
|
|
5323
5335
|
* so the target ids are resolved through the scoped query builder and added to
|
|
@@ -5351,7 +5363,8 @@ async function performAtomic(ModelClass, filter, operations, options) {
|
|
|
5351
5363
|
const resolved = resolveFilter(filter, options);
|
|
5352
5364
|
const scoped = options?.upsert ? await scopeUpsertFilter(ModelClass, resolved) : await scopeWriteFilter(ModelClass, resolved, false);
|
|
5353
5365
|
if (!scoped) return 0;
|
|
5354
|
-
const
|
|
5366
|
+
const upsertOperations = await withAutoGeneratedUpsertId(ModelClass, scoped, operations, options);
|
|
5367
|
+
const result = await ModelClass.getDriver().atomic(ModelClass.table, scoped, upsertOperations, toDriverOptions(options));
|
|
5355
5368
|
return result.modifiedCount + (result.upsertedCount ?? 0);
|
|
5356
5369
|
}
|
|
5357
5370
|
/**
|
|
@@ -5388,7 +5401,8 @@ async function findOneAndUpdateRecord(ModelClass, filter, update, options) {
|
|
|
5388
5401
|
const resolved = resolveFilter(filter, options);
|
|
5389
5402
|
const scoped = options?.upsert ? await scopeUpsertFilter(ModelClass, resolved) : await scopeWriteFilter(ModelClass, resolved, true);
|
|
5390
5403
|
if (!scoped) return null;
|
|
5391
|
-
const
|
|
5404
|
+
const upsertUpdate = await withAutoGeneratedUpsertId(ModelClass, scoped, update, options);
|
|
5405
|
+
const result = await ModelClass.getDriver().findOneAndUpdate(ModelClass.table, scoped, upsertUpdate, toDriverOptions(options));
|
|
5392
5406
|
if (!result) return null;
|
|
5393
5407
|
return ModelClass.hydrate(result);
|
|
5394
5408
|
}
|
|
@@ -10465,7 +10479,7 @@ var QueryBuilder = class QueryBuilder {
|
|
|
10465
10479
|
}
|
|
10466
10480
|
/** Raw GROUP BY expression. */
|
|
10467
10481
|
groupByRaw(expression, bindings) {
|
|
10468
|
-
this.addOperation("
|
|
10482
|
+
this.addOperation("groupByRaw", {
|
|
10469
10483
|
expression,
|
|
10470
10484
|
bindings: bindings ?? []
|
|
10471
10485
|
});
|
|
@@ -13582,7 +13596,7 @@ var MongoQueryBuilder = class MongoQueryBuilder extends QueryBuilder {
|
|
|
13582
13596
|
* keeps a queue-claim pattern from silently running unlocked.
|
|
13583
13597
|
*/
|
|
13584
13598
|
lockForUpdate() {
|
|
13585
|
-
throw new
|
|
13599
|
+
throw new UnsupportedLockForUpdateError();
|
|
13586
13600
|
}
|
|
13587
13601
|
/**
|
|
13588
13602
|
* Executes a callback with the query builder without breaking the chain.
|
|
@@ -14389,6 +14403,7 @@ var MongoDbDriver = class {
|
|
|
14389
14403
|
transactionOptions;
|
|
14390
14404
|
idGeneratorInstance;
|
|
14391
14405
|
_blueprint;
|
|
14406
|
+
monitoredCommands = /* @__PURE__ */ new Map();
|
|
14392
14407
|
get blueprint() {
|
|
14393
14408
|
if (!this._blueprint) this._blueprint = new MongoDBBlueprint(this.database);
|
|
14394
14409
|
return this._blueprint;
|
|
@@ -14516,13 +14531,18 @@ var MongoDbDriver = class {
|
|
|
14516
14531
|
_warlock_js_logger.log.warn("database.mongodb", "connection", "Disconnected from database");
|
|
14517
14532
|
}
|
|
14518
14533
|
});
|
|
14534
|
+
client.on("commandStarted", (event) => this.commandStarted(event));
|
|
14535
|
+
client.on("commandSucceeded", (event) => this.commandSucceeded(event));
|
|
14536
|
+
client.on("commandFailed", (event) => this.commandFailed(event));
|
|
14519
14537
|
if (this.config.logging) {
|
|
14520
14538
|
const ignoredCommands = [
|
|
14521
14539
|
"isMaster",
|
|
14522
14540
|
"hello",
|
|
14523
14541
|
"ping",
|
|
14524
14542
|
"saslStart",
|
|
14525
|
-
"saslContinue"
|
|
14543
|
+
"saslContinue",
|
|
14544
|
+
"endSessions",
|
|
14545
|
+
"buildInfo"
|
|
14526
14546
|
];
|
|
14527
14547
|
client.on("commandStarted", (event) => {
|
|
14528
14548
|
if (ignoredCommands.includes(event.commandName)) return;
|
|
@@ -14579,6 +14599,10 @@ var MongoDbDriver = class {
|
|
|
14579
14599
|
on(event, listener) {
|
|
14580
14600
|
this.events.on(event, listener);
|
|
14581
14601
|
}
|
|
14602
|
+
/** Remove a previously registered driver event listener. */
|
|
14603
|
+
off(event, listener) {
|
|
14604
|
+
this.events.off(event, listener);
|
|
14605
|
+
}
|
|
14582
14606
|
/**
|
|
14583
14607
|
* Insert a single document into the given collection.
|
|
14584
14608
|
*/
|
|
@@ -14895,7 +14919,7 @@ var MongoDbDriver = class {
|
|
|
14895
14919
|
*/
|
|
14896
14920
|
buildClientOptions() {
|
|
14897
14921
|
const baseOptions = { ...this.config.clientOptions ?? {} };
|
|
14898
|
-
if (
|
|
14922
|
+
if (baseOptions.monitorCommands === void 0) baseOptions.monitorCommands = true;
|
|
14899
14923
|
if (this.config.username && !baseOptions.auth) baseOptions.auth = {
|
|
14900
14924
|
username: this.config.username,
|
|
14901
14925
|
password: this.config.password
|
|
@@ -14909,6 +14933,65 @@ var MongoDbDriver = class {
|
|
|
14909
14933
|
emit(event, ...args) {
|
|
14910
14934
|
this.events.emit(event, ...args);
|
|
14911
14935
|
}
|
|
14936
|
+
commandStarted(event) {
|
|
14937
|
+
if (this.ignoredCommand(event.commandName) || this.events.listenerCount("query") === 0) return;
|
|
14938
|
+
const started = performance.now();
|
|
14939
|
+
const { lsid: _lsid, $clusterTime: _clusterTime, $db: _database, ...pipeline } = event.command;
|
|
14940
|
+
const collection = event.command[event.commandName];
|
|
14941
|
+
this.monitoredCommands.set(event.requestId, {
|
|
14942
|
+
connection: this.name,
|
|
14943
|
+
driver: "mongodb",
|
|
14944
|
+
command: event.commandName,
|
|
14945
|
+
collection: typeof collection === "string" ? collection : void 0,
|
|
14946
|
+
pipeline,
|
|
14947
|
+
started,
|
|
14948
|
+
startedAt: performance.timeOrigin + started
|
|
14949
|
+
});
|
|
14950
|
+
}
|
|
14951
|
+
commandSucceeded(event) {
|
|
14952
|
+
const command = this.monitoredCommands.get(event.requestId);
|
|
14953
|
+
if (!command) return;
|
|
14954
|
+
this.monitoredCommands.delete(event.requestId);
|
|
14955
|
+
const { started, ...query } = command;
|
|
14956
|
+
const rowCount = event.reply?.n ?? event.reply?.cursor?.firstBatch?.length;
|
|
14957
|
+
this.emitQueryEvent({
|
|
14958
|
+
...query,
|
|
14959
|
+
durationMs: performance.now() - started,
|
|
14960
|
+
...typeof rowCount === "number" ? { rowCount } : {}
|
|
14961
|
+
});
|
|
14962
|
+
}
|
|
14963
|
+
commandFailed(event) {
|
|
14964
|
+
const command = this.monitoredCommands.get(event.requestId);
|
|
14965
|
+
if (!command) return;
|
|
14966
|
+
this.monitoredCommands.delete(event.requestId);
|
|
14967
|
+
const { started, ...query } = command;
|
|
14968
|
+
this.emitQueryEvent({
|
|
14969
|
+
...query,
|
|
14970
|
+
durationMs: performance.now() - started,
|
|
14971
|
+
error: event.failure
|
|
14972
|
+
});
|
|
14973
|
+
}
|
|
14974
|
+
/**
|
|
14975
|
+
* Emit a `query` event. It runs inside the MongoDB client's own monitoring
|
|
14976
|
+
* callback, and an observer must never break the client, so a throwing
|
|
14977
|
+
* listener is swallowed here.
|
|
14978
|
+
*/
|
|
14979
|
+
emitQueryEvent(event) {
|
|
14980
|
+
try {
|
|
14981
|
+
this.emit("query", event);
|
|
14982
|
+
} catch {}
|
|
14983
|
+
}
|
|
14984
|
+
ignoredCommand(command) {
|
|
14985
|
+
return [
|
|
14986
|
+
"isMaster",
|
|
14987
|
+
"hello",
|
|
14988
|
+
"ping",
|
|
14989
|
+
"saslStart",
|
|
14990
|
+
"saslContinue",
|
|
14991
|
+
"endSessions",
|
|
14992
|
+
"buildInfo"
|
|
14993
|
+
].includes(command);
|
|
14994
|
+
}
|
|
14912
14995
|
/**
|
|
14913
14996
|
* Ensure MongoDB is running as a replica set (required for transactions).
|
|
14914
14997
|
*
|
|
@@ -17314,12 +17397,11 @@ var PostgresQueryParser = class PostgresQueryParser {
|
|
|
17314
17397
|
const columns = Array.isArray(fields) ? fields : [fields];
|
|
17315
17398
|
this.groupColumns.push(...columns);
|
|
17316
17399
|
}
|
|
17317
|
-
/**
|
|
17318
|
-
* Process a raw GROUP BY expression (emitted verbatim, no quoting). Used by
|
|
17319
|
-
* `groupByDate` to add a `date_trunc(...)` bucket to the GROUP BY clause.
|
|
17320
|
-
*/
|
|
17400
|
+
/** Process a raw GROUP BY expression, binding `?` placeholders positionally. */
|
|
17321
17401
|
processGroupByRaw(data) {
|
|
17322
|
-
|
|
17402
|
+
let expression = data.expression;
|
|
17403
|
+
const bindings = data.bindings ?? [];
|
|
17404
|
+
for (const binding of bindings) expression = expression.replace("?", this.addParam(binding));
|
|
17323
17405
|
this.groupRawExpressions.push(expression);
|
|
17324
17406
|
}
|
|
17325
17407
|
/**
|
|
@@ -19322,6 +19404,13 @@ var PostgresDriver = class {
|
|
|
19322
19404
|
if (!this._eventListeners.has(event)) this._eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
19323
19405
|
this._eventListeners.get(event).add(listener);
|
|
19324
19406
|
}
|
|
19407
|
+
/** Remove a previously registered driver event listener. */
|
|
19408
|
+
off(event, listener) {
|
|
19409
|
+
const listeners = this._eventListeners.get(event);
|
|
19410
|
+
if (!listeners) return;
|
|
19411
|
+
listeners.delete(listener);
|
|
19412
|
+
if (listeners.size === 0) this._eventListeners.delete(event);
|
|
19413
|
+
}
|
|
19325
19414
|
/**
|
|
19326
19415
|
* Serialize data for storage in PostgreSQL.
|
|
19327
19416
|
*
|
|
@@ -19814,14 +19903,22 @@ var PostgresDriver = class {
|
|
|
19814
19903
|
* @returns Transaction contract with commit/rollback methods
|
|
19815
19904
|
*/
|
|
19816
19905
|
async beginTransaction(options) {
|
|
19906
|
+
const settings = Object.entries(options?.settings ?? {});
|
|
19907
|
+
for (const [name] of settings) if (!/^[A-Za-z_][A-Za-z0-9_.]*$/.test(name)) throw new Error(`Invalid PostgreSQL transaction setting name: "${name}"`);
|
|
19817
19908
|
const client = await this.pool.connect();
|
|
19909
|
+
let transactionStarted = false;
|
|
19818
19910
|
try {
|
|
19819
19911
|
let beginSql = "BEGIN";
|
|
19820
19912
|
if (options?.isolationLevel) beginSql += ` ISOLATION LEVEL ${options.isolationLevel.toUpperCase()}`;
|
|
19821
19913
|
if (options?.readOnly) beginSql += " READ ONLY";
|
|
19822
19914
|
if (options?.deferrable) beginSql += " DEFERRABLE";
|
|
19823
|
-
await client.query(beginSql);
|
|
19915
|
+
await this.executePostgresQuery(() => client.query(beginSql), beginSql);
|
|
19916
|
+
transactionStarted = true;
|
|
19917
|
+
for (const [name, value] of settings) await this.executePostgresQuery(() => client.query("SELECT set_config($1, $2, true)", [name, String(value)]), "SELECT set_config($1, $2, true)", [name, String(value)]);
|
|
19824
19918
|
} catch (error) {
|
|
19919
|
+
if (transactionStarted) try {
|
|
19920
|
+
await this.executePostgresQuery(() => client.query("ROLLBACK"), "ROLLBACK");
|
|
19921
|
+
} catch {}
|
|
19825
19922
|
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
19826
19923
|
throw error;
|
|
19827
19924
|
}
|
|
@@ -19829,7 +19926,7 @@ var PostgresDriver = class {
|
|
|
19829
19926
|
context: client,
|
|
19830
19927
|
commit: async () => {
|
|
19831
19928
|
try {
|
|
19832
|
-
await client.query("COMMIT");
|
|
19929
|
+
await this.executePostgresQuery(() => client.query("COMMIT"), "COMMIT");
|
|
19833
19930
|
} catch (error) {
|
|
19834
19931
|
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
19835
19932
|
throw error;
|
|
@@ -19838,7 +19935,7 @@ var PostgresDriver = class {
|
|
|
19838
19935
|
},
|
|
19839
19936
|
rollback: async () => {
|
|
19840
19937
|
try {
|
|
19841
|
-
await client.query("ROLLBACK");
|
|
19938
|
+
await this.executePostgresQuery(() => client.query("ROLLBACK"), "ROLLBACK");
|
|
19842
19939
|
} catch (error) {
|
|
19843
19940
|
client.release(error instanceof Error ? error : new Error(String(error)));
|
|
19844
19941
|
throw error;
|
|
@@ -20071,8 +20168,8 @@ var PostgresDriver = class {
|
|
|
20071
20168
|
paramCount: params.length
|
|
20072
20169
|
}
|
|
20073
20170
|
});
|
|
20074
|
-
if (txClient) result = await txClient.query(sql, params);
|
|
20075
|
-
else result = await this.pool.query(sql, params);
|
|
20171
|
+
if (txClient) result = await this.executePostgresQuery(() => txClient.query(sql, params), sql, params);
|
|
20172
|
+
else result = await this.executePostgresQuery(() => this.pool.query(sql, params), sql, params);
|
|
20076
20173
|
if (this.config.logging) {
|
|
20077
20174
|
const duration = (performance.now() - startTime).toFixed(2);
|
|
20078
20175
|
_warlock_js_logger.log.success({
|
|
@@ -20105,6 +20202,47 @@ var PostgresDriver = class {
|
|
|
20105
20202
|
throw error;
|
|
20106
20203
|
}
|
|
20107
20204
|
}
|
|
20205
|
+
/** Execute SQL and emit its public query event when the event is observed. */
|
|
20206
|
+
async executePostgresQuery(execute, sql, bindings = []) {
|
|
20207
|
+
if (!this._eventListeners.get("query")?.size) return execute();
|
|
20208
|
+
const started = performance.now();
|
|
20209
|
+
const startedAt = performance.timeOrigin + started;
|
|
20210
|
+
let result;
|
|
20211
|
+
try {
|
|
20212
|
+
result = await execute();
|
|
20213
|
+
} catch (error) {
|
|
20214
|
+
this.emitQueryEvent({
|
|
20215
|
+
connection: this.name,
|
|
20216
|
+
driver: "postgres",
|
|
20217
|
+
sql,
|
|
20218
|
+
bindings,
|
|
20219
|
+
durationMs: performance.now() - started,
|
|
20220
|
+
startedAt,
|
|
20221
|
+
error
|
|
20222
|
+
});
|
|
20223
|
+
throw error;
|
|
20224
|
+
}
|
|
20225
|
+
const event = {
|
|
20226
|
+
connection: this.name,
|
|
20227
|
+
driver: "postgres",
|
|
20228
|
+
sql,
|
|
20229
|
+
bindings,
|
|
20230
|
+
durationMs: performance.now() - started,
|
|
20231
|
+
startedAt
|
|
20232
|
+
};
|
|
20233
|
+
if (typeof result.rowCount === "number") event.rowCount = result.rowCount;
|
|
20234
|
+
this.emitQueryEvent(event);
|
|
20235
|
+
return result;
|
|
20236
|
+
}
|
|
20237
|
+
/**
|
|
20238
|
+
* Emit a `query` event. An observer is never allowed to fail the query it
|
|
20239
|
+
* observes, so a throwing listener is swallowed here.
|
|
20240
|
+
*/
|
|
20241
|
+
emitQueryEvent(event) {
|
|
20242
|
+
try {
|
|
20243
|
+
this.emit("query", event);
|
|
20244
|
+
} catch {}
|
|
20245
|
+
}
|
|
20108
20246
|
/**
|
|
20109
20247
|
* Emit an event to all registered listeners.
|
|
20110
20248
|
*
|
|
@@ -25312,6 +25450,7 @@ exports.UndefinedWhereValueError = UndefinedWhereValueError;
|
|
|
25312
25450
|
exports.UnsafeFilterError = UnsafeFilterError;
|
|
25313
25451
|
exports.UnsafeRawExpressionError = UnsafeRawExpressionError;
|
|
25314
25452
|
exports.UnsupportedLeanOperationError = UnsupportedLeanOperationError;
|
|
25453
|
+
exports.UnsupportedLockForUpdateError = UnsupportedLockForUpdateError;
|
|
25315
25454
|
exports.UnsupportedQueryOperationError = UnsupportedQueryOperationError;
|
|
25316
25455
|
exports.UnsupportedUpdateOperationError = UnsupportedUpdateOperationError;
|
|
25317
25456
|
exports.afterCommit = afterCommit;
|