@rpcbase/db 0.35.0 → 0.36.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 +10 -15
- package/dist/acl/index.js +11 -0
- package/dist/can-urGFf45M.js +131 -0
- package/dist/extendMongooseSchema.d.ts +6 -0
- package/dist/extendMongooseSchema.d.ts.map +1 -0
- package/dist/index.browser.d.ts +2 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +22 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +309 -1137
- package/dist/models/{Tenant.d.ts → RBTenant.d.ts} +1 -1
- package/dist/models/RBTenant.d.ts.map +1 -0
- package/dist/models/{User.d.ts → RBUser.d.ts} +1 -1
- package/dist/models/RBUser.d.ts.map +1 -0
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/mongoose/index.d.ts +4 -0
- package/dist/mongoose/index.d.ts.map +1 -0
- package/dist/zod/extension.d.ts +17 -0
- package/dist/zod/extension.d.ts.map +1 -0
- package/dist/zod/index.d.ts +3 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/package.json +5 -4
- package/dist/acl.d.ts +0 -2
- package/dist/acl.d.ts.map +0 -1
- package/dist/models/Tenant.d.ts.map +0 -1
- package/dist/models/User.d.ts.map +0 -1
- package/dist/schema/assertions/assertions.d.ts +0 -20
- package/dist/schema/assertions/assertions.d.ts.map +0 -1
- package/dist/schema/assertions/constructor.d.ts +0 -10
- package/dist/schema/assertions/constructor.d.ts.map +0 -1
- package/dist/schema/assertions/custom.d.ts +0 -6
- package/dist/schema/assertions/custom.d.ts.map +0 -1
- package/dist/schema/assertions/instanceOf.d.ts +0 -10
- package/dist/schema/assertions/instanceOf.d.ts.map +0 -1
- package/dist/schema/assertions/staticNames.d.ts +0 -10
- package/dist/schema/assertions/staticNames.d.ts.map +0 -1
- package/dist/schema/assertions/types.d.ts +0 -17
- package/dist/schema/assertions/types.d.ts.map +0 -1
- package/dist/schema/extension.d.ts +0 -78
- package/dist/schema/extension.d.ts.map +0 -1
- package/dist/schema/index.d.ts +0 -73
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/mongoose.types.d.ts +0 -93
- package/dist/schema/mongoose.types.d.ts.map +0 -1
- package/dist/setupFile.d.ts +0 -2
- package/dist/setupFile.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { a as registerPoliciesFromModules } from "./can-urGFf45M.js";
|
|
2
|
+
import { b, d, f, e, g, c, r } from "./can-urGFf45M.js";
|
|
3
|
+
import mongoose, { Schema as Schema$1, Types } from "mongoose";
|
|
4
|
+
import { default as default2 } from "mongoose";
|
|
5
|
+
import { z } from "zod";
|
|
4
6
|
import { z as z2 } from "zod";
|
|
7
|
+
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
8
|
+
import "./index.browser.js";
|
|
5
9
|
import assert from "assert";
|
|
6
10
|
import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
|
|
7
|
-
import
|
|
8
|
-
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
11
|
+
import "@casl/ability";
|
|
9
12
|
const ZRBUser = z.object({
|
|
10
13
|
email: z.string().email().optional(),
|
|
11
14
|
password: z.string(),
|
|
@@ -16,7 +19,7 @@ const ZRBUser = z.object({
|
|
|
16
19
|
emailVerificationCode: z.string().length(6).optional(),
|
|
17
20
|
emailVerificationExpiresAt: z.date().optional()
|
|
18
21
|
});
|
|
19
|
-
const RBUserSchema = new Schema({
|
|
22
|
+
const RBUserSchema = new Schema$1({
|
|
20
23
|
email: { type: String, unique: true, sparse: true },
|
|
21
24
|
phone: { type: String, unique: true, sparse: true },
|
|
22
25
|
password: { type: String, required: true },
|
|
@@ -31,7 +34,7 @@ const ZRBTenant = z.object({
|
|
|
31
34
|
parentTenantId: z.string().optional(),
|
|
32
35
|
name: z.string().optional()
|
|
33
36
|
});
|
|
34
|
-
const RBTenantSchema = new Schema({
|
|
37
|
+
const RBTenantSchema = new Schema$1({
|
|
35
38
|
tenantId: { type: String, required: true, unique: true, index: true },
|
|
36
39
|
parentTenantId: { type: String },
|
|
37
40
|
name: { type: String }
|
|
@@ -72,7 +75,7 @@ const ZRBTenantSubscription = z.object({
|
|
|
72
75
|
latestEventAt: z.date().optional(),
|
|
73
76
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
74
77
|
});
|
|
75
|
-
const RBTenantSubscriptionSchema = new Schema(
|
|
78
|
+
const RBTenantSubscriptionSchema = new Schema$1(
|
|
76
79
|
{
|
|
77
80
|
tenantId: { type: String, required: true, index: true },
|
|
78
81
|
subscriptionId: { type: String, required: true },
|
|
@@ -96,7 +99,7 @@ const RBTenantSubscriptionSchema = new Schema(
|
|
|
96
99
|
providerSubscriptionId: { type: String },
|
|
97
100
|
latestEventId: { type: String },
|
|
98
101
|
latestEventAt: { type: Date },
|
|
99
|
-
metadata: { type: Schema.Types.Mixed }
|
|
102
|
+
metadata: { type: Schema$1.Types.Mixed }
|
|
100
103
|
},
|
|
101
104
|
{
|
|
102
105
|
collection: "tenantSubscriptions"
|
|
@@ -140,7 +143,7 @@ const ZRBTenantSubscriptionEvent = z.object({
|
|
|
140
143
|
providerPayload: z.unknown().optional(),
|
|
141
144
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
142
145
|
});
|
|
143
|
-
const RBTenantSubscriptionEventSchema = new Schema(
|
|
146
|
+
const RBTenantSubscriptionEventSchema = new Schema$1(
|
|
144
147
|
{
|
|
145
148
|
tenantId: { type: String, required: true, index: true },
|
|
146
149
|
subscriptionId: { type: String, required: true, index: true },
|
|
@@ -163,8 +166,8 @@ const RBTenantSubscriptionEventSchema = new Schema(
|
|
|
163
166
|
reason: { type: String },
|
|
164
167
|
provider: { type: String },
|
|
165
168
|
providerEventId: { type: String },
|
|
166
|
-
providerPayload: { type: Schema.Types.Mixed },
|
|
167
|
-
metadata: { type: Schema.Types.Mixed }
|
|
169
|
+
providerPayload: { type: Schema$1.Types.Mixed },
|
|
170
|
+
metadata: { type: Schema$1.Types.Mixed }
|
|
168
171
|
},
|
|
169
172
|
{
|
|
170
173
|
collection: "tenantSubscriptionEvents"
|
|
@@ -176,7 +179,7 @@ const ZRBRtsCounter = z.object({
|
|
|
176
179
|
_id: z.string(),
|
|
177
180
|
seq: z.number().int().min(0)
|
|
178
181
|
});
|
|
179
|
-
const RBRtsCounterSchema = new Schema(
|
|
182
|
+
const RBRtsCounterSchema = new Schema$1(
|
|
180
183
|
{
|
|
181
184
|
_id: { type: String, required: true },
|
|
182
185
|
seq: { type: Number, required: true, default: 0 }
|
|
@@ -196,7 +199,7 @@ const ZRBRtsChange = z.object({
|
|
|
196
199
|
docId: z.string().optional(),
|
|
197
200
|
ts: z.date()
|
|
198
201
|
});
|
|
199
|
-
const RBRtsChangeSchema = new Schema(
|
|
202
|
+
const RBRtsChangeSchema = new Schema$1(
|
|
200
203
|
{
|
|
201
204
|
seq: { type: Number, required: true },
|
|
202
205
|
modelName: { type: String, required: true, index: true },
|
|
@@ -228,7 +231,7 @@ const ZRBUploadSession = z.object({
|
|
|
228
231
|
isPublic: z.boolean().optional(),
|
|
229
232
|
error: z.string().optional()
|
|
230
233
|
});
|
|
231
|
-
const RBUploadSessionSchema = new Schema(
|
|
234
|
+
const RBUploadSessionSchema = new Schema$1(
|
|
232
235
|
{
|
|
233
236
|
_id: { type: String, required: true },
|
|
234
237
|
userId: { type: String, required: false, index: true },
|
|
@@ -277,7 +280,7 @@ const ZRBUploadChunk = z.object({
|
|
|
277
280
|
createdAt: z.date(),
|
|
278
281
|
expiresAt: z.date()
|
|
279
282
|
});
|
|
280
|
-
const RBUploadChunkSchema = new Schema(
|
|
283
|
+
const RBUploadChunkSchema = new Schema$1(
|
|
281
284
|
{
|
|
282
285
|
uploadId: { type: String, required: true, index: true },
|
|
283
286
|
index: { type: Number, required: true },
|
|
@@ -307,7 +310,7 @@ const ZRBNotification = z.object({
|
|
|
307
310
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
308
311
|
});
|
|
309
312
|
const TTL_90_DAYS_S = 60 * 60 * 24 * 90;
|
|
310
|
-
const RBNotificationSchema = new Schema(
|
|
313
|
+
const RBNotificationSchema = new Schema$1(
|
|
311
314
|
{
|
|
312
315
|
userId: { type: String, required: true, index: true },
|
|
313
316
|
topic: { type: String, required: false, index: true },
|
|
@@ -318,7 +321,7 @@ const RBNotificationSchema = new Schema(
|
|
|
318
321
|
seenAt: { type: Date, required: false, index: true },
|
|
319
322
|
readAt: { type: Date, required: false, index: true },
|
|
320
323
|
archivedAt: { type: Date, required: false },
|
|
321
|
-
metadata: { type: Schema.Types.Mixed, required: false }
|
|
324
|
+
metadata: { type: Schema$1.Types.Mixed, required: false }
|
|
322
325
|
},
|
|
323
326
|
{
|
|
324
327
|
versionKey: false,
|
|
@@ -352,7 +355,7 @@ const ZRBNotificationSettings = z.object({
|
|
|
352
355
|
topicPreferences: z.array(ZRBNotificationTopicPreference).optional(),
|
|
353
356
|
lastDigestSentAt: z.date().optional()
|
|
354
357
|
});
|
|
355
|
-
const TopicPreferenceSchema = new Schema(
|
|
358
|
+
const TopicPreferenceSchema = new Schema$1(
|
|
356
359
|
{
|
|
357
360
|
topic: { type: String, required: true },
|
|
358
361
|
inApp: { type: Boolean, required: true, default: true },
|
|
@@ -361,7 +364,7 @@ const TopicPreferenceSchema = new Schema(
|
|
|
361
364
|
},
|
|
362
365
|
{ _id: false }
|
|
363
366
|
);
|
|
364
|
-
const RBNotificationSettingsSchema = new Schema(
|
|
367
|
+
const RBNotificationSettingsSchema = new Schema$1(
|
|
365
368
|
{
|
|
366
369
|
userId: { type: String, required: true },
|
|
367
370
|
digestFrequency: {
|
|
@@ -429,723 +432,291 @@ const frameworkSchemas = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
429
432
|
ZRBUploadSessionStatus,
|
|
430
433
|
ZRBUser
|
|
431
434
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
object(f) {
|
|
440
|
-
return f.constructor.name === "ZodObject";
|
|
441
|
-
},
|
|
442
|
-
array(f) {
|
|
443
|
-
return f.constructor.name === "ZodArray";
|
|
444
|
-
},
|
|
445
|
-
boolean(f) {
|
|
446
|
-
return f.constructor.name === "ZodBoolean";
|
|
447
|
-
},
|
|
448
|
-
enumerable(f) {
|
|
449
|
-
return f.constructor.name === "ZodEnum";
|
|
450
|
-
},
|
|
451
|
-
date(f) {
|
|
452
|
-
return f.constructor.name === "ZodDate";
|
|
453
|
-
},
|
|
454
|
-
def(f) {
|
|
455
|
-
return f.constructor.name === "ZodDefault";
|
|
456
|
-
},
|
|
457
|
-
optional(f) {
|
|
458
|
-
return f.constructor.name === "ZodOptional";
|
|
459
|
-
},
|
|
460
|
-
nullable(f) {
|
|
461
|
-
return f.constructor.name === "ZodNullable";
|
|
462
|
-
},
|
|
463
|
-
union(f) {
|
|
464
|
-
return f.constructor.name === "ZodUnion";
|
|
465
|
-
},
|
|
466
|
-
any(f) {
|
|
467
|
-
return f.constructor.name === "ZodAny";
|
|
468
|
-
},
|
|
469
|
-
mapOrRecord(f) {
|
|
470
|
-
return f.constructor.name === "ZodMap" || f.constructor.name === "ZodRecord";
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
const zmAssertIds = {
|
|
474
|
-
objectId(f) {
|
|
475
|
-
return "__zm_type" in f && f.__zm_type === "ObjectId";
|
|
476
|
-
},
|
|
477
|
-
uuid(f) {
|
|
478
|
-
return "__zm_type" in f && f.__zm_type === "UUID";
|
|
435
|
+
const { Schema, model } = mongoose;
|
|
436
|
+
class PaginationValidationError extends Error {
|
|
437
|
+
code = "invalid_pagination";
|
|
438
|
+
statusCode = 400;
|
|
439
|
+
constructor(message, options) {
|
|
440
|
+
super(message, options);
|
|
441
|
+
this.name = "PaginationValidationError";
|
|
479
442
|
}
|
|
443
|
+
}
|
|
444
|
+
const isPaginationValidationError = (error) => {
|
|
445
|
+
if (!error || typeof error !== "object") return false;
|
|
446
|
+
const anyError = error;
|
|
447
|
+
return anyError.name === "PaginationValidationError" && anyError.code === "invalid_pagination" && anyError.statusCode === 400;
|
|
480
448
|
};
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
enumerable(f) {
|
|
498
|
-
return f instanceof ZodEnum;
|
|
499
|
-
},
|
|
500
|
-
date(f) {
|
|
501
|
-
return f instanceof ZodDate;
|
|
502
|
-
},
|
|
503
|
-
def(f) {
|
|
504
|
-
return f instanceof ZodDefault;
|
|
505
|
-
},
|
|
506
|
-
optional(f) {
|
|
507
|
-
return f instanceof ZodOptional;
|
|
508
|
-
},
|
|
509
|
-
nullable(f) {
|
|
510
|
-
return f instanceof ZodNullable;
|
|
511
|
-
},
|
|
512
|
-
union(f) {
|
|
513
|
-
return f instanceof ZodUnion;
|
|
514
|
-
},
|
|
515
|
-
any(f) {
|
|
516
|
-
return f instanceof ZodAny;
|
|
517
|
-
},
|
|
518
|
-
mapOrRecord(f) {
|
|
519
|
-
return f instanceof ZodMap || f instanceof ZodRecord;
|
|
449
|
+
const DISALLOWED_MONGO_FIELD_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
450
|
+
const PAGINATION_LIMIT_MAX = 128;
|
|
451
|
+
const normalizePaginationSpec = (spec) => {
|
|
452
|
+
if (!spec || typeof spec !== "object") throw new PaginationValidationError("Invalid PaginationSpec");
|
|
453
|
+
const limit = normalizeLimit(spec.limit);
|
|
454
|
+
const direction = spec.direction ?? "next";
|
|
455
|
+
if (direction !== "next" && direction !== "prev") throw new PaginationValidationError("Invalid pagination direction");
|
|
456
|
+
if (!Array.isArray(spec.sort) || spec.sort.length === 0) throw new PaginationValidationError("Invalid pagination sort");
|
|
457
|
+
const sort = spec.sort.map(({ field, order }) => ({
|
|
458
|
+
field: assertSafeMongoFieldPath(field),
|
|
459
|
+
order: normalizeOrder(order)
|
|
460
|
+
}));
|
|
461
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
462
|
+
for (const { field } of sort) {
|
|
463
|
+
if (seenFields.has(field)) throw new PaginationValidationError(`Duplicate pagination sort field: ${field}`);
|
|
464
|
+
seenFields.add(field);
|
|
520
465
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return "__zm_type" in f && f.__zm_type === "String";
|
|
525
|
-
},
|
|
526
|
-
number(f) {
|
|
527
|
-
return "__zm_type" in f && f.__zm_type === "Number";
|
|
528
|
-
},
|
|
529
|
-
object(f) {
|
|
530
|
-
return "__zm_type" in f && f.__zm_type === "Object";
|
|
531
|
-
},
|
|
532
|
-
array(f) {
|
|
533
|
-
return "__zm_type" in f && f.__zm_type === "Array";
|
|
534
|
-
},
|
|
535
|
-
boolean(f) {
|
|
536
|
-
return "__zm_type" in f && f.__zm_type === "Boolean";
|
|
537
|
-
},
|
|
538
|
-
enumerable(f) {
|
|
539
|
-
return "__zm_type" in f && f.__zm_type === "Enum";
|
|
540
|
-
},
|
|
541
|
-
date(f) {
|
|
542
|
-
return "__zm_type" in f && f.__zm_type === "Date";
|
|
543
|
-
},
|
|
544
|
-
def(f) {
|
|
545
|
-
return "__zm_type" in f && f.__zm_type === "Default";
|
|
546
|
-
},
|
|
547
|
-
optional(f) {
|
|
548
|
-
return "__zm_type" in f && f.__zm_type === "Optional";
|
|
549
|
-
},
|
|
550
|
-
nullable(f) {
|
|
551
|
-
return "__zm_type" in f && f.__zm_type === "Nullable";
|
|
552
|
-
},
|
|
553
|
-
union(f) {
|
|
554
|
-
return "__zm_type" in f && f.__zm_type === "Union";
|
|
555
|
-
},
|
|
556
|
-
any(f) {
|
|
557
|
-
return "__zm_type" in f && f.__zm_type === "Any";
|
|
558
|
-
},
|
|
559
|
-
mapOrRecord(f) {
|
|
560
|
-
return "__zm_type" in f && (f.__zm_type === "Map" || f.__zm_type === "Record");
|
|
466
|
+
const primaryOrder = sort[0]?.order;
|
|
467
|
+
if (!seenFields.has("_id")) {
|
|
468
|
+
sort.push({ field: "_id", order: primaryOrder });
|
|
561
469
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
470
|
+
return {
|
|
471
|
+
...spec,
|
|
472
|
+
limit,
|
|
473
|
+
direction,
|
|
474
|
+
sort
|
|
567
475
|
};
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
...zmAssertIds
|
|
573
|
-
};
|
|
574
|
-
let zod_extended = false;
|
|
575
|
-
const isSchemaCandidate = (value) => {
|
|
576
|
-
return Boolean(value && typeof value === "object" && "_def" in value);
|
|
577
|
-
};
|
|
578
|
-
const resolveSchemaAndFlag = (self, args, defaultFlag = true) => {
|
|
579
|
-
let schema = isSchemaCandidate(self) ? self : void 0;
|
|
580
|
-
let flag;
|
|
581
|
-
if (args.length > 0) {
|
|
582
|
-
const [firstArg, secondArg] = args;
|
|
583
|
-
if (typeof firstArg === "boolean") flag = firstArg;
|
|
584
|
-
if (typeof secondArg === "boolean") flag = secondArg;
|
|
585
|
-
if (!schema && isSchemaCandidate(firstArg)) {
|
|
586
|
-
schema = firstArg;
|
|
587
|
-
if (typeof secondArg === "boolean") flag = secondArg;
|
|
588
|
-
}
|
|
476
|
+
};
|
|
477
|
+
const normalizeLimit = (limit) => {
|
|
478
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit) || limit <= 0) {
|
|
479
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
589
480
|
}
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
return { schema, flag };
|
|
593
|
-
};
|
|
594
|
-
const resolveSchemaAndValue = (self, args) => {
|
|
595
|
-
let schema = isSchemaCandidate(self) ? self : void 0;
|
|
596
|
-
let value = args[0];
|
|
597
|
-
if (!schema && isSchemaCandidate(value)) {
|
|
598
|
-
schema = value;
|
|
599
|
-
value = args.length > 1 ? args[1] : void 0;
|
|
481
|
+
if (limit > PAGINATION_LIMIT_MAX) {
|
|
482
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
600
483
|
}
|
|
601
|
-
return
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
schema._def.__zm_unique = flag;
|
|
616
|
-
}
|
|
617
|
-
return schema;
|
|
618
|
-
};
|
|
619
|
-
proto.sparse = function sparse(...args) {
|
|
620
|
-
const { schema, flag } = resolveSchemaAndFlag(this, args);
|
|
621
|
-
if (!schema) return this;
|
|
622
|
-
schema.__zm_sparse = flag;
|
|
623
|
-
if (schema._def && typeof schema._def === "object") {
|
|
624
|
-
schema._def.__zm_sparse = flag;
|
|
625
|
-
}
|
|
626
|
-
return schema;
|
|
627
|
-
};
|
|
484
|
+
return limit;
|
|
485
|
+
};
|
|
486
|
+
const normalizeOrder = (order) => {
|
|
487
|
+
if (order !== "asc" && order !== "desc") throw new PaginationValidationError("Invalid pagination order");
|
|
488
|
+
return order;
|
|
489
|
+
};
|
|
490
|
+
const assertSafeMongoFieldPath = (field) => {
|
|
491
|
+
if (typeof field !== "string" || field.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
492
|
+
if (field.startsWith("$")) throw new PaginationValidationError("Invalid pagination sort field");
|
|
493
|
+
const parts = field.split(".");
|
|
494
|
+
for (const part of parts) {
|
|
495
|
+
if (part.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
496
|
+
if (DISALLOWED_MONGO_FIELD_SEGMENTS.has(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
497
|
+
if (!/^[a-zA-Z0-9_]+$/.test(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
628
498
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
Optional: z_0.ZodOptional,
|
|
639
|
-
Nullable: z_0.ZodNullable,
|
|
640
|
-
Union: z_0.ZodUnion,
|
|
641
|
-
Any: z_0.ZodAny,
|
|
642
|
-
Map: z_0.ZodMap,
|
|
643
|
-
Record: z_0.ZodRecord,
|
|
644
|
-
Pipe: z_0.ZodPipe
|
|
645
|
-
};
|
|
646
|
-
for (const [key, value] of Object.entries(TypesMap)) {
|
|
647
|
-
if (value?.prototype) {
|
|
648
|
-
Object.defineProperty(value.prototype, "__zm_type", {
|
|
649
|
-
value: key,
|
|
650
|
-
configurable: true,
|
|
651
|
-
writable: true
|
|
652
|
-
});
|
|
499
|
+
return field;
|
|
500
|
+
};
|
|
501
|
+
const encodePaginationCursor = (spec, node, options) => {
|
|
502
|
+
const normalized = normalizePaginationSpec(spec);
|
|
503
|
+
const values = /* @__PURE__ */ Object.create(null);
|
|
504
|
+
for (const { field } of normalized.sort) {
|
|
505
|
+
const value = readFieldValue(node, field);
|
|
506
|
+
if (typeof value === "undefined") {
|
|
507
|
+
throw new Error(`Pagination cursor encode failed (missing field: ${field})`);
|
|
653
508
|
}
|
|
509
|
+
values[field] = encodeCursorValue(field, value);
|
|
654
510
|
}
|
|
655
|
-
}
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (target._def && typeof target._def === "object") {
|
|
669
|
-
target._def.__zm_ref = value;
|
|
670
|
-
}
|
|
671
|
-
return target;
|
|
672
|
-
};
|
|
673
|
-
schema.refPath = function refPath(...args) {
|
|
674
|
-
const { schema: target, value } = resolveSchemaAndValue(this, args);
|
|
675
|
-
if (!target || typeof value !== "string") return target ?? this;
|
|
676
|
-
target.__zm_refPath = value;
|
|
677
|
-
if (target._def && typeof target._def === "object") {
|
|
678
|
-
target._def.__zm_refPath = value;
|
|
679
|
-
}
|
|
680
|
-
return target;
|
|
681
|
-
};
|
|
682
|
-
schema.unique = function unique(...args) {
|
|
683
|
-
const { schema: target, flag } = resolveSchemaAndFlag(this, args);
|
|
684
|
-
if (!target) return this;
|
|
685
|
-
target.__zm_unique = flag;
|
|
686
|
-
if (target._def && typeof target._def === "object") {
|
|
687
|
-
target._def.__zm_unique = flag;
|
|
688
|
-
}
|
|
689
|
-
return target;
|
|
690
|
-
};
|
|
691
|
-
schema.sparse = function sparse(...args) {
|
|
692
|
-
const { schema: target, flag } = resolveSchemaAndFlag(this, args);
|
|
693
|
-
if (!target) return this;
|
|
694
|
-
target.__zm_sparse = flag;
|
|
695
|
-
if (target._def && typeof target._def === "object") {
|
|
696
|
-
target._def.__zm_sparse = flag;
|
|
697
|
-
}
|
|
698
|
-
return target;
|
|
699
|
-
};
|
|
700
|
-
return output;
|
|
701
|
-
};
|
|
702
|
-
const createUUID = () => {
|
|
703
|
-
return z.uuid({ error: "Invalid UUID" }).or(z.instanceof(Types.UUID));
|
|
704
|
-
};
|
|
705
|
-
const zUUID = (ref) => {
|
|
706
|
-
const output = createUUID();
|
|
707
|
-
const schema = output;
|
|
708
|
-
schema.__zm_type = "UUID";
|
|
709
|
-
schema.__zm_ref = ref;
|
|
710
|
-
schema.ref = function ref2(...args) {
|
|
711
|
-
const { schema: target, value } = resolveSchemaAndValue(this, args);
|
|
712
|
-
if (!target || typeof value !== "string") return target ?? this;
|
|
713
|
-
target.__zm_ref = value;
|
|
714
|
-
if (target._def && typeof target._def === "object") {
|
|
715
|
-
target._def.__zm_ref = value;
|
|
716
|
-
}
|
|
717
|
-
return target;
|
|
718
|
-
};
|
|
719
|
-
schema.refPath = function refPath(...args) {
|
|
720
|
-
const { schema: target, value } = resolveSchemaAndValue(this, args);
|
|
721
|
-
if (!target || typeof value !== "string") return target ?? this;
|
|
722
|
-
target.__zm_refPath = value;
|
|
723
|
-
if (target._def && typeof target._def === "object") {
|
|
724
|
-
target._def.__zm_refPath = value;
|
|
725
|
-
}
|
|
726
|
-
return target;
|
|
727
|
-
};
|
|
728
|
-
schema.unique = function unique(...args) {
|
|
729
|
-
const { schema: target, flag } = resolveSchemaAndFlag(this, args);
|
|
730
|
-
if (!target) return this;
|
|
731
|
-
target.__zm_unique = flag;
|
|
732
|
-
if (target._def && typeof target._def === "object") {
|
|
733
|
-
target._def.__zm_unique = flag;
|
|
734
|
-
}
|
|
735
|
-
return target;
|
|
736
|
-
};
|
|
737
|
-
schema.sparse = function sparse(...args) {
|
|
738
|
-
const { schema: target, flag } = resolveSchemaAndFlag(this, args);
|
|
739
|
-
if (!target) return this;
|
|
740
|
-
target.__zm_sparse = flag;
|
|
741
|
-
if (target._def && typeof target._def === "object") {
|
|
742
|
-
target._def.__zm_sparse = flag;
|
|
743
|
-
}
|
|
744
|
-
return target;
|
|
745
|
-
};
|
|
746
|
-
return output;
|
|
747
|
-
};
|
|
748
|
-
const LANGUAGE_CODE_REGEX = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
|
|
749
|
-
const LOCALIZED_STRING_PROXY_CACHE = /* @__PURE__ */ new WeakMap();
|
|
750
|
-
function normalizeLocale(locale) {
|
|
751
|
-
const trimmed = locale.trim();
|
|
752
|
-
if (!trimmed) return "";
|
|
753
|
-
const getCanonicalLocales = Intl?.getCanonicalLocales;
|
|
754
|
-
if (typeof getCanonicalLocales !== "function") return trimmed;
|
|
511
|
+
const payload = { v: 1, values };
|
|
512
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
513
|
+
const sigB64 = signCursorPayloadB64(payloadB64, options.signingSecret);
|
|
514
|
+
return `${payloadB64}.${sigB64}`;
|
|
515
|
+
};
|
|
516
|
+
const decodePaginationCursor = (spec, cursor, options) => {
|
|
517
|
+
const normalized = normalizePaginationSpec(spec);
|
|
518
|
+
const [payloadB64, sigB64, ...rest] = cursor.split(".");
|
|
519
|
+
if (rest.length > 0) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
520
|
+
if (!sigB64) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
521
|
+
verifyCursorSignature(payloadB64, sigB64, options.signingSecret);
|
|
522
|
+
const payloadRaw = Buffer.from(payloadB64, "base64url").toString("utf8");
|
|
523
|
+
let payloadUnknown;
|
|
755
524
|
try {
|
|
756
|
-
|
|
525
|
+
payloadUnknown = JSON.parse(payloadRaw);
|
|
757
526
|
} catch {
|
|
758
|
-
|
|
527
|
+
throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
759
528
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
if (!value) return;
|
|
769
|
-
if (seen.has(value)) return;
|
|
770
|
-
seen.add(value);
|
|
771
|
-
output.push(value);
|
|
772
|
-
};
|
|
773
|
-
const addChain = (value) => {
|
|
774
|
-
if (!value) return;
|
|
775
|
-
const parts = value.split("-").filter(Boolean);
|
|
776
|
-
while (parts.length > 0) {
|
|
777
|
-
push(parts.join("-"));
|
|
778
|
-
parts.pop();
|
|
529
|
+
if (!payloadUnknown || typeof payloadUnknown !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
530
|
+
const payload = payloadUnknown;
|
|
531
|
+
if (payload.v !== 1) throw new PaginationValidationError("Unsupported pagination cursor version");
|
|
532
|
+
if (!payload.values || typeof payload.values !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
533
|
+
const decoded = /* @__PURE__ */ Object.create(null);
|
|
534
|
+
for (const { field } of normalized.sort) {
|
|
535
|
+
if (!Object.prototype.hasOwnProperty.call(payload.values, field)) {
|
|
536
|
+
throw new PaginationValidationError(`Pagination cursor missing field: ${field}`);
|
|
779
537
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
addChain(canonical);
|
|
783
|
-
for (const fallback of extra) addChain(normalizeLocale(fallback));
|
|
784
|
-
return output;
|
|
785
|
-
}
|
|
786
|
-
function resolveLocalizedString(value, locale, options) {
|
|
787
|
-
if (!value) return void 0;
|
|
788
|
-
const chain = buildLocaleFallbackChain(locale, options?.fallbacks);
|
|
789
|
-
if (chain.length === 0) return void 0;
|
|
790
|
-
const record = value;
|
|
791
|
-
for (const key of chain) {
|
|
792
|
-
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
793
|
-
return record[key];
|
|
538
|
+
const encodedValue = payload.values[field];
|
|
539
|
+
decoded[field] = decodeCursorValue(field, encodedValue);
|
|
794
540
|
}
|
|
795
|
-
return
|
|
796
|
-
}
|
|
797
|
-
function withLocalizedStringFallback(value) {
|
|
798
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
799
|
-
if (value instanceof Map) return value;
|
|
800
|
-
const cached = LOCALIZED_STRING_PROXY_CACHE.get(value);
|
|
801
|
-
if (cached) return cached;
|
|
802
|
-
const proxy = withFallbackRecord(value);
|
|
803
|
-
LOCALIZED_STRING_PROXY_CACHE.set(value, proxy);
|
|
804
|
-
return proxy;
|
|
805
|
-
}
|
|
806
|
-
function withFallbackRecord(record) {
|
|
807
|
-
const getExact = (key) => record[key];
|
|
808
|
-
const hasExact = (key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
809
|
-
return new Proxy(record, {
|
|
810
|
-
get(target, prop) {
|
|
811
|
-
if (prop === "getExact") return getExact;
|
|
812
|
-
if (prop === "hasExact") return hasExact;
|
|
813
|
-
if (prop === "get") return (key) => resolveLocalizedString(record, key);
|
|
814
|
-
if (typeof prop === "string" && !(prop in target)) {
|
|
815
|
-
return resolveLocalizedString(record, prop);
|
|
816
|
-
}
|
|
817
|
-
return target[prop];
|
|
818
|
-
},
|
|
819
|
-
set(target, prop, next) {
|
|
820
|
-
target[prop] = next;
|
|
821
|
-
return true;
|
|
822
|
-
}
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
const zLocalizedString = () => {
|
|
826
|
-
const schema = z.record(
|
|
827
|
-
z.string().regex(LANGUAGE_CODE_REGEX, { message: "Expected a language code (BCP 47, e.g. en or fr-FR)." }),
|
|
828
|
-
z.string()
|
|
829
|
-
);
|
|
830
|
-
schema.__rpcbase_localizedString = true;
|
|
831
|
-
return schema;
|
|
541
|
+
return decoded;
|
|
832
542
|
};
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const object = {};
|
|
843
|
-
for (const [key, rawField] of Object.entries(obj.shape)) {
|
|
844
|
-
const field = rawField;
|
|
845
|
-
if (zmAssert$1.object(field)) {
|
|
846
|
-
object[key] = parseObject(field);
|
|
847
|
-
} else {
|
|
848
|
-
const f = parseField(field);
|
|
849
|
-
if (!f) throw new Error(`Unsupported field type: ${field.constructor}`);
|
|
850
|
-
object[key] = f;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
return object;
|
|
854
|
-
}
|
|
855
|
-
function extractCustomValidation(field) {
|
|
856
|
-
const checks = field._def?.checks;
|
|
857
|
-
if (!Array.isArray(checks)) return void 0;
|
|
858
|
-
for (const check of checks) {
|
|
859
|
-
if (!check || typeof check !== "object") continue;
|
|
860
|
-
if (check.__zm_validation) {
|
|
861
|
-
return check.__zm_validation;
|
|
862
|
-
}
|
|
863
|
-
const def = check.def;
|
|
864
|
-
if (!def || def.type !== "custom" || typeof def.fn !== "function") continue;
|
|
865
|
-
const validator = (value) => {
|
|
866
|
-
try {
|
|
867
|
-
const result = def.fn(value, {
|
|
868
|
-
ctx: {
|
|
869
|
-
addIssue() {
|
|
870
|
-
return void 0;
|
|
871
|
-
}
|
|
872
|
-
},
|
|
873
|
-
input: value,
|
|
874
|
-
parsedType: typeof value
|
|
875
|
-
});
|
|
876
|
-
return result !== false;
|
|
877
|
-
} catch {
|
|
878
|
-
return false;
|
|
879
|
-
}
|
|
880
|
-
};
|
|
881
|
-
let message;
|
|
882
|
-
if (typeof def.error === "function") {
|
|
883
|
-
try {
|
|
884
|
-
const computed = def.error({
|
|
885
|
-
ctx: {
|
|
886
|
-
addIssue() {
|
|
887
|
-
return void 0;
|
|
888
|
-
}
|
|
889
|
-
},
|
|
890
|
-
input: void 0,
|
|
891
|
-
parsedType: "unknown"
|
|
892
|
-
});
|
|
893
|
-
if (typeof computed === "string") {
|
|
894
|
-
message = computed;
|
|
895
|
-
}
|
|
896
|
-
} catch {
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
const validation = {
|
|
900
|
-
validator,
|
|
901
|
-
message
|
|
902
|
-
};
|
|
903
|
-
check.__zm_validation = validation;
|
|
904
|
-
return validation;
|
|
905
|
-
}
|
|
906
|
-
return void 0;
|
|
907
|
-
}
|
|
908
|
-
function parseField(field, required = true, def, refinement) {
|
|
909
|
-
if (!field) return null;
|
|
910
|
-
const customRefinement = extractCustomValidation(field);
|
|
911
|
-
const directRefinement = field.__zm_validation ?? field._def?.__zm_validation ?? customRefinement;
|
|
912
|
-
const currentRefinement = refinement ?? directRefinement;
|
|
913
|
-
if (zmAssert$1.objectId(field)) {
|
|
914
|
-
const ref = field.__zm_ref;
|
|
915
|
-
const refPath = field.__zm_refPath;
|
|
916
|
-
const unique = field.__zm_unique ?? (field._def?.__zm_unique ?? false);
|
|
917
|
-
const sparse = field.__zm_sparse ?? (field._def?.__zm_sparse ?? false);
|
|
918
|
-
return parseObjectId(required, ref, unique, refPath, sparse);
|
|
919
|
-
}
|
|
920
|
-
if (zmAssert$1.uuid(field)) {
|
|
921
|
-
const ref = field.__zm_ref;
|
|
922
|
-
const refPath = field.__zm_refPath;
|
|
923
|
-
const unique = field.__zm_unique ?? (field._def?.__zm_unique ?? false);
|
|
924
|
-
const sparse = field.__zm_sparse ?? (field._def?.__zm_sparse ?? false);
|
|
925
|
-
return parseUUID(required, ref, unique, refPath, sparse);
|
|
926
|
-
}
|
|
927
|
-
if (zmAssert$1.object(field)) {
|
|
928
|
-
return parseObject(field);
|
|
929
|
-
}
|
|
930
|
-
if (zmAssert$1.number(field)) {
|
|
931
|
-
const isUnique = field.__zm_unique ?? field._def?.__zm_unique ?? false;
|
|
932
|
-
const isSparse = field.__zm_sparse ?? field._def?.__zm_sparse ?? false;
|
|
933
|
-
return parseNumber(
|
|
934
|
-
field,
|
|
935
|
-
required,
|
|
936
|
-
def,
|
|
937
|
-
isUnique,
|
|
938
|
-
currentRefinement,
|
|
939
|
-
isSparse
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
if (zmAssert$1.string(field)) {
|
|
943
|
-
const isUnique = field.__zm_unique ?? field._def?.__zm_unique ?? false;
|
|
944
|
-
const isSparse = field.__zm_sparse ?? field._def?.__zm_sparse ?? false;
|
|
945
|
-
return parseString(
|
|
946
|
-
field,
|
|
947
|
-
required,
|
|
948
|
-
def,
|
|
949
|
-
isUnique,
|
|
950
|
-
currentRefinement,
|
|
951
|
-
isSparse
|
|
952
|
-
);
|
|
953
|
-
}
|
|
954
|
-
if (zmAssert$1.enumerable(field)) {
|
|
955
|
-
const enumValues = Array.isArray(field.options) ? field.options : Object.values(field.enum ?? {});
|
|
956
|
-
return parseEnum(enumValues, required, def);
|
|
957
|
-
}
|
|
958
|
-
if (zmAssert$1.boolean(field)) {
|
|
959
|
-
return parseBoolean(required, def);
|
|
960
|
-
}
|
|
961
|
-
if (zmAssert$1.date(field)) {
|
|
962
|
-
const isUnique = field.__zm_unique ?? field._def?.__zm_unique ?? false;
|
|
963
|
-
const isSparse = field.__zm_sparse ?? field._def?.__zm_sparse ?? false;
|
|
964
|
-
return parseDate(
|
|
965
|
-
required,
|
|
966
|
-
def,
|
|
967
|
-
currentRefinement,
|
|
968
|
-
isUnique,
|
|
969
|
-
isSparse
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
if (zmAssert$1.array(field)) {
|
|
973
|
-
return parseArray(
|
|
974
|
-
required,
|
|
975
|
-
field.element,
|
|
976
|
-
def
|
|
977
|
-
);
|
|
543
|
+
const signCursorPayloadB64 = (payloadB64, secret) => {
|
|
544
|
+
return createHmac("sha256", secret).update(payloadB64, "utf8").digest("base64url");
|
|
545
|
+
};
|
|
546
|
+
const verifyCursorSignature = (payloadB64, sigB64, secret) => {
|
|
547
|
+
const expectedSigB64 = signCursorPayloadB64(payloadB64, secret);
|
|
548
|
+
const a = Buffer.from(sigB64, "utf8");
|
|
549
|
+
const b2 = Buffer.from(expectedSigB64, "utf8");
|
|
550
|
+
if (a.length !== b2.length || !timingSafeEqual(a, b2)) {
|
|
551
|
+
throw new PaginationValidationError("Invalid pagination cursor signature");
|
|
978
552
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
currentRefinement
|
|
986
|
-
);
|
|
553
|
+
};
|
|
554
|
+
const encodeCursorValue = (field, value) => {
|
|
555
|
+
if (value === null) return null;
|
|
556
|
+
if (field === "_id") {
|
|
557
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
558
|
+
throw new Error("Pagination cursor encode failed (_id must be an ObjectId)");
|
|
987
559
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
560
|
+
if (value instanceof Date) return { $date: value.toISOString() };
|
|
561
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
562
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
563
|
+
throw new Error(`Unsupported pagination cursor value type for field: ${field}`);
|
|
564
|
+
};
|
|
565
|
+
const decodeCursorValue = (field, value) => {
|
|
566
|
+
if (value === null) return null;
|
|
567
|
+
if (typeof value === "string") {
|
|
568
|
+
if (field === "_id") throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
569
|
+
return value;
|
|
990
570
|
}
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
571
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
572
|
+
if (!value || typeof value !== "object") throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
573
|
+
if ("$date" in value) {
|
|
574
|
+
if (typeof value.$date !== "string") throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
575
|
+
const d2 = new Date(value.$date);
|
|
576
|
+
if (Number.isNaN(d2.getTime())) throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
577
|
+
return d2;
|
|
998
578
|
}
|
|
999
|
-
if (
|
|
1000
|
-
|
|
579
|
+
if ("$oid" in value) {
|
|
580
|
+
if (typeof value.$oid !== "string" || !Types.ObjectId.isValid(value.$oid)) {
|
|
581
|
+
throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
582
|
+
}
|
|
583
|
+
return new Types.ObjectId(value.$oid);
|
|
1001
584
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
585
|
+
throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
586
|
+
};
|
|
587
|
+
const readFieldValue = (node, field) => {
|
|
588
|
+
if (!node || typeof node !== "object") return void 0;
|
|
589
|
+
if ("get" in node && typeof node.get === "function") {
|
|
590
|
+
return node.get(field);
|
|
1004
591
|
}
|
|
1005
|
-
|
|
592
|
+
return field.split(".").reduce((acc, key) => {
|
|
593
|
+
if (!acc || typeof acc !== "object") return void 0;
|
|
594
|
+
return acc[key];
|
|
595
|
+
}, node);
|
|
596
|
+
};
|
|
597
|
+
const compileMongoPagination = (spec, options) => {
|
|
598
|
+
const normalized = normalizePaginationSpec(spec);
|
|
599
|
+
const mongoLimit = normalized.limit + 1;
|
|
600
|
+
const mongoSort = toMongoSort(normalized);
|
|
601
|
+
if (normalized.cursor == null) {
|
|
1006
602
|
return {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
validator: (value) => {
|
|
1012
|
-
if (value == null) return true;
|
|
1013
|
-
if (typeof value !== "object" || Array.isArray(value)) return false;
|
|
1014
|
-
const proto = Object.getPrototypeOf(value);
|
|
1015
|
-
if (proto !== Object.prototype && proto !== null) return false;
|
|
1016
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1017
|
-
if (!LANGUAGE_CODE_REGEX.test(key)) return false;
|
|
1018
|
-
if (typeof entry !== "string") return false;
|
|
1019
|
-
}
|
|
1020
|
-
return true;
|
|
1021
|
-
},
|
|
1022
|
-
message: "Expected a localized string record."
|
|
1023
|
-
},
|
|
1024
|
-
get: (value) => value == null ? value : withLocalizedStringFallback(value)
|
|
603
|
+
spec: normalized,
|
|
604
|
+
mongoFilterDelta: {},
|
|
605
|
+
mongoSort,
|
|
606
|
+
mongoLimit
|
|
1025
607
|
};
|
|
1026
608
|
}
|
|
1027
|
-
if (
|
|
1028
|
-
|
|
1029
|
-
const mapKeyType = field.keyType ?? field.keySchema;
|
|
1030
|
-
if (!mapKeyType) {
|
|
1031
|
-
throw new Error("Unsupported map key type: undefined");
|
|
1032
|
-
}
|
|
1033
|
-
return parseMap(
|
|
1034
|
-
required,
|
|
1035
|
-
mapValueType,
|
|
1036
|
-
def
|
|
1037
|
-
);
|
|
609
|
+
if (typeof normalized.cursor !== "string" || normalized.cursor.length === 0) {
|
|
610
|
+
throw new PaginationValidationError("Invalid pagination cursor");
|
|
1038
611
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
function parseNumber(field, required = true, def, unique = false, validate, sparse = false) {
|
|
1042
|
-
const output = {
|
|
1043
|
-
type: Number,
|
|
1044
|
-
default: def,
|
|
1045
|
-
min: field.minValue ?? void 0,
|
|
1046
|
-
max: field.maxValue ?? void 0,
|
|
1047
|
-
required,
|
|
1048
|
-
unique,
|
|
1049
|
-
sparse
|
|
1050
|
-
};
|
|
1051
|
-
if (validate) output.validate = validate;
|
|
1052
|
-
return output;
|
|
1053
|
-
}
|
|
1054
|
-
function parseString(field, required = true, def, unique = false, validate, sparse = false) {
|
|
1055
|
-
const output = {
|
|
1056
|
-
type: String,
|
|
1057
|
-
default: def,
|
|
1058
|
-
required,
|
|
1059
|
-
minLength: field.minLength ?? void 0,
|
|
1060
|
-
maxLength: field.maxLength ?? void 0,
|
|
1061
|
-
unique,
|
|
1062
|
-
sparse
|
|
1063
|
-
};
|
|
1064
|
-
if (validate) output.validate = validate;
|
|
1065
|
-
return output;
|
|
1066
|
-
}
|
|
1067
|
-
function parseEnum(values, required = true, def) {
|
|
612
|
+
const cursorValues = decodePaginationCursor(normalized, normalized.cursor, options.cursor);
|
|
613
|
+
const mongoFilterDelta = buildKeysetFilterDelta(normalized, cursorValues);
|
|
1068
614
|
return {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
enum: values,
|
|
1074
|
-
required
|
|
615
|
+
spec: normalized,
|
|
616
|
+
mongoFilterDelta,
|
|
617
|
+
mongoSort,
|
|
618
|
+
mongoLimit
|
|
1075
619
|
};
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
function parseDate(required = true, def, validate, unique = false, sparse = false) {
|
|
1085
|
-
const output = {
|
|
1086
|
-
type: Date,
|
|
1087
|
-
default: def,
|
|
1088
|
-
required,
|
|
1089
|
-
unique,
|
|
1090
|
-
sparse
|
|
1091
|
-
};
|
|
1092
|
-
if (validate) output.validate = validate;
|
|
1093
|
-
return output;
|
|
1094
|
-
}
|
|
1095
|
-
function parseObjectId(required = true, ref, unique = false, refPath, sparse = false) {
|
|
1096
|
-
const output = {
|
|
1097
|
-
type: SchemaTypes.ObjectId,
|
|
1098
|
-
required,
|
|
1099
|
-
unique,
|
|
1100
|
-
sparse
|
|
1101
|
-
};
|
|
1102
|
-
if (ref) output.ref = ref;
|
|
1103
|
-
if (refPath) output.refPath = refPath;
|
|
1104
|
-
return output;
|
|
1105
|
-
}
|
|
1106
|
-
function parseArray(required = true, element, def) {
|
|
1107
|
-
const innerType = parseField(element);
|
|
1108
|
-
if (!innerType) throw new Error("Unsupported array type");
|
|
1109
|
-
return {
|
|
1110
|
-
type: [innerType],
|
|
1111
|
-
default: def,
|
|
1112
|
-
required
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
function parseMap(required = true, valueType, def) {
|
|
1116
|
-
if (!valueType) {
|
|
1117
|
-
throw new Error("Unsupported map value type: undefined");
|
|
620
|
+
};
|
|
621
|
+
const toMongoSort = (spec) => {
|
|
622
|
+
const mongoSort = /* @__PURE__ */ Object.create(null);
|
|
623
|
+
for (const { field, order } of spec.sort) {
|
|
624
|
+
const forQueryOrder = spec.direction === "prev" ? invertOrder(order) : order;
|
|
625
|
+
mongoSort[field] = forQueryOrder === "asc" ? 1 : -1;
|
|
1118
626
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
627
|
+
return mongoSort;
|
|
628
|
+
};
|
|
629
|
+
const buildKeysetFilterDelta = (spec, cursorValues) => {
|
|
630
|
+
const branches = [];
|
|
631
|
+
for (let i = 0; i < spec.sort.length; i++) {
|
|
632
|
+
const current = spec.sort[i];
|
|
633
|
+
if (!current) continue;
|
|
634
|
+
const and = [];
|
|
635
|
+
for (let j = 0; j < i; j++) {
|
|
636
|
+
const prev = spec.sort[j];
|
|
637
|
+
if (!prev) continue;
|
|
638
|
+
const eq = /* @__PURE__ */ Object.create(null);
|
|
639
|
+
eq[prev.field] = cursorValues[prev.field];
|
|
640
|
+
and.push(eq);
|
|
641
|
+
}
|
|
642
|
+
const op = getKeysetOp(current.order, spec.direction);
|
|
643
|
+
const cmpOp = /* @__PURE__ */ Object.create(null);
|
|
644
|
+
cmpOp[op] = cursorValues[current.field];
|
|
645
|
+
const cmp = /* @__PURE__ */ Object.create(null);
|
|
646
|
+
cmp[current.field] = cmpOp;
|
|
647
|
+
and.push(cmp);
|
|
648
|
+
branches.push(and.length === 1 ? and[0] : { $and: and });
|
|
1123
649
|
}
|
|
1124
|
-
return {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
650
|
+
return { $or: branches };
|
|
651
|
+
};
|
|
652
|
+
const getKeysetOp = (order, direction) => {
|
|
653
|
+
if (direction === "next") return order === "asc" ? "$gt" : "$lt";
|
|
654
|
+
return order === "asc" ? "$lt" : "$gt";
|
|
655
|
+
};
|
|
656
|
+
const invertOrder = (order) => {
|
|
657
|
+
return order === "asc" ? "desc" : "asc";
|
|
658
|
+
};
|
|
659
|
+
const materializeMongoPagination = (compiled, fetchedNodes, options) => {
|
|
660
|
+
const limit = compiled.spec.limit;
|
|
661
|
+
const hasMore = fetchedNodes.length > limit;
|
|
662
|
+
const trimmed = fetchedNodes.slice(0, limit);
|
|
663
|
+
const nodes = compiled.spec.direction === "prev" ? trimmed.reverse() : trimmed;
|
|
664
|
+
const hasPrevPage = compiled.spec.direction === "next" ? Boolean(compiled.spec.cursor) : hasMore;
|
|
665
|
+
const hasNextPage = compiled.spec.direction === "next" ? hasMore : Boolean(compiled.spec.cursor);
|
|
666
|
+
const pageInfo = {
|
|
667
|
+
hasNextPage,
|
|
668
|
+
hasPrevPage
|
|
1137
669
|
};
|
|
1138
|
-
if (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
670
|
+
if (nodes.length === 0) {
|
|
671
|
+
return { nodes, pageInfo };
|
|
672
|
+
}
|
|
673
|
+
if (hasPrevPage) {
|
|
674
|
+
pageInfo.prevCursor = encodePaginationCursor(compiled.spec, nodes[0], options.cursor);
|
|
675
|
+
}
|
|
676
|
+
if (hasNextPage) {
|
|
677
|
+
pageInfo.nextCursor = encodePaginationCursor(compiled.spec, nodes[nodes.length - 1], options.cursor);
|
|
678
|
+
}
|
|
679
|
+
return { nodes, pageInfo };
|
|
680
|
+
};
|
|
681
|
+
const MongoAdapter = {
|
|
682
|
+
applyPagination: (query, compiled) => {
|
|
683
|
+
query.where(compiled.mongoFilterDelta);
|
|
684
|
+
query.sort(compiled.mongoSort);
|
|
685
|
+
query.limit(compiled.mongoLimit);
|
|
686
|
+
return query;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
const paginateMongoQuery = async (query, pagination, options) => {
|
|
690
|
+
const compiled = compileMongoPagination(pagination, { cursor: options.cursor });
|
|
691
|
+
MongoAdapter.applyPagination(query, compiled);
|
|
692
|
+
const fetchedNodes = await query.exec();
|
|
693
|
+
if (!Array.isArray(fetchedNodes)) {
|
|
694
|
+
throw new Error("paginateMongoQuery expects query.exec() to return an array");
|
|
695
|
+
}
|
|
696
|
+
return materializeMongoPagination(compiled, fetchedNodes, { cursor: options.cursor });
|
|
697
|
+
};
|
|
698
|
+
const getQueryOptions$1 = (query) => {
|
|
699
|
+
if (!query || typeof query !== "object") return void 0;
|
|
700
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
701
|
+
return query.getOptions();
|
|
702
|
+
};
|
|
703
|
+
const getPaginationFromOptions = (query) => {
|
|
704
|
+
const options = getQueryOptions$1(query);
|
|
705
|
+
return options?.pagination;
|
|
706
|
+
};
|
|
707
|
+
const getCursorFromOptions = (query) => {
|
|
708
|
+
const options = getQueryOptions$1(query);
|
|
709
|
+
return options?.paginationCursor;
|
|
710
|
+
};
|
|
711
|
+
const mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
712
|
+
schema.query.paginate = async function(pagination, options) {
|
|
713
|
+
const spec = pagination ?? getPaginationFromOptions(this);
|
|
714
|
+
if (!spec) throw new Error("Missing pagination spec");
|
|
715
|
+
const cursor = options?.cursor ?? getCursorFromOptions(this) ?? pluginOptions?.cursor;
|
|
716
|
+
if (!cursor?.signingSecret) throw new Error("Missing pagination cursor signingSecret");
|
|
717
|
+
return await paginateMongoQuery(this, spec, { cursor });
|
|
1147
718
|
};
|
|
1148
|
-
}
|
|
719
|
+
};
|
|
1149
720
|
const getAppName$1 = (env = process.env) => {
|
|
1150
721
|
const appName = env.APP_NAME?.trim();
|
|
1151
722
|
if (!appName) {
|
|
@@ -1237,11 +808,11 @@ const insertChanges = async (db, changes) => {
|
|
|
1237
808
|
if (!changes.length) return;
|
|
1238
809
|
const { RtsChange } = getRtsModels(db);
|
|
1239
810
|
const ts = /* @__PURE__ */ new Date();
|
|
1240
|
-
await RtsChange.insertMany(changes.map((
|
|
1241
|
-
seq:
|
|
1242
|
-
modelName:
|
|
1243
|
-
op:
|
|
1244
|
-
docId:
|
|
811
|
+
await RtsChange.insertMany(changes.map((c2) => ({
|
|
812
|
+
seq: c2.seq,
|
|
813
|
+
modelName: c2.modelName,
|
|
814
|
+
op: c2.op,
|
|
815
|
+
docId: c2.docId ?? void 0,
|
|
1245
816
|
ts
|
|
1246
817
|
})));
|
|
1247
818
|
};
|
|
@@ -1272,7 +843,7 @@ const captureDeleteMeta = async (query, mode) => {
|
|
|
1272
843
|
findQuery.limit(maxDeleteIds + 1);
|
|
1273
844
|
}
|
|
1274
845
|
const docs = await findQuery;
|
|
1275
|
-
const ids = Array.isArray(docs) ? docs.map((
|
|
846
|
+
const ids = Array.isArray(docs) ? docs.map((d2) => normalizeId(d2?._id)).filter((id) => Boolean(id)) : [];
|
|
1276
847
|
const reset = mode === "many" && ids.length > maxDeleteIds;
|
|
1277
848
|
const trimmedIds = reset ? [] : ids;
|
|
1278
849
|
const meta = {
|
|
@@ -1326,125 +897,6 @@ const rtsChangeLogPlugin = (schema) => {
|
|
|
1326
897
|
}
|
|
1327
898
|
});
|
|
1328
899
|
};
|
|
1329
|
-
const POLICIES_GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@rpcbase/db/acl/policiesBySubject");
|
|
1330
|
-
const getGlobalPoliciesMap = () => {
|
|
1331
|
-
const store = globalThis;
|
|
1332
|
-
const existing = store[POLICIES_GLOBAL_KEY];
|
|
1333
|
-
if (existing instanceof Map) {
|
|
1334
|
-
return existing;
|
|
1335
|
-
}
|
|
1336
|
-
const created = /* @__PURE__ */ new Map();
|
|
1337
|
-
store[POLICIES_GLOBAL_KEY] = created;
|
|
1338
|
-
return created;
|
|
1339
|
-
};
|
|
1340
|
-
const policiesBySubject = getGlobalPoliciesMap();
|
|
1341
|
-
const isPolicy = (value) => {
|
|
1342
|
-
if (!value || typeof value !== "object") return false;
|
|
1343
|
-
const maybe = value;
|
|
1344
|
-
return typeof maybe.subject === "string" && typeof maybe.define === "function";
|
|
1345
|
-
};
|
|
1346
|
-
const registerPolicy = (policy) => {
|
|
1347
|
-
const set = policiesBySubject.get(policy.subject) ?? /* @__PURE__ */ new Set();
|
|
1348
|
-
set.add(policy.define);
|
|
1349
|
-
policiesBySubject.set(policy.subject, set);
|
|
1350
|
-
};
|
|
1351
|
-
const registerPoliciesFromModules = (modules) => {
|
|
1352
|
-
for (const [exportName, value] of Object.entries(modules)) {
|
|
1353
|
-
if (!isPolicy(value)) continue;
|
|
1354
|
-
if (!exportName.endsWith("Policy")) {
|
|
1355
|
-
throw new Error(
|
|
1356
|
-
`Invalid policy export name "${exportName}". Policies must be exported as "<ModelName>Policy".`
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
const expectedSubject = exportName.slice(0, -"Policy".length);
|
|
1360
|
-
if (value.subject !== expectedSubject) {
|
|
1361
|
-
throw new Error(
|
|
1362
|
-
`Invalid policy "${exportName}": expected subject "${expectedSubject}", got "${value.subject}".`
|
|
1363
|
-
);
|
|
1364
|
-
}
|
|
1365
|
-
registerPolicy(value);
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1368
|
-
const getRegisteredPolicies = () => {
|
|
1369
|
-
const out = [];
|
|
1370
|
-
for (const set of policiesBySubject.values()) {
|
|
1371
|
-
for (const fn of set) out.push(fn);
|
|
1372
|
-
}
|
|
1373
|
-
return out;
|
|
1374
|
-
};
|
|
1375
|
-
const buildAbility = (ctx) => {
|
|
1376
|
-
const builder = new AbilityBuilder(createMongoAbility);
|
|
1377
|
-
const { can: can2 } = builder;
|
|
1378
|
-
if (ctx.roles.includes("owner") || ctx.roles.includes("admin")) {
|
|
1379
|
-
can2("manage", "all");
|
|
1380
|
-
}
|
|
1381
|
-
for (const define of getRegisteredPolicies()) {
|
|
1382
|
-
define(builder, ctx);
|
|
1383
|
-
}
|
|
1384
|
-
return builder.build();
|
|
1385
|
-
};
|
|
1386
|
-
const normalizeRole = (raw) => {
|
|
1387
|
-
if (typeof raw !== "string") return null;
|
|
1388
|
-
const normalized = raw.trim();
|
|
1389
|
-
return normalized ? normalized : null;
|
|
1390
|
-
};
|
|
1391
|
-
const normalizeRoles = (raw) => {
|
|
1392
|
-
if (Array.isArray(raw)) {
|
|
1393
|
-
return raw.map(normalizeRole).filter((r) => Boolean(r));
|
|
1394
|
-
}
|
|
1395
|
-
const role = normalizeRole(raw);
|
|
1396
|
-
return role ? [role] : [];
|
|
1397
|
-
};
|
|
1398
|
-
const normalizeRolesByTenantId = (raw) => {
|
|
1399
|
-
if (!raw || typeof raw !== "object") return null;
|
|
1400
|
-
if (raw instanceof Map) {
|
|
1401
|
-
return Object.fromEntries(Array.from(raw.entries()).map(([key, value]) => [String(key), normalizeRoles(value)]));
|
|
1402
|
-
}
|
|
1403
|
-
const obj = raw;
|
|
1404
|
-
const out = {};
|
|
1405
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1406
|
-
const tenantId = String(key).trim();
|
|
1407
|
-
if (!tenantId) continue;
|
|
1408
|
-
out[tenantId] = normalizeRoles(value);
|
|
1409
|
-
}
|
|
1410
|
-
return out;
|
|
1411
|
-
};
|
|
1412
|
-
const getTenantRolesFromSessionUser = (user, tenantId) => {
|
|
1413
|
-
if (!user || typeof user !== "object") return [];
|
|
1414
|
-
const rolesByTenantId = normalizeRolesByTenantId(user.tenantRoles);
|
|
1415
|
-
if (!rolesByTenantId) return [];
|
|
1416
|
-
return rolesByTenantId[tenantId]?.filter(Boolean) ?? [];
|
|
1417
|
-
};
|
|
1418
|
-
const buildAbilityFromSession = ({
|
|
1419
|
-
tenantId,
|
|
1420
|
-
session,
|
|
1421
|
-
claims
|
|
1422
|
-
}) => {
|
|
1423
|
-
const user = session?.user;
|
|
1424
|
-
const userId = typeof user?.id === "string" ? user.id.trim() : "";
|
|
1425
|
-
const rolesByTenantId = normalizeRolesByTenantId(user?.tenantRoles);
|
|
1426
|
-
const hasExplicitTenantEntry = Boolean(
|
|
1427
|
-
rolesByTenantId && Object.prototype.hasOwnProperty.call(rolesByTenantId, tenantId)
|
|
1428
|
-
);
|
|
1429
|
-
const signedInTenantsRaw = user?.signedInTenants;
|
|
1430
|
-
const signedInTenants = Array.isArray(signedInTenantsRaw) ? signedInTenantsRaw.map(String) : [];
|
|
1431
|
-
const roles = hasExplicitTenantEntry ? rolesByTenantId[tenantId] ?? [] : userId && signedInTenants.includes(tenantId) ? ["owner"] : getTenantRolesFromSessionUser(user, tenantId);
|
|
1432
|
-
return buildAbility({
|
|
1433
|
-
tenantId,
|
|
1434
|
-
userId: userId || null,
|
|
1435
|
-
roles,
|
|
1436
|
-
claims
|
|
1437
|
-
});
|
|
1438
|
-
};
|
|
1439
|
-
const getAccessibleByQuery = (ability, action, subject2) => {
|
|
1440
|
-
return accessibleBy(ability, action).ofType(subject2);
|
|
1441
|
-
};
|
|
1442
|
-
const can = (ability, action, subjectType, object) => {
|
|
1443
|
-
if (object && typeof object === "object") {
|
|
1444
|
-
return ability.can(action, subject(subjectType, object));
|
|
1445
|
-
}
|
|
1446
|
-
return ability.can(action, subjectType);
|
|
1447
|
-
};
|
|
1448
900
|
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1449
901
|
const mergeMongoQuery = (left, right) => {
|
|
1450
902
|
const leftQuery = isRecord(left) ? left : {};
|
|
@@ -1452,13 +904,13 @@ const mergeMongoQuery = (left, right) => {
|
|
|
1452
904
|
if (Object.keys(right).length === 0) return leftQuery;
|
|
1453
905
|
return { $and: [leftQuery, right] };
|
|
1454
906
|
};
|
|
1455
|
-
const getQueryOptions
|
|
907
|
+
const getQueryOptions = (query) => {
|
|
1456
908
|
if (!query || typeof query !== "object") return void 0;
|
|
1457
909
|
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1458
910
|
return query.getOptions();
|
|
1459
911
|
};
|
|
1460
912
|
const getStoredAclFromQuery = (query) => {
|
|
1461
|
-
const options = getQueryOptions
|
|
913
|
+
const options = getQueryOptions(query);
|
|
1462
914
|
const raw = options?.rbAcl;
|
|
1463
915
|
if (!isRecord(raw)) return null;
|
|
1464
916
|
if (!("ability" in raw)) return null;
|
|
@@ -1526,8 +978,8 @@ const patchAggregateAcl = () => {
|
|
|
1526
978
|
return this;
|
|
1527
979
|
};
|
|
1528
980
|
};
|
|
1529
|
-
const createModelAclProxy = (
|
|
1530
|
-
return new Proxy(
|
|
981
|
+
const createModelAclProxy = (model2, ability) => {
|
|
982
|
+
return new Proxy(model2, {
|
|
1531
983
|
get(target, prop, receiver) {
|
|
1532
984
|
const value = Reflect.get(target, prop, receiver);
|
|
1533
985
|
if (typeof value !== "function") return value;
|
|
@@ -1590,290 +1042,6 @@ const mongooseAclPlugin = (schema) => {
|
|
|
1590
1042
|
addQueryAclFilter(this, "update");
|
|
1591
1043
|
});
|
|
1592
1044
|
};
|
|
1593
|
-
class PaginationValidationError extends Error {
|
|
1594
|
-
code = "invalid_pagination";
|
|
1595
|
-
statusCode = 400;
|
|
1596
|
-
constructor(message, options) {
|
|
1597
|
-
super(message, options);
|
|
1598
|
-
this.name = "PaginationValidationError";
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
const isPaginationValidationError = (error) => {
|
|
1602
|
-
if (!error || typeof error !== "object") return false;
|
|
1603
|
-
const anyError = error;
|
|
1604
|
-
return anyError.name === "PaginationValidationError" && anyError.code === "invalid_pagination" && anyError.statusCode === 400;
|
|
1605
|
-
};
|
|
1606
|
-
const DISALLOWED_MONGO_FIELD_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1607
|
-
const PAGINATION_LIMIT_MAX = 128;
|
|
1608
|
-
const normalizePaginationSpec = (spec) => {
|
|
1609
|
-
if (!spec || typeof spec !== "object") throw new PaginationValidationError("Invalid PaginationSpec");
|
|
1610
|
-
const limit = normalizeLimit(spec.limit);
|
|
1611
|
-
const direction = spec.direction ?? "next";
|
|
1612
|
-
if (direction !== "next" && direction !== "prev") throw new PaginationValidationError("Invalid pagination direction");
|
|
1613
|
-
if (!Array.isArray(spec.sort) || spec.sort.length === 0) throw new PaginationValidationError("Invalid pagination sort");
|
|
1614
|
-
const sort = spec.sort.map(({ field, order }) => ({
|
|
1615
|
-
field: assertSafeMongoFieldPath(field),
|
|
1616
|
-
order: normalizeOrder(order)
|
|
1617
|
-
}));
|
|
1618
|
-
const seenFields = /* @__PURE__ */ new Set();
|
|
1619
|
-
for (const { field } of sort) {
|
|
1620
|
-
if (seenFields.has(field)) throw new PaginationValidationError(`Duplicate pagination sort field: ${field}`);
|
|
1621
|
-
seenFields.add(field);
|
|
1622
|
-
}
|
|
1623
|
-
const primaryOrder = sort[0]?.order;
|
|
1624
|
-
if (!seenFields.has("_id")) {
|
|
1625
|
-
sort.push({ field: "_id", order: primaryOrder });
|
|
1626
|
-
}
|
|
1627
|
-
return {
|
|
1628
|
-
...spec,
|
|
1629
|
-
limit,
|
|
1630
|
-
direction,
|
|
1631
|
-
sort
|
|
1632
|
-
};
|
|
1633
|
-
};
|
|
1634
|
-
const normalizeLimit = (limit) => {
|
|
1635
|
-
if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit) || limit <= 0) {
|
|
1636
|
-
throw new PaginationValidationError("Invalid pagination limit");
|
|
1637
|
-
}
|
|
1638
|
-
if (limit > PAGINATION_LIMIT_MAX) {
|
|
1639
|
-
throw new PaginationValidationError("Invalid pagination limit");
|
|
1640
|
-
}
|
|
1641
|
-
return limit;
|
|
1642
|
-
};
|
|
1643
|
-
const normalizeOrder = (order) => {
|
|
1644
|
-
if (order !== "asc" && order !== "desc") throw new PaginationValidationError("Invalid pagination order");
|
|
1645
|
-
return order;
|
|
1646
|
-
};
|
|
1647
|
-
const assertSafeMongoFieldPath = (field) => {
|
|
1648
|
-
if (typeof field !== "string" || field.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1649
|
-
if (field.startsWith("$")) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1650
|
-
const parts = field.split(".");
|
|
1651
|
-
for (const part of parts) {
|
|
1652
|
-
if (part.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1653
|
-
if (DISALLOWED_MONGO_FIELD_SEGMENTS.has(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1654
|
-
if (!/^[a-zA-Z0-9_]+$/.test(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1655
|
-
}
|
|
1656
|
-
return field;
|
|
1657
|
-
};
|
|
1658
|
-
const encodePaginationCursor = (spec, node, options) => {
|
|
1659
|
-
const normalized = normalizePaginationSpec(spec);
|
|
1660
|
-
const values = /* @__PURE__ */ Object.create(null);
|
|
1661
|
-
for (const { field } of normalized.sort) {
|
|
1662
|
-
const value = readFieldValue(node, field);
|
|
1663
|
-
if (typeof value === "undefined") {
|
|
1664
|
-
throw new Error(`Pagination cursor encode failed (missing field: ${field})`);
|
|
1665
|
-
}
|
|
1666
|
-
values[field] = encodeCursorValue(field, value);
|
|
1667
|
-
}
|
|
1668
|
-
const payload = { v: 1, values };
|
|
1669
|
-
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1670
|
-
const sigB64 = signCursorPayloadB64(payloadB64, options.signingSecret);
|
|
1671
|
-
return `${payloadB64}.${sigB64}`;
|
|
1672
|
-
};
|
|
1673
|
-
const decodePaginationCursor = (spec, cursor, options) => {
|
|
1674
|
-
const normalized = normalizePaginationSpec(spec);
|
|
1675
|
-
const [payloadB64, sigB64, ...rest] = cursor.split(".");
|
|
1676
|
-
if (rest.length > 0) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1677
|
-
if (!sigB64) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1678
|
-
verifyCursorSignature(payloadB64, sigB64, options.signingSecret);
|
|
1679
|
-
const payloadRaw = Buffer.from(payloadB64, "base64url").toString("utf8");
|
|
1680
|
-
let payloadUnknown;
|
|
1681
|
-
try {
|
|
1682
|
-
payloadUnknown = JSON.parse(payloadRaw);
|
|
1683
|
-
} catch {
|
|
1684
|
-
throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1685
|
-
}
|
|
1686
|
-
if (!payloadUnknown || typeof payloadUnknown !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1687
|
-
const payload = payloadUnknown;
|
|
1688
|
-
if (payload.v !== 1) throw new PaginationValidationError("Unsupported pagination cursor version");
|
|
1689
|
-
if (!payload.values || typeof payload.values !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1690
|
-
const decoded = /* @__PURE__ */ Object.create(null);
|
|
1691
|
-
for (const { field } of normalized.sort) {
|
|
1692
|
-
if (!Object.prototype.hasOwnProperty.call(payload.values, field)) {
|
|
1693
|
-
throw new PaginationValidationError(`Pagination cursor missing field: ${field}`);
|
|
1694
|
-
}
|
|
1695
|
-
const encodedValue = payload.values[field];
|
|
1696
|
-
decoded[field] = decodeCursorValue(field, encodedValue);
|
|
1697
|
-
}
|
|
1698
|
-
return decoded;
|
|
1699
|
-
};
|
|
1700
|
-
const signCursorPayloadB64 = (payloadB64, secret) => {
|
|
1701
|
-
return createHmac("sha256", secret).update(payloadB64, "utf8").digest("base64url");
|
|
1702
|
-
};
|
|
1703
|
-
const verifyCursorSignature = (payloadB64, sigB64, secret) => {
|
|
1704
|
-
const expectedSigB64 = signCursorPayloadB64(payloadB64, secret);
|
|
1705
|
-
const a = Buffer.from(sigB64, "utf8");
|
|
1706
|
-
const b = Buffer.from(expectedSigB64, "utf8");
|
|
1707
|
-
if (a.length !== b.length || !timingSafeEqual(a, b)) {
|
|
1708
|
-
throw new PaginationValidationError("Invalid pagination cursor signature");
|
|
1709
|
-
}
|
|
1710
|
-
};
|
|
1711
|
-
const encodeCursorValue = (field, value) => {
|
|
1712
|
-
if (value === null) return null;
|
|
1713
|
-
if (field === "_id") {
|
|
1714
|
-
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
1715
|
-
throw new Error("Pagination cursor encode failed (_id must be an ObjectId)");
|
|
1716
|
-
}
|
|
1717
|
-
if (value instanceof Date) return { $date: value.toISOString() };
|
|
1718
|
-
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
1719
|
-
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
1720
|
-
throw new Error(`Unsupported pagination cursor value type for field: ${field}`);
|
|
1721
|
-
};
|
|
1722
|
-
const decodeCursorValue = (field, value) => {
|
|
1723
|
-
if (value === null) return null;
|
|
1724
|
-
if (typeof value === "string") {
|
|
1725
|
-
if (field === "_id") throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
1726
|
-
return value;
|
|
1727
|
-
}
|
|
1728
|
-
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1729
|
-
if (!value || typeof value !== "object") throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1730
|
-
if ("$date" in value) {
|
|
1731
|
-
if (typeof value.$date !== "string") throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1732
|
-
const d = new Date(value.$date);
|
|
1733
|
-
if (Number.isNaN(d.getTime())) throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1734
|
-
return d;
|
|
1735
|
-
}
|
|
1736
|
-
if ("$oid" in value) {
|
|
1737
|
-
if (typeof value.$oid !== "string" || !Types.ObjectId.isValid(value.$oid)) {
|
|
1738
|
-
throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
1739
|
-
}
|
|
1740
|
-
return new Types.ObjectId(value.$oid);
|
|
1741
|
-
}
|
|
1742
|
-
throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1743
|
-
};
|
|
1744
|
-
const readFieldValue = (node, field) => {
|
|
1745
|
-
if (!node || typeof node !== "object") return void 0;
|
|
1746
|
-
if ("get" in node && typeof node.get === "function") {
|
|
1747
|
-
return node.get(field);
|
|
1748
|
-
}
|
|
1749
|
-
return field.split(".").reduce((acc, key) => {
|
|
1750
|
-
if (!acc || typeof acc !== "object") return void 0;
|
|
1751
|
-
return acc[key];
|
|
1752
|
-
}, node);
|
|
1753
|
-
};
|
|
1754
|
-
const compileMongoPagination = (spec, options) => {
|
|
1755
|
-
const normalized = normalizePaginationSpec(spec);
|
|
1756
|
-
const mongoLimit = normalized.limit + 1;
|
|
1757
|
-
const mongoSort = toMongoSort(normalized);
|
|
1758
|
-
if (normalized.cursor == null) {
|
|
1759
|
-
return {
|
|
1760
|
-
spec: normalized,
|
|
1761
|
-
mongoFilterDelta: {},
|
|
1762
|
-
mongoSort,
|
|
1763
|
-
mongoLimit
|
|
1764
|
-
};
|
|
1765
|
-
}
|
|
1766
|
-
if (typeof normalized.cursor !== "string" || normalized.cursor.length === 0) {
|
|
1767
|
-
throw new PaginationValidationError("Invalid pagination cursor");
|
|
1768
|
-
}
|
|
1769
|
-
const cursorValues = decodePaginationCursor(normalized, normalized.cursor, options.cursor);
|
|
1770
|
-
const mongoFilterDelta = buildKeysetFilterDelta(normalized, cursorValues);
|
|
1771
|
-
return {
|
|
1772
|
-
spec: normalized,
|
|
1773
|
-
mongoFilterDelta,
|
|
1774
|
-
mongoSort,
|
|
1775
|
-
mongoLimit
|
|
1776
|
-
};
|
|
1777
|
-
};
|
|
1778
|
-
const toMongoSort = (spec) => {
|
|
1779
|
-
const mongoSort = /* @__PURE__ */ Object.create(null);
|
|
1780
|
-
for (const { field, order } of spec.sort) {
|
|
1781
|
-
const forQueryOrder = spec.direction === "prev" ? invertOrder(order) : order;
|
|
1782
|
-
mongoSort[field] = forQueryOrder === "asc" ? 1 : -1;
|
|
1783
|
-
}
|
|
1784
|
-
return mongoSort;
|
|
1785
|
-
};
|
|
1786
|
-
const buildKeysetFilterDelta = (spec, cursorValues) => {
|
|
1787
|
-
const branches = [];
|
|
1788
|
-
for (let i = 0; i < spec.sort.length; i++) {
|
|
1789
|
-
const current = spec.sort[i];
|
|
1790
|
-
if (!current) continue;
|
|
1791
|
-
const and = [];
|
|
1792
|
-
for (let j = 0; j < i; j++) {
|
|
1793
|
-
const prev = spec.sort[j];
|
|
1794
|
-
if (!prev) continue;
|
|
1795
|
-
const eq = /* @__PURE__ */ Object.create(null);
|
|
1796
|
-
eq[prev.field] = cursorValues[prev.field];
|
|
1797
|
-
and.push(eq);
|
|
1798
|
-
}
|
|
1799
|
-
const op = getKeysetOp(current.order, spec.direction);
|
|
1800
|
-
const cmpOp = /* @__PURE__ */ Object.create(null);
|
|
1801
|
-
cmpOp[op] = cursorValues[current.field];
|
|
1802
|
-
const cmp = /* @__PURE__ */ Object.create(null);
|
|
1803
|
-
cmp[current.field] = cmpOp;
|
|
1804
|
-
and.push(cmp);
|
|
1805
|
-
branches.push(and.length === 1 ? and[0] : { $and: and });
|
|
1806
|
-
}
|
|
1807
|
-
return { $or: branches };
|
|
1808
|
-
};
|
|
1809
|
-
const getKeysetOp = (order, direction) => {
|
|
1810
|
-
if (direction === "next") return order === "asc" ? "$gt" : "$lt";
|
|
1811
|
-
return order === "asc" ? "$lt" : "$gt";
|
|
1812
|
-
};
|
|
1813
|
-
const invertOrder = (order) => {
|
|
1814
|
-
return order === "asc" ? "desc" : "asc";
|
|
1815
|
-
};
|
|
1816
|
-
const materializeMongoPagination = (compiled, fetchedNodes, options) => {
|
|
1817
|
-
const limit = compiled.spec.limit;
|
|
1818
|
-
const hasMore = fetchedNodes.length > limit;
|
|
1819
|
-
const trimmed = fetchedNodes.slice(0, limit);
|
|
1820
|
-
const nodes = compiled.spec.direction === "prev" ? trimmed.reverse() : trimmed;
|
|
1821
|
-
const hasPrevPage = compiled.spec.direction === "next" ? Boolean(compiled.spec.cursor) : hasMore;
|
|
1822
|
-
const hasNextPage = compiled.spec.direction === "next" ? hasMore : Boolean(compiled.spec.cursor);
|
|
1823
|
-
const pageInfo = {
|
|
1824
|
-
hasNextPage,
|
|
1825
|
-
hasPrevPage
|
|
1826
|
-
};
|
|
1827
|
-
if (nodes.length === 0) {
|
|
1828
|
-
return { nodes, pageInfo };
|
|
1829
|
-
}
|
|
1830
|
-
if (hasPrevPage) {
|
|
1831
|
-
pageInfo.prevCursor = encodePaginationCursor(compiled.spec, nodes[0], options.cursor);
|
|
1832
|
-
}
|
|
1833
|
-
if (hasNextPage) {
|
|
1834
|
-
pageInfo.nextCursor = encodePaginationCursor(compiled.spec, nodes[nodes.length - 1], options.cursor);
|
|
1835
|
-
}
|
|
1836
|
-
return { nodes, pageInfo };
|
|
1837
|
-
};
|
|
1838
|
-
const MongoAdapter = {
|
|
1839
|
-
applyPagination: (query, compiled) => {
|
|
1840
|
-
query.where(compiled.mongoFilterDelta);
|
|
1841
|
-
query.sort(compiled.mongoSort);
|
|
1842
|
-
query.limit(compiled.mongoLimit);
|
|
1843
|
-
return query;
|
|
1844
|
-
}
|
|
1845
|
-
};
|
|
1846
|
-
const paginateMongoQuery = async (query, pagination, options) => {
|
|
1847
|
-
const compiled = compileMongoPagination(pagination, { cursor: options.cursor });
|
|
1848
|
-
MongoAdapter.applyPagination(query, compiled);
|
|
1849
|
-
const fetchedNodes = await query.exec();
|
|
1850
|
-
if (!Array.isArray(fetchedNodes)) {
|
|
1851
|
-
throw new Error("paginateMongoQuery expects query.exec() to return an array");
|
|
1852
|
-
}
|
|
1853
|
-
return materializeMongoPagination(compiled, fetchedNodes, { cursor: options.cursor });
|
|
1854
|
-
};
|
|
1855
|
-
const getQueryOptions = (query) => {
|
|
1856
|
-
if (!query || typeof query !== "object") return void 0;
|
|
1857
|
-
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1858
|
-
return query.getOptions();
|
|
1859
|
-
};
|
|
1860
|
-
const getPaginationFromOptions = (query) => {
|
|
1861
|
-
const options = getQueryOptions(query);
|
|
1862
|
-
return options?.pagination;
|
|
1863
|
-
};
|
|
1864
|
-
const getCursorFromOptions = (query) => {
|
|
1865
|
-
const options = getQueryOptions(query);
|
|
1866
|
-
return options?.paginationCursor;
|
|
1867
|
-
};
|
|
1868
|
-
const mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
1869
|
-
schema.query.paginate = async function(pagination, options) {
|
|
1870
|
-
const spec = pagination ?? getPaginationFromOptions(this);
|
|
1871
|
-
if (!spec) throw new Error("Missing pagination spec");
|
|
1872
|
-
const cursor = options?.cursor ?? getCursorFromOptions(this) ?? pluginOptions?.cursor;
|
|
1873
|
-
if (!cursor?.signingSecret) throw new Error("Missing pagination cursor signingSecret");
|
|
1874
|
-
return await paginateMongoQuery(this, spec, { cursor });
|
|
1875
|
-
};
|
|
1876
|
-
};
|
|
1877
1045
|
let cachedModels = null;
|
|
1878
1046
|
const DEFAULT_GLOBAL_RB_MODEL_NAMES_SET = /* @__PURE__ */ new Set([
|
|
1879
1047
|
"RBUser",
|
|
@@ -2010,6 +1178,16 @@ const createModels = (modules) => {
|
|
|
2010
1178
|
getGlobal
|
|
2011
1179
|
};
|
|
2012
1180
|
};
|
|
1181
|
+
function extendMongooseSchema(baseSchema, ...extensions) {
|
|
1182
|
+
const schema = baseSchema.clone();
|
|
1183
|
+
extensions.forEach((extension) => schema.add(extension));
|
|
1184
|
+
return schema;
|
|
1185
|
+
}
|
|
1186
|
+
function omitSchemaPaths(schema, paths) {
|
|
1187
|
+
const clone = schema.clone();
|
|
1188
|
+
paths.forEach((path) => clone.remove(path));
|
|
1189
|
+
return clone;
|
|
1190
|
+
}
|
|
2013
1191
|
const getAppName = () => {
|
|
2014
1192
|
const appName = process.env.APP_NAME?.trim();
|
|
2015
1193
|
assert(appName, "Missing APP_NAME");
|
|
@@ -2028,7 +1206,6 @@ const getTenantFilesystemDbFromCtx = async (ctx) => {
|
|
|
2028
1206
|
return getTenantFilesystemDb(tenantId);
|
|
2029
1207
|
};
|
|
2030
1208
|
export {
|
|
2031
|
-
LANGUAGE_CODE_REGEX,
|
|
2032
1209
|
PaginationValidationError,
|
|
2033
1210
|
RBNotificationPolicy,
|
|
2034
1211
|
RBNotificationSchema,
|
|
@@ -2043,6 +1220,7 @@ export {
|
|
|
2043
1220
|
RBUploadSessionPolicy,
|
|
2044
1221
|
RBUploadSessionSchema,
|
|
2045
1222
|
RBUserSchema,
|
|
1223
|
+
Schema,
|
|
2046
1224
|
ZRBNotification,
|
|
2047
1225
|
ZRBNotificationDigestFrequency,
|
|
2048
1226
|
ZRBNotificationSettings,
|
|
@@ -2063,30 +1241,24 @@ export {
|
|
|
2063
1241
|
ZRBUploadSession,
|
|
2064
1242
|
ZRBUploadSessionStatus,
|
|
2065
1243
|
ZRBUser,
|
|
2066
|
-
buildAbility,
|
|
2067
|
-
buildAbilityFromSession,
|
|
2068
|
-
|
|
2069
|
-
can,
|
|
1244
|
+
b as buildAbility,
|
|
1245
|
+
d as buildAbilityFromSession,
|
|
1246
|
+
f as can,
|
|
2070
1247
|
createModels,
|
|
2071
|
-
|
|
2072
|
-
getAccessibleByQuery,
|
|
2073
|
-
getRegisteredPolicies,
|
|
1248
|
+
extendMongooseSchema,
|
|
1249
|
+
e as getAccessibleByQuery,
|
|
1250
|
+
g as getRegisteredPolicies,
|
|
2074
1251
|
getTenantFilesystemDb,
|
|
2075
1252
|
getTenantFilesystemDbFromCtx,
|
|
2076
1253
|
getTenantFilesystemDbName,
|
|
2077
|
-
getTenantRolesFromSessionUser,
|
|
1254
|
+
c as getTenantRolesFromSessionUser,
|
|
2078
1255
|
isPaginationValidationError,
|
|
1256
|
+
model,
|
|
2079
1257
|
models,
|
|
2080
1258
|
mongoPaginationPlugin,
|
|
1259
|
+
default2 as mongoose,
|
|
1260
|
+
omitSchemaPaths,
|
|
2081
1261
|
registerPoliciesFromModules,
|
|
2082
|
-
registerPolicy,
|
|
2083
|
-
|
|
2084
|
-
withLocalizedStringFallback,
|
|
2085
|
-
z2 as z,
|
|
2086
|
-
zI18nString,
|
|
2087
|
-
zId,
|
|
2088
|
-
zLocalizedString,
|
|
2089
|
-
zUUID,
|
|
2090
|
-
zodSchema,
|
|
2091
|
-
zodSchemaRaw
|
|
1262
|
+
r as registerPolicy,
|
|
1263
|
+
z2 as z
|
|
2092
1264
|
};
|