@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.
- package/README.md +11 -7
- package/dist/Adapter.d.mts +1 -1
- package/dist/Adapter.mjs +8 -8
- package/dist/OAuthPersistence.mjs +10 -5
- package/dist/OAuthPersistence.mjs.map +1 -1
- package/dist/internal/configuration.d.mts +49 -13
- package/dist/internal/configuration.mjs.map +1 -1
- package/dist/internal/email-native.mjs +10 -2
- package/dist/internal/email-native.mjs.map +1 -1
- package/dist/internal/email-policy.mjs +3 -2
- package/dist/internal/email-policy.mjs.map +1 -1
- package/dist/internal/models/oauth-model.d.mts +4 -2
- package/dist/internal/models/oauth-model.mjs.map +1 -1
- package/dist/internal/models/password-model.d.mts +2 -1
- package/dist/internal/models/password-model.mjs.map +1 -1
- package/dist/internal/oauth/native-accounts.d.mts +1 -1
- package/dist/internal/oauth/native-accounts.mjs +6 -6
- package/dist/internal/oauth/native-accounts.mjs.map +1 -1
- package/dist/internal/oauth/native-connected.d.mts +1 -1
- package/dist/internal/oauth/native-connected.mjs +1 -1
- package/dist/internal/oauth/native-registration.mjs +19 -8
- package/dist/internal/oauth/native-registration.mjs.map +1 -1
- package/dist/internal/oauth/native-sign-in.d.mts +1 -1
- package/dist/internal/oauth/native-sign-in.mjs +1 -1
- package/dist/internal/oauth/native-state.mjs +21 -11
- package/dist/internal/oauth/native-state.mjs.map +1 -1
- package/dist/internal/oauth/state.d.mts +1 -1
- package/dist/internal/oauth/state.mjs +1 -1
- package/dist/internal/oauth-managed.mjs +182 -0
- package/dist/internal/oauth-managed.mjs.map +1 -0
- package/dist/internal/passkey-managed.mjs +5 -3
- package/dist/internal/passkey-managed.mjs.map +1 -1
- package/dist/internal/passkey-native-registration.mjs +1 -1
- package/dist/internal/passkey-native-state.mjs +33 -23
- package/dist/internal/passkey-native-state.mjs.map +1 -1
- package/dist/internal/passkey-native-write.mjs +3 -3
- package/dist/internal/passkey-native-write.mjs.map +1 -1
- package/dist/internal/passkey-native.d.mts +15 -7
- package/dist/internal/passkey-native.mjs +2 -1
- package/dist/internal/passkey-native.mjs.map +1 -1
- package/dist/internal/passkey-policy.mjs +9 -7
- package/dist/internal/passkey-policy.mjs.map +1 -1
- package/dist/internal/password-native.mjs +22 -5
- package/dist/internal/password-native.mjs.map +1 -1
- package/dist/internal/password-policy.mjs +7 -6
- package/dist/internal/password-policy.mjs.map +1 -1
- package/dist/internal/password-registration-native.d.mts +1 -1
- package/dist/internal/persistence.d.mts +3 -0
- package/dist/internal/persistence.mjs +99 -42
- package/dist/internal/persistence.mjs.map +1 -1
- package/dist/internal/phone-native.mjs +1 -1
- package/dist/internal/phone-native.mjs.map +1 -1
- package/dist/internal/registration.mjs +17 -8
- package/dist/internal/registration.mjs.map +1 -1
- package/dist/internal/session-native-authority.mjs +8 -6
- package/dist/internal/session-native-authority.mjs.map +1 -1
- package/dist/internal/session-native-login.d.mts +2 -2
- package/dist/internal/session-native-login.mjs +4 -2
- package/dist/internal/session-native-login.mjs.map +1 -1
- package/dist/internal/session-native-pending-read.mjs +26 -20
- package/dist/internal/session-native-pending-read.mjs.map +1 -1
- package/dist/internal/session-native-record.mjs +6 -4
- package/dist/internal/session-native-record.mjs.map +1 -1
- package/dist/internal/session-native-state.mjs +24 -8
- package/dist/internal/session-native-state.mjs.map +1 -1
- package/dist/internal/session-native-stateful.d.mts +2 -2
- package/dist/internal/session-native-stateful.mjs +67 -38
- package/dist/internal/session-native-stateful.mjs.map +1 -1
- package/dist/internal/session-native-step-up.d.mts +2 -2
- package/dist/internal/session-native-step-up.mjs +17 -19
- package/dist/internal/session-native-step-up.mjs.map +1 -1
- package/dist/internal/session-native-validity.mjs +3 -1
- package/dist/internal/session-native-validity.mjs.map +1 -1
- package/dist/internal/sql-authority.mjs +5 -1
- package/dist/internal/sql-authority.mjs.map +1 -1
- package/dist/internal/sql-storage-validation.mjs +2 -1
- package/dist/internal/sql-storage-validation.mjs.map +1 -1
- package/dist/internal/sql-table.d.mts +1 -0
- package/dist/internal/sql-table.mjs.map +1 -1
- package/dist/internal/sqlite-policy-guard.mjs +23 -0
- package/dist/internal/sqlite-policy-guard.mjs.map +1 -0
- package/dist/internal/step-up-state.mjs +19 -6
- package/dist/internal/step-up-state.mjs.map +1 -1
- package/dist/internal/storage-mapping.d.mts +9 -5
- package/dist/internal/storage-mapping.mjs +17 -5
- package/dist/internal/storage-mapping.mjs.map +1 -1
- package/dist/internal/storage-tables.d.mts +237 -0
- package/dist/internal/storage-tables.mjs +68 -0
- package/dist/internal/storage-tables.mjs.map +1 -1
- package/dist/internal/storage-validation.d.mts +3 -1
- package/dist/internal/storage-validation.mjs +70 -17
- package/dist/internal/storage-validation.mjs.map +1 -1
- package/dist/internal/totp-native.mjs +1 -1
- package/package.json +1 -1
- package/src/OAuthPersistence.ts +7 -2
- package/src/internal/configuration.ts +72 -15
- package/src/internal/email-native.ts +16 -6
- package/src/internal/email-policy.ts +12 -3
- package/src/internal/models/oauth-model.ts +7 -1
- package/src/internal/models/password-model.ts +2 -1
- package/src/internal/oauth/native-accounts.ts +3 -3
- package/src/internal/oauth/native-registration.ts +17 -6
- package/src/internal/oauth/native-state.ts +38 -18
- package/src/internal/oauth-managed.ts +277 -0
- package/src/internal/passkey-managed.ts +8 -2
- package/src/internal/passkey-native-state.ts +59 -52
- package/src/internal/passkey-native-write.ts +2 -2
- package/src/internal/passkey-native.ts +4 -2
- package/src/internal/passkey-policy.ts +13 -9
- package/src/internal/password-native.ts +45 -11
- package/src/internal/password-policy.ts +16 -8
- package/src/internal/persistence.ts +360 -204
- package/src/internal/phone-native.ts +1 -1
- package/src/internal/registration.ts +53 -40
- package/src/internal/session-native-authority.ts +26 -8
- package/src/internal/session-native-login.ts +9 -4
- package/src/internal/session-native-pending-read.ts +46 -33
- package/src/internal/session-native-record.ts +6 -4
- package/src/internal/session-native-state.ts +32 -10
- package/src/internal/session-native-stateful.ts +147 -52
- package/src/internal/session-native-step-up.ts +31 -24
- package/src/internal/session-native-validity.ts +3 -1
- package/src/internal/sql-authority.ts +5 -1
- package/src/internal/sql-storage-validation.ts +53 -46
- package/src/internal/sql-table.ts +1 -0
- package/src/internal/sqlite-policy-guard.ts +34 -0
- package/src/internal/step-up-state.ts +43 -6
- package/src/internal/storage-mapping.ts +40 -4
- package/src/internal/storage-tables.ts +76 -0
- 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:
|
|
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
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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(
|
|
58
|
-
if (cache?.active) cache.clients.set(
|
|
59
|
-
const missing = [...new Map(
|
|
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
|
-
}
|
|
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.
|
|
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"}}
|