@lunora/cli 1.0.0-alpha.46 → 1.0.0-alpha.461

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/LICENSE.md +33 -0
  2. package/README.md +3 -1
  3. package/dist/bin.mjs +3 -10
  4. package/dist/cli.d.mts +31 -0
  5. package/dist/cli.d.ts +31 -0
  6. package/dist/cli.mjs +1 -0
  7. package/dist/index.d.mts +1096 -386
  8. package/dist/index.d.ts +1096 -386
  9. package/dist/index.mjs +1 -19
  10. package/dist/packem_chunks/cli.mjs +2 -0
  11. package/dist/packem_chunks/dispatch.mjs +1 -0
  12. package/dist/packem_chunks/handler.mjs +17 -158
  13. package/dist/packem_chunks/handler10.mjs +1 -16
  14. package/dist/packem_chunks/handler11.mjs +1 -22
  15. package/dist/packem_chunks/handler12.mjs +1 -192
  16. package/dist/packem_chunks/handler13.mjs +1 -131
  17. package/dist/packem_chunks/handler14.mjs +1 -65
  18. package/dist/packem_chunks/handler15.mjs +3 -58
  19. package/dist/packem_chunks/handler16.mjs +1 -80
  20. package/dist/packem_chunks/handler17.mjs +1 -43
  21. package/dist/packem_chunks/handler18.mjs +1 -105
  22. package/dist/packem_chunks/handler19.mjs +1 -170
  23. package/dist/packem_chunks/handler2.mjs +1 -114
  24. package/dist/packem_chunks/handler20.mjs +7 -94
  25. package/dist/packem_chunks/handler21.mjs +2 -94
  26. package/dist/packem_chunks/handler22.mjs +1 -0
  27. package/dist/packem_chunks/handler23.mjs +3 -0
  28. package/dist/packem_chunks/handler24.mjs +3 -0
  29. package/dist/packem_chunks/handler25.mjs +1 -0
  30. package/dist/packem_chunks/handler26.mjs +2 -0
  31. package/dist/packem_chunks/handler27.mjs +99 -0
  32. package/dist/packem_chunks/handler28.mjs +15 -0
  33. package/dist/packem_chunks/handler29.mjs +1 -0
  34. package/dist/packem_chunks/handler3.mjs +1 -204
  35. package/dist/packem_chunks/handler30.mjs +1 -0
  36. package/dist/packem_chunks/handler4.mjs +1 -33
  37. package/dist/packem_chunks/handler5.mjs +3 -49
  38. package/dist/packem_chunks/handler6.mjs +1 -91
  39. package/dist/packem_chunks/handler7.mjs +1 -42
  40. package/dist/packem_chunks/handler8.mjs +1 -174
  41. package/dist/packem_chunks/handler9.mjs +2 -315
  42. package/dist/packem_chunks/runCodegenCommand.mjs +4 -52
  43. package/dist/packem_chunks/runDevCommand.mjs +6 -0
  44. package/dist/packem_chunks/runInitCommand.mjs +212 -1432
  45. package/dist/packem_chunks/runResetCommand.mjs +1 -41
  46. package/dist/packem_chunks/runRpcCommand.mjs +1 -68
  47. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-5xxKz-zB.mjs +1 -0
  48. package/dist/packem_shared/EXIT_CODE-DVjUgIaG.mjs +1 -0
  49. package/dist/packem_shared/admin-token-VdUnvnKW.mjs +1 -0
  50. package/dist/packem_shared/admin-url-DJB8P6Jh.mjs +1 -0
  51. package/dist/packem_shared/api-spec-lYyq4vL6.mjs +1 -0
  52. package/dist/packem_shared/binding-manifest-file-CQ1eQYy1.mjs +2 -0
  53. package/dist/packem_shared/buildRegistryIndex-Dc8D8AR6.mjs +1 -0
  54. package/dist/packem_shared/cap-error-body-YBKO32BF.mjs +1 -0
  55. package/dist/packem_shared/catalog-CahzmDLV.mjs +1 -0
  56. package/dist/packem_shared/cli-manifest-DoQ_3bzs.mjs +1 -0
  57. package/dist/packem_shared/cloudflare-credentials-BVmZnDZk.mjs +1 -0
  58. package/dist/packem_shared/codegen-error-AmH54ofi.mjs +3 -0
  59. package/dist/packem_shared/command-B6vu47iR.mjs +1 -0
  60. package/dist/packem_shared/commands-DC3ZX1yk.mjs +13 -0
  61. package/dist/packem_shared/createLogger-4TujevyB.mjs +2 -0
  62. package/dist/packem_shared/createRecordingSpawner-01ZngIwK.mjs +1 -0
  63. package/dist/packem_shared/deploy-target-resolve-BTCW1Iv4.mjs +1 -0
  64. package/dist/packem_shared/diffSnapshots-htqNZT8Q.mjs +7 -0
  65. package/dist/packem_shared/docker-DpVxvYpL.mjs +1 -0
  66. package/dist/packem_shared/free-port-DjbFhaaT.mjs +1 -0
  67. package/dist/packem_shared/health-probe-Bw9JFfIy.mjs +1 -0
  68. package/dist/packem_shared/import-DoAUdyfU.mjs +13 -0
  69. package/dist/packem_shared/index-oHZBGBwB.mjs +1 -0
  70. package/dist/packem_shared/insertSchemaExtension-CbTH3lCQ.mjs +10 -0
  71. package/dist/packem_shared/lint-ignore-report-DKZagpqk.mjs +2 -0
  72. package/dist/packem_shared/mcp-config-file-GR1Y_CVw.mjs +2 -0
  73. package/dist/packem_shared/open-url-EnKy--w-.mjs +1 -0
  74. package/dist/packem_shared/output-format-DWe_tkTR.mjs +2 -0
  75. package/dist/packem_shared/parseManifest-CwPTKdtS.mjs +1 -0
  76. package/dist/packem_shared/path-containment-CgxYZggb.mjs +1 -0
  77. package/dist/packem_shared/plan-BH4ZfiFs.mjs +5 -0
  78. package/dist/packem_shared/planDevCommand-I8-NB_CN.mjs +1 -0
  79. package/dist/packem_shared/platform-diagnostics-Cn2g6Jh-.mjs +4 -0
  80. package/dist/packem_shared/prompt-cancelled-BvsNxg_Q.mjs +1 -0
  81. package/dist/packem_shared/render-lunora-error-DUo0pA61.mjs +2 -0
  82. package/dist/packem_shared/resolve-Bdc9KEUv.mjs +5 -0
  83. package/dist/packem_shared/resolve-target-C_ZloTvd.mjs +1 -0
  84. package/dist/packem_shared/runAddCommand-CTblwN9O.mjs +1 -0
  85. package/dist/packem_shared/runDeployCommand-Tgy9tGRV.mjs +1 -0
  86. package/dist/packem_shared/runExportCommand-DiMCQ-Il.mjs +5 -0
  87. package/dist/packem_shared/runMigrateGenerateCommand-Bd2W9t1k.mjs +11 -0
  88. package/dist/packem_shared/schemaIrToSnapshot-BHuNOfgs.mjs +1 -0
  89. package/dist/packem_shared/shared-D-zCOmgY.mjs +1 -0
  90. package/dist/packem_shared/storage-BLLoZRJa.mjs +1 -0
  91. package/dist/packem_shared/tui-prompts-B3YwUhGw.mjs +4 -0
  92. package/dist/packem_shared/variables-CPYyJtsL.mjs +1 -0
  93. package/dist/packem_shared/vectorize-metadata-BVSF8z2h.mjs +2 -0
  94. package/dist/packem_shared/wrangler-name-Dsk5K1f-.mjs +1 -0
  95. package/dist/packem_shared/wrangler-secrets-DlSlWvRH.mjs +1 -0
  96. package/package.json +44 -19
  97. package/skills/README.md +36 -17
  98. package/skills/lunora/SKILL.md +158 -65
  99. package/skills/lunora-cloudflare-audit/SKILL.md +164 -0
  100. package/skills/lunora-create-package/SKILL.md +97 -84
  101. package/skills/lunora-deploy/SKILL.md +178 -105
  102. package/skills/lunora-functions/SKILL.md +367 -60
  103. package/skills/lunora-migration-helper/SKILL.md +168 -137
  104. package/skills/lunora-performance-audit/SKILL.md +137 -95
  105. package/skills/lunora-quickstart/SKILL.md +136 -136
  106. package/skills/lunora-realtime/SKILL.md +217 -97
  107. package/skills/lunora-setup-auth/SKILL.md +203 -101
  108. package/skills/lunora-setup-hyperdrive/SKILL.md +138 -109
  109. package/skills/lunora-setup-hyperdrive-global/SKILL.md +137 -92
  110. package/skills/lunora-setup-mail/SKILL.md +87 -113
  111. package/skills/lunora-setup-scheduler/SKILL.md +133 -97
  112. package/skills/lunora-setup-storage/SKILL.md +189 -95
  113. package/dist/packem_chunks/planDevCommand.mjs +0 -541
  114. package/dist/packem_chunks/runDeployCommand.mjs +0 -594
  115. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +0 -397
  116. package/dist/packem_shared/COMMANDS-B0ftFD_3.mjs +0 -948
  117. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +0 -244
  118. package/dist/packem_shared/admin-url-4UzT-CI4.mjs +0 -19
  119. package/dist/packem_shared/api-spec-CtA6ilu4.mjs +0 -13
  120. package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +0 -38
  121. package/dist/packem_shared/codegen-error-DJG-ghs_.mjs +0 -31
  122. package/dist/packem_shared/command-D3lB_4Az.mjs +0 -19
  123. package/dist/packem_shared/commands-B-gR09Z_.mjs +0 -845
  124. package/dist/packem_shared/createLogger-B40gPzQo.mjs +0 -78
  125. package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +0 -43
  126. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +0 -61
  127. package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +0 -161
  128. package/dist/packem_shared/docker-hMQ97KSQ.mjs +0 -21
  129. package/dist/packem_shared/insertSchemaExtension-DAqbfr9Z.mjs +0 -64
  130. package/dist/packem_shared/open-url-Dfq6fAyT.mjs +0 -41
  131. package/dist/packem_shared/output-format-wUvAN6AL.mjs +0 -17
  132. package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +0 -56
  133. package/dist/packem_shared/parseManifest--vZf2FY1.mjs +0 -94
  134. package/dist/packem_shared/prompt-cancelled-APzX1Im-.mjs +0 -9
  135. package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +0 -16
  136. package/dist/packem_shared/runAddCommand-bnY6-HKb.mjs +0 -4
  137. package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +0 -79
  138. package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +0 -43
  139. package/dist/packem_shared/storage-BIsph-Vk.mjs +0 -84
  140. package/dist/packem_shared/tui-prompts-BjEN8XgP.mjs +0 -658
  141. package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +0 -12
  142. package/dist/packem_shared/wrangler-secrets-P2_ZUR-k.mjs +0 -47
@@ -1,110 +1,195 @@
1
1
  ---
2
2
  name: lunora-setup-auth
3
- description: Adds authentication to a Lunora app. Use for sign-up/sign-in, email/password,
4
- OAuth (Clerk, Auth0), magic link, or email OTP via `lunora registry add auth`,
5
- wiring the auth handler into the Worker, and gating functions on the session.
3
+ description: Adds authentication to a Lunora app with the `auth` registry item (better-auth via `@lunora/auth`, users and sessions in D1). Covers email/password sign-up and sign-in, Clerk or Auth0 OAuth, magic link, email OTP, copy-in auth screens (`lunora add auth-ui`), wiring `/api/auth/*` and `resolveIdentity` into the Worker, and gating functions on `ctx.auth.userId`. Use when the user asks to "add auth", "add login/sign-in/sign-up", "protect this query", or "add Clerk/Auth0/magic link/OTP", wants to keep an external identity provider (Clerk, WorkOS, Auth0, Supabase, any OIDC JWT) as the session owner, runs `lunora add auth` or `lunora registry add auth`, edits `lunora/auth/index.ts`, or `ctx.auth.userId` is unexpectedly `null`.
6
4
  ---
7
5
 
8
6
  # Lunora Setup Auth
9
7
 
10
- Wire authentication into a Lunora app using the `auth` registry item, which is
11
- built on `@lunora/auth` (a thin wrapper over
12
- [better-auth](https://www.better-auth.com)) with sessions persisted in
13
- `SessionDO` and identity tables in D1.
8
+ The `auth` registry item is built on `@lunora/auth`, a thin wrapper over
9
+ [better-auth](https://www.better-auth.com). Users and sessions are stored in D1.
10
+ Lunora's `SessionDO` is a separate TTL'd token store that `@lunora/auth` never
11
+ calls, so don't wire it for auth. If you need auth state in a Durable Object
12
+ instead of D1, use `LunoraAuthDO` / `.auth({ namespace, internalSecret })`.
14
13
 
15
- ## When to Use
14
+ If the project has no Lunora backend yet, start with `lunora-quickstart`.
16
15
 
17
- - Adding sign-up / sign-in to a Lunora app.
18
- - Adding an OAuth/OIDC provider (Clerk, Auth0), magic link, or email OTP.
19
- - Gating queries/mutations on the signed-in user.
16
+ ## Already have an identity provider? Skip better-auth
20
17
 
21
- ## When Not to Use
18
+ If the app's users sign in through Clerk, WorkOS AuthKit, Auth0, Supabase or any
19
+ OIDC issuer, and that provider should keep owning the session, don't add the
20
+ `auth` item. Verify the provider's JWT instead with `@lunora/jwt-auth`:
22
21
 
23
- - The project has no Lunora backend yet — use `lunora-quickstart` first.
24
- - You only need to read `ctx.auth.userId` in a function and auth is already
25
- installed — just use it.
22
+ ```bash
23
+ lunora init --auth=clerk # new project: or workos | auth0 (wires everything below)
24
+ lunora registry add auth-clerk-jwt # existing project: or auth-workos-jwt | auth-auth0-jwt
25
+ ```
26
+
27
+ ```ts
28
+ import { clerk } from "@lunora/jwt-auth"; // workos, auth0, supabase, jwtIdentityResolver
29
+ import { memoizeIdentity } from "lunorash/runtime";
30
+
31
+ defineApp<Env>()
32
+ .shard((env) => env.SHARD)
33
+ .extend((env) => ({
34
+ resolveIdentity: memoizeIdentity(clerk({ publishableKey: env.CLERK_PUBLISHABLE_KEY ?? "", authorizedParties: env.CLERK_AUTHORIZED_PARTIES ?? "" })),
35
+ }))
36
+ .build();
37
+ ```
38
+
39
+ On the client, create the `LunoraClient` with `authTokenOnUpgrade: true` and,
40
+ once signed in, call `setAuthTokenProvider(client, getToken, { subject: userId })`
41
+ from `lunorash/client`. It refreshes before `exp` and on `onTokenExpired`; map
42
+ the provider's "signed out" error (WorkOS `LoginRequiredError`, Auth0
43
+ `login_required`) to `null`. `authTokenOnUpgrade` is what authenticates
44
+ live-query sockets: the browser offers the bearer as a `Sec-WebSocket-Protocol`
45
+ entry. Without it, a WorkOS or Auth0 socket, or a cross-origin Clerk one, is
46
+ anonymous.
26
47
 
27
- ## Workflow
48
+ Org claims arrive as `orgId` with the org role (`orgRole` for Clerk, `role` /
49
+ `orgRoles` for WorkOS); no preset sets the app-wide `roles` RLS reads, since
50
+ any Clerk user can create an organization and be its admin. Check the org role
51
+ together with `orgId` in a policy. `CLERK_AUTHORIZED_PARTIES` is required.
52
+ Full guide:
53
+ `/docs/concepts/external-auth`, with runnable apps in `examples/clerk-auth` and
54
+ `examples/workos-auth`.
28
55
 
29
- 1. Add the base `auth` item.
30
- 2. Mount the auth request handler in the Worker entry.
31
- 3. Configure env vars and the D1 database.
32
- 4. (Optional) Layer a provider item (Clerk / Auth0 / magic link / OTP) on top.
33
- 5. Gate functions on `ctx.auth.userId`; gate UI with the auth gates/hooks.
56
+ The rest of this skill covers the better-auth path.
34
57
 
35
- ## Step 1: Add the base item
58
+ ## Step 1: Add the item
36
59
 
37
60
  ```bash
38
- lunora registry add auth
61
+ lunora add auth # asks for the provider and D1 database name
62
+ lunora add auth --provider clerk --db my-app-db # non-interactive (auth | clerk | auth0)
63
+ lunora registry add auth # low-level: writes placeholder names
39
64
  ```
40
65
 
41
- This:
66
+ Then run `pnpm install`. The item does the following:
42
67
 
43
- 1. Adds `@lunora/auth`, `@lunora/mail`, and `@lunora/server` to `package.json`
44
- (run `pnpm install` afterwards).
45
- 2. Copies `lunora/auth/index.ts` — the auth instance (`buildAuth` / `getAuth`)
46
- and the `/api/auth/*` request handler (`mountAuth`) — into your project. It is
47
- **yours** to edit.
48
- 3. Adds a D1 `DB` binding to `wrangler.jsonc` (better-auth persists
49
- users/sessions there).
50
- 4. Scaffolds `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, and `MAIL_FROM` into
51
- `.dev.vars`.
68
+ 1. Adds `@lunora/auth`, `@lunora/mail` and `@lunora/server` to `package.json`.
69
+ 2. Copies `lunora/auth/index.ts` into the project. The project owns this file and can edit it. It exports `buildAuth(env)`, `getAuth(env)` (memoized per isolate) and `mountAuth(env, request)`, which routes `/api/auth/*` and returns `undefined` for any other path. The scaffold turns on email/password with `requireEmailVerification: true` and sends verification and reset mail through `@lunora/mail`. It also adds the `uiConfig()` plugin.
70
+ 3. Adds a D1 `DB` binding to `wrangler.jsonc`. The `database_id` is a placeholder.
71
+ 4. Writes `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL` and `MAIL_FROM` to `.dev.vars`.
52
72
 
53
- ## Step 2: Mount the handler
73
+ ## Step 2: Database and env vars
54
74
 
55
- In your Worker entry, route `/api/auth/*` to the scaffolded handler (see the
56
- generated `lunora/auth/index.ts` README block). `createWorker` handles the rest
57
- of the RPC surface; the auth handler owns the better-auth endpoints.
75
+ | Var | Secret | Notes |
76
+ | -------------------- | ------ | --------------------------------------------------------------------------------- |
77
+ | `BETTER_AUTH_SECRET` | yes | At least 32 chars (`openssl rand -base64 32`). Production: `wrangler secret put`. |
78
+ | `BETTER_AUTH_URL` | no | Public base URL. better-auth uses it for callbacks and cookies. |
79
+ | `MAIL_FROM` | no | Sender for verification and reset mail. |
58
80
 
59
- ## Step 3: Env vars and the D1 database
81
+ ```bash
82
+ wrangler d1 create my-app-db # paste the returned id into the DB binding
83
+ lunora doctor # flags a placeholder database_id
84
+ ```
60
85
 
61
- | Var | Secret | Notes |
62
- | -------------------- | ------ | ------------------------------------------------------------------- |
63
- | `BETTER_AUTH_SECRET` | yes | Encryption secret, min 32 chars. `openssl rand -base64 32`. |
64
- | `BETTER_AUTH_URL` | no | Public base URL, e.g. `http://localhost:8787` in dev, your domain. |
65
- | `MAIL_FROM` | no | Sender for verification / reset mail. Captured in the dev Mail tab. |
86
+ Don't declare the better-auth tables (`user`, `session`, `account`,
87
+ `verification`) in `lunora/schema.ts`. better-auth owns them in D1. In dev the
88
+ schema is auto-applied on the first `/api/auth/*` request. For production,
89
+ pre-apply it with `compileMigrationsSql(auth.options)` piped to
90
+ `wrangler d1 execute <db> --remote --file -`. The item README has the script.
91
+ Re-run that migration after adding a plugin that brings its own tables, such as
92
+ `organization` or `twoFactor`.
66
93
 
67
- Create the D1 database and paste its id into the `DB` binding in
68
- `wrangler.jsonc`:
94
+ ## Step 3: Wire the Worker
69
95
 
70
- ```bash
71
- wrangler d1 create my-app-db
96
+ There are two parts. `/api/auth/*` has to reach better-auth. `resolveIdentity`
97
+ has to turn the session cookie into `ctx.auth`. If `resolveIdentity` is missing,
98
+ sign-in still works but `ctx.auth.userId` stays `null` in every function.
99
+
100
+ **Through the generated `defineApp` builder.** This is the default for
101
+ templates. `.auth()` does both parts: it builds the instance lazily, runs
102
+ `ensureMigrated`, dispatches `/api/auth/*` and sets `resolveIdentity`. A
103
+ Vite-first app (`"main": "virtual:lunora/worker"`) has no entry file, so chain
104
+ the call in the `lunora.config.ts` `app` hook. A hand-written entry
105
+ (`src/server.ts`) chains it on `defineApp()` directly:
106
+
107
+ ```ts
108
+ // lunora.config.ts
109
+ import type { AppBuilder, LunoraConfig } from "./lunora/_generated/app";
110
+ // Move the object `buildAuth` passes to `createAuth` (minus `database`; the
111
+ // builder supplies the D1 adapter) into an exported `authOptions(env)` in
112
+ // lunora/auth/index.ts, and have `buildAuth` spread it too.
113
+ import { authOptions } from "./lunora/auth/index.js";
114
+
115
+ interface Env {
116
+ BETTER_AUTH_SECRET: string;
117
+ BETTER_AUTH_URL: string;
118
+ DB: D1Database;
119
+ }
120
+
121
+ export default {
122
+ target: "cloudflare",
123
+ app: (app: AppBuilder<Env>): AppBuilder<Env> =>
124
+ app.auth({
125
+ d1: (env) => env.DB,
126
+ options: (env) => authOptions(env),
127
+ }),
128
+ } satisfies LunoraConfig<Env>;
72
129
  ```
73
130
 
74
- The better-auth schema (user/session/account/verification tables) is **not**
75
- declared in `lunora/schema.ts` — it is managed by better-auth in D1. In dev,
76
- `ensureMigrated(auth)` auto-applies it; in production prefer
77
- `compileMigrationsSql(auth.options)` piped to `wrangler d1 execute`. Run
78
- `lunora doctor` to confirm the `DB` binding has a real `database_id` (not a
79
- placeholder).
131
+ Reuse the scaffold's options rather than retyping a subset: a hand-written
132
+ `{ emailAndPassword: { enabled: true } }` drops `requireEmailVerification`,
133
+ `sendResetPassword` and `sendVerificationEmail`, so users sign in unverified and
134
+ no verification or reset mail goes out.
135
+
136
+ `.auth()` appears on the builder only after codegen sees `@lunora/auth`
137
+ installed, so run `lunora codegen` first.
80
138
 
81
- Verification and password-reset emails are **captured into the Lunora Studio
82
- Mail tab** in dev with zero email setup. For real delivery, `lunora registry add
83
- mail` (adds the `SEND_EMAIL` binding) or set `RESEND_API_KEY`.
139
+ **Hand-rolled `createWorker` entry.** Call the scaffolded `mountAuth` first in
140
+ `fetch`, then hand every other request to the Lunora worker, which resolves the
141
+ identity:
84
142
 
85
- ## Step 4: Add a provider (optional)
143
+ ```ts
144
+ import type { AuthEnv } from "../../lunora/auth/index.js";
145
+ import { getAuth, mountAuth } from "../../lunora/auth/index.js";
146
+
147
+ const worker = createWorker({
148
+ resolveIdentity: async (req, env) => {
149
+ const session = await getAuth(env as AuthEnv).api.getSession({ headers: req.headers });
150
+ return session?.user ? { userId: session.user.id } : null;
151
+ },
152
+ // …
153
+ });
154
+
155
+ export default {
156
+ async fetch(request: Request, env: AuthEnv, ctx: ExecutionContext): Promise<Response> {
157
+ const authResponse = await mountAuth(env, request);
158
+ if (authResponse) return authResponse;
159
+ return worker.fetch(request, env, ctx); // RPC, WebSocket, everything else
160
+ },
161
+ };
162
+ ```
86
163
 
87
- Each provider item builds on the base `auth` item (`requires: ["auth"]`):
164
+ ## Step 4: Providers and add-ons (optional)
165
+
166
+ Each of these items requires the base `auth` item and installs it if it is
167
+ missing:
88
168
 
89
169
  ```bash
90
- lunora registry add auth-clerk # Clerk via better-auth genericOAuth
91
- lunora registry add auth-auth0 # Auth0 via better-auth genericOAuth
92
- lunora registry add auth-magic-link # passwordless magic-link (mail)
93
- lunora registry add auth-otp # passwordless email one-time-password
170
+ lunora add auth-clerk # Clerk OIDC (genericOAuth): CLERK_CLIENT_ID / _SECRET / CLERK_ISSUER_URL
171
+ lunora add auth-auth0 # Auth0 OIDC: AUTH0_CLIENT_ID / _SECRET / AUTH0_DOMAIN
172
+ lunora add auth-magic-link # passwordless link, sent via @lunora/mail
173
+ lunora add auth-otp # email one-time code, sent via @lunora/mail
174
+ lunora add auth-emails # styled React templates for the auth mails (renderEmail)
175
+ lunora add auth-ui # copy-in screens; framework auto-detected (no React Native port)
94
176
  ```
95
177
 
96
- Add the base `auth` item first (or let the registry resolve the `requires`
97
- dependency). OAuth items need the provider's client id/secret added to
98
- `.dev.vars`.
178
+ The provider items copy a file into `lunora/auth/` but don't register it
179
+ anywhere. Add the plugin to the `plugins` array yourself, in `buildAuth` or in
180
+ `.auth({ options })`. For example, `plugins: [uiConfig(), clerk(env)]` with
181
+ `import { clerk } from "./clerk.js"`. The other exports are `auth0`,
182
+ `magicLinkPlugin` and `emailOtpPlugin`. The OAuth callback URL is
183
+ `<BETTER_AUTH_URL>/api/auth/oauth2/callback/<clerk|auth0>`.
99
184
 
100
185
  ## Step 5: Use the session
101
186
 
102
187
  ### In functions
103
188
 
104
- The runtime resolves the session and exposes the user on every context:
105
-
106
189
  ```ts
107
- import { LunoraError, mutation, v } from "@lunora/server";
190
+ import { LunoraError } from "lunorash/server";
191
+
192
+ import { mutation, v } from "#lunora/_generated/server.js";
108
193
 
109
194
  export const createDocument = mutation.input({ title: v.string() }).mutation(async ({ ctx, args: { title } }) => {
110
195
  if (!ctx.auth.userId) {
@@ -114,27 +199,41 @@ export const createDocument = mutation.input({ title: v.string() }).mutation(asy
114
199
  });
115
200
  ```
116
201
 
117
- For richer checks (org membership, roles), compose `withAuthPlugins(auth)` and
118
- call the better-auth server API — see the scaffolded `lunora/auth/index.ts`.
202
+ `ctx.auth.getIdentity()` returns the full session claims, such as email and
203
+ name. For org or role checks, compose `withAuthPlugins(auth)` and call the
204
+ better-auth server API.
205
+
206
+ ### In the client
119
207
 
120
- ### In the UI (React)
208
+ The default is a better-auth **cookie session**. `LunoraClient` needs no token,
209
+ so don't call `setToken` / `setAuthToken` for it. Sign in with the better-auth
210
+ client. Build that client with `createLunoraAuthClient`, which installs
211
+ `lunoraSessionSync()`. That plugin tells every open `LunoraClient`, including
212
+ ones in other tabs, to re-resolve the session after sign-in or sign-out. Without
213
+ it, live queries keep serving the previous user's rows. If you call
214
+ `createAuthClient` yourself, add `lunoraSessionSync()` to its plugins.
121
215
 
122
216
  ```tsx
123
- import { Authenticated, Unauthenticated, useAuth } from "@lunora/react";
217
+ import { createLunoraAuthClient } from "@lunora/auth/plugins/client";
218
+ import { Authenticated, AuthLoading, Unauthenticated, useAuth } from "@lunora/react";
219
+ import { createAuthClient } from "better-auth/react";
124
220
 
125
- function Account() {
126
- const { user, signIn, signOut } = useAuth();
221
+ export const authClient = createLunoraAuthClient(createAuthClient);
222
+
223
+ function Account({ email, password }: { email: string; password: string }) {
224
+ const { user } = useAuth(); // { setToken, status, token, user }
127
225
 
128
226
  return (
129
227
  <>
228
+ <AuthLoading>…</AuthLoading>
130
229
  <Authenticated>
131
230
  <span>Signed in as {user?.email}</span>
132
- <button type="button" onClick={() => signOut()}>
231
+ <button type="button" onClick={() => authClient.signOut()}>
133
232
  Sign out
134
233
  </button>
135
234
  </Authenticated>
136
235
  <Unauthenticated>
137
- <button type="button" onClick={() => signIn()}>
236
+ <button type="button" onClick={() => authClient.signIn.email({ email, password })}>
138
237
  Sign in
139
238
  </button>
140
239
  </Unauthenticated>
@@ -143,28 +242,31 @@ function Account() {
143
242
  }
144
243
  ```
145
244
 
146
- `@lunora/react` also exports `AuthLoading` and `useAuthState` for the loading
147
- window before the session resolves.
148
-
149
- ## Common Pitfalls
150
-
151
- 1. **Declaring better-auth tables in `lunora/schema.ts`.** They live in D1 and
152
- are managed by better-auth — do not add them to `defineSchema`.
153
- 2. **Placeholder `database_id`.** The `DB` binding ships with a placeholder;
154
- `wrangler d1 create` + paste the id, then `lunora doctor` to confirm.
155
- 3. **Missing/short `BETTER_AUTH_SECRET`.** better-auth needs ≥32 chars;
156
- `@lunora/auth` surfaces a clear error when it is absent.
157
- 4. **Expecting prod email to "just work".** Dev captures mail into the Studio;
158
- production needs `mail` (the `SEND_EMAIL` binding) or `RESEND_API_KEY` and a
159
- verified sender domain.
160
-
161
- ## Checklist
162
-
163
- - [ ] `lunora registry add auth` run, `pnpm install` done.
164
- - [ ] Auth handler mounted in the Worker entry.
165
- - [ ] `BETTER_AUTH_SECRET` / `BETTER_AUTH_URL` / `MAIL_FROM` set in `.dev.vars`.
166
- - [ ] D1 database created and its id pasted into the `DB` binding (`lunora
167
- doctor` clean).
168
- - [ ] Provider item added if needed (Clerk / Auth0 / magic link / OTP).
169
- - [ ] Functions gate on `ctx.auth.userId`; UI uses the auth gates/`useAuth`.
170
- - [ ] Verified sign-in → session → an authenticated query round-trip.
245
+ A client plugin toggle such as `{ plugins: { twoFactor: true } }` only adds
246
+ the client half. Register the matching server plugin (`twoFactor()` from
247
+ `@lunora/auth/plugins`) in `buildAuth`'s `plugins` too, or its endpoints don't
248
+ exist.
249
+
250
+ `setToken` is only for bearer-token setups, such as React Native or a JWT from
251
+ an external IdP.
252
+
253
+ Branch on `status`, not on `user === null`. `status` takes one of four values:
254
+ `"unauthenticated"`, `"loading"`, `"authenticated"` or `"unreachable"`. The gates
255
+ in `@lunora/vue`, `@lunora/solid`, `@lunora/svelte` and `@lunora/angular` use the
256
+ same four. `"unreachable"` means a credential is held but the session endpoint
257
+ failed, for example on an offline reload. The gates treat it as authenticated,
258
+ yet `user` can still be `null` at that point. A UI that reads that `null` as
259
+ "signed out" shows a signed-in user the signed-out screen. The full contract is
260
+ documented on `AuthStatus` in `@lunora/client/auth`.
261
+
262
+ ## Pitfalls
263
+
264
+ - **Sign-in rejected right after sign-up.** The scaffold sets `requireEmailVerification: true`, so the user has to open the verification link first. In dev, that link is in the Studio Mail tab.
265
+ - **Mail in production.** Dev captures mail into the Studio. In production with no `MAIL_FROM`, the scaffold throws rather than logging the link. Auth links are bearer credentials, so this is intentional. Production needs `lunora add email` (the `SEND_EMAIL` binding) or `RESEND_API_KEY`, plus a verified sender domain. See `lunora-setup-mail`.
266
+ - **Unauthenticated storage or other items.** Items that call `requireOwner` throw `UNAUTHORIZED` until `resolveIdentity` is wired (Step 3).
267
+
268
+ ## Verify
269
+
270
+ 1. Run `lunora doctor`. It should report no placeholder `database_id`.
271
+ 2. Run `lunora dev`. Sign up, open the verification link from the Studio Mail tab, then sign in.
272
+ 3. Call a query that returns `ctx.auth.userId`. It should return the user's id, not `null`.