datomic-flare 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ ```