datascript

zimablue 2024-07-09T17:29:47.752209Z

the docs imply that it's somewhat easy to add history-like functionality on a client-side or per-specific-big-of-schema basis, is there any example of someone doing that? what would the steps be like? something like manually specifying some transaction filter on write and auto-increment-not-bosh on read?

seepel 2024-07-09T18:37:22.028389Z

Here is what I do. I use d/listen to observe which datoms were added/removed in each tx and save them into postgres. For time travel I select all the datoms in a tx range. To travel back to a point in time I would select all datoms below a target tx.

select * from datom where tx <= ?

Niki 2024-07-09T18:37:49.037119Z

I always imagined listening to conn and putting past db references into a list

Niki 2024-07-09T18:40:25.061209Z

@sean888 that would work too, but you’ll have to recreate an entire db from datoma each time you want to time travel

seepel 2024-07-09T18:48:14.163779Z

Yup, that is exactly what we do. Probably not feasible for all use cases.

seepel 2024-07-09T18:50:59.772219Z

> datoma @tonsky Is this a typo, or is the plural of datom datoma?

zimablue 2024-07-09T18:56:53.722029Z

are the txes already stored by default?

zimablue 2024-07-09T18:57:21.338439Z

seepel are you saying that you like recreate a database by pushing the tx selection to the bottom (?) of the queries

zimablue 2024-07-09T18:57:33.502979Z

I've seem "datoms" a lot but I like "datoma"

Niki 2024-07-09T18:59:44.411449Z

datoms :) I’m typing on a phone

zimablue 2024-07-09T19:02:36.852119Z

so the trade-off to serializing a tx log seperately to the DB is like, all writes are duplicated, and any tx-sensitive queries are not benefitting from the 3-index system and queries on them need to rebuild/not use those indexes (?)

zimablue 2024-07-09T19:03:45.208329Z

and the tradeoff to the niki version is that you'd need to roll your own syntax to do cross-tx queries? but actually that's true for both isn't it, but the Niki version is (# txes) more storage, since no deduplication, full clone each transact

zimablue 2024-07-09T19:04:12.372879Z

but the Niki version's faster for queries since no rebuilding (?)

seepel 2024-07-09T19:08:42.222199Z

My table looks like this

CREATE TABLE IF NOT EXISTS datom (
  id SERIAL     PRIMARY KEY,
  entity        INTEGER,
  attribute     TEXT,
  edn_value     TEXT,
  ref_value     INTEGER,
  tx            INTEGER,
  valid_until   INTEGER,
  owner_guid    UUID
);
This is essentially my listener that takes the tx-data out of the tx-report and saves them into the db. I do some extra work so that I can have that valid_until that makes it easier to query.
(defn save-tx [owner-guid {datoms :tx-data}]
  (when-not (empty? datoms)
    (let [true-datoms (filter #(:added %) datoms)
          false-datoms (filter #(not (:added %)) datoms)
          inserted-datoms (when (not-empty true-datoms)
                            (insert-datoms! owner-guid true-datoms))]
      (doseq [datom false-datoms]
        (execute! (invalidate-datom owner-guid datom))))
      inserted-datoms)))
And I recreate dbs like this
(defn select-datoms [owner-guid & {:keys [:until]}]
  (->> {:select [:entity :attribute :edn_value :ref_value :tx :valid_until
                 :owner_guid]
        :from [:datom]
        :where [:and [:= :owner_guid owner-guid]
                (if until 
                  [:<= :valid_until until]
                  [:= valid_until nil])]

        :order-by [[:tx :asc]]}
       sql-format/format
       execute!
      (map (fn [{entity :datom/entity
                          attribute :datom/attribute
                          edn-value :datom/edn_value
                          tx :datom/tx}]
                      (let [attr (edn/read-string attribute)
                            val (edn/read-string edn-value)]
                        (d/datom entity attr val tx))))))

(d/init-db (select-datoms owner-guid :until target-tx)

seepel 2024-07-09T19:10:11.741689Z

To Niki’s point, this starts to degrade once dbs get big. I’m actually in the process of moving this whole setup to use the storage protocol https://github.com/tonsky/datascript/blob/master/docs/storage.md

seepel 2024-07-09T19:12:00.810669Z

We essentially load up a user’s datoms into a db in memory and keep it around until their session “expires”. (Or at least best effort)

zimablue 2024-07-09T19:12:54.612539Z

I don't want to touch that because I'm in clojurescript and would have to work out how to port the whole protocol/calls to it to be async

zimablue 2024-07-09T19:13:21.926769Z

or let datascript call an async thing thinking it's sync which sounds like a recipe for trouble

zimablue 2024-07-09T19:14:30.713429Z

for valid_until to work, you can't ever reuse eids right? dunno whether datascript disallows that, like add eid=1, remove eia=1, add eid=1

seepel 2024-07-09T19:14:33.092899Z

Is this on the client or on the server with node? If the db is on the client then I don’t think the storage protocol would help much anyway. You could do something really similar to what I’m doing with indexed-db though.

zimablue 2024-07-09T19:15:21.471099Z

definitely want to serialize on the server, not sure what I do on the client

zimablue 2024-07-09T19:16:34.763759Z

thanks a lot for sharing what you do, it's very useful I will try to do something similar, maybe with a kv store instead of SQL

seepel 2024-07-09T19:16:59.252029Z

There are certainly some rough edges, but it’s been working pretty well so far.