-
Notifications
You must be signed in to change notification settings - Fork 21
/
api.cljc
339 lines (279 loc) · 11.3 KB
/
api.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
(ns fluree.db.json-ld.api
(:require [clojure.string :as str]
[fluree.db.conn.ipfs :as ipfs-conn]
[fluree.db.conn.file :as file-conn]
[fluree.db.conn.memory :as memory-conn]
[fluree.db.conn.remote :as remote-conn]
[fluree.json-ld :as json-ld]
#?(:clj [fluree.db.conn.s3 :as s3-conn])
[fluree.db.dbproto :as dbproto]
[fluree.db.platform :as platform]
[clojure.core.async :as async :refer [go <!]]
[fluree.db.api.query :as query-api]
[fluree.db.api.transact :as transact-api]
[fluree.db.util.core :as util]
[fluree.db.ledger.json-ld :as jld-ledger]
[fluree.db.ledger.proto :as ledger-proto]
[fluree.db.util.log :as log]
[fluree.db.query.range :as query-range]
[fluree.db.nameservice.core :as nameservice]
[fluree.db.conn.core :refer [notify-ledger]]
[fluree.db.json-ld.policy :as perm])
(:refer-clojure :exclude [merge load range exists?]))
#?(:clj (set! *warn-on-reflection* true))
(defn promise-wrap
"Wraps an async channel that will contain a response in a promise."
[port]
#?(:clj
(let [p (promise)]
(go
(let [res (<! port)]
(when (util/exception? res)
(log/error res))
(deliver p res)))
p)
:cljs
(js/Promise.
(fn [resolve reject]
(go
(let [res (<! port)]
(if (util/exception? res)
(reject res)
(resolve res))))))))
;; ledger operations
(defn connect
"Forms connection to ledger, enabling automatic pulls of new updates, event
services, index service.
Multiple connections to same endpoint will share underlying network connection.
Options include:
- did - (optional) DId information to use, if storing blocks as verifiable credentials,
or issuing queries against a permissioned database.
- write - (optional) Function to use for all writes, if empty will store in memory until a commit is performed
- read - (optional) Function to use for reads of persisted blocks/data
- commit - (optional) Function to use to write commits. If persistence desired, this must be defined
- push - (optional) Function(s) in a vector that will attempt to push the commit to naming service(s)
"
[{:keys [method parallelism remote-servers] :as opts}]
;; TODO - do some validation
(promise-wrap
(let [opts* (assoc opts :parallelism (or parallelism 4))
method* (cond
method (keyword method)
remote-servers :remote
:else (throw (ex-info (str "No Fluree connection method type specified in configuration: " opts)
{:status 500 :error :db/invalid-configuration})))]
(case method*
:remote (remote-conn/connect opts*)
:ipfs (ipfs-conn/connect opts*)
:file (if platform/BROWSER
(throw (ex-info "File connection not supported in the browser" opts))
(file-conn/connect opts*))
:memory (memory-conn/connect opts*)
:s3 #?(:clj (s3-conn/connect opts*)
:cljs (throw (ex-info "S3 connections not yet supported in ClojureScript"
{:status 400, :error :db/unsupported-operation})))))))
(defn connect-file
[opts]
(connect (assoc opts :method :file)))
(defn connect-ipfs
"Forms an ipfs connection using default settings.
- server - (optional) IPFS http api server endpoint, defaults to http://127.0.0.1:5001/
- profile - (optional) IPFS stored profile to use.
- did - (optional) DId information to use, if storing blocks as verifiable credentials"
[opts]
(connect (assoc opts :method :ipfs)))
(defn connect-memory
"Forms an in-memory connection using default settings.
- did - (optional) DId information to use, if storing blocks as verifiable credentials"
[opts]
(connect (assoc opts :method :memory)))
(defn address?
"Returns true if the argument is a full ledger address, false if it is just an
alias."
[ledger-alias-or-address]
(str/starts-with? ledger-alias-or-address "fluree:"))
(defn create
"Creates a new json-ld ledger. A connection (conn)
must always be supplied.
Ledger-name (optional) is a friendly name that is used for:
- When publishing to a naming service that allows multiple pointers for the
same namespace (e.g. IPNS), this becomes a sub-directory off the namespace.
For multple directories deep, use '/' for a
e.g. the ledgers movies/popular, books/authors, books/best-sellers could
use the same IPNS id (in this example using IPNS DNSLink):
fluree:ipns://my.dns.com/books/authors
fluree:ipns://my.dns.com/books/best-sellers
fluree:ipns://my.dns.com/movies/top-rated
- When combining multiple ledgers, each ledger becomes an individual named
graph which can be referenced by name.
Options map (opts) can include:
- did - DId information to use, if storing blocks as verifiable credentials"
([conn] (create conn nil nil))
([conn ledger-alias] (create conn ledger-alias nil))
([conn ledger-alias opts]
(promise-wrap
(do
(log/info "Creating ledger" ledger-alias)
(jld-ledger/create conn ledger-alias opts)))))
(defn alias->address
"Returns a core.async channel with the connection-specific address of the
given ledger-alias."
[conn ledger-alias]
(log/debug "Looking up address for ledger alias" ledger-alias)
(nameservice/primary-address conn ledger-alias nil))
(defn load
"Loads an existing ledger by its alias (which will be converted to a
connection-specific address first)."
[conn alias-or-address]
(promise-wrap
(jld-ledger/load conn alias-or-address)))
(defn exists?
"Returns a promise with true if the ledger alias or address exists, false
otherwise."
[conn ledger-alias-or-address]
(promise-wrap
(go
(let [address (if (address? ledger-alias-or-address)
ledger-alias-or-address
(<! (alias->address conn ledger-alias-or-address)))]
(log/debug "exists? - ledger address:" address)
(<! (nameservice/exists? conn address))))))
(defn notify
"Notifies the connection with a new commit map (parsed JSON commit with string keys).
If the connection knows of the ledger, and is currently maintaining
an in-memory version of the ledger, will attempt to update the db if the commit
is for the next 't' value. If a commit is for a past 't' value, noop.
If commit is for a future 't' value, will drop in-memory ledger for reload upon next request."
[conn commit-map]
(promise-wrap
(if (map? commit-map)
(notify-ledger conn commit-map)
(go
(ex-info (str "Invalid commit map, perhaps it is JSON that needs to be parsed first?: " commit-map)
{:status 400 :error :db/invalid-commit-map})))))
(defn index
"Performs indexing operation on the specified ledger"
[ledger])
;; MAYBE CHALLENGE?
(defn validate
"Validates a ledger, checks block integrity along with signatures."
[])
(defn pull
"Checks name service for ledger and pulls latest version locally."
[])
(defn combine
"Combines multiple ledgers into a new, read-only ledger."
[])
(defn stage
"Performs a transaction and queues change if valid (does not commit)"
([db json-ld] (stage db json-ld nil))
([db json-ld opts]
(let [result-ch (transact-api/stage db json-ld opts)]
(promise-wrap result-ch))))
(defn commit!
"Commits a staged database to the ledger with all changes since the last commit
aggregated together.
Commits are tracked in the local environment, but if the ledger is distributed
it will still need a 'push' to ensure it is published and verified as per the
distributed rules."
([ledger db]
(promise-wrap
(ledger-proto/-commit! ledger db)))
([ledger db opts]
(promise-wrap
(ledger-proto/-commit! ledger db opts))))
(defn transact!
[conn txn]
(promise-wrap
(transact-api/transact! conn txn)))
(defn create-with-txn
[conn txn]
(promise-wrap
(transact-api/create-with-txn conn txn)))
(defn status
"Returns current status of ledger branch."
([ledger] (ledger-proto/-status ledger))
([ledger branch] (ledger-proto/-status ledger branch)))
(defn push
"Pushes all commits since last push to a name service, e.g. a Fluree Network, IPNS, DNS, Fluree Nexus.
Depending on consensus requirements for a Fluree Network, will accept or reject push as newest update."
[])
(defn squash
"Squashes multiple unpublished commits into a single unpublished commit"
[])
(defn merge
"Merges changes from one branch into another branch."
[])
(defn branch
"Creates a new branch of a given ledger"
[])
;; db operations
(defn db
"Retrieves latest db, or optionally a db at a moment in time
and/or permissioned to a specific identity."
([ledger] (db ledger nil))
([ledger opts]
(if opts
(throw (ex-info "DB opts not yet implemented"
{:status 500 :error :db/unexpected-error}))
(ledger-proto/-db ledger opts))))
(defn wrap-policy
"Wraps a db object with specified permission attributes.
When requesting a db from a ledger, permission attributes can
be requested at that point, however if one has a db already, this
allows the permission attributes to be modified.
Returns promise"
([db identity-map]
(wrap-policy db identity-map nil))
([db identity-map context]
(promise-wrap
(let [parsed-ctx (json-ld/parse-context context)
policy-id (perm/parse-policy-identity identity-map parsed-ctx)]
(perm/wrap-policy db policy-id)))))
(defn query
"Queries a db value and returns a promise with the results."
([db q] (query db q {}))
([db q opts]
(promise-wrap (query-api/query db q opts))))
(defn query-connection
"Queries the latest db in the ledger specified by the 'from' parameter in the
query (what that actually looks like is format-specific). Returns a promise
with the results."
([conn q] (query-connection conn q {}))
([conn q opts]
(promise-wrap (query-api/query-connection conn q opts))))
(defn history
"Return the change history over a specified time range. Optionally include the commit
that produced the changes."
[ledger query]
(let [latest-db (ledger-proto/-db ledger)
res-chan (query-api/history latest-db query)]
(promise-wrap res-chan)))
(defn range
"Performs a range scan against the specified index using test functions
of >=, <=, >, <"
;; TODO - assert index is valid index type
([db index test match]
(promise-wrap
(query-range/index-range db index test match)))
([db index start-test start-match end-test end-match]
(promise-wrap
(query-range/index-range db index start-test start-match end-test end-match))))
(defn slice
"Like range, but returns all flakes that match the supplied flake parts."
[db index match]
(promise-wrap
(query-range/index-range db index = match)))
(defn expand-iri
"Expands given IRI with the default database context, or provided context."
([context compact-iri]
(json-ld/expand-iri compact-iri
(json-ld/parse-context context))))
(defn internal-id
"Returns the internal Fluree integer id for a given IRI.
This can be used for doing range scans, slices and for other
more advanced needs.
Returns promise"
[db iri]
(promise-wrap
(dbproto/-subid db iri)))