@constela/start 0.3.0 → 0.4.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,399 @@
1
+ // src/router/file-router.ts
2
+ import fg from "fast-glob";
3
+ import { existsSync, statSync } from "fs";
4
+ import { join } from "path";
5
+ function filePathToPattern(filePath, _routesDir) {
6
+ let normalized = filePath.replace(/\\/g, "/");
7
+ normalized = normalized.replace(/\.(ts|tsx|js|jsx)$/, "");
8
+ const segments = normalized.split("/");
9
+ const processedSegments = segments.map((segment) => {
10
+ if (segment.startsWith("[...") && segment.endsWith("]")) {
11
+ return "*";
12
+ }
13
+ if (segment.startsWith("[") && segment.endsWith("]")) {
14
+ const paramName = segment.slice(1, -1);
15
+ return `:${paramName}`;
16
+ }
17
+ return segment;
18
+ });
19
+ if (processedSegments.at(-1) === "index") {
20
+ processedSegments.pop();
21
+ }
22
+ const path = "/" + processedSegments.join("/");
23
+ if (path === "/") {
24
+ return "/";
25
+ }
26
+ return path.endsWith("/") ? path.slice(0, -1) : path;
27
+ }
28
+ function extractParams(filePath) {
29
+ const params = [];
30
+ const normalized = filePath.replace(/\\/g, "/");
31
+ const regex = /\[(?:\.\.\.)?([^\]]+)\]/g;
32
+ let match;
33
+ while ((match = regex.exec(normalized)) !== null) {
34
+ const paramName = match[1];
35
+ if (paramName !== void 0) {
36
+ params.push(paramName);
37
+ }
38
+ }
39
+ return params;
40
+ }
41
+ function determineRouteType(filePath) {
42
+ const normalized = filePath.replace(/\\/g, "/");
43
+ const fileName = normalized.split("/").pop() ?? "";
44
+ if (fileName.startsWith("_middleware.")) {
45
+ return "middleware";
46
+ }
47
+ if (normalized.startsWith("api/") || normalized.includes("/api/")) {
48
+ return "api";
49
+ }
50
+ return "page";
51
+ }
52
+ function shouldIncludeRoute(filePath) {
53
+ const normalized = filePath.replace(/\\/g, "/");
54
+ const fileName = normalized.split("/").pop() ?? "";
55
+ if (fileName.endsWith(".d.ts")) {
56
+ return false;
57
+ }
58
+ if (fileName.startsWith("_") && !fileName.startsWith("_middleware.")) {
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ async function scanRoutes(routesDir) {
64
+ if (!existsSync(routesDir)) {
65
+ throw new Error(`Routes directory does not exist: ${routesDir}`);
66
+ }
67
+ const stats = statSync(routesDir);
68
+ if (!stats.isDirectory()) {
69
+ throw new Error(`Routes path is not a directory: ${routesDir}`);
70
+ }
71
+ const files = await fg("**/*.{ts,tsx,js,jsx}", {
72
+ cwd: routesDir,
73
+ ignore: ["node_modules/**", "**/*.d.ts"],
74
+ onlyFiles: true,
75
+ followSymbolicLinks: false
76
+ });
77
+ const routes = [];
78
+ for (const filePath of files) {
79
+ if (!shouldIncludeRoute(filePath)) {
80
+ continue;
81
+ }
82
+ const route = {
83
+ file: join(routesDir, filePath),
84
+ pattern: filePathToPattern(filePath, routesDir),
85
+ type: determineRouteType(filePath),
86
+ params: extractParams(filePath)
87
+ };
88
+ routes.push(route);
89
+ }
90
+ routes.sort((a, b) => {
91
+ return compareRoutes(a, b);
92
+ });
93
+ return routes;
94
+ }
95
+ function compareRoutes(a, b) {
96
+ if (a.type === "middleware" && b.type !== "middleware") return -1;
97
+ if (b.type === "middleware" && a.type !== "middleware") return 1;
98
+ const segmentsA = a.pattern.split("/").filter(Boolean);
99
+ const segmentsB = b.pattern.split("/").filter(Boolean);
100
+ const minLen = Math.min(segmentsA.length, segmentsB.length);
101
+ for (let i = 0; i < minLen; i++) {
102
+ const segA = segmentsA[i] ?? "";
103
+ const segB = segmentsB[i] ?? "";
104
+ const typeA = getSegmentType(segA);
105
+ const typeB = getSegmentType(segB);
106
+ if (typeA !== typeB) {
107
+ return typeA - typeB;
108
+ }
109
+ if (typeA === 0 && segA !== segB) {
110
+ return segA.localeCompare(segB);
111
+ }
112
+ }
113
+ if (segmentsA.length !== segmentsB.length) {
114
+ return segmentsA.length - segmentsB.length;
115
+ }
116
+ return a.pattern.localeCompare(b.pattern);
117
+ }
118
+ function getSegmentType(segment) {
119
+ if (segment === "*") return 2;
120
+ if (segment.startsWith(":")) return 1;
121
+ return 0;
122
+ }
123
+
124
+ // src/static/index.ts
125
+ import { extname, join as join2, normalize, resolve } from "path";
126
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
127
+ var MIME_TYPES = {
128
+ // Images
129
+ ".ico": "image/x-icon",
130
+ ".png": "image/png",
131
+ ".jpg": "image/jpeg",
132
+ ".jpeg": "image/jpeg",
133
+ ".gif": "image/gif",
134
+ ".svg": "image/svg+xml",
135
+ ".webp": "image/webp",
136
+ // Fonts
137
+ ".woff": "font/woff",
138
+ ".woff2": "font/woff2",
139
+ ".ttf": "font/ttf",
140
+ ".otf": "font/otf",
141
+ ".eot": "application/vnd.ms-fontobject",
142
+ // Web assets
143
+ ".css": "text/css",
144
+ ".js": "text/javascript",
145
+ ".json": "application/json",
146
+ ".txt": "text/plain",
147
+ ".html": "text/html",
148
+ ".xml": "application/xml",
149
+ // Other
150
+ ".pdf": "application/pdf",
151
+ ".mp3": "audio/mpeg",
152
+ ".mp4": "video/mp4",
153
+ ".webm": "video/webm",
154
+ ".map": "application/json"
155
+ };
156
+ var DEFAULT_MIME_TYPE = "application/octet-stream";
157
+ function isPathSafe(pathname) {
158
+ if (!pathname) {
159
+ return false;
160
+ }
161
+ if (!pathname.startsWith("/")) {
162
+ return false;
163
+ }
164
+ if (pathname.includes("\\")) {
165
+ return false;
166
+ }
167
+ if (pathname.includes("//")) {
168
+ return false;
169
+ }
170
+ if (pathname.includes("\0") || pathname.includes("%00")) {
171
+ return false;
172
+ }
173
+ let decoded;
174
+ try {
175
+ decoded = pathname;
176
+ let prevDecoded = "";
177
+ while (decoded !== prevDecoded) {
178
+ prevDecoded = decoded;
179
+ decoded = decodeURIComponent(decoded);
180
+ }
181
+ } catch {
182
+ return false;
183
+ }
184
+ if (decoded.includes("\0")) {
185
+ return false;
186
+ }
187
+ if (decoded.includes("..")) {
188
+ return false;
189
+ }
190
+ const segments = decoded.split("/").filter(Boolean);
191
+ for (const segment of segments) {
192
+ if (segment.startsWith(".")) {
193
+ return false;
194
+ }
195
+ }
196
+ return true;
197
+ }
198
+ function getMimeType(filePath) {
199
+ const ext = extname(filePath).toLowerCase();
200
+ return MIME_TYPES[ext] ?? DEFAULT_MIME_TYPE;
201
+ }
202
+ function hasObviousAttackPattern(pathname) {
203
+ if (!pathname) {
204
+ return true;
205
+ }
206
+ if (!pathname.startsWith("/")) {
207
+ return true;
208
+ }
209
+ if (pathname.includes("\\")) {
210
+ return true;
211
+ }
212
+ if (pathname.includes("//")) {
213
+ return true;
214
+ }
215
+ if (pathname.includes("\0") || pathname.includes("%00")) {
216
+ return true;
217
+ }
218
+ let decoded;
219
+ try {
220
+ decoded = pathname;
221
+ let prevDecoded = "";
222
+ while (decoded !== prevDecoded) {
223
+ prevDecoded = decoded;
224
+ decoded = decodeURIComponent(decoded);
225
+ }
226
+ } catch {
227
+ return true;
228
+ }
229
+ if (decoded.includes("\0")) {
230
+ return true;
231
+ }
232
+ const segments = decoded.split("/").filter(Boolean);
233
+ for (const segment of segments) {
234
+ if (segment.startsWith(".") && segment !== "..") {
235
+ return true;
236
+ }
237
+ }
238
+ if (decoded.startsWith("/..")) {
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+ function resolveStaticFile(pathname, publicDir) {
244
+ if (hasObviousAttackPattern(pathname)) {
245
+ return {
246
+ exists: false,
247
+ filePath: null,
248
+ mimeType: null,
249
+ error: "path_traversal"
250
+ };
251
+ }
252
+ let decodedPathname;
253
+ try {
254
+ decodedPathname = decodeURIComponent(pathname);
255
+ } catch {
256
+ return {
257
+ exists: false,
258
+ filePath: null,
259
+ mimeType: null,
260
+ error: "path_traversal"
261
+ };
262
+ }
263
+ const relativePath = decodedPathname.slice(1);
264
+ const resolvedPath = normalize(join2(publicDir, relativePath));
265
+ const absolutePublicDir = resolve(publicDir);
266
+ const publicDirWithSep = absolutePublicDir.endsWith("/") ? absolutePublicDir : absolutePublicDir + "/";
267
+ if (!resolvedPath.startsWith(publicDirWithSep) && resolvedPath !== absolutePublicDir) {
268
+ return {
269
+ exists: false,
270
+ filePath: null,
271
+ mimeType: null,
272
+ error: "outside_public"
273
+ };
274
+ }
275
+ const mimeType = getMimeType(resolvedPath);
276
+ let exists = false;
277
+ if (existsSync2(resolvedPath)) {
278
+ try {
279
+ const stats = statSync2(resolvedPath);
280
+ exists = stats.isFile();
281
+ } catch {
282
+ exists = false;
283
+ }
284
+ }
285
+ return {
286
+ exists,
287
+ filePath: resolvedPath,
288
+ mimeType
289
+ };
290
+ }
291
+
292
+ // src/dev/server.ts
293
+ import { createServer } from "http";
294
+ import { createReadStream } from "fs";
295
+ import { join as join3 } from "path";
296
+ var DEFAULT_PORT = 3e3;
297
+ var DEFAULT_HOST = "localhost";
298
+ var DEFAULT_PUBLIC_DIR = "public";
299
+ async function createDevServer(options = {}) {
300
+ const {
301
+ port = DEFAULT_PORT,
302
+ host = DEFAULT_HOST,
303
+ routesDir: _routesDir = "src/routes",
304
+ publicDir = join3(process.cwd(), DEFAULT_PUBLIC_DIR)
305
+ } = options;
306
+ let httpServer = null;
307
+ let actualPort = port;
308
+ const devServer = {
309
+ get port() {
310
+ return actualPort;
311
+ },
312
+ async listen() {
313
+ return new Promise((resolve2, reject) => {
314
+ httpServer = createServer((req, res) => {
315
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
316
+ const pathname = url.pathname;
317
+ const staticResult = resolveStaticFile(pathname, publicDir);
318
+ if (staticResult.error === "path_traversal" || staticResult.error === "outside_public") {
319
+ res.writeHead(403, { "Content-Type": "text/plain" });
320
+ res.end("Forbidden");
321
+ return;
322
+ }
323
+ if (staticResult.exists && staticResult.filePath && staticResult.mimeType) {
324
+ res.writeHead(200, { "Content-Type": staticResult.mimeType });
325
+ const stream = createReadStream(staticResult.filePath);
326
+ stream.pipe(res);
327
+ stream.on("error", () => {
328
+ if (!res.headersSent) {
329
+ res.writeHead(500, { "Content-Type": "text/plain" });
330
+ }
331
+ res.end("Internal Server Error");
332
+ });
333
+ return;
334
+ }
335
+ if (staticResult.filePath && !staticResult.exists) {
336
+ res.writeHead(404, { "Content-Type": "text/plain" });
337
+ res.end("Not Found");
338
+ return;
339
+ }
340
+ res.writeHead(200, { "Content-Type": "text/html" });
341
+ res.end("<html><body>Constela Dev Server</body></html>");
342
+ });
343
+ httpServer.on("error", (err) => {
344
+ reject(err);
345
+ });
346
+ httpServer.listen(port, host, () => {
347
+ const address = httpServer?.address();
348
+ if (address) {
349
+ actualPort = address.port;
350
+ }
351
+ resolve2();
352
+ });
353
+ });
354
+ },
355
+ async close() {
356
+ return new Promise((resolve2, reject) => {
357
+ if (!httpServer) {
358
+ resolve2();
359
+ return;
360
+ }
361
+ httpServer.close((err) => {
362
+ if (err) {
363
+ reject(err);
364
+ } else {
365
+ httpServer = null;
366
+ resolve2();
367
+ }
368
+ });
369
+ });
370
+ }
371
+ };
372
+ return devServer;
373
+ }
374
+
375
+ // src/build/index.ts
376
+ async function build(options) {
377
+ const outDir = options?.outDir ?? "dist";
378
+ const routesDir = options?.routesDir ?? "src/routes";
379
+ let routes = [];
380
+ try {
381
+ const scannedRoutes = await scanRoutes(routesDir);
382
+ routes = scannedRoutes.map((r) => r.pattern);
383
+ } catch {
384
+ }
385
+ return {
386
+ outDir,
387
+ routes
388
+ };
389
+ }
390
+
391
+ export {
392
+ filePathToPattern,
393
+ scanRoutes,
394
+ isPathSafe,
395
+ getMimeType,
396
+ resolveStaticFile,
397
+ createDevServer,
398
+ build
399
+ };
package/dist/cli/index.js CHANGED
@@ -1,15 +1,39 @@
1
+ import {
2
+ build,
3
+ createDevServer
4
+ } from "../chunk-JXIOHPG5.js";
5
+
1
6
  // src/cli/index.ts
2
7
  import { Command } from "commander";
3
8
  var devHandler = async (options) => {
4
- console.log("Development server not yet implemented");
5
- return { port: Number(options.port) };
9
+ const port = parseInt(options.port, 10);
10
+ const host = options.host ?? "localhost";
11
+ const server = await createDevServer({ port, host });
12
+ await server.listen();
13
+ console.log(`Development server running at http://${host}:${server.port}`);
14
+ process.on("SIGINT", async () => {
15
+ console.log("\nShutting down server...");
16
+ await server.close();
17
+ process.exit(0);
18
+ });
19
+ return { port: server.port };
6
20
  };
7
- var buildHandler = async () => {
8
- console.log("Build not yet implemented");
21
+ var buildHandler = async (options) => {
22
+ console.log("Building for production...");
23
+ await build(options.outDir ? { outDir: options.outDir } : {});
24
+ console.log("Build complete");
9
25
  };
10
26
  var startHandler = async (options) => {
11
- console.log("Production server not yet implemented");
12
- return { port: Number(options.port) };
27
+ const port = parseInt(options.port, 10);
28
+ const server = await createDevServer({ port, host: "0.0.0.0" });
29
+ await server.listen();
30
+ console.log(`Production server running at http://0.0.0.0:${server.port}`);
31
+ process.on("SIGINT", async () => {
32
+ console.log("\nShutting down server...");
33
+ await server.close();
34
+ process.exit(0);
35
+ });
36
+ return { port: server.port };
13
37
  };
14
38
  function setDevHandler(handler) {
15
39
  devHandler = handler;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CompiledProgram } from '@constela/compiler';
2
+ import { LayoutProgram, Program, StaticPathsDefinition, DataSource } from '@constela/core';
2
3
 
3
4
  /**
4
5
  * Scanned route from file system
@@ -38,11 +39,22 @@ interface MiddlewareContext {
38
39
  }
39
40
  type MiddlewareNext = () => Promise<Response>;
40
41
  type Middleware = (ctx: MiddlewareContext, next: MiddlewareNext) => Promise<Response> | Response;
42
+ /**
43
+ * Function-form page export that receives route params.
44
+ *
45
+ * @example
46
+ * // pages/users/[id].ts
47
+ * export default async function(params: Record<string, string>) {
48
+ * const user = await fetchUser(params.id);
49
+ * return createUserPage(user);
50
+ * }
51
+ */
52
+ type PageExportFunction = (params: Record<string, string>) => Promise<CompiledProgram> | CompiledProgram;
41
53
  /**
42
54
  * Page module with Constela program
43
55
  */
44
56
  interface PageModule {
45
- default: CompiledProgram;
57
+ default: CompiledProgram | PageExportFunction;
46
58
  getStaticPaths?: () => Promise<StaticPathsResult> | StaticPathsResult;
47
59
  }
48
60
  interface StaticPathsResult {
@@ -185,14 +197,30 @@ interface BuildResult {
185
197
  */
186
198
  declare function build(options?: BuildOptions): Promise<BuildResult>;
187
199
 
200
+ /**
201
+ * Provider function for static paths when getStaticPaths is not available in the module.
202
+ * Used for dependency injection in tests.
203
+ */
204
+ type StaticPathsProvider = (pattern: string) => StaticPathsResult | null | Promise<StaticPathsResult | null>;
205
+ /**
206
+ * Options for generateStaticPages function
207
+ */
208
+ interface GenerateStaticPagesOptions {
209
+ /**
210
+ * Optional provider for static paths when module doesn't export getStaticPaths.
211
+ * Primarily used for testing purposes.
212
+ */
213
+ staticPathsProvider?: StaticPathsProvider;
214
+ }
188
215
  /**
189
216
  * Generate static pages for SSG routes
190
217
  *
191
218
  * @param routes - Array of scanned routes
192
219
  * @param outDir - Output directory for generated HTML files
220
+ * @param options - Optional configuration including staticPathsProvider for DI
193
221
  * @returns Array of generated file paths
194
222
  */
195
- declare function generateStaticPages(routes: ScannedRoute[], outDir: string): Promise<string[]>;
223
+ declare function generateStaticPages(routes: ScannedRoute[], outDir: string, options?: GenerateStaticPagesOptions): Promise<string[]>;
196
224
 
197
225
  /**
198
226
  * Create handler function from API module
@@ -227,4 +255,187 @@ interface EdgeAdapter {
227
255
  */
228
256
  declare function createAdapter(options: AdapterOptions): EdgeAdapter;
229
257
 
230
- export { type APIContext, type APIModule, type BuildOptions, type ConstelaConfig, type DevServerOptions, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageModule, type ScannedRoute, type StaticFileResult, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, getMimeType, isPathSafe, resolveStaticFile, scanRoutes };
258
+ /**
259
+ * Type guard to check if a page export is a function (dynamic) or a static program
260
+ *
261
+ * @param exported - The default export from a page module
262
+ * @returns true if the export is a function, false if it's a static CompiledProgram
263
+ */
264
+ declare function isPageExportFunction(exported: CompiledProgram | PageExportFunction): exported is PageExportFunction;
265
+ /**
266
+ * Resolve a page export to a CompiledProgram
267
+ *
268
+ * If the export is a function, it will be called with the route params.
269
+ * If the export is a static CompiledProgram, it will be returned as-is.
270
+ *
271
+ * @param pageDefault - The default export from a page module (function or static program)
272
+ * @param params - Route parameters extracted from the URL
273
+ * @param expectedParams - Optional array of expected parameter names for validation
274
+ * @returns Promise resolving to a CompiledProgram
275
+ * @throws Error if expectedParams is provided and a required param is missing
276
+ */
277
+ declare function resolvePageExport(pageDefault: CompiledProgram | PageExportFunction, params: Record<string, string>, expectedParams?: string[]): Promise<CompiledProgram>;
278
+
279
+ /**
280
+ * Layout Resolver - Scans and resolves layout files for Constela Start
281
+ *
282
+ * This module provides functionality to:
283
+ * - Scan layout directories for layout files
284
+ * - Resolve layout by name
285
+ * - Load and validate layout programs
286
+ * - Compose layouts with page content
287
+ */
288
+
289
+ interface ScannedLayout {
290
+ name: string;
291
+ file: string;
292
+ }
293
+ interface LayoutInfo {
294
+ name: string;
295
+ file: string;
296
+ program?: LayoutProgram;
297
+ }
298
+ /**
299
+ * Scans a directory for layout files
300
+ *
301
+ * @param layoutsDir - Directory to scan for layouts
302
+ * @returns Array of scanned layouts with name and file path
303
+ * @throws Error if directory doesn't exist or is not a directory
304
+ */
305
+ declare function scanLayouts(layoutsDir: string): Promise<ScannedLayout[]>;
306
+ /**
307
+ * Resolves a layout by name from scanned layouts
308
+ *
309
+ * @param layoutName - Name of the layout to resolve
310
+ * @param layouts - Array of scanned layouts
311
+ * @returns The matching layout or undefined
312
+ */
313
+ declare function resolveLayout(layoutName: string, layouts: ScannedLayout[]): ScannedLayout | undefined;
314
+ /**
315
+ * Loads and validates a layout file
316
+ *
317
+ * @param layoutFile - Path to the layout file
318
+ * @returns The loaded LayoutProgram
319
+ * @throws Error if file cannot be loaded or is not a valid layout
320
+ */
321
+ declare function loadLayout(layoutFile: string): Promise<LayoutProgram>;
322
+ /**
323
+ * LayoutResolver - Manages layout scanning, resolution, and composition
324
+ */
325
+ declare class LayoutResolver {
326
+ private layoutsDir;
327
+ private layouts;
328
+ private loadedLayouts;
329
+ private initialized;
330
+ constructor(layoutsDir: string);
331
+ /**
332
+ * Initialize the resolver by scanning the layouts directory
333
+ */
334
+ initialize(): Promise<void>;
335
+ /**
336
+ * Check if a layout exists
337
+ */
338
+ hasLayout(name: string): boolean;
339
+ /**
340
+ * Get a layout by name
341
+ *
342
+ * @param name - Layout name
343
+ * @returns The layout program or undefined if not found
344
+ */
345
+ getLayout(name: string): Promise<LayoutProgram | undefined>;
346
+ /**
347
+ * Compose a page with its layout
348
+ *
349
+ * @param page - Page program to compose
350
+ * @returns Composed program (or original if no layout)
351
+ * @throws Error if specified layout is not found
352
+ */
353
+ composeWithLayout(page: Program): Promise<Program>;
354
+ /**
355
+ * Get all scanned layouts
356
+ */
357
+ getAll(): ScannedLayout[];
358
+ }
359
+
360
+ /**
361
+ * Data Loader for Build-time Content Loading
362
+ *
363
+ * This module provides utilities for loading data sources at build time:
364
+ * - Glob patterns for multiple files
365
+ * - Single file loading
366
+ * - API fetching
367
+ * - MDX/YAML/CSV transformations
368
+ * - Static path generation for SSG
369
+ */
370
+
371
+ interface GlobResult {
372
+ file: string;
373
+ raw: string;
374
+ frontmatter?: Record<string, unknown>;
375
+ content?: string;
376
+ }
377
+ interface StaticPath {
378
+ params: Record<string, string>;
379
+ data?: unknown;
380
+ }
381
+ /**
382
+ * Transform MDX content - parse frontmatter and content
383
+ */
384
+ declare function transformMdx(content: string): {
385
+ frontmatter: Record<string, unknown>;
386
+ content: string;
387
+ };
388
+ /**
389
+ * Transform YAML content
390
+ */
391
+ declare function transformYaml(content: string): Record<string, unknown>;
392
+ /**
393
+ * Transform CSV content to array of objects
394
+ */
395
+ declare function transformCsv(content: string): Record<string, string>[];
396
+ /**
397
+ * Load files matching a glob pattern
398
+ */
399
+ declare function loadGlob(baseDir: string, pattern: string, transform?: string): Promise<GlobResult[]>;
400
+ /**
401
+ * Load a single file
402
+ */
403
+ declare function loadFile(baseDir: string, filePath: string, transform?: string): Promise<unknown>;
404
+ /**
405
+ * Load data from API endpoint
406
+ */
407
+ declare function loadApi(url: string, transform?: string): Promise<unknown>;
408
+ /**
409
+ * Generate static paths from data using params expression
410
+ */
411
+ declare function generateStaticPaths(data: unknown[], staticPathsDef: StaticPathsDefinition): Promise<StaticPath[]>;
412
+ /**
413
+ * DataLoader class for managing data source loading with caching
414
+ */
415
+ declare class DataLoader {
416
+ private cache;
417
+ private projectRoot;
418
+ constructor(projectRoot: string);
419
+ /**
420
+ * Load a single data source
421
+ */
422
+ loadDataSource(name: string, dataSource: DataSource): Promise<unknown>;
423
+ /**
424
+ * Load all data sources
425
+ */
426
+ loadAllDataSources(dataSources: Record<string, DataSource>): Promise<Record<string, unknown>>;
427
+ /**
428
+ * Clear cache for a specific data source or all caches
429
+ */
430
+ clearCache(name?: string): void;
431
+ /**
432
+ * Clear all cache entries
433
+ */
434
+ clearAllCache(): void;
435
+ /**
436
+ * Get the current cache size
437
+ */
438
+ getCacheSize(): number;
439
+ }
440
+
441
+ export { type APIContext, type APIModule, type BuildOptions, type ConstelaConfig, DataLoader, type DevServerOptions, type GenerateStaticPagesOptions, type GlobResult, type LayoutInfo, LayoutResolver, type Middleware, type MiddlewareContext, type MiddlewareNext, type PageExportFunction, type PageModule, type ScannedLayout, type ScannedRoute, type StaticFileResult, type StaticPath, type StaticPathsProvider, type StaticPathsResult, build, createAPIHandler, createAdapter, createDevServer, createMiddlewareChain, filePathToPattern, generateStaticPages, generateStaticPaths, getMimeType, isPageExportFunction, isPathSafe, loadApi, loadFile, loadGlob, loadLayout, resolveLayout, resolvePageExport, resolveStaticFile, scanLayouts, scanRoutes, transformCsv, transformMdx, transformYaml };