@toa.io/extensions.cadence 1.0.0-alpha.284
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/CHANGELOG.md +10 -0
- package/components/cadence.metronome/manifest.toa.yaml +72 -0
- package/components/cadence.metronome/operations/delay.js +28 -0
- package/components/cadence.metronome/operations/settle.js +9 -0
- package/components/cadence.metronome/types/index.d.ts +3 -0
- package/components/cadence.metronome/types/toa.d.ts +45 -0
- package/package.json +28 -0
- package/readme.md +188 -0
- package/schemas/annotation.cos.yaml +9 -0
- package/schemas/declaration.cos.yaml +21 -0
- package/source/Aspect.ts +64 -0
- package/source/Composition.ts +47 -0
- package/source/Dispatcher.test.ts +271 -0
- package/source/Dispatcher.ts +221 -0
- package/source/Factory.ts +73 -0
- package/source/Local.ts +39 -0
- package/source/Pulse.test.ts +274 -0
- package/source/Pulse.ts +202 -0
- package/source/const.ts +23 -0
- package/source/context.ts +10 -0
- package/source/deployment.ts +33 -0
- package/source/index.ts +8 -0
- package/source/manifest.test.ts +58 -0
- package/source/manifest.ts +51 -0
- package/source/schemas.ts +10 -0
- package/source/types.ts +41 -0
- package/transpiled/Aspect.d.ts +26 -0
- package/transpiled/Aspect.js +46 -0
- package/transpiled/Aspect.js.map +1 -0
- package/transpiled/Composition.d.ts +14 -0
- package/transpiled/Composition.js +30 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Dispatcher.d.ts +63 -0
- package/transpiled/Dispatcher.js +170 -0
- package/transpiled/Dispatcher.js.map +1 -0
- package/transpiled/Factory.d.ts +22 -0
- package/transpiled/Factory.js +56 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Local.d.ts +18 -0
- package/transpiled/Local.js +29 -0
- package/transpiled/Local.js.map +1 -0
- package/transpiled/Pulse.d.ts +64 -0
- package/transpiled/Pulse.js +148 -0
- package/transpiled/Pulse.js.map +1 -0
- package/transpiled/const.d.ts +15 -0
- package/transpiled/const.js +19 -0
- package/transpiled/const.js.map +1 -0
- package/transpiled/context.d.ts +3 -0
- package/transpiled/context.js +9 -0
- package/transpiled/context.js.map +1 -0
- package/transpiled/deployment.d.ts +9 -0
- package/transpiled/deployment.js +26 -0
- package/transpiled/deployment.js.map +1 -0
- package/transpiled/index.d.ts +7 -0
- package/transpiled/index.js +7 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +11 -0
- package/transpiled/manifest.js +39 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/schemas.d.ts +4 -0
- package/transpiled/schemas.js +7 -0
- package/transpiled/schemas.js.map +1 -0
- package/transpiled/types.d.ts +34 -0
- package/transpiled/types.js +2 -0
- package/transpiled/types.js.map +1 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
import { Connector } from '@toa.io/core';
|
|
3
|
+
/**
|
|
4
|
+
* A recurring call to an operation of its own component, on the cadence its manifest states.
|
|
5
|
+
*
|
|
6
|
+
* Nothing is stored. The cycle is split into `n` intervals and the current one is a function of
|
|
7
|
+
* the clock, so every replica computes the same `i` without talking to any other; the one that
|
|
8
|
+
* owns `i` is the one that calls. An interval no replica was alive for is not made up
|
|
9
|
+
* afterwards — the operation is expected to select by what is still due rather than by where
|
|
10
|
+
* the cycle has reached.
|
|
11
|
+
*/
|
|
12
|
+
export class Pulse extends Connector {
|
|
13
|
+
endpoint;
|
|
14
|
+
label;
|
|
15
|
+
/** milliseconds one whole cycle takes */
|
|
16
|
+
cycle;
|
|
17
|
+
/** intervals the cycle is split into */
|
|
18
|
+
n;
|
|
19
|
+
local;
|
|
20
|
+
atom;
|
|
21
|
+
/** span options, built once */
|
|
22
|
+
options;
|
|
23
|
+
/**
|
|
24
|
+
* The ordinal of the interval this process has already handled, so an early wake-up is not a
|
|
25
|
+
* second call. An ordinal and not an index: with one interval per cycle the index never
|
|
26
|
+
* changes, and comparing it would suppress every call after the first.
|
|
27
|
+
*/
|
|
28
|
+
fired = 0;
|
|
29
|
+
timer;
|
|
30
|
+
/** the call in flight, awaited on close */
|
|
31
|
+
firing;
|
|
32
|
+
closing = false;
|
|
33
|
+
constructor(definition, local, atom) {
|
|
34
|
+
super();
|
|
35
|
+
const { locator, endpoint, cycle, intervals } = definition;
|
|
36
|
+
this.endpoint = endpoint;
|
|
37
|
+
this.label = `${locator.id}.${endpoint}`;
|
|
38
|
+
this.cycle = cycle * 1000;
|
|
39
|
+
this.n = intervals;
|
|
40
|
+
this.local = local;
|
|
41
|
+
this.atom = atom;
|
|
42
|
+
this.depends(local);
|
|
43
|
+
this.depends(atom);
|
|
44
|
+
this.options = {
|
|
45
|
+
name: `${this.label} pulse`,
|
|
46
|
+
kind: 'producer',
|
|
47
|
+
service: locator.id
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* How many intervals have passed since the epoch, `now` included. Against the cycle rather
|
|
52
|
+
* than by adding up gaps, so a cycle that does not divide evenly into its intervals neither
|
|
53
|
+
* drifts nor accumulates error.
|
|
54
|
+
*
|
|
55
|
+
* The arithmetic is exact rather than double: `now` is milliseconds since 1970 and `n` may be
|
|
56
|
+
* as large as the cycle has seconds, so their product leaves the range a double is exact in
|
|
57
|
+
* for a long cycle cut finely. It is evaluated once per interval, where this costs nothing.
|
|
58
|
+
*/
|
|
59
|
+
ordinal(now) {
|
|
60
|
+
return Number(BigInt(now) * BigInt(this.n) / BigInt(this.cycle));
|
|
61
|
+
}
|
|
62
|
+
/** Which of the cycle's intervals `now` falls in. */
|
|
63
|
+
index(now) {
|
|
64
|
+
return this.ordinal(now) % this.n;
|
|
65
|
+
}
|
|
66
|
+
/** When the interval `now` falls in gives way to the next one. */
|
|
67
|
+
boundary(now) {
|
|
68
|
+
const cycle = BigInt(this.cycle);
|
|
69
|
+
const n = BigInt(this.n);
|
|
70
|
+
const next = BigInt(this.ordinal(now) + 1);
|
|
71
|
+
// the earliest millisecond whose ordinal is the next one
|
|
72
|
+
return Number((next * cycle + n - 1n) / n);
|
|
73
|
+
}
|
|
74
|
+
async open() {
|
|
75
|
+
// whatever interval it is now, this replica was not there when it began, so it is not
|
|
76
|
+
// this one's to call — the first call is at the next boundary
|
|
77
|
+
this.fired = this.ordinal(Date.now());
|
|
78
|
+
this.arm();
|
|
79
|
+
}
|
|
80
|
+
async close() {
|
|
81
|
+
this.closing = true;
|
|
82
|
+
clearTimeout(this.timer);
|
|
83
|
+
// a call still running holds the remote it was made through, which is torn down after
|
|
84
|
+
// this returns. Bounded, because an operation that never returns would outlive any
|
|
85
|
+
// grace period and the interval is over either way
|
|
86
|
+
if (this.firing !== undefined)
|
|
87
|
+
await Promise.race([this.firing, delay(DRAIN)]);
|
|
88
|
+
}
|
|
89
|
+
arm() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
// a delay past this fires at once rather than late, so a long cycle is waited out in
|
|
92
|
+
// instalments; the wake-up finds the same interval and simply arms again
|
|
93
|
+
const wait = Math.min(this.boundary(now) - now, MAX_DELAY);
|
|
94
|
+
this.timer = setTimeout(() => this.tick(), wait);
|
|
95
|
+
this.timer.unref();
|
|
96
|
+
}
|
|
97
|
+
tick() {
|
|
98
|
+
if (this.closing)
|
|
99
|
+
return;
|
|
100
|
+
const ordinal = this.ordinal(Date.now());
|
|
101
|
+
// a timer may wake a millisecond before the boundary it was set for, and a cycle longer
|
|
102
|
+
// than one timer wakes several times on the way — both are the interval already handled
|
|
103
|
+
if (ordinal !== this.fired) {
|
|
104
|
+
this.fired = ordinal;
|
|
105
|
+
void this.fire(ordinal % this.n);
|
|
106
|
+
}
|
|
107
|
+
this.arm();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Nothing queues here. A call still running when the next boundary arrives means the work
|
|
111
|
+
* does not fit the gap, and starting a second one would only make that worse.
|
|
112
|
+
*/
|
|
113
|
+
async fire(i) {
|
|
114
|
+
if (this.firing !== undefined) {
|
|
115
|
+
console.warn('Pulse skipped: the previous call has not returned', { pulse: this.label, interval: i });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const owned = this.atom.slots(this.n);
|
|
119
|
+
if (owned === null) {
|
|
120
|
+
console.warn('Pulse skipped: this replica owns nothing', { pulse: this.label, interval: i });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!owned.includes(i))
|
|
124
|
+
return;
|
|
125
|
+
/*
|
|
126
|
+
* A pulse calls and waits, so an operation that raised comes back as an exception here —
|
|
127
|
+
* the runtime carries it across and rethrows it in this process. What to do about one is a
|
|
128
|
+
* choice with no right answer, and asking would put the whole of it in front of anyone who
|
|
129
|
+
* only wanted an operation called every hour. So the interval is skipped: it is reported,
|
|
130
|
+
* and the next one is called as though it had not happened.
|
|
131
|
+
*/
|
|
132
|
+
const firing = console.span(this.options, async () => await this.local.invoke(this.endpoint, { input: { n: this.n, i } }))
|
|
133
|
+
.catch((error) => {
|
|
134
|
+
console.error('Pulse failed', { pulse: this.label, interval: i, error });
|
|
135
|
+
})
|
|
136
|
+
.finally(() => { this.firing = undefined; });
|
|
137
|
+
this.firing = firing;
|
|
138
|
+
await firing;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function delay(ms) {
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, ms).unref());
|
|
143
|
+
}
|
|
144
|
+
/** `setTimeout` fires immediately past this, so a longer wait is taken in instalments */
|
|
145
|
+
const MAX_DELAY = 2 ** 31 - 1;
|
|
146
|
+
/** how long a call in flight is waited for on close */
|
|
147
|
+
const DRAIN = 10_000;
|
|
148
|
+
//# sourceMappingURL=Pulse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Pulse.js","sourceRoot":"","sources":["../source/Pulse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The component the extension ships to hold delayed calls. */
|
|
2
|
+
export declare const NAMESPACE = "cadence";
|
|
3
|
+
export declare const COMPONENT = "metronome";
|
|
4
|
+
/**
|
|
5
|
+
* Constant, never configuration: a row carries its lane, so lowering this would leave rows in
|
|
6
|
+
* lanes nobody reads any more. It is also the ceiling on replicas dispatching, and a power of
|
|
7
|
+
* two so that the common replica counts divide evenly. The outbox says the same of its own.
|
|
8
|
+
*/
|
|
9
|
+
export declare const LANES = 128;
|
|
10
|
+
/** seconds between passes over the calls waiting to be made, and how far ahead each reaches */
|
|
11
|
+
export declare const DISCRETENESS = 60;
|
|
12
|
+
/** rows one scan brings back */
|
|
13
|
+
export declare const BATCH = 200;
|
|
14
|
+
/** A number from the environment, where a deployment or a test suite states one. */
|
|
15
|
+
export declare function number(variable: string, fallback: number): number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The component the extension ships to hold delayed calls. */
|
|
2
|
+
export const NAMESPACE = 'cadence';
|
|
3
|
+
export const COMPONENT = 'metronome';
|
|
4
|
+
/**
|
|
5
|
+
* Constant, never configuration: a row carries its lane, so lowering this would leave rows in
|
|
6
|
+
* lanes nobody reads any more. It is also the ceiling on replicas dispatching, and a power of
|
|
7
|
+
* two so that the common replica counts divide evenly. The outbox says the same of its own.
|
|
8
|
+
*/
|
|
9
|
+
export const LANES = 128;
|
|
10
|
+
/** seconds between passes over the calls waiting to be made, and how far ahead each reaches */
|
|
11
|
+
export const DISCRETENESS = 60;
|
|
12
|
+
/** rows one scan brings back */
|
|
13
|
+
export const BATCH = 200;
|
|
14
|
+
/** A number from the environment, where a deployment or a test suite states one. */
|
|
15
|
+
export function number(variable, fallback) {
|
|
16
|
+
const value = Number(process.env[variable]);
|
|
17
|
+
return Number.isNaN(value) || value <= 0 ? fallback : value;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=const.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"const.js","sourceRoot":"","sources":["../source/const.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,SAAS,CAAA;AAClC,MAAM,CAAC,MAAM,SAAS,GAAG,WAAW,CAAA;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;AAExB,+FAA+F;AAC/F,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAA;AAE9B,gCAAgC;AAChC,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA;AAExB,oFAAoF;AACpF,MAAM,UAAU,MAAM,CAAE,QAAgB,EAAE,QAAgB;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAA;IAE3C,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAA;AAC7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../source/context.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,UAAU,OAAO;IACrB,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,EAAE,4BAA4B,EAAE,CAAC,OAAO,CAAC,EAAE;KACrD,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Annotation } from './types.js';
|
|
2
|
+
import type { Dependency } from '@toa.io/operations';
|
|
3
|
+
/**
|
|
4
|
+
* The component that keeps delayed calls runs in a service of its own, the way the identity
|
|
5
|
+
* components run in the gateway. A composition may claim it and run it in its own pods instead.
|
|
6
|
+
*/
|
|
7
|
+
export declare function deployment(_: unknown, annotation?: Annotation | null): Dependency;
|
|
8
|
+
/** The extension is deployed for the sake of `delay`, which nothing has to declare to use. */
|
|
9
|
+
export declare const standalone = true;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { components } from './Composition.js';
|
|
3
|
+
import { DISCRETENESS, NAMESPACE } from './const.js';
|
|
4
|
+
import * as schemas from './schemas.js';
|
|
5
|
+
/**
|
|
6
|
+
* The component that keeps delayed calls runs in a service of its own, the way the identity
|
|
7
|
+
* components run in the gateway. A composition may claim it and run it in its own pods instead.
|
|
8
|
+
*/
|
|
9
|
+
export function deployment(_, annotation) {
|
|
10
|
+
schemas.annotation.validate(annotation ?? null, 'Invalid cadence annotation');
|
|
11
|
+
// stated in seconds and carried in milliseconds, so that what an application writes is what
|
|
12
|
+
// the rest of a manifest is written in
|
|
13
|
+
const discreteness = (annotation?.discreteness ?? DISCRETENESS) * 1000;
|
|
14
|
+
const variables = [{ name: 'TOA_CADENCE_DISCRETENESS', value: String(discreteness) }];
|
|
15
|
+
const service = {
|
|
16
|
+
group: NAMESPACE,
|
|
17
|
+
name: 'metronome',
|
|
18
|
+
version: JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version,
|
|
19
|
+
components: components().labels,
|
|
20
|
+
variables
|
|
21
|
+
};
|
|
22
|
+
return { services: [service] };
|
|
23
|
+
}
|
|
24
|
+
/** The extension is deployed for the sake of `delay`, which nothing has to declare to use. */
|
|
25
|
+
export const standalone = true;
|
|
26
|
+
//# sourceMappingURL=deployment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment.js","sourceRoot":"","sources":["../source/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAE,CAAU,EAAE,UAA8B;IACpE,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAoB,UAAU,IAAI,IAAI,EAAE,4BAA4B,CAAC,CAAA;IAEhG,4FAA4F;IAC5F,uCAAuC;IACvC,MAAM,YAAY,GAAG,CAAC,UAAU,EAAE,YAAY,IAAI,YAAY,CAAC,GAAG,IAAI,CAAA;IACtE,MAAM,SAAS,GACb,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IAErE,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO;QAC9F,UAAU,EAAE,UAAU,EAAE,CAAC,MAAM;QAC/B,SAAS;KACV,CAAA;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAA;AAChC,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Factory } from './Factory.js';
|
|
2
|
+
export { manifest } from './manifest.js';
|
|
3
|
+
export { components } from './Composition.js';
|
|
4
|
+
export { deployment, standalone } from './deployment.js';
|
|
5
|
+
export { context } from './context.js';
|
|
6
|
+
export { LANES } from './const.js';
|
|
7
|
+
export type { Declaration, Delay, Pulse } from './types.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { Factory } from './Factory.js';
|
|
2
|
+
export { manifest } from './manifest.js';
|
|
3
|
+
export { components } from './Composition.js';
|
|
4
|
+
export { deployment, standalone } from './deployment.js';
|
|
5
|
+
export { context } from './context.js';
|
|
6
|
+
export { LANES } from './const.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Declaration } from './types.js';
|
|
2
|
+
import type { Manifest } from '@toa.io/norm';
|
|
3
|
+
/**
|
|
4
|
+
* The `cadence:` block of a component manifest: the operations called on a cadence, by the
|
|
5
|
+
* operation each one calls. Norm hands over the whole manifest, so what the declaration refers
|
|
6
|
+
* to is checked here rather than left to fail at boot.
|
|
7
|
+
*
|
|
8
|
+
* A component that declares nothing still has a declaration. `cadence: ~` is how one that only
|
|
9
|
+
* delays calls says so, and it has to produce a value or norm rejects the extension.
|
|
10
|
+
*/
|
|
11
|
+
export declare function manifest(declaration: Declaration | null | undefined, component: Manifest): Declaration;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as schemas from './schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* The `cadence:` block of a component manifest: the operations called on a cadence, by the
|
|
4
|
+
* operation each one calls. Norm hands over the whole manifest, so what the declaration refers
|
|
5
|
+
* to is checked here rather than left to fail at boot.
|
|
6
|
+
*
|
|
7
|
+
* A component that declares nothing still has a declaration. `cadence: ~` is how one that only
|
|
8
|
+
* delays calls says so, and it has to produce a value or norm rejects the extension.
|
|
9
|
+
*/
|
|
10
|
+
export function manifest(declaration, component) {
|
|
11
|
+
const normalized = expand(declaration);
|
|
12
|
+
// `intervals` is defaulted by the schema, so the declaration is only whole after this
|
|
13
|
+
schemas.declaration.validate(normalized, 'Invalid cadence declaration');
|
|
14
|
+
for (const [endpoint, pulse] of Object.entries(normalized)) {
|
|
15
|
+
const operation = component.operations?.[endpoint];
|
|
16
|
+
if (operation === undefined)
|
|
17
|
+
throw new Error(`Pulse refers to undefined operation '${endpoint}'`);
|
|
18
|
+
if (!TYPES.has(operation.type))
|
|
19
|
+
throw new Error(`Pulse '${endpoint}' must refer to an operation of the allowed types: ` +
|
|
20
|
+
[...TYPES].join(', '));
|
|
21
|
+
if (pulse.intervals > pulse.cycle)
|
|
22
|
+
throw new Error(`Pulse '${endpoint}' splits a cycle of ${pulse.cycle} seconds into ` +
|
|
23
|
+
`${pulse.intervals} intervals, which is less than a second each`);
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
/** A pulse whose cycle is not split has nothing to state but the cycle. */
|
|
28
|
+
function expand(declaration) {
|
|
29
|
+
if (declaration === null || declaration === undefined)
|
|
30
|
+
return {};
|
|
31
|
+
return Object.fromEntries(Object.entries(declaration)
|
|
32
|
+
.map(([endpoint, pulse]) => [endpoint, typeof pulse === 'number' ? { cycle: pulse } : pulse]));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The types a receiver may refer to. An operation that produces no side effects has nothing to
|
|
36
|
+
* be called periodically for.
|
|
37
|
+
*/
|
|
38
|
+
const TYPES = new Set(['transition', 'assignment', 'effect', 'unmanaged']);
|
|
39
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../source/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAIvC;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAE,WAA2C,EACnE,SAAmB;IACnB,MAAM,UAAU,GAA4B,MAAM,CAAC,WAAW,CAAC,CAAA;IAE/D,sFAAsF;IACtF,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAc,UAAU,EAAE,6BAA6B,CAAC,CAAA;IAEpF,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3D,MAAM,SAAS,GAAG,SAAS,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,CAAA;QAElD,IAAI,SAAS,KAAK,SAAS;YACzB,MAAM,IAAI,KAAK,CAAC,wCAAwC,QAAQ,GAAG,CAAC,CAAA;QAEtE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,UAAU,QAAQ,qDAAqD;gBACrF,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QAE1B,IAAI,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK;YAC/B,MAAM,IAAI,KAAK,CAAC,UAAU,QAAQ,uBAAuB,KAAK,CAAC,KAAK,gBAAgB;gBAClF,GAAG,KAAK,CAAC,SAAS,8CAA8C,CAAC,CAAA;IACvE,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CAAE,WAA2C;IAC1D,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IAEhE,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;SAClD,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CACzB,CAAC,QAAQ,EAAE,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import * as schemas from '@toa.io/schemas';
|
|
3
|
+
const path = resolve(import.meta.dirname, '../schemas');
|
|
4
|
+
const namespace = schemas.namespace(path);
|
|
5
|
+
export const declaration = namespace.schema('declaration');
|
|
6
|
+
export const annotation = namespace.schema('annotation');
|
|
7
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../source/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAA;AAI1C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;AACvD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAwB,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;AAC/E,MAAM,CAAC,MAAM,UAAU,GAAuB,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** What one component declares under `cadence:`, by the operation each one calls. */
|
|
2
|
+
export type Declaration = Record<string, Pulse>;
|
|
3
|
+
export interface Pulse {
|
|
4
|
+
/** seconds one whole cycle takes */
|
|
5
|
+
cycle: number;
|
|
6
|
+
/** intervals the cycle is split into; the gap between calls is `cycle / intervals` */
|
|
7
|
+
intervals: number;
|
|
8
|
+
}
|
|
9
|
+
/** `context.delay` — a call to be made later, answering the id that cancels it. */
|
|
10
|
+
export interface Delay {
|
|
11
|
+
(endpoint: string, request: object | null, options: Options): Promise<string>;
|
|
12
|
+
/** Raises where the id was never issued. */
|
|
13
|
+
cancel: (id: string) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** milliseconds from now */
|
|
17
|
+
interval: number;
|
|
18
|
+
/**
|
|
19
|
+
* Milliseconds the call may be late and still be made, or `null` for no bound at all.
|
|
20
|
+
*
|
|
21
|
+
* Stated rather than defaulted, because only the caller knows whether a late call is still
|
|
22
|
+
* the right call: an unpaid order expires on time or not at all, and a report is wanted
|
|
23
|
+
* whenever it can be had. Nothing else in the system can tell those apart.
|
|
24
|
+
*
|
|
25
|
+
* An upper bound on lateness, not a promise of timeliness. It is bounded below by how often
|
|
26
|
+
* a dispatcher scans, so a bound shorter than that is one nothing can honour.
|
|
27
|
+
*/
|
|
28
|
+
overdue: number | null;
|
|
29
|
+
}
|
|
30
|
+
/** What an application states under `cadence:` in its context. */
|
|
31
|
+
export interface Annotation {
|
|
32
|
+
/** seconds between passes over the calls waiting to be made */
|
|
33
|
+
discreteness?: number;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../source/types.ts"],"names":[],"mappings":""}
|