h1v3 0.13.0 → 0.15.0
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/README.md
CHANGED
|
@@ -194,6 +194,12 @@ export const team_membership = configureEventStore(h1v3.teamMembership.store);
|
|
|
194
194
|
export const team_details = configureEventStore(h1v3.teamDetails.store, teamDetailsSchema);
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
I usually put these in a file called "h1v3.config.js" and then remember to export these from my top-level index.js (as shown above):
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
export * from "h1v3.config.js";
|
|
201
|
+
```
|
|
202
|
+
|
|
197
203
|
### Defining schemas
|
|
198
204
|
|
|
199
205
|
Schemas for the two built-in event stores user_profile and team_details expect a schema defined using a javascript prototype as shown below. Note that there is no required constraint. Instead the schema is a white-list of keys along with the expected type for the associated value.
|
|
@@ -274,6 +280,13 @@ function buildEmulated() {
|
|
|
274
280
|
}
|
|
275
281
|
```
|
|
276
282
|
|
|
283
|
+
## Database rules
|
|
284
|
+
|
|
285
|
+
Once you have defined your configuration (`h1v3.config.js`) you can generate your databse.rules.json file:
|
|
286
|
+
```sh
|
|
287
|
+
cd functions
|
|
288
|
+
npx h1v3 rules --output ../database.rules.json
|
|
289
|
+
```
|
|
277
290
|
|
|
278
291
|
|
|
279
292
|
|
|
@@ -42,7 +42,7 @@ export async function inject({ database, authentication, metaURL, meta, handleEr
|
|
|
42
42
|
|
|
43
43
|
const userTeamsStore = userTeamsStoreFactory({ uid });
|
|
44
44
|
const userProfileStore = userProfileStoreFactory({ uid });
|
|
45
|
-
const client = user({ userTeamsStore, userProfileStore });
|
|
45
|
+
const client = user({ uid, userTeamsStore, userProfileStore, teamDetailsStoreFactory, teamMembershipStoreFactory });
|
|
46
46
|
return makeDisposable(client, [userTeamsStore, userProfileStore]);
|
|
47
47
|
|
|
48
48
|
},
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export async function resolveOrTimeout(tryStrategy, pollingDelay = 300) {
|
|
2
|
+
|
|
3
|
+
let isPolling = true;
|
|
4
|
+
|
|
5
|
+
async function poll() {
|
|
6
|
+
|
|
7
|
+
while (true) {
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
|
|
11
|
+
return await tryStrategy();
|
|
12
|
+
|
|
13
|
+
} catch (err) {
|
|
14
|
+
|
|
15
|
+
// polling failed - wait a bit before retrying
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, pollingDelay));
|
|
17
|
+
if (!isPolling) return undefined;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
|
|
27
|
+
return await Promise.race([
|
|
28
|
+
poll(),
|
|
29
|
+
timeout("Timed out waiting for creation")
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
} finally {
|
|
33
|
+
|
|
34
|
+
isPolling = false;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function timeout(message, timeout = 5000) {
|
|
43
|
+
|
|
44
|
+
await new Promise((_resolve, reject) => setTimeout(() => reject(message), timeout));
|
|
45
|
+
|
|
46
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import { rand62 } from "../id.js";
|
|
2
|
+
import { resolveOrTimeout } from "./promise.js";
|
|
3
|
+
|
|
1
4
|
export const DETAILS_UPDATED = "DETAILS_UPDATED";
|
|
2
5
|
|
|
3
|
-
export function user({ userTeamsStore, userProfileStore }) {
|
|
6
|
+
export function user({ uid, userTeamsStore, userProfileStore, teamDetailsStoreFactory, teamMembershipStoreFactory }) {
|
|
4
7
|
|
|
8
|
+
if (!uid) throw new Error("Missing uid");
|
|
5
9
|
if (!userTeamsStore) throw new Error("Missing userTeamsStore");
|
|
6
10
|
if (!userProfileStore) throw new Error("Missing userProfileStore");
|
|
11
|
+
if (!teamDetailsStoreFactory) throw new Error("Missing teamDetailsStoreFactory");
|
|
12
|
+
if (!teamMembershipStoreFactory) throw new Error("Missing teamMembershipStoreFactory");
|
|
7
13
|
|
|
8
14
|
userTeamsStore.onProjectionValue("current", handleTeamsCurrentChanged);
|
|
9
15
|
|
|
@@ -35,6 +41,11 @@ export function user({ userTeamsStore, userProfileStore }) {
|
|
|
35
41
|
|
|
36
42
|
},
|
|
37
43
|
|
|
44
|
+
async createTeam({ team: { id, name }, user: { displayName } = {} }, progressCallback) {
|
|
45
|
+
|
|
46
|
+
return await createTeam({ team: { id, name }, user: { displayName } }, progressCallback);
|
|
47
|
+
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
}
|
|
40
51
|
|
|
@@ -52,12 +63,74 @@ export function user({ userTeamsStore, userProfileStore }) {
|
|
|
52
63
|
const val = myTeamsSnap.val();
|
|
53
64
|
const teams = val?.teams;
|
|
54
65
|
if (!teams) return null;
|
|
55
|
-
return Object.entries(teams).map(([key, val]) =>
|
|
56
|
-
|
|
57
|
-
name
|
|
58
|
-
|
|
66
|
+
return await Promise.all(Object.entries(teams).map(async ([key, val]) => {
|
|
67
|
+
|
|
68
|
+
const { name } = await fetchTeamDetails(teamDetailsStoreFactory, key);
|
|
69
|
+
return {
|
|
70
|
+
id: key,
|
|
71
|
+
name,
|
|
72
|
+
since: new Date(val)
|
|
73
|
+
};
|
|
74
|
+
|
|
59
75
|
}));
|
|
60
76
|
|
|
61
77
|
}
|
|
62
78
|
|
|
79
|
+
async function createTeam({ team: { id, name }, user: { displayName } = {} }, progressCallback = () => {}) {
|
|
80
|
+
|
|
81
|
+
const progress = {
|
|
82
|
+
createTeam: false,
|
|
83
|
+
ensureAccess: false,
|
|
84
|
+
addToTeam: false,
|
|
85
|
+
setTeamName: false
|
|
86
|
+
}
|
|
87
|
+
const tid = id || rand62(22);
|
|
88
|
+
const teamMembershipStore = teamMembershipStoreFactory({ tid });
|
|
89
|
+
const teamDetailsStore = teamDetailsStoreFactory({ tid });
|
|
90
|
+
try {
|
|
91
|
+
|
|
92
|
+
const accessibleMembers = resolveOrTimeout(() =>
|
|
93
|
+
teamMembershipStore.getProjection("members"));
|
|
94
|
+
// create the access
|
|
95
|
+
const data = { uid };
|
|
96
|
+
if (displayName) data.name = displayName;
|
|
97
|
+
await teamMembershipStore.record("CREATED_TEAM", data);
|
|
98
|
+
progress.createTeam = true;
|
|
99
|
+
progressCallback(progress);
|
|
100
|
+
|
|
101
|
+
// wait for (admin) access
|
|
102
|
+
await accessibleMembers;
|
|
103
|
+
progress.ensureAccess = true;
|
|
104
|
+
progressCallback(progress);
|
|
105
|
+
|
|
106
|
+
// once we get access - record access...
|
|
107
|
+
await userTeamsStore.record("ADDED_TO_TEAM", { tid });
|
|
108
|
+
progress.addToTeam = true;
|
|
109
|
+
progressCallback(progress);
|
|
110
|
+
|
|
111
|
+
// ...and update team details
|
|
112
|
+
await teamDetailsStore.record("DETAILS_UPDATED", { name });
|
|
113
|
+
progress.setTeamName = true;
|
|
114
|
+
progressCallback(progress);
|
|
115
|
+
|
|
116
|
+
} finally {
|
|
117
|
+
|
|
118
|
+
teamMembershipStore.dispose();
|
|
119
|
+
teamDetailsStore.dispose();
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
return tid;
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function fetchTeamDetails(teamDetailsStoreFactory, key) {
|
|
129
|
+
|
|
130
|
+
const teamDetailsStore = teamDetailsStoreFactory({ tid: key });
|
|
131
|
+
const details = await teamDetailsStore.getProjection("current");
|
|
132
|
+
const name = details?.name || key;
|
|
133
|
+
return { name };
|
|
134
|
+
|
|
63
135
|
}
|
|
136
|
+
|
package/package.json
CHANGED