@rpcbase/db 0.35.0 → 0.37.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 +30 -11
- package/dist/acl/index.js +11 -0
- package/dist/can-urGFf45M.js +131 -0
- package/dist/index.browser.d.ts +2 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +114 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +319 -1130
- 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/extendMongooseSchema.d.ts +6 -0
- package/dist/mongoose/extendMongooseSchema.d.ts.map +1 -0
- package/dist/mongoose/index.d.ts +6 -0
- package/dist/mongoose/index.d.ts.map +1 -0
- package/dist/mongoose/localizedStringField.d.ts +4 -0
- package/dist/mongoose/localizedStringField.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 +5 -0
- package/dist/zod/index.d.ts.map +1 -0
- package/dist/zod/localizedString.d.ts +14 -0
- package/dist/zod/localizedString.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,15 @@
|
|
|
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 { withLocalizedStringFallback } from "./index.browser.js";
|
|
9
|
+
import { LANGUAGE_CODE_REGEX, buildLocaleFallbackChain, extendZod, resolveLocalizedString, zI18nString, zLocalizedString } from "./index.browser.js";
|
|
5
10
|
import assert from "assert";
|
|
6
11
|
import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
|
|
7
|
-
import
|
|
8
|
-
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
12
|
+
import "@casl/ability";
|
|
9
13
|
const ZRBUser = z.object({
|
|
10
14
|
email: z.string().email().optional(),
|
|
11
15
|
password: z.string(),
|
|
@@ -16,7 +20,7 @@ const ZRBUser = z.object({
|
|
|
16
20
|
emailVerificationCode: z.string().length(6).optional(),
|
|
17
21
|
emailVerificationExpiresAt: z.date().optional()
|
|
18
22
|
});
|
|
19
|
-
const RBUserSchema = new Schema({
|
|
23
|
+
const RBUserSchema = new Schema$1({
|
|
20
24
|
email: { type: String, unique: true, sparse: true },
|
|
21
25
|
phone: { type: String, unique: true, sparse: true },
|
|
22
26
|
password: { type: String, required: true },
|
|
@@ -31,7 +35,7 @@ const ZRBTenant = z.object({
|
|
|
31
35
|
parentTenantId: z.string().optional(),
|
|
32
36
|
name: z.string().optional()
|
|
33
37
|
});
|
|
34
|
-
const RBTenantSchema = new Schema({
|
|
38
|
+
const RBTenantSchema = new Schema$1({
|
|
35
39
|
tenantId: { type: String, required: true, unique: true, index: true },
|
|
36
40
|
parentTenantId: { type: String },
|
|
37
41
|
name: { type: String }
|
|
@@ -72,7 +76,7 @@ const ZRBTenantSubscription = z.object({
|
|
|
72
76
|
latestEventAt: z.date().optional(),
|
|
73
77
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
74
78
|
});
|
|
75
|
-
const RBTenantSubscriptionSchema = new Schema(
|
|
79
|
+
const RBTenantSubscriptionSchema = new Schema$1(
|
|
76
80
|
{
|
|
77
81
|
tenantId: { type: String, required: true, index: true },
|
|
78
82
|
subscriptionId: { type: String, required: true },
|
|
@@ -96,7 +100,7 @@ const RBTenantSubscriptionSchema = new Schema(
|
|
|
96
100
|
providerSubscriptionId: { type: String },
|
|
97
101
|
latestEventId: { type: String },
|
|
98
102
|
latestEventAt: { type: Date },
|
|
99
|
-
metadata: { type: Schema.Types.Mixed }
|
|
103
|
+
metadata: { type: Schema$1.Types.Mixed }
|
|
100
104
|
},
|
|
101
105
|
{
|
|
102
106
|
collection: "tenantSubscriptions"
|
|
@@ -140,7 +144,7 @@ const ZRBTenantSubscriptionEvent = z.object({
|
|
|
140
144
|
providerPayload: z.unknown().optional(),
|
|
141
145
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
142
146
|
});
|
|
143
|
-
const RBTenantSubscriptionEventSchema = new Schema(
|
|
147
|
+
const RBTenantSubscriptionEventSchema = new Schema$1(
|
|
144
148
|
{
|
|
145
149
|
tenantId: { type: String, required: true, index: true },
|
|
146
150
|
subscriptionId: { type: String, required: true, index: true },
|
|
@@ -163,8 +167,8 @@ const RBTenantSubscriptionEventSchema = new Schema(
|
|
|
163
167
|
reason: { type: String },
|
|
164
168
|
provider: { type: String },
|
|
165
169
|
providerEventId: { type: String },
|
|
166
|
-
providerPayload: { type: Schema.Types.Mixed },
|
|
167
|
-
metadata: { type: Schema.Types.Mixed }
|
|
170
|
+
providerPayload: { type: Schema$1.Types.Mixed },
|
|
171
|
+
metadata: { type: Schema$1.Types.Mixed }
|
|
168
172
|
},
|
|
169
173
|
{
|
|
170
174
|
collection: "tenantSubscriptionEvents"
|
|
@@ -176,7 +180,7 @@ const ZRBRtsCounter = z.object({
|
|
|
176
180
|
_id: z.string(),
|
|
177
181
|
seq: z.number().int().min(0)
|
|
178
182
|
});
|
|
179
|
-
const RBRtsCounterSchema = new Schema(
|
|
183
|
+
const RBRtsCounterSchema = new Schema$1(
|
|
180
184
|
{
|
|
181
185
|
_id: { type: String, required: true },
|
|
182
186
|
seq: { type: Number, required: true, default: 0 }
|
|
@@ -196,7 +200,7 @@ const ZRBRtsChange = z.object({
|
|
|
196
200
|
docId: z.string().optional(),
|
|
197
201
|
ts: z.date()
|
|
198
202
|
});
|
|
199
|
-
const RBRtsChangeSchema = new Schema(
|
|
203
|
+
const RBRtsChangeSchema = new Schema$1(
|
|
200
204
|
{
|
|
201
205
|
seq: { type: Number, required: true },
|
|
202
206
|
modelName: { type: String, required: true, index: true },
|
|
@@ -228,7 +232,7 @@ const ZRBUploadSession = z.object({
|
|
|
228
232
|
isPublic: z.boolean().optional(),
|
|
229
233
|
error: z.string().optional()
|
|
230
234
|
});
|
|
231
|
-
const RBUploadSessionSchema = new Schema(
|
|
235
|
+
const RBUploadSessionSchema = new Schema$1(
|
|
232
236
|
{
|
|
233
237
|
_id: { type: String, required: true },
|
|
234
238
|
userId: { type: String, required: false, index: true },
|
|
@@ -277,7 +281,7 @@ const ZRBUploadChunk = z.object({
|
|
|
277
281
|
createdAt: z.date(),
|
|
278
282
|
expiresAt: z.date()
|
|
279
283
|
});
|
|
280
|
-
const RBUploadChunkSchema = new Schema(
|
|
284
|
+
const RBUploadChunkSchema = new Schema$1(
|
|
281
285
|
{
|
|
282
286
|
uploadId: { type: String, required: true, index: true },
|
|
283
287
|
index: { type: Number, required: true },
|
|
@@ -307,7 +311,7 @@ const ZRBNotification = z.object({
|
|
|
307
311
|
metadata: z.record(z.string(), z.unknown()).optional()
|
|
308
312
|
});
|
|
309
313
|
const TTL_90_DAYS_S = 60 * 60 * 24 * 90;
|
|
310
|
-
const RBNotificationSchema = new Schema(
|
|
314
|
+
const RBNotificationSchema = new Schema$1(
|
|
311
315
|
{
|
|
312
316
|
userId: { type: String, required: true, index: true },
|
|
313
317
|
topic: { type: String, required: false, index: true },
|
|
@@ -318,7 +322,7 @@ const RBNotificationSchema = new Schema(
|
|
|
318
322
|
seenAt: { type: Date, required: false, index: true },
|
|
319
323
|
readAt: { type: Date, required: false, index: true },
|
|
320
324
|
archivedAt: { type: Date, required: false },
|
|
321
|
-
metadata: { type: Schema.Types.Mixed, required: false }
|
|
325
|
+
metadata: { type: Schema$1.Types.Mixed, required: false }
|
|
322
326
|
},
|
|
323
327
|
{
|
|
324
328
|
versionKey: false,
|
|
@@ -352,7 +356,7 @@ const ZRBNotificationSettings = z.object({
|
|
|
352
356
|
topicPreferences: z.array(ZRBNotificationTopicPreference).optional(),
|
|
353
357
|
lastDigestSentAt: z.date().optional()
|
|
354
358
|
});
|
|
355
|
-
const TopicPreferenceSchema = new Schema(
|
|
359
|
+
const TopicPreferenceSchema = new Schema$1(
|
|
356
360
|
{
|
|
357
361
|
topic: { type: String, required: true },
|
|
358
362
|
inApp: { type: Boolean, required: true, default: true },
|
|
@@ -361,7 +365,7 @@ const TopicPreferenceSchema = new Schema(
|
|
|
361
365
|
},
|
|
362
366
|
{ _id: false }
|
|
363
367
|
);
|
|
364
|
-
const RBNotificationSettingsSchema = new Schema(
|
|
368
|
+
const RBNotificationSettingsSchema = new Schema$1(
|
|
365
369
|
{
|
|
366
370
|
userId: { type: String, required: true },
|
|
367
371
|
digestFrequency: {
|
|
@@ -429,723 +433,309 @@ const frameworkSchemas = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
|
|
|
429
433
|
ZRBUploadSessionStatus,
|
|
430
434
|
ZRBUser
|
|
431
435
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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";
|
|
436
|
+
function extendMongooseSchema(baseSchema, ...extensions) {
|
|
437
|
+
const schema = baseSchema.clone();
|
|
438
|
+
extensions.forEach((extension) => schema.add(extension));
|
|
439
|
+
return schema;
|
|
440
|
+
}
|
|
441
|
+
function omitMongooseSchemaPaths(schema, paths) {
|
|
442
|
+
const clone = schema.clone();
|
|
443
|
+
paths.forEach((path) => clone.remove(path));
|
|
444
|
+
return clone;
|
|
445
|
+
}
|
|
446
|
+
function localizedStringField(options) {
|
|
447
|
+
const userGet = options?.get;
|
|
448
|
+
return {
|
|
449
|
+
...options,
|
|
450
|
+
type: Schema$1.Types.Mixed,
|
|
451
|
+
get: (value) => withLocalizedStringFallback(userGet ? userGet(value) : value)
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const { Schema, model } = mongoose;
|
|
455
|
+
class PaginationValidationError extends Error {
|
|
456
|
+
code = "invalid_pagination";
|
|
457
|
+
statusCode = 400;
|
|
458
|
+
constructor(message, options) {
|
|
459
|
+
super(message, options);
|
|
460
|
+
this.name = "PaginationValidationError";
|
|
479
461
|
}
|
|
462
|
+
}
|
|
463
|
+
const isPaginationValidationError = (error) => {
|
|
464
|
+
if (!error || typeof error !== "object") return false;
|
|
465
|
+
const anyError = error;
|
|
466
|
+
return anyError.name === "PaginationValidationError" && anyError.code === "invalid_pagination" && anyError.statusCode === 400;
|
|
480
467
|
};
|
|
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;
|
|
468
|
+
const DISALLOWED_MONGO_FIELD_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
469
|
+
const PAGINATION_LIMIT_MAX = 128;
|
|
470
|
+
const normalizePaginationSpec = (spec) => {
|
|
471
|
+
if (!spec || typeof spec !== "object") throw new PaginationValidationError("Invalid PaginationSpec");
|
|
472
|
+
const limit = normalizeLimit(spec.limit);
|
|
473
|
+
const direction = spec.direction ?? "next";
|
|
474
|
+
if (direction !== "next" && direction !== "prev") throw new PaginationValidationError("Invalid pagination direction");
|
|
475
|
+
if (!Array.isArray(spec.sort) || spec.sort.length === 0) throw new PaginationValidationError("Invalid pagination sort");
|
|
476
|
+
const sort = spec.sort.map(({ field, order }) => ({
|
|
477
|
+
field: assertSafeMongoFieldPath(field),
|
|
478
|
+
order: normalizeOrder(order)
|
|
479
|
+
}));
|
|
480
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
481
|
+
for (const { field } of sort) {
|
|
482
|
+
if (seenFields.has(field)) throw new PaginationValidationError(`Duplicate pagination sort field: ${field}`);
|
|
483
|
+
seenFields.add(field);
|
|
520
484
|
}
|
|
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");
|
|
485
|
+
const primaryOrder = sort[0]?.order;
|
|
486
|
+
if (!seenFields.has("_id")) {
|
|
487
|
+
sort.push({ field: "_id", order: primaryOrder });
|
|
561
488
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
489
|
+
return {
|
|
490
|
+
...spec,
|
|
491
|
+
limit,
|
|
492
|
+
direction,
|
|
493
|
+
sort
|
|
567
494
|
};
|
|
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
|
-
}
|
|
495
|
+
};
|
|
496
|
+
const normalizeLimit = (limit) => {
|
|
497
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit) || limit <= 0) {
|
|
498
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
589
499
|
}
|
|
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;
|
|
500
|
+
if (limit > PAGINATION_LIMIT_MAX) {
|
|
501
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
600
502
|
}
|
|
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
|
-
};
|
|
503
|
+
return limit;
|
|
504
|
+
};
|
|
505
|
+
const normalizeOrder = (order) => {
|
|
506
|
+
if (order !== "asc" && order !== "desc") throw new PaginationValidationError("Invalid pagination order");
|
|
507
|
+
return order;
|
|
508
|
+
};
|
|
509
|
+
const assertSafeMongoFieldPath = (field) => {
|
|
510
|
+
if (typeof field !== "string" || field.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
511
|
+
if (field.startsWith("$")) throw new PaginationValidationError("Invalid pagination sort field");
|
|
512
|
+
const parts = field.split(".");
|
|
513
|
+
for (const part of parts) {
|
|
514
|
+
if (part.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
515
|
+
if (DISALLOWED_MONGO_FIELD_SEGMENTS.has(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
516
|
+
if (!/^[a-zA-Z0-9_]+$/.test(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
628
517
|
}
|
|
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
|
-
});
|
|
518
|
+
return field;
|
|
519
|
+
};
|
|
520
|
+
const encodePaginationCursor = (spec, node, options) => {
|
|
521
|
+
const normalized = normalizePaginationSpec(spec);
|
|
522
|
+
const values = /* @__PURE__ */ Object.create(null);
|
|
523
|
+
for (const { field } of normalized.sort) {
|
|
524
|
+
const value = readFieldValue(node, field);
|
|
525
|
+
if (typeof value === "undefined") {
|
|
526
|
+
throw new Error(`Pagination cursor encode failed (missing field: ${field})`);
|
|
653
527
|
}
|
|
528
|
+
values[field] = encodeCursorValue(field, value);
|
|
654
529
|
}
|
|
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;
|
|
530
|
+
const payload = { v: 1, values };
|
|
531
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
532
|
+
const sigB64 = signCursorPayloadB64(payloadB64, options.signingSecret);
|
|
533
|
+
return `${payloadB64}.${sigB64}`;
|
|
534
|
+
};
|
|
535
|
+
const decodePaginationCursor = (spec, cursor, options) => {
|
|
536
|
+
const normalized = normalizePaginationSpec(spec);
|
|
537
|
+
const [payloadB64, sigB64, ...rest] = cursor.split(".");
|
|
538
|
+
if (rest.length > 0) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
539
|
+
if (!sigB64) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
540
|
+
verifyCursorSignature(payloadB64, sigB64, options.signingSecret);
|
|
541
|
+
const payloadRaw = Buffer.from(payloadB64, "base64url").toString("utf8");
|
|
542
|
+
let payloadUnknown;
|
|
755
543
|
try {
|
|
756
|
-
|
|
544
|
+
payloadUnknown = JSON.parse(payloadRaw);
|
|
757
545
|
} catch {
|
|
758
|
-
|
|
546
|
+
throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
759
547
|
}
|
|
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();
|
|
548
|
+
if (!payloadUnknown || typeof payloadUnknown !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
549
|
+
const payload = payloadUnknown;
|
|
550
|
+
if (payload.v !== 1) throw new PaginationValidationError("Unsupported pagination cursor version");
|
|
551
|
+
if (!payload.values || typeof payload.values !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
552
|
+
const decoded = /* @__PURE__ */ Object.create(null);
|
|
553
|
+
for (const { field } of normalized.sort) {
|
|
554
|
+
if (!Object.prototype.hasOwnProperty.call(payload.values, field)) {
|
|
555
|
+
throw new PaginationValidationError(`Pagination cursor missing field: ${field}`);
|
|
779
556
|
}
|
|
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];
|
|
557
|
+
const encodedValue = payload.values[field];
|
|
558
|
+
decoded[field] = decodeCursorValue(field, encodedValue);
|
|
794
559
|
}
|
|
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;
|
|
560
|
+
return decoded;
|
|
832
561
|
};
|
|
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
|
-
);
|
|
562
|
+
const signCursorPayloadB64 = (payloadB64, secret) => {
|
|
563
|
+
return createHmac("sha256", secret).update(payloadB64, "utf8").digest("base64url");
|
|
564
|
+
};
|
|
565
|
+
const verifyCursorSignature = (payloadB64, sigB64, secret) => {
|
|
566
|
+
const expectedSigB64 = signCursorPayloadB64(payloadB64, secret);
|
|
567
|
+
const a = Buffer.from(sigB64, "utf8");
|
|
568
|
+
const b2 = Buffer.from(expectedSigB64, "utf8");
|
|
569
|
+
if (a.length !== b2.length || !timingSafeEqual(a, b2)) {
|
|
570
|
+
throw new PaginationValidationError("Invalid pagination cursor signature");
|
|
978
571
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
currentRefinement
|
|
986
|
-
);
|
|
572
|
+
};
|
|
573
|
+
const encodeCursorValue = (field, value) => {
|
|
574
|
+
if (value === null) return null;
|
|
575
|
+
if (field === "_id") {
|
|
576
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
577
|
+
throw new Error("Pagination cursor encode failed (_id must be an ObjectId)");
|
|
987
578
|
}
|
|
988
|
-
if (
|
|
989
|
-
|
|
579
|
+
if (value instanceof Date) return { $date: value.toISOString() };
|
|
580
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
581
|
+
if (value instanceof Types.ObjectId) return { $oid: value.toHexString() };
|
|
582
|
+
throw new Error(`Unsupported pagination cursor value type for field: ${field}`);
|
|
583
|
+
};
|
|
584
|
+
const decodeCursorValue = (field, value) => {
|
|
585
|
+
if (value === null) return null;
|
|
586
|
+
if (typeof value === "string") {
|
|
587
|
+
if (field === "_id") throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
588
|
+
return value;
|
|
990
589
|
}
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
590
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
591
|
+
if (!value || typeof value !== "object") throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
592
|
+
if ("$date" in value) {
|
|
593
|
+
if (typeof value.$date !== "string") throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
594
|
+
const d2 = new Date(value.$date);
|
|
595
|
+
if (Number.isNaN(d2.getTime())) throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
596
|
+
return d2;
|
|
998
597
|
}
|
|
999
|
-
if (
|
|
1000
|
-
|
|
598
|
+
if ("$oid" in value) {
|
|
599
|
+
if (typeof value.$oid !== "string" || !Types.ObjectId.isValid(value.$oid)) {
|
|
600
|
+
throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
601
|
+
}
|
|
602
|
+
return new Types.ObjectId(value.$oid);
|
|
1001
603
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
604
|
+
throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
605
|
+
};
|
|
606
|
+
const readFieldValue = (node, field) => {
|
|
607
|
+
if (!node || typeof node !== "object") return void 0;
|
|
608
|
+
if ("get" in node && typeof node.get === "function") {
|
|
609
|
+
return node.get(field);
|
|
1004
610
|
}
|
|
1005
|
-
|
|
611
|
+
return field.split(".").reduce((acc, key) => {
|
|
612
|
+
if (!acc || typeof acc !== "object") return void 0;
|
|
613
|
+
return acc[key];
|
|
614
|
+
}, node);
|
|
615
|
+
};
|
|
616
|
+
const compileMongoPagination = (spec, options) => {
|
|
617
|
+
const normalized = normalizePaginationSpec(spec);
|
|
618
|
+
const mongoLimit = normalized.limit + 1;
|
|
619
|
+
const mongoSort = toMongoSort(normalized);
|
|
620
|
+
if (normalized.cursor == null) {
|
|
1006
621
|
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)
|
|
622
|
+
spec: normalized,
|
|
623
|
+
mongoFilterDelta: {},
|
|
624
|
+
mongoSort,
|
|
625
|
+
mongoLimit
|
|
1025
626
|
};
|
|
1026
627
|
}
|
|
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
|
-
);
|
|
628
|
+
if (typeof normalized.cursor !== "string" || normalized.cursor.length === 0) {
|
|
629
|
+
throw new PaginationValidationError("Invalid pagination cursor");
|
|
1038
630
|
}
|
|
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) {
|
|
1068
|
-
return {
|
|
1069
|
-
type: String,
|
|
1070
|
-
unique: false,
|
|
1071
|
-
sparse: false,
|
|
1072
|
-
default: def,
|
|
1073
|
-
enum: values,
|
|
1074
|
-
required
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
function parseBoolean(required = true, def) {
|
|
1078
|
-
return {
|
|
1079
|
-
type: Boolean,
|
|
1080
|
-
default: def,
|
|
1081
|
-
required
|
|
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");
|
|
631
|
+
const cursorValues = decodePaginationCursor(normalized, normalized.cursor, options.cursor);
|
|
632
|
+
const mongoFilterDelta = buildKeysetFilterDelta(normalized, cursorValues);
|
|
1109
633
|
return {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
634
|
+
spec: normalized,
|
|
635
|
+
mongoFilterDelta,
|
|
636
|
+
mongoSort,
|
|
637
|
+
mongoLimit
|
|
1113
638
|
};
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
639
|
+
};
|
|
640
|
+
const toMongoSort = (spec) => {
|
|
641
|
+
const mongoSort = /* @__PURE__ */ Object.create(null);
|
|
642
|
+
for (const { field, order } of spec.sort) {
|
|
643
|
+
const forQueryOrder = spec.direction === "prev" ? invertOrder(order) : order;
|
|
644
|
+
mongoSort[field] = forQueryOrder === "asc" ? 1 : -1;
|
|
1118
645
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
646
|
+
return mongoSort;
|
|
647
|
+
};
|
|
648
|
+
const buildKeysetFilterDelta = (spec, cursorValues) => {
|
|
649
|
+
const branches = [];
|
|
650
|
+
for (let i = 0; i < spec.sort.length; i++) {
|
|
651
|
+
const current = spec.sort[i];
|
|
652
|
+
if (!current) continue;
|
|
653
|
+
const and = [];
|
|
654
|
+
for (let j = 0; j < i; j++) {
|
|
655
|
+
const prev = spec.sort[j];
|
|
656
|
+
if (!prev) continue;
|
|
657
|
+
const eq = /* @__PURE__ */ Object.create(null);
|
|
658
|
+
eq[prev.field] = cursorValues[prev.field];
|
|
659
|
+
and.push(eq);
|
|
660
|
+
}
|
|
661
|
+
const op = getKeysetOp(current.order, spec.direction);
|
|
662
|
+
const cmpOp = /* @__PURE__ */ Object.create(null);
|
|
663
|
+
cmpOp[op] = cursorValues[current.field];
|
|
664
|
+
const cmp = /* @__PURE__ */ Object.create(null);
|
|
665
|
+
cmp[current.field] = cmpOp;
|
|
666
|
+
and.push(cmp);
|
|
667
|
+
branches.push(and.length === 1 ? and[0] : { $and: and });
|
|
1123
668
|
}
|
|
1124
|
-
return {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
669
|
+
return { $or: branches };
|
|
670
|
+
};
|
|
671
|
+
const getKeysetOp = (order, direction) => {
|
|
672
|
+
if (direction === "next") return order === "asc" ? "$gt" : "$lt";
|
|
673
|
+
return order === "asc" ? "$lt" : "$gt";
|
|
674
|
+
};
|
|
675
|
+
const invertOrder = (order) => {
|
|
676
|
+
return order === "asc" ? "desc" : "asc";
|
|
677
|
+
};
|
|
678
|
+
const materializeMongoPagination = (compiled, fetchedNodes, options) => {
|
|
679
|
+
const limit = compiled.spec.limit;
|
|
680
|
+
const hasMore = fetchedNodes.length > limit;
|
|
681
|
+
const trimmed = fetchedNodes.slice(0, limit);
|
|
682
|
+
const nodes = compiled.spec.direction === "prev" ? trimmed.reverse() : trimmed;
|
|
683
|
+
const hasPrevPage = compiled.spec.direction === "next" ? Boolean(compiled.spec.cursor) : hasMore;
|
|
684
|
+
const hasNextPage = compiled.spec.direction === "next" ? hasMore : Boolean(compiled.spec.cursor);
|
|
685
|
+
const pageInfo = {
|
|
686
|
+
hasNextPage,
|
|
687
|
+
hasPrevPage
|
|
1137
688
|
};
|
|
1138
|
-
if (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
689
|
+
if (nodes.length === 0) {
|
|
690
|
+
return { nodes, pageInfo };
|
|
691
|
+
}
|
|
692
|
+
if (hasPrevPage) {
|
|
693
|
+
pageInfo.prevCursor = encodePaginationCursor(compiled.spec, nodes[0], options.cursor);
|
|
694
|
+
}
|
|
695
|
+
if (hasNextPage) {
|
|
696
|
+
pageInfo.nextCursor = encodePaginationCursor(compiled.spec, nodes[nodes.length - 1], options.cursor);
|
|
697
|
+
}
|
|
698
|
+
return { nodes, pageInfo };
|
|
699
|
+
};
|
|
700
|
+
const MongoAdapter = {
|
|
701
|
+
applyPagination: (query, compiled) => {
|
|
702
|
+
query.where(compiled.mongoFilterDelta);
|
|
703
|
+
query.sort(compiled.mongoSort);
|
|
704
|
+
query.limit(compiled.mongoLimit);
|
|
705
|
+
return query;
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
const paginateMongoQuery = async (query, pagination, options) => {
|
|
709
|
+
const compiled = compileMongoPagination(pagination, { cursor: options.cursor });
|
|
710
|
+
MongoAdapter.applyPagination(query, compiled);
|
|
711
|
+
const fetchedNodes = await query.exec();
|
|
712
|
+
if (!Array.isArray(fetchedNodes)) {
|
|
713
|
+
throw new Error("paginateMongoQuery expects query.exec() to return an array");
|
|
714
|
+
}
|
|
715
|
+
return materializeMongoPagination(compiled, fetchedNodes, { cursor: options.cursor });
|
|
716
|
+
};
|
|
717
|
+
const getQueryOptions$1 = (query) => {
|
|
718
|
+
if (!query || typeof query !== "object") return void 0;
|
|
719
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
720
|
+
return query.getOptions();
|
|
721
|
+
};
|
|
722
|
+
const getPaginationFromOptions = (query) => {
|
|
723
|
+
const options = getQueryOptions$1(query);
|
|
724
|
+
return options?.pagination;
|
|
725
|
+
};
|
|
726
|
+
const getCursorFromOptions = (query) => {
|
|
727
|
+
const options = getQueryOptions$1(query);
|
|
728
|
+
return options?.paginationCursor;
|
|
729
|
+
};
|
|
730
|
+
const mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
731
|
+
schema.query.paginate = async function(pagination, options) {
|
|
732
|
+
const spec = pagination ?? getPaginationFromOptions(this);
|
|
733
|
+
if (!spec) throw new Error("Missing pagination spec");
|
|
734
|
+
const cursor = options?.cursor ?? getCursorFromOptions(this) ?? pluginOptions?.cursor;
|
|
735
|
+
if (!cursor?.signingSecret) throw new Error("Missing pagination cursor signingSecret");
|
|
736
|
+
return await paginateMongoQuery(this, spec, { cursor });
|
|
1147
737
|
};
|
|
1148
|
-
}
|
|
738
|
+
};
|
|
1149
739
|
const getAppName$1 = (env = process.env) => {
|
|
1150
740
|
const appName = env.APP_NAME?.trim();
|
|
1151
741
|
if (!appName) {
|
|
@@ -1237,11 +827,11 @@ const insertChanges = async (db, changes) => {
|
|
|
1237
827
|
if (!changes.length) return;
|
|
1238
828
|
const { RtsChange } = getRtsModels(db);
|
|
1239
829
|
const ts = /* @__PURE__ */ new Date();
|
|
1240
|
-
await RtsChange.insertMany(changes.map((
|
|
1241
|
-
seq:
|
|
1242
|
-
modelName:
|
|
1243
|
-
op:
|
|
1244
|
-
docId:
|
|
830
|
+
await RtsChange.insertMany(changes.map((c2) => ({
|
|
831
|
+
seq: c2.seq,
|
|
832
|
+
modelName: c2.modelName,
|
|
833
|
+
op: c2.op,
|
|
834
|
+
docId: c2.docId ?? void 0,
|
|
1245
835
|
ts
|
|
1246
836
|
})));
|
|
1247
837
|
};
|
|
@@ -1272,7 +862,7 @@ const captureDeleteMeta = async (query, mode) => {
|
|
|
1272
862
|
findQuery.limit(maxDeleteIds + 1);
|
|
1273
863
|
}
|
|
1274
864
|
const docs = await findQuery;
|
|
1275
|
-
const ids = Array.isArray(docs) ? docs.map((
|
|
865
|
+
const ids = Array.isArray(docs) ? docs.map((d2) => normalizeId(d2?._id)).filter((id) => Boolean(id)) : [];
|
|
1276
866
|
const reset = mode === "many" && ids.length > maxDeleteIds;
|
|
1277
867
|
const trimmedIds = reset ? [] : ids;
|
|
1278
868
|
const meta = {
|
|
@@ -1326,125 +916,6 @@ const rtsChangeLogPlugin = (schema) => {
|
|
|
1326
916
|
}
|
|
1327
917
|
});
|
|
1328
918
|
};
|
|
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
919
|
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1449
920
|
const mergeMongoQuery = (left, right) => {
|
|
1450
921
|
const leftQuery = isRecord(left) ? left : {};
|
|
@@ -1452,13 +923,13 @@ const mergeMongoQuery = (left, right) => {
|
|
|
1452
923
|
if (Object.keys(right).length === 0) return leftQuery;
|
|
1453
924
|
return { $and: [leftQuery, right] };
|
|
1454
925
|
};
|
|
1455
|
-
const getQueryOptions
|
|
926
|
+
const getQueryOptions = (query) => {
|
|
1456
927
|
if (!query || typeof query !== "object") return void 0;
|
|
1457
928
|
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1458
929
|
return query.getOptions();
|
|
1459
930
|
};
|
|
1460
931
|
const getStoredAclFromQuery = (query) => {
|
|
1461
|
-
const options = getQueryOptions
|
|
932
|
+
const options = getQueryOptions(query);
|
|
1462
933
|
const raw = options?.rbAcl;
|
|
1463
934
|
if (!isRecord(raw)) return null;
|
|
1464
935
|
if (!("ability" in raw)) return null;
|
|
@@ -1526,8 +997,8 @@ const patchAggregateAcl = () => {
|
|
|
1526
997
|
return this;
|
|
1527
998
|
};
|
|
1528
999
|
};
|
|
1529
|
-
const createModelAclProxy = (
|
|
1530
|
-
return new Proxy(
|
|
1000
|
+
const createModelAclProxy = (model2, ability) => {
|
|
1001
|
+
return new Proxy(model2, {
|
|
1531
1002
|
get(target, prop, receiver) {
|
|
1532
1003
|
const value = Reflect.get(target, prop, receiver);
|
|
1533
1004
|
if (typeof value !== "function") return value;
|
|
@@ -1590,290 +1061,6 @@ const mongooseAclPlugin = (schema) => {
|
|
|
1590
1061
|
addQueryAclFilter(this, "update");
|
|
1591
1062
|
});
|
|
1592
1063
|
};
|
|
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
1064
|
let cachedModels = null;
|
|
1878
1065
|
const DEFAULT_GLOBAL_RB_MODEL_NAMES_SET = /* @__PURE__ */ new Set([
|
|
1879
1066
|
"RBUser",
|
|
@@ -2043,6 +1230,7 @@ export {
|
|
|
2043
1230
|
RBUploadSessionPolicy,
|
|
2044
1231
|
RBUploadSessionSchema,
|
|
2045
1232
|
RBUserSchema,
|
|
1233
|
+
Schema,
|
|
2046
1234
|
ZRBNotification,
|
|
2047
1235
|
ZRBNotificationDigestFrequency,
|
|
2048
1236
|
ZRBNotificationSettings,
|
|
@@ -2063,30 +1251,31 @@ export {
|
|
|
2063
1251
|
ZRBUploadSession,
|
|
2064
1252
|
ZRBUploadSessionStatus,
|
|
2065
1253
|
ZRBUser,
|
|
2066
|
-
buildAbility,
|
|
2067
|
-
buildAbilityFromSession,
|
|
1254
|
+
b as buildAbility,
|
|
1255
|
+
d as buildAbilityFromSession,
|
|
2068
1256
|
buildLocaleFallbackChain,
|
|
2069
|
-
can,
|
|
1257
|
+
f as can,
|
|
2070
1258
|
createModels,
|
|
1259
|
+
extendMongooseSchema,
|
|
2071
1260
|
extendZod,
|
|
2072
|
-
getAccessibleByQuery,
|
|
2073
|
-
getRegisteredPolicies,
|
|
1261
|
+
e as getAccessibleByQuery,
|
|
1262
|
+
g as getRegisteredPolicies,
|
|
2074
1263
|
getTenantFilesystemDb,
|
|
2075
1264
|
getTenantFilesystemDbFromCtx,
|
|
2076
1265
|
getTenantFilesystemDbName,
|
|
2077
|
-
getTenantRolesFromSessionUser,
|
|
1266
|
+
c as getTenantRolesFromSessionUser,
|
|
2078
1267
|
isPaginationValidationError,
|
|
1268
|
+
localizedStringField,
|
|
1269
|
+
model,
|
|
2079
1270
|
models,
|
|
2080
1271
|
mongoPaginationPlugin,
|
|
1272
|
+
default2 as mongoose,
|
|
1273
|
+
omitMongooseSchemaPaths,
|
|
2081
1274
|
registerPoliciesFromModules,
|
|
2082
|
-
registerPolicy,
|
|
1275
|
+
r as registerPolicy,
|
|
2083
1276
|
resolveLocalizedString,
|
|
2084
1277
|
withLocalizedStringFallback,
|
|
2085
1278
|
z2 as z,
|
|
2086
1279
|
zI18nString,
|
|
2087
|
-
|
|
2088
|
-
zLocalizedString,
|
|
2089
|
-
zUUID,
|
|
2090
|
-
zodSchema,
|
|
2091
|
-
zodSchemaRaw
|
|
1280
|
+
zLocalizedString
|
|
2092
1281
|
};
|