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.
Files changed (40) hide show
  1. package/README.md +10 -6
  2. package/dist/index.mjs +139 -24
  3. package/package.json +5 -3
  4. package/skills/questpie/AGENTS.md +2670 -0
  5. package/skills/questpie/SKILL.md +260 -0
  6. package/skills/questpie/references/auth.md +121 -0
  7. package/skills/questpie/references/business-logic.md +550 -0
  8. package/skills/questpie/references/codegen-plugin-api.md +382 -0
  9. package/skills/questpie/references/crud-api.md +378 -0
  10. package/skills/questpie/references/data-modeling.md +493 -0
  11. package/skills/questpie/references/extend.md +557 -0
  12. package/skills/questpie/references/field-types.md +386 -0
  13. package/skills/questpie/references/infrastructure-adapters.md +545 -0
  14. package/skills/questpie/references/multi-tenancy.md +364 -0
  15. package/skills/questpie/references/production.md +475 -0
  16. package/skills/questpie/references/query-operators.md +125 -0
  17. package/skills/questpie/references/quickstart.md +564 -0
  18. package/skills/questpie/references/rules.md +389 -0
  19. package/skills/questpie/references/tanstack-query.md +520 -0
  20. package/skills/questpie-admin/AGENTS.md +1508 -0
  21. package/skills/questpie-admin/SKILL.md +436 -0
  22. package/skills/questpie-admin/references/blocks.md +331 -0
  23. package/skills/questpie-admin/references/custom-ui.md +305 -0
  24. package/skills/questpie-admin/references/views.md +449 -0
  25. package/templates/tanstack-start/AGENTS.md +17 -13
  26. package/templates/tanstack-start/CLAUDE.md +15 -12
  27. package/templates/tanstack-start/README.md +19 -13
  28. package/templates/tanstack-start/env.example +1 -1
  29. package/templates/tanstack-start/package.json +20 -6
  30. package/templates/tanstack-start/src/lib/env.ts +1 -1
  31. package/templates/tanstack-start/src/lib/query-client.ts +10 -1
  32. package/templates/tanstack-start/src/questpie/server/config/admin.ts +27 -30
  33. package/templates/tanstack-start/src/routeTree.gen.ts +138 -0
  34. package/templates/tanstack-start/src/routes/__root.tsx +0 -2
  35. package/templates/tanstack-start/src/routes/admin/$.tsx +12 -1
  36. package/templates/tanstack-start/src/routes/admin/index.tsx +12 -5
  37. package/templates/tanstack-start/src/routes/admin.tsx +8 -1
  38. package/templates/tanstack-start/src/tanstack-start.d.ts +1 -0
  39. package/templates/tanstack-start/src/vite-env.d.ts +1 -0
  40. package/templates/tanstack-start/vite.config.ts +1 -3
@@ -0,0 +1,475 @@
1
+ ---
2
+ name: questpie-core/production
3
+ description:
4
+ QUESTPIE production deployment authentication better-auth OAuth database PostgreSQL Drizzle storage S3 Flydrive queue pg-boss jobs realtime SSE pgNotify Redis migrations email SMTP KV key-value logger Pino OpenAPI Docker environment variables adapters infrastructure
5
+ - questpie-core
6
+ ---
7
+
8
+ This skill builds on questpie-core.
9
+
10
+ ## Overview
11
+
12
+ QUESTPIE uses an adapter-based architecture for all infrastructure. Development defaults work out of the box; production requires explicit adapter configuration in `questpie.config.ts`.
13
+
14
+ | Service | Dev Default | Production Adapter |
15
+ | -------- | --------------------- | ------------------------------------- |
16
+ | Database | PostgreSQL (local) | PostgreSQL (remote, SSL) |
17
+ | Storage | Local filesystem | S3-compatible (`s3` driver) |
18
+ | Queue | None (jobs skip) | pg-boss (`pgBossAdapter`) |
19
+ | Realtime | pgNotify | Redis Streams (`redisStreamsAdapter`) |
20
+ | Email | Console (logs output) | SMTP (`SmtpAdapter`) |
21
+ | KV Store | In-memory | Redis (`"redis"` adapter) |
22
+ | Logger | Pino (console) | Pino (structured JSON) |
23
+
24
+ ## Authentication
25
+
26
+ QUESTPIE uses [Better Auth](https://www.better-auth.com/). Configure via `config/auth.ts`:
27
+
28
+ ```ts
29
+ // src/questpie/server/config/auth.ts
30
+ import { authConfig } from "questpie";
31
+
32
+ export default authConfig({
33
+ emailAndPassword: {
34
+ enabled: true,
35
+ requireEmailVerification: false,
36
+ },
37
+ baseURL: process.env.APP_URL || "http://localhost:3000",
38
+ basePath: "/api/auth",
39
+ secret: process.env.BETTER_AUTH_SECRET || "change-me",
40
+ });
41
+ ```
42
+
43
+ ### Auth Options
44
+
45
+ | Option | Type | Description |
46
+ | ------------------------------------------- | --------- | --------------------------------------------------- |
47
+ | `emailAndPassword.enabled` | `boolean` | Enable email/password login |
48
+ | `emailAndPassword.requireEmailVerification` | `boolean` | Require email verification |
49
+ | `baseURL` | `string` | App public URL |
50
+ | `basePath` | `string` | Auth API path prefix |
51
+ | `secret` | `string` | Session signing secret (min 32 chars in production) |
52
+
53
+ ### Session in Handlers
54
+
55
+ Access the current session in functions, hooks, and access rules:
56
+
57
+ ```ts
58
+ handler: async ({ session }) => {
59
+ if (!session) throw new Error("Not authenticated");
60
+ const user = session.user;
61
+ // user.id, user.email, user.name
62
+ };
63
+ ```
64
+
65
+ ### Access Control with Session
66
+
67
+ ```ts
68
+ .access({
69
+ read: true,
70
+ create: ({ session }) => !!session,
71
+ update: ({ session }) => (session?.user as any)?.role === "admin",
72
+ delete: ({ session }) => (session?.user as any)?.role === "admin",
73
+ })
74
+ ```
75
+
76
+ The `adminModule` provides a built-in `user` collection for storing user accounts.
77
+
78
+ ## Database
79
+
80
+ PostgreSQL with Drizzle ORM. Schema is generated from your collection and global definitions.
81
+
82
+ ```ts
83
+ export default runtimeConfig({
84
+ db: {
85
+ url: process.env.DATABASE_URL || "postgres://localhost/myapp",
86
+ },
87
+ });
88
+ ```
89
+
90
+ Raw access via `db` context, indexes via `.indexes()`. See `references/infrastructure-adapters.md` for field-to-column mapping and full details.
91
+
92
+ ## Migrations
93
+
94
+ ### Development: Push
95
+
96
+ Sync schema directly without migration files:
97
+
98
+ ```bash
99
+ bunx questpie push
100
+ ```
101
+
102
+ ### Production: Migration Files
103
+
104
+ ```bash
105
+ # Generate migration from schema diff
106
+ bunx questpie migrate:generate
107
+
108
+ # Run pending migrations
109
+ bunx questpie migrate:up
110
+
111
+ # Rollback last migration
112
+ bunx questpie migrate:down
113
+
114
+ # Drop everything and re-run (DESTRUCTIVE -- dev only)
115
+ bunx questpie migrate:fresh
116
+
117
+ # Reset migration tracking
118
+ bunx questpie migrate:reset
119
+ ```
120
+
121
+ Configure migration and seed directories in `questpie.config.ts` under `cli.migrations.directory` and `cli.seeds.directory`. Run seeds with `bunx questpie seed`.
122
+
123
+ ## Storage
124
+
125
+ QUESTPIE uses [Flydrive](https://flydrive.dev/) for file storage.
126
+
127
+ ### Local (Development Default)
128
+
129
+ ```ts
130
+ export default runtimeConfig({
131
+ storage: {
132
+ basePath: "/api",
133
+ },
134
+ });
135
+ ```
136
+
137
+ ### S3 (Production)
138
+
139
+ ```ts
140
+ import { S3Driver } from "flydrive/drivers/s3";
141
+
142
+ export default runtimeConfig({
143
+ storage: {
144
+ basePath: "/api",
145
+ driver: new S3Driver({
146
+ bucket: process.env.S3_BUCKET,
147
+ region: process.env.S3_REGION,
148
+ accessKeyId: process.env.S3_ACCESS_KEY,
149
+ secretAccessKey: process.env.S3_SECRET_KEY,
150
+ }),
151
+ },
152
+ });
153
+ ```
154
+
155
+ ### Upload Fields
156
+
157
+ ```ts
158
+ avatar: f.upload({
159
+ to: "assets",
160
+ mimeTypes: ["image/*"],
161
+ maxSize: 5_000_000,
162
+ }),
163
+ ```
164
+
165
+ ## Queue
166
+
167
+ Background job processing with [pg-boss](https://github.com/timgit/pg-boss). Jobs stored in PostgreSQL.
168
+
169
+ ```ts
170
+ import { pgBossAdapter, runtimeConfig } from "questpie";
171
+
172
+ export default runtimeConfig({
173
+ queue: {
174
+ adapter: pgBossAdapter({
175
+ connectionString: process.env.DATABASE_URL,
176
+ }),
177
+ },
178
+ });
179
+ ```
180
+
181
+ ### Publishing Jobs
182
+
183
+ From hooks, functions, or other jobs:
184
+
185
+ ```ts
186
+ handler: async ({ queue }) => {
187
+ await queue.sendAppointmentConfirmation.publish({
188
+ appointmentId: "abc",
189
+ customerId: "def",
190
+ });
191
+ };
192
+ ```
193
+
194
+ The `queue` object is fully typed -- autocompletion shows all registered jobs and their payload schemas.
195
+
196
+ ## Realtime
197
+
198
+ SSE-based live updates via `POST /realtime` multiplexed endpoint.
199
+
200
+ ### pgNotify (Single Instance)
201
+
202
+ ```ts
203
+ import { pgNotifyAdapter, runtimeConfig } from "questpie";
204
+
205
+ export default runtimeConfig({
206
+ realtime: {
207
+ adapter: pgNotifyAdapter({
208
+ connectionString: process.env.DATABASE_URL,
209
+ }),
210
+ },
211
+ });
212
+ ```
213
+
214
+ ### Redis Streams (Multi-Instance)
215
+
216
+ Required for horizontal scaling:
217
+
218
+ ```ts
219
+ import { redisStreamsAdapter } from "questpie";
220
+
221
+ export default runtimeConfig({
222
+ realtime: {
223
+ adapter: redisStreamsAdapter({
224
+ url: process.env.REDIS_URL,
225
+ }),
226
+ },
227
+ });
228
+ ```
229
+
230
+ ## Email
231
+
232
+ Transactional email with typed templates. Two adapters: `SmtpAdapter` (production) and `ConsoleAdapter` (development).
233
+
234
+ ```ts
235
+ import { SmtpAdapter, ConsoleAdapter, runtimeConfig } from "questpie";
236
+
237
+ export default runtimeConfig({
238
+ email: {
239
+ adapter:
240
+ process.env.NODE_ENV === "development"
241
+ ? new ConsoleAdapter({ logHtml: false })
242
+ : new SmtpAdapter({
243
+ transport: { host: process.env.SMTP_HOST, port: 587, secure: true },
244
+ }),
245
+ },
246
+ });
247
+ ```
248
+
249
+ Templates go in `emails/` directory using the `email()` factory. Send via `email.sendTemplate()` in handlers. See `references/infrastructure-adapters.md` for template examples.
250
+
251
+ ## Search
252
+
253
+ PostgreSQL full-text search. Mark collections as searchable:
254
+
255
+ ```ts
256
+ .searchable(["title", "body", "tags"])
257
+ ```
258
+
259
+ Client usage:
260
+
261
+ ```ts
262
+ const results = await client.search.search({
263
+ query: "haircut styles",
264
+ collections: ["posts", "services"],
265
+ limit: 20,
266
+ });
267
+ ```
268
+
269
+ ## KV Store
270
+
271
+ ### Custom Adapter
272
+
273
+ ```ts
274
+ export default runtimeConfig({
275
+ kv: {
276
+ adapter: myKvAdapter,
277
+ defaultTtl: 3600,
278
+ },
279
+ });
280
+ ```
281
+
282
+ ### In-Memory Default
283
+
284
+ ```ts
285
+ kv: {
286
+ defaultTtl: 3600,
287
+ }
288
+ ```
289
+
290
+ ### Usage
291
+
292
+ ```ts
293
+ handler: async ({ kv }) => {
294
+ await kv.set("key", "value", { ttl: 3600 });
295
+ const value = await kv.get("key");
296
+ await kv.delete("key");
297
+ };
298
+ ```
299
+
300
+ ## Logger
301
+
302
+ Structured logging with [Pino](https://getpino.io):
303
+
304
+ ```ts
305
+ handler: async ({ logger }) => {
306
+ logger.info("Processing booking");
307
+ logger.error({ err: error }, "Booking failed");
308
+ logger.debug({ barberId, serviceId }, "Checking availability");
309
+ };
310
+ ```
311
+
312
+ Log levels: `trace`, `debug`, `info`, `warn`, `error`, `fatal`.
313
+
314
+ Structured data goes as the first argument:
315
+
316
+ ```ts
317
+ logger.info({ appointmentId: "abc", action: "created" }, "Appointment created");
318
+ ```
319
+
320
+ ## OpenAPI
321
+
322
+ Auto-generate OpenAPI 3.1 spec with `@questpie/openapi`. Install with `bun add @questpie/openapi`, add `openApiModule` to `modules.ts`, configure it in `config/openapi.ts` with `openApiConfig({ info: { title: "My API", version: "1.0.0" } })`, then run `bunx questpie generate`. Serves spec at `/api/openapi.json` and Scalar docs at `/api/docs`. See `references/infrastructure-adapters.md` for full options.
323
+
324
+ ## Deployment
325
+
326
+ ### Docker
327
+
328
+ ```dockerfile
329
+ FROM oven/bun:1 AS base
330
+ WORKDIR /app
331
+
332
+ FROM base AS build
333
+ COPY package.json bun.lockb ./
334
+ RUN bun install --frozen-lockfile
335
+ COPY . .
336
+ RUN bunx questpie generate
337
+ RUN bun run build
338
+
339
+ FROM base AS production
340
+ COPY --from=build /app/.output /app/.output
341
+ EXPOSE 3000
342
+ CMD ["bun", "run", ".output/server/index.mjs"]
343
+ ```
344
+
345
+ ### Environment Variables
346
+
347
+ | Variable | Required | Description |
348
+ | ----------------------------------- | -------- | ------------------------------------- |
349
+ | `DATABASE_URL` | Yes | PostgreSQL connection string |
350
+ | `APP_URL` | Yes | Public URL of the application |
351
+ | `APP_SECRET` / `BETTER_AUTH_SECRET` | Yes | Session signing secret (min 32 chars) |
352
+ | `SMTP_HOST` | No | Email SMTP host |
353
+ | `SMTP_PORT` | No | Email SMTP port |
354
+ | `REDIS_URL` | No | Redis URL (for KV, realtime) |
355
+ | `S3_BUCKET` | No | S3 bucket name |
356
+ | `S3_REGION` | No | S3 region |
357
+ | `S3_ACCESS_KEY` | No | S3 access key |
358
+ | `S3_SECRET_KEY` | No | S3 secret key |
359
+
360
+ ### Production Checklist
361
+
362
+ - Set strong `APP_SECRET` (min 32 characters)
363
+ - Use production `DATABASE_URL` with SSL
364
+ - Run `bunx questpie migrate:up` before deploying
365
+ - Configure SMTP for transactional email
366
+ - Set `APP_URL` to your public domain
367
+ - Enable HTTPS
368
+ - Configure S3 or persistent storage for uploads
369
+ - Use `redisStreamsAdapter` if running multiple instances
370
+ - Set up health checks
371
+
372
+ ### Health Check
373
+
374
+ ```ts
375
+ // routes/health.ts
376
+ import { route } from "questpie";
377
+
378
+ export default route({
379
+ method: "GET",
380
+ handler: async ({ db }) => {
381
+ await db.execute(sql`SELECT 1`);
382
+ return new Response(JSON.stringify({ status: "ok" }), {
383
+ headers: { "Content-Type": "application/json" },
384
+ });
385
+ },
386
+ });
387
+ ```
388
+
389
+ ## Common Mistakes
390
+
391
+ ### CRITICAL: Missing BETTER_AUTH_SECRET in production
392
+
393
+ Without a strong secret, sessions can be forged. The default `"change-me"` is for development only.
394
+
395
+ ```ts
396
+ // WRONG -- in production
397
+ secret: "change-me";
398
+
399
+ // CORRECT -- strong random secret from environment
400
+ secret: process.env.BETTER_AUTH_SECRET; // min 32 chars
401
+ ```
402
+
403
+ ### HIGH: Not running migrations after schema changes
404
+
405
+ When you add, remove, or change collection fields, the database schema must be updated. Without migrations, queries fail or return stale data.
406
+
407
+ ```bash
408
+ # After changing any collection fields:
409
+ bunx questpie migrate:generate # create migration file
410
+ bunx questpie migrate:up # apply to database
411
+
412
+ # Or in development:
413
+ bunx questpie push # direct schema sync (no migration file)
414
+ ```
415
+
416
+ ### HIGH: Using local storage in production without persistent volume
417
+
418
+ The local storage adapter writes to the filesystem. In containerized deployments, files are lost when the container restarts.
419
+
420
+ ```ts
421
+ // WRONG -- files lost on container restart
422
+ storage: { basePath: "/api" }
423
+
424
+ // CORRECT -- persistent S3 storage
425
+ import { S3Driver } from "flydrive/drivers/s3";
426
+
427
+ storage: {
428
+ basePath: "/api",
429
+ driver: new S3Driver({
430
+ bucket: process.env.S3_BUCKET,
431
+ region: process.env.S3_REGION,
432
+ accessKeyId: process.env.S3_ACCESS_KEY,
433
+ secretAccessKey: process.env.S3_SECRET_KEY,
434
+ }),
435
+ }
436
+ ```
437
+
438
+ ### MEDIUM: Missing queue adapter for background jobs
439
+
440
+ Without pg-boss configured, job `.publish()` calls silently do nothing. Jobs defined in `jobs/` will never run.
441
+
442
+ ```ts
443
+ // REQUIRED for jobs to actually execute
444
+ queue: {
445
+ adapter: pgBossAdapter({
446
+ connectionString: process.env.DATABASE_URL,
447
+ }),
448
+ }
449
+ ```
450
+
451
+ ## Realtime and Live Preview
452
+
453
+ The realtime adapter (`pgNotifyAdapter` or `redisStreamsAdapter`) is relevant for **detached or shared preview sessions** — when the preview runs in a separate browser tab, or multiple collaborators view the same preview.
454
+
455
+ For the default **same-tab preview**, realtime is NOT involved. Current same-tab preview uses `postMessage` for refresh/focus messages between the editor and the iframe.
456
+
457
+ | Preview mode | Transport | Requires realtime adapter? |
458
+ | ------------------- | -------------- | -------------------------- |
459
+ | Same-tab (default) | `postMessage` | No |
460
+ | Detached tab | SSE / realtime | Yes |
461
+ | Shared / multi-user | SSE / realtime | Yes |
462
+
463
+ If your app only uses same-tab preview (the default), you do not need to configure a realtime adapter for preview purposes. Configure it when you need detached preview, multi-user collaboration, or other realtime features (live notifications, presence, etc.).
464
+
465
+ ### MEDIUM: Missing APP_URL environment variable
466
+
467
+ Auth callbacks, email links, and storage URLs all depend on `APP_URL`. Without it, OAuth redirects break and email links point to `localhost`.
468
+
469
+ ```bash
470
+ # WRONG
471
+ # APP_URL not set
472
+
473
+ # CORRECT
474
+ APP_URL=https://myapp.example.com
475
+ ```
@@ -0,0 +1,125 @@
1
+ # Query Operators Reference
2
+
3
+ Full reference for all `where` clause operators in QUESTPIE CRUD queries.
4
+
5
+ ## Text Fields
6
+
7
+ Applies to: `text`, `textarea`, `richText`, `email`, `url`, `slug`
8
+
9
+ | Operator | Example | Description |
10
+ | ------------ | --------------------------------- | ----------------------- |
11
+ | equality | `{ title: "Hello" }` | Exact match (shorthand) |
12
+ | `contains` | `{ title: { contains: "ell" } }` | Substring match |
13
+ | `startsWith` | `{ title: { startsWith: "He" } }` | Prefix match |
14
+ | `endsWith` | `{ title: { endsWith: "lo" } }` | Suffix match |
15
+ | `in` | `{ title: { in: ["A", "B"] } }` | One of values |
16
+
17
+ ## Number Fields
18
+
19
+ Applies to: `number`
20
+
21
+ | Operator | Example | Description |
22
+ | -------- | --------------------------------- | --------------------- |
23
+ | equality | `{ price: 1000 }` | Exact match |
24
+ | `gt` | `{ price: { gt: 1000 } }` | Greater than |
25
+ | `gte` | `{ price: { gte: 1000 } }` | Greater than or equal |
26
+ | `lt` | `{ price: { lt: 5000 } }` | Less than |
27
+ | `lte` | `{ price: { lte: 5000 } }` | Less than or equal |
28
+ | `in` | `{ price: { in: [1000, 2000] } }` | One of values |
29
+
30
+ ## Boolean Fields
31
+
32
+ Applies to: `boolean`
33
+
34
+ | Operator | Example | Description |
35
+ | -------- | -------------------- | ----------- |
36
+ | equality | `{ isActive: true }` | Exact match |
37
+
38
+ ## Date / DateTime Fields
39
+
40
+ Applies to: `date`, `dateTime`
41
+
42
+ | Operator | Example | Description |
43
+ | -------- | --------------------------------- | ------------ |
44
+ | equality | `{ date: "2025-03-01" }` | Exact match |
45
+ | `gt` | `{ date: { gt: "2025-01-01" } }` | After |
46
+ | `gte` | `{ date: { gte: "2025-01-01" } }` | On or after |
47
+ | `lt` | `{ date: { lt: "2025-12-31" } }` | Before |
48
+ | `lte` | `{ date: { lte: "2025-12-31" } }` | On or before |
49
+
50
+ ## Select Fields
51
+
52
+ Applies to: `select`, `multiSelect`
53
+
54
+ | Operator | Example | Description |
55
+ | -------- | -------------------------------------------- | ------------- |
56
+ | equality | `{ status: "published" }` | Exact match |
57
+ | `in` | `{ status: { in: ["draft", "published"] } }` | One of values |
58
+
59
+ ## Relation Fields
60
+
61
+ Applies to: `relation`
62
+
63
+ | Operator | Example | Description |
64
+ | -------- | ----------------------- | ------------------- |
65
+ | equality | `{ author: "user-id" }` | Match by related ID |
66
+
67
+ ## Combining Operators
68
+
69
+ ### Multiple Fields (AND)
70
+
71
+ All top-level fields are combined with AND:
72
+
73
+ ```ts
74
+ where: {
75
+ status: "published",
76
+ price: { gte: 1000 },
77
+ createdAt: { gte: "2025-01-01" },
78
+ }
79
+ // status = "published" AND price >= 1000 AND createdAt >= 2025-01-01
80
+ ```
81
+
82
+ ### Multiple Operators on Same Field (AND)
83
+
84
+ Multiple operators on one field are ANDed together:
85
+
86
+ ```ts
87
+ where: {
88
+ price: { gte: 1000, lt: 5000 },
89
+ }
90
+ // 1000 <= price < 5000
91
+ ```
92
+
93
+ ### Equality Shorthand
94
+
95
+ Direct values are equivalent to exact match:
96
+
97
+ ```ts
98
+ // These are equivalent:
99
+ where: {
100
+ status: "published";
101
+ }
102
+ where: {
103
+ status: {
104
+ eq: "published";
105
+ }
106
+ }
107
+ ```
108
+
109
+ ## Complete Example
110
+
111
+ ```ts
112
+ const result = await collections.products.find({
113
+ where: {
114
+ status: "published",
115
+ price: { gte: 1000, lte: 50000 },
116
+ title: { contains: "premium" },
117
+ category: { in: ["electronics", "software"] },
118
+ createdAt: { gte: "2025-01-01" },
119
+ },
120
+ orderBy: { price: "asc" },
121
+ limit: 20,
122
+ offset: 0,
123
+ with: { category: true },
124
+ });
125
+ ```