@nxgt/janus 0.1.0 → 0.1.1

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.
Files changed (38) hide show
  1. package/README.md +113 -24
  2. package/dist/auth/config.d.ts +3 -3
  3. package/dist/auth/config.d.ts.map +1 -1
  4. package/dist/auth/context.d.ts +1 -1
  5. package/dist/auth/context.d.ts.map +1 -1
  6. package/dist/auth/janus.d.ts +2 -2
  7. package/dist/auth/janus.d.ts.map +1 -1
  8. package/dist/auth/outage.d.ts +11 -17
  9. package/dist/auth/outage.d.ts.map +1 -1
  10. package/dist/auth/port/types.d.ts +6 -6
  11. package/dist/chunks/index-6p56fpbe.js.map +2 -2
  12. package/dist/chunks/{index-6dytvy3h.js → index-hm4v76kd.js} +7 -29
  13. package/dist/chunks/index-hm4v76kd.js.map +11 -0
  14. package/dist/conformance/assert.d.ts +1 -1
  15. package/dist/conformance/index.js +3 -3
  16. package/dist/conformance/index.js.map +5 -5
  17. package/dist/errors/janus-error.d.ts +1 -1
  18. package/dist/index.d.ts +9 -8
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +33 -12
  21. package/dist/index.js.map +7 -6
  22. package/dist/permissions/index.js +3 -3
  23. package/dist/permissions/index.js.map +4 -4
  24. package/dist/stores/guard.d.ts +24 -0
  25. package/dist/stores/guard.d.ts.map +1 -0
  26. package/dist/subjects/subject.d.ts +2 -2
  27. package/docs/README.md +19 -3
  28. package/docs/guide/adapters.md +4 -4
  29. package/docs/guide/errors.md +3 -3
  30. package/docs/guide/passwords.md +1 -1
  31. package/docs/guide/permissions.md +7 -2
  32. package/docs/guide/sessions.md +2 -2
  33. package/docs/guide/users.md +8 -7
  34. package/docs/guide/vocabulary.md +70 -3
  35. package/docs/roadmap.md +4 -4
  36. package/docs/troubleshooting.md +9 -9
  37. package/package.json +2 -2
  38. package/dist/chunks/index-6dytvy3h.js.map +0 -11
@@ -1,7 +1,7 @@
1
1
  # The shared vocabulary
2
2
 
3
- This page is for the small pieces `@nxgt/janus` exports beside `janus()` and
4
- the permissions, because both use them: subjects and the tuple notation, ids,
3
+ This page defines the words the documentation uses, then the small pieces
4
+ `@nxgt/janus` exports for both sides: subjects and the tuple notation, ids,
5
5
  pagination, durations and clocks. All of it is imported from `@nxgt/janus`.
6
6
 
7
7
  ```ts
@@ -18,6 +18,73 @@ formatTuple({
18
18
  parseDuration('8h', 'session.lifespan'); // 28800000
19
19
  ```
20
20
 
21
+ ## Words
22
+
23
+ One word per idea, the same in the README, these guides, the error messages
24
+ and the code. The **Not** column lists the words it replaces, so a search for
25
+ either finds this row.
26
+
27
+ ### The two sides
28
+
29
+ | Word | Means | Not |
30
+ | --- | --- | --- |
31
+ | **side** | One of the two things Janus does, each usable alone: identities or permissions | "half", "module" |
32
+ | **identities** | The side that answers *who is this?* — users, logins, passwords, sessions, one-time tokens. `janus()`, from `@nxgt/janus` | "auth", "authentication", in prose; `auth` is only the variable name in examples |
33
+ | **permissions** | The side that answers *may they?* — a model, the tuples stored against it, `can`, `list`, `grant`, `revoke`. `@nxgt/janus/permissions` | "authorization", "access control", "ACL" |
34
+
35
+ ### Identities
36
+
37
+ | Word | Means | Not |
38
+ | --- | --- | --- |
39
+ | **user** | One stored person or machine, of one user type | "account" — kept only in *account takeover* and *account enumeration*, the names of those attacks |
40
+ | **user type** | A kind of user with its own schema and login: `staff`, `patient`. Wired to permissions, its name is a subject type | "role": a role is a relation in the model |
41
+ | **schema** | A user type's Standard Schema: the fields a user carries | the model |
42
+ | **login** | The value a user signs in with: an e-mail, a username. **sign in** is the verb, **sign-in** the noun | "identifier" |
43
+ | **credential** | What a caller presents to prove who they are: a login and a password at sign-in (`CredentialError`, `CREDENTIALS_INVALID`), or a token on a request — always written *session credential* | |
44
+ | **password policy** | The rules a new password must meet: `minLength`, `normalize` | the model |
45
+ | **session** | A signed-in period, carried by a **session token** | |
46
+ | **one-time token** | A single-use token sent by e-mail, for verification or a password reset | "code" — `code` is an error's code |
47
+ | **token** | Never alone in prose: a *session token* or a *one-time token*. The `tokens` store and the `TOKEN_*` codes are one-time tokens only | |
48
+ | **e-mail flow** | `verifyEmail` or `resetPassword`: send a one-time token, then confirm it | |
49
+
50
+ ### Permissions
51
+
52
+ | Word | Means | Not |
53
+ | --- | --- | --- |
54
+ | **model** | What `defineModel()` answers: the subject types, the object types, their relations and permissions | "schema", "policy" |
55
+ | **subject type** | A name listed in `subjects`: `auth.types` when wired to `janus()`, your own names otherwise | |
56
+ | **object** | What a permission is about: `{ type: 'document', id }` | "resource" |
57
+ | **subject** | Who a permission is about: a user, an object, or a subject set | "principal", "actor" |
58
+ | **entity** | `{ type, id }`: a user or an object — a subject that is not a set (`Entity`, `deleteEntity`) | |
59
+ | **subject set** | Everyone holding one relation on one object: `team:t1#member` | "group" — a group is an object with a `member` relation |
60
+ | **relation** | A named link, stored as tuples or read from a field (`fromField`) | |
61
+ | **holder** | What a relation admits: `'staff'`, or the subject set `'team#member'` | |
62
+ | **permission** | A name computed from relations and other permissions by its rules | |
63
+ | **rule** | One entry of a permission: a relation, another permission, an arrow, or one of them under a condition | |
64
+ | **arrow** | A rule that follows a relation to another object's permission: `'team->view'` | |
65
+ | **condition** | A predicate on a rule, written with `when`, run on the `ctx` passed to `can()` and `list()` | |
66
+ | **tuple** | One stored fact — object, relation, subject: `document:d1#viewer@user:u1` | "grant", "ACL entry" |
67
+
68
+ ### Stores
69
+
70
+ | Word | Means | Not |
71
+ | --- | --- | --- |
72
+ | **store** | Where a side keeps its data, behind a port. The **identity stores** are `users`, `sessions` and `tokens`; the **relation store** holds the tuples | "database", "repository" |
73
+ | **port** | The interface a store implements: `JanusStores` for the identity stores — named after the package, not the side — and `RelationStore` | "driver" |
74
+ | **adapter** | A package implementing the ports for one database: `@nxgt/janus-mongo` | "plugin", "connector" |
75
+
76
+ ### Answers
77
+
78
+ The words of [the one rule](../../README.md#the-one-rule): each is a different
79
+ answer, and none stands in for another.
80
+
81
+ | Word | Means |
82
+ | --- | --- |
83
+ | **absence** | Nothing there: `null`, `[]`, an empty page, `0` |
84
+ | **denial** | A permission not held: `false` |
85
+ | **failure** | A store that could not answer — an **outage**. It throws `STORE_FAILED`: never an absence, never a denial |
86
+ | **refusal** | Anything thrown: a `JanusError` at call time — a taken login, a wrong password, a failure — or a `TypeError` at wiring time, from how the library was called. See [Two kinds of refusal](errors.md#two-kinds-of-refusal) |
87
+
21
88
  ## Subjects and the tuple notation
22
89
 
23
90
  ```ts
@@ -30,7 +97,7 @@ interface RelationTuple { readonly object: Entity; readonly relation: string; re
30
97
  **Subjects are typed**: `{ type, id }` for one entity, `{ type, id, relation }`
31
98
  for a subject set — every `member` of `team:t1`. `type` is the same word as a
32
99
  user's own, so a user id and a subject id are the same thing, and
33
- `subjectOf(user)` is the one-line join between the two halves of the package.
100
+ `subjectOf(user)` is the one-line join between the two sides of the package.
34
101
  It copies `type` and `id` only, so none of the user's own fields ever reaches
35
102
  a tuple.
36
103
 
package/docs/roadmap.md CHANGED
@@ -13,7 +13,7 @@ Nothing yet.
13
13
 
14
14
  ## Later
15
15
 
16
- - **More official adapters** — the store port is cut where atomicity is not
16
+ - **More official adapters** — the ports are cut where atomicity is not
17
17
  required, so users, sessions and permission tuples can each live in the
18
18
  database that suits them. MongoDB is the first adapter
19
19
  ([`@nxgt/janus-mongo`](https://www.npmjs.com/package/@nxgt/janus-mongo)).
@@ -28,8 +28,8 @@ Nothing yet.
28
28
  bundler do. Rewriting the declarations for `nodenext` was tried and reverted:
29
29
  it breaks the same contract one step later. Use `"moduleResolution":
30
30
  "bundler"`.
31
- - **A Kratos-shaped surface** — no `identity.traits`, no identifier derived
32
- from a schema annotation. A user is your schema's fields at the top level,
31
+ - **A Kratos-shaped surface** — no `identity.traits`, no login derived from a
32
+ schema annotation (Kratos's `identifier`). A user is your schema's fields at the top level,
33
33
  and the flows are calls (`signUp`, `signIn`, `authenticate`).
34
34
  - **`snake_case` keys** — every key, option and record field is `camelCase`,
35
35
  and a lint rule holds it. Error codes are `SCREAMING_SNAKE` because they are
@@ -88,6 +88,6 @@ The first public release, v0.1.
88
88
  — v0.1
89
89
  - **The conformance suite** — `@nxgt/janus/conformance`, the suite an adapter
90
90
  runs, outages included. — v0.1
91
- - **The store port and its in-memory reference** — `JanusStores` and
91
+ - **The identity stores' port and its in-memory reference** — `JanusStores` and
92
92
  `createMemoryStores()`, for your tests and as the model for an adapter.
93
93
  — v0.1
@@ -127,14 +127,14 @@ Every entry here is a `TypeError` thrown by `janus(...)` itself, before any
127
127
  request. With several user types, the option is prefixed by the type:
128
128
  `janus: users.staff: password.login …`.
129
129
 
130
- ### `janus: pass either user (one kind of user) or users (several kinds), and exactly one of them`
130
+ ### `janus: pass either user (one user type) or users (several user types), and exactly one of them`
131
131
 
132
132
  **When:** `janus({...})`, with both `user` and `users`, or neither.
133
- **Why:** `user` is the shorthand for one kind of user; `users` declares several, each with its own schema and options. The two cannot be mixed.
133
+ **Why:** `user` is the shorthand for one user type; `users` declares several, each with its own schema and options. The two cannot be mixed.
134
134
  **Fix:**
135
135
 
136
136
  ```ts
137
- // One kind of user
137
+ // One user type
138
138
  janus({ user: User, password: { login: 'email' }, store, hasher });
139
139
 
140
140
  // Several
@@ -294,7 +294,7 @@ Also `janus: cookie.name must be a cookie-name token — letters, digits and !#$
294
294
 
295
295
  **When:** any call that reaches the store — `authenticate`, `signIn`, `get`, `can` — while the database is down, times out, or the adapter throws.
296
296
  **Why:** a store that cannot answer throws; it never answers `null`. The driver's own error is on `error.cause` — not in the message, because a driver message can hold a connection string, and a connection string holds a password.
297
- **Fix:** answer **503**, and log `cause`. Never map it to 401, 404, `null` or `false`: that turns an outage into a silent lockout, where everybody with an account is told they do not have one.
297
+ **Fix:** answer **503**, and log `cause`. Never map it to 401, 404, `null` or `false`: that turns an outage into a silent lockout, where every user is told they do not exist.
298
298
 
299
299
  ```ts
300
300
  import { JanusError } from '@nxgt/janus';
@@ -333,7 +333,7 @@ const user = await auth.find(id); // null when there is nobody
333
333
  `StoreConflict` with `on: 'login'`, carrying `login` and `userType`.
334
334
 
335
335
  **When:** `signUp`, `create`, or an `update` that changes the login, when another user of **the same type** holds it after normalisation (`Ada@Example.com` and `ada@example.com` collide by default).
336
- **Why:** the store's unique constraint refused the write. A login is unique per user type: one e-mail may hold a patient account and a staff account.
336
+ **Why:** the store's unique constraint refused the write. A login is unique per user type: one e-mail may hold a patient user and a staff user.
337
337
  **Fix:** answer 409. If two concurrent sign-ups with one login both succeed, the unique index is missing — with `@nxgt/janus-mongo`, run `syncMongoStores(db)`.
338
338
 
339
339
  ### `VERSION_CONFLICT` — `<call>: expected version <n>, found <m>`
@@ -378,7 +378,7 @@ Also `changePassword: the current password does not match`.
378
378
  **Fix:** answer 401 with the same body whatever the reason:
379
379
 
380
380
  ```ts
381
- // Never: { reason: error.reason } — `unknownLogin` tells an attacker which accounts exist.
381
+ // Never: { reason: error.reason } — `unknownLogin` tells an attacker which users exist.
382
382
  return new Response('Wrong e-mail or password', { status: 401 });
383
383
  ```
384
384
 
@@ -401,7 +401,7 @@ janus({ ..., hasher: scryptHasher(), verifiers: [bcryptVerifier] }); // a Passwo
401
401
  ### `USER_INACTIVE` — `<call>: the user is inactive`
402
402
 
403
403
  **When:** `signIn`, with the **right** password, for a user set inactive.
404
- **Why:** an inactive user keeps their record and password, and every sign-in is refused. It is checked after the password, so only somebody who knows the password learns the account is inactive.
404
+ **Why:** an inactive user keeps their record and password, and every sign-in is refused. It is checked after the password, so only somebody who knows the password learns the user is inactive.
405
405
  **Fix:** answer 403, or reactivate: `await auth.setActive(user, true)`.
406
406
 
407
407
  ### `TOKEN_UNKNOWN`, `TOKEN_SPENT`, `TOKEN_EXPIRED`
@@ -469,7 +469,7 @@ do {
469
469
  Also `list: listing <type>#<permission> crossed more than <n> relations without an answer`. `PermissionDepthError`, carrying `permission` and `maxDepth`.
470
470
 
471
471
  **When:** `access.can(...)` or `access.list(...)`.
472
- **Why:** the walk crossed more than `maxDepth` relations (25 by default) and found no grant. It is not a denial: a check that stopped half-way decided nothing. A cycle in the data — a team member of itself — is cut silently and never causes this.
472
+ **Why:** the walk crossed more than `maxDepth` relations (25 by default) and reached no decision. It is not a denial: a check that stopped half-way decided nothing. A cycle in the data — a team member of itself — is cut silently and never causes this.
473
473
  **Fix:** answer 500 and look at the model or the data: a very deep hierarchy, or a chain of subject sets. If the depth is genuine, raise it:
474
474
 
475
475
  ```ts
@@ -579,7 +579,7 @@ most common:
579
579
 
580
580
  | Message | Fix |
581
581
  | --- | --- |
582
- | `defineModel: subjects must be an array of user types — pass auth.types from janus()` | `defineModel({ subjects: auth.types, types })`. |
582
+ | `defineModel: subjects must be an array of subject type names — auth.types from janus(), or your own` | `defineModel({ subjects: auth.types, types })` with `janus()`, or your own names alone: `subjects: ['user']`. |
583
583
  | `defineModel: types declares no object type` | Declare at least one type under `types`. |
584
584
  | `defineModel: the object type "<name>" must be a camelCase name — letters and digits, starting with a lowercase letter` | Also for relation and permission names. |
585
585
  | `defineModel: "<name>" names a user type and an object type; a subject of type "<name>" would be ambiguous` | Rename the object type. |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nxgt/janus",
3
- "version": "0.1.0",
4
- "description": "Embeddable, type-safe authentication and permissions: bring your own database",
3
+ "version": "0.1.1",
4
+ "description": "Embeddable, type-safe identities and permissions: bring your own database",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -1,11 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/pagination/cursor-page.ts", "../src/auth/outage.ts"],
4
- "sourcesContent": [
5
- "import { InvalidCursorError } from '../errors/janus-error';\n\n/**\n * One page of results, and where the next one starts.\n *\n * `nextCursor` is `null` on the last page and a string on every other, so\n * `while (cursor)` is the loop and there is no separate \"done\" flag to forget.\n *\n * **There is no `total`**, and that is deliberate — the same choice\n * `@nxgt/mongo`'s cursor page makes, for the same reason: an exact count over a\n * large table is a second scan, and no adapter should be made to promise one.\n * An application that needs a count asks its own store for one, where it can\n * decide what the count is allowed to cost.\n *\n * This type is **redefined here rather than imported** from `@nxgt/mongo`. That\n * is deliberate duplication, recorded in AGENTS.md: a port that every adapter\n * implements cannot make one database library a dependency of the contract.\n */\nexport interface CursorPage<T> {\n\treadonly items: readonly T[];\n\treadonly nextCursor: string | null;\n}\n\n/** How many a page holds when the caller did not say. */\nexport const DEFAULT_PAGE_SIZE = 20;\n\n/**\n * The most a page may hold, whatever the caller asked for.\n *\n * Bounded in the core rather than left to each adapter, so a caller cannot ask\n * one store for a million rows and be refused by another. An application that\n * wants everything pages for it.\n */\nexport const MAX_PAGE_SIZE = 100;\n\n/**\n * The limit a store will be given, from the limit a caller asked for.\n *\n * `where` names the call the consumer wrote — `listIdentities`, not an internal\n * function — because several calls in this package take a `limit` and a message\n * that does not say which one leaves the reader to guess. That naming rule is\n * `nxgt-data`'s, and it is the reason its errors read the way they do.\n *\n * A bare `TypeError`: a limit is written in the application's own code, so this\n * cannot come from a request.\n */\nexport function pageLimit(limit: number | undefined, where: string): number {\n\tif (limit === undefined) return DEFAULT_PAGE_SIZE;\n\n\tif (!Number.isInteger(limit) || limit < 1) {\n\t\tthrow new TypeError(\n\t\t\t`${where}: limit must be an integer of at least 1, or absent`,\n\t\t);\n\t}\n\n\treturn Math.min(limit, MAX_PAGE_SIZE);\n}\n\n/**\n * Refuses a cursor this store did not mint.\n *\n * Exported for adapter authors: a cursor from another store, another ordering\n * or another version of the adapter must be refused, **never treated as an\n * absent cursor**. A silent fall back to the first page makes a caller paging a\n * list loop for ever, and the loop looks like a slow query rather than a bug.\n */\nexport function invalidCursor(\n\twhere: string,\n\tcursor: string,\n): InvalidCursorError {\n\t// The cursor's own bytes are not in the message: it is opaque, it may be\n\t// long, and printing it tells the reader nothing they can act on. Its length\n\t// is enough to tell \"truncated in transit\" from \"written by another store\".\n\treturn new InvalidCursorError(\n\t\t`${where}: this cursor was not minted by this store, or was minted for another ordering (${cursor.length} characters)`,\n\t);\n}\n",
6
- "/**\n * **The one place a store call is caught, and the one place a `null` becomes an\n * error.**\n *\n * The transposition of `call.ts` in `nxgt-ory`'s SDK — \"the only place a call\n * is unwrapped\" — into a core with no status codes. The invariant this whole\n * package is built around is checked by reading this file:\n *\n * > An absence is `null`. A failure throws.\n *\n * `outage.spec.ts` reads every other file of `src/auth/` and fails if a\n * `catch` appears in one, because the failure this design exists to prevent is\n * a single careless `catch { return null }`. There are two `catch`es below: the\n * guard's always rethrows, and {@link unlessVersionConflict}'s absorbs one\n * named conflict and rethrows everything else. The spec holds both to that.\n */\n\nimport {\n\ttype JanusError,\n\tJanusError as JanusErrorClass,\n\tStoreConflict,\n\tStoreFailure,\n} from '../errors/janus-error';\nimport type { RelationStore } from '../permissions/port/types';\nimport type { JanusStores } from './port/types';\n\ntype Slot = keyof JanusStores | 'relations';\n\n/**\n * The methods whose answer is legitimately nothing: `undefined` from them is\n * not a forgotten `return`. Every other method answers a value, `null`,\n * `false`, `0` or a page — **never `undefined`**.\n */\nconst ANSWERS_NOTHING = new Set(['insertSession', 'insertToken', 'write']);\n\n/**\n * The stores, with every method guarded.\n *\n * - A `JanusError` the adapter threw — `StoreFailure`, `StoreConflict`,\n * `NotFoundError` — passes through untouched, so `instanceof` still holds.\n * - **Anything else it threw is a failure**: a driver error, a `TypeError` from\n * a bug in the adapter, a string. It becomes `StoreFailure` with the original\n * as `cause`, naming the slot and the method. It is never an absence.\n * - A method that answers `undefined` where the port says `null` is a store\n * that forgot to answer, and becomes `StoreFailure` rather than \"not found\".\n * Rule 2 of the port, enforced at run time for the JavaScript adapter the\n * compiler never saw.\n *\n * The optional `deleteExpiredSessions` is guarded when present and left absent\n * when absent, so capability detection still reads the truth.\n */\nexport function guardStores(stores: JanusStores): JanusStores {\n\treturn {\n\t\tusers: guardSlot('users', stores.users),\n\t\tsessions: guardSlot('sessions', stores.sessions),\n\t\ttokens: guardSlot('tokens', stores.tokens),\n\t};\n}\n\n/**\n * The relation store, guarded the same way: the permission engine's only way\n * to a store, so a failure there is `STORE_FAILED` and never a denial.\n */\nexport function guardRelations(store: RelationStore): RelationStore {\n\treturn guardSlot('relations', store);\n}\n\nfunction guardSlot<S extends object>(slot: Slot, store: S): S {\n\tconst guarded: Record<string, unknown> = {};\n\n\tfor (const method of methodsOf(store)) {\n\t\tconst original = (store as Record<string, unknown>)[method];\n\t\tif (typeof original !== 'function') continue;\n\n\t\tguarded[method] = async (...args: unknown[]) => {\n\t\t\tlet answer: unknown;\n\n\t\t\ttry {\n\t\t\t\tanswer = await original.apply(store, args);\n\t\t\t} catch (error) {\n\t\t\t\t// Rethrown, always. The only question is under which class.\n\t\t\t\tthrow asFailure(error, slot, method);\n\t\t\t}\n\n\t\t\tif (answer === undefined && !ANSWERS_NOTHING.has(method)) {\n\t\t\t\tthrow new StoreFailure(\n\t\t\t\t\t`${slot}.${method} answered undefined: an absence is null, so this store forgot to answer`,\n\t\t\t\t\t{ slot, operation: method },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn answer;\n\t\t};\n\t}\n\n\treturn guarded as S;\n}\n\n/** Own and prototype methods, so a class-based store is guarded like a literal. */\nfunction methodsOf(store: object): string[] {\n\tconst names = new Set<string>();\n\n\tfor (\n\t\tlet proto: object | null = store;\n\t\tproto !== null && proto !== Object.prototype;\n\t\tproto = Object.getPrototypeOf(proto)\n\t) {\n\t\tfor (const name of Object.getOwnPropertyNames(proto)) {\n\t\t\tif (name !== 'constructor') names.add(name);\n\t\t}\n\t}\n\n\treturn [...names];\n}\n\nfunction asFailure(error: unknown, slot: Slot, method: string): JanusError {\n\tif (error instanceof JanusErrorClass) return error;\n\n\t// The message names where, never what: a driver's message may hold a\n\t// connection string, and a connection string holds a password. It is kept\n\t// as `cause`, for the operator's logs.\n\treturn new StoreFailure(`${slot}.${method}: the store could not answer`, {\n\t\tslot,\n\t\toperation: method,\n\t\tcause: error,\n\t});\n}\n\n/**\n * A value the caller requires, or the refusal an absence deserves.\n *\n * The only place in the core where a `null` from a store turns into an error —\n * `get` becoming `NOT_FOUND`. Written as a function so that the conversion is\n * visible at every call site and nowhere else.\n */\nexport function required<T>(value: T | null, absent: () => JanusError): T {\n\tif (value === null) throw absent();\n\treturn value;\n}\n\n/**\n * A write that may lose a race, and is allowed to: its answer, or `null` when\n * the record's version moved under it.\n *\n * **Only `StoreConflict('version')` is absorbed.** A failure still throws, a\n * taken login still throws, `NOT_FOUND` still throws: an outage never reads as\n * \"somebody else wrote first\". It exists for writes the caller did not ask for\n * — rewriting a password hash on sign-in — where losing to a concurrent update\n * means only that the next sign-in tries again.\n */\nexport async function unlessVersionConflict<T>(\n\twrite: Promise<T>,\n): Promise<T | null> {\n\ttry {\n\t\treturn await write;\n\t} catch (error) {\n\t\tif (error instanceof StoreConflict && error.on === 'version') return null;\n\t\tthrow error;\n\t}\n}\n"
7
- ],
8
- "mappings": ";;;;;;;;AAwBO,IAAM,qBAAoB;AAS1B,IAAM,iBAAgB;AAatB,SAAS,UAAS,CAAC,OAA2B,OAAuB;AAAA,EAC3E,IAAI,UAAU;AAAA,IAAW,OAAO;AAAA,EAEhC,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAG;AAAA,IAC1C,MAAM,IAAI,UACT,GAAG,0DACJ;AAAA,EACD;AAAA,EAEA,OAAO,KAAK,IAAI,OAAO,cAAa;AAAA;AAW9B,SAAS,cAAa,CAC5B,OACA,QACqB;AAAA,EAIrB,OAAO,IAAI,oBACV,GAAG,wFAAwF,OAAO,oBACnG;AAAA;;;AC1CD,IAAM,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,eAAe,OAAO,CAAC;AAkBlE,SAAS,WAAW,CAAC,QAAkC;AAAA,EAC7D,OAAO;AAAA,IACN,OAAO,UAAU,SAAS,OAAO,KAAK;AAAA,IACtC,UAAU,UAAU,YAAY,OAAO,QAAQ;AAAA,IAC/C,QAAQ,UAAU,UAAU,OAAO,MAAM;AAAA,EAC1C;AAAA;AAOM,SAAS,cAAc,CAAC,OAAqC;AAAA,EACnE,OAAO,UAAU,aAAa,KAAK;AAAA;AAGpC,SAAS,SAA2B,CAAC,MAAY,OAAa;AAAA,EAC7D,MAAM,UAAmC,CAAC;AAAA,EAE1C,WAAW,UAAU,UAAU,KAAK,GAAG;AAAA,IACtC,MAAM,WAAY,MAAkC;AAAA,IACpD,IAAI,OAAO,aAAa;AAAA,MAAY;AAAA,IAEpC,QAAQ,UAAU,UAAU,SAAoB;AAAA,MAC/C,IAAI;AAAA,MAEJ,IAAI;AAAA,QACH,SAAS,MAAM,SAAS,MAAM,OAAO,IAAI;AAAA,QACxC,OAAO,OAAO;AAAA,QAEf,MAAM,UAAU,OAAO,MAAM,MAAM;AAAA;AAAA,MAGpC,IAAI,WAAW,aAAa,CAAC,gBAAgB,IAAI,MAAM,GAAG;AAAA,QACzD,MAAM,IAAI,cACT,GAAG,QAAQ,iFACX,EAAE,MAAM,WAAW,OAAO,CAC3B;AAAA,MACD;AAAA,MAEA,OAAO;AAAA;AAAA,EAET;AAAA,EAEA,OAAO;AAAA;AAIR,SAAS,SAAS,CAAC,OAAyB;AAAA,EAC3C,MAAM,QAAQ,IAAI;AAAA,EAElB,SACK,QAAuB,MAC3B,UAAU,QAAQ,UAAU,OAAO,WACnC,QAAQ,OAAO,eAAe,KAAK,GAClC;AAAA,IACD,WAAW,QAAQ,OAAO,oBAAoB,KAAK,GAAG;AAAA,MACrD,IAAI,SAAS;AAAA,QAAe,MAAM,IAAI,IAAI;AAAA,IAC3C;AAAA,EACD;AAAA,EAEA,OAAO,CAAC,GAAG,KAAK;AAAA;AAGjB,SAAS,SAAS,CAAC,OAAgB,MAAY,QAA4B;AAAA,EAC1E,IAAI,iBAAiB;AAAA,IAAiB,OAAO;AAAA,EAK7C,OAAO,IAAI,cAAa,GAAG,QAAQ,sCAAsC;AAAA,IACxE;AAAA,IACA,WAAW;AAAA,IACX,OAAO;AAAA,EACR,CAAC;AAAA;AAUK,SAAS,QAAW,CAAC,OAAiB,QAA6B;AAAA,EACzE,IAAI,UAAU;AAAA,IAAM,MAAM,OAAO;AAAA,EACjC,OAAO;AAAA;AAaR,eAAsB,qBAAwB,CAC7C,OACoB;AAAA,EACpB,IAAI;AAAA,IACH,OAAO,MAAM;AAAA,IACZ,OAAO,OAAO;AAAA,IACf,IAAI,iBAAiB,kBAAiB,MAAM,OAAO;AAAA,MAAW,OAAO;AAAA,IACrE,MAAM;AAAA;AAAA;",
9
- "debugId": "047FF5FD235ABA3764756E2164756E21",
10
- "names": []
11
- }