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,545 @@
1
+ # Infrastructure Adapters Reference
2
+
3
+ All adapter configurations for QUESTPIE production infrastructure.
4
+
5
+ ## Database
6
+
7
+ PostgreSQL with Drizzle ORM. Configured in `questpie.config.ts`:
8
+
9
+ ```ts
10
+ import { runtimeConfig } from "questpie";
11
+
12
+ export default runtimeConfig({
13
+ db: {
14
+ url: process.env.DATABASE_URL || "postgres://localhost/myapp",
15
+ },
16
+ });
17
+ ```
18
+
19
+ ### Field-to-Column Mapping
20
+
21
+ | QUESTPIE Field | Drizzle Column Type |
22
+ | -------------- | ------------------- |
23
+ | `f.text()` | `varchar` / `text` |
24
+ | `f.number()` | `integer` |
25
+ | `f.boolean()` | `boolean` |
26
+ | `f.date()` | `date` |
27
+ | `f.datetime()` | `timestamp` |
28
+ | `f.select()` | `varchar` |
29
+ | `f.json()` | `jsonb` |
30
+ | `f.object()` | `jsonb` |
31
+ | `.array()` | `jsonb` |
32
+ | `f.relation()` | `varchar` (FK) |
33
+
34
+ ### Raw Access
35
+
36
+ ```ts
37
+ handler: async ({ db }) => {
38
+ // Raw SQL
39
+ const result = await db.execute(sql`SELECT COUNT(*) FROM posts`);
40
+
41
+ // Drizzle query builder
42
+ const rows = await db.select().from(table).where(eq(table.id, id));
43
+ };
44
+ ```
45
+
46
+ ### Indexes
47
+
48
+ ```ts
49
+ import { uniqueIndex, index } from "drizzle-orm/pg-core";
50
+
51
+ collection("posts")
52
+ .fields(({ f }) => ({ slug: f.text().required() }))
53
+ .indexes(({ table }) => [
54
+ uniqueIndex("posts_slug_unique").on(table.slug),
55
+ index("posts_status_idx").on(table.status),
56
+ ]);
57
+ ```
58
+
59
+ ## Storage
60
+
61
+ File storage via [Flydrive](https://flydrive.dev/).
62
+
63
+ ### Local (Development)
64
+
65
+ Default adapter. Files stored on local filesystem:
66
+
67
+ ```ts
68
+ export default runtimeConfig({
69
+ storage: {
70
+ basePath: "/api",
71
+ },
72
+ });
73
+ ```
74
+
75
+ ### S3-Compatible (Production)
76
+
77
+ Works with AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2:
78
+
79
+ ```ts
80
+ import { S3Driver } from "flydrive/drivers/s3";
81
+
82
+ export default runtimeConfig({
83
+ storage: {
84
+ basePath: "/api",
85
+ driver: new S3Driver({
86
+ bucket: process.env.S3_BUCKET,
87
+ region: process.env.S3_REGION,
88
+ accessKeyId: process.env.S3_ACCESS_KEY,
89
+ secretAccessKey: process.env.S3_SECRET_KEY,
90
+ }),
91
+ },
92
+ });
93
+ ```
94
+
95
+ ### Upload Fields
96
+
97
+ ```ts
98
+ avatar: f.upload({
99
+ to: "assets", // target upload collection
100
+ mimeTypes: ["image/*"], // allowed MIME types
101
+ maxSize: 5_000_000, // max file size in bytes (5MB)
102
+ }),
103
+ ```
104
+
105
+ ### Client Upload
106
+
107
+ ```ts
108
+ const asset = await client.collections.assets.upload(file, {
109
+ onProgress: (percent) => console.log(`${percent}%`),
110
+ });
111
+
112
+ const assets = await client.collections.assets.uploadMany(files, {
113
+ onProgress: (percent) => console.log(`${percent}%`),
114
+ });
115
+ ```
116
+
117
+ ## Queue
118
+
119
+ Background jobs via [pg-boss](https://github.com/timgit/pg-boss). Jobs stored in PostgreSQL -- no external queue service needed.
120
+
121
+ ### Configuration
122
+
123
+ ```ts
124
+ import { pgBossAdapter, runtimeConfig } from "questpie";
125
+
126
+ export default runtimeConfig({
127
+ queue: {
128
+ adapter: pgBossAdapter({
129
+ connectionString: process.env.DATABASE_URL,
130
+ }),
131
+ },
132
+ });
133
+ ```
134
+
135
+ ### Publishing Jobs
136
+
137
+ The `queue` context object is fully typed:
138
+
139
+ ```ts
140
+ handler: async ({ queue }) => {
141
+ await queue.sendConfirmation.publish({
142
+ appointmentId: "abc",
143
+ customerId: "def",
144
+ });
145
+ };
146
+ ```
147
+
148
+ ## Realtime
149
+
150
+ SSE-based live updates.
151
+
152
+ ### pgNotify (Single Instance)
153
+
154
+ Uses PostgreSQL `LISTEN/NOTIFY`. Best for single-server deployments:
155
+
156
+ ```ts
157
+ import { pgNotifyAdapter, runtimeConfig } from "questpie";
158
+
159
+ export default runtimeConfig({
160
+ realtime: {
161
+ adapter: pgNotifyAdapter({
162
+ connectionString: process.env.DATABASE_URL,
163
+ }),
164
+ },
165
+ });
166
+ ```
167
+
168
+ ### Redis Streams (Multi-Instance)
169
+
170
+ Required for horizontal scaling across multiple server instances:
171
+
172
+ ```ts
173
+ import { redisStreamsAdapter, runtimeConfig } from "questpie";
174
+
175
+ export default runtimeConfig({
176
+ realtime: {
177
+ adapter: redisStreamsAdapter({
178
+ url: process.env.REDIS_URL,
179
+ }),
180
+ },
181
+ });
182
+ ```
183
+
184
+ ### When to Use Which
185
+
186
+ | Adapter | Use Case |
187
+ | --------------------- | ----------------------------------------------------- |
188
+ | `pgNotifyAdapter` | Single server, development, simple deployments |
189
+ | `redisStreamsAdapter` | Multiple servers, horizontal scaling, high throughput |
190
+
191
+ ## Email
192
+
193
+ Transactional email with typed templates.
194
+
195
+ ### SMTP (Production)
196
+
197
+ ```ts
198
+ import { SmtpAdapter, runtimeConfig } from "questpie";
199
+
200
+ export default runtimeConfig({
201
+ email: {
202
+ adapter: new SmtpAdapter({
203
+ transport: {
204
+ host: process.env.SMTP_HOST,
205
+ port: parseInt(process.env.SMTP_PORT || "587"),
206
+ secure: true,
207
+ },
208
+ }),
209
+ },
210
+ });
211
+ ```
212
+
213
+ ### Console (Development)
214
+
215
+ Logs emails to console instead of sending:
216
+
217
+ ```ts
218
+ import { ConsoleAdapter, runtimeConfig } from "questpie";
219
+
220
+ export default runtimeConfig({
221
+ email: {
222
+ adapter: new ConsoleAdapter({ logHtml: false }),
223
+ },
224
+ });
225
+ ```
226
+
227
+ ### Environment-Based Switching
228
+
229
+ ```ts
230
+ email: {
231
+ adapter:
232
+ process.env.NODE_ENV === "development"
233
+ ? new ConsoleAdapter({ logHtml: false })
234
+ : new SmtpAdapter({
235
+ transport: {
236
+ host: process.env.SMTP_HOST,
237
+ port: parseInt(process.env.SMTP_PORT || "587"),
238
+ secure: true,
239
+ },
240
+ }),
241
+ }
242
+ ```
243
+
244
+ ### Defining Templates
245
+
246
+ Templates go in the `emails/` directory:
247
+
248
+ ```ts
249
+ // emails/welcome.ts
250
+ import { email } from "questpie";
251
+ import z from "zod";
252
+
253
+ export default email({
254
+ name: "welcome",
255
+ schema: z.object({
256
+ userName: z.string(),
257
+ loginUrl: z.string(),
258
+ }),
259
+ handler: ({ input }) => ({
260
+ subject: `Welcome, ${input.userName}!`,
261
+ html: `<h1>Welcome!</h1><p><a href="${input.loginUrl}">Sign in here</a></p>`,
262
+ }),
263
+ });
264
+ ```
265
+
266
+ ### Sending
267
+
268
+ ```ts
269
+ handler: async ({ email }) => {
270
+ await email.sendTemplate({
271
+ template: "welcome",
272
+ input: { userName: "John", loginUrl: "https://app.example.com/login" },
273
+ to: "john@example.com",
274
+ });
275
+ };
276
+ ```
277
+
278
+ ## KV Store
279
+
280
+ Key-value storage for caching, rate limiting, ephemeral data.
281
+
282
+ ### Custom Adapter
283
+
284
+ ```ts
285
+ export default runtimeConfig({
286
+ kv: {
287
+ adapter: myKvAdapter,
288
+ defaultTtl: 3600,
289
+ },
290
+ });
291
+ ```
292
+
293
+ ### In-Memory Default
294
+
295
+ ```ts
296
+ export default runtimeConfig({
297
+ kv: {
298
+ defaultTtl: 3600,
299
+ },
300
+ });
301
+ ```
302
+
303
+ ### API
304
+
305
+ ```ts
306
+ handler: async ({ kv }) => {
307
+ // Set with TTL (seconds)
308
+ await kv.set("session:abc", JSON.stringify(data), { ttl: 3600 });
309
+
310
+ // Get
311
+ const value = await kv.get("session:abc");
312
+
313
+ // Delete
314
+ await kv.delete("session:abc");
315
+ };
316
+ ```
317
+
318
+ ## Logger
319
+
320
+ Structured logging via [Pino](https://getpino.io).
321
+
322
+ ### Usage
323
+
324
+ ```ts
325
+ handler: async ({ logger }) => {
326
+ logger.info("Processing request");
327
+ logger.error({ err: error }, "Request failed");
328
+ logger.debug({ userId, action }, "User action");
329
+ };
330
+ ```
331
+
332
+ ### Log Levels
333
+
334
+ | Level | Usage |
335
+ | ------- | --------------------- |
336
+ | `trace` | Detailed debugging |
337
+ | `debug` | Development debugging |
338
+ | `info` | Normal operations |
339
+ | `warn` | Potential issues |
340
+ | `error` | Errors |
341
+ | `fatal` | Critical failures |
342
+
343
+ ### Structured Data
344
+
345
+ Pass objects as the first argument:
346
+
347
+ ```ts
348
+ logger.info(
349
+ {
350
+ appointmentId: "abc",
351
+ action: "created",
352
+ barberId: "def",
353
+ },
354
+ "Appointment created",
355
+ );
356
+ ```
357
+
358
+ ## OpenAPI
359
+
360
+ Auto-generates OpenAPI 3.1 spec from your schema.
361
+
362
+ ### Setup
363
+
364
+ ```bash
365
+ bun add @questpie/openapi
366
+ ```
367
+
368
+ ```ts
369
+ // src/questpie/server/modules.ts
370
+ import { adminModule } from "@questpie/admin/server";
371
+ import { openApiModule } from "@questpie/openapi";
372
+
373
+ export default [adminModule, openApiModule] as const;
374
+ ```
375
+
376
+ Configure it in `config/openapi.ts`:
377
+
378
+ ```ts
379
+ import { openApiConfig } from "@questpie/openapi";
380
+
381
+ export default openApiConfig({
382
+ info: { title: "My API", version: "1.0.0" },
383
+ });
384
+ ```
385
+
386
+ Then run codegen:
387
+
388
+ ```bash
389
+ bunx questpie generate
390
+ ```
391
+
392
+ ### Configuration Options
393
+
394
+ ```ts
395
+ openApiConfig({
396
+ info: {
397
+ title: "My API",
398
+ version: "1.0.0",
399
+ description: "Backend for my app",
400
+ },
401
+ servers: [{ url: "https://api.example.com", description: "Production" }],
402
+ basePath: "/api",
403
+ exclude: {
404
+ collections: ["_internal_logs"],
405
+ globals: ["_cache"],
406
+ },
407
+ auth: true, // include auth endpoints
408
+ search: true, // include search endpoints
409
+ scalar: {
410
+ theme: "purple", // Scalar UI theme
411
+ },
412
+ specPath: "openapi.json", // custom spec route
413
+ docsPath: "docs", // custom docs route
414
+ });
415
+ ```
416
+
417
+ ### Routes
418
+
419
+ | Route | Description |
420
+ | ----------------------- | -------------------------------- |
421
+ | `GET /api/openapi.json` | OpenAPI 3.1 JSON spec |
422
+ | `GET /api/docs` | Scalar interactive API reference |
423
+
424
+ ### What Gets Documented
425
+
426
+ | Source | Documented As |
427
+ | ----------- | ------------------------------------------------------- |
428
+ | Collections | CRUD endpoints (`GET`, `POST`, `PUT`, `DELETE`) |
429
+ | Globals | Read/update endpoints (`GET`, `PUT`) |
430
+ | Routes | App route endpoints (`/api/{path}`) |
431
+ | Auth | Sign-in, sign-up, session endpoints (when `auth: true`) |
432
+ | Search | Search endpoints (when `search: true`) |
433
+
434
+ ### Manual Route Approach
435
+
436
+ Instead of the module, create route files directly:
437
+
438
+ ```ts
439
+ // routes/openapi.json.ts
440
+ import { openApiRoute } from "@questpie/openapi";
441
+
442
+ export default openApiRoute({
443
+ info: { title: "My API", version: "1.0.0" },
444
+ });
445
+ ```
446
+
447
+ ```ts
448
+ // routes/docs.ts
449
+ import { docsRoute } from "@questpie/openapi";
450
+
451
+ export default docsRoute({
452
+ scalar: { theme: "purple" },
453
+ });
454
+ ```
455
+
456
+ ### Programmatic Access
457
+
458
+ ```ts
459
+ import { generateOpenApiSpec } from "@questpie/openapi";
460
+
461
+ const spec = generateOpenApiSpec(app, {
462
+ info: { title: "My API", version: "1.0.0" },
463
+ });
464
+ ```
465
+
466
+ ## Migrations CLI
467
+
468
+ | Command | Description |
469
+ | -------------------------------- | ------------------------------------------------ |
470
+ | `bunx questpie push` | Direct schema sync (dev only, no migration file) |
471
+ | `bunx questpie migrate:generate` | Generate migration from schema diff |
472
+ | `bunx questpie migrate:up` | Run pending migrations |
473
+ | `bunx questpie migrate:down` | Rollback last migration |
474
+ | `bunx questpie migrate:fresh` | Drop all and re-run (DESTRUCTIVE) |
475
+ | `bunx questpie migrate:reset` | Reset migration tracking |
476
+ | `bunx questpie seed` | Run seed files |
477
+
478
+ ## Complete Production Config Example
479
+
480
+ ```ts
481
+ import {
482
+ runtimeConfig,
483
+ pgBossAdapter,
484
+ pgNotifyAdapter,
485
+ SmtpAdapter,
486
+ } from "questpie";
487
+ import { S3Driver } from "flydrive/drivers/s3";
488
+
489
+ export default runtimeConfig({
490
+ db: {
491
+ url: process.env.DATABASE_URL!,
492
+ },
493
+ storage: {
494
+ basePath: "/api",
495
+ driver: new S3Driver({
496
+ bucket: process.env.S3_BUCKET!,
497
+ region: process.env.S3_REGION!,
498
+ accessKeyId: process.env.S3_ACCESS_KEY!,
499
+ secretAccessKey: process.env.S3_SECRET_KEY!,
500
+ }),
501
+ },
502
+ queue: {
503
+ adapter: pgBossAdapter({
504
+ connectionString: process.env.DATABASE_URL!,
505
+ }),
506
+ },
507
+ realtime: {
508
+ adapter: pgNotifyAdapter({
509
+ connectionString: process.env.DATABASE_URL!,
510
+ }),
511
+ },
512
+ email: {
513
+ adapter: new SmtpAdapter({
514
+ transport: {
515
+ host: process.env.SMTP_HOST!,
516
+ port: parseInt(process.env.SMTP_PORT || "587"),
517
+ secure: true,
518
+ },
519
+ }),
520
+ },
521
+ kv: {
522
+ adapter: myKvAdapter,
523
+ defaultTtl: 3600,
524
+ },
525
+ cli: {
526
+ migrations: { directory: "./src/migrations" },
527
+ seeds: { directory: "./src/seeds" },
528
+ },
529
+ });
530
+ ```
531
+
532
+ ## Environment Variables Summary
533
+
534
+ | Variable | Service | Required |
535
+ | -------------------- | ----------------------------- | ------------------- |
536
+ | `DATABASE_URL` | Database, Queue, Realtime | Yes |
537
+ | `APP_URL` | Auth, Email links | Yes |
538
+ | `BETTER_AUTH_SECRET` | Auth sessions | Yes (production) |
539
+ | `REDIS_URL` | KV, Realtime (multi-instance) | No |
540
+ | `S3_BUCKET` | Storage | No (if using local) |
541
+ | `S3_REGION` | Storage | No |
542
+ | `S3_ACCESS_KEY` | Storage | No |
543
+ | `S3_SECRET_KEY` | Storage | No |
544
+ | `SMTP_HOST` | Email | No |
545
+ | `SMTP_PORT` | Email | No |