webspresso 0.0.61 → 0.0.63
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/README.md +102 -3
- package/core/compileSchema.js +6 -2
- package/core/orm/index.js +3 -1
- package/core/orm/utils/nanoid.js +53 -0
- package/index.js +15 -0
- package/package.json +1 -1
- package/src/app-context.js +75 -0
- package/src/file-router.js +32 -0
- package/src/server.js +3 -0
- package/templates/skills/webspresso-usage/SKILL.md +5 -3
package/README.md
CHANGED
|
@@ -336,7 +336,7 @@ Creates and configures the Express app.
|
|
|
336
336
|
- `pagesDir` (required): Path to pages directory
|
|
337
337
|
- `viewsDir` (optional): Path to views/layouts directory
|
|
338
338
|
- `publicDir` (optional): Path to public/static directory
|
|
339
|
-
- `db` (optional): Database instance — exposed as `ctx.db` in plugin hooks (`register`, `onRoutesReady`) and in page `load`/`meta` functions
|
|
339
|
+
- `db` (optional): Database instance — exposed as `ctx.db` in plugin hooks (`register`, `onRoutesReady`) and in page `load`/`meta` functions; also registered for [`getDb()` / `getAppContext()`](#app-context-getdb-hasdb-getappcontext) below
|
|
340
340
|
- `logging` (optional): Enable request logging (default: true in development)
|
|
341
341
|
- `helmet` (optional): Helmet security configuration
|
|
342
342
|
- `true` or `undefined`: Use default secure configuration
|
|
@@ -386,6 +386,54 @@ module.exports = {
|
|
|
386
386
|
};
|
|
387
387
|
```
|
|
388
388
|
|
|
389
|
+
### App context (`req.db`, `getDb`, `attachDbMiddleware`)
|
|
390
|
+
|
|
391
|
+
With **`createApp({ db })`**, file-based **API** routes (`pages/api/*.js`) get the same ORM instance on **`req.db`** before your **`middleware`** array and the handler run — no extra `require` in the handler:
|
|
392
|
+
|
|
393
|
+
```javascript
|
|
394
|
+
module.exports = async function handler(req, res) {
|
|
395
|
+
if (!req.db) {
|
|
396
|
+
return res.status(503).json({ error: 'Database not configured' });
|
|
397
|
+
}
|
|
398
|
+
const posts = await req.db.getRepository('Post').query().limit(10).list();
|
|
399
|
+
res.json(posts);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
module.exports.middleware = ['auth']; // can use req.db too
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The framework also registers that instance for **non-request** code (scripts, jobs) and for tests:
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
const { getDb, hasDb } = require('webspresso');
|
|
409
|
+
// hasDb() / getDb() — getDb() throws if createApp had no db
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
For routes you add manually in **`setupRoutes`**, run **`attachDbMiddleware`** early so those handlers get `req.db`:
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
const { createApp, attachDbMiddleware } = require('webspresso');
|
|
416
|
+
|
|
417
|
+
createApp({
|
|
418
|
+
pagesDir: './pages',
|
|
419
|
+
db,
|
|
420
|
+
setupRoutes(app) {
|
|
421
|
+
app.use(attachDbMiddleware);
|
|
422
|
+
app.get('/custom/api', (req, res) => res.json({ ok: !!req.db }));
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
| Export | Role |
|
|
428
|
+
|--------|------|
|
|
429
|
+
| `req.db` | Set on each API request when `createApp({ db })` was used (file-based API routes + after `attachDbMiddleware`) |
|
|
430
|
+
| `getDb()` | Same instance as `req.db`; **throws** if no `db` was passed to `createApp` |
|
|
431
|
+
| `hasDb()` | `true` if `createApp` was given `db` |
|
|
432
|
+
| `getAppContext()` | `{ db }` — `db` may be `null` |
|
|
433
|
+
| `attachDbMiddleware` | Express middleware to populate `req.db` for non–file-router routes |
|
|
434
|
+
| `resetAppContext()` | Clears context (mainly for tests) |
|
|
435
|
+
| `setAppContext(partial)` | Low-level merge; normally only `createApp` uses this |
|
|
436
|
+
|
|
389
437
|
**Custom Error Pages:**
|
|
390
438
|
|
|
391
439
|
```javascript
|
|
@@ -982,7 +1030,40 @@ module.exports = async function handler(req, res) {
|
|
|
982
1030
|
};
|
|
983
1031
|
```
|
|
984
1032
|
|
|
985
|
-
**
|
|
1033
|
+
**Object export (`handler`, `middleware`, `schema`):**
|
|
1034
|
+
|
|
1035
|
+
Use a single export object when you need **named middleware** from `createApp({ middlewares })` and/or a **Zod** `schema`. Order at runtime: **`req.db`** (if configured) → **schema** (`req.input`) → **middleware** chain → **handler**.
|
|
1036
|
+
|
|
1037
|
+
```javascript
|
|
1038
|
+
// pages/api/search.post.js
|
|
1039
|
+
module.exports = {
|
|
1040
|
+
middleware: ['requireAuth'],
|
|
1041
|
+
schema: ({ z }) => ({
|
|
1042
|
+
body: z.object({ q: z.string() }),
|
|
1043
|
+
}),
|
|
1044
|
+
handler: async (req, res) => {
|
|
1045
|
+
const { q } = req.input.body;
|
|
1046
|
+
const results = [];
|
|
1047
|
+
return res.json({ results, q });
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
Register `requireAuth` (and any other names) on the app:
|
|
1053
|
+
|
|
1054
|
+
```javascript
|
|
1055
|
+
createApp({
|
|
1056
|
+
pagesDir: './pages',
|
|
1057
|
+
middlewares: {
|
|
1058
|
+
requireAuth: (req, res, next) => {
|
|
1059
|
+
if (!req.session?.user) return res.status(401).json({ error: 'Unauthorized' });
|
|
1060
|
+
next();
|
|
1061
|
+
},
|
|
1062
|
+
},
|
|
1063
|
+
});
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
**With Schema Validation (same object shape):**
|
|
986
1067
|
|
|
987
1068
|
```javascript
|
|
988
1069
|
// pages/api/posts.post.js
|
|
@@ -1018,7 +1099,25 @@ module.exports = {
|
|
|
1018
1099
|
| `query` | Validates query string parameters |
|
|
1019
1100
|
| `response` | Response schema (for documentation, not enforced) |
|
|
1020
1101
|
|
|
1021
|
-
All schemas use [Zod](https://zod.dev) for validation. Invalid requests
|
|
1102
|
+
All schemas use [Zod](https://zod.dev) for validation. Invalid requests receive **`400 JSON`**: `{ error: 'Validation Error', issues: [...] }` (no `handler` / `middleware` run).
|
|
1103
|
+
|
|
1104
|
+
For **nanoid** route parameters (same alphabet and default length as **`generateNanoid`** / **`zdb.nanoid()`**), use **`z.nanoid()`** on the `z` passed into your API schema (Webspresso extends it; no extra import). Optional: **`z.nanoid(12)`** or **`z.nanoid({ maxLength: 12 })`** (matches **`zdb.nanoid({ maxLength })`**). For scripts or tests outside `schema: ({ z })`, **`zodNanoid(z, size?)`** and **`extendZ(z)`** are exported from `webspresso`.
|
|
1105
|
+
|
|
1106
|
+
```javascript
|
|
1107
|
+
module.exports = {
|
|
1108
|
+
schema: ({ z }) => ({
|
|
1109
|
+
params: z.object({
|
|
1110
|
+
id: z.nanoid(), // default 21 chars
|
|
1111
|
+
shortId: z.nanoid(12), // numeric length
|
|
1112
|
+
other: z.nanoid({ maxLength: 8 }), // zdb-style options
|
|
1113
|
+
}),
|
|
1114
|
+
}),
|
|
1115
|
+
async handler(req, res) {
|
|
1116
|
+
const { id } = req.input.params;
|
|
1117
|
+
// ...
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
```
|
|
1022
1121
|
|
|
1023
1122
|
**Dynamic Route with Params Validation:**
|
|
1024
1123
|
|
package/core/compileSchema.js
CHANGED
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const z = require('zod');
|
|
7
|
+
const { extendZ } = require('./orm/utils/nanoid');
|
|
7
8
|
const schemaCache = require('../utils/schemaCache');
|
|
8
9
|
|
|
10
|
+
/** Zod plus `z.nanoid()` for API schemas (does not mutate the global `z`). */
|
|
11
|
+
const zForApi = extendZ(z);
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Compile schema from an API module
|
|
11
15
|
* @param {string} filePath - Absolute file path to API module
|
|
@@ -32,8 +36,8 @@ function compileSchema(filePath, apiModule) {
|
|
|
32
36
|
throw new Error(`Schema in ${filePath} must be a function`);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
// Call schema function with { z }
|
|
36
|
-
const compiled = schemaFn({ z });
|
|
39
|
+
// Call schema function with { z } (extended with z.nanoid, same as generateNanoid / zdb.nanoid)
|
|
40
|
+
const compiled = schemaFn({ z: zForApi });
|
|
37
41
|
|
|
38
42
|
// Validate compiled schema structure
|
|
39
43
|
if (compiled !== null && typeof compiled !== 'object') {
|
package/core/orm/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const { createSeeder } = require('./seeder');
|
|
|
14
14
|
const { createScopeContext } = require('./scopes');
|
|
15
15
|
const { ModelEvents, Hooks, HookCancellationError, createEventContext } = require('./events');
|
|
16
16
|
const { omitHiddenColumns, sanitizeForOutput } = require('./utils');
|
|
17
|
-
const { generateNanoid } = require('./utils/nanoid');
|
|
17
|
+
const { generateNanoid, zodNanoid, extendZ } = require('./utils/nanoid');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Create a database instance
|
|
@@ -275,6 +275,8 @@ module.exports = {
|
|
|
275
275
|
extractColumnsFromSchema,
|
|
276
276
|
getColumnMeta,
|
|
277
277
|
generateNanoid,
|
|
278
|
+
zodNanoid,
|
|
279
|
+
extendZ,
|
|
278
280
|
// Output sanitization (exclude hidden columns from API/templates)
|
|
279
281
|
omitHiddenColumns,
|
|
280
282
|
sanitizeForOutput,
|
package/core/orm/utils/nanoid.js
CHANGED
|
@@ -24,7 +24,60 @@ function generateNanoid(size = 21) {
|
|
|
24
24
|
return id;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Zod schema for IDs produced by {@link generateNanoid} (same default alphabet & length).
|
|
29
|
+
* Prefer **`z.nanoid()`** in API `schema: ({ z }) => …`; use this when building Zod schemas outside compiled routes.
|
|
30
|
+
* @param {typeof import('zod').z} z - Zod instance (from `schema: ({ z }) => …`)
|
|
31
|
+
* @param {number} [size=21]
|
|
32
|
+
*/
|
|
33
|
+
function zodNanoid(z, size = 21) {
|
|
34
|
+
const n = Math.max(1, Math.floor(size));
|
|
35
|
+
const alphabet = new Set(URL_ALPHABET);
|
|
36
|
+
return z.string().length(n).refine((s) => [...s].every((ch) => alphabet.has(ch)), {
|
|
37
|
+
message: `Expected ${n}-character nanoid (default URL alphabet)`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {unknown} arg - `undefined` → 21; number → length; `{ maxLength }` (zdb-compatible)
|
|
43
|
+
* @returns {number}
|
|
44
|
+
*/
|
|
45
|
+
function resolveNanoidSize(arg) {
|
|
46
|
+
if (arg === undefined) return 21;
|
|
47
|
+
if (arg === null) return 21;
|
|
48
|
+
if (typeof arg === 'number' && !Number.isNaN(arg)) {
|
|
49
|
+
return Math.max(1, Math.floor(arg));
|
|
50
|
+
}
|
|
51
|
+
if (typeof arg === 'object' && arg !== null && 'maxLength' in arg) {
|
|
52
|
+
const n = /** @type {{ maxLength?: unknown }} */ (arg).maxLength;
|
|
53
|
+
if (typeof n === 'number' && !Number.isNaN(n)) {
|
|
54
|
+
return Math.max(1, Math.floor(n));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return 21;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Wraps Zod's `z` with **`z.nanoid()`** / **`z.nanoid(12)`** / **`z.nanoid({ maxLength: 12 })`**
|
|
62
|
+
* without mutating the global `z` object. Used by API `schema: ({ z }) => …`.
|
|
63
|
+
* @param {typeof import('zod').z} zod
|
|
64
|
+
*/
|
|
65
|
+
function extendZ(zod) {
|
|
66
|
+
return new Proxy(zod, {
|
|
67
|
+
get(target, prop, receiver) {
|
|
68
|
+
if (prop === 'nanoid') {
|
|
69
|
+
return function nanoid(arg) {
|
|
70
|
+
return zodNanoid(zod, resolveNanoidSize(arg));
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return Reflect.get(target, prop, receiver);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
27
78
|
module.exports = {
|
|
28
79
|
generateNanoid,
|
|
80
|
+
zodNanoid,
|
|
81
|
+
extendZ,
|
|
29
82
|
URL_ALPHABET,
|
|
30
83
|
};
|
package/index.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const { createApp } = require('./src/server');
|
|
6
|
+
const {
|
|
7
|
+
attachDbMiddleware,
|
|
8
|
+
getAppContext,
|
|
9
|
+
getDb,
|
|
10
|
+
hasDb,
|
|
11
|
+
resetAppContext,
|
|
12
|
+
setAppContext,
|
|
13
|
+
} = require('./src/app-context');
|
|
6
14
|
const {
|
|
7
15
|
mountPages,
|
|
8
16
|
filePathToRoute,
|
|
@@ -35,6 +43,13 @@ const { schemaExplorerPlugin, adminPanelPlugin, siteAnalyticsPlugin, auditLogPlu
|
|
|
35
43
|
module.exports = {
|
|
36
44
|
// Main API
|
|
37
45
|
createApp,
|
|
46
|
+
|
|
47
|
+
attachDbMiddleware,
|
|
48
|
+
getAppContext,
|
|
49
|
+
getDb,
|
|
50
|
+
hasDb,
|
|
51
|
+
resetAppContext,
|
|
52
|
+
setAppContext,
|
|
38
53
|
|
|
39
54
|
// Router utilities (for advanced use)
|
|
40
55
|
mountPages,
|
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide app context set by createApp() — use from API handlers, jobs, etc.
|
|
3
|
+
* @module src/app-context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @type {{ db: object|null }} */
|
|
7
|
+
let context = {
|
|
8
|
+
db: null,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Merge into the current context (typically called once from createApp).
|
|
13
|
+
* @param {{ db?: object|null }} partial
|
|
14
|
+
*/
|
|
15
|
+
function setAppContext(partial) {
|
|
16
|
+
context = { ...context, ...partial };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {{ db: object|null }}
|
|
21
|
+
*/
|
|
22
|
+
function getAppContext() {
|
|
23
|
+
return context;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Database instance passed to createApp({ db }) (Knex + ORM helpers).
|
|
28
|
+
* @returns {object}
|
|
29
|
+
* @throws {Error} If createApp was not given a db instance
|
|
30
|
+
*/
|
|
31
|
+
function getDb() {
|
|
32
|
+
if (!context.db) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'No database registered. Create a DB with createDatabase(), then pass it to createApp({ db }). ' +
|
|
35
|
+
'Or use hasDb() before calling getDb().'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return context.db;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
function hasDb() {
|
|
45
|
+
return context.db != null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Clear context (e.g. between tests).
|
|
50
|
+
*/
|
|
51
|
+
function resetAppContext() {
|
|
52
|
+
context = { db: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Express middleware: sets `req.db` from registered app context.
|
|
57
|
+
* File-based `pages/api/*` routes attach this automatically; use in `setupRoutes`
|
|
58
|
+
* for manually registered handlers that need `req.db`.
|
|
59
|
+
* @type {import('express').RequestHandler}
|
|
60
|
+
*/
|
|
61
|
+
function attachDbMiddleware(req, res, next) {
|
|
62
|
+
if (context.db != null) {
|
|
63
|
+
req.db = context.db;
|
|
64
|
+
}
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
setAppContext,
|
|
70
|
+
getAppContext,
|
|
71
|
+
getDb,
|
|
72
|
+
hasDb,
|
|
73
|
+
resetAppContext,
|
|
74
|
+
attachDbMiddleware,
|
|
75
|
+
};
|
package/src/file-router.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { ZodError } = require('zod');
|
|
9
|
+
const { compileSchema, invalidateSchema } = require('../core/compileSchema');
|
|
10
|
+
const { applySchema } = require('../core/applySchema');
|
|
8
11
|
const { createHelpers } = require('./helpers');
|
|
9
12
|
|
|
10
13
|
// Cache for i18n files (key: filePath, value: { mtime, data })
|
|
@@ -397,6 +400,11 @@ function mountPages(app, options) {
|
|
|
397
400
|
|
|
398
401
|
app[route.method](route.routePath, async (req, res, next) => {
|
|
399
402
|
try {
|
|
403
|
+
// Same instance as createApp({ db }) / getAppContext().db — available to handler & route middleware
|
|
404
|
+
if (db != null) {
|
|
405
|
+
req.db = db;
|
|
406
|
+
}
|
|
407
|
+
|
|
400
408
|
// Reload handler in dev mode
|
|
401
409
|
if (isDev && require.cache[require.resolve(route.fullPath)]) {
|
|
402
410
|
delete require.cache[require.resolve(route.fullPath)];
|
|
@@ -407,6 +415,30 @@ function mountPages(app, options) {
|
|
|
407
415
|
const fn = typeof currentHandler === 'function'
|
|
408
416
|
? currentHandler
|
|
409
417
|
: currentHandler.default || currentHandler.handler;
|
|
418
|
+
|
|
419
|
+
if (isDev) {
|
|
420
|
+
invalidateSchema(route.fullPath);
|
|
421
|
+
}
|
|
422
|
+
let compiledSchema;
|
|
423
|
+
try {
|
|
424
|
+
compiledSchema = compileSchema(route.fullPath, currentHandler);
|
|
425
|
+
} catch (schemaErr) {
|
|
426
|
+
console.error(`API schema compile error ${route.routePath}:`, schemaErr);
|
|
427
|
+
res.status(500).json({ error: 'Internal Server Error', message: schemaErr.message });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
applySchema(req, compiledSchema);
|
|
433
|
+
} catch (err) {
|
|
434
|
+
if (err instanceof ZodError) {
|
|
435
|
+
return res.status(400).json({
|
|
436
|
+
error: 'Validation Error',
|
|
437
|
+
issues: err.issues,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
throw err;
|
|
441
|
+
}
|
|
410
442
|
|
|
411
443
|
// Run middleware if defined
|
|
412
444
|
const mwConfig = isDev ? currentHandler.middleware : routeMiddleware;
|
package/src/server.js
CHANGED
|
@@ -8,6 +8,7 @@ const helmet = require('helmet');
|
|
|
8
8
|
const nunjucks = require('nunjucks');
|
|
9
9
|
const timeout = require('connect-timeout');
|
|
10
10
|
|
|
11
|
+
const { setAppContext } = require('./app-context');
|
|
11
12
|
const { mountPages } = require('./file-router');
|
|
12
13
|
const { configureAssets, createHelpers, getScriptInjector } = require('./helpers');
|
|
13
14
|
const { createPluginManager } = require('./plugin-manager');
|
|
@@ -200,6 +201,8 @@ function createApp(options = {}) {
|
|
|
200
201
|
if (!pagesDir) {
|
|
201
202
|
throw new Error('pagesDir is required');
|
|
202
203
|
}
|
|
204
|
+
|
|
205
|
+
setAppContext({ db: options.db ?? null });
|
|
203
206
|
|
|
204
207
|
const app = express();
|
|
205
208
|
|
|
@@ -102,9 +102,11 @@ project/
|
|
|
102
102
|
**Shapes**
|
|
103
103
|
|
|
104
104
|
1. **Function** — `module.exports = async (req, res) => { ... }`
|
|
105
|
-
2. **Object** —
|
|
105
|
+
2. **Object** — **`handler`**, optional **`middleware`** (names from **`createApp({ middlewares })`**), optional **`schema`**
|
|
106
106
|
|
|
107
|
-
**
|
|
107
|
+
**Order:** `req.db` (if any) → **Zod** `schema` → **`middleware`** → **`handler`**.
|
|
108
|
+
|
|
109
|
+
**Zod** — `schema: ({ z }) => ({ body, query, params, response })` → **`req.input`**; invalid → **400** `{ error: 'Validation Error', issues }`.
|
|
108
110
|
|
|
109
111
|
---
|
|
110
112
|
|
|
@@ -162,7 +164,7 @@ Analytics plugin adds `fsy.analyticsHead`, `fsy.verificationTags`, etc., when co
|
|
|
162
164
|
|
|
163
165
|
**Transactions:** `db.transaction(async (trx) => { trx.getRepository('User') })`.
|
|
164
166
|
|
|
165
|
-
Pass **`db`** into **`createApp({ db })`** so **`ctx.db`** works in pages and plugins.
|
|
167
|
+
Pass **`db`** into **`createApp({ db })`** so **`ctx.db`** works in pages and plugins. **`pages/api/`** handlers receive **`req.db`** (and route **`middleware`** runs after it). Outside requests, use **`getDb()`** / **`hasDb()`**; for **`setupRoutes`**-only routes, use **`attachDbMiddleware`**.
|
|
166
168
|
|
|
167
169
|
---
|
|
168
170
|
|