-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathstore.js
More file actions
154 lines (137 loc) · 3.76 KB
/
store.js
File metadata and controls
154 lines (137 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* MongoDB Storage Layer for /db/ route
*
* Optional dependency — dynamically imports 'mongodb'.
* Provides document-level CRUD keyed by URI.
*/
import crypto from 'crypto';
let client = null;
let db = null;
let col = null;
/**
* Connect to MongoDB
* @param {object} options
* @param {string} options.url - MongoDB connection URL
* @param {string} options.database - Database name
* @returns {Promise<void>}
*/
export async function connect({ url, database }) {
let MongoClient;
try {
({ MongoClient } = await import('mongodb'));
} catch {
throw new Error(
'MongoDB driver not installed. Install it with: npm install mongodb\n' +
'The mongodb package is optional and only needed when using --mongo.'
);
}
client = new MongoClient(url);
await client.connect();
db = client.db(database);
col = db.collection('resources');
// Create unique index on URI for fast lookups
await col.createIndex({ uri: 1 }, { unique: true });
}
/**
* Disconnect from MongoDB
* @returns {Promise<void>}
*/
export async function disconnect() {
if (client) {
await client.close();
client = null;
db = null;
col = null;
}
}
/**
* Generate ETag from data
*/
function generateEtag(data) {
const hash = crypto.createHash('md5').update(JSON.stringify(data)).digest('hex');
return `"${hash}"`;
}
/**
* Find a single document by URI
* @param {string} uri - The resource URI
* @returns {Promise<{data: object, contentType: string, etag: string, modified: Date} | null>}
*/
export async function findOne(uri) {
const doc = await col.findOne({ uri });
if (!doc) return null;
return {
data: doc.data,
contentType: doc.contentType,
etag: doc.etag,
modified: doc.modified
};
}
/**
* Upsert a document by URI
* @param {string} uri - The resource URI
* @param {object} data - The document data (JSON-LD)
* @param {string} contentType - The content type
* @returns {Promise<{created: boolean, etag: string}>}
*/
export async function upsertOne(uri, data, contentType) {
const etag = generateEtag(data);
const now = new Date();
const result = await col.updateOne(
{ uri },
{
$set: { data, contentType, etag, modified: now },
$setOnInsert: { created: now }
},
{ upsert: true }
);
return {
created: result.upsertedCount > 0,
etag
};
}
/**
* Delete a document by URI
* @param {string} uri - The resource URI
* @returns {Promise<boolean>} - true if deleted, false if not found
*/
export async function deleteOne(uri) {
const result = await col.deleteOne({ uri });
return result.deletedCount > 0;
}
/**
* Escape special regex characters in a string
*/
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* List immediate children whose URI starts with a prefix (for container listings)
* @param {string} prefix - URI prefix (must end with '/')
* @returns {Promise<Array<{name: string, isDirectory: boolean}>>}
*/
export async function listByPrefix(prefix) {
const regex = new RegExp('^' + escapeRegex(prefix));
const docs = await col.find({ uri: regex }, { projection: { uri: 1 } }).toArray();
const children = new Map();
for (const doc of docs) {
const remainder = doc.uri.slice(prefix.length);
if (!remainder) continue;
const slashIndex = remainder.indexOf('/');
if (slashIndex === -1) {
// Direct child resource
children.set(remainder, false);
} else {
// Nested under a sub-container
const containerName = remainder.slice(0, slashIndex);
children.set(containerName, true);
}
}
return Array.from(children.entries()).map(([name, isDirectory]) => ({ name, isDirectory }));
}
/**
* Check if MongoDB is connected
* @returns {boolean}
*/
export function isConnected() {
return client !== null;
}