h1v3 0.11.0 → 0.13.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 +13 -2
- package/package.json +1 -1
- package/src/commands/vendor.js +6 -52
- package/src/configuration/rules.js +4 -0
- package/src/event-store/projections.js +2 -0
- package/src/exec-eventstore.js +8 -2
- package/src/membership/team-details/store.js +2 -2
- package/src/membership/team-membership/events.js +2 -0
- package/src/membership/team-membership/projections/details.js +12 -1
- package/src/membership/team-membership/projections/members.js +10 -1
- package/src/membership/team-membership/store.js +10 -2
- package/src/package.js +32 -0
- package/src/system/main.js +5 -3
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# h1v3
|
|
2
2
|
A firebase web platform
|
|
3
3
|
|
|
4
|
-
|
|
5
4
|
# Install
|
|
6
5
|
|
|
7
6
|
```
|
|
@@ -87,7 +86,8 @@ e.g. firebase.json
|
|
|
87
86
|
{
|
|
88
87
|
"source": "/store_meta",
|
|
89
88
|
"function": {
|
|
90
|
-
"functionId": "store_meta"
|
|
89
|
+
"functionId": "store_meta",
|
|
90
|
+
"region": "europe-west1"
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
]
|
|
@@ -101,6 +101,17 @@ This allows you to, in the browser, do:
|
|
|
101
101
|
const meta = import("/store_meta") with { type: "json" }
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
> [!CAUTION]
|
|
105
|
+
> At time of writing, the firebase emulators don't properly support the use of RTDB and triggers outside of the us-central1 region.
|
|
106
|
+
> This is a problem if you are deploying to production based on a `firebase.json`.
|
|
107
|
+
|
|
108
|
+
> For that reason, you many need to use us-central1 as your "default" firebase.json entry, and replace it at deploy time.
|
|
109
|
+
> For example, using:
|
|
110
|
+
>
|
|
111
|
+
> `cat firebase.json | jq "(.hosting.rewrites.[] | select(.source == \"/store_meta\") | .function) += { region: \"europe-west1\" }" > firebase.json`.
|
|
112
|
+
>
|
|
113
|
+
>IKR
|
|
114
|
+
|
|
104
115
|
## Client-side libraries
|
|
105
116
|
|
|
106
117
|
You can vendor the (free) dependencies and client files for the library using npx
|
package/package.json
CHANGED
package/src/commands/vendor.js
CHANGED
|
@@ -1,35 +1,23 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, join
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import me from "../package.js";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
async function readPackageJSON() {
|
|
9
|
-
|
|
10
|
-
return JSON.parse(await fs.readFile(`${__dirname}/../../package.json`));
|
|
7
|
+
const packageName = `${me.name}@${me.version}`;
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
10
|
|
|
14
11
|
async function vendor(subpath) {
|
|
15
12
|
|
|
16
13
|
const src = join(__dirname, "..", "..", "dist", "browser", subpath);;
|
|
17
14
|
|
|
18
15
|
// Destination: include semver
|
|
19
|
-
const
|
|
20
|
-
const dest =join(destVendorPath(), versionedName, subpath);
|
|
16
|
+
const dest =join(destVendorPath(), packageName, subpath);
|
|
21
17
|
|
|
22
18
|
// Recursive copy
|
|
23
19
|
await fs.cp(src, dest, { recursive: true });
|
|
24
|
-
console.log(`Vendored ${
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function versionedPackageName() {
|
|
29
|
-
|
|
30
|
-
const { version, name } = await readPackageJSON();
|
|
31
|
-
const versionedName = `${name}@${version}`;
|
|
32
|
-
return versionedName;
|
|
20
|
+
console.log(`Vendored ${packageName}/${subpath} -> ${dest}`);
|
|
33
21
|
|
|
34
22
|
}
|
|
35
23
|
|
|
@@ -39,18 +27,6 @@ function destVendorPath() {
|
|
|
39
27
|
|
|
40
28
|
}
|
|
41
29
|
|
|
42
|
-
// export async function eventStore(_argv) {
|
|
43
|
-
|
|
44
|
-
// await vendor("event-store");
|
|
45
|
-
|
|
46
|
-
// }
|
|
47
|
-
|
|
48
|
-
// export async function webPlatform(_argv) {
|
|
49
|
-
|
|
50
|
-
// await vendor("web");
|
|
51
|
-
|
|
52
|
-
// }
|
|
53
|
-
|
|
54
30
|
export async function deps(_argv) {
|
|
55
31
|
|
|
56
32
|
// lit
|
|
@@ -78,28 +54,6 @@ export async function client(_argv) {
|
|
|
78
54
|
|
|
79
55
|
}
|
|
80
56
|
|
|
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
|
-
// );
|
|
93
|
-
|
|
94
|
-
// }
|
|
95
|
-
|
|
96
|
-
// export async function membership(_argv) {
|
|
97
|
-
|
|
98
|
-
// await eventStore(_argv);
|
|
99
|
-
// await vendor("team");
|
|
100
|
-
|
|
101
|
-
// }
|
|
102
|
-
|
|
103
57
|
export async function symLinkLatest(_argv) {
|
|
104
58
|
|
|
105
59
|
const vendorPath = destVendorPath();
|
|
@@ -28,6 +28,10 @@ export const teamInviteToEmailMapping = ({ team: { membership: { path } } }) =>
|
|
|
28
28
|
|
|
29
29
|
`root.child('${path}/${PROJECTIONS}/inviteToEmail')`;
|
|
30
30
|
|
|
31
|
+
export const assertTeamDoesNotExist = ({ team: { membership: { path } } }) =>
|
|
32
|
+
|
|
33
|
+
`!root.child('${path}').exists()`;
|
|
34
|
+
|
|
31
35
|
export const assertDoesNotAlreadyExist =
|
|
32
36
|
|
|
33
37
|
"!data.exists()";
|
|
@@ -16,9 +16,11 @@ export async function updateProjections(incomingEventSnap, projectionTransformat
|
|
|
16
16
|
const context = {
|
|
17
17
|
projection: key
|
|
18
18
|
};
|
|
19
|
+
let eventCount = 0;
|
|
19
20
|
const view = sortedEvents.reduce(
|
|
20
21
|
(agg, [eventKey, eventValue]) => {
|
|
21
22
|
|
|
23
|
+
context.eventCount = eventCount++;
|
|
22
24
|
context.eventId = eventKey;
|
|
23
25
|
if (debug) trace.push(agg, e);
|
|
24
26
|
const transform = transformations[eventValue?.type] || fallbackTransformation || missingTransformFor(key);
|
package/src/exec-eventstore.js
CHANGED
|
@@ -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";
|
|
@@ -31,7 +31,13 @@ main({
|
|
|
31
31
|
"rules": {
|
|
32
32
|
|
|
33
33
|
description: "Generate expected database rules JSON",
|
|
34
|
-
parameters:
|
|
34
|
+
parameters: {
|
|
35
|
+
...sharedParameters,
|
|
36
|
+
output: {
|
|
37
|
+
description: "path to write output",
|
|
38
|
+
examples: ["./myconfig.js"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
35
41
|
strategy: generateRules
|
|
36
42
|
|
|
37
43
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { assertIsMyTeam } from "../../configuration/rules.js";
|
|
1
|
+
import { assertIsMyTeam, assertMyRoleIsAdmin } 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";
|
|
@@ -19,7 +19,7 @@ export function store(paths, schema) {
|
|
|
19
19
|
projections: {
|
|
20
20
|
current: current(schema)
|
|
21
21
|
},
|
|
22
|
-
write:
|
|
22
|
+
write: assertMyRoleIsAdmin(paths),
|
|
23
23
|
read: assertIsMyTeam(paths),
|
|
24
24
|
eventTypes
|
|
25
25
|
};
|
|
@@ -5,10 +5,12 @@ export const MEMBER_INVITED = "MEMBER_INVITED";
|
|
|
5
5
|
export const ADMIN_INVITED = "ADMIN_INVITED";
|
|
6
6
|
export const INVITE_ACCEPTED = "INVITE_ACCEPTED";
|
|
7
7
|
export const INVITE_REJECTED = "INVITE_REJECTED";
|
|
8
|
+
export const CREATED_TEAM = "CREATED_TEAM";
|
|
8
9
|
|
|
9
10
|
export const eventTypes = [
|
|
10
11
|
BECAME_MEMBER,
|
|
11
12
|
BECAME_ADMIN,
|
|
13
|
+
CREATED_TEAM,
|
|
12
14
|
LEFT_TEAM,
|
|
13
15
|
MEMBER_INVITED,
|
|
14
16
|
ADMIN_INVITED,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { purgeUser, resolveUserView, purgeInvited, resolveInvitationView, recordInviteInContext } from "./_shared.js";
|
|
2
|
-
import { BECAME_MEMBER, BECAME_ADMIN, LEFT_TEAM, MEMBER_INVITED, ADMIN_INVITED, INVITE_ACCEPTED } from "../events.js";
|
|
2
|
+
import { BECAME_MEMBER, BECAME_ADMIN, LEFT_TEAM, MEMBER_INVITED, ADMIN_INVITED, INVITE_ACCEPTED, CREATED_TEAM } from "../events.js";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
|
|
@@ -23,6 +23,17 @@ export default {
|
|
|
23
23
|
|
|
24
24
|
},
|
|
25
25
|
|
|
26
|
+
[CREATED_TEAM]: (view, { meta, payload }, context) => {
|
|
27
|
+
|
|
28
|
+
if (context?.eventCount) return view; // must be the first event in the stream
|
|
29
|
+
if (!payload.uid) return view;
|
|
30
|
+
return ({
|
|
31
|
+
...view,
|
|
32
|
+
admins: { ...view.admins, [payload.uid]: resolveUser(view, payload, null, meta) }
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
},
|
|
36
|
+
|
|
26
37
|
[INVITE_ACCEPTED]: (view, { meta, payload }, context) => {
|
|
27
38
|
|
|
28
39
|
if (!payload?.id) return view;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BECAME_MEMBER, BECAME_ADMIN, LEFT_TEAM, MEMBER_INVITED, ADMIN_INVITED, INVITE_ACCEPTED } from "../events.js";
|
|
1
|
+
import { BECAME_MEMBER, BECAME_ADMIN, LEFT_TEAM, MEMBER_INVITED, ADMIN_INVITED, INVITE_ACCEPTED, CREATED_TEAM } from "../events.js";
|
|
2
2
|
import { recordInviteInContext } from "./_shared.js";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
@@ -19,6 +19,15 @@ export default {
|
|
|
19
19
|
|
|
20
20
|
},
|
|
21
21
|
|
|
22
|
+
[CREATED_TEAM]: (view, { payload }, context) => {
|
|
23
|
+
|
|
24
|
+
if (context?.eventCount) return view; // must be the first event in the stream
|
|
25
|
+
if (!payload.uid) return view;
|
|
26
|
+
view[payload.uid] = true;
|
|
27
|
+
return view;
|
|
28
|
+
|
|
29
|
+
},
|
|
30
|
+
|
|
22
31
|
[INVITE_ACCEPTED]: (view, { payload }, context) => {
|
|
23
32
|
|
|
24
33
|
if (!payload?.id) return view;
|
|
@@ -2,7 +2,7 @@ import details from "./projections/details.js";
|
|
|
2
2
|
import members from "./projections/members.js";
|
|
3
3
|
import inviteToEmail from "./projections/inviteToEmail.js";
|
|
4
4
|
|
|
5
|
-
import { eventTypes, INVITE_ACCEPTED, INVITE_REJECTED } from "./events.js";
|
|
5
|
+
import { CREATED_TEAM, eventTypes, INVITE_ACCEPTED, INVITE_REJECTED } from "./events.js";
|
|
6
6
|
import {
|
|
7
7
|
newDataPayloadId,
|
|
8
8
|
assertPayloadUidMyUserId,
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
teamInviteToEmailMapping,
|
|
13
13
|
any,
|
|
14
14
|
all,
|
|
15
|
-
assertEventIsOfType
|
|
15
|
+
assertEventIsOfType,
|
|
16
|
+
assertTeamDoesNotExist
|
|
16
17
|
} from "../../configuration/rules.js";
|
|
17
18
|
import { verifyStorePaths } from "../team-details/verify-store-paths.js";
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
export function store(paths) {
|
|
20
22
|
|
|
21
23
|
verifyStorePaths(paths, {
|
|
@@ -44,6 +46,12 @@ export function store(paths) {
|
|
|
44
46
|
|
|
45
47
|
)
|
|
46
48
|
|
|
49
|
+
),
|
|
50
|
+
all(
|
|
51
|
+
|
|
52
|
+
assertTeamDoesNotExist(paths),
|
|
53
|
+
assertEventIsOfType(CREATED_TEAM),
|
|
54
|
+
assertPayloadUidMyUserId
|
|
47
55
|
)
|
|
48
56
|
|
|
49
57
|
),
|
package/src/package.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
|
|
9
|
+
get version() {
|
|
10
|
+
|
|
11
|
+
return ensure().version;
|
|
12
|
+
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
get name() {
|
|
16
|
+
|
|
17
|
+
return ensure().name;
|
|
18
|
+
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ensure() {
|
|
24
|
+
|
|
25
|
+
if (!ensure.json) {
|
|
26
|
+
|
|
27
|
+
ensure.json = JSON.parse(readFileSync(`${__dirname}/../package.json`));
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
return ensure.json;
|
|
31
|
+
|
|
32
|
+
}
|
package/src/system/main.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import minimist from "minimist";
|
|
2
2
|
import { loadConfiguration } from "../configuration/load-configuration.js"
|
|
3
|
+
import me from "../package.js";
|
|
3
4
|
|
|
4
5
|
export async function main(commands) {
|
|
5
6
|
|
|
@@ -45,6 +46,7 @@ async function invoke(commands, verb, argv) {
|
|
|
45
46
|
|
|
46
47
|
function displayHelp(commands) {
|
|
47
48
|
|
|
49
|
+
console.log("Version", me.version);
|
|
48
50
|
console.log("Help:");
|
|
49
51
|
console.log("");
|
|
50
52
|
|
|
@@ -57,15 +59,15 @@ function displayHelp(commands) {
|
|
|
57
59
|
|
|
58
60
|
const maybeExamples = examples?.length ?examples.map(x => `--${name}="${x}"`).join(", ") : "";
|
|
59
61
|
console.log(`${indent}--${name}: ${description}`);
|
|
60
|
-
indent
|
|
62
|
+
let innerIndent = indent + " ".repeat(name.length + 4);
|
|
61
63
|
if (maybeExamples) {
|
|
62
64
|
|
|
63
|
-
console.log(`${
|
|
65
|
+
console.log(`${innerIndent}(e.g. ${maybeExamples})`);
|
|
64
66
|
|
|
65
67
|
}
|
|
66
68
|
if (defaultValue) {
|
|
67
69
|
|
|
68
|
-
console.log(`${
|
|
70
|
+
console.log(`${innerIndent}DEFAULT: ${defaultValue}`);
|
|
69
71
|
|
|
70
72
|
}
|
|
71
73
|
|