@nxgt/janus-mongo 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +275 -226
- package/dist/index.js.map +14 -6
- package/dist/relations/collection.d.ts +32 -0
- package/dist/relations/collection.d.ts.map +1 -0
- package/dist/relations/context.d.ts +13 -0
- package/dist/relations/context.d.ts.map +1 -0
- package/dist/relations/index.d.ts +27 -0
- package/dist/relations/index.d.ts.map +1 -0
- package/dist/relations/reads.d.ts +9 -0
- package/dist/relations/reads.d.ts.map +1 -0
- package/dist/relations/tuples.d.ts +5 -0
- package/dist/relations/tuples.d.ts.map +1 -0
- package/dist/relations/writes.d.ts +7 -0
- package/dist/relations/writes.d.ts.map +1 -0
- package/dist/{stores.d.ts → stores/index.d.ts} +1 -1
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/records.d.ts +20 -0
- package/dist/stores/records.d.ts.map +1 -0
- package/dist/stores/sessions.d.ts +4 -0
- package/dist/stores/sessions.d.ts.map +1 -0
- package/dist/stores/tokens.d.ts +4 -0
- package/dist/stores/tokens.d.ts.map +1 -0
- package/dist/stores/users.d.ts +4 -0
- package/dist/stores/users.d.ts.map +1 -0
- package/dist/translate.d.ts +2 -0
- package/dist/translate.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/relations.d.ts +0 -54
- package/dist/relations.d.ts.map +0 -1
- package/dist/stores.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ bun add @nxgt/janus-mongo @nxgt/janus @nxgt/mongo mongodb zod
|
|
|
37
37
|
bun add -d typescript
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Every peer is required: `@nxgt/janus`, `@nxgt/mongo` (`>=0.17 <1`), `mongodb`
|
|
40
|
+
Every peer is required: `@nxgt/janus`, `@nxgt/mongo` (`>=0.17.0 <1`), `mongodb`
|
|
41
41
|
(7), `zod` (4.6.5 or later, which `@nxgt/mongo` defines collections with) and `typescript`
|
|
42
42
|
(6). `@nxgt/janus` is a **peer**, never a dependency: this package defines no
|
|
43
43
|
error class and throws the peer's own, so `instanceof StoreFailure` holds in
|
package/dist/index.js
CHANGED
|
@@ -89,16 +89,11 @@ var tokens = defineCollection({
|
|
|
89
89
|
});
|
|
90
90
|
var janusCollections = [users, sessions, tokens];
|
|
91
91
|
|
|
92
|
-
// src/relations.ts
|
|
92
|
+
// src/relations/index.ts
|
|
93
93
|
import {
|
|
94
|
-
isSubjectSet
|
|
95
|
-
} from "@nxgt/janus";
|
|
96
|
-
import {
|
|
97
|
-
defineCollection as defineCollection2,
|
|
98
94
|
getCollection,
|
|
99
95
|
syncCollections
|
|
100
96
|
} from "@nxgt/mongo";
|
|
101
|
-
import { z as z2 } from "zod";
|
|
102
97
|
|
|
103
98
|
// src/translate.ts
|
|
104
99
|
import { JanusError, StoreConflict, StoreFailure } from "@nxgt/janus";
|
|
@@ -135,11 +130,17 @@ function takenLogin(duplicate) {
|
|
|
135
130
|
function loginTaken(operation, { type, login }, cause) {
|
|
136
131
|
return new StoreConflict("login", `${operation}: the login is taken by another ${type}`, { login, userType: type, operation, cause });
|
|
137
132
|
}
|
|
133
|
+
function refuseDuplicate(operation, duplicate) {
|
|
134
|
+
const taken = takenLogin(duplicate);
|
|
135
|
+
return taken === null ? unexpectedDuplicate("users", operation, duplicate) : loginTaken(operation, taken, duplicate);
|
|
136
|
+
}
|
|
138
137
|
function unexpectedDuplicate(slot, operation, duplicate) {
|
|
139
138
|
return new StoreFailure(`${slot}.${operation}: a duplicate key on ${Object.keys(duplicate.keyPattern ?? {}).join(", ") || "an unknown index"}, which this adapter never writes on purpose`, { slot, operation, cause: duplicate });
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
// src/relations.ts
|
|
141
|
+
// src/relations/collection.ts
|
|
142
|
+
import { defineCollection as defineCollection2 } from "@nxgt/mongo";
|
|
143
|
+
import { z as z2 } from "zod";
|
|
143
144
|
var relations = defineCollection2({
|
|
144
145
|
name: "relations",
|
|
145
146
|
schema: z2.object({
|
|
@@ -171,82 +172,14 @@ var relations = defineCollection2({
|
|
|
171
172
|
}
|
|
172
173
|
]
|
|
173
174
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}).toArray()).map((document) => document._id.subject);
|
|
183
|
-
return {
|
|
184
|
-
write: ({ add = [], remove = [] }) => run$("write", async () => {
|
|
185
|
-
const removals = remove.map(keyOf);
|
|
186
|
-
const additions = add.map(keyOf);
|
|
187
|
-
const apply = async (session) => {
|
|
188
|
-
if (removals.length > 0) {
|
|
189
|
-
await collection.deleteMany({ _id: { $in: removals } }, session === undefined ? {} : { session });
|
|
190
|
-
}
|
|
191
|
-
if (additions.length > 0) {
|
|
192
|
-
await collection.bulkWrite(additions.map((key) => ({
|
|
193
|
-
replaceOne: {
|
|
194
|
-
filter: { _id: key },
|
|
195
|
-
replacement: { _id: key },
|
|
196
|
-
upsert: true
|
|
197
|
-
}
|
|
198
|
-
})), session === undefined ? {} : { session });
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
if (removals.length + additions.length <= 1)
|
|
202
|
-
return apply();
|
|
203
|
-
const session = db.client.startSession();
|
|
204
|
-
try {
|
|
205
|
-
session.startTransaction();
|
|
206
|
-
await apply(session);
|
|
207
|
-
await session.commitTransaction();
|
|
208
|
-
} finally {
|
|
209
|
-
await session.endSession();
|
|
210
|
-
}
|
|
211
|
-
}),
|
|
212
|
-
has: (tuple) => run$("has", async () => await collection.findOne({ _id: keyOf(tuple) }, { projection: { _id: 1 } }) !== null),
|
|
213
|
-
findSubjectSets: (object, relation) => run$("findSubjectSets", async () => (await holding(object, relation, true)).map((subject) => ({
|
|
214
|
-
type: subject.type,
|
|
215
|
-
id: subject.id,
|
|
216
|
-
relation: subject.relation
|
|
217
|
-
}))),
|
|
218
|
-
findEntities: (object, relation) => run$("findEntities", async () => (await holding(object, relation, false)).map((subject) => ({ type: subject.type, id: subject.id }))),
|
|
219
|
-
findObjects: ({ type, relation, subject, after, limit }) => run$("findObjects", async () => {
|
|
220
|
-
const filter = {
|
|
221
|
-
"_id.subject.type": subject.type,
|
|
222
|
-
"_id.subject.id": subject.id,
|
|
223
|
-
"_id.subject.relation": isSubjectSet(subject) ? subject.relation : null,
|
|
224
|
-
"_id.relation": relation,
|
|
225
|
-
"_id.object.type": type,
|
|
226
|
-
...after === null ? {} : { "_id.object.id": { $gt: after } }
|
|
227
|
-
};
|
|
228
|
-
const found = await collection.find(filter, { projection: { "_id.object.id": 1 } }).sort({ "_id.object.id": 1 }).limit(limit + 1).toArray();
|
|
229
|
-
const items = found.slice(0, limit).map((document) => document._id.object.id);
|
|
230
|
-
const last = items.at(-1);
|
|
231
|
-
return {
|
|
232
|
-
items,
|
|
233
|
-
nextCursor: found.length > limit && last !== undefined ? last : null
|
|
234
|
-
};
|
|
235
|
-
}),
|
|
236
|
-
deleteEntity: (entity) => run$("deleteEntity", async () => {
|
|
237
|
-
const result = await collection.deleteMany({
|
|
238
|
-
$or: [
|
|
239
|
-
{ "_id.object.type": entity.type, "_id.object.id": entity.id },
|
|
240
|
-
{ "_id.subject.type": entity.type, "_id.subject.id": entity.id }
|
|
241
|
-
]
|
|
242
|
-
});
|
|
243
|
-
return result.deletedCount;
|
|
244
|
-
})
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
function syncMongoRelations(db, options) {
|
|
248
|
-
return syncCollections(db, [relations], options);
|
|
249
|
-
}
|
|
175
|
+
|
|
176
|
+
// src/relations/reads.ts
|
|
177
|
+
import {
|
|
178
|
+
isSubjectSet as isSubjectSet2
|
|
179
|
+
} from "@nxgt/janus";
|
|
180
|
+
|
|
181
|
+
// src/relations/tuples.ts
|
|
182
|
+
import { isSubjectSet } from "@nxgt/janus";
|
|
250
183
|
function keyOf(tuple) {
|
|
251
184
|
return {
|
|
252
185
|
object: { type: tuple.object.type, id: tuple.object.id },
|
|
@@ -258,158 +191,115 @@ function subjectKey(subject) {
|
|
|
258
191
|
return isSubjectSet(subject) ? { type: subject.type, id: subject.id, relation: subject.relation } : { type: subject.type, id: subject.id };
|
|
259
192
|
}
|
|
260
193
|
|
|
261
|
-
// src/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
getCollection as getCollection2,
|
|
265
|
-
syncCollections as syncCollections2
|
|
266
|
-
} from "@nxgt/mongo";
|
|
267
|
-
function createMongoStores(db) {
|
|
268
|
-
return {
|
|
269
|
-
users: userStore(db),
|
|
270
|
-
sessions: sessionStore(db),
|
|
271
|
-
tokens: tokenStore(db)
|
|
272
|
-
};
|
|
194
|
+
// src/relations/reads.ts
|
|
195
|
+
async function has({ collection }, tuple) {
|
|
196
|
+
return await collection.findOne({ _id: keyOf(tuple) }, { projection: { _id: 1 } }) !== null;
|
|
273
197
|
}
|
|
274
|
-
function
|
|
275
|
-
return
|
|
198
|
+
async function findSubjectSets(context, object, relation) {
|
|
199
|
+
return (await holding(context, object, relation, true)).map((subject) => ({
|
|
200
|
+
type: subject.type,
|
|
201
|
+
id: subject.id,
|
|
202
|
+
relation: subject.relation
|
|
203
|
+
}));
|
|
276
204
|
}
|
|
277
|
-
function
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
205
|
+
async function findEntities(context, object, relation) {
|
|
206
|
+
return (await holding(context, object, relation, false)).map((subject) => ({ type: subject.type, id: subject.id }));
|
|
207
|
+
}
|
|
208
|
+
async function holding({ collection }, object, relation, sets) {
|
|
209
|
+
return (await collection.find({
|
|
210
|
+
"_id.object.type": object.type,
|
|
211
|
+
"_id.object.id": object.id,
|
|
212
|
+
"_id.relation": relation,
|
|
213
|
+
"_id.subject.relation": { $exists: sets }
|
|
214
|
+
}).toArray()).map((document) => document._id.subject);
|
|
215
|
+
}
|
|
216
|
+
async function findObjects({ collection }, { type, relation, subject, after, limit }) {
|
|
217
|
+
const filter = {
|
|
218
|
+
"_id.subject.type": subject.type,
|
|
219
|
+
"_id.subject.id": subject.id,
|
|
220
|
+
"_id.subject.relation": isSubjectSet2(subject) ? subject.relation : null,
|
|
221
|
+
"_id.relation": relation,
|
|
222
|
+
"_id.object.type": type,
|
|
223
|
+
...after === null ? {} : { "_id.object.id": { $gt: after } }
|
|
287
224
|
};
|
|
225
|
+
const found = await collection.find(filter, { projection: { "_id.object.id": 1 } }).sort({ "_id.object.id": 1 }).limit(limit + 1).toArray();
|
|
226
|
+
const items = found.slice(0, limit).map((document) => document._id.object.id);
|
|
227
|
+
const last = items.at(-1);
|
|
288
228
|
return {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const outcome = await settle(collection.raw.insertOne(document));
|
|
292
|
-
if (!("duplicate" in outcome))
|
|
293
|
-
return toUser(document);
|
|
294
|
-
const stored = await find(record.id);
|
|
295
|
-
if (stored !== null)
|
|
296
|
-
return stored;
|
|
297
|
-
throw refuse("insertUser", outcome.duplicate);
|
|
298
|
-
}),
|
|
299
|
-
findUser: (id) => run$("findUser", () => find(id)),
|
|
300
|
-
findUserByLogin: (type, login) => run$("findUserByLogin", async () => {
|
|
301
|
-
const found = await collection.findFirst({ type, logins: login });
|
|
302
|
-
return found === undefined ? null : toUser(found);
|
|
303
|
-
}),
|
|
304
|
-
listUsers: ({ type, after, limit }) => run$("listUsers", async () => {
|
|
305
|
-
const found = await collection.findMany({
|
|
306
|
-
filter: after === null ? { type } : { type, _id: { $gt: after } },
|
|
307
|
-
sort: { _id: 1 },
|
|
308
|
-
limit: limit + 1
|
|
309
|
-
});
|
|
310
|
-
const items = found.slice(0, limit).map(toUser);
|
|
311
|
-
const last = items.at(-1);
|
|
312
|
-
return {
|
|
313
|
-
items,
|
|
314
|
-
nextCursor: found.length > limit && last !== undefined ? last.id : null
|
|
315
|
-
};
|
|
316
|
-
}),
|
|
317
|
-
updateUser: (id, patch, ifVersion) => run$("updateUser", async () => {
|
|
318
|
-
const outcome = await settle(collection.raw.findOneAndUpdate({ _id: id, version: ifVersion }, { $set: toUserSet(patch), $inc: { version: 1 } }, { returnDocument: "after" }));
|
|
319
|
-
if ("duplicate" in outcome) {
|
|
320
|
-
throw refuse("updateUser", outcome.duplicate);
|
|
321
|
-
}
|
|
322
|
-
if (outcome.written !== null)
|
|
323
|
-
return toUser(outcome.written);
|
|
324
|
-
const stored = await find(id);
|
|
325
|
-
if (stored === null) {
|
|
326
|
-
throw new NotFoundError("updateUser: no user has this id", {
|
|
327
|
-
userId: id,
|
|
328
|
-
operation: "updateUser"
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
throw new StoreConflict2("version", `updateUser: expected version ${ifVersion}, found ${stored.version}`, {
|
|
332
|
-
userId: id,
|
|
333
|
-
expectedVersion: ifVersion,
|
|
334
|
-
actualVersion: stored.version,
|
|
335
|
-
operation: "updateUser"
|
|
336
|
-
});
|
|
337
|
-
}),
|
|
338
|
-
deleteUser: (id) => run$("deleteUser", async () => {
|
|
339
|
-
const result = await collection.raw.deleteOne({ _id: id });
|
|
340
|
-
return result.deletedCount === 1;
|
|
341
|
-
})
|
|
229
|
+
items,
|
|
230
|
+
nextCursor: found.length > limit && last !== undefined ? last : null
|
|
342
231
|
};
|
|
343
232
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
extendSession: (id, expiresAt) => run$("extendSession", async () => {
|
|
361
|
-
const written = await collection.raw.findOneAndUpdate({ _id: id, revokedAt: null }, { $set: { expiresAt } }, { returnDocument: "after" });
|
|
362
|
-
return written === null ? null : toSession(written);
|
|
363
|
-
}),
|
|
364
|
-
revokeSession: (id, at) => run$("revokeSession", async () => {
|
|
365
|
-
const result = await collection.raw.updateOne({ _id: id }, [
|
|
366
|
-
{ $set: { revokedAt: { $ifNull: ["$revokedAt", at] } } }
|
|
367
|
-
]);
|
|
368
|
-
return result.matchedCount === 1;
|
|
369
|
-
}),
|
|
370
|
-
revokeUserSessions: (userId, at, except) => run$("revokeUserSessions", async () => {
|
|
371
|
-
const result = await collection.raw.updateMany(except === undefined ? { userId, revokedAt: null } : { userId, revokedAt: null, _id: { $ne: except } }, { $set: { revokedAt: at } });
|
|
372
|
-
return result.modifiedCount;
|
|
373
|
-
}),
|
|
374
|
-
deleteUserSessions: (userId) => run$("deleteUserSessions", async () => {
|
|
375
|
-
const result = await collection.raw.deleteMany({ userId });
|
|
376
|
-
return result.deletedCount;
|
|
377
|
-
})
|
|
378
|
-
};
|
|
233
|
+
|
|
234
|
+
// src/relations/writes.ts
|
|
235
|
+
async function write({ db, collection }, add, remove) {
|
|
236
|
+
const removals = remove.map(keyOf);
|
|
237
|
+
const additions = add.map(keyOf);
|
|
238
|
+
if (removals.length + additions.length <= 1) {
|
|
239
|
+
return apply(collection, additions, removals);
|
|
240
|
+
}
|
|
241
|
+
const session = db.client.startSession();
|
|
242
|
+
try {
|
|
243
|
+
session.startTransaction();
|
|
244
|
+
await apply(collection, additions, removals, session);
|
|
245
|
+
await session.commitTransaction();
|
|
246
|
+
} finally {
|
|
247
|
+
await session.endSession();
|
|
248
|
+
}
|
|
379
249
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
250
|
+
async function apply(collection, additions, removals, session) {
|
|
251
|
+
if (removals.length > 0) {
|
|
252
|
+
await collection.deleteMany({ _id: { $in: removals } }, session === undefined ? {} : { session });
|
|
253
|
+
}
|
|
254
|
+
if (additions.length > 0) {
|
|
255
|
+
await collection.bulkWrite(additions.map((key) => ({
|
|
256
|
+
replaceOne: {
|
|
257
|
+
filter: { _id: key },
|
|
258
|
+
replacement: { _id: key },
|
|
259
|
+
upsert: true
|
|
260
|
+
}
|
|
261
|
+
})), session === undefined ? {} : { session });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function deleteEntity({ collection }, entity) {
|
|
265
|
+
const result = await collection.deleteMany({
|
|
266
|
+
$or: [
|
|
267
|
+
{ "_id.object.type": entity.type, "_id.object.id": entity.id },
|
|
268
|
+
{ "_id.subject.type": entity.type, "_id.subject.id": entity.id }
|
|
269
|
+
]
|
|
270
|
+
});
|
|
271
|
+
return result.deletedCount;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/relations/index.ts
|
|
275
|
+
function createMongoRelations(db) {
|
|
276
|
+
const context = {
|
|
277
|
+
db,
|
|
278
|
+
collection: getCollection(db, relations).raw
|
|
279
|
+
};
|
|
280
|
+
const run$ = (operation, body) => run("relations", operation, body);
|
|
383
281
|
return {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}),
|
|
391
|
-
countAttempt: (tokenHash, kind) => run$("countAttempt", async () => {
|
|
392
|
-
const after = await collection.raw.findOneAndUpdate({ _id: tokenHash, kind, spentAt: null }, { $inc: { attempts: 1 } }, { returnDocument: "after" });
|
|
393
|
-
if (after !== null)
|
|
394
|
-
return toToken(after);
|
|
395
|
-
const spent = await collection.raw.findOne({ _id: tokenHash, kind });
|
|
396
|
-
return spent === null ? null : toToken(spent);
|
|
397
|
-
}),
|
|
398
|
-
spendUserTokens: (userId, kind, at, except) => run$("spendUserTokens", async () => {
|
|
399
|
-
const result = await collection.raw.updateMany({
|
|
400
|
-
userId,
|
|
401
|
-
kind,
|
|
402
|
-
spentAt: null,
|
|
403
|
-
...except === undefined ? {} : { _id: { $ne: except } }
|
|
404
|
-
}, { $set: { spentAt: at } });
|
|
405
|
-
return result.modifiedCount;
|
|
406
|
-
}),
|
|
407
|
-
deleteUserTokens: (userId) => run$("deleteUserTokens", async () => {
|
|
408
|
-
const result = await collection.raw.deleteMany({ userId });
|
|
409
|
-
return result.deletedCount;
|
|
410
|
-
})
|
|
282
|
+
write: ({ add = [], remove = [] }) => run$("write", () => write(context, add, remove)),
|
|
283
|
+
has: (tuple) => run$("has", () => has(context, tuple)),
|
|
284
|
+
findSubjectSets: (object, relation) => run$("findSubjectSets", () => findSubjectSets(context, object, relation)),
|
|
285
|
+
findEntities: (object, relation) => run$("findEntities", () => findEntities(context, object, relation)),
|
|
286
|
+
findObjects: ({ type, relation, subject, after, limit }) => run$("findObjects", () => findObjects(context, { type, relation, subject, after, limit })),
|
|
287
|
+
deleteEntity: (entity) => run$("deleteEntity", () => deleteEntity(context, entity))
|
|
411
288
|
};
|
|
412
289
|
}
|
|
290
|
+
function syncMongoRelations(db, options) {
|
|
291
|
+
return syncCollections(db, [relations], options);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/stores/index.ts
|
|
295
|
+
import {
|
|
296
|
+
syncCollections as syncCollections2
|
|
297
|
+
} from "@nxgt/mongo";
|
|
298
|
+
|
|
299
|
+
// src/stores/sessions.ts
|
|
300
|
+
import { getCollection as getCollection2 } from "@nxgt/mongo";
|
|
301
|
+
|
|
302
|
+
// src/stores/records.ts
|
|
413
303
|
function toUserDocument(record) {
|
|
414
304
|
return structuredClone({
|
|
415
305
|
_id: record.id,
|
|
@@ -514,6 +404,165 @@ function toToken(document) {
|
|
|
514
404
|
};
|
|
515
405
|
}
|
|
516
406
|
|
|
407
|
+
// src/stores/sessions.ts
|
|
408
|
+
function sessionStore(db) {
|
|
409
|
+
const collection = getCollection2(db, sessions);
|
|
410
|
+
const run$ = (operation, body) => run("sessions", operation, body);
|
|
411
|
+
return {
|
|
412
|
+
insertSession: (record) => run$("insertSession", async () => {
|
|
413
|
+
const outcome = await settle(collection.raw.insertOne(toSessionDocument(record)));
|
|
414
|
+
if (!("duplicate" in outcome))
|
|
415
|
+
return;
|
|
416
|
+
if (await collection.findById(record.id) !== undefined)
|
|
417
|
+
return;
|
|
418
|
+
throw unexpectedDuplicate("sessions", "insertSession", outcome.duplicate);
|
|
419
|
+
}),
|
|
420
|
+
findSessionByTokenHash: (tokenHash) => run$("findSessionByTokenHash", async () => {
|
|
421
|
+
const found = await collection.findFirst({ tokenHash });
|
|
422
|
+
return found === undefined ? null : toSession(found);
|
|
423
|
+
}),
|
|
424
|
+
extendSession: (id, expiresAt) => run$("extendSession", async () => {
|
|
425
|
+
const written = await collection.raw.findOneAndUpdate({ _id: id, revokedAt: null }, { $set: { expiresAt } }, { returnDocument: "after" });
|
|
426
|
+
return written === null ? null : toSession(written);
|
|
427
|
+
}),
|
|
428
|
+
revokeSession: (id, at) => run$("revokeSession", () => revokeSession(collection, id, at)),
|
|
429
|
+
revokeUserSessions: (userId, at, except) => run$("revokeUserSessions", () => revokeUserSessions(collection, userId, at, except)),
|
|
430
|
+
deleteUserSessions: (userId) => run$("deleteUserSessions", async () => {
|
|
431
|
+
const result = await collection.raw.deleteMany({ userId });
|
|
432
|
+
return result.deletedCount;
|
|
433
|
+
})
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function revokeSession(collection, id, at) {
|
|
437
|
+
const result = await collection.raw.updateOne({ _id: id }, [
|
|
438
|
+
{ $set: { revokedAt: { $ifNull: ["$revokedAt", at] } } }
|
|
439
|
+
]);
|
|
440
|
+
return result.matchedCount === 1;
|
|
441
|
+
}
|
|
442
|
+
async function revokeUserSessions(collection, userId, at, except) {
|
|
443
|
+
const result = await collection.raw.updateMany(except === undefined ? { userId, revokedAt: null } : { userId, revokedAt: null, _id: { $ne: except } }, { $set: { revokedAt: at } });
|
|
444
|
+
return result.modifiedCount;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/stores/tokens.ts
|
|
448
|
+
import { getCollection as getCollection3 } from "@nxgt/mongo";
|
|
449
|
+
function tokenStore(db) {
|
|
450
|
+
const collection = getCollection3(db, tokens);
|
|
451
|
+
const run$ = (operation, body) => run("tokens", operation, body);
|
|
452
|
+
return {
|
|
453
|
+
insertToken: (record) => run$("insertToken", async () => {
|
|
454
|
+
await settle(collection.raw.insertOne(toTokenDocument(record)));
|
|
455
|
+
}),
|
|
456
|
+
consumeToken: (tokenHash, kind, at) => run$("consumeToken", () => consumeToken(collection, tokenHash, kind, at)),
|
|
457
|
+
countAttempt: (tokenHash, kind) => run$("countAttempt", () => countAttempt(collection, tokenHash, kind)),
|
|
458
|
+
spendUserTokens: (userId, kind, at, except) => run$("spendUserTokens", async () => {
|
|
459
|
+
const result = await collection.raw.updateMany({
|
|
460
|
+
userId,
|
|
461
|
+
kind,
|
|
462
|
+
spentAt: null,
|
|
463
|
+
...except === undefined ? {} : { _id: { $ne: except } }
|
|
464
|
+
}, { $set: { spentAt: at } });
|
|
465
|
+
return result.modifiedCount;
|
|
466
|
+
}),
|
|
467
|
+
deleteUserTokens: (userId) => run$("deleteUserTokens", async () => {
|
|
468
|
+
const result = await collection.raw.deleteMany({ userId });
|
|
469
|
+
return result.deletedCount;
|
|
470
|
+
})
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async function consumeToken(collection, tokenHash, kind, at) {
|
|
474
|
+
const before = await collection.raw.findOneAndUpdate({ _id: tokenHash, kind }, [{ $set: { spentAt: { $ifNull: ["$spentAt", at] } } }], { returnDocument: "before" });
|
|
475
|
+
return before === null ? null : toToken(before);
|
|
476
|
+
}
|
|
477
|
+
async function countAttempt(collection, tokenHash, kind) {
|
|
478
|
+
const after = await collection.raw.findOneAndUpdate({ _id: tokenHash, kind, spentAt: null }, { $inc: { attempts: 1 } }, { returnDocument: "after" });
|
|
479
|
+
if (after !== null)
|
|
480
|
+
return toToken(after);
|
|
481
|
+
const spent = await collection.raw.findOne({ _id: tokenHash, kind });
|
|
482
|
+
return spent === null ? null : toToken(spent);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/stores/users.ts
|
|
486
|
+
import { NotFoundError, StoreConflict as StoreConflict2 } from "@nxgt/janus";
|
|
487
|
+
import { getCollection as getCollection4 } from "@nxgt/mongo";
|
|
488
|
+
function userStore(db) {
|
|
489
|
+
const collection = getCollection4(db, users);
|
|
490
|
+
const run$ = (operation, body) => run("users", operation, body);
|
|
491
|
+
return {
|
|
492
|
+
insertUser: (record) => run$("insertUser", () => insertUser(collection, record)),
|
|
493
|
+
findUser: (id) => run$("findUser", () => findUser(collection, id)),
|
|
494
|
+
findUserByLogin: (type, login) => run$("findUserByLogin", async () => {
|
|
495
|
+
const found = await collection.findFirst({ type, logins: login });
|
|
496
|
+
return found === undefined ? null : toUser(found);
|
|
497
|
+
}),
|
|
498
|
+
listUsers: ({ type, after, limit }) => run$("listUsers", async () => {
|
|
499
|
+
const found = await collection.findMany({
|
|
500
|
+
filter: after === null ? { type } : { type, _id: { $gt: after } },
|
|
501
|
+
sort: { _id: 1 },
|
|
502
|
+
limit: limit + 1
|
|
503
|
+
});
|
|
504
|
+
const items = found.slice(0, limit).map(toUser);
|
|
505
|
+
const last = items.at(-1);
|
|
506
|
+
return {
|
|
507
|
+
items,
|
|
508
|
+
nextCursor: found.length > limit && last !== undefined ? last.id : null
|
|
509
|
+
};
|
|
510
|
+
}),
|
|
511
|
+
updateUser: (id, patch, ifVersion) => run$("updateUser", () => updateUser(collection, id, patch, ifVersion)),
|
|
512
|
+
deleteUser: (id) => run$("deleteUser", async () => {
|
|
513
|
+
const result = await collection.raw.deleteOne({ _id: id });
|
|
514
|
+
return result.deletedCount === 1;
|
|
515
|
+
})
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async function findUser(collection, id) {
|
|
519
|
+
const found = await collection.findById(id);
|
|
520
|
+
return found === undefined ? null : toUser(found);
|
|
521
|
+
}
|
|
522
|
+
async function insertUser(collection, record) {
|
|
523
|
+
const document = toUserDocument(record);
|
|
524
|
+
const outcome = await settle(collection.raw.insertOne(document));
|
|
525
|
+
if (!("duplicate" in outcome))
|
|
526
|
+
return toUser(document);
|
|
527
|
+
const stored = await findUser(collection, record.id);
|
|
528
|
+
if (stored !== null)
|
|
529
|
+
return stored;
|
|
530
|
+
throw refuseDuplicate("insertUser", outcome.duplicate);
|
|
531
|
+
}
|
|
532
|
+
async function updateUser(collection, id, patch, ifVersion) {
|
|
533
|
+
const outcome = await settle(collection.raw.findOneAndUpdate({ _id: id, version: ifVersion }, { $set: toUserSet(patch), $inc: { version: 1 } }, { returnDocument: "after" }));
|
|
534
|
+
if ("duplicate" in outcome) {
|
|
535
|
+
throw refuseDuplicate("updateUser", outcome.duplicate);
|
|
536
|
+
}
|
|
537
|
+
if (outcome.written !== null)
|
|
538
|
+
return toUser(outcome.written);
|
|
539
|
+
const stored = await findUser(collection, id);
|
|
540
|
+
if (stored === null) {
|
|
541
|
+
throw new NotFoundError("updateUser: no user has this id", {
|
|
542
|
+
userId: id,
|
|
543
|
+
operation: "updateUser"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
throw new StoreConflict2("version", `updateUser: expected version ${ifVersion}, found ${stored.version}`, {
|
|
547
|
+
userId: id,
|
|
548
|
+
expectedVersion: ifVersion,
|
|
549
|
+
actualVersion: stored.version,
|
|
550
|
+
operation: "updateUser"
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/stores/index.ts
|
|
555
|
+
function createMongoStores(db) {
|
|
556
|
+
return {
|
|
557
|
+
users: userStore(db),
|
|
558
|
+
sessions: sessionStore(db),
|
|
559
|
+
tokens: tokenStore(db)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function syncMongoStores(db, options) {
|
|
563
|
+
return syncCollections2(db, janusCollections, options);
|
|
564
|
+
}
|
|
565
|
+
|
|
517
566
|
// src/adapter.ts
|
|
518
567
|
function createMongoAdapter(db) {
|
|
519
568
|
return { store: createMongoStores(db), relations: createMongoRelations(db) };
|
|
@@ -535,5 +584,5 @@ export {
|
|
|
535
584
|
users
|
|
536
585
|
};
|
|
537
586
|
|
|
538
|
-
//# debugId=
|
|
587
|
+
//# debugId=BE876FE436B6984364756E2164756E21
|
|
539
588
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/adapter.ts", "../src/collections.ts", "../src/relations.ts", "../src/translate.ts", "../src/stores.ts"],
|
|
3
|
+
"sources": ["../src/adapter.ts", "../src/collections.ts", "../src/relations/index.ts", "../src/translate.ts", "../src/relations/collection.ts", "../src/relations/reads.ts", "../src/relations/tuples.ts", "../src/relations/writes.ts", "../src/stores/index.ts", "../src/stores/sessions.ts", "../src/stores/records.ts", "../src/stores/tokens.ts", "../src/stores/users.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { JanusStores } from '@nxgt/janus';\nimport type { RelationStore } from '@nxgt/janus/permissions';\nimport {\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { janusCollections } from './collections';\nimport { createMongoRelations, relations } from './relations';\nimport { createMongoStores } from './stores';\n\n/** The whole adapter over one database, keyed as `janus()` takes it. */\nexport interface MongoAdapter {\n\t/** The identity stores: `createMongoStores(db)`. */\n\treadonly store: JanusStores;\n\t/** The relation store: `createMongoRelations(db)`. */\n\treadonly relations: RelationStore;\n}\n\n/**\n * The identity stores and the relation store over one database, under the\n * names `janus()` takes them — so one spread wires both, and deleting a user\n * deletes every tuple naming them:\n *\n * ```ts\n * const mongo = createMongoAdapter(db);\n * const auth = janus({ user, password: { login: 'email' }, hasher, ...mongo });\n * const access = permissions({ model, store: mongo.relations });\n * ```\n *\n * It connects to nothing and sends nothing, as the two it combines. An\n * application that only authenticates can take `createMongoStores(db)` alone.\n */\nexport function createMongoAdapter(db: Db): MongoAdapter {\n\treturn { store: createMongoStores(db), relations: createMongoRelations(db) };\n}\n\n/**\n * Creates the four collections, their validators and their indexes, and says\n * what it changed: `syncMongoStores` and `syncMongoRelations` in one\n * deployment step. Run it twice and the second run sends nothing.\n */\nexport function syncMongoAdapter(\n\tdb: Db,\n\toptions?: SyncOptions,\n): Promise<SyncReport[]> {\n\treturn syncCollections(db, [...janusCollections, relations], options);\n}\n",
|
|
6
6
|
"import { defineCollection } from '@nxgt/mongo';\nimport { z } from 'zod';\n\n/**\n * The three collections, as `@nxgt/mongo` definitions.\n *\n * **What the database holds is the port's record**, with the id under `_id`.\n * Nothing else is renamed and nothing is encoded, so a record comes back byte\n * for byte (rule 4) and a document read in a shell reads like the record in\n * the code.\n *\n * Defining them registers them with `@nxgt/mongo`, so an application that\n * deploys with `syncAll(db)` creates these three with the rest of its own.\n *\n * `@nxgt/mongo`'s optimistic lock and soft delete stay off. The port keeps its\n * own `version`, because it must tell `NOT_FOUND` from `VERSION_CONFLICT`,\n * which takes a second read the lock does not make; and the port has no\n * deletion to soften.\n */\n\n/** Anything JSON. The core validated it against the application's schema already. */\nconst jsonObject = z.record(z.string(), z.unknown());\nconst at = z.date();\n\nexport const users = defineCollection({\n\tname: 'users',\n\tschema: z.object({\n\t\t/** The UUIDv7 the core minted. A string, never an `ObjectId`: the store mints nothing. */\n\t\t_id: z.string(),\n\t\ttype: z.string(),\n\t\tschemaVersion: z.string(),\n\t\tactive: z.boolean(),\n\t\tfields: jsonObject,\n\t\tlogins: z.array(z.string()),\n\t\tpassword: z.object({ hash: z.string(), updatedAt: at }).nullable(),\n\t\t/**\n\t\t * Absent on a user written before 0.3 — read as `null`, so no\n\t\t * migration is needed.\n\t\t */\n\t\tsecondFactor: z\n\t\t\t.object({\n\t\t\t\tmethod: z.literal('totp'),\n\t\t\t\tsecret: z.string(),\n\t\t\t\tconfirmedAt: at.nullable(),\n\t\t\t\tlastStep: z.int().nonnegative().nullable(),\n\t\t\t})\n\t\t\t.nullable()\n\t\t\t.optional(),\n\t\temailVerifiedAt: at.nullable(),\n\t\tversion: z.int().nonnegative(),\n\t\tcreatedAt: at,\n\t\tupdatedAt: at,\n\t}),\n\tindexes: [\n\t\t/**\n\t\t * A login is unique **per type**: a scalar `type` and a flat array of\n\t\t * strings, so the multikey semantics are the plain ones, no collation is\n\t\t * needed, and the driver's `keyValue` names both the type and the login\n\t\t * that collided.\n\t\t */\n\t\t{\n\t\t\tkey: { type: 1, logins: 1 },\n\t\t\tname: 'loginUnique',\n\t\t\tunique: true,\n\t\t\t// Without it, two users holding no login at all collide: an empty\n\t\t\t// array is indexed as one missing value, and a unique index admits\n\t\t\t// one of those per type.\n\t\t\tpartialFilterExpression: { logins: { $type: 'string' } },\n\t\t},\n\t\t/** `listUsers` reads one type in `_id` order. */\n\t\t{ key: { type: 1, _id: 1 }, name: 'typeId' },\n\t],\n});\n\nexport const sessions = defineCollection({\n\tname: 'sessions',\n\tschema: z.object({\n\t\t_id: z.string(),\n\t\ttokenHash: z.string(),\n\t\tuserId: z.string(),\n\t\tauthenticatedAt: at,\n\t\texpiresAt: at,\n\t\trevokedAt: at.nullable(),\n\t\tcreatedAt: at,\n\t}),\n\tindexes: [\n\t\t{\n\t\t\tkey: { tokenHash: 1 },\n\t\t\tname: 'tokenHashUnique',\n\t\t\tunique: true,\n\t\t},\n\t\t{ key: { userId: 1 }, name: 'userId' },\n\t\t/**\n\t\t * Storage hygiene, **not** the expiry: MongoDB's TTL monitor runs every\n\t\t * sixty seconds, so a lapsed session stays readable for up to a minute.\n\t\t * The core compares `expiresAt` on every read, which is what expires it.\n\t\t */\n\t\t{\n\t\t\tkey: { expiresAt: 1 },\n\t\t\tname: 'expiry',\n\t\t\texpireAfterSeconds: 0,\n\t\t},\n\t],\n});\n\nexport const tokens = defineCollection({\n\tname: 'tokens',\n\tschema: z.object({\n\t\t/** The token's `sha256`. Unique by construction, so it is the key itself. */\n\t\t_id: z.string(),\n\t\tkind: z.enum([\n\t\t\t'verifyEmail',\n\t\t\t'resetPassword',\n\t\t\t'secondFactor',\n\t\t\t'signInCode',\n\t\t]),\n\t\tuserId: z.string(),\n\t\taddress: z.string(),\n\t\t/** Absent on a token written before 0.3 — read as `null` and `0`. */\n\t\tcodeHash: z.string().nullable().optional(),\n\t\tattempts: z.int().nonnegative().optional(),\n\t\texpiresAt: at,\n\t\tspentAt: at.nullable(),\n\t\tcreatedAt: at,\n\t}),\n\tindexes: [\n\t\t// The same hygiene as sessions'. A token the monitor dropped is answered\n\t\t// `null`, which the port allows: a lapsed token is refused either way.\n\t\t{ key: { expiresAt: 1 }, name: 'expiry', expireAfterSeconds: 0 },\n\t\t/** `deleteUserTokens` reads one user's tokens. */\n\t\t{ key: { userId: 1 }, name: 'userId' },\n\t],\n});\n\n/** The three definitions, in the order `syncMongoStores` syncs them. */\nexport const janusCollections = [users, sessions, tokens] as const;\n",
|
|
7
|
-
"import
|
|
8
|
-
"import { JanusError, StoreConflict, StoreFailure } from '@nxgt/janus';\n\n/**\n * **The driver's errors, as the port's.** One file, and one default.\n *\n * | What happens | Becomes |\n * | --- | --- |\n * | A duplicate key on `(type, logins)` | `StoreConflict('login')`, carrying `login` and `userType` |\n * | A duplicate key on `_id` | nothing — the insert is a retry, and answers what is stored |\n * | A duplicate key on **any other index** | **`StoreFailure`** |\n * | A `$jsonSchema` validation failure (121) | **`StoreFailure`** |\n * | Anything else, absolutely | `StoreFailure`, with the driver error as `cause` |\n *\n * The two bold rows go against the obvious reading. A duplicate on an index\n * the adapter did not mean to collide on is an adapter bug: reporting it as\n * `LOGIN_TAKEN` would tell somebody their login is taken when it is\n * not. And the core has validated every record before it reaches a store, so a\n * validator refusing one means the adapter and the port disagree — never the\n * caller's fault, never a 400.\n *\n * **The default is a failure, not an absence.** That branch is what keeps the\n * port's first rule: nothing here answers `null` for an error.\n */\n\n/** Runs one port method, and makes every rejection one the port allows. */\nexport async function run<T>(\n\tslot: 'users' | 'sessions' | 'tokens' | 'relations',\n\toperation: string,\n\tbody: () => Promise<T>,\n): Promise<T> {\n\ttry {\n\t\treturn await body();\n\t} catch (error) {\n\t\t// Already the port's — a conflict, a NOT_FOUND after a read — or a\n\t\t// failure raised below with its own message.\n\t\tif (error instanceof JanusError) throw error;\n\t\tthrow new StoreFailure(`${slot}.${operation}: the store could not answer`, {\n\t\t\tslot,\n\t\t\toperation,\n\t\t\tcause: error,\n\t\t});\n\t}\n}\n\n/** The shape of the driver's `E11000`, as far as this file reads it. */\nexport interface DuplicateKey {\n\treadonly code: 11000;\n\treadonly keyPattern?: Readonly<Record<string, unknown>>;\n\treadonly keyValue?: Readonly<Record<string, unknown>>;\n}\n\nexport function isDuplicateKey(error: unknown): error is DuplicateKey {\n\treturn (\n\t\ttypeof error === 'object' &&\n\t\terror !== null &&\n\t\t(error as { code?: unknown }).code === 11000\n\t);\n}\n\n/**\n * Settles a write: what it answered when it landed, or the duplicate-key error\n * when an index refused it. **Any other rejection is rethrown**, so a caller\n * of this is only ever told \"written\" or \"duplicate\", and never \"nothing\".\n */\nexport function settle<T>(\n\twrite: Promise<T>,\n): Promise<\n\t| { readonly written: T }\n\t| { readonly written?: never; duplicate: DuplicateKey }\n> {\n\treturn write.then(\n\t\t(written) => ({ written }),\n\t\t(error: unknown) => {\n\t\t\tif (isDuplicateKey(error)) return { duplicate: error };\n\t\t\tthrow error;\n\t\t},\n\t);\n}\n\n/** A login one type holds. */\nexport interface TypedLogin {\n\treadonly type: string;\n\treadonly login: string;\n}\n\n/**\n * The login a duplicate names, when it is the login index that refused the\n * write — and `null` for every other index, which the caller then reports as\n * a failure.\n */\nexport function takenLogin(duplicate: DuplicateKey): TypedLogin | null {\n\tconst type = duplicate.keyValue?.type;\n\tconst login = duplicate.keyValue?.logins;\n\tif (typeof type !== 'string' || typeof login !== 'string') return null;\n\treturn { type, login };\n}\n\n/** The refusal the port promises for a taken login. */\nexport function loginTaken(\n\toperation: string,\n\t{ type, login }: TypedLogin,\n\tcause: unknown,\n): StoreConflict {\n\treturn new StoreConflict(\n\t\t'login',\n\t\t`${operation}: the login is taken by another ${type}`,\n\t\t{ login, userType: type, operation, cause },\n\t);\n}\n\n/** A duplicate on an index the adapter never meant to collide on: a bug, reported as one. */\nexport function unexpectedDuplicate(\n\tslot: 'users' | 'sessions' | 'tokens',\n\toperation: string,\n\tduplicate: DuplicateKey,\n): StoreFailure {\n\treturn new StoreFailure(\n\t\t`${slot}.${operation}: a duplicate key on ${Object.keys(duplicate.keyPattern ?? {}).join(', ') || 'an unknown index'}, which this adapter never writes on purpose`,\n\t\t{ slot, operation, cause: duplicate },\n\t);\n}\n",
|
|
9
|
-
"import type {\n\tJanusStores,\n\tSessionRecord,\n\tSessionStore,\n\tTokenRecord,\n\tTokenStore,\n\tUserPatch,\n\tUserRecord,\n\tUserStore,\n} from '@nxgt/janus';\nimport { type Id, NotFoundError, StoreConflict } from '@nxgt/janus';\nimport {\n\ttype DocumentOf,\n\tgetCollection,\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { janusCollections, sessions, tokens, users } from './collections';\nimport {\n\tloginTaken,\n\trun,\n\tsettle,\n\ttakenLogin,\n\tunexpectedDuplicate,\n} from './translate';\n\ntype UserDocument = DocumentOf<typeof users>;\ntype SessionDocument = DocumentOf<typeof sessions>;\ntype TokenDocument = DocumentOf<typeof tokens>;\n\n/**\n * The three stores `janus()` takes, over one MongoDB database.\n *\n * ```ts\n * const db = (await connectMongo(process.env.MONGO_URI)).db;\n * await syncMongoStores(db); // a deployment step: creates the indexes\n * const auth = janus({ user, password: { login: 'email' }, store: createMongoStores(db), hasher });\n * ```\n *\n * **Reads go through `@nxgt/mongo`'s `find*`, never its `get*`.** `findById`\n * answers `undefined` for an absence, so an absence arrives as a *value*,\n * turned into `null` here, and everything that arrives as a rejection is a\n * failure. There is no path on which an absence and an outage travel the same\n * channel, so there is none on which a `catch` could confuse them.\n *\n * **Every conditional write is one driver call** on `raw`, the driver's own\n * collection: `@nxgt/mongo` names no update conditioned on anything but its\n * own lock, and the port needs `version`, `revokedAt` and `kind`.\n *\n * `sessions.deleteExpiredSessions` is not implemented: a TTL index drops lapsed\n * sessions, so `sessions.collectExpired()` answers `UNSUPPORTED` — which is\n * what the port asks of a store with its own expiry.\n */\nexport function createMongoStores(db: Db): JanusStores {\n\treturn {\n\t\tusers: userStore(db),\n\t\tsessions: sessionStore(db),\n\t\ttokens: tokenStore(db),\n\t};\n}\n\n/**\n * Creates the three collections, their validators and their indexes, and says\n * what it changed. Run it twice and the second run sends nothing.\n *\n * A deployment step, never a request-time one: it needs the `dbAdmin` role,\n * and the core never calls it (rule 6). An application that deploys with\n * `@nxgt/mongo`'s `syncAll(db)` already syncs these three with its own.\n */\nexport function syncMongoStores(\n\tdb: Db,\n\toptions?: SyncOptions,\n): Promise<SyncReport[]> {\n\treturn syncCollections(db, janusCollections, options);\n}\n\nfunction userStore(db: Db): UserStore {\n\tconst collection = getCollection(db, users);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('users', operation, body);\n\n\tconst find = async (id: string) => {\n\t\tconst found = await collection.findById(id);\n\t\treturn found === undefined ? null : toUser(found);\n\t};\n\n\t/** A duplicate on the login index as the port's conflict, and any other as a bug. */\n\tconst refuse = (\n\t\toperation: string,\n\t\tduplicate: Parameters<typeof takenLogin>[0],\n\t) => {\n\t\tconst taken = takenLogin(duplicate);\n\t\treturn taken === null\n\t\t\t? unexpectedDuplicate('users', operation, duplicate)\n\t\t\t: loginTaken(operation, taken, duplicate);\n\t};\n\n\treturn {\n\t\tinsertUser: (record) =>\n\t\t\trun$('insertUser', async () => {\n\t\t\t\tconst document = toUserDocument(record);\n\t\t\t\tconst outcome = await settle(collection.raw.insertOne(document));\n\t\t\t\tif (!('duplicate' in outcome)) return toUser(document);\n\n\t\t\t\t// A retry whose first attempt landed: whichever index reported it,\n\t\t\t\t// the user with this id is there, and is the answer. That includes\n\t\t\t\t// their own logins, which are not taken by them.\n\t\t\t\tconst stored = await find(record.id);\n\t\t\t\tif (stored !== null) return stored;\n\t\t\t\tthrow refuse('insertUser', outcome.duplicate);\n\t\t\t}),\n\n\t\tfindUser: (id) => run$('findUser', () => find(id)),\n\n\t\tfindUserByLogin: (type, login) =>\n\t\t\trun$('findUserByLogin', async () => {\n\t\t\t\tconst found = await collection.findFirst({ type, logins: login });\n\t\t\t\treturn found === undefined ? null : toUser(found);\n\t\t\t}),\n\n\t\tlistUsers: ({ type, after, limit }) =>\n\t\t\trun$('listUsers', async () => {\n\t\t\t\t// One more than the page, to know whether another follows.\n\t\t\t\tconst found = await collection.findMany({\n\t\t\t\t\tfilter: after === null ? { type } : { type, _id: { $gt: after } },\n\t\t\t\t\tsort: { _id: 1 },\n\t\t\t\t\tlimit: limit + 1,\n\t\t\t\t});\n\t\t\t\tconst items = found.slice(0, limit).map(toUser);\n\t\t\t\tconst last = items.at(-1);\n\n\t\t\t\treturn {\n\t\t\t\t\titems,\n\t\t\t\t\tnextCursor:\n\t\t\t\t\t\tfound.length > limit && last !== undefined ? last.id : null,\n\t\t\t\t};\n\t\t\t}),\n\n\t\tupdateUser: (id, patch, ifVersion) =>\n\t\t\trun$('updateUser', async () => {\n\t\t\t\tconst outcome = await settle(\n\t\t\t\t\tcollection.raw.findOneAndUpdate(\n\t\t\t\t\t\t{ _id: id, version: ifVersion },\n\t\t\t\t\t\t{ $set: toUserSet(patch), $inc: { version: 1 } },\n\t\t\t\t\t\t{ returnDocument: 'after' },\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tif ('duplicate' in outcome) {\n\t\t\t\t\tthrow refuse('updateUser', outcome.duplicate);\n\t\t\t\t}\n\t\t\t\tif (outcome.written !== null) return toUser(outcome.written);\n\n\t\t\t\t// The write matched nothing, which alone cannot tell an unknown id\n\t\t\t\t// from a moved version: read again, and say which.\n\t\t\t\tconst stored = await find(id);\n\t\t\t\tif (stored === null) {\n\t\t\t\t\tthrow new NotFoundError('updateUser: no user has this id', {\n\t\t\t\t\t\tuserId: id,\n\t\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tthrow new StoreConflict(\n\t\t\t\t\t'version',\n\t\t\t\t\t`updateUser: expected version ${ifVersion}, found ${stored.version}`,\n\t\t\t\t\t{\n\t\t\t\t\t\tuserId: id,\n\t\t\t\t\t\texpectedVersion: ifVersion,\n\t\t\t\t\t\tactualVersion: stored.version,\n\t\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}),\n\n\t\tdeleteUser: (id) =>\n\t\t\trun$('deleteUser', async () => {\n\t\t\t\tconst result = await collection.raw.deleteOne({ _id: id });\n\t\t\t\treturn result.deletedCount === 1;\n\t\t\t}),\n\t};\n}\n\nfunction sessionStore(db: Db): SessionStore {\n\tconst collection = getCollection(db, sessions);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('sessions', operation, body);\n\n\treturn {\n\t\tinsertSession: (record) =>\n\t\t\trun$('insertSession', async () => {\n\t\t\t\tconst outcome = await settle(\n\t\t\t\t\tcollection.raw.insertOne(toSessionDocument(record)),\n\t\t\t\t);\n\t\t\t\tif (!('duplicate' in outcome)) return;\n\n\t\t\t\t// Idempotent under retry: a session with this id is already there.\n\t\t\t\tif ((await collection.findById(record.id)) !== undefined) return;\n\t\t\t\tthrow unexpectedDuplicate(\n\t\t\t\t\t'sessions',\n\t\t\t\t\t'insertSession',\n\t\t\t\t\toutcome.duplicate,\n\t\t\t\t);\n\t\t\t}),\n\n\t\tfindSessionByTokenHash: (tokenHash) =>\n\t\t\trun$('findSessionByTokenHash', async () => {\n\t\t\t\tconst found = await collection.findFirst({ tokenHash });\n\t\t\t\treturn found === undefined ? null : toSession(found);\n\t\t\t}),\n\n\t\textendSession: (id, expiresAt) =>\n\t\t\trun$('extendSession', async () => {\n\t\t\t\t// `revokedAt: null` in the filter: an extension racing a revocation\n\t\t\t\t// matches nothing, and never brings the session back.\n\t\t\t\tconst written = await collection.raw.findOneAndUpdate(\n\t\t\t\t\t{ _id: id, revokedAt: null },\n\t\t\t\t\t{ $set: { expiresAt } },\n\t\t\t\t\t{ returnDocument: 'after' },\n\t\t\t\t);\n\t\t\t\treturn written === null ? null : toSession(written);\n\t\t\t}),\n\n\t\trevokeSession: (id, at) =>\n\t\t\trun$('revokeSession', async () => {\n\t\t\t\t// A pipeline, so a session already revoked keeps its first\n\t\t\t\t// `revokedAt` and still counts as matched.\n\t\t\t\tconst result = await collection.raw.updateOne({ _id: id }, [\n\t\t\t\t\t{ $set: { revokedAt: { $ifNull: ['$revokedAt', at] } } },\n\t\t\t\t]);\n\t\t\t\treturn result.matchedCount === 1;\n\t\t\t}),\n\n\t\trevokeUserSessions: (userId, at, except) =>\n\t\t\trun$('revokeUserSessions', async () => {\n\t\t\t\tconst result = await collection.raw.updateMany(\n\t\t\t\t\texcept === undefined\n\t\t\t\t\t\t? { userId, revokedAt: null }\n\t\t\t\t\t\t: { userId, revokedAt: null, _id: { $ne: except } },\n\t\t\t\t\t{ $set: { revokedAt: at } },\n\t\t\t\t);\n\t\t\t\treturn result.modifiedCount;\n\t\t\t}),\n\n\t\tdeleteUserSessions: (userId) =>\n\t\t\trun$('deleteUserSessions', async () => {\n\t\t\t\tconst result = await collection.raw.deleteMany({ userId });\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\n\t};\n}\n\nfunction tokenStore(db: Db): TokenStore {\n\tconst collection = getCollection(db, tokens);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('tokens', operation, body);\n\n\treturn {\n\t\tinsertToken: (record) =>\n\t\t\trun$('insertToken', async () => {\n\t\t\t\t// `_id` is the hash, the only unique index: a duplicate is a retry.\n\t\t\t\tawait settle(collection.raw.insertOne(toTokenDocument(record)));\n\t\t\t}),\n\n\t\tconsumeToken: (tokenHash, kind, at) =>\n\t\t\trun$('consumeToken', async () => {\n\t\t\t\t// **One conditional write**, answering the document as it was\n\t\t\t\t// before it. The pipeline keeps a first `spentAt`, so exactly one\n\t\t\t\t// caller ever reads `spentAt: null`.\n\t\t\t\tconst before = await collection.raw.findOneAndUpdate(\n\t\t\t\t\t{ _id: tokenHash, kind },\n\t\t\t\t\t[{ $set: { spentAt: { $ifNull: ['$spentAt', at] } } }],\n\t\t\t\t\t{ returnDocument: 'before' },\n\t\t\t\t);\n\t\t\t\treturn before === null ? null : toToken(before);\n\t\t\t}),\n\n\t\tcountAttempt: (tokenHash, kind) =>\n\t\t\trun$('countAttempt', async () => {\n\t\t\t\t// **One conditional write**, answering the document after it: an\n\t\t\t\t// unspent token gets one more attempt, atomically. A spent one is\n\t\t\t\t// matched by the second read below, and written nothing.\n\t\t\t\tconst after = await collection.raw.findOneAndUpdate(\n\t\t\t\t\t{ _id: tokenHash, kind, spentAt: null },\n\t\t\t\t\t{ $inc: { attempts: 1 } },\n\t\t\t\t\t{ returnDocument: 'after' },\n\t\t\t\t);\n\t\t\t\tif (after !== null) return toToken(after);\n\t\t\t\tconst spent = await collection.raw.findOne({ _id: tokenHash, kind });\n\t\t\t\treturn spent === null ? null : toToken(spent);\n\t\t\t}),\n\n\t\tspendUserTokens: (userId, kind, at, except) =>\n\t\t\trun$('spendUserTokens', async () => {\n\t\t\t\t// Each document is matched and written in one step, as\n\t\t\t\t// `consumeToken` does: a token it spends at the same moment is\n\t\t\t\t// counted by exactly one of the two.\n\t\t\t\tconst result = await collection.raw.updateMany(\n\t\t\t\t\t{\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tkind,\n\t\t\t\t\t\tspentAt: null,\n\t\t\t\t\t\t...(except === undefined ? {} : { _id: { $ne: except } }),\n\t\t\t\t\t},\n\t\t\t\t\t{ $set: { spentAt: at } },\n\t\t\t\t);\n\t\t\t\treturn result.modifiedCount;\n\t\t\t}),\n\n\t\tdeleteUserTokens: (userId) =>\n\t\t\trun$('deleteUserTokens', async () => {\n\t\t\t\tconst result = await collection.raw.deleteMany({ userId });\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\n\t};\n}\n\n// ─── Documents and records ────────────────────────────────────────────────\n//\n// Each record is rebuilt field by field, never spread from a document: a\n// spread would hand the core `_id` and the `id` getter `@nxgt/mongo` adds,\n// and the port's records are exactly what it declares.\n\nfunction toUserDocument(record: UserRecord): UserDocument {\n\treturn structuredClone({\n\t\t_id: record.id,\n\t\ttype: record.type,\n\t\tschemaVersion: record.schemaVersion,\n\t\tactive: record.active,\n\t\tfields: record.fields,\n\t\tlogins: [...record.logins],\n\t\tpassword: record.password,\n\t\tsecondFactor: record.secondFactor,\n\t\temailVerifiedAt: record.emailVerifiedAt,\n\t\tversion: record.version,\n\t\tcreatedAt: record.createdAt,\n\t\tupdatedAt: record.updatedAt,\n\t});\n}\n\n/**\n * The fields a patch names, as one `$set`. A key present with `undefined` is\n * absent, as the port says — never an erasure. `password: null` is named, and\n * removes the password.\n */\nfunction toUserSet(patch: UserPatch): Record<string, unknown> {\n\tconst set: Record<string, unknown> = { updatedAt: patch.updatedAt };\n\n\tfor (const field of [\n\t\t'schemaVersion',\n\t\t'active',\n\t\t'fields',\n\t\t'logins',\n\t\t'password',\n\t\t'secondFactor',\n\t\t'emailVerifiedAt',\n\t] as const) {\n\t\tif (patch[field] !== undefined) set[field] = patch[field];\n\t}\n\n\treturn structuredClone(set);\n}\n\nfunction toUser(document: UserDocument): UserRecord {\n\tconst password = document.password;\n\tconst secondFactor = document.secondFactor ?? null;\n\treturn {\n\t\tid: document._id as Id,\n\t\ttype: document.type,\n\t\tschemaVersion: document.schemaVersion,\n\t\tactive: document.active,\n\t\tfields: document.fields as UserRecord['fields'],\n\t\tlogins: [...document.logins],\n\t\tpassword:\n\t\t\tpassword === null\n\t\t\t\t? null\n\t\t\t\t: { hash: password.hash, updatedAt: password.updatedAt },\n\t\tsecondFactor:\n\t\t\tsecondFactor === null\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\tmethod: secondFactor.method,\n\t\t\t\t\t\tsecret: secondFactor.secret,\n\t\t\t\t\t\tconfirmedAt: secondFactor.confirmedAt,\n\t\t\t\t\t\tlastStep: secondFactor.lastStep,\n\t\t\t\t\t},\n\t\temailVerifiedAt: document.emailVerifiedAt,\n\t\tversion: document.version,\n\t\tcreatedAt: document.createdAt,\n\t\tupdatedAt: document.updatedAt,\n\t};\n}\n\nfunction toSessionDocument(record: SessionRecord): SessionDocument {\n\treturn {\n\t\t_id: record.id,\n\t\ttokenHash: record.tokenHash,\n\t\tuserId: record.userId,\n\t\tauthenticatedAt: record.authenticatedAt,\n\t\texpiresAt: record.expiresAt,\n\t\trevokedAt: record.revokedAt,\n\t\tcreatedAt: record.createdAt,\n\t};\n}\n\nfunction toSession(document: SessionDocument): SessionRecord {\n\treturn {\n\t\tid: document._id,\n\t\ttokenHash: document.tokenHash,\n\t\tuserId: document.userId as Id,\n\t\tauthenticatedAt: document.authenticatedAt,\n\t\texpiresAt: document.expiresAt,\n\t\trevokedAt: document.revokedAt,\n\t\tcreatedAt: document.createdAt,\n\t};\n}\n\nfunction toTokenDocument(record: TokenRecord): TokenDocument {\n\treturn {\n\t\t_id: record.tokenHash,\n\t\tkind: record.kind,\n\t\tuserId: record.userId,\n\t\taddress: record.address,\n\t\tcodeHash: record.codeHash,\n\t\tattempts: record.attempts,\n\t\texpiresAt: record.expiresAt,\n\t\tspentAt: record.spentAt,\n\t\tcreatedAt: record.createdAt,\n\t};\n}\n\nfunction toToken(document: TokenDocument): TokenRecord {\n\treturn {\n\t\ttokenHash: document._id,\n\t\tkind: document.kind,\n\t\tuserId: document.userId as Id,\n\t\taddress: document.address,\n\t\tcodeHash: document.codeHash ?? null,\n\t\tattempts: document.attempts ?? 0,\n\t\texpiresAt: document.expiresAt,\n\t\tspentAt: document.spentAt,\n\t\tcreatedAt: document.createdAt,\n\t};\n}\n"
|
|
7
|
+
"import type { RelationStore } from '@nxgt/janus/permissions';\nimport {\n\tgetCollection,\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { run } from '../translate';\nimport { relations } from './collection';\nimport type { RelationContext } from './context';\nimport { findEntities, findObjects, findSubjectSets, has } from './reads';\nimport { deleteEntity, write } from './writes';\n\nexport { relations } from './collection';\n\n/**\n * The relation store `permissions()` takes, over one MongoDB database — and\n * the one `janus({ relations })` deletes a user's tuples from.\n *\n * ```ts\n * await syncMongoRelations(db); // a deployment step: creates the indexes\n * const access = permissions({ model, store: createMongoRelations(db) });\n * ```\n *\n * **A write of more than one tuple is a transaction**, which MongoDB runs on\n * a replica set only — as every production deployment is. `grant` and\n * `revoke` write one tuple, and run anywhere. The transaction is not retried\n * here: the driver's `withTransaction` would retry an outage for two minutes\n * before answering, and a write is idempotent, so the caller retries.\n */\nexport function createMongoRelations(db: Db): RelationStore {\n\tconst context: RelationContext = {\n\t\tdb,\n\t\tcollection: getCollection(db, relations).raw,\n\t};\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('relations', operation, body);\n\n\treturn {\n\t\twrite: ({ add = [], remove = [] }) =>\n\t\t\trun$('write', () => write(context, add, remove)),\n\t\thas: (tuple) => run$('has', () => has(context, tuple)),\n\t\tfindSubjectSets: (object, relation) =>\n\t\t\trun$('findSubjectSets', () => findSubjectSets(context, object, relation)),\n\t\tfindEntities: (object, relation) =>\n\t\t\trun$('findEntities', () => findEntities(context, object, relation)),\n\t\t// Unpacked before `run$`, as `write`'s is and as on develop, so a\n\t\t// malformed request throws instead of reading as an outage.\n\t\tfindObjects: ({ type, relation, subject, after, limit }) =>\n\t\t\trun$('findObjects', () =>\n\t\t\t\tfindObjects(context, { type, relation, subject, after, limit }),\n\t\t\t),\n\t\tdeleteEntity: (entity) =>\n\t\t\trun$('deleteEntity', () => deleteEntity(context, entity)),\n\t};\n}\n\n/**\n * Creates the collection and its indexes, and says what it changed. A\n * deployment step, as `syncMongoStores` is — and a separate one, because an\n * application that only authenticates keeps no tuples.\n */\nexport function syncMongoRelations(\n\tdb: Db,\n\toptions?: SyncOptions,\n): Promise<SyncReport[]> {\n\treturn syncCollections(db, [relations], options);\n}\n",
|
|
8
|
+
"import { JanusError, StoreConflict, StoreFailure } from '@nxgt/janus';\n\n/**\n * **The driver's errors, as the port's.** One file, and one default.\n *\n * | What happens | Becomes |\n * | --- | --- |\n * | A duplicate key on `(type, logins)` | `StoreConflict('login')`, carrying `login` and `userType` |\n * | A duplicate key on `_id` | nothing — the insert is a retry, and answers what is stored |\n * | A duplicate key on **any other index** | **`StoreFailure`** |\n * | A `$jsonSchema` validation failure (121) | **`StoreFailure`** |\n * | Anything else, absolutely | `StoreFailure`, with the driver error as `cause` |\n *\n * The two bold rows go against the obvious reading. A duplicate on an index\n * the adapter did not mean to collide on is an adapter bug: reporting it as\n * `LOGIN_TAKEN` would tell somebody their login is taken when it is\n * not. And the core has validated every record before it reaches a store, so a\n * validator refusing one means the adapter and the port disagree — never the\n * caller's fault, never a 400.\n *\n * **The default is a failure, not an absence.** That branch is what keeps the\n * port's first rule: nothing here answers `null` for an error.\n */\n\n/** Runs one port method, and makes every rejection one the port allows. */\nexport async function run<T>(\n\tslot: 'users' | 'sessions' | 'tokens' | 'relations',\n\toperation: string,\n\tbody: () => Promise<T>,\n): Promise<T> {\n\ttry {\n\t\treturn await body();\n\t} catch (error) {\n\t\t// Already the port's — a conflict, a NOT_FOUND after a read — or a\n\t\t// failure raised below with its own message.\n\t\tif (error instanceof JanusError) throw error;\n\t\tthrow new StoreFailure(`${slot}.${operation}: the store could not answer`, {\n\t\t\tslot,\n\t\t\toperation,\n\t\t\tcause: error,\n\t\t});\n\t}\n}\n\n/** The shape of the driver's `E11000`, as far as this file reads it. */\nexport interface DuplicateKey {\n\treadonly code: 11000;\n\treadonly keyPattern?: Readonly<Record<string, unknown>>;\n\treadonly keyValue?: Readonly<Record<string, unknown>>;\n}\n\nexport function isDuplicateKey(error: unknown): error is DuplicateKey {\n\treturn (\n\t\ttypeof error === 'object' &&\n\t\terror !== null &&\n\t\t(error as { code?: unknown }).code === 11000\n\t);\n}\n\n/**\n * Settles a write: what it answered when it landed, or the duplicate-key error\n * when an index refused it. **Any other rejection is rethrown**, so a caller\n * of this is only ever told \"written\" or \"duplicate\", and never \"nothing\".\n */\nexport function settle<T>(\n\twrite: Promise<T>,\n): Promise<\n\t| { readonly written: T }\n\t| { readonly written?: never; duplicate: DuplicateKey }\n> {\n\treturn write.then(\n\t\t(written) => ({ written }),\n\t\t(error: unknown) => {\n\t\t\tif (isDuplicateKey(error)) return { duplicate: error };\n\t\t\tthrow error;\n\t\t},\n\t);\n}\n\n/** A login one type holds. */\nexport interface TypedLogin {\n\treadonly type: string;\n\treadonly login: string;\n}\n\n/**\n * The login a duplicate names, when it is the login index that refused the\n * write — and `null` for every other index, which the caller then reports as\n * a failure.\n */\nexport function takenLogin(duplicate: DuplicateKey): TypedLogin | null {\n\tconst type = duplicate.keyValue?.type;\n\tconst login = duplicate.keyValue?.logins;\n\tif (typeof type !== 'string' || typeof login !== 'string') return null;\n\treturn { type, login };\n}\n\n/** The refusal the port promises for a taken login. */\nexport function loginTaken(\n\toperation: string,\n\t{ type, login }: TypedLogin,\n\tcause: unknown,\n): StoreConflict {\n\treturn new StoreConflict(\n\t\t'login',\n\t\t`${operation}: the login is taken by another ${type}`,\n\t\t{ login, userType: type, operation, cause },\n\t);\n}\n\n/** A duplicate on the login index as the port's conflict, and any other as a bug. */\nexport function refuseDuplicate(\n\toperation: string,\n\tduplicate: DuplicateKey,\n): StoreConflict | StoreFailure {\n\tconst taken = takenLogin(duplicate);\n\treturn taken === null\n\t\t? unexpectedDuplicate('users', operation, duplicate)\n\t\t: loginTaken(operation, taken, duplicate);\n}\n\n/** A duplicate on an index the adapter never meant to collide on: a bug, reported as one. */\nexport function unexpectedDuplicate(\n\tslot: 'users' | 'sessions' | 'tokens',\n\toperation: string,\n\tduplicate: DuplicateKey,\n): StoreFailure {\n\treturn new StoreFailure(\n\t\t`${slot}.${operation}: a duplicate key on ${Object.keys(duplicate.keyPattern ?? {}).join(', ') || 'an unknown index'}, which this adapter never writes on purpose`,\n\t\t{ slot, operation, cause: duplicate },\n\t);\n}\n",
|
|
9
|
+
"import { type DocumentOf, defineCollection } from '@nxgt/mongo';\nimport { z } from 'zod';\n\n/**\n * The tuples, one document each, **keyed by the tuple itself**:\n * `{ _id: { object: { type, id }, relation, subject: { type, id, relation? } } }`.\n *\n * The key is the port's `RelationTuple`, as written, so uniqueness is the\n * `_id` index every collection has — a constraint, never a read — an insert\n * of a stored tuple is a retry, and a document read in a shell is the tuple\n * in the code. A subject set carries `relation`, an entity does not, so\n * `team:t1#members` and `team:t1` are two keys.\n *\n * The key is always built in one fixed field order: MongoDB compares\n * embedded documents field by field, in order.\n */\nexport const relations = defineCollection({\n\tname: 'relations',\n\tschema: z.object({\n\t\t_id: z.object({\n\t\t\tobject: z.object({ type: z.string(), id: z.string() }),\n\t\t\trelation: z.string(),\n\t\t\tsubject: z.object({\n\t\t\t\ttype: z.string(),\n\t\t\t\tid: z.string(),\n\t\t\t\trelation: z.string().optional(),\n\t\t\t}),\n\t\t}),\n\t}),\n\tindexes: [\n\t\t/** One hop forwards: `findSubjectSets`, `findEntities`, and `deleteEntity` as the object. */\n\t\t{\n\t\t\tkey: { '_id.object.type': 1, '_id.object.id': 1, '_id.relation': 1 },\n\t\t\tname: 'objectRelation',\n\t\t},\n\t\t/**\n\t\t * The reverse index `list()` walks, in the order `findObjects` pages it:\n\t\t * every equality first, the object id last. Its prefix is `deleteEntity`\n\t\t * as a subject. An entity has no `subject.relation`, which the index\n\t\t * holds as `null` and `findObjects` asks for as `null`.\n\t\t */\n\t\t{\n\t\t\tkey: {\n\t\t\t\t'_id.subject.type': 1,\n\t\t\t\t'_id.subject.id': 1,\n\t\t\t\t'_id.subject.relation': 1,\n\t\t\t\t'_id.relation': 1,\n\t\t\t\t'_id.object.type': 1,\n\t\t\t\t'_id.object.id': 1,\n\t\t\t},\n\t\t\tname: 'subjectObjects',\n\t\t},\n\t],\n});\n\nexport type RelationDocument = DocumentOf<typeof relations>;\nexport type Key = RelationDocument['_id'];\n",
|
|
10
|
+
"import {\n\ttype CursorPage,\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype SubjectSet,\n} from '@nxgt/janus';\nimport type { ObjectPageRequest } from '@nxgt/janus/permissions';\nimport type { Filter } from 'mongodb';\nimport type { RelationDocument } from './collection';\nimport type { RelationContext } from './context';\nimport { keyOf } from './tuples';\n\nexport async function has(\n\t{ collection }: RelationContext,\n\ttuple: RelationTuple,\n): Promise<boolean> {\n\treturn (\n\t\t(await collection.findOne(\n\t\t\t{ _id: keyOf(tuple) },\n\t\t\t{ projection: { _id: 1 } },\n\t\t)) !== null\n\t);\n}\n\nexport async function findSubjectSets(\n\tcontext: RelationContext,\n\tobject: Entity,\n\trelation: string,\n): Promise<SubjectSet[]> {\n\treturn (await holding(context, object, relation, true)).map(\n\t\t(subject): SubjectSet => ({\n\t\t\ttype: subject.type,\n\t\t\tid: subject.id,\n\t\t\trelation: subject.relation as string,\n\t\t}),\n\t);\n}\n\nexport async function findEntities(\n\tcontext: RelationContext,\n\tobject: Entity,\n\trelation: string,\n): Promise<Entity[]> {\n\treturn (await holding(context, object, relation, false)).map(\n\t\t(subject): Entity => ({ type: subject.type, id: subject.id }),\n\t);\n}\n\n/** One hop from an object: its subject sets, or its entities. */\nasync function holding(\n\t{ collection }: RelationContext,\n\tobject: Entity,\n\trelation: string,\n\tsets: boolean,\n) {\n\treturn (\n\t\tawait collection\n\t\t\t.find({\n\t\t\t\t'_id.object.type': object.type,\n\t\t\t\t'_id.object.id': object.id,\n\t\t\t\t'_id.relation': relation,\n\t\t\t\t'_id.subject.relation': { $exists: sets },\n\t\t\t})\n\t\t\t.toArray()\n\t).map((document) => document._id.subject);\n}\n\n/** A page of object ids, in id order, after the cursor. */\nexport async function findObjects(\n\t{ collection }: RelationContext,\n\t{ type, relation, subject, after, limit }: ObjectPageRequest,\n): Promise<CursorPage<string>> {\n\tconst filter: Filter<RelationDocument> = {\n\t\t'_id.subject.type': subject.type,\n\t\t'_id.subject.id': subject.id,\n\t\t// `null` matches the missing field: an entity, not a set.\n\t\t'_id.subject.relation': isSubjectSet(subject) ? subject.relation : null,\n\t\t'_id.relation': relation,\n\t\t'_id.object.type': type,\n\t\t...(after === null ? {} : { '_id.object.id': { $gt: after } }),\n\t};\n\tconst found = await collection\n\t\t.find(filter, { projection: { '_id.object.id': 1 } })\n\t\t.sort({ '_id.object.id': 1 })\n\t\t.limit(limit + 1)\n\t\t.toArray();\n\tconst items = found.slice(0, limit).map((document) => document._id.object.id);\n\tconst last = items.at(-1);\n\treturn {\n\t\titems,\n\t\tnextCursor: found.length > limit && last !== undefined ? last : null,\n\t};\n}\n",
|
|
11
|
+
"import { isSubjectSet, type RelationTuple, type Subject } from '@nxgt/janus';\nimport type { Key } from './collection';\n\n/** The `_id` of a tuple, in the one field order MongoDB compares. */\nexport function keyOf(tuple: RelationTuple): Key {\n\treturn {\n\t\tobject: { type: tuple.object.type, id: tuple.object.id },\n\t\trelation: tuple.relation,\n\t\tsubject: subjectKey(tuple.subject),\n\t};\n}\n\nfunction subjectKey(subject: Subject): Key['subject'] {\n\treturn isSubjectSet(subject)\n\t\t? { type: subject.type, id: subject.id, relation: subject.relation }\n\t\t: { type: subject.type, id: subject.id };\n}\n",
|
|
12
|
+
"import type { Entity, RelationTuple } from '@nxgt/janus';\nimport type { ClientSession } from 'mongodb';\nimport type { Key } from './collection';\nimport type { RelationContext, Relations } from './context';\nimport { keyOf } from './tuples';\n\n/** One tuple in one driver call; more in one transaction. */\nexport async function write(\n\t{ db, collection }: RelationContext,\n\tadd: readonly RelationTuple[],\n\tremove: readonly RelationTuple[],\n): Promise<void> {\n\tconst removals = remove.map(keyOf);\n\tconst additions = add.map(keyOf);\n\n\tif (removals.length + additions.length <= 1) {\n\t\treturn apply(collection, additions, removals);\n\t}\n\n\tconst session = db.client.startSession();\n\ttry {\n\t\tsession.startTransaction();\n\t\tawait apply(collection, additions, removals, session);\n\t\tawait session.commitTransaction();\n\t} finally {\n\t\t// Ends — and so aborts — a transaction that did not commit.\n\t\tawait session.endSession();\n\t}\n}\n\n/** Removals first, then additions. */\nasync function apply(\n\tcollection: Relations,\n\tadditions: Key[],\n\tremovals: Key[],\n\tsession?: ClientSession,\n): Promise<void> {\n\tif (removals.length > 0) {\n\t\tawait collection.deleteMany(\n\t\t\t{ _id: { $in: removals } },\n\t\t\tsession === undefined ? {} : { session },\n\t\t);\n\t}\n\tif (additions.length > 0) {\n\t\t// A replace of a document by itself: a stored tuple is a no-op,\n\t\t// an absent one is inserted, and concurrent writers of one\n\t\t// tuple meet on the `_id` index, which the server retries.\n\t\tawait collection.bulkWrite(\n\t\t\tadditions.map((key) => ({\n\t\t\t\treplaceOne: {\n\t\t\t\t\tfilter: { _id: key },\n\t\t\t\t\treplacement: { _id: key },\n\t\t\t\t\tupsert: true,\n\t\t\t\t},\n\t\t\t})),\n\t\t\tsession === undefined ? {} : { session },\n\t\t);\n\t}\n}\n\n/** Every tuple naming the entity, as object or as subject. */\nexport async function deleteEntity(\n\t{ collection }: RelationContext,\n\tentity: Entity,\n): Promise<number> {\n\tconst result = await collection.deleteMany({\n\t\t$or: [\n\t\t\t{ '_id.object.type': entity.type, '_id.object.id': entity.id },\n\t\t\t{ '_id.subject.type': entity.type, '_id.subject.id': entity.id },\n\t\t],\n\t});\n\treturn result.deletedCount;\n}\n",
|
|
13
|
+
"import type { JanusStores } from '@nxgt/janus';\nimport {\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { janusCollections } from '../collections';\nimport { sessionStore } from './sessions';\nimport { tokenStore } from './tokens';\nimport { userStore } from './users';\n\n/**\n * The three stores `janus()` takes, over one MongoDB database.\n *\n * ```ts\n * const db = (await connectMongo(process.env.MONGO_URI)).db;\n * await syncMongoStores(db); // a deployment step: creates the indexes\n * const auth = janus({ user, password: { login: 'email' }, store: createMongoStores(db), hasher });\n * ```\n *\n * **Reads go through `@nxgt/mongo`'s `find*`, never its `get*`.** `findById`\n * answers `undefined` for an absence, so an absence arrives as a *value*,\n * turned into `null` here, and everything that arrives as a rejection is a\n * failure. There is no path on which an absence and an outage travel the same\n * channel, so there is none on which a `catch` could confuse them.\n *\n * **Every conditional write is one driver call** on `raw`, the driver's own\n * collection: `@nxgt/mongo` names no update conditioned on anything but its\n * own lock, and the port needs `version`, `revokedAt` and `kind`.\n *\n * `sessions.deleteExpiredSessions` is not implemented: a TTL index drops lapsed\n * sessions, so `sessions.collectExpired()` answers `UNSUPPORTED` — which is\n * what the port asks of a store with its own expiry.\n */\nexport function createMongoStores(db: Db): JanusStores {\n\treturn {\n\t\tusers: userStore(db),\n\t\tsessions: sessionStore(db),\n\t\ttokens: tokenStore(db),\n\t};\n}\n\n/**\n * Creates the three collections, their validators and their indexes, and says\n * what it changed. Run it twice and the second run sends nothing.\n *\n * A deployment step, never a request-time one: it needs the `dbAdmin` role,\n * and the core never calls it (rule 6). An application that deploys with\n * `@nxgt/mongo`'s `syncAll(db)` already syncs these three with its own.\n */\nexport function syncMongoStores(\n\tdb: Db,\n\toptions?: SyncOptions,\n): Promise<SyncReport[]> {\n\treturn syncCollections(db, janusCollections, options);\n}\n",
|
|
14
|
+
"import type { SessionStore } from '@nxgt/janus';\nimport { getCollection, type TypedCollection } from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { sessions } from '../collections';\nimport { run, settle, unexpectedDuplicate } from '../translate';\nimport { toSession, toSessionDocument } from './records';\n\ntype Sessions = TypedCollection<typeof sessions>;\n\nexport function sessionStore(db: Db): SessionStore {\n\tconst collection = getCollection(db, sessions);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('sessions', operation, body);\n\n\treturn {\n\t\tinsertSession: (record) =>\n\t\t\trun$('insertSession', async () => {\n\t\t\t\tconst outcome = await settle(\n\t\t\t\t\tcollection.raw.insertOne(toSessionDocument(record)),\n\t\t\t\t);\n\t\t\t\tif (!('duplicate' in outcome)) return;\n\n\t\t\t\t// Idempotent under retry: a session with this id is already there.\n\t\t\t\tif ((await collection.findById(record.id)) !== undefined) return;\n\t\t\t\tthrow unexpectedDuplicate(\n\t\t\t\t\t'sessions',\n\t\t\t\t\t'insertSession',\n\t\t\t\t\toutcome.duplicate,\n\t\t\t\t);\n\t\t\t}),\n\n\t\tfindSessionByTokenHash: (tokenHash) =>\n\t\t\trun$('findSessionByTokenHash', async () => {\n\t\t\t\tconst found = await collection.findFirst({ tokenHash });\n\t\t\t\treturn found === undefined ? null : toSession(found);\n\t\t\t}),\n\n\t\textendSession: (id, expiresAt) =>\n\t\t\trun$('extendSession', async () => {\n\t\t\t\t// `revokedAt: null` in the filter: an extension racing a revocation\n\t\t\t\t// matches nothing, and never brings the session back.\n\t\t\t\tconst written = await collection.raw.findOneAndUpdate(\n\t\t\t\t\t{ _id: id, revokedAt: null },\n\t\t\t\t\t{ $set: { expiresAt } },\n\t\t\t\t\t{ returnDocument: 'after' },\n\t\t\t\t);\n\t\t\t\treturn written === null ? null : toSession(written);\n\t\t\t}),\n\n\t\trevokeSession: (id, at) =>\n\t\t\trun$('revokeSession', () => revokeSession(collection, id, at)),\n\n\t\trevokeUserSessions: (userId, at, except) =>\n\t\t\trun$('revokeUserSessions', () =>\n\t\t\t\trevokeUserSessions(collection, userId, at, except),\n\t\t\t),\n\n\t\tdeleteUserSessions: (userId) =>\n\t\t\trun$('deleteUserSessions', async () => {\n\t\t\t\tconst result = await collection.raw.deleteMany({ userId });\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\n\t};\n}\n\nasync function revokeSession(\n\tcollection: Sessions,\n\tid: string,\n\tat: Date,\n): Promise<boolean> {\n\t// A pipeline, so a session already revoked keeps its first\n\t// `revokedAt` and still counts as matched.\n\tconst result = await collection.raw.updateOne({ _id: id }, [\n\t\t{ $set: { revokedAt: { $ifNull: ['$revokedAt', at] } } },\n\t]);\n\treturn result.matchedCount === 1;\n}\n\nasync function revokeUserSessions(\n\tcollection: Sessions,\n\tuserId: string,\n\tat: Date,\n\texcept: string | undefined,\n): Promise<number> {\n\tconst result = await collection.raw.updateMany(\n\t\texcept === undefined\n\t\t\t? { userId, revokedAt: null }\n\t\t\t: { userId, revokedAt: null, _id: { $ne: except } },\n\t\t{ $set: { revokedAt: at } },\n\t);\n\treturn result.modifiedCount;\n}\n",
|
|
15
|
+
"import type {\n\tId,\n\tSessionRecord,\n\tTokenRecord,\n\tUserPatch,\n\tUserRecord,\n} from '@nxgt/janus';\nimport type { DocumentOf } from '@nxgt/mongo';\nimport type { sessions, tokens, users } from '../collections';\n\n// ─── Documents and records ────────────────────────────────────────────────\n//\n// Each record is rebuilt field by field, never spread from a document: a\n// spread would hand the core `_id` and the `id` getter `@nxgt/mongo` adds,\n// and the port's records are exactly what it declares.\n\ntype UserDocument = DocumentOf<typeof users>;\ntype SessionDocument = DocumentOf<typeof sessions>;\ntype TokenDocument = DocumentOf<typeof tokens>;\n\nexport function toUserDocument(record: UserRecord): UserDocument {\n\treturn structuredClone({\n\t\t_id: record.id,\n\t\ttype: record.type,\n\t\tschemaVersion: record.schemaVersion,\n\t\tactive: record.active,\n\t\tfields: record.fields,\n\t\tlogins: [...record.logins],\n\t\tpassword: record.password,\n\t\tsecondFactor: record.secondFactor,\n\t\temailVerifiedAt: record.emailVerifiedAt,\n\t\tversion: record.version,\n\t\tcreatedAt: record.createdAt,\n\t\tupdatedAt: record.updatedAt,\n\t});\n}\n\n/**\n * The fields a patch names, as one `$set`. A key present with `undefined` is\n * absent, as the port says — never an erasure. `password: null` is named, and\n * removes the password.\n */\nexport function toUserSet(patch: UserPatch): Record<string, unknown> {\n\tconst set: Record<string, unknown> = { updatedAt: patch.updatedAt };\n\n\tfor (const field of [\n\t\t'schemaVersion',\n\t\t'active',\n\t\t'fields',\n\t\t'logins',\n\t\t'password',\n\t\t'secondFactor',\n\t\t'emailVerifiedAt',\n\t] as const) {\n\t\tif (patch[field] !== undefined) set[field] = patch[field];\n\t}\n\n\treturn structuredClone(set);\n}\n\nexport function toUser(document: UserDocument): UserRecord {\n\tconst password = document.password;\n\tconst secondFactor = document.secondFactor ?? null;\n\treturn {\n\t\tid: document._id as Id,\n\t\ttype: document.type,\n\t\tschemaVersion: document.schemaVersion,\n\t\tactive: document.active,\n\t\tfields: document.fields as UserRecord['fields'],\n\t\tlogins: [...document.logins],\n\t\tpassword:\n\t\t\tpassword === null\n\t\t\t\t? null\n\t\t\t\t: { hash: password.hash, updatedAt: password.updatedAt },\n\t\tsecondFactor:\n\t\t\tsecondFactor === null\n\t\t\t\t? null\n\t\t\t\t: {\n\t\t\t\t\t\tmethod: secondFactor.method,\n\t\t\t\t\t\tsecret: secondFactor.secret,\n\t\t\t\t\t\tconfirmedAt: secondFactor.confirmedAt,\n\t\t\t\t\t\tlastStep: secondFactor.lastStep,\n\t\t\t\t\t},\n\t\temailVerifiedAt: document.emailVerifiedAt,\n\t\tversion: document.version,\n\t\tcreatedAt: document.createdAt,\n\t\tupdatedAt: document.updatedAt,\n\t};\n}\n\nexport function toSessionDocument(record: SessionRecord): SessionDocument {\n\treturn {\n\t\t_id: record.id,\n\t\ttokenHash: record.tokenHash,\n\t\tuserId: record.userId,\n\t\tauthenticatedAt: record.authenticatedAt,\n\t\texpiresAt: record.expiresAt,\n\t\trevokedAt: record.revokedAt,\n\t\tcreatedAt: record.createdAt,\n\t};\n}\n\nexport function toSession(document: SessionDocument): SessionRecord {\n\treturn {\n\t\tid: document._id,\n\t\ttokenHash: document.tokenHash,\n\t\tuserId: document.userId as Id,\n\t\tauthenticatedAt: document.authenticatedAt,\n\t\texpiresAt: document.expiresAt,\n\t\trevokedAt: document.revokedAt,\n\t\tcreatedAt: document.createdAt,\n\t};\n}\n\nexport function toTokenDocument(record: TokenRecord): TokenDocument {\n\treturn {\n\t\t_id: record.tokenHash,\n\t\tkind: record.kind,\n\t\tuserId: record.userId,\n\t\taddress: record.address,\n\t\tcodeHash: record.codeHash,\n\t\tattempts: record.attempts,\n\t\texpiresAt: record.expiresAt,\n\t\tspentAt: record.spentAt,\n\t\tcreatedAt: record.createdAt,\n\t};\n}\n\nexport function toToken(document: TokenDocument): TokenRecord {\n\treturn {\n\t\ttokenHash: document._id,\n\t\tkind: document.kind,\n\t\tuserId: document.userId as Id,\n\t\taddress: document.address,\n\t\tcodeHash: document.codeHash ?? null,\n\t\tattempts: document.attempts ?? 0,\n\t\texpiresAt: document.expiresAt,\n\t\tspentAt: document.spentAt,\n\t\tcreatedAt: document.createdAt,\n\t};\n}\n",
|
|
16
|
+
"import type { TokenKind, TokenRecord, TokenStore } from '@nxgt/janus';\nimport { getCollection, type TypedCollection } from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { tokens } from '../collections';\nimport { run, settle } from '../translate';\nimport { toToken, toTokenDocument } from './records';\n\ntype Tokens = TypedCollection<typeof tokens>;\n\nexport function tokenStore(db: Db): TokenStore {\n\tconst collection = getCollection(db, tokens);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('tokens', operation, body);\n\n\treturn {\n\t\tinsertToken: (record) =>\n\t\t\trun$('insertToken', async () => {\n\t\t\t\t// `_id` is the hash, the only unique index: a duplicate is a retry.\n\t\t\t\tawait settle(collection.raw.insertOne(toTokenDocument(record)));\n\t\t\t}),\n\n\t\tconsumeToken: (tokenHash, kind, at) =>\n\t\t\trun$('consumeToken', () => consumeToken(collection, tokenHash, kind, at)),\n\n\t\tcountAttempt: (tokenHash, kind) =>\n\t\t\trun$('countAttempt', () => countAttempt(collection, tokenHash, kind)),\n\n\t\tspendUserTokens: (userId, kind, at, except) =>\n\t\t\trun$('spendUserTokens', async () => {\n\t\t\t\t// Each document is matched and written in one step, as\n\t\t\t\t// `consumeToken` does: a token it spends at the same moment is\n\t\t\t\t// counted by exactly one of the two.\n\t\t\t\tconst result = await collection.raw.updateMany(\n\t\t\t\t\t{\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tkind,\n\t\t\t\t\t\tspentAt: null,\n\t\t\t\t\t\t...(except === undefined ? {} : { _id: { $ne: except } }),\n\t\t\t\t\t},\n\t\t\t\t\t{ $set: { spentAt: at } },\n\t\t\t\t);\n\t\t\t\treturn result.modifiedCount;\n\t\t\t}),\n\n\t\tdeleteUserTokens: (userId) =>\n\t\t\trun$('deleteUserTokens', async () => {\n\t\t\t\tconst result = await collection.raw.deleteMany({ userId });\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\n\t};\n}\n\nasync function consumeToken(\n\tcollection: Tokens,\n\ttokenHash: string,\n\tkind: TokenKind,\n\tat: Date,\n): Promise<TokenRecord | null> {\n\t// **One conditional write**, answering the document as it was\n\t// before it. The pipeline keeps a first `spentAt`, so exactly one\n\t// caller ever reads `spentAt: null`.\n\tconst before = await collection.raw.findOneAndUpdate(\n\t\t{ _id: tokenHash, kind },\n\t\t[{ $set: { spentAt: { $ifNull: ['$spentAt', at] } } }],\n\t\t{ returnDocument: 'before' },\n\t);\n\treturn before === null ? null : toToken(before);\n}\n\nasync function countAttempt(\n\tcollection: Tokens,\n\ttokenHash: string,\n\tkind: TokenKind,\n): Promise<TokenRecord | null> {\n\t// **One conditional write**, answering the document after it: an\n\t// unspent token gets one more attempt, atomically. A spent one is\n\t// matched by the second read below, and written nothing.\n\tconst after = await collection.raw.findOneAndUpdate(\n\t\t{ _id: tokenHash, kind, spentAt: null },\n\t\t{ $inc: { attempts: 1 } },\n\t\t{ returnDocument: 'after' },\n\t);\n\tif (after !== null) return toToken(after);\n\tconst spent = await collection.raw.findOne({ _id: tokenHash, kind });\n\treturn spent === null ? null : toToken(spent);\n}\n",
|
|
17
|
+
"import type { UserPatch, UserRecord, UserStore } from '@nxgt/janus';\nimport { NotFoundError, StoreConflict } from '@nxgt/janus';\nimport { getCollection, type TypedCollection } from '@nxgt/mongo';\nimport type { Db } from 'mongodb';\nimport { users } from '../collections';\nimport { refuseDuplicate, run, settle } from '../translate';\nimport { toUser, toUserDocument, toUserSet } from './records';\n\ntype Users = TypedCollection<typeof users>;\n\nexport function userStore(db: Db): UserStore {\n\tconst collection = getCollection(db, users);\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('users', operation, body);\n\n\treturn {\n\t\tinsertUser: (record) =>\n\t\t\trun$('insertUser', () => insertUser(collection, record)),\n\n\t\tfindUser: (id) => run$('findUser', () => findUser(collection, id)),\n\n\t\tfindUserByLogin: (type, login) =>\n\t\t\trun$('findUserByLogin', async () => {\n\t\t\t\tconst found = await collection.findFirst({ type, logins: login });\n\t\t\t\treturn found === undefined ? null : toUser(found);\n\t\t\t}),\n\n\t\tlistUsers: ({ type, after, limit }) =>\n\t\t\trun$('listUsers', async () => {\n\t\t\t\t// One more than the page, to know whether another follows.\n\t\t\t\tconst found = await collection.findMany({\n\t\t\t\t\tfilter: after === null ? { type } : { type, _id: { $gt: after } },\n\t\t\t\t\tsort: { _id: 1 },\n\t\t\t\t\tlimit: limit + 1,\n\t\t\t\t});\n\t\t\t\tconst items = found.slice(0, limit).map(toUser);\n\t\t\t\tconst last = items.at(-1);\n\n\t\t\t\treturn {\n\t\t\t\t\titems,\n\t\t\t\t\tnextCursor:\n\t\t\t\t\t\tfound.length > limit && last !== undefined ? last.id : null,\n\t\t\t\t};\n\t\t\t}),\n\n\t\tupdateUser: (id, patch, ifVersion) =>\n\t\t\trun$('updateUser', () => updateUser(collection, id, patch, ifVersion)),\n\n\t\tdeleteUser: (id) =>\n\t\t\trun$('deleteUser', async () => {\n\t\t\t\tconst result = await collection.raw.deleteOne({ _id: id });\n\t\t\t\treturn result.deletedCount === 1;\n\t\t\t}),\n\t};\n}\n\nasync function findUser(\n\tcollection: Users,\n\tid: string,\n): Promise<UserRecord | null> {\n\tconst found = await collection.findById(id);\n\treturn found === undefined ? null : toUser(found);\n}\n\nasync function insertUser(\n\tcollection: Users,\n\trecord: UserRecord,\n): Promise<UserRecord> {\n\tconst document = toUserDocument(record);\n\tconst outcome = await settle(collection.raw.insertOne(document));\n\tif (!('duplicate' in outcome)) return toUser(document);\n\n\t// A retry whose first attempt landed: whichever index reported it,\n\t// the user with this id is there, and is the answer. That includes\n\t// their own logins, which are not taken by them.\n\tconst stored = await findUser(collection, record.id);\n\tif (stored !== null) return stored;\n\tthrow refuseDuplicate('insertUser', outcome.duplicate);\n}\n\nasync function updateUser(\n\tcollection: Users,\n\tid: string,\n\tpatch: UserPatch,\n\tifVersion: number,\n): Promise<UserRecord> {\n\tconst outcome = await settle(\n\t\tcollection.raw.findOneAndUpdate(\n\t\t\t{ _id: id, version: ifVersion },\n\t\t\t{ $set: toUserSet(patch), $inc: { version: 1 } },\n\t\t\t{ returnDocument: 'after' },\n\t\t),\n\t);\n\n\tif ('duplicate' in outcome) {\n\t\tthrow refuseDuplicate('updateUser', outcome.duplicate);\n\t}\n\tif (outcome.written !== null) return toUser(outcome.written);\n\n\t// The write matched nothing, which alone cannot tell an unknown id\n\t// from a moved version: read again, and say which.\n\tconst stored = await findUser(collection, id);\n\tif (stored === null) {\n\t\tthrow new NotFoundError('updateUser: no user has this id', {\n\t\t\tuserId: id,\n\t\t\toperation: 'updateUser',\n\t\t});\n\t}\n\tthrow new StoreConflict(\n\t\t'version',\n\t\t`updateUser: expected version ${ifVersion}, found ${stored.version}`,\n\t\t{\n\t\t\tuserId: id,\n\t\t\texpectedVersion: ifVersion,\n\t\t\tactualVersion: stored.version,\n\t\t\toperation: 'updateUser',\n\t\t},\n\t);\n}\n"
|
|
10
18
|
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "
|
|
19
|
+
"mappings": "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",
|
|
20
|
+
"debugId": "BE876FE436B6984364756E2164756E21",
|
|
13
21
|
"names": []
|
|
14
22
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type DocumentOf } from '@nxgt/mongo';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
/**
|
|
4
|
+
* The tuples, one document each, **keyed by the tuple itself**:
|
|
5
|
+
* `{ _id: { object: { type, id }, relation, subject: { type, id, relation? } } }`.
|
|
6
|
+
*
|
|
7
|
+
* The key is the port's `RelationTuple`, as written, so uniqueness is the
|
|
8
|
+
* `_id` index every collection has — a constraint, never a read — an insert
|
|
9
|
+
* of a stored tuple is a retry, and a document read in a shell is the tuple
|
|
10
|
+
* in the code. A subject set carries `relation`, an entity does not, so
|
|
11
|
+
* `team:t1#members` and `team:t1` are two keys.
|
|
12
|
+
*
|
|
13
|
+
* The key is always built in one fixed field order: MongoDB compares
|
|
14
|
+
* embedded documents field by field, in order.
|
|
15
|
+
*/
|
|
16
|
+
export declare const relations: import("@nxgt/mongo").CollectionDefinition<import("@nxgt/mongo").StampedSchema<{
|
|
17
|
+
_id: z.ZodObject<{
|
|
18
|
+
object: z.ZodObject<{
|
|
19
|
+
type: z.ZodString;
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
relation: z.ZodString;
|
|
23
|
+
subject: z.ZodObject<{
|
|
24
|
+
type: z.ZodString;
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
relation: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
}, false, false, false, false>, import("@nxgt/mongo").StampNamesOf<false, false, false, false>>;
|
|
30
|
+
export type RelationDocument = DocumentOf<typeof relations>;
|
|
31
|
+
export type Key = RelationDocument['_id'];
|
|
32
|
+
//# sourceMappingURL=collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/relations/collection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;+FAqCpB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC5D,MAAM,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TypedCollection } from '@nxgt/mongo';
|
|
2
|
+
import type { Db } from 'mongodb';
|
|
3
|
+
import type { relations } from './collection';
|
|
4
|
+
export type Relations = TypedCollection<typeof relations>['raw'];
|
|
5
|
+
/**
|
|
6
|
+
* What every step queries: the database, which starts a write's session, and
|
|
7
|
+
* the driver's own `relations` collection.
|
|
8
|
+
*/
|
|
9
|
+
export interface RelationContext {
|
|
10
|
+
readonly db: Db;
|
|
11
|
+
readonly collection: Relations;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/relations/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC;IAChB,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;CAC/B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RelationStore } from '@nxgt/janus/permissions';
|
|
2
|
+
import { type SyncOptions, type SyncReport } from '@nxgt/mongo';
|
|
3
|
+
import type { Db } from 'mongodb';
|
|
4
|
+
export { relations } from './collection';
|
|
5
|
+
/**
|
|
6
|
+
* The relation store `permissions()` takes, over one MongoDB database — and
|
|
7
|
+
* the one `janus({ relations })` deletes a user's tuples from.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* await syncMongoRelations(db); // a deployment step: creates the indexes
|
|
11
|
+
* const access = permissions({ model, store: createMongoRelations(db) });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **A write of more than one tuple is a transaction**, which MongoDB runs on
|
|
15
|
+
* a replica set only — as every production deployment is. `grant` and
|
|
16
|
+
* `revoke` write one tuple, and run anywhere. The transaction is not retried
|
|
17
|
+
* here: the driver's `withTransaction` would retry an outage for two minutes
|
|
18
|
+
* before answering, and a write is idempotent, so the caller retries.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createMongoRelations(db: Db): RelationStore;
|
|
21
|
+
/**
|
|
22
|
+
* Creates the collection and its indexes, and says what it changed. A
|
|
23
|
+
* deployment step, as `syncMongoStores` is — and a separate one, because an
|
|
24
|
+
* application that only authenticates keeps no tuples.
|
|
25
|
+
*/
|
|
26
|
+
export declare function syncMongoRelations(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/relations/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAEN,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAOlC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,EAAE,GAAG,aAAa,CAyB1D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type CursorPage, type Entity, type RelationTuple, type SubjectSet } from '@nxgt/janus';
|
|
2
|
+
import type { ObjectPageRequest } from '@nxgt/janus/permissions';
|
|
3
|
+
import type { RelationContext } from './context';
|
|
4
|
+
export declare function has({ collection }: RelationContext, tuple: RelationTuple): Promise<boolean>;
|
|
5
|
+
export declare function findSubjectSets(context: RelationContext, object: Entity, relation: string): Promise<SubjectSet[]>;
|
|
6
|
+
export declare function findEntities(context: RelationContext, object: Entity, relation: string): Promise<Entity[]>;
|
|
7
|
+
/** A page of object ids, in id order, after the cursor. */
|
|
8
|
+
export declare function findObjects({ collection }: RelationContext, { type, relation, subject, after, limit }: ObjectPageRequest): Promise<CursorPage<string>>;
|
|
9
|
+
//# sourceMappingURL=reads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reads.d.ts","sourceRoot":"","sources":["../../src/relations/reads.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,UAAU,EACf,KAAK,MAAM,EAEX,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGjD,wBAAsB,GAAG,CACxB,EAAE,UAAU,EAAE,EAAE,eAAe,EAC/B,KAAK,EAAE,aAAa,GAClB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,wBAAsB,eAAe,CACpC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CAQvB;AAED,wBAAsB,YAAY,CACjC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAInB;AAqBD,2DAA2D;AAC3D,wBAAsB,WAAW,CAChC,EAAE,UAAU,EAAE,EAAE,eAAe,EAC/B,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,iBAAiB,GAC1D,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAqB7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tuples.d.ts","sourceRoot":"","sources":["../../src/relations/tuples.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAgB,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,qEAAqE;AACrE,wBAAgB,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,GAAG,CAM/C"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Entity, RelationTuple } from '@nxgt/janus';
|
|
2
|
+
import type { RelationContext } from './context';
|
|
3
|
+
/** One tuple in one driver call; more in one transaction. */
|
|
4
|
+
export declare function write({ db, collection }: RelationContext, add: readonly RelationTuple[], remove: readonly RelationTuple[]): Promise<void>;
|
|
5
|
+
/** Every tuple naming the entity, as object or as subject. */
|
|
6
|
+
export declare function deleteEntity({ collection }: RelationContext, entity: Entity): Promise<number>;
|
|
7
|
+
//# sourceMappingURL=writes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writes.d.ts","sourceRoot":"","sources":["../../src/relations/writes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,WAAW,CAAC;AAG5D,6DAA6D;AAC7D,wBAAsB,KAAK,CAC1B,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,eAAe,EACnC,GAAG,EAAE,SAAS,aAAa,EAAE,EAC7B,MAAM,EAAE,SAAS,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAiBf;AAgCD,8DAA8D;AAC9D,wBAAsB,YAAY,CACjC,EAAE,UAAU,EAAE,EAAE,eAAe,EAC/B,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAQjB"}
|
|
@@ -34,4 +34,4 @@ export declare function createMongoStores(db: Db): JanusStores;
|
|
|
34
34
|
* `@nxgt/mongo`'s `syncAll(db)` already syncs these three with its own.
|
|
35
35
|
*/
|
|
36
36
|
export declare function syncMongoStores(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
|
|
37
|
-
//# sourceMappingURL=
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/stores/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAMlC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,WAAW,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionRecord, TokenRecord, UserPatch, UserRecord } from '@nxgt/janus';
|
|
2
|
+
import type { DocumentOf } from '@nxgt/mongo';
|
|
3
|
+
import type { sessions, tokens, users } from '../collections';
|
|
4
|
+
type UserDocument = DocumentOf<typeof users>;
|
|
5
|
+
type SessionDocument = DocumentOf<typeof sessions>;
|
|
6
|
+
type TokenDocument = DocumentOf<typeof tokens>;
|
|
7
|
+
export declare function toUserDocument(record: UserRecord): UserDocument;
|
|
8
|
+
/**
|
|
9
|
+
* The fields a patch names, as one `$set`. A key present with `undefined` is
|
|
10
|
+
* absent, as the port says — never an erasure. `password: null` is named, and
|
|
11
|
+
* removes the password.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toUserSet(patch: UserPatch): Record<string, unknown>;
|
|
14
|
+
export declare function toUser(document: UserDocument): UserRecord;
|
|
15
|
+
export declare function toSessionDocument(record: SessionRecord): SessionDocument;
|
|
16
|
+
export declare function toSession(document: SessionDocument): SessionRecord;
|
|
17
|
+
export declare function toTokenDocument(record: TokenRecord): TokenDocument;
|
|
18
|
+
export declare function toToken(document: TokenDocument): TokenRecord;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../src/stores/records.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,aAAa,EACb,WAAW,EACX,SAAS,EACT,UAAU,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAQ9D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAC7C,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AACnD,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AAE/C,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAe/D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBnE;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,CA4BzD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAUxE;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa,CAUlE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAYlE;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAY5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/stores/sessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAOlC,wBAAgB,YAAY,CAAC,EAAE,EAAE,EAAE,GAAG,YAAY,CAsDjD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/stores/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAOlC,wBAAgB,UAAU,CAAC,EAAE,EAAE,EAAE,GAAG,UAAU,CAyC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/stores/users.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,SAAS,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAOlC,wBAAgB,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,SAAS,CA4C3C"}
|
package/dist/translate.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface TypedLogin {
|
|
|
53
53
|
export declare function takenLogin(duplicate: DuplicateKey): TypedLogin | null;
|
|
54
54
|
/** The refusal the port promises for a taken login. */
|
|
55
55
|
export declare function loginTaken(operation: string, { type, login }: TypedLogin, cause: unknown): StoreConflict;
|
|
56
|
+
/** A duplicate on the login index as the port's conflict, and any other as a bug. */
|
|
57
|
+
export declare function refuseDuplicate(operation: string, duplicate: DuplicateKey): StoreConflict | StoreFailure;
|
|
56
58
|
/** A duplicate on an index the adapter never meant to collide on: a bug, reported as one. */
|
|
57
59
|
export declare function unexpectedDuplicate(slot: 'users' | 'sessions' | 'tokens', operation: string, duplicate: DuplicateKey): StoreFailure;
|
|
58
60
|
//# sourceMappingURL=translate.d.ts.map
|
package/dist/translate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,2EAA2E;AAC3E,wBAAsB,GAAG,CAAC,CAAC,EAC1B,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,EACnD,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACvB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CACP;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;CAAE,GACvB;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CACvD,CAQA;AAED,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI,CAKrE;AAED,uDAAuD;AACvD,wBAAgB,UAAU,CACzB,SAAS,EAAE,MAAM,EACjB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,UAAU,EAC3B,KAAK,EAAE,OAAO,GACZ,aAAa,CAMf;AAED,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,EACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,YAAY,GACrB,YAAY,CAKd"}
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,2EAA2E;AAC3E,wBAAsB,GAAG,CAAC,CAAC,EAC1B,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,EACnD,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAaZ;AAED,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAMpE;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACvB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GACf,OAAO,CACP;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;CAAE,GACvB;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CACvD,CAQA;AAED,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI,CAKrE;AAED,uDAAuD;AACvD,wBAAgB,UAAU,CACzB,SAAS,EAAE,MAAM,EACjB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,UAAU,EAC3B,KAAK,EAAE,OAAO,GACZ,aAAa,CAMf;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,YAAY,GACrB,aAAa,GAAG,YAAY,CAK9B;AAED,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,QAAQ,EACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,YAAY,GACrB,YAAY,CAKd"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-mongo",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "The MongoDB adapter for @nxgt/janus: users, sessions, one-time tokens and permission tuples",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
]
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@nxgt/janus": "^0.
|
|
52
|
-
"@nxgt/mongo": "^0.
|
|
51
|
+
"@nxgt/janus": "^0.9.0",
|
|
52
|
+
"@nxgt/mongo": "^0.18.1",
|
|
53
53
|
"@types/bun": "^1.4.2",
|
|
54
54
|
"mongodb": "^7.6.0",
|
|
55
55
|
"mongodb-memory-server-core": "11.2.0",
|
|
56
56
|
"zod": "^4.6.5"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@nxgt/janus": "^0.
|
|
59
|
+
"@nxgt/janus": "^0.9.0",
|
|
60
60
|
"@nxgt/mongo": ">=0.17.0 <1",
|
|
61
61
|
"mongodb": ">=7.0.0 <8",
|
|
62
62
|
"typescript": "^6.0.3",
|
package/dist/relations.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { RelationStore } from '@nxgt/janus/permissions';
|
|
2
|
-
import { type SyncOptions, type SyncReport } from '@nxgt/mongo';
|
|
3
|
-
import type { Db } from 'mongodb';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
/**
|
|
6
|
-
* The tuples, one document each, **keyed by the tuple itself**:
|
|
7
|
-
* `{ _id: { object: { type, id }, relation, subject: { type, id, relation? } } }`.
|
|
8
|
-
*
|
|
9
|
-
* The key is the port's `RelationTuple`, as written, so uniqueness is the
|
|
10
|
-
* `_id` index every collection has — a constraint, never a read — an insert
|
|
11
|
-
* of a stored tuple is a retry, and a document read in a shell is the tuple
|
|
12
|
-
* in the code. A subject set carries `relation`, an entity does not, so
|
|
13
|
-
* `team:t1#members` and `team:t1` are two keys.
|
|
14
|
-
*
|
|
15
|
-
* The key is always built by {@link keyOf}, in one field order: MongoDB
|
|
16
|
-
* compares embedded documents field by field, in order.
|
|
17
|
-
*/
|
|
18
|
-
export declare const relations: import("@nxgt/mongo").CollectionDefinition<import("@nxgt/mongo").StampedSchema<{
|
|
19
|
-
_id: z.ZodObject<{
|
|
20
|
-
object: z.ZodObject<{
|
|
21
|
-
type: z.ZodString;
|
|
22
|
-
id: z.ZodString;
|
|
23
|
-
}, z.core.$strip>;
|
|
24
|
-
relation: z.ZodString;
|
|
25
|
-
subject: z.ZodObject<{
|
|
26
|
-
type: z.ZodString;
|
|
27
|
-
id: z.ZodString;
|
|
28
|
-
relation: z.ZodOptional<z.ZodString>;
|
|
29
|
-
}, z.core.$strip>;
|
|
30
|
-
}, z.core.$strip>;
|
|
31
|
-
}, false, false, false, false>, import("@nxgt/mongo").StampNamesOf<false, false, false, false>>;
|
|
32
|
-
/**
|
|
33
|
-
* The relation store `permissions()` takes, over one MongoDB database — and
|
|
34
|
-
* the one `janus({ relations })` deletes a user's tuples from.
|
|
35
|
-
*
|
|
36
|
-
* ```ts
|
|
37
|
-
* await syncMongoRelations(db); // a deployment step: creates the indexes
|
|
38
|
-
* const access = permissions({ model, store: createMongoRelations(db) });
|
|
39
|
-
* ```
|
|
40
|
-
*
|
|
41
|
-
* **A write of more than one tuple is a transaction**, which MongoDB runs on
|
|
42
|
-
* a replica set only — as every production deployment is. `grant` and
|
|
43
|
-
* `revoke` write one tuple, and run anywhere. The transaction is not retried
|
|
44
|
-
* here: the driver's `withTransaction` would retry an outage for two minutes
|
|
45
|
-
* before answering, and a write is idempotent, so the caller retries.
|
|
46
|
-
*/
|
|
47
|
-
export declare function createMongoRelations(db: Db): RelationStore;
|
|
48
|
-
/**
|
|
49
|
-
* Creates the collection and its indexes, and says what it changed. A
|
|
50
|
-
* deployment step, as `syncMongoStores` is — and a separate one, because an
|
|
51
|
-
* application that only authenticates keeps no tuples.
|
|
52
|
-
*/
|
|
53
|
-
export declare function syncMongoRelations(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
|
|
54
|
-
//# sourceMappingURL=relations.d.ts.map
|
package/dist/relations.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAIN,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAiB,EAAE,EAAU,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;+FAqCpB,CAAC;AAKH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,EAAE,GAAG,aAAa,CA+H1D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
|
package/dist/stores.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../src/stores.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EAQX,MAAM,aAAa,CAAC;AAErB,OAAO,EAGN,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAclC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,WAAW,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
|