@nano-app/backend 0.12.5 → 0.14.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 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,39 +166,36 @@ 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, and an instance only for the Better Auth CLI
177
- import { betterAuth, type BetterAuthOptions } from 'better-auth';
176
+ // src/auth.ts: nano-app and the Better Auth CLI read `options`
177
+ import type { BetterAuthOptions } from 'better-auth';
178
178
 
179
- export const authOptions = { emailAndPassword: { enabled: true } } satisfies BetterAuthOptions;
180
- export const auth = betterAuth(authOptions);
179
+ export default {
180
+ options: { emailAndPassword: { enabled: true } } satisfies BetterAuthOptions,
181
+ };
181
182
  ```
182
183
 
183
184
  ```ts
184
185
  // src/app.ts
185
186
  import { createNanoApp } from '@nano-app/backend';
186
- import { authOptions } from './auth';
187
187
 
188
- createNanoApp({
189
- packages: { betterAuth: authOptions },
190
- mainPlugin: { database: { enabled: true } },
191
- });
188
+ createNanoApp({ mainPlugin: { database: { enabled: true } } });
192
189
  ```
193
190
 
194
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:
195
192
 
196
193
  ```bash
197
- 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
198
195
  npx prisma migrate dev
199
196
  ```
200
197
 
201
- - 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`.
202
199
  - Startup also fails if your schema lacks a model the options need (`user`, `session`, ...), naming them.
203
200
  - Its endpoints are mounted with your routes, at its `basePath` (default `/api/auth`), and stay public.
204
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).
@@ -212,6 +209,8 @@ npx prisma migrate dev
212
209
  - `createRoute({ public: true, ... })` answers without a session; its handlers still receive `session` when the request has one. An unknown path answers 404.
213
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.
214
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`.
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.
215
214
 
216
215
  ### Permissions
217
216
 
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.12.5",
3
+ "version": "0.14.0",
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",