@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.
Files changed (83) hide show
  1. package/LICENSE +22 -0
  2. package/components/chronicles.encounters/manifest.toa.yaml +108 -0
  3. package/components/chronicles.encounters/operations/anonymous.js +4 -0
  4. package/components/chronicles.encounters/operations/describe.js +4 -0
  5. package/components/chronicles.encounters/operations/identified.js +4 -0
  6. package/components/chronicles.encounters/types/index.d.ts +3 -0
  7. package/components/chronicles.encounters/types/toa.d.ts +36 -0
  8. package/components/chronicles.queries/manifest.toa.yaml +139 -0
  9. package/components/chronicles.queries/operations/define.js +3 -0
  10. package/components/chronicles.queries/operations/describe.js +3 -0
  11. package/components/chronicles.queries/operations/drop.js +3 -0
  12. package/components/chronicles.queries/operations/query.js +3 -0
  13. package/components/chronicles.queries/types/index.d.ts +3 -0
  14. package/components/chronicles.queries/types/toa.d.ts +49 -0
  15. package/components/chronicles.relay/manifest.toa.yaml +43 -0
  16. package/components/chronicles.relay/operations/encounter.js +4 -0
  17. package/components/chronicles.relay/operations/record.js +4 -0
  18. package/components/chronicles.relay/types/index.d.ts +3 -0
  19. package/components/chronicles.relay/types/toa.d.ts +24 -0
  20. package/guides/clickhouse.md +19 -0
  21. package/guides/log.md +52 -0
  22. package/package.json +39 -4
  23. package/readme.md +686 -0
  24. package/store/clickhouse.scope.sql +29 -0
  25. package/store/clickhouse.sql +99 -0
  26. package/transpiled/Client.d.ts +32 -0
  27. package/transpiled/Client.js +173 -0
  28. package/transpiled/Client.js.map +1 -0
  29. package/transpiled/Factory.d.ts +24 -0
  30. package/transpiled/Factory.js +62 -0
  31. package/transpiled/Factory.js.map +1 -0
  32. package/transpiled/Provider.d.ts +142 -0
  33. package/transpiled/Provider.js +4 -0
  34. package/transpiled/Provider.js.map +1 -0
  35. package/transpiled/Publisher.d.ts +50 -0
  36. package/transpiled/Publisher.js +169 -0
  37. package/transpiled/Publisher.js.map +1 -0
  38. package/transpiled/Reader.d.ts +28 -0
  39. package/transpiled/Reader.js +166 -0
  40. package/transpiled/Reader.js.map +1 -0
  41. package/transpiled/Recorder.d.ts +38 -0
  42. package/transpiled/Recorder.js +107 -0
  43. package/transpiled/Recorder.js.map +1 -0
  44. package/transpiled/Relay.d.ts +14 -0
  45. package/transpiled/Relay.js +27 -0
  46. package/transpiled/Relay.js.map +1 -0
  47. package/transpiled/Sender.d.ts +25 -0
  48. package/transpiled/Sender.js +86 -0
  49. package/transpiled/Sender.js.map +1 -0
  50. package/transpiled/Service.d.ts +26 -0
  51. package/transpiled/Service.js +70 -0
  52. package/transpiled/Service.js.map +1 -0
  53. package/transpiled/address.d.ts +7 -0
  54. package/transpiled/address.js +27 -0
  55. package/transpiled/address.js.map +1 -0
  56. package/transpiled/agent.d.ts +16 -0
  57. package/transpiled/agent.js +48 -0
  58. package/transpiled/agent.js.map +1 -0
  59. package/transpiled/clickhouse/Client.d.ts +35 -0
  60. package/transpiled/clickhouse/Client.js +71 -0
  61. package/transpiled/clickhouse/Client.js.map +1 -0
  62. package/transpiled/clickhouse/Provider.d.ts +35 -0
  63. package/transpiled/clickhouse/Provider.js +169 -0
  64. package/transpiled/clickhouse/Provider.js.map +1 -0
  65. package/transpiled/clickhouse/ddl.d.ts +9 -0
  66. package/transpiled/clickhouse/ddl.js +15 -0
  67. package/transpiled/clickhouse/ddl.js.map +1 -0
  68. package/transpiled/clickhouse/views.d.ts +11 -0
  69. package/transpiled/clickhouse/views.js +97 -0
  70. package/transpiled/clickhouse/views.js.map +1 -0
  71. package/transpiled/contract.d.ts +12 -0
  72. package/transpiled/contract.js +58 -0
  73. package/transpiled/contract.js.map +1 -0
  74. package/transpiled/index.d.ts +15 -0
  75. package/transpiled/index.js +2 -0
  76. package/transpiled/index.js.map +1 -0
  77. package/transpiled/settings.d.ts +13 -0
  78. package/transpiled/settings.js +48 -0
  79. package/transpiled/settings.js.map +1 -0
  80. package/transpiled/store.d.ts +5 -0
  81. package/transpiled/store.js +11 -0
  82. package/transpiled/store.js.map +1 -0
  83. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2020-present Artem Gurtovoi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,108 @@
1
+ namespace: chronicles
2
+ name: encounters
3
+
4
+ # The extension writes what this component is sent to `encounters`.
5
+ chronicles: ~
6
+
7
+ # What clients send, by name: documentation for whoever reads the log or writes a client, which
8
+ # nothing sent is checked against.
9
+ configuration:
10
+ schema:
11
+ type: object
12
+ propertyNames:
13
+ pattern: ^[a-z][a-z0-9_]{0,63}$
14
+ additionalProperties:
15
+ type: object
16
+ properties:
17
+ description:
18
+ description: What the client tells by sending it
19
+ type: string
20
+ fields:
21
+ description: What each field of its payload is, by path
22
+ type: object
23
+ additionalProperties:
24
+ type: string
25
+ required: [description]
26
+ additionalProperties: false
27
+ defaults: {}
28
+
29
+ operations:
30
+ anonymous:
31
+ type: effect
32
+ input: &batch
33
+ description: >
34
+ `{ sent, visitor?, session?, events }` — `sent` when the client sends the batch, by its
35
+ clock — at most 100 events, each `{ id, name, time, payload? }`, `time` when it
36
+ happened, by the same clock; and the client's `ip`, `country`, `origin` and `agent`,
37
+ which the gateway puts in; as JSON, or as the form `navigator.sendBeacon` sends, whose
38
+ `events` is their JSON and `sent` its digits, which the extension parses and checks
39
+ type: object
40
+ output: &accepted
41
+ type: object
42
+ properties:
43
+ status:
44
+ type: integer
45
+ accepted:
46
+ type: integer
47
+ errors: &errors [INVALID, OVERSIZED]
48
+ identified:
49
+ type: effect
50
+ input: *batch
51
+ output: *accepted
52
+ errors: *errors
53
+ describe:
54
+ type: computation
55
+ output:
56
+ description: What clients send, by name, as the configuration documents it
57
+ type: object
58
+
59
+ exposition:
60
+ isolated: true
61
+ /:
62
+ POST:
63
+ anonymous: true
64
+ map:ip: ip
65
+ map:country: country
66
+ map:headers:
67
+ origin: origin
68
+ agent: user-agent
69
+ io:throttle:
70
+ key: [ip]
71
+ requests: 60
72
+ interval: 60
73
+ io:status: status
74
+ io:output: [status, accepted]
75
+ help:method: null
76
+ endpoint: anonymous
77
+ GET:
78
+ auth:role: system:chronicles:encounters
79
+ mcp:tool: true
80
+ help:method:
81
+ title: What clients send
82
+ description: >
83
+ The encounters clients send, by name: what each tells, and what each field of its
84
+ payload is. It documents, and holds nothing back: a name it does not tell is written
85
+ as any other. A client sends a batch with `sent`, its clock as it sends, and each event
86
+ with `time`, its clock as the event happened — both read as they are, never corrected,
87
+ however wrong the clock is: what is kept is when the gateway received the batch, less
88
+ the distance between the two.
89
+ io:output: true
90
+ endpoint: describe
91
+ /:identity:
92
+ POST:
93
+ auth:id: identity
94
+ map:segments:
95
+ identity: identity
96
+ map:ip: ip
97
+ map:country: country
98
+ map:headers:
99
+ origin: origin
100
+ agent: user-agent
101
+ io:throttle:
102
+ key: [identity]
103
+ requests: 60
104
+ interval: 60
105
+ io:status: status
106
+ io:output: [status, accepted]
107
+ help:method: null
108
+ endpoint: identified
@@ -0,0 +1,4 @@
1
+ // what anyone sent, checked and written by the extension
2
+ export async function effect(input, context) {
3
+ return await context.chronicles.write(input, null)
4
+ }
@@ -0,0 +1,4 @@
1
+ // what the context documents of what clients send
2
+ export function computation(_, context) {
3
+ return context.configuration
4
+ }
@@ -0,0 +1,4 @@
1
+ // what a caller the gateway verified sent, written as that identity's
2
+ export async function effect(input, context) {
3
+ return await context.chronicles.write(input, input.identity)
4
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -0,0 +1,36 @@
1
+ // Written by `toa types`. Every run rewrites it.
2
+ // What a manifest does not state belongs in a file of your own.
3
+
4
+ import type { CodedError } from '@toa.io/core/types'
5
+ import type { Chronicles } from '@toa.io/extensions.chronicles'
6
+ import type { FetchInit } from '@toa.io/extensions.fetch'
7
+ import type { Logs, Metrics, Span } from '@toa.io/extensions.telemetry'
8
+
9
+ export type AnonymousInput = Record<string, unknown>
10
+
11
+ export type AnonymousOutput = {
12
+ status?: number
13
+ accepted?: number
14
+ }
15
+
16
+ export type IdentifiedInput = Record<string, unknown>
17
+
18
+ export type IdentifiedOutput = {
19
+ status?: number
20
+ accepted?: number
21
+ }
22
+
23
+ export type DescribeOutput = Record<string, unknown>
24
+
25
+ export interface Component {
26
+ anonymous: (request: { input: AnonymousInput, task?: boolean }) => Promise<AnonymousOutput | CodedError<"INVALID" | "OVERSIZED">>
27
+ identified: (request: { input: IdentifiedInput, task?: boolean }) => Promise<IdentifiedOutput | CodedError<"INVALID" | "OVERSIZED">>
28
+ describe: (request: { input?: null, task?: boolean }) => Promise<DescribeOutput>
29
+ }
30
+
31
+ export type Configuration = Record<string, {
32
+ /** What the client tells by sending it */
33
+ description: string
34
+ /** What each field of its payload is, by path */
35
+ fields?: Record<string, string>
36
+ }>
@@ -0,0 +1,139 @@
1
+ namespace: chronicles
2
+ name: queries
3
+
4
+ # The extension puts the log's reader on this component.
5
+ chronicles: ~
6
+
7
+ operations:
8
+ describe:
9
+ type: computation
10
+ output:
11
+ type: object
12
+ properties:
13
+ guide:
14
+ description: How to read the log, and how its store is read
15
+ type: string
16
+ labels:
17
+ type: array
18
+ states:
19
+ type: array
20
+ encounters:
21
+ type: array
22
+ views:
23
+ type: array
24
+ query:
25
+ type: computation
26
+ input:
27
+ type: object
28
+ properties:
29
+ sql:
30
+ description: One statement over the log, as the guide `describe` answers says
31
+ type: string
32
+ minLength: 1
33
+ maxLength: 65536
34
+ required: [sql]
35
+ output:
36
+ type: object
37
+ properties:
38
+ columns:
39
+ type: array
40
+ rows:
41
+ type: array
42
+ truncated:
43
+ type: boolean
44
+ errors: [REFUSED]
45
+ define:
46
+ type: effect
47
+ input:
48
+ type: object
49
+ properties:
50
+ name: &name
51
+ description: The view's name, which is what a query reads it by
52
+ type: string
53
+ pattern: ^[a-z][a-z0-9_]{0,62}$
54
+ sql:
55
+ description: The statement the view is, one a query could run
56
+ type: string
57
+ minLength: 1
58
+ maxLength: 65536
59
+ refresh:
60
+ description: Seconds between computations of a materialized view; absent, a plain view
61
+ type: integer
62
+ minimum: 3600
63
+ title:
64
+ description: What the view is, in a few words
65
+ type: string
66
+ minLength: 1
67
+ maxLength: 100
68
+ description:
69
+ description: What a row of the view is, and what each of its columns counts
70
+ type: string
71
+ minLength: 1
72
+ maxLength: 4096
73
+ required: [name, sql]
74
+ errors: [REFUSED, RESERVED, EXHAUSTED]
75
+ drop:
76
+ type: effect
77
+ input:
78
+ type: object
79
+ properties:
80
+ name: *name
81
+ required: [name]
82
+ errors: [NOT_FOUND]
83
+
84
+ exposition:
85
+ isolated: true
86
+ /:
87
+ auth:role: system:chronicles:query
88
+ mcp:tool: true
89
+ GET:
90
+ help:method:
91
+ title: What the log holds
92
+ description: >
93
+ A guide to reading the log and its store, to read before a query — what each table and
94
+ column means, and which time is which; the labels of `events` the components declare,
95
+ each with what it means, the view that reads it in types, how many rows it has, the
96
+ first and last time of them, and its fields with their types and what they mean; the
97
+ components with a state in `states`, likewise; the names of `encounters` clients sent,
98
+ with when the newest batch was received, the fields of their payloads as they were sent
99
+ and what the context documents of them; and the views defined over the log, with their
100
+ titles, descriptions and when a materialized one was last refreshed. `states` is each
101
+ entity as it stands now: what it was before is read from its events.
102
+ io:output: true
103
+ endpoint: describe
104
+ POST:
105
+ help:method:
106
+ title: Query the log
107
+ description: >
108
+ One statement, as the guide `chronicles.queries.GET` answers says, over `events`, a row
109
+ for everything a component did (id, label, component, time, entity, version, identity,
110
+ trace, payload), `states`, each entity as it stands (component, entity, version,
111
+ identity, time, payload), `encounters`, what clients sent (id, name, time, received,
112
+ visitor, session, identity, ip, country, origin, agent, browser, os, device, payload),
113
+ the view of each declared label and state, and the views. Every `time` is when it
114
+ happened, by the server: a change when the component made it, a fact when it was sent,
115
+ an encounter when the gateway received its batch less how long before sending the
116
+ client measured it happened — never the client's clock as it reads, nor when the store
117
+ wrote the row. An encounter's `received` is when the gateway received its batch. At
118
+ most 30 seconds, 10000 rows and 64 MiB; at the rows, `truncated` is true.
119
+ io:output: true
120
+ endpoint: query
121
+ /views/:name:
122
+ auth:role: system:chronicles:views
123
+ mcp:tool: true
124
+ PUT:
125
+ help:method:
126
+ title: Define a view
127
+ description: >
128
+ A view over the log, by a statement a query could run, with a title and a description
129
+ that tell whoever finds it what it counts. Without `refresh` it is computed when it is
130
+ read; with `refresh`, in seconds and at least 3600, it is computed on that cycle and
131
+ read as a table. Defining a name again replaces the view.
132
+ help:route:
133
+ name: The view's name
134
+ endpoint: define
135
+ DELETE:
136
+ help:method: Drop a view
137
+ help:route:
138
+ name: The view's name
139
+ endpoint: drop
@@ -0,0 +1,3 @@
1
+ export async function effect(input, context) {
2
+ return await context.chronicles.define(input)
3
+ }
@@ -0,0 +1,3 @@
1
+ export async function computation(_, context) {
2
+ return await context.chronicles.describe()
3
+ }
@@ -0,0 +1,3 @@
1
+ export async function effect(input, context) {
2
+ return await context.chronicles.drop(input.name)
3
+ }
@@ -0,0 +1,3 @@
1
+ export async function computation(input, context) {
2
+ return await context.chronicles.query(input.sql)
3
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -0,0 +1,49 @@
1
+ // Written by `toa types`. Every run rewrites it.
2
+ // What a manifest does not state belongs in a file of your own.
3
+
4
+ import type { CodedError } from '@toa.io/core/types'
5
+
6
+ export type DescribeOutput = {
7
+ /** How to read the log, and how its store is read */
8
+ guide?: string
9
+ labels?: unknown[]
10
+ states?: unknown[]
11
+ encounters?: unknown[]
12
+ views?: unknown[]
13
+ }
14
+
15
+ export type QueryInput = {
16
+ /** One statement over the log, as the guide `describe` answers says */
17
+ sql: string
18
+ }
19
+
20
+ export type QueryOutput = {
21
+ columns?: unknown[]
22
+ rows?: unknown[]
23
+ truncated?: boolean
24
+ }
25
+
26
+ export type DefineInput = {
27
+ /** The view's name, which is what a query reads it by */
28
+ name: string
29
+ /** The statement the view is, one a query could run */
30
+ sql: string
31
+ /** Seconds between computations of a materialized view; absent, a plain view */
32
+ refresh?: number
33
+ /** What the view is, in a few words */
34
+ title?: string
35
+ /** What a row of the view is, and what each of its columns counts */
36
+ description?: string
37
+ }
38
+
39
+ export type DropInput = {
40
+ /** The view's name, which is what a query reads it by */
41
+ name: string
42
+ }
43
+
44
+ export interface Component {
45
+ describe: (request: { input?: null, task?: boolean }) => Promise<DescribeOutput>
46
+ query: (request: { input: QueryInput, task?: boolean }) => Promise<QueryOutput | CodedError<"REFUSED">>
47
+ define: (request: { input: DefineInput, task?: boolean }) => Promise<{} | null | undefined | CodedError<"REFUSED" | "RESERVED" | "EXHAUSTED">>
48
+ drop: (request: { input: DropInput, task?: boolean }) => Promise<{} | null | undefined | CodedError<"NOT_FOUND">>
49
+ }
@@ -0,0 +1,43 @@
1
+ namespace: chronicles
2
+ name: relay
3
+
4
+ # The extension publishes what this component is tasked with to the broker of the log.
5
+ chronicles: ~
6
+
7
+ operations:
8
+ record:
9
+ type: effect
10
+ input:
11
+ description: A fact a component sent, as the row it is published as
12
+ type: object
13
+ properties:
14
+ id:
15
+ type: string
16
+ label:
17
+ type: string
18
+ component:
19
+ type: string
20
+ time:
21
+ type: integer
22
+ entity:
23
+ type: "null"
24
+ version:
25
+ type: "null"
26
+ identity:
27
+ type: [string, "null"]
28
+ trace:
29
+ type: [string, "null"]
30
+ payload:
31
+ type: object
32
+ required: [id, label, component, time, payload]
33
+ encounter:
34
+ type: effect
35
+ input:
36
+ description: The encounters of a batch a client sent, as the rows they are published as
37
+ type: object
38
+ properties:
39
+ encounters:
40
+ type: array
41
+ items:
42
+ type: object
43
+ required: [encounters]
@@ -0,0 +1,4 @@
1
+ // the encounters of a batch a client sent, published by the extension
2
+ export async function effect(input, context) {
3
+ await context.chronicles.encounter(input.encounters)
4
+ }
@@ -0,0 +1,4 @@
1
+ // a fact a component sent with `context.chronicles.send`, published by the extension
2
+ export async function effect(input, context) {
3
+ await context.chronicles.record(input)
4
+ }
@@ -0,0 +1,3 @@
1
+ export * from './toa.d.ts'
2
+
3
+ // What a manifest does not state belongs here, and every run keeps it.
@@ -0,0 +1,24 @@
1
+ // Written by `toa types`. Every run rewrites it.
2
+ // What a manifest does not state belongs in a file of your own.
3
+
4
+
5
+ export type RecordInput = {
6
+ id: string
7
+ label: string
8
+ component: string
9
+ time: number
10
+ entity?: null
11
+ version?: null
12
+ identity?: string | null
13
+ trace?: string | null
14
+ payload: Record<string, unknown>
15
+ }
16
+
17
+ export type EncounterInput = {
18
+ encounters: Array<Record<string, unknown>>
19
+ }
20
+
21
+ export interface Component {
22
+ record: (request: { input: RecordInput, task?: boolean }) => Promise<unknown>
23
+ encounter: (request: { input: EncounterInput, task?: boolean }) => Promise<unknown>
24
+ }
@@ -0,0 +1,19 @@
1
+ # ClickHouse
2
+
3
+ Queries are ClickHouse SQL, one statement, without `FORMAT`.
4
+
5
+ - Read `events`, `states` and `encounters` with `FINAL`: a row delivered twice is two until
6
+ ClickHouse merges them.
7
+ - `payload` is a `JSON` column. A path is `payload.total`, `payload.shipping.country`; its type is
8
+ whatever was written, so cast it: `payload.total::Int64`, `toString(payload.path)`.
9
+ `JSONAllPaths(payload)` lists the paths of a row, `JSONAllPathsWithTypes(payload)` with types.
10
+ - A declared field's `read` gives it in its type: an object as a named `Tuple`, read as
11
+ `shipping.country`, an array of objects as an `Array(Tuple(...))`, read with
12
+ `ARRAY JOIN items AS item` and `item.sku`. Every one is `Nullable`, and `NULL` where a row's
13
+ value was not of its type: `sum`, `avg` and `count(field)` skip `NULL`, `arraySum` refuses a
14
+ `Nullable` array — use `ARRAY JOIN` instead.
15
+ - `time`, and an encounter's `received`, are `DateTime64(3, 'UTC')`: `toDate(time)`, `toStartOfHour(time)`,
16
+ `time >= now() - INTERVAL 7 DAY`.
17
+ - A query runs at most 30 seconds and answers at most 10000 rows and 64 MiB; past the rows it is
18
+ `truncated`. Aggregate rather than read rows.
19
+ - A view with `refresh` is computed on that cycle: `refreshed` says when it last was.
package/guides/log.md ADDED
@@ -0,0 +1,52 @@
1
+ # Reading the log
2
+
3
+ The log is three tables: `events`, `states` and `encounters`. Every row has `scope`, the
4
+ application it is of; you read the rows of yours.
5
+
6
+ - `events`: a row for everything a component did. `label` is `<component>.<event>` for a change,
7
+ with the `entity` that changed and its `version`, or `<component>.<name>` for a fact, with
8
+ neither. `identity` is whose the row is, where the component says; `trace` is the call that
9
+ caused it.
10
+ - `states`: each entity as it stands now, by `component` and `entity`, at its latest `version`.
11
+ Nothing before it is kept: what an entity was at a time in the past is read from its events.
12
+ - `encounters`: what clients sent, by `name`. `visitor` is the client's id of itself, kept across
13
+ sessions; `session` is one period of its activity; `identity` is set where a signed-in client
14
+ sent it. `ip` is the client's network, `country` its country, `origin` the site it was sent from,
15
+ `agent` its `user-agent`, and `browser`, `os` and `device` what the agent says it is.
16
+
17
+ ## Time
18
+
19
+ Every row has `time`, when it happened, on the server's clock. No column is when the store wrote a
20
+ row.
21
+
22
+ - A change, in `events` and `states`: when the component made it — the entity's `UPDATED`, or the
23
+ property the component names, or else the moment of the change.
24
+ - A fact: when the operation sent it.
25
+ - An encounter: when the gateway received its batch, less how long before sending the batch the
26
+ client says the event happened. A client's clock may be off by minutes or years; what it
27
+ measures between two of its own moments is not. So `time` is the gateway's clock at the
28
+ distance the client measured: the order and the intervals of one client's encounters are exact,
29
+ and so is when they happened, to the time a request takes to arrive. A batch a client held
30
+ while offline is written at when its events happened.
31
+
32
+ An encounter also has `received`, when the gateway received its batch: one `received` is shared by
33
+ every encounter of a batch, and a `received` far behind now means clients stopped sending.
34
+
35
+ Rows can arrive late — after the service that writes them was away — and are written at their own
36
+ time, so a count of the last hours may still grow. Compare `time` of encounters and of events
37
+ directly: both are the server's.
38
+
39
+ A declared label's payload holds only what is declared. `chronicles.queries.GET` tells each
40
+ declared field's type, what it means, and `read`: the expression that selects it from the table
41
+ in its type — use it rather than casting the payload yourself. An encounter's
42
+ payload is what the client sent, and its fields differ between versions of a client: two types of
43
+ one field are two versions.
44
+
45
+ A visitor is not a person: one identity may have many visitors, and a browser that clears its
46
+ storage is a new one. The visitors of an identity are the rows that have both. Leave out
47
+ `device = 'bot'` from any question of who visits. Safari on an iPad reports a desktop Mac.
48
+
49
+ Nothing arrives in order: order by `time`, or by `version` within one entity. A view another agent
50
+ defined says what it counts in its title and description; read them before reusing one, and give
51
+ your own views both. A view is read by its name, which `chronicles.queries.GET` tells with its
52
+ database.
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.chronicles",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0-alpha.333",
4
+ "type": "module",
5
+ "description": "Toa Chronicles: a log of a context, kept in ClickHouse",
6
+ "author": "temich <tema.gurtovoy@gmail.com>",
7
+ "homepage": "https://github.com/toa-io/toa#readme",
8
+ "main": "transpiled/index.js",
9
+ "types": "transpiled/index.d.ts",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/toa-io/toa.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/toa-io/toa/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "files": [
21
+ "transpiled",
22
+ "components",
23
+ "guides",
24
+ "store",
25
+ "readme.md",
26
+ "!**/tsconfig.tsbuildinfo",
27
+ "!**/*.test.*",
28
+ "!**/.env*"
29
+ ],
30
+ "scripts": {
31
+ "transpile": "tsc"
32
+ },
33
+ "dependencies": {
34
+ "@toa.io/core": "1.0.0-alpha.333",
35
+ "@toa.io/definitions": "1.0.0-alpha.333",
36
+ "@toa.io/generic": "1.0.0-alpha.332",
37
+ "comq": "0.28.0",
38
+ "openspan": "1.0.0-alpha.330"
39
+ },
40
+ "gitHead": "ddb74b2f99026fae540a8a8e0a49c26cbd22886a"
41
+ }