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
@@ -0,0 +1,119 @@
1
+ const ADDED_TO_TEAM = "ADDED_TO_TEAM";
2
+ const ACCEPTED_INVITATION = "ACCEPTED_INVITATION";
3
+ const REJECTED_INVITATION = "REJECTED_INVITATION";
4
+ const INVITE_ACCEPTED = "INVITE_ACCEPTED";
5
+ const INVITE_REJECTED = "INVITE_REJECTED";
6
+
7
+ export function userInvites({ email, uid, teamMembershipStoreFactory, userTeamsStore, userInvitesStore, handleErrors = console.error.bind(console) }) {
8
+
9
+ if (!email) throw new Error("Missing email");
10
+ if (!uid) throw new Error("Missing uid");
11
+ if (!teamMembershipStoreFactory) throw new Error("Missing teamMembershipStoreFactory");
12
+ if (!userInvitesStore) throw new Error("Missing userInvitesStore");
13
+ if (!userTeamsStore) throw new Error("Missing userTeamsStore");
14
+
15
+ let unsubscribe = userInvitesStore.onProjectionValue("pending", handleUserInvitesUpdated);
16
+ let lastUserInvites;
17
+ const onInvitesCallbacks = [];
18
+
19
+ return {
20
+
21
+ async getInvites() {
22
+
23
+ return userInvitesStore.getProjection("pending");
24
+
25
+ },
26
+
27
+ async rejectInvite({ id, name = "" }) {
28
+
29
+ try {
30
+
31
+ await respondToInvite({ id, name, invitesEvent: REJECTED_INVITATION, teamMembershipEvent: INVITE_REJECTED });
32
+
33
+ } catch(err) {
34
+
35
+ handleErrors(err);
36
+
37
+ }
38
+
39
+ },
40
+
41
+ async acceptInvite({ id, name = "" }) {
42
+
43
+ try {
44
+
45
+ const { tid } = await respondToInvite({ id, name, invitesEvent: ACCEPTED_INVITATION, teamMembershipEvent: INVITE_ACCEPTED });
46
+ await userTeamsStore.record(ADDED_TO_TEAM, { id, tid });
47
+
48
+ } catch(err) {
49
+
50
+ handleErrors(err);
51
+
52
+ }
53
+
54
+ },
55
+
56
+ onInvites(callback) {
57
+
58
+ onInvitesCallbacks.push(callback);
59
+ callback(lastUserInvites);
60
+
61
+ },
62
+
63
+ offInvites(callback) {
64
+
65
+ while(onInvitesCallbacks.includes(callback)) {
66
+
67
+ onInvitesCallbacks.splice(onInvitesCallbacks.indexOf(callback), 1);
68
+
69
+ }
70
+
71
+ },
72
+
73
+ dispose() {
74
+
75
+ if (!unsubscribe) return;
76
+ unsubscribe();
77
+ unsubscribe = null;
78
+
79
+ }
80
+
81
+ };
82
+
83
+ function handleUserInvitesUpdated(snap) {
84
+
85
+ lastUserInvites = snap.val();
86
+ dispatchInvitesCallbacks();
87
+
88
+ }
89
+
90
+ function dispatchInvitesCallbacks() {
91
+
92
+ onInvitesCallbacks.forEach(x => x(lastUserInvites));
93
+
94
+ }
95
+
96
+ async function respondToInvite({ id, name, invitesEvent, teamMembershipEvent }) {
97
+
98
+ const invitations = await userInvitesStore.getProjection("pending");
99
+ const invite = invitations.invites?.[id];
100
+ if (!invite) throw new Error("Invitation not found");
101
+
102
+ const { tid } = invite;
103
+ if (!tid) throw new Error("Event info invalid: missing tid");
104
+
105
+ const teamMembershipStore = teamMembershipStoreFactory({ tid });
106
+
107
+ // record acceptance/rejection in the user invites store - only if my email matches the accepted invite
108
+ await userInvitesStore.record(invitesEvent, { id });
109
+
110
+ // record acceptance in the team membership store - only if email matches inviteUser's email and uid matches me
111
+ await teamMembershipStore.record(teamMembershipEvent, { id, email, uid, name });
112
+
113
+ return { tid };
114
+
115
+ }
116
+
117
+ }
118
+
119
+
@@ -0,0 +1,143 @@
1
+ export const BECAME_MEMBER = "BECAME_MEMBER";
2
+ export const BECAME_ADMIN = "BECAME_ADMIN";
3
+ export const ADDED_TO_TEAM = "ADDED_TO_TEAM";
4
+ export const MEMBER_INVITED = "MEMBER_INVITED";
5
+ export const ADMIN_INVITED = "ADMIN_INVITED";
6
+ export const DETAILS_UPDATED = "DETAILS_UPDATED";
7
+
8
+ const notSupportedFactory = strategy => () => {
9
+
10
+ throw new Error(`Not supported ${strategy}`);
11
+
12
+ };
13
+
14
+ function bufferedSubscription(store, projectionName, handleError) {
15
+
16
+ store.onProjectionValue(projectionName, onChange, handleError);
17
+
18
+ const callbacks = [];
19
+ let lastProjectionValue;
20
+
21
+ function onChange(snap) {
22
+
23
+ lastProjectionValue = snap.val();
24
+ for(const callback of callbacks)
25
+ callback(lastProjectionValue);
26
+
27
+ }
28
+
29
+ return {
30
+
31
+ onChange(callback) {
32
+
33
+ callbacks.push(callback);
34
+ if(lastProjectionValue) callback(lastProjectionValue);
35
+
36
+ },
37
+
38
+ offChange(callback) {
39
+
40
+ while(callbacks.includes(callback)) {
41
+
42
+ callbacks.splice(callbacks.indexOf(callback), 1);
43
+
44
+ }
45
+
46
+ }
47
+
48
+ };
49
+
50
+ }
51
+
52
+ export function team({
53
+ teamMembershipStore,
54
+ userInvitesStoreFactory,
55
+ teamDetailsStore,
56
+ userTeamsStoreFactory = notSupportedFactory("Build user teams event store"),
57
+ handleError = console.error.bind(console)
58
+ }) {
59
+
60
+ if (!teamMembershipStore) throw new Error(`Missing teamMembershipStore`);
61
+ if (!teamDetailsStore) throw new Error(`Missing teamDetailsStore`);
62
+ if (!userInvitesStoreFactory) throw new Error(`Missing userInvitesStoreFactory`);
63
+
64
+ const teamPathPattern = /\/teams\/([^\/]*)/g.exec(teamMembershipStore?.path);
65
+ if (!teamPathPattern?.[1]) throw new Error(`Invalid team path: ${teamMembershipStore?.path}`);
66
+ const tid = teamPathPattern[1];
67
+
68
+ const { onChange: onMembershipDetails, offChange: offMembershipDetails } = bufferedSubscription(teamMembershipStore, "details", handleError);
69
+ const { onChange: onDetails, offChange: offDetails } = bufferedSubscription(teamDetailsStore, "current", handleError);
70
+
71
+ return {
72
+
73
+ async addAdmin({ uid, name, ...rest }) {
74
+
75
+ await recordMembership({ uid, name, eventType: BECAME_ADMIN, rest });
76
+
77
+ },
78
+
79
+ async addMember({ uid, name, ...rest }) {
80
+
81
+ await recordMembership({ uid, name, eventType: BECAME_MEMBER, rest });
82
+
83
+ },
84
+
85
+ onMembershipDetails,
86
+
87
+ offMembershipDetails,
88
+
89
+ onDetails,
90
+
91
+ offDetails,
92
+
93
+ async inviteMember({ email, name, createdBy }) {
94
+
95
+ if (!email) throw new Error("Missing email");
96
+ if (!createdBy) throw new Error("Missing createdBy");
97
+ await recordInvitation({ eventType: MEMBER_INVITED, email, name, createdBy, role: "Member" });
98
+
99
+ },
100
+
101
+ async inviteAdmin({ email, name, createdBy }) {
102
+
103
+ if (!email) throw new Error("Missing email");
104
+ if (!createdBy) throw new Error("Missing createdBy");
105
+ await recordInvitation({ eventType: ADMIN_INVITED, email, name, createdBy, role: "Administrator" });
106
+
107
+ },
108
+
109
+ async setDetails(details) {
110
+
111
+ await teamDetailsStore.record(DETAILS_UPDATED, details);
112
+
113
+ }
114
+
115
+ };
116
+
117
+ async function recordMembership({ uid, name, eventType, rest }) {
118
+
119
+ if (!uid) throw new Error("Missing uid");
120
+ if (!name) throw new Error("Missing name");
121
+ await teamMembershipStore.record(eventType, { uid, name, ...rest });
122
+ const userTeamsStore = userTeamsStoreFactory({ uid });
123
+ await userTeamsStore.record(ADDED_TO_TEAM, { tid });
124
+
125
+ }
126
+
127
+ async function recordInvitation({ eventType, email, name, createdBy, role }) {
128
+
129
+ if (!email) throw new Error("Missing email");
130
+ if (!createdBy) throw new Error("Missing createdBy");
131
+ if (!name) throw new Error("Missing name");
132
+ if (!role) throw new Error("Missing role");
133
+
134
+ const id = crypto.randomUUID().replaceAll("-", "");
135
+
136
+ const team = await teamDetailsStore.getProjection("current");
137
+ await teamMembershipStore.record(eventType, { email, name, createdBy, id });
138
+ const userInvitesStore = userInvitesStoreFactory({ emailId: btoa(email) });
139
+ await userInvitesStore.record("INVITED_TO_TEAM", { email, name, tid, role, team, id });
140
+
141
+ }
142
+
143
+ }
@@ -0,0 +1,62 @@
1
+ export const DETAILS_UPDATED = "DETAILS_UPDATED";
2
+
3
+ export function user({ userTeamsStore, userProfileStore }) {
4
+
5
+ if (!userTeamsStore) throw new Error("Missing userTeamsStore");
6
+ if (!userProfileStore) throw new Error("Missing userProfileStore");
7
+
8
+ userTeamsStore.onProjectionValue("current", handleTeamsCurrentChanged);
9
+
10
+ const onTeamsCallback = [];
11
+ let lastProjectionValue;
12
+
13
+ return {
14
+
15
+ async setDetails(details) {
16
+
17
+ await userProfileStore.record(DETAILS_UPDATED, details);
18
+
19
+ },
20
+
21
+ onTeams(callback) {
22
+
23
+ onTeamsCallback.push(callback);
24
+ if(lastProjectionValue) callback(lastProjectionValue);
25
+
26
+ },
27
+
28
+ offTeams(callback) {
29
+
30
+ while(onTeamsCallback.includes(callback)) {
31
+
32
+ onTeamsCallback.splice(onTeamsCallback.indexOf(callback), 1);
33
+
34
+ }
35
+
36
+ }
37
+
38
+ }
39
+
40
+ async function handleTeamsCurrentChanged(snap) {
41
+
42
+ lastProjectionValue = await enumerateTeams(snap);
43
+ for(const callback of onTeamsCallback)
44
+ callback(lastProjectionValue);
45
+
46
+ }
47
+
48
+
49
+ async function enumerateTeams(myTeamsSnap) {
50
+
51
+ const val = myTeamsSnap.val();
52
+ const teams = val?.teams;
53
+ if (!teams) return null;
54
+ return Object.entries(teams).map(([key, val]) => ({
55
+ id: key,
56
+ name: key,
57
+ since: new Date(val)
58
+ }));
59
+
60
+ }
61
+
62
+ }
package/package.json CHANGED
@@ -1,23 +1,18 @@
1
1
  {
2
2
  "name": "h1v3",
3
- "version": "0.4.1",
3
+ "version": "0.6.1",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "author": "",
7
7
  "type": "module",
8
8
  "exports": {
9
9
  ".": "./src/index.js",
10
- "./schema": "./src/schema.js",
11
- "./client": "./src/client/index.js",
12
- "./membership": "./src/membership/index.js"
10
+ "./client": "./src/client/index.js"
13
11
  },
14
12
  "bin": {
15
13
  "eventstore": "./src/exec-eventstore.js"
16
14
  },
17
15
  "dependencies": {
18
16
  "minimist": "^1.2.8"
19
- },
20
- "scripts": {
21
- "dist:client": "node ./scripts/dist-client.js"
22
17
  }
23
18
  }
@@ -0,0 +1,58 @@
1
+ import { eventStoreFromConfig as defaultEventStoreFromConfig } from "./node.js";
2
+ import { team } from "../../dist/browser/client/team/team.js";
3
+ import { user } from "../../dist/browser/client/team/user.js";
4
+
5
+ export function inject({
6
+ db,
7
+ teamMembershipConfig,
8
+ userTeamsConfig,
9
+ userInvitesConfig,
10
+ teamDetailsConfig,
11
+ userProfileConfig,
12
+ eventStoreFromConfig = defaultEventStoreFromConfig
13
+ }) {
14
+
15
+ if (!teamMembershipConfig) throw new Error("Missing: teamMembershipConfig");
16
+ if (!userInvitesConfig) throw new Error("Missing: userInvitesConfig");
17
+ if (!teamDetailsConfig) throw new Error("Missing: teamDetailsConfig");
18
+ if (!userProfileConfig) throw new Error("Missing: userProfileConfig");
19
+ if (!db) throw new Error("Missing: db");
20
+
21
+ return {
22
+
23
+ team({ tid }) {
24
+
25
+ const teamMembershipStore = eventStoreFromConfig(db, teamMembershipConfig, { tid });
26
+ const teamDetailsStore = eventStoreFromConfig(db, teamDetailsConfig, { tid });
27
+ return team({
28
+ teamMembershipStore,
29
+ teamDetailsStore,
30
+ userInvitesStoreFactory,
31
+ userTeamsStoreFactory,
32
+ });
33
+
34
+ },
35
+
36
+ userProfile({ uid }) {
37
+
38
+ const userTeamsStore = eventStoreFromConfig(db, userTeamsConfig, { uid });
39
+ const userProfileStore = eventStoreFromConfig(db, userProfileConfig, { uid });
40
+ return user({ userTeamsStore, userProfileStore });
41
+
42
+ }
43
+
44
+ };
45
+
46
+ function userTeamsStoreFactory({ uid }) {
47
+
48
+ return eventStoreFromConfig(db, userTeamsConfig, { uid });
49
+
50
+ }
51
+
52
+ function userInvitesStoreFactory({ emailId }) {
53
+
54
+ return eventStoreFromConfig(db, userInvitesConfig, { emailId });
55
+
56
+ }
57
+
58
+ }
@@ -1,4 +1,2 @@
1
- export * as node from "./node.js";
2
- export * as modular from "./modular.js";
3
- export * as teamNode from "./team-node.js";
4
- export * as team from "./team.js";
1
+ export * from "./node.js";
2
+ export * from "./context.js";
@@ -1,5 +1,5 @@
1
- import { EVENT_STORE_META } from "../event-store/initialise.js";
2
- import { eventStore as eventStoreModular } from "./modular.js";
1
+ import { EVENT_STORE_META } from "../event-store/configuration.js";
2
+ import { eventStore as eventStoreModular } from "../../dist/browser/client/event-store.js";
3
3
 
4
4
  function adapter(db) {
5
5
 
@@ -1,32 +1,56 @@
1
- function eventWriteConditions(config) {
1
+ import { EVENTS, PROJECTIONS } from "../../dist/browser/client/event-store.js";
2
+ import { explodePathVars } from "../index.js";
2
3
 
3
- if (config?.write === false)
4
- return false;
5
- let expr = "!data.exists()"
4
+ function writeConditions(config) {
5
+
6
+ if (config.write === false) return false;
7
+ let expr = "auth.uid != null && !data.exists()";
6
8
  if (config?.write)
7
- expr += " && (" + config.write + ")"
9
+ expr += " && (" + explodePathVars(config.write) + ")"
8
10
  return expr;
9
11
 
10
12
  }
11
13
 
12
- function projectionReadConditions(config) {
14
+ function readConditions(config) {
13
15
 
14
- return config.read || true;
16
+ return config.read === false
17
+ ? false
18
+ : config.read
19
+ ? explodePathVars(config.read)
20
+ : true;
15
21
 
16
22
  }
17
23
 
24
+ function eventValidation(config) {
25
+
26
+ const requireType = "newData.child('type').isString()";
27
+ if (!Array.isArray(config?.eventTypes)) return requireType;
28
+ return [
29
+ requireType,
30
+ `newData.child('type').val().matches(/^(${config.eventTypes.join("|")})$/)`
31
+ ].join(" && ");
32
+
33
+ }
34
+
35
+ const rawRules = config => ({
36
+ ".read": readConditions(config),
37
+ ".write": writeConditions(config)
38
+ });
39
+
18
40
  const eventStoreRules = config => ({
19
41
  ".read": false,
20
42
  ".write": false,
21
- events: {
22
- "$eid": {
23
- ".write": eventWriteConditions(config),
24
- ".validate": eventValidation(config)
25
- }
43
+ [EVENTS]: {
44
+ "$eid": config?.write === false
45
+ ? { ".write": false }
46
+ : {
47
+ ".write": writeConditions(config),
48
+ ".validate": eventValidation(config)
49
+ }
26
50
  },
27
- projections: {
51
+ [PROJECTIONS]: {
28
52
  "$pid": {
29
- ".read": projectionReadConditions(config)
53
+ ".read": readConditions(config)
30
54
  }
31
55
  }
32
56
  });
@@ -36,6 +60,11 @@ const splitPathIntoSegments = path =>
36
60
  .split("/")
37
61
  .filter(x => x);
38
62
 
63
+ const rules = config =>
64
+ config.raw
65
+ ? rawRules(config)
66
+ : eventStoreRules(config);
67
+
39
68
  const addRulesForPath = (existingRules, [
40
69
  [
41
70
  nextSegment,
@@ -55,7 +84,7 @@ const addRulesForPath = (existingRules, [
55
84
  )
56
85
  }
57
86
  // stick the rules here
58
- : eventStoreRules(config);
87
+ : rules(config);
59
88
 
60
89
  const parseConfig = ([_name, config]) => [
61
90
  // 0: the path
@@ -64,30 +93,18 @@ const parseConfig = ([_name, config]) => [
64
93
  config
65
94
  ];
66
95
 
67
- function eventValidation(config) {
68
-
69
- const requireType = "newData.child('type').isString()";
70
- if (!Array.isArray(config?.eventTypes)) return requireType;
71
- return [
72
- requireType,
73
- `newData.child('type').val().matches(/^(${config.eventTypes.join("|")})$/)`
74
- ].join(" && ");
75
-
76
- }
77
-
78
- export function generateRules(argv, stores) {
79
-
80
- const json = JSON.stringify(
81
- stores
82
- // parse the path
83
- .map(parseConfig)
84
- // build the map
85
- .reduce(addRulesForPath, {}),
86
- null, 4);
87
- if(argv.snippet)
88
- console.log(json.slice(1,-1));
89
- else
90
- console.log(json);
96
+ export function generateRules(_argv, eventStores, rawStores) {
97
+
98
+ console.log(
99
+ JSON.stringify(
100
+ {
101
+ rules: [
102
+ ...eventStores.map(parseConfig),
103
+ ...rawStores.map(parseConfig)
104
+ ].reduce(addRulesForPath, {})
105
+ },
106
+ null, 4)
107
+ );
91
108
 
92
109
  }
93
110