@warlock.js/cascade 4.4.0 → 4.6.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 +29 -3
- package/cjs/index.cjs +933 -90
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/database-driver.contract.d.mts +27 -4
- package/esm/contracts/database-driver.contract.d.mts.map +1 -1
- package/esm/contracts/database-id-generator.contract.d.mts +38 -0
- package/esm/contracts/database-id-generator.contract.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/query-builder.contract.d.mts +27 -0
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source.d.mts +21 -1
- package/esm/data-source/data-source.d.mts.map +1 -1
- package/esm/data-source/data-source.mjs +22 -0
- package/esm/data-source/data-source.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +2 -2
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts +98 -48
- package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-id-generator.mjs +153 -59
- package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +25 -0
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +32 -0
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +36 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +80 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +32 -4
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +57 -4
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +42 -0
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +55 -6
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +32 -0
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +47 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +13 -2
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +21 -4
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/types.d.mts +15 -0
- package/esm/drivers/postgres/types.d.mts.map +1 -1
- package/esm/drivers/sql/sql-dialect.contract.d.mts +20 -0
- package/esm/drivers/sql/sql-dialect.contract.d.mts.map +1 -1
- package/esm/expressions/aggregate-expressions.d.mts +71 -34
- package/esm/expressions/aggregate-expressions.d.mts.map +1 -1
- package/esm/expressions/aggregate-expressions.mjs +80 -7
- package/esm/expressions/aggregate-expressions.mjs.map +1 -1
- package/esm/expressions/column-expressions.d.mts +193 -0
- package/esm/expressions/column-expressions.d.mts.map +1 -0
- package/esm/expressions/column-expressions.mjs +152 -0
- package/esm/expressions/column-expressions.mjs.map +1 -0
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/model/methods/write-methods.d.mts +29 -0
- package/esm/model/methods/write-methods.d.mts.map +1 -0
- package/esm/model/methods/write-methods.mjs +164 -2
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +64 -3
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +65 -3
- package/esm/model/model.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +4 -3
- package/esm/writer/database-writer.mjs.map +1 -1
- package/llms-full.txt +106 -7
- package/llms.txt +3 -3
- package/package.json +4 -4
- package/skills/README.md +3 -3
- package/skills/aggregate-data/SKILL.md +43 -3
- package/skills/manage-transactions/SKILL.md +40 -1
- package/skills/perform-atomic-ops/SKILL.md +23 -3
|
@@ -5,40 +5,38 @@ import { MongoDbDriver } from "./mongodb-driver.mjs";
|
|
|
5
5
|
/**
|
|
6
6
|
* MongoDB-specific ID generator for auto-incrementing integer IDs.
|
|
7
7
|
*
|
|
8
|
-
* Maintains a separate collection that tracks the last generated ID for each
|
|
9
|
-
*
|
|
8
|
+
* Maintains a separate collection that tracks the last generated ID for each
|
|
9
|
+
* table, mimicking SQL's `AUTO_INCREMENT` / `SERIAL`.
|
|
10
10
|
*
|
|
11
11
|
* **Collection Structure:**
|
|
12
12
|
* ```json
|
|
13
|
-
* {
|
|
14
|
-
* "collection": "users",
|
|
15
|
-
* "id": 12345
|
|
16
|
-
* }
|
|
13
|
+
* { "collection": "users", "id": 12345 }
|
|
17
14
|
* ```
|
|
18
15
|
*
|
|
19
|
-
* **
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* **Atomicity & concurrency:**
|
|
17
|
+
* - Each id (or block of ids) is reserved with a SINGLE atomic
|
|
18
|
+
* `findOneAndUpdate` on one document; MongoDB serializes concurrent writes to
|
|
19
|
+
* the same document, so concurrent callers receive distinct, non-overlapping
|
|
20
|
+
* ids/blocks.
|
|
21
|
+
* - A unique index on `{ collection: 1 }` is ensured lazily (once per instance)
|
|
22
|
+
* so two concurrent first-time upserts for a brand-new table can't create
|
|
23
|
+
* duplicate counter documents; the loser of that race gets an E11000 and is
|
|
24
|
+
* retried (by then the document exists, so it takes the increment branch).
|
|
25
|
+
*
|
|
26
|
+
* **Transactions — IMPORTANT:** the counter write is its OWN standalone,
|
|
27
|
+
* immediately-durable operation. It does NOT join an ambient
|
|
28
|
+
* `transaction()` session (it calls `findOneAndUpdate` directly without a
|
|
29
|
+
* session). This is intentional and matches SQL `SERIAL` semantics: if the
|
|
30
|
+
* surrounding transaction rolls back, the inserted records are undone but the
|
|
31
|
+
* consumed id(s) are NOT — they remain a gap in the sequence. Do not rely on
|
|
32
|
+
* id reservation being rolled back with a transaction.
|
|
24
33
|
*
|
|
25
34
|
* @example
|
|
26
35
|
* ```typescript
|
|
27
|
-
* const mongoDriver = new MongoDbDriver(config);
|
|
28
36
|
* const idGenerator = new MongoIdGenerator(mongoDriver);
|
|
29
37
|
*
|
|
30
|
-
* const
|
|
31
|
-
*
|
|
32
|
-
* driver: mongoDriver,
|
|
33
|
-
* idGenerator,
|
|
34
|
-
* });
|
|
35
|
-
*
|
|
36
|
-
* // Generate IDs with custom configuration
|
|
37
|
-
* const id = await idGenerator.generateNextId({
|
|
38
|
-
* table: "users",
|
|
39
|
-
* initialId: 1000,
|
|
40
|
-
* incrementIdBy: 1
|
|
41
|
-
* });
|
|
38
|
+
* const id = await idGenerator.generateNextId({ table: "users" }); // one id
|
|
39
|
+
* const ids = await idGenerator.generateNextIds({ table: "users", count: 100 }); // a block
|
|
42
40
|
* ```
|
|
43
41
|
*/
|
|
44
42
|
declare class MongoIdGenerator implements IdGeneratorContract {
|
|
@@ -50,66 +48,118 @@ declare class MongoIdGenerator implements IdGeneratorContract {
|
|
|
50
48
|
* Named "MasterMind" for backward compatibility with legacy Cascade.
|
|
51
49
|
*/
|
|
52
50
|
readonly counterCollection: string;
|
|
51
|
+
/**
|
|
52
|
+
* Memoized "ensure unique index" promise — the index is created at most once
|
|
53
|
+
* per generator instance, before the first reservation completes.
|
|
54
|
+
*/
|
|
55
|
+
private indexEnsured?;
|
|
53
56
|
/**
|
|
54
57
|
* Create a new MongoDB ID generator instance.
|
|
55
58
|
*
|
|
56
59
|
* @param driver - The MongoDB driver instance
|
|
57
60
|
* @param counterCollection - Name of the collection storing ID counters (default: "MasterMind")
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* const idGenerator = new MongoIdGenerator(mongoDriver, "id_counters");
|
|
62
|
-
* ```
|
|
63
61
|
*/
|
|
64
62
|
constructor(driver: MongoDbDriver, counterCollection?: string);
|
|
65
63
|
/**
|
|
66
64
|
* Generate the next ID for a table.
|
|
67
65
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* Reserves a single id via one atomic `findOneAndUpdate` (see the class doc
|
|
67
|
+
* for the atomicity / transaction contract). Equivalent to a block of size 1.
|
|
70
68
|
*
|
|
71
69
|
* @param options - Configuration for ID generation
|
|
72
70
|
* @returns The generated ID
|
|
73
71
|
*
|
|
74
72
|
* @example
|
|
75
73
|
* ```typescript
|
|
76
|
-
*
|
|
77
|
-
* const id = await idGenerator.generateNextId({ table: "users" });
|
|
78
|
-
*
|
|
79
|
-
* // With custom initial ID
|
|
80
|
-
* const id = await idGenerator.generateNextId({
|
|
81
|
-
* table: "products",
|
|
82
|
-
* initialId: 1000,
|
|
83
|
-
* incrementIdBy: 1
|
|
84
|
-
* });
|
|
74
|
+
* const id = await idGenerator.generateNextId({ table: "users", initialId: 1000 });
|
|
85
75
|
* ```
|
|
86
76
|
*/
|
|
87
77
|
generateNextId(options: GenerateIdOptions): Promise<number>;
|
|
88
78
|
/**
|
|
89
|
-
*
|
|
79
|
+
* Reserve a contiguous block of `count` ids in a single atomic operation.
|
|
90
80
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
81
|
+
* Advances the counter by `count * incrementIdBy` in one `findOneAndUpdate`
|
|
82
|
+
* (so the stored last id becomes the block's last id) and returns the block
|
|
83
|
+
* in ascending order. See the class doc for the non-transactional contract.
|
|
84
|
+
*
|
|
85
|
+
* @param options - `GenerateIdOptions` plus the block `count`
|
|
86
|
+
* @returns The reserved ids in ascending order (length `count`)
|
|
93
87
|
*
|
|
94
88
|
* @example
|
|
95
89
|
* ```typescript
|
|
96
|
-
* const
|
|
97
|
-
*
|
|
90
|
+
* const ids = await idGenerator.generateNextIds({ table: "users", count: 100 });
|
|
91
|
+
* // ids[0] is the first id, ids[99] the last; getLastId("users") === ids[99]
|
|
98
92
|
* ```
|
|
99
93
|
*/
|
|
94
|
+
generateNextIds(options: GenerateIdOptions & {
|
|
95
|
+
count: number;
|
|
96
|
+
}): Promise<number[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Atomically advance the counter for `table` by a whole block and return the
|
|
99
|
+
* block's LAST id.
|
|
100
|
+
*
|
|
101
|
+
* One `findOneAndUpdate` with an aggregation pipeline:
|
|
102
|
+
* - **Cold start** (counter field missing or null): seed `initialId + (count - 1) * incrementIdBy`
|
|
103
|
+
* so the FIRST id of the block equals `initialId` (the `count - 1` here vs
|
|
104
|
+
* `count` in the steady-state branch is deliberate — the counter stores the
|
|
105
|
+
* last-issued id and the very first id must be exactly `initialId`).
|
|
106
|
+
* - **Steady state**: add `count * incrementIdBy` to the stored counter.
|
|
107
|
+
*
|
|
108
|
+
* Retries on a duplicate-key error from the cold-start upsert race (see
|
|
109
|
+
* {@link isDuplicateKeyError}).
|
|
110
|
+
*
|
|
111
|
+
* @param table - The table/collection the block is for
|
|
112
|
+
* @param initialId - The first id ever issued for this table
|
|
113
|
+
* @param incrementIdBy - The fixed stride between ids
|
|
114
|
+
* @param count - Block size (`>= 1`)
|
|
115
|
+
* @returns The last id of the reserved block
|
|
116
|
+
*/
|
|
117
|
+
private reserveBlock;
|
|
118
|
+
/**
|
|
119
|
+
* Run `operation`, retrying on a duplicate-key (E11000) error up to
|
|
120
|
+
* {@link MAX_RESERVE_ATTEMPTS} times. Only the cold-start upsert race throws
|
|
121
|
+
* E11000 (once the unique index exists); on retry the counter document
|
|
122
|
+
* already exists, so the increment branch runs and succeeds. Any other error
|
|
123
|
+
* is rethrown immediately.
|
|
124
|
+
*
|
|
125
|
+
* @param operation - The reservation to run
|
|
126
|
+
* @returns The operation's result
|
|
127
|
+
*/
|
|
128
|
+
private withDuplicateKeyRetry;
|
|
129
|
+
/**
|
|
130
|
+
* Ensure the unique index on `{ collection: 1 }` exists (once per instance).
|
|
131
|
+
*
|
|
132
|
+
* Best-effort: a pre-existing duplicate counter document (created before this
|
|
133
|
+
* index existed) makes index creation fail with E11000. We swallow that
|
|
134
|
+
* rather than block app boot — without the index the generator degrades to
|
|
135
|
+
* its prior (un-indexed) behavior, and the retry path becomes a no-op since
|
|
136
|
+
* E11000 can no longer be raised by the upsert.
|
|
137
|
+
*/
|
|
138
|
+
private ensureIndexes;
|
|
139
|
+
/**
|
|
140
|
+
* Create the unique index on the counter collection's `collection` field.
|
|
141
|
+
* Failures are swallowed (see {@link ensureIndexes}).
|
|
142
|
+
*/
|
|
143
|
+
private createCounterIndex;
|
|
144
|
+
/**
|
|
145
|
+
* Get the last generated ID for a table.
|
|
146
|
+
*
|
|
147
|
+
* @param table - The table/collection name
|
|
148
|
+
* @returns The last generated ID, or 0 if none exists
|
|
149
|
+
*/
|
|
100
150
|
getLastId(table: string): Promise<number>;
|
|
101
151
|
/**
|
|
102
152
|
* Set the last ID for a table.
|
|
103
153
|
*
|
|
104
|
-
* Creates or updates the counter document for the specified table.
|
|
105
|
-
*
|
|
154
|
+
* Creates or updates the counter document for the specified table. Useful for
|
|
155
|
+
* seeding or resetting ID sequences.
|
|
106
156
|
*
|
|
107
157
|
* @param table - The table/collection name
|
|
108
158
|
* @param id - The ID to set as the last generated ID
|
|
109
159
|
*
|
|
110
160
|
* @example
|
|
111
161
|
* ```typescript
|
|
112
|
-
* // Reset user IDs to start from 1000
|
|
162
|
+
* // Reset user IDs to start from 1000 (next generated id is 1001)
|
|
113
163
|
* await idGenerator.setLastId("users", 1000);
|
|
114
164
|
* ```
|
|
115
165
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb-id-generator.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-id-generator.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"mongodb-id-generator.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-id-generator.ts"],"mappings":";;;;;;;AA4DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,mBAAA;EAAA,iBAsBpB,MAAA;EA4MoB;;;;;;EAAA,SA3NvB,iBAAA;;;;;UAMR,YAAA;;;;;;;cASW,MAAA,EAAQ,aAAA,EACzB,iBAAA;;;;;;;;;;;;;;;EAqBW,cAAA,CAAe,OAAA,EAAS,iBAAA,GAAoB,OAAA;;;;;;;;;;;;;;;;;EAwB5C,eAAA,CACX,OAAA,EAAS,iBAAA;IAAsB,KAAA;EAAA,IAC9B,OAAA;;;;;;;;;;;;;;;;;;;;;UAmCW,YAAA;;;;;;;;;;;UA8DA,qBAAA;;;;;;;;;;UA6BA,aAAA;;;;;UAYA,kBAAA;;;;;;;EAkBD,SAAA,CAAU,KAAA,WAAgB,OAAA;;;;;;;;;;;;;;;;EAqB1B,SAAA,CAAU,KAAA,UAAe,EAAA,WAAa,OAAA;AAAA"}
|
|
@@ -1,41 +1,52 @@
|
|
|
1
1
|
//#region ../@warlock.js/cascade/src/drivers/mongodb/mongodb-id-generator.ts
|
|
2
|
+
/** Number of times a reservation retries after a duplicate-key (E11000) error. */
|
|
3
|
+
const MAX_RESERVE_ATTEMPTS = 3;
|
|
4
|
+
/** MongoDB duplicate-key error code. */
|
|
5
|
+
const DUPLICATE_KEY_ERROR_CODE = 11e3;
|
|
6
|
+
/**
|
|
7
|
+
* Is `error` a MongoDB duplicate-key (E11000) error?
|
|
8
|
+
*
|
|
9
|
+
* Raised when two concurrent first-time upserts race to create the same
|
|
10
|
+
* counter document once the unique index on `{ collection: 1 }` exists.
|
|
11
|
+
*/
|
|
12
|
+
function isDuplicateKeyError(error) {
|
|
13
|
+
return typeof error === "object" && error !== null && error.code === DUPLICATE_KEY_ERROR_CODE;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* MongoDB-specific ID generator for auto-incrementing integer IDs.
|
|
4
17
|
*
|
|
5
|
-
* Maintains a separate collection that tracks the last generated ID for each
|
|
6
|
-
*
|
|
18
|
+
* Maintains a separate collection that tracks the last generated ID for each
|
|
19
|
+
* table, mimicking SQL's `AUTO_INCREMENT` / `SERIAL`.
|
|
7
20
|
*
|
|
8
21
|
* **Collection Structure:**
|
|
9
22
|
* ```json
|
|
10
|
-
* {
|
|
11
|
-
* "collection": "users",
|
|
12
|
-
* "id": 12345
|
|
13
|
-
* }
|
|
23
|
+
* { "collection": "users", "id": 12345 }
|
|
14
24
|
* ```
|
|
15
25
|
*
|
|
16
|
-
* **
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
26
|
+
* **Atomicity & concurrency:**
|
|
27
|
+
* - Each id (or block of ids) is reserved with a SINGLE atomic
|
|
28
|
+
* `findOneAndUpdate` on one document; MongoDB serializes concurrent writes to
|
|
29
|
+
* the same document, so concurrent callers receive distinct, non-overlapping
|
|
30
|
+
* ids/blocks.
|
|
31
|
+
* - A unique index on `{ collection: 1 }` is ensured lazily (once per instance)
|
|
32
|
+
* so two concurrent first-time upserts for a brand-new table can't create
|
|
33
|
+
* duplicate counter documents; the loser of that race gets an E11000 and is
|
|
34
|
+
* retried (by then the document exists, so it takes the increment branch).
|
|
35
|
+
*
|
|
36
|
+
* **Transactions — IMPORTANT:** the counter write is its OWN standalone,
|
|
37
|
+
* immediately-durable operation. It does NOT join an ambient
|
|
38
|
+
* `transaction()` session (it calls `findOneAndUpdate` directly without a
|
|
39
|
+
* session). This is intentional and matches SQL `SERIAL` semantics: if the
|
|
40
|
+
* surrounding transaction rolls back, the inserted records are undone but the
|
|
41
|
+
* consumed id(s) are NOT — they remain a gap in the sequence. Do not rely on
|
|
42
|
+
* id reservation being rolled back with a transaction.
|
|
21
43
|
*
|
|
22
44
|
* @example
|
|
23
45
|
* ```typescript
|
|
24
|
-
* const mongoDriver = new MongoDbDriver(config);
|
|
25
46
|
* const idGenerator = new MongoIdGenerator(mongoDriver);
|
|
26
47
|
*
|
|
27
|
-
* const
|
|
28
|
-
*
|
|
29
|
-
* driver: mongoDriver,
|
|
30
|
-
* idGenerator,
|
|
31
|
-
* });
|
|
32
|
-
*
|
|
33
|
-
* // Generate IDs with custom configuration
|
|
34
|
-
* const id = await idGenerator.generateNextId({
|
|
35
|
-
* table: "users",
|
|
36
|
-
* initialId: 1000,
|
|
37
|
-
* incrementIdBy: 1
|
|
38
|
-
* });
|
|
48
|
+
* const id = await idGenerator.generateNextId({ table: "users" }); // one id
|
|
49
|
+
* const ids = await idGenerator.generateNextIds({ table: "users", count: 100 }); // a block
|
|
39
50
|
* ```
|
|
40
51
|
*/
|
|
41
52
|
var MongoIdGenerator = class {
|
|
@@ -48,15 +59,15 @@ var MongoIdGenerator = class {
|
|
|
48
59
|
*/
|
|
49
60
|
counterCollection = "MasterMind";
|
|
50
61
|
/**
|
|
62
|
+
* Memoized "ensure unique index" promise — the index is created at most once
|
|
63
|
+
* per generator instance, before the first reservation completes.
|
|
64
|
+
*/
|
|
65
|
+
indexEnsured;
|
|
66
|
+
/**
|
|
51
67
|
* Create a new MongoDB ID generator instance.
|
|
52
68
|
*
|
|
53
69
|
* @param driver - The MongoDB driver instance
|
|
54
70
|
* @param counterCollection - Name of the collection storing ID counters (default: "MasterMind")
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```typescript
|
|
58
|
-
* const idGenerator = new MongoIdGenerator(mongoDriver, "id_counters");
|
|
59
|
-
* ```
|
|
60
71
|
*/
|
|
61
72
|
constructor(driver, counterCollection) {
|
|
62
73
|
this.driver = driver;
|
|
@@ -65,66 +76,149 @@ var MongoIdGenerator = class {
|
|
|
65
76
|
/**
|
|
66
77
|
* Generate the next ID for a table.
|
|
67
78
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
79
|
+
* Reserves a single id via one atomic `findOneAndUpdate` (see the class doc
|
|
80
|
+
* for the atomicity / transaction contract). Equivalent to a block of size 1.
|
|
70
81
|
*
|
|
71
82
|
* @param options - Configuration for ID generation
|
|
72
83
|
* @returns The generated ID
|
|
73
84
|
*
|
|
74
85
|
* @example
|
|
75
86
|
* ```typescript
|
|
76
|
-
*
|
|
77
|
-
* const id = await idGenerator.generateNextId({ table: "users" });
|
|
78
|
-
*
|
|
79
|
-
* // With custom initial ID
|
|
80
|
-
* const id = await idGenerator.generateNextId({
|
|
81
|
-
* table: "products",
|
|
82
|
-
* initialId: 1000,
|
|
83
|
-
* incrementIdBy: 1
|
|
84
|
-
* });
|
|
87
|
+
* const id = await idGenerator.generateNextId({ table: "users", initialId: 1000 });
|
|
85
88
|
* ```
|
|
86
89
|
*/
|
|
87
90
|
async generateNextId(options) {
|
|
88
91
|
const { table, initialId = 1, incrementIdBy = 1 } = options;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if: { $or: [{ $eq: ["$id", null] }, { $not: "$id" }] },
|
|
92
|
-
then: initialId,
|
|
93
|
-
else: { $add: ["$id", incrementIdBy] }
|
|
94
|
-
} },
|
|
95
|
-
collection: table
|
|
96
|
-
} }], {
|
|
97
|
-
upsert: true,
|
|
98
|
-
returnDocument: "after"
|
|
99
|
-
}))?.id ?? initialId;
|
|
92
|
+
await this.ensureIndexes();
|
|
93
|
+
return await this.reserveBlock(table, initialId, incrementIdBy, 1);
|
|
100
94
|
}
|
|
101
95
|
/**
|
|
102
|
-
*
|
|
96
|
+
* Reserve a contiguous block of `count` ids in a single atomic operation.
|
|
103
97
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
98
|
+
* Advances the counter by `count * incrementIdBy` in one `findOneAndUpdate`
|
|
99
|
+
* (so the stored last id becomes the block's last id) and returns the block
|
|
100
|
+
* in ascending order. See the class doc for the non-transactional contract.
|
|
101
|
+
*
|
|
102
|
+
* @param options - `GenerateIdOptions` plus the block `count`
|
|
103
|
+
* @returns The reserved ids in ascending order (length `count`)
|
|
106
104
|
*
|
|
107
105
|
* @example
|
|
108
106
|
* ```typescript
|
|
109
|
-
* const
|
|
110
|
-
*
|
|
107
|
+
* const ids = await idGenerator.generateNextIds({ table: "users", count: 100 });
|
|
108
|
+
* // ids[0] is the first id, ids[99] the last; getLastId("users") === ids[99]
|
|
111
109
|
* ```
|
|
112
110
|
*/
|
|
111
|
+
async generateNextIds(options) {
|
|
112
|
+
const { table, initialId = 1, incrementIdBy = 1, count } = options;
|
|
113
|
+
if (count <= 0) return [];
|
|
114
|
+
await this.ensureIndexes();
|
|
115
|
+
const firstId = await this.reserveBlock(table, initialId, incrementIdBy, count) - (count - 1) * incrementIdBy;
|
|
116
|
+
return Array.from({ length: count }, (_, index) => firstId + index * incrementIdBy);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Atomically advance the counter for `table` by a whole block and return the
|
|
120
|
+
* block's LAST id.
|
|
121
|
+
*
|
|
122
|
+
* One `findOneAndUpdate` with an aggregation pipeline:
|
|
123
|
+
* - **Cold start** (counter field missing or null): seed `initialId + (count - 1) * incrementIdBy`
|
|
124
|
+
* so the FIRST id of the block equals `initialId` (the `count - 1` here vs
|
|
125
|
+
* `count` in the steady-state branch is deliberate — the counter stores the
|
|
126
|
+
* last-issued id and the very first id must be exactly `initialId`).
|
|
127
|
+
* - **Steady state**: add `count * incrementIdBy` to the stored counter.
|
|
128
|
+
*
|
|
129
|
+
* Retries on a duplicate-key error from the cold-start upsert race (see
|
|
130
|
+
* {@link isDuplicateKeyError}).
|
|
131
|
+
*
|
|
132
|
+
* @param table - The table/collection the block is for
|
|
133
|
+
* @param initialId - The first id ever issued for this table
|
|
134
|
+
* @param incrementIdBy - The fixed stride between ids
|
|
135
|
+
* @param count - Block size (`>= 1`)
|
|
136
|
+
* @returns The last id of the reserved block
|
|
137
|
+
*/
|
|
138
|
+
async reserveBlock(table, initialId, incrementIdBy, count) {
|
|
139
|
+
const run = async () => {
|
|
140
|
+
const lastId = (await this.driver.getDatabase().collection(this.counterCollection).findOneAndUpdate({ collection: table }, [{ $set: {
|
|
141
|
+
id: { $cond: {
|
|
142
|
+
if: { $or: [{ $eq: [{ $type: "$id" }, "missing"] }, { $eq: ["$id", null] }] },
|
|
143
|
+
then: initialId + (count - 1) * incrementIdBy,
|
|
144
|
+
else: { $add: ["$id", count * incrementIdBy] }
|
|
145
|
+
} },
|
|
146
|
+
collection: table
|
|
147
|
+
} }], {
|
|
148
|
+
upsert: true,
|
|
149
|
+
returnDocument: "after"
|
|
150
|
+
}))?.id;
|
|
151
|
+
if (typeof lastId !== "number") throw new Error(`Failed to reserve an id block for "${table}": counter returned no id.`);
|
|
152
|
+
return lastId;
|
|
153
|
+
};
|
|
154
|
+
return await this.withDuplicateKeyRetry(run);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Run `operation`, retrying on a duplicate-key (E11000) error up to
|
|
158
|
+
* {@link MAX_RESERVE_ATTEMPTS} times. Only the cold-start upsert race throws
|
|
159
|
+
* E11000 (once the unique index exists); on retry the counter document
|
|
160
|
+
* already exists, so the increment branch runs and succeeds. Any other error
|
|
161
|
+
* is rethrown immediately.
|
|
162
|
+
*
|
|
163
|
+
* @param operation - The reservation to run
|
|
164
|
+
* @returns The operation's result
|
|
165
|
+
*/
|
|
166
|
+
async withDuplicateKeyRetry(operation) {
|
|
167
|
+
let lastError;
|
|
168
|
+
for (let attempt = 1; attempt <= MAX_RESERVE_ATTEMPTS; attempt++) try {
|
|
169
|
+
return await operation();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (!isDuplicateKeyError(error)) throw error;
|
|
172
|
+
lastError = error;
|
|
173
|
+
}
|
|
174
|
+
throw lastError;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Ensure the unique index on `{ collection: 1 }` exists (once per instance).
|
|
178
|
+
*
|
|
179
|
+
* Best-effort: a pre-existing duplicate counter document (created before this
|
|
180
|
+
* index existed) makes index creation fail with E11000. We swallow that
|
|
181
|
+
* rather than block app boot — without the index the generator degrades to
|
|
182
|
+
* its prior (un-indexed) behavior, and the retry path becomes a no-op since
|
|
183
|
+
* E11000 can no longer be raised by the upsert.
|
|
184
|
+
*/
|
|
185
|
+
async ensureIndexes() {
|
|
186
|
+
if (!this.indexEnsured) this.indexEnsured = this.createCounterIndex();
|
|
187
|
+
return await this.indexEnsured;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Create the unique index on the counter collection's `collection` field.
|
|
191
|
+
* Failures are swallowed (see {@link ensureIndexes}).
|
|
192
|
+
*/
|
|
193
|
+
async createCounterIndex() {
|
|
194
|
+
try {
|
|
195
|
+
await this.driver.getDatabase().collection(this.counterCollection).createIndex({ collection: 1 }, {
|
|
196
|
+
unique: true,
|
|
197
|
+
name: "collection_unique"
|
|
198
|
+
});
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get the last generated ID for a table.
|
|
203
|
+
*
|
|
204
|
+
* @param table - The table/collection name
|
|
205
|
+
* @returns The last generated ID, or 0 if none exists
|
|
206
|
+
*/
|
|
113
207
|
async getLastId(table) {
|
|
114
208
|
return (await this.driver.queryBuilder(this.counterCollection).where("collection", table).first())?.id ?? 0;
|
|
115
209
|
}
|
|
116
210
|
/**
|
|
117
211
|
* Set the last ID for a table.
|
|
118
212
|
*
|
|
119
|
-
* Creates or updates the counter document for the specified table.
|
|
120
|
-
*
|
|
213
|
+
* Creates or updates the counter document for the specified table. Useful for
|
|
214
|
+
* seeding or resetting ID sequences.
|
|
121
215
|
*
|
|
122
216
|
* @param table - The table/collection name
|
|
123
217
|
* @param id - The ID to set as the last generated ID
|
|
124
218
|
*
|
|
125
219
|
* @example
|
|
126
220
|
* ```typescript
|
|
127
|
-
* // Reset user IDs to start from 1000
|
|
221
|
+
* // Reset user IDs to start from 1000 (next generated id is 1001)
|
|
128
222
|
* await idGenerator.setLastId("users", 1000);
|
|
129
223
|
* ```
|
|
130
224
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb-id-generator.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-id-generator.ts"],"sourcesContent":["import type { GenerateIdOptions, IdGeneratorContract } from \"../../contracts\";\nimport type { MongoDbDriver } from \"./mongodb-driver\";\n\n/**\n * MongoDB-specific ID generator for auto-incrementing integer IDs.\n *\n * Maintains a separate collection that tracks the last generated ID for each table.\n * Generates auto-incrementing IDs similar to SQL's AUTO_INCREMENT feature.\n *\n * **Collection Structure:**\n * ```json\n * {\n * \"collection\": \"users\",\n * \"id\": 12345\n * }\n * ```\n *\n * **Features:**\n * - Atomic ID generation using findOneAndUpdate with aggregation pipeline\n * - Automatic transaction support (driver handles session context)\n * - Configurable initial ID and increment values\n * - Thread-safe and concurrent-safe\n *\n * @example\n * ```typescript\n * const mongoDriver = new MongoDbDriver(config);\n * const idGenerator = new MongoIdGenerator(mongoDriver);\n *\n * const dataSource = new DataSource({\n * name: \"primary\",\n * driver: mongoDriver,\n * idGenerator,\n * });\n *\n * // Generate IDs with custom configuration\n * const id = await idGenerator.generateNextId({\n * table: \"users\",\n * initialId: 1000,\n * incrementIdBy: 1\n * });\n * ```\n */\nexport class MongoIdGenerator implements IdGeneratorContract {\n /**\n * The collection name that stores ID counters.\n * Each document tracks the last ID for a specific table.\n *\n * Named \"MasterMind\" for backward compatibility with legacy Cascade.\n */\n public readonly counterCollection: string = \"MasterMind\";\n\n /**\n * Create a new MongoDB ID generator instance.\n *\n * @param driver - The MongoDB driver instance\n * @param counterCollection - Name of the collection storing ID counters (default: \"MasterMind\")\n *\n * @example\n * ```typescript\n * const idGenerator = new MongoIdGenerator(mongoDriver, \"id_counters\");\n * ```\n */\n public constructor(\n private readonly driver: MongoDbDriver,\n counterCollection?: string,\n ) {\n if (counterCollection) {\n this.counterCollection = counterCollection;\n }\n }\n\n /**\n * Generate the next ID for a table.\n *\n * Uses atomic findOneAndUpdate with aggregation pipeline to ensure uniqueness\n * even in concurrent scenarios. Automatically participates in active transactions.\n *\n * @param options - Configuration for ID generation\n * @returns The generated ID\n *\n * @example\n * ```typescript\n * // Simple usage\n * const id = await idGenerator.generateNextId({ table: \"users\" });\n *\n * // With custom initial ID\n * const id = await idGenerator.generateNextId({\n * table: \"products\",\n * initialId: 1000,\n * incrementIdBy: 1\n * });\n * ```\n */\n public async generateNextId(options: GenerateIdOptions): Promise<number> {\n const { table, initialId = 1, incrementIdBy = 1 } = options;\n\n // Get the MongoDB database instance\n const database = this.driver.getDatabase();\n const collection = database.collection(this.counterCollection);\n\n // Use atomic findOneAndUpdate with aggregation pipeline\n // The driver's withSession method will automatically attach the session if in a transaction\n const result = await collection.findOneAndUpdate(\n { collection: table },\n [\n {\n $set: {\n id: {\n $cond: {\n if: { $or: [{ $eq: [\"$id\", null] }, { $not: \"$id\" }] },\n then: initialId,\n else: { $add: [\"$id\", incrementIdBy] },\n },\n },\n collection: table,\n },\n },\n ],\n {\n upsert: true,\n returnDocument: \"after\",\n },\n );\n\n return result?.id ?? initialId;\n }\n\n /**\n * Get the last generated ID for a table.\n *\n * @param table - The table/collection name\n * @returns The last generated ID, or 0 if none exists\n *\n * @example\n * ```typescript\n * const lastId = await idGenerator.getLastId(\"users\");\n * console.log(`Last user ID: ${lastId}`);\n * ```\n */\n public async getLastId(table: string): Promise<number> {\n const query = this.driver.queryBuilder(this.counterCollection);\n const doc = (await query.where(\"collection\", table).first()) as Record<string, unknown> | null;\n return (doc?.id as number) ?? 0;\n }\n\n /**\n * Set the last ID for a table.\n *\n * Creates or updates the counter document for the specified table.\n * Useful for seeding or resetting ID sequences.\n *\n * @param table - The table/collection name\n * @param id - The ID to set as the last generated ID\n *\n * @example\n * ```typescript\n * // Reset user IDs to start from 1000\n * await idGenerator.setLastId(\"users\", 1000);\n * ```\n */\n public async setLastId(table: string, id: number): Promise<void> {\n await this.driver.update(\n this.counterCollection,\n { collection: table },\n { $set: { id, collection: table } },\n { upsert: true },\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,mBAAb,MAA6D;CAqBxC;;;;;;;CAdnB,AAAgB,oBAA4B;;;;;;;;;;;;CAa5C,AAAO,YACL,AAAiB,QACjB,mBACA;EAFiB;EAGjB,IAAI,mBACF,KAAK,oBAAoB;CAE7B;;;;;;;;;;;;;;;;;;;;;;;CAwBA,MAAa,eAAe,SAA6C;EACvE,MAAM,EAAE,OAAO,YAAY,GAAG,gBAAgB,MAAM;EA8BpD,QAAO,MA3BU,KAAK,OAAO,YACH,CAAC,CAAC,WAAW,KAAK,iBAId,CAAC,CAAC,iBAC9B,EAAE,YAAY,MAAM,GACpB,CACE,EACE,MAAM;GACJ,IAAI,EACF,OAAO;IACL,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC,EAAE;IACrD,MAAM;IACN,MAAM,EAAE,MAAM,CAAC,OAAO,aAAa,EAAE;GACvC,EACF;GACA,YAAY;EACd,EACF,CACF,GACA;GACE,QAAQ;GACR,gBAAgB;EAClB,CACF,EAEa,EAAE,MAAM;CACvB;;;;;;;;;;;;;CAcA,MAAa,UAAU,OAAgC;EAGrD,QAAQ,MAFM,KAAK,OAAO,aAAa,KAAK,iBACrB,CAAC,CAAC,MAAM,cAAc,KAAK,CAAC,CAAC,MAAM,EAC/C,EAAE,MAAiB;CAChC;;;;;;;;;;;;;;;;CAiBA,MAAa,UAAU,OAAe,IAA2B;EAC/D,MAAM,KAAK,OAAO,OAChB,KAAK,mBACL,EAAE,YAAY,MAAM,GACpB,EAAE,MAAM;GAAE;GAAI,YAAY;EAAM,EAAE,GAClC,EAAE,QAAQ,KAAK,CACjB;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"mongodb-id-generator.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-id-generator.ts"],"sourcesContent":["import type { GenerateIdOptions, IdGeneratorContract } from \"../../contracts\";\nimport type { MongoDbDriver } from \"./mongodb-driver\";\n\n/** Number of times a reservation retries after a duplicate-key (E11000) error. */\nconst MAX_RESERVE_ATTEMPTS = 3;\n\n/** MongoDB duplicate-key error code. */\nconst DUPLICATE_KEY_ERROR_CODE = 11000;\n\n/**\n * Is `error` a MongoDB duplicate-key (E11000) error?\n *\n * Raised when two concurrent first-time upserts race to create the same\n * counter document once the unique index on `{ collection: 1 }` exists.\n */\nfunction isDuplicateKeyError(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as { code?: number }).code === DUPLICATE_KEY_ERROR_CODE\n );\n}\n\n/**\n * MongoDB-specific ID generator for auto-incrementing integer IDs.\n *\n * Maintains a separate collection that tracks the last generated ID for each\n * table, mimicking SQL's `AUTO_INCREMENT` / `SERIAL`.\n *\n * **Collection Structure:**\n * ```json\n * { \"collection\": \"users\", \"id\": 12345 }\n * ```\n *\n * **Atomicity & concurrency:**\n * - Each id (or block of ids) is reserved with a SINGLE atomic\n * `findOneAndUpdate` on one document; MongoDB serializes concurrent writes to\n * the same document, so concurrent callers receive distinct, non-overlapping\n * ids/blocks.\n * - A unique index on `{ collection: 1 }` is ensured lazily (once per instance)\n * so two concurrent first-time upserts for a brand-new table can't create\n * duplicate counter documents; the loser of that race gets an E11000 and is\n * retried (by then the document exists, so it takes the increment branch).\n *\n * **Transactions — IMPORTANT:** the counter write is its OWN standalone,\n * immediately-durable operation. It does NOT join an ambient\n * `transaction()` session (it calls `findOneAndUpdate` directly without a\n * session). This is intentional and matches SQL `SERIAL` semantics: if the\n * surrounding transaction rolls back, the inserted records are undone but the\n * consumed id(s) are NOT — they remain a gap in the sequence. Do not rely on\n * id reservation being rolled back with a transaction.\n *\n * @example\n * ```typescript\n * const idGenerator = new MongoIdGenerator(mongoDriver);\n *\n * const id = await idGenerator.generateNextId({ table: \"users\" }); // one id\n * const ids = await idGenerator.generateNextIds({ table: \"users\", count: 100 }); // a block\n * ```\n */\nexport class MongoIdGenerator implements IdGeneratorContract {\n /**\n * The collection name that stores ID counters.\n * Each document tracks the last ID for a specific table.\n *\n * Named \"MasterMind\" for backward compatibility with legacy Cascade.\n */\n public readonly counterCollection: string = \"MasterMind\";\n\n /**\n * Memoized \"ensure unique index\" promise — the index is created at most once\n * per generator instance, before the first reservation completes.\n */\n private indexEnsured?: Promise<void>;\n\n /**\n * Create a new MongoDB ID generator instance.\n *\n * @param driver - The MongoDB driver instance\n * @param counterCollection - Name of the collection storing ID counters (default: \"MasterMind\")\n */\n public constructor(\n private readonly driver: MongoDbDriver,\n counterCollection?: string,\n ) {\n if (counterCollection) {\n this.counterCollection = counterCollection;\n }\n }\n\n /**\n * Generate the next ID for a table.\n *\n * Reserves a single id via one atomic `findOneAndUpdate` (see the class doc\n * for the atomicity / transaction contract). Equivalent to a block of size 1.\n *\n * @param options - Configuration for ID generation\n * @returns The generated ID\n *\n * @example\n * ```typescript\n * const id = await idGenerator.generateNextId({ table: \"users\", initialId: 1000 });\n * ```\n */\n public async generateNextId(options: GenerateIdOptions): Promise<number> {\n const { table, initialId = 1, incrementIdBy = 1 } = options;\n\n await this.ensureIndexes();\n\n return await this.reserveBlock(table, initialId, incrementIdBy, 1);\n }\n\n /**\n * Reserve a contiguous block of `count` ids in a single atomic operation.\n *\n * Advances the counter by `count * incrementIdBy` in one `findOneAndUpdate`\n * (so the stored last id becomes the block's last id) and returns the block\n * in ascending order. See the class doc for the non-transactional contract.\n *\n * @param options - `GenerateIdOptions` plus the block `count`\n * @returns The reserved ids in ascending order (length `count`)\n *\n * @example\n * ```typescript\n * const ids = await idGenerator.generateNextIds({ table: \"users\", count: 100 });\n * // ids[0] is the first id, ids[99] the last; getLastId(\"users\") === ids[99]\n * ```\n */\n public async generateNextIds(\n options: GenerateIdOptions & { count: number },\n ): Promise<number[]> {\n const { table, initialId = 1, incrementIdBy = 1, count } = options;\n\n if (count <= 0) {\n return [];\n }\n\n await this.ensureIndexes();\n\n const lastId = await this.reserveBlock(table, initialId, incrementIdBy, count);\n const firstId = lastId - (count - 1) * incrementIdBy;\n\n return Array.from({ length: count }, (_, index) => firstId + index * incrementIdBy);\n }\n\n /**\n * Atomically advance the counter for `table` by a whole block and return the\n * block's LAST id.\n *\n * One `findOneAndUpdate` with an aggregation pipeline:\n * - **Cold start** (counter field missing or null): seed `initialId + (count - 1) * incrementIdBy`\n * so the FIRST id of the block equals `initialId` (the `count - 1` here vs\n * `count` in the steady-state branch is deliberate — the counter stores the\n * last-issued id and the very first id must be exactly `initialId`).\n * - **Steady state**: add `count * incrementIdBy` to the stored counter.\n *\n * Retries on a duplicate-key error from the cold-start upsert race (see\n * {@link isDuplicateKeyError}).\n *\n * @param table - The table/collection the block is for\n * @param initialId - The first id ever issued for this table\n * @param incrementIdBy - The fixed stride between ids\n * @param count - Block size (`>= 1`)\n * @returns The last id of the reserved block\n */\n private async reserveBlock(\n table: string,\n initialId: number,\n incrementIdBy: number,\n count: number,\n ): Promise<number> {\n const run = async (): Promise<number> => {\n const database = this.driver.getDatabase();\n const collection = database.collection(this.counterCollection);\n\n const result = await collection.findOneAndUpdate(\n { collection: table },\n [\n {\n $set: {\n id: {\n $cond: {\n // Cold start = the counter field is absent or null. Tested by\n // existence ($type), NOT truthiness — a legitimately stored\n // last-id of 0 (initialId: 0) must take the increment branch,\n // not be mistaken for an unset counter and re-issued.\n if: {\n $or: [{ $eq: [{ $type: \"$id\" }, \"missing\"] }, { $eq: [\"$id\", null] }],\n },\n then: initialId + (count - 1) * incrementIdBy,\n else: { $add: [\"$id\", count * incrementIdBy] },\n },\n },\n collection: table,\n },\n },\n ],\n {\n upsert: true,\n returnDocument: \"after\",\n },\n );\n\n const lastId = result?.id;\n\n if (typeof lastId !== \"number\") {\n throw new Error(\n `Failed to reserve an id block for \"${table}\": counter returned no id.`,\n );\n }\n\n return lastId;\n };\n\n return await this.withDuplicateKeyRetry(run);\n }\n\n /**\n * Run `operation`, retrying on a duplicate-key (E11000) error up to\n * {@link MAX_RESERVE_ATTEMPTS} times. Only the cold-start upsert race throws\n * E11000 (once the unique index exists); on retry the counter document\n * already exists, so the increment branch runs and succeeds. Any other error\n * is rethrown immediately.\n *\n * @param operation - The reservation to run\n * @returns The operation's result\n */\n private async withDuplicateKeyRetry<TResult>(\n operation: () => Promise<TResult>,\n ): Promise<TResult> {\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= MAX_RESERVE_ATTEMPTS; attempt++) {\n try {\n return await operation();\n } catch (error) {\n if (!isDuplicateKeyError(error)) {\n throw error;\n }\n\n lastError = error;\n }\n }\n\n throw lastError;\n }\n\n /**\n * Ensure the unique index on `{ collection: 1 }` exists (once per instance).\n *\n * Best-effort: a pre-existing duplicate counter document (created before this\n * index existed) makes index creation fail with E11000. We swallow that\n * rather than block app boot — without the index the generator degrades to\n * its prior (un-indexed) behavior, and the retry path becomes a no-op since\n * E11000 can no longer be raised by the upsert.\n */\n private async ensureIndexes(): Promise<void> {\n if (!this.indexEnsured) {\n this.indexEnsured = this.createCounterIndex();\n }\n\n return await this.indexEnsured;\n }\n\n /**\n * Create the unique index on the counter collection's `collection` field.\n * Failures are swallowed (see {@link ensureIndexes}).\n */\n private async createCounterIndex(): Promise<void> {\n try {\n const database = this.driver.getDatabase();\n await database.collection(this.counterCollection).createIndex(\n { collection: 1 },\n { unique: true, name: \"collection_unique\" },\n );\n } catch {\n // Best-effort — see ensureIndexes(). Degrade gracefully without the index.\n }\n }\n\n /**\n * Get the last generated ID for a table.\n *\n * @param table - The table/collection name\n * @returns The last generated ID, or 0 if none exists\n */\n public async getLastId(table: string): Promise<number> {\n const query = this.driver.queryBuilder(this.counterCollection);\n const doc = (await query.where(\"collection\", table).first()) as Record<string, unknown> | null;\n return (doc?.id as number) ?? 0;\n }\n\n /**\n * Set the last ID for a table.\n *\n * Creates or updates the counter document for the specified table. Useful for\n * seeding or resetting ID sequences.\n *\n * @param table - The table/collection name\n * @param id - The ID to set as the last generated ID\n *\n * @example\n * ```typescript\n * // Reset user IDs to start from 1000 (next generated id is 1001)\n * await idGenerator.setLastId(\"users\", 1000);\n * ```\n */\n public async setLastId(table: string, id: number): Promise<void> {\n await this.driver.update(\n this.counterCollection,\n { collection: table },\n { $set: { id, collection: table } },\n { upsert: true },\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -675,6 +675,31 @@ declare class MongoQueryBuilder<T = unknown> extends QueryBuilder<T> implements
|
|
|
675
675
|
*/
|
|
676
676
|
groupBy(fields: GroupByInput): this;
|
|
677
677
|
groupBy(fields: GroupByInput, aggregates: Record<string, RawExpression>): this;
|
|
678
|
+
/**
|
|
679
|
+
* Portable date-bucketed grouping.
|
|
680
|
+
*
|
|
681
|
+
* Groups documents by `column` truncated to the given bucket
|
|
682
|
+
* (`day` / `week` / `month` / `year`) via MongoDB's `$dateTrunc`, and runs
|
|
683
|
+
* the optional aggregates over each bucket. The bucket is exposed under the
|
|
684
|
+
* column's own name in the output (the `_id` is renamed in a follow-up
|
|
685
|
+
* `$project`, the same convention as `groupBy`).
|
|
686
|
+
*
|
|
687
|
+
* @param column - The date field to bucket
|
|
688
|
+
* @param unit - The bucket granularity
|
|
689
|
+
* @param aggregates - Optional aggregate operations keyed by output alias
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* ```typescript
|
|
693
|
+
* import { $agg, $expr } from "@warlock.js/cascade";
|
|
694
|
+
*
|
|
695
|
+
* query.groupByDate("created_at", "month", {
|
|
696
|
+
* revenue: $agg.sum($expr.mul("price", "quantity")),
|
|
697
|
+
* });
|
|
698
|
+
* // $group: { _id: { $dateTrunc: { date: "$created_at", unit: "month" } },
|
|
699
|
+
* // revenue: { $sum: { $multiply: ["$price", "$quantity"] } } }
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
groupByDate(column: string, unit: "day" | "week" | "month" | "year", aggregates?: Record<string, RawExpression>): this;
|
|
678
703
|
/**
|
|
679
704
|
* Groups documents using a raw MongoDB expression.
|
|
680
705
|
* @param expression - The raw grouping expression
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mongodb-query-builder.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-query-builder.ts"],"mappings":";;;;;;;;;;;;;cA+Ba,iBAAA,sBACH,YAAA,CAAa,CAAA,aACV,oBAAA,CAAqB,CAAA;EAAA,SAmCd,KAAA;EArCU;;;;;;;EAWZ,UAAA,EAAY,SAAA;EAgDH;;;EAAA,SA3CT,UAAA,EAAY,UAAA;EA6KU;;;EAAA,UAxK5B,iBAAA,GAAoB,oBAAA;EAEvB,eAAA,IAAmB,IAAA,OAAW,KAAA;EAAA,QAE7B,gBAAA;EAAA,QACA,iBAAA;EAAA,QACA,eAAA;EA8LoB;;;;cApLV,KAAA,UAChB,UAAA,GAAa,UAAA;EAuOA;;;;EAAA,cA5ND,gBAAA,IAAoB,oBAAA;EA0Xa;;;EAAA,IAhXpC,UAAA,IAAc,UAAA;EAueC;;;EA9dnB,OAAA,CAAQ,QAAA,GAAW,IAAA,OAAW,KAAA;EAgkBO;;;;EAvjBrC,UAAA,CAAW,QAAA,GAAW,KAAA,kBAAuB,OAAA;EAipBT;;;;EAtoBpC,WAAA,CAAY,QAAA,GAAW,OAAA,SAAgB,OAAA,iBAAwB,OAAA;EAszBxD;;;;EA3yBP,SAAA,CAAU,QAAA,GAAW,OAAA,SAAgB,OAAA,iBAAwB,OAAA;EAi4BrD;;;EAv3BR,kBAAA,IAAsB,UAAA;EA88Be;;;EAt8BrC,mBAAA;EA29BM;;;EA/8BN,KAAA,CAAM,SAAA,aAAsB,IAAA;EA8gCQ;;;EAAA,QA5/BnC,kBAAA;EAqhCkB;;;;;;EA5+BnB,KAAA,CAAM,KAAA,UAAe,KAAA;EACrB,KAAA,CAAM,KAAA,UAAe,QAAA,EAAU,aAAA,EAAe,KAAA;EAC9C,KAAA,CAAM,UAAA,EAAY,WAAA;EAClB,KAAA,CAAM,QAAA,EAAU,aAAA,CAAc,CAAA;EAssCd;;;;;;EA1rChB,OAAA,CAAQ,KAAA,UAAe,KAAA;EACvB,OAAA,CAAQ,KAAA,UAAe,QAAA,EAAU,aAAA,EAAe,KAAA;EAChD,OAAA,CAAQ,UAAA,EAAY,WAAA;EACpB,OAAA,CAAQ,QAAA,EAAU,aAAA,CAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"mongodb-query-builder.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/drivers/mongodb/mongodb-query-builder.ts"],"mappings":"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"}
|