@bitcobblers/wod-wiki-storage 0.35.2129 → 0.36.2130

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.
@@ -892,7 +892,7 @@ async function ensureStoreTables(conn, dialect, def) {
892
892
  BEGIN SELECT RAISE(ABORT, 'invalid_session_pin: Session pin is immutable'); END`);
893
893
  }
894
894
  if (dialect === "postgres" && ["notes", "block_revisions", "sessions"].includes(def.name)) {
895
- const condition = def.name === "block_revisions" ? `EXISTS (SELECT 1 FROM "block_revisions" old WHERE old."k0" = NEW."k0" AND old."k1" = NEW."k1" AND old."k2" = NEW."k2" AND old."data" <> NEW."data")` : def.name === "notes" ? `(NEW."data"::jsonb->>'sourceRevision')::bigint < (OLD."data"::jsonb->>'sourceRevision')::bigint OR ((NEW."data"::jsonb->'rawContent' IS DISTINCT FROM OLD."data"::jsonb->'rawContent' OR NEW."data"::jsonb->'blocks' IS DISTINCT FROM OLD."data"::jsonb->'blocks') AND (NEW."data"::jsonb->>'sourceRevision')::bigint <= (OLD."data"::jsonb->>'sourceRevision')::bigint)` : `NEW."data"::jsonb->'noteId' IS DISTINCT FROM OLD."data"::jsonb->'noteId' OR NEW."data"::jsonb->'blockId' IS DISTINCT FROM OLD."data"::jsonb->'blockId' OR NEW."data"::jsonb->'blockVersion' IS DISTINCT FROM OLD."data"::jsonb->'blockVersion'`;
895
+ const condition = def.name === "block_revisions" ? `EXISTS (SELECT 1 FROM "block_revisions" saved WHERE saved."k0" = NEW."k0" AND saved."k1" = NEW."k1" AND saved."k2" = NEW."k2" AND saved."data" <> NEW."data")` : def.name === "notes" ? `(NEW."data"::jsonb->>'sourceRevision')::bigint < (OLD."data"::jsonb->>'sourceRevision')::bigint OR ((NEW."data"::jsonb->'rawContent' IS DISTINCT FROM OLD."data"::jsonb->'rawContent' OR NEW."data"::jsonb->'blocks' IS DISTINCT FROM OLD."data"::jsonb->'blocks') AND (NEW."data"::jsonb->>'sourceRevision')::bigint <= (OLD."data"::jsonb->>'sourceRevision')::bigint)` : `NEW."data"::jsonb->'noteId' IS DISTINCT FROM OLD."data"::jsonb->'noteId' OR NEW."data"::jsonb->'blockId' IS DISTINCT FROM OLD."data"::jsonb->'blockId' OR NEW."data"::jsonb->'blockVersion' IS DISTINCT FROM OLD."data"::jsonb->'blockVersion'`;
896
896
  const code = def.name === "block_revisions" ? "revision_conflict" : def.name === "notes" ? "transaction_conflict" : "invalid_session_pin";
897
897
  const guard = `${def.name}_guard`;
898
898
  await conn.exec(`CREATE OR REPLACE FUNCTION "${guard}"() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF ${condition} THEN RAISE EXCEPTION '${code}'; END IF; RETURN NEW; END; $$`);
@@ -993,7 +993,7 @@ function ensureDdl(db) {
993
993
  }
994
994
  var DOMAIN_NOTE_COLS = ["note_id", "note_type", "catalog", "source_id", "source_path", "title", "title_search", "created_at", "date", "text_search", "journal", "payload"];
995
995
  var QUERY_BLOCK_COLS = ["occurrence_id", "source_note_id", "parent_note_id", "content_id", "section_key", "ordinal", "data_type", "source_id", "source_path", "catalog", "effective_date", "created_at", "note_title", "normalized_text", "journal", "payload"];
996
- var conflictUpdate = (cols, keys) => `ON CONFLICT (${keys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ` + cols.filter((c) => !keys.includes(c)).map((c) => `"${c}" = excluded."${c}"`).join(",") + ` WHERE ` + cols.filter((col) => !keys.includes(col)).map((col) => `"${col}" IS DISTINCT FROM excluded."${col}"`).join(" OR ");
996
+ var conflictUpdate = (table, cols, keys) => `ON CONFLICT (${keys.map((k) => `"${k}"`).join(", ")}) DO UPDATE SET ` + cols.filter((c) => !keys.includes(c)).map((c) => `"${c}" = excluded."${c}"`).join(",") + ` WHERE ` + cols.filter((col) => !keys.includes(col)).map((col) => `"${table}"."${col}" IS DISTINCT FROM excluded."${col}"`).join(" OR ");
997
997
  function domainNoteRow(note) {
998
998
  const id = text(note.id);
999
999
  if (!id) return void 0;
@@ -1015,7 +1015,7 @@ function domainNoteRow(note) {
1015
1015
  }
1016
1016
  async function upsertDomainNotes(conn, notes) {
1017
1017
  const rows = notes.map(domainNoteRow).filter((r) => r !== void 0);
1018
- await batchInsert(conn, "domain_note", [...DOMAIN_NOTE_COLS], rows, conflictUpdate(DOMAIN_NOTE_COLS, ["note_id"]));
1018
+ await batchInsert(conn, "domain_note", [...DOMAIN_NOTE_COLS], rows, conflictUpdate("domain_note", DOMAIN_NOTE_COLS, ["note_id"]));
1019
1019
  }
1020
1020
  function queryBlockRow(block, parent) {
1021
1021
  const id = text(block.id);
@@ -1044,7 +1044,7 @@ function queryBlockRow(block, parent) {
1044
1044
  }
1045
1045
  async function upsertQueryBlocks(conn, blocks) {
1046
1046
  const rows = blocks.map(([b, p]) => queryBlockRow(b, p)).filter((r) => r !== void 0);
1047
- await batchInsert(conn, "query_block", [...QUERY_BLOCK_COLS], rows, conflictUpdate(QUERY_BLOCK_COLS, ["occurrence_id"]));
1047
+ await batchInsert(conn, "query_block", [...QUERY_BLOCK_COLS], rows, conflictUpdate("query_block", QUERY_BLOCK_COLS, ["occurrence_id"]));
1048
1048
  }
1049
1049
  function indexNoteIdentities(notes) {
1050
1050
  const byNoteId = /* @__PURE__ */ new Map();
@@ -1244,6 +1244,73 @@ async function initializeDomain(db) {
1244
1244
  await rebuildProjections(tx);
1245
1245
  });
1246
1246
  }
1247
+ async function verifyReadiness(db) {
1248
+ const isMissingRelation = (error) => {
1249
+ if (typeof error !== "object" || error === null) return false;
1250
+ if ("errno" in error && error.errno === "42P01") return true;
1251
+ if ("code" in error && error.code === "42P01") return true;
1252
+ return "code" in error && error.code === "SQLITE_ERROR" && /no such table/i.test(error instanceof Error ? error.message : String(error));
1253
+ };
1254
+ const readGuard = async (key) => {
1255
+ const rows = await db.query(`SELECT "value" FROM "${SCHEMA_TABLE}" WHERE "key" = ?`, [key]);
1256
+ if (!rows.length || typeof rows[0].value !== "string") return void 0;
1257
+ try {
1258
+ return JSON.parse(rows[0].value);
1259
+ } catch {
1260
+ throw new StorageError(500, `database guard "${key}" is corrupt; re-run initialization`, "schema_incompatible");
1261
+ }
1262
+ };
1263
+ let schemaGuard;
1264
+ let projectionGuard;
1265
+ try {
1266
+ schemaGuard = await readGuard("schema");
1267
+ projectionGuard = await readGuard(PROJECTION_GUARD_KEY);
1268
+ } catch (error) {
1269
+ if (isMissingRelation(error)) {
1270
+ throw new StorageError(500, "database is not initialized (schema guard table missing)", "db_not_initialized");
1271
+ }
1272
+ throw error;
1273
+ }
1274
+ const schemaVersion = schemaGuard?.version;
1275
+ const projectionVersion = projectionGuard?.version;
1276
+ if (typeof schemaVersion !== "number") {
1277
+ throw new StorageError(500, "database is not initialized (no schema guard row)", "db_not_initialized");
1278
+ }
1279
+ if (typeof projectionVersion !== "number") {
1280
+ throw new StorageError(500, "database is not initialized (projections never built)", "db_not_initialized");
1281
+ }
1282
+ if (schemaVersion > DB_VERSION) {
1283
+ throw new StorageError(
1284
+ 500,
1285
+ `database schema v${schemaVersion} is newer than this server supports (v${DB_VERSION}); refusing to downgrade`,
1286
+ "schema_incompatible"
1287
+ );
1288
+ }
1289
+ if (schemaVersion !== DB_VERSION) {
1290
+ throw new StorageError(
1291
+ 500,
1292
+ `database schema v${schemaVersion} is incompatible with v${DB_VERSION}; re-run initialization`,
1293
+ "schema_incompatible"
1294
+ );
1295
+ }
1296
+ if (projectionVersion !== DOMAIN_PROJECTION_VERSION) {
1297
+ throw new StorageError(
1298
+ 500,
1299
+ `database projections v${projectionVersion} are incompatible with v${DOMAIN_PROJECTION_VERSION}; re-run initialization`,
1300
+ "schema_incompatible"
1301
+ );
1302
+ }
1303
+ for (const table of [...STORE_DEFS.map((def) => def.name), ...TABLES]) {
1304
+ try {
1305
+ await db.query(`SELECT * FROM "${table}" LIMIT 0`);
1306
+ } catch (error) {
1307
+ if (isMissingRelation(error)) {
1308
+ throw new StorageError(500, `database is not initialized (missing table: ${table})`, "db_not_initialized");
1309
+ }
1310
+ throw error;
1311
+ }
1312
+ }
1313
+ }
1247
1314
  async function wipeDomain(conn) {
1248
1315
  for (const t of TABLES) await conn.exec(`DELETE FROM "${t}"`);
1249
1316
  }
@@ -2305,6 +2372,7 @@ exports.refreshDomain = refreshDomain;
2305
2372
  exports.replaceSyncLog = replaceSyncLog;
2306
2373
  exports.storeDefinition = storeDefinition;
2307
2374
  exports.validateTursoUrl = validateTursoUrl;
2375
+ exports.verifyReadiness = verifyReadiness;
2308
2376
  exports.wipeDomain = wipeDomain;
2309
2377
  exports.wipeRows = wipeRows;
2310
2378
  //# sourceMappingURL=index.cjs.map