@browserlessapi/inferlo 0.4.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 (142) hide show
  1. package/README.md +21 -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 +22 -8
  6. package/dist/ai/index.js +107 -15
  7. package/dist/ai/index.js.map +1 -1
  8. package/dist/ai/providers.js +5 -4
  9. package/dist/ai/providers.js.map +1 -1
  10. package/dist/ai/public.d.ts +21 -0
  11. package/dist/ai/public.js +17 -0
  12. package/dist/ai/public.js.map +1 -0
  13. package/dist/ai/tool.d.ts +47 -0
  14. package/dist/ai/tool.js +85 -0
  15. package/dist/ai/tool.js.map +1 -0
  16. package/dist/ai-client.d.ts +3 -0
  17. package/dist/ai-client.js +8 -0
  18. package/dist/ai-client.js.map +1 -0
  19. package/dist/api-version.d.ts +32 -0
  20. package/dist/api-version.js +45 -0
  21. package/dist/api-version.js.map +1 -0
  22. package/dist/app.d.ts +117 -5
  23. package/dist/app.js +667 -156
  24. package/dist/app.js.map +1 -1
  25. package/dist/audit.d.ts +50 -0
  26. package/dist/audit.js +92 -0
  27. package/dist/audit.js.map +1 -0
  28. package/dist/cache.d.ts +92 -0
  29. package/dist/cache.js +245 -0
  30. package/dist/cache.js.map +1 -0
  31. package/dist/cli/main.js +45 -5
  32. package/dist/cli/main.js.map +1 -1
  33. package/dist/config.d.ts +16 -0
  34. package/dist/config.js +101 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/db/table.d.ts +31 -0
  37. package/dist/db/table.js +140 -1
  38. package/dist/db/table.js.map +1 -1
  39. package/dist/deprecation.d.ts +23 -0
  40. package/dist/deprecation.js +70 -0
  41. package/dist/deprecation.js.map +1 -0
  42. package/dist/describe.d.ts +68 -5
  43. package/dist/describe.js +38 -3
  44. package/dist/describe.js.map +1 -1
  45. package/dist/elysia.d.ts +49 -0
  46. package/dist/elysia.js +10 -0
  47. package/dist/elysia.js.map +1 -0
  48. package/dist/env.d.ts +2 -0
  49. package/dist/env.js +5 -4
  50. package/dist/env.js.map +1 -1
  51. package/dist/express.d.ts +38 -0
  52. package/dist/express.js +18 -0
  53. package/dist/express.js.map +1 -0
  54. package/dist/fastify.d.ts +52 -0
  55. package/dist/fastify.js +65 -0
  56. package/dist/fastify.js.map +1 -0
  57. package/dist/h3.d.ts +76 -0
  58. package/dist/h3.js +61 -0
  59. package/dist/h3.js.map +1 -0
  60. package/dist/hapi.d.ts +45 -0
  61. package/dist/hapi.js +57 -0
  62. package/dist/hapi.js.map +1 -0
  63. package/dist/hono.d.ts +26 -0
  64. package/dist/hono.js +10 -0
  65. package/dist/hono.js.map +1 -0
  66. package/dist/index.d.ts +27 -5
  67. package/dist/index.js +9 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/jobs.d.ts +24 -0
  70. package/dist/jobs.js +35 -6
  71. package/dist/jobs.js.map +1 -1
  72. package/dist/koa.d.ts +51 -0
  73. package/dist/koa.js +45 -0
  74. package/dist/koa.js.map +1 -0
  75. package/dist/lambda.d.ts +110 -0
  76. package/dist/lambda.js +169 -0
  77. package/dist/lambda.js.map +1 -0
  78. package/dist/logging.d.ts +23 -0
  79. package/dist/logging.js +87 -0
  80. package/dist/logging.js.map +1 -0
  81. package/dist/mcp.d.ts +1 -7
  82. package/dist/mcp.js +18 -4
  83. package/dist/mcp.js.map +1 -1
  84. package/dist/metrics.d.ts +48 -0
  85. package/dist/metrics.js +184 -0
  86. package/dist/metrics.js.map +1 -0
  87. package/dist/module.d.ts +52 -0
  88. package/dist/module.js +152 -0
  89. package/dist/module.js.map +1 -0
  90. package/dist/nestjs.d.ts +43 -0
  91. package/dist/nestjs.js +47 -0
  92. package/dist/nestjs.js.map +1 -0
  93. package/dist/nextjs.d.ts +35 -0
  94. package/dist/nextjs.js +33 -0
  95. package/dist/nextjs.js.map +1 -0
  96. package/dist/openapi.d.ts +3 -1
  97. package/dist/openapi.js +16 -3
  98. package/dist/openapi.js.map +1 -1
  99. package/dist/permissions.d.ts +26 -0
  100. package/dist/permissions.js +78 -0
  101. package/dist/permissions.js.map +1 -0
  102. package/dist/react-router.d.ts +41 -0
  103. package/dist/react-router.js +42 -0
  104. package/dist/react-router.js.map +1 -0
  105. package/dist/redact.d.ts +19 -0
  106. package/dist/redact.js +150 -0
  107. package/dist/redact.js.map +1 -0
  108. package/dist/request-matcher.d.ts +4 -0
  109. package/dist/request-matcher.js +14 -0
  110. package/dist/request-matcher.js.map +1 -0
  111. package/dist/route.d.ts +54 -1
  112. package/dist/route.js +24 -0
  113. package/dist/route.js.map +1 -1
  114. package/dist/secure-headers.d.ts +8 -0
  115. package/dist/secure-headers.js +61 -0
  116. package/dist/secure-headers.js.map +1 -0
  117. package/dist/serve.d.ts +72 -1
  118. package/dist/serve.js +201 -61
  119. package/dist/serve.js.map +1 -1
  120. package/dist/sessions.d.ts +72 -0
  121. package/dist/sessions.js +171 -0
  122. package/dist/sessions.js.map +1 -0
  123. package/dist/sveltekit.d.ts +72 -0
  124. package/dist/sveltekit.js +68 -0
  125. package/dist/sveltekit.js.map +1 -0
  126. package/dist/tracing.d.ts +118 -0
  127. package/dist/tracing.js +204 -0
  128. package/dist/tracing.js.map +1 -0
  129. package/dist/trpc.d.ts +57 -0
  130. package/dist/trpc.js +142 -0
  131. package/dist/trpc.js.map +1 -0
  132. package/dist/tsed.d.ts +32 -0
  133. package/dist/tsed.js +13 -0
  134. package/dist/tsed.js.map +1 -0
  135. package/llms.txt +15 -5
  136. package/package.json +99 -3
  137. package/templates/app/AGENTS.md +12 -6
  138. package/templates/app/_env.example +2 -0
  139. package/templates/app/src/app.ts +15 -8
  140. package/templates/app/src/config.ts +17 -0
  141. package/templates/app/src/server.ts +9 -9
  142. package/templates/app/src/routes/health.ts +0 -3
package/dist/openapi.js CHANGED
@@ -1,3 +1,5 @@
1
+ /** OpenAPI 3.1 document generated from routes. Served at /openapi.json by default. */
2
+ import { describeDeprecation, resolveDeprecation } from "./deprecation.js";
1
3
  import { toolName } from "./mcp.js";
2
4
  const errorSchema = {
3
5
  type: "object",
@@ -18,7 +20,9 @@ const errorSchema = {
18
20
  };
19
21
  export function buildOpenAPI(app, options) {
20
22
  const paths = {};
21
- 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) {
22
26
  const o = route.options;
23
27
  const openPath = route.path.replace(/:([^/]+)/g, "{$1}").replace(/\/\*$/, "/{wildcard}");
24
28
  const parameters = [];
@@ -38,20 +42,26 @@ export function buildOpenAPI(app, options) {
38
42
  operationId: route.name ?? toolName(route),
39
43
  summary: o.summary,
40
44
  description: o.description,
41
- 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),
42
47
  parameters: parameters.length ? parameters : undefined,
43
48
  requestBody: o.body ? { required: !o.body.def.optional, content: { "application/json": { schema: o.body.toJSONSchema() } } } : undefined,
44
49
  responses: {
45
50
  "200": { description: "Success", content: o.response ? { "application/json": { schema: o.response.toJSONSchema() } } : undefined },
46
51
  ...(o.body || o.query || o.params ? { "422": { description: "Validation failed (IL1001)", content: { "application/json": { schema: errorSchema } } } } : {}),
47
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 } } } } : {}),
48
54
  ...(o.rateLimit
49
55
  ? { "429": { description: `Too many requests (IL2005): ${o.rateLimit.max} per ${o.rateLimit.per} per ${o.rateLimit.by ?? "user"}`, content: { "application/json": { schema: errorSchema } } } }
50
56
  : {}),
51
57
  },
52
58
  security: !isPublic && options.auth ? [{ bearerAuth: [] }] : undefined,
53
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,
54
62
  "x-inferlo-tool": o.tool ? toolName(route) : undefined,
63
+ deprecated: o.deprecated ? true : undefined,
64
+ "x-inferlo-deprecation": typeof o.deprecated === "object" ? describeDeprecation(resolveDeprecation(o.deprecated, `${route.method} ${route.path}`)) : undefined,
55
65
  };
56
66
  for (const key of Object.keys(operation))
57
67
  if (operation[key] === undefined)
@@ -63,7 +73,10 @@ export function buildOpenAPI(app, options) {
63
73
  schemas[m.name] = m.row.toJSONSchema();
64
74
  return {
65
75
  openapi: "3.1.0",
66
- 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
+ : {}),
67
80
  paths,
68
81
  components: {
69
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":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAqB3C,MAAM,GAAG,GAAG,mLAAmL,CAAC;AAEhM,SAAS,OAAO,CAAC,OAAe;IAC9B,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,2HAA2H;AAC3H,MAAM,UAAU,WAAW,CAAqD,KAAY;IAC1F,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,OAAO,CAAC,+CAA+C,CAAC,CAAC;IAChI,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0B,CAAC;IAChD,MAAM,IAAI,GAAkD,EAAE,CAAC;IAC/D,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,OAAO,CAAC,4BAA4B,IAAI,qCAAqC,CAAC,CAAC;QACpI,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;YAC/B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACpB,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAI,IAAmC,EAAE,KAAK,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,EAAE,CAAC;YAC3I,MAAM,OAAO,CAAC,gCAAgC,IAAI,IAAI,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAa,CAAC,CAAC;QACrD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,EAAE,IAAY,EAAE,KAAc,EAAoB,EAAE;QACrE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI;YAAE,MAAM,OAAO,CAAC,uBAAuB,IAAI,eAAe,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC;QAC9G,MAAM,IAAI,GAAI,KAAsD,EAAE,IAAI,CAAC;QAC3E,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxB,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAI,IAA4B,CAAC,KAAK,CAAC;YAClD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,OAAO,OAAO,KAAK,SAAS;YAAE,MAAM,OAAO,CAAC,4BAA4B,IAAI,cAAc,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,OAAO,4BAA4B,CAAC,CAAC;QAChL,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,qBAA8B;QACpC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACzB,GAAG,EAAE,CAAC,IAAY,EAAE,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;QACzD,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,KAAc;YAC1C,IAAI,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC;gBAAE,OAAO;YACrC,IAAI,CAAE,KAA+C,EAAE,IAAI,EAAE,CAAC;gBAC5D,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,mBAAmB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,uEAAuE,EAAE,CAAC,CAAC;YACvJ,CAAC;YACD,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,wBAAwB,IAAI,eAAe,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,8EAA8E,EAAE,CAAC,CAAC;QACtL,CAAC;QACD,GAAG,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;KACvC,CAAuB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAqB,CAAC,IAAI,KAAK,qBAAqB,CAAC;AAC9G,CAAC"}
@@ -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,19 @@
1
+ export declare const REDACTED = "[redacted]";
2
+ /** Internal: remember the secret values an object holds (a config() result or an AI provider). */
3
+ export declare function holdSecrets<T extends object>(owner: T, values: readonly (string | undefined | null)[]): T;
4
+ export declare function secretsOf(owner: unknown): readonly string[];
5
+ export interface Redactor {
6
+ text(value: string): string;
7
+ value(value: unknown): unknown;
8
+ }
9
+ export declare function createRedactor(secretValues: readonly string[]): Redactor;
10
+ export interface LoggerLike {
11
+ info(message: string, data?: unknown, fields?: Record<string, string | number | boolean | undefined>): void;
12
+ error(message: string, data?: unknown, fields?: Record<string, string | number | boolean | undefined>): void;
13
+ }
14
+ /** A logger that redacts every message, data value and field before passing them on. */
15
+ export declare function redactingLogger(logger: LoggerLike, redactor: Redactor): LoggerLike;
16
+ /** The values of createApp({ redact }): strings, or objects that hold secrets (config() results). */
17
+ export declare function redactValues(option: unknown): string[];
18
+ /** Internal: remember a config() result and its secret values, for createApp({ redact: [settings] }). */
19
+ export declare function holdConfigSecrets<T extends object>(settings: T, values: readonly unknown[]): T;
package/dist/redact.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Secret redaction for everything createApp logs (and the error messages exposeErrors shows): credentials in
3
+ * headers, tokens, API keys, passwords in URLs, the app's AI provider keys and the values passed to
4
+ * createApp({ redact }). Internal: createApp wraps its logger with redactingLogger().
5
+ */
6
+ import { InferloError } from "./errors.js";
7
+ export const REDACTED = "[redacted]";
8
+ /** Secret values of objects that hold them (config() results, AI providers), looked up by createApp. */
9
+ const heldSecrets = new WeakMap();
10
+ /** Internal: remember the secret values an object holds (a config() result or an AI provider). */
11
+ export function holdSecrets(owner, values) {
12
+ const list = values.filter((v) => typeof v === "string" && v.length > 0);
13
+ if (list.length)
14
+ heldSecrets.set(owner, [...(heldSecrets.get(owner) ?? []), ...list]);
15
+ return owner;
16
+ }
17
+ export function secretsOf(owner) {
18
+ return owner && typeof owner === "object" ? (heldSecrets.get(owner) ?? []) : [];
19
+ }
20
+ const SENSITIVE_KEY = /^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|api[-_]?key|apikey|password|passwd|secret|client[-_]?secret|token|access[-_]?token|refresh[-_]?token|id[-_]?token|private[-_]?key|session)$/i;
21
+ const PATTERNS = [
22
+ // Header-like "name: value" or "name=value" pairs, also inside JSON text. Cookies run to the end of the value.
23
+ [/\b(cookie|set-cookie)(["']?\s*[:=]\s*["']?)(?!\[redacted\])[^"'\r\n]+/gi, `$1$2${REDACTED}`],
24
+ [
25
+ /\b(authorization|proxy-authorization|x-api-key|api-key)(["']?\s*[:=]\s*["']?)(?:(Bearer|Basic|Token)\s+)?(?!\[redacted\]|(?:Bearer|Basic|Token)\s)[^\s"',;]+/gi,
26
+ (_match, name, separator, scheme) => `${name}${separator}${scheme ? `${scheme} ` : ""}${REDACTED}`,
27
+ ],
28
+ // Bearer <token> and Basic <credentials> anywhere else
29
+ [/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{4,}/g, `$1 ${REDACTED}`],
30
+ // Passwords in connection strings and URLs: postgres://user:password@host
31
+ [/\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]*:)[^\s@/]+@/gi, `$1${REDACTED}@`],
32
+ // Secrets in query strings: ?key=..., &access_token=...
33
+ [/([?&](?:key|api_key|apikey|access_token|token|secret|password|signature|sig)=)[^&\s"'#]+/gi, `$1${REDACTED}`],
34
+ // Well-known API key formats: OpenAI and Anthropic (sk-...), Google (AIza...), GitHub tokens.
35
+ [/\bsk-[A-Za-z0-9_-]{16,}/g, REDACTED],
36
+ [/\bAIza[0-9A-Za-z_-]{30,}/g, REDACTED],
37
+ [/\bgh[pousr]_[A-Za-z0-9]{30,}/g, REDACTED],
38
+ ];
39
+ export function createRedactor(secretValues) {
40
+ // Longest first, so a secret that contains another is replaced whole. Very short values would hide normal text.
41
+ const known = [...new Set(secretValues.filter((v) => v.length >= 6))].sort((a, b) => b.length - a.length);
42
+ const text = (input) => {
43
+ let out = input;
44
+ for (const secret of known)
45
+ if (out.includes(secret))
46
+ out = out.split(secret).join(REDACTED);
47
+ for (const [pattern, replacement] of PATTERNS)
48
+ out = out.replace(pattern, replacement);
49
+ return out;
50
+ };
51
+ const value = (input, depth = 0, seen = new WeakMap()) => {
52
+ if (typeof input === "string")
53
+ return text(input);
54
+ if (!input || typeof input !== "object")
55
+ return input;
56
+ if (seen.has(input))
57
+ return seen.get(input);
58
+ if (depth > 8)
59
+ return input;
60
+ if (input instanceof Error) {
61
+ const copy = Object.create(Object.getPrototypeOf(input));
62
+ seen.set(input, copy);
63
+ Object.defineProperty(copy, "message", { value: text(input.message), writable: true, configurable: true });
64
+ if (input.stack !== undefined)
65
+ Object.defineProperty(copy, "stack", { value: text(input.stack), writable: true, configurable: true });
66
+ if (input.name !== Object.getPrototypeOf(input)?.name)
67
+ Object.defineProperty(copy, "name", { value: input.name, writable: true, configurable: true });
68
+ for (const key of Object.keys(input))
69
+ copy[key] = redactEntry(key, input[key], depth, seen);
70
+ if ("cause" in input)
71
+ Object.defineProperty(copy, "cause", { value: value(input.cause, depth + 1, seen), writable: true, configurable: true });
72
+ return copy;
73
+ }
74
+ if (typeof Headers !== "undefined" && input instanceof Headers) {
75
+ const out = {};
76
+ seen.set(input, out);
77
+ input.forEach((v, k) => (out[k] = redactEntry(k, v, depth, seen)));
78
+ return out;
79
+ }
80
+ if (Array.isArray(input)) {
81
+ const out = [];
82
+ seen.set(input, out);
83
+ for (const item of input)
84
+ out.push(value(item, depth + 1, seen));
85
+ return out;
86
+ }
87
+ const proto = Object.getPrototypeOf(input);
88
+ if (proto !== Object.prototype && proto !== null)
89
+ return input;
90
+ const out = {};
91
+ seen.set(input, out);
92
+ for (const [k, v] of Object.entries(input))
93
+ out[k] = redactEntry(k, v, depth, seen);
94
+ return out;
95
+ };
96
+ const redactEntry = (key, entry, depth, seen) => {
97
+ if (SENSITIVE_KEY.test(key) && entry !== undefined && entry !== null && entry !== "")
98
+ return REDACTED;
99
+ return value(entry, depth + 1, seen);
100
+ };
101
+ return { text, value: (input) => value(input) };
102
+ }
103
+ /** A logger that redacts every message, data value and field before passing them on. */
104
+ export function redactingLogger(logger, redactor) {
105
+ const pass = (level, message, data, fields) => {
106
+ const text = redactor.text(String(message));
107
+ if (fields !== undefined) {
108
+ const clean = {};
109
+ for (const [key, value] of Object.entries(fields))
110
+ clean[key] = typeof value === "string" ? redactor.text(value) : value;
111
+ return logger[level](text, data === undefined ? undefined : redactor.value(data), clean);
112
+ }
113
+ return data === undefined ? logger[level](text) : logger[level](text, redactor.value(data));
114
+ };
115
+ return {
116
+ info: (message, data, fields) => pass("info", message, data, fields),
117
+ error: (message, data, fields) => pass("error", message, data, fields),
118
+ };
119
+ }
120
+ /** The values of createApp({ redact }): strings, or objects that hold secrets (config() results). */
121
+ export function redactValues(option) {
122
+ if (option === undefined)
123
+ return [];
124
+ if (!Array.isArray(option))
125
+ throw invalidRedact(`createApp({ redact }) must be an array, got ${typeof option}.`);
126
+ const out = [];
127
+ option.forEach((item, i) => {
128
+ if (item === undefined || item === null || item === "")
129
+ return; // an unset optional variable
130
+ if (typeof item === "string")
131
+ out.push(item);
132
+ else if (typeof item === "object" && configs.has(item))
133
+ out.push(...secretsOf(item));
134
+ else
135
+ throw invalidRedact(`createApp({ redact }) item ${i} is ${typeof item === "object" ? "an object that is not a config() result" : `a ${typeof item}`}.`);
136
+ });
137
+ return out;
138
+ }
139
+ const configs = new WeakSet();
140
+ /** Internal: remember a config() result and its secret values, for createApp({ redact: [settings] }). */
141
+ export function holdConfigSecrets(settings, values) {
142
+ configs.add(settings);
143
+ return holdSecrets(settings, values.map((v) => (v === undefined || v === null ? undefined : String(v))));
144
+ }
145
+ function invalidRedact(message) {
146
+ return new InferloError("IL6018", message, {
147
+ fix: "Pass the values to hide from logs, or your config() result: createApp({ redact: [settings, process.env.WEBHOOK_SECRET] }).",
148
+ });
149
+ }
150
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
@@ -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"}
package/dist/route.d.ts CHANGED
@@ -8,17 +8,21 @@
8
8
  *
9
9
  * Secure by default: routes require a signed-in user unless you set auth: "public".
10
10
  */
11
+ import type { Deprecation } from "./deprecation.ts";
11
12
  import type { AI } from "./ai/index.ts";
12
13
  import type { Db } from "./db/table.ts";
13
14
  import type { Jobs } from "./jobs.ts";
14
15
  import type { AnyModel } from "./model.ts";
16
+ import type { Audit } from "./audit.ts";
17
+ import type { Cache, RouteCache } from "./cache.ts";
18
+ import type { PermissionInput, Permissions } from "./permissions.ts";
15
19
  import type { RateLimit } from "./rate-limit.ts";
16
20
  import type { AnySchema, Infer, ObjectSchema } from "./schema.ts";
17
21
  /**
18
22
  * Register your app's models and user type once, so every handler is fully typed:
19
23
  *
20
24
  * declare module "@browserlessapi/inferlo" {
21
- * interface Register { models: typeof models; user: User }
25
+ * interface Register { models: typeof models; user: User; permissions: typeof access }
22
26
  * }
23
27
  */
24
28
  export interface Register {
@@ -32,6 +36,15 @@ export type AppUser = Registered<"user", {
32
36
  roles?: string[];
33
37
  }>;
34
38
  export type AppDb = Db<AppModels>;
39
+ /**
40
+ * The permission names a route's `permission` option accepts. With `permissions: typeof access` in Register, only the
41
+ * names whose rule needs nothing but { user } (roles rules and rules of the user alone), so a typo is a type error.
42
+ */
43
+ export type AppPermission = Registered<"permissions", undefined> extends Permissions<infer R> ? {
44
+ [K in keyof R & string]: {
45
+ user: AppUser;
46
+ } extends PermissionInput<R[K]> ? K : never;
47
+ }[keyof R & string] : string;
35
48
  export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
36
49
  /** "public": anyone. "user": any signed-in user. Any other string: the user must have that role. */
37
50
  export type Auth = "public" | "user" | (string & {});
@@ -44,6 +57,12 @@ export interface ToolOptions {
44
57
  export interface RouteOptions<B extends AnySchema | undefined, Q extends ObjectSchema<any> | undefined, P extends ObjectSchema<any> | undefined> {
45
58
  /** Who may call this route. Default "user" (signed in). */
46
59
  auth?: Auth;
60
+ /**
61
+ * A permission from createApp({ permissions }) the signed-in user must have, checked with { user } before the
62
+ * handler runs (also when the route is called as an AI tool). Denied: 403 IL3004. For rules that also need a
63
+ * resource, call access.authorize(name, { user, ticket }) in the handler instead. Typed from Register's permissions.
64
+ */
65
+ permission?: AppPermission;
47
66
  /** Request body schema (JSON or form). */
48
67
  body?: B;
49
68
  /** Query string schema. Strings are converted to numbers and booleans automatically. */
@@ -60,6 +79,22 @@ export interface RouteOptions<B extends AnySchema | undefined, Q extends ObjectS
60
79
  tool?: boolean | ToolOptions;
61
80
  /** Limit requests per user or IP: { max: 10, per: "minute", by: "user" }. More raise IL2005 (HTTP 429). */
62
81
  rateLimit?: RateLimit;
82
+ /**
83
+ * Longest time in milliseconds this route may take (reading the body and running the handler) before it
84
+ * answers 503 IL2007 and ctx.signal is aborted. Overrides createApp({ requestTimeoutMs }); false turns it off.
85
+ */
86
+ timeoutMs?: number | false;
87
+ /**
88
+ * Mark the route deprecated: true, or { since, sunset, link } to also send the Deprecation, Sunset and
89
+ * Link rel="deprecation" headers on every response. Shown in OpenAPI, describe and MCP tool descriptions.
90
+ */
91
+ deprecated?: boolean | Deprecation;
92
+ /**
93
+ * Cache this GET route's JSON responses in the app cache: { ttlMs: 60_000, by: "user", tags: ["tickets"] }.
94
+ * "user" (default) keeps one response per signed-in user, "public" one for every caller. Remove them early with
95
+ * ctx.cache.invalidate(tag). Responses carry an ETag, so clients revalidate with If-None-Match (304).
96
+ */
97
+ cache?: RouteCache;
63
98
  }
64
99
  type PathParams<P extends string> = P extends `${string}:${infer Param}/${infer Rest}` ? {
65
100
  [K in Param | keyof PathParams<`/${Rest}`>]: string;
@@ -77,6 +112,8 @@ export interface Context<Body, Query, Params, User> {
77
112
  ai: AI;
78
113
  /** Queue background jobs: await jobs.enqueue(sendEmail, { to }). */
79
114
  jobs: Jobs;
115
+ /** The app cache: await cache.getOrSet(key, { ttlMs: 60_000, tags: ["tickets"] }, compute), cache.invalidate("tickets"). */
116
+ cache: Cache;
80
117
  req: Request;
81
118
  url: URL;
82
119
  headers: Headers;
@@ -86,8 +123,20 @@ export interface Context<Body, Query, Params, User> {
86
123
  header(name: string, value: string): void;
87
124
  /** Values shared by middleware for this request. */
88
125
  state: Record<string, unknown>;
126
+ /**
127
+ * Aborted when the request times out (requestTimeoutMs or the route's timeoutMs) or the client disconnects.
128
+ * ctx.ai calls use it automatically; pass it to fetch and other slow work: fetch(url, { signal }).
129
+ */
130
+ signal: AbortSignal;
131
+ /** This request's id: sent back in the X-Request-Id header and added to the request's log entries. */
132
+ requestId: string;
89
133
  /** Run work after the response is sent (logging, webhooks). Errors are logged, not thrown. */
90
134
  waitUntil(promise: Promise<unknown>): void;
135
+ /**
136
+ * Write an audit log entry with the signed-in user and this request's id (needs AuditLog in createApp({ models })):
137
+ * await audit("tickets.update", { model: "tickets", before: ticket, after: updated }).
138
+ */
139
+ audit: Audit;
91
140
  }
92
141
  export type AnyContext = Context<any, any, any, any>;
93
142
  export type Handler<C, R> = (ctx: C) => R | Promise<R>;
@@ -99,6 +148,8 @@ export interface RouteDef {
99
148
  readonly handler: Handler<AnyContext, unknown>;
100
149
  /** Export name, filled in by createApp when routes are passed as modules. */
101
150
  name?: string;
151
+ /** API version ("v1") when the route comes from apiVersion("v1", routes). */
152
+ readonly version?: string;
102
153
  }
103
154
  export declare class RouteBuilder<Path extends string, B extends AnySchema | undefined, Q extends ObjectSchema<any> | undefined, P extends ObjectSchema<any> | undefined, A> {
104
155
  readonly method: Method;
@@ -128,4 +179,6 @@ export declare const route: {
128
179
  }) => RouteBuilder<Path, B, Q, P, A>;
129
180
  };
130
181
  export declare function isRoute(value: unknown): value is RouteDef;
182
+ /** The routes in route sources: routes, arrays and modules (import * as tickets), named after their exports. */
183
+ export declare function collectRoutes(sources: unknown): RouteDef[];
131
184
  export {};