@toa.io/extensions.cadence 1.0.0-alpha.324 → 1.0.0-alpha.326

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,31 +1,13 @@
1
- import { LANES } from '@toa.io/extensions.cadence'
1
+ import { row } from '@toa.io/extensions.cadence'
2
2
 
3
3
  /**
4
4
  * A call to make later. The request carries no query, so the transition is handed a new object.
5
- *
6
- * The lane is random, not one this replica owns. An outbox row is written into an owned lane so
7
- * that in steady state a replica settles its own rows before it ever reads them — but a delayed
8
- * row has no immediate path to settle, so nothing is gained, and an even spread is what the
9
- * dispatchers want.
5
+ * What is stored of it is what `row` says, which a schedule stores as well.
10
6
  */
11
7
  export function transition(input, entry) {
12
- const due = Date.now() + input.interval
8
+ const { endpoint, interval, overdue, request, trail } = input
13
9
 
14
- entry.lane = Math.floor(Math.random() * LANES)
15
- entry.due = due
16
- entry.endpoint = input.endpoint
17
-
18
- // absolute, because the bound is the caller's and a scan reads rows of many callers at once.
19
- // No bound is the end of representable time rather than an absent field, so that one
20
- // comparison answers for every row
21
- entry.expires = input.overdue === null ? Number.MAX_SAFE_INTEGER : due + input.overdue
22
-
23
- // a call that takes no request has none: the entity's `request` is an object where it is
24
- // there at all, and a null would not fit it
25
- if (input.request !== undefined) entry.request = input.request
26
-
27
- // the chain that asked for the call, absent where the caller unchained it
28
- if (input.trail !== undefined) entry.trail = input.trail
10
+ Object.assign(entry, row({ endpoint, due: Date.now() + interval, overdue, request, trail }))
29
11
 
30
12
  return entry.id
31
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.cadence",
3
- "version": "1.0.0-alpha.324",
3
+ "version": "1.0.0-alpha.326",
4
4
  "type": "module",
5
5
  "description": "Toa Schedule",
6
6
  "author": "temich <tema.gurtovoy@gmail.com>",
@@ -26,14 +26,14 @@
26
26
  "!**/.env*"
27
27
  ],
28
28
  "dependencies": {
29
- "@toa.io/core": "1.0.0-alpha.323",
30
- "@toa.io/definitions": "1.0.0-alpha.324",
29
+ "@toa.io/core": "1.0.0-alpha.326",
30
+ "@toa.io/definitions": "1.0.0-alpha.326",
31
31
  "@toa.io/generic": "1.0.0-alpha.311",
32
- "@toa.io/schemas": "1.0.0-alpha.311",
32
+ "@toa.io/schemas": "1.0.0-alpha.326",
33
33
  "openspan": "1.0.0-alpha.323"
34
34
  },
35
35
  "scripts": {
36
36
  "transpile": "tsc"
37
37
  },
38
- "gitHead": "eedcbb7b90b3ea759f200b0d03c7ae13a8487b9d"
38
+ "gitHead": "60f11bdc805d0c39bcb06064b2218b11114a35ad"
39
39
  }
package/readme.md CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  Calls a component makes to itself, and calls it puts off.
4
4
 
5
- A **pulse** calls an operation of its own component on a cadence its manifest states. A **delay**
6
- hands one call over to be made later.
5
+ A **pulse** calls an operation of its own component on a cadence its manifest states. A
6
+ **schedule** calls one at the moments of a calendar. A **delay** hands one call over to be made
7
+ later.
7
8
 
8
- A delay, and a pulse that is the component's rather than each replica's, rest on
9
+ A delay, a schedule, and a pulse that is the component's rather than each replica's, rest on
9
10
  [`atomicity`](/connectors/atomicity), which is what keeps two replicas from making one call and
10
- where that promise is written down. Where it is not configured, neither is made at all. A pulse
11
+ where that promise is written down. Where it is not configured, none of them is made at all. A pulse
11
12
  declared [`scope: replica`](#in-one-replica-or-in-every-one) asks nothing of it and is made
12
13
  either way.
13
14
 
@@ -48,8 +49,8 @@ for.
48
49
 
49
50
  A cycle is measured from the Unix epoch rather than from a calendar, so `86400` means UTC
50
51
  midnight and `3600` the top of the hour. A cycle that is not a divisor of a day lands on a
51
- boundary that is not any particular time of day. Time zones and calendar months are not
52
- supported.
52
+ boundary that is not any particular time of day. A time of day in a time zone, a day of the week
53
+ or of the month is what a [schedule](#schedule) states.
53
54
 
54
55
  ### In one replica, or in every one
55
56
 
@@ -88,8 +89,8 @@ what in its share is still due, rather than everything in its share, and a misse
88
89
  a delay instead of a day. An operation written the other way loses a day's work to a
89
90
  thirty-second rollout.
90
91
 
91
- Where a call has to happen even if nothing was running when it came due, it needs a stored
92
- schedule: [`delay`](#delay) is one.
92
+ Where a call has to happen even if nothing was running when it came due, it has to be stored: a
93
+ [schedule](#schedule) is, and so is a [`delay`](#delay).
93
94
 
94
95
  **An interval is called once.** Two calls for one interval need the clocks of two machines to
95
96
  disagree and the work to change hands in the same moment, which is a window the width of that
@@ -115,6 +116,126 @@ the day's calls: the cycle is spread over _time_, not over the fleet. Replicas b
115
116
  make none. How much of the fleet calls is what [`scope`](#in-one-replica-or-in-every-one) says,
116
117
  and it is the only thing that says it.
117
118
 
119
+ ## Schedule
120
+
121
+ ```yaml
122
+ # manifest.toa.yaml
123
+ cadence:
124
+ report: 0 12 * * 1-5 # noon UTC on weekdays
125
+ digest:
126
+ schedule: 0 9 * * 1 # nine on Monday morning
127
+ zone: Europe/Berlin # in Berlin
128
+ overdue: 3600 # seconds it may be late and still be made
129
+ ```
130
+
131
+ A string is a schedule, where a number is a [pulse](#pulse). It is a cron expression of five
132
+ fields — minute, hour, day of the month, month, day of the week — or of six, where the first is
133
+ seconds.
134
+
135
+ An expression that begins with `*` is quoted, because YAML reads a leading `*` as its own:
136
+
137
+ ```yaml
138
+ # manifest.toa.yaml
139
+ cadence:
140
+ poll: '*/5 * * * *' # every five minutes
141
+ ```
142
+
143
+ | | |
144
+ | ---------- | ----------------------------------------------------------------------------- |
145
+ | `schedule` | the cron expression |
146
+ | `zone` | the [time zone](https://www.iana.org/time-zones) it is read in, `UTC` by default |
147
+ | `overdue` | seconds a call may be late and still be made; until the next one by default |
148
+ | `region` | the region that makes it; see [Regions](#in-one-region-or-in-every-one) |
149
+
150
+ The operation is called with the moment the call was scheduled for:
151
+
152
+ ```javascript
153
+ async function report({ at }, context) {}
154
+ ```
155
+
156
+ `at` is milliseconds since the epoch, and it is the moment of the schedule rather than of the
157
+ call: a report made twenty minutes late is still the report for noon. Compute the period from it
158
+ and not from the clock.
159
+
160
+ A schedule may name any operation a pulse may.
161
+
162
+ ### What to expect
163
+
164
+ Each occurrence is a [delayed call](#delay), handed over ahead of its moment, and what a delayed
165
+ call [may and may not do](#what-to-expect-2) holds for it.
166
+
167
+ **A call is made late rather than lost.** An occurrence is stored as soon as the one before it
168
+ comes due, so a component that is down at noon is called when it is back, with the `at` of noon.
169
+
170
+ **It is not made once the next one is due.** At most one occurrence is ever owed: a component
171
+ that was away from Friday to Monday is called for Monday and not for Friday, and an outage of a
172
+ month does not come back as thirty calls. `overdue` states a tighter bound, in seconds, and is
173
+ subject to [`discreteness`](#discreteness) as a delay's is.
174
+
175
+ **An occurrence is lost where the component did not run at all** between the one before it and
176
+ its own moment. Nothing was there to store it.
177
+
178
+ **A call may be made more than once**, so where a second one would cost something the operation
179
+ refuses it by `at`.
180
+
181
+ **Nothing keeps a call from being made while the one before it is running.** A pulse skips an
182
+ interval its work did not fit in; a schedule does not.
183
+
184
+ **A schedule changed or removed may be called once more**, at the moment it had already been
185
+ stored for.
186
+
187
+ **Seconds are served as coarsely as delays are.** An occurrence closer than `discreteness` to the
188
+ one before it is made at the next pass rather than at its moment.
189
+
190
+ ## Several entries
191
+
192
+ An operation takes a list where it has more than one entry, of either kind:
193
+
194
+ ```yaml
195
+ # manifest.toa.yaml
196
+ cadence:
197
+ sweep:
198
+ - cycle: 3600 # what is due, every hour
199
+ - schedule: 0 3 1 * * # and everything, on the first of the month
200
+ ```
201
+
202
+ The entries are independent of one another, and nothing keeps their calls apart. An operation
203
+ given both kinds is called with `{ n, i }` by one and `{ at }` by the other. Two schedules of one
204
+ operation that fall on the same moment are one call.
205
+
206
+ ## In one region, or in every one
207
+
208
+ An application deployed as [several regions](/extensions/convergence) has every region make every
209
+ pulse and every schedule, each for itself. An entry of either kind that is work for the whole
210
+ application names the region that makes it, by rank:
211
+
212
+ ```yaml
213
+ # manifest.toa.yaml
214
+ cadence:
215
+ sweep:
216
+ cycle: 3600
217
+ region: 0 # a pulse only region 0 makes
218
+ invoice:
219
+ schedule: 0 6 1 * *
220
+ region: 0 # a schedule made once, however many regions there are
221
+ digest:
222
+ - schedule: 0 9 * * 1
223
+ zone: Europe/Berlin
224
+ region: 0
225
+ - schedule: 0 9 * * 1
226
+ zone: America/New_York
227
+ region: 1
228
+ ```
229
+
230
+ **An entry that names a rank no region has is never made**, and nothing says so: a deployment
231
+ reads only its own declaration. An application that is one place is rank `0`.
232
+
233
+ A region that is gone has its entries made by the one that
234
+ [takes its delayed calls over](#regions), by the same setting.
235
+
236
+ `region` is not stated beside `scope: replica`: what lives in a process is that process's own in
237
+ every region.
238
+
118
239
  ## Delay
119
240
 
120
241
  ```javascript
@@ -288,7 +409,8 @@ for the call, and a deployment makes the calls of the region it is. So there is
288
409
  for this to be right, and a single-region application never meets it.
289
410
 
290
411
  **`regions` is how a region that is gone is taken over.** Redeploy a surviving region naming the
291
- lost one's rank beside its own, and it makes both:
412
+ lost one's rank beside its own, and it makes both — the delayed calls of the lost region, and the
413
+ pulses and schedules [declared for it](#in-one-region-or-in-every-one):
292
414
 
293
415
  ```yaml
294
416
  cadence:
@@ -7,7 +7,7 @@ export declare class Factory implements extensions.Factory {
7
7
  constructor(host: Host);
8
8
  /**
9
9
  * What runs beside a component. Its own component gets the dispatcher; everyone else gets a
10
- * timer per operation they declared a cadence for.
10
+ * timer per entry they declared.
11
11
  *
12
12
  * A tenant is created before the components of its composition are, which is why nothing here
13
13
  * reaches for one.
@@ -1,10 +1,11 @@
1
1
  import { Connector, Locator } from '@toa.io/core';
2
2
  import { Composition } from './Composition.js';
3
- import { COMPONENT, NAMESPACE } from '@toa.io/definitions/extensions.cadence';
3
+ import { COMPONENT, NAMESPACE, regions } from '@toa.io/definitions/extensions.cadence';
4
4
  import { Aspect } from './Aspect.js';
5
5
  import { Dispatcher } from './Dispatcher.js';
6
6
  import { Local } from './Local.js';
7
7
  import { Pulse } from './Pulse.js';
8
+ import { Schedule } from './Schedule.js';
8
9
  export class Factory {
9
10
  host;
10
11
  /** one per component, however much of this extension calls through it */
@@ -14,7 +15,7 @@ export class Factory {
14
15
  }
15
16
  /**
16
17
  * What runs beside a component. Its own component gets the dispatcher; everyone else gets a
17
- * timer per operation they declared a cadence for.
18
+ * timer per entry they declared.
18
19
  *
19
20
  * A tenant is created before the components of its composition are, which is why nothing here
20
21
  * reaches for one.
@@ -23,19 +24,32 @@ export class Factory {
23
24
  if (mine(locator))
24
25
  return new Dispatcher(this.metronome(), (target) => this.local(target), this.host.atom(locator.id));
25
26
  const tenant = new Connector();
26
- const pulses = declaration;
27
+ const declared = declaration;
27
28
  // `cadence: ~` is a component that only delays calls
28
- if (pulses === null)
29
+ if (declared === null)
30
+ return tenant;
31
+ /*
32
+ * An entry that names a region is made by the deployment of that rank and by no other —
33
+ * and by one that was told to make the work of a region that is gone. One that names none
34
+ * is every region's own.
35
+ */
36
+ const ranks = regions();
37
+ const entries = Object.entries(declared).flatMap(([endpoint, list]) => list
38
+ .filter((entry) => entry.region === undefined || ranks.includes(entry.region))
39
+ .map((entry) => ({ endpoint, entry })));
40
+ if (entries.length === 0)
29
41
  return tenant;
30
42
  const local = this.local(locator);
31
- const entries = Object.entries(pulses);
32
43
  // a pulse every replica makes asks nobody whether it may, so a component whose pulses are
33
44
  // all of that kind decides nothing with its replicas and needs no atom at all
34
- const atom = entries.some(([, pulse]) => pulse.scope === 'group')
45
+ const atom = entries.some(({ entry }) => 'cycle' in entry && entry.scope === 'group')
35
46
  ? this.host.atom(locator.id)
36
47
  : undefined;
37
- for (const [endpoint, { cycle, intervals, scope }] of entries)
38
- tenant.depends(new Pulse({ locator, endpoint, cycle, intervals }, local, scope === 'group' ? atom : undefined));
48
+ for (const { endpoint, entry } of entries)
49
+ if ('schedule' in entry)
50
+ tenant.depends(new Schedule({ locator, endpoint, ...entry }, this.metronome()));
51
+ else
52
+ tenant.depends(new Pulse({ locator, endpoint, cycle: entry.cycle, intervals: entry.intervals }, local, entry.scope === 'group' ? atom : undefined));
39
53
  return tenant;
40
54
  }
41
55
  /** `context.delay` — for every component but the one that keeps the calls. */
@@ -1 +1 @@
1
- {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
@@ -16,7 +16,12 @@ export class Local extends Connector {
16
16
  this.locator = locator;
17
17
  }
18
18
  async invoke(endpoint, request) {
19
- this.remote ??= this.locate();
19
+ // one that could not be found is looked for again by the next call, rather than kept as
20
+ // the answer: what hands a schedule over calls as it boots, and goes on calling
21
+ this.remote ??= this.locate().catch((error) => {
22
+ this.remote = undefined;
23
+ throw error;
24
+ });
20
25
  return await (await this.remote).invoke(endpoint, request);
21
26
  }
22
27
  async locate() {
@@ -1 +1 @@
1
- {"version":3,"file":"Local.js","sourceRoot":"","sources":["../source/Local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAKxC;;;;;;GAMG;AACH,MAAM,OAAO,KAAM,SAAQ,SAAS;IACjB,IAAI,CAAM;IACV,OAAO,CAAS;IACzB,MAAM,CAAkB;IAEhC,YAAmB,IAAU,EAAE,OAAgB;QAC7C,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,OAAgB;QACpD,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QAE7B,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC5D,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAE3D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEpB,MAAM,MAAM,CAAC,OAAO,EAAE,CAAA;QAEtB,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,MAAM,GAAW,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"Local.js","sourceRoot":"","sources":["../source/Local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAKxC;;;;;;GAMG;AACH,MAAM,OAAO,KAAM,SAAQ,SAAS;IACjB,IAAI,CAAM;IACV,OAAO,CAAS;IACzB,MAAM,CAAkB;IAEhC,YAAmB,IAAU,EAAE,OAAgB;QAC7C,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,OAAgB;QACpD,wFAAwF;QACxF,gFAAgF;QAChF,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACrD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAA;YAEvB,MAAM,KAAK,CAAA;QACb,CAAC,CAAC,CAAA;QAEF,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC5D,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAE3D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEpB,MAAM,MAAM,CAAC,OAAO,EAAE,CAAA;QAEtB,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,MAAM,GAAW,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,73 @@
1
+ import { Connector } from '@toa.io/core';
2
+ import type { Local } from './Local.ts';
3
+ import type { Locator } from '@toa.io/core';
4
+ /**
5
+ * A call to an operation of its own component at the moments its manifest states, as a cron
6
+ * expression.
7
+ *
8
+ * It makes no call. Each occurrence is a delayed call, and what runs here keeps the next one
9
+ * stored: at boot, and each time the one stored comes due. What makes it is what makes every
10
+ * delayed call, so it is made where nothing is running at its moment — which a pulse, storing
11
+ * nothing, cannot offer.
12
+ *
13
+ * Every replica stores every occurrence, and one row is kept: the id is derived from what the
14
+ * occurrence is, so nothing here asks which replica's it is. And nothing here is a chain. An
15
+ * occurrence is a function of the expression and the clock, so one that was not made, or not
16
+ * stored, has no bearing on the one after it.
17
+ */
18
+ export declare class Schedule extends Connector {
19
+ private readonly endpoint;
20
+ private readonly label;
21
+ private readonly next;
22
+ /** milliseconds a call may be late, or absent where it is owed until the next one is due */
23
+ private readonly overdue?;
24
+ /** whose occurrence this is: the region the entry names, or the one this deployment is */
25
+ private readonly region;
26
+ private readonly metronome;
27
+ /** the occurrence being waited for, which is the one last handed over */
28
+ private at?;
29
+ private timer?;
30
+ private retry?;
31
+ private closing;
32
+ /** Whether the process has been told to go quiet, see `stop`. */
33
+ private quiesced;
34
+ constructor(definition: Definition, metronome: Local);
35
+ protected open(): Promise<void>;
36
+ protected close(): Promise<void>;
37
+ /**
38
+ * Stops storing. What is stored stays stored, and is made when the deployment is working
39
+ * again as any delayed call is; an occurrence that came due unstored while the process was
40
+ * quiet is not made up.
41
+ */
42
+ protected pause(): Promise<void>;
43
+ protected unpause(): void;
44
+ private disarm;
45
+ /**
46
+ * Hands over the next occurrence once the one waited for has come due, and waits again.
47
+ * Always the next one from now and never one already past: whether that one was made is not
48
+ * something this can know.
49
+ */
50
+ private tick;
51
+ /**
52
+ * The row a delayed call is, found or made under an id that is the occurrence's own — so every
53
+ * replica of the component, and every boot of one, stores the same row.
54
+ *
55
+ * It begins a chain of its own, as what is called from the clock does: no trail is stored.
56
+ */
57
+ private store;
58
+ /**
59
+ * How late the call may be. Owed until the next occurrence is due where nothing states a
60
+ * bound, so at most one is ever waiting and an outage does not come back as every call it
61
+ * covered; with no bound only where nothing ever comes after it.
62
+ */
63
+ private late;
64
+ }
65
+ export interface Definition {
66
+ locator: Locator;
67
+ endpoint: string;
68
+ schedule: string;
69
+ zone: string;
70
+ /** seconds */
71
+ overdue?: number;
72
+ region?: number;
73
+ }
@@ -0,0 +1,149 @@
1
+ import { console } from 'openspan';
2
+ import { Connector, entities } from '@toa.io/core';
3
+ import { occurrences, rank } from '@toa.io/definitions/extensions.cadence';
4
+ import { row } from './row.js';
5
+ /**
6
+ * A call to an operation of its own component at the moments its manifest states, as a cron
7
+ * expression.
8
+ *
9
+ * It makes no call. Each occurrence is a delayed call, and what runs here keeps the next one
10
+ * stored: at boot, and each time the one stored comes due. What makes it is what makes every
11
+ * delayed call, so it is made where nothing is running at its moment — which a pulse, storing
12
+ * nothing, cannot offer.
13
+ *
14
+ * Every replica stores every occurrence, and one row is kept: the id is derived from what the
15
+ * occurrence is, so nothing here asks which replica's it is. And nothing here is a chain. An
16
+ * occurrence is a function of the expression and the clock, so one that was not made, or not
17
+ * stored, has no bearing on the one after it.
18
+ */
19
+ export class Schedule extends Connector {
20
+ endpoint;
21
+ label;
22
+ next;
23
+ /** milliseconds a call may be late, or absent where it is owed until the next one is due */
24
+ overdue;
25
+ /** whose occurrence this is: the region the entry names, or the one this deployment is */
26
+ region;
27
+ metronome;
28
+ /** the occurrence being waited for, which is the one last handed over */
29
+ at;
30
+ timer;
31
+ retry;
32
+ closing = false;
33
+ /** Whether the process has been told to go quiet, see `stop`. */
34
+ quiesced = false;
35
+ constructor(definition, metronome) {
36
+ super();
37
+ const { locator, endpoint, schedule, zone, overdue, region } = definition;
38
+ this.endpoint = `${locator.id}.${endpoint}`;
39
+ this.label = `${this.endpoint} schedule`;
40
+ this.next = occurrences(schedule, zone);
41
+ this.region = region ?? rank();
42
+ this.metronome = metronome;
43
+ if (overdue !== undefined)
44
+ this.overdue = overdue * 1000;
45
+ this.depends(metronome);
46
+ }
47
+ async open() {
48
+ this.tick();
49
+ }
50
+ async close() {
51
+ this.closing = true;
52
+ this.disarm();
53
+ }
54
+ /**
55
+ * Stops storing. What is stored stays stored, and is made when the deployment is working
56
+ * again as any delayed call is; an occurrence that came due unstored while the process was
57
+ * quiet is not made up.
58
+ */
59
+ async pause() {
60
+ this.quiesced = true;
61
+ this.disarm();
62
+ }
63
+ unpause() {
64
+ this.quiesced = false;
65
+ this.at = undefined;
66
+ this.tick();
67
+ }
68
+ disarm() {
69
+ clearTimeout(this.timer);
70
+ clearTimeout(this.retry);
71
+ }
72
+ /**
73
+ * Hands over the next occurrence once the one waited for has come due, and waits again.
74
+ * Always the next one from now and never one already past: whether that one was made is not
75
+ * something this can know.
76
+ */
77
+ tick() {
78
+ if (this.closing || this.quiesced)
79
+ return;
80
+ const now = Date.now();
81
+ // a timer may wake a millisecond before the moment it was set for, and a moment further
82
+ // out than one timer reaches is woken for several times on the way
83
+ let at = this.at;
84
+ if (at === undefined || now >= at) {
85
+ const next = this.next(now);
86
+ // an expression whose last moment has passed
87
+ if (next === null)
88
+ return;
89
+ at = this.at = next;
90
+ void this.store(at);
91
+ }
92
+ // a delay past this fires at once rather than late, so a long wait is taken in instalments
93
+ this.timer = setTimeout(() => this.tick(), Math.min(at - now, MAX_DELAY));
94
+ this.timer.unref();
95
+ }
96
+ /**
97
+ * The row a delayed call is, found or made under an id that is the occurrence's own — so every
98
+ * replica of the component, and every boot of one, stores the same row.
99
+ *
100
+ * It begins a chain of its own, as what is called from the clock does: no trail is stored.
101
+ */
102
+ async store(at) {
103
+ const id = entities.derive('cadence', this.region, this.endpoint, at);
104
+ const entity = {
105
+ id,
106
+ ...row({
107
+ endpoint: this.endpoint,
108
+ due: at,
109
+ overdue: this.late(at),
110
+ request: { input: { at } }
111
+ })
112
+ };
113
+ try {
114
+ await this.metronome.invoke('ensure', { query: { id }, entity });
115
+ }
116
+ catch (error) {
117
+ console.warn('Schedule could not store its next call, and will try again', {
118
+ schedule: this.label,
119
+ at,
120
+ error
121
+ });
122
+ /*
123
+ * Tried again for as long as it is the occurrence being waited for. Past that the next
124
+ * one is handed over instead, and this one is what the readme says may be lost.
125
+ */
126
+ this.retry = setTimeout(() => {
127
+ if (!this.closing && !this.quiesced && this.at === at)
128
+ void this.store(at);
129
+ }, RETRY);
130
+ this.retry.unref();
131
+ }
132
+ }
133
+ /**
134
+ * How late the call may be. Owed until the next occurrence is due where nothing states a
135
+ * bound, so at most one is ever waiting and an outage does not come back as every call it
136
+ * covered; with no bound only where nothing ever comes after it.
137
+ */
138
+ late(at) {
139
+ if (this.overdue !== undefined)
140
+ return this.overdue;
141
+ const next = this.next(at);
142
+ return next === null ? null : next - at;
143
+ }
144
+ }
145
+ /** `setTimeout` fires immediately past this, so a longer wait is taken in instalments */
146
+ const MAX_DELAY = 2 ** 31 - 1;
147
+ /** milliseconds between attempts to store an occurrence the metronome was not there to take */
148
+ const RETRY = 5_000;
149
+ //# sourceMappingURL=Schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Schedule.js","sourceRoot":"","sources":["../source/Schedule.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export { Factory } from './Factory.ts';
2
2
  export { components } from './Composition.ts';
3
3
  export { LANES } from '@toa.io/definitions/extensions.cadence';
4
- export type { Declaration, Delay, Pulse } from '@toa.io/definitions/extensions.cadence';
4
+ export { row } from './row.ts';
5
+ export type { Declaration, Delay, Entry, Pulse, Schedule } from '@toa.io/definitions/extensions.cadence';
@@ -2,4 +2,5 @@ export { Factory } from './Factory.js';
2
2
  export { components } from './Composition.js';
3
3
  // what the metronome component is written against
4
4
  export { LANES } from '@toa.io/definitions/extensions.cadence';
5
+ export { row } from './row.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +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,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,kDAAkD;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,kDAAkD;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What is stored of a call to make later, as whoever hands one over states it: `context.delay`,
3
+ * and a schedule storing its next occurrence. Written once, so that the two store one kind of
4
+ * row and the dispatcher reads one.
5
+ *
6
+ * The lane is random, not one this replica owns. An outbox row is written into an owned lane so
7
+ * that in steady state a replica settles its own rows before it ever reads them — but a delayed
8
+ * row has no immediate path to settle, so nothing is gained, and an even spread is what the
9
+ * dispatchers want.
10
+ */
11
+ export declare function row(call: Call): Stored;
12
+ export interface Call {
13
+ /** what to call, as `namespace.component.operation` */
14
+ endpoint: string;
15
+ /** when, in milliseconds since the epoch */
16
+ due: number;
17
+ /** milliseconds the call may be late and still be made, or `null` for no bound */
18
+ overdue: number | null;
19
+ request?: object;
20
+ trail?: string[];
21
+ }
22
+ export interface Stored {
23
+ lane: number;
24
+ due: number;
25
+ expires: number;
26
+ endpoint: string;
27
+ request?: object;
28
+ trail?: string[];
29
+ }
@@ -0,0 +1,32 @@
1
+ import { LANES } from '@toa.io/definitions/extensions.cadence';
2
+ /**
3
+ * What is stored of a call to make later, as whoever hands one over states it: `context.delay`,
4
+ * and a schedule storing its next occurrence. Written once, so that the two store one kind of
5
+ * row and the dispatcher reads one.
6
+ *
7
+ * The lane is random, not one this replica owns. An outbox row is written into an owned lane so
8
+ * that in steady state a replica settles its own rows before it ever reads them — but a delayed
9
+ * row has no immediate path to settle, so nothing is gained, and an even spread is what the
10
+ * dispatchers want.
11
+ */
12
+ export function row(call) {
13
+ const { endpoint, due, overdue, request, trail } = call;
14
+ const stored = {
15
+ lane: Math.floor(Math.random() * LANES),
16
+ due,
17
+ endpoint,
18
+ // absolute, because the bound is the caller's and a scan reads rows of many callers at once.
19
+ // No bound is the end of representable time rather than an absent field, so that one
20
+ // comparison answers for every row
21
+ expires: overdue === null ? Number.MAX_SAFE_INTEGER : due + overdue
22
+ };
23
+ // a call that takes no request has none: the entity's `request` is an object where it is
24
+ // there at all, and a null would not fit it
25
+ if (request !== undefined)
26
+ stored.request = request;
27
+ // the chain that asked for the call, absent where the call begins one of its own
28
+ if (trail !== undefined)
29
+ stored.trail = trail;
30
+ return stored;
31
+ }
32
+ //# sourceMappingURL=row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row.js","sourceRoot":"","sources":["../source/row.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wCAAwC,CAAA;AAE9D;;;;;;;;;GASG;AACH,MAAM,UAAU,GAAG,CAAC,IAAU;IAC5B,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IAEvD,MAAM,MAAM,GAAW;QACrB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC;QACvC,GAAG;QACH,QAAQ;QAER,6FAA6F;QAC7F,qFAAqF;QACrF,mCAAmC;QACnC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,GAAG,OAAO;KACpE,CAAA;IAED,yFAAyF;IACzF,4CAA4C;IAC5C,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAAA;IAEnD,iFAAiF;IACjF,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAA;IAE7C,OAAO,MAAM,CAAA;AACf,CAAC"}