@classytic/mongokit 3.38.1 → 3.40.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-Dvl8jcRY.mjs +1198 -0
- package/dist/{Repository-Ck5x43wF.mjs → Repository-BMgPSkqZ.mjs} +672 -655
- package/dist/{Repository-BdSkQ8vc.d.mts → Repository-CR8Y9g2E.d.mts} +106 -34
- 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.mjs +2 -2
- package/dist/{create-repository-B5EulQ31.mjs → create-repository-BzBUMvd1.mjs} +3 -3
- package/dist/{create-repository-BU4x9Kb0.d.mts → create-repository-CAle1MSU.d.mts} +5 -5
- package/dist/idempotency/index.d.mts +23 -0
- package/dist/idempotency/index.mjs +134 -0
- package/dist/{index-DKEql4cL.d.mts → index-CmjNyulu.d.mts} +67 -8
- package/dist/index.d.mts +125 -35
- package/dist/index.mjs +286 -676
- package/dist/indexes/verify-indexes.d.mts +14 -1
- package/dist/indexes/verify-indexes.mjs +54 -7
- 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-CBgCo9im.d.mts} +77 -5
- 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 +7 -5
- package/dist/pagination/PaginationEngine.d.mts +1 -1
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/pagination/utils/limits.d.mts +1 -1
- package/dist/pagination/utils/limits.mjs +22 -1
- package/dist/pagination-BpIv2bdS.d.mts +364 -0
- package/dist/{plugin-options-DXnlKM3B.d.mts → plugin-options-Zk46lD6F.d.mts} +16 -2
- package/dist/plugins/index.d.mts +8 -3
- package/dist/plugins/index.mjs +99 -26
- package/dist/{repository-H_ffGgpE.d.mts → repository-odl03582.d.mts} +50 -6
- package/dist/sync/index.d.mts +40 -0
- package/dist/sync/index.mjs +334 -0
- package/dist/tenant/index.d.mts +89 -0
- package/dist/tenant/index.mjs +96 -0
- package/dist/testkit/index.d.mts +2 -2
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-CBQukQzV.mjs → timestamp.plugin-DF0gJB6p.mjs} +116 -9
- package/dist/{to-plain-Dyz_qvuo.d.mts → to-plain-k7SjNlSS.d.mts} +2 -2
- package/dist/transaction-B5L9glO0.mjs +533 -0
- package/dist/{update-jSXp3nu3.mjs → update-DG0fCwRJ.mjs} +293 -83
- package/dist/utils/index.d.mts +1 -1
- package/dist/{validation-chain.plugin-DhNyxNCw.mjs → validation-chain.plugin-BLY2gLEk.mjs} +192 -279
- package/dist/{validation-chain.plugin-yQRob3Av.d.mts → validation-chain.plugin-DYp9E7K2.d.mts} +26 -20
- package/package.json +17 -4
- package/dist/PaginationEngine-BX9o_-4R.mjs +0 -735
- 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-odl03582.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";
|
|
@@ -520,6 +519,37 @@ interface TransitionMachine {
|
|
|
520
519
|
readonly name: string;
|
|
521
520
|
assertTransition(entityId: string, from: string, to: string): void;
|
|
522
521
|
}
|
|
522
|
+
/** `Repository.getAll` parameters. */
|
|
523
|
+
type GetAllParams = {
|
|
524
|
+
filters?: Record<string, unknown>;
|
|
525
|
+
sort?: SortSpec$1 | string;
|
|
526
|
+
cursor?: string;
|
|
527
|
+
after?: string;
|
|
528
|
+
/** Cursor for the PREVIOUS page. Mutually exclusive with `after`/`cursor`. */
|
|
529
|
+
before?: string;
|
|
530
|
+
page?: number;
|
|
531
|
+
pagination?: {
|
|
532
|
+
page?: number;
|
|
533
|
+
limit?: number;
|
|
534
|
+
};
|
|
535
|
+
limit?: number;
|
|
536
|
+
search?: string;
|
|
537
|
+
mode?: 'offset' | 'keyset';
|
|
538
|
+
hint?: string | Record<string, 1 | -1>;
|
|
539
|
+
maxTimeMS?: number;
|
|
540
|
+
countStrategy?: CountStrategy;
|
|
541
|
+
/** Ceiling for `countStrategy: 'capped'`. */
|
|
542
|
+
countLimit?: number;
|
|
543
|
+
readPreference?: ReadPreferenceType;
|
|
544
|
+
/** Advanced populate options (from QueryParser or Arc's BaseController) */
|
|
545
|
+
populateOptions?: PopulateOptions[];
|
|
546
|
+
/** Collation for locale-aware string comparison */
|
|
547
|
+
collation?: CollationOptions;
|
|
548
|
+
/** Lookup configurations for $lookup joins (from QueryParser or manual) */
|
|
549
|
+
lookups?: LookupOptions[];
|
|
550
|
+
/** Skip pagination entirely — returns raw TDoc[] (same as findAll) */
|
|
551
|
+
noPagination?: boolean;
|
|
552
|
+
};
|
|
523
553
|
declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
524
554
|
/**
|
|
525
555
|
* Cache handle ATTACHED BY the unified cache plugin at wiring time —
|
|
@@ -580,6 +610,18 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
580
610
|
* the `Middleware<TDoc>` type for the protocol.
|
|
581
611
|
*/
|
|
582
612
|
private readonly _middlewares;
|
|
613
|
+
/**
|
|
614
|
+
* Shard-key guard built once from `options.distribution`; `undefined`
|
|
615
|
+
* when not configured so the per-op check is a single falsy test.
|
|
616
|
+
*/
|
|
617
|
+
private readonly _checkDistribution;
|
|
618
|
+
/**
|
|
619
|
+
* Whether id-addressed verbs are guarded too — true unless the
|
|
620
|
+
* distribution key IS the id field, where an `_id` match is genuinely
|
|
621
|
+
* routed and guarding would flag every by-id call. See
|
|
622
|
+
* {@link ID_ADDRESSED_OPERATIONS}.
|
|
623
|
+
*/
|
|
624
|
+
private readonly _guardIdAddressed;
|
|
583
625
|
constructor(Model: Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions);
|
|
584
626
|
/**
|
|
585
627
|
* Live read-through view of the hook engine's listener registry.
|
|
@@ -860,8 +902,12 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
860
902
|
* repo without a filter to observe deletes.
|
|
861
903
|
* - `options.signal` ends the iterator (closes the stream). A
|
|
862
904
|
* pre-aborted signal rejects at the op boundary like every other op.
|
|
863
|
-
* - `
|
|
864
|
-
*
|
|
905
|
+
* - Every yielded event carries `resumeToken` (the change document's
|
|
906
|
+
* `_id`). Persist the last one you finished processing and pass it
|
|
907
|
+
* back as `options.resumeAfter` — or `options.startAfter` to also
|
|
908
|
+
* step past an invalidate event — for at-least-once consumption
|
|
909
|
+
* across restarts. Setting both is rejected up front (Mongo forbids
|
|
910
|
+
* the combination; the driver error is far less legible).
|
|
865
911
|
*
|
|
866
912
|
* Context keys for policy plugins (`organizationId`, `bypassTenant`,
|
|
867
913
|
* `includeDeleted`, ...) ride the options bag at runtime — same as every
|
|
@@ -901,32 +947,19 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
901
947
|
* // Skip cache for fresh data
|
|
902
948
|
* await repo.getAll({ filters: { status: 'active' } }, { skipCache: true });
|
|
903
949
|
*/
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
hint?: string | Record<string, 1 | -1>;
|
|
918
|
-
maxTimeMS?: number;
|
|
919
|
-
countStrategy?: 'exact' | 'estimated' | 'none';
|
|
920
|
-
readPreference?: ReadPreferenceType;
|
|
921
|
-
/** Advanced populate options (from QueryParser or Arc's BaseController) */
|
|
922
|
-
populateOptions?: PopulateOptions[];
|
|
923
|
-
/** Collation for locale-aware string comparison */
|
|
924
|
-
collation?: CollationOptions;
|
|
925
|
-
/** Lookup configurations for $lookup joins (from QueryParser or manual) */
|
|
926
|
-
lookups?: LookupOptions[];
|
|
927
|
-
/** Skip pagination entirely — returns raw TDoc[] (same as findAll) */
|
|
928
|
-
noPagination?: boolean;
|
|
929
|
-
}, options?: CacheableOptions): Promise<OffsetPaginationResult<TDoc> | KeysetPaginationResult<TDoc> | TDoc[]>;
|
|
950
|
+
/** An explicit `mode` (or `noPagination`) decides the envelope, so the result type follows it. */
|
|
951
|
+
getAll(params: GetAllParams & {
|
|
952
|
+
mode: 'offset';
|
|
953
|
+
noPagination?: false;
|
|
954
|
+
}, options?: CacheableOptions): Promise<OffsetPaginationResult<TDoc>>;
|
|
955
|
+
getAll(params: GetAllParams & {
|
|
956
|
+
mode: 'keyset';
|
|
957
|
+
noPagination?: false;
|
|
958
|
+
}, options?: CacheableOptions): Promise<KeysetPaginationResult<TDoc>>;
|
|
959
|
+
getAll(params: GetAllParams & {
|
|
960
|
+
noPagination: true;
|
|
961
|
+
}, options?: CacheableOptions): Promise<TDoc[]>;
|
|
962
|
+
getAll(params?: GetAllParams, options?: CacheableOptions): Promise<OffsetPaginationResult<TDoc> | KeysetPaginationResult<TDoc> | TDoc[]>;
|
|
930
963
|
/**
|
|
931
964
|
* Atomic "look up by filter, insert `data` if missing, return the doc."
|
|
932
965
|
*
|
|
@@ -1249,6 +1282,11 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1249
1282
|
}, patch?: Record<string, unknown>, options?: SessionOptions & {
|
|
1250
1283
|
idField?: string;
|
|
1251
1284
|
upsert?: boolean;
|
|
1285
|
+
/**
|
|
1286
|
+
* The document as the caller just read it in this session. Hooks' `loadTarget()`
|
|
1287
|
+
* returns it instead of reading again. Pass only a read taken in the same session.
|
|
1288
|
+
*/
|
|
1289
|
+
target?: Record<string, unknown>;
|
|
1252
1290
|
}): Promise<TDoc | null>;
|
|
1253
1291
|
/**
|
|
1254
1292
|
* State-machine-backed CAS transition with status history — the
|
|
@@ -1782,6 +1820,22 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1782
1820
|
* 4. DEFAULT (500) — user-registered hooks
|
|
1783
1821
|
*/
|
|
1784
1822
|
_buildContext(operation: string, options: Record<string, unknown> | object): Promise<RepositoryContext>;
|
|
1823
|
+
/**
|
|
1824
|
+
* `memo` and (for an id-addressed op) `loadTarget` — attached before the before-hooks
|
|
1825
|
+
* run so every hook of one operation shares one read. A caller that has just read the
|
|
1826
|
+
* document in the same session passes it as `target`, and nothing is read at all.
|
|
1827
|
+
*/
|
|
1828
|
+
private _sharedReadSeams;
|
|
1829
|
+
/**
|
|
1830
|
+
* Feed the resolved filter of a guarded verb to the distribution guard.
|
|
1831
|
+
* The slot follows `OP_REGISTRY[op].policyKey` (`query` vs `filters`);
|
|
1832
|
+
* the portable aggregate verbs ALSO carry the caller's filter on
|
|
1833
|
+
* `aggRequest.filter` (merged into the pipeline later by
|
|
1834
|
+
* `_injectPolicyScopeIntoAgg`), and the kit-native pipeline verbs may
|
|
1835
|
+
* carry it as leading `$match` stages — both are folded in so a key the
|
|
1836
|
+
* caller did supply is never reported missing.
|
|
1837
|
+
*/
|
|
1838
|
+
private _guardDistribution;
|
|
1785
1839
|
/**
|
|
1786
1840
|
* Walk the well-known filter-carrying keys (`query`, `filters`,
|
|
1787
1841
|
* `having`) and route any Filter IR through `compileFilterToMongo`.
|
|
@@ -1796,6 +1850,9 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1796
1850
|
private _resolveSort;
|
|
1797
1851
|
/**
|
|
1798
1852
|
* Detect whether to use keyset (cursor) or offset (page) pagination.
|
|
1853
|
+
* Precedence: explicit `mode` → `page` (offset) → `after` (keyset) →
|
|
1854
|
+
* `PaginationConfig.defaultMode` → the sort heuristic (a non-default
|
|
1855
|
+
* sort with no page means the caller wants a cursor).
|
|
1799
1856
|
*/
|
|
1800
1857
|
private _detectPaginationMode;
|
|
1801
1858
|
/**
|
|
@@ -1866,7 +1923,22 @@ declare class Repository<TDoc = unknown> extends RepositoryBase {
|
|
|
1866
1923
|
* `errorLabels` exactly as we received it. The caller still sees a
|
|
1867
1924
|
* thrown error — only the wrapping layer is skipped.
|
|
1868
1925
|
*/
|
|
1926
|
+
/**
|
|
1927
|
+
* Translate a driver error into the repository's error contract, WITHOUT
|
|
1928
|
+
* losing structured diagnostics an action attached to it.
|
|
1929
|
+
*
|
|
1930
|
+
* The translation replaces the object — an E11000 becomes a 409 — so any
|
|
1931
|
+
* property a caller was meant to read went with it. `createMany` attaches a
|
|
1932
|
+
* `partial` report naming which documents landed, and it arrived nowhere:
|
|
1933
|
+
* the report existed, the test for it failed, and the only evidence was the
|
|
1934
|
+
* absence.
|
|
1935
|
+
*
|
|
1936
|
+
* Carried explicitly rather than by copying every own-property, because the
|
|
1937
|
+
* wrap is deliberate: the point is to hide driver internals, and a blanket
|
|
1938
|
+
* copy would put them back.
|
|
1939
|
+
*/
|
|
1869
1940
|
_handleError(error: Error): HttpError;
|
|
1941
|
+
private _translateError;
|
|
1870
1942
|
}
|
|
1871
1943
|
//#endregion
|
|
1872
|
-
export {
|
|
1944
|
+
export { AggregationBuilder as a, ProjectionSpec as c, VectorSearchOptions as d, TransitionMachine as i, SortOrder as l, HOOK_PRIORITY as n, AggregationPlan as o, Repository as r, GroupSpec as s, GetAllParams as t, SortSpec 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 { S as create_exports, i as update_exports, m as aggregate_exports, p as delete_exports, u as read_exports } from "../update-DG0fCwRJ.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
|
@@ -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-BMgPSkqZ.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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as Repository } from "./Repository-
|
|
2
|
-
import {
|
|
3
|
-
import { n as batchOperationsPlugin, t as methodRegistryPlugin } from "./method-registry.plugin-
|
|
1
|
+
import { n as Repository } from "./Repository-BMgPSkqZ.mjs";
|
|
2
|
+
import { b as auditLogPlugin, g as cachePlugin, i as multiTenantPlugin, l as customIdPlugin, n as softDeletePlugin, t as timestampPlugin, v as auditTrailPlugin } from "./timestamp.plugin-DF0gJB6p.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;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as RepositoryOptions, d as PluginType } from "./repository-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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-odl03582.mjs";
|
|
3
|
+
import { D as AuditTrailOptions, i as CustomIdOptions, t as MultiTenantOptions, y as RepositoryCachePluginOptions } from "./multi-tenant.plugin-CBgCo9im.mjs";
|
|
4
|
+
import { r as Repository } from "./Repository-CR8Y9g2E.mjs";
|
|
5
|
+
import { i as Logger, o as SoftDeleteOptions } from "./plugin-options-Zk46lD6F.mjs";
|
|
6
6
|
import { Model } from "mongoose";
|
|
7
7
|
//#region src/create-repository.d.ts
|
|
8
8
|
/**
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Connection, Model } from "mongoose";
|
|
2
|
+
import { IdempotencyClaimStore } from "@classytic/repo-core/idempotency";
|
|
3
|
+
//#region src/idempotency/index.d.ts
|
|
4
|
+
interface IdempotencyModelOptions {
|
|
5
|
+
/** Collection name. Default `idempotency_claims`. */
|
|
6
|
+
collection?: string;
|
|
7
|
+
/** Mongoose model name. Default `IdempotencyClaim`. Must be unique per connection. */
|
|
8
|
+
modelName?: string;
|
|
9
|
+
}
|
|
10
|
+
interface IdempotencyStoreOptions {
|
|
11
|
+
/**
|
|
12
|
+
* How long a claim is kept after its last write before the TTL monitor removes it.
|
|
13
|
+
* Default 7 days. Size it to the longest a caller may retry — a retry after the claim
|
|
14
|
+
* is gone executes again. An offline till that syncs days later needs more.
|
|
15
|
+
*/
|
|
16
|
+
retentionSeconds?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Build (or return) the claims model on `connection`. Idempotent per connection. */
|
|
19
|
+
declare function createIdempotencyModel(connection: Connection, options?: IdempotencyModelOptions): Model<Record<string, unknown>>;
|
|
20
|
+
/** The store over a model from {@link createIdempotencyModel}. */
|
|
21
|
+
declare function createIdempotencyStore(model: Model<Record<string, unknown>>, options?: IdempotencyStoreOptions): IdempotencyClaimStore;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { IdempotencyModelOptions, IdempotencyStoreOptions, createIdempotencyModel, createIdempotencyStore };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Schema } from "mongoose";
|
|
2
|
+
import { identityKey } from "@classytic/repo-core/idempotency";
|
|
3
|
+
//#region src/idempotency/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* The mongo {@link IdempotencyClaimStore} — use this with `runIdempotent` from
|
|
6
|
+
* `@classytic/repo-core/idempotency`; never keep a per-module claims table.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const store = createIdempotencyStore(createIdempotencyModel(connection));
|
|
10
|
+
* const run = await runIdempotent({ store, identity, requestFingerprint, execute });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* One document per identity, `_id` = `identityKey(identity)`, so "insert iff absent" is
|
|
14
|
+
* the `_id` index and every compare-and-set is a single-document filter. Proven by
|
|
15
|
+
* `runIdempotencyStoreConformance` (`@classytic/repo-core/testing`) in this package's tests.
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_RETENTION_SECONDS = 604800;
|
|
18
|
+
/** Build (or return) the claims model on `connection`. Idempotent per connection. */
|
|
19
|
+
function createIdempotencyModel(connection, options = {}) {
|
|
20
|
+
const { collection = "idempotency_claims", modelName = "IdempotencyClaim" } = options;
|
|
21
|
+
const existing = connection.models[modelName];
|
|
22
|
+
if (existing) return existing;
|
|
23
|
+
const schema = new Schema({
|
|
24
|
+
_id: {
|
|
25
|
+
type: String,
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
identity: {
|
|
29
|
+
type: Schema.Types.Mixed,
|
|
30
|
+
required: true
|
|
31
|
+
},
|
|
32
|
+
requestFingerprint: {
|
|
33
|
+
type: String,
|
|
34
|
+
required: true
|
|
35
|
+
},
|
|
36
|
+
state: {
|
|
37
|
+
type: String,
|
|
38
|
+
enum: [
|
|
39
|
+
"in_flight",
|
|
40
|
+
"succeeded",
|
|
41
|
+
"failed"
|
|
42
|
+
],
|
|
43
|
+
required: true
|
|
44
|
+
},
|
|
45
|
+
leaseToken: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
leaseExpiresAt: {
|
|
50
|
+
type: Date,
|
|
51
|
+
required: true
|
|
52
|
+
},
|
|
53
|
+
createdAt: {
|
|
54
|
+
type: Date,
|
|
55
|
+
required: true
|
|
56
|
+
},
|
|
57
|
+
completedAt: { type: Date },
|
|
58
|
+
result: { type: Schema.Types.Mixed },
|
|
59
|
+
attempts: {
|
|
60
|
+
type: Number,
|
|
61
|
+
required: true
|
|
62
|
+
},
|
|
63
|
+
progress: { type: Schema.Types.Mixed },
|
|
64
|
+
context: { type: Schema.Types.Mixed },
|
|
65
|
+
lastError: { type: String },
|
|
66
|
+
purgeAt: {
|
|
67
|
+
type: Date,
|
|
68
|
+
required: true
|
|
69
|
+
}
|
|
70
|
+
}, {
|
|
71
|
+
collection,
|
|
72
|
+
versionKey: false,
|
|
73
|
+
minimize: false
|
|
74
|
+
});
|
|
75
|
+
schema.index({ purgeAt: 1 }, {
|
|
76
|
+
expireAfterSeconds: 0,
|
|
77
|
+
name: "retention"
|
|
78
|
+
});
|
|
79
|
+
schema.index({
|
|
80
|
+
"identity.operation": 1,
|
|
81
|
+
state: 1,
|
|
82
|
+
leaseExpiresAt: 1
|
|
83
|
+
}, { name: "lapsed_sweep" });
|
|
84
|
+
return connection.model(modelName, schema);
|
|
85
|
+
}
|
|
86
|
+
/** The store over a model from {@link createIdempotencyModel}. */
|
|
87
|
+
function createIdempotencyStore(model, options = {}) {
|
|
88
|
+
const retentionMs = (options.retentionSeconds ?? DEFAULT_RETENTION_SECONDS) * 1e3;
|
|
89
|
+
const claims = () => model.collection;
|
|
90
|
+
const toDoc = (claim) => ({
|
|
91
|
+
...claim,
|
|
92
|
+
_id: identityKey(claim.identity),
|
|
93
|
+
purgeAt: new Date(Date.now() + retentionMs)
|
|
94
|
+
});
|
|
95
|
+
/** The caller's transaction, so a claim commits or rolls back with the writes it describes. */
|
|
96
|
+
const withSession = (options) => options?.session ? { session: options.session } : {};
|
|
97
|
+
const ownedInFlight = (identity, leaseToken) => ({
|
|
98
|
+
_id: identityKey(identity),
|
|
99
|
+
state: "in_flight",
|
|
100
|
+
leaseToken
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
async get(identity, options) {
|
|
104
|
+
const doc = await claims().findOne({ _id: identityKey(identity) }, withSession(options));
|
|
105
|
+
if (!doc) return null;
|
|
106
|
+
const { _id, purgeAt, ...claim } = doc;
|
|
107
|
+
return claim;
|
|
108
|
+
},
|
|
109
|
+
async insert(claim, options) {
|
|
110
|
+
try {
|
|
111
|
+
await claims().insertOne(toDoc(claim), withSession(options));
|
|
112
|
+
return true;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error.code === 11e3) return false;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async swap(identity, expectedLeaseToken, next, options) {
|
|
119
|
+
return (await claims().replaceOne(ownedInFlight(identity, expectedLeaseToken), toDoc(next), withSession(options))).matchedCount === 1;
|
|
120
|
+
},
|
|
121
|
+
async release(identity, expectedLeaseToken, options) {
|
|
122
|
+
return (await claims().deleteOne(ownedInFlight(identity, expectedLeaseToken), withSession(options))).deletedCount === 1;
|
|
123
|
+
},
|
|
124
|
+
async listLapsed(operation, now, limit) {
|
|
125
|
+
return (await claims().find({
|
|
126
|
+
"identity.operation": operation,
|
|
127
|
+
state: "in_flight",
|
|
128
|
+
leaseExpiresAt: { $lte: now }
|
|
129
|
+
}).sort({ leaseExpiresAt: 1 }).limit(limit).toArray()).map(({ _id, purgeAt, ...claim }) => claim);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { createIdempotencyModel, createIdempotencyStore };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as UpdateOptions, a as CreateOptions, b as LookupOptions, c as FindOneAndUpdateOptions, f as MinMaxResult, g as UpdateManyResult, l as GroupResult, p as OperationOptions, s as DeleteResult } from "./operations-
|
|
2
|
-
import {
|
|
1
|
+
import { _ as UpdateOptions, a as CreateOptions, b as LookupOptions, c as FindOneAndUpdateOptions, f as MinMaxResult, g as UpdateManyResult, l as GroupResult, p as OperationOptions, s as DeleteResult } from "./operations-CrRIoc8Z.mjs";
|
|
2
|
+
import { _ as PopulateSpec, g as ObjectId, p as AnyDocument, v as ReadPreferenceType, x as SortSpec, y as SelectSpec } from "./pagination-BpIv2bdS.mjs";
|
|
3
3
|
import { p as UpdateWithValidationResult } from "./type-utils-CLzRFtIA.mjs";
|
|
4
4
|
import { Model, PipelineStage } from "mongoose";
|
|
5
5
|
declare namespace aggregate_d_exports {
|
|
@@ -12,9 +12,18 @@ declare function aggregate<TResult = unknown>(Model: Model<any>, pipeline: Pipel
|
|
|
12
12
|
session?: unknown;
|
|
13
13
|
}): Promise<TResult[]>;
|
|
14
14
|
/**
|
|
15
|
-
* Aggregate with pagination
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Aggregate with pagination.
|
|
16
|
+
*
|
|
17
|
+
* Page and count run as TWO concurrent pipelines rather than one `$facet`.
|
|
18
|
+
* `$facet` bundles every returned document plus the count into a SINGLE output
|
|
19
|
+
* document, and no stage may exceed 16MB — so a page of large documents failed
|
|
20
|
+
* with `BSONObjectTooLarge` instead of paginating, and the only defence was a
|
|
21
|
+
* warning above an arbitrary limit of 1000 that told the caller to go
|
|
22
|
+
* elsewhere. `$facet` also cannot use an index for the stages inside it.
|
|
23
|
+
*
|
|
24
|
+
* The trade: two reads, so a concurrent write between them can leave `total`
|
|
25
|
+
* a document out of step with `data` — the same trade `PaginationEngine`
|
|
26
|
+
* already makes on both its offset and aggregate paths.
|
|
18
27
|
*/
|
|
19
28
|
declare function aggregatePaginate<TDoc = AnyDocument>(Model: Model<TDoc>, pipeline: PipelineStage[], options?: {
|
|
20
29
|
page?: number;
|
|
@@ -91,14 +100,64 @@ declare function minMax(Model: Model<any>, field: string, query?: Record<string,
|
|
|
91
100
|
session?: unknown;
|
|
92
101
|
}): Promise<MinMaxResult>;
|
|
93
102
|
declare namespace create_d_exports {
|
|
94
|
-
export { create, createDefault, createMany, upsert };
|
|
103
|
+
export { CreateManyFailure, CreateManyPartial, CreateManyPartialError, create, createDefault, createMany, isCreateManyPartialError, upsert };
|
|
95
104
|
}
|
|
96
105
|
/**
|
|
97
106
|
* Create single document
|
|
98
107
|
*/
|
|
99
108
|
declare function create<TDoc = AnyDocument>(Model: Model<TDoc>, data: Record<string, unknown>, options?: CreateOptions): Promise<TDoc>;
|
|
100
109
|
/**
|
|
101
|
-
*
|
|
110
|
+
* One rejected input, keyed by its position in the array that was passed in.
|
|
111
|
+
*
|
|
112
|
+
* The index is the load-bearing part: it is the only way to tie a failure back
|
|
113
|
+
* to the caller's own record — the driver reports failures positionally, and a
|
|
114
|
+
* rejected document never comes back with an `_id` to match on.
|
|
115
|
+
*/
|
|
116
|
+
interface CreateManyFailure {
|
|
117
|
+
/** Position in the ORIGINAL `dataArray`. */
|
|
118
|
+
index: number;
|
|
119
|
+
/** The input that was rejected, as supplied. */
|
|
120
|
+
doc: Record<string, unknown>;
|
|
121
|
+
/** MongoDB error code, e.g. 11000 for a duplicate key. */
|
|
122
|
+
code?: number;
|
|
123
|
+
message: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* What a partially-successful `createMany` actually did.
|
|
127
|
+
*
|
|
128
|
+
* Attached to the thrown error as `partial`. `createMany` still REJECTS — that
|
|
129
|
+
* is unchanged and deliberate — but the rejection now says which documents
|
|
130
|
+
* landed and which did not, instead of leaving the caller to dig through a
|
|
131
|
+
* driver error to find out.
|
|
132
|
+
*/
|
|
133
|
+
interface CreateManyPartial<TDoc = AnyDocument> {
|
|
134
|
+
/** Documents the server accepted. */
|
|
135
|
+
inserted: TDoc[];
|
|
136
|
+
/** Every rejected input, in input order. */
|
|
137
|
+
failed: CreateManyFailure[];
|
|
138
|
+
}
|
|
139
|
+
/** The error `createMany` throws when some documents landed and some did not. */
|
|
140
|
+
type CreateManyPartialError<TDoc = AnyDocument> = Error & {
|
|
141
|
+
partial?: CreateManyPartial<TDoc>;
|
|
142
|
+
};
|
|
143
|
+
/** Narrow a caught error to one carrying a partial-write report. */
|
|
144
|
+
declare function isCreateManyPartialError<TDoc = AnyDocument>(err: unknown): err is CreateManyPartialError<TDoc> & {
|
|
145
|
+
partial: CreateManyPartial<TDoc>;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Create multiple documents.
|
|
149
|
+
*
|
|
150
|
+
* **A rejection does NOT mean nothing was written.** Neither ordering does:
|
|
151
|
+
* `ordered: true` stops at the first failure and keeps everything before it,
|
|
152
|
+
* `ordered: false` keeps everything valid. Measured on a six-document batch
|
|
153
|
+
* with a conflict in the middle — ordered wrote 3, unordered wrote 5. Only a
|
|
154
|
+
* transaction gives all-or-nothing.
|
|
155
|
+
*
|
|
156
|
+
* So the rejection carries a `partial` report naming what landed and what did
|
|
157
|
+
* not, because the alternative is a caller retrying the whole batch and
|
|
158
|
+
* double-writing everything that had already succeeded. Use it to retry only
|
|
159
|
+
* `failed`, or to reconcile — and prefer a unique index or an idempotency key
|
|
160
|
+
* for anything that will be retried automatically.
|
|
102
161
|
*/
|
|
103
162
|
declare function createMany<TDoc = AnyDocument>(Model: Model<TDoc>, dataArray: Record<string, unknown>[], options?: CreateOptions): Promise<TDoc[]>;
|
|
104
163
|
/**
|
|
@@ -301,4 +360,4 @@ declare namespace index_d_exports {
|
|
|
301
360
|
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 };
|
|
302
361
|
}
|
|
303
362
|
//#endregion
|
|
304
|
-
export {
|
|
363
|
+
export { CreateManyFailure as a, create_d_exports as c, delete_d_exports as i, isCreateManyPartialError as l, update_d_exports as n, CreateManyPartial as o, read_d_exports as r, CreateManyPartialError as s, index_d_exports as t, aggregate_d_exports as u };
|