@optimystic/db-p2p-storage-fs 0.1.1
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/file-storage.d.ts +29 -0
- package/dist/src/file-storage.d.ts.map +1 -0
- package/dist/src/file-storage.js +123 -0
- package/dist/src/file-storage.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts +3 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +6 -0
- package/dist/src/logger.js.map +1 -0
- package/package.json +60 -0
- package/src/file-storage.ts +158 -0
- package/src/index.ts +1 -0
- package/src/logger.ts +7 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BlockId, IBlock, Transform, ActionId, ActionRev } from "@optimystic/db-core";
|
|
2
|
+
import type { BlockMetadata, IRawStorage } from "@optimystic/db-p2p";
|
|
3
|
+
export declare class FileRawStorage implements IRawStorage {
|
|
4
|
+
private readonly basePath;
|
|
5
|
+
constructor(basePath: string);
|
|
6
|
+
getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined>;
|
|
7
|
+
saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void>;
|
|
8
|
+
getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined>;
|
|
9
|
+
saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void>;
|
|
10
|
+
getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
|
|
11
|
+
savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
|
|
12
|
+
deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
13
|
+
listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId>;
|
|
14
|
+
getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined>;
|
|
15
|
+
listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev>;
|
|
16
|
+
saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void>;
|
|
17
|
+
getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined>;
|
|
18
|
+
saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void>;
|
|
19
|
+
promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void>;
|
|
20
|
+
private getBlockPath;
|
|
21
|
+
private getMetadataPath;
|
|
22
|
+
private getRevisionPath;
|
|
23
|
+
private getPendingActionPath;
|
|
24
|
+
private getActionPath;
|
|
25
|
+
private getMaterializedPath;
|
|
26
|
+
private readIfExists;
|
|
27
|
+
private ensureAndWriteFile;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=file-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage.d.ts","sourceRoot":"","sources":["../../src/file-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKrE,qBAAa,cAAe,YAAW,WAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAIvC,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAIjE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtE,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAIzE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9E,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAI3F,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjG,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5E,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC;IAYnE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAInF,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC;IAS5F,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAO1F,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIvF,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc1F,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBpF,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,mBAAmB;YAIb,YAAY;YASZ,kBAAkB;CAIhC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createLogger } from './logger.js';
|
|
4
|
+
const log = createLogger('storage:file');
|
|
5
|
+
export class FileRawStorage {
|
|
6
|
+
basePath;
|
|
7
|
+
constructor(basePath) {
|
|
8
|
+
this.basePath = basePath;
|
|
9
|
+
// TODO: use https://www.npmjs.com/package/proper-lockfile to take a lock on the basePath, also introduce explicit dispose pattern
|
|
10
|
+
}
|
|
11
|
+
async getMetadata(blockId) {
|
|
12
|
+
return this.readIfExists(this.getMetadataPath(blockId));
|
|
13
|
+
}
|
|
14
|
+
async saveMetadata(blockId, metadata) {
|
|
15
|
+
await this.ensureAndWriteFile(this.getMetadataPath(blockId), JSON.stringify(metadata));
|
|
16
|
+
}
|
|
17
|
+
async getRevision(blockId, rev) {
|
|
18
|
+
return this.readIfExists(this.getRevisionPath(blockId, rev));
|
|
19
|
+
}
|
|
20
|
+
async saveRevision(blockId, rev, actionId) {
|
|
21
|
+
await this.ensureAndWriteFile(this.getRevisionPath(blockId, rev), actionId);
|
|
22
|
+
}
|
|
23
|
+
async getPendingTransaction(blockId, actionId) {
|
|
24
|
+
return this.readIfExists(this.getPendingActionPath(blockId, actionId));
|
|
25
|
+
}
|
|
26
|
+
async savePendingTransaction(blockId, actionId, transform) {
|
|
27
|
+
await this.ensureAndWriteFile(this.getPendingActionPath(blockId, actionId), JSON.stringify(transform));
|
|
28
|
+
}
|
|
29
|
+
async deletePendingTransaction(blockId, actionId) {
|
|
30
|
+
const pendingPath = this.getPendingActionPath(blockId, actionId);
|
|
31
|
+
await fs.unlink(pendingPath)
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
if (err?.code !== 'ENOENT')
|
|
34
|
+
log('deletePendingTransaction unlink failed for %s/%s - %o', blockId, actionId, err);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async *listPendingTransactions(blockId) {
|
|
38
|
+
const pendingPath = path.join(this.getBlockPath(blockId), 'pend');
|
|
39
|
+
const files = await fs.readdir(pendingPath).catch((err) => { log('listPendingTransactions readdir failed for %s - %o', blockId, err); return []; });
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (!file.endsWith('.json'))
|
|
42
|
+
continue;
|
|
43
|
+
const rawActionId = file.slice(0, -5);
|
|
44
|
+
if (!/^[\w\d]+-[\w\d]+-[\w\d]+-[\w\d]+-[\w\d]+$/.test(rawActionId))
|
|
45
|
+
continue;
|
|
46
|
+
yield rawActionId;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async getTransaction(blockId, actionId) {
|
|
50
|
+
return this.readIfExists(this.getActionPath(blockId, actionId));
|
|
51
|
+
}
|
|
52
|
+
async *listRevisions(blockId, startRev, endRev) {
|
|
53
|
+
for (let rev = startRev; startRev <= endRev ? rev <= endRev : rev >= endRev; startRev <= endRev ? ++rev : --rev) {
|
|
54
|
+
const actionId = await this.getRevision(blockId, rev);
|
|
55
|
+
if (actionId) {
|
|
56
|
+
yield { actionId, rev };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async saveTransaction(blockId, actionId, transform) {
|
|
61
|
+
await this.ensureAndWriteFile(this.getActionPath(blockId, actionId), JSON.stringify(transform));
|
|
62
|
+
}
|
|
63
|
+
async getMaterializedBlock(blockId, actionId) {
|
|
64
|
+
return this.readIfExists(this.getMaterializedPath(blockId, actionId));
|
|
65
|
+
}
|
|
66
|
+
async saveMaterializedBlock(blockId, actionId, block) {
|
|
67
|
+
if (block) {
|
|
68
|
+
await this.ensureAndWriteFile(this.getMaterializedPath(blockId, actionId), JSON.stringify(block));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
await fs.unlink(this.getMaterializedPath(blockId, actionId))
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
if (err?.code !== 'ENOENT')
|
|
74
|
+
log('saveMaterializedBlock unlink failed for %s/%s - %o', blockId, actionId, err);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async promotePendingTransaction(blockId, actionId) {
|
|
79
|
+
const pendingPath = this.getPendingActionPath(blockId, actionId);
|
|
80
|
+
const actionPath = this.getActionPath(blockId, actionId);
|
|
81
|
+
await fs.mkdir(path.dirname(actionPath), { recursive: true });
|
|
82
|
+
return fs.rename(pendingPath, actionPath)
|
|
83
|
+
.catch(err => {
|
|
84
|
+
if (err.code === 'ENOENT') {
|
|
85
|
+
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
86
|
+
}
|
|
87
|
+
log('promotePendingTransaction rename failed for %s/%s - %o', blockId, actionId, err);
|
|
88
|
+
throw err;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
getBlockPath(blockId) {
|
|
92
|
+
return path.join(this.basePath, blockId);
|
|
93
|
+
}
|
|
94
|
+
getMetadataPath(blockId) {
|
|
95
|
+
return path.join(this.getBlockPath(blockId), 'meta.json');
|
|
96
|
+
}
|
|
97
|
+
getRevisionPath(blockId, rev) {
|
|
98
|
+
return path.join(this.getBlockPath(blockId), 'revs', `${rev}.json`);
|
|
99
|
+
}
|
|
100
|
+
getPendingActionPath(blockId, actionId) {
|
|
101
|
+
return path.join(this.getBlockPath(blockId), 'pend', `${actionId}.json`);
|
|
102
|
+
}
|
|
103
|
+
getActionPath(blockId, actionId) {
|
|
104
|
+
return path.join(this.getBlockPath(blockId), 'actions', `${actionId}.json`);
|
|
105
|
+
}
|
|
106
|
+
getMaterializedPath(blockId, actionId) {
|
|
107
|
+
return path.join(this.getBlockPath(blockId), 'blocks', `${actionId}.json`);
|
|
108
|
+
}
|
|
109
|
+
async readIfExists(filePath) {
|
|
110
|
+
return fs.readFile(filePath, 'utf-8')
|
|
111
|
+
.then(content => JSON.parse(content))
|
|
112
|
+
.catch(err => {
|
|
113
|
+
if (err.code === 'ENOENT')
|
|
114
|
+
return undefined;
|
|
115
|
+
throw err;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async ensureAndWriteFile(filePath, content) {
|
|
119
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
120
|
+
await fs.writeFile(filePath, content);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=file-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-storage.js","sourceRoot":"","sources":["../../src/file-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,KAAK,CAAC,QAAQ,CAEjE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,cAAc,GAAG,8BAA8B,CAAC;AAEtD,MAAM,UAAU,YAAY,CAAC,YAAoB;IAChD,OAAO,KAAK,CAAC,GAAG,cAAc,IAAI,YAAY,EAAE,CAAC,CAAC;AACnD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optimystic/db-p2p-storage-fs",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Node.js filesystem storage backend for @optimystic/db-p2p",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src",
|
|
10
|
+
"dist",
|
|
11
|
+
"!dist/test",
|
|
12
|
+
"!**/*.tsbuildinfo"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/src/index.d.ts",
|
|
17
|
+
"import": "./dist/src/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/gotchoices/optimystic.git",
|
|
23
|
+
"directory": "packages/db-p2p-storage-fs"
|
|
24
|
+
},
|
|
25
|
+
"author": "Got Choices Foundation",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"optimystic",
|
|
29
|
+
"database",
|
|
30
|
+
"p2p",
|
|
31
|
+
"storage",
|
|
32
|
+
"filesystem",
|
|
33
|
+
"node"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"clean": "aegir clean",
|
|
37
|
+
"build": "aegir build --env node",
|
|
38
|
+
"lint": "aegir lint",
|
|
39
|
+
"test": "aegir test",
|
|
40
|
+
"test:node": "aegir test -t node",
|
|
41
|
+
"dep-check": "aegir dep-check"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/debug": "^4.1.12",
|
|
45
|
+
"@types/mocha": "^10.0.10",
|
|
46
|
+
"@types/node": "^25.1.0",
|
|
47
|
+
"aegir": "^47.0.26",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@optimystic/db-core": "^0.4.1",
|
|
52
|
+
"@optimystic/db-p2p": "^0.1.2",
|
|
53
|
+
"debug": "^4.4.3"
|
|
54
|
+
},
|
|
55
|
+
"aegir": {
|
|
56
|
+
"build": {
|
|
57
|
+
"bundle": false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { BlockId, IBlock, Transform, ActionId, ActionRev } from "@optimystic/db-core";
|
|
4
|
+
import type { BlockMetadata, IRawStorage } from "@optimystic/db-p2p";
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
|
|
7
|
+
const log = createLogger('storage:file');
|
|
8
|
+
|
|
9
|
+
export class FileRawStorage implements IRawStorage {
|
|
10
|
+
constructor(private readonly basePath: string) {
|
|
11
|
+
// TODO: use https://www.npmjs.com/package/proper-lockfile to take a lock on the basePath, also introduce explicit dispose pattern
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
|
|
15
|
+
return this.readIfExists<BlockMetadata>(this.getMetadataPath(blockId));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
|
|
19
|
+
await this.ensureAndWriteFile(
|
|
20
|
+
this.getMetadataPath(blockId),
|
|
21
|
+
JSON.stringify(metadata)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
|
|
26
|
+
return this.readIfExists<ActionId>(this.getRevisionPath(blockId, rev));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
|
|
30
|
+
await this.ensureAndWriteFile(
|
|
31
|
+
this.getRevisionPath(blockId, rev),
|
|
32
|
+
actionId
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
37
|
+
return this.readIfExists<Transform>(this.getPendingActionPath(blockId, actionId));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
41
|
+
await this.ensureAndWriteFile(
|
|
42
|
+
this.getPendingActionPath(blockId, actionId),
|
|
43
|
+
JSON.stringify(transform)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
48
|
+
const pendingPath = this.getPendingActionPath(blockId, actionId);
|
|
49
|
+
await fs.unlink(pendingPath)
|
|
50
|
+
.catch((err) => {
|
|
51
|
+
if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') log('deletePendingTransaction unlink failed for %s/%s - %o', blockId, actionId, err);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
|
|
56
|
+
const pendingPath = path.join(this.getBlockPath(blockId), 'pend');
|
|
57
|
+
|
|
58
|
+
const files = await fs.readdir(pendingPath).catch((err) => { log('listPendingTransactions readdir failed for %s - %o', blockId, err); return [] as string[]; });
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (!file.endsWith('.json')) continue;
|
|
61
|
+
const rawActionId = file.slice(0, -5);
|
|
62
|
+
if (!/^[\w\d]+-[\w\d]+-[\w\d]+-[\w\d]+-[\w\d]+$/.test(rawActionId)) continue;
|
|
63
|
+
yield rawActionId as ActionId;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
68
|
+
return this.readIfExists<Transform>(this.getActionPath(blockId, actionId));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
|
|
72
|
+
for (let rev = startRev; startRev <= endRev ? rev <= endRev : rev >= endRev; startRev <= endRev ? ++rev : --rev) {
|
|
73
|
+
const actionId = await this.getRevision(blockId, rev);
|
|
74
|
+
if (actionId) {
|
|
75
|
+
yield { actionId, rev };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
81
|
+
await this.ensureAndWriteFile(
|
|
82
|
+
this.getActionPath(blockId, actionId),
|
|
83
|
+
JSON.stringify(transform)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
|
|
88
|
+
return this.readIfExists<IBlock>(this.getMaterializedPath(blockId, actionId));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
|
|
92
|
+
if (block) {
|
|
93
|
+
await this.ensureAndWriteFile(
|
|
94
|
+
this.getMaterializedPath(blockId, actionId),
|
|
95
|
+
JSON.stringify(block)
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
await fs.unlink(this.getMaterializedPath(blockId, actionId))
|
|
99
|
+
.catch((err) => {
|
|
100
|
+
if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') log('saveMaterializedBlock unlink failed for %s/%s - %o', blockId, actionId, err);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
106
|
+
const pendingPath = this.getPendingActionPath(blockId, actionId);
|
|
107
|
+
const actionPath = this.getActionPath(blockId, actionId);
|
|
108
|
+
|
|
109
|
+
await fs.mkdir(path.dirname(actionPath), { recursive: true });
|
|
110
|
+
|
|
111
|
+
return fs.rename(pendingPath, actionPath)
|
|
112
|
+
.catch(err => {
|
|
113
|
+
if (err.code === 'ENOENT') {
|
|
114
|
+
throw new Error(`Pending action ${actionId} not found for block ${blockId}`);
|
|
115
|
+
}
|
|
116
|
+
log('promotePendingTransaction rename failed for %s/%s - %o', blockId, actionId, err);
|
|
117
|
+
throw err;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private getBlockPath(blockId: BlockId): string {
|
|
122
|
+
return path.join(this.basePath, blockId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getMetadataPath(blockId: BlockId): string {
|
|
126
|
+
return path.join(this.getBlockPath(blockId), 'meta.json');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private getRevisionPath(blockId: BlockId, rev: number): string {
|
|
130
|
+
return path.join(this.getBlockPath(blockId), 'revs', `${rev}.json`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private getPendingActionPath(blockId: BlockId, actionId: ActionId): string {
|
|
134
|
+
return path.join(this.getBlockPath(blockId), 'pend', `${actionId}.json`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private getActionPath(blockId: BlockId, actionId: ActionId): string {
|
|
138
|
+
return path.join(this.getBlockPath(blockId), 'actions', `${actionId}.json`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private getMaterializedPath(blockId: BlockId, actionId: ActionId): string {
|
|
142
|
+
return path.join(this.getBlockPath(blockId), 'blocks', `${actionId}.json`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async readIfExists<T>(filePath: string): Promise<T | undefined> {
|
|
146
|
+
return fs.readFile(filePath, 'utf-8')
|
|
147
|
+
.then(content => JSON.parse(content) as T)
|
|
148
|
+
.catch(err => {
|
|
149
|
+
if (err.code === 'ENOENT') return undefined;
|
|
150
|
+
throw err;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private async ensureAndWriteFile(filePath: string, content: string): Promise<void> {
|
|
155
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
156
|
+
await fs.writeFile(filePath, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './file-storage.js';
|