@gravito/monolith 3.0.0 → 3.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/CHANGELOG.md +15 -0
- package/dist/index.cjs +298 -13
- package/dist/index.d.cts +113 -4
- package/dist/index.d.ts +113 -4
- package/dist/index.js +296 -13
- package/ion/src/index.js +2775 -2559
- package/package.json +11 -7
- package/scripts/check-coverage.ts +64 -0
- package/src/ContentManager.ts +115 -13
- package/src/ContentWatcher.ts +123 -0
- package/src/Controller.ts +17 -0
- package/src/FormRequest.ts +12 -1
- package/src/Router.ts +4 -0
- package/src/driver/ContentDriver.ts +5 -0
- package/src/driver/GitHubDriver.ts +80 -0
- package/src/driver/LocalDriver.ts +30 -0
- package/src/index.ts +38 -1
- package/tests/content-cache.test.ts +36 -0
- package/tests/content-search.test.ts +79 -0
- package/tests/content-watcher.test.ts +56 -0
- package/tests/content.test.ts +2 -1
- package/tests/extra.test.ts +2 -1
- package/tests/hot-reload.test.ts +74 -0
- package/tsconfig.json +13 -19
- package/dist/src/index.js +0 -5624
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @gravito/monolith
|
|
2
2
|
|
|
3
|
+
## 3.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat: implement ContentWatcher for hot reload during development
|
|
8
|
+
- feat: implement in-memory full-text search index in ContentManager
|
|
9
|
+
|
|
10
|
+
## 3.0.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @gravito/core@1.2.1
|
|
16
|
+
- @gravito/mass@3.0.1
|
|
17
|
+
|
|
3
18
|
## 3.0.0
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,8 @@ __export(index_exports, {
|
|
|
34
34
|
ContentManager: () => ContentManager,
|
|
35
35
|
Controller: () => Controller,
|
|
36
36
|
FormRequest: () => FormRequest,
|
|
37
|
+
GitHubDriver: () => GitHubDriver,
|
|
38
|
+
LocalDriver: () => LocalDriver,
|
|
37
39
|
OrbitMonolith: () => OrbitMonolith,
|
|
38
40
|
Route: () => RouterHelper,
|
|
39
41
|
Schema: () => import_mass2.Schema
|
|
@@ -41,7 +43,6 @@ __export(index_exports, {
|
|
|
41
43
|
module.exports = __toCommonJS(index_exports);
|
|
42
44
|
|
|
43
45
|
// src/ContentManager.ts
|
|
44
|
-
var import_promises = require("fs/promises");
|
|
45
46
|
var import_node_path = require("path");
|
|
46
47
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
47
48
|
var import_marked = require("marked");
|
|
@@ -49,14 +50,16 @@ var ContentManager = class {
|
|
|
49
50
|
/**
|
|
50
51
|
* Create a new ContentManager instance.
|
|
51
52
|
*
|
|
52
|
-
* @param
|
|
53
|
+
* @param driver - The content driver to use.
|
|
53
54
|
*/
|
|
54
|
-
constructor(
|
|
55
|
-
this.
|
|
55
|
+
constructor(driver) {
|
|
56
|
+
this.driver = driver;
|
|
56
57
|
}
|
|
57
58
|
collections = /* @__PURE__ */ new Map();
|
|
58
59
|
// Simple memory cache: collection:locale:slug -> ContentItem
|
|
59
60
|
cache = /* @__PURE__ */ new Map();
|
|
61
|
+
// In-memory search index: term -> Set<cacheKey>
|
|
62
|
+
searchIndex = /* @__PURE__ */ new Map();
|
|
60
63
|
renderer = (() => {
|
|
61
64
|
const renderer = new import_marked.marked.Renderer();
|
|
62
65
|
renderer.html = (html) => this.escapeHtml(html);
|
|
@@ -70,6 +73,31 @@ var ContentManager = class {
|
|
|
70
73
|
};
|
|
71
74
|
return renderer;
|
|
72
75
|
})();
|
|
76
|
+
/**
|
|
77
|
+
* Clear all cached content.
|
|
78
|
+
* Useful for hot reload during development.
|
|
79
|
+
*/
|
|
80
|
+
clearCache() {
|
|
81
|
+
this.cache.clear();
|
|
82
|
+
this.searchIndex.clear();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Invalidate a specific content item.
|
|
86
|
+
* @param collection - The collection name.
|
|
87
|
+
* @param slug - The file slug.
|
|
88
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
89
|
+
*/
|
|
90
|
+
invalidate(collection, slug, locale = "en") {
|
|
91
|
+
const safeSlug = this.sanitizeSegment(slug);
|
|
92
|
+
const safeLocale = this.sanitizeSegment(locale);
|
|
93
|
+
if (safeSlug && safeLocale) {
|
|
94
|
+
const cacheKey = `${collection}:${safeLocale}:${safeSlug}`;
|
|
95
|
+
this.cache.delete(cacheKey);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
getCollectionConfig(name) {
|
|
99
|
+
return this.collections.get(name);
|
|
100
|
+
}
|
|
73
101
|
/**
|
|
74
102
|
* Register a new content collection.
|
|
75
103
|
*
|
|
@@ -99,16 +127,17 @@ var ContentManager = class {
|
|
|
99
127
|
return null;
|
|
100
128
|
}
|
|
101
129
|
const cacheKey = `${collectionName}:${locale}:${slug}`;
|
|
102
|
-
|
|
103
|
-
|
|
130
|
+
const cachedItem = this.cache.get(cacheKey);
|
|
131
|
+
if (cachedItem) {
|
|
132
|
+
return cachedItem;
|
|
104
133
|
}
|
|
105
|
-
const filePath = (0, import_node_path.join)(
|
|
134
|
+
const filePath = (0, import_node_path.join)(config.path, safeLocale, `${safeSlug}.md`);
|
|
106
135
|
try {
|
|
107
|
-
const exists = await
|
|
136
|
+
const exists = await this.driver.exists(filePath);
|
|
108
137
|
if (!exists) {
|
|
109
138
|
return null;
|
|
110
139
|
}
|
|
111
|
-
const fileContent = await
|
|
140
|
+
const fileContent = await this.driver.read(filePath);
|
|
112
141
|
const { data, content, excerpt } = (0, import_gray_matter.default)(fileContent);
|
|
113
142
|
const html = await import_marked.marked.parse(content, { renderer: this.renderer });
|
|
114
143
|
const item = {
|
|
@@ -119,6 +148,7 @@ var ContentManager = class {
|
|
|
119
148
|
excerpt
|
|
120
149
|
};
|
|
121
150
|
this.cache.set(cacheKey, item);
|
|
151
|
+
this.buildSearchIndex(cacheKey, item);
|
|
122
152
|
return item;
|
|
123
153
|
} catch (e) {
|
|
124
154
|
console.error(`[Orbit-Content] Error reading file: ${filePath}`, e);
|
|
@@ -143,9 +173,9 @@ var ContentManager = class {
|
|
|
143
173
|
if (!safeLocale) {
|
|
144
174
|
return [];
|
|
145
175
|
}
|
|
146
|
-
const dirPath = (0, import_node_path.join)(
|
|
176
|
+
const dirPath = (0, import_node_path.join)(config.path, safeLocale);
|
|
147
177
|
try {
|
|
148
|
-
const files = await
|
|
178
|
+
const files = await this.driver.list(dirPath);
|
|
149
179
|
const items = [];
|
|
150
180
|
for (const file of files) {
|
|
151
181
|
if (!file.endsWith(".md")) {
|
|
@@ -162,6 +192,54 @@ var ContentManager = class {
|
|
|
162
192
|
return [];
|
|
163
193
|
}
|
|
164
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Search for content items across collections and locales.
|
|
197
|
+
*
|
|
198
|
+
* @param query - The search query.
|
|
199
|
+
* @param options - Optional filters for collection and locale.
|
|
200
|
+
* @returns An array of matching ContentItems.
|
|
201
|
+
*/
|
|
202
|
+
search(query, options = {}) {
|
|
203
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
204
|
+
if (terms.length === 0) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
const matches = /* @__PURE__ */ new Set();
|
|
208
|
+
for (const term of terms) {
|
|
209
|
+
const keys = this.searchIndex.get(term);
|
|
210
|
+
if (keys) {
|
|
211
|
+
for (const key of keys) {
|
|
212
|
+
matches.add(key);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const results = [];
|
|
217
|
+
for (const key of matches) {
|
|
218
|
+
const item = this.cache.get(key);
|
|
219
|
+
if (!item) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const [collection, locale] = key.split(":");
|
|
223
|
+
if (options.collection && options.collection !== collection) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (options.locale && options.locale !== locale) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
results.push(item);
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
buildSearchIndex(cacheKey, item) {
|
|
234
|
+
const text = `${item.slug} ${item.meta.title || ""} ${item.raw} ${item.excerpt || ""}`.toLowerCase();
|
|
235
|
+
const terms = text.split(/[^\w\d]+/).filter((t) => t.length > 2);
|
|
236
|
+
for (const term of terms) {
|
|
237
|
+
if (!this.searchIndex.has(term)) {
|
|
238
|
+
this.searchIndex.set(term, /* @__PURE__ */ new Set());
|
|
239
|
+
}
|
|
240
|
+
this.searchIndex.get(term)?.add(cacheKey);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
165
243
|
sanitizeSegment(value) {
|
|
166
244
|
if (!value) {
|
|
167
245
|
return null;
|
|
@@ -198,6 +276,128 @@ var ContentManager = class {
|
|
|
198
276
|
}
|
|
199
277
|
};
|
|
200
278
|
|
|
279
|
+
// src/ContentWatcher.ts
|
|
280
|
+
var import_node_fs = require("fs");
|
|
281
|
+
var import_node_path2 = require("path");
|
|
282
|
+
var ContentWatcher = class {
|
|
283
|
+
constructor(contentManager, rootDir, options = {}) {
|
|
284
|
+
this.contentManager = contentManager;
|
|
285
|
+
this.rootDir = rootDir;
|
|
286
|
+
this.options = options;
|
|
287
|
+
}
|
|
288
|
+
watchers = [];
|
|
289
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
290
|
+
watch(collectionName) {
|
|
291
|
+
const config = this.contentManager.getCollectionConfig(collectionName);
|
|
292
|
+
if (!config) {
|
|
293
|
+
console.warn(`[ContentWatcher] Collection '${collectionName}' not found`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const watchPath = (0, import_node_path2.join)(this.rootDir, config.path);
|
|
297
|
+
this.addWatcher(watchPath, collectionName);
|
|
298
|
+
try {
|
|
299
|
+
const entries = (0, import_node_fs.readdirSync)(watchPath, { withFileTypes: true });
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (entry.isDirectory()) {
|
|
302
|
+
this.addWatcher((0, import_node_path2.join)(watchPath, entry.name), collectionName, entry.name);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (_e) {
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
addWatcher(watchPath, collection, localePrefix) {
|
|
309
|
+
try {
|
|
310
|
+
const watcher = (0, import_node_fs.watch)(watchPath, { recursive: true }, (_eventType, filename) => {
|
|
311
|
+
if (!filename) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const key = `${collection}:${localePrefix || ""}:${filename}`;
|
|
315
|
+
if (this.debounceTimers.has(key)) {
|
|
316
|
+
clearTimeout(this.debounceTimers.get(key));
|
|
317
|
+
}
|
|
318
|
+
this.debounceTimers.set(
|
|
319
|
+
key,
|
|
320
|
+
setTimeout(() => {
|
|
321
|
+
this.handleFileChange(collection, filename.toString(), localePrefix);
|
|
322
|
+
this.debounceTimers.delete(key);
|
|
323
|
+
}, this.options.debounceMs ?? 100)
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
this.watchers.push(watcher);
|
|
327
|
+
} catch (_e) {
|
|
328
|
+
try {
|
|
329
|
+
const watcher = (0, import_node_fs.watch)(watchPath, { recursive: false }, (_eventType, filename) => {
|
|
330
|
+
if (!filename) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this.handleFileChange(collection, filename.toString(), localePrefix);
|
|
334
|
+
});
|
|
335
|
+
this.watchers.push(watcher);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
console.error(`[ContentWatcher] Failed to watch ${watchPath}:`, err);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
handleFileChange(collection, filename, localePrefix) {
|
|
342
|
+
const parts = filename.split(/[/\\]/);
|
|
343
|
+
let locale;
|
|
344
|
+
let file;
|
|
345
|
+
if (parts.length >= 2) {
|
|
346
|
+
locale = parts[parts.length - 2];
|
|
347
|
+
file = parts[parts.length - 1];
|
|
348
|
+
} else if (localePrefix) {
|
|
349
|
+
locale = localePrefix;
|
|
350
|
+
file = parts[0];
|
|
351
|
+
} else {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (!file.endsWith(".md")) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const slug = file.replace(/\.md$/, "");
|
|
358
|
+
this.contentManager.invalidate(collection, slug, locale);
|
|
359
|
+
}
|
|
360
|
+
close() {
|
|
361
|
+
for (const watcher of this.watchers) {
|
|
362
|
+
watcher.close();
|
|
363
|
+
}
|
|
364
|
+
this.watchers = [];
|
|
365
|
+
for (const timer of this.debounceTimers.values()) {
|
|
366
|
+
clearTimeout(timer);
|
|
367
|
+
}
|
|
368
|
+
this.debounceTimers.clear();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/driver/LocalDriver.ts
|
|
373
|
+
var import_promises = require("fs/promises");
|
|
374
|
+
var import_node_path3 = require("path");
|
|
375
|
+
var LocalDriver = class {
|
|
376
|
+
constructor(rootDir) {
|
|
377
|
+
this.rootDir = rootDir;
|
|
378
|
+
}
|
|
379
|
+
async read(path) {
|
|
380
|
+
const fullPath = (0, import_node_path3.join)(this.rootDir, path);
|
|
381
|
+
return (0, import_promises.readFile)(fullPath, "utf-8");
|
|
382
|
+
}
|
|
383
|
+
async exists(path) {
|
|
384
|
+
try {
|
|
385
|
+
await (0, import_promises.stat)((0, import_node_path3.join)(this.rootDir, path));
|
|
386
|
+
return true;
|
|
387
|
+
} catch {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async list(dir) {
|
|
392
|
+
try {
|
|
393
|
+
const fullPath = (0, import_node_path3.join)(this.rootDir, dir);
|
|
394
|
+
return await (0, import_promises.readdir)(fullPath);
|
|
395
|
+
} catch {
|
|
396
|
+
return [];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
201
401
|
// src/index.ts
|
|
202
402
|
var import_mass2 = require("@gravito/mass");
|
|
203
403
|
|
|
@@ -297,6 +497,69 @@ var Controller = class {
|
|
|
297
497
|
}
|
|
298
498
|
};
|
|
299
499
|
|
|
500
|
+
// src/driver/GitHubDriver.ts
|
|
501
|
+
var import_rest = require("@octokit/rest");
|
|
502
|
+
var GitHubDriver = class {
|
|
503
|
+
octokit;
|
|
504
|
+
owner;
|
|
505
|
+
repo;
|
|
506
|
+
ref;
|
|
507
|
+
constructor(options) {
|
|
508
|
+
this.octokit = new import_rest.Octokit({ auth: options.auth });
|
|
509
|
+
this.owner = options.owner;
|
|
510
|
+
this.repo = options.repo;
|
|
511
|
+
this.ref = options.ref;
|
|
512
|
+
}
|
|
513
|
+
async read(path) {
|
|
514
|
+
try {
|
|
515
|
+
const { data } = await this.octokit.rest.repos.getContent({
|
|
516
|
+
owner: this.owner,
|
|
517
|
+
repo: this.repo,
|
|
518
|
+
path,
|
|
519
|
+
ref: this.ref
|
|
520
|
+
});
|
|
521
|
+
if (Array.isArray(data) || !("content" in data)) {
|
|
522
|
+
throw new Error(`Path is not a file: ${path}`);
|
|
523
|
+
}
|
|
524
|
+
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
525
|
+
} catch (e) {
|
|
526
|
+
if (e.status === 404) {
|
|
527
|
+
throw new Error(`File not found: ${path}`);
|
|
528
|
+
}
|
|
529
|
+
throw e;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async exists(path) {
|
|
533
|
+
try {
|
|
534
|
+
await this.octokit.rest.repos.getContent({
|
|
535
|
+
owner: this.owner,
|
|
536
|
+
repo: this.repo,
|
|
537
|
+
path,
|
|
538
|
+
ref: this.ref
|
|
539
|
+
});
|
|
540
|
+
return true;
|
|
541
|
+
} catch {
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async list(dir) {
|
|
546
|
+
try {
|
|
547
|
+
const { data } = await this.octokit.rest.repos.getContent({
|
|
548
|
+
owner: this.owner,
|
|
549
|
+
repo: this.repo,
|
|
550
|
+
path: dir,
|
|
551
|
+
ref: this.ref
|
|
552
|
+
});
|
|
553
|
+
if (!Array.isArray(data)) {
|
|
554
|
+
return [];
|
|
555
|
+
}
|
|
556
|
+
return data.map((item) => item.name);
|
|
557
|
+
} catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
300
563
|
// src/FormRequest.ts
|
|
301
564
|
var import_mass = require("@gravito/mass");
|
|
302
565
|
var FormRequest = class {
|
|
@@ -345,7 +608,9 @@ var FormRequest = class {
|
|
|
345
608
|
for (const issue of issues) {
|
|
346
609
|
const path = Array.isArray(issue.path) ? issue.path.join(".") : issue.path || "root";
|
|
347
610
|
const key = path.replace(/^\//, "").replace(/\//g, ".");
|
|
348
|
-
if (!errors[key])
|
|
611
|
+
if (!errors[key]) {
|
|
612
|
+
errors[key] = [];
|
|
613
|
+
}
|
|
349
614
|
errors[key].push(issue.message || "Validation failed");
|
|
350
615
|
}
|
|
351
616
|
return ctx.json(
|
|
@@ -402,7 +667,16 @@ var OrbitMonolith = class {
|
|
|
402
667
|
}
|
|
403
668
|
install(core) {
|
|
404
669
|
const root = this.config.root || process.cwd();
|
|
405
|
-
|
|
670
|
+
let driver;
|
|
671
|
+
let isLocal = false;
|
|
672
|
+
if (this.config.driver) {
|
|
673
|
+
driver = this.config.driver;
|
|
674
|
+
isLocal = driver instanceof LocalDriver;
|
|
675
|
+
} else {
|
|
676
|
+
driver = new LocalDriver(root);
|
|
677
|
+
isLocal = true;
|
|
678
|
+
}
|
|
679
|
+
const manager = new ContentManager(driver);
|
|
406
680
|
if (this.config.collections) {
|
|
407
681
|
for (const [name, config] of Object.entries(this.config.collections)) {
|
|
408
682
|
manager.defineCollection(name, config);
|
|
@@ -412,6 +686,15 @@ var OrbitMonolith = class {
|
|
|
412
686
|
c.set("content", manager);
|
|
413
687
|
return await next();
|
|
414
688
|
});
|
|
689
|
+
if (process.env.NODE_ENV === "development" && isLocal) {
|
|
690
|
+
const watcher = new ContentWatcher(manager, root);
|
|
691
|
+
if (this.config.collections) {
|
|
692
|
+
for (const name of Object.keys(this.config.collections)) {
|
|
693
|
+
watcher.watch(name);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
core.logger.info("Orbit Monolith Hot Reload Active \u{1F525}");
|
|
697
|
+
}
|
|
415
698
|
core.logger.info("Orbit Monolith installed \u2B1B\uFE0F");
|
|
416
699
|
}
|
|
417
700
|
};
|
|
@@ -421,6 +704,8 @@ var OrbitMonolith = class {
|
|
|
421
704
|
ContentManager,
|
|
422
705
|
Controller,
|
|
423
706
|
FormRequest,
|
|
707
|
+
GitHubDriver,
|
|
708
|
+
LocalDriver,
|
|
424
709
|
OrbitMonolith,
|
|
425
710
|
Route,
|
|
426
711
|
Schema
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,16 @@ import { TSchema } from '@gravito/mass';
|
|
|
4
4
|
export { Schema } from '@gravito/mass';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
6
|
|
|
7
|
+
interface ContentDriver {
|
|
8
|
+
read(path: string): Promise<string>;
|
|
9
|
+
exists(path: string): Promise<boolean>;
|
|
10
|
+
list(dir: string): Promise<string[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a single content item (file).
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
7
17
|
interface ContentItem {
|
|
8
18
|
slug: string;
|
|
9
19
|
body: string;
|
|
@@ -11,20 +21,42 @@ interface ContentItem {
|
|
|
11
21
|
meta: Record<string, any>;
|
|
12
22
|
raw: string;
|
|
13
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for a content collection.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
14
28
|
interface CollectionConfig {
|
|
15
29
|
path: string;
|
|
16
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Manages fetching, parsing, and caching of filesystem-based content.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
17
35
|
declare class ContentManager {
|
|
18
|
-
private
|
|
36
|
+
private readonly driver;
|
|
19
37
|
private collections;
|
|
20
38
|
private cache;
|
|
39
|
+
private searchIndex;
|
|
21
40
|
private renderer;
|
|
41
|
+
/**
|
|
42
|
+
* Clear all cached content.
|
|
43
|
+
* Useful for hot reload during development.
|
|
44
|
+
*/
|
|
45
|
+
clearCache(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Invalidate a specific content item.
|
|
48
|
+
* @param collection - The collection name.
|
|
49
|
+
* @param slug - The file slug.
|
|
50
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
51
|
+
*/
|
|
52
|
+
invalidate(collection: string, slug: string, locale?: string): void;
|
|
53
|
+
getCollectionConfig(name: string): CollectionConfig | undefined;
|
|
22
54
|
/**
|
|
23
55
|
* Create a new ContentManager instance.
|
|
24
56
|
*
|
|
25
|
-
* @param
|
|
57
|
+
* @param driver - The content driver to use.
|
|
26
58
|
*/
|
|
27
|
-
constructor(
|
|
59
|
+
constructor(driver: ContentDriver);
|
|
28
60
|
/**
|
|
29
61
|
* Register a new content collection.
|
|
30
62
|
*
|
|
@@ -52,6 +84,18 @@ declare class ContentManager {
|
|
|
52
84
|
* @throws {Error} If the collection is not defined.
|
|
53
85
|
*/
|
|
54
86
|
list(collectionName: string, locale?: string): Promise<ContentItem[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Search for content items across collections and locales.
|
|
89
|
+
*
|
|
90
|
+
* @param query - The search query.
|
|
91
|
+
* @param options - Optional filters for collection and locale.
|
|
92
|
+
* @returns An array of matching ContentItems.
|
|
93
|
+
*/
|
|
94
|
+
search(query: string, options?: {
|
|
95
|
+
collection?: string;
|
|
96
|
+
locale?: string;
|
|
97
|
+
}): ContentItem[];
|
|
98
|
+
private buildSearchIndex;
|
|
55
99
|
private sanitizeSegment;
|
|
56
100
|
private escapeHtml;
|
|
57
101
|
private isSafeUrl;
|
|
@@ -67,10 +111,27 @@ declare class Sanitizer {
|
|
|
67
111
|
static clean(data: any): any;
|
|
68
112
|
}
|
|
69
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Base class for all Monolith Controllers.
|
|
116
|
+
*
|
|
117
|
+
* Provides basic functionality for calling actions and sanitizing data.
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
* @since 3.0.0
|
|
121
|
+
*/
|
|
70
122
|
declare abstract class BaseController {
|
|
71
123
|
protected sanitizer: Sanitizer;
|
|
72
124
|
call(ctx: GravitoContext, method: string): Promise<Response>;
|
|
73
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Controller class with request context awareness and helper methods.
|
|
128
|
+
*
|
|
129
|
+
* This class provides a more feature-rich base for controllers that
|
|
130
|
+
* need direct access to the request context and common response helpers.
|
|
131
|
+
*
|
|
132
|
+
* @public
|
|
133
|
+
* @since 3.0.0
|
|
134
|
+
*/
|
|
74
135
|
declare abstract class Controller {
|
|
75
136
|
protected context: GravitoContext;
|
|
76
137
|
/**
|
|
@@ -107,6 +168,40 @@ declare abstract class Controller {
|
|
|
107
168
|
static call(method: string): any;
|
|
108
169
|
}
|
|
109
170
|
|
|
171
|
+
interface GitHubDriverOptions {
|
|
172
|
+
owner: string;
|
|
173
|
+
repo: string;
|
|
174
|
+
ref?: string;
|
|
175
|
+
auth?: string;
|
|
176
|
+
}
|
|
177
|
+
declare class GitHubDriver implements ContentDriver {
|
|
178
|
+
private octokit;
|
|
179
|
+
private owner;
|
|
180
|
+
private repo;
|
|
181
|
+
private ref?;
|
|
182
|
+
constructor(options: GitHubDriverOptions);
|
|
183
|
+
read(path: string): Promise<string>;
|
|
184
|
+
exists(path: string): Promise<boolean>;
|
|
185
|
+
list(dir: string): Promise<string[]>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
declare class LocalDriver implements ContentDriver {
|
|
189
|
+
private rootDir;
|
|
190
|
+
constructor(rootDir: string);
|
|
191
|
+
read(path: string): Promise<string>;
|
|
192
|
+
exists(path: string): Promise<boolean>;
|
|
193
|
+
list(dir: string): Promise<string[]>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Base class for Monolith Form Requests.
|
|
198
|
+
*
|
|
199
|
+
* Provides a structured way to handle request validation and authorization
|
|
200
|
+
* for the Monolith architecture.
|
|
201
|
+
*
|
|
202
|
+
* @public
|
|
203
|
+
* @since 3.0.0
|
|
204
|
+
*/
|
|
110
205
|
declare abstract class FormRequest {
|
|
111
206
|
protected context: GravitoContext;
|
|
112
207
|
/**
|
|
@@ -135,6 +230,10 @@ declare abstract class FormRequest {
|
|
|
135
230
|
static middleware(): any;
|
|
136
231
|
}
|
|
137
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Utility for registering resourceful routes.
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
138
237
|
declare class RouterHelper {
|
|
139
238
|
/**
|
|
140
239
|
* Register standard resource routes for a controller.
|
|
@@ -155,14 +254,24 @@ declare module '@gravito/core' {
|
|
|
155
254
|
content: ContentManager;
|
|
156
255
|
}
|
|
157
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Configuration for Orbit Monolith (Content Engine).
|
|
259
|
+
* @public
|
|
260
|
+
*/
|
|
158
261
|
interface ContentConfig {
|
|
159
262
|
root?: string;
|
|
263
|
+
driver?: ContentDriver;
|
|
160
264
|
collections?: Record<string, CollectionConfig>;
|
|
161
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Orbit Monolith Service.
|
|
268
|
+
* Provides flat-file CMS capabilities to Gravito applications.
|
|
269
|
+
* @public
|
|
270
|
+
*/
|
|
162
271
|
declare class OrbitMonolith implements GravitoOrbit {
|
|
163
272
|
private config;
|
|
164
273
|
constructor(config?: ContentConfig);
|
|
165
274
|
install(core: PlanetCore): void;
|
|
166
275
|
}
|
|
167
276
|
|
|
168
|
-
export { BaseController, type CollectionConfig, type ContentConfig, type ContentItem, ContentManager, Controller, FormRequest, OrbitMonolith, RouterHelper as Route };
|
|
277
|
+
export { BaseController, type CollectionConfig, type ContentConfig, type ContentDriver, type ContentItem, ContentManager, Controller, FormRequest, GitHubDriver, type GitHubDriverOptions, LocalDriver, OrbitMonolith, RouterHelper as Route };
|