@nano-app/backend 0.18.0 → 0.20.0
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 +54 -71
- package/build/index.d.ts +110 -53
- package/build/index.js +451 -403
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ export default createRoute({
|
|
|
48
48
|
|
|
49
49
|
## Routes
|
|
50
50
|
|
|
51
|
-
Each `*.route.ts` under `
|
|
51
|
+
Each `*.route.ts` under `routesDir` default-exports an object with the HTTP methods it implements (`GET`, `POST`, `PUT`, `DELETE`, `QUERY`). **The file path is the URL** (`index.route.ts` → `/`, `api/users.route.ts` → `/api/users`). A route file holds only those methods and its options (`expression`, `public`, `permissions`, `callbacks`): any other key, or an invalid value, stops the startup with the file and what is wrong. `expression` is appended to that path as is, in Express syntax (`/:id`, `{/:id}` for optional):
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
54
|
export default createRoute({
|
|
@@ -84,10 +84,14 @@ Every option is optional.
|
|
|
84
84
|
| `listener` | `() => {}` | Callback once the server is listening. |
|
|
85
85
|
| `settings` | `{ "trust proxy": <private networks> }` | Values for `app.set()`: see [Client IP](#client-ip). |
|
|
86
86
|
| `packages` | see below | Framework base middlewares. |
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
87
|
+
| `routesDir` | `"src/routes"` | Folder of your routes, relative to `cwd`. |
|
|
88
|
+
| `database` | none | Your Drizzle relations and how its tables are applied: see [Database](#database). |
|
|
89
|
+
| `auth` | none | Better Auth options: see [Better Auth](#better-auth). |
|
|
90
|
+
| `plugins` | `{}` | Plugins mounted `before` and `after` your routes, in order: see [Plugins](#plugins). |
|
|
89
91
|
|
|
90
|
-
`packages` accepts `morgan`, `cors`, `helmet`, `compression`, `express` (`express.json` options) and `urlencoded`.
|
|
92
|
+
`packages` accepts `morgan`, `cors`, `helmet`, `rateLimit`, `compression`, `express` (`express.json` options) and `urlencoded`. Each one you set replaces its default whole and the others keep theirs; pass `false` to disable one. `settings` works the same way, key by key.
|
|
93
|
+
|
|
94
|
+
`rateLimit` is off by default. With `{ capacity, refillPerSecond, message }` (defaults `100`, `1` and a generic message) it limits every request per client IP, Better Auth's endpoints too: each request spends a token of the client's bucket, which refills over time; without one it answers `429` with `message` and `Retry-After`. The addresses of an IPv6 `/56` are one client, and the buckets live in the memory of each process.
|
|
91
95
|
|
|
92
96
|
`compression` is off by default: the proxy in front of the app (an ingress, nginx) usually compresses the answers. Turn it on with `packages: { compression: true }` when nothing does.
|
|
93
97
|
|
|
@@ -104,7 +108,7 @@ createNanoApp({
|
|
|
104
108
|
|
|
105
109
|
### Client IP
|
|
106
110
|
|
|
107
|
-
The [
|
|
111
|
+
The [rate limit](#configuration) and Better Auth count each client by its IP. Behind a proxy (the frontend of `@nano-app/frontend`, nginx, a load balancer, a CDN, Tailscale Funnel), the IP of the request is the proxy's: the client's is the last one the proxies appended to `X-Forwarded-For`.
|
|
108
112
|
|
|
109
113
|
By default the backend reads that header only when the request comes from loopback or a private network (`127.0.0.0/8`, `::1`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), and takes the last address that is not one of them. A request from a public IP is counted by that IP, whatever header it sends. Better Auth gets the same networks as `advanced.ipAddress.trustedProxies`.
|
|
110
114
|
|
|
@@ -114,7 +118,7 @@ By default the backend reads that header only when the request comes from loopba
|
|
|
114
118
|
|
|
115
119
|
## Database
|
|
116
120
|
|
|
117
|
-
Off until the app passes its
|
|
121
|
+
Off until the app passes its relations: nano-app creates a [Drizzle](https://orm.drizzle.team) client of them on `DATABASE_URL` and passes it to your handlers as `db`. It changes your tables only when you ask: `push` or `migrate`. Without them, applying the schema (`drizzle-kit push`, `drizzle-kit generate` and `migrate`) is up to you.
|
|
118
122
|
|
|
119
123
|
```bash
|
|
120
124
|
pnpm add drizzle-orm pg
|
|
@@ -133,10 +137,11 @@ export const users = pgTable('users', {
|
|
|
133
137
|
|
|
134
138
|
```ts
|
|
135
139
|
// src/app.ts — DATABASE_URL comes from the environment (or a .env file)
|
|
140
|
+
import { defineRelations } from 'drizzle-orm';
|
|
136
141
|
import { createNanoApp } from '@nano-app/backend';
|
|
137
142
|
import * as schema from './db/schema';
|
|
138
143
|
|
|
139
|
-
createNanoApp({
|
|
144
|
+
createNanoApp({ database: { relations: defineRelations(schema) } });
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
```ts
|
|
@@ -148,64 +153,48 @@ export default createRoute({
|
|
|
148
153
|
});
|
|
149
154
|
```
|
|
150
155
|
|
|
151
|
-
- `database.
|
|
152
|
-
-
|
|
156
|
+
- `database.relations` is what `defineRelations` gives of your tables, with their relations when you relate them. A `pgSchema` the tables use is exported from the module too, so drizzle-kit creates it. The rest of `database` goes to Drizzle as its options.
|
|
157
|
+
- `database.push: true` applies the tables at startup with drizzle-kit's push (`pnpm add drizzle-kit`), only to the Postgres schemas they use. A change that would lose data stops the startup.
|
|
158
|
+
- `database.migrate` is the folder `drizzle-kit generate` writes: its pending migrations are applied at startup. With both, the migrations run first, then the push.
|
|
159
|
+
- One startup at a time changes the database: a Postgres lock covers the migrations and the push of every database, across replicas.
|
|
160
|
+
- One pool serves the app, Better Auth and every plugin with a database. It connects on its first query: a missing table fails that query, not the startup.
|
|
153
161
|
- Startup fails if `DATABASE_URL` is missing.
|
|
154
|
-
-
|
|
162
|
+
- Each `nano_*` Postgres schema is reserved for the plugin of its name (`nano_auth` for Better Auth): a table of yours in one fails the startup.
|
|
155
163
|
- Without a database, `db` is `undefined`.
|
|
156
164
|
- Drizzle and pg are loaded only when your database or a mounted plugin's is on.
|
|
157
165
|
|
|
158
166
|
## Better Auth
|
|
159
167
|
|
|
160
|
-
Users, sign-in and sessions with [Better Auth](https://www.better-auth.com)
|
|
168
|
+
Users, sign-in and sessions with [Better Auth](https://www.better-auth.com). On when `createNanoApp` has `auth`: its options, everything but `database`. Without it, it stays off.
|
|
161
169
|
|
|
162
170
|
```bash
|
|
163
|
-
pnpm add better-auth
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
// src/auth.ts: nano-app and the Better Auth CLI read `options`
|
|
168
|
-
import type { BetterAuthOptions } from 'better-auth';
|
|
169
|
-
|
|
170
|
-
export default {
|
|
171
|
-
options: { emailAndPassword: { enabled: true } } satisfies BetterAuthOptions,
|
|
172
|
-
};
|
|
171
|
+
pnpm add better-auth kysely pg
|
|
173
172
|
```
|
|
174
173
|
|
|
175
174
|
```ts
|
|
176
175
|
// src/app.ts
|
|
177
176
|
import { createNanoApp } from '@nano-app/backend';
|
|
178
|
-
import * as schema from './db/schema';
|
|
179
|
-
|
|
180
|
-
createNanoApp({ mainPlugin: { database: { schema } } });
|
|
181
|
-
```
|
|
182
177
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
npx auth generate --adapter drizzle --dialect pg --output src/db/auth-schema.ts # then export its tables from src/db/schema.ts
|
|
187
|
-
pnpm drizzle-kit push
|
|
178
|
+
createNanoApp({ auth: { emailAndPassword: { enabled: true } } });
|
|
188
179
|
```
|
|
189
180
|
|
|
190
|
-
- It
|
|
191
|
-
-
|
|
192
|
-
-
|
|
181
|
+
- It uses the pool of `DATABASE_URL`, with or without a database of the app: startup fails if it is missing.
|
|
182
|
+
- Its tables live in the Postgres schema `nano_auth`, and Better Auth decides them from its options (plugins, `user.additionalFields`).
|
|
183
|
+
- `auth.database` says how they are applied at startup, `{ push: true }` by default: `push` creates them with Better Auth's migrations and adds what new options need, never dropping anything; `migrate` is a folder of Drizzle migrations of them (`npx auth generate --adapter drizzle`, then `drizzle-kit generate`), applied in `nano_auth`.
|
|
184
|
+
- Its endpoints are mounted before your routes, at its `basePath` (default `/api/auth`), and stay public.
|
|
193
185
|
- In production, Better Auth limits sign-in, sign-up and password reset to a few attempts per client IP (its `rateLimit`): see [Client IP](#client-ip).
|
|
194
|
-
-
|
|
195
|
-
-
|
|
186
|
+
- With it on, every route of the app and of every plugin needs a session: without one it answers 401 `Not authenticated`.
|
|
187
|
+
- `createRoute({ public: true, ... })` answers without a session. An unknown path answers 404.
|
|
188
|
+
- Handlers and `onBefore` receive the session in `params.session` (`{ user, session }`), also on a public route when the request has one.
|
|
196
189
|
- Sign-up works as Better Auth sets it: with `emailAndPassword` anyone can sign up and reach your routes. Close it with `disableSignUp: true`, or check roles in your handlers.
|
|
197
|
-
-
|
|
190
|
+
- Extra user fields go in its `user.additionalFields`.
|
|
198
191
|
- Set `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL` in the environment, as Better Auth asks.
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
- Its own endpoints, the plugins mounted before the app and the middlewares a plugin adds with `app.use` in `onMount` are not checked: a plugin that exposes something public says it in its README.
|
|
202
|
-
- Better Auth is loaded only when it is on.
|
|
203
|
-
- Before 0.14.0 the options went in `packages.betterAuth`: move them to the default export of `src/auth.ts`.
|
|
204
|
-
- nano-app creates the one instance the app uses: do not call `betterAuth()` in `src/auth.ts`. The CLI's `generate` only reads `options` of its default export.
|
|
192
|
+
- Better Auth is loaded only when it is on, and nano-app creates the one instance the app uses.
|
|
193
|
+
- Before 0.19.0 the options went in the default export of `src/auth.ts` and its tables in your schema: move the options to `auth`.
|
|
205
194
|
|
|
206
195
|
### Permissions
|
|
207
196
|
|
|
208
|
-
A route asks for permissions of Better Auth's [admin plugin](https://www.better-auth.com/docs/plugins/admin): add `admin()` to the `plugins` of your options, with your roles (`ac` and `roles`)
|
|
197
|
+
A route asks for permissions of Better Auth's [admin plugin](https://www.better-auth.com/docs/plugins/admin): add `admin()` to the `plugins` of your options, with your roles (`ac` and `roles`). A user without them gets 403:
|
|
209
198
|
|
|
210
199
|
```ts
|
|
211
200
|
export default createRoute({
|
|
@@ -214,13 +203,13 @@ export default createRoute({
|
|
|
214
203
|
});
|
|
215
204
|
```
|
|
216
205
|
|
|
217
|
-
A method may ask for its own permissions instead of the route's: `POST: { handler, permissions: { notes: ['create'] } }`. A plain function takes those of the route. Without any, a session is enough. In a handler of your own, `isGranted(params, permissions)` asks Better Auth the same.
|
|
206
|
+
A method may ask for its own permissions instead of the route's: `POST: { handler, permissions: { notes: ['create'] } }`. A plain function takes those of the route. Without any, a session is enough. In a handler of your own, `isGranted(params.session, permissions)` asks Better Auth the same.
|
|
218
207
|
|
|
219
|
-
A public route does not check them. Without Better Auth the route answers anyway,
|
|
208
|
+
A public route does not check them. Without Better Auth nothing checks them: the route answers anyway, so turn on `auth` before you rely on them. The params of handlers and callbacks are read-only.
|
|
220
209
|
|
|
221
210
|
## Plugins
|
|
222
211
|
|
|
223
|
-
A plugin is a group of endpoints mounted under a base path. Your routes are mounted as one (named `app`). The official plugins are in [`@nano-app/plugins`](https://github.com/Nano-Nathan/nano-app/blob/main/plugins/README.md). You write one with `createPlugin` and list it in `plugins`. Nothing is mounted unless you list it.
|
|
212
|
+
A plugin is a group of endpoints mounted under a base path. Your routes are mounted as one (named `app`). The official plugins are in [`@nano-app/plugins`](https://github.com/Nano-Nathan/nano-app/blob/main/plugins/README.md). You write one with `createPlugin` and list it in `plugins.before` or `plugins.after`. Nothing is mounted unless you list it.
|
|
224
213
|
|
|
225
214
|
```ts
|
|
226
215
|
// src/plugins/hello/index.ts
|
|
@@ -228,9 +217,9 @@ import { createPlugin } from '@nano-app/backend';
|
|
|
228
217
|
import { dirname, resolve } from 'node:path';
|
|
229
218
|
import { fileURLToPath } from 'node:url';
|
|
230
219
|
|
|
231
|
-
export default createPlugin((
|
|
220
|
+
export default createPlugin(() => ({
|
|
232
221
|
name: 'hello',
|
|
233
|
-
basePath:
|
|
222
|
+
basePath: '/hello',
|
|
234
223
|
routesDir: resolve(dirname(fileURLToPath(import.meta.url)), 'routes'),
|
|
235
224
|
}));
|
|
236
225
|
```
|
|
@@ -249,39 +238,34 @@ export default createRoute({
|
|
|
249
238
|
import { createNanoApp } from '@nano-app/backend';
|
|
250
239
|
import hello from './plugins/hello';
|
|
251
240
|
|
|
252
|
-
createNanoApp({ plugins: [hello()] });
|
|
241
|
+
createNanoApp({ plugins: { before: [hello()] } });
|
|
253
242
|
```
|
|
254
243
|
|
|
255
|
-
|
|
244
|
+
A plugin may also make routes in code with `routes`, by path, beside or instead of `routesDir`: they are checked as the files, and a path in both takes the one in `routes`. Useful when its routes come from its options, as one per table with its own permissions:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
routes: Object.fromEntries(
|
|
248
|
+
models.map(sModel => [`/${sModel}`, createRoute({ GET: { handler, permissions: { [sModel]: ['read'] } } })]),
|
|
249
|
+
),
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
`createPlugin` types the options of that plugin, and adds `basePath` to them: each plugin chooses its default, and the app moves it with `hello({ basePath: '/hi' })`. The app also chooses whether it mounts `before` or `after` its routes: a route of a `before` plugin takes its path from the app's routes, and an `after` plugin only gets what they leave. What the plugin puts in `options` reaches every callback and handler of its routes in `params`.
|
|
256
253
|
|
|
257
254
|
### Callbacks
|
|
258
255
|
|
|
259
|
-
Two levels
|
|
256
|
+
Two levels: the plugin's runs once for the whole plugin, the endpoint's for its route.
|
|
260
257
|
|
|
261
258
|
| Level | Callback | Runs | Receives | Use |
|
|
262
259
|
|---|---|---|---|---|
|
|
263
|
-
| Plugin (`callbacks` in its config) | `onMount` | Once, after its database and before its endpoints | `(
|
|
260
|
+
| Plugin (`callbacks` in its config) | `onMount` | Once, after its database and before its endpoints | `(params)` | Shared setup (a client, a job) |
|
|
264
261
|
| Endpoint (`callbacks` in `createRoute`) | `onInit` | Once, when the endpoint is mounted | `(params)` | Setup of that route |
|
|
265
262
|
| Endpoint | `onBefore` | Before every request the endpoint handles | `(req, method, params)` | Validate or load; its return value reaches the handler as `params.onBeforeResult` (`{}` without `onBefore`) |
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
export default createPlugin((options?: { publicPaths?: string[] }) => ({
|
|
269
|
-
name: 'guard',
|
|
270
|
-
routesDir: resolve(dirname(fileURLToPath(import.meta.url)), 'routes'),
|
|
271
|
-
options: { publicPaths: options?.publicPaths ?? [] },
|
|
272
|
-
callbacks: {
|
|
273
|
-
onMount: async (app, { publicPaths }) => {
|
|
274
|
-
app.use((req, _res, next) => next(publicPaths.includes(req.path) ? undefined : { status: 401, message: 'Unauthorized' }));
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
}));
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
The endpoints of one plugin have no guaranteed order: one route's `onInit` must not depend on another's. `mainPlugin` takes `callbacks` too.
|
|
264
|
+
The endpoints of one plugin have no guaranteed order: one route's `onInit` must not depend on another's.
|
|
281
265
|
|
|
282
|
-
Everything is mounted on one Express application, in this order: base middlewares → `before` plugins → your routes → `after` plugins → 404 → error handler.
|
|
266
|
+
Everything is mounted on one Express application, in this order: base middlewares → Better Auth → `before` plugins → your routes → `after` plugins → 404 → error handler.
|
|
283
267
|
|
|
284
|
-
A plugin with a database passes its own `database
|
|
268
|
+
A plugin with a database passes its own `database` (`relations`, `push`, `migrate`, as the app's). Only it may keep tables in the Postgres schema `nano_<its name>`: a table in the `nano_*` schema of another fails the startup. Without a database of its own, a plugin receives the app's `db`. See [`@nano-app/plugins`](https://github.com/Nano-Nathan/nano-app/blob/main/plugins/README.md#database).
|
|
285
269
|
|
|
286
270
|
## Errors
|
|
287
271
|
|
|
@@ -289,10 +273,9 @@ The framework mounts the 404 and the error handler: an unmatched request becomes
|
|
|
289
273
|
|
|
290
274
|
```ts
|
|
291
275
|
throw { status: 409, message: 'Already exists' }; // → 409 { success: false, warning: false, message: "Already exists" }
|
|
292
|
-
throw 'Field name is required'; // → 400 { success: false, message: "Field name is required" }
|
|
276
|
+
throw 'Field name is required'; // → 400 { success: false, warning: false, message: "Field name is required" }
|
|
293
277
|
throw new Error('boom'); // → 500 "Internal server error", logged to stderr
|
|
294
|
-
// A Postgres error
|
|
295
|
-
// 23505 and 23503 → 409 "Conflict", 23502 and 22P02 → 400 "Invalid request"
|
|
278
|
+
// A Postgres error is one more Error: 500. Catch it in your handler to answer a 409
|
|
296
279
|
GET /unknown // → 404 { success: false, warning: false, message: "/unknown not found" }
|
|
297
280
|
```
|
|
298
281
|
|
package/build/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import express, { Express, Request, Request as Request$1 } from "express";
|
|
2
|
-
import { CorsOptions } from "cors";
|
|
3
|
-
import "dotenv/config";
|
|
4
|
-
import "jiti";
|
|
5
1
|
import { z } from "zod";
|
|
6
|
-
import "
|
|
2
|
+
import express, { Express, Request, Request as Request$1, RequestHandler } from "express";
|
|
3
|
+
import "dotenv/config";
|
|
4
|
+
import { CorsOptions } from "cors";
|
|
5
|
+
import { BetterAuthOptions } from "better-auth";
|
|
6
|
+
import { AnyRelations } from "drizzle-orm";
|
|
7
|
+
import { DrizzlePgConfig } from "drizzle-orm/pg-core";
|
|
8
|
+
/** Generic object */
|
|
9
|
+
type NanoObject<T = any> = Record<string, T>;
|
|
7
10
|
/** API response */
|
|
8
11
|
type ApiResponse<T = any> = {
|
|
9
12
|
status?: number;
|
|
@@ -26,10 +29,34 @@ type QueryParams = {
|
|
|
26
29
|
limit?: number;
|
|
27
30
|
offset?: number;
|
|
28
31
|
};
|
|
32
|
+
/** Error of the framework, with the status a handler answers */
|
|
33
|
+
declare class NanoError extends Error {
|
|
34
|
+
status?: number;
|
|
35
|
+
/** Error with its message, and the status a handler answers (500 without one) */
|
|
36
|
+
constructor(sMessage: string, iStatus?: number);
|
|
37
|
+
/** Prefix of the framework's messages, shown before the message */
|
|
38
|
+
get name(): string;
|
|
39
|
+
}
|
|
40
|
+
/** What every database has, whatever its client */
|
|
41
|
+
type DatabaseOptions = {
|
|
42
|
+
/** Folder of its migrations, applied at startup */
|
|
43
|
+
migrate?: string;
|
|
44
|
+
/** Applies its tables at startup as they are declared, failing on data loss; not with `migrate` */
|
|
45
|
+
push?: boolean;
|
|
46
|
+
};
|
|
47
|
+
/** Drizzle's options for its client: its tables and how they relate, from `defineRelations` */
|
|
48
|
+
type DrizzleConfig = DrizzlePgConfig<AnyRelations> & {
|
|
49
|
+
relations: AnyRelations;
|
|
50
|
+
};
|
|
51
|
+
/** Database of a plugin or of the app: the options of its client and what every database has */
|
|
52
|
+
type DatabaseConfig = DatabaseOptions & DrizzleConfig;
|
|
53
|
+
/** Better Auth options; `database` says how its tables are applied, `push` by default */
|
|
54
|
+
type AuthOptions = Omit<BetterAuthOptions, 'database'> & {
|
|
55
|
+
/** How its tables are applied at startup: `migrate` or `push`. Defaults to `{ push: true }` */
|
|
56
|
+
database?: DatabaseOptions;
|
|
57
|
+
};
|
|
29
58
|
/** Extra params of handlers and callbacks: what the app injects (options, `db`) and the framework adds */
|
|
30
59
|
type Params<Deps = NanoObject<any>> = Deps & {
|
|
31
|
-
/** Better Auth's instance, when it is on */
|
|
32
|
-
auth?: NanoObject;
|
|
33
60
|
/** `{ user, session }` of Better Auth, when the request has one */
|
|
34
61
|
session?: NanoObject;
|
|
35
62
|
};
|
|
@@ -57,8 +84,8 @@ type Handlers<Deps = NanoObject<any>> = {
|
|
|
57
84
|
type Callback<Deps> = (params: Params<Deps>) => Promise<void>;
|
|
58
85
|
/** Callback run before a method, with its request */
|
|
59
86
|
type CallbackMethod<Deps> = (req: Request$1, method: string, params: Params<Deps>) => Promise<NanoObject>;
|
|
60
|
-
/** Callback run with the
|
|
61
|
-
type CallbackMount<Deps> = (
|
|
87
|
+
/** Callback run once with the params */
|
|
88
|
+
type CallbackMount<Deps> = (params: Params<Deps>) => Promise<void>;
|
|
62
89
|
/** Callbacks of an endpoint; what affects the whole app goes in the plugin's onMount */
|
|
63
90
|
type Callbacks<Deps = NanoObject<any>> = {
|
|
64
91
|
/** Run once, when the endpoint is mounted */
|
|
@@ -68,46 +95,34 @@ type Callbacks<Deps = NanoObject<any>> = {
|
|
|
68
95
|
};
|
|
69
96
|
/** Callbacks of a plugin */
|
|
70
97
|
type PluginCallbacks<Deps = NanoObject<any>> = {
|
|
71
|
-
/** Run once
|
|
98
|
+
/** Run once, after its database and before its endpoints: the plugin's setup */
|
|
72
99
|
onMount?: CallbackMount<Deps>;
|
|
73
100
|
};
|
|
74
|
-
/**
|
|
75
|
-
type
|
|
76
|
-
/** Express route expression appended to the path (e.g. `/:id`) */
|
|
77
|
-
expression?: string;
|
|
101
|
+
/** Endpoint params */
|
|
102
|
+
type EndpointParams<Deps = NanoObject<any>> = {
|
|
78
103
|
/** With Better Auth on, answers without a session; the handlers still receive it when there is one */
|
|
79
104
|
public?: boolean;
|
|
80
105
|
/** With Better Auth on, the permissions of its admin plugin the user needs (`{ notes: ['read'] }`) */
|
|
81
106
|
permissions?: NanoObject<string[]>;
|
|
82
107
|
callbacks?: Callbacks<Deps>;
|
|
83
108
|
} & Handlers<Deps>;
|
|
84
|
-
/**
|
|
85
|
-
type
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
status?: number;
|
|
89
|
-
/** Error with its message, and the status a handler answers (500 without one) */
|
|
90
|
-
constructor(sMessage: string, iStatus?: number);
|
|
91
|
-
/** Prefix of the framework's messages, shown before the message */
|
|
92
|
-
get name(): string;
|
|
93
|
-
}
|
|
94
|
-
/** Database of a plugin or of the app */
|
|
95
|
-
type DatabaseConfig = {
|
|
96
|
-
/** Its Drizzle schema: the module of its tables and `relations` */
|
|
97
|
-
schema: NanoObject;
|
|
109
|
+
/** Route params */
|
|
110
|
+
type RouteParams = {
|
|
111
|
+
/** Express route expression appended to the path (e.g. `/:id`) */
|
|
112
|
+
expression?: string;
|
|
98
113
|
};
|
|
99
|
-
/**
|
|
100
|
-
type
|
|
114
|
+
/** Entry of a route file (`*.route.ts`) */
|
|
115
|
+
type RouteEntry<Deps = NanoObject<any>> = RouteParams & EndpointParams<Deps>;
|
|
101
116
|
/** Plugin configuration: a group of endpoints */
|
|
102
117
|
type PluginConfig<Options = NanoObject> = {
|
|
103
118
|
/** Name of the plugin, also the name of its database when it has one */
|
|
104
119
|
name: string;
|
|
105
120
|
/** Directory used to discover the `*.route.ts` files */
|
|
106
|
-
routesDir
|
|
121
|
+
routesDir?: string;
|
|
122
|
+
/** Routes made in code, by path (`{ '/notes': createRoute({...}) }`), validated as the files */
|
|
123
|
+
routes?: NanoObject<RouteEntry<any>>;
|
|
107
124
|
/** Base path prepended to every endpoint, e.g. "/auth" (no trailing slash). Defaults to none */
|
|
108
125
|
basePath?: string;
|
|
109
|
-
/** Mount point relative to the consumer routes. Defaults to "before" */
|
|
110
|
-
stage?: PluginStage;
|
|
111
126
|
/** Drizzle schema of the plugin's database, mounted before its endpoints */
|
|
112
127
|
database?: DatabaseConfig;
|
|
113
128
|
/** Plugin options */
|
|
@@ -115,8 +130,34 @@ type PluginConfig<Options = NanoObject> = {
|
|
|
115
130
|
/** Plugin callbacks */
|
|
116
131
|
callbacks?: PluginCallbacks;
|
|
117
132
|
};
|
|
118
|
-
/**
|
|
119
|
-
type
|
|
133
|
+
/** Options every plugin takes, besides its own */
|
|
134
|
+
type PluginOptions = {
|
|
135
|
+
/** Base path of its endpoints, over the plugin's default */
|
|
136
|
+
basePath?: string;
|
|
137
|
+
};
|
|
138
|
+
/** What PluginManager reads: the app's plugin and the ones around it */
|
|
139
|
+
type PluginManagerConfig = {
|
|
140
|
+
/** Folder of the app's routes. Defaults to "src/routes" */
|
|
141
|
+
routesDir?: string;
|
|
142
|
+
/** Drizzle schema of the app's database */
|
|
143
|
+
database?: DatabaseConfig;
|
|
144
|
+
/** Plugins mounted before and after the app's routes */
|
|
145
|
+
plugins?: {
|
|
146
|
+
/** Before the app's routes */
|
|
147
|
+
before?: PluginConfig[];
|
|
148
|
+
/** After the app's routes */
|
|
149
|
+
after?: PluginConfig[];
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
/** Rate limit per client IP: a bucket of tokens per client, refilled over time */
|
|
153
|
+
type RateLimitConfig = {
|
|
154
|
+
/** Tokens of a full bucket. Defaults to 100 */
|
|
155
|
+
capacity?: number;
|
|
156
|
+
/** Tokens refilled per second. Defaults to 1 */
|
|
157
|
+
refillPerSecond?: number;
|
|
158
|
+
/** Message of the 429 */
|
|
159
|
+
message?: string;
|
|
160
|
+
};
|
|
120
161
|
/** Configuration of the framework's built-in packages */
|
|
121
162
|
type PackagesConfig = {
|
|
122
163
|
/** Morgan configuration */
|
|
@@ -128,6 +169,8 @@ type PackagesConfig = {
|
|
|
128
169
|
};
|
|
129
170
|
/** CORS configuration */
|
|
130
171
|
cors?: CorsOptions | false;
|
|
172
|
+
/** Rate limit per client IP, off by default; it covers every request, Better Auth's too */
|
|
173
|
+
rateLimit?: RateLimitConfig | false;
|
|
131
174
|
/** Helmet configuration */
|
|
132
175
|
helmet?: boolean;
|
|
133
176
|
/** Compression configuration */
|
|
@@ -137,36 +180,50 @@ type PackagesConfig = {
|
|
|
137
180
|
/** urlencoded configuration */
|
|
138
181
|
urlencoded?: Partial<Parameters<typeof express.urlencoded>[0]> | false;
|
|
139
182
|
};
|
|
140
|
-
/**
|
|
141
|
-
type
|
|
183
|
+
/** What NanoApp reads: the server */
|
|
184
|
+
type ServerConfig = {
|
|
142
185
|
/** Listener run once the server is listening */
|
|
143
186
|
listener?: () => void;
|
|
144
187
|
/** Application port. Defaults to 3000 */
|
|
145
188
|
port?: number;
|
|
146
|
-
/** Values
|
|
189
|
+
/** Values for Express `app.set()` (e.g. `{ "trust proxy": 1 }`), over the defaults */
|
|
147
190
|
settings?: NanoObject<unknown>;
|
|
148
|
-
/**
|
|
191
|
+
/** Base middlewares: each one set replaces its default */
|
|
149
192
|
packages?: PackagesConfig;
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
/** Main plugin to mount. */
|
|
153
|
-
mainPlugin?: MainPluginConfig;
|
|
193
|
+
/** Better Auth options: with them, every route needs a session unless it is `public` */
|
|
194
|
+
auth?: AuthOptions;
|
|
154
195
|
};
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
/** A route NanoApp registers */
|
|
197
|
+
type Mount = {
|
|
198
|
+
/** Express path of the route */
|
|
199
|
+
path: string;
|
|
200
|
+
/** Its handler */
|
|
201
|
+
handler: RequestHandler;
|
|
158
202
|
};
|
|
203
|
+
/** Application configuration: the server and its plugins */
|
|
204
|
+
type NanoAppConfig = ServerConfig & PluginManagerConfig;
|
|
205
|
+
/** Better Auth of the app: its tables in `nano_auth`, its endpoints and the access of each request */
|
|
206
|
+
declare abstract class Auth {
|
|
207
|
+
private static oAuth?;
|
|
208
|
+
private static fnHeaders?;
|
|
209
|
+
/** Mounts its database and creates the instance; its endpoints, none without options */
|
|
210
|
+
static init(oOptions?: AuthOptions): Promise<Mount[]>;
|
|
211
|
+
/** The session of a request; 401 without one unless public, 403 without the permissions */
|
|
212
|
+
static guard(oReq: Request$1, isPublic: boolean, oPermissions?: NanoObject<string[]>): Promise<NanoObject | undefined>;
|
|
213
|
+
/** The session of a request, when it has one */
|
|
214
|
+
private static session;
|
|
215
|
+
/** Whether the user of a session has the permissions; always without Better Auth or permissions */
|
|
216
|
+
static isGranted(oSession?: NanoObject, oPermissions?: NanoObject<string[]>): Promise<boolean>;
|
|
217
|
+
}
|
|
159
218
|
/** Main NanoApp class */
|
|
160
219
|
declare class NanoApp {
|
|
161
220
|
protected oApp: Express;
|
|
162
|
-
protected oConfig:
|
|
221
|
+
protected oConfig: NanoAppConfig;
|
|
163
222
|
constructor(oConfig?: NanoAppConfig);
|
|
164
223
|
/** Starts the application */
|
|
165
224
|
private start;
|
|
166
225
|
/** Initializes the application in the right order */
|
|
167
226
|
private initialize;
|
|
168
|
-
/** Adds the packages according to the configuration */
|
|
169
|
-
private setPackages;
|
|
170
227
|
}
|
|
171
228
|
/** Creates a new NanoApp instance */
|
|
172
229
|
export declare function createNanoApp(oConfig?: NanoAppConfig): NanoApp;
|
|
@@ -174,9 +231,9 @@ export declare function createNanoApp(oConfig?: NanoAppConfig): NanoApp;
|
|
|
174
231
|
export declare function createRoute<Deps = NanoObject<any>>(oRoute: RouteEntry<Deps>): RouteEntry<Deps>;
|
|
175
232
|
/** Creates a plugin: a function of the plugin's own options that returns its configuration. */
|
|
176
233
|
type Creator<Options> = (options?: Options) => PluginConfig<Options>;
|
|
177
|
-
export declare function createPlugin<Options>(fnCreator: Creator<Options>):
|
|
234
|
+
export declare function createPlugin<Options extends NanoObject = NanoObject>(fnCreator: Creator<Options>): (options?: Options & PluginOptions) => PluginConfig<Options>;
|
|
178
235
|
/** Creates the callbacks of a plugin, typed with what it injects in its params. */
|
|
179
236
|
export declare function createPluginCallbacks<Deps = NanoObject<any>>(oCallbacks: PluginCallbacks<Deps>): PluginCallbacks<Deps>;
|
|
180
|
-
/** Whether the session
|
|
181
|
-
declare
|
|
182
|
-
export { type ApiPromise, type ApiResponse, type Callbacks, type DatabaseConfig, type Handler, type HandlerParams, type Handlers, type
|
|
237
|
+
/** Whether the user of a session has the permissions, asked to Better Auth */
|
|
238
|
+
export declare const isGranted: typeof Auth.isGranted;
|
|
239
|
+
export { type ApiPromise, type ApiResponse, type Callbacks, type DatabaseConfig, type Handler, type HandlerParams, type Handlers, type MethodEntry, NanoApp, type NanoAppConfig, NanoError, type NanoObject, type PackagesConfig, type Params, type PluginCallbacks, type PluginConfig, type PluginManagerConfig, type PluginOptions, type QueryParams, type Request, type RouteEntry, type ServerConfig };
|