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.
- package/dist/index.mjs +244 -30
- package/package.json +1 -1
- package/skills/questpie/AGENTS.md +310 -103
- package/skills/questpie/SKILL.md +196 -84
- package/skills/questpie/coverage.json +213 -0
- package/skills/questpie/references/auth.md +119 -4
- package/skills/questpie/references/business-logic.md +126 -56
- package/skills/questpie/references/crud-api.md +231 -29
- package/skills/questpie/references/data-modeling.md +26 -6
- package/skills/questpie/references/extend.md +98 -7
- package/skills/questpie/references/field-types.md +14 -2
- package/skills/questpie/references/infrastructure-adapters.md +207 -32
- package/skills/questpie/references/mcp.md +147 -0
- package/skills/questpie/references/multi-tenancy.md +1 -2
- package/skills/questpie/references/production.md +218 -53
- package/skills/questpie/references/quickstart.md +31 -18
- package/skills/questpie/references/rules.md +140 -13
- package/skills/questpie/references/sandbox.md +110 -0
- package/skills/questpie/references/tanstack-query.md +34 -11
- package/skills/questpie/references/type-inference.md +167 -0
- package/skills/questpie/references/workflows.md +155 -0
- package/skills/questpie-admin/AGENTS.md +141 -68
- package/skills/questpie-admin/SKILL.md +96 -63
- package/skills/questpie-admin/references/blocks.md +28 -4
- package/skills/questpie-admin/references/custom-ui.md +1 -1
- package/skills/questpie-admin/references/views.md +21 -5
- package/templates/tanstack-start/AGENTS.md +15 -8
- package/templates/tanstack-start/CLAUDE.md +12 -5
- package/templates/tanstack-start/README.md +7 -6
- package/templates/tanstack-start/package.json +1 -0
- package/templates/tanstack-start/src/lib/query-client.ts +10 -1
- package/templates/tanstack-start/src/questpie/admin/modules.ts +3 -1
- package/templates/tanstack-start/src/questpie/server/.generated/factories.ts +10 -9
- package/templates/tanstack-start/src/questpie/server/config/auth.ts +1 -1
- package/templates/tanstack-start/src/questpie/server/modules.ts +4 -5
- package/templates/tanstack-start/src/questpie/server/questpie.config.ts +2 -1
- 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/api/$.ts +1 -2
- 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 [
|
|
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,
|
|
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 {
|
|
80
|
+
import { s3 } from "files-sdk/s3";
|
|
81
81
|
|
|
82
82
|
export default runtimeConfig({
|
|
83
83
|
storage: {
|
|
84
84
|
basePath: "/api",
|
|
85
|
-
|
|
85
|
+
adapter: s3({
|
|
86
86
|
bucket: process.env.S3_BUCKET,
|
|
87
87
|
region: process.env.S3_REGION,
|
|
88
|
-
|
|
89
|
-
|
|
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)
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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),
|
|
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/
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
668
|
+
adapter: s3({
|
|
496
669
|
bucket: process.env.S3_BUCKET!,
|
|
497
670
|
region: process.env.S3_REGION!,
|
|
498
|
-
|
|
499
|
-
|
|
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,
|