@nano-app/backend 0.13.0 → 0.14.1

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 CHANGED
@@ -87,7 +87,7 @@ Every option is optional.
87
87
  | `mainPlugin` | `{ routesDir: "src/routes" }` | Your routes (`routesDir`, relative to `cwd`), `basePath` (e.g. `/api`, no trailing slash) and database: see [Database](#database). |
88
88
  | `plugins` | `[]` | Plugins mounted around your routes, in order: see [Plugins](#plugins). |
89
89
 
90
- `packages` accepts `morgan`, `cors`, `helmet`, `compression`, `express` (`express.json` options), `urlencoded` and `betterAuth` (off by default: see [Better Auth](#better-auth)). The merge is **per section**: passing one key keeps the others; pass `false` to disable one.
90
+ `packages` accepts `morgan`, `cors`, `helmet`, `compression`, `express` (`express.json` options) and `urlencoded`. The merge is **per section**: passing one key keeps the others; pass `false` to disable one.
91
91
 
92
92
  `morgan` logs `:timestamp | :method :path :status` by default: `:path` is the path without its query string, so tokens sent there stay out of the logs. Set `format` with `:url` to log the full URL.
93
93
 
@@ -166,14 +166,14 @@ export default createRoute<PrismaClient>({
166
166
 
167
167
  ## Better Auth
168
168
 
169
- Users, sign-in and sessions with [Better Auth](https://www.better-auth.com), on your database. Off until `packages.betterAuth` gets its options (everything but `database`: nano-app passes your Prisma client).
169
+ Users, sign-in and sessions with [Better Auth](https://www.better-auth.com), on your database. On when your app has `src/auth.ts` (or the built `src/auth.js`), relative to `cwd`: its default export holds the options in `options` (everything but `database`: nano-app passes your Prisma client). Without the file it stays off.
170
170
 
171
171
  ```bash
172
172
  pnpm add better-auth
173
173
  ```
174
174
 
175
175
  ```ts
176
- // src/auth.ts: the options; nano-app creates the instance, the Better Auth CLI reads `options`
176
+ // src/auth.ts: nano-app and the Better Auth CLI read `options`
177
177
  import type { BetterAuthOptions } from 'better-auth';
178
178
 
179
179
  export default {
@@ -184,22 +184,18 @@ export default {
184
184
  ```ts
185
185
  // src/app.ts
186
186
  import { createNanoApp } from '@nano-app/backend';
187
- import auth from './auth';
188
187
 
189
- createNanoApp({
190
- packages: { betterAuth: auth.options },
191
- mainPlugin: { database: { enabled: true } },
192
- });
188
+ createNanoApp({ mainPlugin: { database: { enabled: true } } });
193
189
  ```
194
190
 
195
191
  Its tables are yours, like the rest of your schema. Add its models with its CLI, then apply them with Prisma; run both again when you change the options:
196
192
 
197
193
  ```bash
198
- npx auth generate --config src/auth.ts --adapter prisma --dialect postgresql --output src/prisma/schema.prisma
194
+ npx auth generate --adapter prisma --dialect postgresql --output src/prisma/schema.prisma
199
195
  npx prisma migrate dev
200
196
  ```
201
197
 
202
- - It needs the database: `packages.betterAuth` without `mainPlugin.database.enabled` fails at startup.
198
+ - It needs the database: `src/auth.ts` without `mainPlugin.database.enabled` fails at startup, and so does a file without `options`.
203
199
  - Startup also fails if your schema lacks a model the options need (`user`, `session`, ...), naming them.
204
200
  - Its endpoints are mounted with your routes, at its `basePath` (default `/api/auth`), and stay public.
205
201
  - 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).
@@ -213,6 +209,7 @@ npx prisma migrate dev
213
209
  - `createRoute({ public: true, ... })` answers without a session; its handlers still receive `session` when the request has one. An unknown path answers 404.
214
210
  - 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.
215
211
  - Better Auth is loaded only when it is on.
212
+ - Before 0.14.0 the options went in `packages.betterAuth`: move them to the default export of `src/auth.ts`.
216
213
  - 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.
217
214
 
218
215
  ### Permissions
package/build/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import express, { Express, Request, Request as Request$1 } from "express";
2
2
  import { CorsOptions } from "cors";
3
3
  import "dotenv/config";
4
4
  import "jiti";
5
- import { BetterAuthOptions } from "better-auth";
5
+ import "better-auth";
6
6
  /** API response */
7
7
  type ApiResponse<T = any> = {
8
8
  status?: number;
@@ -142,8 +142,6 @@ type PackagesConfig = {
142
142
  express?: Partial<Parameters<typeof express.json>[0]> | false;
143
143
  /** urlencoded configuration */
144
144
  urlencoded?: Partial<Parameters<typeof express.urlencoded>[0]> | false;
145
- /** Better Auth options, without `database`: it uses the app's Prisma client. Defaults to false */
146
- betterAuth?: Omit<BetterAuthOptions, 'database'> | false;
147
145
  };
148
146
  /** Application configuration */
149
147
  type NanoAppConfig = {
package/build/index.js CHANGED
@@ -5,9 +5,9 @@ import cors from "cors";
5
5
  import helmet from "helmet";
6
6
  import compression from "compression";
7
7
  import "dotenv/config";
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
9
  import { glob } from "glob";
9
10
  import { dirname, isAbsolute, join, resolve } from "node:path";
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
11
  import { createJiti } from "jiti";
12
12
  import { execFileSync } from "node:child_process";
13
13
  import { createHash } from "node:crypto";
@@ -165,6 +165,57 @@ var FileLoader = class {
165
165
  return Validator.validate(oEntries, extension);
166
166
  }
167
167
  };
168
+ // src/auth/BetterAuth.ts
169
+ /** Files of the app's Better Auth options, relative to `cwd`: source, then built */
170
+ const AUTH_FILES = ["src/auth.ts", "src/auth.js"];
171
+ /** Better Auth on the app's Prisma client: the instance, its handler and the session lookup */
172
+ var BetterAuth = class {
173
+ static oAuth;
174
+ /** The options of the app's `src/auth.ts`, or undefined without it: Better Auth stays off */
175
+ static async findOptions() {
176
+ const sFilePath = AUTH_FILES.find(existsSync);
177
+ if (!sFilePath) return void 0;
178
+ const oFile = await FileReader.loadModule(sFilePath, { default: true });
179
+ if (!oFile?.options) throw new Error(`[nano-app] [ERROR] ${sFilePath} must default export { options }: the Better Auth options.`);
180
+ return oFile.options;
181
+ }
182
+ /** The instance, once the app's plugin created it */
183
+ static get() {
184
+ return this.oAuth;
185
+ }
186
+ /** Creates the instance on the app's client, its handler and the session lookup */
187
+ static async create(oOptions, oDb) {
188
+ const { betterAuth } = await import("better-auth");
189
+ const { prismaAdapter } = await import("better-auth/adapters/prisma");
190
+ const { getSchema } = await import("better-auth/db");
191
+ const { fromNodeHeaders, toNodeHandler } = await import("better-auth/node");
192
+ const aMissing = Object.keys(getSchema(oOptions)).filter((sModel) => !oDb[sModel]);
193
+ if (aMissing.length) throw new Error(`[nano-app] [ERROR] Better Auth needs the models ${aMissing.join(", ")} in the app's schema. Run \`npx auth generate\` and apply your schema with Prisma.`);
194
+ const oAuth = betterAuth({
195
+ ...oOptions,
196
+ advanced: {
197
+ ...oOptions.advanced,
198
+ ipAddress: {
199
+ trustedProxies: PRIVATE_NETWORKS,
200
+ ...oOptions.advanced?.ipAddress
201
+ }
202
+ },
203
+ database: prismaAdapter(oDb, { provider: "postgresql" })
204
+ });
205
+ this.oAuth = oAuth;
206
+ const fnSession = (oReq, oRes, fnNext) => {
207
+ oAuth.api.getSession({ headers: fromNodeHeaders(oReq.headers) }).then((oSession) => {
208
+ oRes.locals.session = oSession ?? void 0;
209
+ fnNext();
210
+ }).catch(fnNext);
211
+ };
212
+ return {
213
+ oHandler: toNodeHandler(oAuth),
214
+ fnSession,
215
+ sPath: `${oOptions.basePath ?? "/api/auth"}/{*path}`
216
+ };
217
+ }
218
+ };
168
219
  // src/auth/permissions.ts
169
220
  /** Whether the session's user has the permissions; always without Better Auth or permissions */
170
221
  async function isGranted({ auth, session }, oPermissions) {
@@ -196,7 +247,7 @@ var Endpoint = class {
196
247
  }
197
248
  /** Handler for all methods */
198
249
  async mount(oParams, sPath) {
199
- if (!oParams.auth && Object.values(this.oMethods).some(({ permissions }) => permissions)) console.warn(`[nano-app] [WARNING] ${sPath}: its permissions are not checked without Better Auth. Turn on packages.betterAuth.`);
250
+ if (!oParams.auth && Object.values(this.oMethods).some(({ permissions }) => permissions)) console.warn(`[nano-app] [WARNING] ${sPath}: its permissions are not checked without Better Auth. Add src/auth.ts.`);
200
251
  await this.oCallbacks.onInit?.(oParams);
201
252
  return this.createHandler(oParams);
202
253
  }
@@ -272,47 +323,6 @@ var Router = class {
272
323
  return `${this.sBasePath}${sEndpoint.replace(/\/index$/, "")}${sExpression}` || "/";
273
324
  }
274
325
  };
275
- // src/auth/BetterAuth.ts
276
- /** Better Auth on the app's Prisma client: the instance, its handler and the session lookup */
277
- var BetterAuth = class {
278
- static oAuth;
279
- /** The instance, once the app's plugin created it */
280
- static get() {
281
- return this.oAuth;
282
- }
283
- /** Creates the instance on the app's client, its handler and the session lookup */
284
- static async create(oOptions, oDb) {
285
- const { betterAuth } = await import("better-auth");
286
- const { prismaAdapter } = await import("better-auth/adapters/prisma");
287
- const { getSchema } = await import("better-auth/db");
288
- const { fromNodeHeaders, toNodeHandler } = await import("better-auth/node");
289
- const aMissing = Object.keys(getSchema(oOptions)).filter((sModel) => !oDb[sModel]);
290
- if (aMissing.length) throw new Error(`[nano-app] [ERROR] Better Auth needs the models ${aMissing.join(", ")} in the app's schema. Run \`npx auth generate\` and apply your schema with Prisma.`);
291
- const oAuth = betterAuth({
292
- ...oOptions,
293
- advanced: {
294
- ...oOptions.advanced,
295
- ipAddress: {
296
- trustedProxies: PRIVATE_NETWORKS,
297
- ...oOptions.advanced?.ipAddress
298
- }
299
- },
300
- database: prismaAdapter(oDb, { provider: "postgresql" })
301
- });
302
- this.oAuth = oAuth;
303
- const fnSession = (oReq, oRes, fnNext) => {
304
- oAuth.api.getSession({ headers: fromNodeHeaders(oReq.headers) }).then((oSession) => {
305
- oRes.locals.session = oSession ?? void 0;
306
- fnNext();
307
- }).catch(fnNext);
308
- };
309
- return {
310
- oHandler: toNodeHandler(oAuth),
311
- fnSession,
312
- sPath: `${oOptions.basePath ?? "/api/auth"}/{*path}`
313
- };
314
- }
315
- };
316
326
  // src/database/Prisma.ts
317
327
  /** Runs the consumer's Prisma CLI: generates clients and applies official plugins' migrations */
318
328
  var Prisma = class {
@@ -462,7 +472,7 @@ var Plugin = class {
462
472
  const { name, routesDir, basePath, database, options, callbacks } = this.oConfig;
463
473
  const oDb = database?.enabled ? await Database.mount(name, database) : Database.get("app");
464
474
  if (this.oAuthOptions) {
465
- if (!oDb) throw new Error("[nano-app] [ERROR] packages.betterAuth needs the database of the app: set mainPlugin.database.enabled to true.");
475
+ if (!oDb) throw new Error("[nano-app] [ERROR] src/auth.ts needs the database of the app: set mainPlugin.database.enabled to true.");
466
476
  const { sPath, oHandler, fnSession } = await BetterAuth.create(this.oAuthOptions, oDb);
467
477
  oApp.all(sPath, oHandler);
468
478
  oApp.use(fnSession);
@@ -506,8 +516,7 @@ const INITIAL_CONFIG = {
506
516
  helmet: true,
507
517
  compression: true,
508
518
  express: { limit: "10kb" },
509
- urlencoded: { extended: false },
510
- betterAuth: false
519
+ urlencoded: { extended: false }
511
520
  },
512
521
  /** Main plugin to mount */
513
522
  mainPlugin: {
@@ -643,8 +652,8 @@ var NanoApp = class {
643
652
  };
644
653
  for (const oPlugin of this.oConfig.plugins) oPlugins[oPlugin.stage ?? "before"].push(oPlugin);
645
654
  for (const oPlugin of oPlugins.before) await new Plugin(oPlugin).mount(this.oApp);
646
- const { mainPlugin, packages } = this.oConfig;
647
- await new Plugin(mainPlugin, packages.betterAuth).mount(this.oApp);
655
+ const { mainPlugin } = this.oConfig;
656
+ await new Plugin(mainPlugin, await BetterAuth.findOptions()).mount(this.oApp);
648
657
  for (const oPlugin of oPlugins.after) await new Plugin(oPlugin).mount(this.oApp);
649
658
  this.oApp.use(notFoundHandler);
650
659
  this.oApp.use(errorHandler);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nano-app/backend",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Express server with file-convention routes, plugins and an optional PostgreSQL layer",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -47,7 +47,7 @@
47
47
  "cors": "^2.8.6",
48
48
  "dotenv": "^18.0.9",
49
49
  "express": "^5.3.0",
50
- "glob": "^14.0.3",
50
+ "glob": "^14.0.4",
51
51
  "helmet": "^8.3.0",
52
52
  "jiti": "^2.7.0",
53
53
  "morgan": "^1.12.1"
@@ -55,7 +55,7 @@
55
55
  "peerDependencies": {
56
56
  "@prisma/adapter-pg": "^7.10.1",
57
57
  "@prisma/client": "^7.10.1",
58
- "better-auth": "^1.7.15",
58
+ "better-auth": "^1.7.18",
59
59
  "pg": "^8.23.1"
60
60
  },
61
61
  "peerDependenciesMeta": {
@@ -81,10 +81,10 @@
81
81
  "@types/morgan": "^1.9.10",
82
82
  "@types/node": "^26.6.6",
83
83
  "@types/pg": "^8.20.0",
84
- "better-auth": "^1.7.15",
84
+ "better-auth": "^1.7.18",
85
85
  "pg": "^8.23.1",
86
86
  "prisma": "^7.10.1",
87
- "tsdown": "^0.23.4",
87
+ "tsdown": "^0.23.5",
88
88
  "tsx": "^4.23.15",
89
89
  "typescript": "^7.0.2"
90
90
  }