@lunora/auth 1.0.0-alpha.50 → 1.0.0-alpha.52

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.
@@ -1,43 +1,4 @@
1
- import { createAdapterFactory } from 'better-auth/adapters';
2
- import { d1Executor } from "./sql-store.mjs";
3
- import { AuthStore } from "./store.mjs";
4
- /**
5
- * A better-auth database adapter backed by an {@link AuthStore} — the bridge
6
- * that routes better-auth's reads and writes through Lunora's data layer
7
- * instead of better-auth's built-in D1/Kysely adapter. Pass the result as
8
- * `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
9
- * `createAdapterFactory` handles id generation, default values, field-name
10
- * mapping and output shaping, so this only translates the cleaned CRUD calls
11
- * onto the store.
12
- *
13
- * ```ts
14
- * const auth = createAuth({
15
- * secret: env.AUTH_SECRET,
16
- * emailAndPassword: { enabled: true },
17
- * database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
18
- * });
19
- * ```
20
- *
21
- * Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
22
- * relational `join` reads (an advanced opt-in) are not handled — pair the
23
- * adapter with `disableJoins` or let better-auth fall back to per-table reads.
24
- */
25
- declare const lunoraAuthAdapter: (store: AuthStore) => ReturnType<typeof createAdapterFactory>;
26
- /**
27
- * One-liner for the common case: a better-auth `database` backed by a Cloudflare
28
- * D1 binding, via Lunora's SQL store — equivalent to
29
- * `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
30
- *
31
- * Prefer this over passing the raw `env.DB` as `database`. With raw D1,
32
- * better-auth resolves its Kysely adapter through a runtime `await import(...)`
33
- * inside `auth.$context`, and that dynamic import never settles under
34
- * `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
35
- * in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
36
- * up-front, so they're unaffected — which makes the hang baffling to debug).
37
- * This explicit adapter skips that import entirely, so dev and prod behave the
38
- * same. The migration instance is the one exception — it wants raw `env.DB` so
39
- * `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
40
- * never resolved, so the hang doesn't apply there).
41
- */
42
- declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
43
- export { lunoraAuthAdapter, lunoraD1Adapter };
1
+ import 'better-auth/adapters';
2
+ export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-CQD49h5g.mjs";
3
+ import "./sql-store.mjs";
4
+ import "./store.mjs";
package/dist/adapter.d.ts CHANGED
@@ -1,43 +1,4 @@
1
- import { createAdapterFactory } from 'better-auth/adapters';
2
- import { d1Executor } from "./sql-store.js";
3
- import { AuthStore } from "./store.js";
4
- /**
5
- * A better-auth database adapter backed by an {@link AuthStore} — the bridge
6
- * that routes better-auth's reads and writes through Lunora's data layer
7
- * instead of better-auth's built-in D1/Kysely adapter. Pass the result as
8
- * `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
9
- * `createAdapterFactory` handles id generation, default values, field-name
10
- * mapping and output shaping, so this only translates the cleaned CRUD calls
11
- * onto the store.
12
- *
13
- * ```ts
14
- * const auth = createAuth({
15
- * secret: env.AUTH_SECRET,
16
- * emailAndPassword: { enabled: true },
17
- * database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
18
- * });
19
- * ```
20
- *
21
- * Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
22
- * relational `join` reads (an advanced opt-in) are not handled — pair the
23
- * adapter with `disableJoins` or let better-auth fall back to per-table reads.
24
- */
25
- declare const lunoraAuthAdapter: (store: AuthStore) => ReturnType<typeof createAdapterFactory>;
26
- /**
27
- * One-liner for the common case: a better-auth `database` backed by a Cloudflare
28
- * D1 binding, via Lunora's SQL store — equivalent to
29
- * `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
30
- *
31
- * Prefer this over passing the raw `env.DB` as `database`. With raw D1,
32
- * better-auth resolves its Kysely adapter through a runtime `await import(...)`
33
- * inside `auth.$context`, and that dynamic import never settles under
34
- * `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
35
- * in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
36
- * up-front, so they're unaffected — which makes the hang baffling to debug).
37
- * This explicit adapter skips that import entirely, so dev and prod behave the
38
- * same. The migration instance is the one exception — it wants raw `env.DB` so
39
- * `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
40
- * never resolved, so the hang doesn't apply there).
41
- */
42
- declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
43
- export { lunoraAuthAdapter, lunoraD1Adapter };
1
+ import 'better-auth/adapters';
2
+ export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-DiqyUMUb.js";
3
+ import "./sql-store.js";
4
+ import "./store.js";
package/dist/adapter.mjs CHANGED
@@ -1 +1 @@
1
- import{createAdapterFactory as d}from"better-auth/adapters";import{createSqlAuthStore as c,d1Executor as l}from"./sql-store.mjs";const m=a=>a,o=a=>a??null,u=a=>a,i=a=>d({adapter:()=>({consumeOne:async({model:e,where:t})=>o(await a.consumeOne(e,t)),count:async({model:e,where:t})=>a.count(e,t??[]),create:async({data:e,model:t})=>m(await a.create(t,e)),delete:async({model:e,where:t})=>{await a.remove(e,t)},deleteMany:async({model:e,where:t})=>a.remove(e,t),findMany:async({limit:e,model:t,offset:r,sortBy:n,where:s})=>u(await a.read(t,{limit:e,offset:r,sortBy:n,where:s??[]})),findOne:async({model:e,where:t})=>{const[r]=await a.read(e,{limit:1,where:t});return o(r)},incrementOne:async({increment:e,model:t,set:r,where:n})=>o(await a.incrementOne(t,n,e,r)),update:async({model:e,update:t,where:r})=>{const[n]=await a.update(e,r,t);return o(n)},updateMany:async({model:e,update:t,where:r})=>(await a.update(e,r,t)).length}),config:{adapterId:"lunora",adapterName:"Lunora Adapter",supportsBooleans:!1,supportsDates:!1,supportsJSON:!1,supportsNumericIds:!1}}),y=a=>i(c(l(a)));export{i as lunoraAuthAdapter,y as lunoraD1Adapter};
1
+ import"better-auth/adapters";import{s as p,l as e,w as l}from"./packem_shared/adapter-RvDcm0Zy.mjs";import"./sql-store.mjs";export{p as lunoraAuthAdapter,e as lunoraD1Adapter,l as lunoraDoAdapter};
package/dist/index.d.mts CHANGED
@@ -1,9 +1,10 @@
1
- export { lunoraAuthAdapter, lunoraD1Adapter } from "./adapter.mjs";
1
+ import { D as DoStorageLike } from "./packem_shared/adapter.d-CQD49h5g.mjs";
2
+ export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-CQD49h5g.mjs";
2
3
  import { LunoraError } from '@lunora/errors';
3
4
  import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
4
5
  export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.mjs";
5
- import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.mjs";
6
- export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type AuthAuditReader, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.mjs";
6
+ import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent, AuthAuditReader } from "./audit.mjs";
7
+ export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.mjs";
7
8
  import { createAuthMiddleware } from 'better-auth/api';
8
9
  import { SqlExecutor } from "./sql-store.mjs";
9
10
  export { createSqlAuthStore, d1Executor } from "./sql-store.mjs";
@@ -563,6 +564,174 @@ declare const withAuthAudit: <Options extends {
563
564
  after?: unknown;
564
565
  };
565
566
  }>(options: Options, config: AuthAuditHookConfig) => Options;
567
+ /**
568
+ * The Durable Object state slice this class needs — structural so unit tests can
569
+ * pass a double without depending on the workers runtime.
570
+ */
571
+ interface AuthDoState {
572
+ storage: DoStorageLike;
573
+ }
574
+ /** Path the worker calls to resolve a request's identity. Not part of `/api/auth/*`. */
575
+ declare const RESOLVE_SESSION_PATH = "/__lunora/auth/session";
576
+ /**
577
+ * Path the worker calls to read the audit log. Also not part of `/api/auth/*`.
578
+ *
579
+ * The audit table lives in this object like every other auth table, so the worker
580
+ * cannot query it directly — same constraint as the session route, same shared secret.
581
+ */
582
+ declare const READ_AUDIT_PATH = "/__lunora/auth/audit";
583
+ /** Header carrying the shared secret that authenticates the calling worker. */
584
+ declare const INTERNAL_SECRET_HEADER = "x-lunora-auth-do-secret";
585
+ /**
586
+ * Options for the auth DO, beyond the better-auth options themselves.
587
+ * @experimental
588
+ */
589
+ interface AuthDoOptions {
590
+ /** Base path the auth routes are served under. Must match the worker's. */
591
+ basePath?: string;
592
+ /**
593
+ * Shared secret authenticating the worker on {@link RESOLVE_SESSION_PATH}.
594
+ *
595
+ * The DO binding is reachable from any worker bound to the namespace, so the
596
+ * binding alone is not an authorization boundary — same reasoning as
597
+ * `SessionDO`'s `SESSION_DO_SECRET`. When this is unset the internal route is
598
+ * refused outright rather than served unauthenticated: a missing secret is a
599
+ * misconfiguration, and answering identity questions to anyone is the one
600
+ * failure mode worth being loud about. `/api/auth/*` is unaffected — those
601
+ * routes carry their own credentials.
602
+ */
603
+ internalSecret?: string;
604
+ }
605
+ /**
606
+ * Base class for an app's auth Durable Object. Subclass it (or let codegen emit the
607
+ * subclass) and register the subclass in `wrangler.jsonc`.
608
+ *
609
+ * ```ts
610
+ * export class AuthDO extends LunoraAuthDO {
611
+ * public constructor(state: DurableObjectState, env: Env) {
612
+ * super(state, () => ({ secret: env.AUTH_SECRET, plugins: [scim({ … })] }), {
613
+ * internalSecret: env.AUTH_DO_SECRET,
614
+ * });
615
+ * }
616
+ * }
617
+ * ```
618
+ * @experimental
619
+ */
620
+ declare class LunoraAuthDO {
621
+ #private;
622
+ /**
623
+ * @param state The Durable Object state — its `storage` becomes better-auth's database.
624
+ * @param optionsFactory Builds the better-auth options. Called once, lazily, on the first request.
625
+ * @param options Auth-DO specific options (base path, internal secret).
626
+ */
627
+ constructor(state: AuthDoState, optionsFactory: () => LunoraAuthOptions, options?: AuthDoOptions);
628
+ /**
629
+ * Serve an auth request. Routes under `basePath` go to better-auth; the internal
630
+ * session route is handled here; anything else is a 404.
631
+ */
632
+ fetch(request: Request): Promise<Response>;
633
+ }
634
+ /**
635
+ * The `CREATE TABLE` / `CREATE INDEX` statements for a better-auth config, in
636
+ * execution order (every table before any index).
637
+ *
638
+ * All statements are `IF NOT EXISTS`, so this is safe to run on every cold start —
639
+ * which is how `LunoraAuthDO` uses it. It creates; it never alters or drops, so a
640
+ * schema that has already diverged is left alone rather than half-migrated.
641
+ *
642
+ * Physical names throughout: `modelName` for tables, `fieldName ?? key` for columns,
643
+ * and better-auth's own resolved `columns` / `name` for indexes — so an index name
644
+ * here is the name better-auth's introspection expects to find.
645
+ * @param options The better-auth options the DO will run — the plugin list decides which tables exist.
646
+ * @returns SQL statements to execute in order.
647
+ * @experimental
648
+ */
649
+ declare const authDoSchemaStatements: (options: LunoraAuthOptions) => string[];
650
+ /**
651
+ * `ALTER TABLE … ADD COLUMN` statements for columns the live schema is missing.
652
+ *
653
+ * ## Why this is needed at all
654
+ *
655
+ * {@link authDoSchemaStatements} is entirely `IF NOT EXISTS`, which makes it safe to
656
+ * re-run but blind to change: a *new table* appears on the next cold start, a *new
657
+ * column on an existing table* never does. That is not a hypothetical — enabling the
658
+ * `admin` plugin after first deploy adds `role` / `banned` / `banExpires` to `user`,
659
+ * and without this the object would keep serving a `user` table that cannot hold them.
660
+ *
661
+ * Additive only. Nothing here drops, renames, or retypes a column: a column that
662
+ * exists is left exactly as it is, so a schema someone has deliberately diverged is
663
+ * never "corrected" underneath them.
664
+ *
665
+ * ## What SQLite will not let us do
666
+ *
667
+ * `ADD COLUMN` cannot introduce a `NOT NULL` column without a default (existing rows
668
+ * would violate it immediately), and cannot introduce `UNIQUE`. So a required column
669
+ * with a static default is added with both; a required column *without* one is added
670
+ * **nullable**, which differs from what a fresh `CREATE TABLE` would produce. That is
671
+ * the deliberate trade: better-auth writes these fields on every insert, so nullable
672
+ * is harmless, whereas refusing to add the column at all would leave the object
673
+ * broken. Uniqueness is unaffected — better-auth expresses it as a separate index, and
674
+ * those are `IF NOT EXISTS`, so they are created by the statements above.
675
+ * @param options The better-auth options the DO runs, already resolved.
676
+ * @param existingColumns Physical column names currently present on a table; empty/absent for a table that does not exist yet (it will be created instead).
677
+ * @returns SQL statements to execute in order; empty when the live schema is current.
678
+ * @experimental
679
+ */
680
+ declare const authDoColumnAdditions: (options: LunoraAuthOptions, existingColumns: (table: string) => Iterable<string>) => string[];
681
+ /** The slice of a Durable Object namespace this needs — structural, so tests need no runtime. */
682
+ interface AuthNamespaceLike {
683
+ get: (id: unknown) => {
684
+ fetch: (request: Request) => Promise<Response>;
685
+ };
686
+ idFromName: (name: string) => unknown;
687
+ }
688
+ /** What {@link createDoAuthWiring} needs, already resolved against `env`. */
689
+ interface DoAuthWiringOptions {
690
+ /** Base path the auth routes are served under. Defaults to `/api/auth`. */
691
+ basePath?: string;
692
+ /**
693
+ * Shared secret presented on the object's internal session route. `undefined`
694
+ * means identity resolution fails closed — see {@link DoAuthWiring.resolveIdentity}.
695
+ */
696
+ internalSecret: string | undefined;
697
+ /** The bound namespace, or `undefined` when the binding is absent from `env`. */
698
+ namespace: AuthNamespaceLike | undefined;
699
+ /**
700
+ * Name of the object instance holding the auth tables. Defaults to `"auth"`.
701
+ *
702
+ * One object owns the whole auth schema, so this exists to let an app pick the
703
+ * name (or run separate objects per deployment/tenant) rather than being pinned to
704
+ * a hardcoded one.
705
+ */
706
+ objectName?: string;
707
+ }
708
+ /** The worker options DO-backed auth replaces. */
709
+ interface DoAuthWiring {
710
+ /**
711
+ * Reads the audit log out of the object, so the studio's audit feed works in DO
712
+ * mode. Answers an empty page rather than throwing when the object is unreachable
713
+ * or no secret is configured — an unavailable feed should read as empty, not 500
714
+ * the studio.
715
+ */
716
+ auditReader: AuthAuditReader;
717
+ /** Forwards `/api/auth/*` to the object; `undefined` for anything else. */
718
+ authHandler: (request: Request) => Promise<Response | undefined>;
719
+ /** Resolves a request's identity by asking the object. `null` when anonymous, unreachable, or ungated. */
720
+ resolveIdentity: (request: Request) => Promise<null | {
721
+ userId: string;
722
+ }>;
723
+ }
724
+ /**
725
+ * Build the worker-side wiring for an auth Durable Object.
726
+ *
727
+ * Every failure path answers "not authenticated" rather than throwing: this runs on
728
+ * the request path for every request that touches `ctx.auth`, and a throw there would
729
+ * turn a misconfiguration into a 500 on traffic that has nothing to do with auth.
730
+ * @param options The resolved namespace, secret, and names.
731
+ * @returns The `authHandler` / `resolveIdentity` pair.
732
+ * @experimental
733
+ */
734
+ declare const createDoAuthWiring: (options: DoAuthWiringOptions) => DoAuthWiring;
566
735
  /** better-auth's `databaseHooks` shape, derived so a rename upstream fails to compile rather than silently mis-hooking. */
567
736
  type DatabaseHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
568
737
  /** Config for the signup gate hooks: the base {@link EmailGateConfig} plus an optional classification tap. */
@@ -697,4 +866,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
697
866
  * with the same 60s cookie cache as `rolling`.
698
867
  */
699
868
  declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
700
- export { type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthCapabilities, type AuthConfigInfo, type AuthInvitation, type AuthMember, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, buildAuditEntry, compileMigrationsSql, createAuthAdmin, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
869
+ export { READ_AUDIT_PATH as AUTH_DO_AUDIT_PATH, INTERNAL_SECRET_HEADER as AUTH_DO_SECRET_HEADER, RESOLVE_SESSION_PATH as AUTH_DO_SESSION_PATH, type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthAuditReader, type AuthCapabilities, type AuthConfigInfo, type AuthDoOptions, type AuthDoState, type AuthInvitation, type AuthMember, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type DoAuthWiring, type DoAuthWiringOptions, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, LunoraAuthDO, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, compileMigrationsSql, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- export { lunoraAuthAdapter, lunoraD1Adapter } from "./adapter.js";
1
+ import { D as DoStorageLike } from "./packem_shared/adapter.d-DiqyUMUb.js";
2
+ export { l as lunoraAuthAdapter, a as lunoraD1Adapter, b as lunoraDoAdapter } from "./packem_shared/adapter.d-DiqyUMUb.js";
2
3
  import { LunoraError } from '@lunora/errors';
3
4
  import { L as LunoraAuth, a as LunoraAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
4
5
  export { c as createAuth, r as resolveAuthOptions } from "./packem_shared/create-auth.d-De6IOirt.js";
5
- import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent } from "./audit.js";
6
- export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type AuthAuditReader, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.js";
6
+ import { AppendAuthAuditEntry, AppendAuthAuditOptions, AuthAuditEvent, AuthAuditReader } from "./audit.js";
7
+ export { AUTH_AUDIT_TABLE, type AuthAuditEntry, type AuthAuditOutcome, type ReadAuthAuditOptions, appendAuthAuditEntry, createAuthAuditReader, ensureAuthAuditTable, readAuthAuditLog } from "./audit.js";
7
8
  import { createAuthMiddleware } from 'better-auth/api';
8
9
  import { SqlExecutor } from "./sql-store.js";
9
10
  export { createSqlAuthStore, d1Executor } from "./sql-store.js";
@@ -563,6 +564,174 @@ declare const withAuthAudit: <Options extends {
563
564
  after?: unknown;
564
565
  };
565
566
  }>(options: Options, config: AuthAuditHookConfig) => Options;
567
+ /**
568
+ * The Durable Object state slice this class needs — structural so unit tests can
569
+ * pass a double without depending on the workers runtime.
570
+ */
571
+ interface AuthDoState {
572
+ storage: DoStorageLike;
573
+ }
574
+ /** Path the worker calls to resolve a request's identity. Not part of `/api/auth/*`. */
575
+ declare const RESOLVE_SESSION_PATH = "/__lunora/auth/session";
576
+ /**
577
+ * Path the worker calls to read the audit log. Also not part of `/api/auth/*`.
578
+ *
579
+ * The audit table lives in this object like every other auth table, so the worker
580
+ * cannot query it directly — same constraint as the session route, same shared secret.
581
+ */
582
+ declare const READ_AUDIT_PATH = "/__lunora/auth/audit";
583
+ /** Header carrying the shared secret that authenticates the calling worker. */
584
+ declare const INTERNAL_SECRET_HEADER = "x-lunora-auth-do-secret";
585
+ /**
586
+ * Options for the auth DO, beyond the better-auth options themselves.
587
+ * @experimental
588
+ */
589
+ interface AuthDoOptions {
590
+ /** Base path the auth routes are served under. Must match the worker's. */
591
+ basePath?: string;
592
+ /**
593
+ * Shared secret authenticating the worker on {@link RESOLVE_SESSION_PATH}.
594
+ *
595
+ * The DO binding is reachable from any worker bound to the namespace, so the
596
+ * binding alone is not an authorization boundary — same reasoning as
597
+ * `SessionDO`'s `SESSION_DO_SECRET`. When this is unset the internal route is
598
+ * refused outright rather than served unauthenticated: a missing secret is a
599
+ * misconfiguration, and answering identity questions to anyone is the one
600
+ * failure mode worth being loud about. `/api/auth/*` is unaffected — those
601
+ * routes carry their own credentials.
602
+ */
603
+ internalSecret?: string;
604
+ }
605
+ /**
606
+ * Base class for an app's auth Durable Object. Subclass it (or let codegen emit the
607
+ * subclass) and register the subclass in `wrangler.jsonc`.
608
+ *
609
+ * ```ts
610
+ * export class AuthDO extends LunoraAuthDO {
611
+ * public constructor(state: DurableObjectState, env: Env) {
612
+ * super(state, () => ({ secret: env.AUTH_SECRET, plugins: [scim({ … })] }), {
613
+ * internalSecret: env.AUTH_DO_SECRET,
614
+ * });
615
+ * }
616
+ * }
617
+ * ```
618
+ * @experimental
619
+ */
620
+ declare class LunoraAuthDO {
621
+ #private;
622
+ /**
623
+ * @param state The Durable Object state — its `storage` becomes better-auth's database.
624
+ * @param optionsFactory Builds the better-auth options. Called once, lazily, on the first request.
625
+ * @param options Auth-DO specific options (base path, internal secret).
626
+ */
627
+ constructor(state: AuthDoState, optionsFactory: () => LunoraAuthOptions, options?: AuthDoOptions);
628
+ /**
629
+ * Serve an auth request. Routes under `basePath` go to better-auth; the internal
630
+ * session route is handled here; anything else is a 404.
631
+ */
632
+ fetch(request: Request): Promise<Response>;
633
+ }
634
+ /**
635
+ * The `CREATE TABLE` / `CREATE INDEX` statements for a better-auth config, in
636
+ * execution order (every table before any index).
637
+ *
638
+ * All statements are `IF NOT EXISTS`, so this is safe to run on every cold start —
639
+ * which is how `LunoraAuthDO` uses it. It creates; it never alters or drops, so a
640
+ * schema that has already diverged is left alone rather than half-migrated.
641
+ *
642
+ * Physical names throughout: `modelName` for tables, `fieldName ?? key` for columns,
643
+ * and better-auth's own resolved `columns` / `name` for indexes — so an index name
644
+ * here is the name better-auth's introspection expects to find.
645
+ * @param options The better-auth options the DO will run — the plugin list decides which tables exist.
646
+ * @returns SQL statements to execute in order.
647
+ * @experimental
648
+ */
649
+ declare const authDoSchemaStatements: (options: LunoraAuthOptions) => string[];
650
+ /**
651
+ * `ALTER TABLE … ADD COLUMN` statements for columns the live schema is missing.
652
+ *
653
+ * ## Why this is needed at all
654
+ *
655
+ * {@link authDoSchemaStatements} is entirely `IF NOT EXISTS`, which makes it safe to
656
+ * re-run but blind to change: a *new table* appears on the next cold start, a *new
657
+ * column on an existing table* never does. That is not a hypothetical — enabling the
658
+ * `admin` plugin after first deploy adds `role` / `banned` / `banExpires` to `user`,
659
+ * and without this the object would keep serving a `user` table that cannot hold them.
660
+ *
661
+ * Additive only. Nothing here drops, renames, or retypes a column: a column that
662
+ * exists is left exactly as it is, so a schema someone has deliberately diverged is
663
+ * never "corrected" underneath them.
664
+ *
665
+ * ## What SQLite will not let us do
666
+ *
667
+ * `ADD COLUMN` cannot introduce a `NOT NULL` column without a default (existing rows
668
+ * would violate it immediately), and cannot introduce `UNIQUE`. So a required column
669
+ * with a static default is added with both; a required column *without* one is added
670
+ * **nullable**, which differs from what a fresh `CREATE TABLE` would produce. That is
671
+ * the deliberate trade: better-auth writes these fields on every insert, so nullable
672
+ * is harmless, whereas refusing to add the column at all would leave the object
673
+ * broken. Uniqueness is unaffected — better-auth expresses it as a separate index, and
674
+ * those are `IF NOT EXISTS`, so they are created by the statements above.
675
+ * @param options The better-auth options the DO runs, already resolved.
676
+ * @param existingColumns Physical column names currently present on a table; empty/absent for a table that does not exist yet (it will be created instead).
677
+ * @returns SQL statements to execute in order; empty when the live schema is current.
678
+ * @experimental
679
+ */
680
+ declare const authDoColumnAdditions: (options: LunoraAuthOptions, existingColumns: (table: string) => Iterable<string>) => string[];
681
+ /** The slice of a Durable Object namespace this needs — structural, so tests need no runtime. */
682
+ interface AuthNamespaceLike {
683
+ get: (id: unknown) => {
684
+ fetch: (request: Request) => Promise<Response>;
685
+ };
686
+ idFromName: (name: string) => unknown;
687
+ }
688
+ /** What {@link createDoAuthWiring} needs, already resolved against `env`. */
689
+ interface DoAuthWiringOptions {
690
+ /** Base path the auth routes are served under. Defaults to `/api/auth`. */
691
+ basePath?: string;
692
+ /**
693
+ * Shared secret presented on the object's internal session route. `undefined`
694
+ * means identity resolution fails closed — see {@link DoAuthWiring.resolveIdentity}.
695
+ */
696
+ internalSecret: string | undefined;
697
+ /** The bound namespace, or `undefined` when the binding is absent from `env`. */
698
+ namespace: AuthNamespaceLike | undefined;
699
+ /**
700
+ * Name of the object instance holding the auth tables. Defaults to `"auth"`.
701
+ *
702
+ * One object owns the whole auth schema, so this exists to let an app pick the
703
+ * name (or run separate objects per deployment/tenant) rather than being pinned to
704
+ * a hardcoded one.
705
+ */
706
+ objectName?: string;
707
+ }
708
+ /** The worker options DO-backed auth replaces. */
709
+ interface DoAuthWiring {
710
+ /**
711
+ * Reads the audit log out of the object, so the studio's audit feed works in DO
712
+ * mode. Answers an empty page rather than throwing when the object is unreachable
713
+ * or no secret is configured — an unavailable feed should read as empty, not 500
714
+ * the studio.
715
+ */
716
+ auditReader: AuthAuditReader;
717
+ /** Forwards `/api/auth/*` to the object; `undefined` for anything else. */
718
+ authHandler: (request: Request) => Promise<Response | undefined>;
719
+ /** Resolves a request's identity by asking the object. `null` when anonymous, unreachable, or ungated. */
720
+ resolveIdentity: (request: Request) => Promise<null | {
721
+ userId: string;
722
+ }>;
723
+ }
724
+ /**
725
+ * Build the worker-side wiring for an auth Durable Object.
726
+ *
727
+ * Every failure path answers "not authenticated" rather than throwing: this runs on
728
+ * the request path for every request that touches `ctx.auth`, and a throw there would
729
+ * turn a misconfiguration into a 500 on traffic that has nothing to do with auth.
730
+ * @param options The resolved namespace, secret, and names.
731
+ * @returns The `authHandler` / `resolveIdentity` pair.
732
+ * @experimental
733
+ */
734
+ declare const createDoAuthWiring: (options: DoAuthWiringOptions) => DoAuthWiring;
566
735
  /** better-auth's `databaseHooks` shape, derived so a rename upstream fails to compile rather than silently mis-hooking. */
567
736
  type DatabaseHooks = NonNullable<BetterAuthOptions["databaseHooks"]>;
568
737
  /** Config for the signup gate hooks: the base {@link EmailGateConfig} plus an optional classification tap. */
@@ -697,4 +866,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
697
866
  * with the same 60s cookie cache as `rolling`.
698
867
  */
699
868
  declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
700
- export { type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthCapabilities, type AuthConfigInfo, type AuthInvitation, type AuthMember, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, buildAuditEntry, compileMigrationsSql, createAuthAdmin, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
869
+ export { READ_AUDIT_PATH as AUTH_DO_AUDIT_PATH, INTERNAL_SECRET_HEADER as AUTH_DO_SECRET_HEADER, RESOLVE_SESSION_PATH as AUTH_DO_SESSION_PATH, type AppendAuthAuditEntry, type AppendAuthAuditOptions, type AuthAccount, type AuthAdmin, type AuthAdminSession, type AuthAdminUser, type AuthAuditEvent, type AuthAuditHookConfig, type AuthAuditReader, type AuthCapabilities, type AuthConfigInfo, type AuthDoOptions, type AuthDoState, type AuthInvitation, type AuthMember, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthTeam, type AuthTeamMember, type AuthTimestamp, type AuthUserFieldSpec, type CreateAuthAdminOptions, DEFAULT_AUTH_BASE_PATH, type DoAuthWiring, type DoAuthWiringOptions, type EmailClassification, type EmailGateConfig, type EmailGateHookConfig, type ImpersonationResult, type ListUsersOptions, type LunoraAuth, LunoraAuthAdminError, LunoraAuthDO, type LunoraAuthOptions, type SessionPolicy, type SqlExecutor, authAuditHook, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, compileMigrationsSql, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthRequest, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{lunoraAuthAdapter as t,lunoraD1Adapter as o}from"./adapter.mjs";import{LunoraAuthAdminError as i,createAuthAdmin as u}from"./packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs";import{AUTH_AUDIT_TABLE as l,appendAuthAuditEntry as m,createAuthAuditReader as s,ensureAuthAuditTable as d,readAuthAuditLog as h}from"./audit.mjs";import{authAuditHook as n,buildAuditEntry as f,eventForPath as x,withAuthAudit as E}from"./packem_shared/authAuditHook-Dx3sqf3G.mjs";import{createAuth as c,resolveAuthOptions as y}from"./packem_shared/createAuth-DS6PL8Mb.mjs";import{emailGateDatabaseHooks as S,withEmailGate as _}from"./packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs";import{assertEmailAllowed as D,classifyEmail as H,emailGateMiddleware as P,loadEmailDomainLists as v}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as U,handleAuthRequest as b}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";import{LunoraAuthHeadersError as I,withAuthPlugins as R}from"./middleware.mjs";import{compileMigrationsSql as F,ensureMigrated as G}from"./packem_shared/compileMigrationsSql-C-2ALo6c.mjs";import{default as k}from"./schema.mjs";import{sessionPresets as O,validateSessionPolicy as V}from"./packem_shared/sessionPresets-DpEFjXKV.mjs";import{createSqlAuthStore as Y,d1Executor as j}from"./sql-store.mjs";import{createMemoryAuthStore as C,matchesWhere as J}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as Q,verifyTurnstile as X}from"./turnstile.mjs";import{verifyTurnstileMiddleware as $}from"./turnstile-middleware.mjs";export{l as AUTH_AUDIT_TABLE,U as DEFAULT_AUTH_BASE_PATH,i as LunoraAuthAdminError,I as LunoraAuthHeadersError,Q as TURNSTILE_VERIFY_ENDPOINT,m as appendAuthAuditEntry,D as assertEmailAllowed,n as authAuditHook,k as authTables,f as buildAuditEntry,H as classifyEmail,F as compileMigrationsSql,c as createAuth,u as createAuthAdmin,s as createAuthAuditReader,C as createMemoryAuthStore,Y as createSqlAuthStore,j as d1Executor,S as emailGateDatabaseHooks,P as emailGateMiddleware,d as ensureAuthAuditTable,G as ensureMigrated,x as eventForPath,b as handleAuthRequest,v as loadEmailDomainLists,t as lunoraAuthAdapter,o as lunoraD1Adapter,J as matchesWhere,h as readAuthAuditLog,y as resolveAuthOptions,O as sessionPresets,V as validateSessionPolicy,X as verifyTurnstile,$ as verifyTurnstileMiddleware,E as withAuthAudit,R as withAuthPlugins,_ as withEmailGate};
1
+ import{s as r,l as o,w as a}from"./packem_shared/adapter-RvDcm0Zy.mjs";import{LunoraAuthAdminError as u,createAuthAdmin as i}from"./packem_shared/LunoraAuthAdminError-CiHsF1qZ.mjs";import{AUTH_AUDIT_TABLE as m,appendAuthAuditEntry as l,createAuthAuditReader as d,ensureAuthAuditTable as h,readAuthAuditLog as E}from"./audit.mjs";import{authAuditHook as p,buildAuditEntry as T,eventForPath as f,withAuthAudit as _}from"./packem_shared/authAuditHook-Dx3sqf3G.mjs";import{READ_AUDIT_PATH as D,INTERNAL_SECRET_HEADER as S,RESOLVE_SESSION_PATH as H,LunoraAuthDO as c}from"./packem_shared/AUTH_DO_AUDIT_PATH-Cy_f9vvT.mjs";import{createAuth as L,resolveAuthOptions as P}from"./packem_shared/createAuth-DS6PL8Mb.mjs";import{authDoColumnAdditions as I,authDoSchemaStatements as O}from"./packem_shared/authDoColumnAdditions-B8BRbdzn.mjs";import{createDoAuthWiring as y}from"./packem_shared/createDoAuthWiring-Dd6AloWo.mjs";import{emailGateDatabaseHooks as g,withEmailGate as v}from"./packem_shared/emailGateDatabaseHooks-DzBD1Qoq.mjs";import{assertEmailAllowed as b,classifyEmail as q,emailGateMiddleware as C,loadEmailDomainLists as F}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as k,handleAuthRequest as B}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";import{LunoraAuthHeadersError as W,withAuthPlugins as Y}from"./middleware.mjs";import{compileMigrationsSql as z,ensureMigrated as J}from"./packem_shared/compileMigrationsSql-C-2ALo6c.mjs";import{default as Q}from"./schema.mjs";import{sessionPresets as Z,validateSessionPolicy as $}from"./packem_shared/sessionPresets-DpEFjXKV.mjs";import{createSqlAuthStore as te,d1Executor as re}from"./sql-store.mjs";import{createMemoryAuthStore as ae,matchesWhere as Ae}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as ie,verifyTurnstile as se}from"./turnstile.mjs";import{verifyTurnstileMiddleware as le}from"./turnstile-middleware.mjs";export{m as AUTH_AUDIT_TABLE,D as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,H as AUTH_DO_SESSION_PATH,k as DEFAULT_AUTH_BASE_PATH,u as LunoraAuthAdminError,c as LunoraAuthDO,W as LunoraAuthHeadersError,ie as TURNSTILE_VERIFY_ENDPOINT,l as appendAuthAuditEntry,b as assertEmailAllowed,p as authAuditHook,I as authDoColumnAdditions,O as authDoSchemaStatements,Q as authTables,T as buildAuditEntry,q as classifyEmail,z as compileMigrationsSql,L as createAuth,i as createAuthAdmin,d as createAuthAuditReader,y as createDoAuthWiring,ae as createMemoryAuthStore,te as createSqlAuthStore,re as d1Executor,g as emailGateDatabaseHooks,C as emailGateMiddleware,h as ensureAuthAuditTable,J as ensureMigrated,f as eventForPath,B as handleAuthRequest,F as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,Ae as matchesWhere,E as readAuthAuditLog,P as resolveAuthOptions,Z as sessionPresets,$ as validateSessionPolicy,se as verifyTurnstile,le as verifyTurnstileMiddleware,_ as withAuthAudit,Y as withAuthPlugins,v as withEmailGate};
@@ -0,0 +1 @@
1
+ import{w as i,d as h}from"./adapter-RvDcm0Zy.mjs";import{ensureAuthAuditTable as u,createAuthAuditReader as c}from"../audit.mjs";import{resolveAuthOptions as l,createAuth as d}from"./createAuth-DS6PL8Mb.mjs";import{authDoSchemaStatements as f,authDoColumnAdditions as m}from"./authDoColumnAdditions-B8BRbdzn.mjs";import{handleAuthRequest as A}from"./DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";const p=(o,t)=>{const s=Math.max(o.length,t.length);let e=o.length^t.length;for(let r=0;r<s;r+=1){const a=r<o.length?o.charCodeAt(r):0,n=r<t.length?t.charCodeAt(r):0;e|=a^n}return e===0},R="/__lunora/auth/session",E="/__lunora/auth/audit",_="x-lunora-auth-do-secret";class j{#e;#r;#t;#s;#o=!1;constructor(t,s,e={}){this.#t=t.storage,this.#r=s,this.#e=e}#a(){if(this.#s!==void 0)return this.#s;const t=this.#r();if(!this.#o){const s=l(t);for(const e of f(s))[...this.#t.sql.exec(e)];for(const e of m(s,r=>this.#i(r)))[...this.#t.sql.exec(e)];this.#o=!0}return this.#s=d({...t,database:i(this.#t)}),this.#s}#i(t){return[...this.#t.sql.exec("SELECT name FROM pragma_table_info(?)",t)].map(s=>String(s.name))}async#h(t){if(!this.#n(t))return Response.json({error:"unauthorized"},{status:401});const s=h(this.#t);await u(s);const e=await t.json(),r=await c(s).read(e??{});return Response.json({entries:r})}#n(t){const{internalSecret:s}=this.#e;if(s===void 0||s==="")return!1;const e=t.headers.get(_);return e!==null&&p(e,s)}async#u(t){if(!this.#n(t))return Response.json({error:"unauthorized"},{status:401});const s=(await this.#a().api.getSession({headers:t.headers}))?.user.id;return Response.json(s===void 0?{}:{userId:s})}async fetch(t){const s=new URL(t.url);if(s.pathname===R)return this.#u(t);if(s.pathname===E)return this.#h(t);const e=this.#a();return await A(e,t,this.#e.basePath)??Response.json({error:"not an auth route"},{status:404})}}export{_ as INTERNAL_SECRET_HEADER,j as LunoraAuthDO,E as READ_AUDIT_PATH,R as RESOLVE_SESSION_PATH};
@@ -0,0 +1 @@
1
+ import{createAdapterFactory as l}from"better-auth/adapters";import{createSqlAuthStore as c,d1Executor as m}from"../sql-store.mjs";const i=e=>({all:(a,r)=>Promise.resolve([...e.sql.exec(a,...r)]),run:(a,r)=>([...e.sql.exec(a,...r)],Promise.resolve())}),u=e=>async a=>e.transaction(a),p=e=>e,o=e=>e??null,w=e=>e,y=e=>({consumeOne:async({model:a,where:r})=>o(await e.consumeOne(a,r)),count:async({model:a,where:r})=>e.count(a,r??[]),create:async({data:a,model:r})=>p(await e.create(r,a)),delete:async({model:a,where:r})=>{await e.remove(a,r)},deleteMany:async({model:a,where:r})=>e.remove(a,r),findMany:async({limit:a,model:r,offset:n,sortBy:t,where:s})=>w(await e.read(r,{limit:a,offset:n,sortBy:t,where:s??[]})),findOne:async({model:a,where:r})=>{const[n]=await e.read(a,{limit:1,where:r});return o(n)},incrementOne:async({increment:a,model:r,set:n,where:t})=>o(await e.incrementOne(r,t,a,n)),update:async({model:a,update:r,where:n})=>{const[t]=await e.update(a,n,r);return o(t)},updateMany:async({model:a,update:r,where:n})=>(await e.update(a,n,r)).length}),d=(e,a)=>{const r=t=>s=>l({adapter:()=>y(e),config:{adapterId:"lunora",adapterName:"Lunora Adapter",transaction:t(s),supportsBooleans:!1,supportsDates:!1,supportsJSON:!1,supportsNumericIds:!1}})(s),n=()=>!1;return r(a?t=>async s=>a(async()=>s(r(n)(t))):n)},O=e=>d(c(m(e))),x=e=>d(c(i(e)),u(e));export{i as d,O as l,d as s,x as w};
@@ -0,0 +1,76 @@
1
+ import { createAdapterFactory } from 'better-auth/adapters';
2
+ import { d1Executor } from "../sql-store.mjs";
3
+ import { AuthStore } from "../store.mjs";
4
+ /**
5
+ * The slice of `DurableObjectStorage` this module uses.
6
+ *
7
+ * Structural on purpose: it keeps `@lunora/auth` free of a `@lunora/do` dependency
8
+ * and lets tests supply a double whose `transaction` has the same semantics.
9
+ */
10
+ interface DoStorageLike {
11
+ /** Synchronous SQL over the object's SQLite. */
12
+ sql: {
13
+ exec: (query: string, ...bindings: unknown[]) => Iterable<Record<string, unknown>>;
14
+ };
15
+ /**
16
+ * The platform's async transaction primitive. Everything the closure executes
17
+ * against `sql` joins the transaction — it is connection-scoped, so no handle is
18
+ * threaded through — and a throw rolls the whole thing back.
19
+ */
20
+ transaction: <R>(closure: () => Promise<R>) => Promise<R>;
21
+ }
22
+ /**
23
+ * Runs `closure` atomically. Supplied by stores whose backend has real
24
+ * transactions — a Durable Object's storage does; D1 does not.
25
+ */
26
+ type TransactionRunner = <R>(closure: () => Promise<R>) => Promise<R>;
27
+ /**
28
+ * A better-auth database adapter backed by an {@link AuthStore} — the bridge
29
+ * that routes better-auth's reads and writes through Lunora's data layer
30
+ * instead of better-auth's built-in D1/Kysely adapter. Pass the result as
31
+ * `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
32
+ * `createAdapterFactory` handles id generation, default values, field-name
33
+ * mapping and output shaping, so this only translates the cleaned CRUD calls
34
+ * onto the store.
35
+ *
36
+ * ```ts
37
+ * const auth = createAuth({
38
+ * secret: env.AUTH_SECRET,
39
+ * emailAndPassword: { enabled: true },
40
+ * database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
41
+ * });
42
+ * ```
43
+ *
44
+ * Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
45
+ * relational `join` reads (an advanced opt-in) are not handled — pair the
46
+ * adapter with `disableJoins` or let better-auth fall back to per-table reads.
47
+ */
48
+ declare const lunoraAuthAdapter: (store: AuthStore, runInTransaction?: TransactionRunner) => ReturnType<typeof createAdapterFactory>;
49
+ /**
50
+ * One-liner for the common case: a better-auth `database` backed by a Cloudflare
51
+ * D1 binding, via Lunora's SQL store — equivalent to
52
+ * `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
53
+ *
54
+ * Prefer this over passing the raw `env.DB` as `database`. With raw D1,
55
+ * better-auth resolves its Kysely adapter through a runtime `await import(...)`
56
+ * inside `auth.$context`, and that dynamic import never settles under
57
+ * `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
58
+ * in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
59
+ * up-front, so they're unaffected — which makes the hang baffling to debug).
60
+ * This explicit adapter skips that import entirely, so dev and prod behave the
61
+ * same. The migration instance is the one exception — it wants raw `env.DB` so
62
+ * `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
63
+ * never resolved, so the hang doesn't apply there).
64
+ */
65
+ declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
66
+ /**
67
+ * Prototype: a better-auth `database` backed by a Durable Object's own SQLite —
68
+ * `lunoraAuthAdapter(createSqlAuthStore(doExecutor(storage)), doTransactionRunner(storage))`.
69
+ *
70
+ * Unlike `lunoraD1Adapter` this one exposes real transactions, so plugins that
71
+ * demand them (`@better-auth/scim`) accept it. Read the trade-offs in `do-store.ts`
72
+ * before reaching for it: the auth tables then live inside a single Durable Object.
73
+ * @experimental
74
+ */
75
+ declare const lunoraDoAdapter: (storage: DoStorageLike) => ReturnType<typeof lunoraAuthAdapter>;
76
+ export { DoStorageLike as D, lunoraD1Adapter as a, lunoraDoAdapter as b, lunoraAuthAdapter as l };
@@ -0,0 +1,76 @@
1
+ import { createAdapterFactory } from 'better-auth/adapters';
2
+ import { d1Executor } from "../sql-store.js";
3
+ import { AuthStore } from "../store.js";
4
+ /**
5
+ * The slice of `DurableObjectStorage` this module uses.
6
+ *
7
+ * Structural on purpose: it keeps `@lunora/auth` free of a `@lunora/do` dependency
8
+ * and lets tests supply a double whose `transaction` has the same semantics.
9
+ */
10
+ interface DoStorageLike {
11
+ /** Synchronous SQL over the object's SQLite. */
12
+ sql: {
13
+ exec: (query: string, ...bindings: unknown[]) => Iterable<Record<string, unknown>>;
14
+ };
15
+ /**
16
+ * The platform's async transaction primitive. Everything the closure executes
17
+ * against `sql` joins the transaction — it is connection-scoped, so no handle is
18
+ * threaded through — and a throw rolls the whole thing back.
19
+ */
20
+ transaction: <R>(closure: () => Promise<R>) => Promise<R>;
21
+ }
22
+ /**
23
+ * Runs `closure` atomically. Supplied by stores whose backend has real
24
+ * transactions — a Durable Object's storage does; D1 does not.
25
+ */
26
+ type TransactionRunner = <R>(closure: () => Promise<R>) => Promise<R>;
27
+ /**
28
+ * A better-auth database adapter backed by an {@link AuthStore} — the bridge
29
+ * that routes better-auth's reads and writes through Lunora's data layer
30
+ * instead of better-auth's built-in D1/Kysely adapter. Pass the result as
31
+ * `createAuth({ database: lunoraAuthAdapter(store) })`; better-auth's
32
+ * `createAdapterFactory` handles id generation, default values, field-name
33
+ * mapping and output shaping, so this only translates the cleaned CRUD calls
34
+ * onto the store.
35
+ *
36
+ * ```ts
37
+ * const auth = createAuth({
38
+ * secret: env.AUTH_SECRET,
39
+ * emailAndPassword: { enabled: true },
40
+ * database: lunoraAuthAdapter(lunoraStore), // lunoraStore writes via ctx.db
41
+ * });
42
+ * ```
43
+ *
44
+ * Scope: the {@link AuthStore} interface is single-table CRUD. better-auth's
45
+ * relational `join` reads (an advanced opt-in) are not handled — pair the
46
+ * adapter with `disableJoins` or let better-auth fall back to per-table reads.
47
+ */
48
+ declare const lunoraAuthAdapter: (store: AuthStore, runInTransaction?: TransactionRunner) => ReturnType<typeof createAdapterFactory>;
49
+ /**
50
+ * One-liner for the common case: a better-auth `database` backed by a Cloudflare
51
+ * D1 binding, via Lunora's SQL store — equivalent to
52
+ * `lunoraAuthAdapter(createSqlAuthStore(d1Executor(d1)))`.
53
+ *
54
+ * Prefer this over passing the raw `env.DB` as `database`. With raw D1,
55
+ * better-auth resolves its Kysely adapter through a runtime `await import(...)`
56
+ * inside `auth.$context`, and that dynamic import never settles under
57
+ * `@cloudflare/vite-plugin`'s worker runner — so it hangs *every* auth request
58
+ * in `pnpm dev` (a standalone `wrangler dev` or a deployed worker bundle it
59
+ * up-front, so they're unaffected — which makes the hang baffling to debug).
60
+ * This explicit adapter skips that import entirely, so dev and prod behave the
61
+ * same. The migration instance is the one exception — it wants raw `env.DB` so
62
+ * `ensureMigrated`'s Kysely migrator can create the tables (its `$context` is
63
+ * never resolved, so the hang doesn't apply there).
64
+ */
65
+ declare const lunoraD1Adapter: (d1: Parameters<typeof d1Executor>[0]) => ReturnType<typeof lunoraAuthAdapter>;
66
+ /**
67
+ * Prototype: a better-auth `database` backed by a Durable Object's own SQLite —
68
+ * `lunoraAuthAdapter(createSqlAuthStore(doExecutor(storage)), doTransactionRunner(storage))`.
69
+ *
70
+ * Unlike `lunoraD1Adapter` this one exposes real transactions, so plugins that
71
+ * demand them (`@better-auth/scim`) accept it. Read the trade-offs in `do-store.ts`
72
+ * before reaching for it: the auth tables then live inside a single Durable Object.
73
+ * @experimental
74
+ */
75
+ declare const lunoraDoAdapter: (storage: DoStorageLike) => ReturnType<typeof lunoraAuthAdapter>;
76
+ export { DoStorageLike as D, lunoraD1Adapter as a, lunoraDoAdapter as b, lunoraAuthAdapter as l };
@@ -0,0 +1 @@
1
+ import{getAuthTablesWithResolvedIndexes as f,getDatabaseFieldIndexName as N}from"@better-auth/core/db/internal";const s=n=>`"${n.replaceAll('"','""')}"`,c=(n,e)=>{if(n==="id"||e.references?.field==="id")return"text";const{type:t}=e;if(Array.isArray(t))return"text";switch(t){case"boolean":return"integer";case"date":return"date";case"number":return"bigint"in e&&e.bigint===!0?"bigint":"integer";default:return"text"}},b=n=>{const{defaultValue:e,type:t,unique:r}=n;if(!(r===!0&&n.required===!1)&&!(e==null||typeof e=="function")){if(t==="boolean")return e===!0?"1":"0";if(t==="number"&&typeof e=="number"&&Number.isFinite(e))return String(e);if(t==="string"&&typeof e=="string")return`'${e.replaceAll("'","''")}'`}},p=(n,e)=>{const t=[];for(const[r,o]of Object.entries(e)){const i=o.unique===!0;if(!i&&o.index!==!0)continue;const l=o.fieldName??r,u=N(n,l,i);t.push(`CREATE ${i?"UNIQUE ":""}INDEX IF NOT EXISTS ${s(u)} ON ${s(n)} (${s(l)})`)}return t},E=(n,e)=>{const t=[s(n),c(n,e)],r=b(e);return e.required!==!1&&r!==void 0&&t.push("NOT NULL"),r!==void 0&&t.push(`DEFAULT ${r}`),t.join(" ")},T=(n,e)=>{const t=[s(n),c(n,e)];return e.required!==!1&&t.push("NOT NULL"),t.join(" ")},A=n=>{const{indexesByTable:e,tables:t}=f(n),r=[],o=[];for(const i of Object.values(t)){if(i.disableMigrations===!0)continue;const l=[`${s("id")} text NOT NULL PRIMARY KEY`,...Object.entries(i.fields).map(([u,a])=>T(a.fieldName??u,a))];r.push(`CREATE TABLE IF NOT EXISTS ${s(i.modelName)} (${l.join(", ")})`),o.push(...p(i.modelName,i.fields));for(const u of e.get(i.modelName)??[]){const a=u.unique===!0?"UNIQUE ":"",d=u.columns.map(m=>s(m)).join(", ");o.push(`CREATE ${a}INDEX IF NOT EXISTS ${s(u.name)} ON ${s(i.modelName)} (${d})`)}}return[...r,...o]},$=(n,e)=>{const{tables:t}=f(n),r=[];for(const o of Object.values(t)){if(o.disableMigrations===!0)continue;const i=new Set(e(o.modelName));if(i.size!==0)for(const[l,u]of Object.entries(o.fields)){const a=u.fieldName??l;i.has(a)||r.push(`ALTER TABLE ${s(o.modelName)} ADD COLUMN ${E(a,u)}`)}}return r};export{$ as authDoColumnAdditions,A as authDoSchemaStatements};
@@ -0,0 +1 @@
1
+ import{INTERNAL_SECRET_HEADER as u,RESOLVE_SESSION_PATH as f,READ_AUDIT_PATH as w}from"./AUTH_DO_AUDIT_PATH-Cy_f9vvT.mjs";import{DEFAULT_AUTH_BASE_PATH as A}from"./DEFAULT_AUTH_BASE_PATH-kIwlEt8i.mjs";const m=c=>{const{basePath:d=A,internalSecret:a,namespace:i,objectName:h="auth"}=c,o=()=>{if(i)return i.get(i.idFromName(h))},l=async(t,e,s)=>{if(!a)return;const n=o();if(!n)return;const r=await n.fetch(new Request(new URL(t,e),{body:JSON.stringify(s),headers:{"content-type":"application/json",[u]:a},method:"POST"}));return r.ok?r:void 0};return{auditReader:{read:async t=>{const e=await l(w,"https://auth-do.invalid",t);return e?(await e.json())?.entries??[]:[]}},authHandler:async t=>{if(new URL(t.url).pathname.startsWith(d))return o()?.fetch(t)},resolveIdentity:async t=>{if(!a)return null;const e=o();if(!e)return null;const s=new Headers(t.headers);s.set(u,a);const n=await e.fetch(new Request(new URL(f,t.url),{headers:s}));if(!n.ok)return null;const r=await n.json();return r?.userId?{userId:r.userId}:null}}};export{m as createDoAuthWiring};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/auth",
3
- "version": "1.0.0-alpha.50",
3
+ "version": "1.0.0-alpha.52",
4
4
  "description": "Auth for Lunora — a thin better-auth wrapper: email/password, OAuth, plugins, D1-backed",
5
5
  "keywords": [
6
6
  "auth",
@@ -104,7 +104,7 @@
104
104
  "@better-auth/passkey": "1.7.0-rc.2",
105
105
  "@better-auth/scim": "1.7.0-rc.2",
106
106
  "@lunora/errors": "1.0.0-alpha.8",
107
- "@lunora/server": "1.0.0-alpha.44",
107
+ "@lunora/server": "1.0.0-alpha.46",
108
108
  "@lunora/values": "1.0.0-alpha.11",
109
109
  "@visulima/disposable-email-domains": "1.0.1",
110
110
  "@visulima/email-verifier": "1.0.1",