@classytic/mongokit 3.39.0 → 3.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{PaginationEngine-DQ7beTCR.mjs → PaginationEngine-Dvl8jcRY.mjs} +33 -17
- package/dist/{Repository-D7fDpyFx.mjs → Repository-BMgPSkqZ.mjs} +279 -625
- package/dist/{Repository-IxI983_m.d.mts → Repository-CR8Y9g2E.d.mts} +57 -32
- package/dist/actions/index.mjs +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.mjs +1 -1
- package/dist/{create-repository-DOe9E_5F.mjs → create-repository-BzBUMvd1.mjs} +2 -2
- package/dist/{create-repository-CRXjDa3N.d.mts → create-repository-CAle1MSU.d.mts} +4 -4
- package/dist/idempotency/index.d.mts +23 -0
- package/dist/idempotency/index.mjs +134 -0
- package/dist/index.d.mts +58 -32
- package/dist/index.mjs +285 -675
- package/dist/indexes/verify-indexes.d.mts +14 -1
- package/dist/indexes/verify-indexes.mjs +54 -7
- package/dist/{multi-tenant.plugin-B-tzNeYL.d.mts → multi-tenant.plugin-CBgCo9im.d.mts} +18 -2
- package/dist/outbox/index.d.mts +1 -1
- package/dist/outbox/index.mjs +6 -4
- package/dist/pagination/PaginationEngine.mjs +1 -1
- package/dist/{plugin-options-BuvtOSZf.d.mts → plugin-options-Zk46lD6F.d.mts} +15 -1
- package/dist/plugins/index.d.mts +8 -3
- package/dist/plugins/index.mjs +98 -25
- package/dist/{repository-ZbLtL7Nk.d.mts → repository-odl03582.d.mts} +20 -2
- package/dist/sync/index.d.mts +40 -0
- package/dist/sync/index.mjs +334 -0
- package/dist/tenant/index.mjs +2 -0
- package/dist/testkit/index.d.mts +2 -2
- package/dist/testkit/index.mjs +1 -1
- package/dist/{timestamp.plugin-B5eiaItQ.mjs → timestamp.plugin-DF0gJB6p.mjs} +86 -2
- package/dist/{to-plain-B8ufk3Qz.d.mts → to-plain-k7SjNlSS.d.mts} +2 -2
- package/dist/transaction-B5L9glO0.mjs +533 -0
- package/dist/{update-c_01A0if.mjs → update-DG0fCwRJ.mjs} +168 -11
- package/dist/utils/index.d.mts +1 -1
- package/dist/{validation-chain.plugin-Bju1D1Sn.mjs → validation-chain.plugin-BLY2gLEk.mjs} +192 -279
- package/dist/{validation-chain.plugin-DvrLJIbJ.d.mts → validation-chain.plugin-DYp9E7K2.d.mts} +25 -3
- package/package.json +13 -4
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { i as withTransaction } from "../transaction-B5L9glO0.mjs";
|
|
2
|
+
import { Schema } from "mongoose";
|
|
3
|
+
//#region src/sync/index.ts
|
|
4
|
+
function createChangeLogModels(connection, options = {}) {
|
|
5
|
+
const { collection = "sync_changes", modelName = "SyncChange" } = options;
|
|
6
|
+
const entriesName = modelName;
|
|
7
|
+
const counterName = `${modelName}Counter`;
|
|
8
|
+
return {
|
|
9
|
+
entries: connection.models[entriesName] ?? connection.model(entriesName, (() => {
|
|
10
|
+
const schema = new Schema({
|
|
11
|
+
seq: {
|
|
12
|
+
type: Number,
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
scope: {
|
|
16
|
+
type: String,
|
|
17
|
+
required: true
|
|
18
|
+
},
|
|
19
|
+
docId: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
op: {
|
|
24
|
+
type: String,
|
|
25
|
+
enum: ["upsert", "delete"],
|
|
26
|
+
required: true
|
|
27
|
+
},
|
|
28
|
+
version: {
|
|
29
|
+
type: Number,
|
|
30
|
+
required: true
|
|
31
|
+
},
|
|
32
|
+
doc: { type: Schema.Types.Mixed },
|
|
33
|
+
tenantId: { type: String },
|
|
34
|
+
at: {
|
|
35
|
+
type: Date,
|
|
36
|
+
required: true
|
|
37
|
+
}
|
|
38
|
+
}, {
|
|
39
|
+
collection,
|
|
40
|
+
versionKey: false,
|
|
41
|
+
minimize: false
|
|
42
|
+
});
|
|
43
|
+
schema.index({ seq: 1 }, {
|
|
44
|
+
unique: true,
|
|
45
|
+
name: "feed_order"
|
|
46
|
+
});
|
|
47
|
+
schema.index({
|
|
48
|
+
tenantId: 1,
|
|
49
|
+
scope: 1,
|
|
50
|
+
seq: 1
|
|
51
|
+
}, { name: "tenant_scope_feed" });
|
|
52
|
+
return schema;
|
|
53
|
+
})()),
|
|
54
|
+
counter: connection.models[counterName] ?? connection.model(counterName, new Schema({
|
|
55
|
+
_id: {
|
|
56
|
+
type: String,
|
|
57
|
+
required: true
|
|
58
|
+
},
|
|
59
|
+
seq: {
|
|
60
|
+
type: Number,
|
|
61
|
+
required: true
|
|
62
|
+
}
|
|
63
|
+
}, {
|
|
64
|
+
collection: `${collection}_counter`,
|
|
65
|
+
versionKey: false
|
|
66
|
+
})),
|
|
67
|
+
feed: collection
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Fixed-width, so the opaque cursor also sorts as text. */
|
|
71
|
+
const toCursor = (seq) => String(seq).padStart(16, "0");
|
|
72
|
+
const fromCursor = (cursor) => {
|
|
73
|
+
if (cursor === "") return 0;
|
|
74
|
+
const seq = Number(cursor);
|
|
75
|
+
if (!Number.isSafeInteger(seq) || seq < 0) throw new Error(`[mongokit:sync] "${cursor}" is not a cursor from this feed`);
|
|
76
|
+
return seq;
|
|
77
|
+
};
|
|
78
|
+
const DEFAULT_LIMIT = 500;
|
|
79
|
+
function createChangeLogStore(models) {
|
|
80
|
+
const { entries, counter, feed } = models;
|
|
81
|
+
const sessionOf = (options) => options?.session ? { session: options.session } : {};
|
|
82
|
+
const filterOf = (after, options = {}) => {
|
|
83
|
+
const { tenantId, scopes, sharedScopes } = options;
|
|
84
|
+
const tenant = tenantId === void 0 ? {} : sharedScopes?.length ? { $or: [{ tenantId }, { scope: { $in: [...sharedScopes] } }] } : { tenantId };
|
|
85
|
+
return {
|
|
86
|
+
seq: { $gt: after },
|
|
87
|
+
...tenant,
|
|
88
|
+
...scopes ? { scope: { $in: [...scopes] } } : {}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const toEntry = (row) => ({
|
|
92
|
+
scope: row.scope,
|
|
93
|
+
docId: row.docId,
|
|
94
|
+
op: row.op,
|
|
95
|
+
version: row.version,
|
|
96
|
+
...row.doc !== void 0 && row.doc !== null ? { doc: row.doc } : {},
|
|
97
|
+
...typeof row.tenantId === "string" ? { tenantId: row.tenantId } : {},
|
|
98
|
+
at: row.at,
|
|
99
|
+
cursor: toCursor(row.seq)
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
async append(entry, options) {
|
|
103
|
+
const session = sessionOf(options);
|
|
104
|
+
const seq = (await counter.findOneAndUpdate({ _id: feed }, { $inc: { seq: 1 } }, {
|
|
105
|
+
upsert: true,
|
|
106
|
+
returnDocument: "after",
|
|
107
|
+
lean: true,
|
|
108
|
+
...session
|
|
109
|
+
}).exec()).seq;
|
|
110
|
+
const at = /* @__PURE__ */ new Date();
|
|
111
|
+
await entries.create([{
|
|
112
|
+
seq,
|
|
113
|
+
scope: entry.scope,
|
|
114
|
+
docId: entry.docId,
|
|
115
|
+
op: entry.op,
|
|
116
|
+
version: entry.version,
|
|
117
|
+
...entry.op === "upsert" && entry.doc !== void 0 ? { doc: entry.doc } : {},
|
|
118
|
+
...entry.tenantId !== void 0 ? { tenantId: entry.tenantId } : {},
|
|
119
|
+
at
|
|
120
|
+
}], session);
|
|
121
|
+
return {
|
|
122
|
+
...entry,
|
|
123
|
+
at,
|
|
124
|
+
cursor: toCursor(seq)
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
async since(cursor, options = {}) {
|
|
128
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
129
|
+
const rows = await entries.find(filterOf(fromCursor(cursor), options)).sort({ seq: 1 }).limit(limit + 1).lean().exec();
|
|
130
|
+
const page = rows.slice(0, limit).map(toEntry);
|
|
131
|
+
const last = page[page.length - 1];
|
|
132
|
+
return {
|
|
133
|
+
changes: page,
|
|
134
|
+
cursor: last ? last.cursor : cursor,
|
|
135
|
+
hasMore: rows.length > limit
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
async latestCursor(options = {}) {
|
|
139
|
+
const row = await entries.findOne(filterOf(0, options)).sort({ seq: -1 }).select({ seq: 1 }).lean().exec();
|
|
140
|
+
return row?.seq ? toCursor(row.seq) : "";
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function createCommandStreamModels(connection, options = {}) {
|
|
145
|
+
const { prefix = "sync", modelName = "SyncStream" } = options;
|
|
146
|
+
const model = (name, schema) => connection.models[name] ?? connection.model(name, schema());
|
|
147
|
+
return {
|
|
148
|
+
streams: model(modelName, () => new Schema({
|
|
149
|
+
_id: {
|
|
150
|
+
type: String,
|
|
151
|
+
required: true
|
|
152
|
+
},
|
|
153
|
+
tenantId: {
|
|
154
|
+
type: String,
|
|
155
|
+
required: true
|
|
156
|
+
},
|
|
157
|
+
processed: {
|
|
158
|
+
type: Number,
|
|
159
|
+
required: true
|
|
160
|
+
}
|
|
161
|
+
}, {
|
|
162
|
+
collection: `${prefix}_streams`,
|
|
163
|
+
versionKey: false
|
|
164
|
+
})),
|
|
165
|
+
verdicts: model(`${modelName}Verdict`, () => {
|
|
166
|
+
const schema = new Schema({
|
|
167
|
+
_id: {
|
|
168
|
+
type: String,
|
|
169
|
+
required: true
|
|
170
|
+
},
|
|
171
|
+
stream: {
|
|
172
|
+
type: String,
|
|
173
|
+
required: true
|
|
174
|
+
},
|
|
175
|
+
seq: {
|
|
176
|
+
type: Number,
|
|
177
|
+
required: true
|
|
178
|
+
},
|
|
179
|
+
outcome: {
|
|
180
|
+
type: String,
|
|
181
|
+
enum: [
|
|
182
|
+
"applied",
|
|
183
|
+
"rejected",
|
|
184
|
+
"blocked",
|
|
185
|
+
"resolved"
|
|
186
|
+
],
|
|
187
|
+
required: true
|
|
188
|
+
},
|
|
189
|
+
code: { type: String },
|
|
190
|
+
fingerprint: {
|
|
191
|
+
type: String,
|
|
192
|
+
required: true
|
|
193
|
+
},
|
|
194
|
+
body: { type: String },
|
|
195
|
+
dependsOn: {
|
|
196
|
+
type: [String],
|
|
197
|
+
default: void 0
|
|
198
|
+
},
|
|
199
|
+
resolution: {
|
|
200
|
+
type: {
|
|
201
|
+
action: String,
|
|
202
|
+
by: String,
|
|
203
|
+
at: String,
|
|
204
|
+
_id: false
|
|
205
|
+
},
|
|
206
|
+
default: void 0
|
|
207
|
+
}
|
|
208
|
+
}, {
|
|
209
|
+
collection: `${prefix}_verdicts`,
|
|
210
|
+
versionKey: false
|
|
211
|
+
});
|
|
212
|
+
schema.index({
|
|
213
|
+
stream: 1,
|
|
214
|
+
seq: 1
|
|
215
|
+
}, {
|
|
216
|
+
unique: true,
|
|
217
|
+
name: "stream_seq"
|
|
218
|
+
});
|
|
219
|
+
schema.index({
|
|
220
|
+
dependsOn: 1,
|
|
221
|
+
outcome: 1
|
|
222
|
+
}, {
|
|
223
|
+
name: "blocked_on",
|
|
224
|
+
partialFilterExpression: { outcome: "blocked" }
|
|
225
|
+
});
|
|
226
|
+
return schema;
|
|
227
|
+
}),
|
|
228
|
+
aliases: model(`${modelName}Alias`, () => {
|
|
229
|
+
const schema = new Schema({
|
|
230
|
+
tenantId: {
|
|
231
|
+
type: String,
|
|
232
|
+
required: true
|
|
233
|
+
},
|
|
234
|
+
localId: {
|
|
235
|
+
type: String,
|
|
236
|
+
required: true
|
|
237
|
+
},
|
|
238
|
+
serverId: {
|
|
239
|
+
type: String,
|
|
240
|
+
required: true
|
|
241
|
+
}
|
|
242
|
+
}, {
|
|
243
|
+
collection: `${prefix}_aliases`,
|
|
244
|
+
versionKey: false
|
|
245
|
+
});
|
|
246
|
+
schema.index({
|
|
247
|
+
tenantId: 1,
|
|
248
|
+
localId: 1
|
|
249
|
+
}, {
|
|
250
|
+
unique: true,
|
|
251
|
+
name: "tenant_local"
|
|
252
|
+
});
|
|
253
|
+
return schema;
|
|
254
|
+
})
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The mongo {@link CommandStreamStore} (replica set). Proven by `runCommandStreamStoreConformance`.
|
|
259
|
+
* Concurrent transactions on one stream write-conflict on its document and `withTransaction`
|
|
260
|
+
* retries the loser, which then reads the winner's watermark.
|
|
261
|
+
*/
|
|
262
|
+
function createCommandStreamStore(connection, models, changes) {
|
|
263
|
+
const { streams, verdicts, aliases } = models;
|
|
264
|
+
const toVerdict = ({ _id, code, ...rest }) => ({
|
|
265
|
+
commandId: _id,
|
|
266
|
+
...rest,
|
|
267
|
+
...code == null ? {} : { code }
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
changes,
|
|
271
|
+
transaction: (work) => withTransaction(connection, (session) => work({ session })),
|
|
272
|
+
async stream({ session }, id) {
|
|
273
|
+
const row = await streams.findById(id).session(session).lean().exec();
|
|
274
|
+
return row ? {
|
|
275
|
+
tenantId: row.tenantId,
|
|
276
|
+
processed: row.processed
|
|
277
|
+
} : null;
|
|
278
|
+
},
|
|
279
|
+
async advance({ session }, id, tenantId, from, to) {
|
|
280
|
+
if (from === 0) {
|
|
281
|
+
if (await streams.exists({ _id: id }).session(session)) return false;
|
|
282
|
+
await streams.create([{
|
|
283
|
+
_id: id,
|
|
284
|
+
tenantId,
|
|
285
|
+
processed: to
|
|
286
|
+
}], { session });
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
return (await streams.updateOne({
|
|
290
|
+
_id: id,
|
|
291
|
+
processed: from
|
|
292
|
+
}, { $set: { processed: to } }, { session }).exec()).modifiedCount === 1;
|
|
293
|
+
},
|
|
294
|
+
async verdict({ session }, commandId) {
|
|
295
|
+
const row = await verdicts.findById(commandId).session(session).lean().exec();
|
|
296
|
+
return row ? toVerdict(row) : null;
|
|
297
|
+
},
|
|
298
|
+
async record({ session }, { commandId, ...rest }) {
|
|
299
|
+
await verdicts.replaceOne({ _id: commandId }, rest, {
|
|
300
|
+
upsert: true,
|
|
301
|
+
session
|
|
302
|
+
}).exec();
|
|
303
|
+
},
|
|
304
|
+
async blockedOn({ session }, commandId) {
|
|
305
|
+
return (await verdicts.find({
|
|
306
|
+
outcome: "blocked",
|
|
307
|
+
dependsOn: commandId
|
|
308
|
+
}).sort({ seq: 1 }).session(session).lean().exec()).map(toVerdict);
|
|
309
|
+
},
|
|
310
|
+
async alias({ session }, { tenantId, localId, serverId }) {
|
|
311
|
+
const existing = await aliases.findOne({
|
|
312
|
+
tenantId,
|
|
313
|
+
localId
|
|
314
|
+
}).session(session).lean().exec();
|
|
315
|
+
if (existing) {
|
|
316
|
+
if (existing.serverId !== serverId) throw new Error(`[mongokit:sync] "${localId}" already names "${existing.serverId}"`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await aliases.create([{
|
|
320
|
+
tenantId,
|
|
321
|
+
localId,
|
|
322
|
+
serverId
|
|
323
|
+
}], { session });
|
|
324
|
+
},
|
|
325
|
+
async aliasOf({ session }, tenantId, localId) {
|
|
326
|
+
return (await aliases.findOne({
|
|
327
|
+
tenantId,
|
|
328
|
+
localId
|
|
329
|
+
}).session(session).lean().exec())?.serverId ?? null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
export { createChangeLogModels, createChangeLogStore, createCommandStreamModels, createCommandStreamStore };
|
package/dist/tenant/index.mjs
CHANGED
|
@@ -64,6 +64,8 @@ function declareIndexes(schema, tenant, declarations) {
|
|
|
64
64
|
*/
|
|
65
65
|
function injectTenantField(schema, tenant, options = {}) {
|
|
66
66
|
for (const declaration of options.indexes ?? []) assertIndexDeclaration(declaration, tenant);
|
|
67
|
+
const declaredNames = new Set(indexEntries(schema).map(([, o]) => o?.name).filter((n) => typeof n === "string"));
|
|
68
|
+
for (const name of options.skipIndexes ?? []) if (!declaredNames.has(name)) throw new Error(`[mongokit/tenant] skipIndexes names '${name}', but no index on this schema has that name — rename the exemption with the index, or the index silently gets the tenant prefix`);
|
|
67
69
|
const isObjectId = tenant.fieldType === "objectId";
|
|
68
70
|
const scoped = isScoped(tenant);
|
|
69
71
|
schema.add({ [tenant.tenantField]: {
|
package/dist/testkit/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as CreateRepositoryConfig } from "../create-repository-
|
|
1
|
+
import { r as Repository } from "../Repository-CR8Y9g2E.mjs";
|
|
2
|
+
import { n as CreateRepositoryConfig } from "../create-repository-CAle1MSU.mjs";
|
|
3
3
|
import { Connection, Model, Schema } from "mongoose";
|
|
4
4
|
import { MongoMemoryReplSet, MongoMemoryServer } from "mongodb-memory-server";
|
|
5
5
|
//#region src/testkit/index.d.ts
|
package/dist/testkit/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as ALL_OPERATIONS, o as OP_REGISTRY, t as HOOK_PRIORITY } from "./Repository-BMgPSkqZ.mjs";
|
|
2
2
|
import { n as warn } from "./logger-K7dPmu2U.mjs";
|
|
3
3
|
import { throwIfAborted, withRetry } from "@classytic/repo-core/repository";
|
|
4
4
|
import mongoose from "mongoose";
|
|
@@ -754,6 +754,33 @@ async function getNextSequence(counterKey, increment = 1, connection, session) {
|
|
|
754
754
|
return result.seq;
|
|
755
755
|
}
|
|
756
756
|
/**
|
|
757
|
+
* Current value of a counter, or `null` when it has never been created.
|
|
758
|
+
* `null` and `0` differ: only a missing counter needs seeding.
|
|
759
|
+
*/
|
|
760
|
+
async function readSequence(counterKey, connection) {
|
|
761
|
+
const doc = await getCounterModel(connection).findById(counterKey).lean();
|
|
762
|
+
return doc ? doc.seq : null;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Raise a counter to at least `floor` — never lowers it. Atomic (`$max` +
|
|
766
|
+
* upsert), so concurrent seeders converge on the highest floor.
|
|
767
|
+
*
|
|
768
|
+
* Use before a counter's first increment when ids were already issued under
|
|
769
|
+
* another key (e.g. moving from a global to a per-tenant sequence); otherwise
|
|
770
|
+
* the new counter restarts at 1 and re-issues taken ids.
|
|
771
|
+
*
|
|
772
|
+
* @returns The counter's value after the raise.
|
|
773
|
+
*/
|
|
774
|
+
async function ensureSequenceAtLeast(counterKey, floor, connection) {
|
|
775
|
+
if (!Number.isSafeInteger(floor) || floor < 0) throw new Error(`ensureSequenceAtLeast: floor must be a non-negative integer, got ${floor}`);
|
|
776
|
+
const result = await getCounterModel(connection).findOneAndUpdate({ _id: counterKey }, { $max: { seq: floor } }, {
|
|
777
|
+
upsert: true,
|
|
778
|
+
returnDocument: "after"
|
|
779
|
+
});
|
|
780
|
+
if (!result) throw new Error(`Failed to seed counter '${counterKey}'`);
|
|
781
|
+
return result.seq;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
757
784
|
* Generator: Simple sequential counter.
|
|
758
785
|
* Produces IDs like `INV-0001`, `INV-0002`, etc.
|
|
759
786
|
*
|
|
@@ -930,6 +957,62 @@ function customIdPlugin(options) {
|
|
|
930
957
|
};
|
|
931
958
|
}
|
|
932
959
|
//#endregion
|
|
960
|
+
//#region src/utils/scope.ts
|
|
961
|
+
/** Tenant context keys forwarded even when no tenant plugin declared one. */
|
|
962
|
+
const CONVENTIONAL_TENANT_KEYS = ["organizationId", "tenantId"];
|
|
963
|
+
const ATTRIBUTION_KEYS = [
|
|
964
|
+
"session",
|
|
965
|
+
"user",
|
|
966
|
+
"userId",
|
|
967
|
+
"requestId"
|
|
968
|
+
];
|
|
969
|
+
/** Where a repository records the tenant context keys its plugins read. */
|
|
970
|
+
const TENANT_KEYS = Symbol.for("@classytic/mongokit/tenant-context-keys");
|
|
971
|
+
/**
|
|
972
|
+
* Record that `repo` resolves its tenant from `contextKey`. Called by
|
|
973
|
+
* `multiTenantPlugin` at bind, so forwarding knows a custom key (`branchId`)
|
|
974
|
+
* without the host restating it.
|
|
975
|
+
*/
|
|
976
|
+
function declareTenantContextKey(repo, contextKey) {
|
|
977
|
+
const holder = repo;
|
|
978
|
+
if (!holder[TENANT_KEYS]) Object.defineProperty(holder, TENANT_KEYS, {
|
|
979
|
+
value: /* @__PURE__ */ new Set(),
|
|
980
|
+
enumerable: false
|
|
981
|
+
});
|
|
982
|
+
holder[TENANT_KEYS]?.add(contextKey);
|
|
983
|
+
}
|
|
984
|
+
/** Every tenant context key relevant to a call spanning these repositories. */
|
|
985
|
+
function tenantContextKeysOf(...repos) {
|
|
986
|
+
const keys = new Set(CONVENTIONAL_TENANT_KEYS);
|
|
987
|
+
for (const repo of repos) for (const key of repo?.[TENANT_KEYS] ?? []) keys.add(key);
|
|
988
|
+
return [...keys];
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* The scope to hand a second repository call made on behalf of `source`
|
|
992
|
+
* (a hook's `RepositoryContext`, or a host's request context).
|
|
993
|
+
*
|
|
994
|
+
* Absent values are omitted, never written as `undefined`, so spreading the
|
|
995
|
+
* result into an options bag cannot erase a value set there.
|
|
996
|
+
*/
|
|
997
|
+
function forwardScope(source, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
|
|
998
|
+
if (!source) return {};
|
|
999
|
+
const out = {};
|
|
1000
|
+
let hasTenant = false;
|
|
1001
|
+
for (const key of tenantKeys) {
|
|
1002
|
+
const value = source[key];
|
|
1003
|
+
if (value === void 0) continue;
|
|
1004
|
+
out[key] = value;
|
|
1005
|
+
if (value !== null && value !== "") hasTenant = true;
|
|
1006
|
+
}
|
|
1007
|
+
if (!hasTenant && source.bypassTenant === true) out.bypassTenant = true;
|
|
1008
|
+
for (const key of ATTRIBUTION_KEYS) if (source[key] !== void 0) out[key] = source[key];
|
|
1009
|
+
return out;
|
|
1010
|
+
}
|
|
1011
|
+
/** Every key `forwardScope` governs. Other forwarding helpers must not copy these themselves. */
|
|
1012
|
+
function isScopeKey(key, tenantKeys = CONVENTIONAL_TENANT_KEYS) {
|
|
1013
|
+
return key === "bypassTenant" || tenantKeys.includes(key) || ATTRIBUTION_KEYS.includes(key);
|
|
1014
|
+
}
|
|
1015
|
+
//#endregion
|
|
933
1016
|
//#region src/plugins/multi-tenant.plugin.ts
|
|
934
1017
|
/**
|
|
935
1018
|
* Multi-Tenant Plugin
|
|
@@ -983,6 +1066,7 @@ function multiTenantPlugin(options = {}) {
|
|
|
983
1066
|
return {
|
|
984
1067
|
name: "multi-tenant",
|
|
985
1068
|
apply(repo) {
|
|
1069
|
+
declareTenantContextKey(repo, contextKey);
|
|
986
1070
|
const allOps = [...ALL_OPERATIONS.map((op) => ({
|
|
987
1071
|
op,
|
|
988
1072
|
policyKey: OP_REGISTRY[op].policyKey
|
|
@@ -1500,4 +1584,4 @@ function timestampPlugin() {
|
|
|
1500
1584
|
};
|
|
1501
1585
|
}
|
|
1502
1586
|
//#endregion
|
|
1503
|
-
export {
|
|
1587
|
+
export { AuditTrailQuery as _, declareTenantContextKey as a, auditLogPlugin as b, tenantContextKeysOf as c, ensureSequenceAtLeast as d, getNextSequence as f, cachePlugin as g, sequentialId as h, multiTenantPlugin as i, customIdPlugin as l, readSequence as m, softDeletePlugin as n, forwardScope as o, prefixedId as p, adminBypass$1 as r, isScopeKey as s, timestampPlugin as t, dateSequentialId as u, auditTrailPlugin as v, ensureAuditTrailReady as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { v as UserContext } from "./repository-
|
|
2
|
-
import { r as FieldPreset } from "./plugin-options-
|
|
1
|
+
import { v as UserContext } from "./repository-odl03582.mjs";
|
|
2
|
+
import { r as FieldPreset } from "./plugin-options-Zk46lD6F.mjs";
|
|
3
3
|
import mongoose, { Schema } from "mongoose";
|
|
4
4
|
import { CacheAdapter } from "@classytic/repo-core/cache";
|
|
5
5
|
import { HttpError } from "@classytic/repo-core/errors";
|