@yielded/auth-persistence-drizzle 0.1.0-beta.30 → 0.1.0-beta.31
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 +12 -8
- package/dist/D1.d.mts +9 -0
- package/dist/D1.mjs +11 -1
- package/dist/D1.mjs.map +1 -1
- package/dist/Libsql.mjs +1 -1
- package/dist/SqliteBun.mjs +1 -1
- package/dist/SqliteNode.mjs +1 -1
- package/dist/SqliteWasm.mjs +1 -1
- package/dist/drizzle/passkey/target.d.mts +1 -1
- package/dist/internal/drizzle-d1-migrations.d.mts +14 -0
- package/dist/internal/drizzle-d1-migrations.mjs +83 -0
- package/dist/internal/drizzle-d1-migrations.mjs.map +1 -0
- package/dist/internal/drizzle-sqlite.mjs +7 -2
- package/dist/internal/drizzle-sqlite.mjs.map +1 -1
- package/package.json +1 -1
- package/src/D1.ts +12 -0
- package/src/internal/drizzle-d1-migrations.ts +150 -0
- package/src/internal/drizzle-sqlite.ts +14 -2
package/README.md
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# @yielded/auth-persistence-drizzle
|
|
2
2
|
|
|
3
3
|
Drizzle bindings, table mappings, and migration helpers for Yielded Auth. This
|
|
4
|
-
package
|
|
5
|
-
|
|
6
|
-
same strategy statements and commit owner; driver modules supply the physical
|
|
7
|
-
transaction or atomic-batch capability.
|
|
4
|
+
package connects Drizzle tables to `@yielded/auth-persistence`; driver modules
|
|
5
|
+
supply transactions or atomic batches.
|
|
8
6
|
|
|
9
7
|
Import mapping helpers from the root and a driver from its explicit module, such
|
|
10
8
|
as `/Postgres` or `/SqliteBun`. Install `drizzle-orm` and the corresponding Effect
|
|
@@ -12,16 +10,22 @@ SQL driver. Supply Effect `Crypto` to persistence construction and transaction
|
|
|
12
10
|
coordinators. Composed drivers expose `AuthPersistence`; explicit driver modules
|
|
13
11
|
also expose lower-level factories.
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
upgrade to a compatible upstream Drizzle release.
|
|
13
|
+
Bun consumers need the
|
|
14
|
+
[@yielded/drizzle-effect-v4-patch](../drizzle-effect-v4-patch/README.md).
|
|
15
|
+
Use its supported Drizzle release and commit the generated patch and lockfile.
|
|
19
16
|
|
|
20
17
|
Applications own subjects, policy, claims, delivery, and database connections.
|
|
21
18
|
Drizzle Kit generates migrations from managed or application-declared tables.
|
|
22
19
|
Provide `AuthPersistence.migrationsLayer({ migrationsFolder })` explicitly to
|
|
23
20
|
apply those files before starting auth.
|
|
24
21
|
|
|
22
|
+
D1 also supports managed tables through `AuthPersistence.make(auth).managed(...)`.
|
|
23
|
+
Supply the primary D1 binding through `D1Client.layer` and apply generated SQL
|
|
24
|
+
with `AuthPersistence.migrationsLayer({ migrations })` using bundled SQL,
|
|
25
|
+
or with your existing D1 migration runner. Its composed services use atomic batches.
|
|
26
|
+
Password provisioning returns subject values through `Persistence.Provisioning`;
|
|
27
|
+
the library commits the account and credentials together.
|
|
28
|
+
|
|
25
29
|
SQLite/D1 and PostgreSQL driver modules also expose `OAuthProxyPersistence` for
|
|
26
30
|
[callback proxy storage](../../docs/src/content/docs/reference/oauth.md#callback-server).
|
|
27
31
|
|
package/dist/D1.d.mts
CHANGED
|
@@ -20,6 +20,7 @@ import { NativeDriverTransaction } from "./drizzle/driver-types.mjs";
|
|
|
20
20
|
import { SuppliedService } from "./drizzle/SuppliedService.mjs";
|
|
21
21
|
import { OAuthProxyPersistence } from "./internal/drizzle-sqlite-oauth-proxy.mjs";
|
|
22
22
|
import { Database } from "./drizzle/d1-database.mjs";
|
|
23
|
+
import { d1MigrationsLayer } from "./internal/drizzle-d1-migrations.mjs";
|
|
23
24
|
import { AnyRelations } from "drizzle-orm";
|
|
24
25
|
import { Layer } from "effect";
|
|
25
26
|
import { AnySQLiteTable } from "drizzle-orm/sqlite-core";
|
|
@@ -138,6 +139,14 @@ export declare const makeIdentityServices: <Subject extends AnySQLiteTable, Iden
|
|
|
138
139
|
readonly bind: (subjectId: import("@yielded/auth/Schema").SubjectId, identity: import("@yielded/auth/Identity").ExternalIdentity) => import("effect/Effect").Effect<void, import("@yielded/auth/Identity").IdentityConflict | import("@yielded/auth/Identity").IdentityUnavailable>;
|
|
139
140
|
};
|
|
140
141
|
}, import("@yielded/auth/Identity").IdentityUnavailable, Database>;
|
|
142
|
+
/** Managed or mapped auth tables committed through D1's atomic batches.
|
|
143
|
+
* Supply D1Client.layer({ db: env.DB }) with the primary database binding.
|
|
144
|
+
* Registration supplies subject values; the account and credentials commit together.
|
|
145
|
+
*/
|
|
146
|
+
export declare const AuthPersistence: {
|
|
147
|
+
make: <C extends import("@yielded/auth-persistence/Adapter").ClaimsCodec, const Id extends string, const A extends import("@yielded/auth-persistence/Adapter").Definition<C, Id>>(auth: A & import("@yielded/auth-persistence/Adapter").Definition<C, Id>) => import("@yielded/auth-persistence/Adapter").BoundPersistence<import("drizzle-orm/sqlite-core").SQLiteTable<import("drizzle-orm/sqlite-core").TableConfig>, D1Client, C, Id, A>;
|
|
148
|
+
migrationsLayer: typeof d1MigrationsLayer;
|
|
149
|
+
};
|
|
141
150
|
type D1Database = EffectSQLiteD1Database<AnyRelations> & {
|
|
142
151
|
readonly $client: D1Client;
|
|
143
152
|
};
|
package/dist/D1.mjs
CHANGED
|
@@ -5,6 +5,8 @@ import { makeD1ExternalIdentityServices, makeD1IdentityServices, makeD1SubjectPr
|
|
|
5
5
|
import { makeProofTarget } from "./drizzle/proof-drivers.mjs";
|
|
6
6
|
import { makePasswordTarget } from "./drizzle/password-drivers.mjs";
|
|
7
7
|
import { makeEmailTarget } from "./drizzle/email-drivers.mjs";
|
|
8
|
+
import { d1MigrationsLayer } from "./internal/drizzle-d1-migrations.mjs";
|
|
9
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
8
10
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers.mjs";
|
|
9
11
|
import { makePasskeyTarget } from "./drizzle/passkey-drivers.mjs";
|
|
10
12
|
import { makePhoneTarget } from "./drizzle/phone-drivers.mjs";
|
|
@@ -28,6 +30,14 @@ const { makeEmailSignInServices, makeEmailAddressServices, makeEmailRegistration
|
|
|
28
30
|
const makeSubjectProvisioningServices = (mapping) => makeD1SubjectProvisioningServices(mapping);
|
|
29
31
|
const makeExternalIdentityServices = (mapping) => makeD1ExternalIdentityServices(mapping);
|
|
30
32
|
const makeIdentityServices = (mapping) => makeD1IdentityServices(mapping);
|
|
33
|
+
/** Managed or mapped auth tables committed through D1's atomic batches.
|
|
34
|
+
* Supply D1Client.layer({ db: env.DB }) with the primary database binding.
|
|
35
|
+
* Registration supplies subject values; the account and credentials commit together.
|
|
36
|
+
*/
|
|
37
|
+
const AuthPersistence = {
|
|
38
|
+
...sqlitePersistence(makeWithDefaults({}), { batch: true }),
|
|
39
|
+
migrationsLayer: d1MigrationsLayer
|
|
40
|
+
};
|
|
31
41
|
const { makeOAuthAccountsServices, makeOAuthSignInServices, makeOAuthRegistrationIntentServices, makeOAuthRegistrationServices, coordinateOAuthRegistration, coordinateOAuthSignIn, coordinateOAuthRegistrationIntents, coordinateOAuthAccounts, makeOAuthConnectedServices, makeOAuthConnectedRevocationServices, coordinateOAuthConnected, coordinateOAuthConnectedRevocations } = makeOAuthTarget(Database, {
|
|
32
42
|
mode: "batch",
|
|
33
43
|
dialect: "sqlite"
|
|
@@ -51,6 +61,6 @@ const { makeTotpPersistenceServices, coordinateTotpPersistence } = makeTotpTarge
|
|
|
51
61
|
dialect: "sqlite"
|
|
52
62
|
});
|
|
53
63
|
//#endregion
|
|
54
|
-
export { D1BatchStatements, Database, OAuthProxyPersistence, coordinateAuthenticationAuthority, coordinateEmailAddress, coordinateEmailRegistration, coordinateOAuthAccounts, coordinateOAuthConnected, coordinateOAuthConnectedRevocations, coordinateOAuthRegistration, coordinateOAuthRegistrationIntents, coordinateOAuthSignIn, coordinatePasskeyManagement, coordinatePasskeyPersistence, coordinatePasskeyRegistration, coordinatePasskeyRegistrationCeremony, coordinatePasswordPersistence, coordinatePasswordRegistration, coordinatePendingAuthentication, coordinatePhonePersistence, coordinateProofPersistence, coordinateSessionStepUp, coordinateSignedSessionValidity, coordinateStatefulSessions, coordinateTotpPersistence, databaseLayer, makeAuthenticationAuthorityServices, makeEmailAddressServices, makeEmailRegistrationServices, makeEmailSignInServices, makeExternalIdentityServices, makeIdentityServices, makeOAuthAccountsServices, makeOAuthConnectedRevocationServices, makeOAuthConnectedServices, makeOAuthRegistrationIntentServices, makeOAuthRegistrationServices, makeOAuthSignInServices, makePasskeyCredentialServices, makePasskeyManagementServices, makePasskeyPersistenceServices, makePasskeyRegistrationCeremonyServices, makePasskeyRegistrationServices, makePasswordPersistenceServices, makePasswordRegistrationServices, makePendingAuthenticationServices, makePhonePersistenceServices, makeProofPersistenceServices, makeSessionCleanupServices, makeSessionStepUpServices, makeSignedSessionValidityServices, makeStatefulSessionServices, makeSubjectProvisioningServices, makeTotpPersistenceServices };
|
|
64
|
+
export { AuthPersistence, D1BatchStatements, Database, OAuthProxyPersistence, coordinateAuthenticationAuthority, coordinateEmailAddress, coordinateEmailRegistration, coordinateOAuthAccounts, coordinateOAuthConnected, coordinateOAuthConnectedRevocations, coordinateOAuthRegistration, coordinateOAuthRegistrationIntents, coordinateOAuthSignIn, coordinatePasskeyManagement, coordinatePasskeyPersistence, coordinatePasskeyRegistration, coordinatePasskeyRegistrationCeremony, coordinatePasswordPersistence, coordinatePasswordRegistration, coordinatePendingAuthentication, coordinatePhonePersistence, coordinateProofPersistence, coordinateSessionStepUp, coordinateSignedSessionValidity, coordinateStatefulSessions, coordinateTotpPersistence, databaseLayer, makeAuthenticationAuthorityServices, makeEmailAddressServices, makeEmailRegistrationServices, makeEmailSignInServices, makeExternalIdentityServices, makeIdentityServices, makeOAuthAccountsServices, makeOAuthConnectedRevocationServices, makeOAuthConnectedServices, makeOAuthRegistrationIntentServices, makeOAuthRegistrationServices, makeOAuthSignInServices, makePasskeyCredentialServices, makePasskeyManagementServices, makePasskeyPersistenceServices, makePasskeyRegistrationCeremonyServices, makePasskeyRegistrationServices, makePasswordPersistenceServices, makePasswordRegistrationServices, makePendingAuthenticationServices, makePhonePersistenceServices, makeProofPersistenceServices, makeSessionCleanupServices, makeSessionStepUpServices, makeSignedSessionValidityServices, makeStatefulSessionServices, makeSubjectProvisioningServices, makeTotpPersistenceServices };
|
|
55
65
|
|
|
56
66
|
//# sourceMappingURL=D1.mjs.map
|
package/dist/D1.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"D1.mjs","names":[],"sources":["../src/D1.ts"],"sourcesContent":["export { OAuthProxyPersistence } from \"./internal/drizzle-sqlite-oauth-proxy\";\n\nimport type { D1Client } from \"@effect/sql-d1/D1Client\";\nimport type { AnyRelations } from \"drizzle-orm\";\nimport { type EffectSQLiteD1Database, makeWithDefaults } from \"drizzle-orm/effect-d1\";\nimport type { AnySQLiteTable } from \"drizzle-orm/sqlite-core\";\n\nimport { Database } from \"./drizzle/d1-database\";\nexport { Database } from \"./drizzle/d1-database\";\n\nimport {\n makeD1ExternalIdentityServices,\n makeD1IdentityServices,\n makeD1SubjectProvisioningServices,\n} from \"./drizzle/d1-identity\";\nimport { makeProofTarget } from \"./drizzle/proof-drivers\";\n\nexport const { coordinateProofPersistence, makeProofPersistenceServices } = makeProofTarget(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\nimport { makePasswordTarget } from \"./drizzle/password-drivers\";\n\nexport const {\n coordinatePasswordPersistence,\n coordinatePasswordRegistration,\n makePasswordPersistenceServices,\n makePasswordRegistrationServices,\n} = makePasswordTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport { makeEmailTarget } from \"./drizzle/email-drivers\";\n\nexport const {\n makeEmailSignInServices,\n makeEmailAddressServices,\n makeEmailRegistrationServices,\n coordinateEmailAddress,\n coordinateEmailRegistration,\n} = makeEmailTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport type {\n D1GeneratedIdentityMapping,\n D1SubjectProvisioningMapping,\n D1ExternalIdentityMapping,\n} from \"./drizzle/model\";\n\nexport const makeSubjectProvisioningServices = <\n Subject extends AnySQLiteTable,\n Identifier extends AnySQLiteTable,\n Request extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1SubjectProvisioningMapping<Subject, Identifier, Request, NativeId>,\n) => makeD1SubjectProvisioningServices(mapping);\n\nexport const makeExternalIdentityServices = <\n Subject extends AnySQLiteTable,\n External extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1ExternalIdentityMapping<Subject, External, NativeId>,\n) => makeD1ExternalIdentityServices(mapping);\n\nexport const makeIdentityServices = <\n Subject extends AnySQLiteTable,\n Identifier extends AnySQLiteTable,\n External extends AnySQLiteTable,\n Request extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1GeneratedIdentityMapping<Subject, Identifier, External, Request, NativeId>,\n) => makeD1IdentityServices(mapping);\n\nimport { Layer } from \"effect\";\n\nimport type { D1BatchStatements } from \"./drizzle/D1BatchStatements\";\nimport { makeOAuthTarget } from \"./drizzle/oauth-drivers\";\nimport type { OAuthD1Mapping } from \"./drizzle/oauth-model\";\nimport { makePasskeyTarget } from \"./drizzle/passkey-drivers\";\nimport type { D1PasskeyMapping } from \"./drizzle/passkey-model\";\n\ntype D1Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };\ntype D1Table = AnySQLiteTable<{ dialect: \"sqlite\" }>;\n\nexport const {\n makeOAuthAccountsServices,\n makeOAuthSignInServices,\n makeOAuthRegistrationIntentServices,\n makeOAuthRegistrationServices,\n coordinateOAuthRegistration,\n coordinateOAuthSignIn,\n coordinateOAuthRegistrationIntents,\n coordinateOAuthAccounts,\n makeOAuthConnectedServices,\n makeOAuthConnectedRevocationServices,\n coordinateOAuthConnected,\n coordinateOAuthConnectedRevocations,\n} = makeOAuthTarget<Database, D1Database, D1Table, OAuthD1Mapping, D1BatchStatements>(Database, {\n mode: \"batch\",\n dialect: \"sqlite\",\n});\n\nexport const {\n makePasskeyCredentialServices,\n makePasskeyPersistenceServices,\n makePasskeyRegistrationCeremonyServices,\n coordinatePasskeyPersistence,\n coordinatePasskeyRegistrationCeremony,\n makePasskeyManagementServices,\n makePasskeyRegistrationServices,\n coordinatePasskeyManagement,\n coordinatePasskeyRegistration,\n} = makePasskeyTarget<Database, D1Database, D1Table, D1PasskeyMapping, D1BatchStatements>(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\nexport { D1BatchStatements } from \"./drizzle/D1BatchStatements\";\n\nimport { makePhoneTarget } from \"./drizzle/phone-drivers\";\n\nexport const { makePhonePersistenceServices, coordinatePhonePersistence } = makePhoneTarget(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\n/** Construct Drizzle from the driver's SQL-client Layer. */\nexport const databaseLayer = Layer.effect(Database, makeWithDefaults({}));\n\nimport { makeSessionTarget } from \"./drizzle/session-drivers\";\n\nexport const {\n coordinateAuthenticationAuthority,\n coordinatePendingAuthentication,\n coordinateSignedSessionValidity,\n coordinateStatefulSessions,\n makeAuthenticationAuthorityServices,\n makePendingAuthenticationServices,\n makeSignedSessionValidityServices,\n makeStatefulSessionServices,\n makeSessionStepUpServices,\n coordinateSessionStepUp,\n makeSessionCleanupServices,\n} = makeSessionTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport { makeTotpTarget } from \"./drizzle/totp-target\";\n\nexport const { makeTotpPersistenceServices, coordinateTotpPersistence } = makeTotpTarget(Database, {\n mode: \"batch\",\n dialect: \"sqlite\",\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"D1.mjs","names":[],"sources":["../src/D1.ts"],"sourcesContent":["export { OAuthProxyPersistence } from \"./internal/drizzle-sqlite-oauth-proxy\";\n\nimport type { D1Client } from \"@effect/sql-d1/D1Client\";\nimport type { AnyRelations } from \"drizzle-orm\";\nimport { type EffectSQLiteD1Database, makeWithDefaults } from \"drizzle-orm/effect-d1\";\nimport type { AnySQLiteTable } from \"drizzle-orm/sqlite-core\";\n\nimport { Database } from \"./drizzle/d1-database\";\nexport { Database } from \"./drizzle/d1-database\";\n\nimport {\n makeD1ExternalIdentityServices,\n makeD1IdentityServices,\n makeD1SubjectProvisioningServices,\n} from \"./drizzle/d1-identity\";\nimport { makeProofTarget } from \"./drizzle/proof-drivers\";\n\nexport const { coordinateProofPersistence, makeProofPersistenceServices } = makeProofTarget(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\nimport { makePasswordTarget } from \"./drizzle/password-drivers\";\n\nexport const {\n coordinatePasswordPersistence,\n coordinatePasswordRegistration,\n makePasswordPersistenceServices,\n makePasswordRegistrationServices,\n} = makePasswordTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport { makeEmailTarget } from \"./drizzle/email-drivers\";\n\nexport const {\n makeEmailSignInServices,\n makeEmailAddressServices,\n makeEmailRegistrationServices,\n coordinateEmailAddress,\n coordinateEmailRegistration,\n} = makeEmailTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport type {\n D1GeneratedIdentityMapping,\n D1SubjectProvisioningMapping,\n D1ExternalIdentityMapping,\n} from \"./drizzle/model\";\n\nexport const makeSubjectProvisioningServices = <\n Subject extends AnySQLiteTable,\n Identifier extends AnySQLiteTable,\n Request extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1SubjectProvisioningMapping<Subject, Identifier, Request, NativeId>,\n) => makeD1SubjectProvisioningServices(mapping);\n\nexport const makeExternalIdentityServices = <\n Subject extends AnySQLiteTable,\n External extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1ExternalIdentityMapping<Subject, External, NativeId>,\n) => makeD1ExternalIdentityServices(mapping);\n\nexport const makeIdentityServices = <\n Subject extends AnySQLiteTable,\n Identifier extends AnySQLiteTable,\n External extends AnySQLiteTable,\n Request extends AnySQLiteTable,\n NativeId,\n>(\n mapping: D1GeneratedIdentityMapping<Subject, Identifier, External, Request, NativeId>,\n) => makeD1IdentityServices(mapping);\n\nimport { Layer } from \"effect\";\n\nimport { d1MigrationsLayer } from \"./internal/drizzle-d1-migrations\";\nimport { sqlitePersistence } from \"./internal/drizzle-sqlite\";\n\n/** Managed or mapped auth tables committed through D1's atomic batches.\n * Supply D1Client.layer({ db: env.DB }) with the primary database binding.\n * Registration supplies subject values; the account and credentials commit together.\n */\nexport const AuthPersistence = {\n ...sqlitePersistence(makeWithDefaults({}), { batch: true }),\n migrationsLayer: d1MigrationsLayer,\n};\n\nimport type { D1BatchStatements } from \"./drizzle/D1BatchStatements\";\nimport { makeOAuthTarget } from \"./drizzle/oauth-drivers\";\nimport type { OAuthD1Mapping } from \"./drizzle/oauth-model\";\nimport { makePasskeyTarget } from \"./drizzle/passkey-drivers\";\nimport type { D1PasskeyMapping } from \"./drizzle/passkey-model\";\n\ntype D1Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };\ntype D1Table = AnySQLiteTable<{ dialect: \"sqlite\" }>;\n\nexport const {\n makeOAuthAccountsServices,\n makeOAuthSignInServices,\n makeOAuthRegistrationIntentServices,\n makeOAuthRegistrationServices,\n coordinateOAuthRegistration,\n coordinateOAuthSignIn,\n coordinateOAuthRegistrationIntents,\n coordinateOAuthAccounts,\n makeOAuthConnectedServices,\n makeOAuthConnectedRevocationServices,\n coordinateOAuthConnected,\n coordinateOAuthConnectedRevocations,\n} = makeOAuthTarget<Database, D1Database, D1Table, OAuthD1Mapping, D1BatchStatements>(Database, {\n mode: \"batch\",\n dialect: \"sqlite\",\n});\n\nexport const {\n makePasskeyCredentialServices,\n makePasskeyPersistenceServices,\n makePasskeyRegistrationCeremonyServices,\n coordinatePasskeyPersistence,\n coordinatePasskeyRegistrationCeremony,\n makePasskeyManagementServices,\n makePasskeyRegistrationServices,\n coordinatePasskeyManagement,\n coordinatePasskeyRegistration,\n} = makePasskeyTarget<Database, D1Database, D1Table, D1PasskeyMapping, D1BatchStatements>(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\nexport { D1BatchStatements } from \"./drizzle/D1BatchStatements\";\n\nimport { makePhoneTarget } from \"./drizzle/phone-drivers\";\n\nexport const { makePhonePersistenceServices, coordinatePhonePersistence } = makePhoneTarget(\n Database,\n { mode: \"batch\", dialect: \"sqlite\" },\n);\n\n/** Construct Drizzle from the driver's SQL-client Layer. */\nexport const databaseLayer = Layer.effect(Database, makeWithDefaults({}));\n\nimport { makeSessionTarget } from \"./drizzle/session-drivers\";\n\nexport const {\n coordinateAuthenticationAuthority,\n coordinatePendingAuthentication,\n coordinateSignedSessionValidity,\n coordinateStatefulSessions,\n makeAuthenticationAuthorityServices,\n makePendingAuthenticationServices,\n makeSignedSessionValidityServices,\n makeStatefulSessionServices,\n makeSessionStepUpServices,\n coordinateSessionStepUp,\n makeSessionCleanupServices,\n} = makeSessionTarget(Database, { mode: \"batch\", dialect: \"sqlite\" });\n\nimport { makeTotpTarget } from \"./drizzle/totp-target\";\n\nexport const { makeTotpPersistenceServices, coordinateTotpPersistence } = makeTotpTarget(Database, {\n mode: \"batch\",\n dialect: \"sqlite\",\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAa,EAAE,4BAA4B,iCAAiC,gBAC1E,UACA;CAAE,MAAM;CAAS,SAAS;AAAS,CACrC;AAIA,MAAa,EACX,+BACA,gCACA,iCACA,qCACE,mBAAmB,UAAU;CAAE,MAAM;CAAS,SAAS;AAAS,CAAC;AAIrE,MAAa,EACX,yBACA,0BACA,+BACA,wBACA,gCACE,gBAAgB,UAAU;CAAE,MAAM;CAAS,SAAS;AAAS,CAAC;AAQlE,MAAa,mCAMX,YACG,kCAAkC,OAAO;AAE9C,MAAa,gCAKX,YACG,+BAA+B,OAAO;AAE3C,MAAa,wBAOX,YACG,uBAAuB,OAAO;;;;;AAWnC,MAAa,kBAAkB;CAC7B,GAAG,kBAAkB,iBAAiB,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC;CAC1D,iBAAiB;AACnB;AAWA,MAAa,EACX,2BACA,yBACA,qCACA,+BACA,6BACA,uBACA,oCACA,yBACA,4BACA,sCACA,0BACA,wCACE,gBAAkF,UAAU;CAC9F,MAAM;CACN,SAAS;AACX,CAAC;AAED,MAAa,EACX,+BACA,gCACA,yCACA,8BACA,uCACA,+BACA,iCACA,6BACA,kCACE,kBACF,UACA;CAAE,MAAM;CAAS,SAAS;AAAS,CACrC;AAMA,MAAa,EAAE,8BAA8B,+BAA+B,gBAC1E,UACA;CAAE,MAAM;CAAS,SAAS;AAAS,CACrC;;AAGA,MAAa,gBAAgB,MAAM,OAAO,UAAU,iBAAiB,CAAC,CAAC,CAAC;AAIxE,MAAa,EACX,mCACA,iCACA,iCACA,4BACA,qCACA,mCACA,mCACA,6BACA,2BACA,yBACA,+BACE,kBAAkB,UAAU;CAAE,MAAM;CAAS,SAAS;AAAS,CAAC;AAIpE,MAAa,EAAE,6BAA6B,8BAA8B,eAAe,UAAU;CACjG,MAAM;CACN,SAAS;AACX,CAAC"}
|
package/dist/Libsql.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { OAuthProxyPersistence } from "./internal/drizzle-sqlite-oauth-proxy.mjs
|
|
|
3
3
|
import { makeProofTarget } from "./drizzle/proof-drivers.mjs";
|
|
4
4
|
import { makePasswordTarget } from "./drizzle/password-drivers.mjs";
|
|
5
5
|
import { makeEmailTarget } from "./drizzle/email-drivers.mjs";
|
|
6
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
6
7
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers.mjs";
|
|
7
8
|
import { makePasskeyTarget } from "./drizzle/passkey-drivers.mjs";
|
|
8
9
|
import { makePhoneTarget } from "./drizzle/phone-drivers.mjs";
|
|
@@ -10,7 +11,6 @@ import { makeSessionTarget } from "./drizzle/session-drivers.mjs";
|
|
|
10
11
|
import { makeTotpTarget } from "./drizzle/totp-target.mjs";
|
|
11
12
|
import { makeSqliteExternalIdentityServices, makeSqliteIdentityServices, makeSqliteSubjectProvisioningServices } from "./drizzle/sqlite-identity.mjs";
|
|
12
13
|
import { drizzleMigrationsLayer } from "./internal/drizzle-migrations.mjs";
|
|
13
|
-
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
14
14
|
import { Context, Effect, Layer } from "effect";
|
|
15
15
|
import { EffectLibsqlTransaction, makeWithDefaults } from "drizzle-orm/effect-libsql";
|
|
16
16
|
//#region src/Libsql.ts
|
package/dist/SqliteBun.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { OAuthProxyPersistence } from "./internal/drizzle-sqlite-oauth-proxy.mjs
|
|
|
3
3
|
import { makeProofTarget } from "./drizzle/proof-drivers.mjs";
|
|
4
4
|
import { makePasswordTarget } from "./drizzle/password-drivers.mjs";
|
|
5
5
|
import { makeEmailTarget } from "./drizzle/email-drivers.mjs";
|
|
6
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
6
7
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers.mjs";
|
|
7
8
|
import { makePasskeyTarget } from "./drizzle/passkey-drivers.mjs";
|
|
8
9
|
import { makePhoneTarget } from "./drizzle/phone-drivers.mjs";
|
|
@@ -10,7 +11,6 @@ import { makeSessionTarget } from "./drizzle/session-drivers.mjs";
|
|
|
10
11
|
import { makeTotpTarget } from "./drizzle/totp-target.mjs";
|
|
11
12
|
import { makeSqliteExternalIdentityServices, makeSqliteIdentityServices, makeSqliteSubjectProvisioningServices } from "./drizzle/sqlite-identity.mjs";
|
|
12
13
|
import { drizzleMigrationsLayer } from "./internal/drizzle-migrations.mjs";
|
|
13
|
-
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
14
14
|
import { Context, Effect, Layer } from "effect";
|
|
15
15
|
import { EffectSQLiteBunTransaction, makeWithDefaults } from "drizzle-orm/effect-sqlite-bun";
|
|
16
16
|
//#region src/SqliteBun.ts
|
package/dist/SqliteNode.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { OAuthProxyPersistence } from "./internal/drizzle-sqlite-oauth-proxy.mjs
|
|
|
3
3
|
import { makeProofTarget } from "./drizzle/proof-drivers.mjs";
|
|
4
4
|
import { makePasswordTarget } from "./drizzle/password-drivers.mjs";
|
|
5
5
|
import { makeEmailTarget } from "./drizzle/email-drivers.mjs";
|
|
6
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
6
7
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers.mjs";
|
|
7
8
|
import { makePasskeyTarget } from "./drizzle/passkey-drivers.mjs";
|
|
8
9
|
import { makePhoneTarget } from "./drizzle/phone-drivers.mjs";
|
|
@@ -10,7 +11,6 @@ import { makeSessionTarget } from "./drizzle/session-drivers.mjs";
|
|
|
10
11
|
import { makeTotpTarget } from "./drizzle/totp-target.mjs";
|
|
11
12
|
import { makeSqliteExternalIdentityServices, makeSqliteIdentityServices, makeSqliteSubjectProvisioningServices } from "./drizzle/sqlite-identity.mjs";
|
|
12
13
|
import { drizzleMigrationsLayer } from "./internal/drizzle-migrations.mjs";
|
|
13
|
-
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
14
14
|
import { Context, Effect, Layer } from "effect";
|
|
15
15
|
import { EffectSQLiteNodeTransaction, makeWithDefaults } from "drizzle-orm/effect-sqlite-node";
|
|
16
16
|
//#region src/SqliteNode.ts
|
package/dist/SqliteWasm.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { OAuthProxyPersistence } from "./internal/drizzle-sqlite-oauth-proxy.mjs
|
|
|
3
3
|
import { makeProofTarget } from "./drizzle/proof-drivers.mjs";
|
|
4
4
|
import { makePasswordTarget } from "./drizzle/password-drivers.mjs";
|
|
5
5
|
import { makeEmailTarget } from "./drizzle/email-drivers.mjs";
|
|
6
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
6
7
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers.mjs";
|
|
7
8
|
import { makePasskeyTarget } from "./drizzle/passkey-drivers.mjs";
|
|
8
9
|
import { makePhoneTarget } from "./drizzle/phone-drivers.mjs";
|
|
@@ -10,7 +11,6 @@ import { makeSessionTarget } from "./drizzle/session-drivers.mjs";
|
|
|
10
11
|
import { makeTotpTarget } from "./drizzle/totp-target.mjs";
|
|
11
12
|
import { makeSqliteExternalIdentityServices, makeSqliteIdentityServices, makeSqliteSubjectProvisioningServices } from "./drizzle/sqlite-identity.mjs";
|
|
12
13
|
import { drizzleMigrationsLayer } from "./internal/drizzle-migrations.mjs";
|
|
13
|
-
import { sqlitePersistence } from "./internal/drizzle-sqlite.mjs";
|
|
14
14
|
import { Context, Effect, Layer } from "effect";
|
|
15
15
|
import { EffectSQLiteWasmTransaction, makeWithDefaults } from "drizzle-orm/effect-sqlite-wasm";
|
|
16
16
|
//#region src/SqliteWasm.ts
|
|
@@ -4,7 +4,7 @@ import "../native-database.mjs";
|
|
|
4
4
|
import "../D1BatchStatements.mjs";
|
|
5
5
|
import "../native-target.mjs";
|
|
6
6
|
import { SqlBatchCommit, makeNativePasskeyServices } from "@yielded/auth-persistence/Adapter";
|
|
7
|
-
import { Effect } from "effect";
|
|
7
|
+
import { Crypto, Effect } from "effect";
|
|
8
8
|
import { LifecycleHooks } from "@yielded/auth/Hooks";
|
|
9
9
|
import { SqlClient } from "effect/sql";
|
|
10
10
|
import { PasskeyConfigurationError, PasskeyUnavailable } from "@yielded/auth/Passkey";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PersistenceConfigurationError } from "@yielded/auth-persistence/Adapter";
|
|
2
|
+
import { Crypto, Layer } from "effect";
|
|
3
|
+
import { D1Client } from "@effect/sql-d1/D1Client";
|
|
4
|
+
//#region src/internal/drizzle-d1-migrations.d.ts
|
|
5
|
+
/** Drizzle's D1 migration journal and selection, with bundled SQL in place of
|
|
6
|
+
* filesystem loading. Pending statements and journal entries share one D1 batch.
|
|
7
|
+
* Applications own when migrations run; do not run competing migration owners.
|
|
8
|
+
*/
|
|
9
|
+
export declare const d1MigrationsLayer: (options: {
|
|
10
|
+
readonly migrations: Readonly<Record<string, string>>;
|
|
11
|
+
readonly migrationsTable?: string;
|
|
12
|
+
}) => Layer.Layer<never, PersistenceConfigurationError, Crypto.Crypto | D1Client>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//# sourceMappingURL=drizzle-d1-migrations.d.mts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PersistenceConfigurationError } from "@yielded/auth-persistence/Adapter";
|
|
2
|
+
import { sql } from "drizzle-orm";
|
|
3
|
+
import { Crypto, DateTime, Effect, Layer, Schema } from "effect";
|
|
4
|
+
import { D1Client } from "@effect/sql-d1/D1Client";
|
|
5
|
+
import { drizzle } from "drizzle-orm/d1";
|
|
6
|
+
import { formatToMillis, getMigrationsToRun } from "drizzle-orm/migrator.utils";
|
|
7
|
+
//#region src/internal/drizzle-d1-migrations.ts
|
|
8
|
+
const MigrationFiles = Schema.Record(Schema.String, Schema.NonEmptyString);
|
|
9
|
+
const MigrationName = Schema.String.check(Schema.isPattern(/^\d{14}_.+$/));
|
|
10
|
+
const JournalColumns = Schema.Array(Schema.Struct({ name: Schema.String }));
|
|
11
|
+
const configurationError = () => PersistenceConfigurationError.make({ reason: "Could not apply D1 migrations; check the bundled SQL and database binding" });
|
|
12
|
+
const JournalRows = Schema.Array(Schema.Struct({
|
|
13
|
+
id: Schema.Int,
|
|
14
|
+
hash: Schema.String,
|
|
15
|
+
created_at: Schema.String,
|
|
16
|
+
name: Schema.NullOr(Schema.String)
|
|
17
|
+
}));
|
|
18
|
+
/** Drizzle's D1 migration journal and selection, with bundled SQL in place of
|
|
19
|
+
* filesystem loading. Pending statements and journal entries share one D1 batch.
|
|
20
|
+
* Applications own when migrations run; do not run competing migration owners.
|
|
21
|
+
*/
|
|
22
|
+
const d1MigrationsLayer = (options) => Layer.effectDiscard(Effect.gen(function* () {
|
|
23
|
+
const files = yield* Schema.decodeEffect(MigrationFiles)(options.migrations);
|
|
24
|
+
const client = yield* D1Client;
|
|
25
|
+
const crypto = yield* Crypto.Crypto;
|
|
26
|
+
const appliedAt = DateTime.formatIso(yield* DateTime.now);
|
|
27
|
+
const migrations = yield* Effect.forEach(Object.entries(files).sort(([left], [right]) => left.localeCompare(right)), Effect.fnUntraced(function* ([name, contents]) {
|
|
28
|
+
yield* Schema.decodeEffect(MigrationName)(name);
|
|
29
|
+
const folderMillis = yield* Schema.decodeEffect(Schema.Int)(formatToMillis(name.slice(0, 14)));
|
|
30
|
+
const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode(contents));
|
|
31
|
+
return {
|
|
32
|
+
name,
|
|
33
|
+
folderMillis,
|
|
34
|
+
hash: Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join(""),
|
|
35
|
+
sql: contents.split("--> statement-breakpoint"),
|
|
36
|
+
bps: true
|
|
37
|
+
};
|
|
38
|
+
}));
|
|
39
|
+
yield* Effect.tryPromise({
|
|
40
|
+
try: async () => {
|
|
41
|
+
const db = drizzle(client.config.db);
|
|
42
|
+
const name = options.migrationsTable ?? "__drizzle_migrations";
|
|
43
|
+
const table = sql.identifier(name);
|
|
44
|
+
const columns = Schema.decodeUnknownSync(JournalColumns)(await db.all(sql`select name from pragma_table_info(${name})`)).map((column) => column.name);
|
|
45
|
+
const newDb = columns.length === 0;
|
|
46
|
+
const legacy = !newDb && !columns.includes("name");
|
|
47
|
+
if (!newDb && (![
|
|
48
|
+
"id",
|
|
49
|
+
"hash",
|
|
50
|
+
"created_at"
|
|
51
|
+
].every((column) => columns.includes(column)) || legacy === columns.includes("applied_at"))) throw configurationError();
|
|
52
|
+
const journal = newDb ? [] : Schema.decodeUnknownSync(JournalRows)(await db.all(sql`select rowid as id, hash, cast(created_at as text) as created_at,
|
|
53
|
+
${legacy ? sql`null` : sql`name`} as name from ${table} order by rowid`)).map((entry) => ({ ...entry }));
|
|
54
|
+
const journalStatements = newDb ? [db.run(sql`create table ${table} (
|
|
55
|
+
id integer primary key, hash text not null, created_at numeric,
|
|
56
|
+
name text, applied_at text
|
|
57
|
+
)`)] : [];
|
|
58
|
+
if (legacy) {
|
|
59
|
+
journalStatements.push(db.run(sql`alter table ${table} add column name text`), db.run(sql`alter table ${table} add column applied_at text`));
|
|
60
|
+
for (const entry of journal) {
|
|
61
|
+
const millis = Math.floor(Number(entry.created_at) / 1e3) * 1e3;
|
|
62
|
+
const candidates = migrations.filter((migration) => migration.folderMillis === millis);
|
|
63
|
+
const matches = candidates.length === 1 ? candidates : (candidates.length === 0 ? migrations : candidates).filter((migration) => migration.hash === entry.hash);
|
|
64
|
+
const match = matches[0];
|
|
65
|
+
if (matches.length !== 1 || match === void 0) throw configurationError();
|
|
66
|
+
entry.name = match.name;
|
|
67
|
+
journalStatements.push(db.run(sql`update ${table} set name = ${match.name} where rowid = ${entry.id}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const [first, ...rest] = [...journalStatements, ...getMigrationsToRun({
|
|
71
|
+
localMigrations: migrations,
|
|
72
|
+
dbMigrations: [...journal]
|
|
73
|
+
}).flatMap((migration) => [...migration.sql.map((statement) => db.run(sql.raw(statement))), db.run(sql`insert into ${table} (hash, created_at, name, applied_at)
|
|
74
|
+
values (${migration.hash}, ${migration.folderMillis}, ${migration.name}, ${appliedAt})`)])];
|
|
75
|
+
if (first !== void 0) await db.batch([first, ...rest]);
|
|
76
|
+
},
|
|
77
|
+
catch: configurationError
|
|
78
|
+
});
|
|
79
|
+
}).pipe(Effect.mapError(configurationError)));
|
|
80
|
+
//#endregion
|
|
81
|
+
export { d1MigrationsLayer };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=drizzle-d1-migrations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle-d1-migrations.mjs","names":[],"sources":["../../src/internal/drizzle-d1-migrations.ts"],"sourcesContent":["import { D1Client } from \"@effect/sql-d1/D1Client\";\nimport { PersistenceConfigurationError } from \"@yielded/auth-persistence/Adapter\";\nimport { sql } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/d1\";\nimport { formatToMillis, getMigrationsToRun } from \"drizzle-orm/migrator.utils\";\nimport { Crypto, DateTime, Effect, Layer, Schema } from \"effect\";\n\nconst MigrationFiles = Schema.Record(Schema.String, Schema.NonEmptyString);\nconst MigrationName = Schema.String.check(Schema.isPattern(/^\\d{14}_.+$/));\nconst JournalColumns = Schema.Array(Schema.Struct({ name: Schema.String }));\n\nconst configurationError = () =>\n PersistenceConfigurationError.make({\n reason: \"Could not apply D1 migrations; check the bundled SQL and database binding\",\n });\n\nconst JournalRows = Schema.Array(\n Schema.Struct({\n id: Schema.Int,\n hash: Schema.String,\n created_at: Schema.String,\n name: Schema.NullOr(Schema.String),\n }),\n);\n\n/** Drizzle's D1 migration journal and selection, with bundled SQL in place of\n * filesystem loading. Pending statements and journal entries share one D1 batch.\n * Applications own when migrations run; do not run competing migration owners.\n */\nexport const d1MigrationsLayer = (options: {\n readonly migrations: Readonly<Record<string, string>>;\n readonly migrationsTable?: string;\n}) =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const files = yield* Schema.decodeEffect(MigrationFiles)(options.migrations);\n const client = yield* D1Client;\n const crypto = yield* Crypto.Crypto;\n const appliedAt = DateTime.formatIso(yield* DateTime.now);\n\n const migrations = yield* Effect.forEach(\n Object.entries(files).sort(([left], [right]) => left.localeCompare(right)),\n Effect.fnUntraced(function* ([name, contents]) {\n yield* Schema.decodeEffect(MigrationName)(name);\n\n const folderMillis = yield* Schema.decodeEffect(Schema.Int)(\n formatToMillis(name.slice(0, 14)),\n );\n\n const digest = yield* crypto.digest(\"SHA-256\", new TextEncoder().encode(contents));\n\n return {\n name,\n folderMillis,\n hash: Array.from(digest, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\"),\n sql: contents.split(\"--> statement-breakpoint\"),\n bps: true,\n };\n }),\n );\n\n yield* Effect.tryPromise({\n try: async () => {\n const db = drizzle(client.config.db);\n const name = options.migrationsTable ?? \"__drizzle_migrations\";\n const table = sql.identifier(name);\n\n const columns = Schema.decodeUnknownSync(JournalColumns)(\n await db.all(sql`select name from pragma_table_info(${name})`),\n ).map((column) => column.name);\n\n const newDb = columns.length === 0;\n const legacy = !newDb && !columns.includes(\"name\");\n\n if (\n !newDb &&\n (![\"id\", \"hash\", \"created_at\"].every((column) => columns.includes(column)) ||\n legacy === columns.includes(\"applied_at\"))\n )\n throw configurationError();\n\n // Original Drizzle SQLite journals can have NULL serial IDs. Use the\n // physical rowid to backfill each retained entry without changing it.\n const journal = newDb\n ? []\n : Schema.decodeUnknownSync(JournalRows)(\n await db.all(sql`select rowid as id, hash, cast(created_at as text) as created_at,\n ${legacy ? sql`null` : sql`name`} as name from ${table} order by rowid`),\n ).map((entry) => ({ ...entry }));\n\n const journalStatements = newDb\n ? [\n db.run(sql`create table ${table} (\n id integer primary key, hash text not null, created_at numeric,\n name text, applied_at text\n )`),\n ]\n : [];\n\n if (legacy) {\n // Match retained entries using Drizzle's timestamp/hash convention,\n // but stage the upgrade with pending SQL: D1 cannot run BEGIN.\n journalStatements.push(\n db.run(sql`alter table ${table} add column name text`),\n db.run(sql`alter table ${table} add column applied_at text`),\n );\n for (const entry of journal) {\n const millis = Math.floor(Number(entry.created_at) / 1000) * 1000;\n\n const candidates = migrations.filter(\n (migration) => migration.folderMillis === millis,\n );\n\n const matches =\n candidates.length === 1\n ? candidates\n : (candidates.length === 0 ? migrations : candidates).filter(\n (migration) => migration.hash === entry.hash,\n );\n\n const match = matches[0];\n\n if (matches.length !== 1 || match === undefined) throw configurationError();\n entry.name = match.name;\n journalStatements.push(\n db.run(sql`update ${table} set name = ${match.name} where rowid = ${entry.id}`),\n );\n }\n }\n\n const statements = [\n ...journalStatements,\n ...getMigrationsToRun({\n localMigrations: migrations,\n dbMigrations: [...journal],\n }).flatMap((migration) => [\n ...migration.sql.map((statement) => db.run(sql.raw(statement))),\n db.run(sql`insert into ${table} (hash, created_at, name, applied_at)\n values (${migration.hash}, ${migration.folderMillis}, ${migration.name}, ${appliedAt})`),\n ]),\n ];\n\n const [first, ...rest] = statements;\n\n if (first !== undefined) await db.batch([first, ...rest]);\n },\n catch: configurationError,\n });\n }).pipe(Effect.mapError(configurationError)),\n );\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { nativeDatabase } from "../drizzle/native-database.mjs";
|
|
|
2
2
|
import { makeDrizzleSqlTables } from "../drizzle/native-sql-table.mjs";
|
|
3
3
|
import { PersistenceConfigurationError, createPersistence } from "@yielded/auth-persistence/Adapter";
|
|
4
4
|
import { getTableColumns } from "drizzle-orm";
|
|
5
|
+
import { Effect } from "effect";
|
|
5
6
|
import { getTableConfig, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
6
7
|
//#region src/internal/drizzle-sqlite.ts
|
|
7
8
|
const makeTable = (definition) => sqliteTable(definition.name, Object.fromEntries(Object.entries(definition.columns).map(([key, column]) => {
|
|
@@ -23,11 +24,15 @@ const describe = (table) => {
|
|
|
23
24
|
unique: []
|
|
24
25
|
};
|
|
25
26
|
};
|
|
26
|
-
const sqlitePersistence = (acquire) => createPersistence({
|
|
27
|
+
const sqlitePersistence = (acquire, options) => createPersistence({
|
|
27
28
|
nativeTables: (database) => makeDrizzleSqlTables(database.$client, database),
|
|
28
29
|
makeTable,
|
|
29
30
|
describe,
|
|
30
|
-
acquire: nativeDatabase(acquire)
|
|
31
|
+
acquire: nativeDatabase(acquire),
|
|
32
|
+
...options?.batch === true ? { batch: (database) => ({
|
|
33
|
+
client: database.$client,
|
|
34
|
+
execute: (statements) => database.$client.batch(statements).pipe(Effect.asVoid)
|
|
35
|
+
}) } : {}
|
|
31
36
|
});
|
|
32
37
|
//#endregion
|
|
33
38
|
export { sqlitePersistence };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-sqlite.mjs","names":[],"sources":["../../src/internal/drizzle-sqlite.ts"],"sourcesContent":["import {\n PersistenceConfigurationError,\n createPersistence,\n} from \"@yielded/auth-persistence/Adapter\";\nimport type { StorageTable } from \"@yielded/auth-persistence/Adapter\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport {\n getTableConfig,\n integer,\n sqliteTable,\n text,\n uniqueIndex,\n type SQLiteTable,\n} from \"drizzle-orm/sqlite-core\";\nimport
|
|
1
|
+
{"version":3,"file":"drizzle-sqlite.mjs","names":[],"sources":["../../src/internal/drizzle-sqlite.ts"],"sourcesContent":["import {\n PersistenceConfigurationError,\n createPersistence,\n} from \"@yielded/auth-persistence/Adapter\";\nimport type { StorageTable } from \"@yielded/auth-persistence/Adapter\";\nimport { getTableColumns } from \"drizzle-orm\";\nimport {\n getTableConfig,\n integer,\n sqliteTable,\n text,\n uniqueIndex,\n type SQLiteTable,\n} from \"drizzle-orm/sqlite-core\";\nimport { Effect } from \"effect\";\nimport type { SqlClient } from \"effect/sql/SqlClient\";\n\nimport { nativeDatabase, type NativeDatabaseHandle } from \"../drizzle/native-database\";\nimport { makeDrizzleSqlTables } from \"../drizzle/native-sql-table\";\n\nconst makeTable = (definition: StorageTable) =>\n sqliteTable(\n definition.name,\n Object.fromEntries(\n Object.entries(definition.columns).map(([key, column]) => {\n const builder =\n column.type === \"text\"\n ? text(column.name)\n : column.type === \"boolean\"\n ? integer(column.name, { mode: \"boolean\" })\n : integer(column.name);\n\n return [key, column.nullable ? builder : builder.notNull()];\n }),\n ),\n (columns) =>\n definition.unique.map((keys, i) => {\n const [first, ...rest] = keys;\n\n if (first === undefined)\n throw PersistenceConfigurationError.make({ reason: \"An empty unique key is invalid\" });\n\n return uniqueIndex(`${definition.name}_key_${i}`).on(\n columns[first],\n ...rest.map((key) => columns[key]),\n );\n }),\n );\n\nconst describe = (table: SQLiteTable): StorageTable => {\n const config = getTableConfig(table);\n\n return {\n name: config.name,\n columns: Object.fromEntries(\n Object.entries(getTableColumns(table)).map(([key, column]) => [\n key,\n {\n name: column.name,\n type:\n column.dataType === \"boolean\"\n ? \"boolean\"\n : column.dataType === \"number\"\n ? \"integer\"\n : \"text\",\n nullable: !column.notNull,\n },\n ]),\n ),\n unique: [],\n };\n};\n\nexport const sqlitePersistence = <R>(\n acquire: Effect.Effect<object, never, R | SqlClient>,\n options?: { readonly batch: true },\n) =>\n createPersistence<SQLiteTable, R, NativeDatabaseHandle>({\n nativeTables: (database) => makeDrizzleSqlTables(database.$client, database),\n makeTable,\n describe,\n acquire: nativeDatabase(acquire),\n ...(options?.batch === true\n ? {\n batch: (database: NativeDatabaseHandle) => ({\n client: database.$client,\n execute: (statements: Parameters<typeof database.$client.batch>[0]) =>\n database.$client.batch(statements).pipe(Effect.asVoid),\n }),\n }\n : {}),\n });\n"],"mappings":";;;;;;;AAoBA,MAAM,aAAa,eACjB,YACE,WAAW,MACX,OAAO,YACL,OAAO,QAAQ,WAAW,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY;CACxD,MAAM,UACJ,OAAO,SAAS,SACZ,KAAK,OAAO,IAAI,IAChB,OAAO,SAAS,YACd,QAAQ,OAAO,MAAM,EAAE,MAAM,UAAU,CAAC,IACxC,QAAQ,OAAO,IAAI;CAE3B,OAAO,CAAC,KAAK,OAAO,WAAW,UAAU,QAAQ,QAAQ,CAAC;AAC5D,CAAC,CACH,IACC,YACC,WAAW,OAAO,KAAK,MAAM,MAAM;CACjC,MAAM,CAAC,OAAO,GAAG,QAAQ;CAEzB,IAAI,UAAU,KAAA,GACZ,MAAM,8BAA8B,KAAK,EAAE,QAAQ,iCAAiC,CAAC;CAEvF,OAAO,YAAY,GAAG,WAAW,KAAK,OAAO,GAAG,CAAC,CAAC,GAChD,QAAQ,QACR,GAAG,KAAK,KAAK,QAAQ,QAAQ,IAAI,CACnC;AACF,CAAC,CACL;AAEF,MAAM,YAAY,UAAqC;CAGrD,OAAO;EACL,MAHa,eAAe,KAGjB,CAAC,CAAC;EACb,SAAS,OAAO,YACd,OAAO,QAAQ,gBAAgB,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY,CAC5D,KACA;GACE,MAAM,OAAO;GACb,MACE,OAAO,aAAa,YAChB,YACA,OAAO,aAAa,WAClB,YACA;GACR,UAAU,CAAC,OAAO;EACpB,CACF,CAAC,CACH;EACA,QAAQ,CAAC;CACX;AACF;AAEA,MAAa,qBACX,SACA,YAEA,kBAAwD;CACtD,eAAe,aAAa,qBAAqB,SAAS,SAAS,QAAQ;CAC3E;CACA;CACA,SAAS,eAAe,OAAO;CAC/B,GAAI,SAAS,UAAU,OACnB,EACE,QAAQ,cAAoC;EAC1C,QAAQ,SAAS;EACjB,UAAU,eACR,SAAS,QAAQ,MAAM,UAAU,CAAC,CAAC,KAAK,OAAO,MAAM;CACzD,GACF,IACA,CAAC;AACP,CAAC"}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@yielded/auth-persistence-drizzle","version":"0.1.0-beta.
|
|
1
|
+
{"name":"@yielded/auth-persistence-drizzle","version":"0.1.0-beta.31","dependencies":{"@yielded/auth":"0.1.0-beta.31","@yielded/auth-persistence":"0.1.0-beta.31"},"devDependencies":{"@effect/platform-bun":"4.0.0","@effect/platform-node":"4.0.0","@effect/sql-d1":"4.0.0","@effect/sql-libsql":"4.0.0","@effect/sql-mysql2":"4.0.0","@effect/sql-pg":"4.0.0","@effect/sql-pglite":"4.0.0","@effect/sql-sqlite-bun":"4.0.0","@effect/sql-sqlite-do":"4.0.0","@effect/sql-sqlite-node":"4.0.0","@effect/sql-sqlite-wasm":"4.0.0","@effect/vitest":"4.0.0","@yielded/crypto":"0.1.0-beta.31","drizzle-orm":"1.0.0-rc.4","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/sql-d1":">=4.0.0","@effect/sql-libsql":">=4.0.0","@effect/sql-mysql2":">=4.0.0","@effect/sql-pg":">=4.0.0","@effect/sql-pglite":">=4.0.0","@effect/sql-sqlite-bun":">=4.0.0","@effect/sql-sqlite-do":">=4.0.0","@effect/sql-sqlite-node":">=4.0.0","@effect/sql-sqlite-wasm":">=4.0.0","drizzle-orm":">=1.0.0-rc.4","effect":"^4.0.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./D1":{"types":"./dist/D1.d.mts","default":"./dist/D1.mjs"},"./Libsql":{"types":"./dist/Libsql.d.mts","default":"./dist/Libsql.mjs"},"./Mysql2":{"types":"./dist/Mysql2.d.mts","default":"./dist/Mysql2.mjs"},"./Pglite":{"types":"./dist/Pglite.d.mts","default":"./dist/Pglite.mjs"},"./Postgres":{"types":"./dist/Postgres.d.mts","default":"./dist/Postgres.mjs"},"./SqliteBun":{"types":"./dist/SqliteBun.d.mts","default":"./dist/SqliteBun.mjs"},"./SqliteDo":{"types":"./dist/SqliteDo.d.mts","default":"./dist/SqliteDo.mjs"},"./SqliteNode":{"types":"./dist/SqliteNode.d.mts","default":"./dist/SqliteNode.mjs"},"./SqliteWasm":{"types":"./dist/SqliteWasm.d.mts","default":"./dist/SqliteWasm.mjs"}},"description":"Drizzle persistence, table mappings, and migration adapters 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-drizzle"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests","example:totp":"bun test/fixtures/totp-sqlite-bun.ts"},"peerDependenciesMeta":{"@effect/sql-d1":{"optional":true},"@effect/sql-libsql":{"optional":true},"@effect/sql-mysql2":{"optional":true},"@effect/sql-pg":{"optional":true},"@effect/sql-pglite":{"optional":true},"@effect/sql-sqlite-bun":{"optional":true},"@effect/sql-sqlite-do":{"optional":true},"@effect/sql-sqlite-node":{"optional":true},"@effect/sql-sqlite-wasm":{"optional":true}}}
|
package/src/D1.ts
CHANGED
|
@@ -74,6 +74,18 @@ export const makeIdentityServices = <
|
|
|
74
74
|
|
|
75
75
|
import { Layer } from "effect";
|
|
76
76
|
|
|
77
|
+
import { d1MigrationsLayer } from "./internal/drizzle-d1-migrations";
|
|
78
|
+
import { sqlitePersistence } from "./internal/drizzle-sqlite";
|
|
79
|
+
|
|
80
|
+
/** Managed or mapped auth tables committed through D1's atomic batches.
|
|
81
|
+
* Supply D1Client.layer({ db: env.DB }) with the primary database binding.
|
|
82
|
+
* Registration supplies subject values; the account and credentials commit together.
|
|
83
|
+
*/
|
|
84
|
+
export const AuthPersistence = {
|
|
85
|
+
...sqlitePersistence(makeWithDefaults({}), { batch: true }),
|
|
86
|
+
migrationsLayer: d1MigrationsLayer,
|
|
87
|
+
};
|
|
88
|
+
|
|
77
89
|
import type { D1BatchStatements } from "./drizzle/D1BatchStatements";
|
|
78
90
|
import { makeOAuthTarget } from "./drizzle/oauth-drivers";
|
|
79
91
|
import type { OAuthD1Mapping } from "./drizzle/oauth-model";
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { D1Client } from "@effect/sql-d1/D1Client";
|
|
2
|
+
import { PersistenceConfigurationError } from "@yielded/auth-persistence/Adapter";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
import { drizzle } from "drizzle-orm/d1";
|
|
5
|
+
import { formatToMillis, getMigrationsToRun } from "drizzle-orm/migrator.utils";
|
|
6
|
+
import { Crypto, DateTime, Effect, Layer, Schema } from "effect";
|
|
7
|
+
|
|
8
|
+
const MigrationFiles = Schema.Record(Schema.String, Schema.NonEmptyString);
|
|
9
|
+
const MigrationName = Schema.String.check(Schema.isPattern(/^\d{14}_.+$/));
|
|
10
|
+
const JournalColumns = Schema.Array(Schema.Struct({ name: Schema.String }));
|
|
11
|
+
|
|
12
|
+
const configurationError = () =>
|
|
13
|
+
PersistenceConfigurationError.make({
|
|
14
|
+
reason: "Could not apply D1 migrations; check the bundled SQL and database binding",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const JournalRows = Schema.Array(
|
|
18
|
+
Schema.Struct({
|
|
19
|
+
id: Schema.Int,
|
|
20
|
+
hash: Schema.String,
|
|
21
|
+
created_at: Schema.String,
|
|
22
|
+
name: Schema.NullOr(Schema.String),
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
/** Drizzle's D1 migration journal and selection, with bundled SQL in place of
|
|
27
|
+
* filesystem loading. Pending statements and journal entries share one D1 batch.
|
|
28
|
+
* Applications own when migrations run; do not run competing migration owners.
|
|
29
|
+
*/
|
|
30
|
+
export const d1MigrationsLayer = (options: {
|
|
31
|
+
readonly migrations: Readonly<Record<string, string>>;
|
|
32
|
+
readonly migrationsTable?: string;
|
|
33
|
+
}) =>
|
|
34
|
+
Layer.effectDiscard(
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
const files = yield* Schema.decodeEffect(MigrationFiles)(options.migrations);
|
|
37
|
+
const client = yield* D1Client;
|
|
38
|
+
const crypto = yield* Crypto.Crypto;
|
|
39
|
+
const appliedAt = DateTime.formatIso(yield* DateTime.now);
|
|
40
|
+
|
|
41
|
+
const migrations = yield* Effect.forEach(
|
|
42
|
+
Object.entries(files).sort(([left], [right]) => left.localeCompare(right)),
|
|
43
|
+
Effect.fnUntraced(function* ([name, contents]) {
|
|
44
|
+
yield* Schema.decodeEffect(MigrationName)(name);
|
|
45
|
+
|
|
46
|
+
const folderMillis = yield* Schema.decodeEffect(Schema.Int)(
|
|
47
|
+
formatToMillis(name.slice(0, 14)),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode(contents));
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
folderMillis,
|
|
55
|
+
hash: Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join(""),
|
|
56
|
+
sql: contents.split("--> statement-breakpoint"),
|
|
57
|
+
bps: true,
|
|
58
|
+
};
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
yield* Effect.tryPromise({
|
|
63
|
+
try: async () => {
|
|
64
|
+
const db = drizzle(client.config.db);
|
|
65
|
+
const name = options.migrationsTable ?? "__drizzle_migrations";
|
|
66
|
+
const table = sql.identifier(name);
|
|
67
|
+
|
|
68
|
+
const columns = Schema.decodeUnknownSync(JournalColumns)(
|
|
69
|
+
await db.all(sql`select name from pragma_table_info(${name})`),
|
|
70
|
+
).map((column) => column.name);
|
|
71
|
+
|
|
72
|
+
const newDb = columns.length === 0;
|
|
73
|
+
const legacy = !newDb && !columns.includes("name");
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
!newDb &&
|
|
77
|
+
(!["id", "hash", "created_at"].every((column) => columns.includes(column)) ||
|
|
78
|
+
legacy === columns.includes("applied_at"))
|
|
79
|
+
)
|
|
80
|
+
throw configurationError();
|
|
81
|
+
|
|
82
|
+
// Original Drizzle SQLite journals can have NULL serial IDs. Use the
|
|
83
|
+
// physical rowid to backfill each retained entry without changing it.
|
|
84
|
+
const journal = newDb
|
|
85
|
+
? []
|
|
86
|
+
: Schema.decodeUnknownSync(JournalRows)(
|
|
87
|
+
await db.all(sql`select rowid as id, hash, cast(created_at as text) as created_at,
|
|
88
|
+
${legacy ? sql`null` : sql`name`} as name from ${table} order by rowid`),
|
|
89
|
+
).map((entry) => ({ ...entry }));
|
|
90
|
+
|
|
91
|
+
const journalStatements = newDb
|
|
92
|
+
? [
|
|
93
|
+
db.run(sql`create table ${table} (
|
|
94
|
+
id integer primary key, hash text not null, created_at numeric,
|
|
95
|
+
name text, applied_at text
|
|
96
|
+
)`),
|
|
97
|
+
]
|
|
98
|
+
: [];
|
|
99
|
+
|
|
100
|
+
if (legacy) {
|
|
101
|
+
// Match retained entries using Drizzle's timestamp/hash convention,
|
|
102
|
+
// but stage the upgrade with pending SQL: D1 cannot run BEGIN.
|
|
103
|
+
journalStatements.push(
|
|
104
|
+
db.run(sql`alter table ${table} add column name text`),
|
|
105
|
+
db.run(sql`alter table ${table} add column applied_at text`),
|
|
106
|
+
);
|
|
107
|
+
for (const entry of journal) {
|
|
108
|
+
const millis = Math.floor(Number(entry.created_at) / 1000) * 1000;
|
|
109
|
+
|
|
110
|
+
const candidates = migrations.filter(
|
|
111
|
+
(migration) => migration.folderMillis === millis,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const matches =
|
|
115
|
+
candidates.length === 1
|
|
116
|
+
? candidates
|
|
117
|
+
: (candidates.length === 0 ? migrations : candidates).filter(
|
|
118
|
+
(migration) => migration.hash === entry.hash,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const match = matches[0];
|
|
122
|
+
|
|
123
|
+
if (matches.length !== 1 || match === undefined) throw configurationError();
|
|
124
|
+
entry.name = match.name;
|
|
125
|
+
journalStatements.push(
|
|
126
|
+
db.run(sql`update ${table} set name = ${match.name} where rowid = ${entry.id}`),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const statements = [
|
|
132
|
+
...journalStatements,
|
|
133
|
+
...getMigrationsToRun({
|
|
134
|
+
localMigrations: migrations,
|
|
135
|
+
dbMigrations: [...journal],
|
|
136
|
+
}).flatMap((migration) => [
|
|
137
|
+
...migration.sql.map((statement) => db.run(sql.raw(statement))),
|
|
138
|
+
db.run(sql`insert into ${table} (hash, created_at, name, applied_at)
|
|
139
|
+
values (${migration.hash}, ${migration.folderMillis}, ${migration.name}, ${appliedAt})`),
|
|
140
|
+
]),
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
const [first, ...rest] = statements;
|
|
144
|
+
|
|
145
|
+
if (first !== undefined) await db.batch([first, ...rest]);
|
|
146
|
+
},
|
|
147
|
+
catch: configurationError,
|
|
148
|
+
});
|
|
149
|
+
}).pipe(Effect.mapError(configurationError)),
|
|
150
|
+
);
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
uniqueIndex,
|
|
13
13
|
type SQLiteTable,
|
|
14
14
|
} from "drizzle-orm/sqlite-core";
|
|
15
|
-
import
|
|
15
|
+
import { Effect } from "effect";
|
|
16
16
|
import type { SqlClient } from "effect/sql/SqlClient";
|
|
17
17
|
|
|
18
18
|
import { nativeDatabase, type NativeDatabaseHandle } from "../drizzle/native-database";
|
|
@@ -71,10 +71,22 @@ const describe = (table: SQLiteTable): StorageTable => {
|
|
|
71
71
|
};
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
export const sqlitePersistence = <R>(
|
|
74
|
+
export const sqlitePersistence = <R>(
|
|
75
|
+
acquire: Effect.Effect<object, never, R | SqlClient>,
|
|
76
|
+
options?: { readonly batch: true },
|
|
77
|
+
) =>
|
|
75
78
|
createPersistence<SQLiteTable, R, NativeDatabaseHandle>({
|
|
76
79
|
nativeTables: (database) => makeDrizzleSqlTables(database.$client, database),
|
|
77
80
|
makeTable,
|
|
78
81
|
describe,
|
|
79
82
|
acquire: nativeDatabase(acquire),
|
|
83
|
+
...(options?.batch === true
|
|
84
|
+
? {
|
|
85
|
+
batch: (database: NativeDatabaseHandle) => ({
|
|
86
|
+
client: database.$client,
|
|
87
|
+
execute: (statements: Parameters<typeof database.$client.batch>[0]) =>
|
|
88
|
+
database.$client.batch(statements).pipe(Effect.asVoid),
|
|
89
|
+
}),
|
|
90
|
+
}
|
|
91
|
+
: {}),
|
|
80
92
|
});
|