@browserlessapi/inferlo 0.5.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 +8 -2
- package/dist/ai/index.d.ts +7 -0
- package/dist/ai/index.js +68 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/public.d.ts +1 -0
- package/dist/ai/public.js.map +1 -1
- 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 +30 -3
- package/dist/app.js +210 -43
- 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 +25 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/describe.d.ts +42 -1
- package/dist/describe.js +25 -2
- package/dist/describe.js.map +1 -1
- 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 +14 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs.d.ts +20 -0
- package/dist/jobs.js +19 -7
- 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/mcp.d.ts +1 -1
- package/dist/mcp.js +7 -3
- package/dist/mcp.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/react-router.d.ts +41 -0
- package/dist/react-router.js +42 -0
- package/dist/react-router.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 +36 -1
- package/dist/route.js +24 -0
- package/dist/route.js.map +1 -1
- package/dist/serve.d.ts +20 -1
- package/dist/serve.js +48 -4
- 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/llms.txt +7 -3
- package/package.json +44 -2
- package/templates/app/AGENTS.md +5 -3
package/dist/route.d.ts
CHANGED
|
@@ -13,13 +13,16 @@ import type { AI } from "./ai/index.ts";
|
|
|
13
13
|
import type { Db } from "./db/table.ts";
|
|
14
14
|
import type { Jobs } from "./jobs.ts";
|
|
15
15
|
import type { AnyModel } from "./model.ts";
|
|
16
|
+
import type { Audit } from "./audit.ts";
|
|
17
|
+
import type { Cache, RouteCache } from "./cache.ts";
|
|
18
|
+
import type { PermissionInput, Permissions } from "./permissions.ts";
|
|
16
19
|
import type { RateLimit } from "./rate-limit.ts";
|
|
17
20
|
import type { AnySchema, Infer, ObjectSchema } from "./schema.ts";
|
|
18
21
|
/**
|
|
19
22
|
* Register your app's models and user type once, so every handler is fully typed:
|
|
20
23
|
*
|
|
21
24
|
* declare module "@browserlessapi/inferlo" {
|
|
22
|
-
* interface Register { models: typeof models; user: User }
|
|
25
|
+
* interface Register { models: typeof models; user: User; permissions: typeof access }
|
|
23
26
|
* }
|
|
24
27
|
*/
|
|
25
28
|
export interface Register {
|
|
@@ -33,6 +36,15 @@ export type AppUser = Registered<"user", {
|
|
|
33
36
|
roles?: string[];
|
|
34
37
|
}>;
|
|
35
38
|
export type AppDb = Db<AppModels>;
|
|
39
|
+
/**
|
|
40
|
+
* The permission names a route's `permission` option accepts. With `permissions: typeof access` in Register, only the
|
|
41
|
+
* names whose rule needs nothing but { user } (roles rules and rules of the user alone), so a typo is a type error.
|
|
42
|
+
*/
|
|
43
|
+
export type AppPermission = Registered<"permissions", undefined> extends Permissions<infer R> ? {
|
|
44
|
+
[K in keyof R & string]: {
|
|
45
|
+
user: AppUser;
|
|
46
|
+
} extends PermissionInput<R[K]> ? K : never;
|
|
47
|
+
}[keyof R & string] : string;
|
|
36
48
|
export type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
37
49
|
/** "public": anyone. "user": any signed-in user. Any other string: the user must have that role. */
|
|
38
50
|
export type Auth = "public" | "user" | (string & {});
|
|
@@ -45,6 +57,12 @@ export interface ToolOptions {
|
|
|
45
57
|
export interface RouteOptions<B extends AnySchema | undefined, Q extends ObjectSchema<any> | undefined, P extends ObjectSchema<any> | undefined> {
|
|
46
58
|
/** Who may call this route. Default "user" (signed in). */
|
|
47
59
|
auth?: Auth;
|
|
60
|
+
/**
|
|
61
|
+
* A permission from createApp({ permissions }) the signed-in user must have, checked with { user } before the
|
|
62
|
+
* handler runs (also when the route is called as an AI tool). Denied: 403 IL3004. For rules that also need a
|
|
63
|
+
* resource, call access.authorize(name, { user, ticket }) in the handler instead. Typed from Register's permissions.
|
|
64
|
+
*/
|
|
65
|
+
permission?: AppPermission;
|
|
48
66
|
/** Request body schema (JSON or form). */
|
|
49
67
|
body?: B;
|
|
50
68
|
/** Query string schema. Strings are converted to numbers and booleans automatically. */
|
|
@@ -71,6 +89,12 @@ export interface RouteOptions<B extends AnySchema | undefined, Q extends ObjectS
|
|
|
71
89
|
* Link rel="deprecation" headers on every response. Shown in OpenAPI, describe and MCP tool descriptions.
|
|
72
90
|
*/
|
|
73
91
|
deprecated?: boolean | Deprecation;
|
|
92
|
+
/**
|
|
93
|
+
* Cache this GET route's JSON responses in the app cache: { ttlMs: 60_000, by: "user", tags: ["tickets"] }.
|
|
94
|
+
* "user" (default) keeps one response per signed-in user, "public" one for every caller. Remove them early with
|
|
95
|
+
* ctx.cache.invalidate(tag). Responses carry an ETag, so clients revalidate with If-None-Match (304).
|
|
96
|
+
*/
|
|
97
|
+
cache?: RouteCache;
|
|
74
98
|
}
|
|
75
99
|
type PathParams<P extends string> = P extends `${string}:${infer Param}/${infer Rest}` ? {
|
|
76
100
|
[K in Param | keyof PathParams<`/${Rest}`>]: string;
|
|
@@ -88,6 +112,8 @@ export interface Context<Body, Query, Params, User> {
|
|
|
88
112
|
ai: AI;
|
|
89
113
|
/** Queue background jobs: await jobs.enqueue(sendEmail, { to }). */
|
|
90
114
|
jobs: Jobs;
|
|
115
|
+
/** The app cache: await cache.getOrSet(key, { ttlMs: 60_000, tags: ["tickets"] }, compute), cache.invalidate("tickets"). */
|
|
116
|
+
cache: Cache;
|
|
91
117
|
req: Request;
|
|
92
118
|
url: URL;
|
|
93
119
|
headers: Headers;
|
|
@@ -106,6 +132,11 @@ export interface Context<Body, Query, Params, User> {
|
|
|
106
132
|
requestId: string;
|
|
107
133
|
/** Run work after the response is sent (logging, webhooks). Errors are logged, not thrown. */
|
|
108
134
|
waitUntil(promise: Promise<unknown>): void;
|
|
135
|
+
/**
|
|
136
|
+
* Write an audit log entry with the signed-in user and this request's id (needs AuditLog in createApp({ models })):
|
|
137
|
+
* await audit("tickets.update", { model: "tickets", before: ticket, after: updated }).
|
|
138
|
+
*/
|
|
139
|
+
audit: Audit;
|
|
109
140
|
}
|
|
110
141
|
export type AnyContext = Context<any, any, any, any>;
|
|
111
142
|
export type Handler<C, R> = (ctx: C) => R | Promise<R>;
|
|
@@ -117,6 +148,8 @@ export interface RouteDef {
|
|
|
117
148
|
readonly handler: Handler<AnyContext, unknown>;
|
|
118
149
|
/** Export name, filled in by createApp when routes are passed as modules. */
|
|
119
150
|
name?: string;
|
|
151
|
+
/** API version ("v1") when the route comes from apiVersion("v1", routes). */
|
|
152
|
+
readonly version?: string;
|
|
120
153
|
}
|
|
121
154
|
export declare class RouteBuilder<Path extends string, B extends AnySchema | undefined, Q extends ObjectSchema<any> | undefined, P extends ObjectSchema<any> | undefined, A> {
|
|
122
155
|
readonly method: Method;
|
|
@@ -146,4 +179,6 @@ export declare const route: {
|
|
|
146
179
|
}) => RouteBuilder<Path, B, Q, P, A>;
|
|
147
180
|
};
|
|
148
181
|
export declare function isRoute(value: unknown): value is RouteDef;
|
|
182
|
+
/** The routes in route sources: routes, arrays and modules (import * as tickets), named after their exports. */
|
|
183
|
+
export declare function collectRoutes(sources: unknown): RouteDef[];
|
|
149
184
|
export {};
|
package/dist/route.js
CHANGED
|
@@ -25,4 +25,28 @@ export const route = {
|
|
|
25
25
|
export function isRoute(value) {
|
|
26
26
|
return typeof value === "object" && value !== null && value.kind === "inferlo.route";
|
|
27
27
|
}
|
|
28
|
+
/** The routes in route sources: routes, arrays and modules (import * as tickets), named after their exports. */
|
|
29
|
+
export function collectRoutes(sources) {
|
|
30
|
+
const out = [];
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const visit = (source, name) => {
|
|
33
|
+
if (!source || typeof source !== "object" || seen.has(source))
|
|
34
|
+
return;
|
|
35
|
+
seen.add(source);
|
|
36
|
+
if (isRoute(source)) {
|
|
37
|
+
out.push(name && !source.name ? { ...source, name } : source);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(source)) {
|
|
41
|
+
for (const item of source)
|
|
42
|
+
visit(item);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const [key, value] of Object.entries(source))
|
|
46
|
+
if (isRoute(value) || Array.isArray(value))
|
|
47
|
+
visit(value, key === "default" ? undefined : key);
|
|
48
|
+
};
|
|
49
|
+
visit(sources);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
28
52
|
//# sourceMappingURL=route.js.map
|
package/dist/route.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AA8JA,MAAM,OAAO,YAAY;IACd,MAAM,CAAS;IACf,IAAI,CAAO;IACX,OAAO,CAAwB;IAExC,YAAY,MAAc,EAAE,IAAU,EAAE,OAA8B;QACpE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,qFAAqF;IACrF,MAAM,CACJ,OAQC;QAED,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAuC,EAE5I,CAAC;IACJ,CAAC;CACF;AAED,SAAS,OAAO,CAAC,MAAc;IAC7B,OAAO,CAOL,IAAU,EACV,UAAgD,EAAE,EAClD,EAAE,CAAC,IAAI,YAAY,CAAmB,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC;CAC1B,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAK,KAAkB,CAAC,IAAI,KAAK,eAAe,CAAC;AACrG,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAW,CAAC;IAChC,MAAM,KAAK,GAAG,CAAC,MAAe,EAAE,IAAa,EAAE,EAAE;QAC/C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QACtE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,KAAK,MAAM,IAAI,IAAI,MAAM;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACnJ,CAAC,CAAC;IACF,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/dist/serve.d.ts
CHANGED
|
@@ -19,7 +19,10 @@ export interface NodeRequest {
|
|
|
19
19
|
remoteAddress?: string;
|
|
20
20
|
encrypted?: boolean;
|
|
21
21
|
} | null;
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* A body the host framework already read (express.json(), body-parser): a string, bytes, or a plain object or
|
|
24
|
+
* array. Used instead of the stream. Instances of other classes are ignored and the stream is read.
|
|
25
|
+
*/
|
|
23
26
|
body?: unknown;
|
|
24
27
|
[Symbol.asyncIterator](): AsyncIterator<unknown>;
|
|
25
28
|
}
|
|
@@ -40,12 +43,28 @@ export interface NodeHandlerOptions {
|
|
|
40
43
|
* Leave it out when the host framework already strips the mount path (Express app.use("/ai", ...)).
|
|
41
44
|
*/
|
|
42
45
|
prefix?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Pass requests the app has no route for (by method and path, outside its OpenAPI, MCP, health and metrics
|
|
48
|
+
* endpoints) to the host framework's next() instead of answering 404 or 405. Mount the app before the host's
|
|
49
|
+
* routes with it, to move routes to Inferlo one at a time at the same URLs. Needs an app from createApp and
|
|
50
|
+
* a host that calls the handler with next (Express, NestJS, Connect). Default false.
|
|
51
|
+
*/
|
|
52
|
+
fallthrough?: boolean;
|
|
43
53
|
}
|
|
44
54
|
export interface FromNodeRequestOptions extends NodeHandlerOptions {
|
|
45
55
|
/** Aborts the request (for example when the client disconnects). */
|
|
46
56
|
signal?: AbortSignal;
|
|
47
57
|
}
|
|
48
58
|
export type NodeHandler = (req: NodeRequest, res: NodeResponse, next?: (error?: unknown) => void) => Promise<void>;
|
|
59
|
+
export declare function normalizePrefix(prefix: string | undefined): string;
|
|
60
|
+
/** The path and query of a Node request with the prefix removed, or null when it is outside the prefix. */
|
|
61
|
+
export declare function stripPrefix(rawUrl: string, prefix: string): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Whether toNodeHandler(app, { prefix, fallthrough: true }) answers a Node request itself instead of passing it
|
|
64
|
+
* on. For hosts that must decide before handing the request over, such as a Fastify onRequest hook.
|
|
65
|
+
*/
|
|
66
|
+
export declare function answersNodeRequest(app: object, req: Pick<NodeRequest, "url" | "method">, prefix?: string): boolean;
|
|
67
|
+
export declare function buildRequest(req: NodeRequest, path: string, signal?: AbortSignal): Request;
|
|
49
68
|
/**
|
|
50
69
|
* Convert a Node.js request (node:http, Express, Fastify's request.raw, NestJS) to a Web Request.
|
|
51
70
|
* With prefix, the prefix is removed from the path; a request outside it keeps its path.
|
package/dist/serve.js
CHANGED
|
@@ -6,14 +6,16 @@
|
|
|
6
6
|
* http.createServer(toNodeHandler(app, { prefix: "/ai" })) // or strip it with prefix
|
|
7
7
|
*/
|
|
8
8
|
import { createRedactor } from "./redact.js";
|
|
9
|
+
import { appAnswers } from "./request-matcher.js";
|
|
10
|
+
import { InferloError } from "./errors.js";
|
|
9
11
|
import { clientIps } from "./client-ip.js";
|
|
10
|
-
function normalizePrefix(prefix) {
|
|
12
|
+
export function normalizePrefix(prefix) {
|
|
11
13
|
if (!prefix || prefix === "/")
|
|
12
14
|
return "";
|
|
13
15
|
return ("/" + prefix.replace(/^\/+|\/+$/g, "")).replace(/\/+/g, "/");
|
|
14
16
|
}
|
|
15
17
|
/** The path and query of a Node request with the prefix removed, or null when it is outside the prefix. */
|
|
16
|
-
function stripPrefix(rawUrl, prefix) {
|
|
18
|
+
export function stripPrefix(rawUrl, prefix) {
|
|
17
19
|
const url = rawUrl.startsWith("/") ? rawUrl : "/" + rawUrl;
|
|
18
20
|
if (!prefix)
|
|
19
21
|
return url;
|
|
@@ -26,6 +28,25 @@ function stripPrefix(rawUrl, prefix) {
|
|
|
26
28
|
return null;
|
|
27
29
|
return rest;
|
|
28
30
|
}
|
|
31
|
+
/** Whether the app answers a method and a path (with query, prefix already removed) itself. */
|
|
32
|
+
function answersPath(app, method, path) {
|
|
33
|
+
let pathname = path.split("?")[0];
|
|
34
|
+
try {
|
|
35
|
+
pathname = new URL(pathname, "http://localhost").pathname;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Leave a path the URL parser rejects to the app, which answers it.
|
|
39
|
+
}
|
|
40
|
+
return appAnswers(app, method, pathname) === true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether toNodeHandler(app, { prefix, fallthrough: true }) answers a Node request itself instead of passing it
|
|
44
|
+
* on. For hosts that must decide before handing the request over, such as a Fastify onRequest hook.
|
|
45
|
+
*/
|
|
46
|
+
export function answersNodeRequest(app, req, prefix) {
|
|
47
|
+
const path = stripPrefix(req.url ?? "/", normalizePrefix(prefix));
|
|
48
|
+
return path !== null && answersPath(app, req.method ?? "GET", path);
|
|
49
|
+
}
|
|
29
50
|
function bodyStream(req) {
|
|
30
51
|
const iterator = req[Symbol.asyncIterator]();
|
|
31
52
|
return new ReadableStream({
|
|
@@ -45,7 +66,20 @@ function bodyStream(req) {
|
|
|
45
66
|
},
|
|
46
67
|
});
|
|
47
68
|
}
|
|
48
|
-
|
|
69
|
+
/**
|
|
70
|
+
* True when req.body holds a body the host framework already read: a string or bytes (body-parser text and
|
|
71
|
+
* raw), or a plain object or array (express.json(), urlencoded forms). An instance of some other class is the
|
|
72
|
+
* host's own object, such as the body reader of an Encore raw request, and the stream is read instead.
|
|
73
|
+
*/
|
|
74
|
+
function isParsedBody(body) {
|
|
75
|
+
if (body === undefined)
|
|
76
|
+
return false;
|
|
77
|
+
if (body === null || typeof body !== "object" || body instanceof Uint8Array || Array.isArray(body))
|
|
78
|
+
return true;
|
|
79
|
+
const proto = Object.getPrototypeOf(body);
|
|
80
|
+
return proto === Object.prototype || proto === null;
|
|
81
|
+
}
|
|
82
|
+
export function buildRequest(req, path, signal) {
|
|
49
83
|
const host = [req.headers.host].flat()[0] ?? "localhost";
|
|
50
84
|
const url = new URL(path, `${req.socket?.encrypted ? "https" : "http"}://${host}`);
|
|
51
85
|
const headers = new Headers();
|
|
@@ -61,7 +95,7 @@ function buildRequest(req, path, signal) {
|
|
|
61
95
|
const method = (req.method ?? "GET").toUpperCase();
|
|
62
96
|
let body;
|
|
63
97
|
if (method !== "GET" && method !== "HEAD") {
|
|
64
|
-
if (req.body
|
|
98
|
+
if (isParsedBody(req.body)) {
|
|
65
99
|
// The host framework already read the stream: use what it parsed.
|
|
66
100
|
headers.delete("content-length");
|
|
67
101
|
if (typeof req.body === "string" || req.body instanceof Uint8Array)
|
|
@@ -125,10 +159,20 @@ export async function writeResponse(res, response, method) {
|
|
|
125
159
|
*/
|
|
126
160
|
export function toNodeHandler(app, options = {}) {
|
|
127
161
|
const prefix = normalizePrefix(options.prefix);
|
|
162
|
+
if (options.fallthrough !== undefined && typeof options.fallthrough !== "boolean") {
|
|
163
|
+
throw new InferloError("IL5019", "toNodeHandler fallthrough must be true or false.", { fix: "Use toNodeHandler(app, { fallthrough: true })." });
|
|
164
|
+
}
|
|
165
|
+
if (options.fallthrough && appAnswers(app, "GET", "/") === undefined) {
|
|
166
|
+
throw new InferloError("IL5019", "toNodeHandler(app, { fallthrough: true }) needs an app from createApp.", {
|
|
167
|
+
fix: "Pass the app returned by createApp, which knows its routes; a plain fetch handler cannot tell which requests to pass on.",
|
|
168
|
+
});
|
|
169
|
+
}
|
|
128
170
|
return async (req, res, next) => {
|
|
129
171
|
const path = stripPrefix(req.url ?? "/", prefix);
|
|
130
172
|
if (path === null && next)
|
|
131
173
|
return next();
|
|
174
|
+
if (options.fallthrough && next && path !== null && !answersPath(app, req.method ?? "GET", path))
|
|
175
|
+
return next();
|
|
132
176
|
try {
|
|
133
177
|
const controller = new AbortController();
|
|
134
178
|
res.on("close", () => {
|
package/dist/serve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie sessions for browser apps: signed, HttpOnly cookies and CSRF protection, with no session table.
|
|
3
|
+
*
|
|
4
|
+
* export const session = sessions({
|
|
5
|
+
* secret: config.SESSION_SECRET,
|
|
6
|
+
* user: ({ userId }, { db }) => db.users.find(userId),
|
|
7
|
+
* });
|
|
8
|
+
* createApp({ auth: session, ... });
|
|
9
|
+
*
|
|
10
|
+
* // in a login route (auth: "public"), after checking the password:
|
|
11
|
+
* const { csrfToken } = await session.signIn(ctx, user.id);
|
|
12
|
+
* // in a logout route:
|
|
13
|
+
* session.signOut(ctx);
|
|
14
|
+
*
|
|
15
|
+
* Requests that change data (POST, PUT, PATCH, DELETE) with a session cookie must send the CSRF token in an
|
|
16
|
+
* X-CSRF-Token header or a _csrf form field, or they are rejected with IL3005.
|
|
17
|
+
*/
|
|
18
|
+
import type { AuthConfig } from "./app.ts";
|
|
19
|
+
import type { AppDb, AppUser } from "./route.ts";
|
|
20
|
+
export interface SessionData {
|
|
21
|
+
/** The id passed to session.signIn(ctx, userId). */
|
|
22
|
+
userId: string;
|
|
23
|
+
/** When the session was created (milliseconds since 1970). Compare it to a "signed out everywhere" time to revoke older sessions. */
|
|
24
|
+
issuedAt: number;
|
|
25
|
+
}
|
|
26
|
+
export interface SessionCookieOptions {
|
|
27
|
+
/** Cookie name. Default "inferlo_session"; the CSRF cookie is the same name with "_csrf" added. */
|
|
28
|
+
name?: string;
|
|
29
|
+
/** SameSite attribute. Default "lax" (links from other sites keep the session, cross-site form posts do not send it). */
|
|
30
|
+
sameSite?: "lax" | "strict";
|
|
31
|
+
/** Secure attribute. Default true, except for plain http on localhost, 127.0.0.1 and ::1 during development. */
|
|
32
|
+
secure?: boolean;
|
|
33
|
+
/** Domain attribute, to share the session with subdomains. Default: none (only the host that set it). */
|
|
34
|
+
domain?: string;
|
|
35
|
+
/** Path attribute. Default "/". */
|
|
36
|
+
path?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface SessionsOptions {
|
|
39
|
+
/**
|
|
40
|
+
* The signing secret, at least 32 characters: keep it in config({ secrets }). To rotate it, pass
|
|
41
|
+
* [newSecret, oldSecret]: new sessions are signed with the first, and sessions signed with any of them stay valid.
|
|
42
|
+
*/
|
|
43
|
+
secret: string | readonly string[];
|
|
44
|
+
/** Load the signed-in user for a session, or return null to end it (deleted or disabled users, revoked sessions). */
|
|
45
|
+
user: (session: SessionData, ctx: {
|
|
46
|
+
db: AppDb;
|
|
47
|
+
req: Request;
|
|
48
|
+
}) => AppUser | null | undefined | Promise<AppUser | null | undefined>;
|
|
49
|
+
/** How long a session lasts after sign-in, in milliseconds. Default 7 days; 1 minute to 400 days. */
|
|
50
|
+
maxAgeMs?: number;
|
|
51
|
+
cookie?: SessionCookieOptions;
|
|
52
|
+
/** Roles of a user, checked when a route uses auth: "<role>". Default: user.roles ?? []. */
|
|
53
|
+
roles?: (user: AppUser) => string[];
|
|
54
|
+
}
|
|
55
|
+
/** What session.signIn and session.signOut need from the route context. */
|
|
56
|
+
export interface SessionResponseContext {
|
|
57
|
+
url: URL;
|
|
58
|
+
header(name: string, value: string): void;
|
|
59
|
+
}
|
|
60
|
+
/** createApp({ auth: session }) with sign-in, sign-out and the CSRF token of the current session. */
|
|
61
|
+
export interface Sessions extends AuthConfig {
|
|
62
|
+
/** Start a session for this user: sets the session and CSRF cookies. Returns the CSRF token to send with changes. */
|
|
63
|
+
signIn(ctx: SessionResponseContext, userId: string): Promise<{
|
|
64
|
+
csrfToken: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** End the session: clears both cookies. */
|
|
67
|
+
signOut(ctx: SessionResponseContext): void;
|
|
68
|
+
/** The CSRF token of the request's session (to put in a form's _csrf field), or null without a valid session. */
|
|
69
|
+
csrfToken(req: Request): Promise<string | null>;
|
|
70
|
+
}
|
|
71
|
+
/** Signed cookie sessions with CSRF protection: createApp({ auth: sessions({ secret, user }) }). */
|
|
72
|
+
export declare function sessions(options: SessionsOptions): Sessions;
|
package/dist/sessions.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { safeEqual } from "./auth.js";
|
|
2
|
+
import { InferloError } from "./errors.js";
|
|
3
|
+
const DAY = 86_400_000;
|
|
4
|
+
const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
5
|
+
const MAX_FORM_BYTES = 64 * 1024;
|
|
6
|
+
const fix = 'Pass sessions({ secret: config.SESSION_SECRET, user: ({ userId }, { db }) => db.users.find(userId) }) with a secret of at least 32 random characters (openssl rand -base64 32).';
|
|
7
|
+
/** Signed cookie sessions with CSRF protection: createApp({ auth: sessions({ secret, user }) }). */
|
|
8
|
+
export function sessions(options) {
|
|
9
|
+
if (!options || typeof options !== "object")
|
|
10
|
+
throw new InferloError("IL6029", "sessions() needs { secret, user }.", { fix });
|
|
11
|
+
const secrets = typeof options.secret === "string" ? [options.secret] : Array.isArray(options.secret) ? [...options.secret] : [];
|
|
12
|
+
if (!secrets.length || secrets.some((s) => typeof s !== "string" || s.length < 32)) {
|
|
13
|
+
throw new InferloError("IL6029", "sessions() secret must be a string of at least 32 characters (or a list of them, newest first).", { fix });
|
|
14
|
+
}
|
|
15
|
+
if (typeof options.user !== "function")
|
|
16
|
+
throw new InferloError("IL6029", "sessions() needs a user({ userId }, { db }) function that loads the signed-in user.", { fix });
|
|
17
|
+
const maxAgeMs = options.maxAgeMs ?? 7 * DAY;
|
|
18
|
+
if (!Number.isInteger(maxAgeMs) || maxAgeMs < 60_000 || maxAgeMs > 400 * DAY) {
|
|
19
|
+
throw new InferloError("IL6029", "sessions() maxAgeMs must be a whole number of milliseconds from 60000 (1 minute) to 34560000000 (400 days).", { fix });
|
|
20
|
+
}
|
|
21
|
+
const cookie = options.cookie ?? {};
|
|
22
|
+
const name = cookie.name ?? "inferlo_session";
|
|
23
|
+
if (!/^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/.test(name))
|
|
24
|
+
throw new InferloError("IL6029", `sessions() cookie name "${name}" is not a valid cookie name.`, { fix: 'Use letters, digits, "-" and "_", such as "app_session".' });
|
|
25
|
+
if (cookie.sameSite !== undefined && cookie.sameSite !== "lax" && cookie.sameSite !== "strict") {
|
|
26
|
+
throw new InferloError("IL6029", 'sessions() cookie.sameSite must be "lax" or "strict".', { fix: 'Use sameSite: "lax" (the default) or "strict".' });
|
|
27
|
+
}
|
|
28
|
+
for (const key of ["domain", "path"]) {
|
|
29
|
+
const value = cookie[key];
|
|
30
|
+
if (value !== undefined && (typeof value !== "string" || !value || /[;,\s]/.test(value))) {
|
|
31
|
+
throw new InferloError("IL6029", `sessions() cookie.${key} must be a non-empty string without spaces, commas or semicolons.`, { fix });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const csrfName = `${name}_csrf`;
|
|
35
|
+
const sameSite = cookie.sameSite === "strict" ? "Strict" : "Lax";
|
|
36
|
+
const path = cookie.path ?? "/";
|
|
37
|
+
const keys = secrets.map((secret) => crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]));
|
|
38
|
+
const sign = async (key, text) => base64url(new Uint8Array(await crypto.subtle.sign("HMAC", await key, new TextEncoder().encode(text))));
|
|
39
|
+
const csrfFor = (sessionId) => sign(keys[0], `csrf.${sessionId}`);
|
|
40
|
+
const attributes = (url, httpOnly, maxAge) => {
|
|
41
|
+
const local = url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
42
|
+
const secure = cookie.secure ?? !local;
|
|
43
|
+
return [`Path=${path}`, cookie.domain ? `Domain=${cookie.domain}` : "", `Max-Age=${maxAge}`, httpOnly ? "HttpOnly" : "", secure ? "Secure" : "", `SameSite=${sameSite}`].filter(Boolean).join("; ");
|
|
44
|
+
};
|
|
45
|
+
/** The verified session of a request, or null. */
|
|
46
|
+
const read = async (req) => {
|
|
47
|
+
const value = readCookie(req.headers.get("cookie"), name);
|
|
48
|
+
if (!value)
|
|
49
|
+
return null;
|
|
50
|
+
const [version, payload, signature, extra] = value.split(".");
|
|
51
|
+
if (version !== "v1" || !payload || !signature || extra !== undefined)
|
|
52
|
+
return null;
|
|
53
|
+
let valid = false;
|
|
54
|
+
for (const key of keys)
|
|
55
|
+
if (safeEqual(await sign(key, `v1.${payload}`), signature))
|
|
56
|
+
valid = true;
|
|
57
|
+
if (!valid)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(new TextDecoder().decode(unbase64url(payload)));
|
|
61
|
+
if (typeof parsed.u !== "string" || typeof parsed.s !== "string" || typeof parsed.i !== "number" || typeof parsed.e !== "number")
|
|
62
|
+
return null;
|
|
63
|
+
if (parsed.e <= Date.now())
|
|
64
|
+
return null;
|
|
65
|
+
return { data: { userId: parsed.u, issuedAt: parsed.i }, sessionId: parsed.s };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const session = {
|
|
72
|
+
async resolve(req, { db }) {
|
|
73
|
+
const current = await read(req);
|
|
74
|
+
if (!current)
|
|
75
|
+
return null;
|
|
76
|
+
if (!SAFE_METHODS.has(req.method.toUpperCase())) {
|
|
77
|
+
const sent = req.headers.get("x-csrf-token") ?? (await formField(req, "_csrf"));
|
|
78
|
+
if (!sent || !safeEqual(sent, await csrfFor(current.sessionId))) {
|
|
79
|
+
throw new InferloError("IL3005", "Missing or invalid CSRF token.", {
|
|
80
|
+
status: 403,
|
|
81
|
+
fix: "Send the session's CSRF token in an X-CSRF-Token header (from session.signIn or the " + csrfName + " cookie) or a _csrf form field (from session.csrfToken(req)).",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return (await options.user(current.data, { db, req })) ?? null;
|
|
86
|
+
},
|
|
87
|
+
roles: options.roles,
|
|
88
|
+
async signIn(ctx, userId) {
|
|
89
|
+
if (typeof userId !== "string" || !userId)
|
|
90
|
+
throw new InferloError("IL6029", "session.signIn(ctx, userId) needs the user's id as a non-empty string.", { fix: "Call it after checking the password: await session.signIn(ctx, user.id)." });
|
|
91
|
+
if (!ctx || typeof ctx.header !== "function" || !(ctx.url instanceof URL))
|
|
92
|
+
throw new InferloError("IL6029", "session.signIn(ctx, userId) needs the route context.", { fix: "Pass the handler's context: route.post(\"/login\", ...).handle(async (ctx) => { await session.signIn(ctx, user.id) })." });
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const sessionId = base64url(crypto.getRandomValues(new Uint8Array(18)));
|
|
95
|
+
const payload = base64url(new TextEncoder().encode(JSON.stringify({ u: userId, s: sessionId, i: now, e: now + maxAgeMs })));
|
|
96
|
+
const value = `v1.${payload}.${await sign(keys[0], `v1.${payload}`)}`;
|
|
97
|
+
const csrfToken = await csrfFor(sessionId);
|
|
98
|
+
const seconds = Math.floor(maxAgeMs / 1000);
|
|
99
|
+
ctx.header("set-cookie", `${name}=${value}; ${attributes(ctx.url, true, seconds)}`);
|
|
100
|
+
ctx.header("set-cookie", `${csrfName}=${csrfToken}; ${attributes(ctx.url, false, seconds)}`);
|
|
101
|
+
return { csrfToken };
|
|
102
|
+
},
|
|
103
|
+
signOut(ctx) {
|
|
104
|
+
if (!ctx || typeof ctx.header !== "function" || !(ctx.url instanceof URL))
|
|
105
|
+
throw new InferloError("IL6029", "session.signOut(ctx) needs the route context.", { fix: "Pass the handler's context: session.signOut(ctx)." });
|
|
106
|
+
ctx.header("set-cookie", `${name}=; ${attributes(ctx.url, true, 0)}`);
|
|
107
|
+
ctx.header("set-cookie", `${csrfName}=; ${attributes(ctx.url, false, 0)}`);
|
|
108
|
+
},
|
|
109
|
+
async csrfToken(req) {
|
|
110
|
+
const current = await read(req);
|
|
111
|
+
return current ? csrfFor(current.sessionId) : null;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
return Object.freeze(session);
|
|
115
|
+
}
|
|
116
|
+
function readCookie(header, name) {
|
|
117
|
+
if (!header)
|
|
118
|
+
return undefined;
|
|
119
|
+
for (const part of header.split(";")) {
|
|
120
|
+
const i = part.indexOf("=");
|
|
121
|
+
if (i > 0 && part.slice(0, i).trim() === name)
|
|
122
|
+
return part.slice(i + 1).trim();
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
/** A field of a URL-encoded form body, read from a copy of the request (at most 64 KiB) so the route can still read it. */
|
|
127
|
+
async function formField(req, field) {
|
|
128
|
+
if (!(req.headers.get("content-type") ?? "").toLowerCase().includes("application/x-www-form-urlencoded"))
|
|
129
|
+
return null;
|
|
130
|
+
if (Number(req.headers.get("content-length") ?? "0") > MAX_FORM_BYTES)
|
|
131
|
+
return null;
|
|
132
|
+
// Cloned before req.body is touched: on Bun 1.3, a body stream read before clone() comes back empty.
|
|
133
|
+
const body = req.clone().body;
|
|
134
|
+
if (!body)
|
|
135
|
+
return null;
|
|
136
|
+
const reader = body.getReader();
|
|
137
|
+
const chunks = [];
|
|
138
|
+
let size = 0;
|
|
139
|
+
try {
|
|
140
|
+
while (size <= MAX_FORM_BYTES) {
|
|
141
|
+
const { done, value } = await reader.read();
|
|
142
|
+
if (done)
|
|
143
|
+
break;
|
|
144
|
+
chunks.push(value);
|
|
145
|
+
size += value.byteLength;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
void reader.cancel().catch(() => { });
|
|
150
|
+
}
|
|
151
|
+
if (size > MAX_FORM_BYTES)
|
|
152
|
+
return null;
|
|
153
|
+
const bytes = new Uint8Array(size);
|
|
154
|
+
let offset = 0;
|
|
155
|
+
for (const chunk of chunks) {
|
|
156
|
+
bytes.set(chunk, offset);
|
|
157
|
+
offset += chunk.byteLength;
|
|
158
|
+
}
|
|
159
|
+
return new URLSearchParams(new TextDecoder().decode(bytes)).get(field);
|
|
160
|
+
}
|
|
161
|
+
function base64url(bytes) {
|
|
162
|
+
let text = "";
|
|
163
|
+
for (const b of bytes)
|
|
164
|
+
text += String.fromCharCode(b);
|
|
165
|
+
return btoa(text).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
166
|
+
}
|
|
167
|
+
function unbase64url(text) {
|
|
168
|
+
const padded = text.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (text.length % 4)) % 4);
|
|
169
|
+
return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsD3C,MAAM,GAAG,GAAG,UAAU,CAAC;AACvB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AACzD,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC;AACjC,MAAM,GAAG,GAAG,iLAAiL,CAAC;AAE9L,oGAAoG;AACpG,MAAM,UAAU,QAAQ,CAAC,OAAwB;IAC/C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,oCAAoC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC7H,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjI,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,iGAAiG,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/I,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU;QAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,qFAAqF,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACzK,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,GAAG,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,MAAM,IAAI,QAAQ,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;QAC7E,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,6GAA6G,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3J,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,iBAAiB,CAAC;IAC9C,IAAI,CAAC,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,2BAA2B,IAAI,+BAA+B,EAAE,EAAE,GAAG,EAAE,0DAA0D,EAAE,CAAC,CAAC;IACxN,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC/F,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,uDAAuD,EAAE,EAAE,GAAG,EAAE,gDAAgD,EAAE,CAAC,CAAC;IACvJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAU,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACzF,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,qBAAqB,GAAG,mEAAmE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACzI,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,IAAI,OAAO,CAAC;IAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC;IAChC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAE3J,MAAM,IAAI,GAAG,KAAK,EAAE,GAAuB,EAAE,IAAY,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrK,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,QAAQ,SAAS,EAAE,CAAC,CAAC;IAE3E,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAE,QAAiB,EAAE,MAAc,EAAE,EAAE;QACjE,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACrG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC;QACvC,OAAO,CAAC,QAAQ,IAAI,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,YAAY,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtM,CAAC,CAAC;IAEF,kDAAkD;IAClD,MAAM,IAAI,GAAG,KAAK,EAAE,GAAY,EAA4D,EAAE;QAC5F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1D,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9D,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnF,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,MAAM,OAAO,EAAE,CAAC,EAAE,SAAS,CAAC;gBAAE,KAAK,GAAG,IAAI,CAAC;QACjG,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAA2D,CAAC;YACpI,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC9I,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;gBAAE,OAAO,IAAI,CAAC;YACxC,OAAO,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QACjF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,OAAO,GAAa;QACxB,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE;YACvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;gBAChF,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;oBAChE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,gCAAgC,EAAE;wBACjE,MAAM,EAAE,GAAG;wBACX,GAAG,EAAE,sFAAsF,GAAG,QAAQ,GAAG,+DAA+D;qBACzK,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC;QACjE,CAAC;QACD,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM;YACtB,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,wEAAwE,EAAE,EAAE,GAAG,EAAE,0EAA0E,EAAE,CAAC,CAAC;YAC3O,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC;gBAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,sDAAsD,EAAE,EAAE,GAAG,EAAE,wHAAwH,EAAE,CAAC,CAAC;YACvS,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACxE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5H,MAAM,KAAK,GAAG,MAAM,OAAO,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,MAAM,OAAO,EAAE,CAAC,EAAE,CAAC;YACvE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,IAAI,IAAI,KAAK,KAAK,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YACpF,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,QAAQ,IAAI,SAAS,KAAK,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;YAC7F,OAAO,EAAE,SAAS,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,CAAC,GAAG;YACT,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,YAAY,GAAG,CAAC;gBAAE,MAAM,IAAI,YAAY,CAAC,QAAQ,EAAE,+CAA+C,EAAE,EAAE,GAAG,EAAE,mDAAmD,EAAE,CAAC,CAAC;YAC3N,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,IAAI,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YACtE,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,QAAQ,MAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,GAAG;YACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;YAChC,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACrD,CAAC;KACF,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAChC,CAAC;AAED,SAAS,UAAU,CAAC,MAAqB,EAAE,IAAY;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2HAA2H;AAC3H,KAAK,UAAU,SAAS,CAAC,GAAY,EAAE,KAAa;IAClD,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtH,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,GAAG,cAAc;QAAE,OAAO,IAAI,CAAC;IACnF,qGAAqG;IACrG,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,cAAc,EAAE,CAAC;YAC9B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC;QAC3B,CAAC;IACH,CAAC;YAAS,CAAC;QACT,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,GAAG,cAAc;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,eAAe,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,SAAS,CAAC,KAAiB;IAClC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,IAAI,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpG,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|