@protobase/server 0.62.4 → 0.63.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.
Files changed (61) hide show
  1. package/dist/better-auth/auth-settings.d.ts +4 -1
  2. package/dist/better-auth/create-auth.d.ts +14 -2
  3. package/dist/better-auth/create-auth.js +8 -0
  4. package/dist/better-auth/organizations/organization-role.js +13 -0
  5. package/dist/better-auth/status-route.js +8 -1
  6. package/dist/calls/call-hub.js +28 -7
  7. package/dist/calls/call-member.d.ts +5 -1
  8. package/dist/calls/call-reads.d.ts +7 -2
  9. package/dist/calls/call-reads.js +6 -4
  10. package/dist/calls/call-store.d.ts +7 -0
  11. package/dist/calls/call-store.js +35 -24
  12. package/dist/calls/call-sweep.js +8 -6
  13. package/dist/calls/calls-access.js +7 -2
  14. package/dist/calls/calls-runtime.d.ts +26 -5
  15. package/dist/calls/calls-runtime.js +84 -19
  16. package/dist/calls/calls-service.js +4 -2
  17. package/dist/calls/calls-sql.d.ts +1 -1
  18. package/dist/calls/calls-sql.js +56 -5
  19. package/dist/calls/calls-store.js +2 -1
  20. package/dist/calls/client-message.js +27 -2
  21. package/dist/calls/guest-checks-sql.d.ts +14 -0
  22. package/dist/calls/guest-checks-sql.js +43 -0
  23. package/dist/calls/guest-limits.js +49 -0
  24. package/dist/calls/guest-links-sql.d.ts +16 -0
  25. package/dist/calls/guest-links-sql.js +78 -0
  26. package/dist/calls/guest-lobby-sql.d.ts +17 -0
  27. package/dist/calls/guest-lobby-sql.js +81 -0
  28. package/dist/calls/guest-name.js +10 -0
  29. package/dist/calls/guest-routes.js +97 -0
  30. package/dist/calls/guest-socket.js +58 -0
  31. package/dist/calls/guest-store.d.ts +48 -0
  32. package/dist/calls/guest-store.js +20 -0
  33. package/dist/calls/guest-tokens.js +17 -0
  34. package/dist/calls/member-relay.js +63 -5
  35. package/dist/calls/organization-guests-route.js +38 -0
  36. package/dist/calls/record-organization.js +24 -0
  37. package/dist/create-admin.js +23 -2
  38. package/dist/functions/define-function.d.ts +10 -0
  39. package/dist/functions/own-mailbox.js +9 -0
  40. package/dist/functions/routes.js +5 -2
  41. package/dist/functions/upgrade-caller.js +1 -1
  42. package/dist/index.d.ts +4 -2
  43. package/dist/index.js +3 -2
  44. package/dist/jobs/job-types.d.ts +1 -1
  45. package/dist/jobs/runtime.d.ts +1 -1
  46. package/dist/mail/composer-message.js +27 -0
  47. package/dist/mail/define-email.d.ts +7 -4
  48. package/dist/mail/define-email.js +3 -2
  49. package/dist/mail/{smtp-mailer.d.ts → mail-message.d.ts} +9 -1
  50. package/dist/mail/smtp-mailer.js +2 -8
  51. package/dist/package.js +1 -1
  52. package/dist/personal-mail/gmail.js +43 -0
  53. package/dist/personal-mail/linked-accounts.js +36 -0
  54. package/dist/personal-mail/personal-mail-error.d.ts +22 -0
  55. package/dist/personal-mail/personal-mail-error.js +25 -0
  56. package/dist/personal-mail/personal-mail-provider.d.ts +25 -0
  57. package/dist/personal-mail/personal-mail-providers.d.ts +8 -0
  58. package/dist/personal-mail/personal-mail-providers.js +16 -0
  59. package/dist/personal-mail/personal-mailer.js +26 -0
  60. package/dist/personal-mail/rfc822-message.js +15 -0
  61. package/package.json +4 -4
@@ -0,0 +1,97 @@
1
+ import { HttpProblem, forbidden, notFound, unauthorized } from "../problem.js";
2
+ import { socketAcceptor } from "../functions/accept-socket.js";
3
+ import { allowedOrigin } from "../functions/upgrade-caller.js";
4
+ import { guestLimits } from "./guest-limits.js";
5
+ import { guestName } from "./guest-name.js";
6
+ import { guestTokenPrefix, linkTokenPrefix, newToken, tokenHash } from "./guest-tokens.js";
7
+ import { guestSocket } from "./guest-socket.js";
8
+ import { Hono } from "hono";
9
+ //#region src/calls/guest-routes.ts
10
+ const noStore = { "Cache-Control": "no-store" };
11
+ const tooMany = () => new HttpProblem(429, "too-many-requests", "Too Many Requests", "Too many tries: wait a minute", {}, { "retry-after": "60" });
12
+ const noLobby = () => notFound("This link lets nobody into a call");
13
+ const bearer = (c) => c.req.header("authorization")?.match(/^Bearer (.+)$/)?.[1];
14
+ /**
15
+ * The guests' routes, mounted at `guestCallsPath` before the API's authenticator, which takes none of their tokens,
16
+ * as no route but these takes them:
17
+ * - `POST /lobby` with `{ link, name }`: the guest waits in the lobby of the link's call; `201 { guest, token }`.
18
+ * - `GET /lobby` with `Authorization: Bearer <token>`: `{ state }`, with `call` and `publish` once let in.
19
+ * - `DELETE /lobby`: the waiting guest leaves.
20
+ * - `GET /socket`: the guest's call socket (see `guestSocket`).
21
+ * Each address may enter `entriesPerMinute` times a minute, and is answered 429 for the rest of the minute after
22
+ * `refusalsPerMinute` unknown or refused tokens; each guest asks after their wait `pollsPerMinute` times at most.
23
+ */
24
+ const guestRoutes = ({ calls, addressOf, origins, upgradeWebSocket, onError, report }) => {
25
+ const app = new Hono();
26
+ app.onError(onError);
27
+ const limits = guestLimits();
28
+ const address = (c) => addressOf(c.req.raw) ?? "no-trusted-address";
29
+ const open = (c) => {
30
+ const at = address(c);
31
+ if (limits.blocked(at)) throw tooMany();
32
+ return at;
33
+ };
34
+ const guestToken = (c, at) => {
35
+ const hash = tokenHash(guestTokenPrefix, bearer(c));
36
+ if (!hash) {
37
+ limits.refused(at);
38
+ throw unauthorized("Send the token the lobby gave you");
39
+ }
40
+ if (!limits.poll(hash.toString("hex"))) throw tooMany();
41
+ return hash;
42
+ };
43
+ app.post("/lobby", async (c) => {
44
+ const at = open(c);
45
+ const body = await c.req.json().catch(() => void 0);
46
+ if (!limits.enter(at)) throw tooMany();
47
+ const name = guestName(body?.name);
48
+ const link = tokenHash(linkTokenPrefix, body?.link);
49
+ if (!link) {
50
+ limits.refused(at);
51
+ throw noLobby();
52
+ }
53
+ const token = newToken(guestTokenPrefix);
54
+ const entered = await (await calls.runtime()).guests().enter({
55
+ link,
56
+ name,
57
+ token: tokenHash(guestTokenPrefix, token)
58
+ });
59
+ if ("refused" in entered) {
60
+ limits.refused(at);
61
+ throw noLobby();
62
+ }
63
+ return c.json({
64
+ guest: entered.guest,
65
+ token
66
+ }, 201, noStore);
67
+ });
68
+ app.get("/lobby", async (c) => {
69
+ const at = open(c);
70
+ const hash = guestToken(c, at);
71
+ const state = await (await calls.runtime()).guests().state(hash);
72
+ if (!state) {
73
+ limits.refused(at);
74
+ throw unauthorized("This token lets nobody into a call");
75
+ }
76
+ return c.json(state, 200, noStore);
77
+ });
78
+ app.delete("/lobby", async (c) => {
79
+ const at = open(c);
80
+ await (await calls.runtime()).guests().leave(guestToken(c, at));
81
+ return c.body(null, 204);
82
+ });
83
+ app.get("/socket", async (c) => {
84
+ const at = open(c);
85
+ if (!allowedOrigin(c.req.raw, origins)) throw forbidden("foreign-origin", "This page’s origin may not open a socket to this app");
86
+ const runtime = await calls.runtime();
87
+ const acceptor = socketAcceptor({
88
+ c,
89
+ upgradeWebSocket,
90
+ report
91
+ });
92
+ return acceptor.answer("guest call", acceptor.accept(guestSocket(runtime, () => limits.refused(at))));
93
+ });
94
+ return app;
95
+ };
96
+ //#endregion
97
+ export { guestRoutes };
@@ -0,0 +1,58 @@
1
+ import { forbidden } from "../problem.js";
2
+ import { parseClientMessage } from "./client-message.js";
3
+ import { guestTokenPrefix, tokenHash } from "./guest-tokens.js";
4
+ //#region src/calls/guest-socket.ts
5
+ /** How long a guest's socket may stay open before its first message names the guest, in milliseconds. */
6
+ const guestFirstMessageMs = 1e4;
7
+ const refusedCloseCode = 1008;
8
+ const maxMessageBytes = 307200;
9
+ /**
10
+ * A guest's call socket: open to anyone, it lets in only a guest whose first message, their `join` or `resume`, carries
11
+ * the token of a guest a member let in who may still be in the call. From then on it is a member's socket, as `guest:<id>` in their link's call,
12
+ * checked every minute: removed, their link revoked or expired, guests turned off or the call ended closes it. A
13
+ * socket that names no such guest, or none in time, closes with 1008 and `refused` (the address is counted).
14
+ */
15
+ const guestSocket = (runtime, refused) => {
16
+ let socket;
17
+ let relay;
18
+ let guest;
19
+ let timer;
20
+ const refuse = () => {
21
+ refused();
22
+ socket.send(JSON.stringify({
23
+ type: "left",
24
+ reason: "refused"
25
+ }));
26
+ socket.close(refusedCloseCode, "Not let into this call");
27
+ };
28
+ const first = async (data) => {
29
+ clearTimeout(timer);
30
+ const message = parseClientMessage(data);
31
+ const hash = message && (message.type === "join" || message.type === "resume") ? tokenHash(guestTokenPrefix, message.guest) : void 0;
32
+ const row = hash && await runtime.guests().byToken(hash);
33
+ if (!row || row.admitted !== true || !await runtime.guests().stillAllowed(row.id)) return refuse();
34
+ guest = row.id;
35
+ relay = await runtime.relayGuest(row);
36
+ await relay.onOpen?.(socket);
37
+ await relay.onMessage?.(data, socket);
38
+ };
39
+ return {
40
+ maxMessageBytes,
41
+ onOpen: (opened) => {
42
+ socket = opened;
43
+ timer = setTimeout(refuse, guestFirstMessageMs);
44
+ timer.unref?.();
45
+ },
46
+ onMessage: (data) => relay ? relay.onMessage?.(data, socket) : first(data),
47
+ onClose: (code, reason) => {
48
+ clearTimeout(timer);
49
+ return relay?.onClose?.(code, reason);
50
+ },
51
+ recheck: async () => {
52
+ if (guest === void 0) return;
53
+ if (!await runtime.guests().stillAllowed(guest)) throw forbidden("guest-not-allowed", "This guest may no longer be in the call");
54
+ }
55
+ };
56
+ };
57
+ //#endregion
58
+ export { guestFirstMessageMs, guestSocket };
@@ -0,0 +1,48 @@
1
+ import { GuestRow } from "./guest-checks-sql.js";
2
+ import { CreateLinkInput } from "./guest-links-sql.js";
3
+ import { DecideInput, LobbyState } from "./guest-lobby-sql.js";
4
+ import { CallGuestLink, CallGuestRefusal, CallLobbyGuest } from "@protobase/schema/internal";
5
+ import { Db } from "@protobase/query";
6
+ //#region src/calls/guest-store.d.ts
7
+ /** Where guest links, the lobby's guests and the organizations' switch are kept: the `calls` schema. */
8
+ export type GuestStore = {
9
+ createLink: (input: CreateLinkInput) => Promise<{
10
+ link: CallGuestLink;
11
+ token: string;
12
+ } | {
13
+ refused: CallGuestRefusal;
14
+ }>;
15
+ revokeLink: (input: {
16
+ call: string;
17
+ link: string;
18
+ member: string;
19
+ }) => Promise<boolean>;
20
+ decide: (input: DecideInput) => Promise<{
21
+ refused?: CallGuestRefusal;
22
+ }>;
23
+ lobbies: (calls: readonly string[]) => Promise<Map<string, {
24
+ guests: CallLobbyGuest[];
25
+ links: CallGuestLink[];
26
+ }>>;
27
+ enter: (input: {
28
+ link: Buffer;
29
+ name: string;
30
+ token: Buffer;
31
+ }) => Promise<{
32
+ guest: string;
33
+ } | {
34
+ refused: 'gone';
35
+ }>;
36
+ state: (token: Buffer) => Promise<LobbyState | undefined>;
37
+ leave: (token: Buffer) => Promise<void>;
38
+ byToken: (token: Buffer) => Promise<GuestRow | undefined>;
39
+ stillAllowed: (guest: string) => Promise<boolean>;
40
+ /** The calls whose guests turning guests off took out. */
41
+ setOrganization: (input: {
42
+ organization: string;
43
+ allowed: boolean;
44
+ by: string;
45
+ }) => Promise<string[]>;
46
+ organizationAllowed: (organization: string) => Promise<boolean>;
47
+ };
48
+ //#endregion
@@ -0,0 +1,20 @@
1
+ import { guestByToken, guestStillAllowed } from "./guest-checks-sql.js";
2
+ import { createLink, organizationGuests, revokeLink, setOrganizationGuests } from "./guest-links-sql.js";
3
+ import { decideGuest, enterLobby, leaveLobby, lobbies, lobbyState } from "./guest-lobby-sql.js";
4
+ //#region src/calls/guest-store.ts
5
+ /** The `calls` schema of the app's database; writes under the API's statement timeout. */
6
+ const sqlGuestStore = (db, timeoutMs, organizationOf) => ({
7
+ createLink: (input) => createLink(db, timeoutMs, organizationOf, input),
8
+ revokeLink: (input) => revokeLink(db, timeoutMs, input),
9
+ decide: (input) => decideGuest(db, timeoutMs, input),
10
+ lobbies: (calls) => lobbies(db, calls),
11
+ enter: (input) => enterLobby(db, timeoutMs, input),
12
+ state: (token) => lobbyState(db, token),
13
+ leave: (token) => leaveLobby(db, token),
14
+ byToken: (token) => guestByToken(db, token),
15
+ stillAllowed: (guest) => guestStillAllowed(db, guest),
16
+ setOrganization: (input) => setOrganizationGuests(db, timeoutMs, input),
17
+ organizationAllowed: (organization) => organizationGuests(db, organization)
18
+ });
19
+ //#endregion
20
+ export { sqlGuestStore };
@@ -0,0 +1,17 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ //#region src/calls/guest-tokens.ts
3
+ /** What starts a guest link's token, and a guest's: neither is ever a session token, a JWT or an API key. */
4
+ const linkTokenPrefix = "pbgl_";
5
+ const guestTokenPrefix = "pbg_";
6
+ /** A new token, 32 random bytes in base64url after its prefix. */
7
+ const newToken = (prefix) => `${prefix}${randomBytes(32).toString("base64url")}`;
8
+ /** The SHA-256 a token is stored and looked up by; `undefined` for a value that is no token of this kind. */
9
+ const tokenHash = (prefix, token) => {
10
+ if (typeof token !== "string" || !token.startsWith(prefix) || token.length !== prefix.length + 43) return;
11
+ return createHash("sha256").update(token).digest();
12
+ };
13
+ /** The member a guest is in their call: no `calls.join` takes a member of this form. */
14
+ const guestMember = (guest) => `guest:${guest}`;
15
+ const isGuestMember = (member) => member.startsWith("guest:");
16
+ //#endregion
17
+ export { guestMember, guestTokenPrefix, isGuestMember, linkTokenPrefix, newToken, tokenHash };
@@ -1,9 +1,11 @@
1
- import { memberEnd } from "./close-codes.js";
2
1
  import { parseClientMessage } from "./client-message.js";
2
+ import { memberEnd } from "./close-codes.js";
3
3
  //#region src/calls/member-relay.ts
4
4
  /** The close code of a socket whose caller Protobase refused: the member leaves the call too. */
5
5
  const refusedCloseCode = 1008;
6
6
  const mayPublish = (rights, tracks) => rights.publish === true || Array.isArray(rights.publish) && tracks.every((track) => rights.publish.includes(track.source));
7
+ const guestSources = (rights, asked) => [...new Set(asked)].filter((source) => rights.publish === true || Array.isArray(rights.publish) && rights.publish.includes(source));
8
+ const isGuestMessage = (message) => message.type.startsWith("guest-");
7
9
  const refusals = {
8
10
  removed: "You were removed from this call",
9
11
  full: "This call is full",
@@ -15,7 +17,7 @@ const refusals = {
15
17
  * may not make, and tells the call and the plugin how the socket ended. The plugin only ever sees well-formed messages
16
18
  * of a member Protobase let in.
17
19
  */
18
- const memberRelay = ({ door, backend, record, member, rights, maxMembers, maxMinutes, report, user, ring }) => {
20
+ const memberRelay = ({ door, backend, record, member, rights, maxMembers, maxMinutes, report, user, ring, invites = false, name, guest }) => {
19
21
  let browser;
20
22
  let session;
21
23
  let inCall;
@@ -49,14 +51,20 @@ const memberRelay = ({ door, backend, record, member, rights, maxMembers, maxMin
49
51
  muted: false
50
52
  }))
51
53
  } : void 0;
54
+ const more = {
55
+ ...user !== void 0 && { user },
56
+ ...ring && !resume && { ring },
57
+ ...invites && { invites },
58
+ ...name !== void 0 && { name },
59
+ ...guest && { guest }
60
+ };
52
61
  const admitted = await door.admit({
53
62
  record,
54
63
  member,
55
64
  maxMembers,
56
65
  maxMinutes,
57
66
  ...resume && { resume },
58
- ...user !== void 0 && { user },
59
- ...ring && !resume && { ring }
67
+ ...more
60
68
  }, {
61
69
  send,
62
70
  close
@@ -80,9 +88,59 @@ const memberRelay = ({ door, backend, record, member, rights, maxMembers, maxMin
80
88
  await session.receive(message);
81
89
  await admitted.listen(session.roster ?? (() => {}));
82
90
  };
91
+ const guests = async (message, call) => {
92
+ const actions = door.guests;
93
+ if (!invites || !actions) return send({
94
+ type: "guest-refused",
95
+ reason: "not-allowed"
96
+ });
97
+ switch (message.type) {
98
+ case "guest-link": {
99
+ const made = await actions.link({
100
+ call,
101
+ member,
102
+ publish: guestSources(rights, message.publish),
103
+ ...message.expires_at && { expiresAt: new Date(message.expires_at) }
104
+ });
105
+ if ("refused" in made) return send({
106
+ type: "guest-refused",
107
+ reason: made.refused
108
+ });
109
+ return send({
110
+ type: "guest-link",
111
+ link: made.link,
112
+ token: made.token
113
+ });
114
+ }
115
+ case "guest-link-revoke": return send(await actions.revoke({
116
+ call,
117
+ link: message.link,
118
+ member
119
+ }) ? { type: "guest-done" } : {
120
+ type: "guest-refused",
121
+ reason: "gone"
122
+ });
123
+ case "guest-decide": {
124
+ const decided = await actions.decide({
125
+ call,
126
+ guest: message.guest,
127
+ member,
128
+ admit: message.admit
129
+ });
130
+ return send(decided.refused ? {
131
+ type: "guest-refused",
132
+ reason: decided.refused
133
+ } : { type: "guest-done" });
134
+ }
135
+ case "guest-remove":
136
+ await actions.remove(call, message.guest);
137
+ return send({ type: "guest-done" });
138
+ }
139
+ };
83
140
  const relay = async (message) => {
84
141
  if (message.type === "join" || message.type === "resume") return enter(message);
85
- if (!session) return close(refusedCloseCode, "Join or resume the call first");
142
+ if (!session || !inCall) return close(refusedCloseCode, "Join or resume the call first");
143
+ if (isGuestMessage(message)) return guests(message, inCall.call);
86
144
  if (message.type === "publish" && !mayPublish(rights, message.tracks)) return send({
87
145
  type: "publish-refused",
88
146
  reason: "The member may not publish these tracks"
@@ -0,0 +1,38 @@
1
+ import { badRequest, forbidden } from "../problem.js";
2
+ import { Hono } from "hono";
3
+ //#region src/calls/organization-guests-route.ts
4
+ const organizationOf = (session) => {
5
+ if (session.anonymous || session.client || session.organization === void 0) throw forbidden("organization-required", "Sign in and work in an organization first");
6
+ return String(session.organization.id);
7
+ };
8
+ /**
9
+ * Whether the calls of the organization the user works in take guests, mounted behind the API's authenticator: `GET /`
10
+ * `{ allowed }` for any member, `PUT /` with `{ allowed }` for its owner or an admin. Turning guests off revokes every
11
+ * guest link of the organization's calls and takes their guests out at once.
12
+ */
13
+ const organizationGuestsRoute = ({ calls, roleIn }) => {
14
+ const app = new Hono();
15
+ app.get("/", async (c) => {
16
+ const session = c.get("session");
17
+ const organization = organizationOf(session);
18
+ if (!await roleIn(organization, String(session.user.id))) throw forbidden("not-a-member", "You are not a member of this organization");
19
+ return c.json({ allowed: await (await calls.runtime()).guests().organizationAllowed(organization) }, 200, { "Cache-Control": "no-store" });
20
+ });
21
+ app.put("/", async (c) => {
22
+ const session = c.get("session");
23
+ const organization = organizationOf(session);
24
+ const role = await roleIn(organization, String(session.user.id));
25
+ if (role !== "owner" && role !== "admin") throw forbidden("organization-admin-required", "Only an owner or admin of the organization turns guest links on or off");
26
+ const body = await c.req.json().catch(() => void 0);
27
+ if (typeof body?.allowed !== "boolean") throw badRequest("allowed-required", "Send { allowed: true } or { allowed: false }");
28
+ await (await calls.runtime()).setOrganizationGuests({
29
+ organization,
30
+ allowed: body.allowed,
31
+ by: String(session.user.id)
32
+ });
33
+ return c.json({ allowed: body.allowed });
34
+ });
35
+ return app;
36
+ };
37
+ //#endregion
38
+ export { organizationGuestsRoute };
@@ -0,0 +1,24 @@
1
+ import { decodeKey } from "@protobase/schema/internal";
2
+ import { sql } from "kysely";
3
+ //#region src/calls/record-organization.ts
4
+ /**
5
+ * The organization a call's record is in, read from the record's own tenant field with no access check: `null` for a
6
+ * record of a resource without a tenant, or one that is gone, which no organization's switch covers. A key of a relation
7
+ * to a resource with a key of several fields is read as none either, so its calls take no guests.
8
+ */
9
+ const recordOrganization = (registry) => async (trx, record) => {
10
+ const entry = registry.find(record.resource);
11
+ const tenant = entry?.model.tenant;
12
+ if (!entry || !tenant) return null;
13
+ const { model } = entry;
14
+ const decoded = decodeKey(record.key, entry.keyTypes);
15
+ const values = Array.isArray(decoded) ? decoded : [decoded];
16
+ if (values.length !== model.primaryKey.length) return null;
17
+ const table = model.table.schema ? sql.id(model.table.schema, model.table.name) : sql.id(model.table.name);
18
+ const where = sql.join(model.primaryKey.map((name, index) => sql`${sql.id(model.fields[name].column)} = ${values[index]}`), sql` and `);
19
+ const { rows } = await sql`select ${sql.id(model.fields[tenant].column)} as tenant from ${table} where ${where}`.execute(trx);
20
+ const found = rows[0]?.tenant;
21
+ return found === void 0 || found === null ? null : String(found);
22
+ };
23
+ //#endregion
24
+ export { recordOrganization };
@@ -61,6 +61,10 @@ import { webhookTable } from "./webhooks/table/webhook-table.js";
61
61
  import { subscriptionUrlCheck, subscriptionWriteCheck } from "./webhooks/table/validate-hook.js";
62
62
  import { callsService } from "./calls/calls-service.js";
63
63
  import { ringRoutes } from "./calls/ring-routes.js";
64
+ import { upgradeAuthenticator } from "./functions/upgrade-caller.js";
65
+ import { guestRoutes } from "./calls/guest-routes.js";
66
+ import { organizationGuestsRoute } from "./calls/organization-guests-route.js";
67
+ import { organizationRole } from "./better-auth/organizations/organization-role.js";
64
68
  import { deviceStore, removeDevicesWithUsers } from "./notifications/device-store-of.js";
65
69
  import { historyStore, removeHistoryWithUsers } from "./notifications/history-store-of.js";
66
70
  import { devicesPath, historyPath } from "./notifications/paths.js";
@@ -91,7 +95,6 @@ import { fieldDecrypter } from "./encryption/decrypt-field.js";
91
95
  import { createEncryptionRuntime } from "./encryption/runtime.js";
92
96
  import { functionRoutes } from "./functions/routes.js";
93
97
  import { supabaseFunctionRoutes } from "./functions/supabase-routes.js";
94
- import { upgradeAuthenticator } from "./functions/upgrade-caller.js";
95
98
  import { localeSetting } from "./locale/locale-option.js";
96
99
  import { requestLocale } from "./locale/request-locale.js";
97
100
  import { mapSettings } from "./maps/map-settings.js";
@@ -112,7 +115,8 @@ import { createScanGuard } from "./scan-guard.js";
112
115
  import { failedJobs, failedJobsView } from "./jobs/failed-jobs.js";
113
116
  import { Hono } from "hono";
114
117
  import { accountPage, accountPageName, checkPages, settingsResources, withSettingsFields } from "@protobase/layout";
115
- import { ringsPath, statusPath } from "@protobase/schema/internal";
118
+ import { guestCallsPath, ringsPath, statusPath } from "@protobase/schema/internal";
119
+ import { getIP } from "better-auth/api";
116
120
  //#region src/create-admin.ts
117
121
  const declaredRoles = (auth, roles) => {
118
122
  const names = [...auth?.roles ?? [], ...Object.keys(roles?.definitions ?? {})];
@@ -270,6 +274,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
270
274
  notices: changes,
271
275
  channels: () => channels,
272
276
  notifications: Boolean(notifications),
277
+ organizations: Boolean(auth?.organizations),
273
278
  ...auth?.origins[0] && { origin: auth.origins[0] },
274
279
  ...statusTables && { doNotDisturb: (ringDb, userIds) => onDoNotDisturb(ringDb, statusTables.statuses, userIds) }
275
280
  }) }
@@ -414,6 +419,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
414
419
  records: (session) => callerRecords(deps, session),
415
420
  channels,
416
421
  decryptField,
422
+ ...auth && auth.personalMail.length > 0 && { personalMailer: auth.personalMailer },
417
423
  onError,
418
424
  wake: () => wakeDispatch(deps),
419
425
  upgradeWebSocket,
@@ -469,6 +475,21 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
469
475
  }
470
476
  if (statusTables) app.route(statusPath(systemPath), guarded((routes) => void routes.route("/", statusRoutes(db, statusTables))));
471
477
  if (deps.calls) app.route(ringsPath(systemPath), guarded((routes) => void routes.route("/", ringRoutes(deps.calls))));
478
+ if (deps.calls && auth) {
479
+ const addressOf = (request) => getIP(request, auth.options);
480
+ app.route(guestCallsPath(systemPath), guestRoutes({
481
+ calls: deps.calls,
482
+ addressOf,
483
+ origins: auth.origins,
484
+ onError,
485
+ ...upgradeWebSocket && { upgradeWebSocket },
486
+ report
487
+ }));
488
+ if (auth.organizations) app.route(guestCallsPath(systemPath, "organization"), guarded((routes) => void routes.route("/", organizationGuestsRoute({
489
+ calls: deps.calls,
490
+ roleIn: organizationRole(auth)
491
+ }))));
492
+ }
472
493
  if (forms) app.route(`${systemPath}/forms`, formRoutes(deps, forms));
473
494
  const feed = tableFeed({
474
495
  db,
@@ -1,4 +1,5 @@
1
1
  import { DecryptField } from "../encryption/decrypt-field-types.js";
2
+ import { Mailer } from "../mail/mail-message.js";
2
3
  import { Session } from "../types.js";
3
4
  import { Caller } from "../api-keys/resolve-caller.js";
4
5
  import { SocketEvents } from "./function-socket.js";
@@ -43,6 +44,15 @@ export type FunctionContext = PublicFunctionContext & FunctionCaller & {
43
44
  * the audit does. Not on a public function's context: a public function that needs a value enqueues a job.
44
45
  */
45
46
  decryptField: DecryptField;
47
+ /**
48
+ * Mail from the caller's own mailbox, through the mail provider they linked (`auth.personalMail`), attachments and
49
+ * all: `personalMail.send({ to, subject, text, html, attachments })`, or an email template's `send(to, data,
50
+ * context.personalMail, { attachments })`. Rejects with a `PersonalMailError`, sending nothing, when they linked no
51
+ * mailbox (`not_linked`) or the link no longer works (`link_expired`); thrown on, it answers as a 409 problem with
52
+ * that `code`. Only someone signed in as themselves sends from their mailbox: staff signed in as them, or an outside
53
+ * app acting for them, is refused with a 403 problem. Not on a public function's context.
54
+ */
55
+ personalMail: Mailer;
46
56
  };
47
57
  /** What a function gets at `/api/sb/functions/v1/<name>`, where supabase-js calls it: the caller its keys and token make. */
48
58
  export type KeyFunctionContext = {
@@ -0,0 +1,9 @@
1
+ import { ownSignInUser } from "../notifications/caller.js";
2
+ //#region src/functions/own-mailbox.ts
3
+ /**
4
+ * The caller's own mailbox: mail goes out from it only for someone signed in as themselves. Staff signed in as them,
5
+ * an outside app acting for them and a visitor are refused with a 403 problem, sending nothing.
6
+ */
7
+ const ownMailbox = (session, personalMailer) => ({ send: async (message) => personalMailer(ownSignInUser(session, "send mail from their own mailbox")).send(message) });
8
+ //#endregion
9
+ export { ownMailbox };
@@ -2,9 +2,10 @@ import { forbidden, notFound } from "../problem.js";
2
2
  import { namingTimeouts } from "../statement-timeout.js";
3
3
  import { auditOrigin } from "../audit/origin.js";
4
4
  import { invokeFunction } from "./define-function.js";
5
+ import { socketAcceptor } from "./accept-socket.js";
5
6
  import { runInBackground } from "../background-work.js";
6
7
  import { callsAccess } from "../calls/calls-access.js";
7
- import { socketAcceptor } from "./accept-socket.js";
8
+ import { ownMailbox } from "./own-mailbox.js";
8
9
  import { functionGate, requireGate } from "./function-gate.js";
9
10
  import { sameCaller } from "./same-caller.js";
10
11
  import { Hono } from "hono";
@@ -15,6 +16,7 @@ const relativeRequest = (request, prefix) => {
15
16
  url.pathname = url.pathname.slice(prefix.length) || "/";
16
17
  return new Request(url, request);
17
18
  };
19
+ const noPersonalMail = () => ({ send: () => Promise.reject(/* @__PURE__ */ new Error("Personal mail is off: name the mail providers people may link in auth.personalMail, such as [\"gmail\"]")) });
18
20
  const isUpgrade = (request) => request.headers.get("upgrade")?.toLowerCase() === "websocket";
19
21
  /**
20
22
  * Every function at `<basePath>/<name>` and below, any method. A protected function authenticates the request first, so
@@ -24,7 +26,7 @@ const isUpgrade = (request) => request.headers.get("upgrade")?.toLowerCase() ===
24
26
  * A function may answer an upgrade with a socket, only once it returned. While it is open, its caller is checked again
25
27
  * every minute: a protected function's always, a public one's once `caller()` resolved.
26
28
  */
27
- const functionRoutes = ({ basePath, functions, authenticate, access, db, records, channels, decryptField, onError, wake, upgradeWebSocket, upgradeAuthenticate = authenticate, calls, report }) => {
29
+ const functionRoutes = ({ basePath, functions, authenticate, access, db, records, channels, decryptField, personalMailer = noPersonalMail, onError, wake, upgradeWebSocket, upgradeAuthenticate = authenticate, calls, report }) => {
28
30
  const app = new Hono().basePath(basePath);
29
31
  app.onError(onError);
30
32
  const sessionOf = (request) => isUpgrade(request) ? upgradeAuthenticate(request) : authenticate(request);
@@ -99,6 +101,7 @@ const functionRoutes = ({ basePath, functions, authenticate, access, db, records
99
101
  ...caller,
100
102
  caller: async () => caller,
101
103
  decryptField: decryptField(reader),
104
+ personalMail: ownMailbox(caller.session, personalMailer),
102
105
  acceptWebSocket: acceptor.accept,
103
106
  calls: callsAccess(calls, async () => caller)
104
107
  };
@@ -22,4 +22,4 @@ const upgradeAuthenticator = ({ authenticate, origins, sessionToken }) => async
22
22
  return authenticate(new Request(request, { headers }));
23
23
  };
24
24
  //#endregion
25
- export { upgradeAuthenticator };
25
+ export { allowedOrigin, upgradeAuthenticator };
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { RealtimeOptions, relayed, sqlSends } from "./realtime/relay/realtime-op
5
5
  import { EventsOptions } from "./changes/events-options.js";
6
6
  import { DecryptField, FieldDecrypter, FieldReader, RecordKey } from "./encryption/decrypt-field-types.js";
7
7
  import { StandardSchema, readJson } from "./functions/read-json.js";
8
- import { MailMessage, Mailer } from "./mail/smtp-mailer.js";
8
+ import { MailAttachment, MailMessage, Mailer } from "./mail/mail-message.js";
9
9
  import { JsonObject, JsonValue } from "./notifications/json-value.js";
10
10
  import { HistoryPosition, NewNotification, StoredNotification, historyIdPattern, historyLimits } from "./notifications/history.js";
11
11
  import { PushMessage, PushMessageError, Urgency, WebPushFields, maxTtlSeconds, pushMessage, urgencies } from "./notifications/message.js";
@@ -175,9 +175,11 @@ import { defineJob, isJobDefinition, jobDefinitions } from "./jobs/define-job.js
175
175
  import { FatalJobError } from "./jobs/run-job.js";
176
176
  import { ArgumentsPayload, EnqueueJob, JobDeclaration, JobName, JobPayload, JobRun, enqueueJob } from "./jobs/declared-job.js";
177
177
  import { isSchedule } from "./jobs/schedule.js";
178
+ import { PersonalMailError, PersonalMailErrorCode } from "./personal-mail/personal-mail-error.js";
179
+ import { PersonalMailProviderId } from "./personal-mail/personal-mail-providers.js";
178
180
  import { EmailComponent, EmailOptions, EmailTemplate, RenderedEmail, defineEmail } from "./mail/define-email.js";
179
181
  import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
180
182
  import { jobsDrainApp } from "./jobs/drain-route.js";
181
183
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
182
184
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
183
- export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StatusOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
185
+ export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailAttachment, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StatusOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };