monsqlize 3.3.1 → 3.3.2
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 +4 -3
- package/MIGRATION.md +66 -5
- package/README.md +6 -4
- package/changelogs/v3.3.2.md +37 -0
- package/dist/cjs/cli/data-task.cjs +5 -4
- package/dist/cjs/index.cjs +4218 -2911
- package/dist/cjs/transaction/Transaction.cjs +107 -9
- package/dist/cjs/transaction/TransactionManager.cjs +107 -9
- package/dist/esm/index.mjs +3895 -2589
- package/dist/types/base.d.mts +1 -1
- package/dist/types/base.d.ts +1 -1
- package/dist/types/collection.d.mts +4 -4
- package/dist/types/collection.d.ts +4 -4
- package/dist/types/index.d.mts +3 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/model.d.mts +13 -12
- package/dist/types/model.d.ts +13 -12
- package/dist/types/monsqlize.d.mts +4 -2
- package/dist/types/monsqlize.d.ts +4 -2
- package/dist/types/pool.d.mts +3 -0
- package/dist/types/pool.d.ts +3 -0
- package/dist/types/runtime.d.mts +11 -23
- package/dist/types/runtime.d.ts +11 -23
- package/dist/types/slow-query-log.d.mts +9 -1
- package/dist/types/slow-query-log.d.ts +9 -1
- package/dist/types/sync.d.mts +4 -2
- package/dist/types/sync.d.ts +4 -2
- package/dist/types/transaction.d.mts +6 -4
- package/dist/types/transaction.d.ts +6 -4
- package/package.json +5 -4
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
// src/core/cache-coordination.ts
|
|
4
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
var origins = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function coordinator(cache) {
|
|
7
|
+
const origin = origins.get(cache) ?? cache;
|
|
8
|
+
let state = coordinators.get(origin);
|
|
9
|
+
if (!state) {
|
|
10
|
+
state = { tickets: /* @__PURE__ */ new Set(), tails: /* @__PURE__ */ new Map() };
|
|
11
|
+
coordinators.set(origin, state);
|
|
12
|
+
}
|
|
13
|
+
return state;
|
|
14
|
+
}
|
|
15
|
+
function matches(intent, key) {
|
|
16
|
+
if (intent.type === "key") return intent.value === key;
|
|
17
|
+
const pattern = intent.value.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
18
|
+
return new RegExp(`^${pattern}$`).test(key);
|
|
19
|
+
}
|
|
20
|
+
function serialize(state, key, task) {
|
|
21
|
+
const result = (state.tails.get(key) ?? Promise.resolve()).catch(() => void 0).then(task);
|
|
22
|
+
state.tails.set(key, result);
|
|
23
|
+
void result.finally(() => {
|
|
24
|
+
if (state.tails.get(key) === result) state.tails.delete(key);
|
|
25
|
+
}).catch(() => void 0);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
async function coordinateInvalidation(cache, intents, task) {
|
|
29
|
+
const state = coordinator(cache);
|
|
30
|
+
for (const ticket of state.tickets) {
|
|
31
|
+
if (intents.some((intent) => matches(intent, ticket.key))) ticket.valid = false;
|
|
32
|
+
}
|
|
33
|
+
const keys = [...state.tails.keys()].filter((key) => intents.some((intent) => matches(intent, key)));
|
|
34
|
+
const previous = keys.map((key) => state.tails.get(key));
|
|
35
|
+
let finish;
|
|
36
|
+
const done = new Promise((resolve) => {
|
|
37
|
+
finish = resolve;
|
|
38
|
+
});
|
|
39
|
+
const pending = keys.map((key) => serialize(state, key, () => done));
|
|
40
|
+
try {
|
|
41
|
+
await Promise.allSettled(previous);
|
|
42
|
+
return await task();
|
|
43
|
+
} finally {
|
|
44
|
+
finish();
|
|
45
|
+
await Promise.allSettled(pending);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
3
49
|
// src/capabilities/transaction/index.ts
|
|
4
50
|
var import_node_crypto = require("node:crypto");
|
|
5
51
|
|
|
@@ -148,6 +194,10 @@ var Transaction = class {
|
|
|
148
194
|
constructor(session, options = {}) {
|
|
149
195
|
this.session = session;
|
|
150
196
|
this.options = options;
|
|
197
|
+
this.commitPromise = null;
|
|
198
|
+
this.abortPromise = null;
|
|
199
|
+
this.endPromise = null;
|
|
200
|
+
this.commitOutcomeUnknown = false;
|
|
151
201
|
this.id = `tx_${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
|
|
152
202
|
this.state = "pending";
|
|
153
203
|
this.acceptingWrites = false;
|
|
@@ -164,6 +214,7 @@ var Transaction = class {
|
|
|
164
214
|
* @since v1.4.0
|
|
165
215
|
*/
|
|
166
216
|
async start() {
|
|
217
|
+
if (this.endPromise || this.abortPromise) throw createError(ErrorCodes.INVALID_OPERATION, "Transaction is already ending.");
|
|
167
218
|
if (this.state !== "pending") {
|
|
168
219
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
|
|
169
220
|
}
|
|
@@ -189,12 +240,42 @@ var Transaction = class {
|
|
|
189
240
|
* @since v1.4.0
|
|
190
241
|
*/
|
|
191
242
|
async commit() {
|
|
243
|
+
if (this.commitPromise) return this.commitPromise;
|
|
244
|
+
if (this.endPromise || this.abortPromise) {
|
|
245
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Transaction is already ending.");
|
|
246
|
+
}
|
|
192
247
|
if (this.state !== "active") {
|
|
193
248
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
194
249
|
}
|
|
195
250
|
this.acceptingWrites = false;
|
|
251
|
+
const timeout = this.options.timeout ?? 3e4;
|
|
252
|
+
if (!this.commitOutcomeUnknown && timeout > 0 && this.getDuration() >= timeout) {
|
|
253
|
+
await this.abort();
|
|
254
|
+
throw createError(ErrorCodes.OPERATION_TIMEOUT, "Transaction work exceeded its timeout before commit.");
|
|
255
|
+
}
|
|
256
|
+
this.clearTimeout();
|
|
257
|
+
const committing = this.commitOnce();
|
|
258
|
+
this.commitPromise = committing;
|
|
259
|
+
try {
|
|
260
|
+
await committing;
|
|
261
|
+
} finally {
|
|
262
|
+
if (this.commitPromise === committing) this.commitPromise = null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async commitOnce() {
|
|
196
266
|
if (typeof this.session.commitTransaction === "function") {
|
|
197
|
-
|
|
267
|
+
try {
|
|
268
|
+
await commitTransactionWithRetry(this.session, this.options.logger);
|
|
269
|
+
this.commitOutcomeUnknown = false;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.commitOutcomeUnknown = isUnknownTransactionCommitResult(error);
|
|
272
|
+
if (this.commitOutcomeUnknown) {
|
|
273
|
+
await this.flushPendingInvalidations().catch((cacheError) => {
|
|
274
|
+
this.options.logger?.warn?.("[Transaction] uncertain commit cache invalidation failed.", cacheError);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
198
279
|
}
|
|
199
280
|
this.state = "committed";
|
|
200
281
|
try {
|
|
@@ -219,6 +300,13 @@ var Transaction = class {
|
|
|
219
300
|
*/
|
|
220
301
|
async abort() {
|
|
221
302
|
this.acceptingWrites = false;
|
|
303
|
+
if (this.commitPromise) await this.commitPromise.catch(() => void 0);
|
|
304
|
+
if (this.commitOutcomeUnknown) return;
|
|
305
|
+
if (this.abortPromise) return this.abortPromise;
|
|
306
|
+
this.abortPromise = this.abortOnce();
|
|
307
|
+
return this.abortPromise;
|
|
308
|
+
}
|
|
309
|
+
async abortOnce() {
|
|
222
310
|
if (this.state !== "pending" && this.state !== "active") {
|
|
223
311
|
return;
|
|
224
312
|
}
|
|
@@ -243,7 +331,15 @@ var Transaction = class {
|
|
|
243
331
|
* @since v1.4.0
|
|
244
332
|
*/
|
|
245
333
|
async end() {
|
|
334
|
+
if (this.endPromise) return this.endPromise;
|
|
335
|
+
this.endPromise = this.endOnce();
|
|
336
|
+
return this.endPromise;
|
|
337
|
+
}
|
|
338
|
+
async endOnce() {
|
|
246
339
|
this.acceptingWrites = false;
|
|
340
|
+
if (this.commitPromise) await this.commitPromise.catch(() => void 0);
|
|
341
|
+
if (this.abortPromise) await this.abortPromise;
|
|
342
|
+
else if (this.state === "active" && !this.commitOutcomeUnknown) await this.abort();
|
|
247
343
|
this.clearTimeout();
|
|
248
344
|
this.options.lockManager?.releaseLocks(this.id);
|
|
249
345
|
await this.session.endSession();
|
|
@@ -280,15 +376,17 @@ var Transaction = class {
|
|
|
280
376
|
return;
|
|
281
377
|
}
|
|
282
378
|
const cacheWithDelete = this.options.cache;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
379
|
+
await coordinateInvalidation(this.options.cache, [...this.pendingInvalidationIntents.values()], async () => {
|
|
380
|
+
for (const intent of this.pendingInvalidationIntents.values()) {
|
|
381
|
+
if (intent.type === "pattern") {
|
|
382
|
+
await cacheWithDelete.delPattern?.(intent.value);
|
|
383
|
+
} else if (typeof cacheWithDelete.del === "function") {
|
|
384
|
+
await cacheWithDelete.del(intent.value);
|
|
385
|
+
} else if (typeof cacheWithDelete.delete === "function") {
|
|
386
|
+
await cacheWithDelete.delete(intent.value);
|
|
387
|
+
}
|
|
290
388
|
}
|
|
291
|
-
}
|
|
389
|
+
});
|
|
292
390
|
}
|
|
293
391
|
async clearPendingInvalidationBarriers() {
|
|
294
392
|
if (!this.options.cache || this.pendingInvalidationIntents.size === 0) {
|
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
// src/core/cache-coordination.ts
|
|
4
|
+
var coordinators = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
var origins = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function coordinator(cache) {
|
|
7
|
+
const origin = origins.get(cache) ?? cache;
|
|
8
|
+
let state = coordinators.get(origin);
|
|
9
|
+
if (!state) {
|
|
10
|
+
state = { tickets: /* @__PURE__ */ new Set(), tails: /* @__PURE__ */ new Map() };
|
|
11
|
+
coordinators.set(origin, state);
|
|
12
|
+
}
|
|
13
|
+
return state;
|
|
14
|
+
}
|
|
15
|
+
function matches(intent, key) {
|
|
16
|
+
if (intent.type === "key") return intent.value === key;
|
|
17
|
+
const pattern = intent.value.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
18
|
+
return new RegExp(`^${pattern}$`).test(key);
|
|
19
|
+
}
|
|
20
|
+
function serialize(state, key, task) {
|
|
21
|
+
const result = (state.tails.get(key) ?? Promise.resolve()).catch(() => void 0).then(task);
|
|
22
|
+
state.tails.set(key, result);
|
|
23
|
+
void result.finally(() => {
|
|
24
|
+
if (state.tails.get(key) === result) state.tails.delete(key);
|
|
25
|
+
}).catch(() => void 0);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
async function coordinateInvalidation(cache, intents, task) {
|
|
29
|
+
const state = coordinator(cache);
|
|
30
|
+
for (const ticket of state.tickets) {
|
|
31
|
+
if (intents.some((intent) => matches(intent, ticket.key))) ticket.valid = false;
|
|
32
|
+
}
|
|
33
|
+
const keys = [...state.tails.keys()].filter((key) => intents.some((intent) => matches(intent, key)));
|
|
34
|
+
const previous = keys.map((key) => state.tails.get(key));
|
|
35
|
+
let finish;
|
|
36
|
+
const done = new Promise((resolve) => {
|
|
37
|
+
finish = resolve;
|
|
38
|
+
});
|
|
39
|
+
const pending = keys.map((key) => serialize(state, key, () => done));
|
|
40
|
+
try {
|
|
41
|
+
await Promise.allSettled(previous);
|
|
42
|
+
return await task();
|
|
43
|
+
} finally {
|
|
44
|
+
finish();
|
|
45
|
+
await Promise.allSettled(pending);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
3
49
|
// src/capabilities/transaction/index.ts
|
|
4
50
|
var import_node_crypto = require("node:crypto");
|
|
5
51
|
|
|
@@ -148,6 +194,10 @@ var Transaction = class {
|
|
|
148
194
|
constructor(session, options = {}) {
|
|
149
195
|
this.session = session;
|
|
150
196
|
this.options = options;
|
|
197
|
+
this.commitPromise = null;
|
|
198
|
+
this.abortPromise = null;
|
|
199
|
+
this.endPromise = null;
|
|
200
|
+
this.commitOutcomeUnknown = false;
|
|
151
201
|
this.id = `tx_${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
|
|
152
202
|
this.state = "pending";
|
|
153
203
|
this.acceptingWrites = false;
|
|
@@ -164,6 +214,7 @@ var Transaction = class {
|
|
|
164
214
|
* @since v1.4.0
|
|
165
215
|
*/
|
|
166
216
|
async start() {
|
|
217
|
+
if (this.endPromise || this.abortPromise) throw createError(ErrorCodes.INVALID_OPERATION, "Transaction is already ending.");
|
|
167
218
|
if (this.state !== "pending") {
|
|
168
219
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot start transaction in state: ${this.state}`);
|
|
169
220
|
}
|
|
@@ -189,12 +240,42 @@ var Transaction = class {
|
|
|
189
240
|
* @since v1.4.0
|
|
190
241
|
*/
|
|
191
242
|
async commit() {
|
|
243
|
+
if (this.commitPromise) return this.commitPromise;
|
|
244
|
+
if (this.endPromise || this.abortPromise) {
|
|
245
|
+
throw createError(ErrorCodes.INVALID_OPERATION, "Transaction is already ending.");
|
|
246
|
+
}
|
|
192
247
|
if (this.state !== "active") {
|
|
193
248
|
throw createError(ErrorCodes.INVALID_OPERATION, `Cannot commit transaction in state: ${this.state}`);
|
|
194
249
|
}
|
|
195
250
|
this.acceptingWrites = false;
|
|
251
|
+
const timeout = this.options.timeout ?? 3e4;
|
|
252
|
+
if (!this.commitOutcomeUnknown && timeout > 0 && this.getDuration() >= timeout) {
|
|
253
|
+
await this.abort();
|
|
254
|
+
throw createError(ErrorCodes.OPERATION_TIMEOUT, "Transaction work exceeded its timeout before commit.");
|
|
255
|
+
}
|
|
256
|
+
this.clearTimeout();
|
|
257
|
+
const committing = this.commitOnce();
|
|
258
|
+
this.commitPromise = committing;
|
|
259
|
+
try {
|
|
260
|
+
await committing;
|
|
261
|
+
} finally {
|
|
262
|
+
if (this.commitPromise === committing) this.commitPromise = null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async commitOnce() {
|
|
196
266
|
if (typeof this.session.commitTransaction === "function") {
|
|
197
|
-
|
|
267
|
+
try {
|
|
268
|
+
await commitTransactionWithRetry(this.session, this.options.logger);
|
|
269
|
+
this.commitOutcomeUnknown = false;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.commitOutcomeUnknown = isUnknownTransactionCommitResult(error);
|
|
272
|
+
if (this.commitOutcomeUnknown) {
|
|
273
|
+
await this.flushPendingInvalidations().catch((cacheError) => {
|
|
274
|
+
this.options.logger?.warn?.("[Transaction] uncertain commit cache invalidation failed.", cacheError);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
198
279
|
}
|
|
199
280
|
this.state = "committed";
|
|
200
281
|
try {
|
|
@@ -219,6 +300,13 @@ var Transaction = class {
|
|
|
219
300
|
*/
|
|
220
301
|
async abort() {
|
|
221
302
|
this.acceptingWrites = false;
|
|
303
|
+
if (this.commitPromise) await this.commitPromise.catch(() => void 0);
|
|
304
|
+
if (this.commitOutcomeUnknown) return;
|
|
305
|
+
if (this.abortPromise) return this.abortPromise;
|
|
306
|
+
this.abortPromise = this.abortOnce();
|
|
307
|
+
return this.abortPromise;
|
|
308
|
+
}
|
|
309
|
+
async abortOnce() {
|
|
222
310
|
if (this.state !== "pending" && this.state !== "active") {
|
|
223
311
|
return;
|
|
224
312
|
}
|
|
@@ -243,7 +331,15 @@ var Transaction = class {
|
|
|
243
331
|
* @since v1.4.0
|
|
244
332
|
*/
|
|
245
333
|
async end() {
|
|
334
|
+
if (this.endPromise) return this.endPromise;
|
|
335
|
+
this.endPromise = this.endOnce();
|
|
336
|
+
return this.endPromise;
|
|
337
|
+
}
|
|
338
|
+
async endOnce() {
|
|
246
339
|
this.acceptingWrites = false;
|
|
340
|
+
if (this.commitPromise) await this.commitPromise.catch(() => void 0);
|
|
341
|
+
if (this.abortPromise) await this.abortPromise;
|
|
342
|
+
else if (this.state === "active" && !this.commitOutcomeUnknown) await this.abort();
|
|
247
343
|
this.clearTimeout();
|
|
248
344
|
this.options.lockManager?.releaseLocks(this.id);
|
|
249
345
|
await this.session.endSession();
|
|
@@ -280,15 +376,17 @@ var Transaction = class {
|
|
|
280
376
|
return;
|
|
281
377
|
}
|
|
282
378
|
const cacheWithDelete = this.options.cache;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
379
|
+
await coordinateInvalidation(this.options.cache, [...this.pendingInvalidationIntents.values()], async () => {
|
|
380
|
+
for (const intent of this.pendingInvalidationIntents.values()) {
|
|
381
|
+
if (intent.type === "pattern") {
|
|
382
|
+
await cacheWithDelete.delPattern?.(intent.value);
|
|
383
|
+
} else if (typeof cacheWithDelete.del === "function") {
|
|
384
|
+
await cacheWithDelete.del(intent.value);
|
|
385
|
+
} else if (typeof cacheWithDelete.delete === "function") {
|
|
386
|
+
await cacheWithDelete.delete(intent.value);
|
|
387
|
+
}
|
|
290
388
|
}
|
|
291
|
-
}
|
|
389
|
+
});
|
|
292
390
|
}
|
|
293
391
|
async clearPendingInvalidationBarriers() {
|
|
294
392
|
if (!this.options.cache || this.pendingInvalidationIntents.size === 0) {
|