@browserlessapi/inferlo 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/dist/adonisjs.d.ts +33 -0
- package/dist/adonisjs.js +30 -0
- package/dist/adonisjs.js.map +1 -0
- package/dist/ai/index.d.ts +22 -8
- package/dist/ai/index.js +107 -15
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/providers.js +5 -4
- package/dist/ai/providers.js.map +1 -1
- package/dist/ai/public.d.ts +21 -0
- package/dist/ai/public.js +17 -0
- package/dist/ai/public.js.map +1 -0
- package/dist/ai/tool.d.ts +47 -0
- package/dist/ai/tool.js +85 -0
- package/dist/ai/tool.js.map +1 -0
- package/dist/ai-client.d.ts +3 -0
- package/dist/ai-client.js +8 -0
- package/dist/ai-client.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 +117 -5
- package/dist/app.js +667 -156
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +50 -0
- package/dist/audit.js +92 -0
- package/dist/audit.js.map +1 -0
- package/dist/cache.d.ts +92 -0
- package/dist/cache.js +245 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli/main.js +45 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/db/table.d.ts +31 -0
- package/dist/db/table.js +140 -1
- package/dist/db/table.js.map +1 -1
- package/dist/deprecation.d.ts +23 -0
- package/dist/deprecation.js +70 -0
- package/dist/deprecation.js.map +1 -0
- package/dist/describe.d.ts +68 -5
- package/dist/describe.js +38 -3
- package/dist/describe.js.map +1 -1
- package/dist/elysia.d.ts +49 -0
- package/dist/elysia.js +10 -0
- package/dist/elysia.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +5 -4
- package/dist/env.js.map +1 -1
- package/dist/express.d.ts +38 -0
- package/dist/express.js +18 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.d.ts +52 -0
- package/dist/fastify.js +65 -0
- package/dist/fastify.js.map +1 -0
- 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/hono.d.ts +26 -0
- package/dist/hono.js +10 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.d.ts +27 -5
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +24 -0
- package/dist/jobs.js +35 -6
- 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/logging.d.ts +23 -0
- package/dist/logging.js +87 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp.d.ts +1 -7
- package/dist/mcp.js +18 -4
- package/dist/mcp.js.map +1 -1
- package/dist/metrics.d.ts +48 -0
- package/dist/metrics.js +184 -0
- package/dist/metrics.js.map +1 -0
- package/dist/module.d.ts +52 -0
- package/dist/module.js +152 -0
- package/dist/module.js.map +1 -0
- package/dist/nestjs.d.ts +43 -0
- package/dist/nestjs.js +47 -0
- package/dist/nestjs.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 -1
- package/dist/openapi.js +16 -3
- package/dist/openapi.js.map +1 -1
- package/dist/permissions.d.ts +26 -0
- package/dist/permissions.js +78 -0
- package/dist/permissions.js.map +1 -0
- package/dist/react-router.d.ts +41 -0
- package/dist/react-router.js +42 -0
- package/dist/react-router.js.map +1 -0
- package/dist/redact.d.ts +19 -0
- package/dist/redact.js +150 -0
- package/dist/redact.js.map +1 -0
- package/dist/request-matcher.d.ts +4 -0
- package/dist/request-matcher.js +14 -0
- package/dist/request-matcher.js.map +1 -0
- package/dist/route.d.ts +54 -1
- package/dist/route.js +24 -0
- package/dist/route.js.map +1 -1
- package/dist/secure-headers.d.ts +8 -0
- package/dist/secure-headers.js +61 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve.d.ts +72 -1
- package/dist/serve.js +201 -61
- package/dist/serve.js.map +1 -1
- package/dist/sessions.d.ts +72 -0
- package/dist/sessions.js +171 -0
- package/dist/sessions.js.map +1 -0
- package/dist/sveltekit.d.ts +72 -0
- package/dist/sveltekit.js +68 -0
- package/dist/sveltekit.js.map +1 -0
- package/dist/tracing.d.ts +118 -0
- package/dist/tracing.js +204 -0
- package/dist/tracing.js.map +1 -0
- package/dist/trpc.d.ts +57 -0
- package/dist/trpc.js +142 -0
- package/dist/trpc.js.map +1 -0
- package/dist/tsed.d.ts +32 -0
- package/dist/tsed.js +13 -0
- package/dist/tsed.js.map +1 -0
- package/llms.txt +15 -5
- package/package.json +99 -3
- package/templates/app/AGENTS.md +12 -6
- package/templates/app/_env.example +2 -0
- package/templates/app/src/app.ts +15 -8
- package/templates/app/src/config.ts +17 -0
- package/templates/app/src/server.ts +9 -9
- package/templates/app/src/routes/health.ts +0 -3
package/dist/metrics.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics: request, job and AI counters and histograms in the Prometheus text format.
|
|
3
|
+
*
|
|
4
|
+
* createApp({ metrics: { token: settings.METRICS_TOKEN } })
|
|
5
|
+
*
|
|
6
|
+
* serves GET /metrics for Prometheus, Grafana Agent, the OpenTelemetry Collector, Datadog and others.
|
|
7
|
+
*/
|
|
8
|
+
import { InferloError } from "./errors.js";
|
|
9
|
+
/** Default histogram buckets in seconds, the same as the Prometheus client libraries. */
|
|
10
|
+
export const DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
11
|
+
/** Methods outside this list are counted as "OTHER", so clients cannot create unlimited series. */
|
|
12
|
+
const METHODS = new Set(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]);
|
|
13
|
+
/** Checks createApp({ metrics }) and returns it with defaults, or undefined when metrics are off. */
|
|
14
|
+
export function resolveMetrics(option, taken) {
|
|
15
|
+
if (option === undefined || option === false)
|
|
16
|
+
return undefined;
|
|
17
|
+
const problems = [];
|
|
18
|
+
let path = "/metrics";
|
|
19
|
+
let token;
|
|
20
|
+
let buckets = DEFAULT_BUCKETS;
|
|
21
|
+
if (option !== true) {
|
|
22
|
+
if (!option || typeof option !== "object" || Array.isArray(option))
|
|
23
|
+
problems.push("metrics must be true or an object such as { token: settings.METRICS_TOKEN }");
|
|
24
|
+
else {
|
|
25
|
+
const { path: p, token: tk, buckets: b, ...rest } = option;
|
|
26
|
+
if (p !== undefined) {
|
|
27
|
+
if (typeof p !== "string" || !p.startsWith("/"))
|
|
28
|
+
problems.push(`path must start with "/", got ${JSON.stringify(p)}`);
|
|
29
|
+
else
|
|
30
|
+
path = p;
|
|
31
|
+
}
|
|
32
|
+
// { token: settings.METRICS_TOKEN } with the variable unset must not make the endpoint public.
|
|
33
|
+
if (tk === undefined && "token" in option)
|
|
34
|
+
problems.push("token is undefined (is the variable it comes from set?); leave token out only for a public endpoint");
|
|
35
|
+
else if (tk !== undefined) {
|
|
36
|
+
if (typeof tk !== "string" || tk.length < 16)
|
|
37
|
+
problems.push("token must be a string of at least 16 characters (a random secret from configuration)");
|
|
38
|
+
else
|
|
39
|
+
token = tk;
|
|
40
|
+
}
|
|
41
|
+
if (b !== undefined) {
|
|
42
|
+
const ok = Array.isArray(b) && b.length > 0 && b.every((n, i) => typeof n === "number" && Number.isFinite(n) && n > 0 && (i === 0 || n > b[i - 1]));
|
|
43
|
+
if (!ok)
|
|
44
|
+
problems.push("buckets must be increasing positive numbers of seconds, such as [0.1, 0.5, 1, 5]");
|
|
45
|
+
else
|
|
46
|
+
buckets = [...b];
|
|
47
|
+
}
|
|
48
|
+
for (const key of Object.keys(rest))
|
|
49
|
+
problems.push(`unknown option "${key}"`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const { path: other, what } of taken)
|
|
53
|
+
if (!problems.length && other && other.replace(/\/+$/, "") === path.replace(/\/+$/, ""))
|
|
54
|
+
problems.push(`path ${path} is also ${what}`);
|
|
55
|
+
if (problems.length) {
|
|
56
|
+
throw new InferloError("IL6020", `Invalid metrics options: ${problems.join("; ")}.`, {
|
|
57
|
+
fix: 'Use createApp({ metrics: true }) or createApp({ metrics: { path: "/metrics", token: settings.METRICS_TOKEN } }).',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { path, token, buckets };
|
|
61
|
+
}
|
|
62
|
+
/** True when the request carries Authorization: Bearer <token>, compared in constant time. */
|
|
63
|
+
export function hasMetricsToken(req, token) {
|
|
64
|
+
const header = req.headers.get("authorization") ?? "";
|
|
65
|
+
const match = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
66
|
+
if (!match)
|
|
67
|
+
return false;
|
|
68
|
+
const a = new TextEncoder().encode(match[1]);
|
|
69
|
+
const b = new TextEncoder().encode(token);
|
|
70
|
+
let diff = a.length ^ b.length;
|
|
71
|
+
for (let i = 0; i < b.length; i++)
|
|
72
|
+
diff |= (a[i] ?? 0) ^ b[i];
|
|
73
|
+
return diff === 0;
|
|
74
|
+
}
|
|
75
|
+
function key(labels) {
|
|
76
|
+
return JSON.stringify(Object.entries(labels));
|
|
77
|
+
}
|
|
78
|
+
function escape(value) {
|
|
79
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
|
|
80
|
+
}
|
|
81
|
+
function labelText(labels) {
|
|
82
|
+
const parts = Object.entries(labels).map(([k, v]) => `${k}="${escape(v)}"`);
|
|
83
|
+
return parts.length ? `{${parts.join(",")}}` : "";
|
|
84
|
+
}
|
|
85
|
+
function number(n) {
|
|
86
|
+
if (n === Infinity)
|
|
87
|
+
return "+Inf";
|
|
88
|
+
return String(Math.round(n * 1e9) / 1e9);
|
|
89
|
+
}
|
|
90
|
+
export function createMetrics(info, buckets) {
|
|
91
|
+
const counters = new Map();
|
|
92
|
+
const histograms = new Map();
|
|
93
|
+
let inFlight = 0;
|
|
94
|
+
const add = (name, labels, value) => {
|
|
95
|
+
let series = counters.get(name);
|
|
96
|
+
if (!series)
|
|
97
|
+
counters.set(name, (series = new Map()));
|
|
98
|
+
const k = key(labels);
|
|
99
|
+
const entry = series.get(k);
|
|
100
|
+
if (entry)
|
|
101
|
+
entry.value += value;
|
|
102
|
+
else
|
|
103
|
+
series.set(k, { labels, value });
|
|
104
|
+
};
|
|
105
|
+
const observe = (name, labels, value) => {
|
|
106
|
+
let series = histograms.get(name);
|
|
107
|
+
if (!series)
|
|
108
|
+
histograms.set(name, (series = new Map()));
|
|
109
|
+
const k = key(labels);
|
|
110
|
+
let h = series.get(k);
|
|
111
|
+
if (!h)
|
|
112
|
+
series.set(k, (h = { labels, counts: buckets.map(() => 0), sum: 0, count: 0 }));
|
|
113
|
+
for (let i = 0; i < buckets.length; i++)
|
|
114
|
+
if (value <= buckets[i])
|
|
115
|
+
h.counts[i]++;
|
|
116
|
+
h.sum += value;
|
|
117
|
+
h.count++;
|
|
118
|
+
};
|
|
119
|
+
const help = {
|
|
120
|
+
inferlo_info: ["gauge", "The app's name and version and the Inferlo version (always 1)."],
|
|
121
|
+
inferlo_http_requests_total: ["counter", "HTTP requests by method, route pattern and status."],
|
|
122
|
+
inferlo_http_request_duration_seconds: ["histogram", "Time until the response headers were ready, by method and route pattern."],
|
|
123
|
+
inferlo_http_requests_in_flight: ["gauge", "HTTP requests being handled now."],
|
|
124
|
+
inferlo_job_runs_total: ["counter", "Job attempts run in this process, by job and outcome (done, retried or failed)."],
|
|
125
|
+
inferlo_job_duration_seconds: ["histogram", "Time each job attempt took, by job."],
|
|
126
|
+
inferlo_ai_calls_total: ["counter", "AI calls by requested alias or model, the model that answered, and kind (generate, stream or embed)."],
|
|
127
|
+
inferlo_ai_tokens_total: ["counter", "AI tokens by requested alias or model, the model that answered, and type (input or output)."],
|
|
128
|
+
inferlo_ai_cost_usd_total: ["counter", "AI cost in US dollars from ai.pricing, by requested alias or model and the model that answered."],
|
|
129
|
+
inferlo_ai_call_duration_seconds: ["histogram", "Time each AI call took, by requested alias or model and the model that answered."],
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
request(method, route, status, seconds) {
|
|
133
|
+
const m = method.toUpperCase();
|
|
134
|
+
const labels = { method: METHODS.has(m) ? m : "OTHER", route };
|
|
135
|
+
add("inferlo_http_requests_total", { ...labels, status: String(status) }, 1);
|
|
136
|
+
observe("inferlo_http_request_duration_seconds", labels, seconds);
|
|
137
|
+
},
|
|
138
|
+
inFlight(delta) {
|
|
139
|
+
inFlight += delta;
|
|
140
|
+
},
|
|
141
|
+
job(name, outcome, seconds) {
|
|
142
|
+
add("inferlo_job_runs_total", { job: name, outcome }, 1);
|
|
143
|
+
observe("inferlo_job_duration_seconds", { job: name }, seconds);
|
|
144
|
+
},
|
|
145
|
+
ai(usage) {
|
|
146
|
+
const labels = { requested: usage.requested, model: usage.model };
|
|
147
|
+
add("inferlo_ai_calls_total", { ...labels, kind: usage.kind }, 1);
|
|
148
|
+
add("inferlo_ai_tokens_total", { ...labels, type: "input" }, usage.inputTokens);
|
|
149
|
+
add("inferlo_ai_tokens_total", { ...labels, type: "output" }, usage.outputTokens);
|
|
150
|
+
add("inferlo_ai_cost_usd_total", labels, usage.cost);
|
|
151
|
+
observe("inferlo_ai_call_duration_seconds", labels, usage.durationMs / 1000);
|
|
152
|
+
},
|
|
153
|
+
render() {
|
|
154
|
+
const lines = [];
|
|
155
|
+
const header = (name) => {
|
|
156
|
+
const [type, text] = help[name];
|
|
157
|
+
lines.push(`# HELP ${name} ${text}`, `# TYPE ${name} ${type}`);
|
|
158
|
+
};
|
|
159
|
+
header("inferlo_info");
|
|
160
|
+
lines.push(`inferlo_info${labelText(info)} 1`);
|
|
161
|
+
header("inferlo_http_requests_in_flight");
|
|
162
|
+
lines.push(`inferlo_http_requests_in_flight ${inFlight}`);
|
|
163
|
+
for (const name of Object.keys(help)) {
|
|
164
|
+
const series = counters.get(name);
|
|
165
|
+
if (series) {
|
|
166
|
+
header(name);
|
|
167
|
+
for (const { labels, value } of series.values())
|
|
168
|
+
lines.push(`${name}${labelText(labels)} ${number(value)}`);
|
|
169
|
+
}
|
|
170
|
+
const hist = histograms.get(name);
|
|
171
|
+
if (hist) {
|
|
172
|
+
header(name);
|
|
173
|
+
for (const h of hist.values()) {
|
|
174
|
+
buckets.forEach((le, i) => lines.push(`${name}_bucket${labelText({ ...h.labels, le: number(le) })} ${h.counts[i]}`));
|
|
175
|
+
lines.push(`${name}_bucket${labelText({ ...h.labels, le: "+Inf" })} ${h.count}`);
|
|
176
|
+
lines.push(`${name}_sum${labelText(h.labels)} ${number(h.sum)}`, `${name}_count${labelText(h.labels)} ${h.count}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return lines.join("\n") + "\n";
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { JobSource } from "./jobs.ts";
|
|
2
|
+
import type { AnyModel } from "./model.ts";
|
|
3
|
+
import type { RouteSource } from "./app.ts";
|
|
4
|
+
export interface ModuleOptions<Models extends readonly AnyModel[] = readonly AnyModel[], Provides extends Record<string, unknown> = Record<string, unknown>> {
|
|
5
|
+
/** One sentence about what the module does, shown in describe() and as the OpenAPI tag description. */
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Modules this module depends on. Each must also be passed to createApp({ modules }). */
|
|
8
|
+
imports?: readonly AnyModule[];
|
|
9
|
+
/** The models (tables) this module owns. */
|
|
10
|
+
models?: Models;
|
|
11
|
+
/** The module's routes: routes, arrays or route files (import * as invoices from "./routes/invoices.ts"). */
|
|
12
|
+
routes?: readonly RouteSource[];
|
|
13
|
+
/** The module's background jobs and crons. */
|
|
14
|
+
jobs?: readonly JobSource[];
|
|
15
|
+
/** What other modules may use, by name: plain functions and values, read as billing.provides.createInvoice. */
|
|
16
|
+
provides?: Provides;
|
|
17
|
+
}
|
|
18
|
+
export interface Module<Name extends string = string, Models extends readonly AnyModel[] = readonly AnyModel[], Provides extends Record<string, unknown> = Record<string, unknown>> {
|
|
19
|
+
readonly kind: "inferlo.module";
|
|
20
|
+
readonly name: Name;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
readonly imports: readonly AnyModule[];
|
|
23
|
+
readonly models: Models;
|
|
24
|
+
readonly routes: readonly RouteSource[];
|
|
25
|
+
readonly jobs: readonly JobSource[];
|
|
26
|
+
readonly provides: Readonly<Provides>;
|
|
27
|
+
}
|
|
28
|
+
export type AnyModule = Module<string, readonly AnyModel[], any>;
|
|
29
|
+
/** The models of a list of modules, in order: ModuleModels<[typeof users, typeof billing]>. */
|
|
30
|
+
export type ModuleModels<M extends readonly AnyModule[]> = M extends readonly [infer H extends AnyModule, ...infer R extends readonly AnyModule[]] ? [...H["models"], ...ModuleModels<R>] : M extends readonly [] ? [] : M[number]["models"][number][];
|
|
31
|
+
/**
|
|
32
|
+
* Define a feature module: module("billing", { imports, models, routes, jobs, provides }).
|
|
33
|
+
* Register it with createApp({ modules: [billing] }).
|
|
34
|
+
*/
|
|
35
|
+
export declare function module<const Name extends string, const Models extends readonly AnyModel[] = [], const Provides extends Record<string, unknown> = {}>(name: Name, options?: ModuleOptions<Models, Provides>): Module<Name, Models, Provides>;
|
|
36
|
+
export declare function isModule(value: unknown): value is AnyModule;
|
|
37
|
+
/**
|
|
38
|
+
* Checks createApp({ modules }) and returns the modules with each model's owner. Every imported
|
|
39
|
+
* module must be registered, names are unique, and a model may only reference models of its own
|
|
40
|
+
* module, of a module it imports, or of the app itself (createApp({ models })).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveModules(option: unknown, appModels: readonly AnyModel[]): {
|
|
43
|
+
modules: readonly AnyModule[];
|
|
44
|
+
modelOwner: Map<string, string>;
|
|
45
|
+
};
|
|
46
|
+
/** Which module each route, model and job came from (for describe() and OpenAPI). */
|
|
47
|
+
export interface ModuleInfo {
|
|
48
|
+
modules: readonly AnyModule[];
|
|
49
|
+
routeModule: ReadonlyMap<unknown, string>;
|
|
50
|
+
modelOwner: ReadonlyMap<string, string>;
|
|
51
|
+
jobModule: ReadonlyMap<string, string>;
|
|
52
|
+
}
|
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/nestjs.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inferlo inside NestJS (11 and later), without a runtime dependency on NestJS:
|
|
3
|
+
*
|
|
4
|
+
* import { INFERLO_AI, InferloModule } from "@browserlessapi/inferlo/nestjs";
|
|
5
|
+
*
|
|
6
|
+
* @Module({ imports: [InferloModule.forRoot({ ai })] }) // or forRootAsync({ inject, useFactory })
|
|
7
|
+
* export class AppModule {}
|
|
8
|
+
*
|
|
9
|
+
* @Injectable()
|
|
10
|
+
* export class TicketsService {
|
|
11
|
+
* constructor(@Inject(INFERLO_AI) private readonly ai: AI) {}
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* A whole Inferlo app mounts with app.use("/api", toNodeHandler(inferloApp)) on the Express platform.
|
|
15
|
+
* This entry point never loads the router, database or jobs code.
|
|
16
|
+
*/
|
|
17
|
+
import type { DynamicModule } from "@nestjs/common";
|
|
18
|
+
import type { AI } from "./ai/index.ts";
|
|
19
|
+
/** Injection token for the AI client: constructor(@Inject(INFERLO_AI) private readonly ai: AI) {}. */
|
|
20
|
+
export declare const INFERLO_AI = "INFERLO_AI";
|
|
21
|
+
export interface InferloModuleOptions {
|
|
22
|
+
/** The AI client: createAI(...) from @browserlessapi/inferlo/ai, or an Inferlo app's app.ai. */
|
|
23
|
+
ai: AI;
|
|
24
|
+
/** Make INFERLO_AI injectable in every module without importing InferloModule again. Default true. */
|
|
25
|
+
global?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface InferloModuleAsyncOptions {
|
|
28
|
+
/** Modules whose providers useFactory needs, for example ConfigModule. */
|
|
29
|
+
imports?: DynamicModule["imports"];
|
|
30
|
+
/** Providers passed to useFactory, in order, for example [ConfigService]. */
|
|
31
|
+
inject?: unknown[];
|
|
32
|
+
/** Returns the AI client, for example from configuration: (config) => createAI({ ... }). */
|
|
33
|
+
useFactory: (...args: any[]) => AI | Promise<AI>;
|
|
34
|
+
/** Default true, as in forRoot. */
|
|
35
|
+
global?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** A NestJS module that provides the AI client as INFERLO_AI. Use InferloModule.forRoot({ ai }) in imports. */
|
|
38
|
+
export declare class InferloModule {
|
|
39
|
+
/** Provide an AI client you already have. */
|
|
40
|
+
static forRoot(options: InferloModuleOptions): DynamicModule;
|
|
41
|
+
/** Create the AI client when Nest starts, from other providers (for example ConfigService). */
|
|
42
|
+
static forRootAsync(options: InferloModuleAsyncOptions): DynamicModule;
|
|
43
|
+
}
|
package/dist/nestjs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { assertAIClient } from "./ai-client.js";
|
|
2
|
+
import { InferloError } from "./errors.js";
|
|
3
|
+
/** Injection token for the AI client: constructor(@Inject(INFERLO_AI) private readonly ai: AI) {}. */
|
|
4
|
+
export const INFERLO_AI = "INFERLO_AI";
|
|
5
|
+
const fix = 'Pass createAI({ providers, models }) from "@browserlessapi/inferlo/ai", or an Inferlo app\'s app.ai: InferloModule.forRoot({ ai }).';
|
|
6
|
+
/** A NestJS module that provides the AI client as INFERLO_AI. Use InferloModule.forRoot({ ai }) in imports. */
|
|
7
|
+
export class InferloModule {
|
|
8
|
+
/** Provide an AI client you already have. */
|
|
9
|
+
static forRoot(options) {
|
|
10
|
+
if (!options || typeof options !== "object")
|
|
11
|
+
throw new InferloError("IL5016", "InferloModule.forRoot() needs options with ai.", { fix });
|
|
12
|
+
assertAIClient(options.ai, fix);
|
|
13
|
+
return {
|
|
14
|
+
module: InferloModule,
|
|
15
|
+
global: options.global ?? true,
|
|
16
|
+
providers: [{ provide: INFERLO_AI, useValue: options.ai }],
|
|
17
|
+
exports: [INFERLO_AI],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Create the AI client when Nest starts, from other providers (for example ConfigService). */
|
|
21
|
+
static forRootAsync(options) {
|
|
22
|
+
if (!options || typeof options.useFactory !== "function") {
|
|
23
|
+
throw new InferloError("IL5016", "InferloModule.forRootAsync() needs a useFactory function.", {
|
|
24
|
+
fix: "Use InferloModule.forRootAsync({ inject: [ConfigService], useFactory: (config) => createAI({ ... }) }).",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const { useFactory } = options;
|
|
28
|
+
return {
|
|
29
|
+
module: InferloModule,
|
|
30
|
+
global: options.global ?? true,
|
|
31
|
+
imports: options.imports ?? [],
|
|
32
|
+
providers: [
|
|
33
|
+
{
|
|
34
|
+
provide: INFERLO_AI,
|
|
35
|
+
inject: (options.inject ?? []),
|
|
36
|
+
useFactory: async (...args) => {
|
|
37
|
+
const ai = await useFactory(...args);
|
|
38
|
+
assertAIClient(ai, "useFactory must return createAI({ providers, models }) or an Inferlo app's app.ai.");
|
|
39
|
+
return ai;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
exports: [INFERLO_AI],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=nestjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nestjs.js","sourceRoot":"","sources":["../src/nestjs.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,sGAAsG;AACtG,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC;AAoBvC,MAAM,GAAG,GAAG,qIAAqI,CAAC;AAElJ,+GAA+G;AAC/G,MAAM,OAAO,aAAa;IACxB,6CAA6C;IAC7C,MAAM,CAAC,OAAO,CAAC,OAA6B;QAC1C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,gDAAgD,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACzI,cAAc,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAChC,OAAO;YACL,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;YAC9B,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;YAC1D,OAAO,EAAE,CAAC,UAAU,CAAC;SACtB,CAAC;IACJ,CAAC;IAED,+FAA+F;IAC/F,MAAM,CAAC,YAAY,CAAC,OAAkC;QACpD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YACzD,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,2DAA2D,EAAE;gBAC5F,GAAG,EAAE,yGAAyG;aAC/G,CAAC,CAAC;QACL,CAAC;QACD,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;QAC/B,OAAO;YACL,MAAM,EAAE,aAAa;YACrB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;YAC9B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,UAAU;oBACnB,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAY;oBACzC,UAAU,EAAE,KAAK,EAAE,GAAG,IAAe,EAAE,EAAE;wBACvC,MAAM,EAAE,GAAG,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;wBACrC,cAAc,CAAC,EAAE,EAAE,oFAAoF,CAAC,CAAC;wBACzG,OAAO,EAAE,CAAC;oBACZ,CAAC;iBACF;aACF;YACD,OAAO,EAAE,CAAC,UAAU,CAAC;SACtB,CAAC;IACJ,CAAC;CACF"}
|
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,6 +1,8 @@
|
|
|
1
|
-
/** OpenAPI 3.1 document generated from routes. Served at /openapi.json by default. */
|
|
2
1
|
import type { App } from "./app.ts";
|
|
2
|
+
import type { ModuleInfo } from "./module.ts";
|
|
3
3
|
export declare function buildOpenAPI(app: App<any>, options: {
|
|
4
4
|
description?: string;
|
|
5
5
|
auth: boolean;
|
|
6
|
+
modules?: ModuleInfo;
|
|
7
|
+
version?: string;
|
|
6
8
|
}): Record<string, unknown>;
|