@toa.io/extensions.chronicles 0.0.0-stage → 1.0.0-alpha.333
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/LICENSE +22 -0
- package/components/chronicles.encounters/manifest.toa.yaml +108 -0
- package/components/chronicles.encounters/operations/anonymous.js +4 -0
- package/components/chronicles.encounters/operations/describe.js +4 -0
- package/components/chronicles.encounters/operations/identified.js +4 -0
- package/components/chronicles.encounters/types/index.d.ts +3 -0
- package/components/chronicles.encounters/types/toa.d.ts +36 -0
- package/components/chronicles.queries/manifest.toa.yaml +139 -0
- package/components/chronicles.queries/operations/define.js +3 -0
- package/components/chronicles.queries/operations/describe.js +3 -0
- package/components/chronicles.queries/operations/drop.js +3 -0
- package/components/chronicles.queries/operations/query.js +3 -0
- package/components/chronicles.queries/types/index.d.ts +3 -0
- package/components/chronicles.queries/types/toa.d.ts +49 -0
- package/components/chronicles.relay/manifest.toa.yaml +43 -0
- package/components/chronicles.relay/operations/encounter.js +4 -0
- package/components/chronicles.relay/operations/record.js +4 -0
- package/components/chronicles.relay/types/index.d.ts +3 -0
- package/components/chronicles.relay/types/toa.d.ts +24 -0
- package/guides/clickhouse.md +19 -0
- package/guides/log.md +52 -0
- package/package.json +39 -4
- package/readme.md +686 -0
- package/store/clickhouse.scope.sql +29 -0
- package/store/clickhouse.sql +99 -0
- package/transpiled/Client.d.ts +32 -0
- package/transpiled/Client.js +173 -0
- package/transpiled/Client.js.map +1 -0
- package/transpiled/Factory.d.ts +24 -0
- package/transpiled/Factory.js +62 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Provider.d.ts +142 -0
- package/transpiled/Provider.js +4 -0
- package/transpiled/Provider.js.map +1 -0
- package/transpiled/Publisher.d.ts +50 -0
- package/transpiled/Publisher.js +169 -0
- package/transpiled/Publisher.js.map +1 -0
- package/transpiled/Reader.d.ts +28 -0
- package/transpiled/Reader.js +166 -0
- package/transpiled/Reader.js.map +1 -0
- package/transpiled/Recorder.d.ts +38 -0
- package/transpiled/Recorder.js +107 -0
- package/transpiled/Recorder.js.map +1 -0
- package/transpiled/Relay.d.ts +14 -0
- package/transpiled/Relay.js +27 -0
- package/transpiled/Relay.js.map +1 -0
- package/transpiled/Sender.d.ts +25 -0
- package/transpiled/Sender.js +86 -0
- package/transpiled/Sender.js.map +1 -0
- package/transpiled/Service.d.ts +26 -0
- package/transpiled/Service.js +70 -0
- package/transpiled/Service.js.map +1 -0
- package/transpiled/address.d.ts +7 -0
- package/transpiled/address.js +27 -0
- package/transpiled/address.js.map +1 -0
- package/transpiled/agent.d.ts +16 -0
- package/transpiled/agent.js +48 -0
- package/transpiled/agent.js.map +1 -0
- package/transpiled/clickhouse/Client.d.ts +35 -0
- package/transpiled/clickhouse/Client.js +71 -0
- package/transpiled/clickhouse/Client.js.map +1 -0
- package/transpiled/clickhouse/Provider.d.ts +35 -0
- package/transpiled/clickhouse/Provider.js +169 -0
- package/transpiled/clickhouse/Provider.js.map +1 -0
- package/transpiled/clickhouse/ddl.d.ts +9 -0
- package/transpiled/clickhouse/ddl.js +15 -0
- package/transpiled/clickhouse/ddl.js.map +1 -0
- package/transpiled/clickhouse/views.d.ts +11 -0
- package/transpiled/clickhouse/views.js +97 -0
- package/transpiled/clickhouse/views.js.map +1 -0
- package/transpiled/contract.d.ts +12 -0
- package/transpiled/contract.js +58 -0
- package/transpiled/contract.js.map +1 -0
- package/transpiled/index.d.ts +15 -0
- package/transpiled/index.js +2 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/settings.d.ts +13 -0
- package/transpiled/settings.js +48 -0
- package/transpiled/settings.js.map +1 -0
- package/transpiled/store.d.ts +5 -0
- package/transpiled/store.js +11 -0
- package/transpiled/store.js.map +1 -0
- package/README.md +0 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- A context's user of the store: it reads the rows of its scope and nothing else, and keeps its
|
|
2
|
+
-- views in a database of its own. {scope} is the context's scope, {password} the `store_password`
|
|
3
|
+
-- of its `chronicles` secret.
|
|
4
|
+
|
|
5
|
+
CREATE DATABASE IF NOT EXISTS `{scope}`;
|
|
6
|
+
|
|
7
|
+
-- limits a query cannot lift, because they are its profile's, not settings a request passes
|
|
8
|
+
CREATE SETTINGS PROFILE IF NOT EXISTS `{scope}` SETTINGS
|
|
9
|
+
max_execution_time = 30 CONST,
|
|
10
|
+
max_result_rows = 10001 CONST,
|
|
11
|
+
max_result_bytes = 67108864 CONST,
|
|
12
|
+
max_memory_usage = 4000000000 CONST,
|
|
13
|
+
max_concurrent_queries_for_user = 4 CONST;
|
|
14
|
+
|
|
15
|
+
CREATE USER IF NOT EXISTS `{scope}` IDENTIFIED WITH sha256_password BY '{password}'
|
|
16
|
+
DEFAULT DATABASE chronicles SETTINGS PROFILE '{scope}';
|
|
17
|
+
|
|
18
|
+
GRANT SELECT ON chronicles.events TO `{scope}`;
|
|
19
|
+
GRANT SELECT ON chronicles.states TO `{scope}`;
|
|
20
|
+
GRANT SELECT ON chronicles.encounters TO `{scope}`;
|
|
21
|
+
GRANT SELECT, CREATE VIEW, CREATE TABLE, INSERT, DROP VIEW, DROP TABLE ON `{scope}`.* TO `{scope}`;
|
|
22
|
+
|
|
23
|
+
-- when its materialized views were last refreshed
|
|
24
|
+
GRANT SELECT ON system.view_refreshes TO `{scope}`;
|
|
25
|
+
|
|
26
|
+
-- replaced rather than kept: a policy outlives its user, and one left to a user made again would
|
|
27
|
+
-- apply to no one, which reads as no restriction at all
|
|
28
|
+
CREATE ROW POLICY OR REPLACE `{scope}` ON chronicles.events, chronicles.states, chronicles.encounters
|
|
29
|
+
USING scope = '{scope}' TO `{scope}`;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
-- The store of every context that publishes to one analytics broker: what Toa publishes, and what
|
|
2
|
+
-- it reads. Toa runs none of this; whoever sets the store up does, once, and again where a release
|
|
3
|
+
-- of Toa says so. {broker}, {broker_username} and {broker_password} are the broker ClickHouse
|
|
4
|
+
-- consumes, as ClickHouse reaches it.
|
|
5
|
+
|
|
6
|
+
CREATE DATABASE IF NOT EXISTS chronicles;
|
|
7
|
+
|
|
8
|
+
-- a row for everything a component did, by `id`, kept until `expires`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS chronicles.events (
|
|
10
|
+
scope LowCardinality(String),
|
|
11
|
+
id String,
|
|
12
|
+
label LowCardinality(String),
|
|
13
|
+
component LowCardinality(String),
|
|
14
|
+
time DateTime64(3, 'UTC'),
|
|
15
|
+
entity Nullable(String),
|
|
16
|
+
version Nullable(UInt64),
|
|
17
|
+
identity Nullable(String),
|
|
18
|
+
trace Nullable(String),
|
|
19
|
+
payload JSON,
|
|
20
|
+
expires DateTime DEFAULT '2106-01-01 00:00:00'
|
|
21
|
+
) ENGINE = ReplacingMergeTree
|
|
22
|
+
PARTITION BY toYYYYMM(time)
|
|
23
|
+
ORDER BY (scope, label, toDate(time), id)
|
|
24
|
+
TTL expires;
|
|
25
|
+
|
|
26
|
+
-- each entity as it stands, by the latest `version`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS chronicles.states (
|
|
28
|
+
scope LowCardinality(String),
|
|
29
|
+
component LowCardinality(String),
|
|
30
|
+
entity String,
|
|
31
|
+
version UInt64,
|
|
32
|
+
identity Nullable(String),
|
|
33
|
+
time DateTime64(3, 'UTC'),
|
|
34
|
+
payload JSON
|
|
35
|
+
) ENGINE = ReplacingMergeTree(version)
|
|
36
|
+
ORDER BY (scope, component, entity);
|
|
37
|
+
|
|
38
|
+
-- what clients sent, by `id`, kept and expired by when the gateway received it
|
|
39
|
+
CREATE TABLE IF NOT EXISTS chronicles.encounters (
|
|
40
|
+
scope LowCardinality(String),
|
|
41
|
+
id String,
|
|
42
|
+
name LowCardinality(String),
|
|
43
|
+
time DateTime64(3, 'UTC'),
|
|
44
|
+
received DateTime64(3, 'UTC'),
|
|
45
|
+
visitor Nullable(String),
|
|
46
|
+
session Nullable(String),
|
|
47
|
+
identity Nullable(String),
|
|
48
|
+
ip Nullable(String),
|
|
49
|
+
country LowCardinality(Nullable(String)),
|
|
50
|
+
origin LowCardinality(Nullable(String)),
|
|
51
|
+
agent Nullable(String),
|
|
52
|
+
browser LowCardinality(Nullable(String)),
|
|
53
|
+
os LowCardinality(Nullable(String)),
|
|
54
|
+
device LowCardinality(Nullable(String)),
|
|
55
|
+
payload JSON,
|
|
56
|
+
expires DateTime DEFAULT '2106-01-01 00:00:00'
|
|
57
|
+
) ENGINE = ReplacingMergeTree
|
|
58
|
+
PARTITION BY toYYYYMM(received)
|
|
59
|
+
ORDER BY (scope, name, toDate(time), id)
|
|
60
|
+
TTL expires;
|
|
61
|
+
|
|
62
|
+
-- what the broker delivers: one JSON row a message, from the exchange `chronicles` under the key
|
|
63
|
+
-- of its table, moved into the table by the view beside it
|
|
64
|
+
CREATE TABLE IF NOT EXISTS chronicles.events_queue AS chronicles.events
|
|
65
|
+
ENGINE = RabbitMQ SETTINGS
|
|
66
|
+
rabbitmq_host_port = '{broker}', rabbitmq_username = '{broker_username}',
|
|
67
|
+
rabbitmq_password = '{broker_password}', rabbitmq_exchange_name = 'chronicles',
|
|
68
|
+
rabbitmq_exchange_type = 'direct', rabbitmq_routing_key_list = 'events',
|
|
69
|
+
rabbitmq_queue_base = 'chronicles.events', rabbitmq_format = 'JSONEachRow',
|
|
70
|
+
rabbitmq_flush_interval_ms = 1000;
|
|
71
|
+
|
|
72
|
+
-- a message without `expires` reads it as zero rather than as the default, which would expire it
|
|
73
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS chronicles.events_consumer TO chronicles.events
|
|
74
|
+
AS SELECT * REPLACE (if(toUnixTimestamp(expires) = 0, toDateTime('2106-01-01 00:00:00'), expires) AS expires)
|
|
75
|
+
FROM chronicles.events_queue;
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS chronicles.states_queue AS chronicles.states
|
|
78
|
+
ENGINE = RabbitMQ SETTINGS
|
|
79
|
+
rabbitmq_host_port = '{broker}', rabbitmq_username = '{broker_username}',
|
|
80
|
+
rabbitmq_password = '{broker_password}', rabbitmq_exchange_name = 'chronicles',
|
|
81
|
+
rabbitmq_exchange_type = 'direct', rabbitmq_routing_key_list = 'states',
|
|
82
|
+
rabbitmq_queue_base = 'chronicles.states', rabbitmq_format = 'JSONEachRow',
|
|
83
|
+
rabbitmq_flush_interval_ms = 1000;
|
|
84
|
+
|
|
85
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS chronicles.states_consumer TO chronicles.states
|
|
86
|
+
AS SELECT * FROM chronicles.states_queue;
|
|
87
|
+
|
|
88
|
+
CREATE TABLE IF NOT EXISTS chronicles.encounters_queue AS chronicles.encounters
|
|
89
|
+
ENGINE = RabbitMQ SETTINGS
|
|
90
|
+
rabbitmq_host_port = '{broker}', rabbitmq_username = '{broker_username}',
|
|
91
|
+
rabbitmq_password = '{broker_password}', rabbitmq_exchange_name = 'chronicles',
|
|
92
|
+
rabbitmq_exchange_type = 'direct', rabbitmq_routing_key_list = 'encounters',
|
|
93
|
+
rabbitmq_queue_base = 'chronicles.encounters', rabbitmq_format = 'JSONEachRow',
|
|
94
|
+
rabbitmq_flush_interval_ms = 1000;
|
|
95
|
+
|
|
96
|
+
-- a message without `expires` reads it as zero rather than as the default, which would expire it
|
|
97
|
+
CREATE MATERIALIZED VIEW IF NOT EXISTS chronicles.encounters_consumer TO chronicles.encounters
|
|
98
|
+
AS SELECT * REPLACE (if(toUnixTimestamp(expires) = 0, toDateTime('2106-01-01 00:00:00'), expires) AS expires)
|
|
99
|
+
FROM chronicles.encounters_queue;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
import type { Address } from '@toa.io/definitions/extensions.chronicles';
|
|
4
|
+
/**
|
|
5
|
+
* What `chronicles.encounters` takes what clients send through: each event a row of `encounters`,
|
|
6
|
+
* with the client's own id and the visitor and the session the batch names, all of a batch one
|
|
7
|
+
* task to the relay, and answered once the context's broker has it — the relay publishes it, then
|
|
8
|
+
* or once the broker of the log is back.
|
|
9
|
+
*
|
|
10
|
+
* A client's clock is its own, and may be off by anything; what it measures between two of its
|
|
11
|
+
* moments is not. So an event is when the gateway received the batch, less how long before
|
|
12
|
+
* sending it the client says it happened: the time of the gateway, at the distance the client
|
|
13
|
+
* measured.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Client extends Connector {
|
|
16
|
+
readonly name = "chronicles";
|
|
17
|
+
private readonly host;
|
|
18
|
+
private readonly address;
|
|
19
|
+
private relay?;
|
|
20
|
+
constructor(host: extensions.Host, address: () => Address);
|
|
21
|
+
invoke(method: string, ...args: any[]): Promise<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* `identity` is the one the gateway verified the caller to be, from the path; `null` for the
|
|
24
|
+
* route anyone may call, whose body is then refused where it says whose it is.
|
|
25
|
+
*/
|
|
26
|
+
private write;
|
|
27
|
+
/**
|
|
28
|
+
* Found on the first batch and not before: an aspect is made ahead of the components of its
|
|
29
|
+
* composition, and one that could not be found is looked for again by the next batch.
|
|
30
|
+
*/
|
|
31
|
+
private relayed;
|
|
32
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Connector, Locator, exceptions } from '@toa.io/core';
|
|
2
|
+
import { NAMESPACE, RELAY } from '@toa.io/definitions/extensions.chronicles';
|
|
3
|
+
import { keep } from './address.js';
|
|
4
|
+
import { classify } from './agent.js';
|
|
5
|
+
/**
|
|
6
|
+
* What `chronicles.encounters` takes what clients send through: each event a row of `encounters`,
|
|
7
|
+
* with the client's own id and the visitor and the session the batch names, all of a batch one
|
|
8
|
+
* task to the relay, and answered once the context's broker has it — the relay publishes it, then
|
|
9
|
+
* or once the broker of the log is back.
|
|
10
|
+
*
|
|
11
|
+
* A client's clock is its own, and may be off by anything; what it measures between two of its
|
|
12
|
+
* moments is not. So an event is when the gateway received the batch, less how long before
|
|
13
|
+
* sending it the client says it happened: the time of the gateway, at the distance the client
|
|
14
|
+
* measured.
|
|
15
|
+
*/
|
|
16
|
+
export class Client extends Connector {
|
|
17
|
+
name = 'chronicles';
|
|
18
|
+
host;
|
|
19
|
+
address;
|
|
20
|
+
relay;
|
|
21
|
+
constructor(host, address) {
|
|
22
|
+
super();
|
|
23
|
+
this.host = host;
|
|
24
|
+
this.address = address;
|
|
25
|
+
}
|
|
26
|
+
async invoke(method, ...args) {
|
|
27
|
+
if (method !== 'write')
|
|
28
|
+
throw new exceptions.MisuseException(`Chronicles have no '${method}'`);
|
|
29
|
+
return await this.write(args[0], args[1] ?? null);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `identity` is the one the gateway verified the caller to be, from the path; `null` for the
|
|
33
|
+
* route anyone may call, whose body is then refused where it says whose it is.
|
|
34
|
+
*/
|
|
35
|
+
async write(input, identity) {
|
|
36
|
+
const received = Date.now();
|
|
37
|
+
const batch = parse(input, identity !== null);
|
|
38
|
+
if (batch instanceof Error)
|
|
39
|
+
return batch;
|
|
40
|
+
// put in by the gateway, which overwrites whatever the body says of them
|
|
41
|
+
const { ip, country, origin, agent } = input;
|
|
42
|
+
const address = keep(ip, this.address());
|
|
43
|
+
const from = typeof country === 'string' ? country : null;
|
|
44
|
+
const page = typeof origin === 'string' ? origin : null;
|
|
45
|
+
const client = typeof agent === 'string' ? agent : null;
|
|
46
|
+
const { browser, os, device } = classify(client);
|
|
47
|
+
const encounters = batch.events.map((event) => ({
|
|
48
|
+
id: event.id,
|
|
49
|
+
name: event.name,
|
|
50
|
+
time: received - (batch.sent - event.time),
|
|
51
|
+
received,
|
|
52
|
+
visitor: batch.visitor,
|
|
53
|
+
session: batch.session,
|
|
54
|
+
identity,
|
|
55
|
+
ip: address,
|
|
56
|
+
country: from,
|
|
57
|
+
origin: page,
|
|
58
|
+
agent: client,
|
|
59
|
+
browser,
|
|
60
|
+
os,
|
|
61
|
+
device,
|
|
62
|
+
payload: event.payload ?? {}
|
|
63
|
+
}));
|
|
64
|
+
const relay = await this.relayed();
|
|
65
|
+
await relay.invoke('encounter', { input: { encounters }, task: true });
|
|
66
|
+
return { status: 202, accepted: encounters.length };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Found on the first batch and not before: an aspect is made ahead of the components of its
|
|
70
|
+
* composition, and one that could not be found is looked for again by the next batch.
|
|
71
|
+
*/
|
|
72
|
+
async relayed() {
|
|
73
|
+
this.relay ??= (async () => {
|
|
74
|
+
const remote = await this.host.remote(LOCATOR, SOURCE);
|
|
75
|
+
this.depends(remote);
|
|
76
|
+
await remote.connect();
|
|
77
|
+
return remote;
|
|
78
|
+
})().catch((error) => {
|
|
79
|
+
this.relay = undefined;
|
|
80
|
+
throw error;
|
|
81
|
+
});
|
|
82
|
+
return await this.relay;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A batch, `{ sent, visitor?, session?, events }`: as JSON, or as the form `navigator.sendBeacon`
|
|
87
|
+
* sends, whose `events` is the JSON of them, and `sent` the digits of a number. Checked here rather than by the operation's schema, which
|
|
88
|
+
* cannot read the form's. `identified` is whether the gateway put the identity in, which is then a
|
|
89
|
+
* key the body may have beside `events`.
|
|
90
|
+
*/
|
|
91
|
+
function parse(input, identified) {
|
|
92
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
93
|
+
return invalid('The body is `{ sent, visitor?, session?, events }`');
|
|
94
|
+
// `ip`, `country`, `origin` and `agent` are the gateway's, which sets them on every request it
|
|
95
|
+
// makes
|
|
96
|
+
const { events: given, sent: stated, visitor, session, identity, ip: _ip, country: _country, origin: _origin, agent: _agent, ...rest } = input;
|
|
97
|
+
const extra = Object.keys(rest);
|
|
98
|
+
if (extra.length > 0)
|
|
99
|
+
return invalid(`The body has no '${extra[0]}'`);
|
|
100
|
+
// the identity is the path's, never the body's
|
|
101
|
+
if (!identified && identity !== undefined)
|
|
102
|
+
return invalid('The body names no identity: a client that has one sends it in the path');
|
|
103
|
+
// `null` is a client that has none, as absent is
|
|
104
|
+
for (const [key, value] of [
|
|
105
|
+
['visitor', visitor],
|
|
106
|
+
['session', session]
|
|
107
|
+
])
|
|
108
|
+
if (value !== undefined &&
|
|
109
|
+
value !== null &&
|
|
110
|
+
(typeof value !== 'string' || value === '' || value.length > 64))
|
|
111
|
+
return invalid(`\`${key}\` is a string of 1 to 64 characters`);
|
|
112
|
+
// a form's every value is a string
|
|
113
|
+
const sent = typeof stated === 'string' && /^\d{1,15}$/.test(stated) ? Number(stated) : stated;
|
|
114
|
+
if (!Number.isInteger(sent) || sent < 0)
|
|
115
|
+
return invalid('`sent` is when the client sent the batch, in milliseconds by its clock');
|
|
116
|
+
let events = given;
|
|
117
|
+
if (typeof events === 'string')
|
|
118
|
+
try {
|
|
119
|
+
events = JSON.parse(events);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return invalid('`events` is not JSON');
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(events))
|
|
125
|
+
return invalid('`events` is an array of events');
|
|
126
|
+
if (events.length > MAX_EVENTS)
|
|
127
|
+
return invalid(`A request carries at most ${MAX_EVENTS} events`);
|
|
128
|
+
for (const [index, event] of events.entries()) {
|
|
129
|
+
const problem = check(event);
|
|
130
|
+
if (problem !== null)
|
|
131
|
+
return invalid(problem, index);
|
|
132
|
+
if (event.time > sent)
|
|
133
|
+
return invalid('An event happens before the batch that carries it is sent', index);
|
|
134
|
+
if (JSON.stringify(event.payload ?? {}).length > MAX_PAYLOAD)
|
|
135
|
+
return new Error('OVERSIZED', {
|
|
136
|
+
cause: { index, message: `A payload is at most ${MAX_PAYLOAD} bytes of JSON` }
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
sent: sent,
|
|
141
|
+
visitor: visitor ?? null,
|
|
142
|
+
session: session ?? null,
|
|
143
|
+
events: events
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function check(event) {
|
|
147
|
+
if (typeof event !== 'object' || event === null || Array.isArray(event))
|
|
148
|
+
return 'An event is an object';
|
|
149
|
+
const { id, name, time, payload, ...rest } = event;
|
|
150
|
+
if (Object.keys(rest).length > 0)
|
|
151
|
+
return `An event has no '${Object.keys(rest)[0]}'`;
|
|
152
|
+
if (typeof id !== 'string' || id.length === 0 || id.length > 64)
|
|
153
|
+
return '`id` is a string of 1 to 64 characters';
|
|
154
|
+
if (typeof name !== 'string' || name.length > 64 || !NAME.test(name))
|
|
155
|
+
return '`name` is lowercase letters, digits and underscores from a letter, at most 64 characters';
|
|
156
|
+
if (!Number.isInteger(time) || time < 0)
|
|
157
|
+
return "`time` is when it happened, in milliseconds by the client's clock";
|
|
158
|
+
if (payload !== undefined &&
|
|
159
|
+
(typeof payload !== 'object' || payload === null || Array.isArray(payload)))
|
|
160
|
+
return '`payload` is an object';
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function invalid(message, index) {
|
|
164
|
+
return new Error('INVALID', {
|
|
165
|
+
cause: index === undefined ? { message } : { index, message }
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const MAX_EVENTS = 100;
|
|
169
|
+
const MAX_PAYLOAD = 16 * 1024;
|
|
170
|
+
const NAME = /^[a-z][a-z0-9_]*$/;
|
|
171
|
+
const LOCATOR = new Locator(RELAY, NAMESPACE);
|
|
172
|
+
const SOURCE = { service: 'chronicles' };
|
|
173
|
+
//# sourceMappingURL=Client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Client.js","sourceRoot":"","sources":["../source/Client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Connector, Locator } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
import type { Declaration } from '@toa.io/definitions/extensions.chronicles';
|
|
4
|
+
/**
|
|
5
|
+
* A component sends facts through its context; the events it declares reach the log as events of
|
|
6
|
+
* the context, which the service consumes. The components this extension ships are given what
|
|
7
|
+
* they serve instead: the queries, the store; the relay, the publisher; the encounters, the relay.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Factory implements extensions.Factory {
|
|
10
|
+
private readonly host;
|
|
11
|
+
/** the publisher of the service running, which the relay it runs publishes through */
|
|
12
|
+
private publisher;
|
|
13
|
+
/** one store for the process, made where the process is one that reads it */
|
|
14
|
+
private store;
|
|
15
|
+
constructor(host: extensions.Host);
|
|
16
|
+
aspect(locator: Locator, declaration?: Declaration): extensions.Aspect;
|
|
17
|
+
service(): Connector;
|
|
18
|
+
/**
|
|
19
|
+
* The publisher of the settings there are, made anew where they have changed or the one there was
|
|
20
|
+
* has stopped with its service.
|
|
21
|
+
*/
|
|
22
|
+
private publish;
|
|
23
|
+
private read;
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { environment } from '@toa.io/generic';
|
|
2
|
+
import { ENCOUNTERS, NAMESPACE, QUERIES, RELAY } from '@toa.io/definitions/extensions.chronicles';
|
|
3
|
+
import { Client } from './Client.js';
|
|
4
|
+
import { Publisher } from './Publisher.js';
|
|
5
|
+
import { Reader } from './Reader.js';
|
|
6
|
+
import { Relay } from './Relay.js';
|
|
7
|
+
import { Sender } from './Sender.js';
|
|
8
|
+
import { Service } from './Service.js';
|
|
9
|
+
import { credentials, settings } from './settings.js';
|
|
10
|
+
import { provider } from './store.js';
|
|
11
|
+
/**
|
|
12
|
+
* A component sends facts through its context; the events it declares reach the log as events of
|
|
13
|
+
* the context, which the service consumes. The components this extension ships are given what
|
|
14
|
+
* they serve instead: the queries, the store; the relay, the publisher; the encounters, the relay.
|
|
15
|
+
*/
|
|
16
|
+
export class Factory {
|
|
17
|
+
host;
|
|
18
|
+
/** the publisher of the service running, which the relay it runs publishes through */
|
|
19
|
+
publisher;
|
|
20
|
+
/** one store for the process, made where the process is one that reads it */
|
|
21
|
+
store;
|
|
22
|
+
constructor(host) {
|
|
23
|
+
this.host = host;
|
|
24
|
+
}
|
|
25
|
+
aspect(locator, declaration) {
|
|
26
|
+
if (mine(locator))
|
|
27
|
+
switch (locator.name) {
|
|
28
|
+
case QUERIES:
|
|
29
|
+
return new Reader(this.host, () => this.read(), settings);
|
|
30
|
+
case RELAY:
|
|
31
|
+
return new Relay(this.publish());
|
|
32
|
+
case ENCOUNTERS:
|
|
33
|
+
return new Client(this.host, () => settings().ip);
|
|
34
|
+
}
|
|
35
|
+
return new Sender(this.host, locator, declaration?.facts ?? {});
|
|
36
|
+
}
|
|
37
|
+
// nothing connects to this, so a halt takes the whole of it
|
|
38
|
+
service() {
|
|
39
|
+
return this.host.gate(async () => new Service(this.host, settings(), this.publish()));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The publisher of the settings there are, made anew where they have changed or the one there was
|
|
43
|
+
* has stopped with its service.
|
|
44
|
+
*/
|
|
45
|
+
publish() {
|
|
46
|
+
const { broker, retention } = settings();
|
|
47
|
+
if (this.publisher === undefined ||
|
|
48
|
+
this.publisher.done ||
|
|
49
|
+
this.publisher.address !== broker)
|
|
50
|
+
this.publisher = new Publisher(broker, credentials().broker, environment.scope(), retention);
|
|
51
|
+
return this.publisher;
|
|
52
|
+
}
|
|
53
|
+
read() {
|
|
54
|
+
this.store ??= provider(settings(), credentials().store, environment.scope());
|
|
55
|
+
return this.store;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Whether this is a component the extension ships rather than one of the application's. */
|
|
59
|
+
function mine(locator) {
|
|
60
|
+
return locator.namespace === NAMESPACE;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=Factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EACL,UAAU,EACV,SAAS,EACT,OAAO,EACP,KAAK,EACN,MAAM,2CAA2C,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAMrC;;;;GAIG;AACH,MAAM,OAAO,OAAO;IACD,IAAI,CAAiB;IAEtC,sFAAsF;IAC9E,SAAS,CAAuB;IAExC,6EAA6E;IACrE,KAAK,CAAsB;IAEnC,YAAmB,IAAqB;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAEM,MAAM,CAAC,OAAgB,EAAE,WAAyB;QACvD,IAAI,IAAI,CAAC,OAAO,CAAC;YACf,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,OAAO;oBACV,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAA;gBAC3D,KAAK,KAAK;oBACR,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;gBAClC,KAAK,UAAU;oBACb,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAA;YACrD,CAAC;QAEH,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,CAAA;IACjE,CAAC;IAED,4DAA4D;IACrD,OAAO;QACZ,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IACvF,CAAC;IAED;;;OAGG;IACK,OAAO;QACb,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,CAAA;QAExC,IACE,IAAI,CAAC,SAAS,KAAK,SAAS;YAC5B,IAAI,CAAC,SAAS,CAAC,IAAI;YACnB,IAAI,CAAC,SAAS,CAAC,OAAO,KAAK,MAAM;YAEjC,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAC5B,MAAM,EACN,WAAW,EAAE,CAAC,MAAM,EACpB,WAAW,CAAC,KAAK,EAAE,EACnB,SAAS,CACV,CAAA;QAEH,OAAO,IAAI,CAAC,SAAS,CAAA;IACvB,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,QAAQ,EAAE,EAAE,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC,CAAA;QAE7E,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;CACF;AAED,4FAA4F;AAC5F,SAAS,IAAI,CAAC,OAAgB;IAC5B,OAAO,OAAO,CAAC,SAAS,KAAK,SAAS,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Property } from '@toa.io/definitions/extensions.chronicles';
|
|
2
|
+
/**
|
|
3
|
+
* Where the log is read. A provider reads the log's tables — `events`, a row for everything a
|
|
4
|
+
* component did, `states`, each entity as it stands, and `encounters`, what clients sent — in a
|
|
5
|
+
* dialect of its own, which is what an agent writes its queries in. What it reads is what the store
|
|
6
|
+
* lets the context's user read; it creates nothing there but the views it is asked to.
|
|
7
|
+
*/
|
|
8
|
+
export interface Provider {
|
|
9
|
+
/** how the store is read: its dialect and what is particular to it, for whoever writes a query */
|
|
10
|
+
readonly guide: string;
|
|
11
|
+
/** the names a view cannot take: the provider's own tables */
|
|
12
|
+
readonly tables: string[];
|
|
13
|
+
/** the fields of declared properties, at every depth, each with the type its view reads it as */
|
|
14
|
+
fields(properties: Record<string, Property>): Field[];
|
|
15
|
+
/** the labels written, with their counts and their times */
|
|
16
|
+
labels(): Promise<Label[]>;
|
|
17
|
+
/** the components with a state, with how many entities each has */
|
|
18
|
+
states(): Promise<Summary[]>;
|
|
19
|
+
/** the names of encounters, with their counts, their times and the fields their payloads have */
|
|
20
|
+
encounters(): Promise<Encountered[]>;
|
|
21
|
+
/** a statement of the dialect, run as a reader that can read the log and nothing else */
|
|
22
|
+
query(sql: string): Promise<Result>;
|
|
23
|
+
/** refuses what a view could not be: anything but one statement a query could run */
|
|
24
|
+
validate(sql: string): Promise<void>;
|
|
25
|
+
/** the views defined over the log */
|
|
26
|
+
views(): Promise<View[]>;
|
|
27
|
+
/** a view of `name`, replacing one there is; with `refresh`, recomputed on that cycle */
|
|
28
|
+
define(definition: Definition): Promise<void>;
|
|
29
|
+
drop(name: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** A row of `events`. */
|
|
32
|
+
export interface Row {
|
|
33
|
+
/** the same however many times it is delivered */
|
|
34
|
+
id: string;
|
|
35
|
+
/** `<component>.<event>`, or `<component>.<name>` for a fact */
|
|
36
|
+
label: string;
|
|
37
|
+
/** the component it is of */
|
|
38
|
+
component: string;
|
|
39
|
+
/** when it happened, in milliseconds */
|
|
40
|
+
time: number;
|
|
41
|
+
/** the id of the record that changed, where the payload carries one */
|
|
42
|
+
entity: string | null;
|
|
43
|
+
/** its `VERSION`, likewise */
|
|
44
|
+
version: number | null;
|
|
45
|
+
/** the identity it is of, where something says so */
|
|
46
|
+
identity: string | null;
|
|
47
|
+
/** the W3C traceparent of the call that caused it */
|
|
48
|
+
trace: string | null;
|
|
49
|
+
payload: object;
|
|
50
|
+
}
|
|
51
|
+
/** An entity as it stands, as the event a component names its `state` renders it. */
|
|
52
|
+
export interface State {
|
|
53
|
+
component: string;
|
|
54
|
+
entity: string;
|
|
55
|
+
version: number;
|
|
56
|
+
identity: string | null;
|
|
57
|
+
time: number;
|
|
58
|
+
payload: object;
|
|
59
|
+
}
|
|
60
|
+
/** A row of `encounters`: what a client sent. */
|
|
61
|
+
export interface Encounter {
|
|
62
|
+
/** the client's own */
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
/** when it happened, by the gateway's clock, at the distance from sending the client measured */
|
|
66
|
+
time: number;
|
|
67
|
+
/** when the gateway received it */
|
|
68
|
+
received: number;
|
|
69
|
+
/** the client's id of itself, and of the period of its activity, where it gave them */
|
|
70
|
+
visitor: string | null;
|
|
71
|
+
session: string | null;
|
|
72
|
+
/** the identity the gateway verified the client to be */
|
|
73
|
+
identity: string | null;
|
|
74
|
+
/** the address and the country the request came from */
|
|
75
|
+
ip: string | null;
|
|
76
|
+
country: string | null;
|
|
77
|
+
/** the origin of the page that sent it, and the client's `user-agent` */
|
|
78
|
+
origin: string | null;
|
|
79
|
+
agent: string | null;
|
|
80
|
+
/** what the agent says the client is: its browser, its system, its kind of device */
|
|
81
|
+
browser: string | null;
|
|
82
|
+
os: string | null;
|
|
83
|
+
device: string | null;
|
|
84
|
+
payload: object;
|
|
85
|
+
}
|
|
86
|
+
export interface Label {
|
|
87
|
+
label: string;
|
|
88
|
+
rows: number;
|
|
89
|
+
first: string;
|
|
90
|
+
last: string;
|
|
91
|
+
}
|
|
92
|
+
export interface Summary {
|
|
93
|
+
component: string;
|
|
94
|
+
entities: number;
|
|
95
|
+
/** when the latest change of an entity was made */
|
|
96
|
+
last: string;
|
|
97
|
+
}
|
|
98
|
+
export interface Encountered {
|
|
99
|
+
name: string;
|
|
100
|
+
rows: number;
|
|
101
|
+
first: string;
|
|
102
|
+
last: string;
|
|
103
|
+
received: string;
|
|
104
|
+
/** the paths of the latest payloads, each with the types it was sent as */
|
|
105
|
+
fields: Array<{
|
|
106
|
+
path: string;
|
|
107
|
+
types: string[];
|
|
108
|
+
}>;
|
|
109
|
+
}
|
|
110
|
+
/** A field of a declared payload, and the type its view reads it as. */
|
|
111
|
+
export interface Field {
|
|
112
|
+
path: string;
|
|
113
|
+
type: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
/** the expression that reads it from its table in its type, of a property of the payload */
|
|
116
|
+
read?: string;
|
|
117
|
+
}
|
|
118
|
+
export interface Definition {
|
|
119
|
+
name: string;
|
|
120
|
+
sql: string;
|
|
121
|
+
/** seconds between computations of a view that is a table */
|
|
122
|
+
refresh?: number;
|
|
123
|
+
title?: string;
|
|
124
|
+
description?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface View extends Definition {
|
|
127
|
+
/** what a query reads it by */
|
|
128
|
+
read: string;
|
|
129
|
+
/** when a view that is a table was last computed */
|
|
130
|
+
refreshed?: string;
|
|
131
|
+
}
|
|
132
|
+
export interface Result {
|
|
133
|
+
columns: Array<{
|
|
134
|
+
name: string;
|
|
135
|
+
type: string;
|
|
136
|
+
}>;
|
|
137
|
+
rows: unknown[][];
|
|
138
|
+
truncated: boolean;
|
|
139
|
+
}
|
|
140
|
+
/** What a provider refused a statement with, in its own words. */
|
|
141
|
+
export declare class Refusal extends Error {
|
|
142
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Provider.js","sourceRoot":"","sources":["../source/Provider.ts"],"names":[],"mappings":"AAiLA,kEAAkE;AAClE,MAAM,OAAO,OAAQ,SAAQ,KAAK;CAAG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Encounter, Row, State } from './Provider.ts';
|
|
3
|
+
import type { User } from './settings.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Rows to the broker of the log: each a JSON message to the exchange `chronicles` under the key of
|
|
6
|
+
* its table, with the scope of the context and when it expires. A publication resolves once the
|
|
7
|
+
* broker has confirmed it, which is what the caller acknowledges what it carried by.
|
|
8
|
+
*
|
|
9
|
+
* The connection is made by the first publication and not before, and nothing waits for it to
|
|
10
|
+
* start: while the broker is away, a publication waits for it, and what it carries waits, unacknowledged,
|
|
11
|
+
* where it came from.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Publisher extends Connector {
|
|
14
|
+
private readonly url;
|
|
15
|
+
private readonly scope;
|
|
16
|
+
private readonly retention;
|
|
17
|
+
private io?;
|
|
18
|
+
private opened?;
|
|
19
|
+
/** aborted as the publisher stops: what waits for the broker then gives up, to be delivered again */
|
|
20
|
+
private closing;
|
|
21
|
+
private stopped;
|
|
22
|
+
/** where it publishes, which a publisher of other settings does not */
|
|
23
|
+
readonly address: string;
|
|
24
|
+
constructor(address: string, user: User, scope: string, retention?: number);
|
|
25
|
+
publish(rows: Row[], states?: State[], encounters?: Encounter[]): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Fails what waits for the broker, and what is published from now on, so the service publishing
|
|
28
|
+
* through it is not held open by what would wait for the broker past the service's own end: it
|
|
29
|
+
* is delivered again, to this process or another.
|
|
30
|
+
*/
|
|
31
|
+
get done(): boolean;
|
|
32
|
+
stop(): void;
|
|
33
|
+
protected open(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* A connection still being made is not waited for — it may never be — but closed once it is,
|
|
36
|
+
* and what waits for it is failed, so it is delivered again, to this process or another.
|
|
37
|
+
*/
|
|
38
|
+
protected close(): Promise<void>;
|
|
39
|
+
private connection;
|
|
40
|
+
private route;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A row as the store reads it: of a scope, its times in the store's format, and when it expires,
|
|
44
|
+
* where it does.
|
|
45
|
+
*/
|
|
46
|
+
export declare function message(row: Row | State | Encounter, scope: string, until: string | null): Record<string, unknown>;
|
|
47
|
+
/** When a row stops being kept: its time and the retention, or the end of the store's time. */
|
|
48
|
+
export declare function expires(from: number, retention: number | undefined): string;
|
|
49
|
+
/** A `DateTime64(3)` as the store reads one without guessing: UTC, to the millisecond. */
|
|
50
|
+
export declare function timestamp(ms: number): string;
|