@syncular/server 0.15.48 → 0.17.0
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/README.md +66 -9
- package/dist/admin.js +1 -5
- package/dist/authoritative-query.d.ts +14 -6
- package/dist/authoritative-query.js +60 -82
- package/dist/d1-storage.d.ts +13 -1
- package/dist/d1-storage.js +468 -121
- package/dist/operations.d.ts +2 -0
- package/dist/operations.js +23 -4
- package/dist/postgres-storage.d.ts +6 -1
- package/dist/postgres-storage.js +80 -24
- package/dist/prune.d.ts +3 -1
- package/dist/prune.js +18 -14
- package/dist/pull.js +79 -39
- package/dist/push.js +5 -5
- package/dist/realtime.d.ts +1 -1
- package/dist/realtime.js +16 -10
- package/dist/relational-rows.d.ts +7 -1
- package/dist/relational-rows.js +17 -2
- package/dist/sqlite-bun.js +2 -2
- package/dist/sqlite-image.d.ts +3 -3
- package/dist/sqlite-image.js +10 -6
- package/dist/sqlite-node.js +2 -2
- package/dist/sqlite-storage.d.ts +6 -1
- package/dist/sqlite-storage.js +93 -32
- package/dist/storage-errors.d.ts +1 -1
- package/dist/storage-errors.js +7 -0
- package/dist/storage.d.ts +29 -5
- package/package.json +2 -2
- package/src/admin.ts +4 -6
- package/src/authoritative-query.ts +89 -94
- package/src/d1-storage.ts +641 -159
- package/src/operations.ts +31 -3
- package/src/postgres-storage.ts +146 -46
- package/src/prune.ts +29 -15
- package/src/pull.ts +102 -49
- package/src/push.ts +5 -5
- package/src/realtime.ts +20 -9
- package/src/relational-rows.ts +18 -2
- package/src/sqlite-bun.ts +2 -2
- package/src/sqlite-image.ts +26 -15
- package/src/sqlite-node.ts +2 -2
- package/src/sqlite-storage.ts +131 -37
- package/src/storage-errors.ts +22 -1
- package/src/storage.ts +50 -5
package/README.md
CHANGED
|
@@ -15,6 +15,11 @@ registry, command mutations use the ordinary serialized push path, and
|
|
|
15
15
|
specified in [`docs/REMOTE.md`](../../docs/REMOTE.md) and the practical setup is
|
|
16
16
|
in the [remote operations guide](https://syncular.dev/guide-remote-operations/).
|
|
17
17
|
|
|
18
|
+
Remote query registration requires generated `relationPlans` for each selected
|
|
19
|
+
SQL statement. Run `syncular generate` before upgrading existing query modules.
|
|
20
|
+
The server uses those boundaries to bind every physical table occurrence to
|
|
21
|
+
the authenticated partition, including quoted self joins and CTE bodies.
|
|
22
|
+
|
|
18
23
|
Application intent belongs in immutable domain event rows written in the same
|
|
19
24
|
commit as the state change. `SyncularServerEvents` below remains operational
|
|
20
25
|
telemetry. See the [domain event guide](https://syncular.dev/guide-domain-events/).
|
|
@@ -105,6 +110,11 @@ Log the cause for operators and stop startup. Do not catch readiness errors in
|
|
|
105
110
|
authentication or convert them to a 401; request-time schema checks are only a
|
|
106
111
|
defensive fallback.
|
|
107
112
|
|
|
113
|
+
For D1, finish `storage.migrateSchema(compileSchema(schema))` across separate
|
|
114
|
+
Worker invocations before admitting traffic. Each call returns `complete` and
|
|
115
|
+
`statementsExecuted`; the default budget is 50 statements. See
|
|
116
|
+
[D1 schema migration](https://syncular.dev/server-workers/#schema-migration).
|
|
117
|
+
|
|
108
118
|
After restoring an authoritative database, keep traffic stopped and call
|
|
109
119
|
`rotatePartitionLogEpoch({ storage, partition })` for every restored
|
|
110
120
|
partition. The rotation clears stale client cursors and requires version 2
|
|
@@ -927,10 +937,22 @@ at or below it. Nothing prunes automatically — the host schedules it.
|
|
|
927
937
|
|
|
928
938
|
**When to run.** A periodic job per partition — hourly to daily is the
|
|
929
939
|
sensible range; there is no benefit below the granularity of your
|
|
930
|
-
`activeWindowMs`.
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
940
|
+
`activeWindowMs`. Pass `events` to get `prune.completed` per pass.
|
|
941
|
+
|
|
942
|
+
Pruning verifies the captured log epoch and updates the horizon together with
|
|
943
|
+
commit/change/scope deletion in one transaction. Concurrent passes cannot
|
|
944
|
+
lower the horizon. A retry cleans up eligible records even when the horizon
|
|
945
|
+
already covers them. A restore invalidates a pending pass with
|
|
946
|
+
`sync.storage.prune_epoch_mismatch`; recompute its retention inputs.
|
|
947
|
+
Unregistered partitions cannot be pruned. D1 maintenance enters the owning
|
|
948
|
+
Durable Object's existing write queue through
|
|
949
|
+
`SyncularRealtimeHost.pruneCommitLog`.
|
|
950
|
+
|
|
951
|
+
Custom storage adapters must add `getPartitionLogEpoch(partition)` and replace
|
|
952
|
+
`pruneCommitsThrough(partition, seq)` with
|
|
953
|
+
`pruneCommitsThrough(partition, { logEpoch, throughSeq })`, returning
|
|
954
|
+
`{ previousHorizonSeq, horizonSeq, removedCommits }` from the transaction.
|
|
955
|
+
`setHorizonSeq` remains monotonic and is no longer used by the pruning helper.
|
|
934
956
|
|
|
935
957
|
**The retention floors (§4.6, encoded in `RetentionPolicy`).** The
|
|
936
958
|
horizon never advances past `min(cursor)` of *active* clients — clients
|
|
@@ -1094,13 +1116,19 @@ cannot silently return.
|
|
|
1094
1116
|
### commitSeq allocation under concurrency
|
|
1095
1117
|
|
|
1096
1118
|
Per-partition `commitSeq` is dense and gap-free (§2.1). `appendCommit`
|
|
1097
|
-
allocates it with
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1119
|
+
allocates it with an upsert that increments `sync_partitions.max_commit_seq`
|
|
1120
|
+
and returns the allocated value. A common table expression feeds that value
|
|
1121
|
+
into the commit metadata insert in the same SQL statement. The upsert takes a
|
|
1122
|
+
row-level write lock on the partition for the transaction duration. Concurrent
|
|
1123
|
+
pushes to that partition serialize; separate partitions use separate locks. A Postgres `SEQUENCE` is deliberately **not** used: it would leave
|
|
1102
1124
|
gaps on rollback, which the §4.5 pull-window arithmetic does not tolerate.
|
|
1103
1125
|
|
|
1126
|
+
Each change insert expands its scope object into the inverted scope entries in
|
|
1127
|
+
the same statement. Serialized scopes bind as text before JSONB parsing; this
|
|
1128
|
+
avoids driver-specific JSON string encoding. Historical string-form scopes
|
|
1129
|
+
remain readable. Empty scopes still produce a change row, and repeated scopes
|
|
1130
|
+
within a commit retain one index entry.
|
|
1131
|
+
|
|
1104
1132
|
### Multi-instance fanout (LISTEN/NOTIFY)
|
|
1105
1133
|
|
|
1106
1134
|
Behind a load balancer, a commit applied on instance A fans out to A's
|
|
@@ -1149,3 +1177,32 @@ deterministic in-process sqlite loopback):
|
|
|
1149
1177
|
```sh
|
|
1150
1178
|
SYNCULAR_PG_URL=postgres://user:pass@localhost:5432/db bun run bench
|
|
1151
1179
|
```
|
|
1180
|
+
|
|
1181
|
+
Custom storage adapters must implement
|
|
1182
|
+
`getActiveClientCursorFloor(partition, cutoffMs)`. Return the minimum cursor
|
|
1183
|
+
whose `updatedAtMs >= cutoffMs`, or `null` when no client qualifies. Preserve
|
|
1184
|
+
negative bootstrap cursors. Pruning and admin horizon status use this scalar
|
|
1185
|
+
aggregate; `listClientCursors` remains the explicit listing interface.
|
|
1186
|
+
|
|
1187
|
+
SQLite image builders now return `Promise<Uint8Array>` and receive
|
|
1188
|
+
`rowBatches`, an iterable or async iterable of row arrays. Replace custom
|
|
1189
|
+
builders' `input.rows` loop with `for await (const rows of input.rowBatches)`,
|
|
1190
|
+
insert each batch into the dedicated image database, and count rows during
|
|
1191
|
+
consumption. Write the final row count into `_syncular_segment` before
|
|
1192
|
+
serialization. Await `buildSqliteImage(input)` when calling the built-in
|
|
1193
|
+
Bun or Node builder directly.
|
|
1194
|
+
|
|
1195
|
+
The server shares in-flight builds for the same storage pair and artifact
|
|
1196
|
+
identity after authorization. Sharing is local to one process. Signed URL
|
|
1197
|
+
grants remain per request. The first eligibility probe has at most
|
|
1198
|
+
`limitSnapshotRows + 1` rows; subsequent builder batches have at most 5,000
|
|
1199
|
+
rows. The image database and serialized output still consume memory.
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
Realtime acknowledgements call `advanceClientCursor(partition, clientId,
|
|
1203
|
+
actorId, logEpoch, cursor, updatedAtMs)`. Custom storage adapters must implement
|
|
1204
|
+
this atomic update: advance the cursor and activity timestamp to their respective
|
|
1205
|
+
maxima, preserve registration fields, and require a matching actor and current
|
|
1206
|
+
partition log epoch. Leave missing records unchanged. SQLite, Postgres, and D1
|
|
1207
|
+
perform one update without reading or serializing the subscription list. HTTP
|
|
1208
|
+
registration keeps its existing cursor and subscription replacement rules.
|
package/dist/admin.js
CHANGED
|
@@ -173,11 +173,7 @@ export class SyncularAdmin {
|
|
|
173
173
|
const nowMs = this.#clock();
|
|
174
174
|
const maxCommitSeq = await this.#storage.getMaxCommitSeq(partition);
|
|
175
175
|
const horizonSeq = await this.#storage.getHorizonSeq(partition);
|
|
176
|
-
const
|
|
177
|
-
const activeCursors = cursors
|
|
178
|
-
.filter((c) => c.updatedAtMs >= nowMs - this.#retention.activeWindowMs)
|
|
179
|
-
.map((c) => c.cursor);
|
|
180
|
-
const activeCursorFloor = activeCursors.length > 0 ? Math.min(...activeCursors) : null;
|
|
176
|
+
const activeCursorFloor = await this.#storage.getActiveClientCursorFloor(partition, nowMs - this.#retention.activeWindowMs);
|
|
181
177
|
const cursorFloor = activeCursorFloor ?? Number.MAX_SAFE_INTEGER;
|
|
182
178
|
const forcedSeq = await this.#storage.getCommitSeqBefore(partition, nowMs - this.#retention.ageForceMs);
|
|
183
179
|
const retainFloor = maxCommitSeq - this.#retention.minRetainedCommits;
|
|
@@ -9,12 +9,20 @@ export interface BoundAuthoritativeQuery {
|
|
|
9
9
|
readonly params: readonly AuthoritativeQueryValue[];
|
|
10
10
|
}
|
|
11
11
|
declare const PARTITION_BIND: unique symbol;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/** Compiler-proven occurrences in the exact generated positional statement. */
|
|
13
|
+
export interface AuthoritativeRelationPlan {
|
|
14
|
+
readonly sql: string;
|
|
15
|
+
readonly relations: readonly {
|
|
16
|
+
readonly table: string;
|
|
17
|
+
readonly start: number;
|
|
18
|
+
readonly end: number;
|
|
19
|
+
readonly alias?: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
/** Validate trusted generated metadata before registration or storage execution. */
|
|
23
|
+
export declare function validateAuthoritativeRelationPlan(plan: AuthoritativeRelationPlan, declaredTables: readonly string[]): void;
|
|
24
|
+
/** Bind every compiler-proven table occurrence to the authenticated partition. */
|
|
25
|
+
export declare function prepareAuthoritativeQuery(plan: AuthoritativeRelationPlan, params: readonly AuthoritativeQueryValue[], declaredTables: readonly string[], tables: ReadonlyMap<string, CompiledTable>): PreparedAuthoritativeQuery;
|
|
18
26
|
export declare function bindAuthoritativePartition(prepared: PreparedAuthoritativeQuery, partition: string): BoundAuthoritativeQuery;
|
|
19
27
|
export declare function postgresPlaceholders(sql: string): string;
|
|
20
28
|
export {};
|
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
import { quoteIdent, SYNC_PARTITION_COLUMN } from './relational-rows.js';
|
|
2
2
|
const PARTITION_BIND = Symbol('syncular.authoritative_partition');
|
|
3
|
-
const RESERVED_ALIAS = new Set([
|
|
4
|
-
'on',
|
|
5
|
-
'where',
|
|
6
|
-
'group',
|
|
7
|
-
'order',
|
|
8
|
-
'inner',
|
|
9
|
-
'left',
|
|
10
|
-
'right',
|
|
11
|
-
'full',
|
|
12
|
-
'outer',
|
|
13
|
-
'natural',
|
|
14
|
-
'join',
|
|
15
|
-
'cross',
|
|
16
|
-
'using',
|
|
17
|
-
'limit',
|
|
18
|
-
'having',
|
|
19
|
-
]);
|
|
20
|
-
const IDENT = '[A-Za-z_][A-Za-z0-9_]*';
|
|
21
|
-
const TABLE_REF_RE = new RegExp(`\\b(FROM|(?:NATURAL\\s+)?(?:(?:LEFT|RIGHT|FULL)(?:\\s+OUTER)?|INNER|CROSS)?\\s*JOIN)\\s+((?:\\(\\s*)*)(${IDENT})(?:\\s+(?:AS\\s+)?((?!(?:${[...RESERVED_ALIAS].join('|')})\\b)${IDENT}))?`, 'gi');
|
|
22
3
|
function protectedSqlEnd(sql, index) {
|
|
23
4
|
const char = sql[index];
|
|
24
5
|
const next = sql[index + 1];
|
|
@@ -48,97 +29,94 @@ function protectedSqlEnd(sql, index) {
|
|
|
48
29
|
}
|
|
49
30
|
return undefined;
|
|
50
31
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
out += sql[index];
|
|
58
|
-
else
|
|
59
|
-
out += sql.slice(index, end).replace(/[^\n]/g, ' ');
|
|
60
|
-
index = end ?? index + 1;
|
|
32
|
+
/** Validate trusted generated metadata before registration or storage execution. */
|
|
33
|
+
export function validateAuthoritativeRelationPlan(plan, declaredTables) {
|
|
34
|
+
if (plan === undefined ||
|
|
35
|
+
typeof plan.sql !== 'string' ||
|
|
36
|
+
!Array.isArray(plan.relations)) {
|
|
37
|
+
throw new Error('registered query requires generated relation plans; regenerate queries');
|
|
61
38
|
}
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Turn generated local SQL into a partition-local authoritative statement.
|
|
66
|
-
* Only relations declared by the generated descriptor are rewritten. Values
|
|
67
|
-
* remain parameters; request data is never interpolated into SQL.
|
|
68
|
-
*/
|
|
69
|
-
export function prepareAuthoritativeQuery(sql, params, declaredTables, tables) {
|
|
70
|
-
if (maskedSql(sql).includes(';'))
|
|
71
|
-
throw new Error('registered query must be one SELECT');
|
|
39
|
+
const sql = plan.sql;
|
|
72
40
|
const declared = new Set(declaredTables);
|
|
73
|
-
const masked = maskedSql(sql);
|
|
74
|
-
const replacements = [];
|
|
75
41
|
const found = new Set();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
42
|
+
let previousEnd = 0;
|
|
43
|
+
for (const relation of plan.relations) {
|
|
44
|
+
if (!Number.isSafeInteger(relation.start) ||
|
|
45
|
+
!Number.isSafeInteger(relation.end) ||
|
|
46
|
+
relation.start < previousEnd ||
|
|
47
|
+
relation.end <= relation.start ||
|
|
48
|
+
relation.end > sql.length) {
|
|
49
|
+
throw new Error('registered query relation boundaries do not match its SQL; regenerate queries');
|
|
83
50
|
}
|
|
84
|
-
|
|
85
|
-
|
|
51
|
+
previousEnd = relation.end;
|
|
52
|
+
if (!declared.has(relation.table)) {
|
|
53
|
+
throw new Error('registered query table metadata does not match its SQL');
|
|
86
54
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
55
|
+
const spelling = sql.slice(relation.start, relation.end);
|
|
56
|
+
const quote = spelling[0];
|
|
57
|
+
const name = quote === '['
|
|
58
|
+
? spelling.slice(1, -1)
|
|
59
|
+
: quote === '"' || quote === '`'
|
|
60
|
+
? spelling
|
|
61
|
+
.slice(1, -1)
|
|
62
|
+
.split(quote + quote)
|
|
63
|
+
.join(quote)
|
|
64
|
+
: spelling;
|
|
65
|
+
if (name.toLowerCase() !== relation.table.toLowerCase()) {
|
|
66
|
+
throw new Error('registered query relation name does not match its SQL; regenerate queries');
|
|
90
67
|
}
|
|
91
|
-
|
|
92
|
-
const afterOperator = match[1].length;
|
|
93
|
-
const relative = match[0]
|
|
94
|
-
.toLowerCase()
|
|
95
|
-
.indexOf(rawTable.toLowerCase(), afterOperator);
|
|
96
|
-
const start = matchStart + relative;
|
|
97
|
-
const projection = table.columns.map((column) => quoteIdent(column.name));
|
|
98
|
-
replacements.push({
|
|
99
|
-
start,
|
|
100
|
-
end: start + rawTable.length,
|
|
101
|
-
text: `(SELECT ${projection.join(', ')} FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=/*syncular_partition*/?)${alias === undefined ? ` AS ${quoteIdent(table.name)}` : ''}`,
|
|
102
|
-
});
|
|
103
|
-
found.add(table.name);
|
|
68
|
+
found.add(relation.table);
|
|
104
69
|
}
|
|
105
70
|
if (found.size !== declared.size ||
|
|
106
71
|
[...declared].some((table) => !found.has(table))) {
|
|
107
72
|
throw new Error('registered query table metadata does not match its SQL');
|
|
108
73
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
74
|
+
}
|
|
75
|
+
/** Bind every compiler-proven table occurrence to the authenticated partition. */
|
|
76
|
+
export function prepareAuthoritativeQuery(plan, params, declaredTables, tables) {
|
|
77
|
+
validateAuthoritativeRelationPlan(plan, declaredTables);
|
|
78
|
+
const sql = plan.sql;
|
|
79
|
+
const replacements = plan.relations.map((relation) => {
|
|
80
|
+
const table = tables.get(relation.table);
|
|
81
|
+
if (table === undefined)
|
|
82
|
+
throw new Error('registered query targets an unknown table');
|
|
83
|
+
if (!table.materialize)
|
|
84
|
+
throw new Error('registered query targets a non-materialized table');
|
|
85
|
+
return {
|
|
86
|
+
start: relation.start,
|
|
87
|
+
end: relation.end,
|
|
88
|
+
text: `(SELECT ${table.columns.map((column) => quoteIdent(column.name)).join(', ')} FROM ${quoteIdent(table.name)} WHERE ${quoteIdent(SYNC_PARTITION_COLUMN)}=?)${relation.alias === undefined ? ` AS ${quoteIdent(table.name)}` : ''}`,
|
|
89
|
+
};
|
|
90
|
+
});
|
|
116
91
|
const bound = [];
|
|
117
92
|
let anonymousIndex = 0;
|
|
118
93
|
let rendered = '';
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
94
|
+
let nextRelation = 0;
|
|
95
|
+
for (let index = 0; index < sql.length; index += 1) {
|
|
96
|
+
const replacement = replacements[nextRelation];
|
|
97
|
+
if (replacement?.start === index) {
|
|
98
|
+
rendered += replacement.text;
|
|
122
99
|
bound.push(PARTITION_BIND);
|
|
123
|
-
index
|
|
100
|
+
index = replacement.end - 1;
|
|
101
|
+
nextRelation += 1;
|
|
124
102
|
continue;
|
|
125
103
|
}
|
|
126
|
-
const protectedEnd = protectedSqlEnd(
|
|
104
|
+
const protectedEnd = protectedSqlEnd(sql, index);
|
|
127
105
|
if (protectedEnd !== undefined) {
|
|
128
|
-
rendered +=
|
|
106
|
+
rendered += sql.slice(index, protectedEnd);
|
|
129
107
|
index = protectedEnd - 1;
|
|
130
108
|
continue;
|
|
131
109
|
}
|
|
132
|
-
const char =
|
|
110
|
+
const char = sql[index];
|
|
133
111
|
if (char !== '?') {
|
|
134
112
|
rendered += char;
|
|
135
113
|
continue;
|
|
136
114
|
}
|
|
137
115
|
let end = index + 1;
|
|
138
|
-
while (end <
|
|
116
|
+
while (end < sql.length && /[0-9]/.test(sql[end])) {
|
|
139
117
|
end += 1;
|
|
140
118
|
}
|
|
141
|
-
const numbered =
|
|
119
|
+
const numbered = sql.slice(index + 1, end);
|
|
142
120
|
const parameterIndex = numbered.length > 0
|
|
143
121
|
? Number.parseInt(numbered, 10) - 1
|
|
144
122
|
: anonymousIndex++;
|
package/dist/d1-storage.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CommitPruneQuery, CommitPruneResult } from './storage.js';
|
|
1
2
|
import type { CompiledSchema, CompiledTable } from './schema.js';
|
|
2
3
|
import type { AuthoritativeQueryRequest, AuthoritativeQueryResult, ClientCursorInfo, ClientRecord, CommitMetadata, CommitMetadataQuery, CommitWindowQuery, IndexRowScanQuery, PartitionRegistryEntry, PrunedReactionCounts, ReactionClaimQuery, ReactionFailureUpdate, ReactionListQuery, ReactionPruneQuery, RowScanQuery, ScopeActivityQuery, ScopeCommitActivity, ServerStorage, StorageTransaction, StoredCommit, StoredPushResult, StoredReaction, StoredRow } from './storage.js';
|
|
3
4
|
export interface D1PreparedStatement {
|
|
@@ -36,15 +37,23 @@ export declare class D1ServerStorage implements ServerStorage {
|
|
|
36
37
|
/** Resolve a table's compiled schema; row operations require `ensureSchema`. */
|
|
37
38
|
table(name: string): CompiledTable;
|
|
38
39
|
ensureSchema(schema: CompiledSchema): Promise<void>;
|
|
40
|
+
/** Run once per Worker invocation. Resume an incomplete result in another invocation. */
|
|
41
|
+
migrateSchema(schema: CompiledSchema, options?: {
|
|
42
|
+
readonly maxStatements?: number;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
readonly complete: boolean;
|
|
45
|
+
readonly statementsExecuted: number;
|
|
46
|
+
}>;
|
|
39
47
|
touchPartition(partition: string, authenticatedAtMs: number, initialLogEpoch: string): Promise<PartitionRegistryEntry>;
|
|
40
48
|
rotatePartitionLogEpoch(partition: string, logEpoch: string, authenticatedAtMs: number): Promise<PartitionRegistryEntry>;
|
|
41
49
|
listPartitionRegistry(): Promise<PartitionRegistryEntry[]>;
|
|
42
50
|
begin(partition: string): Promise<StorageTransaction>;
|
|
43
51
|
getMaxCommitSeq(partition: string): Promise<number>;
|
|
44
52
|
queryAuthoritative(partition: string, query: AuthoritativeQueryRequest): Promise<AuthoritativeQueryResult>;
|
|
53
|
+
getPartitionLogEpoch(partition: string): Promise<string | undefined>;
|
|
45
54
|
getHorizonSeq(partition: string): Promise<number>;
|
|
46
55
|
setHorizonSeq(partition: string, seq: number): Promise<void>;
|
|
47
|
-
pruneCommitsThrough(partition: string,
|
|
56
|
+
pruneCommitsThrough(partition: string, query: CommitPruneQuery): Promise<CommitPruneResult>;
|
|
48
57
|
getCommitSeqBefore(partition: string, createdBeforeMs: number): Promise<number>;
|
|
49
58
|
getRow(partition: string, table: string, rowId: string): Promise<StoredRow | undefined>;
|
|
50
59
|
getPushResult(partition: string, clientId: string, clientCommitId: string): Promise<StoredPushResult | undefined>;
|
|
@@ -61,6 +70,9 @@ export declare class D1ServerStorage implements ServerStorage {
|
|
|
61
70
|
scanRowsByIndex(partition: string, query: IndexRowScanQuery): Promise<StoredRow[]>;
|
|
62
71
|
getClientRecord(partition: string, clientId: string): Promise<ClientRecord | undefined>;
|
|
63
72
|
putClientRecord(partition: string, record: ClientRecord): Promise<void>;
|
|
73
|
+
advanceClientCursor(partition: string, clientId: string, actorId: string, logEpoch: string, cursor: number, updatedAtMs: number): Promise<void>;
|
|
74
|
+
updateClientCursor(partition: string, clientId: string, cursor: number, updatedAtMs: number): Promise<void>;
|
|
75
|
+
getActiveClientCursorFloor(partition: string, cutoffMs: number): Promise<number | null>;
|
|
64
76
|
listClientCursors(partition: string): Promise<ClientCursorInfo[]>;
|
|
65
77
|
listRowsReferencingBlob(partition: string, blobId: string): Promise<{
|
|
66
78
|
readonly table: string;
|