appy-one / acebase-core Goto Github PK
View Code? Open in Web Editor NEWCore functionality for AceBase realtime database repositories
License: MIT License
Core functionality for AceBase realtime database repositories
License: MIT License
If the value of a proxy target did not exist in the database when instantiated (and no default value was given), it is not updated once the database node is created afterwards. This only applies to a created proxy's root value.
To replicate:
// Create proxy without default value
const proxy = await db.ref('chats/chat1').proxy();
// proxy.value is now a Proxy whose .valueOf() === null
console.assert(proxy.value.valueOf() === null);
// Create the target
await db.ref('chats/chat1').set({ title: 'New chat' });
// proxy.value now SHOULD be a Proxy whose .valueOf() is the object { title: 'New chat' }
// However, it is still null
console.assert(proxy.value.valueOf() !== null); // Fails assertion
The indexOf
function of a proxified array value does not return expected result.
Example:
const proxy = await db.ref('chats/chat1').proxy();
const liveChat = proxy.value;
const member1 = liveChat.members[0]; // Assuming members is an array of objects
liveChat.members.indexOf(member1); // -1, should be 0
The reason this fails, is because member1
is the proxified version of the object, which is not found in the internal array of liveChat.members
.
Current workaround is to use getTarget()
on member1
:
liveChat.members.indexOf(member1.getTarget()); // === 0
Suggested fix: override indexOf
in data-proxy.ts and check if provided value is a Proxy, if so, call getTarget()
on it and pass it on to native indexOf
.
Using .valueOf()
on a proxied object that does not exists/has no value throws this error.
Example:
const proxy = db.ref('some/nonexisting/object').proxy();
const obj = proxy.value;
console.log(obj.valueOf()) // <-- throws error, should return null
The serialization of BigInt
s to binary and vice/versa does not work correctly
The DataProxy's reduce
method on arrays does not use the passed initialValue argument
While testing code changes I discovered there are some issues with ref.observe()
:
Promise
instead of an Observable
, because it is async
, which it shouldn't beCrashes when setting a property value of a proxy object to null
to delete it because the code attempts to access property [isProxy] on the null value.
To reproduce:
const defaultVal = { title: 'My chat', description: 'Some description' };
const proxy = await db.ref('chats/chat1').proxy(defaultVal);
const chat = proxy.value;
chat.description = null; // <-- Error
Current workaround:
delete chat.description;
When using a live data proxy, forEach on an array value will iterate over the underlying (unproxied) values, which is unexpected behaviour.
Example:
const defaultChatValue = {
title: 'Members is an Array with objects',
description: 'Which is not recommended, but should work anyway',
members: [{ name: 'Ewout', lastOnline: new Date() }]
};
const proxy = await db.ref('chats/chat1').proxy(defaultChatValue);
const chat = proxy.value;
chat.members.forEach(member => {
// member is unproxied by forEach on the members array
if (member.name === 'Ewout') {
member.lastOnline = new Date(); // Won't trigger db update!! ๐ฑ
}
});
See acebase issue #28. Issue is caused by execution of callback in addition to publishing the event (which already runs the callback)
When adding multiple onChange
handlers to a live data proxy, stopping 1 actually stops them all
Example:
const proxy = await db.ref('books/book1').proxy();
const book = proxy.value;
const subscription1 = book.onChanged((val, prev) => {
// I must keep getting these notifications
});
const subscription2 = book.onChanged((val, prev) => {
// I'm just here shortly
});
book.title = 'New title';
// subscription1 and subscription2 are both triggered
subscription2.stop(); // <-- this causes subscription1 to be stopped too
book.title = 'Original title';
// no subscriptions are triggered!
The problem is caused in data-proxy.ts:466 - the .off('mutations')
removes all handlers and should have never been in there
I've been trying out AceBase with SvelteKit and really enjoying the combination except for the fact that I can't seem to remove an implicit dependency on the NodeJS process
module being included in bundled builds.
This repo has a simple case that shows what's happening: https://github.com/rcoder/dbc
tl;dr: development mode works fine, but when I make a production build of the client, the cuid fingerprint
function gets dragged in, which in turn uses process.pid
.
The exact error in the browser console is:
Uncaught (in promise) ReferenceError: process is not defined
<anonymous> http://localhost:3000/_app/chunks/stores-a9318368.js:1
I'm by no means an expert on Vite, or modern bundling in general, but it seems to me that the shimmed browser-specific code in this module isn't being pulled in correctly.
Are there examples or recommended settings for using AceBase as a client in an SSR framwork like SvelteKit? I've certainly been bitten by other modules in this way, but my usual tricks to force ESM loading, change the bundling rules, etc. haven't seemed to help.
I'm also open to the argument that this is a SvelteKit problem, not an AceBase issue. The former is explicitly in beta, but I suspect the fundamental issue is related to Vite, not Svelte, which means it seems likely to pop up in other frameworks and tools that use Vite...which is a lot of them.
P.S.: As an aside, I'm curious what cuid
brings to the table that, say, the more general ulid
module (which works quite nicely across bundlers + packaging models in my experience) doesn't provide. The precise timing measurements between client and server seem nice in theory, but many of the systems I work with have no reliable clock at all and I'm a bit worried that skew between client nodes could cause issues down the road.
DataSnapshot#val()
always returns any
, despite the fact that, given the existence of TypeMappings
bind
ing, a user will often know at compile time what type will be returned. I have a few suggestions for type-level changes with no runtime impact, apart from the 3rd option, listed roughly in order from least change to acebase-core to greatest internal change. I'd be happy to submit a PR for any of them, just wanted to get feedback first.
const snapshot = await db.ref<MyClass>("foo/test").get();
const value = snapshot.val();
// ^? - MyClass
snapshot.val() as MyClass
declare module "acebase" {
interface BindingMap {
"foo/*": MyClass;
}
}
const snapshot = await db.ref("foo/test").get();
const value = snapshot.val();
// ^? - MyClass
TypeMappings#bind
any
for users not using type mappingsbind
as assertion function)db.bindType("foo/*", MyClass);
const snapshot = await db.ref("foo/test").get();
const value = snapshot.val();
// ^? - MyClass
TypeMappings
bind
to be implemented as a top level method of AceBase
, as current TypeScript limitations prevent the functionality used from working if it's nested in a member classWhen assigning a proxied object to another location within the DataProxy, making changes to it will also change the original in memory. It updates the db at the right location though, so there is a discrepancy between in-memory and db states.
Example:
const proxy = await db.ref('cars').proxy();
const cars = proxy.value;
// Copy sportscar to create a new racecar:
cars.racecar = products.sportscar;
// Make change to racecar:
cars.racecar.horsepower = 980;
if (cars.sportscar.horsepower === cars.racecar.horsepower) {
// Not good: change was also made in original object
}
Note that this is normal javascript object behavior but should be prevented in this proxied database context.
The solution would be to clone the object at assignment.
When binding a proxied value to Angular inputs with ngModel but don't want the user inputs to be saved to the db immediately, one could use a proxy transaction that can be committed or rolled back based on the user's decision to save or cancel their edits. It is currently not possible however to query the transaction if any changes were made - which would enable asking the user if they want to save their changes.
Imagine doing this in ngOnInit:
const proxy = await db.ref('contacts/contact1').proxy({});
this.contact = proxy.value;
this.tx = contact.startTransaction();
Then imagine Angular doing this while the user edits the form:
this.contact.name = 'Ewout';
this.contact.country = 'NL';
Now, the user exits the form without saving:
// Should we...
this.tx.rollback();
// Or...
this.tx.commit();
Nice to have:
let save = false;
if (this.tx.hasMutations) { // <-- this
save = await askUserIfTheyWantToSave();
}
if (save) {
await this.tx.commit();
}
else {
await this.tx.rollback();
}
I am aware it is possible to use an Angular FormGroup
instead and checking form.dirty
. However, when not using Angular, this is still nice to have for situations where one can't be sure if changes to the data were made or not.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.