@based/db 0.0.58 → 0.0.59

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.
Binary file
Binary file
package/dist/src/hooks.js CHANGED
@@ -14,12 +14,11 @@ export const getDefaultHooks = (server, subInterval = 200) => {
14
14
  onData(res);
15
15
  }
16
16
  else if (res.byteLength === 1 && res[0] === 0) {
17
- console.info('schema mismatch, should resolve after update');
18
- // ignore update and stop polling
17
+ server.emit('info', `[${displayTarget(q.def)}] Subscribe schema mismatch - should resolve after update`);
19
18
  return;
20
19
  }
21
20
  else {
22
- const def = this.def;
21
+ const def = q.def;
23
22
  let name = picocolors.red(`QueryError[${displayTarget(def)}]\n`);
24
23
  name += ` Incorrect buffer received in subscription (maybe server not started ${res.byteLength}) bytes\n`;
25
24
  onError(new Error(name));
@@ -4,6 +4,7 @@ import { DbServer } from './server/index.js';
4
4
  import { DbClient } from './client/index.js';
5
5
  import { getDefaultHooks } from './hooks.js';
6
6
  import { Emitter } from './shared/Emitter.js';
7
+ import { BasedDbOpts } from './types.js';
7
8
  export * from './client/modify/modify.js';
8
9
  export { compress, decompress };
9
10
  export { ModifyCtx };
@@ -24,12 +25,7 @@ export declare class BasedDb extends Emitter {
24
25
  server: DbServer;
25
26
  fileSystemPath: string;
26
27
  maxModifySize: number;
27
- constructor(opts: {
28
- path: string;
29
- maxModifySize?: number;
30
- debug?: boolean | 'server' | 'client';
31
- saveIntervalInSeconds?: number;
32
- });
28
+ constructor(opts: BasedDbOpts);
33
29
  create: DbClient['create'];
34
30
  copy: DbClient['copy'];
35
31
  update: DbClient['update'];
@@ -1,11 +1,12 @@
1
1
  import { Worker, MessagePort } from 'node:worker_threads';
2
2
  import { DbServer } from './index.js';
3
3
  export declare class DbWorker {
4
- constructor(address: BigInt, db: DbServer);
4
+ constructor(address: BigInt, db: DbServer, workerIndex: number);
5
5
  db: DbServer;
6
6
  channel: MessagePort;
7
7
  worker: Worker;
8
8
  resolvers: any[];
9
+ readyPromise: Promise<true>;
9
10
  callback: (resolve: any) => void;
10
11
  updateCtx(address: BigInt): Promise<void>;
11
12
  getQueryBuf(buf: Uint8Array): Promise<Uint8Array>;
@@ -5,7 +5,7 @@ const __filename = fileURLToPath(import.meta.url);
5
5
  const __dirname = dirname(__filename);
6
6
  const workerPath = join(__dirname, 'worker.js');
7
7
  export class DbWorker {
8
- constructor(address, db) {
8
+ constructor(address, db, workerIndex) {
9
9
  const { port1, port2 } = new MessageChannel();
10
10
  this.db = db;
11
11
  this.channel = port1;
@@ -17,6 +17,30 @@ export class DbWorker {
17
17
  },
18
18
  transferList: [port2],
19
19
  });
20
+ this.readyPromise = new Promise((resolve) => {
21
+ const onReady = (msg) => {
22
+ if (msg === 'READY') {
23
+ this.worker.off('message', onReady);
24
+ resolve(true);
25
+ }
26
+ };
27
+ this.worker.on('message', onReady);
28
+ });
29
+ this.worker.on('error', (err) => {
30
+ console.error('error in query worker:', err);
31
+ this.worker.terminate();
32
+ });
33
+ this.worker.on('exit', (code) => {
34
+ if (!this.db.stopped) {
35
+ console.info('unexpected exit query worker with code:', code);
36
+ const err = new Error('Worker could not process query');
37
+ for (const resolve of this.resolvers) {
38
+ resolve(err);
39
+ }
40
+ this.resolvers = [];
41
+ this.db.workers[workerIndex] = new DbWorker(address, db, workerIndex);
42
+ }
43
+ });
20
44
  port1.on('message', (buf) => {
21
45
  this.resolvers.shift()(new Uint8Array(buf));
22
46
  this.db.onQueryEnd();
@@ -26,6 +50,7 @@ export class DbWorker {
26
50
  channel;
27
51
  worker;
28
52
  resolvers = [];
53
+ readyPromise;
29
54
  callback = (resolve) => {
30
55
  this.db.processingQueries++;
31
56
  this.resolvers.push(resolve);
@@ -1,5 +1,6 @@
1
1
  import { LangName, StrictSchema } from '@based/schema';
2
2
  import { createTree } from './csmt/index.js';
3
+ import { StartOpts } from './start.js';
3
4
  import { CsmtNodeRange } from './tree.js';
4
5
  import { TransformFns } from './migrate/index.js';
5
6
  import exitHook from 'exit-hook';
@@ -17,6 +18,7 @@ export declare class DbServer extends DbShared {
17
18
  modifyDirtyRanges: Float64Array;
18
19
  dbCtxExternal: any;
19
20
  migrating: number;
21
+ saveInProgress: boolean;
20
22
  fileSystemPath: string;
21
23
  merkleTree: ReturnType<typeof createTree<CsmtNodeRange>>;
22
24
  dirtyRanges: Set<number>;
@@ -34,15 +36,13 @@ export declare class DbServer extends DbShared {
34
36
  unlistenExit: ReturnType<typeof exitHook>;
35
37
  saveIntervalInSeconds?: number;
36
38
  saveInterval?: NodeJS.Timeout;
39
+ delayInMs?: number;
37
40
  constructor({ path, debug, saveIntervalInSeconds, }: {
38
41
  path: string;
39
42
  debug?: boolean;
40
43
  saveIntervalInSeconds?: number;
41
44
  });
42
- start(opts?: {
43
- clean?: boolean;
44
- hosted?: boolean;
45
- }): Promise<void>;
45
+ start(opts?: StartOpts): Promise<void>;
46
46
  save(opts?: {
47
47
  forceFullDump?: boolean;
48
48
  }): Promise<void>;
@@ -28,6 +28,7 @@ export class DbServer extends DbShared {
28
28
  modifyDirtyRanges;
29
29
  dbCtxExternal; // pointer to zig dbCtx
30
30
  migrating = null;
31
+ saveInProgress = false;
31
32
  fileSystemPath;
32
33
  merkleTree;
33
34
  dirtyRanges = new Set();
@@ -41,6 +42,7 @@ export class DbServer extends DbShared {
41
42
  unlistenExit;
42
43
  saveIntervalInSeconds;
43
44
  saveInterval;
45
+ delayInMs;
44
46
  constructor({ path, debug, saveIntervalInSeconds, }) {
45
47
  super();
46
48
  this.fileSystemPath = path;
@@ -9,7 +9,6 @@ import { setToAwake, waitUntilSleeping } from './utils.js';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  const workerPath = join(__dirname, 'worker.js');
12
- let migrationCnt = 0;
13
12
  const parseTransform = (transform) => {
14
13
  const res = {};
15
14
  if (typeof transform === 'object' && transform !== null) {
@@ -37,13 +36,20 @@ export const migrate = async (server, fromSchema, toSchema, transform) => {
37
36
  server.emit('info', `migration killed something went wrong ${migrationId}`);
38
37
  return true;
39
38
  }
40
- server.emit('info', `abort migration - migrating: ${server.migrating} abort: ${migrationId}`);
41
- return server.migrating !== migrationId;
39
+ const newMigrationInProgress = server.migrating !== migrationId;
40
+ if (newMigrationInProgress) {
41
+ server.emit('info', `abort migration - migrating: ${server.migrating} abort: ${migrationId}`);
42
+ }
43
+ return newMigrationInProgress;
42
44
  };
43
45
  const tmpDb = new BasedDb({
44
46
  path: null,
45
47
  });
46
- await tmpDb.start({ clean: true });
48
+ await tmpDb.start({
49
+ clean: true,
50
+ delayInMs: server.delayInMs,
51
+ queryThreads: 0,
52
+ });
47
53
  if (abort()) {
48
54
  await tmpDb.destroy();
49
55
  return;
@@ -27,95 +27,128 @@ export function save(db, sync = false, forceFullDump = false, skipMigrationCheck
27
27
  return;
28
28
  }
29
29
  if (db.migrating && !skipMigrationCheck) {
30
+ db.emit('info', 'Block save db is migrating');
30
31
  return;
31
32
  }
32
- let err;
33
- const ts = Date.now();
34
- err = native.saveCommon(join(db.fileSystemPath, COMMON_SDB_FILE), db.dbCtxExternal);
35
- if (err) {
36
- console.error(`Save common failed: ${err}`);
33
+ if (db.saveInProgress) {
34
+ db.emit('info', 'Already have a save in progress cancel save');
35
+ return;
37
36
  }
38
- if (forceFullDump) {
39
- // We just rebuild the whole tree
40
- initCsmt(db);
41
- for (const key in db.schemaTypesParsed) {
42
- const def = db.schemaTypesParsed[key];
43
- foreachBlock(db, def, (start, end, _hash) => {
44
- const typeId = def.id;
45
- const mtKey = makeCsmtKey(typeId, start);
37
+ let ts = Date.now();
38
+ db.saveInProgress = true;
39
+ try {
40
+ let err;
41
+ err = native.saveCommon(join(db.fileSystemPath, COMMON_SDB_FILE), db.dbCtxExternal);
42
+ if (err) {
43
+ console.error(`Save common failed: ${err}`);
44
+ // Return ?
45
+ }
46
+ if (forceFullDump) {
47
+ // We just rebuild the whole tree
48
+ initCsmt(db);
49
+ for (const key in db.schemaTypesParsed) {
50
+ const def = db.schemaTypesParsed[key];
51
+ foreachBlock(db, def, (start, end, _hash) => {
52
+ const typeId = def.id;
53
+ const mtKey = makeCsmtKey(typeId, start);
54
+ const hash = new Uint8Array(16);
55
+ const file = saveRange(db, typeId, start, end, hash);
56
+ if (file === null) {
57
+ throw new Error('full dump failed');
58
+ }
59
+ else {
60
+ const data = {
61
+ file,
62
+ typeId,
63
+ start,
64
+ end,
65
+ };
66
+ db.merkleTree.insert(mtKey, hash, data);
67
+ }
68
+ });
69
+ }
70
+ }
71
+ else {
72
+ foreachDirtyBlock(db, (mtKey, typeId, start, end) => {
46
73
  const hash = new Uint8Array(16);
47
74
  const file = saveRange(db, typeId, start, end, hash);
48
75
  if (file === null) {
49
- throw new Error('full dump failed');
76
+ // The previous state should remain in the merkle tree for
77
+ // load and sync purposes.
78
+ return;
50
79
  }
51
80
  else {
52
- const data = {
53
- file,
54
- typeId,
55
- start,
56
- end,
57
- };
58
- db.merkleTree.insert(mtKey, hash, data);
81
+ const oldLeaf = db.merkleTree.search(mtKey);
82
+ // If (file.length === 0) then the range is empty but that's fine,
83
+ // we'll keep them around for now to maintain the order of the tree.
84
+ if (oldLeaf) {
85
+ oldLeaf.data.file = file;
86
+ db.merkleTree.update(mtKey, hash);
87
+ }
88
+ else {
89
+ const data = {
90
+ file,
91
+ typeId,
92
+ start,
93
+ end,
94
+ };
95
+ db.merkleTree.insert(mtKey, hash, data);
96
+ }
59
97
  }
60
98
  });
61
99
  }
62
- }
63
- else {
64
- foreachDirtyBlock(db, (mtKey, typeId, start, end) => {
65
- const hash = new Uint8Array(16);
66
- const file = saveRange(db, typeId, start, end, hash);
67
- if (file === null) {
68
- // The previous state should remain in the merkle tree for
69
- // load and sync purposes.
70
- return;
71
- }
72
- else {
73
- const oldLeaf = db.merkleTree.search(mtKey);
74
- // If (file.length === 0) then the range is empty but that's fine,
75
- // we'll keep them around for now to maintain the order of the tree.
76
- if (oldLeaf) {
77
- oldLeaf.data.file = file;
78
- db.merkleTree.update(mtKey, hash);
79
- }
80
- else {
81
- const data = {
82
- file,
83
- typeId,
84
- start,
85
- end,
86
- };
87
- db.merkleTree.insert(mtKey, hash, data);
88
- }
100
+ db.dirtyRanges.clear();
101
+ const types = {};
102
+ const rangeDumps = {};
103
+ for (const key in db.schemaTypesParsed) {
104
+ const { id, lastId, blockCapacity } = db.schemaTypesParsed[key];
105
+ types[id] = { lastId, blockCapacity };
106
+ rangeDumps[id] = [];
107
+ }
108
+ db.merkleTree.visitLeafNodes((leaf) => {
109
+ const [typeId, start] = destructureCsmtKey(leaf.key);
110
+ if (start == specialBlock)
111
+ return; // skip the type specialBlock
112
+ const data = leaf.data;
113
+ if (start != data.start) {
114
+ console.error(`csmtKey start and range start mismatch: ${start} != ${data.start}`);
89
115
  }
116
+ rangeDumps[typeId].push({ ...data, hash: bufToHex(leaf.hash) });
90
117
  });
118
+ const data = {
119
+ ts,
120
+ types,
121
+ commonDump: COMMON_SDB_FILE,
122
+ rangeDumps,
123
+ hash: bufToHex(db.merkleTree.getRoot()?.hash ?? new Uint8Array(0)),
124
+ };
125
+ const filePath = join(db.fileSystemPath, WRITELOG_FILE);
126
+ const content = JSON.stringify(data);
127
+ db.emit('info', `Save done took ${Date.now() - ts}ms`);
128
+ if (sync) {
129
+ db.saveInProgress = false;
130
+ return writeFileSync(filePath, content);
131
+ }
132
+ else {
133
+ return new Promise((resolve, reject) => {
134
+ return writeFile(filePath, content)
135
+ .then((v) => {
136
+ db.saveInProgress = false;
137
+ resolve(v);
138
+ })
139
+ .catch((err) => {
140
+ console.error('Save: writing writeLog failed');
141
+ db.emit('info', `Save: writing writeLog failed ${err.message}`);
142
+ db.saveInProgress = false;
143
+ reject(err);
144
+ });
145
+ });
146
+ }
91
147
  }
92
- db.dirtyRanges.clear();
93
- const types = {};
94
- const rangeDumps = {};
95
- for (const key in db.schemaTypesParsed) {
96
- const { id, lastId, blockCapacity } = db.schemaTypesParsed[key];
97
- types[id] = { lastId, blockCapacity };
98
- rangeDumps[id] = [];
148
+ catch (err) {
149
+ db.emit('info', `Save failed ${err.message}`);
150
+ db.saveInProgress = false;
151
+ throw err;
99
152
  }
100
- db.merkleTree.visitLeafNodes((leaf) => {
101
- const [typeId, start] = destructureCsmtKey(leaf.key);
102
- if (start == specialBlock)
103
- return; // skip the type specialBlock
104
- const data = leaf.data;
105
- if (start != data.start) {
106
- console.error(`csmtKey start and range start mismatch: ${start} != ${data.start}`);
107
- }
108
- rangeDumps[typeId].push({ ...data, hash: bufToHex(leaf.hash) });
109
- });
110
- const data = {
111
- ts,
112
- types,
113
- commonDump: COMMON_SDB_FILE,
114
- rangeDumps,
115
- hash: bufToHex(db.merkleTree.getRoot()?.hash ?? new Uint8Array(0)),
116
- };
117
- const filePath = join(db.fileSystemPath, WRITELOG_FILE);
118
- const content = JSON.stringify(data);
119
- return sync ? writeFileSync(filePath, content) : writeFile(filePath, content);
120
153
  }
121
154
  //# sourceMappingURL=save.js.map
@@ -1,5 +1,8 @@
1
1
  import { DbServer } from './index.js';
2
- export declare function start(db: DbServer, opts: {
2
+ export type StartOpts = {
3
3
  clean?: boolean;
4
4
  hosted?: boolean;
5
- }): Promise<void>;
5
+ delayInMs?: number;
6
+ queryThreads?: number;
7
+ };
8
+ export declare function start(db: DbServer, opts: StartOpts): Promise<void>;
@@ -7,7 +7,7 @@ import { availableParallelism } from 'node:os';
7
7
  import exitHook from 'exit-hook';
8
8
  import { save } from './save.js';
9
9
  import { DEFAULT_BLOCK_CAPACITY } from '@based/schema/def';
10
- import { bufToHex } from '@saulx/utils';
10
+ import { bufToHex, wait } from '@saulx/utils';
11
11
  import { SCHEMA_FILE, WRITELOG_FILE } from '../types.js';
12
12
  import { setSchemaOnServer } from './schema.js';
13
13
  export async function start(db, opts) {
@@ -46,9 +46,8 @@ export async function start(db, opts) {
46
46
  }
47
47
  const schema = await readFile(join(path, SCHEMA_FILE));
48
48
  if (schema) {
49
- // Prop need to not call setting in selva
50
- setSchemaOnServer(db, JSON.parse(schema.toString()));
51
- // setNativeSchema might still be nessecary...
49
+ const s = JSON.parse(schema.toString());
50
+ setSchemaOnServer(db, s);
52
51
  }
53
52
  }
54
53
  catch (err) {
@@ -58,7 +57,6 @@ export async function start(db, opts) {
58
57
  for (const key in csmtTypes) {
59
58
  const def = csmtTypes[key];
60
59
  const [total, lastId] = native.getTypeInfo(def.id, db.dbCtxExternal);
61
- def.total = total;
62
60
  def.lastId = writelog?.types[def.id]?.lastId || lastId;
63
61
  def.blockCapacity =
64
62
  writelog?.types[def.id]?.blockCapacity || DEFAULT_BLOCK_CAPACITY;
@@ -100,11 +98,11 @@ export async function start(db, opts) {
100
98
  }
101
99
  }
102
100
  // start workers
103
- let i = availableParallelism();
101
+ const queryThreads = opts?.queryThreads ?? availableParallelism();
104
102
  const address = native.intFromExternal(db.dbCtxExternal);
105
- db.workers = new Array(i);
106
- while (i--) {
107
- db.workers[i] = new DbWorker(address, db);
103
+ db.workers = [];
104
+ for (let i = 0; i < queryThreads; i++) {
105
+ db.workers.push(new DbWorker(address, db, i));
108
106
  }
109
107
  if (!opts?.hosted) {
110
108
  db.unlistenExit = exitHook((signal) => {
@@ -114,12 +112,16 @@ export async function start(db, opts) {
114
112
  // This is needed because there is no way to set the process signal mask
115
113
  // in Node.js.
116
114
  signals.forEach((sig) => process.on(sig, blockSig));
117
- console.log(`Exiting with signal: ${signal}`);
115
+ db.emit('info', `Exiting with signal: ${signal}`);
118
116
  save(db, true);
119
- console.log('Successfully saved.');
117
+ db.emit('info', 'Successfully saved.');
120
118
  signals.forEach((sig) => process.off(sig, blockSig));
121
119
  });
122
120
  }
121
+ const d = performance.now();
122
+ await Promise.all(db.workers.map(({ readyPromise }) => readyPromise));
123
+ db.emit('info', `Starting workers took ${d}ms`);
124
+ // use timeout
123
125
  if (db.saveIntervalInSeconds > 0) {
124
126
  db.saveInterval ??= setInterval(() => {
125
127
  save(db);
@@ -128,5 +130,9 @@ export async function start(db, opts) {
128
130
  if (db.schema) {
129
131
  db.emit('schema', db.schema);
130
132
  }
133
+ if (opts?.delayInMs) {
134
+ this.delayInMs = opts.delayInMs;
135
+ await wait(opts.delayInMs);
136
+ }
131
137
  }
132
138
  //# sourceMappingURL=start.js.map
@@ -1,4 +1,4 @@
1
- import { isMainThread, receiveMessageOnPort, workerData, } from 'node:worker_threads';
1
+ import { isMainThread, parentPort, workerData } from 'node:worker_threads';
2
2
  import native from '../native.js';
3
3
  if (isMainThread) {
4
4
  console.warn('running query worker.ts in mainthread - incorrect');
@@ -7,7 +7,6 @@ else if (workerData?.isDbWorker) {
7
7
  let { address, channel } = workerData;
8
8
  let dbCtx = native.externalFromInt(address);
9
9
  native.workerCtxInit();
10
- // const transferList = new Array(1)
11
10
  const handleMsg = (msg) => {
12
11
  try {
13
12
  if (typeof msg === 'bigint') {
@@ -18,7 +17,6 @@ else if (workerData?.isDbWorker) {
18
17
  }
19
18
  else {
20
19
  const arrayBuf = native.getQueryBuf(msg, dbCtx);
21
- // transferList[0] = arrayBuf
22
20
  channel.postMessage(arrayBuf, [arrayBuf]);
23
21
  }
24
22
  }
@@ -27,10 +25,7 @@ else if (workerData?.isDbWorker) {
27
25
  }
28
26
  };
29
27
  channel.on('message', handleMsg);
30
- const msg = receiveMessageOnPort(channel);
31
- if (msg) {
32
- handleMsg(msg.message);
33
- }
28
+ parentPort.postMessage('READY');
34
29
  }
35
30
  else {
36
31
  console.info('incorrect worker db query');
@@ -1,3 +1,9 @@
1
1
  export declare const SCHEMA_FILE = "schema.json";
2
2
  export declare const WRITELOG_FILE = "writelog.json";
3
3
  export declare const COMMON_SDB_FILE = "common.sdb";
4
+ export type BasedDbOpts = {
5
+ path: string;
6
+ maxModifySize?: number;
7
+ debug?: boolean | 'server' | 'client';
8
+ saveIntervalInSeconds?: number;
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@based/db",
3
- "version": "0.0.58",
3
+ "version": "0.0.59",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",