@graffy/fill 0.19.1-alpha.1 → 0.19.1
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/index.cjs +130 -0
- package/index.mjs +131 -0
- package/package.json +9 -14
- package/types/subscribe.d.ts +15 -0
- package/index.js +0 -42
- package/subscribe.d.ts +0 -9
- package/subscribe.js +0 -130
- /package/{index.d.ts → types/index.d.ts} +0 -0
package/index.cjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const common = require("@graffy/common");
|
|
3
|
+
const debug = require("debug");
|
|
4
|
+
const stream = require("@graffy/stream");
|
|
5
|
+
const log$1 = debug("graffy:fill:subscribe");
|
|
6
|
+
function subscribe(store, originalQuery, { raw }) {
|
|
7
|
+
const empty = () => common.finalize([], originalQuery, 0);
|
|
8
|
+
let push;
|
|
9
|
+
let end;
|
|
10
|
+
let upstream;
|
|
11
|
+
const query = [];
|
|
12
|
+
let data = empty();
|
|
13
|
+
let payload = [];
|
|
14
|
+
const stream$1 = stream.makeStream((streamPush, streamEnd) => {
|
|
15
|
+
push = (v) => {
|
|
16
|
+
log$1("Push", v);
|
|
17
|
+
streamPush(v);
|
|
18
|
+
};
|
|
19
|
+
end = streamEnd;
|
|
20
|
+
return unsubscribe;
|
|
21
|
+
});
|
|
22
|
+
resubscribe(originalQuery);
|
|
23
|
+
return stream$1;
|
|
24
|
+
async function resubscribe(unknown) {
|
|
25
|
+
try {
|
|
26
|
+
const changed = common.add(query, unknown);
|
|
27
|
+
log$1(changed ? "Resubscribing" : "Not resubscribing", unknown);
|
|
28
|
+
if (!changed) return;
|
|
29
|
+
if (upstream) upstream.return();
|
|
30
|
+
upstream = store.call("watch", query, { skipFill: true });
|
|
31
|
+
let { value } = await upstream.next();
|
|
32
|
+
log$1("First payload", typeof value, value);
|
|
33
|
+
if (typeof value === "undefined") {
|
|
34
|
+
value = await store.call("read", unknown, { skipCache: true });
|
|
35
|
+
log$1("Read initial value", value);
|
|
36
|
+
}
|
|
37
|
+
putValue(value, false);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
log$1("resubscribe error", e);
|
|
40
|
+
error(e);
|
|
41
|
+
}
|
|
42
|
+
putStream(upstream);
|
|
43
|
+
}
|
|
44
|
+
async function putStream(stream2) {
|
|
45
|
+
log$1("Start putting stream");
|
|
46
|
+
try {
|
|
47
|
+
for await (const value of stream2) putValue(value, true);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
error(e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function putValue(value, isChange) {
|
|
53
|
+
if (typeof value === "undefined") return;
|
|
54
|
+
log$1("Put", isChange ? "Change" : "Value", typeof value);
|
|
55
|
+
if (value === null) {
|
|
56
|
+
data = empty();
|
|
57
|
+
push(null);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let sieved;
|
|
61
|
+
if (isChange) {
|
|
62
|
+
sieved = common.sieve(data, value);
|
|
63
|
+
} else {
|
|
64
|
+
sieved = common.slice(value, query).known;
|
|
65
|
+
if (sieved) common.merge(data, sieved);
|
|
66
|
+
}
|
|
67
|
+
log$1("Sieved: ", sieved && sieved);
|
|
68
|
+
if (raw && sieved) {
|
|
69
|
+
common.merge(payload, sieved);
|
|
70
|
+
}
|
|
71
|
+
let { known, unknown } = common.slice(data, originalQuery);
|
|
72
|
+
data = known || empty();
|
|
73
|
+
log$1("Data and unknown", data, unknown && unknown);
|
|
74
|
+
log$1("Payload and value", payload, value && value);
|
|
75
|
+
if (isChange && value && unknown) {
|
|
76
|
+
const valueParts = common.slice(value, unknown);
|
|
77
|
+
if (valueParts.known) {
|
|
78
|
+
common.merge(data, valueParts.known);
|
|
79
|
+
if (raw) common.merge(payload, valueParts.known);
|
|
80
|
+
unknown = valueParts.unknown;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (unknown) return resubscribe(unknown);
|
|
84
|
+
if (!raw) {
|
|
85
|
+
if (!isChange || sieved?.length) push(data);
|
|
86
|
+
} else if (payload.length) {
|
|
87
|
+
push(payload);
|
|
88
|
+
payload = [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function error(e) {
|
|
92
|
+
if (end) end(e);
|
|
93
|
+
unsubscribe();
|
|
94
|
+
}
|
|
95
|
+
function unsubscribe() {
|
|
96
|
+
if (upstream) upstream.return();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const log = debug("graffy:fill");
|
|
100
|
+
const MAX_RECURSIONS = 10;
|
|
101
|
+
function fill(_) {
|
|
102
|
+
return (store) => {
|
|
103
|
+
store.on("read", [], async function fillOnRead(query, options, next) {
|
|
104
|
+
let value = await next(query);
|
|
105
|
+
if (options.skipFill) return value;
|
|
106
|
+
if (!value?.length) {
|
|
107
|
+
log("No progress", query);
|
|
108
|
+
throw Error("fill.no_progress");
|
|
109
|
+
}
|
|
110
|
+
let budget = MAX_RECURSIONS;
|
|
111
|
+
while (budget-- > 1) {
|
|
112
|
+
const { known, unknown } = common.slice(value, query);
|
|
113
|
+
value = known;
|
|
114
|
+
if (!unknown) break;
|
|
115
|
+
const res = await store.call("read", unknown, { skipFill: true });
|
|
116
|
+
common.merge(value, res);
|
|
117
|
+
}
|
|
118
|
+
if (!budget) {
|
|
119
|
+
log("fill.max_recursion", common.slice(value, query).unknown);
|
|
120
|
+
throw new Error("fill.max_recursion");
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
});
|
|
124
|
+
store.on("watch", [], function fillOnWatch(query, options, next) {
|
|
125
|
+
if (options.skipFill) return next(query);
|
|
126
|
+
return subscribe(store, query, options);
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
module.exports = fill;
|
package/index.mjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { finalize, add, sieve, slice, merge } from "@graffy/common";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { makeStream } from "@graffy/stream";
|
|
4
|
+
const log$1 = debug("graffy:fill:subscribe");
|
|
5
|
+
function subscribe(store, originalQuery, { raw }) {
|
|
6
|
+
const empty = () => finalize([], originalQuery, 0);
|
|
7
|
+
let push;
|
|
8
|
+
let end;
|
|
9
|
+
let upstream;
|
|
10
|
+
const query = [];
|
|
11
|
+
let data = empty();
|
|
12
|
+
let payload = [];
|
|
13
|
+
const stream = makeStream((streamPush, streamEnd) => {
|
|
14
|
+
push = (v) => {
|
|
15
|
+
log$1("Push", v);
|
|
16
|
+
streamPush(v);
|
|
17
|
+
};
|
|
18
|
+
end = streamEnd;
|
|
19
|
+
return unsubscribe;
|
|
20
|
+
});
|
|
21
|
+
resubscribe(originalQuery);
|
|
22
|
+
return stream;
|
|
23
|
+
async function resubscribe(unknown) {
|
|
24
|
+
try {
|
|
25
|
+
const changed = add(query, unknown);
|
|
26
|
+
log$1(changed ? "Resubscribing" : "Not resubscribing", unknown);
|
|
27
|
+
if (!changed) return;
|
|
28
|
+
if (upstream) upstream.return();
|
|
29
|
+
upstream = store.call("watch", query, { skipFill: true });
|
|
30
|
+
let { value } = await upstream.next();
|
|
31
|
+
log$1("First payload", typeof value, value);
|
|
32
|
+
if (typeof value === "undefined") {
|
|
33
|
+
value = await store.call("read", unknown, { skipCache: true });
|
|
34
|
+
log$1("Read initial value", value);
|
|
35
|
+
}
|
|
36
|
+
putValue(value, false);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
log$1("resubscribe error", e);
|
|
39
|
+
error(e);
|
|
40
|
+
}
|
|
41
|
+
putStream(upstream);
|
|
42
|
+
}
|
|
43
|
+
async function putStream(stream2) {
|
|
44
|
+
log$1("Start putting stream");
|
|
45
|
+
try {
|
|
46
|
+
for await (const value of stream2) putValue(value, true);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
error(e);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function putValue(value, isChange) {
|
|
52
|
+
if (typeof value === "undefined") return;
|
|
53
|
+
log$1("Put", isChange ? "Change" : "Value", typeof value);
|
|
54
|
+
if (value === null) {
|
|
55
|
+
data = empty();
|
|
56
|
+
push(null);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let sieved;
|
|
60
|
+
if (isChange) {
|
|
61
|
+
sieved = sieve(data, value);
|
|
62
|
+
} else {
|
|
63
|
+
sieved = slice(value, query).known;
|
|
64
|
+
if (sieved) merge(data, sieved);
|
|
65
|
+
}
|
|
66
|
+
log$1("Sieved: ", sieved && sieved);
|
|
67
|
+
if (raw && sieved) {
|
|
68
|
+
merge(payload, sieved);
|
|
69
|
+
}
|
|
70
|
+
let { known, unknown } = slice(data, originalQuery);
|
|
71
|
+
data = known || empty();
|
|
72
|
+
log$1("Data and unknown", data, unknown && unknown);
|
|
73
|
+
log$1("Payload and value", payload, value && value);
|
|
74
|
+
if (isChange && value && unknown) {
|
|
75
|
+
const valueParts = slice(value, unknown);
|
|
76
|
+
if (valueParts.known) {
|
|
77
|
+
merge(data, valueParts.known);
|
|
78
|
+
if (raw) merge(payload, valueParts.known);
|
|
79
|
+
unknown = valueParts.unknown;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (unknown) return resubscribe(unknown);
|
|
83
|
+
if (!raw) {
|
|
84
|
+
if (!isChange || sieved?.length) push(data);
|
|
85
|
+
} else if (payload.length) {
|
|
86
|
+
push(payload);
|
|
87
|
+
payload = [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function error(e) {
|
|
91
|
+
if (end) end(e);
|
|
92
|
+
unsubscribe();
|
|
93
|
+
}
|
|
94
|
+
function unsubscribe() {
|
|
95
|
+
if (upstream) upstream.return();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const log = debug("graffy:fill");
|
|
99
|
+
const MAX_RECURSIONS = 10;
|
|
100
|
+
function fill(_) {
|
|
101
|
+
return (store) => {
|
|
102
|
+
store.on("read", [], async function fillOnRead(query, options, next) {
|
|
103
|
+
let value = await next(query);
|
|
104
|
+
if (options.skipFill) return value;
|
|
105
|
+
if (!value?.length) {
|
|
106
|
+
log("No progress", query);
|
|
107
|
+
throw Error("fill.no_progress");
|
|
108
|
+
}
|
|
109
|
+
let budget = MAX_RECURSIONS;
|
|
110
|
+
while (budget-- > 1) {
|
|
111
|
+
const { known, unknown } = slice(value, query);
|
|
112
|
+
value = known;
|
|
113
|
+
if (!unknown) break;
|
|
114
|
+
const res = await store.call("read", unknown, { skipFill: true });
|
|
115
|
+
merge(value, res);
|
|
116
|
+
}
|
|
117
|
+
if (!budget) {
|
|
118
|
+
log("fill.max_recursion", slice(value, query).unknown);
|
|
119
|
+
throw new Error("fill.max_recursion");
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
});
|
|
123
|
+
store.on("watch", [], function fillOnWatch(query, options, next) {
|
|
124
|
+
if (options.skipFill) return next(query);
|
|
125
|
+
return subscribe(store, query, options);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
fill as default
|
|
131
|
+
};
|
package/package.json
CHANGED
|
@@ -2,27 +2,22 @@
|
|
|
2
2
|
"name": "@graffy/fill",
|
|
3
3
|
"description": "Graffy module for fulfilling queries using multiple backends, traversing links and turning subscriptions into live queries.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.19.1
|
|
6
|
-
"main": "./
|
|
5
|
+
"version": "0.19.1",
|
|
6
|
+
"main": "./index.cjs",
|
|
7
7
|
"exports": {
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
"types": "./index.d.ts"
|
|
11
|
-
},
|
|
12
|
-
"./*": {
|
|
13
|
-
"import": "./*.js",
|
|
14
|
-
"types": "./*.d.ts"
|
|
15
|
-
}
|
|
8
|
+
"import": "./index.mjs",
|
|
9
|
+
"require": "./index.cjs"
|
|
16
10
|
},
|
|
17
|
-
"
|
|
11
|
+
"module": "./index.mjs",
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
18
13
|
"repository": {
|
|
19
14
|
"type": "git",
|
|
20
15
|
"url": "git+https://github.com/usegraffy/graffy.git"
|
|
21
16
|
},
|
|
22
17
|
"license": "Apache-2.0",
|
|
23
18
|
"dependencies": {
|
|
24
|
-
"@graffy/common": "0.19.1
|
|
25
|
-
"
|
|
26
|
-
"
|
|
19
|
+
"@graffy/common": "0.19.1",
|
|
20
|
+
"debug": "^4.4.3",
|
|
21
|
+
"@graffy/stream": "0.19.1"
|
|
27
22
|
}
|
|
28
23
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function subscribe(store: any, originalQuery: any, { raw }: {
|
|
2
|
+
raw: any;
|
|
3
|
+
}): {
|
|
4
|
+
debugId: any;
|
|
5
|
+
next: () => any;
|
|
6
|
+
return(value: any): Promise<{
|
|
7
|
+
value: any;
|
|
8
|
+
done: boolean;
|
|
9
|
+
}>;
|
|
10
|
+
throw(error: any): Promise<{
|
|
11
|
+
value: any;
|
|
12
|
+
done: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
[Symbol.asyncIterator](): /*elided*/ any;
|
|
15
|
+
};
|
package/index.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { merge, slice } from '@graffy/common';
|
|
2
|
-
import debug from 'debug';
|
|
3
|
-
import subscribe from "./subscribe.js";
|
|
4
|
-
const log = debug('graffy:fill');
|
|
5
|
-
const MAX_RECURSIONS = 10;
|
|
6
|
-
export default function fill(_) {
|
|
7
|
-
return (store) => {
|
|
8
|
-
store.on('read', [], async function fillOnRead(query, options, next) {
|
|
9
|
-
let value = await next(query);
|
|
10
|
-
if (options.skipFill)
|
|
11
|
-
return value;
|
|
12
|
-
if (!value?.length) {
|
|
13
|
-
log('No progress', query);
|
|
14
|
-
throw Error('fill.no_progress');
|
|
15
|
-
// return null;
|
|
16
|
-
}
|
|
17
|
-
let budget = MAX_RECURSIONS;
|
|
18
|
-
while (budget-- > 1) {
|
|
19
|
-
// console.log('filled value', value);
|
|
20
|
-
const { known, unknown } = slice(value, query);
|
|
21
|
-
value = known;
|
|
22
|
-
if (!unknown)
|
|
23
|
-
break;
|
|
24
|
-
// console.log('unknown', unknown);
|
|
25
|
-
const res = await store.call('read', unknown, { skipFill: true });
|
|
26
|
-
// console.log('fetched', res);
|
|
27
|
-
merge(value, res);
|
|
28
|
-
}
|
|
29
|
-
if (!budget) {
|
|
30
|
-
log('fill.max_recursion', slice(value, query).unknown);
|
|
31
|
-
throw new Error('fill.max_recursion');
|
|
32
|
-
}
|
|
33
|
-
// console.log('Read', debug(query), 'returned', debug(value));
|
|
34
|
-
return value;
|
|
35
|
-
});
|
|
36
|
-
store.on('watch', [], function fillOnWatch(query, options, next) {
|
|
37
|
-
if (options.skipFill)
|
|
38
|
-
return next(query);
|
|
39
|
-
return subscribe(store, query, options);
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
}
|
package/subscribe.d.ts
DELETED
package/subscribe.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { add, finalize, merge, sieve, slice } from '@graffy/common';
|
|
2
|
-
import { makeStream } from '@graffy/stream';
|
|
3
|
-
import debug from 'debug';
|
|
4
|
-
const log = debug('graffy:fill:subscribe');
|
|
5
|
-
export default function subscribe(store, originalQuery, { raw }) {
|
|
6
|
-
const empty = () => finalize([], originalQuery, 0);
|
|
7
|
-
let push;
|
|
8
|
-
let end;
|
|
9
|
-
let upstream;
|
|
10
|
-
const query = [];
|
|
11
|
-
let data = empty();
|
|
12
|
-
let payload = [];
|
|
13
|
-
const stream = makeStream((streamPush, streamEnd) => {
|
|
14
|
-
push = (v) => {
|
|
15
|
-
log('Push', v);
|
|
16
|
-
streamPush(v);
|
|
17
|
-
};
|
|
18
|
-
end = streamEnd;
|
|
19
|
-
return unsubscribe;
|
|
20
|
-
});
|
|
21
|
-
resubscribe(originalQuery);
|
|
22
|
-
return stream;
|
|
23
|
-
async function resubscribe(unknown) {
|
|
24
|
-
try {
|
|
25
|
-
const changed = add(query, unknown);
|
|
26
|
-
log(changed ? 'Resubscribing' : 'Not resubscribing', unknown);
|
|
27
|
-
if (!changed)
|
|
28
|
-
return;
|
|
29
|
-
if (upstream)
|
|
30
|
-
upstream.return(); // Close the existing stream.
|
|
31
|
-
upstream = store.call('watch', query, { skipFill: true });
|
|
32
|
-
let { value } = await upstream.next();
|
|
33
|
-
log('First payload', typeof value, value);
|
|
34
|
-
if (typeof value === 'undefined') {
|
|
35
|
-
// The upstream is a change subscription, not a live query,
|
|
36
|
-
// so we need to fetch the initial value.
|
|
37
|
-
// TODO: Get a version corresponding to the subscription's start
|
|
38
|
-
// and verify that the store.read response is newer.
|
|
39
|
-
value = await store.call('read', unknown, { skipCache: true });
|
|
40
|
-
log('Read initial value', value);
|
|
41
|
-
}
|
|
42
|
-
// value = value && slice(value, unknown).known;
|
|
43
|
-
putValue(value, false);
|
|
44
|
-
}
|
|
45
|
-
catch (e) {
|
|
46
|
-
log('resubscribe error', e);
|
|
47
|
-
error(e);
|
|
48
|
-
}
|
|
49
|
-
putStream(upstream);
|
|
50
|
-
}
|
|
51
|
-
async function putStream(stream) {
|
|
52
|
-
// TODO: Backpressure: pause pulling if downstream listener is saturated.
|
|
53
|
-
log('Start putting stream');
|
|
54
|
-
try {
|
|
55
|
-
for await (const value of stream)
|
|
56
|
-
putValue(value, true);
|
|
57
|
-
}
|
|
58
|
-
catch (e) {
|
|
59
|
-
error(e);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function putValue(value, isChange) {
|
|
63
|
-
if (typeof value === 'undefined')
|
|
64
|
-
return;
|
|
65
|
-
log('Put', isChange ? 'Change' : 'Value', typeof value);
|
|
66
|
-
if (value === null) {
|
|
67
|
-
// No results exist at this moment.
|
|
68
|
-
data = empty();
|
|
69
|
-
push(null);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
// We do this to ensure that everything in value gets incorporated
|
|
73
|
-
// into data, even as we use sieve to ensure only actual changes are
|
|
74
|
-
// added to payload.
|
|
75
|
-
// if (!isChange) merge(data, [{ key: '', end: '\uffff', version: -1 }]);
|
|
76
|
-
// log('Data before sieve', (data));
|
|
77
|
-
let sieved;
|
|
78
|
-
if (isChange) {
|
|
79
|
-
sieved = sieve(data, value);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
sieved = slice(value, query).known;
|
|
83
|
-
if (sieved)
|
|
84
|
-
merge(data, sieved);
|
|
85
|
-
}
|
|
86
|
-
log('Sieved: ', sieved && sieved);
|
|
87
|
-
if (raw && sieved) {
|
|
88
|
-
// log('Payload before adding sieved', (payload));
|
|
89
|
-
merge(payload, sieved);
|
|
90
|
-
// log('Payload after adding sieved', (payload));
|
|
91
|
-
}
|
|
92
|
-
// The new value might have removed a link, making parts of
|
|
93
|
-
// data unnecessary, or added a link, introducing
|
|
94
|
-
// new data requirements. Let's find out.
|
|
95
|
-
let { known, unknown } = slice(data, originalQuery);
|
|
96
|
-
data = known || empty();
|
|
97
|
-
log('Data and unknown', data, unknown && unknown);
|
|
98
|
-
log('Payload and value', payload, value && value);
|
|
99
|
-
if (isChange && value && unknown) {
|
|
100
|
-
// The sieve may have removed some necessary data (that we weren't aware
|
|
101
|
-
// was necessary). Get it back.
|
|
102
|
-
const valueParts = slice(value, unknown);
|
|
103
|
-
if (valueParts.known) {
|
|
104
|
-
merge(data, valueParts.known);
|
|
105
|
-
if (raw)
|
|
106
|
-
merge(payload, valueParts.known);
|
|
107
|
-
unknown = valueParts.unknown;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (unknown)
|
|
111
|
-
return resubscribe(unknown);
|
|
112
|
-
if (!raw) {
|
|
113
|
-
if (!isChange || sieved?.length)
|
|
114
|
-
push(data);
|
|
115
|
-
}
|
|
116
|
-
else if (payload.length) {
|
|
117
|
-
push(payload);
|
|
118
|
-
payload = [];
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function error(e) {
|
|
122
|
-
if (end)
|
|
123
|
-
end(e);
|
|
124
|
-
unsubscribe();
|
|
125
|
-
}
|
|
126
|
-
function unsubscribe() {
|
|
127
|
-
if (upstream)
|
|
128
|
-
upstream.return();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
File without changes
|