From dcf5b5a6d40f256c4e16dadfdc9846de5be83390 Mon Sep 17 00:00:00 2001 From: Fahrzin Hemmati Date: Wed, 2 Aug 2023 13:12:19 -0700 Subject: [PATCH 1/2] Move `db` in DataReference into [_private] This makes DataReferences not include any non-private fields, which matches firebase's DataSnapshot class --- src/data-reference.ts | 97 ++++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/data-reference.ts b/src/data-reference.ts index 29eb210..f55da8b 100644 --- a/src/data-reference.ts +++ b/src/data-reference.ts @@ -144,7 +144,7 @@ export class DataReference { /** * 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); @@ -154,6 +154,7 @@ export class DataReference { get path() { return path; }, get key() { return key; }, get callbacks() { return callbacks; }, + db: db, vars: vars || {}, context: {}, pushed: false, @@ -273,7 +274,7 @@ export class DataReference { 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); } /** @@ -293,7 +294,7 @@ export class DataReference { 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}` } /** @@ -313,11 +314,11 @@ export class DataReference { 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); } @@ -346,8 +347,8 @@ export class DataReference { 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); @@ -356,8 +357,8 @@ export class DataReference { 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') { @@ -386,12 +387,12 @@ export class DataReference { 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 { @@ -405,7 +406,7 @@ export class DataReference { 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 @@ -413,10 +414,10 @@ export class DataReference { }); } 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 @@ -468,10 +469,10 @@ export class DataReference { 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; @@ -481,8 +482,8 @@ export class DataReference { } 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); @@ -527,17 +528,17 @@ export class DataReference { // 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 @@ -578,7 +579,7 @@ export class DataReference { 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); @@ -594,11 +595,11 @@ export class DataReference { } }; - if (this.db.isReady) { + if (this[_private].db.isReady) { subscribe(); } else { - this.db.ready(subscribe); + this[_private].db.ready(subscribe); } return eventStream; @@ -616,7 +617,7 @@ export class DataReference { 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(); @@ -650,8 +651,8 @@ export class DataReference { get(options:DataRetrievalOptions, callback: EventCallback>): void get(optionsOrCallback?:DataRetrievalOptions|EventCallback>, callback?: EventCallback>): Promise>|void; get(optionsOrCallback?:DataRetrievalOptions|EventCallback>, callback?: EventCallback>): Promise>|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 } @@ -669,14 +670,14 @@ export class DataReference { } 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; @@ -786,10 +787,10 @@ export class DataReference { 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() { @@ -860,10 +861,10 @@ export class DataReference { 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); } /** @@ -881,11 +882,11 @@ export class DataReference { 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); } /** @@ -898,10 +899,10 @@ export class DataReference { 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); } /** @@ -933,7 +934,7 @@ export class DataReference { proxy(options?: LiveDataProxyOptions) { 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); @@ -1114,7 +1115,7 @@ export class DataReference { 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 }); } /** @@ -1130,7 +1131,7 @@ export class DataReference { 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 }); } } From 15ead3e3cbc2d1d9e4f92a4d03200d7f8208f869 Mon Sep 17 00:00:00 2001 From: Fahrzin Hemmati Date: Wed, 2 Aug 2023 20:08:05 -0700 Subject: [PATCH 2/2] Fix typing --- src/data-reference.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/data-reference.ts b/src/data-reference.ts index f55da8b..7348398 100644 --- a/src/data-reference.ts +++ b/src/data-reference.ts @@ -135,6 +135,7 @@ export class DataReference { readonly path: string, readonly key: string|number, readonly callbacks: IEventSubscription[], + db: AceBaseBase, vars: PathVariables, context: any, pushed: boolean, // If DataReference was created by .push @@ -162,6 +163,10 @@ export class DataReference { }; } + 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