swoof
v0.1.9
Published
Google Firebase Firestore, Auth, Storage, Functions library for Svelte
Downloads
38
Readme
Swoof
Swoof is Google Firebase Firestore, Auth, Storage, Functions library for Svelte.
Docs are coming.
See /dummy
for some examples.
- Setting up
- API
- Issues
- TODO
Setting up
$ npm install swoof --save-dev
// App.svete
<script>
import { swoof, state, setGlobal, User } from 'swoof';
import SomethingNice from './SomethingNice.svelte';
class FancyUser extends User {
}
let { firebase } = process.env.CONFIG;
let config = {
firebase: {
apiKey: "...",
authDomain: "...",
databaseURL: "...",
projectId: "...",
storageBucket: "...",
messagingSenderId: "...",
appId: "..."
},
firestore: {
enablePersistence: true
},
swoof: {
auth: {
User: FancyUser
},
// override default region for store.functions
// functions: {
// region: 'us-central1'
// }
}
};
// internally creates FirebaseApp named main
swoof.configure('main', config);
// creates store named `main` using firebase app named `main`
// swoof supports multiple firebase apps
let store = swoof.create('main', 'main');
// Optional tools for playing around in console
setGlobal({ store });
setGlobal({ state });
</script>
<SomethingNice/>
<style>
</style>
// console
await store.doc('message/hello').new({ text: 'hey there' }).save();
If you're getting weird build or runtime errors, see below.
API
swoof
import { swoof } from 'swoof';
configure(name, config): undefined
Creates FirebaseApp and links it to the name.
create(identifier, name): store
Creates and returns swoof store with given identifier and configuration name.
store(identifier): store or undefined
Returns existing store for identifier.
swoof.create('main', 'production'); // once
// somewhere else
let store = swoof.store('main');
destroy(): undefined
Destroys internal FirebaseApp instances
Model
Soon. See /dummy for examples
// lib/messages.js
import { Model, properties } from 'swoof';
const {
attr,
models,
tap
} = properties;
class Message extends Model {
constructor(message) {
super();
// tap doesn't bind, just forwards change notifications in this context
this.property('doc', tap(doc));
}
get data() {
return this.doc.data;
}
get text() {
return this.data.text;
}
async save() {
await this.doc.save();
}
}
export default class Messages extends Model {
constructor(store) {
super();
this.store = this;
this.coll = store.collection('messages');
// query autosubscribes to ref.onSnapshot
this.property('query', attr(this.coll.orderBy('createdAt').query()));
// Message models are automatically created for each document.
// then added/removed based on snapshot.docChanges
this.property('messages', models('query.content', doc => new Message(doc)));
}
async add(text) {
let { store } = this;
let doc = this.coll.doc().new({
text,
createdAt: store.serverTimestamp();
});
await doc.save();
}
}
<script>
import { store } from 'swoof';
import Messages from './lib/messages';
// Writable when subscribed starts all onSnapshot listeners and
// property change notifications
// Everything is torn down when last subscriber unsubscribes.
let messages = writable(new Messages(store));
</script>
<!-- use "$" only for `messages` - first level -->
<div>{$messages.count} messages.</div>
<div>
{#each $messages.message as message}
<div>{message.text}</div>
{/each}
</div>
writable(model): svelte/writable
Creates Svelte writable for sfoof model instance or tree.
Properties
- attr
- array
- models
- tap
- alias
- logger
load()
await load(....modelsOrPromises);
Store
import { swoof } from 'swoof';
let store = swoof.store('main');
doc(path): DocumentReference
Creates swoof firestore document reference.
let ref = store.doc('messages/first');
collection(path): CollectionReference
Creates swoof firestore collection reference.
let ref = store.doc('messages/first/comments');
serverTimestamp(): firestore.FieldValue.ServerTimestamp
let doc = store.doc('messages/first').new({
text: 'hey there',
createdAt: store.serverTimestamp()
});
await doc.save();
DocumentReference
let ref = store.doc('messages/first');
let ref = store.collection('messages').doc('first');
let ref = store.collection('messages').doc(); // generated id
id: string
Document id
path: string
Document path
collection(path): CollectionReference
Creates nested Collection Reference
let coll = store.doc('messages/first').collection('comments');
new(props): Document
Creates Document instance which is not automatically subscribed to onSnapshot listener.
Subscription to onSnapshot happens right after save
or load
.
let doc = store.doc('messages/first').new({
ok: true
});
// doc.isNew === true
// doc.isSaved === false
await doc.save();
// doc.isNew === false
// now doc is subscribed to onSnashot listener
existing(): Document
Creates Document instance which is automatically subscribed to onSnapshot listener.
let doc = store.doc('messages/first').existing();
// doc.isNew === false
async load({ optional: false }): Document or undefined
Loads document and creates Document instance for it.
let doc = await store.doc('messages/first').load({ optional: true });
If document doesn't exist and optional is:
true
:undefined
is returnedfalse
:SwoofError
with{ code: 'document/missing' }
is thrown
CollectionReference
id: string
Dollection id
path: string
Collection full path
doc(path): DocumentReference
Creates nested document reference
let ref = store.collection('messages').doc(); // generated id
let ref = store.collection('messages').doc('first');
conditions
There are also all firestore condition operators which all also return QueryableReference
for further conditions and query()
, load()
methods.
- where()
- orderBy()
- limit()
- limitToLast()
- startAt()
- startAfter()
- endAt()
- endBefore()
query({ type: 'array' }): ArrayQuery or SingleQuery
Creates onSnapshot
supporting Query instance. There are two types: array
, single
.
- array query has
content
property which is array of Document instances - single query has
content
property which is Document instance or null
let array = store.collection('messages').query();
let single = store.collection('messages').orderBy('createdAt', 'asc').limit(1).query({ type: 'single' });
async load(): Array
Loads documents from firestore and creates Document instances for each of them.
let ref = store.collection('messages').load();
let array = await ref.lod(); // [ <Document>, ... ]
first({ optional: false }): Document or undefined
Loads first document from firestore and creates Document instance
let zeeba = await store.collection('messages').where('name', '==', 'zeeba').limit(1).first();
If document doesn't exist and optional is:
true
:undefined
is returnedfalse
:SwoofError
with{ code: 'document/missing' }
is thrown
Document extends Model
Document instance represents one firestore document.
let doc = store.doc('messages/first').new({
ok: true
});
store: Store
Store for which this document is created.
ref: DocumentReference
DocumentReference for this document
id: string
Document id
path: string
Document full path
promise: Promise
Promise which is resolved after 1st load or 1st onSnapshot call
data: ObjectProxy
Document's data.
let doc = await store.doc('messages/first').load();
doc.data.name = 'new name';
// or
doc.data = { name: 'new name' };
Both editing properties directly or replacing data will trigger Svelte component renders.
merge(props): undefined
Deep merge document data
let doc = store.doc('messages/first').new({
name: 'zeeba',
thumbnail: {
size: {
width: 100,
height: 100
},
url: null
}
});
doc.merge({
thumbnail: {
url: 'https:/....'
}
});
async load({ force: false }): Document
Loads document if it's not already loaded.
let doc = await store.doc('messages/first').existing();
await doc.load(); // loads
await doc.load(); // ignores. already loade
await doc.load({ force: true }); // loads or reloads
async reload(): Document
Reloads document. The same as doc.load({ force: true })
async save({ force: false, merge: false }): Document
Saves document if isDirty
is true
.
let doc = await store.doc('messages/first').new({
ok: true
});
await doc.save(); // saves
await doc.save(); // ignores. not dirty
doc.data.name = 'zeeba';
await doc.save(); // saves
await doc.save({ force: true }); // saves even if not dirty
await doc.save({ merge: true }) // does `ref.set(data, { merge: true });
async delete(): Document
Deletes a document
let doc = await store.doc('messages/first');
await doc.delete();
serialized: Object
Returns JSON debugish representation of document.
let doc = await store.doc('messages/first').load();
{
id: "first",
path: "messages/first",
exists: true,
isNew: false,
isDirty: false,
isLoading: false,
isSaving: false,
isLoaded: true,
isError: false,
error: null,
data: {
name: "Zeeba"
}
}
toJSON(): Object
Basically same as serialized with additional data
Query extends Model
onSnapshot aware query.
let array = store.collection('messages').where('status', '==', 'sent').query({ type: 'array' });
let single = store.collection('messages').limit(1).query({ type: 'single' });
promise: Promise
Promise which is resolved after 1st load or 1st onSnapshot call.
let query = store.collection('messages').query();
await query.promise; // resolves after 1st load or onSnapshot
async load({ force: false }): Query
Loads query if it is not already loaded. See Document.load
for details on force
.
let query = store.collection('messages').query();
await query.load();
// isLoaded === true
await query.load(); // doesn't do anything
await query.load({ force: true }); // loads
reload(): Query
Relaods query. Same as load({ force: true })
string: string
More or less readable query as a string.
serialized: object
Debugish query status representation
{
error: null
isError: false
isLoaded: false
isLoading: false
string: "messages.where(status, ==, sent).limit(10)"
}
content
if { type }
is:
array
(default): array of Document instancessingle
: single (first) Document instance or null
Auth
let auth = store.auth;
Sign in
await auth.methods.anonymous.signIn();
await auth.methods.email.signIn(email, password);
Link anonymous to credentials
await auth.methods.anonymous.signIn();
let user = auth.user;
await user.link('email', email, password);
User
let user = auth.user;
await user.delete();
await user.signOut();
import { User, toString } from 'swoof';
export default class DummyUser extends User {
constructor(store, user) {
super(store, user);
}
// restoe is called with user arg only if
// user.uid === this.user.uid
async restore(user) {
if(user) {
this.user = user;
}
}
toString() {
let { uid, email } = this;
return toString(this, `${email || uid}`);
}
}
Storage
let storage = store.storage;
let ref = storage.ref(`users/${uid}/avatar`);
let task = ref.put({
type: 'data',
data: file,
metadata: {
contentType: file.type
}
});
await task.promise;
let ref = storage.ref(`users/${uid}/avatar`);
await ref.url();
await ref.metadata();
await ref.update({ contentType: 'image/png' });
Task extends Model
import { Model, writable, properties, objectToJSON } from 'swoof';
const {
attr
} = properties;
class Storage extends Model {
constructor() {
super();
this.property('task', attr(null))
}
async upload() {
let task = store.storage.ref('hello').put({
type: 'string',
format: 'raw',
data: 'hey there',
metadata: {
contentType: 'text/plain'
}
});
this.task = task;
}
get serialized() {
let { task } = this;
return {
task: objectToJSON(task)
};
}
}
let model = writable(new Storage());
Functions
await store.functions.call('hey-there', { ok: true });
await store.functions.region('us-central1').call('hey-there', { ok: true });
Issues
process is not defined
Uncaught ReferenceError: process is not defined
add plugin-replace
to rollup config:
// rollup.config.js
import replace from '@rollup/plugin-replace';
plugins([
//...
svelte({
// ...
}),
replace({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}),
// ...
])
'registerComponent' of undefined
Uncaught TypeError: Cannot read property 'registerComponent' of undefined
update plugin-commonjs
:
// package.json
"devDependencies": {
// ...
"@rollup/plugin-commonjs": "^15.0.0"
}
TODO
- [ ] alias() property
- [ ] diff doc onSnapshot changes + state and do writable.set(this) only if there are changes present
- [x] models() property
- [x] tap: needs some kind of tool to forward change notifications to nested models
- [x] add basic auth support (sign up, sign in (email, anon), forgot password, link account, sign out)
- [x] add basic storage support