acebase-core
Version:
Shared AceBase core components, no need to install manually
384 lines • 17.2 kB
TypeScript
import { DataReference } from './data-reference';
import { DataSnapshot } from './data-snapshot';
import { type Observable } from './optional-observable';
import { IObservableLike } from './simple-observable';
import type { ObjectCollection } from './object-collection';
import type { EventSubscription } from './subscription';
type ProxyObserveMutation = {
snapshot: DataSnapshot;
isRemote: boolean;
};
type ProxyObserveMutationsCallback = (mutationSnapshot: DataSnapshot, isRemoteChange: boolean) => any;
type ProxyObserveError = {
source: string;
message: string;
details: Error;
};
type ProxyObserveErrorCallback = (error: ProxyObserveError) => any;
export interface ILiveDataProxy<T> {
/**
* The live value of the data wrapped in a Proxy
*/
value: T;
/**
* Whether the loaded value exists in the database
*/
readonly hasValue: boolean;
/**
* Reference to the proxied data path
*/
readonly ref: DataReference;
/**
* Current cursor for the proxied data. If you are connected to a remote server with transaction logging enabled,
* and your client has a cache database, you can use this cursor the next time you initialize this live data proxy.
* If you do that, your local cache value will be updated with remote changes since your cursor, and the proxy will
* load the updated value from cache instead of from the server. For larger datasets this greatly improves performance.
*
* Use `proxy.on('cursor', callback)` if you want to be notified of cursor updates.
*/
readonly cursor: string;
/**
* Releases used resources and stops monitoring changes. Equivalent to `proxy.stop()`
*/
destroy(): void;
/**
* Releases used resources and stops monitoring changes. Equivalent to `proxy.destroy()` but sounds more civilized.
*/
stop(): void;
/**
* Manually reloads current value. Is automatically done after server reconnects if no cursor is available (after sync_done event has fired)
*/
reload(): Promise<void>;
/**
* @deprecated Use `.on('mutation', callback)`
* Registers a callback function to call when the underlying data is being changed. This is optional.
* @param callback function to invoke when data is changed
* @see Also see onChanged event in {@link ILiveDataProxyValue<T>}
*/
onMutation(callback: ProxyObserveMutationsCallback): void;
/**
* Registers a callback function to call when an error occurs behind the scenes
* @deprecated Use `.on('error', callback)`
* @param callback
*/
onError(callback: ProxyObserveErrorCallback): void;
/**
* Registers a callback function to call each time the server cursor changes. This is very useful if you are connected
* to a server with transaction logging enabled, and have a local cache database. You can store the cursor somewhere so
* you can synchronize your local cache with the server at app restarts.
*/
on(event: 'cursor', callback: (cursor: string) => any): void;
/**
* Registers a callback function to call when the underlying data is being changed. This is optional.
* If you make changes to the proxy value in your callback function, make sure you are not creating an endless loop!
* @param callback function to invoke when data is changed, `mutationSnapshot` contains a `DataSnapshot` of
* the mutated target, `isRemoteChange` indicates whether the change was made through the proxy (`false`)
* or outside the proxied object (`true`), eg through `ref.update(...)`
*/
on(event: 'mutation', callback: (event: ProxyObserveMutation) => any): void;
/**
* Registers a callback function to call when an error occurs behind the scenes
*/
on(event: 'error', callback: ProxyObserveErrorCallback): any;
off(event: 'cursor' | 'mutation' | 'error', callback: (event: any) => any): void;
}
export interface LiveDataProxyOptions<ValueType> {
/**
* Default value to use for the proxy if the database path does not exist yet. This value will also be written to the database.
*/
defaultValue?: ValueType;
/**
* Cursor to use
*/
cursor?: string;
/**
* Optional callback to execute when a database mutation could not be saved and a rollback to the proxied value is about to be performed.
* If the callback returns `false`, the rollback will be prevented. This can be useful if you want to handle the error yourself and maybe retry.
* @param err the error that causes the rollback
* @param mutation the mutation that failed to be written to the database
* @returns `false` to prevent rollback
*/
shouldRollback?: (err: Error, mutation: {
type: 'set' | 'update';
ref: DataReference;
value: any;
previous: any;
}) => boolean | Promise<boolean>;
}
export declare class LiveDataProxy {
/**
* Creates a live data proxy for the given reference. The data of the reference's path will be loaded, and kept in-sync
* with live data by listening for 'mutations' events. Any changes made to the value by the client will be synced back
* to the database.
* @param ref DataReference to create proxy for.
* @param options proxy initialization options
* be written to the database.
*/
static create<T>(ref: DataReference, options?: LiveDataProxyOptions<T>): Promise<ILiveDataProxy<T>>;
}
/**
* Callback function used for creating an Observer
*/
export type SubscribeFunction<T> = (observer: {
next: (val: T) => void;
}) => () => void;
/**
* @param value Read-only copy of the new value.
* @param previous Read-only copy of the previous value.
* @param isRemote Whether the change was done outside of the current proxy.
* @param context Context used by the code that causing this change.
* @returns Return false if you want to stop monitoring changes
*/
export type DataProxyOnChangeCallback<T> = (value: T, previous: T, isRemote: boolean, context: any) => void | boolean;
export interface ILiveDataProxyTransaction {
readonly status: 'started' | 'finished' | 'canceled';
/**
* Indicates if this transaction has completed, or still needs to be committed or rolled back
*/
readonly completed: boolean;
/**
* Gets pending mutations, can be used to determine if user made changes.
* Useful for asking users "Do you want to save your changes?" when they navigate away from a form without saving.
* Note that this array only contains previous values, the mutated values are in the proxied object value.
* The previous value is needed to rollback the value, and the new value will be read from the proxied object upon commit.
*/
readonly mutations: {
target: Array<string | number>;
previous: any;
}[];
/**
* Whether the transaction has pending mutations that can be committed or rolled back.
*/
readonly hasMutations: boolean;
/**
* Commits the transaction by updating the database with all changes made to the proxied object while the transaction was active
*/
commit(): Promise<void>;
/**
* Rolls back any changes made to the proxied value while the transaction was active.
*/
rollback(): void;
}
export interface ILiveDataProxyValue<ValueType = Record<string, any>> {
/**
* Pushes a child value to an object collection
* @param entry child to add
* @returns returns the new child's key (property name)
*/
push(entry: any): string;
/**
* Removes the stored value from the database. Useful if you don't have a reference
* to current value's parent object.
* @example
* const chat = proxy.value as IChat;
* chat.messages.forEach<IChatMessage>((message, key) => {
* if (message.text.includes('bad words')) {
* (message as any).remove();
* // above is equivalent to:
* chat.messages[key] = null;
* }
* })
*/
remove(): void;
/**
* Executes a callback for each child in the object collection.
* @param callback Callback function to run for each child. If the callback returns false, it will stop.
*/
forEach(callback: (child: ValueType[keyof ValueType], key: string, index: number) => void | boolean): void;
[Symbol.iterator]: IterableIterator<any>;
/**
* Gets an iterator that can be used in `for`...`of` loops
*/
values(): IterableIterator<ValueType[keyof ValueType]>;
/**
* Gets an iterator for all keys in the object collection that can be used in `for`...`of` loops
*/
keys(): IterableIterator<string>;
/**
* Gets an iterator for all key/value pairs in the object collection that can be used in `for`...`of` loops
*/
entries(): IterableIterator<[string, ValueType[keyof ValueType]]>;
/**
* Creates an array from current object collection, and optionally sorts it with passed
* sorting function. All entries in the array will remain proxied values, but the array
* itself is not: changes to the array itself (adding/removing/ordering items) will NOT be
* saved to the database!
*/
toArray(sortFn?: (a: ValueType[keyof ValueType], b: ValueType[keyof ValueType]) => number): ValueType[keyof ValueType][];
/**
* Gets the value wrapped by this proxy. If the value is an object, it is still live but
* READ-ONLY, meaning that it is still being updated with changes made in the database,
* BUT any changes made to this object will NOT be saved to the database!
* @deprecated Use .valueOf() instead
*/
getTarget(): ValueType;
/**
* @param warn whether to log a warning message. Default is true
*/
getTarget(warn: boolean): ValueType;
/**
* Gets the value wrapped by this proxy. Be careful, changes to the returned
* object are not tracked and synchronized.
*/
valueOf(): ValueType;
/**
* Gets a reference to the target data
*/
getRef(): DataReference;
/**
* Starts a subscription that monitors the current value for changes.
* @param callback Function that is called each time the value was updated in the database.
* The callback might be called before the local cache value is updated, so make sure to
* use the READ-ONLY values passed to your callback. If you make changes to the value being
* monitored (the proxied version), make sure you are not creating an endless loop!
* If your callback returns false, the subscription is stopped.
* @returns Returns an EventSubscription, call .stop() on it to unsubscribe.
*/
onChanged(callback: DataProxyOnChangeCallback<ValueType>): EventSubscription;
/**
* EXPERIMENTAL: Returns a subscribe function that can be used to create an RxJS Observable with.
* @example
* const proxy = await db.ref('posts/post1').proxy();
* const post = proxy.value;
* const observable = new Observable(post.comments.subscribe());
* const subscription = observable.subscribe(comments => {
* // re-render comments
* });
* // Later, don't forget:
* subscription.unsubscribe();
*/
subscribe(): SubscribeFunction<ValueType>;
/**
* Returns an RxJS Observable with READ-ONLY values each time a mutation takes place.
* @returns Returns an Observable.
* @example
* const proxy = await db.ref('posts/post1').proxy();
* const post = proxy.value;
* const observable = (post.comments as any).getObservable();
* const subscription = observable.subscribe(comments => {
* // re-render comments
* });
* // Later, don't forget:
* subscription.unsubscribe()
*/
getObservable(): Observable<ValueType>;
getOrderedCollection<OrderKeyName extends string = 'order'>(): OrderedCollectionProxy<ValueType extends Record<string, any> ? ValueType[keyof ValueType] : any, OrderKeyName>;
/**
* Starts a transaction on the value. Local changes made to the value and its children
* will be queued until committed, or undone when rolled back. Meanwhile, the value will
* still be updated with remote changes. Use this to enable editing of values (eg with a
* UI binding), but only saving them once user clicks 'Save'.
* @example
* // ... part of an Angular component:
* class CustomerAddressForm {
* address: CustomerAddress; // Bound to input form
* private transaction: ILiveDataProxyTransaction;
* constructor(private db: MyDBProvider) { }
* async ngOnInit() {
* const ref = this.db.ref('customers/customer1/address');
* const proxy = await ref.proxy<CustomerAddress>();
* this.address = proxy.value;
* this.transaction = proxyAccess(this.address).startTransaction();
* }
* async save() {
* // Executed when user click "Save" button
* await this.transaction.commit();
* }
* cancel() {
* // Executes when user click "Cancel" button, or closes the form
* this.transaction.rollback();
* }
* }
*/
startTransaction(): Promise<ILiveDataProxyTransaction>;
}
/**
* Convenience function to access ILiveDataProxyValue methods on a proxied value
* @param proxiedValue The proxied value to get access to
* @returns Returns the same object typecasted to an ILiveDataProxyValue
* @example
* // IChatMessages is an ObjectCollection<IChatMessage>
* let observable: Observable<IChatMessages>;
*
* // Allows you to do this:
* observable = proxyAccess<IChatMessages>(chat.messages).getObservable();
*
* // Instead of:
* observable = (chat.messages.msg1 as any as ILiveDataProxyValue<IChatMessages>).getObservable();
*
* // Both do the exact same, but the first is less obscure
*/
export declare function proxyAccess<ValueType>(proxiedValue: ValueType): ILiveDataProxyValue<ValueType>;
/**
* Provides functionality to work with ordered collections through a live data proxy. Eliminates
* the need for arrays to handle ordered data by adding a 'sort' properties to child objects in a
* collection, and provides functionality to sort and reorder items with a minimal amount of database
* updates.
*/
export declare class OrderedCollectionProxy<ItemType extends {
[KeyName in OrderKeyName]: number;
}, OrderKeyName extends string = 'order'> {
private collection;
private orderProperty;
private orderIncrement;
constructor(collection: ObjectCollection<ItemType & {
[KeyName in OrderKeyName]: number;
}>, orderProperty?: OrderKeyName, orderIncrement?: number);
/**
* Gets an observable for the target object collection. Same as calling `collection.getObservable()`
* @returns
*/
getObservable(): IObservableLike<ObjectCollection<ItemType>>;
/**
* Gets an observable that emits a new ordered array representation of the object collection each time
* the unlaying data is changed. Same as calling `getArray()` in a `getObservable().subscribe` callback
* @returns
*/
getArrayObservable(): IObservableLike<ItemType[]>;
/**
* Gets an ordered array representation of the items in your object collection. The items in the array
* are proxied values, changes will be in sync with the database. Note that the array itself
* is not mutable: adding or removing items to it will NOT update the collection in the
* the database and vice versa. Use `add`, `delete`, `sort` and `move` methods to make changes
* that impact the collection's sorting order
* @returns order array
*/
getArray(): ItemType[];
/**
* Adds or moves an item to/within the object collection and takes care of the proper sorting order.
* @param item Item to add or move
* @param index Optional target index in the sorted representation, appends if not specified.
* @param from If the item is being moved
* @returns
*/
add(item: ItemType, index?: number, from?: number): {
key: string;
index: number;
};
/**
* Deletes an item from the object collection using the their index in the sorted array representation
* @param index
* @returns the key of the collection's child that was deleted
*/
delete(index: number): {
key: string;
index: number;
};
/**
* Moves an item in the object collection by reordering it
* @param fromIndex Current index in the array (the ordered representation of the object collection)
* @param toIndex Target index in the array
* @returns
*/
move(fromIndex: number, toIndex: number): {
key: string;
index: number;
};
/**
* Reorders the object collection using given sort function. Allows quick reordering of the collection which is persisted in the database
* @param sortFn
*/
sort(sortFn: (a: ItemType, b: ItemType) => number): void;
}
export {};
//# sourceMappingURL=data-proxy.d.ts.map