create-questpie 2.0.3 → 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 (35) hide show
  1. package/dist/index.mjs +244 -30
  2. package/package.json +1 -1
  3. package/skills/questpie/AGENTS.md +299 -98
  4. package/skills/questpie/SKILL.md +50 -17
  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 +22 -6
  10. package/skills/questpie/references/extend.md +34 -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 +6 -8
  17. package/skills/questpie/references/rules.md +86 -21
  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 +47 -40
  23. package/skills/questpie-admin/SKILL.md +46 -39
  24. package/skills/questpie-admin/references/custom-ui.md +1 -1
  25. package/templates/tanstack-start/AGENTS.md +15 -8
  26. package/templates/tanstack-start/CLAUDE.md +12 -5
  27. package/templates/tanstack-start/README.md +7 -6
  28. package/templates/tanstack-start/package.json +1 -0
  29. package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
  30. package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
  31. package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
  32. package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
  33. package/templates/tanstack-start/src/questpie/server/questpie.config.ts +2 -1
  34. package/templates/tanstack-start/src/routes/api/$.ts +1 -2
  35. package/templates/tanstack-start/vite.config.ts +2 -2
@@ -62,6 +62,24 @@ export default collection("posts")
62
62
  | `.options({...})` | Timestamps, versioning, soft delete |
63
63
  | `.search({...})` | Search indexing |
64
64
  | `.searchable(string[])` | Searchable fields |
65
+ | `.merge(other)` | Extend a same-name builder (see below) |
66
+
67
+ ### Extending Collections — `.merge()`
68
+
69
+ To extend a collection a module already provides, merge its builder — never redefine the collection from scratch (same-key registration replaces the module's collection wholesale):
70
+
71
+ ```ts
72
+ import { starterModule } from "questpie/app";
73
+ import { collection } from "#questpie/factories";
74
+
75
+ export default collection("user")
76
+ .merge(starterModule.collections.user)
77
+ .fields(({ f }) => ({
78
+ internalNotes: f.textarea(),
79
+ }));
80
+ ```
81
+
82
+ Fields/options/extension keys combine by key (merged-in side wins), hooks concatenate, and `.fields()` after `.merge()` is cumulative — it never wipes merged fields. The result stays fully typed.
65
83
 
66
84
  ### Collection Options
67
85
 
@@ -242,8 +260,7 @@ Every field accepts:
242
260
  ### Virtual (Computed) Fields
243
261
 
244
262
  ```ts
245
- import { sql } from "questpie";
246
-
263
+ import { sql } from "questpie/builders";
247
264
  displayTitle: f.text().virtual(sql<string>`(
248
265
  SELECT COALESCE(name, 'Unknown') || ' - ' ||
249
266
  TO_CHAR("scheduledAt", 'YYYY-MM-DD HH24:MI')
@@ -260,7 +277,7 @@ All relations are defined via `f.relation()` inside `.fields()`.
260
277
  ### Belongs-To (Single)
261
278
 
262
279
  ```ts
263
- author: f.relation("users").required(),
280
+ author: f.relation("user").required(),
264
281
  barber: f.relation("barbers").required().onDelete("cascade"),
265
282
  ```
266
283
 
@@ -319,8 +336,7 @@ const appointments = await collections.appointments.find({
319
336
  ### Locale Configuration
320
337
 
321
338
  ```ts title="config/app.ts"
322
- import { appConfig } from "questpie";
323
-
339
+ import { appConfig } from "questpie/app";
324
340
  export default appConfig({
325
341
  locale: {
326
342
  locales: [
@@ -450,7 +466,7 @@ collection("posts").relations({ author: belongsTo("users") });
450
466
 
451
467
  // CORRECT -- use f.relation() inside .fields()
452
468
  collection("posts").fields(({ f }) => ({
453
- author: f.relation("users"),
469
+ author: f.relation("user"),
454
470
  }));
455
471
  ```
456
472
 
@@ -15,7 +15,7 @@ A plugin tells codegen what to discover and what types to generate. Plugins cont
15
15
  ### Plugin Structure
16
16
 
17
17
  ```ts
18
- import type { CodegenPlugin } from "questpie";
18
+ import type { CodegenPlugin } from "questpie/codegen";
19
19
 
20
20
  export function myPlugin(): CodegenPlugin {
21
21
  return {
@@ -76,7 +76,7 @@ export function myPlugin(): CodegenPlugin {
76
76
  ### Register in Config
77
77
 
78
78
  ```ts title="questpie.config.ts"
79
- import { runtimeConfig } from "questpie";
79
+ import { runtimeConfig } from "questpie/app";
80
80
  import { myPlugin } from "my-plugin-package";
81
81
 
82
82
  export default runtimeConfig({
@@ -167,7 +167,9 @@ The admin module contributes a codegen plugin to both `"server"` and `"admin-cli
167
167
  A module is a reusable package that contributes entities to any QUESTPIE project.
168
168
 
169
169
  ```ts
170
- import { module, collection, job } from "questpie";
170
+ import { module } from "questpie/app";
171
+ import { collection } from "questpie/builders";
172
+ import { job } from "questpie/services";
171
173
  import { z } from "zod";
172
174
 
173
175
  const notificationsCollection = collection("notifications")
@@ -235,10 +237,35 @@ export const notificationsModule = module({
235
237
  | `seeds` | `Seed[]` | Seed data |
236
238
  | `messages` | `Record` | i18n translations |
237
239
 
240
+ ### How Module Contributions Merge
241
+
242
+ When several modules (and the app) contribute the same key, `createApp()` merges them deterministically — later modules win per entry:
243
+
244
+ | Key | Strategy |
245
+ | --- | --- |
246
+ | `collections`, `globals`, `jobs`, `routes`, `fields`, `services` | record spread-merge — same key: later wins |
247
+ | `messages` | deep-merge by locale — same message key: later wins |
248
+ | `migrations`, `seeds` | array concatenation |
249
+ | `config.*` (app, auth, admin, plugin config keys) | per-key strategies; `auth`/`admin` deep-merge; unknown keys: incoming replaces existing |
250
+ | anything else | auto-detect: object+object → spread, array+array → concat, otherwise incoming wins |
251
+
252
+ The merge helpers behind these strategies are exported from `questpie/app` for module authors combining config fragments of their own:
253
+
254
+ ```ts
255
+ import { lastWins, mergeConcat, mergeDeepConcat, mergeRecord, type MergeFn } from "questpie/app";
256
+
257
+ mergeRecord(a, b); // { ...a, ...b }
258
+ mergeConcat(a, b); // [...a, ...b]
259
+ mergeDeepConcat(a, b); // spread objects, concat array-valued props
260
+ lastWins(a, b); // b
261
+ ```
262
+
263
+ Use them (instead of hand-rolled spreads) when a module exposes its own "combine these contributions" surface — the semantics then match what the framework does for built-in keys.
264
+
238
265
  ### Using a Module
239
266
 
240
267
  ```ts title="modules.ts"
241
- import { adminModule } from "@questpie/admin/server";
268
+ import { adminModule } from "@questpie/admin/modules/admin";
242
269
  import { notificationsModule } from "my-notifications-package";
243
270
 
244
271
  export default [adminModule, notificationsModule] as const;
@@ -281,7 +308,7 @@ A custom field defines:
281
308
  The `Field` class is an immutable builder:
282
309
 
283
310
  ```ts
284
- import { Field } from "questpie";
311
+ import { Field } from "questpie/builders";
285
312
 
286
313
  // Each method returns a new Field with updated type state
287
314
  f.text(255).required().label({ en: "Name" }).admin({ placeholder: "..." });
@@ -312,7 +339,7 @@ QUESTPIE ships with adapters for Hono, Elysia, and Next.js. For other frameworks
312
339
  ### Generic Fetch Handler
313
340
 
314
341
  ```ts
315
- import { createFetchHandler } from "questpie";
342
+ import { createFetchHandler } from "questpie/http";
316
343
  import { app } from "#questpie";
317
344
 
318
345
  const handler = createFetchHandler(app, { basePath: "/api" });
@@ -360,7 +387,7 @@ export const { GET, POST, PATCH, DELETE } = questpieNextRouteHandlers(app, {
360
387
 
361
388
  ```ts title="src/routes/api/$.ts"
362
389
  import { createAPIFileRoute } from "@tanstack/react-start/api";
363
- import { createFetchHandler } from "questpie";
390
+ import { createFetchHandler } from "questpie/http";
364
391
  import { app } from "#questpie";
365
392
 
366
393
  const handler = createFetchHandler(app, { basePath: "/api" });
@@ -14,6 +14,9 @@ Complete configuration patterns for built-in QUESTPIE field types. Fields use fl
14
14
  | `.inputOptional()` | Optional in API input but required in DB |
15
15
  | `.admin(config)` | Admin UI rendering hints |
16
16
  | `.virtual(sql)` | SQL expression for computed read-only field |
17
+ | `.zod(fn)` | Extend/replace Zod schema (output narrows value type) |
18
+ | `.drizzle(fn)` | Raw Drizzle column builder — constraints/SQL defaults land in DDL; `$type` narrows value type |
19
+ | `.$type<T>()` | Explicitly set TS value type (type-level; mainly json) |
17
20
 
18
21
  ## `f.text(options?)`
19
22
 
@@ -206,7 +209,7 @@ Reference to another collection.
206
209
  Belongs-to (single):
207
210
 
208
211
  ```ts
209
- author: f.relation("users").required(),
212
+ author: f.relation("user").required(),
210
213
  category: f.relation("categories").onDelete("set null"),
211
214
  ```
212
215
 
@@ -355,13 +358,22 @@ pageContent: f.blocks(),
355
358
 
356
359
  ## `f.json(options?)`
357
360
 
358
- Raw JSON data. No schema validation.
361
+ Raw JSON data. No schema validation by default; value types as loose `JsonValue`.
359
362
 
360
363
  ```ts
361
364
  metadata: f.json(),
362
365
  rawConfig: f.json().label("Configuration"),
363
366
  ```
364
367
 
368
+ Type it explicitly with `.$type<T>()` (type only) or `.zod()` (type + runtime validation) — the type flows into CRUD select/insert types:
369
+
370
+ ```ts
371
+ type Layout = { rows: { id: string; span: number }[] };
372
+
373
+ layout: f.json().$type<Layout>(),
374
+ settings: f.json().zod(() => z.object({ theme: z.enum(["light", "dark"]) })),
375
+ ```
376
+
365
377
  ## Admin Meta Options
366
378
 
367
379
  The `meta.admin` object controls field rendering in the admin panel:
@@ -7,8 +7,7 @@ All adapter configurations for QUESTPIE production infrastructure.
7
7
  PostgreSQL with Drizzle ORM. Configured in `questpie.config.ts`:
8
8
 
9
9
  ```ts
10
- import { runtimeConfig } from "questpie";
11
-
10
+ import { runtimeConfig } from "questpie/app";
12
11
  export default runtimeConfig({
13
12
  db: {
14
13
  url: process.env.DATABASE_URL || "postgres://localhost/myapp",
@@ -58,7 +57,7 @@ collection("posts")
58
57
 
59
58
  ## Storage
60
59
 
61
- File storage via [Flydrive](https://flydrive.dev/).
60
+ File storage via [Files SDK](https://files-sdk.dev/).
62
61
 
63
62
  ### Local (Development)
64
63
 
@@ -74,19 +73,42 @@ export default runtimeConfig({
74
73
 
75
74
  ### S3-Compatible (Production)
76
75
 
77
- Works with AWS S3, MinIO, DigitalOcean Spaces, Cloudflare R2:
76
+ Works with AWS S3, MinIO, DigitalOcean Spaces, and other S3-compatible
77
+ providers that do not have a dedicated Files SDK adapter:
78
78
 
79
79
  ```ts
80
- import { S3Driver } from "flydrive/drivers/s3";
80
+ import { s3 } from "files-sdk/s3";
81
81
 
82
82
  export default runtimeConfig({
83
83
  storage: {
84
84
  basePath: "/api",
85
- driver: new S3Driver({
85
+ adapter: s3({
86
86
  bucket: process.env.S3_BUCKET,
87
87
  region: process.env.S3_REGION,
88
- accessKeyId: process.env.S3_ACCESS_KEY,
89
- secretAccessKey: process.env.S3_SECRET_KEY,
88
+ credentials: {
89
+ accessKeyId: process.env.S3_ACCESS_KEY!,
90
+ secretAccessKey: process.env.S3_SECRET_KEY!,
91
+ },
92
+ }),
93
+ },
94
+ });
95
+ ```
96
+
97
+ ### Cloudflare R2 (Production)
98
+
99
+ Use Files SDK's dedicated R2 adapter:
100
+
101
+ ```ts
102
+ import { r2 } from "files-sdk/r2";
103
+
104
+ export default runtimeConfig({
105
+ storage: {
106
+ basePath: "/api",
107
+ adapter: r2({
108
+ bucket: process.env.R2_BUCKET!,
109
+ accountId: process.env.R2_ACCOUNT_ID!,
110
+ accessKeyId: process.env.R2_ACCESS_KEY_ID!,
111
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
90
112
  }),
91
113
  },
92
114
  });
@@ -114,14 +136,41 @@ const assets = await client.collections.assets.uploadMany(files, {
114
136
  });
115
137
  ```
116
138
 
139
+ Failed uploads reject with `UploadError` (from `questpie/client`) carrying the HTTP status and server message.
140
+
141
+ ### Signed URLs (Private Files)
142
+
143
+ Upload rows carry a `url` populated automatically on read: `visibility: "public"` files get a plain collection-scoped URL (`{basePath}/{collection}/files/{key}`); `"private"` files get an HMAC-signed token appended (`?token=...`), signed with `app.config.secret` and expiring after `storage.signedUrlExpiration` (default `3600` seconds):
144
+
145
+ ```ts
146
+ export default runtimeConfig({
147
+ storage: {
148
+ basePath: "/api",
149
+ signedUrlExpiration: 900, // 15 minutes
150
+ },
151
+ });
152
+ ```
153
+
154
+ Serving stays collection-scoped — the file route verifies the token **and** the collection's `serve` access rule. To mint URLs manually (custom emails, server-rendered pages):
155
+
156
+ ```ts
157
+ import { buildStorageFileUrl, generateSignedUrlToken } from "questpie/storage";
158
+
159
+ const token = await generateSignedUrlToken(asset.key, app.config.secret!, 900, "assets");
160
+ const url = buildStorageFileUrl(app.config.app.url, "/api", "assets", asset.key, token);
161
+ ```
162
+
163
+ (`verifySignedUrlToken` is the verification half the serve route runs — you rarely call it yourself.)
164
+
117
165
  ## Queue
118
166
 
119
- Background jobs via [pg-boss](https://github.com/timgit/pg-boss). Jobs stored in PostgreSQL -- no external queue service needed.
167
+ Background jobs via [pg-boss](https://github.com/timgit/pg-boss), BullMQ, or a custom queue adapter.
120
168
 
121
169
  ### Configuration
122
170
 
123
171
  ```ts
124
- import { pgBossAdapter, runtimeConfig } from "questpie";
172
+ import { runtimeConfig } from "questpie/app";
173
+ import { pgBossAdapter } from "questpie/adapters/pg-boss";
125
174
 
126
175
  export default runtimeConfig({
127
176
  queue: {
@@ -132,6 +181,24 @@ export default runtimeConfig({
132
181
  });
133
182
  ```
134
183
 
184
+ ### BullMQ
185
+
186
+ ```ts
187
+ import { runtimeConfig } from "questpie/app";
188
+ import { bullMQAdapter } from "questpie/adapters/bullmq";
189
+
190
+ export default runtimeConfig({
191
+ queue: {
192
+ adapter: bullMQAdapter({
193
+ connection: { url: process.env.REDIS_URL! },
194
+ queuePrefix: "my-app",
195
+ }),
196
+ },
197
+ });
198
+ ```
199
+
200
+ The built-in BullMQ adapter targets open-source BullMQ and does not expose per-group FIFO behavior. Use a native grouped queue adapter if the workload needs one active job per group with cross-group parallelism.
201
+
135
202
  ### Publishing Jobs
136
203
 
137
204
  The `queue` context object is fully typed:
@@ -154,7 +221,8 @@ SSE-based live updates.
154
221
  Uses PostgreSQL `LISTEN/NOTIFY`. Best for single-server deployments:
155
222
 
156
223
  ```ts
157
- import { pgNotifyAdapter, runtimeConfig } from "questpie";
224
+ import { runtimeConfig } from "questpie/app";
225
+ import { pgNotifyAdapter } from "questpie/adapters/pg-notify";
158
226
 
159
227
  export default runtimeConfig({
160
228
  realtime: {
@@ -170,7 +238,8 @@ export default runtimeConfig({
170
238
  Required for horizontal scaling across multiple server instances:
171
239
 
172
240
  ```ts
173
- import { redisStreamsAdapter, runtimeConfig } from "questpie";
241
+ import { runtimeConfig } from "questpie/app";
242
+ import { redisStreamsAdapter } from "questpie/adapters/redis-streams";
174
243
 
175
244
  export default runtimeConfig({
176
245
  realtime: {
@@ -188,6 +257,48 @@ export default runtimeConfig({
188
257
  | `pgNotifyAdapter` | Single server, development, simple deployments |
189
258
  | `redisStreamsAdapter` | Multiple servers, horizontal scaling, high throughput |
190
259
 
260
+ ## Search
261
+
262
+ Full-text search via PostgreSQL (no extra service). The adapter goes directly on the `search` key:
263
+
264
+ ```ts
265
+ import { runtimeConfig } from "questpie/app";
266
+ import { createPostgresSearchAdapter } from "questpie/adapters/postgres-search";
267
+
268
+ export default runtimeConfig({
269
+ search: createPostgresSearchAdapter(), // pg_trgm + tsvector FTS
270
+ });
271
+ ```
272
+
273
+ ### Semantic Search (pgvector + Embeddings)
274
+
275
+ `createPgVectorSearchAdapter` wraps the Postgres adapter and adds an `embedding` vector column + cosine-distance search. It needs the `pgvector` extension (`CREATE EXTENSION "vector";` — the adapter ships its own migrations) and an embedding provider:
276
+
277
+ ```ts
278
+ import { runtimeConfig } from "questpie/app";
279
+ import {
280
+ createOpenAIEmbeddingProvider,
281
+ createPgVectorSearchAdapter,
282
+ } from "questpie/adapters/pgvector-search";
283
+
284
+ export default runtimeConfig({
285
+ search: createPgVectorSearchAdapter({
286
+ embeddingProvider: createOpenAIEmbeddingProvider({
287
+ apiKey: process.env.OPENAI_API_KEY!,
288
+ model: "text-embedding-3-small",
289
+ }),
290
+ // Hybrid scoring weights (defaults shown)
291
+ lexicalWeight: 0.4,
292
+ semanticWeight: 0.6,
293
+ indexType: "ivfflat", // or "hnsw"
294
+ }),
295
+ });
296
+ ```
297
+
298
+ Search modes: `lexical` (FTS + trigram), `semantic` (pure vector similarity), `hybrid` (weighted combination). Embeddings are generated on `index()`; if generation fails, the row still indexes for lexical search.
299
+
300
+ For non-OpenAI providers, `createCustomEmbeddingProvider({ name, model, dimensions, generate })` wraps any embedding function.
301
+
191
302
  ## Email
192
303
 
193
304
  Transactional email with typed templates.
@@ -195,7 +306,8 @@ Transactional email with typed templates.
195
306
  ### SMTP (Production)
196
307
 
197
308
  ```ts
198
- import { SmtpAdapter, runtimeConfig } from "questpie";
309
+ import { runtimeConfig } from "questpie/app";
310
+ import { SmtpAdapter } from "questpie/adapters/smtp";
199
311
 
200
312
  export default runtimeConfig({
201
313
  email: {
@@ -215,7 +327,8 @@ export default runtimeConfig({
215
327
  Logs emails to console instead of sending:
216
328
 
217
329
  ```ts
218
- import { ConsoleAdapter, runtimeConfig } from "questpie";
330
+ import { runtimeConfig } from "questpie/app";
331
+ import { ConsoleAdapter } from "questpie/adapters/console";
219
332
 
220
333
  export default runtimeConfig({
221
334
  email: {
@@ -224,6 +337,46 @@ export default runtimeConfig({
224
337
  });
225
338
  ```
226
339
 
340
+ ### Resend (HTTP API)
341
+
342
+ For [Resend](https://resend.com) and Resend-compatible providers — no SMTP credentials needed:
343
+
344
+ ```ts
345
+ import { runtimeConfig } from "questpie/app";
346
+ import { resendAdapter } from "questpie/adapters/resend";
347
+
348
+ export default runtimeConfig({
349
+ email: {
350
+ adapter: resendAdapter({
351
+ apiKey: process.env.RESEND_API_KEY!,
352
+ // baseUrl: "https://api.resend.com", // override for compatible providers
353
+ }),
354
+ },
355
+ });
356
+ ```
357
+
358
+ ### Plunk (HTTP API)
359
+
360
+ For [Plunk](https://www.useplunk.com) transactional email (also self-hosted):
361
+
362
+ ```ts
363
+ import { runtimeConfig } from "questpie/app";
364
+ import { plunkAdapter } from "questpie/adapters/plunk";
365
+
366
+ export default runtimeConfig({
367
+ email: {
368
+ adapter: plunkAdapter({
369
+ apiKey: process.env.PLUNK_API_KEY!, // secret key — public keys only track events
370
+ // baseUrl: "https://next-api.useplunk.com", // override for self-hosted
371
+ }),
372
+ },
373
+ });
374
+ ```
375
+
376
+ ### Custom Mail Adapter
377
+
378
+ For any other provider, extend the `MailAdapter` base class from `questpie/mailer` (implement `send(options)`) and pass an instance as `email.adapter`.
379
+
227
380
  ### Environment-Based Switching
228
381
 
229
382
  ```ts
@@ -247,7 +400,7 @@ Templates go in the `emails/` directory:
247
400
 
248
401
  ```ts
249
402
  // emails/welcome.ts
250
- import { email } from "questpie";
403
+ import { email } from "questpie/services";
251
404
  import z from "zod";
252
405
 
253
406
  export default email({
@@ -279,6 +432,26 @@ handler: async ({ email }) => {
279
432
 
280
433
  Key-value storage for caching, rate limiting, ephemeral data.
281
434
 
435
+ ### Redis
436
+
437
+ ```ts
438
+ import { createClient } from "redis";
439
+ import { redisKVAdapter } from "questpie/adapters/redis-kv";
440
+
441
+ async function getRedis() {
442
+ const redis = createClient({ url: process.env.REDIS_URL! });
443
+ await redis.connect();
444
+ return redis;
445
+ }
446
+
447
+ export default runtimeConfig({
448
+ kv: {
449
+ adapter: redisKVAdapter({ client: getRedis, keyPrefix: "my-app:" }),
450
+ defaultTtl: 3600,
451
+ },
452
+ });
453
+ ```
454
+
282
455
  ### Custom Adapter
283
456
 
284
457
  ```ts
@@ -305,7 +478,7 @@ export default runtimeConfig({
305
478
  ```ts
306
479
  handler: async ({ kv }) => {
307
480
  // Set with TTL (seconds)
308
- await kv.set("session:abc", JSON.stringify(data), { ttl: 3600 });
481
+ await kv.set("session:abc", JSON.stringify(data), 3600);
309
482
 
310
483
  // Get
311
484
  const value = await kv.get("session:abc");
@@ -367,16 +540,18 @@ bun add @questpie/openapi
367
540
 
368
541
  ```ts
369
542
  // src/questpie/server/modules.ts
370
- import { adminModule } from "@questpie/admin/server";
371
- import { openApiModule } from "@questpie/openapi";
543
+ import { adminModule } from "@questpie/admin/modules/admin";
544
+ import { openApiModule } from "@questpie/openapi/modules/openapi";
372
545
 
373
546
  export default [adminModule, openApiModule] as const;
374
547
  ```
375
548
 
549
+ `openApiModule` carries its codegen plugin — do not also add `openApiPlugin()` to `questpie.config.ts` unless you deliberately omit the module.
550
+
376
551
  Configure it in `config/openapi.ts`:
377
552
 
378
553
  ```ts
379
- import { openApiConfig } from "@questpie/openapi";
554
+ import { openApiConfig } from "@questpie/openapi/server";
380
555
 
381
556
  export default openApiConfig({
382
557
  info: { title: "My API", version: "1.0.0" },
@@ -437,7 +612,7 @@ Instead of the module, create route files directly:
437
612
 
438
613
  ```ts
439
614
  // routes/openapi.json.ts
440
- import { openApiRoute } from "@questpie/openapi";
615
+ import { openApiRoute } from "@questpie/openapi/server";
441
616
 
442
617
  export default openApiRoute({
443
618
  info: { title: "My API", version: "1.0.0" },
@@ -446,7 +621,7 @@ export default openApiRoute({
446
621
 
447
622
  ```ts
448
623
  // routes/docs.ts
449
- import { docsRoute } from "@questpie/openapi";
624
+ import { docsRoute } from "@questpie/openapi/server";
450
625
 
451
626
  export default docsRoute({
452
627
  scalar: { theme: "purple" },
@@ -456,7 +631,7 @@ export default docsRoute({
456
631
  ### Programmatic Access
457
632
 
458
633
  ```ts
459
- import { generateOpenApiSpec } from "@questpie/openapi";
634
+ import { generateOpenApiSpec } from "@questpie/openapi/server";
460
635
 
461
636
  const spec = generateOpenApiSpec(app, {
462
637
  info: { title: "My API", version: "1.0.0" },
@@ -478,13 +653,11 @@ const spec = generateOpenApiSpec(app, {
478
653
  ## Complete Production Config Example
479
654
 
480
655
  ```ts
481
- import {
482
- runtimeConfig,
483
- pgBossAdapter,
484
- pgNotifyAdapter,
485
- SmtpAdapter,
486
- } from "questpie";
487
- import { S3Driver } from "flydrive/drivers/s3";
656
+ import { runtimeConfig } from "questpie/app";
657
+ import { pgBossAdapter } from "questpie/adapters/pg-boss";
658
+ import { pgNotifyAdapter } from "questpie/adapters/pg-notify";
659
+ import { SmtpAdapter } from "questpie/adapters/smtp";
660
+ import { s3 } from "files-sdk/s3";
488
661
 
489
662
  export default runtimeConfig({
490
663
  db: {
@@ -492,11 +665,13 @@ export default runtimeConfig({
492
665
  },
493
666
  storage: {
494
667
  basePath: "/api",
495
- driver: new S3Driver({
668
+ adapter: s3({
496
669
  bucket: process.env.S3_BUCKET!,
497
670
  region: process.env.S3_REGION!,
498
- accessKeyId: process.env.S3_ACCESS_KEY!,
499
- secretAccessKey: process.env.S3_SECRET_KEY!,
671
+ credentials: {
672
+ accessKeyId: process.env.S3_ACCESS_KEY!,
673
+ secretAccessKey: process.env.S3_SECRET_KEY!,
674
+ },
500
675
  }),
501
676
  },
502
677
  queue: {