@skipruntime/helpers 0.0.12 → 0.0.14
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/src/external.d.ts +43 -69
- package/dist/src/external.d.ts.map +1 -1
- package/dist/src/external.js +23 -94
- package/dist/src/external.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/remote.d.ts +24 -1
- package/dist/src/remote.d.ts.map +1 -1
- package/dist/src/remote.js +56 -0
- package/dist/src/remote.js.map +1 -1
- package/package.json +2 -2
- package/src/external.ts +58 -144
- package/src/index.ts +3 -5
- package/src/remote.ts +80 -1
package/dist/src/external.d.ts
CHANGED
|
@@ -1,46 +1,4 @@
|
|
|
1
1
|
import type { Entry, ExternalService, Json } from "@skipruntime/core";
|
|
2
|
-
/**
|
|
3
|
-
* Interface required by `GenericExternalService` for external resources.
|
|
4
|
-
*/
|
|
5
|
-
export interface ExternalResource {
|
|
6
|
-
open(instance: string, params: Json, callbacks: {
|
|
7
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
8
|
-
error: (error: Json) => void;
|
|
9
|
-
loading: () => void;
|
|
10
|
-
}): void;
|
|
11
|
-
close(instance: string): void;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* A generic external service providing external resources.
|
|
15
|
-
*
|
|
16
|
-
* `GenericExternalService` provides an implementation of `ExternalService` for external resources by lifting the `open` and `close` operations from `ExternalResource` to the `subscribe` and `unsubscribe` operations required by `ExternalService`.
|
|
17
|
-
*/
|
|
18
|
-
export declare class GenericExternalService implements ExternalService {
|
|
19
|
-
private readonly resources;
|
|
20
|
-
private readonly instances;
|
|
21
|
-
/**
|
|
22
|
-
* @param resources - Association of resource names to `ExternalResource`s.
|
|
23
|
-
*/
|
|
24
|
-
constructor(resources: {
|
|
25
|
-
[name: string]: ExternalResource;
|
|
26
|
-
});
|
|
27
|
-
subscribe(instance: string, resourceName: string, params: Json, callbacks: {
|
|
28
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
29
|
-
error: (error: Json) => void;
|
|
30
|
-
loading: () => void;
|
|
31
|
-
}): void;
|
|
32
|
-
unsubscribe(instance: string): void;
|
|
33
|
-
shutdown(): Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
export declare class TimerResource implements ExternalResource {
|
|
36
|
-
private readonly intervals;
|
|
37
|
-
open(instance: string, params: Json, callbacks: {
|
|
38
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
39
|
-
error: (error: Json) => void;
|
|
40
|
-
loading: () => void;
|
|
41
|
-
}): void;
|
|
42
|
-
close(instance: string): void;
|
|
43
|
-
}
|
|
44
2
|
/**
|
|
45
3
|
* Encode params for external resource request.
|
|
46
4
|
*
|
|
@@ -51,45 +9,61 @@ export declare class TimerResource implements ExternalResource {
|
|
|
51
9
|
*/
|
|
52
10
|
export declare function defaultParamEncoder(params: Json): string;
|
|
53
11
|
/**
|
|
54
|
-
*
|
|
12
|
+
* Description of an external HTTP endpoint and how to poll it.
|
|
55
13
|
*
|
|
56
|
-
*
|
|
57
|
-
* @typeParam K - Type of keys.
|
|
58
|
-
* @typeParam V - Type of values.
|
|
14
|
+
* The URL of the external resource is formed by appending the given base `url` and the result of `encodeParams(params)` where `params` are the parameters provided to [`Context#useExternalResource`](https://skiplabs.io/docs/api/core/interfaces/Context#useexternalresource)
|
|
59
15
|
*/
|
|
60
|
-
export
|
|
61
|
-
private readonly url;
|
|
62
|
-
private readonly duration;
|
|
63
|
-
private readonly conv;
|
|
64
|
-
private readonly encodeParams;
|
|
65
|
-
private readonly options?;
|
|
66
|
-
private readonly intervals;
|
|
16
|
+
export interface PolledHTTPResource {
|
|
67
17
|
/**
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
*
|
|
18
|
+
* Base URL of resource to poll.
|
|
19
|
+
*/
|
|
20
|
+
url: string;
|
|
21
|
+
/**
|
|
22
|
+
* The interval of time to wait before refreshing the data, given in milliseconds
|
|
23
|
+
*/
|
|
24
|
+
interval: number;
|
|
25
|
+
/**
|
|
26
|
+
* Function to convert data received from external resource to `key`-`value` entries.
|
|
27
|
+
*/
|
|
28
|
+
conv: (data: Json) => Entry<Json, Json>[];
|
|
29
|
+
/**
|
|
30
|
+
* Function to use to encode params of type `Json` for external resource request.
|
|
73
31
|
*
|
|
74
|
-
*
|
|
75
|
-
* @param duration - Refresh interval, in milliseconds.
|
|
76
|
-
* @param conv - Function to convert data of type `S` received from external resource to `key`-`value` entries.
|
|
77
|
-
* @param encodeParams - Function to use to encode params of type `Json` for external resource request.
|
|
78
|
-
* @param options - Optional parameters.
|
|
79
|
-
* @param options.headers - Additional headers to add to request.
|
|
80
|
-
* @param options.timeout - Timeout for request, in milliseconds. Defaults to 1000ms.
|
|
32
|
+
* Note that the result of `encodeParams` may contain a `?` separator, but it need not be at the beginning of the returned string, so some parameters can be used in part of the URL preceding the `?`.
|
|
81
33
|
*/
|
|
82
|
-
|
|
34
|
+
encodeParams?: (params: Json) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional parameters: additional `headers` to add to request, and `timeout` for request, in milliseconds. (default 1000ms)
|
|
37
|
+
*/
|
|
38
|
+
options?: {
|
|
83
39
|
headers?: {
|
|
84
40
|
[header: string]: string;
|
|
85
41
|
};
|
|
86
42
|
timeout?: number;
|
|
87
|
-
}
|
|
88
|
-
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An external HTTP service that is kept up-to-date by polling.
|
|
47
|
+
*
|
|
48
|
+
* A `PolledExternalService` may be composed of one or more [`PolledHTTPResource`](https://skiplabs.io/docs/api/helpers/interfaces/PolledHTTPResource)s, each of which describes a single endpoint and how to poll it.
|
|
49
|
+
*/
|
|
50
|
+
export declare class PolledExternalService implements ExternalService {
|
|
51
|
+
private readonly resources;
|
|
52
|
+
private readonly intervals;
|
|
53
|
+
/**
|
|
54
|
+
* Construct a polled external service.
|
|
55
|
+
*
|
|
56
|
+
* @param resources - Specification(s) of external resource(s) to poll
|
|
57
|
+
*/
|
|
58
|
+
constructor(resources: {
|
|
59
|
+
[resource: string]: PolledHTTPResource;
|
|
60
|
+
});
|
|
61
|
+
subscribe(instance: string, resourceName: string, params: Json, callbacks: {
|
|
89
62
|
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
90
63
|
error: (error: Json) => void;
|
|
91
64
|
loading: () => void;
|
|
92
65
|
}): void;
|
|
93
|
-
|
|
66
|
+
unsubscribe(instance: string): void;
|
|
67
|
+
shutdown(): Promise<void>;
|
|
94
68
|
}
|
|
95
69
|
//# sourceMappingURL=external.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAMtE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CASxD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;IAC1C;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,MAAM,CAAC;IACxC;;OAEG;IACH,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE;YAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IASzD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAR5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IAExD;;;;OAIG;gBAEgB,SAAS,EAAE;QAC1B,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAAC;KACxC;IAGH,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,IAAI,EACZ,SAAS,EAAE;QACT,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;QAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB;IAwBH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQnC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAO1B"}
|
package/dist/src/external.js
CHANGED
|
@@ -1,70 +1,5 @@
|
|
|
1
1
|
import { SkipUnknownResourceError } from "@skipruntime/core";
|
|
2
2
|
import { fetchJSON } from "./rest.js";
|
|
3
|
-
/**
|
|
4
|
-
* A generic external service providing external resources.
|
|
5
|
-
*
|
|
6
|
-
* `GenericExternalService` provides an implementation of `ExternalService` for external resources by lifting the `open` and `close` operations from `ExternalResource` to the `subscribe` and `unsubscribe` operations required by `ExternalService`.
|
|
7
|
-
*/
|
|
8
|
-
export class GenericExternalService {
|
|
9
|
-
/**
|
|
10
|
-
* @param resources - Association of resource names to `ExternalResource`s.
|
|
11
|
-
*/
|
|
12
|
-
constructor(resources) {
|
|
13
|
-
this.resources = resources;
|
|
14
|
-
this.instances = new Map();
|
|
15
|
-
}
|
|
16
|
-
subscribe(instance, resourceName, params, callbacks) {
|
|
17
|
-
const resource = this.resources[resourceName];
|
|
18
|
-
if (!resource) {
|
|
19
|
-
throw new SkipUnknownResourceError(`Unknown resource named '${resourceName}'`);
|
|
20
|
-
}
|
|
21
|
-
this.instances.set(instance, resource);
|
|
22
|
-
resource.open(instance, params, callbacks);
|
|
23
|
-
}
|
|
24
|
-
unsubscribe(instance) {
|
|
25
|
-
const resource = this.instances.get(instance);
|
|
26
|
-
if (resource) {
|
|
27
|
-
resource.close(instance);
|
|
28
|
-
this.instances.delete(instance);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
shutdown() {
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export class TimerResource {
|
|
36
|
-
constructor() {
|
|
37
|
-
this.intervals = new Map();
|
|
38
|
-
}
|
|
39
|
-
open(instance, params, callbacks) {
|
|
40
|
-
const time = new Date().getTime();
|
|
41
|
-
const values = [];
|
|
42
|
-
for (const name of Object.keys(params)) {
|
|
43
|
-
values.push([name, [time]]);
|
|
44
|
-
}
|
|
45
|
-
callbacks.update(values, true);
|
|
46
|
-
const intervals = {};
|
|
47
|
-
for (const [name, duration] of Object.entries(params)) {
|
|
48
|
-
const ms = Number(duration);
|
|
49
|
-
if (ms > 0) {
|
|
50
|
-
intervals[name] = setInterval(() => {
|
|
51
|
-
const newvalue = [name, [new Date().getTime()]];
|
|
52
|
-
callbacks.update([newvalue], true);
|
|
53
|
-
}, ms);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
this.intervals.set(instance, intervals);
|
|
57
|
-
}
|
|
58
|
-
close(instance) {
|
|
59
|
-
const intervals = this.intervals.get(instance);
|
|
60
|
-
if (intervals != null) {
|
|
61
|
-
for (const interval of Object.values(intervals)) {
|
|
62
|
-
clearInterval(interval);
|
|
63
|
-
}
|
|
64
|
-
this.intervals.delete(instance);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
3
|
/**
|
|
69
4
|
* Encode params for external resource request.
|
|
70
5
|
*
|
|
@@ -88,43 +23,30 @@ export function defaultParamEncoder(params) {
|
|
|
88
23
|
return `?params=${JSON.stringify(params)}`;
|
|
89
24
|
}
|
|
90
25
|
/**
|
|
91
|
-
* An external
|
|
26
|
+
* An external HTTP service that is kept up-to-date by polling.
|
|
92
27
|
*
|
|
93
|
-
*
|
|
94
|
-
* @typeParam K - Type of keys.
|
|
95
|
-
* @typeParam V - Type of values.
|
|
28
|
+
* A `PolledExternalService` may be composed of one or more [`PolledHTTPResource`](https://skiplabs.io/docs/api/helpers/interfaces/PolledHTTPResource)s, each of which describes a single endpoint and how to poll it.
|
|
96
29
|
*/
|
|
97
|
-
export class
|
|
30
|
+
export class PolledExternalService {
|
|
98
31
|
/**
|
|
99
|
-
* Construct a
|
|
100
|
-
*
|
|
101
|
-
* The URL of the external resource is formed by appending the given base `url` and the result of `encodeParams(params)` where `params` are the parameters provided when instantiating the resource.
|
|
102
|
-
*
|
|
103
|
-
* Note that the result of `encodeParams` contains the `?` separator, but it need not be at the beginning of the returned string, so some parameters can be used in part of the URL preceding the `?`.
|
|
32
|
+
* Construct a polled external service.
|
|
104
33
|
*
|
|
105
|
-
* @param
|
|
106
|
-
* @param duration - Refresh interval, in milliseconds.
|
|
107
|
-
* @param conv - Function to convert data of type `S` received from external resource to `key`-`value` entries.
|
|
108
|
-
* @param encodeParams - Function to use to encode params of type `Json` for external resource request.
|
|
109
|
-
* @param options - Optional parameters.
|
|
110
|
-
* @param options.headers - Additional headers to add to request.
|
|
111
|
-
* @param options.timeout - Timeout for request, in milliseconds. Defaults to 1000ms.
|
|
34
|
+
* @param resources - Specification(s) of external resource(s) to poll
|
|
112
35
|
*/
|
|
113
|
-
constructor(
|
|
114
|
-
this.
|
|
115
|
-
this.duration = duration;
|
|
116
|
-
this.conv = conv;
|
|
117
|
-
this.encodeParams = encodeParams;
|
|
118
|
-
this.options = options;
|
|
36
|
+
constructor(resources) {
|
|
37
|
+
this.resources = resources;
|
|
119
38
|
this.intervals = new Map();
|
|
120
39
|
}
|
|
121
|
-
|
|
122
|
-
const
|
|
40
|
+
subscribe(instance, resourceName, params, callbacks) {
|
|
41
|
+
const resource = this.resources[resourceName];
|
|
42
|
+
if (!resource)
|
|
43
|
+
throw new SkipUnknownResourceError(`Unknown resource named '${resourceName}'`);
|
|
44
|
+
const url = `${resource.url}${(resource.encodeParams ?? defaultParamEncoder)(params)}`;
|
|
123
45
|
const call = () => {
|
|
124
46
|
callbacks.loading();
|
|
125
|
-
fetchJSON(url, "GET",
|
|
47
|
+
fetchJSON(url, "GET", resource.options ?? {})
|
|
126
48
|
.then((r) => {
|
|
127
|
-
callbacks.update(
|
|
49
|
+
callbacks.update(resource.conv(r[0] ?? []), true);
|
|
128
50
|
})
|
|
129
51
|
.catch((e) => {
|
|
130
52
|
callbacks.error(e instanceof Error ? e.message : JSON.stringify(e));
|
|
@@ -132,14 +54,21 @@ export class Polled {
|
|
|
132
54
|
});
|
|
133
55
|
};
|
|
134
56
|
call();
|
|
135
|
-
this.intervals.set(instance, setInterval(call,
|
|
57
|
+
this.intervals.set(instance, setInterval(call, resource.interval));
|
|
136
58
|
}
|
|
137
|
-
|
|
59
|
+
unsubscribe(instance) {
|
|
138
60
|
const interval = this.intervals.get(instance);
|
|
139
61
|
if (interval) {
|
|
140
62
|
clearInterval(interval);
|
|
141
63
|
this.intervals.delete(instance);
|
|
142
64
|
}
|
|
143
65
|
}
|
|
66
|
+
shutdown() {
|
|
67
|
+
for (const [instance, interval] of Object.entries(this.intervals)) {
|
|
68
|
+
clearInterval(interval);
|
|
69
|
+
this.intervals.delete(instance);
|
|
70
|
+
}
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
144
73
|
}
|
|
145
74
|
//# sourceMappingURL=external.js.map
|
package/dist/src/external.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
export { defaultParamEncoder,
|
|
7
|
-
export { SkipExternalService } from "./remote.js";
|
|
6
|
+
export { defaultParamEncoder, PolledExternalService, type PolledHTTPResource, } from "./external.js";
|
|
7
|
+
export { SkipExternalService, asLeader, asFollower } from "./remote.js";
|
|
8
8
|
export { SkipServiceBroker, fetchJSON, type Entrypoint } from "./rest.js";
|
|
9
9
|
export { Count, Max, Min, Sum } from "./utils.js";
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
export { defaultParamEncoder,
|
|
7
|
-
export { SkipExternalService } from "./remote.js";
|
|
6
|
+
export { defaultParamEncoder, PolledExternalService, } from "./external.js";
|
|
7
|
+
export { SkipExternalService, asLeader, asFollower } from "./remote.js";
|
|
8
8
|
export { SkipServiceBroker, fetchJSON } from "./rest.js";
|
|
9
9
|
export { Count, Max, Min, Sum } from "./utils.js";
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GAEtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAmB,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/src/remote.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Entry, ExternalService, Json } from "@skipruntime/core";
|
|
1
|
+
import type { Entry, ExternalService, Json, SkipService } from "@skipruntime/core";
|
|
2
2
|
import type { Entrypoint } from "./rest.js";
|
|
3
3
|
/**
|
|
4
4
|
* An external Skip reactive service.
|
|
@@ -29,4 +29,27 @@ export declare class SkipExternalService implements ExternalService {
|
|
|
29
29
|
unsubscribe(instance: string): void;
|
|
30
30
|
shutdown(): Promise<void>;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Run a `SkipService` as the *leader* in a leader-follower topology.
|
|
34
|
+
*
|
|
35
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
36
|
+
*
|
|
37
|
+
* @returns The *leader* component to run `service` in such a configuration.
|
|
38
|
+
*/
|
|
39
|
+
export declare function asLeader(service: SkipService): SkipService;
|
|
40
|
+
/**
|
|
41
|
+
* Run a `SkipService` as a *follower* in a leader-follower topology.
|
|
42
|
+
*
|
|
43
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
44
|
+
*
|
|
45
|
+
* @returns The *follower* component to run `service` in such a configuration, given the leader's address and the names of the shared computation graph collections to be mirrored from it (typically the `ResourceInputs` of `service`).
|
|
46
|
+
*/
|
|
47
|
+
export declare function asFollower(service: SkipService, leader: {
|
|
48
|
+
leader: {
|
|
49
|
+
host: string;
|
|
50
|
+
streaming_port: number;
|
|
51
|
+
control_port: number;
|
|
52
|
+
};
|
|
53
|
+
collections: string[];
|
|
54
|
+
}): SkipService;
|
|
32
55
|
//# sourceMappingURL=remote.d.ts.map
|
package/dist/src/remote.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,KAAK,EACL,eAAe,EACf,IAAI,EAGJ,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAG3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAM5C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,eAAe;IAQvD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD;;;OAGG;gBAEgB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM;IAGtC;;;;;OAKG;IAEH,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,GAAG,mBAAmB;IAU1D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,IAAI,EACZ,SAAS,EAAE;QACT,MAAM,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;QAEnE,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;QAE7B,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,GACA,IAAI;IA8BP,WAAW,CAAC,QAAQ,EAAE,MAAM;IAQ5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAM1B;AAqBD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CAM1D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE;IACN,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GACA,WAAW,CAoBb"}
|
package/dist/src/remote.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TODO: Remove once global `EventSource` makes it out of experimental
|
|
2
2
|
// in nodejs LTS.
|
|
3
3
|
import EventSource from "eventsource";
|
|
4
|
+
import { SkipError } from "@skipruntime/core";
|
|
4
5
|
/**
|
|
5
6
|
* An external Skip reactive service.
|
|
6
7
|
*
|
|
@@ -75,4 +76,59 @@ export class SkipExternalService {
|
|
|
75
76
|
return Promise.resolve();
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
class LeaderResource {
|
|
80
|
+
constructor(param) {
|
|
81
|
+
if (typeof param == "string")
|
|
82
|
+
this.collection = param;
|
|
83
|
+
else
|
|
84
|
+
throw new SkipError("Followers must specify a shared collection to mirror from leader.");
|
|
85
|
+
}
|
|
86
|
+
instantiate(collections) {
|
|
87
|
+
if (this.collection in collections)
|
|
88
|
+
return collections[this.collection];
|
|
89
|
+
throw new SkipError(`Unknown shared collection in leader: ${this.collection}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run a `SkipService` as the *leader* in a leader-follower topology.
|
|
94
|
+
*
|
|
95
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
96
|
+
*
|
|
97
|
+
* @returns The *leader* component to run `service` in such a configuration.
|
|
98
|
+
*/
|
|
99
|
+
export function asLeader(service) {
|
|
100
|
+
//TODO: add mechanism to split externals between leader/follower
|
|
101
|
+
return {
|
|
102
|
+
...service,
|
|
103
|
+
resources: { leader: LeaderResource },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run a `SkipService` as a *follower* in a leader-follower topology.
|
|
108
|
+
*
|
|
109
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
110
|
+
*
|
|
111
|
+
* @returns The *follower* component to run `service` in such a configuration, given the leader's address and the names of the shared computation graph collections to be mirrored from it (typically the `ResourceInputs` of `service`).
|
|
112
|
+
*/
|
|
113
|
+
export function asFollower(service, leader) {
|
|
114
|
+
return {
|
|
115
|
+
...service,
|
|
116
|
+
initialData: {},
|
|
117
|
+
externalServices: {
|
|
118
|
+
...service.externalServices,
|
|
119
|
+
__skip_leader: SkipExternalService.direct(leader.leader),
|
|
120
|
+
},
|
|
121
|
+
createGraph(_inputs, context) {
|
|
122
|
+
const mirroredCollections = {};
|
|
123
|
+
for (const collection of leader.collections) {
|
|
124
|
+
mirroredCollections[collection] = context.useExternalResource({
|
|
125
|
+
service: "__skip_leader",
|
|
126
|
+
identifier: "leader",
|
|
127
|
+
params: collection,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return mirroredCollections;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
78
134
|
//# sourceMappingURL=remote.js.map
|
package/dist/src/remote.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,iBAAiB;AACjB,OAAO,WAAW,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skipruntime/helpers",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/src/index.js"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"eventsource": "^2.0.2",
|
|
21
21
|
"express": "^4.21.1",
|
|
22
|
-
"@skipruntime/core": "0.0.
|
|
22
|
+
"@skipruntime/core": "0.0.14"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/eventsource": "^1.1.15"
|
package/src/external.ts
CHANGED
|
@@ -2,117 +2,7 @@ import type { Entry, ExternalService, Json } from "@skipruntime/core";
|
|
|
2
2
|
import { SkipUnknownResourceError } from "@skipruntime/core";
|
|
3
3
|
import { fetchJSON } from "./rest.js";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Interface required by `GenericExternalService` for external resources.
|
|
7
|
-
*/
|
|
8
|
-
export interface ExternalResource {
|
|
9
|
-
open(
|
|
10
|
-
instance: string,
|
|
11
|
-
params: Json,
|
|
12
|
-
callbacks: {
|
|
13
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
14
|
-
error: (error: Json) => void;
|
|
15
|
-
loading: () => void;
|
|
16
|
-
},
|
|
17
|
-
): void;
|
|
18
|
-
|
|
19
|
-
close(instance: string): void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* A generic external service providing external resources.
|
|
24
|
-
*
|
|
25
|
-
* `GenericExternalService` provides an implementation of `ExternalService` for external resources by lifting the `open` and `close` operations from `ExternalResource` to the `subscribe` and `unsubscribe` operations required by `ExternalService`.
|
|
26
|
-
*/
|
|
27
|
-
export class GenericExternalService implements ExternalService {
|
|
28
|
-
private readonly instances = new Map<string, ExternalResource>();
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param resources - Association of resource names to `ExternalResource`s.
|
|
32
|
-
*/
|
|
33
|
-
constructor(
|
|
34
|
-
private readonly resources: { [name: string]: ExternalResource },
|
|
35
|
-
) {}
|
|
36
|
-
|
|
37
|
-
subscribe(
|
|
38
|
-
instance: string,
|
|
39
|
-
resourceName: string,
|
|
40
|
-
params: Json,
|
|
41
|
-
callbacks: {
|
|
42
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
43
|
-
error: (error: Json) => void;
|
|
44
|
-
loading: () => void;
|
|
45
|
-
},
|
|
46
|
-
) {
|
|
47
|
-
const resource = this.resources[resourceName] as
|
|
48
|
-
| ExternalResource
|
|
49
|
-
| undefined;
|
|
50
|
-
if (!resource) {
|
|
51
|
-
throw new SkipUnknownResourceError(
|
|
52
|
-
`Unknown resource named '${resourceName}'`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
this.instances.set(instance, resource);
|
|
56
|
-
resource.open(instance, params, callbacks);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
unsubscribe(instance: string) {
|
|
60
|
-
const resource = this.instances.get(instance);
|
|
61
|
-
if (resource) {
|
|
62
|
-
resource.close(instance);
|
|
63
|
-
this.instances.delete(instance);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
shutdown(): Promise<void> {
|
|
68
|
-
return Promise.resolve();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
5
|
type Timeout = ReturnType<typeof setInterval>;
|
|
73
|
-
type Timeouts = { [name: string]: Timeout };
|
|
74
|
-
|
|
75
|
-
export class TimerResource implements ExternalResource {
|
|
76
|
-
private readonly intervals = new Map<string, { [name: string]: Timeout }>();
|
|
77
|
-
|
|
78
|
-
open(
|
|
79
|
-
instance: string,
|
|
80
|
-
params: Json,
|
|
81
|
-
callbacks: {
|
|
82
|
-
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
83
|
-
error: (error: Json) => void;
|
|
84
|
-
loading: () => void;
|
|
85
|
-
},
|
|
86
|
-
) {
|
|
87
|
-
const time = new Date().getTime();
|
|
88
|
-
const values: Entry<string, number>[] = [];
|
|
89
|
-
for (const name of Object.keys(params)) {
|
|
90
|
-
values.push([name, [time]]);
|
|
91
|
-
}
|
|
92
|
-
callbacks.update(values, true);
|
|
93
|
-
const intervals: Timeouts = {};
|
|
94
|
-
for (const [name, duration] of Object.entries(params)) {
|
|
95
|
-
const ms = Number(duration);
|
|
96
|
-
if (ms > 0) {
|
|
97
|
-
intervals[name] = setInterval(() => {
|
|
98
|
-
const newvalue: Entry<Json, Json> = [name, [new Date().getTime()]];
|
|
99
|
-
callbacks.update([newvalue], true);
|
|
100
|
-
}, ms);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
this.intervals.set(instance, intervals);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
close(instance: string): void {
|
|
107
|
-
const intervals = this.intervals.get(instance);
|
|
108
|
-
if (intervals != null) {
|
|
109
|
-
for (const interval of Object.values(intervals)) {
|
|
110
|
-
clearInterval(interval);
|
|
111
|
-
}
|
|
112
|
-
this.intervals.delete(instance);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
6
|
|
|
117
7
|
/**
|
|
118
8
|
* Encode params for external resource request.
|
|
@@ -134,47 +24,57 @@ export function defaultParamEncoder(params: Json): string {
|
|
|
134
24
|
}
|
|
135
25
|
|
|
136
26
|
/**
|
|
137
|
-
*
|
|
27
|
+
* Description of an external HTTP endpoint and how to poll it.
|
|
28
|
+
*
|
|
29
|
+
* The URL of the external resource is formed by appending the given base `url` and the result of `encodeParams(params)` where `params` are the parameters provided to [`Context#useExternalResource`](https://skiplabs.io/docs/api/core/interfaces/Context#useexternalresource)
|
|
30
|
+
*/
|
|
31
|
+
export interface PolledHTTPResource {
|
|
32
|
+
/**
|
|
33
|
+
* Base URL of resource to poll.
|
|
34
|
+
*/
|
|
35
|
+
url: string;
|
|
36
|
+
/**
|
|
37
|
+
* The interval of time to wait before refreshing the data, given in milliseconds
|
|
38
|
+
*/
|
|
39
|
+
interval: number;
|
|
40
|
+
/**
|
|
41
|
+
* Function to convert data received from external resource to `key`-`value` entries.
|
|
42
|
+
*/
|
|
43
|
+
conv: (data: Json) => Entry<Json, Json>[];
|
|
44
|
+
/**
|
|
45
|
+
* Function to use to encode params of type `Json` for external resource request.
|
|
46
|
+
*
|
|
47
|
+
* Note that the result of `encodeParams` may contain a `?` separator, but it need not be at the beginning of the returned string, so some parameters can be used in part of the URL preceding the `?`.
|
|
48
|
+
*/
|
|
49
|
+
encodeParams?: (params: Json) => string;
|
|
50
|
+
/**
|
|
51
|
+
* Optional parameters: additional `headers` to add to request, and `timeout` for request, in milliseconds. (default 1000ms)
|
|
52
|
+
*/
|
|
53
|
+
options?: { headers?: { [header: string]: string }; timeout?: number };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* An external HTTP service that is kept up-to-date by polling.
|
|
138
58
|
*
|
|
139
|
-
*
|
|
140
|
-
* @typeParam K - Type of keys.
|
|
141
|
-
* @typeParam V - Type of values.
|
|
59
|
+
* A `PolledExternalService` may be composed of one or more [`PolledHTTPResource`](https://skiplabs.io/docs/api/helpers/interfaces/PolledHTTPResource)s, each of which describes a single endpoint and how to poll it.
|
|
142
60
|
*/
|
|
143
|
-
export class
|
|
144
|
-
implements ExternalResource
|
|
145
|
-
{
|
|
61
|
+
export class PolledExternalService implements ExternalService {
|
|
146
62
|
private readonly intervals = new Map<string, Timeout>();
|
|
147
63
|
|
|
148
64
|
/**
|
|
149
|
-
* Construct a
|
|
65
|
+
* Construct a polled external service.
|
|
150
66
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* Note that the result of `encodeParams` contains the `?` separator, but it need not be at the beginning of the returned string, so some parameters can be used in part of the URL preceding the `?`.
|
|
154
|
-
*
|
|
155
|
-
* @param url - HTTP endpoint of external resource to poll.
|
|
156
|
-
* @param duration - Refresh interval, in milliseconds.
|
|
157
|
-
* @param conv - Function to convert data of type `S` received from external resource to `key`-`value` entries.
|
|
158
|
-
* @param encodeParams - Function to use to encode params of type `Json` for external resource request.
|
|
159
|
-
* @param options - Optional parameters.
|
|
160
|
-
* @param options.headers - Additional headers to add to request.
|
|
161
|
-
* @param options.timeout - Timeout for request, in milliseconds. Defaults to 1000ms.
|
|
67
|
+
* @param resources - Specification(s) of external resource(s) to poll
|
|
162
68
|
*/
|
|
163
69
|
constructor(
|
|
164
|
-
private readonly
|
|
165
|
-
|
|
166
|
-
private readonly conv: (data: S) => Entry<K, V>[],
|
|
167
|
-
private readonly encodeParams: (
|
|
168
|
-
params: Json,
|
|
169
|
-
) => string = defaultParamEncoder,
|
|
170
|
-
private readonly options?: {
|
|
171
|
-
headers?: { [header: string]: string };
|
|
172
|
-
timeout?: number;
|
|
70
|
+
private readonly resources: {
|
|
71
|
+
[resource: string]: PolledHTTPResource;
|
|
173
72
|
},
|
|
174
73
|
) {}
|
|
175
74
|
|
|
176
|
-
|
|
75
|
+
subscribe(
|
|
177
76
|
instance: string,
|
|
77
|
+
resourceName: string,
|
|
178
78
|
params: Json,
|
|
179
79
|
callbacks: {
|
|
180
80
|
update: (updates: Entry<Json, Json>[], isInit: boolean) => void;
|
|
@@ -182,12 +82,18 @@ export class Polled<S extends Json, K extends Json, V extends Json>
|
|
|
182
82
|
loading: () => void;
|
|
183
83
|
},
|
|
184
84
|
) {
|
|
185
|
-
const
|
|
85
|
+
const resource = this.resources[resourceName];
|
|
86
|
+
if (!resource)
|
|
87
|
+
throw new SkipUnknownResourceError(
|
|
88
|
+
`Unknown resource named '${resourceName}'`,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const url = `${resource.url}${(resource.encodeParams ?? defaultParamEncoder)(params)}`;
|
|
186
92
|
const call = () => {
|
|
187
93
|
callbacks.loading();
|
|
188
|
-
fetchJSON(url, "GET",
|
|
94
|
+
fetchJSON(url, "GET", resource.options ?? {})
|
|
189
95
|
.then((r) => {
|
|
190
|
-
callbacks.update(
|
|
96
|
+
callbacks.update(resource.conv(r[0] ?? []), true);
|
|
191
97
|
})
|
|
192
98
|
.catch((e: unknown) => {
|
|
193
99
|
callbacks.error(e instanceof Error ? e.message : JSON.stringify(e));
|
|
@@ -195,14 +101,22 @@ export class Polled<S extends Json, K extends Json, V extends Json>
|
|
|
195
101
|
});
|
|
196
102
|
};
|
|
197
103
|
call();
|
|
198
|
-
this.intervals.set(instance, setInterval(call,
|
|
104
|
+
this.intervals.set(instance, setInterval(call, resource.interval));
|
|
199
105
|
}
|
|
200
106
|
|
|
201
|
-
|
|
107
|
+
unsubscribe(instance: string): void {
|
|
202
108
|
const interval = this.intervals.get(instance);
|
|
203
109
|
if (interval) {
|
|
204
110
|
clearInterval(interval);
|
|
205
111
|
this.intervals.delete(instance);
|
|
206
112
|
}
|
|
207
113
|
}
|
|
114
|
+
|
|
115
|
+
shutdown(): Promise<void> {
|
|
116
|
+
for (const [instance, interval] of Object.entries(this.intervals)) {
|
|
117
|
+
clearInterval(interval as Timeout);
|
|
118
|
+
this.intervals.delete(instance);
|
|
119
|
+
}
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
}
|
|
208
122
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
export {
|
|
8
8
|
defaultParamEncoder,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Polled,
|
|
12
|
-
TimerResource,
|
|
9
|
+
PolledExternalService,
|
|
10
|
+
type PolledHTTPResource,
|
|
13
11
|
} from "./external.js";
|
|
14
|
-
export { SkipExternalService } from "./remote.js";
|
|
12
|
+
export { SkipExternalService, asLeader, asFollower } from "./remote.js";
|
|
15
13
|
export { SkipServiceBroker, fetchJSON, type Entrypoint } from "./rest.js";
|
|
16
14
|
export { Count, Max, Min, Sum } from "./utils.js";
|
package/src/remote.ts
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
// in nodejs LTS.
|
|
3
3
|
import EventSource from "eventsource";
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
Context,
|
|
7
|
+
EagerCollection,
|
|
8
|
+
Entry,
|
|
9
|
+
ExternalService,
|
|
10
|
+
Json,
|
|
11
|
+
NamedCollections,
|
|
12
|
+
Resource,
|
|
13
|
+
SkipService,
|
|
14
|
+
} from "@skipruntime/core";
|
|
15
|
+
import { SkipError } from "@skipruntime/core";
|
|
6
16
|
|
|
7
17
|
import type { Entrypoint } from "./rest.js";
|
|
8
18
|
|
|
@@ -100,3 +110,72 @@ export class SkipExternalService implements ExternalService {
|
|
|
100
110
|
return Promise.resolve();
|
|
101
111
|
}
|
|
102
112
|
}
|
|
113
|
+
|
|
114
|
+
class LeaderResource implements Resource {
|
|
115
|
+
private collection: string;
|
|
116
|
+
|
|
117
|
+
constructor(param: Json) {
|
|
118
|
+
if (typeof param == "string") this.collection = param;
|
|
119
|
+
else
|
|
120
|
+
throw new SkipError(
|
|
121
|
+
"Followers must specify a shared collection to mirror from leader.",
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
instantiate(collections: NamedCollections): EagerCollection<Json, Json> {
|
|
126
|
+
if (this.collection in collections) return collections[this.collection]!;
|
|
127
|
+
throw new SkipError(
|
|
128
|
+
`Unknown shared collection in leader: ${this.collection}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Run a `SkipService` as the *leader* in a leader-follower topology.
|
|
135
|
+
*
|
|
136
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
137
|
+
*
|
|
138
|
+
* @returns The *leader* component to run `service` in such a configuration.
|
|
139
|
+
*/
|
|
140
|
+
export function asLeader(service: SkipService): SkipService {
|
|
141
|
+
//TODO: add mechanism to split externals between leader/follower
|
|
142
|
+
return {
|
|
143
|
+
...service,
|
|
144
|
+
resources: { leader: LeaderResource },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Run a `SkipService` as a *follower* in a leader-follower topology.
|
|
150
|
+
*
|
|
151
|
+
* Instead of running a `service` on one machine, it can be distributed across multiple in a leader-follower architecture, with one "leader" maintaining the shared computation graph and one or more "followers" across which client-requested resource instances are distributed.
|
|
152
|
+
*
|
|
153
|
+
* @returns The *follower* component to run `service` in such a configuration, given the leader's address and the names of the shared computation graph collections to be mirrored from it (typically the `ResourceInputs` of `service`).
|
|
154
|
+
*/
|
|
155
|
+
export function asFollower(
|
|
156
|
+
service: SkipService,
|
|
157
|
+
leader: {
|
|
158
|
+
leader: { host: string; streaming_port: number; control_port: number };
|
|
159
|
+
collections: string[];
|
|
160
|
+
},
|
|
161
|
+
): SkipService {
|
|
162
|
+
return {
|
|
163
|
+
...service,
|
|
164
|
+
initialData: {},
|
|
165
|
+
externalServices: {
|
|
166
|
+
...service.externalServices,
|
|
167
|
+
__skip_leader: SkipExternalService.direct(leader.leader),
|
|
168
|
+
},
|
|
169
|
+
createGraph(_inputs: object, context: Context): NamedCollections {
|
|
170
|
+
const mirroredCollections: NamedCollections = {};
|
|
171
|
+
for (const collection of leader.collections) {
|
|
172
|
+
mirroredCollections[collection] = context.useExternalResource({
|
|
173
|
+
service: "__skip_leader",
|
|
174
|
+
identifier: "leader",
|
|
175
|
+
params: collection,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return mirroredCollections;
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|