@nxgt/janus 0.1.3 → 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 +39 -30
- package/dist/auth/port/types.d.ts +6 -4
- package/dist/auth/port/types.d.ts.map +1 -1
- package/dist/chunks/{index-658b6mr2.js → index-0xarpm7z.js} +3 -3
- package/dist/chunks/index-0xarpm7z.js.map +11 -0
- package/dist/chunks/index-6p56fpbe.js.map +2 -2
- package/dist/conformance/cases/sessions.d.ts.map +1 -1
- package/dist/conformance/cases/users.d.ts.map +1 -1
- package/dist/conformance/index.js +12 -4
- package/dist/conformance/index.js.map +4 -4
- package/dist/errors/janus-error.d.ts +2 -1
- package/dist/errors/janus-error.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/permissions/engine.d.ts +1 -1
- package/dist/permissions/index.js +17 -13
- package/dist/permissions/index.js.map +5 -5
- package/dist/permissions/model.d.ts +28 -28
- package/dist/permissions/model.d.ts.map +1 -1
- package/dist/permissions/resolve.d.ts.map +1 -1
- package/docs/guide/adapters.md +6 -2
- package/docs/guide/errors.md +3 -2
- package/docs/guide/permissions.md +287 -109
- package/docs/guide/vocabulary.md +21 -18
- package/docs/roadmap.md +31 -8
- package/docs/troubleshooting.md +34 -16
- package/package.json +1 -1
- package/dist/chunks/index-658b6mr2.js.map +0 -11
package/README.md
CHANGED
|
@@ -80,15 +80,15 @@ const access = permissions({
|
|
|
80
80
|
subjects: ['user'],
|
|
81
81
|
types: {
|
|
82
82
|
document: {
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
related: { owners: ['user'], viewers: ['user'] },
|
|
84
|
+
permits: { view: ['owners', 'viewers'] },
|
|
85
85
|
},
|
|
86
86
|
},
|
|
87
87
|
}),
|
|
88
88
|
store: createMemoryRelations(),
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
await access.grant({ type: 'document', id: 'd1' }, '
|
|
91
|
+
await access.grant({ type: 'document', id: 'd1' }, 'viewers', { type: 'user', id: 'u1' });
|
|
92
92
|
await access.can({ type: 'user', id: 'u1' }, 'view', { type: 'document', id: 'd1' }); // true
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -119,8 +119,8 @@ const access = permissions({
|
|
|
119
119
|
subjects: auth.types,
|
|
120
120
|
types: {
|
|
121
121
|
document: {
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
related: { owners: ['user'] },
|
|
123
|
+
permits: { view: ['owners'] },
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
126
|
}),
|
|
@@ -192,8 +192,8 @@ across the two packages.
|
|
|
192
192
|
|
|
193
193
|
**No message ever holds a secret** — not a password, not a hash, not a session
|
|
194
194
|
token, not a token's hash, and not a connection URI, because a connection string
|
|
195
|
-
holds a password.
|
|
196
|
-
|
|
195
|
+
holds a password. Nor a login: a message reports a shape, never a value, so
|
|
196
|
+
`LOGIN_TAKEN` names the login in `error.login`, not in its message.
|
|
197
197
|
|
|
198
198
|
A refusal that can only come from how you wired the library — a lifespan that is
|
|
199
199
|
not a duration, a store missing a method — throws a bare `TypeError` instead. No
|
|
@@ -207,11 +207,11 @@ import { type Subject, subjectOf, formatTuple, parseTuple, isSubjectSet } from '
|
|
|
207
207
|
subjectOf(user); // { type: 'staff', id: '…' }: the user IS the subject
|
|
208
208
|
formatTuple({
|
|
209
209
|
object: { type: 'record', id: 'r1' },
|
|
210
|
-
relation: '
|
|
211
|
-
subject: { type: 'team', id: '
|
|
210
|
+
relation: 'members',
|
|
211
|
+
subject: { type: 'team', id: 't2', relation: 'members' },
|
|
212
212
|
});
|
|
213
|
-
// '
|
|
214
|
-
parseTuple('
|
|
213
|
+
// 'team:t1#members@team:t2#members'
|
|
214
|
+
parseTuple('team:t1#members@staff:u1'); // the RelationTuple back
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
`formatEntity`, `formatSubject` and `parseSubject` do the same for one part,
|
|
@@ -392,35 +392,41 @@ export const model = defineModel({
|
|
|
392
392
|
subjects: clinic.types, // 'patient' | 'staff': a user type is a subject type
|
|
393
393
|
types: {
|
|
394
394
|
team: {
|
|
395
|
-
|
|
396
|
-
|
|
395
|
+
related: { members: ['staff', 'team#members'], leads: ['staff'] },
|
|
396
|
+
permits: { manage: ['leads'], view: ['members', 'manage'] },
|
|
397
397
|
},
|
|
398
398
|
record: {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
related: {
|
|
400
|
+
doctors: fromField('doctorId', 'staff', { lookup: (staffId) => db.records.idsByDoctor(staffId) }),
|
|
401
|
+
patients: fromField('patientId', 'patient', { lookup: (patientId) => db.records.idsByPatient(patientId) }),
|
|
402
|
+
teams: ['team'],
|
|
402
403
|
},
|
|
403
|
-
|
|
404
|
-
view: ['
|
|
405
|
-
|
|
404
|
+
permits: {
|
|
405
|
+
view: ['doctors', 'patients', 'teams->view'],
|
|
406
|
+
review: ['teams->leads'],
|
|
407
|
+
edit: [when('doctors', (ctx: { onShift: boolean }) => ctx.onShift)],
|
|
406
408
|
},
|
|
407
409
|
},
|
|
408
410
|
},
|
|
409
411
|
});
|
|
410
412
|
|
|
413
|
+
// staff: a user from clinic.staff; record: a row carrying doctorId and patientId.
|
|
411
414
|
const access = permissions({ model, store: createMemoryRelations() });
|
|
412
|
-
await access.grant({ type: 'team', id: 't1' }, '
|
|
415
|
+
await access.grant({ type: 'team', id: 't1' }, 'members', staff);
|
|
413
416
|
await access.can(staff, 'edit', { type: 'record', ...record }, { ctx: { onShift } }); // boolean
|
|
414
417
|
await access.list(staff, 'view', 'record', { limit: 50 }); // CursorPage<string>; view reaches no condition
|
|
415
|
-
await access.revoke({ type: 'team', id: 't1' }, '
|
|
418
|
+
await access.revoke({ type: 'team', id: 't1' }, 'members', staff); // idempotent
|
|
416
419
|
```
|
|
417
420
|
|
|
418
421
|
Zanzibar's model — relations between objects and subjects, permissions
|
|
419
422
|
computed from them — **without its infrastructure**: the tuples live in your
|
|
420
423
|
database, so a read follows a write and there is nothing to cache or to
|
|
421
|
-
sequence.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
+
sequence. An object type declares its relations under `related` and its
|
|
425
|
+
permissions under `permits` — Keto's words — and relation names are plural by convention (`defineModel`
|
|
426
|
+
does not enforce it).
|
|
427
|
+
Subject sets (`'team#members'`), arrows (`'teams->view'`: whoever can view one
|
|
428
|
+
of the record's teams; `'teams->leads'`: whoever leads one) and permissions
|
|
429
|
+
naming permissions are Zanzibar's. Two things are not:
|
|
424
430
|
|
|
425
431
|
- **`fromField`** reads a relation from the object's own data — a record's
|
|
426
432
|
`doctorId` — instead of a tuple kept in sync with it. `can()` is given the
|
|
@@ -443,7 +449,9 @@ exist, a rule naming nothing, an arrow to a permission its target lacks, a
|
|
|
443
449
|
permission asked of the wrong type, an object missing a field, a missing
|
|
444
450
|
`ctx`, a `grant` of a relation read from a field or to a holder it does not
|
|
445
451
|
admit, a `list()` through a `fromField` without a `lookup`: each is a compile
|
|
446
|
-
error, on the offending argument.
|
|
452
|
+
error, on the offending argument. So are the keys before 0.2, `relations` and
|
|
453
|
+
`permissions`: the error names the new one — `team.relations is now related:
|
|
454
|
+
rename the key` — and `defineModel` refuses them the same way at run time. `defineModel` refuses with a `TypeError`
|
|
447
455
|
what only running it can see: names that are not camelCase, a permission that
|
|
448
456
|
reaches itself without crossing a relation, a subject set or an arrow that
|
|
449
457
|
would have to read another object's field.
|
|
@@ -483,7 +491,8 @@ describeJanusStores({
|
|
|
483
491
|
There are 37 cases. They cover:
|
|
484
492
|
- round-trip, byte for byte;
|
|
485
493
|
- uniqueness, as a constraint: of twenty concurrent inserts of one login,
|
|
486
|
-
exactly one is accepted — and a login is unique per user type;
|
|
494
|
+
exactly one is accepted — and a login is unique per user type; the refusal
|
|
495
|
+
carries the login in `error.login`, never in its message;
|
|
487
496
|
- versions: a refused update writes nothing;
|
|
488
497
|
- **omission**, named after the Kratos `PUT` trap;
|
|
489
498
|
- pagination;
|
|
@@ -571,9 +580,9 @@ answers `null`. `undefined` is what a missing property *and* a function with no
|
|
|
571
580
|
by accident. `null` has to be written on purpose.
|
|
572
581
|
|
|
573
582
|
**The notation is typed, and refuses Keto's untyped subject.**
|
|
574
|
-
`
|
|
583
|
+
`team:t1#members@staff:u1`, and `team:t1#members@team:t2#members` for a subject
|
|
575
584
|
set. No part may hold `@`, `#` or a parenthesis, and a type may not hold a `:`,
|
|
576
|
-
so every string reads one way. `parseTuple` refuses `
|
|
585
|
+
so every string reads one way. `parseTuple` refuses `team:t1#members@grace`,
|
|
577
586
|
and its message says what a subject is.
|
|
578
587
|
|
|
579
588
|
**`parseTuple` throws a bare `TypeError`, not a `JanusError`.** Nothing in this
|
|
@@ -618,7 +627,7 @@ could not answer: that is a denial made of an outage.
|
|
|
618
627
|
|
|
619
628
|
## Type safety, counted
|
|
620
629
|
|
|
621
|
-
**
|
|
630
|
+
**Ninety plausible mistakes, ninety refused at compile time — and
|
|
622
631
|
one gap, named.**
|
|
623
632
|
|
|
624
633
|
The lists are typechecked and never run, with one `@ts-expect-error` per
|
|
@@ -626,7 +635,7 @@ mistake beside the shapes that must keep compiling:
|
|
|
626
635
|
`test/types/refusals.ts` (fourteen, on the shared vocabulary),
|
|
627
636
|
`test/types/port.ts` (fifteen, on the identity stores' port, from the point
|
|
628
637
|
of view of the person implementing it), `test/types/auth.ts` (twenty, on
|
|
629
|
-
`janus()`, from the point of view of the application) and `test/types/permissions.ts` (
|
|
638
|
+
`janus()`, from the point of view of the application) and `test/types/permissions.ts` (forty-one, on the
|
|
630
639
|
permission model and the questions asked of it). The rule
|
|
631
640
|
comes from `nxgt-data`, and so does the reason to
|
|
632
641
|
distrust the claim without the files: when it was last measured on
|
|
@@ -192,8 +192,8 @@ export interface UserStore {
|
|
|
192
192
|
* logins: a login held by the user with this same `id` is not taken.
|
|
193
193
|
*
|
|
194
194
|
* A login held by **another** user of the same type rejects with
|
|
195
|
-
* `StoreConflict('login', …)`, carrying `login` and `userType
|
|
196
|
-
* nothing. That refusal comes from the store's own constraint, never from a
|
|
195
|
+
* `StoreConflict('login', …)`, carrying `login` and `userType` — never the
|
|
196
|
+
* login in its message — and writes nothing. That refusal comes from the store's own constraint, never from a
|
|
197
197
|
* read made first.
|
|
198
198
|
*/
|
|
199
199
|
insertUser(record: UserRecord): Promise<UserRecord>;
|
|
@@ -233,7 +233,8 @@ export interface UserStore {
|
|
|
233
233
|
* this from an absent id on its own, so the adapter reads again after a
|
|
234
234
|
* write that matched nothing;
|
|
235
235
|
* - `StoreConflict('login', …)` when the patch's logins collide with another
|
|
236
|
-
* user's of the same type
|
|
236
|
+
* user's of the same type, carrying `login` and `userType` — never the
|
|
237
|
+
* login in its message.
|
|
237
238
|
*/
|
|
238
239
|
updateUser(id: Id, patch: UserPatch, ifVersion: number): Promise<UserRecord>;
|
|
239
240
|
/**
|
|
@@ -310,7 +311,8 @@ export interface SessionStore {
|
|
|
310
311
|
/**
|
|
311
312
|
* Deletes every session of one user — standing, revoked or lapsed — and
|
|
312
313
|
* answers how many. `0` is an answer, not a failure. What deleting a user
|
|
313
|
-
* calls: a revoked session still names who held it.
|
|
314
|
+
* calls: a revoked session still names who held it. A lapsed session the
|
|
315
|
+
* store already dropped is not there to count.
|
|
314
316
|
*/
|
|
315
317
|
deleteUserSessions(userId: Id): Promise<number>;
|
|
316
318
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/port/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/port/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -66,7 +66,7 @@ function memoryUserStore() {
|
|
|
66
66
|
const holder = byLogin.get(keyOf(type, login));
|
|
67
67
|
return holder !== undefined && holder !== id;
|
|
68
68
|
});
|
|
69
|
-
const taken = (operation, type, login) => new StoreConflict2("login", `${operation}: the login
|
|
69
|
+
const taken = (operation, type, login) => new StoreConflict2("login", `${operation}: the login is taken by another ${type}`, { login, userType: type, operation });
|
|
70
70
|
return {
|
|
71
71
|
async insertUser(record) {
|
|
72
72
|
const stored = byId.get(record.id);
|
|
@@ -265,5 +265,5 @@ function memoryTokenStore() {
|
|
|
265
265
|
|
|
266
266
|
export { mintId2, isId2, mintedAt2, createMemoryStores2 };
|
|
267
267
|
|
|
268
|
-
//# debugId=
|
|
269
|
-
//# sourceMappingURL=index-
|
|
268
|
+
//# debugId=609E38330FBCD6ED64756E2164756E21
|
|
269
|
+
//# sourceMappingURL=index-0xarpm7z.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ids/id.ts", "../src/auth/port/memory.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * An id — of a user, a session: a UUIDv7, lowercase, hyphenated.\n *\n * **The core mints it, not the store**, and that decision pays for itself three\n * times over:\n *\n * - A UUIDv7 leads with a 48-bit millisecond timestamp, so ids sort in\n * creation order as strings. The pagination cursor is therefore *the last\n * id* — one index on the id, and the ordering is already total. A\n * store-minted id would need a `createdAt` index plus the id as a tiebreak,\n * because a timestamp alone is not a total order, and an opaque cursor per\n * adapter.\n * - `insertUser` becomes **idempotent under retry**: the id is decided\n * before the call, so a retry after a timeout writes the same row rather\n * than a second user.\n * - Every adapter reports the same shape, so moving an application from one\n * adapter to another is a copy rather than a rewrite of every stored\n * reference.\n *\n * The price, stated plainly: an adapter cannot reuse an existing numeric primary\n * key. It gets a `uuid` column, or a 36-character string, and the monotonic\n * prefix gives it the index locality a UUIDv4 destroys.\n */\nexport type Id = string;\n\nconst HEX: readonly string[] = Array.from({ length: 256 }, (_, i) =>\n\ti.toString(16).padStart(2, '0'),\n);\n\nlet lastMs = -1;\nlet sequence = 0;\n\n/**\n * The 12-bit sequence UUIDv7 keeps for ordering inside one millisecond.\n *\n * RFC 9562 calls this the \"replace leftmost random bits with an increased clock\n * precision\" method. Without it, two ids minted in the same millisecond order\n * by their random tail — still a total order, which is all the port requires,\n * but not creation order, and \"newest first\" would then be wrong for anything\n * created in a burst. A signup storm is exactly such a burst.\n */\nconst SEQUENCE_MAX = 0xfff;\n\n/**\n * A fresh id.\n *\n * Strictly increasing: within one millisecond it uses the sequence, and if that\n * overflows — more than 4096 ids in a millisecond, which no real\n * application reaches — it borrows the next millisecond rather than repeating\n * one. Across processes the 62 random bits of the tail are what keep two machines\n * apart.\n */\nexport function mintId(now: number = Date.now()): Id {\n\tlet ms = now;\n\n\tif (ms === lastMs) {\n\t\tsequence += 1;\n\t\tif (sequence > SEQUENCE_MAX) {\n\t\t\t// Borrow from the next millisecond. This keeps ids strictly\n\t\t\t// increasing at the cost of a timestamp a hair ahead of the clock,\n\t\t\t// which is the right trade: a repeated id would break the cursor.\n\t\t\tms = lastMs + 1;\n\t\t\tsequence = 0;\n\t\t}\n\t} else if (ms > lastMs) {\n\t\tsequence = 0;\n\t} else {\n\t\t// The clock went backwards — an NTP step, or a container resumed. Hold\n\t\t// the last millisecond and keep counting, so ids stay ordered even\n\t\t// though they stop matching the wall clock for a moment.\n\t\tms = lastMs;\n\t\tsequence += 1;\n\t\tif (sequence > SEQUENCE_MAX) {\n\t\t\tms = lastMs + 1;\n\t\t\tsequence = 0;\n\t\t}\n\t}\n\n\tlastMs = ms;\n\n\tconst bytes = new Uint8Array(16);\n\tcrypto.getRandomValues(bytes.subarray(8));\n\n\tbytes[0] = (ms / 2 ** 40) & 0xff;\n\tbytes[1] = (ms / 2 ** 32) & 0xff;\n\tbytes[2] = (ms / 2 ** 24) & 0xff;\n\tbytes[3] = (ms / 2 ** 16) & 0xff;\n\tbytes[4] = (ms / 2 ** 8) & 0xff;\n\tbytes[5] = ms & 0xff;\n\t// Version 7 in the high nibble, the sequence's top 4 bits below it.\n\tbytes[6] = 0x70 | ((sequence >> 8) & 0x0f);\n\tbytes[7] = sequence & 0xff;\n\t// Variant 10 in the two high bits of byte 8; the rest of it stays random.\n\tbytes[8] = 0x80 | ((bytes[8] as number) & 0x3f);\n\n\treturn `${HEX[bytes[0] as number]}${HEX[bytes[1] as number]}${HEX[bytes[2] as number]}${HEX[bytes[3] as number]}-${HEX[bytes[4] as number]}${HEX[bytes[5] as number]}-${HEX[bytes[6] as number]}${HEX[bytes[7] as number]}-${HEX[bytes[8] as number]}${HEX[bytes[9] as number]}-${HEX[bytes[10] as number]}${HEX[bytes[11] as number]}${HEX[bytes[12] as number]}${HEX[bytes[13] as number]}${HEX[bytes[14] as number]}${HEX[bytes[15] as number]}`;\n}\n\nconst UUID_V7 =\n\t/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;\n\n/**\n * Whether this string is an id this package could have minted.\n *\n * Used by an adapter to answer `null` for a malformed id **without reaching the\n * store**: an id arrives off a URL, and one of the wrong shape is \"no such\n * user\", not an outage and not a query. Rejecting it here also keeps a\n * hand-written id out of a store that would then hold something the cursor\n * cannot order.\n */\nexport function isId(value: string): boolean {\n\treturn UUID_V7.test(value);\n}\n\n/**\n * When this id was minted, from the id itself.\n *\n * Offered because the timestamp is *in* the id, so a `createdAt` column is a\n * convenience rather than the truth — and an adapter that loses one can still\n * answer. It is not a substitute for `createdAt`: the sequence may have borrowed\n * a millisecond, and a clock that stepped backwards is held rather than\n * followed, so this is accurate to the millisecond and no further.\n */\nexport function mintedAt(id: Id): Date {\n\tconst hex = id.replace(/-/g, '').slice(0, 12);\n\treturn new Date(Number.parseInt(hex, 16));\n}\n",
|
|
6
|
+
"import { NotFoundError, StoreConflict } from '../../errors/janus-error';\nimport type { Id } from '../../ids/id';\nimport type {\n\tJanusStores,\n\tSessionId,\n\tSessionRecord,\n\tSessionStore,\n\tTokenRecord,\n\tTokenStore,\n\tUserPatch,\n\tUserRecord,\n\tUserStore,\n} from './types';\n\n/**\n * The reference implementation of the store port, in memory.\n *\n * **Shipped and documented, not a test helper.** It is what a consumer uses in\n * their own unit tests, and what an adapter author compares against when a\n * conformance case they do not understand turns red. So it keeps the six rules\n * for real rather than approximately:\n *\n * - uniqueness is a constraint — an index checked and written inside the same\n * synchronous step, which is what atomic means on one event loop;\n * - `consumeToken` reads and writes with no `await` between the two, so twenty\n * concurrent calls see exactly one unspent token;\n * - every record is **copied in and copied out**, so a caller that mutates what\n * it passed or what it got back cannot reach the store — the in-memory\n * version of \"bytes round-trip\";\n * - a patch applies only the fields it names, and only the fields the port\n * declares: a stray `version` or `id` in a patch from JavaScript is ignored,\n * not written.\n *\n * Every method is `async` even though none waits on anything: a caller that\n * forgot an `await` must fail here the way it would against a real database.\n */\nexport function createMemoryStores(): JanusStores {\n\treturn {\n\t\tusers: memoryUserStore(),\n\t\tsessions: memorySessionStore(),\n\t\ttokens: memoryTokenStore(),\n\t};\n}\n\nconst copy = <T>(value: T): T => structuredClone(value);\n\n/** The unique key of one login. `\\u0000` cannot occur in a type name the core accepts. */\nconst keyOf = (type: string, login: string): string => `${type}\\u0000${login}`;\n\nfunction memoryUserStore(): UserStore {\n\tconst byId = new Map<Id, UserRecord>();\n\t// The unique index: (type, login) key → the id holding it.\n\tconst byLogin = new Map<string, Id>();\n\n\t/** The first of `logins` held by a user of `type` other than `id`. */\n\tconst takenBy = (\n\t\ttype: string,\n\t\tlogins: readonly string[],\n\t\tid: Id,\n\t): string | undefined =>\n\t\tlogins.find((login) => {\n\t\t\tconst holder = byLogin.get(keyOf(type, login));\n\t\t\treturn holder !== undefined && holder !== id;\n\t\t});\n\n\tconst taken = (operation: string, type: string, login: string) =>\n\t\tnew StoreConflict(\n\t\t\t'login',\n\t\t\t`${operation}: the login is taken by another ${type}`,\n\t\t\t{ login, userType: type, operation },\n\t\t);\n\n\treturn {\n\t\tasync insertUser(record) {\n\t\t\tconst stored = byId.get(record.id);\n\t\t\tif (stored !== undefined) return copy(stored);\n\n\t\t\tconst collision = takenBy(record.type, record.logins, record.id);\n\t\t\tif (collision !== undefined) {\n\t\t\t\tthrow taken('insertUser', record.type, collision);\n\t\t\t}\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tfor (const login of written.logins) {\n\t\t\t\tbyLogin.set(keyOf(written.type, login), written.id);\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync findUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync findUserByLogin(type, login) {\n\t\t\tconst id = byLogin.get(keyOf(type, login));\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync listUsers({ type, after, limit }) {\n\t\t\t// A Map keeps insertion order, not id order: a retried insert or an\n\t\t\t// id minted on another machine can land out of sequence.\n\t\t\tconst ids = [...byId.values()]\n\t\t\t\t.filter((user) => user.type === type)\n\t\t\t\t.map((user) => user.id)\n\t\t\t\t.filter((id) => after === null || id > after)\n\t\t\t\t.sort();\n\t\t\tconst pageIds = ids.slice(0, limit);\n\t\t\tconst last = pageIds.at(-1);\n\n\t\t\treturn {\n\t\t\t\titems: pageIds.map((id) => copy(byId.get(id) as UserRecord)),\n\t\t\t\tnextCursor: ids.length > limit && last !== undefined ? last : null,\n\t\t\t};\n\t\t},\n\n\t\tasync updateUser(id, patch, ifVersion) {\n\t\t\tconst stored = byId.get(id);\n\n\t\t\tif (stored === undefined) {\n\t\t\t\tthrow new NotFoundError('updateUser: no user has this id', {\n\t\t\t\t\tuserId: id,\n\t\t\t\t\toperation: 'updateUser',\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (stored.version !== ifVersion) {\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\t\tif (patch.logins !== undefined) {\n\t\t\t\tconst collision = takenBy(stored.type, patch.logins, id);\n\t\t\t\tif (collision !== undefined) {\n\t\t\t\t\tthrow taken('updateUser', stored.type, collision);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst written = applyPatch(stored, copy(patch));\n\n\t\t\tbyId.set(id, written);\n\t\t\tif (patch.logins !== undefined) {\n\t\t\t\tfor (const login of stored.logins) {\n\t\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t\t}\n\t\t\t\tfor (const login of written.logins) {\n\t\t\t\t\tbyLogin.set(keyOf(written.type, login), id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync deleteUser(id) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tbyId.delete(id);\n\t\t\tfor (const login of stored.logins) {\n\t\t\t\tbyLogin.delete(keyOf(stored.type, login));\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t};\n}\n\n/**\n * The record a patch produces: the fields it names, replaced whole; the fields\n * it does not name, untouched.\n *\n * Reads each field by name rather than spreading the patch, so a key the port\n * does not declare — `version`, `id`, `type`, `createdAt`, or a `snake_case`\n * typo from JavaScript — never reaches the record. A key present as\n * `undefined` is absent, never an erasure.\n */\nfunction applyPatch(stored: UserRecord, patch: UserPatch): UserRecord {\n\treturn {\n\t\t...stored,\n\t\tschemaVersion: patch.schemaVersion ?? stored.schemaVersion,\n\t\tactive: patch.active ?? stored.active,\n\t\tfields: patch.fields ?? stored.fields,\n\t\tlogins: patch.logins ?? stored.logins,\n\t\tpassword: patch.password === undefined ? stored.password : patch.password,\n\t\temailVerifiedAt:\n\t\t\tpatch.emailVerifiedAt === undefined\n\t\t\t\t? stored.emailVerifiedAt\n\t\t\t\t: patch.emailVerifiedAt,\n\t\tversion: stored.version + 1,\n\t\tupdatedAt: patch.updatedAt,\n\t};\n}\n\nfunction memorySessionStore(): SessionStore {\n\tconst byId = new Map<SessionId, SessionRecord>();\n\tconst byTokenHash = new Map<string, SessionId>();\n\n\tconst revoke = (id: SessionId, stored: SessionRecord, at: Date): void => {\n\t\tbyId.set(id, { ...stored, revokedAt: new Date(at) });\n\t};\n\n\treturn {\n\t\tasync insertSession(record) {\n\t\t\tif (byId.has(record.id)) return;\n\n\t\t\tconst written = copy(record);\n\t\t\tbyId.set(written.id, written);\n\t\t\tbyTokenHash.set(written.tokenHash, written.id);\n\t\t},\n\n\t\tasync findSessionByTokenHash(tokenHash) {\n\t\t\tconst id = byTokenHash.get(tokenHash);\n\t\t\tconst stored = id === undefined ? undefined : byId.get(id);\n\t\t\treturn stored === undefined ? null : copy(stored);\n\t\t},\n\n\t\tasync extendSession(id, expiresAt) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined || stored.revokedAt !== null) return null;\n\n\t\t\tconst written: SessionRecord = {\n\t\t\t\t...stored,\n\t\t\t\texpiresAt: new Date(expiresAt),\n\t\t\t};\n\t\t\tbyId.set(id, written);\n\t\t\treturn copy(written);\n\t\t},\n\n\t\tasync revokeSession(id, at) {\n\t\t\tconst stored = byId.get(id);\n\t\t\tif (stored === undefined) return false;\n\n\t\t\tif (stored.revokedAt === null) revoke(id, stored, at);\n\t\t\treturn true;\n\t\t},\n\n\t\tasync revokeUserSessions(userId, at, except) {\n\t\t\tlet revoked = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (\n\t\t\t\t\tstored.userId === userId &&\n\t\t\t\t\tstored.revokedAt === null &&\n\t\t\t\t\tid !== except\n\t\t\t\t) {\n\t\t\t\t\trevoke(id, stored, at);\n\t\t\t\t\trevoked += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn revoked;\n\t\t},\n\n\t\tasync deleteUserSessions(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\n\t\tasync deleteExpiredSessions(before) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [id, stored] of byId) {\n\t\t\t\tif (stored.expiresAt.getTime() <= before.getTime()) {\n\t\t\t\t\tbyId.delete(id);\n\t\t\t\t\tbyTokenHash.delete(stored.tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\nfunction memoryTokenStore(): TokenStore {\n\tconst byTokenHash = new Map<string, TokenRecord>();\n\n\treturn {\n\t\tasync insertToken(record) {\n\t\t\tif (byTokenHash.has(record.tokenHash)) return;\n\t\t\tbyTokenHash.set(record.tokenHash, copy(record));\n\t\t},\n\n\t\tasync consumeToken(tokenHash, kind, at) {\n\t\t\t// No `await` between this read and the write below: on one event loop\n\t\t\t// that is what makes the pair a single conditional write.\n\t\t\tconst stored = byTokenHash.get(tokenHash);\n\t\t\tif (stored === undefined || stored.kind !== kind) return null;\n\n\t\t\tconst before = copy(stored);\n\t\t\tif (stored.spentAt === null) {\n\t\t\t\tbyTokenHash.set(tokenHash, { ...stored, spentAt: new Date(at) });\n\t\t\t}\n\n\t\t\treturn before;\n\t\t},\n\n\t\tasync deleteUserTokens(userId) {\n\t\t\tlet deleted = 0;\n\n\t\t\tfor (const [tokenHash, stored] of byTokenHash) {\n\t\t\t\tif (stored.userId === userId) {\n\t\t\t\t\tbyTokenHash.delete(tokenHash);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": ";;;;;;AAyBA,IAAM,MAAyB,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,CAAC,GAAG,MAC9D,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAC/B;AAEA,IAAI,SAAS;AACb,IAAI,WAAW;AAWf,IAAM,eAAe;AAWd,SAAS,OAAM,CAAC,MAAc,KAAK,IAAI,GAAO;AAAA,EACpD,IAAI,KAAK;AAAA,EAET,IAAI,OAAO,QAAQ;AAAA,IAClB,YAAY;AAAA,IACZ,IAAI,WAAW,cAAc;AAAA,MAI5B,KAAK,SAAS;AAAA,MACd,WAAW;AAAA,IACZ;AAAA,EACD,EAAO,SAAI,KAAK,QAAQ;AAAA,IACvB,WAAW;AAAA,EACZ,EAAO;AAAA,IAIN,KAAK;AAAA,IACL,YAAY;AAAA,IACZ,IAAI,WAAW,cAAc;AAAA,MAC5B,KAAK,SAAS;AAAA,MACd,WAAW;AAAA,IACZ;AAAA;AAAA,EAGD,SAAS;AAAA,EAET,MAAM,QAAQ,IAAI,WAAW,EAAE;AAAA,EAC/B,OAAO,gBAAgB,MAAM,SAAS,CAAC,CAAC;AAAA,EAExC,MAAM,KAAM,KAAK,KAAK,KAAM;AAAA,EAC5B,MAAM,KAAM,KAAK,KAAK,KAAM;AAAA,EAC5B,MAAM,KAAM,KAAK,KAAK,KAAM;AAAA,EAC5B,MAAM,KAAM,KAAK,KAAK,KAAM;AAAA,EAC5B,MAAM,KAAM,KAAK,KAAK,IAAK;AAAA,EAC3B,MAAM,KAAK,KAAK;AAAA,EAEhB,MAAM,KAAK,MAAS,YAAY,IAAK;AAAA,EACrC,MAAM,KAAK,WAAW;AAAA,EAEtB,MAAM,KAAK,MAAS,MAAM,KAAgB;AAAA,EAE1C,OAAO,GAAG,IAAI,MAAM,MAAgB,IAAI,MAAM,MAAgB,IAAI,MAAM,MAAgB,IAAI,MAAM,OAAiB,IAAI,MAAM,MAAgB,IAAI,MAAM,OAAiB,IAAI,MAAM,MAAgB,IAAI,MAAM,OAAiB,IAAI,MAAM,MAAgB,IAAI,MAAM,OAAiB,IAAI,MAAM,OAAiB,IAAI,MAAM,OAAiB,IAAI,MAAM,OAAiB,IAAI,MAAM,OAAiB,IAAI,MAAM,OAAiB,IAAI,MAAM;AAAA;AAGpa,IAAM,UACL;AAWM,SAAS,KAAI,CAAC,OAAwB;AAAA,EAC5C,OAAO,QAAQ,KAAK,KAAK;AAAA;AAYnB,SAAS,SAAQ,CAAC,IAAc;AAAA,EACtC,MAAM,MAAM,GAAG,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE;AAAA,EAC5C,OAAO,IAAI,KAAK,OAAO,SAAS,KAAK,EAAE,CAAC;AAAA;;;ACzFlC,SAAS,mBAAkB,GAAgB;AAAA,EACjD,OAAO;AAAA,IACN,OAAO,gBAAgB;AAAA,IACvB,UAAU,mBAAmB;AAAA,IAC7B,QAAQ,iBAAiB;AAAA,EAC1B;AAAA;AAGD,IAAM,OAAO,CAAI,UAAgB,gBAAgB,KAAK;AAGtD,IAAM,QAAQ,CAAC,MAAc,UAA0B,GAAG,WAAa;AAEvE,SAAS,eAAe,GAAc;AAAA,EACrC,MAAM,OAAO,IAAI;AAAA,EAEjB,MAAM,UAAU,IAAI;AAAA,EAGpB,MAAM,UAAU,CACf,MACA,QACA,OAEA,OAAO,KAAK,CAAC,UAAU;AAAA,IACtB,MAAM,SAAS,QAAQ,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA,IAC7C,OAAO,WAAW,aAAa,WAAW;AAAA,GAC1C;AAAA,EAEF,MAAM,QAAQ,CAAC,WAAmB,MAAc,UAC/C,IAAI,eACH,SACA,GAAG,4CAA4C,QAC/C,EAAE,OAAO,UAAU,MAAM,UAAU,CACpC;AAAA,EAED,OAAO;AAAA,SACA,WAAU,CAAC,QAAQ;AAAA,MACxB,MAAM,SAAS,KAAK,IAAI,OAAO,EAAE;AAAA,MACjC,IAAI,WAAW;AAAA,QAAW,OAAO,KAAK,MAAM;AAAA,MAE5C,MAAM,YAAY,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,EAAE;AAAA,MAC/D,IAAI,cAAc,WAAW;AAAA,QAC5B,MAAM,MAAM,cAAc,OAAO,MAAM,SAAS;AAAA,MACjD;AAAA,MAEA,MAAM,UAAU,KAAK,MAAM;AAAA,MAC3B,KAAK,IAAI,QAAQ,IAAI,OAAO;AAAA,MAC5B,WAAW,SAAS,QAAQ,QAAQ;AAAA,QACnC,QAAQ,IAAI,MAAM,QAAQ,MAAM,KAAK,GAAG,QAAQ,EAAE;AAAA,MACnD;AAAA,MAEA,OAAO,KAAK,OAAO;AAAA;AAAA,SAGd,SAAQ,CAAC,IAAI;AAAA,MAClB,MAAM,SAAS,KAAK,IAAI,EAAE;AAAA,MAC1B,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM;AAAA;AAAA,SAG3C,gBAAe,CAAC,MAAM,OAAO;AAAA,MAClC,MAAM,KAAK,QAAQ,IAAI,MAAM,MAAM,KAAK,CAAC;AAAA,MACzC,MAAM,SAAS,OAAO,YAAY,YAAY,KAAK,IAAI,EAAE;AAAA,MACzD,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM;AAAA;AAAA,SAG3C,UAAS,GAAG,MAAM,OAAO,SAAS;AAAA,MAGvC,MAAM,MAAM,CAAC,GAAG,KAAK,OAAO,CAAC,EAC3B,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,EACnC,IAAI,CAAC,SAAS,KAAK,EAAE,EACrB,OAAO,CAAC,OAAO,UAAU,QAAQ,KAAK,KAAK,EAC3C,KAAK;AAAA,MACP,MAAM,UAAU,IAAI,MAAM,GAAG,KAAK;AAAA,MAClC,MAAM,OAAO,QAAQ,GAAG,EAAE;AAAA,MAE1B,OAAO;AAAA,QACN,OAAO,QAAQ,IAAI,CAAC,OAAO,KAAK,KAAK,IAAI,EAAE,CAAe,CAAC;AAAA,QAC3D,YAAY,IAAI,SAAS,SAAS,SAAS,YAAY,OAAO;AAAA,MAC/D;AAAA;AAAA,SAGK,WAAU,CAAC,IAAI,OAAO,WAAW;AAAA,MACtC,MAAM,SAAS,KAAK,IAAI,EAAE;AAAA,MAE1B,IAAI,WAAW,WAAW;AAAA,QACzB,MAAM,IAAI,eAAc,mCAAmC;AAAA,UAC1D,QAAQ;AAAA,UACR,WAAW;AAAA,QACZ,CAAC;AAAA,MACF;AAAA,MAEA,IAAI,OAAO,YAAY,WAAW;AAAA,QACjC,MAAM,IAAI,eACT,WACA,gCAAgC,oBAAoB,OAAO,WAC3D;AAAA,UACC,QAAQ;AAAA,UACR,iBAAiB;AAAA,UACjB,eAAe,OAAO;AAAA,UACtB,WAAW;AAAA,QACZ,CACD;AAAA,MACD;AAAA,MAEA,IAAI,MAAM,WAAW,WAAW;AAAA,QAC/B,MAAM,YAAY,QAAQ,OAAO,MAAM,MAAM,QAAQ,EAAE;AAAA,QACvD,IAAI,cAAc,WAAW;AAAA,UAC5B,MAAM,MAAM,cAAc,OAAO,MAAM,SAAS;AAAA,QACjD;AAAA,MACD;AAAA,MAEA,MAAM,UAAU,WAAW,QAAQ,KAAK,KAAK,CAAC;AAAA,MAE9C,KAAK,IAAI,IAAI,OAAO;AAAA,MACpB,IAAI,MAAM,WAAW,WAAW;AAAA,QAC/B,WAAW,SAAS,OAAO,QAAQ;AAAA,UAClC,QAAQ,OAAO,MAAM,OAAO,MAAM,KAAK,CAAC;AAAA,QACzC;AAAA,QACA,WAAW,SAAS,QAAQ,QAAQ;AAAA,UACnC,QAAQ,IAAI,MAAM,QAAQ,MAAM,KAAK,GAAG,EAAE;AAAA,QAC3C;AAAA,MACD;AAAA,MAEA,OAAO,KAAK,OAAO;AAAA;AAAA,SAGd,WAAU,CAAC,IAAI;AAAA,MACpB,MAAM,SAAS,KAAK,IAAI,EAAE;AAAA,MAC1B,IAAI,WAAW;AAAA,QAAW,OAAO;AAAA,MAEjC,KAAK,OAAO,EAAE;AAAA,MACd,WAAW,SAAS,OAAO,QAAQ;AAAA,QAClC,QAAQ,OAAO,MAAM,OAAO,MAAM,KAAK,CAAC;AAAA,MACzC;AAAA,MACA,OAAO;AAAA;AAAA,EAET;AAAA;AAYD,SAAS,UAAU,CAAC,QAAoB,OAA8B;AAAA,EACrE,OAAO;AAAA,OACH;AAAA,IACH,eAAe,MAAM,iBAAiB,OAAO;AAAA,IAC7C,QAAQ,MAAM,UAAU,OAAO;AAAA,IAC/B,QAAQ,MAAM,UAAU,OAAO;AAAA,IAC/B,QAAQ,MAAM,UAAU,OAAO;AAAA,IAC/B,UAAU,MAAM,aAAa,YAAY,OAAO,WAAW,MAAM;AAAA,IACjE,iBACC,MAAM,oBAAoB,YACvB,OAAO,kBACP,MAAM;AAAA,IACV,SAAS,OAAO,UAAU;AAAA,IAC1B,WAAW,MAAM;AAAA,EAClB;AAAA;AAGD,SAAS,kBAAkB,GAAiB;AAAA,EAC3C,MAAM,OAAO,IAAI;AAAA,EACjB,MAAM,cAAc,IAAI;AAAA,EAExB,MAAM,SAAS,CAAC,IAAe,QAAuB,OAAmB;AAAA,IACxE,KAAK,IAAI,IAAI,KAAK,QAAQ,WAAW,IAAI,KAAK,EAAE,EAAE,CAAC;AAAA;AAAA,EAGpD,OAAO;AAAA,SACA,cAAa,CAAC,QAAQ;AAAA,MAC3B,IAAI,KAAK,IAAI,OAAO,EAAE;AAAA,QAAG;AAAA,MAEzB,MAAM,UAAU,KAAK,MAAM;AAAA,MAC3B,KAAK,IAAI,QAAQ,IAAI,OAAO;AAAA,MAC5B,YAAY,IAAI,QAAQ,WAAW,QAAQ,EAAE;AAAA;AAAA,SAGxC,uBAAsB,CAAC,WAAW;AAAA,MACvC,MAAM,KAAK,YAAY,IAAI,SAAS;AAAA,MACpC,MAAM,SAAS,OAAO,YAAY,YAAY,KAAK,IAAI,EAAE;AAAA,MACzD,OAAO,WAAW,YAAY,OAAO,KAAK,MAAM;AAAA;AAAA,SAG3C,cAAa,CAAC,IAAI,WAAW;AAAA,MAClC,MAAM,SAAS,KAAK,IAAI,EAAE;AAAA,MAC1B,IAAI,WAAW,aAAa,OAAO,cAAc;AAAA,QAAM,OAAO;AAAA,MAE9D,MAAM,UAAyB;AAAA,WAC3B;AAAA,QACH,WAAW,IAAI,KAAK,SAAS;AAAA,MAC9B;AAAA,MACA,KAAK,IAAI,IAAI,OAAO;AAAA,MACpB,OAAO,KAAK,OAAO;AAAA;AAAA,SAGd,cAAa,CAAC,IAAI,IAAI;AAAA,MAC3B,MAAM,SAAS,KAAK,IAAI,EAAE;AAAA,MAC1B,IAAI,WAAW;AAAA,QAAW,OAAO;AAAA,MAEjC,IAAI,OAAO,cAAc;AAAA,QAAM,OAAO,IAAI,QAAQ,EAAE;AAAA,MACpD,OAAO;AAAA;AAAA,SAGF,mBAAkB,CAAC,QAAQ,IAAI,QAAQ;AAAA,MAC5C,IAAI,UAAU;AAAA,MAEd,YAAY,IAAI,WAAW,MAAM;AAAA,QAChC,IACC,OAAO,WAAW,UAClB,OAAO,cAAc,QACrB,OAAO,QACN;AAAA,UACD,OAAO,IAAI,QAAQ,EAAE;AAAA,UACrB,WAAW;AAAA,QACZ;AAAA,MACD;AAAA,MAEA,OAAO;AAAA;AAAA,SAGF,mBAAkB,CAAC,QAAQ;AAAA,MAChC,IAAI,UAAU;AAAA,MAEd,YAAY,IAAI,WAAW,MAAM;AAAA,QAChC,IAAI,OAAO,WAAW,QAAQ;AAAA,UAC7B,KAAK,OAAO,EAAE;AAAA,UACd,YAAY,OAAO,OAAO,SAAS;AAAA,UACnC,WAAW;AAAA,QACZ;AAAA,MACD;AAAA,MAEA,OAAO;AAAA;AAAA,SAGF,sBAAqB,CAAC,QAAQ;AAAA,MACnC,IAAI,UAAU;AAAA,MAEd,YAAY,IAAI,WAAW,MAAM;AAAA,QAChC,IAAI,OAAO,UAAU,QAAQ,KAAK,OAAO,QAAQ,GAAG;AAAA,UACnD,KAAK,OAAO,EAAE;AAAA,UACd,YAAY,OAAO,OAAO,SAAS;AAAA,UACnC,WAAW;AAAA,QACZ;AAAA,MACD;AAAA,MAEA,OAAO;AAAA;AAAA,EAET;AAAA;AAGD,SAAS,gBAAgB,GAAe;AAAA,EACvC,MAAM,cAAc,IAAI;AAAA,EAExB,OAAO;AAAA,SACA,YAAW,CAAC,QAAQ;AAAA,MACzB,IAAI,YAAY,IAAI,OAAO,SAAS;AAAA,QAAG;AAAA,MACvC,YAAY,IAAI,OAAO,WAAW,KAAK,MAAM,CAAC;AAAA;AAAA,SAGzC,aAAY,CAAC,WAAW,MAAM,IAAI;AAAA,MAGvC,MAAM,SAAS,YAAY,IAAI,SAAS;AAAA,MACxC,IAAI,WAAW,aAAa,OAAO,SAAS;AAAA,QAAM,OAAO;AAAA,MAEzD,MAAM,SAAS,KAAK,MAAM;AAAA,MAC1B,IAAI,OAAO,YAAY,MAAM;AAAA,QAC5B,YAAY,IAAI,WAAW,KAAK,QAAQ,SAAS,IAAI,KAAK,EAAE,EAAE,CAAC;AAAA,MAChE;AAAA,MAEA,OAAO;AAAA;AAAA,SAGF,iBAAgB,CAAC,QAAQ;AAAA,MAC9B,IAAI,UAAU;AAAA,MAEd,YAAY,WAAW,WAAW,aAAa;AAAA,QAC9C,IAAI,OAAO,WAAW,QAAQ;AAAA,UAC7B,YAAY,OAAO,SAAS;AAAA,UAC5B,WAAW;AAAA,QACZ;AAAA,MACD;AAAA,MAEA,OAAO;AAAA;AAAA,EAET;AAAA;",
|
|
9
|
+
"debugId": "609E38330FBCD6ED64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/errors/janus-error.ts", "../src/subjects/subject.ts", "../src/subjects/notation.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * What this package refuses, as a string a caller can switch on.\n *\n * Every code is a **refusal at call time, on a value that could have come from\n * a request** — which is the rule that decides whether something belongs here\n * or stays a bare `TypeError`. A refusal that can only come from how the\n * application was wired (`janus()` with no schema, a lifespan that is\n * not a duration, a store missing a method) throws a plain `TypeError`\n * instead: no request handler should ever answer one, so no handler needs to\n * tell it apart from the others.\n *\n * The codes are `SCREAMING_SNAKE`, and that is not an exception to this\n * repository's camelCase rule — they are data values, not API identifiers, the\n * same shape `code` has in `@nxgt/mongo` and `@nxgt/redis`. Every *key* in\n * this package is camelCase.\n */\nexport type JanusErrorCode =\n\t/**\n\t * The store could not answer — a refused connection, a timeout, a primary\n\t * stepping down, a deserialisation failure, a bug in the adapter.\n\t *\n\t * **Never a negative answer.** A handler answers 503 and lets the visitor\n\t * retry. Mapping this to a 404, to `null` or to `false` turns an outage\n\t * into a silent lockout: every user is told they do not exist.\n\t * That failure has been measured twice in this organisation, two days\n\t * apart, and it is the reason this package's port is specified rather than\n\t * merely documented.\n\t */\n\t| 'STORE_FAILED'\n\t/**\n\t * The store answered, and there is no such record.\n\t *\n\t * Raised by the `get*` calls, never by the `find*` calls — those return\n\t * `null`, which is a value the caller decides what to do with.\n\t */\n\t| 'NOT_FOUND'\n\t/**\n\t * The login — an e-mail, a username — is already held by another user of\n\t * the same type.\n\t *\n\t * Raised by the **store's own unique constraint** and surfaced here, never\n\t * decided by reading first: two concurrent sign-ups both pass a read, and\n\t * only a constraint refuses one of them. Carries `login` and `userType`.\n\t */\n\t| 'LOGIN_TAKEN'\n\t/**\n\t * The record changed since it was read: the version it was expected to\n\t * hold is no longer the version it holds, and **nothing was written**.\n\t * Read it again and retry. Carries `expectedVersion` and `actualVersion`.\n\t */\n\t| 'VERSION_CONFLICT'\n\t/**\n\t * The fields failed the user type's schema. Carries `issues`, whose paths\n\t * are the fields' own, so a handler can answer 400 field by field.\n\t */\n\t| 'USER_INVALID'\n\t/**\n\t * The password is shorter than the policy's minimum. Reports the policy,\n\t * never the password.\n\t */\n\t| 'PASSWORD_TOO_SHORT'\n\t/**\n\t * The login and the password do not match: no such login, no password set,\n\t * or the wrong one — **one code for the three**, so a response cannot tell\n\t * which accounts exist. `reason` tells them apart for your logs and your\n\t * rate limiter, and never belongs in a response body.\n\t */\n\t| 'CREDENTIALS_INVALID'\n\t/**\n\t * A stored hash whose prefix names no wired verifier — typically an import\n\t * from a system whose format this core cannot read. Reports the prefix,\n\t * never the hash.\n\t */\n\t| 'HASH_UNSUPPORTED'\n\t/**\n\t * The user is inactive: the record and its password are kept, and every\n\t * sign-in is refused. Only told to somebody who gave the right password.\n\t */\n\t| 'USER_INACTIVE'\n\t/** No token holds that secret. */\n\t| 'TOKEN_UNKNOWN'\n\t/**\n\t * The token was already spent. Told apart from `TOKEN_UNKNOWN` for the\n\t * message only — both are refusals, and the outcome is the same.\n\t */\n\t| 'TOKEN_SPENT'\n\t/**\n\t * The token existed and its expiry has passed. It is spent all the same,\n\t * so it cannot be retried.\n\t */\n\t| 'TOKEN_EXPIRED'\n\t/**\n\t * The token was sent to an e-mail the user no longer has. Confirming it\n\t * would verify an address nobody holds any more, so it is spent and refused.\n\t */\n\t| 'TOKEN_STALE'\n\t/** A cursor this store did not mint, or one written for another ordering.\n\t * Never a silent first page: a caller paging a list would loop for ever. */\n\t| 'INVALID_CURSOR'\n\t/**\n\t * The wired store does not implement the optional capability this call\n\t * needs. Names the method and the slot, so the sentence says which store to\n\t * change or which call to stop making.\n\t */\n\t| 'UNSUPPORTED'\n\t/**\n\t * A permission check walked deeper than `maxDepth` relations without an\n\t * answer. **Not a refusal**: an evaluation that stopped half-way has not\n\t * decided anything, and answering `false` would hide a model that is too\n\t * deep behind denials nobody can explain. A cycle in the data is not this —\n\t * it is cut, silently. Carries `permission` and `maxDepth`.\n\t */\n\t| 'PERMISSION_DEPTH';\n\n/** One thing that was wrong with a user's fields, at one path. */\nexport interface Issue {\n\t/** The path inside the fields, as the schema reported it: `['address', 'city']`. */\n\treadonly path: readonly (string | number)[];\n\treadonly message: string;\n}\n\n/**\n * Why a sign-in was refused, for your logs and rate limiter.\n *\n * **Never put it in a response body.** `unknownLogin` is an account\n * enumeration oracle.\n */\nexport type CredentialRefusal = 'unknownLogin' | 'noPassword' | 'wrongPassword';\n\n/**\n * What an error may carry beside its code.\n *\n * **No field here ever holds a secret.** Not a password, not a hash, not a\n * session token, not a token secret, not a token's hash, and not a connection\n * URI — a connection string holds a password, and the specs assert its absence\n * from every message. A `login` may appear, because the caller just sent it.\n */\nexport interface JanusErrorOptions {\n\treadonly userId?: string;\n\treadonly userType?: string;\n\t/** The login a conflict names: an address, never a secret. */\n\treadonly login?: string;\n\treadonly reason?: CredentialRefusal;\n\t/** The prefix of a hash whose format is unknown. Never the hash. */\n\treadonly hashPrefix?: string;\n\treadonly expectedVersion?: number;\n\treadonly actualVersion?: number;\n\treadonly issues?: readonly Issue[];\n\t/** The minimum the policy requires. Never the password that failed it. */\n\treadonly minLength?: number;\n\t/** The port method being called: `insertUser`, `consumeToken`. */\n\treadonly operation?: string;\n\t/** Which store slot: the sentence should say which store to change. */\n\treadonly slot?: 'users' | 'sessions' | 'tokens' | 'relations';\n\t/** The permission being checked, in the notation: `record:r1#view`. */\n\treadonly permission?: string;\n\t/** The depth a check may walk. */\n\treadonly maxDepth?: number;\n\treadonly cause?: unknown;\n}\n\n/**\n * The base of everything this package throws at call time.\n *\n * It extends `Error` and not `TypeError`, and the rule behind that is\n * `nxgt-data`'s: *extend whichever class the refusals it replaces already\n * threw, so no consumer's `catch` stops working*. These replace nothing — the\n * package is new — and `DataError`, `RedisError` and `S3Error` all extend\n * `Error`, so nobody has to order their `catch` blocks.\n *\n * **There is exactly one definition of this class**, and that matters more here\n * than it looks: an adapter in another package throws `StoreFailure` and this\n * package tests it with `instanceof`. Two copies and the product is wrong about\n * what an outage is. `build.ts` shares the module across entry points with\n * `splitting: true`, and `scripts/verify-artifacts.ts` fails the build if any\n * class name appears in two entry bundles of the packed tarball.\n */\nexport class JanusError extends Error {\n\toverride name = 'JanusError';\n\treadonly code: JanusErrorCode = 'STORE_FAILED';\n\treadonly userId: string | undefined;\n\treadonly userType: string | undefined;\n\treadonly login: string | undefined;\n\treadonly reason: CredentialRefusal | undefined;\n\treadonly hashPrefix: string | undefined;\n\treadonly expectedVersion: number | undefined;\n\treadonly actualVersion: number | undefined;\n\treadonly issues: readonly Issue[] | undefined;\n\treadonly minLength: number | undefined;\n\treadonly operation: string | undefined;\n\treadonly slot: 'users' | 'sessions' | 'tokens' | 'relations' | undefined;\n\treadonly permission: string | undefined;\n\treadonly maxDepth: number | undefined;\n\n\tconstructor(message: string, options?: JanusErrorOptions) {\n\t\tsuper(message, { cause: options?.cause });\n\t\tthis.userId = options?.userId;\n\t\tthis.userType = options?.userType;\n\t\tthis.login = options?.login;\n\t\tthis.reason = options?.reason;\n\t\tthis.hashPrefix = options?.hashPrefix;\n\t\tthis.expectedVersion = options?.expectedVersion;\n\t\tthis.actualVersion = options?.actualVersion;\n\t\tthis.issues = options?.issues;\n\t\tthis.minLength = options?.minLength;\n\t\tthis.operation = options?.operation;\n\t\tthis.slot = options?.slot;\n\t\tthis.permission = options?.permission;\n\t\tthis.maxDepth = options?.maxDepth;\n\t}\n}\n\n/**\n * The store could not answer.\n *\n * **This is the class an adapter throws**, and it is exported for that reason:\n * an adapter defines no error class of its own, so `instanceof` holds across\n * the two packages. Any other throw from a store is treated as a failure too —\n * throwing this one is how an adapter says so precisely, and sets `cause`.\n */\nexport class StoreFailure extends JanusError {\n\toverride name = 'StoreFailure';\n\toverride readonly code = 'STORE_FAILED' as const;\n}\n\n/**\n * A uniqueness or a version constraint the store refused.\n *\n * Also thrown by an adapter, and also for the `instanceof` reason. `on` says\n * which constraint, because the two are answered differently: a login\n * collision is the caller's to fix, a version conflict is a retry.\n */\nexport class StoreConflict extends JanusError {\n\toverride name = 'StoreConflict';\n\toverride readonly code: JanusErrorCode;\n\treadonly on: 'login' | 'version';\n\n\tconstructor(\n\t\ton: 'login' | 'version',\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.on = on;\n\t\tthis.code = on === 'login' ? 'LOGIN_TAKEN' : 'VERSION_CONFLICT';\n\t}\n}\n\n/** There is no such record, and the store said so. */\nexport class NotFoundError extends JanusError {\n\toverride name = 'NotFoundError';\n\toverride readonly code = 'NOT_FOUND' as const;\n}\n\n/** The fields failed the user type's schema. */\nexport class UserInvalidError extends JanusError {\n\toverride name = 'UserInvalidError';\n\toverride readonly code = 'USER_INVALID' as const;\n}\n\n/** A password too short, credentials that do not match, or a hash format nobody reads. */\nexport class CredentialError extends JanusError {\n\toverride name = 'CredentialError';\n\toverride readonly code: JanusErrorCode;\n\n\tconstructor(\n\t\tcode: Extract<\n\t\t\tJanusErrorCode,\n\t\t\t'PASSWORD_TOO_SHORT' | 'CREDENTIALS_INVALID' | 'HASH_UNSUPPORTED'\n\t\t>,\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t}\n}\n\n/** The user is inactive, and the password given was the right one. */\nexport class UserInactiveError extends JanusError {\n\toverride name = 'UserInactiveError';\n\toverride readonly code = 'USER_INACTIVE' as const;\n}\n\n/** A one-time token that is unknown, already spent, lapsed, or sent to an e-mail the user no longer has. */\nexport class TokenError extends JanusError {\n\toverride name = 'TokenError';\n\toverride readonly code: JanusErrorCode;\n\n\tconstructor(\n\t\tcode: Extract<\n\t\t\tJanusErrorCode,\n\t\t\t'TOKEN_UNKNOWN' | 'TOKEN_SPENT' | 'TOKEN_EXPIRED' | 'TOKEN_STALE'\n\t\t>,\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t}\n}\n\n/** A cursor this store did not mint, or one for another ordering. */\nexport class InvalidCursorError extends JanusError {\n\toverride name = 'InvalidCursorError';\n\toverride readonly code = 'INVALID_CURSOR' as const;\n}\n\n/** The wired store does not implement the optional capability asked for. */\nexport class UnsupportedError extends JanusError {\n\toverride name = 'UnsupportedError';\n\toverride readonly code = 'UNSUPPORTED' as const;\n}\n\n/** A permission check walked deeper than `maxDepth` without an answer. */\nexport class PermissionDepthError extends JanusError {\n\toverride name = 'PermissionDepthError';\n\toverride readonly code = 'PERMISSION_DEPTH' as const;\n}\n",
|
|
5
|
+
"/**\n * What this package refuses, as a string a caller can switch on.\n *\n * Every code is a **refusal at call time, on a value that could have come from\n * a request** — which is the rule that decides whether something belongs here\n * or stays a bare `TypeError`. A refusal that can only come from how the\n * application was wired (`janus()` with no schema, a lifespan that is\n * not a duration, a store missing a method) throws a plain `TypeError`\n * instead: no request handler should ever answer one, so no handler needs to\n * tell it apart from the others.\n *\n * The codes are `SCREAMING_SNAKE`, and that is not an exception to this\n * repository's camelCase rule — they are data values, not API identifiers, the\n * same shape `code` has in `@nxgt/mongo` and `@nxgt/redis`. Every *key* in\n * this package is camelCase.\n */\nexport type JanusErrorCode =\n\t/**\n\t * The store could not answer — a refused connection, a timeout, a primary\n\t * stepping down, a deserialisation failure, a bug in the adapter.\n\t *\n\t * **Never a negative answer.** A handler answers 503 and lets the visitor\n\t * retry. Mapping this to a 404, to `null` or to `false` turns an outage\n\t * into a silent lockout: every user is told they do not exist.\n\t * That failure has been measured twice in this organisation, two days\n\t * apart, and it is the reason this package's port is specified rather than\n\t * merely documented.\n\t */\n\t| 'STORE_FAILED'\n\t/**\n\t * The store answered, and there is no such record.\n\t *\n\t * Raised by the `get*` calls, never by the `find*` calls — those return\n\t * `null`, which is a value the caller decides what to do with.\n\t */\n\t| 'NOT_FOUND'\n\t/**\n\t * The login — an e-mail, a username — is already held by another user of\n\t * the same type.\n\t *\n\t * Raised by the **store's own unique constraint** and surfaced here, never\n\t * decided by reading first: two concurrent sign-ups both pass a read, and\n\t * only a constraint refuses one of them. Carries `login` and `userType`.\n\t */\n\t| 'LOGIN_TAKEN'\n\t/**\n\t * The record changed since it was read: the version it was expected to\n\t * hold is no longer the version it holds, and **nothing was written**.\n\t * Read it again and retry. Carries `expectedVersion` and `actualVersion`.\n\t */\n\t| 'VERSION_CONFLICT'\n\t/**\n\t * The fields failed the user type's schema. Carries `issues`, whose paths\n\t * are the fields' own, so a handler can answer 400 field by field.\n\t */\n\t| 'USER_INVALID'\n\t/**\n\t * The password is shorter than the policy's minimum. Reports the policy,\n\t * never the password.\n\t */\n\t| 'PASSWORD_TOO_SHORT'\n\t/**\n\t * The login and the password do not match: no such login, no password set,\n\t * or the wrong one — **one code for the three**, so a response cannot tell\n\t * which accounts exist. `reason` tells them apart for your logs and your\n\t * rate limiter, and never belongs in a response body.\n\t */\n\t| 'CREDENTIALS_INVALID'\n\t/**\n\t * A stored hash whose prefix names no wired verifier — typically an import\n\t * from a system whose format this core cannot read. Reports the prefix,\n\t * never the hash.\n\t */\n\t| 'HASH_UNSUPPORTED'\n\t/**\n\t * The user is inactive: the record and its password are kept, and every\n\t * sign-in is refused. Only told to somebody who gave the right password.\n\t */\n\t| 'USER_INACTIVE'\n\t/** No token holds that secret. */\n\t| 'TOKEN_UNKNOWN'\n\t/**\n\t * The token was already spent. Told apart from `TOKEN_UNKNOWN` for the\n\t * message only — both are refusals, and the outcome is the same.\n\t */\n\t| 'TOKEN_SPENT'\n\t/**\n\t * The token existed and its expiry has passed. It is spent all the same,\n\t * so it cannot be retried.\n\t */\n\t| 'TOKEN_EXPIRED'\n\t/**\n\t * The token was sent to an e-mail the user no longer has. Confirming it\n\t * would verify an address nobody holds any more, so it is spent and refused.\n\t */\n\t| 'TOKEN_STALE'\n\t/** A cursor this store did not mint, or one written for another ordering.\n\t * Never a silent first page: a caller paging a list would loop for ever. */\n\t| 'INVALID_CURSOR'\n\t/**\n\t * The wired store does not implement the optional capability this call\n\t * needs. Names the method and the slot, so the sentence says which store to\n\t * change or which call to stop making.\n\t */\n\t| 'UNSUPPORTED'\n\t/**\n\t * A permission check walked deeper than `maxDepth` relations without an\n\t * answer. **Not a refusal**: an evaluation that stopped half-way has not\n\t * decided anything, and answering `false` would hide a model that is too\n\t * deep behind denials nobody can explain. A cycle in the data is not this —\n\t * it is cut, silently. Carries `permission` and `maxDepth`.\n\t */\n\t| 'PERMISSION_DEPTH';\n\n/** One thing that was wrong with a user's fields, at one path. */\nexport interface Issue {\n\t/** The path inside the fields, as the schema reported it: `['address', 'city']`. */\n\treadonly path: readonly (string | number)[];\n\treadonly message: string;\n}\n\n/**\n * Why a sign-in was refused, for your logs and rate limiter.\n *\n * **Never put it in a response body.** `unknownLogin` is an account\n * enumeration oracle.\n */\nexport type CredentialRefusal = 'unknownLogin' | 'noPassword' | 'wrongPassword';\n\n/**\n * What an error may carry beside its code.\n *\n * **No field here ever holds a secret.** Not a password, not a hash, not a\n * session token, not a token secret, not a token's hash, and not a connection\n * URI — a connection string holds a password, and the specs assert its absence\n * from every message. Nor a login: a message reports a shape, never a value,\n * and a conflict carries the login in `login`.\n */\nexport interface JanusErrorOptions {\n\treadonly userId?: string;\n\treadonly userType?: string;\n\t/** The login a conflict names: an address, never a secret. */\n\treadonly login?: string;\n\treadonly reason?: CredentialRefusal;\n\t/** The prefix of a hash whose format is unknown. Never the hash. */\n\treadonly hashPrefix?: string;\n\treadonly expectedVersion?: number;\n\treadonly actualVersion?: number;\n\treadonly issues?: readonly Issue[];\n\t/** The minimum the policy requires. Never the password that failed it. */\n\treadonly minLength?: number;\n\t/** The port method being called: `insertUser`, `consumeToken`. */\n\treadonly operation?: string;\n\t/** Which store slot: the sentence should say which store to change. */\n\treadonly slot?: 'users' | 'sessions' | 'tokens' | 'relations';\n\t/** The permission being checked, in the notation: `record:r1#view`. */\n\treadonly permission?: string;\n\t/** The depth a check may walk. */\n\treadonly maxDepth?: number;\n\treadonly cause?: unknown;\n}\n\n/**\n * The base of everything this package throws at call time.\n *\n * It extends `Error` and not `TypeError`, and the rule behind that is\n * `nxgt-data`'s: *extend whichever class the refusals it replaces already\n * threw, so no consumer's `catch` stops working*. These replace nothing — the\n * package is new — and `DataError`, `RedisError` and `S3Error` all extend\n * `Error`, so nobody has to order their `catch` blocks.\n *\n * **There is exactly one definition of this class**, and that matters more here\n * than it looks: an adapter in another package throws `StoreFailure` and this\n * package tests it with `instanceof`. Two copies and the product is wrong about\n * what an outage is. `build.ts` shares the module across entry points with\n * `splitting: true`, and `scripts/verify-artifacts.ts` fails the build if any\n * class name appears in two entry bundles of the packed tarball.\n */\nexport class JanusError extends Error {\n\toverride name = 'JanusError';\n\treadonly code: JanusErrorCode = 'STORE_FAILED';\n\treadonly userId: string | undefined;\n\treadonly userType: string | undefined;\n\treadonly login: string | undefined;\n\treadonly reason: CredentialRefusal | undefined;\n\treadonly hashPrefix: string | undefined;\n\treadonly expectedVersion: number | undefined;\n\treadonly actualVersion: number | undefined;\n\treadonly issues: readonly Issue[] | undefined;\n\treadonly minLength: number | undefined;\n\treadonly operation: string | undefined;\n\treadonly slot: 'users' | 'sessions' | 'tokens' | 'relations' | undefined;\n\treadonly permission: string | undefined;\n\treadonly maxDepth: number | undefined;\n\n\tconstructor(message: string, options?: JanusErrorOptions) {\n\t\tsuper(message, { cause: options?.cause });\n\t\tthis.userId = options?.userId;\n\t\tthis.userType = options?.userType;\n\t\tthis.login = options?.login;\n\t\tthis.reason = options?.reason;\n\t\tthis.hashPrefix = options?.hashPrefix;\n\t\tthis.expectedVersion = options?.expectedVersion;\n\t\tthis.actualVersion = options?.actualVersion;\n\t\tthis.issues = options?.issues;\n\t\tthis.minLength = options?.minLength;\n\t\tthis.operation = options?.operation;\n\t\tthis.slot = options?.slot;\n\t\tthis.permission = options?.permission;\n\t\tthis.maxDepth = options?.maxDepth;\n\t}\n}\n\n/**\n * The store could not answer.\n *\n * **This is the class an adapter throws**, and it is exported for that reason:\n * an adapter defines no error class of its own, so `instanceof` holds across\n * the two packages. Any other throw from a store is treated as a failure too —\n * throwing this one is how an adapter says so precisely, and sets `cause`.\n */\nexport class StoreFailure extends JanusError {\n\toverride name = 'StoreFailure';\n\toverride readonly code = 'STORE_FAILED' as const;\n}\n\n/**\n * A uniqueness or a version constraint the store refused.\n *\n * Also thrown by an adapter, and also for the `instanceof` reason. `on` says\n * which constraint, because the two are answered differently: a login\n * collision is the caller's to fix, a version conflict is a retry.\n */\nexport class StoreConflict extends JanusError {\n\toverride name = 'StoreConflict';\n\toverride readonly code: JanusErrorCode;\n\treadonly on: 'login' | 'version';\n\n\tconstructor(\n\t\ton: 'login' | 'version',\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.on = on;\n\t\tthis.code = on === 'login' ? 'LOGIN_TAKEN' : 'VERSION_CONFLICT';\n\t}\n}\n\n/** There is no such record, and the store said so. */\nexport class NotFoundError extends JanusError {\n\toverride name = 'NotFoundError';\n\toverride readonly code = 'NOT_FOUND' as const;\n}\n\n/** The fields failed the user type's schema. */\nexport class UserInvalidError extends JanusError {\n\toverride name = 'UserInvalidError';\n\toverride readonly code = 'USER_INVALID' as const;\n}\n\n/** A password too short, credentials that do not match, or a hash format nobody reads. */\nexport class CredentialError extends JanusError {\n\toverride name = 'CredentialError';\n\toverride readonly code: JanusErrorCode;\n\n\tconstructor(\n\t\tcode: Extract<\n\t\t\tJanusErrorCode,\n\t\t\t'PASSWORD_TOO_SHORT' | 'CREDENTIALS_INVALID' | 'HASH_UNSUPPORTED'\n\t\t>,\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t}\n}\n\n/** The user is inactive, and the password given was the right one. */\nexport class UserInactiveError extends JanusError {\n\toverride name = 'UserInactiveError';\n\toverride readonly code = 'USER_INACTIVE' as const;\n}\n\n/** A one-time token that is unknown, already spent, lapsed, or sent to an e-mail the user no longer has. */\nexport class TokenError extends JanusError {\n\toverride name = 'TokenError';\n\toverride readonly code: JanusErrorCode;\n\n\tconstructor(\n\t\tcode: Extract<\n\t\t\tJanusErrorCode,\n\t\t\t'TOKEN_UNKNOWN' | 'TOKEN_SPENT' | 'TOKEN_EXPIRED' | 'TOKEN_STALE'\n\t\t>,\n\t\tmessage: string,\n\t\toptions?: JanusErrorOptions,\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t}\n}\n\n/** A cursor this store did not mint, or one for another ordering. */\nexport class InvalidCursorError extends JanusError {\n\toverride name = 'InvalidCursorError';\n\toverride readonly code = 'INVALID_CURSOR' as const;\n}\n\n/** The wired store does not implement the optional capability asked for. */\nexport class UnsupportedError extends JanusError {\n\toverride name = 'UnsupportedError';\n\toverride readonly code = 'UNSUPPORTED' as const;\n}\n\n/** A permission check walked deeper than `maxDepth` without an answer. */\nexport class PermissionDepthError extends JanusError {\n\toverride name = 'PermissionDepthError';\n\toverride readonly code = 'PERMISSION_DEPTH' as const;\n}\n",
|
|
6
6
|
"/**\n * The id every decision in this package hangs on: a user's `id`, as `janus()`\n * mints it.\n *\n * In Ory, the equality between a Kratos identity id and Keto's `subject_id` is\n * a comment and a convention, restated in three repositories and enforced\n * nowhere. Here a user **is** a subject — see {@link subjectOf} — and that\n * shared vocabulary is the whole reason users and permissions are one package\n * rather than two: two packages would make this contract implicit, and an\n * implicit contract between independently versioned packages breaks silently.\n *\n * Opaque: compared with `===`, carried in a URL, logged, never parsed.\n */\nexport type SubjectId = string;\n\n/**\n * Something a permission is about or held by — a user, a record, a team — by\n * its type and its id.\n *\n * **Typed, unlike Keto's subjects.** One application has patients and staff,\n * and an object can hold a relation too (a record's team), so an id alone does\n * not say who. `type` is a user type of `janus()` or an object type of the\n * permission model, and the same word as a user's own `type`.\n */\nexport interface Entity {\n\treadonly type: string;\n\treadonly id: string;\n}\n\n/**\n * Everyone who holds `relation` on an entity — `team:t1#member` — which is how\n * a permission is inherited rather than granted.\n *\n * A tuple whose subject is a subject set lets a whole group be given access\n * with one write: granting a record's `viewer` to `team:t1#member` means every\n * member of that team can view it, and no member holds a tuple on the record.\n */\nexport interface SubjectSet extends Entity {\n\treadonly relation: string;\n}\n\n/**\n * Who a tuple is about: one entity, or everyone who holds a relation on one.\n * {@link isSubjectSet} tells them apart.\n */\nexport type Subject = Entity | SubjectSet;\n\n/**\n * A relation tuple: `subject` holds `relation` on `object`.\n *\n * `relation` is Zanzibar's word, and a tuple is Zanzibar's unit. What is not\n * carried over is Keto's `namespace`/`object` pair: here the object is an\n * {@link Entity}, with the `type` and `id` a user already has.\n */\nexport interface RelationTuple {\n\treadonly object: Entity;\n\treadonly relation: string;\n\treadonly subject: Subject;\n}\n\n/**\n * Whether this subject is a set rather than one entity. Narrows.\n *\n * It reads the shape, so pass a user through {@link subjectOf} first: a user\n * whose fields include a string `relation` would otherwise read as a set.\n */\nexport function isSubjectSet(subject: Subject): subject is SubjectSet {\n\treturn typeof (subject as { relation?: unknown }).relation === 'string';\n}\n\n/**\n * The subject a user is: their `type` and `id`, and nothing else.\n *\n * One line, and the entire join between the two sides of the package. It\n * takes the narrowest shape it reads, so the permissions side never has to\n * know what a user is, and it copies those two fields so none of the user's\n * own fields ever reaches a tuple.\n */\nexport function subjectOf(user: {\n\treadonly type: string;\n\treadonly id: SubjectId;\n}): Entity {\n\treturn { type: user.type, id: user.id };\n}\n",
|
|
7
7
|
"import {\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype Subject,\n} from './subject';\n\n/**\n * Zanzibar's notation, with typed subjects:\n *\n * ```\n * record:r1#viewer@staff:u1\n * record:r1#viewer@team:t1#member\n * ```\n *\n * It exists for **messages, logs and documentation**, not as a wire format:\n * nothing in this package parses a tuple off the network, and a tuple that\n * crosses a boundary crosses it as an object. Keeping the notation is worth it\n * because every piece of Zanzibar writing uses it, so an error message in it is\n * an error message a reader has already learned to read.\n *\n * A type holds no `:`, and no part holds `@`, `#` or a parenthesis, so a\n * subject set needs no parentheses: after the `@`, a `#` can only begin its\n * relation. {@link parseTuple} refuses a string where that would not hold,\n * rather than reading it two ways.\n */\nexport function formatEntity(entity: Entity): string {\n\treturn `${entity.type}:${entity.id}`;\n}\n\n/** One subject: `staff:u1`, or `team:t1#member`. */\nexport function formatSubject(subject: Subject): string {\n\treturn isSubjectSet(subject)\n\t\t? `${formatEntity(subject)}#${subject.relation}`\n\t\t: formatEntity(subject);\n}\n\n/** One relation tuple, in the notation above. */\nexport function formatTuple(tuple: RelationTuple): string {\n\treturn `${formatEntity(tuple.object)}#${tuple.relation}@${formatSubject(tuple.subject)}`;\n}\n\nconst TYPE = '([^:@#()]+)';\nconst ID = '([^@#()]+)';\nconst RELATION = '([^:@#()]+)';\n\nconst TUPLE = new RegExp(`^${TYPE}:${ID}#${RELATION}@(.+)$`);\nconst SUBJECT = new RegExp(`^${TYPE}:${ID}(?:#${RELATION})?$`);\n\n/**\n * Reads a tuple back. Refuses, with a `TypeError`, a string that is not one —\n * a permission question built from a malformed string is a question whose\n * answer means nothing, and no request is behind it: the string came from a\n * developer's own code.\n */\nexport function parseTuple(text: string): RelationTuple {\n\tconst match = TUPLE.exec(text);\n\n\tif (!match) {\n\t\tthrow new TypeError(\n\t\t\t`parseTuple: \"${text}\" is not a relation tuple; expected type:id#relation@subject`,\n\t\t);\n\t}\n\n\tconst [, type, id, relation, subject] = match as unknown as [\n\t\tstring,\n\t\tstring,\n\t\tstring,\n\t\tstring,\n\t\tstring,\n\t];\n\n\treturn { object: { type, id }, relation, subject: parseSubject(subject) };\n}\n\n/** Reads one subject back: `staff:u1`, or `team:t1#member`. */\nexport function parseSubject(text: string): Subject {\n\tconst match = SUBJECT.exec(text);\n\n\tif (!match) {\n\t\tthrow new TypeError(\n\t\t\t`parseSubject: \"${text}\" is not a subject; expected type:id, or type:id#relation for a subject set`,\n\t\t);\n\t}\n\n\tconst [, type, id, relation] = match as unknown as [\n\t\tstring,\n\t\tstring,\n\t\tstring,\n\t\tstring | undefined,\n\t];\n\n\treturn relation === undefined ? { type, id } : { type, id, relation };\n}\n"
|
|
8
8
|
],
|
|
9
|
-
"mappings": ";
|
|
9
|
+
"mappings": "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",
|
|
10
10
|
"debugId": "B82DDDDA4EE307BC64756E2164756E21",
|
|
11
11
|
"names": []
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/sessions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/sessions.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EA4QvD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/users.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,cAAc,EAAE,SAAS,eAAe,EAscpD,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
mintId2,
|
|
3
3
|
createMemoryStores2
|
|
4
|
-
} from "../chunks/index-
|
|
4
|
+
} from "../chunks/index-0xarpm7z.js";
|
|
5
5
|
import {
|
|
6
6
|
JanusError2,
|
|
7
7
|
StoreFailure2,
|
|
@@ -292,16 +292,23 @@ var sessionStoreCases = [
|
|
|
292
292
|
name: "deletes every session of one user — standing, revoked or lapsed — and counts them",
|
|
293
293
|
async run({ stores }) {
|
|
294
294
|
const userId = mintId2();
|
|
295
|
+
const lapsed = sessionRecord({
|
|
296
|
+
userId,
|
|
297
|
+
expiresAt: at("2026-01-02T00:00:00.000Z")
|
|
298
|
+
});
|
|
295
299
|
const records = [
|
|
296
300
|
sessionRecord({ userId }),
|
|
297
301
|
sessionRecord({ userId, revokedAt: at("2026-01-02T00:00:00.000Z") }),
|
|
298
|
-
|
|
302
|
+
lapsed
|
|
299
303
|
];
|
|
300
304
|
const stranger = sessionRecord();
|
|
301
305
|
for (const record of [...records, stranger]) {
|
|
302
306
|
await stores.sessions.insertSession(record);
|
|
303
307
|
}
|
|
304
|
-
|
|
308
|
+
const lapsedHeld = await stores.sessions.findSessionByTokenHash(lapsed.tokenHash) !== null;
|
|
309
|
+
const expected = lapsedHeld ? 3 : 2;
|
|
310
|
+
const deleted = await stores.sessions.deleteUserSessions(userId);
|
|
311
|
+
ok(deleted === expected, `deleteUserSessions: how many it deleted, revoked and lapsed ones included — ${String(expected)}, as the lapsed one is ${lapsedHeld ? "still held" : "already expired"}; got ${String(deleted)}`);
|
|
305
312
|
for (const record of records) {
|
|
306
313
|
isNull(await stores.sessions.findSessionByTokenHash(record.tokenHash), "findSessionByTokenHash after deleteUserSessions");
|
|
307
314
|
}
|
|
@@ -507,6 +514,7 @@ var userStoreCases = [
|
|
|
507
514
|
isOurs(error, StoreConflict2, "insertUser should refuse a taken login with StoreConflict");
|
|
508
515
|
equal(error.code, "LOGIN_TAKEN", "insertUser: the conflict code");
|
|
509
516
|
equal(error.login, "contested@example.test", "insertUser: the conflict should name the login");
|
|
517
|
+
ok(!error.message.includes("contested@example.test"), `insertUser: the conflict's message should not quote the login — a message never carries a value; got ${JSON.stringify(error.message)}`);
|
|
510
518
|
}
|
|
511
519
|
let stored = 0;
|
|
512
520
|
for (const record of records) {
|
|
@@ -1109,5 +1117,5 @@ export {
|
|
|
1109
1117
|
userStoreCases
|
|
1110
1118
|
};
|
|
1111
1119
|
|
|
1112
|
-
//# debugId=
|
|
1120
|
+
//# debugId=21560593CE33B02F64756E2164756E21
|
|
1113
1121
|
//# sourceMappingURL=index.js.map
|