@browserlessapi/inferlo 0.0.0-stage → 0.4.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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -2
  3. package/dist/ai/index.d.ts +138 -0
  4. package/dist/ai/index.js +450 -0
  5. package/dist/ai/index.js.map +1 -0
  6. package/dist/ai/providers.d.ts +150 -0
  7. package/dist/ai/providers.js +294 -0
  8. package/dist/ai/providers.js.map +1 -0
  9. package/dist/app.d.ts +109 -0
  10. package/dist/app.js +419 -0
  11. package/dist/app.js.map +1 -0
  12. package/dist/auth.d.ts +29 -0
  13. package/dist/auth.js +61 -0
  14. package/dist/auth.js.map +1 -0
  15. package/dist/cli/main.d.ts +2 -0
  16. package/dist/cli/main.js +520 -0
  17. package/dist/cli/main.js.map +1 -0
  18. package/dist/client-ip.d.ts +7 -0
  19. package/dist/client-ip.js +27 -0
  20. package/dist/client-ip.js.map +1 -0
  21. package/dist/cron.d.ts +17 -0
  22. package/dist/cron.js +143 -0
  23. package/dist/cron.js.map +1 -0
  24. package/dist/db/memory.d.ts +2 -0
  25. package/dist/db/memory.js +128 -0
  26. package/dist/db/memory.js.map +1 -0
  27. package/dist/db/migrate.d.ts +87 -0
  28. package/dist/db/migrate.js +471 -0
  29. package/dist/db/migrate.js.map +1 -0
  30. package/dist/db/postgres.d.ts +37 -0
  31. package/dist/db/postgres.js +338 -0
  32. package/dist/db/postgres.js.map +1 -0
  33. package/dist/db/table.d.ts +117 -0
  34. package/dist/db/table.js +274 -0
  35. package/dist/db/table.js.map +1 -0
  36. package/dist/db/types.d.ts +36 -0
  37. package/dist/db/types.js +2 -0
  38. package/dist/db/types.js.map +1 -0
  39. package/dist/describe.d.ts +74 -0
  40. package/dist/describe.js +69 -0
  41. package/dist/describe.js.map +1 -0
  42. package/dist/env.d.ts +19 -0
  43. package/dist/env.js +72 -0
  44. package/dist/env.js.map +1 -0
  45. package/dist/errors.d.ts +40 -0
  46. package/dist/errors.js +99 -0
  47. package/dist/errors.js.map +1 -0
  48. package/dist/index.d.ts +31 -0
  49. package/dist/index.js +20 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/jobs.d.ts +177 -0
  52. package/dist/jobs.js +374 -0
  53. package/dist/jobs.js.map +1 -0
  54. package/dist/mcp.d.ts +22 -0
  55. package/dist/mcp.js +171 -0
  56. package/dist/mcp.js.map +1 -0
  57. package/dist/model.d.ts +36 -0
  58. package/dist/model.js +76 -0
  59. package/dist/model.js.map +1 -0
  60. package/dist/openapi.d.ts +6 -0
  61. package/dist/openapi.js +74 -0
  62. package/dist/openapi.js.map +1 -0
  63. package/dist/rate-limit.d.ts +45 -0
  64. package/dist/rate-limit.js +93 -0
  65. package/dist/rate-limit.js.map +1 -0
  66. package/dist/route.d.ts +131 -0
  67. package/dist/route.js +28 -0
  68. package/dist/route.js.map +1 -0
  69. package/dist/schema.d.ts +164 -0
  70. package/dist/schema.js +442 -0
  71. package/dist/schema.js.map +1 -0
  72. package/dist/serve.d.ts +11 -0
  73. package/dist/serve.js +86 -0
  74. package/dist/serve.js.map +1 -0
  75. package/dist/testing.d.ts +16 -0
  76. package/dist/testing.js +30 -0
  77. package/dist/testing.js.map +1 -0
  78. package/llms.txt +27 -0
  79. package/package.json +82 -4
  80. package/templates/app/AGENTS.md +55 -0
  81. package/templates/app/CLAUDE.md +1 -0
  82. package/templates/app/README.md +16 -0
  83. package/templates/app/_env.example +5 -0
  84. package/templates/app/_gitignore +3 -0
  85. package/templates/app/package.json.tpl +32 -0
  86. package/templates/app/src/app.ts +38 -0
  87. package/templates/app/src/models.ts +31 -0
  88. package/templates/app/src/routes/health.ts +3 -0
  89. package/templates/app/src/routes/notes.ts +56 -0
  90. package/templates/app/src/server.ts +26 -0
  91. package/templates/app/test/notes.test.ts +50 -0
  92. package/templates/app/tsconfig.json +17 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BrowserlessAPI (Nero Softwares & IT Services Pvt. Ltd.)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,131 @@
1
- # Temporary Holding Version
1
+ # Inferlo
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **The TypeScript backend framework with built-in AI model support.**
4
+
5
+ Inferlo is a batteries-included TypeScript backend framework with AI models built into its core. Connect Anthropic, OpenAI, Gemini, DeepSeek, Qwen, local models through Ollama or any OpenAI-compatible server, and call them from any route with one API: automatic fallback between models, typed structured output, streaming, embeddings, and per-user usage and cost tracking. Routing, validation, database, auth, OpenAPI and an MCP server come in the same package, with zero runtime dependencies.
6
+
7
+ ```ts
8
+ import { model, route, t } from "@browserlessapi/inferlo";
9
+
10
+ export const Ticket = model("tickets", {
11
+ title: t.string().min(3).max(200),
12
+ body: t.text(),
13
+ priority: t.enum(["low", "normal", "urgent"]).default("normal"),
14
+ ownerId: t.ref("users"),
15
+ });
16
+
17
+ export const createTicket = route
18
+ .post("/tickets", { body: Ticket.pick("title", "body"), tool: true })
19
+ .handle(({ body, user, db }) => db.tickets.insert({ ...body, ownerId: user.id }));
20
+
21
+ export const triageTicket = route
22
+ .post("/tickets/:id/triage", { params: t.object({ id: t.uuid() }), auth: "agent" })
23
+ .handle(async ({ params, db, ai, user }) => {
24
+ const ticket = await db.tickets.get(params.id);
25
+ const { object } = await ai.generate({
26
+ model: "fast",
27
+ prompt: ticket.body,
28
+ output: t.object({ priority: Ticket.fields.priority, summary: t.string().max(300) }),
29
+ for: user.id,
30
+ });
31
+ return db.tickets.update(ticket.id, object);
32
+ });
33
+ ```
34
+
35
+ That one model definition gives you the database table, insert and update validation, TypeScript types, OpenAPI docs, an MCP tool and the AI output schema.
36
+
37
+ ## AI models built in
38
+
39
+ - **Every major provider, one API.** Anthropic, OpenAI, Gemini, Ollama and any OpenAI-compatible server (DeepSeek, Qwen, Groq, OpenRouter, vLLM and more).
40
+ - **Model aliases with fallback.** Routes ask for `"fast"` or `"smart"`; if the first model is down or rate limited, Inferlo tries the next one.
41
+ - **Typed structured output.** Pass a schema and get validated, typed data back. Invalid answers are repaired automatically.
42
+ - **Tool calling.** Pass routes as `tools` and the model calls them as the current user, with the same auth and validation, a step limit and usage per step.
43
+ - **Streaming.** Send model output to the browser as it is generated with one call.
44
+ - **Images.** Send image URLs or base64 data with a prompt to any vision model.
45
+ - **Embeddings** for search and recommendations.
46
+ - **Usage, cost and budgets.** Every call is tracked per user, so you can bill customers and stop calls when their credits run out.
47
+ - **MCP server.** Mark a route `tool: true` and AI agents can call your API, with the same permissions as any other client.
48
+ - **Background jobs for slow AI work.** Queue a typed job from a route with `ctx.jobs.enqueue(...)` and it runs after the response, with retries, from a Postgres queue shared by every instance (`inferlo worker` runs them in their own process), and schedule jobs with `cron("0 3 * * *", job)`.
49
+
50
+ ## Why Inferlo
51
+
52
+ | | NestJS | Hono | AdonisJS | Express | **Inferlo** |
53
+ |---|---|---|---|---|---|
54
+ | Batteries included (DB, validation, auth) | Partial | No | Yes | No | **Yes** |
55
+ | Runs on Node, Bun, Deno and edge | No | Yes | No | No | **Yes** |
56
+ | One schema for DB, validation, API docs and AI | No | No | No | No | **Yes** |
57
+ | AI providers, fallback, structured output, streaming in core | No | No | No | No | **Yes** |
58
+ | Built-in MCP server from your routes | No | No | No | No | **Yes** |
59
+ | Token usage tracking and budgets | No | No | No | No | **Yes** |
60
+ | Private routes by default | No | No | No | No | **Yes** |
61
+ | Errors with codes, fixes and docs links | No | No | Partial | No | **Yes** |
62
+ | Zero runtime dependencies | No | Yes | No | No | **Yes** |
63
+
64
+ ## Easy to build with AI coding tools
65
+
66
+ Inferlo is also designed so AI coding tools (Claude Code, Cursor, Codex, NeonCodex and others) write correct Inferlo code on the first try:
67
+
68
+ - **Familiar shapes.** Hono-style routes and Zod-style schemas: patterns models already know.
69
+ - **One way to do things.** One route API, one schema API, one query API, one AI API.
70
+ - **No hidden behaviour.** No decorators, no dependency injection, no global registration. Everything a handler needs is in its file.
71
+ - **Types catch invented code.** Misspelled fields, missing tables and wrong types fail at compile time. At runtime, unknown fields are rejected with "Did you mean ...?".
72
+ - **Errors that explain the fix.** Every error has a stable code (`IL1001`), the cause, the fix and a docs link.
73
+ - **Tools for agents.** `inferlo describe --json` returns the whole app map. `inferlo check --json` returns typecheck, load and test results as JSON. `inferlo add resource` generates correct CRUD code.
74
+ - **Docs for machines.** [`llms.txt`](llms.txt), [`llms-full.txt`](llms-full.txt), and every new project ships with `AGENTS.md` and `CLAUDE.md`.
75
+ - **Secure by default.** Routes require a signed-in user unless marked public, every query is parameterised, and every insert is validated, so AI-written code stays safe even with light review.
76
+
77
+ ## Quick start
78
+
79
+ Requires Node.js 22.18+ or Bun 1.3+. TypeScript runs directly with no build step.
80
+
81
+ Every release is tested on Node.js 22.18, 22, 24, 26 and the current release; Bun 1.3.0 and latest; PostgreSQL 14, 15, 16, 17, 18 and latest; and TypeScript 5.9 and latest.
82
+
83
+ ```bash
84
+ npx @browserlessapi/inferlo new my-app
85
+ cd my-app && npm install && npm run dev
86
+ ```
87
+
88
+ Or add it to an existing project:
89
+
90
+ ```bash
91
+ npm install @browserlessapi/inferlo
92
+ ```
93
+
94
+ ## Features
95
+
96
+ - **Routing:** `route.get/post/put/patch/delete`, typed path params, query coercion, CORS, middleware, body limits
97
+ - **Schemas:** `t.string()`, `t.email()`, `t.enum()`, `t.ref()`, ... with `.optional()`, `.default()`, `.pick()`, `.partial()`, JSON Schema output
98
+ - **Database:** typed tables (`db.tickets.findMany(...)`), PostgreSQL and in-memory adapters, `inferlo db push`, versioned migrations (`inferlo db migrate`), transactions, unique and foreign keys
99
+ - **Auth:** `auth.resolve(req)`, `bearer()` API keys, roles, password hashing with Web Crypto
100
+ - **AI:** Anthropic, OpenAI, Gemini, Ollama and any OpenAI-compatible server; aliases with fallback; structured output with automatic repair; streaming to SSE; embeddings; per-user usage and cost tracking; budgets
101
+ - **MCP:** routes with `tool: true` become MCP tools at `/mcp`, with the same auth as HTTP
102
+ - **OpenAPI 3.1** at `/openapi.json`
103
+ - **Testing:** `app.request(path, { json, as: user })`, `mockProvider()`, `memory()`
104
+ - **CLI:** `new`, `dev`, `describe`, `check`, `db push`, `db migrate`, `openapi`, `add resource`
105
+
106
+ ## Documentation
107
+
108
+ - [Getting started](docs/getting-started.md)
109
+ - [Schemas](docs/schema.md)
110
+ - [Models and database](docs/database.md)
111
+ - [Routes](docs/routes.md)
112
+ - [Auth](docs/auth.md)
113
+ - [AI](docs/ai.md)
114
+ - [MCP, OpenAPI and describe](docs/mcp.md)
115
+ - [CLI](docs/cli.md)
116
+ - [Testing](docs/testing.md)
117
+ - [Deployment](docs/deploy.md)
118
+ - [Error codes](docs/errors.md)
119
+ - [Example: helpdesk with AI triage](examples/helpdesk)
120
+
121
+ ## Status
122
+
123
+ Inferlo is at **v0.1**. The core API is usable and tested on Node and Bun with PostgreSQL. See [ROADMAP.md](ROADMAP.md) for what is next and [CHANGELOG.md](CHANGELOG.md) for what changed.
124
+
125
+ ## Contributing
126
+
127
+ See [CONTRIBUTING.md](CONTRIBUTING.md). AI agents working on this repository: read [AGENTS.md](AGENTS.md).
128
+
129
+ ## License
130
+
131
+ [MIT](LICENSE)
@@ -0,0 +1,138 @@
1
+ import { type AnySchema, type Infer } from "../schema.ts";
2
+ import type { RouteDef } from "../route.ts";
3
+ import { type Image, type Message, type Provider, type ProviderUsage, type ToolSpec } from "./providers.ts";
4
+ export * from "./providers.ts";
5
+ export interface Pricing {
6
+ /** USD per million input tokens. */
7
+ input: number;
8
+ /** USD per million output tokens. */
9
+ output: number;
10
+ }
11
+ export interface Usage {
12
+ /** Alias or model id you asked for. */
13
+ requested: string;
14
+ /** "provider:model" that actually answered. */
15
+ model: string;
16
+ inputTokens: number;
17
+ outputTokens: number;
18
+ /** USD, when pricing is configured for the model. Otherwise 0. */
19
+ cost: number;
20
+ /** Who to bill: the userId / tenant you passed as `for`. */
21
+ for?: string;
22
+ kind: "generate" | "stream" | "embed";
23
+ durationMs: number;
24
+ }
25
+ export interface AIConfig {
26
+ /** Named providers: { anthropic: anthropic({...}), local: ollama() }. */
27
+ providers: Record<string, Provider>;
28
+ /** Aliases to "provider:model" ids. A list means fallback order. */
29
+ models?: Record<string, string | string[]>;
30
+ /** Alias or id used when a call does not name a model. */
31
+ defaultModel?: string;
32
+ /** Prices per model id ("anthropic:claude-haiku-4-5") for cost tracking. Inferlo ships no prices because they change. */
33
+ pricing?: Record<string, Pricing>;
34
+ /** Called after every call. Store it, bill it, log it. */
35
+ onUsage?: (usage: Usage) => void | Promise<void>;
36
+ /**
37
+ * Called before a call that has `for` set. Return false (or throw) to block it,
38
+ * e.g. when a user has used up their credits. Blocked calls raise IL5004 (HTTP 402).
39
+ */
40
+ allow?: (info: {
41
+ for: string;
42
+ model: string;
43
+ }) => boolean | Promise<boolean>;
44
+ }
45
+ export interface GenerateOptions<O extends AnySchema | undefined> {
46
+ /** Alias ("fast") or "provider:model". */
47
+ model?: string;
48
+ /** Shorthand for a single user message. */
49
+ prompt?: string;
50
+ /** Images for the prompt message: [{ url: "https://..." }] or [{ base64, mediaType: "image/png" }]. Needs a vision model. */
51
+ images?: Image[];
52
+ system?: string;
53
+ messages?: Message[];
54
+ /** Schema for structured output. The result's `object` is validated and typed. */
55
+ output?: O;
56
+ temperature?: number;
57
+ maxTokens?: number;
58
+ /** Who this call is for (user or tenant id), for usage tracking and budgets. */
59
+ for?: string;
60
+ signal?: AbortSignal;
61
+ /** Extra attempts when structured output does not match the schema (default 1). */
62
+ repairAttempts?: number;
63
+ /**
64
+ * Routes the model may call as tools, with the current user's permissions:
65
+ * ctx.ai.generate({ prompt, tools: [createTicket, getTicket] }). Only inside a route handler.
66
+ */
67
+ tools?: readonly RouteDef[];
68
+ /** Most model calls when tools are given: tool rounds plus the final answer (default 5). More raises IL5010. */
69
+ maxSteps?: number;
70
+ }
71
+ /** One tool call the model made, with what the route returned. */
72
+ export interface ToolCallResult {
73
+ id: string;
74
+ /** Tool name: the route's export name (createTicket). */
75
+ name: string;
76
+ input: unknown;
77
+ /** HTTP status the route answered with. */
78
+ status: number;
79
+ /** Parsed JSON response (or text), exactly what the model saw. */
80
+ output: unknown;
81
+ /** True when the route answered with an error status. */
82
+ isError: boolean;
83
+ }
84
+ /** One model call. */
85
+ export interface Step {
86
+ text: string;
87
+ /** Tools the model called in this step (empty for the final answer). */
88
+ toolCalls: ToolCallResult[];
89
+ usage: ProviderUsage;
90
+ }
91
+ export interface GenerateResult<O> {
92
+ text: string;
93
+ object: O;
94
+ /** Totals over every step. */
95
+ usage: Usage;
96
+ /** Every model call in order, with tool calls and usage per step. */
97
+ steps: Step[];
98
+ }
99
+ /** Runs one tool call for the current request. Set by createApp for ctx.ai. */
100
+ export type ToolRunner = (route: RouteDef, input: unknown) => Promise<{
101
+ name: string;
102
+ status: number;
103
+ output: unknown;
104
+ text: string;
105
+ }>;
106
+ /** ctx.ai for one request: the same AI, with tools that run as the request's user. Internal: used by createApp. */
107
+ export declare function bindTools(ai: AI, runner: ToolRunner, describe: (route: RouteDef) => ToolSpec): AI;
108
+ export interface StreamResult {
109
+ /** Text chunks as they arrive. */
110
+ [Symbol.asyncIterator](): AsyncIterator<string>;
111
+ /** Full text once the stream finishes. */
112
+ text(): Promise<string>;
113
+ /** A server-sent events Response for browsers (events: "text", then "done" with usage). */
114
+ toResponse(init?: ResponseInit): Response;
115
+ /** Usage once the stream finishes. */
116
+ usage(): Promise<Usage>;
117
+ }
118
+ export interface AI {
119
+ generate<O extends AnySchema | undefined = undefined>(options: GenerateOptions<O>): Promise<GenerateResult<O extends AnySchema ? Infer<O> : undefined>>;
120
+ stream(options: Omit<GenerateOptions<undefined>, "output" | "tools" | "maxSteps">): StreamResult;
121
+ embed(options: {
122
+ model?: string;
123
+ input: string | string[];
124
+ for?: string;
125
+ signal?: AbortSignal;
126
+ }): Promise<{
127
+ embeddings: number[][];
128
+ usage: Usage;
129
+ }>;
130
+ /** Configured aliases, for describe(). */
131
+ readonly aliases: Record<string, string[]>;
132
+ readonly configured: boolean;
133
+ }
134
+ /** Pull a JSON value out of model text, tolerating ```json fences and leading prose. */
135
+ export declare function extractJson(text: string): unknown;
136
+ export declare function createAI(config?: AIConfig): AI;
137
+ /** Cosine similarity of two vectors, for small in-memory searches. */
138
+ export declare function cosineSimilarity(a: number[], b: number[]): number;