SvelteKit
Server load versus universal load, and graftRoute on +server.ts.
pnpm add @usegraft/sdk-sveltekit @usegraft/auth @usegraft/mcpnpm i @usegraft/sdk-sveltekit @usegraft/auth @usegraft/mcpbun add @usegraft/sdk-sveltekit @usegraft/auth @usegraft/mcpBuild the handle in $lib/server/, so SvelteKit's server-only enforcement
guards it. The client holds a database handle and must never reach the browser.
Create the handle
// src/lib/server/graft.ts
import { DATABASE_URL } from "$env/static/private";
import { createDb } from "@usegraft/db";
import { createGraft } from "@usegraft/sdk-sveltekit";
import { collections } from "../../../graft.config";
export const db = createDb(DATABASE_URL).db;
export const graft = createGraft({ db, collections });
Static index instead of Postgres:
import { openStaticIndex } from "@usegraft/db";
export const graft = createGraft({
index: await openStaticIndex(".graft/index.db"),
collections,
});
Read one document
// src/routes/[slug]/+page.server.ts
import { error } from "@sveltejs/kit";
import { graft } from "$lib/server/graft";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ params }) => {
const page = await graft.getContent("pages", params.slug);
if (!page) error(404);
return { page };
};
Mount functions and MCP
// src/routes/api/fn/[name]/+server.ts
import { createFunctionsHandler } from "@usegraft/core";
import { graftRoute } from "@usegraft/sdk-sveltekit";
import { functions } from "../../../../graft.config";
import { db } from "$lib/server/graft";
const handler = createFunctionsHandler({ db, functions });
export const POST = graftRoute(handler);
export const GET = graftRoute(handler); // 405s with Allow and a fix
// src/routes/api/mcp/+server.ts
import { createActorResolver } from "@usegraft/auth";
import { createGraftMcpHandler } from "@usegraft/mcp";
import { graftRoute } from "@usegraft/sdk-sveltekit";
import { collections, functions } from "../../../../graft.config";
import { db } from "$lib/server/graft";
const actor = createActorResolver({
issuers: (process.env.GRAFT_TRUSTED_ISSUERS ?? "")
.split(/[,\s]+/)
.filter(Boolean)
.map((issuer) => ({ issuer })),
devTokens: process.env.GRAFT_DEV_TOKEN
? {
[process.env.GRAFT_DEV_TOKEN]: {
kind: "human",
id: "owner",
scopes: ["content:write"],
},
}
: undefined,
});
export const POST = graftRoute(
createGraftMcpHandler({
contentDir: "./content",
db,
collections,
functions,
actor,
}),
);
This endpoint serves content writes and asset uploads. JWT verification runs
only when issuers is passed; the library does not read
GRAFT_TRUSTED_ISSUERS (graft serve does). See Auth.
Unauthenticated callers get 401. allowAnonymous: true is only for a
loopback local server — never on anything reachable from a network. A public
docs surface is createDocsMcpHandler, which has no write
tools and no anonymous opt-in.
The parameter is typed structurally as { request: Request }, so this package
needs no @sveltejs/kit dependency.
Cache and invalidation
SvelteKit has no tag-based data cache. Stamp tagsFor(...) into a CDN
surrogate-key header with setHeaders in load, and purge
tagsForChanges(branch, changeSet) from your compile webhook.
import { tagsFor } from "@usegraft/sdk-sveltekit";
export const load: PageServerLoad = async ({ params, setHeaders }) => {
const page = await graft.getContent("pages", params.slug);
setHeaders({ "Cache-Tag": tagsFor("main", "pages", params.slug).join(",") });
return { page };
};
Cache-Tag is the surrogate-key header. Purge the same strings
tagsForChanges returns from your compile webhook.
The caveat you cannot infer
Read from +page.server.ts or +layout.server.ts. A universal load in
+page.ts or +layout.ts also runs in the browser on client-side navigation,
so a database connection does not belong there — the same care TanStack Start
needs, under different filenames.
node:sqlite backs the static index and must stay external to the client
bundle. Keeping the handle in $lib/server/ is what guarantees that.
See SDK reference for mounts across adapters and the honest gaps.