@browserlessapi/inferlo 0.3.0 → 0.5.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.
Files changed (115) hide show
  1. package/README.md +18 -3
  2. package/dist/adonisjs.d.ts +33 -0
  3. package/dist/adonisjs.js +30 -0
  4. package/dist/adonisjs.js.map +1 -0
  5. package/dist/ai/index.d.ts +51 -2
  6. package/dist/ai/index.js +186 -37
  7. package/dist/ai/index.js.map +1 -1
  8. package/dist/ai/providers.d.ts +56 -3
  9. package/dist/ai/providers.js +102 -11
  10. package/dist/ai/providers.js.map +1 -1
  11. package/dist/ai/public.d.ts +20 -0
  12. package/dist/ai/public.js +17 -0
  13. package/dist/ai/public.js.map +1 -0
  14. package/dist/ai/tool.d.ts +47 -0
  15. package/dist/ai/tool.js +85 -0
  16. package/dist/ai/tool.js.map +1 -0
  17. package/dist/ai-client.d.ts +3 -0
  18. package/dist/ai-client.js +8 -0
  19. package/dist/ai-client.js.map +1 -0
  20. package/dist/app.d.ts +102 -2
  21. package/dist/app.js +509 -111
  22. package/dist/app.js.map +1 -1
  23. package/dist/cli/main.js +76 -4
  24. package/dist/cli/main.js.map +1 -1
  25. package/dist/client-ip.d.ts +7 -0
  26. package/dist/client-ip.js +27 -0
  27. package/dist/client-ip.js.map +1 -0
  28. package/dist/config.d.ts +16 -0
  29. package/dist/config.js +101 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/cron.d.ts +17 -0
  32. package/dist/cron.js +143 -0
  33. package/dist/cron.js.map +1 -0
  34. package/dist/db/migrate.d.ts +6 -3
  35. package/dist/db/migrate.js +126 -10
  36. package/dist/db/migrate.js.map +1 -1
  37. package/dist/db/table.d.ts +83 -7
  38. package/dist/db/table.js +245 -9
  39. package/dist/db/table.js.map +1 -1
  40. package/dist/deprecation.d.ts +23 -0
  41. package/dist/deprecation.js +70 -0
  42. package/dist/deprecation.js.map +1 -0
  43. package/dist/describe.d.ts +45 -5
  44. package/dist/describe.js +19 -2
  45. package/dist/describe.js.map +1 -1
  46. package/dist/elysia.d.ts +49 -0
  47. package/dist/elysia.js +10 -0
  48. package/dist/elysia.js.map +1 -0
  49. package/dist/env.d.ts +2 -0
  50. package/dist/env.js +5 -4
  51. package/dist/env.js.map +1 -1
  52. package/dist/express.d.ts +38 -0
  53. package/dist/express.js +18 -0
  54. package/dist/express.js.map +1 -0
  55. package/dist/fastify.d.ts +44 -0
  56. package/dist/fastify.js +47 -0
  57. package/dist/fastify.js.map +1 -0
  58. package/dist/hono.d.ts +26 -0
  59. package/dist/hono.js +10 -0
  60. package/dist/hono.js.map +1 -0
  61. package/dist/index.d.ts +20 -6
  62. package/dist/index.js +7 -3
  63. package/dist/index.js.map +1 -1
  64. package/dist/jobs.d.ts +181 -0
  65. package/dist/jobs.js +391 -0
  66. package/dist/jobs.js.map +1 -0
  67. package/dist/logging.d.ts +23 -0
  68. package/dist/logging.js +87 -0
  69. package/dist/logging.js.map +1 -0
  70. package/dist/mcp.d.ts +4 -6
  71. package/dist/mcp.js +53 -38
  72. package/dist/mcp.js.map +1 -1
  73. package/dist/metrics.d.ts +48 -0
  74. package/dist/metrics.js +184 -0
  75. package/dist/metrics.js.map +1 -0
  76. package/dist/nestjs.d.ts +43 -0
  77. package/dist/nestjs.js +47 -0
  78. package/dist/nestjs.js.map +1 -0
  79. package/dist/openapi.d.ts +0 -1
  80. package/dist/openapi.js +7 -0
  81. package/dist/openapi.js.map +1 -1
  82. package/dist/rate-limit.d.ts +45 -0
  83. package/dist/rate-limit.js +93 -0
  84. package/dist/rate-limit.js.map +1 -0
  85. package/dist/redact.d.ts +19 -0
  86. package/dist/redact.js +150 -0
  87. package/dist/redact.js.map +1 -0
  88. package/dist/route.d.ts +24 -0
  89. package/dist/route.js.map +1 -1
  90. package/dist/schema.d.ts +10 -2
  91. package/dist/schema.js +13 -1
  92. package/dist/schema.js.map +1 -1
  93. package/dist/secure-headers.d.ts +8 -0
  94. package/dist/secure-headers.js +61 -0
  95. package/dist/secure-headers.js.map +1 -0
  96. package/dist/serve.d.ts +53 -1
  97. package/dist/serve.js +158 -59
  98. package/dist/serve.js.map +1 -1
  99. package/dist/trpc.d.ts +57 -0
  100. package/dist/trpc.js +142 -0
  101. package/dist/trpc.js.map +1 -0
  102. package/dist/tsed.d.ts +32 -0
  103. package/dist/tsed.js +13 -0
  104. package/dist/tsed.js.map +1 -0
  105. package/llms.txt +10 -4
  106. package/package.json +57 -3
  107. package/templates/app/AGENTS.md +14 -8
  108. package/templates/app/README.md +2 -0
  109. package/templates/app/_env.example +2 -0
  110. package/templates/app/package.json.tpl +1 -0
  111. package/templates/app/src/app.ts +15 -8
  112. package/templates/app/src/config.ts +17 -0
  113. package/templates/app/src/routes/notes.ts +1 -1
  114. package/templates/app/src/server.ts +13 -3
  115. package/templates/app/src/routes/health.ts +0 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@browserlessapi/inferlo",
3
- "version": "0.3.0",
4
- "description": "The TypeScript backend framework with built-in AI model support: Anthropic, OpenAI, Gemini, Ollama and OpenAI-compatible models, plus routing, database, validation, auth and MCP in one package.",
3
+ "version": "0.5.0",
4
+ "description": "The enterprise-ready TypeScript backend framework, with AI models built in: routing, validation, PostgreSQL and migrations, auth, rate limiting, jobs, OpenAPI and MCP, plus Anthropic, OpenAI, Gemini, Ollama and OpenAI-compatible models.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "BrowserlessAPI",
@@ -35,6 +35,42 @@
35
35
  "types": "./dist/index.d.ts",
36
36
  "default": "./dist/index.js"
37
37
  },
38
+ "./adonisjs": {
39
+ "types": "./dist/adonisjs.d.ts",
40
+ "default": "./dist/adonisjs.js"
41
+ },
42
+ "./ai": {
43
+ "types": "./dist/ai/public.d.ts",
44
+ "default": "./dist/ai/public.js"
45
+ },
46
+ "./elysia": {
47
+ "types": "./dist/elysia.d.ts",
48
+ "default": "./dist/elysia.js"
49
+ },
50
+ "./express": {
51
+ "types": "./dist/express.d.ts",
52
+ "default": "./dist/express.js"
53
+ },
54
+ "./fastify": {
55
+ "types": "./dist/fastify.d.ts",
56
+ "default": "./dist/fastify.js"
57
+ },
58
+ "./hono": {
59
+ "types": "./dist/hono.d.ts",
60
+ "default": "./dist/hono.js"
61
+ },
62
+ "./nestjs": {
63
+ "types": "./dist/nestjs.d.ts",
64
+ "default": "./dist/nestjs.js"
65
+ },
66
+ "./tsed": {
67
+ "types": "./dist/tsed.d.ts",
68
+ "default": "./dist/tsed.js"
69
+ },
70
+ "./trpc": {
71
+ "types": "./dist/trpc.d.ts",
72
+ "default": "./dist/trpc.js"
73
+ },
38
74
  "./postgres": {
39
75
  "types": "./dist/db/postgres.d.ts",
40
76
  "default": "./dist/db/postgres.js"
@@ -57,7 +93,7 @@
57
93
  "scripts": {
58
94
  "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli/main.js",
59
95
  "prepare": "npm run build",
60
- "typecheck": "tsc --noEmit && tsc --noEmit -p examples/helpdesk",
96
+ "typecheck": "tsc --noEmit && tsc --noEmit -p examples/helpdesk && tsc --noEmit -p examples/with-hono && tsc --noEmit -p examples/with-express && tsc --noEmit -p examples/with-fastify && tsc --noEmit -p examples/with-elysia && tsc --noEmit -p examples/with-nestjs && tsc --noEmit -p examples/with-trpc && tsc --noEmit -p examples/with-adonisjs && tsc --noEmit -p examples/with-tsed",
61
97
  "test": "node --test --test-reporter=spec \"test/**/*.test.ts\" \"examples/*/test/*.test.ts\"",
62
98
  "test:bun": "bun test test/",
63
99
  "llms": "node scripts/build-llms.ts",
@@ -73,9 +109,27 @@
73
109
  }
74
110
  },
75
111
  "devDependencies": {
112
+ "@adonisjs/core": "^6.21.0",
113
+ "@nestjs/common": "^12.3.3",
114
+ "@nestjs/core": "^12.3.3",
115
+ "@nestjs/platform-express": "^12.3.3",
116
+ "@trpc/server": "^11.24.0",
117
+ "@tsed/core": "^8.45.0",
118
+ "@tsed/di": "^8.45.0",
119
+ "@tsed/platform-express": "^8.45.0",
120
+ "@tsed/platform-http": "^8.45.0",
121
+ "@tsed/platform-params": "^8.45.0",
122
+ "@tsed/schema": "^8.45.0",
123
+ "@types/express": "^5.0.6",
76
124
  "@types/node": "^22.18.0",
77
125
  "@types/pg": "^8.15.0",
126
+ "elysia": "^1.4.30",
127
+ "express": "^5.3.0",
128
+ "fastify": "^5.12.8",
129
+ "hono": "^4.13.16",
78
130
  "pg": "^8.16.0",
131
+ "reflect-metadata": "^0.2.2",
132
+ "rxjs": "^7.8.2",
79
133
  "typescript": "^5.9.3"
80
134
  },
81
135
  "publishConfig": {
@@ -22,6 +22,7 @@ Do not finish until `inferlo check` passes.
22
22
  |---|---|
23
23
  | `src/models.ts` | Every model (`model("table_name", { ... })`) and the `models` array |
24
24
  | `src/routes/<feature>.ts` | Routes for one feature, each one an exported `const` |
25
+ | `src/config.ts` | `settings`: every environment variable, validated at startup with `config({ schema, secrets })` |
25
26
  | `src/app.ts` | `createApp({ models, routes, db, auth, ai })`. Import route modules with `import * as x` |
26
27
  | `src/server.ts` | Starts the server. Keep it small |
27
28
  | `test/<feature>.test.ts` | Tests using `app.request(path, { method, json, as: user })` |
@@ -32,14 +33,19 @@ Do not finish until `inferlo check` passes.
32
33
  2. **Routes are private by default.** A route without `auth` needs a signed-in user. Use `auth: "public"` only for routes anyone may call, and `auth: "<role>"` for role checks.
33
34
  3. **Validate every input with a schema.** `body`, `query` and `params` options. Reuse model schemas: `Ticket.pick("title", "body")`, `Ticket.omit("ownerId")`, `.partial()` for PATCH.
34
35
  4. **Check ownership.** Fetch with `db.x.get(id)` (throws 404) and compare `ownerId` to `user.id`; throw `notFound()` when it does not match, so other users' ids are not revealed.
35
- 5. **Never write raw SQL with string concatenation.** Use the table methods. If you really need SQL, use `db.$sql(text, params)` with `$1` placeholders.
36
- 6. **Model ids, timestamps:** every model already has `id`, `createdAt`, `updatedAt`. Never define them or send them as input.
37
- 7. **New field?** Add it to the model in `src/models.ts`, then run `npx inferlo db push` in development, or `npx inferlo db migrate <name>` to write a migration file for production (applied with `npx inferlo db migrate --apply`). Use `.optional()` or `.default()` for new fields on existing tables. Never edit `migrations/snapshot.json`, and never edit a migration file that has been applied (`--apply` warns with IL4018 and does not run the change): put changes in a new migration. Removing a model or field needs `npx inferlo db migrate <name> --allow-drop` (it deletes that data); never use it for a rename. Changing a field's default or making it required or optional needs `--allow-change`; give a newly required field a `.default()` so existing NULL rows get a value.
38
- 8. **New resource?** `npx inferlo add resource <plural_name>` generates the model and CRUD routes. Then edit them.
39
- 9. **AI calls** use aliases from `src/app.ts` (`model: "fast"`), never hard-coded provider model ids in routes. Use `output: schema` for structured data and `for: user.id` so usage is tracked per user.
40
- 10. **Errors:** throw `notFound()`, `forbidden()`, `badRequest()` or `httpError(status, message)`. Every Inferlo error has a code (KRxxxx), a fix and a docs link: read the fix before changing code.
41
- 11. **Imports:** relative imports end in `.ts` (`import { Note } from "../models.ts"`).
42
- 12. **Expose to AI agents:** add `tool: true` to a route to make it an MCP tool at `/mcp`. Same auth applies.
36
+ 5. **Related rows:** a `t.ref` field named `<name>Id` is a relation: `db.notes.findMany({ include: { owner: true } })` adds `note.owner` with one extra query, and `db.users.findMany({ include: { notes: true } })` adds `user.notes` (the rows that point to each user). Never load related rows in a loop. For lists clients page through, use `db.notes.paginate({ where, orderBy: { createdAt: "desc" }, cursor: query.cursor, limit: query.limit })`, which returns `{ items, nextCursor }`.
37
+ 6. **Never write raw SQL with string concatenation.** Use the table methods. If you really need SQL, use `db.$sql(text, params)` with `$1` placeholders.
38
+ 7. **Model ids, timestamps:** every model already has `id`, `createdAt`, `updatedAt`. Never define them or send them as input.
39
+ 8. **New field?** Add it to the model in `src/models.ts`, then run `npx inferlo db push` in development, or `npx inferlo db migrate <name>` to write a migration file for production (applied with `npx inferlo db migrate --apply`). Use `.optional()` or `.default()` for new fields on existing tables. Never edit `migrations/snapshot.json`, and never edit a migration file that has been applied (`--apply` warns with IL4018 and does not run the change): put changes in a new migration. Removing a model or field needs `npx inferlo db migrate <name> --allow-drop` (it deletes that data); never use it for a rename. Changing a field's type or default, making it required or optional, or changing an enum's values needs `--allow-change`; give a newly required field a `.default()` so existing NULL rows get a value, and before removing an enum value add an `UPDATE` in the new migration file (before its `ADD CONSTRAINT`) that moves rows off it (IL4021).
40
+ 9. **New resource?** `npx inferlo add resource <plural_name>` generates the model and CRUD routes. Then edit them.
41
+ 10. **AI calls** use aliases from `src/app.ts` (`model: "fast"`), never hard-coded provider model ids in routes. Use `output: schema` for structured data and `for: user.id` so usage is tracked per user. To let the model act, pass routes as tools: `ai.generate({ prompt, tools: [createNote, getNote], for: user.id })`. Each tool call runs as the current user with that route's auth and validation. For work that is not a route (an external API, a calculation), make a function tool: `tool("getWeather", { description, input: t.object({ ... }) }).handle(async ({ input }) => ...)`. To send images: `ai.generate({ prompt, images: [{ url }] })` or `[{ base64, mediaType: "image/png" }]`. Give routes that call AI models, send email or check passwords a `rateLimit: { max, per, by }` (IL2005 is the 429 it returns).
42
+ 11. **Errors:** throw `notFound()`, `forbidden()`, `badRequest()` or `httpError(status, message)`. Every Inferlo error has a code (ILxxxx), a fix and a docs link: read the fix before changing code.
43
+ 12. **Imports:** relative imports end in `.ts` (`import { Note } from "../models.ts"`).
44
+ 13. **Configuration:** read environment variables only through `settings` from `src/config.ts`. A new variable is a new field in its `t.object({ ... })` schema (`.default(...)` or `.optional()` unless the app cannot start without it), and API keys, passwords and connection URLs go in `secrets` so they are never logged. Never read `process.env` in routes.
45
+ 14. **Expose to AI agents:** add `tool: true` to a route to make it an MCP tool at `/mcp`. Same auth applies.
46
+ 15. **Slow work after the response** (email, slow AI calls) is a background job: `export const x = job("send-email", { input: t.object({ ... }) }).handle(async ({ input, db, ai }) => { ... })` in `src/jobs.ts`, registered with `createApp({ jobs: [jobs] })`, queued with `await ctx.jobs.enqueue(x, { ... })`. Pass ids in the input, not rows. In tests, run queued jobs with `await app.jobs.runDue()`. Jobs run in the server process (`app.jobs.start()` in `src/server.ts`); with `createApp({ jobQueue: { store: "database" } })` they are kept in Postgres and `npm run worker` (`inferlo worker`) runs them in separate processes. Scheduled work is a cron next to its job: `export const nightly = cron("0 3 * * *", cleanupJob)` (UTC; the job's input must be `t.object({})`).
47
+ 16. **Security headers and health:** every response already gets security headers (CSP, HSTS over HTTPS, nosniff, frame and referrer policies) and `GET /health` and `GET /ready` are built in (`health: true` in `src/app.ts`). Change a header with `createApp({ secureHeaders: { "Content-Security-Policy": "..." } })` instead of setting it in every route, never turn them all off without a proxy that adds them, and never add your own `/health` route. On SIGTERM, `src/server.ts` calls `app.close()`, which finishes in-flight requests and jobs first. Prometheus metrics (`GET /metrics`: requests, jobs, AI calls, tokens and cost) are served when `METRICS_TOKEN` is set; never add your own `/metrics` route.
48
+ 17. **Replacing a route:** do not delete a route clients use. Add the new one and mark the old one `deprecated: { since: "YYYY-MM-DD", sunset: "YYYY-MM-DD", link: "https://..." }` in its options; it then sends the standard Deprecation and Sunset headers and is marked in OpenAPI and MCP. Remove it after the sunset date.
43
49
 
44
50
  ## Testing
45
51
 
@@ -11,4 +11,6 @@ npx inferlo describe # routes, models and auth at a glance
11
11
 
12
12
  Set `DATABASE_URL` in `.env` to use PostgreSQL, then run `npx inferlo db push`.
13
13
 
14
+ Background jobs run in the server process. To run them in separate processes too, set `jobQueue: { store: "database" }` in `src/app.ts` (needs PostgreSQL) and start workers with `npm run worker`.
15
+
14
16
  AI coding agents: read `AGENTS.md`.
@@ -3,3 +3,5 @@ DATABASE_URL=
3
3
  PORT=3000
4
4
  ANTHROPIC_API_KEY=
5
5
  OPENAI_API_KEY=
6
+ # Set to a random secret of 16+ characters to serve Prometheus metrics at GET /metrics.
7
+ # METRICS_TOKEN=
@@ -10,6 +10,7 @@
10
10
  "scripts": {
11
11
  "dev": "inferlo dev",
12
12
  "start": "node src/server.ts",
13
+ "worker": "inferlo worker",
13
14
  "check": "inferlo check",
14
15
  "describe": "inferlo describe",
15
16
  "db:push": "inferlo db push",
@@ -1,18 +1,25 @@
1
- import { anthropic, bearer, createApp, env, hashToken, memory, openai } from "@browserlessapi/inferlo";
1
+ import { anthropic, bearer, createApp, hashToken, memory, openai } from "@browserlessapi/inferlo";
2
2
  import { postgres } from "@browserlessapi/inferlo/postgres";
3
+ import { settings } from "./config.ts";
3
4
  import { models } from "./models.ts";
4
- import * as health from "./routes/health.ts";
5
5
  import * as notes from "./routes/notes.ts";
6
6
 
7
- const databaseUrl = env("DATABASE_URL", { optional: true });
8
-
9
7
  export const app = createApp({
10
8
  name: "{{name}}",
11
9
  version: "0.1.0",
12
10
  models,
13
11
  // inferlo:routes
14
- routes: [health, notes],
15
- db: databaseUrl ? postgres({ url: databaseUrl }) : memory(),
12
+ routes: [notes],
13
+ // GET /health (liveness) and GET /ready (readiness: database and AI configuration).
14
+ health: true,
15
+ // Config secrets never appear in logs (provider keys, tokens and passwords in URLs are hidden too).
16
+ redact: [settings],
17
+ // One log line per request with its id (also in the X-Request-Id header), status and duration.
18
+ // Use format: "json" for one JSON object per line when a log collector reads the output.
19
+ logging: { requests: true },
20
+ // Prometheus metrics at GET /metrics (requests, jobs, AI calls, tokens and cost) when METRICS_TOKEN is set.
21
+ metrics: settings.METRICS_TOKEN ? { token: settings.METRICS_TOKEN } : false,
22
+ db: settings.DATABASE_URL ? postgres({ url: settings.DATABASE_URL }) : memory(),
16
23
 
17
24
  // Requests send "Authorization: Bearer <api key>". Keys are stored as SHA-256 hashes.
18
25
  auth: bearer(async (token, { db }) => {
@@ -24,8 +31,8 @@ export const app = createApp({
24
31
 
25
32
  ai: {
26
33
  providers: {
27
- anthropic: anthropic({ apiKey: env("ANTHROPIC_API_KEY", { default: "" }) }),
28
- openai: openai({ apiKey: env("OPENAI_API_KEY", { default: "" }) }),
34
+ anthropic: anthropic({ apiKey: settings.ANTHROPIC_API_KEY }),
35
+ openai: openai({ apiKey: settings.OPENAI_API_KEY }),
29
36
  },
30
37
  // Aliases keep model ids in one place. A list means: try in order.
31
38
  models: {
@@ -0,0 +1,17 @@
1
+ import { config, t } from "@browserlessapi/inferlo";
2
+
3
+ // Every environment variable the app reads, validated once at startup (IL7003 lists what is wrong).
4
+ // Values come from the environment (inferlo dev reads .env), then from `environments`, then defaults.
5
+ // Secrets are redacted when settings are logged or serialised.
6
+ export const settings = config({
7
+ schema: t.object({
8
+ // Leave empty to use the in-memory database (data resets on restart).
9
+ DATABASE_URL: t.string().optional(),
10
+ PORT: t.int().min(1).max(65535).default(3000),
11
+ ANTHROPIC_API_KEY: t.string().default(""),
12
+ OPENAI_API_KEY: t.string().default(""),
13
+ // Set to serve Prometheus metrics at GET /metrics (scrapers send it as a bearer token).
14
+ METRICS_TOKEN: t.string().min(16).optional(),
15
+ }),
16
+ secrets: ["DATABASE_URL", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "METRICS_TOKEN"],
17
+ });
@@ -47,7 +47,7 @@ export const deleteNote = route
47
47
  const Summary = t.object({ summary: t.string().max(300).describe("One or two sentences") });
48
48
 
49
49
  export const summarizeNote = route
50
- .post("/notes/:id/summary", { params: IdParams, summary: "Summarize a note with AI" })
50
+ .post("/notes/:id/summary", { params: IdParams, summary: "Summarize a note with AI", rateLimit: { max: 20, per: "hour" } })
51
51
  .handle(async ({ params, user, db, ai }) => {
52
52
  const note = await db.notes.get(params.id);
53
53
  if (note.ownerId !== user.id) throw notFound();
@@ -1,5 +1,6 @@
1
- import { createToken, env, hashToken } from "@browserlessapi/inferlo";
1
+ import { createToken, hashToken } from "@browserlessapi/inferlo";
2
2
  import { app } from "./app.ts";
3
+ import { settings } from "./config.ts";
3
4
 
4
5
  await app.db.$sync();
5
6
 
@@ -9,8 +10,17 @@ if (app.db.$adapter.dialect === "memory") {
9
10
  const token = createToken("dev");
10
11
  await app.db.api_keys.insert({ userId: user.id, hash: await hashToken(token) });
11
12
  console.log(`Dev API key: ${token}`);
12
- console.log(`Try: curl -H "Authorization: Bearer ${token}" http://localhost:${env("PORT", { default: "3000" })}/notes`);
13
+ console.log(`Try: curl -H "Authorization: Bearer ${token}" http://localhost:${settings.PORT}/notes`);
13
14
  }
14
15
 
15
- const server = await app.listen(env.int("PORT", { default: 3000 }));
16
+ const server = await app.listen(settings.PORT);
16
17
  console.log(`{{name}} running on http://localhost:${server.port}`);
18
+
19
+ // Background jobs run in this process. With jobQueue: { store: "database" } in src/app.ts they can
20
+ // also run in separate processes: npm run worker.
21
+ app.jobs.start();
22
+
23
+ // Graceful shutdown: /ready answers 503 at once so the load balancer stops sending traffic, requests are
24
+ // still served for 5 seconds, then in-flight requests and the running job finish and the server and the
25
+ // database close.
26
+ process.on("SIGTERM", () => void app.close({ delayMs: 5000 }));
@@ -1,3 +0,0 @@
1
- import { route } from "@browserlessapi/inferlo";
2
-
3
- export const health = route.get("/health", { auth: "public", summary: "Health check" }).handle(() => ({ ok: true }));