Cybernetically enhanced Firebase apps 💪🔥
Psuedo Example
Handle multiple levels of async relational data (and their loading & fallback states) entirely from the Svelte HTML.
<FirebaseApp {firebase}>
<User let:user>
Howdy, {user.uid}
<Doc path={`posts/${user.uid}`} let:data={post} let:ref={postRef}>
<h2>{post.title}</h2>
<Collection path={postRef.collection('comments')} let:data={comments}>
{#each comments as comment}
{/each}
...
Create a Svelte App and install Firebase.
npx degit sveltejs/template fireapp
cd fireapp
npm install
npm install sveltefire firebase
Create a web app from the Firebase Console and grab your project config. Enable Anonymous Login and create a Firestore database instance in test mode.
Initialize the Firebase app in the App.svelte file.
<script>
import { FirebaseApp, User, Doc, Collection } from 'sveltefire';
// Import the Firebase Services you want bundled and call initializeApp
import firebase from "firebase/app";
import 'firebase/firestore';
import 'firebase/auth';
import 'firebase/performance';
import 'firebase/analytics';
const firebaseConfig = {
apiKey: 'api-key',
authDomain: 'project-id.firebaseapp.com',
databaseURL: 'https://project-id.firebaseio.com',
projectId: 'project-id',
storageBucket: 'project-id.appspot.com',
messagingSenderId: 'sender-id',
appId: 'app-id',
measurementId: 'G-measurement-id',
}
firebase.initializeApp(firebaseConfig)
</script>
Full Example
Start by building an authenticated realtime CRUD app . A user can sign-in, create posts, and add comments to that post. Paste this code into your app.
<FirebaseApp {firebase}>
<User let:user let:auth>
Howdy, {user.uid}
<button on:click={() => auth.signOut()}>Sign Out</button>
<button on:click={() => auth.signInAnonymously()}>Sign In</button>
<Doc path={`posts/${user.uid}`} let:data={post} let:ref={postRef} log>
<h2>{post.title}</h2>
<span slot="loading">Loading post...</span>
<span slot="fallback">
Demo post not created yet...
<button on:click={() => postRef.set({ title: 'I like Svelte' })}>
Create it Now
</button>
</span>
<Collection
path={postRef.collection('comments')}
let:data={comments}
let:ref={commentsRef}
log>
{#each comments as comment}
{comment.text}
<button on:click={() => comment.ref.delete()}>Delete</button>
{/each}
<hr />
<button on:click={() => commentsRef.add({ text: 'Cool!' })}>
Add Comment
</button>
<span slot="loading">Loading comments...</span>
</Collection>
</Doc>
</User>
</FirebaseApp>
Run it on localhost:5000
npm run dev
If you see the error 'openDb' is not exported by node_modules\idb\build\idb.js, go in therollup.config.js` and add this line:
resolve({
...
mainFields: ['main', 'module'] /// <-- here
}),
SvelteFire allows you to use Firebase data anywhere in the Svelte component without the need to manage async state, promises, or streams.
Slots render different UI templates based on the state of your data. The loading state is shown until the first response is received from Firebase. The fallback state is shown if there is an error or timeout.
In most cases, state flows from loading -> default. For errors, non-existent data, or high-latency, state flows from loading -> fallback maxWait default is 10000ms).
<Doc path={'foods/ice-cream'}>
Data loaded, yay 🍦!
</Doc>
Error handling made easy:
<Doc {path} let:error>
😔 This doc cannot be read {error}
</Doc>
Loading state made easy:
<Doc {path}>
⌛
</Doc>
You can bypass the loading state entirely by passing a startWith prop.
<Doc {path} startWith={ {flavor: 'vanilla'} }>
Slot props pass data down to children the component tree. SvelteFire has done the hard work to expose the data you will need in the UI. For example, let:data gives you access to the document data, while ={yourVar} is the name you use to reference it in your code. The data is a plain object for showing data in the UI, while the ref is a Firestore DocumentReference used to execute writes.
<Doc path={`food/ice-cream`} let:data={icecream} let:ref={docRef}>
{icecream.flavor} yay 🍦!
<button on:click={() => docRef.delete()}>Delete</button>
</Doc>
Events emit data up to the parent. You can use components as a mechanism to read documents without actually rendering UI. Also useful for trigging side effects.
<Doc path={'food/ice-cream'} on:data={(e) => console.log(e.detail.data)} />
Components are reactive. When props change, they unsubscribe from the last stream and start a new one. This means you can change the document path or query function by simplying changing a prop value.
Example: Collections have special slot props for pagination called first and last. Use them to create reactive pagination queries.
<script>
let query = (ref) => ref.orderBy('flavor').limit(3)
function nextPage(last) {
query = (ref) => ref.orderBy('flavor').startAfter(last.flavor).limit(3);
}
</script>
<Collection path={'foods'} {query} let:data let:last>
{#each data as food}
{food.name}
{/each}
<button on:click={() => nextPage(last) }>Next</button>
</Collection>
Stores are used under the hood to manage async data in components. It's an advanced use-case, but they can be used directly in a component script or plain JS.
<script>
import { collectionStore } from 'sveltefire';
const data = collectionStore('things', (ref => ref.orderBy('time') ));
data.subscribe(v => doStuff(v) )
</script>
The Firebase SDK is available via the Context API under the key of firebase.
const db = getContext('firebase').firestore();
<FirebaseApp>Sets Firebase app context
Props:
window.firebase. <FirebaseApp firebase={firebase} perf analytics>
</FirebaseApp>
<User>Listens to the current user.
Props:
sessionStorage or localStorage. Can prevent flash if user refreshes browser. Default null;Slots:
Slot Props & Events:
null<User persist={sessionStorage} let:user={user} let:auth={auth} on:user>
{user.uid}
</User>
<Doc>Retrieves and listens to a Firestore document.
Props:
string OR a DocumentReference i.e db.doc('path')number milliseconds to wait before showing fallback slot if nothing is returned. Default 10000. false. false. string name that runs a Firebase Performance trace for latency.Slots:
Slot Props & Events:
<Doc
path={'posts/postId'}
startWith={defaultData}
log
traceId={'postRead'}
let:data={myData}
let:ref={myRef}
on:data
on:ref
>
{post.title}
<span slot="loading">Loading...</span>
<span slot="fallback">Error...</span>
</Doc>
<Collection>Retrieves and listens to a Firestore collection or query.
Props:
string OR CollectionReference i.e db.collection('path')function, i.e (ref) => ref.where('age, '==', 23)number milliseconds to wait before showing fallback slot if nothing is returned. Default 10000. false. false. string name that runs a Firebase Performance trace for latency.Slots:
Slot Props & Events:
<Collection
path={'comments'}
query={ (ref) => ref.orderBy(date).limit(10) }
traceId={'readComments'}
log
let:data={comments}
let:ref={commentsRef}
let:last={firstComment}
let:first={lastComment}
on:data
on:ref
>
{#each comments as comment}
{comment.text}
{/each}
Loading...
Unable to display comments...
</Collection>
Note: Each data item in the collection contains the document data AND fields for the id and ref (DocumentReference).
$ claude mcp add sveltefire \
-- python -m otcore.mcp_server <graph>