@thi.ng/rstream-query 2.1.86 → 2.1.88

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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-09T19:12:03Z
3
+ - **Last updated**: 2023-12-18T13:41:20Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/api.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/convert.js CHANGED
@@ -5,70 +5,24 @@ import { mapcat } from "@thi.ng/transducers/mapcat";
5
5
  import { pairs } from "@thi.ng/transducers/pairs";
6
6
  let NEXT_ID = 0;
7
7
  const mapBNode = (s, p, o) => {
8
- const id = `__b${NEXT_ID++}__`;
9
- return concat([[s, p, id]], asTriples(o, id));
8
+ const id = `__b${NEXT_ID++}__`;
9
+ return concat([[s, p, id]], asTriples(o, id));
10
10
  };
11
11
  const mapSubject = (subject) => ([p, o]) => {
12
- if (isArray(o)) {
13
- return mapcat((o) => isPlainObject(o)
14
- ? mapBNode(subject, p, o)
15
- : [[subject, p, o]], o);
16
- }
17
- else if (isPlainObject(o)) {
18
- return mapBNode(subject, p, o);
19
- }
20
- return [[subject, p, o]];
12
+ if (isArray(o)) {
13
+ return mapcat(
14
+ (o2) => isPlainObject(o2) ? mapBNode(subject, p, o2) : [[subject, p, o2]],
15
+ o
16
+ );
17
+ } else if (isPlainObject(o)) {
18
+ return mapBNode(subject, p, o);
19
+ }
20
+ return [[subject, p, o]];
21
+ };
22
+ const asTriples = (obj, subject) => mapcat(
23
+ subject === void 0 ? ([s, v]) => mapcat(mapSubject(s), pairs(v)) : mapSubject(subject),
24
+ pairs(obj)
25
+ );
26
+ export {
27
+ asTriples
21
28
  };
22
- /**
23
- * Converts given object into an iterable of triples, with the following
24
- * conversion rules:
25
- *
26
- * - Toplevel object keys are used as subjects and MUST each have a
27
- * plain object as value, where its keys are used as predicates and
28
- * values as objects (in the SPO sense).
29
- * - Plain objects in SPO object position are translated into unique IDs
30
- * in order to allow the nested map to become a subject itself. In RDF
31
- * terms, this is equivalent to BNodes.
32
- * - Arrays in SPO object position cause multiple triples with same
33
- * subject & predicate to be emitted. If any of the items in the array
34
- * is a plain object, it will be treated as BNode and transformed as
35
- * described in the previous rule
36
- *
37
- * @example
38
- * ```ts
39
- * src = {
40
- * "@thi.ng/rstream-query": {
41
- * type: "project",
42
- * author: "toxi",
43
- * tag: ["ES6", "TypeScript", "graph"]
44
- * },
45
- * toxi: {
46
- * type: "person",
47
- * hasAccount: [
48
- * {type: "twitter", id: "toxi"},
49
- * {type: "github", id: "postspectacular"}
50
- * ]
51
- * }
52
- * };
53
- *
54
- * [...asTriples(src)]
55
- * // [ [ '@thi.ng/rstream-query', 'type', 'project' ],
56
- * // [ '@thi.ng/rstream-query', 'author', 'toxi' ],
57
- * // [ '@thi.ng/rstream-query', 'tag', 'ES6' ],
58
- * // [ '@thi.ng/rstream-query', 'tag', 'TypeScript' ],
59
- * // [ '@thi.ng/rstream-query', 'tag', 'graph' ],
60
- * // [ 'toxi', 'type', 'person' ],
61
- * // [ 'toxi', 'hasAccount', '__b0__' ],
62
- * // [ '__b0__', 'type', 'twitter' ],
63
- * // [ '__b0__', 'id', 'toxi' ],
64
- * // [ 'toxi', 'hasAccount', '__b1__' ],
65
- * // [ '__b1__', 'type', 'github' ],
66
- * // [ '__b1__', 'id', 'postspectacular' ] ]
67
- * ```
68
- *
69
- * @param obj -
70
- * @param subject - internal use only, do not specify!
71
- */
72
- export const asTriples = (obj, subject) => mapcat(subject === undefined
73
- ? ([s, v]) => mapcat(mapSubject(s), pairs(v))
74
- : mapSubject(subject), pairs(obj));
package/logger.js CHANGED
@@ -1,3 +1,7 @@
1
1
  import { NULL_LOGGER } from "@thi.ng/logger/null";
2
- export let LOGGER = NULL_LOGGER;
3
- export const setLogger = (logger) => (LOGGER = logger);
2
+ let LOGGER = NULL_LOGGER;
3
+ const setLogger = (logger) => LOGGER = logger;
4
+ export {
5
+ LOGGER,
6
+ setLogger
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/rstream-query",
3
- "version": "2.1.86",
3
+ "version": "2.1.88",
4
4
  "description": "@thi.ng/rstream based triple store & reactive query engine",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,19 +35,20 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.11",
37
- "@thi.ng/associative": "^6.3.23",
38
- "@thi.ng/checks": "^3.4.11",
39
- "@thi.ng/equiv": "^2.1.36",
40
- "@thi.ng/errors": "^2.4.5",
41
- "@thi.ng/logger": "^2.0.1",
42
- "@thi.ng/math": "^5.7.6",
43
- "@thi.ng/rstream": "^8.2.13",
44
- "@thi.ng/rstream-dot": "^3.0.36",
45
- "@thi.ng/transducers": "^8.8.14"
38
+ "@thi.ng/api": "^8.9.13",
39
+ "@thi.ng/associative": "^6.3.25",
40
+ "@thi.ng/checks": "^3.4.13",
41
+ "@thi.ng/equiv": "^2.1.38",
42
+ "@thi.ng/errors": "^2.4.7",
43
+ "@thi.ng/logger": "^2.1.0",
44
+ "@thi.ng/math": "^5.7.8",
45
+ "@thi.ng/rstream": "^8.2.15",
46
+ "@thi.ng/rstream-dot": "^3.0.38",
47
+ "@thi.ng/transducers": "^8.8.16"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@microsoft/api-extractor": "^7.38.3",
51
+ "esbuild": "^0.19.8",
49
52
  "rimraf": "^5.0.5",
50
53
  "tools": "^0.0.1",
51
54
  "typedoc": "^0.25.4",
@@ -70,7 +73,7 @@
70
73
  "access": "public"
71
74
  },
72
75
  "engines": {
73
- "node": ">=12.7"
76
+ "node": ">=18"
74
77
  },
75
78
  "files": [
76
79
  "./*.js",
@@ -106,5 +109,5 @@
106
109
  "parent": "@thi.ng/rstream",
107
110
  "year": 2018
108
111
  },
109
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
112
+ "gitHead": "25a42a81fac8603a1e440a7aa8bc343276211ff4\n"
110
113
  }
package/pattern.js CHANGED
@@ -1,49 +1,35 @@
1
1
  import { repeatedly } from "@thi.ng/transducers/repeatedly";
2
2
  import { autoQVar, isQVar, qvarName } from "./qvar.js";
3
- export const patternVarCount = (p) => {
4
- let n = 0;
5
- if (isQVar(p[0]))
6
- n++;
7
- if (isQVar(p[1]))
8
- n++;
9
- if (isQVar(p[2]))
10
- n++;
11
- return n;
3
+ const patternVarCount = (p) => {
4
+ let n = 0;
5
+ if (isQVar(p[0]))
6
+ n++;
7
+ if (isQVar(p[1]))
8
+ n++;
9
+ if (isQVar(p[2]))
10
+ n++;
11
+ return n;
12
12
  };
13
- export const patternVars = ([s, p, o]) => {
14
- const vars = [];
15
- isQVar(s) && vars.push(qvarName(s));
16
- isQVar(p) && vars.push(qvarName(p));
17
- isQVar(o) && vars.push(qvarName(o));
18
- return vars;
13
+ const patternVars = ([s, p, o]) => {
14
+ const vars = [];
15
+ isQVar(s) && vars.push(qvarName(s));
16
+ isQVar(p) && vars.push(qvarName(p));
17
+ isQVar(o) && vars.push(qvarName(o));
18
+ return vars;
19
19
  };
20
- /**
21
- * Takes a path triple pattern and max depth. The pattern's predicate
22
- * must be a seq of preds. Returns a 2-elem vector [patterns vars],
23
- * where `patterns` is a list of generated sub-query patterns with
24
- * injected temp qvars for in between patterns and `vars` are the temp
25
- * qvars themselves.
26
- *
27
- * Example:
28
- *
29
- * ```
30
- * ["?s", [p1, p2, p3], "?o"] =>
31
- * [
32
- * [["?s", p1, "?__q0"], ["?__q0", p2, "?__q1"], ["?__q1", p3, "?o"] ],
33
- * ["?__q0", "?__q1"]
34
- * ]
35
- * ```
36
- *
37
- * @param pattern -
38
- * @param maxLen -
39
- */
40
- export const resolvePathPattern = ([s, p, o], maxLen = p.length) => {
41
- const res = [];
42
- const avars = [...repeatedly(autoQVar, maxLen - 1)];
43
- for (let i = 0; i < maxLen; i++) {
44
- res.push([s, p[i % p.length], (s = avars[i])]);
45
- }
46
- res[res.length - 1][2] = o;
47
- return [res, avars];
20
+ const resolvePathPattern = ([s, p, o], maxLen = p.length) => {
21
+ const res = [];
22
+ const avars = [...repeatedly(autoQVar, maxLen - 1)];
23
+ for (let i = 0; i < maxLen; i++) {
24
+ res.push([s, p[i % p.length], s = avars[i]]);
25
+ }
26
+ res[res.length - 1][2] = o;
27
+ return [res, avars];
28
+ };
29
+ const sortPatterns = (patterns) => patterns.sort((a, b) => patternVarCount(a) - patternVarCount(b));
30
+ export {
31
+ patternVarCount,
32
+ patternVars,
33
+ resolvePathPattern,
34
+ sortPatterns
48
35
  };
49
- export const sortPatterns = (patterns) => patterns.sort((a, b) => patternVarCount(a) - patternVarCount(b));
package/qvar.js CHANGED
@@ -1,46 +1,39 @@
1
1
  import { isString } from "@thi.ng/checks/is-string";
2
2
  const AUTO_QVAR_PREFIX = "?__q";
3
3
  let AUTO_QVAR_ID = 0;
4
- export const isQVar = (x) => isString(x) && x.charAt(0) === "?";
5
- export const isAutoQVar = (x) => isString(x) && x.indexOf(AUTO_QVAR_PREFIX) == 0;
6
- export const autoQVar = () => AUTO_QVAR_PREFIX + (AUTO_QVAR_ID++).toString(36);
7
- export const qvarName = (x) => x.substring(1);
8
- /**
9
- * Returns an optimized query variable solution extractor function based
10
- * on given pattern type. `vs`, `vp`, `vo` are flags to indicate if `s`,
11
- * `p` and/or `o` pattern items are query variables. The returned fn
12
- * will be optimized to 1 of the 8 possible case and accepts a single
13
- * fact to extract the respective variables from.
14
- *
15
- * @param vs -
16
- * @param vp -
17
- * @param vo -
18
- * @param s -
19
- * @param p -
20
- * @param o -
21
- */
22
- export const qvarResolver = (vs, vp, vo, s, p, o) => {
23
- const type = (vs << 2) | (vp << 1) | vo;
24
- let ss = vs && qvarName(s);
25
- let pp = vp && qvarName(p);
26
- let oo = vo && qvarName(o);
27
- switch (type) {
28
- case 0:
29
- default:
30
- return;
31
- case 1:
32
- return (f) => ({ [oo]: f[2] });
33
- case 2:
34
- return (f) => ({ [pp]: f[1] });
35
- case 3:
36
- return (f) => ({ [pp]: f[1], [oo]: f[2] });
37
- case 4:
38
- return (f) => ({ [ss]: f[0] });
39
- case 5:
40
- return (f) => ({ [ss]: f[0], [oo]: f[2] });
41
- case 6:
42
- return (f) => ({ [ss]: f[0], [pp]: f[1] });
43
- case 7:
44
- return (f) => ({ [ss]: f[0], [pp]: f[1], [oo]: f[2] });
45
- }
4
+ const isQVar = (x) => isString(x) && x.charAt(0) === "?";
5
+ const isAutoQVar = (x) => isString(x) && x.indexOf(AUTO_QVAR_PREFIX) == 0;
6
+ const autoQVar = () => AUTO_QVAR_PREFIX + (AUTO_QVAR_ID++).toString(36);
7
+ const qvarName = (x) => x.substring(1);
8
+ const qvarResolver = (vs, vp, vo, s, p, o) => {
9
+ const type = vs << 2 | vp << 1 | vo;
10
+ let ss = vs && qvarName(s);
11
+ let pp = vp && qvarName(p);
12
+ let oo = vo && qvarName(o);
13
+ switch (type) {
14
+ case 0:
15
+ default:
16
+ return;
17
+ case 1:
18
+ return (f) => ({ [oo]: f[2] });
19
+ case 2:
20
+ return (f) => ({ [pp]: f[1] });
21
+ case 3:
22
+ return (f) => ({ [pp]: f[1], [oo]: f[2] });
23
+ case 4:
24
+ return (f) => ({ [ss]: f[0] });
25
+ case 5:
26
+ return (f) => ({ [ss]: f[0], [oo]: f[2] });
27
+ case 6:
28
+ return (f) => ({ [ss]: f[0], [pp]: f[1] });
29
+ case 7:
30
+ return (f) => ({ [ss]: f[0], [pp]: f[1], [oo]: f[2] });
31
+ }
32
+ };
33
+ export {
34
+ autoQVar,
35
+ isAutoQVar,
36
+ isQVar,
37
+ qvarName,
38
+ qvarResolver
46
39
  };
package/store.js CHANGED
@@ -16,317 +16,354 @@ import { mapIndexed } from "@thi.ng/transducers/map-indexed";
16
16
  import { transduce } from "@thi.ng/transducers/transduce";
17
17
  import { patternVars, resolvePathPattern } from "./pattern.js";
18
18
  import { isQVar, qvarResolver } from "./qvar.js";
19
- import { bindVars, filterSolutions, indexSel, intersect2, intersect3, joinSolutions, limitSolutions, resultTriples, } from "./xforms.js";
20
- export class TripleStore {
21
- NEXT_ID;
22
- freeIDs;
23
- triples;
24
- indexS;
25
- indexP;
26
- indexO;
27
- indexSelections;
28
- queries;
29
- allIDs;
30
- streamAll;
31
- streamS;
32
- streamP;
33
- streamO;
34
- constructor(triples) {
35
- this.triples = [];
36
- this.freeIDs = [];
37
- this.queries = new Map();
38
- this.indexS = new Map();
39
- this.indexP = new Map();
40
- this.indexO = new Map();
41
- this.indexSelections = {
42
- s: new Map(),
43
- p: new Map(),
44
- o: new Map(),
45
- };
46
- this.streamS = new Stream({ id: "S", closeOut: CloseMode.NEVER });
47
- this.streamP = new Stream({ id: "P", closeOut: CloseMode.NEVER });
48
- this.streamO = new Stream({ id: "O", closeOut: CloseMode.NEVER });
49
- this.streamAll = new Stream({ id: "ALL", closeOut: CloseMode.NEVER });
50
- this.allIDs = new Set();
51
- this.NEXT_ID = 0;
52
- if (triples) {
53
- this.into(triples);
54
- }
55
- }
56
- *[Symbol.iterator]() {
57
- for (let t of this.triples) {
58
- if (t) {
59
- yield t;
60
- }
61
- }
62
- }
63
- has(t) {
64
- return this.get(t) !== undefined;
65
- }
66
- get(t, notFound) {
67
- const id = this.findTriple(this.indexS.get(t[0]), this.indexP.get(t[1]), this.indexO.get(t[2]), t);
68
- return id !== -1 ? this.triples[id] : notFound;
69
- }
70
- add(t) {
71
- let s = this.indexS.get(t[0]);
72
- let p = this.indexP.get(t[1]);
73
- let o = this.indexO.get(t[2]);
74
- if (this.findTriple(s, p, o, t) !== -1)
75
- return false;
76
- const id = this.nextID();
77
- const is = s || new Set();
78
- const ip = p || new Set();
79
- const io = o || new Set();
80
- this.triples[id] = t;
81
- is.add(id);
82
- ip.add(id);
83
- io.add(id);
84
- this.allIDs.add(id);
85
- !s && this.indexS.set(t[0], is);
86
- !p && this.indexP.set(t[1], ip);
87
- !o && this.indexO.set(t[2], io);
88
- this.broadcastTriple(is, ip, io, t);
89
- return true;
19
+ import {
20
+ bindVars,
21
+ filterSolutions,
22
+ indexSel,
23
+ intersect2,
24
+ intersect3,
25
+ joinSolutions,
26
+ limitSolutions,
27
+ resultTriples
28
+ } from "./xforms.js";
29
+ class TripleStore {
30
+ NEXT_ID;
31
+ freeIDs;
32
+ triples;
33
+ indexS;
34
+ indexP;
35
+ indexO;
36
+ indexSelections;
37
+ queries;
38
+ allIDs;
39
+ streamAll;
40
+ streamS;
41
+ streamP;
42
+ streamO;
43
+ constructor(triples) {
44
+ this.triples = [];
45
+ this.freeIDs = [];
46
+ this.queries = /* @__PURE__ */ new Map();
47
+ this.indexS = /* @__PURE__ */ new Map();
48
+ this.indexP = /* @__PURE__ */ new Map();
49
+ this.indexO = /* @__PURE__ */ new Map();
50
+ this.indexSelections = {
51
+ s: /* @__PURE__ */ new Map(),
52
+ p: /* @__PURE__ */ new Map(),
53
+ o: /* @__PURE__ */ new Map()
54
+ };
55
+ this.streamS = new Stream({ id: "S", closeOut: CloseMode.NEVER });
56
+ this.streamP = new Stream({ id: "P", closeOut: CloseMode.NEVER });
57
+ this.streamO = new Stream({ id: "O", closeOut: CloseMode.NEVER });
58
+ this.streamAll = new Stream({ id: "ALL", closeOut: CloseMode.NEVER });
59
+ this.allIDs = /* @__PURE__ */ new Set();
60
+ this.NEXT_ID = 0;
61
+ if (triples) {
62
+ this.into(triples);
90
63
  }
91
- into(triples) {
92
- let ok = true;
93
- for (let f of triples) {
94
- ok = this.add(f) && ok;
95
- }
96
- return ok;
97
- }
98
- delete(t) {
99
- let s = this.indexS.get(t[0]);
100
- let p = this.indexP.get(t[1]);
101
- let o = this.indexO.get(t[2]);
102
- const id = this.findTriple(s, p, o, t);
103
- if (id === -1)
104
- return false;
105
- s.delete(id);
106
- !s.size && this.indexS.delete(t[0]);
107
- p.delete(id);
108
- !p.size && this.indexP.delete(t[1]);
109
- o.delete(id);
110
- !o.size && this.indexO.delete(t[2]);
111
- this.allIDs.delete(id);
112
- delete this.triples[id];
113
- this.freeIDs.push(id);
114
- this.broadcastTriple(s, p, o, t);
115
- return true;
64
+ }
65
+ *[Symbol.iterator]() {
66
+ for (let t of this.triples) {
67
+ if (t) {
68
+ yield t;
69
+ }
116
70
  }
117
- /**
118
- * Replaces triple `a` with `b`, *iff* `a` is actually in the store.
119
- * Else does nothing.
120
- *
121
- * @param a -
122
- * @param b -
123
- */
124
- replace(a, b) {
125
- if (this.delete(a)) {
126
- return this.add(b);
127
- }
128
- return false;
71
+ }
72
+ has(t) {
73
+ return this.get(t) !== void 0;
74
+ }
75
+ get(t, notFound) {
76
+ const id = this.findTriple(
77
+ this.indexS.get(t[0]),
78
+ this.indexP.get(t[1]),
79
+ this.indexO.get(t[2]),
80
+ t
81
+ );
82
+ return id !== -1 ? this.triples[id] : notFound;
83
+ }
84
+ add(t) {
85
+ let s = this.indexS.get(t[0]);
86
+ let p = this.indexP.get(t[1]);
87
+ let o = this.indexO.get(t[2]);
88
+ if (this.findTriple(s, p, o, t) !== -1)
89
+ return false;
90
+ const id = this.nextID();
91
+ const is = s || /* @__PURE__ */ new Set();
92
+ const ip = p || /* @__PURE__ */ new Set();
93
+ const io = o || /* @__PURE__ */ new Set();
94
+ this.triples[id] = t;
95
+ is.add(id);
96
+ ip.add(id);
97
+ io.add(id);
98
+ this.allIDs.add(id);
99
+ !s && this.indexS.set(t[0], is);
100
+ !p && this.indexP.set(t[1], ip);
101
+ !o && this.indexO.set(t[2], io);
102
+ this.broadcastTriple(is, ip, io, t);
103
+ return true;
104
+ }
105
+ into(triples) {
106
+ let ok = true;
107
+ for (let f of triples) {
108
+ ok = this.add(f) && ok;
129
109
  }
130
- addPatternQuery(pattern, id, emitTriples = true) {
131
- let results;
132
- const [s, p, o] = pattern;
133
- if (s == null && p == null && o == null) {
134
- results = this.streamAll;
135
- }
136
- else {
137
- const key = JSON.stringify(pattern);
138
- if (!(results = this.queries.get(key))) {
139
- const qs = this.getIndexSelection(this.streamS, s, "s");
140
- const qp = this.getIndexSelection(this.streamP, p, "p");
141
- const qo = this.getIndexSelection(this.streamO, o, "o");
142
- let src;
143
- let xform = intersect2;
144
- // optimize cases with 2 null terms (only needs single intersection w/ streamAll)
145
- if (s == null && p == null) {
146
- src = { a: qo, b: qs };
147
- }
148
- else if (s == null && o == null) {
149
- src = { a: qp, b: qs };
150
- }
151
- else if (p == null && o == null) {
152
- src = { a: qs, b: qp };
153
- }
154
- else {
155
- src = { s: qs, p: qp, o: qo };
156
- xform = intersect3;
157
- }
158
- results = sync({
159
- id,
160
- src,
161
- xform,
162
- reset: true,
163
- });
164
- this.queries.set(key, results);
165
- submit(this.indexS, qs, s);
166
- submit(this.indexP, qp, p);
167
- submit(this.indexO, qo, o);
168
- }
169
- }
170
- return emitTriples ? results.transform(resultTriples(this)) : results;
110
+ return ok;
111
+ }
112
+ delete(t) {
113
+ let s = this.indexS.get(t[0]);
114
+ let p = this.indexP.get(t[1]);
115
+ let o = this.indexO.get(t[2]);
116
+ const id = this.findTriple(s, p, o, t);
117
+ if (id === -1)
118
+ return false;
119
+ s.delete(id);
120
+ !s.size && this.indexS.delete(t[0]);
121
+ p.delete(id);
122
+ !p.size && this.indexP.delete(t[1]);
123
+ o.delete(id);
124
+ !o.size && this.indexO.delete(t[2]);
125
+ this.allIDs.delete(id);
126
+ delete this.triples[id];
127
+ this.freeIDs.push(id);
128
+ this.broadcastTriple(s, p, o, t);
129
+ return true;
130
+ }
131
+ /**
132
+ * Replaces triple `a` with `b`, *iff* `a` is actually in the store.
133
+ * Else does nothing.
134
+ *
135
+ * @param a -
136
+ * @param b -
137
+ */
138
+ replace(a, b) {
139
+ if (this.delete(a)) {
140
+ return this.add(b);
171
141
  }
172
- /**
173
- * Creates a new parametric query using given pattern with at least
174
- * 1 query variable. Query vars are strings with `?` prefix. The
175
- * rest of the string is considered the variable name.
176
- *
177
- * ```
178
- * g.addParamQuery(["?a", "friend", "?b"]);
179
- * ```
180
- *
181
- * Internally, the query pattern is translated into a basic param
182
- * query with an additional result transformation to resolve the
183
- * stated query variable solutions. Returns a rstream subscription
184
- * emitting arrays of solution objects like:
185
- *
186
- * ```
187
- * [{a: "asterix", b: "obelix"}, {a: "romeo", b: "julia"}]
188
- * ```
189
- *
190
- * @param id -
191
- * @param param1 -
192
- */
193
- addParamQuery([s, p, o], id) {
194
- const vs = isQVar(s);
195
- const vp = isQVar(p);
196
- const vo = isQVar(o);
197
- const resolve = qvarResolver(vs, vp, vo, s, p, o);
198
- if (!resolve) {
199
- illegalArgs("at least 1 query variable is required in pattern");
142
+ return false;
143
+ }
144
+ addPatternQuery(pattern, id, emitTriples = true) {
145
+ let results;
146
+ const [s, p, o] = pattern;
147
+ if (s == null && p == null && o == null) {
148
+ results = this.streamAll;
149
+ } else {
150
+ const key = JSON.stringify(pattern);
151
+ if (!(results = this.queries.get(key))) {
152
+ const qs = this.getIndexSelection(this.streamS, s, "s");
153
+ const qp = this.getIndexSelection(this.streamP, p, "p");
154
+ const qo = this.getIndexSelection(this.streamO, o, "o");
155
+ let src;
156
+ let xform = intersect2;
157
+ if (s == null && p == null) {
158
+ src = { a: qo, b: qs };
159
+ } else if (s == null && o == null) {
160
+ src = { a: qp, b: qs };
161
+ } else if (p == null && o == null) {
162
+ src = { a: qs, b: qp };
163
+ } else {
164
+ src = { s: qs, p: qp, o: qo };
165
+ xform = intersect3;
200
166
  }
201
- id || (id = `query-${__nextID()}`);
202
- const query = (this.addPatternQuery([vs ? null : s, vp ? null : p, vo ? null : o], id + "-raw"));
203
- return query.transform(map((triples) => {
204
- const res = new Set();
205
- for (let f of triples) {
206
- res.add(resolve(f));
207
- }
208
- return res;
209
- }), dedupe(equiv), { id });
210
- }
211
- /**
212
- * Converts the given path pattern into a number of sub-queries and
213
- * return a rstream subscription of re-joined result solutions. If
214
- * `maxLen` is given and greater than the number of actual path
215
- * predicates, the predicates are repeated.
216
- *
217
- * @param path -
218
- * @param maxDepth -
219
- * @param id -
220
- */
221
- addPathQuery(path, len = path[1].length, id) {
222
- return this.addMultiJoin(this.addParamQueries(resolvePathPattern(path, len)[0]), patternVars(path), id);
223
- }
224
- /**
225
- * Like {@link TripleStore.addMultiJoin}, but optimized for only two
226
- * input queries. Returns a rstream subscription computing the
227
- * natural join of the given input query results.
228
- *
229
- * @param id -
230
- * @param a -
231
- * @param b -
232
- */
233
- addJoin(a, b, id) {
234
- return sync({
235
- id,
236
- src: { a, b },
237
- xform: comp(map(({ a, b }) => join(a, b)), dedupe(equiv)),
167
+ results = sync({
168
+ id,
169
+ src,
170
+ xform,
171
+ reset: true
238
172
  });
173
+ this.queries.set(key, results);
174
+ submit(this.indexS, qs, s);
175
+ submit(this.indexP, qp, p);
176
+ submit(this.indexO, qo, o);
177
+ }
239
178
  }
240
- addMultiJoin(queries, keepVars, id) {
241
- const src = transduce(mapIndexed((i, q) => [
242
- String(i),
243
- q,
244
- ]), assocObj(), queries);
245
- let xforms = [
246
- joinSolutions(Object.keys(src).length),
247
- dedupe(equiv),
248
- ];
249
- keepVars && xforms.push(filterSolutions(keepVars));
250
- return sync({
251
- id,
252
- src,
253
- xform: comp.apply(null, xforms),
254
- });
179
+ return emitTriples ? results.transform(resultTriples(this)) : results;
180
+ }
181
+ /**
182
+ * Creates a new parametric query using given pattern with at least
183
+ * 1 query variable. Query vars are strings with `?` prefix. The
184
+ * rest of the string is considered the variable name.
185
+ *
186
+ * ```
187
+ * g.addParamQuery(["?a", "friend", "?b"]);
188
+ * ```
189
+ *
190
+ * Internally, the query pattern is translated into a basic param
191
+ * query with an additional result transformation to resolve the
192
+ * stated query variable solutions. Returns a rstream subscription
193
+ * emitting arrays of solution objects like:
194
+ *
195
+ * ```
196
+ * [{a: "asterix", b: "obelix"}, {a: "romeo", b: "julia"}]
197
+ * ```
198
+ *
199
+ * @param id -
200
+ * @param param1 -
201
+ */
202
+ addParamQuery([s, p, o], id) {
203
+ const vs = isQVar(s);
204
+ const vp = isQVar(p);
205
+ const vo = isQVar(o);
206
+ const resolve = qvarResolver(vs, vp, vo, s, p, o);
207
+ if (!resolve) {
208
+ illegalArgs("at least 1 query variable is required in pattern");
255
209
  }
256
- /**
257
- * Compiles given query spec into a number of sub-queries and result
258
- * transformations. Returns rstream subscription of final result
259
- * sets. See {@link QuerySpec} docs for further details.
260
- *
261
- * @param spec -
262
- */
263
- addQueryFromSpec(spec) {
264
- let query;
265
- let curr;
266
- for (let q of spec.q) {
267
- if (isWhereQuery(q)) {
268
- curr = this.addMultiJoin(this.addParamQueries(q.where));
269
- }
270
- else if (isPathQuery(q)) {
271
- curr = this.addPathQuery(q.path);
272
- }
273
- query && curr && (curr = this.addJoin(query, curr));
274
- query = curr;
275
- }
276
- assert(!!query, "illegal query spec");
277
- let xforms = [];
278
- spec.limit && xforms.push(limitSolutions(spec.limit));
279
- spec.bind && xforms.push(bindVars(spec.bind));
280
- spec.select && xforms.push(filterSolutions(spec.select));
281
- if (xforms.length) {
282
- // @ts-ignore
283
- query = query.transform(...xforms);
210
+ id || (id = `query-${__nextID()}`);
211
+ const query = this.addPatternQuery(
212
+ [vs ? null : s, vp ? null : p, vo ? null : o],
213
+ id + "-raw"
214
+ );
215
+ return query.transform(
216
+ map((triples) => {
217
+ const res = /* @__PURE__ */ new Set();
218
+ for (let f of triples) {
219
+ res.add(resolve(f));
284
220
  }
285
- return query;
286
- }
287
- toDot(opts) {
288
- return serialize([this.streamS, this.streamP, this.streamO, this.streamAll], opts);
221
+ return res;
222
+ }),
223
+ dedupe(equiv),
224
+ { id }
225
+ );
226
+ }
227
+ /**
228
+ * Converts the given path pattern into a number of sub-queries and
229
+ * return a rstream subscription of re-joined result solutions. If
230
+ * `maxLen` is given and greater than the number of actual path
231
+ * predicates, the predicates are repeated.
232
+ *
233
+ * @param path -
234
+ * @param maxDepth -
235
+ * @param id -
236
+ */
237
+ addPathQuery(path, len = path[1].length, id) {
238
+ return this.addMultiJoin(
239
+ this.addParamQueries(resolvePathPattern(path, len)[0]),
240
+ patternVars(path),
241
+ id
242
+ );
243
+ }
244
+ /**
245
+ * Like {@link TripleStore.addMultiJoin}, but optimized for only two
246
+ * input queries. Returns a rstream subscription computing the
247
+ * natural join of the given input query results.
248
+ *
249
+ * @param id -
250
+ * @param a -
251
+ * @param b -
252
+ */
253
+ addJoin(a, b, id) {
254
+ return sync({
255
+ id,
256
+ src: { a, b },
257
+ xform: comp(
258
+ map(({ a: a2, b: b2 }) => join(a2, b2)),
259
+ dedupe(equiv)
260
+ )
261
+ });
262
+ }
263
+ addMultiJoin(queries, keepVars, id) {
264
+ const src = transduce(
265
+ mapIndexed((i, q) => [
266
+ String(i),
267
+ q
268
+ ]),
269
+ assocObj(),
270
+ queries
271
+ );
272
+ let xforms = [
273
+ joinSolutions(Object.keys(src).length),
274
+ dedupe(equiv)
275
+ ];
276
+ keepVars && xforms.push(filterSolutions(keepVars));
277
+ return sync({
278
+ id,
279
+ src,
280
+ xform: comp.apply(null, xforms)
281
+ });
282
+ }
283
+ /**
284
+ * Compiles given query spec into a number of sub-queries and result
285
+ * transformations. Returns rstream subscription of final result
286
+ * sets. See {@link QuerySpec} docs for further details.
287
+ *
288
+ * @param spec -
289
+ */
290
+ addQueryFromSpec(spec) {
291
+ let query;
292
+ let curr;
293
+ for (let q of spec.q) {
294
+ if (isWhereQuery(q)) {
295
+ curr = this.addMultiJoin(this.addParamQueries(q.where));
296
+ } else if (isPathQuery(q)) {
297
+ curr = this.addPathQuery(q.path);
298
+ }
299
+ query && curr && (curr = this.addJoin(query, curr));
300
+ query = curr;
289
301
  }
290
- nextID() {
291
- return this.freeIDs.length ? this.freeIDs.pop() : this.NEXT_ID++;
302
+ assert(!!query, "illegal query spec");
303
+ let xforms = [];
304
+ spec.limit && xforms.push(limitSolutions(spec.limit));
305
+ spec.bind && xforms.push(bindVars(spec.bind));
306
+ spec.select && xforms.push(filterSolutions(spec.select));
307
+ if (xforms.length) {
308
+ query = query.transform(...xforms);
292
309
  }
293
- broadcastTriple(s, p, o, t) {
294
- this.streamAll.next(this.allIDs);
295
- this.streamS.next({ index: s, key: t[0] });
296
- this.streamP.next({ index: p, key: t[1] });
297
- this.streamO.next({ index: o, key: t[2] });
298
- }
299
- findTriple(s, p, o, f) {
300
- if (s && p && o) {
301
- const triples = this.triples;
302
- const index = [s, p, o][min3id(s.size, p.size, o.size)];
303
- for (let id of index) {
304
- if (equiv(triples[id], f)) {
305
- return id;
306
- }
307
- }
310
+ return query;
311
+ }
312
+ toDot(opts) {
313
+ return serialize(
314
+ [this.streamS, this.streamP, this.streamO, this.streamAll],
315
+ opts
316
+ );
317
+ }
318
+ nextID() {
319
+ return this.freeIDs.length ? this.freeIDs.pop() : this.NEXT_ID++;
320
+ }
321
+ broadcastTriple(s, p, o, t) {
322
+ this.streamAll.next(this.allIDs);
323
+ this.streamS.next({ index: s, key: t[0] });
324
+ this.streamP.next({ index: p, key: t[1] });
325
+ this.streamO.next({ index: o, key: t[2] });
326
+ }
327
+ findTriple(s, p, o, f) {
328
+ if (s && p && o) {
329
+ const triples = this.triples;
330
+ const index = [s, p, o][min3id(s.size, p.size, o.size)];
331
+ for (let id of index) {
332
+ if (equiv(triples[id], f)) {
333
+ return id;
308
334
  }
309
- return -1;
335
+ }
310
336
  }
311
- getIndexSelection(stream, key, id) {
312
- if (key == null) {
313
- return this.streamAll;
314
- }
315
- let sel = this.indexSelections[id].get(key);
316
- if (!sel) {
317
- this.indexSelections[id].set(key, (sel = stream.transform(indexSel(key), { id })));
318
- }
319
- return sel;
337
+ return -1;
338
+ }
339
+ getIndexSelection(stream, key, id) {
340
+ if (key == null) {
341
+ return this.streamAll;
320
342
  }
321
- addParamQueries(patterns) {
322
- return map((q) => this.addParamQuery(q), patterns);
343
+ let sel = this.indexSelections[id].get(key);
344
+ if (!sel) {
345
+ this.indexSelections[id].set(
346
+ key,
347
+ sel = stream.transform(indexSel(key), { id })
348
+ );
323
349
  }
350
+ return sel;
351
+ }
352
+ addParamQueries(patterns) {
353
+ return map(
354
+ (q) => this.addParamQuery(q),
355
+ patterns
356
+ );
357
+ }
324
358
  }
325
359
  const submit = (index, stream, key) => {
326
- if (key != null) {
327
- const ids = index.get(key);
328
- ids && stream.next({ index: ids, key });
329
- }
360
+ if (key != null) {
361
+ const ids = index.get(key);
362
+ ids && stream.next({ index: ids, key });
363
+ }
330
364
  };
331
365
  const isWhereQuery = (q) => !!q.where;
332
366
  const isPathQuery = (q) => !!q.path;
367
+ export {
368
+ TripleStore
369
+ };
package/xforms.js CHANGED
@@ -7,62 +7,78 @@ import { compR } from "@thi.ng/transducers/compr";
7
7
  import { dedupe } from "@thi.ng/transducers/dedupe";
8
8
  import { keySelector } from "@thi.ng/transducers/key-selector";
9
9
  import { map } from "@thi.ng/transducers/map";
10
- export const intersect2 = comp(map(({ a, b }) => intersection(a, b)), dedupe(equiv));
11
- export const intersect3 = comp(map(({ s, p, o }) => intersection(intersection(s, p), o)), dedupe(equiv));
12
- export const indexSel = (key) => (rfn) => {
13
- const r = rfn[2];
14
- return compR(rfn, (acc, e) => {
15
- LOGGER.fine("index sel", e.key, key);
16
- if (equiv(e.key, key)) {
17
- return r(acc, e.index);
18
- }
19
- return acc;
20
- });
10
+ const intersect2 = comp(
11
+ map(({ a, b }) => intersection(a, b)),
12
+ dedupe(equiv)
13
+ );
14
+ const intersect3 = comp(
15
+ map(({ s, p, o }) => intersection(intersection(s, p), o)),
16
+ dedupe(equiv)
17
+ );
18
+ const indexSel = (key) => (rfn) => {
19
+ const r = rfn[2];
20
+ return compR(rfn, (acc, e) => {
21
+ LOGGER.fine("index sel", e.key, key);
22
+ if (equiv(e.key, key)) {
23
+ return r(acc, e.index);
24
+ }
25
+ return acc;
26
+ });
21
27
  };
22
- export const resultTriples = (graph) => map((ids) => {
23
- const res = new Set();
24
- for (let id of ids)
25
- res.add(graph.triples[id]);
26
- return res;
28
+ const resultTriples = (graph) => map((ids) => {
29
+ const res = /* @__PURE__ */ new Set();
30
+ for (let id of ids)
31
+ res.add(graph.triples[id]);
32
+ return res;
27
33
  });
28
- export const joinSolutions = (n) => map((src) => {
29
- let res = src[0];
30
- for (let i = 1; i < n && res.size; i++) {
31
- res = join(res, src[i]);
32
- }
33
- return res;
34
+ const joinSolutions = (n) => map((src) => {
35
+ let res = src[0];
36
+ for (let i = 1; i < n && res.size; i++) {
37
+ res = join(res, src[i]);
38
+ }
39
+ return res;
34
40
  });
35
- export const filterSolutions = (qvars) => {
36
- const filterVars = keySelector([...qvars]);
37
- return map((sol) => {
38
- const res = new Set();
39
- for (let s of sol) {
40
- res.add(filterVars(s));
41
- }
42
- return res;
43
- });
44
- };
45
- export const limitSolutions = (n) => map((sol) => {
46
- if (sol.size <= n) {
47
- return sol;
48
- }
49
- const res = new Set();
50
- let m = n;
41
+ const filterSolutions = (qvars) => {
42
+ const filterVars = keySelector([...qvars]);
43
+ return map((sol) => {
44
+ const res = /* @__PURE__ */ new Set();
51
45
  for (let s of sol) {
52
- res.add(s);
53
- if (--m <= 0)
54
- break;
46
+ res.add(filterVars(s));
55
47
  }
56
48
  return res;
49
+ });
50
+ };
51
+ const limitSolutions = (n) => map((sol) => {
52
+ if (sol.size <= n) {
53
+ return sol;
54
+ }
55
+ const res = /* @__PURE__ */ new Set();
56
+ let m = n;
57
+ for (let s of sol) {
58
+ res.add(s);
59
+ if (--m <= 0)
60
+ break;
61
+ }
62
+ return res;
57
63
  });
58
- export const bindVars = (bindings) => map((sol) => {
59
- const res = new Set();
60
- for (let s of sol) {
61
- s = { ...s };
62
- res.add(s);
63
- for (let b in bindings) {
64
- s[b] = bindings[b](s);
65
- }
64
+ const bindVars = (bindings) => map((sol) => {
65
+ const res = /* @__PURE__ */ new Set();
66
+ for (let s of sol) {
67
+ s = { ...s };
68
+ res.add(s);
69
+ for (let b in bindings) {
70
+ s[b] = bindings[b](s);
66
71
  }
67
- return res;
72
+ }
73
+ return res;
68
74
  });
75
+ export {
76
+ bindVars,
77
+ filterSolutions,
78
+ indexSel,
79
+ intersect2,
80
+ intersect3,
81
+ joinSolutions,
82
+ limitSolutions,
83
+ resultTriples
84
+ };