@bitcobblers/wod-wiki-storage 0.35.2117 → 0.35.2120
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.
- package/dist/{chunk-Y576CUFQ.mjs → chunk-7ZCBM5NS.mjs} +3 -3
- package/dist/chunk-7ZCBM5NS.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/sql/index.cjs +318 -7
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +158 -3
- package/dist/sql/index.d.ts +158 -3
- package/dist/sql/index.mjs +310 -9
- package/dist/sql/index.mjs.map +1 -1
- package/dist/{wire-Cfvz-I0h.d.cts → wire-CiFcjo6_.d.cts} +1 -1
- package/dist/{wire-Cfvz-I0h.d.ts → wire-CiFcjo6_.d.ts} +1 -1
- package/package.json +6 -6
- package/dist/chunk-Y576CUFQ.mjs.map +0 -1
package/dist/sql/index.cjs
CHANGED
|
@@ -124,7 +124,7 @@ function decodeRowJson(text2, binaryField) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
// src/schema.ts
|
|
127
|
-
var DB_VERSION =
|
|
127
|
+
var DB_VERSION = 30;
|
|
128
128
|
var COLUMN_TYPES = {
|
|
129
129
|
notes: { date: "number", createdAt: "number" },
|
|
130
130
|
page: { createdAt: "number" },
|
|
@@ -588,7 +588,7 @@ async function getRow(conn, def, key) {
|
|
|
588
588
|
}
|
|
589
589
|
async function getRows(conn, def, keys) {
|
|
590
590
|
const found = /* @__PURE__ */ new Map();
|
|
591
|
-
for (const part of chunks(keys,
|
|
591
|
+
for (const part of chunks(keys, 50)) {
|
|
592
592
|
const params = [];
|
|
593
593
|
const conditions = part.map((key) => {
|
|
594
594
|
const parts = toParts(key);
|
|
@@ -611,7 +611,7 @@ async function listRowsFromIndexMany(conn, def, indexName, keys) {
|
|
|
611
611
|
return out;
|
|
612
612
|
}
|
|
613
613
|
const found = /* @__PURE__ */ new Map();
|
|
614
|
-
for (const part of chunks(keys,
|
|
614
|
+
for (const part of chunks(keys, 50)) {
|
|
615
615
|
const params = [];
|
|
616
616
|
const conditions = part.map((key) => {
|
|
617
617
|
const parts = toParts(key);
|
|
@@ -905,25 +905,41 @@ async function initializeStore(db) {
|
|
|
905
905
|
await tx.exec(
|
|
906
906
|
`CREATE TABLE IF NOT EXISTS "${SCHEMA_TABLE}" ("key" TEXT PRIMARY KEY, "value" TEXT NOT NULL)`
|
|
907
907
|
);
|
|
908
|
+
const ignored = (name) => name.startsWith("_wodwiki_") || name.startsWith("sqlite_");
|
|
908
909
|
const storeTables = new Set(STORE_DEFS.map((d) => d.name));
|
|
910
|
+
for (const def of STORE_DEFS) {
|
|
911
|
+
const multi = def.indexes.find((i) => i.multiEntry);
|
|
912
|
+
if (multi) storeTables.add(junctionTable(def, multi));
|
|
913
|
+
}
|
|
909
914
|
const tableNames = new Set(
|
|
910
|
-
(await tx.query(tableListSql(db.dialect))).map((r) => String(Object.values(r)[0]))
|
|
915
|
+
(await tx.query(tableListSql(db.dialect))).map((r) => String(Object.values(r)[0])).filter((n) => !ignored(n))
|
|
911
916
|
);
|
|
912
917
|
const guardRows = await tx.query(`SELECT "value" FROM "${SCHEMA_TABLE}" WHERE "key" = ?`, [SCHEMA_KEY]);
|
|
913
918
|
let stored;
|
|
914
919
|
if (guardRows[0]) {
|
|
915
920
|
const guard = JSON.parse(String(guardRows[0].value));
|
|
916
|
-
if (typeof guard.version !== "number") {
|
|
921
|
+
if (typeof guard.version !== "number" || !Number.isSafeInteger(guard.version) || guard.version < 0) {
|
|
917
922
|
throw new StorageError(500, `corrupt ${SCHEMA_TABLE} guard row`, "bad_schema_guard");
|
|
918
923
|
}
|
|
919
924
|
stored = guard.version;
|
|
920
|
-
if (stored
|
|
925
|
+
if (stored > DB_VERSION) {
|
|
921
926
|
throw new StorageError(
|
|
922
927
|
500,
|
|
923
|
-
`database schema v${stored} is
|
|
928
|
+
`database schema v${stored} is newer than this server supports (v${DB_VERSION}); refusing to downgrade`,
|
|
924
929
|
"schema_incompatible"
|
|
925
930
|
);
|
|
926
931
|
}
|
|
932
|
+
if (stored < DB_VERSION) {
|
|
933
|
+
const missing = [...storeTables].filter((name) => !tableNames.has(name));
|
|
934
|
+
const unexpected = [...tableNames].filter((name) => name !== SCHEMA_TABLE && !storeTables.has(name));
|
|
935
|
+
if (missing.length > 0 || unexpected.length > 0) {
|
|
936
|
+
throw new StorageError(
|
|
937
|
+
500,
|
|
938
|
+
`database schema v${stored} is incompatible with v${DB_VERSION} (missing tables: ${missing.join(", ") || "none"}; unrecognized tables: ${unexpected.join(", ") || "none"}); recreation required using an explicitly disposable database`,
|
|
939
|
+
"schema_incompatible"
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
927
943
|
} else if ([...tableNames].some((name) => storeTables.has(name))) {
|
|
928
944
|
throw new StorageError(
|
|
929
945
|
500,
|
|
@@ -1970,28 +1986,323 @@ var BrowserTursoStorage = class extends SqlStorage {
|
|
|
1970
1986
|
}
|
|
1971
1987
|
};
|
|
1972
1988
|
|
|
1989
|
+
// src/sql/sync.ts
|
|
1990
|
+
var SYNC_OPS_TABLE = "_wodwiki_sync_ops";
|
|
1991
|
+
var SYNC_STATE_TABLE = "_wodwiki_sync_state";
|
|
1992
|
+
var SYNC_ARCHIVE_TABLE = "_wodwiki_sync_ops_archive";
|
|
1993
|
+
var BINARY_FIELD2 = { attachments: "data" };
|
|
1994
|
+
var STORE_NAMES = Object.fromEntries(STORE_DEFS.map((d) => [d.name, true]));
|
|
1995
|
+
var STATE_KEY = "meta";
|
|
1996
|
+
function isSafeInt(value) {
|
|
1997
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
1998
|
+
}
|
|
1999
|
+
function isScalar(value) {
|
|
2000
|
+
return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
2001
|
+
}
|
|
2002
|
+
function assertKey(key, what) {
|
|
2003
|
+
if (isScalar(key)) return;
|
|
2004
|
+
if (Array.isArray(key) && key.length > 0 && key.every(isScalar)) return;
|
|
2005
|
+
throw new StorageError(400, `${what} must be a string, finite number, or non-empty scalar tuple`, "bad_sync_key");
|
|
2006
|
+
}
|
|
2007
|
+
function validateMutation(raw) {
|
|
2008
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2009
|
+
throw new StorageError(400, "sync mutation must be an object", "bad_sync_mutation");
|
|
2010
|
+
}
|
|
2011
|
+
const mutation = raw;
|
|
2012
|
+
if (mutation.op === "wipe") return { op: "wipe" };
|
|
2013
|
+
if (typeof mutation.store !== "string" || !STORE_NAMES[mutation.store]) {
|
|
2014
|
+
throw new StorageError(400, `sync mutation has unknown store ${String(mutation.store)}`, "bad_sync_mutation");
|
|
2015
|
+
}
|
|
2016
|
+
const store = mutation.store;
|
|
2017
|
+
if (mutation.op === "clear") return { op: "clear", store };
|
|
2018
|
+
if (mutation.op === "delete") {
|
|
2019
|
+
assertKey(mutation.key, "sync delete key");
|
|
2020
|
+
return { op: "delete", store, key: mutation.key };
|
|
2021
|
+
}
|
|
2022
|
+
if (mutation.op === "put") {
|
|
2023
|
+
assertKey(mutation.key, "sync put key");
|
|
2024
|
+
if (mutation.value === null || typeof mutation.value !== "object" || Array.isArray(mutation.value)) {
|
|
2025
|
+
throw new StorageError(400, "sync put value must be an object row", "bad_sync_mutation");
|
|
2026
|
+
}
|
|
2027
|
+
return { op: "put", store, key: mutation.key, value: mutation.value };
|
|
2028
|
+
}
|
|
2029
|
+
throw new StorageError(400, `sync mutation has unknown op ${String(mutation.op)}`, "bad_sync_mutation");
|
|
2030
|
+
}
|
|
2031
|
+
function validateEntry(entry) {
|
|
2032
|
+
if (typeof entry.id !== "string" || entry.id.length === 0 || entry.id.length > 200) {
|
|
2033
|
+
throw new StorageError(400, "sync entry id must be a non-empty string", "bad_sync_entry");
|
|
2034
|
+
}
|
|
2035
|
+
if (!isSafeInt(entry.at) || entry.at < 0) {
|
|
2036
|
+
throw new StorageError(400, "sync entry at must be a non-negative safe integer", "bad_sync_entry");
|
|
2037
|
+
}
|
|
2038
|
+
if (!Array.isArray(entry.ops) || entry.ops.length === 0) {
|
|
2039
|
+
throw new StorageError(400, "sync entry ops must be a non-empty array", "bad_sync_entry");
|
|
2040
|
+
}
|
|
2041
|
+
for (const op of entry.ops) validateMutation(op);
|
|
2042
|
+
}
|
|
2043
|
+
async function runStatements(tx, statements) {
|
|
2044
|
+
if (statements.length === 0) return [];
|
|
2045
|
+
if (tx.batch) return tx.batch(statements);
|
|
2046
|
+
const results = [];
|
|
2047
|
+
for (const statement of statements) {
|
|
2048
|
+
results.push(await tx.query(statement.sql, statement.params ?? []));
|
|
2049
|
+
}
|
|
2050
|
+
return results;
|
|
2051
|
+
}
|
|
2052
|
+
async function tableExists(conn, dialect, name) {
|
|
2053
|
+
const sql = dialect === "postgres" ? `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = $1` : `SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`;
|
|
2054
|
+
return (await conn.query(sql, [name])).length > 0;
|
|
2055
|
+
}
|
|
2056
|
+
async function readSyncState(tx) {
|
|
2057
|
+
const rows = await tx.query(`SELECT "value" FROM "${SYNC_STATE_TABLE}" WHERE "key" = ?`, [STATE_KEY]);
|
|
2058
|
+
if (!rows[0]) throw new StorageError(409, "sync target is not initialized", "sync_not_initialized");
|
|
2059
|
+
let parsed;
|
|
2060
|
+
try {
|
|
2061
|
+
parsed = JSON.parse(String(rows[0].value));
|
|
2062
|
+
} catch {
|
|
2063
|
+
throw new StorageError(400, "malformed sync state row", "bad_sync_state");
|
|
2064
|
+
}
|
|
2065
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2066
|
+
throw new StorageError(400, "malformed sync state row", "bad_sync_state");
|
|
2067
|
+
}
|
|
2068
|
+
const state = parsed;
|
|
2069
|
+
if (!isSafeInt(state.schema) || state.schema < 0 || !isSafeInt(state.rev) || state.rev < 0) {
|
|
2070
|
+
throw new StorageError(400, "sync state schema/rev must be non-negative safe integers", "bad_sync_state");
|
|
2071
|
+
}
|
|
2072
|
+
return { schema: state.schema, rev: state.rev };
|
|
2073
|
+
}
|
|
2074
|
+
async function probeSyncTarget(db) {
|
|
2075
|
+
if (await tableExists(db, db.dialect, SYNC_STATE_TABLE)) {
|
|
2076
|
+
const state = await readSyncState(db);
|
|
2077
|
+
return { kind: "current", schemaVersion: state.schema, rev: state.rev };
|
|
2078
|
+
}
|
|
2079
|
+
if (await tableExists(db, db.dialect, SCHEMA_TABLE)) {
|
|
2080
|
+
const rows = await db.query(`SELECT "value" FROM "${SCHEMA_TABLE}" WHERE "key" = 'schema'`);
|
|
2081
|
+
if (rows[0]) {
|
|
2082
|
+
const guard = JSON.parse(String(rows[0].value));
|
|
2083
|
+
if (typeof guard.version === "number" && Number.isSafeInteger(guard.version) && guard.version >= 0) {
|
|
2084
|
+
return { kind: "legacy", schemaVersion: guard.version };
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return { kind: "legacy", schemaVersion: 0 };
|
|
2088
|
+
}
|
|
2089
|
+
const others = await db.query(
|
|
2090
|
+
db.dialect === "postgres" ? `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'` : `SELECT name FROM sqlite_master WHERE type = 'table'`
|
|
2091
|
+
);
|
|
2092
|
+
const internal = (name) => name.startsWith("_wodwiki_") || name.startsWith("sqlite_");
|
|
2093
|
+
return others.some((row) => !internal(String(Object.values(row)[0]))) ? { kind: "unknown" } : "empty";
|
|
2094
|
+
}
|
|
2095
|
+
async function initSyncTarget(db, schemaVersion) {
|
|
2096
|
+
await db.transaction(async (tx) => {
|
|
2097
|
+
await tx.exec(`CREATE TABLE IF NOT EXISTS "${SYNC_STATE_TABLE}" ("key" TEXT PRIMARY KEY, "value" TEXT NOT NULL)`);
|
|
2098
|
+
await tx.exec(
|
|
2099
|
+
`CREATE TABLE IF NOT EXISTS "${SYNC_OPS_TABLE}" ("op_id" TEXT PRIMARY KEY, "rev" INTEGER NOT NULL, "idx" INTEGER NOT NULL, "at" INTEGER NOT NULL, "data" TEXT NOT NULL)`
|
|
2100
|
+
);
|
|
2101
|
+
await tx.exec(
|
|
2102
|
+
`CREATE INDEX IF NOT EXISTS "${SYNC_OPS_TABLE}_rev" ON "${SYNC_OPS_TABLE}" ("rev", "idx")`
|
|
2103
|
+
);
|
|
2104
|
+
await tx.exec(
|
|
2105
|
+
`INSERT INTO "${SYNC_STATE_TABLE}" ("key", "value") VALUES (?, ?) ON CONFLICT ("key") DO NOTHING`,
|
|
2106
|
+
[STATE_KEY, JSON.stringify({ schema: schemaVersion, rev: 0 })]
|
|
2107
|
+
);
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
function encodeMutation(mutation) {
|
|
2111
|
+
if (mutation.op !== "put") return JSON.stringify(mutation);
|
|
2112
|
+
const encoded = encodeRowJson(mutation.value, BINARY_FIELD2[mutation.store]);
|
|
2113
|
+
return `{"key":${JSON.stringify(mutation.key)},"op":"put","store":${JSON.stringify(mutation.store)},"value":${encoded}}`;
|
|
2114
|
+
}
|
|
2115
|
+
function decodeMutation(data) {
|
|
2116
|
+
if (typeof data !== "string") {
|
|
2117
|
+
throw new StorageError(400, "sync op data must be a string", "bad_sync_mutation");
|
|
2118
|
+
}
|
|
2119
|
+
let parsed;
|
|
2120
|
+
try {
|
|
2121
|
+
parsed = JSON.parse(data);
|
|
2122
|
+
} catch {
|
|
2123
|
+
throw new StorageError(400, "sync op data is not valid JSON", "bad_sync_mutation");
|
|
2124
|
+
}
|
|
2125
|
+
const mutation = validateMutation(parsed);
|
|
2126
|
+
if (mutation.op !== "put") return mutation;
|
|
2127
|
+
return {
|
|
2128
|
+
op: "put",
|
|
2129
|
+
store: mutation.store,
|
|
2130
|
+
key: mutation.key,
|
|
2131
|
+
value: decodeRowJson(JSON.stringify(mutation.value), BINARY_FIELD2[mutation.store])
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
async function applySyncBatch(db, entries, opts = {}) {
|
|
2135
|
+
if (entries.length === 0) return 0;
|
|
2136
|
+
for (const entry of entries) validateEntry(entry);
|
|
2137
|
+
return db.transaction(async (tx) => {
|
|
2138
|
+
const state = await readSyncState(tx);
|
|
2139
|
+
if (opts.expectSchema !== void 0 && state.schema !== opts.expectSchema) {
|
|
2140
|
+
throw new StorageError(
|
|
2141
|
+
409,
|
|
2142
|
+
`sync target schema changed from v${opts.expectSchema} to v${state.schema} between probe and write \u2014 retry the cycle`,
|
|
2143
|
+
"sync_schema_conflict"
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
const firstIds = entries.map((entry) => `${entry.id}:0`);
|
|
2147
|
+
const present = /* @__PURE__ */ new Set();
|
|
2148
|
+
for (let i = 0; i < firstIds.length; i += 100) {
|
|
2149
|
+
const chunk = firstIds.slice(i, i + 100);
|
|
2150
|
+
const rows = await tx.query(
|
|
2151
|
+
`SELECT "op_id" FROM "${SYNC_OPS_TABLE}" WHERE "op_id" IN (${chunk.map(() => "?").join(", ")})`,
|
|
2152
|
+
chunk
|
|
2153
|
+
);
|
|
2154
|
+
for (const row of rows) present.add(String(row.op_id));
|
|
2155
|
+
}
|
|
2156
|
+
const statements = [];
|
|
2157
|
+
let rev = state.rev;
|
|
2158
|
+
let committed = 0;
|
|
2159
|
+
for (const entry of entries) {
|
|
2160
|
+
if (present.has(`${entry.id}:0`)) continue;
|
|
2161
|
+
rev += 1;
|
|
2162
|
+
committed += 1;
|
|
2163
|
+
entry.ops.forEach((mutation, idx) => {
|
|
2164
|
+
statements.push({
|
|
2165
|
+
sql: `INSERT INTO "${SYNC_OPS_TABLE}" ("op_id", "rev", "idx", "at", "data") VALUES (?, ?, ?, ?, ?) ON CONFLICT ("op_id") DO NOTHING`,
|
|
2166
|
+
params: [`${entry.id}:${idx}`, rev, idx, entry.at, encodeMutation(mutation)]
|
|
2167
|
+
});
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
const nextSchema = opts.toSchemaVersion ?? state.schema;
|
|
2171
|
+
statements.push({
|
|
2172
|
+
sql: `UPDATE "${SYNC_STATE_TABLE}" SET "value" = ? WHERE "key" = ?`,
|
|
2173
|
+
params: [JSON.stringify({ schema: nextSchema, rev }), STATE_KEY]
|
|
2174
|
+
});
|
|
2175
|
+
await runStatements(tx, statements);
|
|
2176
|
+
return committed;
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
async function fetchSyncOps(db, afterRev, limit) {
|
|
2180
|
+
if (!isSafeInt(afterRev) || afterRev < 0) {
|
|
2181
|
+
throw new StorageError(400, "fetchSyncOps cursor must be a non-negative safe integer", "bad_sync_cursor");
|
|
2182
|
+
}
|
|
2183
|
+
const bounded = Math.min(Math.max(1, Math.floor(limit)), 4294967295);
|
|
2184
|
+
const revRows = await db.query(
|
|
2185
|
+
`SELECT DISTINCT "rev" FROM (SELECT "rev" FROM "${SYNC_OPS_TABLE}" WHERE "rev" > ? ORDER BY "rev" ASC LIMIT ?)`,
|
|
2186
|
+
[afterRev, bounded]
|
|
2187
|
+
);
|
|
2188
|
+
const revs = revRows.map((row) => {
|
|
2189
|
+
const rev = Number(row.rev);
|
|
2190
|
+
if (!Number.isSafeInteger(rev) || rev < 0) {
|
|
2191
|
+
throw new StorageError(400, "sync op rev must be a non-negative safe integer", "bad_sync_row");
|
|
2192
|
+
}
|
|
2193
|
+
return rev;
|
|
2194
|
+
});
|
|
2195
|
+
if (revs.length === 0) return [];
|
|
2196
|
+
const rows = await db.query(
|
|
2197
|
+
`SELECT "op_id", "rev", "idx", "at", "data" FROM "${SYNC_OPS_TABLE}" WHERE "rev" IN (${revs.map(() => "?").join(", ")}) ORDER BY "rev", "idx"`,
|
|
2198
|
+
revs
|
|
2199
|
+
);
|
|
2200
|
+
return rows.map((row, at) => {
|
|
2201
|
+
const opId = row.op_id;
|
|
2202
|
+
const rev = Number(row.rev);
|
|
2203
|
+
const idx = Number(row.idx);
|
|
2204
|
+
const atMs = Number(row.at);
|
|
2205
|
+
if (typeof opId !== "string" || opId.length === 0 || !Number.isSafeInteger(rev) || rev < 0 || !Number.isSafeInteger(idx) || idx < 0 || !Number.isSafeInteger(atMs) || atMs < 0) {
|
|
2206
|
+
throw new StorageError(400, `sync op row ${at} has malformed shape`, "bad_sync_row");
|
|
2207
|
+
}
|
|
2208
|
+
return { opId, rev, idx, at: atMs, mutation: decodeMutation(row.data) };
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
async function replaceSyncLog(db, entries, toSchemaVersion) {
|
|
2212
|
+
if (entries.length === 0) throw new StorageError(400, "replacement log must contain the converted snapshot", "bad_sync_entry");
|
|
2213
|
+
for (const entry of entries) validateEntry(entry);
|
|
2214
|
+
await db.transaction(async (tx) => {
|
|
2215
|
+
await readSyncState(tx);
|
|
2216
|
+
await tx.exec(
|
|
2217
|
+
`CREATE TABLE IF NOT EXISTS "${SYNC_ARCHIVE_TABLE}" ("op_id" TEXT PRIMARY KEY, "rev" INTEGER NOT NULL, "idx" INTEGER NOT NULL, "at" INTEGER NOT NULL, "data" TEXT NOT NULL)`
|
|
2218
|
+
);
|
|
2219
|
+
await tx.exec(`INSERT INTO "${SYNC_ARCHIVE_TABLE}" ("op_id", "rev", "idx", "at", "data") SELECT "op_id", "rev", "idx", "at", "data" FROM "${SYNC_OPS_TABLE}"`);
|
|
2220
|
+
await tx.exec(`DELETE FROM "${SYNC_OPS_TABLE}"`);
|
|
2221
|
+
const statements = [];
|
|
2222
|
+
let rev = 0;
|
|
2223
|
+
for (const entry of entries) {
|
|
2224
|
+
rev += 1;
|
|
2225
|
+
entry.ops.forEach((mutation, idx) => {
|
|
2226
|
+
statements.push({
|
|
2227
|
+
sql: `INSERT INTO "${SYNC_OPS_TABLE}" ("op_id", "rev", "idx", "at", "data") VALUES (?, ?, ?, ?, ?) ON CONFLICT ("op_id") DO NOTHING`,
|
|
2228
|
+
params: [`${entry.id}:${idx}`, rev, idx, entry.at, encodeMutation(mutation)]
|
|
2229
|
+
});
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
statements.push({
|
|
2233
|
+
sql: `UPDATE "${SYNC_STATE_TABLE}" SET "value" = ? WHERE "key" = ?`,
|
|
2234
|
+
params: [JSON.stringify({ schema: toSchemaVersion, rev }), STATE_KEY]
|
|
2235
|
+
});
|
|
2236
|
+
await runStatements(tx, statements);
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
async function readRawSyncOps(db) {
|
|
2240
|
+
const rows = await db.query(`SELECT "rev", "idx", "data" FROM "${SYNC_OPS_TABLE}" ORDER BY "rev", "idx"`);
|
|
2241
|
+
return rows.map((row) => {
|
|
2242
|
+
const rev = Number(row.rev);
|
|
2243
|
+
const idx = Number(row.idx);
|
|
2244
|
+
if (!Number.isSafeInteger(rev) || rev < 0 || !Number.isSafeInteger(idx) || idx < 0 || typeof row.data !== "string") {
|
|
2245
|
+
throw new StorageError(400, "sync op row has malformed shape", "bad_sync_row");
|
|
2246
|
+
}
|
|
2247
|
+
let parsed;
|
|
2248
|
+
try {
|
|
2249
|
+
parsed = JSON.parse(row.data);
|
|
2250
|
+
} catch {
|
|
2251
|
+
throw new StorageError(400, "sync op data is not valid JSON", "bad_sync_row");
|
|
2252
|
+
}
|
|
2253
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2254
|
+
throw new StorageError(400, "sync op data must be an object", "bad_sync_row");
|
|
2255
|
+
}
|
|
2256
|
+
const mutation = parsed;
|
|
2257
|
+
if (typeof mutation.op !== "string" || typeof mutation.store !== "string") {
|
|
2258
|
+
throw new StorageError(400, "sync op must carry string op/store", "bad_sync_row");
|
|
2259
|
+
}
|
|
2260
|
+
return {
|
|
2261
|
+
op: mutation.op,
|
|
2262
|
+
store: mutation.store,
|
|
2263
|
+
key: mutation.key,
|
|
2264
|
+
value: mutation.value === void 0 || mutation.value === null ? void 0 : decodeRowJson(JSON.stringify(mutation.value), BINARY_FIELD2[mutation.store])
|
|
2265
|
+
};
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
async function readLegacyRows(db, table) {
|
|
2269
|
+
if (!await tableExists(db, db.dialect, table)) return [];
|
|
2270
|
+
const rows = await db.query(`SELECT "data" FROM "${table}"`);
|
|
2271
|
+
return rows.map((row) => decodeRowJson(String(row.data), BINARY_FIELD2[table]));
|
|
2272
|
+
}
|
|
2273
|
+
|
|
1973
2274
|
exports.BrowserTursoStorage = BrowserTursoStorage;
|
|
1974
2275
|
exports.MAX_BODY = MAX_BODY;
|
|
1975
2276
|
exports.SCHEMA_TABLE = SCHEMA_TABLE;
|
|
2277
|
+
exports.SYNC_ARCHIVE_TABLE = SYNC_ARCHIVE_TABLE;
|
|
2278
|
+
exports.SYNC_OPS_TABLE = SYNC_OPS_TABLE;
|
|
2279
|
+
exports.SYNC_STATE_TABLE = SYNC_STATE_TABLE;
|
|
1976
2280
|
exports.SqlStorage = SqlStorage;
|
|
1977
2281
|
exports.StorageError = StorageError;
|
|
2282
|
+
exports.applySyncBatch = applySyncBatch;
|
|
1978
2283
|
exports.clearRows = clearRows;
|
|
1979
2284
|
exports.countRows = countRows;
|
|
1980
2285
|
exports.decodeRowJson = decodeRowJson;
|
|
1981
2286
|
exports.deleteRows = deleteRows;
|
|
1982
2287
|
exports.encodeRowJson = encodeRowJson;
|
|
2288
|
+
exports.fetchSyncOps = fetchSyncOps;
|
|
1983
2289
|
exports.getRow = getRow;
|
|
1984
2290
|
exports.getRows = getRows;
|
|
1985
2291
|
exports.handleDomainQuery = handleDomainQuery;
|
|
2292
|
+
exports.initSyncTarget = initSyncTarget;
|
|
1986
2293
|
exports.initializeDomain = initializeDomain;
|
|
1987
2294
|
exports.initializeStore = initializeStore;
|
|
1988
2295
|
exports.libsqlDatabase = libsqlDatabase;
|
|
1989
2296
|
exports.listRows = listRows;
|
|
1990
2297
|
exports.listRowsFromIndexMany = listRowsFromIndexMany;
|
|
2298
|
+
exports.probeSyncTarget = probeSyncTarget;
|
|
1991
2299
|
exports.putRow = putRow;
|
|
1992
2300
|
exports.putRows = putRows;
|
|
2301
|
+
exports.readLegacyRows = readLegacyRows;
|
|
2302
|
+
exports.readRawSyncOps = readRawSyncOps;
|
|
1993
2303
|
exports.rebuildProjections = rebuildProjections;
|
|
1994
2304
|
exports.refreshDomain = refreshDomain;
|
|
2305
|
+
exports.replaceSyncLog = replaceSyncLog;
|
|
1995
2306
|
exports.storeDefinition = storeDefinition;
|
|
1996
2307
|
exports.validateTursoUrl = validateTursoUrl;
|
|
1997
2308
|
exports.wipeDomain = wipeDomain;
|