@protobase/server 0.66.6 → 0.66.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 (45) hide show
  1. package/dist/assistant/delete-record-tool.d.ts +1 -1
  2. package/dist/assistant/delete-record-tool.js +11 -11
  3. package/dist/better-auth/auth-mail/auth-emails.d.ts +2 -0
  4. package/dist/better-auth/auth-mail/auth-emails.js +3 -1
  5. package/dist/better-auth/auth-mail/confirmation-code-email.d.ts +1 -0
  6. package/dist/better-auth/auth-mail/confirmation-code-email.js +32 -0
  7. package/dist/better-auth/auth-mail/password-changed-email.d.ts +1 -0
  8. package/dist/better-auth/auth-mail/password-changed-email.js +32 -0
  9. package/dist/better-auth/auth-settings.d.ts +108 -2
  10. package/dist/better-auth/confirmation-code-plugin.d.ts +9 -0
  11. package/dist/better-auth/confirmation-code-plugin.js +95 -0
  12. package/dist/better-auth/confirmation-codes.js +54 -0
  13. package/dist/better-auth/create-auth.d.ts +119 -3
  14. package/dist/better-auth/create-auth.js +34 -7
  15. package/dist/better-auth/mcp/revoke-grants.js +7 -4
  16. package/dist/better-auth/mobile-requests.js +23 -3
  17. package/dist/better-auth/password-change.js +68 -0
  18. package/dist/better-auth/password-checks.js +69 -0
  19. package/dist/better-auth/password-reset.js +2 -1
  20. package/dist/better-auth/recent-confirmation.js +58 -0
  21. package/dist/better-auth/sign-in-mail.js +8 -5
  22. package/dist/better-auth/sign-up/sign-up-code.js +4 -3
  23. package/dist/better-auth/sign-up/sign-up-plugin.js +3 -2
  24. package/dist/better-auth/staff-checks.js +2 -0
  25. package/dist/better-auth/strong-sign-ins.js +2 -2
  26. package/dist/built-in-resources.d.ts +89 -0
  27. package/dist/built-in-resources.js +31 -0
  28. package/dist/changes/capture-schema.js +3 -1
  29. package/dist/comments/comments-sql.d.ts +4 -1
  30. package/dist/comments/comments-sql.js +16 -7
  31. package/dist/comments/record-comments.js +1 -1
  32. package/dist/config-exports.d.ts +3 -1
  33. package/dist/config-exports.js +3 -1
  34. package/dist/create-admin.js +9 -16
  35. package/dist/gotrue/user-routes.js +6 -4
  36. package/dist/has-indexes.js +9 -0
  37. package/dist/history/history-sql.js +13 -4
  38. package/dist/internal.js +1 -1
  39. package/dist/mcp/mcp-input-schemas.js +24 -0
  40. package/dist/mcp/mcp-server.js +3 -9
  41. package/dist/package.js +1 -1
  42. package/dist/reveal-service.js +1 -1
  43. package/dist/webhooks/table/schema-sql.js +30 -6
  44. package/dist/webhooks/table/schema.js +10 -4
  45. package/package.json +4 -4
@@ -1,6 +1,8 @@
1
1
  import { isCaptured, quoteIdent, tableSql } from "./capture-sql.js";
2
+ import { historyIndexes } from "../history/history-sql.js";
2
3
  import { capturePlan, planSql, uncaptured } from "./capture-plan.js";
3
4
  import { changesSchema } from "./events-options.js";
5
+ import { hasIndexes } from "../has-indexes.js";
4
6
  import { sql } from "kysely";
5
7
  //#region src/changes/capture-schema.ts
6
8
  /** The command that writes what the config needs as a migration file of the project. */
@@ -25,7 +27,7 @@ const readCaptureState = async (db, schema, models) => {
25
27
  where not t.tgisinternal and (t.tgfoid::text = ${capture?.oid ?? ""} or t.tgrelid::text = any(${oids}::text[]))`);
26
28
  return {
27
29
  outbox: await exists("outbox"),
28
- history: await exists("history"),
30
+ history: await exists("history") && await hasIndexes(db, schema, "history", historyIndexes.map(([name]) => name)),
29
31
  notices: await exists("notices"),
30
32
  ...capture && { capture },
31
33
  triggers: triggers.map((trigger) => ({
@@ -1,7 +1,10 @@
1
1
  import { ResourceModel } from "@protobase/schema";
2
2
  import { Db } from "@protobase/query";
3
3
  //#region src/comments/comments-sql.d.ts
4
- /** The SQL that adds the `comments` schema and its table, `undefined` when it is there. The table is never dropped. */
4
+ /**
5
+ * The SQL that adds the `comments` schema and its table, or the indexes it lacks, `undefined` when it has them all. The
6
+ * table is never dropped.
7
+ */
5
8
  export declare const commentsMigration: (db: Db) => Promise<string | undefined>;
6
9
  /** Refuses to go on while a resource has `.comments()` and the database lacks their table; the project's migrations add it. */
7
10
  export declare const checkComments: (db: Db, models: readonly ResourceModel[]) => Promise<void>;
@@ -1,8 +1,17 @@
1
+ import { hasIndexes } from "../has-indexes.js";
1
2
  import { migrationCommand } from "../changes/capture-schema.js";
2
3
  import { sql } from "kysely";
3
4
  //#region src/comments/comments-sql.ts
4
5
  /** The table the comments of every resource with `.comments()` are kept in, in a schema of its own. */
5
6
  const commentsTable = "\"comments\".\"comments\"";
7
+ /** An index for each read of the comments: the record pages', the admin list's filters, by name. */
8
+ const commentsIndexes = [
9
+ ["comments_record", "\"resource\", \"record_key\", \"id\""],
10
+ ["comments_tenant", "\"tenant_id\", \"id\""],
11
+ ["comments_author", "\"author_id\", \"id\""],
12
+ ["comments_created_at", "\"created_at\""],
13
+ ["comments_record_key", "\"record_key\""]
14
+ ];
6
15
  /**
7
16
  * A row per comment: the record it is on by resource and encoded key, its tenant, its author and the user ids it
8
17
  * mentions (`[]` for none). Every column the admin list filters on has an index.
@@ -17,12 +26,12 @@ const commentsTableSql = () => `create table if not exists ${commentsTable} (
17
26
  "mentions" jsonb not null,
18
27
  "created_at" timestamptz not null default now()
19
28
  );
20
- create index if not exists "comments_record" on ${commentsTable} ("resource", "record_key", "id");
21
- create index if not exists "comments_tenant" on ${commentsTable} ("tenant_id", "id");
22
- create index if not exists "comments_author" on ${commentsTable} ("author_id", "id");
23
- create index if not exists "comments_created_at" on ${commentsTable} ("created_at")`;
24
- const hasCommentsTable = async (db) => (await sql`select to_regclass('comments.comments') is not null as found`.execute(db)).rows[0].found;
25
- /** The SQL that adds the `comments` schema and its table, `undefined` when it is there. The table is never dropped. */
29
+ ${commentsIndexes.map(([name, columns]) => `create index if not exists "${name}" on ${commentsTable} (${columns})`).join(";\n")}`;
30
+ const hasCommentsTable = async (db) => (await sql`select to_regclass('comments.comments') is not null as found`.execute(db)).rows[0].found && await hasIndexes(db, "comments", "comments", commentsIndexes.map(([name]) => name));
31
+ /**
32
+ * The SQL that adds the `comments` schema and its table, or the indexes it lacks, `undefined` when it has them all. The
33
+ * table is never dropped.
34
+ */
26
35
  const commentsMigration = async (db) => {
27
36
  if (await hasCommentsTable(db)) return void 0;
28
37
  return `create schema if not exists "comments";\n\n${commentsTableSql()};`;
@@ -31,7 +40,7 @@ const commentsMigration = async (db) => {
31
40
  const checkComments = async (db, models) => {
32
41
  if (!models.some((model) => model.comments)) return;
33
42
  if (await hasCommentsTable(db)) return;
34
- throw new Error(`The comments schema is missing, and resources with .comments() keep their comments in comments.comments. Write it as a migration with \`${migrationCommand}\`, apply it with the project's migrations, then start again.`);
43
+ throw new Error(`The comments schema is missing or behind this version of Protobase, and resources with .comments() keep their comments in comments.comments. Write it as a migration with \`${migrationCommand}\`, apply it with the project's migrations, then start again.`);
35
44
  };
36
45
  //#endregion
37
46
  export { checkComments, commentsMigration, commentsTable, commentsTableSql };
@@ -15,7 +15,7 @@ const commentsResource = ({ tenant }) => {
15
15
  author_id: f.text().readOnly().filterable(),
16
16
  body: f.text().readOnly(),
17
17
  mentions: f.json().readOnly(),
18
- created_at: f.timestamp().readOnly().filterable()
18
+ created_at: f.timestamp().readOnly().dbDefault().filterable()
19
19
  }).primaryKey((r) => r.id).access({
20
20
  list: admins,
21
21
  read: admins,
@@ -1,2 +1,4 @@
1
1
  import { configExports } from "./resource-source.js";
2
- export { configExports };
2
+ import { builtInResources } from "./built-in-resources.js";
3
+ import { webhooksModule } from "./webhooks/table/webhooks-module.js";
4
+ export { builtInResources, configExports, webhooksModule };
@@ -1,2 +1,4 @@
1
+ import { builtInResources } from "./built-in-resources.js";
1
2
  import { configExports } from "./resource-source.js";
2
- export { configExports };
3
+ import { webhooksModule } from "./webhooks/table/webhooks-module.js";
4
+ export { builtInResources, configExports, webhooksModule };
@@ -50,8 +50,6 @@ import { appOrigin } from "./comments/app-origin.js";
50
50
  import { betterAuthActorNames } from "./history/actor-names.js";
51
51
  import { betterAuthCommentUsers } from "./comments/comment-users.js";
52
52
  import { mentionJob } from "./comments/mention-job.js";
53
- import { commentsResource, commentsView } from "./comments/record-comments.js";
54
- import { activityResource, activityView } from "./history/activity.js";
55
53
  import { historyDays } from "./history/options.js";
56
54
  import { pruneHistoryJob } from "./history/prune-history.js";
57
55
  import { callerRecords } from "./functions/records.js";
@@ -113,7 +111,7 @@ import { mcpIcons } from "./web-app/mcp-icons.js";
113
111
  import { recordRoutes } from "./routes/record.js";
114
112
  import { firstAdminUrl } from "./better-auth/first-admin-url.js";
115
113
  import { createScanGuard } from "./scan-guard.js";
116
- import { failedJobs, failedJobsView } from "./jobs/failed-jobs.js";
114
+ import { builtInResources } from "./built-in-resources.js";
117
115
  import { Hono } from "hono";
118
116
  import { accountPage, accountPageName, checkPages, settingsResources, withSettingsFields } from "@protobase/layout";
119
117
  import { guestCallsPath, ringsPath, statusPath } from "@protobase/schema/internal";
@@ -129,22 +127,17 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
129
127
  const appModels = appResources.map((resource) => resource.toModel());
130
128
  const withComments = appModels.some((model) => model.comments);
131
129
  const withAiFields = hasAiFields(appModels);
132
- const withJobs = jobs.length > 0 || appModels.some(isCaptured) || withComments || withAiFields || notifications !== void 0;
133
130
  const withHistory = appModels.some((model) => model.history);
134
131
  const changes = changesSchema(events);
135
132
  const days = historyDays(historyOptions);
136
- const resources = [
137
- ...appResources,
138
- ...withJobs ? [failedJobs] : [],
139
- ...withHistory ? [activityResource(changes, { tenant: appModels.some((model) => model.history && model.tenant) })] : [],
140
- ...withComments ? [commentsResource({ tenant: appModels.some((model) => model.comments && model.tenant) })] : []
141
- ];
142
- const views = [
143
- ...appViews,
144
- ...withJobs ? [failedJobsView] : [],
145
- ...withHistory ? [activityView] : [],
146
- ...withComments ? [commentsView] : []
147
- ];
133
+ const builtIn = builtInResources({
134
+ models: appModels,
135
+ jobs: jobs.length > 0,
136
+ notifications: notifications !== void 0,
137
+ ...events && { events }
138
+ });
139
+ const resources = [...appResources, ...builtIn.resources];
140
+ const views = [...appViews, ...builtIn.views];
148
141
  const commentUsers = withComments ? givenCommentUsers ?? (auth && betterAuthCommentUsers(auth)) : void 0;
149
142
  const mentions = withComments ? mentionJob(commentUsers, shownAppName(auth, globalThis.process?.env ?? {})) : void 0;
150
143
  const registry = buildRegistry(resources);
@@ -24,9 +24,10 @@ const logoutScopes = [
24
24
  /**
25
25
  * The signed-in user's own endpoints, with their access token: `GET /user`, `PUT /user` for a new password or metadata,
26
26
  * and `POST /logout`. A token whose session has ended, by signing out or a ban, is GoTrue's `session_not_found`.
27
- * With MCP on, signing out everywhere or the other sessions, and setting a password, disconnect the user's MCP clients,
28
- * as the same steps do through Better Auth: GoTrue has no way to tell a password reset from a change, so every new
29
- * password does.
27
+ * With MCP on, signing out everywhere or the other sessions disconnects the user's MCP clients, as the same steps do
28
+ * through Better Auth. A new password signs out the other sessions, with the MCP clients, and mails the owner, as a
29
+ * change through Better Auth does: GoTrue has no way to tell a password reset or a first password from a change, so
30
+ * every new password does.
30
31
  */
31
32
  const userRoutes = ({ auth, context }) => {
32
33
  const claimsOf = accessClaimsOf(goTrueTokens({ auth }));
@@ -85,7 +86,8 @@ const userRoutes = ({ auth, context }) => {
85
86
  accountId: user.id,
86
87
  password: hash
87
88
  });
88
- if (auth.mcp) await revokeMcpGrants(ctx.adapter, user.id);
89
+ await signOut(found.claims, "others");
90
+ await auth.api.passwordChanged({ body: { email: user.email } });
89
91
  }
90
92
  const merged = data && Object.fromEntries(Object.entries({
91
93
  ...userMetadata(user.metadata),
@@ -0,0 +1,9 @@
1
+ import { sql } from "kysely";
2
+ //#region src/has-indexes.ts
3
+ /** Whether the table `schema`.`table` has an index of each name in `names`. Only reads the catalogue. */
4
+ const hasIndexes = async (db, schema, table, names) => {
5
+ const { rows } = await sql`select indexname as name from pg_indexes where schemaname = ${schema} and tablename = ${table}`.execute(db);
6
+ return names.every((name) => rows.some((row) => row.name === name));
7
+ };
8
+ //#endregion
9
+ export { hasIndexes };
@@ -6,6 +6,17 @@ import { quoteIdent } from "../changes/capture-sql.js";
6
6
  * ones included. `actor_id` and `actor_roles` are the API's caller, `null` for a write from elsewhere; `database_role`
7
7
  * is the role that wrote, `null` for a change captured before Protobase kept it.
8
8
  */
9
+ /** An index for each read the record pages and the Activity list take, by name, so a migration can add the ones a table lacks. */
10
+ const historyIndexes = [
11
+ ["history_record", "\"resource\", \"record_key\", \"id\""],
12
+ ["history_tenant", "\"tenant_id\", \"id\""],
13
+ ["history_at", "\"at\""],
14
+ ["history_record_key", "\"record_key\""],
15
+ ["history_operation", "\"operation\""],
16
+ ["history_actor", "\"actor_id\""],
17
+ ["history_database_role", "\"database_role\""],
18
+ ["history_transaction", "\"transaction\""]
19
+ ];
9
20
  const historyTableSql = (schema) => {
10
21
  const table = `${quoteIdent(schema)}."history"`;
11
22
  return `create table if not exists ${table} (
@@ -23,9 +34,7 @@ const historyTableSql = (schema) => {
23
34
  "transaction" bigint not null,
24
35
  "at" timestamptz not null
25
36
  );
26
- create index if not exists "history_record" on ${table} ("resource", "record_key", "id");
27
- create index if not exists "history_tenant" on ${table} ("tenant_id", "id");
28
- create index if not exists "history_at" on ${table} ("at")`;
37
+ ${historyIndexes.map(([name, columns]) => `create index if not exists "${name}" on ${table} (${columns})`).join(";\n")}`;
29
38
  };
30
39
  //#endregion
31
- export { historyTableSql };
40
+ export { historyIndexes, historyTableSql };
package/dist/internal.js CHANGED
@@ -8,8 +8,8 @@ import { appPool, longQueryPool } from "./app-pool.js";
8
8
  import { betterAuthAuthenticator } from "./better-auth/authenticator.js";
9
9
  import { fileProviderOf } from "./files/provider-settings.js";
10
10
  import { declaredJob } from "./jobs/declared-job.js";
11
+ import { webhooksModule } from "./webhooks/table/webhooks-module.js";
11
12
  import { appAuthOptions, buildAppAuth } from "./better-auth/auth-settings.js";
12
13
  import { buildAuthenticator } from "./better-auth/authenticate-settings.js";
13
- import { webhooksModule } from "./webhooks/table/webhooks-module.js";
14
14
  import { checkCallsOptions } from "./calls/calls-options.js";
15
15
  export { appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
@@ -0,0 +1,24 @@
1
+ import { fromJsonSchema } from "@modelcontextprotocol/server";
2
+ //#region src/mcp/mcp-input-schemas.ts
3
+ /**
4
+ * Turns a tool's parameters object into its MCP input schema, keeping the last `limit` it turned, least recently used
5
+ * dropped first. The SDK compiles each schema object it is given into a cache that never lets go, so a server built per
6
+ * request from the same tools gets the same input schema and adds nothing to it.
7
+ */
8
+ const inputSchemaMemo = (limit) => {
9
+ const schemas = /* @__PURE__ */ new Map();
10
+ return (parameters) => {
11
+ const known = schemas.get(parameters);
12
+ if (known) {
13
+ schemas.delete(parameters);
14
+ schemas.set(parameters, known);
15
+ return known;
16
+ }
17
+ const schema = fromJsonSchema(parameters);
18
+ schemas.set(parameters, schema);
19
+ if (schemas.size > limit) schemas.delete(schemas.keys().next().value);
20
+ return schema;
21
+ };
22
+ };
23
+ //#endregion
24
+ export { inputSchemaMemo };
@@ -3,18 +3,12 @@ import { checkedTools } from "../assistant/checked-tools.js";
3
3
  import { refusalOf } from "../assistant/record-tool-args.js";
4
4
  import { ApprovalNeeded, approvalAsked, approvedUnasked, mcpApproval, writesUnasked } from "./mcp-approval.js";
5
5
  import { callHash, callScope, scopeHash } from "./mcp-call-hash.js";
6
+ import { inputSchemaMemo } from "./mcp-input-schemas.js";
6
7
  import { mcpInstructions } from "./mcp-instructions.js";
7
- import { CLIENT_CAPABILITIES_META_KEY, McpServer, fromJsonSchema } from "@modelcontextprotocol/server";
8
+ import { CLIENT_CAPABILITIES_META_KEY, McpServer } from "@modelcontextprotocol/server";
8
9
  //#region src/mcp/mcp-server.ts
9
10
  const capabilitiesOf = (envelope) => envelope?.[CLIENT_CAPABILITIES_META_KEY];
10
- const inputSchemas = /* @__PURE__ */ new WeakMap();
11
- const inputSchemaOf = (parameters) => {
12
- const known = inputSchemas.get(parameters);
13
- if (known) return known;
14
- const schema = fromJsonSchema(parameters);
15
- inputSchemas.set(parameters, schema);
16
- return schema;
17
- };
11
+ const inputSchemaOf = inputSchemaMemo(1e3);
18
12
  const failed = {
19
13
  content: [{
20
14
  type: "text",
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.66.6";
2
+ var version = "0.66.8";
3
3
  //#endregion
4
4
  export { version };
@@ -17,7 +17,7 @@ const revealedValue = (deps, model, field, record) => {
17
17
  });
18
18
  };
19
19
  /** The refusal of a reveal that needs a strong sign-in more recent than the caller's, which the app answers by asking them to confirm it's them. */
20
- const strongSignInRequired = (minutes) => new HttpProblem(403, "strong-sign-in-required", "Forbidden", `Confirm it's you with a passkey or a second step: this needs one in the last ${minutes} minutes.`, { minutes });
20
+ const strongSignInRequired = (minutes) => new HttpProblem(403, "strong-sign-in-required", "Forbidden", `Confirm it's you with a passkey, a second step or an emailed code: this needs one in the last ${minutes} minutes.`, { minutes });
21
21
  const requireStrongSignIn = async (session, minutes, now = /* @__PURE__ */ new Date()) => {
22
22
  const at = session.signIn && !session.signIn.staff && !session.client ? await session.signIn.strongAt?.() : void 0;
23
23
  if (at && now.getTime() - at.getTime() <= minutes * 6e4) return;
@@ -1,7 +1,7 @@
1
1
  import { jobsSchema } from "../../jobs/schema.js";
2
2
  import { quoteIdent } from "../../changes/capture-sql.js";
3
3
  /** The comment the schema's functions carry, which the startup check reads. */
4
- const webhooksComment = `Protobase webhooks 1`;
4
+ const webhooksComment = `Protobase webhooks 2`;
5
5
  /** The delivery jobs, whose waiting and failed ones the deliveries view lists. */
6
6
  const deliveryJobs = ["webhooks.deliver", "webhooks.deliver-ordered"];
7
7
  /** The job that sends a subscription a test event. */
@@ -34,8 +34,23 @@ const subscriptionsSql = (schema, changesSchema) => `create table if not exists
34
34
  "rotate_secret" boolean not null default false,
35
35
  "send_test" boolean not null default false,
36
36
  "resend_failed" boolean not null default false
37
- );
38
- create index if not exists "subscriptions_tenant" on ${quoteIdent(schema)}."subscriptions" ("tenant_id")`;
37
+ )`;
38
+ /**
39
+ * An index for each read of the subscriptions the API takes: the tenant and owner every caller's rows keep to, the
40
+ * sorts and filter the list offers, and pg_trgm ones for its search over the name and URL.
41
+ */
42
+ const subscriptionsIndexesSql = (schema) => {
43
+ const table = `${quoteIdent(schema)}."subscriptions"`;
44
+ return `create extension if not exists pg_trgm;
45
+ create index if not exists "subscriptions_tenant" on ${table} ("tenant_id");
46
+ create index if not exists "subscriptions_owner" on ${table} ("owner_id");
47
+ create index if not exists "subscriptions_created_at" on ${table} ("created_at");
48
+ create index if not exists "subscriptions_name" on ${table} ("name");
49
+ create index if not exists "subscriptions_enabled" on ${table} ("enabled");
50
+ create index if not exists "subscriptions_last_success_at" on ${table} ("last_success_at");
51
+ create index if not exists "subscriptions_name_search" on ${table} using gin ("name" gin_trgm_ops);
52
+ create index if not exists "subscriptions_url_search" on ${table} using gin ("url" gin_trgm_ops)`;
53
+ };
39
54
  const retryJobs = (where) => `update ${jobsSchema}.job
40
55
  set state = 'retry', retry_limit = retry_limit + 1, completed_on = null,
41
56
  start_after = greatest(start_after, ${jobsSchema}.job_now()), keep_until = greatest(keep_until, ${jobsSchema}.job_now() + interval '14 days')
@@ -110,7 +125,16 @@ const deliveryLogSql = (schema) => `create table if not exists ${quoteIdent(sche
110
125
  "response" text,
111
126
  "duration_ms" integer not null,
112
127
  "at" timestamptz not null default now()
113
- );
114
- create index if not exists "delivery_log_subscription" on ${quoteIdent(schema)}."delivery_log" ("subscription_id", "at")`;
128
+ )`;
129
+ /** An index for each filter and sort of the delivery log the API takes. */
130
+ const deliveryLogIndexesSql = (schema) => {
131
+ const table = `${quoteIdent(schema)}."delivery_log"`;
132
+ return `create index if not exists "delivery_log_subscription" on ${table} ("subscription_id", "at");
133
+ create index if not exists "delivery_log_at" on ${table} ("at");
134
+ create index if not exists "delivery_log_event_id" on ${table} ("event_id");
135
+ create index if not exists "delivery_log_event_type" on ${table} ("event_type");
136
+ create index if not exists "delivery_log_status" on ${table} ("status");
137
+ create index if not exists "delivery_log_status_code" on ${table} ("status_code")`;
138
+ };
115
139
  //#endregion
116
- export { commandsSql, deliveriesSql, deliveryJobs, deliveryLogSql, sendTestJob, subscriptionsSql, webhooksComment };
140
+ export { commandsSql, deliveriesSql, deliveryJobs, deliveryLogIndexesSql, deliveryLogSql, sendTestJob, subscriptionsIndexesSql, subscriptionsSql, webhooksComment };
@@ -2,7 +2,7 @@ import { quoteIdent } from "../../changes/capture-sql.js";
2
2
  import { changesSchema } from "../../changes/events-options.js";
3
3
  import { migrationCommand } from "../../changes/capture-schema.js";
4
4
  import { webhooksSchema } from "./options.js";
5
- import { commandsSql, deliveriesSql, deliveryLogSql, subscriptionsSql, webhooksComment } from "./schema-sql.js";
5
+ import { commandsSql, deliveriesSql, deliveryLogIndexesSql, deliveryLogSql, subscriptionsIndexesSql, subscriptionsSql, webhooksComment } from "./schema-sql.js";
6
6
  import { sql } from "kysely";
7
7
  //#region src/webhooks/table/schema.ts
8
8
  const functionNames = ["subscription_commands", "deliveries_resend"];
@@ -25,12 +25,18 @@ const webhooksMigration = async (db, options = {}, events) => {
25
25
  const schema = webhooksSchema(options);
26
26
  const found = await readWebhooksSchema(db, schema);
27
27
  const needsLog = Boolean(options.deliveryLog) && !found.deliveryLog;
28
- if (found.subscriptions && found.current && !needsLog) return void 0;
28
+ const current = found.subscriptions && found.current;
29
+ if (current && !needsLog) return void 0;
29
30
  return `${[
30
31
  `create schema if not exists ${quoteIdent(schema)}`,
31
32
  ...found.subscriptions ? [] : [subscriptionsSql(schema, changesSchema(events))],
32
- ...found.current && found.subscriptions ? [] : [commandsSql(schema), deliveriesSql(schema)],
33
- ...needsLog ? [deliveryLogSql(schema)] : []
33
+ ...current ? [] : [
34
+ subscriptionsIndexesSql(schema),
35
+ commandsSql(schema),
36
+ deliveriesSql(schema)
37
+ ],
38
+ ...needsLog ? [deliveryLogSql(schema)] : [],
39
+ ...needsLog || !current && found.deliveryLog ? [deliveryLogIndexesSql(schema)] : []
34
40
  ].join(";\n\n")};`;
35
41
  };
36
42
  /** Refuses to go on while the `webhooks` schema lacks what `webhooks` needs; the project's migrations add it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.66.6",
3
+ "version": "0.66.8",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,9 +43,9 @@
43
43
  "@better-auth/sso": "^1.7.12",
44
44
  "@livekit/protocol": "1.53.1",
45
45
  "@modelcontextprotocol/server": "^2.3.1",
46
- "@protobase/layout": "0.66.6",
47
- "@protobase/query": "0.66.6",
48
- "@protobase/schema": "0.66.6",
46
+ "@protobase/layout": "0.66.8",
47
+ "@protobase/query": "0.66.8",
48
+ "@protobase/schema": "0.66.8",
49
49
  "@react-email/render": "^2.1.0",
50
50
  "@react-pdf/primitives": "^4.4.0",
51
51
  "@react-pdf/renderer": "^4.9.0",