@nxgt/janus 0.24.0 → 0.26.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.
Files changed (57) hide show
  1. package/README.md +39 -12
  2. package/dist/auth/config/reserved.d.ts +1 -1
  3. package/dist/auth/config/reserved.d.ts.map +1 -1
  4. package/dist/auth/email-flows/any-type-change-email.d.ts +18 -0
  5. package/dist/auth/email-flows/any-type-change-email.d.ts.map +1 -0
  6. package/dist/auth/email-flows/change-email-token.d.ts +6 -1
  7. package/dist/auth/email-flows/change-email-token.d.ts.map +1 -1
  8. package/dist/auth/email-flows/change-email.d.ts +6 -0
  9. package/dist/auth/email-flows/change-email.d.ts.map +1 -1
  10. package/dist/auth/index.d.ts +1 -1
  11. package/dist/auth/index.d.ts.map +1 -1
  12. package/dist/auth/janus.d.ts.map +1 -1
  13. package/dist/auth/outage.d.ts +2 -2
  14. package/dist/auth/port/assert-stores.d.ts.map +1 -1
  15. package/dist/auth/port/memory/tokens.d.ts.map +1 -1
  16. package/dist/auth/port/types/stores.d.ts +2 -0
  17. package/dist/auth/port/types/stores.d.ts.map +1 -1
  18. package/dist/auth/port/types/tokens.d.ts +12 -0
  19. package/dist/auth/port/types/tokens.d.ts.map +1 -1
  20. package/dist/auth/sessions/collect-expired.d.ts +14 -0
  21. package/dist/auth/sessions/collect-expired.d.ts.map +1 -0
  22. package/dist/auth/sessions/shared-api.d.ts +1 -1
  23. package/dist/auth/sessions/shared-api.d.ts.map +1 -1
  24. package/dist/auth/types/change-email.d.ts +31 -0
  25. package/dist/auth/types/change-email.d.ts.map +1 -1
  26. package/dist/auth/types/index.d.ts +1 -1
  27. package/dist/auth/types/index.d.ts.map +1 -1
  28. package/dist/auth/types/janus.d.ts +13 -5
  29. package/dist/auth/types/janus.d.ts.map +1 -1
  30. package/dist/chunks/{index-6bjfv4xv.js → index-q3w3vr69.js} +13 -3
  31. package/dist/chunks/{index-6bjfv4xv.js.map → index-q3w3vr69.js.map} +3 -3
  32. package/dist/chunks/{index-cntabs9d.js → index-rpp0bw6e.js} +2 -2
  33. package/dist/chunks/{index-kppkcbn5.js → index-spwh8b9z.js} +2 -5
  34. package/dist/chunks/{index-kppkcbn5.js.map → index-spwh8b9z.js.map} +2 -2
  35. package/dist/chunks/{index-4khsm7mw.js → index-y9b327j8.js} +18 -18
  36. package/dist/chunks/{index-4khsm7mw.js.map → index-y9b327j8.js.map} +5 -5
  37. package/dist/conformance/cases/outage.d.ts.map +1 -1
  38. package/dist/conformance/cases/tokens/deletion.d.ts.map +1 -1
  39. package/dist/conformance/describe.d.ts +1 -0
  40. package/dist/conformance/describe.d.ts.map +1 -1
  41. package/dist/conformance/index.js +39 -5
  42. package/dist/conformance/index.js.map +5 -5
  43. package/dist/conformance/types.d.ts +1 -1
  44. package/dist/conformance/types.d.ts.map +1 -1
  45. package/dist/index.js +478 -453
  46. package/dist/index.js.map +21 -19
  47. package/dist/permissions/index.js +3 -3
  48. package/docs/guide/adapters.md +40 -3
  49. package/docs/guide/email-flows.md +16 -2
  50. package/docs/guide/errors.md +1 -1
  51. package/docs/guide/passwords.md +7 -10
  52. package/docs/guide/sessions.md +15 -4
  53. package/docs/guide/users.md +1 -1
  54. package/docs/roadmap.md +14 -20
  55. package/docs/troubleshooting.md +12 -10
  56. package/package.json +1 -1
  57. /package/dist/chunks/{index-cntabs9d.js.map → index-rpp0bw6e.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isSubjectSet2,
3
3
  formatTuple2
4
- } from "./index-kppkcbn5.js";
4
+ } from "./index-spwh8b9z.js";
5
5
 
6
6
  // src/permissions/port/memory.ts
7
7
  function createMemoryRelations2() {
@@ -77,4 +77,4 @@ function copyTuple(tuple) {
77
77
  export { createMemoryRelations2 };
78
78
 
79
79
  //# debugId=0BA3AA406E650E8464756E2164756E21
80
- //# sourceMappingURL=index-cntabs9d.js.map
80
+ //# sourceMappingURL=index-rpp0bw6e.js.map
@@ -36,7 +36,6 @@ class JanusError2 extends Error {
36
36
  this.retryAfter = options?.retryAfter;
37
37
  }
38
38
  }
39
-
40
39
  // src/errors/janus-error/refusals.ts
41
40
  class UserInvalidError2 extends JanusError2 {
42
41
  name = "UserInvalidError";
@@ -99,7 +98,6 @@ class PermissionDepthError2 extends JanusError2 {
99
98
  name = "PermissionDepthError";
100
99
  code = "PERMISSION_DEPTH";
101
100
  }
102
-
103
101
  // src/errors/janus-error/status.ts
104
102
  function statusOf2(code) {
105
103
  switch (code) {
@@ -135,7 +133,6 @@ function statusOf2(code) {
135
133
  return 500;
136
134
  }
137
135
  }
138
-
139
136
  // src/errors/janus-error/store.ts
140
137
  class StoreFailure2 extends JanusError2 {
141
138
  name = "StoreFailure";
@@ -217,5 +214,5 @@ function parseSubject2(text) {
217
214
 
218
215
  export { JanusError2, UserInvalidError2, CredentialError2, UserInactiveError2, TokenError2, SecondFactorError2, StepUpRequiredError2, MailThrottledError2, InvalidCursorError2, UnsupportedError2, PermissionDepthError2, statusOf2, StoreFailure2, StoreConflict2, NotFoundError2, isSubjectSet2, subjectOf2, setOf2, isSetOf2, formatEntity2, formatSubject2, formatTuple2, parseTuple2, parseSubject2 };
219
216
 
220
- //# debugId=2BB721119E78839E64756E2164756E21
221
- //# sourceMappingURL=index-kppkcbn5.js.map
217
+ //# debugId=B36CA6F795B9956364756E2164756E21
218
+ //# sourceMappingURL=index-spwh8b9z.js.map
@@ -9,7 +9,7 @@
9
9
  "/**\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\n/**\n * The mark `setOf()` puts on a set: a symbol property, so it survives a spread\n * and `Object.assign`, and no value read from a database or from JSON can\n * carry it. Registered with `Symbol.for`, so two copies of this module — two\n * bundles, two entry points — still agree on it.\n */\nconst SET_OF: unique symbol = Symbol.for('@nxgt/janus/setOf');\n\n/**\n * A subject set `setOf()` made: everyone who holds `relation` on `type:id`.\n *\n * Marked, because a user is passed to `can()` and `grant()` as it is, fields\n * flat on it: a user whose fields include `relation` must stay that user, and\n * never read as the set of whoever holds that relation on them. For an object\n * type, `{ type, id, relation }` is a set as written; for a user type — one\n * the model also declares as an object type — only `setOf()` makes one.\n *\n * A spread keeps the mark. `JSON.stringify` and `structuredClone` drop it:\n * through either, a set comes back as a user — call `setOf()` again, or\n * `parseSubject()` on its notation.\n */\nexport type SetOf<\n\tType extends string = string,\n\tRelation extends string = string,\n> = SubjectSet & {\n\treadonly type: Type;\n\treadonly relation: Relation;\n\treadonly [SET_OF]: true;\n};\n\n/**\n * A user or an object, never a set `setOf()` made: where a relation admits\n * `staff` but not `staff#managers`, a set passed there is refused at compile\n * time as it is at run time.\n */\nexport type NotASet = { readonly [SET_OF]?: never };\n\n/**\n * Everyone who holds `relation` on this user or object — `staff:s1#managers`.\n *\n * ```ts\n * await access.grant(record, 'viewers', setOf(ada, 'managers'));\n * ```\n *\n * Copies `type` and `id` only, like {@link subjectOf}: none of a user's own\n * fields reaches a tuple.\n */\nexport function setOf<const Type extends string, const Relation extends string>(\n\tentity: { readonly type: Type; readonly id: SubjectId },\n\trelation: Relation,\n): SetOf<Type, Relation> {\n\tif (\n\t\ttypeof entity !== 'object' ||\n\t\tentity === null ||\n\t\ttypeof entity.type !== 'string' ||\n\t\ttypeof entity.id !== 'string'\n\t) {\n\t\tthrow new TypeError('setOf: pass a user or { type, id }, then a relation');\n\t}\n\tif (typeof relation !== 'string' || relation === '') {\n\t\tthrow new TypeError('setOf: the relation must be a non-empty string');\n\t}\n\treturn Object.freeze({\n\t\ttype: entity.type,\n\t\tid: entity.id,\n\t\trelation,\n\t\t[SET_OF]: true as const,\n\t});\n}\n\n/**\n * Whether `setOf()` made this value, or a spread of one — what `can()` and\n * `grant()` read to tell a set on a user type from a user.\n */\nexport function isSetOf(value: unknown): value is SetOf {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t(value as { readonly [SET_OF]?: unknown })[SET_OF] === true\n\t);\n}\n",
10
10
  "import {\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype SetOf,\n\ttype Subject,\n\tsetOf,\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/**\n * Reads one subject back: `staff:u1`, or `team:t1#member`. A set is answered\n * as `setOf()` makes it, so `can()` and `grant()` read it as the set it names\n * on a user type too.\n */\nexport function parseSubject(text: string): Entity | SetOf {\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 } : setOf({ type, id }, relation);\n}\n"
11
11
  ],
12
- "mappings": "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",
13
- "debugId": "2BB721119E78839E64756E2164756E21",
12
+ "mappings": "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",
13
+ "debugId": "B36CA6F795B9956364756E2164756E21",
14
14
  "names": []
15
15
  }
@@ -2,22 +2,7 @@ import {
2
2
  JanusError2,
3
3
  InvalidCursorError2,
4
4
  StoreFailure2
5
- } from "./index-kppkcbn5.js";
6
-
7
- // src/pagination/cursor-page.ts
8
- var DEFAULT_PAGE_SIZE2 = 20;
9
- var MAX_PAGE_SIZE2 = 100;
10
- function pageLimit2(limit, where) {
11
- if (limit === undefined)
12
- return DEFAULT_PAGE_SIZE2;
13
- if (!Number.isInteger(limit) || limit < 1) {
14
- throw new TypeError(`${where}: limit must be an integer of at least 1, or absent`);
15
- }
16
- return Math.min(limit, MAX_PAGE_SIZE2);
17
- }
18
- function invalidCursor2(where, cursor) {
19
- return new InvalidCursorError2(`${where}: this cursor was not minted by this store, or was minted for another ordering (${cursor.length} characters)`);
20
- }
5
+ } from "./index-spwh8b9z.js";
21
6
 
22
7
  // src/stores/guard.ts
23
8
  var ANSWERS_NOTHING = new Set(["insertSession", "insertToken", "write"]);
@@ -69,7 +54,22 @@ function guardRelations(store) {
69
54
  var isStorable = (value) => !value.includes("\x00") && value.isWellFormed();
70
55
  var UNSTORABLE = "holds a NUL character or a lone surrogate, which no store can keep";
71
56
 
57
+ // src/pagination/cursor-page.ts
58
+ var DEFAULT_PAGE_SIZE2 = 20;
59
+ var MAX_PAGE_SIZE2 = 100;
60
+ function pageLimit2(limit, where) {
61
+ if (limit === undefined)
62
+ return DEFAULT_PAGE_SIZE2;
63
+ if (!Number.isInteger(limit) || limit < 1) {
64
+ throw new TypeError(`${where}: limit must be an integer of at least 1, or absent`);
65
+ }
66
+ return Math.min(limit, MAX_PAGE_SIZE2);
67
+ }
68
+ function invalidCursor2(where, cursor) {
69
+ return new InvalidCursorError2(`${where}: this cursor was not minted by this store, or was minted for another ordering (${cursor.length} characters)`);
70
+ }
71
+
72
72
  export { guardStore, guardRelations, isStorable, UNSTORABLE, DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE2, pageLimit2, invalidCursor2 };
73
73
 
74
- //# debugId=7AD3830DC22148E664756E2164756E21
75
- //# sourceMappingURL=index-4khsm7mw.js.map
74
+ //# debugId=5702950D83C4F86D64756E2164756E21
75
+ //# sourceMappingURL=index-y9b327j8.js.map
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/pagination/cursor-page.ts", "../src/stores/guard.ts", "../src/stores/storable.ts"],
3
+ "sources": ["../src/stores/guard.ts", "../src/stores/storable.ts", "../src/pagination/cursor-page.ts"],
4
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
5
  "/**\n * **The one place a store's answer is caught**, shared by both sides of the\n * package: the identity stores and the relation store reach a caller only\n * through {@link guardStore}.\n *\n * - A `JanusError` the store threw passes through, so `instanceof` holds.\n * - **Anything else it threw is a failure** — `StoreFailure`, naming the slot\n * and the method, with the original as `cause`. Never an absence.\n * - `undefined` where the port says `null` is a store that forgot to answer,\n * and becomes `StoreFailure` rather than \"not found\".\n *\n * The `catch` below always rethrows; `src/auth/outage.scan.spec.ts` holds it to that.\n */\n\nimport {\n\ttype JanusError,\n\tJanusError as JanusErrorClass,\n\tStoreFailure,\n} from '../errors/janus-error';\nimport type { RelationStore } from '../permissions/port/types';\n\n/** A store the guard names in a `StoreFailure`: `users`, `sessions`, `tokens` or `relations`. */\nexport type StoreSlot = NonNullable<StoreFailure['slot']>;\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\nexport function guardStore<S extends object>(slot: StoreSlot, 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(\n\terror: unknown,\n\tslot: StoreSlot,\n\tmethod: string,\n): 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 * 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 guardStore('relations', store);\n}\n",
7
- "/**\n * What every store can keep. PostgreSQL refuses `\\u0000` in `text` and\n * `jsonb`, and a lone surrogate in `jsonb`; MongoDB and the memory store keep\n * both. The core refuses them before a store is asked, so a request that sends\n * one is answered the same way on every adapter — not `STORE_FAILED` on one of\n * them, a 503 for a database that is up.\n *\n * Shared by both sides of the package, like `guard.ts`: a string from a\n * request reaches the identity stores and the relation store alike.\n */\n\n/** Whether every store can keep this string. */\nexport const isStorable = (value: string): boolean =>\n\t!value.includes('\\u0000') && value.isWellFormed();\n\n/** What an issue says about a string no store can keep. */\nexport const UNSTORABLE =\n\t'holds a NUL character or a lone surrogate, which no store can keep';\n"
6
+ "/**\n * What every store can keep. PostgreSQL refuses `\\u0000` in `text` and\n * `jsonb`, and a lone surrogate in `jsonb`; MongoDB and the memory store keep\n * both. The core refuses them before a store is asked, so a request that sends\n * one is answered the same way on every adapter — not `STORE_FAILED` on one of\n * them, a 503 for a database that is up.\n *\n * Shared by both sides of the package, like `guard.ts`: a string from a\n * request reaches the identity stores and the relation store alike.\n */\n\n/** Whether every store can keep this string. */\nexport const isStorable = (value: string): boolean =>\n\t!value.includes('\\u0000') && value.isWellFormed();\n\n/** What an issue says about a string no store can keep. */\nexport const UNSTORABLE =\n\t'holds a NUL character or a lone surrogate, which no store can keep';\n",
7
+ "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"
8
8
  ],
9
- "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;;;AC9CD,IAAM,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,eAAe,OAAO,CAAC;AAElE,SAAS,UAA4B,CAAC,MAAiB,OAAa;AAAA,EAC1E,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,CACjB,OACA,MACA,QACa;AAAA,EACb,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;AAOK,SAAS,cAAc,CAAC,OAAqC;AAAA,EACnE,OAAO,WAAW,aAAa,KAAK;AAAA;;;ACzF9B,IAAM,aAAa,CAAC,UAC1B,CAAC,MAAM,SAAS,MAAQ,KAAK,MAAM,aAAa;AAG1C,IAAM,aACZ;",
10
- "debugId": "7AD3830DC22148E664756E2164756E21",
9
+ "mappings": ";;;;;;;AA6BA,IAAM,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,eAAe,OAAO,CAAC;AAElE,SAAS,UAA4B,CAAC,MAAiB,OAAa;AAAA,EAC1E,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,CACjB,OACA,MACA,QACa;AAAA,EACb,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;AAOK,SAAS,cAAc,CAAC,OAAqC;AAAA,EACnE,OAAO,WAAW,aAAa,KAAK;AAAA;;;ACzF9B,IAAM,aAAa,CAAC,UAC1B,CAAC,MAAM,SAAS,MAAQ,KAAK,MAAM,aAAa;AAG1C,IAAM,aACZ;;;ACOM,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;",
10
+ "debugId": "5702950D83C4F86D64756E2164756E21",
11
11
  "names": []
12
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;AAkEzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAuGjD,CAAC"}
1
+ {"version":3,"file":"outage.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/outage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAe,eAAe,EAAc,MAAM,UAAU,CAAC;AAkEzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAkHjD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"deletion.d.ts","sourceRoot":"","sources":["../../../../src/conformance/cases/tokens/deletion.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EA6CxD,CAAC"}
1
+ {"version":3,"file":"deletion.d.ts","sourceRoot":"","sources":["../../../../src/conformance/cases/tokens/deletion.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD,sEAAsE;AACtE,eAAO,MAAM,kBAAkB,EAAE,SAAS,eAAe,EA+FxD,CAAC"}
@@ -5,6 +5,7 @@ export declare const allCases: readonly ConformanceCase[];
5
5
  export declare const SKIP_REASONS: {
6
6
  readonly faults: "faults not provided: the outage invariant is not proven for this adapter";
7
7
  readonly deleteExpiredSessions: "stores.sessions does not implement the optional deleteExpiredSessions";
8
+ readonly deleteExpiredTokens: "stores.tokens does not implement the optional deleteExpiredTokens";
8
9
  readonly findUsers: "stores.users does not implement the optional findUsers: findMany reads with findUser instead";
9
10
  };
10
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/conformance/describe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEX,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,SAAS,CAAC;AAEjB,mDAAmD;AACnD,eAAO,MAAM,QAAQ,EAAE,SAAS,eAAe,EAK9C,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,YAAY;;;;CAOf,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC,CAiBnE;AAoBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,IAAI,CASP;AAED,oEAAoE;AACpE,UAAU,aAAa;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CAC5C;AAQD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,OAAO,EAAE;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,CACb,eAAe,EAAE,CAAC,KACd,OAAO,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACvE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,GAAG,IAAI,CA6CP;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAgB5D"}
1
+ {"version":3,"file":"describe.d.ts","sourceRoot":"","sources":["../../src/conformance/describe.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEX,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EAEjB,MAAM,SAAS,CAAC;AAEjB,mDAAmD;AACnD,eAAO,MAAM,QAAQ,EAAE,SAAS,eAAe,EAK9C,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,YAAY;;;;;CASf,CAAC;AAEX;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC5B,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC,CAiBnE;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,IAAI,CASP;AAED,oEAAoE;AACpE,UAAU,aAAa;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CAC5C;AAQD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,OAAO,EAAE;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,CACb,eAAe,EAAE,CAAC,KACd,OAAO,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACvE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,GAAG,IAAI,CA6CP;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,iBAAiB,CAgB5D"}
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  mintId2,
3
3
  createMemoryStores2
4
- } from "../chunks/index-6bjfv4xv.js";
4
+ } from "../chunks/index-q3w3vr69.js";
5
5
  import {
6
6
  JanusError2,
7
7
  StoreFailure2,
8
8
  StoreConflict2,
9
9
  NotFoundError2,
10
10
  formatSubject2
11
- } from "../chunks/index-kppkcbn5.js";
11
+ } from "../chunks/index-spwh8b9z.js";
12
12
  import {
13
13
  createMemoryRelations2
14
- } from "../chunks/index-cntabs9d.js";
14
+ } from "../chunks/index-rpp0bw6e.js";
15
15
 
16
16
  // src/conformance/assert.ts
17
17
  function equal(actual, expected, what) {
@@ -196,7 +196,11 @@ var outageCases = [
196
196
  outage("tokens", "spendUserTokens", ({ stores }) => stores.tokens.spendUserTokens(user.id, "resetPassword", now), seed),
197
197
  outage("users", "deleteUser", ({ stores }) => stores.users.deleteUser(user.id), seed),
198
198
  outage("sessions", "deleteUserSessions", ({ stores }) => stores.sessions.deleteUserSessions(user.id), seed),
199
- outage("tokens", "deleteUserTokens", ({ stores }) => stores.tokens.deleteUserTokens(user.id), seed)
199
+ outage("tokens", "deleteUserTokens", ({ stores }) => stores.tokens.deleteUserTokens(user.id), seed),
200
+ {
201
+ ...outage("tokens", "deleteExpiredTokens", async ({ stores }) => stores.tokens.deleteExpiredTokens?.call(stores.tokens, now), seed),
202
+ needs: ["faults", "deleteExpiredTokens"]
203
+ }
200
204
  ];
201
205
  // src/conformance/cases/sessions/deletion.ts
202
206
  var group = "sessions";
@@ -553,6 +557,33 @@ var tokenDeletionCases = [
553
557
  equal(await stores.tokens.consumeToken(stranger.tokenHash, stranger.kind, now), stranger, "deleteUserTokens should not touch another user");
554
558
  equal(await stores.tokens.deleteUserTokens(userId), 0, "deleteUserTokens for a user with none answers 0, not a failure");
555
559
  }
560
+ },
561
+ {
562
+ id: "tokens.deleteExpired",
563
+ group: group9,
564
+ name: "deletes tokens whose expiry is at or before the instant, spent or not, and only those",
565
+ needs: "deleteExpiredTokens",
566
+ async run({ stores }) {
567
+ const collect = stores.tokens.deleteExpiredTokens;
568
+ ok(collect !== undefined, "deleteExpiredTokens should be present");
569
+ const lapsedAt = at("2026-01-10T00:00:00.000Z");
570
+ const laterAt = at("2026-01-10T00:00:00.001Z");
571
+ const spentAt = at("2026-01-05T00:00:00.000Z");
572
+ const lapsed = tokenRecord({ expiresAt: lapsedAt });
573
+ const lapsedSpent = tokenRecord({ expiresAt: lapsedAt, spentAt });
574
+ const living = tokenRecord({ expiresAt: laterAt });
575
+ const livingSpent = tokenRecord({ expiresAt: laterAt, spentAt });
576
+ for (const record of [lapsed, lapsedSpent, living, livingSpent]) {
577
+ await stores.tokens.insertToken(record);
578
+ }
579
+ equal(await collect?.call(stores.tokens, lapsedAt), 2, "deleteExpiredTokens: how many it deleted, spent ones included");
580
+ for (const record of [lapsed, lapsedSpent]) {
581
+ isNull(await stores.tokens.countAttempt(record.tokenHash, record.kind), "countAttempt after deleteExpiredTokens");
582
+ }
583
+ equal(await stores.tokens.countAttempt(living.tokenHash, living.kind), { ...living, attempts: 1 }, "deleteExpiredTokens should keep a token one millisecond short of lapsing");
584
+ equal(await stores.tokens.countAttempt(livingSpent.tokenHash, livingSpent.kind), livingSpent, "deleteExpiredTokens should keep a spent token until it lapses, still spent");
585
+ equal(await collect?.call(stores.tokens, lapsedAt), 0, "deleteExpiredTokens with nothing lapsed answers 0, not a failure");
586
+ }
556
587
  }
557
588
  ];
558
589
 
@@ -1213,6 +1244,7 @@ var allCases = [
1213
1244
  var SKIP_REASONS = {
1214
1245
  faults: "faults not provided: the outage invariant is not proven for this adapter",
1215
1246
  deleteExpiredSessions: "stores.sessions does not implement the optional deleteExpiredSessions",
1247
+ deleteExpiredTokens: "stores.tokens does not implement the optional deleteExpiredTokens",
1216
1248
  findUsers: "stores.users does not implement the optional findUsers: findMany reads with findUser instead"
1217
1249
  };
1218
1250
  async function runCase(conformanceCase, harness) {
@@ -1240,6 +1272,8 @@ function provides(opened, need) {
1240
1272
  return opened.faults !== undefined;
1241
1273
  case "deleteExpiredSessions":
1242
1274
  return opened.stores.sessions.deleteExpiredSessions !== undefined;
1275
+ case "deleteExpiredTokens":
1276
+ return opened.stores.tokens.deleteExpiredTokens !== undefined;
1243
1277
  case "findUsers":
1244
1278
  return opened.stores.users.findUsers !== undefined;
1245
1279
  }
@@ -1707,5 +1741,5 @@ export {
1707
1741
  userStoreCases
1708
1742
  };
1709
1743
 
1710
- //# debugId=A279A4865433665964756E2164756E21
1744
+ //# debugId=6890F6DE5C8C608764756E2164756E21
1711
1745
  //# sourceMappingURL=index.js.map