From b121c4761ca96decf83c52f684a7e7d1013ec123 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Wed, 25 Apr 2018 00:41:45 +0100 Subject: [PATCH] refactor(rstream-query): rename types, update readme - rename Fact => Triple, FactIds => TripleIds - rename FactGraph => TripleStore - add TripleStore.addTriples() - update TripleStore ctor --- assets/rs-query1.dot | 69 +++ assets/rs-query1.svg | 422 ++++++++++++++++++ packages/rstream-query/README.md | 73 ++- packages/rstream-query/package.json | 2 +- packages/rstream-query/src/api.ts | 4 +- packages/rstream-query/src/index.ts | 2 +- packages/rstream-query/src/pattern.ts | 16 +- .../rstream-query/src/{graph.ts => store.ts} | 111 ++--- packages/rstream-query/test/index.ts | 46 +- 9 files changed, 646 insertions(+), 99 deletions(-) create mode 100644 assets/rs-query1.dot create mode 100644 assets/rs-query1.svg rename packages/rstream-query/src/{graph.ts => store.ts} (67%) diff --git a/assets/rs-query1.dot b/assets/rs-query1.dot new file mode 100644 index 0000000000..e14e373d2c --- /dev/null +++ b/assets/rs-query1.dot @@ -0,0 +1,69 @@ +digraph g { +rankdir=LR; +node[fontname=Inconsolata,fontsize=11,style=filled,fontcolor=white]; +edge[fontname=Inconsolata,fontsize=11]; +s0[label="S\n(Stream)", color=blue]; +s1[label="s", color=black]; +s2[label="in-s", color=black]; +s3[label="", color=gray]; +s4[label="london-raw\n(StreamSync)", color=red]; +s5[label="xform-2", color=black]; +s6[label="london", color=black]; +s7[label="sub-3", color=black]; +s8[label="", color=gray]; +s9[label="P\n(Stream)", color=blue]; +s10[label="p", color=black]; +s11[label="in-p", color=black]; +s12[label="", color=gray]; +s13[label="countries-raw\n(StreamSync)", color=red]; +s14[label="xform-0", color=black]; +s15[label="countries", color=black]; +s16[label="sub-1", color=black]; +s17[label="", color=gray]; +s18[label="O\n(Stream)", color=blue]; +s19[label="o", color=black]; +s20[label="in-o", color=black]; +s21[label="", color=gray]; +s22[label="ALL\n(Stream)", color=blue]; +s23[label="s", color=black]; +s24[label="in-s", color=black]; +s25[label="", color=gray]; +s26[label="p", color=black]; +s27[label="in-p", color=black]; +s28[label="", color=gray]; +s29[label="o", color=black]; +s30[label="in-o", color=black]; +s31[label="", color=gray]; +s7 -> s8; +s6 -> s7; +s5 -> s6[label="xform"]; +s4 -> s5[label="xform"]; +s3 -> s4[label="xform"]; +s2 -> s3; +s1 -> s2[label="xform"]; +s0 -> s1[label="xform"]; +s16 -> s17; +s15 -> s16; +s14 -> s15[label="xform"]; +s13 -> s14[label="xform"]; +s12 -> s13[label="xform"]; +s11 -> s12; +s10 -> s11[label="xform"]; +s9 -> s10[label="xform"]; +s21 -> s13[label="xform"]; +s20 -> s21; +s19 -> s20[label="xform"]; +s18 -> s19[label="xform"]; +s25 -> s13[label="xform"]; +s24 -> s25; +s23 -> s24[label="xform"]; +s28 -> s4[label="xform"]; +s27 -> s28; +s26 -> s27[label="xform"]; +s31 -> s4[label="xform"]; +s30 -> s31; +s29 -> s30[label="xform"]; +s22 -> s23; +s22 -> s26; +s22 -> s29; +} \ No newline at end of file diff --git a/assets/rs-query1.svg b/assets/rs-query1.svg new file mode 100644 index 0000000000..896d4c1b84 --- /dev/null +++ b/assets/rs-query1.svg @@ -0,0 +1,422 @@ + + + + + + +g + + + +s0 + +S +(Stream) + + + +s1 + +s + + + +s0->s1 + + +xform + + + +s2 + +in-s + + + +s1->s2 + + +xform + + + +s3 + +<noid> + + + +s2->s3 + + + + + +s4 + +london-raw +(StreamSync) + + + +s3->s4 + + +xform + + + +s5 + +xform-2 + + + +s4->s5 + + +xform + + + +s6 + +london + + + +s5->s6 + + +xform + + + +s7 + +sub-3 + + + +s6->s7 + + + + + +s8 + +<noid> + + + +s7->s8 + + + + + +s9 + +P +(Stream) + + + +s10 + +p + + + +s9->s10 + + +xform + + + +s11 + +in-p + + + +s10->s11 + + +xform + + + +s12 + +<noid> + + + +s11->s12 + + + + + +s13 + +countries-raw +(StreamSync) + + + +s12->s13 + + +xform + + + +s14 + +xform-0 + + + +s13->s14 + + +xform + + + +s15 + +countries + + + +s14->s15 + + +xform + + + +s16 + +sub-1 + + + +s15->s16 + + + + + +s17 + +<noid> + + + +s16->s17 + + + + + +s18 + +O +(Stream) + + + +s19 + +o + + + +s18->s19 + + +xform + + + +s20 + +in-o + + + +s19->s20 + + +xform + + + +s21 + +<noid> + + + +s20->s21 + + + + + +s21->s13 + + +xform + + + +s22 + +ALL +(Stream) + + + +s23 + +s + + + +s22->s23 + + + + + +s26 + +p + + + +s22->s26 + + + + + +s29 + +o + + + +s22->s29 + + + + + +s24 + +in-s + + + +s23->s24 + + +xform + + + +s25 + +<noid> + + + +s24->s25 + + + + + +s25->s13 + + +xform + + + +s27 + +in-p + + + +s26->s27 + + +xform + + + +s28 + +<noid> + + + +s27->s28 + + + + + +s28->s4 + + +xform + + + +s30 + +in-o + + + +s29->s30 + + +xform + + + +s31 + +<noid> + + + +s30->s31 + + + + + +s31->s4 + + +xform + + + diff --git a/packages/rstream-query/README.md b/packages/rstream-query/README.md index 14504ce37d..f4740527bd 100644 --- a/packages/rstream-query/README.md +++ b/packages/rstream-query/README.md @@ -9,21 +9,33 @@ This project is part of the @thi.ng/rstream based [triple store](https://en.wikipedia.org/wiki/Triplestore) & reactive query -engine with high re-use ratio of sub-query results. Inserted -facts/triples are broadcast to multiple indexing streams and query +engine with high re-use ratio of sub-query results. Inserted triples / +facts are broadcast to multiple indexing streams and any query subscriptions attached to them. This enables push-based, auto-updating -query results, which are only changing if upstream transformations & -filters have been triggered. +query results, which are changing each time upstream transformations & +filters have been triggered (due to updates to the set of triples / +facts). -Unlike with traditional RDF triple stores, any JS data types can be used -as subject, predicate or object (though support for such must be -explicitly enabled). +Triples are 3-tuples of `[subject, predicate, object]`. Unlike with +traditional +[RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) +triple stores, any JS data types can be used as subject, predicate or +object (though support for such must be explicitly enabled & this +feature is currently WIP). + +### Current features + +- Entirely based on stream abstractions provided by @thi.ng/rstream +- All data transformations done using @thi.ng/tranducers +- Dynamic dataflow graph construction via high-level methods +- Extensive re-use of existing sub-query results (via subscriptions) +- Auto-updating query results ### Status -This project is currently in pre-ALPHA and intended as a continuation of -the Clojure based [thi.ng/fabric](http://thi.ng/fabric) project, this -time built on the streaming primitives provided by +This project is currently still in early development and intended as a +continuation of the Clojure based [thi.ng/fabric](http://thi.ng/fabric), +this time built on the streaming primitives provided by [@thi.ng/rstream](https://github.com/thi-ng/umbrella/tree/master/packages/rstream). ## Installation @@ -35,10 +47,47 @@ yarn add @thi.ng/rstream-query ## Usage examples ```typescript -import * as rstream-query from "@thi.ng/rstream-query"; +import { TripleStore } from "@thi.ng/rstream-query"; + +store = new TripleStore(); +store.addTriples([ + ["london", "type", "city"], + ["london", "part-of", "uk"], + ["portland", "type", "city"], + ["portland", "part-of", "oregon"], + ["portland", "part-of", "usa"], + ["oregon", "type", "state"], + ["usa", "type", "country"], + ["uk", "type", "country"], +]); + +// find all subjects with type = country +store.addParamQuery("countries", ["?country", "type", "country"]).subscribe(trace("country results:")); + +// find all relations for subject "london" +store.addParamQuery("london", ["london", "?p", "?o"]).subscribe(trace("london results:")); + +// country results: [ { country: 'usa' }, { country: 'uk' } ] +// london results: [ { p: 'type', o: 'city' }, { p: 'part-of', o: 'uk' } ] +``` +After setting up the above 2 queries, the dataflow topology then looks as follows: -``` +![graphviz output](../../assets/rs-query1.svg) + +The blue nodes are `TripleStore`-internal index stream sources, which +emit changes when new triples are added. The red nodes are basic pattern +queries, responsible for joining the individual (S)ubject, (P)redicate +and (O)bject sub-queries. The results of these are then further +transformed to bind result values to query variables, as well as the +final subscriptions to output them to the console (using `trace`, see +above). + +Btw. The diagram has been generated using +[@thi.ng/rstream-dot](https://github.com/thi-ng/umbrella/tree/master/packages/rstream) +and can be recreated by calling `store.toDot()` (for the above example) + +(Many) more features forthcoming... ## Authors diff --git a/packages/rstream-query/package.json b/packages/rstream-query/package.json index a6c57048cd..7327f61567 100644 --- a/packages/rstream-query/package.json +++ b/packages/rstream-query/package.json @@ -1,7 +1,7 @@ { "name": "@thi.ng/rstream-query", "version": "0.0.1", - "description": "TODO", + "description": "@thi.ng/rstream based triple store & reactive query engine", "main": "./index.js", "typings": "./index.d.ts", "repository": "https://github.com/thi-ng/umbrella", diff --git a/packages/rstream-query/src/api.ts b/packages/rstream-query/src/api.ts index b5a0f27f09..1fd061574c 100644 --- a/packages/rstream-query/src/api.ts +++ b/packages/rstream-query/src/api.ts @@ -1,8 +1,8 @@ export type Pattern = [any, any, any]; -export type Fact = Pattern; +export type Triple = Pattern; -export type FactIds = Set +export type TripleIds = Set export const CHOICES = Symbol("CHOICES"); diff --git a/packages/rstream-query/src/index.ts b/packages/rstream-query/src/index.ts index 8d4589cdef..cde4f0f325 100644 --- a/packages/rstream-query/src/index.ts +++ b/packages/rstream-query/src/index.ts @@ -1,4 +1,4 @@ export * from "./api"; -export * from "./graph"; export * from "./pattern"; export * from "./qvar"; +export * from "./store"; diff --git a/packages/rstream-query/src/pattern.ts b/packages/rstream-query/src/pattern.ts index 00c965568c..6a82e3d68b 100644 --- a/packages/rstream-query/src/pattern.ts +++ b/packages/rstream-query/src/pattern.ts @@ -1,6 +1,6 @@ import { repeatedly } from "@thi.ng/transducers/iter/repeatedly"; -import { Fact, Pattern } from "./api"; +import { Triple, Pattern } from "./api"; import { isQVar, autoQVar, qvarName } from "./qvar"; export const patternVarCount = (p: Pattern) => { @@ -67,18 +67,18 @@ export const qvarResolver = (vs: boolean, vp: boolean, vo: boolean, s, p, o) => default: return; case 1: - return (f: Fact) => ({ [oo]: f[2] }); + return (f: Triple) => ({ [oo]: f[2] }); case 2: - return (f: Fact) => ({ [pp]: f[1] }); + return (f: Triple) => ({ [pp]: f[1] }); case 3: - return (f: Fact) => ({ [pp]: f[1], [oo]: f[2] }); + return (f: Triple) => ({ [pp]: f[1], [oo]: f[2] }); case 4: - return (f: Fact) => ({ [ss]: f[0] }); + return (f: Triple) => ({ [ss]: f[0] }); case 5: - return (f: Fact) => ({ [ss]: f[0], [oo]: f[2] }); + return (f: Triple) => ({ [ss]: f[0], [oo]: f[2] }); case 6: - return (f: Fact) => ({ [ss]: f[0], [pp]: f[1] }); + return (f: Triple) => ({ [ss]: f[0], [pp]: f[1] }); case 7: - return (f: Fact) => ({ [ss]: f[0], [pp]: f[1], [oo]: f[2] }); + return (f: Triple) => ({ [ss]: f[0], [pp]: f[1], [oo]: f[2] }); } }; diff --git a/packages/rstream-query/src/graph.ts b/packages/rstream-query/src/store.ts similarity index 67% rename from packages/rstream-query/src/graph.ts rename to packages/rstream-query/src/store.ts index e90815abc2..bd98869b9a 100644 --- a/packages/rstream-query/src/graph.ts +++ b/packages/rstream-query/src/store.ts @@ -8,30 +8,30 @@ import { Transducer, Reducer } from "@thi.ng/transducers/api"; import { compR } from "@thi.ng/transducers/func/compr"; import { map } from "@thi.ng/transducers/xform/map"; -import { DEBUG, Edit, Fact, FactIds, Pattern } from "./api"; +import { DEBUG, Edit, Triple, TripleIds, Pattern } from "./api"; import { qvarResolver } from "./pattern"; import { isQVar } from "./qvar"; -export class FactGraph implements +export class TripleStore implements IToDot { static NEXT_ID = 0; - facts: Fact[]; - indexS: Map; - indexP: Map; - indexO: Map; - indexSelections: IObjectOf>>; - allSelections: IObjectOf>; - allIDs: FactIds; + triples: Triple[]; + indexS: Map; + indexP: Map; + indexO: Map; + indexSelections: IObjectOf>>; + allSelections: IObjectOf>; + allIDs: TripleIds; - streamAll: Stream; + streamAll: Stream; streamS: Stream; streamP: Stream; streamO: Stream; - constructor() { - this.facts = []; + constructor(triples?: Iterable) { + this.triples = []; this.indexS = new Map(); this.indexP = new Map(); this.indexO = new Map(); @@ -50,9 +50,12 @@ export class FactGraph implements "p": this.streamAll.subscribe(null, "p"), "o": this.streamAll.subscribe(null, "o") }; + if (triples) { + this.addTriples(triples); + } } - has(f: Fact) { + has(f: Triple) { return this.findInIndices( this.indexS.get(f[0]), this.indexP.get(f[1]), @@ -61,54 +64,62 @@ export class FactGraph implements ) !== -1; } - addFact(f: Fact) { - let s = this.indexS.get(f[0]); - let p = this.indexP.get(f[1]); - let o = this.indexO.get(f[2]); - if (this.findInIndices(s, p, o, f) !== -1) return this; - const id = FactGraph.NEXT_ID++; + addTriple(t: Triple) { + let s = this.indexS.get(t[0]); + let p = this.indexP.get(t[1]); + let o = this.indexO.get(t[2]); + if (this.findInIndices(s, p, o, t) !== -1) return false; + const id = TripleStore.NEXT_ID++; const is = s || new Set(); const ip = p || new Set(); const io = o || new Set(); - this.facts[id] = f; + this.triples[id] = t; is.add(id); ip.add(id); io.add(id); this.allIDs.add(id); - !s && this.indexS.set(f[0], is); - !p && this.indexP.set(f[1], ip); - !o && this.indexO.set(f[2], io); + !s && this.indexS.set(t[0], is); + !p && this.indexP.set(t[1], ip); + !o && this.indexO.set(t[2], io); this.streamAll.next(this.allIDs); - this.streamS.next({ index: is, key: f[0] }); - this.streamP.next({ index: ip, key: f[1] }); - this.streamO.next({ index: io, key: f[2] }); - return this; + this.streamS.next({ index: is, key: t[0] }); + this.streamP.next({ index: ip, key: t[1] }); + this.streamO.next({ index: io, key: t[2] }); + return true; + } + + addTriples(triples: Iterable) { + let ok = true; + for (let f of triples) { + ok = this.addTriple(f) && ok; + } + return ok; } /** * Creates a new query subscription from given SPO pattern. Any * `null` values in the pattern act as wildcard selectors and any - * other value as filter for the given fact component. E.g. the - * pattern `[null, "type", "person"]` matches all facts which have + * other value as filter for the given triple component. E.g. the + * pattern `[null, "type", "person"]` matches all triples which have * `"type"` as predicate and `"person"` as object. Likewise the - * pattern `[null, null, null]` matches ALL facts in the graph. + * pattern `[null, null, null]` matches ALL triples in the graph. * * By default, the returned rstream subscription emits sets of - * matched facts. If only the raw fact IDs are wanted, set - * `emitFacts` arg to `false`. + * matched triples. If only the raw triple IDs are wanted, set + * `emitTriples` arg to `false`. * * @param id * @param param1 */ - addPatternQuery(id: string, [s, p, o]: Pattern, emitFacts = true): Subscription> { - let results: Subscription>; + addPatternQuery(id: string, [s, p, o]: Pattern, emitTriples = true): Subscription> { + let results: Subscription>; if (s == null && p == null && o == null) { results = this.streamAll; } else { const qs = this.getIndexSelection(this.streamS, s, "s"); const qp = this.getIndexSelection(this.streamP, p, "p"); const qo = this.getIndexSelection(this.streamO, o, "o"); - results = sync({ + results = sync({ id, src: [qs, qp, qo], xform: map(({ s, p, o }) => intersection(intersection(s, p), o)), @@ -124,8 +135,8 @@ export class FactGraph implements submit(this.indexP, qp, p); submit(this.indexO, qo, o); } - return emitFacts ? - results.transform(asFacts(this)) : + return emitTriples ? + results.transform(asTriples(this)) : results; } @@ -158,14 +169,14 @@ export class FactGraph implements if (!resolve) { illegalArgs("at least 1 query variable is required in pattern"); } - const query = >>this.addPatternQuery( + const query = >>this.addPatternQuery( id + "-raw", [vs ? null : s, vp ? null : p, vo ? null : o], ); return query.transform( - map((facts: Set) => { + map((triples: Set) => { const res = []; - for (let f of facts) { + for (let f of triples) { res.push(resolve(f)); } return res; @@ -178,14 +189,14 @@ export class FactGraph implements return toDot(walk([this.streamS, this.streamP, this.streamO, this.streamAll]), opts); } - protected findInIndices(s: FactIds, p: FactIds, o: FactIds, f: Fact) { + protected findInIndices(s: TripleIds, p: TripleIds, o: TripleIds, f: Triple) { if (s && p && o) { - const facts = this.facts; + const triples = this.triples; const index = s.size < p.size ? s.size < o.size ? s : p.size < o.size ? p : o : p.size < o.size ? p : s.size < o.size ? s : o; for (let id of index) { - if (equiv(facts[id], f)) { + if (equiv(triples[id], f)) { return id; } } @@ -193,7 +204,7 @@ export class FactGraph implements return -1; } - protected getIndexSelection(stream: Stream, key: any, id: string): Subscription { + protected getIndexSelection(stream: Stream, key: any, id: string): Subscription { if (key != null) { let sel = this.indexSelections[id].get(key); if (!sel) { @@ -205,8 +216,8 @@ export class FactGraph implements } } -export const indexSel = (key: any): Transducer => - (rfn: Reducer) => { +export const indexSel = (key: any): Transducer => + (rfn: Reducer) => { const r = rfn[2]; return compR(rfn, (acc, e) => { @@ -219,10 +230,10 @@ export const indexSel = (key: any): Transducer => ); }; -export const asFacts = (graph: FactGraph) => - map>( +export const asTriples = (graph: TripleStore) => + map>( (ids) => { - const res = new Set(); - for (let id of ids) res.add(graph.facts[id]); + const res = new Set(); + for (let id of ids) res.add(graph.triples[id]); return res; }); diff --git a/packages/rstream-query/test/index.ts b/packages/rstream-query/test/index.ts index 287c333f98..37f77ae662 100644 --- a/packages/rstream-query/test/index.ts +++ b/packages/rstream-query/test/index.ts @@ -1,9 +1,9 @@ import * as assert from "assert"; -import { FactGraph, Fact } from "../src/index"; +import { TripleStore, Triple } from "../src/index"; describe("rstream-query", () => { - const facts: Fact[] = [ + const triples: Triple[] = [ ["a", "type", "foo"], // 0 ["b", "type", "bar"], // 1 ["c", "type", "baz"], // 2 @@ -12,108 +12,104 @@ describe("rstream-query", () => { ["c", "friend", "a"], // 5 ]; - let g: FactGraph; + let store: TripleStore; beforeEach(() => { - g = new FactGraph(); - FactGraph.NEXT_ID = 0; - for (let f of facts) { - g.addFact(f); - - } + TripleStore.NEXT_ID = 0; + store = new TripleStore(triples); }); it("pattern query (S)", () => { const res = []; - g.addPatternQuery("q", ["a", null, null], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", ["a", null, null], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([0, 3])]); }); it("pattern query (P)", () => { const res = []; - g.addPatternQuery("q", [null, "type", null], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", [null, "type", null], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([0, 1, 2])]); }); it("pattern query (O)", () => { const res = []; - g.addPatternQuery("q", [null, null, "a"], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", [null, null, "a"], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([5])]); }); it("pattern query (SP)", () => { const res = []; - g.addPatternQuery("q", ["a", "value", null], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", ["a", "value", null], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([3])]); }); it("pattern query (PO)", () => { const res = []; - g.addPatternQuery("q", [null, "value", 0], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", [null, "value", 0], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([3])]); }); it("pattern query (SO)", () => { const res = []; - g.addPatternQuery("q", ["b", null, "bar"], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", ["b", null, "bar"], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([1])]); }); it("pattern query (SPO)", () => { const res = []; - g.addPatternQuery("q", ["c", "type", "baz"], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", ["c", "type", "baz"], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([2])]); }); it("pattern query (all)", () => { const res = []; - g.addPatternQuery("q", [null, null, null], false).subscribe({ next: (r) => res.push(r) }); + store.addPatternQuery("q", [null, null, null], false).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [new Set([0, 1, 2, 3, 4, 5])]); }); it("param query (S)", () => { const res = []; - g.addParamQuery("q", ["a", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["a", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ p: "type", o: "foo" }, { p: "value", o: 0 }]]); }); it("param query (P)", () => { const res = []; - g.addParamQuery("q", ["?s", "type", "?o"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["?s", "type", "?o"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ s: "a", o: "foo" }, { s: "b", o: "bar" }, { s: "c", o: "baz" }]]); }); it("param query (O)", () => { const res = []; - g.addParamQuery("q", ["?s", "?p", "a"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["?s", "?p", "a"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ s: "c", p: "friend" }]]); }); it("param query (SP)", () => { const res = []; - g.addParamQuery("q", ["a", "value", "?o"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["a", "value", "?o"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ o: 0 }]]); }); it("param query (PO)", () => { const res = []; - g.addParamQuery("q", ["?s", "value", 0]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["?s", "value", 0]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ s: "a" }]]); }); it("param query (SO)", () => { const res = []; - g.addParamQuery("q", ["b", "?p", "bar"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["b", "?p", "bar"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[{ p: "type" }]]); }); it("param query (SPO)", () => { - assert.throws(() => g.addParamQuery("q", ["c", "type", "baz"])); + assert.throws(() => store.addParamQuery("q", ["c", "type", "baz"])); }); it("param query (all)", () => { const res = []; - g.addParamQuery("q", ["?s", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); + store.addParamQuery("q", ["?s", "?p", "?o"]).subscribe({ next: (r) => res.push(r) }); assert.deepEqual(res, [[ { s: "a", p: "type", o: "foo" }, { s: "b", p: "type", o: "bar" },