@toa.io/extensions.chronicles 1.0.0-alpha.332 → 1.0.0-alpha.335

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.
Files changed (82) hide show
  1. package/LICENSE +22 -0
  2. package/components/chronicles.encounters/manifest.toa.yaml +108 -0
  3. package/components/{chronicles.ingestion → chronicles.encounters}/operations/anonymous.js +1 -1
  4. package/components/chronicles.encounters/operations/describe.js +4 -0
  5. package/components/{chronicles.ingestion → chronicles.encounters}/operations/identified.js +1 -1
  6. package/components/{chronicles.ingestion → chronicles.encounters}/types/toa.d.ts +13 -0
  7. package/components/chronicles.queries/manifest.toa.yaml +39 -15
  8. package/components/chronicles.queries/operations/define.js +1 -1
  9. package/components/chronicles.queries/types/toa.d.ts +8 -3
  10. package/components/{chronicles.facts → chronicles.relay}/manifest.toa.yaml +14 -3
  11. package/components/chronicles.relay/operations/encounter.js +4 -0
  12. package/components/{chronicles.facts → chronicles.relay}/operations/record.js +1 -1
  13. package/components/{chronicles.facts → chronicles.relay}/types/toa.d.ts +5 -0
  14. package/guides/clickhouse.md +19 -0
  15. package/guides/log.md +52 -0
  16. package/package.json +8 -4
  17. package/readme.md +459 -142
  18. package/store/clickhouse.scope.sql +29 -0
  19. package/store/clickhouse.sql +99 -0
  20. package/transpiled/Client.d.ts +19 -6
  21. package/transpiled/Client.js +89 -37
  22. package/transpiled/Client.js.map +1 -1
  23. package/transpiled/Factory.d.ts +13 -5
  24. package/transpiled/Factory.js +32 -23
  25. package/transpiled/Factory.js.map +1 -1
  26. package/transpiled/Provider.d.ts +76 -25
  27. package/transpiled/Provider.js.map +1 -1
  28. package/transpiled/Publisher.d.ts +50 -0
  29. package/transpiled/Publisher.js +169 -0
  30. package/transpiled/Publisher.js.map +1 -0
  31. package/transpiled/Reader.d.ts +15 -3
  32. package/transpiled/Reader.js +98 -14
  33. package/transpiled/Reader.js.map +1 -1
  34. package/transpiled/Recorder.d.ts +21 -11
  35. package/transpiled/Recorder.js +49 -24
  36. package/transpiled/Recorder.js.map +1 -1
  37. package/transpiled/Relay.d.ts +14 -0
  38. package/transpiled/Relay.js +27 -0
  39. package/transpiled/Relay.js.map +1 -0
  40. package/transpiled/Sender.d.ts +8 -4
  41. package/transpiled/Sender.js +27 -10
  42. package/transpiled/Sender.js.map +1 -1
  43. package/transpiled/Service.d.ts +26 -0
  44. package/transpiled/Service.js +70 -0
  45. package/transpiled/Service.js.map +1 -0
  46. package/transpiled/agent.d.ts +16 -0
  47. package/transpiled/agent.js +48 -0
  48. package/transpiled/agent.js.map +1 -0
  49. package/transpiled/clickhouse/Client.d.ts +5 -8
  50. package/transpiled/clickhouse/Client.js +12 -14
  51. package/transpiled/clickhouse/Client.js.map +1 -1
  52. package/transpiled/clickhouse/Provider.d.ts +20 -18
  53. package/transpiled/clickhouse/Provider.js +97 -72
  54. package/transpiled/clickhouse/Provider.js.map +1 -1
  55. package/transpiled/clickhouse/ddl.d.ts +3 -20
  56. package/transpiled/clickhouse/ddl.js +6 -79
  57. package/transpiled/clickhouse/ddl.js.map +1 -1
  58. package/transpiled/clickhouse/views.d.ts +11 -0
  59. package/transpiled/clickhouse/views.js +97 -0
  60. package/transpiled/clickhouse/views.js.map +1 -0
  61. package/transpiled/contract.d.ts +12 -0
  62. package/transpiled/contract.js +58 -0
  63. package/transpiled/contract.js.map +1 -0
  64. package/transpiled/index.d.ts +9 -3
  65. package/transpiled/settings.d.ts +6 -4
  66. package/transpiled/settings.js +27 -14
  67. package/transpiled/settings.js.map +1 -1
  68. package/transpiled/store.d.ts +3 -3
  69. package/transpiled/store.js +3 -3
  70. package/transpiled/store.js.map +1 -1
  71. package/components/chronicles.ingestion/manifest.toa.yaml +0 -60
  72. package/transpiled/Facts.d.ts +0 -9
  73. package/transpiled/Facts.js +0 -16
  74. package/transpiled/Facts.js.map +0 -1
  75. package/transpiled/Scribe.d.ts +0 -25
  76. package/transpiled/Scribe.js +0 -61
  77. package/transpiled/Scribe.js.map +0 -1
  78. package/transpiled/Writer.d.ts +0 -14
  79. package/transpiled/Writer.js +0 -50
  80. package/transpiled/Writer.js.map +0 -1
  81. /package/components/{chronicles.facts → chronicles.encounters}/types/index.d.ts +0 -0
  82. /package/components/{chronicles.ingestion → chronicles.relay}/types/index.d.ts +0 -0
@@ -1,28 +1,38 @@
1
1
  import { Connector } from '@toa.io/core';
2
- import type { Writer } from './Writer.ts';
2
+ import type { Publisher } from './Publisher.ts';
3
3
  import type { Row, State } from './Provider.ts';
4
+ import type { Contract } from '@toa.io/definitions/extensions.chronicles';
4
5
  import type { Message } from '@toa.io/core/types';
5
6
  /**
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`.
7
+ * The consumer of one event the log is kept of: a row of `events` for an event a component declares
8
+ * under `events`, and a state for the one it declares its `state`. A message carries what the event
9
+ * rendered, its id and when the change was made, so what is said of the entity — which, its
10
+ * version — is read from the payload, where the entity as it stands carries `id` and `VERSION`;
11
+ * when, from the property the component names, or the message; and what is written of the payload
12
+ * is what the contract declares.
10
13
  */
11
14
  export declare class Recorder extends Connector {
12
15
  private readonly label;
13
16
  private readonly component;
14
17
  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);
18
+ private readonly of;
19
+ private readonly publisher;
20
+ constructor(label: string, kind: Kind, of: Of, publisher: Publisher);
19
21
  receive(message: Message): Promise<void>;
20
22
  }
21
- export declare function row(label: string, component: string, message: Message, identity?: string | null): Row;
23
+ /** What a component declares of the payloads of a label. */
24
+ export interface Of {
25
+ contract: Contract;
26
+ /** the property that names whose a row is; `null` where none does */
27
+ identity: string | null;
28
+ /** the property that says when the change was made; `null` where none does */
29
+ timestamp: string | null;
30
+ }
31
+ export declare function row(label: string, component: string, message: Message, of: Of): Row;
22
32
  /**
23
33
  * The entity as its state event renders it. One that does not say which entity it is, and which
24
34
  * version, cannot be kept, and no delivery of it will say otherwise: refused as a contract the
25
35
  * component broke, so it is parked on its first delivery rather than retried.
26
36
  */
27
- export declare function state(label: string, component: string, message: Message, identity?: string | null): State;
37
+ export declare function state(label: string, component: string, message: Message, of: Of): State;
28
38
  export type Kind = 'event' | 'state';
@@ -1,50 +1,50 @@
1
1
  import { Connector, entities, exceptions } from '@toa.io/core';
2
+ import { Mismatch, project } from './contract.js';
2
3
  /**
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`.
4
+ * The consumer of one event the log is kept of: a row of `events` for an event a component declares
5
+ * under `events`, and a state for the one it declares its `state`. A message carries what the event
6
+ * rendered, its id and when the change was made, so what is said of the entity — which, its
7
+ * version — is read from the payload, where the entity as it stands carries `id` and `VERSION`;
8
+ * when, from the property the component names, or the message; and what is written of the payload
9
+ * is what the contract declares.
7
10
  */
8
11
  export class Recorder extends Connector {
9
12
  label;
10
13
  component;
11
14
  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
+ of;
16
+ publisher;
15
17
  // eslint-disable-next-line max-params
16
- constructor(label, kind, identity, writer) {
18
+ constructor(label, kind, of, publisher) {
17
19
  super();
18
20
  this.label = label;
19
21
  this.component = label.slice(0, label.lastIndexOf('.'));
20
22
  this.kind = kind;
21
- this.identity = identity;
22
- this.writer = writer;
23
+ this.of = of;
24
+ this.publisher = publisher;
23
25
  }
24
26
  async receive(message) {
25
27
  if (this.kind === 'state')
26
- await this.writer.write([], [state(this.label, this.component, message, this.identity)]);
28
+ await this.publisher.publish([], [state(this.label, this.component, message, this.of)]);
27
29
  else
28
- await this.writer.write([row(this.label, this.component, message, this.identity)]);
30
+ await this.publisher.publish([row(this.label, this.component, message, this.of)]);
29
31
  }
30
32
  }
31
33
  // eslint-disable-next-line max-params
32
- export function row(label, component, message, identity = null) {
34
+ export function row(label, component, message, of) {
33
35
  const payload = object(message.payload);
34
- const { id, VERSION, UPDATED } = payload;
36
+ const { id, VERSION } = payload;
35
37
  return {
36
38
  // a message from outside Toa carries no id, and is then the same message by what it says
37
39
  id: message.id ?? entities.derive(label, JSON.stringify(payload)),
38
40
  label,
39
41
  component,
40
- time: typeof UPDATED === 'number' ? UPDATED : Date.now(),
42
+ time: when(payload, message, of.timestamp),
41
43
  entity: typeof id === 'string' ? id : null,
42
44
  version: typeof VERSION === 'number' ? VERSION : null,
43
- identity: whose(payload, identity),
44
- ip: null,
45
- country: null,
45
+ identity: whose(payload, of.identity),
46
46
  trace: message.telemetry ?? null,
47
- payload
47
+ payload: declared(label, payload, of.contract)
48
48
  };
49
49
  }
50
50
  /**
@@ -53,20 +53,45 @@ export function row(label, component, message, identity = null) {
53
53
  * component broke, so it is parked on its first delivery rather than retried.
54
54
  */
55
55
  // eslint-disable-next-line max-params
56
- export function state(label, component, message, identity = null) {
56
+ export function state(label, component, message, of) {
57
57
  const payload = object(message.payload);
58
- const { id, VERSION, UPDATED } = payload;
58
+ const { id, VERSION } = payload;
59
59
  if (typeof id !== 'string' || typeof VERSION !== 'number')
60
60
  throw new exceptions.EntityContractException(`The state '${label}' is an entity as it stands, and its payload carries no 'id' and 'VERSION'`);
61
61
  return {
62
62
  component,
63
63
  entity: id,
64
64
  version: VERSION,
65
- identity: whose(payload, identity),
66
- time: typeof UPDATED === 'number' ? UPDATED : Date.now(),
67
- payload
65
+ identity: whose(payload, of.identity),
66
+ time: when(payload, message, of.timestamp),
67
+ payload: declared(label, payload, of.contract)
68
68
  };
69
69
  }
70
+ /**
71
+ * When the change was made: what the payload says in the property the component names, or else
72
+ * what the message says, which the process that made the change stamped as it made it. A message
73
+ * of a process from before messages said so says nothing, and is when it is taken.
74
+ */
75
+ function when(payload, message, property) {
76
+ const said = property === null ? undefined : payload[property];
77
+ if (typeof said === 'number')
78
+ return said;
79
+ return message.time ?? Date.now();
80
+ }
81
+ /**
82
+ * What the contract declares of a payload. One it does not admit is a contract the component broke,
83
+ * which no delivery will mend: refused, so it is parked on its first delivery.
84
+ */
85
+ function declared(label, payload, contract) {
86
+ try {
87
+ return project(payload, contract.properties);
88
+ }
89
+ catch (exception) {
90
+ if (exception instanceof Mismatch)
91
+ throw new exceptions.EntityContractException(`The payload of '${label}' is not what its chronicles declare: ${exception.message}`);
92
+ throw exception;
93
+ }
94
+ }
70
95
  /** The identity a payload names in the property the component declares, where it is one. */
71
96
  function whose(payload, property) {
72
97
  if (property === null)
@@ -1 +1 @@
1
- {"version":3,"file":"Recorder.js","sourceRoot":"","sources":["../source/Recorder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Recorder.js","sourceRoot":"","sources":["../source/Recorder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAMjD;;;;;;;GAOG;AACH,MAAM,OAAO,QAAS,SAAQ,SAAS;IACpB,KAAK,CAAQ;IACb,SAAS,CAAQ;IACjB,IAAI,CAAM;IACV,EAAE,CAAI;IACN,SAAS,CAAW;IAErC,sCAAsC;IACtC,YAAmB,KAAa,EAAE,IAAU,EAAE,EAAM,EAAE,SAAoB;QACxE,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAA;QACvD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;QACZ,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;IAC5B,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,OAAgB;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YACvB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAC1B,EAAE,EACF,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CACtD,CAAA;;YACE,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACxF,CAAC;CACF;AAaD,sCAAsC;AACtC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAgB,EAAE,EAAM;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAkC,CAAA;IAE1D,OAAO;QACL,yFAAyF;QACzF,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACjE,KAAK;QACL,SAAS;QACT,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC;QAC1C,MAAM,EAAE,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1C,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;QACrD,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC;QACrC,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;QAChC,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC;KAC/C,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,sCAAsC;AACtC,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAgB,EAAE,EAAM;IAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAkC,CAAA;IAE1D,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,QAAQ;QACvD,MAAM,IAAI,UAAU,CAAC,uBAAuB,CAC1C,cAAc,KAAK,4EAA4E,CAChG,CAAA;IAEH,OAAO;QACL,SAAS;QACT,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC;QACrC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC;QAC1C,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC;KAC/C,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,IAAI,CAAC,OAAe,EAAE,OAAgB,EAAE,QAAuB;IACtE,MAAM,IAAI,GACR,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,OAAmC,CAAC,QAAQ,CAAC,CAAA;IAEhF,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAEzC,OAAO,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,QAAQ,CAAC,KAAa,EAAE,OAAe,EAAE,QAAkB;IAClE,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAA;IAC9C,CAAC;IAAC,OAAO,SAAS,EAAE,CAAC;QACnB,IAAI,SAAS,YAAY,QAAQ;YAC/B,MAAM,IAAI,UAAU,CAAC,uBAAuB,CAC1C,mBAAmB,KAAK,yCAAyC,SAAS,CAAC,OAAO,EAAE,CACrF,CAAA;QAEH,MAAM,SAAS,CAAA;IACjB,CAAC;AACH,CAAC;AAID,4FAA4F;AAC5F,SAAS,KAAK,CAAC,OAAe,EAAE,QAAuB;IACrD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAElC,MAAM,KAAK,GAAI,OAAmC,CAAC,QAAQ,CAAC,CAAA;IAE5D,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AACjD,CAAC;AAED,SAAS,MAAM,CAAC,OAAgB;IAC9B,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAC/E,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;AACxB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Connector } from '@toa.io/core';
2
+ import type { Publisher } from './Publisher.ts';
3
+ /**
4
+ * What `chronicles.relay` publishes the tasks it is given through: a fact a component sent, and the
5
+ * encounters of a batch a client sent. A task is done once the broker has confirmed its rows. The
6
+ * publisher is the relay's own, closed with it: the component may be composed by another factory
7
+ * than the one of the service it runs in.
8
+ */
9
+ export declare class Relay extends Connector {
10
+ readonly name = "chronicles";
11
+ private readonly publisher;
12
+ constructor(publisher: Publisher);
13
+ invoke(method: string, ...args: any[]): Promise<void>;
14
+ }
@@ -0,0 +1,27 @@
1
+ import { Connector, exceptions } from '@toa.io/core';
2
+ /**
3
+ * What `chronicles.relay` publishes the tasks it is given through: a fact a component sent, and the
4
+ * encounters of a batch a client sent. A task is done once the broker has confirmed its rows. The
5
+ * publisher is the relay's own, closed with it: the component may be composed by another factory
6
+ * than the one of the service it runs in.
7
+ */
8
+ export class Relay extends Connector {
9
+ name = 'chronicles';
10
+ publisher;
11
+ constructor(publisher) {
12
+ super();
13
+ this.publisher = publisher;
14
+ this.depends(publisher);
15
+ }
16
+ async invoke(method, ...args) {
17
+ switch (method) {
18
+ case 'record':
19
+ return await this.publisher.publish([args[0]]);
20
+ case 'encounter':
21
+ return await this.publisher.publish([], [], args[0]);
22
+ default:
23
+ throw new exceptions.MisuseException(`Chronicles have no '${method}'`);
24
+ }
25
+ }
26
+ }
27
+ //# sourceMappingURL=Relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Relay.js","sourceRoot":"","sources":["../source/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAIpD;;;;;GAKG;AACH,MAAM,OAAO,KAAM,SAAQ,SAAS;IAClB,IAAI,GAAG,YAAY,CAAA;IAElB,SAAS,CAAW;IAErC,YAAmB,SAAoB;QACrC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACzB,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,GAAG,IAAW;QAChD,QAAQ,MAAM,EAAE,CAAC;YACf,KAAK,QAAQ;gBACX,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAQ,CAAC,CAAC,CAAA;YACvD,KAAK,WAAW;gBACd,OAAO,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAgB,CAAC,CAAA;YACrE;gBACE,MAAM,IAAI,UAAU,CAAC,eAAe,CAAC,uBAAuB,MAAM,GAAG,CAAC,CAAA;QAC1E,CAAC;IACH,CAAC;CACF"}
@@ -1,21 +1,25 @@
1
1
  import { Connector, Locator } from '@toa.io/core';
2
+ import type { Contract } from '@toa.io/definitions/extensions.chronicles';
2
3
  import type { extensions } from '@toa.io/core/types';
3
4
  /**
4
5
  * `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.
6
+ * `<component>.<name>`, of which what the component declares of it is sent. It is a task to the
7
+ * component that writes facts, so it resolves once the broker has it, and is kept until that
8
+ * component takes it.
7
9
  */
8
10
  export declare class Sender extends Connector {
9
11
  readonly name = "chronicles";
10
12
  private readonly host;
11
13
  private readonly locator;
14
+ private readonly facts;
12
15
  private remote?;
13
- constructor(host: extensions.Host, locator: Locator);
16
+ /** `facts` are the contracts of the facts the component declares, by name */
17
+ constructor(host: extensions.Host, locator: Locator, facts: Record<string, Contract>);
14
18
  invoke(method: string, ...args: any[]): Promise<void>;
15
19
  private send;
16
20
  /**
17
21
  * Resolved on the first fact and not before: an aspect is made ahead of the components of its
18
22
  * composition, and one that could not be found is looked for again by the next fact.
19
23
  */
20
- private facts;
24
+ private recorder;
21
25
  }
@@ -1,20 +1,25 @@
1
1
  import { current, encode } from 'openspan';
2
2
  import { Connector, Locator, entities, exceptions } from '@toa.io/core';
3
- import { FACTS, NAMESPACE } from '@toa.io/definitions/extensions.chronicles';
3
+ import { NAMESPACE, RELAY } from '@toa.io/definitions/extensions.chronicles';
4
+ import { Mismatch, project } from './contract.js';
4
5
  /**
5
6
  * `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.
7
+ * `<component>.<name>`, of which what the component declares of it is sent. It is a task to the
8
+ * component that writes facts, so it resolves once the broker has it, and is kept until that
9
+ * component takes it.
8
10
  */
9
11
  export class Sender extends Connector {
10
12
  name = 'chronicles';
11
13
  host;
12
14
  locator;
15
+ facts;
13
16
  remote;
14
- constructor(host, locator) {
17
+ /** `facts` are the contracts of the facts the component declares, by name */
18
+ constructor(host, locator, facts) {
15
19
  super();
16
20
  this.host = host;
17
21
  this.locator = locator;
22
+ this.facts = facts;
18
23
  }
19
24
  // named rather than forwarded, so that the connector's lifecycle is not reachable from an
20
25
  // algorithm
@@ -29,6 +34,9 @@ export class Sender extends Connector {
29
34
  if (payload !== undefined &&
30
35
  (typeof payload !== 'object' || payload === null || Array.isArray(payload)))
31
36
  throw new exceptions.MisuseException('The payload of a chronicle is an object');
37
+ const contract = this.facts[name];
38
+ if (contract === undefined)
39
+ throw new exceptions.MisuseException(`'${this.locator.id}' declares no fact '${name}'`);
32
40
  const context = current();
33
41
  const row = {
34
42
  id: entities.newid(),
@@ -38,19 +46,17 @@ export class Sender extends Connector {
38
46
  entity: null,
39
47
  version: null,
40
48
  identity: typeof options?.identity === 'string' ? options.identity : null,
41
- ip: null,
42
- country: null,
43
49
  trace: context === undefined ? null : encode(context),
44
- payload: payload ?? {}
50
+ payload: declared(name, payload ?? {}, contract)
45
51
  };
46
- const remote = await this.facts();
52
+ const remote = await this.recorder();
47
53
  await remote.invoke('record', { input: row, task: true });
48
54
  }
49
55
  /**
50
56
  * Resolved on the first fact and not before: an aspect is made ahead of the components of its
51
57
  * composition, and one that could not be found is looked for again by the next fact.
52
58
  */
53
- async facts() {
59
+ async recorder() {
54
60
  this.remote ??= (async () => {
55
61
  const remote = await this.host.remote(LOCATOR, SOURCE);
56
62
  this.depends(remote);
@@ -63,7 +69,18 @@ export class Sender extends Connector {
63
69
  return await this.remote;
64
70
  }
65
71
  }
66
- const LOCATOR = new Locator(FACTS, NAMESPACE);
72
+ /** What the contract declares of the payload of a fact; one it does not admit is not sent. */
73
+ function declared(name, payload, contract) {
74
+ try {
75
+ return project(payload, contract.properties);
76
+ }
77
+ catch (exception) {
78
+ if (exception instanceof Mismatch)
79
+ throw new exceptions.MisuseException(`The fact '${name}' is not what its chronicles declare: ${exception.message}`);
80
+ throw exception;
81
+ }
82
+ }
83
+ const LOCATOR = new Locator(RELAY, NAMESPACE);
67
84
  const SOURCE = { service: 'chronicles' };
68
85
  const NAME = /^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)*$/;
69
86
  //# sourceMappingURL=Sender.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sender.js","sourceRoot":"","sources":["../source/Sender.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Sender.js","sourceRoot":"","sources":["../source/Sender.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Connector } from '@toa.io/core';
2
+ import type { Publisher } from './Publisher.ts';
3
+ import type { Settings } from '@toa.io/definitions/extensions.chronicles';
4
+ import type { extensions } from '@toa.io/core/types';
5
+ /**
6
+ * The extension's own service: a consumer of every event the log is kept of — one group for every
7
+ * replica, so each event is published once — and the components it ships: the queries, the relay,
8
+ * and the encounters where the annotation asks for them. Nothing of it waits for the broker the
9
+ * rows are published to.
10
+ */
11
+ export declare class Service extends Connector {
12
+ private readonly host;
13
+ private readonly settings;
14
+ private readonly publisher;
15
+ constructor(host: extensions.Host, settings: Settings, publisher: Publisher);
16
+ /** first of all, so what waits for the broker of the log holds up none of what closes after */
17
+ protected close(): Promise<void>;
18
+ protected open(): Promise<void>;
19
+ }
20
+ /** The components this extension ships, which run inside its own service. */
21
+ export declare function components(): Components;
22
+ interface Components {
23
+ labels: string[];
24
+ paths: string[];
25
+ }
26
+ export {};
@@ -0,0 +1,70 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { Connector } from '@toa.io/core';
4
+ import { ENCOUNTERS, NAMESPACE, SERVICE } from '@toa.io/definitions/extensions.chronicles';
5
+ import { Recorder } from './Recorder.js';
6
+ /**
7
+ * The extension's own service: a consumer of every event the log is kept of — one group for every
8
+ * replica, so each event is published once — and the components it ships: the queries, the relay,
9
+ * and the encounters where the annotation asks for them. Nothing of it waits for the broker the
10
+ * rows are published to.
11
+ */
12
+ export class Service extends Connector {
13
+ host;
14
+ settings;
15
+ publisher;
16
+ // eslint-disable-next-line max-params
17
+ constructor(host, settings, publisher) {
18
+ super();
19
+ this.host = host;
20
+ this.settings = settings;
21
+ this.publisher = publisher;
22
+ this.depends(publisher);
23
+ }
24
+ /** first of all, so what waits for the broker of the log holds up none of what closes after */
25
+ async close() {
26
+ this.publisher.stop();
27
+ }
28
+ async open() {
29
+ const consumed = [
30
+ ...Object.entries(this.settings.events).map(([label, contract]) => [label, 'event', contract]),
31
+ ...Object.entries(this.settings.states).map(([label, contract]) => [label, 'state', contract])
32
+ ];
33
+ for (const [label, kind, contract] of consumed) {
34
+ // an event that is both a row and a state is two consumers, each of a group of its own
35
+ const group = kind === 'state' ? `${GROUP}.states` : GROUP;
36
+ const of = {
37
+ contract,
38
+ identity: this.settings.identities[component(label)] ?? null,
39
+ timestamp: this.settings.timestamps[component(label)] ?? null
40
+ };
41
+ const recorder = new Recorder(label, kind, of, this.publisher);
42
+ const consumer = await this.host.receive(label, group, recorder);
43
+ this.depends(consumer);
44
+ await consumer.connect();
45
+ }
46
+ const composition = await this.host.composition(paths(this.settings));
47
+ this.depends(composition);
48
+ await composition.connect();
49
+ }
50
+ }
51
+ /** The components this extension ships, which run inside its own service. */
52
+ export function components() {
53
+ const labels = [];
54
+ const paths = [];
55
+ for (const entry of readdirSync(ROOT, { withFileTypes: true }))
56
+ if (entry.isDirectory()) {
57
+ labels.push(entry.name.replace('.', '-'));
58
+ paths.push(resolve(ROOT, entry.name));
59
+ }
60
+ return { labels, paths };
61
+ }
62
+ function component(label) {
63
+ return label.slice(0, label.lastIndexOf('.'));
64
+ }
65
+ function paths(settings) {
66
+ return components().paths.filter((path) => settings.encounters || !path.endsWith(`${NAMESPACE}.${ENCOUNTERS}`));
67
+ }
68
+ const ROOT = resolve(import.meta.dirname, '../components/');
69
+ const GROUP = `${NAMESPACE}.${SERVICE}`;
70
+ //# sourceMappingURL=Service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Service.js","sourceRoot":"","sources":["../source/Service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What a client is, as its `user-agent` says, in the few words a question of who visits asks for:
3
+ * the browser, the operating system and the kind of device, by name and without versions — which
4
+ * browsers freeze or leave out, and which make an agent more of a fingerprint. Each is the first of
5
+ * its list that matches, so a name another agent copies comes after the one that copies it: Edge
6
+ * says Chrome and Safari, Chrome says Safari. What matches nothing is `null`.
7
+ *
8
+ * Safari on an iPad says it is a Mac, as it has since iPadOS 13, and nothing the request carries
9
+ * says otherwise: it is a desktop running macOS here.
10
+ */
11
+ export declare function classify(agent: unknown): Classified;
12
+ export interface Classified {
13
+ browser: string | null;
14
+ os: string | null;
15
+ device: string | null;
16
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * What a client is, as its `user-agent` says, in the few words a question of who visits asks for:
3
+ * the browser, the operating system and the kind of device, by name and without versions — which
4
+ * browsers freeze or leave out, and which make an agent more of a fingerprint. Each is the first of
5
+ * its list that matches, so a name another agent copies comes after the one that copies it: Edge
6
+ * says Chrome and Safari, Chrome says Safari. What matches nothing is `null`.
7
+ *
8
+ * Safari on an iPad says it is a Mac, as it has since iPadOS 13, and nothing the request carries
9
+ * says otherwise: it is a desktop running macOS here.
10
+ */
11
+ export function classify(agent) {
12
+ if (typeof agent !== 'string' || agent === '')
13
+ return { browser: null, os: null, device: null };
14
+ return {
15
+ browser: first(BROWSERS, agent),
16
+ os: first(SYSTEMS, agent),
17
+ device: first(DEVICES, agent) ?? 'desktop'
18
+ };
19
+ }
20
+ function first(list, agent) {
21
+ return list.find(([, pattern]) => pattern.test(agent))?.[0] ?? null;
22
+ }
23
+ const BROWSERS = [
24
+ ['Edge', /\bEdg(e|A|iOS)?\//],
25
+ ['Opera', /\bOPR\/|\bOPT\/|\bOpera\b/],
26
+ ['Samsung Internet', /\bSamsungBrowser\//],
27
+ ['Firefox', /\bFirefox\/|\bFxiOS\//],
28
+ ['Chrome', /(\b|Headless)Chrome\/|\bCriOS\//],
29
+ ['Safari', /\bVersion\/[\d.]+.*\bSafari\//]
30
+ ];
31
+ const SYSTEMS = [
32
+ ['Windows', /\bWindows\b/],
33
+ ['iOS', /\b(iPhone|iPad|iPod)\b/],
34
+ ['macOS', /\bMac OS X\b|\bMacintosh\b/],
35
+ ['Android', /\bAndroid\b/],
36
+ ['ChromeOS', /\bCrOS\b/],
37
+ ['Linux', /\bLinux\b/]
38
+ ];
39
+ // a desktop is what is none of these, so it is not listed
40
+ const DEVICES = [
41
+ [
42
+ 'bot',
43
+ /bot\b|crawl|spider|slurp|preview|headless|^curl\/|^wget\/|^python-|^go-http|^okhttp/i
44
+ ],
45
+ ['tablet', /\biPad\b|\bTablet\b|\bAndroid\b(?!.*\bMobile\b)/],
46
+ ['mobile', /\bMobile\b|\biPhone\b|\biPod\b/]
47
+ ];
48
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../source/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;QAC3C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IAElD,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC;QAC/B,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC;QACzB,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,SAAS;KAC3C,CAAA;AACH,CAAC;AAQD,SAAS,KAAK,CAAC,IAA6B,EAAE,KAAa;IACzD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AACrE,CAAC;AAED,MAAM,QAAQ,GAA4B;IACxC,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAC7B,CAAC,OAAO,EAAE,2BAA2B,CAAC;IACtC,CAAC,kBAAkB,EAAE,oBAAoB,CAAC;IAC1C,CAAC,SAAS,EAAE,uBAAuB,CAAC;IACpC,CAAC,QAAQ,EAAE,iCAAiC,CAAC;IAC7C,CAAC,QAAQ,EAAE,+BAA+B,CAAC;CAC5C,CAAA;AAED,MAAM,OAAO,GAA4B;IACvC,CAAC,SAAS,EAAE,aAAa,CAAC;IAC1B,CAAC,KAAK,EAAE,wBAAwB,CAAC;IACjC,CAAC,OAAO,EAAE,4BAA4B,CAAC;IACvC,CAAC,SAAS,EAAE,aAAa,CAAC;IAC1B,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,OAAO,EAAE,WAAW,CAAC;CACvB,CAAA;AAED,0DAA0D;AAC1D,MAAM,OAAO,GAA4B;IACvC;QACE,KAAK;QACL,sFAAsF;KACvF;IACD,CAAC,QAAQ,EAAE,iDAAiD,CAAC;IAC7D,CAAC,QAAQ,EAAE,gCAAgC,CAAC;CAC7C,CAAA"}
@@ -3,22 +3,19 @@
3
3
  * needs of it is a statement and its answer.
4
4
  */
5
5
  export declare class ClickHouse {
6
- readonly database: string;
7
6
  private readonly endpoint;
8
7
  private readonly headers;
8
+ /** The statements run in the user's default database, which is what the store gives it to read. */
9
9
  constructor(address: string, user: string, key: string);
10
- /**
11
- * A statement that answers nothing worth reading. `database` is the one it runs in, where that
12
- * is not this client's: `''` for a statement that must run before the database exists.
13
- */
14
- run(sql: string, settings?: Record<string, string>, database?: string): Promise<void>;
10
+ /** A statement that answers nothing worth reading. */
11
+ run(sql: string, settings?: Record<string, string>): Promise<void>;
15
12
  /**
16
13
  * A query, answered as `JSONCompact`: the columns, and the rows as arrays. The format is the
17
14
  * default rather than a clause, which a statement like `EXPLAIN` would take as its own.
18
15
  */
19
16
  select(sql: string, settings?: Record<string, string>): Promise<Answer>;
20
- /** Each row as an object, for what this extension reads of the database itself. */
21
- rows<T = Record<string, unknown>>(sql: string): Promise<T[]>;
17
+ /** Each row as an object, for what this extension reads of the store itself. */
18
+ rows<T = Record<string, unknown>>(sql: string, settings?: Record<string, string>): Promise<T[]>;
22
19
  private request;
23
20
  }
24
21
  export interface Answer {
@@ -3,21 +3,17 @@
3
3
  * needs of it is a statement and its answer.
4
4
  */
5
5
  export class ClickHouse {
6
- database;
7
6
  endpoint;
8
7
  headers;
8
+ /** The statements run in the user's default database, which is what the store gives it to read. */
9
9
  constructor(address, user, key) {
10
10
  const url = new URL(address);
11
- this.database = decodeURIComponent(url.pathname.replace(/^\/+|\/+$/g, ''));
12
11
  this.endpoint = `${url.protocol}//${url.host}/`;
13
12
  this.headers = { 'x-clickhouse-user': user, 'x-clickhouse-key': key };
14
13
  }
15
- /**
16
- * A statement that answers nothing worth reading. `database` is the one it runs in, where that
17
- * is not this client's: `''` for a statement that must run before the database exists.
18
- */
19
- async run(sql, settings = {}, database = this.database) {
20
- await this.request(sql, settings, database);
14
+ /** A statement that answers nothing worth reading. */
15
+ async run(sql, settings = {}) {
16
+ await this.request(sql, settings);
21
17
  }
22
18
  /**
23
19
  * A query, answered as `JSONCompact`: the columns, and the rows as arrays. The format is the
@@ -27,15 +23,17 @@ export class ClickHouse {
27
23
  const text = await this.request(sql, { ...settings, default_format: 'JSONCompact' });
28
24
  return parse(text);
29
25
  }
30
- /** Each row as an object, for what this extension reads of the database itself. */
31
- async rows(sql) {
32
- const text = await this.request(sql, { ...NUMBERS, default_format: 'JSON' });
26
+ /** Each row as an object, for what this extension reads of the store itself. */
27
+ async rows(sql, settings = {}) {
28
+ const text = await this.request(sql, {
29
+ ...settings,
30
+ ...NUMBERS,
31
+ default_format: 'JSON'
32
+ });
33
33
  return parse(text).data;
34
34
  }
35
- async request(sql, settings, database = this.database) {
35
+ async request(sql, settings) {
36
36
  const search = new URLSearchParams(settings);
37
- if (database !== '')
38
- search.set('database', database);
39
37
  const response = await fetch(`${this.endpoint}?${search.toString()}`, {
40
38
  method: 'POST',
41
39
  headers: this.headers,