@syncular/server 0.15.23 → 0.15.25
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 +18 -20
- package/dist/d1-storage.d.ts +9 -3
- package/dist/d1-storage.js +27 -21
- package/dist/postgres-storage.js +9 -9
- package/dist/push.js +43 -61
- package/dist/realtime.d.ts +14 -30
- package/dist/realtime.js +28 -5
- package/dist/sqlite-storage.js +39 -13
- package/dist/storage.d.ts +14 -6
- package/package.json +2 -2
- package/src/d1-storage.ts +36 -25
- package/src/postgres-storage.ts +9 -15
- package/src/push.ts +70 -96
- package/src/realtime.ts +39 -39
- package/src/sqlite-storage.ts +40 -19
- package/src/storage.ts +14 -6
package/src/sqlite-storage.ts
CHANGED
|
@@ -69,11 +69,17 @@ class SqliteTransaction implements StorageTransaction {
|
|
|
69
69
|
#storage: SqliteServerStorage;
|
|
70
70
|
#partition: string;
|
|
71
71
|
#open = true;
|
|
72
|
-
#
|
|
72
|
+
#pushApplySavepoint = false;
|
|
73
|
+
readonly #release: () => void;
|
|
73
74
|
|
|
74
|
-
constructor(
|
|
75
|
+
constructor(
|
|
76
|
+
storage: SqliteServerStorage,
|
|
77
|
+
partition: string,
|
|
78
|
+
release: () => void,
|
|
79
|
+
) {
|
|
75
80
|
this.#storage = storage;
|
|
76
81
|
this.#partition = partition;
|
|
82
|
+
this.#release = release;
|
|
77
83
|
storage.db.exec('BEGIN IMMEDIATE');
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -96,11 +102,11 @@ class SqliteTransaction implements StorageTransaction {
|
|
|
96
102
|
return this.#storage.scanRowsByIndex(this.#partition, query);
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
async
|
|
105
|
+
async lockPartitionForPush(): Promise<void> {
|
|
100
106
|
this.#assertOpen();
|
|
101
107
|
// BEGIN IMMEDIATE in the constructor already owns SQLite's writer lock.
|
|
102
|
-
this.#storage.db.exec('SAVEPOINT
|
|
103
|
-
this.#
|
|
108
|
+
this.#storage.db.exec('SAVEPOINT syncular_push_candidate');
|
|
109
|
+
this.#pushApplySavepoint = true;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
async commitRejectedPushResult(
|
|
@@ -109,18 +115,12 @@ class SqliteTransaction implements StorageTransaction {
|
|
|
109
115
|
result: StoredPushResult,
|
|
110
116
|
): Promise<void> {
|
|
111
117
|
this.#assertOpen();
|
|
112
|
-
if (!this.#
|
|
113
|
-
throw new Error(
|
|
114
|
-
'whole-commit rejection requires its validation savepoint',
|
|
115
|
-
);
|
|
118
|
+
if (!this.#pushApplySavepoint) {
|
|
119
|
+
throw new Error('push rejection requires its apply savepoint');
|
|
116
120
|
}
|
|
117
|
-
this.#storage.db.exec(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.#storage.db.exec(
|
|
121
|
-
'RELEASE SAVEPOINT syncular_commit_validation_candidate',
|
|
122
|
-
);
|
|
123
|
-
this.#commitValidationSavepoint = false;
|
|
121
|
+
this.#storage.db.exec('ROLLBACK TO SAVEPOINT syncular_push_candidate');
|
|
122
|
+
this.#storage.db.exec('RELEASE SAVEPOINT syncular_push_candidate');
|
|
123
|
+
this.#pushApplySavepoint = false;
|
|
124
124
|
await this.putPushResult(clientId, clientCommitId, result);
|
|
125
125
|
await this.commit();
|
|
126
126
|
}
|
|
@@ -245,18 +245,28 @@ class SqliteTransaction implements StorageTransaction {
|
|
|
245
245
|
async commit(): Promise<void> {
|
|
246
246
|
this.#assertOpen();
|
|
247
247
|
this.#open = false;
|
|
248
|
-
|
|
248
|
+
try {
|
|
249
|
+
this.#storage.db.exec('COMMIT');
|
|
250
|
+
} finally {
|
|
251
|
+
this.#release();
|
|
252
|
+
}
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
async rollback(): Promise<void> {
|
|
252
256
|
if (!this.#open) return;
|
|
253
257
|
this.#open = false;
|
|
254
|
-
|
|
258
|
+
try {
|
|
259
|
+
this.#storage.db.exec('ROLLBACK');
|
|
260
|
+
} finally {
|
|
261
|
+
this.#release();
|
|
262
|
+
}
|
|
255
263
|
}
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
export class SqliteServerStorage implements ServerStorage {
|
|
259
267
|
readonly db: Database;
|
|
268
|
+
/** One bun:sqlite connection can own only one transaction at a time. */
|
|
269
|
+
#transactionTail: Promise<void> = Promise.resolve();
|
|
260
270
|
/** Set by `ensureSchema`: app-table lookup for the relational row store. */
|
|
261
271
|
#tables: ReadonlyMap<string, CompiledTable> | undefined;
|
|
262
272
|
#schemaVersion: number | undefined;
|
|
@@ -405,7 +415,18 @@ export class SqliteServerStorage implements ServerStorage {
|
|
|
405
415
|
}
|
|
406
416
|
|
|
407
417
|
async begin(partition: string): Promise<StorageTransaction> {
|
|
408
|
-
|
|
418
|
+
const previous = this.#transactionTail;
|
|
419
|
+
let release!: () => void;
|
|
420
|
+
this.#transactionTail = new Promise<void>((resolve) => {
|
|
421
|
+
release = resolve;
|
|
422
|
+
});
|
|
423
|
+
await previous;
|
|
424
|
+
try {
|
|
425
|
+
return new SqliteTransaction(this, partition, release);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
release();
|
|
428
|
+
throw error;
|
|
429
|
+
}
|
|
409
430
|
}
|
|
410
431
|
|
|
411
432
|
/** Internal: write a row + refresh its scope-index entries. */
|
package/src/storage.ts
CHANGED
|
@@ -203,15 +203,23 @@ export interface StorageTransaction {
|
|
|
203
203
|
*/
|
|
204
204
|
scanRowsByIndex?(query: IndexRowScanQuery): Promise<StoredRow[]>;
|
|
205
205
|
/**
|
|
206
|
-
* Serialize
|
|
207
|
-
*
|
|
206
|
+
* Serialize every push apply for this partition before any operation read,
|
|
207
|
+
* validation, merge, or write. The push layer re-checks idempotency only
|
|
208
|
+
* after this resolves and retains the lock through terminal-result commit.
|
|
209
|
+
* Missing support fails closed before an app-row mutation.
|
|
210
|
+
*/
|
|
211
|
+
lockPartitionForPush?(): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* @deprecated Implement `lockPartitionForPush`. Kept as a compatibility
|
|
214
|
+
* bridge for custom adapters whose existing implementation already locks
|
|
215
|
+
* the complete partition from before candidate reads through commit.
|
|
208
216
|
*/
|
|
209
217
|
lockPartitionForCommitValidation?(): Promise<void>;
|
|
210
218
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
219
|
+
* Rejection finalization while the push-apply serialization lock is still
|
|
220
|
+
* held: discard every candidate write, persist the rejected idempotency
|
|
221
|
+
* result, and finish the transaction atomically. Required for every push so
|
|
222
|
+
* a concurrent duplicate cannot rerun operations, validators, or merges.
|
|
215
223
|
*/
|
|
216
224
|
commitRejectedPushResult?(
|
|
217
225
|
clientId: string,
|