rasler 0.1.0 → 0.2.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/package.json +27 -18
- package/src/handlers/operator.js +290 -0
- package/src/handlers/rasl.js +119 -0
- package/src/index.js +6 -6
- package/src/routes/mountPoints.js +5 -20
- package/src/routes/operator.js +38 -250
- package/src/routes/rasl.js +14 -103
- package/src/static.js +13 -39
- package/src/storage/local-blobs.js +19 -0
- package/src/storage/local-db.js +39 -0
- package/src/storage/store.js +83 -96
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
writeContent, readContent, readContentFromPath,
|
|
3
|
+
readContentStream, readContentStreamFromPath, deleteContent,
|
|
4
|
+
} from './files.js';
|
|
5
|
+
|
|
6
|
+
export function makeLocalBlobs(dataDir) {
|
|
7
|
+
return {
|
|
8
|
+
put: (cid, bytes) => { writeContent(dataDir, cid, bytes); },
|
|
9
|
+
get: (cid, meta) => {
|
|
10
|
+
if (meta?.source_path) return readContentFromPath(meta.source_path);
|
|
11
|
+
return readContent(dataDir, cid);
|
|
12
|
+
},
|
|
13
|
+
getStream: (cid, meta) => {
|
|
14
|
+
if (meta?.source_path) return readContentStreamFromPath(meta.source_path);
|
|
15
|
+
return readContentStream(dataDir, cid);
|
|
16
|
+
},
|
|
17
|
+
delete: (cid) => { deleteContent(dataDir, cid); },
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dbPutContent, dbGetContent, dbHasContent, dbListContent, dbDeleteContent,
|
|
3
|
+
dbRecordRequest, dbSetPinned, dbGetTotalPoolSize, dbGetTotalPinnedSize,
|
|
4
|
+
dbCountPinned, dbCountContent, dbListContentPage,
|
|
5
|
+
dbSetMountPoint, dbDeleteMountPoint, dbListMountPoints,
|
|
6
|
+
dbPutStaticContent, dbGetContentBySourcePath, dbListStaticContent,
|
|
7
|
+
} from './db.js';
|
|
8
|
+
|
|
9
|
+
export function makeLocalDb(db) {
|
|
10
|
+
return {
|
|
11
|
+
putContent: (cid, opts) => { dbPutContent(db, cid, opts); },
|
|
12
|
+
getContent: (cid) => dbGetContent(db, cid),
|
|
13
|
+
hasContent: (cid) => dbHasContent(db, cid),
|
|
14
|
+
listContent: () => dbListContent(db),
|
|
15
|
+
countContent: () => dbCountContent(db),
|
|
16
|
+
listContentPage: (limit, cursor) => dbListContentPage(db, limit, cursor),
|
|
17
|
+
deleteContent: (cid) => { dbDeleteContent(db, cid); },
|
|
18
|
+
recordRequest: (cid) => { dbRecordRequest(db, cid); },
|
|
19
|
+
setPinned: (cid, pinned) => { dbSetPinned(db, cid, pinned); },
|
|
20
|
+
getTotalPoolSize: () => dbGetTotalPoolSize(db),
|
|
21
|
+
getTotalPinnedSize: () => dbGetTotalPinnedSize(db),
|
|
22
|
+
countPinned: () => dbCountPinned(db),
|
|
23
|
+
findEvictionCandidate: () => {
|
|
24
|
+
const row = db.prepare(`
|
|
25
|
+
SELECT cid FROM content
|
|
26
|
+
WHERE pinned = 0
|
|
27
|
+
ORDER BY last_requested ASC NULLS FIRST
|
|
28
|
+
LIMIT 1
|
|
29
|
+
`).get();
|
|
30
|
+
return row?.cid ?? null;
|
|
31
|
+
},
|
|
32
|
+
putStaticContent: (cid, opts) => { dbPutStaticContent(db, cid, opts); },
|
|
33
|
+
getContentBySourcePath: (sourcePath) => dbGetContentBySourcePath(db, sourcePath),
|
|
34
|
+
listStaticContent: () => dbListStaticContent(db),
|
|
35
|
+
setMountPoint: (hostname, mountPath, maslCid) => { dbSetMountPoint(db, hostname, mountPath, maslCid); },
|
|
36
|
+
deleteMountPoint: (hostname, mountPath) => { dbDeleteMountPoint(db, hostname, mountPath); },
|
|
37
|
+
listMountPoints: () => dbListMountPoints(db),
|
|
38
|
+
};
|
|
39
|
+
}
|
package/src/storage/store.js
CHANGED
|
@@ -1,36 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dbPutContent, dbGetContent, dbHasContent, dbListContent, dbDeleteContent,
|
|
3
|
-
dbRecordRequest, dbSetPinned, dbGetTotalPoolSize, dbGetTotalPinnedSize,
|
|
4
|
-
dbCountPinned, dbCountContent, dbListContentPage,
|
|
5
|
-
dbSetMountPoint, dbDeleteMountPoint, dbListMountPoints,
|
|
6
|
-
} from './db.js';
|
|
7
1
|
import { sortMountPoints } from '../util/parseJsonConfig.js';
|
|
8
|
-
import {
|
|
9
|
-
writeContent, readContent, readContentFromPath,
|
|
10
|
-
readContentStream, readContentStreamFromPath, deleteContent,
|
|
11
|
-
} from './files.js';
|
|
12
2
|
import { realpathSync } from 'fs';
|
|
13
3
|
import { sep } from 'path';
|
|
14
4
|
|
|
15
|
-
// Default eviction policy: oldest unpinned by last_requested (LRU).
|
|
16
|
-
// An overlay can supply a network-aware policy that considers
|
|
17
|
-
// replica counts and primary-holder status.
|
|
18
|
-
function defaultFindEvictionCandidate(store) {
|
|
19
|
-
const row = store.db.prepare(`
|
|
20
|
-
SELECT cid FROM content
|
|
21
|
-
WHERE pinned = 0
|
|
22
|
-
ORDER BY last_requested ASC NULLS FIRST
|
|
23
|
-
LIMIT 1
|
|
24
|
-
`).get();
|
|
25
|
-
return row?.cid ?? null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
5
|
export class Store {
|
|
29
|
-
|
|
6
|
+
// db: DbAdapter (see local-db.js for the interface)
|
|
7
|
+
// blobs: BlobBackend (see local-blobs.js for the interface)
|
|
8
|
+
constructor(db, blobs, totalCapacity, { staticRoots = [] } = {}) {
|
|
30
9
|
this.db = db;
|
|
31
|
-
this.
|
|
10
|
+
this.blobs = blobs;
|
|
32
11
|
this.totalCapacity = totalCapacity;
|
|
33
|
-
this._findEvictionCandidate = findEvictionCandidate ?? defaultFindEvictionCandidate;
|
|
34
12
|
// Pre-resolve static roots once so symlink checks at serve time are fast.
|
|
35
13
|
this._realStaticRoots = staticRoots.map(r => {
|
|
36
14
|
const dir = typeof r === 'string' ? r : r.directory;
|
|
@@ -38,41 +16,43 @@ export class Store {
|
|
|
38
16
|
});
|
|
39
17
|
// Populated by indexStaticRoot after each root is indexed. Maps realpath → maslCid.
|
|
40
18
|
this.staticRootMasls = new Map();
|
|
41
|
-
// Runtime mount point mappings set via operator API. Persisted in
|
|
19
|
+
// Runtime mount point mappings set via operator API. Persisted in the db adapter.
|
|
42
20
|
// Array of {hostname, prefix, maslCid} — hostname='' means any host.
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.runtimeMountPoints =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
21
|
+
// Callers that use an async db adapter must call store.loadMountPoints() after construction.
|
|
22
|
+
const mpResult = db.listMountPoints();
|
|
23
|
+
const rows = Array.isArray(mpResult)
|
|
24
|
+
? mpResult
|
|
25
|
+
: []; // async adapters return a Promise; callers must await loadMountPoints()
|
|
26
|
+
this.runtimeMountPoints = rows.map(
|
|
27
|
+
row => ({ hostname: row.hostname, prefix: row.mount_path, maslCid: row.masl_cid })
|
|
28
|
+
);
|
|
29
|
+
sortMountPoints(this.runtimeMountPoints);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Must be called after construction when using an async db adapter (e.g. D1).
|
|
33
|
+
async loadMountPoints() {
|
|
34
|
+
const rows = await this.db.listMountPoints();
|
|
35
|
+
this.runtimeMountPoints = rows.map(
|
|
36
|
+
row => ({ hostname: row.hostname, prefix: row.mount_path, maslCid: row.masl_cid })
|
|
37
|
+
);
|
|
38
|
+
sortMountPoints(this.runtimeMountPoints);
|
|
59
39
|
}
|
|
60
40
|
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
async putContent(cid, bytes, { maslCid = null, pinned = false } = {}) {
|
|
42
|
+
await this.blobs.put(cid, bytes);
|
|
43
|
+
await this.db.putContent(cid, { maslCid, size: bytes.length, pinned, lastRequested: null });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getContent(cid) {
|
|
47
|
+
const meta = await this.db.getContent(cid);
|
|
63
48
|
if (!meta) return null;
|
|
64
|
-
const bytes = meta
|
|
65
|
-
? readContentFromPath(meta.source_path)
|
|
66
|
-
: readContent(this.dataDir, cid);
|
|
49
|
+
const bytes = await this.blobs.get(cid, meta);
|
|
67
50
|
if (!bytes) return null;
|
|
68
51
|
return { bytes, meta };
|
|
69
52
|
}
|
|
70
53
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// that source_path still resolves to a path under a configured static root.
|
|
74
|
-
getContentStream(cid) {
|
|
75
|
-
const meta = dbGetContent(this.db, cid);
|
|
54
|
+
async getContentStream(cid) {
|
|
55
|
+
const meta = await this.db.getContent(cid);
|
|
76
56
|
if (!meta) return null;
|
|
77
57
|
if (meta.source_path) {
|
|
78
58
|
let realFile;
|
|
@@ -81,72 +61,66 @@ export class Store {
|
|
|
81
61
|
root => realFile === root || realFile.startsWith(root + sep)
|
|
82
62
|
);
|
|
83
63
|
if (!allowed) return null;
|
|
84
|
-
const stream = readContentStreamFromPath(meta.source_path);
|
|
85
|
-
if (!stream) return null;
|
|
86
|
-
return { stream, meta };
|
|
87
64
|
}
|
|
88
|
-
const stream =
|
|
65
|
+
const stream = await this.blobs.getStream(cid, meta);
|
|
89
66
|
if (!stream) return null;
|
|
90
67
|
return { stream, meta };
|
|
91
68
|
}
|
|
92
69
|
|
|
93
|
-
getContentMeta(cid) {
|
|
94
|
-
return
|
|
70
|
+
async getContentMeta(cid) {
|
|
71
|
+
return this.db.getContent(cid);
|
|
95
72
|
}
|
|
96
73
|
|
|
97
|
-
hasContent(cid) {
|
|
98
|
-
return
|
|
74
|
+
async hasContent(cid) {
|
|
75
|
+
return this.db.hasContent(cid);
|
|
99
76
|
}
|
|
100
77
|
|
|
101
|
-
listContent() {
|
|
102
|
-
return
|
|
78
|
+
async listContent() {
|
|
79
|
+
return this.db.listContent();
|
|
103
80
|
}
|
|
104
81
|
|
|
105
|
-
countContent() {
|
|
106
|
-
return
|
|
82
|
+
async countContent() {
|
|
83
|
+
return this.db.countContent();
|
|
107
84
|
}
|
|
108
85
|
|
|
109
|
-
listContentPage(limit, cursor) {
|
|
110
|
-
return
|
|
86
|
+
async listContentPage(limit, cursor) {
|
|
87
|
+
return this.db.listContentPage(limit, cursor);
|
|
111
88
|
}
|
|
112
89
|
|
|
113
|
-
deleteContent(cid) {
|
|
114
|
-
const meta =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
dbDeleteContent(this.db, cid);
|
|
90
|
+
async deleteContent(cid) {
|
|
91
|
+
const meta = await this.db.getContent(cid);
|
|
92
|
+
if (!meta?.source_path) await this.blobs.delete(cid);
|
|
93
|
+
await this.db.deleteContent(cid);
|
|
118
94
|
}
|
|
119
95
|
|
|
120
|
-
recordRequest(cid) {
|
|
121
|
-
|
|
96
|
+
async recordRequest(cid) {
|
|
97
|
+
await this.db.recordRequest(cid);
|
|
122
98
|
}
|
|
123
99
|
|
|
124
|
-
setPinned(cid, pinned) {
|
|
125
|
-
|
|
100
|
+
async setPinned(cid, pinned) {
|
|
101
|
+
await this.db.setPinned(cid, pinned);
|
|
126
102
|
}
|
|
127
103
|
|
|
128
|
-
getPoolUsed() {
|
|
129
|
-
return
|
|
104
|
+
async getPoolUsed() {
|
|
105
|
+
return this.db.getTotalPoolSize();
|
|
130
106
|
}
|
|
131
107
|
|
|
132
|
-
getPinnedUsed() {
|
|
133
|
-
return
|
|
108
|
+
async getPinnedUsed() {
|
|
109
|
+
return this.db.getTotalPinnedSize();
|
|
134
110
|
}
|
|
135
111
|
|
|
136
|
-
getPoolAvailable() {
|
|
137
|
-
|
|
112
|
+
async getPoolAvailable() {
|
|
113
|
+
const pool = await this.db.getTotalPoolSize();
|
|
114
|
+
const pinned = await this.db.getTotalPinnedSize();
|
|
115
|
+
return Math.max(0, this.totalCapacity - pool - pinned);
|
|
138
116
|
}
|
|
139
117
|
|
|
140
|
-
countPinned() {
|
|
141
|
-
return
|
|
118
|
+
async countPinned() {
|
|
119
|
+
return this.db.countPinned();
|
|
142
120
|
}
|
|
143
121
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// injected via the constructor; replica-row cleanup happens automatically
|
|
147
|
-
// via FK cascade on the replicas table.
|
|
148
|
-
setMountPoint(hostname, prefix, maslCid) {
|
|
149
|
-
dbSetMountPoint(this.db, hostname, prefix, maslCid);
|
|
122
|
+
async setMountPoint(hostname, prefix, maslCid) {
|
|
123
|
+
await this.db.setMountPoint(hostname, prefix, maslCid);
|
|
150
124
|
this.runtimeMountPoints = this.runtimeMountPoints.filter(
|
|
151
125
|
mp => !(mp.hostname === hostname && mp.prefix === prefix)
|
|
152
126
|
);
|
|
@@ -154,18 +128,31 @@ export class Store {
|
|
|
154
128
|
sortMountPoints(this.runtimeMountPoints);
|
|
155
129
|
}
|
|
156
130
|
|
|
157
|
-
deleteMountPoint(hostname, prefix) {
|
|
158
|
-
|
|
131
|
+
async deleteMountPoint(hostname, prefix) {
|
|
132
|
+
await this.db.deleteMountPoint(hostname, prefix);
|
|
159
133
|
this.runtimeMountPoints = this.runtimeMountPoints.filter(
|
|
160
134
|
mp => !(mp.hostname === hostname && mp.prefix === prefix)
|
|
161
135
|
);
|
|
162
136
|
}
|
|
163
137
|
|
|
164
|
-
evictIfNeeded(requiredBytes) {
|
|
165
|
-
if (this.getPoolAvailable() >= requiredBytes) return true;
|
|
166
|
-
const cid = this.
|
|
138
|
+
async evictIfNeeded(requiredBytes) {
|
|
139
|
+
if (await this.getPoolAvailable() >= requiredBytes) return true;
|
|
140
|
+
const cid = await this.db.findEvictionCandidate();
|
|
167
141
|
if (!cid) return false;
|
|
168
|
-
this.deleteContent(cid);
|
|
142
|
+
await this.deleteContent(cid);
|
|
169
143
|
return true;
|
|
170
144
|
}
|
|
145
|
+
|
|
146
|
+
// Static-content methods (local Node.js deployment only).
|
|
147
|
+
async putStaticContent(cid, opts) {
|
|
148
|
+
await this.db.putStaticContent(cid, opts);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async getContentBySourcePath(sourcePath) {
|
|
152
|
+
return this.db.getContentBySourcePath(sourcePath);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async listStaticContent() {
|
|
156
|
+
return this.db.listStaticContent();
|
|
157
|
+
}
|
|
171
158
|
}
|