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 +157 -90
- package/dist/types/core/Dataply.d.ts +10 -5
- package/dist/types/core/DataplyAPI.d.ts +4 -13
- package/dist/types/core/transaction/GlobalTransaction.d.ts +12 -3
- package/dist/types/core/transaction/Transaction.d.ts +1 -7
- package/package.json +3 -3
- package/readme.md +20 -43
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5149
|
+
const { rootId } = this._readCtx();
|
|
5150
|
+
return getOp(this._createReadOps(), rootId, key);
|
|
5134
5151
|
}
|
|
5135
5152
|
exists(key, value) {
|
|
5136
|
-
|
|
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.
|
|
5141
|
-
|
|
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.
|
|
5153
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
5379
|
+
async getRootId() {
|
|
5380
|
+
return (await this._readCtx()).rootId;
|
|
5341
5381
|
}
|
|
5342
|
-
getOrder() {
|
|
5343
|
-
return this.
|
|
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
|
-
|
|
5395
|
+
const { rootId } = await this._readCtx();
|
|
5396
|
+
return getOpAsync(this._createReadOps(), rootId, key);
|
|
5356
5397
|
}
|
|
5357
5398
|
async exists(key, value) {
|
|
5358
|
-
|
|
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.
|
|
5366
|
-
|
|
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.
|
|
5384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11078
|
+
await this.mvccTx.write(pageId, copy);
|
|
11043
11079
|
} else {
|
|
11044
|
-
await
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 *
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
|
|
11704
|
-
return this.api.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
#### `
|
|
200
|
-
|
|
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
|
|
203
|
-
|
|
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
|
|
208
|
-
|
|
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
|
|
211
|
-
|
|
193
|
+
#### `async close(): Promise<void>`
|
|
194
|
+
Closes the file handles and shuts down safely.
|
|
212
195
|
|
|
213
196
|
### GlobalTransaction Class
|
|
214
197
|
|
|
215
|
-
#### `
|
|
216
|
-
|
|
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
|
|