Skip to content

Commit

Permalink
feat: !breaking "local" data store plugin now needs to be called `cac…
Browse files Browse the repository at this point in the history
…he`, deprecated `localStoreName` prop
  • Loading branch information
mesqueeb committed Jun 3, 2024
1 parent dd17480 commit 79439ff
Show file tree
Hide file tree
Showing 71 changed files with 410 additions and 383 deletions.
38 changes: 36 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,40 @@
- Magnetar is now ESM only. This means you need to use `import` instead of `require`.
- Magnetar now requires Node v18+.
- `@magnetarjs/plugin-vue2` was removed.
- `local` store plugin now needs to be called `cache`

Before:

```js
import { CreatePlugin as PluginVue3 } from '@magnetarjs/plugin-vue3'

const local = PluginVue3({ generateRandomId })
const remote = ...

export const magnetar = Magnetar({
localStoreName: 'local',
stores: { local, remote },
executionOrder: {
read: ['local', 'remote'],
write: ['local', 'remote'],
// ...
```
After:
```js
import { CreatePlugin as PluginVue3 } from '@magnetarjs/plugin-vue3'

const cache = PluginVue3({ generateRandomId })
const remote = ...

export const magnetar = Magnetar({
stores: { cache, remote },
executionOrder: {
read: ['cache', 'remote'],
write: ['cache', 'remote'],
// ...
```
## v0.4.0
Expand All @@ -30,7 +64,7 @@ firebase.initializeApp({
// initialise PluginFirestore
import { CreatePlugin as PluginFirestore } from '@magnetarjs/plugin-firestore'

const remote = PluginFirestore.CreatePlugin({ firebaseInstance: firebase })
const remote = PluginFirestore({ firebaseInstance: firebase })
```
After:
Expand All @@ -48,7 +82,7 @@ const db = getFirestore(firebaseApp)
// initialise PluginFirestore
import { CreatePlugin as PluginFirestore } from '@magnetarjs/plugin-firestore'

const remote = PluginFirestore.CreatePlugin({ db })
const remote = PluginFirestore({ db })
```
## v0.3.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Magnetar 🌟

A framework-agnostic syncing solution that auto-connects any DB/API with your local data store and has optimistic-UI built in.
A framework-agnostic syncing solution that auto-connects any DB/API with your local cache data store and has optimistic-UI built in.

```sh
npm i magnetar
Expand Down
16 changes: 8 additions & 8 deletions docs/docs-main/about/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

- Magnetar is a **_state management_** library.<br />A library that brings a simple syntax for reading and writing data and allows you to easily work with this data in your app like a global store.

- Magnetar has 2-way sync **_database integration_** for Google Firestore (and others coming). You do not need to learn to work with the database SDK.<br />— Whenever you modify data in your local store, Magnetar will update your database on the server.<br />— Whenever the server has changes, Magnetar will reflect those in your local store.
- Magnetar has 2-way sync **_database integration_** for Google Firestore (and others coming). You do not need to learn to work with the database SDK.<br />— Whenever you modify data in your local cache store, Magnetar will update your database on the server.<br />— Whenever the server has changes, Magnetar will reflect those in your local cache store.

- Magnetar's main focus is to be the **_local representation of your database_**.
- Magnetar's main focus is to be the **_cache as representation of your database_**.

- Magnetar is framework-agnostic. It can be used with Vue/React/Angular/Vanilla JS projects.

- Magnetar is modular. It works with plugins that provide capabilities so you can only include what you actually need.

- Magnetar has plugins for Vue 2 & Vue 3 that offer **_built-in reactivity_**.<br />Just like Vuex, displaying data in Vue just works as you would expect and is reactive on local/server changes. (Magnetar does not rely on Vuex)
- Magnetar has plugins for Vue 2 & Vue 3 that offer **_built-in reactivity_**.<br />Just like Vuex, displaying data in Vue just works as you would expect and is reactive on local cache/server changes. (Magnetar does not rely on Vuex)

<img src="/media/magnetar-value-proposition.jpg" style="width: 100%" />

Expand All @@ -34,36 +34,36 @@ Here is a hypothetical example of a To Do list powered by Magnetar that uses the
onCreated(() => {
// fetch all documents from Firestore & continue to watch for any changes
// will keep local data up to date when changes on database occur
// will keep cached data up to date when changes on database occur
todoItemsModule.stream()
})
/**
* Displays the local data of your module (data comes in via the stream)
* Displays the cached data of your module (data comes in via the stream)
*/
const items = computed(() => {
// in magnetar, the collection `.data` is a JS Map
return todoItemsModule.data.values()
})
/**
* Adds a new item to the local data & makes API call to Firestore
* Adds a new item to the cached data & makes API call to Firestore
* UI is reflected automatically
*/
function addItem(newData) {
todoItemsModule.insert(newData)
}
/**
* Edits an item in the local data & makes API call to Firestore
* Edits an item in the cached data & makes API call to Firestore
* UI is reflected automatically
*/
function editItem(id, newData) {
todoItemsModule.doc(id).merge(newData)
}
/**
* Deletes an item from the local data & makes API call to Firestore
* Deletes an item from the cached data & makes API call to Firestore
* UI is reflected automatically
*/
function deleteItem(id) {
Expand Down
14 changes: 7 additions & 7 deletions docs/docs-main/concepts/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ The idea of Magnetar is that you only need to learn how to work with one syntax

In most cases you use Magnetar with two store plugins installed:

- A "local" store plugin, for the data you need cached while using the app. (like Vuex or a simple global object)
- A "cache" store plugin, for the data you need cached while using the app. (like Vuex or a simple global object)
- A "remote" store plugin, for the remote data stored. (like Firestore or any other database)

**When reading data:** the _remote_ store will fetch the data; the _local_ store will then add that data for you, so you can easily access and use it in your app.
**When reading data:** the _remote_ store will fetch the data; the _cache_ store will then add that data for you, so you can easily access and use it in your app.

**When writing data:** the _local_ store will save your changes in its cache; the _remote_ will then make an API call to your database. <small>(you can also flip this around, so the local store is only updated after the remote one)</small>
**When writing data:** the _cache_ store will save your changes in its cache; the _remote_ will then make an API call to your database. <small>(you can also flip this around, so the local cache store is only updated after the remote one)</small>

### List of Plugins

Available store plugins

- Firestore (remote)
- Simple Store (local)
- Vue 2 (local)
- Vue 3 (local)
- Simple Store (cache)
- Vue 2 (cache)
- Vue 3 (cache)

Planned store plugins (TBD)

Expand Down Expand Up @@ -75,6 +75,6 @@ const myDoc = magnetar.collection('some-collection').doc('some-doc')
### The Purpose of a Collection/Doc Path

Each collection and doc need to have a _path_ that points to that collection. The purpose of this path is to become the identifier where your local store will save your documents.
Each collection and doc need to have a _path_ that points to that collection. The purpose of this path is to become the identifier where your local cache store will save your documents.

By default a _path_ is the same _path_ to the data in your remote store. Eg. a doc module with path `users/abc123` will represent the same document as in your database at that path.
8 changes: 4 additions & 4 deletions docs/docs-main/faq/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

TLDR; Firestore SDK **charges you on every read**. Magnetar does cache management for free.

Firestore has its own implementation of locally cached data. However, every time you access that data it will also double check if the data is up to date with the server. This means that you are charged money every time you simply want to use your data locally.
Firestore has its own implementation of locally cached data. However, every time you access that data it will also double check if the data is up to date with the server. This means that you are charged money every time you simply want to use your cached data locally.

When you think about this, most devs end up saving the data in some sort of object or in a state management library Vue data or Vuex/Redux. This means that you then have to add write logic to write the data back to the database either way.

Magnetar's goal is to replace all this logic you need to write yourself to read and write data, providing its own local store so you can more easily prevent Firebase from charging you when reading docs you already fetched.
Magnetar's goal is to replace all this logic you need to write yourself to read and write data, providing its own local cache store so you can more easily prevent Firebase from charging you when reading docs you already fetched.

## Why did you build Magnetar?

Expand All @@ -26,13 +26,13 @@ I'm the creator of [Vuex Easy Firestore](https://mesqueeb.github.io/vuex-easy-fi
<!-- ![](/architecture/magnetar-architecture.png) -->
<img src="/architecture/magnetar-architecture.png" style="width: 100%" />

## Why the concept of local/remote store plugins?
## Why the concept of cache/remote store plugins?

I wanted Magnetar to be compatible with a wide variety of use cases and projects.

When it comes to remote stores, I wanted to be able to use Firestore but competitors like Fauna and Supabase also looked interesting. I didn't want to limit the future to just Firestore.

When it comes to local stores, there are different approaches again. A simple store that just works with a Map or Object is tricky to make reactive out of the box for Vue projects. Adding reactivity out of the box needed a different implementation for Vue 2 and Vue 3. Again, not to limit the future and prevent code bloat, using a similar plugin system for local stores was the way to go.
When it comes to local cache stores, there are different approaches again. A simple store that just works with a Map or Object is tricky to make reactive out of the box for Vue projects. Adding reactivity out of the box needed a different implementation for Vue 2 and Vue 3. Again, not to limit the future and prevent code bloat, using a similar plugin system for local cache stores was the way to go.

Finally I also like the idea of having your own data caching options. I'm planning to also add store plugins that work with localStorage, indexedDB, etc. All of this meant that a store plugin system was the best choice!

Expand Down
6 changes: 3 additions & 3 deletions docs/docs-main/hooks-and-events/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pokedexModule.doc('abc').insert({ name: 'Unown', type: undefined })

### Remove Certain Values

Some remote stores (eg. Firestore) do not allow the value `undefined`. In this case you can set up a hook that completely removes `undefined` before the data is sent to your local and remote stores. For this example we're going to use a tiny helper utility I wrote called [remove-anything](https://github.com/mesqueeb/remove-anything).
Some remote stores (eg. Firestore) do not allow the value `undefined`. In this case you can set up a hook that completely removes `undefined` before the data is sent to your cache and remote stores. For this example we're going to use a tiny helper utility I wrote called [remove-anything](https://github.com/mesqueeb/remove-anything).

```js
import { removeProp } from 'remove-anything'
Expand Down Expand Up @@ -172,7 +172,7 @@ The config you can pass for `modifyReadResponseOn` is an object with the followi
- `modified` — triggered every time data is modified on your remote store (the server), during the method `stream`
- `removed` — triggered every time data is removed from your remote store (the server) OR if a document satisfy the query filters of your module anymore, during the method `stream`

Your `modifyReadResponseOn`-function will receive a `payload` as param which is the _incoming data_ and **must** return that `payload` again. The main purpose is that you can modify the payload before it is added to your local store.
Your `modifyReadResponseOn`-function will receive a `payload` as param which is the _incoming data_ and **must** return that `payload` again. The main purpose is that you can modify the payload before it is added to your local cache store.

Here we give some examples with common use cases.

Expand Down Expand Up @@ -205,7 +205,7 @@ pokedexModule.stream()

> documentation below is still WIP
> hint: returning `undefined` will discard the document change and do nothing with the local store
> hint: returning `undefined` will discard the document change and do nothing with the local cache store
### Accessing Metadata when Reading Data

Expand Down
6 changes: 3 additions & 3 deletions docs/docs-main/module-setup/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ It's also become clear that these modules might be better off each having their

> Why do you need to define both `modifyPayloadOn.insert` and `modifyReadResponseOn.added` ?
- `modifyPayloadOn.insert` — is triggered every time you write data locally (which is then synced to the server with those default values)
- `modifyPayloadOn.insert` — is triggered every time you write data locally to cache (which is then synced to the server with those default values)
- `modifyReadResponseOn.added` — is triggered every time data comes in from your remote store (the server)

To learn more about these functions and other possibilities read [Hooks and Events](../hooks-and-events/).
Expand Down Expand Up @@ -96,7 +96,7 @@ import { pokedexModule } from 'pokedexModule.js'
// making a read request will retrieve docs with the fixed query enabled:
await pokedexModule.fetch()

// accessing local data will also filter on just docs as per the fixed query:
// accessing cached data will also filter on just docs as per the fixed query:
pokedexModule.data.values()
})()
```
Expand Down Expand Up @@ -167,7 +167,7 @@ async function searchPokemon(type) {
}
```

As you can see in the example, using a query in Magnetar is very powerful because it will not only query your read requests to the remote store, but can also apply that same query when reading your local data.
As you can see in the example, using a query in Magnetar is very powerful because it will not only query your read requests to the remote store, but can also apply that same query when reading your cached data.

You can find more information on reading data at [Read Data](../read-data/#query-data-filter-order-by-limit).

Expand Down
4 changes: 2 additions & 2 deletions docs/docs-main/plugins/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function generateRandomId() {
// this function is used when you execute `insert` without specifying an ID
}

const local = PluginVue3.CreatePlugin({ generateRandomId })
const cache = PluginVue3.CreatePlugin({ generateRandomId })
```

## Simple Store
Expand All @@ -95,5 +95,5 @@ function generateRandomId() {
// this function is used when you execute `insert` without specifying an ID
}

const local = PluginSimpleStore.CreatePlugin({ generateRandomId })
const cache = PluginSimpleStore.CreatePlugin({ generateRandomId })
```
18 changes: 9 additions & 9 deletions docs/docs-main/read-data/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ There are two ways to retrieve data from your remote stores. Either of these met

## Fetch Data Once

When you get data by executing `fetch()`, the data will be fetched from a server by your "remote" store plugin and then added to your module's data by your "local" store plugin.
When you get data by executing `fetch()`, the data will be fetched from a server by your "remote" store plugin and then added to your module's data by your "cache" store plugin.

For displaying fetched data in the DOM see the [Displaying data in the DOM](../setup/#displaying-data-in-the-dom).

### Fetch a Single Document

When you call `fetch()` on a document module, your remote store will go and fetch the document from your database and add it to your local store.
When you call `fetch()` on a document module, your remote store will go and fetch the document from your database and add it to your local cache store.

```javascript
const bulbasaur = magnetar.doc('pokedex/001')

// bulbasaur's data is not yet in local data
// bulbasaur's data is not yet in cached data
// bulbasaur.data ≈ {}

await bulbasaur.fetch()

// now it is available locally:
// now it is available:

const data = bulbasaur.data
// bulbasaur.data ≈ { name: 'Bulbasaur' }
Expand Down Expand Up @@ -144,9 +144,9 @@ magnetar.collection('pokedex').average // { base: { HP: 64.2 } }

## Stream Realtime Updates

When you set up a _**stream**_ for a document or collection, just like `fetch()`, your the data will be fetched from a server by your _remote_ store plugin and then added to your module's _local_ data.
When you set up a _**stream**_ for a document or collection, just like `fetch()`, your the data will be fetched from a server by your _remote_ store plugin and then added to your module's _cache_ data.

Afterwards, any changes to this document remotely will automatically be reflected in your module's _local_ data while the stream is open.
Afterwards, any changes to this document remotely will automatically be reflected in your module's _cache_ data while the stream is open.

Please note: a streaming promise will never resolve as long as your stream is open! There is **no way** to know when or how many documents will be loaded in, as this depends on your remote store.

Expand Down Expand Up @@ -265,10 +265,10 @@ There are founr methods to query more specific data in a collection:

You can execute and chain these methods on collections to create a _queried module_ that is just like a regular module but with your query applied.

When you apply a query it affects both the remote and local stores:
When you apply a query it affects both the remote and cache stores:

- If you make a `fetch()` call with a _queried module_, it will pass the queries to the remote store, which will make sure that your query is applied to the API call.
- If you access module data with a _queried module_, the local store will also make sure that your query is applied to whatever data it returns.
- If you access module data with a _queried module_, the local cache store will also make sure that your query is applied to whatever data it returns.

```js
const pokedexModule = magnetar.collection('pokedex')
Expand Down Expand Up @@ -312,7 +312,7 @@ magnetar
```
<!-- prettier-ignore-end -->

For now read the Firestore documentation on [Simple and Compound Queries](https://firebase.google.com/docs/firestore/query-data/queries). The concept is inspired by Firestore, but with Magnetar _every_ local and remote store plugin implements the proper logic to work with these kind of queries!
For now read the Firestore documentation on [Simple and Compound Queries](https://firebase.google.com/docs/firestore/query-data/queries). The concept is inspired by Firestore, but with Magnetar _every_ cache and remote store plugin implements the proper logic to work with these kind of queries!

More Magnetar specific information on this will come soon.

Expand Down
Loading

0 comments on commit 79439ff

Please sign in to comment.