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
@@ -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: {
@@ -0,0 +1,147 @@
1
+ # MCP Integration
2
+
3
+ Use `@questpie/mcp` when a QUESTPIE app should expose collections, globals, annotated JSON routes, schemas, or custom tools to Model Context Protocol clients.
4
+
5
+ ## Static Module Pattern
6
+
7
+ MCP is codegen-aware. Keep `modules.ts` static and put options in `config/mcp.ts`.
8
+
9
+ ```ts title="modules.ts"
10
+ import mcpModule from "@questpie/mcp";
11
+
12
+ export default [mcpModule] as const;
13
+ ```
14
+
15
+ ```ts title="config/mcp.ts"
16
+ import { mcpConfig } from "@questpie/mcp";
17
+
18
+ export default mcpConfig({
19
+ crud: {
20
+ defaults: {
21
+ collections: { read: true, write: false, delete: false },
22
+ globals: { read: true, write: false },
23
+ },
24
+ collections: {
25
+ posts: { read: true, write: true },
26
+ users: false,
27
+ },
28
+ globals: {
29
+ siteSettings: { read: true, write: true },
30
+ },
31
+ },
32
+ routes: {
33
+ exposeAnnotated: true,
34
+ },
35
+ });
36
+ ```
37
+
38
+ Do not use `mcpModule(options)`. Runtime options belong in the plugin-discovered config file.
39
+
40
+ `mcpModule` carries its codegen plugin. Do not also add `mcpPlugin()` to `questpie.config.ts` unless you are doing a custom setup that deliberately omits `mcpModule` — double registration duplicates the plugin.
41
+
42
+ ## CRUD Policy
43
+
44
+ Generated collection tools:
45
+
46
+ - `collections.{name}.list`
47
+ - `collections.{name}.count`
48
+ - `collections.{name}.get`
49
+ - `collections.{name}.create`
50
+ - `collections.{name}.update`
51
+ - `collections.{name}.delete`
52
+
53
+ Generated global tools:
54
+
55
+ - `globals.{name}.get`
56
+ - `globals.{name}.update`
57
+
58
+ Policy order:
59
+
60
+ 1. Transport defaults.
61
+ 2. CRUD defaults.
62
+ 3. Per-entity override.
63
+ 4. QUESTPIE access rules execute last and can still deny.
64
+
65
+ HTTP is user mode and read-oriented by default. HTTP cannot be made system mode with config or options. Stdio defaults to trusted system mode unless explicitly lowered to user mode.
66
+
67
+ Use `fields.include` / `fields.exclude` for top-level filtering. It applies to create/update input, CRUD outputs, list docs, global results, and schema resources. Nested relation projection is out of scope for v1.
68
+
69
+ ## Route Tools
70
+
71
+ Only simple JSON routes are auto-converted:
72
+
73
+ - Route has `.schema(...)`.
74
+ - Route is not `.raw()`.
75
+ - Route has `meta.mcp.expose === true`.
76
+ - `routes.exposeAnnotated` is not `false`.
77
+
78
+ ```ts
79
+ route()
80
+ .post()
81
+ .schema(inputSchema)
82
+ .outputSchema(outputSchema)
83
+ .meta({
84
+ title: "Generate report",
85
+ mcp: {
86
+ expose: true,
87
+ name: "reports.generate",
88
+ annotations: { readOnlyHint: true },
89
+ },
90
+ })
91
+ .handler(async ({ input }) => ({ ok: true }));
92
+ ```
93
+
94
+ Routes without path params use the route input schema directly. Routes with params use `{ params, input }`. Route policy keys use the route key, not the overridden tool name.
95
+
96
+ ## Resources
97
+
98
+ Built-in resources:
99
+
100
+ - `questpie://schema/collections`
101
+ - `questpie://schema/collections/{name}`
102
+ - `questpie://schema/globals`
103
+ - `questpie://schema/globals/{name}`
104
+ - `questpie://schema/routes`
105
+ - `questpie://schema/routes/{key}`
106
+
107
+ Resources honor MCP policy and QUESTPIE access visibility. Route resources include input/output JSON Schema when the route has Zod schemas.
108
+
109
+ ## Custom Tools
110
+
111
+ Custom tools live in `mcp-tools/` and are discovered by codegen.
112
+
113
+ ```ts
114
+ import { mcpTool } from "@questpie/mcp";
115
+ import { z } from "zod";
116
+
117
+ export default mcpTool("generate-report", {
118
+ description: "Generate a report.",
119
+ inputSchema: z.object({ period: z.string() }),
120
+ access: ({ session }) => !!session,
121
+ }).handler(async ({ input, ctx }) => ({
122
+ structuredContent: await ctx.services.reports.generate(input),
123
+ }));
124
+ ```
125
+
126
+ Custom tool access is checked during `tools/list` and again during `tools/call`.
127
+
128
+ ## Programmatic Servers
129
+
130
+ Use `createMcpServer(app, { transport: "http", request })` for programmatic HTTP setup. If no `ctx` is passed, the request is preserved through `app.createContext()`.
131
+
132
+ Use `startStdioServer(app)` for trusted stdio integrations:
133
+
134
+ ```ts
135
+ import { app } from "#questpie";
136
+ import { startStdioServer } from "@questpie/mcp/stdio";
137
+
138
+ await startStdioServer(app);
139
+ ```
140
+
141
+ ## Gotchas
142
+
143
+ - Add `mcpModule` to static `modules.ts`, then run codegen.
144
+ - HTTP system mode is intentionally impossible until a future trusted-token design exists.
145
+ - Field filtering is top-level only.
146
+ - Raw routes and unannotated routes are not tools.
147
+ - Custom tool results should use `structuredContent` for machine-readable output.
@@ -278,8 +278,7 @@ For advanced cases, create a request-scoped service that provides a tenant-aware
278
278
 
279
279
  ```ts
280
280
  // services/scoped-db.ts
281
- import { service } from "questpie";
282
-
281
+ import { service } from "questpie/services";
283
282
  export default service({
284
283
  lifecycle: "request",
285
284
  deps: ["db", "session"] as const,