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.
@@ -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
+ }
@@ -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
- constructor(db, dataDir, totalCapacity, { findEvictionCandidate, staticRoots = [] } = {}) {
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.dataDir = dataDir;
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 SQLite.
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
- // Sorted: longer prefix first; equal prefix: specific hostname before wildcard.
44
- // Takes priority over staticRootMasls in mount-point routing.
45
- const runtimeRows = dbListMountPoints(db)
46
- .map(row => ({ hostname: row.hostname, prefix: row.mount_path, maslCid: row.masl_cid }));
47
- sortMountPoints(runtimeRows);
48
- this.runtimeMountPoints = runtimeRows;
49
- }
50
-
51
- putContent(cid, bytes, { maslCid = null, pinned = false } = {}) {
52
- writeContent(this.dataDir, cid, bytes);
53
- dbPutContent(this.db, cid, {
54
- maslCid,
55
- size: bytes.length,
56
- pinned,
57
- lastRequested: null,
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
- getContent(cid) {
62
- const meta = dbGetContent(this.db, cid);
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.source_path
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
- // Returns { stream: ReadStream, meta } for efficient large-file serving,
72
- // or null if the content is unavailable. For static entries, verifies
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 = readContentStream(this.dataDir, cid);
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 dbGetContent(this.db, cid);
70
+ async getContentMeta(cid) {
71
+ return this.db.getContent(cid);
95
72
  }
96
73
 
97
- hasContent(cid) {
98
- return dbHasContent(this.db, cid);
74
+ async hasContent(cid) {
75
+ return this.db.hasContent(cid);
99
76
  }
100
77
 
101
- listContent() {
102
- return dbListContent(this.db);
78
+ async listContent() {
79
+ return this.db.listContent();
103
80
  }
104
81
 
105
- countContent() {
106
- return dbCountContent(this.db);
82
+ async countContent() {
83
+ return this.db.countContent();
107
84
  }
108
85
 
109
- listContentPage(limit, cursor) {
110
- return dbListContentPage(this.db, limit, cursor);
86
+ async listContentPage(limit, cursor) {
87
+ return this.db.listContentPage(limit, cursor);
111
88
  }
112
89
 
113
- deleteContent(cid) {
114
- const meta = dbGetContent(this.db, cid);
115
- // Static content: bytes live on operator's filesystem; only remove the DB record.
116
- if (!meta?.source_path) deleteContent(this.dataDir, cid);
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
- dbRecordRequest(this.db, cid);
96
+ async recordRequest(cid) {
97
+ await this.db.recordRequest(cid);
122
98
  }
123
99
 
124
- setPinned(cid, pinned) {
125
- dbSetPinned(this.db, cid, pinned);
100
+ async setPinned(cid, pinned) {
101
+ await this.db.setPinned(cid, pinned);
126
102
  }
127
103
 
128
- getPoolUsed() {
129
- return dbGetTotalPoolSize(this.db);
104
+ async getPoolUsed() {
105
+ return this.db.getTotalPoolSize();
130
106
  }
131
107
 
132
- getPinnedUsed() {
133
- return dbGetTotalPinnedSize(this.db);
108
+ async getPinnedUsed() {
109
+ return this.db.getTotalPinnedSize();
134
110
  }
135
111
 
136
- getPoolAvailable() {
137
- return Math.max(0, this.totalCapacity - this.getPoolUsed() - this.getPinnedUsed());
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 dbCountPinned(this.db);
118
+ async countPinned() {
119
+ return this.db.countPinned();
142
120
  }
143
121
 
144
- // Evicts one CID if needed to free requiredBytes. Returns true if eviction
145
- // was performed or not needed, false if impossible. The eviction policy is
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
- dbDeleteMountPoint(this.db, hostname, prefix);
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._findEvictionCandidate(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
  }