dataply 0.0.2 → 0.0.4
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 +521 -69
- package/dist/types/core/DataplyAPI.d.ts +22 -8
- package/dist/types/core/LogManager.d.ts +6 -0
- package/dist/types/core/VirtualFileSystem.d.ts +14 -1
- package/dist/types/core/transaction/GlobalTransaction.d.ts +25 -0
- package/dist/types/core/transaction/Transaction.d.ts +5 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +3 -2
- package/readme.md +42 -1
- package/dist/types/core/RowIndexStategy.d.ts +0 -19
- package/dist/types/core/Shard.d.ts +0 -75
- package/dist/types/core/ShareAPI.d.ts +0 -121
package/dist/cjs/index.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(src_exports, {
|
|
|
36
36
|
CacheEntanglementSync: () => CacheEntanglementSync2,
|
|
37
37
|
Dataply: () => Dataply,
|
|
38
38
|
DataplyAPI: () => DataplyAPI,
|
|
39
|
+
GlobalTransaction: () => GlobalTransaction,
|
|
39
40
|
InMemoryStoreStrategyAsync: () => InMemoryStoreStrategyAsync,
|
|
40
41
|
InMemoryStoreStrategySync: () => InMemoryStoreStrategySync,
|
|
41
42
|
InvertedWeakMap: () => InvertedWeakMap,
|
|
@@ -45,6 +46,7 @@ __export(src_exports, {
|
|
|
45
46
|
SerializeStrategyAsync: () => SerializeStrategyAsync,
|
|
46
47
|
SerializeStrategySync: () => SerializeStrategySync,
|
|
47
48
|
StringComparator: () => StringComparator,
|
|
49
|
+
Transaction: () => Transaction,
|
|
48
50
|
ValueComparator: () => ValueComparator
|
|
49
51
|
});
|
|
50
52
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -2906,6 +2908,320 @@ var InvertedWeakMap = class {
|
|
|
2906
2908
|
// src/core/DataplyAPI.ts
|
|
2907
2909
|
var import_node_fs3 = __toESM(require("node:fs"));
|
|
2908
2910
|
|
|
2911
|
+
// node_modules/hookall/dist/esm/index.mjs
|
|
2912
|
+
var HookallStore = class extends WeakMap {
|
|
2913
|
+
ensure(obj, key) {
|
|
2914
|
+
if (!this.has(obj)) {
|
|
2915
|
+
const scope2 = {};
|
|
2916
|
+
this.set(obj, scope2);
|
|
2917
|
+
}
|
|
2918
|
+
const scope = this.get(obj);
|
|
2919
|
+
if (!Object.prototype.hasOwnProperty.call(scope, key)) {
|
|
2920
|
+
scope[key] = /* @__PURE__ */ new Map();
|
|
2921
|
+
}
|
|
2922
|
+
return scope[key];
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
var Hookall = class _Hookall {
|
|
2926
|
+
static Global = {};
|
|
2927
|
+
static _Store = new HookallStore();
|
|
2928
|
+
beforeHooks;
|
|
2929
|
+
afterHooks;
|
|
2930
|
+
/**
|
|
2931
|
+
* Create hook system. you can pass a target object or undefined.
|
|
2932
|
+
* If you pass a object, the hook system will be work for object locally. You're going to want this kind of usage in general.
|
|
2933
|
+
* If not specified, will be work for global. This is useful when you want to share your work with multiple files.
|
|
2934
|
+
* @param target The object to work with locally. If not specified, will be work for global.
|
|
2935
|
+
*/
|
|
2936
|
+
constructor(target) {
|
|
2937
|
+
this.beforeHooks = _Hookall._Store.ensure(target, "before");
|
|
2938
|
+
this.afterHooks = _Hookall._Store.ensure(target, "after");
|
|
2939
|
+
}
|
|
2940
|
+
_ensureCommand(hooks, command) {
|
|
2941
|
+
if (!hooks.has(command)) {
|
|
2942
|
+
hooks.set(command, []);
|
|
2943
|
+
}
|
|
2944
|
+
return hooks.get(command);
|
|
2945
|
+
}
|
|
2946
|
+
_createWrapper(command, callback, repeat) {
|
|
2947
|
+
return {
|
|
2948
|
+
callback,
|
|
2949
|
+
command,
|
|
2950
|
+
repeat
|
|
2951
|
+
};
|
|
2952
|
+
}
|
|
2953
|
+
_on(hooks, command, callback, repeat) {
|
|
2954
|
+
const wrappers = this._ensureCommand(hooks, command);
|
|
2955
|
+
const wrapper = this._createWrapper(command, callback, repeat);
|
|
2956
|
+
wrappers.unshift(wrapper);
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2959
|
+
* You register a preprocessing function, which is called before the callback function of the `trigger` method.
|
|
2960
|
+
* The value returned by this function is passed as a parameter to the `trigger` method's callback function.
|
|
2961
|
+
* If you register multiple preprocessing functions, they are executed in order, with each function receiving the value returned by the previous one as a parameter.
|
|
2962
|
+
* @param command Command to work.
|
|
2963
|
+
* @param callback Preprocessing function to register.
|
|
2964
|
+
*/
|
|
2965
|
+
onBefore(command, callback) {
|
|
2966
|
+
this._on(this.beforeHooks, command, callback, -1);
|
|
2967
|
+
return this;
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Similar to the `onBefore` method, but it only runs once.
|
|
2971
|
+
* For more details, please refer to the `onBefore` method.
|
|
2972
|
+
* @param command Command to work.
|
|
2973
|
+
* @param callback Preprocessing function to register.
|
|
2974
|
+
*/
|
|
2975
|
+
onceBefore(command, callback) {
|
|
2976
|
+
this._on(this.beforeHooks, command, callback, 1);
|
|
2977
|
+
return this;
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* You register a post-processing function which is called after the callback function of the `trigger` method finishes.
|
|
2981
|
+
* This function receives the value returned by the `trigger` method's callback function as a parameter.
|
|
2982
|
+
* If you register multiple post-processing functions, they are executed in order, with each function receiving the value returned by the previous one as a parameter.
|
|
2983
|
+
* @param command Command to work.
|
|
2984
|
+
* @param callback Post-preprocessing function to register.
|
|
2985
|
+
*/
|
|
2986
|
+
onAfter(command, callback) {
|
|
2987
|
+
this._on(this.afterHooks, command, callback, -1);
|
|
2988
|
+
return this;
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Similar to the `onAfter` method, but it only runs once.
|
|
2992
|
+
* For more details, please refer to the `onAfter` method.
|
|
2993
|
+
* @param command Command to work.
|
|
2994
|
+
* @param callback Post-preprocessing function to register.
|
|
2995
|
+
*/
|
|
2996
|
+
onceAfter(command, callback) {
|
|
2997
|
+
this._on(this.afterHooks, command, callback, 1);
|
|
2998
|
+
return this;
|
|
2999
|
+
}
|
|
3000
|
+
_off(hooks, command, callback) {
|
|
3001
|
+
const wrappers = this._ensureCommand(hooks, command);
|
|
3002
|
+
if (callback) {
|
|
3003
|
+
const i = wrappers.findIndex((wrapper) => wrapper.callback === callback);
|
|
3004
|
+
if (i !== -1) {
|
|
3005
|
+
wrappers.splice(i, 1);
|
|
3006
|
+
}
|
|
3007
|
+
} else {
|
|
3008
|
+
wrappers.length = 0;
|
|
3009
|
+
}
|
|
3010
|
+
return this;
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* You remove the preprocessing functions registered with `onBefore` or `onceBefore` methods.
|
|
3014
|
+
* If you don't specify a callback parameter, it removes all preprocessing functions registered for that command.
|
|
3015
|
+
* @param command Commands with preprocessing functions to be deleted.
|
|
3016
|
+
* @param callback Preprocessing function to be deleted.
|
|
3017
|
+
*/
|
|
3018
|
+
offBefore(command, callback) {
|
|
3019
|
+
this._off(this.beforeHooks, command, callback);
|
|
3020
|
+
return this;
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* You remove the post-preprocessing functions registered with `onAfter` or `onceAfter` methods.
|
|
3024
|
+
* If you don't specify a callback parameter, it removes all post-preprocessing functions registered for that command.
|
|
3025
|
+
* @param command Commands with post-preprocessing functions to be deleted.
|
|
3026
|
+
* @param callback post-Preprocessing function to be deleted.
|
|
3027
|
+
*/
|
|
3028
|
+
offAfter(command, callback) {
|
|
3029
|
+
this._off(this.afterHooks, command, callback);
|
|
3030
|
+
return this;
|
|
3031
|
+
}
|
|
3032
|
+
async _hookWith(hooks, command, value, ...params) {
|
|
3033
|
+
let wrappers = this._ensureCommand(hooks, command);
|
|
3034
|
+
let i = wrappers.length;
|
|
3035
|
+
while (i--) {
|
|
3036
|
+
const wrapper = wrappers[i];
|
|
3037
|
+
value = await wrapper.callback(value, ...params);
|
|
3038
|
+
wrapper.repeat -= 1;
|
|
3039
|
+
if (wrapper.repeat === 0) {
|
|
3040
|
+
this._off(hooks, command, wrapper.callback);
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return value;
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* You execute the callback function provided as a parameter. This callback function receives the 'initialValue' parameter.
|
|
3047
|
+
*
|
|
3048
|
+
* If preprocessing functions are registered, they run first, and the value returned by the preprocessing functions becomes the 'initialValue' parameter.
|
|
3049
|
+
* After the callback function finishes, post-processing functions are called.
|
|
3050
|
+
* These post-processing functions receive the value returned by the callback function as a parameter and run sequentially.
|
|
3051
|
+
*
|
|
3052
|
+
* The final value returned becomes the result of the `trigger` method.
|
|
3053
|
+
* @param command Command to work.
|
|
3054
|
+
* @param initialValue Initial value to be passed to the callback function.
|
|
3055
|
+
* @param callback The callback function to be executed.
|
|
3056
|
+
*/
|
|
3057
|
+
async trigger(command, initialValue, callback, ...params) {
|
|
3058
|
+
let value;
|
|
3059
|
+
value = await this._hookWith(this.beforeHooks, command, initialValue, ...params);
|
|
3060
|
+
value = await callback(value, ...params);
|
|
3061
|
+
value = await this._hookWith(this.afterHooks, command, value, ...params);
|
|
3062
|
+
return value;
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
function useHookall(target = Hookall.Global) {
|
|
3066
|
+
return new Hookall(target);
|
|
3067
|
+
}
|
|
3068
|
+
var HookallStore2 = class extends WeakMap {
|
|
3069
|
+
ensure(obj, key) {
|
|
3070
|
+
if (!this.has(obj)) {
|
|
3071
|
+
const scope2 = {};
|
|
3072
|
+
this.set(obj, scope2);
|
|
3073
|
+
}
|
|
3074
|
+
const scope = this.get(obj);
|
|
3075
|
+
if (!Object.prototype.hasOwnProperty.call(scope, key)) {
|
|
3076
|
+
scope[key] = /* @__PURE__ */ new Map();
|
|
3077
|
+
}
|
|
3078
|
+
return scope[key];
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
var HookallSync = class _HookallSync {
|
|
3082
|
+
static Global = {};
|
|
3083
|
+
static _Store = new HookallStore2();
|
|
3084
|
+
beforeHooks;
|
|
3085
|
+
afterHooks;
|
|
3086
|
+
/**
|
|
3087
|
+
* Create hook system. you can pass a target object or undefined.
|
|
3088
|
+
* If you pass a object, the hook system will be work for object locally. You're going to want this kind of usage in general.
|
|
3089
|
+
* If not specified, will be work for global. This is useful when you want to share your work with multiple files.
|
|
3090
|
+
* @param target The object to work with locally. If not specified, will be work for global.
|
|
3091
|
+
*/
|
|
3092
|
+
constructor(target) {
|
|
3093
|
+
this.beforeHooks = _HookallSync._Store.ensure(target, "before");
|
|
3094
|
+
this.afterHooks = _HookallSync._Store.ensure(target, "after");
|
|
3095
|
+
}
|
|
3096
|
+
_ensureCommand(hooks, command) {
|
|
3097
|
+
if (!hooks.has(command)) {
|
|
3098
|
+
hooks.set(command, []);
|
|
3099
|
+
}
|
|
3100
|
+
return hooks.get(command);
|
|
3101
|
+
}
|
|
3102
|
+
_createWrapper(command, callback, repeat) {
|
|
3103
|
+
return {
|
|
3104
|
+
callback,
|
|
3105
|
+
command,
|
|
3106
|
+
repeat
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3109
|
+
_on(hooks, command, callback, repeat) {
|
|
3110
|
+
const wrappers = this._ensureCommand(hooks, command);
|
|
3111
|
+
const wrapper = this._createWrapper(command, callback, repeat);
|
|
3112
|
+
wrappers.unshift(wrapper);
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* You register a preprocessing function, which is called before the callback function of the `trigger` method.
|
|
3116
|
+
* The value returned by this function is passed as a parameter to the `trigger` method's callback function.
|
|
3117
|
+
* If you register multiple preprocessing functions, they are executed in order, with each function receiving the value returned by the previous one as a parameter.
|
|
3118
|
+
* @param command Command to work.
|
|
3119
|
+
* @param callback Preprocessing function to register.
|
|
3120
|
+
*/
|
|
3121
|
+
onBefore(command, callback) {
|
|
3122
|
+
this._on(this.beforeHooks, command, callback, -1);
|
|
3123
|
+
return this;
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Similar to the `onBefore` method, but it only runs once.
|
|
3127
|
+
* For more details, please refer to the `onBefore` method.
|
|
3128
|
+
* @param command Command to work.
|
|
3129
|
+
* @param callback Preprocessing function to register.
|
|
3130
|
+
*/
|
|
3131
|
+
onceBefore(command, callback) {
|
|
3132
|
+
this._on(this.beforeHooks, command, callback, 1);
|
|
3133
|
+
return this;
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* You register a post-processing function which is called after the callback function of the `trigger` method finishes.
|
|
3137
|
+
* This function receives the value returned by the `trigger` method's callback function as a parameter.
|
|
3138
|
+
* If you register multiple post-processing functions, they are executed in order, with each function receiving the value returned by the previous one as a parameter.
|
|
3139
|
+
* @param command Command to work.
|
|
3140
|
+
* @param callback Post-preprocessing function to register.
|
|
3141
|
+
*/
|
|
3142
|
+
onAfter(command, callback) {
|
|
3143
|
+
this._on(this.afterHooks, command, callback, -1);
|
|
3144
|
+
return this;
|
|
3145
|
+
}
|
|
3146
|
+
/**
|
|
3147
|
+
* Similar to the `onAfter` method, but it only runs once.
|
|
3148
|
+
* For more details, please refer to the `onAfter` method.
|
|
3149
|
+
* @param command Command to work.
|
|
3150
|
+
* @param callback Post-preprocessing function to register.
|
|
3151
|
+
*/
|
|
3152
|
+
onceAfter(command, callback) {
|
|
3153
|
+
this._on(this.afterHooks, command, callback, 1);
|
|
3154
|
+
return this;
|
|
3155
|
+
}
|
|
3156
|
+
_off(hooks, command, callback) {
|
|
3157
|
+
const wrappers = this._ensureCommand(hooks, command);
|
|
3158
|
+
if (callback) {
|
|
3159
|
+
const i = wrappers.findIndex((wrapper) => wrapper.callback === callback);
|
|
3160
|
+
if (i !== -1) {
|
|
3161
|
+
wrappers.splice(i, 1);
|
|
3162
|
+
}
|
|
3163
|
+
} else {
|
|
3164
|
+
wrappers.length = 0;
|
|
3165
|
+
}
|
|
3166
|
+
return this;
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* You remove the preprocessing functions registered with `onBefore` or `onceBefore` methods.
|
|
3170
|
+
* If you don't specify a callback parameter, it removes all preprocessing functions registered for that command.
|
|
3171
|
+
* @param command Commands with preprocessing functions to be deleted.
|
|
3172
|
+
* @param callback Preprocessing function to be deleted.
|
|
3173
|
+
*/
|
|
3174
|
+
offBefore(command, callback) {
|
|
3175
|
+
this._off(this.beforeHooks, command, callback);
|
|
3176
|
+
return this;
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3179
|
+
* You remove the post-preprocessing functions registered with `onAfter` or `onceAfter` methods.
|
|
3180
|
+
* If you don't specify a callback parameter, it removes all post-preprocessing functions registered for that command.
|
|
3181
|
+
* @param command Commands with post-preprocessing functions to be deleted.
|
|
3182
|
+
* @param callback post-Preprocessing function to be deleted.
|
|
3183
|
+
*/
|
|
3184
|
+
offAfter(command, callback) {
|
|
3185
|
+
this._off(this.afterHooks, command, callback);
|
|
3186
|
+
return this;
|
|
3187
|
+
}
|
|
3188
|
+
_hookWith(hooks, command, value, ...params) {
|
|
3189
|
+
let wrappers = this._ensureCommand(hooks, command);
|
|
3190
|
+
let i = wrappers.length;
|
|
3191
|
+
while (i--) {
|
|
3192
|
+
const wrapper = wrappers[i];
|
|
3193
|
+
value = wrapper.callback(value, ...params);
|
|
3194
|
+
wrapper.repeat -= 1;
|
|
3195
|
+
if (wrapper.repeat === 0) {
|
|
3196
|
+
this._off(hooks, command, wrapper.callback);
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return value;
|
|
3200
|
+
}
|
|
3201
|
+
/**
|
|
3202
|
+
* You execute the callback function provided as a parameter. This callback function receives the 'initialValue' parameter.
|
|
3203
|
+
*
|
|
3204
|
+
* If preprocessing functions are registered, they run first, and the value returned by the preprocessing functions becomes the 'initialValue' parameter.
|
|
3205
|
+
* After the callback function finishes, post-processing functions are called.
|
|
3206
|
+
* These post-processing functions receive the value returned by the callback function as a parameter and run sequentially.
|
|
3207
|
+
*
|
|
3208
|
+
* The final value returned becomes the result of the `trigger` method.
|
|
3209
|
+
* @param command Command to work.
|
|
3210
|
+
* @param initialValue Initial value to be passed to the callback function.
|
|
3211
|
+
* @param callback The callback function to be executed.
|
|
3212
|
+
*/
|
|
3213
|
+
trigger(command, initialValue, callback, ...params) {
|
|
3214
|
+
let value;
|
|
3215
|
+
value = this._hookWith(this.beforeHooks, command, initialValue, ...params);
|
|
3216
|
+
value = callback(value, ...params);
|
|
3217
|
+
value = this._hookWith(this.afterHooks, command, value, ...params);
|
|
3218
|
+
return value;
|
|
3219
|
+
}
|
|
3220
|
+
};
|
|
3221
|
+
function useHookallSync(target = HookallSync.Global) {
|
|
3222
|
+
return new HookallSync(target);
|
|
3223
|
+
}
|
|
3224
|
+
|
|
2909
3225
|
// src/utils/numberToBytes.ts
|
|
2910
3226
|
function numberToBytes(value, buffer, offset = 0, length = buffer.length) {
|
|
2911
3227
|
if (length === 4) {
|
|
@@ -4323,9 +4639,33 @@ var LogManager = class {
|
|
|
4323
4639
|
});
|
|
4324
4640
|
});
|
|
4325
4641
|
}
|
|
4642
|
+
/**
|
|
4643
|
+
* Writes a commit marker to the log file.
|
|
4644
|
+
* This indicates that the preceding logs are part of a committed transaction.
|
|
4645
|
+
*/
|
|
4646
|
+
async writeCommitMarker() {
|
|
4647
|
+
if (this.fd === null) {
|
|
4648
|
+
this.open();
|
|
4649
|
+
}
|
|
4650
|
+
this.view.setUint32(0, 4294967295, true);
|
|
4651
|
+
this.buffer.fill(0, 4);
|
|
4652
|
+
await new Promise((resolve, reject) => {
|
|
4653
|
+
import_node_fs.default.write(this.fd, this.buffer, 0, this.entrySize, null, (err) => {
|
|
4654
|
+
if (err) return reject(err);
|
|
4655
|
+
resolve();
|
|
4656
|
+
});
|
|
4657
|
+
});
|
|
4658
|
+
await new Promise((resolve, reject) => {
|
|
4659
|
+
import_node_fs.default.fsync(this.fd, (err) => {
|
|
4660
|
+
if (err) return reject(err);
|
|
4661
|
+
resolve();
|
|
4662
|
+
});
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4326
4665
|
/**
|
|
4327
4666
|
* Reads the log file to recover the page map.
|
|
4328
4667
|
* Runs synchronously as it is called by the VFS constructor.
|
|
4668
|
+
* Only returns pages from committed transactions (ended with a commit marker).
|
|
4329
4669
|
* @returns Recovered page map
|
|
4330
4670
|
*/
|
|
4331
4671
|
readAllSync() {
|
|
@@ -4335,11 +4675,19 @@ var LogManager = class {
|
|
|
4335
4675
|
const restoredPages = /* @__PURE__ */ new Map();
|
|
4336
4676
|
const currentFileSize = import_node_fs.default.fstatSync(this.fd).size;
|
|
4337
4677
|
let offset = 0;
|
|
4678
|
+
let pendingPages = /* @__PURE__ */ new Map();
|
|
4338
4679
|
while (offset + this.entrySize <= currentFileSize) {
|
|
4339
4680
|
import_node_fs.default.readSync(this.fd, this.buffer, 0, this.entrySize, offset);
|
|
4340
4681
|
const pageId = this.view.getUint32(0, true);
|
|
4341
|
-
|
|
4342
|
-
|
|
4682
|
+
if (pageId === 4294967295) {
|
|
4683
|
+
for (const [pId, pData] of pendingPages) {
|
|
4684
|
+
restoredPages.set(pId, pData);
|
|
4685
|
+
}
|
|
4686
|
+
pendingPages.clear();
|
|
4687
|
+
} else {
|
|
4688
|
+
const pageData = this.buffer.slice(4, 4 + this.pageSize);
|
|
4689
|
+
pendingPages.set(pageId, pageData);
|
|
4690
|
+
}
|
|
4343
4691
|
offset += this.entrySize;
|
|
4344
4692
|
}
|
|
4345
4693
|
return restoredPages;
|
|
@@ -4441,13 +4789,13 @@ var VirtualFileSystem = class {
|
|
|
4441
4789
|
}
|
|
4442
4790
|
}
|
|
4443
4791
|
/**
|
|
4444
|
-
*
|
|
4792
|
+
* Prepares the transaction for commit (Phase 1).
|
|
4793
|
+
* Writes dirty pages to WAL but does not update the main database file.
|
|
4445
4794
|
* @param tx Transaction
|
|
4446
4795
|
*/
|
|
4447
|
-
async
|
|
4796
|
+
async prepareCommit(tx) {
|
|
4448
4797
|
const dirtyPages = tx.__getDirtyPages();
|
|
4449
4798
|
if (dirtyPages.size === 0) {
|
|
4450
|
-
this.cleanupTransaction(tx);
|
|
4451
4799
|
return;
|
|
4452
4800
|
}
|
|
4453
4801
|
const dirtyPageMap = /* @__PURE__ */ new Map();
|
|
@@ -4460,6 +4808,21 @@ var VirtualFileSystem = class {
|
|
|
4460
4808
|
if (this.logManager && dirtyPageMap.size > 0) {
|
|
4461
4809
|
await this.logManager.append(dirtyPageMap);
|
|
4462
4810
|
}
|
|
4811
|
+
}
|
|
4812
|
+
/**
|
|
4813
|
+
* Finalizes the transaction commit (Phase 2).
|
|
4814
|
+
* Writes commit marker to WAL and updates the main database file (Checkpoint).
|
|
4815
|
+
* @param tx Transaction
|
|
4816
|
+
*/
|
|
4817
|
+
async finalizeCommit(tx) {
|
|
4818
|
+
const dirtyPages = tx.__getDirtyPages();
|
|
4819
|
+
if (dirtyPages.size === 0) {
|
|
4820
|
+
this.cleanupTransaction(tx);
|
|
4821
|
+
return;
|
|
4822
|
+
}
|
|
4823
|
+
if (this.logManager) {
|
|
4824
|
+
await this.logManager.writeCommitMarker();
|
|
4825
|
+
}
|
|
4463
4826
|
const sortedPages = Array.from(dirtyPages).sort((a, b) => a - b);
|
|
4464
4827
|
const promises = [];
|
|
4465
4828
|
for (const pageId of sortedPages) {
|
|
@@ -4482,6 +4845,15 @@ var VirtualFileSystem = class {
|
|
|
4482
4845
|
}
|
|
4483
4846
|
this.cleanupTransaction(tx);
|
|
4484
4847
|
}
|
|
4848
|
+
/**
|
|
4849
|
+
* Commits the transaction (Single Phase).
|
|
4850
|
+
* Wrapper for prepare + finalize for backward compatibility.
|
|
4851
|
+
* @param tx Transaction
|
|
4852
|
+
*/
|
|
4853
|
+
async commit(tx) {
|
|
4854
|
+
await this.prepareCommit(tx);
|
|
4855
|
+
await this.finalizeCommit(tx);
|
|
4856
|
+
}
|
|
4485
4857
|
/**
|
|
4486
4858
|
* Rolls back the transaction.
|
|
4487
4859
|
* @param tx Transaction
|
|
@@ -5728,11 +6100,19 @@ var Transaction = class {
|
|
|
5728
6100
|
this.heldLocks.add(lockId);
|
|
5729
6101
|
this.pageLocks.set(pageId, lockId);
|
|
5730
6102
|
}
|
|
6103
|
+
/**
|
|
6104
|
+
* Prepares the transaction for commit (Phase 1 of 2PC).
|
|
6105
|
+
* Writes dirty pages to WAL but does not update the database file yet.
|
|
6106
|
+
*/
|
|
6107
|
+
async prepare() {
|
|
6108
|
+
await this.vfs.prepareCommit(this);
|
|
6109
|
+
}
|
|
5731
6110
|
/**
|
|
5732
6111
|
* Commits the transaction.
|
|
5733
6112
|
*/
|
|
5734
6113
|
async commit() {
|
|
5735
|
-
await this.vfs.
|
|
6114
|
+
await this.vfs.prepareCommit(this);
|
|
6115
|
+
await this.vfs.finalizeCommit(this);
|
|
5736
6116
|
await TxContext.run(this, async () => {
|
|
5737
6117
|
for (const hook of this.commitHooks) {
|
|
5738
6118
|
await hook();
|
|
@@ -5776,11 +6156,20 @@ var Transaction = class {
|
|
|
5776
6156
|
|
|
5777
6157
|
// src/core/DataplyAPI.ts
|
|
5778
6158
|
var DataplyAPI = class {
|
|
5779
|
-
constructor(file,
|
|
6159
|
+
constructor(file, options) {
|
|
5780
6160
|
this.file = file;
|
|
5781
|
-
this.
|
|
5782
|
-
|
|
5783
|
-
|
|
6161
|
+
this.hook = {
|
|
6162
|
+
sync: useHookallSync(this),
|
|
6163
|
+
async: useHookall(this)
|
|
6164
|
+
};
|
|
6165
|
+
this.options = this.verboseOptions(options);
|
|
6166
|
+
this.fileHandle = this.createOrOpen(file, this.options);
|
|
6167
|
+
this.pfs = new PageFileSystem(
|
|
6168
|
+
this.fileHandle,
|
|
6169
|
+
this.options.pageSize,
|
|
6170
|
+
this.options.pageCacheCapacity,
|
|
6171
|
+
this.options.wal
|
|
6172
|
+
);
|
|
5784
6173
|
this.textCodec = new TextCodec();
|
|
5785
6174
|
this.lockManager = new LockManager();
|
|
5786
6175
|
this.rowTableEngine = new RowTableEngine(this.pfs, this.options);
|
|
@@ -5788,10 +6177,12 @@ var DataplyAPI = class {
|
|
|
5788
6177
|
this.txIdCounter = 0;
|
|
5789
6178
|
}
|
|
5790
6179
|
options;
|
|
6180
|
+
fileHandle;
|
|
5791
6181
|
pfs;
|
|
5792
6182
|
rowTableEngine;
|
|
5793
6183
|
lockManager;
|
|
5794
6184
|
textCodec;
|
|
6185
|
+
hook;
|
|
5795
6186
|
initialized;
|
|
5796
6187
|
txIdCounter;
|
|
5797
6188
|
/**
|
|
@@ -5800,7 +6191,7 @@ var DataplyAPI = class {
|
|
|
5800
6191
|
* @param fileHandle File handle
|
|
5801
6192
|
* @returns Whether the page file is a valid Dataply file
|
|
5802
6193
|
*/
|
|
5803
|
-
|
|
6194
|
+
verifyFormat(fileHandle) {
|
|
5804
6195
|
const size = MetadataPageManager.CONSTANT.OFFSET_MAGIC_STRING + MetadataPageManager.CONSTANT.MAGIC_STRING.length;
|
|
5805
6196
|
const metadataPage = new Uint8Array(size);
|
|
5806
6197
|
import_node_fs3.default.readSync(fileHandle, metadataPage, 0, size, 0);
|
|
@@ -5814,7 +6205,7 @@ var DataplyAPI = class {
|
|
|
5814
6205
|
* @param options Options
|
|
5815
6206
|
* @returns Options filled without omissions
|
|
5816
6207
|
*/
|
|
5817
|
-
|
|
6208
|
+
verboseOptions(options) {
|
|
5818
6209
|
return Object.assign({
|
|
5819
6210
|
pageSize: 8192,
|
|
5820
6211
|
pageCacheCapacity: 1e4,
|
|
@@ -5825,67 +6216,71 @@ var DataplyAPI = class {
|
|
|
5825
6216
|
* Initializes the database file.
|
|
5826
6217
|
* The first page is initialized as the metadata page.
|
|
5827
6218
|
* The second page is initialized as the first data page.
|
|
6219
|
+
* @param file Database file path
|
|
5828
6220
|
* @param fileHandle File handle
|
|
5829
6221
|
*/
|
|
5830
|
-
|
|
5831
|
-
const
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
6222
|
+
initializeFile(file, fileHandle, options) {
|
|
6223
|
+
const fileData = this.hook.sync.trigger("create", new Uint8Array(), (prepareFileData) => {
|
|
6224
|
+
const metadataPageManager = new MetadataPageManager();
|
|
6225
|
+
const bitmapPageManager = new BitmapPageManager();
|
|
6226
|
+
const dataPageManager = new DataPageManager();
|
|
6227
|
+
const metadataPage = new Uint8Array(options.pageSize);
|
|
6228
|
+
const dataPage = new Uint8Array(options.pageSize);
|
|
6229
|
+
metadataPageManager.initial(
|
|
6230
|
+
metadataPage,
|
|
6231
|
+
MetadataPageManager.CONSTANT.PAGE_TYPE_METADATA,
|
|
6232
|
+
0,
|
|
6233
|
+
0,
|
|
6234
|
+
options.pageSize - MetadataPageManager.CONSTANT.SIZE_PAGE_HEADER
|
|
6235
|
+
);
|
|
6236
|
+
metadataPageManager.setMagicString(metadataPage);
|
|
6237
|
+
metadataPageManager.setPageSize(metadataPage, options.pageSize);
|
|
6238
|
+
metadataPageManager.setRootIndexPageId(metadataPage, -1);
|
|
6239
|
+
metadataPageManager.setBitmapPageId(metadataPage, 1);
|
|
6240
|
+
metadataPageManager.setLastInsertPageId(metadataPage, 2);
|
|
6241
|
+
metadataPageManager.setPageCount(metadataPage, 3);
|
|
6242
|
+
metadataPageManager.setFreePageId(metadataPage, -1);
|
|
6243
|
+
const bitmapPage = new Uint8Array(options.pageSize);
|
|
6244
|
+
bitmapPageManager.initial(
|
|
6245
|
+
bitmapPage,
|
|
6246
|
+
BitmapPageManager.CONSTANT.PAGE_TYPE_BITMAP,
|
|
6247
|
+
1,
|
|
6248
|
+
-1,
|
|
6249
|
+
options.pageSize - BitmapPageManager.CONSTANT.SIZE_PAGE_HEADER
|
|
6250
|
+
);
|
|
6251
|
+
dataPageManager.initial(
|
|
6252
|
+
dataPage,
|
|
6253
|
+
DataPageManager.CONSTANT.PAGE_TYPE_DATA,
|
|
6254
|
+
2,
|
|
6255
|
+
-1,
|
|
6256
|
+
options.pageSize - DataPageManager.CONSTANT.SIZE_PAGE_HEADER
|
|
6257
|
+
);
|
|
6258
|
+
return new Uint8Array([
|
|
6259
|
+
...prepareFileData,
|
|
6260
|
+
...metadataPage,
|
|
6261
|
+
...bitmapPage,
|
|
6262
|
+
...dataPage
|
|
6263
|
+
]);
|
|
6264
|
+
}, file, fileHandle, options);
|
|
6265
|
+
import_node_fs3.default.appendFileSync(fileHandle, fileData);
|
|
5870
6266
|
}
|
|
5871
6267
|
/**
|
|
5872
6268
|
* Opens the database file. If the file does not exist, it initializes it.
|
|
5873
6269
|
* @param file Database file path
|
|
5874
6270
|
* @param options Options
|
|
5875
|
-
* @returns
|
|
6271
|
+
* @returns File handle
|
|
5876
6272
|
*/
|
|
5877
|
-
|
|
5878
|
-
const verboseOption = this.VerboseOptions(options);
|
|
6273
|
+
createOrOpen(file, options) {
|
|
5879
6274
|
let fileHandle;
|
|
5880
|
-
if (
|
|
6275
|
+
if (options.pageCacheCapacity < 100) {
|
|
5881
6276
|
throw new Error("Page cache capacity must be at least 100");
|
|
5882
6277
|
}
|
|
5883
6278
|
if (!import_node_fs3.default.existsSync(file)) {
|
|
5884
|
-
if (
|
|
6279
|
+
if (options.pageSize < 4096) {
|
|
5885
6280
|
throw new Error("Page size must be at least 4096 bytes");
|
|
5886
6281
|
}
|
|
5887
6282
|
fileHandle = import_node_fs3.default.openSync(file, "w+");
|
|
5888
|
-
this.
|
|
6283
|
+
this.initializeFile(file, fileHandle, options);
|
|
5889
6284
|
} else {
|
|
5890
6285
|
fileHandle = import_node_fs3.default.openSync(file, "r+");
|
|
5891
6286
|
const buffer = new Uint8Array(
|
|
@@ -5896,14 +6291,14 @@ var DataplyAPI = class {
|
|
|
5896
6291
|
if (metadataManager.isMetadataPage(buffer)) {
|
|
5897
6292
|
const storedPageSize = metadataManager.getPageSize(buffer);
|
|
5898
6293
|
if (storedPageSize > 0) {
|
|
5899
|
-
|
|
6294
|
+
options.pageSize = storedPageSize;
|
|
5900
6295
|
}
|
|
5901
6296
|
}
|
|
5902
6297
|
}
|
|
5903
|
-
if (!this.
|
|
6298
|
+
if (!this.verifyFormat(fileHandle)) {
|
|
5904
6299
|
throw new Error("Invalid dataply file");
|
|
5905
6300
|
}
|
|
5906
|
-
return
|
|
6301
|
+
return fileHandle;
|
|
5907
6302
|
}
|
|
5908
6303
|
/**
|
|
5909
6304
|
* Initializes the dataply instance.
|
|
@@ -5914,8 +6309,12 @@ var DataplyAPI = class {
|
|
|
5914
6309
|
if (this.initialized) {
|
|
5915
6310
|
return;
|
|
5916
6311
|
}
|
|
5917
|
-
await this.runWithDefault(() =>
|
|
5918
|
-
|
|
6312
|
+
await this.runWithDefault(() => {
|
|
6313
|
+
return this.hook.async.trigger("init", void 0, async () => {
|
|
6314
|
+
await this.rowTableEngine.init();
|
|
6315
|
+
this.initialized = true;
|
|
6316
|
+
});
|
|
6317
|
+
});
|
|
5919
6318
|
}
|
|
5920
6319
|
/**
|
|
5921
6320
|
* Creates a transaction.
|
|
@@ -5974,10 +6373,11 @@ var DataplyAPI = class {
|
|
|
5974
6373
|
throw new Error("Dataply instance is not initialized");
|
|
5975
6374
|
}
|
|
5976
6375
|
return this.runWithDefault((tx2) => {
|
|
6376
|
+
incrementRowCount = incrementRowCount ?? true;
|
|
5977
6377
|
if (typeof data === "string") {
|
|
5978
6378
|
data = this.textCodec.encode(data);
|
|
5979
6379
|
}
|
|
5980
|
-
return this.rowTableEngine.insert(data, incrementRowCount
|
|
6380
|
+
return this.rowTableEngine.insert(data, incrementRowCount, tx2);
|
|
5981
6381
|
}, tx);
|
|
5982
6382
|
}
|
|
5983
6383
|
/**
|
|
@@ -5993,10 +6393,11 @@ var DataplyAPI = class {
|
|
|
5993
6393
|
throw new Error("Dataply instance is not initialized");
|
|
5994
6394
|
}
|
|
5995
6395
|
return this.runWithDefault(async (tx2) => {
|
|
6396
|
+
incrementRowCount = incrementRowCount ?? true;
|
|
5996
6397
|
const pks = [];
|
|
5997
6398
|
for (const data of dataList) {
|
|
5998
6399
|
const encoded = typeof data === "string" ? this.textCodec.encode(data) : data;
|
|
5999
|
-
const pk = await this.rowTableEngine.insert(encoded, incrementRowCount
|
|
6400
|
+
const pk = await this.rowTableEngine.insert(encoded, incrementRowCount, tx2);
|
|
6000
6401
|
pks.push(pk);
|
|
6001
6402
|
}
|
|
6002
6403
|
return pks;
|
|
@@ -6030,7 +6431,8 @@ var DataplyAPI = class {
|
|
|
6030
6431
|
throw new Error("Dataply instance is not initialized");
|
|
6031
6432
|
}
|
|
6032
6433
|
return this.runWithDefault(async (tx2) => {
|
|
6033
|
-
|
|
6434
|
+
decrementRowCount = decrementRowCount ?? true;
|
|
6435
|
+
await this.rowTableEngine.delete(pk, decrementRowCount, tx2);
|
|
6034
6436
|
}, tx);
|
|
6035
6437
|
}
|
|
6036
6438
|
async select(pk, asRaw = false, tx) {
|
|
@@ -6051,8 +6453,10 @@ var DataplyAPI = class {
|
|
|
6051
6453
|
if (!this.initialized) {
|
|
6052
6454
|
throw new Error("Dataply instance is not initialized");
|
|
6053
6455
|
}
|
|
6054
|
-
|
|
6055
|
-
|
|
6456
|
+
return this.hook.async.trigger("close", void 0, async () => {
|
|
6457
|
+
await this.pfs.close();
|
|
6458
|
+
import_node_fs3.default.closeSync(this.fileHandle);
|
|
6459
|
+
});
|
|
6056
6460
|
}
|
|
6057
6461
|
};
|
|
6058
6462
|
|
|
@@ -6060,7 +6464,7 @@ var DataplyAPI = class {
|
|
|
6060
6464
|
var Dataply = class {
|
|
6061
6465
|
api;
|
|
6062
6466
|
constructor(file, options) {
|
|
6063
|
-
this.api = DataplyAPI
|
|
6467
|
+
this.api = new DataplyAPI(file, options ?? {});
|
|
6064
6468
|
}
|
|
6065
6469
|
/**
|
|
6066
6470
|
* Gets the options used to open the dataply.
|
|
@@ -6139,6 +6543,52 @@ var Dataply = class {
|
|
|
6139
6543
|
return this.api.close();
|
|
6140
6544
|
}
|
|
6141
6545
|
};
|
|
6546
|
+
|
|
6547
|
+
// src/core/transaction/GlobalTransaction.ts
|
|
6548
|
+
var GlobalTransaction = class {
|
|
6549
|
+
transactions = [];
|
|
6550
|
+
isCommitted = false;
|
|
6551
|
+
isRolledBack = false;
|
|
6552
|
+
/**
|
|
6553
|
+
* Adds a transaction to the global transaction.
|
|
6554
|
+
* @param tx Transaction to add
|
|
6555
|
+
*/
|
|
6556
|
+
add(tx) {
|
|
6557
|
+
this.transactions.push(tx);
|
|
6558
|
+
}
|
|
6559
|
+
/**
|
|
6560
|
+
* Commits all transactions atomically.
|
|
6561
|
+
* Phase 1: Prepare (Write WAL)
|
|
6562
|
+
* Phase 2: Commit (Write Commit Marker & Checkpoint)
|
|
6563
|
+
*/
|
|
6564
|
+
async commit() {
|
|
6565
|
+
if (this.isCommitted || this.isRolledBack) {
|
|
6566
|
+
throw new Error("Transaction is already finished");
|
|
6567
|
+
}
|
|
6568
|
+
try {
|
|
6569
|
+
await Promise.all(this.transactions.map((tx) => tx.prepare()));
|
|
6570
|
+
} catch (e) {
|
|
6571
|
+
await this.rollback();
|
|
6572
|
+
throw new Error(`Global commit failed during prepare phase: ${e}`);
|
|
6573
|
+
}
|
|
6574
|
+
try {
|
|
6575
|
+
await Promise.all(this.transactions.map((tx) => tx.commit()));
|
|
6576
|
+
this.isCommitted = true;
|
|
6577
|
+
} catch (e) {
|
|
6578
|
+
throw new Error(`Global commit failed during finalize phase: ${e}`);
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
/**
|
|
6582
|
+
* Rolls back all transactions.
|
|
6583
|
+
*/
|
|
6584
|
+
async rollback() {
|
|
6585
|
+
if (this.isCommitted || this.isRolledBack) {
|
|
6586
|
+
return;
|
|
6587
|
+
}
|
|
6588
|
+
await Promise.all(this.transactions.map((tx) => tx.rollback()));
|
|
6589
|
+
this.isRolledBack = true;
|
|
6590
|
+
}
|
|
6591
|
+
};
|
|
6142
6592
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6143
6593
|
0 && (module.exports = {
|
|
6144
6594
|
BPTreeAsync,
|
|
@@ -6147,6 +6597,7 @@ var Dataply = class {
|
|
|
6147
6597
|
CacheEntanglementSync,
|
|
6148
6598
|
Dataply,
|
|
6149
6599
|
DataplyAPI,
|
|
6600
|
+
GlobalTransaction,
|
|
6150
6601
|
InMemoryStoreStrategyAsync,
|
|
6151
6602
|
InMemoryStoreStrategySync,
|
|
6152
6603
|
InvertedWeakMap,
|
|
@@ -6156,5 +6607,6 @@ var Dataply = class {
|
|
|
6156
6607
|
SerializeStrategyAsync,
|
|
6157
6608
|
SerializeStrategySync,
|
|
6158
6609
|
StringComparator,
|
|
6610
|
+
Transaction,
|
|
6159
6611
|
ValueComparator
|
|
6160
6612
|
});
|
|
@@ -1,50 +1,63 @@
|
|
|
1
1
|
import type { DataplyOptions, DataplyMetadata } from '../types';
|
|
2
|
+
import { type IHookall, type IHookallSync } from 'hookall';
|
|
2
3
|
import { PageFileSystem } from './PageFileSystem';
|
|
3
4
|
import { RowTableEngine } from './RowTableEngine';
|
|
4
5
|
import { TextCodec } from '../utils/TextCodec';
|
|
5
6
|
import { LockManager } from './transaction/LockManager';
|
|
6
7
|
import { Transaction } from './transaction/Transaction';
|
|
8
|
+
interface DataplyAPISyncHook {
|
|
9
|
+
create: (fileData: Uint8Array, file: string, fileHandle: number, options: Required<DataplyOptions>) => Uint8Array;
|
|
10
|
+
}
|
|
11
|
+
interface DataplyAPIAsyncHook {
|
|
12
|
+
init: () => Promise<void>;
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
}
|
|
7
15
|
/**
|
|
8
16
|
* Class for managing Dataply files.
|
|
9
17
|
*/
|
|
10
18
|
export declare class DataplyAPI {
|
|
11
|
-
protected file: string;
|
|
12
|
-
protected fileHandle: number;
|
|
19
|
+
protected readonly file: string;
|
|
13
20
|
readonly options: Required<DataplyOptions>;
|
|
21
|
+
protected readonly fileHandle: number;
|
|
14
22
|
protected readonly pfs: PageFileSystem;
|
|
15
23
|
protected readonly rowTableEngine: RowTableEngine;
|
|
16
24
|
protected readonly lockManager: LockManager;
|
|
17
25
|
protected readonly textCodec: TextCodec;
|
|
26
|
+
protected readonly hook: {
|
|
27
|
+
sync: IHookallSync<DataplyAPISyncHook>;
|
|
28
|
+
async: IHookall<DataplyAPIAsyncHook>;
|
|
29
|
+
};
|
|
18
30
|
protected initialized: boolean;
|
|
19
31
|
private txIdCounter;
|
|
20
|
-
|
|
32
|
+
constructor(file: string, options: DataplyOptions);
|
|
21
33
|
/**
|
|
22
34
|
* Verifies if the page file is a valid Dataply file.
|
|
23
35
|
* The metadata page must be located at the beginning of the Dataply file.
|
|
24
36
|
* @param fileHandle File handle
|
|
25
37
|
* @returns Whether the page file is a valid Dataply file
|
|
26
38
|
*/
|
|
27
|
-
|
|
39
|
+
private verifyFormat;
|
|
28
40
|
/**
|
|
29
41
|
* Fills missing options with default values.
|
|
30
42
|
* @param options Options
|
|
31
43
|
* @returns Options filled without omissions
|
|
32
44
|
*/
|
|
33
|
-
|
|
45
|
+
private verboseOptions;
|
|
34
46
|
/**
|
|
35
47
|
* Initializes the database file.
|
|
36
48
|
* The first page is initialized as the metadata page.
|
|
37
49
|
* The second page is initialized as the first data page.
|
|
50
|
+
* @param file Database file path
|
|
38
51
|
* @param fileHandle File handle
|
|
39
52
|
*/
|
|
40
|
-
|
|
53
|
+
private initializeFile;
|
|
41
54
|
/**
|
|
42
55
|
* Opens the database file. If the file does not exist, it initializes it.
|
|
43
56
|
* @param file Database file path
|
|
44
57
|
* @param options Options
|
|
45
|
-
* @returns
|
|
58
|
+
* @returns File handle
|
|
46
59
|
*/
|
|
47
|
-
|
|
60
|
+
private createOrOpen;
|
|
48
61
|
/**
|
|
49
62
|
* Initializes the dataply instance.
|
|
50
63
|
* Must be called before using the dataply instance.
|
|
@@ -119,3 +132,4 @@ export declare class DataplyAPI {
|
|
|
119
132
|
*/
|
|
120
133
|
close(): Promise<void>;
|
|
121
134
|
}
|
|
135
|
+
export {};
|
|
@@ -25,9 +25,15 @@ export declare class LogManager {
|
|
|
25
25
|
* @param pages Map of changed pages (Page ID -> Data)
|
|
26
26
|
*/
|
|
27
27
|
append(pages: Map<number, Uint8Array>): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Writes a commit marker to the log file.
|
|
30
|
+
* This indicates that the preceding logs are part of a committed transaction.
|
|
31
|
+
*/
|
|
32
|
+
writeCommitMarker(): Promise<void>;
|
|
28
33
|
/**
|
|
29
34
|
* Reads the log file to recover the page map.
|
|
30
35
|
* Runs synchronously as it is called by the VFS constructor.
|
|
36
|
+
* Only returns pages from committed transactions (ended with a commit marker).
|
|
31
37
|
* @returns Recovered page map
|
|
32
38
|
*/
|
|
33
39
|
readAllSync(): Map<number, Uint8Array>;
|
|
@@ -29,7 +29,20 @@ export declare class VirtualFileSystem {
|
|
|
29
29
|
*/
|
|
30
30
|
private recover;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Prepares the transaction for commit (Phase 1).
|
|
33
|
+
* Writes dirty pages to WAL but does not update the main database file.
|
|
34
|
+
* @param tx Transaction
|
|
35
|
+
*/
|
|
36
|
+
prepareCommit(tx: Transaction): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Finalizes the transaction commit (Phase 2).
|
|
39
|
+
* Writes commit marker to WAL and updates the main database file (Checkpoint).
|
|
40
|
+
* @param tx Transaction
|
|
41
|
+
*/
|
|
42
|
+
finalizeCommit(tx: Transaction): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Commits the transaction (Single Phase).
|
|
45
|
+
* Wrapper for prepare + finalize for backward compatibility.
|
|
33
46
|
* @param tx Transaction
|
|
34
47
|
*/
|
|
35
48
|
commit(tx: Transaction): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Transaction } from './Transaction';
|
|
2
|
+
/**
|
|
3
|
+
* Global Transaction Manager.
|
|
4
|
+
* Coordinates transactions across multiple instances (shards) using 2-Phase Commit (2PC).
|
|
5
|
+
*/
|
|
6
|
+
export declare class GlobalTransaction {
|
|
7
|
+
private transactions;
|
|
8
|
+
private isCommitted;
|
|
9
|
+
private isRolledBack;
|
|
10
|
+
/**
|
|
11
|
+
* Adds a transaction to the global transaction.
|
|
12
|
+
* @param tx Transaction to add
|
|
13
|
+
*/
|
|
14
|
+
add(tx: Transaction): void;
|
|
15
|
+
/**
|
|
16
|
+
* Commits all transactions atomically.
|
|
17
|
+
* Phase 1: Prepare (Write WAL)
|
|
18
|
+
* Phase 2: Commit (Write Commit Marker & Checkpoint)
|
|
19
|
+
*/
|
|
20
|
+
commit(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Rolls back all transactions.
|
|
23
|
+
*/
|
|
24
|
+
rollback(): Promise<void>;
|
|
25
|
+
}
|
|
@@ -86,6 +86,11 @@ export declare class Transaction {
|
|
|
86
86
|
* @param pageId Page ID
|
|
87
87
|
*/
|
|
88
88
|
__acquireWriteLock(pageId: number): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Prepares the transaction for commit (Phase 1 of 2PC).
|
|
91
|
+
* Writes dirty pages to WAL but does not update the database file yet.
|
|
92
|
+
*/
|
|
93
|
+
prepare(): Promise<void>;
|
|
89
94
|
/**
|
|
90
95
|
* Commits the transaction.
|
|
91
96
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,3 +4,5 @@ export * from 'cache-entanglement';
|
|
|
4
4
|
export type { DataplyOptions } from './types';
|
|
5
5
|
export { Dataply } from './core/Dataply';
|
|
6
6
|
export { DataplyAPI } from './core/DataplyAPI';
|
|
7
|
+
export { Transaction } from './core/transaction/Transaction';
|
|
8
|
+
export { GlobalTransaction } from './core/transaction/GlobalTransaction';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataply",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
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>",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"cache-entanglement": "^1.7.1",
|
|
48
|
+
"hookall": "^2.2.0",
|
|
48
49
|
"ryoiki": "^1.2.0",
|
|
49
50
|
"serializable-bptree": "^5.2.1"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|
package/readme.md
CHANGED
|
@@ -82,6 +82,36 @@ try {
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
### Global Transactions
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { Dataply, GlobalTransaction } from 'dataply'
|
|
90
|
+
|
|
91
|
+
const db1 = new Dataply('./db1.db', { wal: './db1.wal' })
|
|
92
|
+
const db2 = new Dataply('./db2.db', { wal: './db2.wal' })
|
|
93
|
+
|
|
94
|
+
await db1.init()
|
|
95
|
+
await db2.init()
|
|
96
|
+
|
|
97
|
+
const tx1 = db1.createTransaction()
|
|
98
|
+
const tx2 = db2.createTransaction()
|
|
99
|
+
|
|
100
|
+
const globalTx = new GlobalTransaction()
|
|
101
|
+
globalTx.add(tx1)
|
|
102
|
+
globalTx.add(tx2)
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
await db1.insert('Data for DB1', tx1)
|
|
106
|
+
await db2.insert('Data for DB2', tx2)
|
|
107
|
+
|
|
108
|
+
// Phase 1: Prepare (WAL write) -> Phase 2: Commit (Marker write)
|
|
109
|
+
await globalTx.commit()
|
|
110
|
+
} catch (error) {
|
|
111
|
+
await globalTx.rollback()
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
85
115
|
### Auto-Transaction
|
|
86
116
|
If you omit the `tx` argument when calling methods like `insert`, `update`, or `delete`, Dataply internally **creates an individual transaction automatically**.
|
|
87
117
|
|
|
@@ -133,6 +163,17 @@ Permanently reflects all changes made during the transaction to disk and release
|
|
|
133
163
|
#### `async rollback(): Promise<void>`
|
|
134
164
|
Cancels all changes made during the transaction and restores the original state.
|
|
135
165
|
|
|
166
|
+
### GlobalTransaction Class
|
|
167
|
+
|
|
168
|
+
#### `add(tx: Transaction): void`
|
|
169
|
+
Adds an individual transaction from a `Dataply` instance to the global transaction.
|
|
170
|
+
|
|
171
|
+
#### `async commit(): Promise<void>`
|
|
172
|
+
Atomically commits all added transactions using a 2-Phase Commit (2PC) process.
|
|
173
|
+
|
|
174
|
+
#### `async rollback(): Promise<void>`
|
|
175
|
+
Rolls back all added transactions.
|
|
176
|
+
|
|
136
177
|
## Extending Dataply
|
|
137
178
|
|
|
138
179
|
If you want to extend Dataply's functionality, use the `DataplyAPI` class. Unlike the standard `Dataply` class, `DataplyAPI` provides direct access to internal components like `PageFileSystem` or `RowTableEngine`, offering much more flexibility for custom implementations.
|
|
@@ -152,7 +193,7 @@ class CustomDataply extends DataplyAPI {
|
|
|
152
193
|
}
|
|
153
194
|
}
|
|
154
195
|
|
|
155
|
-
const custom = CustomDataply
|
|
196
|
+
const custom = new CustomDataply('./data.db')
|
|
156
197
|
await custom.init()
|
|
157
198
|
|
|
158
199
|
const stats = await custom.getInternalStats()
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type BPTreeNode, type SerializeStrategyHead, SerializeStrategyAsync } from 'serializable-bptree';
|
|
2
|
-
import { PageFileSystem } from './PageFileSystem';
|
|
3
|
-
import { IndexPageManager, PageManagerFactory } from './Page';
|
|
4
|
-
import { TextCodec } from '../utils/TextCodec';
|
|
5
|
-
export declare class RowIdentifierStrategy extends SerializeStrategyAsync<number, number> {
|
|
6
|
-
readonly order: number;
|
|
7
|
-
protected readonly pfs: PageFileSystem;
|
|
8
|
-
protected rootPageId: number;
|
|
9
|
-
protected factory: PageManagerFactory;
|
|
10
|
-
protected indexPageManger: IndexPageManager;
|
|
11
|
-
protected codec: TextCodec;
|
|
12
|
-
constructor(order: number, pfs: PageFileSystem);
|
|
13
|
-
id(isLeaf: boolean): Promise<string>;
|
|
14
|
-
read(id: string): Promise<BPTreeNode<number, number>>;
|
|
15
|
-
write(id: string, node: BPTreeNode<number, number>): Promise<void>;
|
|
16
|
-
delete(id: string): Promise<void>;
|
|
17
|
-
readHead(): Promise<SerializeStrategyHead | null>;
|
|
18
|
-
writeHead(head: SerializeStrategyHead): Promise<void>;
|
|
19
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { ShardOptions, ShardMetadata } from '../types';
|
|
2
|
-
import { ShardAPI } from './ShareAPI';
|
|
3
|
-
import { Transaction } from './transaction/Transaction';
|
|
4
|
-
/**
|
|
5
|
-
* Class for managing Shard files.
|
|
6
|
-
*/
|
|
7
|
-
export declare class Shard {
|
|
8
|
-
protected readonly api: ShardAPI;
|
|
9
|
-
constructor(file: string, options?: ShardOptions);
|
|
10
|
-
/**
|
|
11
|
-
* Gets the options used to open the shard.
|
|
12
|
-
* @returns Options used to open the shard.
|
|
13
|
-
*/
|
|
14
|
-
get options(): Required<ShardOptions>;
|
|
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
|
|
20
|
-
*/
|
|
21
|
-
createTransaction(): Transaction;
|
|
22
|
-
/**
|
|
23
|
-
* Initializes the shard instance.
|
|
24
|
-
* Must be called before using the shard instance.
|
|
25
|
-
* If not called, the shard instance cannot be used.
|
|
26
|
-
*/
|
|
27
|
-
init(): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Retrieves metadata from the shard.
|
|
30
|
-
* @returns Metadata of the shard.
|
|
31
|
-
*/
|
|
32
|
-
getMetadata(): Promise<ShardMetadata>;
|
|
33
|
-
/**
|
|
34
|
-
* Inserts data. Returns the PK of the added row.
|
|
35
|
-
* @param data Data to add
|
|
36
|
-
* @param tx Transaction
|
|
37
|
-
* @returns PK of the added data
|
|
38
|
-
*/
|
|
39
|
-
insert(data: string | Uint8Array, tx?: Transaction): Promise<number>;
|
|
40
|
-
/**
|
|
41
|
-
* Inserts multiple data in batch.
|
|
42
|
-
* If a transaction is not provided, it internally creates a single transaction to process.
|
|
43
|
-
* @param dataList Array of data to add
|
|
44
|
-
* @param tx Transaction
|
|
45
|
-
* @returns Array of PKs of the added data
|
|
46
|
-
*/
|
|
47
|
-
insertBatch(dataList: (string | Uint8Array)[], tx?: Transaction): Promise<number[]>;
|
|
48
|
-
/**
|
|
49
|
-
* Updates data.
|
|
50
|
-
* @param pk PK of the data to update
|
|
51
|
-
* @param data Data to update
|
|
52
|
-
* @param tx Transaction
|
|
53
|
-
*/
|
|
54
|
-
update(pk: number, data: string | Uint8Array, tx?: Transaction): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Deletes data.
|
|
57
|
-
* @param pk PK of the data to delete
|
|
58
|
-
* @param tx Transaction
|
|
59
|
-
*/
|
|
60
|
-
delete(pk: number, tx?: Transaction): Promise<void>;
|
|
61
|
-
/**
|
|
62
|
-
* Selects data.
|
|
63
|
-
* @param pk PK of the data to select
|
|
64
|
-
* @param asRaw Whether to return the selected data as raw
|
|
65
|
-
* @param tx Transaction
|
|
66
|
-
* @returns Selected data
|
|
67
|
-
*/
|
|
68
|
-
select(pk: number, asRaw: true, tx?: Transaction): Promise<Uint8Array | null>;
|
|
69
|
-
select(pk: number, asRaw: false, tx?: Transaction): Promise<string | null>;
|
|
70
|
-
select(pk: number, asRaw?: boolean, tx?: Transaction): Promise<string | null>;
|
|
71
|
-
/**
|
|
72
|
-
* Closes the shard file.
|
|
73
|
-
*/
|
|
74
|
-
close(): Promise<void>;
|
|
75
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { ShardOptions, ShardMetadata } from '../types';
|
|
2
|
-
import { PageFileSystem } from './PageFileSystem';
|
|
3
|
-
import { RowTableEngine } from './RowTableEngine';
|
|
4
|
-
import { TextCodec } from '../utils/TextCodec';
|
|
5
|
-
import { LockManager } from './transaction/LockManager';
|
|
6
|
-
import { Transaction } from './transaction/Transaction';
|
|
7
|
-
/**
|
|
8
|
-
* Class for managing Shard files.
|
|
9
|
-
*/
|
|
10
|
-
export declare class ShardAPI {
|
|
11
|
-
protected file: string;
|
|
12
|
-
protected fileHandle: number;
|
|
13
|
-
readonly options: Required<ShardOptions>;
|
|
14
|
-
protected readonly pfs: PageFileSystem;
|
|
15
|
-
protected readonly rowTableEngine: RowTableEngine;
|
|
16
|
-
protected readonly lockManager: LockManager;
|
|
17
|
-
protected readonly textCodec: TextCodec;
|
|
18
|
-
protected initialized: boolean;
|
|
19
|
-
private txIdCounter;
|
|
20
|
-
protected constructor(file: string, fileHandle: number, options: Required<ShardOptions>);
|
|
21
|
-
/**
|
|
22
|
-
* Verifies if the page file is a valid Shard file.
|
|
23
|
-
* The metadata page must be located at the beginning of the Shard file.
|
|
24
|
-
* @param fileHandle File handle
|
|
25
|
-
* @returns Whether the page file is a valid Shard file
|
|
26
|
-
*/
|
|
27
|
-
static VerifyFormat(fileHandle: number): boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Fills missing options with default values.
|
|
30
|
-
* @param options Options
|
|
31
|
-
* @returns Options filled without omissions
|
|
32
|
-
*/
|
|
33
|
-
static VerboseOptions(options?: ShardOptions): Required<ShardOptions>;
|
|
34
|
-
/**
|
|
35
|
-
* Initializes the database file.
|
|
36
|
-
* The first page is initialized as the metadata page.
|
|
37
|
-
* The second page is initialized as the first data page.
|
|
38
|
-
* @param fileHandle File handle
|
|
39
|
-
*/
|
|
40
|
-
static InitializeFile(fileHandle: number, options: Required<ShardOptions>): void;
|
|
41
|
-
/**
|
|
42
|
-
* Opens the database file. If the file does not exist, it initializes it.
|
|
43
|
-
* @param file Database file path
|
|
44
|
-
* @param options Options
|
|
45
|
-
* @returns Shard instance
|
|
46
|
-
*/
|
|
47
|
-
static Use(file: string, options?: ShardOptions): ShardAPI;
|
|
48
|
-
/**
|
|
49
|
-
* Initializes the shard instance.
|
|
50
|
-
* Must be called before using the shard instance.
|
|
51
|
-
* If not called, the shard instance cannot be used.
|
|
52
|
-
*/
|
|
53
|
-
init(): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* Creates a transaction.
|
|
56
|
-
* The created transaction object can be used to add or modify data.
|
|
57
|
-
* A transaction must be terminated by calling either `commit` or `rollback`.
|
|
58
|
-
* @returns Transaction object
|
|
59
|
-
*/
|
|
60
|
-
createTransaction(): Transaction;
|
|
61
|
-
/**
|
|
62
|
-
* Runs a callback function within a transaction context.
|
|
63
|
-
* If no transaction is provided, a new transaction is created.
|
|
64
|
-
* The transaction is committed if the callback completes successfully,
|
|
65
|
-
* or rolled back if an error occurs.
|
|
66
|
-
* @param callback The callback function to run within the transaction context.
|
|
67
|
-
* @param tx The transaction to use. If not provided, a new transaction is created.
|
|
68
|
-
* @returns The result of the callback function.
|
|
69
|
-
*/
|
|
70
|
-
private runWithDefault;
|
|
71
|
-
/**
|
|
72
|
-
* Retrieves metadata from the shard.
|
|
73
|
-
* @returns Metadata of the shard.
|
|
74
|
-
*/
|
|
75
|
-
getMetadata(): Promise<ShardMetadata>;
|
|
76
|
-
/**
|
|
77
|
-
* Inserts data. Returns the PK of the added row.
|
|
78
|
-
* @param data Data to add
|
|
79
|
-
* @param incrementRowCount Whether to increment the row count to metadata
|
|
80
|
-
* @param tx Transaction
|
|
81
|
-
* @returns PK of the added data
|
|
82
|
-
*/
|
|
83
|
-
insert(data: string | Uint8Array, incrementRowCount?: boolean, tx?: Transaction): Promise<number>;
|
|
84
|
-
/**
|
|
85
|
-
* Inserts multiple data in batch.
|
|
86
|
-
* If a transaction is not provided, it internally creates a single transaction to process.
|
|
87
|
-
* @param dataList Array of data to add
|
|
88
|
-
* @param incrementRowCount Whether to increment the row count to metadata
|
|
89
|
-
* @param tx Transaction
|
|
90
|
-
* @returns Array of PKs of the added data
|
|
91
|
-
*/
|
|
92
|
-
insertBatch(dataList: (string | Uint8Array)[], incrementRowCount?: boolean, tx?: Transaction): Promise<number[]>;
|
|
93
|
-
/**
|
|
94
|
-
* Updates data.
|
|
95
|
-
* @param pk PK of the data to update
|
|
96
|
-
* @param data Data to update
|
|
97
|
-
* @param tx Transaction
|
|
98
|
-
*/
|
|
99
|
-
update(pk: number, data: string | Uint8Array, tx?: Transaction): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* Deletes data.
|
|
102
|
-
* @param pk PK of the data to delete
|
|
103
|
-
* @param decrementRowCount Whether to decrement the row count to metadata
|
|
104
|
-
* @param tx Transaction
|
|
105
|
-
*/
|
|
106
|
-
delete(pk: number, decrementRowCount?: boolean, tx?: Transaction): Promise<void>;
|
|
107
|
-
/**
|
|
108
|
-
* Selects data.
|
|
109
|
-
* @param pk PK of the data to select
|
|
110
|
-
* @param asRaw Whether to return the selected data as raw
|
|
111
|
-
* @param tx Transaction
|
|
112
|
-
* @returns Selected data
|
|
113
|
-
*/
|
|
114
|
-
select(pk: number, asRaw: true, tx?: Transaction): Promise<Uint8Array | null>;
|
|
115
|
-
select(pk: number, asRaw: false, tx?: Transaction): Promise<string | null>;
|
|
116
|
-
select(pk: number, asRaw?: boolean, tx?: Transaction): Promise<string | null>;
|
|
117
|
-
/**
|
|
118
|
-
* Closes the shard file.
|
|
119
|
-
*/
|
|
120
|
-
close(): Promise<void>;
|
|
121
|
-
}
|