@optimystic/db-core 0.5.2 → 0.7.0
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/src/btree/btree.d.ts +2 -0
- package/dist/src/btree/btree.d.ts.map +1 -1
- package/dist/src/btree/btree.js +72 -52
- package/dist/src/btree/btree.js.map +1 -1
- package/dist/src/cluster/structs.d.ts +13 -0
- package/dist/src/cluster/structs.d.ts.map +1 -1
- package/dist/src/collection/collection.d.ts +10 -0
- package/dist/src/collection/collection.d.ts.map +1 -1
- package/dist/src/collection/collection.js +34 -0
- package/dist/src/collection/collection.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/log/log.js +1 -1
- package/dist/src/log/log.js.map +1 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +8 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/transaction/coordinator.d.ts +31 -8
- package/dist/src/transaction/coordinator.d.ts.map +1 -1
- package/dist/src/transaction/coordinator.js +206 -53
- package/dist/src/transaction/coordinator.js.map +1 -1
- package/dist/src/transaction/index.d.ts +2 -2
- package/dist/src/transaction/index.d.ts.map +1 -1
- package/dist/src/transaction/index.js +1 -1
- package/dist/src/transaction/index.js.map +1 -1
- package/dist/src/transaction/session.d.ts +11 -7
- package/dist/src/transaction/session.d.ts.map +1 -1
- package/dist/src/transaction/session.js +27 -14
- package/dist/src/transaction/session.js.map +1 -1
- package/dist/src/transaction/transaction.d.ts +9 -3
- package/dist/src/transaction/transaction.d.ts.map +1 -1
- package/dist/src/transaction/transaction.js +14 -7
- package/dist/src/transaction/transaction.js.map +1 -1
- package/dist/src/transaction/validator.d.ts +9 -2
- package/dist/src/transaction/validator.d.ts.map +1 -1
- package/dist/src/transaction/validator.js +26 -6
- package/dist/src/transaction/validator.js.map +1 -1
- package/dist/src/transactor/network-transactor.d.ts.map +1 -1
- package/dist/src/transactor/network-transactor.js +84 -9
- package/dist/src/transactor/network-transactor.js.map +1 -1
- package/dist/src/transactor/transactor-source.d.ts +4 -0
- package/dist/src/transactor/transactor-source.d.ts.map +1 -1
- package/dist/src/transactor/transactor-source.js +25 -9
- package/dist/src/transactor/transactor-source.js.map +1 -1
- package/dist/src/transform/atomic-proxy.d.ts +26 -0
- package/dist/src/transform/atomic-proxy.d.ts.map +1 -0
- package/dist/src/transform/atomic-proxy.js +47 -0
- package/dist/src/transform/atomic-proxy.js.map +1 -0
- package/dist/src/transform/cache-source.d.ts +3 -2
- package/dist/src/transform/cache-source.d.ts.map +1 -1
- package/dist/src/transform/cache-source.js +15 -3
- package/dist/src/transform/cache-source.js.map +1 -1
- package/dist/src/transform/index.d.ts +1 -0
- package/dist/src/transform/index.d.ts.map +1 -1
- package/dist/src/transform/index.js +1 -0
- package/dist/src/transform/index.js.map +1 -1
- package/dist/src/utility/batch-coordinator.d.ts +2 -0
- package/dist/src/utility/batch-coordinator.d.ts.map +1 -1
- package/dist/src/utility/batch-coordinator.js +6 -1
- package/dist/src/utility/batch-coordinator.js.map +1 -1
- package/dist/src/utility/hash-string.d.ts +3 -6
- package/dist/src/utility/hash-string.d.ts.map +1 -1
- package/dist/src/utility/hash-string.js +8 -11
- package/dist/src/utility/hash-string.js.map +1 -1
- package/dist/src/utility/lru-map.d.ts +18 -0
- package/dist/src/utility/lru-map.d.ts.map +1 -0
- package/dist/src/utility/lru-map.js +52 -0
- package/dist/src/utility/lru-map.js.map +1 -0
- package/package.json +15 -8
- package/src/btree/btree.ts +71 -50
- package/src/cluster/structs.ts +11 -0
- package/src/collection/collection.ts +44 -0
- package/src/index.ts +1 -0
- package/src/log/log.ts +1 -1
- package/src/logger.ts +10 -0
- package/src/transaction/coordinator.ts +244 -57
- package/src/transaction/index.ts +4 -2
- package/src/transaction/session.ts +38 -14
- package/src/transaction/transaction.ts +23 -10
- package/src/transaction/validator.ts +34 -7
- package/src/transactor/network-transactor.ts +94 -13
- package/src/transactor/transactor-source.ts +28 -9
- package/src/transform/atomic-proxy.ts +49 -0
- package/src/transform/cache-source.ts +18 -4
- package/src/transform/index.ts +1 -0
- package/src/utility/batch-coordinator.ts +9 -1
- package/src/utility/hash-string.ts +14 -17
- package/src/utility/lru-map.ts +55 -0
- package/dist/index.min.js +0 -9
- package/dist/index.min.js.map +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimystic/db-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core database functionality for Optimystic",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./dist/src/index.d.ts",
|
|
17
17
|
"import": "./dist/src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./test": {
|
|
20
|
+
"types": "./dist/test/test-transactor.d.ts",
|
|
21
|
+
"import": "./dist/test/test-transactor.js"
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
"repository": {
|
|
@@ -36,12 +40,9 @@
|
|
|
36
40
|
"acid"
|
|
37
41
|
],
|
|
38
42
|
"scripts": {
|
|
39
|
-
"clean": "
|
|
40
|
-
"build": "
|
|
41
|
-
"
|
|
42
|
-
"test": "aegir test",
|
|
43
|
-
"test:node": "aegir test -t node",
|
|
44
|
-
"dep-check": "aegir dep-check"
|
|
43
|
+
"clean": "rimraf dist",
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"test": "node --import ./register.mjs node_modules/mocha/bin/mocha.js \"test/**/*.spec.ts\" --colors --reporter min"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@libp2p/crypto": "^5.1.13",
|
|
@@ -49,9 +50,15 @@
|
|
|
49
50
|
"@libp2p/peer-id": "^6.0.4",
|
|
50
51
|
"@libp2p/peer-id-factory": "^4.2.4",
|
|
51
52
|
"@multiformats/multiaddr": "^13.0.1",
|
|
53
|
+
"@types/chai-as-promised": "^8.0.2",
|
|
54
|
+
"@types/debug": "^4.1.12",
|
|
52
55
|
"@types/mocha": "^10.0.10",
|
|
53
56
|
"@types/node": "^25.1.0",
|
|
54
|
-
"
|
|
57
|
+
"chai": "^6.2.2",
|
|
58
|
+
"chai-as-promised": "^8.0.2",
|
|
59
|
+
"mocha": "^11.7.5",
|
|
60
|
+
"rimraf": "^6.1.2",
|
|
61
|
+
"ts-node": "^10.9.2",
|
|
55
62
|
"typescript": "^5.9.3"
|
|
56
63
|
},
|
|
57
64
|
"dependencies": {
|
package/src/btree/btree.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { apply, get } from "../blocks/index.js";
|
|
|
4
4
|
import { TreeLeafBlockType, TreeBranchBlockType, entries$, nodes$, partitions$ } from "./nodes.js";
|
|
5
5
|
import type { BranchNode, ITreeNode, LeafNode } from "./nodes.js";
|
|
6
6
|
import type { TreeBlock } from "./tree-block.js";
|
|
7
|
+
import { AtomicProxy } from "../transform/atomic-proxy.js";
|
|
7
8
|
|
|
8
9
|
export const NodeCapacity = 64;
|
|
9
10
|
|
|
@@ -15,6 +16,7 @@ export const NodeCapacity = 64;
|
|
|
15
16
|
*/
|
|
16
17
|
export class BTree<TKey, TEntry> {
|
|
17
18
|
protected _version = 0; // only for path invalidation
|
|
19
|
+
private _proxy?: AtomicProxy<ITreeNode>;
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* @param [compare=(a: TKey, b: TKey) => a < b ? -1 : a > b ? 1 : 0] a comparison function for keys. The default uses < and > operators.
|
|
@@ -28,6 +30,10 @@ export class BTree<TKey, TEntry> {
|
|
|
28
30
|
) {
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
private atomic<T>(fn: () => Promise<T>): Promise<T> {
|
|
34
|
+
return this._proxy ? this._proxy.atomic(fn) : fn();
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
static createRoot(
|
|
32
38
|
store: BlockStore<ITreeNode>
|
|
33
39
|
) {
|
|
@@ -41,10 +47,13 @@ export class BTree<TKey, TEntry> {
|
|
|
41
47
|
compare = (a: TKey, b: TKey) => a < b ? -1 : a > b ? 1 : 0,
|
|
42
48
|
newId?: BlockId,
|
|
43
49
|
) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const proxy = new AtomicProxy(store);
|
|
51
|
+
const root = BTree.createRoot(proxy as BlockStore<TreeBlock>);
|
|
52
|
+
proxy.insert(root);
|
|
53
|
+
const trunk = createTrunk(proxy as BlockStore<TreeBlock>, root.header.id, newId);
|
|
54
|
+
const tree = new BTree(proxy, trunk, keyFromEntry, compare);
|
|
55
|
+
tree._proxy = proxy;
|
|
56
|
+
return tree;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/** @returns a path to the first entry (on = false if no entries) */
|
|
@@ -118,12 +127,14 @@ export class BTree<TKey, TEntry> {
|
|
|
118
127
|
* Added entries are frozen to ensure immutability
|
|
119
128
|
* @returns path to the new (on = true) or conflicting (on = false) row. */
|
|
120
129
|
async insert(entry: TEntry): Promise<Path<TKey, TEntry>> {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
path.
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
return this.atomic(async () => {
|
|
131
|
+
Object.freeze(entry); // Ensure immutability
|
|
132
|
+
const path = await this.internalInsert(entry);
|
|
133
|
+
if (path.on) {
|
|
134
|
+
path.version = ++this._version;
|
|
135
|
+
}
|
|
136
|
+
return path;
|
|
137
|
+
});
|
|
127
138
|
}
|
|
128
139
|
|
|
129
140
|
/** Updates the entry at the given path to the given value. Deletes and inserts if the key changes.
|
|
@@ -135,30 +146,34 @@ export class BTree<TKey, TEntry> {
|
|
|
135
146
|
* * wasUpdate = true, given path is not on an entry
|
|
136
147
|
* * else newEntry's new key already present; returned path is "near" existing entry */
|
|
137
148
|
async updateAt(path: Path<TKey, TEntry>, newEntry: TEntry): Promise<[path: Path<TKey, TEntry>, wasUpdate: boolean]> {
|
|
138
|
-
this.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
result[0].
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
return this.atomic(async () => {
|
|
150
|
+
this.validatePath(path);
|
|
151
|
+
if (path.on) {
|
|
152
|
+
Object.freeze(newEntry);
|
|
153
|
+
}
|
|
154
|
+
const result = await this.internalUpdate(path, newEntry);
|
|
155
|
+
if (result[0].on) {
|
|
156
|
+
result[0].version = ++this._version;
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
});
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
/** Inserts the entry if it doesn't exist, or updates it if it does.
|
|
150
163
|
* The entry is frozen to ensure immutability.
|
|
151
164
|
* @returns path to the new entry. on = true if existing; on = false if new. */
|
|
152
165
|
async upsert(entry: TEntry): Promise<Path<TKey, TEntry>> {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
return this.atomic(async () => {
|
|
167
|
+
const path = await this.find(this.keyFromEntry(entry));
|
|
168
|
+
Object.freeze(entry);
|
|
169
|
+
if (path.on) {
|
|
170
|
+
this.updateEntry(path, entry);
|
|
171
|
+
} else {
|
|
172
|
+
await this.internalInsertAt(path, entry);
|
|
173
|
+
}
|
|
174
|
+
path.version = ++this._version;
|
|
175
|
+
return path;
|
|
176
|
+
});
|
|
162
177
|
}
|
|
163
178
|
|
|
164
179
|
/** Inserts or updates depending on the existence of the given key, using callbacks to generate the new value.
|
|
@@ -167,18 +182,20 @@ export class BTree<TKey, TEntry> {
|
|
|
167
182
|
* @returns path to new entry and whether an update or insert attempted.
|
|
168
183
|
* If getUpdated callback returns a row that is already present, the resulting path will not be on. */
|
|
169
184
|
async merge(newEntry: TEntry, getUpdated: (existing: TEntry) => TEntry): Promise<[path: Path<TKey, TEntry>, wasUpdate: boolean]> {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
185
|
+
return this.atomic(async () => {
|
|
186
|
+
const newKey = await this.keyFromEntry(newEntry);
|
|
187
|
+
const path = await this.find(newKey);
|
|
188
|
+
if (path.on) {
|
|
189
|
+
const result = await this.updateAt(path, getUpdated(this.getEntry(path))); // Don't use internalUpdate - need to freeze and check for mutation
|
|
190
|
+
// Note: updateAt already increments version, so don't double-increment here
|
|
191
|
+
return result;
|
|
192
|
+
} else {
|
|
193
|
+
await this.internalInsertAt(path, Object.freeze(newEntry));
|
|
194
|
+
path.on = true;
|
|
195
|
+
path.version = ++this._version;
|
|
196
|
+
return [path, false];
|
|
197
|
+
}
|
|
198
|
+
});
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
/** Deletes the entry at the given path.
|
|
@@ -186,19 +203,23 @@ export class BTree<TKey, TEntry> {
|
|
|
186
203
|
* @returns true if the delete succeeded (the key was found); false otherwise.
|
|
187
204
|
*/
|
|
188
205
|
async deleteAt(path: Path<TKey, TEntry>): Promise<boolean> {
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
206
|
+
return this.atomic(async () => {
|
|
207
|
+
this.validatePath(path);
|
|
208
|
+
const result = await this.internalDelete(path);
|
|
209
|
+
if (result) {
|
|
210
|
+
++this._version;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
});
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
async drop() { // Node: only when a root treeBlock
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
this.
|
|
201
|
-
|
|
217
|
+
return this.atomic(async () => {
|
|
218
|
+
const root = await this.trunk.get();
|
|
219
|
+
for await (const id of this.nodeIds(root)) {
|
|
220
|
+
this.store.delete(id);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
202
223
|
}
|
|
203
224
|
|
|
204
225
|
/** Iterates forward starting from the path location (inclusive) to the end.
|
package/src/cluster/structs.ts
CHANGED
|
@@ -27,6 +27,13 @@ export type ClusterRecord = {
|
|
|
27
27
|
networkSizeHint?: number;
|
|
28
28
|
/** Confidence in the network size estimate (0-1) */
|
|
29
29
|
networkSizeConfidence?: number;
|
|
30
|
+
/** Transaction proceeded despite minority rejections */
|
|
31
|
+
disputed?: boolean;
|
|
32
|
+
/** Evidence of the dispute: which peers rejected and why */
|
|
33
|
+
disputeEvidence?: {
|
|
34
|
+
rejectingPeers: string[];
|
|
35
|
+
rejectReasons: { [peerId: string]: string };
|
|
36
|
+
};
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export interface ClusterConsensusConfig {
|
|
@@ -42,4 +49,8 @@ export interface ClusterConsensusConfig {
|
|
|
42
49
|
clusterSizeTolerance: number;
|
|
43
50
|
/** Window for detecting partition in milliseconds (default 60000 = 1 min) */
|
|
44
51
|
partitionDetectionWindow: number;
|
|
52
|
+
/** Enable dispute escalation protocol (default false) */
|
|
53
|
+
disputeEnabled?: boolean;
|
|
54
|
+
/** Timeout for dispute arbitration in milliseconds (default 60000) */
|
|
55
|
+
disputeArbitrationTimeoutMs?: number;
|
|
45
56
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IBlock, Action, ActionType, ActionHandler, BlockId, ITransactor, BlockStore } from "../index.js";
|
|
2
2
|
import { Log, Atomic, Tracker, copyTransforms, CacheSource, isTransformsEmpty, TransactorSource } from "../index.js";
|
|
3
3
|
import type { CollectionHeaderBlock, CollectionId, ICollection } from "./index.js";
|
|
4
|
+
import type { ReadDependency } from "../transaction/transaction.js";
|
|
4
5
|
import { randomBytes } from '@noble/hashes/utils.js';
|
|
5
6
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
|
|
6
7
|
import { Latches } from "../utility/latches.js";
|
|
@@ -45,6 +46,10 @@ export class Collection<TAction> implements ICollection<TAction> {
|
|
|
45
46
|
const header = await source.tryGet(id) as CollectionHeaderBlock | undefined;
|
|
46
47
|
|
|
47
48
|
if (header) { // Collection already exists
|
|
49
|
+
// Bootstrap ActionContext from the committed tail before walking the chain.
|
|
50
|
+
// This allows the transactor to serve pending non-tail blocks during Log.open.
|
|
51
|
+
await Collection.bootstrapContext(source, transactor, header);
|
|
52
|
+
|
|
48
53
|
const log = (await Log.open<Action<TAction>>(tracker, id))!;
|
|
49
54
|
source.actionContext = await log.getActionContext();
|
|
50
55
|
} else { // Collection does not exist
|
|
@@ -100,6 +105,13 @@ export class Collection<TAction> implements ICollection<TAction> {
|
|
|
100
105
|
const source = new TransactorSource(this.id, this.transactor, undefined);
|
|
101
106
|
const tracker = new Tracker(source);
|
|
102
107
|
|
|
108
|
+
// Bootstrap context from committed tail so pending blocks are accessible.
|
|
109
|
+
// Read through tracker so Chain.open inside Log.open reuses the cached header.
|
|
110
|
+
const header = await tracker.tryGet(this.id);
|
|
111
|
+
if (header) {
|
|
112
|
+
await Collection.bootstrapContext(source, this.transactor, header);
|
|
113
|
+
}
|
|
114
|
+
|
|
103
115
|
// Get the latest entries from the log, starting from where we left off
|
|
104
116
|
const actionContext = this.source.actionContext;
|
|
105
117
|
const log = await Log.open<Action<TAction>>(tracker, this.id);
|
|
@@ -207,6 +219,14 @@ export class Collection<TAction> implements ICollection<TAction> {
|
|
|
207
219
|
}
|
|
208
220
|
}
|
|
209
221
|
|
|
222
|
+
getReadDependencies(): ReadDependency[] {
|
|
223
|
+
return this.source.getReadDependencies();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
clearReadDependencies(): void {
|
|
227
|
+
this.source.clearReadDependencies();
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
/** Called for each local action that may be in conflict with a remote action (always called under latch).
|
|
211
231
|
* @param action - The local action to check
|
|
212
232
|
* @param potential - The remote action that is potentially in conflict
|
|
@@ -224,4 +244,28 @@ export class Collection<TAction> implements ICollection<TAction> {
|
|
|
224
244
|
}
|
|
225
245
|
return true;
|
|
226
246
|
}
|
|
247
|
+
|
|
248
|
+
/** Bootstrap ActionContext from the committed tail block's state.
|
|
249
|
+
* The tail is always committed first (commit protocol guarantee), so it's readable
|
|
250
|
+
* with context=undefined. Its state.latest contains the ActionRev of the most recent
|
|
251
|
+
* committed action — exactly the proof needed for the transactor to serve pending
|
|
252
|
+
* non-tail blocks during chain walks.
|
|
253
|
+
*/
|
|
254
|
+
private static async bootstrapContext(
|
|
255
|
+
source: TransactorSource<IBlock>,
|
|
256
|
+
transactor: ITransactor,
|
|
257
|
+
header: IBlock,
|
|
258
|
+
): Promise<void> {
|
|
259
|
+
const tailId = Object.hasOwn(header, 'tailId') ? (header as any).tailId as BlockId : undefined;
|
|
260
|
+
if (tailId) {
|
|
261
|
+
const tailResult = await transactor.get({ blockIds: [tailId] });
|
|
262
|
+
const tailState = tailResult?.[tailId]?.state;
|
|
263
|
+
if (tailState?.latest) {
|
|
264
|
+
source.actionContext = {
|
|
265
|
+
committed: [{ actionId: tailState.latest.actionId, rev: tailState.latest.rev }],
|
|
266
|
+
rev: tailState.latest.rev,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
227
271
|
}
|
package/src/index.ts
CHANGED
package/src/log/log.ts
CHANGED
|
@@ -56,7 +56,7 @@ export class Log<TAction> {
|
|
|
56
56
|
/** Gets the action context of the log. */
|
|
57
57
|
async getActionContext(): Promise<ActionContext | undefined> {
|
|
58
58
|
const tailPath = await this.chain.getTail();
|
|
59
|
-
if (!tailPath) {
|
|
59
|
+
if (!tailPath || tailPath.block.entries.length === 0) {
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
62
|
const checkpoint = await this.findCheckpoint(tailPath);
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import debug from 'debug'
|
|
2
|
+
|
|
3
|
+
const BASE_NAMESPACE = 'optimystic:db-core'
|
|
4
|
+
|
|
5
|
+
export function createLogger(subNamespace: string): debug.Debugger {
|
|
6
|
+
return debug(`${BASE_NAMESPACE}:${subNamespace}`)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const verbose = typeof process !== 'undefined'
|
|
10
|
+
&& (process.env.OPTIMYSTIC_VERBOSE === '1' || process.env.OPTIMYSTIC_VERBOSE === 'true');
|