@classytic/mongokit 3.43.1 → 3.45.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/README.md +18 -3
- package/dist/{PaginationEngine-DZnkbSrr.mjs → PaginationEngine-BRlLTQQz.mjs} +13 -27
- package/dist/{PaginationEngine-CRHyuUtw.d.mts → PaginationEngine-BWb-c47a.d.mts} +1 -3
- package/dist/{Repository-DnL4xk5w.d.mts → Repository-DJuRmBNx.d.mts} +44 -13
- package/dist/{Repository-Dj6xqPxJ.mjs → Repository-DheA7oW-.mjs} +330 -112
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.mjs +2 -2
- package/dist/{actions-ChYfkPYP.mjs → actions-DKQx9ToJ.mjs} +3 -4
- package/dist/adapter/index.d.mts +6 -10
- package/dist/adapter/index.mjs +1 -1
- package/dist/{adapter-CkMoMF83.mjs → adapter-Qzgc3ZD_.mjs} +4 -6
- package/dist/ai/index.d.mts +21 -8
- package/dist/ai/index.mjs +15 -9
- package/dist/better-auth/index.d.mts +21 -20
- package/dist/better-auth/index.mjs +12 -23
- package/dist/{coercion-BjdPX3YF.d.mts → coercion-COp9DRNw.d.mts} +0 -2
- package/dist/{create-repository-UjwPWR1z.mjs → create-repository-C9DHHcl7.mjs} +4 -5
- package/dist/{create-repository-CJtVdwig.d.mts → create-repository-DG-gwOf0.d.mts} +5 -7
- package/dist/{derive-DRBiegRH.mjs → derive-Dxsc31fV.mjs} +3 -6
- package/dist/{error-BuByZwKU.mjs → error-DjdmPLWO.mjs} +1 -2
- package/dist/{field-selection-FSQFLpsU.mjs → field-selection-DPFaFw9Y.mjs} +1 -2
- package/dist/{id-chunks-BmAwsqNk.mjs → id-chunks-DH-RAZWG.mjs} +13 -8
- package/dist/{id-resolution-BqDrk0S2.mjs → id-resolution-Cqh49d9J.mjs} +1 -2
- package/dist/idempotency/index.d.mts +4 -7
- package/dist/idempotency/index.mjs +1 -2
- package/dist/{index-SYF8N28D.d.mts → index-5mhQ63_B.d.mts} +3 -4
- package/dist/{index-C0AjVqX3.d.mts → index-ehGZLbrd.d.mts} +2 -8
- package/dist/index.d.mts +70 -102
- package/dist/index.mjs +36 -52
- package/dist/indexes/verify-indexes.d.mts +8 -11
- package/dist/indexes/verify-indexes.mjs +1 -2
- package/dist/{indexes-DIjMpYES.d.mts → indexes-FfyOuhDK.d.mts} +0 -2
- package/dist/{join-scope-DSAznE8E.mjs → join-scope-C1gOtZ59.mjs} +2 -3
- package/dist/kernel-conformance/index.d.mts +39 -13
- package/dist/kernel-conformance/index.mjs +4 -7
- package/dist/lock/index.d.mts +134 -7
- package/dist/lock/index.mjs +349 -14
- package/dist/{logger-K7dPmu2U.mjs → logger-hbWOlj_X.mjs} +1 -2
- package/dist/{method-registry.plugin-C63pGn1d.mjs → method-registry.plugin-BeJfQBJV.mjs} +5 -7
- package/dist/{model-registry-ClKtWiHa.mjs → model-registry-DrVfJ3Nt.mjs} +1 -2
- package/dist/{mongooseToJsonSchema-C3k4ide0.mjs → mongooseToJsonSchema-DMDotz4_.mjs} +2 -3
- package/dist/{multi-tenant.plugin-B_q9NUHc.d.mts → multi-tenant.plugin-BTPtCLNK.d.mts} +143 -11
- package/dist/{operations-XiFgG5US.d.mts → operations-D6XsHx4e.d.mts} +19 -11
- package/dist/outbox/index.d.mts +24 -9
- package/dist/outbox/index.mjs +17 -6
- package/dist/pagination/PaginationEngine.d.mts +1 -1
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/pagination/utils/limits.d.mts +6 -9
- package/dist/pagination/utils/limits.mjs +2 -3
- package/dist/{pagination-D6psses0.d.mts → pagination-BAM9cA4N.d.mts} +6 -16
- package/dist/{plugin-options-Cirmb4Kp.d.mts → plugin-options-DrNGWV5b.d.mts} +2 -4
- package/dist/plugins/index.d.mts +6 -7
- package/dist/plugins/index.mjs +6 -7
- package/dist/query/primitives/coercion.d.mts +1 -1
- package/dist/query/primitives/coercion.mjs +2 -3
- package/dist/query/primitives/geo.d.mts +11 -14
- package/dist/query/primitives/geo.mjs +1 -2
- package/dist/query/primitives/indexes.d.mts +1 -1
- package/dist/query/primitives/indexes.mjs +1 -2
- package/dist/{query-defaults-BxUcSOoF.mjs → query-defaults-DwvWUjZU.mjs} +18 -54
- package/dist/read-model/index.d.mts +18 -21
- package/dist/read-model/index.mjs +3 -4
- package/dist/{repository-MqAP409e.d.mts → repository-B0aFVgP2.d.mts} +25 -29
- package/dist/retention-purge-CCd_VXUf.d.mts +38 -0
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-D8jQpDG7.mjs} +1 -2
- package/dist/search/index.d.mts +1 -1
- package/dist/search/index.mjs +1 -1
- package/dist/sync/index.d.mts +113 -17
- package/dist/sync/index.mjs +421 -65
- package/dist/tenant/index.d.mts +8 -11
- package/dist/tenant/index.mjs +1 -2
- package/dist/testkit/index.d.mts +179 -23
- package/dist/testkit/index.mjs +421 -23
- package/dist/{timestamp.plugin-DpOWavVI.mjs → timestamp.plugin-B-IJrlYb.mjs} +192 -49
- package/dist/{to-plain-vksRrLtc.mjs → to-plain-CEKv_ylg.mjs} +2 -4
- package/dist/{to-plain-o5tUY2nV.d.mts → to-plain-Do-8jTKC.d.mts} +2 -16
- package/dist/{transaction-DmiB4k2P.mjs → transaction-C6Oj941P.mjs} +18 -19
- package/dist/{type-utils-CLzRFtIA.d.mts → type-utils-CKvuewAL.d.mts} +0 -2
- package/dist/{update-WgeAUJ_I.mjs → update-CYaJMu6j.mjs} +13 -23
- package/dist/usage/index.d.mts +2 -5
- package/dist/usage/index.mjs +8 -6
- package/dist/utils/index.d.mts +8 -10
- package/dist/utils/index.mjs +7 -7
- package/dist/{validation-chain.plugin-BAeC5eVT.d.mts → validation-chain.plugin-COYrUdd8.d.mts} +150 -48
- package/dist/{validation-chain.plugin-DRApTmkN.mjs → validation-chain.plugin-DJplVJ12.mjs} +210 -55
- package/llms.txt +33 -0
- package/package.json +29 -25
- package/skills/mongokit/SKILL.md +112 -0
- package/skills/mongokit/references/pagination-search.md +87 -0
- package/skills/mongokit/references/plugins-tenancy.md +103 -0
- package/skills/mongokit/references/repository-api.md +83 -0
- package/skills/mongokit/references/transactions-outbox.md +103 -0
package/dist/lock/index.mjs
CHANGED
|
@@ -1,5 +1,276 @@
|
|
|
1
1
|
import mongoose, { Schema } from "mongoose";
|
|
2
|
-
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
// src/lock/daily-ledger.ts
|
|
5
|
+
/**
|
|
6
|
+
* Mongo-backed daily ledger — one claim row per `(key, civilDate)`.
|
|
7
|
+
*
|
|
8
|
+
* Structurally implements arc's `DailyLedger` (`@classytic/arc` →
|
|
9
|
+
* `createDailyGate` / `ScheduleDefinition.daily`) without importing arc:
|
|
10
|
+
* pass the returned object as `ledger`. Exactly one replica wins
|
|
11
|
+
* `claimDay` for a given key and civil date (unique `_id` + upsert; the
|
|
12
|
+
* loser's upsert hits E11000 and returns `false`). An uncompleted claim
|
|
13
|
+
* older than `info.staleAfterMs` may be re-granted (crash mid-run); a
|
|
14
|
+
* completed day never is.
|
|
15
|
+
*
|
|
16
|
+
* {
|
|
17
|
+
* _id: 'cron.digest|2026-10-30',
|
|
18
|
+
* key: 'cron.digest',
|
|
19
|
+
* civilDate: '2026-10-30',
|
|
20
|
+
* claimedAt: 2026-10-30T06:00:01.000Z, // the gate's clock (info.now)
|
|
21
|
+
* completed: true,
|
|
22
|
+
* completedAt: 2026-10-30T06:00:04.000Z,
|
|
23
|
+
* result: { sent: 12 },
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* `civilDate` must be `YYYY-MM-DD` (what arc passes), so the composite
|
|
27
|
+
* `_id` stays unambiguous whatever the key contains.
|
|
28
|
+
*
|
|
29
|
+
* Fencing is per INVOCATION, not per ledger instance: every won claim
|
|
30
|
+
* gets a unique `claimToken` (stored on the row), and completing or
|
|
31
|
+
* releasing only touches the row while it still carries that token. A
|
|
32
|
+
* run that outlived `staleAfterMs` and was re-granted (by another
|
|
33
|
+
* replica, or by this same instance on a later tick) can therefore
|
|
34
|
+
* neither delete nor complete the newer claim, and a retry of its
|
|
35
|
+
* completion stays fenced.
|
|
36
|
+
*
|
|
37
|
+
* - `claim()` returns a handle carrying the token; its `complete()` /
|
|
38
|
+
* `release()` are fenced by it. Prefer it in host code.
|
|
39
|
+
* - `claimDay` / `completeDay` / `releaseDay` (arc's token-less
|
|
40
|
+
* `DailyLedger` shape) bind the token to the calling async context:
|
|
41
|
+
* `completeDay` / `releaseDay` act on the claim that THIS invocation
|
|
42
|
+
* (the async flow that awaited `claimDay`) won. Called from a flow
|
|
43
|
+
* that won nothing, they fall back to the instance's claim on the row
|
|
44
|
+
* only when it holds exactly one, and are otherwise a no-op.
|
|
45
|
+
* - `adminCompleteDay` is the separate, explicit, unfenced manual
|
|
46
|
+
* completion. Nothing ever falls through to it.
|
|
47
|
+
*/
|
|
48
|
+
const CIVIL_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
49
|
+
function rowId(key, civilDate) {
|
|
50
|
+
if (typeof key !== "string" || key.length === 0) throw new Error("MongoDailyLedger: key must be a non-empty string");
|
|
51
|
+
if (typeof civilDate !== "string" || !CIVIL_DATE.test(civilDate)) throw new Error(`MongoDailyLedger: civilDate must be YYYY-MM-DD, got "${String(civilDate)}"`);
|
|
52
|
+
return `${key}|${civilDate}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build a Mongo-backed daily ledger. Idempotent per `(connection,
|
|
56
|
+
* modelName)` like `createMongoLockAdapter`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* import { createDailyGate } from '@classytic/arc/plugins';
|
|
60
|
+
* import { createMongoDailyLedger } from '@classytic/mongokit/lock';
|
|
61
|
+
*
|
|
62
|
+
* const gate = createDailyGate({
|
|
63
|
+
* key: 'digest', zone: 'Asia/Dubai', hour: 6, staleAfterMs: 15 * 60_000,
|
|
64
|
+
* ledger: createMongoDailyLedger({ connection, collectionName: 'app_daily_ledger' }),
|
|
65
|
+
* });
|
|
66
|
+
*/
|
|
67
|
+
function createMongoDailyLedger(options = {}) {
|
|
68
|
+
const { connection, collectionName = "mongo_daily_ledger", modelName = "MongoDailyLedger", nowFn = () => /* @__PURE__ */ new Date() } = options;
|
|
69
|
+
const model = getOrCreateLedgerModel(connection ?? mongoose, modelName, collectionName);
|
|
70
|
+
const sessionOpt = () => {
|
|
71
|
+
const s = options.session?.();
|
|
72
|
+
return s ? { session: s } : {};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Token-less (arc-shaped) claims this instance won and has not finished: rowId → tokens. The
|
|
76
|
+
* fallback for a `completeDay` / `releaseDay` called outside the invocation that claimed.
|
|
77
|
+
*/
|
|
78
|
+
const live = /* @__PURE__ */ new Map();
|
|
79
|
+
const invocation = new AsyncLocalStorage();
|
|
80
|
+
const slotToken = (slot) => {
|
|
81
|
+
for (let s = slot; s; s = s.prev) if (s.token !== void 0) return s.token;
|
|
82
|
+
};
|
|
83
|
+
const isLive = (id, token) => live.get(id)?.has(token) === true;
|
|
84
|
+
const forget = (id, token) => {
|
|
85
|
+
const set = live.get(id);
|
|
86
|
+
set?.delete(token);
|
|
87
|
+
if (set?.size === 0) live.delete(id);
|
|
88
|
+
};
|
|
89
|
+
/** The token-less caller's claim on the row: its invocation's, else the instance's only one. */
|
|
90
|
+
const tokenFor = (id) => {
|
|
91
|
+
const own = slotToken(invocation.getStore()?.get(id));
|
|
92
|
+
if (own !== void 0) return own;
|
|
93
|
+
const set = live.get(id);
|
|
94
|
+
return set?.size === 1 ? [...set][0] : void 0;
|
|
95
|
+
};
|
|
96
|
+
const validate = (key, civilDate, info) => {
|
|
97
|
+
const _id = rowId(key, civilDate);
|
|
98
|
+
const now = info?.now;
|
|
99
|
+
if (!(now instanceof Date) || Number.isNaN(now.getTime())) throw new Error("MongoDailyLedger.claimDay: info.now must be a valid Date");
|
|
100
|
+
const staleAfterMs = info.staleAfterMs;
|
|
101
|
+
if (staleAfterMs !== void 0 && (typeof staleAfterMs !== "number" || !Number.isFinite(staleAfterMs) || staleAfterMs < 0)) throw new Error("MongoDailyLedger.claimDay: info.staleAfterMs must be a finite number >= 0");
|
|
102
|
+
return {
|
|
103
|
+
_id,
|
|
104
|
+
now,
|
|
105
|
+
staleAfterMs
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
/** Try to win the row; the new claim's token, or `undefined` when not won. */
|
|
109
|
+
const claimRow = async (key, civilDate, v) => {
|
|
110
|
+
const { _id, now, staleAfterMs } = v;
|
|
111
|
+
const claimToken = randomUUID();
|
|
112
|
+
try {
|
|
113
|
+
if (staleAfterMs === void 0) {
|
|
114
|
+
await model.create([{
|
|
115
|
+
_id,
|
|
116
|
+
key,
|
|
117
|
+
civilDate,
|
|
118
|
+
claimedAt: now,
|
|
119
|
+
claimToken,
|
|
120
|
+
completed: false
|
|
121
|
+
}]);
|
|
122
|
+
return claimToken;
|
|
123
|
+
}
|
|
124
|
+
const res = await model.updateOne({
|
|
125
|
+
_id,
|
|
126
|
+
completed: false,
|
|
127
|
+
claimedAt: { $lt: new Date(now.getTime() - staleAfterMs) }
|
|
128
|
+
}, {
|
|
129
|
+
$set: {
|
|
130
|
+
key,
|
|
131
|
+
civilDate,
|
|
132
|
+
claimedAt: now,
|
|
133
|
+
claimToken,
|
|
134
|
+
completed: false
|
|
135
|
+
},
|
|
136
|
+
$unset: {
|
|
137
|
+
result: "",
|
|
138
|
+
completedAt: ""
|
|
139
|
+
}
|
|
140
|
+
}, { upsert: true });
|
|
141
|
+
return res.upsertedCount + res.modifiedCount > 0 ? claimToken : void 0;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (err?.code === 11e3) return void 0;
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
/** Complete the row while `token` still holds it uncompleted. */
|
|
148
|
+
const completeRow = async (_id, token, result) => {
|
|
149
|
+
return (await model.updateOne({
|
|
150
|
+
_id,
|
|
151
|
+
claimToken: token,
|
|
152
|
+
completed: false
|
|
153
|
+
}, { $set: {
|
|
154
|
+
completed: true,
|
|
155
|
+
completedAt: nowFn(),
|
|
156
|
+
...result !== void 0 ? { result } : {}
|
|
157
|
+
} }, sessionOpt())).modifiedCount > 0;
|
|
158
|
+
};
|
|
159
|
+
/** Delete the row while `token` still holds it uncompleted. */
|
|
160
|
+
const releaseRow = async (_id, token) => {
|
|
161
|
+
return (await model.deleteOne({
|
|
162
|
+
_id,
|
|
163
|
+
claimToken: token,
|
|
164
|
+
completed: false
|
|
165
|
+
}, sessionOpt())).deletedCount > 0;
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
async claimDay(key, civilDate, info) {
|
|
169
|
+
const v = validate(key, civilDate, info);
|
|
170
|
+
const outer = invocation.getStore();
|
|
171
|
+
const scope = /* @__PURE__ */ new Map();
|
|
172
|
+
if (outer) for (const [id, slot] of outer) {
|
|
173
|
+
const t = slotToken(slot);
|
|
174
|
+
if (t === void 0 || isLive(id, t)) scope.set(id, slot);
|
|
175
|
+
}
|
|
176
|
+
const slot = { prev: outer?.get(v._id) };
|
|
177
|
+
scope.set(v._id, slot);
|
|
178
|
+
invocation.enterWith(scope);
|
|
179
|
+
const token = await claimRow(key, civilDate, v);
|
|
180
|
+
if (token === void 0) return false;
|
|
181
|
+
slot.token = token;
|
|
182
|
+
const set = live.get(v._id) ?? /* @__PURE__ */ new Set();
|
|
183
|
+
set.add(token);
|
|
184
|
+
live.set(v._id, set);
|
|
185
|
+
return true;
|
|
186
|
+
},
|
|
187
|
+
async completeDay(key, civilDate, result) {
|
|
188
|
+
const _id = rowId(key, civilDate);
|
|
189
|
+
const token = tokenFor(_id);
|
|
190
|
+
if (token === void 0) return;
|
|
191
|
+
await completeRow(_id, token, result);
|
|
192
|
+
forget(_id, token);
|
|
193
|
+
},
|
|
194
|
+
async releaseDay(key, civilDate) {
|
|
195
|
+
const _id = rowId(key, civilDate);
|
|
196
|
+
const token = tokenFor(_id);
|
|
197
|
+
if (token === void 0) return;
|
|
198
|
+
forget(_id, token);
|
|
199
|
+
await releaseRow(_id, token);
|
|
200
|
+
},
|
|
201
|
+
async claim(key, civilDate, info) {
|
|
202
|
+
const v = validate(key, civilDate, info);
|
|
203
|
+
const token = await claimRow(key, civilDate, v);
|
|
204
|
+
if (token === void 0) return null;
|
|
205
|
+
return {
|
|
206
|
+
key,
|
|
207
|
+
civilDate,
|
|
208
|
+
token,
|
|
209
|
+
claimedAt: v.now,
|
|
210
|
+
complete: (result) => completeRow(v._id, token, result),
|
|
211
|
+
release: () => releaseRow(v._id, token)
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
async adminCompleteDay(key, civilDate, result) {
|
|
215
|
+
const _id = rowId(key, civilDate);
|
|
216
|
+
return (await model.updateOne({
|
|
217
|
+
_id,
|
|
218
|
+
completed: false
|
|
219
|
+
}, { $set: {
|
|
220
|
+
completed: true,
|
|
221
|
+
completedAt: nowFn(),
|
|
222
|
+
...result !== void 0 ? { result } : {}
|
|
223
|
+
} }, sessionOpt())).modifiedCount > 0;
|
|
224
|
+
},
|
|
225
|
+
async inspect(key, civilDate) {
|
|
226
|
+
const doc = await model.findById(rowId(key, civilDate), null, sessionOpt()).lean();
|
|
227
|
+
if (!doc) return null;
|
|
228
|
+
const { _id: _ignored, ...row } = doc;
|
|
229
|
+
return row;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function getOrCreateLedgerModel(conn, modelName, collectionName) {
|
|
234
|
+
const existing = "models" in conn ? conn.models[modelName] : void 0;
|
|
235
|
+
if (existing) return existing;
|
|
236
|
+
const schema = new Schema({
|
|
237
|
+
_id: {
|
|
238
|
+
type: String,
|
|
239
|
+
required: true
|
|
240
|
+
},
|
|
241
|
+
key: {
|
|
242
|
+
type: String,
|
|
243
|
+
required: true
|
|
244
|
+
},
|
|
245
|
+
civilDate: {
|
|
246
|
+
type: String,
|
|
247
|
+
required: true
|
|
248
|
+
},
|
|
249
|
+
claimedAt: {
|
|
250
|
+
type: Date,
|
|
251
|
+
required: true
|
|
252
|
+
},
|
|
253
|
+
claimToken: { type: String },
|
|
254
|
+
completed: {
|
|
255
|
+
type: Boolean,
|
|
256
|
+
required: true,
|
|
257
|
+
default: false
|
|
258
|
+
},
|
|
259
|
+
completedAt: { type: Date },
|
|
260
|
+
result: { type: Schema.Types.Mixed }
|
|
261
|
+
}, {
|
|
262
|
+
versionKey: false,
|
|
263
|
+
timestamps: false,
|
|
264
|
+
_id: false,
|
|
265
|
+
minimize: false
|
|
266
|
+
});
|
|
267
|
+
schema.index({
|
|
268
|
+
key: 1,
|
|
269
|
+
civilDate: -1
|
|
270
|
+
});
|
|
271
|
+
return conn.model(modelName, schema, collectionName);
|
|
272
|
+
}
|
|
273
|
+
// src/lock/index.ts
|
|
3
274
|
/**
|
|
4
275
|
* Build a Mongo-backed lock adapter. Idempotent — the underlying
|
|
5
276
|
* model is registered once per `(connection, modelName)` pair, so
|
|
@@ -7,27 +278,30 @@ import mongoose, { Schema } from "mongoose";
|
|
|
7
278
|
* collection (no duplicate-model errors).
|
|
8
279
|
*/
|
|
9
280
|
function createMongoLockAdapter(options = {}) {
|
|
10
|
-
const { connection, collectionName = "mongo_locks", modelName = "MongoLock", defaultLeaseMs = 3e4 } = options;
|
|
281
|
+
const { connection, collectionName = "mongo_locks", modelName = "MongoLock", defaultLeaseMs = 3e4, nowFn = () => /* @__PURE__ */ new Date() } = options;
|
|
11
282
|
const model = getOrCreateModel(connection ?? mongoose, modelName, collectionName);
|
|
12
283
|
return {
|
|
13
284
|
async tryAcquire(name, holderId, leaseMs) {
|
|
14
285
|
const ms = leaseMs > 0 ? leaseMs : defaultLeaseMs;
|
|
15
|
-
const now =
|
|
286
|
+
const now = nowFn();
|
|
16
287
|
const expiresAt = new Date(now.getTime() + ms);
|
|
17
288
|
try {
|
|
289
|
+
const holder = { $literal: holderId };
|
|
18
290
|
const result = await model.findOneAndUpdate({
|
|
19
291
|
_id: name,
|
|
20
292
|
$or: [{ expiresAt: { $lt: now } }, { holder: holderId }]
|
|
21
|
-
}, {
|
|
22
|
-
$
|
|
23
|
-
holder:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
293
|
+
}, [{ $set: {
|
|
294
|
+
acquiredAt: { $cond: [
|
|
295
|
+
{ $and: [{ $eq: ["$holder", holder] }, { $ne: [{ $type: "$acquiredAt" }, "missing"] }] },
|
|
296
|
+
"$acquiredAt",
|
|
297
|
+
{ $literal: now }
|
|
298
|
+
] },
|
|
299
|
+
holder,
|
|
300
|
+
expiresAt: { $literal: expiresAt }
|
|
301
|
+
} }], {
|
|
28
302
|
upsert: true,
|
|
29
303
|
returnDocument: "after",
|
|
30
|
-
|
|
304
|
+
updatePipeline: true
|
|
31
305
|
}).lean();
|
|
32
306
|
return !!result && result.holder === holderId;
|
|
33
307
|
} catch (err) {
|
|
@@ -35,6 +309,68 @@ function createMongoLockAdapter(options = {}) {
|
|
|
35
309
|
throw err;
|
|
36
310
|
}
|
|
37
311
|
},
|
|
312
|
+
async acquire(name, holderId, leaseMs) {
|
|
313
|
+
const ms = leaseMs > 0 ? leaseMs : defaultLeaseMs;
|
|
314
|
+
const now = nowFn();
|
|
315
|
+
const expiresAt = new Date(now.getTime() + ms);
|
|
316
|
+
const holder = { $literal: holderId };
|
|
317
|
+
const won = "$__lockWon";
|
|
318
|
+
const pipeline = [
|
|
319
|
+
{ $set: { __lockWon: { $or: [{ $lt: ["$expiresAt", { $literal: now }] }, { $eq: ["$holder", holder] }] } } },
|
|
320
|
+
{ $set: {
|
|
321
|
+
acquiredAt: { $cond: [
|
|
322
|
+
won,
|
|
323
|
+
{ $cond: [
|
|
324
|
+
{ $and: [{ $eq: ["$holder", holder] }, { $ne: [{ $type: "$acquiredAt" }, "missing"] }] },
|
|
325
|
+
"$acquiredAt",
|
|
326
|
+
{ $literal: now }
|
|
327
|
+
] },
|
|
328
|
+
"$acquiredAt"
|
|
329
|
+
] },
|
|
330
|
+
holder: { $cond: [
|
|
331
|
+
won,
|
|
332
|
+
holder,
|
|
333
|
+
"$holder"
|
|
334
|
+
] },
|
|
335
|
+
expiresAt: { $cond: [
|
|
336
|
+
won,
|
|
337
|
+
{ $literal: expiresAt },
|
|
338
|
+
"$expiresAt"
|
|
339
|
+
] }
|
|
340
|
+
} },
|
|
341
|
+
{ $unset: "__lockWon" }
|
|
342
|
+
];
|
|
343
|
+
for (let attempt = 0;; attempt++) try {
|
|
344
|
+
const before = await model.findOneAndUpdate({ _id: name }, pipeline, {
|
|
345
|
+
upsert: true,
|
|
346
|
+
returnDocument: "before",
|
|
347
|
+
updatePipeline: true
|
|
348
|
+
}).lean();
|
|
349
|
+
if (!before) return {
|
|
350
|
+
acquired: true,
|
|
351
|
+
holder: holderId,
|
|
352
|
+
acquiredAt: now,
|
|
353
|
+
expiresAt
|
|
354
|
+
};
|
|
355
|
+
const mine = before.holder === holderId;
|
|
356
|
+
if (mine || before.expiresAt.getTime() < now.getTime()) return {
|
|
357
|
+
acquired: true,
|
|
358
|
+
holder: holderId,
|
|
359
|
+
previousHolder: before.holder,
|
|
360
|
+
acquiredAt: mine && before.acquiredAt ? before.acquiredAt : now,
|
|
361
|
+
expiresAt
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
acquired: false,
|
|
365
|
+
holder: before.holder,
|
|
366
|
+
acquiredAt: before.acquiredAt,
|
|
367
|
+
expiresAt: before.expiresAt
|
|
368
|
+
};
|
|
369
|
+
} catch (err) {
|
|
370
|
+
if (err?.code === 11e3 && attempt < 3) continue;
|
|
371
|
+
throw err;
|
|
372
|
+
}
|
|
373
|
+
},
|
|
38
374
|
async release(name, holderId) {
|
|
39
375
|
return !!await model.findOneAndDelete({
|
|
40
376
|
_id: name,
|
|
@@ -44,7 +380,7 @@ function createMongoLockAdapter(options = {}) {
|
|
|
44
380
|
async inspect(name) {
|
|
45
381
|
const doc = await model.findById(name).lean();
|
|
46
382
|
if (!doc) return null;
|
|
47
|
-
if (doc.expiresAt.getTime() <=
|
|
383
|
+
if (doc.expiresAt.getTime() <= nowFn().getTime()) return null;
|
|
48
384
|
return {
|
|
49
385
|
name,
|
|
50
386
|
holder: doc.holder,
|
|
@@ -88,5 +424,4 @@ function getOrCreateModel(conn, modelName, collectionName) {
|
|
|
88
424
|
schema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
89
425
|
return conn.model(modelName, schema, collectionName);
|
|
90
426
|
}
|
|
91
|
-
|
|
92
|
-
export { createMongoLockAdapter };
|
|
427
|
+
export { createMongoDailyLedger, createMongoLockAdapter };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// src/utils/logger.ts
|
|
2
2
|
const noop = () => {};
|
|
3
3
|
let current = {
|
|
4
4
|
warn: console.warn.bind(console),
|
|
@@ -22,5 +22,4 @@ function configureLogger(config) {
|
|
|
22
22
|
function warn(message, ...args) {
|
|
23
23
|
current.warn(message, ...args);
|
|
24
24
|
}
|
|
25
|
-
//#endregion
|
|
26
25
|
export { warn as n, configureLogger as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createError } from "./error-
|
|
2
|
-
|
|
1
|
+
import { t as createError } from "./error-DjdmPLWO.mjs";
|
|
2
|
+
// src/plugins/batch-operations.plugin.ts
|
|
3
3
|
/**
|
|
4
4
|
* Batch operations plugin.
|
|
5
5
|
*
|
|
@@ -31,7 +31,8 @@ const BULK_WRITE_OPTIONS = [
|
|
|
31
31
|
"bypassDocumentValidation",
|
|
32
32
|
"comment",
|
|
33
33
|
"let",
|
|
34
|
-
"ignoreUndefined"
|
|
34
|
+
"ignoreUndefined",
|
|
35
|
+
"maxTimeMS"
|
|
35
36
|
];
|
|
36
37
|
/**
|
|
37
38
|
* Driver options that EXIST but this method does not forward.
|
|
@@ -51,7 +52,6 @@ const BULK_WRITE_OPTIONS = [
|
|
|
51
52
|
const UNSUPPORTED_DRIVER_OPTIONS = /* @__PURE__ */ new Set([
|
|
52
53
|
"forceServerObjectId",
|
|
53
54
|
"timeoutMS",
|
|
54
|
-
"maxTimeMS",
|
|
55
55
|
"retryWrites",
|
|
56
56
|
"checkKeys",
|
|
57
57
|
"serializeFunctions",
|
|
@@ -123,8 +123,7 @@ function batchOperationsPlugin() {
|
|
|
123
123
|
}
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
-
|
|
127
|
-
//#region src/plugins/method-registry.plugin.ts
|
|
126
|
+
// src/plugins/method-registry.plugin.ts
|
|
128
127
|
/**
|
|
129
128
|
* Method registry plugin that enables dynamic method registration
|
|
130
129
|
*/
|
|
@@ -158,5 +157,4 @@ function methodRegistryPlugin() {
|
|
|
158
157
|
}
|
|
159
158
|
};
|
|
160
159
|
}
|
|
161
|
-
//#endregion
|
|
162
160
|
export { batchOperationsPlugin as n, methodRegistryPlugin as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import mongoose from "mongoose";
|
|
2
|
-
|
|
2
|
+
// src/model-registry.ts
|
|
3
3
|
/**
|
|
4
4
|
* Model registration — the ONE place `mongoose.models` is touched.
|
|
5
5
|
*
|
|
@@ -63,5 +63,4 @@ function defineModel(name, schema, options) {
|
|
|
63
63
|
}
|
|
64
64
|
return target ? target.model(name, schema(), options.collection) : mongoose.model(name, schema(), options.collection);
|
|
65
65
|
}
|
|
66
|
-
//#endregion
|
|
67
66
|
export { defineModel as n, isModelRegistered as r, ModelCollisionError as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { r as isObjectIdInstance } from "./id-resolution-
|
|
1
|
+
import { r as isObjectIdInstance } from "./id-resolution-Cqh49d9J.mjs";
|
|
2
2
|
import mongoose from "mongoose";
|
|
3
3
|
import { applyFieldRules, collectFieldsToOmit } from "@classytic/repo-core/schema";
|
|
4
|
-
|
|
4
|
+
// src/utils/mongooseToJsonSchema.ts
|
|
5
5
|
/**
|
|
6
6
|
* Mongoose → JSON Schema converter.
|
|
7
7
|
*
|
|
@@ -600,5 +600,4 @@ function buildJsonSchemaForQuery(_tree, options) {
|
|
|
600
600
|
for (const [k, v] of Object.entries(filterable)) if (basePagination.properties) basePagination.properties[k] = v && typeof v === "object" && "type" in v ? v : { type: "string" };
|
|
601
601
|
return basePagination;
|
|
602
602
|
}
|
|
603
|
-
//#endregion
|
|
604
603
|
export { buildCrudSchemasFromMongooseSchema as n, buildCrudSchemasFromModel as t };
|