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.
data/README.md ADDED
@@ -0,0 +1,1042 @@
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 'datomic-flare', '~> 1.0.0'
13
+ ```
14
+
15
+ ```ruby
16
+ require 'datomic-flare'
17
+
18
+ client = Flare.new(credentials: { address: 'http://localhost:3042' })
19
+ ```
20
+
21
+ ```ruby
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
+ {
34
+ title: 'The Tell-Tale Heart',
35
+ genre: 'Horror'
36
+ }
37
+ )
38
+
39
+ client.dsl.query(
40
+ datalog: <<~EDN
41
+ [:find ?e ?title ?genre
42
+ :where [?e :book/title ?title]
43
+ [?e :book/genre ?genre]]
44
+ EDN
45
+ )
46
+ ```
47
+
48
+ ```ruby
49
+ [[4611681620380877802, 'The Tell-Tale Heart', 'Horror']]
50
+ ```
51
+
52
+ - [TL;DR and Quick Start](#tldr-and-quick-start)
53
+ - [Flare](#flare)
54
+ - [Creating a Client](#creating-a-client)
55
+ - [Meta](#meta)
56
+ - [Flare DSL](#flare-dsl)
57
+ - [Creating a Database](#creating-a-database)
58
+ - [Deleting a Database](#deleting-a-database)
59
+ - [Listing Databases](#listing-databases)
60
+ - [Transacting Schema](#transacting-schema)
61
+ - [Checking Schema](#checking-schema)
62
+ - [Asserting Facts](#asserting-facts)
63
+ - [Reading Data by Entity](#reading-data-by-entity)
64
+ - [Reading Data by Querying](#reading-data-by-querying)
65
+ - [Accumulating Facts](#accumulating-facts)
66
+ - [Retracting Facts](#retracting-facts)
67
+ - [Flare API](#flare-api)
68
+ - [Creating a Database](#creating-a-database-1)
69
+ - [Deleting a Database](#deleting-a-database-1)
70
+ - [Listing Databases](#listing-databases-1)
71
+ - [Transacting Schema](#transacting-schema-1)
72
+ - [Checking Schema](#checking-schema-1)
73
+ - [Asserting Facts](#asserting-facts-1)
74
+ - [Reading Data by Entity](#reading-data-by-entity-1)
75
+ - [Reading Data by Querying](#reading-data-by-querying-1)
76
+ - [Accumulating Facts](#accumulating-facts-1)
77
+ - [Retracting Facts](#retracting-facts-1)
78
+ - [Development](#development)
79
+ - [Publish to RubyGems](#publish-to-rubygems)
80
+ - [Setup for Tests and Documentation](#setup-for-tests-and-documentation)
81
+ - [Running Tests](#running-tests)
82
+ - [Updating the README](#updating-the-readme)
83
+
84
+ ## Flare
85
+
86
+ ### Creating a Client
87
+
88
+ ```ruby
89
+ require 'datomic-flare'
90
+
91
+ client = Flare.new(credentials: { address: 'http://localhost:3042' })
92
+ ```
93
+
94
+ ### Meta
95
+
96
+ ```ruby
97
+ client.meta
98
+ ```
99
+
100
+ ```ruby
101
+ {
102
+ 'meta' =>
103
+ {
104
+ 'at' => '2024-09-29T13:52:52.252882446Z',
105
+ 'mode' => 'peer',
106
+ 'took' => { 'milliseconds' => 0.377446 }
107
+ },
108
+ 'data' =>
109
+ {
110
+ 'mode' => 'peer',
111
+ 'datomic-flare' => '1.0.0',
112
+ 'org.clojure/clojure' => '1.12.0',
113
+ 'com.datomic/peer' => '1.0.7187',
114
+ 'com.datomic/client-pro' => '1.0.81'
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Flare DSL
120
+
121
+ It provides a Ruby-familiar approach to working with Datomic. It brings Ruby’s conventions and idioms while preserving Datomic’s data-first principles and terminology.
122
+
123
+ This approach should be cozy to those who are familiar with Ruby.
124
+
125
+ Learn more about Ruby and The Rails Doctrine:
126
+
127
+ - [About Ruby](https://www.ruby-lang.org/en/about/)
128
+ - [The Rails Doctrine](https://rubyonrails.org/doctrine)
129
+
130
+ ### Creating a Database
131
+
132
+ ```ruby
133
+ client.dsl.create_database!('radioactive')
134
+ ```
135
+
136
+ ```ruby
137
+ true
138
+ ```
139
+
140
+ ### Deleting a Database
141
+
142
+ ```ruby
143
+ client.dsl.destroy_database!('radioactive')
144
+ ```
145
+
146
+ ```ruby
147
+ true
148
+ ```
149
+
150
+ ### Listing Databases
151
+
152
+ ```ruby
153
+ client.dsl.databases
154
+ ```
155
+
156
+ ```ruby
157
+ ['my-datomic-database']
158
+ ```
159
+
160
+ ### Transacting Schema
161
+
162
+ Like `CREATE TABLE` in SQL databases or defining document or record structures in other databases.
163
+
164
+ ```ruby
165
+ client.dsl.transact_schema!(
166
+ {
167
+ book: {
168
+ title: { type: :string, doc: 'The title of the book.' },
169
+ genre: { type: :string, doc: 'The genre of the book.' },
170
+ published_at_year: { type: :long, doc: 'The year the book was first published.' }
171
+ }
172
+ }
173
+ )
174
+ ```
175
+
176
+ ```ruby
177
+ true
178
+ ```
179
+
180
+ ### Checking Schema
181
+
182
+ Like `SHOW COLUMNS FROM` in SQL databases or checking document or record structures in other databases.
183
+
184
+ ```ruby
185
+ client.dsl.schema
186
+ ```
187
+
188
+ ```ruby
189
+ {
190
+ book: {
191
+ published_at_year: {
192
+ type: :long,
193
+ cardinality: :one,
194
+ doc: 'The year the book was first published.',
195
+ unique: false,
196
+ index: false,
197
+ history: true
198
+ },
199
+ title: {
200
+ type: :string,
201
+ cardinality: :one,
202
+ doc: 'The title of the book.',
203
+ unique: false,
204
+ index: false,
205
+ history: true
206
+ },
207
+ genre: {
208
+ type: :string,
209
+ cardinality: :one,
210
+ doc: 'The genre of the book.',
211
+ unique: false,
212
+ index: false,
213
+ history: true
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Asserting Facts
220
+
221
+ Like `INSERT INTO` in SQL databases or creating a new document or record in other databases.
222
+
223
+ ```ruby
224
+ client.dsl.assert_into!(
225
+ :book,
226
+ {
227
+ title: 'Pride and Prejudice',
228
+ genre: 'Romance',
229
+ published_at_year: 1813
230
+ }
231
+ )
232
+ ```
233
+
234
+ ```ruby
235
+ 4611681620380877802
236
+ ```
237
+
238
+ ```ruby
239
+ client.dsl.assert_into!(
240
+ :book,
241
+ [{
242
+ title: 'Near to the Wild Heart',
243
+ genre: 'Novel',
244
+ published_at_year: 1943
245
+ },
246
+ {
247
+ title: 'A Study in Scarlet',
248
+ genre: 'Detective',
249
+ published_at_year: 1887
250
+ },
251
+ {
252
+ title: 'The Tell-Tale Heart',
253
+ genre: 'Horror',
254
+ published_at_year: 1843
255
+ }]
256
+ )
257
+ ```
258
+
259
+
260
+ ```ruby
261
+ [4611681620380877804, 4611681620380877805, 4611681620380877806]
262
+ ```
263
+
264
+ ### Reading Data by Entity
265
+
266
+ Like `SELECT` in SQL databases or querying documents or records in other databases.
267
+
268
+ ```ruby
269
+ client.dsl.find_by_entity_id(4611681620380877804)
270
+ ```
271
+
272
+ ```ruby
273
+ {
274
+ book: {
275
+ title: 'Near to the Wild Heart',
276
+ genre: 'Novel',
277
+ published_at_year: 1943,
278
+ _id: 4611681620380877804
279
+ }
280
+ }
281
+ ```
282
+
283
+ ### Reading Data by Querying
284
+
285
+ Like `SELECT` in SQL databases or querying documents or records in other databases.
286
+
287
+ ```ruby
288
+ client.dsl.query(
289
+ datalog: <<~EDN
290
+ [:find ?e ?title ?genre ?year
291
+ :where [?e :book/title ?title]
292
+ [?e :book/genre ?genre]
293
+ [?e :book/published_at_year ?year]]
294
+ EDN
295
+ )
296
+ ```
297
+
298
+ ```ruby
299
+ [[4611681620380877805, 'A Study in Scarlet', 'Detective', 1887],
300
+ [4611681620380877804, 'Near to the Wild Heart', 'Novel', 1943],
301
+ [4611681620380877806, 'The Tell-Tale Heart', 'Horror', 1843],
302
+ [4611681620380877802, 'Pride and Prejudice', 'Romance', 1813]]
303
+ ```
304
+
305
+ ```ruby
306
+ client.dsl.query(
307
+ params: ['The Tell-Tale Heart'],
308
+ datalog: <<~EDN
309
+ [:find ?e ?title ?genre ?year
310
+ :in $ ?title
311
+ :where [?e :book/title ?title]
312
+ [?e :book/genre ?genre]
313
+ [?e :book/published_at_year ?year]]
314
+ EDN
315
+ )
316
+ ```
317
+
318
+
319
+ ```ruby
320
+ [[4611681620380877806, 'The Tell-Tale Heart', 'Horror', 1843]]
321
+ ```
322
+
323
+ ### Accumulating Facts
324
+
325
+ Like `UPDATE` in SQL databases or updating documents or records in other databases. However, Datomic never updates data. It is an immutable database that only accumulates new facts or retracts past facts.
326
+
327
+ ```ruby
328
+ client.dsl.assert_into!(
329
+ :book, { _id: 4611681620380877806, genre: 'Gothic' }
330
+ )
331
+ ```
332
+
333
+ ```ruby
334
+ 4611681620380877806
335
+ ```
336
+
337
+ ### Retracting Facts
338
+
339
+ Like `DELETE` in SQL databases or deleting documents or records in other databases. However, Datomic never deletes data. It is an immutable database that only accumulates new facts or retracts past facts.
340
+
341
+ Retract the value of an attribute:
342
+
343
+ ```ruby
344
+ client.dsl.retract_from!(
345
+ :book, { _id: 4611681620380877806, genre: 'Gothic' }
346
+ )
347
+ ```
348
+
349
+ ```ruby
350
+ true
351
+ ```
352
+
353
+ Retract an attribute:
354
+
355
+ ```ruby
356
+ client.dsl.retract_from!(
357
+ :book, { _id: 4611681620380877804, genre: nil }
358
+ )
359
+ ```
360
+
361
+ ```ruby
362
+ true
363
+ ```
364
+
365
+ Retract an entity:
366
+
367
+ ```ruby
368
+ client.dsl.retract_from!(
369
+ :book, { _id: 4611681620380877805 }
370
+ )
371
+ ```
372
+
373
+ ```ruby
374
+ true
375
+ ```
376
+
377
+
378
+ ## Flare API
379
+
380
+ 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).
381
+
382
+ This approach should be familiar to those who know Datomic concepts and APIs.
383
+
384
+ Learn more about Clojure and Datomic:
385
+
386
+ - [Clojure Rationale](https://clojure.org/about/rationale)
387
+ - [Datomic Introduction](https://docs.datomic.com/datomic-overview.html)
388
+
389
+ ### Creating a Database
390
+
391
+ ```ruby
392
+ client.api.create_database!({ name: 'fireball' })['data']
393
+ ```
394
+
395
+ ```ruby
396
+ true
397
+ ```
398
+
399
+ ### Deleting a Database
400
+
401
+ ```ruby
402
+ client.api.delete_database!({ name: 'fireball' })['data']
403
+ ```
404
+
405
+ ```ruby
406
+ true
407
+ ```
408
+
409
+ ### Listing Databases
410
+
411
+ ```ruby
412
+ # Flare on Peer Mode
413
+ client.api.get_database_names['data']
414
+
415
+ # Flare on Client Mode
416
+ client.api.list_databases['data']
417
+ ```
418
+
419
+ ```ruby
420
+ ['my-datomic-database']
421
+ ```
422
+
423
+ ### Transacting Schema
424
+
425
+ ```ruby
426
+ client.api.transact!(
427
+ { data: <<~EDN
428
+ [{:db/ident :book/title
429
+ :db/valueType :db.type/string
430
+ :db/cardinality :db.cardinality/one
431
+ :db/doc "The title of the book."}
432
+
433
+ {:db/ident :book/genre
434
+ :db/valueType :db.type/string
435
+ :db/cardinality :db.cardinality/one
436
+ :db/doc "The genre of the book."}
437
+
438
+ {:db/ident :book/published_at_year
439
+ :db/valueType :db.type/long
440
+ :db/cardinality :db.cardinality/one
441
+ :db/doc "The year the book was first published."}]
442
+ EDN
443
+ }
444
+ )['data']
445
+ ```
446
+
447
+ ```ruby
448
+ {
449
+ 'db-before' => 'datomic.db.Db@3062d44c',
450
+ 'db-after' => 'datomic.db.Db@7802aade',
451
+ 'tx-data' =>
452
+ [[13194139534312, 50, '2024-09-29T13:52:52.435Z', 13194139534312, true],
453
+ [72, 10, ':book/title', 13194139534312, true],
454
+ [72, 40, 23, 13194139534312, true],
455
+ [72, 41, 35, 13194139534312, true],
456
+ [72, 62, 'The title of the book.', 13194139534312, true],
457
+ [73, 10, ':book/genre', 13194139534312, true],
458
+ [73, 40, 23, 13194139534312, true],
459
+ [73, 41, 35, 13194139534312, true],
460
+ [73, 62, 'The genre of the book.', 13194139534312, true],
461
+ [74, 10, ':book/published_at_year', 13194139534312, true],
462
+ [74, 40, 22, 13194139534312, true],
463
+ [74, 41, 35, 13194139534312, true],
464
+ [74, 62, 'The year the book was first published.', 13194139534312, true],
465
+ [0, 13, 72, 13194139534312, true],
466
+ [0, 13, 73, 13194139534312, true],
467
+ [0, 13, 74, 13194139534312, true]],
468
+ 'tempids' =>
469
+ {
470
+ '-9223300668110558618' => 72,
471
+ '-9223300668110558617' => 73,
472
+ '-9223300668110558616' => 74
473
+ }
474
+ }
475
+ ```
476
+
477
+ ### Checking Schema
478
+
479
+ ```ruby
480
+ client.api.q(
481
+ {
482
+ inputs: [{ database: { latest: true } }],
483
+ query: <<~EDN
484
+ [:find
485
+ ?e ?ident ?value_type ?cardinality ?doc
486
+ ?unique ?index ?no_history
487
+ :in $
488
+ :where
489
+ [?e :db/ident ?ident]
490
+
491
+ [?e :db/valueType ?value_type_id]
492
+ [?value_type_id :db/ident ?value_type]
493
+
494
+ [?e :db/cardinality ?cardinality_id]
495
+ [?cardinality_id :db/ident ?cardinality]
496
+
497
+ [(get-else $ ?e :db/doc "") ?doc]
498
+
499
+ [(get-else $ ?e :db/unique -1) ?unique_id]
500
+ [(get-else $ ?unique_id :db/ident false) ?unique]
501
+
502
+ [(get-else $ ?e :db/index false) ?index]
503
+ [(get-else $ ?e :db/noHistory false) ?no_history]]
504
+ EDN
505
+ }
506
+ )['data'].filter do |datom|
507
+ !%w[
508
+ db
509
+ db.alter db.attr db.bootstrap db.cardinality db.entity db.excise
510
+ db.fn db.install db.lang db.part db.sys db.type db.unique
511
+ fressian
512
+ ].include?(datom[1].split('/').first)
513
+ end
514
+ ```
515
+
516
+ ```ruby
517
+ [[74,
518
+ 'book/published_at_year',
519
+ 'db.type/long',
520
+ 'db.cardinality/one',
521
+ 'The year the book was first published.',
522
+ false,
523
+ false,
524
+ false],
525
+ [72,
526
+ 'book/title',
527
+ 'db.type/string',
528
+ 'db.cardinality/one',
529
+ 'The title of the book.',
530
+ false,
531
+ false,
532
+ false],
533
+ [73,
534
+ 'book/genre',
535
+ 'db.type/string',
536
+ 'db.cardinality/one',
537
+ 'The genre of the book.',
538
+ false,
539
+ false,
540
+ false]]
541
+ ```
542
+
543
+ ### Asserting Facts
544
+
545
+ ```ruby
546
+ client.api.transact!(
547
+ { data: <<~EDN
548
+ [{:db/id -1
549
+ :book/title "Pride and Prejudice"
550
+ :book/genre "Romance"
551
+ :book/published_at_year 1813}]
552
+ EDN
553
+ }
554
+ )['data']
555
+ ```
556
+
557
+ ```ruby
558
+ {
559
+ 'db-before' => 'datomic.db.Db@5342ef1a',
560
+ 'db-after' => 'datomic.db.Db@643ac781',
561
+ 'tx-data' =>
562
+ [[13194139534313, 50, '2024-09-29T13:52:52.546Z', 13194139534313, true],
563
+ [4611681620380877802, 72, 'Pride and Prejudice', 13194139534313, true],
564
+ [4611681620380877802, 73, 'Romance', 13194139534313, true],
565
+ [4611681620380877802, 74, 1813, 13194139534313, true]],
566
+ 'tempids' => { '-1' => 4611681620380877802 }
567
+ }
568
+ ```
569
+
570
+ ```ruby
571
+ client.api.transact!(
572
+ { data: <<~EDN
573
+ [{:db/id -1
574
+ :book/title "Near to the Wild Heart"
575
+ :book/genre "Novel"
576
+ :book/published_at_year 1943}
577
+ {:db/id -2
578
+ :book/title "A Study in Scarlet"
579
+ :book/genre "Detective"
580
+ :book/published_at_year 1887}
581
+ {:db/id -3
582
+ :book/title "The Tell-Tale Heart"
583
+ :book/genre "Horror"
584
+ :book/published_at_year 1843}]
585
+ EDN
586
+ }
587
+ )['data']
588
+ ```
589
+
590
+
591
+ ```ruby
592
+ {
593
+ 'db-before' => 'datomic.db.Db@6afd3576',
594
+ 'db-after' => 'datomic.db.Db@73000a74',
595
+ 'tx-data' =>
596
+ [[13194139534315, 50, '2024-09-29T13:52:52.599Z', 13194139534315, true],
597
+ [4611681620380877804, 72, 'Near to the Wild Heart', 13194139534315, true],
598
+ [4611681620380877804, 73, 'Novel', 13194139534315, true],
599
+ [4611681620380877804, 74, 1943, 13194139534315, true],
600
+ [4611681620380877805, 72, 'A Study in Scarlet', 13194139534315, true],
601
+ [4611681620380877805, 73, 'Detective', 13194139534315, true],
602
+ [4611681620380877805, 74, 1887, 13194139534315, true],
603
+ [4611681620380877806, 72, 'The Tell-Tale Heart', 13194139534315, true],
604
+ [4611681620380877806, 73, 'Horror', 13194139534315, true],
605
+ [4611681620380877806, 74, 1843, 13194139534315, true]],
606
+ 'tempids' =>
607
+ {
608
+ '-1' => 4611681620380877804,
609
+ '-2' => 4611681620380877805,
610
+ '-3' => 4611681620380877806
611
+ }
612
+ }
613
+ ```
614
+
615
+ ### Reading Data by Entity
616
+
617
+ ```ruby
618
+ client.api.entity(
619
+ {
620
+ database: { latest: true },
621
+ id: 4611681620380877804
622
+ }
623
+ )['data']
624
+ ```
625
+
626
+ ```ruby
627
+ {
628
+ ':book/title' => 'Near to the Wild Heart',
629
+ ':book/genre' => 'Novel',
630
+ ':book/published_at_year' => 1943,
631
+ ':db/id' => 4611681620380877804
632
+ }
633
+ ```
634
+
635
+ ### Reading Data by Querying
636
+
637
+ ```ruby
638
+ client.api.q(
639
+ {
640
+ inputs: [{ database: { latest: true } }],
641
+ query: <<~EDN
642
+ [:find ?e ?title ?genre ?year
643
+ :where [?e :book/title ?title]
644
+ [?e :book/genre ?genre]
645
+ [?e :book/published_at_year ?year]]
646
+ EDN
647
+ }
648
+ )['data']
649
+ ```
650
+
651
+ ```ruby
652
+ [[4611681620380877805, 'A Study in Scarlet', 'Detective', 1887],
653
+ [4611681620380877804, 'Near to the Wild Heart', 'Novel', 1943],
654
+ [4611681620380877806, 'The Tell-Tale Heart', 'Horror', 1843],
655
+ [4611681620380877802, 'Pride and Prejudice', 'Romance', 1813]]
656
+ ```
657
+
658
+ ```ruby
659
+ client.api.q(
660
+ {
661
+ inputs: [
662
+ { database: { latest: true } },
663
+ 'The Tell-Tale Heart'
664
+ ],
665
+ query: <<~EDN
666
+ [:find ?e ?title ?genre ?year
667
+ :in $ ?title
668
+ :where [?e :book/title ?title]
669
+ [?e :book/genre ?genre]
670
+ [?e :book/published_at_year ?year]]
671
+ EDN
672
+ }
673
+ )['data']
674
+ ```
675
+
676
+
677
+ ```ruby
678
+ [[4611681620380877806, 'The Tell-Tale Heart', 'Horror', 1843]]
679
+ ```
680
+
681
+ ### Accumulating Facts
682
+
683
+ ```ruby
684
+ client.api.transact!(
685
+ { data: <<~EDN
686
+ [{:db/id 4611681620380877806 :book/genre "Gothic"}]
687
+ EDN
688
+ }
689
+ )['data']
690
+ ```
691
+
692
+ ```ruby
693
+ {
694
+ 'db-before' => 'datomic.db.Db@55e660ec',
695
+ 'db-after' => 'datomic.db.Db@385f74d4',
696
+ 'tx-data' =>
697
+ [[13194139534319, 50, '2024-09-29T13:52:52.817Z', 13194139534319, true],
698
+ [4611681620380877806, 73, 'Gothic', 13194139534319, true],
699
+ [4611681620380877806, 73, 'Horror', 13194139534319, false]],
700
+ 'tempids' => {}
701
+ }
702
+ ```
703
+
704
+ ### Retracting Facts
705
+
706
+ Retract the value of an attribute:
707
+
708
+ ```ruby
709
+ client.api.transact!(
710
+ { data: <<~EDN
711
+ [[:db/retract 4611681620380877806 :book/genre "Gothic"]]
712
+ EDN
713
+ }
714
+ )['data']
715
+ ```
716
+
717
+ ```ruby
718
+ {
719
+ 'db-before' => 'datomic.db.Db@7c2176fa',
720
+ 'db-after' => 'datomic.db.Db@6ec2acf1',
721
+ 'tx-data' =>
722
+ [[13194139534320, 50, '2024-09-29T13:52:52.869Z', 13194139534320, true],
723
+ [4611681620380877806, 73, 'Gothic', 13194139534320, false]],
724
+ 'tempids' => {}
725
+ }
726
+ ```
727
+
728
+ Retract an attribute:
729
+
730
+ ```ruby
731
+ client.api.transact!(
732
+ { data: <<~EDN
733
+ [[:db/retract 4611681620380877804 :book/genre]]
734
+ EDN
735
+ }
736
+ )['data']
737
+ ```
738
+
739
+ ```ruby
740
+ {
741
+ 'db-before' => 'datomic.db.Db@f8180a0',
742
+ 'db-after' => 'datomic.db.Db@2bc03f4d',
743
+ 'tx-data' =>
744
+ [[13194139534321, 50, '2024-09-29T13:52:52.913Z', 13194139534321, true],
745
+ [4611681620380877804, 73, 'Novel', 13194139534321, false]],
746
+ 'tempids' => {}
747
+ }
748
+ ```
749
+
750
+ Retract an entity:
751
+
752
+ ```ruby
753
+ client.api.transact!(
754
+ { data: <<~EDN
755
+ [[:db/retractEntity 4611681620380877805]]
756
+ EDN
757
+ }
758
+ )['data']
759
+ ```
760
+
761
+ ```ruby
762
+ {
763
+ 'db-before' => 'datomic.db.Db@698670bc',
764
+ 'db-after' => 'datomic.db.Db@2abc508c',
765
+ 'tx-data' =>
766
+ [[13194139534322, 50, '2024-09-29T13:52:52.955Z', 13194139534322, true],
767
+ [4611681620380877805, 72, 'A Study in Scarlet', 13194139534322, false],
768
+ [4611681620380877805, 73, 'Detective', 13194139534322, false],
769
+ [4611681620380877805, 74, 1887, 13194139534322, false]],
770
+ 'tempids' => {}
771
+ }
772
+ ```
773
+
774
+
775
+ ## Development
776
+
777
+ ```bash
778
+ bundle
779
+ rubocop -A
780
+
781
+ ```
782
+
783
+ ### Publish to RubyGems
784
+
785
+ ```bash
786
+ gem build datomic-flare.gemspec
787
+
788
+ gem signin
789
+
790
+ gem push datomic-flare-1.0.0.gem
791
+
792
+ ```
793
+
794
+ ### Setup for Tests and Documentation
795
+
796
+ Tests run against real Datomic databases, and documentation (README) is generated by interacting with real Datomic databases.
797
+
798
+ To accomplish that, we need to have [Datomic](https://github.com/gbaptista/datomic-pro-docker) and [Flare](https://github.com/gbaptista/datomic-flare) running.
799
+
800
+ **TL;DR:**
801
+
802
+ ```bash
803
+ git clone https://github.com/gbaptista/datomic-pro-docker.git
804
+
805
+ cd datomic-pro-docker
806
+
807
+ cp compose/flare-dev.yml docker-compose.yml
808
+
809
+ docker compose up -d datomic-storage
810
+
811
+ docker compose run datomic-tools psql \
812
+ -f bin/sql/postgres-table.sql \
813
+ -h datomic-storage \
814
+ -U datomic-user \
815
+ -d my-datomic-storage
816
+
817
+ docker compose up -d datomic-transactor
818
+
819
+ docker compose run datomic-tools clojure -M -e "$(cat <<'CLOJURE'
820
+ (require '[datomic.api :as d])
821
+
822
+ (d/create-database "datomic:sql://my-datomic-database?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
823
+
824
+ (d/create-database "datomic:sql://my-datomic-database-test?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
825
+
826
+ (d/create-database "datomic:sql://my-datomic-database-test-green?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
827
+
828
+ (System/exit 0)
829
+ CLOJURE
830
+ )"
831
+
832
+ docker compose up -d datomic-peer-server
833
+
834
+ docker compose up -d datomic-flare-peer datomic-flare-client
835
+
836
+ ```
837
+
838
+ ```bash
839
+ curl -s http://localhost:3042/meta \
840
+ -X GET \
841
+ -H "Content-Type: application/json" \
842
+ | jq
843
+
844
+ ```
845
+
846
+ ```json
847
+ {
848
+ "data": {
849
+ "mode": "peer"
850
+ }
851
+ }
852
+
853
+ ```
854
+
855
+ ```bash
856
+ curl -s http://localhost:3043/meta \
857
+ -X GET \
858
+ -H "Content-Type: application/json" \
859
+ | jq
860
+
861
+ ```
862
+
863
+ ```json
864
+ {
865
+ "data": {
866
+ "mode": "client"
867
+ }
868
+ }
869
+
870
+ ```
871
+
872
+ You are ready to run tests and generate documentation.
873
+
874
+ **Detailed instructions:**
875
+
876
+ Clone the [datomic-pro-docker](https://github.com/gbaptista/datomic-pro-docker) repository and copy the Docker Compose template:
877
+
878
+ ```bash
879
+ git clone https://github.com/gbaptista/datomic-pro-docker.git
880
+
881
+ cd datomic-pro-docker
882
+
883
+ cp compose/flare-dev.yml docker-compose.yml
884
+
885
+ ```
886
+
887
+ Start PostgreSQL as Datomic's storage service:
888
+
889
+ ```bash
890
+ docker compose up -d datomic-storage
891
+
892
+ docker compose logs -f datomic-storage
893
+
894
+ ```
895
+
896
+ Create the table for Datomic databases:
897
+
898
+ ```bash
899
+ docker compose run datomic-tools psql \
900
+ -f bin/sql/postgres-table.sql \
901
+ -h datomic-storage \
902
+ -U datomic-user \
903
+ -d my-datomic-storage
904
+
905
+ ```
906
+
907
+ You will be prompted for a password, which is `unsafe`.
908
+
909
+ Start the Datomic Transactor:
910
+
911
+ ```bash
912
+ docker compose up -d datomic-transactor
913
+
914
+ docker compose logs -f datomic-transactor
915
+
916
+ ```
917
+
918
+ Create the following databases:
919
+
920
+ - `my-datomic-database`
921
+ - `my-datomic-database-test`
922
+ - `my-datomic-database-test-green`
923
+
924
+ ```bash
925
+ docker compose run datomic-tools clojure -M -e "$(cat <<'CLOJURE'
926
+ (require '[datomic.api :as d])
927
+
928
+ (d/create-database "datomic:sql://my-datomic-database?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
929
+
930
+ (d/create-database "datomic:sql://my-datomic-database-test?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
931
+
932
+ (d/create-database "datomic:sql://my-datomic-database-test-green?jdbc:postgresql://datomic-storage:5432/my-datomic-storage?user=datomic-user&password=unsafe")
933
+
934
+ (System/exit 0)
935
+ CLOJURE
936
+ )"
937
+
938
+ ```
939
+
940
+ Start the Peer Server:
941
+
942
+ ```bash
943
+ docker compose up -d datomic-peer-server
944
+
945
+ docker compose logs -f datomic-peer-server
946
+
947
+ ```
948
+
949
+ Start 2 instances of Flare, one in Peer Mode and another in Client Mode:
950
+
951
+ ```bash
952
+ docker compose up -d datomic-flare-peer datomic-flare-client
953
+
954
+ docker compose logs -f datomic-flare-peer
955
+ docker compose logs -f datomic-flare-client
956
+
957
+ ```
958
+
959
+ You should be able to request both:
960
+
961
+ Datomic Flare in Peer Mode:
962
+ ```bash
963
+ curl -s http://localhost:3042/meta \
964
+ -X GET \
965
+ -H "Content-Type: application/json" \
966
+ | jq
967
+
968
+ ```
969
+
970
+ ```json
971
+ {
972
+ "data": {
973
+ "mode": "peer"
974
+ }
975
+ }
976
+
977
+ ```
978
+
979
+ Datomic Flare in Client Mode:
980
+ ```bash
981
+ curl -s http://localhost:3043/meta \
982
+ -X GET \
983
+ -H "Content-Type: application/json" \
984
+ | jq
985
+
986
+ ```
987
+
988
+ ```json
989
+ {
990
+ "data": {
991
+ "mode": "client"
992
+ }
993
+ }
994
+
995
+ ```
996
+
997
+ You are ready to run tests and generate documentation.
998
+
999
+ ### Running Tests
1000
+
1001
+ Tests run against real Datomic databases, so complete the [Setup for Tests and Documentation](#setup-for-tests-and-documentation) first.
1002
+
1003
+ ```bash
1004
+ cp .env.example .env
1005
+
1006
+ bundle exec rspec
1007
+
1008
+ ```
1009
+
1010
+ ### Updating the README
1011
+
1012
+ Documentation (README) is generated by interacting with real Datomic databases, so complete the [Setup for Tests and Documentation](#setup-for-tests-and-documentation) first.
1013
+
1014
+ Update the `docs/templates/*.md` files, and then:
1015
+
1016
+ ```sh
1017
+ cp .env.example .env
1018
+
1019
+ bundle exec ruby ports/cli.rb docs:generate
1020
+
1021
+ ```
1022
+
1023
+ Trick for automatically updating the `README.md` when `docs/templates/*.md` files change:
1024
+
1025
+ ```sh
1026
+ sudo pacman -S inotify-tools # Arch / Manjaro
1027
+ sudo apt-get install inotify-tools # Debian / Ubuntu / Raspberry Pi OS
1028
+ sudo dnf install inotify-tools # Fedora / CentOS / RHEL
1029
+
1030
+ while inotifywait -e modify docs/templates/*; \
1031
+ do bundle exec ruby ports/cli.rb docs:generate; \
1032
+ done
1033
+
1034
+ ```
1035
+
1036
+ Trick for Markdown Live Preview:
1037
+ ```sh
1038
+ pip install -U markdown_live_preview
1039
+
1040
+ mlp README.md -p 8042 --no-follow
1041
+
1042
+ ```