create-questpie 2.0.1 → 2.0.3
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 +10 -6
- package/dist/index.mjs +139 -24
- package/package.json +5 -3
- package/skills/questpie/AGENTS.md +2670 -0
- package/skills/questpie/SKILL.md +260 -0
- package/skills/questpie/references/auth.md +121 -0
- package/skills/questpie/references/business-logic.md +550 -0
- package/skills/questpie/references/codegen-plugin-api.md +382 -0
- package/skills/questpie/references/crud-api.md +378 -0
- package/skills/questpie/references/data-modeling.md +493 -0
- package/skills/questpie/references/extend.md +557 -0
- package/skills/questpie/references/field-types.md +386 -0
- package/skills/questpie/references/infrastructure-adapters.md +545 -0
- package/skills/questpie/references/multi-tenancy.md +364 -0
- package/skills/questpie/references/production.md +475 -0
- package/skills/questpie/references/query-operators.md +125 -0
- package/skills/questpie/references/quickstart.md +564 -0
- package/skills/questpie/references/rules.md +389 -0
- package/skills/questpie/references/tanstack-query.md +520 -0
- package/skills/questpie-admin/AGENTS.md +1508 -0
- package/skills/questpie-admin/SKILL.md +436 -0
- package/skills/questpie-admin/references/blocks.md +331 -0
- package/skills/questpie-admin/references/custom-ui.md +305 -0
- package/skills/questpie-admin/references/views.md +449 -0
- package/templates/tanstack-start/AGENTS.md +17 -13
- package/templates/tanstack-start/CLAUDE.md +15 -12
- package/templates/tanstack-start/README.md +19 -13
- package/templates/tanstack-start/env.example +1 -1
- package/templates/tanstack-start/package.json +20 -6
- package/templates/tanstack-start/src/lib/env.ts +1 -1
- package/templates/tanstack-start/src/lib/query-client.ts +10 -1
- package/templates/tanstack-start/src/questpie/server/config/admin.ts +27 -30
- package/templates/tanstack-start/src/routeTree.gen.ts +138 -0
- package/templates/tanstack-start/src/routes/__root.tsx +0 -2
- package/templates/tanstack-start/src/routes/admin/$.tsx +12 -1
- package/templates/tanstack-start/src/routes/admin/index.tsx +12 -5
- package/templates/tanstack-start/src/routes/admin.tsx +8 -1
- package/templates/tanstack-start/src/tanstack-start.d.ts +1 -0
- package/templates/tanstack-start/src/vite-env.d.ts +1 -0
- package/templates/tanstack-start/vite.config.ts +1 -3
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: questpie-quickstart
|
|
3
|
+
description: >
|
|
4
|
+
End-to-end getting started with QUESTPIE — from scaffolding to production.
|
|
5
|
+
Load when starting a new project, onboarding, or following the happy path
|
|
6
|
+
from zero to a running app with collections, admin panel, and migrations.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# QUESTPIE Quickstart
|
|
10
|
+
|
|
11
|
+
Complete lifecycle guide: scaffold, define data, generate, migrate, serve, deploy.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Scaffold a New Project
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun create questpie my-app
|
|
19
|
+
cd my-app
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
|
|
24
|
+
- `-t, --template <name>` — template to use (default: `tanstack-start`)
|
|
25
|
+
- `--no-install` — skip `bun install`
|
|
26
|
+
- `--no-git` — skip git init
|
|
27
|
+
|
|
28
|
+
After scaffolding:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cp .env.example .env # Set DATABASE_URL, APP_URL, APP_SECRET
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Required Environment Variables
|
|
35
|
+
|
|
36
|
+
| Variable | Required | Description |
|
|
37
|
+
| -------------- | -------- | ---------------------------- |
|
|
38
|
+
| `DATABASE_URL` | Yes | PostgreSQL connection string |
|
|
39
|
+
| `APP_URL` | Yes | Public URL of the app |
|
|
40
|
+
| `APP_SECRET` | Yes | Secret for auth sessions |
|
|
41
|
+
| `SMTP_HOST` | No | Email SMTP host |
|
|
42
|
+
| `SMTP_PORT` | No | Email SMTP port |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Project Structure
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
my-app/
|
|
50
|
+
├── questpie.config.ts # CLI config (re-exports server config)
|
|
51
|
+
├── src/
|
|
52
|
+
│ ├── questpie/
|
|
53
|
+
│ │ ├── server/
|
|
54
|
+
│ │ │ ├── questpie.config.ts # Runtime config (DB, adapters, secrets)
|
|
55
|
+
│ │ │ ├── modules.ts # Module dependencies
|
|
56
|
+
│ │ │ ├── config/ # Typed configuration files
|
|
57
|
+
│ │ │ │ ├── auth.ts # authConfig({...}) — Better Auth options
|
|
58
|
+
│ │ │ │ ├── app.ts # appConfig({ locale, access, hooks, context })
|
|
59
|
+
│ │ │ │ ├── admin.ts # adminConfig({ sidebar, dashboard, branding, locale })
|
|
60
|
+
│ │ │ │ └── openapi.ts # openApiConfig({ info, scalar })
|
|
61
|
+
│ │ │ ├── collections/ # One file per collection
|
|
62
|
+
│ │ │ ├── globals/ # One file per global
|
|
63
|
+
│ │ │ ├── routes/ # App routes (JSON or raw)
|
|
64
|
+
│ │ │ ├── jobs/ # Background tasks
|
|
65
|
+
│ │ │ ├── services/ # Singleton services
|
|
66
|
+
│ │ │ ├── blocks/ # Content blocks (admin plugin)
|
|
67
|
+
│ │ │ ├── emails/ # Email templates
|
|
68
|
+
│ │ │ └── .generated/ # Codegen output (NEVER edit)
|
|
69
|
+
│ │ └── admin/ # Admin client customizations
|
|
70
|
+
│ │ ├── .generated/ # Codegen output (NEVER edit)
|
|
71
|
+
│ │ └── blocks/ # Block renderers (React)
|
|
72
|
+
│ ├── lib/
|
|
73
|
+
│ │ └── client.ts # Typed client SDK
|
|
74
|
+
│ └── routes/
|
|
75
|
+
│ └── api/
|
|
76
|
+
│ └── $.ts # API catch-all handler
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Discovery Rules
|
|
80
|
+
|
|
81
|
+
Codegen discovers files by **directory name** and **export pattern**:
|
|
82
|
+
|
|
83
|
+
| Directory | Key derivation | Example |
|
|
84
|
+
| --------------- | -------------------------- | ------------------------------------------ |
|
|
85
|
+
| `collections/` | Factory arg to camelCase | `collection("blog-posts")` -> `blogPosts` |
|
|
86
|
+
| `globals/` | Factory arg to camelCase | `global("siteSettings")` -> `siteSettings` |
|
|
87
|
+
| `routes/` | Filename to camelCase/path | `create-booking.ts` -> `createBooking` |
|
|
88
|
+
| `jobs/` | Filename to camelCase | `send-email.ts` -> `sendEmail` |
|
|
89
|
+
| `routes/` (raw) | Filename to path | `webhook.ts` -> `webhook` |
|
|
90
|
+
| `services/` | Filename to camelCase | `blog.ts` -> `blog` |
|
|
91
|
+
| `blocks/` | Factory arg/export name | `block("hero")` -> `hero` |
|
|
92
|
+
| `emails/` | Filename to camelCase | `welcome.ts` -> `welcome` |
|
|
93
|
+
|
|
94
|
+
Routes support nested directories for namespacing (`routes/booking/create.ts` -> `client.routes.booking.create()`).
|
|
95
|
+
|
|
96
|
+
Only hyphens are camelized in factory args; underscores are preserved (`global("site_settings")` -> `site_settings`).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 3. Runtime Config
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// src/questpie/server/questpie.config.ts
|
|
104
|
+
import { runtimeConfig } from "questpie";
|
|
105
|
+
|
|
106
|
+
export default runtimeConfig({
|
|
107
|
+
app: {
|
|
108
|
+
url: process.env.APP_URL || "http://localhost:3000",
|
|
109
|
+
},
|
|
110
|
+
db: {
|
|
111
|
+
url: process.env.DATABASE_URL || "postgres://localhost/myapp",
|
|
112
|
+
},
|
|
113
|
+
secret: process.env.APP_SECRET || "change-me-in-production",
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The CLI config at the project root re-exports the server config:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// questpie.config.ts (project root)
|
|
121
|
+
export { default } from "./src/questpie/server/questpie.config";
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 4. Define a Collection
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// src/questpie/server/collections/tasks.ts
|
|
130
|
+
import { collection } from "#questpie/factories";
|
|
131
|
+
|
|
132
|
+
export default collection("tasks").fields(({ f }) => ({
|
|
133
|
+
title: f.text(255).required(),
|
|
134
|
+
description: f.textarea(),
|
|
135
|
+
priority: f
|
|
136
|
+
.select([
|
|
137
|
+
{ value: "low", label: "Low" },
|
|
138
|
+
{ value: "medium", label: "Medium" },
|
|
139
|
+
{ value: "high", label: "High" },
|
|
140
|
+
])
|
|
141
|
+
.default("medium")
|
|
142
|
+
.required(),
|
|
143
|
+
dueDate: f.date(),
|
|
144
|
+
completed: f.boolean().default(false).required(),
|
|
145
|
+
}));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This creates:
|
|
149
|
+
|
|
150
|
+
- A `tasks` database table with typed columns
|
|
151
|
+
- CRUD API endpoints at `/api/tasks`
|
|
152
|
+
- Zod validation for create/update
|
|
153
|
+
- Type-safe query operators for `where`, `orderBy`
|
|
154
|
+
|
|
155
|
+
### Built-in Field Types
|
|
156
|
+
|
|
157
|
+
Core: `text`, `number`, `boolean`, `date`, `datetime`, `time`, `select`, `relation`, `upload`, `object`, `json`, `email`, `url`, `textarea`. Admin module fields: `richText`, `blocks`.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 5. Add a Route
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// src/questpie/server/routes/get-overdue-tasks.ts
|
|
165
|
+
import { route } from "questpie";
|
|
166
|
+
import z from "zod";
|
|
167
|
+
|
|
168
|
+
export default route()
|
|
169
|
+
.post()
|
|
170
|
+
.schema(z.object({}))
|
|
171
|
+
.handler(async ({ collections }) => {
|
|
172
|
+
return await collections.tasks.find({
|
|
173
|
+
where: {
|
|
174
|
+
completed: false,
|
|
175
|
+
dueDate: { lt: new Date() },
|
|
176
|
+
},
|
|
177
|
+
orderBy: { dueDate: "asc" },
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Typed JSON routes are exposed as flat endpoints at `/api/<route-path>`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 6. Run Codegen
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
bunx questpie generate
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This scans your file convention directories and generates:
|
|
193
|
+
|
|
194
|
+
- `src/questpie/server/.generated/index.ts` — `app` instance, `AppConfig` type
|
|
195
|
+
- `src/questpie/server/.generated/module.ts` — merged module with all discovered entities
|
|
196
|
+
- Module augmentation for `AppContext` (typed `collections`, `queue`, `email` in every handler)
|
|
197
|
+
|
|
198
|
+
Use `#questpie/factories` in collection, global, and block files (they need codegen-generated types). Routes, jobs, services, emails use `"questpie"` directly. Use `#questpie` for the generated app/runtime exports.
|
|
199
|
+
|
|
200
|
+
**Run codegen again every time you add, rename, or remove a file in a convention directory.**
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 7. Push Schema / Run Migrations
|
|
205
|
+
|
|
206
|
+
### Development (quick iteration)
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
bunx questpie push
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Syncs your Drizzle schema directly to the database. No migration files created. Use this during development only.
|
|
213
|
+
|
|
214
|
+
### Production (migration files)
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Generate a migration from schema diff
|
|
218
|
+
bunx questpie migrate:generate
|
|
219
|
+
|
|
220
|
+
# Run pending migrations
|
|
221
|
+
bunx questpie migrate:up
|
|
222
|
+
|
|
223
|
+
# Rollback last migration
|
|
224
|
+
bunx questpie migrate:down
|
|
225
|
+
|
|
226
|
+
# Reset all migrations
|
|
227
|
+
bunx questpie migrate:reset
|
|
228
|
+
|
|
229
|
+
# Reset + run all (fresh start)
|
|
230
|
+
bunx questpie migrate:fresh
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 8. Wire Up the HTTP Handler
|
|
236
|
+
|
|
237
|
+
### TanStack Start (default template)
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
// src/routes/api/$.ts
|
|
241
|
+
import { createAPIFileRoute } from "@tanstack/react-start/api";
|
|
242
|
+
import { createFetchHandler } from "questpie";
|
|
243
|
+
import { app } from "#questpie";
|
|
244
|
+
|
|
245
|
+
const handler = createFetchHandler(app, { basePath: "/api" });
|
|
246
|
+
|
|
247
|
+
export const Route = createAPIFileRoute("/api/$")({
|
|
248
|
+
GET: ({ request }) => handler(request),
|
|
249
|
+
POST: ({ request }) => handler(request),
|
|
250
|
+
PATCH: ({ request }) => handler(request),
|
|
251
|
+
DELETE: ({ request }) => handler(request),
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Hono
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { questpieHono } from "@questpie/hono/server";
|
|
259
|
+
import { Hono } from "hono";
|
|
260
|
+
import { app } from "#questpie";
|
|
261
|
+
|
|
262
|
+
export default new Hono().route("/api", questpieHono(app));
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Available Adapters
|
|
266
|
+
|
|
267
|
+
| Adapter | Package | Use case |
|
|
268
|
+
| -------------- | ------------------ | --------------------- |
|
|
269
|
+
| Hono | `@questpie/hono` | General purpose, fast |
|
|
270
|
+
| Elysia | `@questpie/elysia` | Bun-native |
|
|
271
|
+
| Next.js | `@questpie/next` | Next.js API routes |
|
|
272
|
+
| TanStack Start | (built-in) | Generic fetch handler |
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 9. Add the Admin Panel
|
|
277
|
+
|
|
278
|
+
### Install
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bun add @questpie/admin
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Register the admin module
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// src/questpie/server/modules.ts
|
|
288
|
+
import { adminModule } from "@questpie/admin/server";
|
|
289
|
+
|
|
290
|
+
export default [adminModule] as const;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Re-run codegen
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
bunx questpie generate
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
This picks up admin conventions (`config/admin.ts`, blocks, views, components) and generates `admin/.generated/client.ts`.
|
|
300
|
+
|
|
301
|
+
Navigate to `/admin` to see the admin panel with your collections.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 10. Typed Client SDK
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
// src/lib/client.ts
|
|
309
|
+
import { createClient } from "questpie/client";
|
|
310
|
+
import type { AppConfig } from "#questpie";
|
|
311
|
+
|
|
312
|
+
export const client = createClient<AppConfig>({
|
|
313
|
+
baseURL: "http://localhost:3000",
|
|
314
|
+
basePath: "/api",
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Usage with full type inference:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
// Typed collection queries — autocomplete on field names and operators
|
|
322
|
+
const tasks = await client.collections.tasks.find({
|
|
323
|
+
where: { completed: false },
|
|
324
|
+
orderBy: { priority: "desc" },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Call route
|
|
328
|
+
const overdue = await client.routes.getOverdueTasks({});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 11. Start Development
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bun dev
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Test: `curl http://localhost:3000/api/tasks` for collection CRUD, `curl -X POST http://localhost:3000/api/get-overdue-tasks -H "Content-Type: application/json" -d '{}'` for typed route calls.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Common Mistakes
|
|
344
|
+
|
|
345
|
+
### CRITICAL: Using npm/yarn/pnpm instead of Bun
|
|
346
|
+
|
|
347
|
+
QUESTPIE requires **Bun** as the package manager and runtime. All commands use `bun` or `bunx`:
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
# WRONG
|
|
351
|
+
npm install questpie
|
|
352
|
+
npx questpie generate
|
|
353
|
+
yarn add questpie
|
|
354
|
+
|
|
355
|
+
# CORRECT
|
|
356
|
+
bun add questpie
|
|
357
|
+
bunx questpie generate
|
|
358
|
+
bun dev
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### HIGH: Forgetting to run `questpie generate` after changes
|
|
362
|
+
|
|
363
|
+
Every time you add, rename, or remove a file in a convention directory (`collections/`, `routes/`, `globals/`, `jobs/`, etc.), you must re-run:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
bunx questpie generate
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Without this, the `app` instance and types will be stale. New collections will not appear in CRUD endpoints or admin.
|
|
370
|
+
|
|
371
|
+
### HIGH: Not setting DATABASE_URL
|
|
372
|
+
|
|
373
|
+
QUESTPIE requires PostgreSQL. Set `DATABASE_URL` in `.env` before running `push` or `migrate`:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### MEDIUM: Missing `export default` on convention files
|
|
380
|
+
|
|
381
|
+
Codegen discovers files by their **default export**. Files without `export default` are silently ignored:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
// WRONG — codegen will not discover this
|
|
385
|
+
export const tasks = collection("tasks").fields(/* ... */);
|
|
386
|
+
|
|
387
|
+
// CORRECT
|
|
388
|
+
export default collection("tasks").fields(/* ... */);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### MEDIUM: Putting business logic in route handlers
|
|
392
|
+
|
|
393
|
+
Framework route handlers should only mount the QUESTPIE fetch handler. Business logic belongs in `routes/`, `jobs/`, or collection hooks:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
// WRONG — business logic in route file
|
|
397
|
+
export const Route = createAPIFileRoute("/api/custom")({
|
|
398
|
+
POST: async ({ request }) => {
|
|
399
|
+
const db = getDB();
|
|
400
|
+
// ... manual queries
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// CORRECT — use a typed route
|
|
405
|
+
// src/questpie/server/routes/my-logic.ts
|
|
406
|
+
export default route()
|
|
407
|
+
.post()
|
|
408
|
+
.schema(
|
|
409
|
+
z.object({
|
|
410
|
+
/* ... */
|
|
411
|
+
}),
|
|
412
|
+
)
|
|
413
|
+
.handler(async ({ input, collections }) => {
|
|
414
|
+
return await collections.tasks.create(input);
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## Quick Reference: CLI Commands
|
|
421
|
+
|
|
422
|
+
| Command | Purpose |
|
|
423
|
+
| -------------------------------- | ------------------------------------------- |
|
|
424
|
+
| `bun create questpie my-app` | Scaffold a new project |
|
|
425
|
+
| `bunx questpie generate` | Scan conventions, generate types and app |
|
|
426
|
+
| `bunx questpie push` | Push schema to DB (dev only, no migrations) |
|
|
427
|
+
| `bunx questpie migrate:generate` | Generate migration from schema diff |
|
|
428
|
+
| `bunx questpie migrate:up` | Run pending migrations |
|
|
429
|
+
| `bunx questpie migrate:down` | Rollback last migration |
|
|
430
|
+
| `bunx questpie migrate:fresh` | Reset + run all migrations |
|
|
431
|
+
| `bun dev` | Start development server |
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Minimal Complete Example
|
|
436
|
+
|
|
437
|
+
Starting from zero — every file needed for a working app:
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
// questpie.config.ts (project root)
|
|
441
|
+
export { default } from "./src/questpie/server/questpie.config";
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
// src/questpie/server/questpie.config.ts
|
|
446
|
+
import { runtimeConfig } from "questpie";
|
|
447
|
+
|
|
448
|
+
export default runtimeConfig({
|
|
449
|
+
app: { url: process.env.APP_URL || "http://localhost:3000" },
|
|
450
|
+
db: { url: process.env.DATABASE_URL! },
|
|
451
|
+
secret: process.env.APP_SECRET || "dev-secret",
|
|
452
|
+
});
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
// src/questpie/server/collections/posts.ts
|
|
457
|
+
import { collection } from "#questpie/factories";
|
|
458
|
+
|
|
459
|
+
export default collection("posts").fields(({ f }) => ({
|
|
460
|
+
title: f.text().required(),
|
|
461
|
+
body: f.richText(),
|
|
462
|
+
status: f.select([
|
|
463
|
+
{ value: "draft", label: "Draft" },
|
|
464
|
+
{ value: "published", label: "Published" },
|
|
465
|
+
]),
|
|
466
|
+
}));
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
// src/routes/api/$.ts
|
|
471
|
+
import { createAPIFileRoute } from "@tanstack/react-start/api";
|
|
472
|
+
import { createFetchHandler } from "questpie";
|
|
473
|
+
import { app } from "#questpie";
|
|
474
|
+
|
|
475
|
+
const handler = createFetchHandler(app, { basePath: "/api" });
|
|
476
|
+
|
|
477
|
+
export const Route = createAPIFileRoute("/api/$")({
|
|
478
|
+
GET: ({ request }) => handler(request),
|
|
479
|
+
POST: ({ request }) => handler(request),
|
|
480
|
+
PATCH: ({ request }) => handler(request),
|
|
481
|
+
DELETE: ({ request }) => handler(request),
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Then run:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
bunx questpie generate
|
|
489
|
+
bunx questpie push
|
|
490
|
+
bun dev
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## 12. Live Preview (Optional)
|
|
496
|
+
|
|
497
|
+
Add split-screen live preview to any collection with `.preview()`. Live Preview uses the existing admin `FormView`, Preview button, `LivePreviewMode`, and iframe. Do not introduce a second default form view or parallel preview API names.
|
|
498
|
+
|
|
499
|
+
### Add Preview to a Collection
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
// src/questpie/server/collections/pages.ts
|
|
503
|
+
import { collection } from "#questpie/factories";
|
|
504
|
+
|
|
505
|
+
export default collection("pages")
|
|
506
|
+
.fields(({ f }) => ({
|
|
507
|
+
title: f.text().required(),
|
|
508
|
+
slug: f.text().required(),
|
|
509
|
+
content: f.blocks(),
|
|
510
|
+
}))
|
|
511
|
+
.preview({
|
|
512
|
+
enabled: true,
|
|
513
|
+
position: "right",
|
|
514
|
+
defaultWidth: 50,
|
|
515
|
+
url: ({ record }) => `/${record.slug}?preview=true`,
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Add Preview Support to the Frontend Page
|
|
520
|
+
|
|
521
|
+
Frontend checklist:
|
|
522
|
+
|
|
523
|
+
1. Call `useCollectionPreview({ initialData, onRefresh })`.
|
|
524
|
+
2. Wrap the rendered output in `PreviewProvider`.
|
|
525
|
+
3. Render from `preview.data`, not directly from loader data.
|
|
526
|
+
4. Wrap editable scalar text in `PreviewField`.
|
|
527
|
+
5. Render blocks with `BlockRenderer` when the page uses `f.blocks()`.
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
import {
|
|
531
|
+
BlockRenderer,
|
|
532
|
+
PreviewField,
|
|
533
|
+
PreviewProvider,
|
|
534
|
+
useCollectionPreview,
|
|
535
|
+
} from "@questpie/admin/client";
|
|
536
|
+
import admin from "@/questpie/admin/.generated/client";
|
|
537
|
+
|
|
538
|
+
function PageView({ page }) {
|
|
539
|
+
const router = useRouter();
|
|
540
|
+
const preview = useCollectionPreview({
|
|
541
|
+
initialData: page,
|
|
542
|
+
onRefresh: () => router.invalidate(),
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
<PreviewProvider preview={preview}>
|
|
547
|
+
<PreviewField field="title" editable="text" as="h1">
|
|
548
|
+
{preview.data.title}
|
|
549
|
+
</PreviewField>
|
|
550
|
+
<BlockRenderer
|
|
551
|
+
content={preview.data.content}
|
|
552
|
+
data={preview.data.content?._data}
|
|
553
|
+
renderers={admin.blocks}
|
|
554
|
+
selectedBlockId={preview.selectedBlockId}
|
|
555
|
+
onBlockClick={
|
|
556
|
+
preview.isPreviewMode ? preview.handleBlockClick : undefined
|
|
557
|
+
}
|
|
558
|
+
/>
|
|
559
|
+
</PreviewProvider>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
The form remains authoritative. Save, autosave, Cmd+S, history, workflow, locks, and actions stay in the existing form lifecycle.
|