Code Monkey home page Code Monkey logo

acebase-core's People

Contributors

akvadrako avatar appy-one avatar dependabot[bot] avatar futurgh avatar mitsichury avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

acebase-core's Issues

Live data proxy's value not updating when it's being created

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

Proxyfied array .indexOf issue

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.

ref.observe() issues

While testing code changes I discovered there are some issues with ref.observe():

  • it returns a Promise instead of an Observable, because it is async, which it shouldn't be
  • doesn't work if the initial value of the target path doesn't exist.

Setting proxy property value to null

Crashes 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;

Proxy array.forEach iterates over unproxied values

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!! ๐Ÿ˜ฑ
   }
});

Stopping a live data proxies' onChange subscription cancels others

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

Vendored `cuid` module causes issues w/Vite bundling of client code

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.

Allow better types for DataReference and DataSnapshot

DataSnapshot#val() always returns any, despite the fact that, given the existence of TypeMappings binding, 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.

Option 1 (Generic-as-cast)

Workbench Example

const snapshot = await db.ref<MyClass>("foo/test").get();
const value = snapshot.val();
//    ^? - MyClass
  • Simplest internal implementation, no runtime impact on JS
  • Equivalent to snapshot.val() as MyClass
  • Potentially controversial because it can hide the fact that there's no type checking going on, especially for developers new to AceBase and/or the codebase

Option 2 (User declaration merging)

Workbench Example

declare module "acebase" {
    interface BindingMap {
            "foo/*": MyClass;
    }
}
const snapshot = await db.ref("foo/test").get();
const value = snapshot.val();
//    ^? - MyClass
  • Moderate option; allows users to declare mappings on the type level with the same syntax used for TypeMappings#bind
  • Can default to any for users not using type mappings
  • May be unintuitive depending on user's knowledge of TypeScript

Option 3 (bind as assertion function)

Workbench Example

db.bindType("foo/*", MyClass);
const snapshot = await db.ref("foo/test").get();
const value = snapshot.val();
//    ^? - MyClass
  • "Just works" if user binds types with TypeMappings
  • Unfortunately would require 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 class
  • Would be a breaking change for all users

Copying objects within DataProxy

When 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.

Nice to have: check if changes were made in proxy transaction

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.