@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.
- package/README.md +8 -2
- package/dist/ai/index.d.ts +7 -0
- package/dist/ai/index.js +68 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/public.d.ts +1 -0
- package/dist/ai/public.js.map +1 -1
- package/dist/api-version.d.ts +32 -0
- package/dist/api-version.js +45 -0
- package/dist/api-version.js.map +1 -0
- package/dist/app.d.ts +30 -3
- package/dist/app.js +210 -43
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +50 -0
- package/dist/audit.js +92 -0
- package/dist/audit.js.map +1 -0
- package/dist/cache.d.ts +92 -0
- package/dist/cache.js +245 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli/main.js +25 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/describe.d.ts +42 -1
- package/dist/describe.js +25 -2
- package/dist/describe.js.map +1 -1
- package/dist/fastify.d.ts +10 -2
- package/dist/fastify.js +22 -4
- package/dist/fastify.js.map +1 -1
- package/dist/h3.d.ts +76 -0
- package/dist/h3.js +61 -0
- package/dist/h3.js.map +1 -0
- package/dist/hapi.d.ts +45 -0
- package/dist/hapi.js +57 -0
- package/dist/hapi.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +20 -0
- package/dist/jobs.js +19 -7
- package/dist/jobs.js.map +1 -1
- package/dist/koa.d.ts +51 -0
- package/dist/koa.js +45 -0
- package/dist/koa.js.map +1 -0
- package/dist/lambda.d.ts +110 -0
- package/dist/lambda.js +169 -0
- package/dist/lambda.js.map +1 -0
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +7 -3
- package/dist/mcp.js.map +1 -1
- package/dist/module.d.ts +52 -0
- package/dist/module.js +152 -0
- package/dist/module.js.map +1 -0
- package/dist/nextjs.d.ts +35 -0
- package/dist/nextjs.js +33 -0
- package/dist/nextjs.js.map +1 -0
- package/dist/openapi.d.ts +3 -0
- package/dist/openapi.js +12 -3
- package/dist/openapi.js.map +1 -1
- package/dist/permissions.d.ts +26 -0
- package/dist/permissions.js +78 -0
- package/dist/permissions.js.map +1 -0
- package/dist/react-router.d.ts +41 -0
- package/dist/react-router.js +42 -0
- package/dist/react-router.js.map +1 -0
- package/dist/request-matcher.d.ts +4 -0
- package/dist/request-matcher.js +14 -0
- package/dist/request-matcher.js.map +1 -0
- package/dist/route.d.ts +36 -1
- package/dist/route.js +24 -0
- package/dist/route.js.map +1 -1
- package/dist/serve.d.ts +20 -1
- package/dist/serve.js +48 -4
- package/dist/serve.js.map +1 -1
- package/dist/sessions.d.ts +72 -0
- package/dist/sessions.js +171 -0
- package/dist/sessions.js.map +1 -0
- package/dist/sveltekit.d.ts +72 -0
- package/dist/sveltekit.js +68 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/tracing.d.ts +118 -0
- package/dist/tracing.js +204 -0
- package/dist/tracing.js.map +1 -0
- package/llms.txt +7 -3
- package/package.json +44 -2
- package/templates/app/AGENTS.md +5 -3
package/dist/fastify.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* const fastify = Fastify();
|
|
9
9
|
* await fastify.register(inferloPlugin, { ai }); // fastify.ai and request.ai
|
|
10
10
|
* await fastify.register(inferloPlugin, { app: inferloApp, prefix: "/api" }); // or a whole Inferlo app under /api
|
|
11
|
+
* await fastify.register(inferloPlugin, { app: inferloApp, fallthrough: true }); // or only the app's routes, at their own URLs
|
|
11
12
|
*
|
|
12
13
|
* Importing this entry point adds `ai` to Fastify's instance and request types. It never loads the router,
|
|
13
14
|
* database or jobs code.
|
|
@@ -32,13 +33,20 @@ export interface InferloPluginOptions {
|
|
|
32
33
|
fetch(req: Request, ...rest: unknown[]): Promise<Response>;
|
|
33
34
|
ai?: AI;
|
|
34
35
|
};
|
|
35
|
-
/** Path the app is served under, for example "/api". Required with app. */
|
|
36
|
+
/** Path the app is served under, for example "/api". Required with app, unless fallthrough is true. */
|
|
36
37
|
prefix?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Answer only the requests the app has a route for (method and path) and its own endpoints (OpenAPI, MCP,
|
|
40
|
+
* health, metrics, CORS preflights), and leave every other request to Fastify's routes and 404 handler.
|
|
41
|
+
* With it, routes move from Fastify to Inferlo one at a time at the same URLs. Default false.
|
|
42
|
+
*/
|
|
43
|
+
fallthrough?: boolean;
|
|
37
44
|
}
|
|
38
45
|
/** A Fastify plugin, registered with fastify.register(inferloPlugin, options). */
|
|
39
46
|
export type InferloPlugin = FastifyPluginAsync<InferloPluginOptions>;
|
|
40
47
|
/**
|
|
41
48
|
* Fastify plugin. { ai } decorates fastify.ai and request.ai everywhere in the Fastify app; { app, prefix }
|
|
42
|
-
* serves a whole Inferlo app (routes, auth, validation, OpenAPI, MCP) under prefix
|
|
49
|
+
* serves a whole Inferlo app (routes, auth, validation, OpenAPI, MCP) under prefix; { app, fallthrough: true }
|
|
50
|
+
* answers only the app's routes and leaves every other request to Fastify. They can be combined.
|
|
43
51
|
*/
|
|
44
52
|
export declare const inferloPlugin: InferloPlugin;
|
package/dist/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertAIClient } from "./ai-client.js";
|
|
2
2
|
import { InferloError } from "./errors.js";
|
|
3
|
-
import { toNodeHandler } from "./serve.js";
|
|
3
|
+
import { answersNodeRequest, toNodeHandler } from "./serve.js";
|
|
4
4
|
const bad = (message, fix) => new InferloError("IL5015", message, { fix });
|
|
5
5
|
async function plugin(fastify, options) {
|
|
6
6
|
const { app, prefix } = options ?? {};
|
|
@@ -18,8 +18,25 @@ async function plugin(fastify, options) {
|
|
|
18
18
|
}
|
|
19
19
|
if (!app)
|
|
20
20
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const { fallthrough } = options;
|
|
22
|
+
if (fallthrough !== undefined && typeof fallthrough !== "boolean") {
|
|
23
|
+
throw bad("inferloPlugin's fallthrough must be true or false.", "Use fastify.register(inferloPlugin, { app, fallthrough: true }).");
|
|
24
|
+
}
|
|
25
|
+
const validPrefix = typeof prefix === "string" && /^\/[^*?#]*[^/*?#]$/.test(prefix);
|
|
26
|
+
if (fallthrough ? prefix !== undefined && !validPrefix : !validPrefix) {
|
|
27
|
+
throw bad(`inferloPlugin needs a prefix such as "/api" to serve an app, got ${JSON.stringify(prefix)}.`, 'Use fastify.register(inferloPlugin, { app, prefix: "/api" }), or { app, fallthrough: true } to serve the app\'s routes at their own URLs.');
|
|
28
|
+
}
|
|
29
|
+
if (fallthrough) {
|
|
30
|
+
const handler = toNodeHandler(app, { prefix, fallthrough: true }); // also checks that app comes from createApp
|
|
31
|
+
// onRequest runs before Fastify reads the body, for Fastify's routes and its 404 handler alike, so the app
|
|
32
|
+
// can take over the requests it answers and Fastify keeps everything else, also other methods on a path.
|
|
33
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
34
|
+
if (!answersNodeRequest(app, request.raw, prefix))
|
|
35
|
+
return;
|
|
36
|
+
reply.hijack();
|
|
37
|
+
await handler(request.raw, reply.raw);
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
23
40
|
}
|
|
24
41
|
const handler = toNodeHandler(app, { prefix });
|
|
25
42
|
// In a child context, so the catch-all body parser applies to the app's routes only: Inferlo reads the
|
|
@@ -37,7 +54,8 @@ async function plugin(fastify, options) {
|
|
|
37
54
|
}
|
|
38
55
|
/**
|
|
39
56
|
* Fastify plugin. { ai } decorates fastify.ai and request.ai everywhere in the Fastify app; { app, prefix }
|
|
40
|
-
* serves a whole Inferlo app (routes, auth, validation, OpenAPI, MCP) under prefix
|
|
57
|
+
* serves a whole Inferlo app (routes, auth, validation, OpenAPI, MCP) under prefix; { app, fallthrough: true }
|
|
58
|
+
* answers only the app's routes and leaves every other request to Fastify. They can be combined.
|
|
41
59
|
*/
|
|
42
60
|
export const inferloPlugin = Object.assign(plugin, {
|
|
43
61
|
// Like fastify-plugin: the decorators are visible to the whole Fastify app, not only inside this plugin.
|
package/dist/fastify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"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"}
|
package/dist/h3.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inferlo inside h3 (1 and 2), Nitro and Nuxt server routes, without a runtime dependency on h3:
|
|
3
|
+
*
|
|
4
|
+
* // server/routes/api/[...path].ts (Nitro or Nuxt)
|
|
5
|
+
* import { toH3Handler } from "@browserlessapi/inferlo/h3";
|
|
6
|
+
* import { app } from "../../inferlo/app";
|
|
7
|
+
*
|
|
8
|
+
* export default toH3Handler(app, { prefix: "/api" });
|
|
9
|
+
*
|
|
10
|
+
* // server/plugins/ai.ts: event.context.ai in every server route
|
|
11
|
+
* import { inferloAI } from "@browserlessapi/inferlo/h3";
|
|
12
|
+
* export default defineNitroPlugin(inferloAI(ai));
|
|
13
|
+
*
|
|
14
|
+
* It never loads the router, database or jobs code.
|
|
15
|
+
*/
|
|
16
|
+
import type { H3EventContext } from "h3";
|
|
17
|
+
import type { AI } from "./ai/index.ts";
|
|
18
|
+
import type { App } from "./app.ts";
|
|
19
|
+
declare module "h3" {
|
|
20
|
+
interface H3EventContext {
|
|
21
|
+
/** The AI client from the inferloAI() Nitro plugin of @browserlessapi/inferlo/h3. */
|
|
22
|
+
ai: AI;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** The parts of an h3 1 or h3 2 event Inferlo uses. */
|
|
26
|
+
export interface H3EventLike {
|
|
27
|
+
/** h3 2: the Web request (and event.url, the parsed URL). */
|
|
28
|
+
req?: {
|
|
29
|
+
url: string;
|
|
30
|
+
method: string;
|
|
31
|
+
headers: HeadersInit;
|
|
32
|
+
body?: ReadableStream<Uint8Array> | null;
|
|
33
|
+
signal?: AbortSignal;
|
|
34
|
+
ip?: string;
|
|
35
|
+
};
|
|
36
|
+
url?: URL;
|
|
37
|
+
/** h3 1: the Node request and response, and event.path (after an app.use() prefix). h3 2 has node on Node.js too. */
|
|
38
|
+
node?: {
|
|
39
|
+
req: unknown;
|
|
40
|
+
res: {
|
|
41
|
+
on(event: "close", listener: () => void): unknown;
|
|
42
|
+
writableFinished?: boolean;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
path?: string;
|
|
46
|
+
/** event.context, where the inferloAI() plugin puts ai. */
|
|
47
|
+
context: H3EventContext | Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
/** An h3 event handler: export it from a Nitro server route, or pass it to app.use() or router.add(). */
|
|
50
|
+
export type H3Handler = ((event: H3EventLike) => Promise<Response>) & {
|
|
51
|
+
readonly __is_handler__: true;
|
|
52
|
+
};
|
|
53
|
+
export interface H3HandlerOptions {
|
|
54
|
+
/**
|
|
55
|
+
* The path the handler is served under, removed before the app routes the request: "/api" for
|
|
56
|
+
* server/routes/api/[...path].ts. Requests outside it keep their path. Leave it out when h3 already removed the
|
|
57
|
+
* path (h3 1 app.use("/api", handler)).
|
|
58
|
+
*/
|
|
59
|
+
prefix?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A Nitro plugin: export default defineNitroPlugin(inferloAI(ai)). */
|
|
62
|
+
export type NitroPlugin = (nitroApp: {
|
|
63
|
+
hooks: {
|
|
64
|
+
hook(name: "request", fn: (event: H3EventLike) => void): unknown;
|
|
65
|
+
};
|
|
66
|
+
}) => void;
|
|
67
|
+
/**
|
|
68
|
+
* An h3 event handler that runs a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP), for
|
|
69
|
+
* Nitro and Nuxt server routes and h3 apps, on h3 1 (Nuxt 3 and 4, Nitro 2) and h3 2 (Nitro 3).
|
|
70
|
+
*/
|
|
71
|
+
export declare function toH3Handler(app: Pick<App<any>, "fetch">, options?: H3HandlerOptions): H3Handler;
|
|
72
|
+
/**
|
|
73
|
+
* A Nitro plugin that puts an AI client (createAI(...) or an Inferlo app's app.ai) on every request as
|
|
74
|
+
* event.context.ai: export default defineNitroPlugin(inferloAI(ai)) in server/plugins/ai.ts.
|
|
75
|
+
*/
|
|
76
|
+
export declare function inferloAI(ai: AI): NitroPlugin;
|
package/dist/h3.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { assertAIClient } from "./ai-client.js";
|
|
2
|
+
import { clientIps } from "./client-ip.js";
|
|
3
|
+
import { InferloError } from "./errors.js";
|
|
4
|
+
import { buildRequest, normalizePrefix, stripPrefix } from "./serve.js";
|
|
5
|
+
const fix = 'Pass the app from createApp and the route\'s path: export default toH3Handler(app, { prefix: "/api" }).';
|
|
6
|
+
/**
|
|
7
|
+
* An h3 event handler that runs a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP), for
|
|
8
|
+
* Nitro and Nuxt server routes and h3 apps, on h3 1 (Nuxt 3 and 4, Nitro 2) and h3 2 (Nitro 3).
|
|
9
|
+
*/
|
|
10
|
+
export function toH3Handler(app, options = {}) {
|
|
11
|
+
if (!app || typeof app.fetch !== "function") {
|
|
12
|
+
throw new InferloError("IL5021", "toH3Handler() needs an Inferlo app (the value returned by createApp).", { fix });
|
|
13
|
+
}
|
|
14
|
+
if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
|
|
15
|
+
throw new InferloError("IL5021", 'toH3Handler() prefix must be a path that starts with /, such as "/api".', { fix });
|
|
16
|
+
}
|
|
17
|
+
const prefix = normalizePrefix(options.prefix);
|
|
18
|
+
const handler = async (event) => {
|
|
19
|
+
const e = event;
|
|
20
|
+
// h3 2: the event carries a Web request.
|
|
21
|
+
if (e?.req && e.url instanceof URL) {
|
|
22
|
+
const url = new URL(e.url.href);
|
|
23
|
+
url.pathname = stripPrefix(url.pathname, prefix) ?? url.pathname;
|
|
24
|
+
const method = e.req.method.toUpperCase();
|
|
25
|
+
const hasBody = e.req.body != null && method !== "GET" && method !== "HEAD";
|
|
26
|
+
const init = { method, headers: e.req.headers, body: hasBody ? e.req.body : undefined, signal: e.req.signal };
|
|
27
|
+
if (hasBody)
|
|
28
|
+
init.duplex = "half";
|
|
29
|
+
const request = new Request(url, init);
|
|
30
|
+
if (typeof e.req.ip === "string" && e.req.ip)
|
|
31
|
+
clientIps.set(request, e.req.ip);
|
|
32
|
+
return app.fetch(request);
|
|
33
|
+
}
|
|
34
|
+
// h3 1: the event carries Node's request and response.
|
|
35
|
+
if (e?.node?.req) {
|
|
36
|
+
const raw = typeof e.path === "string" ? e.path : (e.node.req.url ?? "/");
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
e.node.res.on("close", () => {
|
|
39
|
+
if (!e.node.res.writableFinished)
|
|
40
|
+
controller.abort();
|
|
41
|
+
});
|
|
42
|
+
return app.fetch(buildRequest(e.node.req, stripPrefix(raw, prefix) ?? raw, controller.signal));
|
|
43
|
+
}
|
|
44
|
+
throw new InferloError("IL5021", "toH3Handler()'s handler was called with something that is not an h3 event.", { fix: "Export it from a Nitro server route or pass it to an h3 app: app.use(\"/api/**\", toH3Handler(inferlo, { prefix: \"/api\" }))." });
|
|
45
|
+
};
|
|
46
|
+
// Marks it as an event handler for h3 1 (what defineEventHandler does); h3 2 takes plain functions.
|
|
47
|
+
return Object.assign(handler, { __is_handler__: true });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A Nitro plugin that puts an AI client (createAI(...) or an Inferlo app's app.ai) on every request as
|
|
51
|
+
* event.context.ai: export default defineNitroPlugin(inferloAI(ai)) in server/plugins/ai.ts.
|
|
52
|
+
*/
|
|
53
|
+
export function inferloAI(ai) {
|
|
54
|
+
assertAIClient(ai, 'Pass createAI({ providers, models }) from "@browserlessapi/inferlo/ai", or an Inferlo app\'s app.ai: export default defineNitroPlugin(inferloAI(ai)).');
|
|
55
|
+
return (nitroApp) => {
|
|
56
|
+
nitroApp.hooks.hook("request", (event) => {
|
|
57
|
+
event.context.ai = ai;
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=h3.js.map
|
package/dist/h3.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h3.js","sourceRoot":"","sources":["../src/h3.ts"],"names":[],"mappings":"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"}
|
package/dist/hapi.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inferlo inside hapi (21 and later), without a runtime dependency on hapi:
|
|
3
|
+
*
|
|
4
|
+
* import Hapi from "@hapi/hapi";
|
|
5
|
+
* import { createAI, anthropic } from "@browserlessapi/inferlo/ai";
|
|
6
|
+
* import { inferloPlugin } from "@browserlessapi/inferlo/hapi";
|
|
7
|
+
*
|
|
8
|
+
* const server = Hapi.server({ port: 3000 });
|
|
9
|
+
* await server.register({ plugin: inferloPlugin, options: { ai } }); // server.ai and request.ai
|
|
10
|
+
* await server.register({ plugin: inferloPlugin, options: { app: inferloApp }, routes: { prefix: "/api" } }); // a whole Inferlo app under /api
|
|
11
|
+
*
|
|
12
|
+
* Importing this entry point adds `ai` to hapi's server and request types. It never loads the router,
|
|
13
|
+
* database or jobs code.
|
|
14
|
+
*/
|
|
15
|
+
import type { Plugin } from "@hapi/hapi";
|
|
16
|
+
import type { AI } from "./ai/index.ts";
|
|
17
|
+
declare module "@hapi/hapi" {
|
|
18
|
+
interface Server {
|
|
19
|
+
/** The AI client registered with inferloPlugin from @browserlessapi/inferlo/hapi. */
|
|
20
|
+
ai: AI;
|
|
21
|
+
}
|
|
22
|
+
interface Request {
|
|
23
|
+
/** The AI client registered with inferloPlugin from @browserlessapi/inferlo/hapi. */
|
|
24
|
+
ai: AI;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export interface InferloPluginOptions {
|
|
28
|
+
/** AI client for server.ai and request.ai: createAI(...) or an Inferlo app's app.ai. Default: app.ai, unless server.ai is already set. */
|
|
29
|
+
ai?: AI;
|
|
30
|
+
/**
|
|
31
|
+
* A whole Inferlo app (routes, auth, validation, OpenAPI, MCP) to serve. It is served under the prefix you
|
|
32
|
+
* register the plugin with (routes: { prefix: "/api" }), or, without one, for every path hapi has no route for.
|
|
33
|
+
*/
|
|
34
|
+
app?: {
|
|
35
|
+
fetch(req: Request, ...rest: unknown[]): Promise<Response>;
|
|
36
|
+
ai?: AI;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A hapi plugin, registered with server.register({ plugin: inferloPlugin, options }). */
|
|
40
|
+
export type InferloPlugin = Plugin<InferloPluginOptions>;
|
|
41
|
+
/**
|
|
42
|
+
* hapi plugin. options: { ai } decorates server.ai and request.ai; options: { app } serves a whole Inferlo app
|
|
43
|
+
* (routes, auth, validation, OpenAPI, MCP) under the prefix the plugin is registered with (routes: { prefix }).
|
|
44
|
+
*/
|
|
45
|
+
export declare const inferloPlugin: InferloPlugin;
|
package/dist/hapi.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { assertAIClient } from "./ai-client.js";
|
|
2
|
+
import { InferloError } from "./errors.js";
|
|
3
|
+
import { toNodeHandler } from "./serve.js";
|
|
4
|
+
const bad = (message, fix) => new InferloError("IL5025", message, { fix });
|
|
5
|
+
async function register(server, options) {
|
|
6
|
+
if (options === null || typeof options !== "object") {
|
|
7
|
+
throw bad("inferloPlugin options must be an object with ai, app or both.", "Use server.register({ plugin: inferloPlugin, options: { ai } }).");
|
|
8
|
+
}
|
|
9
|
+
if ("prefix" in options) {
|
|
10
|
+
throw bad("inferloPlugin takes the path prefix from hapi, not from its options.", 'Use server.register({ plugin: inferloPlugin, options: { app }, routes: { prefix: "/api" } }).');
|
|
11
|
+
}
|
|
12
|
+
const { app } = options;
|
|
13
|
+
if (app !== undefined && (typeof app !== "object" || app === null || typeof app.fetch !== "function")) {
|
|
14
|
+
throw bad("inferloPlugin's app must be an Inferlo app (createApp(...)).", 'Use server.register({ plugin: inferloPlugin, options: { app: inferloApp }, routes: { prefix: "/api" } }).');
|
|
15
|
+
}
|
|
16
|
+
if (!app && !options.ai) {
|
|
17
|
+
throw bad("inferloPlugin needs ai, app or both.", 'Use options: { ai } for server.ai and request.ai, or options: { app } with routes: { prefix: "/api" } to serve an Inferlo app.');
|
|
18
|
+
}
|
|
19
|
+
const decorated = server.decorations.server.includes("ai");
|
|
20
|
+
// app.ai is the default, unless an earlier registration with { ai } already set server.ai.
|
|
21
|
+
const ai = options.ai ?? (decorated ? undefined : app?.ai);
|
|
22
|
+
if (ai !== undefined) {
|
|
23
|
+
assertAIClient(ai, 'Pass createAI({ providers, models }) from "@browserlessapi/inferlo/ai", or an Inferlo app\'s app.ai: server.register({ plugin: inferloPlugin, options: { ai } }).');
|
|
24
|
+
if (decorated) {
|
|
25
|
+
throw bad("server.ai is already set by an earlier inferloPlugin registration.", "Register inferloPlugin with { ai } once, and with { app } (without ai) for each app you serve.");
|
|
26
|
+
}
|
|
27
|
+
// A value, not a method (older hapi types only describe method decorations).
|
|
28
|
+
server.decorate("server", "ai", ai);
|
|
29
|
+
// A getter, so every request reads the one client.
|
|
30
|
+
server.decorate("request", "ai", () => ai, { apply: true });
|
|
31
|
+
}
|
|
32
|
+
if (!app)
|
|
33
|
+
return;
|
|
34
|
+
const prefix = server.realm.modifiers.route.prefix;
|
|
35
|
+
const handler = toNodeHandler(app, { prefix });
|
|
36
|
+
// Inferlo reads the body stream itself (JSON, forms, its own size limits) and writes the answer, streams
|
|
37
|
+
// included, to the Node response; hapi's own routes stay more specific than this catch-all.
|
|
38
|
+
server.route({
|
|
39
|
+
method: "*",
|
|
40
|
+
path: "/{path*}",
|
|
41
|
+
options: { payload: { output: "stream", parse: false }, description: "Inferlo app" },
|
|
42
|
+
handler: async (request, h) => {
|
|
43
|
+
await handler(request.raw.req, request.raw.res);
|
|
44
|
+
return h.abandon;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* hapi plugin. options: { ai } decorates server.ai and request.ai; options: { app } serves a whole Inferlo app
|
|
50
|
+
* (routes, auth, validation, OpenAPI, MCP) under the prefix the plugin is registered with (routes: { prefix }).
|
|
51
|
+
*/
|
|
52
|
+
export const inferloPlugin = {
|
|
53
|
+
name: "@browserlessapi/inferlo",
|
|
54
|
+
multiple: true,
|
|
55
|
+
register,
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=hapi.js.map
|
package/dist/hapi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hapi.js","sourceRoot":"","sources":["../src/hapi.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,12 @@ export type { MetricsConfig } from "./metrics.ts";
|
|
|
8
8
|
export type { Deprecation } from "./deprecation.ts";
|
|
9
9
|
export type { App, AppConfig, AuthConfig, CorsConfig, Middleware, MiddlewareContext, RequestOptions, RouteSource, Logger, HealthConfig, HealthCheck, CheckResult, ReadinessReport, CloseOptions } from "./app.ts";
|
|
10
10
|
export type { SecureHeadersConfig } from "./secure-headers.ts";
|
|
11
|
+
export { module, isModule } from "./module.ts";
|
|
12
|
+
export type { Module, ModuleOptions, AnyModule, ModuleModels } from "./module.ts";
|
|
13
|
+
export { apiVersion } from "./api-version.ts";
|
|
14
|
+
export type { ApiVersionOptions } from "./api-version.ts";
|
|
11
15
|
export { route, isRoute } from "./route.ts";
|
|
12
|
-
export type { Register, Context, RouteDef, RouteOptions, Auth, Method, ToolOptions, AppUser, AppDb } from "./route.ts";
|
|
16
|
+
export type { Register, Context, RouteDef, RouteOptions, Auth, Method, ToolOptions, AppUser, AppDb, AppPermission } from "./route.ts";
|
|
13
17
|
export { t, Schema, ObjectSchema, ArraySchema, RefSchema, ValidationError, toJSONSchema } from "./schema.ts";
|
|
14
18
|
export type { Infer, InferInput, Issue, JSONSchema, ParseResult, Shape, AnySchema } from "./schema.ts";
|
|
15
19
|
export { model, Model, columnName } from "./model.ts";
|
|
@@ -28,8 +32,17 @@ export type { RateLimit, RateLimitConfig } from "./rate-limit.ts";
|
|
|
28
32
|
export { job, isJob, retryDelayMs } from "./jobs.ts";
|
|
29
33
|
export { cron, isCron } from "./cron.ts";
|
|
30
34
|
export type { CronDef } from "./cron.ts";
|
|
35
|
+
export type { JobTraceContext } from "./tracing.ts";
|
|
31
36
|
export type { JobDef, JobOptions, JobContext, JobInput, JobRecord, JobStatus, JobSource, JobQueueConfig, Jobs, AppJobs, EnqueueOptions, RunResult, Worker } from "./jobs.ts";
|
|
37
|
+
export { AuditLog, auditChanges } from "./audit.ts";
|
|
38
|
+
export type { Audit, AuditEntry, AuditChange } from "./audit.ts";
|
|
39
|
+
export type { Cache, CacheConfig, CacheSetOptions, RouteCache } from "./cache.ts";
|
|
40
|
+
export { permissions, isPermissions } from "./permissions.ts";
|
|
41
|
+
export type { Permissions, PermissionRule, PermissionInput } from "./permissions.ts";
|
|
32
42
|
export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.ts";
|
|
43
|
+
export { sessions } from "./sessions.ts";
|
|
44
|
+
export type { TracingConfig, OtelApi, OtelSpan } from "./tracing.ts";
|
|
45
|
+
export type { Sessions, SessionsOptions, SessionData, SessionCookieOptions, SessionResponseContext } from "./sessions.ts";
|
|
33
46
|
export { env, setEnv } from "./env.ts";
|
|
34
47
|
export { config } from "./config.ts";
|
|
35
48
|
export type { Config, ConfigOptions } from "./config.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* https://github.com/BrowserlessAPI/Inferlo
|
|
4
4
|
*/
|
|
5
5
|
export { createApp } from "./app.js";
|
|
6
|
+
export { module, isModule } from "./module.js";
|
|
7
|
+
export { apiVersion } from "./api-version.js";
|
|
6
8
|
export { route, isRoute } from "./route.js";
|
|
7
9
|
export { t, Schema, ObjectSchema, ArraySchema, RefSchema, ValidationError, toJSONSchema } from "./schema.js";
|
|
8
10
|
export { model, Model, columnName } from "./model.js";
|
|
@@ -13,7 +15,10 @@ export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anth
|
|
|
13
15
|
export { tool, isTool, ToolBuilder } from "./ai/tool.js";
|
|
14
16
|
export { job, isJob, retryDelayMs } from "./jobs.js";
|
|
15
17
|
export { cron, isCron } from "./cron.js";
|
|
18
|
+
export { AuditLog, auditChanges } from "./audit.js";
|
|
19
|
+
export { permissions, isPermissions } from "./permissions.js";
|
|
16
20
|
export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.js";
|
|
21
|
+
export { sessions } from "./sessions.js";
|
|
17
22
|
export { env, setEnv } from "./env.js";
|
|
18
23
|
export { config } from "./config.js";
|
|
19
24
|
export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAOrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAWjE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1J,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAwBzD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAOrC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAWjE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1J,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAwBzD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAIzC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACpG,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/jobs.d.ts
CHANGED
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
* jobs in this process, app.jobs.runDue() runs them once (tests).
|
|
12
12
|
*/
|
|
13
13
|
import type { AI } from "./ai/index.ts";
|
|
14
|
+
import type { Audit } from "./audit.ts";
|
|
15
|
+
import type { Cache } from "./cache.ts";
|
|
14
16
|
import { type CronDef } from "./cron.ts";
|
|
15
17
|
import type { Adapter } from "./db/types.ts";
|
|
16
18
|
import type { AppDb } from "./route.ts";
|
|
17
19
|
import { type AnySchema, type Infer, type InferInput } from "./schema.ts";
|
|
20
|
+
import type { JobTraceContext, JobTracing } from "./tracing.ts";
|
|
18
21
|
export interface JobOptions<I extends AnySchema> {
|
|
19
22
|
/** Schema of the job's input. Checked when the job is enqueued and again when it runs. */
|
|
20
23
|
input: I;
|
|
@@ -30,10 +33,17 @@ export interface JobContext<Input> {
|
|
|
30
33
|
ai: AI;
|
|
31
34
|
/** Enqueue follow-up jobs. */
|
|
32
35
|
jobs: Jobs;
|
|
36
|
+
/** The app cache, the same as ctx.cache in routes and app.cache. */
|
|
37
|
+
cache: Cache;
|
|
33
38
|
/** Job id. */
|
|
34
39
|
id: string;
|
|
35
40
|
/** 1 on the first run, 2 on the first retry, and so on. */
|
|
36
41
|
attempt: number;
|
|
42
|
+
/**
|
|
43
|
+
* Write an audit log entry (needs AuditLog in createApp({ models })). Jobs have no signed-in user: pass the
|
|
44
|
+
* userId of the user who asked for the work, or leave it out for work the system does on its own.
|
|
45
|
+
*/
|
|
46
|
+
audit: Audit;
|
|
37
47
|
}
|
|
38
48
|
export interface JobDef<Input = any, Output = any> {
|
|
39
49
|
readonly kind: "inferlo.job";
|
|
@@ -64,6 +74,8 @@ export interface JobRecord {
|
|
|
64
74
|
runAt: Date;
|
|
65
75
|
/** Message of the last failure. */
|
|
66
76
|
lastError?: string;
|
|
77
|
+
/** With createApp({ tracing }): the trace (W3C traceparent and tracestate) of the request or job that enqueued it, which its runs continue. */
|
|
78
|
+
trace?: JobTraceContext;
|
|
67
79
|
createdAt: Date;
|
|
68
80
|
updatedAt: Date;
|
|
69
81
|
}
|
|
@@ -161,6 +173,12 @@ export declare function jobStoreFor(config: JobQueueConfig | undefined, adapter:
|
|
|
161
173
|
export interface JobRuntime {
|
|
162
174
|
db: AppDb;
|
|
163
175
|
ai: AI;
|
|
176
|
+
cache: Cache;
|
|
177
|
+
/** Builds ctx.audit for one run. */
|
|
178
|
+
audit(job: {
|
|
179
|
+
name: string;
|
|
180
|
+
id: string;
|
|
181
|
+
}): Audit;
|
|
164
182
|
store: JobStore;
|
|
165
183
|
/** Name of the store, shown as app.jobs.queue. Default "memory". */
|
|
166
184
|
queue?: "memory" | "database";
|
|
@@ -172,6 +190,8 @@ export interface JobRuntime {
|
|
|
172
190
|
};
|
|
173
191
|
/** Called after each attempt with its outcome and duration (metrics). */
|
|
174
192
|
onRun?(name: string, outcome: keyof RunResult, durationMs: number): void;
|
|
193
|
+
/** With createApp({ tracing }): a span per attempt that continues the trace the job was enqueued in. */
|
|
194
|
+
tracing?: JobTracing;
|
|
175
195
|
}
|
|
176
196
|
/** app.jobs plus what the framework tests reach directly. */
|
|
177
197
|
export interface InternalJobs extends AppJobs {
|
package/dist/jobs.js
CHANGED
|
@@ -125,7 +125,7 @@ export const STALE_AFTER_MS = 5 * 60_000;
|
|
|
125
125
|
/** How often a worker reports that a running job is still alive. */
|
|
126
126
|
export const HEARTBEAT_MS = 30_000;
|
|
127
127
|
export const STOPPED_MESSAGE = "The worker stopped while running this job (the process exited or lost its database connection).";
|
|
128
|
-
const JOB_COLUMNS = `"id", "name", "input", "status", "attempts", "max_attempts", "run_at", "last_error", "created_at", "updated_at"`;
|
|
128
|
+
const JOB_COLUMNS = `"id", "name", "input", "status", "attempts", "max_attempts", "run_at", "last_error", "created_at", "updated_at", "traceparent", "tracestate"`;
|
|
129
129
|
function fromRow(row) {
|
|
130
130
|
const record = {
|
|
131
131
|
id: String(row.id),
|
|
@@ -140,6 +140,11 @@ function fromRow(row) {
|
|
|
140
140
|
};
|
|
141
141
|
if (row.last_error !== null && row.last_error !== undefined)
|
|
142
142
|
record.lastError = String(row.last_error);
|
|
143
|
+
if (typeof row.traceparent === "string") {
|
|
144
|
+
record.trace = { traceparent: row.traceparent };
|
|
145
|
+
if (typeof row.tracestate === "string")
|
|
146
|
+
record.trace.tracestate = row.tracestate;
|
|
147
|
+
}
|
|
143
148
|
return record;
|
|
144
149
|
}
|
|
145
150
|
/**
|
|
@@ -161,7 +166,10 @@ export function databaseJobStore(adapter, options = {}) {
|
|
|
161
166
|
await sql(`CREATE TABLE IF NOT EXISTS "inferlo_jobs" (
|
|
162
167
|
"id" uuid PRIMARY KEY, "name" text NOT NULL, "input" jsonb NOT NULL, "status" text NOT NULL,
|
|
163
168
|
"attempts" integer NOT NULL, "max_attempts" integer NOT NULL, "run_at" timestamptz NOT NULL,
|
|
164
|
-
"last_error" text, "created_at" timestamptz NOT NULL, "updated_at" timestamptz NOT NULL
|
|
169
|
+
"last_error" text, "created_at" timestamptz NOT NULL, "updated_at" timestamptz NOT NULL,
|
|
170
|
+
"traceparent" text, "tracestate" text)`);
|
|
171
|
+
// Tables created by Inferlo 0.5 and earlier get the trace columns.
|
|
172
|
+
await sql('ALTER TABLE "inferlo_jobs" ADD COLUMN IF NOT EXISTS "traceparent" text, ADD COLUMN IF NOT EXISTS "tracestate" text');
|
|
165
173
|
await sql('CREATE INDEX IF NOT EXISTS "inferlo_jobs_due_idx" ON "inferlo_jobs" ("status", "run_at")');
|
|
166
174
|
}
|
|
167
175
|
catch {
|
|
@@ -179,8 +187,8 @@ export function databaseJobStore(adapter, options = {}) {
|
|
|
179
187
|
return {
|
|
180
188
|
async add(r) {
|
|
181
189
|
await table();
|
|
182
|
-
await sql(`INSERT INTO "inferlo_jobs" (${JOB_COLUMNS}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT ("id") DO NOTHING`, [
|
|
183
|
-
r.id, r.name, JSON.stringify(r.input), r.status, r.attempts, r.maxAttempts, r.runAt, r.lastError ?? null, r.createdAt, r.updatedAt,
|
|
190
|
+
await sql(`INSERT INTO "inferlo_jobs" (${JOB_COLUMNS}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT ("id") DO NOTHING`, [
|
|
191
|
+
r.id, r.name, JSON.stringify(r.input), r.status, r.attempts, r.maxAttempts, r.runAt, r.lastError ?? null, r.createdAt, r.updatedAt, r.trace?.traceparent ?? null, r.trace?.tracestate ?? null,
|
|
184
192
|
]);
|
|
185
193
|
},
|
|
186
194
|
async claim(now, limit, names) {
|
|
@@ -296,6 +304,9 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
296
304
|
throw new InferloError("IL6009", `runAt for job "${def.name}" must be a valid Date.`, { fix: "Pass runAt: new Date(Date.now() + 60_000) or leave it out to run now." });
|
|
297
305
|
}
|
|
298
306
|
const queued = record(def, input, options.runAt, crypto.randomUUID());
|
|
307
|
+
const trace = runtime.tracing?.capture();
|
|
308
|
+
if (trace?.traceparent)
|
|
309
|
+
queued.trace = trace;
|
|
299
310
|
await runtime.store.add(queued);
|
|
300
311
|
return queued;
|
|
301
312
|
},
|
|
@@ -355,22 +366,23 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
355
366
|
workers.set(jobs, running);
|
|
356
367
|
async function run(record, now) {
|
|
357
368
|
const started = performance.now();
|
|
358
|
-
const outcome = await attempt(record, now);
|
|
369
|
+
const outcome = runtime.tracing ? await runtime.tracing.run(record, (fail) => attempt(record, now, fail)) : await attempt(record, now);
|
|
359
370
|
runtime.onRun?.(record.name, outcome, performance.now() - started);
|
|
360
371
|
return outcome;
|
|
361
372
|
}
|
|
362
|
-
async function attempt(record, now) {
|
|
373
|
+
async function attempt(record, now, fail) {
|
|
363
374
|
const def = byName.get(record.name);
|
|
364
375
|
const touch = runtime.store.touch?.bind(runtime.store);
|
|
365
376
|
const heartbeat = touch ? setInterval(() => void touch(record.id).catch(() => { }), runtime.heartbeatMs ?? HEARTBEAT_MS) : undefined;
|
|
366
377
|
try {
|
|
367
378
|
if (!def)
|
|
368
379
|
throw new InferloError("IL6010", `"${record.name}" is not a registered job.`, { fix: "Add the job to createApp({ jobs: [...] }) in the process that runs jobs." });
|
|
369
|
-
await def.handler({ input: parse(def, record.input), db: runtime.db, ai: runtime.ai, jobs, id: record.id, attempt: record.attempts });
|
|
380
|
+
await def.handler({ input: parse(def, record.input), db: runtime.db, ai: runtime.ai, jobs, cache: runtime.cache, id: record.id, attempt: record.attempts, audit: runtime.audit({ name: record.name, id: record.id }) });
|
|
370
381
|
await runtime.store.update(record.id, { status: "done" });
|
|
371
382
|
return "done";
|
|
372
383
|
}
|
|
373
384
|
catch (error) {
|
|
385
|
+
fail?.(error);
|
|
374
386
|
const message = error instanceof Error ? error.message : String(error);
|
|
375
387
|
if (record.attempts < record.maxAttempts) {
|
|
376
388
|
await runtime.store.update(record.id, { status: "pending", lastError: message, runAt: new Date(now.getTime() + retryDelayMs(record.attempts)) });
|