dataply 0.0.26-alpha.7 → 0.0.26-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -5002,8 +5002,6 @@ var BPTreePureSync = class {
5002
5002
  comparator;
5003
5003
  option;
5004
5004
  _cachedRegexp = /* @__PURE__ */ new Map();
5005
- _ctx;
5006
- _ops;
5007
5005
  _verifierMap;
5008
5006
  _searchConfigs;
5009
5007
  constructor(strategy, comparator, option) {
@@ -5017,7 +5015,7 @@ var BPTreePureSync = class {
5017
5015
  _ensureValues(v) {
5018
5016
  return Array.isArray(v) ? v : [v];
5019
5017
  }
5020
- _createOps() {
5018
+ _createReadOps() {
5021
5019
  const strategy = this.strategy;
5022
5020
  return {
5023
5021
  getNode(id) {
@@ -5084,16 +5082,35 @@ var BPTreePureSync = class {
5084
5082
  }
5085
5083
  return { ops, flush };
5086
5084
  }
5085
+ _readCtx() {
5086
+ const head = this.strategy.readHead();
5087
+ if (head === null) {
5088
+ throw new Error("Tree not initialized. Call init() first.");
5089
+ }
5090
+ return { rootId: head.root, order: head.order };
5091
+ }
5092
+ _createCtx() {
5093
+ const strategy = this.strategy;
5094
+ const head = strategy.readHead();
5095
+ if (head === null) {
5096
+ throw new Error("Tree not initialized. Call init() first.");
5097
+ }
5098
+ return {
5099
+ rootId: head.root,
5100
+ order: head.order,
5101
+ headData: () => strategy.head.data
5102
+ };
5103
+ }
5087
5104
  init() {
5088
5105
  const { ops, flush } = this._createBufferedOps();
5089
- this._ctx = {
5106
+ const ctx = {
5090
5107
  rootId: "",
5091
5108
  order: this.strategy.order,
5092
5109
  headData: () => this.strategy.head.data
5093
5110
  };
5094
5111
  initOp(
5095
5112
  ops,
5096
- this._ctx,
5113
+ ctx,
5097
5114
  this.strategy.order,
5098
5115
  this.strategy.head,
5099
5116
  (head) => {
@@ -5101,19 +5118,18 @@ var BPTreePureSync = class {
5101
5118
  }
5102
5119
  );
5103
5120
  flush();
5104
- this._ops = this._createOps();
5105
5121
  }
5106
5122
  /**
5107
5123
  * Returns the ID of the root node.
5108
5124
  */
5109
5125
  getRootId() {
5110
- return this._ctx.rootId;
5126
+ return this._readCtx().rootId;
5111
5127
  }
5112
5128
  /**
5113
5129
  * Returns the order of the B+Tree.
5114
5130
  */
5115
5131
  getOrder() {
5116
- return this._ctx.order;
5132
+ return this._readCtx().order;
5117
5133
  }
5118
5134
  /**
5119
5135
  * Verified if the value satisfies the condition.
@@ -5130,15 +5146,18 @@ var BPTreePureSync = class {
5130
5146
  }
5131
5147
  // ─── Query ───────────────────────────────────────────────────────
5132
5148
  get(key) {
5133
- return getOp(this._ops, this._ctx.rootId, key);
5149
+ const { rootId } = this._readCtx();
5150
+ return getOp(this._createReadOps(), rootId, key);
5134
5151
  }
5135
5152
  exists(key, value) {
5136
- return existsOp(this._ops, this._ctx.rootId, key, value, this.comparator);
5153
+ const { rootId } = this._readCtx();
5154
+ return existsOp(this._createReadOps(), rootId, key, value, this.comparator);
5137
5155
  }
5138
5156
  *keysStream(condition, options) {
5157
+ const { rootId } = this._readCtx();
5139
5158
  yield* keysStreamOp(
5140
- this._ops,
5141
- this._ctx.rootId,
5159
+ this._createReadOps(),
5160
+ rootId,
5142
5161
  condition,
5143
5162
  this.comparator,
5144
5163
  this._verifierMap,
@@ -5148,9 +5167,10 @@ var BPTreePureSync = class {
5148
5167
  );
5149
5168
  }
5150
5169
  *whereStream(condition, options) {
5170
+ const { rootId } = this._readCtx();
5151
5171
  yield* whereStreamOp(
5152
- this._ops,
5153
- this._ctx.rootId,
5172
+ this._createReadOps(),
5173
+ rootId,
5154
5174
  condition,
5155
5175
  this.comparator,
5156
5176
  this._verifierMap,
@@ -5176,27 +5196,31 @@ var BPTreePureSync = class {
5176
5196
  // ─── Mutation ────────────────────────────────────────────────────
5177
5197
  insert(key, value) {
5178
5198
  const { ops, flush } = this._createBufferedOps();
5179
- insertOp(ops, this._ctx, key, value, this.comparator);
5199
+ const ctx = this._createCtx();
5200
+ insertOp(ops, ctx, key, value, this.comparator);
5180
5201
  flush();
5181
5202
  }
5182
5203
  delete(key, value) {
5183
5204
  const { ops, flush } = this._createBufferedOps();
5184
- deleteOp(ops, this._ctx, key, this.comparator, value);
5205
+ const ctx = this._createCtx();
5206
+ deleteOp(ops, ctx, key, this.comparator, value);
5185
5207
  flush();
5186
5208
  }
5187
5209
  batchInsert(entries) {
5188
5210
  const { ops, flush } = this._createBufferedOps();
5189
- batchInsertOp(ops, this._ctx, entries, this.comparator);
5211
+ const ctx = this._createCtx();
5212
+ batchInsertOp(ops, ctx, entries, this.comparator);
5190
5213
  flush();
5191
5214
  }
5192
5215
  bulkLoad(entries) {
5193
5216
  const { ops, flush } = this._createBufferedOps();
5194
- bulkLoadOp(ops, this._ctx, entries, this.comparator);
5217
+ const ctx = this._createCtx();
5218
+ bulkLoadOp(ops, ctx, entries, this.comparator);
5195
5219
  flush();
5196
5220
  }
5197
5221
  // ─── Head Data ───────────────────────────────────────────────────
5198
5222
  getHeadData() {
5199
- const head = this._ops.readHead();
5223
+ const head = this.strategy.readHead();
5200
5224
  if (head === null) {
5201
5225
  throw new Error("Head not found");
5202
5226
  }
@@ -5224,8 +5248,6 @@ var BPTreePureAsync = class {
5224
5248
  option;
5225
5249
  lock = new Ryoiki2();
5226
5250
  _cachedRegexp = /* @__PURE__ */ new Map();
5227
- _ctx;
5228
- _ops;
5229
5251
  _verifierMap;
5230
5252
  _searchConfigs;
5231
5253
  constructor(strategy, comparator, option) {
@@ -5239,7 +5261,7 @@ var BPTreePureAsync = class {
5239
5261
  _ensureValues(v) {
5240
5262
  return Array.isArray(v) ? v : [v];
5241
5263
  }
5242
- _createOps() {
5264
+ _createReadOps() {
5243
5265
  const strategy = this.strategy;
5244
5266
  return {
5245
5267
  async getNode(id) {
@@ -5306,6 +5328,25 @@ var BPTreePureAsync = class {
5306
5328
  }
5307
5329
  return { ops, flush };
5308
5330
  }
5331
+ async _readCtx() {
5332
+ const head = await this.strategy.readHead();
5333
+ if (head === null) {
5334
+ throw new Error("Tree not initialized. Call init() first.");
5335
+ }
5336
+ return { rootId: head.root, order: head.order };
5337
+ }
5338
+ async _createCtx() {
5339
+ const strategy = this.strategy;
5340
+ const head = await strategy.readHead();
5341
+ if (head === null) {
5342
+ throw new Error("Tree not initialized. Call init() first.");
5343
+ }
5344
+ return {
5345
+ rootId: head.root,
5346
+ order: head.order,
5347
+ headData: () => strategy.head.data
5348
+ };
5349
+ }
5309
5350
  async writeLock(fn) {
5310
5351
  let lockId;
5311
5352
  return this.lock.writeLock(async (_lockId) => {
@@ -5318,14 +5359,14 @@ var BPTreePureAsync = class {
5318
5359
  async init() {
5319
5360
  return this.writeLock(async () => {
5320
5361
  const { ops, flush } = this._createBufferedOps();
5321
- this._ctx = {
5362
+ const ctx = {
5322
5363
  rootId: "",
5323
5364
  order: this.strategy.order,
5324
5365
  headData: () => this.strategy.head.data
5325
5366
  };
5326
5367
  await initOpAsync(
5327
5368
  ops,
5328
- this._ctx,
5369
+ ctx,
5329
5370
  this.strategy.order,
5330
5371
  this.strategy.head,
5331
5372
  (head) => {
@@ -5333,14 +5374,13 @@ var BPTreePureAsync = class {
5333
5374
  }
5334
5375
  );
5335
5376
  await flush();
5336
- this._ops = this._createOps();
5337
5377
  });
5338
5378
  }
5339
- getRootId() {
5340
- return this._ctx.rootId;
5379
+ async getRootId() {
5380
+ return (await this._readCtx()).rootId;
5341
5381
  }
5342
- getOrder() {
5343
- return this._ctx.order;
5382
+ async getOrder() {
5383
+ return (await this._readCtx()).order;
5344
5384
  }
5345
5385
  verify(nodeValue, condition) {
5346
5386
  for (const key in condition) {
@@ -5352,18 +5392,21 @@ var BPTreePureAsync = class {
5352
5392
  }
5353
5393
  // ─── Query ───────────────────────────────────────────────────────
5354
5394
  async get(key) {
5355
- return getOpAsync(this._ops, this._ctx.rootId, key);
5395
+ const { rootId } = await this._readCtx();
5396
+ return getOpAsync(this._createReadOps(), rootId, key);
5356
5397
  }
5357
5398
  async exists(key, value) {
5358
- return existsOpAsync(this._ops, this._ctx.rootId, key, value, this.comparator);
5399
+ const { rootId } = await this._readCtx();
5400
+ return existsOpAsync(this._createReadOps(), rootId, key, value, this.comparator);
5359
5401
  }
5360
5402
  async *keysStream(condition, options) {
5361
5403
  let lockId;
5362
5404
  try {
5363
5405
  lockId = await this.lock.readLock([0, 0.1], async (id) => id);
5406
+ const { rootId } = await this._readCtx();
5364
5407
  yield* keysStreamOpAsync(
5365
- this._ops,
5366
- this._ctx.rootId,
5408
+ this._createReadOps(),
5409
+ rootId,
5367
5410
  condition,
5368
5411
  this.comparator,
5369
5412
  this._verifierMap,
@@ -5379,9 +5422,10 @@ var BPTreePureAsync = class {
5379
5422
  let lockId;
5380
5423
  try {
5381
5424
  lockId = await this.lock.readLock([0, 0.1], async (id) => id);
5425
+ const { rootId } = await this._readCtx();
5382
5426
  yield* whereStreamOpAsync(
5383
- this._ops,
5384
- this._ctx.rootId,
5427
+ this._createReadOps(),
5428
+ rootId,
5385
5429
  condition,
5386
5430
  this.comparator,
5387
5431
  this._verifierMap,
@@ -5411,34 +5455,38 @@ var BPTreePureAsync = class {
5411
5455
  async insert(key, value) {
5412
5456
  return this.writeLock(async () => {
5413
5457
  const { ops, flush } = this._createBufferedOps();
5414
- await insertOpAsync(ops, this._ctx, key, value, this.comparator);
5458
+ const ctx = await this._createCtx();
5459
+ await insertOpAsync(ops, ctx, key, value, this.comparator);
5415
5460
  await flush();
5416
5461
  });
5417
5462
  }
5418
5463
  async delete(key, value) {
5419
5464
  return this.writeLock(async () => {
5420
5465
  const { ops, flush } = this._createBufferedOps();
5421
- await deleteOpAsync(ops, this._ctx, key, this.comparator, value);
5466
+ const ctx = await this._createCtx();
5467
+ await deleteOpAsync(ops, ctx, key, this.comparator, value);
5422
5468
  await flush();
5423
5469
  });
5424
5470
  }
5425
5471
  async batchInsert(entries) {
5426
5472
  return this.writeLock(async () => {
5427
5473
  const { ops, flush } = this._createBufferedOps();
5428
- await batchInsertOpAsync(ops, this._ctx, entries, this.comparator);
5474
+ const ctx = await this._createCtx();
5475
+ await batchInsertOpAsync(ops, ctx, entries, this.comparator);
5429
5476
  await flush();
5430
5477
  });
5431
5478
  }
5432
5479
  async bulkLoad(entries) {
5433
5480
  return this.writeLock(async () => {
5434
5481
  const { ops, flush } = this._createBufferedOps();
5435
- await bulkLoadOpAsync(ops, this._ctx, entries, this.comparator);
5482
+ const ctx = await this._createCtx();
5483
+ await bulkLoadOpAsync(ops, ctx, entries, this.comparator);
5436
5484
  await flush();
5437
5485
  });
5438
5486
  }
5439
5487
  // ─── Head Data ───────────────────────────────────────────────────
5440
5488
  async getHeadData() {
5441
- const head = await this._ops.readHead();
5489
+ const head = await this.strategy.readHead();
5442
5490
  if (head === null) throw new Error("Head not found");
5443
5491
  return head.data;
5444
5492
  }
@@ -10967,6 +11015,7 @@ var Transaction = class {
10967
11015
  this.pfs = pfs;
10968
11016
  this.id = id;
10969
11017
  this.rootTx = rootTx;
11018
+ this.mvccTx = rootTx.createNested();
10970
11019
  }
10971
11020
  /** Transaction ID */
10972
11021
  id;
@@ -10977,22 +11026,11 @@ var Transaction = class {
10977
11026
  /** List of callbacks to execute on commit */
10978
11027
  commitHooks = [];
10979
11028
  /** Nested MVCC Transaction for snapshot isolation (lazy init) */
10980
- mvccTx = null;
11029
+ mvccTx;
10981
11030
  /** Root MVCC Transaction reference */
10982
11031
  rootTx;
10983
11032
  /** Release function for global write lock, set by DataplyAPI */
10984
11033
  _writeLockRelease = null;
10985
- /**
10986
- * Lazily initializes the nested MVCC transaction.
10987
- * This ensures the snapshot is taken at the time of first access,
10988
- * picking up the latest committed root version.
10989
- */
10990
- ensureMvccTx() {
10991
- if (!this.mvccTx) {
10992
- this.mvccTx = this.rootTx.createNested();
10993
- }
10994
- return this.mvccTx;
10995
- }
10996
11034
  /**
10997
11035
  * Registers a commit hook.
10998
11036
  * @param hook Function to execute
@@ -11019,8 +11057,7 @@ var Transaction = class {
11019
11057
  * @returns Page data
11020
11058
  */
11021
11059
  async __readPage(pageId) {
11022
- const tx = this.ensureMvccTx();
11023
- const data = await tx.read(pageId);
11060
+ const data = await this.mvccTx.read(pageId);
11024
11061
  if (data === null) {
11025
11062
  return new Uint8Array(this.pfs.pageSize);
11026
11063
  }
@@ -11034,14 +11071,13 @@ var Transaction = class {
11034
11071
  * @param data Page data
11035
11072
  */
11036
11073
  async __writePage(pageId, data) {
11037
- const tx = this.ensureMvccTx();
11038
- const exists = await tx.exists(pageId);
11074
+ const exists = await this.mvccTx.exists(pageId);
11039
11075
  if (exists) {
11040
11076
  const copy = new Uint8Array(data.length);
11041
11077
  copy.set(data);
11042
- await tx.write(pageId, copy);
11078
+ await this.mvccTx.write(pageId, copy);
11043
11079
  } else {
11044
- await tx.create(pageId, data);
11080
+ await this.mvccTx.create(pageId, data);
11045
11081
  }
11046
11082
  }
11047
11083
  /**
@@ -11069,10 +11105,9 @@ var Transaction = class {
11069
11105
  await hook();
11070
11106
  }
11071
11107
  });
11072
- const tx = this.ensureMvccTx();
11073
11108
  let shouldTriggerCheckpoint = false;
11074
11109
  await this.pfs.runGlobalLock(async () => {
11075
- const entries = tx.getResultEntries();
11110
+ const entries = this.mvccTx.getResultEntries();
11076
11111
  const dirtyPages = /* @__PURE__ */ new Map();
11077
11112
  for (const entry of [...entries.created, ...entries.updated]) {
11078
11113
  dirtyPages.set(entry.key, entry.data);
@@ -11082,7 +11117,7 @@ var Transaction = class {
11082
11117
  await this.pfs.wal.prepareCommit(dirtyPages);
11083
11118
  await this.pfs.wal.writeCommitMarker();
11084
11119
  }
11085
- await tx.commit();
11120
+ await this.mvccTx.commit();
11086
11121
  if (hasDirtyPages) {
11087
11122
  await this.rootTx.commit();
11088
11123
  }
@@ -11396,7 +11431,7 @@ var DataplyAPI = class {
11396
11431
  if (this.initialized) {
11397
11432
  return;
11398
11433
  }
11399
- await this.runWithDefault(async (tx) => {
11434
+ await this.withReadTransaction(async (tx) => {
11400
11435
  await this.hook.trigger("init", tx, async (tx2) => {
11401
11436
  await this.pfs.init();
11402
11437
  await this.rowTableEngine.init();
@@ -11421,15 +11456,6 @@ var DataplyAPI = class {
11421
11456
  this.pfs
11422
11457
  );
11423
11458
  }
11424
- /**
11425
- * Runs a callback function within a transaction context.
11426
- * If no transaction is provided, a new transaction is created.
11427
- * The transaction is committed if the callback completes successfully,
11428
- * or rolled back if an error occurs.
11429
- * @param callback The callback function to run within the transaction context.
11430
- * @param tx The transaction to use. If not provided, a new transaction is created.
11431
- * @returns The result of the callback function.
11432
- */
11433
11459
  /**
11434
11460
  * Acquires the global write lock.
11435
11461
  * Returns a release function that MUST be called to unlock.
@@ -11454,7 +11480,7 @@ var DataplyAPI = class {
11454
11480
  * @param tx Optional external transaction.
11455
11481
  * @returns The result of the callback.
11456
11482
  */
11457
- async runWithDefaultWrite(callback, tx) {
11483
+ async withWriteTransaction(callback, tx) {
11458
11484
  this.logger.debug("Running with default write transaction");
11459
11485
  if (!tx) {
11460
11486
  const release = await this.acquireWriteLock();
@@ -11478,7 +11504,7 @@ var DataplyAPI = class {
11478
11504
  }
11479
11505
  return result;
11480
11506
  }
11481
- async runWithDefault(callback, tx) {
11507
+ async withReadTransaction(callback, tx) {
11482
11508
  this.logger.debug("Running with default transaction");
11483
11509
  const isInternalTx = !tx;
11484
11510
  if (!tx) {
@@ -11506,7 +11532,7 @@ var DataplyAPI = class {
11506
11532
  * @param tx The transaction to use. If not provided, a new transaction is created.
11507
11533
  * @returns An AsyncGenerator that yields values from the callback.
11508
11534
  */
11509
- async *streamWithDefault(callback, tx) {
11535
+ async *withReadStreamTransaction(callback, tx) {
11510
11536
  this.logger.debug("Streaming with default transaction");
11511
11537
  const isInternalTx = !tx;
11512
11538
  if (!tx) {
@@ -11539,7 +11565,7 @@ var DataplyAPI = class {
11539
11565
  if (!this.initialized) {
11540
11566
  throw new Error("Dataply instance is not initialized");
11541
11567
  }
11542
- return this.runWithDefault((tx2) => this.rowTableEngine.getMetadata(tx2), tx);
11568
+ return this.withReadTransaction((tx2) => this.rowTableEngine.getMetadata(tx2), tx);
11543
11569
  }
11544
11570
  /**
11545
11571
  * Inserts data. Returns the PK of the added row.
@@ -11553,7 +11579,7 @@ var DataplyAPI = class {
11553
11579
  if (!this.initialized) {
11554
11580
  throw new Error("Dataply instance is not initialized");
11555
11581
  }
11556
- return this.runWithDefaultWrite(async (tx2) => {
11582
+ return this.withWriteTransaction(async (tx2) => {
11557
11583
  incrementRowCount = incrementRowCount ?? true;
11558
11584
  if (typeof data === "string") {
11559
11585
  data = this.textCodec.encode(data);
@@ -11574,7 +11600,7 @@ var DataplyAPI = class {
11574
11600
  if (!this.initialized) {
11575
11601
  throw new Error("Dataply instance is not initialized");
11576
11602
  }
11577
- return this.runWithDefaultWrite(async (tx2) => {
11603
+ return this.withWriteTransaction(async (tx2) => {
11578
11604
  incrementRowCount = incrementRowCount ?? true;
11579
11605
  if (typeof data === "string") {
11580
11606
  data = this.textCodec.encode(data);
@@ -11596,7 +11622,7 @@ var DataplyAPI = class {
11596
11622
  if (!this.initialized) {
11597
11623
  throw new Error("Dataply instance is not initialized");
11598
11624
  }
11599
- return this.runWithDefaultWrite(async (tx2) => {
11625
+ return this.withWriteTransaction(async (tx2) => {
11600
11626
  incrementRowCount = incrementRowCount ?? true;
11601
11627
  const encodedList = dataList.map(
11602
11628
  (data) => typeof data === "string" ? this.textCodec.encode(data) : data
@@ -11615,7 +11641,7 @@ var DataplyAPI = class {
11615
11641
  if (!this.initialized) {
11616
11642
  throw new Error("Dataply instance is not initialized");
11617
11643
  }
11618
- return this.runWithDefaultWrite(async (tx2) => {
11644
+ return this.withWriteTransaction(async (tx2) => {
11619
11645
  if (typeof data === "string") {
11620
11646
  data = this.textCodec.encode(data);
11621
11647
  }
@@ -11633,7 +11659,7 @@ var DataplyAPI = class {
11633
11659
  if (!this.initialized) {
11634
11660
  throw new Error("Dataply instance is not initialized");
11635
11661
  }
11636
- return this.runWithDefaultWrite(async (tx2) => {
11662
+ return this.withWriteTransaction(async (tx2) => {
11637
11663
  decrementRowCount = decrementRowCount ?? true;
11638
11664
  await this.rowTableEngine.delete(pk, decrementRowCount, tx2);
11639
11665
  }, tx);
@@ -11643,7 +11669,7 @@ var DataplyAPI = class {
11643
11669
  if (!this.initialized) {
11644
11670
  throw new Error("Dataply instance is not initialized");
11645
11671
  }
11646
- return this.runWithDefault(async (tx2) => {
11672
+ return this.withReadTransaction(async (tx2) => {
11647
11673
  const data = await this.rowTableEngine.selectByPK(pk, tx2);
11648
11674
  if (data === null) return null;
11649
11675
  if (asRaw) return data;
@@ -11655,7 +11681,7 @@ var DataplyAPI = class {
11655
11681
  if (!this.initialized) {
11656
11682
  throw new Error("Dataply instance is not initialized");
11657
11683
  }
11658
- return this.runWithDefault(async (tx2) => {
11684
+ return this.withReadTransaction(async (tx2) => {
11659
11685
  const results = await this.rowTableEngine.selectMany(pks, tx2);
11660
11686
  return results.map((data) => {
11661
11687
  if (data === null) return null;
@@ -11672,7 +11698,7 @@ var DataplyAPI = class {
11672
11698
  if (!this.initialized) {
11673
11699
  throw new Error("Dataply instance is not initialized");
11674
11700
  }
11675
- return this.runWithDefaultWrite(() => {
11701
+ return this.withWriteTransaction(() => {
11676
11702
  return this.hook.trigger("close", void 0, async () => {
11677
11703
  await this.pfs.close();
11678
11704
  import_node_fs3.default.closeSync(this.fileHandle);
@@ -11695,13 +11721,22 @@ var Dataply = class {
11695
11721
  return this.api.options;
11696
11722
  }
11697
11723
  /**
11698
- * Creates a transaction.
11699
- * The created transaction object can be used to add or modify data.
11700
- * A transaction must be terminated by calling either `commit` or `rollback`.
11701
- * @returns Transaction object
11724
+ * Runs a write callback within a transaction context.
11702
11725
  */
11703
- createTransaction() {
11704
- return this.api.createTransaction();
11726
+ async withWriteTransaction(callback, tx) {
11727
+ return this.api.withWriteTransaction(callback, tx);
11728
+ }
11729
+ /**
11730
+ * Runs a read callback within a transaction context.
11731
+ */
11732
+ async withReadTransaction(callback, tx) {
11733
+ return this.api.withReadTransaction(callback, tx);
11734
+ }
11735
+ /**
11736
+ * Runs a generator callback function within a transaction context.
11737
+ */
11738
+ async *withReadStreamTransaction(callback, tx) {
11739
+ return this.api.withReadStreamTransaction(callback, tx);
11705
11740
  }
11706
11741
  /**
11707
11742
  * Initializes the dataply instance.
@@ -11769,10 +11804,42 @@ var Dataply = class {
11769
11804
  };
11770
11805
 
11771
11806
  // src/core/transaction/GlobalTransaction.ts
11772
- var GlobalTransaction = class {
11807
+ var GlobalTransaction = class _GlobalTransaction {
11773
11808
  transactions = [];
11774
11809
  isCommitted = false;
11775
11810
  isRolledBack = false;
11811
+ /**
11812
+ * Executes a global transaction across multiple Dataply instances using a callback.
11813
+ * Locks are acquired in the order instances are provided.
11814
+ * @param dbs Array of Dataply instances
11815
+ * @param callback Function to execute with the array of Transactions
11816
+ */
11817
+ static async Run(dbs, callback) {
11818
+ const globalTx = new _GlobalTransaction();
11819
+ const txs = [];
11820
+ const releases = [];
11821
+ try {
11822
+ for (const db of dbs) {
11823
+ const release = await db.api.acquireWriteLock();
11824
+ releases.push(release);
11825
+ const tx = db.api.createTransaction();
11826
+ tx.__setWriteLockRelease(release);
11827
+ txs.push(tx);
11828
+ globalTx.add(tx);
11829
+ }
11830
+ const result = await callback(txs);
11831
+ await globalTx.commit();
11832
+ return result;
11833
+ } catch (e) {
11834
+ await globalTx.rollback();
11835
+ for (let i = txs.length; i < releases.length; i++) {
11836
+ releases[i]();
11837
+ }
11838
+ throw e;
11839
+ }
11840
+ }
11841
+ constructor() {
11842
+ }
11776
11843
  /**
11777
11844
  * Adds a transaction to the global transaction.
11778
11845
  * @param tx Transaction to add
@@ -13,12 +13,17 @@ export declare class Dataply {
13
13
  */
14
14
  get options(): Required<DataplyOptions>;
15
15
  /**
16
- * Creates a transaction.
17
- * The created transaction object can be used to add or modify data.
18
- * A transaction must be terminated by calling either `commit` or `rollback`.
19
- * @returns Transaction object
16
+ * Runs a write callback within a transaction context.
20
17
  */
21
- createTransaction(): Transaction;
18
+ withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
19
+ /**
20
+ * Runs a read callback within a transaction context.
21
+ */
22
+ withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
23
+ /**
24
+ * Runs a generator callback function within a transaction context.
25
+ */
26
+ withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
22
27
  /**
23
28
  * Initializes the dataply instance.
24
29
  * Must be called before using the dataply instance.
@@ -89,22 +89,13 @@ export declare class DataplyAPI {
89
89
  * @returns Transaction object
90
90
  */
91
91
  createTransaction(): Transaction;
92
- /**
93
- * Runs a callback function within a transaction context.
94
- * If no transaction is provided, a new transaction is created.
95
- * The transaction is committed if the callback completes successfully,
96
- * or rolled back if an error occurs.
97
- * @param callback The callback function to run within the transaction context.
98
- * @param tx The transaction to use. If not provided, a new transaction is created.
99
- * @returns The result of the callback function.
100
- */
101
92
  /**
102
93
  * Acquires the global write lock.
103
94
  * Returns a release function that MUST be called to unlock.
104
95
  * Used internally by runWithDefaultWrite.
105
96
  * @returns A release function
106
97
  */
107
- protected acquireWriteLock(): Promise<() => void>;
98
+ acquireWriteLock(): Promise<() => void>;
108
99
  /**
109
100
  * Runs a write callback within a transaction context with global write serialization.
110
101
  * If no transaction is provided, a new transaction is created, committed on success, rolled back on error.
@@ -114,8 +105,8 @@ export declare class DataplyAPI {
114
105
  * @param tx Optional external transaction.
115
106
  * @returns The result of the callback.
116
107
  */
117
- protected runWithDefaultWrite<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
118
- protected runWithDefault<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
108
+ withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
109
+ withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
119
110
  /**
120
111
  * Runs a generator callback function within a transaction context.
121
112
  * Similar to runWithDefault but allows yielding values from an AsyncGenerator.
@@ -126,7 +117,7 @@ export declare class DataplyAPI {
126
117
  * @param tx The transaction to use. If not provided, a new transaction is created.
127
118
  * @returns An AsyncGenerator that yields values from the callback.
128
119
  */
129
- protected streamWithDefault<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
120
+ withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
130
121
  /**
131
122
  * Retrieves metadata from the dataply.
132
123
  * @returns Metadata of the dataply.
@@ -1,4 +1,5 @@
1
1
  import { Transaction } from './Transaction';
2
+ import { Dataply } from '../Dataply';
2
3
  /**
3
4
  * Global Transaction Manager.
4
5
  * Coordinates transactions across multiple instances (shards).
@@ -9,19 +10,27 @@ export declare class GlobalTransaction {
9
10
  private transactions;
10
11
  private isCommitted;
11
12
  private isRolledBack;
13
+ /**
14
+ * Executes a global transaction across multiple Dataply instances using a callback.
15
+ * Locks are acquired in the order instances are provided.
16
+ * @param dbs Array of Dataply instances
17
+ * @param callback Function to execute with the array of Transactions
18
+ */
19
+ static Run<T>(dbs: Dataply[], callback: (txs: Transaction[]) => Promise<T>): Promise<T>;
20
+ protected constructor();
12
21
  /**
13
22
  * Adds a transaction to the global transaction.
14
23
  * @param tx Transaction to add
15
24
  */
16
- add(tx: Transaction): void;
25
+ protected add(tx: Transaction): void;
17
26
  /**
18
27
  * Commits all transactions.
19
28
  * Note: This is now a single-phase commit. For true atomicity across shards,
20
29
  * each instance's WAL provides durability, but cross-shard atomicity is best-effort.
21
30
  */
22
- commit(): Promise<void>;
31
+ protected commit(): Promise<void>;
23
32
  /**
24
33
  * Rolls back all transactions.
25
34
  */
26
- rollback(): Promise<void>;
35
+ protected rollback(): Promise<void>;
27
36
  }
@@ -21,7 +21,7 @@ export declare class Transaction {
21
21
  /** List of callbacks to execute on commit */
22
22
  private commitHooks;
23
23
  /** Nested MVCC Transaction for snapshot isolation (lazy init) */
24
- private mvccTx;
24
+ private readonly mvccTx;
25
25
  /** Root MVCC Transaction reference */
26
26
  private readonly rootTx;
27
27
  /** Release function for global write lock, set by DataplyAPI */
@@ -34,12 +34,6 @@ export declare class Transaction {
34
34
  * @param pfs Page File System
35
35
  */
36
36
  constructor(id: number, context: TransactionContext, rootTx: AsyncMVCCTransaction<PageMVCCStrategy, number, Uint8Array>, lockManager: LockManager, pfs: PageFileSystem);
37
- /**
38
- * Lazily initializes the nested MVCC transaction.
39
- * This ensures the snapshot is taken at the time of first access,
40
- * picking up the latest committed root version.
41
- */
42
- private ensureMvccTx;
43
37
  /**
44
38
  * Registers a commit hook.
45
39
  * @param hook Function to execute
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.26-alpha.7",
3
+ "version": "0.0.26-alpha.9",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -49,6 +49,6 @@
49
49
  "hookall": "^2.2.0",
50
50
  "mvcc-api": "^1.3.7",
51
51
  "ryoiki": "^1.2.0",
52
- "serializable-bptree": "^9.0.1"
52
+ "serializable-bptree": "^9.0.2"
53
53
  }
54
- }
54
+ }
package/readme.md CHANGED
@@ -105,23 +105,17 @@ db.init().then(() => {
105
105
  ## Transaction Management
106
106
 
107
107
  ### Explicit Transactions
108
- You can group multiple operations into a single unit of work to ensure atomicity.
108
+ You can group multiple operations into a single unit of work to ensure atomicity. The `withWriteTransaction` method handles the transaction lifecycle automatically, committing on success and rolling back on failure.
109
109
 
110
110
  ```typescript
111
- const tx = dataply.createTransaction()
112
-
113
- try {
114
- await dataply.insert('Data 1', tx)
111
+ await dataply.withWriteTransaction(async (tx) => {
112
+ const pk = await dataply.insert('Data 1', tx)
115
113
  await dataply.update(pk, 'Updated Data', tx)
116
-
117
- await tx.commit() // Persist changes to disk and clear WAL on success
118
- } catch (error) {
119
- await tx.rollback() // Revert all changes on failure (Undo)
120
- }
114
+ }) // Persists changes automatically on success or rolls back on failure
121
115
  ```
122
116
 
123
117
  ### Global Transactions
124
- You can perform atomic operations across multiple `Dataply` instances using the `GlobalTransaction` class. This uses a **2-Phase Commit (2PC)** mechanism to ensure that either all instances commit successfully or all are rolled back.
118
+ You can perform atomic operations across multiple `Dataply` instances using the `GlobalTransaction` class. This safely acquires write locks on all instances sequentially and manages the transaction lifecycle to ensure either all instances commit successfully or all are rolled back.
125
119
 
126
120
  ```typescript
127
121
  import { Dataply, GlobalTransaction } from 'dataply'
@@ -132,22 +126,13 @@ const db2 = new Dataply('./db2.db', { wal: './db2.wal' })
132
126
  await db1.init()
133
127
  await db2.init()
134
128
 
135
- const tx1 = db1.createTransaction()
136
- const tx2 = db2.createTransaction()
137
-
138
- const globalTx = new GlobalTransaction()
139
- globalTx.add(tx1)
140
- globalTx.add(tx2)
141
-
142
129
  try {
143
- await db1.insert('Data for DB1', tx1)
144
- await db2.insert('Data for DB2', tx2)
145
-
146
- // Commit transactions across all instances
147
- // Note: This is a best-effort atomic commit.
148
- await globalTx.commit()
130
+ await GlobalTransaction.Run([db1, db2], async ([tx1, tx2]) => {
131
+ await db1.insert('Data for DB1', tx1)
132
+ await db2.insert('Data for DB2', tx2)
133
+ })
149
134
  } catch (error) {
150
- await globalTx.rollback()
135
+ console.error('Global transaction failed and rolled back.', error)
151
136
  }
152
137
  ```
153
138
 
@@ -196,30 +181,22 @@ Marks data as deleted.
196
181
  #### `async getMetadata(tx?: Transaction): Promise<DataplyMetadata>`
197
182
  Returns the current metadata of the dataply, including `pageSize`, `pageCount`, and `rowCount`.
198
183
 
199
- #### `createTransaction(): Transaction`
200
- Creates a new transaction instance.
184
+ #### `async withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>`
185
+ Executes write operations within a serialized write-lock transaction. Automatically commits on success and rolls back on failure if creating a new internal transaction.
201
186
 
202
- #### `async close(): Promise<void>`
203
- Closes the file handles and shuts down safely.
204
-
205
- ### Transaction Class
187
+ #### `async withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>`
188
+ Executes read operations within a transaction context.
206
189
 
207
- #### `async commit(): Promise<void>`
208
- Permanently reflects all changes made during the transaction to disk and releases locks.
190
+ #### `async *withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>`
191
+ Executes streaming read operations using an async generator in a transaction.
209
192
 
210
- #### `async rollback(): Promise<void>`
211
- Cancels all changes made during the transaction and restores the original state.
193
+ #### `async close(): Promise<void>`
194
+ Closes the file handles and shuts down safely.
212
195
 
213
196
  ### GlobalTransaction Class
214
197
 
215
- #### `add(tx: Transaction): void`
216
- Registers a transaction from a Dataply instance to the global unit.
217
-
218
- #### `async commit(): Promise<void>`
219
- Executes a coordinated commit across all registered transactions. Note that without a prepare phase, this is a best-effort atomic commit.
220
-
221
- #### `async rollback(): Promise<void>`
222
- Rolls back all registered transactions simultaneously.
198
+ #### `static async Run<T>(dbs: Dataply[], callback: (txs: Transaction[]) => Promise<T>): Promise<T>`
199
+ Executes a callback-based global transaction across multiple Dataply instances sequentially locking them to prevent deadlocks, providing true atomicity within the Dataply nodes. Automatically commits on success and rolls back on failure.
223
200
 
224
201
  ## Extending Dataply
225
202