@rpcbase/db 0.28.0 → 0.30.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 +7 -7
- package/dist/acl/mongooseAclPlugin.d.ts +36 -0
- package/dist/acl/mongooseAclPlugin.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +451 -0
- package/dist/pagination/compileMongoPagination.d.ts +14 -0
- package/dist/pagination/compileMongoPagination.d.ts.map +1 -0
- package/dist/pagination/cursor.d.ts +7 -0
- package/dist/pagination/cursor.d.ts.map +1 -0
- package/dist/pagination/errors.d.ts +8 -0
- package/dist/pagination/errors.d.ts.map +1 -0
- package/dist/pagination/index.d.ts +5 -0
- package/dist/pagination/index.d.ts.map +1 -0
- package/dist/pagination/indexHint.d.ts +4 -0
- package/dist/pagination/indexHint.d.ts.map +1 -0
- package/dist/pagination/materializePagination.d.ts +8 -0
- package/dist/pagination/materializePagination.d.ts.map +1 -0
- package/dist/pagination/mongoAdapter.d.ts +11 -0
- package/dist/pagination/mongoAdapter.d.ts.map +1 -0
- package/dist/pagination/mongoPaginationPlugin.d.ts +17 -0
- package/dist/pagination/mongoPaginationPlugin.d.ts.map +1 -0
- package/dist/pagination/normalizeSpec.d.ts +11 -0
- package/dist/pagination/normalizeSpec.d.ts.map +1 -0
- package/dist/pagination/paginateMongoQuery.d.ts +14 -0
- package/dist/pagination/paginateMongoQuery.d.ts.map +1 -0
- package/dist/registerModels.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
### `LocalizedString` / `zI18nString()`
|
|
4
4
|
|
|
5
|
-
`zI18nString()` (alias
|
|
5
|
+
`zI18nString()` (alias of `zLocalizedString()`) describes a localized string as an object, keyed by *language codes* (BCP47):
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import { z } from "zod"
|
|
@@ -15,11 +15,11 @@ const ZPost = z.object({
|
|
|
15
15
|
const schema = zodSchema(ZPost)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
**
|
|
18
|
+
**Mongoose-side (important)**
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
20
|
+
- The field is stored as an object and typed as `SchemaTypes.Mixed` (not a `Map`).
|
|
21
|
+
- On read, the Mongoose getter returns a “proxy” object with automatic fallback: `fr-FR` falls back to `fr` if `fr-FR` doesn’t exist.
|
|
22
22
|
- `doc.title["fr-FR"]` → fallback
|
|
23
|
-
- `doc.title.get("fr-FR")` → fallback (helper
|
|
24
|
-
- `doc.title.getExact("fr-FR")` → exact (
|
|
25
|
-
-
|
|
23
|
+
- `doc.title.get("fr-FR")` → fallback (read helper)
|
|
24
|
+
- `doc.title.getExact("fr-FR")` → exact (no fallback)
|
|
25
|
+
- On write, because it’s `Mixed`, avoid silent deep mutations: prefer replacing the object (`doc.title = { ...doc.title, fr: "Salut" }`) or call `doc.markModified("title")`.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { default as mongoose } from '../../../vite/node_modules/mongoose';
|
|
2
|
+
import { AclAction, AppAbility } from './types';
|
|
3
|
+
type StoredAcl = {
|
|
4
|
+
ability: AppAbility;
|
|
5
|
+
action?: AclAction;
|
|
6
|
+
};
|
|
7
|
+
type PipelineStageLike = Record<string, unknown>;
|
|
8
|
+
declare const mongooseAclPluginBrand: unique symbol;
|
|
9
|
+
declare module '../../../vite/node_modules/mongoose' {
|
|
10
|
+
interface QueryOptions<DocType = unknown> {
|
|
11
|
+
rbAcl?: StoredAcl;
|
|
12
|
+
[mongooseAclPluginBrand]?: DocType;
|
|
13
|
+
}
|
|
14
|
+
interface AggregateOptions {
|
|
15
|
+
rbAcl?: StoredAcl;
|
|
16
|
+
}
|
|
17
|
+
interface Query<ResultType, DocType, THelpers = {}, RawDocType = unknown, QueryOp = "find", TDocOverrides = Record<string, never>> {
|
|
18
|
+
[mongooseAclPluginBrand]?: [ResultType, DocType, THelpers, RawDocType, QueryOp, TDocOverrides];
|
|
19
|
+
acl: (ability: AppAbility, action?: AclAction) => this;
|
|
20
|
+
}
|
|
21
|
+
interface Aggregate<ResultType> {
|
|
22
|
+
[mongooseAclPluginBrand]?: ResultType;
|
|
23
|
+
acl: (ability: AppAbility, action?: AclAction) => this;
|
|
24
|
+
}
|
|
25
|
+
interface Model<TRawDocType, TQueryHelpers, TInstanceMethods, TVirtuals, THydratedDocumentType, TSchema, TLeanResultType> {
|
|
26
|
+
[mongooseAclPluginBrand]?: [TRawDocType, TQueryHelpers, TInstanceMethods, TVirtuals, THydratedDocumentType, TSchema, TLeanResultType];
|
|
27
|
+
acl: (ability: AppAbility) => this;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export declare const mongooseAclPlugin: (schema: mongoose.Schema) => void;
|
|
31
|
+
export declare const _private: {
|
|
32
|
+
injectAggregateMatch: (pipeline: PipelineStageLike[], match: Record<string, unknown>) => void;
|
|
33
|
+
mergeMongoQuery: (left: unknown, right: Record<string, unknown>) => Record<string, unknown>;
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=mongooseAclPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongooseAclPlugin.d.ts","sourceRoot":"","sources":["../../src/acl/mongooseAclPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAG/B,OAAO,KAAK,EAAE,SAAS,EAAkB,UAAU,EAAE,MAAM,SAAS,CAAA;AAGpE,KAAK,SAAS,GAAG;IACf,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAID,KAAK,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AA6HhD,OAAO,CAAC,MAAM,sBAAsB,EAAE,OAAO,MAAM,CAAA;AAEnD,OAAO,QAAQ,UAAU,CAAC;IACxB,UAAU,YAAY,CAAC,OAAO,GAAG,OAAO;QACtC,KAAK,CAAC,EAAE,SAAS,CAAC;QAClB,CAAC,sBAAsB,CAAC,CAAC,EAAE,OAAO,CAAC;KACpC;IAED,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,SAAS,CAAC;KACnB;IAED,UAAU,KAAK,CACb,UAAU,EACV,OAAO,EACP,QAAQ,GAAG,EAAE,EACb,UAAU,GAAG,OAAO,EACpB,OAAO,GAAG,MAAM,EAChB,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAErC,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAC/F,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;KACxD;IAED,UAAU,SAAS,CAAC,UAAU;QAC5B,CAAC,sBAAsB,CAAC,CAAC,EAAE,UAAU,CAAC;QACtC,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;KACxD;IAED,UAAU,KAAK,CACb,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,qBAAqB,EACrB,OAAO,EACP,eAAe;QAEf,CAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,qBAAqB,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;QACtI,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;KACpC;CACF;AAED,eAAO,MAAM,iBAAiB,GAAI,QAAQ,QAAQ,CAAC,MAAM,KAAG,IAsE3D,CAAA;AAED,eAAO,MAAM,QAAQ;qCAjMmB,iBAAiB,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAAI;4BA3CnE,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CA+O/F,CAAA"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,OAAO,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,OAAO,CAAA;AACrB,cAAc,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { z as z2 } from "zod";
|
|
|
5
5
|
import assert from "assert";
|
|
6
6
|
import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
|
|
7
7
|
import { AbilityBuilder, createMongoAbility, subject } from "@casl/ability";
|
|
8
|
+
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
8
9
|
const ZRBUser = z.object({
|
|
9
10
|
email: z.string().email().optional(),
|
|
10
11
|
password: z.string(),
|
|
@@ -1424,8 +1425,439 @@ const can = (ability, action, subjectType, object) => {
|
|
|
1424
1425
|
}
|
|
1425
1426
|
return ability.can(action, subjectType);
|
|
1426
1427
|
};
|
|
1428
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1429
|
+
const mergeMongoQuery = (left, right) => {
|
|
1430
|
+
const leftQuery = isRecord(left) ? left : {};
|
|
1431
|
+
if (Object.keys(leftQuery).length === 0) return right;
|
|
1432
|
+
if (Object.keys(right).length === 0) return leftQuery;
|
|
1433
|
+
return { $and: [leftQuery, right] };
|
|
1434
|
+
};
|
|
1435
|
+
const getQueryOptions$1 = (query) => {
|
|
1436
|
+
if (!query || typeof query !== "object") return void 0;
|
|
1437
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1438
|
+
return query.getOptions();
|
|
1439
|
+
};
|
|
1440
|
+
const getStoredAclFromQuery = (query) => {
|
|
1441
|
+
const options = getQueryOptions$1(query);
|
|
1442
|
+
const raw = options?.rbAcl;
|
|
1443
|
+
if (!isRecord(raw)) return null;
|
|
1444
|
+
if (!("ability" in raw)) return null;
|
|
1445
|
+
return raw;
|
|
1446
|
+
};
|
|
1447
|
+
const getStoredAclFromAggregate = (aggregate) => {
|
|
1448
|
+
if (!aggregate || typeof aggregate !== "object") return null;
|
|
1449
|
+
const raw = aggregate.options;
|
|
1450
|
+
if (!isRecord(raw)) return null;
|
|
1451
|
+
const acl = raw.rbAcl;
|
|
1452
|
+
if (!isRecord(acl)) return null;
|
|
1453
|
+
if (!("ability" in acl)) return null;
|
|
1454
|
+
return acl;
|
|
1455
|
+
};
|
|
1456
|
+
const addQueryAclFilter = (query, action) => {
|
|
1457
|
+
const storedAcl = getStoredAclFromQuery(query);
|
|
1458
|
+
if (!storedAcl) return;
|
|
1459
|
+
const ability = storedAcl.ability;
|
|
1460
|
+
const resolvedAction = storedAcl.action ?? action;
|
|
1461
|
+
const modelName = query.model.modelName;
|
|
1462
|
+
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1463
|
+
query.and([accessQuery]);
|
|
1464
|
+
};
|
|
1465
|
+
const injectAggregateMatch = (pipeline, match) => {
|
|
1466
|
+
if (pipeline.length === 0) {
|
|
1467
|
+
pipeline.unshift({ $match: match });
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const first = pipeline[0];
|
|
1471
|
+
if (!isRecord(first)) {
|
|
1472
|
+
pipeline.unshift({ $match: match });
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if ("$geoNear" in first) {
|
|
1476
|
+
const geoNear = first.$geoNear;
|
|
1477
|
+
if (isRecord(geoNear)) {
|
|
1478
|
+
geoNear.query = mergeMongoQuery(geoNear.query, match);
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if ("$search" in first || "$vectorSearch" in first || "$searchMeta" in first) {
|
|
1483
|
+
pipeline.splice(1, 0, { $match: match });
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
pipeline.unshift({ $match: match });
|
|
1487
|
+
};
|
|
1488
|
+
const addAggregateAclFilter = (aggregate, action) => {
|
|
1489
|
+
const storedAcl = getStoredAclFromAggregate(aggregate);
|
|
1490
|
+
if (!storedAcl) return;
|
|
1491
|
+
const ability = storedAcl.ability;
|
|
1492
|
+
const resolvedAction = storedAcl.action ?? action;
|
|
1493
|
+
const modelName = aggregate.model().modelName;
|
|
1494
|
+
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1495
|
+
injectAggregateMatch(aggregate.pipeline(), accessQuery);
|
|
1496
|
+
};
|
|
1497
|
+
const patchAggregateAcl = () => {
|
|
1498
|
+
const globalKey = /* @__PURE__ */ Symbol.for("@rpcbase/db/acl/mongooseAggregateAclPatched");
|
|
1499
|
+
const globalState = globalThis;
|
|
1500
|
+
if (globalState[globalKey]) return;
|
|
1501
|
+
globalState[globalKey] = true;
|
|
1502
|
+
const AggregatePrototype = mongoose.Aggregate.prototype;
|
|
1503
|
+
if (typeof AggregatePrototype.acl === "function") return;
|
|
1504
|
+
AggregatePrototype.acl = function(ability, action = "read") {
|
|
1505
|
+
this.option({ rbAcl: { ability, action } });
|
|
1506
|
+
return this;
|
|
1507
|
+
};
|
|
1508
|
+
};
|
|
1509
|
+
const createModelAclProxy = (model, ability) => {
|
|
1510
|
+
return new Proxy(model, {
|
|
1511
|
+
get(target, prop, receiver) {
|
|
1512
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1513
|
+
if (typeof value !== "function") return value;
|
|
1514
|
+
return (...args) => {
|
|
1515
|
+
const result = Reflect.apply(value, target, args);
|
|
1516
|
+
if (result && typeof result === "object" && "acl" in result && typeof result.acl === "function") {
|
|
1517
|
+
return result.acl(ability);
|
|
1518
|
+
}
|
|
1519
|
+
return result;
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
};
|
|
1524
|
+
const mongooseAclPlugin = (schema) => {
|
|
1525
|
+
patchAggregateAcl();
|
|
1526
|
+
schema.query.acl = function(ability, action) {
|
|
1527
|
+
this.setOptions({ rbAcl: { ability, action } });
|
|
1528
|
+
return this;
|
|
1529
|
+
};
|
|
1530
|
+
schema.statics.acl = function(ability) {
|
|
1531
|
+
return createModelAclProxy(this, ability);
|
|
1532
|
+
};
|
|
1533
|
+
schema.pre("aggregate", function() {
|
|
1534
|
+
addAggregateAclFilter(this, "read");
|
|
1535
|
+
});
|
|
1536
|
+
schema.pre("countDocuments", function() {
|
|
1537
|
+
addQueryAclFilter(this, "read");
|
|
1538
|
+
});
|
|
1539
|
+
schema.pre("deleteMany", function() {
|
|
1540
|
+
addQueryAclFilter(this, "delete");
|
|
1541
|
+
});
|
|
1542
|
+
schema.pre("deleteOne", function() {
|
|
1543
|
+
addQueryAclFilter(this, "delete");
|
|
1544
|
+
});
|
|
1545
|
+
schema.pre("distinct", function() {
|
|
1546
|
+
addQueryAclFilter(this, "read");
|
|
1547
|
+
});
|
|
1548
|
+
schema.pre("find", function() {
|
|
1549
|
+
addQueryAclFilter(this, "read");
|
|
1550
|
+
});
|
|
1551
|
+
schema.pre("findOne", function() {
|
|
1552
|
+
addQueryAclFilter(this, "read");
|
|
1553
|
+
});
|
|
1554
|
+
schema.pre("findOneAndDelete", function() {
|
|
1555
|
+
addQueryAclFilter(this, "delete");
|
|
1556
|
+
});
|
|
1557
|
+
schema.pre("findOneAndReplace", function() {
|
|
1558
|
+
addQueryAclFilter(this, "update");
|
|
1559
|
+
});
|
|
1560
|
+
schema.pre("findOneAndUpdate", function() {
|
|
1561
|
+
addQueryAclFilter(this, "update");
|
|
1562
|
+
});
|
|
1563
|
+
schema.pre("replaceOne", function() {
|
|
1564
|
+
addQueryAclFilter(this, "update");
|
|
1565
|
+
});
|
|
1566
|
+
schema.pre("updateMany", function() {
|
|
1567
|
+
addQueryAclFilter(this, "update");
|
|
1568
|
+
});
|
|
1569
|
+
schema.pre("updateOne", function() {
|
|
1570
|
+
addQueryAclFilter(this, "update");
|
|
1571
|
+
});
|
|
1572
|
+
};
|
|
1573
|
+
class PaginationValidationError extends Error {
|
|
1574
|
+
code = "invalid_pagination";
|
|
1575
|
+
statusCode = 400;
|
|
1576
|
+
constructor(message, options) {
|
|
1577
|
+
super(message, options);
|
|
1578
|
+
this.name = "PaginationValidationError";
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const isPaginationValidationError = (error) => {
|
|
1582
|
+
if (!error || typeof error !== "object") return false;
|
|
1583
|
+
const anyError = error;
|
|
1584
|
+
return anyError.name === "PaginationValidationError" && anyError.code === "invalid_pagination" && anyError.statusCode === 400;
|
|
1585
|
+
};
|
|
1586
|
+
const DISALLOWED_MONGO_FIELD_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1587
|
+
const PAGINATION_LIMIT_MAX = 128;
|
|
1588
|
+
const normalizePaginationSpec = (spec) => {
|
|
1589
|
+
if (!spec || typeof spec !== "object") throw new PaginationValidationError("Invalid PaginationSpec");
|
|
1590
|
+
const limit = normalizeLimit(spec.limit);
|
|
1591
|
+
const direction = spec.direction ?? "next";
|
|
1592
|
+
if (direction !== "next" && direction !== "prev") throw new PaginationValidationError("Invalid pagination direction");
|
|
1593
|
+
if (!Array.isArray(spec.sort) || spec.sort.length === 0) throw new PaginationValidationError("Invalid pagination sort");
|
|
1594
|
+
const sort = spec.sort.map(({ field, order }) => ({
|
|
1595
|
+
field: assertSafeMongoFieldPath(field),
|
|
1596
|
+
order: normalizeOrder(order)
|
|
1597
|
+
}));
|
|
1598
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
1599
|
+
for (const { field } of sort) {
|
|
1600
|
+
if (seenFields.has(field)) throw new PaginationValidationError(`Duplicate pagination sort field: ${field}`);
|
|
1601
|
+
seenFields.add(field);
|
|
1602
|
+
}
|
|
1603
|
+
const primaryOrder = sort[0]?.order;
|
|
1604
|
+
if (!seenFields.has("_id")) {
|
|
1605
|
+
sort.push({ field: "_id", order: primaryOrder });
|
|
1606
|
+
}
|
|
1607
|
+
return {
|
|
1608
|
+
...spec,
|
|
1609
|
+
limit,
|
|
1610
|
+
direction,
|
|
1611
|
+
sort
|
|
1612
|
+
};
|
|
1613
|
+
};
|
|
1614
|
+
const normalizeLimit = (limit) => {
|
|
1615
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit) || limit <= 0) {
|
|
1616
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
1617
|
+
}
|
|
1618
|
+
if (limit > PAGINATION_LIMIT_MAX) {
|
|
1619
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
1620
|
+
}
|
|
1621
|
+
return limit;
|
|
1622
|
+
};
|
|
1623
|
+
const normalizeOrder = (order) => {
|
|
1624
|
+
if (order !== "asc" && order !== "desc") throw new PaginationValidationError("Invalid pagination order");
|
|
1625
|
+
return order;
|
|
1626
|
+
};
|
|
1627
|
+
const assertSafeMongoFieldPath = (field) => {
|
|
1628
|
+
if (typeof field !== "string" || field.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1629
|
+
if (field.startsWith("$")) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1630
|
+
const parts = field.split(".");
|
|
1631
|
+
for (const part of parts) {
|
|
1632
|
+
if (part.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1633
|
+
if (DISALLOWED_MONGO_FIELD_SEGMENTS.has(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1634
|
+
if (!/^[a-zA-Z0-9_]+$/.test(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1635
|
+
}
|
|
1636
|
+
return field;
|
|
1637
|
+
};
|
|
1638
|
+
const encodePaginationCursor = (spec, node, options) => {
|
|
1639
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1640
|
+
const values = /* @__PURE__ */ Object.create(null);
|
|
1641
|
+
for (const { field } of normalized.sort) {
|
|
1642
|
+
const value = readFieldValue(node, field);
|
|
1643
|
+
if (typeof value === "undefined") {
|
|
1644
|
+
throw new Error(`Pagination cursor encode failed (missing field: ${field})`);
|
|
1645
|
+
}
|
|
1646
|
+
values[field] = encodeCursorValue(field, value);
|
|
1647
|
+
}
|
|
1648
|
+
const payload = { v: 1, values };
|
|
1649
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1650
|
+
const sigB64 = signCursorPayloadB64(payloadB64, options.signingSecret);
|
|
1651
|
+
return `${payloadB64}.${sigB64}`;
|
|
1652
|
+
};
|
|
1653
|
+
const decodePaginationCursor = (spec, cursor, options) => {
|
|
1654
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1655
|
+
const [payloadB64, sigB64, ...rest] = cursor.split(".");
|
|
1656
|
+
if (rest.length > 0) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1657
|
+
if (!sigB64) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1658
|
+
verifyCursorSignature(payloadB64, sigB64, options.signingSecret);
|
|
1659
|
+
const payloadRaw = Buffer.from(payloadB64, "base64url").toString("utf8");
|
|
1660
|
+
let payloadUnknown;
|
|
1661
|
+
try {
|
|
1662
|
+
payloadUnknown = JSON.parse(payloadRaw);
|
|
1663
|
+
} catch {
|
|
1664
|
+
throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1665
|
+
}
|
|
1666
|
+
if (!payloadUnknown || typeof payloadUnknown !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1667
|
+
const payload = payloadUnknown;
|
|
1668
|
+
if (payload.v !== 1) throw new PaginationValidationError("Unsupported pagination cursor version");
|
|
1669
|
+
if (!payload.values || typeof payload.values !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1670
|
+
const decoded = /* @__PURE__ */ Object.create(null);
|
|
1671
|
+
for (const { field } of normalized.sort) {
|
|
1672
|
+
if (!Object.prototype.hasOwnProperty.call(payload.values, field)) {
|
|
1673
|
+
throw new PaginationValidationError(`Pagination cursor missing field: ${field}`);
|
|
1674
|
+
}
|
|
1675
|
+
const encodedValue = payload.values[field];
|
|
1676
|
+
decoded[field] = decodeCursorValue(field, encodedValue);
|
|
1677
|
+
}
|
|
1678
|
+
return decoded;
|
|
1679
|
+
};
|
|
1680
|
+
const signCursorPayloadB64 = (payloadB64, secret) => {
|
|
1681
|
+
return createHmac("sha256", secret).update(payloadB64, "utf8").digest("base64url");
|
|
1682
|
+
};
|
|
1683
|
+
const verifyCursorSignature = (payloadB64, sigB64, secret) => {
|
|
1684
|
+
const expectedSigB64 = signCursorPayloadB64(payloadB64, secret);
|
|
1685
|
+
const a = Buffer.from(sigB64, "utf8");
|
|
1686
|
+
const b = Buffer.from(expectedSigB64, "utf8");
|
|
1687
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
1688
|
+
throw new PaginationValidationError("Invalid pagination cursor signature");
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
const encodeCursorValue = (field, value) => {
|
|
1692
|
+
if (value === null) return null;
|
|
1693
|
+
if (field === "_id") {
|
|
1694
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
1695
|
+
throw new Error("Pagination cursor encode failed (_id must be an ObjectId)");
|
|
1696
|
+
}
|
|
1697
|
+
if (value instanceof Date) return { $date: value.toISOString() };
|
|
1698
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
1699
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
1700
|
+
throw new Error(`Unsupported pagination cursor value type for field: ${field}`);
|
|
1701
|
+
};
|
|
1702
|
+
const decodeCursorValue = (field, value) => {
|
|
1703
|
+
if (value === null) return null;
|
|
1704
|
+
if (typeof value === "string") {
|
|
1705
|
+
if (field === "_id") throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
1706
|
+
return value;
|
|
1707
|
+
}
|
|
1708
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1709
|
+
if (!value || typeof value !== "object") throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1710
|
+
if ("$date" in value) {
|
|
1711
|
+
if (typeof value.$date !== "string") throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1712
|
+
const d = new Date(value.$date);
|
|
1713
|
+
if (Number.isNaN(d.getTime())) throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1714
|
+
return d;
|
|
1715
|
+
}
|
|
1716
|
+
if ("$oid" in value) {
|
|
1717
|
+
if (typeof value.$oid !== "string" || !Types.ObjectId.isValid(value.$oid)) {
|
|
1718
|
+
throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
1719
|
+
}
|
|
1720
|
+
return new Types.ObjectId(value.$oid);
|
|
1721
|
+
}
|
|
1722
|
+
throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1723
|
+
};
|
|
1724
|
+
const readFieldValue = (node, field) => {
|
|
1725
|
+
if (!node || typeof node !== "object") return void 0;
|
|
1726
|
+
if ("get" in node && typeof node.get === "function") {
|
|
1727
|
+
return node.get(field);
|
|
1728
|
+
}
|
|
1729
|
+
return field.split(".").reduce((acc, key) => {
|
|
1730
|
+
if (!acc || typeof acc !== "object") return void 0;
|
|
1731
|
+
return acc[key];
|
|
1732
|
+
}, node);
|
|
1733
|
+
};
|
|
1734
|
+
const compileMongoPagination = (spec, options) => {
|
|
1735
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1736
|
+
const mongoLimit = normalized.limit + 1;
|
|
1737
|
+
const mongoSort = toMongoSort(normalized);
|
|
1738
|
+
if (normalized.cursor == null) {
|
|
1739
|
+
return {
|
|
1740
|
+
spec: normalized,
|
|
1741
|
+
mongoFilterDelta: {},
|
|
1742
|
+
mongoSort,
|
|
1743
|
+
mongoLimit
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
if (typeof normalized.cursor !== "string" || normalized.cursor.length === 0) {
|
|
1747
|
+
throw new PaginationValidationError("Invalid pagination cursor");
|
|
1748
|
+
}
|
|
1749
|
+
const cursorValues = decodePaginationCursor(normalized, normalized.cursor, options.cursor);
|
|
1750
|
+
const mongoFilterDelta = buildKeysetFilterDelta(normalized, cursorValues);
|
|
1751
|
+
return {
|
|
1752
|
+
spec: normalized,
|
|
1753
|
+
mongoFilterDelta,
|
|
1754
|
+
mongoSort,
|
|
1755
|
+
mongoLimit
|
|
1756
|
+
};
|
|
1757
|
+
};
|
|
1758
|
+
const toMongoSort = (spec) => {
|
|
1759
|
+
const mongoSort = /* @__PURE__ */ Object.create(null);
|
|
1760
|
+
for (const { field, order } of spec.sort) {
|
|
1761
|
+
const forQueryOrder = spec.direction === "prev" ? invertOrder(order) : order;
|
|
1762
|
+
mongoSort[field] = forQueryOrder === "asc" ? 1 : -1;
|
|
1763
|
+
}
|
|
1764
|
+
return mongoSort;
|
|
1765
|
+
};
|
|
1766
|
+
const buildKeysetFilterDelta = (spec, cursorValues) => {
|
|
1767
|
+
const branches = [];
|
|
1768
|
+
for (let i = 0; i < spec.sort.length; i++) {
|
|
1769
|
+
const current = spec.sort[i];
|
|
1770
|
+
if (!current) continue;
|
|
1771
|
+
const and = [];
|
|
1772
|
+
for (let j = 0; j < i; j++) {
|
|
1773
|
+
const prev = spec.sort[j];
|
|
1774
|
+
if (!prev) continue;
|
|
1775
|
+
const eq = /* @__PURE__ */ Object.create(null);
|
|
1776
|
+
eq[prev.field] = cursorValues[prev.field];
|
|
1777
|
+
and.push(eq);
|
|
1778
|
+
}
|
|
1779
|
+
const op = getKeysetOp(current.order, spec.direction);
|
|
1780
|
+
const cmpOp = /* @__PURE__ */ Object.create(null);
|
|
1781
|
+
cmpOp[op] = cursorValues[current.field];
|
|
1782
|
+
const cmp = /* @__PURE__ */ Object.create(null);
|
|
1783
|
+
cmp[current.field] = cmpOp;
|
|
1784
|
+
and.push(cmp);
|
|
1785
|
+
branches.push(and.length === 1 ? and[0] : { $and: and });
|
|
1786
|
+
}
|
|
1787
|
+
return { $or: branches };
|
|
1788
|
+
};
|
|
1789
|
+
const getKeysetOp = (order, direction) => {
|
|
1790
|
+
if (direction === "next") return order === "asc" ? "$gt" : "$lt";
|
|
1791
|
+
return order === "asc" ? "$lt" : "$gt";
|
|
1792
|
+
};
|
|
1793
|
+
const invertOrder = (order) => {
|
|
1794
|
+
return order === "asc" ? "desc" : "asc";
|
|
1795
|
+
};
|
|
1796
|
+
const materializeMongoPagination = (compiled, fetchedNodes, options) => {
|
|
1797
|
+
const limit = compiled.spec.limit;
|
|
1798
|
+
const hasMore = fetchedNodes.length > limit;
|
|
1799
|
+
const trimmed = fetchedNodes.slice(0, limit);
|
|
1800
|
+
const nodes = compiled.spec.direction === "prev" ? trimmed.reverse() : trimmed;
|
|
1801
|
+
const hasPrevPage = compiled.spec.direction === "next" ? Boolean(compiled.spec.cursor) : hasMore;
|
|
1802
|
+
const hasNextPage = compiled.spec.direction === "next" ? hasMore : Boolean(compiled.spec.cursor);
|
|
1803
|
+
const pageInfo = {
|
|
1804
|
+
hasNextPage,
|
|
1805
|
+
hasPrevPage
|
|
1806
|
+
};
|
|
1807
|
+
if (nodes.length === 0) {
|
|
1808
|
+
return { nodes, pageInfo };
|
|
1809
|
+
}
|
|
1810
|
+
if (hasPrevPage) {
|
|
1811
|
+
pageInfo.prevCursor = encodePaginationCursor(compiled.spec, nodes[0], options.cursor);
|
|
1812
|
+
}
|
|
1813
|
+
if (hasNextPage) {
|
|
1814
|
+
pageInfo.nextCursor = encodePaginationCursor(compiled.spec, nodes[nodes.length - 1], options.cursor);
|
|
1815
|
+
}
|
|
1816
|
+
return { nodes, pageInfo };
|
|
1817
|
+
};
|
|
1818
|
+
const MongoAdapter = {
|
|
1819
|
+
applyPagination: (query, compiled) => {
|
|
1820
|
+
query.where(compiled.mongoFilterDelta);
|
|
1821
|
+
query.sort(compiled.mongoSort);
|
|
1822
|
+
query.limit(compiled.mongoLimit);
|
|
1823
|
+
return query;
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
const paginateMongoQuery = async (query, pagination, options) => {
|
|
1827
|
+
const compiled = compileMongoPagination(pagination, { cursor: options.cursor });
|
|
1828
|
+
MongoAdapter.applyPagination(query, compiled);
|
|
1829
|
+
const fetchedNodes = await query.exec();
|
|
1830
|
+
if (!Array.isArray(fetchedNodes)) {
|
|
1831
|
+
throw new Error("paginateMongoQuery expects query.exec() to return an array");
|
|
1832
|
+
}
|
|
1833
|
+
return materializeMongoPagination(compiled, fetchedNodes, { cursor: options.cursor });
|
|
1834
|
+
};
|
|
1835
|
+
const getQueryOptions = (query) => {
|
|
1836
|
+
if (!query || typeof query !== "object") return void 0;
|
|
1837
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1838
|
+
return query.getOptions();
|
|
1839
|
+
};
|
|
1840
|
+
const getPaginationFromOptions = (query) => {
|
|
1841
|
+
const options = getQueryOptions(query);
|
|
1842
|
+
return options?.pagination;
|
|
1843
|
+
};
|
|
1844
|
+
const getCursorFromOptions = (query) => {
|
|
1845
|
+
const options = getQueryOptions(query);
|
|
1846
|
+
return options?.paginationCursor;
|
|
1847
|
+
};
|
|
1848
|
+
const mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
1849
|
+
schema.query.paginate = async function(pagination, options) {
|
|
1850
|
+
const spec = pagination ?? getPaginationFromOptions(this);
|
|
1851
|
+
if (!spec) throw new Error("Missing pagination spec");
|
|
1852
|
+
const cursor = options?.cursor ?? getCursorFromOptions(this) ?? pluginOptions?.cursor;
|
|
1853
|
+
if (!cursor?.signingSecret) throw new Error("Missing pagination cursor signingSecret");
|
|
1854
|
+
return await paginateMongoQuery(this, spec, { cursor });
|
|
1855
|
+
};
|
|
1856
|
+
};
|
|
1427
1857
|
const rtsChangeLogApplied = /* @__PURE__ */ new WeakSet();
|
|
1428
1858
|
const accessibleRecordsApplied = /* @__PURE__ */ new WeakSet();
|
|
1859
|
+
const aclPluginApplied = /* @__PURE__ */ new WeakSet();
|
|
1860
|
+
const paginationPluginApplied = /* @__PURE__ */ new WeakSet();
|
|
1429
1861
|
let cachedModels = null;
|
|
1430
1862
|
const isRtsChangelogExcludedModelName = (name) => name.startsWith("RB");
|
|
1431
1863
|
const assertSchema = (exportName, value) => {
|
|
@@ -1445,6 +1877,14 @@ const buildAppModels = (modules) => Object.entries(modules).filter(([key]) => ke
|
|
|
1445
1877
|
schema.plugin(accessibleRecordsPlugin);
|
|
1446
1878
|
accessibleRecordsApplied.add(schema);
|
|
1447
1879
|
}
|
|
1880
|
+
if (!aclPluginApplied.has(schema)) {
|
|
1881
|
+
schema.plugin(mongooseAclPlugin);
|
|
1882
|
+
aclPluginApplied.add(schema);
|
|
1883
|
+
}
|
|
1884
|
+
if (!paginationPluginApplied.has(schema)) {
|
|
1885
|
+
schema.plugin(mongoPaginationPlugin);
|
|
1886
|
+
paginationPluginApplied.add(schema);
|
|
1887
|
+
}
|
|
1448
1888
|
if (!isRtsChangelogExcludedModelName(name)) {
|
|
1449
1889
|
if (!rtsChangeLogApplied.has(schema)) {
|
|
1450
1890
|
schema.plugin(rtsChangeLogPlugin);
|
|
@@ -1463,6 +1903,14 @@ const buildFrameworkModels = () => Object.entries(frameworkSchemas).filter(([key
|
|
|
1463
1903
|
schema.plugin(accessibleRecordsPlugin);
|
|
1464
1904
|
accessibleRecordsApplied.add(schema);
|
|
1465
1905
|
}
|
|
1906
|
+
if (!aclPluginApplied.has(schema)) {
|
|
1907
|
+
schema.plugin(mongooseAclPlugin);
|
|
1908
|
+
aclPluginApplied.add(schema);
|
|
1909
|
+
}
|
|
1910
|
+
if (!paginationPluginApplied.has(schema)) {
|
|
1911
|
+
schema.plugin(mongoPaginationPlugin);
|
|
1912
|
+
paginationPluginApplied.add(schema);
|
|
1913
|
+
}
|
|
1466
1914
|
if (!isRtsChangelogExcludedModelName(name)) {
|
|
1467
1915
|
if (!rtsChangeLogApplied.has(schema)) {
|
|
1468
1916
|
schema.plugin(rtsChangeLogPlugin);
|
|
@@ -1562,6 +2010,7 @@ const getTenantFilesystemDbFromCtx = async (ctx) => {
|
|
|
1562
2010
|
};
|
|
1563
2011
|
export {
|
|
1564
2012
|
LANGUAGE_CODE_REGEX,
|
|
2013
|
+
PaginationValidationError,
|
|
1565
2014
|
RBNotificationPolicy,
|
|
1566
2015
|
RBNotificationSchema,
|
|
1567
2016
|
RBNotificationSettingsPolicy,
|
|
@@ -1607,8 +2056,10 @@ export {
|
|
|
1607
2056
|
getTenantFilesystemDbFromCtx,
|
|
1608
2057
|
getTenantFilesystemDbName,
|
|
1609
2058
|
getTenantRolesFromSessionUser,
|
|
2059
|
+
isPaginationValidationError,
|
|
1610
2060
|
loadModel,
|
|
1611
2061
|
loadRbModel,
|
|
2062
|
+
mongoPaginationPlugin,
|
|
1612
2063
|
registerPoliciesFromModules,
|
|
1613
2064
|
registerPolicy,
|
|
1614
2065
|
resolveLocalizedString,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PaginationSpec } from '../../../api/src';
|
|
2
|
+
import { PaginationCursorCodecOptions } from './cursor';
|
|
3
|
+
import { NormalizedPaginationSpec } from './normalizeSpec';
|
|
4
|
+
export type CompiledMongoPagination = {
|
|
5
|
+
spec: NormalizedPaginationSpec;
|
|
6
|
+
mongoFilterDelta: Record<string, unknown>;
|
|
7
|
+
mongoSort: Record<string, 1 | -1>;
|
|
8
|
+
mongoLimit: number;
|
|
9
|
+
};
|
|
10
|
+
export type CompileMongoPaginationOptions = {
|
|
11
|
+
cursor: PaginationCursorCodecOptions;
|
|
12
|
+
};
|
|
13
|
+
export declare const compileMongoPagination: (spec: PaginationSpec, options: CompileMongoPaginationOptions) => CompiledMongoPagination;
|
|
14
|
+
//# sourceMappingURL=compileMongoPagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compileMongoPagination.d.ts","sourceRoot":"","sources":["../../src/pagination/compileMongoPagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,cAAc,CAAA;AAEnE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAG5D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAI/D,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAA;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,MAAM,cAAc,EACpB,SAAS,6BAA6B,KACrC,uBA4BF,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PaginationSpec } from '../../../api/src';
|
|
2
|
+
export type PaginationCursorCodecOptions = {
|
|
3
|
+
signingSecret: string | Buffer;
|
|
4
|
+
};
|
|
5
|
+
export declare const encodePaginationCursor: (spec: PaginationSpec, node: unknown, options: PaginationCursorCodecOptions) => string;
|
|
6
|
+
export declare const decodePaginationCursor: (spec: PaginationSpec, cursor: string, options: PaginationCursorCodecOptions) => Record<string, unknown>;
|
|
7
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/pagination/cursor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAmBlD,MAAM,MAAM,4BAA4B,GAAG;IACzC,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,MAAM,cAAc,EACpB,MAAM,OAAO,EACb,SAAS,4BAA4B,KACpC,MAiBF,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,MAAM,cAAc,EACpB,QAAQ,MAAM,EACd,SAAS,4BAA4B,KACpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAiCxB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type PaginationErrorCode = "invalid_pagination";
|
|
2
|
+
export declare class PaginationValidationError extends Error {
|
|
3
|
+
readonly code: PaginationErrorCode;
|
|
4
|
+
readonly statusCode = 400;
|
|
5
|
+
constructor(message: string, options?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
export declare const isPaginationValidationError: (error: unknown) => error is PaginationValidationError;
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/pagination/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,oBAAoB,CAAA;AAEtD,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAuB;IACzD,QAAQ,CAAC,UAAU,OAAM;gBAEb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED,eAAO,MAAM,2BAA2B,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,yBAQrE,CAAA"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { PaginationCursorCodecOptions } from './cursor';
|
|
2
|
+
export type { MongoPaginationPluginOptions } from './mongoPaginationPlugin';
|
|
3
|
+
export { mongoPaginationPlugin } from './mongoPaginationPlugin';
|
|
4
|
+
export { PaginationValidationError, isPaginationValidationError } from './errors';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pagination/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAC5D,YAAY,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,EAAE,yBAAyB,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PaginationSpec } from '../../../api/src';
|
|
2
|
+
export type MongoIndexHint = Record<string, 1 | -1>;
|
|
3
|
+
export declare const getMongoPaginationIndexHint: (spec: PaginationSpec, baseFilter: Record<string, unknown>) => MongoIndexHint;
|
|
4
|
+
//# sourceMappingURL=indexHint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexHint.d.ts","sourceRoot":"","sources":["../../src/pagination/indexHint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAKlD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAEnD,eAAO,MAAM,2BAA2B,GACtC,MAAM,cAAc,EACpB,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAClC,cAeF,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PaginationResponse } from '../../../api/src';
|
|
2
|
+
import { PaginationCursorCodecOptions } from './cursor';
|
|
3
|
+
import { CompiledMongoPagination } from './compileMongoPagination';
|
|
4
|
+
export type MaterializePaginationOptions = {
|
|
5
|
+
cursor: PaginationCursorCodecOptions;
|
|
6
|
+
};
|
|
7
|
+
export declare const materializeMongoPagination: <TNode>(compiled: CompiledMongoPagination, fetchedNodes: TNode[], options: MaterializePaginationOptions) => PaginationResponse<TNode>;
|
|
8
|
+
//# sourceMappingURL=materializePagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materializePagination.d.ts","sourceRoot":"","sources":["../../src/pagination/materializePagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEtD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAE5D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAGvE,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAA;AAED,eAAO,MAAM,0BAA0B,GAAI,KAAK,EAC9C,UAAU,uBAAuB,EACjC,cAAc,KAAK,EAAE,EACrB,SAAS,4BAA4B,KACpC,kBAAkB,CAAC,KAAK,CA4B1B,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CompiledMongoPagination } from './compileMongoPagination';
|
|
2
|
+
type MongoQueryLike = {
|
|
3
|
+
where: (delta: Record<string, unknown>) => unknown;
|
|
4
|
+
sort: (sort: Record<string, 1 | -1>) => unknown;
|
|
5
|
+
limit: (limit: number) => unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare const MongoAdapter: {
|
|
8
|
+
applyPagination: <TQuery extends MongoQueryLike>(query: TQuery, compiled: CompiledMongoPagination) => TQuery;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=mongoAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongoAdapter.d.ts","sourceRoot":"","sources":["../../src/pagination/mongoAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAGvE,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;IAChD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC,CAAA;AAED,eAAO,MAAM,YAAY;sBACL,MAAM,SAAS,cAAc,SAAS,MAAM,YAAY,uBAAuB,KAAG,MAAM;CAM3G,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { default as mongoose } from '../../../vite/node_modules/mongoose';
|
|
2
|
+
import { PaginationResponse, PaginationSpec } from '../../../api/src';
|
|
3
|
+
import { PaginationCursorCodecOptions } from './cursor';
|
|
4
|
+
declare const mongoPaginationPluginBrand: unique symbol;
|
|
5
|
+
export type MongoPaginationPluginOptions = {
|
|
6
|
+
cursor: PaginationCursorCodecOptions;
|
|
7
|
+
};
|
|
8
|
+
type QueryResultNode<TResult> = TResult extends Array<infer TNode> ? TNode : unknown;
|
|
9
|
+
declare module '../../../vite/node_modules/mongoose' {
|
|
10
|
+
interface Query<ResultType, DocType, THelpers = {}, RawDocType = unknown, QueryOp = "find", TDocOverrides = Record<string, never>> {
|
|
11
|
+
[mongoPaginationPluginBrand]?: [DocType, THelpers, RawDocType, QueryOp, TDocOverrides];
|
|
12
|
+
paginate: (pagination?: PaginationSpec, options?: MongoPaginationPluginOptions) => Promise<PaginationResponse<QueryResultNode<ResultType>>>;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export declare const mongoPaginationPlugin: (schema: mongoose.Schema, pluginOptions?: MongoPaginationPluginOptions) => void;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=mongoPaginationPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongoPaginationPlugin.d.ts","sourceRoot":"","sources":["../../src/pagination/mongoPaginationPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAEtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAI5D,OAAO,CAAC,MAAM,0BAA0B,EAAE,OAAO,MAAM,CAAA;AAqBvD,MAAM,MAAM,4BAA4B,GAAG;IACzC,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAA;AAED,KAAK,eAAe,CAAC,OAAO,IAAI,OAAO,SAAS,KAAK,CAAC,MAAM,KAAK,CAAC,GAAG,KAAK,GAAG,OAAO,CAAA;AAEpF,OAAO,QAAQ,UAAU,CAAC;IACxB,UAAU,KAAK,CACb,UAAU,EACV,OAAO,EACP,QAAQ,GAAG,EAAE,EACb,UAAU,GAAG,OAAO,EACpB,OAAO,GAAG,MAAM,EAChB,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAErC,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QACvF,QAAQ,EAAE,CACR,UAAU,CAAC,EAAE,cAAc,EAC3B,OAAO,CAAC,EAAE,4BAA4B,KACnC,OAAO,CAAC,kBAAkB,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;KAC/D;CACF;AAED,eAAO,MAAM,qBAAqB,GAAI,QAAQ,QAAQ,CAAC,MAAM,EAAE,gBAAgB,4BAA4B,KAAG,IAc7G,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PaginationDirection, PaginationOrder, PaginationSpec } from '../../../api/src';
|
|
2
|
+
export type NormalizedPaginationSpec = Omit<PaginationSpec, "direction" | "sort" | "limit"> & {
|
|
3
|
+
direction: PaginationDirection;
|
|
4
|
+
limit: number;
|
|
5
|
+
sort: Array<{
|
|
6
|
+
field: string;
|
|
7
|
+
order: PaginationOrder;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
export declare const normalizePaginationSpec: (spec: PaginationSpec) => NormalizedPaginationSpec;
|
|
11
|
+
//# sourceMappingURL=normalizeSpec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeSpec.d.ts","sourceRoot":"","sources":["../../src/pagination/normalizeSpec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAQxF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG;IAC5F,SAAS,EAAE,mBAAmB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,KAAK,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC,CAAC;CACJ,CAAA;AAED,eAAO,MAAM,uBAAuB,GAAI,MAAM,cAAc,KAAG,wBAgC9D,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PaginationResponse, PaginationSpec } from '../../../api/src';
|
|
2
|
+
import { PaginationCursorCodecOptions } from './cursor';
|
|
3
|
+
type MongoPaginateQueryLike<TNode> = {
|
|
4
|
+
where: (delta: Record<string, unknown>) => unknown;
|
|
5
|
+
sort: (sort: Record<string, 1 | -1>) => unknown;
|
|
6
|
+
limit: (limit: number) => unknown;
|
|
7
|
+
exec: () => Promise<TNode[]>;
|
|
8
|
+
};
|
|
9
|
+
export type PaginateMongoQueryOptions = {
|
|
10
|
+
cursor: PaginationCursorCodecOptions;
|
|
11
|
+
};
|
|
12
|
+
export declare const paginateMongoQuery: <TNode>(query: MongoPaginateQueryLike<TNode>, pagination: PaginationSpec, options: PaginateMongoQueryOptions) => Promise<PaginationResponse<TNode>>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=paginateMongoQuery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paginateMongoQuery.d.ts","sourceRoot":"","sources":["../../src/pagination/paginateMongoQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAEtE,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,UAAU,CAAA;AAM5D,KAAK,sBAAsB,CAAC,KAAK,IAAI;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;IAChD,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;CAC9B,CAAA;AAED,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAU,KAAK,EAC5C,OAAO,sBAAsB,CAAC,KAAK,CAAC,EACpC,YAAY,cAAc,EAC1B,SAAS,yBAAyB,KACjC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAWnC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerModels.d.ts","sourceRoot":"","sources":["../src/registerModels.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"registerModels.d.ts","sourceRoot":"","sources":["../src/registerModels.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAiB/B,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAwF3C,eAAO,MAAM,cAAc,GAAI,SAAS,YAAY,SAInD,CAAA;AAED,eAAO,MAAM,mBAAmB,sEAK/B,CAAA;AAED,YAAY,EAAE,YAAY,EAAE,CAAA"}
|