@syncular/server 0.30.6 → 0.30.7

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.
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure } from './storage-errors.js';
1
2
  function queryable(db) {
2
3
  return {
3
4
  async query(text, params) {
@@ -32,7 +33,12 @@ export function pgliteExecutor(db) {
32
33
  return result;
33
34
  }
34
35
  catch (error) {
35
- await like.exec('ROLLBACK');
36
+ try {
37
+ await like.exec('ROLLBACK');
38
+ }
39
+ catch (rollbackError) {
40
+ retainRollbackFailure(error, rollbackError);
41
+ }
36
42
  throw error;
37
43
  }
38
44
  }
package/dist/push.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure } from './storage-errors.js';
1
2
  /**
2
3
  * Push apply (SPEC.md §6) with §3.4 write-path authorization.
3
4
  *
@@ -1102,14 +1103,18 @@ export async function processPushOperationsWithTrace(ctx, schema, resolved, clie
1102
1103
  try {
1103
1104
  await tx.rollback();
1104
1105
  }
1105
- catch {
1106
- // Surface the finalization failure; a rollback failure would
1107
- // otherwise mask it.
1106
+ catch (rollbackError) {
1107
+ retainRollbackFailure(finalizationError, rollbackError);
1108
1108
  }
1109
1109
  throw finalizationError;
1110
1110
  }
1111
1111
  }
1112
- await tx.rollback();
1112
+ try {
1113
+ await tx.rollback();
1114
+ }
1115
+ catch (rollbackError) {
1116
+ retainRollbackFailure(error, rollbackError);
1117
+ }
1113
1118
  throw error;
1114
1119
  }
1115
1120
  }
@@ -1,3 +1,4 @@
1
+ import { rollbackAfterError } from './storage-errors.js';
1
2
  /**
2
3
  * SQLite-image segment generation (SPEC.md §5.3): a complete SQLite
3
4
  * database file carrying one table's whole effective-scope snapshot at
@@ -79,7 +80,8 @@ export async function writeSqliteImage(db, input) {
79
80
  db.exec('COMMIT');
80
81
  }
81
82
  catch (error) {
82
- db.exec('ROLLBACK');
83
- throw error;
83
+ rollbackAfterError(() => {
84
+ db.exec('ROLLBACK');
85
+ }, error);
84
86
  }
85
87
  }
@@ -1,3 +1,4 @@
1
+ import { rollbackAfterError } from './storage-errors.js';
1
2
  /**
2
3
  * SQLite-backed segment store over the shared synchronous driver.
3
4
  */
@@ -137,8 +138,9 @@ export class SqliteSegmentStore {
137
138
  this.db.exec('COMMIT');
138
139
  }
139
140
  catch (error) {
140
- this.db.exec('ROLLBACK');
141
- throw error;
141
+ rollbackAfterError(() => {
142
+ this.db.exec('ROLLBACK');
143
+ }, error);
142
144
  }
143
145
  return record;
144
146
  }
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure, rollbackAfterError } from './storage-errors.js';
1
2
  import { validateCommitPruneQuery } from './prune.js';
2
3
  import { StorageQueryError } from './storage-errors.js';
3
4
  import { serveGateRefusal } from './storage.js';
@@ -229,9 +230,8 @@ class SqliteTransaction {
229
230
  try {
230
231
  this.#storage.db.exec('ROLLBACK');
231
232
  }
232
- catch {
233
- // Surface the COMMIT failure; a rollback failure would otherwise
234
- // mask it.
233
+ catch (rollbackError) {
234
+ retainRollbackFailure(error, rollbackError);
235
235
  }
236
236
  throw error;
237
237
  }
@@ -433,8 +433,9 @@ END`);
433
433
  this.db.exec('COMMIT');
434
434
  }
435
435
  catch (error) {
436
- this.db.exec('ROLLBACK');
437
- throw error;
436
+ rollbackAfterError(() => {
437
+ this.db.exec('ROLLBACK');
438
+ }, error);
438
439
  }
439
440
  });
440
441
  }
@@ -486,8 +487,9 @@ END`);
486
487
  this.db.exec('COMMIT');
487
488
  }
488
489
  catch (error) {
489
- this.db.exec('ROLLBACK');
490
- throw error;
490
+ rollbackAfterError(() => {
491
+ this.db.exec('ROLLBACK');
492
+ }, error);
491
493
  }
492
494
  return {
493
495
  partition,
@@ -624,9 +626,10 @@ END`);
624
626
  return result;
625
627
  }
626
628
  catch (error) {
627
- if (open)
628
- this.db.exec('ROLLBACK');
629
- throw error;
629
+ rollbackAfterError(() => {
630
+ if (open)
631
+ this.db.exec('ROLLBACK');
632
+ }, error);
630
633
  }
631
634
  finally {
632
635
  release();
@@ -690,8 +693,9 @@ END`);
690
693
  };
691
694
  }
692
695
  catch (error) {
693
- this.db.exec('ROLLBACK');
694
- throw error;
696
+ rollbackAfterError(() => {
697
+ this.db.exec('ROLLBACK');
698
+ }, error);
695
699
  }
696
700
  });
697
701
  }
@@ -771,8 +775,9 @@ END`);
771
775
  return toStoredCheckpoint(row);
772
776
  }
773
777
  catch (error) {
774
- this.db.exec('ROLLBACK');
775
- throw error;
778
+ rollbackAfterError(() => {
779
+ this.db.exec('ROLLBACK');
780
+ }, error);
776
781
  }
777
782
  });
778
783
  }
@@ -810,8 +815,9 @@ END`);
810
815
  return Number(updated.changes) === 1 ? 'activated' : 'stale';
811
816
  }
812
817
  catch (error) {
813
- this.db.exec('ROLLBACK');
814
- throw error;
818
+ rollbackAfterError(() => {
819
+ this.db.exec('ROLLBACK');
820
+ }, error);
815
821
  }
816
822
  });
817
823
  }
@@ -31,3 +31,6 @@ export declare function isPostgresConstraintError(error: unknown): boolean;
31
31
  * the original message is copied to the public StorageConstraintError.
32
32
  */
33
33
  export declare function isD1ConstraintError(error: unknown): boolean;
34
+ /** Preserve the operation failure when transaction cleanup also fails. */
35
+ export declare function retainRollbackFailure(error: unknown, rollbackError: unknown): void;
36
+ export declare function rollbackAfterError(run: () => void, error: unknown): never;
@@ -84,3 +84,18 @@ export function isD1ConstraintError(error) {
84
84
  return (typeof message === 'string' &&
85
85
  /^(?:D1(?:_EXEC)?_ERROR:\s*)?(?:UNIQUE|NOT NULL|CHECK|FOREIGN KEY) constraint failed\b/i.test(message));
86
86
  }
87
+ /** Preserve the operation failure when transaction cleanup also fails. */
88
+ export function retainRollbackFailure(error, rollbackError) {
89
+ if (error instanceof Error && !('rollbackError' in error)) {
90
+ Object.defineProperty(error, 'rollbackError', { value: rollbackError });
91
+ }
92
+ }
93
+ export function rollbackAfterError(run, error) {
94
+ try {
95
+ run();
96
+ }
97
+ catch (rollbackError) {
98
+ retainRollbackFailure(error, rollbackError);
99
+ }
100
+ throw error;
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/server",
3
- "version": "0.30.6",
3
+ "version": "0.30.7",
4
4
  "description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -68,7 +68,7 @@
68
68
  "verify:node": "cd ../.. && bun build ./packages/server/test/sqlite-runtime/verify-node.mjs --target=node --conditions=bun --outfile=./packages/server/.verify-node.built.mjs && node ./packages/server/.verify-node.built.mjs"
69
69
  },
70
70
  "dependencies": {
71
- "@syncular/core": "0.30.6"
71
+ "@syncular/core": "0.30.7"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@electric-sql/pglite": "^0.5.4"
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure } from './storage-errors';
1
2
  /**
2
3
  * A `PgExecutor` over @electric-sql/pglite (embedded WASM Postgres).
3
4
  *
@@ -64,7 +65,11 @@ export function pgliteExecutor(db: PGlite | PgliteLike): PgExecutor {
64
65
  await like.exec('COMMIT');
65
66
  return result;
66
67
  } catch (error) {
67
- await like.exec('ROLLBACK');
68
+ try {
69
+ await like.exec('ROLLBACK');
70
+ } catch (rollbackError) {
71
+ retainRollbackFailure(error, rollbackError);
72
+ }
68
73
  throw error;
69
74
  }
70
75
  } finally {
package/src/push.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure } from './storage-errors';
1
2
  /**
2
3
  * Push apply (SPEC.md §6) with §3.4 write-path authorization.
3
4
  *
@@ -1661,14 +1662,17 @@ export async function processPushOperationsWithTrace(
1661
1662
  // a no-op when the finalization already committed.
1662
1663
  try {
1663
1664
  await tx.rollback();
1664
- } catch {
1665
- // Surface the finalization failure; a rollback failure would
1666
- // otherwise mask it.
1665
+ } catch (rollbackError) {
1666
+ retainRollbackFailure(finalizationError, rollbackError);
1667
1667
  }
1668
1668
  throw finalizationError;
1669
1669
  }
1670
1670
  }
1671
- await tx.rollback();
1671
+ try {
1672
+ await tx.rollback();
1673
+ } catch (rollbackError) {
1674
+ retainRollbackFailure(error, rollbackError);
1675
+ }
1672
1676
  throw error;
1673
1677
  }
1674
1678
  }
@@ -1,3 +1,4 @@
1
+ import { rollbackAfterError } from './storage-errors';
1
2
  /**
2
3
  * SQLite-image segment generation (SPEC.md §5.3): a complete SQLite
3
4
  * database file carrying one table's whole effective-scope snapshot at
@@ -124,7 +125,8 @@ export async function writeSqliteImage(
124
125
 
125
126
  db.exec('COMMIT');
126
127
  } catch (error) {
127
- db.exec('ROLLBACK');
128
- throw error;
128
+ rollbackAfterError(() => {
129
+ db.exec('ROLLBACK');
130
+ }, error);
129
131
  }
130
132
  }
@@ -1,3 +1,4 @@
1
+ import { rollbackAfterError } from './storage-errors';
1
2
  /**
2
3
  * SQLite-backed segment store over the shared synchronous driver.
3
4
  */
@@ -238,8 +239,9 @@ export class SqliteSegmentStore implements SegmentStore {
238
239
  }
239
240
  this.db.exec('COMMIT');
240
241
  } catch (error) {
241
- this.db.exec('ROLLBACK');
242
- throw error;
242
+ rollbackAfterError(() => {
243
+ this.db.exec('ROLLBACK');
244
+ }, error);
243
245
  }
244
246
  return record;
245
247
  }
@@ -1,3 +1,4 @@
1
+ import { retainRollbackFailure, rollbackAfterError } from './storage-errors';
1
2
  import { validateCommitPruneQuery } from './prune';
2
3
  import { StorageQueryError } from './storage-errors';
3
4
  import type { CommitPruneQuery, CommitPruneResult } from './storage';
@@ -469,9 +470,8 @@ class SqliteTransaction implements StorageTransaction {
469
470
  // connection.
470
471
  try {
471
472
  this.#storage.db.exec('ROLLBACK');
472
- } catch {
473
- // Surface the COMMIT failure; a rollback failure would otherwise
474
- // mask it.
473
+ } catch (rollbackError) {
474
+ retainRollbackFailure(error, rollbackError);
475
475
  }
476
476
  throw error;
477
477
  } finally {
@@ -725,8 +725,9 @@ END`);
725
725
  }
726
726
  this.db.exec('COMMIT');
727
727
  } catch (error) {
728
- this.db.exec('ROLLBACK');
729
- throw error;
728
+ rollbackAfterError(() => {
729
+ this.db.exec('ROLLBACK');
730
+ }, error);
730
731
  }
731
732
  });
732
733
  }
@@ -799,8 +800,9 @@ END`);
799
800
  .run(partition);
800
801
  this.db.exec('COMMIT');
801
802
  } catch (error) {
802
- this.db.exec('ROLLBACK');
803
- throw error;
803
+ rollbackAfterError(() => {
804
+ this.db.exec('ROLLBACK');
805
+ }, error);
804
806
  }
805
807
  return {
806
808
  partition,
@@ -1002,8 +1004,9 @@ END`);
1002
1004
  open = false;
1003
1005
  return result;
1004
1006
  } catch (error) {
1005
- if (open) this.db.exec('ROLLBACK');
1006
- throw error;
1007
+ rollbackAfterError(() => {
1008
+ if (open) this.db.exec('ROLLBACK');
1009
+ }, error);
1007
1010
  } finally {
1008
1011
  release();
1009
1012
  }
@@ -1085,8 +1088,9 @@ END`);
1085
1088
  removedCommits: Number(removed.changes),
1086
1089
  };
1087
1090
  } catch (error) {
1088
- this.db.exec('ROLLBACK');
1089
- throw error;
1091
+ rollbackAfterError(() => {
1092
+ this.db.exec('ROLLBACK');
1093
+ }, error);
1090
1094
  }
1091
1095
  });
1092
1096
  }
@@ -1202,8 +1206,9 @@ END`);
1202
1206
  this.db.exec('COMMIT');
1203
1207
  return toStoredCheckpoint(row);
1204
1208
  } catch (error) {
1205
- this.db.exec('ROLLBACK');
1206
- throw error;
1209
+ rollbackAfterError(() => {
1210
+ this.db.exec('ROLLBACK');
1211
+ }, error);
1207
1212
  }
1208
1213
  });
1209
1214
  }
@@ -1260,8 +1265,9 @@ END`);
1260
1265
  this.db.exec('COMMIT');
1261
1266
  return Number(updated.changes) === 1 ? 'activated' : 'stale';
1262
1267
  } catch (error) {
1263
- this.db.exec('ROLLBACK');
1264
- throw error;
1268
+ rollbackAfterError(() => {
1269
+ this.db.exec('ROLLBACK');
1270
+ }, error);
1265
1271
  }
1266
1272
  });
1267
1273
  }
@@ -150,3 +150,22 @@ export function isD1ConstraintError(error: unknown): boolean {
150
150
  )
151
151
  );
152
152
  }
153
+
154
+ /** Preserve the operation failure when transaction cleanup also fails. */
155
+ export function retainRollbackFailure(
156
+ error: unknown,
157
+ rollbackError: unknown,
158
+ ): void {
159
+ if (error instanceof Error && !('rollbackError' in error)) {
160
+ Object.defineProperty(error, 'rollbackError', { value: rollbackError });
161
+ }
162
+ }
163
+
164
+ export function rollbackAfterError(run: () => void, error: unknown): never {
165
+ try {
166
+ run();
167
+ } catch (rollbackError) {
168
+ retainRollbackFailure(error, rollbackError);
169
+ }
170
+ throw error;
171
+ }