@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/module.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature modules split a large app into parts with explicit dependencies. A module is a plain value:
|
|
3
|
+
* its models, routes and jobs, the modules it depends on, and what it offers other modules.
|
|
4
|
+
*
|
|
5
|
+
* export const billing = module("billing", {
|
|
6
|
+
* imports: [users], // modules this one uses
|
|
7
|
+
* models: [invoices],
|
|
8
|
+
* routes: [invoiceRoutes], // import * as invoiceRoutes from "./routes.ts"
|
|
9
|
+
* jobs: [sendInvoice],
|
|
10
|
+
* provides: { createInvoice }, // what other modules may use: billing.provides.createInvoice
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* createApp({ modules: [users, billing] })
|
|
14
|
+
*
|
|
15
|
+
* No decorators, no container, no injection: other modules import the module value and call
|
|
16
|
+
* billing.provides.createInvoice(...) directly. createApp checks that every imported module is
|
|
17
|
+
* registered and that models only reference models of their own module, an imported module or the
|
|
18
|
+
* app itself, and describe() shows each module with what it contains.
|
|
19
|
+
*/
|
|
20
|
+
import { InferloError, didYouMean } from "./errors.js";
|
|
21
|
+
const MODULE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
|
|
22
|
+
const OPTION_KEYS = new Set(["description", "imports", "models", "routes", "jobs", "provides"]);
|
|
23
|
+
function invalid(message, fix) {
|
|
24
|
+
return new InferloError("IL6022", message, { fix });
|
|
25
|
+
}
|
|
26
|
+
function isList(value) {
|
|
27
|
+
return Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Define a feature module: module("billing", { imports, models, routes, jobs, provides }).
|
|
31
|
+
* Register it with createApp({ modules: [billing] }).
|
|
32
|
+
*/
|
|
33
|
+
export function module(name, options = {}) {
|
|
34
|
+
if (typeof name !== "string" || !MODULE_NAME.test(name)) {
|
|
35
|
+
throw invalid(`Module name ${JSON.stringify(name)} is not valid.`, 'Use lowercase letters, digits and dashes, starting with a letter: module("billing", { ... }).');
|
|
36
|
+
}
|
|
37
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
38
|
+
throw invalid(`module("${name}") options must be an object.`, `Use module("${name}", { models, routes, jobs, imports, provides }).`);
|
|
39
|
+
}
|
|
40
|
+
for (const key of Object.keys(options)) {
|
|
41
|
+
if (!OPTION_KEYS.has(key)) {
|
|
42
|
+
throw invalid(`module("${name}") has an unknown option "${key}".${didYouMean(key, OPTION_KEYS)}`, "A module takes description, imports, models, routes, jobs and provides.");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const key of ["imports", "models", "routes", "jobs"]) {
|
|
46
|
+
if (options[key] !== undefined && !isList(options[key])) {
|
|
47
|
+
throw invalid(`module("${name}") ${key} must be an array.`, `Use module("${name}", { ${key}: [...] }).`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (options.description !== undefined && typeof options.description !== "string") {
|
|
51
|
+
throw invalid(`module("${name}") description must be a string.`, "Describe the module in one sentence.");
|
|
52
|
+
}
|
|
53
|
+
const imports = options.imports ?? [];
|
|
54
|
+
for (const [i, dep] of imports.entries()) {
|
|
55
|
+
if (!isModule(dep)) {
|
|
56
|
+
throw invalid(`module("${name}") imports[${i}] is not a module.`, "List module values created with module(), for example imports: [users].");
|
|
57
|
+
}
|
|
58
|
+
if (dep.name === name)
|
|
59
|
+
throw invalid(`module("${name}") imports itself.`, `Remove "${name}" from its own imports.`);
|
|
60
|
+
}
|
|
61
|
+
if (new Set(imports.map((m) => m.name)).size !== imports.length) {
|
|
62
|
+
throw invalid(`module("${name}") imports the same module twice.`, "List each module once in imports.");
|
|
63
|
+
}
|
|
64
|
+
for (const [i, m] of (options.models ?? []).entries()) {
|
|
65
|
+
if (typeof m !== "object" || m === null || typeof m.name !== "string" || typeof m.fields !== "object") {
|
|
66
|
+
throw invalid(`module("${name}") models[${i}] is not a model.`, 'List models created with model("table", { ... }).');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const provides = options.provides ?? {};
|
|
70
|
+
if (typeof provides !== "object" || provides === null || Array.isArray(provides)) {
|
|
71
|
+
throw invalid(`module("${name}") provides must be an object.`, `Use provides: { createInvoice } and call ${name}.provides.createInvoice(...) from other modules.`);
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
kind: "inferlo.module",
|
|
75
|
+
name,
|
|
76
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
77
|
+
imports: Object.freeze([...imports]),
|
|
78
|
+
models: Object.freeze([...(options.models ?? [])]),
|
|
79
|
+
routes: Object.freeze([...(options.routes ?? [])]),
|
|
80
|
+
jobs: Object.freeze([...(options.jobs ?? [])]),
|
|
81
|
+
provides: Object.freeze({ ...provides }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function isModule(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && value.kind === "inferlo.module";
|
|
86
|
+
}
|
|
87
|
+
function wiring(message, fix) {
|
|
88
|
+
return new InferloError("IL6023", message, { fix });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Checks createApp({ modules }) and returns the modules with each model's owner. Every imported
|
|
92
|
+
* module must be registered, names are unique, and a model may only reference models of its own
|
|
93
|
+
* module, of a module it imports, or of the app itself (createApp({ models })).
|
|
94
|
+
*/
|
|
95
|
+
export function resolveModules(option, appModels) {
|
|
96
|
+
if (option === undefined)
|
|
97
|
+
return { modules: [], modelOwner: new Map() };
|
|
98
|
+
if (!isList(option))
|
|
99
|
+
throw wiring("createApp({ modules }) must be an array of modules.", "Use createApp({ modules: [users, billing] }).");
|
|
100
|
+
const byName = new Map();
|
|
101
|
+
for (const [i, m] of option.entries()) {
|
|
102
|
+
if (!isModule(m))
|
|
103
|
+
throw wiring(`createApp({ modules }) item ${i} is not a module.`, 'Create modules with module("name", { ... }) and pass the values: modules: [users, billing].');
|
|
104
|
+
const existing = byName.get(m.name);
|
|
105
|
+
if (existing === m)
|
|
106
|
+
throw wiring(`The module "${m.name}" is listed twice in createApp({ modules }).`, "List each module once.");
|
|
107
|
+
if (existing)
|
|
108
|
+
throw wiring(`Two modules are named "${m.name}".`, "Module names must be unique.");
|
|
109
|
+
byName.set(m.name, m);
|
|
110
|
+
}
|
|
111
|
+
for (const m of byName.values()) {
|
|
112
|
+
for (const dep of m.imports) {
|
|
113
|
+
const registered = byName.get(dep.name);
|
|
114
|
+
if (!registered) {
|
|
115
|
+
throw wiring(`The module "${m.name}" imports "${dep.name}", which is not in createApp({ modules }).`, `Add ${dep.name} to createApp({ modules: [...] }).`);
|
|
116
|
+
}
|
|
117
|
+
if (registered !== dep)
|
|
118
|
+
throw wiring(`The module "${m.name}" imports a different module also named "${dep.name}".`, "Import the same module value that is passed to createApp({ modules }).");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const modelOwner = new Map();
|
|
122
|
+
for (const m of byName.values()) {
|
|
123
|
+
for (const model of m.models) {
|
|
124
|
+
const owner = modelOwner.get(model.name);
|
|
125
|
+
if (owner !== undefined) {
|
|
126
|
+
throw wiring(`The model "${model.name}" is in the modules "${owner}" and "${m.name}".`, "A model belongs to one module; other modules import that module to use it.");
|
|
127
|
+
}
|
|
128
|
+
modelOwner.set(model.name, m.name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const model of appModels) {
|
|
132
|
+
const owner = modelOwner.get(model.name);
|
|
133
|
+
if (owner !== undefined)
|
|
134
|
+
throw wiring(`The model "${model.name}" is in createApp({ models }) and in the module "${owner}".`, "List the model in one place only.");
|
|
135
|
+
}
|
|
136
|
+
for (const m of byName.values()) {
|
|
137
|
+
const allowed = new Set([m.name, ...m.imports.map((d) => d.name)]);
|
|
138
|
+
for (const model of m.models) {
|
|
139
|
+
for (const [field, schema] of Object.entries(model.fields)) {
|
|
140
|
+
const ref = schema.def.refTable;
|
|
141
|
+
const owner = ref ? modelOwner.get(ref) : undefined;
|
|
142
|
+
if (owner !== undefined && !allowed.has(owner)) {
|
|
143
|
+
throw new InferloError("IL6024", `${model.name}.${field} in the module "${m.name}" references "${ref}" from the module "${owner}", which "${m.name}" does not import.`, {
|
|
144
|
+
fix: `Add ${owner} to the imports of the "${m.name}" module: module("${m.name}", { imports: [${owner}], ... }).`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { modules: [...byName.values()], modelOwner };
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"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"}
|
package/dist/nextjs.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inferlo inside Next.js App Router route handlers (Next.js 15 and 16), without a runtime dependency on Next.js:
|
|
3
|
+
*
|
|
4
|
+
* // app/api/[[...path]]/route.ts
|
|
5
|
+
* import { toNextHandlers } from "@browserlessapi/inferlo/nextjs";
|
|
6
|
+
* import { app } from "@/inferlo/app";
|
|
7
|
+
*
|
|
8
|
+
* export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS } = toNextHandlers(app, { prefix: "/api" });
|
|
9
|
+
*
|
|
10
|
+
* Every request under /api runs through the Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
|
|
11
|
+
* with /api removed from the path. In server actions and other server code, use @browserlessapi/inferlo/ai.
|
|
12
|
+
* It never loads the router, database or jobs code.
|
|
13
|
+
*/
|
|
14
|
+
import type { App } from "./app.ts";
|
|
15
|
+
/** A Next.js route handler: it takes the Request (a NextRequest in Next.js) and returns a Response. */
|
|
16
|
+
export type NextRouteHandler = (request: Request, context?: unknown) => Promise<Response>;
|
|
17
|
+
/** One route handler per HTTP method, to export from an app/.../route.ts file. */
|
|
18
|
+
export interface NextHandlers {
|
|
19
|
+
GET: NextRouteHandler;
|
|
20
|
+
POST: NextRouteHandler;
|
|
21
|
+
PUT: NextRouteHandler;
|
|
22
|
+
PATCH: NextRouteHandler;
|
|
23
|
+
DELETE: NextRouteHandler;
|
|
24
|
+
HEAD: NextRouteHandler;
|
|
25
|
+
OPTIONS: NextRouteHandler;
|
|
26
|
+
}
|
|
27
|
+
export interface NextHandlersOptions {
|
|
28
|
+
/**
|
|
29
|
+
* The URL path of the route.ts folder, removed before the app routes the request: "/api" for
|
|
30
|
+
* app/api/[[...path]]/route.ts. Requests outside it keep their path. Include Next.js's basePath if one is set.
|
|
31
|
+
*/
|
|
32
|
+
prefix?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Route handlers that run a whole Inferlo app from a Next.js catch-all route: export const { GET, POST } = toNextHandlers(app). */
|
|
35
|
+
export declare function toNextHandlers(app: Pick<App<any>, "fetch">, options?: NextHandlersOptions): NextHandlers;
|
package/dist/nextjs.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { InferloError } from "./errors.js";
|
|
2
|
+
const fix = 'Pass the app from createApp and a path prefix: export const { GET, POST } = toNextHandlers(app, { prefix: "/api" }).';
|
|
3
|
+
/** Route handlers that run a whole Inferlo app from a Next.js catch-all route: export const { GET, POST } = toNextHandlers(app). */
|
|
4
|
+
export function toNextHandlers(app, options = {}) {
|
|
5
|
+
if (!app || typeof app.fetch !== "function") {
|
|
6
|
+
throw new InferloError("IL5020", "toNextHandlers() needs an Inferlo app (the value returned by createApp).", { fix });
|
|
7
|
+
}
|
|
8
|
+
if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
|
|
9
|
+
throw new InferloError("IL5020", "toNextHandlers() prefix must be a path that starts with /, such as \"/api\".", { fix });
|
|
10
|
+
}
|
|
11
|
+
const prefix = (options.prefix ?? "").replace(/\/+$/, "").replace(/\/{2,}/g, "/");
|
|
12
|
+
const handle = async (request) => {
|
|
13
|
+
if (!prefix)
|
|
14
|
+
return app.fetch(request);
|
|
15
|
+
const url = new URL(request.url);
|
|
16
|
+
if (url.pathname !== prefix && !url.pathname.startsWith(prefix + "/"))
|
|
17
|
+
return app.fetch(request);
|
|
18
|
+
url.pathname = url.pathname.slice(prefix.length) || "/";
|
|
19
|
+
const hasBody = request.body !== null && request.method !== "GET" && request.method !== "HEAD";
|
|
20
|
+
const init = {
|
|
21
|
+
method: request.method,
|
|
22
|
+
headers: request.headers,
|
|
23
|
+
body: hasBody ? request.body : undefined,
|
|
24
|
+
signal: request.signal,
|
|
25
|
+
redirect: request.redirect,
|
|
26
|
+
};
|
|
27
|
+
if (hasBody)
|
|
28
|
+
init.duplex = "half";
|
|
29
|
+
return app.fetch(new Request(url, init));
|
|
30
|
+
};
|
|
31
|
+
return Object.freeze({ GET: handle, POST: handle, PUT: handle, PATCH: handle, DELETE: handle, HEAD: handle, OPTIONS: handle });
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=nextjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../src/nextjs.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwB3C,MAAM,GAAG,GAAG,sHAAsH,CAAC;AAEnI,oIAAoI;AACpI,MAAM,UAAU,cAAc,CAAC,GAA4B,EAAE,UAA+B,EAAE;IAC5F,IAAI,CAAC,GAAG,IAAI,OAAQ,GAA2B,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,0EAA0E,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACxH,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACjK,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,8EAA8E,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5H,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAElF,MAAM,MAAM,GAAqB,KAAK,EAAE,OAAO,EAAE,EAAE;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;QACxD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC;QAC/F,MAAM,IAAI,GAAsC;YAC9C,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACxC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;SAC3B,CAAC;QACF,IAAI,OAAO;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAClC,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AACjI,CAAC"}
|
package/dist/openapi.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { App } from "./app.ts";
|
|
2
|
+
import type { ModuleInfo } from "./module.ts";
|
|
2
3
|
export declare function buildOpenAPI(app: App<any>, options: {
|
|
3
4
|
description?: string;
|
|
4
5
|
auth: boolean;
|
|
6
|
+
modules?: ModuleInfo;
|
|
7
|
+
version?: string;
|
|
5
8
|
}): Record<string, unknown>;
|
package/dist/openapi.js
CHANGED
|
@@ -20,7 +20,9 @@ const errorSchema = {
|
|
|
20
20
|
};
|
|
21
21
|
export function buildOpenAPI(app, options) {
|
|
22
22
|
const paths = {};
|
|
23
|
-
|
|
23
|
+
// A version's document has its routes and the routes outside every version.
|
|
24
|
+
const routes = options.version === undefined ? app.routes : app.routes.filter((r) => r.version === undefined || r.version === options.version);
|
|
25
|
+
for (const route of routes) {
|
|
24
26
|
const o = route.options;
|
|
25
27
|
const openPath = route.path.replace(/:([^/]+)/g, "{$1}").replace(/\/\*$/, "/{wildcard}");
|
|
26
28
|
const parameters = [];
|
|
@@ -40,19 +42,23 @@ export function buildOpenAPI(app, options) {
|
|
|
40
42
|
operationId: route.name ?? toolName(route),
|
|
41
43
|
summary: o.summary,
|
|
42
44
|
description: o.description,
|
|
43
|
-
tags
|
|
45
|
+
// Routes of a feature module are tagged with its name unless they set their own tags.
|
|
46
|
+
tags: o.tags ?? (options.modules?.routeModule.has(route) ? [options.modules.routeModule.get(route)] : undefined),
|
|
44
47
|
parameters: parameters.length ? parameters : undefined,
|
|
45
48
|
requestBody: o.body ? { required: !o.body.def.optional, content: { "application/json": { schema: o.body.toJSONSchema() } } } : undefined,
|
|
46
49
|
responses: {
|
|
47
50
|
"200": { description: "Success", content: o.response ? { "application/json": { schema: o.response.toJSONSchema() } } : undefined },
|
|
48
51
|
...(o.body || o.query || o.params ? { "422": { description: "Validation failed (IL1001)", content: { "application/json": { schema: errorSchema } } } } : {}),
|
|
49
52
|
...(!isPublic ? { "401": { description: "Sign in required (IL3001)", content: { "application/json": { schema: errorSchema } } } } : {}),
|
|
53
|
+
...(o.permission ? { "403": { description: `Needs the "${o.permission}" permission (IL3004)`, content: { "application/json": { schema: errorSchema } } } } : {}),
|
|
50
54
|
...(o.rateLimit
|
|
51
55
|
? { "429": { description: `Too many requests (IL2005): ${o.rateLimit.max} per ${o.rateLimit.per} per ${o.rateLimit.by ?? "user"}`, content: { "application/json": { schema: errorSchema } } } }
|
|
52
56
|
: {}),
|
|
53
57
|
},
|
|
54
58
|
security: !isPublic && options.auth ? [{ bearerAuth: [] }] : undefined,
|
|
55
59
|
"x-inferlo-auth": o.auth ?? "user",
|
|
60
|
+
"x-inferlo-permission": o.permission,
|
|
61
|
+
"x-inferlo-cache": o.cache ? { ttlMs: o.cache.ttlMs, by: o.cache.by ?? "user", tags: [...(o.cache.tags ?? [])] } : undefined,
|
|
56
62
|
"x-inferlo-tool": o.tool ? toolName(route) : undefined,
|
|
57
63
|
deprecated: o.deprecated ? true : undefined,
|
|
58
64
|
"x-inferlo-deprecation": typeof o.deprecated === "object" ? describeDeprecation(resolveDeprecation(o.deprecated, `${route.method} ${route.path}`)) : undefined,
|
|
@@ -67,7 +73,10 @@ export function buildOpenAPI(app, options) {
|
|
|
67
73
|
schemas[m.name] = m.row.toJSONSchema();
|
|
68
74
|
return {
|
|
69
75
|
openapi: "3.1.0",
|
|
70
|
-
info: { title: app.name, version: app.version, description: options.description },
|
|
76
|
+
info: { title: app.name, version: app.version, description: options.description, ...(options.version ? { "x-inferlo-api-version": options.version } : {}) },
|
|
77
|
+
...(options.modules?.modules.length
|
|
78
|
+
? { tags: options.modules.modules.map((m) => (m.description ? { name: m.name, description: m.description } : { name: m.name })) }
|
|
79
|
+
: {}),
|
|
71
80
|
paths,
|
|
72
81
|
components: {
|
|
73
82
|
schemas: { ...schemas, Error: errorSchema },
|
package/dist/openapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AppUser } from "./route.ts";
|
|
2
|
+
/** A rule: a function of the user (and the resources it needs) that returns true to allow, or the roles that may. */
|
|
3
|
+
export type PermissionRule = ((input: any) => boolean | Promise<boolean>) | {
|
|
4
|
+
readonly roles: readonly string[];
|
|
5
|
+
};
|
|
6
|
+
/** What can() and authorize() take for a rule: the function's input, or { user } for a roles rule. */
|
|
7
|
+
export type PermissionInput<R> = R extends (input: infer I) => unknown ? I : {
|
|
8
|
+
user: AppUser | null | undefined;
|
|
9
|
+
};
|
|
10
|
+
export interface Permissions<Rules extends Record<string, PermissionRule> = Record<string, PermissionRule>> {
|
|
11
|
+
readonly kind: "inferlo.permissions";
|
|
12
|
+
/** Every permission with its roles, for roles rules (shown in describe). */
|
|
13
|
+
readonly list: readonly {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly roles?: readonly string[];
|
|
16
|
+
}[];
|
|
17
|
+
/** True when the rule allows it. Never throws for a denial; a missing user is always denied. */
|
|
18
|
+
can<K extends keyof Rules & string>(name: K, input: PermissionInput<Rules[K]>): Promise<boolean>;
|
|
19
|
+
/** Resolves when the rule allows it; throws IL3001 (401) without a user and IL3004 (403) otherwise. */
|
|
20
|
+
authorize<K extends keyof Rules & string>(name: K, input: PermissionInput<Rules[K]>): Promise<void>;
|
|
21
|
+
/** True when the permission exists. */
|
|
22
|
+
has(name: string): boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Define the app's permissions by name. Pass the result to createApp({ permissions }) to use route permission options. */
|
|
25
|
+
export declare function permissions<const Rules extends Record<string, PermissionRule>>(rules: Rules): Permissions<Rules>;
|
|
26
|
+
export declare function isPermissions(value: unknown): value is Permissions;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named permissions, checked the same way in routes, AI tools and jobs:
|
|
3
|
+
*
|
|
4
|
+
* export const access = permissions({
|
|
5
|
+
* "reports.view": { roles: ["admin", "analyst"] },
|
|
6
|
+
* "tickets.update": ({ user, ticket }: { user: AppUser; ticket: Row<typeof Ticket> }) =>
|
|
7
|
+
* ticket.ownerId === user.id || (user.roles ?? []).includes("admin"),
|
|
8
|
+
* });
|
|
9
|
+
*
|
|
10
|
+
* await access.can("tickets.update", { user, ticket }) // true or false
|
|
11
|
+
* await access.authorize("tickets.update", { user, ticket }) // throws IL3004 (403) when not allowed
|
|
12
|
+
* route.get("/reports", { permission: "reports.view" }) // checked before the handler runs
|
|
13
|
+
*
|
|
14
|
+
* Deny by default: a missing user, an unknown permission or a rule that does not return true never allows.
|
|
15
|
+
*/
|
|
16
|
+
import { InferloError } from "./errors.js";
|
|
17
|
+
const fix = 'Define each permission as a function that returns true or false, or as { roles: ["admin"] }: permissions({ "tickets.update": ({ user, ticket }) => ticket.ownerId === user.id }).';
|
|
18
|
+
function invalid(message) {
|
|
19
|
+
return new InferloError("IL6026", message, { fix });
|
|
20
|
+
}
|
|
21
|
+
/** Define the app's permissions by name. Pass the result to createApp({ permissions }) to use route permission options. */
|
|
22
|
+
export function permissions(rules) {
|
|
23
|
+
if (!rules || typeof rules !== "object" || Array.isArray(rules))
|
|
24
|
+
throw invalid("permissions() takes an object of named rules.");
|
|
25
|
+
const table = new Map();
|
|
26
|
+
const list = [];
|
|
27
|
+
for (const [name, rule] of Object.entries(rules)) {
|
|
28
|
+
if (!/^[A-Za-z][\w-]*(\.[\w-]+)*$/.test(name))
|
|
29
|
+
throw invalid(`Invalid permission name "${name}". Use names like "tickets.update".`);
|
|
30
|
+
if (typeof rule === "function") {
|
|
31
|
+
table.set(name, rule);
|
|
32
|
+
list.push({ name });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const roles = rule?.roles;
|
|
36
|
+
const keys = rule && typeof rule === "object" ? Object.keys(rule) : [];
|
|
37
|
+
if (!Array.isArray(roles) || roles.length === 0 || roles.some((r) => typeof r !== "string" || r === "") || keys.some((k) => k !== "roles")) {
|
|
38
|
+
throw invalid(`Invalid rule for permission "${name}".`);
|
|
39
|
+
}
|
|
40
|
+
const frozen = Object.freeze([...roles]);
|
|
41
|
+
table.set(name, { roles: frozen });
|
|
42
|
+
list.push(Object.freeze({ name, roles: frozen }));
|
|
43
|
+
}
|
|
44
|
+
const check = async (name, input) => {
|
|
45
|
+
const rule = table.get(name);
|
|
46
|
+
if (!rule)
|
|
47
|
+
throw invalid(`Unknown permission "${name}". Defined: ${[...table.keys()].join(", ") || "none"}.`);
|
|
48
|
+
const user = input?.user;
|
|
49
|
+
if (!user)
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof rule !== "function") {
|
|
52
|
+
const roles = user.roles;
|
|
53
|
+
return Array.isArray(roles) && rule.roles.some((role) => roles.includes(role));
|
|
54
|
+
}
|
|
55
|
+
const allowed = await rule(input);
|
|
56
|
+
if (typeof allowed !== "boolean")
|
|
57
|
+
throw invalid(`The rule for permission "${name}" returned ${allowed === undefined ? "undefined" : typeof allowed} instead of true or false.`);
|
|
58
|
+
return allowed;
|
|
59
|
+
};
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
kind: "inferlo.permissions",
|
|
62
|
+
list: Object.freeze(list),
|
|
63
|
+
can: (name, input) => check(name, input),
|
|
64
|
+
async authorize(name, input) {
|
|
65
|
+
if (await check(name, input))
|
|
66
|
+
return;
|
|
67
|
+
if (!input?.user) {
|
|
68
|
+
throw new InferloError("IL3001", "Sign in required.", { status: 401, fix: "Send valid credentials, e.g. an Authorization: Bearer <token> header." });
|
|
69
|
+
}
|
|
70
|
+
throw new InferloError("IL3004", `You do not have the "${name}" permission.`, { status: 403, fix: "Sign in as a user the permission allows, or ask an administrator for access." });
|
|
71
|
+
},
|
|
72
|
+
has: (name) => table.has(name),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function isPermissions(value) {
|
|
76
|
+
return typeof value === "object" && value !== null && value.kind === "inferlo.permissions";
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limit.d.ts
CHANGED
|
@@ -11,16 +11,18 @@ export interface RateLimit {
|
|
|
11
11
|
per: "second" | "minute" | "hour" | "day";
|
|
12
12
|
/**
|
|
13
13
|
* Who is counted. "user": each signed-in user (requests without a user count by IP).
|
|
14
|
-
* "
|
|
14
|
+
* "tenant": each tenant (organisation) in multi-tenant apps, shared by all its users (users without a tenant
|
|
15
|
+
* count one by one, requests without a user by IP). "ip": each client IP address. Default "user".
|
|
15
16
|
*/
|
|
16
|
-
by?: "user" | "ip";
|
|
17
|
+
by?: "user" | "tenant" | "ip";
|
|
17
18
|
}
|
|
18
19
|
export interface RateLimitConfig {
|
|
19
20
|
/**
|
|
20
21
|
* Where counts are kept. "memory" (default): in this process, for one instance and tests.
|
|
21
22
|
* "database": in the inferlo_rate_limits table of the app's Postgres database, shared by every instance.
|
|
23
|
+
* "redis": in the Redis of createApp({ redis }), shared by every instance.
|
|
22
24
|
*/
|
|
23
|
-
store?: "memory" | "database";
|
|
25
|
+
store?: "memory" | "database" | "redis";
|
|
24
26
|
}
|
|
25
27
|
/** Counts hits per key in fixed windows. */
|
|
26
28
|
export interface RateLimitStore {
|
package/dist/rate-limit.js
CHANGED
|
@@ -14,12 +14,12 @@ export function checkRateLimit(limit, where) {
|
|
|
14
14
|
problems.push("max must be a whole number of at least 1");
|
|
15
15
|
if (!Object.hasOwn(WINDOWS, l.per))
|
|
16
16
|
problems.push(`per must be one of ${Object.keys(WINDOWS).map((k) => `"${k}"`).join(", ")}`);
|
|
17
|
-
if (l.by !== undefined && l.by !== "user" && l.by !== "ip")
|
|
18
|
-
problems.push('by must be "user" or "ip"');
|
|
17
|
+
if (l.by !== undefined && l.by !== "user" && l.by !== "tenant" && l.by !== "ip")
|
|
18
|
+
problems.push('by must be "user", "tenant" or "ip"');
|
|
19
19
|
}
|
|
20
20
|
if (problems.length) {
|
|
21
21
|
throw new InferloError("IL6007", `Invalid rateLimit on ${where}: ${problems.join(", ")}.`, {
|
|
22
|
-
fix: 'Use rateLimit: { max: 10, per: "minute", by: "user" } (per: "second" | "minute" | "hour" | "day", by: "user" | "ip").',
|
|
22
|
+
fix: 'Use rateLimit: { max: 10, per: "minute", by: "user" } (per: "second" | "minute" | "hour" | "day", by: "user" | "tenant" | "ip").',
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
}
|
package/dist/rate-limit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inferlo inside React Router 7 and 8 (framework mode, formerly Remix) resource routes, without a runtime
|
|
3
|
+
* dependency on React Router:
|
|
4
|
+
*
|
|
5
|
+
* // app/routes.ts: route("api/*", "routes/api.ts")
|
|
6
|
+
* // app/routes/api.ts
|
|
7
|
+
* import { toReactRouterHandlers } from "@browserlessapi/inferlo/react-router";
|
|
8
|
+
* import { app } from "~/inferlo/app.server";
|
|
9
|
+
*
|
|
10
|
+
* export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" });
|
|
11
|
+
*
|
|
12
|
+
* GET and HEAD requests reach the loader, POST, PUT, PATCH and DELETE the action; both run the whole Inferlo app.
|
|
13
|
+
* In other loaders and actions, use @browserlessapi/inferlo/ai. It never loads the router, database or jobs code.
|
|
14
|
+
*/
|
|
15
|
+
import type { App } from "./app.ts";
|
|
16
|
+
/** The parts of React Router's loader and action arguments Inferlo uses. */
|
|
17
|
+
export interface ReactRouterArgsLike {
|
|
18
|
+
/** The incoming Web request. */
|
|
19
|
+
request: Request;
|
|
20
|
+
}
|
|
21
|
+
/** A React Router loader or action that answers with the Inferlo app's Response. */
|
|
22
|
+
export type ReactRouterHandler = (args: ReactRouterArgsLike) => Promise<Response>;
|
|
23
|
+
/** The loader and action of a resource route: export const { loader, action } = toReactRouterHandlers(app). */
|
|
24
|
+
export interface ReactRouterHandlers {
|
|
25
|
+
/** GET and HEAD requests. */
|
|
26
|
+
loader: ReactRouterHandler;
|
|
27
|
+
/** POST, PUT, PATCH and DELETE requests. */
|
|
28
|
+
action: ReactRouterHandler;
|
|
29
|
+
}
|
|
30
|
+
export interface ReactRouterHandlersOptions {
|
|
31
|
+
/**
|
|
32
|
+
* The URL path of the splat route, removed before the app routes the request: "/api" for route("api/*", ...).
|
|
33
|
+
* Requests outside it keep their path. Include the app's basename if one is set.
|
|
34
|
+
*/
|
|
35
|
+
prefix?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A loader and an action that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
|
|
39
|
+
* from a React Router resource route.
|
|
40
|
+
*/
|
|
41
|
+
export declare function toReactRouterHandlers(app: Pick<App<any>, "fetch">, options?: ReactRouterHandlersOptions): ReactRouterHandlers;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { InferloError } from "./errors.js";
|
|
2
|
+
import { normalizePrefix, stripPrefix } from "./serve.js";
|
|
3
|
+
const fix = 'Pass the app from createApp and the route\'s path: export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" }).';
|
|
4
|
+
/**
|
|
5
|
+
* A loader and an action that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP)
|
|
6
|
+
* from a React Router resource route.
|
|
7
|
+
*/
|
|
8
|
+
export function toReactRouterHandlers(app, options = {}) {
|
|
9
|
+
if (!app || typeof app.fetch !== "function") {
|
|
10
|
+
throw new InferloError("IL5023", "toReactRouterHandlers() needs an Inferlo app (the value returned by createApp).", { fix });
|
|
11
|
+
}
|
|
12
|
+
if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
|
|
13
|
+
throw new InferloError("IL5023", 'toReactRouterHandlers() prefix must be a path that starts with /, such as "/api".', { fix });
|
|
14
|
+
}
|
|
15
|
+
const prefix = normalizePrefix(options.prefix);
|
|
16
|
+
const handle = async (args) => {
|
|
17
|
+
const incoming = args?.request;
|
|
18
|
+
if (!(incoming instanceof Request)) {
|
|
19
|
+
throw new InferloError("IL5023", "toReactRouterHandlers()'s loader and action were called without a request.", {
|
|
20
|
+
fix: 'Export them from a resource route module so React Router calls them: export const { loader, action } = toReactRouterHandlers(app, { prefix: "/api" }).',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const url = new URL(incoming.url);
|
|
24
|
+
const stripped = prefix ? stripPrefix(url.pathname, prefix) : null;
|
|
25
|
+
if (stripped === null)
|
|
26
|
+
return app.fetch(incoming);
|
|
27
|
+
url.pathname = stripped;
|
|
28
|
+
const hasBody = incoming.body !== null && incoming.method !== "GET" && incoming.method !== "HEAD";
|
|
29
|
+
const init = {
|
|
30
|
+
method: incoming.method,
|
|
31
|
+
headers: incoming.headers,
|
|
32
|
+
body: hasBody ? incoming.body : undefined,
|
|
33
|
+
signal: incoming.signal,
|
|
34
|
+
redirect: incoming.redirect,
|
|
35
|
+
};
|
|
36
|
+
if (hasBody)
|
|
37
|
+
init.duplex = "half";
|
|
38
|
+
return app.fetch(new Request(url, init));
|
|
39
|
+
};
|
|
40
|
+
return Object.freeze({ loader: handle, action: handle });
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=react-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-router.js","sourceRoot":"","sources":["../src/react-router.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA2B1D,MAAM,GAAG,GAAG,sIAAsI,CAAC;AAEnJ;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAA4B,EAAE,UAAsC,EAAE;IAC1G,IAAI,CAAC,GAAG,IAAI,OAAQ,GAA2B,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,iFAAiF,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/H,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACjK,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,mFAAmF,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACjI,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAuB,KAAK,EAAE,IAAI,EAAE,EAAE;QAChD,MAAM,QAAQ,GAAI,IAAwC,EAAE,OAAO,CAAC;QACpE,IAAI,CAAC,CAAC,QAAQ,YAAY,OAAO,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,4EAA4E,EAAE;gBAC7G,GAAG,EAAE,wJAAwJ;aAC9J,CAAC,CAAC;QACL,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAQ,GAA+B,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/E,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC;QAClG,MAAM,IAAI,GAAsC;YAC9C,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;YACzC,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B,CAAC;QACF,IAAI,OAAO;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAClC,OAAQ,GAA+B,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC;IAEF,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events between services over Redis Streams: createApp({ name: "billing", redis, events: { transport: "redis" } }).
|
|
3
|
+
*
|
|
4
|
+
* Keys (shared by every app on the Redis, so services find each other's events):
|
|
5
|
+
* inferlo:events:<event name> a stream with one entry per published event (fields id, payload, publishedAt,
|
|
6
|
+
* and traceparent and tracestate with tracing), trimmed to about maxLength entries
|
|
7
|
+
*
|
|
8
|
+
* Each app reads with a consumer group named after the app (createApp({ name })), so every service subscribed to
|
|
9
|
+
* an event gets each event once, and its instances share the work. An entry is acknowledged (XACK) after its jobs
|
|
10
|
+
* are queued; an entry a crashed instance read but did not acknowledge is claimed again (XAUTOCLAIM) after
|
|
11
|
+
* staleAfterMs. Delivery is at least once; the jobs it queues have ids derived from the event id, so a repeated
|
|
12
|
+
* delivery queues nothing new.
|
|
13
|
+
*/
|
|
14
|
+
import type { EventTransport } from "./events.ts";
|
|
15
|
+
import type { RedisClient } from "./redis.ts";
|
|
16
|
+
export declare const EVENT_STREAM_PREFIX = "inferlo:events:";
|
|
17
|
+
export interface RedisEventTransportOptions {
|
|
18
|
+
/** The consumer group: the app name. */
|
|
19
|
+
group: string;
|
|
20
|
+
/** Entries kept per event stream (approximately, trimmed by Redis as events are added). Default 100,000. */
|
|
21
|
+
maxLength?: number;
|
|
22
|
+
/** An entry read but not acknowledged for this long (its instance stopped) is delivered again. Default 30 seconds. */
|
|
23
|
+
staleAfterMs?: number;
|
|
24
|
+
/** Entries read per stream and call. Default 100. */
|
|
25
|
+
batch?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function redisEventTransport(client: RedisClient, options: RedisEventTransportOptions): EventTransport;
|