Skip to content
This repository was archived by the owner on Jan 19, 2024. It is now read-only.

Commit 67b66e9

Browse files
committed
feat: complete rewrite of result encoding algorithm
See rgraphql/magellan -> Design.md for the new design.
1 parent d935cb7 commit 67b66e9

File tree

17 files changed

+656
-734
lines changed

17 files changed

+656
-734
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"main": "./lib/index.js",
77
"types": "./lib/index.d.ts",
88
"peerDependencies": {
9-
"rgraphql": "^0.3.0",
9+
"rgraphql": "^0.4.5",
1010
"rxjs": "^5.0.0"
1111
},
1212
"dependencies": {
13-
"lodash": "^4.17.0",
1413
"graphql": "^0.9.0",
14+
"lodash": "^4.17.0",
15+
"lru_map": "^0.3.3",
1516
"rgraphql": "^0.3.0",
1617
"rxjs": "^5.0.0"
1718
},
@@ -22,6 +23,7 @@
2223
"lint": "tslint -c tslint.json --project tsconfig.json --type-check",
2324
"mocha": "ts-node node_modules/istanbul/lib/cli.js cover -e .ts -x \"*.d.ts\" -x \"*.spec.ts\" test/run_tests.js",
2425
"mocha-nocover": "ts-node test/run_tests.js",
26+
"mocha-nocover-debug": "ts-node debug test/run_tests.js",
2527
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
2628
},
2729
"devDependencies": {

src/client.spec.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ import { ITransport } from './transport';
33
import {
44
IRGQLServerMessage,
55
IRGQLClientMessage,
6+
CacheStrategy,
7+
IRGQLValue,
8+
RGQLValue,
9+
Kind,
610
} from 'rgraphql';
711
import {
812
parse,
913
} from 'graphql';
1014

1115
class MockTransport implements ITransport {
1216
public messageHandler: (mes: IRGQLServerMessage) => void;
17+
private queryIdCtr = 1;
1318

1419
public onMessage(cb: (mes: IRGQLServerMessage) => void) {
1520
this.messageHandler = cb;
@@ -18,6 +23,10 @@ class MockTransport implements ITransport {
1823
public send(msg: IRGQLClientMessage) {
1924
console.log(`Sending: ${JSON.stringify(msg)}`);
2025
}
26+
27+
public nextQueryId(): number {
28+
return this.queryIdCtr++;
29+
}
2130
}
2231

2332
describe('SoyuzClient', () => {
@@ -49,24 +58,25 @@ query myQuery($age: Int) {
4958
});
5059
sub.subscribe((val) => {
5160
console.log(`Query returned value: ${JSON.stringify(val)}`);
52-
if (val.data && val.data.allPeople && val.data.allPeople.length) {
61+
if (val.data && val.data.allPeople && val.data.allPeople.length && val.data.allPeople[0].name === 'Test') {
5362
done();
5463
}
5564
});
5665
console.log('Setting transport.');
5766
client.setTransport(mt);
67+
let batchValues: IRGQLValue[] = [
68+
{queryNodeId: 1},
69+
{arrayIndex: 1},
70+
{queryNodeId: 2, value: {kind: Kind.PRIMITIVE_KIND_STRING, stringValue: 'Test'}},
71+
];
72+
let encValues: Uint8Array[] = [];
73+
for (let v of batchValues) {
74+
encValues.push(RGQLValue.encode(v).finish());
75+
}
5876
let msgs: IRGQLServerMessage[] = [
59-
{mutateValue: {valueNodeId: 1, queryNodeId: 1, isArray: true}},
60-
{mutateValue: {valueNodeId: 4, queryNodeId: 1, parentValueNodeId: 1, arrayIdx: 1}},
61-
{
62-
mutateValue: {
63-
valueNodeId: 5,
64-
parentValueNodeId: 4,
65-
queryNodeId: 2,
66-
valueJson: '"John"',
67-
hasValue: true,
68-
},
69-
},
77+
{valueInit: {queryId: 1, resultId: 1, cacheSize: 200, cacheStrategy: CacheStrategy.CACHE_LRU}},
78+
{valueBatch: {resultId: 1, values: encValues}},
79+
{valueFinalize: {resultId: 1}},
7080
];
7181
for (let msg of msgs) {
7282
mt.messageHandler(msg);

src/client.ts

Lines changed: 62 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,73 @@
11
import { QueryTreeNode } from './query-tree';
2-
import { ValueTreeNode } from './value-tree';
2+
import { ResultTree } from './result';
33
import { ITransport } from './transport';
4-
import { ClientBus } from './client-bus';
4+
import { RunningQuery } from './running-query';
55
import {
66
ObservableQuery,
77
IQueryOptions,
88
} from './query';
99
import {
10-
Mutation,
1110
IMutationOptions,
1211
} from './mutation';
13-
import {
14-
ISoyuzClientContext,
15-
ISoyuzSerialOperation,
16-
} from './interfaces';
1712
import { parse, OperationDefinitionNode } from 'graphql';
1813
import { simplifyQueryAst } from './util/graphql';
1914
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
15+
import { Subscription } from 'rxjs/Subscription';
2016

2117
// Soyuz client.
2218
export class SoyuzClient {
23-
private queryTree: QueryTreeNode;
24-
private context = new BehaviorSubject<ISoyuzClientContext>(null);
19+
// queryTree holds the global live query tree.
20+
private queryTree: QueryTreeNode = new QueryTreeNode();
2521
private transportIdCounter = 0;
26-
27-
// Active serial operations in-flight.
28-
private serialOperations: { [operationId: number]: ISoyuzSerialOperation } = {};
29-
private serialOperationIdCounter = 0;
30-
31-
constructor() {
32-
this.queryTree = new QueryTreeNode();
33-
this.initHandlers();
34-
}
22+
private transport: ITransport;
23+
private queries: { [id: number]: RunningQuery } = {};
24+
private primaryQueryId: number;
25+
private primaryResultTree: BehaviorSubject<ResultTree> = new BehaviorSubject<ResultTree>(null);
26+
private transportSubs: Subscription[] = [];
3527

3628
// Set transport causes the client to start using a new transport to talk to the server.
3729
// Pass null to stop using the previous transport.
3830
public setTransport(transport: ITransport) {
39-
if (this.context.value && this.context.value.transport === transport) {
31+
if (this.transport === transport) {
4032
return;
4133
}
4234

35+
if (this.transport) {
36+
for (let queryId in this.queries) {
37+
if (!this.queries.hasOwnProperty(queryId)) {
38+
continue;
39+
}
40+
this.queries[queryId].dispose();
41+
}
42+
this.queries = {};
43+
for (let sub of this.transportSubs) {
44+
sub.unsubscribe();
45+
}
46+
this.transportSubs.length = 0;
47+
}
48+
4349
if (!transport) {
44-
this.context.next(null);
4550
return;
4651
}
4752

4853
let tid: number = this.transportIdCounter++;
49-
let vtr = new ValueTreeNode(this.queryTree);
50-
let clib = new ClientBus(transport, this.queryTree, vtr, this.serialOperations);
51-
this.context.next({
52-
transport: transport,
53-
valueTree: vtr,
54-
clientBus: clib,
55-
});
54+
// start the initial root query
55+
let query = new RunningQuery(transport, this.queryTree, 'query');
56+
this.transportSubs.push(query.resultTree.subscribe((tree) => {
57+
this.primaryResultTree.next(tree);
58+
}));
59+
this.primaryQueryId = query.id;
60+
this.queries = {};
61+
this.queries[query.id] = query;
62+
query.resultTree.subscribe({complete: () => {
63+
if (this.queries[query.id] === query) {
64+
delete this.queries[query.id];
65+
}
66+
}});
5667
}
5768

5869
// Build a query against the system.
59-
public query<T>(options: IQueryOptions): ObservableQuery<T> {
70+
public query<T>(options: IQueryOptions): ObservableQuery {
6071
if (!options || !options.query) {
6172
throw new Error('You must specify a options object and query.');
6273
}
@@ -70,10 +81,7 @@ export class SoyuzClient {
7081
if (!odef) {
7182
throw new Error('Your provided query document did not contain a query definition.');
7283
}
73-
return new ObservableQuery<T>(this.context,
74-
this.queryTree,
75-
odef,
76-
options.variables);
84+
return new ObservableQuery(this.primaryResultTree, this.queryTree, odef, options.variables);
7785
}
7886

7987
// Execute a mutation against the system.
@@ -91,27 +99,29 @@ export class SoyuzClient {
9199
if (!odef) {
92100
throw new Error('Your provided mutation document did not contain a mutation definition.');
93101
}
94-
let operationId = ++this.serialOperationIdCounter;
95-
let mutation: ISoyuzSerialOperation = new Mutation<T>(operationId, this.context, odef, options.variables);
96-
this.startSerialOperation(operationId, mutation);
97-
return mutation.asPromise();
98-
}
99-
100-
// startSerialOperation begins a already-built operation.
101-
private startSerialOperation(id: number, operation: ISoyuzSerialOperation) {
102-
this.serialOperations[id] = operation;
103-
operation.init();
104-
}
105-
106-
private initHandlers() {
107-
let lastContext: ISoyuzClientContext;
108-
this.context.subscribe((ctx) => {
109-
if (lastContext) {
110-
lastContext.valueTree.dispose();
111-
lastContext.clientBus.dispose();
102+
// start the query.
103+
let qt = new QueryTreeNode();
104+
let qr = new RunningQuery(this.transport, qt, 'mutation');
105+
let uqr = qt.buildQuery(odef, options.variables || {});
106+
this.queries[qr.id] = qr;
107+
let lrt: any;
108+
let data: any;
109+
let rtsub = qr.resultTree.subscribe((rt) => {
110+
if (!rt || rt === lrt) {
111+
return;
112112
}
113-
114-
lastContext = ctx;
113+
lrt = rt;
114+
data = rt.addQuery(uqr.id, (id) => {});
115+
});
116+
return new Promise<T>((reject, resolve) => {
117+
qr.resultTree.subscribe({
118+
error: (err) => {
119+
reject(err);
120+
},
121+
complete: () => {
122+
resolve(data);
123+
},
124+
});
115125
});
116126
}
117127
}

src/interfaces.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)