@g9tm/syncdata 1.2.1
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/LICENSE.md +21 -0
- package/PARITY.md +71 -0
- package/README.md +71 -0
- package/THIRD-PARTY-NOTICES.md +31 -0
- package/dist/abstractions/digest.d.ts +27 -0
- package/dist/abstractions/digest.js +78 -0
- package/dist/abstractions/digest.js.map +1 -0
- package/dist/abstractions/enums.d.ts +42 -0
- package/dist/abstractions/enums.js +60 -0
- package/dist/abstractions/enums.js.map +1 -0
- package/dist/abstractions/errors.d.ts +72 -0
- package/dist/abstractions/errors.js +90 -0
- package/dist/abstractions/errors.js.map +1 -0
- package/dist/abstractions/guid.d.ts +24 -0
- package/dist/abstractions/guid.js +93 -0
- package/dist/abstractions/guid.js.map +1 -0
- package/dist/abstractions/key.d.ts +16 -0
- package/dist/abstractions/key.js +47 -0
- package/dist/abstractions/key.js.map +1 -0
- package/dist/abstractions/long.d.ts +103 -0
- package/dist/abstractions/long.js +351 -0
- package/dist/abstractions/long.js.map +1 -0
- package/dist/abstractions/parameters.d.ts +49 -0
- package/dist/abstractions/parameters.js +153 -0
- package/dist/abstractions/parameters.js.map +1 -0
- package/dist/brotli-js/decode.d.ts +8 -0
- package/dist/brotli-js/decode.js +2004 -0
- package/dist/brotli-js/decode.js.map +1 -0
- package/dist/brotli-js/index.d.ts +5 -0
- package/dist/brotli-js/index.js +19 -0
- package/dist/brotli-js/index.js.map +1 -0
- package/dist/client/engine.d.ts +110 -0
- package/dist/client/engine.js +1158 -0
- package/dist/client/engine.js.map +1 -0
- package/dist/client/model.d.ts +61 -0
- package/dist/client/model.js +154 -0
- package/dist/client/model.js.map +1 -0
- package/dist/client/progress.d.ts +101 -0
- package/dist/client/progress.js +251 -0
- package/dist/client/progress.js.map +1 -0
- package/dist/client/runs.d.ts +219 -0
- package/dist/client/runs.js +239 -0
- package/dist/client/runs.js.map +1 -0
- package/dist/client/storeTypes.d.ts +428 -0
- package/dist/client/storeTypes.js +256 -0
- package/dist/client/storeTypes.js.map +1 -0
- package/dist/client/transport.d.ts +19 -0
- package/dist/client/transport.js +2 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/abort.d.ts +21 -0
- package/dist/internal/abort.js +125 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/base64.d.ts +8 -0
- package/dist/internal/base64.js +85 -0
- package/dist/internal/base64.js.map +1 -0
- package/dist/internal/dotnetFormat.d.ts +32 -0
- package/dist/internal/dotnetFormat.js +207 -0
- package/dist/internal/dotnetFormat.js.map +1 -0
- package/dist/internal/md5.d.ts +2 -0
- package/dist/internal/md5.js +97 -0
- package/dist/internal/md5.js.map +1 -0
- package/dist/internal/sha256.d.ts +20 -0
- package/dist/internal/sha256.js +153 -0
- package/dist/internal/sha256.js.map +1 -0
- package/dist/internal/utf8.d.ts +8 -0
- package/dist/internal/utf8.js +143 -0
- package/dist/internal/utf8.js.map +1 -0
- package/dist/node/index.d.ts +9 -0
- package/dist/node/index.js +35 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/sqliteDriver.d.ts +11 -0
- package/dist/node/sqliteDriver.js +252 -0
- package/dist/node/sqliteDriver.js.map +1 -0
- package/dist/protocol/dtos.d.ts +280 -0
- package/dist/protocol/dtos.js +33 -0
- package/dist/protocol/dtos.js.map +1 -0
- package/dist/protocol/protocol.d.ts +36 -0
- package/dist/protocol/protocol.js +63 -0
- package/dist/protocol/protocol.js.map +1 -0
- package/dist/protocol/rowBatch/columnLayout.d.ts +4 -0
- package/dist/protocol/rowBatch/columnLayout.js +309 -0
- package/dist/protocol/rowBatch/columnLayout.js.map +1 -0
- package/dist/protocol/rowBatch/format.d.ts +74 -0
- package/dist/protocol/rowBatch/format.js +163 -0
- package/dist/protocol/rowBatch/format.js.map +1 -0
- package/dist/protocol/rowBatch/reader.d.ts +119 -0
- package/dist/protocol/rowBatch/reader.js +459 -0
- package/dist/protocol/rowBatch/reader.js.map +1 -0
- package/dist/protocol/rowBatch/writer.d.ts +101 -0
- package/dist/protocol/rowBatch/writer.js +412 -0
- package/dist/protocol/rowBatch/writer.js.map +1 -0
- package/dist/protocol/wire.d.ts +32 -0
- package/dist/protocol/wire.js +329 -0
- package/dist/protocol/wire.js.map +1 -0
- package/dist/signalr/index.d.ts +2 -0
- package/dist/signalr/index.js +5 -0
- package/dist/signalr/index.js.map +1 -0
- package/dist/signalr/protocols.d.ts +17 -0
- package/dist/signalr/protocols.js +109 -0
- package/dist/signalr/protocols.js.map +1 -0
- package/dist/signalr/transport.d.ts +67 -0
- package/dist/signalr/transport.js +286 -0
- package/dist/signalr/transport.js.map +1 -0
- package/dist/sqlite/apply.d.ts +91 -0
- package/dist/sqlite/apply.js +657 -0
- package/dist/sqlite/apply.js.map +1 -0
- package/dist/sqlite/commands.d.ts +24 -0
- package/dist/sqlite/commands.js +167 -0
- package/dist/sqlite/commands.js.map +1 -0
- package/dist/sqlite/connection.d.ts +81 -0
- package/dist/sqlite/connection.js +322 -0
- package/dist/sqlite/connection.js.map +1 -0
- package/dist/sqlite/driver.d.ts +63 -0
- package/dist/sqlite/driver.js +9 -0
- package/dist/sqlite/driver.js.map +1 -0
- package/dist/sqlite/index.d.ts +6 -0
- package/dist/sqlite/index.js +7 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/internals.d.ts +63 -0
- package/dist/sqlite/internals.js +194 -0
- package/dist/sqlite/internals.js.map +1 -0
- package/dist/sqlite/options.d.ts +78 -0
- package/dist/sqlite/options.js +78 -0
- package/dist/sqlite/options.js.map +1 -0
- package/dist/sqlite/push.d.ts +9 -0
- package/dist/sqlite/push.js +272 -0
- package/dist/sqlite/push.js.map +1 -0
- package/dist/sqlite/query.d.ts +38 -0
- package/dist/sqlite/query.js +476 -0
- package/dist/sqlite/query.js.map +1 -0
- package/dist/sqlite/revocation.d.ts +28 -0
- package/dist/sqlite/revocation.js +337 -0
- package/dist/sqlite/revocation.js.map +1 -0
- package/dist/sqlite/sql.d.ts +60 -0
- package/dist/sqlite/sql.js +324 -0
- package/dist/sqlite/sql.js.map +1 -0
- package/dist/sqlite/store.d.ts +116 -0
- package/dist/sqlite/store.js +664 -0
- package/dist/sqlite/store.js.map +1 -0
- package/dist/sqlite/values.d.ts +29 -0
- package/dist/sqlite/values.js +288 -0
- package/dist/sqlite/values.js.map +1 -0
- package/dist/testing/index.d.ts +61 -0
- package/dist/testing/index.js +175 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.Revocation.cs: revocation dispositions (ADR-0026). Every path that
|
|
2
|
+
// releases a row — a move-out in a pull or a reconciliation, an unsubscribe, an expired ephemeral subscription — ends in
|
|
3
|
+
// disposeReleasedRow, which asks the revocation policy what to do with a row NO subscription holds any more.
|
|
4
|
+
//
|
|
5
|
+
// The local sequence such a row gets is the clock's next value, (SELECT seq FROM g9_clock) + 1; the CALLER moves the
|
|
6
|
+
// clock afterwards (unconditionally, like the .NET store's `++sequence`).
|
|
7
|
+
import { G9ESyncColumnType } from '../abstractions/enums.js';
|
|
8
|
+
import { G9Long } from '../abstractions/long.js';
|
|
9
|
+
import { G9ApplyReport, G9ERevocationDisposition, G9ERevocationReason, } from '../client/storeTypes.js';
|
|
10
|
+
import { asLong, asNumber, asOptionalString, asString, G9StatementBatch, statementsOf } from './connection.js';
|
|
11
|
+
import { byTid, deserializeValues, getLocal, keyValue, nowMs, readLocalValues, serializeValues } from './internals.js';
|
|
12
|
+
import { quote } from './sql.js';
|
|
13
|
+
export const REVOKED_REJECTION_CODE = 'G9S-REJ-REVOKED';
|
|
14
|
+
const NEXT_SEQ = '(SELECT seq FROM g9_clock WHERE id = 1) + 1';
|
|
15
|
+
class RowEditor {
|
|
16
|
+
constructor(values) {
|
|
17
|
+
this.changed = new Map();
|
|
18
|
+
this._values = values;
|
|
19
|
+
}
|
|
20
|
+
set(column, value) {
|
|
21
|
+
if (typeof column !== 'string' || column.trim() === '')
|
|
22
|
+
throw new TypeError('A column is required.');
|
|
23
|
+
const actual = Object.keys(this._values).find((c) => c.toLowerCase() === column.toLowerCase());
|
|
24
|
+
if (actual === undefined)
|
|
25
|
+
throw new RangeError(`'${column}' is not a column of the row.`);
|
|
26
|
+
this._values[actual] = value;
|
|
27
|
+
this.changed.set(actual.toLowerCase(), actual);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** A row with no metadata row: what the per-row path assumes when its first read finds nothing. */
|
|
31
|
+
function emptyState() {
|
|
32
|
+
return { held: false, dirty: 0, dirtyMask: 0, rejected: 0, pendingCommand: false, values: {} };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The revocation decision for one released row, as the statements that carry it out, in the order the .NET store runs
|
|
36
|
+
* them. `seq` is the SQL of the local sequence the row gets when it moves: NEXT_SEQ, or `$seq` with its value in `seqParams`.
|
|
37
|
+
*/
|
|
38
|
+
function planDisposal(store, table, key, state, reason, subscriptionKey, report, seq, seqParams) {
|
|
39
|
+
if (state.held)
|
|
40
|
+
return { removed: false, statements: [] };
|
|
41
|
+
// A row touched by a pending command is settled by that command's result, never disposed underneath it.
|
|
42
|
+
if (state.dirty === 0 && state.pendingCommand)
|
|
43
|
+
return { removed: false, statements: [] };
|
|
44
|
+
const values = state.values;
|
|
45
|
+
if (Object.keys(values).length === 0) {
|
|
46
|
+
const script = 'UPDATE g9_rows SET deleted = 1, dirty = 0, dirty_mask = 0, rejected = 0, local_seq = ' + seq + ' WHERE tid = $t AND pk = $pk';
|
|
47
|
+
return { removed: false, statements: [{ script, params: { ...seqParams, $t: table.tid, $pk: key } }] };
|
|
48
|
+
}
|
|
49
|
+
const editor = new RowEditor(values);
|
|
50
|
+
const row = {
|
|
51
|
+
localTable: table.model.localName,
|
|
52
|
+
key,
|
|
53
|
+
reason,
|
|
54
|
+
subscriptionKey,
|
|
55
|
+
hasPendingChanges: state.dirty !== 0,
|
|
56
|
+
isRejected: state.rejected !== 0,
|
|
57
|
+
values,
|
|
58
|
+
};
|
|
59
|
+
const { rule, disposition } = store.options.revocation.decide(row, editor);
|
|
60
|
+
const now = nowMs();
|
|
61
|
+
const statements = [];
|
|
62
|
+
switch (disposition) {
|
|
63
|
+
case G9ERevocationDisposition.MarkRevoked:
|
|
64
|
+
case G9ERevocationDisposition.Retain: {
|
|
65
|
+
if (editor.changed.size > 0) {
|
|
66
|
+
const columns = Array.from(editor.changed.values());
|
|
67
|
+
const params = { $k: keyValue(table, key) };
|
|
68
|
+
columns.forEach((c, i) => (params['$v' + i] = values[c]));
|
|
69
|
+
statements.push({ script: `UPDATE ${table.quoted} SET ${columns.map((c, i) => quote(c) + ' = $v' + i).join(', ')} WHERE ${table.keyQuoted} = $k`, params });
|
|
70
|
+
}
|
|
71
|
+
statements.push({
|
|
72
|
+
script: 'INSERT OR REPLACE INTO g9_revoked (tid, pk, disposition, reason, rule, at_utc) VALUES ($t, $pk, $d, $r, $rule, $now)',
|
|
73
|
+
params: { $t: table.tid, $pk: key, $d: disposition, $r: reason, $rule: rule ? rule.name : null, $now: now },
|
|
74
|
+
});
|
|
75
|
+
if (state.dirty !== 0) {
|
|
76
|
+
// Its pending change can no longer be accepted: park it as rejected instead of uploading it on every push.
|
|
77
|
+
statements.push({
|
|
78
|
+
script: 'UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk; ' +
|
|
79
|
+
'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, at_utc) VALUES ($t, $pk, $code, $message, $now)',
|
|
80
|
+
params: { $t: table.tid, $pk: key, $code: REVOKED_REJECTION_CODE, $message: 'The row is no longer visible to this device; its pending change was not uploaded.', $now: now },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (report)
|
|
84
|
+
report.revokedRetained++;
|
|
85
|
+
return { removed: false, statements };
|
|
86
|
+
}
|
|
87
|
+
case G9ERevocationDisposition.Quarantine:
|
|
88
|
+
statements.push({
|
|
89
|
+
script: 'INSERT OR REPLACE INTO g9_quarantine (tid, pk, reason, rule, row_json, had_pending, dirty_mask, at_utc) VALUES ($t, $pk, $r, $rule, $json, $p, $m, $now)',
|
|
90
|
+
params: { $t: table.tid, $pk: key, $r: reason, $rule: rule ? rule.name : null, $json: serializeValues(values), $p: state.dirty !== 0 ? 1 : 0, $m: state.dirtyMask, $now: now },
|
|
91
|
+
});
|
|
92
|
+
statements.push(...removeLocalRowStatements(table, key, seq, seqParams));
|
|
93
|
+
if (report)
|
|
94
|
+
report.revokedQuarantined++;
|
|
95
|
+
return { removed: true, statements };
|
|
96
|
+
default:
|
|
97
|
+
statements.push(...removeLocalRowStatements(table, key, seq, seqParams));
|
|
98
|
+
if (report)
|
|
99
|
+
report.revokedDeleted++;
|
|
100
|
+
return { removed: true, statements };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Applies the revocation policy to a released row (`DisposeReleasedRow`). Rows still held by another subscription are
|
|
105
|
+
* untouched. Returns true when the row left the app's table (deleted or quarantined). Reads the row first, so it sees
|
|
106
|
+
* whatever the caller ran before it; a release of many rows goes through disposeReleasedRows.
|
|
107
|
+
*/
|
|
108
|
+
export async function disposeReleasedRow(store, connection, table, key, reason, subscriptionKey, report) {
|
|
109
|
+
const state = emptyState();
|
|
110
|
+
const first = (await connection.query('SELECT subs, dirty, dirty_mask, rejected FROM g9_rows WHERE tid = $t AND pk = $pk AND deleted = 0', { $t: table.tid, $pk: key }))[0];
|
|
111
|
+
if (first) {
|
|
112
|
+
state.held = !asLong(first[0]).isZero();
|
|
113
|
+
state.dirty = asNumber(first[1]);
|
|
114
|
+
state.dirtyMask = first[2];
|
|
115
|
+
state.rejected = asNumber(first[3]);
|
|
116
|
+
}
|
|
117
|
+
if (state.held)
|
|
118
|
+
return false;
|
|
119
|
+
if (state.dirty === 0) {
|
|
120
|
+
const commands = await connection.scalar('SELECT count(*) FROM g9_command_rows r JOIN g9_commands c ON c.command_id = r.command_id WHERE c.state = 0 AND r.tid = $t AND r.pk = $pk', { $t: table.tid, $pk: key });
|
|
121
|
+
state.pendingCommand = asNumber(commands) > 0;
|
|
122
|
+
if (state.pendingCommand)
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
state.values = await readLocalValues(connection, table, key);
|
|
126
|
+
const plan = planDisposal(store, table, key, state, reason, subscriptionKey, report, NEXT_SEQ, {});
|
|
127
|
+
for (const statement of plan.statements)
|
|
128
|
+
await connection.exec(statement.script, statement.params);
|
|
129
|
+
return plan.removed;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* `DisposeReleasedRow` for every row `releasedSql` selects (`SELECT tid, pk …`, in the order the .NET store walks them),
|
|
133
|
+
* then the clock moves once per row of a known table — the database state the row-by-row path leaves, in a handful of
|
|
134
|
+
* driver calls instead of ~6 per row (on Lynx every call crosses the bridge: 25,000 rows took 850 s row by row,
|
|
135
|
+
* LES-0051). Everything is read before anything is written, which is exact because disposing a row writes nothing
|
|
136
|
+
* another released row's decision reads: the store's connections run with foreign keys OFF, and the CALLER must take the
|
|
137
|
+
* row-by-row path when the database holds a foreign trigger. Returns how many rows left the app's tables.
|
|
138
|
+
*/
|
|
139
|
+
export async function disposeReleasedRows(store, connection, releasedSql, releasedParams, reason, subscriptionKey, report) {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
const released = (await connection.query(releasedSql, releasedParams)).map((r) => ({ tid: asNumber(r[0]), key: asString(r[1]) }));
|
|
142
|
+
if (released.length === 0)
|
|
143
|
+
return 0;
|
|
144
|
+
const id = (tid, key) => tid + '\u001f' + key;
|
|
145
|
+
const states = new Map();
|
|
146
|
+
const stateRows = await connection.query('SELECT r.tid, r.pk, r.subs, r.dirty, r.dirty_mask, r.rejected, ' +
|
|
147
|
+
'EXISTS (SELECT 1 FROM g9_command_rows cr JOIN g9_commands c ON c.command_id = cr.command_id WHERE c.state = 0 AND cr.tid = r.tid AND cr.pk = r.pk) ' +
|
|
148
|
+
`FROM (${releasedSql}) g JOIN g9_rows r ON r.tid = g.tid AND r.pk = g.pk WHERE r.deleted = 0`, releasedParams);
|
|
149
|
+
for (const r of stateRows) {
|
|
150
|
+
states.set(id(asNumber(r[0]), asString(r[1])), {
|
|
151
|
+
held: !asLong(r[2]).isZero(),
|
|
152
|
+
dirty: asNumber(r[3]),
|
|
153
|
+
dirtyMask: r[4],
|
|
154
|
+
rejected: asNumber(r[5]),
|
|
155
|
+
pendingCommand: asNumber(r[6]) !== 0,
|
|
156
|
+
values: {},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// The app rows, one query per table: `SELECT *` of exactly the rows the per-row path reads by key.
|
|
160
|
+
for (const tid of new Set(released.map((r) => r.tid))) {
|
|
161
|
+
const table = byTid(store, tid);
|
|
162
|
+
if (!table)
|
|
163
|
+
continue;
|
|
164
|
+
const integerKey = table.model.keyType === G9ESyncColumnType.Int32 || table.model.keyType === G9ESyncColumnType.Int64;
|
|
165
|
+
const sql = `SELECT g.pk, t.* FROM (${releasedSql}) g JOIN ${table.quoted} t ON t.${table.keyQuoted} = ${integerKey ? 'CAST(g.pk AS INTEGER)' : 'g.pk'} WHERE g.tid = ${tid}`;
|
|
166
|
+
const result = (await connection.runStatements(statementsOf(sql, releasedParams)))[0];
|
|
167
|
+
if (!result)
|
|
168
|
+
continue;
|
|
169
|
+
const columns = result.columns.slice(1);
|
|
170
|
+
for (const row of result.rows) {
|
|
171
|
+
const rowId = id(tid, asString(row[0]));
|
|
172
|
+
const state = (_a = states.get(rowId)) !== null && _a !== void 0 ? _a : emptyState();
|
|
173
|
+
states.set(rowId, state);
|
|
174
|
+
columns.forEach((name, i) => (state.values[name] = row[i + 1]));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const batch = new G9StatementBatch(connection, store.options.statementsPerBatch);
|
|
178
|
+
const base = asLong(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));
|
|
179
|
+
let advanced = 0;
|
|
180
|
+
let removed = 0;
|
|
181
|
+
for (const row of released) {
|
|
182
|
+
const table = byTid(store, row.tid);
|
|
183
|
+
// The .NET store advances its sequence for every released row of a known table, whether or not it moved.
|
|
184
|
+
if (!table)
|
|
185
|
+
continue;
|
|
186
|
+
const state = (_b = states.get(id(row.tid, row.key))) !== null && _b !== void 0 ? _b : emptyState();
|
|
187
|
+
const plan = planDisposal(store, table, row.key, state, reason, subscriptionKey, report, '$seq', { $seq: base.add(G9Long.fromNumber(++advanced)) });
|
|
188
|
+
if (plan.removed)
|
|
189
|
+
removed++;
|
|
190
|
+
for (const statement of plan.statements)
|
|
191
|
+
if (batch.add(statement.script, statement.params))
|
|
192
|
+
await batch.flush();
|
|
193
|
+
}
|
|
194
|
+
await batch.flush();
|
|
195
|
+
if (advanced > 0)
|
|
196
|
+
await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: advanced });
|
|
197
|
+
return removed;
|
|
198
|
+
}
|
|
199
|
+
function removeLocalRowStatements(table, key, seq, seqParams) {
|
|
200
|
+
return [
|
|
201
|
+
{ script: `DELETE FROM ${table.quoted} WHERE ${table.keyQuoted} = $k`, params: { $k: keyValue(table, key) } },
|
|
202
|
+
{
|
|
203
|
+
script: 'UPDATE g9_rows SET deleted = 1, dirty = 0, dirty_mask = 0, rejected = 0, local_seq = ' +
|
|
204
|
+
seq +
|
|
205
|
+
' WHERE tid = $t AND pk = $pk; ' +
|
|
206
|
+
'DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk; DELETE FROM g9_revoked WHERE tid = $t AND pk = $pk',
|
|
207
|
+
params: { ...seqParams, $t: table.tid, $pk: key },
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
/** `RemoveLocalRow`: the row leaves the app's table; its metadata becomes a tombstone at the clock's next value. */
|
|
212
|
+
export async function removeLocalRow(connection, table, key) {
|
|
213
|
+
for (const statement of removeLocalRowStatements(table, key, NEXT_SEQ, {}))
|
|
214
|
+
await connection.exec(statement.script, statement.params);
|
|
215
|
+
}
|
|
216
|
+
// ================================================================ holding area and retained rows
|
|
217
|
+
export async function getQuarantined(store) {
|
|
218
|
+
store.ensureInitialized();
|
|
219
|
+
const rows = await store
|
|
220
|
+
.reader()
|
|
221
|
+
.query('SELECT t.local_name, q.pk, q.reason, q.rule, q.had_pending, q.row_json, q.at_utc FROM g9_quarantine q JOIN g9_tables t ON t.tid = q.tid ORDER BY q.at_utc');
|
|
222
|
+
return rows.map((r) => ({
|
|
223
|
+
localTable: asString(r[0]),
|
|
224
|
+
key: asString(r[1]),
|
|
225
|
+
reason: asNumber(r[2]),
|
|
226
|
+
rule: asOptionalString(r[3]),
|
|
227
|
+
hadPendingChanges: asNumber(r[4]) !== 0,
|
|
228
|
+
values: deserializeValues(asString(r[5])),
|
|
229
|
+
atUtcMs: asNumber(r[6]),
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
/** Puts a quarantined row back as a pending local change (capture is ON: the restored values upload on the next push). */
|
|
233
|
+
export async function restoreQuarantined(store, localTable, key) {
|
|
234
|
+
store.ensureInitialized();
|
|
235
|
+
const table = getLocal(store, localTable);
|
|
236
|
+
return store.writeLock.run(() => store.writer().transaction('immediate', async () => {
|
|
237
|
+
const connection = store.writer();
|
|
238
|
+
const rows = await connection.query('SELECT row_json, dirty_mask, had_pending FROM g9_quarantine WHERE tid = $t AND pk = $pk', { $t: table.tid, $pk: key });
|
|
239
|
+
const first = rows[0];
|
|
240
|
+
if (!first)
|
|
241
|
+
return 0;
|
|
242
|
+
const values = deserializeValues(asString(first[0]));
|
|
243
|
+
const mask = asNumber(first[2]) !== 0 ? asLong(first[1]) : null;
|
|
244
|
+
const exists = Object.keys(await readLocalValues(connection, table, key)).length > 0;
|
|
245
|
+
const lookup = (name) => Object.keys(values).find((c) => c.toLowerCase() === name.toLowerCase());
|
|
246
|
+
if (exists) {
|
|
247
|
+
// Only the columns the user had changed are written back, so newer server values of the others survive.
|
|
248
|
+
const columns = [];
|
|
249
|
+
table.model.columns.forEach((c, i) => {
|
|
250
|
+
const actual = lookup(c.name);
|
|
251
|
+
if (actual !== undefined && (mask === null || mask.isZero() || mask.testBit(i)))
|
|
252
|
+
columns.push(actual);
|
|
253
|
+
});
|
|
254
|
+
if (columns.length > 0) {
|
|
255
|
+
const params = { $k: keyValue(table, key) };
|
|
256
|
+
columns.forEach((c, i) => (params['$v' + i] = values[c]));
|
|
257
|
+
await connection.exec(`UPDATE ${table.quoted} SET ${columns.map((c, i) => quote(c) + ' = $v' + i).join(', ')} WHERE ${table.keyQuoted} = $k`, params);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const columns = Object.keys(values);
|
|
262
|
+
const params = {};
|
|
263
|
+
columns.forEach((c, i) => (params['$v' + i] = values[c]));
|
|
264
|
+
await connection.exec(`INSERT INTO ${table.quoted} (${columns.map(quote).join(', ')}) VALUES (${columns.map((_, i) => '$v' + i).join(', ')})`, params);
|
|
265
|
+
}
|
|
266
|
+
await connection.exec('DELETE FROM g9_quarantine WHERE tid = $t AND pk = $pk', { $t: table.tid, $pk: key });
|
|
267
|
+
return 1;
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
export async function discardQuarantined(store, localTable, key) {
|
|
271
|
+
store.ensureInitialized();
|
|
272
|
+
const tid = localTable === null ? null : getLocal(store, localTable).tid;
|
|
273
|
+
return store.writeLock.run(() => store.writer().exec('DELETE FROM g9_quarantine WHERE ($t IS NULL OR tid = $t) AND ($pk IS NULL OR pk = $pk)', { $t: tid, $pk: key }));
|
|
274
|
+
}
|
|
275
|
+
export async function getRetained(store) {
|
|
276
|
+
store.ensureInitialized();
|
|
277
|
+
const rows = await store.reader().query('SELECT t.local_name, r.pk, r.disposition, r.reason, r.rule, r.at_utc FROM g9_revoked r JOIN g9_tables t ON t.tid = r.tid ORDER BY r.at_utc');
|
|
278
|
+
return rows.map((r) => ({
|
|
279
|
+
localTable: asString(r[0]),
|
|
280
|
+
key: asString(r[1]),
|
|
281
|
+
disposition: asNumber(r[2]),
|
|
282
|
+
reason: asNumber(r[3]),
|
|
283
|
+
rule: asOptionalString(r[4]),
|
|
284
|
+
atUtcMs: asNumber(r[5]),
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
/** Re-checks rows retained by a RetainWhile rule; deletes those no longer retained (`ReevaluateRetainedAsync`). */
|
|
288
|
+
export async function reevaluateRetained(store) {
|
|
289
|
+
store.ensureInitialized();
|
|
290
|
+
return store.writeLock.run(() => store.writer().transaction('immediate', async () => {
|
|
291
|
+
const connection = store.writer();
|
|
292
|
+
const retained = (await connection.query('SELECT tid, pk, reason, rule, disposition FROM g9_revoked')).map((r) => ({
|
|
293
|
+
tid: asNumber(r[0]),
|
|
294
|
+
key: asString(r[1]),
|
|
295
|
+
reason: asNumber(r[2]),
|
|
296
|
+
rule: asOptionalString(r[3]),
|
|
297
|
+
disposition: asNumber(r[4]),
|
|
298
|
+
}));
|
|
299
|
+
if (retained.length === 0)
|
|
300
|
+
return 0;
|
|
301
|
+
await connection.exec('UPDATE g9_ctx SET applying = 1 WHERE id = 1');
|
|
302
|
+
let deleted = 0;
|
|
303
|
+
for (const entry of retained) {
|
|
304
|
+
const table = byTid(store, entry.tid);
|
|
305
|
+
if (!table)
|
|
306
|
+
continue;
|
|
307
|
+
const subs = asLong(await connection.scalar('SELECT coalesce(max(subs), 0) FROM g9_rows WHERE tid = $t AND pk = $pk AND deleted = 0', { $t: entry.tid, $pk: entry.key }));
|
|
308
|
+
if (!subs.isZero()) {
|
|
309
|
+
// Visible again: no longer revoked, and a pending change parked by the revocation may upload again.
|
|
310
|
+
await connection.exec('DELETE FROM g9_revoked WHERE tid = $t AND pk = $pk; DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk AND code = $code; ' +
|
|
311
|
+
'UPDATE g9_rows SET rejected = 0 WHERE tid = $t AND pk = $pk AND NOT EXISTS (SELECT 1 FROM g9_rejections WHERE tid = $t AND pk = $pk)', { $t: entry.tid, $pk: entry.key, $code: REVOKED_REJECTION_CODE });
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (entry.disposition !== G9ERevocationDisposition.Retain)
|
|
315
|
+
continue;
|
|
316
|
+
const values = await readLocalValues(connection, table, entry.key);
|
|
317
|
+
const rule = entry.rule === null ? null : store.options.revocation.find(entry.rule);
|
|
318
|
+
const row = {
|
|
319
|
+
localTable: table.model.localName,
|
|
320
|
+
key: entry.key,
|
|
321
|
+
reason: entry.reason,
|
|
322
|
+
subscriptionKey: null,
|
|
323
|
+
hasPendingChanges: false,
|
|
324
|
+
isRejected: false,
|
|
325
|
+
values,
|
|
326
|
+
};
|
|
327
|
+
if (rule && rule.retainWhile && Object.keys(values).length > 0 && rule.retainWhile(row))
|
|
328
|
+
continue;
|
|
329
|
+
await removeLocalRow(connection, table, entry.key);
|
|
330
|
+
await connection.exec('UPDATE g9_clock SET seq = seq + 1 WHERE id = 1');
|
|
331
|
+
deleted++;
|
|
332
|
+
}
|
|
333
|
+
await connection.exec('UPDATE g9_ctx SET applying = 0 WHERE id = 1');
|
|
334
|
+
return deleted;
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=revocation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revocation.js","sourceRoot":"","sources":["../../src/sqlite/revocation.ts"],"names":[],"mappings":"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","sourcesContent":["// Twin of G9SyncData.Client.Sqlite/G9CSqliteSyncStore.Revocation.cs: revocation dispositions (ADR-0026). Every path that\n// releases a row — a move-out in a pull or a reconciliation, an unsubscribe, an expired ephemeral subscription — ends in\n// disposeReleasedRow, which asks the revocation policy what to do with a row NO subscription holds any more.\n//\n// The local sequence such a row gets is the clock's next value, (SELECT seq FROM g9_clock) + 1; the CALLER moves the\n// clock afterwards (unconditionally, like the .NET store's `++sequence`).\nimport { G9ESyncColumnType } from '../abstractions/enums.js';\nimport { G9Long } from '../abstractions/long.js';\nimport {\n G9ApplyReport,\n G9ERevocationDisposition,\n G9ERevocationReason,\n type G9QuarantinedRow,\n type G9RetainedRow,\n type G9RevokedRow,\n type G9RevokedRowEditor,\n} from '../client/storeTypes.js';\nimport { asLong, asNumber, asOptionalString, asString, G9StatementBatch, statementsOf, type G9Params, type G9SqliteConnection } from './connection.js';\nimport type { G9SqlValue } from './driver.js';\nimport { byTid, deserializeValues, getLocal, keyValue, nowMs, readLocalValues, serializeValues, type LocalTable, type StoreInternals } from './internals.js';\nimport { quote } from './sql.js';\n\nexport const REVOKED_REJECTION_CODE = 'G9S-REJ-REVOKED';\nconst NEXT_SEQ = '(SELECT seq FROM g9_clock WHERE id = 1) + 1';\n\nclass RowEditor implements G9RevokedRowEditor {\n readonly changed = new Map<string, string>();\n private readonly _values: { [column: string]: G9SqlValue };\n\n constructor(values: { [column: string]: G9SqlValue }) {\n this._values = values;\n }\n\n set(column: string, value: unknown): void {\n if (typeof column !== 'string' || column.trim() === '') throw new TypeError('A column is required.');\n const actual = Object.keys(this._values).find((c) => c.toLowerCase() === column.toLowerCase());\n if (actual === undefined) throw new RangeError(`'${column}' is not a column of the row.`);\n this._values[actual] = value as G9SqlValue;\n this.changed.set(actual.toLowerCase(), actual);\n }\n}\n\n/** What disposing a released row reads about it before anything is written. */\ninterface ReleasedRowState {\n /** Another subscription still holds the row. */\n held: boolean;\n dirty: number;\n dirtyMask: G9SqlValue;\n rejected: number;\n /** A pending command touches the row (only looked at when the row has no pending change). */\n pendingCommand: boolean;\n /** The app row; empty when it is gone. */\n values: { [column: string]: G9SqlValue };\n}\n\n/** A row with no metadata row: what the per-row path assumes when its first read finds nothing. */\nfunction emptyState(): ReleasedRowState {\n return { held: false, dirty: 0, dirtyMask: 0, rejected: 0, pendingCommand: false, values: {} };\n}\n\ninterface Statement {\n script: string;\n params: G9Params;\n}\n\n/**\n * The revocation decision for one released row, as the statements that carry it out, in the order the .NET store runs\n * them. `seq` is the SQL of the local sequence the row gets when it moves: NEXT_SEQ, or `$seq` with its value in `seqParams`.\n */\nfunction planDisposal(\n store: StoreInternals,\n table: LocalTable,\n key: string,\n state: ReleasedRowState,\n reason: G9ERevocationReason,\n subscriptionKey: string | null,\n report: G9ApplyReport | null,\n seq: string,\n seqParams: G9Params,\n): { removed: boolean; statements: Statement[] } {\n if (state.held) return { removed: false, statements: [] };\n // A row touched by a pending command is settled by that command's result, never disposed underneath it.\n if (state.dirty === 0 && state.pendingCommand) return { removed: false, statements: [] };\n\n const values = state.values;\n if (Object.keys(values).length === 0) {\n const script = 'UPDATE g9_rows SET deleted = 1, dirty = 0, dirty_mask = 0, rejected = 0, local_seq = ' + seq + ' WHERE tid = $t AND pk = $pk';\n return { removed: false, statements: [{ script, params: { ...seqParams, $t: table.tid, $pk: key } }] };\n }\n\n const editor = new RowEditor(values);\n const row: G9RevokedRow = {\n localTable: table.model.localName,\n key,\n reason,\n subscriptionKey,\n hasPendingChanges: state.dirty !== 0,\n isRejected: state.rejected !== 0,\n values,\n };\n const { rule, disposition } = store.options.revocation.decide(row, editor);\n const now = nowMs();\n const statements: Statement[] = [];\n\n switch (disposition) {\n case G9ERevocationDisposition.MarkRevoked:\n case G9ERevocationDisposition.Retain: {\n if (editor.changed.size > 0) {\n const columns = Array.from(editor.changed.values());\n const params: G9Params = { $k: keyValue(table, key) };\n columns.forEach((c, i) => (params['$v' + i] = values[c] as G9SqlValue));\n statements.push({ script: `UPDATE ${table.quoted} SET ${columns.map((c, i) => quote(c) + ' = $v' + i).join(', ')} WHERE ${table.keyQuoted} = $k`, params });\n }\n statements.push({\n script: 'INSERT OR REPLACE INTO g9_revoked (tid, pk, disposition, reason, rule, at_utc) VALUES ($t, $pk, $d, $r, $rule, $now)',\n params: { $t: table.tid, $pk: key, $d: disposition, $r: reason, $rule: rule ? rule.name : null, $now: now },\n });\n if (state.dirty !== 0) {\n // Its pending change can no longer be accepted: park it as rejected instead of uploading it on every push.\n statements.push({\n script:\n 'UPDATE g9_rows SET rejected = 1 WHERE tid = $t AND pk = $pk; ' +\n 'INSERT OR REPLACE INTO g9_rejections (tid, pk, code, message, at_utc) VALUES ($t, $pk, $code, $message, $now)',\n params: { $t: table.tid, $pk: key, $code: REVOKED_REJECTION_CODE, $message: 'The row is no longer visible to this device; its pending change was not uploaded.', $now: now },\n });\n }\n if (report) report.revokedRetained++;\n return { removed: false, statements };\n }\n case G9ERevocationDisposition.Quarantine:\n statements.push({\n script: 'INSERT OR REPLACE INTO g9_quarantine (tid, pk, reason, rule, row_json, had_pending, dirty_mask, at_utc) VALUES ($t, $pk, $r, $rule, $json, $p, $m, $now)',\n params: { $t: table.tid, $pk: key, $r: reason, $rule: rule ? rule.name : null, $json: serializeValues(values), $p: state.dirty !== 0 ? 1 : 0, $m: state.dirtyMask, $now: now },\n });\n statements.push(...removeLocalRowStatements(table, key, seq, seqParams));\n if (report) report.revokedQuarantined++;\n return { removed: true, statements };\n default:\n statements.push(...removeLocalRowStatements(table, key, seq, seqParams));\n if (report) report.revokedDeleted++;\n return { removed: true, statements };\n }\n}\n\n/**\n * Applies the revocation policy to a released row (`DisposeReleasedRow`). Rows still held by another subscription are\n * untouched. Returns true when the row left the app's table (deleted or quarantined). Reads the row first, so it sees\n * whatever the caller ran before it; a release of many rows goes through disposeReleasedRows.\n */\nexport async function disposeReleasedRow(\n store: StoreInternals,\n connection: G9SqliteConnection,\n table: LocalTable,\n key: string,\n reason: G9ERevocationReason,\n subscriptionKey: string | null,\n report: G9ApplyReport | null,\n): Promise<boolean> {\n const state = emptyState();\n const first = (await connection.query('SELECT subs, dirty, dirty_mask, rejected FROM g9_rows WHERE tid = $t AND pk = $pk AND deleted = 0', { $t: table.tid, $pk: key }))[0];\n if (first) {\n state.held = !asLong(first[0]).isZero();\n state.dirty = asNumber(first[1]);\n state.dirtyMask = first[2] as G9SqlValue;\n state.rejected = asNumber(first[3]);\n }\n if (state.held) return false;\n if (state.dirty === 0) {\n const commands = await connection.scalar(\n 'SELECT count(*) FROM g9_command_rows r JOIN g9_commands c ON c.command_id = r.command_id WHERE c.state = 0 AND r.tid = $t AND r.pk = $pk',\n { $t: table.tid, $pk: key },\n );\n state.pendingCommand = asNumber(commands) > 0;\n if (state.pendingCommand) return false;\n }\n state.values = await readLocalValues(connection, table, key);\n const plan = planDisposal(store, table, key, state, reason, subscriptionKey, report, NEXT_SEQ, {});\n for (const statement of plan.statements) await connection.exec(statement.script, statement.params);\n return plan.removed;\n}\n\n/**\n * `DisposeReleasedRow` for every row `releasedSql` selects (`SELECT tid, pk …`, in the order the .NET store walks them),\n * then the clock moves once per row of a known table — the database state the row-by-row path leaves, in a handful of\n * driver calls instead of ~6 per row (on Lynx every call crosses the bridge: 25,000 rows took 850 s row by row,\n * LES-0051). Everything is read before anything is written, which is exact because disposing a row writes nothing\n * another released row's decision reads: the store's connections run with foreign keys OFF, and the CALLER must take the\n * row-by-row path when the database holds a foreign trigger. Returns how many rows left the app's tables.\n */\nexport async function disposeReleasedRows(\n store: StoreInternals,\n connection: G9SqliteConnection,\n releasedSql: string,\n releasedParams: G9Params,\n reason: G9ERevocationReason,\n subscriptionKey: string | null,\n report: G9ApplyReport | null,\n): Promise<number> {\n const released = (await connection.query(releasedSql, releasedParams)).map((r) => ({ tid: asNumber(r[0]), key: asString(r[1]) }));\n if (released.length === 0) return 0;\n const id = (tid: number, key: string): string => tid + '\\u001f' + key;\n\n const states = new Map<string, ReleasedRowState>();\n const stateRows = await connection.query(\n 'SELECT r.tid, r.pk, r.subs, r.dirty, r.dirty_mask, r.rejected, ' +\n 'EXISTS (SELECT 1 FROM g9_command_rows cr JOIN g9_commands c ON c.command_id = cr.command_id WHERE c.state = 0 AND cr.tid = r.tid AND cr.pk = r.pk) ' +\n `FROM (${releasedSql}) g JOIN g9_rows r ON r.tid = g.tid AND r.pk = g.pk WHERE r.deleted = 0`,\n releasedParams,\n );\n for (const r of stateRows) {\n states.set(id(asNumber(r[0]), asString(r[1])), {\n held: !asLong(r[2]).isZero(),\n dirty: asNumber(r[3]),\n dirtyMask: r[4] as G9SqlValue,\n rejected: asNumber(r[5]),\n pendingCommand: asNumber(r[6]) !== 0,\n values: {},\n });\n }\n\n // The app rows, one query per table: `SELECT *` of exactly the rows the per-row path reads by key.\n for (const tid of new Set(released.map((r) => r.tid))) {\n const table = byTid(store, tid);\n if (!table) continue;\n const integerKey = table.model.keyType === G9ESyncColumnType.Int32 || table.model.keyType === G9ESyncColumnType.Int64;\n const sql = `SELECT g.pk, t.* FROM (${releasedSql}) g JOIN ${table.quoted} t ON t.${table.keyQuoted} = ${integerKey ? 'CAST(g.pk AS INTEGER)' : 'g.pk'} WHERE g.tid = ${tid}`;\n const result = (await connection.runStatements(statementsOf(sql, releasedParams)))[0];\n if (!result) continue;\n const columns = result.columns.slice(1);\n for (const row of result.rows) {\n const rowId = id(tid, asString(row[0] as G9SqlValue));\n const state = states.get(rowId) ?? emptyState();\n states.set(rowId, state);\n columns.forEach((name, i) => (state.values[name] = row[i + 1] as G9SqlValue));\n }\n }\n\n const batch = new G9StatementBatch(connection, store.options.statementsPerBatch);\n const base = asLong(await connection.scalar('SELECT seq FROM g9_clock WHERE id = 1'));\n let advanced = 0;\n let removed = 0;\n for (const row of released) {\n const table = byTid(store, row.tid);\n // The .NET store advances its sequence for every released row of a known table, whether or not it moved.\n if (!table) continue;\n const state = states.get(id(row.tid, row.key)) ?? emptyState();\n const plan = planDisposal(store, table, row.key, state, reason, subscriptionKey, report, '$seq', { $seq: base.add(G9Long.fromNumber(++advanced)) });\n if (plan.removed) removed++;\n for (const statement of plan.statements) if (batch.add(statement.script, statement.params)) await batch.flush();\n }\n await batch.flush();\n if (advanced > 0) await connection.exec('UPDATE g9_clock SET seq = seq + $n WHERE id = 1', { $n: advanced });\n return removed;\n}\n\nfunction removeLocalRowStatements(table: LocalTable, key: string, seq: string, seqParams: G9Params): Statement[] {\n return [\n { script: `DELETE FROM ${table.quoted} WHERE ${table.keyQuoted} = $k`, params: { $k: keyValue(table, key) } },\n {\n script:\n 'UPDATE g9_rows SET deleted = 1, dirty = 0, dirty_mask = 0, rejected = 0, local_seq = ' +\n seq +\n ' WHERE tid = $t AND pk = $pk; ' +\n 'DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk; DELETE FROM g9_revoked WHERE tid = $t AND pk = $pk',\n params: { ...seqParams, $t: table.tid, $pk: key },\n },\n ];\n}\n\n/** `RemoveLocalRow`: the row leaves the app's table; its metadata becomes a tombstone at the clock's next value. */\nexport async function removeLocalRow(connection: G9SqliteConnection, table: LocalTable, key: string): Promise<void> {\n for (const statement of removeLocalRowStatements(table, key, NEXT_SEQ, {})) await connection.exec(statement.script, statement.params);\n}\n\n// ================================================================ holding area and retained rows\n\nexport async function getQuarantined(store: StoreInternals): Promise<G9QuarantinedRow[]> {\n store.ensureInitialized();\n const rows = await store\n .reader()\n .query('SELECT t.local_name, q.pk, q.reason, q.rule, q.had_pending, q.row_json, q.at_utc FROM g9_quarantine q JOIN g9_tables t ON t.tid = q.tid ORDER BY q.at_utc');\n return rows.map((r) => ({\n localTable: asString(r[0]),\n key: asString(r[1]),\n reason: asNumber(r[2]) as G9ERevocationReason,\n rule: asOptionalString(r[3]),\n hadPendingChanges: asNumber(r[4]) !== 0,\n values: deserializeValues(asString(r[5])),\n atUtcMs: asNumber(r[6]),\n }));\n}\n\n/** Puts a quarantined row back as a pending local change (capture is ON: the restored values upload on the next push). */\nexport async function restoreQuarantined(store: StoreInternals, localTable: string, key: string): Promise<number> {\n store.ensureInitialized();\n const table = getLocal(store, localTable);\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n const rows = await connection.query('SELECT row_json, dirty_mask, had_pending FROM g9_quarantine WHERE tid = $t AND pk = $pk', { $t: table.tid, $pk: key });\n const first = rows[0];\n if (!first) return 0;\n const values = deserializeValues(asString(first[0]));\n const mask = asNumber(first[2]) !== 0 ? asLong(first[1]) : null;\n const exists = Object.keys(await readLocalValues(connection, table, key)).length > 0;\n const lookup = (name: string): string | undefined => Object.keys(values).find((c) => c.toLowerCase() === name.toLowerCase());\n if (exists) {\n // Only the columns the user had changed are written back, so newer server values of the others survive.\n const columns: string[] = [];\n table.model.columns.forEach((c, i) => {\n const actual = lookup(c.name);\n if (actual !== undefined && (mask === null || mask.isZero() || mask.testBit(i))) columns.push(actual);\n });\n if (columns.length > 0) {\n const params: { [name: string]: G9SqlValue } = { $k: keyValue(table, key) };\n columns.forEach((c, i) => (params['$v' + i] = values[c] as G9SqlValue));\n await connection.exec(`UPDATE ${table.quoted} SET ${columns.map((c, i) => quote(c) + ' = $v' + i).join(', ')} WHERE ${table.keyQuoted} = $k`, params);\n }\n } else {\n const columns = Object.keys(values);\n const params: { [name: string]: G9SqlValue } = {};\n columns.forEach((c, i) => (params['$v' + i] = values[c] as G9SqlValue));\n await connection.exec(`INSERT INTO ${table.quoted} (${columns.map(quote).join(', ')}) VALUES (${columns.map((_, i) => '$v' + i).join(', ')})`, params);\n }\n await connection.exec('DELETE FROM g9_quarantine WHERE tid = $t AND pk = $pk', { $t: table.tid, $pk: key });\n return 1;\n }),\n );\n}\n\nexport async function discardQuarantined(store: StoreInternals, localTable: string | null, key: string | null): Promise<number> {\n store.ensureInitialized();\n const tid = localTable === null ? null : getLocal(store, localTable).tid;\n return store.writeLock.run(() =>\n store.writer().exec('DELETE FROM g9_quarantine WHERE ($t IS NULL OR tid = $t) AND ($pk IS NULL OR pk = $pk)', { $t: tid, $pk: key }),\n );\n}\n\nexport async function getRetained(store: StoreInternals): Promise<G9RetainedRow[]> {\n store.ensureInitialized();\n const rows = await store.reader().query('SELECT t.local_name, r.pk, r.disposition, r.reason, r.rule, r.at_utc FROM g9_revoked r JOIN g9_tables t ON t.tid = r.tid ORDER BY r.at_utc');\n return rows.map((r) => ({\n localTable: asString(r[0]),\n key: asString(r[1]),\n disposition: asNumber(r[2]) as G9ERevocationDisposition,\n reason: asNumber(r[3]) as G9ERevocationReason,\n rule: asOptionalString(r[4]),\n atUtcMs: asNumber(r[5]),\n }));\n}\n\n/** Re-checks rows retained by a RetainWhile rule; deletes those no longer retained (`ReevaluateRetainedAsync`). */\nexport async function reevaluateRetained(store: StoreInternals): Promise<number> {\n store.ensureInitialized();\n return store.writeLock.run(() =>\n store.writer().transaction('immediate', async () => {\n const connection = store.writer();\n const retained = (await connection.query('SELECT tid, pk, reason, rule, disposition FROM g9_revoked')).map((r) => ({\n tid: asNumber(r[0]),\n key: asString(r[1]),\n reason: asNumber(r[2]),\n rule: asOptionalString(r[3]),\n disposition: asNumber(r[4]),\n }));\n if (retained.length === 0) return 0;\n await connection.exec('UPDATE g9_ctx SET applying = 1 WHERE id = 1');\n let deleted = 0;\n for (const entry of retained) {\n const table = byTid(store, entry.tid);\n if (!table) continue;\n const subs = asLong(\n await connection.scalar('SELECT coalesce(max(subs), 0) FROM g9_rows WHERE tid = $t AND pk = $pk AND deleted = 0', { $t: entry.tid, $pk: entry.key }),\n );\n if (!subs.isZero()) {\n // Visible again: no longer revoked, and a pending change parked by the revocation may upload again.\n await connection.exec(\n 'DELETE FROM g9_revoked WHERE tid = $t AND pk = $pk; DELETE FROM g9_rejections WHERE tid = $t AND pk = $pk AND code = $code; ' +\n 'UPDATE g9_rows SET rejected = 0 WHERE tid = $t AND pk = $pk AND NOT EXISTS (SELECT 1 FROM g9_rejections WHERE tid = $t AND pk = $pk)',\n { $t: entry.tid, $pk: entry.key, $code: REVOKED_REJECTION_CODE },\n );\n continue;\n }\n if (entry.disposition !== G9ERevocationDisposition.Retain) continue;\n const values = await readLocalValues(connection, table, entry.key);\n const rule = entry.rule === null ? null : store.options.revocation.find(entry.rule);\n const row: G9RevokedRow = {\n localTable: table.model.localName,\n key: entry.key,\n reason: entry.reason as G9ERevocationReason,\n subscriptionKey: null,\n hasPendingChanges: false,\n isRejected: false,\n values,\n };\n if (rule && rule.retainWhile && Object.keys(values).length > 0 && rule.retainWhile(row)) continue;\n await removeLocalRow(connection, table, entry.key);\n await connection.exec('UPDATE g9_clock SET seq = seq + 1 WHERE id = 1');\n deleted++;\n }\n await connection.exec('UPDATE g9_ctx SET applying = 0 WHERE id = 1');\n return deleted;\n }),\n );\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { G9Long } from '../abstractions/long.js';
|
|
2
|
+
import type { G9SyncClientColumn, G9SyncClientTable } from '../client/model.js';
|
|
3
|
+
/** How DateTime values are stored locally (`G9ESqliteDateTimeStorage`). */
|
|
4
|
+
export declare const G9ESqliteDateTimeStorage: {
|
|
5
|
+
/** ISO-like text in the DateTimeFormat (sqlite-net's text format by default). */
|
|
6
|
+
readonly Text: 0;
|
|
7
|
+
/** .NET ticks as INTEGER (sqlite-net storeDateTimeAsTicks). */
|
|
8
|
+
readonly Ticks: 1;
|
|
9
|
+
};
|
|
10
|
+
export type G9ESqliteDateTimeStorage = (typeof G9ESqliteDateTimeStorage)[keyof typeof G9ESqliteDateTimeStorage];
|
|
11
|
+
/** How Decimal values are stored locally (`G9ESqliteDecimalStorage`). */
|
|
12
|
+
export declare const G9ESqliteDecimalStorage: {
|
|
13
|
+
/** REAL, like sqlite-net. */
|
|
14
|
+
readonly Real: 0;
|
|
15
|
+
/** Invariant text, exact. */
|
|
16
|
+
readonly Text: 1;
|
|
17
|
+
};
|
|
18
|
+
export type G9ESqliteDecimalStorage = (typeof G9ESqliteDecimalStorage)[keyof typeof G9ESqliteDecimalStorage];
|
|
19
|
+
/** The storage options the schema depends on. */
|
|
20
|
+
export interface G9SqliteStorageOptions {
|
|
21
|
+
dateTimeStorage: G9ESqliteDateTimeStorage;
|
|
22
|
+
decimalStorage: G9ESqliteDecimalStorage;
|
|
23
|
+
/** Only the .NET default `yyyy-MM-dd'T'HH:mm:ss.fffffff` is supported (see PARITY.md). */
|
|
24
|
+
dateTimeFormat: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const EPHEMERAL_SLOT = 63;
|
|
27
|
+
export declare const NOW_MS = "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
28
|
+
export declare const APPLYING = "(SELECT applying FROM g9_ctx WHERE id = 1) = 0";
|
|
29
|
+
export declare const SEQ = "(SELECT seq FROM g9_clock WHERE id = 1)";
|
|
30
|
+
/** Command mode (ADR-0025): local effects of a command being recorded. */
|
|
31
|
+
export declare const COMMAND_MODE = "(SELECT applying FROM g9_ctx WHERE id = 1) = 2";
|
|
32
|
+
export declare const METADATA = "CREATE TABLE IF NOT EXISTS g9_meta (key TEXT NOT NULL PRIMARY KEY, value TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_clock (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), seq INTEGER NOT NULL);\nINSERT OR IGNORE INTO g9_clock (id, seq) VALUES (1, 0);\nCREATE TABLE IF NOT EXISTS g9_ctx (id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1), applying INTEGER NOT NULL DEFAULT 0);\nINSERT OR IGNORE INTO g9_ctx (id, applying) VALUES (1, 0);\nUPDATE g9_ctx SET applying = 0 WHERE id = 1 AND applying <> 0;\nCREATE TABLE IF NOT EXISTS g9_tables (\n tid INTEGER PRIMARY KEY AUTOINCREMENT,\n local_name TEXT NOT NULL UNIQUE,\n server_name TEXT NOT NULL,\n model_hash TEXT NOT NULL,\n server_table_id INTEGER NULL,\n negotiated_json TEXT NULL,\n synced_columns TEXT NULL);\nCREATE TABLE IF NOT EXISTS g9_rows (\n tid INTEGER NOT NULL,\n pk TEXT NOT NULL,\n server_ver INTEGER NOT NULL DEFAULT 0,\n local_seq INTEGER NOT NULL DEFAULT 0,\n dirty INTEGER NOT NULL DEFAULT 0,\n dirty_mask INTEGER NOT NULL DEFAULT 0,\n hlc_ms INTEGER NULL,\n subs INTEGER NOT NULL DEFAULT 0,\n rejected INTEGER NOT NULL DEFAULT 0,\n deleted INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_rows_dirty ON g9_rows (local_seq) WHERE dirty <> 0;\nCREATE INDEX IF NOT EXISTS g9_rows_feed ON g9_rows (tid, local_seq);\nCREATE INDEX IF NOT EXISTS g9_rows_deleted ON g9_rows (local_seq) WHERE deleted = 1;\nCREATE TABLE IF NOT EXISTS g9_subscriptions (\n key TEXT NOT NULL PRIMARY KEY,\n name TEXT NOT NULL,\n params TEXT NOT NULL,\n slot INTEGER NOT NULL,\n ephemeral INTEGER NOT NULL,\n ttl_seconds INTEGER NULL,\n cursor TEXT NULL,\n definition_hash TEXT NULL,\n auth_hash TEXT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n error_code TEXT NULL,\n move_out_policy INTEGER NULL,\n created_utc INTEGER NOT NULL,\n last_success_utc INTEGER NULL,\n last_reconcile_utc INTEGER NULL,\n last_access_utc INTEGER NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_ephemeral_rows (sub_key TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (sub_key, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_ephemeral_rows_row ON g9_ephemeral_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_stage_runs (run_id TEXT NOT NULL PRIMARY KEY, plan_json TEXT NOT NULL, backfills_json TEXT NULL, created_utc INTEGER NOT NULL, complete INTEGER NOT NULL DEFAULT 0, generation TEXT NULL, stage_seq INTEGER NOT NULL DEFAULT 0, from_cursors_json TEXT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_stage (run_id TEXT NOT NULL, seq INTEGER NOT NULL, subscription_key TEXT NULL, row_count INTEGER NOT NULL, continuation_json TEXT NULL, payload BLOB NULL, PRIMARY KEY (run_id, seq)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_rejections (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, code TEXT NOT NULL, message TEXT NULL, sql_state TEXT NULL, constraint_name TEXT NULL,\n column_name TEXT NULL, details TEXT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_runs (run_id TEXT NOT NULL PRIMARY KEY, started_utc INTEGER NOT NULL, report_json TEXT NOT NULL);\nCREATE TABLE IF NOT EXISTS g9_stats (key TEXT NOT NULL PRIMARY KEY, value REAL NOT NULL, samples INTEGER NOT NULL) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_commands (\n command_id TEXT NOT NULL PRIMARY KEY,\n seq INTEGER NOT NULL,\n name TEXT NOT NULL,\n version INTEGER NOT NULL,\n payload TEXT NOT NULL,\n created_utc INTEGER NOT NULL,\n state INTEGER NOT NULL DEFAULT 0,\n batch_id INTEGER NULL,\n code TEXT NULL,\n message TEXT NULL,\n result TEXT NULL,\n completed_utc INTEGER NULL) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_commands_pending ON g9_commands (seq) WHERE state = 0;\nCREATE TABLE IF NOT EXISTS g9_command_rows (command_id TEXT NOT NULL, tid INTEGER NOT NULL, pk TEXT NOT NULL, PRIMARY KEY (command_id, tid, pk)) WITHOUT ROWID;\nCREATE INDEX IF NOT EXISTS g9_command_rows_row ON g9_command_rows (tid, pk);\nCREATE TABLE IF NOT EXISTS g9_quarantine (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, row_json TEXT NOT NULL,\n had_pending INTEGER NOT NULL, dirty_mask INTEGER NOT NULL, at_utc INTEGER NOT NULL, PRIMARY KEY (tid, pk)) WITHOUT ROWID;\nCREATE TABLE IF NOT EXISTS g9_revoked (\n tid INTEGER NOT NULL, pk TEXT NOT NULL, disposition INTEGER NOT NULL, reason INTEGER NOT NULL, rule TEXT NULL, at_utc INTEGER NOT NULL,\n PRIMARY KEY (tid, pk)) WITHOUT ROWID;";
|
|
33
|
+
/** `[name]` with `]` doubled. */
|
|
34
|
+
export declare function quote(identifier: string): string;
|
|
35
|
+
/** The membership bit of a slot (slot 63 is the sign bit). */
|
|
36
|
+
export declare function bit(slot: number): G9Long;
|
|
37
|
+
/** Every column of a table set in the dirty mask. */
|
|
38
|
+
export declare function allColumnsMask(count: number): G9Long;
|
|
39
|
+
export declare function literal(value: number | G9Long): string;
|
|
40
|
+
export declare function columnType(type: number, options: G9SqliteStorageOptions): string;
|
|
41
|
+
/** DateTime.MinValue in the default text format. */
|
|
42
|
+
export declare const DATE_TIME_MIN_TEXT = "0001-01-01T00:00:00.0000000";
|
|
43
|
+
export declare function defaultLiteral(type: number, options: G9SqliteStorageOptions): string;
|
|
44
|
+
export declare function columnDefinition(column: G9SyncClientColumn, options: G9SqliteStorageOptions): string;
|
|
45
|
+
export declare function createTable(table: G9SyncClientTable, options: G9SqliteStorageOptions): string;
|
|
46
|
+
/** Canonical key text of a row alias (`NEW`, `OLD`, a table). */
|
|
47
|
+
export declare function keyExpression(table: G9SyncClientTable, alias: string): string;
|
|
48
|
+
export declare function triggerName(table: G9SyncClientTable, suffix: string): string;
|
|
49
|
+
export declare function dropTriggers(table: G9SyncClientTable): string;
|
|
50
|
+
/** Command-mode triggers (ADR-0025), on every table regardless of direction. */
|
|
51
|
+
export declare function createCommandTriggers(table: G9SyncClientTable, tid: number): string;
|
|
52
|
+
/** The capture triggers (D-03). */
|
|
53
|
+
export declare function createTriggers(table: G9SyncClientTable, tid: number): string;
|
|
54
|
+
/** SHA-256 (first 16 bytes, lower hex) of the table's model text: a change re-creates the triggers. */
|
|
55
|
+
export declare function modelHash(table: G9SyncClientTable): string;
|
|
56
|
+
/**
|
|
57
|
+
* Splits a script into single statements (the driver runs one statement per entry). Semicolons inside strings, quoted
|
|
58
|
+
* identifiers, comments and a trigger's BEGIN … END body do not split.
|
|
59
|
+
*/
|
|
60
|
+
export declare function splitSql(script: string): string[];
|