@protobase/server 0.21.6 → 0.21.8

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 (91) hide show
  1. package/dist/aggregate-groups.js +1 -1
  2. package/dist/aggregate-service.js +3 -3
  3. package/dist/ai/ai-fields.js +9 -0
  4. package/dist/ai/ai-model.js +9 -0
  5. package/dist/ai/check-ai.d.ts +13 -0
  6. package/dist/ai/check-ai.js +11 -0
  7. package/dist/ai/fill-hook.js +47 -0
  8. package/dist/ai/fill-job.js +67 -0
  9. package/dist/ai/generate-route.js +57 -0
  10. package/dist/ai/generate-value.js +28 -0
  11. package/dist/ai/values.js +15 -0
  12. package/dist/anonymous.js +7 -2
  13. package/dist/assistant/assistant-settings.d.ts +2 -2
  14. package/dist/assistant/assistant-settings.js +7 -21
  15. package/dist/assistant/built-in-assistant.js +2 -1
  16. package/dist/assistant/model-settings.js +31 -0
  17. package/dist/assistant/turn.d.ts +1 -1
  18. package/dist/assistant/turn.js +2 -2
  19. package/dist/batch/reorder.js +2 -2
  20. package/dist/batch/run-batch.js +1 -1
  21. package/dist/better-auth/create-auth.d.ts +12 -4
  22. package/dist/better-auth/create-auth.js +0 -1
  23. package/dist/better-auth/organizations/invitation-endpoints.js +1 -1
  24. package/dist/better-auth/organizations/member-endpoints.js +1 -1
  25. package/dist/better-auth/passkey-plugin.js +1 -1
  26. package/dist/better-auth/policy-plugin.js +1 -1
  27. package/dist/better-auth/roles/roles-plugin.js +1 -1
  28. package/dist/better-auth/sign-up/sign-up-plugin.js +1 -1
  29. package/dist/better-auth/staff-sign-in-endpoints.js +1 -1
  30. package/dist/better-auth/status-route.js +1 -1
  31. package/dist/changes/match-events.js +1 -1
  32. package/dist/check-roles.js +1 -1
  33. package/dist/check-shell.js +1 -1
  34. package/dist/check-views.js +26 -4
  35. package/dist/comments/app-origin.js +11 -0
  36. package/dist/comments/comment-access.js +54 -0
  37. package/dist/comments/comment-input.js +26 -0
  38. package/dist/comments/comment-item.d.ts +16 -0
  39. package/dist/comments/comment-item.js +18 -0
  40. package/dist/comments/comment-users.d.ts +21 -0
  41. package/dist/comments/comment-users.js +35 -0
  42. package/dist/comments/comments-sql.d.ts +8 -0
  43. package/dist/comments/comments-sql.js +37 -0
  44. package/dist/comments/mention-email.js +37 -0
  45. package/dist/comments/mention-job.d.ts +3 -0
  46. package/dist/comments/mention-job.js +43 -0
  47. package/dist/comments/mentionable.js +38 -0
  48. package/dist/comments/post-comment.js +57 -0
  49. package/dist/comments/read-comments.js +45 -0
  50. package/dist/comments/record-comments.js +53 -0
  51. package/dist/comments/record-label.js +19 -0
  52. package/dist/create-admin.d.ts +9 -3
  53. package/dist/create-admin.js +55 -11
  54. package/dist/deps.d.ts +2 -0
  55. package/dist/error-handler.js +1 -1
  56. package/dist/files/attach.js +1 -1
  57. package/dist/files/upload.js +2 -2
  58. package/dist/forms/challenge.d.ts +8 -0
  59. package/dist/forms/challenge.js +71 -0
  60. package/dist/forms/check-forms.js +59 -0
  61. package/dist/forms/rate-limit.js +38 -0
  62. package/dist/forms/routes.js +83 -0
  63. package/dist/forms/runtime.js +31 -0
  64. package/dist/history/read-history.js +4 -3
  65. package/dist/index.d.ts +7 -3
  66. package/dist/index.js +11 -9
  67. package/dist/jobs/runtime.js +1 -1
  68. package/dist/list-photos.js +3 -3
  69. package/dist/list-service.js +2 -2
  70. package/dist/map-service.js +1 -1
  71. package/dist/meta.js +4 -4
  72. package/dist/openapi/resource-paths.js +151 -0
  73. package/dist/project-config.d.ts +1 -1
  74. package/dist/read-service.js +2 -2
  75. package/dist/related-labels.js +1 -1
  76. package/dist/request-access.js +26 -2
  77. package/dist/routes/record.js +17 -0
  78. package/dist/row-permissions.js +3 -3
  79. package/dist/sql-parts.js +1 -1
  80. package/dist/types.d.ts +2 -0
  81. package/dist/validate-body.js +6 -1
  82. package/dist/view-access.js +35 -6
  83. package/dist/view-related.js +15 -2
  84. package/dist/webhooks/route-events.js +1 -1
  85. package/dist/webhooks/table/schema.js +1 -1
  86. package/dist/webhooks/table/webhook-table.js +1 -1
  87. package/dist/webhooks/webhooks.js +1 -1
  88. package/dist/write-pipeline.js +8 -5
  89. package/dist/write-shared.js +3 -2
  90. package/dist/write-update.js +1 -1
  91. package/package.json +5 -4
@@ -1,11 +1,13 @@
1
1
  import { AdminAuth } from "./better-auth/create-auth.js";
2
2
  import { EventsOptions } from "./changes/events-options.js";
3
- import { HistoryOptions } from "./history/options.js";
4
3
  import { JobDefinition } from "./jobs/job-types.js";
5
4
  import { Admin, Authenticator, PageSource, ViewSource } from "./types.js";
5
+ import { CommentUsers } from "./comments/comment-users.js";
6
+ import { HistoryOptions } from "./history/options.js";
6
7
  import { WebhooksOptions } from "./webhooks/table/options.js";
7
8
  import { SubscriptionDefinition } from "./webhooks/subscription.js";
8
9
  import { FilesOptions } from "./files/options.js";
10
+ import { Difficulty } from "./forms/challenge.js";
9
11
  import { ResourceSource } from "./resource-source.js";
10
12
  import { FunctionSource } from "./functions/define-function.js";
11
13
  import { AdminOptions } from "./admin-options.js";
@@ -33,7 +35,7 @@ export type CreateAdminInput = {
33
35
  events?: EventsOptions;
34
36
  /** How long the history of resources with `.history()` is kept (see `HistoryOptions`). */
35
37
  history?: HistoryOptions;
36
- /** The app's jobs (see `defineJob`): with any, or a resource with events or history, admins get the failed jobs page (`failed_jobs`). */
38
+ /** The app's jobs (see `defineJob`): with any, or a resource with events, history or comments, admins get the failed jobs page (`failed_jobs`). */
37
39
  jobs?: JobDefinition[];
38
40
  /** Where the resources' events are delivered (`subscription(...)` exports of the config module). */
39
41
  subscriptions?: SubscriptionDefinition[];
@@ -52,7 +54,11 @@ export type CreateAdminInput = {
52
54
  */
53
55
  fieldTypes?: Record<string, string[]>;
54
56
  options?: AdminOptions;
57
+ /** @internal The proof-of-work range of public forms' bot checks, which tests make tiny. */
58
+ formsDifficulty?: Difficulty;
59
+ /** @internal The users comments name, mention and mail, which tests fake; from `auth` otherwise. */
60
+ commentUsers?: CommentUsers;
55
61
  };
56
62
  /** The admin REST API as a Hono app. It uses only Web APIs, so it runs on Node, Workers and anywhere else Hono does. */
57
- export declare const createAdmin: ({ resources: appResources, views: appViews, pages, userMenu, bottomBars, db, authenticate, auth, functions, files: filesOptions, events, history: historyOptions, jobs, subscriptions, webhooks: webhooksOptions, bundlePlugins, fieldTypes, options }: CreateAdminInput) => Admin;
63
+ export declare const createAdmin: ({ resources: appResources, views: appViews, pages, userMenu, bottomBars, db, authenticate, auth, functions, files: filesOptions, events, history: historyOptions, jobs, subscriptions, webhooks: webhooksOptions, bundlePlugins, fieldTypes, options, formsDifficulty, commentUsers: givenCommentUsers }: CreateAdminInput) => Admin;
58
64
  //#endregion
@@ -1,7 +1,11 @@
1
- import { assistantSettings } from "./assistant/assistant-settings.js";
1
+ import { aiModel } from "./ai/ai-model.js";
2
+ import { hasAiActions, hasAiFields } from "./ai/ai-fields.js";
3
+ import { fillHook } from "./ai/fill-hook.js";
2
4
  import { createErrorHandler } from "./error-handler.js";
3
5
  import { isCaptured } from "./changes/capture-sql.js";
4
6
  import { assistantRecords } from "./assistant/records.js";
7
+ import { fillJob } from "./ai/fill-job.js";
8
+ import { assistantSettings } from "./assistant/assistant-settings.js";
5
9
  import { builtInAssistant } from "./assistant/built-in-assistant.js";
6
10
  import { consoleAuditQueue } from "./audit/console-queue.js";
7
11
  import { webauthnPath } from "./better-auth/origins.js";
@@ -11,8 +15,12 @@ import { authBasePath } from "./better-auth/create-auth.js";
11
15
  import { organizationRoutes } from "./better-auth/organizations/admin-routes.js";
12
16
  import { statusRoute } from "./better-auth/status-route.js";
13
17
  import { changesSchema } from "./changes/events-options.js";
14
- import { activityResource, activityView } from "./history/activity.js";
18
+ import { appOrigin } from "./comments/app-origin.js";
15
19
  import { betterAuthActorNames } from "./history/actor-names.js";
20
+ import { betterAuthCommentUsers } from "./comments/comment-users.js";
21
+ import { mentionJob } from "./comments/mention-job.js";
22
+ import { commentsResource, commentsView } from "./comments/record-comments.js";
23
+ import { activityResource, activityView } from "./history/activity.js";
16
24
  import { historyDays } from "./history/options.js";
17
25
  import { pruneHistoryJob } from "./history/prune-history.js";
18
26
  import { callerRecords } from "./functions/records.js";
@@ -20,7 +28,7 @@ import { toApiFunction } from "./functions/define-function.js";
20
28
  import { createWebhooks } from "./webhooks/webhooks.js";
21
29
  import { webhookTable } from "./webhooks/table/webhook-table.js";
22
30
  import { subscriptionWriteCheck } from "./webhooks/table/validate-hook.js";
23
- import { withAnonymous } from "./anonymous.js";
31
+ import { visitorsBrowse, withAnonymous } from "./anonymous.js";
24
32
  import { checkBottomBars, checkShell } from "./check-shell.js";
25
33
  import { checkOwnerFields } from "./check-owners.js";
26
34
  import { checkRoleLimits } from "./check-roles.js";
@@ -28,6 +36,8 @@ import { checkViews } from "./check-views.js";
28
36
  import { errorReportingSettings } from "./error-reporting/error-reporting-settings.js";
29
37
  import { createFilesRuntime } from "./files/runtime.js";
30
38
  import { fileRoutes } from "./files/routes.js";
39
+ import { formRoutes } from "./forms/routes.js";
40
+ import { createFormsRuntime } from "./forms/runtime.js";
31
41
  import { functionRoutes } from "./functions/routes.js";
32
42
  import { mapSettings } from "./maps/map-settings.js";
33
43
  import { pluginOptions, pluginsBeforeSignIn } from "./plugins/plugin-settings.js";
@@ -44,30 +54,35 @@ import { createScanGuard } from "./scan-guard.js";
44
54
  import { failedJobs, failedJobsView } from "./jobs/failed-jobs.js";
45
55
  import { Hono } from "hono";
46
56
  import { checkPages } from "@protobase/layout";
47
- import { anonymousRole } from "@protobase/schema";
48
57
  //#region src/create-admin.ts
49
58
  const declaredRoles = (auth, roles) => {
50
59
  const names = [...auth?.roles ?? [], ...Object.keys(roles?.definitions ?? {})];
51
60
  return names.length > 0 ? [...new Set(names)] : void 0;
52
61
  };
53
62
  /** The admin REST API as a Hono app. It uses only Web APIs, so it runs on Node, Workers and anywhere else Hono does. */
54
- const createAdmin = ({ resources: appResources, views: appViews = [], pages = [], userMenu, bottomBars = [], db, authenticate, auth, functions = {}, files: filesOptions, events, history: historyOptions, jobs = [], subscriptions = [], webhooks: webhooksOptions, bundlePlugins = [], fieldTypes = {}, options = {} }) => {
63
+ const createAdmin = ({ resources: appResources, views: appViews = [], pages = [], userMenu, bottomBars = [], db, authenticate, auth, functions = {}, files: filesOptions, events, history: historyOptions, jobs = [], subscriptions = [], webhooks: webhooksOptions, bundlePlugins = [], fieldTypes = {}, options = {}, formsDifficulty, commentUsers: givenCommentUsers }) => {
55
64
  const basePath = options.basePath ?? "/api/v1";
56
65
  const appModels = appResources.map((resource) => resource.toModel());
57
- const withJobs = jobs.length > 0 || appModels.some(isCaptured);
66
+ const withComments = appModels.some((model) => model.comments);
67
+ const withAiFields = hasAiFields(appModels);
68
+ const withJobs = jobs.length > 0 || appModels.some(isCaptured) || withComments || withAiFields;
58
69
  const withHistory = appModels.some((model) => model.history);
59
70
  const changes = changesSchema(events);
60
71
  const days = historyDays(historyOptions);
61
72
  const resources = [
62
73
  ...appResources,
63
74
  ...withJobs ? [failedJobs] : [],
64
- ...withHistory ? [activityResource(changes, { tenant: appModels.some((model) => model.history && model.tenant) })] : []
75
+ ...withHistory ? [activityResource(changes, { tenant: appModels.some((model) => model.history && model.tenant) })] : [],
76
+ ...withComments ? [commentsResource({ tenant: appModels.some((model) => model.comments && model.tenant) })] : []
65
77
  ];
66
78
  const views = [
67
79
  ...appViews,
68
80
  ...withJobs ? [failedJobsView] : [],
69
- ...withHistory ? [activityView] : []
81
+ ...withHistory ? [activityView] : [],
82
+ ...withComments ? [commentsView] : []
70
83
  ];
84
+ const commentUsers = withComments ? givenCommentUsers ?? (auth && betterAuthCommentUsers(auth)) : void 0;
85
+ const mentions = withComments ? mentionJob(commentUsers, auth?.appName ?? readAppName(globalThis.process?.env ?? {}) ?? "Protobase") : void 0;
71
86
  const registry = buildRegistry(resources);
72
87
  const apiFunctions = Object.fromEntries(Object.entries(functions).map(([name, source]) => [name, toApiFunction(name, source)]));
73
88
  const viewModels = views.map((view) => view.toModel());
@@ -83,6 +98,13 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
83
98
  models: Object.fromEntries(registry.entries.map((entry) => [entry.name, entry.model])),
84
99
  actions: (resource) => new Set(viewModels.filter((view) => view.resource === resource).flatMap((view) => view.actions.map((action) => action.name)))
85
100
  });
101
+ const forms = createFormsRuntime({
102
+ registry,
103
+ views: viewModels,
104
+ limits: options.roles?.limits ?? [],
105
+ env: globalThis.process?.env ?? {},
106
+ ...formsDifficulty && { difficulty: formsDifficulty }
107
+ });
86
108
  const systemPath = basePath.slice(0, basePath.lastIndexOf("/"));
87
109
  const assistant = assistantSettings(options.assistant, globalThis.process?.env ?? {});
88
110
  const runtime = runtimeUrl(options.runtime, globalThis.process?.env ?? {});
@@ -101,6 +123,9 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
101
123
  systemPath
102
124
  });
103
125
  const table = webhooksOptions && webhookTable(webhooksOptions, auth);
126
+ const withAi = withAiFields || hasAiActions(viewModels);
127
+ const model = withAi ? aiModel(options.assistant, globalThis.process?.env ?? {}) : void 0;
128
+ const fill = withAiFields ? fillJob(() => deps) : void 0;
104
129
  const deps = {
105
130
  db,
106
131
  registry,
@@ -112,7 +137,11 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
112
137
  systemPath,
113
138
  statementTimeoutMs: options.statementTimeoutMs ?? 15e3,
114
139
  guard: createScanGuard(db, options.scanGuard),
115
- hooks: [...options.writeHooks ?? [], ...table ? [subscriptionWriteCheck(registry.entries.map((entry) => entry.model), table.checkUrl)] : []],
140
+ hooks: [
141
+ ...options.writeHooks ?? [],
142
+ ...table ? [subscriptionWriteCheck(registry.entries.map((entry) => entry.model), table.checkUrl)] : [],
143
+ ...fill ? [fillHook(() => deps, fill)] : []
144
+ ],
116
145
  audit: options.audit ?? consoleAuditQueue(),
117
146
  accessOptions: {
118
147
  ...options.roles && { roles: options.roles },
@@ -130,6 +159,15 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
130
159
  ...withHistory && { history: {
131
160
  schema: changes,
132
161
  ...auth && { actorNames: betterAuthActorNames(auth) }
162
+ } },
163
+ ...mentions && { comments: {
164
+ ...commentUsers && { users: commentUsers },
165
+ mentionJob: mentions,
166
+ appOrigin: appOrigin(auth?.origins ?? [])
167
+ } },
168
+ ...withAi && { ai: {
169
+ ...model && { model },
170
+ ...options.onUnhandledError && { report: options.onUnhandledError }
133
171
  } }
134
172
  };
135
173
  const meta = createMeta(deps, auth?.appName ?? readAppName(globalThis.process?.env ?? {}) ?? "Protobase");
@@ -173,7 +211,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
173
211
  }
174
212
  if (auth) {
175
213
  app.route(authBasePath, statusRoute(auth, {
176
- anonymous: Object.hasOwn(options.roles?.definitions ?? {}, anonymousRole),
214
+ anonymous: visitorsBrowse(options.roles),
177
215
  plugins: pluginsBeforeSignIn(plugins)
178
216
  }));
179
217
  if (auth.organizations) app.route(authBasePath, organizationRoutes({
@@ -209,6 +247,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
209
247
  }));
210
248
  app.route("/", batchRoute(deps, authenticate, meta));
211
249
  app.route("/", fileRoutes(deps, authenticate));
250
+ if (forms) app.route(`${systemPath}/forms`, formRoutes(deps, forms));
212
251
  app.route(basePath, api);
213
252
  app.route(`${deps.systemPath}/meta`, metaApp);
214
253
  app.route(deps.systemPath || "/", system);
@@ -220,7 +259,12 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
220
259
  log: (line) => console.log(line),
221
260
  ...table && { table }
222
261
  });
223
- return Object.assign(app, { jobs: [...webhooks.jobs, ...withHistory && days !== void 0 ? [pruneHistoryJob(changes, days)] : []] });
262
+ return Object.assign(app, { jobs: [
263
+ ...webhooks.jobs,
264
+ ...withHistory && days !== void 0 ? [pruneHistoryJob(changes, days)] : [],
265
+ ...mentions ? [mentions] : [],
266
+ ...fill ? [fill] : []
267
+ ] });
224
268
  };
225
269
  //#endregion
226
270
  export { createAdmin };
package/dist/deps.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import "./types.js";
2
+ import "./comments/comment-users.js";
3
+ import "./comments/mention-job.js";
2
4
  import "./files/runtime.js";
3
5
  import "./plugins/plugin-settings.js";
4
6
  import "./registry.js";
@@ -1,6 +1,6 @@
1
1
  import { HttpProblem, problemResponse } from "./problem.js";
2
- import { HTTPException } from "hono/http-exception";
3
2
  import { QueryError } from "@protobase/query";
3
+ import { HTTPException } from "hono/http-exception";
4
4
  //#region src/error-handler.ts
5
5
  const titles = {
6
6
  400: "Bad Request",
@@ -1,8 +1,8 @@
1
1
  import { badRequest } from "../problem.js";
2
+ import { tenantScope } from "../tenant.js";
2
3
  import { accepts } from "./media-types.js";
3
4
  import { externalFile, parseExternalUrl } from "./external-url.js";
4
5
  import { baseOf, parseUri, variantPaths } from "./uri.js";
5
- import { tenantScope } from "../tenant.js";
6
6
  import { readTicket } from "./ticket.js";
7
7
  //#region src/files/attach.ts
8
8
  /** The ticket scope of a caller's upload for one field. */
@@ -1,8 +1,8 @@
1
1
  import { HttpProblem, badRequest } from "../problem.js";
2
- import { accepts, servedType } from "./media-types.js";
3
- import { cleanName, tenantSegment, variantPath } from "./uri.js";
4
2
  import { tenantScope } from "../tenant.js";
5
3
  import { requestAccess, requireOperation, writableFields } from "../request-access.js";
4
+ import { accepts, servedType } from "./media-types.js";
5
+ import { cleanName, tenantSegment, variantPath } from "./uri.js";
6
6
  import { issueTicket } from "./ticket.js";
7
7
  import { ticketScope } from "./attach.js";
8
8
  import { ticketSeconds } from "./runtime.js";
@@ -0,0 +1,8 @@
1
+ import "altcha-lib";
2
+ //#region src/forms/challenge.d.ts
3
+ /** The range of rounds a challenge is made for. */
4
+ export type Difficulty = {
5
+ min: number;
6
+ max: number;
7
+ };
8
+ //#endregion
@@ -0,0 +1,71 @@
1
+ import { CappedMap, createChallenge, randomInt, verifySolution } from "altcha-lib";
2
+ import { deriveKey } from "altcha-lib/algorithms/web/sha";
3
+ //#region src/forms/challenge.ts
4
+ /**
5
+ * How many SHA-256 rounds a visitor's browser runs to send a form: the challenge is made for one count in this range,
6
+ * and the solver counts up to it. About a second in Chromium on a current laptop: 0.4 s at the low end and 1.1 s at
7
+ * the high end, measured on an Apple M1 Pro.
8
+ */
9
+ const formDifficulty = {
10
+ min: 5e4,
11
+ max: 1e5
12
+ };
13
+ const expiresInSeconds = 300;
14
+ const spentCapacity = 1e4;
15
+ const encoder = new TextEncoder();
16
+ const toHex = (bytes) => [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
17
+ const purposeKey = async (secret, info) => {
18
+ const material = await crypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
19
+ return toHex(await crypto.subtle.deriveBits({
20
+ name: "HKDF",
21
+ hash: "SHA-256",
22
+ salt: /* @__PURE__ */ new Uint8Array(0),
23
+ info: encoder.encode(info)
24
+ }, material, 256));
25
+ };
26
+ const isProof = (value) => {
27
+ if (typeof value !== "object" || value === null) return false;
28
+ const { challenge, solution } = value;
29
+ return typeof challenge === "object" && challenge !== null && typeof challenge.parameters === "object" && typeof solution === "object" && solution !== null;
30
+ };
31
+ /**
32
+ * The bot check of public forms: a signed proof-of-work challenge (ALTCHA) per submission, made for one form and valid
33
+ * for five minutes, which verifies once. The secret signs the challenges, so they verify on every instance that shares it.
34
+ */
35
+ const createFormChallenges = (secret, { difficulty = formDifficulty, now = Date.now } = {}) => {
36
+ const keys = Promise.all([purposeKey(secret, "protobase forms challenge"), purposeKey(secret, "protobase forms key")]);
37
+ const spent = new CappedMap({ maxSize: spentCapacity });
38
+ const issue = async (form) => {
39
+ const [hmacSignatureSecret, hmacKeySignatureSecret] = await keys;
40
+ return createChallenge({
41
+ algorithm: "SHA-256",
42
+ cost: 1,
43
+ deriveKey,
44
+ counter: randomInt(difficulty.max, difficulty.min),
45
+ data: { form },
46
+ expiresAt: Math.floor(now() / 1e3) + expiresInSeconds,
47
+ hmacSignatureSecret,
48
+ hmacKeySignatureSecret
49
+ });
50
+ };
51
+ const verify = async (form, proof) => {
52
+ if (!isProof(proof) || proof.challenge.parameters.data?.form !== form) return "invalid";
53
+ const [hmacSignatureSecret, hmacKeySignatureSecret] = await keys;
54
+ const result = await verifySolution({
55
+ challenge: proof.challenge,
56
+ solution: proof.solution,
57
+ deriveKey,
58
+ hmacSignatureSecret,
59
+ hmacKeySignatureSecret
60
+ });
61
+ if (result.expired) return "expired";
62
+ if (!result.verified) return "invalid";
63
+ return spent.setIfAbsent(proof.challenge.signature, true) ? "ok" : "replayed";
64
+ };
65
+ return {
66
+ issue,
67
+ verify
68
+ };
69
+ };
70
+ //#endregion
71
+ export { createFormChallenges, formDifficulty };
@@ -0,0 +1,59 @@
1
+ import { requiredOnCreate } from "../validate-body.js";
2
+ //#region src/forms/check-forms.ts
3
+ const plainTypes = [
4
+ "text",
5
+ "integer",
6
+ "bigint",
7
+ "decimal",
8
+ "boolean",
9
+ "date",
10
+ "enum",
11
+ "currency",
12
+ "country"
13
+ ];
14
+ const fieldErrors = (registry, { form, view }, granted) => {
15
+ const entry = registry.find(view.resource);
16
+ if (!entry) return [];
17
+ const { model } = entry;
18
+ if (!granted) return [`the anonymous role must grant creating its fields: { '${view.resource}.create': [${form.fields.map((name) => `'${name}'`).join(", ")}] }`];
19
+ const fields = form.fields.flatMap((name) => {
20
+ const field = model.fields[name];
21
+ if (!field) return [`field "${name}" does not exist on ${view.resource}`];
22
+ if (field.readOnly) return [`field "${name}" is read-only, so nobody fills it in`];
23
+ if (field.sensitive) return [`field "${name}" is sensitive, and a public form does not take sensitive fields`];
24
+ if (field.type === "relation") return [`field "${name}" is a relation, whose records a visitor cannot read to pick one`];
25
+ if (field.type === "file") return [`field "${name}" is a file field, and a visitor cannot upload`];
26
+ if (!plainTypes.includes(field.type)) return [`field "${name}" is ${field.type}, which a form has no input for`];
27
+ if (name === model.tenant) return [`field "${name}" is the tenant, which the server sets`];
28
+ if (!granted.includes(name)) return [`the anonymous role does not create "${name}": add it to { '${view.resource}.create': [...] }`];
29
+ return [];
30
+ });
31
+ const sources = entry.source.state.fields ?? {};
32
+ const missing = Object.values(model.fields).filter((field) => !field.readOnly && field.name !== model.tenant && !form.fields.includes(field.name)).filter((field) => sources[field.name] && requiredOnCreate(field, sources[field.name].schema)).map((field) => `it leaves out "${field.name}", which every create must give, so no submission could be saved`);
33
+ return [...fields, ...missing];
34
+ };
35
+ /**
36
+ * What stops startup about the public forms: a name two forms share, a resource that does not exist, and fields a
37
+ * visitor cannot fill in, the anonymous role does not create, or that leave out a field the create needs. Identical forms
38
+ * on several views of one resource, such as one view built on another, count as one. Returns the forms by name.
39
+ */
40
+ const checkForms = (registry, declared, anonymousCreates) => {
41
+ const forms = /* @__PURE__ */ new Map();
42
+ const problems = [];
43
+ for (const item of declared) {
44
+ const { form, view } = item;
45
+ const where = `View "${view.resource}": form "${form.name}"`;
46
+ const taken = forms.get(form.name);
47
+ if (taken) {
48
+ if (!(taken.view.resource === view.resource && JSON.stringify(taken.form) === JSON.stringify(form))) problems.push(`${where}: the name is taken by a form of ${taken.view.resource}; form names are unique across the app`);
49
+ continue;
50
+ }
51
+ forms.set(form.name, item);
52
+ if (!registry.find(view.resource)) problems.push(`${where}: there is no resource "${view.resource}"`);
53
+ problems.push(...fieldErrors(registry, item, anonymousCreates(view.resource)).map((problem) => `${where}: ${problem}`));
54
+ }
55
+ if (problems.length > 0) throw new Error(`The public forms cannot be served:\n${problems.map((problem) => ` - ${problem}`).join("\n")}`);
56
+ return forms;
57
+ };
58
+ //#endregion
59
+ export { checkForms };
@@ -0,0 +1,38 @@
1
+ import { getIP } from "better-auth/api";
2
+ //#region src/forms/rate-limit.ts
3
+ const windowMs = 6e4;
4
+ /**
5
+ * The client a request counts against, as Better Auth's sign-in limit reads it: an `X-Forwarded-For` of one valid
6
+ * address (IPv6 by its /64), or `unknown`, one bucket for every request without one (several addresses included, since
7
+ * the leftmost is whatever the client sent). In development and tests a request without one is `127.0.0.1`.
8
+ */
9
+ const clientAddress = (headers) => getIP(headers, {}) ?? "unknown";
10
+ /**
11
+ * At most `perMinute` takes per key in a fixed window of a minute, kept in this process. Keys whose window has passed are
12
+ * dropped as new ones come, so memory follows the clients of the last minute.
13
+ */
14
+ const createRateLimit = ({ now = Date.now } = {}) => {
15
+ const windows = /* @__PURE__ */ new Map();
16
+ const prune = (at) => {
17
+ for (const [key, window] of windows) if (at - window.start >= windowMs) windows.delete(key);
18
+ };
19
+ /** Counts a take for `key`: `0` when allowed, else the seconds until its window lets it through again. */
20
+ const take = (key, perMinute) => {
21
+ const at = now();
22
+ const current = windows.get(key);
23
+ if (!current || at - current.start >= windowMs) {
24
+ prune(at);
25
+ windows.set(key, {
26
+ start: at,
27
+ count: 1
28
+ });
29
+ return 0;
30
+ }
31
+ if (current.count >= perMinute) return Math.ceil((current.start + windowMs - at) / 1e3);
32
+ current.count += 1;
33
+ return 0;
34
+ };
35
+ return { take };
36
+ };
37
+ //#endregion
38
+ export { clientAddress, createRateLimit };
@@ -0,0 +1,83 @@
1
+ import { HttpProblem, badRequest, notFound } from "../problem.js";
2
+ import { runWrite } from "../write-pipeline.js";
3
+ import { anonymousSession } from "../anonymous.js";
4
+ import { pruneView } from "../view-access.js";
5
+ import { clientAddress } from "./rate-limit.js";
6
+ import { Hono } from "hono";
7
+ import { restrictModel } from "@protobase/schema";
8
+ //#region src/forms/routes.ts
9
+ const refusedProof = {
10
+ invalid: "The check that you are not a bot failed; send the form again",
11
+ expired: "The check that you are not a bot expired; send the form again",
12
+ replayed: "This check was already used; send the form again"
13
+ };
14
+ const formPage = (deps, { form, view }) => {
15
+ const entry = deps.registry.find(view.resource);
16
+ const model = restrictModel(entry.model, {
17
+ readableFields: form.fields,
18
+ writableFields: {
19
+ create: form.fields,
20
+ update: []
21
+ }
22
+ });
23
+ const pruned = pruneView(view, model, { [model.name]: model });
24
+ const shown = {
25
+ resource: view.resource,
26
+ ...view.names && { names: view.names },
27
+ fields: pruned.fields,
28
+ filters: [],
29
+ layout: [],
30
+ actions: []
31
+ };
32
+ return {
33
+ name: form.name,
34
+ ...form.title !== void 0 && { title: form.title },
35
+ fields: form.fields,
36
+ thanks: form.thanks,
37
+ resource: model,
38
+ view: shown
39
+ };
40
+ };
41
+ /**
42
+ * Beside the API, without sign-in: `GET <systemPath>/forms/<name>` describes a public form, `GET .../<name>:challenge`
43
+ * hands out a bot check, and `POST .../<name>` with `{ values, proof }` creates the record as the anonymous role, limited
44
+ * to the form's fields, after the rate limit and the check. It answers `204`: the visitor never reads the record back.
45
+ */
46
+ const formRoutes = (deps, runtime) => {
47
+ const app = new Hono();
48
+ const find = (target) => {
49
+ const colon = target.indexOf(":");
50
+ const declared = runtime.forms.get(colon < 0 ? target : target.slice(0, colon));
51
+ if (!declared) throw notFound(`There is no form "${colon < 0 ? target : target.slice(0, colon)}"`);
52
+ return {
53
+ declared,
54
+ action: colon < 0 ? "" : target.slice(colon + 1)
55
+ };
56
+ };
57
+ app.get("/:target", async (c) => {
58
+ const { declared, action } = find(c.req.param("target"));
59
+ if (action === "challenge") return c.json(await runtime.challenges.issue(declared.form.name), 200, { "Cache-Control": "no-store" });
60
+ if (action !== "") throw notFound(`There is no method ":${action}"`);
61
+ return c.json(formPage(deps, declared));
62
+ });
63
+ app.post("/:target", async (c) => {
64
+ const { declared, action } = find(c.req.param("target"));
65
+ if (action !== "") throw notFound(`There is no method ":${action}"`);
66
+ const { form, view } = declared;
67
+ const wait = runtime.limit.take(`${form.name}|${clientAddress(c.req.raw.headers)}`, form.perMinute);
68
+ if (wait > 0) throw new HttpProblem(429, "too-many-submissions", "Too Many Requests", "This form was sent too often; wait a minute and send it again", {}, { "Retry-After": String(wait) });
69
+ const body = await c.req.json();
70
+ if (typeof body !== "object" || body === null || Array.isArray(body)) throw badRequest("invalid-submission", "Send the form as { values, proof }");
71
+ const { values, proof } = body;
72
+ const verdict = await runtime.challenges.verify(form.name, proof);
73
+ if (verdict !== "ok") throw badRequest("bot-check-failed", refusedProof[verdict]);
74
+ await runWrite(deps, deps.registry.find(view.resource), anonymousSession(), {
75
+ operation: "create",
76
+ body: values
77
+ }, form);
78
+ return c.body(null, 204);
79
+ });
80
+ return app;
81
+ };
82
+ //#endregion
83
+ export { formRoutes };
@@ -0,0 +1,31 @@
1
+ import { createRateLimit } from "./rate-limit.js";
2
+ import { createFormChallenges } from "./challenge.js";
3
+ import { checkForms } from "./check-forms.js";
4
+ import { anonymousRole } from "@protobase/schema";
5
+ //#region src/forms/runtime.ts
6
+ /** The fields the anonymous role may create on `resource`, or `undefined` when it may not create there. */
7
+ const anonymousCreates = (limits) => (resource) => {
8
+ const held = limits.filter((limit) => limit.role === anonymousRole && limit.capability === `${resource}.create`);
9
+ return held.length === 0 ? void 0 : held.flatMap((limit) => limit.fields ?? []);
10
+ };
11
+ /**
12
+ * The public forms of the views, checked, with the challenges that guard them and their rate limit; `undefined` without
13
+ * any. Fails at startup on a form that cannot be served and when nothing signs the challenges.
14
+ */
15
+ const createFormsRuntime = ({ registry, views, limits, env, difficulty }) => {
16
+ const declared = [...views].sort((a, b) => Number(a.roles !== void 0) - Number(b.roles !== void 0)).flatMap((view) => (view.forms ?? []).map((form) => ({
17
+ form,
18
+ view
19
+ })));
20
+ if (declared.length === 0) return void 0;
21
+ const forms = checkForms(registry, declared, anonymousCreates(limits));
22
+ const secret = env.PROTOBASE_FORMS_SECRET ?? env.BETTER_AUTH_SECRET;
23
+ if (!secret) throw new Error("Public forms need a secret to sign their bot checks: set PROTOBASE_FORMS_SECRET or BETTER_AUTH_SECRET");
24
+ return {
25
+ forms,
26
+ challenges: createFormChallenges(secret, { ...difficulty && { difficulty } }),
27
+ limit: createRateLimit()
28
+ };
29
+ };
30
+ //#endregion
31
+ export { createFormsRuntime };
@@ -6,14 +6,15 @@ import { fetchReadable } from "../read-service.js";
6
6
  import { quoteIdent } from "../changes/capture-sql.js";
7
7
  import { parseParams } from "../params.js";
8
8
  import { encodeKey } from "@protobase/schema/internal";
9
- import { sql } from "kysely";
10
9
  import { z } from "zod";
10
+ import { sql } from "kysely";
11
11
  //#region src/history/read-history.ts
12
12
  const params = z.object({
13
13
  page_size: z.coerce.number().int().min(0).max(100).optional(),
14
14
  page_token: z.string().regex(/^\d+$/, "Use the next_page_token of the previous page").optional()
15
15
  });
16
16
  const defaultPageSize = 20;
17
+ const visitorName = "Visitor";
17
18
  const picked = (row, names) => row ? Object.fromEntries(Object.entries(row).filter(([name]) => names.has(name))) : void 0;
18
19
  const shown = (row, readable, names) => {
19
20
  const values = new Set(Object.values(readable.fields).filter((field) => !field.sensitive && !field.file).map((field) => field.name));
@@ -21,7 +22,7 @@ const shown = (row, readable, names) => {
21
22
  if (row.operation === "update" && changed.length === 0) return [];
22
23
  const before = picked(row.before, values);
23
24
  const after = picked(row.after, values);
24
- const name = row.actor_id === null ? void 0 : names[row.actor_id];
25
+ const name = row.actor_id === null ? void 0 : row.actor_id === "" ? visitorName : names[row.actor_id];
25
26
  return [{
26
27
  id: row.id,
27
28
  at: row.at,
@@ -61,7 +62,7 @@ const readHistory = async (deps, entry, session, key, query) => {
61
62
  });
62
63
  if (!rows) throw notFound(`No ${entry.name} with this key`);
63
64
  const page = rows.slice(0, size);
64
- const ids = [...new Set(page.flatMap((row) => row.actor_id === null ? [] : [row.actor_id]))];
65
+ const ids = [...new Set(page.flatMap((row) => row.actor_id === null || row.actor_id === "" ? [] : [row.actor_id]))];
65
66
  const names = ids.length > 0 && actorNames ? await actorNames(ids) : {};
66
67
  return {
67
68
  items: page.flatMap((row) => shown(row, access.readable, names)),
package/dist/index.d.ts CHANGED
@@ -9,16 +9,18 @@ import { PlatformSignIn, readPlatformSignIn } from "./better-auth/platform-sign-
9
9
  import { EmailComponent, EmailOptions, EmailTemplate, RenderedEmail, defineEmail } from "./mail/define-email.js";
10
10
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, createAuth, normalizeRoles } from "./better-auth/create-auth.js";
11
11
  import { EventsOptions } from "./changes/events-options.js";
12
- import { HistoryOptions } from "./history/options.js";
13
12
  import { StandardSchema, readJson } from "./functions/read-json.js";
14
13
  import { EnqueueOptions, JobContext, JobDefinition, JobOptions } from "./jobs/job-types.js";
15
14
  import { Admin, AuditEvent, AuditQueue, Authenticator, PipelineHook, ScanGuardMode, Session, SessionOrganization, TenantValue, User, ViewSource, WriteEvent, WriteOperation } from "./types.js";
15
+ import { CommentUsers } from "./comments/comment-users.js";
16
+ import { HistoryOptions } from "./history/options.js";
16
17
  import { WebhooksOptions } from "./webhooks/table/options.js";
17
18
  import { EventData, RunContext, SubscriptionDefinition, SubscriptionEvent, SubscriptionOptions, subscription } from "./webhooks/subscription.js";
18
19
  import { Sharp, renderVariant, thumbHash } from "./files/images.js";
19
20
  import { FileProvider, FileStore, StoredObject } from "./files/store.js";
20
21
  import { FilesOptions } from "./files/options.js";
21
22
  import { ErrorReportingOptions, ErrorReportingSettings, errorReportingSettings, errorReportingVariable } from "./error-reporting/error-reporting-settings.js";
23
+ import { ChatMessage, ChatTextPart, Completion, CompletionOptions, ModelError, ModelSettings, ReasoningDetail, ReasoningEffort, ToolCall, ToolSpec, streamChatCompletion } from "./assistant/chat-completions.js";
22
24
  import { CleanupEntry, CleanupSchedule, ScheduledDelete, storageCleanupSchedule } from "./files/cleanup-schedule.js";
23
25
  import { CleanupReport } from "./files/cleanup-runner.js";
24
26
  import { MapLayer } from "./maps/map-layers.js";
@@ -30,7 +32,6 @@ import { RecordKeyInput, ToolRecord, ToolRecords } from "./assistant/records.js"
30
32
  import { FunctionRecords, ListParams, RecordPage } from "./functions/records.js";
31
33
  import { ApiFunction, CorsOptions, FunctionApp, FunctionCaller, FunctionContext, FunctionHandler, FunctionOptions, FunctionSource, ProtectedFunctionOptions, PublicFunctionContext, PublicFunctionHandler, PublicFunctionOptions, defineFunction } from "./functions/define-function.js";
32
34
  import { CorsOrigins } from "./api-cors.js";
33
- import { ChatMessage, ChatTextPart, Completion, CompletionOptions, ModelError, ModelSettings, ReasoningDetail, ReasoningEffort, ToolCall, ToolSpec, streamChatCompletion } from "./assistant/chat-completions.js";
34
35
  import { ConversationRecord, ConversationStore } from "./assistant/conversation-store.js";
35
36
  import { Conversation, ConversationsOptions, conversationKey, createConversations } from "./assistant/conversations.js";
36
37
  import { ApprovalAnswer, ApprovalRequest, requestApproval } from "./assistant/approval.js";
@@ -61,6 +62,9 @@ import { parseColumns, readOutbox } from "./changes/outbox.js";
61
62
  import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
62
63
  import { webhooks } from "./webhooks/table/plugin.js";
63
64
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
65
+ import { checkComments, commentsMigration } from "./comments/comments-sql.js";
66
+ import { CheckAiInput, checkAi } from "./ai/check-ai.js";
67
+ import { CommentItem } from "./comments/comment-item.js";
64
68
  import { MemberInput, NewOrganization, addAppRoleToOwners, addMember, createOrganization, listOrganizations, removeMember, setMemberRoles } from "./better-auth/organizations/host.js";
65
69
  import { AssistantBackend, assistantProtocolRoutes } from "./assistant/protocol-routes.js";
66
70
  import { fileConversationStore } from "./assistant/file-conversation-store.js";
@@ -87,4 +91,4 @@ import { FatalJobError } from "./jobs/run-job.js";
87
91
  import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
88
92
  import { jobsDrainApp } from "./jobs/drain-route.js";
89
93
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
90
- export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, 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 Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
94
+ export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentUsers, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, type HistoryOptions, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type MemberInput, MissingCredentials, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, 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 Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, beforeSignInPluginsVariable, betterAuthAuthenticator, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkJobsSchema, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentsMigration, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineEmail, defineFunction, defineJob, defineTool, deleteUser, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, handleDroppedConnections, hasUsers, imageSize, isCaptured, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, limitedFileStore, listOrganizations, listUsers, localFileStore, localFiles, mapSettings, mapStyleUrl, mapStyleVariable, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, s3Files, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storeFile, streamChatCompletion, subscription, thumbHash, unauthorized, variantPath, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };