@nxgt/janus 0.1.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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +554 -0
  3. package/dist/auth/config.d.ts +201 -0
  4. package/dist/auth/config.d.ts.map +1 -0
  5. package/dist/auth/context.d.ts +87 -0
  6. package/dist/auth/context.d.ts.map +1 -0
  7. package/dist/auth/hashers.d.ts +35 -0
  8. package/dist/auth/hashers.d.ts.map +1 -0
  9. package/dist/auth/index.d.ts +9 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/janus.d.ts +38 -0
  12. package/dist/auth/janus.d.ts.map +1 -0
  13. package/dist/auth/outage.d.ts +61 -0
  14. package/dist/auth/outage.d.ts.map +1 -0
  15. package/dist/auth/port/assert-stores.d.ts +16 -0
  16. package/dist/auth/port/assert-stores.d.ts.map +1 -0
  17. package/dist/auth/port/memory.d.ts +25 -0
  18. package/dist/auth/port/memory.d.ts.map +1 -0
  19. package/dist/auth/port/types.d.ts +393 -0
  20. package/dist/auth/port/types.d.ts.map +1 -0
  21. package/dist/auth/secrets.d.ts +17 -0
  22. package/dist/auth/secrets.d.ts.map +1 -0
  23. package/dist/auth/sessions.d.ts +31 -0
  24. package/dist/auth/sessions.d.ts.map +1 -0
  25. package/dist/auth/standard-schema.d.ts +41 -0
  26. package/dist/auth/standard-schema.d.ts.map +1 -0
  27. package/dist/auth/types.d.ts +353 -0
  28. package/dist/auth/types.d.ts.map +1 -0
  29. package/dist/auth/users.d.ts +9 -0
  30. package/dist/auth/users.d.ts.map +1 -0
  31. package/dist/chunks/index-658b6mr2.js +269 -0
  32. package/dist/chunks/index-658b6mr2.js.map +11 -0
  33. package/dist/chunks/index-6dytvy3h.js +93 -0
  34. package/dist/chunks/index-6dytvy3h.js.map +11 -0
  35. package/dist/chunks/index-6p56fpbe.js +147 -0
  36. package/dist/chunks/index-6p56fpbe.js.map +12 -0
  37. package/dist/chunks/index-fgb3t64y.js +77 -0
  38. package/dist/chunks/index-fgb3t64y.js.map +10 -0
  39. package/dist/conformance/assert.d.ts +36 -0
  40. package/dist/conformance/assert.d.ts.map +1 -0
  41. package/dist/conformance/cases/outage.d.ts +3 -0
  42. package/dist/conformance/cases/outage.d.ts.map +1 -0
  43. package/dist/conformance/cases/sessions.d.ts +3 -0
  44. package/dist/conformance/cases/sessions.d.ts.map +1 -0
  45. package/dist/conformance/cases/tokens.d.ts +3 -0
  46. package/dist/conformance/cases/tokens.d.ts.map +1 -0
  47. package/dist/conformance/cases/users.d.ts +3 -0
  48. package/dist/conformance/cases/users.d.ts.map +1 -0
  49. package/dist/conformance/describe.d.ts +74 -0
  50. package/dist/conformance/describe.d.ts.map +1 -0
  51. package/dist/conformance/fixtures.d.ts +11 -0
  52. package/dist/conformance/fixtures.d.ts.map +1 -0
  53. package/dist/conformance/index.d.ts +33 -0
  54. package/dist/conformance/index.d.ts.map +1 -0
  55. package/dist/conformance/index.js +1113 -0
  56. package/dist/conformance/index.js.map +18 -0
  57. package/dist/conformance/reference.d.ts +13 -0
  58. package/dist/conformance/reference.d.ts.map +1 -0
  59. package/dist/conformance/relations.d.ts +67 -0
  60. package/dist/conformance/relations.d.ts.map +1 -0
  61. package/dist/conformance/types.d.ts +71 -0
  62. package/dist/conformance/types.d.ts.map +1 -0
  63. package/dist/errors/janus-error.d.ts +259 -0
  64. package/dist/errors/janus-error.d.ts.map +1 -0
  65. package/dist/ids/id.d.ts +55 -0
  66. package/dist/ids/id.d.ts.map +1 -0
  67. package/dist/index.d.ts +30 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +962 -0
  70. package/dist/index.js.map +19 -0
  71. package/dist/pagination/cursor-page.d.ts +53 -0
  72. package/dist/pagination/cursor-page.d.ts.map +1 -0
  73. package/dist/permissions/engine.d.ts +46 -0
  74. package/dist/permissions/engine.d.ts.map +1 -0
  75. package/dist/permissions/index.d.ts +24 -0
  76. package/dist/permissions/index.d.ts.map +1 -0
  77. package/dist/permissions/index.js +685 -0
  78. package/dist/permissions/index.js.map +15 -0
  79. package/dist/permissions/input.d.ts +29 -0
  80. package/dist/permissions/input.d.ts.map +1 -0
  81. package/dist/permissions/model.d.ts +368 -0
  82. package/dist/permissions/model.d.ts.map +1 -0
  83. package/dist/permissions/port/memory.d.ts +17 -0
  84. package/dist/permissions/port/memory.d.ts.map +1 -0
  85. package/dist/permissions/port/types.d.ts +84 -0
  86. package/dist/permissions/port/types.d.ts.map +1 -0
  87. package/dist/permissions/resolve.d.ts +59 -0
  88. package/dist/permissions/resolve.d.ts.map +1 -0
  89. package/dist/permissions/reverse.d.ts +53 -0
  90. package/dist/permissions/reverse.d.ts.map +1 -0
  91. package/dist/permissions/walk.d.ts +29 -0
  92. package/dist/permissions/walk.d.ts.map +1 -0
  93. package/dist/subjects/notation.d.ts +35 -0
  94. package/dist/subjects/notation.d.ts.map +1 -0
  95. package/dist/subjects/subject.d.ts +75 -0
  96. package/dist/subjects/subject.d.ts.map +1 -0
  97. package/dist/time/clock.d.ts +31 -0
  98. package/dist/time/clock.d.ts.map +1 -0
  99. package/dist/time/duration.d.ts +23 -0
  100. package/dist/time/duration.d.ts.map +1 -0
  101. package/docs/README.md +17 -0
  102. package/docs/guide/adapters.md +296 -0
  103. package/docs/guide/email-flows.md +146 -0
  104. package/docs/guide/errors.md +146 -0
  105. package/docs/guide/passwords.md +136 -0
  106. package/docs/guide/permissions.md +347 -0
  107. package/docs/guide/sessions.md +211 -0
  108. package/docs/guide/users.md +277 -0
  109. package/docs/guide/vocabulary.md +163 -0
  110. package/docs/roadmap.md +93 -0
  111. package/docs/troubleshooting.md +648 -0
  112. package/package.json +70 -0
@@ -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 \"${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": "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",
9
+ "debugId": "127433514BF2BB4B64756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,93 @@
1
+ import {
2
+ JanusError2,
3
+ StoreFailure2,
4
+ StoreConflict2,
5
+ InvalidCursorError2
6
+ } from "./index-6p56fpbe.js";
7
+
8
+ // src/pagination/cursor-page.ts
9
+ var DEFAULT_PAGE_SIZE2 = 20;
10
+ var MAX_PAGE_SIZE2 = 100;
11
+ function pageLimit2(limit, where) {
12
+ if (limit === undefined)
13
+ return DEFAULT_PAGE_SIZE2;
14
+ if (!Number.isInteger(limit) || limit < 1) {
15
+ throw new TypeError(`${where}: limit must be an integer of at least 1, or absent`);
16
+ }
17
+ return Math.min(limit, MAX_PAGE_SIZE2);
18
+ }
19
+ function invalidCursor2(where, cursor) {
20
+ return new InvalidCursorError2(`${where}: this cursor was not minted by this store, or was minted for another ordering (${cursor.length} characters)`);
21
+ }
22
+
23
+ // src/auth/outage.ts
24
+ var ANSWERS_NOTHING = new Set(["insertSession", "insertToken", "write"]);
25
+ function guardStores(stores) {
26
+ return {
27
+ users: guardSlot("users", stores.users),
28
+ sessions: guardSlot("sessions", stores.sessions),
29
+ tokens: guardSlot("tokens", stores.tokens)
30
+ };
31
+ }
32
+ function guardRelations(store) {
33
+ return guardSlot("relations", store);
34
+ }
35
+ function guardSlot(slot, store) {
36
+ const guarded = {};
37
+ for (const method of methodsOf(store)) {
38
+ const original = store[method];
39
+ if (typeof original !== "function")
40
+ continue;
41
+ guarded[method] = async (...args) => {
42
+ let answer;
43
+ try {
44
+ answer = await original.apply(store, args);
45
+ } catch (error) {
46
+ throw asFailure(error, slot, method);
47
+ }
48
+ if (answer === undefined && !ANSWERS_NOTHING.has(method)) {
49
+ throw new StoreFailure2(`${slot}.${method} answered undefined: an absence is null, so this store forgot to answer`, { slot, operation: method });
50
+ }
51
+ return answer;
52
+ };
53
+ }
54
+ return guarded;
55
+ }
56
+ function methodsOf(store) {
57
+ const names = new Set;
58
+ for (let proto = store;proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
59
+ for (const name of Object.getOwnPropertyNames(proto)) {
60
+ if (name !== "constructor")
61
+ names.add(name);
62
+ }
63
+ }
64
+ return [...names];
65
+ }
66
+ function asFailure(error, slot, method) {
67
+ if (error instanceof JanusError2)
68
+ return error;
69
+ return new StoreFailure2(`${slot}.${method}: the store could not answer`, {
70
+ slot,
71
+ operation: method,
72
+ cause: error
73
+ });
74
+ }
75
+ function required(value, absent) {
76
+ if (value === null)
77
+ throw absent();
78
+ return value;
79
+ }
80
+ async function unlessVersionConflict(write) {
81
+ try {
82
+ return await write;
83
+ } catch (error) {
84
+ if (error instanceof StoreConflict2 && error.on === "version")
85
+ return null;
86
+ throw error;
87
+ }
88
+ }
89
+
90
+ export { guardStores, guardRelations, required, unlessVersionConflict, DEFAULT_PAGE_SIZE2, MAX_PAGE_SIZE2, pageLimit2, invalidCursor2 };
91
+
92
+ //# debugId=047FF5FD235ABA3764756E2164756E21
93
+ //# sourceMappingURL=index-6dytvy3h.js.map
@@ -0,0 +1,11 @@
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
+ }
@@ -0,0 +1,147 @@
1
+ // src/errors/janus-error.ts
2
+ class JanusError2 extends Error {
3
+ constructor(message, options) {
4
+ super(message, { cause: options?.cause });
5
+ this.name = "JanusError";
6
+ this.code = "STORE_FAILED";
7
+ this.userId = options?.userId;
8
+ this.userType = options?.userType;
9
+ this.login = options?.login;
10
+ this.reason = options?.reason;
11
+ this.hashPrefix = options?.hashPrefix;
12
+ this.expectedVersion = options?.expectedVersion;
13
+ this.actualVersion = options?.actualVersion;
14
+ this.issues = options?.issues;
15
+ this.minLength = options?.minLength;
16
+ this.operation = options?.operation;
17
+ this.slot = options?.slot;
18
+ this.permission = options?.permission;
19
+ this.maxDepth = options?.maxDepth;
20
+ }
21
+ }
22
+
23
+ class StoreFailure2 extends JanusError2 {
24
+ constructor() {
25
+ super(...arguments);
26
+ this.name = "StoreFailure";
27
+ this.code = "STORE_FAILED";
28
+ }
29
+ }
30
+
31
+ class StoreConflict2 extends JanusError2 {
32
+ constructor(on, message, options) {
33
+ super(message, options);
34
+ this.name = "StoreConflict";
35
+ this.on = on;
36
+ this.code = on === "login" ? "LOGIN_TAKEN" : "VERSION_CONFLICT";
37
+ }
38
+ }
39
+
40
+ class NotFoundError2 extends JanusError2 {
41
+ constructor() {
42
+ super(...arguments);
43
+ this.name = "NotFoundError";
44
+ this.code = "NOT_FOUND";
45
+ }
46
+ }
47
+
48
+ class UserInvalidError2 extends JanusError2 {
49
+ constructor() {
50
+ super(...arguments);
51
+ this.name = "UserInvalidError";
52
+ this.code = "USER_INVALID";
53
+ }
54
+ }
55
+
56
+ class CredentialError2 extends JanusError2 {
57
+ constructor(code, message, options) {
58
+ super(message, options);
59
+ this.name = "CredentialError";
60
+ this.code = code;
61
+ }
62
+ }
63
+
64
+ class UserInactiveError2 extends JanusError2 {
65
+ constructor() {
66
+ super(...arguments);
67
+ this.name = "UserInactiveError";
68
+ this.code = "USER_INACTIVE";
69
+ }
70
+ }
71
+
72
+ class TokenError2 extends JanusError2 {
73
+ constructor(code, message, options) {
74
+ super(message, options);
75
+ this.name = "TokenError";
76
+ this.code = code;
77
+ }
78
+ }
79
+
80
+ class InvalidCursorError2 extends JanusError2 {
81
+ constructor() {
82
+ super(...arguments);
83
+ this.name = "InvalidCursorError";
84
+ this.code = "INVALID_CURSOR";
85
+ }
86
+ }
87
+
88
+ class UnsupportedError2 extends JanusError2 {
89
+ constructor() {
90
+ super(...arguments);
91
+ this.name = "UnsupportedError";
92
+ this.code = "UNSUPPORTED";
93
+ }
94
+ }
95
+
96
+ class PermissionDepthError2 extends JanusError2 {
97
+ constructor() {
98
+ super(...arguments);
99
+ this.name = "PermissionDepthError";
100
+ this.code = "PERMISSION_DEPTH";
101
+ }
102
+ }
103
+
104
+ // src/subjects/subject.ts
105
+ function isSubjectSet2(subject) {
106
+ return typeof subject.relation === "string";
107
+ }
108
+ function subjectOf2(user) {
109
+ return { type: user.type, id: user.id };
110
+ }
111
+
112
+ // src/subjects/notation.ts
113
+ function formatEntity2(entity) {
114
+ return `${entity.type}:${entity.id}`;
115
+ }
116
+ function formatSubject2(subject) {
117
+ return isSubjectSet2(subject) ? `${formatEntity2(subject)}#${subject.relation}` : formatEntity2(subject);
118
+ }
119
+ function formatTuple2(tuple) {
120
+ return `${formatEntity2(tuple.object)}#${tuple.relation}@${formatSubject2(tuple.subject)}`;
121
+ }
122
+ var TYPE = "([^:@#()]+)";
123
+ var ID = "([^@#()]+)";
124
+ var RELATION = "([^:@#()]+)";
125
+ var TUPLE = new RegExp(`^${TYPE}:${ID}#${RELATION}@(.+)$`);
126
+ var SUBJECT = new RegExp(`^${TYPE}:${ID}(?:#${RELATION})?$`);
127
+ function parseTuple2(text) {
128
+ const match = TUPLE.exec(text);
129
+ if (!match) {
130
+ throw new TypeError(`parseTuple: "${text}" is not a relation tuple; expected type:id#relation@subject`);
131
+ }
132
+ const [, type, id, relation, subject] = match;
133
+ return { object: { type, id }, relation, subject: parseSubject2(subject) };
134
+ }
135
+ function parseSubject2(text) {
136
+ const match = SUBJECT.exec(text);
137
+ if (!match) {
138
+ throw new TypeError(`parseSubject: "${text}" is not a subject; expected type:id, or type:id#relation for a subject set`);
139
+ }
140
+ const [, type, id, relation] = match;
141
+ return relation === undefined ? { type, id } : { type, id, relation };
142
+ }
143
+
144
+ export { JanusError2, StoreFailure2, StoreConflict2, NotFoundError2, UserInvalidError2, CredentialError2, UserInactiveError2, TokenError2, InvalidCursorError2, UnsupportedError2, PermissionDepthError2, isSubjectSet2, subjectOf2, formatEntity2, formatSubject2, formatTuple2, parseTuple2, parseSubject2 };
145
+
146
+ //# debugId=B82DDDDA4EE307BC64756E2164756E21
147
+ //# sourceMappingURL=index-6p56fpbe.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors/janus-error.ts", "../src/subjects/subject.ts", "../src/subjects/notation.ts"],
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: everybody who has an account is told they do not.\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",
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 halves of the package. It\n * takes the narrowest shape it reads, so the permissions half 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
+ "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
+ ],
9
+ "mappings": "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",
10
+ "debugId": "B82DDDDA4EE307BC64756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,77 @@
1
+ import {
2
+ isSubjectSet2,
3
+ formatTuple2
4
+ } from "./index-6p56fpbe.js";
5
+
6
+ // src/permissions/port/memory.ts
7
+ function createMemoryRelations2() {
8
+ const tuples = new Map;
9
+ const sameEntity = (a, b) => a.type === b.type && a.id === b.id;
10
+ const sameSubject = (a, b) => sameEntity(a, b) && (isSubjectSet2(a) ? isSubjectSet2(b) && a.relation === b.relation : !isSubjectSet2(b));
11
+ const on = (tuple, object, relation) => tuple.relation === relation && sameEntity(tuple.object, object);
12
+ return {
13
+ async write({ add = [], remove = [] }) {
14
+ for (const tuple of remove)
15
+ tuples.delete(formatTuple2(tuple));
16
+ for (const tuple of add)
17
+ tuples.set(formatTuple2(tuple), copyTuple(tuple));
18
+ },
19
+ async has(tuple) {
20
+ return tuples.has(formatTuple2(tuple));
21
+ },
22
+ async findSubjectSets(object, relation) {
23
+ const sets = [];
24
+ for (const tuple of tuples.values()) {
25
+ if (on(tuple, object, relation) && isSubjectSet2(tuple.subject)) {
26
+ sets.push(copySubject(tuple.subject));
27
+ }
28
+ }
29
+ return sets;
30
+ },
31
+ async findEntities(object, relation) {
32
+ const entities = [];
33
+ for (const tuple of tuples.values()) {
34
+ if (on(tuple, object, relation) && !isSubjectSet2(tuple.subject)) {
35
+ entities.push(copySubject(tuple.subject));
36
+ }
37
+ }
38
+ return entities;
39
+ },
40
+ async findObjects({ type, relation, subject, after, limit }) {
41
+ const ids = [
42
+ ...new Set([...tuples.values()].filter((tuple) => tuple.object.type === type && tuple.relation === relation && sameSubject(tuple.subject, subject) && (after === null || tuple.object.id > after)).map((tuple) => tuple.object.id))
43
+ ].sort();
44
+ const items = ids.slice(0, limit);
45
+ const last = items.at(-1);
46
+ return {
47
+ items,
48
+ nextCursor: ids.length > limit && last !== undefined ? last : null
49
+ };
50
+ },
51
+ async deleteEntity(entity) {
52
+ let deleted = 0;
53
+ for (const [key, tuple] of tuples) {
54
+ if (sameEntity(tuple.object, entity) || sameEntity(tuple.subject, entity)) {
55
+ tuples.delete(key);
56
+ deleted += 1;
57
+ }
58
+ }
59
+ return deleted;
60
+ }
61
+ };
62
+ }
63
+ function copySubject(subject) {
64
+ return isSubjectSet2(subject) ? { type: subject.type, id: subject.id, relation: subject.relation } : { type: subject.type, id: subject.id };
65
+ }
66
+ function copyTuple(tuple) {
67
+ return {
68
+ object: { type: tuple.object.type, id: tuple.object.id },
69
+ relation: tuple.relation,
70
+ subject: copySubject(tuple.subject)
71
+ };
72
+ }
73
+
74
+ export { createMemoryRelations2 };
75
+
76
+ //# debugId=9168B1A5A9C0688D64756E2164756E21
77
+ //# sourceMappingURL=index-fgb3t64y.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/permissions/port/memory.ts"],
4
+ "sourcesContent": [
5
+ "import { formatTuple } from '../../subjects/notation';\nimport {\n\ttype Entity,\n\tisSubjectSet,\n\ttype RelationTuple,\n\ttype Subject,\n\ttype SubjectSet,\n} from '../../subjects/subject';\nimport type { RelationStore } from './types';\n\n/**\n * The reference implementation of the relation store, in memory.\n *\n * **Shipped and documented, not a test helper**, like `createMemoryStores()`:\n * what a consumer uses in their own tests, and what an adapter author compares\n * against. It favours being obviously right over being fast — every query\n * scans — and keeps the rules for real:\n *\n * - a tuple is unique because its key is its notation, and one `Map` holds it;\n * - `write` checks nothing it could fail on half-way, so it is all or nothing\n * on one event loop;\n * - every tuple is copied in and out, so a caller cannot reach the store by\n * mutating what it passed or got back.\n */\nexport function createMemoryRelations(): RelationStore {\n\tconst tuples = new Map<string, RelationTuple>();\n\n\tconst sameEntity = (a: Entity, b: Entity) =>\n\t\ta.type === b.type && a.id === b.id;\n\tconst sameSubject = (a: Subject, b: Subject) =>\n\t\tsameEntity(a, b) &&\n\t\t(isSubjectSet(a)\n\t\t\t? isSubjectSet(b) && a.relation === b.relation\n\t\t\t: !isSubjectSet(b));\n\tconst on = (tuple: RelationTuple, object: Entity, relation: string) =>\n\t\ttuple.relation === relation && sameEntity(tuple.object, object);\n\n\treturn {\n\t\tasync write({ add = [], remove = [] }) {\n\t\t\tfor (const tuple of remove) tuples.delete(formatTuple(tuple));\n\t\t\tfor (const tuple of add) tuples.set(formatTuple(tuple), copyTuple(tuple));\n\t\t},\n\n\t\tasync has(tuple) {\n\t\t\treturn tuples.has(formatTuple(tuple));\n\t\t},\n\n\t\tasync findSubjectSets(object, relation) {\n\t\t\tconst sets: SubjectSet[] = [];\n\t\t\tfor (const tuple of tuples.values()) {\n\t\t\t\tif (on(tuple, object, relation) && isSubjectSet(tuple.subject)) {\n\t\t\t\t\tsets.push(copySubject(tuple.subject) as SubjectSet);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sets;\n\t\t},\n\n\t\tasync findEntities(object, relation) {\n\t\t\tconst entities: Entity[] = [];\n\t\t\tfor (const tuple of tuples.values()) {\n\t\t\t\tif (on(tuple, object, relation) && !isSubjectSet(tuple.subject)) {\n\t\t\t\t\tentities.push(copySubject(tuple.subject));\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn entities;\n\t\t},\n\n\t\tasync findObjects({ type, relation, subject, after, limit }) {\n\t\t\tconst ids = [\n\t\t\t\t...new Set(\n\t\t\t\t\t[...tuples.values()]\n\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t(tuple) =>\n\t\t\t\t\t\t\t\ttuple.object.type === type &&\n\t\t\t\t\t\t\t\ttuple.relation === relation &&\n\t\t\t\t\t\t\t\tsameSubject(tuple.subject, subject) &&\n\t\t\t\t\t\t\t\t(after === null || tuple.object.id > after),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.map((tuple) => tuple.object.id),\n\t\t\t\t),\n\t\t\t].sort();\n\t\t\tconst items = ids.slice(0, limit);\n\t\t\tconst last = items.at(-1);\n\n\t\t\treturn {\n\t\t\t\titems,\n\t\t\t\tnextCursor: ids.length > limit && last !== undefined ? last : null,\n\t\t\t};\n\t\t},\n\n\t\tasync deleteEntity(entity) {\n\t\t\tlet deleted = 0;\n\t\t\tfor (const [key, tuple] of tuples) {\n\t\t\t\tif (\n\t\t\t\t\tsameEntity(tuple.object, entity) ||\n\t\t\t\t\tsameEntity(tuple.subject, entity)\n\t\t\t\t) {\n\t\t\t\t\ttuples.delete(key);\n\t\t\t\t\tdeleted += 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n/** Field by field, so nothing but the declared shape reaches the store. */\nfunction copySubject(subject: Subject): Subject {\n\treturn isSubjectSet(subject)\n\t\t? { type: subject.type, id: subject.id, relation: subject.relation }\n\t\t: { type: subject.type, id: subject.id };\n}\n\nfunction copyTuple(tuple: RelationTuple): RelationTuple {\n\treturn {\n\t\tobject: { type: tuple.object.type, id: tuple.object.id },\n\t\trelation: tuple.relation,\n\t\tsubject: copySubject(tuple.subject),\n\t};\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "9168B1A5A9C0688D64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The suite's five assertions.
3
+ *
4
+ * **No assertion library, no test framework**: `bun:test` in the dependency
5
+ * graph of a published library is a dependency every consumer installs, and an
6
+ * adapter author on vitest could not run the suite at all. Each throws a plain
7
+ * `Error` whose message names the port method and what it should have answered
8
+ * — the sentence an adapter author reads first.
9
+ */
10
+ /** Fails unless `actual` deep-equals `expected`: dates by instant, `1` never `'1'`. */
11
+ export declare function equal(actual: unknown, expected: unknown, what: string): void;
12
+ /** Fails unless `actual` is `null` — not `undefined`, which is a store that forgot to answer. */
13
+ export declare function isNull(actual: unknown, what: string): void;
14
+ /** Fails unless `condition` holds. */
15
+ export declare function ok(condition: boolean, what: string): void;
16
+ /**
17
+ * The rejection of `promise`, or a failure when it resolves.
18
+ *
19
+ * **Settled where it is created**, with `.then(ok, ko)`: a rejection awaited
20
+ * too late is counted unhandled by Bun and fails the case with the very error
21
+ * it was checking — measured in nxgt-data, where a loaded CI runner failed
22
+ * where an idle laptop passed.
23
+ */
24
+ export declare function rejects(promise: Promise<unknown>, what: string): Promise<unknown>;
25
+ /**
26
+ * The `instanceof` probe: a class that is named like this package's but is not
27
+ * this package's class means **two copies of `@nxgt/janus`** are installed —
28
+ * the adapter depends on it instead of peering it. Then `StoreConflict` stops
29
+ * passing through the core, and an identifier that is taken is answered as an
30
+ * outage.
31
+ *
32
+ * The probe says what breaks; `verify:artifacts` says whether a duplicate is
33
+ * present. Both are needed.
34
+ */
35
+ export declare function isOurs<T>(error: unknown, cls: abstract new (...args: never[]) => T, what: string): asserts error is T;
36
+ //# sourceMappingURL=assert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/conformance/assert.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uFAAuF;AACvF,wBAAgB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAO5E;AAED,iGAAiG;AACjG,wBAAgB,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAM1D;AAED,sCAAsC;AACtC,wBAAgB,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEzD;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACtB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EACzB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACvB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EACzC,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,IAAI,CAAC,CAWpB"}
@@ -0,0 +1,3 @@
1
+ import type { ConformanceCase } from '../types';
2
+ export declare const outageCases: readonly ConformanceCase[];
3
+ //# sourceMappingURL=outage.d.ts.map
@@ -0,0 +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;AA4DzE,eAAO,MAAM,WAAW,EAAE,SAAS,eAAe,EAuEjD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ConformanceCase } from '../types';
2
+ export declare const sessionStoreCases: readonly ConformanceCase[];
3
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +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,EAiQvD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ConformanceCase } from '../types';
2
+ export declare const tokenStoreCases: readonly ConformanceCase[];
3
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../../src/conformance/cases/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAgMrD,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ConformanceCase } from '../types';
2
+ export declare const userStoreCases: readonly ConformanceCase[];
3
+ //# sourceMappingURL=users.d.ts.map
@@ -0,0 +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,EAkcpD,CAAC"}