@gravito/monolith 1.0.0-beta.1 → 1.0.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 +9 -0
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/index.cjs +428 -0
- package/dist/index.d.cts +168 -0
- package/dist/index.d.ts +168 -0
- package/dist/index.js +309 -5521
- package/ion/src/index.js +2 -2
- package/package.json +29 -15
- package/src/ContentManager.ts +76 -6
- package/src/Controller.ts +82 -0
- package/src/FormRequest.ts +86 -0
- package/src/Router.ts +35 -0
- package/src/Sanitizer.ts +32 -0
- package/src/index.ts +6 -2
- package/src/middleware/TrimStrings.ts +39 -0
- package/tests/ecommerce_integration.test.ts +58 -0
- package/tests/extra.test.ts +135 -0
- package/tests/mvc.test.ts +150 -0
- package/tsconfig.json +3 -1
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
package/README.zh-TW.md
CHANGED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BaseController: () => BaseController,
|
|
34
|
+
ContentManager: () => ContentManager,
|
|
35
|
+
Controller: () => Controller,
|
|
36
|
+
FormRequest: () => FormRequest,
|
|
37
|
+
OrbitMonolith: () => OrbitMonolith,
|
|
38
|
+
Route: () => RouterHelper,
|
|
39
|
+
Schema: () => import_mass2.Schema
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/ContentManager.ts
|
|
44
|
+
var import_promises = require("fs/promises");
|
|
45
|
+
var import_node_path = require("path");
|
|
46
|
+
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
47
|
+
var import_marked = require("marked");
|
|
48
|
+
var ContentManager = class {
|
|
49
|
+
/**
|
|
50
|
+
* Create a new ContentManager instance.
|
|
51
|
+
*
|
|
52
|
+
* @param rootDir - The root directory of the application.
|
|
53
|
+
*/
|
|
54
|
+
constructor(rootDir) {
|
|
55
|
+
this.rootDir = rootDir;
|
|
56
|
+
}
|
|
57
|
+
collections = /* @__PURE__ */ new Map();
|
|
58
|
+
// Simple memory cache: collection:locale:slug -> ContentItem
|
|
59
|
+
cache = /* @__PURE__ */ new Map();
|
|
60
|
+
renderer = (() => {
|
|
61
|
+
const renderer = new import_marked.marked.Renderer();
|
|
62
|
+
renderer.html = (html) => this.escapeHtml(html);
|
|
63
|
+
renderer.link = (href, title, text) => {
|
|
64
|
+
if (!href || !this.isSafeUrl(href)) {
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
const safeHref = this.escapeHtml(href);
|
|
68
|
+
const titleAttr = title ? ` title="${this.escapeHtml(title)}"` : "";
|
|
69
|
+
return `<a href="${safeHref}"${titleAttr}>${text}</a>`;
|
|
70
|
+
};
|
|
71
|
+
return renderer;
|
|
72
|
+
})();
|
|
73
|
+
/**
|
|
74
|
+
* Register a new content collection.
|
|
75
|
+
*
|
|
76
|
+
* @param name - The name of the collection.
|
|
77
|
+
* @param config - The collection configuration.
|
|
78
|
+
*/
|
|
79
|
+
defineCollection(name, config) {
|
|
80
|
+
this.collections.set(name, config);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Find a single content item.
|
|
84
|
+
*
|
|
85
|
+
* @param collectionName - The collection name (e.g. 'docs').
|
|
86
|
+
* @param slug - The file slug (e.g. 'installation').
|
|
87
|
+
* @param locale - The locale (e.g. 'en'). Defaults to 'en'.
|
|
88
|
+
* @returns A promise resolving to the ContentItem or null if not found.
|
|
89
|
+
* @throws {Error} If the collection is not defined.
|
|
90
|
+
*/
|
|
91
|
+
async find(collectionName, slug, locale = "en") {
|
|
92
|
+
const config = this.collections.get(collectionName);
|
|
93
|
+
if (!config) {
|
|
94
|
+
throw new Error(`Collection '${collectionName}' not defined`);
|
|
95
|
+
}
|
|
96
|
+
const safeSlug = this.sanitizeSegment(slug);
|
|
97
|
+
const safeLocale = this.sanitizeSegment(locale);
|
|
98
|
+
if (!safeSlug || !safeLocale) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const cacheKey = `${collectionName}:${locale}:${slug}`;
|
|
102
|
+
if (this.cache.has(cacheKey)) {
|
|
103
|
+
return this.cache.get(cacheKey);
|
|
104
|
+
}
|
|
105
|
+
const filePath = (0, import_node_path.join)(this.rootDir, config.path, safeLocale, `${safeSlug}.md`);
|
|
106
|
+
try {
|
|
107
|
+
const exists = await (0, import_promises.stat)(filePath).then(() => true).catch(() => false);
|
|
108
|
+
if (!exists) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const fileContent = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
112
|
+
const { data, content, excerpt } = (0, import_gray_matter.default)(fileContent);
|
|
113
|
+
const html = await import_marked.marked.parse(content, { renderer: this.renderer });
|
|
114
|
+
const item = {
|
|
115
|
+
slug,
|
|
116
|
+
body: html,
|
|
117
|
+
meta: data,
|
|
118
|
+
raw: content,
|
|
119
|
+
excerpt
|
|
120
|
+
};
|
|
121
|
+
this.cache.set(cacheKey, item);
|
|
122
|
+
return item;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.error(`[Orbit-Content] Error reading file: ${filePath}`, e);
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* List all items in a collection for a locale.
|
|
130
|
+
* Useful for generating sitemaps or index pages.
|
|
131
|
+
*
|
|
132
|
+
* @param collectionName - The collection name.
|
|
133
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
134
|
+
* @returns A promise resolving to an array of ContentItems.
|
|
135
|
+
* @throws {Error} If the collection is not defined.
|
|
136
|
+
*/
|
|
137
|
+
async list(collectionName, locale = "en") {
|
|
138
|
+
const config = this.collections.get(collectionName);
|
|
139
|
+
if (!config) {
|
|
140
|
+
throw new Error(`Collection '${collectionName}' not defined`);
|
|
141
|
+
}
|
|
142
|
+
const safeLocale = this.sanitizeSegment(locale);
|
|
143
|
+
if (!safeLocale) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const dirPath = (0, import_node_path.join)(this.rootDir, config.path, safeLocale);
|
|
147
|
+
try {
|
|
148
|
+
const files = await (0, import_promises.readdir)(dirPath);
|
|
149
|
+
const items = [];
|
|
150
|
+
for (const file of files) {
|
|
151
|
+
if (!file.endsWith(".md")) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const slug = (0, import_node_path.parse)(file).name;
|
|
155
|
+
const item = await this.find(collectionName, slug, safeLocale);
|
|
156
|
+
if (item) {
|
|
157
|
+
items.push(item);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return items;
|
|
161
|
+
} catch (_e) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
sanitizeSegment(value) {
|
|
166
|
+
if (!value) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (value.includes("\0")) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (value.includes("/") || value.includes("\\")) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (value.includes("..")) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
escapeHtml(value) {
|
|
181
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
182
|
+
}
|
|
183
|
+
isSafeUrl(href) {
|
|
184
|
+
const trimmed = href.trim();
|
|
185
|
+
if (!trimmed) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const lower = trimmed.toLowerCase();
|
|
189
|
+
if (lower.startsWith("javascript:") || lower.startsWith("vbscript:") || lower.startsWith("data:")) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const schemeMatch = lower.match(/^[a-z][a-z0-9+.-]*:/);
|
|
193
|
+
if (!schemeMatch) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
const scheme = schemeMatch[0];
|
|
197
|
+
return scheme === "http:" || scheme === "https:" || scheme === "mailto:";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/index.ts
|
|
202
|
+
var import_mass2 = require("@gravito/mass");
|
|
203
|
+
|
|
204
|
+
// src/Sanitizer.ts
|
|
205
|
+
var Sanitizer = class {
|
|
206
|
+
/**
|
|
207
|
+
* Recursively trim strings and convert empty strings to null.
|
|
208
|
+
*/
|
|
209
|
+
static clean(data) {
|
|
210
|
+
if (!data || typeof data !== "object") {
|
|
211
|
+
return data;
|
|
212
|
+
}
|
|
213
|
+
const cleaned = Array.isArray(data) ? [] : {};
|
|
214
|
+
for (const key in data) {
|
|
215
|
+
let value = data[key];
|
|
216
|
+
if (typeof value === "string") {
|
|
217
|
+
value = value.trim();
|
|
218
|
+
if (value === "") {
|
|
219
|
+
value = null;
|
|
220
|
+
}
|
|
221
|
+
} else if (typeof value === "object" && value !== null) {
|
|
222
|
+
value = this.clean(value);
|
|
223
|
+
}
|
|
224
|
+
cleaned[key] = value;
|
|
225
|
+
}
|
|
226
|
+
return cleaned;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/Controller.ts
|
|
231
|
+
var BaseController = class {
|
|
232
|
+
sanitizer = new Sanitizer();
|
|
233
|
+
async call(ctx, method) {
|
|
234
|
+
const action = this[method];
|
|
235
|
+
if (typeof action !== "function") {
|
|
236
|
+
throw new Error(`Method ${method} not found on controller`);
|
|
237
|
+
}
|
|
238
|
+
return await action.apply(this, [ctx]);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var Controller = class {
|
|
242
|
+
context;
|
|
243
|
+
/**
|
|
244
|
+
* Set the request context for this controller instance.
|
|
245
|
+
*/
|
|
246
|
+
setContext(context) {
|
|
247
|
+
this.context = context;
|
|
248
|
+
return this;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Return a JSON response.
|
|
252
|
+
*/
|
|
253
|
+
json(data, status = 200) {
|
|
254
|
+
return this.context.json(data, status);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Return a text response.
|
|
258
|
+
*/
|
|
259
|
+
text(text, status = 200) {
|
|
260
|
+
return this.context.text(text, status);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Redirect to a given URL.
|
|
264
|
+
*/
|
|
265
|
+
redirect(url, status = 302) {
|
|
266
|
+
return this.context.redirect(url, status);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get an item from the context variables.
|
|
270
|
+
*/
|
|
271
|
+
get(key) {
|
|
272
|
+
return this.context.get(key);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get the request object.
|
|
276
|
+
*/
|
|
277
|
+
get request() {
|
|
278
|
+
return this.context.req;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Validate the request against a schema.
|
|
282
|
+
*/
|
|
283
|
+
async validate(_schema, source = "json") {
|
|
284
|
+
const req = this.context.req;
|
|
285
|
+
return req.valid(source);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Resolve a controller action into a handler compatible with GravitoContext.
|
|
289
|
+
*/
|
|
290
|
+
static call(method) {
|
|
291
|
+
return async (c) => {
|
|
292
|
+
const instance = new this();
|
|
293
|
+
instance.setContext(c);
|
|
294
|
+
const action = instance[method];
|
|
295
|
+
return action.apply(instance, [c]);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// src/FormRequest.ts
|
|
301
|
+
var import_mass = require("@gravito/mass");
|
|
302
|
+
var FormRequest = class {
|
|
303
|
+
context;
|
|
304
|
+
/**
|
|
305
|
+
* Define the source of data (json, query, form, etc.).
|
|
306
|
+
*/
|
|
307
|
+
source() {
|
|
308
|
+
return "json";
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Determine if the user is authorized to make this request.
|
|
312
|
+
*/
|
|
313
|
+
authorize() {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Set the context.
|
|
318
|
+
*/
|
|
319
|
+
setContext(context) {
|
|
320
|
+
this.context = context;
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get the validated data.
|
|
325
|
+
*/
|
|
326
|
+
validated() {
|
|
327
|
+
const data = this.context.req.valid(this.source());
|
|
328
|
+
return Sanitizer.clean(data);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Static helper to create a middleware from this request class.
|
|
332
|
+
*/
|
|
333
|
+
static middleware() {
|
|
334
|
+
const RequestClass = this;
|
|
335
|
+
return async (c, next) => {
|
|
336
|
+
const instance = new RequestClass();
|
|
337
|
+
instance.setContext(c);
|
|
338
|
+
if (!instance.authorize()) {
|
|
339
|
+
return c.json({ message: "This action is unauthorized." }, 403);
|
|
340
|
+
}
|
|
341
|
+
const validator = (0, import_mass.validate)(instance.source(), instance.schema(), (result, ctx) => {
|
|
342
|
+
if (!result.success) {
|
|
343
|
+
const errors = {};
|
|
344
|
+
const issues = result.error?.issues || [];
|
|
345
|
+
for (const issue of issues) {
|
|
346
|
+
const path = Array.isArray(issue.path) ? issue.path.join(".") : issue.path || "root";
|
|
347
|
+
const key = path.replace(/^\//, "").replace(/\//g, ".");
|
|
348
|
+
if (!errors[key]) errors[key] = [];
|
|
349
|
+
errors[key].push(issue.message || "Validation failed");
|
|
350
|
+
}
|
|
351
|
+
return ctx.json(
|
|
352
|
+
{
|
|
353
|
+
message: "The given data was invalid.",
|
|
354
|
+
errors: Object.keys(errors).length > 0 ? errors : { root: ["Validation failed"] }
|
|
355
|
+
},
|
|
356
|
+
422
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
return validator(c, next);
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// src/Router.ts
|
|
366
|
+
var RouterHelper = class {
|
|
367
|
+
/**
|
|
368
|
+
* Register standard resource routes for a controller.
|
|
369
|
+
*
|
|
370
|
+
* GET /prefix -> index
|
|
371
|
+
* GET /prefix/create -> create
|
|
372
|
+
* POST /prefix -> store
|
|
373
|
+
* GET /prefix/:id -> show
|
|
374
|
+
* GET /prefix/:id/edit -> edit
|
|
375
|
+
* PUT /prefix/:id -> update
|
|
376
|
+
* DELETE /prefix/:id -> destroy
|
|
377
|
+
*/
|
|
378
|
+
static resource(app, prefix, controller) {
|
|
379
|
+
const p = prefix.startsWith("/") ? prefix : `/${prefix}`;
|
|
380
|
+
const routes = {
|
|
381
|
+
index: ["get", ""],
|
|
382
|
+
create: ["get", "/create"],
|
|
383
|
+
store: ["post", ""],
|
|
384
|
+
show: ["get", "/:id"],
|
|
385
|
+
edit: ["get", "/:id/edit"],
|
|
386
|
+
update: ["put", "/:id"],
|
|
387
|
+
destroy: ["delete", "/:id"]
|
|
388
|
+
};
|
|
389
|
+
for (const [method, [verb, suffix]] of Object.entries(routes)) {
|
|
390
|
+
if (typeof controller.prototype[method] === "function") {
|
|
391
|
+
;
|
|
392
|
+
app[verb](`${p}${suffix}`, controller.call(method));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/index.ts
|
|
399
|
+
var OrbitMonolith = class {
|
|
400
|
+
constructor(config = {}) {
|
|
401
|
+
this.config = config;
|
|
402
|
+
}
|
|
403
|
+
install(core) {
|
|
404
|
+
const root = this.config.root || process.cwd();
|
|
405
|
+
const manager = new ContentManager(root);
|
|
406
|
+
if (this.config.collections) {
|
|
407
|
+
for (const [name, config] of Object.entries(this.config.collections)) {
|
|
408
|
+
manager.defineCollection(name, config);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
core.adapter.use("*", async (c, next) => {
|
|
412
|
+
c.set("content", manager);
|
|
413
|
+
await next();
|
|
414
|
+
return void 0;
|
|
415
|
+
});
|
|
416
|
+
core.logger.info("Orbit Monolith installed \u2B1B\uFE0F");
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
420
|
+
0 && (module.exports = {
|
|
421
|
+
BaseController,
|
|
422
|
+
ContentManager,
|
|
423
|
+
Controller,
|
|
424
|
+
FormRequest,
|
|
425
|
+
OrbitMonolith,
|
|
426
|
+
Route,
|
|
427
|
+
Schema
|
|
428
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as _gravito_core from '@gravito/core';
|
|
2
|
+
import { GravitoContext, GravitoOrbit, PlanetCore } from '@gravito/core';
|
|
3
|
+
import { TSchema } from '@gravito/mass';
|
|
4
|
+
export { Schema } from '@gravito/mass';
|
|
5
|
+
import { Hono } from 'hono';
|
|
6
|
+
|
|
7
|
+
interface ContentItem {
|
|
8
|
+
slug: string;
|
|
9
|
+
body: string;
|
|
10
|
+
excerpt?: string;
|
|
11
|
+
meta: Record<string, any>;
|
|
12
|
+
raw: string;
|
|
13
|
+
}
|
|
14
|
+
interface CollectionConfig {
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
declare class ContentManager {
|
|
18
|
+
private rootDir;
|
|
19
|
+
private collections;
|
|
20
|
+
private cache;
|
|
21
|
+
private renderer;
|
|
22
|
+
/**
|
|
23
|
+
* Create a new ContentManager instance.
|
|
24
|
+
*
|
|
25
|
+
* @param rootDir - The root directory of the application.
|
|
26
|
+
*/
|
|
27
|
+
constructor(rootDir: string);
|
|
28
|
+
/**
|
|
29
|
+
* Register a new content collection.
|
|
30
|
+
*
|
|
31
|
+
* @param name - The name of the collection.
|
|
32
|
+
* @param config - The collection configuration.
|
|
33
|
+
*/
|
|
34
|
+
defineCollection(name: string, config: CollectionConfig): void;
|
|
35
|
+
/**
|
|
36
|
+
* Find a single content item.
|
|
37
|
+
*
|
|
38
|
+
* @param collectionName - The collection name (e.g. 'docs').
|
|
39
|
+
* @param slug - The file slug (e.g. 'installation').
|
|
40
|
+
* @param locale - The locale (e.g. 'en'). Defaults to 'en'.
|
|
41
|
+
* @returns A promise resolving to the ContentItem or null if not found.
|
|
42
|
+
* @throws {Error} If the collection is not defined.
|
|
43
|
+
*/
|
|
44
|
+
find(collectionName: string, slug: string, locale?: string): Promise<ContentItem | null>;
|
|
45
|
+
/**
|
|
46
|
+
* List all items in a collection for a locale.
|
|
47
|
+
* Useful for generating sitemaps or index pages.
|
|
48
|
+
*
|
|
49
|
+
* @param collectionName - The collection name.
|
|
50
|
+
* @param locale - The locale. Defaults to 'en'.
|
|
51
|
+
* @returns A promise resolving to an array of ContentItems.
|
|
52
|
+
* @throws {Error} If the collection is not defined.
|
|
53
|
+
*/
|
|
54
|
+
list(collectionName: string, locale?: string): Promise<ContentItem[]>;
|
|
55
|
+
private sanitizeSegment;
|
|
56
|
+
private escapeHtml;
|
|
57
|
+
private isSafeUrl;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Utility to clean up request data.
|
|
62
|
+
*/
|
|
63
|
+
declare class Sanitizer {
|
|
64
|
+
/**
|
|
65
|
+
* Recursively trim strings and convert empty strings to null.
|
|
66
|
+
*/
|
|
67
|
+
static clean(data: any): any;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare abstract class BaseController {
|
|
71
|
+
protected sanitizer: Sanitizer;
|
|
72
|
+
call(ctx: GravitoContext, method: string): Promise<Response>;
|
|
73
|
+
}
|
|
74
|
+
declare abstract class Controller {
|
|
75
|
+
protected context: GravitoContext;
|
|
76
|
+
/**
|
|
77
|
+
* Set the request context for this controller instance.
|
|
78
|
+
*/
|
|
79
|
+
setContext(context: GravitoContext): this;
|
|
80
|
+
/**
|
|
81
|
+
* Return a JSON response.
|
|
82
|
+
*/
|
|
83
|
+
protected json(data: any, status?: number): Response;
|
|
84
|
+
/**
|
|
85
|
+
* Return a text response.
|
|
86
|
+
*/
|
|
87
|
+
protected text(text: string, status?: number): Response;
|
|
88
|
+
/**
|
|
89
|
+
* Redirect to a given URL.
|
|
90
|
+
*/
|
|
91
|
+
protected redirect(url: string, status?: number): Response;
|
|
92
|
+
/**
|
|
93
|
+
* Get an item from the context variables.
|
|
94
|
+
*/
|
|
95
|
+
protected get(key: string): any;
|
|
96
|
+
/**
|
|
97
|
+
* Get the request object.
|
|
98
|
+
*/
|
|
99
|
+
protected get request(): _gravito_core.GravitoRequest;
|
|
100
|
+
/**
|
|
101
|
+
* Validate the request against a schema.
|
|
102
|
+
*/
|
|
103
|
+
protected validate(_schema: any, source?: 'json' | 'query' | 'form'): Promise<any>;
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a controller action into a handler compatible with GravitoContext.
|
|
106
|
+
*/
|
|
107
|
+
static call(method: string): any;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
declare abstract class FormRequest {
|
|
111
|
+
protected context: GravitoContext;
|
|
112
|
+
/**
|
|
113
|
+
* Define the validation schema.
|
|
114
|
+
*/
|
|
115
|
+
abstract schema(): TSchema;
|
|
116
|
+
/**
|
|
117
|
+
* Define the source of data (json, query, form, etc.).
|
|
118
|
+
*/
|
|
119
|
+
source(): 'json' | 'query' | 'form';
|
|
120
|
+
/**
|
|
121
|
+
* Determine if the user is authorized to make this request.
|
|
122
|
+
*/
|
|
123
|
+
authorize(): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Set the context.
|
|
126
|
+
*/
|
|
127
|
+
setContext(context: GravitoContext): this;
|
|
128
|
+
/**
|
|
129
|
+
* Get the validated data.
|
|
130
|
+
*/
|
|
131
|
+
validated(): any;
|
|
132
|
+
/**
|
|
133
|
+
* Static helper to create a middleware from this request class.
|
|
134
|
+
*/
|
|
135
|
+
static middleware(): any;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare class RouterHelper {
|
|
139
|
+
/**
|
|
140
|
+
* Register standard resource routes for a controller.
|
|
141
|
+
*
|
|
142
|
+
* GET /prefix -> index
|
|
143
|
+
* GET /prefix/create -> create
|
|
144
|
+
* POST /prefix -> store
|
|
145
|
+
* GET /prefix/:id -> show
|
|
146
|
+
* GET /prefix/:id/edit -> edit
|
|
147
|
+
* PUT /prefix/:id -> update
|
|
148
|
+
* DELETE /prefix/:id -> destroy
|
|
149
|
+
*/
|
|
150
|
+
static resource(app: Hono<any, any, any>, prefix: string, controller: any): void;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
declare module '@gravito/core' {
|
|
154
|
+
interface Variables {
|
|
155
|
+
content: ContentManager;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
interface ContentConfig {
|
|
159
|
+
root?: string;
|
|
160
|
+
collections?: Record<string, CollectionConfig>;
|
|
161
|
+
}
|
|
162
|
+
declare class OrbitMonolith implements GravitoOrbit {
|
|
163
|
+
private config;
|
|
164
|
+
constructor(config?: ContentConfig);
|
|
165
|
+
install(core: PlanetCore): void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { BaseController, type CollectionConfig, type ContentConfig, type ContentItem, ContentManager, Controller, FormRequest, OrbitMonolith, RouterHelper as Route };
|