npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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

$ 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 returned
  • false: 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 returned
  • false: 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 instances
  • single: 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