h1v3 0.4.1 → 0.6.1

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 (77) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/dist/browser/client/auth.js +128 -0
  3. package/dist/browser/client/bus.js +1 -0
  4. package/{src/client/web-ui → dist/browser/client}/components/login.js +11 -2
  5. package/dist/browser/client/components/notification.html.js +25 -0
  6. package/dist/browser/client/components/notification.js +27 -0
  7. package/{src/client/web-ui → dist/browser/client}/components/partials/wa-utils.js +1 -0
  8. package/dist/browser/client/context.js +98 -0
  9. package/dist/browser/{event-store/modular.js → client/event-store.js} +22 -14
  10. package/dist/browser/{web → client}/events.js +5 -1
  11. package/{src/client/web/system.js → dist/browser/client/firebase.js} +1 -7
  12. package/dist/browser/client/id.js +37 -0
  13. package/dist/browser/client/logger.js +60 -0
  14. package/dist/browser/client/notifications.js +50 -0
  15. package/dist/browser/{web-ui → client}/system.js +10 -4
  16. package/dist/browser/client/team/invites.js +119 -0
  17. package/dist/browser/client/team/team.js +143 -0
  18. package/dist/browser/client/team/user.js +62 -0
  19. package/package.json +2 -7
  20. package/src/client/context.js +58 -0
  21. package/src/client/index.js +2 -4
  22. package/src/client/node.js +2 -2
  23. package/src/commands/generate-rules.js +56 -39
  24. package/src/commands/vendor.js +86 -23
  25. package/src/configuration.js +116 -0
  26. package/src/event-store/configuration.js +44 -0
  27. package/src/event-store/projections.js +40 -11
  28. package/src/exec-eventstore.js +48 -14
  29. package/src/index.js +20 -4
  30. package/src/load-configuration.js +17 -3
  31. package/src/membership/index.js +5 -3
  32. package/src/membership/team-details/events.js +5 -0
  33. package/src/membership/team-details/projections/current.js +9 -0
  34. package/src/membership/team-details/store.js +30 -0
  35. package/src/membership/team-details/verify-store-paths.js +8 -0
  36. package/src/membership/team-membership/events.js +17 -0
  37. package/src/membership/team-membership/projections/_shared.js +55 -0
  38. package/src/membership/team-membership/projections/details.js +105 -0
  39. package/src/membership/team-membership/projections/inviteToEmail.js +25 -0
  40. package/src/membership/team-membership/projections/members.js +68 -0
  41. package/src/membership/team-membership/store.js +56 -0
  42. package/src/membership/user-profile/events.js +6 -0
  43. package/src/membership/user-profile/projections/current.js +9 -0
  44. package/src/membership/user-profile/store.js +28 -0
  45. package/src/membership/user-teams/events.js +5 -0
  46. package/src/membership/{user-teams-store.js → user-teams/projections/current.js} +2 -24
  47. package/src/membership/user-teams/store.js +27 -0
  48. package/src/membership/userInvites/events.js +12 -0
  49. package/src/membership/userInvites/projections/pending.js +45 -0
  50. package/src/membership/userInvites/store.js +46 -0
  51. package/src/paths.js +5 -0
  52. package/src/rules.js +153 -0
  53. package/src/schema.js +91 -1
  54. package/src/system/main.js +1 -1
  55. package/dist/browser/team/team.js +0 -25
  56. package/dist/browser/web/login.js +0 -48
  57. package/dist/browser/web/system.js +0 -67
  58. package/dist/browser/web-ui/components/login.js +0 -74
  59. package/dist/browser/web-ui/components/notification.html.js +0 -12
  60. package/dist/browser/web-ui/components/notification.js +0 -25
  61. package/dist/browser/web-ui/components/partials/wa-utils.js +0 -17
  62. package/dist/browser/web-ui/errors.js +0 -23
  63. package/scripts/dist-client.js +0 -32
  64. package/src/client/modular.js +0 -81
  65. package/src/client/team-node.js +0 -28
  66. package/src/client/team.js +0 -38
  67. package/src/client/web/events.js +0 -7
  68. package/src/client/web/login.js +0 -48
  69. package/src/client/web-ui/components/login.html.js +0 -44
  70. package/src/client/web-ui/components/notification.html.js +0 -12
  71. package/src/client/web-ui/components/notification.js +0 -25
  72. package/src/client/web-ui/errors.js +0 -23
  73. package/src/client/web-ui/system.js +0 -20
  74. package/src/event-store/initialise.js +0 -28
  75. package/src/membership/membership-store.js +0 -131
  76. package/src/membership/user-profile-store.js +0 -42
  77. /package/dist/browser/{web-ui → client}/components/login.html.js +0 -0
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
- import { dirname, join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
 
5
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -12,13 +13,11 @@ async function readPackageJSON() {
12
13
 
13
14
  async function vendor(subpath) {
14
15
 
15
- const { version, name } = await readPackageJSON();
16
16
  const src = join(__dirname, "..", "..", "dist", "browser", subpath);;
17
17
 
18
-
19
18
  // Destination: include semver
20
- const versionedName = `${name}@${version}`;
21
- const dest =join(".", "public", "vendor", versionedName, subpath);
19
+ const versionedName = await versionedPackageName();
20
+ const dest =join(destVendorPath(), versionedName, subpath);
22
21
 
23
22
  // Recursive copy
24
23
  await fs.cp(src, dest, { recursive: true });
@@ -26,35 +25,99 @@ async function vendor(subpath) {
26
25
 
27
26
  }
28
27
 
29
- export async function eventStore(_argv) {
28
+ async function versionedPackageName() {
29
+
30
+ const { version, name } = await readPackageJSON();
31
+ const versionedName = `${name}@${version}`;
32
+ return versionedName;
33
+
34
+ }
35
+
36
+ function destVendorPath() {
37
+
38
+ return join(process.cwd(), "public", "vendor");
39
+
40
+ }
41
+
42
+ // export async function eventStore(_argv) {
43
+
44
+ // await vendor("event-store");
45
+
46
+ // }
47
+
48
+ // export async function webPlatform(_argv) {
30
49
 
31
- await vendor("event-store");
50
+ // await vendor("web");
51
+
52
+ // }
53
+
54
+ export async function deps(_argv) {
55
+
56
+ // lit
57
+ const dest = join(destVendorPath(), "lit@3.3.1", "dist");
58
+ const destFile = join(dest, "lit-core.min.js");
59
+ if (existsSync(destFile)) {
60
+
61
+ console.log("Skipping", destFile);
62
+
63
+ } else {
64
+
65
+ const resp = await fetch("https://cdn.jsdelivr.net/gh/lit/dist@3.3.1/core/lit-core.min.js")
66
+ const lit = await resp.text();
67
+ await fs.mkdir(dest, { recursive: true });
68
+ await fs.writeFile(destFile, lit);
69
+ console.log("Vendored", destFile);
70
+
71
+ }
32
72
 
33
73
  }
34
74
 
35
- export async function webPlatform(_argv) {
75
+ export async function client(_argv) {
36
76
 
37
- await vendor("web");
77
+ await vendor("client");
38
78
 
39
79
  }
40
80
 
41
- export async function webPlatformUI(_argv) {
81
+ // export async function webPlatformUI(_argv) {
82
+
83
+ // await webPlatform(_argv);
84
+ // await vendor("web-ui");
85
+ // const resp = await fetch("https://cdn.jsdelivr.net/gh/lit/dist@3.3.1/core/lit-core.min.js")
86
+ // const lit = await resp.text();
87
+ // const dest = join(".", "public", "vendor", "lit@3.3.1", "dist");
88
+ // await fs.mkdir(dest, { recursive: true });
89
+ // await fs.writeFile(
90
+ // join(".", "public", "vendor", "lit@3.3.1", "dist", "lit-core.min.js"),
91
+ // lit
92
+ // );
42
93
 
43
- await webPlatform(_argv);
44
- await vendor("web-ui");
45
- const resp = await fetch("https://cdn.jsdelivr.net/gh/lit/dist@3.3.1/core/lit-core.min.js")
46
- const lit = await resp.text();
47
- const dest = join(".", "public", "vendor", "lit@3.3.1", "dist");
48
- await fs.mkdir(dest, { recursive: true });
49
- await fs.writeFile(
50
- join(".", "public", "vendor", "lit@3.3.1", "dist", "lit-core.min.js"),
51
- lit
52
- );
94
+ // }
95
+
96
+ // export async function membership(_argv) {
97
+
98
+ // await eventStore(_argv);
99
+ // await vendor("team");
100
+
101
+ // }
102
+
103
+ export async function symLinkLatest(_argv) {
104
+
105
+ const vendorPath = destVendorPath();
106
+ const versionedName = await versionedPackageName();
107
+
108
+ const path = join(vendorPath, "h1v3@latest");
109
+ const target = join(vendorPath, versionedName);
110
+ await fs.unlink(path);
111
+ await fs.symlink(versionedName, path, "dir");
112
+
113
+ console.log("Symlinked", versionedName, "to", path);
53
114
 
54
115
  }
55
116
 
56
- export async function membership(_argv) {
117
+ export async function all(argv) {
118
+
119
+ await deps(argv);
120
+ await client(argv);
121
+ await symLinkLatest(argv)
57
122
 
58
- await eventStore(_argv);
59
- await vendor("team");
60
123
  }
@@ -0,0 +1,116 @@
1
+ import { refPathToTriggerPath } from "./paths.js";
2
+ import { all, assertNewDataDoesNotHave, assertNewDataHasString, any, assertNewDataHasNumber, assertNewDataHasOneOf, assertNewDataHasLowercaseHexCharacters, assertNewDataFieldDoesNotMatch } from "./rules.js";
3
+
4
+ export const RAW_STORE_META = Symbol("Raw database metadata");
5
+
6
+ export function configureConfigStore(paths, _onValueWritten, _logger) {
7
+
8
+ if (!paths?.config?.path) throw new Error("Missing: paths.config.path");
9
+ return {
10
+ [RAW_STORE_META]: {
11
+ raw: true,
12
+ ref: paths.config.path,
13
+ read: true,
14
+ write: false
15
+ }
16
+ };
17
+
18
+ }
19
+
20
+ function decideSeverity(newData) {
21
+
22
+ if (!newData.severity_text) return "INFO";
23
+ return newData.severity_text.toUpperCase();
24
+
25
+ }
26
+
27
+ function decideMessage(newData) {
28
+
29
+ return newData?.body || "Log message without body";
30
+
31
+ }
32
+
33
+ function handleLogValueWritten(logger, e) {
34
+
35
+ const { after } = e.data;
36
+ const newData = after.val();
37
+ if (!newData.time_server_ms) {
38
+
39
+ const now = new Date();
40
+ const time_server_ms = now.valueOf();
41
+ const timestamp = now.toISOString();
42
+ const payload = {
43
+ "logging.googleapis.com/trace": `projects/${process.env.GCLOUD_PROJECT}/traces/${newData.trace_id}`,
44
+ "logging.googleapis.com/spanId": newData.span_id,
45
+ "message": decideMessage(newData),
46
+ "severity": decideSeverity(newData),
47
+ "raw": {
48
+ ...newData,
49
+ time_server_ms
50
+ },
51
+ timestamp
52
+ };
53
+ console.log("Writing log", payload);
54
+ logger.write(payload);
55
+ after.ref.update({ time_server_ms });
56
+
57
+ }
58
+
59
+ }
60
+
61
+ export function configureLoggingStore({ paths, onValueWritten, logger, region, instance }) {
62
+
63
+ if (!paths) throw new Error("Missing paths");
64
+ if (!onValueWritten) throw new Error("Missing onValueWritten");
65
+ if (!logger) throw new Error("Missing logger");
66
+ if (!region) throw new Error("Missing region");
67
+ if (!instance) throw new Error("Missing instance");
68
+
69
+ const ref = paths?.logs?.path;
70
+ if (!ref) throw new Error("Missing: paths.logs.path");
71
+ const triggerPath = refPathToTriggerPath(ref);
72
+ return Object.assign(
73
+ onValueWritten({
74
+ ref: triggerPath,
75
+ region,
76
+ instance
77
+ }, handleLogValueWritten.bind(this, logger)),
78
+ {
79
+ [RAW_STORE_META]: {
80
+ raw: true,
81
+ ref,
82
+ read: false,
83
+ write: all(
84
+ // required
85
+ assertNewDataHasString("body", 500, 1),
86
+ assertNewDataHasOneOf("severity_text", "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"),
87
+ // trace context
88
+ all(
89
+ assertNewDataHasLowercaseHexCharacters("trace_id", 32),
90
+ assertNewDataFieldDoesNotMatch("trace_id", "0*")
91
+ ),
92
+ all(
93
+ assertNewDataHasLowercaseHexCharacters("span_id", 16),
94
+ assertNewDataFieldDoesNotMatch("span_id", "0*")
95
+ ),
96
+ // forbid server time faking
97
+ assertNewDataDoesNotHave("time_server_ms"),
98
+ // optional bits
99
+ any(
100
+ assertNewDataDoesNotHave("time_client_ms"),
101
+ assertNewDataHasNumber("time_client_ms")
102
+ ),
103
+ any(
104
+ assertNewDataDoesNotHave("parent_id"),
105
+ all(
106
+ assertNewDataHasLowercaseHexCharacters("parent_id", 16),
107
+ assertNewDataFieldDoesNotMatch("parent_id", "0*")
108
+ )
109
+ )
110
+ )
111
+ }
112
+ }
113
+ );
114
+
115
+ }
116
+
@@ -0,0 +1,44 @@
1
+ import { EVENTS } from "../../dist/browser/client/event-store.js";
2
+ import { updateProjections } from "./projections.js";
3
+ import { refPathToTriggerPath } from "../paths.js";
4
+
5
+ export const EVENT_STORE_META = Symbol("Event store configuration metadata");
6
+
7
+ export function configureEventStore({ ref, projections, ...rest }, { onValueWritten, logger, region, instance }) {
8
+
9
+ if (!onValueWritten) throw new Error("Missing onValueWritten");
10
+ if (!logger) throw new Error("Missing logger");
11
+ if (!region) throw new Error("Missing region");
12
+ if (!instance) throw new Error("Missing instance");
13
+
14
+ // considered the "home" of the event store, under which events and projections will be stored
15
+ const eventStorePath = refPathToTriggerPath(ref);
16
+
17
+ // the path which will trigger event store projections
18
+ const writeRefPath = `${eventStorePath}/${EVENTS}/{eid}`;
19
+
20
+ // handle an incoming write event
21
+ const handler = e => updateProjections(e.data.after, projections, logger, rest.debug);
22
+
23
+ const metadata = {
24
+ ...rest,
25
+ ref,
26
+ triggerPath: writeRefPath,
27
+ projections: Object.keys(projections)
28
+ };
29
+
30
+ // tag the trigger with metadata
31
+ return Object.assign(
32
+ onValueWritten({ ref: writeRefPath, region, instance }, handler),
33
+ { [EVENT_STORE_META]: metadata }
34
+ );
35
+
36
+ }
37
+
38
+ configureEventStore.inject = ({ onValueWritten, logger, paths, region, instance }) =>
39
+
40
+ (eventStoreFactory, ...args) =>
41
+
42
+ configureEventStore(eventStoreFactory(paths, ...args), { onValueWritten, logger, region, instance });
43
+
44
+
@@ -1,4 +1,7 @@
1
- export async function updateProjections(incomingEventSnap, projectionTransformationPerEvent, logger) {
1
+ import { PROJECTIONS } from "../../dist/browser/client/event-store.js";
2
+ import { inspect } from "node:util";
3
+
4
+ export async function updateProjections(incomingEventSnap, projectionTransformationPerEvent, logger, debug = false) {
2
5
 
3
6
  // order the events
4
7
  const eventsSnap = await incomingEventSnap.ref.parent.get();
@@ -9,29 +12,55 @@ export async function updateProjections(incomingEventSnap, projectionTransformat
9
12
  for(const [key, transformations] of projections) {
10
13
 
11
14
  const fallbackTransformation = transformations["?"];
15
+ const trace = [key];
16
+ const context = {
17
+ projection: key
18
+ };
12
19
  const view = sortedEvents.reduce(
13
- (agg, e) => {
20
+ (agg, [eventKey, eventValue]) => {
14
21
 
15
- const transform = transformations[e[1]?.type] || fallbackTransformation || missingTransform;
16
- return transform(agg, e[1], e[0]);
22
+ context.eventId = eventKey;
23
+ if (debug) trace.push(agg, e);
24
+ const transform = transformations[eventValue?.type] || fallbackTransformation || missingTransformFor(key);
25
+ return transform(agg, eventValue, context);
17
26
 
18
27
  }, {});
28
+
29
+ if (debug) console.debug(...trace, view);
19
30
  await writeProjection(incomingEventSnap, key, view);
20
31
 
21
32
  }
22
33
 
23
- function missingTransform(agg, e) {
34
+ function missingTransformFor(projection) {
35
+
36
+ return (agg, e) => {
37
+
38
+ logger.warn("Missing transform for event", { projection, event: e });
39
+ return agg;
24
40
 
25
- logger.warn("Missing transformation for event", { event: e });
26
- return agg;
41
+ };
27
42
 
28
43
  }
29
44
 
30
- async function writeProjection(incomingEventSnap, key, view) {
45
+ async function writeProjection(incomingEventSnap, projection, view) {
46
+
47
+ const projectionRef = incomingEventSnap.ref.parent.parent.child(PROJECTIONS).child(projection);
48
+ const incomingEvent = incomingEventSnap.val();
49
+ logger.info("Writing projection after event", {
50
+ path: projectionRef,
51
+ incomingEvent
52
+ });
53
+ try {
54
+
55
+ await projectionRef.set(view);
56
+
57
+ } catch(err) {
58
+
59
+ const data = inspect(view).replace(/\n */g, " ");
60
+ const path = projectionRef.toString();
61
+ logger.error(`Error writing projection: ${err?.message}`, { projection, path, data });
31
62
 
32
- const projectionRef = incomingEventSnap.ref.parent.parent.child("projections").child(key);
33
- logger.info("Writing projection after event", { path: projectionRef, incomingEvent: incomingEventSnap.val() });
34
- await projectionRef.set(view);
63
+ }
35
64
 
36
65
  }
37
66
 
@@ -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";
@@ -35,37 +35,71 @@ main({
35
35
  strategy: generateRules
36
36
 
37
37
  },
38
- "vendor-eventstore": {
38
+ // "vendor-eventstore": {
39
39
 
40
- description: "Copy event store client into your browser assets folder",
40
+ // description: "Copy event store client into your browser assets folder",
41
+ // loadConfig: false,
42
+ // parameters: {},
43
+ // strategy: vendor.eventStore
44
+
45
+ // },
46
+ // "vendor-web": {
47
+
48
+ // description: "Copy web logic platform into your browser assets folder",
49
+ // loadConfig: false,
50
+ // parameters: {},
51
+ // strategy: vendor.webPlatform
52
+
53
+ // },
54
+ // "vendor-webui": {
55
+
56
+ // description: "Copy web UI platform into your browser assets folder (requires web)",
57
+ // loadConfig: false,
58
+ // parameters: {},
59
+ // strategy: vendor.webPlatformUI
60
+
61
+ // },
62
+ // "vendor-membership": {
63
+
64
+ // description: "Copy team management logic into your browser assets folder",
65
+ // loadConfig: false,
66
+ // parameters: {},
67
+ // strategy: vendor.membership
68
+
69
+ // },
70
+ "vendor-link": {
71
+
72
+ description: "Symlink latest vendored h1v3 assets as h1v3@latest",
41
73
  loadConfig: false,
42
74
  parameters: {},
43
- strategy: vendor.eventStore
75
+ strategy: vendor.symLinkLatest
44
76
 
45
77
  },
46
- "vendor-web": {
47
78
 
48
- description: "Copy web logic platform into your browser assets folder",
79
+ "vendor-client": {
80
+
81
+ description: "Copy all h1v3 logic into your browser assets folder",
49
82
  loadConfig: false,
50
83
  parameters: {},
51
- strategy: vendor.webPlatform
84
+ strategy: vendor.client
52
85
 
53
86
  },
54
- "vendor-webui": {
55
87
 
56
- description: "Copy web UI platform into your browser assets folder (requires web)",
88
+ "vendor-deps": {
89
+
90
+ description: "Copy all external dependencies for h1v3 into your browser assets folder",
57
91
  loadConfig: false,
58
92
  parameters: {},
59
- strategy: vendor.webPlatformUI
93
+ strategy: vendor.deps
60
94
 
61
95
  },
62
- "vendor-membership": {
63
96
 
64
- description: "Copy team management logic into your browser assets folder",
97
+ "vendor-all": {
98
+
99
+ description: "Copy all h1v3 logic into your browser assets folder",
65
100
  loadConfig: false,
66
101
  parameters: {},
67
- strategy: vendor.membership
68
-
102
+ strategy: vendor.all
69
103
  }
70
104
 
71
105
  });
package/src/index.js CHANGED
@@ -1,4 +1,8 @@
1
- export * from "./event-store/initialise.js";
1
+ export { PROJECTIONS, EVENTS } from "../dist/browser/client/event-store.js";
2
+
3
+ export * from "./event-store/configuration.js";
4
+
5
+ export * from "./configuration.js";
2
6
 
3
7
  export const passThroughView = {
4
8
  "?": (view, e, key) => {
@@ -10,9 +14,21 @@ export const passThroughView = {
10
14
  }
11
15
  };
12
16
 
13
- export function ifUserIdExists(path) {
17
+ export * as userProfile from "./membership/user-profile/store.js";
18
+
19
+ export * as teamMembership from "./membership/team-membership/store.js";
20
+
21
+ export * as teamDetails from "./membership/team-details/store.js";
22
+
23
+ export * as userTeams from "./membership/user-teams/store.js";
24
+
25
+ export * as userInvites from "./membership/userInvites/store.js";
26
+
27
+ export * as rules from "./rules.js";
28
+
29
+ export function explodePathVars(expr) {
14
30
 
15
- const expr = `root.child('${path}/' + auth.uid).exists()`;
16
- return expr.replaceAll(/(\$[^/]*)/g, "' + $1 + '")
31
+ if (typeof expr !== "string") return expr;
32
+ return expr.replaceAll(/\/(\$[^/]*)/g, "/' + $1 + '");
17
33
 
18
34
  }
@@ -1,6 +1,7 @@
1
1
  import { resolve } from "path";
2
2
  import { pathToFileURL } from "url";
3
- import { EVENT_STORE_META } from "./event-store/initialise.js";
3
+ import { EVENT_STORE_META } from "./event-store/configuration.js";
4
+ import { RAW_STORE_META } from "./configuration.js";
4
5
 
5
6
  export async function loadConfiguration(argv) {
6
7
 
@@ -8,7 +9,19 @@ export async function loadConfiguration(argv) {
8
9
  if (!argv.snippet)
9
10
  console.log("Loading configuration:", configPath);
10
11
  const module = await import(pathToFileURL(configPath));
11
- return readStoresMetadata(module);
12
+ return [
13
+ readStoresMetadata(module),
14
+ readRawMetadata(module)
15
+ ]
16
+
17
+ }
18
+
19
+ export function readRawMetadata(config) {
20
+
21
+ return Object.entries(config)
22
+ .map(([k, v]) => [k, v?.[RAW_STORE_META]])
23
+ .filter(x => x[1])
24
+ ;
12
25
 
13
26
  }
14
27
 
@@ -16,6 +29,7 @@ export function readStoresMetadata(config) {
16
29
 
17
30
  return Object.entries(config)
18
31
  .map(([k, v]) => [k, v?.[EVENT_STORE_META]])
19
- .filter(x => x[1]);
32
+ .filter(x => x[1])
33
+ ;
20
34
 
21
35
  }
@@ -1,3 +1,5 @@
1
- export * as userProfile from "./user-profile-store.js";
2
- export * as membership from "./membership-store.js";
3
- export * as userTeams from "./user-teams-store.js";
1
+ export * as userProfile from "./user-profile/store.js";
2
+ export * as teamMembership from "./team-membership/store.js";
3
+ export * as teamDetails from "./team-details/store.js";
4
+ export * as userTeams from "./user-teams/store.js";
5
+ export * as userInvites from "./userInvites/store.js";
@@ -0,0 +1,5 @@
1
+ export const DETAILS_UPDATED = "DETAILS_UPDATED";
2
+
3
+ export const eventTypes = [
4
+ DETAILS_UPDATED
5
+ ];
@@ -0,0 +1,9 @@
1
+ import { DETAILS_UPDATED } from "../events.js";
2
+ import { patchViewWithSchema } from "../../../schema.js";
3
+
4
+ export default schema => ({
5
+
6
+ [DETAILS_UPDATED]: (view, { payload }) =>
7
+ patchViewWithSchema({ view, payload, schema })
8
+
9
+ });
@@ -0,0 +1,30 @@
1
+ import { assertIsMyTeam } from "../../rules.js";
2
+ import { eventTypes } from "./events.js";
3
+ import current from "./projections/current.js";
4
+ import { verifyStorePaths } from "./verify-store-paths.js";
5
+
6
+
7
+ export function store(paths, schema) {
8
+
9
+ verifyStorePaths(paths, {
10
+ team: {
11
+ membership: { path: String },
12
+ details: { path: String }
13
+ }
14
+ });
15
+
16
+ if (!schema) throw new Error("Missing schema");
17
+ return {
18
+ ref: paths.team.details.path,
19
+ projections: {
20
+ current: current(schema)
21
+ },
22
+ write: false,
23
+ read: assertIsMyTeam(paths),
24
+ eventTypes
25
+ };
26
+
27
+ }
28
+
29
+ export * from "./events.js";
30
+
@@ -0,0 +1,8 @@
1
+ import { checkSchema } from "../../schema.js";
2
+
3
+ export function verifyStorePaths(paths, schema) {
4
+
5
+ const { valid, errors } = checkSchema(schema, paths, { allowUnknownKeys: true, requireAllKeys: true });
6
+ if (!valid) throw new Error(`Store paths:\n - ${errors.join("\n - ")}`);
7
+
8
+ }
@@ -0,0 +1,17 @@
1
+ export const BECAME_MEMBER = "BECAME_MEMBER";
2
+ export const BECAME_ADMIN = "BECAME_ADMIN";
3
+ export const LEFT_TEAM = "LEFT_TEAM";
4
+ export const MEMBER_INVITED = "MEMBER_INVITED";
5
+ export const ADMIN_INVITED = "ADMIN_INVITED";
6
+ export const INVITE_ACCEPTED = "INVITE_ACCEPTED";
7
+ export const INVITE_REJECTED = "INVITE_REJECTED";
8
+
9
+ export const eventTypes = [
10
+ BECAME_MEMBER,
11
+ BECAME_ADMIN,
12
+ LEFT_TEAM,
13
+ MEMBER_INVITED,
14
+ ADMIN_INVITED,
15
+ INVITE_ACCEPTED,
16
+ INVITE_REJECTED
17
+ ];
@@ -0,0 +1,55 @@
1
+
2
+ export function resolveInvitationView(payload, meta) {
3
+
4
+ return {
5
+ id: payload.id,
6
+ name: payload.name || "",
7
+ email: payload.email || "",
8
+ created: meta?.when || "",
9
+ createdBy: payload.createdBy || ""
10
+ };
11
+
12
+ }
13
+
14
+ export function resolveUserView({ name, email, createdBy }, found, meta) {
15
+
16
+ name = name || found?.name || "";
17
+ email = email || found?.email;
18
+ const view = {
19
+ name
20
+ };
21
+ if (meta?.when) view.updated = meta.when;
22
+ if (email) view.email = email;
23
+ if (createdBy) view.createdBy = createdBy;
24
+ return view;
25
+
26
+ }
27
+
28
+ export function purgeInvited(view, key) {
29
+
30
+ if (view.invitedMembers && (key in view.invitedMembers))
31
+ delete view.invitedMembers[key];
32
+ if (view.invitedAdmins && (key in view.invitedAdmins))
33
+ delete view.invitedAdmins[key];
34
+
35
+ }
36
+
37
+ export function purgeUser(view, { uid }) {
38
+
39
+ const found = view.members?.[uid] || view.admins?.[uid] || view.owners?.[uid];
40
+ if (view.members && (uid in view.members))
41
+ delete view.members[uid];
42
+ if (view.admins && (uid in view.admins))
43
+ delete view.admins[uid];
44
+ return found;
45
+
46
+ }
47
+
48
+ export const passThrough = x => x;
49
+ export function recordInviteInContext(context, id, data) {
50
+
51
+ context.invites = context.invites || {};
52
+ context.invites[id] = data;
53
+
54
+ }
55
+