@toa.io/extensions.chronicles 0.0.0-stage → 1.0.0-alpha.333
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/components/chronicles.encounters/manifest.toa.yaml +108 -0
- package/components/chronicles.encounters/operations/anonymous.js +4 -0
- package/components/chronicles.encounters/operations/describe.js +4 -0
- package/components/chronicles.encounters/operations/identified.js +4 -0
- package/components/chronicles.encounters/types/index.d.ts +3 -0
- package/components/chronicles.encounters/types/toa.d.ts +36 -0
- package/components/chronicles.queries/manifest.toa.yaml +139 -0
- package/components/chronicles.queries/operations/define.js +3 -0
- package/components/chronicles.queries/operations/describe.js +3 -0
- package/components/chronicles.queries/operations/drop.js +3 -0
- package/components/chronicles.queries/operations/query.js +3 -0
- package/components/chronicles.queries/types/index.d.ts +3 -0
- package/components/chronicles.queries/types/toa.d.ts +49 -0
- package/components/chronicles.relay/manifest.toa.yaml +43 -0
- package/components/chronicles.relay/operations/encounter.js +4 -0
- package/components/chronicles.relay/operations/record.js +4 -0
- package/components/chronicles.relay/types/index.d.ts +3 -0
- package/components/chronicles.relay/types/toa.d.ts +24 -0
- package/guides/clickhouse.md +19 -0
- package/guides/log.md +52 -0
- package/package.json +39 -4
- package/readme.md +686 -0
- package/store/clickhouse.scope.sql +29 -0
- package/store/clickhouse.sql +99 -0
- package/transpiled/Client.d.ts +32 -0
- package/transpiled/Client.js +173 -0
- package/transpiled/Client.js.map +1 -0
- package/transpiled/Factory.d.ts +24 -0
- package/transpiled/Factory.js +62 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Provider.d.ts +142 -0
- package/transpiled/Provider.js +4 -0
- package/transpiled/Provider.js.map +1 -0
- package/transpiled/Publisher.d.ts +50 -0
- package/transpiled/Publisher.js +169 -0
- package/transpiled/Publisher.js.map +1 -0
- package/transpiled/Reader.d.ts +28 -0
- package/transpiled/Reader.js +166 -0
- package/transpiled/Reader.js.map +1 -0
- package/transpiled/Recorder.d.ts +38 -0
- package/transpiled/Recorder.js +107 -0
- package/transpiled/Recorder.js.map +1 -0
- package/transpiled/Relay.d.ts +14 -0
- package/transpiled/Relay.js +27 -0
- package/transpiled/Relay.js.map +1 -0
- package/transpiled/Sender.d.ts +25 -0
- package/transpiled/Sender.js +86 -0
- package/transpiled/Sender.js.map +1 -0
- package/transpiled/Service.d.ts +26 -0
- package/transpiled/Service.js +70 -0
- package/transpiled/Service.js.map +1 -0
- package/transpiled/address.d.ts +7 -0
- package/transpiled/address.js +27 -0
- package/transpiled/address.js.map +1 -0
- package/transpiled/agent.d.ts +16 -0
- package/transpiled/agent.js +48 -0
- package/transpiled/agent.js.map +1 -0
- package/transpiled/clickhouse/Client.d.ts +35 -0
- package/transpiled/clickhouse/Client.js +71 -0
- package/transpiled/clickhouse/Client.js.map +1 -0
- package/transpiled/clickhouse/Provider.d.ts +35 -0
- package/transpiled/clickhouse/Provider.js +169 -0
- package/transpiled/clickhouse/Provider.js.map +1 -0
- package/transpiled/clickhouse/ddl.d.ts +9 -0
- package/transpiled/clickhouse/ddl.js +15 -0
- package/transpiled/clickhouse/ddl.js.map +1 -0
- package/transpiled/clickhouse/views.d.ts +11 -0
- package/transpiled/clickhouse/views.js +97 -0
- package/transpiled/clickhouse/views.js.map +1 -0
- package/transpiled/contract.d.ts +12 -0
- package/transpiled/contract.js +58 -0
- package/transpiled/contract.js.map +1 -0
- package/transpiled/index.d.ts +15 -0
- package/transpiled/index.js +2 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/settings.d.ts +13 -0
- package/transpiled/settings.js +48 -0
- package/transpiled/settings.js.map +1 -0
- package/transpiled/store.d.ts +5 -0
- package/transpiled/store.js +11 -0
- package/transpiled/store.js.map +1 -0
- package/README.md +0 -3
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,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,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,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": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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
|
+
}
|