@browserlessapi/inferlo 0.5.0 → 0.7.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 +14 -5
- package/dist/ai/ai-sdk.d.ts +48 -0
- package/dist/ai/ai-sdk.js +235 -0
- package/dist/ai/ai-sdk.js.map +1 -0
- package/dist/ai/index.d.ts +44 -3
- package/dist/ai/index.js +164 -9
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/public.d.ts +6 -1
- package/dist/ai/public.js +2 -0
- package/dist/ai/public.js.map +1 -1
- package/dist/ai/ui.d.ts +47 -0
- package/dist/ai/ui.js +33 -0
- package/dist/ai/ui.js.map +1 -0
- 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 +60 -6
- package/dist/app.js +310 -66
- package/dist/app.js.map +1 -1
- package/dist/astro.d.ts +60 -0
- package/dist/astro.js +68 -0
- package/dist/astro.js.map +1 -0
- package/dist/audit.d.ts +57 -0
- package/dist/audit.js +99 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/azure.d.ts +66 -0
- package/dist/azure.js +119 -0
- package/dist/azure.js.map +1 -0
- package/dist/cache.d.ts +96 -0
- package/dist/cache.js +247 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli/main.js +28 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/client-ip.d.ts +36 -1
- package/dist/client-ip.js +80 -1
- package/dist/client-ip.js.map +1 -1
- package/dist/db/memory.js +2 -1
- package/dist/db/memory.js.map +1 -1
- package/dist/db/postgres.js +4 -1
- package/dist/db/postgres.js.map +1 -1
- package/dist/db/table.d.ts +37 -5
- package/dist/db/table.js +96 -15
- package/dist/db/table.js.map +1 -1
- package/dist/db/types.d.ts +2 -0
- package/dist/describe.d.ts +54 -2
- package/dist/describe.js +34 -3
- package/dist/describe.js.map +1 -1
- package/dist/events.d.ts +134 -0
- package/dist/events.js +196 -0
- package/dist/events.js.map +1 -0
- 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 +25 -5
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +70 -10
- package/dist/jobs.js +78 -13
- 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/langchain.d.ts +58 -0
- package/dist/langchain.js +220 -0
- package/dist/langchain.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/model.d.ts +23 -6
- package/dist/model.js +35 -6
- package/dist/model.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/rate-limit.d.ts +5 -3
- package/dist/rate-limit.js +3 -3
- package/dist/rate-limit.js.map +1 -1
- 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/redis-events.d.ts +27 -0
- package/dist/redis-events.js +163 -0
- package/dist/redis-events.js.map +1 -0
- package/dist/redis-jobs.d.ts +24 -0
- package/dist/redis-jobs.js +203 -0
- package/dist/redis-jobs.js.map +1 -0
- package/dist/redis.d.ts +30 -0
- package/dist/redis.js +106 -0
- package/dist/redis.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/response-body.d.ts +6 -0
- package/dist/response-body.js +7 -0
- package/dist/response-body.js.map +1 -0
- package/dist/route.d.ts +50 -2
- package/dist/route.js +24 -0
- package/dist/route.js.map +1 -1
- package/dist/schema.d.ts +2 -2
- package/dist/serve.d.ts +26 -2
- package/dist/serve.js +111 -10
- package/dist/serve.js.map +1 -1
- package/dist/service-client.d.ts +91 -0
- package/dist/service-client.js +177 -0
- package/dist/service-client.js.map +1 -0
- package/dist/sessions.d.ts +74 -0
- package/dist/sessions.js +172 -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 +122 -0
- package/dist/tracing.js +210 -0
- package/dist/tracing.js.map +1 -0
- package/llms.txt +8 -3
- package/package.json +78 -4
- package/templates/app/AGENTS.md +10 -6
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { InferloError } from "./errors.ts";
|
|
2
|
+
import type { JobDef, JobInput } from "./jobs.ts";
|
|
3
|
+
import { type AnySchema, type Infer, type InferInput } from "./schema.ts";
|
|
4
|
+
import type { JobTraceContext } from "./tracing.ts";
|
|
5
|
+
export interface EventOptions<P extends AnySchema> {
|
|
6
|
+
/** Schema of the payload. Checked when the event is published. */
|
|
7
|
+
payload: P;
|
|
8
|
+
/** One line shown in describe. */
|
|
9
|
+
summary?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface EventDef<Input = any, Payload = any, S extends AnySchema = AnySchema> {
|
|
12
|
+
readonly kind: "inferlo.event";
|
|
13
|
+
/** Lowercase words separated by dots: "order.paid". */
|
|
14
|
+
readonly name: string;
|
|
15
|
+
/** The payload schema, also usable as a subscribed job's input: job("send-receipt", { input: orderPaid.payload }). */
|
|
16
|
+
readonly payload: S;
|
|
17
|
+
readonly summary?: string;
|
|
18
|
+
/** Type only: what publish accepts. */
|
|
19
|
+
readonly $input?: Input;
|
|
20
|
+
/** Type only: the payload after validation, which subscribed jobs receive. */
|
|
21
|
+
readonly $payload?: Payload;
|
|
22
|
+
}
|
|
23
|
+
/** What publish accepts for an event. */
|
|
24
|
+
export type EventPayload<E> = E extends EventDef<infer I, any, any> ? I : never;
|
|
25
|
+
/** A subscription: on(event, job) in createApp({ jobs }) queues the job with the payload of every published event. */
|
|
26
|
+
export interface OnDef<E extends EventDef = EventDef, J extends JobDef = JobDef> {
|
|
27
|
+
readonly kind: "inferlo.on";
|
|
28
|
+
readonly event: E;
|
|
29
|
+
readonly job: J;
|
|
30
|
+
}
|
|
31
|
+
export interface PublishOptions {
|
|
32
|
+
/** The event id. Default: a new UUID. Publishing the same id again queues no job twice (for outboxes and redelivery). */
|
|
33
|
+
id?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The tenant the event belongs to: the subscribed jobs get it (their ctx.db is scoped to it). Default: the tenant
|
|
36
|
+
* of the request or job that publishes it, none from app.events. Pass null to publish it without a tenant.
|
|
37
|
+
*/
|
|
38
|
+
tenantId?: string | null;
|
|
39
|
+
}
|
|
40
|
+
export interface PublishedEvent {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
publishedAt: Date;
|
|
44
|
+
/**
|
|
45
|
+
* Ids of the jobs for it, one per subscription in this app. Without a transport they are queued now; with one,
|
|
46
|
+
* they are queued with these ids when this app receives the event.
|
|
47
|
+
*/
|
|
48
|
+
jobs: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface Events {
|
|
51
|
+
/** Validate the payload and queue every job subscribed to the event with on(event, job). */
|
|
52
|
+
publish<E extends EventDef>(event: E, payload: EventPayload<E>, options?: PublishOptions): Promise<PublishedEvent>;
|
|
53
|
+
}
|
|
54
|
+
/** app.events: publish events and see the subscriptions. */
|
|
55
|
+
export interface AppEvents extends Events {
|
|
56
|
+
/** Events with at least one subscription in this app, in the order they were registered. */
|
|
57
|
+
readonly list: readonly EventDef[];
|
|
58
|
+
/** The subscriptions: on(event, job) values in createApp({ jobs }) and in modules. */
|
|
59
|
+
readonly subscriptions: readonly OnDef[];
|
|
60
|
+
/** The transport that carries events between services (createApp({ events: { transport } })), or undefined: events stay in this app. */
|
|
61
|
+
readonly transport: string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* With a transport: take the events waiting for this app's subscriptions and queue their jobs, then return how
|
|
64
|
+
* many events were received. app.jobs.start() and inferlo worker call it on every poll; call it yourself in tests.
|
|
65
|
+
* Without a transport it returns 0.
|
|
66
|
+
*/
|
|
67
|
+
receive(): Promise<number>;
|
|
68
|
+
}
|
|
69
|
+
/** One published event as a transport carries it. */
|
|
70
|
+
export interface EventMessage {
|
|
71
|
+
/** The event id: the same id queues each subscribed job once, also when the transport delivers it twice. */
|
|
72
|
+
id: string;
|
|
73
|
+
name: string;
|
|
74
|
+
/** The validated payload, as JSON. */
|
|
75
|
+
payload: unknown;
|
|
76
|
+
publishedAt: Date;
|
|
77
|
+
/** With createApp({ tracing }): the trace the event was published in, which the subscribed jobs continue. */
|
|
78
|
+
trace?: JobTraceContext;
|
|
79
|
+
/** The tenant the event belongs to (multi-tenancy), which the subscribed jobs get. */
|
|
80
|
+
tenantId?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Carries events between services: createApp({ events: { transport } }). Inferlo has a Redis Streams transport
|
|
84
|
+
* built in (transport: "redis"); other brokers implement this interface.
|
|
85
|
+
*/
|
|
86
|
+
export interface EventTransport {
|
|
87
|
+
/** Shown as app.events.transport and in describe: "redis". */
|
|
88
|
+
readonly name: string;
|
|
89
|
+
/** Send one event to every service subscribed to its name. */
|
|
90
|
+
send(message: EventMessage): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Deliver the waiting events with these names to deliver(), one at a time, and acknowledge each after deliver()
|
|
93
|
+
* resolves; an event whose deliver() rejects is delivered again later. Returns how many events were delivered.
|
|
94
|
+
*/
|
|
95
|
+
receive(names: readonly string[], deliver: (message: EventMessage) => Promise<void>): Promise<number>;
|
|
96
|
+
}
|
|
97
|
+
export interface EventsConfig {
|
|
98
|
+
/**
|
|
99
|
+
* How events reach other services. Default: none, publishing queues the subscribed jobs of this app only.
|
|
100
|
+
* "redis": Redis Streams in the Redis of createApp({ redis }), one consumer group per app name, so every
|
|
101
|
+
* service subscribed to an event gets it once (at least once, with each job queued once).
|
|
102
|
+
*/
|
|
103
|
+
transport?: "redis" | EventTransport;
|
|
104
|
+
}
|
|
105
|
+
/** Define an event: event("order.paid", { payload: t.object({ ... }) }). */
|
|
106
|
+
export declare function event<P extends AnySchema>(name: string, options: EventOptions<P>): EventDef<InferInput<P>, Infer<P>, P>;
|
|
107
|
+
export declare function isEvent(value: unknown): value is EventDef;
|
|
108
|
+
export declare function isOn(value: unknown): value is OnDef;
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe a job to an event: on(orderPaid, sendReceipt) in createApp({ jobs }). Every published event queues the
|
|
111
|
+
* job with the payload as its input (use input: orderPaid.payload); a job whose input is an object with fewer fields
|
|
112
|
+
* gets only those fields.
|
|
113
|
+
*/
|
|
114
|
+
export declare function on<E extends EventDef, J extends JobDef>(event: E, job: J & (NonNullable<E["$payload"]> extends JobInput<J> ? unknown : never)): OnDef<E, J>;
|
|
115
|
+
/** Checks the subscriptions of an app: one definition per event name, and each job subscribed once per event. */
|
|
116
|
+
export declare function checkSubscriptions(subscriptions: readonly OnDef[]): EventDef[];
|
|
117
|
+
/** The id of the job queued for one subscription of one event: the same for the same event id, so it is queued once. */
|
|
118
|
+
export declare function eventJobId(eventId: string, jobName: string): Promise<string>;
|
|
119
|
+
export interface EventsRuntime {
|
|
120
|
+
/** Queue one job with a fixed id (returning its id), continuing the given trace or the active one. */
|
|
121
|
+
enqueue(job: JobDef, input: unknown, id: string, trace?: JobTraceContext, tenantId?: string): Promise<string>;
|
|
122
|
+
transport?: EventTransport;
|
|
123
|
+
/** The active trace when an event is published, sent with it. */
|
|
124
|
+
capture?(): JobTraceContext | undefined;
|
|
125
|
+
logger?: {
|
|
126
|
+
error(message: string, data?: unknown): void;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** IL6033: an invalid events option or transport. */
|
|
130
|
+
export declare function invalidTransport(message: string): InferloError;
|
|
131
|
+
/** Checks createApp({ events }) and returns the custom transport, "redis", or undefined. */
|
|
132
|
+
export declare function checkEventsConfig(config: EventsConfig | undefined): EventsConfig["transport"];
|
|
133
|
+
/** app.events, built by createJobs. */
|
|
134
|
+
export declare function createEvents(subscriptions: readonly OnDef[], runtime: EventsRuntime): AppEvents;
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events: something that happened, published once and handled by any number of jobs.
|
|
3
|
+
*
|
|
4
|
+
* export const orderPaid = event("order.paid", { payload: t.object({ orderId: t.uuid(), amount: t.int() }) });
|
|
5
|
+
* export const sendReceipt = job("send-receipt", { input: orderPaid.payload }).handle(async ({ input }) => { ... });
|
|
6
|
+
*
|
|
7
|
+
* createApp({ jobs: [on(orderPaid, sendReceipt), on(orderPaid, addPoints)] })
|
|
8
|
+
* await ctx.events.publish(orderPaid, { orderId, amount }); // in a route handler or job, validated
|
|
9
|
+
*
|
|
10
|
+
* An event is delivered through the job queue: publishing queues every subscribed job with the payload, so
|
|
11
|
+
* retries, the memory, database and Redis stores, the worker and tracing work as for any job. The queued jobs get
|
|
12
|
+
* ids derived from the event id, so publishing the same event id again (a repeated delivery) queues nothing new.
|
|
13
|
+
*/
|
|
14
|
+
import { checkTenantId } from "./db/table.js";
|
|
15
|
+
import { InferloError } from "./errors.js";
|
|
16
|
+
import { ValidationError } from "./schema.js";
|
|
17
|
+
const EVENT_NAME = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)*$/;
|
|
18
|
+
const FIX = 'Use event("order.paid", { payload: t.object({ orderId: t.uuid() }) }) and subscribe jobs in createApp({ jobs: [on(orderPaid, sendReceipt)] }).';
|
|
19
|
+
function invalid(message, fix = FIX) {
|
|
20
|
+
return new InferloError("IL6032", message, { fix });
|
|
21
|
+
}
|
|
22
|
+
/** Define an event: event("order.paid", { payload: t.object({ ... }) }). */
|
|
23
|
+
export function event(name, options) {
|
|
24
|
+
if (typeof name !== "string" || name.length > 100 || !EVENT_NAME.test(name)) {
|
|
25
|
+
throw invalid(`Invalid event name ${JSON.stringify(name)}: the name must be lowercase words of letters, digits and dashes, separated by dots, such as "order.paid".`);
|
|
26
|
+
}
|
|
27
|
+
if (!options || typeof options !== "object" || !options.payload || typeof options.payload.safeParse !== "function") {
|
|
28
|
+
throw invalid(`Invalid event "${name}": it needs a payload schema such as t.object({ ... }).`);
|
|
29
|
+
}
|
|
30
|
+
for (const key of Object.keys(options)) {
|
|
31
|
+
if (key !== "payload" && key !== "summary")
|
|
32
|
+
throw invalid(`Invalid event "${name}": unknown option "${key}". An event takes payload and summary; retries belong to the subscribed jobs.`);
|
|
33
|
+
}
|
|
34
|
+
if (options.summary !== undefined && typeof options.summary !== "string")
|
|
35
|
+
throw invalid(`Invalid event "${name}": summary must be a string.`);
|
|
36
|
+
const def = { kind: "inferlo.event", name, payload: options.payload, ...(options.summary ? { summary: options.summary } : {}) };
|
|
37
|
+
return Object.freeze(def);
|
|
38
|
+
}
|
|
39
|
+
export function isEvent(value) {
|
|
40
|
+
return typeof value === "object" && value !== null && value.kind === "inferlo.event";
|
|
41
|
+
}
|
|
42
|
+
export function isOn(value) {
|
|
43
|
+
return typeof value === "object" && value !== null && value.kind === "inferlo.on";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe a job to an event: on(orderPaid, sendReceipt) in createApp({ jobs }). Every published event queues the
|
|
47
|
+
* job with the payload as its input (use input: orderPaid.payload); a job whose input is an object with fewer fields
|
|
48
|
+
* gets only those fields.
|
|
49
|
+
*/
|
|
50
|
+
export function on(event, job) {
|
|
51
|
+
if (!isEvent(event))
|
|
52
|
+
throw invalid("on() needs an event made with event(name, { payload }) as its first argument.");
|
|
53
|
+
const j = job;
|
|
54
|
+
if (typeof j !== "object" || j === null || j.kind !== "inferlo.job")
|
|
55
|
+
throw invalid(`on("${event.name}") needs a job made with job(name, { input }).handle(fn) as its second argument.`);
|
|
56
|
+
// The payload must have every field the job requires (checked on the JSON Schemas; publish validates again).
|
|
57
|
+
const payload = event.payload.toJSONSchema();
|
|
58
|
+
const input = j.options.input.toJSONSchema();
|
|
59
|
+
const missing = payload.type === "object" && input.type === "object" ? (input.required ?? []).filter((key) => !(key in (payload.properties ?? {}))) : [];
|
|
60
|
+
if (missing.length) {
|
|
61
|
+
throw invalid(`Job "${j.name}" cannot handle event "${event.name}": its input requires ${missing.map((k) => `"${k}"`).join(", ")}, which the payload does not have.`, `Use the event's payload as the job's input: job("${j.name}", { input: ${event.name.replace(/\W(\w)/g, (_, c) => c.toUpperCase())}.payload }).`);
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze({ kind: "inferlo.on", event, job: job });
|
|
64
|
+
}
|
|
65
|
+
/** Checks the subscriptions of an app: one definition per event name, and each job subscribed once per event. */
|
|
66
|
+
export function checkSubscriptions(subscriptions) {
|
|
67
|
+
const events = new Map();
|
|
68
|
+
const pairs = new Set();
|
|
69
|
+
for (const s of subscriptions) {
|
|
70
|
+
const known = events.get(s.event.name);
|
|
71
|
+
if (known && known !== s.event) {
|
|
72
|
+
throw invalid(`Two different events are named "${s.event.name}".`, "Define each event once (export const orderPaid = event(...)) and import it wherever it is published or subscribed to.");
|
|
73
|
+
}
|
|
74
|
+
events.set(s.event.name, s.event);
|
|
75
|
+
const pair = `${s.event.name}\u0000${s.job.name}`;
|
|
76
|
+
if (pairs.has(pair))
|
|
77
|
+
throw invalid(`Job "${s.job.name}" is subscribed to event "${s.event.name}" twice.`, "Keep one on(event, job) for each pair.");
|
|
78
|
+
pairs.add(pair);
|
|
79
|
+
}
|
|
80
|
+
return [...events.values()];
|
|
81
|
+
}
|
|
82
|
+
/** The id of the job queued for one subscription of one event: the same for the same event id, so it is queued once. */
|
|
83
|
+
export async function eventJobId(eventId, jobName) {
|
|
84
|
+
const data = new TextEncoder().encode(`inferlo.event:${eventId}:${jobName}`);
|
|
85
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", data)).slice(0, 16);
|
|
86
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x80; // version 8 (custom)
|
|
87
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80; // RFC 4122 variant
|
|
88
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
89
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
90
|
+
}
|
|
91
|
+
/** IL6033: an invalid events option or transport. */
|
|
92
|
+
export function invalidTransport(message) {
|
|
93
|
+
return new InferloError("IL6033", message, {
|
|
94
|
+
fix: 'Use createApp({ name: "billing", redis, events: { transport: "redis" } }), or pass an object with name, send(message) and receive(names, deliver).',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** Checks createApp({ events }) and returns the custom transport, "redis", or undefined. */
|
|
98
|
+
export function checkEventsConfig(config) {
|
|
99
|
+
if (config === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
if (typeof config !== "object" || config === null)
|
|
102
|
+
throw invalidTransport("createApp({ events }) must be an object such as { transport: \"redis\" }.");
|
|
103
|
+
for (const key of Object.keys(config))
|
|
104
|
+
if (key !== "transport")
|
|
105
|
+
throw invalidTransport(`Unknown events option "${key}". createApp({ events }) takes transport.`);
|
|
106
|
+
const t = config.transport;
|
|
107
|
+
if (t === undefined || t === "redis")
|
|
108
|
+
return t;
|
|
109
|
+
if (typeof t !== "object" || t === null || typeof t.name !== "string" || !t.name || typeof t.send !== "function" || typeof t.receive !== "function") {
|
|
110
|
+
throw invalidTransport(`Invalid events transport ${typeof t === "string" ? JSON.stringify(t) : "object"}: use "redis" or an object with name, send(message) and receive(names, deliver).`);
|
|
111
|
+
}
|
|
112
|
+
return t;
|
|
113
|
+
}
|
|
114
|
+
/** app.events, built by createJobs. */
|
|
115
|
+
export function createEvents(subscriptions, runtime) {
|
|
116
|
+
const { enqueue, transport } = runtime;
|
|
117
|
+
const list = checkSubscriptions(subscriptions);
|
|
118
|
+
const byName = new Map(list.map((e) => [e.name, e]));
|
|
119
|
+
const names = list.map((e) => e.name);
|
|
120
|
+
// A job may take part of the payload (input: t.object({ orderId })): it gets only the fields its input has.
|
|
121
|
+
const fields = new Map();
|
|
122
|
+
for (const s of subscriptions) {
|
|
123
|
+
const input = s.job.options.input.toJSONSchema();
|
|
124
|
+
fields.set(s, input.type === "object" && input.properties ? Object.keys(input.properties) : undefined);
|
|
125
|
+
}
|
|
126
|
+
const inputFor = (s, payload) => {
|
|
127
|
+
const keys = fields.get(s);
|
|
128
|
+
if (!keys || typeof payload !== "object" || payload === null || Array.isArray(payload))
|
|
129
|
+
return payload;
|
|
130
|
+
return Object.fromEntries(keys.filter((k) => k in payload).map((k) => [k, payload[k]]));
|
|
131
|
+
};
|
|
132
|
+
/** Queue the jobs subscribed to one event in this app. */
|
|
133
|
+
const queueJobs = async (name, id, payload, trace, tenantId) => {
|
|
134
|
+
const jobs = [];
|
|
135
|
+
for (const s of subscriptions) {
|
|
136
|
+
if (s.event.name !== name)
|
|
137
|
+
continue;
|
|
138
|
+
jobs.push(await enqueue(s.job, inputFor(s, payload), await eventJobId(id, s.job.name), trace, tenantId));
|
|
139
|
+
}
|
|
140
|
+
return jobs;
|
|
141
|
+
};
|
|
142
|
+
/** An event from the transport: checked against this app's definition, then its jobs are queued. */
|
|
143
|
+
const deliver = async (message) => {
|
|
144
|
+
const def = byName.get(message.name);
|
|
145
|
+
if (!def)
|
|
146
|
+
return; // not subscribed here (a transport may deliver more than it was asked for)
|
|
147
|
+
const parsed = def.payload.safeParse(message.payload);
|
|
148
|
+
if (!parsed.ok) {
|
|
149
|
+
// Delivering it again cannot help: log it (without the payload, which may hold personal data) and acknowledge it.
|
|
150
|
+
const error = new InferloError("IL6034", `Received event "${message.name}" (id ${message.id}) does not match this service's payload schema and was dropped: ${new ValidationError(parsed.issues, "payload").message}`, {
|
|
151
|
+
fix: "Publish and subscribe with the same event definition, from a shared package, and change payloads compatibly (add optional fields only).",
|
|
152
|
+
});
|
|
153
|
+
runtime.logger?.error(`event "${message.name}" dropped`, error);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// A tenant id that is not valid is dropped rather than trusted: the jobs then run without a tenant.
|
|
157
|
+
const tenantId = typeof message.tenantId === "string" && message.tenantId.length > 0 && message.tenantId.length <= 200 ? message.tenantId : undefined;
|
|
158
|
+
await queueJobs(message.name, message.id, parsed.value, message.trace, tenantId);
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
list,
|
|
162
|
+
subscriptions,
|
|
163
|
+
transport: transport?.name,
|
|
164
|
+
async receive() {
|
|
165
|
+
if (!transport || !names.length)
|
|
166
|
+
return 0;
|
|
167
|
+
return transport.receive(names, deliver);
|
|
168
|
+
},
|
|
169
|
+
async publish(def, payload, options = {}) {
|
|
170
|
+
if (!isEvent(def))
|
|
171
|
+
throw invalid("events.publish() needs an event made with event(name, { payload }).", "Pass the event and its payload: await events.publish(orderPaid, { orderId }).");
|
|
172
|
+
if (options.id !== undefined && (typeof options.id !== "string" || options.id.length === 0 || options.id.length > 200)) {
|
|
173
|
+
throw invalid(`The id for event "${def.name}" must be a non-empty string of at most 200 characters.`, "Leave it out for a new UUID, or pass the id of the record the event comes from (an outbox row).");
|
|
174
|
+
}
|
|
175
|
+
const tenantId = options.tenantId === undefined || options.tenantId === null ? undefined : checkTenantId(options.tenantId);
|
|
176
|
+
const parsed = def.payload.safeParse(payload);
|
|
177
|
+
if (!parsed.ok)
|
|
178
|
+
throw new ValidationError(parsed.issues, `payload for event "${def.name}"`);
|
|
179
|
+
const id = options.id ?? crypto.randomUUID();
|
|
180
|
+
const publishedAt = new Date();
|
|
181
|
+
if (transport) {
|
|
182
|
+
// Every service (this one too) queues its jobs when it receives the event; their ids are known already.
|
|
183
|
+
const trace = runtime.capture?.();
|
|
184
|
+
const json = JSON.parse(JSON.stringify(parsed.value ?? null));
|
|
185
|
+
await transport.send({ id, name: def.name, payload: json, publishedAt, ...(trace?.traceparent ? { trace } : {}), ...(tenantId ? { tenantId } : {}) });
|
|
186
|
+
const jobs = [];
|
|
187
|
+
for (const s of subscriptions)
|
|
188
|
+
if (s.event.name === def.name)
|
|
189
|
+
jobs.push(await eventJobId(id, s.job.name));
|
|
190
|
+
return { id, name: def.name, publishedAt, jobs };
|
|
191
|
+
}
|
|
192
|
+
return { id, name: def.name, publishedAt, jobs: await queueJobs(def.name, id, parsed.value, undefined, tenantId) };
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
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;
|