@nxgt/janus-mongo 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,31 +6,28 @@ users, sessions and one-time tokens — and the relation store of
6
6
  [`@nxgt/mongo`](https://www.npmjs.com/package/@nxgt/mongo).
7
7
 
8
8
  It passes both `@nxgt/janus/conformance` suites against a real mongod, outages
9
- included. Each side is usable alone, as in `@nxgt/janus`: `createMongoStores`
10
- for identities, `createMongoRelations` for permissions, or both, as below.
9
+ included. `createMongoAdapter(db)` gives both sides under the names `janus()`
10
+ takes them, so one spread wires them; each is also usable alone, as in
11
+ `@nxgt/janus`: `createMongoStores` for identities, `createMongoRelations` for
12
+ permissions.
11
13
 
12
14
  ```ts
13
15
  import { janus, scryptHasher } from '@nxgt/janus';
14
16
  import { permissions } from '@nxgt/janus/permissions';
15
- import {
16
- createMongoRelations,
17
- createMongoStores,
18
- syncMongoRelations,
19
- syncMongoStores,
20
- } from '@nxgt/janus-mongo';
17
+ import { createMongoAdapter, syncMongoAdapter } from '@nxgt/janus-mongo';
21
18
 
22
- await syncMongoStores(db); // a deployment step: creates the collections and indexes
19
+ await syncMongoAdapter(db); // a deployment step: creates the collections and indexes
20
+
21
+ const mongo = createMongoAdapter(db); // { store, relations }
23
22
 
24
23
  export const auth = janus({
25
24
  user: User,
26
25
  password: { login: 'email' },
27
- store: createMongoStores(db),
28
26
  hasher: scryptHasher(),
29
- relations: createMongoRelations(db), // deleting a user deletes their tuples
27
+ ...mongo, // deleting a user deletes their tuples too
30
28
  });
31
29
 
32
- await syncMongoRelations(db);
33
- export const access = permissions({ model, store: createMongoRelations(db) });
30
+ export const access = permissions({ model, store: mongo.relations });
34
31
  ```
35
32
 
36
33
  ## Install
@@ -50,6 +47,9 @@ declarations import without extensions, so `nodenext` is not supported.
50
47
 
51
48
  | Export | What it is |
52
49
  | --- | --- |
50
+ | `createMongoAdapter(db)` | `{ store, relations }`: both of the below, keyed as `janus()` takes them, so `janus({ …, ...mongo })` wires both. Connects to nothing. |
51
+ | `syncMongoAdapter(db, options?)` | `syncMongoStores` and `syncMongoRelations` in one step: the four collections. |
52
+ | `MongoAdapter` | The type of what `createMongoAdapter` answers. |
53
53
  | `createMongoStores(db)` | The `{ users, sessions, tokens }` that `janus()` takes as `store`. Connects to nothing. |
54
54
  | `syncMongoStores(db, options?)` | Creates the three collections, their validators and indexes, and answers what it changed. Needs `dbAdmin`; run it when you deploy, never per request. |
55
55
  | `users`, `sessions`, `tokens` | The `@nxgt/mongo` definitions. Defining them registers them, so `syncAll(db)` syncs them with your own collections. |
@@ -82,7 +82,8 @@ passwords a self-describing hash.
82
82
  - **`auth.collectExpired()` answers `UNSUPPORTED`** with this adapter. The TTL
83
83
  index already drops lapsed sessions, so `deleteExpiredSessions` is
84
84
  deliberately not implemented.
85
- - **`syncMongoStores` is not called for you.** The core never manages a schema.
85
+ - **No sync function is called for you** — `syncMongoAdapter`, or
86
+ `syncMongoStores` for identities alone. The core never manages a schema.
86
87
  Without it there is no unique index, and nothing stops two concurrent sign-ups
87
88
  with one login.
88
89
  - **A duplicate key on an index other than the login index is a
@@ -0,0 +1,33 @@
1
+ import type { JanusStores } from '@nxgt/janus';
2
+ import type { RelationStore } from '@nxgt/janus/permissions';
3
+ import { type SyncOptions, type SyncReport } from '@nxgt/mongo';
4
+ import type { Db } from 'mongodb';
5
+ /** The whole adapter over one database, keyed as `janus()` takes it. */
6
+ export interface MongoAdapter {
7
+ /** The identity stores: `createMongoStores(db)`. */
8
+ readonly store: JanusStores;
9
+ /** The relation store: `createMongoRelations(db)`. */
10
+ readonly relations: RelationStore;
11
+ }
12
+ /**
13
+ * The identity stores and the relation store over one database, under the
14
+ * names `janus()` takes them — so one spread wires both, and deleting a user
15
+ * deletes every tuple naming them:
16
+ *
17
+ * ```ts
18
+ * const mongo = createMongoAdapter(db);
19
+ * const auth = janus({ user, password: { login: 'email' }, hasher, ...mongo });
20
+ * const access = permissions({ model, store: mongo.relations });
21
+ * ```
22
+ *
23
+ * It connects to nothing and sends nothing, as the two it combines. An
24
+ * application that only authenticates can take `createMongoStores(db)` alone.
25
+ */
26
+ export declare function createMongoAdapter(db: Db): MongoAdapter;
27
+ /**
28
+ * Creates the four collections, their validators and their indexes, and says
29
+ * what it changed: `syncMongoStores` and `syncMongoRelations` in one
30
+ * deployment step. Run it twice and the second run sends nothing.
31
+ */
32
+ export declare function syncMongoAdapter(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
33
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAKlC,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,GAAG,YAAY,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,EAAE,EAAE,EAAE,EACN,OAAO,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAEvB"}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * own, from the peer the application installed, so `instanceof` holds across
7
7
  * the two packages.
8
8
  */
9
+ export { createMongoAdapter, type MongoAdapter, syncMongoAdapter, } from './adapter';
9
10
  export { janusCollections, sessions, tokens, users } from './collections';
10
11
  export { createMongoRelations, relations, syncMongoRelations, } from './relations';
11
12
  export { createMongoStores, syncMongoStores } from './stores';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACN,oBAAoB,EACpB,SAAS,EACT,kBAAkB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACN,kBAAkB,EAClB,KAAK,YAAY,EACjB,gBAAgB,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACN,oBAAoB,EACpB,SAAS,EACT,kBAAkB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ // src/adapter.ts
2
+ import {
3
+ syncCollections as syncCollections3
4
+ } from "@nxgt/mongo";
5
+
1
6
  // src/collections.ts
2
7
  import { defineCollection } from "@nxgt/mongo";
3
8
  import { z } from "zod";
@@ -70,6 +75,7 @@ var tokens = defineCollection({
70
75
  ]
71
76
  });
72
77
  var janusCollections = [users, sessions, tokens];
78
+
73
79
  // src/relations.ts
74
80
  import {
75
81
  isSubjectSet
@@ -114,7 +120,7 @@ function takenLogin(duplicate) {
114
120
  return { type, login };
115
121
  }
116
122
  function loginTaken(operation, { type, login }, cause) {
117
- return new StoreConflict("login", `${operation}: the login "${login}" is taken by another ${type}`, { login, userType: type, operation, cause });
123
+ return new StoreConflict("login", `${operation}: the login is taken by another ${type}`, { login, userType: type, operation, cause });
118
124
  }
119
125
  function unexpectedDuplicate(slot, operation, duplicate) {
120
126
  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 });
@@ -238,6 +244,7 @@ function keyOf(tuple) {
238
244
  function subjectKey(subject) {
239
245
  return isSubjectSet(subject) ? { type: subject.type, id: subject.id, relation: subject.relation } : { type: subject.type, id: subject.id };
240
246
  }
247
+
241
248
  // src/stores.ts
242
249
  import { NotFoundError, StoreConflict as StoreConflict2 } from "@nxgt/janus";
243
250
  import {
@@ -464,17 +471,27 @@ function toToken(document) {
464
471
  createdAt: document.createdAt
465
472
  };
466
473
  }
474
+
475
+ // src/adapter.ts
476
+ function createMongoAdapter(db) {
477
+ return { store: createMongoStores(db), relations: createMongoRelations(db) };
478
+ }
479
+ function syncMongoAdapter(db, options) {
480
+ return syncCollections3(db, [...janusCollections, relations], options);
481
+ }
467
482
  export {
483
+ createMongoAdapter,
468
484
  createMongoRelations,
469
485
  createMongoStores,
470
486
  janusCollections,
471
487
  relations,
472
488
  sessions,
489
+ syncMongoAdapter,
473
490
  syncMongoRelations,
474
491
  syncMongoStores,
475
492
  tokens,
476
493
  users
477
494
  };
478
495
 
479
- //# debugId=8620B43C28476F8F64756E2164756E21
496
+ //# debugId=55FFE47757FDC9B264756E2164756E21
480
497
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/collections.ts", "../src/relations.ts", "../src/translate.ts", "../src/stores.ts"],
3
+ "sources": ["../src/adapter.ts", "../src/collections.ts", "../src/relations.ts", "../src/translate.ts", "../src/stores.ts"],
4
4
  "sourcesContent": [
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",
5
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\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(['verifyEmail', 'resetPassword']),\n\t\tuserId: z.string(),\n\t\taddress: z.string(),\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",
6
- "import {\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype Subject,\n\ttype SubjectSet,\n} from '@nxgt/janus';\nimport type { RelationStore } from '@nxgt/janus/permissions';\nimport {\n\ttype DocumentOf,\n\tdefineCollection,\n\tgetCollection,\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { ClientSession, Db, Filter } from 'mongodb';\nimport { z } from 'zod';\nimport { run } from './translate';\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#member` and `team:t1` are two keys.\n *\n * The key is always built by {@link keyOf}, in one field order: MongoDB\n * compares 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\ntype RelationDocument = DocumentOf<typeof relations>;\ntype Key = RelationDocument['_id'];\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 collection = getCollection(db, relations).raw;\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('relations', operation, body);\n\n\t/** One hop from an object: its subject sets, or its entities. */\n\tconst holding = async (object: Entity, relation: string, sets: boolean) =>\n\t\t(\n\t\t\tawait collection\n\t\t\t\t.find({\n\t\t\t\t\t'_id.object.type': object.type,\n\t\t\t\t\t'_id.object.id': object.id,\n\t\t\t\t\t'_id.relation': relation,\n\t\t\t\t\t'_id.subject.relation': { $exists: sets },\n\t\t\t\t})\n\t\t\t\t.toArray()\n\t\t).map((document) => document._id.subject);\n\n\treturn {\n\t\twrite: ({ add = [], remove = [] }) =>\n\t\t\trun$('write', async () => {\n\t\t\t\tconst removals = remove.map(keyOf);\n\t\t\t\tconst additions = add.map(keyOf);\n\t\t\t\tconst apply = async (session?: ClientSession) => {\n\t\t\t\t\tif (removals.length > 0) {\n\t\t\t\t\t\tawait collection.deleteMany(\n\t\t\t\t\t\t\t{ _id: { $in: removals } },\n\t\t\t\t\t\t\tsession === undefined ? {} : { session },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (additions.length > 0) {\n\t\t\t\t\t\t// A replace of a document by itself: a stored tuple is a no-op,\n\t\t\t\t\t\t// an absent one is inserted, and concurrent writers of one\n\t\t\t\t\t\t// tuple meet on the `_id` index, which the server retries.\n\t\t\t\t\t\tawait collection.bulkWrite(\n\t\t\t\t\t\t\tadditions.map((key) => ({\n\t\t\t\t\t\t\t\treplaceOne: {\n\t\t\t\t\t\t\t\t\tfilter: { _id: key },\n\t\t\t\t\t\t\t\t\treplacement: { _id: key },\n\t\t\t\t\t\t\t\t\tupsert: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\tsession === undefined ? {} : { session },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif (removals.length + additions.length <= 1) return apply();\n\n\t\t\t\tconst session = db.client.startSession();\n\t\t\t\ttry {\n\t\t\t\t\tsession.startTransaction();\n\t\t\t\t\tawait apply(session);\n\t\t\t\t\tawait session.commitTransaction();\n\t\t\t\t} finally {\n\t\t\t\t\t// Ends — and so aborts — a transaction that did not commit.\n\t\t\t\t\tawait session.endSession();\n\t\t\t\t}\n\t\t\t}),\n\n\t\thas: (tuple) =>\n\t\t\trun$(\n\t\t\t\t'has',\n\t\t\t\tasync () =>\n\t\t\t\t\t(await collection.findOne(\n\t\t\t\t\t\t{ _id: keyOf(tuple) },\n\t\t\t\t\t\t{ projection: { _id: 1 } },\n\t\t\t\t\t)) !== null,\n\t\t\t),\n\n\t\tfindSubjectSets: (object, relation) =>\n\t\t\trun$('findSubjectSets', async () =>\n\t\t\t\t(await holding(object, relation, true)).map(\n\t\t\t\t\t(subject): SubjectSet => ({\n\t\t\t\t\t\ttype: subject.type,\n\t\t\t\t\t\tid: subject.id,\n\t\t\t\t\t\trelation: subject.relation as string,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\n\t\tfindEntities: (object, relation) =>\n\t\t\trun$('findEntities', async () =>\n\t\t\t\t(await holding(object, relation, false)).map(\n\t\t\t\t\t(subject): Entity => ({ type: subject.type, id: subject.id }),\n\t\t\t\t),\n\t\t\t),\n\n\t\tfindObjects: ({ type, relation, subject, after, limit }) =>\n\t\t\trun$('findObjects', async () => {\n\t\t\t\tconst filter: Filter<RelationDocument> = {\n\t\t\t\t\t'_id.subject.type': subject.type,\n\t\t\t\t\t'_id.subject.id': subject.id,\n\t\t\t\t\t// `null` matches the missing field: an entity, not a set.\n\t\t\t\t\t'_id.subject.relation': isSubjectSet(subject)\n\t\t\t\t\t\t? subject.relation\n\t\t\t\t\t\t: null,\n\t\t\t\t\t'_id.relation': relation,\n\t\t\t\t\t'_id.object.type': type,\n\t\t\t\t\t...(after === null ? {} : { '_id.object.id': { $gt: after } }),\n\t\t\t\t};\n\t\t\t\tconst found = await collection\n\t\t\t\t\t.find(filter, { projection: { '_id.object.id': 1 } })\n\t\t\t\t\t.sort({ '_id.object.id': 1 })\n\t\t\t\t\t.limit(limit + 1)\n\t\t\t\t\t.toArray();\n\t\t\t\tconst items = found\n\t\t\t\t\t.slice(0, limit)\n\t\t\t\t\t.map((document) => document._id.object.id);\n\t\t\t\tconst last = items.at(-1);\n\t\t\t\treturn {\n\t\t\t\t\titems,\n\t\t\t\t\tnextCursor: found.length > limit && last !== undefined ? last : null,\n\t\t\t\t};\n\t\t\t}),\n\n\t\tdeleteEntity: (entity) =>\n\t\t\trun$('deleteEntity', async () => {\n\t\t\t\tconst result = await collection.deleteMany({\n\t\t\t\t\t$or: [\n\t\t\t\t\t\t{ '_id.object.type': entity.type, '_id.object.id': entity.id },\n\t\t\t\t\t\t{ '_id.subject.type': entity.type, '_id.subject.id': entity.id },\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\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\n/** The `_id` of a tuple, in the one field order MongoDB compares. */\nfunction 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",
7
- "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')`, naming the login and the type |\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 \"${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",
7
+ "import {\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype Subject,\n\ttype SubjectSet,\n} from '@nxgt/janus';\nimport type { RelationStore } from '@nxgt/janus/permissions';\nimport {\n\ttype DocumentOf,\n\tdefineCollection,\n\tgetCollection,\n\ttype SyncOptions,\n\ttype SyncReport,\n\tsyncCollections,\n} from '@nxgt/mongo';\nimport type { ClientSession, Db, Filter } from 'mongodb';\nimport { z } from 'zod';\nimport { run } from './translate';\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 by {@link keyOf}, in one field order: MongoDB\n * compares 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\ntype RelationDocument = DocumentOf<typeof relations>;\ntype Key = RelationDocument['_id'];\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 collection = getCollection(db, relations).raw;\n\tconst run$ = <T>(operation: string, body: () => Promise<T>) =>\n\t\trun('relations', operation, body);\n\n\t/** One hop from an object: its subject sets, or its entities. */\n\tconst holding = async (object: Entity, relation: string, sets: boolean) =>\n\t\t(\n\t\t\tawait collection\n\t\t\t\t.find({\n\t\t\t\t\t'_id.object.type': object.type,\n\t\t\t\t\t'_id.object.id': object.id,\n\t\t\t\t\t'_id.relation': relation,\n\t\t\t\t\t'_id.subject.relation': { $exists: sets },\n\t\t\t\t})\n\t\t\t\t.toArray()\n\t\t).map((document) => document._id.subject);\n\n\treturn {\n\t\twrite: ({ add = [], remove = [] }) =>\n\t\t\trun$('write', async () => {\n\t\t\t\tconst removals = remove.map(keyOf);\n\t\t\t\tconst additions = add.map(keyOf);\n\t\t\t\tconst apply = async (session?: ClientSession) => {\n\t\t\t\t\tif (removals.length > 0) {\n\t\t\t\t\t\tawait collection.deleteMany(\n\t\t\t\t\t\t\t{ _id: { $in: removals } },\n\t\t\t\t\t\t\tsession === undefined ? {} : { session },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (additions.length > 0) {\n\t\t\t\t\t\t// A replace of a document by itself: a stored tuple is a no-op,\n\t\t\t\t\t\t// an absent one is inserted, and concurrent writers of one\n\t\t\t\t\t\t// tuple meet on the `_id` index, which the server retries.\n\t\t\t\t\t\tawait collection.bulkWrite(\n\t\t\t\t\t\t\tadditions.map((key) => ({\n\t\t\t\t\t\t\t\treplaceOne: {\n\t\t\t\t\t\t\t\t\tfilter: { _id: key },\n\t\t\t\t\t\t\t\t\treplacement: { _id: key },\n\t\t\t\t\t\t\t\t\tupsert: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\tsession === undefined ? {} : { session },\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif (removals.length + additions.length <= 1) return apply();\n\n\t\t\t\tconst session = db.client.startSession();\n\t\t\t\ttry {\n\t\t\t\t\tsession.startTransaction();\n\t\t\t\t\tawait apply(session);\n\t\t\t\t\tawait session.commitTransaction();\n\t\t\t\t} finally {\n\t\t\t\t\t// Ends — and so aborts — a transaction that did not commit.\n\t\t\t\t\tawait session.endSession();\n\t\t\t\t}\n\t\t\t}),\n\n\t\thas: (tuple) =>\n\t\t\trun$(\n\t\t\t\t'has',\n\t\t\t\tasync () =>\n\t\t\t\t\t(await collection.findOne(\n\t\t\t\t\t\t{ _id: keyOf(tuple) },\n\t\t\t\t\t\t{ projection: { _id: 1 } },\n\t\t\t\t\t)) !== null,\n\t\t\t),\n\n\t\tfindSubjectSets: (object, relation) =>\n\t\t\trun$('findSubjectSets', async () =>\n\t\t\t\t(await holding(object, relation, true)).map(\n\t\t\t\t\t(subject): SubjectSet => ({\n\t\t\t\t\t\ttype: subject.type,\n\t\t\t\t\t\tid: subject.id,\n\t\t\t\t\t\trelation: subject.relation as string,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t),\n\n\t\tfindEntities: (object, relation) =>\n\t\t\trun$('findEntities', async () =>\n\t\t\t\t(await holding(object, relation, false)).map(\n\t\t\t\t\t(subject): Entity => ({ type: subject.type, id: subject.id }),\n\t\t\t\t),\n\t\t\t),\n\n\t\tfindObjects: ({ type, relation, subject, after, limit }) =>\n\t\t\trun$('findObjects', async () => {\n\t\t\t\tconst filter: Filter<RelationDocument> = {\n\t\t\t\t\t'_id.subject.type': subject.type,\n\t\t\t\t\t'_id.subject.id': subject.id,\n\t\t\t\t\t// `null` matches the missing field: an entity, not a set.\n\t\t\t\t\t'_id.subject.relation': isSubjectSet(subject)\n\t\t\t\t\t\t? subject.relation\n\t\t\t\t\t\t: null,\n\t\t\t\t\t'_id.relation': relation,\n\t\t\t\t\t'_id.object.type': type,\n\t\t\t\t\t...(after === null ? {} : { '_id.object.id': { $gt: after } }),\n\t\t\t\t};\n\t\t\t\tconst found = await collection\n\t\t\t\t\t.find(filter, { projection: { '_id.object.id': 1 } })\n\t\t\t\t\t.sort({ '_id.object.id': 1 })\n\t\t\t\t\t.limit(limit + 1)\n\t\t\t\t\t.toArray();\n\t\t\t\tconst items = found\n\t\t\t\t\t.slice(0, limit)\n\t\t\t\t\t.map((document) => document._id.object.id);\n\t\t\t\tconst last = items.at(-1);\n\t\t\t\treturn {\n\t\t\t\t\titems,\n\t\t\t\t\tnextCursor: found.length > limit && last !== undefined ? last : null,\n\t\t\t\t};\n\t\t\t}),\n\n\t\tdeleteEntity: (entity) =>\n\t\t\trun$('deleteEntity', async () => {\n\t\t\t\tconst result = await collection.deleteMany({\n\t\t\t\t\t$or: [\n\t\t\t\t\t\t{ '_id.object.type': entity.type, '_id.object.id': entity.id },\n\t\t\t\t\t\t{ '_id.subject.type': entity.type, '_id.subject.id': entity.id },\n\t\t\t\t\t],\n\t\t\t\t});\n\t\t\t\treturn result.deletedCount;\n\t\t\t}),\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\n/** The `_id` of a tuple, in the one field order MongoDB compares. */\nfunction 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",
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",
8
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\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\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'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\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\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\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\texpiresAt: document.expiresAt,\n\t\tspentAt: document.spentAt,\n\t\tcreatedAt: document.createdAt,\n\t};\n}\n"
9
10
  ],
10
- "mappings": "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",
11
- "debugId": "8620B43C28476F8F64756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "55FFE47757FDC9B264756E2164756E21",
12
13
  "names": []
13
14
  }
@@ -10,7 +10,7 @@ import { z } from 'zod';
10
10
  * `_id` index every collection has — a constraint, never a read — an insert
11
11
  * of a stored tuple is a retry, and a document read in a shell is the tuple
12
12
  * in the code. A subject set carries `relation`, an entity does not, so
13
- * `team:t1#member` and `team:t1` are two keys.
13
+ * `team:t1#members` and `team:t1` are two keys.
14
14
  *
15
15
  * The key is always built by {@link keyOf}, in one field order: MongoDB
16
16
  * compares embedded documents field by field, in order.
@@ -4,7 +4,7 @@ import { StoreConflict, StoreFailure } from '@nxgt/janus';
4
4
  *
5
5
  * | What happens | Becomes |
6
6
  * | --- | --- |
7
- * | A duplicate key on `(type, logins)` | `StoreConflict('login')`, naming the login and the type |
7
+ * | A duplicate key on `(type, logins)` | `StoreConflict('login')`, carrying `login` and `userType` |
8
8
  * | A duplicate key on `_id` | nothing — the insert is a retry, and answers what is stored |
9
9
  * | A duplicate key on **any other index** | **`StoreFailure`** |
10
10
  * | A `$jsonSchema` validation failure (121) | **`StoreFailure`** |
@@ -6,25 +6,28 @@ for knowing what ends up stored. Wiring the stores into `janus()` and
6
6
  `permissions()` is on [the wiring page](wiring.md).
7
7
 
8
8
  ```ts
9
- import { syncMongoRelations, syncMongoStores } from '@nxgt/janus-mongo';
9
+ import { syncMongoAdapter } from '@nxgt/janus-mongo';
10
10
  import { MongoClient } from 'mongodb';
11
11
 
12
12
  const client = await MongoClient.connect(process.env.MONGO_URL ?? 'mongodb://localhost:27017');
13
13
  const db = client.db('janus');
14
14
 
15
- await syncMongoStores(db); // users, sessions, tokens
16
- await syncMongoRelations(db); // relations — only if you use @nxgt/janus/permissions
15
+ await syncMongoAdapter(db); // users, sessions, tokens and relations
17
16
 
18
17
  await client.close();
19
18
  ```
20
19
 
21
- **The core never manages a schema, and neither function is called for you.**
20
+ An application that only authenticates runs `syncMongoStores(db)` instead,
21
+ and creates no `relations` collection.
22
+
23
+ **The core never manages a schema, and no sync function is called for you.**
22
24
  Without `syncMongoStores` there is no unique index on logins, and nothing stops
23
25
  two concurrent sign-ups with one e-mail.
24
26
 
25
- ## `syncMongoStores` and `syncMongoRelations`
27
+ ## `syncMongoAdapter`, `syncMongoStores` and `syncMongoRelations`
26
28
 
27
29
  ```ts
30
+ function syncMongoAdapter(db: Db, options?: SyncOptions): Promise<SyncReport[]>; // the two below, in one
28
31
  function syncMongoStores(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
29
32
  function syncMongoRelations(db: Db, options?: SyncOptions): Promise<SyncReport[]>;
30
33
  ```
@@ -101,7 +104,7 @@ encoded: a document read in a shell reads like the record in the code.
101
104
  store mints nothing.
102
105
  - **A tuple is unique by construction**, since it is its own `_id`, so writing
103
106
  a stored tuple again is a no-op rather than a duplicate. A subject set and
104
- its entity (`team:t1#member`, `team:t1`) are two different keys.
107
+ its entity (`team:t1#members`, `team:t1`) are two different keys.
105
108
  - `findObjects` is served by `subjectObjects` with no in-memory sort —
106
109
  measured, 11 keys examined for a page of 10.
107
110
  - The collection names are fixed: `users`, `sessions`, `tokens`, `relations`.
@@ -8,36 +8,51 @@ on [the sync page](sync.md).
8
8
  ```ts
9
9
  import { janus, scryptHasher } from '@nxgt/janus';
10
10
  import { defineModel, permissions } from '@nxgt/janus/permissions';
11
- import { createMongoRelations, createMongoStores } from '@nxgt/janus-mongo';
11
+ import { createMongoAdapter } from '@nxgt/janus-mongo';
12
12
  import { MongoClient } from 'mongodb';
13
13
  import { z } from 'zod';
14
14
 
15
15
  const client = new MongoClient(process.env.MONGO_URL ?? 'mongodb://localhost:27017');
16
16
  const db = client.db('janus');
17
17
 
18
- const relations = createMongoRelations(db);
18
+ const mongo = createMongoAdapter(db); // { store, relations }
19
19
 
20
20
  export const auth = janus({
21
21
  user: z.object({ email: z.email(), name: z.string() }),
22
22
  password: { login: 'email' },
23
- store: createMongoStores(db),
24
- relations, // deleting a user deletes every tuple naming them
25
23
  hasher: scryptHasher(),
24
+ ...mongo, // deleting a user deletes every tuple naming them
26
25
  });
27
26
 
28
27
  export const model = defineModel({
29
28
  subjects: auth.types,
30
29
  types: {
31
30
  document: {
32
- relations: { owner: ['user'], viewer: ['user'] },
33
- permissions: { view: ['owner', 'viewer'] },
31
+ related: { owners: ['user'], viewers: ['user'] },
32
+ permits: { view: ['owners', 'viewers'] },
34
33
  },
35
34
  },
36
35
  });
37
36
 
38
- export const access = permissions({ model, store: relations });
37
+ export const access = permissions({ model, store: mongo.relations });
39
38
  ```
40
39
 
40
+ ## `createMongoAdapter(db)`
41
+
42
+ ```ts
43
+ function createMongoAdapter(db: Db): MongoAdapter; // { store, relations }
44
+ ```
45
+
46
+ `createMongoStores(db)` as `store` and `createMongoRelations(db)` as
47
+ `relations`: the two keys `janus()` takes them under, so a spread wires both,
48
+ and `permissions()` takes the same relation store as `mongo.relations`. It
49
+ connects to nothing.
50
+
51
+ **Pass the relation store to both.** Given to `permissions()` alone, tuples
52
+ are written, and deleting a user leaves every one naming them behind — the
53
+ spread is what makes that impossible to forget. An application that only
54
+ authenticates takes `createMongoStores(db)` alone, below.
55
+
41
56
  ## `createMongoStores(db)`
42
57
 
43
58
  ```ts
@@ -81,7 +96,7 @@ is one copy of it:
81
96
 
82
97
  | What the driver reports | What you get |
83
98
  | --- | --- |
84
- | A duplicate key on the login index | `StoreConflict` — `LOGIN_TAKEN`, naming the login and the user type |
99
+ | A duplicate key on the login index | `StoreConflict` — `LOGIN_TAKEN`, carrying `login` and `userType` |
85
100
  | A duplicate key on `_id` | nothing: the insert was a retry, and answers what is stored |
86
101
  | A duplicate key on **any other index** | `StoreFailure` — an adapter bug, never reported as a taken login |
87
102
  | A validator refusing a document (121) | `StoreFailure` — the core validated it already, so it is never the caller's fault |
@@ -135,7 +150,7 @@ single-node replica set — the adapter's own specs use
135
150
  `mongodb-memory-server-core`, one database per case:
136
151
 
137
152
  ```ts
138
- import { createMongoRelations, createMongoStores, syncMongoRelations, syncMongoStores } from '@nxgt/janus-mongo';
153
+ import { createMongoAdapter, syncMongoAdapter } from '@nxgt/janus-mongo';
139
154
  import { MongoClient } from 'mongodb';
140
155
  import { MongoMemoryReplSet } from 'mongodb-memory-server-core';
141
156
 
@@ -143,10 +158,8 @@ const server = await MongoMemoryReplSet.create({ replSet: { count: 1 } });
143
158
  const client = await MongoClient.connect(server.getUri());
144
159
 
145
160
  const db = client.db('janusTest');
146
- await syncMongoStores(db);
147
- await syncMongoRelations(db);
148
- const store = createMongoStores(db);
149
- const relations = createMongoRelations(db);
161
+ await syncMongoAdapter(db);
162
+ const mongo = createMongoAdapter(db);
150
163
 
151
164
  // after the suite
152
165
  await client.close();
@@ -155,5 +168,5 @@ await server.stop();
155
168
 
156
169
  ## See also
157
170
 
158
- - [Collections and indexes](sync.md) — `syncMongoStores`, `syncMongoRelations`, and what the database holds
171
+ - [Collections and indexes](sync.md) — `syncMongoAdapter`, `syncMongoStores`, `syncMongoRelations`, and what the database holds
159
172
  - [`@nxgt/janus`](https://www.npmjs.com/package/@nxgt/janus) — its `docs/guide/` covers users, sessions and permissions
package/docs/roadmap.md CHANGED
@@ -40,6 +40,12 @@ Nothing yet.
40
40
 
41
41
  The first public release, v0.1.
42
42
 
43
+ - **`LOGIN_TAKEN` no longer quotes the login in its message**; `error.login`
44
+ still names it. — next patch
45
+ - **One call for both sides** — `createMongoAdapter(db)` answers
46
+ `{ store, relations }`, spread into `janus()` so deleting a user deletes
47
+ their tuples without a second wiring; `syncMongoAdapter(db)` syncs the four
48
+ collections in one step. — v0.1.2
43
49
  - **Guides and troubleshooting pages** — a `docs/` folder shipped in the
44
50
  package, with the errors and traps you can meet running the adapter, each
45
51
  with its cause and fix. — v0.1
@@ -14,7 +14,7 @@ How the driver's errors become the port's:
14
14
 
15
15
  | What MongoDB answers | What you get |
16
16
  | --- | --- |
17
- | A duplicate key on the login index | `LOGIN_TAKEN`, naming the login and the user type |
17
+ | A duplicate key on the login index | `LOGIN_TAKEN`, carrying `login` and `userType` |
18
18
  | A duplicate key on `_id` | Nothing: the insert was a retry, and what is stored is answered |
19
19
  | A duplicate key on any other index | `STORE_FAILED` |
20
20
  | A `$jsonSchema` validation failure (code 121) | `STORE_FAILED` |
@@ -33,7 +33,7 @@ How the driver's errors become the port's:
33
33
 
34
34
  **Runtime**
35
35
  - [`STORE_FAILED` — `<slot>.<operation>: the store could not answer`](#store_failed--slotoperation-the-store-could-not-answer)
36
- - [`LOGIN_TAKEN` — `<operation>: the login "<login>" is taken by another <type>`](#login_taken--operation-the-login-login-is-taken-by-another-type)
36
+ - [`LOGIN_TAKEN` — `<operation>: the login is taken by another <type>`](#login_taken--operation-the-login-is-taken-by-another-type)
37
37
  - [`STORE_FAILED` — `<slot>.<operation>: a duplicate key on <fields>, which this adapter never writes on purpose`](#store_failed--slotoperation-a-duplicate-key-on-fields-which-this-adapter-never-writes-on-purpose)
38
38
  - [`NOT_FOUND` / `VERSION_CONFLICT` — `updateUser: …`](#not_found--version_conflict--updateuser-)
39
39
  - [`UNSUPPORTED` — `collectExpired: store.sessions does not implement deleteExpiredSessions …`](#unsupported--collectexpired-storesessions-does-not-implement-deleteexpiredsessions--)
@@ -88,10 +88,10 @@ bun pm ls --all | grep @nxgt/janus # one @nxgt/janus, not two
88
88
  **Fix:** run the sync as a deployment step, with a user that has the `dbAdmin` role — never per request:
89
89
 
90
90
  ```ts
91
- import { syncMongoRelations, syncMongoStores } from '@nxgt/janus-mongo';
91
+ import { syncMongoAdapter } from '@nxgt/janus-mongo';
92
92
 
93
- await syncMongoStores(db); // users, sessions, tokens: validators and indexes
94
- await syncMongoRelations(db); // only if you use @nxgt/janus/permissions
93
+ await syncMongoAdapter(db); // users, sessions, tokens and relations: validators and indexes
94
+ // identities alone: syncMongoStores(db)
95
95
  ```
96
96
 
97
97
  An application that deploys with `@nxgt/mongo`'s `syncAll(db)` already syncs
@@ -152,9 +152,9 @@ if (error instanceof JanusError && error.code === 'STORE_FAILED') {
152
152
  }
153
153
  ```
154
154
 
155
- ### `LOGIN_TAKEN` — `<operation>: the login "<login>" is taken by another <type>`
155
+ ### `LOGIN_TAKEN` — `<operation>: the login is taken by another <type>`
156
156
 
157
- `StoreConflict` with `on: 'login'`, for example `insertUser: the login "ada@example.com" is taken by another patient`. It carries `login` and `userType`.
157
+ `StoreConflict` with `on: 'login'`, for example `insertUser: the login is taken by another patient`. It carries `login` and `userType`. The login is not in the message, which never carries a value: an e-mail in a log line is personal data. Read it from `error.login`.
158
158
 
159
159
  **When:** `signUp`, `create`, or an `update` that changes the login, when another user **of the same type** already holds it. The message names the port method (`insertUser`, `updateUser`), not your call.
160
160
  **Why:** the `loginUnique` index on `{ type, logins }` refused the write. The same e-mail may hold one user per user type.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/janus-mongo",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
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,7 +48,7 @@
48
48
  ]
49
49
  },
50
50
  "devDependencies": {
51
- "@nxgt/janus": "^0.1.1",
51
+ "@nxgt/janus": "^0.2.0",
52
52
  "@nxgt/mongo": "^0.17.1",
53
53
  "@types/bun": "^1.4.2",
54
54
  "mongodb": "^7.6.0",
@@ -56,7 +56,7 @@
56
56
  "zod": "^4.6.5"
57
57
  },
58
58
  "peerDependencies": {
59
- "@nxgt/janus": "^0.1.1",
59
+ "@nxgt/janus": "^0.2.0",
60
60
  "@nxgt/mongo": ">=0.17.0 <1",
61
61
  "mongodb": ">=7.0.0 <8",
62
62
  "typescript": "^6.0.3",