@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/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,32 +8,52 @@ 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, RouteInput, 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";
|
|
16
|
-
export type { Row, CreateInput, UpdateInput, AnyModel } from "./model.ts";
|
|
20
|
+
export type { Row, CreateInput, UpdateInput, AnyModel, ModelOptions } from "./model.ts";
|
|
17
21
|
export { memory } from "./db/memory.ts";
|
|
18
22
|
export { createDb, Table } from "./db/table.ts";
|
|
19
|
-
export type { Db, Where, OrderBy, FindManyOptions, FindOptions, PaginateOptions, Page, Include, Relations, ReverseRelations, WithInclude } from "./db/table.ts";
|
|
23
|
+
export type { Db, PgClient, Where, OrderBy, FindManyOptions, FindOptions, PaginateOptions, Page, Include, Relations, ReverseRelations, WithInclude } from "./db/table.ts";
|
|
20
24
|
export type { Adapter, Condition, Query, DbRecord } from "./db/types.ts";
|
|
21
25
|
export { planMigration, applyMigrations } from "./db/migrate.ts";
|
|
22
26
|
export type { ApplyMigrationsOptions, Migration, MigrationPlan, MigrationSnapshot, MigrationSnapshotColumn, MigrationSnapshotV1, PlanMigrationOptions, } from "./db/migrate.ts";
|
|
23
|
-
export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.ts";
|
|
27
|
+
export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError, uiChatBody, fromUIMessages } from "./ai/index.ts";
|
|
24
28
|
export { tool, isTool, ToolBuilder } from "./ai/tool.ts";
|
|
29
|
+
export { aiSdk } from "./ai/ai-sdk.ts";
|
|
30
|
+
export type { AISdkProvider, AISdkLanguageModel, AISdkEmbeddingModel, AISdkOptions } from "./ai/ai-sdk.ts";
|
|
25
31
|
export type { ToolDef, FunctionToolOptions, ToolContext } from "./ai/tool.ts";
|
|
26
|
-
export type { AI, AIConfig, GenerateOptions, GenerateResult, StreamResult, Usage, Pricing, Provider, ProviderRequest, ProviderResponse, Image, ImageMediaType, Message, MockReply, Step, ToolCall, ToolCallResult, ToolSpec, } from "./ai/index.ts";
|
|
32
|
+
export type { AI, AIConfig, GenerateOptions, GenerateResult, StreamResult, StreamResponseInit, UIChatMessage, Usage, Pricing, Provider, ProviderRequest, ProviderResponse, Image, ImageMediaType, Message, MockReply, Step, ToolCall, ToolCallResult, ToolSpec, } from "./ai/index.ts";
|
|
27
33
|
export type { RateLimit, RateLimitConfig } from "./rate-limit.ts";
|
|
34
|
+
export type { RedisClient } from "./redis.ts";
|
|
28
35
|
export { job, isJob, retryDelayMs } from "./jobs.ts";
|
|
29
36
|
export { cron, isCron } from "./cron.ts";
|
|
37
|
+
export { event, isEvent, on } from "./events.ts";
|
|
38
|
+
export type { AppEvents, EventDef, EventMessage, EventOptions, EventPayload, Events, EventsConfig, EventTransport, OnDef, PublishedEvent, PublishOptions } from "./events.ts";
|
|
30
39
|
export type { CronDef } from "./cron.ts";
|
|
40
|
+
export type { JobTraceContext } from "./tracing.ts";
|
|
31
41
|
export type { JobDef, JobOptions, JobContext, JobInput, JobRecord, JobStatus, JobSource, JobQueueConfig, Jobs, AppJobs, EnqueueOptions, RunResult, Worker } from "./jobs.ts";
|
|
42
|
+
export { AuditLog, auditChanges } from "./audit.ts";
|
|
43
|
+
export type { Audit, AuditEntry, AuditChange } from "./audit.ts";
|
|
44
|
+
export type { Cache, CacheConfig, CacheSetOptions, RouteCache } from "./cache.ts";
|
|
45
|
+
export { permissions, isPermissions } from "./permissions.ts";
|
|
46
|
+
export type { Permissions, PermissionRule, PermissionInput } from "./permissions.ts";
|
|
32
47
|
export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.ts";
|
|
48
|
+
export { sessions } from "./sessions.ts";
|
|
49
|
+
export type { TracingConfig, OtelApi, OtelSpan } from "./tracing.ts";
|
|
50
|
+
export type { Sessions, SessionsOptions, SessionData, SessionCookieOptions, SessionResponseContext } from "./sessions.ts";
|
|
33
51
|
export { env, setEnv } from "./env.ts";
|
|
34
52
|
export { config } from "./config.ts";
|
|
35
53
|
export type { Config, ConfigOptions } from "./config.ts";
|
|
36
54
|
export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.ts";
|
|
55
|
+
export { serviceClient, ServiceCallError } from "./service-client.ts";
|
|
56
|
+
export type { ServiceClient, ServiceClientOptions, CallInput, CallOptions, CallContext, CallResult, TypedRoute } from "./service-client.ts";
|
|
37
57
|
export { serve, toNodeHandler, fromNodeRequest } from "./serve.ts";
|
|
38
58
|
export type { ServeOptions, Server, NodeRequest, NodeResponse, NodeHandler, NodeHandlerOptions, FromNodeRequestOptions } from "./serve.ts";
|
|
39
59
|
export { INFERLO_VERSION } from "./describe.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,20 +3,28 @@
|
|
|
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";
|
|
9
11
|
export { memory } from "./db/memory.js";
|
|
10
12
|
export { createDb, Table } from "./db/table.js";
|
|
11
13
|
export { planMigration, applyMigrations } from "./db/migrate.js";
|
|
12
|
-
export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.js";
|
|
14
|
+
export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError, uiChatBody, fromUIMessages } from "./ai/index.js";
|
|
13
15
|
export { tool, isTool, ToolBuilder } from "./ai/tool.js";
|
|
16
|
+
export { aiSdk } from "./ai/ai-sdk.js";
|
|
14
17
|
export { job, isJob, retryDelayMs } from "./jobs.js";
|
|
15
18
|
export { cron, isCron } from "./cron.js";
|
|
19
|
+
export { event, isEvent, on } from "./events.js";
|
|
20
|
+
export { AuditLog, auditChanges } from "./audit.js";
|
|
21
|
+
export { permissions, isPermissions } from "./permissions.js";
|
|
16
22
|
export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.js";
|
|
23
|
+
export { sessions } from "./sessions.js";
|
|
17
24
|
export { env, setEnv } from "./env.js";
|
|
18
25
|
export { config } from "./config.js";
|
|
19
26
|
export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.js";
|
|
27
|
+
export { serviceClient, ServiceCallError } from "./service-client.js";
|
|
20
28
|
export { serve, toNodeHandler, fromNodeRequest } from "./serve.js";
|
|
21
29
|
export { INFERLO_VERSION } from "./describe.js";
|
|
22
30
|
//# sourceMappingURL=index.js.map
|
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;
|
|
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,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACtL,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AA4BvC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAKjD,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,aAAa,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEtE,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,14 @@
|
|
|
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";
|
|
18
|
+
import { type AppEvents, type EventTransport, type Events, type OnDef } from "./events.ts";
|
|
16
19
|
import type { AppDb } from "./route.ts";
|
|
17
20
|
import { type AnySchema, type Infer, type InferInput } from "./schema.ts";
|
|
21
|
+
import type { JobTraceContext, JobTracing } from "./tracing.ts";
|
|
18
22
|
export interface JobOptions<I extends AnySchema> {
|
|
19
23
|
/** Schema of the job's input. Checked when the job is enqueued and again when it runs. */
|
|
20
24
|
input: I;
|
|
@@ -30,10 +34,24 @@ export interface JobContext<Input> {
|
|
|
30
34
|
ai: AI;
|
|
31
35
|
/** Enqueue follow-up jobs. */
|
|
32
36
|
jobs: Jobs;
|
|
37
|
+
/** Publish events: await events.publish(orderPaid, { orderId }) queues the jobs subscribed with on(event, job). */
|
|
38
|
+
events: Events;
|
|
39
|
+
/** The app cache, the same as ctx.cache in routes and app.cache. */
|
|
40
|
+
cache: Cache;
|
|
33
41
|
/** Job id. */
|
|
34
42
|
id: string;
|
|
35
43
|
/** 1 on the first run, 2 on the first retry, and so on. */
|
|
36
44
|
attempt: number;
|
|
45
|
+
/**
|
|
46
|
+
* The tenant the job works for (the tenant of the request or job that queued it, or enqueue's tenantId), or null.
|
|
47
|
+
* ctx.db is scoped to it, and jobs and events queued from this job keep it.
|
|
48
|
+
*/
|
|
49
|
+
tenantId: string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Write an audit log entry (needs AuditLog in createApp({ models })). Jobs have no signed-in user: pass the
|
|
52
|
+
* userId of the user who asked for the work, or leave it out for work the system does on its own.
|
|
53
|
+
*/
|
|
54
|
+
audit: Audit;
|
|
37
55
|
}
|
|
38
56
|
export interface JobDef<Input = any, Output = any> {
|
|
39
57
|
readonly kind: "inferlo.job";
|
|
@@ -48,6 +66,11 @@ export type JobInput<J> = J extends JobDef<infer I, any> ? I : never;
|
|
|
48
66
|
export interface EnqueueOptions {
|
|
49
67
|
/** Run no earlier than this time. Default: now. */
|
|
50
68
|
runAt?: Date;
|
|
69
|
+
/**
|
|
70
|
+
* The tenant the job works for: its ctx.db is scoped to it (tenant-scoped models). Default: the tenant of the
|
|
71
|
+
* request or job that enqueues it, none from app.jobs. Pass null to queue it without a tenant.
|
|
72
|
+
*/
|
|
73
|
+
tenantId?: string | null;
|
|
51
74
|
}
|
|
52
75
|
export type JobStatus = "pending" | "running" | "done" | "failed";
|
|
53
76
|
export interface JobRecord {
|
|
@@ -64,6 +87,10 @@ export interface JobRecord {
|
|
|
64
87
|
runAt: Date;
|
|
65
88
|
/** Message of the last failure. */
|
|
66
89
|
lastError?: string;
|
|
90
|
+
/** With createApp({ tracing }): the trace (W3C traceparent and tracestate) of the request or job that enqueued it, which its runs continue. */
|
|
91
|
+
trace?: JobTraceContext;
|
|
92
|
+
/** The tenant the job works for (multi-tenancy): its ctx.db is scoped to it. */
|
|
93
|
+
tenantId?: string;
|
|
67
94
|
createdAt: Date;
|
|
68
95
|
updatedAt: Date;
|
|
69
96
|
}
|
|
@@ -75,8 +102,9 @@ export interface JobQueueConfig {
|
|
|
75
102
|
/**
|
|
76
103
|
* Where queued jobs are kept. "memory" (default): in this process, for development and tests.
|
|
77
104
|
* "database": in the inferlo_jobs table of the app's Postgres database, shared by every instance.
|
|
105
|
+
* "redis": in the Redis of createApp({ redis }), shared by every instance.
|
|
78
106
|
*/
|
|
79
|
-
store?: "memory" | "database";
|
|
107
|
+
store?: "memory" | "database" | "redis";
|
|
80
108
|
}
|
|
81
109
|
/** Where queued jobs are kept. */
|
|
82
110
|
export interface JobStore {
|
|
@@ -86,7 +114,7 @@ export interface JobStore {
|
|
|
86
114
|
claim(now: Date, limit: number, names: readonly string[]): Promise<JobRecord[]>;
|
|
87
115
|
update(id: string, patch: Partial<Pick<JobRecord, "status" | "runAt" | "lastError">>): Promise<void>;
|
|
88
116
|
get(id: string): Promise<JobRecord | null>;
|
|
89
|
-
/** Record that a running job is still alive (database
|
|
117
|
+
/** Record that a running job is still alive (database and Redis stores), so it is not picked up again as stuck. */
|
|
90
118
|
touch?(id: string): Promise<void>;
|
|
91
119
|
}
|
|
92
120
|
export interface RunResult {
|
|
@@ -108,14 +136,17 @@ export interface AppJobs extends Jobs {
|
|
|
108
136
|
/** The registered crons: cron(schedule, job) values in createApp({ jobs }). */
|
|
109
137
|
readonly crons: readonly CronDef[];
|
|
110
138
|
/** Where queued jobs are kept (createApp({ jobQueue: { store } })). */
|
|
111
|
-
readonly queue: "memory" | "database";
|
|
139
|
+
readonly queue: "memory" | "database" | "redis";
|
|
112
140
|
/** A queued job by id, or null. */
|
|
113
141
|
get(id: string): Promise<JobRecord | null>;
|
|
114
142
|
/** Run every job that is due now (or at options.now), one after another, including follow-ups they enqueue, then return. For tests and scripts. */
|
|
115
143
|
runDue(options?: {
|
|
116
144
|
now?: Date;
|
|
117
145
|
}): Promise<RunResult>;
|
|
118
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Run due jobs in this process every pollMs (default 1000) until stop() is called. Also queues each cron's job at
|
|
148
|
+
* every tick after the start, and with an events transport, the jobs of the events it receives.
|
|
149
|
+
*/
|
|
119
150
|
start(options?: {
|
|
120
151
|
pollMs?: number;
|
|
121
152
|
}): Worker;
|
|
@@ -134,13 +165,15 @@ export declare class JobBuilder<I extends AnySchema> {
|
|
|
134
165
|
/** Define a background job: job("send-email", { input: t.object({ to: t.email() }) }).handle(fn). */
|
|
135
166
|
export declare function job<I extends AnySchema>(name: string, options: JobOptions<I>): JobBuilder<I>;
|
|
136
167
|
export declare function isJob(value: unknown): value is JobDef;
|
|
137
|
-
/** Anything that contains jobs: a job, a cron, an array, or a module (import * as jobs from "./jobs.ts"). */
|
|
138
|
-
export type JobSource = JobDef | CronDef | readonly JobSource[] | {
|
|
168
|
+
/** Anything that contains jobs: a job, a cron, an event subscription (on), an array, or a module (import * as jobs from "./jobs.ts"). */
|
|
169
|
+
export type JobSource = JobDef | CronDef | OnDef | readonly JobSource[] | {
|
|
139
170
|
readonly [name: string]: unknown;
|
|
140
171
|
};
|
|
141
172
|
/** The jobs and crons in createApp({ jobs }). A cron's job is registered with it. */
|
|
142
173
|
export declare function collectJobs(sources: readonly JobSource[] | undefined): JobDef[];
|
|
143
174
|
export declare function collectCrons(sources: readonly JobSource[] | undefined): CronDef[];
|
|
175
|
+
/** The event subscriptions, on(event, job), in createApp({ jobs }). */
|
|
176
|
+
export declare function collectSubscriptions(sources: readonly JobSource[] | undefined): OnDef[];
|
|
144
177
|
/** Jobs kept in this process. They are lost when it stops, so use it for development and tests. */
|
|
145
178
|
export declare function memoryJobStore(): JobStore;
|
|
146
179
|
/** A running job whose worker has not reported for this long is treated as stuck (its process stopped) and run again. */
|
|
@@ -156,14 +189,24 @@ export declare const STOPPED_MESSAGE = "The worker stopped while running this jo
|
|
|
156
189
|
export declare function databaseJobStore(adapter: Adapter, options?: {
|
|
157
190
|
staleAfterMs?: number;
|
|
158
191
|
}): JobStore;
|
|
159
|
-
/**
|
|
160
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Pick the job store for createApp({ jobQueue }). Raises IL6011 for an unknown store, IL6012 for "database" without
|
|
194
|
+
* Postgres. "redis" uses the redis option, a function that returns the Redis store (or throws IL6031 without a client).
|
|
195
|
+
*/
|
|
196
|
+
export declare function jobStoreFor(config: JobQueueConfig | undefined, adapter: Adapter, redis?: () => JobStore): JobStore;
|
|
161
197
|
export interface JobRuntime {
|
|
162
198
|
db: AppDb;
|
|
163
199
|
ai: AI;
|
|
200
|
+
cache: Cache;
|
|
201
|
+
/** Builds ctx.audit for one run. */
|
|
202
|
+
audit(job: {
|
|
203
|
+
name: string;
|
|
204
|
+
id: string;
|
|
205
|
+
tenantId: string | null;
|
|
206
|
+
}): Audit;
|
|
164
207
|
store: JobStore;
|
|
165
208
|
/** Name of the store, shown as app.jobs.queue. Default "memory". */
|
|
166
|
-
queue?: "memory" | "database";
|
|
209
|
+
queue?: "memory" | "database" | "redis";
|
|
167
210
|
/** How often a running job is reported alive to stores that support it. Default HEARTBEAT_MS. */
|
|
168
211
|
heartbeatMs?: number;
|
|
169
212
|
logger: {
|
|
@@ -172,10 +215,27 @@ export interface JobRuntime {
|
|
|
172
215
|
};
|
|
173
216
|
/** Called after each attempt with its outcome and duration (metrics). */
|
|
174
217
|
onRun?(name: string, outcome: keyof RunResult, durationMs: number): void;
|
|
218
|
+
/** With createApp({ tracing }): a span per attempt that continues the trace the job was enqueued in. */
|
|
219
|
+
tracing?: JobTracing;
|
|
220
|
+
/** With createApp({ events: { transport } }): carries published events between services. */
|
|
221
|
+
eventTransport?: EventTransport;
|
|
175
222
|
}
|
|
176
223
|
/** app.jobs plus what the framework tests reach directly. */
|
|
177
224
|
export interface InternalJobs extends AppJobs {
|
|
178
225
|
/** Queue the job of each cron's latest tick in (from, to]. */
|
|
179
226
|
queueCrons(from: Date, to: Date): Promise<void>;
|
|
227
|
+
/** app.events: publishing queues the jobs subscribed with on(event, job). */
|
|
228
|
+
readonly events: AppEvents;
|
|
180
229
|
}
|
|
181
|
-
export declare function createJobs(list: readonly JobDef[], runtime: JobRuntime, crons?: readonly CronDef[]): InternalJobs;
|
|
230
|
+
export declare function createJobs(list: readonly JobDef[], runtime: JobRuntime, crons?: readonly CronDef[], subscriptions?: readonly OnDef[]): InternalJobs;
|
|
231
|
+
/**
|
|
232
|
+
* ctx.jobs and ctx.events for one tenant: jobs and events queued through them carry the tenant unless the call
|
|
233
|
+
* names another (tenantId) or none (null). Internal: used for requests and jobs with a tenant.
|
|
234
|
+
*/
|
|
235
|
+
export declare function withTenant(base: {
|
|
236
|
+
jobs: Jobs;
|
|
237
|
+
events: Events;
|
|
238
|
+
}, tenantId: string): {
|
|
239
|
+
jobs: Jobs;
|
|
240
|
+
events: Events;
|
|
241
|
+
};
|
package/dist/jobs.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cronTickId, isCron } from "./cron.js";
|
|
2
|
+
import { checkTenantId } from "./db/table.js";
|
|
2
3
|
import { InferloError } from "./errors.js";
|
|
4
|
+
import { createEvents, isOn } from "./events.js";
|
|
3
5
|
import { ValidationError } from "./schema.js";
|
|
4
6
|
/** Workers started with app.jobs.start() that have not been stopped, so app.close() can stop them. */
|
|
5
7
|
const workers = new WeakMap();
|
|
@@ -61,9 +63,14 @@ export function collectJobs(sources) {
|
|
|
61
63
|
export function collectCrons(sources) {
|
|
62
64
|
return collect(sources).crons;
|
|
63
65
|
}
|
|
66
|
+
/** The event subscriptions, on(event, job), in createApp({ jobs }). */
|
|
67
|
+
export function collectSubscriptions(sources) {
|
|
68
|
+
return collect(sources).subscriptions;
|
|
69
|
+
}
|
|
64
70
|
function collect(sources) {
|
|
65
71
|
const out = [];
|
|
66
72
|
const crons = [];
|
|
73
|
+
const subscriptions = [];
|
|
67
74
|
const seen = new Set();
|
|
68
75
|
const visit = (source) => {
|
|
69
76
|
if (!source || typeof source !== "object" || seen.has(source))
|
|
@@ -75,11 +82,15 @@ function collect(sources) {
|
|
|
75
82
|
crons.push(source);
|
|
76
83
|
visit(source.job);
|
|
77
84
|
}
|
|
85
|
+
else if (isOn(source)) {
|
|
86
|
+
subscriptions.push(source);
|
|
87
|
+
visit(source.job);
|
|
88
|
+
}
|
|
78
89
|
else if (Array.isArray(source))
|
|
79
90
|
source.forEach(visit);
|
|
80
91
|
else
|
|
81
92
|
for (const value of Object.values(source))
|
|
82
|
-
if (isJob(value) || isCron(value) || Array.isArray(value))
|
|
93
|
+
if (isJob(value) || isCron(value) || isOn(value) || Array.isArray(value))
|
|
83
94
|
visit(value);
|
|
84
95
|
};
|
|
85
96
|
visit(sources);
|
|
@@ -89,7 +100,7 @@ function collect(sources) {
|
|
|
89
100
|
throw new InferloError("IL6009", `Two jobs are named "${j.name}".`, { fix: "Give each job a unique name." });
|
|
90
101
|
names.set(j.name, j);
|
|
91
102
|
}
|
|
92
|
-
return { jobs: out, crons };
|
|
103
|
+
return { jobs: out, crons, subscriptions };
|
|
93
104
|
}
|
|
94
105
|
/** Jobs kept in this process. They are lost when it stops, so use it for development and tests. */
|
|
95
106
|
export function memoryJobStore() {
|
|
@@ -125,7 +136,7 @@ export const STALE_AFTER_MS = 5 * 60_000;
|
|
|
125
136
|
/** How often a worker reports that a running job is still alive. */
|
|
126
137
|
export const HEARTBEAT_MS = 30_000;
|
|
127
138
|
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"`;
|
|
139
|
+
const JOB_COLUMNS = `"id", "name", "input", "status", "attempts", "max_attempts", "run_at", "last_error", "created_at", "updated_at", "traceparent", "tracestate", "tenant_id"`;
|
|
129
140
|
function fromRow(row) {
|
|
130
141
|
const record = {
|
|
131
142
|
id: String(row.id),
|
|
@@ -140,6 +151,13 @@ function fromRow(row) {
|
|
|
140
151
|
};
|
|
141
152
|
if (row.last_error !== null && row.last_error !== undefined)
|
|
142
153
|
record.lastError = String(row.last_error);
|
|
154
|
+
if (typeof row.traceparent === "string") {
|
|
155
|
+
record.trace = { traceparent: row.traceparent };
|
|
156
|
+
if (typeof row.tracestate === "string")
|
|
157
|
+
record.trace.tracestate = row.tracestate;
|
|
158
|
+
}
|
|
159
|
+
if (typeof row.tenant_id === "string")
|
|
160
|
+
record.tenantId = row.tenant_id;
|
|
143
161
|
return record;
|
|
144
162
|
}
|
|
145
163
|
/**
|
|
@@ -161,7 +179,10 @@ export function databaseJobStore(adapter, options = {}) {
|
|
|
161
179
|
await sql(`CREATE TABLE IF NOT EXISTS "inferlo_jobs" (
|
|
162
180
|
"id" uuid PRIMARY KEY, "name" text NOT NULL, "input" jsonb NOT NULL, "status" text NOT NULL,
|
|
163
181
|
"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
|
|
182
|
+
"last_error" text, "created_at" timestamptz NOT NULL, "updated_at" timestamptz NOT NULL,
|
|
183
|
+
"traceparent" text, "tracestate" text, "tenant_id" text)`);
|
|
184
|
+
// Tables created by earlier versions get the trace (Inferlo 0.6) and tenant (0.7) columns.
|
|
185
|
+
await sql('ALTER TABLE "inferlo_jobs" ADD COLUMN IF NOT EXISTS "traceparent" text, ADD COLUMN IF NOT EXISTS "tracestate" text, ADD COLUMN IF NOT EXISTS "tenant_id" text');
|
|
165
186
|
await sql('CREATE INDEX IF NOT EXISTS "inferlo_jobs_due_idx" ON "inferlo_jobs" ("status", "run_at")');
|
|
166
187
|
}
|
|
167
188
|
catch {
|
|
@@ -179,8 +200,8 @@ export function databaseJobStore(adapter, options = {}) {
|
|
|
179
200
|
return {
|
|
180
201
|
async add(r) {
|
|
181
202
|
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,
|
|
203
|
+
await sql(`INSERT INTO "inferlo_jobs" (${JOB_COLUMNS}) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) ON CONFLICT ("id") DO NOTHING`, [
|
|
204
|
+
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, r.tenantId ?? null,
|
|
184
205
|
]);
|
|
185
206
|
},
|
|
186
207
|
async claim(now, limit, names) {
|
|
@@ -229,16 +250,21 @@ export function databaseJobStore(adapter, options = {}) {
|
|
|
229
250
|
},
|
|
230
251
|
};
|
|
231
252
|
}
|
|
232
|
-
/**
|
|
233
|
-
|
|
253
|
+
/**
|
|
254
|
+
* Pick the job store for createApp({ jobQueue }). Raises IL6011 for an unknown store, IL6012 for "database" without
|
|
255
|
+
* Postgres. "redis" uses the redis option, a function that returns the Redis store (or throws IL6031 without a client).
|
|
256
|
+
*/
|
|
257
|
+
export function jobStoreFor(config, adapter, redis) {
|
|
234
258
|
const store = config?.store ?? "memory";
|
|
235
259
|
if (store === "memory")
|
|
236
260
|
return memoryJobStore();
|
|
237
261
|
if (store === "database")
|
|
238
262
|
return databaseJobStore(adapter);
|
|
239
|
-
|
|
263
|
+
if (store === "redis" && redis)
|
|
264
|
+
return redis();
|
|
265
|
+
throw new InferloError("IL6011", `Invalid jobQueue store "${String(store)}".`, { fix: 'Use createApp({ jobQueue: { store: "memory" } }) (the default), { store: "database" } or { store: "redis" } with createApp({ redis }).' });
|
|
240
266
|
}
|
|
241
|
-
export function createJobs(list, runtime, crons = []) {
|
|
267
|
+
export function createJobs(list, runtime, crons = [], subscriptions = []) {
|
|
242
268
|
const byName = new Map(list.map((j) => [j.name, j]));
|
|
243
269
|
const names = list.map((j) => j.name);
|
|
244
270
|
const running = new Set();
|
|
@@ -285,9 +311,25 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
285
311
|
await runtime.store.add(record(c.job, {}, tick, await cronTickId(c, tick)));
|
|
286
312
|
}
|
|
287
313
|
};
|
|
314
|
+
const events = createEvents(subscriptions, {
|
|
315
|
+
async enqueue(def, input, id, from, tenantId) {
|
|
316
|
+
const queued = record(registered(def), input, undefined, id);
|
|
317
|
+
const trace = from ?? runtime.tracing?.capture();
|
|
318
|
+
if (runtime.tracing && trace?.traceparent)
|
|
319
|
+
queued.trace = trace;
|
|
320
|
+
if (tenantId)
|
|
321
|
+
queued.tenantId = tenantId;
|
|
322
|
+
await runtime.store.add(queued);
|
|
323
|
+
return id;
|
|
324
|
+
},
|
|
325
|
+
transport: runtime.eventTransport,
|
|
326
|
+
capture: runtime.tracing ? () => runtime.tracing.capture() : undefined,
|
|
327
|
+
logger: runtime.logger,
|
|
328
|
+
});
|
|
288
329
|
const jobs = {
|
|
289
330
|
list,
|
|
290
331
|
crons,
|
|
332
|
+
events,
|
|
291
333
|
queueCrons,
|
|
292
334
|
queue: runtime.queue ?? "memory",
|
|
293
335
|
async enqueue(j, input, options = {}) {
|
|
@@ -295,7 +337,13 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
295
337
|
if (options.runAt !== undefined && (!(options.runAt instanceof Date) || Number.isNaN(options.runAt.getTime()))) {
|
|
296
338
|
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
339
|
}
|
|
340
|
+
const tenantId = options.tenantId === undefined || options.tenantId === null ? undefined : checkTenantId(options.tenantId);
|
|
298
341
|
const queued = record(def, input, options.runAt, crypto.randomUUID());
|
|
342
|
+
const trace = runtime.tracing?.capture();
|
|
343
|
+
if (trace?.traceparent)
|
|
344
|
+
queued.trace = trace;
|
|
345
|
+
if (tenantId)
|
|
346
|
+
queued.tenantId = tenantId;
|
|
299
347
|
await runtime.store.add(queued);
|
|
300
348
|
return queued;
|
|
301
349
|
},
|
|
@@ -325,6 +373,9 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
325
373
|
await queueCrons(cronFrom, now);
|
|
326
374
|
cronFrom = now;
|
|
327
375
|
}
|
|
376
|
+
// Events from other services (createApp({ events: { transport } })) queue their jobs first.
|
|
377
|
+
if (events.transport)
|
|
378
|
+
await events.receive().catch((error) => runtime.logger.error("event receive error", error));
|
|
328
379
|
await jobs.runDue();
|
|
329
380
|
}
|
|
330
381
|
catch (error) {
|
|
@@ -355,22 +406,26 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
355
406
|
workers.set(jobs, running);
|
|
356
407
|
async function run(record, now) {
|
|
357
408
|
const started = performance.now();
|
|
358
|
-
const outcome = await attempt(record, now);
|
|
409
|
+
const outcome = runtime.tracing ? await runtime.tracing.run(record, (fail) => attempt(record, now, fail)) : await attempt(record, now);
|
|
359
410
|
runtime.onRun?.(record.name, outcome, performance.now() - started);
|
|
360
411
|
return outcome;
|
|
361
412
|
}
|
|
362
|
-
async function attempt(record, now) {
|
|
413
|
+
async function attempt(record, now, fail) {
|
|
363
414
|
const def = byName.get(record.name);
|
|
364
415
|
const touch = runtime.store.touch?.bind(runtime.store);
|
|
365
416
|
const heartbeat = touch ? setInterval(() => void touch(record.id).catch(() => { }), runtime.heartbeatMs ?? HEARTBEAT_MS) : undefined;
|
|
366
417
|
try {
|
|
367
418
|
if (!def)
|
|
368
419
|
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
|
-
|
|
420
|
+
const tenantId = record.tenantId ?? null;
|
|
421
|
+
const scoped = tenantId ? withTenant({ jobs, events }, tenantId) : { jobs, events };
|
|
422
|
+
const db = tenantId ? runtime.db.$tenant(tenantId) : runtime.db;
|
|
423
|
+
await def.handler({ input: parse(def, record.input), db, ai: runtime.ai, jobs: scoped.jobs, events: scoped.events, cache: runtime.cache, id: record.id, attempt: record.attempts, tenantId, audit: runtime.audit({ name: record.name, id: record.id, tenantId }) });
|
|
370
424
|
await runtime.store.update(record.id, { status: "done" });
|
|
371
425
|
return "done";
|
|
372
426
|
}
|
|
373
427
|
catch (error) {
|
|
428
|
+
fail?.(error);
|
|
374
429
|
const message = error instanceof Error ? error.message : String(error);
|
|
375
430
|
if (record.attempts < record.maxAttempts) {
|
|
376
431
|
await runtime.store.update(record.id, { status: "pending", lastError: message, runAt: new Date(now.getTime() + retryDelayMs(record.attempts)) });
|
|
@@ -388,4 +443,14 @@ export function createJobs(list, runtime, crons = []) {
|
|
|
388
443
|
}
|
|
389
444
|
return jobs;
|
|
390
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* ctx.jobs and ctx.events for one tenant: jobs and events queued through them carry the tenant unless the call
|
|
448
|
+
* names another (tenantId) or none (null). Internal: used for requests and jobs with a tenant.
|
|
449
|
+
*/
|
|
450
|
+
export function withTenant(base, tenantId) {
|
|
451
|
+
return {
|
|
452
|
+
jobs: { enqueue: (job, input, options = {}) => base.jobs.enqueue(job, input, { ...options, tenantId: options.tenantId === undefined ? tenantId : options.tenantId }) },
|
|
453
|
+
events: { publish: (event, payload, options = {}) => base.events.publish(event, payload, { ...options, tenantId: options.tenantId === undefined ? tenantId : options.tenantId }) },
|
|
454
|
+
};
|
|
455
|
+
}
|
|
391
456
|
//# sourceMappingURL=jobs.js.map
|