Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,21 @@
"types": "./dist/libraries/backend/index.d.ts",
"require": "./dist/libraries/backend/index.js",
"import": "./dist/libraries/backend/index.mjs"
},
"./sveltekit": {
"types": "./dist/libraries/backend/sveltekit.d.ts",
"require": "./dist/libraries/backend/sveltekit.js",
"import": "./dist/libraries/backend/sveltekit.mjs"
},
"./svelte": {
"svelte": "./dist/libraries/svelte/index.js",
"types": "./dist/libraries/svelte/index.d.ts",
"require": "./dist/libraries/svelte/index.js",
"import": "./dist/libraries/svelte/index.mjs"
},
"./svelte/AutumnProvider.svelte": {
"svelte": "./dist/libraries/svelte/AutumnProvider.svelte",
"default": "./dist/libraries/svelte/AutumnProvider.svelte"
}
},
"keywords": [
Expand Down Expand Up @@ -140,12 +155,16 @@
"tsconfig-paths": "^4.2.0",
"tsup": "^8.4.0",
"tsx": "^4.19.3",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"svelte": "^5.28.2",
"@sveltejs/kit": "^2.21.5"
},
"peerDependencies": {
"better-auth": "^1.3.17",
"better-call": "^1.0.12",
"convex": "^1.25.4"
"convex": "^1.25.4",
"svelte": "^5.0.0",
"@sveltejs/kit": "^2.0.0"
},
"peerDependenciesMeta": {
"react": {
Expand All @@ -156,6 +175,12 @@
},
"better-call": {
"optional": true
},
"svelte": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
}
},
"dependencies": {
Expand Down Expand Up @@ -192,6 +217,12 @@
],
"supabase": [
"./dist/libraries/backend/supabase.d.ts"
],
"sveltekit": [
"./dist/libraries/backend/sveltekit.d.ts"
],
"svelte": [
"./dist/libraries/svelte/index.d.ts"
]
}
}
Expand Down
28 changes: 27 additions & 1 deletion package/scripts/post-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,32 @@ function processInjectedCss() {
});
}

// Copy Svelte component files to dist (they need to be processed by user's Svelte compiler)
function copySvelteFiles() {
const svelteSourceDir = path.join(__dirname, '../src/libraries/svelte');
const svelteDistDir = path.join(__dirname, '../dist/libraries/svelte');

// Ensure dist directory exists
if (!fs.existsSync(svelteDistDir)) {
fs.mkdirSync(svelteDistDir, { recursive: true });
}

// Find and copy all .svelte files
if (fs.existsSync(svelteSourceDir)) {
const files = fs.readdirSync(svelteSourceDir);
files.forEach(file => {
if (file.endsWith('.svelte')) {
const sourcePath = path.join(svelteSourceDir, file);
const destPath = path.join(svelteDistDir, file);
fs.copyFileSync(sourcePath, destPath);
console.log('📄 Copied Svelte component:', file);
}
});
console.log('✅ Svelte component files copied to dist');
}
}

// Run the processing
processCssFiles();
processInjectedCss();
processInjectedCss();
copySvelteFiles();
123 changes: 123 additions & 0 deletions package/src/libraries/backend/sveltekit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { findRoute } from "rou3";
import { Autumn } from "../../sdk";
import { createRouterWithOptions } from "./routes/backendRouter";
import { AuthResult } from "./utils/AuthFunction";
import { autumnApiUrl } from "./constants";
import { secretKeyCheck } from "./utils/secretKeyCheck";

// SvelteKit types - using generic types to avoid requiring @sveltejs/kit as dependency
type RequestEvent = {
request: Request;
url: URL;
params: Record<string, string>;
cookies: {
get(name: string): string | undefined;
set(name: string, value: string, options?: any): void;
delete(name: string, options?: any): void;
};
locals: Record<string, any>;
};

type RequestHandler = (event: RequestEvent) => Promise<Response> | Response;

export type AutumnSvelteKitHandlerOptions = {
identify: (event: RequestEvent) => AuthResult;
url?: string;
version?: string;
secretKey?: string;
};

/**
* Creates an Autumn handler for SvelteKit API routes.
*
* @example
* ```typescript
* // src/routes/api/autumn/[...path]/+server.ts
* import { autumnHandler } from 'autumn-js/sveltekit';
*
* const handler = autumnHandler({
* identify: async (event) => {
* const session = await event.locals.auth();
* if (!session?.user?.id) return null;
* return {
* customerId: session.user.id,
* customerData: {
* name: session.user.name,
* email: session.user.email,
* }
* };
* }
* });
*
* export const GET = handler.GET;
* export const POST = handler.POST;
* ```
*/
export function autumnHandler(options: AutumnSvelteKitHandlerOptions): {
GET: RequestHandler;
POST: RequestHandler;
} {
const router = createRouterWithOptions();

const { found, error: resError } = secretKeyCheck(options?.secretKey);

const handler: RequestHandler = async (event) => {
if (!found && !options?.secretKey) {
return Response.json(resError, { status: resError!.statusCode });
}

const autumn = new Autumn({
url: options?.url || autumnApiUrl,
version: options?.version,
});

const request = event.request;
const url = event.url;
const method = request.method;
const pathname = url.pathname;
const searchParams = Object.fromEntries(url.searchParams);

const match = findRoute(router, method, pathname);

if (!match) {
return Response.json({ error: "Not found" }, { status: 404 });
}

const { data, params: pathParams } = match;
const { handler: routeHandler } = data;

let body = null;
if (method === "POST" || method === "PUT" || method === "PATCH") {
try {
body = await request.json();
} catch (_) {
// Body parsing failed, continue with null body
}
}

try {
const result = await routeHandler({
autumn,
body,
path: pathname,
getCustomer: async () => await options.identify(event),
pathParams,
searchParams,
});

return Response.json(result.body, { status: result.statusCode });
} catch (error) {
console.error("[Autumn] Error handling request:", error);
return Response.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
};

return {
GET: handler,
POST: handler,
};
}

61 changes: 61 additions & 0 deletions package/src/libraries/svelte/AutumnProvider.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<script lang="ts">
import { createAutumnContext, type AutumnContextValue } from "./context.svelte";
import type { CustomerData } from "../../sdk";

interface Props {
/** Backend URL for API requests (optional if using same origin) */
backendUrl?: string;
/** Function to get the bearer token for authentication */
getBearerToken?: () => Promise<string | null>;
/** Customer data to pass with requests */
customerData?: CustomerData;
/** Whether to include credentials in requests */
includeCredentials?: boolean;
/** Better Auth URL (sets prefix to /api/auth/autumn) */
betterAuthUrl?: string;
/** Additional headers to include in requests */
headers?: Record<string, string>;
/** Custom path prefix for API routes (default: /api/autumn) */
pathPrefix?: string;
/** Default return URL for checkout/billing portal */
defaultReturnUrl?: string;
/** Slot content */
children: import("svelte").Snippet;
}

let props: Props = $props();

// Create the context with the provided options
// Note: The context is created once when the provider mounts.
// Props changes after mount won't update the client configuration.
const context: AutumnContextValue = createAutumnContext({
backendUrl: props.backendUrl,
getBearerToken: props.getBearerToken,
customerData: props.customerData,
includeCredentials: props.includeCredentials,
betterAuthUrl: props.betterAuthUrl,
headers: props.headers,
pathPrefix: props.pathPrefix,
defaultReturnUrl: props.defaultReturnUrl,
});
</script>

<!--
AutumnProvider - Provides Autumn billing context to child components.

@example
```svelte
<script>
import { AutumnProvider } from 'autumn-js/svelte';
</script>

<AutumnProvider
backendUrl="https://api.example.com"
getBearerToken={() => getAuthToken()}
>
<App />
</AutumnProvider>
```
-->

{@render props.children()}
Loading