Skip to content

Local & Cloud Data

FeatureCloud StudysetsLocal Studysets
StorageRemote Database (PostgreSQL)Browser’s IndexedDB
Access MethodGraphQL APIidb-api-layer (Dexie.js wrapper)
AuthenticationRequired for creation/editingNo account needed
IDsStrings (UUIDs/HashIDs)Integers (Auto-incrementing)
Server-Side RenderingSupportedNot Supported (Browser-only API)

Cloud data is typically fetched on the server in +page.server.ts load functions. This allows the page to be pre-rendered (SSR), making the data available immediately upon the first render.

Example: src/routes/studysets/[id]/+page.server.ts

export async function load({ params, locals }) {
const data = await locals.sdk.PublicStudyset({ id: params.id });
return { studyset: data.studyset };
}

IndexedDB is a browser API and is not available during server-side rendering. Therefore, local data cannot be loaded in +page.server.ts.

Instead, the server load function only passes the ID from the URL, and the actual data is fetched on the client side within onMount. This results in a “loading state” or a slight delay where the data is populated after the initial page shell has loaded.

Example: src/routes/studyset/local/+page.server.ts

export async function load({ url, locals }) {
let localId = parseInt(url.searchParams.get("id") ?? "");
return { localId: localId }; // Server only knows the ID
}

Example: src/routes/StudysetPage.svelte

let title = $state(data?.studyset?.title); // Initialized immediately for Cloud
let terms = $state(data?.studyset?.terms);
onMount(function () {
if (data.local) {
(async () => {
// Fetched after page load for Local
const localStudyset = await idbApiLayer.getStudysetById(data.localId);
title = localStudyset?.title;
terms = localStudyset?.terms ?? [];
})();
}
});

Images are handled fundamentally differently between the two storage types.

Cloud images are served via standard URLs from the backend (e.g., https://usercontent.static.quizfreely.org/api/term-images/[id]/[side]). The browser handles caching and memory management for these URLs normally.

Local images are stored as Blob objects in IndexedDB. To display them in an <img> tag, we must generate an Object URL.

  1. Generation: We use URL.createObjectURL(blob) (wrapped in idbLayerImg.getImageObjectUrl).
  2. Cleanup: Unlike standard URLs, Object URLs live in memory until the document is closed or they are manually revoked. To prevent memory leaks, you must call URL.revokeObjectURL(url) when the component is destroyed.

Reference Implementation in src/routes/StudysetPage.svelte:

onMount(() => {
let objectUrls: string[] = [];
if (data.local) {
(async () => {
// ... fetch studyset ...
terms.forEach((term: any) => {
if (term.termImageUrl) objectUrls.push(term.termImageUrl);
if (term.defImageUrl) objectUrls.push(term.defImageUrl);
});
})();
}
return () => {
// CLEANUP: Revoke all generated Object URLs
objectUrls.forEach(objectUrl => URL.revokeObjectURL(objectUrl));
};
});

The project uses a “dual-path” approach in shared components like Edit.svelte.

  • Cloud: Uses the GraphQL SDK (e.g., sdk.UpdateStudysetAndTerms).
  • Local: Uses idbApiLayer (e.g., idbApiLayer.updateStudyset).

Reference: src/lib/components/Edit.svelte

function saveButton() {
if (data.local) {
updateLocalStudyset(); // Uses idbApiLayer
} else {
updateCloudStudyset(); // Uses GraphQL SDK
}
}