rdflib 2.2.32 → 2.2.33
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.
- package/dist/rdflib.min.js +1 -1
- package/dist/rdflib.min.js.map +1 -1
- package/esm/serializer.js +1 -1
- package/esm/update-manager.js +122 -67
- package/lib/serializer.js +1 -1
- package/lib/update-manager.d.ts +24 -4
- package/lib/update-manager.js +122 -67
- package/package.json +2 -1
- package/src/serializer.js +1 -1
- package/src/update-manager.ts +240 -169
package/src/update-manager.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/* @file Update Manager Class
|
|
2
2
|
**
|
|
3
|
-
** 2007-07-15
|
|
3
|
+
** 2007-07-15 original SPARQL Update module by Joe Presbrey <presbrey@mit.edu>
|
|
4
4
|
** 2010-08-08 TimBL folded in Kenny's WEBDAV
|
|
5
|
-
** 2010-12-07 TimBL
|
|
5
|
+
** 2010-12-07 TimBL added local file write code
|
|
6
6
|
*/
|
|
7
7
|
import IndexedFormula from './store'
|
|
8
|
-
import {docpart, join as uriJoin} from './uri'
|
|
9
|
-
import Fetcher, {Options} from './fetcher'
|
|
8
|
+
import { docpart, join as uriJoin } from './uri'
|
|
9
|
+
import Fetcher, { Options } from './fetcher'
|
|
10
10
|
import Namespace from './namespace'
|
|
11
11
|
import Serializer from './serializer'
|
|
12
|
-
import {isBlankNode, isStore} from './utils/terms'
|
|
12
|
+
import { isBlankNode, isStore } from './utils/terms'
|
|
13
13
|
import * as Util from './utils-js'
|
|
14
14
|
import Statement from './statement'
|
|
15
15
|
import RDFlibNamedNode from './named-node'
|
|
16
|
-
import {termValue} from './utils/termValue'
|
|
17
|
-
import {BlankNode, NamedNode, Quad, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term,} from './tf-types'
|
|
16
|
+
import { termValue } from './utils/termValue'
|
|
17
|
+
import { BlankNode, NamedNode, Quad, Quad_Graph, Quad_Object, Quad_Predicate, Quad_Subject, Term, } from './tf-types'
|
|
18
18
|
|
|
19
19
|
interface UpdateManagerFormula extends IndexedFormula {
|
|
20
20
|
fetcher: Fetcher
|
|
@@ -45,7 +45,7 @@ export default class UpdateManager {
|
|
|
45
45
|
/**
|
|
46
46
|
* @param store - The quadstore to store data and metadata. Created if not passed.
|
|
47
47
|
*/
|
|
48
|
-
constructor
|
|
48
|
+
constructor(store?: IndexedFormula) {
|
|
49
49
|
store = store || new IndexedFormula()
|
|
50
50
|
if (store.updater) {
|
|
51
51
|
throw new Error("You can't have two UpdateManagers for the same store")
|
|
@@ -70,65 +70,65 @@ export default class UpdateManager {
|
|
|
70
70
|
this.patchControl = []
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
patchControlFor
|
|
73
|
+
patchControlFor(doc: NamedNode) {
|
|
74
74
|
if (!this.patchControl[doc.value]) {
|
|
75
75
|
this.patchControl[doc.value] = []
|
|
76
76
|
}
|
|
77
77
|
return this.patchControl[doc.value]
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
isHttpUri(uri:string){
|
|
81
|
-
return(
|
|
80
|
+
isHttpUri(uri: string) {
|
|
81
|
+
return (uri.slice(0, 4) === 'http')
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** Remove from the store HTTP authorization metadata
|
|
85
|
-
* The
|
|
86
|
-
* of the results of previous HTTP transactions.
|
|
87
|
-
* the user logs in, then that data misrepresents what would happen
|
|
88
|
-
* if the user tried again.
|
|
89
|
-
*/
|
|
90
|
-
flagAuthorizationMetadata
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
/** Remove from the store HTTP authorization metadata
|
|
85
|
+
* The editable function below relies on copies we have in the store
|
|
86
|
+
* of the results of previous HTTP transactions. However, when
|
|
87
|
+
* the user logs in, then that data misrepresents what would happen
|
|
88
|
+
* if the user tried again.
|
|
89
|
+
*/
|
|
90
|
+
flagAuthorizationMetadata(kb?: IndexedFormula) {
|
|
91
|
+
if (!kb) {
|
|
92
|
+
kb = this.store
|
|
93
|
+
}
|
|
94
|
+
const meta = kb.fetcher?.appNode
|
|
95
|
+
const requests = kb.statementsMatching(undefined, this.ns.link('requestedURI'), undefined, meta).map(st => st.subject)
|
|
96
|
+
for (const request of requests) {
|
|
97
|
+
const response = kb.any(request, this.ns.link('response'), null, meta) as Quad_Subject
|
|
98
|
+
if (response !== undefined) { // ts
|
|
99
|
+
kb.add(response, this.ns.link('outOfDate'), true as any, meta) // @@ Boolean is fine - fix types
|
|
100
|
+
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
|
-
}
|
|
103
103
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Tests whether a file is editable.
|
|
106
|
+
* If the file has a specific annotation that it is machine written,
|
|
107
|
+
* for safety, it is editable (this doesn't actually check for write access)
|
|
108
|
+
* If the file has wac-allow and accept patch headers, those are respected.
|
|
109
|
+
* and local write access is determined by those headers.
|
|
110
|
+
* This async version not only looks at past HTTP requests, it also makes new ones if necessary.
|
|
111
|
+
*
|
|
112
|
+
* @returns The method string N3PATCH or SPARQL or DAV or
|
|
113
|
+
* LOCALFILE or false if known, undefined if not known.
|
|
114
|
+
*/
|
|
115
|
+
async checkEditable(uri: string | NamedNode, kb?: IndexedFormula): Promise<string | boolean | undefined> {
|
|
116
|
+
if (!uri) {
|
|
117
|
+
return false // Eg subject is bnode, no known doc to write to
|
|
118
|
+
}
|
|
119
|
+
if (!kb) {
|
|
120
|
+
kb = this.store
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
const initial = this.editable(uri, kb)
|
|
124
|
+
if (initial !== undefined) {
|
|
125
|
+
return initial
|
|
126
|
+
}
|
|
127
|
+
await kb.fetcher?.load(uri)
|
|
128
|
+
const final = this.editable(uri, kb)
|
|
129
|
+
// console.log(`Loaded ${uri} just to check editable, result: ${final}.`)
|
|
130
|
+
return final
|
|
131
|
+
}
|
|
132
132
|
/**
|
|
133
133
|
* Tests whether a file is editable.
|
|
134
134
|
* If the file has a specific annotation that it is machine written,
|
|
@@ -140,7 +140,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
140
140
|
* @returns The method string SPARQL or DAV or
|
|
141
141
|
* LOCALFILE or false if known, undefined if not known.
|
|
142
142
|
*/
|
|
143
|
-
editable
|
|
143
|
+
editable(uri: string | NamedNode, kb?: IndexedFormula): string | boolean | undefined {
|
|
144
144
|
if (!uri) {
|
|
145
145
|
return false // Eg subject is bnode, no known doc to write to
|
|
146
146
|
}
|
|
@@ -149,11 +149,11 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
149
149
|
}
|
|
150
150
|
uri = termValue(uri)
|
|
151
151
|
|
|
152
|
-
if (
|
|
152
|
+
if (!this.isHttpUri(uri as string)) {
|
|
153
153
|
if (kb.holds(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
kb.rdfFactory.namedNode(uri),
|
|
155
|
+
kb.rdfFactory.namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
|
|
156
|
+
kb.rdfFactory.namedNode('http://www.w3.org/2007/ont/link#MachineEditableDocument'))) {
|
|
157
157
|
return 'LOCALFILE'
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -163,7 +163,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
163
163
|
const meta = kb.fetcher?.appNode
|
|
164
164
|
// const kb = s
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
// @ts-ignore passes a string to kb.each, which expects a term. Should this work?
|
|
167
167
|
var requests = kb.each(undefined, this.ns.link('requestedURI'), docpart(uri), meta)
|
|
168
168
|
var method: string
|
|
169
169
|
for (var r = 0; r < requests.length; r++) {
|
|
@@ -179,7 +179,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
179
179
|
if (wacAllow) {
|
|
180
180
|
for (var bit of wacAllow.split(',')) {
|
|
181
181
|
var lr = bit.split('=')
|
|
182
|
-
if (lr[0].includes('user') && !lr[1].includes('write') && !lr[1].includes('append')
|
|
182
|
+
if (lr[0].includes('user') && !lr[1].includes('write') && !lr[1].includes('append')) {
|
|
183
183
|
// console.log(' editable? excluded by WAC-Allow: ', wacAllow)
|
|
184
184
|
return false
|
|
185
185
|
}
|
|
@@ -189,6 +189,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
189
189
|
if (acceptPatch.length) {
|
|
190
190
|
for (let i = 0; i < acceptPatch.length; i++) {
|
|
191
191
|
method = acceptPatch[i].value.trim()
|
|
192
|
+
if (method.indexOf('text/n3') >= 0) return 'N3PATCH'
|
|
192
193
|
if (method.indexOf('application/sparql-update') >= 0) return 'SPARQL'
|
|
193
194
|
if (method.indexOf('application/sparql-update-single-match') >= 0) return 'SPARQL'
|
|
194
195
|
}
|
|
@@ -206,8 +207,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
if (
|
|
210
|
-
if(
|
|
210
|
+
if (!this.isHttpUri(uri as string)) {
|
|
211
|
+
if (!wacAllow) return false;
|
|
211
212
|
else return 'LOCALFILE';
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -237,13 +238,15 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
237
238
|
return undefined // We don't know (yet) as we haven't had a response (yet)
|
|
238
239
|
}
|
|
239
240
|
|
|
240
|
-
anonymize
|
|
241
|
-
|
|
241
|
+
anonymize(obj) {
|
|
242
|
+
let anonymized = (obj.toNT().substr(0, 2) === '_:' && this.mentioned(obj))
|
|
242
243
|
? '?' + obj.toNT().substr(2)
|
|
243
|
-
: obj.toNT()
|
|
244
|
+
: obj.toNT();
|
|
245
|
+
|
|
246
|
+
return anonymized;
|
|
244
247
|
}
|
|
245
248
|
|
|
246
|
-
anonymizeNT
|
|
249
|
+
anonymizeNT(stmt: Quad) {
|
|
247
250
|
return this.anonymize(stmt.subject) + ' ' +
|
|
248
251
|
this.anonymize(stmt.predicate) + ' ' +
|
|
249
252
|
this.anonymize(stmt.object) + ' .'
|
|
@@ -257,7 +260,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
257
260
|
* Returns a list of all bnodes occurring in a statement
|
|
258
261
|
* @private
|
|
259
262
|
*/
|
|
260
|
-
statementBnodes
|
|
263
|
+
statementBnodes(st: Quad): BlankNode[] {
|
|
261
264
|
return [st.subject, st.predicate, st.object].filter(function (x) {
|
|
262
265
|
return isBlankNode(x)
|
|
263
266
|
}) as BlankNode[]
|
|
@@ -267,7 +270,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
267
270
|
* Returns a list of all bnodes occurring in a list of statements
|
|
268
271
|
* @private
|
|
269
272
|
*/
|
|
270
|
-
statementArrayBnodes
|
|
273
|
+
statementArrayBnodes(sts: ReadonlyArray<Quad>) {
|
|
271
274
|
var bnodes: BlankNode[] = []
|
|
272
275
|
for (let i = 0; i < sts.length; i++) {
|
|
273
276
|
bnodes = bnodes.concat(this.statementBnodes(sts[i]))
|
|
@@ -286,7 +289,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
286
289
|
* Makes a cached list of [Inverse-]Functional properties
|
|
287
290
|
* @private
|
|
288
291
|
*/
|
|
289
|
-
cacheIfps
|
|
292
|
+
cacheIfps() {
|
|
290
293
|
this.ifps = {}
|
|
291
294
|
var a = this.store.each(undefined, this.ns.rdf('type'),
|
|
292
295
|
this.ns.owl('InverseFunctionalProperty'))
|
|
@@ -304,7 +307,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
304
307
|
* Returns a context to bind a given node, up to a given depth
|
|
305
308
|
* @private
|
|
306
309
|
*/
|
|
307
|
-
bnodeContext2
|
|
310
|
+
bnodeContext2(x, source, depth) {
|
|
308
311
|
// Return a list of statements which indirectly identify a node
|
|
309
312
|
// Depth > 1 if try further indirection.
|
|
310
313
|
// Return array of statements (possibly empty), or null if failure
|
|
@@ -315,12 +318,12 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
315
318
|
if (this.fps[sts[i].predicate.value]) {
|
|
316
319
|
y = sts[i].subject
|
|
317
320
|
if (!y.isBlank) {
|
|
318
|
-
return [
|
|
321
|
+
return [sts[i]]
|
|
319
322
|
}
|
|
320
323
|
if (depth) {
|
|
321
324
|
res = this.bnodeContext2(y, source, depth - 1)
|
|
322
325
|
if (res) {
|
|
323
|
-
return res.concat([
|
|
326
|
+
return res.concat([sts[i]])
|
|
324
327
|
}
|
|
325
328
|
}
|
|
326
329
|
}
|
|
@@ -331,12 +334,12 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
331
334
|
if (this.ifps[sts[i].predicate.value]) {
|
|
332
335
|
y = sts[i].object
|
|
333
336
|
if (!y.isBlank) {
|
|
334
|
-
return [
|
|
337
|
+
return [sts[i]]
|
|
335
338
|
}
|
|
336
339
|
if (depth) {
|
|
337
340
|
res = this.bnodeContext2(y, source, depth - 1)
|
|
338
341
|
if (res) {
|
|
339
|
-
return res.concat([
|
|
342
|
+
return res.concat([sts[i]])
|
|
340
343
|
}
|
|
341
344
|
}
|
|
342
345
|
}
|
|
@@ -348,7 +351,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
348
351
|
* Returns the smallest context to bind a given single bnode
|
|
349
352
|
* @private
|
|
350
353
|
*/
|
|
351
|
-
bnodeContext1
|
|
354
|
+
bnodeContext1(x, source) {
|
|
352
355
|
// Return a list of statements which indirectly identify a node
|
|
353
356
|
// Breadth-first
|
|
354
357
|
for (var depth = 0; depth < 3; depth++) { // Try simple first
|
|
@@ -363,7 +366,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
363
366
|
/**
|
|
364
367
|
* @private
|
|
365
368
|
*/
|
|
366
|
-
mentioned
|
|
369
|
+
mentioned(x) {
|
|
367
370
|
return this.store.statementsMatching(x, null, null, null).length !== 0 || // Don't pin fresh bnodes
|
|
368
371
|
this.store.statementsMatching(null, x).length !== 0 ||
|
|
369
372
|
this.store.statementsMatching(null, null, x).length !== 0
|
|
@@ -372,7 +375,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
372
375
|
/**
|
|
373
376
|
* @private
|
|
374
377
|
*/
|
|
375
|
-
bnodeContext
|
|
378
|
+
bnodeContext(bnodes, doc) {
|
|
376
379
|
var context = []
|
|
377
380
|
if (bnodes.length) {
|
|
378
381
|
this.cacheIfps()
|
|
@@ -389,7 +392,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
389
392
|
* Returns the best context for a single statement
|
|
390
393
|
* @private
|
|
391
394
|
*/
|
|
392
|
-
statementContext
|
|
395
|
+
statementContext(st: Quad) {
|
|
393
396
|
var bnodes = this.statementBnodes(st)
|
|
394
397
|
return this.bnodeContext(bnodes, st.graph)
|
|
395
398
|
}
|
|
@@ -397,7 +400,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
397
400
|
/**
|
|
398
401
|
* @private
|
|
399
402
|
*/
|
|
400
|
-
contextWhere
|
|
403
|
+
contextWhere(context) {
|
|
401
404
|
var updater = this
|
|
402
405
|
return (!context || context.length === 0)
|
|
403
406
|
? ''
|
|
@@ -410,7 +413,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
410
413
|
/**
|
|
411
414
|
* @private
|
|
412
415
|
*/
|
|
413
|
-
fire
|
|
416
|
+
fire(
|
|
414
417
|
uri: string,
|
|
415
418
|
query: string,
|
|
416
419
|
callbackFunction: CallBackFunction,
|
|
@@ -424,7 +427,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
424
427
|
// console.log('UpdateManager: sending update to <' + uri + '>')
|
|
425
428
|
|
|
426
429
|
options.noMeta = true;
|
|
427
|
-
options.contentType = 'application/sparql-update';
|
|
430
|
+
options.contentType = options.contentType || 'application/sparql-update';
|
|
428
431
|
options.body = query;
|
|
429
432
|
|
|
430
433
|
return this.store.fetcher.webOperation('PATCH', uri, options)
|
|
@@ -447,7 +450,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
447
450
|
})
|
|
448
451
|
}
|
|
449
452
|
|
|
450
|
-
// ARE THESE
|
|
453
|
+
// ARE THESE THREE FUNCTIONS USED? DEPRECATE?
|
|
451
454
|
|
|
452
455
|
/** return a statemnet updating function
|
|
453
456
|
*
|
|
@@ -455,7 +458,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
455
458
|
* It returns an object which includes
|
|
456
459
|
* function which can be used to change the object of the statement.
|
|
457
460
|
*/
|
|
458
|
-
update_statement
|
|
461
|
+
update_statement(statement: Quad) {
|
|
459
462
|
if (statement && !statement.graph) {
|
|
460
463
|
return
|
|
461
464
|
}
|
|
@@ -483,7 +486,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
483
486
|
}
|
|
484
487
|
}
|
|
485
488
|
|
|
486
|
-
insert_statement
|
|
489
|
+
insert_statement(st: Quad, callbackFunction: CallBackFunction): void {
|
|
487
490
|
var st0 = st instanceof Array ? st[0] : st
|
|
488
491
|
var query = this.contextWhere(this.statementContext(st0))
|
|
489
492
|
|
|
@@ -501,7 +504,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
501
504
|
this.fire(st0.graph.value, query, callbackFunction)
|
|
502
505
|
}
|
|
503
506
|
|
|
504
|
-
delete_statement
|
|
507
|
+
delete_statement(st: Quad | Quad[], callbackFunction: CallBackFunction): void {
|
|
505
508
|
var st0 = st instanceof Array ? st[0] : st
|
|
506
509
|
var query = this.contextWhere(this.statementContext(st0))
|
|
507
510
|
|
|
@@ -519,7 +522,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
519
522
|
this.fire(st0.graph.value, query, callbackFunction)
|
|
520
523
|
}
|
|
521
524
|
|
|
522
|
-
/// //////////////////////
|
|
525
|
+
/// //////////////////////
|
|
523
526
|
|
|
524
527
|
/**
|
|
525
528
|
* Requests a now or future action to refresh changes coming downstream
|
|
@@ -530,7 +533,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
530
533
|
* @param doc
|
|
531
534
|
* @param action
|
|
532
535
|
*/
|
|
533
|
-
requestDownstreamAction
|
|
536
|
+
requestDownstreamAction(doc: NamedNode, action): void {
|
|
534
537
|
var control = this.patchControlFor(doc)
|
|
535
538
|
if (!control.pendingUpstream) {
|
|
536
539
|
action(doc)
|
|
@@ -549,18 +552,18 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
549
552
|
* We want to start counting websocket notifications
|
|
550
553
|
* to distinguish the ones from others from our own.
|
|
551
554
|
*/
|
|
552
|
-
clearUpstreamCount
|
|
555
|
+
clearUpstreamCount(doc: NamedNode): void {
|
|
553
556
|
var control = this.patchControlFor(doc)
|
|
554
557
|
control.upstreamCount = 0
|
|
555
558
|
}
|
|
556
559
|
|
|
557
|
-
getUpdatesVia
|
|
560
|
+
getUpdatesVia(doc: NamedNode): string | null {
|
|
558
561
|
var linkHeaders = this.store.fetcher.getHeader(doc, 'updates-via')
|
|
559
562
|
if (!linkHeaders || !linkHeaders.length) return null
|
|
560
563
|
return linkHeaders[0].trim()
|
|
561
564
|
}
|
|
562
565
|
|
|
563
|
-
addDownstreamChangeListener
|
|
566
|
+
addDownstreamChangeListener(doc: NamedNode, listener): void {
|
|
564
567
|
var control = this.patchControlFor(doc)
|
|
565
568
|
if (!control.downstreamChangeListeners) { control.downstreamChangeListeners = [] }
|
|
566
569
|
control.downstreamChangeListeners.push(listener)
|
|
@@ -569,7 +572,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
569
572
|
})
|
|
570
573
|
}
|
|
571
574
|
|
|
572
|
-
reloadAndSync
|
|
575
|
+
reloadAndSync(doc: NamedNode): void {
|
|
573
576
|
var control = this.patchControlFor(doc)
|
|
574
577
|
var updater = this
|
|
575
578
|
|
|
@@ -591,7 +594,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
591
594
|
}
|
|
592
595
|
}
|
|
593
596
|
control.reloading = false
|
|
594
|
-
if (control.outOfDate){
|
|
597
|
+
if (control.outOfDate) {
|
|
595
598
|
// console.log(' Extra reload because of extra update.')
|
|
596
599
|
control.outOfDate = false
|
|
597
600
|
tryReload()
|
|
@@ -631,7 +634,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
631
634
|
*
|
|
632
635
|
* @returns {boolean}
|
|
633
636
|
*/
|
|
634
|
-
setRefreshHandler
|
|
637
|
+
setRefreshHandler(doc: NamedNode, handler): boolean {
|
|
635
638
|
let wssURI = this.getUpdatesVia(doc) // relative
|
|
636
639
|
// var kb = this.store
|
|
637
640
|
var theHandler = handler
|
|
@@ -672,7 +675,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
672
675
|
var control = self.patchControlFor(doc)
|
|
673
676
|
control.upstreamCount = 0
|
|
674
677
|
|
|
675
|
-
socket.onerror = function onerror
|
|
678
|
+
socket.onerror = function onerror(err: Error) {
|
|
676
679
|
// console.log('Error on Websocket:', err)
|
|
677
680
|
}
|
|
678
681
|
|
|
@@ -737,8 +740,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
737
740
|
const thisUpdater = this
|
|
738
741
|
const uniqueDocs: Array<NamedNode> = []
|
|
739
742
|
docs.forEach(doc => {
|
|
740
|
-
|
|
741
|
-
|
|
743
|
+
if (!uniqueDocs.find(uniqueDoc => uniqueDoc.equals(doc))) uniqueDocs.push(doc as NamedNode)
|
|
744
|
+
})
|
|
742
745
|
const updates = uniqueDocs.map(doc =>
|
|
743
746
|
thisUpdater.update(deletions.filter(st => st.why.equals(doc)),
|
|
744
747
|
insertions.filter(st => st.why.equals(doc))))
|
|
@@ -749,7 +752,103 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
749
752
|
}
|
|
750
753
|
|
|
751
754
|
/**
|
|
752
|
-
*
|
|
755
|
+
* @private
|
|
756
|
+
*
|
|
757
|
+
* This helper function constructs SPARQL Update query from resolved arguments.
|
|
758
|
+
*
|
|
759
|
+
* @param ds: deletions array.
|
|
760
|
+
* @param is: insertions array.
|
|
761
|
+
* @param bnodes_context: Additional context to uniquely identify any blank nodes.
|
|
762
|
+
*/
|
|
763
|
+
constructSparqlUpdateQuery(
|
|
764
|
+
ds: ReadonlyArray<Statement>,
|
|
765
|
+
is: ReadonlyArray<Statement>,
|
|
766
|
+
bnodes_context,
|
|
767
|
+
): string {
|
|
768
|
+
var whereClause = this.contextWhere(bnodes_context)
|
|
769
|
+
var query = ''
|
|
770
|
+
if (whereClause.length) { // Is there a WHERE clause?
|
|
771
|
+
if (ds.length) {
|
|
772
|
+
query += 'DELETE { '
|
|
773
|
+
for (let i = 0; i < ds.length; i++) {
|
|
774
|
+
query += this.anonymizeNT(ds[i]) + '\n'
|
|
775
|
+
}
|
|
776
|
+
query += ' }\n'
|
|
777
|
+
}
|
|
778
|
+
if (is.length) {
|
|
779
|
+
query += 'INSERT { '
|
|
780
|
+
for (let i = 0; i < is.length; i++) {
|
|
781
|
+
query += this.anonymizeNT(is[i]) + '\n'
|
|
782
|
+
}
|
|
783
|
+
query += ' }\n'
|
|
784
|
+
}
|
|
785
|
+
query += whereClause
|
|
786
|
+
} else { // no where clause
|
|
787
|
+
if (ds.length) {
|
|
788
|
+
query += 'DELETE DATA { '
|
|
789
|
+
for (let i = 0; i < ds.length; i++) {
|
|
790
|
+
query += this.anonymizeNT(ds[i]) + '\n'
|
|
791
|
+
}
|
|
792
|
+
query += ' } \n'
|
|
793
|
+
}
|
|
794
|
+
if (is.length) {
|
|
795
|
+
if (ds.length) query += ' ; '
|
|
796
|
+
query += 'INSERT DATA { '
|
|
797
|
+
for (let i = 0; i < is.length; i++) {
|
|
798
|
+
query += this.nTriples(is[i]) + '\n'
|
|
799
|
+
}
|
|
800
|
+
query += ' }\n'
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return query;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* @private
|
|
808
|
+
*
|
|
809
|
+
* This helper function constructs n3-patch query from resolved arguments.
|
|
810
|
+
*
|
|
811
|
+
* @param ds: deletions array.
|
|
812
|
+
* @param is: insertions array.
|
|
813
|
+
* @param bnodes_context: Additional context to uniquely identify any blanknodes.
|
|
814
|
+
*/
|
|
815
|
+
constructN3PatchQuery(
|
|
816
|
+
ds: ReadonlyArray<Statement>,
|
|
817
|
+
is: ReadonlyArray<Statement>,
|
|
818
|
+
bnodes_context,
|
|
819
|
+
): string {
|
|
820
|
+
var query = `
|
|
821
|
+
@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
822
|
+
@prefix ex: <http://www.example.org/terms#>.
|
|
823
|
+
|
|
824
|
+
_:patch
|
|
825
|
+
`;
|
|
826
|
+
// If bnode context is non trivial, express it as ?conditions formula.
|
|
827
|
+
if (bnodes_context && bnodes_context.length > 0) {
|
|
828
|
+
query += `
|
|
829
|
+
solid:where {
|
|
830
|
+
${bnodes_context.map((x) => this.anonymizeNT(x)).join('\n ')}
|
|
831
|
+
};`
|
|
832
|
+
}
|
|
833
|
+
if (ds.length > 0) {
|
|
834
|
+
query += `
|
|
835
|
+
solid:deletes {
|
|
836
|
+
${ds.map((x) => this.anonymizeNT(x)).join('\n ')}
|
|
837
|
+
};`
|
|
838
|
+
}
|
|
839
|
+
if (is.length > 0) {
|
|
840
|
+
query += `
|
|
841
|
+
solid:inserts {
|
|
842
|
+
${is.map((x) => this.anonymizeNT(x)).join('\n ')}
|
|
843
|
+
};`
|
|
844
|
+
}
|
|
845
|
+
query += " a solid:InsertDeletePatch .\n"
|
|
846
|
+
|
|
847
|
+
return query;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* This high-level function updates the local store if the web is changed successfully.
|
|
753
852
|
* Deletions, insertions may be undefined or single statements or lists or formulae (may contain bnodes which can be indirectly identified by a where clause).
|
|
754
853
|
* The `why` property of each statement must be the same and give the web document to be updated.
|
|
755
854
|
* @param deletions - Statement or statements to be deleted.
|
|
@@ -759,16 +858,16 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
759
858
|
* @param options - Options for the fetch call
|
|
760
859
|
*/
|
|
761
860
|
update(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
861
|
+
deletions: ReadonlyArray<Statement>,
|
|
862
|
+
insertions: ReadonlyArray<Statement>,
|
|
863
|
+
callback?: (
|
|
864
|
+
uri: string | undefined | null,
|
|
865
|
+
success: boolean,
|
|
866
|
+
errorBody?: string,
|
|
867
|
+
response?: Response | Error
|
|
868
|
+
) => void,
|
|
869
|
+
secondTry?: boolean,
|
|
870
|
+
options: Options = {}
|
|
772
871
|
): void | Promise<void> {
|
|
773
872
|
if (!callback) {
|
|
774
873
|
var thisUpdater = this
|
|
@@ -787,10 +886,10 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
787
886
|
var kb = this.store
|
|
788
887
|
var ds = !deletions ? []
|
|
789
888
|
: isStore(deletions) ? deletions.statements
|
|
790
|
-
: deletions instanceof Array ? deletions : [
|
|
889
|
+
: deletions instanceof Array ? deletions : [deletions]
|
|
791
890
|
var is = !insertions ? []
|
|
792
891
|
: isStore(insertions) ? insertions.statements
|
|
793
|
-
: insertions instanceof Array ? insertions : [
|
|
892
|
+
: insertions instanceof Array ? insertions : [insertions]
|
|
794
893
|
if (!(ds instanceof Array)) {
|
|
795
894
|
throw new Error('Type Error ' + (typeof ds) + ': ' + ds)
|
|
796
895
|
}
|
|
@@ -832,70 +931,42 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
832
931
|
})
|
|
833
932
|
})
|
|
834
933
|
|
|
835
|
-
var protocol = this.editable(doc.value, kb)
|
|
934
|
+
var protocol = this.editable(doc.value, kb);
|
|
935
|
+
|
|
836
936
|
if (protocol === false) {
|
|
837
937
|
throw new Error('Update: Can\'t make changes in uneditable ' + doc)
|
|
838
938
|
}
|
|
839
939
|
if (protocol === undefined) { // Not enough metadata
|
|
840
940
|
if (secondTry) {
|
|
841
|
-
throw new Error('Update: Loaded ' + doc + "but
|
|
941
|
+
throw new Error('Update: Loaded ' + doc + "but still can't figure out what editing protocol it supports.")
|
|
842
942
|
}
|
|
843
943
|
// console.log(`Update: have not loaded ${doc} before: loading now...`);
|
|
844
944
|
(this.store.fetcher.load(doc as NamedNode) as Promise<Response>).then(response => {
|
|
845
945
|
this.update(deletions, insertions, callback, true, options)
|
|
846
946
|
}, err => {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
947
|
+
if (err.response.status === 404) { // nonexistent files are fine
|
|
948
|
+
this.update(deletions, insertions, callback, true, options)
|
|
949
|
+
} else {
|
|
950
|
+
throw new Error(`Update: Can't get updatability status ${doc} before patching: ${err}`)
|
|
951
|
+
}
|
|
852
952
|
})
|
|
853
953
|
return
|
|
854
|
-
} else if ((protocol as string).indexOf('SPARQL') >= 0) {
|
|
954
|
+
} else if ((protocol as string).indexOf('SPARQL') >= 0 || (protocol as string).indexOf('N3PATCH') >= 0) {
|
|
955
|
+
var isSparql = (protocol as string).indexOf('SPARQL') >= 0
|
|
956
|
+
|
|
855
957
|
var bnodes: BlankNode[] = []
|
|
856
958
|
// change ReadOnly type to Mutable type
|
|
857
959
|
type Mutable<Type> = {
|
|
858
960
|
-readonly [Key in keyof Type]: Type[Key];
|
|
859
961
|
}
|
|
860
|
-
|
|
962
|
+
|
|
861
963
|
if (ds.length) bnodes = this.statementArrayBnodes(ds as Mutable<typeof ds>)
|
|
862
964
|
if (is.length) bnodes = bnodes.concat(this.statementArrayBnodes(is as Mutable<typeof is>))
|
|
863
965
|
var context = this.bnodeContext(bnodes, doc)
|
|
864
|
-
|
|
865
|
-
var query =
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
query += 'DELETE { '
|
|
869
|
-
for (let i = 0; i < ds.length; i++) {
|
|
870
|
-
query += this.anonymizeNT(ds[i]) + '\n'
|
|
871
|
-
}
|
|
872
|
-
query += ' }\n'
|
|
873
|
-
}
|
|
874
|
-
if (is.length) {
|
|
875
|
-
query += 'INSERT { '
|
|
876
|
-
for (let i = 0; i < is.length; i++) {
|
|
877
|
-
query += this.anonymizeNT(is[i]) + '\n'
|
|
878
|
-
}
|
|
879
|
-
query += ' }\n'
|
|
880
|
-
}
|
|
881
|
-
query += whereClause
|
|
882
|
-
} else { // no where clause
|
|
883
|
-
if (ds.length) {
|
|
884
|
-
query += 'DELETE DATA { '
|
|
885
|
-
for (let i = 0; i < ds.length; i++) {
|
|
886
|
-
query += this.anonymizeNT(ds[i]) + '\n'
|
|
887
|
-
}
|
|
888
|
-
query += ' } \n'
|
|
889
|
-
}
|
|
890
|
-
if (is.length) {
|
|
891
|
-
if (ds.length) query += ' ; '
|
|
892
|
-
query += 'INSERT DATA { '
|
|
893
|
-
for (let i = 0; i < is.length; i++) {
|
|
894
|
-
query += this.nTriples(is[i]) + '\n'
|
|
895
|
-
}
|
|
896
|
-
query += ' }\n'
|
|
897
|
-
}
|
|
898
|
-
}
|
|
966
|
+
|
|
967
|
+
var query = isSparql ? this.constructSparqlUpdateQuery(ds, is, context) : this.constructN3PatchQuery(ds, is, context);
|
|
968
|
+
options.contentType = isSparql ? 'application/sparql-update' : 'text/n3'
|
|
969
|
+
|
|
899
970
|
// Track pending upstream patches until they have finished their callbackFunction
|
|
900
971
|
control.pendingUpstream = control.pendingUpstream ? control.pendingUpstream + 1 : 1
|
|
901
972
|
if ('upstreamCount' in control) {
|
|
@@ -953,7 +1024,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
953
1024
|
}
|
|
954
1025
|
}
|
|
955
1026
|
|
|
956
|
-
updateDav
|
|
1027
|
+
updateDav(
|
|
957
1028
|
doc: Quad_Subject,
|
|
958
1029
|
ds,
|
|
959
1030
|
is,
|
|
@@ -971,7 +1042,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
971
1042
|
if (!response) {
|
|
972
1043
|
return null // throw "No record HTTP GET response for document: "+doc
|
|
973
1044
|
}
|
|
974
|
-
var contentType = (kb.the(response, this.ns.httph('content-type'))as Term).value
|
|
1045
|
+
var contentType = (kb.the(response, this.ns.httph('content-type')) as Term).value
|
|
975
1046
|
|
|
976
1047
|
// prepare contents of revised document
|
|
977
1048
|
let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy!
|
|
@@ -1024,7 +1095,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1024
1095
|
* @param callbackFunction
|
|
1025
1096
|
* @param options
|
|
1026
1097
|
*/
|
|
1027
|
-
updateLocalFile
|
|
1098
|
+
updateLocalFile(doc: NamedNode, ds, is, callbackFunction, options: Options = {}): void {
|
|
1028
1099
|
const kb = this.store
|
|
1029
1100
|
// console.log('Writing back to local file\n')
|
|
1030
1101
|
|
|
@@ -1032,10 +1103,10 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1032
1103
|
let newSts = kb.statementsMatching(undefined, undefined, undefined, doc).slice() // copy!
|
|
1033
1104
|
|
|
1034
1105
|
for (let i = 0; i < ds.length; i++) {
|
|
1035
|
-
Util.RDFArrayRemove(newSts, ds[
|
|
1106
|
+
Util.RDFArrayRemove(newSts, ds[i])
|
|
1036
1107
|
}
|
|
1037
1108
|
for (let i = 0; i < is.length; i++) {
|
|
1038
|
-
newSts.push(is[
|
|
1109
|
+
newSts.push(is[i])
|
|
1039
1110
|
}
|
|
1040
1111
|
// serialize to the appropriate format
|
|
1041
1112
|
var dot = doc.value.lastIndexOf('.')
|
|
@@ -1044,7 +1115,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1044
1115
|
}
|
|
1045
1116
|
var ext = doc.value.slice(dot + 1)
|
|
1046
1117
|
|
|
1047
|
-
let contentType = Fetcher.CONTENT_TYPE_BY_EXT[
|
|
1118
|
+
let contentType = Fetcher.CONTENT_TYPE_BY_EXT[ext]
|
|
1048
1119
|
if (!contentType) {
|
|
1049
1120
|
throw new Error('File extension .' + ext + ' not supported for data write')
|
|
1050
1121
|
}
|
|
@@ -1052,8 +1123,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1052
1123
|
options.body = this.serialize(doc.value, newSts, contentType);
|
|
1053
1124
|
options.contentType = contentType;
|
|
1054
1125
|
|
|
1055
|
-
kb.fetcher.webOperation('PUT', doc.value, options).then(
|
|
1056
|
-
if(!response.ok) return callbackFunction(doc.value,false,response.error)
|
|
1126
|
+
kb.fetcher.webOperation('PUT', doc.value, options).then((response) => {
|
|
1127
|
+
if (!response.ok) return callbackFunction(doc.value, false, response.error)
|
|
1057
1128
|
for (let i = 0; i < ds.length; i++) {
|
|
1058
1129
|
kb.remove(ds[i]);
|
|
1059
1130
|
}
|
|
@@ -1069,7 +1140,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1069
1140
|
*
|
|
1070
1141
|
* @returns {string}
|
|
1071
1142
|
*/
|
|
1072
|
-
serialize
|
|
1143
|
+
serialize(uri: string, data: string | Quad[], contentType: string): string {
|
|
1073
1144
|
const kb = this.store
|
|
1074
1145
|
let documentString
|
|
1075
1146
|
|
|
@@ -1152,7 +1223,7 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1152
1223
|
* @param doc {RDFlibNamedNode}
|
|
1153
1224
|
* @param callbackFunction
|
|
1154
1225
|
*/
|
|
1155
|
-
reload
|
|
1226
|
+
reload(
|
|
1156
1227
|
kb: IndexedFormula,
|
|
1157
1228
|
doc: docReloadType,
|
|
1158
1229
|
callbackFunction: (ok: boolean, message?: string, response?: Error | Response) => {} | void
|
|
@@ -1172,8 +1243,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1172
1243
|
//@ts-ignore Where does onErrorWasCalled come from?
|
|
1173
1244
|
} else if (response.onErrorWasCalled || response.status !== 200) {
|
|
1174
1245
|
// console.log(' Non-HTTP error reloading data! onErrorWasCalled=' +
|
|
1175
|
-
|
|
1176
|
-
|
|
1246
|
+
//@ts-ignore Where does onErrorWasCalled come from?
|
|
1247
|
+
// response.onErrorWasCalled + ' status: ' + response.status)
|
|
1177
1248
|
callbackFunction(false, 'Non-HTTP error reloading data: ' + body, response)
|
|
1178
1249
|
} else {
|
|
1179
1250
|
var elapsedTimeMs = Date.now() - startTime
|
|
@@ -1185,8 +1256,8 @@ flagAuthorizationMetadata (kb?: IndexedFormula) {
|
|
|
1185
1256
|
doc.reloadTimeCount += 1
|
|
1186
1257
|
|
|
1187
1258
|
// console.log(' Fetch took ' + elapsedTimeMs + 'ms, av. of ' +
|
|
1188
|
-
|
|
1189
|
-
|
|
1259
|
+
// doc.reloadTimeCount + ' = ' +
|
|
1260
|
+
// (doc.reloadTimeTotal / doc.reloadTimeCount) + 'ms.')
|
|
1190
1261
|
|
|
1191
1262
|
callbackFunction(true)
|
|
1192
1263
|
}
|