datomic-flare 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,319 @@
1
+ # Datomic Flare for Ruby
2
+
3
+ A Ruby gem for interacting with [Datomic](https://www.datomic.com) through [Datomic Flare](https://github.com/gbaptista/datomic-flare).
4
+
5
+ ![The image features a logo with curved lines forming a ruby, suggesting distortion and movement like space-time.](https://media.githubusercontent.com/media/gbaptista/assets/refs/heads/main/ruby-datomic-flare/ruby-datomic-flare-canvas.png)
6
+
7
+ _This is not an official Datomic project or documentation and it is not affiliated with Datomic in any way._
8
+
9
+ ## TL;DR and Quick Start
10
+
11
+ ```ruby
12
+ gem '{{ gem.name }}', '~> {{ gem.version }}'
13
+ ```
14
+
15
+ ```ruby
16
+ require '{{ gem.name }}'
17
+
18
+ client = Flare.new(credentials: { address: 'http://localhost:3042' })
19
+ ```
20
+
21
+ ```ruby:runnable
22
+ client.dsl.transact_schema!(
23
+ {
24
+ book: {
25
+ title: { type: :string, doc: 'The title of the book.' },
26
+ genre: { type: :string, doc: 'The genre of the book.' },
27
+ }
28
+ }
29
+ )
30
+
31
+ client.dsl.assert_into!(
32
+ :book,
33
+ { title: 'The Tell-Tale Heart',
34
+ genre: 'Horror' }
35
+ )
36
+
37
+ client.dsl.query(
38
+ datalog: <<~EDN
39
+ [:find ?e ?title ?genre
40
+ :where [?e :book/title ?title]
41
+ [?e :book/genre ?genre]]
42
+ EDN
43
+ )
44
+ ```
45
+
46
+ ```ruby:placeholder
47
+ ```
48
+
49
+ {{ index }}
50
+
51
+ ## Flare
52
+
53
+ ### Creating a Client
54
+
55
+ ```ruby
56
+ require '{{ gem.name }}'
57
+
58
+ client = Flare.new(credentials: { address: 'http://localhost:3042' })
59
+ ```
60
+
61
+ ### Meta
62
+
63
+ ```ruby:runnable
64
+ client.meta
65
+ ```
66
+
67
+ ```ruby:placeholder
68
+ ```
69
+
70
+ {{ dsl }}
71
+
72
+ {{ api }}
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ bundle
78
+ rubocop -A
79
+ ```
80
+
81
+ ### Publish to RubyGems
82
+
83
+ ```bash
84
+ gem build {{ gem.name }}.gemspec
85
+
86
+ gem signin
87
+
88
+ gem push {{ gem.name }}-{{ gem.version }}.gem
89
+ ```
90
+
91
+ ### Setup for Tests and Documentation
92
+
93
+ Tests run against real Datomic databases, and documentation (README) is generated by interacting with real Datomic databases.
94
+
95
+ To accomplish that, we need to have [Datomic](https://github.com/gbaptista/datomic-pro-docker) and [Flare](https://github.com/gbaptista/datomic-flare) running.
96
+
97
+ **TL;DR:**
98
+
99
+ ```bash
100
+ git clone https://github.com/gbaptista/datomic-pro-docker.git
101
+
102
+ cd datomic-pro-docker
103
+
104
+ cp compose/flare-dev.yml docker-compose.yml
105
+
106
+ docker compose up -d datomic-storage
107
+
108
+ docker compose run datomic-tools psql \
109
+ -f bin/sql/postgres-table.sql \
110
+ -h datomic-storage \
111
+ -U datomic-user \
112
+ -d my-datomic-storage
113
+
114
+ docker compose up -d datomic-transactor
115
+
116
+ docker compose run datomic-tools clojure -M -e "$(cat <<'CLOJURE'
117
+ (require '[datomic.api :as d])
118
+
119
+ (d/create-database "datomic:sql://my-datomic-database?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
120
+
121
+ (d/create-database "datomic:sql://my-datomic-database-test?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
122
+
123
+ (d/create-database "datomic:sql://my-datomic-database-test-green?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
124
+
125
+ (System/exit 0)
126
+ CLOJURE
127
+ )"
128
+
129
+ docker compose up -d datomic-peer-server
130
+
131
+ docker compose up -d datomic-flare-peer datomic-flare-client
132
+ ```
133
+
134
+ ```bash
135
+ curl -s http://localhost:3042/meta \
136
+ -X GET \
137
+ -H "Content-Type: application/json" \
138
+ | jq
139
+ ```
140
+
141
+ ```json
142
+ {
143
+ "data": {
144
+ "mode": "peer"
145
+ }
146
+ }
147
+ ```
148
+
149
+ ```bash
150
+ curl -s http://localhost:3043/meta \
151
+ -X GET \
152
+ -H "Content-Type: application/json" \
153
+ | jq
154
+ ```
155
+
156
+ ```json
157
+ {
158
+ "data": {
159
+ "mode": "client"
160
+ }
161
+ }
162
+ ```
163
+
164
+ You are ready to run tests and generate documentation.
165
+
166
+ **Detailed instructions:**
167
+
168
+ Clone the [datomic-pro-docker](https://github.com/gbaptista/datomic-pro-docker) repository and copy the Docker Compose template:
169
+
170
+ ```bash
171
+ git clone https://github.com/gbaptista/datomic-pro-docker.git
172
+
173
+ cd datomic-pro-docker
174
+
175
+ cp compose/flare-dev.yml docker-compose.yml
176
+ ```
177
+
178
+ Start PostgreSQL as Datomic's storage service:
179
+
180
+ ```bash
181
+ docker compose up -d datomic-storage
182
+
183
+ docker compose logs -f datomic-storage
184
+ ```
185
+
186
+ Create the table for Datomic databases:
187
+
188
+ ```bash
189
+ docker compose run datomic-tools psql \
190
+ -f bin/sql/postgres-table.sql \
191
+ -h datomic-storage \
192
+ -U datomic-user \
193
+ -d my-datomic-storage
194
+ ```
195
+
196
+ You will be prompted for a password, which is `unsafe`.
197
+
198
+ Start the Datomic Transactor:
199
+
200
+ ```bash
201
+ docker compose up -d datomic-transactor
202
+
203
+ docker compose logs -f datomic-transactor
204
+ ```
205
+
206
+ Create the following databases:
207
+
208
+ - `my-datomic-database`
209
+ - `my-datomic-database-test`
210
+ - `my-datomic-database-test-green`
211
+
212
+ ```bash
213
+ docker compose run datomic-tools clojure -M -e "$(cat <<'CLOJURE'
214
+ (require '[datomic.api :as d])
215
+
216
+ (d/create-database "datomic:sql://my-datomic-database?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
217
+
218
+ (d/create-database "datomic:sql://my-datomic-database-test?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
219
+
220
+ (d/create-database "datomic:sql://my-datomic-database-test-green?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
221
+
222
+ (System/exit 0)
223
+ CLOJURE
224
+ )"
225
+ ```
226
+
227
+ Start the Peer Server:
228
+
229
+ ```bash
230
+ docker compose up -d datomic-peer-server
231
+
232
+ docker compose logs -f datomic-peer-server
233
+ ```
234
+
235
+ Start 2 instances of Flare, one in Peer Mode and another in Client Mode:
236
+
237
+ ```bash
238
+ docker compose up -d datomic-flare-peer datomic-flare-client
239
+
240
+ docker compose logs -f datomic-flare-peer
241
+ docker compose logs -f datomic-flare-client
242
+ ```
243
+
244
+ You should be able to request both:
245
+
246
+ Datomic Flare in Peer Mode:
247
+ ```bash
248
+ curl -s http://localhost:3042/meta \
249
+ -X GET \
250
+ -H "Content-Type: application/json" \
251
+ | jq
252
+ ```
253
+
254
+ ```json
255
+ {
256
+ "data": {
257
+ "mode": "peer"
258
+ }
259
+ }
260
+ ```
261
+
262
+ Datomic Flare in Client Mode:
263
+ ```bash
264
+ curl -s http://localhost:3043/meta \
265
+ -X GET \
266
+ -H "Content-Type: application/json" \
267
+ | jq
268
+ ```
269
+
270
+ ```json
271
+ {
272
+ "data": {
273
+ "mode": "client"
274
+ }
275
+ }
276
+ ```
277
+
278
+ You are ready to run tests and generate documentation.
279
+
280
+ ### Running Tests
281
+
282
+ Tests run against real Datomic databases, so complete the [Setup for Tests and Documentation](#setup-for-tests-and-documentation) first.
283
+
284
+ ```bash
285
+ cp .env.example .env
286
+
287
+ bundle exec rspec
288
+ ```
289
+
290
+ ### Updating the README
291
+
292
+ Documentation (README) is generated by interacting with real Datomic databases, so complete the [Setup for Tests and Documentation](#setup-for-tests-and-documentation) first.
293
+
294
+ Update the `docs/templates/*.md` files, and then:
295
+
296
+ ```sh
297
+ cp .env.example .env
298
+
299
+ bundle exec ruby ports/cli.rb docs:generate
300
+ ```
301
+
302
+ Trick for automatically updating the `README.md` when `docs/templates/*.md` files change:
303
+
304
+ ```sh
305
+ sudo pacman -S inotify-tools # Arch / Manjaro
306
+ sudo apt-get install inotify-tools # Debian / Ubuntu / Raspberry Pi OS
307
+ sudo dnf install inotify-tools # Fedora / CentOS / RHEL
308
+
309
+ while inotifywait -e modify docs/templates/*; \
310
+ do bundle exec ruby ports/cli.rb docs:generate; \
311
+ done
312
+ ```
313
+
314
+ Trick for Markdown Live Preview:
315
+ ```sh
316
+ pip install -U markdown_live_preview
317
+
318
+ mlp README.md -p 8042 --no-follow
319
+ ```
@@ -0,0 +1,267 @@
1
+ ## Flare API
2
+
3
+ It provides methods that mirror [Datomic's APIs](https://docs.datomic.com/clojure/index.html). Most interactions use EDN, closely following [Datomic’s documentation](https://docs.datomic.com).
4
+
5
+ This approach should be familiar to those who know Datomic concepts and APIs.
6
+
7
+ Learn more about Clojure and Datomic:
8
+
9
+ - [Clojure Rationale](https://clojure.org/about/rationale)
10
+ - [Datomic Introduction](https://docs.datomic.com/datomic-overview.html)
11
+
12
+ ### Creating a Database
13
+
14
+ ```ruby:runnable
15
+ client.api.create_database!({ name: 'fireball' })['data']
16
+ ```
17
+
18
+ ```ruby:placeholder
19
+ ```
20
+
21
+ ### Deleting a Database
22
+
23
+ ```ruby:runnable
24
+ client.api.delete_database!({ name: 'fireball' })['data']
25
+ ```
26
+
27
+ ```ruby:placeholder
28
+ ```
29
+
30
+ ### Listing Databases
31
+
32
+ ```ruby
33
+ # Flare on Peer Mode
34
+ client.api.get_database_names['data']
35
+
36
+ # Flare on Client Mode
37
+ client.api.list_databases['data']
38
+ ```
39
+
40
+ ```ruby
41
+ ['my-datomic-database']
42
+ ```
43
+
44
+ ### Transacting Schema
45
+
46
+ ```ruby:runnable
47
+ client.api.transact!(
48
+ { data: <<~EDN
49
+ [{:db/ident :book/title
50
+ :db/valueType :db.type/string
51
+ :db/cardinality :db.cardinality/one
52
+ :db/doc "The title of the book."}
53
+
54
+ {:db/ident :book/genre
55
+ :db/valueType :db.type/string
56
+ :db/cardinality :db.cardinality/one
57
+ :db/doc "The genre of the book."}
58
+
59
+ {:db/ident :book/published_at_year
60
+ :db/valueType :db.type/long
61
+ :db/cardinality :db.cardinality/one
62
+ :db/doc "The year the book was first published."}]
63
+ EDN
64
+ }
65
+ )['data']
66
+ ```
67
+
68
+ ```ruby:placeholder
69
+ ```
70
+
71
+ ### Checking Schema
72
+
73
+ ```ruby:runnable
74
+ client.api.q(
75
+ { inputs: [{ database: { latest: true } }],
76
+ query: <<~EDN
77
+ [:find
78
+ ?e ?ident ?value_type ?cardinality ?doc
79
+ ?unique ?index ?no_history
80
+ :in $
81
+ :where
82
+ [?e :db/ident ?ident]
83
+
84
+ [?e :db/valueType ?value_type_id]
85
+ [?value_type_id :db/ident ?value_type]
86
+
87
+ [?e :db/cardinality ?cardinality_id]
88
+ [?cardinality_id :db/ident ?cardinality]
89
+
90
+ [(get-else $ ?e :db/doc "") ?doc]
91
+
92
+ [(get-else $ ?e :db/unique -1) ?unique_id]
93
+ [(get-else $ ?unique_id :db/ident false) ?unique]
94
+
95
+ [(get-else $ ?e :db/index false) ?index]
96
+ [(get-else $ ?e :db/noHistory false) ?no_history]]
97
+ EDN
98
+ }
99
+ )['data'].filter do |datom|
100
+ !%w[
101
+ db
102
+ db.alter db.attr db.bootstrap db.cardinality db.entity db.excise
103
+ db.fn db.install db.lang db.part db.sys db.type db.unique
104
+ fressian
105
+ ].include?(datom[1].split('/').first)
106
+ end
107
+ ```
108
+
109
+ ```ruby:placeholder
110
+ ```
111
+
112
+ ### Asserting Facts
113
+
114
+ ```ruby:runnable
115
+ client.api.transact!(
116
+ { data: <<~EDN
117
+ [{:db/id -1
118
+ :book/title "Pride and Prejudice"
119
+ :book/genre "Romance"
120
+ :book/published_at_year 1813}]
121
+ EDN
122
+ }
123
+ )['data']
124
+ ```
125
+
126
+ ```ruby:placeholder
127
+ ```
128
+
129
+ ```ruby:runnable
130
+ client.api.transact!(
131
+ { data: <<~EDN
132
+ [{:db/id -1
133
+ :book/title "Near to the Wild Heart"
134
+ :book/genre "Novel"
135
+ :book/published_at_year 1943}
136
+ {:db/id -2
137
+ :book/title "A Study in Scarlet"
138
+ :book/genre "Detective"
139
+ :book/published_at_year 1887}
140
+ {:db/id -3
141
+ :book/title "The Tell-Tale Heart"
142
+ :book/genre "Horror"
143
+ :book/published_at_year 1843}]
144
+ EDN
145
+ }
146
+ )['data']
147
+ ```
148
+
149
+ ```ruby:state
150
+ state[:wild_heart_entity_id] = result['tempids']['-1']
151
+ state[:scarlet_entity_id] = result['tempids']['-2']
152
+ ```
153
+
154
+ ```ruby:placeholder
155
+ ```
156
+
157
+ ### Reading Data by Entity
158
+
159
+ ```ruby:runnable/render
160
+ client.api.entity(
161
+ { database: { latest: true },
162
+ id: {{ state.wild_heart_entity_id }} }
163
+ )['data']
164
+ ```
165
+
166
+ ```ruby:placeholder
167
+ ```
168
+
169
+ ### Reading Data by Querying
170
+
171
+ ```ruby:runnable
172
+ client.api.q(
173
+ { inputs: [{ database: { latest: true } }],
174
+ query: <<~EDN
175
+ [:find ?e ?title ?genre ?year
176
+ :where [?e :book/title ?title]
177
+ [?e :book/genre ?genre]
178
+ [?e :book/published_at_year ?year]]
179
+ EDN
180
+ }
181
+ )['data']
182
+ ```
183
+
184
+ ```ruby:placeholder
185
+ ```
186
+
187
+ ```ruby:runnable
188
+ client.api.q(
189
+ { inputs: [
190
+ { database: { latest: true } },
191
+ 'The Tell-Tale Heart'
192
+ ],
193
+ query: <<~EDN
194
+ [:find ?e ?title ?genre ?year
195
+ :in $ ?title
196
+ :where [?e :book/title ?title]
197
+ [?e :book/genre ?genre]
198
+ [?e :book/published_at_year ?year]]
199
+ EDN
200
+ }
201
+ )['data']
202
+ ```
203
+
204
+ ```ruby:state
205
+ state[:tale_heart_entity_id] = result[0][0]
206
+ ```
207
+
208
+ ```ruby:placeholder
209
+ ```
210
+
211
+ ### Accumulating Facts
212
+
213
+ ```ruby:runnable/render
214
+ client.api.transact!(
215
+ { data: <<~EDN
216
+ [{:db/id {{ state.tale_heart_entity_id }} :book/genre "Gothic"}]
217
+ EDN
218
+ }
219
+ )['data']
220
+ ```
221
+
222
+ ```ruby:placeholder
223
+ ```
224
+
225
+ ### Retracting Facts
226
+
227
+ Retract the value of an attribute:
228
+
229
+ ```ruby:runnable/render
230
+ client.api.transact!(
231
+ { data: <<~EDN
232
+ [[:db/retract {{ state.tale_heart_entity_id }} :book/genre "Gothic"]]
233
+ EDN
234
+ }
235
+ )['data']
236
+ ```
237
+
238
+ ```ruby:placeholder
239
+ ```
240
+
241
+ Retract an attribute:
242
+
243
+ ```ruby:runnable/render
244
+ client.api.transact!(
245
+ { data: <<~EDN
246
+ [[:db/retract {{ state.wild_heart_entity_id }} :book/genre]]
247
+ EDN
248
+ }
249
+ )['data']
250
+ ```
251
+
252
+ ```ruby:placeholder
253
+ ```
254
+
255
+ Retract an entity:
256
+
257
+ ```ruby:runnable/render
258
+ client.api.transact!(
259
+ { data: <<~EDN
260
+ [[:db/retractEntity {{ state.scarlet_entity_id }}]]
261
+ EDN
262
+ }
263
+ )['data']
264
+ ```
265
+
266
+ ```ruby:placeholder
267
+ ```