small-tech / jsdb

A transparent, in-memory, streaming write-on-update JavaScript database for Small Web applications that persists to a JavaScript transaction log.
Other
237 stars 8 forks source link

Feature request: update row #5

Open DJSundog opened 3 years ago

DJSundog commented 3 years ago

Use case:

If we assume a JSDB table initialized as

db.cars = [
  { make: "Mazda", model: "Miato", color: "red", year: 1992 },
  { make: "GMC", model:" Timmy", color: "black", year: 1987 },
  { make: "Ford", model: "F5", color": blue", year: 1987 }
]

I would like to be able to update data in the table, either by querying for row index/indices, like

let editRowIndex = db.cars.where('model').is('Miato').getFirstIndex()
// editRowIndex now equals 0

let manyRowIndices = db.cars.where('year').is(1987).getIndices()
// manyRowIndices now equals [1, 2]

or by having access to an update method, like

let result = db.cars.where('model').is('F5').getFirst().update({ model: 'F150' });
// result is true if data was successfully persisted after merging updated fields into db.cars[2]

let multiresult = db.cars.where('year').isLessThan(2000).get().update({ needsSmogTest: true });
// multiresult is true if all matching rows were successfully persisted

As an alternative to the second version, I'd be fine with having the update(newData) method hanging off the query pre-get (so, db.cars.where('model').is('F5').updateFirst(newData) and the like.

Thoughts?

aral commented 3 years ago

Hey @DJSundog, you already can! The returned results are proxies and are thus reactive :)

Try:

db.cars.where('model').is('F5').getFirst().model = 'F150'

db.cars.where('year').isLessThan(2000).get().forEach(car => car.needsSmogTest = true)

For the first one, though, I’d write it more defensively as:

const theFirstF5 = db.cars.where('model').is('F5').getFirst()
if (theFirstF5) theFirstF5.model = 'F150'

If we wanted to avoid having to write that out in two lines (i.e., support chaining when there is no result, ala optional chaining) we could modify the behaviour of getFirst(), getLast(), etc., so that they return an empty object when there is no result instead of undefined as they do now*.

So, instead of throwing, the assignment would simply set the model property on a simple empty object, thereby effectively ignoring it.

This would also be consistent with how the results of get() are handled (where an empty array is returned when there is no result).

Thoughts? :)


(db.cars.where('model').is('F5').getFirst() ?? {}).model = 'F150'
aral commented 3 years ago

Also, this makes me realise that we need a section along the lines of “Updating data” in the readme.

I’ll wait for your thoughts and then possibly open two internal issues regarding this:

DJSundog commented 3 years ago

Hey, thanks - I just flipped some of my code around and can confirm that it works as you describe (not that I am surprised or anything). That definitely makes things easier!

I think returning undefined on getFirst() et al is semantically nicer than returning an empty object, so that a check for undefined acts as "no such record", but I suppose checking for the truthiness of any property of the returned object would be semantically the same for most use cases.

Adding more docs about the results being reactive would be welcome - I was mistakenly assuming I'd need to write modified data back to the table manually, so this is much nicer - thanks!

aral commented 3 years ago

Tracking internally at: