Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 54 additions & 48 deletions src/data-reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export class DataReference<T = any> {
readonly path: string,
readonly key: string|number,
readonly callbacks: IEventSubscription[],
db: AceBaseBase,
vars: PathVariables,
context: any,
pushed: boolean, // If DataReference was created by .push
Expand All @@ -144,7 +145,7 @@ export class DataReference<T = any> {
/**
* Creates a reference to a node
*/
constructor (public readonly db: AceBaseBase, path: string, vars?: PathVariables) {
constructor (db: AceBaseBase, path: string, vars?: PathVariables) {
if (!path) { path = ''; }
path = path.replace(/^\/|\/$/g, ''); // Trim slashes
const pathInfo = PathInfo.get(path);
Expand All @@ -154,13 +155,18 @@ export class DataReference<T = any> {
get path() { return path; },
get key() { return key; },
get callbacks() { return callbacks; },
db: db,
vars: vars || {},
context: {},
pushed: false,
cursor: null,
};
}

get db() {
return this[_private].db;
}

/**
* Adds contextual info for database updates through this reference.
* This allows you to identify the event source (and/or reason) of
Expand Down Expand Up @@ -273,7 +279,7 @@ export class DataReference<T = any> {
if (info.parentPath === null) {
return null;
}
return new DataReference(this.db, info.parentPath).context(this[_private].context);
return new DataReference(this[_private].db, info.parentPath).context(this[_private].context);
}

/**
Expand All @@ -293,7 +299,7 @@ export class DataReference<T = any> {
childPath = typeof childPath === 'number' ? childPath : childPath.replace(/^\/|\/$/g, '');
const currentPath = PathInfo.fillVariables2(this.path, this.vars);
const targetPath = PathInfo.getChildPath(currentPath, childPath);
return new DataReference(this.db, targetPath).context(this[_private].context); // `${this.path}/${childPath}`
return new DataReference(this[_private].db, targetPath).context(this[_private].context); // `${this.path}/${childPath}`
}

/**
Expand All @@ -313,11 +319,11 @@ export class DataReference<T = any> {
if (typeof value === 'undefined') {
throw new TypeError(`Cannot store undefined value in "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
value = this.db.types.serialize(this.path, value);
const { cursor } = await this.db.api.set(this.path, value, { context: this[_private].context });
value = this[_private].db.types.serialize(this.path, value);
const { cursor } = await this[_private].db.api.set(this.path, value, { context: this[_private].context });
this.cursor = cursor;
if (typeof onComplete === 'function') {
try { onComplete(null, this);} catch(err) { console.error('Error in onComplete callback:', err); }
Expand Down Expand Up @@ -346,8 +352,8 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot update the value of wildcard path "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
if (typeof updates !== 'object' || updates instanceof Array || updates instanceof ArrayBuffer || updates instanceof Date) {
await this.set(updates as any);
Expand All @@ -356,8 +362,8 @@ export class DataReference<T = any> {
console.warn(`update called on path "/${this.path}", but there is nothing to update`);
}
else {
updates = this.db.types.serialize(this.path, updates);
const { cursor } = await this.db.api.update(this.path, updates, { context: this[_private].context });
updates = this[_private].db.types.serialize(this.path, updates);
const { cursor } = await this[_private].db.api.update(this.path, updates, { context: this[_private].context });
this.cursor = cursor;
}
if (typeof onComplete === 'function') {
Expand Down Expand Up @@ -386,12 +392,12 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot start a transaction on wildcard path "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
let throwError;
const cb = (currentValue: any) => {
currentValue = this.db.types.deserialize(this.path, currentValue);
currentValue = this[_private].db.types.deserialize(this.path, currentValue);
const snap = new DataSnapshot(this, currentValue);
let newValue;
try {
Expand All @@ -405,18 +411,18 @@ export class DataReference<T = any> {
if (newValue instanceof Promise) {
return newValue
.then((val) => {
return this.db.types.serialize(this.path, val);
return this[_private].db.types.serialize(this.path, val);
})
.catch(err => {
throwError = err; // Remember error
return; // cancel transaction by returning undefined
});
}
else {
return this.db.types.serialize(this.path, newValue);
return this[_private].db.types.serialize(this.path, newValue);
}
};
const { cursor } = await this.db.api.transaction(this.path, cb, { context: this[_private].context });
const { cursor } = await this[_private].db.api.transaction(this.path, cb, { context: this[_private].context });
this.cursor = cursor;
if (throwError) {
// Rethrow error from callback code
Expand Down Expand Up @@ -468,10 +474,10 @@ export class DataReference<T = any> {
ourCallback: (err, path, newValue, oldValue, eventContext) => {
if (err) {
// TODO: Investigate if this ever happens?
this.db.debug.error(`Error getting data for event ${event} on path "${path}"`, err);
this[_private].db.debug.error(`Error getting data for event ${event} on path "${path}"`, err);
return;
}
const ref = this.db.ref(path);
const ref = this[_private].db.ref(path);
ref[_private].vars = PathInfo.extractVariables(this.path, path);

let callbackObject;
Expand All @@ -481,8 +487,8 @@ export class DataReference<T = any> {
}
else {
const values = {
previous: this.db.types.deserialize(path, oldValue),
current: this.db.types.deserialize(path, newValue),
previous: this[_private].db.types.deserialize(path, oldValue),
current: this[_private].db.types.deserialize(path, newValue),
};
if (event === 'child_removed') {
callbackObject = new DataSnapshot(ref, values.previous, true, values.previous, eventContext);
Expand Down Expand Up @@ -527,17 +533,17 @@ export class DataReference<T = any> {
// Cancel subscription
const callbacks = this[_private].callbacks;
callbacks.splice(callbacks.indexOf(cb), 1);
this.db.api.unsubscribe(this.path, event, cb.ourCallback);
this[_private].db.api.unsubscribe(this.path, event, cb.ourCallback);

// Call cancelCallbacks
this.db.debug.error(`Subscription "${event}" on path "/${this.path}" canceled because of an error: ${err.message}`);
this[_private].db.debug.error(`Subscription "${event}" on path "/${this.path}" canceled because of an error: ${err.message}`);
eventPublisher.cancel(err.message);
};
const authorized = this.db.api.subscribe(this.path, event, cb.ourCallback, { newOnly: advancedOptions.newOnly, cancelCallback: cancelSubscription, syncFallback: advancedOptions.syncFallback });
const authorized = this[_private].db.api.subscribe(this.path, event, cb.ourCallback, { newOnly: advancedOptions.newOnly, cancelCallback: cancelSubscription, syncFallback: advancedOptions.syncFallback });
const allSubscriptionsStoppedCallback = () => {
const callbacks = this[_private].callbacks;
callbacks.splice(callbacks.indexOf(cb), 1);
return this.db.api.unsubscribe(this.path, event, cb.ourCallback);
return this[_private].db.api.unsubscribe(this.path, event, cb.ourCallback);
};
if (authorized instanceof Promise) {
// Web API now returns a promise that resolves if the request is allowed
Expand Down Expand Up @@ -578,7 +584,7 @@ export class DataReference<T = any> {
const step = 100, limit = step;
let skip = 0;
const more = async () => {
const children = await this.db.api.reflect(this.path, 'children', { limit, skip });
const children = await this[_private].db.api.reflect(this.path, 'children', { limit, skip });
children.list.forEach(child => {
const childRef = this.child(child.key);
eventPublisher.publish(childRef);
Expand All @@ -594,11 +600,11 @@ export class DataReference<T = any> {
}
};

if (this.db.isReady) {
if (this[_private].db.isReady) {
subscribe();
}
else {
this.db.ready(subscribe);
this[_private].db.ready(subscribe);
}

return eventStream;
Expand All @@ -616,7 +622,7 @@ export class DataReference<T = any> {
const subscriptions = this[_private].callbacks;
const stopSubs = subscriptions.filter(sub => (!event || sub.event === event) && (!callback || sub.userCallback === callback));
if (stopSubs.length === 0) {
this.db.debug.warn(`Can't find event subscriptions to stop (path: "${this.path}", event: ${event || '(any)'}, callback: ${callback})`);
this[_private].db.debug.warn(`Can't find event subscriptions to stop (path: "${this.path}", event: ${event || '(any)'}, callback: ${callback})`);
}
stopSubs.forEach(sub => {
sub.stream.stop();
Expand Down Expand Up @@ -650,8 +656,8 @@ export class DataReference<T = any> {
get<Value = T>(options:DataRetrievalOptions, callback: EventCallback<DataSnapshot<Value>>): void
get<Value = T>(optionsOrCallback?:DataRetrievalOptions|EventCallback<DataSnapshot<Value>>, callback?: EventCallback<DataSnapshot<Value>>): Promise<DataSnapshot<Value>>|void;
get<Value = T>(optionsOrCallback?:DataRetrievalOptions|EventCallback<DataSnapshot<Value>>, callback?: EventCallback<DataSnapshot<Value>>): Promise<DataSnapshot<Value>>|void {
if (!this.db.isReady) {
const promise = this.db.ready().then(() => this.get(optionsOrCallback, callback) as any);
if (!this[_private].db.isReady) {
const promise = this[_private].db.ready().then(() => this.get(optionsOrCallback, callback) as any);
return typeof optionsOrCallback !== 'function' && typeof callback !== 'function' ? promise : undefined; // only return promise if no callback is used
}

Expand All @@ -669,14 +675,14 @@ export class DataReference<T = any> {
}

const options = new DataRetrievalOptions(typeof optionsOrCallback === 'object' ? optionsOrCallback : { cache_mode: 'allow' });
const promise = this.db.api.get(this.path, options).then(result => {
const promise = this[_private].db.api.get(this.path, options).then(result => {
const isNewApiResult = ('context' in result && 'value' in result);
if (!isNewApiResult) {
// acebase-core version package was updated but acebase or acebase-client package was not? Warn, but don't throw an error.
console.warn('AceBase api.get method returned an old response value. Update your acebase or acebase-client package');
result = { value: result, context: {} };
}
const value = this.db.types.deserialize(this.path, result.value);
const value = this[_private].db.types.deserialize(this.path, result.value);
const snapshot = new DataSnapshot(this, value, undefined, undefined, result.context);
if (result.context?.acebase_cursor) {
this.cursor = result.context.acebase_cursor;
Expand Down Expand Up @@ -786,10 +792,10 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot check wildcard path "/${this.path}" existence`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
return this.db.api.exists(this.path);
return this[_private].db.api.exists(this.path);
}

get isWildcardPath() {
Expand Down Expand Up @@ -860,10 +866,10 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot reflect on wildcard path "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
return this.db.api.reflect(this.path, type, args);
return this[_private].db.api.reflect(this.path, type, args);
}

/**
Expand All @@ -881,11 +887,11 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot export wildcard path "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
const writeFn = typeof write === 'function' ? write : write.write.bind(write);
return this.db.api.export(this.path, writeFn, options);
return this[_private].db.api.export(this.path, writeFn, options);
}

/**
Expand All @@ -898,10 +904,10 @@ export class DataReference<T = any> {
if (this.isWildcardPath) {
throw new Error(`Cannot import to wildcard path "/${this.path}"`);
}
if (!this.db.isReady) {
await this.db.ready();
if (!this[_private].db.isReady) {
await this[_private].db.ready();
}
return this.db.api.import(this.path, read, options);
return this[_private].db.api.import(this.path, read, options);
}

/**
Expand Down Expand Up @@ -933,7 +939,7 @@ export class DataReference<T = any> {
proxy<T = any>(options?: LiveDataProxyOptions<T>) {
const isOptionsArg = typeof options === 'object' && (typeof options.cursor !== 'undefined' || typeof options.defaultValue !== 'undefined');
if (typeof options !== 'undefined' && !isOptionsArg) {
this.db.debug.warn('Warning: live data proxy is being initialized with a deprecated method signature. Use ref.proxy(options) instead of ref.proxy(defaultValue)');
this[_private].db.debug.warn('Warning: live data proxy is being initialized with a deprecated method signature. Use ref.proxy(options) instead of ref.proxy(defaultValue)');
options = { defaultValue: options as T };
}
return LiveDataProxy.create(this, options);
Expand Down Expand Up @@ -1114,7 +1120,7 @@ export class DataReference<T = any> {
async getMutations(cursorOrDate?: string|Date|null): Promise<{ used_cursor: string, new_cursor: string, mutations: ValueMutation[] }> {
const cursor = typeof cursorOrDate === 'string' ? cursorOrDate : undefined;
const timestamp = cursorOrDate === null || typeof cursorOrDate === 'undefined' ? 0 : cursorOrDate instanceof Date ? cursorOrDate.getTime() : undefined;
return this.db.api.getMutations({ path: this.path, cursor, timestamp });
return this[_private].db.api.getMutations({ path: this.path, cursor, timestamp });
}

/**
Expand All @@ -1130,7 +1136,7 @@ export class DataReference<T = any> {
async getChanges(cursorOrDate?: string|Date|null): Promise<{ used_cursor: string, new_cursor: string, changes: ValueChange[] }> {
const cursor = typeof cursorOrDate === 'string' ? cursorOrDate : undefined;
const timestamp = cursorOrDate === null || typeof cursorOrDate === 'undefined' ? 0 : cursorOrDate instanceof Date ? cursorOrDate.getTime() : undefined;
return this.db.api.getChanges({ path: this.path, cursor, timestamp });
return this[_private].db.api.getChanges({ path: this.path, cursor, timestamp });
}
}

Expand Down