mirror of
https://github.com/appy-one/acebase.git
synced 2026-06-30 06:02:02 -06:00
Merge pull request #193 from appy-one/improvement/docs-20221219
docs update
This commit is contained in:
commit
c8e46d4555
1 changed files with 159 additions and 182 deletions
341
README.md
341
README.md
|
|
@ -203,35 +203,30 @@ NOTE: The `logLevel` option specifies how much info should be written to the con
|
|||
|
||||
### Loading data
|
||||
|
||||
Run ```.get``` on a reference to get the currently stored value. This is short for the Firebase syntax of ```.once("value")```
|
||||
Run `.get` on a reference to get the currently stored value. This is short for the Firebase syntax of `.once("value")`.
|
||||
|
||||
```javascript
|
||||
db.ref('game/config')
|
||||
.get(snapshot => {
|
||||
if (snapshot.exists()) {
|
||||
config = snapshot.val();
|
||||
}
|
||||
else {
|
||||
config = new MyGameConfig(); // use defaults
|
||||
}
|
||||
});
|
||||
const snapshot = await db.ref('game/config').get();
|
||||
if (snapshot.exists()) {
|
||||
config = snapshot.val();
|
||||
}
|
||||
else {
|
||||
config = defaultGameConfig; // use defaults
|
||||
}
|
||||
```
|
||||
|
||||
Note: When loading data, the currently stored value will be wrapped and returned in a ```DataSnapshot``` object. Use ```snapshot.exists()``` to determine if the node exists, ```snapshot.val()``` to get the value.
|
||||
Note: When loading data, the currently stored value will be wrapped and returned in a `DataSnapshot` object. Use `snapshot.exists()` to determine if the node exists, `snapshot.val()` to get the value.
|
||||
|
||||
### Storing data
|
||||
|
||||
Setting the value of a node, overwriting if it exists:
|
||||
|
||||
```javascript
|
||||
db.ref('game/config')
|
||||
.set({
|
||||
const ref = await db.ref('game/config').set({
|
||||
name: 'Name of the game',
|
||||
max_players: 10
|
||||
})
|
||||
.then(ref => {
|
||||
// stored at /game/config
|
||||
})
|
||||
});
|
||||
// stored at /game/config
|
||||
```
|
||||
|
||||
Note: When storing data, it doesn't matter whether the target path, and/or parent paths exist already. If you store data in _'chats/somechatid/messages/msgid/receipts'_, it will create any nonexistent node in that path.
|
||||
|
|
@ -241,25 +236,23 @@ Note: When storing data, it doesn't matter whether the target path, and/or paren
|
|||
Updating the value of a node merges the stored value with the new object. If the target node doesn't exist, it will be created with the passed value.
|
||||
|
||||
```javascript
|
||||
db.ref('game/config').update({
|
||||
const ref = await db.ref('game/config').update({
|
||||
description: 'The coolest game in the history of mankind'
|
||||
})
|
||||
.then(ref => {
|
||||
// config was updated, now get the value
|
||||
return ref.get(); // shorthand for firebase syntax ref.once("value")
|
||||
})
|
||||
.then(snapshot => {
|
||||
const config = snapshot.val();
|
||||
// config now has properties "name", "max_players" and "description"
|
||||
});
|
||||
|
||||
// config was updated, now get the value (ref points to 'game/config')
|
||||
const snapshot = await ref.get();
|
||||
const config = snapshot.val();
|
||||
|
||||
// `config` now has properties "name", "max_players" and "description"
|
||||
```
|
||||
|
||||
### Transactional updating
|
||||
|
||||
If you want to update data based upon its current value, and you want to make sure the data is not changed in between your ```get``` and ```update```, use ```transaction```. A transaction gets the current value, runs your callback with a snapshot. The value you return from the callback will be used to overwrite the node with. Returning ```null``` will remove the entire node, returning ```undefined``` will cancel the transaction.
|
||||
If you want to update data based upon its current value, and you want to make sure the data is not changed in between your `get` and `update`, use `transaction`. A transaction gets the current value, runs your callback with a snapshot. The value you return from the callback will be used to overwrite the node with. Returning `null` will remove the entire node, returning nothing (`undefined`) will cancel the transaction.
|
||||
|
||||
```javascript
|
||||
db.ref('accounts/some_account')
|
||||
await db.ref('accounts/some_account')
|
||||
.transaction(snapshot => {
|
||||
// some_account is locked until its new value is returned by this callback
|
||||
var account = snapshot.val();
|
||||
|
|
@ -290,7 +283,7 @@ db.ref('accounts/some_account/balance')
|
|||
|
||||
### Removing data
|
||||
|
||||
You can remove data with the ```remove``` method
|
||||
You can remove data with the `remove` method
|
||||
|
||||
```javascript
|
||||
db.ref('animals/dog')
|
||||
|
|
@ -298,7 +291,7 @@ db.ref('animals/dog')
|
|||
.then(() => { /* removed successfully */ )};
|
||||
```
|
||||
|
||||
Removing data can also be done by setting or updating its value to ```null```. Any property that has a null value will be removed from the parent object node.
|
||||
Removing data can also be done by setting or updating its value to `null`. Any property that has a null value will be removed from the parent object node.
|
||||
|
||||
```javascript
|
||||
// Remove by setting it to null
|
||||
|
|
@ -314,7 +307,7 @@ db.ref('animals')
|
|||
|
||||
### Generating unique keys
|
||||
|
||||
For all generic data you add, you need to create keys that are unique and won't clash with keys generated by other clients. To do this, you can have unique keys generated with ```push```. Under the hood, ```push``` uses [cuid](https://www.npmjs.com/package/cuid) to generated keys that are guaranteed to be unique and time-sortable.
|
||||
For all generic data you add, you need to create keys that are unique and won't clash with keys generated by other clients. To do this, you can have unique keys generated with `push`. Under the hood, `push` uses [cuid](https://www.npmjs.com/package/cuid) to generated keys that are guaranteed to be unique and time-sortable.
|
||||
|
||||
```javascript
|
||||
db.ref('users')
|
||||
|
|
@ -360,17 +353,17 @@ db.ref('messages').update(newMessages)
|
|||
|
||||
### Using arrays
|
||||
|
||||
AceBase supports storage of arrays, but there are some caveats when working with them. For instance, you cannot remove or insert items that are not at the end of the array. AceBase arrays work like a stack, you can add and remove from the top, not within. It is possible however to edit individual entries, or to overwrite the entire array. The safest way to edit arrays is with a ```transaction```, which requires all data to be loaded and stored again. In many cases, it is wiser to use object collections instead.
|
||||
AceBase supports storage of arrays, but there are some caveats when working with them. For instance, you cannot remove or insert items that are not at the end of the array. AceBase arrays work like a stack, you can add and remove from the top, not within. It is possible however to edit individual entries, or to overwrite the entire array. The safest way to edit arrays is with a `transaction`, which requires all data to be loaded and stored again. In many cases, it is wiser to use object collections instead.
|
||||
|
||||
You can safely use arrays when:
|
||||
* The number of items are small and finite, meaning you could estimate the typical average number of items in it.
|
||||
* There is no need to retrieve/edit individual items using their stored path. If you reorder the items in an array, their paths change (eg from ```"playlist/songs[4]"``` to ```"playlist/songs[1]"```)
|
||||
* The entries stored are small and do not have a lot of nested data (small strings or simple objects, eg: ```chat/members``` with user IDs array ```['ewout','john','pete']```)
|
||||
* There is no need to retrieve/edit individual items using their stored path. If you reorder the items in an array, their paths change (eg from `"playlist/songs[4]"` to `"playlist/songs[1]"`)
|
||||
* The entries stored are small and do not have a lot of nested data (small strings or simple objects, eg: `chat/members` with user IDs array `['ewout','john','pete']`)
|
||||
* The collection does not need to be edited frequently.
|
||||
|
||||
Use object collections instead when:
|
||||
* The collection keeps growing (eg: user generated content)
|
||||
* The path of items are important and preferably not change, eg ```"playlist/songs[4]"``` might point to a different entry if the array is edited. When using an object collection, ```playlist/songs/jld2cjxh0000qzrmn831i7rn``` will always refer to that same item.
|
||||
* The path of items are important and preferably not change, eg `"playlist/songs[4]"` might point to a different entry if the array is edited. When using an object collection, `playlist/songs/jld2cjxh0000qzrmn831i7rn` will always refer to that same item.
|
||||
* The entries stored are large (eg large strings / blobs / objects with lots of nested data)
|
||||
* You have to edit the collection frequently.
|
||||
|
||||
|
|
@ -420,13 +413,13 @@ let songs = snap.val();
|
|||
// NOTE: songs is instanceof PartialArray, which is an object with properties '0', '5', '8'
|
||||
```
|
||||
|
||||
NOTE: you CANNOT use ```ref.push()``` to add entries to an array! `push` can only be used on object collections because it generates unique child IDs such as `"jpx0k53u0002ecr7s354c51l"` (which obviously is not a valid array index)
|
||||
NOTE: you CANNOT use `ref.push()` to add entries to an array! `push` can only be used on object collections because it generates unique child IDs such as `"jpx0k53u0002ecr7s354c51l"` (which obviously is not a valid array index)
|
||||
|
||||
To summarize: ONLY use arrays if using an object collection seems like overkill, and be very cautious! Adding and removing items can only be done to/from the END of an array, unless you rewrite the entire array. That means you will have to know how many entries your array has up-front to be able to add new entries, which is not really desirable in most situations. If you feel the urge to use an array because the order of the entries are important for you or your app: consider using an object collection instead, and add an 'order' property to the entries to perform a sort on.
|
||||
|
||||
## Counting children
|
||||
|
||||
To quickly find out how many children a specific node has, use the ```count``` method on a ```DataReference```:
|
||||
To quickly find out how many children a specific node has, use the `count` method on a `DataReference`:
|
||||
|
||||
```javascript
|
||||
const messageCount = await db.ref('chat/messages').count();
|
||||
|
|
@ -434,7 +427,7 @@ const messageCount = await db.ref('chat/messages').count();
|
|||
|
||||
### Limit nested data loading
|
||||
|
||||
If your database structure is using nesting (eg storing posts in ```'users/someuser/posts'``` instead of in ```'posts'```), you might want to limit the amount of data you are retrieving in most cases. Eg: if you want to get the details of a user, but don't want to load all nested data, you can explicitly limit the nested data retrieval by passing ```exclude```, ```include```, and/or ```child_objects``` options to ```.get```:
|
||||
If your database structure is using nesting (eg storing posts in `'users/someuser/posts'` instead of in `'posts'`), you might want to limit the amount of data you are retrieving in most cases. Eg: if you want to get the details of a user, but don't want to load all nested data, you can explicitly limit the nested data retrieval by passing `exclude`, `include`, and/or `child_objects` options to `.get`:
|
||||
|
||||
```javascript
|
||||
// Exclude specific nested data:
|
||||
|
|
@ -496,13 +489,13 @@ Also see [Streaming query results](#streaming-query-results)
|
|||
## Monitoring realtime data changes
|
||||
|
||||
You can subscribe to data events to get realtime notifications as the monitored node is being changed. When connected to a remote AceBase server, the events will be pushed to clients through a websocket connection. Supported events are:
|
||||
- ```'value'```: triggered when a node's value changes (including changes to any child value)
|
||||
- ```'child_added'```: triggered when a child node is added, callback contains a snapshot of the added child node
|
||||
- ```'child_changed'```: triggered when a child node's value changed, callback contains a snapshot of the changed child node
|
||||
- ```'child_removed'```: triggered when a child node is removed, callback contains a snapshot of the removed child node
|
||||
- ```'mutated'```: (NEW v0.9.51) triggered when any nested property of a node changes, callback contains a snapshot and reference of the exact mutation.
|
||||
- ```'mutations'```: (NEW v0.9.60) like ```'mutated'```, but fires with an array of all mutations caused by a single database update.
|
||||
- ```'notify_*'```: notification only version of above events without data, see "Notify only events" below
|
||||
- `'value'`: triggered when a node's value changes (including changes to any child value)
|
||||
- `'child_added'`: triggered when a child node is added, callback contains a snapshot of the added child node
|
||||
- `'child_changed'`: triggered when a child node's value changed, callback contains a snapshot of the changed child node
|
||||
- `'child_removed'`: triggered when a child node is removed, callback contains a snapshot of the removed child node
|
||||
- `'mutated'`: (NEW v0.9.51) triggered when any nested property of a node changes, callback contains a snapshot and reference of the exact mutation.
|
||||
- `'mutations'`: (NEW v0.9.60) like `'mutated'`, but fires with an array of all mutations caused by a single database update.
|
||||
- `'notify_*'`: notification only version of above events without data, see "Notify only events" below
|
||||
|
||||
```javascript
|
||||
// Using event callback
|
||||
|
|
@ -521,7 +514,7 @@ db.ref('users').on('child_added', userAdded);
|
|||
db.ref('users').off('child_added', userAdded);
|
||||
```
|
||||
|
||||
AceBase uses the same ```.on``` and ```.off``` method signatures as Firebase, but also offers another way to subscribe to the events using the returned ```EventStream``` you can ```subscribe``` to. Having a subscription helps to easier unsubscribe from the events later. Additionally, ```subscribe``` callbacks only fire for future events by default, as opposed to the ```.on``` callback, which also fires for current values of events ```'value'``` and ```'child_added'```:
|
||||
AceBase uses the same `.on` and `.off` method signatures as Firebase, but also offers another way to subscribe to the events using the returned `EventStream` you can `subscribe` to. Having a subscription helps to easier unsubscribe from the events later. Additionally, `subscribe` callbacks only fire for future events by default, as opposed to the `.on` callback, which also fires for current values of events `'value'` and `'child_added'`:
|
||||
|
||||
```javascript
|
||||
// Using .subscribe
|
||||
|
|
@ -551,7 +544,7 @@ removeSubscription.stop();
|
|||
changesSubscription.stop();
|
||||
```
|
||||
|
||||
If you want to use ```.subscribe``` while also getting callbacks on existing data, pass ```true``` as the callback argument:
|
||||
If you want to use `.subscribe` while also getting callbacks on existing data, pass `true` as the callback argument:
|
||||
```javascript
|
||||
db.ref('users/some_user')
|
||||
.on('value', true) // passing true triggers .subscribe callback for current value as well
|
||||
|
|
@ -560,7 +553,7 @@ db.ref('users/some_user')
|
|||
});
|
||||
```
|
||||
|
||||
The ```EventStream``` returned by ```.on``` can also be used to ```subscribe``` more than once:
|
||||
The `EventStream` returned by `.on` can also be used to `subscribe` more than once:
|
||||
|
||||
```javascript
|
||||
const newPostStream = db.ref('posts').on('child_added');
|
||||
|
|
@ -613,7 +606,7 @@ db.ref('users/ewout/posts').push({ title: 'new post' });
|
|||
|
||||
### Notify only events
|
||||
|
||||
In additional to the events mentioned above, you can also subscribe to their ```notify_``` counterparts which do the same, but with a reference to the changed data instead of a snapshot. This is quite useful if you want to monitor changes, but are not interested in the actual values. Doing this also saves serverside resources, and results in less data being transferred from the server. Eg: ```notify_child_changed``` will run your callback with a reference to the changed node:
|
||||
In additional to the events mentioned above, you can also subscribe to their `notify_` counterparts which do the same, but with a reference to the changed data instead of a snapshot. This is quite useful if you want to monitor changes, but are not interested in the actual values. Doing this also saves serverside resources, and results in less data being transferred from the server. Eg: `notify_child_changed` will run your callback with a reference to the changed node:
|
||||
|
||||
```javascript
|
||||
ref.on('notify_child_changed', childRef => {
|
||||
|
|
@ -642,7 +635,7 @@ subscription.activated()
|
|||
});
|
||||
```
|
||||
|
||||
If you want to handle changes in the subscription state after it was activated (eg because server-side access rights have changed), provide a callback function to the ```activated``` call:
|
||||
If you want to handle changes in the subscription state after it was activated (eg because server-side access rights have changed), provide a callback function to the `activated` call:
|
||||
```javascript
|
||||
subscription.activated((activated, cancelReason) => {
|
||||
if (!activated) {
|
||||
|
|
@ -744,7 +737,7 @@ chatRef.child('messages').push({
|
|||
|
||||
NOTE: if you are connected to a remote AceBase server and the connection was lost, it is important that you always get the latest value upon reconnecting because you might have missed mutation events.
|
||||
|
||||
The ```'mutations'``` event does the same as ```'mutated'```, but will be fired on the subscription path with an array of all mutations caused by a single database update. The best way to handle these mutations is by iterating them using ```snapshot.forEach```:
|
||||
The `'mutations'` event does the same as `'mutated'`, but will be fired on the subscription path with an array of all mutations caused by a single database update. The best way to handle these mutations is by iterating them using `snapshot.forEach`:
|
||||
|
||||
```javascript
|
||||
chatRef.on('mutations', snap => {
|
||||
|
|
@ -757,7 +750,7 @@ chatRef.on('mutations', snap => {
|
|||
### Observe realtime value changes
|
||||
(NEW v0.9.51)
|
||||
|
||||
You can now observe the realtime value of a path, and (for example) bind it to your UI. ```ref.observe()``` returns a RxJS Observable that can be used to observe updates to this node and its children. It does not return snapshots, so you can bind the observable straight to your UI. The value being observed is updated internally using the "mutations" database event. All database mutations are automatically applied to the in-memory value, and trigger the observable to emit the new value.
|
||||
You can now observe the realtime value of a path, and (for example) bind it to your UI. `ref.observe()` returns a RxJS Observable that can be used to observe updates to this node and its children. It does not return snapshots, so you can bind the observable straight to your UI. The value being observed is updated internally using the "mutations" database event. All database mutations are automatically applied to the in-memory value, and trigger the observable to emit the new value.
|
||||
|
||||
```html
|
||||
<!-- In your Angular view template: -->
|
||||
|
|
@ -770,7 +763,7 @@ You can now observe the realtime value of a path, and (for example) bind it to y
|
|||
</ng-container>
|
||||
```
|
||||
|
||||
_Note that to use Angular's ```*ngFor``` on an object collection, you have to use the ```keyvalue``` pipe._
|
||||
_Note that to use Angular's `*ngFor` on an object collection, you have to use the `keyvalue` pipe._
|
||||
|
||||
```javascript
|
||||
// In your Angular component:
|
||||
|
|
@ -792,7 +785,7 @@ ngOnDestroy() {
|
|||
}
|
||||
```
|
||||
|
||||
NOTE: objects returned in the observable are only updated downstream - any changes made locally won't be updated in the database. If that is what you would want to do... keep reading! (Spoiler alert - use ```proxy()```!)
|
||||
NOTE: objects returned in the observable are only updated downstream - any changes made locally won't be updated in the database. If that is what you would want to do... keep reading! (Spoiler alert - use `proxy()`!)
|
||||
|
||||
### Realtime synchronization with a live data proxy
|
||||
(NEW v0.9.51)
|
||||
|
|
@ -844,7 +837,7 @@ db.ref('chats/chat1/messages').update({
|
|||
});
|
||||
```
|
||||
|
||||
To get a notification each time a mutation is made to the value, use ```proxy.onMutation(handler)```. To get notifications about any errors that might occur, use ```proxy.onError(handler)```:
|
||||
To get a notification each time a mutation is made to the value, use `proxy.onMutation(handler)`. To get notifications about any errors that might occur, use `proxy.onError(handler)`:
|
||||
|
||||
```javascript
|
||||
proxy.onError(err => {
|
||||
|
|
@ -855,7 +848,7 @@ proxy.onMutation((mutationSnapshot, isRemoteChange) => {
|
|||
})
|
||||
```
|
||||
|
||||
If you no longer need the proxy object, use ```proxy.destroy()``` to stop realtime updating. Don't forget this!
|
||||
If you no longer need the proxy object, use `proxy.destroy()` to stop realtime updating. Don't forget this!
|
||||
|
||||
A number of additional methods are available to all proxied object values to make it possible to monitor specific properties being changed, get the actual target values, add children etc. See code below for more details:
|
||||
|
||||
|
|
@ -868,14 +861,14 @@ if (!proxy.hasValue) {
|
|||
const chat = proxy.value;
|
||||
```
|
||||
|
||||
**```forEach```**: iterate object collection
|
||||
**`forEach`**: iterate object collection
|
||||
```javascript
|
||||
chat.messages.forEach((message, key, index) => {
|
||||
// Fired for all messages in collection, or until returning false
|
||||
});
|
||||
```
|
||||
|
||||
**```for...of```**: iterate array or object collection's values, keys or entries (v1.2.0+)
|
||||
**`for...of`**: iterate array or object collection's values, keys or entries (v1.2.0+)
|
||||
```js
|
||||
for (let message of chat.messages) {
|
||||
// Iterates with default .values iterator, same as:
|
||||
|
|
@ -889,12 +882,12 @@ for (let [key, message] of chat.messages.entries()) {
|
|||
}
|
||||
```
|
||||
|
||||
**```push```**: Add item to object collection with generated key
|
||||
**`push`**: Add item to object collection with generated key
|
||||
```javascript
|
||||
const key = chat.messages.push({ text: 'New message' });
|
||||
```
|
||||
|
||||
**```remove```**: delete a node
|
||||
**`remove`**: delete a node
|
||||
```javascript
|
||||
chat.messages[key].remove();
|
||||
chat.messages.someotherkey.remove();
|
||||
|
|
@ -905,24 +898,24 @@ delete chat.messages.somemessage;
|
|||
chat.messages.somemessage = null;
|
||||
```
|
||||
|
||||
**```toArray```**: access an object collection like an array:
|
||||
**`toArray`**: access an object collection like an array:
|
||||
```javascript
|
||||
const array = chat.messages.toArray();
|
||||
```
|
||||
|
||||
**```toArray``` (with sort)**: like above, sorting the results:
|
||||
**`toArray` (with sort)**: like above, sorting the results:
|
||||
```javascript
|
||||
const sortedArray = chat.messages.toArray((a, b) => a.sent < b.sent ? -1 : 1);
|
||||
```
|
||||
|
||||
**```valueOf```** (or **```getTarget```**): gets the underlying value (unproxied, be careful!)
|
||||
**`valueOf`** (or **`getTarget`**): gets the underlying value (unproxied, be careful!)
|
||||
```js
|
||||
const message = chat.messages.message1.valueOf();
|
||||
message.text = 'This does NOT update the database'; // Because it is not the proxied value
|
||||
chat.messages.message1.text = 'This does'; // Just so you know
|
||||
```
|
||||
|
||||
**```onChanged```**: registers a callback for the value that is called every time the underlying value changes:
|
||||
**`onChanged`**: registers a callback for the value that is called every time the underlying value changes:
|
||||
```javascript
|
||||
chat.messages.message1.onChanged((message, previous, isRemote, context) => {
|
||||
if (message.read) {
|
||||
|
|
@ -935,7 +928,7 @@ chat.messages.message1.onChanged((message, previous, isRemote, context) => {
|
|||
});
|
||||
```
|
||||
|
||||
**```getRef```**: returns a DataReference instance to current target if you'd want or need to do stuff outside of the proxy's scope:
|
||||
**`getRef`**: returns a DataReference instance to current target if you'd want or need to do stuff outside of the proxy's scope:
|
||||
```javascript
|
||||
const messageRef = chat.messages.message1.getRef();
|
||||
// Eg: add an "old fashioned" event handler
|
||||
|
|
@ -944,7 +937,7 @@ messageRef.on('child_changed', snap => { /* .. */ });
|
|||
await messageRef.update({ read: new Date() });
|
||||
```
|
||||
|
||||
**```getObservable```**: returns a RxJS Observable that is updated each time the underlying value changes:
|
||||
**`getObservable`**: returns a RxJS Observable that is updated each time the underlying value changes:
|
||||
```javascript
|
||||
const observable = chat.messages.message1.getObservable();
|
||||
const subscription = observable.subscribe(message => {
|
||||
|
|
@ -956,7 +949,7 @@ const subscription = observable.subscribe(message => {
|
|||
subscription.unsubscribe();
|
||||
```
|
||||
|
||||
**```startTransaction```**: (NEW v0.9.62) Enables you to make changes to the proxied value, but not writing them to the database until you want them to. This makes it possble to bind a proxy to an input form, and wait to save the changes until the user click 'Save', or rollback when canceling. Meanwhile, the value will still be updated with any remote changes.
|
||||
**`startTransaction`**: (NEW v0.9.62) Enables you to make changes to the proxied value, but not writing them to the database until you want them to. This makes it possble to bind a proxy to an input form, and wait to save the changes until the user click 'Save', or rollback when canceling. Meanwhile, the value will still be updated with any remote changes.
|
||||
|
||||
```javascript
|
||||
const proxy = await db.ref('contacts/ewout').proxy();
|
||||
|
|
@ -979,11 +972,11 @@ function rollback() {
|
|||
console.log('All changes made were rolled back');
|
||||
}
|
||||
```
|
||||
Once ```tx.commit()``` is called, all pending updates will be processed and saved to the database. When ```tx.rollback()``` is called, all changes made to the proxied object will be reverted and no further action is taken.
|
||||
Once `tx.commit()` is called, all pending updates will be processed and saved to the database. When `tx.rollback()` is called, all changes made to the proxied object will be reverted and no further action is taken.
|
||||
|
||||
### Using proxy methods in Typescript
|
||||
|
||||
In TypeScript some additional typecasting is needed to access proxy methods shown above. You can use the ```proxyAccess``` function to get help with that. This function typecasts and also checks if your passed value is indeed a proxy.
|
||||
In TypeScript some additional typecasting is needed to access proxy methods shown above. You can use the `proxyAccess` function to get help with that. This function typecasts and also checks if your passed value is indeed a proxy.
|
||||
```typescript
|
||||
type ChatMessage = { from: string, text: string, sent: Date, received: Date, read: Date };
|
||||
type MessageCollection = ObjectCollection<ChatMessage>;
|
||||
|
|
@ -1013,7 +1006,7 @@ proxyAccess<MessageCollection>(chat.messages)
|
|||
});
|
||||
```
|
||||
|
||||
With Angular, ```getObservable``` comes in handy for UI binding and updating:
|
||||
With Angular, `getObservable` comes in handy for UI binding and updating:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
|
|
@ -1038,7 +1031,7 @@ export class ChatComponent {
|
|||
}
|
||||
```
|
||||
|
||||
For completeness of above example, ```MyDataProvider``` would look something like this:
|
||||
For completeness of above example, `MyDataProvider` would look something like this:
|
||||
```typescript
|
||||
import { AceBase } from 'acebase';
|
||||
@Injectable({
|
||||
|
|
@ -1052,7 +1045,7 @@ export class MyDataProvider {
|
|||
}
|
||||
```
|
||||
|
||||
I'll leave up to your imagination what the ```MessageComponent``` would look like.
|
||||
I'll leave up to your imagination what the `MessageComponent` would look like.
|
||||
|
||||
## Querying data
|
||||
|
||||
|
|
@ -1157,7 +1150,7 @@ The snapshots in the example above will only contain each matching song's _title
|
|||
|
||||
### Removing data with a query
|
||||
|
||||
To remove all nodes that match a query, simply call ```remove``` instead of ```get```:
|
||||
To remove all nodes that match a query, simply call `remove` instead of `get`:
|
||||
```javascript
|
||||
db.query('songs')
|
||||
.filter('year', '<', 1950)
|
||||
|
|
@ -1275,11 +1268,11 @@ db.query('books')
|
|||
.get(gotMatches)
|
||||
```
|
||||
|
||||
NOTE: Usage of ```take``` and ```skip``` are currently not taken into consideration, events might fire for results that are not in the requested range.
|
||||
NOTE: Usage of `take` and `skip` are currently not taken into consideration, events might fire for results that are not in the requested range.
|
||||
|
||||
## Indexing data
|
||||
|
||||
Indexing data will dramatically improve the speed of queries on your data, especially as it increases in size. Any indexes you create will be updated automatically when underlying data is changed, added or removed. Indexes are used to speed up filters and sorts, and to limit the amount of results. NOTE: If you are connected to an external AceBase server (using ```AceBaseClient```), indexes can only be created if you are signed in as the *admin* user.
|
||||
Indexing data will dramatically improve the speed of queries on your data, especially as it increases in size. Any indexes you create will be updated automatically when underlying data is changed, added or removed. Indexes are used to speed up filters and sorts, and to limit the amount of results. NOTE: If you are connected to an external AceBase server (using `AceBaseClient`), indexes can only be created if you are signed in as the *admin* user.
|
||||
|
||||
```javascript
|
||||
Promise.all([
|
||||
|
|
@ -1350,7 +1343,7 @@ const snapshots = await db.query('songs')
|
|||
.get();
|
||||
```
|
||||
|
||||
If you are filtering data on one key, and are sorting on another key, it is highly recommended to include the ```sort``` key in your index on the ```filter``` key, because this will greatly increase sorting performance:
|
||||
If you are filtering data on one key, and are sorting on another key, it is highly recommended to include the `sort` key in your index on the `filter` key, because this will greatly increase sorting performance:
|
||||
|
||||
```javascript
|
||||
await db.indexes.create('songs', 'title', { include: ['year', 'genre'] });
|
||||
|
|
@ -1382,11 +1375,11 @@ In addition to the `include` option described above, you can specify the followi
|
|||
|
||||
### Special indexes
|
||||
|
||||
Normal indexes are able to index ```string```, ```number```, ```Date```, ```boolean``` and ```undefined``` (non-existent) values. To index other data, you have to create a special index. Currently supported special indexes are: **Array**, **FullText** and **Geo** indexes.
|
||||
Normal indexes are able to index `string`, `number`, `Date`, `boolean` and `undefined` (non-existent) values. To index other data, you have to create a special index. Currently supported special indexes are: **Array**, **FullText** and **Geo** indexes.
|
||||
|
||||
### Array indexes
|
||||
|
||||
Use Array indexes to dramatically improve the speed of ```"contains"``` filters on array values.
|
||||
Use Array indexes to dramatically improve the speed of `"contains"` filters on array values.
|
||||
Consider the following data structure:
|
||||
|
||||
```javascript
|
||||
|
|
@ -1398,7 +1391,7 @@ chats: {
|
|||
}
|
||||
```
|
||||
|
||||
By adding an index to the ```members``` key, this will speed up queries to get all chats a specific user is in.
|
||||
By adding an index to the `members` key, this will speed up queries to get all chats a specific user is in.
|
||||
|
||||
```javascript
|
||||
db.indexes.create('chats', 'members', { type: 'array' });
|
||||
|
|
@ -1420,7 +1413,7 @@ db.query('chats')
|
|||
// Got all chats with ewout AND jack
|
||||
})
|
||||
```
|
||||
Using ```!contains``` you can check which chats do not involve 1 or more users:
|
||||
Using `!contains` you can check which chats do not involve 1 or more users:
|
||||
```javascript
|
||||
db.query('chats')
|
||||
.filter('members', '!contains', ['ewout', 'jack']);
|
||||
|
|
@ -1599,7 +1592,7 @@ landmarks: {
|
|||
}
|
||||
```
|
||||
|
||||
To query all landmarks in a range of a given location, create a _geo_ index on nodes containing ```lat``` and ```long``` keys. Then use the ```geo:nearby``` filter:
|
||||
To query all landmarks in a range of a given location, create a _geo_ index on nodes containing `lat` and `long` keys. Then use the `geo:nearby` filter:
|
||||
|
||||
```javascript
|
||||
db.indexes.create('landmarks', 'location', { type: 'geo' });
|
||||
|
|
@ -1693,7 +1686,7 @@ const schemas = await db.schema.all();
|
|||
|
||||
## Mapping data to custom classes
|
||||
|
||||
Mapping data to your own classes allows you to store and load objects to/from the database without them losing their class type. Once you have mapped a database path to a class, you won't ever have to worry about serialization or deserialization of the objects => Store a ```User```, get a ```User```. Store a ```Chat``` that has a collection of ```Messages```, get a ```Chat``` with ```Messages``` back from the database. Any class specific methods can be executed directly on the objects you get back from the db, because they will be an ```instanceof``` your class.
|
||||
Mapping data to your own classes allows you to store and load objects to/from the database without them losing their class type. Once you have mapped a database path to a class, you won't ever have to worry about serialization or deserialization of the objects => Store a `User`, get a `User`. Store a `Chat` that has a collection of `Messages`, get a `Chat` with `Messages` back from the database. Any class specific methods can be executed directly on the objects you get back from the db, because they will be an `instanceof` your class.
|
||||
|
||||
By default, AceBase runs your class constructor with a snapshot of the data to instantiate new objects, and uses all properties of your class to serialize them for storage.
|
||||
|
||||
|
|
@ -1728,7 +1721,7 @@ let savedUser = userSnapshot.val();
|
|||
```
|
||||
|
||||
|
||||
If you are unable (or don't want to) to change your class constructor, add a static method named ```create``` to deserialize stored objects:
|
||||
If you are unable (or don't want to) to change your class constructor, add a static method named `create` to deserialize stored objects:
|
||||
|
||||
```javascript
|
||||
class Pet {
|
||||
|
|
@ -1747,7 +1740,7 @@ class Pet {
|
|||
db.types.bind("users/*/pets", Pet);
|
||||
```
|
||||
|
||||
If you want to change how your objects are serialized for storage, add a method named ```serialize``` to your class. You should do this if your class contains properties that should not be serialized (eg ```get``` properties).
|
||||
If you want to change how your objects are serialized for storage, add a method named `serialize` to your class. You should do this if your class contains properties that should not be serialized (eg `get` properties).
|
||||
|
||||
```javascript
|
||||
class Pet {
|
||||
|
|
@ -1764,7 +1757,7 @@ class Pet {
|
|||
db.types.bind("users/*/pets", Pet);
|
||||
```
|
||||
|
||||
If you want to use other methods for instantiation and/or serialization than the defaults explained above, you can manually specify them in the ```bind``` call:
|
||||
If you want to use other methods for instantiation and/or serialization than the defaults explained above, you can manually specify them in the `bind` call:
|
||||
```javascript
|
||||
class Pet {
|
||||
// ...
|
||||
|
|
@ -1808,11 +1801,11 @@ By default, AceBase uses its own binary database format in Node.js environments,
|
|||
### Using SQLite or MSSQL storage
|
||||
(NEW v0.8.0)
|
||||
|
||||
From v0.8.0+ it is now possible to have AceBase store all data in a SQLite or SQL Server database backend! They're not as fast as the default AceBase binary database (which is about 5x faster), but if you want more control over your data, storing it in a widely used DBMS might come in handy. I developed it to be able to make ports to the browser and/or Android/iOS HTML5 apps easier, so ```AceBaseClient```s will be able to store and query data locally also.
|
||||
From v0.8.0+ it is now possible to have AceBase store all data in a SQLite or SQL Server database backend! They're not as fast as the default AceBase binary database (which is about 5x faster), but if you want more control over your data, storing it in a widely used DBMS might come in handy. I developed it to be able to make ports to the browser and/or Android/iOS HTML5 apps easier, so `AceBaseClient`s will be able to store and query data locally also.
|
||||
|
||||
To use a different backend database, simply pass a typed ```StorageSettings``` object to the ```AceBase``` constructor. You can use ```SQLiteStorageSettings``` for a SQLite backend, ```MSSQLStorageSettings``` for SQL Server etc.
|
||||
To use a different backend database, simply pass a typed `StorageSettings` object to the `AceBase` constructor. You can use `SQLiteStorageSettings` for a SQLite backend, `MSSQLStorageSettings` for SQL Server etc.
|
||||
|
||||
Dependencies: SQLite requires the ```sqlite3``` package to be installed from npm (```npm i sqlite3```), MSSQL requires the ```mssql``` package. mssql uses the tedious driver by default, but if you're on Windows you can also use Microsoft's native sql server driver by adding the ```msnodesqlv8``` package as well, and specifying ```driver: 'native'``` in the ```MSSQLStorageSettings```
|
||||
Dependencies: SQLite requires the `sqlite3` package to be installed from npm (`npm i sqlite3`), MSSQL requires the `mssql` package. mssql uses the tedious driver by default, but if you're on Windows you can also use Microsoft's native sql server driver by adding the `msnodesqlv8` package as well, and specifying `driver: 'native'` in the `MSSQLStorageSettings`
|
||||
|
||||
```javascript
|
||||
// Using SQLite backend:
|
||||
|
|
@ -1893,7 +1886,7 @@ const db = AceBase.WithIndexedDB('mydb', { multipleTabs: true });
|
|||
|
||||
Once you've enabled this setting, the AceBase instances running in multiple tabs will exchange what events they are listening for, and notify eachother with any changes made to the monitored data.
|
||||
|
||||
\* Safari (both desktop and iOS versions) do not currently support `BroadcastChannel`, a polyfill will be used. [Browser support](https://caniuse.com/broadcastchannel) is currently at 77% (April 2021)
|
||||
\* If the browser does not support `BroadcastChannel`, a polyfill will be used. [Browser support](https://caniuse.com/broadcastchannel) is currently at 93% (December 2022)
|
||||
|
||||
NOTE: This applies to local databases only. If you are using an `AceBaseClient`, connected to an `AceBaseServer`, changing something in one browser tab will already notify other tabs, because the events are raised by the AceBase server and sent back to the clients automatically. If you use a local `AceBase` instance as offline cache for an `AceBaseClient` and have `multipleTabs` enabled, cross-tab synchronization will only be used while offline.
|
||||
|
||||
|
|
@ -1901,165 +1894,149 @@ NOTE: This applies to local databases only. If you are using an `AceBaseClient`,
|
|||
|
||||
In additional to the already available binary, SQL Server, SQLite, IndexedDB and LocalStorage backends, it's also possible to roll your own custom storage backend, such as MongoDB, MySQL, WebSQL etc. To do this, all you have to do is write a couple of methods to get, set, remove and query data within a transactional context. The only prerequisite is that your used database provider is able to execute queries, or provides some other way to iterate through record entries without having to load them all into memory at once. (Firebase won't do because it can't do that)
|
||||
|
||||
The example below shows how to implement a ```CustomStorage``` class that uses the browser's `LocalStorage`, but you can use anything you'd want. It's easy to change the code below to use any other database provider like MongoDB, PostgreSQL, MySQL etc.
|
||||
The example below shows how to implement a `CustomStorage` class that uses the browser's `LocalStorage`, but you can use anything you'd want. It's easy to change the code below to use any other database provider like MongoDB, PostgreSQL, MySQL etc.
|
||||
|
||||
NOTE: The code below is similar to the implementation of `AceBase.WithLocalStorage`
|
||||
|
||||
```javascript
|
||||
const { AceBase, CustomStorageSettings, CustomStorageTransaction, CustomStorageHelpers } = require('acebase');
|
||||
|
||||
const dbname = 'test';
|
||||
```typescript
|
||||
import { AceBase, CustomStorageSettings, CustomStorageTransaction, CustomStorageHelpers, ICustomStorageNodeMetaData, ICustomStorageNode } from 'acebase';
|
||||
|
||||
// Setup our CustomStorageSettings
|
||||
const storageSettings = new CustomStorageSettings({
|
||||
name: 'LocalStorage',
|
||||
locking: true, // Let AceBase handle resource locking to prevent multiple simultanious updates to the same data
|
||||
|
||||
ready() {
|
||||
async ready() {
|
||||
// LocalStorage is always ready
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
getTransaction(target) {
|
||||
async getTransaction(target: { path: string, write: boolean }) {
|
||||
// Create an instance of our transaction class
|
||||
const context = {
|
||||
debug: true,
|
||||
dbname
|
||||
}
|
||||
const context = { debug: true, dbname };
|
||||
const transaction = new LocalStorageTransaction(context, target);
|
||||
return Promise.resolve(transaction);
|
||||
return transaction;
|
||||
}
|
||||
});
|
||||
|
||||
// Setup CustomStorageTransaction for browser's LocalStorage
|
||||
class LocalStorageTransaction extends CustomStorageTransaction {
|
||||
|
||||
/**
|
||||
* @param {{debug: boolean, dbname: string}} context
|
||||
* @param {{path: string, write: boolean}} target
|
||||
*/
|
||||
constructor(context, target) {
|
||||
constructor(context: { debug: boolean; dbname: string }, target: { path: string, write: boolean }) {
|
||||
super(target);
|
||||
this.context = context;
|
||||
this._storageKeysPrefix = `${this.context.dbname}.acebase::`;
|
||||
}
|
||||
|
||||
commit() {
|
||||
async commit() {
|
||||
// To implement REAL commit and rollback capabilities, we'd have to add pending mutations to a batch,
|
||||
// and store upon commit, or toss upon rollback. This is what AceBase.WithIndexedDB does, is also way faster.
|
||||
return Promise.resolve(); // All changes have already been committed
|
||||
// All changes have already been committed
|
||||
}
|
||||
|
||||
rollback(err) {
|
||||
async rollback(err: any) {
|
||||
// Not able to rollback changes, was already comitted.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
get(path) {
|
||||
async get(path: string) {
|
||||
// Gets value from localStorage, wrapped in Promise
|
||||
return new Promise(resolve => {
|
||||
const json = localStorage.getItem(this.getStorageKeyForPath(path));
|
||||
const val = JSON.parse(json);
|
||||
resolve(val);
|
||||
});
|
||||
const json = localStorage.getItem(this.getStorageKeyForPath(path));
|
||||
const val = JSON.parse(json);
|
||||
return val;
|
||||
}
|
||||
|
||||
set(path, val) {
|
||||
async set(path: string, val: any) {
|
||||
// Sets value in localStorage, wrapped in Promise
|
||||
return new Promise(resolve => {
|
||||
const json = JSON.stringify(val);
|
||||
localStorage.setItem(this.getStorageKeyForPath(path), json);
|
||||
resolve();
|
||||
});
|
||||
const json = JSON.stringify(val);
|
||||
const key = this.getStorageKeyForPath(path);
|
||||
localStorage.setItem(key, json);
|
||||
}
|
||||
|
||||
remove(path) {
|
||||
async remove(path: string) {
|
||||
// Removes a value from localStorage, wrapped in Promise
|
||||
return new Promise(resolve => {
|
||||
localStorage.removeItem(this.getStorageKeyForPath(path));
|
||||
resolve();
|
||||
});
|
||||
const key = this.getStorageKeyForPath(path);
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
childrenOf(path, include, checkCallback, addCallback) {
|
||||
async childrenOf(
|
||||
path: string,
|
||||
include: { metadata: boolean; value: boolean; },
|
||||
checkCallback: (path: string) => boolean,
|
||||
addCallback: (path: string, node: ICustomStorageNodeMetaData | ICustomStorageNode) => boolean,
|
||||
) {
|
||||
// Streams all child paths
|
||||
// Cannot query localStorage, so loop through all stored keys to find children
|
||||
return new Promise(resolve => {
|
||||
const pathInfo = CustomStorageHelpers.PathInfo.get(path);
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key.startsWith(this._storageKeysPrefix)) { continue; }
|
||||
let otherPath = this.getPathFromStorageKey(key);
|
||||
if (pathInfo.isParentOf(otherPath) && checkCallback(otherPath)) {
|
||||
let node;
|
||||
if (include.metadata || include.value) {
|
||||
const json = localStorage.getItem(key);
|
||||
node = JSON.parse(json);
|
||||
}
|
||||
const keepGoing = addCallback(otherPath, node);
|
||||
if (!keepGoing) { break; }
|
||||
const pathInfo = CustomStorageHelpers.PathInfo.get(path);
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key.startsWith(this._storageKeysPrefix)) { continue; }
|
||||
let otherPath = this.getPathFromStorageKey(key);
|
||||
if (pathInfo.isParentOf(otherPath) && checkCallback(otherPath)) {
|
||||
let node;
|
||||
if (include.metadata || include.value) {
|
||||
const json = localStorage.getItem(key);
|
||||
node = JSON.parse(json);
|
||||
}
|
||||
const keepGoing = addCallback(otherPath, node);
|
||||
if (!keepGoing) { break; }
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
descendantsOf(path, include, checkCallback, addCallback) {
|
||||
async descendantsOf(
|
||||
path: string,
|
||||
include: { metadata: boolean; value: boolean; },
|
||||
checkCallback: (path: string) => boolean,
|
||||
addCallback: (path: string, node: ICustomStorageNodeMetaData | ICustomStorageNode) => boolean,
|
||||
) {
|
||||
// Streams all descendant paths
|
||||
// Cannot query localStorage, so loop through all stored keys to find descendants
|
||||
return new Promise(resolve => {
|
||||
const pathInfo = CustomStorageHelpers.PathInfo.get(path);
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key.startsWith(this._storageKeysPrefix)) { continue; }
|
||||
let otherPath = this.getPathFromStorageKey(key);
|
||||
if (pathInfo.isAncestorOf(otherPath) && checkCallback(otherPath)) {
|
||||
let node;
|
||||
if (include.metadata || include.value) {
|
||||
const json = localStorage.getItem(key);
|
||||
node = JSON.parse(json);
|
||||
}
|
||||
const keepGoing = addCallback(otherPath, node);
|
||||
if (!keepGoing) { break; }
|
||||
const pathInfo = CustomStorageHelpers.PathInfo.get(path);
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key.startsWith(this._storageKeysPrefix)) { continue; }
|
||||
let otherPath = this.getPathFromStorageKey(key);
|
||||
if (pathInfo.isAncestorOf(otherPath) && checkCallback(otherPath)) {
|
||||
let node;
|
||||
if (include.metadata || include.value) {
|
||||
const json = localStorage.getItem(key);
|
||||
node = JSON.parse(json);
|
||||
}
|
||||
const keepGoing = addCallback(otherPath, node);
|
||||
if (!keepGoing) { break; }
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the path from a localStorage key
|
||||
* @param {string} key
|
||||
*/
|
||||
getPathFromStorageKey(key) {
|
||||
getPathFromStorageKey(key: string) {
|
||||
return key.slice(this._storageKeysPrefix.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the localStorage key for a path
|
||||
* @param {string} path
|
||||
*/
|
||||
getStorageKeyForPath(path) {
|
||||
getStorageKeyForPath(path: string) {
|
||||
return `${this._storageKeysPrefix}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, create the database
|
||||
const db = new AceBase(dbname, { logLevel: settings.logLevel, storage: storageSettings });
|
||||
db.ready(ready => {
|
||||
// That's it!
|
||||
})
|
||||
// Now, create the database with our custom storage class
|
||||
const db = new AceBase('dbname', { storage: storageSettings });
|
||||
|
||||
// Ready to use!
|
||||
```
|
||||
|
||||
## Reflect API
|
||||
|
||||
AceBase has a built-in reflection API that enables browsing the database content without retrieving any (nested) data. This API is available for local databases, and remote databases when signed in as the ```admin``` user or on paths the authenticated user has access to.
|
||||
AceBase has a built-in reflection API that enables browsing the database content without retrieving any (nested) data. This API is available for local databases, and remote databases when signed in as the `admin` user or on paths the authenticated user has access to.
|
||||
|
||||
The reflect API is also used internally: AceBase server's webmanager uses it to allow database exploration, and the ```DataReference``` class uses it to deliver results for ```count()``` and initial ```notify_child_added``` event callbacks.
|
||||
The reflect API is also used internally: AceBase server's webmanager uses it to allow database exploration, and the `DataReference` class uses it to deliver results for `count()` and initial `notify_child_added` event callbacks.
|
||||
|
||||
### Get information about a node
|
||||
|
||||
To get information about a node and its children, use an ```info``` query:
|
||||
To get information about a node and its children, use an `info` query:
|
||||
|
||||
```javascript
|
||||
// Get info about the root node and a maximum of 200 children:
|
||||
|
|
@ -2084,7 +2061,7 @@ The above example will return an info object with the following structure:
|
|||
}
|
||||
```
|
||||
|
||||
To get the number of children of a node (instead of enumerating them), pass ```{ child_count: true }``` with the ```info``` reflect request:
|
||||
To get the number of children of a node (instead of enumerating them), pass `{ child_count: true }` with the `info` reflect request:
|
||||
|
||||
```javascript
|
||||
const info = await db.ref('chats/somechat/messages')
|
||||
|
|
@ -2105,7 +2082,7 @@ This will return an info object with the following structure:
|
|||
|
||||
### Get children of a node
|
||||
|
||||
To get information about the children of a node, use the ```children``` reflection query:
|
||||
To get information about the children of a node, use the `children` reflection query:
|
||||
```javascript
|
||||
const children = await db.ref('chats/somechat/messages')
|
||||
.reflect('children', { limit: 10, skip: 0 });
|
||||
|
|
@ -2127,8 +2104,8 @@ The returned children object in above example will have to following structure:
|
|||
## Export API
|
||||
(NEW v0.9.1)
|
||||
|
||||
To export data from any node to json, you can use the export API. Simply pass an object that has a ```write``` method to ```yourRef.export```, and the entire node's value (including nested data) will be streamed in ```json``` format. If your ```write``` function returns a ```Promise```, streaming will be paused until the promise resolves (local databases only). You can use this to back off writing if the target stream's buffer is full (eg while waiting for a file stream to "drain"). This API is available for local databases, and remote databases
|
||||
~~when signed in as the ```admin``` user~~ (from server v0.9.29+) on paths the authenticated user has access to.
|
||||
To export data from any node to json, you can use the export API. Simply pass an object that has a `write` method to `yourRef.export`, and the entire node's value (including nested data) will be streamed in `json` format. If your `write` function returns a `Promise`, streaming will be paused until the promise resolves (local databases only). You can use this to back off writing if the target stream's buffer is full (eg while waiting for a file stream to "drain"). This API is available for local databases, and remote databases
|
||||
~~when signed in as the `admin` user~~ (from server v0.9.29+) on paths the authenticated user has access to.
|
||||
|
||||
```javascript
|
||||
let json = '';
|
||||
|
|
@ -2216,11 +2193,11 @@ See https://github.com/appy-one/acebase/discussions/98 for more info.
|
|||
|
||||
* v0.9.68 - To get the used updating context in data event handlers, read from `snap.context()` instead of `snap.ref.context()`. This is to prevent further updates on `snap.ref` to use the same context. If you need to reuse the event context for new updates, you will have to manually set it: `snap.ref.context(snap.context()).update(...)`
|
||||
|
||||
* v0.7.0 - Changed DataReference.vars object for subscription events, it now contains all values for path wildcards and variables with their index, and (for named variables:) ```name``` and ($-)prefixed ```$name```. The ```wildcards``` array has been removed. See *Using variables and wildcards in subscription paths* in the documentation above.
|
||||
* v0.7.0 - Changed DataReference.vars object for subscription events, it now contains all values for path wildcards and variables with their index, and (for named variables:) `name` and ($-)prefixed `$name`. The `wildcards` array has been removed. See *Using variables and wildcards in subscription paths* in the documentation above.
|
||||
|
||||
* v0.6.0 - Changed ```db.types.bind``` method signature. Serialization and creator functions can now also access the ```DataReference``` for the object being serialized/instantiated, this enables the use of path variables.
|
||||
* v0.6.0 - Changed `db.types.bind` method signature. Serialization and creator functions can now also access the `DataReference` for the object being serialized/instantiated, this enables the use of path variables.
|
||||
|
||||
* v0.4.0 - introduced fulltext, geo and array indexes. This required making changes to the index file format, you will have to delete all index files and create them again using ```db.indexes.create```.
|
||||
* v0.4.0 - introduced fulltext, geo and array indexes. This required making changes to the index file format, you will have to delete all index files and create them again using `db.indexes.create`.
|
||||
|
||||
## Known issues
|
||||
|
||||
|
|
@ -2238,7 +2215,7 @@ What can you help me with?
|
|||
|
||||
* Bugfixes - if you find bugs please create a new issue on github. If you know how to fix one, feel free to submit a pull request or drop me an email
|
||||
* Enhancements - if you've got code to make AceBase even faster or better, you're welcome to contribute!
|
||||
* Ports - If you would like to port ```AceBaseClient``` to other languages (Java, Swift, C#, etc) that would be awesome!
|
||||
* Ports - If you would like to port `AceBaseClient` to other languages (Java, Swift, C#, etc) that would be awesome!
|
||||
* Ideas - I love new ideas, share them!
|
||||
* Money - I am an independant developer and many (MANY) months were put into developing this. I also have a family to feed so if you like AceBase, send me a donation or become a sponsor ♥
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue