@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.
@@ -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
- * An external resource that is refreshed at some polling interval.
12
+ * Description of an external HTTP endpoint and how to poll it.
55
13
  *
56
- * @typeParam S - Type of data received from external resource.
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 declare class Polled<S extends Json, K extends Json, V extends Json> implements ExternalResource {
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
- * Construct a `Polled` external resource.
69
- *
70
- * 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.
71
- *
72
- * 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 `?`.
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
- * @param url - HTTP endpoint of external resource to poll.
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
- constructor(url: string, duration: number, conv: (data: S) => Entry<K, V>[], encodeParams?: (params: Json) => string, options?: {
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
- } | undefined);
88
- open(instance: string, params: Json, callbacks: {
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
- close(instance: string): void;
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":"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"}
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"}
@@ -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 resource that is refreshed at some polling interval.
26
+ * An external HTTP service that is kept up-to-date by polling.
92
27
  *
93
- * @typeParam S - Type of data received from external resource.
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 Polled {
30
+ export class PolledExternalService {
98
31
  /**
99
- * Construct a `Polled` external resource.
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 url - HTTP endpoint of external resource to poll.
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(url, duration, conv, encodeParams = defaultParamEncoder, options) {
114
- this.url = url;
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
- open(instance, params, callbacks) {
122
- const url = `${this.url}${this.encodeParams(params)}`;
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", this.options)
47
+ fetchJSON(url, "GET", resource.options ?? {})
126
48
  .then((r) => {
127
- callbacks.update(this.conv(r[0]), true);
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, this.duration));
57
+ this.intervals.set(instance, setInterval(call, resource.interval));
136
58
  }
137
- close(instance) {
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
@@ -1 +1 @@
1
- {"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/external.ts"],"names":[],"mappings":"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"}
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * @packageDocumentation
5
5
  */
6
- export { defaultParamEncoder, type ExternalResource, GenericExternalService, Polled, TimerResource, } from "./external.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,sBAAsB,EACtB,MAAM,EACN,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,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"}
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, GenericExternalService, Polled, TimerResource, } from "./external.js";
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,mBAAmB,EAEnB,sBAAsB,EACtB,MAAM,EACN,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAmB,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC"}
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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEtE,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"}
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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/remote.ts"],"names":[],"mappings":"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"}
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.12",
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.12"
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
- * An external resource that is refreshed at some polling interval.
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
- * @typeParam S - Type of data received from external resource.
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 Polled<S extends Json, K extends Json, V extends Json>
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 `Polled` external resource.
65
+ * Construct a polled external service.
150
66
  *
151
- * 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.
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 url: string,
165
- private readonly duration: number,
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
- open(
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 url = `${this.url}${this.encodeParams(params)}`;
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", this.options)
94
+ fetchJSON(url, "GET", resource.options ?? {})
189
95
  .then((r) => {
190
- callbacks.update(this.conv(r[0] as S), true);
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, this.duration));
104
+ this.intervals.set(instance, setInterval(call, resource.interval));
199
105
  }
200
106
 
201
- close(instance: string): void {
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
- type ExternalResource,
10
- GenericExternalService,
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 { Entry, ExternalService, Json } from "@skipruntime/core";
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
+ }