@protobase/server 0.66.11 → 0.66.12

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.
@@ -35,7 +35,8 @@ const capturePlan = (schema, models, state) => {
35
35
  const drop = state.triggers.filter((found) => found.function === capture?.oid).filter((found) => !wanted.some(({ trigger, table }) => table === found.table && trigger.name === found.name));
36
36
  return {
37
37
  setup: captured.length > 0 && (!state.outbox || !current),
38
- history: models.some((model) => model.history) && !state.history,
38
+ history: models.some((model) => model.history) && (!state.history || !state.historyIndexed),
39
+ historyMissing: models.some((model) => model.history) && !state.history,
39
40
  notices: models.some((model) => model.live) && !state.notices,
40
41
  create: create.map(({ model, trigger }) => ({
41
42
  model,
@@ -65,7 +66,7 @@ const planSql = (schema, plan) => {
65
66
  /** The resources whose changes would not be captured or kept as the config declares: what a server refuses to start with. */
66
67
  const uncaptured = (plan, models) => [...new Set([
67
68
  ...plan.setup ? models.filter(isCaptured) : plan.create.map(({ model }) => model),
68
- ...plan.history ? models.filter((model) => model.history) : [],
69
+ ...plan.historyMissing ? models.filter((model) => model.history) : [],
69
70
  ...plan.notices ? models.filter((model) => model.live) : []
70
71
  ].map((model) => model.name))];
71
72
  //#endregion
@@ -25,9 +25,12 @@ const readCaptureState = async (db, schema, models) => {
25
25
  t.tgfoid::text as "function", t.tgtype::int as type, encode(t.tgargs, 'hex') as args
26
26
  from pg_trigger t join pg_class c on c.oid = t.tgrelid join pg_namespace ns on ns.oid = c.relnamespace
27
27
  where not t.tgisinternal and (t.tgfoid::text = ${capture?.oid ?? ""} or t.tgrelid::text = any(${oids}::text[]))`);
28
+ const outbox = await exists("outbox");
29
+ const history = await exists("history");
28
30
  return {
29
- outbox: await exists("outbox"),
30
- history: await exists("history") && await hasIndexes(db, schema, "history", historyIndexes.map(([name]) => name)),
31
+ outbox,
32
+ history,
33
+ historyIndexed: history && await hasIndexes(db, schema, "history", historyIndexes.map(([name]) => name)),
31
34
  notices: await exists("notices"),
32
35
  ...capture && { capture },
33
36
  triggers: triggers.map((trigger) => ({
@@ -6,6 +6,9 @@ import { Db } from "@protobase/query";
6
6
  * table is never dropped.
7
7
  */
8
8
  export declare const commentsMigration: (db: Db) => Promise<string | undefined>;
9
- /** Refuses to go on while a resource has `.comments()` and the database lacks their table; the project's migrations add it. */
9
+ /**
10
+ * Refuses to go on while a resource has `.comments()` and the database lacks their table; the project's migrations add
11
+ * it. An index added since only speeds reads up, so a table without it still serves and `protobase migration` adds it.
12
+ */
10
13
  export declare const checkComments: (db: Db, models: readonly ResourceModel[]) => Promise<void>;
11
14
  //#endregion
@@ -27,20 +27,23 @@ const commentsTableSql = () => `create table if not exists ${commentsTable} (
27
27
  "created_at" timestamptz not null default now()
28
28
  );
29
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));
30
+ const hasCommentsTable = async (db) => (await sql`select to_regclass('comments.comments') is not null as found`.execute(db)).rows[0].found;
31
31
  /**
32
32
  * The SQL that adds the `comments` schema and its table, or the indexes it lacks, `undefined` when it has them all. The
33
33
  * table is never dropped.
34
34
  */
35
35
  const commentsMigration = async (db) => {
36
- if (await hasCommentsTable(db)) return void 0;
36
+ if (await hasCommentsTable(db) && await hasIndexes(db, "comments", "comments", commentsIndexes.map(([name]) => name))) return void 0;
37
37
  return `create schema if not exists "comments";\n\n${commentsTableSql()};`;
38
38
  };
39
- /** Refuses to go on while a resource has `.comments()` and the database lacks their table; the project's migrations add it. */
39
+ /**
40
+ * Refuses to go on while a resource has `.comments()` and the database lacks their table; the project's migrations add
41
+ * it. An index added since only speeds reads up, so a table without it still serves and `protobase migration` adds it.
42
+ */
40
43
  const checkComments = async (db, models) => {
41
44
  if (!models.some((model) => model.comments)) return;
42
45
  if (await hasCommentsTable(db)) return;
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.`);
46
+ 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.`);
44
47
  };
45
48
  //#endregion
46
49
  export { checkComments, commentsMigration, commentsTable, commentsTableSql };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.66.11";
2
+ var version = "0.66.12";
3
3
  //#endregion
4
4
  export { version };
@@ -2,6 +2,11 @@ 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
4
  const webhooksComment = `Protobase webhooks 2`;
5
+ /** The version a function's comment names, `undefined` when it is not one Protobase wrote. */
6
+ const commentVersion = (comment) => {
7
+ const match = comment?.match(/^Protobase webhooks (\d+)$/);
8
+ return match ? Number(match[1]) : void 0;
9
+ };
5
10
  /** The delivery jobs, whose waiting and failed ones the deliveries view lists. */
6
11
  const deliveryJobs = ["webhooks.deliver", "webhooks.deliver-ordered"];
7
12
  /** The job that sends a subscription a test event. */
@@ -137,4 +142,4 @@ create index if not exists "delivery_log_status" on ${table} ("status");
137
142
  create index if not exists "delivery_log_status_code" on ${table} ("status_code")`;
138
143
  };
139
144
  //#endregion
140
- export { commandsSql, deliveriesSql, deliveryJobs, deliveryLogIndexesSql, deliveryLogSql, sendTestJob, subscriptionsIndexesSql, subscriptionsSql, webhooksComment };
145
+ export { commandsSql, commentVersion, deliveriesSql, deliveryJobs, deliveryLogIndexesSql, deliveryLogSql, sendTestJob, subscriptionsIndexesSql, subscriptionsSql, webhooksComment };
@@ -8,6 +8,9 @@ import { Db } from "@protobase/query";
8
8
  * deliveries view reads. Tables are never dropped.
9
9
  */
10
10
  export declare const webhooksMigration: (db: Db, options?: WebhooksOptions, events?: EventsOptions) => Promise<string | undefined>;
11
- /** Refuses to go on while the `webhooks` schema lacks what `webhooks` needs; the project's migrations add it. */
11
+ /**
12
+ * Refuses to go on while the `webhooks` schema lacks what `webhooks` needs; the project's migrations add it. A schema
13
+ * written by an earlier version this one still serves on starts, and `protobase migration` brings it current.
14
+ */
12
15
  export declare const checkWebhooksSchema: (db: Db, options?: WebhooksOptions) => Promise<void>;
13
16
  //#endregion
@@ -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, deliveryLogIndexesSql, deliveryLogSql, subscriptionsIndexesSql, subscriptionsSql, webhooksComment } from "./schema-sql.js";
5
+ import { commandsSql, commentVersion, deliveriesSql, deliveryLogIndexesSql, deliveryLogSql, subscriptionsIndexesSql, subscriptionsSql } 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"];
@@ -10,9 +10,12 @@ const readWebhooksSchema = async (db, schema) => {
10
10
  const exists = async (table) => (await sql`select to_regclass(${`${quoteIdent(schema)}.${quoteIdent(table)}`}) is not null as found`.execute(db)).rows[0].found;
11
11
  const { rows } = await sql`select p.proname as name, obj_description(p.oid, 'pg_proc') as comment
12
12
  from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = ${schema} and p.proname = any(${functionNames}::text[]) order by p.proname`.execute(db);
13
+ const versions = functionNames.map((name) => commentVersion(rows.find((row) => row.name === name)?.comment ?? null));
14
+ const version = await exists("deliveries") && versions.every((found) => found !== void 0) ? Math.min(...versions) : void 0;
13
15
  return {
14
16
  subscriptions: await exists("subscriptions"),
15
- current: functionNames.every((name) => rows.some((row) => row.name === name && row.comment === webhooksComment)) && await exists("deliveries"),
17
+ current: version === 2,
18
+ served: version !== void 0 && version >= 1,
16
19
  deliveryLog: await exists("delivery_log")
17
20
  };
18
21
  };
@@ -39,11 +42,14 @@ const webhooksMigration = async (db, options = {}, events) => {
39
42
  ...needsLog || !current && found.deliveryLog ? [deliveryLogIndexesSql(schema)] : []
40
43
  ].join(";\n\n")};`;
41
44
  };
42
- /** Refuses to go on while the `webhooks` schema lacks what `webhooks` needs; the project's migrations add it. */
45
+ /**
46
+ * Refuses to go on while the `webhooks` schema lacks what `webhooks` needs; the project's migrations add it. A schema
47
+ * written by an earlier version this one still serves on starts, and `protobase migration` brings it current.
48
+ */
43
49
  const checkWebhooksSchema = async (db, options = {}) => {
44
50
  const schema = webhooksSchema(options);
45
51
  const found = await readWebhooksSchema(db, schema);
46
- if (found.subscriptions && found.current && (!options.deliveryLog || found.deliveryLog)) return;
52
+ if (found.subscriptions && found.served && (!options.deliveryLog || found.deliveryLog)) return;
47
53
  throw new Error(`The ${schema} schema is missing or behind this version of Protobase. Write it as a migration with \`${migrationCommand}\`, apply it with the project's migrations, then start again.`);
48
54
  };
49
55
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.66.11",
3
+ "version": "0.66.12",
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.11",
47
- "@protobase/query": "0.66.11",
48
- "@protobase/schema": "0.66.11",
46
+ "@protobase/layout": "0.66.12",
47
+ "@protobase/query": "0.66.12",
48
+ "@protobase/schema": "0.66.12",
49
49
  "@react-email/render": "^2.1.0",
50
50
  "@react-pdf/primitives": "^4.4.0",
51
51
  "@react-pdf/renderer": "^4.9.0",