@rpcbase/db 0.86.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acl/index.js +13 -109
- package/dist/acl/index.js.map +1 -1
- package/dist/can-B4pD_pnR.js +132 -0
- package/dist/can-B4pD_pnR.js.map +1 -0
- package/dist/index-BQeL_LHp.js +133 -0
- package/dist/index-BQeL_LHp.js.map +1 -0
- package/dist/index.browser.js +16 -2
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js +2074 -1774
- package/dist/index.js.map +1 -1
- package/dist/model.js +2 -0
- package/dist/model.js.map +1 -0
- package/package.json +6 -6
- package/dist/zod-Co_dG17m.js +0 -123
- package/dist/zod-Co_dG17m.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1820 +1,2120 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a
|
|
3
|
-
import "
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { r as registerPoliciesFromModules, h as hasRegisteredPolicy } from "./can-B4pD_pnR.js";
|
|
2
|
+
import { b, a, c, g, d, e, f } from "./can-B4pD_pnR.js";
|
|
3
|
+
import mongoose, { Schema as Schema$1, Types } from "mongoose";
|
|
4
|
+
import { default as default2 } from "mongoose";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { timingSafeEqual, createHmac } from "node:crypto";
|
|
7
|
+
import { w as withLocalizedStringFallback } from "./index-BQeL_LHp.js";
|
|
8
|
+
import { E, a as a2, L, b as b2, e as e2, m, r, z as z2, c as c2, d as d2, f as f2 } from "./index-BQeL_LHp.js";
|
|
8
9
|
import assert from "assert";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expiresAt: z$1.date().optional(),
|
|
39
|
-
rawUserInfo: z$1.unknown().optional(),
|
|
40
|
-
createdAt: z$1.date().optional(),
|
|
41
|
-
updatedAt: z$1.date().optional()
|
|
42
|
-
})).optional(),
|
|
43
|
-
emailVerificationCode: z$1.string().length(6).optional(),
|
|
44
|
-
emailVerificationExpiresAt: z$1.date().optional(),
|
|
45
|
-
passwordResetCode: z$1.string().length(6).optional(),
|
|
46
|
-
passwordResetCodeExpiresAt: z$1.date().optional(),
|
|
47
|
-
passwordResetToken: z$1.string().optional(),
|
|
48
|
-
passwordResetTokenExpiresAt: z$1.date().optional()
|
|
10
|
+
import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
|
|
11
|
+
import "@casl/ability";
|
|
12
|
+
const ZRBUser = z.object({
|
|
13
|
+
email: z.string().email().optional(),
|
|
14
|
+
password: z.string(),
|
|
15
|
+
name: z.string().optional(),
|
|
16
|
+
phone: z.string().optional(),
|
|
17
|
+
tenants: z.array(z.string()),
|
|
18
|
+
tenantRoles: z.record(z.string(), z.array(z.string())).optional(),
|
|
19
|
+
oauthProviders: z.record(z.string(), z.object({
|
|
20
|
+
subject: z.string(),
|
|
21
|
+
email: z.string().email().optional(),
|
|
22
|
+
name: z.string().optional(),
|
|
23
|
+
accessToken: z.string().optional(),
|
|
24
|
+
refreshToken: z.string().optional(),
|
|
25
|
+
idToken: z.string().optional(),
|
|
26
|
+
scope: z.string().optional(),
|
|
27
|
+
tokenType: z.string().optional(),
|
|
28
|
+
expiresAt: z.date().optional(),
|
|
29
|
+
rawUserInfo: z.unknown().optional(),
|
|
30
|
+
createdAt: z.date().optional(),
|
|
31
|
+
updatedAt: z.date().optional()
|
|
32
|
+
})).optional(),
|
|
33
|
+
emailVerificationCode: z.string().length(6).optional(),
|
|
34
|
+
emailVerificationExpiresAt: z.date().optional(),
|
|
35
|
+
passwordResetCode: z.string().length(6).optional(),
|
|
36
|
+
passwordResetCodeExpiresAt: z.date().optional(),
|
|
37
|
+
passwordResetToken: z.string().optional(),
|
|
38
|
+
passwordResetTokenExpiresAt: z.date().optional()
|
|
49
39
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
40
|
+
const RBUserSchema = new Schema$1({
|
|
41
|
+
email: {
|
|
42
|
+
type: String,
|
|
43
|
+
unique: true,
|
|
44
|
+
sparse: true
|
|
45
|
+
},
|
|
46
|
+
phone: {
|
|
47
|
+
type: String,
|
|
48
|
+
unique: true,
|
|
49
|
+
sparse: true
|
|
50
|
+
},
|
|
51
|
+
password: {
|
|
52
|
+
type: String,
|
|
53
|
+
required: true
|
|
54
|
+
},
|
|
55
|
+
name: String,
|
|
56
|
+
tenants: {
|
|
57
|
+
type: [String],
|
|
58
|
+
index: true,
|
|
59
|
+
required: true
|
|
60
|
+
},
|
|
61
|
+
tenantRoles: {
|
|
62
|
+
type: Map,
|
|
63
|
+
of: [String],
|
|
64
|
+
required: false,
|
|
65
|
+
default: {}
|
|
66
|
+
},
|
|
67
|
+
oauthProviders: {
|
|
68
|
+
type: Map,
|
|
69
|
+
of: new Schema$1({
|
|
70
|
+
subject: {
|
|
71
|
+
type: String,
|
|
72
|
+
required: true
|
|
73
|
+
},
|
|
74
|
+
email: {
|
|
75
|
+
type: String,
|
|
76
|
+
required: false
|
|
77
|
+
},
|
|
78
|
+
name: {
|
|
79
|
+
type: String,
|
|
80
|
+
required: false
|
|
81
|
+
},
|
|
82
|
+
accessToken: {
|
|
83
|
+
type: String,
|
|
84
|
+
required: false
|
|
85
|
+
},
|
|
86
|
+
refreshToken: {
|
|
87
|
+
type: String,
|
|
88
|
+
required: false
|
|
89
|
+
},
|
|
90
|
+
idToken: {
|
|
91
|
+
type: String,
|
|
92
|
+
required: false
|
|
93
|
+
},
|
|
94
|
+
scope: {
|
|
95
|
+
type: String,
|
|
96
|
+
required: false
|
|
97
|
+
},
|
|
98
|
+
tokenType: {
|
|
99
|
+
type: String,
|
|
100
|
+
required: false
|
|
101
|
+
},
|
|
102
|
+
expiresAt: {
|
|
103
|
+
type: Date,
|
|
104
|
+
required: false
|
|
105
|
+
},
|
|
106
|
+
rawUserInfo: {
|
|
107
|
+
type: Schema$1.Types.Mixed,
|
|
108
|
+
required: false
|
|
109
|
+
},
|
|
110
|
+
createdAt: {
|
|
111
|
+
type: Date,
|
|
112
|
+
required: false
|
|
113
|
+
},
|
|
114
|
+
updatedAt: {
|
|
115
|
+
type: Date,
|
|
116
|
+
required: false
|
|
117
|
+
}
|
|
118
|
+
}, {
|
|
119
|
+
_id: false
|
|
120
|
+
}),
|
|
121
|
+
default: {},
|
|
122
|
+
required: false
|
|
123
|
+
},
|
|
124
|
+
emailVerificationCode: {
|
|
125
|
+
type: String,
|
|
126
|
+
required: false
|
|
127
|
+
},
|
|
128
|
+
emailVerificationExpiresAt: {
|
|
129
|
+
type: Date,
|
|
130
|
+
required: false
|
|
131
|
+
},
|
|
132
|
+
passwordResetCode: {
|
|
133
|
+
type: String,
|
|
134
|
+
required: false
|
|
135
|
+
},
|
|
136
|
+
passwordResetCodeExpiresAt: {
|
|
137
|
+
type: Date,
|
|
138
|
+
required: false
|
|
139
|
+
},
|
|
140
|
+
passwordResetToken: {
|
|
141
|
+
type: String,
|
|
142
|
+
required: false
|
|
143
|
+
},
|
|
144
|
+
passwordResetTokenExpiresAt: {
|
|
145
|
+
type: Date,
|
|
146
|
+
required: false
|
|
147
|
+
}
|
|
156
148
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
parentTenantId: z$1.string().optional(),
|
|
162
|
-
name: z$1.string().optional()
|
|
149
|
+
const ZRBTenant = z.object({
|
|
150
|
+
tenantId: z.string(),
|
|
151
|
+
parentTenantId: z.string().optional(),
|
|
152
|
+
name: z.string().optional()
|
|
163
153
|
});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
154
|
+
const RBTenantSchema = new Schema$1({
|
|
155
|
+
tenantId: {
|
|
156
|
+
type: String,
|
|
157
|
+
required: true,
|
|
158
|
+
unique: true,
|
|
159
|
+
index: true
|
|
160
|
+
},
|
|
161
|
+
parentTenantId: {
|
|
162
|
+
type: String
|
|
163
|
+
},
|
|
164
|
+
name: {
|
|
165
|
+
type: String
|
|
166
|
+
}
|
|
173
167
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
modules: z$1.array(z$1.string()).optional(),
|
|
203
|
-
currentPeriodStart: z$1.date().optional(),
|
|
204
|
-
currentPeriodEnd: z$1.date().optional(),
|
|
205
|
-
trialEndsAt: z$1.date().optional(),
|
|
206
|
-
cancelAt: z$1.date().optional(),
|
|
207
|
-
cancelAtPeriodEnd: z$1.boolean().optional(),
|
|
208
|
-
canceledAt: z$1.date().optional(),
|
|
209
|
-
provider: z$1.string().optional(),
|
|
210
|
-
providerCustomerId: z$1.string().optional(),
|
|
211
|
-
providerSubscriptionId: z$1.string().optional(),
|
|
212
|
-
latestEventId: z$1.string().optional(),
|
|
213
|
-
latestEventAt: z$1.date().optional(),
|
|
214
|
-
metadata: z$1.record(z$1.string(), z$1.unknown()).optional()
|
|
168
|
+
const ZRBTenantSubscriptionStatus = z.enum(["trialing", "active", "past_due", "paused", "canceled", "expired"]);
|
|
169
|
+
const ZRBTenantSubscriptionIntervalUnit = z.enum(["month", "year"]);
|
|
170
|
+
const ZRBTenantSubscriptionType = z.enum(["primary", "addon"]);
|
|
171
|
+
const ZRBTenantSubscriptionScope = z.enum(["tenant", "shop", "custom"]);
|
|
172
|
+
const ZRBTenantSubscription = z.object({
|
|
173
|
+
tenantId: z.string(),
|
|
174
|
+
subscriptionId: z.string(),
|
|
175
|
+
type: ZRBTenantSubscriptionType.optional(),
|
|
176
|
+
parentSubscriptionId: z.string().optional(),
|
|
177
|
+
scope: ZRBTenantSubscriptionScope.optional(),
|
|
178
|
+
scopeId: z.string().optional(),
|
|
179
|
+
planKey: z.string(),
|
|
180
|
+
status: ZRBTenantSubscriptionStatus,
|
|
181
|
+
intervalUnit: ZRBTenantSubscriptionIntervalUnit,
|
|
182
|
+
intervalCount: z.number().int().min(1).optional(),
|
|
183
|
+
modules: z.array(z.string()).optional(),
|
|
184
|
+
currentPeriodStart: z.date().optional(),
|
|
185
|
+
currentPeriodEnd: z.date().optional(),
|
|
186
|
+
trialEndsAt: z.date().optional(),
|
|
187
|
+
cancelAt: z.date().optional(),
|
|
188
|
+
cancelAtPeriodEnd: z.boolean().optional(),
|
|
189
|
+
canceledAt: z.date().optional(),
|
|
190
|
+
provider: z.string().optional(),
|
|
191
|
+
providerCustomerId: z.string().optional(),
|
|
192
|
+
providerSubscriptionId: z.string().optional(),
|
|
193
|
+
latestEventId: z.string().optional(),
|
|
194
|
+
latestEventAt: z.date().optional(),
|
|
195
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
215
196
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
197
|
+
const RBTenantSubscriptionSchema = new Schema$1({
|
|
198
|
+
tenantId: {
|
|
199
|
+
type: String,
|
|
200
|
+
required: true,
|
|
201
|
+
index: true
|
|
202
|
+
},
|
|
203
|
+
subscriptionId: {
|
|
204
|
+
type: String,
|
|
205
|
+
required: true
|
|
206
|
+
},
|
|
207
|
+
type: {
|
|
208
|
+
type: String,
|
|
209
|
+
required: true,
|
|
210
|
+
enum: ZRBTenantSubscriptionType.options,
|
|
211
|
+
default: "primary"
|
|
212
|
+
},
|
|
213
|
+
parentSubscriptionId: {
|
|
214
|
+
type: String
|
|
215
|
+
},
|
|
216
|
+
scope: {
|
|
217
|
+
type: String,
|
|
218
|
+
enum: ZRBTenantSubscriptionScope.options,
|
|
219
|
+
default: "tenant"
|
|
220
|
+
},
|
|
221
|
+
scopeId: {
|
|
222
|
+
type: String
|
|
223
|
+
},
|
|
224
|
+
planKey: {
|
|
225
|
+
type: String,
|
|
226
|
+
required: true
|
|
227
|
+
},
|
|
228
|
+
status: {
|
|
229
|
+
type: String,
|
|
230
|
+
required: true,
|
|
231
|
+
enum: ZRBTenantSubscriptionStatus.options
|
|
232
|
+
},
|
|
233
|
+
intervalUnit: {
|
|
234
|
+
type: String,
|
|
235
|
+
required: true,
|
|
236
|
+
enum: ZRBTenantSubscriptionIntervalUnit.options
|
|
237
|
+
},
|
|
238
|
+
intervalCount: {
|
|
239
|
+
type: Number,
|
|
240
|
+
min: 1,
|
|
241
|
+
default: 1
|
|
242
|
+
},
|
|
243
|
+
modules: {
|
|
244
|
+
type: [String],
|
|
245
|
+
default: []
|
|
246
|
+
},
|
|
247
|
+
currentPeriodStart: {
|
|
248
|
+
type: Date
|
|
249
|
+
},
|
|
250
|
+
currentPeriodEnd: {
|
|
251
|
+
type: Date
|
|
252
|
+
},
|
|
253
|
+
trialEndsAt: {
|
|
254
|
+
type: Date
|
|
255
|
+
},
|
|
256
|
+
cancelAt: {
|
|
257
|
+
type: Date
|
|
258
|
+
},
|
|
259
|
+
cancelAtPeriodEnd: {
|
|
260
|
+
type: Boolean,
|
|
261
|
+
default: false
|
|
262
|
+
},
|
|
263
|
+
canceledAt: {
|
|
264
|
+
type: Date
|
|
265
|
+
},
|
|
266
|
+
provider: {
|
|
267
|
+
type: String
|
|
268
|
+
},
|
|
269
|
+
providerCustomerId: {
|
|
270
|
+
type: String
|
|
271
|
+
},
|
|
272
|
+
providerSubscriptionId: {
|
|
273
|
+
type: String
|
|
274
|
+
},
|
|
275
|
+
latestEventId: {
|
|
276
|
+
type: String
|
|
277
|
+
},
|
|
278
|
+
latestEventAt: {
|
|
279
|
+
type: Date
|
|
280
|
+
},
|
|
281
|
+
metadata: {
|
|
282
|
+
type: Schema$1.Types.Mixed
|
|
283
|
+
}
|
|
277
284
|
});
|
|
278
285
|
RBTenantSubscriptionSchema.index({
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}, {
|
|
286
|
+
tenantId: 1,
|
|
287
|
+
subscriptionId: 1
|
|
288
|
+
}, {
|
|
289
|
+
unique: true
|
|
290
|
+
});
|
|
282
291
|
RBTenantSubscriptionSchema.index({
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
tenantId: 1,
|
|
293
|
+
scope: 1,
|
|
294
|
+
scopeId: 1
|
|
286
295
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
toIntervalUnit: ZRBTenantSubscriptionIntervalUnit.optional(),
|
|
314
|
-
fromIntervalCount: z$1.number().int().min(1).optional(),
|
|
315
|
-
toIntervalCount: z$1.number().int().min(1).optional(),
|
|
316
|
-
direction: ZRBTenantSubscriptionChangeDirection.optional(),
|
|
317
|
-
actorUserId: z$1.string().optional(),
|
|
318
|
-
source: ZRBTenantSubscriptionEventSource.optional(),
|
|
319
|
-
reason: z$1.string().optional(),
|
|
320
|
-
provider: z$1.string().optional(),
|
|
321
|
-
providerEventId: z$1.string().optional(),
|
|
322
|
-
providerPayload: z$1.unknown().optional(),
|
|
323
|
-
metadata: z$1.record(z$1.string(), z$1.unknown()).optional()
|
|
296
|
+
const ZRBTenantSubscriptionEventSource = z.enum(["admin", "system", "webhook", "user"]);
|
|
297
|
+
const ZRBTenantSubscriptionChangeDirection = z.enum(["upgrade", "downgrade", "lateral"]);
|
|
298
|
+
const ZRBTenantSubscriptionEvent = z.object({
|
|
299
|
+
tenantId: z.string(),
|
|
300
|
+
subscriptionId: z.string(),
|
|
301
|
+
type: z.string(),
|
|
302
|
+
occurredAt: z.date(),
|
|
303
|
+
effectiveAt: z.date().optional(),
|
|
304
|
+
fromPlanKey: z.string().optional(),
|
|
305
|
+
toPlanKey: z.string().optional(),
|
|
306
|
+
fromStatus: ZRBTenantSubscriptionStatus.optional(),
|
|
307
|
+
toStatus: ZRBTenantSubscriptionStatus.optional(),
|
|
308
|
+
fromModules: z.array(z.string()).optional(),
|
|
309
|
+
toModules: z.array(z.string()).optional(),
|
|
310
|
+
fromIntervalUnit: ZRBTenantSubscriptionIntervalUnit.optional(),
|
|
311
|
+
toIntervalUnit: ZRBTenantSubscriptionIntervalUnit.optional(),
|
|
312
|
+
fromIntervalCount: z.number().int().min(1).optional(),
|
|
313
|
+
toIntervalCount: z.number().int().min(1).optional(),
|
|
314
|
+
direction: ZRBTenantSubscriptionChangeDirection.optional(),
|
|
315
|
+
actorUserId: z.string().optional(),
|
|
316
|
+
source: ZRBTenantSubscriptionEventSource.optional(),
|
|
317
|
+
reason: z.string().optional(),
|
|
318
|
+
provider: z.string().optional(),
|
|
319
|
+
providerEventId: z.string().optional(),
|
|
320
|
+
providerPayload: z.unknown().optional(),
|
|
321
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
324
322
|
});
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
323
|
+
const RBTenantSubscriptionEventSchema = new Schema$1({
|
|
324
|
+
tenantId: {
|
|
325
|
+
type: String,
|
|
326
|
+
required: true,
|
|
327
|
+
index: true
|
|
328
|
+
},
|
|
329
|
+
subscriptionId: {
|
|
330
|
+
type: String,
|
|
331
|
+
required: true,
|
|
332
|
+
index: true
|
|
333
|
+
},
|
|
334
|
+
type: {
|
|
335
|
+
type: String,
|
|
336
|
+
required: true
|
|
337
|
+
},
|
|
338
|
+
occurredAt: {
|
|
339
|
+
type: Date,
|
|
340
|
+
required: true,
|
|
341
|
+
default: Date.now
|
|
342
|
+
},
|
|
343
|
+
effectiveAt: {
|
|
344
|
+
type: Date
|
|
345
|
+
},
|
|
346
|
+
fromPlanKey: {
|
|
347
|
+
type: String
|
|
348
|
+
},
|
|
349
|
+
toPlanKey: {
|
|
350
|
+
type: String
|
|
351
|
+
},
|
|
352
|
+
fromStatus: {
|
|
353
|
+
type: String,
|
|
354
|
+
enum: ZRBTenantSubscriptionStatus.options
|
|
355
|
+
},
|
|
356
|
+
toStatus: {
|
|
357
|
+
type: String,
|
|
358
|
+
enum: ZRBTenantSubscriptionStatus.options
|
|
359
|
+
},
|
|
360
|
+
fromModules: {
|
|
361
|
+
type: [String],
|
|
362
|
+
default: void 0
|
|
363
|
+
},
|
|
364
|
+
toModules: {
|
|
365
|
+
type: [String],
|
|
366
|
+
default: void 0
|
|
367
|
+
},
|
|
368
|
+
fromIntervalUnit: {
|
|
369
|
+
type: String,
|
|
370
|
+
enum: ZRBTenantSubscriptionIntervalUnit.options
|
|
371
|
+
},
|
|
372
|
+
toIntervalUnit: {
|
|
373
|
+
type: String,
|
|
374
|
+
enum: ZRBTenantSubscriptionIntervalUnit.options
|
|
375
|
+
},
|
|
376
|
+
fromIntervalCount: {
|
|
377
|
+
type: Number,
|
|
378
|
+
min: 1
|
|
379
|
+
},
|
|
380
|
+
toIntervalCount: {
|
|
381
|
+
type: Number,
|
|
382
|
+
min: 1
|
|
383
|
+
},
|
|
384
|
+
direction: {
|
|
385
|
+
type: String,
|
|
386
|
+
enum: ZRBTenantSubscriptionChangeDirection.options
|
|
387
|
+
},
|
|
388
|
+
actorUserId: {
|
|
389
|
+
type: String
|
|
390
|
+
},
|
|
391
|
+
source: {
|
|
392
|
+
type: String,
|
|
393
|
+
enum: ZRBTenantSubscriptionEventSource.options
|
|
394
|
+
},
|
|
395
|
+
reason: {
|
|
396
|
+
type: String
|
|
397
|
+
},
|
|
398
|
+
provider: {
|
|
399
|
+
type: String
|
|
400
|
+
},
|
|
401
|
+
providerEventId: {
|
|
402
|
+
type: String
|
|
403
|
+
},
|
|
404
|
+
providerPayload: {
|
|
405
|
+
type: Schema$1.Types.Mixed
|
|
406
|
+
},
|
|
407
|
+
metadata: {
|
|
408
|
+
type: Schema$1.Types.Mixed
|
|
409
|
+
}
|
|
394
410
|
});
|
|
395
411
|
RBTenantSubscriptionEventSchema.index({
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
412
|
+
tenantId: 1,
|
|
413
|
+
subscriptionId: 1,
|
|
414
|
+
occurredAt: 1
|
|
399
415
|
});
|
|
400
416
|
RBTenantSubscriptionEventSchema.index({
|
|
401
|
-
|
|
402
|
-
|
|
417
|
+
provider: 1,
|
|
418
|
+
providerEventId: 1
|
|
419
|
+
}, {
|
|
420
|
+
unique: true,
|
|
421
|
+
sparse: true
|
|
422
|
+
});
|
|
423
|
+
const ZRBRtsCounter = z.object({
|
|
424
|
+
_id: z.string(),
|
|
425
|
+
seq: z.number().int().min(0)
|
|
426
|
+
});
|
|
427
|
+
const RBRtsCounterSchema = new Schema$1({
|
|
428
|
+
_id: {
|
|
429
|
+
type: String,
|
|
430
|
+
required: true
|
|
431
|
+
},
|
|
432
|
+
seq: {
|
|
433
|
+
type: Number,
|
|
434
|
+
required: true,
|
|
435
|
+
default: 0
|
|
436
|
+
}
|
|
403
437
|
}, {
|
|
404
|
-
|
|
405
|
-
sparse: true
|
|
438
|
+
versionKey: false
|
|
406
439
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
440
|
+
const ttlSecondsRaw = process.env.RB_RTS_CHANGES_TTL_S ?? "";
|
|
441
|
+
const ttlSeconds = Number.isFinite(Number(ttlSecondsRaw)) ? Math.max(60, Math.floor(Number(ttlSecondsRaw))) : 60 * 60 * 24 * 30;
|
|
442
|
+
const ZRBRtsChangeOp = z.enum(["delete", "reset_model"]);
|
|
443
|
+
const ZRBRtsChange = z.object({
|
|
444
|
+
seq: z.number().int().min(0),
|
|
445
|
+
modelName: z.string(),
|
|
446
|
+
op: ZRBRtsChangeOp,
|
|
447
|
+
docId: z.string().optional(),
|
|
448
|
+
ts: z.date()
|
|
412
449
|
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
450
|
+
const RBRtsChangeSchema = new Schema$1({
|
|
451
|
+
seq: {
|
|
452
|
+
type: Number,
|
|
453
|
+
required: true
|
|
454
|
+
},
|
|
455
|
+
modelName: {
|
|
456
|
+
type: String,
|
|
457
|
+
required: true,
|
|
458
|
+
index: true
|
|
459
|
+
},
|
|
460
|
+
op: {
|
|
461
|
+
type: String,
|
|
462
|
+
required: true,
|
|
463
|
+
enum: ZRBRtsChangeOp.options
|
|
464
|
+
},
|
|
465
|
+
docId: {
|
|
466
|
+
type: String,
|
|
467
|
+
required: false
|
|
468
|
+
},
|
|
469
|
+
ts: {
|
|
470
|
+
type: Date,
|
|
471
|
+
required: true,
|
|
472
|
+
default: Date.now
|
|
473
|
+
}
|
|
474
|
+
}, {
|
|
475
|
+
versionKey: false
|
|
476
|
+
});
|
|
477
|
+
RBRtsChangeSchema.index({
|
|
478
|
+
seq: 1
|
|
479
|
+
}, {
|
|
480
|
+
unique: true
|
|
435
481
|
});
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
482
|
+
RBRtsChangeSchema.index({
|
|
483
|
+
ts: 1
|
|
484
|
+
}, {
|
|
485
|
+
expireAfterSeconds: ttlSeconds
|
|
486
|
+
});
|
|
487
|
+
const ZRBUploadSessionStatus = z.enum(["uploading", "assembling", "done", "error"]);
|
|
488
|
+
const ZRBUploadSession = z.object({
|
|
489
|
+
_id: z.string(),
|
|
490
|
+
userId: z.string().optional(),
|
|
491
|
+
ownerKeyHash: z.string().optional(),
|
|
492
|
+
filename: z.string(),
|
|
493
|
+
mimeType: z.string(),
|
|
494
|
+
totalSize: z.number().int().min(0),
|
|
495
|
+
chunkSize: z.number().int().min(1),
|
|
496
|
+
chunksTotal: z.number().int().min(1),
|
|
497
|
+
status: ZRBUploadSessionStatus,
|
|
498
|
+
createdAt: z.date(),
|
|
499
|
+
expiresAt: z.date(),
|
|
500
|
+
fileId: z.string().optional(),
|
|
501
|
+
isPublic: z.boolean().optional(),
|
|
502
|
+
error: z.string().optional()
|
|
503
|
+
});
|
|
504
|
+
const RBUploadSessionSchema = new Schema$1({
|
|
505
|
+
_id: {
|
|
506
|
+
type: String,
|
|
507
|
+
required: true
|
|
508
|
+
},
|
|
509
|
+
userId: {
|
|
510
|
+
type: String,
|
|
511
|
+
required: false,
|
|
512
|
+
index: true
|
|
513
|
+
},
|
|
514
|
+
ownerKeyHash: {
|
|
515
|
+
type: String,
|
|
516
|
+
required: false
|
|
517
|
+
},
|
|
518
|
+
filename: {
|
|
519
|
+
type: String,
|
|
520
|
+
required: true
|
|
521
|
+
},
|
|
522
|
+
mimeType: {
|
|
523
|
+
type: String,
|
|
524
|
+
required: true
|
|
525
|
+
},
|
|
526
|
+
totalSize: {
|
|
527
|
+
type: Number,
|
|
528
|
+
required: true
|
|
529
|
+
},
|
|
530
|
+
chunkSize: {
|
|
531
|
+
type: Number,
|
|
532
|
+
required: true
|
|
533
|
+
},
|
|
534
|
+
chunksTotal: {
|
|
535
|
+
type: Number,
|
|
536
|
+
required: true
|
|
537
|
+
},
|
|
538
|
+
status: {
|
|
539
|
+
type: String,
|
|
540
|
+
required: true,
|
|
541
|
+
enum: ZRBUploadSessionStatus.options
|
|
542
|
+
},
|
|
543
|
+
createdAt: {
|
|
544
|
+
type: Date,
|
|
545
|
+
required: true,
|
|
546
|
+
default: Date.now
|
|
547
|
+
},
|
|
548
|
+
expiresAt: {
|
|
549
|
+
type: Date,
|
|
550
|
+
required: true
|
|
551
|
+
},
|
|
552
|
+
fileId: {
|
|
553
|
+
type: String,
|
|
554
|
+
required: false
|
|
555
|
+
},
|
|
556
|
+
isPublic: {
|
|
557
|
+
type: Boolean,
|
|
558
|
+
required: false
|
|
559
|
+
},
|
|
560
|
+
error: {
|
|
561
|
+
type: String,
|
|
562
|
+
required: false
|
|
563
|
+
}
|
|
564
|
+
}, {
|
|
565
|
+
versionKey: false
|
|
566
|
+
});
|
|
567
|
+
RBUploadSessionSchema.index({
|
|
568
|
+
expiresAt: 1
|
|
569
|
+
}, {
|
|
570
|
+
expireAfterSeconds: 0
|
|
571
|
+
});
|
|
572
|
+
const RBUploadSessionPolicy = {
|
|
573
|
+
subject: "RBUploadSession",
|
|
574
|
+
define: (builder, ctx) => {
|
|
575
|
+
builder.can("create", "RBUploadSession");
|
|
576
|
+
if (ctx.userId) {
|
|
577
|
+
builder.can("read", "RBUploadSession", {
|
|
578
|
+
userId: ctx.userId
|
|
579
|
+
});
|
|
580
|
+
builder.can("update", "RBUploadSession", {
|
|
581
|
+
userId: ctx.userId
|
|
582
|
+
});
|
|
583
|
+
builder.can("delete", "RBUploadSession", {
|
|
584
|
+
userId: ctx.userId
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
const uploadKeyHash = typeof ctx.claims?.uploadKeyHash === "string" ? ctx.claims.uploadKeyHash.trim() : "";
|
|
588
|
+
if (uploadKeyHash) {
|
|
589
|
+
builder.can("read", "RBUploadSession", {
|
|
590
|
+
ownerKeyHash: uploadKeyHash
|
|
591
|
+
});
|
|
592
|
+
builder.can("update", "RBUploadSession", {
|
|
593
|
+
ownerKeyHash: uploadKeyHash
|
|
594
|
+
});
|
|
595
|
+
builder.can("delete", "RBUploadSession", {
|
|
596
|
+
ownerKeyHash: uploadKeyHash
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
const ZRBUploadChunk = z.object({
|
|
602
|
+
uploadId: z.string(),
|
|
603
|
+
index: z.number().int().min(0),
|
|
604
|
+
data: z.unknown(),
|
|
605
|
+
size: z.number().int().min(0),
|
|
606
|
+
sha256: z.string().optional(),
|
|
607
|
+
createdAt: z.date(),
|
|
608
|
+
expiresAt: z.date()
|
|
609
|
+
});
|
|
610
|
+
const RBUploadChunkSchema = new Schema$1({
|
|
611
|
+
uploadId: {
|
|
612
|
+
type: String,
|
|
613
|
+
required: true,
|
|
614
|
+
index: true
|
|
615
|
+
},
|
|
616
|
+
index: {
|
|
617
|
+
type: Number,
|
|
618
|
+
required: true
|
|
619
|
+
},
|
|
620
|
+
data: {
|
|
621
|
+
type: Buffer,
|
|
622
|
+
required: true
|
|
623
|
+
},
|
|
624
|
+
size: {
|
|
625
|
+
type: Number,
|
|
626
|
+
required: true
|
|
627
|
+
},
|
|
628
|
+
sha256: {
|
|
629
|
+
type: String,
|
|
630
|
+
required: false
|
|
631
|
+
},
|
|
632
|
+
createdAt: {
|
|
633
|
+
type: Date,
|
|
634
|
+
required: true,
|
|
635
|
+
default: Date.now
|
|
636
|
+
},
|
|
637
|
+
expiresAt: {
|
|
638
|
+
type: Date,
|
|
639
|
+
required: true
|
|
640
|
+
}
|
|
641
|
+
}, {
|
|
642
|
+
versionKey: false
|
|
486
643
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
userId: {
|
|
493
|
-
type: String,
|
|
494
|
-
required: false,
|
|
495
|
-
index: true
|
|
496
|
-
},
|
|
497
|
-
ownerKeyHash: {
|
|
498
|
-
type: String,
|
|
499
|
-
required: false
|
|
500
|
-
},
|
|
501
|
-
filename: {
|
|
502
|
-
type: String,
|
|
503
|
-
required: true
|
|
504
|
-
},
|
|
505
|
-
mimeType: {
|
|
506
|
-
type: String,
|
|
507
|
-
required: true
|
|
508
|
-
},
|
|
509
|
-
totalSize: {
|
|
510
|
-
type: Number,
|
|
511
|
-
required: true
|
|
512
|
-
},
|
|
513
|
-
chunkSize: {
|
|
514
|
-
type: Number,
|
|
515
|
-
required: true
|
|
516
|
-
},
|
|
517
|
-
chunksTotal: {
|
|
518
|
-
type: Number,
|
|
519
|
-
required: true
|
|
520
|
-
},
|
|
521
|
-
status: {
|
|
522
|
-
type: String,
|
|
523
|
-
required: true,
|
|
524
|
-
enum: ZRBUploadSessionStatus.options
|
|
525
|
-
},
|
|
526
|
-
createdAt: {
|
|
527
|
-
type: Date,
|
|
528
|
-
required: true,
|
|
529
|
-
default: Date.now
|
|
530
|
-
},
|
|
531
|
-
expiresAt: {
|
|
532
|
-
type: Date,
|
|
533
|
-
required: true
|
|
534
|
-
},
|
|
535
|
-
fileId: {
|
|
536
|
-
type: String,
|
|
537
|
-
required: false
|
|
538
|
-
},
|
|
539
|
-
isPublic: {
|
|
540
|
-
type: Boolean,
|
|
541
|
-
required: false
|
|
542
|
-
},
|
|
543
|
-
error: {
|
|
544
|
-
type: String,
|
|
545
|
-
required: false
|
|
546
|
-
}
|
|
547
|
-
}, { versionKey: false });
|
|
548
|
-
RBUploadSessionSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
|
549
|
-
var RBUploadSessionPolicy = {
|
|
550
|
-
subject: "RBUploadSession",
|
|
551
|
-
define: (builder, ctx) => {
|
|
552
|
-
builder.can("create", "RBUploadSession");
|
|
553
|
-
if (ctx.userId) {
|
|
554
|
-
builder.can("read", "RBUploadSession", { userId: ctx.userId });
|
|
555
|
-
builder.can("update", "RBUploadSession", { userId: ctx.userId });
|
|
556
|
-
builder.can("delete", "RBUploadSession", { userId: ctx.userId });
|
|
557
|
-
}
|
|
558
|
-
const uploadKeyHash = typeof ctx.claims?.uploadKeyHash === "string" ? ctx.claims.uploadKeyHash.trim() : "";
|
|
559
|
-
if (uploadKeyHash) {
|
|
560
|
-
builder.can("read", "RBUploadSession", { ownerKeyHash: uploadKeyHash });
|
|
561
|
-
builder.can("update", "RBUploadSession", { ownerKeyHash: uploadKeyHash });
|
|
562
|
-
builder.can("delete", "RBUploadSession", { ownerKeyHash: uploadKeyHash });
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
//#endregion
|
|
567
|
-
//#region src/models/RBUploadChunk.ts
|
|
568
|
-
var ZRBUploadChunk = z$1.object({
|
|
569
|
-
uploadId: z$1.string(),
|
|
570
|
-
index: z$1.number().int().min(0),
|
|
571
|
-
data: z$1.unknown(),
|
|
572
|
-
size: z$1.number().int().min(0),
|
|
573
|
-
sha256: z$1.string().optional(),
|
|
574
|
-
createdAt: z$1.date(),
|
|
575
|
-
expiresAt: z$1.date()
|
|
644
|
+
RBUploadChunkSchema.index({
|
|
645
|
+
uploadId: 1,
|
|
646
|
+
index: 1
|
|
647
|
+
}, {
|
|
648
|
+
unique: true
|
|
576
649
|
});
|
|
577
|
-
var RBUploadChunkSchema = new Schema$1({
|
|
578
|
-
uploadId: {
|
|
579
|
-
type: String,
|
|
580
|
-
required: true,
|
|
581
|
-
index: true
|
|
582
|
-
},
|
|
583
|
-
index: {
|
|
584
|
-
type: Number,
|
|
585
|
-
required: true
|
|
586
|
-
},
|
|
587
|
-
data: {
|
|
588
|
-
type: Buffer,
|
|
589
|
-
required: true
|
|
590
|
-
},
|
|
591
|
-
size: {
|
|
592
|
-
type: Number,
|
|
593
|
-
required: true
|
|
594
|
-
},
|
|
595
|
-
sha256: {
|
|
596
|
-
type: String,
|
|
597
|
-
required: false
|
|
598
|
-
},
|
|
599
|
-
createdAt: {
|
|
600
|
-
type: Date,
|
|
601
|
-
required: true,
|
|
602
|
-
default: Date.now
|
|
603
|
-
},
|
|
604
|
-
expiresAt: {
|
|
605
|
-
type: Date,
|
|
606
|
-
required: true
|
|
607
|
-
}
|
|
608
|
-
}, { versionKey: false });
|
|
609
650
|
RBUploadChunkSchema.index({
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
651
|
+
expiresAt: 1
|
|
652
|
+
}, {
|
|
653
|
+
expireAfterSeconds: 0
|
|
654
|
+
});
|
|
655
|
+
const ZRBNotification = z.object({
|
|
656
|
+
userId: z.string(),
|
|
657
|
+
topic: z.string().optional(),
|
|
658
|
+
title: z.string(),
|
|
659
|
+
body: z.string().optional(),
|
|
660
|
+
url: z.string().optional(),
|
|
661
|
+
createdAt: z.date(),
|
|
662
|
+
seenAt: z.date().optional(),
|
|
663
|
+
readAt: z.date().optional(),
|
|
664
|
+
archivedAt: z.date().optional(),
|
|
665
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
666
|
+
});
|
|
667
|
+
const TTL_90_DAYS_S = 60 * 60 * 24 * 90;
|
|
668
|
+
const RBNotificationSchema = new Schema$1({
|
|
669
|
+
userId: {
|
|
670
|
+
type: String,
|
|
671
|
+
required: true,
|
|
672
|
+
index: true
|
|
673
|
+
},
|
|
674
|
+
topic: {
|
|
675
|
+
type: String,
|
|
676
|
+
required: false,
|
|
677
|
+
index: true
|
|
678
|
+
},
|
|
679
|
+
title: {
|
|
680
|
+
type: String,
|
|
681
|
+
required: true
|
|
682
|
+
},
|
|
683
|
+
body: {
|
|
684
|
+
type: String,
|
|
685
|
+
required: false
|
|
686
|
+
},
|
|
687
|
+
url: {
|
|
688
|
+
type: String,
|
|
689
|
+
required: false
|
|
690
|
+
},
|
|
691
|
+
createdAt: {
|
|
692
|
+
type: Date,
|
|
693
|
+
required: true,
|
|
694
|
+
default: Date.now,
|
|
695
|
+
index: true
|
|
696
|
+
},
|
|
697
|
+
seenAt: {
|
|
698
|
+
type: Date,
|
|
699
|
+
required: false,
|
|
700
|
+
index: true
|
|
701
|
+
},
|
|
702
|
+
readAt: {
|
|
703
|
+
type: Date,
|
|
704
|
+
required: false,
|
|
705
|
+
index: true
|
|
706
|
+
},
|
|
707
|
+
archivedAt: {
|
|
708
|
+
type: Date,
|
|
709
|
+
required: false
|
|
710
|
+
},
|
|
711
|
+
metadata: {
|
|
712
|
+
type: Schema$1.Types.Mixed,
|
|
713
|
+
required: false
|
|
714
|
+
}
|
|
715
|
+
}, {
|
|
716
|
+
versionKey: false
|
|
627
717
|
});
|
|
628
|
-
var TTL_90_DAYS_S = 3600 * 24 * 90;
|
|
629
|
-
var RBNotificationSchema = new Schema$1({
|
|
630
|
-
userId: {
|
|
631
|
-
type: String,
|
|
632
|
-
required: true,
|
|
633
|
-
index: true
|
|
634
|
-
},
|
|
635
|
-
topic: {
|
|
636
|
-
type: String,
|
|
637
|
-
required: false,
|
|
638
|
-
index: true
|
|
639
|
-
},
|
|
640
|
-
title: {
|
|
641
|
-
type: String,
|
|
642
|
-
required: true
|
|
643
|
-
},
|
|
644
|
-
body: {
|
|
645
|
-
type: String,
|
|
646
|
-
required: false
|
|
647
|
-
},
|
|
648
|
-
url: {
|
|
649
|
-
type: String,
|
|
650
|
-
required: false
|
|
651
|
-
},
|
|
652
|
-
createdAt: {
|
|
653
|
-
type: Date,
|
|
654
|
-
required: true,
|
|
655
|
-
default: Date.now,
|
|
656
|
-
index: true
|
|
657
|
-
},
|
|
658
|
-
seenAt: {
|
|
659
|
-
type: Date,
|
|
660
|
-
required: false,
|
|
661
|
-
index: true
|
|
662
|
-
},
|
|
663
|
-
readAt: {
|
|
664
|
-
type: Date,
|
|
665
|
-
required: false,
|
|
666
|
-
index: true
|
|
667
|
-
},
|
|
668
|
-
archivedAt: {
|
|
669
|
-
type: Date,
|
|
670
|
-
required: false
|
|
671
|
-
},
|
|
672
|
-
metadata: {
|
|
673
|
-
type: Schema$1.Types.Mixed,
|
|
674
|
-
required: false
|
|
675
|
-
}
|
|
676
|
-
}, { versionKey: false });
|
|
677
718
|
RBNotificationSchema.index({
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
719
|
+
userId: 1,
|
|
720
|
+
archivedAt: 1,
|
|
721
|
+
createdAt: -1
|
|
681
722
|
});
|
|
682
723
|
RBNotificationSchema.index({
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
724
|
+
userId: 1,
|
|
725
|
+
seenAt: 1,
|
|
726
|
+
archivedAt: 1,
|
|
727
|
+
createdAt: -1
|
|
687
728
|
});
|
|
688
729
|
RBNotificationSchema.index({
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
730
|
+
userId: 1,
|
|
731
|
+
readAt: 1,
|
|
732
|
+
archivedAt: 1,
|
|
733
|
+
createdAt: -1
|
|
693
734
|
});
|
|
694
|
-
RBNotificationSchema.index({
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (!ctx.userId) return;
|
|
699
|
-
builder.can("create", "RBNotification");
|
|
700
|
-
builder.can("read", "RBNotification", { userId: ctx.userId });
|
|
701
|
-
builder.can("update", "RBNotification", { userId: ctx.userId });
|
|
702
|
-
builder.can("delete", "RBNotification", { userId: ctx.userId });
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
//#endregion
|
|
706
|
-
//#region src/models/RBNotificationSettings.ts
|
|
707
|
-
var ZRBNotificationDigestFrequency = z$1.enum([
|
|
708
|
-
"off",
|
|
709
|
-
"daily",
|
|
710
|
-
"weekly"
|
|
711
|
-
]);
|
|
712
|
-
var ZRBNotificationTopicPreference = z$1.object({
|
|
713
|
-
topic: z$1.string(),
|
|
714
|
-
inApp: z$1.boolean(),
|
|
715
|
-
emailDigest: z$1.boolean(),
|
|
716
|
-
push: z$1.boolean()
|
|
735
|
+
RBNotificationSchema.index({
|
|
736
|
+
archivedAt: 1
|
|
737
|
+
}, {
|
|
738
|
+
expireAfterSeconds: TTL_90_DAYS_S
|
|
717
739
|
});
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
740
|
+
const RBNotificationPolicy = {
|
|
741
|
+
subject: "RBNotification",
|
|
742
|
+
define: (builder, ctx) => {
|
|
743
|
+
if (!ctx.userId) return;
|
|
744
|
+
builder.can("create", "RBNotification");
|
|
745
|
+
builder.can("read", "RBNotification", {
|
|
746
|
+
userId: ctx.userId
|
|
747
|
+
});
|
|
748
|
+
builder.can("update", "RBNotification", {
|
|
749
|
+
userId: ctx.userId
|
|
750
|
+
});
|
|
751
|
+
builder.can("delete", "RBNotification", {
|
|
752
|
+
userId: ctx.userId
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const ZRBNotificationDigestFrequency = z.enum(["off", "daily", "weekly"]);
|
|
757
|
+
const ZRBNotificationTopicPreference = z.object({
|
|
758
|
+
topic: z.string(),
|
|
759
|
+
inApp: z.boolean(),
|
|
760
|
+
emailDigest: z.boolean(),
|
|
761
|
+
push: z.boolean()
|
|
723
762
|
});
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
},
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
digestFrequency: {
|
|
751
|
-
type: String,
|
|
752
|
-
required: true,
|
|
753
|
-
enum: ZRBNotificationDigestFrequency.options,
|
|
754
|
-
default: "weekly"
|
|
755
|
-
},
|
|
756
|
-
topicPreferences: {
|
|
757
|
-
type: [TopicPreferenceSchema],
|
|
758
|
-
default: []
|
|
759
|
-
},
|
|
760
|
-
lastDigestSentAt: {
|
|
761
|
-
type: Date,
|
|
762
|
-
required: false
|
|
763
|
-
}
|
|
763
|
+
const ZRBNotificationSettings = z.object({
|
|
764
|
+
userId: z.string(),
|
|
765
|
+
digestFrequency: ZRBNotificationDigestFrequency,
|
|
766
|
+
topicPreferences: z.array(ZRBNotificationTopicPreference).optional(),
|
|
767
|
+
lastDigestSentAt: z.date().optional()
|
|
768
|
+
});
|
|
769
|
+
const TopicPreferenceSchema = new Schema$1({
|
|
770
|
+
topic: {
|
|
771
|
+
type: String,
|
|
772
|
+
required: true
|
|
773
|
+
},
|
|
774
|
+
inApp: {
|
|
775
|
+
type: Boolean,
|
|
776
|
+
required: true,
|
|
777
|
+
default: true
|
|
778
|
+
},
|
|
779
|
+
emailDigest: {
|
|
780
|
+
type: Boolean,
|
|
781
|
+
required: true,
|
|
782
|
+
default: true
|
|
783
|
+
},
|
|
784
|
+
push: {
|
|
785
|
+
type: Boolean,
|
|
786
|
+
required: true,
|
|
787
|
+
default: false
|
|
788
|
+
}
|
|
764
789
|
}, {
|
|
765
|
-
|
|
766
|
-
|
|
790
|
+
_id: false
|
|
791
|
+
});
|
|
792
|
+
const RBNotificationSettingsSchema = new Schema$1({
|
|
793
|
+
userId: {
|
|
794
|
+
type: String,
|
|
795
|
+
required: true
|
|
796
|
+
},
|
|
797
|
+
digestFrequency: {
|
|
798
|
+
type: String,
|
|
799
|
+
required: true,
|
|
800
|
+
enum: ZRBNotificationDigestFrequency.options,
|
|
801
|
+
default: "weekly"
|
|
802
|
+
},
|
|
803
|
+
topicPreferences: {
|
|
804
|
+
type: [TopicPreferenceSchema],
|
|
805
|
+
default: []
|
|
806
|
+
},
|
|
807
|
+
lastDigestSentAt: {
|
|
808
|
+
type: Date,
|
|
809
|
+
required: false
|
|
810
|
+
}
|
|
811
|
+
}, {
|
|
812
|
+
versionKey: false,
|
|
813
|
+
timestamps: true
|
|
767
814
|
});
|
|
768
|
-
RBNotificationSettingsSchema.index({ userId: 1 }, { unique: true });
|
|
769
815
|
RBNotificationSettingsSchema.index({
|
|
770
|
-
|
|
771
|
-
|
|
816
|
+
userId: 1
|
|
817
|
+
}, {
|
|
818
|
+
unique: true
|
|
772
819
|
});
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
if (!ctx.userId) return;
|
|
777
|
-
builder.can("create", "RBNotificationSettings");
|
|
778
|
-
builder.can("read", "RBNotificationSettings", { userId: ctx.userId });
|
|
779
|
-
builder.can("update", "RBNotificationSettings", { userId: ctx.userId });
|
|
780
|
-
}
|
|
781
|
-
};
|
|
782
|
-
//#endregion
|
|
783
|
-
//#region src/models/RBOAuthRequest.ts
|
|
784
|
-
var ZRBOAuthRequest = z$1.object({
|
|
785
|
-
_id: z$1.string(),
|
|
786
|
-
providerId: z$1.string(),
|
|
787
|
-
codeVerifier: z$1.string(),
|
|
788
|
-
returnTo: z$1.string().optional(),
|
|
789
|
-
createdAt: z$1.date(),
|
|
790
|
-
expiresAt: z$1.date()
|
|
820
|
+
RBNotificationSettingsSchema.index({
|
|
821
|
+
userId: 1,
|
|
822
|
+
"topicPreferences.topic": 1
|
|
791
823
|
});
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
ZRBOAuthRequest: () => ZRBOAuthRequest,
|
|
843
|
-
ZRBRtsChange: () => ZRBRtsChange,
|
|
844
|
-
ZRBRtsChangeOp: () => ZRBRtsChangeOp,
|
|
845
|
-
ZRBRtsCounter: () => ZRBRtsCounter,
|
|
846
|
-
ZRBTenant: () => ZRBTenant,
|
|
847
|
-
ZRBTenantSubscription: () => ZRBTenantSubscription,
|
|
848
|
-
ZRBTenantSubscriptionChangeDirection: () => ZRBTenantSubscriptionChangeDirection,
|
|
849
|
-
ZRBTenantSubscriptionEvent: () => ZRBTenantSubscriptionEvent,
|
|
850
|
-
ZRBTenantSubscriptionEventSource: () => ZRBTenantSubscriptionEventSource,
|
|
851
|
-
ZRBTenantSubscriptionIntervalUnit: () => ZRBTenantSubscriptionIntervalUnit,
|
|
852
|
-
ZRBTenantSubscriptionScope: () => ZRBTenantSubscriptionScope,
|
|
853
|
-
ZRBTenantSubscriptionStatus: () => ZRBTenantSubscriptionStatus,
|
|
854
|
-
ZRBTenantSubscriptionType: () => ZRBTenantSubscriptionType,
|
|
855
|
-
ZRBUploadChunk: () => ZRBUploadChunk,
|
|
856
|
-
ZRBUploadSession: () => ZRBUploadSession,
|
|
857
|
-
ZRBUploadSessionStatus: () => ZRBUploadSessionStatus,
|
|
858
|
-
ZRBUser: () => ZRBUser
|
|
824
|
+
const RBNotificationSettingsPolicy = {
|
|
825
|
+
subject: "RBNotificationSettings",
|
|
826
|
+
define: (builder, ctx) => {
|
|
827
|
+
if (!ctx.userId) return;
|
|
828
|
+
builder.can("create", "RBNotificationSettings");
|
|
829
|
+
builder.can("read", "RBNotificationSettings", {
|
|
830
|
+
userId: ctx.userId
|
|
831
|
+
});
|
|
832
|
+
builder.can("update", "RBNotificationSettings", {
|
|
833
|
+
userId: ctx.userId
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
const ZRBOAuthRequest = z.object({
|
|
838
|
+
_id: z.string(),
|
|
839
|
+
providerId: z.string(),
|
|
840
|
+
codeVerifier: z.string(),
|
|
841
|
+
returnTo: z.string().optional(),
|
|
842
|
+
createdAt: z.date(),
|
|
843
|
+
expiresAt: z.date()
|
|
844
|
+
});
|
|
845
|
+
const RBOAuthRequestSchema = new Schema$1({
|
|
846
|
+
_id: {
|
|
847
|
+
type: String,
|
|
848
|
+
required: true
|
|
849
|
+
},
|
|
850
|
+
providerId: {
|
|
851
|
+
type: String,
|
|
852
|
+
required: true,
|
|
853
|
+
index: true
|
|
854
|
+
},
|
|
855
|
+
codeVerifier: {
|
|
856
|
+
type: String,
|
|
857
|
+
required: true
|
|
858
|
+
},
|
|
859
|
+
returnTo: {
|
|
860
|
+
type: String,
|
|
861
|
+
required: false
|
|
862
|
+
},
|
|
863
|
+
createdAt: {
|
|
864
|
+
type: Date,
|
|
865
|
+
required: true,
|
|
866
|
+
default: Date.now
|
|
867
|
+
},
|
|
868
|
+
expiresAt: {
|
|
869
|
+
type: Date,
|
|
870
|
+
required: true
|
|
871
|
+
}
|
|
872
|
+
}, {
|
|
873
|
+
versionKey: false
|
|
859
874
|
});
|
|
860
|
-
|
|
861
|
-
|
|
875
|
+
RBOAuthRequestSchema.index({
|
|
876
|
+
expiresAt: 1
|
|
877
|
+
}, {
|
|
878
|
+
expireAfterSeconds: 0
|
|
879
|
+
});
|
|
880
|
+
const frameworkSchemas = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
881
|
+
__proto__: null,
|
|
882
|
+
RBNotificationPolicy,
|
|
883
|
+
RBNotificationSchema,
|
|
884
|
+
RBNotificationSettingsPolicy,
|
|
885
|
+
RBNotificationSettingsSchema,
|
|
886
|
+
RBOAuthRequestSchema,
|
|
887
|
+
RBRtsChangeSchema,
|
|
888
|
+
RBRtsCounterSchema,
|
|
889
|
+
RBTenantSchema,
|
|
890
|
+
RBTenantSubscriptionEventSchema,
|
|
891
|
+
RBTenantSubscriptionSchema,
|
|
892
|
+
RBUploadChunkSchema,
|
|
893
|
+
RBUploadSessionPolicy,
|
|
894
|
+
RBUploadSessionSchema,
|
|
895
|
+
RBUserSchema,
|
|
896
|
+
ZRBNotification,
|
|
897
|
+
ZRBNotificationDigestFrequency,
|
|
898
|
+
ZRBNotificationSettings,
|
|
899
|
+
ZRBNotificationTopicPreference,
|
|
900
|
+
ZRBOAuthRequest,
|
|
901
|
+
ZRBRtsChange,
|
|
902
|
+
ZRBRtsChangeOp,
|
|
903
|
+
ZRBRtsCounter,
|
|
904
|
+
ZRBTenant,
|
|
905
|
+
ZRBTenantSubscription,
|
|
906
|
+
ZRBTenantSubscriptionChangeDirection,
|
|
907
|
+
ZRBTenantSubscriptionEvent,
|
|
908
|
+
ZRBTenantSubscriptionEventSource,
|
|
909
|
+
ZRBTenantSubscriptionIntervalUnit,
|
|
910
|
+
ZRBTenantSubscriptionScope,
|
|
911
|
+
ZRBTenantSubscriptionStatus,
|
|
912
|
+
ZRBTenantSubscriptionType,
|
|
913
|
+
ZRBUploadChunk,
|
|
914
|
+
ZRBUploadSession,
|
|
915
|
+
ZRBUploadSessionStatus,
|
|
916
|
+
ZRBUser
|
|
917
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
862
918
|
function extendMongooseSchema(baseSchema, ...extensions) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
919
|
+
const schema = baseSchema.clone();
|
|
920
|
+
extensions.forEach((extension) => schema.add(extension));
|
|
921
|
+
return schema;
|
|
866
922
|
}
|
|
867
923
|
function omitMongooseSchemaPaths(schema, paths) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
924
|
+
const clone = schema.clone();
|
|
925
|
+
paths.forEach((path) => clone.remove(path));
|
|
926
|
+
return clone;
|
|
871
927
|
}
|
|
872
|
-
//#endregion
|
|
873
|
-
//#region src/mongoose/localizedStringField.ts
|
|
874
928
|
function localizedStringField(options) {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
929
|
+
const userGet = options?.get;
|
|
930
|
+
return {
|
|
931
|
+
...options,
|
|
932
|
+
type: Schema$1.Types.Mixed,
|
|
933
|
+
get: (value) => withLocalizedStringFallback(userGet ? userGet(value) : value)
|
|
934
|
+
};
|
|
881
935
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
};
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
};
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
};
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
};
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
};
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
};
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
936
|
+
const {
|
|
937
|
+
Schema,
|
|
938
|
+
model
|
|
939
|
+
} = mongoose;
|
|
940
|
+
class PaginationValidationError extends Error {
|
|
941
|
+
code = "invalid_pagination";
|
|
942
|
+
statusCode = 400;
|
|
943
|
+
constructor(message, options) {
|
|
944
|
+
super(message, options);
|
|
945
|
+
this.name = "PaginationValidationError";
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
const isPaginationValidationError = (error) => {
|
|
949
|
+
if (!error || typeof error !== "object") return false;
|
|
950
|
+
const anyError = error;
|
|
951
|
+
return anyError.name === "PaginationValidationError" && anyError.code === "invalid_pagination" && anyError.statusCode === 400;
|
|
952
|
+
};
|
|
953
|
+
const DISALLOWED_MONGO_FIELD_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
954
|
+
const PAGINATION_LIMIT_MAX = 128;
|
|
955
|
+
const normalizePaginationSpec = (spec) => {
|
|
956
|
+
if (!spec || typeof spec !== "object") throw new PaginationValidationError("Invalid PaginationSpec");
|
|
957
|
+
const limit = normalizeLimit(spec.limit);
|
|
958
|
+
const direction = spec.direction ?? "next";
|
|
959
|
+
if (direction !== "next" && direction !== "prev") throw new PaginationValidationError("Invalid pagination direction");
|
|
960
|
+
if (!Array.isArray(spec.sort) || spec.sort.length === 0) throw new PaginationValidationError("Invalid pagination sort");
|
|
961
|
+
const sort = spec.sort.map(({
|
|
962
|
+
field,
|
|
963
|
+
order
|
|
964
|
+
}) => ({
|
|
965
|
+
field: assertSafeMongoFieldPath(field),
|
|
966
|
+
order: normalizeOrder(order)
|
|
967
|
+
}));
|
|
968
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
969
|
+
for (const {
|
|
970
|
+
field
|
|
971
|
+
} of sort) {
|
|
972
|
+
if (seenFields.has(field)) throw new PaginationValidationError(`Duplicate pagination sort field: ${field}`);
|
|
973
|
+
seenFields.add(field);
|
|
974
|
+
}
|
|
975
|
+
const primaryOrder = sort[0]?.order;
|
|
976
|
+
if (!seenFields.has("_id")) {
|
|
977
|
+
sort.push({
|
|
978
|
+
field: "_id",
|
|
979
|
+
order: primaryOrder
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
return {
|
|
983
|
+
...spec,
|
|
984
|
+
limit,
|
|
985
|
+
direction,
|
|
986
|
+
sort
|
|
987
|
+
};
|
|
988
|
+
};
|
|
989
|
+
const normalizeLimit = (limit) => {
|
|
990
|
+
if (typeof limit !== "number" || !Number.isFinite(limit) || !Number.isInteger(limit) || limit <= 0) {
|
|
991
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
992
|
+
}
|
|
993
|
+
if (limit > PAGINATION_LIMIT_MAX) {
|
|
994
|
+
throw new PaginationValidationError("Invalid pagination limit");
|
|
995
|
+
}
|
|
996
|
+
return limit;
|
|
997
|
+
};
|
|
998
|
+
const normalizeOrder = (order) => {
|
|
999
|
+
if (order !== "asc" && order !== "desc") throw new PaginationValidationError("Invalid pagination order");
|
|
1000
|
+
return order;
|
|
1001
|
+
};
|
|
1002
|
+
const assertSafeMongoFieldPath = (field) => {
|
|
1003
|
+
if (typeof field !== "string" || field.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1004
|
+
if (field.startsWith("$")) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1005
|
+
const parts = field.split(".");
|
|
1006
|
+
for (const part of parts) {
|
|
1007
|
+
if (part.length === 0) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1008
|
+
if (DISALLOWED_MONGO_FIELD_SEGMENTS.has(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1009
|
+
if (!/^[a-zA-Z0-9_]+$/.test(part)) throw new PaginationValidationError("Invalid pagination sort field");
|
|
1010
|
+
}
|
|
1011
|
+
return field;
|
|
1012
|
+
};
|
|
1013
|
+
const encodePaginationCursor = (spec, node, options) => {
|
|
1014
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1015
|
+
const values = /* @__PURE__ */ Object.create(null);
|
|
1016
|
+
for (const {
|
|
1017
|
+
field
|
|
1018
|
+
} of normalized.sort) {
|
|
1019
|
+
const value = readFieldValue(node, field);
|
|
1020
|
+
if (typeof value === "undefined") {
|
|
1021
|
+
throw new Error(`Pagination cursor encode failed (missing field: ${field})`);
|
|
1022
|
+
}
|
|
1023
|
+
values[field] = encodeCursorValue(field, value);
|
|
1024
|
+
}
|
|
1025
|
+
const payload = {
|
|
1026
|
+
v: 1,
|
|
1027
|
+
values
|
|
1028
|
+
};
|
|
1029
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1030
|
+
const sigB64 = signCursorPayloadB64(payloadB64, options.signingSecret);
|
|
1031
|
+
return `${payloadB64}.${sigB64}`;
|
|
1032
|
+
};
|
|
1033
|
+
const decodePaginationCursor = (spec, cursor, options) => {
|
|
1034
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1035
|
+
const [payloadB64, sigB64, ...rest] = cursor.split(".");
|
|
1036
|
+
if (rest.length > 0) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1037
|
+
if (!sigB64) throw new PaginationValidationError("Invalid pagination cursor format");
|
|
1038
|
+
verifyCursorSignature(payloadB64, sigB64, options.signingSecret);
|
|
1039
|
+
const payloadRaw = Buffer.from(payloadB64, "base64url").toString("utf8");
|
|
1040
|
+
let payloadUnknown;
|
|
1041
|
+
try {
|
|
1042
|
+
payloadUnknown = JSON.parse(payloadRaw);
|
|
1043
|
+
} catch {
|
|
1044
|
+
throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1045
|
+
}
|
|
1046
|
+
if (!payloadUnknown || typeof payloadUnknown !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1047
|
+
const payload = payloadUnknown;
|
|
1048
|
+
if (payload.v !== 1) throw new PaginationValidationError("Unsupported pagination cursor version");
|
|
1049
|
+
if (!payload.values || typeof payload.values !== "object") throw new PaginationValidationError("Invalid pagination cursor payload");
|
|
1050
|
+
const decoded = /* @__PURE__ */ Object.create(null);
|
|
1051
|
+
for (const {
|
|
1052
|
+
field
|
|
1053
|
+
} of normalized.sort) {
|
|
1054
|
+
if (!Object.prototype.hasOwnProperty.call(payload.values, field)) {
|
|
1055
|
+
throw new PaginationValidationError(`Pagination cursor missing field: ${field}`);
|
|
1056
|
+
}
|
|
1057
|
+
const encodedValue = payload.values[field];
|
|
1058
|
+
decoded[field] = decodeCursorValue(field, encodedValue);
|
|
1059
|
+
}
|
|
1060
|
+
return decoded;
|
|
1061
|
+
};
|
|
1062
|
+
const signCursorPayloadB64 = (payloadB64, secret) => {
|
|
1063
|
+
return createHmac("sha256", secret).update(payloadB64, "utf8").digest("base64url");
|
|
1064
|
+
};
|
|
1065
|
+
const verifyCursorSignature = (payloadB64, sigB64, secret) => {
|
|
1066
|
+
const expectedSigB64 = signCursorPayloadB64(payloadB64, secret);
|
|
1067
|
+
const a3 = Buffer.from(sigB64, "utf8");
|
|
1068
|
+
const b3 = Buffer.from(expectedSigB64, "utf8");
|
|
1069
|
+
if (a3.length !== b3.length || !timingSafeEqual(a3, b3)) {
|
|
1070
|
+
throw new PaginationValidationError("Invalid pagination cursor signature");
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
const unwrapCursorComparableValue = (value) => {
|
|
1074
|
+
if (value === null || typeof value !== "object") return value;
|
|
1075
|
+
if (value instanceof Date || value instanceof Types.ObjectId) return value;
|
|
1076
|
+
let current = value;
|
|
1077
|
+
for (let depth = 0; depth < 4; depth += 1) {
|
|
1078
|
+
if (!current || typeof current !== "object") return current;
|
|
1079
|
+
if (current instanceof Date || current instanceof Types.ObjectId) return current;
|
|
1080
|
+
if (!("_id" in current)) return current;
|
|
1081
|
+
current = current._id;
|
|
1082
|
+
}
|
|
1083
|
+
return current;
|
|
1084
|
+
};
|
|
1085
|
+
const encodeCursorValue = (field, value) => {
|
|
1086
|
+
const comparableValue = unwrapCursorComparableValue(value);
|
|
1087
|
+
if (comparableValue === null) return null;
|
|
1088
|
+
if (field === "_id") {
|
|
1089
|
+
if (comparableValue instanceof Types.ObjectId) return {
|
|
1090
|
+
$oid: comparableValue.toHexString()
|
|
1091
|
+
};
|
|
1092
|
+
if (typeof comparableValue === "string") return comparableValue;
|
|
1093
|
+
throw new Error("Pagination cursor encode failed (_id must be an ObjectId or string)");
|
|
1094
|
+
}
|
|
1095
|
+
if (comparableValue instanceof Date) return {
|
|
1096
|
+
$date: comparableValue.toISOString()
|
|
1097
|
+
};
|
|
1098
|
+
if (typeof comparableValue === "string" || typeof comparableValue === "number" || typeof comparableValue === "boolean") {
|
|
1099
|
+
return comparableValue;
|
|
1100
|
+
}
|
|
1101
|
+
if (comparableValue instanceof Types.ObjectId) return {
|
|
1102
|
+
$oid: comparableValue.toHexString()
|
|
1103
|
+
};
|
|
1104
|
+
throw new Error(`Unsupported pagination cursor value type for field: ${field}`);
|
|
1105
|
+
};
|
|
1106
|
+
const decodeCursorValue = (field, value) => {
|
|
1107
|
+
if (value === null) return null;
|
|
1108
|
+
if (typeof value === "string") {
|
|
1109
|
+
return value;
|
|
1110
|
+
}
|
|
1111
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1112
|
+
if (!value || typeof value !== "object") throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1113
|
+
if ("$date" in value) {
|
|
1114
|
+
if (typeof value.$date !== "string") throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1115
|
+
const d3 = new Date(value.$date);
|
|
1116
|
+
if (Number.isNaN(d3.getTime())) throw new PaginationValidationError(`Invalid pagination cursor date for field: ${field}`);
|
|
1117
|
+
return d3;
|
|
1118
|
+
}
|
|
1119
|
+
if ("$oid" in value) {
|
|
1120
|
+
if (typeof value.$oid !== "string" || !Types.ObjectId.isValid(value.$oid)) {
|
|
1121
|
+
throw new PaginationValidationError(`Invalid pagination cursor ObjectId for field: ${field}`);
|
|
1122
|
+
}
|
|
1123
|
+
return new Types.ObjectId(value.$oid);
|
|
1124
|
+
}
|
|
1125
|
+
throw new PaginationValidationError(`Invalid pagination cursor value for field: ${field}`);
|
|
1126
|
+
};
|
|
1127
|
+
const readFieldValue = (node, field) => {
|
|
1128
|
+
if (!node || typeof node !== "object") return void 0;
|
|
1129
|
+
if ("get" in node && typeof node.get === "function") {
|
|
1130
|
+
return node.get(field);
|
|
1131
|
+
}
|
|
1132
|
+
return field.split(".").reduce((acc, key) => {
|
|
1133
|
+
if (!acc || typeof acc !== "object") return void 0;
|
|
1134
|
+
return acc[key];
|
|
1135
|
+
}, node);
|
|
1136
|
+
};
|
|
1137
|
+
const compileMongoPagination = (spec, options) => {
|
|
1138
|
+
const normalized = normalizePaginationSpec(spec);
|
|
1139
|
+
const mongoLimit = normalized.limit + 1;
|
|
1140
|
+
const mongoSort = toMongoSort(normalized);
|
|
1141
|
+
if (normalized.cursor == null) {
|
|
1142
|
+
return {
|
|
1143
|
+
spec: normalized,
|
|
1144
|
+
mongoFilterDelta: {},
|
|
1145
|
+
mongoSort,
|
|
1146
|
+
mongoLimit
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
if (typeof normalized.cursor !== "string" || normalized.cursor.length === 0) {
|
|
1150
|
+
throw new PaginationValidationError("Invalid pagination cursor");
|
|
1151
|
+
}
|
|
1152
|
+
const cursorValues = decodePaginationCursor(normalized, normalized.cursor, options.cursor);
|
|
1153
|
+
const mongoFilterDelta = buildKeysetFilterDelta(normalized, cursorValues);
|
|
1154
|
+
return {
|
|
1155
|
+
spec: normalized,
|
|
1156
|
+
mongoFilterDelta,
|
|
1157
|
+
mongoSort,
|
|
1158
|
+
mongoLimit
|
|
1159
|
+
};
|
|
1160
|
+
};
|
|
1161
|
+
const toMongoSort = (spec) => {
|
|
1162
|
+
const mongoSort = /* @__PURE__ */ Object.create(null);
|
|
1163
|
+
for (const {
|
|
1164
|
+
field,
|
|
1165
|
+
order
|
|
1166
|
+
} of spec.sort) {
|
|
1167
|
+
const forQueryOrder = spec.direction === "prev" ? invertOrder(order) : order;
|
|
1168
|
+
mongoSort[field] = forQueryOrder === "asc" ? 1 : -1;
|
|
1169
|
+
}
|
|
1170
|
+
return mongoSort;
|
|
1171
|
+
};
|
|
1172
|
+
const buildKeysetFilterDelta = (spec, cursorValues) => {
|
|
1173
|
+
const branches = [];
|
|
1174
|
+
for (let i = 0; i < spec.sort.length; i++) {
|
|
1175
|
+
const current = spec.sort[i];
|
|
1176
|
+
if (!current) continue;
|
|
1177
|
+
const and = [];
|
|
1178
|
+
for (let j = 0; j < i; j++) {
|
|
1179
|
+
const prev = spec.sort[j];
|
|
1180
|
+
if (!prev) continue;
|
|
1181
|
+
const eq = /* @__PURE__ */ Object.create(null);
|
|
1182
|
+
eq[prev.field] = cursorValues[prev.field];
|
|
1183
|
+
and.push(eq);
|
|
1184
|
+
}
|
|
1185
|
+
const op = getKeysetOp(current.order, spec.direction);
|
|
1186
|
+
const cmpOp = /* @__PURE__ */ Object.create(null);
|
|
1187
|
+
cmpOp[op] = cursorValues[current.field];
|
|
1188
|
+
const cmp = /* @__PURE__ */ Object.create(null);
|
|
1189
|
+
cmp[current.field] = cmpOp;
|
|
1190
|
+
and.push(cmp);
|
|
1191
|
+
branches.push(and.length === 1 ? and[0] : {
|
|
1192
|
+
$and: and
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return {
|
|
1196
|
+
$or: branches
|
|
1197
|
+
};
|
|
1198
|
+
};
|
|
1199
|
+
const getKeysetOp = (order, direction) => {
|
|
1200
|
+
if (direction === "next") return order === "asc" ? "$gt" : "$lt";
|
|
1201
|
+
return order === "asc" ? "$lt" : "$gt";
|
|
1202
|
+
};
|
|
1203
|
+
const invertOrder = (order) => {
|
|
1204
|
+
return order === "asc" ? "desc" : "asc";
|
|
1205
|
+
};
|
|
1206
|
+
const materializeMongoPagination = (compiled, fetchedNodes, options) => {
|
|
1207
|
+
const limit = compiled.spec.limit;
|
|
1208
|
+
const hasMore = fetchedNodes.length > limit;
|
|
1209
|
+
const trimmed = fetchedNodes.slice(0, limit);
|
|
1210
|
+
const nodes = compiled.spec.direction === "prev" ? trimmed.reverse() : trimmed;
|
|
1211
|
+
const hasPrevPage = compiled.spec.direction === "next" ? Boolean(compiled.spec.cursor) : hasMore;
|
|
1212
|
+
const hasNextPage = compiled.spec.direction === "next" ? hasMore : Boolean(compiled.spec.cursor);
|
|
1213
|
+
const pageInfo = {
|
|
1214
|
+
hasNextPage,
|
|
1215
|
+
hasPrevPage
|
|
1216
|
+
};
|
|
1217
|
+
if (nodes.length === 0) {
|
|
1218
|
+
return {
|
|
1219
|
+
nodes,
|
|
1220
|
+
pageInfo
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
if (hasPrevPage) {
|
|
1224
|
+
pageInfo.prevCursor = encodePaginationCursor(compiled.spec, nodes[0], options.cursor);
|
|
1225
|
+
}
|
|
1226
|
+
if (hasNextPage) {
|
|
1227
|
+
pageInfo.nextCursor = encodePaginationCursor(compiled.spec, nodes[nodes.length - 1], options.cursor);
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
nodes,
|
|
1231
|
+
pageInfo
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
1234
|
+
const MongoAdapter = {
|
|
1235
|
+
applyPagination: (query, compiled) => {
|
|
1236
|
+
query.where(compiled.mongoFilterDelta);
|
|
1237
|
+
query.sort(compiled.mongoSort);
|
|
1238
|
+
query.limit(compiled.mongoLimit);
|
|
1239
|
+
return query;
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
const paginateMongoQuery = async (query, pagination, options) => {
|
|
1243
|
+
const compiled = compileMongoPagination(pagination, {
|
|
1244
|
+
cursor: options.cursor
|
|
1245
|
+
});
|
|
1246
|
+
MongoAdapter.applyPagination(query, compiled);
|
|
1247
|
+
const fetchedNodes = await query.exec();
|
|
1248
|
+
if (!Array.isArray(fetchedNodes)) {
|
|
1249
|
+
throw new Error("paginateMongoQuery expects query.exec() to return an array");
|
|
1250
|
+
}
|
|
1251
|
+
return materializeMongoPagination(compiled, fetchedNodes, {
|
|
1252
|
+
cursor: options.cursor
|
|
1253
|
+
});
|
|
1254
|
+
};
|
|
1255
|
+
const getQueryOptions$1 = (query) => {
|
|
1256
|
+
if (!query || typeof query !== "object") return void 0;
|
|
1257
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1258
|
+
return query.getOptions();
|
|
1259
|
+
};
|
|
1260
|
+
const getPaginationFromOptions = (query) => {
|
|
1261
|
+
const options = getQueryOptions$1(query);
|
|
1262
|
+
return options?.pagination;
|
|
1263
|
+
};
|
|
1264
|
+
const getCursorFromOptions = (query) => {
|
|
1265
|
+
const options = getQueryOptions$1(query);
|
|
1266
|
+
return options?.paginationCursor;
|
|
1267
|
+
};
|
|
1268
|
+
const mongoPaginationPlugin = (schema, pluginOptions) => {
|
|
1269
|
+
schema.query.paginate = async function(pagination, options) {
|
|
1270
|
+
const spec = pagination ?? getPaginationFromOptions(this);
|
|
1271
|
+
if (!spec) throw new Error("Missing pagination spec");
|
|
1272
|
+
const cursor = options?.cursor ?? getCursorFromOptions(this) ?? pluginOptions?.cursor;
|
|
1273
|
+
if (!cursor?.signingSecret) throw new Error("Missing pagination cursor signingSecret");
|
|
1274
|
+
return await paginateMongoQuery(this, spec, {
|
|
1275
|
+
cursor
|
|
1276
|
+
});
|
|
1277
|
+
};
|
|
1278
|
+
};
|
|
1279
|
+
const buildSearchTextStage = (options) => {
|
|
1280
|
+
const index = options.index.trim();
|
|
1281
|
+
if (!index) throw new Error("Missing search index name");
|
|
1282
|
+
const query = options.query.trim();
|
|
1283
|
+
if (!query) throw new Error("Missing search query");
|
|
1284
|
+
const stage = {
|
|
1285
|
+
$search: {
|
|
1286
|
+
index,
|
|
1287
|
+
text: {
|
|
1288
|
+
query,
|
|
1289
|
+
path: options.path
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
if (options.highlightPath) {
|
|
1294
|
+
stage.$search.highlight = {
|
|
1295
|
+
path: options.highlightPath
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
return stage;
|
|
1299
|
+
};
|
|
1300
|
+
const searchMetaProjection = () => {
|
|
1301
|
+
return {
|
|
1302
|
+
score: {
|
|
1303
|
+
$meta: "searchScore"
|
|
1304
|
+
},
|
|
1305
|
+
highlights: {
|
|
1306
|
+
$meta: "searchHighlights"
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
const listResultHasIndex = (listResult, name) => {
|
|
1311
|
+
if (!listResult || typeof listResult !== "object") return false;
|
|
1312
|
+
if (!("cursor" in listResult)) return false;
|
|
1313
|
+
const cursor = listResult.cursor;
|
|
1314
|
+
if (!cursor || typeof cursor !== "object") return false;
|
|
1315
|
+
const firstBatch = cursor.firstBatch;
|
|
1316
|
+
if (!Array.isArray(firstBatch)) return false;
|
|
1317
|
+
return firstBatch.some((idx) => idx && typeof idx === "object" && "name" in idx && idx.name === name);
|
|
1318
|
+
};
|
|
1319
|
+
const isIndexAlreadyExistsError = (error) => {
|
|
1320
|
+
if (!error || typeof error !== "object") return false;
|
|
1321
|
+
const codeName = "codeName" in error ? error.codeName : void 0;
|
|
1322
|
+
if (codeName === "IndexAlreadyExists") return true;
|
|
1323
|
+
const message = "message" in error ? String(error.message ?? "") : "";
|
|
1324
|
+
return /already exists/i.test(message);
|
|
1325
|
+
};
|
|
1326
|
+
const ensureSearchIndex = async (params) => {
|
|
1327
|
+
const collection = params.collection.trim();
|
|
1328
|
+
if (!collection) throw new Error("Missing collection name");
|
|
1329
|
+
const name = params.name.trim();
|
|
1330
|
+
if (!name) throw new Error("Missing search index name");
|
|
1331
|
+
let listResult;
|
|
1332
|
+
try {
|
|
1333
|
+
listResult = await params.db.command({
|
|
1334
|
+
listSearchIndexes: collection
|
|
1335
|
+
});
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1338
|
+
throw new Error(`listSearchIndexes failed for "${collection}": ${message}`);
|
|
1339
|
+
}
|
|
1340
|
+
if (listResultHasIndex(listResult, name)) {
|
|
1341
|
+
return {
|
|
1342
|
+
created: false
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
try {
|
|
1346
|
+
await params.db.command({
|
|
1347
|
+
createSearchIndexes: collection,
|
|
1348
|
+
indexes: [{
|
|
1349
|
+
name,
|
|
1350
|
+
definition: params.definition
|
|
1351
|
+
}]
|
|
1352
|
+
});
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
if (isIndexAlreadyExistsError(error)) {
|
|
1355
|
+
return {
|
|
1356
|
+
created: false
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1360
|
+
throw new Error(`createSearchIndexes failed for "${collection}" (index "${name}"): ${message}`);
|
|
1361
|
+
}
|
|
1362
|
+
return {
|
|
1363
|
+
created: true
|
|
1364
|
+
};
|
|
1365
|
+
};
|
|
1366
|
+
const getAppName$1 = (env = process.env) => {
|
|
1367
|
+
const appName = env.APP_NAME?.trim();
|
|
1368
|
+
if (!appName) {
|
|
1369
|
+
throw new Error("Missing APP_NAME");
|
|
1370
|
+
}
|
|
1371
|
+
return appName;
|
|
1372
|
+
};
|
|
1373
|
+
const GLOBAL_DB_SUFFIX = "-global-db";
|
|
1374
|
+
const getGlobalDbName = (env = process.env) => {
|
|
1375
|
+
return `${getAppName$1(env)}${GLOBAL_DB_SUFFIX}`;
|
|
1376
|
+
};
|
|
1377
|
+
const getTenantDbName = (tenantId, env = process.env) => {
|
|
1378
|
+
return `${getAppName$1(env)}-${tenantId.trim()}-db`;
|
|
1379
|
+
};
|
|
1380
|
+
const getMongoUrl = (env = process.env) => {
|
|
1381
|
+
const explicitUrl = env.MONGODB_URL ?? env.MONGO_URL ?? env.MONGODB_URI ?? env.DB_URL;
|
|
1382
|
+
if (explicitUrl && explicitUrl.trim()) {
|
|
1383
|
+
return explicitUrl.trim();
|
|
1384
|
+
}
|
|
1385
|
+
const port = env.DB_PORT?.trim();
|
|
1386
|
+
if (!port) throw new Error("Missing Mongo connection details (MONGODB_URL/MONGO_URL/MONGODB_URI/DB_URL/DB_PORT)");
|
|
1387
|
+
const host = env.DB_HOST?.trim() || "localhost";
|
|
1388
|
+
return `mongodb://${host}:${port}`;
|
|
1389
|
+
};
|
|
1390
|
+
const connections = /* @__PURE__ */ new Map();
|
|
1391
|
+
let rootConnection = null;
|
|
1392
|
+
const CONNECTION_MAX_LISTENERS = 50;
|
|
1393
|
+
const waitForOpen = async (connection) => {
|
|
1394
|
+
if (connection.readyState === 1) return;
|
|
1395
|
+
if (connection.getMaxListeners() < CONNECTION_MAX_LISTENERS) {
|
|
1396
|
+
connection.setMaxListeners(CONNECTION_MAX_LISTENERS);
|
|
1397
|
+
}
|
|
1398
|
+
await new Promise((resolve, reject) => {
|
|
1399
|
+
connection.once("open", resolve);
|
|
1400
|
+
connection.once("error", reject);
|
|
1401
|
+
});
|
|
1402
|
+
};
|
|
1403
|
+
const ensureMongooseConnection = async (dbName) => {
|
|
1404
|
+
const normalizedDbName = dbName.trim();
|
|
1405
|
+
if (!normalizedDbName) {
|
|
1406
|
+
throw new Error("Missing dbName");
|
|
1407
|
+
}
|
|
1408
|
+
const existing = connections.get(normalizedDbName);
|
|
1409
|
+
if (existing) {
|
|
1410
|
+
await waitForOpen(existing);
|
|
1411
|
+
return existing;
|
|
1412
|
+
}
|
|
1413
|
+
if (!rootConnection) {
|
|
1414
|
+
const mongoUrl = getMongoUrl();
|
|
1415
|
+
rootConnection = mongoose.createConnection(mongoUrl, {
|
|
1416
|
+
sanitizeFilter: true,
|
|
1417
|
+
dbName: normalizedDbName
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
await waitForOpen(rootConnection);
|
|
1421
|
+
const connection = rootConnection.name === normalizedDbName ? rootConnection : rootConnection.useDb(normalizedDbName, {
|
|
1422
|
+
useCache: true
|
|
1423
|
+
});
|
|
1424
|
+
await waitForOpen(connection);
|
|
1425
|
+
connections.set(normalizedDbName, connection);
|
|
1426
|
+
return connection;
|
|
1427
|
+
};
|
|
1428
|
+
const RTS_COUNTER_ID = "rts";
|
|
1429
|
+
const EXCLUDED_MODEL_NAMES = /* @__PURE__ */ new Set(["RBRtsChange", "RBRtsCounter"]);
|
|
1430
|
+
const maxDeleteIdsRaw = process.env.RB_RTS_DELETE_LOG_MAX_IDS ?? "";
|
|
1431
|
+
const maxDeleteIds = Number.isFinite(Number(maxDeleteIdsRaw)) ? Math.max(1, Math.floor(Number(maxDeleteIdsRaw))) : 5e3;
|
|
1432
|
+
const deleteMetaByQuery = /* @__PURE__ */ new WeakMap();
|
|
1433
|
+
const hasToString = (value) => {
|
|
1434
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1435
|
+
const maybe = value;
|
|
1436
|
+
return typeof maybe.toString === "function";
|
|
1437
|
+
};
|
|
1438
|
+
const normalizeId = (id) => {
|
|
1439
|
+
if (!id) return null;
|
|
1440
|
+
if (typeof id === "string") return id;
|
|
1441
|
+
if (hasToString(id)) return id.toString();
|
|
1442
|
+
return null;
|
|
1443
|
+
};
|
|
1444
|
+
const getDbName = (db) => {
|
|
1445
|
+
if (!db || typeof db !== "object") return "";
|
|
1446
|
+
const maybe = db;
|
|
1447
|
+
const raw = maybe.name ?? maybe.db?.databaseName;
|
|
1448
|
+
return typeof raw === "string" ? raw : "";
|
|
1449
|
+
};
|
|
1450
|
+
const isGlobalDb = (db) => getDbName(db).endsWith(GLOBAL_DB_SUFFIX);
|
|
1451
|
+
const getQuerySession = (query) => {
|
|
1452
|
+
const opts = typeof query.getOptions === "function" ? query.getOptions() : void 0;
|
|
1453
|
+
if (!opts || typeof opts !== "object") return void 0;
|
|
1454
|
+
const session = opts.session;
|
|
1455
|
+
if (!session || typeof session !== "object") return void 0;
|
|
1456
|
+
return session;
|
|
1457
|
+
};
|
|
1458
|
+
const getRtsModels = (db) => {
|
|
1459
|
+
const RtsCounter = db.models.RBRtsCounter ?? db.model("RBRtsCounter", RBRtsCounterSchema);
|
|
1460
|
+
const RtsChange = db.models.RBRtsChange ?? db.model("RBRtsChange", RBRtsChangeSchema);
|
|
1461
|
+
return {
|
|
1462
|
+
RtsCounter,
|
|
1463
|
+
RtsChange
|
|
1464
|
+
};
|
|
1465
|
+
};
|
|
1466
|
+
const allocateSeqRange = async (db, count, session) => {
|
|
1467
|
+
const {
|
|
1468
|
+
RtsCounter
|
|
1469
|
+
} = getRtsModels(db);
|
|
1470
|
+
const updated = await RtsCounter.findOneAndUpdate({
|
|
1471
|
+
_id: RTS_COUNTER_ID
|
|
1472
|
+
}, {
|
|
1473
|
+
$inc: {
|
|
1474
|
+
seq: count
|
|
1475
|
+
}
|
|
1476
|
+
}, {
|
|
1477
|
+
upsert: true,
|
|
1478
|
+
returnDocument: "after",
|
|
1479
|
+
setDefaultsOnInsert: true,
|
|
1480
|
+
projection: {
|
|
1481
|
+
seq: 1
|
|
1482
|
+
},
|
|
1483
|
+
session
|
|
1484
|
+
}).lean();
|
|
1485
|
+
const end = Number(updated?.seq ?? 0);
|
|
1486
|
+
const start = end - count + 1;
|
|
1487
|
+
return {
|
|
1488
|
+
start,
|
|
1489
|
+
end
|
|
1490
|
+
};
|
|
1491
|
+
};
|
|
1492
|
+
const insertChanges = async (db, changes, session) => {
|
|
1493
|
+
if (!changes.length) return;
|
|
1494
|
+
const {
|
|
1495
|
+
RtsChange
|
|
1496
|
+
} = getRtsModels(db);
|
|
1497
|
+
const ts = /* @__PURE__ */ new Date();
|
|
1498
|
+
const docs = changes.map((c3) => ({
|
|
1499
|
+
seq: c3.seq,
|
|
1500
|
+
modelName: c3.modelName,
|
|
1501
|
+
op: c3.op,
|
|
1502
|
+
docId: c3.docId ?? void 0,
|
|
1503
|
+
ts
|
|
1504
|
+
}));
|
|
1505
|
+
if (session) {
|
|
1506
|
+
await RtsChange.insertMany(docs, {
|
|
1507
|
+
session
|
|
1508
|
+
});
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
await RtsChange.insertMany(docs);
|
|
1512
|
+
};
|
|
1513
|
+
const recordDeleteChanges = async (db, modelName, ids, session) => {
|
|
1514
|
+
const uniqueIds = Array.from(new Set(ids)).filter(Boolean);
|
|
1515
|
+
if (!uniqueIds.length) return;
|
|
1516
|
+
const {
|
|
1517
|
+
start
|
|
1518
|
+
} = await allocateSeqRange(db, uniqueIds.length, session);
|
|
1519
|
+
await insertChanges(db, uniqueIds.map((docId, idx) => ({
|
|
1520
|
+
seq: start + idx,
|
|
1521
|
+
modelName,
|
|
1522
|
+
op: "delete",
|
|
1523
|
+
docId
|
|
1524
|
+
})), session);
|
|
1525
|
+
};
|
|
1526
|
+
const recordResetModel = async (db, modelName, session) => {
|
|
1527
|
+
const {
|
|
1528
|
+
start
|
|
1529
|
+
} = await allocateSeqRange(db, 1, session);
|
|
1530
|
+
await insertChanges(db, [{
|
|
1531
|
+
seq: start,
|
|
1532
|
+
modelName,
|
|
1533
|
+
op: "reset_model"
|
|
1534
|
+
}], session);
|
|
1535
|
+
};
|
|
1536
|
+
const captureDeleteMeta = async (query, mode) => {
|
|
1537
|
+
const modelName = String(query?.model?.modelName ?? "");
|
|
1538
|
+
if (!modelName || modelName.startsWith("RB") || EXCLUDED_MODEL_NAMES.has(modelName)) return;
|
|
1539
|
+
if (isGlobalDb(query?.model?.db)) return;
|
|
1540
|
+
const filter = typeof query.getFilter === "function" ? query.getFilter() : query.getQuery?.() ?? {};
|
|
1541
|
+
const session = getQuerySession(query);
|
|
1542
|
+
const findQuery = query.model.find(filter, {
|
|
1543
|
+
_id: 1
|
|
1544
|
+
});
|
|
1545
|
+
if (session && typeof findQuery.session === "function") {
|
|
1546
|
+
findQuery.session(session);
|
|
1547
|
+
}
|
|
1548
|
+
findQuery.lean();
|
|
1549
|
+
if (mode === "one") {
|
|
1550
|
+
findQuery.limit(1);
|
|
1551
|
+
} else {
|
|
1552
|
+
findQuery.limit(maxDeleteIds + 1);
|
|
1553
|
+
}
|
|
1554
|
+
const docs = await findQuery;
|
|
1555
|
+
const ids = Array.isArray(docs) ? docs.map((d3) => normalizeId(d3?._id)).filter((id) => Boolean(id)) : [];
|
|
1556
|
+
const reset = mode === "many" && ids.length > maxDeleteIds;
|
|
1557
|
+
const trimmedIds = reset ? [] : ids;
|
|
1558
|
+
const meta = {
|
|
1559
|
+
modelName,
|
|
1560
|
+
ids: trimmedIds,
|
|
1561
|
+
reset,
|
|
1562
|
+
session
|
|
1563
|
+
};
|
|
1564
|
+
deleteMetaByQuery.set(query, meta);
|
|
1565
|
+
};
|
|
1566
|
+
const flushDeleteMeta = async (query) => {
|
|
1567
|
+
const meta = deleteMetaByQuery.get(query);
|
|
1568
|
+
deleteMetaByQuery.delete(query);
|
|
1569
|
+
if (!meta) return;
|
|
1570
|
+
const db = query?.model?.db;
|
|
1571
|
+
if (!db) return;
|
|
1572
|
+
try {
|
|
1573
|
+
if (meta.reset) {
|
|
1574
|
+
await recordResetModel(db, meta.modelName, meta.session);
|
|
1575
|
+
} else {
|
|
1576
|
+
await recordDeleteChanges(db, meta.modelName, meta.ids, meta.session);
|
|
1577
|
+
}
|
|
1578
|
+
} catch {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
const rtsChangeLogPlugin = (schema) => {
|
|
1583
|
+
schema.pre("deleteOne", {
|
|
1584
|
+
query: true,
|
|
1585
|
+
document: false
|
|
1586
|
+
}, async function() {
|
|
1587
|
+
await captureDeleteMeta(this, "one");
|
|
1588
|
+
});
|
|
1589
|
+
schema.pre("deleteMany", {
|
|
1590
|
+
query: true,
|
|
1591
|
+
document: false
|
|
1592
|
+
}, async function() {
|
|
1593
|
+
await captureDeleteMeta(this, "many");
|
|
1594
|
+
});
|
|
1595
|
+
schema.post("deleteOne", {
|
|
1596
|
+
query: true,
|
|
1597
|
+
document: false
|
|
1598
|
+
}, async function() {
|
|
1599
|
+
await flushDeleteMeta(this);
|
|
1600
|
+
});
|
|
1601
|
+
schema.post("deleteMany", {
|
|
1602
|
+
query: true,
|
|
1603
|
+
document: false
|
|
1604
|
+
}, async function() {
|
|
1605
|
+
await flushDeleteMeta(this);
|
|
1606
|
+
});
|
|
1607
|
+
schema.post("findOneAndDelete", {
|
|
1608
|
+
query: true,
|
|
1609
|
+
document: false
|
|
1610
|
+
}, async function(doc) {
|
|
1611
|
+
const modelName = String(this?.model?.modelName ?? "");
|
|
1612
|
+
if (!modelName || modelName.startsWith("RB") || EXCLUDED_MODEL_NAMES.has(modelName)) return;
|
|
1613
|
+
const db = this?.model?.db;
|
|
1614
|
+
if (!db) return;
|
|
1615
|
+
if (isGlobalDb(db)) return;
|
|
1616
|
+
const docId = normalizeId(doc?._id);
|
|
1617
|
+
if (!docId) return;
|
|
1618
|
+
try {
|
|
1619
|
+
const session = getQuerySession(this);
|
|
1620
|
+
await recordDeleteChanges(db, modelName, [docId], session);
|
|
1621
|
+
} catch {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
};
|
|
1626
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1627
|
+
const mergeMongoQuery = (left, right) => {
|
|
1628
|
+
const leftQuery = isRecord(left) ? left : {};
|
|
1629
|
+
if (Object.keys(leftQuery).length === 0) return right;
|
|
1630
|
+
if (Object.keys(right).length === 0) return leftQuery;
|
|
1631
|
+
return {
|
|
1632
|
+
$and: [leftQuery, right]
|
|
1633
|
+
};
|
|
1634
|
+
};
|
|
1635
|
+
const getQueryOptions = (query) => {
|
|
1636
|
+
if (!query || typeof query !== "object") return void 0;
|
|
1637
|
+
if (!("getOptions" in query) || typeof query.getOptions !== "function") return void 0;
|
|
1638
|
+
return query.getOptions();
|
|
1639
|
+
};
|
|
1640
|
+
const getStoredAclFromQuery = (query) => {
|
|
1641
|
+
const options = getQueryOptions(query);
|
|
1642
|
+
const raw = options?.rbAcl;
|
|
1643
|
+
if (!isRecord(raw)) return null;
|
|
1644
|
+
if (!("ability" in raw)) return null;
|
|
1645
|
+
return raw;
|
|
1646
|
+
};
|
|
1647
|
+
const getStoredAclFromAggregate = (aggregate) => {
|
|
1648
|
+
if (!aggregate || typeof aggregate !== "object") return null;
|
|
1649
|
+
const raw = aggregate.options;
|
|
1650
|
+
if (!isRecord(raw)) return null;
|
|
1651
|
+
const acl = raw.rbAcl;
|
|
1652
|
+
if (!isRecord(acl)) return null;
|
|
1653
|
+
if (!("ability" in acl)) return null;
|
|
1654
|
+
return acl;
|
|
1655
|
+
};
|
|
1656
|
+
const addQueryAclFilter = (query, action) => {
|
|
1657
|
+
const storedAcl = getStoredAclFromQuery(query);
|
|
1658
|
+
if (!storedAcl) return;
|
|
1659
|
+
const ability = storedAcl.ability;
|
|
1660
|
+
const resolvedAction = storedAcl.action ?? action;
|
|
1661
|
+
const modelName = query.model.modelName;
|
|
1662
|
+
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1663
|
+
query.and([accessQuery]);
|
|
1664
|
+
};
|
|
1665
|
+
const injectAggregateMatch = (pipeline, match) => {
|
|
1666
|
+
if (pipeline.length === 0) {
|
|
1667
|
+
pipeline.unshift({
|
|
1668
|
+
$match: match
|
|
1669
|
+
});
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
const first = pipeline[0];
|
|
1673
|
+
if (!isRecord(first)) {
|
|
1674
|
+
pipeline.unshift({
|
|
1675
|
+
$match: match
|
|
1676
|
+
});
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
if ("$geoNear" in first) {
|
|
1680
|
+
const geoNear = first.$geoNear;
|
|
1681
|
+
if (isRecord(geoNear)) {
|
|
1682
|
+
geoNear.query = mergeMongoQuery(geoNear.query, match);
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if ("$search" in first || "$vectorSearch" in first || "$searchMeta" in first) {
|
|
1687
|
+
pipeline.splice(1, 0, {
|
|
1688
|
+
$match: match
|
|
1689
|
+
});
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
pipeline.unshift({
|
|
1693
|
+
$match: match
|
|
1694
|
+
});
|
|
1695
|
+
};
|
|
1696
|
+
const addAggregateAclFilter = (aggregate, action) => {
|
|
1697
|
+
const storedAcl = getStoredAclFromAggregate(aggregate);
|
|
1698
|
+
if (!storedAcl) return;
|
|
1699
|
+
const ability = storedAcl.ability;
|
|
1700
|
+
const resolvedAction = storedAcl.action ?? action;
|
|
1701
|
+
const modelName = aggregate.model().modelName;
|
|
1702
|
+
const accessQuery = accessibleBy(ability, resolvedAction).ofType(modelName);
|
|
1703
|
+
injectAggregateMatch(aggregate.pipeline(), accessQuery);
|
|
1704
|
+
};
|
|
1705
|
+
const patchAggregateAcl = () => {
|
|
1706
|
+
const globalKey = /* @__PURE__ */ Symbol.for("@rpcbase/db/acl/mongooseAggregateAclPatched");
|
|
1707
|
+
const globalState = globalThis;
|
|
1708
|
+
if (globalState[globalKey]) return;
|
|
1709
|
+
globalState[globalKey] = true;
|
|
1710
|
+
const AggregatePrototype = mongoose.Aggregate.prototype;
|
|
1711
|
+
if (typeof AggregatePrototype.acl === "function") return;
|
|
1712
|
+
AggregatePrototype.acl = function(ability, action = "read") {
|
|
1713
|
+
this.option({
|
|
1714
|
+
rbAcl: {
|
|
1715
|
+
ability,
|
|
1716
|
+
action
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
return this;
|
|
1720
|
+
};
|
|
1721
|
+
};
|
|
1722
|
+
const createModelAclProxy = (model2, ability) => {
|
|
1723
|
+
return new Proxy(model2, {
|
|
1724
|
+
get(target, prop, receiver) {
|
|
1725
|
+
if (prop === "acl") {
|
|
1726
|
+
return () => {
|
|
1727
|
+
throw new Error(`Model "${target.modelName}" is already ACL-scoped. Do not call .acl(...) again.`);
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1731
|
+
if (typeof value !== "function") return value;
|
|
1732
|
+
return (...args) => {
|
|
1733
|
+
const result = Reflect.apply(value, target, args);
|
|
1734
|
+
if (result && typeof result === "object" && "acl" in result && typeof result.acl === "function") {
|
|
1735
|
+
return result.acl(ability);
|
|
1736
|
+
}
|
|
1737
|
+
return result;
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
};
|
|
1742
|
+
const mongooseAclPlugin = (schema) => {
|
|
1743
|
+
patchAggregateAcl();
|
|
1744
|
+
schema.query.acl = function(ability, action) {
|
|
1745
|
+
this.setOptions({
|
|
1746
|
+
rbAcl: {
|
|
1747
|
+
ability,
|
|
1748
|
+
action
|
|
1749
|
+
}
|
|
1750
|
+
});
|
|
1751
|
+
return this;
|
|
1752
|
+
};
|
|
1753
|
+
schema.statics.acl = function(ability) {
|
|
1754
|
+
return createModelAclProxy(this, ability);
|
|
1755
|
+
};
|
|
1756
|
+
schema.pre("aggregate", function() {
|
|
1757
|
+
addAggregateAclFilter(this, "read");
|
|
1758
|
+
});
|
|
1759
|
+
schema.pre("countDocuments", function() {
|
|
1760
|
+
addQueryAclFilter(this, "read");
|
|
1761
|
+
});
|
|
1762
|
+
schema.pre("deleteMany", function() {
|
|
1763
|
+
addQueryAclFilter(this, "delete");
|
|
1764
|
+
});
|
|
1765
|
+
schema.pre("deleteOne", function() {
|
|
1766
|
+
addQueryAclFilter(this, "delete");
|
|
1767
|
+
});
|
|
1768
|
+
schema.pre("distinct", function() {
|
|
1769
|
+
addQueryAclFilter(this, "read");
|
|
1770
|
+
});
|
|
1771
|
+
schema.pre("find", function() {
|
|
1772
|
+
addQueryAclFilter(this, "read");
|
|
1773
|
+
});
|
|
1774
|
+
schema.pre("findOne", function() {
|
|
1775
|
+
addQueryAclFilter(this, "read");
|
|
1776
|
+
});
|
|
1777
|
+
schema.pre("findOneAndDelete", function() {
|
|
1778
|
+
addQueryAclFilter(this, "delete");
|
|
1779
|
+
});
|
|
1780
|
+
schema.pre("findOneAndReplace", function() {
|
|
1781
|
+
addQueryAclFilter(this, "update");
|
|
1782
|
+
});
|
|
1783
|
+
schema.pre("findOneAndUpdate", function() {
|
|
1784
|
+
addQueryAclFilter(this, "update");
|
|
1785
|
+
});
|
|
1786
|
+
schema.pre("replaceOne", function() {
|
|
1787
|
+
addQueryAclFilter(this, "update");
|
|
1788
|
+
});
|
|
1789
|
+
schema.pre("updateMany", function() {
|
|
1790
|
+
addQueryAclFilter(this, "update");
|
|
1791
|
+
});
|
|
1792
|
+
schema.pre("updateOne", function() {
|
|
1793
|
+
addQueryAclFilter(this, "update");
|
|
1794
|
+
});
|
|
1795
|
+
};
|
|
1796
|
+
let cachedModels = null;
|
|
1797
|
+
const DEFAULT_GLOBAL_RB_MODEL_NAMES_SET = /* @__PURE__ */ new Set(["RBUser", "RBTenant", "RBOAuthRequest"]);
|
|
1798
|
+
const assertSchema = (exportName, value) => {
|
|
1799
|
+
if (value instanceof mongoose.Schema) return value;
|
|
1800
|
+
throw new Error([`Expected ${exportName} to be an instance of mongoose.Schema, but it was not.`, "rpcbase supports mongoose 9+ only.", "Fix: ensure the project is using mongoose 9.x and that all packages resolve the same mongoose instance (try `npm ls mongoose`)."].join(" "));
|
|
1801
|
+
};
|
|
1802
|
+
const getFrameworkSchemaForModelName = (modelName) => {
|
|
1803
|
+
const exportName = `${modelName}Schema`;
|
|
1804
|
+
const value = frameworkSchemas[exportName];
|
|
1805
|
+
if (!(value instanceof mongoose.Schema)) return null;
|
|
1806
|
+
return value;
|
|
1807
|
+
};
|
|
1808
|
+
const applyTenantPlugins = (schema) => {
|
|
1809
|
+
schema.plugin(accessibleRecordsPlugin);
|
|
1810
|
+
schema.plugin(mongooseAclPlugin);
|
|
1811
|
+
schema.plugin(mongoPaginationPlugin);
|
|
1812
|
+
schema.plugin(rtsChangeLogPlugin);
|
|
1813
|
+
};
|
|
1814
|
+
const registerSchema = (target, other, modelName, schema, scope) => {
|
|
1815
|
+
if (target[modelName] || other[modelName]) {
|
|
1816
|
+
throw new Error(`Duplicate model name "${modelName}" across tenant/global scopes`);
|
|
1817
|
+
}
|
|
1818
|
+
target[modelName] = schema;
|
|
1819
|
+
};
|
|
1820
|
+
const buildSchemasFromModules = (modules) => Object.entries(modules).filter(([key]) => key.endsWith("Schema")).map(([key, schemaValue]) => {
|
|
1821
|
+
const schema = assertSchema(key, schemaValue);
|
|
1822
|
+
const modelName = key.replace(/Schema$/, "");
|
|
1823
|
+
return {
|
|
1824
|
+
modelName,
|
|
1825
|
+
schema
|
|
1826
|
+
};
|
|
1827
|
+
});
|
|
1828
|
+
const registerModels = ({
|
|
1829
|
+
tenant,
|
|
1830
|
+
global
|
|
1831
|
+
}, options = {}) => {
|
|
1832
|
+
registerPoliciesFromModules(frameworkSchemas);
|
|
1833
|
+
registerPoliciesFromModules(tenant);
|
|
1834
|
+
const tenantSchemas = {};
|
|
1835
|
+
const globalSchemas = {};
|
|
1836
|
+
const allowReservedRbModelNames = options.allowReservedRbModelNames === true;
|
|
1837
|
+
for (const {
|
|
1838
|
+
modelName,
|
|
1839
|
+
schema
|
|
1840
|
+
} of buildSchemasFromModules(frameworkSchemas)) {
|
|
1841
|
+
if (DEFAULT_GLOBAL_RB_MODEL_NAMES_SET.has(modelName)) {
|
|
1842
|
+
const cloned = schema.clone();
|
|
1843
|
+
registerSchema(globalSchemas, tenantSchemas, modelName, cloned);
|
|
1844
|
+
} else {
|
|
1845
|
+
const cloned = schema.clone();
|
|
1846
|
+
applyTenantPlugins(cloned);
|
|
1847
|
+
registerSchema(tenantSchemas, globalSchemas, modelName, cloned);
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
for (const {
|
|
1851
|
+
modelName,
|
|
1852
|
+
schema
|
|
1853
|
+
} of buildSchemasFromModules(tenant)) {
|
|
1854
|
+
if (modelName === "RBUser" || modelName === "RBTenant") {
|
|
1855
|
+
throw new Error(`Invalid tenant model name "${modelName}". RBUser/RBTenant are global models.`);
|
|
1856
|
+
}
|
|
1857
|
+
if (modelName.startsWith("RB")) {
|
|
1858
|
+
const frameworkSchema = getFrameworkSchemaForModelName(modelName);
|
|
1859
|
+
if (frameworkSchema && schema === frameworkSchema) continue;
|
|
1860
|
+
if (!allowReservedRbModelNames) {
|
|
1861
|
+
throw new Error(`Invalid tenant model name "${modelName}". RB* models are reserved for rpcbase.`);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const cloned = schema.clone();
|
|
1865
|
+
applyTenantPlugins(cloned);
|
|
1866
|
+
registerSchema(tenantSchemas, globalSchemas, modelName, cloned);
|
|
1867
|
+
}
|
|
1868
|
+
for (const {
|
|
1869
|
+
modelName,
|
|
1870
|
+
schema
|
|
1871
|
+
} of buildSchemasFromModules(global ?? {})) {
|
|
1872
|
+
if (modelName.startsWith("RB")) {
|
|
1873
|
+
const frameworkSchema = getFrameworkSchemaForModelName(modelName);
|
|
1874
|
+
if (frameworkSchema && schema === frameworkSchema) continue;
|
|
1875
|
+
if (!allowReservedRbModelNames) {
|
|
1876
|
+
throw new Error(`Invalid global model name "${modelName}". RB* models are reserved for rpcbase.`);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const cloned = schema.clone();
|
|
1880
|
+
registerSchema(globalSchemas, tenantSchemas, modelName, cloned);
|
|
1881
|
+
}
|
|
1882
|
+
const allSchemas = {
|
|
1883
|
+
...globalSchemas,
|
|
1884
|
+
...tenantSchemas
|
|
1885
|
+
};
|
|
1886
|
+
for (const [modelName, schema] of Object.entries(allSchemas)) {
|
|
1887
|
+
if (!mongoose.models[modelName]) {
|
|
1888
|
+
mongoose.model(modelName, schema);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
cachedModels = {
|
|
1892
|
+
tenant: {
|
|
1893
|
+
...cachedModels?.tenant ?? {},
|
|
1894
|
+
...tenantSchemas
|
|
1895
|
+
},
|
|
1896
|
+
global: {
|
|
1897
|
+
...cachedModels?.global ?? {},
|
|
1898
|
+
...globalSchemas
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
};
|
|
1902
|
+
const getRegisteredModels = (scope) => {
|
|
1903
|
+
if (!cachedModels) {
|
|
1904
|
+
throw new Error("Models not registered. Call createModels(...) once at startup (or import your models module) before using models.get.");
|
|
1905
|
+
}
|
|
1906
|
+
return cachedModels[scope];
|
|
1907
|
+
};
|
|
1908
|
+
const loadModelFromDb = async (modelName, dbName, scope) => {
|
|
1909
|
+
const schemas = getRegisteredModels(scope);
|
|
1910
|
+
const schema = schemas[modelName];
|
|
1911
|
+
assert(schema, `Model ${modelName} not registered. Available models: ${Object.keys(schemas).join(", ")}`);
|
|
1912
|
+
const modelConnection = await ensureMongooseConnection(dbName);
|
|
1913
|
+
if (!modelConnection.models[modelName]) {
|
|
1914
|
+
modelConnection.model(modelName, schema);
|
|
1915
|
+
}
|
|
1916
|
+
return modelConnection.models[modelName];
|
|
1917
|
+
};
|
|
1918
|
+
const normalizeTenantId$1 = (value) => {
|
|
1919
|
+
if (typeof value !== "string") return null;
|
|
1920
|
+
const tenantId = value.trim();
|
|
1921
|
+
return tenantId || null;
|
|
1922
|
+
};
|
|
1923
|
+
const getTenantIdFromLoadModelCtx = (ctx) => {
|
|
1924
|
+
const tenantId = normalizeTenantId$1(ctx.tenantId) ?? normalizeTenantId$1(ctx.req?.session?.user?.currentTenantId);
|
|
1925
|
+
assert(tenantId, "Tenant ID is missing from ctx (expected ctx.tenantId or ctx.req.session.user.currentTenantId)");
|
|
1926
|
+
return tenantId;
|
|
1927
|
+
};
|
|
1928
|
+
const models = {
|
|
1929
|
+
register: registerModels,
|
|
1930
|
+
getUnsafe: async (modelName, ctx) => {
|
|
1931
|
+
const tenantId = getTenantIdFromLoadModelCtx(ctx);
|
|
1932
|
+
const dbName = getTenantDbName(tenantId);
|
|
1933
|
+
return loadModelFromDb(modelName, dbName, "tenant");
|
|
1934
|
+
},
|
|
1935
|
+
get: async (modelName, ctx) => {
|
|
1936
|
+
const model2 = await models.getUnsafe(modelName, ctx);
|
|
1937
|
+
const resolvedAbility = ctx.ability;
|
|
1938
|
+
const isProtected = hasRegisteredPolicy(modelName);
|
|
1939
|
+
if (!isProtected) {
|
|
1940
|
+
return model2;
|
|
1941
|
+
}
|
|
1942
|
+
if (!resolvedAbility) {
|
|
1943
|
+
throw new Error(`Model "${modelName}" is ACL-protected. Set ctx.ability or use models.getUnsafe(...) explicitly.`);
|
|
1944
|
+
}
|
|
1945
|
+
if (typeof model2.acl !== "function") return model2;
|
|
1946
|
+
return model2.acl(resolvedAbility);
|
|
1947
|
+
},
|
|
1948
|
+
getGlobal: async (modelName, ctx) => {
|
|
1949
|
+
const dbName = getGlobalDbName();
|
|
1950
|
+
return loadModelFromDb(modelName, dbName, "global");
|
|
1951
|
+
}
|
|
1952
|
+
};
|
|
1953
|
+
const createModels = (modules, options) => {
|
|
1954
|
+
registerModels(modules, options);
|
|
1955
|
+
const get = (async (modelNameOrNames, ctx) => {
|
|
1956
|
+
if (Array.isArray(modelNameOrNames)) {
|
|
1957
|
+
return Promise.all(modelNameOrNames.map((modelName) => models.get(modelName, ctx)));
|
|
1958
|
+
}
|
|
1959
|
+
return models.get(modelNameOrNames, ctx);
|
|
1960
|
+
});
|
|
1961
|
+
const getUnsafe = (async (modelNameOrNames, ctx) => {
|
|
1962
|
+
if (Array.isArray(modelNameOrNames)) {
|
|
1963
|
+
return Promise.all(modelNameOrNames.map((modelName) => models.getUnsafe(modelName, ctx)));
|
|
1964
|
+
}
|
|
1965
|
+
return models.getUnsafe(modelNameOrNames, ctx);
|
|
1966
|
+
});
|
|
1967
|
+
const getGlobal = (async (modelNameOrNames, ctx) => {
|
|
1968
|
+
if (Array.isArray(modelNameOrNames)) {
|
|
1969
|
+
return Promise.all(modelNameOrNames.map((modelName) => models.getGlobal(modelName, ctx)));
|
|
1970
|
+
}
|
|
1971
|
+
return models.getGlobal(modelNameOrNames, ctx);
|
|
1972
|
+
});
|
|
1973
|
+
return {
|
|
1974
|
+
register: (nextModules, nextOptions) => registerModels(nextModules, nextOptions),
|
|
1975
|
+
get,
|
|
1976
|
+
getUnsafe,
|
|
1977
|
+
getGlobal
|
|
1978
|
+
};
|
|
1979
|
+
};
|
|
1980
|
+
const getAppName = () => {
|
|
1981
|
+
const appName = process.env.APP_NAME?.trim();
|
|
1982
|
+
assert(appName, "Missing APP_NAME");
|
|
1983
|
+
return appName;
|
|
1984
|
+
};
|
|
1985
|
+
const normalizeTenantId = (tenantId) => {
|
|
1986
|
+
const normalized = tenantId.trim();
|
|
1987
|
+
assert(normalized, "Tenant ID is missing");
|
|
1988
|
+
return normalized;
|
|
1989
|
+
};
|
|
1990
|
+
const getTenantFilesystemDbName = (tenantId) => `${getAppName()}-${normalizeTenantId(tenantId)}-filesystem-db`;
|
|
1991
|
+
const getTenantFilesystemDb = async (tenantId) => ensureMongooseConnection(getTenantFilesystemDbName(tenantId));
|
|
1992
|
+
const getTenantFilesystemDbFromCtx = async (ctx) => {
|
|
1993
|
+
const tenantId = getTenantIdFromLoadModelCtx(ctx);
|
|
1994
|
+
return getTenantFilesystemDb(tenantId);
|
|
1995
|
+
};
|
|
1996
|
+
const buildTenantLoadModelCtx = (tenantId) => ({
|
|
1997
|
+
req: {
|
|
1998
|
+
session: {
|
|
1999
|
+
user: {
|
|
2000
|
+
currentTenantId: tenantId
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
1652
2004
|
});
|
|
1653
|
-
var registerModels = ({ tenant, global }, options = {}) => {
|
|
1654
|
-
registerPoliciesFromModules(models_exports);
|
|
1655
|
-
registerPoliciesFromModules(tenant);
|
|
1656
|
-
const tenantSchemas = {};
|
|
1657
|
-
const globalSchemas = {};
|
|
1658
|
-
const allowReservedRbModelNames = options.allowReservedRbModelNames === true;
|
|
1659
|
-
for (const { modelName, schema } of buildSchemasFromModules(models_exports)) if (DEFAULT_GLOBAL_RB_MODEL_NAMES_SET.has(modelName)) registerSchema(globalSchemas, tenantSchemas, modelName, schema.clone(), "global");
|
|
1660
|
-
else {
|
|
1661
|
-
const cloned = schema.clone();
|
|
1662
|
-
applyTenantPlugins(cloned);
|
|
1663
|
-
registerSchema(tenantSchemas, globalSchemas, modelName, cloned, "tenant");
|
|
1664
|
-
}
|
|
1665
|
-
for (const { modelName, schema } of buildSchemasFromModules(tenant)) {
|
|
1666
|
-
if (modelName === "RBUser" || modelName === "RBTenant") throw new Error(`Invalid tenant model name "${modelName}". RBUser/RBTenant are global models.`);
|
|
1667
|
-
if (modelName.startsWith("RB")) {
|
|
1668
|
-
const frameworkSchema = getFrameworkSchemaForModelName(modelName);
|
|
1669
|
-
if (frameworkSchema && schema === frameworkSchema) continue;
|
|
1670
|
-
if (!allowReservedRbModelNames) throw new Error(`Invalid tenant model name "${modelName}". RB* models are reserved for rpcbase.`);
|
|
1671
|
-
}
|
|
1672
|
-
const cloned = schema.clone();
|
|
1673
|
-
applyTenantPlugins(cloned);
|
|
1674
|
-
registerSchema(tenantSchemas, globalSchemas, modelName, cloned, "tenant");
|
|
1675
|
-
}
|
|
1676
|
-
for (const { modelName, schema } of buildSchemasFromModules(global ?? {})) {
|
|
1677
|
-
if (modelName.startsWith("RB")) {
|
|
1678
|
-
const frameworkSchema = getFrameworkSchemaForModelName(modelName);
|
|
1679
|
-
if (frameworkSchema && schema === frameworkSchema) continue;
|
|
1680
|
-
if (!allowReservedRbModelNames) throw new Error(`Invalid global model name "${modelName}". RB* models are reserved for rpcbase.`);
|
|
1681
|
-
}
|
|
1682
|
-
registerSchema(globalSchemas, tenantSchemas, modelName, schema.clone(), "global");
|
|
1683
|
-
}
|
|
1684
|
-
const allSchemas = {
|
|
1685
|
-
...globalSchemas,
|
|
1686
|
-
...tenantSchemas
|
|
1687
|
-
};
|
|
1688
|
-
for (const [modelName, schema] of Object.entries(allSchemas)) if (!mongoose$1.models[modelName]) mongoose$1.model(modelName, schema);
|
|
1689
|
-
cachedModels = {
|
|
1690
|
-
tenant: {
|
|
1691
|
-
...cachedModels?.tenant ?? {},
|
|
1692
|
-
...tenantSchemas
|
|
1693
|
-
},
|
|
1694
|
-
global: {
|
|
1695
|
-
...cachedModels?.global ?? {},
|
|
1696
|
-
...globalSchemas
|
|
1697
|
-
}
|
|
1698
|
-
};
|
|
1699
|
-
};
|
|
1700
|
-
var getRegisteredModels = (scope) => {
|
|
1701
|
-
if (!cachedModels) throw new Error("Models not registered. Call createModels(...) once at startup (or import your models module) before using models.get.");
|
|
1702
|
-
return cachedModels[scope];
|
|
1703
|
-
};
|
|
1704
|
-
//#endregion
|
|
1705
|
-
//#region src/modelsApi.ts
|
|
1706
|
-
var loadModelFromDb = async (modelName, dbName, scope) => {
|
|
1707
|
-
const schemas = getRegisteredModels(scope);
|
|
1708
|
-
const schema = schemas[modelName];
|
|
1709
|
-
assert(schema, `Model ${modelName} not registered. Available models: ${Object.keys(schemas).join(", ")}`);
|
|
1710
|
-
const modelConnection = await ensureMongooseConnection(dbName);
|
|
1711
|
-
if (!modelConnection.models[modelName]) modelConnection.model(modelName, schema);
|
|
1712
|
-
return modelConnection.models[modelName];
|
|
1713
|
-
};
|
|
1714
|
-
var normalizeTenantId$1 = (value) => {
|
|
1715
|
-
if (typeof value !== "string") return null;
|
|
1716
|
-
return value.trim() || null;
|
|
1717
|
-
};
|
|
1718
|
-
var getTenantIdFromLoadModelCtx = (ctx) => {
|
|
1719
|
-
const tenantId = normalizeTenantId$1(ctx.tenantId) ?? normalizeTenantId$1(ctx.req?.session?.user?.currentTenantId);
|
|
1720
|
-
assert(tenantId, "Tenant ID is missing from ctx (expected ctx.tenantId or ctx.req.session.user.currentTenantId)");
|
|
1721
|
-
return tenantId;
|
|
1722
|
-
};
|
|
1723
|
-
var models = {
|
|
1724
|
-
register: registerModels,
|
|
1725
|
-
getUnsafe: async (modelName, ctx) => {
|
|
1726
|
-
return loadModelFromDb(modelName, getTenantDbName(getTenantIdFromLoadModelCtx(ctx)), "tenant");
|
|
1727
|
-
},
|
|
1728
|
-
get: async (modelName, ctx) => {
|
|
1729
|
-
const model = await models.getUnsafe(modelName, ctx);
|
|
1730
|
-
const resolvedAbility = ctx.ability;
|
|
1731
|
-
if (!hasRegisteredPolicy(modelName)) return model;
|
|
1732
|
-
if (!resolvedAbility) throw new Error(`Model "${modelName}" is ACL-protected. Set ctx.ability or use models.getUnsafe(...) explicitly.`);
|
|
1733
|
-
if (typeof model.acl !== "function") return model;
|
|
1734
|
-
return model.acl(resolvedAbility);
|
|
1735
|
-
},
|
|
1736
|
-
getGlobal: async (modelName, ctx) => {
|
|
1737
|
-
return loadModelFromDb(modelName, getGlobalDbName(), "global");
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
1740
|
-
//#endregion
|
|
1741
|
-
//#region src/createModels.ts
|
|
1742
|
-
var createModels = (modules, options) => {
|
|
1743
|
-
registerModels(modules, options);
|
|
1744
|
-
const get = (async (modelNameOrNames, ctx) => {
|
|
1745
|
-
if (Array.isArray(modelNameOrNames)) return Promise.all(modelNameOrNames.map((modelName) => models.get(modelName, ctx)));
|
|
1746
|
-
return models.get(modelNameOrNames, ctx);
|
|
1747
|
-
});
|
|
1748
|
-
const getUnsafe = (async (modelNameOrNames, ctx) => {
|
|
1749
|
-
if (Array.isArray(modelNameOrNames)) return Promise.all(modelNameOrNames.map((modelName) => models.getUnsafe(modelName, ctx)));
|
|
1750
|
-
return models.getUnsafe(modelNameOrNames, ctx);
|
|
1751
|
-
});
|
|
1752
|
-
const getGlobal = (async (modelNameOrNames, ctx) => {
|
|
1753
|
-
if (Array.isArray(modelNameOrNames)) return Promise.all(modelNameOrNames.map((modelName) => models.getGlobal(modelName, ctx)));
|
|
1754
|
-
return models.getGlobal(modelNameOrNames, ctx);
|
|
1755
|
-
});
|
|
1756
|
-
return {
|
|
1757
|
-
register: (nextModules, nextOptions) => registerModels(nextModules, nextOptions),
|
|
1758
|
-
get,
|
|
1759
|
-
getUnsafe,
|
|
1760
|
-
getGlobal
|
|
1761
|
-
};
|
|
1762
|
-
};
|
|
1763
|
-
//#endregion
|
|
1764
|
-
//#region src/tenantFilesystemDb.ts
|
|
1765
|
-
var getAppName = () => {
|
|
1766
|
-
const appName = process.env.APP_NAME?.trim();
|
|
1767
|
-
assert(appName, "Missing APP_NAME");
|
|
1768
|
-
return appName;
|
|
1769
|
-
};
|
|
1770
|
-
var normalizeTenantId = (tenantId) => {
|
|
1771
|
-
const normalized = tenantId.trim();
|
|
1772
|
-
assert(normalized, "Tenant ID is missing");
|
|
1773
|
-
return normalized;
|
|
1774
|
-
};
|
|
1775
|
-
var getTenantFilesystemDbName = (tenantId) => `${getAppName()}-${normalizeTenantId(tenantId)}-filesystem-db`;
|
|
1776
|
-
var getTenantFilesystemDb = async (tenantId) => ensureMongooseConnection(getTenantFilesystemDbName(tenantId));
|
|
1777
|
-
var getTenantFilesystemDbFromCtx = async (ctx) => {
|
|
1778
|
-
return getTenantFilesystemDb(getTenantIdFromLoadModelCtx(ctx));
|
|
1779
|
-
};
|
|
1780
|
-
//#endregion
|
|
1781
|
-
//#region src/transactions.ts
|
|
1782
|
-
var buildTenantLoadModelCtx = (tenantId) => ({ req: { session: { user: { currentTenantId: tenantId } } } });
|
|
1783
2005
|
async function withTransaction(scope, fn, options) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2006
|
+
const normalizedTenantId = (() => {
|
|
2007
|
+
if (typeof scope === "string") return scope.trim();
|
|
2008
|
+
if ("tenantId" in scope) return scope.tenantId.trim();
|
|
2009
|
+
return getTenantIdFromLoadModelCtx(scope.ctx);
|
|
2010
|
+
})();
|
|
2011
|
+
if (!normalizedTenantId) throw new Error("Tenant ID is missing");
|
|
2012
|
+
const tenantDbName = getTenantDbName(normalizedTenantId);
|
|
2013
|
+
const globalDbName = getGlobalDbName();
|
|
2014
|
+
const filesystemDbName = getTenantFilesystemDbName(normalizedTenantId);
|
|
2015
|
+
const tenantDb = await ensureMongooseConnection(tenantDbName);
|
|
2016
|
+
const globalDb = await ensureMongooseConnection(globalDbName);
|
|
2017
|
+
const filesystemDb = await ensureMongooseConnection(filesystemDbName);
|
|
2018
|
+
const session = await tenantDb.startSession();
|
|
2019
|
+
const tenantCtx = typeof scope === "object" && "ctx" in scope ? scope.ctx : buildTenantLoadModelCtx(normalizedTenantId);
|
|
2020
|
+
const globalCtx = {
|
|
2021
|
+
req: {
|
|
2022
|
+
session: null
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
try {
|
|
2026
|
+
return await session.withTransaction(async () => fn({
|
|
2027
|
+
tenantId: normalizedTenantId,
|
|
2028
|
+
session,
|
|
2029
|
+
ctx: {
|
|
2030
|
+
tenant: tenantCtx,
|
|
2031
|
+
global: globalCtx
|
|
2032
|
+
},
|
|
2033
|
+
db: {
|
|
2034
|
+
tenant: tenantDb,
|
|
2035
|
+
global: globalDb,
|
|
2036
|
+
filesystem: filesystemDb
|
|
2037
|
+
}
|
|
2038
|
+
}), options);
|
|
2039
|
+
} finally {
|
|
2040
|
+
await session.endSession();
|
|
2041
|
+
}
|
|
1816
2042
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2043
|
+
export {
|
|
2044
|
+
E as E164_PHONE_OR_EMPTY_REGEX,
|
|
2045
|
+
a2 as E164_PHONE_REGEX,
|
|
2046
|
+
L as LANGUAGE_CODE_REGEX,
|
|
2047
|
+
PaginationValidationError,
|
|
2048
|
+
RBNotificationPolicy,
|
|
2049
|
+
RBNotificationSchema,
|
|
2050
|
+
RBNotificationSettingsPolicy,
|
|
2051
|
+
RBNotificationSettingsSchema,
|
|
2052
|
+
RBOAuthRequestSchema,
|
|
2053
|
+
RBRtsChangeSchema,
|
|
2054
|
+
RBRtsCounterSchema,
|
|
2055
|
+
RBTenantSchema,
|
|
2056
|
+
RBTenantSubscriptionEventSchema,
|
|
2057
|
+
RBTenantSubscriptionSchema,
|
|
2058
|
+
RBUploadChunkSchema,
|
|
2059
|
+
RBUploadSessionPolicy,
|
|
2060
|
+
RBUploadSessionSchema,
|
|
2061
|
+
RBUserSchema,
|
|
2062
|
+
Schema,
|
|
2063
|
+
ZRBNotification,
|
|
2064
|
+
ZRBNotificationDigestFrequency,
|
|
2065
|
+
ZRBNotificationSettings,
|
|
2066
|
+
ZRBNotificationTopicPreference,
|
|
2067
|
+
ZRBOAuthRequest,
|
|
2068
|
+
ZRBRtsChange,
|
|
2069
|
+
ZRBRtsChangeOp,
|
|
2070
|
+
ZRBRtsCounter,
|
|
2071
|
+
ZRBTenant,
|
|
2072
|
+
ZRBTenantSubscription,
|
|
2073
|
+
ZRBTenantSubscriptionChangeDirection,
|
|
2074
|
+
ZRBTenantSubscriptionEvent,
|
|
2075
|
+
ZRBTenantSubscriptionEventSource,
|
|
2076
|
+
ZRBTenantSubscriptionIntervalUnit,
|
|
2077
|
+
ZRBTenantSubscriptionScope,
|
|
2078
|
+
ZRBTenantSubscriptionStatus,
|
|
2079
|
+
ZRBTenantSubscriptionType,
|
|
2080
|
+
ZRBUploadChunk,
|
|
2081
|
+
ZRBUploadSession,
|
|
2082
|
+
ZRBUploadSessionStatus,
|
|
2083
|
+
ZRBUser,
|
|
2084
|
+
b as buildAbility,
|
|
2085
|
+
a as buildAbilityFromSession,
|
|
2086
|
+
b2 as buildLocaleFallbackChain,
|
|
2087
|
+
buildSearchTextStage,
|
|
2088
|
+
c as can,
|
|
2089
|
+
createModels,
|
|
2090
|
+
ensureSearchIndex,
|
|
2091
|
+
extendMongooseSchema,
|
|
2092
|
+
e2 as extendZod,
|
|
2093
|
+
g as getAccessibleByQuery,
|
|
2094
|
+
d as getRegisteredPolicies,
|
|
2095
|
+
getTenantFilesystemDb,
|
|
2096
|
+
getTenantFilesystemDbFromCtx,
|
|
2097
|
+
getTenantFilesystemDbName,
|
|
2098
|
+
getTenantIdFromLoadModelCtx,
|
|
2099
|
+
e as getTenantRolesFromSessionUser,
|
|
2100
|
+
hasRegisteredPolicy,
|
|
2101
|
+
isPaginationValidationError,
|
|
2102
|
+
localizedStringField,
|
|
2103
|
+
m as makeZE164Phone,
|
|
2104
|
+
model,
|
|
2105
|
+
models,
|
|
2106
|
+
mongoPaginationPlugin,
|
|
2107
|
+
default2 as mongoose,
|
|
2108
|
+
omitMongooseSchemaPaths,
|
|
2109
|
+
registerPoliciesFromModules,
|
|
2110
|
+
f as registerPolicy,
|
|
2111
|
+
r as resolveLocalizedString,
|
|
2112
|
+
searchMetaProjection,
|
|
2113
|
+
withLocalizedStringFallback,
|
|
2114
|
+
withTransaction,
|
|
2115
|
+
z2 as z,
|
|
2116
|
+
c2 as zE164Phone,
|
|
2117
|
+
d2 as zI18nString,
|
|
2118
|
+
f2 as zLocalizedString
|
|
2119
|
+
};
|
|
2120
|
+
//# sourceMappingURL=index.js.map
|