h1v3 0.7.2 → 0.7.4

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.
@@ -26,7 +26,8 @@ export async function inject({ database, authentication, metaURL, handleError =
26
26
  if (!metaURL) throw new Error("Missing metaURL");
27
27
  if (!authentication) throw new Error("Missing authentication");
28
28
 
29
- const meta = await fetch(metaURL).then(resp => resp.json())
29
+ // const meta = await fetch(metaURL).then(resp => resp.json())
30
+ const { default: meta } = await import(metaURL, { with: { type: "json" } });
30
31
 
31
32
  const log = logger(database, authentication, meta.o11y.path);
32
33
  const teamMembershipStoreFactory = ({ tid }) => eventStore(database, authentication, populateParameters({ tid }, meta.team.membership.path));
@@ -23,7 +23,7 @@ function rand62(n) {
23
23
 
24
24
  }
25
25
 
26
- export const timeId = () => `${Date.now()}-${rand62(21)}`;
26
+ export const timeId = suffix => `${Date.now()}-${suffix || rand62(21)}`;
27
27
 
28
28
  export const generateSpanId = () => generateLowerHex(8);
29
29
 
@@ -14,25 +14,35 @@ function munge(attributes) {
14
14
  export function logger(database, authentication, path, defaultAttributes) {
15
15
 
16
16
  const { db, ref, set } = database;
17
+ const { auth } = authentication;
17
18
 
18
- async function recordLog(trace_id, level, sharedAttributes, { body, parent_id, attributes }) {
19
-
20
- const span_id = generateSpanId();
21
- console.log(defaultAttributes, sharedAttributes, attributes);
22
- const data = {
23
- meta: { uid: authentication.auth.currentUser.uid },
24
- body,
25
- trace_id,
26
- span_id,
27
- severity_text: level,
28
- time_client_ms: Date.now(),
29
- attributes: munge({ ...defaultAttributes, ...sharedAttributes, ...attributes })
30
- };
31
- if (parent_id) data.parent_id = parent_id;
32
- const refPath = path.replace("$id", timeId());
33
- console.log("Logging", data, "to", refPath);
34
- return await set(ref(db, refPath), data);
19
+ async function recordLog(traceId, level, sharedAttributes, { message, parentSpanId, spanId, attributes }) {
35
20
 
21
+ let data;
22
+ try {
23
+
24
+ const uid = auth?.currentUser?.uid;
25
+ if (!uid) throw new Error("No current user id. Is the user logged in?");
26
+
27
+ spanId = spanId || generateSpanId();
28
+ data = {
29
+ meta: { uid },
30
+ message,
31
+ traceId,
32
+ spanId,
33
+ severity_text: level,
34
+ time_client_ms: Date.now(),
35
+ attributes: munge({ ...defaultAttributes, ...sharedAttributes, ...attributes })
36
+ };
37
+ if (parentSpanId) data.parentSpanId = parentSpanId;
38
+ const refPath = path.replace("$id", `${traceId}-${spanId}`);
39
+ return await set(ref(db, refPath), data);
40
+
41
+ } catch(err) {
42
+
43
+ console.error("Error dispatching o11y message", data, err);
44
+
45
+ }
36
46
 
37
47
  }
38
48
 
@@ -41,17 +51,13 @@ export function logger(database, authentication, path, defaultAttributes) {
41
51
  context(sharedAttributes) {
42
52
 
43
53
  const id = generateTraceId();
44
- return {
45
- id,
46
- trace: recordLog.bind(this, id, "TRACE", sharedAttributes),
47
- debug: recordLog.bind(this, id, "DEBUG", sharedAttributes),
48
- info: recordLog.bind(this, id, "INFO", sharedAttributes),
49
- warn: recordLog.bind(this, id, "WARN", sharedAttributes),
50
- error: recordLog.bind(this, id, "ERROR", sharedAttributes),
51
- fatal: recordLog.bind(this, id, "FATAL", sharedAttributes)
52
- };
54
+ const ret = { id };
55
+ for(const x of ["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"])
56
+ ret[x] = recordLog.bind(this, id, x.toUpperCase(), sharedAttributes);
57
+ return ret;
53
58
 
54
59
  }
60
+
55
61
  };
56
62
 
57
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h1v3",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -1,4 +1,4 @@
1
- import { EVENT_STORE_META } from "../event-store/configuration.js";
1
+ import { EVENT_STORE_META } from "../configuration/configure-eventStore.js";
2
2
  import { eventStore as eventStoreModular } from "../../dist/browser/client/event-store.js";
3
3
 
4
4
  function adaptDatabase(db) {
@@ -1,6 +1,7 @@
1
1
  import { EVENTS } from "../../dist/browser/client/event-store.js";
2
- import { updateProjections } from "./projections.js";
3
- import { refPathToTriggerPath } from "../paths.js";
2
+ import { refPathToTriggerPath } from "./paths.js";
3
+ import { updateProjections } from "../event-store/projections.js";
4
+
4
5
 
5
6
  export const EVENT_STORE_META = Symbol("Event store configuration metadata");
6
7
 
@@ -34,11 +35,4 @@ export function configureEventStore({ ref, projections, ...rest }, { onValueWrit
34
35
  );
35
36
 
36
37
  }
37
-
38
- configureEventStore.inject = ({ onValueWritten, observe, paths, region, instance }) =>
39
-
40
- (eventStoreFactory, ...args) =>
41
-
42
- configureEventStore(eventStoreFactory(paths, ...args), { onValueWritten, observe, region, instance });
43
-
44
-
38
+ configureEventStore.inject = ({ onValueWritten, observe, paths, region, instance }) => (eventStoreFactory, ...args) => configureEventStore(eventStoreFactory(paths, ...args), { onValueWritten, observe, region, instance });
@@ -0,0 +1,48 @@
1
+ import { loadConfigurationFromModule } from "./load-configuration.js";
2
+
3
+ function buildConfigDigest(module, paths) {
4
+
5
+
6
+ const refConfigMap = loadConfigurationFromModule(module)
7
+ .flatMap(x => x.map(y => y[1]))
8
+ .reduce((index, config) => ({ ...index, [config.ref]: config }), {});
9
+
10
+ function patchForPath(map) {
11
+
12
+ const ret = {
13
+ ...map
14
+ };
15
+ const mappedConfig = refConfigMap[map.path];
16
+ for (const [x, y] of Object.entries(ret).filter(([_, v]) => typeof v === "object")) {
17
+
18
+ ret[x] = patchForPath(y);
19
+
20
+ }
21
+ if (mappedConfig && (mappedConfig.read || mappedConfig.write)) {
22
+
23
+ ret.projections = mappedConfig.projections;
24
+ ret.eventTypes = mappedConfig.eventTypes;
25
+
26
+ }
27
+ return ret;
28
+
29
+ }
30
+
31
+ return patchForPath(paths);
32
+
33
+ }
34
+ const publicFor1Day = "public, max-age=86400";
35
+
36
+ export function configureMetadataEndpoint({ module, paths, onRequest, region, cacheControl = publicFor1Day }) {
37
+
38
+ const digest = buildConfigDigest(module, paths);
39
+ return onRequest(
40
+ { region },
41
+ async (_, res) => {
42
+
43
+ res.header("Cache-Control", cacheControl).send(digest);
44
+
45
+ }
46
+ );
47
+
48
+ }
@@ -1,72 +1,8 @@
1
- import { loadConfigurationFromModule } from "./load-configuration.js";
2
1
  import { refPathToTriggerPath } from "./paths.js";
3
- import { all, assertNewDataDoesNotHave, assertNewDataHasString, any, assertNewDataHasNumber, assertNewDataHasOneOf, assertNewDataHasLowercaseHexCharacters, assertNewDataFieldDoesNotMatch } from "./rules.js";
2
+ import { all, assertNewDataHasString, assertNewDataHasOneOf, assertNewDataHasLowercaseHexCharacters, assertNewDataFieldDoesNotMatch, assertNewDataDoesNotHave, any, assertNewDataHasNumber } from "./rules.js";
4
3
 
5
- export const RAW_STORE_META = Symbol("Raw database metadata");
6
-
7
- export function configureConfigStore(paths, _onValueWritten, _observe) {
8
-
9
- if (!paths?.config?.path) throw new Error("Missing: paths.config.path");
10
- return {
11
- [RAW_STORE_META]: {
12
- raw: true,
13
- ref: paths.config.path,
14
- read: true,
15
- write: false
16
- }
17
- };
18
-
19
- }
20
-
21
- function buildConfigDigest(module, paths) {
22
-
23
-
24
- const refConfigMap =
25
- loadConfigurationFromModule(module)
26
- .flatMap(x => x.map(y => y[1]))
27
- .reduce((index, config) => ({ ...index, [config.ref]: config }), {});
28
-
29
- function patchForPath(map) {
30
-
31
- const ret = {
32
- ...map
33
- };
34
- const mappedConfig = refConfigMap[map.path];
35
- for (const [x, y] of Object.entries(ret).filter(([_, v]) => typeof v === "object")) {
36
-
37
- ret[x] = patchForPath(y);
38
-
39
- }
40
- if (mappedConfig && (mappedConfig.read || mappedConfig.write)) {
41
-
42
- ret.projections = mappedConfig.projections;
43
- ret.eventTypes = mappedConfig.eventTypes;
44
-
45
- }
46
- return ret;
47
-
48
- }
49
-
50
- return patchForPath(paths);
51
-
52
- }
53
-
54
- const publicFor1Day = "public, max-age=86400";
55
-
56
- export function configureMetadataEndpoint({ module, paths, onRequest, region, cacheControl = publicFor1Day }) {
57
-
58
- const digest = buildConfigDigest(module, paths);
59
- return onRequest(
60
- { region },
61
- async (_, res) => {
62
-
63
- res.header("Cache-Control", cacheControl).send(digest);
64
-
65
- }
66
- );
67
-
68
- }
69
4
 
5
+ export const RAW_STORE_META = Symbol("Raw database metadata");
70
6
  const googleLogLevels = ["DEBUG", "INFO", "NOTICE", "WARNING", "ERROR", "CRITICAL", "ALERT", "EMERGENCY"];
71
7
 
72
8
  function decideSeverity(newData) {
@@ -75,13 +11,15 @@ function decideSeverity(newData) {
75
11
  return googleLogLevels.includes(text) ? text : "INFO";
76
12
 
77
13
  }
14
+ const DISPATCHED = "DISPATCHED";
15
+ const BLOCKED = "BLOCKED";
78
16
 
79
17
  /*
80
18
 
81
19
  message, severity, // required
82
20
  traceId, spanId, timestamp, // advised
83
21
  logData, // optional log line
84
- spanStart, spanEnd, spanAttributes // optional trace attributes
22
+ spanStart, spanEnd, spanAttributes, parentSpanId // optional trace attributes
85
23
 
86
24
  */
87
25
  function handleO11yEventWritten(observe, e) {
@@ -91,31 +29,72 @@ function handleO11yEventWritten(observe, e) {
91
29
  if (!newData.time_server_ms) {
92
30
 
93
31
  const now = new Date();
94
- const time_server_ms = now.valueOf();
95
32
  const timestamp = now.toISOString();
96
- const message = newData?.message || "Missing log message";
33
+ const time_server_ms = now.valueOf();
34
+ const { traceId, spanId, parentSpanId, ...rest } = newData;
97
35
 
98
- const { traceId, spanId, ...rest } = newData
99
- const logData = { ...rest, time_server_ms };
36
+ const { id } = e.params;
37
+ const [id_traceId, id_spanId] = id.split("-");
38
+ let action;
39
+ if (traceId !== id_traceId || spanId !== id_spanId) {
40
+
41
+ action = blockEvent({ message: "Mismatched event id", timestamp, details: { id, traceId, spanId }});
42
+
43
+ } else if (!newData?.meta?.uid) {
100
44
 
45
+ action = blockEvent({ message: "Missing / invalid uid", timestamp, details: { uid: newData?.meta?.uid } });
46
+
47
+ } else {
48
+
49
+ action = dispatch({ rest, time_server_ms, traceId, spanId, parentSpanId, timestamp });
50
+
51
+ }
52
+
53
+ after.ref.update({
54
+ time_server_ms,
55
+ timestamp,
56
+ action
57
+ });
58
+
59
+ }
60
+
61
+
62
+ function dispatch({ rest, time_server_ms, traceId, spanId, parentSpanId, timestamp }) {
63
+
64
+ const message = newData?.message || "Missing log message";
65
+ const uid = newData.meta?.uid;
66
+ const logData = { ...rest, time_server_ms };
101
67
  observe({
102
68
  message,
103
69
  severity: decideSeverity(newData),
104
70
 
105
- traceId: newData.trace_id,
106
- spanId: newData.span_id,
71
+ traceId,
72
+ spanId,
73
+ parentSpanId,
107
74
  timestamp,
108
75
 
109
- logData
76
+ logData,
77
+ spanAttributes: { "user.id": uid }
78
+ });
79
+ return DISPATCHED;
80
+
81
+ }
82
+
83
+ function blockEvent({ message, timestamp, details }) {
110
84
 
85
+ observe({
86
+ message: `O11y event blocked: ${message}`,
87
+ severity: "CRITICAL",
88
+ timestamp,
89
+ logData: { ...details, newData }
111
90
  });
112
- after.ref.update({ time_server_ms });
91
+ return BLOCKED;
113
92
 
114
93
  }
115
94
 
116
95
  }
117
96
 
118
- export function configureLoggingStore({ paths, onValueWritten, observe, region, instance }) {
97
+ export function configureO11yStore({ paths, onValueWritten, observe, region, instance }) {
119
98
 
120
99
  if (!paths) throw new Error("Missing paths");
121
100
  if (!onValueWritten) throw new Error("Missing onValueWritten");
@@ -139,29 +118,30 @@ export function configureLoggingStore({ paths, onValueWritten, observe, region,
139
118
  read: false,
140
119
  write: all(
141
120
  // required
142
- assertNewDataHasString("body", 500, 1),
121
+ assertNewDataHasString("message", 500, 1),
143
122
  assertNewDataHasOneOf("severity_text", ...googleLogLevels),
144
123
  // trace context
145
124
  all(
146
- assertNewDataHasLowercaseHexCharacters("trace_id", 32),
147
- assertNewDataFieldDoesNotMatch("trace_id", "0*")
125
+ assertNewDataHasLowercaseHexCharacters("traceId", 32),
126
+ assertNewDataFieldDoesNotMatch("traceId", "0*")
148
127
  ),
149
128
  all(
150
- assertNewDataHasLowercaseHexCharacters("span_id", 16),
151
- assertNewDataFieldDoesNotMatch("span_id", "0*")
129
+ assertNewDataHasLowercaseHexCharacters("spanId", 16),
130
+ assertNewDataFieldDoesNotMatch("spanId", "0*")
152
131
  ),
153
- // forbid server time faking
132
+ // forbid server time and action faking
154
133
  assertNewDataDoesNotHave("time_server_ms"),
134
+ assertNewDataDoesNotHave("action"),
155
135
  // optional bits
156
136
  any(
157
137
  assertNewDataDoesNotHave("time_client_ms"),
158
138
  assertNewDataHasNumber("time_client_ms")
159
139
  ),
160
140
  any(
161
- assertNewDataDoesNotHave("parent_id"),
141
+ assertNewDataDoesNotHave("parentSpanId"),
162
142
  all(
163
- assertNewDataHasLowercaseHexCharacters("parent_id", 16),
164
- assertNewDataFieldDoesNotMatch("parent_id", "0*")
143
+ assertNewDataHasLowercaseHexCharacters("parentSpanId", 16),
144
+ assertNewDataFieldDoesNotMatch("parentSpanId", "0*")
165
145
  )
166
146
  )
167
147
  )
@@ -170,4 +150,3 @@ export function configureLoggingStore({ paths, onValueWritten, observe, region,
170
150
  );
171
151
 
172
152
  }
173
-
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "path";
2
2
  import { pathToFileURL } from "url";
3
- import { EVENT_STORE_META } from "./event-store/configuration.js";
4
- import { RAW_STORE_META } from "./configuration.js";
3
+ import { EVENT_STORE_META } from "./configure-eventStore.js";
4
+ import { RAW_STORE_META } from "./configure-o11y-store.js";
5
5
 
6
6
  export async function loadConfiguration(argv) {
7
7
 
@@ -1,4 +1,4 @@
1
- import { PROJECTIONS } from "../dist/browser/client/event-store.js";
1
+ import { PROJECTIONS } from "../../dist/browser/client/event-store.js";
2
2
 
3
3
  export const any = (...args) =>
4
4
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { listEventStores } from "./commands/list-event-stores.js";
4
4
  import { generateRules } from "./commands/generate-rules.js";
package/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  export { PROJECTIONS, EVENTS } from "../dist/browser/client/event-store.js";
2
2
 
3
- export * from "./event-store/configuration.js";
4
-
5
- export * from "./configuration.js";
3
+ export * from "./configuration/configure-eventStore.js";
4
+ export * from "./configuration/configure-metadata-endpoint.js";
5
+ export * from "./configuration/configure-o11y-store.js";
6
6
 
7
7
  export const passThroughView = {
8
8
  "?": (view, e, key) => {
@@ -24,7 +24,7 @@ export * as userTeams from "./membership/user-teams/store.js";
24
24
 
25
25
  export * as userInvites from "./membership/userInvites/store.js";
26
26
 
27
- export * as rules from "./rules.js";
27
+ export * as rules from "./configuration/rules.js";
28
28
 
29
29
  export function explodePathVars(expr) {
30
30
 
@@ -34,5 +34,3 @@ export function explodePathVars(expr) {
34
34
  }
35
35
 
36
36
  export * as o11y from "./observability/index.js";
37
-
38
- export { loadConfigurationFromModule } from "./load-configuration.js";
@@ -1,4 +1,4 @@
1
- import { assertIsMyTeam } from "../../rules.js";
1
+ import { assertIsMyTeam } from "../../configuration/rules.js";
2
2
  import { eventTypes } from "./events.js";
3
3
  import current from "./projections/current.js";
4
4
  import { verifyStorePaths } from "./verify-store-paths.js";
@@ -13,7 +13,7 @@ import {
13
13
  any,
14
14
  all,
15
15
  assertEventIsOfType
16
- } from "../../rules.js";
16
+ } from "../../configuration/rules.js";
17
17
  import { verifyStorePaths } from "../team-details/verify-store-paths.js";
18
18
 
19
19
  export function store(paths) {
@@ -1,4 +1,4 @@
1
- import { assertUserIsMe } from "../../rules.js";
1
+ import { assertUserIsMe } from "../../configuration/rules.js";
2
2
  import { verifyStorePaths } from "../team-details/verify-store-paths.js";
3
3
  import { eventTypes } from "./events.js";
4
4
  import current from "./projections/current.js";
@@ -1,4 +1,4 @@
1
- import { assertUserIsMe } from "../../rules.js";
1
+ import { assertUserIsMe } from "../../configuration/rules.js";
2
2
  import { verifyStorePaths } from "../team-details/verify-store-paths.js";
3
3
  import { eventTypes } from "./events.js";
4
4
  import current from "./projections/current.js";
@@ -2,7 +2,7 @@ import { eventTypes, ACCEPTED_INVITATION, REJECTED_INVITATION } from "./events.j
2
2
  import pending from "./projections/pending.js";
3
3
  import { PROJECTIONS } from "../../index.js";
4
4
  import { verifyStorePaths } from "../team-details/verify-store-paths.js";
5
- import { all, any, assertEventIsOfType, assertDataFieldIsMyVerifiedEmail, assertMyVerifiedEmail, not, assertNewDataHas } from "../../rules.js";
5
+ import { all, any, assertEventIsOfType, assertDataFieldIsMyVerifiedEmail, assertMyVerifiedEmail, not, assertNewDataHas } from "../../configuration/rules.js";
6
6
 
7
7
  const assertAcceptedOrRejectedEvent = assertEventIsOfType(
8
8
  ACCEPTED_INVITATION,
@@ -9,7 +9,7 @@ export function provider({ trace, projectId, serviceName, log }) {
9
9
  message, severity, // required
10
10
  traceId, spanId, timestamp, // advised
11
11
  logData, // optional log line
12
- spanStart, spanEnd, spanAttributes // optional trace attributes
12
+ spanStart, spanEnd, spanAttributes, parentSpanId // optional trace attributes
13
13
  }) => {
14
14
 
15
15
  if (!message) throw new Error("Missing message");
@@ -30,7 +30,7 @@ export function provider({ trace, projectId, serviceName, log }) {
30
30
  const startTime = asISOString(spanStart);
31
31
  const endTime = asISOString(spanEnd);
32
32
 
33
- await createSpan({ trace }, { serviceName, projectTraceSpanName, message, spanId, startTime, endTime, spanAttributes });
33
+ await createSpan({ trace }, { parentSpanId, serviceName, projectTraceSpanName, message, spanId, startTime, endTime, spanAttributes });
34
34
  await writeLogLine({ log, projectId }, { tracePath, spanId, severity, timestamp, message, logData });
35
35
 
36
36
  };
@@ -92,23 +92,25 @@ async function writeLogLine({ log, projectId }, { message, tracePath, spanId, se
92
92
 
93
93
  }
94
94
 
95
- async function createSpan({ trace }, { serviceName, projectTraceSpanName, spanId, startTime, endTime, spanAttributes, message }) {
95
+ async function createSpan({ trace }, { serviceName, projectTraceSpanName, spanId, parentSpanId, startTime, endTime, spanAttributes, message }) {
96
96
 
97
97
  const attributes = toTraceAttributes({
98
98
  "service.name": serviceName,
99
99
  message,
100
100
  ...spanAttributes
101
101
  });
102
+ const requestBody = {
103
+ attributes,
104
+ name: projectTraceSpanName,
105
+ spanId,
106
+ displayName: { value: serviceName },
107
+ startTime,
108
+ endTime
109
+ };
110
+ if (parentSpanId) requestBody.parentSpanId = parentSpanId;
102
111
  await trace.projects.traces.spans.createSpan({
103
112
  name: projectTraceSpanName,
104
- requestBody: {
105
- attributes,
106
- name: projectTraceSpanName,
107
- spanId,
108
- displayName: { value: serviceName },
109
- startTime,
110
- endTime
111
- }
113
+ requestBody
112
114
  });
113
115
 
114
116
  }
@@ -1,5 +1,5 @@
1
1
  import minimist from "minimist";
2
- import { loadConfiguration } from "../load-configuration.js";
2
+ import { loadConfiguration } from "../configuration/load-configuration.js"
3
3
 
4
4
  export async function main(commands) {
5
5
 
File without changes