@yielded/auth-persistence 0.1.0-beta.30 → 0.1.0-beta.32

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 (130) hide show
  1. package/README.md +11 -7
  2. package/dist/Adapter.d.mts +1 -1
  3. package/dist/Adapter.mjs +8 -8
  4. package/dist/OAuthPersistence.mjs +10 -5
  5. package/dist/OAuthPersistence.mjs.map +1 -1
  6. package/dist/internal/configuration.d.mts +49 -13
  7. package/dist/internal/configuration.mjs.map +1 -1
  8. package/dist/internal/email-native.mjs +10 -2
  9. package/dist/internal/email-native.mjs.map +1 -1
  10. package/dist/internal/email-policy.mjs +3 -2
  11. package/dist/internal/email-policy.mjs.map +1 -1
  12. package/dist/internal/models/oauth-model.d.mts +4 -2
  13. package/dist/internal/models/oauth-model.mjs.map +1 -1
  14. package/dist/internal/models/password-model.d.mts +2 -1
  15. package/dist/internal/models/password-model.mjs.map +1 -1
  16. package/dist/internal/oauth/native-accounts.d.mts +1 -1
  17. package/dist/internal/oauth/native-accounts.mjs +6 -6
  18. package/dist/internal/oauth/native-accounts.mjs.map +1 -1
  19. package/dist/internal/oauth/native-connected.d.mts +1 -1
  20. package/dist/internal/oauth/native-connected.mjs +1 -1
  21. package/dist/internal/oauth/native-registration.mjs +19 -8
  22. package/dist/internal/oauth/native-registration.mjs.map +1 -1
  23. package/dist/internal/oauth/native-sign-in.d.mts +1 -1
  24. package/dist/internal/oauth/native-sign-in.mjs +1 -1
  25. package/dist/internal/oauth/native-state.mjs +21 -11
  26. package/dist/internal/oauth/native-state.mjs.map +1 -1
  27. package/dist/internal/oauth/state.d.mts +1 -1
  28. package/dist/internal/oauth/state.mjs +1 -1
  29. package/dist/internal/oauth-managed.mjs +182 -0
  30. package/dist/internal/oauth-managed.mjs.map +1 -0
  31. package/dist/internal/passkey-managed.mjs +5 -3
  32. package/dist/internal/passkey-managed.mjs.map +1 -1
  33. package/dist/internal/passkey-native-registration.mjs +1 -1
  34. package/dist/internal/passkey-native-state.mjs +33 -23
  35. package/dist/internal/passkey-native-state.mjs.map +1 -1
  36. package/dist/internal/passkey-native-write.mjs +3 -3
  37. package/dist/internal/passkey-native-write.mjs.map +1 -1
  38. package/dist/internal/passkey-native.d.mts +15 -7
  39. package/dist/internal/passkey-native.mjs +2 -1
  40. package/dist/internal/passkey-native.mjs.map +1 -1
  41. package/dist/internal/passkey-policy.mjs +9 -7
  42. package/dist/internal/passkey-policy.mjs.map +1 -1
  43. package/dist/internal/password-native.mjs +22 -5
  44. package/dist/internal/password-native.mjs.map +1 -1
  45. package/dist/internal/password-policy.mjs +7 -6
  46. package/dist/internal/password-policy.mjs.map +1 -1
  47. package/dist/internal/password-registration-native.d.mts +1 -1
  48. package/dist/internal/persistence.d.mts +3 -0
  49. package/dist/internal/persistence.mjs +99 -42
  50. package/dist/internal/persistence.mjs.map +1 -1
  51. package/dist/internal/phone-native.mjs +1 -1
  52. package/dist/internal/phone-native.mjs.map +1 -1
  53. package/dist/internal/registration.mjs +17 -8
  54. package/dist/internal/registration.mjs.map +1 -1
  55. package/dist/internal/session-native-authority.mjs +8 -6
  56. package/dist/internal/session-native-authority.mjs.map +1 -1
  57. package/dist/internal/session-native-login.d.mts +2 -2
  58. package/dist/internal/session-native-login.mjs +4 -2
  59. package/dist/internal/session-native-login.mjs.map +1 -1
  60. package/dist/internal/session-native-pending-read.mjs +26 -20
  61. package/dist/internal/session-native-pending-read.mjs.map +1 -1
  62. package/dist/internal/session-native-record.mjs +6 -4
  63. package/dist/internal/session-native-record.mjs.map +1 -1
  64. package/dist/internal/session-native-state.mjs +24 -8
  65. package/dist/internal/session-native-state.mjs.map +1 -1
  66. package/dist/internal/session-native-stateful.d.mts +2 -2
  67. package/dist/internal/session-native-stateful.mjs +67 -38
  68. package/dist/internal/session-native-stateful.mjs.map +1 -1
  69. package/dist/internal/session-native-step-up.d.mts +2 -2
  70. package/dist/internal/session-native-step-up.mjs +17 -19
  71. package/dist/internal/session-native-step-up.mjs.map +1 -1
  72. package/dist/internal/session-native-validity.mjs +3 -1
  73. package/dist/internal/session-native-validity.mjs.map +1 -1
  74. package/dist/internal/sql-authority.mjs +5 -1
  75. package/dist/internal/sql-authority.mjs.map +1 -1
  76. package/dist/internal/sql-storage-validation.mjs +2 -1
  77. package/dist/internal/sql-storage-validation.mjs.map +1 -1
  78. package/dist/internal/sql-table.d.mts +1 -0
  79. package/dist/internal/sql-table.mjs.map +1 -1
  80. package/dist/internal/sqlite-policy-guard.mjs +23 -0
  81. package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
  82. package/dist/internal/step-up-state.mjs +19 -6
  83. package/dist/internal/step-up-state.mjs.map +1 -1
  84. package/dist/internal/storage-mapping.d.mts +9 -5
  85. package/dist/internal/storage-mapping.mjs +17 -5
  86. package/dist/internal/storage-mapping.mjs.map +1 -1
  87. package/dist/internal/storage-tables.d.mts +237 -0
  88. package/dist/internal/storage-tables.mjs +68 -0
  89. package/dist/internal/storage-tables.mjs.map +1 -1
  90. package/dist/internal/storage-validation.d.mts +3 -1
  91. package/dist/internal/storage-validation.mjs +70 -17
  92. package/dist/internal/storage-validation.mjs.map +1 -1
  93. package/dist/internal/totp-native.mjs +1 -1
  94. package/package.json +1 -1
  95. package/src/OAuthPersistence.ts +7 -2
  96. package/src/internal/configuration.ts +72 -15
  97. package/src/internal/email-native.ts +16 -6
  98. package/src/internal/email-policy.ts +12 -3
  99. package/src/internal/models/oauth-model.ts +7 -1
  100. package/src/internal/models/password-model.ts +2 -1
  101. package/src/internal/oauth/native-accounts.ts +3 -3
  102. package/src/internal/oauth/native-registration.ts +17 -6
  103. package/src/internal/oauth/native-state.ts +38 -18
  104. package/src/internal/oauth-managed.ts +277 -0
  105. package/src/internal/passkey-managed.ts +8 -2
  106. package/src/internal/passkey-native-state.ts +59 -52
  107. package/src/internal/passkey-native-write.ts +2 -2
  108. package/src/internal/passkey-native.ts +4 -2
  109. package/src/internal/passkey-policy.ts +13 -9
  110. package/src/internal/password-native.ts +45 -11
  111. package/src/internal/password-policy.ts +16 -8
  112. package/src/internal/persistence.ts +360 -204
  113. package/src/internal/phone-native.ts +1 -1
  114. package/src/internal/registration.ts +53 -40
  115. package/src/internal/session-native-authority.ts +26 -8
  116. package/src/internal/session-native-login.ts +9 -4
  117. package/src/internal/session-native-pending-read.ts +46 -33
  118. package/src/internal/session-native-record.ts +6 -4
  119. package/src/internal/session-native-state.ts +32 -10
  120. package/src/internal/session-native-stateful.ts +147 -52
  121. package/src/internal/session-native-step-up.ts +31 -24
  122. package/src/internal/session-native-validity.ts +3 -1
  123. package/src/internal/sql-authority.ts +5 -1
  124. package/src/internal/sql-storage-validation.ts +53 -46
  125. package/src/internal/sql-table.ts +1 -0
  126. package/src/internal/sqlite-policy-guard.ts +34 -0
  127. package/src/internal/step-up-state.ts +43 -6
  128. package/src/internal/storage-mapping.ts +40 -4
  129. package/src/internal/storage-tables.ts +76 -0
  130. package/src/internal/storage-validation.ts +127 -27
@@ -220,6 +220,243 @@ export declare const storageTables: {
220
220
  };
221
221
  unique: readonly (readonly ("expiresAt" | "flowId" | "issuedAt" | "moduleId" | "purpose" | "requestBindingExpiresAt" | "requestBindingVerifier" | "snapshot")[])[];
222
222
  };
223
+ readonly oauthIdentities: {
224
+ columns: {
225
+ identityKey: {
226
+ type: "text";
227
+ };
228
+ provider: {
229
+ type: "text";
230
+ };
231
+ issuer: {
232
+ type: "text";
233
+ };
234
+ externalSubject: {
235
+ type: "text";
236
+ };
237
+ subjectId: {
238
+ type: "text";
239
+ };
240
+ };
241
+ unique: readonly (readonly ("externalSubject" | "identityKey" | "issuer" | "provider" | "subjectId")[])[];
242
+ };
243
+ readonly oauthCredentials: {
244
+ columns: {
245
+ moduleId: {
246
+ type: "text";
247
+ };
248
+ credentialId: {
249
+ type: "text";
250
+ };
251
+ subjectId: {
252
+ type: "text";
253
+ };
254
+ identityKey: {
255
+ type: "text";
256
+ };
257
+ credentialRevision: {
258
+ type: "text";
259
+ };
260
+ active: {
261
+ type: "boolean";
262
+ };
263
+ };
264
+ unique: readonly (readonly ("active" | "credentialId" | "credentialRevision" | "identityKey" | "moduleId" | "subjectId")[])[];
265
+ };
266
+ readonly oauthSignInFlows: {
267
+ columns: {
268
+ moduleId: {
269
+ type: "text";
270
+ };
271
+ flowId: {
272
+ type: "text";
273
+ };
274
+ purpose: {
275
+ type: "text";
276
+ };
277
+ generation: {
278
+ type: "integer";
279
+ };
280
+ provider: {
281
+ type: "text";
282
+ };
283
+ callbackId: {
284
+ type: "text";
285
+ };
286
+ issuer: {
287
+ type: "text";
288
+ };
289
+ responseIssuerMode: {
290
+ type: "text";
291
+ };
292
+ subjectId: {
293
+ type: "text";
294
+ nullable: boolean;
295
+ };
296
+ stateDigest: {
297
+ type: "text";
298
+ };
299
+ binderVerifier: {
300
+ type: "text";
301
+ };
302
+ binderExpiresAt: {
303
+ type: "integer";
304
+ };
305
+ snapshot: {
306
+ type: "text";
307
+ };
308
+ issuedAt: {
309
+ type: "integer";
310
+ };
311
+ expiresAt: {
312
+ type: "integer";
313
+ };
314
+ };
315
+ unique: readonly (readonly ("binderExpiresAt" | "binderVerifier" | "callbackId" | "expiresAt" | "flowId" | "generation" | "issuedAt" | "issuer" | "moduleId" | "provider" | "purpose" | "responseIssuerMode" | "snapshot" | "stateDigest" | "subjectId")[])[];
316
+ };
317
+ readonly oauthConnectedFlows: {
318
+ columns: {
319
+ moduleId: {
320
+ type: "text";
321
+ };
322
+ flowId: {
323
+ type: "text";
324
+ };
325
+ purpose: {
326
+ type: "text";
327
+ };
328
+ generation: {
329
+ type: "integer";
330
+ };
331
+ provider: {
332
+ type: "text";
333
+ };
334
+ callbackId: {
335
+ type: "text";
336
+ };
337
+ issuer: {
338
+ type: "text";
339
+ };
340
+ responseIssuerMode: {
341
+ type: "text";
342
+ };
343
+ subjectId: {
344
+ type: "text";
345
+ nullable: boolean;
346
+ };
347
+ stateDigest: {
348
+ type: "text";
349
+ };
350
+ binderVerifier: {
351
+ type: "text";
352
+ };
353
+ binderExpiresAt: {
354
+ type: "integer";
355
+ };
356
+ snapshot: {
357
+ type: "text";
358
+ };
359
+ issuedAt: {
360
+ type: "integer";
361
+ };
362
+ expiresAt: {
363
+ type: "integer";
364
+ };
365
+ };
366
+ unique: readonly (readonly ("binderExpiresAt" | "binderVerifier" | "callbackId" | "expiresAt" | "flowId" | "generation" | "issuedAt" | "issuer" | "moduleId" | "provider" | "purpose" | "responseIssuerMode" | "snapshot" | "stateDigest" | "subjectId")[])[];
367
+ };
368
+ readonly oauthConnectedGrants: {
369
+ columns: {
370
+ moduleId: {
371
+ type: "text";
372
+ };
373
+ grantId: {
374
+ type: "text";
375
+ };
376
+ subjectId: {
377
+ type: "text";
378
+ };
379
+ identityKey: {
380
+ type: "text";
381
+ };
382
+ profileKey: {
383
+ type: "text";
384
+ };
385
+ grantVersion: {
386
+ type: "text";
387
+ };
388
+ tokenVersion: {
389
+ type: "text";
390
+ };
391
+ state: {
392
+ type: "text";
393
+ };
394
+ snapshot: {
395
+ type: "text";
396
+ };
397
+ summary: {
398
+ type: "text";
399
+ };
400
+ refreshClaimId: {
401
+ type: "text";
402
+ nullable: boolean;
403
+ };
404
+ refreshNextTokenVersion: {
405
+ type: "text";
406
+ nullable: boolean;
407
+ };
408
+ refreshClaimedAt: {
409
+ type: "integer";
410
+ nullable: boolean;
411
+ };
412
+ refreshClaimExpiresAt: {
413
+ type: "integer";
414
+ nullable: boolean;
415
+ };
416
+ expiresAt: {
417
+ type: "integer";
418
+ };
419
+ };
420
+ unique: readonly (readonly ("expiresAt" | "grantId" | "grantVersion" | "identityKey" | "moduleId" | "profileKey" | "refreshClaimExpiresAt" | "refreshClaimId" | "refreshClaimedAt" | "refreshNextTokenVersion" | "snapshot" | "state" | "subjectId" | "summary" | "tokenVersion")[])[];
421
+ };
422
+ readonly oauthConnectedRevocations: {
423
+ columns: {
424
+ jobId: {
425
+ type: "text";
426
+ };
427
+ moduleId: {
428
+ type: "text";
429
+ };
430
+ subjectId: {
431
+ type: "text";
432
+ };
433
+ identityKey: {
434
+ type: "text";
435
+ };
436
+ snapshot: {
437
+ type: "text";
438
+ };
439
+ state: {
440
+ type: "text";
441
+ };
442
+ claimId: {
443
+ type: "text";
444
+ nullable: boolean;
445
+ };
446
+ claimedAt: {
447
+ type: "integer";
448
+ nullable: boolean;
449
+ };
450
+ claimExpiresAt: {
451
+ type: "integer";
452
+ nullable: boolean;
453
+ };
454
+ retentionUntil: {
455
+ type: "integer";
456
+ };
457
+ };
458
+ unique: readonly (readonly ("claimExpiresAt" | "claimId" | "claimedAt" | "identityKey" | "jobId" | "moduleId" | "retentionUntil" | "snapshot" | "state" | "subjectId")[])[];
459
+ };
223
460
  readonly proofs: {
224
461
  columns: {
225
462
  moduleId: {
@@ -29,6 +29,23 @@ const spec = (columns, unique) => ({
29
29
  columns,
30
30
  unique
31
31
  });
32
+ const oauthFlow = spec({
33
+ moduleId: text,
34
+ flowId: text,
35
+ purpose: text,
36
+ generation: integer,
37
+ provider: text,
38
+ callbackId: text,
39
+ issuer: text,
40
+ responseIssuerMode: text,
41
+ subjectId: optionalText,
42
+ stateDigest: text,
43
+ binderVerifier: text,
44
+ binderExpiresAt: integer,
45
+ snapshot: text,
46
+ issuedAt: integer,
47
+ expiresAt: integer
48
+ }, [["moduleId", "flowId"], ["stateDigest"]]);
32
49
  /** Logical roles are independent of table names and migration ownership. */
33
50
  const storageTables = {
34
51
  subjects: spec({
@@ -105,6 +122,57 @@ const storageTables = {
105
122
  issuedAt: integer,
106
123
  expiresAt: integer
107
124
  }, [["moduleId", "flowId"]]),
125
+ oauthIdentities: spec({
126
+ identityKey: text,
127
+ provider: text,
128
+ issuer: text,
129
+ externalSubject: text,
130
+ subjectId: text
131
+ }, [["identityKey"]]),
132
+ oauthCredentials: spec({
133
+ moduleId: text,
134
+ credentialId: text,
135
+ subjectId: text,
136
+ identityKey: text,
137
+ credentialRevision: text,
138
+ active: boolean
139
+ }, [["credentialId"], ["identityKey"]]),
140
+ oauthSignInFlows: oauthFlow,
141
+ oauthConnectedFlows: oauthFlow,
142
+ oauthConnectedGrants: spec({
143
+ moduleId: text,
144
+ grantId: text,
145
+ subjectId: text,
146
+ identityKey: text,
147
+ profileKey: text,
148
+ grantVersion: text,
149
+ tokenVersion: text,
150
+ state: text,
151
+ snapshot: text,
152
+ summary: text,
153
+ refreshClaimId: optionalText,
154
+ refreshNextTokenVersion: optionalText,
155
+ refreshClaimedAt: optionalInteger,
156
+ refreshClaimExpiresAt: optionalInteger,
157
+ expiresAt: integer
158
+ }, [["moduleId", "grantId"], [
159
+ "moduleId",
160
+ "subjectId",
161
+ "profileKey",
162
+ "identityKey"
163
+ ]]),
164
+ oauthConnectedRevocations: spec({
165
+ jobId: text,
166
+ moduleId: text,
167
+ subjectId: text,
168
+ identityKey: text,
169
+ snapshot: text,
170
+ state: text,
171
+ claimId: optionalText,
172
+ claimedAt: optionalInteger,
173
+ claimExpiresAt: optionalInteger,
174
+ retentionUntil: integer
175
+ }, [["jobId"]]),
108
176
  proofs: spec({
109
177
  moduleId: text,
110
178
  purpose: text,
@@ -1 +1 @@
1
- {"version":3,"file":"storage-tables.mjs","names":[],"sources":["../../src/internal/storage-tables.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\nexport const StorageTable = Schema.Struct({\n name: Schema.NonEmptyString,\n schema: Schema.optionalKey(Schema.NonEmptyString),\n columns: Schema.Record(\n Schema.String,\n Schema.Struct({\n name: Schema.NonEmptyString,\n type: Schema.Literals([\"text\", \"integer\", \"boolean\"]),\n nullable: Schema.optionalKey(Schema.Boolean),\n }),\n ),\n unique: Schema.Array(Schema.Array(Schema.String)),\n});\n\nexport type StorageTable = typeof StorageTable.Type;\n\nconst text = { type: \"text\" as const };\nconst integer = { type: \"integer\" as const };\nconst boolean = { type: \"boolean\" as const };\nconst optionalText = { ...text, nullable: true };\nconst optionalInteger = { ...integer, nullable: true };\n\nconst spec = <C extends Readonly<Record<string, Omit<StorageTable[\"columns\"][string], \"name\">>>>(\n columns: C,\n unique: ReadonlyArray<ReadonlyArray<Extract<keyof C, string>>>,\n) => ({ columns, unique });\n\n/** Logical roles are independent of table names and migration ownership. */\nexport const storageTables = {\n subjects: spec({ id: text, active: boolean, securityRevision: text }, [[\"id\"]]),\n identifiers: spec(\n {\n namespace: text,\n value: text,\n moduleId: optionalText,\n credentialId: optionalText,\n subjectId: text,\n revision: text,\n verifiedAt: optionalInteger,\n active: boolean,\n },\n [[\"namespace\", \"value\"]],\n ),\n credentials: spec({ credentialId: text, subjectId: text, revision: text, active: boolean }, [\n [\"credentialId\"],\n ]),\n passwords: spec(\n {\n moduleId: text,\n subjectId: text,\n credentialId: text,\n credentialRevision: text,\n verifierVersion: text,\n verifier: text,\n normalization: text,\n },\n [\n [\"moduleId\", \"subjectId\"],\n [\"moduleId\", \"credentialId\"],\n ],\n ),\n emailCredentials: spec(\n {\n moduleId: text,\n subjectId: text,\n credentialId: text,\n identifierNamespace: text,\n identifierValue: text,\n credentialRevision: text,\n active: boolean,\n },\n [\n [\"moduleId\", \"credentialId\"],\n [\"moduleId\", \"identifierNamespace\", \"identifierValue\"],\n ],\n ),\n passkeyCredentials: spec(\n {\n credentialId: text,\n subjectId: text,\n rpId: text,\n protocolCredentialId: text,\n credentialKey: text,\n userHandle: text,\n publicKey: text,\n algorithm: integer,\n profile: text,\n credentialRevision: text,\n active: boolean,\n primarySignIn: boolean,\n enrollmentUserVerified: boolean,\n backupEligible: boolean,\n backupState: boolean,\n counter: integer,\n name: text,\n createdAt: integer,\n lastUsedAt: optionalInteger,\n },\n [[\"credentialId\"], [\"credentialKey\"]],\n ),\n passkeyFlows: spec(\n {\n moduleId: text,\n flowId: text,\n purpose: text,\n snapshot: text,\n requestBindingVerifier: text,\n requestBindingExpiresAt: integer,\n issuedAt: integer,\n expiresAt: integer,\n },\n [[\"moduleId\", \"flowId\"]],\n ),\n proofs: spec(\n {\n moduleId: text,\n purpose: text,\n seriesKey: text,\n proofId: text,\n binding: text,\n verifierKeyId: text,\n verifierDigest: text,\n issuedAt: integer,\n expiresAt: integer,\n failedAttempts: integer,\n },\n [\n [\"moduleId\", \"purpose\", \"seriesKey\"],\n [\"moduleId\", \"proofId\"],\n ],\n ),\n sessions: spec(\n {\n sessionId: text,\n subjectId: text,\n digest: text,\n securityRevision: text,\n issuedAt: integer,\n expiresAt: integer,\n absoluteExpiresAt: integer,\n record: text,\n },\n [[\"sessionId\"], [\"digest\"]],\n ),\n pending: spec(\n {\n moduleId: text,\n kind: text,\n digest: text,\n version: text,\n flowId: text,\n subjectId: text,\n bindingDigest: text,\n snapshot: text,\n expiresAt: integer,\n attemptLimit: integer,\n failedAttempts: integer,\n consumed: boolean,\n },\n [[\"digest\"]],\n ),\n} as const;\n\nexport type StorageRole = keyof typeof storageTables;\n\nexport const tableDefinition = (\n role: StorageRole,\n name: string,\n subjectType: \"text\" | \"integer\" = \"text\",\n): StorageTable => {\n const table = storageTables[role];\n\n return {\n name,\n columns: Object.fromEntries(\n Object.entries(table.columns).map(([key, column]) => [\n key,\n {\n ...column,\n name: key.replace(/[A-Z]/g, (letter) => \"_\" + letter.toLowerCase()),\n ...(key === \"subjectId\" ? { type: subjectType } : {}),\n },\n ]),\n ),\n unique: table.unique,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"storage-tables.mjs","names":[],"sources":["../../src/internal/storage-tables.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\nexport const StorageTable = Schema.Struct({\n name: Schema.NonEmptyString,\n schema: Schema.optionalKey(Schema.NonEmptyString),\n columns: Schema.Record(\n Schema.String,\n Schema.Struct({\n name: Schema.NonEmptyString,\n type: Schema.Literals([\"text\", \"integer\", \"boolean\"]),\n nullable: Schema.optionalKey(Schema.Boolean),\n }),\n ),\n unique: Schema.Array(Schema.Array(Schema.String)),\n});\n\nexport type StorageTable = typeof StorageTable.Type;\n\nconst text = { type: \"text\" as const };\nconst integer = { type: \"integer\" as const };\nconst boolean = { type: \"boolean\" as const };\nconst optionalText = { ...text, nullable: true };\nconst optionalInteger = { ...integer, nullable: true };\n\nconst spec = <C extends Readonly<Record<string, Omit<StorageTable[\"columns\"][string], \"name\">>>>(\n columns: C,\n unique: ReadonlyArray<ReadonlyArray<Extract<keyof C, string>>>,\n) => ({ columns, unique });\n\nconst oauthFlow = spec(\n {\n moduleId: text,\n flowId: text,\n purpose: text,\n generation: integer,\n provider: text,\n callbackId: text,\n issuer: text,\n responseIssuerMode: text,\n subjectId: optionalText,\n stateDigest: text,\n binderVerifier: text,\n binderExpiresAt: integer,\n snapshot: text,\n issuedAt: integer,\n expiresAt: integer,\n },\n [[\"moduleId\", \"flowId\"], [\"stateDigest\"]],\n);\n\n/** Logical roles are independent of table names and migration ownership. */\nexport const storageTables = {\n subjects: spec({ id: text, active: boolean, securityRevision: text }, [[\"id\"]]),\n identifiers: spec(\n {\n namespace: text,\n value: text,\n moduleId: optionalText,\n credentialId: optionalText,\n subjectId: text,\n revision: text,\n verifiedAt: optionalInteger,\n active: boolean,\n },\n [[\"namespace\", \"value\"]],\n ),\n credentials: spec({ credentialId: text, subjectId: text, revision: text, active: boolean }, [\n [\"credentialId\"],\n ]),\n passwords: spec(\n {\n moduleId: text,\n subjectId: text,\n credentialId: text,\n credentialRevision: text,\n verifierVersion: text,\n verifier: text,\n normalization: text,\n },\n [\n [\"moduleId\", \"subjectId\"],\n [\"moduleId\", \"credentialId\"],\n ],\n ),\n emailCredentials: spec(\n {\n moduleId: text,\n subjectId: text,\n credentialId: text,\n identifierNamespace: text,\n identifierValue: text,\n credentialRevision: text,\n active: boolean,\n },\n [\n [\"moduleId\", \"credentialId\"],\n [\"moduleId\", \"identifierNamespace\", \"identifierValue\"],\n ],\n ),\n passkeyCredentials: spec(\n {\n credentialId: text,\n subjectId: text,\n rpId: text,\n protocolCredentialId: text,\n credentialKey: text,\n userHandle: text,\n publicKey: text,\n algorithm: integer,\n profile: text,\n credentialRevision: text,\n active: boolean,\n primarySignIn: boolean,\n enrollmentUserVerified: boolean,\n backupEligible: boolean,\n backupState: boolean,\n counter: integer,\n name: text,\n createdAt: integer,\n lastUsedAt: optionalInteger,\n },\n [[\"credentialId\"], [\"credentialKey\"]],\n ),\n passkeyFlows: spec(\n {\n moduleId: text,\n flowId: text,\n purpose: text,\n snapshot: text,\n requestBindingVerifier: text,\n requestBindingExpiresAt: integer,\n issuedAt: integer,\n expiresAt: integer,\n },\n [[\"moduleId\", \"flowId\"]],\n ),\n oauthIdentities: spec(\n { identityKey: text, provider: text, issuer: text, externalSubject: text, subjectId: text },\n [[\"identityKey\"]],\n ),\n oauthCredentials: spec(\n {\n moduleId: text,\n credentialId: text,\n subjectId: text,\n identityKey: text,\n credentialRevision: text,\n active: boolean,\n },\n [[\"credentialId\"], [\"identityKey\"]],\n ),\n oauthSignInFlows: oauthFlow,\n oauthConnectedFlows: oauthFlow,\n oauthConnectedGrants: spec(\n {\n moduleId: text,\n grantId: text,\n subjectId: text,\n identityKey: text,\n profileKey: text,\n grantVersion: text,\n tokenVersion: text,\n state: text,\n snapshot: text,\n summary: text,\n refreshClaimId: optionalText,\n refreshNextTokenVersion: optionalText,\n refreshClaimedAt: optionalInteger,\n refreshClaimExpiresAt: optionalInteger,\n expiresAt: integer,\n },\n [\n [\"moduleId\", \"grantId\"],\n [\"moduleId\", \"subjectId\", \"profileKey\", \"identityKey\"],\n ],\n ),\n oauthConnectedRevocations: spec(\n {\n jobId: text,\n moduleId: text,\n subjectId: text,\n identityKey: text,\n snapshot: text,\n state: text,\n claimId: optionalText,\n claimedAt: optionalInteger,\n claimExpiresAt: optionalInteger,\n retentionUntil: integer,\n },\n [[\"jobId\"]],\n ),\n proofs: spec(\n {\n moduleId: text,\n purpose: text,\n seriesKey: text,\n proofId: text,\n binding: text,\n verifierKeyId: text,\n verifierDigest: text,\n issuedAt: integer,\n expiresAt: integer,\n failedAttempts: integer,\n },\n [\n [\"moduleId\", \"purpose\", \"seriesKey\"],\n [\"moduleId\", \"proofId\"],\n ],\n ),\n sessions: spec(\n {\n sessionId: text,\n subjectId: text,\n digest: text,\n securityRevision: text,\n issuedAt: integer,\n expiresAt: integer,\n absoluteExpiresAt: integer,\n record: text,\n },\n [[\"sessionId\"], [\"digest\"]],\n ),\n pending: spec(\n {\n moduleId: text,\n kind: text,\n digest: text,\n version: text,\n flowId: text,\n subjectId: text,\n bindingDigest: text,\n snapshot: text,\n expiresAt: integer,\n attemptLimit: integer,\n failedAttempts: integer,\n consumed: boolean,\n },\n [[\"digest\"]],\n ),\n} as const;\n\nexport type StorageRole = keyof typeof storageTables;\n\nexport const tableDefinition = (\n role: StorageRole,\n name: string,\n subjectType: \"text\" | \"integer\" = \"text\",\n): StorageTable => {\n const table = storageTables[role];\n\n return {\n name,\n columns: Object.fromEntries(\n Object.entries(table.columns).map(([key, column]) => [\n key,\n {\n ...column,\n name: key.replace(/[A-Z]/g, (letter) => \"_\" + letter.toLowerCase()),\n ...(key === \"subjectId\" ? { type: subjectType } : {}),\n },\n ]),\n ),\n unique: table.unique,\n };\n};\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import "./storage-tables.mjs";
2
2
  import { PersistenceConfigurationError } from "./configuration.mjs";
3
+ import "./native-sql-table.mjs";
3
4
  import { Effect } from "effect";
4
5
  import { SqlClient } from "effect/sql/SqlClient";
5
6
  //#region src/internal/storage-validation.d.ts
@@ -19,9 +20,10 @@ export interface StorageValidation {
19
20
  readonly table: PhysicalStorageTable;
20
21
  readonly required: ReadonlyArray<ReadonlyArray<string>>;
21
22
  }
23
+ type Dialect = "pg" | "mysql" | "sqlite";
22
24
  /** Batch physical metadata reads, reusing only the current acquisition snapshot.
23
25
  * Every requirement is still checked; later acquisitions read fresh metadata. */
24
- export declare const validateStorageBatch: (dialect: "mysql" | "pg" | "sqlite", requirements: readonly StorageValidation[]) => Effect.Effect<undefined, PersistenceConfigurationError, SqlClient>;
26
+ export declare const validateStorageBatch: (dialect: Dialect, requirements: readonly StorageValidation[]) => Effect.Effect<undefined, PersistenceConfigurationError, SqlClient>;
25
27
  /** Validate physical columns and unconditional uniqueness before operations run. */
26
28
  export declare const validateStorage: (dialect: "pg" | "mysql" | "sqlite", table: PhysicalStorageTable, required: ReadonlyArray<ReadonlyArray<string>>) => Effect.Effect<undefined, PersistenceConfigurationError, SqlClient>;
27
29
  //#endregion
@@ -4,20 +4,29 @@ import { SqlClient } from "effect/sql/SqlClient";
4
4
  //#region src/internal/storage-validation.ts
5
5
  const Columns = Schema.Array(Schema.String);
6
6
  const Keys = Schema.Array(Columns);
7
+ const TextColumns = Schema.Array(Schema.Struct({
8
+ name: Schema.String,
9
+ type: Schema.String,
10
+ collation: Schema.String
11
+ }));
7
12
  const Metadata = Schema.Struct({
8
13
  relation: Schema.String,
9
14
  columns: Columns,
10
- keys: Keys
15
+ keys: Keys,
16
+ textColumns: TextColumns
11
17
  });
12
18
  const PgMetadata = Schema.Array(Metadata);
13
19
  const SqliteMetadata = Schema.Array(Schema.Struct({
14
20
  relation: Schema.String,
15
21
  columns: Schema.fromJsonString(Columns),
16
- keys: Schema.fromJsonString(Keys)
22
+ keys: Schema.fromJsonString(Keys),
23
+ textColumns: Schema.fromJsonString(TextColumns)
17
24
  }));
18
25
  const MysqlColumns = Schema.Array(Schema.Struct({
19
26
  relation: Schema.String,
20
- name: Schema.String
27
+ name: Schema.String,
28
+ type: Schema.String,
29
+ collation: Schema.NullOr(Schema.String)
21
30
  }));
22
31
  const MysqlIndexes = Schema.Array(Schema.Struct({
23
32
  relation: Schema.String,
@@ -47,16 +56,15 @@ const withStorageValidation = Effect.fnUntraced(function* (effect) {
47
56
  cache.clients.clear();
48
57
  })));
49
58
  });
50
- /** Batch physical metadata reads, reusing only the current acquisition snapshot.
51
- * Every requirement is still checked; later acquisitions read fresh metadata. */
52
- const validateStorageBatch = Effect.fnUntraced(function* (dialect, requirements) {
53
- if (requirements.length === 0) return;
54
- if (dialect === "sqlite" && requirements.some(({ table }) => table.schema !== void 0 && table.schema !== "main")) return yield* PersistenceConfigurationError.make({ reason: "SQLite storage must belong to the main database" });
55
- const client = yield* SqlClient;
59
+ /** Share physical type, collation and key observations only during acquisition. */
60
+ const readStorageMetadata = Effect.fnUntraced(function* (dialect, physical) {
61
+ if (dialect === "sqlite" && physical.some((table) => table.schema !== void 0 && table.schema !== "main")) return yield* PersistenceConfigurationError.make({ reason: "SQLite storage must belong to the main database" });
62
+ const captured = yield* SqlClient;
63
+ const client = captured.withoutTransforms();
56
64
  const cache = yield* CurrentMetadata;
57
- const byRelation = cache?.active ? cache.clients.get(client) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map();
58
- if (cache?.active) cache.clients.set(client, byRelation);
59
- const missing = [...new Map(requirements.map(({ table }) => [qualifiedTableName(table), table])).entries()].filter(([relation]) => !byRelation.has(`${dialect}:${relation}`));
65
+ const byRelation = cache?.active ? cache.clients.get(captured) ?? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Map();
66
+ if (cache?.active) cache.clients.set(captured, byRelation);
67
+ const missing = [...new Map(physical.map((table) => [qualifiedTableName(table), table])).entries()].filter(([relation]) => !byRelation.has(`${dialect}:${relation}`));
60
68
  const relations = missing.map(([relation]) => relation);
61
69
  if (relations.length > 0) {
62
70
  const metadata = yield* dialect === "sqlite" ? Effect.gen(function* () {
@@ -74,13 +82,18 @@ const validateStorageBatch = Effect.fnUntraced(function* (dialect, requirements)
74
82
  from pragma_index_list(requested.name) i
75
83
  where i."unique" = 1 and i.partial = 0
76
84
  and not exists(select 1 from pragma_index_info(i.name) where name is null)
77
- ) storage_key where json_array_length(storage_key.columns) > 0) as keys
85
+ ) storage_key where json_array_length(storage_key.columns) > 0) as keys,
86
+ (select json_group_array(json_object('name', c.name, 'type', 'TEXT', 'collation', 'BINARY'))
87
+ from pragma_table_info(requested.name) c
88
+ join sqlite_schema s on s.name = requested.name and s.type = 'table'
89
+ where upper(c.type) = 'TEXT' and instr(lower(s.sql), 'collate') = 0) as "textColumns"
78
90
  from requested
79
91
  `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(SqliteMetadata)));
80
92
  }) : dialect === "mysql" ? Effect.gen(function* () {
81
93
  const requested = client.join(" union all ", false)(missing.map(([relation, table]) => client`select ${relation} as relation, ${table.schema ?? null} as schemaName, ${table.name} as tableName`));
82
94
  const columns = yield* client`
83
- select requested.relation, c.column_name as name
95
+ select requested.relation, c.column_name as name,
96
+ c.data_type as type, c.collation_name as collation
84
97
  from (${requested}) requested join information_schema.columns c
85
98
  on c.table_schema = coalesce(requested.schemaName, database()) and c.table_name = requested.tableName
86
99
  `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(MysqlColumns)));
@@ -101,6 +114,17 @@ const validateStorageBatch = Effect.fnUntraced(function* (dialect, requirements)
101
114
  return {
102
115
  relation,
103
116
  columns: columns.filter((column) => column.relation === relation).map((column) => column.name),
117
+ textColumns: columns.flatMap((column) => column.relation === relation && column.collation !== null && [
118
+ "varchar",
119
+ "text",
120
+ "tinytext",
121
+ "mediumtext",
122
+ "longtext"
123
+ ].includes(column.type) ? [{
124
+ name: column.name,
125
+ type: column.type,
126
+ collation: column.collation
127
+ }] : []),
104
128
  keys: [...grouped.values()].filter((entries) => entries.every((entry) => entry.columnName !== null && entry.prefix === null)).map((entries) => entries.flatMap((entry) => entry.columnName === null ? [] : [entry.columnName]))
105
129
  };
106
130
  });
@@ -121,25 +145,54 @@ const validateStorageBatch = Effect.fnUntraced(function* (dialect, requirements)
121
145
  and i.indpred is null and i.indexprs is null
122
146
  and k.ordinality <= i.indnkeyatts
123
147
  group by i.indexrelid
124
- ) storage_key), '[]'::jsonb) as keys
148
+ ) storage_key), '[]'::jsonb) as keys,
149
+ coalesce((select jsonb_agg(jsonb_build_object(
150
+ 'name', a.attname::text, 'type', a.atttypid::text, 'collation', a.attcollation::text))
151
+ from pg_attribute a where a.attrelid = to_regclass(requested.relation)
152
+ and a.attnum > 0 and not a.attisdropped
153
+ and a.atttypid in ('text'::regtype, 'varchar'::regtype)), '[]'::jsonb) as "textColumns"
125
154
  from (values ${requested}) requested(relation)
126
155
  `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(PgMetadata)));
127
156
  });
128
157
  for (const row of metadata) byRelation.set(`${dialect}:${row.relation}`, row);
129
158
  }
159
+ return byRelation;
160
+ }, Effect.mapError((error) => Schema.is(PersistenceConfigurationError)(error) ? error : PersistenceConfigurationError.make({ reason: "Cannot validate SQL storage metadata" })));
161
+ /** Batch physical metadata reads, reusing only the current acquisition snapshot.
162
+ * Every requirement is still checked; later acquisitions read fresh metadata. */
163
+ const validateStorageBatch = Effect.fnUntraced(function* (dialect, requirements) {
164
+ if (requirements.length === 0) return;
165
+ const byRelation = yield* readStorageMetadata(dialect, requirements.map(({ table }) => table));
130
166
  for (const { table, required } of requirements) {
131
167
  const relation = qualifiedTableName(table);
132
168
  const actual = byRelation.get(`${dialect}:${relation}`);
133
169
  for (const column of Object.values(table.columns)) if (actual === void 0 || !actual.columns.includes(column.name)) return yield* PersistenceConfigurationError.make({ reason: `Missing SQL column ${relation}.${column.name}` });
134
170
  yield* validateKeys(table, required, actual?.keys ?? []);
135
171
  }
136
- }, Effect.mapError((error) => Schema.is(PersistenceConfigurationError)(error) ? error : PersistenceConfigurationError.make({ reason: "Cannot validate SQL storage metadata" })));
172
+ });
173
+ /** Candidate declarations do not establish SQL equality compatibility. SQLite
174
+ * omits column collations, so only TEXT tables without COLLATE qualify. */
175
+ const canJoinTextColumns = Effect.fnUntraced(function* (columns) {
176
+ const candidates = columns.filter((column) => column !== void 0);
177
+ if (candidates.length !== columns.length || candidates.length === 0) return false;
178
+ const dialect = (yield* SqlClient).onDialectOrElse({
179
+ pg: () => "pg",
180
+ mysql: () => "mysql",
181
+ sqlite: () => "sqlite",
182
+ orElse: () => void 0
183
+ });
184
+ if (dialect === void 0 || dialect === "sqlite" && candidates.some(({ table }) => table.schema !== void 0 && table.schema !== "main")) return false;
185
+ const metadata = yield* readStorageMetadata(dialect, candidates.map(({ table }) => table));
186
+ const physical = candidates.map(({ table, name }) => metadata.get(`${dialect}:${qualifiedTableName(table)}`)?.textColumns.find((column) => column.name === name));
187
+ const first = physical[0];
188
+ return first !== void 0 && physical.every((column) => column?.type === first.type && column.collation === first.collation);
189
+ });
137
190
  /** Validate physical columns and unconditional uniqueness before operations run. */
138
191
  const validateStorage = (dialect, table, required) => validateStorageBatch(dialect, [{
139
192
  table,
140
193
  required
141
194
  }]);
142
195
  //#endregion
143
- export { qualifiedTableName, validateStorage, validateStorageBatch, withStorageValidation };
196
+ export { canJoinTextColumns, qualifiedTableName, validateStorage, validateStorageBatch, withStorageValidation };
144
197
 
145
198
  //# sourceMappingURL=storage-validation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage-validation.mjs","names":[],"sources":["../../src/internal/storage-validation.ts"],"sourcesContent":["import { Context, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { PersistenceConfigurationError } from \"./configuration\";\nimport type { StorageTable } from \"./storage-tables\";\n\nconst Columns = Schema.Array(Schema.String);\nconst Keys = Schema.Array(Columns);\nconst Metadata = Schema.Struct({ relation: Schema.String, columns: Columns, keys: Keys });\nconst PgMetadata = Schema.Array(Metadata);\n\nconst SqliteMetadata = Schema.Array(\n Schema.Struct({\n relation: Schema.String,\n columns: Schema.fromJsonString(Columns),\n keys: Schema.fromJsonString(Keys),\n }),\n);\n\nconst MysqlColumns = Schema.Array(Schema.Struct({ relation: Schema.String, name: Schema.String }));\n\nconst MysqlIndexes = Schema.Array(\n Schema.Struct({\n relation: Schema.String,\n name: Schema.String,\n columnName: Schema.NullOr(Schema.String),\n prefix: Schema.NullOr(Schema.Number),\n }),\n);\n\nconst quote = (value: string) => '\"' + value.replaceAll('\"', '\"\"') + '\"';\n\n/** Physical metadata needed for catalog validation; value codecs remain adapter-owned. */\nexport interface PhysicalStorageTable {\n readonly name: string;\n readonly schema?: string;\n readonly columns: Readonly<Record<string, { readonly name: string }>>;\n}\n\nexport const qualifiedTableName = (table: Pick<StorageTable, \"name\" | \"schema\">) =>\n (table.schema === undefined ? \"\" : quote(table.schema) + \".\") + quote(table.name);\n\nconst validateKeys = Effect.fnUntraced(function* (\n table: PhysicalStorageTable,\n required: ReadonlyArray<ReadonlyArray<string>>,\n physicalKeys: ReadonlyArray<ReadonlyArray<string>>,\n) {\n for (const keys of required) {\n const columns = keys.map((key) => table.columns[key]?.name);\n\n if (\n columns.some((column) => column === undefined) ||\n !physicalKeys.some(\n // A stronger key also guarantees the required tuple's uniqueness.\n (actual) =>\n actual.length > 0 &&\n actual.length <= columns.length &&\n actual.every((column) => columns.includes(column)),\n )\n )\n return yield* PersistenceConfigurationError.make({\n reason: `Missing unique key on ${table.name} (${keys.join(\", \")})`,\n });\n }\n});\n\ninterface MetadataCache {\n active: boolean;\n readonly clients: Map<SqlClient, Map<string, typeof Metadata.Type>>;\n}\n\nconst CurrentMetadata = Context.Reference<MetadataCache | undefined>(\n \"@yielded/auth-persistence/StorageMetadata\",\n { defaultValue: () => undefined },\n);\n\n/** Share metadata only while constructing one service graph. Captured contexts\n * cannot reuse it after acquisition succeeds, fails, or is interrupted. */\nexport const withStorageValidation = Effect.fnUntraced(function* <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.fn.Return<A, E, R> {\n const parent = yield* CurrentMetadata;\n\n if (parent?.active) return yield* effect;\n const cache: MetadataCache = { active: true, clients: new Map() };\n\n return yield* effect.pipe(\n Effect.provideService(CurrentMetadata, cache),\n Effect.ensuring(\n Effect.sync(() => {\n cache.active = false;\n cache.clients.clear();\n }),\n ),\n );\n});\n\n/** Requirements checked against one physical catalog snapshot. */\nexport interface StorageValidation {\n readonly table: PhysicalStorageTable;\n readonly required: ReadonlyArray<ReadonlyArray<string>>;\n}\n\n/** Batch physical metadata reads, reusing only the current acquisition snapshot.\n * Every requirement is still checked; later acquisitions read fresh metadata. */\nexport const validateStorageBatch = Effect.fnUntraced(\n function* (dialect: \"pg\" | \"mysql\" | \"sqlite\", requirements: ReadonlyArray<StorageValidation>) {\n if (requirements.length === 0) return;\n if (\n dialect === \"sqlite\" &&\n requirements.some(({ table }) => table.schema !== undefined && table.schema !== \"main\")\n )\n return yield* PersistenceConfigurationError.make({\n reason: \"SQLite storage must belong to the main database\",\n });\n const client = yield* SqlClient;\n const cache = yield* CurrentMetadata;\n\n const byRelation = cache?.active\n ? (cache.clients.get(client) ?? new Map<string, typeof Metadata.Type>())\n : new Map<string, typeof Metadata.Type>();\n\n if (cache?.active) cache.clients.set(client, byRelation);\n\n const tables = new Map(requirements.map(({ table }) => [qualifiedTableName(table), table]));\n\n const missing = [...tables.entries()].filter(\n ([relation]) => !byRelation.has(`${dialect}:${relation}`),\n );\n\n const relations = missing.map(([relation]) => relation);\n\n if (relations.length > 0) {\n const metadata = yield* dialect === \"sqlite\"\n ? Effect.gen(function* () {\n const requested = client.join(\n \", \",\n false,\n )(missing.map(([relation, table]) => client`(${relation}, ${table.name})`));\n\n return yield* client`\n with requested(relation, name) as (values ${requested})\n select requested.relation,\n (select json_group_array(name) from pragma_table_info(requested.name)) as columns,\n (select json_group_array(json(storage_key.columns)) from (\n select json_group_array(name) as columns from (\n select name from pragma_table_info(requested.name) where pk > 0 order by pk\n )\n union all\n select (select json_group_array(name) from pragma_index_info(i.name)) as columns\n from pragma_index_list(requested.name) i\n where i.\"unique\" = 1 and i.partial = 0\n and not exists(select 1 from pragma_index_info(i.name) where name is null)\n ) storage_key where json_array_length(storage_key.columns) > 0) as keys\n from requested\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(SqliteMetadata)));\n })\n : dialect === \"mysql\"\n ? Effect.gen(function* () {\n const requested = client.join(\n \" union all \",\n false,\n )(\n missing.map(\n ([relation, table]) =>\n client`select ${relation} as relation, ${table.schema ?? null} as schemaName, ${table.name} as tableName`,\n ),\n );\n\n const columns = yield* client`\n select requested.relation, c.column_name as name\n from (${requested}) requested join information_schema.columns c\n on c.table_schema = coalesce(requested.schemaName, database()) and c.table_name = requested.tableName\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(MysqlColumns)));\n\n const indexes = yield* client`\n select requested.relation, i.index_name as name, i.column_name as columnName, i.sub_part as prefix\n from (${requested}) requested join information_schema.statistics i\n on i.table_schema = coalesce(requested.schemaName, database()) and i.table_name = requested.tableName\n where i.non_unique = 0 order by requested.relation, i.index_name, i.seq_in_index\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(MysqlIndexes)));\n\n return relations.map((relation) => {\n const grouped = new Map<string, Array<(typeof indexes)[number]>>();\n\n for (const index of indexes) {\n if (index.relation !== relation) continue;\n const entries = grouped.get(index.name) ?? [];\n\n entries.push(index);\n grouped.set(index.name, entries);\n }\n\n return {\n relation,\n columns: columns\n .filter((column) => column.relation === relation)\n .map((column) => column.name),\n keys: [...grouped.values()]\n .filter((entries) =>\n entries.every((entry) => entry.columnName !== null && entry.prefix === null),\n )\n .map((entries) =>\n entries.flatMap((entry) =>\n entry.columnName === null ? [] : [entry.columnName],\n ),\n ),\n };\n });\n })\n : Effect.gen(function* () {\n const requested = client.join(\n \", \",\n false,\n )(relations.map((relation) => client`(${relation}::text)`));\n\n return yield* client`\n select requested.relation,\n array(select a.attname::text from pg_attribute a\n where a.attrelid = to_regclass(requested.relation)\n and a.attnum > 0 and not a.attisdropped) as columns,\n coalesce((select jsonb_agg(storage_key.columns) from (\n select array_agg(a.attname::text order by k.ordinality) as columns\n from pg_index i\n cross join lateral unnest(i.indkey) with ordinality as k(attnum, ordinality)\n join pg_attribute a on a.attrelid = i.indrelid and a.attnum = k.attnum\n where i.indrelid = to_regclass(requested.relation)\n and i.indisunique and i.indisvalid and i.indimmediate\n and i.indpred is null and i.indexprs is null\n and k.ordinality <= i.indnkeyatts\n group by i.indexrelid\n ) storage_key), '[]'::jsonb) as keys\n from (values ${requested}) requested(relation)\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(PgMetadata)));\n });\n\n for (const row of metadata) byRelation.set(`${dialect}:${row.relation}`, row);\n }\n\n for (const { table, required } of requirements) {\n const relation = qualifiedTableName(table);\n const actual = byRelation.get(`${dialect}:${relation}`);\n\n for (const column of Object.values(table.columns)) {\n if (actual === undefined || !actual.columns.includes(column.name))\n return yield* PersistenceConfigurationError.make({\n reason: `Missing SQL column ${relation}.${column.name}`,\n });\n }\n yield* validateKeys(table, required, actual?.keys ?? []);\n }\n },\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : PersistenceConfigurationError.make({ reason: \"Cannot validate SQL storage metadata\" }),\n ),\n);\n\n/** Validate physical columns and unconditional uniqueness before operations run. */\nexport const validateStorage = (\n dialect: \"pg\" | \"mysql\" | \"sqlite\",\n table: PhysicalStorageTable,\n required: ReadonlyArray<ReadonlyArray<string>>,\n) => validateStorageBatch(dialect, [{ table, required }]);\n"],"mappings":"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"}
1
+ {"version":3,"file":"storage-validation.mjs","names":[],"sources":["../../src/internal/storage-validation.ts"],"sourcesContent":["import { Context, Effect, Schema } from \"effect\";\nimport { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { PersistenceConfigurationError } from \"./configuration\";\nimport type { PhysicalTextColumn } from \"./native-sql-table\";\nimport type { StorageTable } from \"./storage-tables\";\n\nconst Columns = Schema.Array(Schema.String);\nconst Keys = Schema.Array(Columns);\n\nconst TextColumns = Schema.Array(\n Schema.Struct({ name: Schema.String, type: Schema.String, collation: Schema.String }),\n);\n\nconst Metadata = Schema.Struct({\n relation: Schema.String,\n columns: Columns,\n keys: Keys,\n textColumns: TextColumns,\n});\n\nconst PgMetadata = Schema.Array(Metadata);\n\nconst SqliteMetadata = Schema.Array(\n Schema.Struct({\n relation: Schema.String,\n columns: Schema.fromJsonString(Columns),\n keys: Schema.fromJsonString(Keys),\n textColumns: Schema.fromJsonString(TextColumns),\n }),\n);\n\nconst MysqlColumns = Schema.Array(\n Schema.Struct({\n relation: Schema.String,\n name: Schema.String,\n type: Schema.String,\n collation: Schema.NullOr(Schema.String),\n }),\n);\n\nconst MysqlIndexes = Schema.Array(\n Schema.Struct({\n relation: Schema.String,\n name: Schema.String,\n columnName: Schema.NullOr(Schema.String),\n prefix: Schema.NullOr(Schema.Number),\n }),\n);\n\nconst quote = (value: string) => '\"' + value.replaceAll('\"', '\"\"') + '\"';\n\n/** Physical metadata needed for catalog validation; value codecs remain adapter-owned. */\nexport interface PhysicalStorageTable {\n readonly name: string;\n readonly schema?: string;\n readonly columns: Readonly<Record<string, { readonly name: string }>>;\n}\n\nexport const qualifiedTableName = (table: Pick<StorageTable, \"name\" | \"schema\">) =>\n (table.schema === undefined ? \"\" : quote(table.schema) + \".\") + quote(table.name);\n\nconst validateKeys = Effect.fnUntraced(function* (\n table: PhysicalStorageTable,\n required: ReadonlyArray<ReadonlyArray<string>>,\n physicalKeys: ReadonlyArray<ReadonlyArray<string>>,\n) {\n for (const keys of required) {\n const columns = keys.map((key) => table.columns[key]?.name);\n\n if (\n columns.some((column) => column === undefined) ||\n !physicalKeys.some(\n // A stronger key also guarantees the required tuple's uniqueness.\n (actual) =>\n actual.length > 0 &&\n actual.length <= columns.length &&\n actual.every((column) => columns.includes(column)),\n )\n )\n return yield* PersistenceConfigurationError.make({\n reason: `Missing unique key on ${table.name} (${keys.join(\", \")})`,\n });\n }\n});\n\ninterface MetadataCache {\n active: boolean;\n readonly clients: Map<SqlClient, Map<string, typeof Metadata.Type>>;\n}\n\nconst CurrentMetadata = Context.Reference<MetadataCache | undefined>(\n \"@yielded/auth-persistence/StorageMetadata\",\n { defaultValue: () => undefined },\n);\n\n/** Share metadata only while constructing one service graph. Captured contexts\n * cannot reuse it after acquisition succeeds, fails, or is interrupted. */\nexport const withStorageValidation = Effect.fnUntraced(function* <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.fn.Return<A, E, R> {\n const parent = yield* CurrentMetadata;\n\n if (parent?.active) return yield* effect;\n const cache: MetadataCache = { active: true, clients: new Map() };\n\n return yield* effect.pipe(\n Effect.provideService(CurrentMetadata, cache),\n Effect.ensuring(\n Effect.sync(() => {\n cache.active = false;\n cache.clients.clear();\n }),\n ),\n );\n});\n\n/** Requirements checked against one physical catalog snapshot. */\nexport interface StorageValidation {\n readonly table: PhysicalStorageTable;\n readonly required: ReadonlyArray<ReadonlyArray<string>>;\n}\n\ntype Dialect = \"pg\" | \"mysql\" | \"sqlite\";\n\n/** Share physical type, collation and key observations only during acquisition. */\nconst readStorageMetadata = Effect.fnUntraced(\n function* (\n dialect: Dialect,\n physical: ReadonlyArray<Pick<PhysicalStorageTable, \"name\" | \"schema\">>,\n ) {\n if (\n dialect === \"sqlite\" &&\n physical.some((table) => table.schema !== undefined && table.schema !== \"main\")\n )\n return yield* PersistenceConfigurationError.make({\n reason: \"SQLite storage must belong to the main database\",\n });\n const captured = yield* SqlClient;\n const client = captured.withoutTransforms();\n const cache = yield* CurrentMetadata;\n\n const byRelation = cache?.active\n ? (cache.clients.get(captured) ?? new Map<string, typeof Metadata.Type>())\n : new Map<string, typeof Metadata.Type>();\n\n if (cache?.active) cache.clients.set(captured, byRelation);\n\n const tables = new Map(physical.map((table) => [qualifiedTableName(table), table]));\n\n const missing = [...tables.entries()].filter(\n ([relation]) => !byRelation.has(`${dialect}:${relation}`),\n );\n\n const relations = missing.map(([relation]) => relation);\n\n if (relations.length > 0) {\n const metadata = yield* dialect === \"sqlite\"\n ? Effect.gen(function* () {\n const requested = client.join(\n \", \",\n false,\n )(missing.map(([relation, table]) => client`(${relation}, ${table.name})`));\n\n return yield* client`\n with requested(relation, name) as (values ${requested})\n select requested.relation,\n (select json_group_array(name) from pragma_table_info(requested.name)) as columns,\n (select json_group_array(json(storage_key.columns)) from (\n select json_group_array(name) as columns from (\n select name from pragma_table_info(requested.name) where pk > 0 order by pk\n )\n union all\n select (select json_group_array(name) from pragma_index_info(i.name)) as columns\n from pragma_index_list(requested.name) i\n where i.\"unique\" = 1 and i.partial = 0\n and not exists(select 1 from pragma_index_info(i.name) where name is null)\n ) storage_key where json_array_length(storage_key.columns) > 0) as keys,\n (select json_group_array(json_object('name', c.name, 'type', 'TEXT', 'collation', 'BINARY'))\n from pragma_table_info(requested.name) c\n join sqlite_schema s on s.name = requested.name and s.type = 'table'\n where upper(c.type) = 'TEXT' and instr(lower(s.sql), 'collate') = 0) as \"textColumns\"\n from requested\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(SqliteMetadata)));\n })\n : dialect === \"mysql\"\n ? Effect.gen(function* () {\n const requested = client.join(\n \" union all \",\n false,\n )(\n missing.map(\n ([relation, table]) =>\n client`select ${relation} as relation, ${table.schema ?? null} as schemaName, ${table.name} as tableName`,\n ),\n );\n\n const columns = yield* client`\n select requested.relation, c.column_name as name,\n c.data_type as type, c.collation_name as collation\n from (${requested}) requested join information_schema.columns c\n on c.table_schema = coalesce(requested.schemaName, database()) and c.table_name = requested.tableName\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(MysqlColumns)));\n\n const indexes = yield* client`\n select requested.relation, i.index_name as name, i.column_name as columnName, i.sub_part as prefix\n from (${requested}) requested join information_schema.statistics i\n on i.table_schema = coalesce(requested.schemaName, database()) and i.table_name = requested.tableName\n where i.non_unique = 0 order by requested.relation, i.index_name, i.seq_in_index\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(MysqlIndexes)));\n\n return relations.map((relation) => {\n const grouped = new Map<string, Array<(typeof indexes)[number]>>();\n\n for (const index of indexes) {\n if (index.relation !== relation) continue;\n const entries = grouped.get(index.name) ?? [];\n\n entries.push(index);\n grouped.set(index.name, entries);\n }\n\n return {\n relation,\n columns: columns\n .filter((column) => column.relation === relation)\n .map((column) => column.name),\n textColumns: columns.flatMap((column) =>\n column.relation === relation &&\n column.collation !== null &&\n [\"varchar\", \"text\", \"tinytext\", \"mediumtext\", \"longtext\"].includes(column.type)\n ? [{ name: column.name, type: column.type, collation: column.collation }]\n : [],\n ),\n keys: [...grouped.values()]\n .filter((entries) =>\n entries.every((entry) => entry.columnName !== null && entry.prefix === null),\n )\n .map((entries) =>\n entries.flatMap((entry) =>\n entry.columnName === null ? [] : [entry.columnName],\n ),\n ),\n };\n });\n })\n : Effect.gen(function* () {\n const requested = client.join(\n \", \",\n false,\n )(relations.map((relation) => client`(${relation}::text)`));\n\n return yield* client`\n select requested.relation,\n array(select a.attname::text from pg_attribute a\n where a.attrelid = to_regclass(requested.relation)\n and a.attnum > 0 and not a.attisdropped) as columns,\n coalesce((select jsonb_agg(storage_key.columns) from (\n select array_agg(a.attname::text order by k.ordinality) as columns\n from pg_index i\n cross join lateral unnest(i.indkey) with ordinality as k(attnum, ordinality)\n join pg_attribute a on a.attrelid = i.indrelid and a.attnum = k.attnum\n where i.indrelid = to_regclass(requested.relation)\n and i.indisunique and i.indisvalid and i.indimmediate\n and i.indpred is null and i.indexprs is null\n and k.ordinality <= i.indnkeyatts\n group by i.indexrelid\n ) storage_key), '[]'::jsonb) as keys,\n coalesce((select jsonb_agg(jsonb_build_object(\n 'name', a.attname::text, 'type', a.atttypid::text, 'collation', a.attcollation::text))\n from pg_attribute a where a.attrelid = to_regclass(requested.relation)\n and a.attnum > 0 and not a.attisdropped\n and a.atttypid in ('text'::regtype, 'varchar'::regtype)), '[]'::jsonb) as \"textColumns\"\n from (values ${requested}) requested(relation)\n `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(PgMetadata)));\n });\n\n for (const row of metadata) byRelation.set(`${dialect}:${row.relation}`, row);\n }\n\n return byRelation;\n },\n Effect.mapError((error) =>\n Schema.is(PersistenceConfigurationError)(error)\n ? error\n : PersistenceConfigurationError.make({ reason: \"Cannot validate SQL storage metadata\" }),\n ),\n);\n\n/** Batch physical metadata reads, reusing only the current acquisition snapshot.\n * Every requirement is still checked; later acquisitions read fresh metadata. */\nexport const validateStorageBatch = Effect.fnUntraced(function* (\n dialect: Dialect,\n requirements: ReadonlyArray<StorageValidation>,\n) {\n if (requirements.length === 0) return;\n\n const byRelation = yield* readStorageMetadata(\n dialect,\n requirements.map(({ table }) => table),\n );\n\n for (const { table, required } of requirements) {\n const relation = qualifiedTableName(table);\n const actual = byRelation.get(`${dialect}:${relation}`);\n\n for (const column of Object.values(table.columns)) {\n if (actual === undefined || !actual.columns.includes(column.name))\n return yield* PersistenceConfigurationError.make({\n reason: `Missing SQL column ${relation}.${column.name}`,\n });\n }\n yield* validateKeys(table, required, actual?.keys ?? []);\n }\n});\n\n/** Candidate declarations do not establish SQL equality compatibility. SQLite\n * omits column collations, so only TEXT tables without COLLATE qualify. */\nexport const canJoinTextColumns = Effect.fnUntraced(function* (\n columns: ReadonlyArray<PhysicalTextColumn | undefined>,\n) {\n const candidates = columns.filter((column) => column !== undefined);\n\n if (candidates.length !== columns.length || candidates.length === 0) return false;\n const client = yield* SqlClient;\n\n const dialect = client.onDialectOrElse({\n pg: () => \"pg\" as const,\n mysql: () => \"mysql\" as const,\n sqlite: () => \"sqlite\" as const,\n orElse: () => undefined,\n });\n\n if (\n dialect === undefined ||\n (dialect === \"sqlite\" &&\n candidates.some(({ table }) => table.schema !== undefined && table.schema !== \"main\"))\n )\n return false;\n\n const metadata = yield* readStorageMetadata(\n dialect,\n candidates.map(({ table }) => table),\n );\n\n const physical = candidates.map(({ table, name }) =>\n metadata\n .get(`${dialect}:${qualifiedTableName(table)}`)\n ?.textColumns.find((column) => column.name === name),\n );\n\n const first = physical[0];\n\n return (\n first !== undefined &&\n physical.every((column) => column?.type === first.type && column.collation === first.collation)\n );\n});\n\n/** Validate physical columns and unconditional uniqueness before operations run. */\nexport const validateStorage = (\n dialect: \"pg\" | \"mysql\" | \"sqlite\",\n table: PhysicalStorageTable,\n required: ReadonlyArray<ReadonlyArray<string>>,\n) => validateStorageBatch(dialect, [{ table, required }]);\n"],"mappings":"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"}
@@ -2,9 +2,9 @@ import { PersistenceMappingError } from "./mapping-error.mjs";
2
2
  import { sqlBatchAssertion } from "./d1-planning.mjs";
3
3
  import { exactSqlText, executeSqlChange } from "./sql-change.mjs";
4
4
  import { CurrentSqlCommit, SqlBatchCommit, appendSqlBatchStatement, makeSqlCommitExecutor, registerSqlBatchPostcondition, registerSqlPostcondition } from "./sql-commit.mjs";
5
- import { digest, randomId } from "./crypto.mjs";
6
5
  import { assessSessionAt, sameSessionRevision } from "./session-native-state.mjs";
7
6
  import { makeConditionalSqlInsert } from "./session-native-record.mjs";
7
+ import { digest, randomId } from "./crypto.mjs";
8
8
  import { makeNativeSessionPending, prepareNativeSession } from "./session-native-pending.mjs";
9
9
  import { Crypto, DateTime, Effect, Option, Schema } from "effect";
10
10
  import { AuthenticationRequirement, SecurityRevision } from "@yielded/auth/Sessions";
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@yielded/auth-persistence","version":"0.1.0-beta.30","dependencies":{"@yielded/auth":"0.1.0-beta.30","@yielded/crypto":"0.1.0-beta.30"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/sql-libsql":"4.0.0","@effect/sql-sqlite-node":"4.0.0","@effect/vitest":"4.0.0","@yielded/jose":"0.1.0-beta.30","effect":"4.0.0","typescript":"7.0.2","vite-plus":"0.3.3"},"peerDependencies":{"effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Adapter":{"types":"./dist/Adapter.d.mts","default":"./dist/Adapter.mjs"},"./OAuthPersistence":{"types":"./dist/OAuthPersistence.d.mts","default":"./dist/OAuthPersistence.mjs"},"./Testing":{"types":"./dist/Testing.d.mts","default":"./dist/Testing.mjs"}},"description":"Direct Effect SQL persistence and shared storage contracts for Yielded Auth.","homepage":"https://yielded.dev/auth/","bugs":{"url":"https://github.com/yielded-dev/auth/issues"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yielded-dev/auth.git","directory":"packages/auth-persistence"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
1
+ {"name":"@yielded/auth-persistence","version":"0.1.0-beta.32","dependencies":{"@yielded/auth":"0.1.0-beta.32","@yielded/crypto":"0.1.0-beta.32"},"devDependencies":{"@effect/platform-node":"4.0.0","@effect/sql-libsql":"4.0.0","@effect/sql-sqlite-node":"4.0.0","@effect/vitest":"4.0.0","@yielded/jose":"0.1.0-beta.32","effect":"4.0.0","typescript":"7.0.2","vite":"npm:@voidzero-dev/vite-plus-core@1.1.0","vite-plus":"1.1.0","vitest":"5.0.3"},"peerDependencies":{"effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Adapter":{"types":"./dist/Adapter.d.mts","default":"./dist/Adapter.mjs"},"./OAuthPersistence":{"types":"./dist/OAuthPersistence.d.mts","default":"./dist/OAuthPersistence.mjs"},"./Testing":{"types":"./dist/Testing.d.mts","default":"./dist/Testing.mjs"}},"description":"Direct Effect SQL persistence and shared storage contracts for Yielded Auth.","homepage":"https://yielded.dev/auth/","bugs":{"url":"https://github.com/yielded-dev/auth/issues"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yielded-dev/auth.git","directory":"packages/auth-persistence"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}