@toa.io/extensions.chronicles 0.0.0-stage → 1.0.0-alpha.332
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/components/chronicles.facts/manifest.toa.yaml +32 -0
- package/components/chronicles.facts/operations/record.js +4 -0
- package/components/chronicles.facts/types/index.d.ts +3 -0
- package/components/chronicles.facts/types/toa.d.ts +19 -0
- package/components/chronicles.ingestion/manifest.toa.yaml +60 -0
- package/components/chronicles.ingestion/operations/anonymous.js +4 -0
- package/components/chronicles.ingestion/operations/identified.js +4 -0
- package/components/chronicles.ingestion/types/index.d.ts +3 -0
- package/components/chronicles.ingestion/types/toa.d.ts +23 -0
- package/components/chronicles.queries/manifest.toa.yaml +115 -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 +44 -0
- package/package.json +35 -4
- package/readme.md +369 -0
- package/transpiled/Client.d.ts +19 -0
- package/transpiled/Client.js +121 -0
- package/transpiled/Client.js.map +1 -0
- package/transpiled/Factory.d.ts +16 -0
- package/transpiled/Factory.js +53 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Facts.d.ts +9 -0
- package/transpiled/Facts.js +16 -0
- package/transpiled/Facts.js.map +1 -0
- package/transpiled/Provider.d.ts +91 -0
- package/transpiled/Provider.js +4 -0
- package/transpiled/Provider.js.map +1 -0
- package/transpiled/Reader.d.ts +16 -0
- package/transpiled/Reader.js +82 -0
- package/transpiled/Reader.js.map +1 -0
- package/transpiled/Recorder.d.ts +28 -0
- package/transpiled/Recorder.js +82 -0
- package/transpiled/Recorder.js.map +1 -0
- package/transpiled/Scribe.d.ts +25 -0
- package/transpiled/Scribe.js +61 -0
- package/transpiled/Scribe.js.map +1 -0
- package/transpiled/Sender.d.ts +21 -0
- package/transpiled/Sender.js +69 -0
- package/transpiled/Sender.js.map +1 -0
- package/transpiled/Writer.d.ts +14 -0
- package/transpiled/Writer.js +50 -0
- package/transpiled/Writer.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/clickhouse/Client.d.ts +38 -0
- package/transpiled/clickhouse/Client.js +73 -0
- package/transpiled/clickhouse/Client.js.map +1 -0
- package/transpiled/clickhouse/Provider.d.ts +33 -0
- package/transpiled/clickhouse/Provider.js +144 -0
- package/transpiled/clickhouse/Provider.js.map +1 -0
- package/transpiled/clickhouse/ddl.d.ts +26 -0
- package/transpiled/clickhouse/ddl.js +88 -0
- package/transpiled/clickhouse/ddl.js.map +1 -0
- package/transpiled/index.d.ts +9 -0
- package/transpiled/index.js +2 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/settings.d.ts +11 -0
- package/transpiled/settings.js +35 -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,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the log is kept. A provider holds the log's two tables — `events`, a row for everything
|
|
3
|
+
* that happened, and `states`, each entity as it stands — and reads them in a dialect of its own,
|
|
4
|
+
* which is what an agent writes its queries in.
|
|
5
|
+
*/
|
|
6
|
+
export interface Provider {
|
|
7
|
+
/** what a query is written in, which `describe` tells whoever writes one */
|
|
8
|
+
readonly dialect: string;
|
|
9
|
+
/** the names a view cannot take: the provider's own tables */
|
|
10
|
+
readonly tables: string[];
|
|
11
|
+
/** the tables, and the reader queries run as, made what the settings say */
|
|
12
|
+
provision(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Rows of `events`, written once however often a row of one `id` is written; and states, each
|
|
15
|
+
* kept where it is of a higher version than the one there.
|
|
16
|
+
*/
|
|
17
|
+
write(rows: Row[], states: State[]): Promise<void>;
|
|
18
|
+
/** the labels, with their counts, their times and the fields of their payloads */
|
|
19
|
+
labels(): Promise<Label[]>;
|
|
20
|
+
/** the components with a state, with how many entities each has and the fields of the state */
|
|
21
|
+
states(): Promise<Summary[]>;
|
|
22
|
+
/** a statement of the dialect, run as a reader that can read the log and nothing else */
|
|
23
|
+
query(sql: string): Promise<Result>;
|
|
24
|
+
/** refuses what a view could not be: anything but one statement a query could run */
|
|
25
|
+
validate(sql: string): Promise<void>;
|
|
26
|
+
views(): Promise<View[]>;
|
|
27
|
+
/** a view of `name`, replacing one there is; with `refresh`, recomputed on that cycle */
|
|
28
|
+
define(name: string, sql: string, refresh?: number): Promise<void>;
|
|
29
|
+
drop(name: string): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** A row of the log. */
|
|
32
|
+
export interface Row {
|
|
33
|
+
/** the same however many times it is delivered */
|
|
34
|
+
id: string;
|
|
35
|
+
/** `<component>.<event>`, `<component>.<name>` for a fact, `agent.<name>` for a browser */
|
|
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 address and the country of the browser that sent it, where one did */
|
|
48
|
+
ip: string | null;
|
|
49
|
+
country: string | null;
|
|
50
|
+
/** the W3C traceparent of the call that caused it */
|
|
51
|
+
trace: string | null;
|
|
52
|
+
payload: object;
|
|
53
|
+
}
|
|
54
|
+
/** An entity as it stands, as the event a component names its `state` renders it. */
|
|
55
|
+
export interface State {
|
|
56
|
+
component: string;
|
|
57
|
+
entity: string;
|
|
58
|
+
version: number;
|
|
59
|
+
identity: string | null;
|
|
60
|
+
time: number;
|
|
61
|
+
payload: object;
|
|
62
|
+
}
|
|
63
|
+
export interface Summary {
|
|
64
|
+
component: string;
|
|
65
|
+
entities: number;
|
|
66
|
+
paths: string[];
|
|
67
|
+
}
|
|
68
|
+
export interface Label {
|
|
69
|
+
label: string;
|
|
70
|
+
rows: number;
|
|
71
|
+
first: string;
|
|
72
|
+
last: string;
|
|
73
|
+
paths: string[];
|
|
74
|
+
}
|
|
75
|
+
export interface View {
|
|
76
|
+
name: string;
|
|
77
|
+
sql: string;
|
|
78
|
+
/** seconds between computations of a view that is a table */
|
|
79
|
+
refresh?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface Result {
|
|
82
|
+
columns: Array<{
|
|
83
|
+
name: string;
|
|
84
|
+
type: string;
|
|
85
|
+
}>;
|
|
86
|
+
rows: unknown[][];
|
|
87
|
+
truncated: boolean;
|
|
88
|
+
}
|
|
89
|
+
/** What a provider refused a statement with, in its own words. */
|
|
90
|
+
export declare class Refusal extends Error {
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Provider.js","sourceRoot":"","sources":["../source/Provider.ts"],"names":[],"mappings":"AAgHA,kEAAkE;AAClE,MAAM,OAAO,OAAQ,SAAQ,KAAK;CAAG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Provider } from './Provider.ts';
|
|
3
|
+
/**
|
|
4
|
+
* What `chronicles.queries` reads the log through: queries in the provider's dialect, and views
|
|
5
|
+
* made from a statement the provider let a query run.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Reader extends Connector {
|
|
8
|
+
readonly name = "chronicles";
|
|
9
|
+
private readonly provider;
|
|
10
|
+
constructor(provider: () => Provider);
|
|
11
|
+
invoke(method: string, ...args: any[]): Promise<unknown>;
|
|
12
|
+
private describe;
|
|
13
|
+
private query;
|
|
14
|
+
private define;
|
|
15
|
+
private drop;
|
|
16
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Connector, exceptions } from '@toa.io/core';
|
|
2
|
+
import { Refusal } from './Provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* What `chronicles.queries` reads the log through: queries in the provider's dialect, and views
|
|
5
|
+
* made from a statement the provider let a query run.
|
|
6
|
+
*/
|
|
7
|
+
export class Reader extends Connector {
|
|
8
|
+
name = 'chronicles';
|
|
9
|
+
provider;
|
|
10
|
+
constructor(provider) {
|
|
11
|
+
super();
|
|
12
|
+
this.provider = provider;
|
|
13
|
+
}
|
|
14
|
+
async invoke(method, ...args) {
|
|
15
|
+
switch (method) {
|
|
16
|
+
case 'describe':
|
|
17
|
+
return await this.describe();
|
|
18
|
+
case 'query':
|
|
19
|
+
return await this.query(args[0]);
|
|
20
|
+
case 'define':
|
|
21
|
+
return await this.define(args[0], args[1], args[2]);
|
|
22
|
+
case 'drop':
|
|
23
|
+
return await this.drop(args[0]);
|
|
24
|
+
default:
|
|
25
|
+
throw new exceptions.MisuseException(`Chronicles have no '${method}'`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async describe() {
|
|
29
|
+
const provider = this.provider();
|
|
30
|
+
return {
|
|
31
|
+
dialect: provider.dialect,
|
|
32
|
+
labels: await provider.labels(),
|
|
33
|
+
states: await provider.states(),
|
|
34
|
+
views: await provider.views()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async query(sql) {
|
|
38
|
+
return await refused(async () => await this.provider().query(statement(sql)));
|
|
39
|
+
}
|
|
40
|
+
async define(name, sql, refresh) {
|
|
41
|
+
const provider = this.provider();
|
|
42
|
+
if (provider.tables.includes(name))
|
|
43
|
+
return new Error('RESERVED', {
|
|
44
|
+
cause: { message: `'${name}' is a table of the log` }
|
|
45
|
+
});
|
|
46
|
+
const select = statement(sql);
|
|
47
|
+
const invalid = await refused(async () => await provider.validate(select));
|
|
48
|
+
if (invalid instanceof Error)
|
|
49
|
+
return invalid;
|
|
50
|
+
const views = await provider.views();
|
|
51
|
+
if (!views.some((view) => view.name === name) && views.length >= MAX_VIEWS)
|
|
52
|
+
return new Error('EXHAUSTED', {
|
|
53
|
+
cause: { message: `A context has at most ${MAX_VIEWS} views` }
|
|
54
|
+
});
|
|
55
|
+
const defined = await refused(async () => await provider.define(name, select, refresh));
|
|
56
|
+
return defined instanceof Error ? defined : undefined;
|
|
57
|
+
}
|
|
58
|
+
async drop(name) {
|
|
59
|
+
const provider = this.provider();
|
|
60
|
+
const views = await provider.views();
|
|
61
|
+
if (!views.some((view) => view.name === name))
|
|
62
|
+
return new Error('NOT_FOUND');
|
|
63
|
+
await provider.drop(name);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** One statement: a trailing semicolon is an agent's habit. */
|
|
68
|
+
function statement(sql) {
|
|
69
|
+
return sql.trim().replace(/;+$/, '').trim();
|
|
70
|
+
}
|
|
71
|
+
async function refused(fn) {
|
|
72
|
+
try {
|
|
73
|
+
return await fn();
|
|
74
|
+
}
|
|
75
|
+
catch (exception) {
|
|
76
|
+
if (exception instanceof Refusal)
|
|
77
|
+
return new Error('REFUSED', { cause: { message: exception.message } });
|
|
78
|
+
throw exception;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const MAX_VIEWS = 50;
|
|
82
|
+
//# sourceMappingURL=Reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Reader.js","sourceRoot":"","sources":["../source/Reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Writer } from './Writer.ts';
|
|
3
|
+
import type { Row, State } from './Provider.ts';
|
|
4
|
+
import type { Message } from '@toa.io/core/types';
|
|
5
|
+
/**
|
|
6
|
+
* The consumer of one event the log is kept of: a row of `events` for an event a component names
|
|
7
|
+
* under `events`, and a state for the one it names its `state`. A message carries what the event
|
|
8
|
+
* rendered and its id, so what is said of the entity — which, its version, when — is read from
|
|
9
|
+
* the payload, where the entity as it stands carries `id`, `VERSION` and `UPDATED`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Recorder extends Connector {
|
|
12
|
+
private readonly label;
|
|
13
|
+
private readonly component;
|
|
14
|
+
private readonly kind;
|
|
15
|
+
private readonly identity;
|
|
16
|
+
private readonly writer;
|
|
17
|
+
/** `identity` is the property of the payload that names whose a row is, where the component has one */
|
|
18
|
+
constructor(label: string, kind: Kind, identity: string | null, writer: Writer);
|
|
19
|
+
receive(message: Message): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function row(label: string, component: string, message: Message, identity?: string | null): Row;
|
|
22
|
+
/**
|
|
23
|
+
* The entity as its state event renders it. One that does not say which entity it is, and which
|
|
24
|
+
* version, cannot be kept, and no delivery of it will say otherwise: refused as a contract the
|
|
25
|
+
* component broke, so it is parked on its first delivery rather than retried.
|
|
26
|
+
*/
|
|
27
|
+
export declare function state(label: string, component: string, message: Message, identity?: string | null): State;
|
|
28
|
+
export type Kind = 'event' | 'state';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Connector, entities, exceptions } from '@toa.io/core';
|
|
2
|
+
/**
|
|
3
|
+
* The consumer of one event the log is kept of: a row of `events` for an event a component names
|
|
4
|
+
* under `events`, and a state for the one it names its `state`. A message carries what the event
|
|
5
|
+
* rendered and its id, so what is said of the entity — which, its version, when — is read from
|
|
6
|
+
* the payload, where the entity as it stands carries `id`, `VERSION` and `UPDATED`.
|
|
7
|
+
*/
|
|
8
|
+
export class Recorder extends Connector {
|
|
9
|
+
label;
|
|
10
|
+
component;
|
|
11
|
+
kind;
|
|
12
|
+
identity;
|
|
13
|
+
writer;
|
|
14
|
+
/** `identity` is the property of the payload that names whose a row is, where the component has one */
|
|
15
|
+
// eslint-disable-next-line max-params
|
|
16
|
+
constructor(label, kind, identity, writer) {
|
|
17
|
+
super();
|
|
18
|
+
this.label = label;
|
|
19
|
+
this.component = label.slice(0, label.lastIndexOf('.'));
|
|
20
|
+
this.kind = kind;
|
|
21
|
+
this.identity = identity;
|
|
22
|
+
this.writer = writer;
|
|
23
|
+
}
|
|
24
|
+
async receive(message) {
|
|
25
|
+
if (this.kind === 'state')
|
|
26
|
+
await this.writer.write([], [state(this.label, this.component, message, this.identity)]);
|
|
27
|
+
else
|
|
28
|
+
await this.writer.write([row(this.label, this.component, message, this.identity)]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line max-params
|
|
32
|
+
export function row(label, component, message, identity = null) {
|
|
33
|
+
const payload = object(message.payload);
|
|
34
|
+
const { id, VERSION, UPDATED } = payload;
|
|
35
|
+
return {
|
|
36
|
+
// a message from outside Toa carries no id, and is then the same message by what it says
|
|
37
|
+
id: message.id ?? entities.derive(label, JSON.stringify(payload)),
|
|
38
|
+
label,
|
|
39
|
+
component,
|
|
40
|
+
time: typeof UPDATED === 'number' ? UPDATED : Date.now(),
|
|
41
|
+
entity: typeof id === 'string' ? id : null,
|
|
42
|
+
version: typeof VERSION === 'number' ? VERSION : null,
|
|
43
|
+
identity: whose(payload, identity),
|
|
44
|
+
ip: null,
|
|
45
|
+
country: null,
|
|
46
|
+
trace: message.telemetry ?? null,
|
|
47
|
+
payload
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The entity as its state event renders it. One that does not say which entity it is, and which
|
|
52
|
+
* version, cannot be kept, and no delivery of it will say otherwise: refused as a contract the
|
|
53
|
+
* component broke, so it is parked on its first delivery rather than retried.
|
|
54
|
+
*/
|
|
55
|
+
// eslint-disable-next-line max-params
|
|
56
|
+
export function state(label, component, message, identity = null) {
|
|
57
|
+
const payload = object(message.payload);
|
|
58
|
+
const { id, VERSION, UPDATED } = payload;
|
|
59
|
+
if (typeof id !== 'string' || typeof VERSION !== 'number')
|
|
60
|
+
throw new exceptions.EntityContractException(`The state '${label}' is an entity as it stands, and its payload carries no 'id' and 'VERSION'`);
|
|
61
|
+
return {
|
|
62
|
+
component,
|
|
63
|
+
entity: id,
|
|
64
|
+
version: VERSION,
|
|
65
|
+
identity: whose(payload, identity),
|
|
66
|
+
time: typeof UPDATED === 'number' ? UPDATED : Date.now(),
|
|
67
|
+
payload
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** The identity a payload names in the property the component declares, where it is one. */
|
|
71
|
+
function whose(payload, property) {
|
|
72
|
+
if (property === null)
|
|
73
|
+
return null;
|
|
74
|
+
const value = payload[property];
|
|
75
|
+
return typeof value === 'string' ? value : null;
|
|
76
|
+
}
|
|
77
|
+
function object(payload) {
|
|
78
|
+
return typeof payload === 'object' && payload !== null && !Array.isArray(payload)
|
|
79
|
+
? payload
|
|
80
|
+
: { value: payload };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=Recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Recorder.js","sourceRoot":"","sources":["../source/Recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Connector } from '@toa.io/core';
|
|
2
|
+
import type { Provider } from './Provider.ts';
|
|
3
|
+
import type { Writer } from './Writer.ts';
|
|
4
|
+
import type { Settings } from '@toa.io/definitions/extensions.chronicles';
|
|
5
|
+
import type { extensions } from '@toa.io/core/types';
|
|
6
|
+
/**
|
|
7
|
+
* The extension's own service: the store provisioned, then a consumer of every event the log is
|
|
8
|
+
* kept of — one group for every replica, so each event is written once — and the components it
|
|
9
|
+
* ships: the queries, the facts, and the client route where the annotation asks for it.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Scribe extends Connector {
|
|
12
|
+
private readonly host;
|
|
13
|
+
private readonly settings;
|
|
14
|
+
private readonly provider;
|
|
15
|
+
private readonly writer;
|
|
16
|
+
constructor(host: extensions.Host, settings: Settings, provider: Provider, writer: Writer);
|
|
17
|
+
protected open(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** The components this extension ships, which run inside its own service. */
|
|
20
|
+
export declare function components(): Components;
|
|
21
|
+
interface Components {
|
|
22
|
+
labels: string[];
|
|
23
|
+
paths: string[];
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { Connector } from '@toa.io/core';
|
|
4
|
+
import { INGESTION, NAMESPACE, SERVICE } from '@toa.io/definitions/extensions.chronicles';
|
|
5
|
+
import { Recorder } from './Recorder.js';
|
|
6
|
+
/**
|
|
7
|
+
* The extension's own service: the store provisioned, then a consumer of every event the log is
|
|
8
|
+
* kept of — one group for every replica, so each event is written once — and the components it
|
|
9
|
+
* ships: the queries, the facts, and the client route where the annotation asks for it.
|
|
10
|
+
*/
|
|
11
|
+
export class Scribe extends Connector {
|
|
12
|
+
host;
|
|
13
|
+
settings;
|
|
14
|
+
provider;
|
|
15
|
+
writer;
|
|
16
|
+
// eslint-disable-next-line max-params
|
|
17
|
+
constructor(host, settings, provider, writer) {
|
|
18
|
+
super();
|
|
19
|
+
this.host = host;
|
|
20
|
+
this.settings = settings;
|
|
21
|
+
this.provider = provider;
|
|
22
|
+
this.writer = writer;
|
|
23
|
+
}
|
|
24
|
+
async open() {
|
|
25
|
+
await this.provider.provision();
|
|
26
|
+
const consumed = [
|
|
27
|
+
...this.settings.events.map((label) => [label, 'event']),
|
|
28
|
+
...this.settings.states.map((label) => [label, 'state'])
|
|
29
|
+
];
|
|
30
|
+
for (const [label, kind] of consumed) {
|
|
31
|
+
// an event that is both a row and a state is two consumers, each of a group of its own
|
|
32
|
+
const group = kind === 'state' ? `${GROUP}.states` : GROUP;
|
|
33
|
+
const component = label.slice(0, label.lastIndexOf('.'));
|
|
34
|
+
const identity = this.settings.identities[component] ?? null;
|
|
35
|
+
const recorder = new Recorder(label, kind, identity, this.writer);
|
|
36
|
+
const consumer = await this.host.receive(label, group, recorder);
|
|
37
|
+
this.depends(consumer);
|
|
38
|
+
await consumer.connect();
|
|
39
|
+
}
|
|
40
|
+
const composition = await this.host.composition(paths(this.settings));
|
|
41
|
+
this.depends(composition);
|
|
42
|
+
await composition.connect();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** The components this extension ships, which run inside its own service. */
|
|
46
|
+
export function components() {
|
|
47
|
+
const labels = [];
|
|
48
|
+
const paths = [];
|
|
49
|
+
for (const entry of readdirSync(ROOT, { withFileTypes: true }))
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
labels.push(entry.name.replace('.', '-'));
|
|
52
|
+
paths.push(resolve(ROOT, entry.name));
|
|
53
|
+
}
|
|
54
|
+
return { labels, paths };
|
|
55
|
+
}
|
|
56
|
+
function paths(settings) {
|
|
57
|
+
return components().paths.filter((path) => settings.ingest || !path.endsWith(`${NAMESPACE}.${INGESTION}`));
|
|
58
|
+
}
|
|
59
|
+
const ROOT = resolve(import.meta.dirname, '../components/');
|
|
60
|
+
const GROUP = `${NAMESPACE}.${SERVICE}`;
|
|
61
|
+
//# sourceMappingURL=Scribe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scribe.js","sourceRoot":"","sources":["../source/Scribe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Connector, Locator } from '@toa.io/core';
|
|
2
|
+
import type { extensions } from '@toa.io/core/types';
|
|
3
|
+
/**
|
|
4
|
+
* `context.chronicles.send(name, payload, { identity })` — a fact that changes no state, written under
|
|
5
|
+
* `<component>.<name>`. It is a task to the component that writes facts, so it resolves once the
|
|
6
|
+
* broker has it, and is kept until that component takes it.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Sender extends Connector {
|
|
9
|
+
readonly name = "chronicles";
|
|
10
|
+
private readonly host;
|
|
11
|
+
private readonly locator;
|
|
12
|
+
private remote?;
|
|
13
|
+
constructor(host: extensions.Host, locator: Locator);
|
|
14
|
+
invoke(method: string, ...args: any[]): Promise<void>;
|
|
15
|
+
private send;
|
|
16
|
+
/**
|
|
17
|
+
* Resolved on the first fact and not before: an aspect is made ahead of the components of its
|
|
18
|
+
* composition, and one that could not be found is looked for again by the next fact.
|
|
19
|
+
*/
|
|
20
|
+
private facts;
|
|
21
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { current, encode } from 'openspan';
|
|
2
|
+
import { Connector, Locator, entities, exceptions } from '@toa.io/core';
|
|
3
|
+
import { FACTS, NAMESPACE } from '@toa.io/definitions/extensions.chronicles';
|
|
4
|
+
/**
|
|
5
|
+
* `context.chronicles.send(name, payload, { identity })` — a fact that changes no state, written under
|
|
6
|
+
* `<component>.<name>`. It is a task to the component that writes facts, so it resolves once the
|
|
7
|
+
* broker has it, and is kept until that component takes it.
|
|
8
|
+
*/
|
|
9
|
+
export class Sender extends Connector {
|
|
10
|
+
name = 'chronicles';
|
|
11
|
+
host;
|
|
12
|
+
locator;
|
|
13
|
+
remote;
|
|
14
|
+
constructor(host, locator) {
|
|
15
|
+
super();
|
|
16
|
+
this.host = host;
|
|
17
|
+
this.locator = locator;
|
|
18
|
+
}
|
|
19
|
+
// named rather than forwarded, so that the connector's lifecycle is not reachable from an
|
|
20
|
+
// algorithm
|
|
21
|
+
async invoke(method, ...args) {
|
|
22
|
+
if (method !== 'send')
|
|
23
|
+
throw new exceptions.MisuseException(`Chronicles have no '${method}'`);
|
|
24
|
+
await this.send(args[0], args[1], args[2]);
|
|
25
|
+
}
|
|
26
|
+
async send(name, payload, options) {
|
|
27
|
+
if (typeof name !== 'string' || !NAME.test(name))
|
|
28
|
+
throw new exceptions.MisuseException(`A chronicle is named by segments of letters and digits separated by dots, not '${String(name)}'`);
|
|
29
|
+
if (payload !== undefined &&
|
|
30
|
+
(typeof payload !== 'object' || payload === null || Array.isArray(payload)))
|
|
31
|
+
throw new exceptions.MisuseException('The payload of a chronicle is an object');
|
|
32
|
+
const context = current();
|
|
33
|
+
const row = {
|
|
34
|
+
id: entities.newid(),
|
|
35
|
+
label: `${this.locator.id}.${name}`,
|
|
36
|
+
component: this.locator.id,
|
|
37
|
+
time: Date.now(),
|
|
38
|
+
entity: null,
|
|
39
|
+
version: null,
|
|
40
|
+
identity: typeof options?.identity === 'string' ? options.identity : null,
|
|
41
|
+
ip: null,
|
|
42
|
+
country: null,
|
|
43
|
+
trace: context === undefined ? null : encode(context),
|
|
44
|
+
payload: payload ?? {}
|
|
45
|
+
};
|
|
46
|
+
const remote = await this.facts();
|
|
47
|
+
await remote.invoke('record', { input: row, task: true });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolved on the first fact and not before: an aspect is made ahead of the components of its
|
|
51
|
+
* composition, and one that could not be found is looked for again by the next fact.
|
|
52
|
+
*/
|
|
53
|
+
async facts() {
|
|
54
|
+
this.remote ??= (async () => {
|
|
55
|
+
const remote = await this.host.remote(LOCATOR, SOURCE);
|
|
56
|
+
this.depends(remote);
|
|
57
|
+
await remote.connect();
|
|
58
|
+
return remote;
|
|
59
|
+
})().catch((error) => {
|
|
60
|
+
this.remote = undefined;
|
|
61
|
+
throw error;
|
|
62
|
+
});
|
|
63
|
+
return await this.remote;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const LOCATOR = new Locator(FACTS, NAMESPACE);
|
|
67
|
+
const SOURCE = { service: 'chronicles' };
|
|
68
|
+
const NAME = /^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/;
|
|
69
|
+
//# sourceMappingURL=Sender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sender.js","sourceRoot":"","sources":["../source/Sender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Provider, Row, State } from './Provider.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Rows to the provider in batches: what arrives within a moment, or until a batch is full, is
|
|
4
|
+
* one write. Each caller is answered when the write its rows went in is, so a delivery is
|
|
5
|
+
* acknowledged once what it carried is kept, and is delivered again where it was not.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Writer {
|
|
8
|
+
private readonly provider;
|
|
9
|
+
private pending;
|
|
10
|
+
constructor(provider: Provider);
|
|
11
|
+
write(rows: Row[], states?: State[]): Promise<void>;
|
|
12
|
+
private open;
|
|
13
|
+
private flush;
|
|
14
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { console } from 'openspan';
|
|
2
|
+
/**
|
|
3
|
+
* Rows to the provider in batches: what arrives within a moment, or until a batch is full, is
|
|
4
|
+
* one write. Each caller is answered when the write its rows went in is, so a delivery is
|
|
5
|
+
* acknowledged once what it carried is kept, and is delivered again where it was not.
|
|
6
|
+
*/
|
|
7
|
+
export class Writer {
|
|
8
|
+
provider;
|
|
9
|
+
pending = null;
|
|
10
|
+
constructor(provider) {
|
|
11
|
+
this.provider = provider;
|
|
12
|
+
}
|
|
13
|
+
async write(rows, states = []) {
|
|
14
|
+
const batch = (this.pending ??= this.open());
|
|
15
|
+
batch.rows.push(...rows);
|
|
16
|
+
batch.states.push(...states);
|
|
17
|
+
if (batch.rows.length + batch.states.length >= SIZE)
|
|
18
|
+
this.flush(batch);
|
|
19
|
+
await batch.written;
|
|
20
|
+
}
|
|
21
|
+
open() {
|
|
22
|
+
let settle;
|
|
23
|
+
const written = new Promise((resolve, reject) => {
|
|
24
|
+
settle = (error) => (error === undefined ? resolve() : reject(error));
|
|
25
|
+
});
|
|
26
|
+
const batch = { rows: [], states: [], written, settle, timer: null };
|
|
27
|
+
batch.timer = setTimeout(() => this.flush(batch), WAIT);
|
|
28
|
+
return batch;
|
|
29
|
+
}
|
|
30
|
+
flush(batch) {
|
|
31
|
+
if (this.pending === batch)
|
|
32
|
+
this.pending = null;
|
|
33
|
+
if (batch.timer === null)
|
|
34
|
+
return;
|
|
35
|
+
clearTimeout(batch.timer);
|
|
36
|
+
batch.timer = null;
|
|
37
|
+
this.provider.write(batch.rows, batch.states).then(() => batch.settle(), (error) => {
|
|
38
|
+
console.warn('Chronicles failed to write', {
|
|
39
|
+
rows: batch.rows.length,
|
|
40
|
+
states: batch.states.length,
|
|
41
|
+
error: error instanceof Error ? error.message : String(error)
|
|
42
|
+
});
|
|
43
|
+
batch.settle(error);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** rows that make a batch full, and milliseconds a batch waits for more */
|
|
48
|
+
const SIZE = 1000;
|
|
49
|
+
const WAIT = 500;
|
|
50
|
+
//# sourceMappingURL=Writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Writer.js","sourceRoot":"","sources":["../source/Writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAGlC;;;;GAIG;AACH,MAAM,OAAO,MAAM;IACA,QAAQ,CAAU;IAC3B,OAAO,GAAiB,IAAI,CAAA;IAEpC,YAAmB,QAAkB;QACnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,IAAW,EAAE,MAAM,GAAY,EAAE;QAClD,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;QAE5C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;QACxB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAA;QAE5B,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAEtE,MAAM,KAAK,CAAC,OAAO,CAAA;IACrB,CAAC;IAEO,IAAI;QACV,IAAI,MAAkC,CAAA;QAEtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpD,MAAM,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;QACvE,CAAC,CAAC,CAAA;QAEF,MAAM,KAAK,GAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAE3E,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;QAEvD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,KAAK,CAAC,KAAY;QACxB,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK;YAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QAE/C,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI;YAAE,OAAM;QAEhC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACzB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAA;QAElB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAChD,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,EACpB,CAAC,KAAc,EAAE,EAAE;YACjB,OAAO,CAAC,IAAI,CAAC,4BAA4B,EAAE;gBACzC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;gBACvB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;gBAC3B,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAA;YAEF,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC,CACF,CAAA;IACH,CAAC;CACF;AAUD,2EAA2E;AAC3E,MAAM,IAAI,GAAG,IAAI,CAAA;AACjB,MAAM,IAAI,GAAG,GAAG,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Address } from '@toa.io/definitions/extensions.chronicles';
|
|
2
|
+
/**
|
|
3
|
+
* How much of a browser's address the log keeps. An address is personal data, and the log erases
|
|
4
|
+
* nothing, so by default it keeps the network the address is in — a /24 of IPv4, a /48 of IPv6 —
|
|
5
|
+
* which is as much as a question of where a client is asks for. What is not an address is not kept.
|
|
6
|
+
*/
|
|
7
|
+
export declare function keep(address: unknown, how: Address): string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isIPv4, isIPv6 } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* How much of a browser's address the log keeps. An address is personal data, and the log erases
|
|
4
|
+
* nothing, so by default it keeps the network the address is in — a /24 of IPv4, a /48 of IPv6 —
|
|
5
|
+
* which is as much as a question of where a client is asks for. What is not an address is not kept.
|
|
6
|
+
*/
|
|
7
|
+
export function keep(address, how) {
|
|
8
|
+
if (typeof address !== 'string')
|
|
9
|
+
return null;
|
|
10
|
+
if (isIPv4(address))
|
|
11
|
+
return how === 'full' ? address : address.replace(/\.\d+$/, '.0');
|
|
12
|
+
if (isIPv6(address))
|
|
13
|
+
return how === 'full' ? address : network(address);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
/** The first three groups of an IPv6 address, and none of the rest. */
|
|
17
|
+
function network(address) {
|
|
18
|
+
const [head, tail = ''] = address.split('::');
|
|
19
|
+
const left = head === '' ? [] : head.split(':');
|
|
20
|
+
const right = tail === '' ? [] : tail.split(':');
|
|
21
|
+
const groups = [...left, ...Array(8 - left.length - right.length).fill('0'), ...right];
|
|
22
|
+
return (groups
|
|
23
|
+
.slice(0, 3)
|
|
24
|
+
.map((group) => group.replace(/^0+(?=.)/, ''))
|
|
25
|
+
.join(':') + '::');
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../source/address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGzC;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAAC,OAAgB,EAAE,GAAY;IACjD,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE5C,IAAI,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAEtF,IAAI,MAAM,CAAC,OAAO,CAAC;QAAE,OAAO,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE,OAAO,IAAI,CAAA;AACb,CAAC;AAED,uEAAuE;AACvE,SAAS,OAAO,CAAC,OAAe;IAC9B,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,CAAA;IAEtF,OAAO,CACL,MAAM;SACH,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;SAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CACpB,CAAA;AACH,CAAC"}
|