@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.
Files changed (138) hide show
  1. package/README.md +14 -5
  2. package/dist/ai/ai-sdk.d.ts +48 -0
  3. package/dist/ai/ai-sdk.js +235 -0
  4. package/dist/ai/ai-sdk.js.map +1 -0
  5. package/dist/ai/index.d.ts +44 -3
  6. package/dist/ai/index.js +164 -9
  7. package/dist/ai/index.js.map +1 -1
  8. package/dist/ai/public.d.ts +6 -1
  9. package/dist/ai/public.js +2 -0
  10. package/dist/ai/public.js.map +1 -1
  11. package/dist/ai/ui.d.ts +47 -0
  12. package/dist/ai/ui.js +33 -0
  13. package/dist/ai/ui.js.map +1 -0
  14. package/dist/api-version.d.ts +32 -0
  15. package/dist/api-version.js +45 -0
  16. package/dist/api-version.js.map +1 -0
  17. package/dist/app.d.ts +60 -6
  18. package/dist/app.js +310 -66
  19. package/dist/app.js.map +1 -1
  20. package/dist/astro.d.ts +60 -0
  21. package/dist/astro.js +68 -0
  22. package/dist/astro.js.map +1 -0
  23. package/dist/audit.d.ts +57 -0
  24. package/dist/audit.js +99 -0
  25. package/dist/audit.js.map +1 -0
  26. package/dist/auth.d.ts +1 -0
  27. package/dist/auth.js +1 -0
  28. package/dist/auth.js.map +1 -1
  29. package/dist/azure.d.ts +66 -0
  30. package/dist/azure.js +119 -0
  31. package/dist/azure.js.map +1 -0
  32. package/dist/cache.d.ts +96 -0
  33. package/dist/cache.js +247 -0
  34. package/dist/cache.js.map +1 -0
  35. package/dist/cli/main.js +28 -7
  36. package/dist/cli/main.js.map +1 -1
  37. package/dist/client-ip.d.ts +36 -1
  38. package/dist/client-ip.js +80 -1
  39. package/dist/client-ip.js.map +1 -1
  40. package/dist/db/memory.js +2 -1
  41. package/dist/db/memory.js.map +1 -1
  42. package/dist/db/postgres.js +4 -1
  43. package/dist/db/postgres.js.map +1 -1
  44. package/dist/db/table.d.ts +37 -5
  45. package/dist/db/table.js +96 -15
  46. package/dist/db/table.js.map +1 -1
  47. package/dist/db/types.d.ts +2 -0
  48. package/dist/describe.d.ts +54 -2
  49. package/dist/describe.js +34 -3
  50. package/dist/describe.js.map +1 -1
  51. package/dist/events.d.ts +134 -0
  52. package/dist/events.js +196 -0
  53. package/dist/events.js.map +1 -0
  54. package/dist/fastify.d.ts +10 -2
  55. package/dist/fastify.js +22 -4
  56. package/dist/fastify.js.map +1 -1
  57. package/dist/h3.d.ts +76 -0
  58. package/dist/h3.js +61 -0
  59. package/dist/h3.js.map +1 -0
  60. package/dist/hapi.d.ts +45 -0
  61. package/dist/hapi.js +57 -0
  62. package/dist/hapi.js.map +1 -0
  63. package/dist/index.d.ts +25 -5
  64. package/dist/index.js +9 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/jobs.d.ts +70 -10
  67. package/dist/jobs.js +78 -13
  68. package/dist/jobs.js.map +1 -1
  69. package/dist/koa.d.ts +51 -0
  70. package/dist/koa.js +45 -0
  71. package/dist/koa.js.map +1 -0
  72. package/dist/lambda.d.ts +110 -0
  73. package/dist/lambda.js +169 -0
  74. package/dist/lambda.js.map +1 -0
  75. package/dist/langchain.d.ts +58 -0
  76. package/dist/langchain.js +220 -0
  77. package/dist/langchain.js.map +1 -0
  78. package/dist/mcp.d.ts +1 -1
  79. package/dist/mcp.js +7 -3
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/model.d.ts +23 -6
  82. package/dist/model.js +35 -6
  83. package/dist/model.js.map +1 -1
  84. package/dist/module.d.ts +52 -0
  85. package/dist/module.js +152 -0
  86. package/dist/module.js.map +1 -0
  87. package/dist/nextjs.d.ts +35 -0
  88. package/dist/nextjs.js +33 -0
  89. package/dist/nextjs.js.map +1 -0
  90. package/dist/openapi.d.ts +3 -0
  91. package/dist/openapi.js +12 -3
  92. package/dist/openapi.js.map +1 -1
  93. package/dist/permissions.d.ts +26 -0
  94. package/dist/permissions.js +78 -0
  95. package/dist/permissions.js.map +1 -0
  96. package/dist/rate-limit.d.ts +5 -3
  97. package/dist/rate-limit.js +3 -3
  98. package/dist/rate-limit.js.map +1 -1
  99. package/dist/react-router.d.ts +41 -0
  100. package/dist/react-router.js +42 -0
  101. package/dist/react-router.js.map +1 -0
  102. package/dist/redis-events.d.ts +27 -0
  103. package/dist/redis-events.js +163 -0
  104. package/dist/redis-events.js.map +1 -0
  105. package/dist/redis-jobs.d.ts +24 -0
  106. package/dist/redis-jobs.js +203 -0
  107. package/dist/redis-jobs.js.map +1 -0
  108. package/dist/redis.d.ts +30 -0
  109. package/dist/redis.js +106 -0
  110. package/dist/redis.js.map +1 -0
  111. package/dist/request-matcher.d.ts +4 -0
  112. package/dist/request-matcher.js +14 -0
  113. package/dist/request-matcher.js.map +1 -0
  114. package/dist/response-body.d.ts +6 -0
  115. package/dist/response-body.js +7 -0
  116. package/dist/response-body.js.map +1 -0
  117. package/dist/route.d.ts +50 -2
  118. package/dist/route.js +24 -0
  119. package/dist/route.js.map +1 -1
  120. package/dist/schema.d.ts +2 -2
  121. package/dist/serve.d.ts +26 -2
  122. package/dist/serve.js +111 -10
  123. package/dist/serve.js.map +1 -1
  124. package/dist/service-client.d.ts +91 -0
  125. package/dist/service-client.js +177 -0
  126. package/dist/service-client.js.map +1 -0
  127. package/dist/sessions.d.ts +74 -0
  128. package/dist/sessions.js +172 -0
  129. package/dist/sessions.js.map +1 -0
  130. package/dist/sveltekit.d.ts +72 -0
  131. package/dist/sveltekit.js +68 -0
  132. package/dist/sveltekit.js.map +1 -0
  133. package/dist/tracing.d.ts +122 -0
  134. package/dist/tracing.js +210 -0
  135. package/dist/tracing.js.map +1 -0
  136. package/llms.txt +8 -3
  137. package/package.json +78 -4
  138. package/templates/app/AGENTS.md +10 -6
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Typed calls from one Inferlo service to another (microservices, part 1).
3
+ *
4
+ * import { getTicket, createTicket } from "../../tickets/src/routes/tickets.ts"; // the other service's routes
5
+ * const tickets = serviceClient({ baseUrl: settings.TICKETS_URL, token: settings.TICKETS_TOKEN });
6
+ *
7
+ * // in a route handler: input and answer are typed from the route
8
+ * const ticket = await tickets.call(getTicket, { params: { id }, ctx });
9
+ *
10
+ * The route definition gives the method, the path and the types; nothing is generated and nothing is global.
11
+ * Passing ctx forwards the request id (X-Request-Id) and cancels the call when the request is aborted. With
12
+ * tracing: { api: otel }, every attempt is a client span that sends its W3C traceparent, so the other service's
13
+ * spans join the caller's trace.
14
+ */
15
+ import { InferloError } from "./errors.js";
16
+ import { checkTracing, clientTracing } from "./tracing.js";
17
+ /** Raised when a call to another service fails: remoteStatus is its HTTP status (0 when no answer came), remoteCode its error code. */
18
+ export class ServiceCallError extends InferloError {
19
+ remoteStatus;
20
+ remoteCode;
21
+ constructor(message, remoteStatus, remoteCode, fix, details) {
22
+ super("IL2008", message, { status: 502, expose: false, fix, details });
23
+ this.remoteStatus = remoteStatus;
24
+ this.remoteCode = remoteCode;
25
+ }
26
+ }
27
+ const IDEMPOTENT = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
28
+ const RETRY_STATUS = new Set([502, 503, 504]);
29
+ function invalid(message, fix) {
30
+ return new InferloError("IL2009", message, { status: 500, expose: false, fix });
31
+ }
32
+ function isRouteDef(value) {
33
+ return typeof value === "object" && value !== null && value.kind === "inferlo.route";
34
+ }
35
+ /** The path with :params filled in, each value URL-encoded. */
36
+ function fillPath(path, params, route) {
37
+ return path.replace(/:([A-Za-z0-9_]+)/g, (_, name) => {
38
+ const value = params?.[name];
39
+ if (value === undefined || value === null || value === "") {
40
+ throw invalid(`The call to ${route.method} ${route.path} has no value for the path parameter "${name}".`, `Pass it: client.call(route, { params: { ${name}: "..." } }).`);
41
+ }
42
+ return encodeURIComponent(String(value));
43
+ });
44
+ }
45
+ const sleep = (ms, signal) => new Promise((resolve) => {
46
+ const timer = setTimeout(resolve, ms);
47
+ signal?.addEventListener("abort", () => (clearTimeout(timer), resolve()), { once: true });
48
+ });
49
+ /** A client for calls to another Inferlo service. See ServiceClientOptions. */
50
+ export function serviceClient(options) {
51
+ if (typeof options !== "object" || options === null)
52
+ throw invalid("serviceClient() needs options.", 'Use serviceClient({ baseUrl: "http://tickets.internal" }).');
53
+ for (const key of Object.keys(options)) {
54
+ if (!["baseUrl", "token", "headers", "timeoutMs", "retries", "fetch", "tracing"].includes(key)) {
55
+ throw invalid(`serviceClient() has an unknown option "${key}".`, "The options are baseUrl, token, headers, timeoutMs, retries, fetch and tracing.");
56
+ }
57
+ }
58
+ let base;
59
+ try {
60
+ base = new URL(options.baseUrl);
61
+ }
62
+ catch {
63
+ throw invalid(`serviceClient() baseUrl ${JSON.stringify(options.baseUrl)} is not a URL.`, 'Use the address of the other service, such as "http://tickets.internal" or "https://api.example.com/v1".');
64
+ }
65
+ if (base.protocol !== "http:" && base.protocol !== "https:")
66
+ throw invalid(`serviceClient() baseUrl must use http or https, got ${base.protocol}.`, 'Use "http://..." or "https://...".');
67
+ if (base.search || base.hash)
68
+ throw invalid("serviceClient() baseUrl must not have a query string or fragment.", "Pass query values per call: client.call(route, { query }).");
69
+ const timeoutMs = options.timeoutMs ?? 10_000;
70
+ const retries = options.retries ?? 0;
71
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
72
+ throw invalid(`serviceClient() timeoutMs must be a positive number of milliseconds, got ${String(options.timeoutMs)}.`, "Use timeoutMs: 5000, or leave it out for 10 s.");
73
+ if (!Number.isInteger(retries) || retries < 0 || retries > 10)
74
+ throw invalid(`serviceClient() retries must be a whole number from 0 to 10, got ${String(options.retries)}.`, "Use retries: 2 for idempotent calls that may meet a restarting instance.");
75
+ const doFetch = options.fetch ?? ((request) => globalThis.fetch(request));
76
+ const basePath = base.pathname.replace(/\/+$/, "");
77
+ const tracingConfig = checkTracing(options.tracing, "serviceClient");
78
+ const tracing = tracingConfig ? clientTracing(tracingConfig, "inferlo") : undefined;
79
+ return {
80
+ async call(route, input = {}) {
81
+ if (!isRouteDef(route))
82
+ throw invalid("client.call() needs a route made with route.<method>(...).handle(...).", "Import the route from the other service and pass it: client.call(getTicket, { params: { id } }).");
83
+ const { params, query, body, ctx, signal, headers } = input;
84
+ const url = new URL(base.origin + basePath + fillPath(route.path, params, route));
85
+ for (const [k, v] of Object.entries(query ?? {})) {
86
+ if (v === undefined || v === null)
87
+ continue;
88
+ for (const item of Array.isArray(v) ? v : [v])
89
+ url.searchParams.append(k, String(item));
90
+ }
91
+ const method = route.method.toUpperCase();
92
+ const sent = { accept: "application/json", ...options.headers, ...headers };
93
+ if (options.token)
94
+ sent.authorization = `Bearer ${options.token}`;
95
+ if (ctx?.requestId)
96
+ sent["x-request-id"] = ctx.requestId;
97
+ const hasBody = body !== undefined && method !== "GET" && method !== "HEAD";
98
+ if (hasBody)
99
+ sent["content-type"] = "application/json";
100
+ const payload = hasBody ? JSON.stringify(body) : undefined;
101
+ const callerSignals = [ctx?.signal, signal].filter((s) => s !== undefined);
102
+ const callerSignal = callerSignals.length ? AbortSignal.any(callerSignals) : undefined;
103
+ const attempts = IDEMPOTENT.has(method) ? retries + 1 : 1;
104
+ const where = `${method} ${url.origin}${url.pathname}`;
105
+ const details = (extra) => ({ method, url: `${url.origin}${url.pathname}`, requestId: ctx?.requestId, ...extra });
106
+ for (let attempt = 1;; attempt++) {
107
+ const timeout = AbortSignal.timeout(timeoutMs);
108
+ const combined = callerSignal ? AbortSignal.any([timeout, callerSignal]) : timeout;
109
+ let span;
110
+ let headersSent = sent;
111
+ if (tracing) {
112
+ const attributes = { "http.request.method": method, "url.full": `${url.origin}${url.pathname}`, "url.template": route.path, "server.address": url.hostname };
113
+ if (url.port)
114
+ attributes["server.port"] = Number(url.port);
115
+ if (attempt > 1)
116
+ attributes["http.request.resend_count"] = attempt - 1;
117
+ span = tracing.start(`${method} ${route.path}`, attributes);
118
+ headersSent = { ...sent };
119
+ tracing.inject(span, headersSent);
120
+ }
121
+ let response;
122
+ try {
123
+ const request = new Request(url, { method, headers: headersSent, body: payload, signal: combined });
124
+ response = await (span ? tracing.within(span, () => doFetch(request)) : doFetch(request));
125
+ }
126
+ catch (error) {
127
+ const timedOut = timeout.aborted;
128
+ if (span)
129
+ tracing.fail(span, error, callerSignal?.aborted ? "cancelled" : timedOut ? "timeout" : undefined);
130
+ if (callerSignal?.aborted)
131
+ throw callerSignal.reason ?? error;
132
+ if (attempt < attempts) {
133
+ await sleep(100 * 2 ** (attempt - 1), callerSignal);
134
+ continue;
135
+ }
136
+ const failure = new ServiceCallError(timedOut ? `${where} did not answer within ${timeoutMs} ms.` : `${where} could not be reached: ${error instanceof Error ? error.message : String(error)}.`, 0, undefined, timedOut ? "Check that the service is healthy, or raise serviceClient({ timeoutMs })." : "Check serviceClient({ baseUrl }) and that the service is running and reachable.", details({ attempts: attempt }));
137
+ failure.cause = error;
138
+ throw failure;
139
+ }
140
+ span?.setAttribute("http.response.status_code", response.status);
141
+ if (response.ok) {
142
+ span?.end();
143
+ if (response.status === 204 || method === "HEAD")
144
+ return undefined;
145
+ const type = response.headers.get("content-type") ?? "";
146
+ if (/^application\/(.+\+)?json\b/i.test(type))
147
+ return (await response.json());
148
+ return response;
149
+ }
150
+ if (RETRY_STATUS.has(response.status) && attempt < attempts) {
151
+ if (span)
152
+ tracing.fail(span, new Error(`${where} answered ${response.status}.`), String(response.status));
153
+ await response.body?.cancel();
154
+ await sleep(100 * 2 ** (attempt - 1), callerSignal);
155
+ continue;
156
+ }
157
+ let remote;
158
+ try {
159
+ remote = (await response.json()).error;
160
+ }
161
+ catch {
162
+ remote = undefined;
163
+ }
164
+ const code = typeof remote?.code === "string" ? remote.code : undefined;
165
+ const failure = new ServiceCallError(`${where} answered ${response.status}${code ? ` ${code}` : ""}${remote?.message ? `: ${remote.message}` : ""}.`, response.status, code, response.status === 401 || response.status === 403
166
+ ? "Check serviceClient({ token }) and the other service's auth."
167
+ : response.status >= 500
168
+ ? "See the other service's logs for this request id."
169
+ : "Check the input against the other service's route, or handle the answer: catch the error and read error.remoteStatus.", details({ status: response.status, code, attempts: attempt }));
170
+ if (span)
171
+ tracing.fail(span, failure, String(response.status));
172
+ throw failure;
173
+ }
174
+ },
175
+ };
176
+ }
177
+ //# sourceMappingURL=service-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-client.js","sourceRoot":"","sources":["../src/service-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
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
+ /** The tenant of a user, for tenant-scoped models. Default: user.tenantId. */
55
+ tenant?: (user: AppUser) => string | null | undefined;
56
+ }
57
+ /** What session.signIn and session.signOut need from the route context. */
58
+ export interface SessionResponseContext {
59
+ url: URL;
60
+ header(name: string, value: string): void;
61
+ }
62
+ /** createApp({ auth: session }) with sign-in, sign-out and the CSRF token of the current session. */
63
+ export interface Sessions extends AuthConfig {
64
+ /** Start a session for this user: sets the session and CSRF cookies. Returns the CSRF token to send with changes. */
65
+ signIn(ctx: SessionResponseContext, userId: string): Promise<{
66
+ csrfToken: string;
67
+ }>;
68
+ /** End the session: clears both cookies. */
69
+ signOut(ctx: SessionResponseContext): void;
70
+ /** The CSRF token of the request's session (to put in a form's _csrf field), or null without a valid session. */
71
+ csrfToken(req: Request): Promise<string | null>;
72
+ }
73
+ /** Signed cookie sessions with CSRF protection: createApp({ auth: sessions({ secret, user }) }). */
74
+ export declare function sessions(options: SessionsOptions): Sessions;
@@ -0,0 +1,172 @@
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
+ tenant: options.tenant,
89
+ async signIn(ctx, userId) {
90
+ if (typeof userId !== "string" || !userId)
91
+ 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)." });
92
+ if (!ctx || typeof ctx.header !== "function" || !(ctx.url instanceof URL))
93
+ 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) })." });
94
+ const now = Date.now();
95
+ const sessionId = base64url(crypto.getRandomValues(new Uint8Array(18)));
96
+ const payload = base64url(new TextEncoder().encode(JSON.stringify({ u: userId, s: sessionId, i: now, e: now + maxAgeMs })));
97
+ const value = `v1.${payload}.${await sign(keys[0], `v1.${payload}`)}`;
98
+ const csrfToken = await csrfFor(sessionId);
99
+ const seconds = Math.floor(maxAgeMs / 1000);
100
+ ctx.header("set-cookie", `${name}=${value}; ${attributes(ctx.url, true, seconds)}`);
101
+ ctx.header("set-cookie", `${csrfName}=${csrfToken}; ${attributes(ctx.url, false, seconds)}`);
102
+ return { csrfToken };
103
+ },
104
+ signOut(ctx) {
105
+ if (!ctx || typeof ctx.header !== "function" || !(ctx.url instanceof URL))
106
+ throw new InferloError("IL6029", "session.signOut(ctx) needs the route context.", { fix: "Pass the handler's context: session.signOut(ctx)." });
107
+ ctx.header("set-cookie", `${name}=; ${attributes(ctx.url, true, 0)}`);
108
+ ctx.header("set-cookie", `${csrfName}=; ${attributes(ctx.url, false, 0)}`);
109
+ },
110
+ async csrfToken(req) {
111
+ const current = await read(req);
112
+ return current ? csrfFor(current.sessionId) : null;
113
+ },
114
+ };
115
+ return Object.freeze(session);
116
+ }
117
+ function readCookie(header, name) {
118
+ if (!header)
119
+ return undefined;
120
+ for (const part of header.split(";")) {
121
+ const i = part.indexOf("=");
122
+ if (i > 0 && part.slice(0, i).trim() === name)
123
+ return part.slice(i + 1).trim();
124
+ }
125
+ return undefined;
126
+ }
127
+ /** 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. */
128
+ async function formField(req, field) {
129
+ if (!(req.headers.get("content-type") ?? "").toLowerCase().includes("application/x-www-form-urlencoded"))
130
+ return null;
131
+ if (Number(req.headers.get("content-length") ?? "0") > MAX_FORM_BYTES)
132
+ return null;
133
+ // Cloned before req.body is touched: on Bun 1.3, a body stream read before clone() comes back empty.
134
+ const body = req.clone().body;
135
+ if (!body)
136
+ return null;
137
+ const reader = body.getReader();
138
+ const chunks = [];
139
+ let size = 0;
140
+ try {
141
+ while (size <= MAX_FORM_BYTES) {
142
+ const { done, value } = await reader.read();
143
+ if (done)
144
+ break;
145
+ chunks.push(value);
146
+ size += value.byteLength;
147
+ }
148
+ }
149
+ finally {
150
+ void reader.cancel().catch(() => { });
151
+ }
152
+ if (size > MAX_FORM_BYTES)
153
+ return null;
154
+ const bytes = new Uint8Array(size);
155
+ let offset = 0;
156
+ for (const chunk of chunks) {
157
+ bytes.set(chunk, offset);
158
+ offset += chunk.byteLength;
159
+ }
160
+ return new URLSearchParams(new TextDecoder().decode(bytes)).get(field);
161
+ }
162
+ function base64url(bytes) {
163
+ let text = "";
164
+ for (const b of bytes)
165
+ text += String.fromCharCode(b);
166
+ return btoa(text).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
167
+ }
168
+ function unbase64url(text) {
169
+ const padded = text.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (text.length % 4)) % 4);
170
+ return Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
171
+ }
172
+ //# 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;AAwD3C,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,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Inferlo inside SvelteKit (2 and 3) server routes, without a runtime dependency on SvelteKit:
3
+ *
4
+ * // src/routes/api/[...path]/+server.ts
5
+ * import { toSvelteKitHandlers } from "@browserlessapi/inferlo/sveltekit";
6
+ * import { app } from "$lib/server/inferlo";
7
+ *
8
+ * export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, fallback } = toSvelteKitHandlers(app, { prefix: "/api" });
9
+ *
10
+ * // src/hooks.server.ts: event.locals.ai in load functions, form actions and +server.ts files
11
+ * import { inferloAI } from "@browserlessapi/inferlo/sveltekit";
12
+ * export const handle = inferloAI(ai);
13
+ *
14
+ * It never loads the router, database or jobs code.
15
+ */
16
+ import type { AI } from "./ai/index.ts";
17
+ import type { App as InferloApp } from "./app.ts";
18
+ declare global {
19
+ namespace App {
20
+ interface Locals {
21
+ /** The AI client from the inferloAI() handle hook of @browserlessapi/inferlo/sveltekit. */
22
+ ai: AI;
23
+ }
24
+ }
25
+ }
26
+ /** The parts of a SvelteKit RequestEvent Inferlo uses. */
27
+ export interface SvelteKitEventLike {
28
+ /** The incoming Web request. */
29
+ request: Request;
30
+ /** The client IP from the adapter; called once per request, and errors (some adapters have none) are ignored. */
31
+ getClientAddress?: () => string;
32
+ /** event.locals, where the inferloAI() hook puts ai. */
33
+ locals: object;
34
+ }
35
+ /** A SvelteKit request handler, to export from a +server.ts file. */
36
+ export type SvelteKitRequestHandler = (event: SvelteKitEventLike) => Promise<Response>;
37
+ /** One request handler per HTTP method and a fallback for every other method, to export from +server.ts. */
38
+ export interface SvelteKitHandlers {
39
+ GET: SvelteKitRequestHandler;
40
+ POST: SvelteKitRequestHandler;
41
+ PUT: SvelteKitRequestHandler;
42
+ PATCH: SvelteKitRequestHandler;
43
+ DELETE: SvelteKitRequestHandler;
44
+ HEAD: SvelteKitRequestHandler;
45
+ OPTIONS: SvelteKitRequestHandler;
46
+ fallback: SvelteKitRequestHandler;
47
+ }
48
+ export interface SvelteKitHandlersOptions {
49
+ /**
50
+ * The URL path of the route folder, removed before the app routes the request: "/api" for
51
+ * src/routes/api/[...path]/+server.ts. Requests outside it keep their path. Include SvelteKit's paths.base if set.
52
+ */
53
+ prefix?: string;
54
+ }
55
+ /** A SvelteKit handle hook: export const handle = inferloAI(ai), or sequence(inferloAI(ai), ...) to combine it. */
56
+ export type SvelteKitHandle = (input: {
57
+ event: {
58
+ locals: object;
59
+ };
60
+ resolve: (event: any, opts?: any) => Response | Promise<Response>;
61
+ }) => Response | Promise<Response>;
62
+ /**
63
+ * Request handlers that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP) from a
64
+ * SvelteKit rest route: export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, fallback } = toSvelteKitHandlers(app).
65
+ */
66
+ export declare function toSvelteKitHandlers(app: Pick<InferloApp<any>, "fetch">, options?: SvelteKitHandlersOptions): SvelteKitHandlers;
67
+ /**
68
+ * A SvelteKit handle hook that puts an AI client (createAI(...) or an Inferlo app's app.ai) on every request as
69
+ * event.locals.ai, for load functions, form actions and +server.ts files: export const handle = inferloAI(ai) in
70
+ * src/hooks.server.ts, or sequence(inferloAI(ai), yourHandle).
71
+ */
72
+ export declare function inferloAI(ai: AI): SvelteKitHandle;
@@ -0,0 +1,68 @@
1
+ import { assertAIClient } from "./ai-client.js";
2
+ import { clientIps } from "./client-ip.js";
3
+ import { InferloError } from "./errors.js";
4
+ import { normalizePrefix, stripPrefix } from "./serve.js";
5
+ const fix = 'Pass the app from createApp and the route folder\'s path: export const { GET, POST, fallback } = toSvelteKitHandlers(app, { prefix: "/api" }).';
6
+ /**
7
+ * Request handlers that run a whole Inferlo app (routes, auth, validation, rate limits, OpenAPI and MCP) from a
8
+ * SvelteKit rest route: export const { GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, fallback } = toSvelteKitHandlers(app).
9
+ */
10
+ export function toSvelteKitHandlers(app, options = {}) {
11
+ if (!app || typeof app.fetch !== "function") {
12
+ throw new InferloError("IL5022", "toSvelteKitHandlers() needs an Inferlo app (the value returned by createApp).", { fix });
13
+ }
14
+ if (options === null || typeof options !== "object" || (options.prefix !== undefined && (typeof options.prefix !== "string" || !options.prefix.startsWith("/")))) {
15
+ throw new InferloError("IL5022", 'toSvelteKitHandlers() prefix must be a path that starts with /, such as "/api".', { fix });
16
+ }
17
+ const prefix = normalizePrefix(options.prefix);
18
+ const handle = async (event) => {
19
+ const incoming = event?.request;
20
+ if (!(incoming instanceof Request)) {
21
+ throw new InferloError("IL5022", "toSvelteKitHandlers()'s handlers were called with something that is not a SvelteKit request event.", {
22
+ fix: "Export them from a +server.ts file so SvelteKit calls them: export const { GET, POST, fallback } = toSvelteKitHandlers(app, { prefix: \"/api\" }).",
23
+ });
24
+ }
25
+ let request = incoming;
26
+ if (prefix) {
27
+ const url = new URL(incoming.url);
28
+ const stripped = stripPrefix(url.pathname, prefix);
29
+ if (stripped !== null) {
30
+ url.pathname = stripped;
31
+ const hasBody = incoming.body !== null && incoming.method !== "GET" && incoming.method !== "HEAD";
32
+ const init = {
33
+ method: incoming.method,
34
+ headers: incoming.headers,
35
+ body: hasBody ? incoming.body : undefined,
36
+ signal: incoming.signal,
37
+ redirect: incoming.redirect,
38
+ };
39
+ if (hasBody)
40
+ init.duplex = "half";
41
+ request = new Request(url, init);
42
+ }
43
+ }
44
+ try {
45
+ const ip = event.getClientAddress?.();
46
+ if (typeof ip === "string" && ip)
47
+ clientIps.set(request, ip);
48
+ }
49
+ catch {
50
+ // The adapter cannot tell the client address (for example while prerendering).
51
+ }
52
+ return app.fetch(request);
53
+ };
54
+ return Object.freeze({ GET: handle, POST: handle, PUT: handle, PATCH: handle, DELETE: handle, HEAD: handle, OPTIONS: handle, fallback: handle });
55
+ }
56
+ /**
57
+ * A SvelteKit handle hook that puts an AI client (createAI(...) or an Inferlo app's app.ai) on every request as
58
+ * event.locals.ai, for load functions, form actions and +server.ts files: export const handle = inferloAI(ai) in
59
+ * src/hooks.server.ts, or sequence(inferloAI(ai), yourHandle).
60
+ */
61
+ export function inferloAI(ai) {
62
+ assertAIClient(ai, 'Pass createAI({ providers, models }) from "@browserlessapi/inferlo/ai", or an Inferlo app\'s app.ai: export const handle = inferloAI(ai).');
63
+ return ({ event, resolve }) => {
64
+ event.locals.ai = ai;
65
+ return resolve(event);
66
+ };
67
+ }
68
+ //# sourceMappingURL=sveltekit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sveltekit.js","sourceRoot":"","sources":["../src/sveltekit.ts"],"names":[],"mappings":"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"}