create-questpie 2.0.2 → 2.0.4

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 (40) hide show
  1. package/dist/index.mjs +244 -30
  2. package/package.json +1 -1
  3. package/skills/questpie/AGENTS.md +310 -103
  4. package/skills/questpie/SKILL.md +196 -84
  5. package/skills/questpie/coverage.json +213 -0
  6. package/skills/questpie/references/auth.md +119 -4
  7. package/skills/questpie/references/business-logic.md +126 -56
  8. package/skills/questpie/references/crud-api.md +231 -29
  9. package/skills/questpie/references/data-modeling.md +26 -6
  10. package/skills/questpie/references/extend.md +98 -7
  11. package/skills/questpie/references/field-types.md +14 -2
  12. package/skills/questpie/references/infrastructure-adapters.md +207 -32
  13. package/skills/questpie/references/mcp.md +147 -0
  14. package/skills/questpie/references/multi-tenancy.md +1 -2
  15. package/skills/questpie/references/production.md +218 -53
  16. package/skills/questpie/references/quickstart.md +31 -18
  17. package/skills/questpie/references/rules.md +140 -13
  18. package/skills/questpie/references/sandbox.md +110 -0
  19. package/skills/questpie/references/tanstack-query.md +34 -11
  20. package/skills/questpie/references/type-inference.md +167 -0
  21. package/skills/questpie/references/workflows.md +155 -0
  22. package/skills/questpie-admin/AGENTS.md +141 -68
  23. package/skills/questpie-admin/SKILL.md +96 -63
  24. package/skills/questpie-admin/references/blocks.md +28 -4
  25. package/skills/questpie-admin/references/custom-ui.md +1 -1
  26. package/skills/questpie-admin/references/views.md +21 -5
  27. package/templates/tanstack-start/AGENTS.md +15 -8
  28. package/templates/tanstack-start/CLAUDE.md +12 -5
  29. package/templates/tanstack-start/README.md +7 -6
  30. package/templates/tanstack-start/package.json +1 -0
  31. package/templates/tanstack-start/src/lib/query-client.ts +10 -1
  32. package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
  33. package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
  34. package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
  35. package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
  36. package/templates/tanstack-start/src/questpie/server/questpie.config.ts +2 -1
  37. package/templates/tanstack-start/src/routes/admin/$.tsx +12 -1
  38. package/templates/tanstack-start/src/routes/admin/index.tsx +12 -5
  39. package/templates/tanstack-start/src/routes/api/$.ts +1 -2
  40. package/templates/tanstack-start/vite.config.ts +2 -2
@@ -13,11 +13,11 @@ The QUESTPIE admin panel is a **projection of your server schema** — not the f
13
13
 
14
14
  ## Reference Topics
15
15
 
16
- | Topic | File | Covers |
17
- |---|---|---|
18
- | Views | `references/views.md` | List views, form views, dashboard, sidebar, filters, bulk actions, visibility, history |
19
- | Blocks | `references/blocks.md` | Block definitions, fields, prefetch, renderers, block picker |
20
- | Custom UI | `references/custom-ui.md` | Custom fields, custom views, registries, reactive fields, widgets |
16
+ | Topic | File | Covers |
17
+ | --------- | ------------------------- | -------------------------------------------------------------------------------------- |
18
+ | Views | `references/views.md` | List views, form views, dashboard, sidebar, filters, bulk actions, visibility, history |
19
+ | Blocks | `references/blocks.md` | Block definitions, fields, prefetch, renderers, block picker |
20
+ | Custom UI | `references/custom-ui.md` | Custom fields, custom views, registries, reactive fields, widgets |
21
21
 
22
22
  ## Full Compiled Document
23
23
 
@@ -29,7 +29,8 @@ For the complete admin reference with all topics expanded: `AGENTS.md`
29
29
  - **@base-ui/react** primitives (NOT @radix-ui)
30
30
  - **@iconify/react** with Phosphor icon set (`ph:icon-name`)
31
31
  - **sonner** for toasts — `toast.error()`, `toast.success()`
32
- - Brutalist flat design: `--radius: 0px`, no shadows
32
+ - QUESTPIE Neutral Design: flat surfaces, soft neutral geometry,
33
+ tokenized radius, and restrained floating shadows
33
34
 
34
35
  ## Setup
35
36
 
@@ -42,8 +43,7 @@ bun add @questpie/admin
42
43
  ### 2. Runtime Config
43
44
 
44
45
  ```ts title="questpie.config.ts"
45
- import { runtimeConfig } from "questpie";
46
-
46
+ import { runtimeConfig } from "questpie/app";
47
47
  export default runtimeConfig({
48
48
  app: { url: process.env.APP_URL || "http://localhost:3000" },
49
49
  db: { url: process.env.DATABASE_URL },
@@ -56,7 +56,8 @@ The admin module contributes the codegen plugin automatically. It discovers `con
56
56
  ### 3. Modules
57
57
 
58
58
  ```ts title="modules.ts"
59
- import { adminModule, auditModule } from "@questpie/admin/server";
59
+ import { adminModule } from "@questpie/admin/modules/admin";
60
+ import { auditModule } from "@questpie/admin/modules/audit";
60
61
 
61
62
  export default [adminModule, auditModule] as const;
62
63
  ```
@@ -66,6 +67,26 @@ export default [adminModule, auditModule] as const;
66
67
  | `adminModule` | User collection, auth pages, admin UI |
67
68
  | `auditModule` | Audit log collection, timeline widget |
68
69
 
70
+ ### Auth/User Contract - Critical
71
+
72
+ `adminModule` includes the starter auth model and owns the canonical Better Auth `user` collection shape used by admin setup and login guards. That contract includes `user.role` with at least `admin` and `user` values. The built-in setup route checks for `role = "admin"`, and the admin `AuthGuard` expects `session.user.role === "admin"`.
73
+
74
+ Do not create a replacement `collection("user")` from scratch in an app that uses `adminModule`. If the app needs to customize the user admin UI or add fields, merge the starter user collection and extend it:
75
+
76
+ ```ts title="collections/user.ts"
77
+ import { starterModule } from "questpie/app";
78
+ import { collection } from "#questpie/factories";
79
+
80
+ export default collection("user")
81
+ .merge(starterModule.collections.user)
82
+ .fields(({ f }) => ({
83
+ // add app-specific user fields here
84
+ internalNotes: f.textarea(),
85
+ }));
86
+ ```
87
+
88
+ App-specific authorization tables are fine, but they must not replace or remove the admin user contract unless the app also replaces the built-in admin setup route and auth guard.
89
+
69
90
  ### 4. Admin Config
70
91
 
71
92
  ```ts title="config/admin.ts"
@@ -138,44 +159,31 @@ export default adminConfig({
138
159
 
139
160
  The admin uses CSS variables for all theming. Override them in your own CSS file.
140
161
 
141
- ### Light Theme (`:root`)
142
-
143
- | Variable | Default | Purpose |
144
- | ---------------------- | --------- | -------------------------------- |
145
- | `--background` | `#FFFFFF` | Page background |
146
- | `--foreground` | `#0A0A0A` | Primary text |
147
- | `--card` | `#F8F8F8` | Cards, panels, sidebar |
148
- | `--popover` | `#FFFFFF` | Dropdowns, tooltips, dialogs |
149
- | `--muted` | `#F0F0F0` | Hover states, table headers |
150
- | `--muted-foreground` | `#666666` | Secondary text, placeholders |
151
- | `--primary` | `#B700FF` | Brand accent (CTAs, focus rings) |
152
- | `--primary-foreground` | `#FFFFFF` | Text on primary backgrounds |
153
- | `--destructive` | `#FF3D57` | Errors, delete actions |
154
- | `--success` | `#00E676` | Positive states |
155
- | `--warning` | `#FFB300` | Caution states |
156
- | `--info` | `#40C4FF` | Informational emphasis |
157
- | `--border` | `#E0E0E0` | All structural borders |
158
- | `--ring` | `#B700FF` | Focus ring color |
159
- | `--radius` | `0px` | Border radius (0 = brutalist) |
160
-
161
- ### Dark Theme (`.dark` class)
162
-
163
- Dark mode uses the `.dark` class on the root element. Key overrides:
164
-
165
- | Variable | Dark Value |
166
- | -------------- | ---------- |
167
- | `--background` | `#0A0A0A` |
168
- | `--foreground` | `#FFFFFF` |
169
- | `--card` | `#111111` |
170
- | `--border` | `#333333` |
171
- | `--muted` | `#1A1A1A` |
162
+ The full source of truth is `packages/admin/DESIGN.md`. Key defaults:
163
+
164
+ | Role | Dark | Light |
165
+ | ------------- | --------- | --------- |
166
+ | Background | `#121212` | `#fafafa` |
167
+ | Foreground | `#ececec` | `#1c1c1c` |
168
+ | Card | `#1b1b1b` | `#ffffff` |
169
+ | Surface high | `#2a2a2a` | `#e8e8e8` |
170
+ | Border | `#343434` | `#e2e2e2` |
171
+ | Border subtle | `#262626` | `#ebebeb` |
172
+ | Brand primary | `#b700ff` | `#b700ff` |
173
+
174
+ | Token | Default | Use |
175
+ | ------------------------ | ------- | ------------------------------------------- |
176
+ | `--control-radius-inner` | `8px` | Icon buttons/actions nested inside controls |
177
+ | `--control-radius` | `12px` | Inputs, selects, buttons, compact controls |
178
+ | `--surface-radius` | `14px` | Cards, panels, grouped fields, docs blocks |
179
+ | `--floating-radius` | `14px` | Menus, popovers, dialogs, command panels |
172
180
 
173
181
  ### Typography
174
182
 
175
183
  | Variable | Value |
176
184
  | ------------- | ------------------------------------------------------------------- |
177
- | `--font-sans` | `"Geist Variable"` — body text, descriptions |
178
- | `--font-mono` | `"JetBrains Mono Variable"` — UI chrome: nav, buttons, tabs, badges |
185
+ | `--font-sans` | `"Geist Variable"` — UI, prose, headings, labels, navigation |
186
+ | `--font-mono` | `"JetBrains Mono Variable"` — code, file paths, commands, IDs |
179
187
 
180
188
  ### Sidebar Variables
181
189
 
@@ -193,8 +201,7 @@ Separate tokens for independent sidebar theming: `--sidebar`, `--sidebar-foregro
193
201
  When collections have `.localized()` fields, the admin shows a locale switcher:
194
202
 
195
203
  ```ts title="config/app.ts"
196
- import { appConfig } from "questpie";
197
-
204
+ import { appConfig } from "questpie/app";
198
205
  export default appConfig({
199
206
  locale: {
200
207
  locales: [
@@ -222,9 +229,9 @@ The admin renders drag-and-drop upload, image preview, file info, and remove but
222
229
 
223
230
  ## Live Preview
224
231
 
225
- Live Preview uses a split-screen iframe. The current implementation refreshes the iframe after save/autosave and uses `postMessage` for field/block focus sync.
232
+ Live Preview is one system: the existing collection `FormView`, Preview button, `LivePreviewMode`, and frontend iframe. Preserve the normal form lifecycle. Do not introduce a separate visual-edit form API, a second default form view, or a parallel preview surface.
226
233
 
227
- Preview V2 patch-based docs are design notes until `useQuestpiePreview`, `PreviewRoot`, and `PreviewBlock` are exported.
234
+ The admin form is authoritative. The iframe mirrors form state through `postMessage`, supports field/block focus, and may request inline scalar edits. Persistence still goes through existing save, autosave, Cmd+S, history, workflow, locks, and actions.
228
235
 
229
236
  ### Server Config
230
237
 
@@ -248,35 +255,57 @@ export const pages = collection("pages")
248
255
  });
249
256
  ```
250
257
 
251
- Current preview refreshes the iframe after save/autosave and supports field focus through `postMessage`.
258
+ Preview opens the existing split-screen editor. Patches and refresh/resync messages update the iframe mirror; save/autosave still writes through the form.
252
259
 
253
- ### Frontend Integration
260
+ ### Prepare a Frontend Page
254
261
 
255
- Use `useCollectionPreview` with `PreviewProvider` and `PreviewField`:
262
+ Use exported APIs only: `useCollectionPreview`, `PreviewProvider`, `PreviewField`, and `BlockRenderer`.
263
+
264
+ Checklist:
265
+
266
+ 1. Configure `.preview({ url })` on the collection.
267
+ 2. Load the same record shape the page normally renders.
268
+ 3. For workflow-published pages, public reads use `stage: "published"`; if the public client/HTTP API is exposed, anonymous read access also checks `input?.stage === "published"`. Authorized preview/draft reads can load the working record.
269
+ 4. Call `useCollectionPreview({ initialData, onRefresh })` in the page renderer.
270
+ 5. Wrap the visual output in `PreviewProvider`.
271
+ 6. Render from `preview.data`, not the original loader object.
272
+ 7. Wrap editable scalar text with `PreviewField field="..." editable="text" | "textarea"`.
273
+ 8. Render block content with `BlockRenderer`; pass `selectedBlockId` and `onBlockClick`.
274
+ 9. Keep add/remove/reorder/nesting block operations in the existing block editor.
256
275
 
257
276
  ```tsx
258
277
  import {
278
+ BlockRenderer,
259
279
  PreviewField,
260
280
  PreviewProvider,
261
281
  useCollectionPreview,
262
282
  } from "@questpie/admin/client";
283
+ import admin from "@/questpie/admin/.generated/client";
263
284
 
264
- function PagePreview({ initialData }) {
285
+ function PagePreview({ page }) {
265
286
  const router = useRouter();
266
287
  const preview = useCollectionPreview({
267
- initialData,
288
+ initialData: page,
268
289
  onRefresh: () => router.invalidate(),
269
290
  });
270
291
 
271
292
  return (
272
- <PreviewProvider
273
- isPreviewMode={preview.isPreviewMode}
274
- focusedField={preview.focusedField}
275
- onFieldClick={preview.handleFieldClick}
276
- >
277
- <PreviewField field="title" as="h1">
278
- {preview.data.title}
279
- </PreviewField>
293
+ <PreviewProvider preview={preview}>
294
+ <main className={preview.isPreviewMode ? "questpie-preview" : undefined}>
295
+ <PreviewField field="title" editable="text" as="h1">
296
+ {preview.data.title}
297
+ </PreviewField>
298
+
299
+ <BlockRenderer
300
+ content={preview.data.content}
301
+ data={preview.data.content?._data}
302
+ renderers={admin.blocks}
303
+ selectedBlockId={preview.selectedBlockId}
304
+ onBlockClick={
305
+ preview.isPreviewMode ? preview.handleBlockClick : undefined
306
+ }
307
+ />
308
+ </main>
280
309
  </PreviewProvider>
281
310
  );
282
311
  }
@@ -284,11 +313,15 @@ function PagePreview({ initialData }) {
284
313
 
285
314
  ### Key Principles
286
315
 
287
- - Current preview = save/autosave refresh plus field/block focus sync
288
- - `useCollectionPreview` sends `PREVIEW_READY`, `FIELD_CLICKED`, and `BLOCK_CLICKED`
316
+ - Keep `FormView`, the Preview button, and `LivePreviewMode`
317
+ - Never add a separate visual-edit form API, a second default form view, or parallel preview API names
318
+ - Preserve save/autosave/Cmd+S/history/workflow/locks/actions
319
+ - `useCollectionPreview` handles preview mode, mirrored data, refresh/resync, and focus state
289
320
  - `PreviewProvider` supplies preview context to `PreviewField`
290
- - Each message carries `sessionId`, `seq`, `timestamp`, `protocolVersion`
291
- - Preview wrappers must prevent accidental navigation in the iframe
321
+ - `PreviewField` annotates scalar fields and can opt into inline editing with `editable`
322
+ - `BlockRenderer` preserves block IDs and block scopes for block annotations
323
+ - Use `BlockScopeProvider` only for custom/manual block rendering outside `BlockRenderer`
324
+ - Validate all iframe messages before updating form state
292
325
 
293
326
  ## History & Versions
294
327
 
@@ -281,11 +281,35 @@ function PageRenderer({ page }) {
281
281
 
282
282
  ## Blocks in Live Preview
283
283
 
284
- When a collection has `.preview()` configured, blocks can participate in preview focus by combining `BlockScopeProvider` with `PreviewField`.
284
+ When a collection has `.preview()` configured, blocks participate in the existing Live Preview system through `BlockRenderer` and `PreviewField`. The form remains authoritative; block annotations only mirror values, focus fields, select blocks, or request inline scalar edits.
285
285
 
286
- ### BlockScopeProvider Wrapper
286
+ ### Preferred: BlockRenderer
287
287
 
288
- Use `BlockScopeProvider` in your frontend to scope field paths inside a block:
288
+ Use `BlockRenderer` for normal frontend page rendering. It preserves `data-block-id`, routes block selection, and scopes nested `PreviewField` paths automatically.
289
+
290
+ ```tsx
291
+ <BlockRenderer
292
+ content={preview.data.content}
293
+ data={preview.data.content?._data}
294
+ renderers={admin.blocks}
295
+ selectedBlockId={preview.selectedBlockId}
296
+ onBlockClick={preview.isPreviewMode ? preview.handleBlockClick : undefined}
297
+ />
298
+ ```
299
+
300
+ Inside custom block renderers, annotate scalar values with `PreviewField`:
301
+
302
+ ```tsx
303
+ <PreviewField field="title" editable="text" as="h2">
304
+ {values.title}
305
+ </PreviewField>
306
+ ```
307
+
308
+ This resolves to `content._values.{blockId}.title` when rendered inside `BlockRenderer`.
309
+
310
+ ### Manual BlockScopeProvider Wrapper
311
+
312
+ Use `BlockScopeProvider` only when you manually render blocks outside `BlockRenderer`:
289
313
 
290
314
  ```tsx
291
315
  import { BlockScopeProvider } from "@questpie/admin/client";
@@ -304,4 +328,4 @@ function PageRenderer({ blocks, previewData }) {
304
328
 
305
329
  `PreviewField` components inside the provider resolve paths like `content._values.{blockId}.title`.
306
330
 
307
- Blocks with declarative prefetch (`{ with: { image: true } }`) resolve relations during reconcile the preview shows the image URL immediately after the server round-trip completes, not just the asset ID.
331
+ Inline block edits target `_values`, for example `content._values.<blockId>.title`. Tree edits such as add, remove, reorder, and nesting stay in the existing block editor and should trigger refresh or resync when patching is not safe.
@@ -302,4 +302,4 @@ toast.error("Failed to save");
302
302
 
303
303
  6. **MEDIUM: Using `@phosphor-icons/react` or `lucide-react`** — use `@iconify/react` with `ph:` prefix for all icons.
304
304
 
305
- 7. **LOW: Not using shadcn components** — prefer `<Button>`, `<Card>`, `<Input>` from the shadcn component library instead of raw HTML elements. The admin has a consistent brutalist design system.
305
+ 7. **LOW: Not using shadcn components** — prefer `<Button>`, `<Card>`, `<Input>` from the shadcn component library instead of raw HTML elements. The admin follows QUESTPIE Neutral Design.
@@ -161,7 +161,7 @@ Section-level visibility:
161
161
  {
162
162
  type: "section",
163
163
  label: { en: "SEO" },
164
- hidden: ({ data }) => !data.isPublished,
164
+ hidden: ({ data }) => !data.showSeo,
165
165
  fields: [f.metaTitle, f.metaDescription],
166
166
  }
167
167
  ```
@@ -404,7 +404,7 @@ export const logs = collection("logs")
404
404
 
405
405
  ## Form Views and Live Preview
406
406
 
407
- Form views connect to the Live Preview V2 system when the collection has `.preview()` configured. The form editor becomes the source of `postMessage` patches every field change emits a patch through the bus, giving the preview iframe instant updates.
407
+ Form views connect to the existing Live Preview system when the collection has `.preview()` configured. Keep `v.collectionForm()` as the form surface; do not introduce a separate visual-edit form API, a second default form view, or parallel preview API names. The form editor remains the source of patches, refreshes, commits, and resyncs.
408
408
 
409
409
  ### Enabling Preview on a Collection
410
410
 
@@ -428,6 +428,22 @@ export const pages = collection("pages")
428
428
  ### How It Works
429
429
 
430
430
  1. The form view detects `.preview()` config and opens a split-screen layout
431
- 2. Save/autosave sends a `PREVIEW_REFRESH` message to the preview iframe
432
- 3. The preview page handles refreshes through `useCollectionPreview({ initialData, onRefresh })`
433
- 4. `PreviewProvider` and `PreviewField` wire field focus and click-to-focus messages
431
+ 2. The preview iframe mirrors form state with snapshot, patch, refresh, commit, and resync messages
432
+ 3. Save/autosave/Cmd+S/history/workflow/locks/actions stay in the form lifecycle
433
+ 4. The preview page uses `useCollectionPreview({ initialData, onRefresh })`
434
+ 5. `PreviewProvider`, `PreviewField`, and `BlockRenderer` wire field and block annotations
435
+
436
+ ### Frontend Preparation Checklist
437
+
438
+ Use this checklist before expecting visual editing to work:
439
+
440
+ 1. The collection has `.preview({ url })`.
441
+ 2. The page loader returns the complete rendered record shape.
442
+ 3. Public workflow reads use `stage: "published"`; if public client/HTTP access is enabled, anonymous read access also requires `input?.stage === "published"`. Preview/draft-mode reads load the working record for authorized editors.
443
+ 4. The page renderer calls `useCollectionPreview({ initialData, onRefresh })`.
444
+ 5. The rendered page is wrapped in `PreviewProvider preview={preview}`.
445
+ 6. UI reads from `preview.data`, not directly from loader data.
446
+ 7. Scalar text uses `PreviewField field="..." editable="text" | "textarea"` when inline editing is expected.
447
+ 8. Blocks render through `BlockRenderer` with `selectedBlockId` and `onBlockClick`.
448
+ 9. `BlockScopeProvider` is only needed for custom/manual block rendering outside `BlockRenderer`.
449
+ 10. Add/remove/reorder/nesting block operations stay in the existing block editor.
@@ -124,6 +124,12 @@ src/
124
124
  - **`questpie.config.ts`** — CLI config (migration directory, app reference).
125
125
  - **`src/routes/api/$.ts`** — API catch-all handler. Serves REST + OpenAPI docs at `/api/docs`.
126
126
 
127
+ ### Admin User Contract
128
+
129
+ `adminModule` includes the starter auth model and owns the canonical Better Auth `user` collection shape used by admin setup and login guards. That contract includes `user.role` (`admin` or `user`).
130
+
131
+ Do **not** create a replacement `collection("user")` from scratch. If you need custom user fields or admin layout, merge `starterModule.collections.user` or `adminModule.collections.user` and extend it. Replacing the user collection breaks admin setup/login.
132
+
127
133
  ## How To Write Code
128
134
 
129
135
  ### Creating a Collection
@@ -160,7 +166,7 @@ export const posts = collection("posts")
160
166
  { value: "tutorial", label: "Tutorial" },
161
167
  ])
162
168
  .label("Category")
163
- author: f.relation("users").label("Author"),
169
+ author: f.relation("user").label("Author"),
164
170
  image: f.upload().label("Cover Image"),
165
171
  }))
166
172
  .title(({ f }) => f.title)
@@ -194,12 +200,12 @@ Then (preferred):
194
200
 
195
201
  1. Use `bun questpie add collection <name>` to scaffold files
196
202
  2. Codegen runs automatically
197
- 3. Run `bun run migrate:create` to generate migration
203
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
198
204
 
199
205
  Manual workflow (if you create files yourself):
200
206
 
201
207
  1. Run `bun run questpie:generate` to regenerate `.generated/index.ts`
202
- 2. Run `bun run migrate:create` to generate migration
208
+ 2. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
203
209
 
204
210
  Collections are auto-discovered by codegen — no manual registration needed.
205
211
 
@@ -233,12 +239,12 @@ Then (preferred):
233
239
 
234
240
  1. Use `bun questpie add global <name>` to scaffold files
235
241
  2. Codegen runs automatically
236
- 3. Run `bun run migrate:create`
242
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
237
243
 
238
244
  Manual workflow (if you create files yourself):
239
245
 
240
246
  1. Run `bun run questpie:generate` to regenerate `.generated/index.ts`
241
- 2. Run `bun run migrate:create`
247
+ 2. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
242
248
 
243
249
  Globals are auto-discovered by codegen — no manual registration needed.
244
250
 
@@ -250,7 +256,7 @@ Routes are defined as standalone files in `routes/` and auto-discovered by codeg
250
256
 
251
257
  ```ts
252
258
  // src/questpie/server/routes/get-stats.ts
253
- import { route } from "questpie";
259
+ import { route } from "questpie/services";
254
260
  import { z } from "zod";
255
261
 
256
262
  export default route()
@@ -384,7 +390,7 @@ If your blocks only use declarative prefetch (`{ with: { field: true } }`), you
384
390
 
385
391
  Fields support reactive behaviors in `meta.admin`:
386
392
 
387
- - **`hidden`**: Conditionally hide — `({ data }: { data: Record<string, any> }) => !data.isPublished`
393
+ - **`hidden`**: Conditionally hide — `({ data }: { data: Record<string, unknown> }) => !data.isPublished`
388
394
  - **`readOnly`**: Make read-only based on conditions
389
395
  - **`disabled`**: Disable conditionally
390
396
  - **`compute`**: Auto-compute values — `{ handler, deps, debounce }`
@@ -457,7 +463,7 @@ export const {
457
463
 
458
464
  ```ts
459
465
  // src/routes/api/$.ts
460
- import { createFetchHandler } from "questpie";
466
+ import { createFetchHandler } from "questpie/http";
461
467
  import { app } from "#questpie";
462
468
 
463
469
  const handler = createFetchHandler(app, { basePath: "/api" });
@@ -498,6 +504,7 @@ Optional (with defaults):
498
504
  bun dev # Start dev server
499
505
  bun build # Build for production
500
506
  bun start # Start production server
507
+ bun run db:push # Push schema to local dev database
501
508
  bun run migrate # Run database migrations
502
509
  bun run migrate:create # Create new migration
503
510
  bun run routes:generate # Regenerate TanStack Router route tree
@@ -15,6 +15,7 @@ This is a [QUESTPIE](https://questpie.com) project scaffolded with `create-quest
15
15
  | `bun run questpie:generate` | Regenerate .generated/index.ts |
16
16
  | `bun run scaffold:generate` | Regenerate route tree and QUESTPIE output |
17
17
  | `bun run scaffold:verify` | Regenerate codegen and type-check |
18
+ | `bun run db:push` | Push schema to local dev database |
18
19
  | `bun run migrate:create` | Generate a migration from schema diff |
19
20
  | `bun run migrate` | Run pending migrations |
20
21
  | `bun questpie seed` | Run pending seeds |
@@ -62,6 +63,12 @@ src/questpie/
62
63
  - **`questpie.config.ts`** — CLI config (migration directory, app reference).
63
64
  - **`src/routes/api/$.ts`** — API catch-all handler. Serves REST + OpenAPI docs at `/api/docs`.
64
65
 
66
+ ## Admin User Contract
67
+
68
+ `adminModule` includes the starter auth model and owns the canonical Better Auth `user` collection shape used by admin setup and login guards. That contract includes `user.role` (`admin` or `user`).
69
+
70
+ Do **not** create a replacement `collection("user")` from scratch. If you need custom user fields or admin layout, merge `starterModule.collections.user` or `adminModule.collections.user` and extend it. Replacing the user collection breaks admin setup/login.
71
+
65
72
  ## Environment Variables
66
73
 
67
74
  Defined in `src/lib/env.ts` with runtime validation. See `.env.example` for all available variables.
@@ -84,7 +91,7 @@ Preferred workflow:
84
91
 
85
92
  1. Run `bun questpie add collection my-thing`
86
93
  2. The CLI creates the file and auto-runs codegen
87
- 3. Run `bun run migrate:create`
94
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
88
95
 
89
96
  Manual workflow:
90
97
 
@@ -94,7 +101,7 @@ Manual workflow:
94
101
  export const myThing = collection("my-thing").fields(({ f }) => ({ ... }));
95
102
  ```
96
103
  2. Run `bun run questpie:generate` to regenerate `.generated/index.ts`
97
- 3. Run `bun run migrate:create` to generate migration
104
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
98
105
 
99
106
  Collections are auto-discovered by codegen — no manual registration needed.
100
107
 
@@ -104,20 +111,20 @@ Preferred workflow:
104
111
 
105
112
  1. Run `bun questpie add global my-global`
106
113
  2. The CLI creates the file and auto-runs codegen
107
- 3. Run `bun run migrate:create`
114
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
108
115
 
109
116
  Manual workflow:
110
117
 
111
118
  1. Create `src/questpie/server/globals/my-global.ts` with a named export
112
119
  2. Run `bun run questpie:generate`
113
- 3. Run `bun run migrate:create`
120
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
114
121
 
115
122
  ### Add a server route (end-to-end type-safe)
116
123
 
117
124
  1. Create `src/questpie/server/routes/my-function.ts`:
118
125
 
119
126
  ```ts
120
- import { route } from "questpie";
127
+ import { route } from "questpie/services";
121
128
  import { z } from "zod";
122
129
 
123
130
  export default route()
@@ -18,8 +18,8 @@ docker compose up -d
18
18
  # 2) Regenerate codegen and type-check
19
19
  bun run scaffold:verify
20
20
 
21
- # 3) Run migrations
22
- bun run migrate
21
+ # 3) Create local database tables
22
+ bun run db:push
23
23
 
24
24
  # 4) Start development server
25
25
  bun run dev
@@ -75,6 +75,7 @@ migrations/
75
75
  | `bun run routes:generate` | Regenerate TanStack Router route tree |
76
76
  | `bun run questpie:generate` | Regenerate `src/questpie/server/.generated/*` |
77
77
  | `bun questpie add <type> <name>` | Scaffold entity files (auto-runs codegen) |
78
+ | `bun run db:push` | Push schema directly to local dev database |
78
79
  | `bun run migrate` | Run migrations |
79
80
  | `bun run migrate:create` | Create migration |
80
81
 
@@ -84,14 +85,14 @@ Preferred workflow:
84
85
 
85
86
  1. Run `bun questpie add collection products`.
86
87
  2. The CLI creates the file and runs codegen automatically.
87
- 3. Run `bun run migrate:create`.
88
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
88
89
 
89
90
  Manual workflow (when you create files by hand):
90
91
 
91
92
  1. Create a file in `src/questpie/server/collections/`.
92
93
  2. Export a collection builder from that file.
93
94
  3. Run `bun run questpie:generate`.
94
- 4. Run `bun run migrate:create`.
95
+ 4. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
95
96
 
96
97
  Collections are discovered automatically by codegen. No manual `app.ts` registration is required.
97
98
 
@@ -101,13 +102,13 @@ Preferred workflow:
101
102
 
102
103
  1. Run `bun questpie add global marketing`.
103
104
  2. The CLI creates the file and runs codegen automatically.
104
- 3. Run `bun run migrate:create`.
105
+ 3. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
105
106
 
106
107
  Manual workflow (when you create files by hand):
107
108
 
108
109
  1. Create a file in `src/questpie/server/globals/`.
109
110
  2. Export a global builder from that file.
110
111
  3. Run `bun run questpie:generate`.
111
- 4. Run `bun run migrate:create`.
112
+ 4. Run `bun run db:push` for local development, or `bun run migrate:create` for production migrations.
112
113
 
113
114
  Globals are discovered automatically by codegen. No manual `app.ts` registration is required.
@@ -15,6 +15,7 @@
15
15
  "routes:generate": "tsr generate",
16
16
  "questpie:generate": "questpie generate -c src/questpie/server/questpie.config.ts",
17
17
  "scaffold:generate": "bun run routes:generate && bun run questpie:generate",
18
+ "db:push": "questpie push -c questpie.config.ts",
18
19
  "migrate": "questpie migrate -c questpie.config.ts",
19
20
  "migrate:create": "questpie migrate:create -c questpie.config.ts",
20
21
  "migrate:status": "questpie migrate:status -c questpie.config.ts",
@@ -1,9 +1,18 @@
1
1
  import { QueryClient } from "@tanstack/react-query";
2
2
 
3
+ const ONE_MINUTE = 60 * 1000;
4
+ const FIVE_MINUTES = 5 * ONE_MINUTE;
5
+
3
6
  export const queryClient = new QueryClient({
4
7
  defaultOptions: {
5
8
  queries: {
6
- staleTime: 60 * 1000,
9
+ staleTime: ONE_MINUTE,
10
+ gcTime: FIVE_MINUTES,
11
+ refetchOnWindowFocus: false,
12
+ retry: 1,
13
+ },
14
+ mutations: {
15
+ retry: 0,
7
16
  },
8
17
  },
9
18
  });
@@ -1 +1,3 @@
1
- export { default } from "@questpie/admin/client-module";
1
+ import { adminClientModule } from "@questpie/admin/client/modules/admin";
2
+
3
+ export default [adminClientModule] as const;