@classytic/mongokit 3.36.0 → 3.39.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/dist/{PaginationEngine-CBS1XGzp.d.mts → PaginationEngine-D-P19Y9m.d.mts} +34 -9
- package/dist/PaginationEngine-DQ7beTCR.mjs +1182 -0
- package/dist/{Repository-BArvU32x.mjs → Repository-D7fDpyFx.mjs} +405 -37
- package/dist/{Repository-BdSkQ8vc.d.mts → Repository-IxI983_m.d.mts} +55 -8
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.mjs +1 -1
- package/dist/adapter/index.d.mts +21 -0
- package/dist/adapter/index.mjs +1 -1
- package/dist/{adapter-Bfqz8yHE.mjs → adapter-BrVfPFSW.mjs} +45 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.d.mts +78 -1
- package/dist/better-auth/index.mjs +233 -3
- package/dist/{create-repository-BU4x9Kb0.d.mts → create-repository-CRXjDa3N.d.mts} +5 -5
- package/dist/{create-repository-CPNbktFG.mjs → create-repository-DOe9E_5F.mjs} +3 -3
- package/dist/{index-DKEql4cL.d.mts → index-CmjNyulu.d.mts} +67 -8
- package/dist/index.d.mts +77 -13
- package/dist/index.mjs +31 -8
- package/dist/indexes/verify-indexes.d.mts +69 -0
- package/dist/indexes/verify-indexes.mjs +76 -0
- package/dist/{method-registry.plugin-DblM7V6j.mjs → method-registry.plugin-C63pGn1d.mjs} +54 -4
- package/dist/{multi-tenant.plugin-BwSvTirB.d.mts → multi-tenant.plugin-B-tzNeYL.d.mts} +60 -4
- package/dist/{operations-DZodvjC-.d.mts → operations-CrRIoc8Z.d.mts} +22 -7
- package/dist/outbox/index.d.mts +1 -1
- package/dist/outbox/index.mjs +2 -2
- package/dist/pagination/PaginationEngine.d.mts +1 -1
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/pagination/utils/limits.d.mts +47 -0
- package/dist/pagination/utils/limits.mjs +85 -0
- package/dist/pagination-BpIv2bdS.d.mts +364 -0
- package/dist/{plugin-options-DXnlKM3B.d.mts → plugin-options-BuvtOSZf.d.mts} +2 -2
- package/dist/plugins/index.d.mts +3 -3
- package/dist/plugins/index.mjs +3 -3
- package/dist/{repository-H_ffGgpE.d.mts → repository-ZbLtL7Nk.d.mts} +30 -4
- package/dist/tenant/index.d.mts +89 -0
- package/dist/tenant/index.mjs +94 -0
- package/dist/testkit/index.d.mts +2 -2
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-BEc6TANm.mjs → timestamp.plugin-B5eiaItQ.mjs} +75 -8
- package/dist/{to-plain-Dyz_qvuo.d.mts → to-plain-B8ufk3Qz.d.mts} +2 -2
- package/dist/{update-jSXp3nu3.mjs → update-c_01A0if.mjs} +126 -73
- package/dist/utils/index.d.mts +1 -1
- package/dist/{validation-chain.plugin-CyK1s9Ze.mjs → validation-chain.plugin-Bju1D1Sn.mjs} +2 -2
- package/dist/{validation-chain.plugin-yQRob3Av.d.mts → validation-chain.plugin-DvrLJIbJ.d.mts} +3 -19
- package/package.json +15 -3
- package/dist/PaginationEngine-GdyX29tE.mjs +0 -797
- package/dist/core-62oo3N1C.d.mts +0 -50
- package/dist/pagination-jSUdrACK.d.mts +0 -158
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { _ as UpdateOptions, a as CreateOptions, b as LookupOptions, c as FindOneAndUpdateOptions, d as LookupPopulateResult, g as UpdateManyResult, h as SessionOptions, m as ReadOptions, o as DeleteManyResult, p as OperationOptions, r as CacheableOptions, s as DeleteResult, t as AggregateOptions, u as LookupPopulateOptions, v as WithTransactionOptions, y as LookupBuilder } from "./operations-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as PaginationEngine } from "./PaginationEngine-CBS1XGzp.mjs";
|
|
1
|
+
import { _ as UpdateOptions, a as CreateOptions, b as LookupOptions, c as FindOneAndUpdateOptions, d as LookupPopulateResult, g as UpdateManyResult, h as SessionOptions, m as ReadOptions, o as DeleteManyResult, p as OperationOptions, r as CacheableOptions, s as DeleteResult, t as AggregateOptions, u as LookupPopulateOptions, v as WithTransactionOptions, y as LookupBuilder } from "./operations-CrRIoc8Z.mjs";
|
|
2
|
+
import { _ as PopulateSpec, g as ObjectId, i as CountStrategy, r as CollationOptions, t as AggregatePaginationOptions, u as PaginationConfig, v as ReadPreferenceType, x as SortSpec$1, y as SelectSpec } from "./pagination-BpIv2bdS.mjs";
|
|
3
|
+
import { _ as RepositoryOptions, d as PluginType, f as PrioritizedHook, o as Middleware, p as RepositoryContext } from "./repository-ZbLtL7Nk.mjs";
|
|
4
|
+
import { t as PaginationEngine } from "./PaginationEngine-D-P19Y9m.mjs";
|
|
6
5
|
import { AggPaginationRequest, AggRequest, AggResult, ArchiveOptions, ArchiveResult, ArchiveSink, ChangeEvent, FilterInput, KeysetAggPaginationResult, QueryOptions, RepoCapabilities, RepositoryBase, TenantPurgeOptions, TenantPurgeResult, TenantPurgeStrategy, WatchOptions } from "@classytic/repo-core/repository";
|
|
7
6
|
import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
|
|
8
7
|
import { Filter } from "@classytic/repo-core/filter";
|
|
@@ -580,6 +579,18 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
580
579
|
* the `Middleware<TDoc>` type for the protocol.
|
|
581
580
|
*/
|
|
582
581
|
private readonly _middlewares;
|
|
582
|
+
/**
|
|
583
|
+
* Shard-key guard built once from `options.distribution`; `undefined`
|
|
584
|
+
* when not configured so the per-op check is a single falsy test.
|
|
585
|
+
*/
|
|
586
|
+
private readonly _checkDistribution;
|
|
587
|
+
/**
|
|
588
|
+
* Whether id-addressed verbs are guarded too — true unless the
|
|
589
|
+
* distribution key IS the id field, where an `_id` match is genuinely
|
|
590
|
+
* routed and guarding would flag every by-id call. See
|
|
591
|
+
* {@link ID_ADDRESSED_OPERATIONS}.
|
|
592
|
+
*/
|
|
593
|
+
private readonly _guardIdAddressed;
|
|
583
594
|
constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
|
|
584
595
|
/**
|
|
585
596
|
* Live read-through view of the hook engine's listener registry.
|
|
@@ -860,8 +871,12 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
860
871
|
* repo without a filter to observe deletes.
|
|
861
872
|
* - `options.signal` ends the iterator (closes the stream). A
|
|
862
873
|
* pre-aborted signal rejects at the op boundary like every other op.
|
|
863
|
-
* - `
|
|
864
|
-
*
|
|
874
|
+
* - Every yielded event carries `resumeToken` (the change document's
|
|
875
|
+
* `_id`). Persist the last one you finished processing and pass it
|
|
876
|
+
* back as `options.resumeAfter` — or `options.startAfter` to also
|
|
877
|
+
* step past an invalidate event — for at-least-once consumption
|
|
878
|
+
* across restarts. Setting both is rejected up front (Mongo forbids
|
|
879
|
+
* the combination; the driver error is far less legible).
|
|
865
880
|
*
|
|
866
881
|
* Context keys for policy plugins (`organizationId`, `bypassTenant`,
|
|
867
882
|
* `includeDeleted`, ...) ride the options bag at runtime — same as every
|
|
@@ -906,6 +921,8 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
906
921
|
sort?: SortSpec$1 | string;
|
|
907
922
|
cursor?: string;
|
|
908
923
|
after?: string;
|
|
924
|
+
/** Cursor for the PREVIOUS page. Mutually exclusive with `after`/`cursor`. */
|
|
925
|
+
before?: string;
|
|
909
926
|
page?: number;
|
|
910
927
|
pagination?: {
|
|
911
928
|
page?: number;
|
|
@@ -916,7 +933,9 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
916
933
|
mode?: 'offset' | 'keyset';
|
|
917
934
|
hint?: string | Record<string, 1 | -1>;
|
|
918
935
|
maxTimeMS?: number;
|
|
919
|
-
countStrategy?:
|
|
936
|
+
countStrategy?: CountStrategy;
|
|
937
|
+
/** Ceiling for `countStrategy: 'capped'`. */
|
|
938
|
+
countLimit?: number;
|
|
920
939
|
readPreference?: ReadPreferenceType;
|
|
921
940
|
/** Advanced populate options (from QueryParser or Arc's BaseController) */
|
|
922
941
|
populateOptions?: PopulateOptions[];
|
|
@@ -1782,6 +1801,16 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1782
1801
|
* 4. DEFAULT (500) — user-registered hooks
|
|
1783
1802
|
*/
|
|
1784
1803
|
_buildContext(operation: string, options: Record<string, unknown> | object): Promise<RepositoryContext>;
|
|
1804
|
+
/**
|
|
1805
|
+
* Feed the resolved filter of a guarded verb to the distribution guard.
|
|
1806
|
+
* The slot follows `OP_REGISTRY[op].policyKey` (`query` vs `filters`);
|
|
1807
|
+
* the portable aggregate verbs ALSO carry the caller's filter on
|
|
1808
|
+
* `aggRequest.filter` (merged into the pipeline later by
|
|
1809
|
+
* `_injectPolicyScopeIntoAgg`), and the kit-native pipeline verbs may
|
|
1810
|
+
* carry it as leading `$match` stages — both are folded in so a key the
|
|
1811
|
+
* caller did supply is never reported missing.
|
|
1812
|
+
*/
|
|
1813
|
+
private _guardDistribution;
|
|
1785
1814
|
/**
|
|
1786
1815
|
* Walk the well-known filter-carrying keys (`query`, `filters`,
|
|
1787
1816
|
* `having`) and route any Filter IR through `compileFilterToMongo`.
|
|
@@ -1796,6 +1825,9 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1796
1825
|
private _resolveSort;
|
|
1797
1826
|
/**
|
|
1798
1827
|
* Detect whether to use keyset (cursor) or offset (page) pagination.
|
|
1828
|
+
* Precedence: explicit `mode` → `page` (offset) → `after` (keyset) →
|
|
1829
|
+
* `PaginationConfig.defaultMode` → the sort heuristic (a non-default
|
|
1830
|
+
* sort with no page means the caller wants a cursor).
|
|
1799
1831
|
*/
|
|
1800
1832
|
private _detectPaginationMode;
|
|
1801
1833
|
/**
|
|
@@ -1866,7 +1898,22 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1866
1898
|
* `errorLabels` exactly as we received it. The caller still sees a
|
|
1867
1899
|
* thrown error — only the wrapping layer is skipped.
|
|
1868
1900
|
*/
|
|
1901
|
+
/**
|
|
1902
|
+
* Translate a driver error into the repository's error contract, WITHOUT
|
|
1903
|
+
* losing structured diagnostics an action attached to it.
|
|
1904
|
+
*
|
|
1905
|
+
* The translation replaces the object — an E11000 becomes a 409 — so any
|
|
1906
|
+
* property a caller was meant to read went with it. `createMany` attaches a
|
|
1907
|
+
* `partial` report naming which documents landed, and it arrived nowhere:
|
|
1908
|
+
* the report existed, the test for it failed, and the only evidence was the
|
|
1909
|
+
* absence.
|
|
1910
|
+
*
|
|
1911
|
+
* Carried explicitly rather than by copying every own-property, because the
|
|
1912
|
+
* wrap is deliberate: the point is to hide driver internals, and a blanket
|
|
1913
|
+
* copy would put them back.
|
|
1914
|
+
*/
|
|
1869
1915
|
_handleError(error: Error): HttpError;
|
|
1916
|
+
private _translateError;
|
|
1870
1917
|
}
|
|
1871
1918
|
//#endregion
|
|
1872
1919
|
export { AggregationPlan as a, SortOrder as c, AggregationBuilder as i, SortSpec as l, Repository as n, GroupSpec as o, TransitionMachine as r, ProjectionSpec as s, HOOK_PRIORITY as t, VectorSearchOptions as u };
|
package/dist/actions/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as create_d_exports, i as delete_d_exports, n as update_d_exports, r as read_d_exports, u as aggregate_d_exports } from "../index-CmjNyulu.mjs";
|
|
2
2
|
export { aggregate_d_exports as aggregate, create_d_exports as create, delete_d_exports as deleteActions, read_d_exports as read, update_d_exports as update };
|
package/dist/actions/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { b as create_exports, i as update_exports, m as aggregate_exports, p as delete_exports, u as read_exports } from "../update-c_01A0if.mjs";
|
|
3
3
|
//#region src/actions/index.ts
|
|
4
4
|
var actions_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
aggregate: () => aggregate_exports,
|
package/dist/adapter/index.d.mts
CHANGED
|
@@ -129,6 +129,27 @@ declare class MongooseAdapter<TDoc = unknown, TModelDoc = unknown> implements Da
|
|
|
129
129
|
* `matchesRecordFilter` — one shared contract/IR across every kit; its
|
|
130
130
|
* evaluator is id-coercion aware so a Mongo `ObjectId` `_id` matches its
|
|
131
131
|
* string form. Arrow field: keeps its binding when passed by reference.
|
|
132
|
+
*
|
|
133
|
+
* The document is flattened to a plain record FIRST, and that step is the
|
|
134
|
+
* whole reason this is not a one-liner. The evaluator resolves field paths
|
|
135
|
+
* with `Object.hasOwn`, own-properties-only and deliberately so: it must not
|
|
136
|
+
* walk a prototype chain, or a crafted `{ 'constructor.x': … }` policy filter
|
|
137
|
+
* could match an inherited member. A HYDRATED Mongoose document keeps its
|
|
138
|
+
* values in `_doc` and exposes them through prototype getters, so it has no
|
|
139
|
+
* own `status`, no own `organizationId` — every path resolves to absent and
|
|
140
|
+
* the filter matches NOTHING. Fail-closed, so it does not leak; it denies.
|
|
141
|
+
*
|
|
142
|
+
* That turned any policy at all into a blanket 404 on every non-compound
|
|
143
|
+
* fetch (`getBySlug`, cache revalidation, the realtime feed) while the same
|
|
144
|
+
* policy worked perfectly on list/get, which filter at the DB. The asymmetry
|
|
145
|
+
* is what made it so hard to see: a resource reads fine until the day someone
|
|
146
|
+
* gives it a row-level policy, and then one route starts lying.
|
|
147
|
+
*
|
|
148
|
+
* Knowing that a Mongoose document is not a plain record is the MONGO kit's
|
|
149
|
+
* job, not the dialect-agnostic evaluator's, so the normalisation lives here.
|
|
150
|
+
* `toObject` rather than `toJSON`: it preserves `ObjectId` and `Date` values,
|
|
151
|
+
* which the evaluator compares by type. A `.lean()` result is already plain
|
|
152
|
+
* and passes through untouched.
|
|
132
153
|
*/
|
|
133
154
|
readonly matchesFilter: (item: unknown, filters: Record<string, unknown>) => boolean;
|
|
134
155
|
/**
|
package/dist/adapter/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-
|
|
1
|
+
import { n as createMongooseAdapter, r as isMongooseModel, t as MongooseAdapter } from "../adapter-BrVfPFSW.mjs";
|
|
2
2
|
export { MongooseAdapter, createMongooseAdapter, isMongooseModel };
|
|
@@ -19,6 +19,29 @@ const REQUIRED_REPO_METHODS = [
|
|
|
19
19
|
"update",
|
|
20
20
|
"delete"
|
|
21
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* A Mongoose document as a plain record, for anything that reads fields directly.
|
|
24
|
+
*
|
|
25
|
+
* Hydrated documents hold their values in `_doc` and surface them through prototype getters, so
|
|
26
|
+
* `Object.hasOwn(doc, 'status')` is FALSE even though `doc.status` reads fine. Any consumer that
|
|
27
|
+
* refuses to walk the prototype chain — as a prototype-pollution-safe field resolver must — sees
|
|
28
|
+
* an empty document. See `matchesFilter` for what that cost us.
|
|
29
|
+
*
|
|
30
|
+
* `toObject` over `toJSON`: it keeps `ObjectId` and `Date` as values rather than stringifying
|
|
31
|
+
* them, and callers here compare by type. Anything already plain (a `.lean()` result, a POJO) is
|
|
32
|
+
* returned untouched, and a `toObject` that throws degrades to the original rather than taking
|
|
33
|
+
* the caller down.
|
|
34
|
+
*/
|
|
35
|
+
function toPlainRecord(item) {
|
|
36
|
+
if (item === null || typeof item !== "object") return item;
|
|
37
|
+
const maybe = item;
|
|
38
|
+
if (typeof maybe.toObject !== "function") return item;
|
|
39
|
+
try {
|
|
40
|
+
return maybe.toObject();
|
|
41
|
+
} catch {
|
|
42
|
+
return item;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
22
45
|
function describeValue(value) {
|
|
23
46
|
if (value === null) return "null";
|
|
24
47
|
if (value === void 0) return "undefined";
|
|
@@ -215,8 +238,29 @@ var MongooseAdapter = class {
|
|
|
215
238
|
* `matchesRecordFilter` — one shared contract/IR across every kit; its
|
|
216
239
|
* evaluator is id-coercion aware so a Mongo `ObjectId` `_id` matches its
|
|
217
240
|
* string form. Arrow field: keeps its binding when passed by reference.
|
|
241
|
+
*
|
|
242
|
+
* The document is flattened to a plain record FIRST, and that step is the
|
|
243
|
+
* whole reason this is not a one-liner. The evaluator resolves field paths
|
|
244
|
+
* with `Object.hasOwn`, own-properties-only and deliberately so: it must not
|
|
245
|
+
* walk a prototype chain, or a crafted `{ 'constructor.x': … }` policy filter
|
|
246
|
+
* could match an inherited member. A HYDRATED Mongoose document keeps its
|
|
247
|
+
* values in `_doc` and exposes them through prototype getters, so it has no
|
|
248
|
+
* own `status`, no own `organizationId` — every path resolves to absent and
|
|
249
|
+
* the filter matches NOTHING. Fail-closed, so it does not leak; it denies.
|
|
250
|
+
*
|
|
251
|
+
* That turned any policy at all into a blanket 404 on every non-compound
|
|
252
|
+
* fetch (`getBySlug`, cache revalidation, the realtime feed) while the same
|
|
253
|
+
* policy worked perfectly on list/get, which filter at the DB. The asymmetry
|
|
254
|
+
* is what made it so hard to see: a resource reads fine until the day someone
|
|
255
|
+
* gives it a row-level policy, and then one route starts lying.
|
|
256
|
+
*
|
|
257
|
+
* Knowing that a Mongoose document is not a plain record is the MONGO kit's
|
|
258
|
+
* job, not the dialect-agnostic evaluator's, so the normalisation lives here.
|
|
259
|
+
* `toObject` rather than `toJSON`: it preserves `ObjectId` and `Date` values,
|
|
260
|
+
* which the evaluator compares by type. A `.lean()` result is already plain
|
|
261
|
+
* and passes through untouched.
|
|
218
262
|
*/
|
|
219
|
-
matchesFilter = (item, filters) => matchesRecordFilter(item, filters);
|
|
263
|
+
matchesFilter = (item, filters) => matchesRecordFilter(toPlainRecord(item), filters);
|
|
220
264
|
/**
|
|
221
265
|
* No-op — mongokit's per-call resources (`watch()` change streams,
|
|
222
266
|
* `cursor()` iterators) are released by their own `AbortSignal` /
|
package/dist/ai/index.d.mts
CHANGED
|
@@ -259,6 +259,83 @@ declare function ensureBetterAuthSessionTtl(connection: {
|
|
|
259
259
|
collection?: string;
|
|
260
260
|
indexName?: string;
|
|
261
261
|
}): Promise<boolean>;
|
|
262
|
+
/**
|
|
263
|
+
* One declared index on a Better Auth collection.
|
|
264
|
+
*
|
|
265
|
+
* `unique` is a REQUEST, not a guarantee: {@link ensureBetterAuthIndexes}
|
|
266
|
+
* downgrades to a non-unique index when existing rows already violate it,
|
|
267
|
+
* because failing a boot over historical duplicates is worse than running with
|
|
268
|
+
* a plain lookup index and reporting the degradation.
|
|
269
|
+
*/
|
|
270
|
+
interface BetterAuthIndexSpec {
|
|
271
|
+
/** Canonical collection name (`'user'`, `'session'`, ...). */
|
|
272
|
+
collection: string;
|
|
273
|
+
keys: Record<string, 1 | -1>;
|
|
274
|
+
name: string;
|
|
275
|
+
unique?: boolean;
|
|
276
|
+
/** Present on self-expiring rows; always paired with `expireAfterSeconds: 0`. */
|
|
277
|
+
ttl?: boolean;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The indexes Better Auth's own queries need, which its mongo adapter does NOT
|
|
281
|
+
* create. Grouped by the plugin that owns the collection so a host only pays
|
|
282
|
+
* for what it enabled.
|
|
283
|
+
*
|
|
284
|
+
* Every entry below backs a query BA runs on a hot path:
|
|
285
|
+
* - `session.token` — read on EVERY authenticated request
|
|
286
|
+
* - `user.email` — read on every sign-in / sign-up, and must be unique
|
|
287
|
+
* - `account.(providerId|issuer, accountId)` — the credential lookup at sign-in
|
|
288
|
+
* - `member.(organizationId, userId)` — the membership check on org-scoped requests
|
|
289
|
+
* Without them each of those is a collection scan, and `email` / `slug` /
|
|
290
|
+
* `token` have no uniqueness guarantee at all.
|
|
291
|
+
*
|
|
292
|
+
* TTL entries (`expiresAt`) additionally keep the collection BOUNDED — see
|
|
293
|
+
* {@link ensureBetterAuthSessionTtl} for the measurements that motivated it.
|
|
294
|
+
*/
|
|
295
|
+
declare const BA_INDEXES_BY_PLUGIN: Record<string, readonly BetterAuthIndexSpec[]>;
|
|
296
|
+
/** Outcome of one declared index. */
|
|
297
|
+
interface BetterAuthIndexResult {
|
|
298
|
+
collection: string;
|
|
299
|
+
name: string;
|
|
300
|
+
status: 'created' | 'degraded' | 'failed';
|
|
301
|
+
/** Set on `degraded` (unique dropped) and `failed`. */
|
|
302
|
+
reason?: string;
|
|
303
|
+
}
|
|
304
|
+
interface EnsureBetterAuthIndexesOptions {
|
|
305
|
+
/** Plugin sets to include beyond `core` (always implied). */
|
|
306
|
+
plugins?: BetterAuthPluginKey$1[];
|
|
307
|
+
/** Mirror BA's `usePlural` — appends `s` to every collection name. */
|
|
308
|
+
usePlural?: boolean;
|
|
309
|
+
/** Per-collection name override (mirrors BA's `user.modelName`). */
|
|
310
|
+
modelOverrides?: Partial<Record<string, string>>;
|
|
311
|
+
/** Canonical collection names to skip entirely. */
|
|
312
|
+
exclude?: string[];
|
|
313
|
+
}
|
|
314
|
+
interface IndexCreator {
|
|
315
|
+
collection(name: string): {
|
|
316
|
+
createIndex(keys: Record<string, 1 | -1>, opts: Record<string, unknown>): Promise<unknown>;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Create every index Better Auth's queries need. Idempotent, never throws.
|
|
321
|
+
*
|
|
322
|
+
* BA's mongo adapter creates NO indexes — not even on `session.token`, which is
|
|
323
|
+
* read on every authenticated request, nor a unique one on `user.email`. On a
|
|
324
|
+
* small database nobody notices; the cost arrives as a full collection scan per
|
|
325
|
+
* login once the tables grow, and until then nothing stops a duplicate email.
|
|
326
|
+
*
|
|
327
|
+
* This lives in the kit rather than a host for the same reason
|
|
328
|
+
* {@link ensureBetterAuthSessionTtl} does: the kit is what knows BA's collection
|
|
329
|
+
* names, so fixing it here protects every consumer instead of one host.
|
|
330
|
+
*
|
|
331
|
+
* **Uniqueness degrades rather than fails.** A `unique: true` spec is retried
|
|
332
|
+
* without uniqueness when the collection already holds violating rows, and
|
|
333
|
+
* reported as `degraded` — a boot must not die because historical data has a
|
|
334
|
+
* duplicate. Inspect the returned report to find those.
|
|
335
|
+
*
|
|
336
|
+
* Call once at boot, after the connection is live. Safe to call again.
|
|
337
|
+
*/
|
|
338
|
+
declare function ensureBetterAuthIndexes(connection: IndexCreator, options?: EnsureBetterAuthIndexesOptions): Promise<BetterAuthIndexResult[]>;
|
|
262
339
|
interface SessionUpdaterLike {
|
|
263
340
|
updateMany(filter: Record<string, unknown>, update: Record<string, unknown>): Promise<{
|
|
264
341
|
modifiedCount?: number;
|
|
@@ -293,4 +370,4 @@ declare function clearActiveOrganizationFromSessions(sessions: SessionUpdaterLik
|
|
|
293
370
|
userId?: unknown;
|
|
294
371
|
}): Promise<number>;
|
|
295
372
|
//#endregion
|
|
296
|
-
export { BetterAuthIdType, BetterAuthInstance, BetterAuthOverlayOptions, type BetterAuthPluginKey, MongooseLike, RegisterBetterAuthStubsOptions, SessionUpdaterLike, clearActiveOrganizationFromSessions, createBetterAuthOverlay, ensureBetterAuthSessionTtl, registerBetterAuthStubs };
|
|
373
|
+
export { BA_INDEXES_BY_PLUGIN, BetterAuthIdType, BetterAuthIndexResult, BetterAuthIndexSpec, BetterAuthInstance, BetterAuthOverlayOptions, type BetterAuthPluginKey, EnsureBetterAuthIndexesOptions, IndexCreator, MongooseLike, RegisterBetterAuthStubsOptions, SessionUpdaterLike, clearActiveOrganizationFromSessions, createBetterAuthOverlay, ensureBetterAuthIndexes, ensureBetterAuthSessionTtl, registerBetterAuthStubs };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as Repository } from "../Repository-
|
|
2
|
-
import { n as createMongooseAdapter } from "../adapter-
|
|
1
|
+
import { n as Repository } from "../Repository-D7fDpyFx.mjs";
|
|
2
|
+
import { n as createMongooseAdapter } from "../adapter-BrVfPFSW.mjs";
|
|
3
3
|
import { asReadOnlyRepo } from "@classytic/repo-core/repository";
|
|
4
4
|
import { pluralizeBetterAuthCollection, resolveBetterAuthCollections } from "@classytic/repo-core/better-auth";
|
|
5
5
|
//#region src/better-auth/index.ts
|
|
@@ -203,6 +203,236 @@ async function ensureBetterAuthSessionTtl(connection, options = {}) {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
/**
|
|
206
|
+
* The indexes Better Auth's own queries need, which its mongo adapter does NOT
|
|
207
|
+
* create. Grouped by the plugin that owns the collection so a host only pays
|
|
208
|
+
* for what it enabled.
|
|
209
|
+
*
|
|
210
|
+
* Every entry below backs a query BA runs on a hot path:
|
|
211
|
+
* - `session.token` — read on EVERY authenticated request
|
|
212
|
+
* - `user.email` — read on every sign-in / sign-up, and must be unique
|
|
213
|
+
* - `account.(providerId|issuer, accountId)` — the credential lookup at sign-in
|
|
214
|
+
* - `member.(organizationId, userId)` — the membership check on org-scoped requests
|
|
215
|
+
* Without them each of those is a collection scan, and `email` / `slug` /
|
|
216
|
+
* `token` have no uniqueness guarantee at all.
|
|
217
|
+
*
|
|
218
|
+
* TTL entries (`expiresAt`) additionally keep the collection BOUNDED — see
|
|
219
|
+
* {@link ensureBetterAuthSessionTtl} for the measurements that motivated it.
|
|
220
|
+
*/
|
|
221
|
+
const BA_INDEXES_BY_PLUGIN = {
|
|
222
|
+
core: [
|
|
223
|
+
{
|
|
224
|
+
collection: "user",
|
|
225
|
+
keys: { email: 1 },
|
|
226
|
+
name: "ba_user_email",
|
|
227
|
+
unique: true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
collection: "session",
|
|
231
|
+
keys: { token: 1 },
|
|
232
|
+
name: "ba_session_token",
|
|
233
|
+
unique: true
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
collection: "session",
|
|
237
|
+
keys: { userId: 1 },
|
|
238
|
+
name: "ba_session_userId"
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
collection: "session",
|
|
242
|
+
keys: { expiresAt: 1 },
|
|
243
|
+
name: "ttl_session_expiresAt",
|
|
244
|
+
ttl: true
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
collection: "account",
|
|
248
|
+
keys: { userId: 1 },
|
|
249
|
+
name: "ba_account_userId"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
collection: "account",
|
|
253
|
+
keys: {
|
|
254
|
+
providerId: 1,
|
|
255
|
+
accountId: 1
|
|
256
|
+
},
|
|
257
|
+
name: "ba_account_provider_accountId"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
collection: "account",
|
|
261
|
+
keys: {
|
|
262
|
+
issuer: 1,
|
|
263
|
+
accountId: 1
|
|
264
|
+
},
|
|
265
|
+
name: "ba_account_issuer_accountId"
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
collection: "verification",
|
|
269
|
+
keys: { identifier: 1 },
|
|
270
|
+
name: "ba_verification_identifier"
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
collection: "verification",
|
|
274
|
+
keys: { expiresAt: 1 },
|
|
275
|
+
name: "ttl_verification_expiresAt",
|
|
276
|
+
ttl: true
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
organization: [
|
|
280
|
+
{
|
|
281
|
+
collection: "organization",
|
|
282
|
+
keys: { slug: 1 },
|
|
283
|
+
name: "ba_organization_slug",
|
|
284
|
+
unique: true
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
collection: "member",
|
|
288
|
+
keys: {
|
|
289
|
+
organizationId: 1,
|
|
290
|
+
userId: 1
|
|
291
|
+
},
|
|
292
|
+
name: "ba_member_org_user",
|
|
293
|
+
unique: true
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
collection: "member",
|
|
297
|
+
keys: { userId: 1 },
|
|
298
|
+
name: "ba_member_userId"
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
collection: "invitation",
|
|
302
|
+
keys: { organizationId: 1 },
|
|
303
|
+
name: "ba_invitation_org"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
collection: "invitation",
|
|
307
|
+
keys: { email: 1 },
|
|
308
|
+
name: "ba_invitation_email"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
collection: "invitation",
|
|
312
|
+
keys: { expiresAt: 1 },
|
|
313
|
+
name: "ttl_invitation_expiresAt",
|
|
314
|
+
ttl: true
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
"organization-teams": [{
|
|
318
|
+
collection: "team",
|
|
319
|
+
keys: { organizationId: 1 },
|
|
320
|
+
name: "ba_team_org"
|
|
321
|
+
}, {
|
|
322
|
+
collection: "teamMember",
|
|
323
|
+
keys: {
|
|
324
|
+
teamId: 1,
|
|
325
|
+
userId: 1
|
|
326
|
+
},
|
|
327
|
+
name: "ba_teamMember_team_user"
|
|
328
|
+
}],
|
|
329
|
+
twoFactor: [{
|
|
330
|
+
collection: "twoFactor",
|
|
331
|
+
keys: { userId: 1 },
|
|
332
|
+
name: "ba_twoFactor_userId"
|
|
333
|
+
}],
|
|
334
|
+
deviceAuthorization: [
|
|
335
|
+
{
|
|
336
|
+
collection: "deviceCode",
|
|
337
|
+
keys: { deviceCode: 1 },
|
|
338
|
+
name: "ba_deviceCode_code"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
collection: "deviceCode",
|
|
342
|
+
keys: { userCode: 1 },
|
|
343
|
+
name: "ba_deviceCode_userCode"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
collection: "deviceCode",
|
|
347
|
+
keys: { expiresAt: 1 },
|
|
348
|
+
name: "ttl_deviceCode_expiresAt",
|
|
349
|
+
ttl: true
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Create every index Better Auth's queries need. Idempotent, never throws.
|
|
355
|
+
*
|
|
356
|
+
* BA's mongo adapter creates NO indexes — not even on `session.token`, which is
|
|
357
|
+
* read on every authenticated request, nor a unique one on `user.email`. On a
|
|
358
|
+
* small database nobody notices; the cost arrives as a full collection scan per
|
|
359
|
+
* login once the tables grow, and until then nothing stops a duplicate email.
|
|
360
|
+
*
|
|
361
|
+
* This lives in the kit rather than a host for the same reason
|
|
362
|
+
* {@link ensureBetterAuthSessionTtl} does: the kit is what knows BA's collection
|
|
363
|
+
* names, so fixing it here protects every consumer instead of one host.
|
|
364
|
+
*
|
|
365
|
+
* **Uniqueness degrades rather than fails.** A `unique: true` spec is retried
|
|
366
|
+
* without uniqueness when the collection already holds violating rows, and
|
|
367
|
+
* reported as `degraded` — a boot must not die because historical data has a
|
|
368
|
+
* duplicate. Inspect the returned report to find those.
|
|
369
|
+
*
|
|
370
|
+
* Call once at boot, after the connection is live. Safe to call again.
|
|
371
|
+
*/
|
|
372
|
+
async function ensureBetterAuthIndexes(connection, options = {}) {
|
|
373
|
+
const plugins = options.plugins ?? [];
|
|
374
|
+
const excluded = new Set(options.exclude ?? []);
|
|
375
|
+
const keys = ["core", ...plugins];
|
|
376
|
+
const resolveName = (canonical) => {
|
|
377
|
+
const overridden = options.modelOverrides?.[canonical] ?? canonical;
|
|
378
|
+
return options.usePlural ? pluralizeBetterAuthCollection(overridden) : overridden;
|
|
379
|
+
};
|
|
380
|
+
const specs = [];
|
|
381
|
+
const seen = /* @__PURE__ */ new Set();
|
|
382
|
+
for (const key of keys) for (const spec of BA_INDEXES_BY_PLUGIN[key] ?? []) {
|
|
383
|
+
if (excluded.has(spec.collection)) continue;
|
|
384
|
+
const id = `${spec.collection}.${spec.name}`;
|
|
385
|
+
if (seen.has(id)) continue;
|
|
386
|
+
seen.add(id);
|
|
387
|
+
specs.push(spec);
|
|
388
|
+
}
|
|
389
|
+
const results = [];
|
|
390
|
+
for (const spec of specs) {
|
|
391
|
+
const target = resolveName(spec.collection);
|
|
392
|
+
const base = { name: spec.name };
|
|
393
|
+
if (spec.ttl) base.expireAfterSeconds = 0;
|
|
394
|
+
try {
|
|
395
|
+
await connection.collection(target).createIndex(spec.keys, {
|
|
396
|
+
...base,
|
|
397
|
+
...spec.unique ? { unique: true } : {}
|
|
398
|
+
});
|
|
399
|
+
results.push({
|
|
400
|
+
collection: target,
|
|
401
|
+
name: spec.name,
|
|
402
|
+
status: "created"
|
|
403
|
+
});
|
|
404
|
+
} catch (err) {
|
|
405
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
406
|
+
if (!spec.unique) {
|
|
407
|
+
results.push({
|
|
408
|
+
collection: target,
|
|
409
|
+
name: spec.name,
|
|
410
|
+
status: "failed",
|
|
411
|
+
reason
|
|
412
|
+
});
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
await connection.collection(target).createIndex(spec.keys, base);
|
|
417
|
+
results.push({
|
|
418
|
+
collection: target,
|
|
419
|
+
name: spec.name,
|
|
420
|
+
status: "degraded",
|
|
421
|
+
reason
|
|
422
|
+
});
|
|
423
|
+
} catch (err2) {
|
|
424
|
+
results.push({
|
|
425
|
+
collection: target,
|
|
426
|
+
name: spec.name,
|
|
427
|
+
status: "failed",
|
|
428
|
+
reason: err2 instanceof Error ? err2.message : String(err2)
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return results;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
206
436
|
* Clear a dangling `activeOrganizationId` from Better Auth `session` rows.
|
|
207
437
|
*
|
|
208
438
|
* WHY THIS EXISTS: Better Auth purges its `member` / `invitation` rows when an
|
|
@@ -233,4 +463,4 @@ async function clearActiveOrganizationFromSessions(sessions, organizationId, opt
|
|
|
233
463
|
return (await sessions.updateMany(filter, { $set: { activeOrganizationId: null } })).modifiedCount ?? 0;
|
|
234
464
|
}
|
|
235
465
|
//#endregion
|
|
236
|
-
export { clearActiveOrganizationFromSessions, createBetterAuthOverlay, ensureBetterAuthSessionTtl, registerBetterAuthStubs };
|
|
466
|
+
export { BA_INDEXES_BY_PLUGIN, clearActiveOrganizationFromSessions, createBetterAuthOverlay, ensureBetterAuthIndexes, ensureBetterAuthSessionTtl, registerBetterAuthStubs };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as RepositoryOptions, d as PluginType } from "./repository-
|
|
3
|
-
import { T as AuditTrailOptions, _ as RepositoryCachePluginOptions, i as CustomIdOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-
|
|
4
|
-
import { n as Repository } from "./Repository-
|
|
5
|
-
import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-
|
|
1
|
+
import { u as PaginationConfig } from "./pagination-BpIv2bdS.mjs";
|
|
2
|
+
import { _ as RepositoryOptions, d as PluginType } from "./repository-ZbLtL7Nk.mjs";
|
|
3
|
+
import { T as AuditTrailOptions, _ as RepositoryCachePluginOptions, i as CustomIdOptions, t as MultiTenantOptions } from "./multi-tenant.plugin-B-tzNeYL.mjs";
|
|
4
|
+
import { n as Repository } from "./Repository-IxI983_m.mjs";
|
|
5
|
+
import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-BuvtOSZf.mjs";
|
|
6
6
|
import { Model } from "mongoose";
|
|
7
7
|
//#region src/create-repository.d.ts
|
|
8
8
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as Repository } from "./Repository-
|
|
2
|
-
import { a as customIdPlugin, f as auditTrailPlugin, i as multiTenantPlugin, m as auditLogPlugin, n as softDeletePlugin, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-
|
|
3
|
-
import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-
|
|
1
|
+
import { n as Repository } from "./Repository-D7fDpyFx.mjs";
|
|
2
|
+
import { a as customIdPlugin, f as auditTrailPlugin, i as multiTenantPlugin, m as auditLogPlugin, n as softDeletePlugin, t as timestampPlugin, u as cachePlugin } from "./timestamp.plugin-B5eiaItQ.mjs";
|
|
3
|
+
import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-C63pGn1d.mjs";
|
|
4
4
|
//#region src/create-repository.ts
|
|
5
5
|
function isLoggerAudit(value) {
|
|
6
6
|
const v = value;
|