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.
- checksums.yaml +7 -0
- data/.env.example +4 -0
- data/.gitignore +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +15 -0
- data/.ruby-version +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +100 -0
- data/LICENSE +9 -0
- data/README.md +1042 -0
- data/components/errors.rb +22 -0
- data/components/http.rb +55 -0
- data/controllers/api.rb +48 -0
- data/controllers/client.rb +43 -0
- data/controllers/documentation/formatter.rb +37 -0
- data/controllers/documentation/generator.rb +390 -0
- data/controllers/dsl/querying.rb +39 -0
- data/controllers/dsl/schema.rb +37 -0
- data/controllers/dsl/transacting.rb +62 -0
- data/controllers/dsl.rb +48 -0
- data/datomic-flare.gemspec +39 -0
- data/docs/CURL.md +781 -0
- data/docs/README.md +360 -0
- data/docs/api.md +395 -0
- data/docs/dsl.md +257 -0
- data/docs/templates/.rubocop.yml +15 -0
- data/docs/templates/README.md +319 -0
- data/docs/templates/api.md +267 -0
- data/docs/templates/dsl.md +206 -0
- data/helpers/h.rb +17 -0
- data/logic/dangerous_override.rb +108 -0
- data/logic/querying.rb +34 -0
- data/logic/schema.rb +91 -0
- data/logic/transacting.rb +53 -0
- data/logic/types.rb +141 -0
- data/ports/cli.rb +26 -0
- data/ports/dsl/datomic-flare/errors.rb +5 -0
- data/ports/dsl/datomic-flare.rb +20 -0
- data/static/gem.rb +15 -0
- metadata +146 -0
@@ -0,0 +1,206 @@
|
|
1
|
+
## Flare DSL
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
This approach should be cozy to those who are familiar with Ruby.
|
6
|
+
|
7
|
+
Learn more about Ruby and The Rails Doctrine:
|
8
|
+
|
9
|
+
- [About Ruby](https://www.ruby-lang.org/en/about/)
|
10
|
+
- [The Rails Doctrine](https://rubyonrails.org/doctrine)
|
11
|
+
|
12
|
+
### Creating a Database
|
13
|
+
|
14
|
+
```ruby:runnable
|
15
|
+
client.dsl.create_database!('radioactive')
|
16
|
+
```
|
17
|
+
|
18
|
+
```ruby:placeholder
|
19
|
+
```
|
20
|
+
|
21
|
+
### Deleting a Database
|
22
|
+
|
23
|
+
```ruby:runnable
|
24
|
+
client.dsl.destroy_database!('radioactive')
|
25
|
+
```
|
26
|
+
|
27
|
+
```ruby:placeholder
|
28
|
+
```
|
29
|
+
|
30
|
+
### Listing Databases
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
client.dsl.databases
|
34
|
+
```
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
['my-datomic-database']
|
38
|
+
```
|
39
|
+
|
40
|
+
### Transacting Schema
|
41
|
+
|
42
|
+
Like `CREATE TABLE` in SQL databases or defining document or record structures in other databases.
|
43
|
+
|
44
|
+
```ruby:runnable
|
45
|
+
client.dsl.transact_schema!(
|
46
|
+
{
|
47
|
+
book: {
|
48
|
+
title: { type: :string, doc: 'The title of the book.' },
|
49
|
+
genre: { type: :string, doc: 'The genre of the book.' },
|
50
|
+
published_at_year: { type: :long, doc: 'The year the book was first published.' }
|
51
|
+
}
|
52
|
+
})
|
53
|
+
```
|
54
|
+
|
55
|
+
```ruby:placeholder
|
56
|
+
```
|
57
|
+
|
58
|
+
### Checking Schema
|
59
|
+
|
60
|
+
Like `SHOW COLUMNS FROM` in SQL databases or checking document or record structures in other databases.
|
61
|
+
|
62
|
+
```ruby:runnable
|
63
|
+
client.dsl.schema
|
64
|
+
```
|
65
|
+
|
66
|
+
```ruby:placeholder
|
67
|
+
|
68
|
+
```
|
69
|
+
|
70
|
+
### Asserting Facts
|
71
|
+
|
72
|
+
Like `INSERT INTO` in SQL databases or creating a new document or record in other databases.
|
73
|
+
|
74
|
+
```ruby:runnable
|
75
|
+
client.dsl.assert_into!(
|
76
|
+
:book,
|
77
|
+
{ title: 'Pride and Prejudice',
|
78
|
+
genre: 'Romance',
|
79
|
+
published_at_year: 1813 }
|
80
|
+
)
|
81
|
+
```
|
82
|
+
|
83
|
+
```ruby:placeholder
|
84
|
+
```
|
85
|
+
|
86
|
+
```ruby:runnable
|
87
|
+
client.dsl.assert_into!(
|
88
|
+
:book,
|
89
|
+
[{ title: 'Near to the Wild Heart',
|
90
|
+
genre: 'Novel',
|
91
|
+
published_at_year: 1943 },
|
92
|
+
{ title: 'A Study in Scarlet',
|
93
|
+
genre: 'Detective',
|
94
|
+
published_at_year: 1887 },
|
95
|
+
{ title: 'The Tell-Tale Heart',
|
96
|
+
genre: 'Horror',
|
97
|
+
published_at_year: 1843 }]
|
98
|
+
)
|
99
|
+
```
|
100
|
+
|
101
|
+
```ruby:state
|
102
|
+
state[:wild_heart_entity_id] = result[0]
|
103
|
+
state[:scarlet_entity_id] = result[1]
|
104
|
+
```
|
105
|
+
|
106
|
+
```ruby:placeholder
|
107
|
+
```
|
108
|
+
|
109
|
+
### Reading Data by Entity
|
110
|
+
|
111
|
+
Like `SELECT` in SQL databases or querying documents or records in other databases.
|
112
|
+
|
113
|
+
```ruby:runnable/render
|
114
|
+
client.dsl.find_by_entity_id({{ state.wild_heart_entity_id }})
|
115
|
+
```
|
116
|
+
|
117
|
+
```ruby:placeholder
|
118
|
+
```
|
119
|
+
|
120
|
+
### Reading Data by Querying
|
121
|
+
|
122
|
+
Like `SELECT` in SQL databases or querying documents or records in other databases.
|
123
|
+
|
124
|
+
```ruby:runnable
|
125
|
+
client.dsl.query(
|
126
|
+
datalog: <<~EDN
|
127
|
+
[:find ?e ?title ?genre ?year
|
128
|
+
:where [?e :book/title ?title]
|
129
|
+
[?e :book/genre ?genre]
|
130
|
+
[?e :book/published_at_year ?year]]
|
131
|
+
EDN
|
132
|
+
)
|
133
|
+
```
|
134
|
+
|
135
|
+
```ruby:placeholder
|
136
|
+
```
|
137
|
+
|
138
|
+
```ruby:runnable
|
139
|
+
client.dsl.query(
|
140
|
+
params: ['The Tell-Tale Heart'],
|
141
|
+
datalog: <<~EDN
|
142
|
+
[:find ?e ?title ?genre ?year
|
143
|
+
:in $ ?title
|
144
|
+
:where [?e :book/title ?title]
|
145
|
+
[?e :book/genre ?genre]
|
146
|
+
[?e :book/published_at_year ?year]]
|
147
|
+
EDN
|
148
|
+
)
|
149
|
+
```
|
150
|
+
|
151
|
+
```ruby:state
|
152
|
+
state[:tale_heart_entity_id] = result[0][0]
|
153
|
+
```
|
154
|
+
|
155
|
+
```ruby:placeholder
|
156
|
+
```
|
157
|
+
|
158
|
+
### Accumulating Facts
|
159
|
+
|
160
|
+
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.
|
161
|
+
|
162
|
+
```ruby:runnable/render
|
163
|
+
client.dsl.assert_into!(
|
164
|
+
:book, { _id: {{ state.tale_heart_entity_id }}, genre: 'Gothic' }
|
165
|
+
)
|
166
|
+
```
|
167
|
+
|
168
|
+
```ruby:placeholder
|
169
|
+
```
|
170
|
+
|
171
|
+
### Retracting Facts
|
172
|
+
|
173
|
+
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.
|
174
|
+
|
175
|
+
Retract the value of an attribute:
|
176
|
+
|
177
|
+
```ruby:runnable/render
|
178
|
+
client.dsl.retract_from!(
|
179
|
+
:book, { _id: {{ state.tale_heart_entity_id }}, genre: 'Gothic' }
|
180
|
+
)
|
181
|
+
```
|
182
|
+
|
183
|
+
```ruby:placeholder
|
184
|
+
```
|
185
|
+
|
186
|
+
Retract an attribute:
|
187
|
+
|
188
|
+
```ruby:runnable/render
|
189
|
+
client.dsl.retract_from!(
|
190
|
+
:book, { _id: {{ state.wild_heart_entity_id }}, genre: nil }
|
191
|
+
)
|
192
|
+
```
|
193
|
+
|
194
|
+
```ruby:placeholder
|
195
|
+
```
|
196
|
+
|
197
|
+
Retract an entity:
|
198
|
+
|
199
|
+
```ruby:runnable/render
|
200
|
+
client.dsl.retract_from!(
|
201
|
+
:book, { _id: {{ state.scarlet_entity_id }} }
|
202
|
+
)
|
203
|
+
```
|
204
|
+
|
205
|
+
```ruby:placeholder
|
206
|
+
```
|
data/helpers/h.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flare
|
4
|
+
module H
|
5
|
+
def self.symbolize_keys(structure)
|
6
|
+
result = {}
|
7
|
+
|
8
|
+
structure.each do |key, value|
|
9
|
+
string_key = key.to_sym
|
10
|
+
|
11
|
+
result[string_key] = value.is_a?(Hash) ? symbolize_keys(value) : value
|
12
|
+
end
|
13
|
+
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flare
|
4
|
+
module DangerousOverrideLogic
|
5
|
+
CONNECTION_DATABASE_KEYS = [:name].freeze
|
6
|
+
|
7
|
+
DATABASE_KEYS = %i[name latest as_of].freeze
|
8
|
+
|
9
|
+
def self.apply_dangerous_overrides_to_payload(path, overrides, payload)
|
10
|
+
case path
|
11
|
+
when 'datomic/create-database', 'datomic/delete-database',
|
12
|
+
'datomic/get-database-names', 'datomic/list-databases',
|
13
|
+
'meta',
|
14
|
+
'datomic/_debug/as-peer/create-database',
|
15
|
+
'datomic/_debug/as-peer/delete-database'
|
16
|
+
payload
|
17
|
+
when 'datomic/transact'
|
18
|
+
inject_connection_overrides(overrides, payload)
|
19
|
+
when 'datomic/entity', 'datomic/datoms'
|
20
|
+
inject_database_overrides(overrides, payload)
|
21
|
+
when 'datomic/q'
|
22
|
+
inject_database_overrides_into_inputs(overrides, payload)
|
23
|
+
else
|
24
|
+
raise "Unexpected path: '#{path}'"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.inject_connection_overrides(overrides, payload)
|
29
|
+
if !overrides.key?(:database) || overrides[:database].slice(
|
30
|
+
*CONNECTION_DATABASE_KEYS
|
31
|
+
).empty?
|
32
|
+
return payload
|
33
|
+
end
|
34
|
+
|
35
|
+
payload[:connection] = {} unless payload.key?(:connection)
|
36
|
+
|
37
|
+
payload[:connection][:database] = {} unless payload[:connection].key?(:database)
|
38
|
+
|
39
|
+
payload[:connection][:database] = payload[:connection][:database].merge(
|
40
|
+
overrides[:database].slice(*CONNECTION_DATABASE_KEYS)
|
41
|
+
)
|
42
|
+
|
43
|
+
payload
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.inject_overrides_into_input(overrides, input)
|
47
|
+
input_has_latest = input[:database].key?(:latest)
|
48
|
+
|
49
|
+
input_has_as_of = input[:database].key?(:as_of)
|
50
|
+
|
51
|
+
overrides_has_as_of = overrides[:database].key?(:as_of)
|
52
|
+
overrides_has_latest = overrides[:database].key?(:latest)
|
53
|
+
|
54
|
+
input[:database] = input[:database].except(:latest) if input_has_latest && overrides_has_as_of
|
55
|
+
|
56
|
+
input[:database] = input[:database].except(:as_of) if input_has_as_of && overrides_has_latest
|
57
|
+
|
58
|
+
input[:database] = input[:database].merge(
|
59
|
+
overrides[:database].slice(*DATABASE_KEYS)
|
60
|
+
)
|
61
|
+
|
62
|
+
input
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.inject_database_overrides_into_inputs(overrides, payload)
|
66
|
+
if !overrides.key?(:database) || overrides[:database].slice(
|
67
|
+
*DATABASE_KEYS
|
68
|
+
).empty?
|
69
|
+
return payload
|
70
|
+
end
|
71
|
+
|
72
|
+
payload[:inputs] = {} unless payload.key?(:inputs)
|
73
|
+
|
74
|
+
index_for_database = payload[:inputs].index do |input|
|
75
|
+
input.key?(:database)
|
76
|
+
end
|
77
|
+
|
78
|
+
if index_for_database.nil?
|
79
|
+
require 'pry'
|
80
|
+
binding.pry
|
81
|
+
end
|
82
|
+
|
83
|
+
payload[:inputs][index_for_database] = inject_overrides_into_input(
|
84
|
+
overrides,
|
85
|
+
payload[:inputs][index_for_database]
|
86
|
+
)
|
87
|
+
|
88
|
+
payload
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.inject_database_overrides(overrides, payload)
|
92
|
+
if !overrides.key?(:database) || overrides[:database].slice(
|
93
|
+
*DATABASE_KEYS
|
94
|
+
).empty?
|
95
|
+
return payload
|
96
|
+
end
|
97
|
+
|
98
|
+
payload[:database] = {} unless payload.key?(:database)
|
99
|
+
|
100
|
+
payload[:database] = inject_overrides_into_input(
|
101
|
+
overrides,
|
102
|
+
{ database: payload[:database] }
|
103
|
+
)[:database]
|
104
|
+
|
105
|
+
payload
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/logic/querying.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flare
|
4
|
+
module QueryingLogic
|
5
|
+
def self.entity_to_dsl(entity)
|
6
|
+
namespace = entity.keys.find { |key| key != ':db/id' }
|
7
|
+
|
8
|
+
return nil if namespace.nil?
|
9
|
+
|
10
|
+
namespace = namespace.split('/').first.sub(/^:/, '').to_sym
|
11
|
+
|
12
|
+
{
|
13
|
+
namespace => keys_to_dsl(entity)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.keys_to_dsl(entity)
|
18
|
+
result = {}
|
19
|
+
|
20
|
+
entity.each do |key, value|
|
21
|
+
# TODO: Is this correct? Should the 'id' exist?
|
22
|
+
dsl_key = if [':db/id', 'id'].include?(key)
|
23
|
+
:_id
|
24
|
+
else
|
25
|
+
key.split('/').last.to_sym
|
26
|
+
end
|
27
|
+
|
28
|
+
result[dsl_key] = value.is_a?(Hash) ? keys_to_dsl(value) : value
|
29
|
+
end
|
30
|
+
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/logic/schema.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../helpers/h'
|
4
|
+
|
5
|
+
require_relative 'types'
|
6
|
+
|
7
|
+
module Flare
|
8
|
+
module SchemaLogic
|
9
|
+
QUERY = <<~EDN
|
10
|
+
[:find
|
11
|
+
?e ?ident ?value_type ?cardinality ?doc
|
12
|
+
?unique ?index ?no_history
|
13
|
+
:in $
|
14
|
+
:where
|
15
|
+
[?e :db/ident ?ident]
|
16
|
+
|
17
|
+
[?e :db/valueType ?value_type_id]
|
18
|
+
[?value_type_id :db/ident ?value_type]
|
19
|
+
|
20
|
+
[?e :db/cardinality ?cardinality_id]
|
21
|
+
[?cardinality_id :db/ident ?cardinality]
|
22
|
+
|
23
|
+
[(get-else $ ?e :db/doc "") ?doc]
|
24
|
+
|
25
|
+
[(get-else $ ?e :db/unique -1) ?unique_id]
|
26
|
+
[(get-else $ ?unique_id :db/ident false) ?unique]
|
27
|
+
|
28
|
+
[(get-else $ ?e :db/index false) ?index]
|
29
|
+
[(get-else $ ?e :db/noHistory false) ?no_history]]
|
30
|
+
EDN
|
31
|
+
|
32
|
+
NON_SCHEMA_NAMESPACES = %w[
|
33
|
+
db
|
34
|
+
db.alter db.attr db.bootstrap db.cardinality db.entity db.excise
|
35
|
+
db.fn db.install db.lang db.part db.sys db.type db.unique
|
36
|
+
fressian
|
37
|
+
].freeze
|
38
|
+
|
39
|
+
def self.specification_to_edn(specification)
|
40
|
+
edn_schema = specification.flat_map do |namespace, attributes|
|
41
|
+
attributes.map.with_index do |(attribute, options), i|
|
42
|
+
fields = [
|
43
|
+
"#{i.zero? ? '' : ' '}{:db/ident :#{namespace}/#{attribute}",
|
44
|
+
" :db/valueType #{TypesLogic.ruby_to_datomic_type(options[:type])}",
|
45
|
+
" :db/cardinality #{TypesLogic.ruby_to_datomic_cardinality(options[:cardinality] || :one)}"
|
46
|
+
]
|
47
|
+
|
48
|
+
fields << " :db/doc \"#{options[:doc]}\"" if options[:doc]
|
49
|
+
fields << " :db/unique #{TypesLogic.ruby_to_datomic_unique(options[:unique])}" if options[:unique]
|
50
|
+
fields << ' :db/index true' if options[:index]
|
51
|
+
fields << ' :db/noHistory true' if options[:history] == false
|
52
|
+
|
53
|
+
fields[fields.size - 1] = "#{fields.last}}"
|
54
|
+
|
55
|
+
fields.join("\n")
|
56
|
+
end
|
57
|
+
end.join("\n\n")
|
58
|
+
|
59
|
+
"[#{edn_schema}]"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.datoms_to_specification(datoms)
|
63
|
+
specification = {}
|
64
|
+
|
65
|
+
datoms.filter do |datom|
|
66
|
+
!NON_SCHEMA_NAMESPACES.include?(datom[1].split('/').first)
|
67
|
+
end.each do |entry|
|
68
|
+
namespace, attribute = entry[1].split('/')
|
69
|
+
type = TypesLogic.datomic_to_ruby_type(entry[2])
|
70
|
+
cardinality = TypesLogic.datomic_to_ruby_cardinality(entry[3])
|
71
|
+
doc = entry[4].empty? ? nil : entry[4]
|
72
|
+
|
73
|
+
unique = entry[5] ? TypesLogic.datomic_to_ruby_unique(entry[5]) : false
|
74
|
+
indexed = entry[6]
|
75
|
+
no_history = entry[7]
|
76
|
+
|
77
|
+
specification[namespace] ||= {}
|
78
|
+
specification[namespace][attribute] = {
|
79
|
+
type:,
|
80
|
+
cardinality:,
|
81
|
+
doc:,
|
82
|
+
unique:,
|
83
|
+
index: indexed,
|
84
|
+
history: !no_history
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
H.symbolize_keys(specification)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'types'
|
4
|
+
|
5
|
+
module Flare
|
6
|
+
module TransactingLogic
|
7
|
+
def self.retractions_to_edn(namespace, retractions)
|
8
|
+
edn = retractions.map do |retraction|
|
9
|
+
retraction_to_edn(namespace, retraction)
|
10
|
+
end
|
11
|
+
|
12
|
+
"[#{edn.join("\n ")}]"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.retraction_to_edn(namespace, retraction)
|
16
|
+
id = retraction[:_id]
|
17
|
+
|
18
|
+
attributes = retraction.except(:_id)
|
19
|
+
|
20
|
+
if attributes.empty?
|
21
|
+
# Built-In Transaction Functions
|
22
|
+
# https://docs.datomic.com/transactions/transaction-functions.html#built-in
|
23
|
+
"[:db/retractEntity #{id}]"
|
24
|
+
else
|
25
|
+
attributes.map do |attribute, value|
|
26
|
+
if value.nil?
|
27
|
+
"[:db/retract #{id} :#{namespace}/#{attribute}]"
|
28
|
+
else
|
29
|
+
"[:db/retract #{id} :#{namespace}/#{attribute} #{TypesLogic.to_datomic_value(value)}]"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.transactions_to_edn(namespace, transactions)
|
36
|
+
edn = transactions.map do |transaction|
|
37
|
+
attributes = transaction.map.with_index do |(attribute, value), i|
|
38
|
+
ident = if %i[_id _temporary_id].include?(attribute)
|
39
|
+
':db/id'
|
40
|
+
else
|
41
|
+
":#{namespace}/#{attribute}"
|
42
|
+
end
|
43
|
+
|
44
|
+
"#{i.zero? ? '' : ' '}#{ident} #{TypesLogic.to_datomic_value(value)}"
|
45
|
+
end.join("\n")
|
46
|
+
|
47
|
+
"{#{attributes}}"
|
48
|
+
end.join("\n ")
|
49
|
+
|
50
|
+
"[#{edn}]"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/logic/types.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'bigdecimal'
|
5
|
+
|
6
|
+
module Flare
|
7
|
+
module TypesLogic
|
8
|
+
def self.to_datomic_value(value)
|
9
|
+
case value
|
10
|
+
when String
|
11
|
+
to_datomic_string(value)
|
12
|
+
when Integer
|
13
|
+
value.to_s
|
14
|
+
when Float
|
15
|
+
value.to_s
|
16
|
+
when BigDecimal
|
17
|
+
# https://github.com/relevance/edn-ruby/blob/master/lib/edn/core_ext.rb#L25
|
18
|
+
"#{value.to_s('F')}M"
|
19
|
+
when Integer, Float, TrueClass, FalseClass
|
20
|
+
value.to_s
|
21
|
+
when Time, DateTime, Date
|
22
|
+
to_datomic_instant(value)
|
23
|
+
when Symbol
|
24
|
+
":#{value}"
|
25
|
+
when Array
|
26
|
+
'[' + value.map { |v| to_datomic_value(v) }.join(' ') + ']'
|
27
|
+
when NilClass
|
28
|
+
'nil'
|
29
|
+
when Hash
|
30
|
+
raise ArgumentError, "Missing :_id for reference: #{value.class}" unless value.key?(:_id)
|
31
|
+
|
32
|
+
"{:db/id #{value[:_id]}}"
|
33
|
+
else
|
34
|
+
raise ArgumentError, "Unsupported value type: #{value.class}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.to_datomic_string(value)
|
39
|
+
# https://github.com/relevance/edn-ruby/blob/master/lib/edn/core_ext.rb#L36
|
40
|
+
array = value.chars.map do |ch|
|
41
|
+
if %w[" \\].include?(ch)
|
42
|
+
"\\#{ch}"
|
43
|
+
else
|
44
|
+
ch
|
45
|
+
end
|
46
|
+
end
|
47
|
+
"\"#{array.join}\""
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.to_datomic_instant(value)
|
51
|
+
time = case value
|
52
|
+
when Time
|
53
|
+
value
|
54
|
+
when Date
|
55
|
+
value.to_time
|
56
|
+
end
|
57
|
+
|
58
|
+
"#inst \"#{time.utc.strftime('%Y-%m-%dT%H:%M:%S.%L%:z')}\""
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.ruby_to_datomic_type(ruby_type)
|
62
|
+
case ruby_type
|
63
|
+
when :string then ':db.type/string'
|
64
|
+
when :long then ':db.type/long'
|
65
|
+
when :boolean then ':db.type/boolean'
|
66
|
+
when :double then ':db.type/double'
|
67
|
+
when :instant then ':db.type/instant'
|
68
|
+
when :keyword then ':db.type/keyword'
|
69
|
+
when :uuid then ':db.type/uuid'
|
70
|
+
when :ref then ':db.type/ref'
|
71
|
+
when :bigdec then ':db.type/bigdec'
|
72
|
+
when :bigint then ':db.type/bigint'
|
73
|
+
when :uri then ':db.type/uri'
|
74
|
+
else
|
75
|
+
raise ArgumentError, "Unknown type: #{ruby_type}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.ruby_to_datomic_cardinality(cardinality)
|
80
|
+
case cardinality
|
81
|
+
when :one then ':db.cardinality/one'
|
82
|
+
when :many then ':db.cardinality/many'
|
83
|
+
else
|
84
|
+
raise ArgumentError, "Unknown cardinality: #{cardinality}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.ruby_to_datomic_unique(unique_type)
|
89
|
+
case unique_type
|
90
|
+
when :identity then ':db.unique/identity'
|
91
|
+
when :value then ':db.unique/value'
|
92
|
+
when nil then nil
|
93
|
+
else
|
94
|
+
raise ArgumentError, "Unknown uniqueness constraint: #{unique_type}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.datomic_to_ruby_type(datomic_type)
|
99
|
+
datomic_type = ":#{datomic_type}" unless datomic_type.start_with?(':')
|
100
|
+
case datomic_type
|
101
|
+
when ':db.type/bigdec' then :bigdec
|
102
|
+
when ':db.type/bigint' then :bigint
|
103
|
+
when ':db.type/boolean' then :boolean
|
104
|
+
when ':db.type/bytes' then :bytes
|
105
|
+
when ':db.type/double' then :double
|
106
|
+
when ':db.type/float' then :float
|
107
|
+
when ':db.type/instant' then :instant
|
108
|
+
when ':db.type/keyword' then :keyword
|
109
|
+
when ':db.type/long' then :long
|
110
|
+
when ':db.type/ref' then :ref
|
111
|
+
when ':db.type/string' then :string
|
112
|
+
when ':db.type/symbol' then :symbol
|
113
|
+
when ':db.type/tuple' then :tuple
|
114
|
+
when ':db.type/uuid' then :uuid
|
115
|
+
when ':db.type/uri' then :uri
|
116
|
+
else
|
117
|
+
raise ArgumentError, "Unknown Datomic type: #{datomic_type}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.datomic_to_ruby_unique(datomic_unique)
|
122
|
+
datomic_unique = ":#{datomic_unique}" unless datomic_unique.start_with?(':')
|
123
|
+
case datomic_unique
|
124
|
+
when ':db.unique/value' then :value
|
125
|
+
when ':db.unique/identity' then :identity
|
126
|
+
else
|
127
|
+
raise ArgumentError, "Unknown Datomic uniqueness: #{datomic_unique}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.datomic_to_ruby_cardinality(datomic_cardinality)
|
132
|
+
datomic_cardinality = ":#{datomic_cardinality}" unless datomic_cardinality.start_with?(':')
|
133
|
+
case datomic_cardinality
|
134
|
+
when ':db.cardinality/one' then :one
|
135
|
+
when ':db.cardinality/many' then :many
|
136
|
+
else
|
137
|
+
raise ArgumentError, "Unknown Datomic cardinality: #{datomic_cardinality}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/ports/cli.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dotenv/load'
|
4
|
+
|
5
|
+
require_relative '../controllers/documentation/generator'
|
6
|
+
|
7
|
+
module Flare
|
8
|
+
module CLI
|
9
|
+
def self.handle(command)
|
10
|
+
case command
|
11
|
+
when 'docs:generate'
|
12
|
+
Flare::Controllers::Documentation::Generator.handler
|
13
|
+
else
|
14
|
+
puts 'Invalid command.'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if __FILE__ == $PROGRAM_NAME
|
21
|
+
if ARGV.empty?
|
22
|
+
puts 'No command provided.'
|
23
|
+
else
|
24
|
+
Flare::CLI.handle(ARGV[0])
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uuidx'
|
4
|
+
|
5
|
+
require_relative '../../static/gem'
|
6
|
+
require_relative '../../controllers/client'
|
7
|
+
|
8
|
+
module Flare
|
9
|
+
def self.new(...)
|
10
|
+
Controllers::Client.new(...)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.uuid
|
14
|
+
Uuidx
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.version
|
18
|
+
Flare::GEM[:version]
|
19
|
+
end
|
20
|
+
end
|