Kirlovon/aloedb

Data "corrupts" on insert

Opened this issue · 2 comments

Hii there,

I'm trying to use AloeDB to keep track of user mutes.
However, I'm trying to insert data that, on runtime is an instance of a custom Time class (that allows for easier manipulation of datetime data).

This is what creating the object looks like:

interface IMutedUser {
  id: string;
  username: string;
  start: any;
  expiry: any;
  reason: string;
}

const mute: IMutedUser = {
  id: userId.value,
  username: user.username,
  start: new Time(),
  expiry: new Time().addHours(1),
  reason: (reasonObj) ? reasonObj.value : 'No reason specified.'
}

And this is how it looks on runtime:

{
  id: "1234567890",
  username: "FinlayDaG33k",
  start: Time { time: 2021-06-03T07:42:45.463Z },
  expiry: Time { time: 2021-06-03T08:42:45.463Z },
  reason: "No reason specified."
}

Now I insert the data into my database like this:

await AppData.mutedUsers.insertOne(mute);

And suddenly, the object has changed to this:

{
  id: "1234567890",
  username: "FinlayDaG33k",
  start: Time {},
  expiry: Time {},
  reason: "No reason specified."
}

This causes issues later in the code where the mute object's Time instance will be used to format a message.

Additionally, in the output JSON, it shows the following:

[
	{
		"id": "1234567890",
		"username": "FinlayDaG33k",
		"start": {},
		"expiry": {},
		"reason": "No reason specified."
	}
]

So the dates are just not saved at all.

Is there a way to get around this?
I mainly work in CakePHP and there we have a beforeSave method that allows us to specify behaviour that needs to be done before saving the data as well as an afterFind that allows us to specify behaviour that needs to be done when obtaining an entity from the database.

In AloeDB, this could look something like this:

// Open the database
const db = new Database<IMutedUser>(`muted-users.json`);

// Do some things before saving an entity
db.beforeSave((entity: IMutedUser) => {
  entity.start = entity.start.getTime; // Get the "vanilla" DateTime object
  entity.expiry = entity.expiry.getTime; // Get the "vanilla" DateTime object
  return entity;
});

// Do some things after loading an entity
db.afterLoad((entity: IMutedUser) => {
  entity.start = new Time(entity.start); // Instantiate our custom Time class with DateTime object
  entity.expiry = new Time(entity.expiry); // Instantiate our custom Time class with DateTime object
  return entity;
});

Hi! Only primitive types, arrays and objects are supported now. So all fields with complex types such as Dates, Maps, Sets, etc. are automatically removed.

The beforeSave and afterLoad example you gave looks pretty cool, so I'll try to add it in the next update. Also, I'm thinking of adding Dates support, but not sure how to do that yet.

I think, as a temporary option, you could do something like that:

const mute: IMutedUser = {
  id: userId.value,
  username: user.username,
  start: new Time(),
  expiry: new Time().addHours(1),
  reason: (reasonObj) ? reasonObj.value : 'No reason specified.'
}

mute.start = mute.start.time.toJSON();
mute.expiry = mute.expiry.time.toJSON();

await AppData.mutedUsers.insertOne(mute);

Hii there,

Yea, I solved it in a similar fashion for now (because I need the mute object as-is for later`).

const mute: IMutedUser = {
      id: userId.value,
      username: user.username,
      start: new Time().getTime,
      expiry: new Time().addHours(1).getTime,
      reason: (reasonObj) ? reasonObj.value : 'No reason specified.'
}

// ... Some more stuff

await AppData.mutedUsers.insertOne({
        id: mute.id,
        username: mute.username,
        start: mute.start.toString(),
        expiry: mute.expiry.toString(),
        reason: mute.reason
});

// ... Some more stuff that needs the start and expiry

Adding a beforeSave and afterLoad will also add some additional benefits (like encrypting and decrypting data, automatically hashing user passwords while saving) etc.

Additionally, it may be wise to add some information to the beforeSave on whether its an insert or an update.
Below is just a little mock-up so you should be able to understand what I mean:

class DbSaveEvent {
  private isNew: boolean;
  public get isNew() { return this.isNew; }  

  construct() {
    this.isNew = true;
  }
}

db.beforeSave((entity: IUser, event: DbSaveEvent) => {
  if(event.isNew) {
    entity.password = securePasswordHashingThingyMahJingles(entity.password);
  }
});;