@browserlessapi/inferlo 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +130 -2
  3. package/dist/ai/index.d.ts +138 -0
  4. package/dist/ai/index.js +450 -0
  5. package/dist/ai/index.js.map +1 -0
  6. package/dist/ai/providers.d.ts +150 -0
  7. package/dist/ai/providers.js +294 -0
  8. package/dist/ai/providers.js.map +1 -0
  9. package/dist/app.d.ts +109 -0
  10. package/dist/app.js +419 -0
  11. package/dist/app.js.map +1 -0
  12. package/dist/auth.d.ts +29 -0
  13. package/dist/auth.js +61 -0
  14. package/dist/auth.js.map +1 -0
  15. package/dist/cli/main.d.ts +2 -0
  16. package/dist/cli/main.js +520 -0
  17. package/dist/cli/main.js.map +1 -0
  18. package/dist/client-ip.d.ts +7 -0
  19. package/dist/client-ip.js +27 -0
  20. package/dist/client-ip.js.map +1 -0
  21. package/dist/cron.d.ts +17 -0
  22. package/dist/cron.js +143 -0
  23. package/dist/cron.js.map +1 -0
  24. package/dist/db/memory.d.ts +2 -0
  25. package/dist/db/memory.js +128 -0
  26. package/dist/db/memory.js.map +1 -0
  27. package/dist/db/migrate.d.ts +87 -0
  28. package/dist/db/migrate.js +471 -0
  29. package/dist/db/migrate.js.map +1 -0
  30. package/dist/db/postgres.d.ts +37 -0
  31. package/dist/db/postgres.js +338 -0
  32. package/dist/db/postgres.js.map +1 -0
  33. package/dist/db/table.d.ts +117 -0
  34. package/dist/db/table.js +274 -0
  35. package/dist/db/table.js.map +1 -0
  36. package/dist/db/types.d.ts +36 -0
  37. package/dist/db/types.js +2 -0
  38. package/dist/db/types.js.map +1 -0
  39. package/dist/describe.d.ts +74 -0
  40. package/dist/describe.js +69 -0
  41. package/dist/describe.js.map +1 -0
  42. package/dist/env.d.ts +19 -0
  43. package/dist/env.js +72 -0
  44. package/dist/env.js.map +1 -0
  45. package/dist/errors.d.ts +40 -0
  46. package/dist/errors.js +99 -0
  47. package/dist/errors.js.map +1 -0
  48. package/dist/index.d.ts +31 -0
  49. package/dist/index.js +20 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/jobs.d.ts +177 -0
  52. package/dist/jobs.js +374 -0
  53. package/dist/jobs.js.map +1 -0
  54. package/dist/mcp.d.ts +22 -0
  55. package/dist/mcp.js +171 -0
  56. package/dist/mcp.js.map +1 -0
  57. package/dist/model.d.ts +36 -0
  58. package/dist/model.js +76 -0
  59. package/dist/model.js.map +1 -0
  60. package/dist/openapi.d.ts +6 -0
  61. package/dist/openapi.js +74 -0
  62. package/dist/openapi.js.map +1 -0
  63. package/dist/rate-limit.d.ts +45 -0
  64. package/dist/rate-limit.js +93 -0
  65. package/dist/rate-limit.js.map +1 -0
  66. package/dist/route.d.ts +131 -0
  67. package/dist/route.js +28 -0
  68. package/dist/route.js.map +1 -0
  69. package/dist/schema.d.ts +164 -0
  70. package/dist/schema.js +442 -0
  71. package/dist/schema.js.map +1 -0
  72. package/dist/serve.d.ts +11 -0
  73. package/dist/serve.js +86 -0
  74. package/dist/serve.js.map +1 -0
  75. package/dist/testing.d.ts +16 -0
  76. package/dist/testing.js +30 -0
  77. package/dist/testing.js.map +1 -0
  78. package/llms.txt +27 -0
  79. package/package.json +82 -4
  80. package/templates/app/AGENTS.md +55 -0
  81. package/templates/app/CLAUDE.md +1 -0
  82. package/templates/app/README.md +16 -0
  83. package/templates/app/_env.example +5 -0
  84. package/templates/app/_gitignore +3 -0
  85. package/templates/app/package.json.tpl +32 -0
  86. package/templates/app/src/app.ts +38 -0
  87. package/templates/app/src/models.ts +31 -0
  88. package/templates/app/src/routes/health.ts +3 -0
  89. package/templates/app/src/routes/notes.ts +56 -0
  90. package/templates/app/src/server.ts +26 -0
  91. package/templates/app/test/notes.test.ts +50 -0
  92. package/templates/app/tsconfig.json +17 -0
package/dist/errors.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Every error Inferlo raises has a stable code, a plain cause, a suggested fix and a docs link.
3
+ * Error codes never change meaning once released, so AI models and humans can rely on them.
4
+ */
5
+ export const DOCS_BASE = "https://github.com/BrowserlessAPI/Inferlo/blob/main/docs/errors.md";
6
+ export function docsUrl(code) {
7
+ return `${DOCS_BASE}#${code.toLowerCase()}`;
8
+ }
9
+ export class InferloError extends Error {
10
+ code;
11
+ status;
12
+ fix;
13
+ details;
14
+ expose;
15
+ constructor(code, message, options = {}) {
16
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
17
+ this.name = `InferloError[${code}]`;
18
+ this.code = code;
19
+ this.status = options.status ?? 500;
20
+ this.fix = options.fix;
21
+ this.details = options.details;
22
+ this.expose = options.expose ?? this.status < 500;
23
+ }
24
+ get docs() {
25
+ return docsUrl(this.code);
26
+ }
27
+ toJSON() {
28
+ const error = { code: this.code, message: this.expose ? this.message : "Internal server error" };
29
+ if (this.expose && this.fix)
30
+ error.fix = this.fix;
31
+ if (this.expose && this.details !== undefined)
32
+ error.details = this.details;
33
+ error.docs = this.docs;
34
+ return { error };
35
+ }
36
+ /** Multi-line text for terminals and logs. */
37
+ format() {
38
+ const lines = [`${this.code} ${this.message}`];
39
+ if (this.fix)
40
+ lines.push(`Fix: ${this.fix}`);
41
+ lines.push(`Docs: ${this.docs}`);
42
+ return lines.join("\n");
43
+ }
44
+ }
45
+ /** Throw from a handler to return an HTTP error with your own code and message. */
46
+ export function httpError(status, message, options = {}) {
47
+ return new InferloError(options.code ?? `HTTP${status}`, message, { status, fix: options.fix, details: options.details, expose: true });
48
+ }
49
+ export function notFound(message = "Not found", details) {
50
+ return new InferloError("IL4001", message, { status: 404, details });
51
+ }
52
+ export function forbidden(message = "You do not have permission to do this.") {
53
+ return new InferloError("IL3002", message, { status: 403 });
54
+ }
55
+ export function badRequest(message, details) {
56
+ return new InferloError("HTTP400", message, { status: 400, details });
57
+ }
58
+ /** Closest candidate by edit distance, used for "did you mean" hints. */
59
+ export function suggest(input, candidates) {
60
+ let best;
61
+ let bestScore = Infinity;
62
+ const lower = input.toLowerCase();
63
+ for (const candidate of candidates) {
64
+ const c = candidate.toLowerCase();
65
+ let score = distance(lower, c);
66
+ if (c.startsWith(lower) || lower.startsWith(c))
67
+ score = Math.min(score, 1);
68
+ if (score < bestScore) {
69
+ bestScore = score;
70
+ best = candidate;
71
+ }
72
+ }
73
+ if (best === undefined)
74
+ return undefined;
75
+ const limit = Math.max(2, Math.floor(input.length / 3));
76
+ return bestScore <= limit ? best : undefined;
77
+ }
78
+ function distance(a, b) {
79
+ if (a === b)
80
+ return 0;
81
+ const prev = new Array(b.length + 1);
82
+ for (let j = 0; j <= b.length; j++)
83
+ prev[j] = j;
84
+ for (let i = 1; i <= a.length; i++) {
85
+ let diag = prev[0];
86
+ prev[0] = i;
87
+ for (let j = 1; j <= b.length; j++) {
88
+ const above = prev[j];
89
+ prev[j] = Math.min(above + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
90
+ diag = above;
91
+ }
92
+ }
93
+ return prev[b.length];
94
+ }
95
+ export function didYouMean(input, candidates) {
96
+ const match = suggest(input, candidates);
97
+ return match ? ` Did you mean "${match}"?` : "";
98
+ }
99
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,oEAAoE,CAAC;AAE9F,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,GAAG,SAAS,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;AAC9C,CAAC;AAWD,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,CAAS;IACb,MAAM,CAAS;IACf,GAAG,CAAqB;IACxB,OAAO,CAAU;IACjB,MAAM,CAAU;IAEzB,YAAY,IAAY,EAAE,OAAe,EAAE,UAA+B,EAAE;QAC1E,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,gBAAgB,IAAI,GAAG,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC;QACpC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IACpD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM;QACJ,MAAM,KAAK,GAA4B,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;QAC1H,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG;YAAE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QAClD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5E,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,8CAA8C;IAC9C,MAAM;QACJ,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,OAAe,EAAE,UAA8D,EAAE;IACzH,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1I,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAO,GAAG,WAAW,EAAE,OAAiB;IAC/D,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAO,GAAG,wCAAwC;IAC1E,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAiB;IAC3D,OAAO,IAAI,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,UAA4B;IACjE,IAAI,IAAwB,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QAClC,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC3E,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;YACtB,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/C,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IACpC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACvB,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACxF,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,MAAM,CAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,UAA4B;IACpE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,CAAC,CAAC,kBAAkB,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Inferlo: the TypeScript backend framework with built-in AI model support.
3
+ * https://github.com/BrowserlessAPI/Inferlo
4
+ */
5
+ export { createApp } from "./app.ts";
6
+ export type { App, AppConfig, AuthConfig, CorsConfig, Middleware, MiddlewareContext, RequestOptions, RouteSource, Logger } from "./app.ts";
7
+ export { route, isRoute } from "./route.ts";
8
+ export type { Register, Context, RouteDef, RouteOptions, Auth, Method, ToolOptions, AppUser, AppDb } from "./route.ts";
9
+ export { t, Schema, ObjectSchema, ArraySchema, RefSchema, ValidationError, toJSONSchema } from "./schema.ts";
10
+ export type { Infer, InferInput, Issue, JSONSchema, ParseResult, Shape, AnySchema } from "./schema.ts";
11
+ export { model, Model, columnName } from "./model.ts";
12
+ export type { Row, CreateInput, UpdateInput, AnyModel } from "./model.ts";
13
+ export { memory } from "./db/memory.ts";
14
+ export { createDb, Table } from "./db/table.ts";
15
+ export type { Db, Where, OrderBy, FindManyOptions, FindOptions, Include, Relations, ReverseRelations, WithInclude } from "./db/table.ts";
16
+ export type { Adapter, Condition, Query, DbRecord } from "./db/types.ts";
17
+ export { planMigration, applyMigrations } from "./db/migrate.ts";
18
+ export type { ApplyMigrationsOptions, Migration, MigrationPlan, MigrationSnapshot, MigrationSnapshotColumn, MigrationSnapshotV1, PlanMigrationOptions, } from "./db/migrate.ts";
19
+ export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.ts";
20
+ export type { AI, AIConfig, GenerateOptions, GenerateResult, StreamResult, Usage, Pricing, Provider, ProviderRequest, ProviderResponse, Image, ImageMediaType, Message, MockReply, Step, ToolCall, ToolCallResult, ToolSpec, } from "./ai/index.ts";
21
+ export type { RateLimit, RateLimitConfig } from "./rate-limit.ts";
22
+ export { job, isJob, retryDelayMs } from "./jobs.ts";
23
+ export { cron, isCron } from "./cron.ts";
24
+ export type { CronDef } from "./cron.ts";
25
+ export type { JobDef, JobOptions, JobContext, JobInput, JobRecord, JobStatus, JobSource, JobQueueConfig, Jobs, AppJobs, EnqueueOptions, RunResult, Worker } from "./jobs.ts";
26
+ export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.ts";
27
+ export { env, setEnv } from "./env.ts";
28
+ export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.ts";
29
+ export { serve } from "./serve.ts";
30
+ export { INFERLO_VERSION } from "./describe.ts";
31
+ export type { AppDescription } from "./describe.ts";
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Inferlo: the TypeScript backend framework with built-in AI model support.
3
+ * https://github.com/BrowserlessAPI/Inferlo
4
+ */
5
+ export { createApp } from "./app.js";
6
+ export { route, isRoute } from "./route.js";
7
+ export { t, Schema, ObjectSchema, ArraySchema, RefSchema, ValidationError, toJSONSchema } from "./schema.js";
8
+ export { model, Model, columnName } from "./model.js";
9
+ export { memory } from "./db/memory.js";
10
+ export { createDb, Table } from "./db/table.js";
11
+ export { planMigration, applyMigrations } from "./db/migrate.js";
12
+ export { createAI, cosineSimilarity, extractJson, openai, openaiCompatible, anthropic, gemini, ollama, mockProvider, ProviderError } from "./ai/index.js";
13
+ export { job, isJob, retryDelayMs } from "./jobs.js";
14
+ export { cron, isCron } from "./cron.js";
15
+ export { bearer, hashToken, createToken, hashPassword, verifyPassword, safeEqual } from "./auth.js";
16
+ export { env, setEnv } from "./env.js";
17
+ export { InferloError, httpError, notFound, forbidden, badRequest, docsUrl } from "./errors.js";
18
+ export { serve } from "./serve.js";
19
+ export { INFERLO_VERSION } from "./describe.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAGrC,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;AAuB1J,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACpG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAChG,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
package/dist/jobs.d.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Background jobs: work that runs after the request, with retries.
3
+ *
4
+ * export const sendWelcome = job("send-welcome", { input: t.object({ userId: t.uuid() }) })
5
+ * .handle(async ({ input, db }) => { ... })
6
+ *
7
+ * createApp({ jobs: [jobs] }) // import * as jobs from "./jobs.ts"
8
+ * await ctx.jobs.enqueue(sendWelcome, { userId }) // in a route handler, validated
9
+ *
10
+ * Jobs are plain values like routes. The queue is kept by a JobStore; app.jobs.start() runs due
11
+ * jobs in this process, app.jobs.runDue() runs them once (tests).
12
+ */
13
+ import type { AI } from "./ai/index.ts";
14
+ import { type CronDef } from "./cron.ts";
15
+ import type { Adapter } from "./db/types.ts";
16
+ import type { AppDb } from "./route.ts";
17
+ import { type AnySchema, type Infer, type InferInput } from "./schema.ts";
18
+ export interface JobOptions<I extends AnySchema> {
19
+ /** Schema of the job's input. Checked when the job is enqueued and again when it runs. */
20
+ input: I;
21
+ /** Extra attempts after a failure, with growing delays (10s, 20s, 40s, ... up to 1 hour). Default 3. */
22
+ retries?: number;
23
+ /** One line shown in describe. */
24
+ summary?: string;
25
+ }
26
+ export interface JobContext<Input> {
27
+ /** The validated input. */
28
+ input: Input;
29
+ db: AppDb;
30
+ ai: AI;
31
+ /** Enqueue follow-up jobs. */
32
+ jobs: Jobs;
33
+ /** Job id. */
34
+ id: string;
35
+ /** 1 on the first run, 2 on the first retry, and so on. */
36
+ attempt: number;
37
+ }
38
+ export interface JobDef<Input = any, Output = any> {
39
+ readonly kind: "inferlo.job";
40
+ readonly name: string;
41
+ readonly options: JobOptions<AnySchema>;
42
+ readonly handler: (ctx: JobContext<Output>) => unknown;
43
+ /** Type only: what enqueue accepts. */
44
+ readonly $input?: Input;
45
+ }
46
+ /** What enqueue accepts for a job. */
47
+ export type JobInput<J> = J extends JobDef<infer I, any> ? I : never;
48
+ export interface EnqueueOptions {
49
+ /** Run no earlier than this time. Default: now. */
50
+ runAt?: Date;
51
+ }
52
+ export type JobStatus = "pending" | "running" | "done" | "failed";
53
+ export interface JobRecord {
54
+ id: string;
55
+ name: string;
56
+ /** The input as stored (JSON). */
57
+ input: unknown;
58
+ status: JobStatus;
59
+ /** Runs started so far. */
60
+ attempts: number;
61
+ /** 1 + retries. */
62
+ maxAttempts: number;
63
+ /** When the job runs next (pending) or last ran. */
64
+ runAt: Date;
65
+ /** Message of the last failure. */
66
+ lastError?: string;
67
+ createdAt: Date;
68
+ updatedAt: Date;
69
+ }
70
+ export interface Jobs {
71
+ /** Validate the input and queue the job. Returns the queued record. */
72
+ enqueue<J extends JobDef>(job: J, input: JobInput<J>, options?: EnqueueOptions): Promise<JobRecord>;
73
+ }
74
+ export interface JobQueueConfig {
75
+ /**
76
+ * Where queued jobs are kept. "memory" (default): in this process, for development and tests.
77
+ * "database": in the inferlo_jobs table of the app's Postgres database, shared by every instance.
78
+ */
79
+ store?: "memory" | "database";
80
+ }
81
+ /** Where queued jobs are kept. */
82
+ export interface JobStore {
83
+ /** Add a record. A record whose id is already stored is ignored (cron ticks use fixed ids). */
84
+ add(record: JobRecord): Promise<void>;
85
+ /** Mark up to limit pending jobs named in names with runAt <= now as running (attempts + 1) and return them, oldest first. */
86
+ claim(now: Date, limit: number, names: readonly string[]): Promise<JobRecord[]>;
87
+ update(id: string, patch: Partial<Pick<JobRecord, "status" | "runAt" | "lastError">>): Promise<void>;
88
+ get(id: string): Promise<JobRecord | null>;
89
+ /** Record that a running job is still alive (database store), so it is not picked up again as stuck. */
90
+ touch?(id: string): Promise<void>;
91
+ }
92
+ export interface RunResult {
93
+ /** Jobs that finished. */
94
+ done: number;
95
+ /** Jobs that failed and will run again later. */
96
+ retried: number;
97
+ /** Jobs that failed for the last time. */
98
+ failed: number;
99
+ }
100
+ export interface Worker {
101
+ /** Stop polling and wait for running jobs to finish. */
102
+ stop(): Promise<void>;
103
+ }
104
+ /** app.jobs: enqueue, inspect and run jobs. */
105
+ export interface AppJobs extends Jobs {
106
+ /** The registered jobs. */
107
+ readonly list: readonly JobDef[];
108
+ /** The registered crons: cron(schedule, job) values in createApp({ jobs }). */
109
+ readonly crons: readonly CronDef[];
110
+ /** Where queued jobs are kept (createApp({ jobQueue: { store } })). */
111
+ readonly queue: "memory" | "database";
112
+ /** A queued job by id, or null. */
113
+ get(id: string): Promise<JobRecord | null>;
114
+ /** 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
+ runDue(options?: {
116
+ now?: Date;
117
+ }): Promise<RunResult>;
118
+ /** Run due jobs in this process every pollMs (default 1000) until stop() is called. Also queues each cron's job at every tick after the start. */
119
+ start(options?: {
120
+ pollMs?: number;
121
+ }): Worker;
122
+ }
123
+ /** Delay before retry n (1-based): 10s, 20s, 40s, ... at most one hour. */
124
+ export declare function retryDelayMs(attempt: number): number;
125
+ export declare class JobBuilder<I extends AnySchema> {
126
+ readonly name: string;
127
+ readonly options: JobOptions<I>;
128
+ constructor(name: string, options: JobOptions<I>);
129
+ /** The job's work. Throw to fail the attempt; it is retried until retries run out. */
130
+ handle(handler: (ctx: JobContext<Infer<I>>) => unknown): JobDef<InferInput<I>, Infer<I>>;
131
+ }
132
+ /** Define a background job: job("send-email", { input: t.object({ to: t.email() }) }).handle(fn). */
133
+ export declare function job<I extends AnySchema>(name: string, options: JobOptions<I>): JobBuilder<I>;
134
+ export declare function isJob(value: unknown): value is JobDef;
135
+ /** Anything that contains jobs: a job, a cron, an array, or a module (import * as jobs from "./jobs.ts"). */
136
+ export type JobSource = JobDef | CronDef | readonly JobSource[] | {
137
+ readonly [name: string]: unknown;
138
+ };
139
+ /** The jobs and crons in createApp({ jobs }). A cron's job is registered with it. */
140
+ export declare function collectJobs(sources: readonly JobSource[] | undefined): JobDef[];
141
+ export declare function collectCrons(sources: readonly JobSource[] | undefined): CronDef[];
142
+ /** Jobs kept in this process. They are lost when it stops, so use it for development and tests. */
143
+ export declare function memoryJobStore(): JobStore;
144
+ /** A running job whose worker has not reported for this long is treated as stuck (its process stopped) and run again. */
145
+ export declare const STALE_AFTER_MS: number;
146
+ /** How often a worker reports that a running job is still alive. */
147
+ export declare const HEARTBEAT_MS = 30000;
148
+ export declare const STOPPED_MESSAGE = "The worker stopped while running this job (the process exited or lost its database connection).";
149
+ /**
150
+ * Jobs in the inferlo_jobs table (created on first use), shared by every instance. Due jobs are
151
+ * claimed with FOR UPDATE SKIP LOCKED, so each one runs in one place. A running job whose worker
152
+ * stopped reporting (see STALE_AFTER_MS) is claimed again, or marked failed if it has no attempts left.
153
+ */
154
+ export declare function databaseJobStore(adapter: Adapter, options?: {
155
+ staleAfterMs?: number;
156
+ }): JobStore;
157
+ /** Pick the job store for createApp({ jobQueue }). Raises IL6011 for an unknown store, IL6012 for "database" without Postgres. */
158
+ export declare function jobStoreFor(config: JobQueueConfig | undefined, adapter: Adapter): JobStore;
159
+ export interface JobRuntime {
160
+ db: AppDb;
161
+ ai: AI;
162
+ store: JobStore;
163
+ /** Name of the store, shown as app.jobs.queue. Default "memory". */
164
+ queue?: "memory" | "database";
165
+ /** How often a running job is reported alive to stores that support it. Default HEARTBEAT_MS. */
166
+ heartbeatMs?: number;
167
+ logger: {
168
+ info(message: string, data?: unknown): void;
169
+ error(message: string, data?: unknown): void;
170
+ };
171
+ }
172
+ /** app.jobs plus what the framework tests reach directly. */
173
+ export interface InternalJobs extends AppJobs {
174
+ /** Queue the job of each cron's latest tick in (from, to]. */
175
+ queueCrons(from: Date, to: Date): Promise<void>;
176
+ }
177
+ export declare function createJobs(list: readonly JobDef[], runtime: JobRuntime, crons?: readonly CronDef[]): InternalJobs;