@lunora/auth 1.0.0-alpha.21 → 1.0.0-alpha.211

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 (85) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +99 -10
  3. package/dist/adapter.d.mts +4 -43
  4. package/dist/adapter.d.ts +4 -43
  5. package/dist/adapter.mjs +1 -48
  6. package/dist/audit.d.mts +142 -0
  7. package/dist/audit.d.ts +142 -0
  8. package/dist/audit.mjs +12 -0
  9. package/dist/cimd-node.d.mts +1 -0
  10. package/dist/cimd-node.d.ts +1 -0
  11. package/dist/cimd-node.mjs +1 -0
  12. package/dist/cimd-workers.d.mts +32 -0
  13. package/dist/cimd-workers.d.ts +32 -0
  14. package/dist/cimd-workers.mjs +1 -0
  15. package/dist/email-guard.d.mts +127 -0
  16. package/dist/email-guard.d.ts +127 -0
  17. package/dist/email-guard.mjs +1 -0
  18. package/dist/index.d.mts +769 -173
  19. package/dist/index.d.ts +769 -173
  20. package/dist/index.mjs +1 -12
  21. package/dist/middleware.d.mts +165 -155
  22. package/dist/middleware.d.ts +165 -155
  23. package/dist/middleware.mjs +1 -56
  24. package/dist/packem_shared/AUTH_DO_AUDIT_PATH-1kIGHxKo.mjs +1 -0
  25. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs +1 -0
  26. package/dist/packem_shared/LunoraAuthAdminError-uhKG1MgQ.mjs +1 -0
  27. package/dist/packem_shared/adapter-CBZ17jKi.mjs +2 -0
  28. package/dist/packem_shared/adapter.d-CElAPJ1U.d.ts +88 -0
  29. package/dist/packem_shared/adapter.d-CaPwF2v3.d.mts +88 -0
  30. package/dist/packem_shared/auth-do-DEd6xEKe.mjs +1 -0
  31. package/dist/packem_shared/authAuditHook-DPTKdjBe.mjs +1 -0
  32. package/dist/packem_shared/authDiscoveryPaths-B14lM14u.mjs +1 -0
  33. package/dist/packem_shared/authDoColumnAdditions-UwF3Rl41.mjs +1 -0
  34. package/dist/packem_shared/compileMigrationsSql-BiYsLz91.mjs +1 -0
  35. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.mts +142 -0
  36. package/dist/packem_shared/create-auth.d-D1CKOFWc.d.ts +142 -0
  37. package/dist/packem_shared/createAuth-BdVltKyf.mjs +1 -0
  38. package/dist/packem_shared/createDoAuthWiring-B-c6qEcK.mjs +1 -0
  39. package/dist/packem_shared/createLunoraAuthClient-DRcFNwp9.mjs +1 -0
  40. package/dist/packem_shared/createSignUpInvitation-DHZL9fNl.mjs +1 -0
  41. package/dist/packem_shared/emailGateDatabaseHooks-CAygR3iq.mjs +1 -0
  42. package/dist/packem_shared/invite-only.d-CsPJWAwM.d.ts +126 -0
  43. package/dist/packem_shared/invite-only.d-DKyHHkkQ.d.mts +126 -0
  44. package/dist/packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs +1 -0
  45. package/dist/packem_shared/mcp-CgX1hTOv.mjs +1 -0
  46. package/dist/packem_shared/on-cloudflare-edge-Dx6gFjBO.mjs +1 -0
  47. package/dist/packem_shared/quote-identifier-CGiYFBvY.mjs +1 -0
  48. package/dist/packem_shared/sessionPresets-C867Mlo4.mjs +1 -0
  49. package/dist/packem_shared/sql-store-uMubPYI9.mjs +1 -0
  50. package/dist/packem_shared/uiConfig-B8pN6pks.mjs +1 -0
  51. package/dist/plugins-client.d.mts +250 -1
  52. package/dist/plugins-client.d.ts +250 -1
  53. package/dist/plugins-client.mjs +1 -2
  54. package/dist/plugins-enterprise-client.d.mts +13 -0
  55. package/dist/plugins-enterprise-client.d.ts +13 -0
  56. package/dist/plugins-enterprise-client.mjs +1 -0
  57. package/dist/plugins-enterprise.d.mts +1668 -0
  58. package/dist/plugins-enterprise.d.ts +1668 -0
  59. package/dist/plugins-enterprise.mjs +1 -0
  60. package/dist/plugins.d.mts +167 -2
  61. package/dist/plugins.d.ts +167 -2
  62. package/dist/plugins.mjs +1 -22
  63. package/dist/schema.d.mts +39 -39
  64. package/dist/schema.d.ts +39 -39
  65. package/dist/schema.mjs +1 -62
  66. package/dist/sql-store.d.mts +43 -28
  67. package/dist/sql-store.d.ts +43 -28
  68. package/dist/sql-store.mjs +1 -184
  69. package/dist/store.d.mts +65 -47
  70. package/dist/store.d.ts +65 -47
  71. package/dist/store.mjs +1 -183
  72. package/dist/turnstile-middleware.d.mts +73 -55
  73. package/dist/turnstile-middleware.d.ts +73 -55
  74. package/dist/turnstile-middleware.mjs +1 -45
  75. package/dist/turnstile.d.mts +42 -59
  76. package/dist/turnstile.d.ts +42 -59
  77. package/dist/turnstile.mjs +1 -61
  78. package/package.json +48 -6
  79. package/dist/packem_shared/DEFAULT_AUTH_BASE_PATH-DjcUWEQl.mjs +0 -11
  80. package/dist/packem_shared/LunoraAuthAdminError-D4L7n6gN.mjs +0 -510
  81. package/dist/packem_shared/compileMigrationsSql-B9bj-mJv.mjs +0 -29
  82. package/dist/packem_shared/create-auth.d-Mwhb4gSc.d.mts +0 -128
  83. package/dist/packem_shared/create-auth.d-Mwhb4gSc.d.ts +0 -128
  84. package/dist/packem_shared/createAuth-BVMMllTm.mjs +0 -87
  85. package/dist/packem_shared/sessionPresets-Dwwd74_J.mjs +0 -38
package/dist/store.d.ts CHANGED
@@ -2,10 +2,10 @@ import { CustomAdapter } from 'better-auth/adapters';
2
2
  /** A stored auth row — an opaque bag of columns keyed by better-auth field name. */
3
3
  type AuthRow = Record<string, unknown>;
4
4
  /**
5
- * One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
6
- * own method signature (rather than re-declared) so a better-auth change to the
7
- * clause shape surfaces as a compile error here, not a silent mis-match.
8
- */
5
+ * One normalized better-auth where clause. Derived from {@link CustomAdapter}'s
6
+ * own method signature (rather than re-declared) so a better-auth change to the
7
+ * clause shape surfaces as a compile error here, not a silent mis-match.
8
+ */
9
9
  type AuthWhereClause = NonNullable<Parameters<CustomAdapter["findOne"]>[0]["where"]>[number];
10
10
  /** Read query handed to {@link AuthStore.read}: a where filter plus optional sort/window. */
11
11
  interface AuthQuery {
@@ -18,48 +18,48 @@ interface AuthQuery {
18
18
  where: ReadonlyArray<AuthWhereClause>;
19
19
  }
20
20
  /**
21
- * The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
22
- * seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
23
- * the global (D1) auth tables that `authTables(...)` generates, and better-auth's
24
- * reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
25
- * instead of better-auth's own adapter. Field names and table (`model`) names are
26
- * already the database names better-auth resolved from the schema, so a store
27
- * passes them straight through.
28
- *
29
- * {@link createMemoryAuthStore} is a reference in-memory implementation (also
30
- * what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
31
- */
21
+ * The minimal table-addressed store the `lunoraAuthAdapter` drives. This is the
22
+ * seam a Lunora runtime binds to its ORM: back each method with `ctx.db` over
23
+ * the global (D1) auth tables that `authTables(...)` generates, and better-auth's
24
+ * reads/writes flow through Lunora's data layer (triggers, aggregates, OCC)
25
+ * instead of better-auth's own adapter. Field names and table (`model`) names are
26
+ * already the database names better-auth resolved from the schema, so a store
27
+ * passes them straight through.
28
+ *
29
+ * {@link createMemoryAuthStore} is a reference in-memory implementation (also
30
+ * what the tests run better-auth against); `createSqlAuthStore` is the SQL one.
31
+ */
32
32
  interface AuthStore {
33
33
  /**
34
- * Atomically delete **at most one** row in `model` matching `where` and
35
- * return it (or `undefined` if none matched). Backs better-auth's
36
- * single-use-token consume (OTP / magic-link / email-verification /
37
- * password-reset): implementing it natively — one round trip that finds and
38
- * deletes in a single statement — closes the read-then-delete race the
39
- * factory's `findMany` + `deleteMany` fallback would otherwise leave open.
40
- */
34
+ * Atomically delete **at most one** row in `model` matching `where` and
35
+ * return it (or `undefined` if none matched). Backs better-auth's
36
+ * single-use-token consume (OTP / magic-link / email-verification /
37
+ * password-reset): implementing it natively — one round trip that finds and
38
+ * deletes in a single statement — closes the read-then-delete race the
39
+ * factory's `findMany` + `deleteMany` fallback would otherwise leave open.
40
+ */
41
41
  consumeOne: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<AuthRow | undefined>;
42
42
  /** Count rows in `model` matching `where` (empty `where` = all rows). */
43
43
  count: (model: string, where: ReadonlyArray<AuthWhereClause>) => Promise<number>;
44
44
  /** Insert `data` into `model`; return the stored row (the adapter pre-fills `id`). */
45
45
  create: (model: string, data: AuthRow) => Promise<AuthRow>;
46
46
  /**
47
- * Atomically apply signed numeric deltas to **at most one** row in `model`
48
- * matching `where`, then return the updated row (or `undefined` if the guard
49
- * matched none). For each `increment` entry it applies `field = field + delta`
50
- * (a negative delta decrements); the optional `set` map assigns absolute
51
- * values in the same step. The `where` clause is both selector **and** guard
52
- * — comparison operators are honoured, so a guard like
53
- * `{ field: "count", operator: "lt", value: max }` only mutates the row while
54
- * it still satisfies the predicate.
55
- *
56
- * Backs better-auth's durable (`storage: "database"`) rate limiter, whose
57
- * counter rides these tables. Implementing it natively — one statement that
58
- * guards, increments, and returns — gives the **one-winner-across-isolates**
59
- * guarantee the read-then-update fallback cannot: on Workers two concurrent
60
- * requests would otherwise both read `count=4` and both write `5`, letting a
61
- * `max` of 5 pass 6+. Same race-closing rationale as {@link AuthStore.consumeOne}.
62
- */
47
+ * Atomically apply signed numeric deltas to **at most one** row in `model`
48
+ * matching `where`, then return the updated row (or `undefined` if the guard
49
+ * matched none). For each `increment` entry it applies `field = field + delta`
50
+ * (a negative delta decrements); the optional `set` map assigns absolute
51
+ * values in the same step. The `where` clause is both selector **and** guard
52
+ * — comparison operators are honoured, so a guard like
53
+ * `{ field: "count", operator: "lt", value: max }` only mutates the row while
54
+ * it still satisfies the predicate.
55
+ *
56
+ * Backs better-auth's durable (`storage: "database"`) rate limiter, whose
57
+ * counter rides these tables. Implementing it natively — one statement that
58
+ * guards, increments, and returns — gives the **one-winner-across-isolates**
59
+ * guarantee the read-then-update fallback cannot: on Workers two concurrent
60
+ * requests would otherwise both read `count=4` and both write `5`, letting a
61
+ * `max` of 5 pass 6+. Same race-closing rationale as {@link AuthStore.consumeOne}.
62
+ */
63
63
  incrementOne: (model: string, where: ReadonlyArray<AuthWhereClause>, increment: Record<string, number>, set?: AuthRow) => Promise<AuthRow | undefined>;
64
64
  /** Read rows from `model` honouring the filter/sort/window in `query`. */
65
65
  read: (model: string, query: AuthQuery) => Promise<AuthRow[]>;
@@ -69,16 +69,34 @@ interface AuthStore {
69
69
  update: (model: string, where: ReadonlyArray<AuthWhereClause>, values: AuthRow) => Promise<AuthRow[]>;
70
70
  }
71
71
  /**
72
- * Evaluate a better-auth where clause list against a row. Clauses fold
73
- * left-to-right by their `connector` (`AND` by default, `OR` when set) — the
74
- * same precedence better-auth's own adapters use. An empty list matches every
75
- * row. Exported so any in-memory-style {@link AuthStore} can reuse it.
76
- */
72
+ * Evaluate a better-auth where clause list against a row.
73
+ *
74
+ * better-auth hands an adapter a FLAT list in which each clause carries its own
75
+ * `connector` (`AND` by default). Every persistent adapter it ships resolves that
76
+ * by PARTITIONING: `AND(and-clauses) AND OR(or-clauses)`. The OR clauses are
77
+ * alternatives among themselves, never an escape hatch from the AND clauses —
78
+ * `@better-auth/kysely-adapter` pushes each group into its own `.where()` (two
79
+ * `.where()` calls are ANDed), `@better-auth/drizzle-adapter` ends in
80
+ * `and(andClause, orClause)`, and `@better-auth/prisma-adapter` emits
81
+ * `{ AND: […], OR: […] }`, which Prisma also ANDs.
82
+ *
83
+ * This used to fold the list left-associatively instead, so `[A, B(OR), C(OR)]`
84
+ * meant `A OR B OR C` — strictly BROADER than any of them. On a credential lookup
85
+ * that is an authentication bypass in shape: a row failing the primary condition
86
+ * is still returned because a secondary one matched. `@better-auth/memory-adapter`
87
+ * does fold left, but it is the only one and it re-evaluates `where[0]` inside the
88
+ * same loop; the persistent adapters are the contract worth mirroring, and they
89
+ * are also what a plugin author will have tested against.
90
+ *
91
+ * An empty list matches every row, and a list with no OR clause is unaffected —
92
+ * which is every clause list better-auth 1.7.1 itself builds. Exported so any
93
+ * in-memory-style {@link AuthStore} can reuse it.
94
+ */
77
95
  declare const matchesWhere: (row: AuthRow, where: ReadonlyArray<AuthWhereClause>) => boolean;
78
96
  /**
79
- * Reference in-memory {@link AuthStore} — used to run better-auth end to end in
80
- * tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
81
- * fulfils. Not for production (state is per-instance and non-durable).
82
- */
97
+ * Reference in-memory {@link AuthStore} — used to run better-auth end to end in
98
+ * tests, and a worked example of the contract a Lunora-`ctx.db`-backed store
99
+ * fulfils. Not for production (state is per-instance and non-durable).
100
+ */
83
101
  declare const createMemoryAuthStore: () => AuthStore;
84
102
  export { AuthQuery, type AuthRow, AuthStore, type AuthWhereClause, createMemoryAuthStore, matchesWhere };
package/dist/store.mjs CHANGED
@@ -1,183 +1 @@
1
- const asString = (value) => typeof value === "string" ? value : void 0;
2
- const isNullish = (value) => value === void 0 || value === null;
3
- const looseEquals = (left, right, insensitive) => {
4
- if (insensitive) {
5
- const a = asString(left);
6
- const b = asString(right);
7
- if (a !== void 0 && b !== void 0) {
8
- return a.toLowerCase() === b.toLowerCase();
9
- }
10
- }
11
- return left === right;
12
- };
13
- const matchesPattern = (cell, value, operator, insensitive) => {
14
- const haystack = asString(cell);
15
- const needle = asString(value);
16
- if (haystack === void 0 || needle === void 0) {
17
- return false;
18
- }
19
- const [a, b] = insensitive ? [haystack.toLowerCase(), needle.toLowerCase()] : [haystack, needle];
20
- if (operator === "contains") {
21
- return a.includes(b);
22
- }
23
- return operator === "starts_with" ? a.startsWith(b) : a.endsWith(b);
24
- };
25
- const evaluateClause = (row, clause) => {
26
- const { field, mode, operator, value } = clause;
27
- const cell = row[field];
28
- const insensitive = mode === "insensitive";
29
- switch (operator) {
30
- case "contains":
31
- case "ends_with":
32
- case "starts_with": {
33
- return matchesPattern(cell, value, operator, insensitive);
34
- }
35
- case "gt": {
36
- return !isNullish(value) && cell > value;
37
- }
38
- case "gte": {
39
- return !isNullish(value) && cell >= value;
40
- }
41
- case "in": {
42
- return Array.isArray(value) && value.some((entry) => looseEquals(cell, entry, insensitive));
43
- }
44
- case "lt": {
45
- return !isNullish(value) && cell < value;
46
- }
47
- case "lte": {
48
- return !isNullish(value) && cell <= value;
49
- }
50
- case "ne": {
51
- return !looseEquals(cell, value, insensitive);
52
- }
53
- case "not_in": {
54
- return Array.isArray(value) && !value.some((entry) => looseEquals(cell, entry, insensitive));
55
- }
56
- // "eq" and the default: null-aware equality.
57
- default: {
58
- return isNullish(value) ? isNullish(cell) : looseEquals(cell, value, insensitive);
59
- }
60
- }
61
- };
62
- const compareCells = (a, b) => {
63
- if (typeof a === "string" && typeof b === "string") {
64
- return a.localeCompare(b);
65
- }
66
- const left = Number(a);
67
- const right = Number(b);
68
- if (left < right) {
69
- return -1;
70
- }
71
- return left > right ? 1 : 0;
72
- };
73
- const sortRows = (rows, sortBy) => {
74
- const direction = sortBy.direction === "asc" ? 1 : -1;
75
- return rows.toSorted((left, right) => {
76
- const a = left[sortBy.field];
77
- const b = right[sortBy.field];
78
- if (isNullish(a) && isNullish(b)) {
79
- return 0;
80
- }
81
- if (isNullish(a)) {
82
- return -direction;
83
- }
84
- if (isNullish(b)) {
85
- return direction;
86
- }
87
- return compareCells(a, b) * direction;
88
- });
89
- };
90
- const matchesWhere = (row, where) => {
91
- let result = true;
92
- for (const [index, clause] of where.entries()) {
93
- const clauseResult = evaluateClause(row, clause);
94
- if (index === 0) {
95
- result = clauseResult;
96
- } else if (clause.connector === "OR") {
97
- result = result || clauseResult;
98
- } else {
99
- result = result && clauseResult;
100
- }
101
- }
102
- return result;
103
- };
104
- const createMemoryAuthStore = () => {
105
- const tables = /* @__PURE__ */ new Map();
106
- const tableOf = (model) => {
107
- const existing = tables.get(model);
108
- if (existing) {
109
- return existing;
110
- }
111
- const created = [];
112
- tables.set(model, created);
113
- return created;
114
- };
115
- return {
116
- consumeOne: (model, where) => {
117
- const table = tableOf(model);
118
- const index = table.findIndex((row2) => matchesWhere(row2, where));
119
- if (index === -1) {
120
- return Promise.resolve(void 0);
121
- }
122
- const [row] = table.splice(index, 1);
123
- return Promise.resolve(row ? { ...row } : void 0);
124
- },
125
- count: (model, where) => Promise.resolve(tableOf(model).filter((row) => matchesWhere(row, where)).length),
126
- create: (model, data) => {
127
- const row = { ...data };
128
- tableOf(model).push(row);
129
- return Promise.resolve({ ...row });
130
- },
131
- incrementOne: (model, where, increment, set) => {
132
- const row = tableOf(model).find((candidate) => matchesWhere(candidate, where));
133
- if (!row) {
134
- return Promise.resolve(void 0);
135
- }
136
- for (const [field, delta] of Object.entries(increment)) {
137
- row[field] = (typeof row[field] === "number" ? row[field] : 0) + delta;
138
- }
139
- if (set) {
140
- Object.assign(row, set);
141
- }
142
- return Promise.resolve({ ...row });
143
- },
144
- read: (model, query) => {
145
- let rows = tableOf(model).filter((row) => matchesWhere(row, query.where));
146
- if (query.sortBy) {
147
- rows = sortRows(rows, query.sortBy);
148
- }
149
- if (query.offset) {
150
- rows = rows.slice(query.offset);
151
- }
152
- if (query.limit !== void 0) {
153
- rows = rows.slice(0, query.limit);
154
- }
155
- return Promise.resolve(
156
- rows.map((row) => {
157
- return { ...row };
158
- })
159
- );
160
- },
161
- remove: (model, where) => {
162
- const table = tableOf(model);
163
- const kept = table.filter((row) => !matchesWhere(row, where));
164
- const removed = table.length - kept.length;
165
- table.length = 0;
166
- table.push(...kept);
167
- return Promise.resolve(removed);
168
- },
169
- update: (model, where, values) => {
170
- const matched = tableOf(model).filter((row) => matchesWhere(row, where));
171
- for (const row of matched) {
172
- Object.assign(row, values);
173
- }
174
- return Promise.resolve(
175
- matched.map((row) => {
176
- return { ...row };
177
- })
178
- );
179
- }
180
- };
181
- };
182
-
183
- export { createMemoryAuthStore, matchesWhere };
1
+ const d=i=>typeof i=="string"?i:void 0,a=i=>i==null,f=(i,o,n)=>{if(n){const s=d(i),e=d(o);if(s!==void 0&&e!==void 0)return s.toLowerCase()===e.toLowerCase()}return i===o},v=(i,o,n,s)=>{const e=d(i),t=d(o);if(e===void 0||t===void 0)return!1;const[r,c]=s?[e.toLowerCase(),t.toLowerCase()]:[e,t];return n==="contains"?r.includes(c):n==="starts_with"?r.startsWith(c):r.endsWith(c)},m=(i,o)=>{const{field:n,mode:s,operator:e,value:t}=o,r=i[n],c=s==="insensitive";switch(e){case"contains":case"ends_with":case"starts_with":return v(r,t,e,c);case"gt":return!a(t)&&r>t;case"gte":return!a(t)&&r>=t;case"in":return Array.isArray(t)&&t.some(u=>f(r,u,c));case"lt":return!a(t)&&r<t;case"lte":return!a(t)&&r<=t;case"ne":return!f(r,t,c);case"not_in":return Array.isArray(t)&&!t.some(u=>f(r,u,c));default:return a(t)?a(r):f(r,t,c)}},h=(i,o)=>{if(typeof i=="string"&&typeof o=="string")return i.localeCompare(o);const n=Number(i),s=Number(o);return n<s?-1:n>s?1:0},g=(i,o)=>{const n=o.direction==="asc"?1:-1;return i.toSorted((s,e)=>{const t=s[o.field],r=e[o.field];return a(t)&&a(r)?0:a(t)?-n:a(r)?n:h(t,r)*n})},l=(i,o)=>{const n=o.filter(e=>e.connector==="OR");return o.filter(e=>e.connector!=="OR").every(e=>m(i,e))&&(n.length===0||n.some(e=>m(i,e)))},p=()=>{const i=new Map,o=n=>{const s=i.get(n);if(s)return s;const e=[];return i.set(n,e),e};return{consumeOne:(n,s)=>{const e=o(n),t=e.findIndex(c=>l(c,s));if(t===-1)return Promise.resolve(void 0);const[r]=e.splice(t,1);return Promise.resolve(r?{...r}:void 0)},count:(n,s)=>Promise.resolve(o(n).filter(e=>l(e,s)).length),create:(n,s)=>{const e={...s};return o(n).push(e),Promise.resolve({...e})},incrementOne:(n,s,e,t)=>{const r=o(n).find(c=>l(c,s));if(!r)return Promise.resolve(void 0);for(const[c,u]of Object.entries(e))r[c]=(typeof r[c]=="number"?r[c]:0)+u;return t&&Object.assign(r,t),Promise.resolve({...r})},read:(n,s)=>{let e=o(n).filter(t=>l(t,s.where));return s.sortBy&&(e=g(e,s.sortBy)),s.offset&&(e=e.slice(s.offset)),s.limit!==void 0&&(e=e.slice(0,s.limit)),Promise.resolve(e.map(t=>({...t})))},remove:(n,s)=>{const e=o(n),t=e.filter(c=>!l(c,s)),r=e.length-t.length;return e.length=0,e.push(...t),Promise.resolve(r)},update:(n,s,e)=>{const t=o(n).filter(r=>l(r,s));for(const r of t)Object.assign(r,e);return Promise.resolve(t.map(r=>({...r})))}}};export{p as createMemoryAuthStore,l as matchesWhere};
@@ -2,79 +2,97 @@ import { Middleware } from '@lunora/server';
2
2
  import { FetchLike, TurnstileVerifyResult } from "./turnstile.mjs";
3
3
  interface VerifyTurnstileMiddlewareOptions<Context> {
4
4
  /**
5
- * Assert the widget `action` the token was solved for (forwarded to
6
- * {@link verifyTurnstile}). When set and the siteverify response's `action`
7
- * does not match, the verdict is treated as a failure (403). Optional.
8
- */
5
+ * Assert the widget `action` the token was solved for (forwarded to
6
+ * {@link verifyTurnstile}). When set and the siteverify response's `action`
7
+ * does not match, the verdict is treated as a failure (403). Optional.
8
+ */
9
9
  expectedAction?: string;
10
10
  /**
11
- * Assert the `hostname` the challenge was solved on (forwarded to
12
- * {@link verifyTurnstile}). When set and the siteverify response's `hostname`
13
- * does not match, the verdict is treated as a failure (403). Set this when a
14
- * single secret/sitekey is shared across multiple domains to stop a token
15
- * harvested on one origin being replayed against this procedure. Optional.
16
- */
11
+ * Assert the `hostname` the challenge was solved on (forwarded to
12
+ * {@link verifyTurnstile}). When set and the siteverify response's `hostname`
13
+ * does not match, the verdict is treated as a failure (403). Set this when a
14
+ * single secret/sitekey is shared across multiple domains to stop a token
15
+ * harvested on one origin being replayed against this procedure. Optional.
16
+ */
17
17
  expectedHostname?: string;
18
18
  /**
19
- * Behavior when the siteverify call itself throws (network error, non-2xx).
20
- * Defaults to `false` (**fail closed**: reject with 403). Set `true` only
21
- * when degraded availability is preferable to denying traffic — a failing
22
- * siteverify then admits every request. Mirrors `@lunora/ratelimit`'s
23
- * `rateLimit` failure policy.
24
- */
19
+ * Behavior when the siteverify call itself throws (network error, non-2xx).
20
+ * Defaults to `false` (**fail closed**: reject with 403). Set `true` only
21
+ * when degraded availability is preferable to denying traffic — a failing
22
+ * siteverify then admits every request. Mirrors `@lunora/ratelimit`'s
23
+ * `rateLimit` failure policy.
24
+ */
25
25
  failOpen?: boolean;
26
26
  /**
27
- * Inject a `fetch` implementation (forwarded to {@link verifyTurnstile}).
28
- * Primarily for tests.
29
- */
27
+ * Inject a `fetch` implementation (forwarded to {@link verifyTurnstile}).
28
+ * Primarily for tests.
29
+ */
30
30
  fetch?: FetchLike;
31
31
  /** Override the error message thrown on a failed verdict. */
32
32
  message?: string;
33
33
  /**
34
- * Selector that pulls the visitor IP from `ctx` (the procedure context has
35
- * no raw `Headers`, so this must come from `args`/ctx). Optional.
36
- */
34
+ * Selector that pulls the visitor IP off `ctx` — typically `ctx.ip`, or
35
+ * `ctx.args.ip` when the caller sends it (the procedure context has no raw
36
+ * `Headers`). Optional.
37
+ */
37
38
  remoteip?: (context: Context) => string | undefined;
38
39
  /** Your Turnstile secret key (the `TURNSTILE_SECRET_KEY` env var). */
39
40
  secret: string;
40
41
  /**
41
- * Selector that pulls the `cf-turnstile-response` token from `ctx`. The
42
- * procedure context carries only the resolved identity, **not** the raw
43
- * inbound `Headers` (see `withAuthPlugins` in `./middleware`), so the token
44
- * must travel in the function `args` and be read out here.
45
- */
42
+ * Selector that pulls the `cf-turnstile-response` token off `ctx`. The
43
+ * procedure context carries only the resolved identity, **not** the raw
44
+ * inbound `Headers` (see `withAuthPlugins` in `./middleware`), so the token
45
+ * travels in the function `args` — which the builder surfaces to middleware
46
+ * as `ctx.args` (validated, frozen):
47
+ *
48
+ * ```ts
49
+ * export const submit = mutation
50
+ * .input({ message: v.string(), turnstileToken: v.string() })
51
+ * .use(verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (ctx) => ctx.args.turnstileToken }))
52
+ * .mutation(async ({ args, ctx }) => { … });
53
+ * ```
54
+ */
46
55
  token: (context: Context) => string | undefined;
47
56
  /**
48
- * Extra predicate run on a `success: true` verdict (after the built-in
49
- * `expectedHostname`/`expectedAction` checks). Return `false` to reject the
50
- * request with 403 — use it for any custom replay/abuse guard that needs the
51
- * full verdict (e.g. matching `cdata`, or one hostname out of an allow-list).
52
- */
57
+ * Extra predicate run on a `success: true` verdict (after the built-in
58
+ * `expectedHostname`/`expectedAction` checks). Return `false` to reject the
59
+ * request with 403 — use it for any custom replay/abuse guard that needs the
60
+ * full verdict (e.g. matching `cdata`, or one hostname out of an allow-list).
61
+ */
53
62
  validate?: (result: TurnstileVerifyResult) => boolean;
54
63
  }
55
64
  /**
56
- * Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
57
- * handler runs. Attach it with `.use()`. It reads the token (and optional IP)
58
- * from `ctx` via the provided selectors — because the procedure context does
59
- * not carry raw request headers, the token must be passed through the function
60
- * `args`. To gate the better-auth sign-in/sign-up flow itself, prefer
61
- * better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
62
- * — this middleware is for non-auth Lunora procedures.
63
- *
64
- * On a `success: false` verdict (or a missing token) it throws a structural
65
- * `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
66
- * the runtime maps it to the matching RPC/HTTP status without any runtime
67
- * import of `@lunora/server` (the `Middleware` import is type-only).
68
- *
69
- * **Failure policy:** if the siteverify call itself throws, the middleware
70
- * **fails closed by default** (logs and rejects with 403). Pass
71
- * `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
72
- *
73
- * **Cross-origin replay:** a token solved on one origin can be replayed against
74
- * a different endpoint when a single secret/sitekey is shared across multiple
75
- * domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
76
- * `validate(result)` predicate) to assert the siteverify response's `hostname`
77
- * /`action` and reject mismatches with 403.
78
- */
65
+ * Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
66
+ * handler runs. Attach it with `.use()`. It reads the token (and optional IP)
67
+ * from `ctx` via the provided selectors — because the procedure context does
68
+ * not carry raw request headers, the token is passed through the function
69
+ * `args` and read back as `ctx.args.<field>` (the builder surfaces the
70
+ * VALIDATED args on the middleware context, frozen). To gate the better-auth
71
+ * sign-in/sign-up flow itself, prefer
72
+ * better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
73
+ * — this middleware is for non-auth Lunora procedures.
74
+ *
75
+ * On a `success: false` verdict (or a missing token) it throws a structural
76
+ * `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
77
+ * the runtime maps it to the matching RPC/HTTP status without any import of
78
+ * the error machinery from `@lunora/server`.
79
+ *
80
+ * The middleware is tagged PER-DISPATCH: a Turnstile token is single-use, so
81
+ * verifying one is an effect of the request that a memoized response cannot
82
+ * stand in for. The builder hoists the mark onto the registered function and
83
+ * the generated `isCacheableQuery` keeps such a query out of the reactive
84
+ * cache — a hit answers without running this chain, so one solved token would
85
+ * otherwise admit every request the memo served.
86
+ *
87
+ * **Failure policy:** if the siteverify call itself throws, the middleware
88
+ * **fails closed by default** (logs and rejects with 403). Pass
89
+ * `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
90
+ *
91
+ * **Cross-origin replay:** a token solved on one origin can be replayed against
92
+ * a different endpoint when a single secret/sitekey is shared across multiple
93
+ * domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
94
+ * `validate(result)` predicate) to assert the siteverify response's `hostname`
95
+ * /`action` and reject mismatches with 403.
96
+ */
79
97
  declare const verifyTurnstileMiddleware: <Context>(options: VerifyTurnstileMiddlewareOptions<Context>) => Middleware<Context, Context>;
80
98
  export { type VerifyTurnstileMiddlewareOptions, verifyTurnstileMiddleware };
@@ -2,79 +2,97 @@ import { Middleware } from '@lunora/server';
2
2
  import { FetchLike, TurnstileVerifyResult } from "./turnstile.js";
3
3
  interface VerifyTurnstileMiddlewareOptions<Context> {
4
4
  /**
5
- * Assert the widget `action` the token was solved for (forwarded to
6
- * {@link verifyTurnstile}). When set and the siteverify response's `action`
7
- * does not match, the verdict is treated as a failure (403). Optional.
8
- */
5
+ * Assert the widget `action` the token was solved for (forwarded to
6
+ * {@link verifyTurnstile}). When set and the siteverify response's `action`
7
+ * does not match, the verdict is treated as a failure (403). Optional.
8
+ */
9
9
  expectedAction?: string;
10
10
  /**
11
- * Assert the `hostname` the challenge was solved on (forwarded to
12
- * {@link verifyTurnstile}). When set and the siteverify response's `hostname`
13
- * does not match, the verdict is treated as a failure (403). Set this when a
14
- * single secret/sitekey is shared across multiple domains to stop a token
15
- * harvested on one origin being replayed against this procedure. Optional.
16
- */
11
+ * Assert the `hostname` the challenge was solved on (forwarded to
12
+ * {@link verifyTurnstile}). When set and the siteverify response's `hostname`
13
+ * does not match, the verdict is treated as a failure (403). Set this when a
14
+ * single secret/sitekey is shared across multiple domains to stop a token
15
+ * harvested on one origin being replayed against this procedure. Optional.
16
+ */
17
17
  expectedHostname?: string;
18
18
  /**
19
- * Behavior when the siteverify call itself throws (network error, non-2xx).
20
- * Defaults to `false` (**fail closed**: reject with 403). Set `true` only
21
- * when degraded availability is preferable to denying traffic — a failing
22
- * siteverify then admits every request. Mirrors `@lunora/ratelimit`'s
23
- * `rateLimit` failure policy.
24
- */
19
+ * Behavior when the siteverify call itself throws (network error, non-2xx).
20
+ * Defaults to `false` (**fail closed**: reject with 403). Set `true` only
21
+ * when degraded availability is preferable to denying traffic — a failing
22
+ * siteverify then admits every request. Mirrors `@lunora/ratelimit`'s
23
+ * `rateLimit` failure policy.
24
+ */
25
25
  failOpen?: boolean;
26
26
  /**
27
- * Inject a `fetch` implementation (forwarded to {@link verifyTurnstile}).
28
- * Primarily for tests.
29
- */
27
+ * Inject a `fetch` implementation (forwarded to {@link verifyTurnstile}).
28
+ * Primarily for tests.
29
+ */
30
30
  fetch?: FetchLike;
31
31
  /** Override the error message thrown on a failed verdict. */
32
32
  message?: string;
33
33
  /**
34
- * Selector that pulls the visitor IP from `ctx` (the procedure context has
35
- * no raw `Headers`, so this must come from `args`/ctx). Optional.
36
- */
34
+ * Selector that pulls the visitor IP off `ctx` — typically `ctx.ip`, or
35
+ * `ctx.args.ip` when the caller sends it (the procedure context has no raw
36
+ * `Headers`). Optional.
37
+ */
37
38
  remoteip?: (context: Context) => string | undefined;
38
39
  /** Your Turnstile secret key (the `TURNSTILE_SECRET_KEY` env var). */
39
40
  secret: string;
40
41
  /**
41
- * Selector that pulls the `cf-turnstile-response` token from `ctx`. The
42
- * procedure context carries only the resolved identity, **not** the raw
43
- * inbound `Headers` (see `withAuthPlugins` in `./middleware`), so the token
44
- * must travel in the function `args` and be read out here.
45
- */
42
+ * Selector that pulls the `cf-turnstile-response` token off `ctx`. The
43
+ * procedure context carries only the resolved identity, **not** the raw
44
+ * inbound `Headers` (see `withAuthPlugins` in `./middleware`), so the token
45
+ * travels in the function `args` — which the builder surfaces to middleware
46
+ * as `ctx.args` (validated, frozen):
47
+ *
48
+ * ```ts
49
+ * export const submit = mutation
50
+ * .input({ message: v.string(), turnstileToken: v.string() })
51
+ * .use(verifyTurnstileMiddleware({ secret: env.TURNSTILE_SECRET_KEY, token: (ctx) => ctx.args.turnstileToken }))
52
+ * .mutation(async ({ args, ctx }) => { … });
53
+ * ```
54
+ */
46
55
  token: (context: Context) => string | undefined;
47
56
  /**
48
- * Extra predicate run on a `success: true` verdict (after the built-in
49
- * `expectedHostname`/`expectedAction` checks). Return `false` to reject the
50
- * request with 403 — use it for any custom replay/abuse guard that needs the
51
- * full verdict (e.g. matching `cdata`, or one hostname out of an allow-list).
52
- */
57
+ * Extra predicate run on a `success: true` verdict (after the built-in
58
+ * `expectedHostname`/`expectedAction` checks). Return `false` to reject the
59
+ * request with 403 — use it for any custom replay/abuse guard that needs the
60
+ * full verdict (e.g. matching `cdata`, or one hostname out of an allow-list).
61
+ */
53
62
  validate?: (result: TurnstileVerifyResult) => boolean;
54
63
  }
55
64
  /**
56
- * Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
57
- * handler runs. Attach it with `.use()`. It reads the token (and optional IP)
58
- * from `ctx` via the provided selectors — because the procedure context does
59
- * not carry raw request headers, the token must be passed through the function
60
- * `args`. To gate the better-auth sign-in/sign-up flow itself, prefer
61
- * better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
62
- * — this middleware is for non-auth Lunora procedures.
63
- *
64
- * On a `success: false` verdict (or a missing token) it throws a structural
65
- * `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
66
- * the runtime maps it to the matching RPC/HTTP status without any runtime
67
- * import of `@lunora/server` (the `Middleware` import is type-only).
68
- *
69
- * **Failure policy:** if the siteverify call itself throws, the middleware
70
- * **fails closed by default** (logs and rejects with 403). Pass
71
- * `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
72
- *
73
- * **Cross-origin replay:** a token solved on one origin can be replayed against
74
- * a different endpoint when a single secret/sitekey is shared across multiple
75
- * domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
76
- * `validate(result)` predicate) to assert the siteverify response's `hostname`
77
- * /`action` and reject mismatches with 403.
78
- */
65
+ * Procedure middleware that enforces a Turnstile (CAPTCHA) check before the
66
+ * handler runs. Attach it with `.use()`. It reads the token (and optional IP)
67
+ * from `ctx` via the provided selectors — because the procedure context does
68
+ * not carry raw request headers, the token is passed through the function
69
+ * `args` and read back as `ctx.args.<field>` (the builder surfaces the
70
+ * VALIDATED args on the middleware context, frozen). To gate the better-auth
71
+ * sign-in/sign-up flow itself, prefer
72
+ * better-auth's native `captcha` plugin (re-exported from `@lunora/auth/plugins`)
73
+ * — this middleware is for non-auth Lunora procedures.
74
+ *
75
+ * On a `success: false` verdict (or a missing token) it throws a structural
76
+ * `LunoraError` (`{ name: "LunoraError", code: "FORBIDDEN", status: 403 }`) —
77
+ * the runtime maps it to the matching RPC/HTTP status without any import of
78
+ * the error machinery from `@lunora/server`.
79
+ *
80
+ * The middleware is tagged PER-DISPATCH: a Turnstile token is single-use, so
81
+ * verifying one is an effect of the request that a memoized response cannot
82
+ * stand in for. The builder hoists the mark onto the registered function and
83
+ * the generated `isCacheableQuery` keeps such a query out of the reactive
84
+ * cache — a hit answers without running this chain, so one solved token would
85
+ * otherwise admit every request the memo served.
86
+ *
87
+ * **Failure policy:** if the siteverify call itself throws, the middleware
88
+ * **fails closed by default** (logs and rejects with 403). Pass
89
+ * `failOpen: true` to admit the request instead — mirrors `@lunora/ratelimit`.
90
+ *
91
+ * **Cross-origin replay:** a token solved on one origin can be replayed against
92
+ * a different endpoint when a single secret/sitekey is shared across multiple
93
+ * domains. Pass `expectedHostname` (and optionally `expectedAction`, or a custom
94
+ * `validate(result)` predicate) to assert the siteverify response's `hostname`
95
+ * /`action` and reject mismatches with 403.
96
+ */
79
97
  declare const verifyTurnstileMiddleware: <Context>(options: VerifyTurnstileMiddlewareOptions<Context>) => Middleware<Context, Context>;
80
98
  export { type VerifyTurnstileMiddlewareOptions, verifyTurnstileMiddleware };