@yielded/auth-persistence 0.1.0-beta.23 → 0.1.0-beta.25

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 (182) hide show
  1. package/README.md +11 -0
  2. package/dist/Adapter.d.mts +12 -6
  3. package/dist/Adapter.mjs +8 -3
  4. package/dist/OAuthPersistence.d.mts +87 -0
  5. package/dist/OAuthPersistence.mjs +125 -0
  6. package/dist/OAuthPersistence.mjs.map +1 -0
  7. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  8. package/dist/index.d.mts +3 -1
  9. package/dist/index.mjs +3 -1
  10. package/dist/internal/configuration.d.mts +1 -1
  11. package/dist/internal/configuration.mjs.map +1 -1
  12. package/dist/internal/email-kernel.d.mts +1 -1
  13. package/dist/internal/email-kernel.mjs +1 -1
  14. package/dist/internal/email-kernel.mjs.map +1 -1
  15. package/dist/internal/models/oauth-connected-model.d.mts +294 -0
  16. package/dist/internal/models/oauth-connected-model.mjs +22 -0
  17. package/dist/internal/models/oauth-connected-model.mjs.map +1 -0
  18. package/dist/internal/models/oauth-model.d.mts +467 -0
  19. package/dist/internal/models/oauth-model.mjs +49 -0
  20. package/dist/internal/models/oauth-model.mjs.map +1 -0
  21. package/dist/internal/models/password-model.d.mts +6 -48
  22. package/dist/internal/models/password-model.mjs +0 -1
  23. package/dist/internal/models/password-model.mjs.map +1 -1
  24. package/dist/internal/native-sql-table.d.mts +4 -0
  25. package/dist/internal/native-sql-table.mjs +7 -0
  26. package/dist/internal/native-sql-table.mjs.map +1 -1
  27. package/dist/internal/oauth/accounts.d.mts +8 -0
  28. package/dist/internal/oauth/accounts.mjs +379 -0
  29. package/dist/internal/oauth/accounts.mjs.map +1 -0
  30. package/dist/internal/oauth/connected-access.d.mts +9 -0
  31. package/dist/internal/oauth/connected-access.mjs +229 -0
  32. package/dist/internal/oauth/connected-access.mjs.map +1 -0
  33. package/dist/internal/oauth/connected-collection.d.mts +7 -0
  34. package/dist/internal/oauth/connected-collection.mjs +214 -0
  35. package/dist/internal/oauth/connected-collection.mjs.map +1 -0
  36. package/dist/internal/oauth/connected-custody.d.mts +6 -0
  37. package/dist/internal/oauth/connected-custody.mjs +165 -0
  38. package/dist/internal/oauth/connected-custody.mjs.map +1 -0
  39. package/dist/internal/oauth/connected-flow.d.mts +7 -0
  40. package/dist/internal/oauth/connected-flow.mjs +324 -0
  41. package/dist/internal/oauth/connected-flow.mjs.map +1 -0
  42. package/dist/internal/oauth/connected-input.mjs +122 -0
  43. package/dist/internal/oauth/connected-input.mjs.map +1 -0
  44. package/dist/internal/oauth/connected-keys.d.mts +2 -0
  45. package/dist/internal/oauth/connected-keys.mjs +22 -0
  46. package/dist/internal/oauth/connected-keys.mjs.map +1 -0
  47. package/dist/internal/oauth/connected-maintenance.d.mts +9 -0
  48. package/dist/internal/oauth/connected-maintenance.mjs +226 -0
  49. package/dist/internal/oauth/connected-maintenance.mjs.map +1 -0
  50. package/dist/internal/oauth/connected-management.d.mts +9 -0
  51. package/dist/internal/oauth/connected-management.mjs +243 -0
  52. package/dist/internal/oauth/connected-management.mjs.map +1 -0
  53. package/dist/internal/oauth/connected-reference.mjs +63 -0
  54. package/dist/internal/oauth/connected-reference.mjs.map +1 -0
  55. package/dist/internal/oauth/connected-settlement.d.mts +8 -0
  56. package/dist/internal/oauth/connected-settlement.mjs +141 -0
  57. package/dist/internal/oauth/connected-settlement.mjs.map +1 -0
  58. package/dist/internal/oauth/connected-sign-in.d.mts +10 -0
  59. package/dist/internal/oauth/connected-sign-in.mjs +281 -0
  60. package/dist/internal/oauth/connected-sign-in.mjs.map +1 -0
  61. package/dist/internal/oauth/connected-state.d.mts +14 -0
  62. package/dist/internal/oauth/connected-state.mjs +469 -0
  63. package/dist/internal/oauth/connected-state.mjs.map +1 -0
  64. package/dist/internal/oauth/connected-target.d.mts +21 -0
  65. package/dist/internal/oauth/connected-target.mjs +117 -0
  66. package/dist/internal/oauth/connected-target.mjs.map +1 -0
  67. package/dist/internal/oauth/flow.d.mts +5 -0
  68. package/dist/internal/oauth/flow.mjs +279 -0
  69. package/dist/internal/oauth/flow.mjs.map +1 -0
  70. package/dist/internal/oauth/input.mjs +127 -0
  71. package/dist/internal/oauth/input.mjs.map +1 -0
  72. package/dist/internal/oauth/owner.d.mts +10 -0
  73. package/dist/internal/oauth/owner.mjs +10 -0
  74. package/dist/internal/oauth/owner.mjs.map +1 -0
  75. package/dist/internal/oauth/query-compiler.d.mts +7 -0
  76. package/dist/internal/oauth/query-compiler.mjs +9 -0
  77. package/dist/internal/oauth/query-compiler.mjs.map +1 -0
  78. package/dist/internal/oauth/registration.d.mts +7 -0
  79. package/dist/internal/oauth/registration.mjs +393 -0
  80. package/dist/internal/oauth/registration.mjs.map +1 -0
  81. package/dist/internal/oauth/state.mjs +107 -0
  82. package/dist/internal/oauth/state.mjs.map +1 -0
  83. package/dist/internal/oauth/target.d.mts +31 -0
  84. package/dist/internal/oauth/target.mjs +273 -0
  85. package/dist/internal/oauth/target.mjs.map +1 -0
  86. package/dist/internal/oauth-kernel.d.mts +1423 -0
  87. package/dist/internal/oauth-kernel.mjs +187 -0
  88. package/dist/internal/oauth-kernel.mjs.map +1 -0
  89. package/dist/internal/oauth-proxy.d.mts +31 -0
  90. package/dist/internal/oauth-proxy.mjs +129 -0
  91. package/dist/internal/oauth-proxy.mjs.map +1 -0
  92. package/dist/internal/passkey/write-state.mjs +9 -1
  93. package/dist/internal/passkey/write-state.mjs.map +1 -1
  94. package/dist/internal/passkey-kernel.d.mts +9 -9
  95. package/dist/internal/{password-attempts.d.mts → password-credentials.d.mts} +3 -4
  96. package/dist/internal/password-credentials.mjs +121 -0
  97. package/dist/internal/password-credentials.mjs.map +1 -0
  98. package/dist/internal/password-kernel.d.mts +8 -24
  99. package/dist/internal/password-kernel.mjs +5 -43
  100. package/dist/internal/password-kernel.mjs.map +1 -1
  101. package/dist/internal/persistence.d.mts +1 -1
  102. package/dist/internal/persistence.mjs +2 -2
  103. package/dist/internal/persistence.mjs.map +1 -1
  104. package/dist/internal/query-operations.d.mts +2 -0
  105. package/dist/internal/session-kernel.d.mts +2 -2
  106. package/dist/internal/session-kernel.mjs +74 -18
  107. package/dist/internal/session-kernel.mjs.map +1 -1
  108. package/dist/internal/sql-oauth-connected-model.d.mts +29 -0
  109. package/dist/internal/sql-oauth-connected-model.mjs +7 -0
  110. package/dist/internal/sql-oauth-connected-model.mjs.map +1 -0
  111. package/dist/internal/sql-oauth-model.d.mts +60 -0
  112. package/dist/internal/sql-oauth-model.mjs +10 -0
  113. package/dist/internal/sql-oauth-model.mjs.map +1 -0
  114. package/dist/internal/sql-query.d.mts +12 -0
  115. package/dist/internal/sql-query.mjs +15 -18
  116. package/dist/internal/sql-query.mjs.map +1 -1
  117. package/dist/internal/sql-storage-validation.mjs +54 -0
  118. package/dist/internal/sql-storage-validation.mjs.map +1 -0
  119. package/dist/internal/storage-mapping.mjs +1 -37
  120. package/dist/internal/storage-mapping.mjs.map +1 -1
  121. package/dist/internal/storage-plans.d.mts +7 -0
  122. package/dist/internal/storage-plans.mjs +189 -0
  123. package/dist/internal/storage-plans.mjs.map +1 -0
  124. package/dist/internal/storage-tables.d.mts +0 -56
  125. package/dist/internal/storage-tables.mjs +0 -17
  126. package/dist/internal/storage-tables.mjs.map +1 -1
  127. package/dist/internal/storage-validation.d.mts +6 -1
  128. package/dist/internal/storage-validation.mjs +42 -1
  129. package/dist/internal/storage-validation.mjs.map +1 -1
  130. package/dist/internal/transaction-kernel.mjs +3 -2
  131. package/dist/internal/transaction-kernel.mjs.map +1 -1
  132. package/package.json +1 -1
  133. package/src/Adapter.ts +89 -5
  134. package/src/OAuthPersistence.ts +427 -0
  135. package/src/index.ts +3 -0
  136. package/src/internal/configuration.ts +1 -3
  137. package/src/internal/email-kernel.ts +2 -1
  138. package/src/internal/models/oauth-connected-model.ts +396 -0
  139. package/src/internal/models/oauth-model.ts +708 -0
  140. package/src/internal/models/password-model.ts +2 -57
  141. package/src/internal/native-sql-table.ts +14 -0
  142. package/src/internal/oauth/accounts.ts +839 -0
  143. package/src/internal/oauth/connected-access.ts +388 -0
  144. package/src/internal/oauth/connected-collection.ts +345 -0
  145. package/src/internal/oauth/connected-custody.ts +308 -0
  146. package/src/internal/oauth/connected-flow.ts +562 -0
  147. package/src/internal/oauth/connected-input.ts +147 -0
  148. package/src/internal/oauth/connected-keys.ts +31 -0
  149. package/src/internal/oauth/connected-maintenance.ts +379 -0
  150. package/src/internal/oauth/connected-management.ts +391 -0
  151. package/src/internal/oauth/connected-reference.ts +135 -0
  152. package/src/internal/oauth/connected-settlement.ts +278 -0
  153. package/src/internal/oauth/connected-sign-in.ts +472 -0
  154. package/src/internal/oauth/connected-state.ts +924 -0
  155. package/src/internal/oauth/connected-target.ts +307 -0
  156. package/src/internal/oauth/flow.ts +505 -0
  157. package/src/internal/oauth/input.ts +201 -0
  158. package/src/internal/oauth/owner.ts +22 -0
  159. package/src/internal/oauth/query-compiler.ts +15 -0
  160. package/src/internal/oauth/registration.ts +676 -0
  161. package/src/internal/oauth/state.ts +218 -0
  162. package/src/internal/oauth/target.ts +803 -0
  163. package/src/internal/oauth-kernel.ts +294 -0
  164. package/src/internal/oauth-proxy.ts +176 -0
  165. package/src/internal/passkey/write-state.ts +18 -4
  166. package/src/internal/password-credentials.ts +245 -0
  167. package/src/internal/password-kernel.ts +7 -99
  168. package/src/internal/persistence.ts +1 -1
  169. package/src/internal/query-operations.ts +2 -0
  170. package/src/internal/session-kernel.ts +143 -55
  171. package/src/internal/sql-oauth-connected-model.ts +145 -0
  172. package/src/internal/sql-oauth-model.ts +263 -0
  173. package/src/internal/sql-query.ts +16 -13
  174. package/src/internal/sql-storage-validation.ts +64 -0
  175. package/src/internal/storage-mapping.ts +0 -37
  176. package/src/internal/storage-plans.ts +218 -0
  177. package/src/internal/storage-tables.ts +0 -20
  178. package/src/internal/storage-validation.ts +58 -0
  179. package/src/internal/transaction-kernel.ts +6 -2
  180. package/dist/internal/password-attempts.mjs +0 -197
  181. package/dist/internal/password-attempts.mjs.map +0 -1
  182. package/src/internal/password-attempts.ts +0 -466
@@ -0,0 +1,263 @@
1
+ import * as Shared from "./models/oauth-model";
2
+ import type { TableModel } from "./query-operations";
3
+ import type { Fragment as SQL, Table } from "./sql-table";
4
+
5
+ export interface SqlTableModel<T extends Table = Table> extends TableModel {
6
+ readonly table: T;
7
+ }
8
+
9
+ export {
10
+ type OAuthFlowState,
11
+ type OAuthTupleState,
12
+ type OAuthRegistrationState,
13
+ type OAuthAction,
14
+ type OAuthEligibilityFact,
15
+ type OAuthD1Mapping,
16
+ requiredOAuthSignInConstraints,
17
+ requiredOAuthTupleConstraints,
18
+ requiredOAuthRegistrationConstraints,
19
+ requiredOAuthAccountsConstraints,
20
+ type OAuthRegistrationAuthority,
21
+ } from "./models/oauth-model";
22
+
23
+ /** The engine writes every critical column explicitly after applying the pure
24
+ * consumer encoder. Native instant values must roundtrip without precision loss. */
25
+ export type OAuthClock = Shared.OAuthClock<SQL>;
26
+
27
+ export type OAuthSubjectReadTable<S extends Table> = Shared.OAuthSubjectReadTable<
28
+ SqlTableModel<S>,
29
+ SQL
30
+ >;
31
+
32
+ export type OAuthSubjectTable<S extends Table> = Shared.OAuthSubjectTable<SqlTableModel<S>, SQL>;
33
+
34
+ export type OAuthOwnershipReadTable<O extends Table, N> = Shared.OAuthOwnershipReadTable<
35
+ SqlTableModel<O>,
36
+ N,
37
+ SQL
38
+ >;
39
+
40
+ export type OAuthOwnershipTable<O extends Table, N> = Shared.OAuthOwnershipTable<
41
+ SqlTableModel<O>,
42
+ N,
43
+ SQL
44
+ >;
45
+
46
+ export type OAuthCredentialReadTable<C extends Table> = Shared.OAuthCredentialReadTable<
47
+ SqlTableModel<C>,
48
+ SQL
49
+ >;
50
+
51
+ export type OAuthCredentialTable<C extends Table, N> = Shared.OAuthCredentialTable<
52
+ SqlTableModel<C>,
53
+ N,
54
+ SQL
55
+ >;
56
+
57
+ export type OAuthAuthorityReadTable<C extends Table> = Shared.OAuthAuthorityReadTable<
58
+ SqlTableModel<C>,
59
+ SQL
60
+ >;
61
+
62
+ export type OAuthAuthorityTable<C extends Table, N> = Shared.OAuthAuthorityTable<
63
+ SqlTableModel<C>,
64
+ N,
65
+ SQL
66
+ >;
67
+
68
+ /** Both purposes may use the same physical table. Shared tables share module/flow
69
+ * and module/command uniqueness. The snapshot has the exact purpose-specific core
70
+ * codec; terminal writes erase it, while identity, claim and retention columns remain. */
71
+ export type OAuthFlowTable<F extends Table> = Shared.OAuthFlowTable<SqlTableModel<F>>;
72
+
73
+ export type OAuthTupleAuthorityTable<T extends Table, N> = Shared.OAuthTupleAuthorityTable<
74
+ SqlTableModel<T>,
75
+ N
76
+ >;
77
+
78
+ export type OAuthRegistrationIntentTable<I extends Table> = Shared.OAuthRegistrationIntentTable<
79
+ SqlTableModel<I>
80
+ >;
81
+
82
+ export type OAuthRegistrationCommandTable<
83
+ R extends Table,
84
+ Registration,
85
+ > = Shared.OAuthRegistrationCommandTable<SqlTableModel<R>, Registration>;
86
+
87
+ export type OAuthUnlinkCommandTable<U extends Table> = Shared.OAuthUnlinkCommandTable<
88
+ SqlTableModel<U>
89
+ >;
90
+
91
+ /** Each descriptor reads only its own subject-scoped method rows. The native row
92
+ * projector and SQL condition must express the same installed method eligibility.
93
+ * All source/identifier/policy mutations advance subject securityRevision. D1 also
94
+ * guards exact selected IDs/versions and absence/count assumptions, never just a
95
+ * stale boolean. An over-limit graph fails closed rather than truncating. */
96
+ export type OAuthEligibilityTable<T extends Table, N> = Shared.OAuthEligibilityTable<
97
+ SqlTableModel<T>,
98
+ N,
99
+ SQL
100
+ >;
101
+
102
+ /** Authenticated command replay uses current subject status and this additional
103
+ * nonconsuming SQL policy. It never consumes a fresh action factor. */
104
+ export type OAuthMetadataPolicy<N> = Shared.OAuthMetadataPolicy<N, SQL>;
105
+
106
+ export type OAuthCleanupTable<T extends Table, N> = Shared.OAuthCleanupTable<
107
+ SqlTableModel<T>,
108
+ N,
109
+ SQL
110
+ >;
111
+
112
+ export type OAuthRegistrationEligibility<N> = Shared.OAuthRegistrationEligibility<N, SQL>;
113
+
114
+ export type OAuthSignInMapping<
115
+ S extends Table,
116
+ O extends Table,
117
+ C extends Table,
118
+ AC extends Table,
119
+ F extends Table,
120
+ N,
121
+ > = Shared.OAuthSignInMapping<
122
+ SqlTableModel<S>,
123
+ SqlTableModel<O>,
124
+ SqlTableModel<C>,
125
+ SqlTableModel<AC>,
126
+ SqlTableModel<F>,
127
+ N,
128
+ SQL
129
+ >;
130
+
131
+ export type OAuthOwnershipMutation<
132
+ T extends Table,
133
+ O extends Table,
134
+ N,
135
+ > = Shared.OAuthOwnershipMutation<SqlTableModel<T>, SqlTableModel<O>, N, SQL>;
136
+
137
+ export type OAuthRegistrationIntentMapping<
138
+ S extends Table,
139
+ O extends Table,
140
+ C extends Table,
141
+ AC extends Table,
142
+ F extends Table,
143
+ T extends Table,
144
+ I extends Table,
145
+ N,
146
+ > = Shared.OAuthRegistrationIntentMapping<
147
+ SqlTableModel<S>,
148
+ SqlTableModel<O>,
149
+ SqlTableModel<C>,
150
+ SqlTableModel<AC>,
151
+ SqlTableModel<F>,
152
+ SqlTableModel<T>,
153
+ SqlTableModel<I>,
154
+ N,
155
+ SQL
156
+ >;
157
+
158
+ export type OAuthRegistrationBase<
159
+ Registration,
160
+ T extends Table,
161
+ O extends Table,
162
+ I extends Table,
163
+ R extends Table,
164
+ N,
165
+ > = Shared.OAuthRegistrationBase<
166
+ Registration,
167
+ SqlTableModel<T>,
168
+ SqlTableModel<O>,
169
+ SqlTableModel<I>,
170
+ SqlTableModel<R>,
171
+ N,
172
+ SQL,
173
+ Table
174
+ >;
175
+
176
+ export type OAuthRegistrationGuardTable<
177
+ T extends Table,
178
+ Registration,
179
+ N,
180
+ > = Shared.OAuthRegistrationGuardTable<SqlTableModel<T>, Registration, N, SQL>;
181
+
182
+ export type OAuthRegistrationGuardDescriptor<Registration, N> =
183
+ Shared.OAuthRegistrationGuardDescriptor<Registration, N, SQL, Table>;
184
+
185
+ export const oauthRegistrationGuardTable = <T extends Table, Registration, N>(
186
+ input: OAuthRegistrationGuardTable<T, Registration, N>,
187
+ ): OAuthRegistrationGuardDescriptor<Registration, N> =>
188
+ Shared.oauthRegistrationGuardTable<SqlTableModel<T>, Registration, N, SQL>(input);
189
+
190
+ export type OAuthRegistrationMapping<
191
+ Registration,
192
+ S extends Table,
193
+ O extends Table,
194
+ C extends Table,
195
+ AC extends Table,
196
+ T extends Table,
197
+ I extends Table,
198
+ R extends Table,
199
+ N,
200
+ > = Shared.OAuthRegistrationMapping<
201
+ Registration,
202
+ SqlTableModel<S>,
203
+ SqlTableModel<O>,
204
+ SqlTableModel<C>,
205
+ SqlTableModel<AC>,
206
+ SqlTableModel<T>,
207
+ SqlTableModel<I>,
208
+ SqlTableModel<R>,
209
+ N,
210
+ SQL,
211
+ Table
212
+ >;
213
+
214
+ /** Heterogeneous descriptors are made by typed factories and projected privately. */
215
+ export type OAuthEligibilityDescriptor<N> = Shared.OAuthEligibilityDescriptor<N, SQL, Table>;
216
+
217
+ export const oauthEligibilityTable = <T extends Table, N>(
218
+ input: OAuthEligibilityTable<T, N>,
219
+ ): OAuthEligibilityDescriptor<N> => Shared.oauthEligibilityTable<SqlTableModel<T>, N, SQL>(input);
220
+
221
+ export type OAuthCleanupDescriptor<N> = Shared.OAuthCleanupDescriptor<N, SQL, Table>;
222
+
223
+ export const oauthCleanupTable = <T extends Table, N>(
224
+ input: OAuthCleanupTable<T, N>,
225
+ ): OAuthCleanupDescriptor<N> => Shared.oauthCleanupTable<SqlTableModel<T>, N, SQL>(input);
226
+
227
+ /** A stable application/reference lock acquired before an ownership target.
228
+ * Missing rows retain ownership. Conditions use detached native IDs; every
229
+ * writer of the guarded reference follows the same order. */
230
+ export type OAuthReferenceGuardTable<T extends Table, N> = Shared.OAuthReferenceGuardTable<
231
+ SqlTableModel<T>,
232
+ N,
233
+ SQL
234
+ >;
235
+
236
+ export type OAuthReferenceGuardDescriptor<N> = Shared.OAuthReferenceGuardDescriptor<N, SQL, Table>;
237
+
238
+ export const oauthReferenceGuardTable = <T extends Table, N>(
239
+ input: OAuthReferenceGuardTable<T, N>,
240
+ ): OAuthReferenceGuardDescriptor<N> =>
241
+ Shared.oauthReferenceGuardTable<SqlTableModel<T>, N, SQL>(input);
242
+
243
+ export type OAuthAccountsMapping<
244
+ S extends Table,
245
+ O extends Table,
246
+ C extends Table,
247
+ AC extends Table,
248
+ F extends Table,
249
+ T extends Table,
250
+ U extends Table,
251
+ N,
252
+ > = Shared.OAuthAccountsMapping<
253
+ SqlTableModel<S>,
254
+ SqlTableModel<O>,
255
+ SqlTableModel<C>,
256
+ SqlTableModel<AC>,
257
+ SqlTableModel<F>,
258
+ SqlTableModel<T>,
259
+ SqlTableModel<U>,
260
+ N,
261
+ SQL,
262
+ Table
263
+ >;
@@ -46,7 +46,7 @@ const combine = (separator: string, values: ReadonlyArray<Fragment | undefined>)
46
46
  );
47
47
  };
48
48
 
49
- const and = (...values: ReadonlyArray<Fragment | undefined>) => combine(" AND ", values);
49
+ export const and = (...values: ReadonlyArray<Fragment | undefined>) => combine(" AND ", values);
50
50
  const or = (...values: ReadonlyArray<Fragment | undefined>) => combine(" OR ", values);
51
51
 
52
52
  const operations = {
@@ -54,9 +54,11 @@ const operations = {
54
54
  or,
55
55
  asc: (value: unknown) => template`${value} ASC`,
56
56
  eq: (left: unknown, right: unknown) => template`${left} = ${right}`,
57
+ ne: (left: unknown, right: unknown) => template`${left} <> ${right}`,
57
58
  gt: (left: unknown, right: unknown) => template`${left} > ${right}`,
58
59
  gte: (left: unknown, right: unknown) => template`${left} >= ${right}`,
59
60
  lte: (left: unknown, right: unknown) => template`${left} <= ${right}`,
61
+ isNotNull: (value: unknown) => template`${value} IS NOT NULL`,
60
62
  isNull: (value: unknown) => template`${value} IS NULL`,
61
63
  notExists: (value: unknown) => template`NOT EXISTS (${value})`,
62
64
  inArray: (column: unknown, values: ReadonlyArray<unknown>) =>
@@ -119,8 +121,8 @@ interface QueryState {
119
121
  const selectionFor = (state: QueryState): Selection =>
120
122
  state.selection ?? (state.table instanceof Table ? state.table.columns : {});
121
123
 
124
+ // Logical keys may exceed SQL identifier limits; only short internal aliases reach the driver.
122
125
  const selectionFields = (selection: Selection) => {
123
- const used = new Set(Object.keys(selection));
124
126
  let index = 0;
125
127
 
126
128
  return Object.entries(selection).flatMap<{
@@ -130,16 +132,13 @@ const selectionFields = (selection: Selection) => {
130
132
  readonly alias: string;
131
133
  }>(([key, value]) =>
132
134
  value instanceof Table
133
- ? Object.entries(value.columns).map(([name, column]) => {
134
- let alias: string;
135
-
136
- do alias = `auth_column_${index++}`;
137
- while (used.has(alias));
138
- used.add(alias);
139
-
140
- return { key, name, column, alias };
141
- })
142
- : [{ key, name: undefined, column: value, alias: key }],
135
+ ? Object.entries(value.columns).map(([name, column]) => ({
136
+ key,
137
+ name,
138
+ column,
139
+ alias: `auth_column_${index++}`,
140
+ }))
141
+ : [{ key, name: undefined, column: value, alias: `auth_column_${index++}` }],
143
142
  );
144
143
  };
145
144
 
@@ -214,7 +213,9 @@ const compile = (state: QueryState, compiler: Compiler): string => {
214
213
 
215
214
  /** Captures one Effect SQL client. Its transaction service remains the physical owner. */
216
215
  export const makeSqlDatabase = Effect.fnUntraced(function* (dialect: Dialect) {
217
- const client = yield* SqlClient;
216
+ // Mappings and projection aliases own their names. Retain the client's
217
+ // connection and transaction identity while bypassing naming transforms.
218
+ const client = (yield* SqlClient).withoutTransforms();
218
219
 
219
220
  const query = (state: QueryState): Query => {
220
221
  const execute = Effect.suspend(() => {
@@ -299,3 +300,5 @@ export interface SqlDatabase {
299
300
  }
300
301
 
301
302
  export const sql = template;
303
+
304
+ export const { eq } = operations;
@@ -0,0 +1,64 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+
4
+ import { PersistenceMappingError } from "./mapping-error";
5
+ import { Table } from "./sql-table";
6
+ import { storageKeyPlans } from "./storage-plans";
7
+ import { validateStorageBatch, type StorageValidation } from "./storage-validation";
8
+ import { NativeDatabase } from "./transaction-kernel";
9
+
10
+ /** Check declared mappings against physical keys before acquiring OAuth services. */
11
+ export const validateSqlStorage = Effect.fnUntraced(function* (mapping: unknown) {
12
+ const plans = yield* Effect.try({
13
+ try: () =>
14
+ storageKeyPlans(mapping, (table) => {
15
+ if (!(table instanceof Table)) throw new Error("Expected a SQL table");
16
+
17
+ return table.columns;
18
+ }),
19
+ catch: () =>
20
+ PersistenceMappingError.make({ operation: "mapping", cause: "Invalid storage key mapping" }),
21
+ });
22
+
23
+ const { $client: client } = yield* NativeDatabase;
24
+
25
+ const dialect = client.onDialectOrElse({
26
+ pg: () => "pg" as const,
27
+ sqlite: () => "sqlite" as const,
28
+ orElse: () => undefined,
29
+ });
30
+
31
+ if (dialect === undefined)
32
+ return yield* PersistenceMappingError.make({
33
+ operation: "mapping",
34
+ cause: "OAuth persistence requires PostgreSQL or SQLite",
35
+ });
36
+ const validations: StorageValidation[] = [];
37
+
38
+ for (const plan of plans) {
39
+ if (!(plan.table instanceof Table))
40
+ return yield* PersistenceMappingError.make({
41
+ operation: "mapping",
42
+ cause: "Expected a SQL table",
43
+ });
44
+ validations.push({
45
+ table: {
46
+ name: plan.table.name,
47
+ ...(plan.table.schema === undefined ? {} : { schema: plan.table.schema }),
48
+ columns: Object.fromEntries(
49
+ Object.entries(plan.table.columns).map(([key, column]) => [key, column.options]),
50
+ ),
51
+ },
52
+ required: [plan.keys],
53
+ });
54
+ }
55
+ yield* validateStorageBatch(dialect, validations).pipe(
56
+ Effect.provideService(SqlClient.SqlClient, client),
57
+ Effect.mapError(() =>
58
+ PersistenceMappingError.make({
59
+ operation: "mapping",
60
+ cause: "Required SQL storage key is unavailable",
61
+ }),
62
+ ),
63
+ );
64
+ });
@@ -1,7 +1,6 @@
1
1
  import { EmailCredentialSnapshot } from "@yielded/auth/Email";
2
2
  import { LoginIdentifier } from "@yielded/auth/Identity";
3
3
  import {
4
- PasswordAttemptId,
5
4
  PasswordCredentialSnapshot,
6
5
  EncodedPasswordHash,
7
6
  type PasswordReplacement,
@@ -375,7 +374,6 @@ export const makeMappings = Effect.fnUntraced(function* (input: MappingInput) {
375
374
  isCommandConflict: () => false,
376
375
  encodeInstant: instant,
377
376
  decodeInstant: readInstant,
378
- allocateAttemptId: allocate.pipe(Effect.map(PasswordAttemptId.make)),
379
377
  allocateCredentialId: allocate,
380
378
  allocateRevision: allocate.pipe(Effect.map(SecurityRevision.make)),
381
379
  commandRetentionMillis: 86_400_000,
@@ -444,41 +442,6 @@ export const makeMappings = Effect.fnUntraced(function* (input: MappingInput) {
444
442
  });
445
443
  }),
446
444
  },
447
- attempt: {
448
- ...mapped("passwordAttempts"),
449
- moduleId: "moduleId",
450
- action: "action",
451
- attemptId: "attemptId",
452
- identifierNamespace: "identifierNamespace",
453
- identifierValue: "identifierValue",
454
- subjectId: "subjectId",
455
- credentialId: "credentialId",
456
- securityRevision: "securityRevision",
457
- credentialRevision: "credentialRevision",
458
- verifierVersion: "verifierVersion",
459
- identifierBindingRevision: "identifierBindingRevision",
460
- admittedAt: "admittedAt",
461
- deadline: "deadline",
462
- retentionUntil: "retentionUntil",
463
- state: "state",
464
- encodeInsert: (row, { nativeSubjectId, state }) => ({
465
- moduleId: row.moduleId,
466
- action: row.action,
467
- attemptId: row.attemptId,
468
- identifierNamespace: row.identifier.namespace,
469
- identifierValue: row.identifier.value,
470
- subjectId: nativeSubjectId ?? null,
471
- credentialId: row.credentialId ?? null,
472
- securityRevision: row.securityRevision ?? null,
473
- credentialRevision: row.credentialRevision ?? null,
474
- verifierVersion: row.verifierVersion ?? null,
475
- identifierBindingRevision: row.identifierBindingRevision ?? null,
476
- admittedAt: instant(row.admittedAtMillis),
477
- deadline: instant(row.deadlineMillis),
478
- retentionUntil: instant(row.retentionUntilMillis),
479
- state,
480
- }),
481
- },
482
445
  command: {
483
446
  ...mapped("passwordCommands"),
484
447
  moduleId: "moduleId",
@@ -0,0 +1,218 @@
1
+ import { Predicate, Schema } from "effect";
2
+
3
+ // These are mapping contracts, not a parser for arbitrary constraint declarations.
4
+ // Role aliases are explicit because their documentation names differ from mapping fields.
5
+ const uniquePlans: Readonly<Record<string, readonly [string, ReadonlyArray<string>]>> = {
6
+ "unique(abuseEvent.moduleId,abuseEvent.action,abuseEvent.scopeKind,abuseEvent.scopeKey,abuseEvent.commandId)":
7
+ ["abuseEvent", ["moduleId", "action", "scopeKind", "scopeKey", "commandId"]],
8
+ "unique(admission.moduleId)": ["admission", ["moduleId"]],
9
+ "unique(attempt.moduleId,attempt.attemptId)": ["attempt", ["moduleId", "attemptId"]],
10
+ "unique(authority.subjectId,authority.credentialId)": [
11
+ "authority",
12
+ ["subjectId", "credentialId"],
13
+ ],
14
+ "unique(authorityCredential.subjectId,authorityCredential.credentialId)": [
15
+ "authorityCredential",
16
+ ["subjectId", "credentialId"],
17
+ ],
18
+ "unique(command.moduleId,command.commandId)": ["command", ["moduleId", "commandId"]],
19
+ "unique(connectedAdmission.admissionId)": ["admission", ["admissionId"]],
20
+ "unique(connectedClient.clientKey)": ["client", ["clientKey"]],
21
+ "unique(connectedCohort.cohortKey)": ["cohort", ["cohortKey"]],
22
+ "unique(connectedCommand.moduleId,connectedCommand.commandId)": [
23
+ "command",
24
+ ["moduleId", "commandId"],
25
+ ],
26
+ "unique(connectedFlow.moduleId,connectedFlow.commandId)": ["flow", ["moduleId", "commandId"]],
27
+ "unique(connectedFlow.moduleId,connectedFlow.flowId)": ["flow", ["moduleId", "flowId"]],
28
+ "unique(connectedFlow.stateDigest)": ["flow", ["stateDigest"]],
29
+ "unique(connectedGrant.moduleId,connectedGrant.grantId)": ["grant", ["moduleId", "grantId"]],
30
+ "unique(connectedGrant.moduleId,connectedGrant.subjectId,connectedGrant.profileKey,connectedGrant.activeIdentityKey)":
31
+ ["grant", ["moduleId", "subjectId", "profileKey", "activeIdentityKey"]],
32
+ "unique(connectedRevocation.jobId)": ["job", ["jobId"]],
33
+ "unique(continuation.moduleId,continuation.continuationId)": [
34
+ "continuation",
35
+ ["moduleId", "continuationId"],
36
+ ],
37
+ "unique(continuation.moduleId,continuation.digest)": ["continuation", ["moduleId", "digest"]],
38
+ "unique(credential.credentialId)": ["credential", ["credentialId"]],
39
+ "unique(credential.identityKey)": ["credential", ["identityKey"]],
40
+ "unique(credential.moduleId,credential.credentialId)": [
41
+ "credential",
42
+ ["moduleId", "credentialId"],
43
+ ],
44
+ "unique(credential.moduleId,credential.subjectId)": ["credential", ["moduleId", "subjectId"]],
45
+ "unique(emailCommand.moduleId,emailCommand.commandId)": ["command", ["moduleId", "commandId"]],
46
+ "unique(emailCredential.moduleId,emailCredential.credentialId)": [
47
+ "credential",
48
+ ["moduleId", "credentialId"],
49
+ ],
50
+ "unique(emailCredential.moduleId,emailCredential.identifierNamespace,emailCredential.identifierValue)":
51
+ ["credential", ["moduleId", "identifierNamespace", "identifierValue"]],
52
+ "unique(emailRegistration.moduleId,emailRegistration.commandId)": [
53
+ "registration",
54
+ ["moduleId", "commandId"],
55
+ ],
56
+ "unique(emailRegistration.pendingReference)": ["registration", ["pendingReference"]],
57
+ "unique(failureEvent.moduleId,failureEvent.seriesKey,failureEvent.commandId)": [
58
+ "failureEvent",
59
+ ["moduleId", "seriesKey", "commandId"],
60
+ ],
61
+ "unique(flow.flowId)": ["flow", ["flowId"]],
62
+ "unique(flow.moduleId,flow.commandId)": ["flow", ["moduleId", "commandId"]],
63
+ "unique(flow.moduleId,flow.flowId)": ["flow", ["moduleId", "flowId"]],
64
+ "unique(flow.stateDigest)": ["flow", ["stateDigest"]],
65
+ "unique(generation.moduleId,generation.deliveryId)": ["generation", ["moduleId", "deliveryId"]],
66
+ "unique(generation.moduleId,generation.proofId)": ["generation", ["moduleId", "proofId"]],
67
+ "unique(identifier.namespace,identifier.value)": ["identifier", ["namespace", "value"]],
68
+ "unique(intent.digest)": ["intent", ["digest"]],
69
+ "unique(intent.flowId)": ["intent", ["flowId"]],
70
+ "unique(intent.moduleId,intent.commandId)": ["intent", ["moduleId", "commandId"]],
71
+ "unique(intent.moduleId,intent.flowId)": ["intent", ["moduleId", "flowId"]],
72
+ "unique(intent.moduleId,intent.intentId)": ["intent", ["moduleId", "intentId"]],
73
+ "unique(intent.moduleId,intent.reference)": ["intent", ["moduleId", "reference"]],
74
+ "unique(ownership.identityKey)": ["ownership", ["identityKey"]],
75
+ "unique(pending.digest)": ["pending", ["digest"]],
76
+ "unique(pending.flowId)": ["pending", ["flowId"]],
77
+ "unique(rateScope.moduleId,rateScope.purpose,rateScope.action,rateScope.scopeKind,rateScope.scopeKey)":
78
+ ["rateScope", ["moduleId", "purpose", "action", "scopeKind", "scopeKey"]],
79
+ "unique(registration.moduleId,registration.requestId)": [
80
+ "registration",
81
+ ["moduleId", "requestId"],
82
+ ],
83
+ "unique(registration.recoveryReference)": ["registration", ["recoveryReference"]],
84
+ "unique(registrationCommand.moduleId,registrationCommand.commandId)": [
85
+ "command",
86
+ ["moduleId", "commandId"],
87
+ ],
88
+ "unique(request.moduleId,request.requestId)": ["request", ["moduleId", "requestId"]],
89
+ "unique(series.moduleId,series.purpose,series.scopeKey)": [
90
+ "series",
91
+ ["moduleId", "purpose", "scopeKey"],
92
+ ],
93
+ "unique(session.digest)": ["session", ["digest"]],
94
+ "unique(tombstone.subjectId,tombstone.sessionId)": ["tombstone", ["subjectId", "sessionId"]],
95
+ "unique(tuple.identityKey)": ["ownership.tuple", ["identityKey"]],
96
+ "unique(unlinkCommand.moduleId,unlinkCommand.commandId)": ["command", ["moduleId", "commandId"]],
97
+ };
98
+
99
+ const arrayRoles: Readonly<Record<string, string>> = {
100
+ credentialId: "credential",
101
+ factor: "authority",
102
+ credentialOwnership: "credentialOwnership",
103
+ handleOwnership: "handleOwnership",
104
+ boundSubjectHandle: "handleOwnership",
105
+ flow: "flow",
106
+ command: "flow",
107
+ admission: "admission",
108
+ charge: "charge",
109
+ intentFlow: "intent",
110
+ intentCommand: "intent",
111
+ handle: "handle",
112
+ };
113
+
114
+ const nestedMappings = [
115
+ "read",
116
+ "signIn",
117
+ "password",
118
+ "proof",
119
+ "proofs",
120
+ "revocation",
121
+ "pending",
122
+ "session",
123
+ "source",
124
+ ];
125
+
126
+ const constraintGroups = [
127
+ "constraints",
128
+ "tupleConstraints",
129
+ "registrationConstraints",
130
+ "managementConstraints",
131
+ ];
132
+
133
+ const MappingRecord = Schema.Record(Schema.String, Schema.Unknown);
134
+ const record = Schema.decodeUnknownSync(MappingRecord);
135
+
136
+ export const storageKeyPlans = (
137
+ input: unknown,
138
+ columnsFor: (table: object) => Readonly<Record<string, unknown>>,
139
+ ) => {
140
+ const mapping = record(input);
141
+ const result: Array<{ readonly table: object; readonly keys: ReadonlyArray<string> }> = [];
142
+
143
+ for (const group of constraintGroups) {
144
+ if (mapping[group] === undefined) continue;
145
+ for (const [name, declaration] of Object.entries(record(mapping[group]))) {
146
+ if (declaration === "notNull(provisioningRequest.subjectId)") continue;
147
+ let role: string;
148
+ let keys: ReadonlyArray<string>;
149
+
150
+ if (typeof declaration === "string") {
151
+ const plan = uniquePlans[declaration];
152
+
153
+ if (plan !== undefined) [role, keys] = plan;
154
+ else if (declaration === "unique(id)") {
155
+ role = "subject";
156
+ keys = ["id"];
157
+ } else if (declaration === "unique(scope)") {
158
+ role = name;
159
+ keys = ["scope"];
160
+ } else if (declaration === "unique(credentialId)") {
161
+ role = "credential";
162
+ keys = ["id"];
163
+ } else if (declaration === "unique(namespace,value)") {
164
+ role = "identifier";
165
+ keys = ["namespace", "value"];
166
+ } else if (declaration === "unique(provider,issuer,subject)") {
167
+ role = "externalIdentity";
168
+ keys = ["provider", "issuer", "subject"];
169
+ } else if (declaration === "unique(requestId)") {
170
+ role = "provisioningRequest";
171
+ keys = ["requestId"];
172
+ } else throw new Error("Unknown storage constraint");
173
+ } else if (Array.isArray(declaration) && declaration.every(Predicate.isString)) {
174
+ const mappedRole =
175
+ group === "managementConstraints" && name === "command" ? "command" : arrayRoles[name];
176
+
177
+ if (mappedRole === undefined) throw new Error("Unknown storage constraint");
178
+ role = mappedRole;
179
+ keys = declaration;
180
+ } else throw new Error("Invalid storage constraint");
181
+
182
+ let target: unknown = mapping;
183
+
184
+ for (const segment of role.split(".")) target = record(target)[segment];
185
+ // Session authority places its pending tables in one nested mapping.
186
+ if (target === undefined && Schema.is(MappingRecord)(mapping.pending))
187
+ target = mapping.pending[role];
188
+ let mapped = record(target);
189
+
190
+ if (role === "pending" && mapped.table === undefined) mapped = record(mapped.pending);
191
+ // OAuth ownership can be integrated into its tuple authority or separate.
192
+ if (role === "ownership" && mapped.table === undefined)
193
+ mapped = record(mapped.external ?? mapped.tuple);
194
+ if (!Predicate.isObject(mapped.table)) throw new Error("Missing storage table");
195
+ const columns = columnsFor(mapped.table);
196
+
197
+ result.push({
198
+ table: mapped.table,
199
+ keys: keys.map((key) => {
200
+ const value = mapped[key];
201
+
202
+ if (typeof value !== "string" || columns[value] === undefined)
203
+ throw new Error("Missing mapped key");
204
+
205
+ return value;
206
+ }),
207
+ });
208
+ }
209
+ }
210
+ for (const nested of nestedMappings) {
211
+ const value = mapping[nested];
212
+
213
+ if (Schema.is(MappingRecord)(value) && value.table === undefined)
214
+ result.push(...storageKeyPlans(value, columnsFor));
215
+ }
216
+
217
+ return result;
218
+ };