@toa.io/extensions.continuity 1.0.0-alpha.321

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.
@@ -0,0 +1,47 @@
1
+ namespace: continuity
2
+ name: journal
3
+
4
+ # One record per step of a run, under an id derived from the run and the step, so that a step is
5
+ # written once whoever writes it first.
6
+ entity:
7
+ associated: true
8
+ properties:
9
+ run:
10
+ description: The identity of the task or the event the step was made for
11
+ type: string
12
+ answer:
13
+ description: What the step answered, encoded by the extension
14
+ type: object
15
+ expires:
16
+ description: >
17
+ When the run can no longer be picked up. Every step of a run carries the moment its
18
+ first step fixed, so a run is reaped whole.
19
+ type: integer
20
+ format: epoch-millis
21
+
22
+ operations:
23
+ # keeps an answer unless one is kept already, and answers what is kept
24
+ record:
25
+ type: transition
26
+ # two copies of a run writing one step race to create it, and the loser reads the winner's
27
+ concurrency: retry
28
+ input:
29
+ type: object
30
+ properties:
31
+ run:
32
+ type: string
33
+ answer:
34
+ type: object
35
+ expires:
36
+ type: integer
37
+ required:
38
+ - run
39
+ - answer
40
+ - expires
41
+ output:
42
+ type: object
43
+ # what a run has kept
44
+ recall:
45
+ type: observation
46
+ scope: entries
47
+ query: true
@@ -0,0 +1,11 @@
1
+ # A run is read whole, once per attempt.
2
+ - index:
3
+ name: index_run
4
+ keys:
5
+ run: asc
6
+ # A run is kept until the window its first step fixed has passed, and then reaped whole.
7
+ - index:
8
+ name: index_expires
9
+ keys:
10
+ expires: asc
11
+ ttl: 0
@@ -0,0 +1,3 @@
1
+ export function observation(_, entries) {
2
+ return entries
3
+ }
@@ -0,0 +1,9 @@
1
+ export function transition(input, entry) {
2
+ if (entry.answer === undefined) {
3
+ entry.run = input.run
4
+ entry.answer = input.answer
5
+ entry.expires = input.expires
6
+ }
7
+
8
+ return entry.answer
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -0,0 +1,41 @@
1
+ // Written by `toa types`. Every run rewrites it.
2
+ // What a manifest does not state belongs in a file of your own.
3
+
4
+ import type { Query } from '@toa.io/core/types'
5
+ import type { Readable } from 'node:stream'
6
+
7
+ export interface Entity {
8
+ /** The identity of the task or the event the step was made for */
9
+ run?: string
10
+ /** What the step answered, encoded by the extension */
11
+ answer?: Record<string, unknown>
12
+ /** When the run can no longer be picked up. Every step of a run carries the moment its first step fixed, so a run is reaped whole. */
13
+ expires?: number
14
+ id: string
15
+ VERSION: number
16
+ CREATED: number
17
+ UPDATED: number
18
+ DELETED: number | null
19
+ REGION: number
20
+ }
21
+
22
+ export type State = Entity & { DISCARD: boolean, TRAILERS: Record<string, unknown> }
23
+
24
+ export type RecordInput = {
25
+ run: string
26
+ answer: Record<string, unknown>
27
+ expires: number
28
+ }
29
+
30
+ export type RecordOutput = Record<string, unknown>
31
+
32
+ export interface Component {
33
+ record: (request: { input: RecordInput, query?: Query<Entity>, task?: boolean }) => Promise<RecordOutput>
34
+ recall: (request: { input?: null, query: Query<Entity>, task?: boolean }) => Promise<unknown>
35
+ assign: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Entity>
36
+ ensure: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Entity>
37
+ enumerate: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Entity[]>
38
+ observe: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Entity | null>
39
+ stream: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Readable>
40
+ terminate: (request: { input?: null, query?: Query<Entity>, task?: boolean }) => Promise<Entity>
41
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@toa.io/extensions.continuity",
3
+ "version": "1.0.0-alpha.321",
4
+ "type": "module",
5
+ "description": "Toa Continuity",
6
+ "author": "temich <tema.gurtovoy@gmail.com>",
7
+ "homepage": "https://github.com/toa-io/toa#readme",
8
+ "main": "transpiled/index.js",
9
+ "types": "transpiled/index.d.ts",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/toa-io/toa.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/toa-io/toa/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "files": [
21
+ "transpiled",
22
+ "components",
23
+ "readme.md",
24
+ "!**/tsconfig.tsbuildinfo",
25
+ "!**/*.test.*",
26
+ "!**/.env*"
27
+ ],
28
+ "dependencies": {
29
+ "@toa.io/core": "1.0.0-alpha.321",
30
+ "@toa.io/definitions": "1.0.0-alpha.321"
31
+ },
32
+ "scripts": {
33
+ "transpile": "tsc"
34
+ }
35
+ }
package/readme.md ADDED
@@ -0,0 +1,150 @@
1
+ # Toa Continuity
2
+
3
+ An operation that picks up where it failed.
4
+
5
+ A [task](/documentation/tasks.md), or an event a receiver hands on, is tried again when it raises,
6
+ and every attempt runs the operation from the top. An operation this extension continues is given
7
+ back, on a later attempt, whatever its context answered on an earlier one: the calls, fetches and
8
+ stashes that already answered are not made again, and what is made is the step that failed and
9
+ what follows it.
10
+
11
+ ## Declaring it
12
+
13
+ ```yaml
14
+ # manifest.toa.yaml
15
+ operations:
16
+ onboard:
17
+ type: effect
18
+
19
+ continuity:
20
+ onboard: 604800 # seconds an unfinished run can still be resumed
21
+ ```
22
+
23
+ Each operation named is an `effect` of the component, with how long, in seconds, a run of it that
24
+ has not finished can still be picked up. There is no default: how late a parked task can be put
25
+ back is yours to know. The window is at least `600`, and a manifest that states less, names an
26
+ operation the component does not have, or names one that is not an effect is refused.
27
+
28
+ The operation is written as any other:
29
+
30
+ ```javascript
31
+ async function effect(input, context) {
32
+ const account = await context.remote.identity.accounts.create({ input })
33
+ await context.fetch('crm', { method: 'POST', body: JSON.stringify(account) })
34
+ await context.remote.mail.messages.send({ input: { to: account.email } })
35
+ }
36
+ ```
37
+
38
+ If `send` raises, the next attempt is given the account `create` answered and the response the
39
+ CRM gave, and only `send` is made again.
40
+
41
+ ## Calling it
42
+
43
+ As a task, or from a receiver — where nobody waits for the answer:
44
+
45
+ ```javascript
46
+ await context.call('users.onboarding.onboard', { input, task: true })
47
+ ```
48
+
49
+ Any other call is refused, and so is a pulse naming it.
50
+
51
+ ## What you can count on
52
+
53
+ **What answered is not asked again.** Within one run — a task, or one event — a step that answered
54
+ is given back on every later attempt: after an exception, after the process died, after a redeploy,
55
+ and after a parked task is put back within the window. A task delivered again after it finished is
56
+ given everything back, and makes nothing.
57
+
58
+ **Concurrent branches are fine.** A step is recognized by what it asks, not by when:
59
+
60
+ ```javascript
61
+ await Promise.all([a().then(b), c().then(b)])
62
+ ```
63
+
64
+ Each `b` is given its own answer however `a` and `c` complete on the next attempt.
65
+
66
+ **Two copies of a run agree.** Where a run is delivered twice at once, both copies go on with the
67
+ answer the first of them kept. A value taken at once — `context.id()`, `context.now()`,
68
+ `context.random()` — is the exception: each copy has its own until it is kept, so a step one copy
69
+ builds from it may be made by the other as well.
70
+
71
+ **A call keeps its identity.** A call made from the operation carries the same identity on every
72
+ attempt, so a callee that declares [`once`](/documentation/inbox.md) refuses a repeat of it.
73
+
74
+ ## What that asks of you
75
+
76
+ **The next attempt may run somewhere else.** A task or an event that is tried again is taken by
77
+ whichever replica is free, and after a restart or a redeploy by a process that has never seen the run.
78
+ What the context answered is given back wherever that is; what the operation kept in its own process
79
+ is not. `context.state`, a variable of the module, a cache in memory, a file on the local disk — on the
80
+ next attempt each of them is whatever that process holds, which is not what the earlier attempt left:
81
+
82
+ ```javascript
83
+ // counts in whichever process the attempt runs in, and not across attempts
84
+ context.state.sent = (context.state.sent ?? 0) + 1
85
+
86
+ // kept with the run, and given back wherever the next attempt runs
87
+ const sent = await context.remote.mail.messages.send({ input })
88
+ ```
89
+
90
+ So nothing a later attempt depends on is carried in the process: take it from a step, or from what
91
+ the operation writes and reads back.
92
+
93
+ **Take time, randomness and new ids from the context.** A step is recognized by its arguments, so an
94
+ argument that differs between attempts makes a new step, and the step is made again:
95
+
96
+ ```javascript
97
+ // a new timestamp every attempt: the call is made again every attempt
98
+ await context.remote.billing.invoices.issue({ input: { at: Date.now() } })
99
+
100
+ // the same timestamp on every attempt: the call is made once
101
+ await context.remote.billing.invoices.issue({ input: { at: context.now() } })
102
+ ```
103
+
104
+ `context.now()`, `context.random()` and `context.id()` are given back like any other step. The same
105
+ holds for the entity an effect reads: it is read again on every attempt, and a step whose arguments
106
+ come from it is made again if it moved.
107
+
108
+ **A step that took effect may still be made again.** Where the process dies after a step took
109
+ effect and before its answer was kept, the next attempt makes it again. A call into Toa arrives with
110
+ the same identity, so a callee that declares `once` refuses it; a `fetch` to a third party is sent
111
+ again, so make it safe to repeat, or pass it a key it deduplicates on.
112
+
113
+ **Among identical steps, which branch gets which answer is not fixed.** Two concurrent `b(x)` are
114
+ the same request, and each is given one of the two answers — possibly the other one than the first
115
+ time. A later step built from the answer together with something of the branch's own has new
116
+ arguments, and is made again. `context.id()` twice in two branches is the common case.
117
+
118
+ **What cannot be kept is refused.** A step that answers with a stream raises `Unrecordable`, and
119
+ the run is parked. A `fetch` is kept whole — its status, its headers and its body — and given back
120
+ as a `Response`. An answer is kept as one MongoDB document, so one past its 16 MB fails to be kept,
121
+ and the step raises.
122
+
123
+ **An answer is given as it is kept, the first time too.** What a step answers is given back as the
124
+ broker carries it, on the attempt that made it as on any later one, so the operation reads the same
125
+ thing every time.
126
+
127
+ **A run that used up its attempts is parked**, as any task is. It picks up where it stopped if it is
128
+ put back within its window; after that, it starts from the beginning.
129
+
130
+ ## Not steps
131
+
132
+ `logs`, `span`, `metrics`, `configuration`, `state` and `atom` are what they are on every attempt,
133
+ and nothing of them is kept. `state` in particular belongs to one process, and the next attempt may
134
+ run in another.
135
+
136
+ ## Deployment
137
+
138
+ The runs are kept in the extension's own component, `continuity.journal`, in MongoDB. A context
139
+ whose components declare `continuity` deploys it as a service of its own. Its database is given as
140
+ for any component, so a context whose `mongodb` names one address for everything has nothing to add;
141
+ one that names them per namespace names this one too:
142
+
143
+ ```yaml
144
+ # context.toa.yaml
145
+ mongodb:
146
+ todos: mongodb://todos.mongo.example.com
147
+ continuity: mongodb://mongo.example.com
148
+ ```
149
+
150
+ See [deployment](/documentation/deployment.md).
@@ -0,0 +1,14 @@
1
+ import { Connector } from '@toa.io/core';
2
+ import type { Host } from './Factory.ts';
3
+ /** The components this extension ships, which run inside its own service. */
4
+ export declare function components(): Components;
5
+ export declare class Composition extends Connector {
6
+ private readonly host;
7
+ constructor(host: Host);
8
+ protected open(): Promise<void>;
9
+ }
10
+ interface Components {
11
+ labels: string[];
12
+ paths: string[];
13
+ }
14
+ export {};
@@ -0,0 +1,30 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Connector } from '@toa.io/core';
4
+ /** The components this extension ships, which run inside its own service. */
5
+ export function components() {
6
+ const labels = [];
7
+ const paths = [];
8
+ for (const entry of entries()) {
9
+ labels.push(entry.name.replace('.', '-'));
10
+ paths.push(resolve(ROOT, entry.name));
11
+ }
12
+ return { labels, paths };
13
+ }
14
+ export class Composition extends Connector {
15
+ host;
16
+ constructor(host) {
17
+ super();
18
+ this.host = host;
19
+ }
20
+ async open() {
21
+ const composition = await this.host.composition(components().paths);
22
+ await composition.connect();
23
+ this.depends(composition);
24
+ }
25
+ }
26
+ function entries() {
27
+ return readdirSync(ROOT, { withFileTypes: true }).filter((entry) => entry.isDirectory());
28
+ }
29
+ const ROOT = resolve(import.meta.dirname, '../components/');
30
+ //# sourceMappingURL=Composition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Composition.js","sourceRoot":"","sources":["../source/Composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAIxC,6EAA6E;AAC7E,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACzC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IACvC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,OAAO,WAAY,SAAQ,SAAS;IACvB,IAAI,CAAM;IAE3B,YAAmB,IAAU;QAC3B,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAA;QAEnE,MAAM,WAAW,CAAC,OAAO,EAAE,CAAA;QAE3B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3B,CAAC;CACF;AAED,SAAS,OAAO;IACd,OAAO,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;AAC1F,CAAC;AAOD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { Component, Connector, Context, Locator } from '@toa.io/core';
2
+ import type { Declaration } from '@toa.io/definitions/extensions.continuity';
3
+ import type { extensions } from '@toa.io/core/types';
4
+ export declare class Factory implements extensions.Factory {
5
+ private readonly host;
6
+ /** what each component declared, by its locator: the hooks that wrap it are handed no more */
7
+ private readonly declared;
8
+ private journal?;
9
+ constructor(host: extensions.Host);
10
+ /**
11
+ * Nothing is put on the context: this is where a component's declaration is handed over, ahead
12
+ * of the context and the component being built.
13
+ */
14
+ aspect(locator: Locator, declaration: Declaration): extensions.Aspect[];
15
+ component(component: Component): Component;
16
+ context(context: Context): Context;
17
+ service(): Connector;
18
+ /** one journal for the process, which the component it serves goes down with */
19
+ private connected;
20
+ }
21
+ export type Host = extensions.Host;
@@ -0,0 +1,156 @@
1
+ import { exceptions } from '@toa.io/core';
2
+ import { NAMESPACE } from '@toa.io/definitions/extensions.continuity';
3
+ import { Composition } from './Composition.js';
4
+ import { Journal } from './Journal.js';
5
+ import { Run } from './Run.js';
6
+ export class Factory {
7
+ host;
8
+ /** what each component declared, by its locator: the hooks that wrap it are handed no more */
9
+ declared = {};
10
+ journal;
11
+ constructor(host) {
12
+ this.host = host;
13
+ }
14
+ /**
15
+ * Nothing is put on the context: this is where a component's declaration is handed over, ahead
16
+ * of the context and the component being built.
17
+ */
18
+ aspect(locator, declaration) {
19
+ if (!mine(locator))
20
+ this.declared[locator.id] = declaration;
21
+ return [];
22
+ }
23
+ component(component) {
24
+ const declaration = this.declared[component.locator.id];
25
+ if (declaration === undefined)
26
+ return component;
27
+ const journal = this.connected(component);
28
+ const invoke = component.invoke.bind(component);
29
+ component.invoke = async (endpoint, request, options) => {
30
+ const window = declaration[endpoint];
31
+ if (window === undefined)
32
+ return await invoke(endpoint, request, options);
33
+ const refusal = refuse(component, endpoint, request);
34
+ if (refusal !== undefined)
35
+ return { exception: refusal };
36
+ let run;
37
+ try {
38
+ run = await Run.begin(request.id, journal, window);
39
+ }
40
+ catch (exception) {
41
+ return { exception: coded(exception) };
42
+ }
43
+ const reply = await run.within(async () => await invoke(endpoint, request, options));
44
+ // what was taken without waiting is kept before the attempt is done with
45
+ try {
46
+ await run.settle();
47
+ }
48
+ catch (exception) {
49
+ return { exception: coded(exception) };
50
+ }
51
+ return reply;
52
+ };
53
+ return component;
54
+ }
55
+ context(context) {
56
+ if (context.locator === undefined || this.declared[context.locator.id] === undefined)
57
+ return context;
58
+ continued(context);
59
+ return context;
60
+ }
61
+ // nothing connects to this, so a halt takes the whole of it
62
+ service() {
63
+ return this.host.gate(async () => new Composition(this.host));
64
+ }
65
+ /** one journal for the process, which the component it serves goes down with */
66
+ connected(component) {
67
+ if (this.journal === undefined || this.journal.disposed)
68
+ this.journal = new Journal(this.host);
69
+ component.depends(this.journal);
70
+ return this.journal;
71
+ }
72
+ }
73
+ /**
74
+ * The members of the context that answer an operation, each made a step of the run where there is
75
+ * one, and left alone where there is none. What is no step — a log, a span, a metric, the
76
+ * configuration, a process's own state and what its replicas decide — is not touched.
77
+ */
78
+ function continued(context) {
79
+ const call = context.call.bind(context);
80
+ const apply = context.apply.bind(context);
81
+ const newid = context.newid.bind(context);
82
+ const now = context.now.bind(context);
83
+ const random = context.random.bind(context);
84
+ // eslint-disable-next-line max-params
85
+ context.call = async (namespace, name, endpoint, request, options) => {
86
+ const run = Run.current();
87
+ if (run === undefined)
88
+ return await call(namespace, name, endpoint, request, options);
89
+ return await run.step(`call:${namespace}.${name}.${endpoint}`, [request], (id) => call(namespace, name, endpoint, identified(request, id), options));
90
+ };
91
+ context.apply = async (endpoint, request, options) => {
92
+ const run = Run.current();
93
+ if (run === undefined)
94
+ return await apply(endpoint, request, options);
95
+ return await run.step(`apply:${endpoint}`, [request], (id) => apply(endpoint, identified(request, id), options));
96
+ };
97
+ context.newid = () => Run.current()?.value('newid', newid) ?? newid();
98
+ context.now = () => Run.current()?.value('now', now) ?? now();
99
+ context.random = () => Run.current()?.value('random', random) ?? random();
100
+ const aspects = context.aspects;
101
+ for (let i = 0; i < aspects.length; i++) {
102
+ const aspect = aspects[i];
103
+ if (PASSED.has(aspect.name))
104
+ continue;
105
+ const invoke = aspect.invoke.bind(aspect);
106
+ // what the bridge reads of an aspect is its name and `invoke`, and the aspect itself stays a
107
+ // dependency of the context as it was
108
+ aspects[i] = {
109
+ name: aspect.name,
110
+ invoke: (...args) => {
111
+ const run = Run.current();
112
+ if (run === undefined)
113
+ return invoke(...args);
114
+ return run.step(`aspect:${aspect.name}`, args, async () => await invoke(...args));
115
+ }
116
+ };
117
+ }
118
+ }
119
+ /**
120
+ * A call made as a step carries the identity its key derives, so that it arrives under the same one
121
+ * on every attempt. The request is copied rather than written to: one request object may be handed
122
+ * to more than one call.
123
+ */
124
+ function identified(request, id) {
125
+ return { ...request, id: request?.id ?? id };
126
+ }
127
+ /**
128
+ * A run is known by the identity a task or an event carries on every attempt. A caller that waits
129
+ * retries by itself, under an identity of its own, and a run of it would not be one run.
130
+ */
131
+ function refuse(component, endpoint, request) {
132
+ const target = `'${component.locator.id}.${endpoint}'`;
133
+ if (request?.task !== true && !delivered(request))
134
+ return new exceptions.RequestContractException(`${target} is continued, and runs as a task or for an event, which nobody waits for`);
135
+ if (typeof request.id !== 'string' || !IDENTITY.test(request.id))
136
+ return new exceptions.RequestContractException(`${target} is continued, and a request that carries no identity has no run to continue`);
137
+ return undefined;
138
+ }
139
+ /** whether a receiver handed this request on, which is the last hop its chain names */
140
+ function delivered(request) {
141
+ const hops = request?.trail;
142
+ return Array.isArray(hops) && hops.at(-1)?.startsWith('~') === true;
143
+ }
144
+ function coded(exception) {
145
+ if (typeof exception?.code === 'number')
146
+ return exception;
147
+ return new exceptions.SystemException(exception);
148
+ }
149
+ /** Whether this is a component the extension ships rather than one of the application's. */
150
+ function mine(locator) {
151
+ return locator.namespace === NAMESPACE;
152
+ }
153
+ const PASSED = new Set(['logs', 'span', 'metrics', 'configuration', 'state', 'atom']);
154
+ // what a run is looked up by in the journal's criteria, so nothing it could hold is quoted
155
+ const IDENTITY = /^[0-9A-Za-z_-]{1,128}$/;
156
+ //# sourceMappingURL=Factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { Connector } from '@toa.io/core';
2
+ import type { extensions } from '@toa.io/core/types';
3
+ import type { Kept } from './answers.ts';
4
+ /**
5
+ * The component that keeps the runs, as something to call.
6
+ *
7
+ * Resolved on the first call and not before: an extension is built ahead of the components of its
8
+ * composition, so at that point there is nothing to look up yet.
9
+ */
10
+ export declare class Journal extends Connector {
11
+ private readonly host;
12
+ private remote?;
13
+ constructor(host: extensions.Host);
14
+ /** what a run has kept so far, by the key of each step, and when the run is reaped */
15
+ recall(run: string): Promise<Recalled>;
16
+ /** keeps what a step answered, unless another attempt kept it first, and answers what is kept */
17
+ record(run: string, key: string, answer: Kept, expires: number): Promise<Kept>;
18
+ private invoke;
19
+ private locate;
20
+ }
21
+ export interface Recalled {
22
+ steps: Map<string, Kept>;
23
+ expires: number | undefined;
24
+ }
@@ -0,0 +1,57 @@
1
+ import { Connector, Locator } from '@toa.io/core';
2
+ import { COMPONENT, NAMESPACE } from '@toa.io/definitions/extensions.continuity';
3
+ /**
4
+ * The component that keeps the runs, as something to call.
5
+ *
6
+ * Resolved on the first call and not before: an extension is built ahead of the components of its
7
+ * composition, so at that point there is nothing to look up yet.
8
+ */
9
+ export class Journal extends Connector {
10
+ host;
11
+ remote;
12
+ constructor(host) {
13
+ super();
14
+ this.host = host;
15
+ }
16
+ /** what a run has kept so far, by the key of each step, and when the run is reaped */
17
+ async recall(run) {
18
+ const steps = new Map();
19
+ let expires;
20
+ for (let omit = 0;; omit += PAGE) {
21
+ const page = (await this.invoke('recall', {
22
+ query: { criteria: `run=="${run}"`, limit: PAGE, omit }
23
+ }));
24
+ for (const record of page) {
25
+ steps.set(record.id, record.answer);
26
+ expires = record.expires;
27
+ }
28
+ if (page.length < PAGE)
29
+ break;
30
+ }
31
+ return { steps, expires };
32
+ }
33
+ /** keeps what a step answered, unless another attempt kept it first, and answers what is kept */
34
+ // eslint-disable-next-line max-params
35
+ async record(run, key, answer, expires) {
36
+ return (await this.invoke('record', {
37
+ query: { id: key },
38
+ input: { run, answer, expires }
39
+ }));
40
+ }
41
+ async invoke(endpoint, request) {
42
+ this.remote ??= this.locate();
43
+ // what the journal refuses or fails with is thrown, as for any call
44
+ return await (await this.remote).invoke(endpoint, request);
45
+ }
46
+ async locate() {
47
+ const remote = await this.host.remote(new Locator(COMPONENT, NAMESPACE), SOURCE);
48
+ this.depends(remote);
49
+ await remote.connect();
50
+ return remote;
51
+ }
52
+ }
53
+ /** records one read brings back */
54
+ const PAGE = 1000;
55
+ // what the journal is called by; without it the calls arrive unattributed
56
+ const SOURCE = { service: 'continuity' };
57
+ //# sourceMappingURL=Journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Journal.js","sourceRoot":"","sources":["../source/Journal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAKhF;;;;;GAKG;AACH,MAAM,OAAO,OAAQ,SAAQ,SAAS;IACnB,IAAI,CAAiB;IAC9B,MAAM,CAAkB;IAEhC,YAAmB,IAAqB;QACtC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,sFAAsF;IAC/E,KAAK,CAAC,MAAM,CAAC,GAAW;QAC7B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAA;QACrC,IAAI,OAA2B,CAAA;QAE/B,KAAK,IAAI,IAAI,GAAG,CAAC,GAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;gBACxC,KAAK,EAAE,EAAE,QAAQ,EAAE,SAAS,GAAG,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE;aACxD,CAAC,CAAa,CAAA;YAEf,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;gBAC1B,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;gBACnC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;YAC1B,CAAC;YAED,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI;gBAAE,MAAK;QAC/B,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAC3B,CAAC;IAED,iGAAiG;IACjG,sCAAsC;IAC/B,KAAK,CAAC,MAAM,CACjB,GAAW,EACX,GAAW,EACX,MAAY,EACZ,OAAe;QAEf,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;YAClC,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE;YAClB,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;SAChC,CAAC,CAAS,CAAA;IACb,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,QAAgB,EAAE,OAAe;QACpD,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAA;QAE7B,oEAAoE;QACpE,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,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAA;QAEhF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEpB,MAAM,MAAM,CAAC,OAAO,EAAE,CAAA;QAEtB,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAaD,mCAAmC;AACnC,MAAM,IAAI,GAAG,IAAI,CAAA;AAEjB,0EAA0E;AAC1E,MAAM,MAAM,GAAW,EAAE,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,52 @@
1
+ import type { Journal } from './Journal.ts';
2
+ /**
3
+ * One attempt of a run: a task or an event, known by the identity it carries on every attempt.
4
+ *
5
+ * Put in scope where the component is invoked, and read where the context is asked. The context
6
+ * of an operation is one object shared by every invocation of it, so nothing per invocation can
7
+ * be handed to it — as the trail is not, for the same reason.
8
+ */
9
+ export declare class Run {
10
+ readonly id: string;
11
+ private readonly journal;
12
+ private readonly window;
13
+ private readonly steps;
14
+ private expires;
15
+ /** how many times this attempt has asked each thing, by what it asked */
16
+ private readonly asked;
17
+ /** what was taken without waiting and is still being written */
18
+ private readonly writing;
19
+ private constructor();
20
+ /** an attempt, with what the run kept before it */
21
+ static begin(id: string, journal: Journal, window: number): Promise<Run>;
22
+ /**
23
+ * The run the invocation running now is, where it is one. Only the invocation the run was
24
+ * handed to: a call it makes to its own component runs in the same scope, and under an identity
25
+ * of its own.
26
+ */
27
+ static current(): Run | undefined;
28
+ /** runs `attempt` as this run */
29
+ within<T>(attempt: () => Promise<T>): Promise<T>;
30
+ /**
31
+ * A step that answers later: what was kept is given back, and what was not is made — under the
32
+ * identity the step's key is — kept, and what is kept is given back. So the operation is given
33
+ * what a later attempt will be given, including where a copy of the run kept it first.
34
+ */
35
+ step(member: string, args: unknown[], make: (id: string) => Promise<unknown>): Promise<unknown>;
36
+ /**
37
+ * A step that answers at once — a new id, the time, a random number. What was kept is given
38
+ * back; what was not is taken, given, and written without waiting, and anything the attempt makes
39
+ * after it waits for the write.
40
+ */
41
+ value<T>(member: string, take: () => T): T;
42
+ /** waits for what is being written, and raises where a write failed */
43
+ settle(): Promise<void>;
44
+ /**
45
+ * The member, a digest of what it was asked, and how many times this attempt has asked exactly
46
+ * that before: the order steps are made in is not the same on every attempt, and what they ask
47
+ * is.
48
+ */
49
+ private key;
50
+ /** fixed by the run's first step, so that a run is kept whole and reaped whole */
51
+ private expiry;
52
+ }
@@ -0,0 +1,109 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { entities, trail } from '@toa.io/core';
3
+ import { decode, encode } from './answers.js';
4
+ import { fingerprint } from './fingerprint.js';
5
+ /**
6
+ * One attempt of a run: a task or an event, known by the identity it carries on every attempt.
7
+ *
8
+ * Put in scope where the component is invoked, and read where the context is asked. The context
9
+ * of an operation is one object shared by every invocation of it, so nothing per invocation can
10
+ * be handed to it — as the trail is not, for the same reason.
11
+ */
12
+ export class Run {
13
+ id;
14
+ journal;
15
+ window;
16
+ steps;
17
+ expires;
18
+ /** how many times this attempt has asked each thing, by what it asked */
19
+ asked = new Map();
20
+ /** what was taken without waiting and is still being written */
21
+ writing = new Set();
22
+ // eslint-disable-next-line max-params
23
+ constructor(id, journal, window, recalled) {
24
+ this.id = id;
25
+ this.journal = journal;
26
+ this.window = window;
27
+ this.steps = recalled.steps;
28
+ this.expires = recalled.expires;
29
+ }
30
+ /** an attempt, with what the run kept before it */
31
+ static async begin(id, journal, window) {
32
+ return new Run(id, journal, window, await journal.recall(id));
33
+ }
34
+ /**
35
+ * The run the invocation running now is, where it is one. Only the invocation the run was
36
+ * handed to: a call it makes to its own component runs in the same scope, and under an identity
37
+ * of its own.
38
+ */
39
+ static current() {
40
+ const run = storage.getStore();
41
+ if (run === undefined || trail.current()?.id !== run.id)
42
+ return undefined;
43
+ return run;
44
+ }
45
+ /** runs `attempt` as this run */
46
+ async within(attempt) {
47
+ return await storage.run(this, attempt);
48
+ }
49
+ /**
50
+ * A step that answers later: what was kept is given back, and what was not is made — under the
51
+ * identity the step's key is — kept, and what is kept is given back. So the operation is given
52
+ * what a later attempt will be given, including where a copy of the run kept it first.
53
+ */
54
+ async step(member, args, make) {
55
+ const key = this.key(member, args);
56
+ const kept = this.steps.get(key);
57
+ if (kept !== undefined)
58
+ return decode(kept);
59
+ // a value taken without waiting may be what this step is asked with, so it is kept first
60
+ await this.settle();
61
+ const answer = await make(key);
62
+ const stored = await this.journal.record(this.id, key, await encode(answer), this.expiry());
63
+ this.steps.set(key, stored);
64
+ return decode(stored);
65
+ }
66
+ /**
67
+ * A step that answers at once — a new id, the time, a random number. What was kept is given
68
+ * back; what was not is taken, given, and written without waiting, and anything the attempt makes
69
+ * after it waits for the write.
70
+ */
71
+ value(member, take) {
72
+ const key = this.key(member, []);
73
+ const kept = this.steps.get(key);
74
+ if (kept !== undefined)
75
+ return decode(kept);
76
+ const value = take();
77
+ const encoded = { value };
78
+ this.steps.set(key, encoded);
79
+ const write = this.journal.record(this.id, key, encoded, this.expiry());
80
+ this.writing.add(write);
81
+ void write.then(() => this.writing.delete(write), () => undefined);
82
+ return value;
83
+ }
84
+ /** waits for what is being written, and raises where a write failed */
85
+ async settle() {
86
+ if (this.writing.size > 0)
87
+ await Promise.all(this.writing);
88
+ }
89
+ /**
90
+ * The member, a digest of what it was asked, and how many times this attempt has asked exactly
91
+ * that before: the order steps are made in is not the same on every attempt, and what they ask
92
+ * is.
93
+ */
94
+ key(member, args) {
95
+ const asked = member + ':' + fingerprint(args);
96
+ const count = this.asked.get(asked) ?? 0;
97
+ this.asked.set(asked, count + 1);
98
+ return entities.derive(this.id, asked, count);
99
+ }
100
+ /** fixed by the run's first step, so that a run is kept whole and reaped whole */
101
+ expiry() {
102
+ this.expires ??= Date.now() + this.window * 1000;
103
+ return this.expires;
104
+ }
105
+ }
106
+ // as the trail holds its own: a process may carry two copies of this module
107
+ const KEY = Symbol.for('toa.continuity.run');
108
+ const storage = (globalThis[KEY] ??= new AsyncLocalStorage());
109
+ //# sourceMappingURL=Run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Run.js","sourceRoot":"","sources":["../source/Run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * What a step answered, in the form it is kept in: something the journal's storage holds and the
3
+ * broker carries as it is, and that gives back what the operation was given.
4
+ */
5
+ export type Kept = {
6
+ value: unknown;
7
+ } | {
8
+ none: true;
9
+ } | {
10
+ bytes: string;
11
+ } | {
12
+ error: Record<string, unknown>;
13
+ } | {
14
+ response: KeptResponse;
15
+ };
16
+ interface KeptResponse {
17
+ status: number;
18
+ statusText: string;
19
+ headers: Array<[string, string]>;
20
+ body: string | null;
21
+ }
22
+ /**
23
+ * A stream cannot be kept: it is read once, and what it would give back is whatever is left of it.
24
+ * A `Response` is the exception, because what a `fetch` answers with is one, and it is read whole
25
+ * to be kept.
26
+ */
27
+ export declare function encode(answer: unknown): Promise<Kept>;
28
+ export declare function decode(kept: Kept): unknown;
29
+ export {};
@@ -0,0 +1,61 @@
1
+ import { exceptions } from '@toa.io/core';
2
+ /**
3
+ * A stream cannot be kept: it is read once, and what it would give back is whatever is left of it.
4
+ * A `Response` is the exception, because what a `fetch` answers with is one, and it is read whole
5
+ * to be kept.
6
+ */
7
+ export async function encode(answer) {
8
+ if (answer === undefined)
9
+ return { none: true };
10
+ if (answer instanceof Response)
11
+ return { response: await response(answer) };
12
+ if (streamed(answer))
13
+ throw new exceptions.UnrecordableException(`a step answered with a stream, which is read once and cannot be given back`);
14
+ if (ArrayBuffer.isView(answer))
15
+ return {
16
+ bytes: Buffer.from(answer.buffer, answer.byteOffset, answer.byteLength).toString('base64')
17
+ };
18
+ // a declared error a call answered with: its own properties are what the caller reads
19
+ if (answer instanceof Error)
20
+ return { error: { ...answer } };
21
+ return { value: answer };
22
+ }
23
+ export function decode(kept) {
24
+ if ('none' in kept)
25
+ return undefined;
26
+ if ('bytes' in kept)
27
+ return Buffer.from(kept.bytes, 'base64');
28
+ if ('error' in kept)
29
+ return Object.assign(new Error(), kept.error);
30
+ if ('response' in kept)
31
+ return revive(kept.response);
32
+ return kept.value;
33
+ }
34
+ async function response(answer) {
35
+ const body = answer.body === null
36
+ ? null
37
+ : Buffer.from(await answer.arrayBuffer()).toString('base64');
38
+ return {
39
+ status: answer.status,
40
+ statusText: answer.statusText,
41
+ headers: [...answer.headers.entries()],
42
+ body
43
+ };
44
+ }
45
+ function revive(kept) {
46
+ const body = kept.body === null ? null : Buffer.from(kept.body, 'base64');
47
+ return new Response(body, {
48
+ status: kept.status,
49
+ statusText: kept.statusText,
50
+ headers: kept.headers
51
+ });
52
+ }
53
+ function streamed(value) {
54
+ if (value === null || typeof value !== 'object')
55
+ return false;
56
+ const object = value;
57
+ return (typeof object.pipe === 'function' ||
58
+ typeof object.getReader === 'function' ||
59
+ typeof object[Symbol.asyncIterator] === 'function');
60
+ }
61
+ //# sourceMappingURL=answers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answers.js","sourceRoot":"","sources":["../source/answers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAoBzC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAe;IAC1C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAE/C,IAAI,MAAM,YAAY,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAA;IAE3E,IAAI,QAAQ,CAAC,MAAM,CAAC;QAClB,MAAM,IAAI,UAAU,CAAC,qBAAqB,CACxC,4EAA4E,CAC7E,CAAA;IAEH,IAAI,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC;QAC5B,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,CAC9E,QAAQ,CACT;SACF,CAAA;IAEH,sFAAsF;IACtF,IAAI,MAAM,YAAY,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,MAAM,EAAE,EAAE,CAAA;IAE5D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAU;IAC/B,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,SAAS,CAAA;IACpC,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;IAC7D,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAClE,IAAI,UAAU,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEpD,OAAO,IAAI,CAAC,KAAK,CAAA;AACnB,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,MAAgB;IACtC,MAAM,IAAI,GACR,MAAM,CAAC,IAAI,KAAK,IAAI;QAClB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAEhE,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI;KACL,CAAA;AACH,CAAC;AAED,SAAS,MAAM,CAAC,IAAkB;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IAEzE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE7D,MAAM,MAAM,GAAG,KAAyC,CAAA;IAExD,OAAO,CACL,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU;QACjC,OAAO,MAAM,CAAC,SAAS,KAAK,UAAU;QACtC,OAAO,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,UAAU,CACnD,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What a step asks, as a digest: the same arguments give the same one on every attempt, whatever
3
+ * order an object's keys were written in.
4
+ *
5
+ * Only what a step is asked is read. A value no attempt can give twice — a function, a signal, a
6
+ * stream — is told by its kind alone, so it neither tells two attempts apart nor fails to digest.
7
+ */
8
+ export declare function fingerprint(args: unknown[]): string;
@@ -0,0 +1,60 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * What a step asks, as a digest: the same arguments give the same one on every attempt, whatever
4
+ * order an object's keys were written in.
5
+ *
6
+ * Only what a step is asked is read. A value no attempt can give twice — a function, a signal, a
7
+ * stream — is told by its kind alone, so it neither tells two attempts apart nor fails to digest.
8
+ */
9
+ export function fingerprint(args) {
10
+ const hash = createHash('sha1');
11
+ write(hash, args, new Set());
12
+ return hash.digest('hex');
13
+ }
14
+ function write(hash, value, seen) {
15
+ if (value === null || typeof value !== 'object') {
16
+ // the type as well, so that `1` and `'1'` are two different asks
17
+ hash.update(typeof value + ':' + String(value) + ';');
18
+ return;
19
+ }
20
+ if (seen.has(value)) {
21
+ hash.update('cycle;');
22
+ return;
23
+ }
24
+ seen.add(value);
25
+ if (ArrayBuffer.isView(value)) {
26
+ hash.update('bytes:');
27
+ hash.update(value);
28
+ hash.update(';');
29
+ }
30
+ else if (value instanceof Date)
31
+ hash.update('date:' + value.toISOString() + ';');
32
+ else if (value instanceof URL)
33
+ hash.update('url:' + value.href + ';');
34
+ else if (Array.isArray(value)) {
35
+ hash.update('[');
36
+ for (const item of value)
37
+ write(hash, item, seen);
38
+ hash.update(']');
39
+ }
40
+ else if (plain(value)) {
41
+ hash.update('{');
42
+ for (const key of Object.keys(value).sort()) {
43
+ const item = value[key];
44
+ // absent and undefined are one ask, as they are once the request is on the wire
45
+ if (item === undefined)
46
+ continue;
47
+ hash.update(JSON.stringify(key) + '=');
48
+ write(hash, item, seen);
49
+ }
50
+ hash.update('}');
51
+ }
52
+ else
53
+ hash.update('<' + (value.constructor?.name ?? 'object') + '>;');
54
+ seen.delete(value);
55
+ }
56
+ function plain(value) {
57
+ const prototype = Object.getPrototypeOf(value);
58
+ return prototype === Object.prototype || prototype === null;
59
+ }
60
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../source/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAE/B,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAA;IAE5B,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC3B,CAAC;AAID,SAAS,KAAK,CAAC,IAAU,EAAE,KAAc,EAAE,IAAiB;IAC1D,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,iEAAiE;QACjE,IAAI,CAAC,MAAM,CAAC,OAAO,KAAK,GAAG,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAA;QAErD,OAAM;IACR,CAAC;IAED,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAErB,OAAM;IACR,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAEf,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACrB,IAAI,CAAC,MAAM,CAAC,KAAmB,CAAC,CAAA;QAChC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;SAAM,IAAI,KAAK,YAAY,IAAI;QAAE,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,GAAG,GAAG,CAAC,CAAA;SAC7E,IAAI,KAAK,YAAY,GAAG;QAAE,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,CAAA;SAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAEhB,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAEjD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;SAAM,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAEhB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAA;YAEpD,gFAAgF;YAChF,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAQ;YAEhC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;YACtC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACzB,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAClB,CAAC;;QAAM,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,IAAI,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAA;IAEtE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACpB,CAAC;AAED,SAAS,KAAK,CAAC,KAAa;IAC1B,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IAE9C,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI,CAAA;AAC7D,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Factory } from './Factory.ts';
2
+ export { components } from './Composition.ts';
3
+ export type { Declaration } from '@toa.io/definitions/extensions.continuity';
@@ -0,0 +1,3 @@
1
+ export { Factory } from './Factory.js';
2
+ export { components } from './Composition.js';
3
+ //# 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,UAAU,EAAE,MAAM,kBAAkB,CAAA"}