@browserlessapi/inferlo 0.5.0 → 0.6.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 (83) hide show
  1. package/README.md +8 -2
  2. package/dist/ai/index.d.ts +7 -0
  3. package/dist/ai/index.js +68 -2
  4. package/dist/ai/index.js.map +1 -1
  5. package/dist/ai/public.d.ts +1 -0
  6. package/dist/ai/public.js.map +1 -1
  7. package/dist/api-version.d.ts +32 -0
  8. package/dist/api-version.js +45 -0
  9. package/dist/api-version.js.map +1 -0
  10. package/dist/app.d.ts +30 -3
  11. package/dist/app.js +210 -43
  12. package/dist/app.js.map +1 -1
  13. package/dist/audit.d.ts +50 -0
  14. package/dist/audit.js +92 -0
  15. package/dist/audit.js.map +1 -0
  16. package/dist/cache.d.ts +92 -0
  17. package/dist/cache.js +245 -0
  18. package/dist/cache.js.map +1 -0
  19. package/dist/cli/main.js +25 -4
  20. package/dist/cli/main.js.map +1 -1
  21. package/dist/describe.d.ts +42 -1
  22. package/dist/describe.js +25 -2
  23. package/dist/describe.js.map +1 -1
  24. package/dist/fastify.d.ts +10 -2
  25. package/dist/fastify.js +22 -4
  26. package/dist/fastify.js.map +1 -1
  27. package/dist/h3.d.ts +76 -0
  28. package/dist/h3.js +61 -0
  29. package/dist/h3.js.map +1 -0
  30. package/dist/hapi.d.ts +45 -0
  31. package/dist/hapi.js +57 -0
  32. package/dist/hapi.js.map +1 -0
  33. package/dist/index.d.ts +14 -1
  34. package/dist/index.js +5 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/jobs.d.ts +20 -0
  37. package/dist/jobs.js +19 -7
  38. package/dist/jobs.js.map +1 -1
  39. package/dist/koa.d.ts +51 -0
  40. package/dist/koa.js +45 -0
  41. package/dist/koa.js.map +1 -0
  42. package/dist/lambda.d.ts +110 -0
  43. package/dist/lambda.js +169 -0
  44. package/dist/lambda.js.map +1 -0
  45. package/dist/mcp.d.ts +1 -1
  46. package/dist/mcp.js +7 -3
  47. package/dist/mcp.js.map +1 -1
  48. package/dist/module.d.ts +52 -0
  49. package/dist/module.js +152 -0
  50. package/dist/module.js.map +1 -0
  51. package/dist/nextjs.d.ts +35 -0
  52. package/dist/nextjs.js +33 -0
  53. package/dist/nextjs.js.map +1 -0
  54. package/dist/openapi.d.ts +3 -0
  55. package/dist/openapi.js +12 -3
  56. package/dist/openapi.js.map +1 -1
  57. package/dist/permissions.d.ts +26 -0
  58. package/dist/permissions.js +78 -0
  59. package/dist/permissions.js.map +1 -0
  60. package/dist/react-router.d.ts +41 -0
  61. package/dist/react-router.js +42 -0
  62. package/dist/react-router.js.map +1 -0
  63. package/dist/request-matcher.d.ts +4 -0
  64. package/dist/request-matcher.js +14 -0
  65. package/dist/request-matcher.js.map +1 -0
  66. package/dist/route.d.ts +36 -1
  67. package/dist/route.js +24 -0
  68. package/dist/route.js.map +1 -1
  69. package/dist/serve.d.ts +20 -1
  70. package/dist/serve.js +48 -4
  71. package/dist/serve.js.map +1 -1
  72. package/dist/sessions.d.ts +72 -0
  73. package/dist/sessions.js +171 -0
  74. package/dist/sessions.js.map +1 -0
  75. package/dist/sveltekit.d.ts +72 -0
  76. package/dist/sveltekit.js +68 -0
  77. package/dist/sveltekit.js.map +1 -0
  78. package/dist/tracing.d.ts +118 -0
  79. package/dist/tracing.js +204 -0
  80. package/dist/tracing.js.map +1 -0
  81. package/llms.txt +7 -3
  82. package/package.json +44 -2
  83. package/templates/app/AGENTS.md +5 -3
package/dist/module.js ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Feature modules split a large app into parts with explicit dependencies. A module is a plain value:
3
+ * its models, routes and jobs, the modules it depends on, and what it offers other modules.
4
+ *
5
+ * export const billing = module("billing", {
6
+ * imports: [users], // modules this one uses
7
+ * models: [invoices],
8
+ * routes: [invoiceRoutes], // import * as invoiceRoutes from "./routes.ts"
9
+ * jobs: [sendInvoice],
10
+ * provides: { createInvoice }, // what other modules may use: billing.provides.createInvoice
11
+ * });
12
+ *
13
+ * createApp({ modules: [users, billing] })
14
+ *
15
+ * No decorators, no container, no injection: other modules import the module value and call
16
+ * billing.provides.createInvoice(...) directly. createApp checks that every imported module is
17
+ * registered and that models only reference models of their own module, an imported module or the
18
+ * app itself, and describe() shows each module with what it contains.
19
+ */
20
+ import { InferloError, didYouMean } from "./errors.js";
21
+ const MODULE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
22
+ const OPTION_KEYS = new Set(["description", "imports", "models", "routes", "jobs", "provides"]);
23
+ function invalid(message, fix) {
24
+ return new InferloError("IL6022", message, { fix });
25
+ }
26
+ function isList(value) {
27
+ return Array.isArray(value);
28
+ }
29
+ /**
30
+ * Define a feature module: module("billing", { imports, models, routes, jobs, provides }).
31
+ * Register it with createApp({ modules: [billing] }).
32
+ */
33
+ export function module(name, options = {}) {
34
+ if (typeof name !== "string" || !MODULE_NAME.test(name)) {
35
+ throw invalid(`Module name ${JSON.stringify(name)} is not valid.`, 'Use lowercase letters, digits and dashes, starting with a letter: module("billing", { ... }).');
36
+ }
37
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
38
+ throw invalid(`module("${name}") options must be an object.`, `Use module("${name}", { models, routes, jobs, imports, provides }).`);
39
+ }
40
+ for (const key of Object.keys(options)) {
41
+ if (!OPTION_KEYS.has(key)) {
42
+ throw invalid(`module("${name}") has an unknown option "${key}".${didYouMean(key, OPTION_KEYS)}`, "A module takes description, imports, models, routes, jobs and provides.");
43
+ }
44
+ }
45
+ for (const key of ["imports", "models", "routes", "jobs"]) {
46
+ if (options[key] !== undefined && !isList(options[key])) {
47
+ throw invalid(`module("${name}") ${key} must be an array.`, `Use module("${name}", { ${key}: [...] }).`);
48
+ }
49
+ }
50
+ if (options.description !== undefined && typeof options.description !== "string") {
51
+ throw invalid(`module("${name}") description must be a string.`, "Describe the module in one sentence.");
52
+ }
53
+ const imports = options.imports ?? [];
54
+ for (const [i, dep] of imports.entries()) {
55
+ if (!isModule(dep)) {
56
+ throw invalid(`module("${name}") imports[${i}] is not a module.`, "List module values created with module(), for example imports: [users].");
57
+ }
58
+ if (dep.name === name)
59
+ throw invalid(`module("${name}") imports itself.`, `Remove "${name}" from its own imports.`);
60
+ }
61
+ if (new Set(imports.map((m) => m.name)).size !== imports.length) {
62
+ throw invalid(`module("${name}") imports the same module twice.`, "List each module once in imports.");
63
+ }
64
+ for (const [i, m] of (options.models ?? []).entries()) {
65
+ if (typeof m !== "object" || m === null || typeof m.name !== "string" || typeof m.fields !== "object") {
66
+ throw invalid(`module("${name}") models[${i}] is not a model.`, 'List models created with model("table", { ... }).');
67
+ }
68
+ }
69
+ const provides = options.provides ?? {};
70
+ if (typeof provides !== "object" || provides === null || Array.isArray(provides)) {
71
+ throw invalid(`module("${name}") provides must be an object.`, `Use provides: { createInvoice } and call ${name}.provides.createInvoice(...) from other modules.`);
72
+ }
73
+ return Object.freeze({
74
+ kind: "inferlo.module",
75
+ name,
76
+ ...(options.description !== undefined ? { description: options.description } : {}),
77
+ imports: Object.freeze([...imports]),
78
+ models: Object.freeze([...(options.models ?? [])]),
79
+ routes: Object.freeze([...(options.routes ?? [])]),
80
+ jobs: Object.freeze([...(options.jobs ?? [])]),
81
+ provides: Object.freeze({ ...provides }),
82
+ });
83
+ }
84
+ export function isModule(value) {
85
+ return typeof value === "object" && value !== null && value.kind === "inferlo.module";
86
+ }
87
+ function wiring(message, fix) {
88
+ return new InferloError("IL6023", message, { fix });
89
+ }
90
+ /**
91
+ * Checks createApp({ modules }) and returns the modules with each model's owner. Every imported
92
+ * module must be registered, names are unique, and a model may only reference models of its own
93
+ * module, of a module it imports, or of the app itself (createApp({ models })).
94
+ */
95
+ export function resolveModules(option, appModels) {
96
+ if (option === undefined)
97
+ return { modules: [], modelOwner: new Map() };
98
+ if (!isList(option))
99
+ throw wiring("createApp({ modules }) must be an array of modules.", "Use createApp({ modules: [users, billing] }).");
100
+ const byName = new Map();
101
+ for (const [i, m] of option.entries()) {
102
+ if (!isModule(m))
103
+ throw wiring(`createApp({ modules }) item ${i} is not a module.`, 'Create modules with module("name", { ... }) and pass the values: modules: [users, billing].');
104
+ const existing = byName.get(m.name);
105
+ if (existing === m)
106
+ throw wiring(`The module "${m.name}" is listed twice in createApp({ modules }).`, "List each module once.");
107
+ if (existing)
108
+ throw wiring(`Two modules are named "${m.name}".`, "Module names must be unique.");
109
+ byName.set(m.name, m);
110
+ }
111
+ for (const m of byName.values()) {
112
+ for (const dep of m.imports) {
113
+ const registered = byName.get(dep.name);
114
+ if (!registered) {
115
+ throw wiring(`The module "${m.name}" imports "${dep.name}", which is not in createApp({ modules }).`, `Add ${dep.name} to createApp({ modules: [...] }).`);
116
+ }
117
+ if (registered !== dep)
118
+ throw wiring(`The module "${m.name}" imports a different module also named "${dep.name}".`, "Import the same module value that is passed to createApp({ modules }).");
119
+ }
120
+ }
121
+ const modelOwner = new Map();
122
+ for (const m of byName.values()) {
123
+ for (const model of m.models) {
124
+ const owner = modelOwner.get(model.name);
125
+ if (owner !== undefined) {
126
+ throw wiring(`The model "${model.name}" is in the modules "${owner}" and "${m.name}".`, "A model belongs to one module; other modules import that module to use it.");
127
+ }
128
+ modelOwner.set(model.name, m.name);
129
+ }
130
+ }
131
+ for (const model of appModels) {
132
+ const owner = modelOwner.get(model.name);
133
+ if (owner !== undefined)
134
+ throw wiring(`The model "${model.name}" is in createApp({ models }) and in the module "${owner}".`, "List the model in one place only.");
135
+ }
136
+ for (const m of byName.values()) {
137
+ const allowed = new Set([m.name, ...m.imports.map((d) => d.name)]);
138
+ for (const model of m.models) {
139
+ for (const [field, schema] of Object.entries(model.fields)) {
140
+ const ref = schema.def.refTable;
141
+ const owner = ref ? modelOwner.get(ref) : undefined;
142
+ if (owner !== undefined && !allowed.has(owner)) {
143
+ throw new InferloError("IL6024", `${model.name}.${field} in the module "${m.name}" references "${ref}" from the module "${owner}", which "${m.name}" does not import.`, {
144
+ fix: `Add ${owner} to the imports of the "${m.name}" module: module("${m.name}", { imports: [${owner}], ... }).`,
145
+ });
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return { modules: [...byName.values()], modelOwner };
151
+ }
152
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Inferlo inside Next.js App Router route handlers (Next.js 15 and 16), without a runtime dependency on Next.js:
3
+ *
4
+ * // app/api/[[...path]]/route.ts
5
+ * import { toNextHandlers } from "@browserlessapi/inferlo/nextjs";
6
+ * import { app } from "@/inferlo/app";
7
+ *
8
+ * export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = toNextHandlers(app, { prefix: "/api" });
9
+ *
10
+ * Every request under /api runs through the Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
11
+ * with /api removed from the path. In server actions and other server code, use @browserlessapi/inferlo/ai.
12
+ * It never loads the router, database or jobs code.
13
+ */
14
+ import type { App } from "./app.ts";
15
+ /** A Next.js route handler: it takes the Request (a NextRequest in Next.js) and returns a Response. */
16
+ export type NextRouteHandler = (request: Request, context?: unknown) => Promise<Response>;
17
+ /** One route handler per HTTP method, to export from an app/.../route.ts file. */
18
+ export interface NextHandlers {
19
+ GET: NextRouteHandler;
20
+ POST: NextRouteHandler;
21
+ PUT: NextRouteHandler;
22
+ PATCH: NextRouteHandler;
23
+ DELETE: NextRouteHandler;
24
+ HEAD: NextRouteHandler;
25
+ OPTIONS: NextRouteHandler;
26
+ }
27
+ export interface NextHandlersOptions {
28
+ /**
29
+ * The URL path of the route.ts folder, removed before the app routes the request: "/api" for
30
+ * app/api/[[...path]]/route.ts. Requests outside it keep their path. Include Next.js's basePath if one is set.
31
+ */
32
+ prefix?: string;
33
+ }
34
+ /** Route handlers that run a whole Inferlo app from a Next.js catch-all route: export const { GET, POST } = toNextHandlers(app). */
35
+ export declare function toNextHandlers(app: Pick<App<any>, "fetch">, options?: NextHandlersOptions): NextHandlers;
package/dist/nextjs.js ADDED
@@ -0,0 +1,33 @@
1
+ import { InferloError } from "./errors.js";
2
+ const fix = 'Pass the app from createApp and a path prefix: export const { GET, POST } = toNextHandlers(app, { prefix: "/api" }).';
3
+ /** Route handlers that run a whole Inferlo app from a Next.js catch-all route: export const { GET, POST } = toNextHandlers(app). */
4
+ export function toNextHandlers(app, options = {}) {
5
+ if (!app || typeof app.fetch !== "function") {
6
+ throw new InferloError("IL5020", "toNextHandlers() needs an Inferlo app (the value returned by createApp).", { fix });
7
+ }
8
+ if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
9
+ throw new InferloError("IL5020", "toNextHandlers() prefix must be a path that starts with /, such as \"/api\".", { fix });
10
+ }
11
+ const prefix = (options.prefix ?? "").replace(/\/+$/, "").replace(/\/{2,}/g, "/");
12
+ const handle = async (request) => {
13
+ if (!prefix)
14
+ return app.fetch(request);
15
+ const url = new URL(request.url);
16
+ if (url.pathname !== prefix && !url.pathname.startsWith(prefix + "/"))
17
+ return app.fetch(request);
18
+ url.pathname = url.pathname.slice(prefix.length) || "/";
19
+ const hasBody = request.body !== null && request.method !== "GET" && request.method !== "HEAD";
20
+ const init = {
21
+ method: request.method,
22
+ headers: request.headers,
23
+ body: hasBody ? request.body : undefined,
24
+ signal: request.signal,
25
+ redirect: request.redirect,
26
+ };
27
+ if (hasBody)
28
+ init.duplex = "half";
29
+ return app.fetch(new Request(url, init));
30
+ };
31
+ return Object.freeze({ GET: handle, POST: handle, PUT: handle, PATCH: handle, DELETE: handle, HEAD: handle, OPTIONS: handle });
32
+ }
33
+ //# sourceMappingURL=nextjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../src/nextjs.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwB3C,MAAM,GAAG,GAAG,sHAAsH,CAAC;AAEnI,oIAAoI;AACpI,MAAM,UAAU,cAAc,CAAC,GAA4B,EAAE,UAA+B,EAAE;IAC5F,IAAI,CAAC,GAAG,IAAI,OAAQ,GAA2B,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,0EAA0E,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACxH,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACjK,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,8EAA8E,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5H,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAElF,MAAM,MAAM,GAAqB,KAAK,EAAE,OAAO,EAAE,EAAE;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;QACxD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC;QAC/F,MAAM,IAAI,GAAsC;YAC9C,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACxC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC;QACF,IAAI,OAAO;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAClC,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AACjI,CAAC"}
package/dist/openapi.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import type { App } from "./app.ts";
2
+ import type { ModuleInfo } from "./module.ts";
2
3
  export declare function buildOpenAPI(app: App<any>, options: {
3
4
  description?: string;
4
5
  auth: boolean;
6
+ modules?: ModuleInfo;
7
+ version?: string;
5
8
  }): Record<string, unknown>;
package/dist/openapi.js CHANGED
@@ -20,7 +20,9 @@ const errorSchema = {
20
20
  };
21
21
  export function buildOpenAPI(app, options) {
22
22
  const paths = {};
23
- for (const route of app.routes) {
23
+ // A version's document has its routes and the routes outside every version.
24
+ const routes = options.version === undefined ? app.routes : app.routes.filter((r) => r.version === undefined || r.version === options.version);
25
+ for (const route of routes) {
24
26
  const o = route.options;
25
27
  const openPath = route.path.replace(/:([^/]+)/g, "{$1}").replace(/\/\*$/, "/{wildcard}");
26
28
  const parameters = [];
@@ -40,19 +42,23 @@ export function buildOpenAPI(app, options) {
40
42
  operationId: route.name ?? toolName(route),
41
43
  summary: o.summary,
42
44
  description: o.description,
43
- tags: o.tags,
45
+ // Routes of a feature module are tagged with its name unless they set their own tags.
46
+ tags: o.tags ?? (options.modules?.routeModule.has(route) ? [options.modules.routeModule.get(route)] : undefined),
44
47
  parameters: parameters.length ? parameters : undefined,
45
48
  requestBody: o.body ? { required: !o.body.def.optional, content: { "application/json": { schema: o.body.toJSONSchema() } } } : undefined,
46
49
  responses: {
47
50
  "200": { description: "Success", content: o.response ? { "application/json": { schema: o.response.toJSONSchema() } } : undefined },
48
51
  ...(o.body || o.query || o.params ? { "422": { description: "Validation failed (IL1001)", content: { "application/json": { schema: errorSchema } } } } : {}),
49
52
  ...(!isPublic ? { "401": { description: "Sign in required (IL3001)", content: { "application/json": { schema: errorSchema } } } } : {}),
53
+ ...(o.permission ? { "403": { description: `Needs the "${o.permission}" permission (IL3004)`, content: { "application/json": { schema: errorSchema } } } } : {}),
50
54
  ...(o.rateLimit
51
55
  ? { "429": { description: `Too many requests (IL2005): ${o.rateLimit.max} per ${o.rateLimit.per} per ${o.rateLimit.by ?? "user"}`, content: { "application/json": { schema: errorSchema } } } }
52
56
  : {}),
53
57
  },
54
58
  security: !isPublic && options.auth ? [{ bearerAuth: [] }] : undefined,
55
59
  "x-inferlo-auth": o.auth ?? "user",
60
+ "x-inferlo-permission": o.permission,
61
+ "x-inferlo-cache": o.cache ? { ttlMs: o.cache.ttlMs, by: o.cache.by ?? "user", tags: [...(o.cache.tags ?? [])] } : undefined,
56
62
  "x-inferlo-tool": o.tool ? toolName(route) : undefined,
57
63
  deprecated: o.deprecated ? true : undefined,
58
64
  "x-inferlo-deprecation": typeof o.deprecated === "object" ? describeDeprecation(resolveDeprecation(o.deprecated, `${route.method} ${route.path}`)) : undefined,
@@ -67,7 +73,10 @@ export function buildOpenAPI(app, options) {
67
73
  schemas[m.name] = m.row.toJSONSchema();
68
74
  return {
69
75
  openapi: "3.1.0",
70
- info: { title: app.name, version: app.version, description: options.description },
76
+ info: { title: app.name, version: app.version, description: options.description, ...(options.version ? { "x-inferlo-api-version": options.version } : {}) },
77
+ ...(options.modules?.modules.length
78
+ ? { tags: options.modules.modules.map((m) => (m.description ? { name: m.name, description: m.description } : { name: m.name })) }
79
+ : {}),
71
80
  paths,
72
81
  components: {
73
82
  schemas: { ...schemas, Error: errorSchema },
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { AppUser } from "./route.ts";
2
+ /** A rule: a function of the user (and the resources it needs) that returns true to allow, or the roles that may. */
3
+ export type PermissionRule = ((input: any) => boolean | Promise<boolean>) | {
4
+ readonly roles: readonly string[];
5
+ };
6
+ /** What can() and authorize() take for a rule: the function's input, or { user } for a roles rule. */
7
+ export type PermissionInput<R> = R extends (input: infer I) => unknown ? I : {
8
+ user: AppUser | null | undefined;
9
+ };
10
+ export interface Permissions<Rules extends Record<string, PermissionRule> = Record<string, PermissionRule>> {
11
+ readonly kind: "inferlo.permissions";
12
+ /** Every permission with its roles, for roles rules (shown in describe). */
13
+ readonly list: readonly {
14
+ readonly name: string;
15
+ readonly roles?: readonly string[];
16
+ }[];
17
+ /** True when the rule allows it. Never throws for a denial; a missing user is always denied. */
18
+ can<K extends keyof Rules & string>(name: K, input: PermissionInput<Rules[K]>): Promise<boolean>;
19
+ /** Resolves when the rule allows it; throws IL3001 (401) without a user and IL3004 (403) otherwise. */
20
+ authorize<K extends keyof Rules & string>(name: K, input: PermissionInput<Rules[K]>): Promise<void>;
21
+ /** True when the permission exists. */
22
+ has(name: string): boolean;
23
+ }
24
+ /** Define the app's permissions by name. Pass the result to createApp({ permissions }) to use route permission options. */
25
+ export declare function permissions<const Rules extends Record<string, PermissionRule>>(rules: Rules): Permissions<Rules>;
26
+ export declare function isPermissions(value: unknown): value is Permissions;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Named permissions, checked the same way in routes, AI tools and jobs:
3
+ *
4
+ * export const access = permissions({
5
+ * "reports.view": { roles: ["admin", "analyst"] },
6
+ * "tickets.update": ({ user, ticket }: { user: AppUser; ticket: Row<typeof Ticket> }) =>
7
+ * ticket.ownerId === user.id || (user.roles ?? []).includes("admin"),
8
+ * });
9
+ *
10
+ * await access.can("tickets.update", { user, ticket }) // true or false
11
+ * await access.authorize("tickets.update", { user, ticket }) // throws IL3004 (403) when not allowed
12
+ * route.get("/reports", { permission: "reports.view" }) // checked before the handler runs
13
+ *
14
+ * Deny by default: a missing user, an unknown permission or a rule that does not return true never allows.
15
+ */
16
+ import { InferloError } from "./errors.js";
17
+ const fix = 'Define each permission as a function that returns true or false, or as { roles: ["admin"] }: permissions({ "tickets.update": ({ user, ticket }) => ticket.ownerId === user.id }).';
18
+ function invalid(message) {
19
+ return new InferloError("IL6026", message, { fix });
20
+ }
21
+ /** Define the app's permissions by name. Pass the result to createApp({ permissions }) to use route permission options. */
22
+ export function permissions(rules) {
23
+ if (!rules || typeof rules !== "object" || Array.isArray(rules))
24
+ throw invalid("permissions() takes an object of named rules.");
25
+ const table = new Map();
26
+ const list = [];
27
+ for (const [name, rule] of Object.entries(rules)) {
28
+ if (!/^[A-Za-z][\w-]*(\.[\w-]+)*$/.test(name))
29
+ throw invalid(`Invalid permission name "${name}". Use names like "tickets.update".`);
30
+ if (typeof rule === "function") {
31
+ table.set(name, rule);
32
+ list.push({ name });
33
+ continue;
34
+ }
35
+ const roles = rule?.roles;
36
+ const keys = rule && typeof rule === "object" ? Object.keys(rule) : [];
37
+ if (!Array.isArray(roles) || roles.length === 0 || roles.some((r) => typeof r !== "string" || r === "") || keys.some((k) => k !== "roles")) {
38
+ throw invalid(`Invalid rule for permission "${name}".`);
39
+ }
40
+ const frozen = Object.freeze([...roles]);
41
+ table.set(name, { roles: frozen });
42
+ list.push(Object.freeze({ name, roles: frozen }));
43
+ }
44
+ const check = async (name, input) => {
45
+ const rule = table.get(name);
46
+ if (!rule)
47
+ throw invalid(`Unknown permission "${name}". Defined: ${[...table.keys()].join(", ") || "none"}.`);
48
+ const user = input?.user;
49
+ if (!user)
50
+ return false;
51
+ if (typeof rule !== "function") {
52
+ const roles = user.roles;
53
+ return Array.isArray(roles) && rule.roles.some((role) => roles.includes(role));
54
+ }
55
+ const allowed = await rule(input);
56
+ if (typeof allowed !== "boolean")
57
+ throw invalid(`The rule for permission "${name}" returned ${allowed === undefined ? "undefined" : typeof allowed} instead of true or false.`);
58
+ return allowed;
59
+ };
60
+ return Object.freeze({
61
+ kind: "inferlo.permissions",
62
+ list: Object.freeze(list),
63
+ can: (name, input) => check(name, input),
64
+ async authorize(name, input) {
65
+ if (await check(name, input))
66
+ return;
67
+ if (!input?.user) {
68
+ throw new InferloError("IL3001", "Sign in required.", { status: 401, fix: "Send valid credentials, e.g. an Authorization: Bearer <token> header." });
69
+ }
70
+ throw new InferloError("IL3004", `You do not have the "${name}" permission.`, { status: 403, fix: "Sign in as a user the permission allows, or ask an administrator for access." });
71
+ },
72
+ has: (name) => table.has(name),
73
+ });
74
+ }
75
+ export function isPermissions(value) {
76
+ return typeof value === "object" && value !== null && value.kind === "inferlo.permissions";
77
+ }
78
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Inferlo inside React Router 7 and 8 (framework mode, formerly Remix) resource routes, without a runtime
3
+ * dependency on React Router:
4
+ *
5
+ * // app/routes.ts: route("api/*", "routes/api.ts")
6
+ * // app/routes/api.ts
7
+ * import { toReactRouterHandlers } from "@browserlessapi/inferlo/react-router";
8
+ * import { app } from "~/inferlo/app.server";
9
+ *
10
+ * export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" });
11
+ *
12
+ * GET and HEAD requests reach the loader, POST, PUT, PATCH and DELETE the action; both run the whole Inferlo app.
13
+ * In other loaders and actions, use @browserlessapi/inferlo/ai. It never loads the router, database or jobs code.
14
+ */
15
+ import type { App } from "./app.ts";
16
+ /** The parts of React Router's loader and action arguments Inferlo uses. */
17
+ export interface ReactRouterArgsLike {
18
+ /** The incoming Web request. */
19
+ request: Request;
20
+ }
21
+ /** A React Router loader or action that answers with the Inferlo app's Response. */
22
+ export type ReactRouterHandler = (args: ReactRouterArgsLike) => Promise<Response>;
23
+ /** The loader and action of a resource route: export const { loader, action } = toReactRouterHandlers(app). */
24
+ export interface ReactRouterHandlers {
25
+ /** GET and HEAD requests. */
26
+ loader: ReactRouterHandler;
27
+ /** POST, PUT, PATCH and DELETE requests. */
28
+ action: ReactRouterHandler;
29
+ }
30
+ export interface ReactRouterHandlersOptions {
31
+ /**
32
+ * The URL path of the splat route, removed before the app routes the request: "/api" for route("api/*", ...).
33
+ * Requests outside it keep their path. Include the app's basename if one is set.
34
+ */
35
+ prefix?: string;
36
+ }
37
+ /**
38
+ * A loader and an action that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
39
+ * from a React Router resource route.
40
+ */
41
+ export declare function toReactRouterHandlers(app: Pick<App<any>, "fetch">, options?: ReactRouterHandlersOptions): ReactRouterHandlers;
@@ -0,0 +1,42 @@
1
+ import { InferloError } from "./errors.js";
2
+ import { normalizePrefix, stripPrefix } from "./serve.js";
3
+ const fix = 'Pass the app from createApp and the route\'s path: export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" }).';
4
+ /**
5
+ * A loader and an action that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
6
+ * from a React Router resource route.
7
+ */
8
+ export function toReactRouterHandlers(app, options = {}) {
9
+ if (!app || typeof app.fetch !== "function") {
10
+ throw new InferloError("IL5023", "toReactRouterHandlers() needs an Inferlo app (the value returned by createApp).", { fix });
11
+ }
12
+ if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
13
+ throw new InferloError("IL5023", 'toReactRouterHandlers() prefix must be a path that starts with /, such as "/api".', { fix });
14
+ }
15
+ const prefix = normalizePrefix(options.prefix);
16
+ const handle = async (args) => {
17
+ const incoming = args?.request;
18
+ if (!(incoming instanceof Request)) {
19
+ throw new InferloError("IL5023", "toReactRouterHandlers()'s loader and action were called without a request.", {
20
+ fix: 'Export them from a resource route module so React Router calls them: export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" }).',
21
+ });
22
+ }
23
+ const url = new URL(incoming.url);
24
+ const stripped = prefix ? stripPrefix(url.pathname, prefix) : null;
25
+ if (stripped === null)
26
+ return app.fetch(incoming);
27
+ url.pathname = stripped;
28
+ const hasBody = incoming.body !== null && incoming.method !== "GET" && incoming.method !== "HEAD";
29
+ const init = {
30
+ method: incoming.method,
31
+ headers: incoming.headers,
32
+ body: hasBody ? incoming.body : undefined,
33
+ signal: incoming.signal,
34
+ redirect: incoming.redirect,
35
+ };
36
+ if (hasBody)
37
+ init.duplex = "half";
38
+ return app.fetch(new Request(url, init));
39
+ };
40
+ return Object.freeze({ loader: handle, action: handle });
41
+ }
42
+ //# sourceMappingURL=react-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-router.js","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA2B1D,MAAM,GAAG,GAAG,sIAAsI,CAAC;AAEnJ;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAA4B,EAAE,UAAsC,EAAE;IAC1G,IAAI,CAAC,GAAG,IAAI,OAAQ,GAA2B,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,iFAAiF,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/H,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACjK,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,mFAAmF,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACjI,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAuB,KAAK,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,QAAQ,GAAI,IAAwC,EAAE,OAAO,CAAC;QACpE,IAAI,CAAC,CAAC,QAAQ,YAAY,OAAO,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,4EAA4E,EAAE;gBAC7G,GAAG,EAAE,wJAAwJ;aAC9J,CAAC,CAAC;QACL,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAQ,GAA+B,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/E,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC;QAClG,MAAM,IAAI,GAAsC;YAC9C,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACzC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B,CAAC;QACF,IAAI,OAAO;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAClC,OAAQ,GAA+B,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Called by createApp with a function that knows the app's routes and built-in endpoints. */
2
+ export declare function registerRequestMatcher(app: object, matcher: (method: string, pathname: string) => boolean): void;
3
+ /** True when the app has a route or built-in endpoint for this method and path; undefined for objects that are not apps from createApp. */
4
+ export declare function appAnswers(app: object, method: string, pathname: string): boolean | undefined;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Which requests an app answers itself, for toNodeHandler(app, { fallthrough: true }). Kept apart from
3
+ * app.ts so the Node handler (and the framework entry points that use it) load no router code.
4
+ */
5
+ const matchers = new WeakMap();
6
+ /** Called by createApp with a function that knows the app's routes and built-in endpoints. */
7
+ export function registerRequestMatcher(app, matcher) {
8
+ matchers.set(app, matcher);
9
+ }
10
+ /** True when the app has a route or built-in endpoint for this method and path; undefined for objects that are not apps from createApp. */
11
+ export function appAnswers(app, method, pathname) {
12
+ return matchers.get(app)?.(method, pathname);
13
+ }
14
+ //# sourceMappingURL=request-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-matcher.js","sourceRoot":"","sources":["../src/request-matcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAyD,CAAC;AAEtF,8FAA8F;AAC9F,MAAM,UAAU,sBAAsB,CAAC,GAAW,EAAE,OAAsD;IACxG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,2IAA2I;AAC3I,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAc,EAAE,QAAgB;IACtE,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC"}