feat: improve worker routing and auth startup

This commit is contained in:
2026-06-22 18:26:39 -07:00
parent 8216b79964
commit 23cd3f7605
21 changed files with 2682 additions and 43 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import type { Context } from "hono";
import { Stripe } from "stripe";
import { handleStripeInvoicePaid } from "@/external/stripe/webhookHandlers/handleStripeInvoicePaid/handleStripeInvoicePaid.js";
import { handleStripeSubscriptionUpdated } from "@/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/handleStripeSubscriptionUpdated.js";
import { unsetOrgStripeKeys } from "@/internal/orgs/orgUtils.js";
import { handleWebhookErrorSkip } from "@/utils/routerUtils/webhookErrorSkip.js";
import { captureException, getSentryTags } from "../sentry/sentryUtils.js";
import { isStripeError } from "./stripeErrorUtils.js";
import { handleCusDiscountDeleted } from "./webhookHandlers/handleCusDiscountDeleted.js";
import { handleStripeCustomerUpdated } from "./webhookHandlers/handleStripeCustomerUpdated.js";
import { handleInvoiceUpdated } from "./webhookHandlers/handleInvoiceUpdated.js";
@@ -103,7 +103,7 @@ export const handleStripeWebhookEvent = async (
}),
});
if (error instanceof Stripe.errors.StripeError) {
if (isStripeError(error)) {
if (error.message.includes("No such customer")) {
logger.warn(`stripe customer missing: ${error.message}`);
return c.json({ success: true }, 200);

View File

@@ -0,0 +1,11 @@
import type Stripe from "stripe";
export const isStripeError = (error: unknown): error is Stripe.errors.StripeError => {
if (!(error instanceof Error)) return false;
const stripeError = error as Partial<Stripe.errors.StripeError>;
return (
typeof stripeError.type === "string" &&
stripeError.type.startsWith("Stripe")
);
};

View File

@@ -9,11 +9,14 @@ export type TinybirdConfig = {
export let tinybirdConfig: TinybirdConfig | null = null;
export const initTinybirdConfig = (env: Env) => {
const baseUrl = env.TINYBIRD_US_EAST_API_URL ?? env.TINYBIRD_API_URL;
const token = env.TINYBIRD_US_EAST_TOKEN ?? env.TINYBIRD_TOKEN;
tinybirdConfig =
env.TINYBIRD_API_URL && env.TINYBIRD_TOKEN
baseUrl && token
? {
baseUrl: env.TINYBIRD_API_URL,
token: env.TINYBIRD_TOKEN,
baseUrl,
token,
}
: null;
};

View File

@@ -1,9 +1,9 @@
import { ErrCode, RecaseError } from "@autumn/shared";
import type { Context } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import Stripe from "stripe";
import { ZodError } from "zod/v4";
import { formatZodError } from "@/errors/formatZodError.js";
import { isStripeError } from "@/external/stripe/stripeErrorUtils.js";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import {
captureException,
@@ -68,17 +68,8 @@ export const errorMiddleware = (err: Error, c: Context<HonoEnv>) => {
);
}
// If we got here, it's an error worth tracking - capture to Sentry
captureException(err, {
tags: getSentryTags({
ctx,
path: c.req.path,
method: c.req.method,
}),
});
// 2. Handle Stripe errors
if (err instanceof Stripe.errors.StripeError) {
if (isStripeError(err)) {
logger.error(
`STRIPE ERROR (${ctx.org?.slug || "unknown"}): ${err.message}`,
{
@@ -121,6 +112,15 @@ export const errorMiddleware = (err: Error, c: Context<HonoEnv>) => {
);
}
// If we got here, it's an error worth tracking - capture to Sentry
captureException(err, {
tags: getSentryTags({
ctx,
path: c.req.path,
method: c.req.method,
}),
});
// 4. Handle unknown errors
logger.error(
`UNKNOWN ERROR (${ctx.org?.slug || "unknown"}): ${err.message}`,

View File

@@ -1,9 +1,10 @@
import { ErrCode, RecaseError } from "@autumn/shared";
import type { Context } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import Stripe from "stripe";
import type Stripe from "stripe";
import { ZodError } from "zod/v4";
import { formatZodError } from "@/errors/formatZodError.js";
import { isStripeError } from "@/external/stripe/stripeErrorUtils.js";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import { matchRoute } from "./middlewareUtils.js";
@@ -57,7 +58,7 @@ const STRIPE_RULES = [
{
name: "Stripe rate limit exceeded",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
(err.type === "StripeRateLimitError" || err.statusCode === 429),
statusCode: 429,
code: "stripe_rate_limit_exceeded",
@@ -65,7 +66,7 @@ const STRIPE_RULES = [
{
name: "Exchange router invalid API key",
match: (err: Error, c: Context<HonoEnv>) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
c.req.url.includes("/exchange") &&
err.message.includes("Invalid API Key provided"),
statusCode: 400,
@@ -74,7 +75,7 @@ const STRIPE_RULES = [
{
name: "Billing portal config error",
match: (err: Error, c: Context<HonoEnv>) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
c.req.url.includes("/billing_portal") &&
err.message.includes("Provide a configuration or create your default"),
statusCode: 404,
@@ -83,7 +84,7 @@ const STRIPE_RULES = [
{
name: "Billing portal return_url error",
match: (err: Error, c: Context<HonoEnv>) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
c.req.url.includes("/billing_portal") &&
err.message.includes("Invalid URL: An explicit scheme (such as https)"),
statusCode: 400,
@@ -92,15 +93,14 @@ const STRIPE_RULES = [
{
name: "Card declined error",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
err.message.includes("Your card was declined."),
isStripeError(err) && err.message.includes("Your card was declined."),
statusCode: 400,
code: ErrCode.InvalidRequest,
},
{
name: "Cannot delete org with production customers",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
err.message.includes("Cannot delete org with production mode customers"),
statusCode: 400,
code: ErrCode.InvalidRequest,
@@ -108,7 +108,7 @@ const STRIPE_RULES = [
{
name: "Webhook endpoint limit reached",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
err.message.includes(
"You have reached the maximum of 16 test webhook endpoints",
),
@@ -118,7 +118,7 @@ const STRIPE_RULES = [
{
name: "Invalid URL scheme error",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
isStripeError(err) &&
err.message.includes(
"Invalid URL: An explicit scheme (such as https) must be provided",
),
@@ -128,8 +128,7 @@ const STRIPE_RULES = [
{
name: "Not a valid URL error",
match: (err: Error) =>
err instanceof Stripe.errors.StripeError &&
err.message.includes("Not a valid URL"),
isStripeError(err) && err.message.includes("Not a valid URL"),
statusCode: 400,
code: ErrCode.InvalidRequest,
},

View File

@@ -66,10 +66,17 @@ const memoizeRouter = (createRouter: (env: Env) => LazyRouter) => {
};
const routeToRouter =
(getRouter: (env: Env) => LazyRouter) => async (c: Context<HonoEnv>) => {
(
getRouter: (env: Env) => LazyRouter,
options?: { stripPrefix?: string },
) =>
async (c: Context<HonoEnv>) => {
const router = getRouter(c.env);
const request = options?.stripPrefix
? rewriteMountedRequestPath(c.req.raw, options.stripPrefix)
: c.req.raw;
return router.fetch(
c.req.raw,
request,
c.env,
c.executionCtx as ExecutionContext<unknown>,
);
@@ -79,12 +86,49 @@ const mountRouter = (
app: Hono<HonoEnv>,
path: string,
getRouter: (env: Env) => LazyRouter,
options?: { stripPrefix?: boolean },
) => {
const handler = routeToRouter(getRouter);
const handler = routeToRouter(
getRouter,
options?.stripPrefix ? { stripPrefix: path } : undefined,
);
app.all(path, handler);
app.all(`${path}/*`, handler);
};
export const rewriteMountedRequestPath = (
request: Request,
mountPath: string,
): Request => {
const url = new URL(request.url);
const suffix =
url.pathname === mountPath ? "" : url.pathname.slice(mountPath.length);
url.pathname = suffix || "/";
return new Request(url, request);
};
export const rewriteAutumnAuthRequest = (request: Request): Request => {
const url = new URL(request.url);
url.pathname = `/api/auth/autumn${url.pathname.slice("/api/autumn".length)}`;
return new Request(url, request);
};
export const isPublicDevRoute = (method: string, pathname: string): boolean => {
if (method === "POST" && pathname === "/dev/cli/stripe") return true;
return method === "GET" && /^\/dev\/otp\/[^/]+$/.test(pathname);
};
const handleAuthRequest = async (c: Context<HonoEnv>, request: Request) => {
const requestDb = createRequestScopedDb(c.env);
try {
return await createAuth(c.env, {
db: requestDb.db,
}).handler(request);
} finally {
c.executionCtx.waitUntil(requestDb.dispose());
}
};
export const createHonoApp = (_env: Env) => {
const app = new Hono<HonoEnv>();
const getChatProxyRouter = memoizeRouter((env) => createChatProxyRouter(env));
@@ -138,15 +182,12 @@ export const createHonoApp = (_env: Env) => {
return handleListAuthOrganizations(c);
});
app.on(["POST", "GET"], "/api/autumn/*", async (c) => {
return handleAuthRequest(c, rewriteAutumnAuthRequest(c.req.raw));
});
app.on(["POST", "GET"], "/api/auth/*", async (c) => {
const requestDb = createRequestScopedDb(c.env);
try {
return await createAuth(c.env, {
db: requestDb.db,
}).handler(c.req.raw);
} finally {
c.executionCtx.waitUntil(requestDb.dispose());
}
return handleAuthRequest(c, c.req.raw);
});
app.get("/ready/:token", handleReadyCheck);
@@ -170,6 +211,14 @@ export const createHonoApp = (_env: Env) => {
});
mountRouter(app, "/cli", getCliRouter);
const publicRouterHandler = routeToRouter(getPublicRouter);
app.all("/dev/otp/:otp", async (c, next) => {
if (!isPublicDevRoute(c.req.method, new URL(c.req.url).pathname)) {
return next();
}
return publicRouterHandler(c);
});
app.post("/dev/cli/stripe", publicRouterHandler);
// Add Render region identifier header for load balancer verification
app.use("*", async (c, next) => {
@@ -189,10 +238,9 @@ export const createHonoApp = (_env: Env) => {
mountRouter(app, "/checkouts", getPublicRouter);
mountRouter(app, "/invoices", getPublicRouter);
mountRouter(app, "/dev", getPublicRouter);
mountRouter(app, "/trmnl", getPublicRouter);
mountRouter(app, "/v1", getApiRouter);
mountRouter(app, "/v1", getApiRouter, { stripPrefix: true });
for (const path of [
"/admin",
@@ -200,6 +248,7 @@ export const createHonoApp = (_env: Env) => {
"/invoice_templates",
"/products",
"/customers",
"/dev",
"/consents",
"/pricing-agent",
"/feedback",

View File

@@ -5,6 +5,7 @@ import { analyticsMiddleware } from "../honoMiddlewares/analyticsMiddleware";
import { apiVersionMiddleware } from "../honoMiddlewares/apiVersionMiddleware";
import { baseMiddleware } from "../honoMiddlewares/baseMiddleware.js";
import { betterAuthMiddleware } from "../honoMiddlewares/betterAuthMiddleware";
import { errorMiddleware } from "../honoMiddlewares/errorMiddleware.js";
import { orgConfigMiddleware } from "../honoMiddlewares/orgConfigMiddleware";
import { queryMiddleware } from "../honoMiddlewares/queryMiddleware";
import { refreshCacheMiddleware } from "../honoMiddlewares/refreshCacheMiddleware";
@@ -58,5 +59,7 @@ export const createInternalRouter = () => {
internalRouter.route("", migrationRpcRouter);
internalRouter.route("/workbench", workbenchRouter);
internalRouter.onError(errorMiddleware);
return internalRouter;
};

View File

@@ -24,6 +24,7 @@ import { createAfterSessionCreated } from "./authUtils/afterSessionCreated.js";
import { createAfterSessionDeleted } from "./authUtils/afterSessionDeleted.js";
import { createBeforeSessionCreated } from "./authUtils/beforeSessionCreated.js";
import { getScopesForUserInOrg } from "./authUtils/customSessionScopes.js";
import { createAutumnBetterAuthPlugin } from "./autumnBetterAuthPlugin.js";
import { ADMIN_USER_IDs } from "./constants.js";
import { isAllowedOrigin } from "./corsOrigins.js";
import { ensureHeadersGetSetCookie } from "./headersGetSetCookiePolyfill.js";
@@ -104,6 +105,30 @@ export const getAuthCookieAdvancedOptions = (env: Env) => {
};
};
const isLoopbackUrl = (rawUrl?: string) => {
if (!rawUrl) return false;
try {
const { hostname } = new URL(rawUrl);
return hostname === "localhost" || hostname === "127.0.0.1";
} catch {
return false;
}
};
export const getAutumnBetterAuthApiUrl = (env: Env) => {
const configuredUrl = env.BETTER_AUTH_URL ?? env.SERVER_URL;
if (
env.NODE_ENV === "production" &&
env.WORKER === "true" &&
isLoopbackUrl(configuredUrl)
) {
return "https://autumn-api.bowong.cc";
}
return configuredUrl;
};
export const createJwtOptions = () => ({
disableSettingJwtHeader: true,
});
@@ -376,6 +401,11 @@ export const createAuth = (env: Env, overrides: { db: DrizzleCli }) => {
},
},
}),
createAutumnBetterAuthPlugin({
env,
autumnURL: getAutumnBetterAuthApiUrl(env),
}),
],
} satisfies BetterAuthOptions;

View File

@@ -0,0 +1,170 @@
import { createAuthEndpoint } from "@better-auth/core/api";
import { Autumn } from "@useautumn/sdk";
import { getSessionFromCtx } from "better-auth/api";
type BetterAuthSession = {
user?: {
id: string;
name?: string | null;
email?: string | null;
};
session?: {
activeOrganizationId?: string | null;
};
};
type BetterAuthOrganization = {
id: string;
name?: string | null;
};
const getActiveOrganization = async (
ctx: unknown,
session: BetterAuthSession | null,
): Promise<BetterAuthOrganization | null> => {
const organizationId = session?.session?.activeOrganizationId;
if (!organizationId) return null;
const context = (ctx as { context?: { adapter?: unknown } }).context;
const adapter = context?.adapter as
| {
findOne: (params: {
model: string;
where: { field: string; value: string }[];
}) => Promise<BetterAuthOrganization | null>;
}
| undefined;
return (
(await adapter?.findOne({
model: "organization",
where: [{ field: "id", value: organizationId }],
})) ?? null
);
};
const resolveIdentity = (
session: BetterAuthSession | null,
organization: BetterAuthOrganization | null,
) => {
if (organization) {
return {
customerId: organization.id,
customerData: { name: organization.name ?? undefined },
};
}
if (!session?.user) return null;
return {
customerId: session.user.id,
customerData: {
name: session.user.name ?? undefined,
email: session.user.email ?? undefined,
},
};
};
const readBodyObject = (body: unknown): Record<string, unknown> => {
return body && typeof body === "object" && !Array.isArray(body)
? (body as Record<string, unknown>)
: {};
};
const jsonError = (error: unknown) => {
if (
error &&
typeof error === "object" &&
"statusCode" in error &&
"body" in error
) {
const autumnError = error as {
statusCode: number;
body: string;
message?: string;
};
const body = (() => {
try {
return JSON.parse(autumnError.body);
} catch {
return {
message: autumnError.message ?? "Request failed",
code: "autumn_request_failed",
statusCode: autumnError.statusCode,
};
}
})();
return {
status: autumnError.statusCode,
body,
};
}
return {
status: 500,
body: {
message: error instanceof Error ? error.message : "Request failed",
code: "autumn_request_failed",
statusCode: 500,
},
};
};
export const createAutumnBetterAuthPlugin = ({
env,
autumnURL,
}: {
env: Env;
autumnURL?: string;
}) => ({
id: "autumn",
endpoints: {
getOrCreateCustomer: createAuthEndpoint(
"/autumn/getOrCreateCustomer",
{ method: "POST" },
async (ctx) => {
const session = (await getSessionFromCtx(
ctx as Parameters<typeof getSessionFromCtx>[0],
)) as BetterAuthSession | null;
const organization = await getActiveOrganization(ctx, session);
const identity = resolveIdentity(session, organization);
const body = readBodyObject((ctx as { body?: unknown }).body);
if (!identity?.customerId && body.errorOnNotFound === false) {
return ctx.json(null, { status: 204 });
}
if (!identity?.customerId) {
return ctx.json(
{
message: "customerId not found",
code: "no_customer_id",
statusCode: 401,
},
{ status: 401 },
);
}
try {
const autumn = new Autumn({
secretKey: env.AUTUMN_SECRET_KEY,
...(autumnURL && { serverURL: autumnURL }),
});
const expand = Array.isArray(body.expand) ? body.expand : [];
const customer = await autumn.customers.getOrCreate({
...body,
customerId: identity.customerId,
...identity.customerData,
expand: [...expand, "balances.feature"],
});
return ctx.json(customer);
} catch (error) {
const result = jsonError(error);
return ctx.json(result.body, { status: result.status });
}
},
),
},
});

View File

@@ -26,3 +26,54 @@ test("error middleware returns RecaseError status before request context exists"
code: ErrCode.NoAuthHeader,
});
});
test("error middleware maps Stripe-shaped errors to a 400", async () => {
const app = new Hono<HonoEnv>();
const consoleErrors: unknown[][] = [];
const originalConsoleError = console.error;
console.error = (...args: unknown[]) => {
consoleErrors.push(args);
};
app.use("*", async (c, next) => {
c.set("ctx", {
env: "sandbox",
logger: {
error: () => {},
warn: () => {},
},
} as never);
await next();
});
app.post("/v1/rewards", () => {
const error = new Error("No such product: prod_missing") as Error & {
type: string;
statusCode: number;
code: string;
};
error.type = "StripeInvalidRequestError";
error.statusCode = 400;
error.code = "resource_missing";
throw error;
});
app.onError(errorMiddleware);
try {
const response = await app.fetch(
new Request("https://autumn-api.bowong.cc/v1/rewards", {
method: "POST",
}),
);
expect(response.status).toBe(400);
await expect(response.json()).resolves.toMatchObject({
message: "(Stripe Error) No such product: prod_missing",
code: ErrCode.StripeError,
env: "sandbox",
});
expect(consoleErrors).toEqual([]);
} finally {
console.error = originalConsoleError;
}
});

View File

@@ -0,0 +1,31 @@
import { expect, test } from "bun:test";
import { rewriteAutumnAuthRequest } from "@/initHono.js";
test("legacy Autumn SDK route rewrites to the better-auth Autumn endpoint", async () => {
const request = new Request(
"https://autumn-api.bowong.cc/api/autumn/getOrCreateCustomer?expand=balances",
{
method: "POST",
headers: {
Cookie: "__Secure-better-auth.session_token=session-token",
Origin: "http://192.168.0.15:3000",
"Content-Type": "application/json",
},
body: JSON.stringify({ errorOnNotFound: false }),
},
);
const rewritten = rewriteAutumnAuthRequest(request);
const rewrittenUrl = new URL(rewritten.url);
expect(rewrittenUrl.pathname).toBe(
"/api/auth/autumn/getOrCreateCustomer",
);
expect(rewrittenUrl.search).toBe("?expand=balances");
expect(rewritten.method).toBe("POST");
expect(rewritten.headers.get("cookie")).toContain(
"__Secure-better-auth.session_token=session-token",
);
const body = (await rewritten.json()) as { errorOnNotFound: boolean };
expect(body).toMatchObject({ errorOnNotFound: false });
});

View File

@@ -3,6 +3,7 @@ import {
createEmailOtpOptions,
createEmailVerificationOptions,
createJwtOptions,
getAutumnBetterAuthApiUrl,
getAuthCookieAdvancedOptions,
getAuthTrustedOrigins,
} from "@/utils/auth.js";
@@ -76,6 +77,17 @@ test("production Worker auth cookies are cross-site secure even when base URL en
});
});
test("production Worker Autumn better-auth API URL falls back from stale localhost config", () => {
expect(
getAutumnBetterAuthApiUrl({
NODE_ENV: "production",
WORKER: "true",
BETTER_AUTH_URL: "http://localhost:8080",
SERVER_URL: "http://localhost:8080",
} as unknown as Env),
).toBe("https://autumn-api.bowong.cc");
});
test("session responses do not mint unused JWT headers", () => {
expect(createJwtOptions()).toEqual({
disableSettingJwtHeader: true,

View File

@@ -0,0 +1,8 @@
import { readFileSync } from "node:fs";
import { expect, test } from "bun:test";
test("auth setup does not import the generated Autumn better-auth plugin at startup", () => {
const source = readFileSync("src/utils/auth.ts", "utf8");
expect(source).not.toContain("autumn-js/better-auth");
});

View File

@@ -0,0 +1,74 @@
import { expect, test } from "bun:test";
import {
createHonoApp,
isPublicDevRoute,
rewriteMountedRequestPath,
} from "@/initHono.js";
const createExecutionContext = (): ExecutionContext =>
({
waitUntil: () => undefined,
passThroughOnException: () => undefined,
props: {},
}) as unknown as ExecutionContext;
test("mounted API router requests strip the /v1 prefix before reaching apiRouter", async () => {
const request = new Request(
"https://autumn-api.bowong.cc/v1/organization/flags?env=sandbox",
{
method: "GET",
headers: {
Accept: "application/json",
},
},
);
const rewritten = rewriteMountedRequestPath(request, "/v1");
const rewrittenUrl = new URL(rewritten.url);
expect(rewrittenUrl.pathname).toBe("/organization/flags");
expect(rewrittenUrl.search).toBe("?env=sandbox");
expect(rewritten.method).toBe("GET");
expect(rewritten.headers.get("accept")).toBe("application/json");
});
test("mounted API router root requests rewrite to /", () => {
const request = new Request("https://autumn-api.bowong.cc/v1");
const rewritten = rewriteMountedRequestPath(request, "/v1");
expect(new URL(rewritten.url).pathname).toBe("/");
});
test("only unauthenticated dev routes stay on the public router", () => {
expect(isPublicDevRoute("GET", "/dev/otp/123456")).toBe(true);
expect(isPublicDevRoute("POST", "/dev/cli/stripe")).toBe(true);
expect(isPublicDevRoute("GET", "/dev/data")).toBe(false);
expect(isPublicDevRoute("POST", "/dev/otp")).toBe(false);
expect(isPublicDevRoute("POST", "/dev/api_key")).toBe(false);
});
test("mounted internal router errors use the app error middleware", async () => {
const env = {
AWS_REGION: "test",
BETTER_AUTH_SECRET: "test-secret",
BETTER_AUTH_URL: "https://autumn-api.bowong.cc",
CLIENT_URL: "https://app.bowong.cc",
DATABASE_URL: "postgres://autumn:autumn@127.0.0.1:5432/autumn",
NODE_ENV: "test",
} as Env;
const app = createHonoApp(env);
const response = await app.fetch(
new Request("https://autumn-api.bowong.cc/query/event_names/list"),
env,
createExecutionContext(),
);
expect(response.status).toBe(401);
await expect(response.json()).resolves.toMatchObject({
message: "Unauthorized - no session found",
code: "no_auth_header",
});
});

View File

@@ -1,6 +1,9 @@
import { expect, test } from "bun:test";
import { getTinybirdPipes } from "@/external/tinybird/initTinybird.js";
import { initTinybirdConfig } from "@/external/tinybird/tinybirdUtils.js";
import {
initTinybirdConfig,
tinybirdConfig,
} from "@/external/tinybird/tinybirdUtils.js";
test("Tinybird pipes initialize after Worker env config arrives", () => {
initTinybirdConfig({
@@ -10,3 +13,17 @@ test("Tinybird pipes initialize after Worker env config arrives", () => {
expect(() => getTinybirdPipes()).not.toThrow();
});
test("Tinybird config prefers the new us-east workspace when both envs exist", () => {
initTinybirdConfig({
TINYBIRD_API_URL: "https://api.tinybird.co",
TINYBIRD_TOKEN: "legacy-token",
TINYBIRD_US_EAST_API_URL: "https://api.us-east.aws.tinybird.co",
TINYBIRD_US_EAST_TOKEN: "new-token",
} as Env);
expect(tinybirdConfig).toEqual({
baseUrl: "https://api.us-east.aws.tinybird.co",
token: "new-token",
});
});

View File

@@ -50,6 +50,9 @@
"autumn-js": [
"../packages/autumn-js/src/sdk/index.ts"
],
"autumn-js/better-auth": [
"../packages/autumn-js/src/better-auth/index.ts"
],
"autumn-js/backend/hono": [
"../packages/autumn-js/src/backend/adapters/hono.ts"
],

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +1,3 @@
VITE_FRONTEND_URL=https://autumn-api.bowong.cc
VITE_BACKEND_URL=https://autumn-api.bowong.cc
AUTUMN_API_KEY=sk_test_51SBUgtGv9HLu1VZC7T2KRIfwcmcxlUmodws3pddPIUDtgOI5998PTHenfEOkzXBuk88lFKGP1ZxPIya7GZgDAjBN00vLLZoZTW

View File

@@ -33,6 +33,7 @@ export function OnboardingLayout() {
"$1",
)}
includeCredentials={true}
useBetterAuth={true}
>
<div className="w-screen h-screen flex items-center justify-center bg-background">
<LoadingScreen />
@@ -54,6 +55,7 @@ export function OnboardingLayout() {
"$1",
)}
includeCredentials={true}
useBetterAuth={true}
>
<NuqsAdapter>
<main className="w-screen h-screen bg-background">

View File

@@ -73,6 +73,7 @@ export function MainLayout() {
<AutumnProvider
backendUrl={import.meta.env.VITE_BACKEND_URL}
includeCredentials={true}
useBetterAuth={true}
>
<div className="w-screen h-screen flex bg-outer-background">
<div className="hidden sm:flex">
@@ -100,6 +101,7 @@ export function MainLayout() {
backendUrl={import.meta.env.VITE_BACKEND_URL}
// backendUrl="http://localhost:8080"
includeCredentials={true}
useBetterAuth={true}
>
<NuqsAdapter>
<PortalContainerContext.Provider value={containerRef}>