@lunora/auth 1.0.0-alpha.212 → 1.0.0-alpha.213

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 CHANGED
@@ -55,7 +55,8 @@ pnpm add @lunora/auth
55
55
  ## Usage
56
56
 
57
57
  ```ts
58
- import { createAuth, ensureMigrated, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";
58
+ import { createAuth, handleAuthRequest, lunoraD1Adapter } from "@lunora/auth";
59
+ import { createMigrationAuth, ensureMigrated } from "@lunora/auth/migrate";
59
60
 
60
61
  const options = { secret: env.AUTH_SECRET, emailAndPassword: { enabled: true } };
61
62
 
@@ -69,7 +70,7 @@ const auth = createAuth({ ...options, database: lunoraD1Adapter(env.DB) });
69
70
  // it once and keep it — `ensureMigrated` single-flights on the options object it
70
71
  // is handed, so a `createAuth({...})` built inside `fetch` is a fresh key every
71
72
  // request and the migration re-runs on each one.
72
- const migrationAuth = createAuth({ ...options, database: env.DB });
73
+ const migrationAuth = createMigrationAuth({ ...options, database: env.DB });
73
74
 
74
75
  // In your Worker's fetch handler, route /api/auth/* to better-auth and fall
75
76
  // through to the Lunora worker for everything else:
@@ -33,6 +33,17 @@ interface EmailGateConfig {
33
33
  * top of the built-in list. Wildcard/subdomain aware.
34
34
  */
35
35
  denyDomains?: ReadonlyArray<string>;
36
+ /**
37
+ * Which built-in lists {@link assertEmailAllowed} loads (both by default, so the
38
+ * returned class is complete). The disposable list is ~2.7 MB and the free list
39
+ * ~143 KB of bundle; `emailGateMiddleware` and `emailGateDatabaseHooks` set this
40
+ * for you: the free list loads only with `onClassify`, the disposable list only
41
+ * with `blockDisposable !== false` or `onClassify`.
42
+ */
43
+ lists?: {
44
+ disposable?: boolean;
45
+ free?: boolean;
46
+ };
36
47
  /**
37
48
  * Opt-in MX deliverability verification. Off by default because it needs DNS
38
49
  * (`@visulima/email-verifier/checks/mx` → `node:dns`), which is not available
@@ -75,13 +86,18 @@ interface EmailGateMiddlewareOptions<Context> extends EmailGateConfig {
75
86
  onClassify?: (classification: EmailClassification, context: Context) => void;
76
87
  }
77
88
  /**
78
- * Inject the built-in disposable + free domain lists into the lookup packages,
79
- * once. Call this at worker init on workerd (where the packages' `node:fs`
80
- * loader is unavailable) so {@link classifyEmail} has data to match against;
81
- * {@link assertEmailAllowed} / {@link emailGateMiddleware} await it for you, so
82
- * the gating path is always edge-safe. Idempotent — repeat calls share one load.
89
+ * Inject the built-in domain lists into the lookup packages, once each. Call this
90
+ * at worker init on workerd (where the packages' `node:fs` loader is unavailable)
91
+ * so {@link classifyEmail} has data to match against; {@link assertEmailAllowed} /
92
+ * {@link emailGateMiddleware} await it for you, loading only what the config needs.
93
+ * Both lists (~2.7 MB disposable, ~143 KB free) load by default; pass
94
+ * `{ free: false }` or `{ disposable: false }` to skip one. Idempotent — repeat
95
+ * calls share one load per list.
83
96
  */
84
- declare const loadEmailDomainLists: () => Promise<void>;
97
+ declare const loadEmailDomainLists: ({ disposable, free }?: {
98
+ disposable?: boolean;
99
+ free?: boolean;
100
+ }) => Promise<void>;
85
101
  /**
86
102
  * Classify an email address' domain as `disposable` / `free` / `business`,
87
103
  * pure-data and edge-safe (no DNS, no filesystem). A structurally invalid
@@ -33,6 +33,17 @@ interface EmailGateConfig {
33
33
  * top of the built-in list. Wildcard/subdomain aware.
34
34
  */
35
35
  denyDomains?: ReadonlyArray<string>;
36
+ /**
37
+ * Which built-in lists {@link assertEmailAllowed} loads (both by default, so the
38
+ * returned class is complete). The disposable list is ~2.7 MB and the free list
39
+ * ~143 KB of bundle; `emailGateMiddleware` and `emailGateDatabaseHooks` set this
40
+ * for you: the free list loads only with `onClassify`, the disposable list only
41
+ * with `blockDisposable !== false` or `onClassify`.
42
+ */
43
+ lists?: {
44
+ disposable?: boolean;
45
+ free?: boolean;
46
+ };
36
47
  /**
37
48
  * Opt-in MX deliverability verification. Off by default because it needs DNS
38
49
  * (`@visulima/email-verifier/checks/mx` → `node:dns`), which is not available
@@ -75,13 +86,18 @@ interface EmailGateMiddlewareOptions<Context> extends EmailGateConfig {
75
86
  onClassify?: (classification: EmailClassification, context: Context) => void;
76
87
  }
77
88
  /**
78
- * Inject the built-in disposable + free domain lists into the lookup packages,
79
- * once. Call this at worker init on workerd (where the packages' `node:fs`
80
- * loader is unavailable) so {@link classifyEmail} has data to match against;
81
- * {@link assertEmailAllowed} / {@link emailGateMiddleware} await it for you, so
82
- * the gating path is always edge-safe. Idempotent — repeat calls share one load.
89
+ * Inject the built-in domain lists into the lookup packages, once each. Call this
90
+ * at worker init on workerd (where the packages' `node:fs` loader is unavailable)
91
+ * so {@link classifyEmail} has data to match against; {@link assertEmailAllowed} /
92
+ * {@link emailGateMiddleware} await it for you, loading only what the config needs.
93
+ * Both lists (~2.7 MB disposable, ~143 KB free) load by default; pass
94
+ * `{ free: false }` or `{ disposable: false }` to skip one. Idempotent — repeat
95
+ * calls share one load per list.
83
96
  */
84
- declare const loadEmailDomainLists: () => Promise<void>;
97
+ declare const loadEmailDomainLists: ({ disposable, free }?: {
98
+ disposable?: boolean;
99
+ free?: boolean;
100
+ }) => Promise<void>;
85
101
  /**
86
102
  * Classify an email address' domain as `disposable` / `free` / `business`,
87
103
  * pure-data and edge-safe (no DNS, no filesystem). A structurally invalid
@@ -1 +1 @@
1
- import{LunoraError as r}from"@lunora/errors";import{assertListLoaded as c,isDisposableDomain as u,setDomains as w}from"@visulima/disposable-email-domains";import{extractDomain as h,assertListLoaded as f,isFreeDomain as D,setDomains as y}from"@visulima/free-email-domains";const n=a=>{const s=a.default??a;return Array.isArray(s)?s:[]};let i;const L=async()=>{if(i)return i;const a=(async()=>{const[s,e]=await Promise.all([import("@visulima/disposable-email-domains/domains"),import("@visulima/free-email-domains/domains")]);w(n(s)),y(n(e))})();return i=a,a.catch(()=>{i=void 0}),a},m=a=>{try{return new URL(`https://${a}`).hostname}catch{return a.toLowerCase()}},l=a=>a&&a.length>0?new Set(a.map(s=>m(s))):void 0,p=(a,s={})=>{const e=h(a);if(e===void 0)return{domain:void 0,emailClass:"business"};try{c(),f()}catch(d){throw new Error("@lunora/auth: the email domain lists are not loaded; `await loadEmailDomainLists()` before calling classifyEmail (assertEmailAllowed does this for you).",{cause:d})}const t=m(e),o=l(s.allowDomains);return u(t,{allowDomains:o,customDomains:l(s.denyDomains)})?{domain:t,emailClass:"disposable"}:D(t,{allowDomains:o})?{domain:t,emailClass:"free"}:{domain:t,emailClass:"business"}},E=async a=>{const{checkMxRecords:s}=await import("@visulima/email-verifier/checks/mx"),e=await s(a);return e.valid||e.deferred===!0},v=async(a,s={})=>{if(s.requireValidSyntax!==!1){const{validateSyntax:t}=await import("@visulima/email-verifier/checks/syntax");if(!t(a))throw new r("VALIDATION_ERROR",`@lunora/auth: "${a}" is not a valid email address.`)}await L();const e=p(a,s);if(e.emailClass==="disposable"&&s.blockDisposable!==!1)throw new r("EMAIL_DOMAIN_BLOCKED",`@lunora/auth: signups from the disposable/throwaway domain "${e.domain??a}" are not allowed.`);if(s.mx===!0&&e.domain!==void 0&&!await E(e.domain))throw new r("EMAIL_UNDELIVERABLE",`@lunora/auth: the domain "${e.domain}" publishes no MX records, so mail to it cannot be delivered.`);return e},R=a=>async({ctx:s,next:e})=>{const t=a.email(s);if(t===void 0||t==="")throw new r("VALIDATION_ERROR","@lunora/auth: emailGateMiddleware received no email to check.");const o=await v(t,a);return a.onClassify?.(o,s),e()};export{v as assertEmailAllowed,p as classifyEmail,R as emailGateMiddleware,L as loadEmailDomainLists};
1
+ import{LunoraError as l}from"@lunora/errors";import{assertListLoaded as D,isDisposableDomain as v,setDomains as y}from"@visulima/disposable-email-domains";import{extractDomain as w,assertListLoaded as L,setDomains as b,isFreeDomain as p}from"@visulima/free-email-domains";const c=a=>{const s=a.default??a;return Array.isArray(s)?s:[]};let r,n,d=!1;const u=(a,s,t)=>{if(a)return a;const e=s();return e.catch(t),e},E=async({disposable:a=!0,free:s=!0}={})=>{a&&(r=u(r,async()=>{y(c(await import("@visulima/disposable-email-domains/domains")))},()=>{r=void 0})),s&&(n=u(n,async()=>{b(c(await import("@visulima/free-email-domains/domains"))),d=!0},()=>{n=void 0})),await Promise.all([a?r:void 0,s?n:void 0])},m=a=>{try{return new URL(`https://${a}`).hostname}catch{return a.toLowerCase()}},f=a=>a&&a.length>0?new Set(a.map(s=>m(s))):void 0,A=(a,s={})=>{const t=w(a);if(t===void 0)return{domain:void 0,emailClass:"business"};try{D(),d&&L()}catch(i){throw new Error("@lunora/auth: the email domain lists are not loaded; `await loadEmailDomainLists()` before calling classifyEmail (assertEmailAllowed does this for you).",{cause:i})}const e=m(t),o=f(s.allowDomains);return v(e,{allowDomains:o,customDomains:f(s.denyDomains)})?{domain:e,emailClass:"disposable"}:d&&p(e,{allowDomains:o})?{domain:e,emailClass:"free"}:{domain:e,emailClass:"business"}},C=async a=>{const{checkMxRecords:s}=await import("@visulima/email-verifier/checks/mx"),t=await s(a);return t.valid||t.deferred===!0},M=async(a,s={})=>{if(s.requireValidSyntax!==!1){const{validateSyntax:h}=await import("@visulima/email-verifier/checks/syntax");if(!h(a))throw new l("VALIDATION_ERROR",`@lunora/auth: "${a}" is not a valid email address.`)}const t=s.lists??{},e=t.disposable!==!1;await E({disposable:e,free:t.free!==!1});const o=w(a),i=e?A(a,s):{domain:o===void 0?void 0:m(o),emailClass:"business"};if(i.emailClass==="disposable"&&s.blockDisposable!==!1)throw new l("EMAIL_DOMAIN_BLOCKED",`@lunora/auth: signups from the disposable/throwaway domain "${i.domain??a}" are not allowed.`);if(s.mx===!0&&i.domain!==void 0&&!await C(i.domain))throw new l("EMAIL_UNDELIVERABLE",`@lunora/auth: the domain "${i.domain}" publishes no MX records, so mail to it cannot be delivered.`);return i},O=a=>async({ctx:s,next:t})=>{const e=a.email(s);if(e===void 0||e==="")throw new l("VALIDATION_ERROR","@lunora/auth: emailGateMiddleware received no email to check.");const o=a.onClassify!==void 0,i=await M(e,{...a,lists:{disposable:a.blockDisposable!==!1||o,free:o}});return a.onClassify?.(i,s),t()};export{M as assertEmailAllowed,A as classifyEmail,O as emailGateMiddleware,E as loadEmailDomainLists};
package/dist/index.d.mts CHANGED
@@ -1079,43 +1079,6 @@ declare const handleAuthRequest: (auth: LunoraAuth, request: Request, basePath?:
1079
1079
  * @returns The `DROP INDEX` statements followed by the `DROP COLUMN`.
1080
1080
  */
1081
1081
  declare const legacyIssuerCleanupStatements: (accountTable?: string, indexNames?: Iterable<string>) => string[];
1082
- /**
1083
- * Apply better-auth's required schema (`user`, `session`, `account`,
1084
- * `verification`) to the configured database. Idempotent — better-auth
1085
- * diffs the existing schema and only runs the missing DDL.
1086
- *
1087
- * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
1088
- * per table) doesn't fire on every request. In Cloudflare Workers the same
1089
- * `env.DB` binding is reused across invocations within a single isolate, so
1090
- * caching against the options object — which captures the binding — is
1091
- * sufficient.
1092
- *
1093
- * For production you should prefer pre-applying the schema at deploy time
1094
- * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
1095
- * dev/playground and small deployments.
1096
- */
1097
- declare const ensureMigrated: (auth: LunoraAuth | {
1098
- options: LunoraAuthOptions;
1099
- }) => Promise<void>;
1100
- /**
1101
- * Compile better-auth's migrations to a single SQL string. Useful for
1102
- * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
1103
- * before the first user request.
1104
- *
1105
- * Compiles from the SAME resolved options `createAuth` runs with (via
1106
- * `resolveAuthOptions`), not the raw caller options — so the schema includes the
1107
- * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
1108
- * from the raw options would omit it, and the running worker would then write to
1109
- * a table the migration never created.
1110
- *
1111
- * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
1112
- * reading the database, so it cannot know whether the `account.issuer` column better-auth
1113
- * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
1114
- * to make that moot. `ensureMigrated` handles it automatically; on this path, run
1115
- * `legacyIssuerCleanupStatements()` once against a database that still has the column.
1116
- * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
1117
- */
1118
- declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
1119
1082
  /**
1120
1083
  * Lunora-friendly view over better-auth's `session` option.
1121
1084
  *
@@ -1171,4 +1134,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
1171
1134
  * with the same 60s cookie cache as `rolling`.
1172
1135
  */
1173
1136
  declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
1174
- 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 AuthJurisdiction, type AuthJurisdictionMove, type AuthMember, type AuthMoveResult, type AuthMoveTableReport, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthSignUpInvitation, 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, authDiscoveryPaths, authDiscoveryPathsFor, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, compileMigrationsSql, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthDiscoveryRequest, handleAuthRequest, legacyIssuerCleanupStatements, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
1137
+ 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 AuthJurisdiction, type AuthJurisdictionMove, type AuthMember, type AuthMoveResult, type AuthMoveTableReport, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthSignUpInvitation, 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, authDiscoveryPaths, authDiscoveryPathsFor, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, eventForPath, handleAuthDiscoveryRequest, handleAuthRequest, legacyIssuerCleanupStatements, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
package/dist/index.d.ts CHANGED
@@ -1079,43 +1079,6 @@ declare const handleAuthRequest: (auth: LunoraAuth, request: Request, basePath?:
1079
1079
  * @returns The `DROP INDEX` statements followed by the `DROP COLUMN`.
1080
1080
  */
1081
1081
  declare const legacyIssuerCleanupStatements: (accountTable?: string, indexNames?: Iterable<string>) => string[];
1082
- /**
1083
- * Apply better-auth's required schema (`user`, `session`, `account`,
1084
- * `verification`) to the configured database. Idempotent — better-auth
1085
- * diffs the existing schema and only runs the missing DDL.
1086
- *
1087
- * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
1088
- * per table) doesn't fire on every request. In Cloudflare Workers the same
1089
- * `env.DB` binding is reused across invocations within a single isolate, so
1090
- * caching against the options object — which captures the binding — is
1091
- * sufficient.
1092
- *
1093
- * For production you should prefer pre-applying the schema at deploy time
1094
- * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
1095
- * dev/playground and small deployments.
1096
- */
1097
- declare const ensureMigrated: (auth: LunoraAuth | {
1098
- options: LunoraAuthOptions;
1099
- }) => Promise<void>;
1100
- /**
1101
- * Compile better-auth's migrations to a single SQL string. Useful for
1102
- * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
1103
- * before the first user request.
1104
- *
1105
- * Compiles from the SAME resolved options `createAuth` runs with (via
1106
- * `resolveAuthOptions`), not the raw caller options — so the schema includes the
1107
- * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
1108
- * from the raw options would omit it, and the running worker would then write to
1109
- * a table the migration never created.
1110
- *
1111
- * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
1112
- * reading the database, so it cannot know whether the `account.issuer` column better-auth
1113
- * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
1114
- * to make that moot. `ensureMigrated` handles it automatically; on this path, run
1115
- * `legacyIssuerCleanupStatements()` once against a database that still has the column.
1116
- * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
1117
- */
1118
- declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
1119
1082
  /**
1120
1083
  * Lunora-friendly view over better-auth's `session` option.
1121
1084
  *
@@ -1171,4 +1134,4 @@ declare const validateSessionPolicy: (policy: SessionPolicy) => SessionPolicy;
1171
1134
  * with the same 60s cookie cache as `rolling`.
1172
1135
  */
1173
1136
  declare const sessionPresets: Record<"longLived" | "rolling" | "strict", SessionPolicy>;
1174
- 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 AuthJurisdiction, type AuthJurisdictionMove, type AuthMember, type AuthMoveResult, type AuthMoveTableReport, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthSignUpInvitation, 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, authDiscoveryPaths, authDiscoveryPathsFor, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, compileMigrationsSql, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, ensureMigrated, eventForPath, handleAuthDiscoveryRequest, handleAuthRequest, legacyIssuerCleanupStatements, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
1137
+ 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 AuthJurisdiction, type AuthJurisdictionMove, type AuthMember, type AuthMoveResult, type AuthMoveTableReport, type AuthNamespaceLike, type AuthOrgRole, type AuthOrganization, type AuthPage, type AuthPasskey, type AuthSignUpInvitation, 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, authDiscoveryPaths, authDiscoveryPathsFor, authDoColumnAdditions, authDoSchemaStatements, buildAuditEntry, createAuthAdmin, createDoAuthWiring, emailGateDatabaseHooks, eventForPath, handleAuthDiscoveryRequest, handleAuthRequest, legacyIssuerCleanupStatements, sessionPresets, validateSessionPolicy, withAuthAudit, withEmailGate };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{l as r,a as o,b as a}from"./packem_shared/adapter-CBZ17jKi.mjs";import{LunoraAuthAdminError as s,createAuthAdmin as u}from"./packem_shared/LunoraAuthAdminError-uhKG1MgQ.mjs";import{AUTH_AUDIT_TABLE as n,appendAuthAuditEntry as m,createAuthAuditReader as h,ensureAuthAuditTable as l,readAuthAuditLog as p}from"./audit.mjs";import{authAuditHook as f,buildAuditEntry as x,eventForPath as E,withAuthAudit as T}from"./packem_shared/authAuditHook-DPTKdjBe.mjs";import{R as c,I as S,a as _,L as v}from"./packem_shared/auth-do-DEd6xEKe.mjs";import{createAuth as H,resolveAuthOptions as I}from"./packem_shared/createAuth-BdVltKyf.mjs";import{authDiscoveryPaths as g,authDiscoveryPathsFor as P,handleAuthDiscoveryRequest as L}from"./packem_shared/authDiscoveryPaths-B14lM14u.mjs";import{authDoColumnAdditions as O,authDoSchemaStatements as w}from"./packem_shared/authDoColumnAdditions-UwF3Rl41.mjs";import{createDoAuthWiring as M}from"./packem_shared/createDoAuthWiring-B-c6qEcK.mjs";import{emailGateDatabaseHooks as F,withEmailGate as N}from"./packem_shared/emailGateDatabaseHooks-CAygR3iq.mjs";import{assertEmailAllowed as C,classifyEmail as G,emailGateMiddleware as B,loadEmailDomainLists as W}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as Y,handleAuthRequest as j}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import{createSignUpInvitation as J,listSignUpInvitations as K,pruneSignUpInvitations as Q,revokeSignUpInvitation as X}from"./packem_shared/createSignUpInvitation-DHZL9fNl.mjs";import{legacyIssuerCleanupStatements as $}from"./packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs";import{LunoraAuthHeadersError as et,withAuthPlugins as rt}from"./middleware.mjs";import{compileMigrationsSql as at,ensureMigrated as it}from"./packem_shared/compileMigrationsSql-BiYsLz91.mjs";import{default as ut}from"./schema.mjs";import{sessionPresets as nt,validateSessionPolicy as mt}from"./packem_shared/sessionPresets-C867Mlo4.mjs";import{c as lt,d as pt}from"./packem_shared/sql-store-uMubPYI9.mjs";import{createMemoryAuthStore as ft,matchesWhere as xt}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as Tt,verifyTurnstile as Dt}from"./turnstile.mjs";import{verifyTurnstileMiddleware as St}from"./turnstile-middleware.mjs";export{n as AUTH_AUDIT_TABLE,c as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,_ as AUTH_DO_SESSION_PATH,Y as DEFAULT_AUTH_BASE_PATH,s as LunoraAuthAdminError,v as LunoraAuthDO,et as LunoraAuthHeadersError,Tt as TURNSTILE_VERIFY_ENDPOINT,m as appendAuthAuditEntry,C as assertEmailAllowed,f as authAuditHook,g as authDiscoveryPaths,P as authDiscoveryPathsFor,O as authDoColumnAdditions,w as authDoSchemaStatements,ut as authTables,x as buildAuditEntry,G as classifyEmail,at as compileMigrationsSql,H as createAuth,u as createAuthAdmin,h as createAuthAuditReader,M as createDoAuthWiring,ft as createMemoryAuthStore,J as createSignUpInvitation,lt as createSqlAuthStore,pt as d1Executor,F as emailGateDatabaseHooks,B as emailGateMiddleware,l as ensureAuthAuditTable,it as ensureMigrated,E as eventForPath,L as handleAuthDiscoveryRequest,j as handleAuthRequest,$ as legacyIssuerCleanupStatements,K as listSignUpInvitations,W as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,xt as matchesWhere,Q as pruneSignUpInvitations,p as readAuthAuditLog,I as resolveAuthOptions,X as revokeSignUpInvitation,nt as sessionPresets,mt as validateSessionPolicy,Dt as verifyTurnstile,St as verifyTurnstileMiddleware,T as withAuthAudit,rt as withAuthPlugins,N as withEmailGate};
1
+ import{l as r,a as o,b as a}from"./packem_shared/adapter-CBZ17jKi.mjs";import{LunoraAuthAdminError as u,createAuthAdmin as s}from"./packem_shared/LunoraAuthAdminError-uhKG1MgQ.mjs";import{AUTH_AUDIT_TABLE as n,appendAuthAuditEntry as m,createAuthAuditReader as h,ensureAuthAuditTable as l,readAuthAuditLog as p}from"./audit.mjs";import{authAuditHook as f,buildAuditEntry as x,eventForPath as E,withAuthAudit as T}from"./packem_shared/authAuditHook-DPTKdjBe.mjs";import{R as c,I as S,a as _,L as v}from"./packem_shared/auth-do-Dr3ogic7.mjs";import{createAuth as H,resolveAuthOptions as I}from"./packem_shared/createAuth-B-Pq7nmN.mjs";import{authDiscoveryPaths as P,authDiscoveryPathsFor as L,handleAuthDiscoveryRequest as g}from"./packem_shared/authDiscoveryPaths-B14lM14u.mjs";import{authDoColumnAdditions as O,authDoSchemaStatements as w}from"./packem_shared/authDoColumnAdditions-UwF3Rl41.mjs";import{createDoAuthWiring as F}from"./packem_shared/createDoAuthWiring-BpLmcH71.mjs";import{emailGateDatabaseHooks as k,withEmailGate as q}from"./packem_shared/emailGateDatabaseHooks-BYLUpiN_.mjs";import{assertEmailAllowed as G,classifyEmail as M,emailGateMiddleware as B,loadEmailDomainLists as W}from"./email-guard.mjs";import{DEFAULT_AUTH_BASE_PATH as Y,handleAuthRequest as j}from"./packem_shared/DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import{createSignUpInvitation as J,listSignUpInvitations as K,pruneSignUpInvitations as Q,revokeSignUpInvitation as X}from"./packem_shared/createSignUpInvitation-DHZL9fNl.mjs";import{legacyIssuerCleanupStatements as $}from"./packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs";import{LunoraAuthHeadersError as et,withAuthPlugins as rt}from"./middleware.mjs";import{default as at}from"./schema.mjs";import{sessionPresets as ut,validateSessionPolicy as st}from"./packem_shared/sessionPresets-C867Mlo4.mjs";import{c as nt,d as mt}from"./packem_shared/sql-store-uMubPYI9.mjs";import{createMemoryAuthStore as lt,matchesWhere as pt}from"./store.mjs";import{TURNSTILE_VERIFY_ENDPOINT as ft,verifyTurnstile as xt}from"./turnstile.mjs";import{verifyTurnstileMiddleware as Tt}from"./turnstile-middleware.mjs";export{n as AUTH_AUDIT_TABLE,c as AUTH_DO_AUDIT_PATH,S as AUTH_DO_SECRET_HEADER,_ as AUTH_DO_SESSION_PATH,Y as DEFAULT_AUTH_BASE_PATH,u as LunoraAuthAdminError,v as LunoraAuthDO,et as LunoraAuthHeadersError,ft as TURNSTILE_VERIFY_ENDPOINT,m as appendAuthAuditEntry,G as assertEmailAllowed,f as authAuditHook,P as authDiscoveryPaths,L as authDiscoveryPathsFor,O as authDoColumnAdditions,w as authDoSchemaStatements,at as authTables,x as buildAuditEntry,M as classifyEmail,H as createAuth,s as createAuthAdmin,h as createAuthAuditReader,F as createDoAuthWiring,lt as createMemoryAuthStore,J as createSignUpInvitation,nt as createSqlAuthStore,mt as d1Executor,k as emailGateDatabaseHooks,B as emailGateMiddleware,l as ensureAuthAuditTable,E as eventForPath,g as handleAuthDiscoveryRequest,j as handleAuthRequest,$ as legacyIssuerCleanupStatements,K as listSignUpInvitations,W as loadEmailDomainLists,r as lunoraAuthAdapter,o as lunoraD1Adapter,a as lunoraDoAdapter,pt as matchesWhere,Q as pruneSignUpInvitations,p as readAuthAuditLog,I as resolveAuthOptions,X as revokeSignUpInvitation,ut as sessionPresets,st as validateSessionPolicy,xt as verifyTurnstile,Tt as verifyTurnstileMiddleware,T as withAuthAudit,rt as withAuthPlugins,q as withEmailGate};
@@ -0,0 +1,47 @@
1
+ import { a as LunoraAuthOptions, L as LunoraAuth } from "./packem_shared/create-auth.d-D1CKOFWc.mjs";
2
+ import 'better-auth';
3
+ /**
4
+ * Apply better-auth's required schema (`user`, `session`, `account`,
5
+ * `verification`) to the configured database. Idempotent — better-auth
6
+ * diffs the existing schema and only runs the missing DDL.
7
+ *
8
+ * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
9
+ * per table) doesn't fire on every request. In Cloudflare Workers the same
10
+ * `env.DB` binding is reused across invocations within a single isolate, so
11
+ * caching against the options object — which captures the binding — is
12
+ * sufficient.
13
+ *
14
+ * For production you should prefer pre-applying the schema at deploy time
15
+ * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
16
+ * dev/playground and small deployments.
17
+ */
18
+ declare const ensureMigrated: (auth: LunoraAuth | {
19
+ options: LunoraAuthOptions;
20
+ }) => Promise<void>;
21
+ /**
22
+ * Compile better-auth's migrations to a single SQL string. Useful for
23
+ * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
24
+ * before the first user request.
25
+ *
26
+ * Compiles from the SAME resolved options `createAuth` runs with (via
27
+ * `resolveAuthOptions`), not the raw caller options — so the schema includes the
28
+ * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
29
+ * from the raw options would omit it, and the running worker would then write to
30
+ * a table the migration never created.
31
+ *
32
+ * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
33
+ * reading the database, so it cannot know whether the `account.issuer` column better-auth
34
+ * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
35
+ * to make that moot. `ensureMigrated` handles it automatically; on this path, run
36
+ * `legacyIssuerCleanupStatements()` once against a database that still has the column.
37
+ * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
38
+ */
39
+ declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
40
+ /**
41
+ * The migration-only auth instance: the same validated, hardened options as `createAuth`, but
42
+ * through the full `better-auth` entry so `database` may be the raw D1 binding the Kysely
43
+ * migrator drives. Hand it to {@link ensureMigrated}; never serve requests from it. Lives here
44
+ * so the migrator's Kysely edge stays out of the runtime bundle.
45
+ */
46
+ declare const createMigrationAuth: (options: LunoraAuthOptions) => LunoraAuth;
47
+ export { compileMigrationsSql, createMigrationAuth, ensureMigrated };
@@ -0,0 +1,47 @@
1
+ import { a as LunoraAuthOptions, L as LunoraAuth } from "./packem_shared/create-auth.d-D1CKOFWc.js";
2
+ import 'better-auth';
3
+ /**
4
+ * Apply better-auth's required schema (`user`, `session`, `account`,
5
+ * `verification`) to the configured database. Idempotent — better-auth
6
+ * diffs the existing schema and only runs the missing DDL.
7
+ *
8
+ * Cached per `options` reference so the diff cost (one PRAGMA-style sweep
9
+ * per table) doesn't fire on every request. In Cloudflare Workers the same
10
+ * `env.DB` binding is reused across invocations within a single isolate, so
11
+ * caching against the options object — which captures the binding — is
12
+ * sufficient.
13
+ *
14
+ * For production you should prefer pre-applying the schema at deploy time
15
+ * via `compileMigrationsSql` + `wrangler d1 execute`; this helper exists for
16
+ * dev/playground and small deployments.
17
+ */
18
+ declare const ensureMigrated: (auth: LunoraAuth | {
19
+ options: LunoraAuthOptions;
20
+ }) => Promise<void>;
21
+ /**
22
+ * Compile better-auth's migrations to a single SQL string. Useful for
23
+ * `wrangler d1 execute --file -` in CI so the deploy step applies the schema
24
+ * before the first user request.
25
+ *
26
+ * Compiles from the SAME resolved options `createAuth` runs with (via
27
+ * `resolveAuthOptions`), not the raw caller options — so the schema includes the
28
+ * `rateLimit` table the worker's default-on durable limiter writes to. Compiling
29
+ * from the raw options would omit it, and the running worker would then write to
30
+ * a table the migration never created.
31
+ *
32
+ * **Upgrading a database created by an older `@lunora/auth`.** This compiles DDL without
33
+ * reading the database, so it cannot know whether the `account.issuer` column better-auth
34
+ * 1.7.0 required and 1.7.3 reverted is present — and SQLite has no `DROP COLUMN IF EXISTS`
35
+ * to make that moot. `ensureMigrated` handles it automatically; on this path, run
36
+ * `legacyIssuerCleanupStatements()` once against a database that still has the column.
37
+ * Leaving it in place fails every sign-up with `NOT NULL constraint failed: account.issuer`.
38
+ */
39
+ declare const compileMigrationsSql: (options: LunoraAuthOptions) => Promise<string>;
40
+ /**
41
+ * The migration-only auth instance: the same validated, hardened options as `createAuth`, but
42
+ * through the full `better-auth` entry so `database` may be the raw D1 binding the Kysely
43
+ * migrator drives. Hand it to {@link ensureMigrated}; never serve requests from it. Lives here
44
+ * so the migrator's Kysely edge stays out of the runtime bundle.
45
+ */
46
+ declare const createMigrationAuth: (options: LunoraAuthOptions) => LunoraAuth;
47
+ export { compileMigrationsSql, createMigrationAuth, ensureMigrated };
@@ -0,0 +1 @@
1
+ import{getAuthTablesWithResolvedIndexes as w,invalidateSchemaChecks as y}from"@better-auth/core/db/internal";import{LunoraError as R}from"@lunora/errors";import{betterAuth as _}from"better-auth";import{getMigrations as l}from"better-auth/db/migration";import{q as c}from"./packem_shared/quote-identifier-CGiYFBvY.mjs";import{resolveAuthOptions as A,prepareAuthOptions as M}from"./packem_shared/createAuth-B-Pq7nmN.mjs";import{schemaDeclaresIssuer as x,legacyIssuerCleanupStatements as T,indexesReferencingIssuer as N}from"./packem_shared/legacyIssuerCleanupStatements-D9Kf8OD1.mjs";const C=/pragma_index_list\s*\(/iu,D=/sqlite_master/iu,S=/^["`[]|["\]`]$/gu,O=/\s+/u,P=/create\s+unique\s+index/iu,L=/\bwhere\b/iu,U=t=>C.test(t)&&D.test(t),q=t=>(t.trim().split(O)[0]??"").replaceAll(S,"").trim(),v=t=>{const e=t.indexOf("(");if(e===-1)return{columns:[],tail:""};let s=0;for(let n=e;n<t.length;n+=1){const a=t[n];if(a==="(")s+=1;else if(a===")"&&(s-=1,s===0))return{columns:t.slice(e+1,n).split(",").map(o=>q(o)).filter(o=>o!==""),tail:t.slice(n+1)}}return{columns:[],tail:""}},W=async t=>{const e=t.prepare("SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index'"),{results:s}=await e.all(),n=[];for(const a of s??[]){const o=a.name,r=a.tbl_name,i=a.sql;if(typeof o!="string"||typeof r!="string")continue;if(typeof i!="string"){n.push({columnPosition:0,indexName:o,isPartial:0,isUnique:1,tableName:r});continue}const d=P.test(i)?1:0,{columns:h,tail:b}=v(i),E=L.test(b)?1:0;for(const[g,I]of h.entries())n.push({columnName:I,columnPosition:g,indexName:o,isPartial:E,isUnique:d,tableName:r})}return n},F=t=>{const e=async()=>{const n=await W(t);return{meta:{},results:n,success:!0}},s={all:e,bind:()=>s,run:e};return s},H=t=>new Proxy(t,{get(e,s,n){if(s==="prepare")return o=>U(o)?F(e):e.prepare(o);const a=Reflect.get(e,s,n);return typeof a=="function"?a.bind(e):a}}),m=t=>typeof t=="object"&&t!==null&&typeof t.prepare=="function"&&typeof t.batch=="function",p=t=>{const{database:e}=t;if(!(e&&typeof e!="function"))throw new R("AUTH_MIGRATOR_UNSUPPORTED",e?"@lunora/auth: this auth instance's `database` is a custom adapter, which better-auth's migrator cannot drive.":"@lunora/auth: this auth instance has no `database`, so better-auth's migrator has nothing to introspect.")},f=t=>m(t.database)?{...t,database:H(t.database)}:t,$=async(t,e)=>{const s=c("t");try{return await t.prepare(`SELECT ${s}.${c("issuer")} FROM ${c(e)} AS ${s} LIMIT 0`).run(),!0}catch{return!1}},Q=async t=>{const{database:e}=t,{tables:s}=w(t),{account:n}=s;if(n!==void 0&&!x(Object.entries(n.fields).map(([a,o])=>o.fieldName??a))&&m(e)&&await $(e,n.modelName))try{const{results:a}=await e.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?").bind(n.modelName).all();await e.batch(T(n.modelName,N(a)).map(o=>e.prepare(o)))}catch(a){console.error("@lunora/auth: could not drop the reverted `account.issuer` column; if it is still present, sign-ups will fail until it is removed.",a)}},j=t=>{const{database:e}=t;typeof e=="object"&&e!==null&&y(e)},u=new WeakMap,J=async t=>{const{options:e}=t;p(e);const s=u.get(e);if(s){await s;return}const n=(async()=>{const{runMigrations:a}=await l(f(e));await a(),await Q(e),j(e)})();u.set(e,n);try{await n}catch(a){throw u.delete(e),a}},V=async t=>{const e=A(t);p(e);const{compileMigrations:s}=await l(f(e));return s()},Z=t=>_(M(t));export{V as compileMigrationsSql,Z as createMigrationAuth,J as ensureMigrated};
@@ -1 +1 @@
1
- import{I as R,L as _,R as S,a as T}from"./auth-do-DEd6xEKe.mjs";import"./adapter-CBZ17jKi.mjs";import"../audit.mjs";import"./createAuth-BdVltKyf.mjs";import"./authDiscoveryPaths-B14lM14u.mjs";import"./authDoColumnAdditions-UwF3Rl41.mjs";import"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";export{R as INTERNAL_SECRET_HEADER,_ as LunoraAuthDO,S as READ_AUDIT_PATH,T as RESOLVE_SESSION_PATH};
1
+ import{I as R,L as _,R as S,a as T}from"./auth-do-Dr3ogic7.mjs";import"./adapter-CBZ17jKi.mjs";import"../audit.mjs";import"./createAuth-B-Pq7nmN.mjs";import"./authDiscoveryPaths-B14lM14u.mjs";import"./authDoColumnAdditions-UwF3Rl41.mjs";import"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";export{R as INTERNAL_SECRET_HEADER,_ as LunoraAuthDO,S as READ_AUDIT_PATH,T as RESOLVE_SESSION_PATH};
@@ -1 +1 @@
1
- import{getAuthTablesWithResolvedIndexes as F}from"@better-auth/core/db/internal";import{LunoraError as b,toErrorBody as re}from"@lunora/errors";import{d as P,b as ne}from"./adapter-CBZ17jKi.mjs";import{AUTH_AUDIT_TABLE as oe,ensureAuthAuditTable as se,createAuthAuditReader as ie}from"../audit.mjs";import{resolveAuthOptions as ae,createAuth as ce}from"./createAuth-BdVltKyf.mjs";import{handleAuthDiscoveryRequest as ue}from"./authDiscoveryPaths-B14lM14u.mjs";import{authDoSchemaStatements as fe,authDoColumnAdditions as le}from"./authDoColumnAdditions-UwF3Rl41.mjs";import{handleAuthRequest as me}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import{q as c}from"./quote-identifier-CGiYFBvY.mjs";import{schemaDeclaresIssuer as de,legacyIssuerCleanupStatements as pe,indexesReferencingIssuer as Ee}from"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";const he=(t,e)=>{const r=Math.max(t.length,e.length);let n=t.length^e.length;for(let s=0;s<r;s+=1){const o=s<t.length?t.charCodeAt(s):0,i=s<e.length?e.charCodeAt(s):0;n|=o^i}return n===0},W=2166136261,V=16777619,H=(t,e=W)=>{let r=e;for(let n=0;n<t.length;n+=1)r^=t.charCodeAt(n),r=Math.imul(r,V);return(r>>>0).toString(16).padStart(8,"0")},we=t=>`${H(t,W)}${H(t,V)}`,$=t=>{let e="";for(let n=0;n<t.length;n+=32768)e+=String.fromCharCode(...t.subarray(n,n+32768));return btoa(e)},ye=t=>{const e=atob(t),r=new Uint8Array(e.length);for(let n=0;n<e.length;n+=1)r[n]=e.codePointAt(n)??0;return r},d="$lunora.wire$",j=64,U=1024,M="__proto__",B={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},q={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},Te=t=>{if(t===null||typeof t!="object")return!1;const e=Object.getPrototypeOf(t);return e===null||e===Object.prototype},T=(t,e=0)=>{if(e>j)throw new RangeError(`wire-codec: value nesting exceeds the ${j}-level limit`);if(t===void 0)return[d,"undefined"];if(t===null)return null;const r=typeof t;if(r==="bigint"){const o=t.toString();if(o.length>U)throw new RangeError(`wire-codec: over-long bigint (max ${U} digits)`);return[d,"bigint",o]}if(r==="number"){const o=t;return Number.isNaN(o)?[d,"nan"]:o===1/0?[d,"inf"]:o===-1/0?[d,"-inf"]:o}if(r!=="object")return t;if(t instanceof Date)return[d,"date",T(t.getTime(),e+1)];if(t instanceof Error){const o=t,i=T(Object.fromEntries(Object.keys(o).map(u=>[u,o[u]])),e+1),a=[d,"error",String(o.name),String(o.message),i];return o.cause!==void 0&&a.push(T(o.cause,e+1)),a}if(t instanceof URL)return[d,"url",t.href];if(t instanceof Map)return[d,"map",[...t.entries()].map(([o,i])=>[T(o,e+1),T(i,e+1)])];if(t instanceof Set)return[d,"set",[...t].map(o=>T(o,e+1))];if(t instanceof ArrayBuffer)return[d,"bytes",$(new Uint8Array(t)),"ArrayBuffer"];if(ArrayBuffer.isView(t)){const o=t,i=o.constructor.name,a=new Uint8Array(o.buffer,o.byteOffset,o.byteLength);return i==="Uint8Array"?[d,"bytes",$(a)]:[d,"bytes",$(a),i]}if(Array.isArray(t)){const o=t.map(i=>T(i,e+1));return o.length>0&&o[0]===d?[d,"arr",o]:o}if(!Te(t)){const o=t.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${o} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const n=t,s={};for(const o of Object.keys(n)){const i=n[o];if(i===void 0)continue;const a=T(i,e+1);o===M?Object.defineProperty(s,o,{configurable:!0,enumerable:!0,value:a,writable:!0}):s[o]=a}return s},E=(t,e=0)=>{if(e>j)throw new RangeError(`wire-codec: value nesting exceeds the ${j}-level limit`);if(t===null||typeof t!="object")return t;if(Array.isArray(t)){if(t[0]===d)switch(t[1]){case"-inf":return-1/0;case"arr":return t[2].map(o=>E(o,e+1));case"bigint":{const o=t[2];if(typeof o!="string"||o.length>U||!/^-?\d+$/.test(o))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${U} digits)`);return BigInt(o)}case"date":{const o=E(t[2],e+1);if(typeof o!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(o)}case"map":{const o=t[2];return new Map(o.map(i=>{if(!Array.isArray(i)||i.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[E(i[0],e+1),E(i[1],e+1)]}))}case"set":return new Set(t[2].map(o=>E(o,e+1)));case"url":{const o=t[2];if(typeof o!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(o)}case"error":{const o=t[2],i=t[3];if(typeof o!="string"||typeof i!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const a=(Object.hasOwn(q,o)?q[o]:void 0)??Error,u=new a(i);u.name!==o&&Object.defineProperty(u,"name",{configurable:!0,value:o,writable:!0});const f=E(t[4],e+1);if(f===null||typeof f!="object"||Array.isArray(f))throw new TypeError("wire-codec: malformed error — props must be an object");for(const m of Object.keys(f))m===M?Object.defineProperty(u,m,{configurable:!0,enumerable:!0,value:f[m],writable:!0}):u[m]=f[m];return t.length>5&&Object.defineProperty(u,"cause",{configurable:!0,value:E(t[5],e+1),writable:!0}),u}case"bytes":{const o=t[2];if(typeof o!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const i=ye(o);if($(i)!==o)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const a=t[3]??"Uint8Array";if(a==="ArrayBuffer")return i.buffer.byteLength===i.byteLength?i.buffer:i.slice().buffer;const u=Object.hasOwn(B,a)?B[a]:void 0;return u?new u(i.slice().buffer):i}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return t.map(o=>E(o,e+1))}return t.map(s=>E(s,e+1))}const r=t,n={};for(const s of Object.keys(r)){const o=E(r[s],e+1);s===M?Object.defineProperty(n,s,{configurable:!0,enumerable:!0,value:o,writable:!0}):n[s]=o}return n},ge="/__lunora/auth/move",g="__lunora_auth_move__",S="__lunora_auth_move_rows__",D="__lunora_auth_purged__",G=100,Ae=16,be=100,Se=2n**64n,X=t=>t.startsWith("sqlite_")||t.startsWith("_cf_")||t===g||t===S||t===D,Y=async(t,e)=>t.transaction(async()=>Promise.resolve().then(e)),h=(t,e,...r)=>[...t.sql.exec(e,...r)],l=(t,e,...r)=>{[...t.sql.exec(e,...r)]},N=t=>h(t,"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").map(e=>String(e.name)).filter(e=>!X(e)),J=(t,e)=>h(t,"SELECT name, pk FROM pragma_table_info(?)",e).map(r=>({name:String(r.name),pk:Number(r.pk)})),L=(t,e)=>J(t,e).map(({name:r})=>r),K=(t,e)=>{const r=J(t,e),n=r.filter(({pk:s})=>s>0).toSorted((s,o)=>s.pk-o.pk).map(({name:s})=>s);return n.length>0?n:r.map(({name:s})=>s)},Q=(t,e)=>Number(h(t,`SELECT count(*) AS n FROM ${c(e)}`)[0]?.n??0),z=t=>JSON.stringify(T(t)),C=(t,e)=>BigInt(`0x${we(z(e.map(r=>t[r]??null)))}`),Z=(t,e)=>{let r=0,n=0n;for(const s of t)r+=1,n=(n+e(s))%Se;return`${String(r)}:${n.toString(16)}`},ee=(t,e)=>{const r=L(t,e).toSorted((n,s)=>n.localeCompare(s));return Z(t.sql.exec(`SELECT * FROM ${c(e)}`),n=>C(n,r))},Re=t=>N(t).map(e=>{const r=h(t,"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",e)[0]?.sql,n=h(t,"SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL",e).map(s=>({name:String(s.name),sql:String(s.sql)}));return{columns:L(t,e),indexes:n,name:e,rows:Q(t,e),sql:typeof r=="string"?r:""}}),v=(t,e)=>{if(typeof e!="string"||!N(t).includes(e))throw new TypeError(`unknown table ${JSON.stringify(e)}`);return e},Oe=(t,e,r)=>{const n=h(t,`SELECT rowid AS "__lunora_rowid__", * FROM ${c(e)} WHERE rowid > ? ORDER BY rowid LIMIT ?`,r,G),s=n.at(-1)?.__lunora_rowid__;return{last:typeof s=="number"?s:void 0,rows:n.map(({__lunora_rowid__:o,...i})=>i)}},Ne=t=>L(t,g).length>0,_=t=>Ne(t)?Object.fromEntries(h(t,`SELECT tbl, after, done, pass, cols, watermark FROM ${c(g)}`).map(e=>[String(e.tbl),{after:Number(e.after),columns:JSON.parse(String(e.cols)),done:e.done===1,pass:Number(e.pass),watermark:Number(e.watermark)}])):{},_e=(t,e,r)=>Z(t.sql.exec(`SELECT h FROM ${c(S)} WHERE tbl = ? AND pass = ?`,e,r),n=>BigInt(`0x${String(n.h)}`)),Le=t=>{l(t,`CREATE TABLE IF NOT EXISTS ${c(g)} (tbl TEXT NOT NULL PRIMARY KEY, after INTEGER NOT NULL, done INTEGER NOT NULL, pass INTEGER NOT NULL, cols TEXT NOT NULL, watermark INTEGER NOT NULL DEFAULT 0)`),l(t,`CREATE TABLE IF NOT EXISTS ${c(S)} (tbl TEXT NOT NULL, k TEXT NOT NULL, h TEXT NOT NULL, pass INTEGER NOT NULL, PRIMARY KEY (tbl, k))`)},Ie=(t,e,r)=>{if(!N(t).includes(e))return 0;const n=K(t,e).map(s=>`u.${c(s)}`).join(", ");return Number(h(t,`SELECT count(*) AS n FROM ${c(e)} u WHERE u.rowid > ? AND NOT EXISTS (SELECT 1 FROM ${c(S)} c WHERE c.tbl = ? AND c.k = json_array(${n}))`,r,e)[0]?.n??0)},$e=/^CREATE TABLE /iu,je=/^CREATE (?:UNIQUE )?INDEX /iu,Ue=(t,e,r)=>{if(X(e.name))throw new TypeError(`refusing reserved table ${JSON.stringify(e.name)}`);if(!$e.test(e.sql)||e.indexes.some(({sql:s})=>!je.test(s)))throw new TypeError(`refusing unexpected DDL for ${JSON.stringify(e.name)}`);if(r.has(e.name)){const s=new Set(L(t,e.name));for(const o of e.columns.filter(i=>!s.has(i)))l(t,`ALTER TABLE ${c(e.name)} ADD COLUMN ${c(o)}`)}else l(t,e.sql);const n=new Set(h(t,"SELECT name FROM sqlite_master WHERE type = 'index'").map(s=>String(s.name)));for(const s of e.indexes.filter(({name:o})=>!n.has(o)))l(t,s.sql)},De=(t,e,r)=>{Le(t);const n=Ie(t,r.userTable,_(t)[r.userTable]?.watermark??0);if(n>0&&!r.force)throw new b("AUTH_MOVE_TARGET_NOT_EMPTY",`the pinned auth object has ${String(n)} user(s) the copy did not write; copying into it would merge two user bases — pass force to copy anyway`,{data:{users:n}});const s=new Set(N(t)),o=_(t);for(const a of e){Ue(t,a,s);const u=o[a.name],f=JSON.stringify(a.columns.toSorted((m,w)=>m.localeCompare(w)));u===void 0?l(t,`INSERT INTO ${c(g)} (tbl, after, done, pass, cols) VALUES (?, 0, 0, 1, ?)`,a.name,f):l(t,`UPDATE ${c(g)} SET cols = ? WHERE tbl = ?`,f,a.name)}l(t,`UPDATE ${c(g)} SET watermark = (SELECT coalesce(max(rowid), 0) FROM ${c(r.userTable)}) WHERE tbl = ?`,r.userTable);const i=_(t);return{cursors:Object.fromEntries(e.map(({name:a})=>[a,i[a]?.after??0])),done:Object.fromEntries(e.map(({name:a})=>[a,i[a]?.done??!1])),targetRows:Object.fromEntries(e.map(({name:a})=>[a,Q(t,a)]))}},te=(t,e,r,n,s)=>h(t,`SELECT ${s.map(o=>c(o)).join(", ")} FROM ${c(e)} WHERE ${r.map(o=>`${c(o)} IS ?`).join(" AND ")} LIMIT 1`,...n)[0],k=(t,e)=>{if(!e)throw new b("AUTH_MOVE_CONFLICT",`a row copied into ${t} collides with a row the pinned auth object holds on its own — nothing from this page was written; pass force to keep the pinned object's rows`,{data:{table:t}});return"conflicts"},ke=t=>t instanceof Error&&t.message.includes("UNIQUE constraint failed"),Ce=(t,e)=>{if(e.prior!==void 0)return k(e.table,e.force);try{l(t,`INSERT INTO ${c(e.table)} (${e.columns.map(r=>c(r)).join(", ")}) VALUES (${e.columns.map(()=>"?").join(", ")})`,...e.columns.map(r=>e.row[r]))}catch(r){if(!ke(r))throw r;return k(e.table,e.force)}return"copied"},Me=(t,e,r)=>{const n=C(r,e.columns).toString(16);return n===e.h?"unchanged":e.prior===void 0||n!==e.prior?k(e.table,e.force):(l(t,`UPDATE ${c(e.table)} SET ${e.columns.map(s=>`${c(s)} = ?`).join(", ")} WHERE ${e.keys.map(s=>`${c(s)} IS ?`).join(" AND ")}`,...e.columns.map(s=>e.row[s]),...e.key),"updated")},xe=(t,e,r,n)=>{const{force:s,keys:o,pass:i}=n,a=Object.keys(r).toSorted((A,I)=>A.localeCompare(I));if(a.length+o.length>be)throw new TypeError(`${e} has too many columns to copy`);const u=o.map(A=>r[A]??null),f=z(u),m=C(r,a).toString(16),w=h(t,`SELECT h FROM ${c(S)} WHERE tbl = ? AND k = ?`,e,f)[0]?.h,p={columns:a,force:s,h:m,key:u,keys:o,prior:typeof w=="string"?w:void 0,row:r,table:e},y=p.prior===m?void 0:te(t,e,o,u,a);let R="unchanged";return p.prior!==m&&(R=y===void 0?Ce(t,p):Me(t,p,y)),l(t,`INSERT INTO ${c(S)} (tbl, k, h, pass) VALUES (?, ?, ?, ?) ON CONFLICT (tbl, k) DO UPDATE SET h = excluded.h, pass = excluded.pass`,e,f,m,i),R},Fe=(t,e,r)=>{const{counts:n,force:s,keys:o,marker:i}=r,a=h(t,`SELECT k, h FROM ${c(S)} WHERE tbl = ? AND pass < ?`,e,i.pass);for(const u of a){const f=E(JSON.parse(String(u.k))),m=te(t,e,o,f,i.columns);m!==void 0&&(C(m,i.columns).toString(16)===String(u.h)?(l(t,`DELETE FROM ${c(e)} WHERE ${o.map(w=>`${c(w)} IS ?`).join(" AND ")}`,...f),n.deleted+=1):n[k(e,s)]+=1),l(t,`DELETE FROM ${c(S)} WHERE tbl = ? AND k = ?`,e,String(u.k))}},Pe=async(t,e,r)=>{const n=_(t)[e];if(n===void 0)throw new TypeError(`table ${JSON.stringify(e)} was not part of this move`);return Y(t,()=>{const s={conflicts:0,copied:0,deleted:0,unchanged:0,updated:0},o=K(t,e);for(const i of r.rows)s[xe(t,e,i,{force:r.force,keys:o,pass:n.pass})]+=1;return r.final&&Fe(t,e,{counts:s,force:r.force,keys:o,marker:n}),l(t,`UPDATE ${c(g)} SET after = ?, done = ? WHERE tbl = ?`,r.last,r.final?1:0,e),s})},He=async(t,e)=>{const r=N(t);return await Y(t,()=>{const n=r.filter(o=>e[o]===void 0);if(n.length>0)throw new b("AUTH_MOVE_INCOMPLETE",`refusing to purge the un-pinned auth object: ${n.join(", ")} not fully copied — run the copy to done: true first`,{data:{unfinished:n}});const s=r.filter(o=>ee(t,o)!==e[o]);if(s.length>0)throw new b("AUTH_MOVE_SOURCE_CHANGED",`refusing to purge the un-pinned auth object: ${s.join(", ")} changed since it was copied — run the copy again to carry the changes, then purge`,{data:{changed:s}});for(const o of r)l(t,`DROP TABLE ${c(o)}`);l(t,`CREATE TABLE IF NOT EXISTS ${c(D)} (at INTEGER NOT NULL)`),l(t,`INSERT INTO ${c(D)} (at) VALUES (?)`,Date.now())}),{dropped:r}},Be=t=>{if(L(t,D).length>0)throw new b("AUTH_MOVE_SOURCE_PURGED","the un-pinned auth object was purged after its tables were copied; anything in it now is new, and copying it would delete the pinned object's users")},qe=new Set(["fingerprints","manifest","page","purge"]),ve=(t,e,r)=>{e.prepare();const n=_(t);return{copied:r?Object.fromEntries(Object.entries(n).map(([s,o])=>[s,_e(t,s,o.pass)])):{},markers:n,order:e.order()}},We=(t,e)=>{for(const r of e)l(t,`UPDATE ${c(g)} SET after = 0, done = 0, pass = pass + 1 WHERE tbl = ?`,r);return{}},Ve=[/(?:UNIQUE|NOT NULL|CHECK|FOREIGN KEY|PRIMARY KEY) constraint failed/u,/no such (?:table|column)/u,/datatype mismatch/u,/SQLITE_[A-Z_]+/u],Ge=t=>{const e=t instanceof Error?t.message:String(t);for(const r of Ve){const n=r.exec(e);if(n)return n[0]}return t instanceof Error?t.name:"unknown"},Xe=async(t,e,r)=>{switch(qe.has(String(e.op))&&Be(t),e.op){case"begin":return r.prepare(),De(t,e.tables,{force:e.force===!0,userTable:r.userTable()});case"fingerprints":return{fingerprints:Object.fromEntries(N(t).map(n=>[n,ee(t,n)]))};case"manifest":return{tables:Re(t)};case"page":{const n=Oe(t,v(t,e.table),Number(e.after??0));return{last:n.last,rows:T(n.rows)}}case"purge":{const n=await He(t,e.expected??{});return r.onPurge(),n}case"rescan":return We(t,e.tables??[]);case"status":return ve(t,r,e.fingerprints===!0);case"write":return Pe(t,v(t,e.table),{final:e.final===!0,force:e.force===!0,last:Number(e.last),rows:E(e.rows)});default:throw new TypeError(`unknown move op ${JSON.stringify(e.op)}`)}},Ye=async(t,e,r)=>{try{return await Xe(t,e,r)}catch(n){if(n instanceof b)throw n;console.error("@lunora/auth: auth move step failed",n);const s=String(e.op),o=typeof e.table=="string"?e.table:void 0,i=Ge(n);throw new b("AUTH_MOVE_FAILED",`auth move ${s}${o===void 0?"":` on ${o}`} failed: ${i}`,{data:{cause:i,op:s,...o===void 0?{}:{table:o}}})}},Je=(t,e)=>{const r=n=>{const s=e.first.indexOf(n);if(s!==-1)return s;const o=e.last.indexOf(n);return o===-1?e.first.length:e.first.length+1+o};return t.toSorted((n,s)=>r(n.name)-r(s.name)||n.name.localeCompare(s.name))},Ke=(t,e)=>({conflicts:0,copied:0,deleted:0,sourceRows:t.rows,table:t.name,targetRows:e,unchanged:0,updated:0}),lt=t=>{const e=async(r,n)=>{const s=await t(r,n);if(!s.ok){let o={};try{const i=await s.json();typeof i.error=="object"&&i.error!==null&&(o=i.error)}catch{}throw new b(o.code??"AUTH_MOVE_FAILED",o.message??`auth move ${String(n.op)} on the ${r} object failed (${String(s.status)})`,{data:o.data,status:s.status})}return s.json()};return{copy:async(r={})=>{const n=r.force===!0,s=await e("target",{op:"status"}),{tables:o}=await e("source",{op:"manifest"}),i=await e("target",{force:n,op:"begin",tables:o}),a=[];let u=0;for(const p of Je(o,s.order)){const y=Ke(p,i.targetRows[p.name]??0);let R=i.cursors[p.name]??0;for(a.push(y);;){if(u>=Ae)return{done:!1,tables:a};u+=1;const A=await e("source",{after:R,op:"page",table:p.name}),I=E(A.rows),x=I.length<G;if(I.length===0&&i.done[p.name]===!0)break;const O=await e("target",{final:x,force:n,last:A.last??R,op:"write",rows:A.rows,table:p.name});if(y.conflicts+=O.conflicts,y.copied+=O.copied,y.deleted+=O.deleted,y.unchanged+=O.unchanged,y.updated+=O.updated,y.targetRows+=O.copied-O.deleted,R=A.last??R,x)break}}const{fingerprints:f}=await e("source",{op:"fingerprints"}),m=await e("target",{fingerprints:!0,op:"status"}),w=Object.keys(f).filter(p=>m.copied[p]!==f[p]);return w.length>0&&await e("target",{op:"rescan",tables:w}),{done:w.length===0,tables:a}},purge:async()=>{const{copied:r,markers:n}=await e("target",{fingerprints:!0,op:"status"}),s=Object.fromEntries(Object.entries(n).filter(([,o])=>o.done).map(([o])=>[o,r[o]]));return e("source",{expected:s,op:"purge"})}}},Qe="/__lunora/auth/session",ze="/__lunora/auth/audit",Ze="x-lunora-auth-do-secret",et=t=>{if(t===null||typeof t!="object"||Array.isArray(t))return{error:"body must be a JSON object"};const e={};for(const[r,n]of Object.entries(t))if(r==="limit"||r==="sinceSeq"){if(typeof n!="number"||!Number.isFinite(n))return{error:`"${r}" must be a finite number`};e[r]=n}else if(r==="actorId"||r==="event"){if(typeof n!="string")return{error:`"${r}" must be a string`};e[r]=n}else return{error:`unknown audit read option "${r}"`};return{options:e}};class mt{#r;#a;#c;#e;#t;#n=!1;#o={first:["user"],last:[]};constructor(e,r,n={}){this.#e=e.storage,this.#r=P(e.storage),this.#c=r,this.#a=n}#s(){if(this.#t!==void 0)return this.#t;const e=this.#c(),r=ae(e),{tables:n}=F(r);if(this.#o={first:[n.user?.modelName??"user",n.account?.modelName??"account",n.session?.modelName??"session"],last:[oe,n.rateLimit?.modelName??"rateLimit"]},!this.#n){for(const s of fe(r))[...this.#e.sql.exec(s)];for(const s of le(r,o=>this.#u(o)))[...this.#e.sql.exec(s)];this.#f(r),this.#n=!0}return this.#t=ce({...e,database:ne(this.#e)}),this.#t}#u(e){return[...this.#e.sql.exec("SELECT name FROM pragma_table_info(?)",e)].map(n=>String(n.name))}#f(e){const{tables:r}=F(e),{account:n}=r;if(n===void 0)return;const s=Object.entries(n.fields).map(([o,i])=>i.fieldName??o);if(!(de(s)||!this.#u(n.modelName).includes("issuer")))try{const o=[...this.#e.sql.exec("SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?",n.modelName)].map(i=>({name:String(i.name),sql:typeof i.sql=="string"?i.sql:void 0}));for(const i of pe(n.modelName,Ee(o)))[...this.#e.sql.exec(i)]}catch(o){console.error("@lunora/auth: could not drop the reverted `account.issuer` column; if it is still present, sign-ups will fail until it is removed.",o)}}async#l(e){if(!this.#i(e))return Response.json({error:"unauthorized"},{status:401});let r;try{r=await e.json()}catch{return Response.json({error:"invalid body"},{status:400})}const n=et(r??{});if("error"in n)return Response.json({error:n.error},{status:400});await se(this.#r);const s=await ie(this.#r).read(n.options);return Response.json({entries:s})}async#m(e){if(e.method!=="POST"||!this.#i(e))return Response.json({error:"unauthorized"},{status:401});try{const r=await e.json(),n=await Ye(this.#e,r,{onPurge:()=>{this.#n=!1,this.#t=void 0,this.#r=P(this.#e)},order:()=>this.#o,prepare:()=>{this.#s()},userTable:()=>this.#o.first[0]??"user"});return Response.json(n)}catch(r){const{body:n,redacted:s,status:o}=re(r,{encodeData:i=>i,fallbackCode:"AUTH_MOVE_FAILED",redactedMessage:"auth move failed"});return s&&console.error("@lunora/auth: auth move failed",r),Response.json({error:n},{status:o})}}#i(e){const{internalSecret:r}=this.#a;if(r===void 0||r==="")return!1;const n=e.headers.get(Ze);return n!==null&&he(n,r)}async#d(e){if(!this.#i(e))return Response.json({error:"unauthorized"},{status:401});const n=await this.#s().api.getSession({headers:e.headers}),s=n?.user.id;if(s===void 0)return Response.json({});const o=n?.session.expiresAt,i=n?.user,a=i?.role;return Response.json({...typeof i?.email=="string"&&i.email.length>0?{email:i.email}:{},...o instanceof Date?{expiresAtMs:o.getTime()}:{},...typeof i?.name=="string"&&i.name.length>0?{name:i.name}:{},...typeof a=="string"&&a.length>0?{role:a}:{},userId:s})}async fetch(e){const r=new URL(e.url);if(r.pathname===Qe)return this.#d(e);if(r.pathname===ze)return this.#l(e);if(r.pathname===ge)return this.#m(e);const n=this.#s();return await me(n,e)??await ue(n,e)??Response.json({error:"not an auth route"},{status:404})}}export{Ze as I,mt as L,ge as M,ze as R,Qe as a,lt as c};
1
+ import{getAuthTablesWithResolvedIndexes as F}from"@better-auth/core/db/internal";import{LunoraError as b,toErrorBody as re}from"@lunora/errors";import{d as P,b as ne}from"./adapter-CBZ17jKi.mjs";import{AUTH_AUDIT_TABLE as oe,ensureAuthAuditTable as se,createAuthAuditReader as ie}from"../audit.mjs";import{resolveAuthOptions as ae,createAuth as ce}from"./createAuth-B-Pq7nmN.mjs";import{handleAuthDiscoveryRequest as ue}from"./authDiscoveryPaths-B14lM14u.mjs";import{authDoSchemaStatements as fe,authDoColumnAdditions as le}from"./authDoColumnAdditions-UwF3Rl41.mjs";import{handleAuthRequest as me}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";import{q as c}from"./quote-identifier-CGiYFBvY.mjs";import{schemaDeclaresIssuer as de,legacyIssuerCleanupStatements as pe,indexesReferencingIssuer as Ee}from"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";const he=(t,e)=>{const r=Math.max(t.length,e.length);let n=t.length^e.length;for(let s=0;s<r;s+=1){const o=s<t.length?t.charCodeAt(s):0,i=s<e.length?e.charCodeAt(s):0;n|=o^i}return n===0},W=2166136261,V=16777619,H=(t,e=W)=>{let r=e;for(let n=0;n<t.length;n+=1)r^=t.charCodeAt(n),r=Math.imul(r,V);return(r>>>0).toString(16).padStart(8,"0")},we=t=>`${H(t,W)}${H(t,V)}`,$=t=>{let e="";for(let n=0;n<t.length;n+=32768)e+=String.fromCharCode(...t.subarray(n,n+32768));return btoa(e)},ye=t=>{const e=atob(t),r=new Uint8Array(e.length);for(let n=0;n<e.length;n+=1)r[n]=e.codePointAt(n)??0;return r},d="$lunora.wire$",j=64,U=1024,M="__proto__",B={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},q={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},Te=t=>{if(t===null||typeof t!="object")return!1;const e=Object.getPrototypeOf(t);return e===null||e===Object.prototype},T=(t,e=0)=>{if(e>j)throw new RangeError(`wire-codec: value nesting exceeds the ${j}-level limit`);if(t===void 0)return[d,"undefined"];if(t===null)return null;const r=typeof t;if(r==="bigint"){const o=t.toString();if(o.length>U)throw new RangeError(`wire-codec: over-long bigint (max ${U} digits)`);return[d,"bigint",o]}if(r==="number"){const o=t;return Number.isNaN(o)?[d,"nan"]:o===1/0?[d,"inf"]:o===-1/0?[d,"-inf"]:o}if(r!=="object")return t;if(t instanceof Date)return[d,"date",T(t.getTime(),e+1)];if(t instanceof Error){const o=t,i=T(Object.fromEntries(Object.keys(o).map(u=>[u,o[u]])),e+1),a=[d,"error",String(o.name),String(o.message),i];return o.cause!==void 0&&a.push(T(o.cause,e+1)),a}if(t instanceof URL)return[d,"url",t.href];if(t instanceof Map)return[d,"map",[...t.entries()].map(([o,i])=>[T(o,e+1),T(i,e+1)])];if(t instanceof Set)return[d,"set",[...t].map(o=>T(o,e+1))];if(t instanceof ArrayBuffer)return[d,"bytes",$(new Uint8Array(t)),"ArrayBuffer"];if(ArrayBuffer.isView(t)){const o=t,i=o.constructor.name,a=new Uint8Array(o.buffer,o.byteOffset,o.byteLength);return i==="Uint8Array"?[d,"bytes",$(a)]:[d,"bytes",$(a),i]}if(Array.isArray(t)){const o=t.map(i=>T(i,e+1));return o.length>0&&o[0]===d?[d,"arr",o]:o}if(!Te(t)){const o=t.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${o} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const n=t,s={};for(const o of Object.keys(n)){const i=n[o];if(i===void 0)continue;const a=T(i,e+1);o===M?Object.defineProperty(s,o,{configurable:!0,enumerable:!0,value:a,writable:!0}):s[o]=a}return s},E=(t,e=0)=>{if(e>j)throw new RangeError(`wire-codec: value nesting exceeds the ${j}-level limit`);if(t===null||typeof t!="object")return t;if(Array.isArray(t)){if(t[0]===d)switch(t[1]){case"-inf":return-1/0;case"arr":return t[2].map(o=>E(o,e+1));case"bigint":{const o=t[2];if(typeof o!="string"||o.length>U||!/^-?\d+$/.test(o))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${U} digits)`);return BigInt(o)}case"date":{const o=E(t[2],e+1);if(typeof o!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(o)}case"map":{const o=t[2];return new Map(o.map(i=>{if(!Array.isArray(i)||i.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[E(i[0],e+1),E(i[1],e+1)]}))}case"set":return new Set(t[2].map(o=>E(o,e+1)));case"url":{const o=t[2];if(typeof o!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(o)}case"error":{const o=t[2],i=t[3];if(typeof o!="string"||typeof i!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const a=(Object.hasOwn(q,o)?q[o]:void 0)??Error,u=new a(i);u.name!==o&&Object.defineProperty(u,"name",{configurable:!0,value:o,writable:!0});const f=E(t[4],e+1);if(f===null||typeof f!="object"||Array.isArray(f))throw new TypeError("wire-codec: malformed error — props must be an object");for(const m of Object.keys(f))m===M?Object.defineProperty(u,m,{configurable:!0,enumerable:!0,value:f[m],writable:!0}):u[m]=f[m];return t.length>5&&Object.defineProperty(u,"cause",{configurable:!0,value:E(t[5],e+1),writable:!0}),u}case"bytes":{const o=t[2];if(typeof o!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const i=ye(o);if($(i)!==o)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const a=t[3]??"Uint8Array";if(a==="ArrayBuffer")return i.buffer.byteLength===i.byteLength?i.buffer:i.slice().buffer;const u=Object.hasOwn(B,a)?B[a]:void 0;return u?new u(i.slice().buffer):i}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return t.map(o=>E(o,e+1))}return t.map(s=>E(s,e+1))}const r=t,n={};for(const s of Object.keys(r)){const o=E(r[s],e+1);s===M?Object.defineProperty(n,s,{configurable:!0,enumerable:!0,value:o,writable:!0}):n[s]=o}return n},ge="/__lunora/auth/move",g="__lunora_auth_move__",S="__lunora_auth_move_rows__",D="__lunora_auth_purged__",G=100,Ae=16,be=100,Se=2n**64n,X=t=>t.startsWith("sqlite_")||t.startsWith("_cf_")||t===g||t===S||t===D,Y=async(t,e)=>t.transaction(async()=>Promise.resolve().then(e)),h=(t,e,...r)=>[...t.sql.exec(e,...r)],l=(t,e,...r)=>{[...t.sql.exec(e,...r)]},N=t=>h(t,"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").map(e=>String(e.name)).filter(e=>!X(e)),J=(t,e)=>h(t,"SELECT name, pk FROM pragma_table_info(?)",e).map(r=>({name:String(r.name),pk:Number(r.pk)})),L=(t,e)=>J(t,e).map(({name:r})=>r),K=(t,e)=>{const r=J(t,e),n=r.filter(({pk:s})=>s>0).toSorted((s,o)=>s.pk-o.pk).map(({name:s})=>s);return n.length>0?n:r.map(({name:s})=>s)},Q=(t,e)=>Number(h(t,`SELECT count(*) AS n FROM ${c(e)}`)[0]?.n??0),z=t=>JSON.stringify(T(t)),C=(t,e)=>BigInt(`0x${we(z(e.map(r=>t[r]??null)))}`),Z=(t,e)=>{let r=0,n=0n;for(const s of t)r+=1,n=(n+e(s))%Se;return`${String(r)}:${n.toString(16)}`},ee=(t,e)=>{const r=L(t,e).toSorted((n,s)=>n.localeCompare(s));return Z(t.sql.exec(`SELECT * FROM ${c(e)}`),n=>C(n,r))},Re=t=>N(t).map(e=>{const r=h(t,"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",e)[0]?.sql,n=h(t,"SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL",e).map(s=>({name:String(s.name),sql:String(s.sql)}));return{columns:L(t,e),indexes:n,name:e,rows:Q(t,e),sql:typeof r=="string"?r:""}}),v=(t,e)=>{if(typeof e!="string"||!N(t).includes(e))throw new TypeError(`unknown table ${JSON.stringify(e)}`);return e},Oe=(t,e,r)=>{const n=h(t,`SELECT rowid AS "__lunora_rowid__", * FROM ${c(e)} WHERE rowid > ? ORDER BY rowid LIMIT ?`,r,G),s=n.at(-1)?.__lunora_rowid__;return{last:typeof s=="number"?s:void 0,rows:n.map(({__lunora_rowid__:o,...i})=>i)}},Ne=t=>L(t,g).length>0,_=t=>Ne(t)?Object.fromEntries(h(t,`SELECT tbl, after, done, pass, cols, watermark FROM ${c(g)}`).map(e=>[String(e.tbl),{after:Number(e.after),columns:JSON.parse(String(e.cols)),done:e.done===1,pass:Number(e.pass),watermark:Number(e.watermark)}])):{},_e=(t,e,r)=>Z(t.sql.exec(`SELECT h FROM ${c(S)} WHERE tbl = ? AND pass = ?`,e,r),n=>BigInt(`0x${String(n.h)}`)),Le=t=>{l(t,`CREATE TABLE IF NOT EXISTS ${c(g)} (tbl TEXT NOT NULL PRIMARY KEY, after INTEGER NOT NULL, done INTEGER NOT NULL, pass INTEGER NOT NULL, cols TEXT NOT NULL, watermark INTEGER NOT NULL DEFAULT 0)`),l(t,`CREATE TABLE IF NOT EXISTS ${c(S)} (tbl TEXT NOT NULL, k TEXT NOT NULL, h TEXT NOT NULL, pass INTEGER NOT NULL, PRIMARY KEY (tbl, k))`)},Ie=(t,e,r)=>{if(!N(t).includes(e))return 0;const n=K(t,e).map(s=>`u.${c(s)}`).join(", ");return Number(h(t,`SELECT count(*) AS n FROM ${c(e)} u WHERE u.rowid > ? AND NOT EXISTS (SELECT 1 FROM ${c(S)} c WHERE c.tbl = ? AND c.k = json_array(${n}))`,r,e)[0]?.n??0)},$e=/^CREATE TABLE /iu,je=/^CREATE (?:UNIQUE )?INDEX /iu,Ue=(t,e,r)=>{if(X(e.name))throw new TypeError(`refusing reserved table ${JSON.stringify(e.name)}`);if(!$e.test(e.sql)||e.indexes.some(({sql:s})=>!je.test(s)))throw new TypeError(`refusing unexpected DDL for ${JSON.stringify(e.name)}`);if(r.has(e.name)){const s=new Set(L(t,e.name));for(const o of e.columns.filter(i=>!s.has(i)))l(t,`ALTER TABLE ${c(e.name)} ADD COLUMN ${c(o)}`)}else l(t,e.sql);const n=new Set(h(t,"SELECT name FROM sqlite_master WHERE type = 'index'").map(s=>String(s.name)));for(const s of e.indexes.filter(({name:o})=>!n.has(o)))l(t,s.sql)},De=(t,e,r)=>{Le(t);const n=Ie(t,r.userTable,_(t)[r.userTable]?.watermark??0);if(n>0&&!r.force)throw new b("AUTH_MOVE_TARGET_NOT_EMPTY",`the pinned auth object has ${String(n)} user(s) the copy did not write; copying into it would merge two user bases — pass force to copy anyway`,{data:{users:n}});const s=new Set(N(t)),o=_(t);for(const a of e){Ue(t,a,s);const u=o[a.name],f=JSON.stringify(a.columns.toSorted((m,w)=>m.localeCompare(w)));u===void 0?l(t,`INSERT INTO ${c(g)} (tbl, after, done, pass, cols) VALUES (?, 0, 0, 1, ?)`,a.name,f):l(t,`UPDATE ${c(g)} SET cols = ? WHERE tbl = ?`,f,a.name)}l(t,`UPDATE ${c(g)} SET watermark = (SELECT coalesce(max(rowid), 0) FROM ${c(r.userTable)}) WHERE tbl = ?`,r.userTable);const i=_(t);return{cursors:Object.fromEntries(e.map(({name:a})=>[a,i[a]?.after??0])),done:Object.fromEntries(e.map(({name:a})=>[a,i[a]?.done??!1])),targetRows:Object.fromEntries(e.map(({name:a})=>[a,Q(t,a)]))}},te=(t,e,r,n,s)=>h(t,`SELECT ${s.map(o=>c(o)).join(", ")} FROM ${c(e)} WHERE ${r.map(o=>`${c(o)} IS ?`).join(" AND ")} LIMIT 1`,...n)[0],k=(t,e)=>{if(!e)throw new b("AUTH_MOVE_CONFLICT",`a row copied into ${t} collides with a row the pinned auth object holds on its own — nothing from this page was written; pass force to keep the pinned object's rows`,{data:{table:t}});return"conflicts"},ke=t=>t instanceof Error&&t.message.includes("UNIQUE constraint failed"),Ce=(t,e)=>{if(e.prior!==void 0)return k(e.table,e.force);try{l(t,`INSERT INTO ${c(e.table)} (${e.columns.map(r=>c(r)).join(", ")}) VALUES (${e.columns.map(()=>"?").join(", ")})`,...e.columns.map(r=>e.row[r]))}catch(r){if(!ke(r))throw r;return k(e.table,e.force)}return"copied"},Me=(t,e,r)=>{const n=C(r,e.columns).toString(16);return n===e.h?"unchanged":e.prior===void 0||n!==e.prior?k(e.table,e.force):(l(t,`UPDATE ${c(e.table)} SET ${e.columns.map(s=>`${c(s)} = ?`).join(", ")} WHERE ${e.keys.map(s=>`${c(s)} IS ?`).join(" AND ")}`,...e.columns.map(s=>e.row[s]),...e.key),"updated")},xe=(t,e,r,n)=>{const{force:s,keys:o,pass:i}=n,a=Object.keys(r).toSorted((A,I)=>A.localeCompare(I));if(a.length+o.length>be)throw new TypeError(`${e} has too many columns to copy`);const u=o.map(A=>r[A]??null),f=z(u),m=C(r,a).toString(16),w=h(t,`SELECT h FROM ${c(S)} WHERE tbl = ? AND k = ?`,e,f)[0]?.h,p={columns:a,force:s,h:m,key:u,keys:o,prior:typeof w=="string"?w:void 0,row:r,table:e},y=p.prior===m?void 0:te(t,e,o,u,a);let R="unchanged";return p.prior!==m&&(R=y===void 0?Ce(t,p):Me(t,p,y)),l(t,`INSERT INTO ${c(S)} (tbl, k, h, pass) VALUES (?, ?, ?, ?) ON CONFLICT (tbl, k) DO UPDATE SET h = excluded.h, pass = excluded.pass`,e,f,m,i),R},Fe=(t,e,r)=>{const{counts:n,force:s,keys:o,marker:i}=r,a=h(t,`SELECT k, h FROM ${c(S)} WHERE tbl = ? AND pass < ?`,e,i.pass);for(const u of a){const f=E(JSON.parse(String(u.k))),m=te(t,e,o,f,i.columns);m!==void 0&&(C(m,i.columns).toString(16)===String(u.h)?(l(t,`DELETE FROM ${c(e)} WHERE ${o.map(w=>`${c(w)} IS ?`).join(" AND ")}`,...f),n.deleted+=1):n[k(e,s)]+=1),l(t,`DELETE FROM ${c(S)} WHERE tbl = ? AND k = ?`,e,String(u.k))}},Pe=async(t,e,r)=>{const n=_(t)[e];if(n===void 0)throw new TypeError(`table ${JSON.stringify(e)} was not part of this move`);return Y(t,()=>{const s={conflicts:0,copied:0,deleted:0,unchanged:0,updated:0},o=K(t,e);for(const i of r.rows)s[xe(t,e,i,{force:r.force,keys:o,pass:n.pass})]+=1;return r.final&&Fe(t,e,{counts:s,force:r.force,keys:o,marker:n}),l(t,`UPDATE ${c(g)} SET after = ?, done = ? WHERE tbl = ?`,r.last,r.final?1:0,e),s})},He=async(t,e)=>{const r=N(t);return await Y(t,()=>{const n=r.filter(o=>e[o]===void 0);if(n.length>0)throw new b("AUTH_MOVE_INCOMPLETE",`refusing to purge the un-pinned auth object: ${n.join(", ")} not fully copied — run the copy to done: true first`,{data:{unfinished:n}});const s=r.filter(o=>ee(t,o)!==e[o]);if(s.length>0)throw new b("AUTH_MOVE_SOURCE_CHANGED",`refusing to purge the un-pinned auth object: ${s.join(", ")} changed since it was copied — run the copy again to carry the changes, then purge`,{data:{changed:s}});for(const o of r)l(t,`DROP TABLE ${c(o)}`);l(t,`CREATE TABLE IF NOT EXISTS ${c(D)} (at INTEGER NOT NULL)`),l(t,`INSERT INTO ${c(D)} (at) VALUES (?)`,Date.now())}),{dropped:r}},Be=t=>{if(L(t,D).length>0)throw new b("AUTH_MOVE_SOURCE_PURGED","the un-pinned auth object was purged after its tables were copied; anything in it now is new, and copying it would delete the pinned object's users")},qe=new Set(["fingerprints","manifest","page","purge"]),ve=(t,e,r)=>{e.prepare();const n=_(t);return{copied:r?Object.fromEntries(Object.entries(n).map(([s,o])=>[s,_e(t,s,o.pass)])):{},markers:n,order:e.order()}},We=(t,e)=>{for(const r of e)l(t,`UPDATE ${c(g)} SET after = 0, done = 0, pass = pass + 1 WHERE tbl = ?`,r);return{}},Ve=[/(?:UNIQUE|NOT NULL|CHECK|FOREIGN KEY|PRIMARY KEY) constraint failed/u,/no such (?:table|column)/u,/datatype mismatch/u,/SQLITE_[A-Z_]+/u],Ge=t=>{const e=t instanceof Error?t.message:String(t);for(const r of Ve){const n=r.exec(e);if(n)return n[0]}return t instanceof Error?t.name:"unknown"},Xe=async(t,e,r)=>{switch(qe.has(String(e.op))&&Be(t),e.op){case"begin":return r.prepare(),De(t,e.tables,{force:e.force===!0,userTable:r.userTable()});case"fingerprints":return{fingerprints:Object.fromEntries(N(t).map(n=>[n,ee(t,n)]))};case"manifest":return{tables:Re(t)};case"page":{const n=Oe(t,v(t,e.table),Number(e.after??0));return{last:n.last,rows:T(n.rows)}}case"purge":{const n=await He(t,e.expected??{});return r.onPurge(),n}case"rescan":return We(t,e.tables??[]);case"status":return ve(t,r,e.fingerprints===!0);case"write":return Pe(t,v(t,e.table),{final:e.final===!0,force:e.force===!0,last:Number(e.last),rows:E(e.rows)});default:throw new TypeError(`unknown move op ${JSON.stringify(e.op)}`)}},Ye=async(t,e,r)=>{try{return await Xe(t,e,r)}catch(n){if(n instanceof b)throw n;console.error("@lunora/auth: auth move step failed",n);const s=String(e.op),o=typeof e.table=="string"?e.table:void 0,i=Ge(n);throw new b("AUTH_MOVE_FAILED",`auth move ${s}${o===void 0?"":` on ${o}`} failed: ${i}`,{data:{cause:i,op:s,...o===void 0?{}:{table:o}}})}},Je=(t,e)=>{const r=n=>{const s=e.first.indexOf(n);if(s!==-1)return s;const o=e.last.indexOf(n);return o===-1?e.first.length:e.first.length+1+o};return t.toSorted((n,s)=>r(n.name)-r(s.name)||n.name.localeCompare(s.name))},Ke=(t,e)=>({conflicts:0,copied:0,deleted:0,sourceRows:t.rows,table:t.name,targetRows:e,unchanged:0,updated:0}),lt=t=>{const e=async(r,n)=>{const s=await t(r,n);if(!s.ok){let o={};try{const i=await s.json();typeof i.error=="object"&&i.error!==null&&(o=i.error)}catch{}throw new b(o.code??"AUTH_MOVE_FAILED",o.message??`auth move ${String(n.op)} on the ${r} object failed (${String(s.status)})`,{data:o.data,status:s.status})}return s.json()};return{copy:async(r={})=>{const n=r.force===!0,s=await e("target",{op:"status"}),{tables:o}=await e("source",{op:"manifest"}),i=await e("target",{force:n,op:"begin",tables:o}),a=[];let u=0;for(const p of Je(o,s.order)){const y=Ke(p,i.targetRows[p.name]??0);let R=i.cursors[p.name]??0;for(a.push(y);;){if(u>=Ae)return{done:!1,tables:a};u+=1;const A=await e("source",{after:R,op:"page",table:p.name}),I=E(A.rows),x=I.length<G;if(I.length===0&&i.done[p.name]===!0)break;const O=await e("target",{final:x,force:n,last:A.last??R,op:"write",rows:A.rows,table:p.name});if(y.conflicts+=O.conflicts,y.copied+=O.copied,y.deleted+=O.deleted,y.unchanged+=O.unchanged,y.updated+=O.updated,y.targetRows+=O.copied-O.deleted,R=A.last??R,x)break}}const{fingerprints:f}=await e("source",{op:"fingerprints"}),m=await e("target",{fingerprints:!0,op:"status"}),w=Object.keys(f).filter(p=>m.copied[p]!==f[p]);return w.length>0&&await e("target",{op:"rescan",tables:w}),{done:w.length===0,tables:a}},purge:async()=>{const{copied:r,markers:n}=await e("target",{fingerprints:!0,op:"status"}),s=Object.fromEntries(Object.entries(n).filter(([,o])=>o.done).map(([o])=>[o,r[o]]));return e("source",{expected:s,op:"purge"})}}},Qe="/__lunora/auth/session",ze="/__lunora/auth/audit",Ze="x-lunora-auth-do-secret",et=t=>{if(t===null||typeof t!="object"||Array.isArray(t))return{error:"body must be a JSON object"};const e={};for(const[r,n]of Object.entries(t))if(r==="limit"||r==="sinceSeq"){if(typeof n!="number"||!Number.isFinite(n))return{error:`"${r}" must be a finite number`};e[r]=n}else if(r==="actorId"||r==="event"){if(typeof n!="string")return{error:`"${r}" must be a string`};e[r]=n}else return{error:`unknown audit read option "${r}"`};return{options:e}};class mt{#r;#a;#c;#e;#t;#n=!1;#o={first:["user"],last:[]};constructor(e,r,n={}){this.#e=e.storage,this.#r=P(e.storage),this.#c=r,this.#a=n}#s(){if(this.#t!==void 0)return this.#t;const e=this.#c(),r=ae(e),{tables:n}=F(r);if(this.#o={first:[n.user?.modelName??"user",n.account?.modelName??"account",n.session?.modelName??"session"],last:[oe,n.rateLimit?.modelName??"rateLimit"]},!this.#n){for(const s of fe(r))[...this.#e.sql.exec(s)];for(const s of le(r,o=>this.#u(o)))[...this.#e.sql.exec(s)];this.#f(r),this.#n=!0}return this.#t=ce({...e,database:ne(this.#e)}),this.#t}#u(e){return[...this.#e.sql.exec("SELECT name FROM pragma_table_info(?)",e)].map(n=>String(n.name))}#f(e){const{tables:r}=F(e),{account:n}=r;if(n===void 0)return;const s=Object.entries(n.fields).map(([o,i])=>i.fieldName??o);if(!(de(s)||!this.#u(n.modelName).includes("issuer")))try{const o=[...this.#e.sql.exec("SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?",n.modelName)].map(i=>({name:String(i.name),sql:typeof i.sql=="string"?i.sql:void 0}));for(const i of pe(n.modelName,Ee(o)))[...this.#e.sql.exec(i)]}catch(o){console.error("@lunora/auth: could not drop the reverted `account.issuer` column; if it is still present, sign-ups will fail until it is removed.",o)}}async#l(e){if(!this.#i(e))return Response.json({error:"unauthorized"},{status:401});let r;try{r=await e.json()}catch{return Response.json({error:"invalid body"},{status:400})}const n=et(r??{});if("error"in n)return Response.json({error:n.error},{status:400});await se(this.#r);const s=await ie(this.#r).read(n.options);return Response.json({entries:s})}async#m(e){if(e.method!=="POST"||!this.#i(e))return Response.json({error:"unauthorized"},{status:401});try{const r=await e.json(),n=await Ye(this.#e,r,{onPurge:()=>{this.#n=!1,this.#t=void 0,this.#r=P(this.#e)},order:()=>this.#o,prepare:()=>{this.#s()},userTable:()=>this.#o.first[0]??"user"});return Response.json(n)}catch(r){const{body:n,redacted:s,status:o}=re(r,{encodeData:i=>i,fallbackCode:"AUTH_MOVE_FAILED",redactedMessage:"auth move failed"});return s&&console.error("@lunora/auth: auth move failed",r),Response.json({error:n},{status:o})}}#i(e){const{internalSecret:r}=this.#a;if(r===void 0||r==="")return!1;const n=e.headers.get(Ze);return n!==null&&he(n,r)}async#d(e){if(!this.#i(e))return Response.json({error:"unauthorized"},{status:401});const n=await this.#s().api.getSession({headers:e.headers}),s=n?.user.id;if(s===void 0)return Response.json({});const o=n?.session.expiresAt,i=n?.user,a=i?.role;return Response.json({...typeof i?.email=="string"&&i.email.length>0?{email:i.email}:{},...o instanceof Date?{expiresAtMs:o.getTime()}:{},...typeof i?.name=="string"&&i.name.length>0?{name:i.name}:{},...typeof a=="string"&&a.length>0?{role:a}:{},userId:s})}async fetch(e){const r=new URL(e.url);if(r.pathname===Qe)return this.#d(e);if(r.pathname===ze)return this.#l(e);if(r.pathname===ge)return this.#m(e);const n=this.#s();return await me(n,e)??await ue(n,e)??Response.json({error:"not an auth route"},{status:404})}}export{Ze as I,mt as L,ge as M,ze as R,Qe as a,lt as c};
@@ -0,0 +1 @@
1
+ import{LunoraError as a}from"@lunora/errors";import{getIP as l}from"better-auth/api";import{betterAuth as u}from"better-auth/minimal";import{o as h}from"./on-cloudflare-edge-Dx6gFjBO.mjs";import{authDiscoveryPaths as f}from"./authDiscoveryPaths-B14lM14u.mjs";import{validateSessionPolicy as m}from"./sessionPresets-C867Mlo4.mjs";const n="cf-connecting-ip",p=t=>{const e=t!==void 0&&t.length>0;return h()?e?[n,"x-forwarded-for"]:[n]:e?["x-forwarded-for"]:[]},i="/**",A=100,E=t=>{const e=t.rateLimit?.customRules??{};return e[i]!==void 0?e:{...e,[i]:(o,r)=>l(o,t)===null?{max:r.max*A,window:r.window}:r}},d=32,g=t=>{const e=typeof t=="string"?t.trim().length:0;return e>0&&e<d},c=t=>typeof t=="string"?t.toLowerCase().startsWith("http://"):t&&typeof t=="object"?t.protocol==="http"?!0:t.protocol==="https"?!1:typeof t.fallback=="string"&&t.fallback.toLowerCase().startsWith("http://"):!1,w=new Set(["127.0.0.1","localhost"]),L=t=>{if(typeof t!="string")return;let e;try{e=new URL(t)}catch{return}if(!(e.protocol!=="http:"||!w.has(e.hostname)))return{allowedHosts:[e.hostname,`${e.hostname}:*`],fallback:t,protocol:"http"}},v=t=>{const e=L(t.baseURL)??t.baseURL;if(g(t.secret)){const s=`@lunora/auth: AUTH_SECRET is only ${String(t.secret?.trim().length)} characters. Use at least ${String(d)} for a brute-force-resistant secret — generate one with \`openssl rand -hex 32\`.`;if(!c(e))throw new a("INTERNAL",s);console.warn(s)}const o=t.advanced??{},r=o.ipAddress??{};return{...t,advanced:{...o,defaultCookieAttributes:o.defaultCookieAttributes??{httpOnly:!0,path:"/",sameSite:"lax"},ipAddress:{...r,...r.ipAddressHeaders===void 0?{ipAddressHeaders:p(r.trustedProxies)}:{}},...o.useSecureCookies===void 0?{useSecureCookies:!c(e)}:{}},baseURL:e}},C=t=>{const e=v(t),o=e.rateLimit?.enabled===void 0,r=e.rateLimit?.storage===void 0&&e.rateLimit?.enabled!==!1,s=e.rateLimit?.enabled===!1?{}:{rateLimit:{...e.rateLimit,...o?{enabled:!0}:{},...r?{storage:"database"}:{},customRules:E(e)}};return{...e,...s,...e.session?.cookieCache===void 0?{session:{...e.session,cookieCache:{enabled:!0,maxAge:60}}}:{}}},S=t=>{if(!t.secret||t.secret.trim()==="")throw new a("INTERNAL",'@lunora/auth: `secret` is required. Set AUTH_SECRET locally in .dev.vars (`lunora env set AUTH_SECRET "$(openssl rand -hex 32)"`), and in production with `wrangler secret put AUTH_SECRET`.');return t.session&&m(t.session),f(t),C(t)},x=t=>{const e=S(t);if(typeof e.database!="function")throw new a("INTERNAL","@lunora/auth: `createAuth` needs an adapter factory as `database` (`lunoraD1Adapter(env.DB)` or the Durable Object wiring). A raw D1 binding is for the migration-only instance: build it with `createMigrationAuth` from `@lunora/auth/migrate`.");return u(e)};export{x as createAuth,S as prepareAuthOptions,C as resolveAuthOptions};
@@ -1 +1 @@
1
- import{LunoraError as v}from"@lunora/errors";import{I as d,a as w,c as A,R,M as E}from"./auth-do-DEd6xEKe.mjs";import{isDiscoveryRequest as g,unlessNotFound as j}from"./authDiscoveryPaths-B14lM14u.mjs";import{isAuthRoutePath as T,DEFAULT_AUTH_BASE_PATH as b}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";const H=a=>{const{internalSecret:i,jurisdiction:c,objectName:l="auth"}=a,m=new Set(a.discoveryPaths),h=a.namespace;let{namespace:r}=a;if(r&&c!==void 0){if(typeof r.jurisdiction!="function")throw new TypeError(`@lunora/auth: Durable Object namespace does not support jurisdiction("${c}") — update @cloudflare/workers-types or remove the jurisdiction option`);r=r.jurisdiction(c)}const u=()=>{if(r)return r.get(r.idFromName(l))},y=async(t,n,o)=>{if(!i)return;const s=u();if(!s)return;const e=await s.fetch(new Request(new URL(t,n),{body:JSON.stringify(o),headers:{"content-type":"application/json",[d]:i},method:"POST"}));return e.ok?e:void 0},p=r,f=h&&p&&c!==void 0?A(async(t,n)=>{if(!i)throw new v("AUTH_MOVE_NOT_CONFIGURED","copying the auth object needs its internal secret; set the auth object's `internalSecret`");const o=t==="source"?h:p;return o.get(o.idFromName(l)).fetch(new Request(new URL(E,"https://auth-do.invalid"),{body:JSON.stringify(n),headers:{"content-type":"application/json",[d]:i},method:"POST"}))}):void 0;return{...f===void 0?{}:{jurisdictionMove:f},auditReader:{read:async t=>{const n=await y(R,"https://auth-do.invalid",t);return n?(await n.json())?.entries??[]:[]}},authHandler:async t=>{if(T(new URL(t.url).pathname,b))return u()?.fetch(t)},discoveryHandler:async t=>{if(g(t,m))return j(await u()?.fetch(t))},resolveIdentity:async t=>{if(!i)return null;const n=u();if(!n)return null;const o=new Headers(t.headers);o.set(d,i);const s=await n.fetch(new Request(new URL(w,t.url),{headers:o}));if(!s.ok)return null;const e=await s.json();return e?.userId?{...typeof e.email=="string"&&e.email.length>0?{email:e.email}:{},...typeof e.expiresAtMs=="number"&&Number.isFinite(e.expiresAtMs)?{expiresAtMs:e.expiresAtMs}:{},...typeof e.name=="string"&&e.name.length>0?{name:e.name}:{},...typeof e.role=="string"&&e.role.length>0?{role:e.role}:{},userId:e.userId}:null}}};export{H as createDoAuthWiring};
1
+ import{LunoraError as v}from"@lunora/errors";import{I as d,a as w,c as A,R,M as E}from"./auth-do-Dr3ogic7.mjs";import{isDiscoveryRequest as g,unlessNotFound as j}from"./authDiscoveryPaths-B14lM14u.mjs";import{isAuthRoutePath as T,DEFAULT_AUTH_BASE_PATH as b}from"./DEFAULT_AUTH_BASE_PATH-DneiLGpv.mjs";const H=a=>{const{internalSecret:i,jurisdiction:c,objectName:l="auth"}=a,m=new Set(a.discoveryPaths),h=a.namespace;let{namespace:r}=a;if(r&&c!==void 0){if(typeof r.jurisdiction!="function")throw new TypeError(`@lunora/auth: Durable Object namespace does not support jurisdiction("${c}") — update @cloudflare/workers-types or remove the jurisdiction option`);r=r.jurisdiction(c)}const u=()=>{if(r)return r.get(r.idFromName(l))},y=async(t,n,o)=>{if(!i)return;const s=u();if(!s)return;const e=await s.fetch(new Request(new URL(t,n),{body:JSON.stringify(o),headers:{"content-type":"application/json",[d]:i},method:"POST"}));return e.ok?e:void 0},p=r,f=h&&p&&c!==void 0?A(async(t,n)=>{if(!i)throw new v("AUTH_MOVE_NOT_CONFIGURED","copying the auth object needs its internal secret; set the auth object's `internalSecret`");const o=t==="source"?h:p;return o.get(o.idFromName(l)).fetch(new Request(new URL(E,"https://auth-do.invalid"),{body:JSON.stringify(n),headers:{"content-type":"application/json",[d]:i},method:"POST"}))}):void 0;return{...f===void 0?{}:{jurisdictionMove:f},auditReader:{read:async t=>{const n=await y(R,"https://auth-do.invalid",t);return n?(await n.json())?.entries??[]:[]}},authHandler:async t=>{if(T(new URL(t.url).pathname,b))return u()?.fetch(t)},discoveryHandler:async t=>{if(g(t,m))return j(await u()?.fetch(t))},resolveIdentity:async t=>{if(!i)return null;const n=u();if(!n)return null;const o=new Headers(t.headers);o.set(d,i);const s=await n.fetch(new Request(new URL(w,t.url),{headers:o}));if(!s.ok)return null;const e=await s.json();return e?.userId?{...typeof e.email=="string"&&e.email.length>0?{email:e.email}:{},...typeof e.expiresAtMs=="number"&&Number.isFinite(e.expiresAtMs)?{expiresAtMs:e.expiresAtMs}:{},...typeof e.name=="string"&&e.name.length>0?{name:e.name}:{},...typeof e.role=="string"&&e.role.length>0?{role:e.role}:{},userId:e.userId}:null}}};export{H as createDoAuthWiring};
@@ -0,0 +1 @@
1
+ import{LunoraError as n}from"@lunora/errors";import{APIError as l}from"better-auth/api";import{assertEmailAllowed as d}from"../email-guard.mjs";const u=e=>{switch(e){case 400:return"BAD_REQUEST";case 422:return"UNPROCESSABLE_ENTITY";case 429:return"TOO_MANY_REQUESTS";default:return"INTERNAL_SERVER_ERROR"}},c=e=>async(r,t)=>{const s=typeof r.email=="string"?r.email:void 0;if(s===void 0||s==="")return;let o;try{const a=e.onClassify!==void 0;o=await d(s,{...e,lists:{disposable:e.blockDisposable!==!1||a,free:a}})}catch(a){throw a instanceof n?new l(u(a.status),{code:a.code,message:a.message}):a}e.onClassify?.(o,r,t)},m=(e={})=>({user:{create:{before:c(e)}}}),R=(e,r={})=>{const t=c(r),s=e.databaseHooks?.user?.create?.before,o=s?async(a,i)=>(await t(a,i),s(a,i)):t;return{...e,databaseHooks:{...e.databaseHooks,user:{...e.databaseHooks?.user,create:{...e.databaseHooks?.user?.create,before:o}}}}};export{m as emailGateDatabaseHooks,R as withEmailGate};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/auth",
3
- "version": "1.0.0-alpha.212",
3
+ "version": "1.0.0-alpha.213",
4
4
  "description": "Auth for Lunora — a thin better-auth wrapper: email/password, OAuth, plugins, D1-backed",
5
5
  "keywords": [
6
6
  "auth",
@@ -96,6 +96,10 @@
96
96
  "types": "./dist/store.d.ts",
97
97
  "import": "./dist/store.mjs"
98
98
  },
99
+ "./migrate": {
100
+ "types": "./dist/migrate.d.ts",
101
+ "import": "./dist/migrate.mjs"
102
+ },
99
103
  "./sql-store": {
100
104
  "types": "./dist/sql-store.d.ts",
101
105
  "import": "./dist/sql-store.mjs"
@@ -113,7 +117,7 @@
113
117
  "@better-auth/passkey": "1.7.7",
114
118
  "@better-auth/scim": "1.7.7",
115
119
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
116
- "@lunora/values": "1.0.0-alpha.64",
120
+ "@lunora/values": "1.0.0-alpha.65",
117
121
  "@visulima/disposable-email-domains": "1.1.25",
118
122
  "@visulima/email-verifier": "1.0.42",
119
123
  "@visulima/free-email-domains": "1.0.2",
@@ -1 +0,0 @@
1
- import{getAuthTablesWithResolvedIndexes as w,invalidateSchemaChecks as y}from"@better-auth/core/db/internal";import{LunoraError as R}from"@lunora/errors";import{getMigrations as l}from"better-auth/db/migration";import{q as c}from"./quote-identifier-CGiYFBvY.mjs";import{resolveAuthOptions as _}from"./createAuth-BdVltKyf.mjs";import{schemaDeclaresIssuer as x,legacyIssuerCleanupStatements as M,indexesReferencingIssuer as T}from"./legacyIssuerCleanupStatements-D9Kf8OD1.mjs";const A=/pragma_index_list\s*\(/iu,N=/sqlite_master/iu,C=/^["`[]|["\]`]$/gu,D=/\s+/u,S=/create\s+unique\s+index/iu,O=/\bwhere\b/iu,P=e=>A.test(e)&&N.test(e),L=e=>(e.trim().split(D)[0]??"").replaceAll(C,"").trim(),U=e=>{const t=e.indexOf("(");if(t===-1)return{columns:[],tail:""};let s=0;for(let n=t;n<e.length;n+=1){const a=e[n];if(a==="(")s+=1;else if(a===")"&&(s-=1,s===0))return{columns:e.slice(t+1,n).split(",").map(o=>L(o)).filter(o=>o!==""),tail:e.slice(n+1)}}return{columns:[],tail:""}},q=async e=>{const t=e.prepare("SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index'"),{results:s}=await t.all(),n=[];for(const a of s??[]){const o=a.name,r=a.tbl_name,i=a.sql;if(typeof o!="string"||typeof r!="string")continue;if(typeof i!="string"){n.push({columnPosition:0,indexName:o,isPartial:0,isUnique:1,tableName:r});continue}const f=S.test(i)?1:0,{columns:h,tail:b}=U(i),E=O.test(b)?1:0;for(const[I,g]of h.entries())n.push({columnName:g,columnPosition:I,indexName:o,isPartial:E,isUnique:f,tableName:r})}return n},v=e=>{const t=async()=>{const n=await q(e);return{meta:{},results:n,success:!0}},s={all:t,bind:()=>s,run:t};return s},W=e=>new Proxy(e,{get(t,s,n){if(s==="prepare")return o=>P(o)?v(t):t.prepare(o);const a=Reflect.get(t,s,n);return typeof a=="function"?a.bind(t):a}}),m=e=>typeof e=="object"&&e!==null&&typeof e.prepare=="function"&&typeof e.batch=="function",p=e=>{const{database:t}=e;if(!(t&&typeof t!="function"))throw new R("AUTH_MIGRATOR_UNSUPPORTED",t?"@lunora/auth: this auth instance's `database` is a custom adapter, which better-auth's migrator cannot drive.":"@lunora/auth: this auth instance has no `database`, so better-auth's migrator has nothing to introspect.")},d=e=>m(e.database)?{...e,database:W(e.database)}:e,F=async(e,t)=>{const s=c("t");try{return await e.prepare(`SELECT ${s}.${c("issuer")} FROM ${c(t)} AS ${s} LIMIT 0`).run(),!0}catch{return!1}},H=async e=>{const{database:t}=e,{tables:s}=w(e),{account:n}=s;if(n!==void 0&&!x(Object.entries(n.fields).map(([a,o])=>o.fieldName??a))&&m(t)&&await F(t,n.modelName))try{const{results:a}=await t.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?").bind(n.modelName).all();await t.batch(M(n.modelName,T(a)).map(o=>t.prepare(o)))}catch(a){console.error("@lunora/auth: could not drop the reverted `account.issuer` column; if it is still present, sign-ups will fail until it is removed.",a)}},$=e=>{const{database:t}=e;typeof t=="object"&&t!==null&&y(t)},u=new WeakMap,Y=async e=>{const{options:t}=e;p(t);const s=u.get(t);if(s){await s;return}const n=(async()=>{const{runMigrations:a}=await l(d(t));await a(),await H(t),$(t)})();u.set(t,n);try{await n}catch(a){throw u.delete(t),a}},z=async e=>{const t=_(e);p(t);const{compileMigrations:s}=await l(d(t));return s()};export{z as compileMigrationsSql,Y as ensureMigrated};
@@ -1 +0,0 @@
1
- import{LunoraError as c}from"@lunora/errors";import{betterAuth as l}from"better-auth";import{getIP as u}from"better-auth/api";import{o as f}from"./on-cloudflare-edge-Dx6gFjBO.mjs";import{authDiscoveryPaths as h}from"./authDiscoveryPaths-B14lM14u.mjs";import{validateSessionPolicy as m}from"./sessionPresets-C867Mlo4.mjs";const n="cf-connecting-ip",p=e=>{const t=e!==void 0&&e.length>0;return f()?t?[n,"x-forwarded-for"]:[n]:t?["x-forwarded-for"]:[]},a="/**",A=100,E=e=>{const t=e.rateLimit?.customRules??{};return t[a]!==void 0?t:{...t,[a]:(o,r)=>u(o,e)===null?{max:r.max*A,window:r.window}:r}},d=32,L=e=>{const t=typeof e=="string"?e.trim().length:0;return t>0&&t<d},i=e=>typeof e=="string"?e.toLowerCase().startsWith("http://"):e&&typeof e=="object"?e.protocol==="http"?!0:e.protocol==="https"?!1:typeof e.fallback=="string"&&e.fallback.toLowerCase().startsWith("http://"):!1,g=new Set(["127.0.0.1","localhost"]),v=e=>{if(typeof e!="string")return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"||!g.has(t.hostname)))return{allowedHosts:[t.hostname,`${t.hostname}:*`],fallback:e,protocol:"http"}},w=e=>{const t=v(e.baseURL)??e.baseURL;if(L(e.secret)){const s=`@lunora/auth: AUTH_SECRET is only ${String(e.secret?.trim().length)} characters. Use at least ${String(d)} for a brute-force-resistant secret — generate one with \`openssl rand -hex 32\`.`;if(!i(t))throw new c("INTERNAL",s);console.warn(s)}const o=e.advanced??{},r=o.ipAddress??{};return{...e,advanced:{...o,defaultCookieAttributes:o.defaultCookieAttributes??{httpOnly:!0,path:"/",sameSite:"lax"},ipAddress:{...r,...r.ipAddressHeaders===void 0?{ipAddressHeaders:p(r.trustedProxies)}:{}},...o.useSecureCookies===void 0?{useSecureCookies:!i(t)}:{}},baseURL:t}},C=e=>{const t=w(e),o=t.rateLimit?.enabled===void 0,r=t.rateLimit?.storage===void 0&&t.rateLimit?.enabled!==!1,s=t.rateLimit?.enabled===!1?{}:{rateLimit:{...t.rateLimit,...o?{enabled:!0}:{},...r?{storage:"database"}:{},customRules:E(t)}};return{...t,...s,...t.session?.cookieCache===void 0?{session:{...t.session,cookieCache:{enabled:!0,maxAge:60}}}:{}}},R=e=>{if(!e.secret||e.secret.trim()==="")throw new c("INTERNAL",'@lunora/auth: `secret` is required. Set AUTH_SECRET locally in .dev.vars (`lunora env set AUTH_SECRET "$(openssl rand -hex 32)"`), and in production with `wrangler secret put AUTH_SECRET`.');return e.session&&m(e.session),h(e),l(C(e))};export{R as createAuth,C as resolveAuthOptions};
@@ -1 +0,0 @@
1
- import{LunoraError as n}from"@lunora/errors";import{APIError as u}from"better-auth/api";import{assertEmailAllowed as E}from"../email-guard.mjs";const m=e=>{switch(e){case 400:return"BAD_REQUEST";case 422:return"UNPROCESSABLE_ENTITY";case 429:return"TOO_MANY_REQUESTS";default:return"INTERNAL_SERVER_ERROR"}},i=e=>async(t,s)=>{const r=typeof t.email=="string"?t.email:void 0;if(r===void 0||r==="")return;let o;try{o=await E(r,e)}catch(a){throw a instanceof n?new u(m(a.status),{code:a.code,message:a.message}):a}e.onClassify?.(o,t,s)},b=(e={})=>({user:{create:{before:i(e)}}}),R=(e,t={})=>{const s=i(t),r=e.databaseHooks?.user?.create?.before,o=r?async(a,c)=>(await s(a,c),r(a,c)):s;return{...e,databaseHooks:{...e.databaseHooks,user:{...e.databaseHooks?.user,create:{...e.databaseHooks?.user?.create,before:o}}}}};export{b as emailGateDatabaseHooks,R as withEmailGate};