UNPKG

8.36 kBJavaScriptView Raw
1var __defProp = Object.defineProperty;
2var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3var __publicField = (obj, key, value) => {
4 __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5 return value;
6};
7import { resolve, join, dirname } from "node:path";
8import { mkdir, writeFile } from "node:fs/promises";
9import { createRouter } from "radix3";
10import { NotFoundError } from "./errors.js";
11import { runCLI } from "./runCLI.js";
12import { akteWelcome } from "./akteWelcome.js";
13import { __PRODUCTION__ } from "./lib/__PRODUCTION__.js";
14import { createDebugger } from "./lib/createDebugger.js";
15import { pathToRouterPath } from "./lib/pathToRouterPath.js";
16import { isCLI } from "./lib/isCLI.js";
17const debug = createDebugger("akte:app");
18const debugWrite = createDebugger("akte:app:write");
19const debugRender = createDebugger("akte:app:render");
20const debugRouter = createDebugger("akte:app:router");
21const debugCache = createDebugger("akte:app:cache");
22class AkteApp {
23 constructor(config) {
24 __publicField(this, "config");
25 __publicField(this, "_globalDataPromise");
26 __publicField(this, "_router");
27 if (!__PRODUCTION__) {
28 if (config.files.length === 0 && akteWelcome) {
29 config.files.push(akteWelcome);
30 }
31 }
32 this.config = config;
33 debug("created with %o files", this.config.files.length);
34 if (isCLI) {
35 runCLI(this);
36 }
37 }
38 /**
39 * Looks up the Akte file responsible for rendering the path.
40 *
41 * @param path - Path to lookup, e.g. "/foo"
42 * @returns A match featuring the path, the path parameters if any, and the
43 * Akte file.
44 * @throws {@link NotFoundError} When no Akte file is found for handling
45 * looked up path.
46 * @experimental Programmatic API might still change not following SemVer.
47 */
48 lookup(path) {
49 const pathWithExtension = pathToRouterPath(path);
50 debugRouter("looking up %o (%o)", path, pathWithExtension);
51 const maybeMatch = this.getRouter().lookup(pathWithExtension);
52 if (!maybeMatch || !maybeMatch.file) {
53 debugRouter("not found %o", path);
54 throw new NotFoundError(path);
55 }
56 return {
57 ...maybeMatch,
58 path
59 };
60 }
61 /**
62 * Renders a match from {@link lookup}.
63 *
64 * @param match - Match to render.
65 * @returns Rendered file.
66 * @throws {@link NotFoundError} When the Akte file could not render the match
67 * (404), with an optional `cause` attached to it for uncaught errors (500)
68 * @experimental Programmatic API might still change not following SemVer.
69 */
70 async render(match) {
71 debugRender("rendering %o...", match.path);
72 const params = match.params || {};
73 const globalData = await this.getGlobalDataPromise();
74 try {
75 const content = await match.file.render({
76 path: match.path,
77 params,
78 globalData
79 });
80 debugRender("rendered %o", match.path);
81 return content;
82 } catch (error) {
83 if (error instanceof NotFoundError) {
84 throw error;
85 }
86 debugRender("could not render %o", match.path);
87 throw new NotFoundError(match.path, { cause: error });
88 }
89 }
90 /**
91 * Renders all Akte files.
92 *
93 * @returns Rendered files map.
94 * @experimental Programmatic API might still change not following SemVer.
95 */
96 async renderAll() {
97 debugRender("rendering all files...");
98 const globalData = await this.getGlobalDataPromise();
99 const renderAll = async (akteFiles) => {
100 try {
101 const files2 = await akteFiles.renderAll({ globalData });
102 return files2;
103 } catch (error) {
104 debug.error("Akte → Failed to build %o\n", akteFiles.path);
105 throw error;
106 }
107 };
108 const promises = [];
109 for (const akteFiles of this.config.files) {
110 promises.push(renderAll(akteFiles));
111 }
112 const rawFilesArray = await Promise.all(promises);
113 const files = {};
114 for (const rawFiles of rawFilesArray) {
115 for (const path in rawFiles) {
116 if (path in files) {
117 debug.warn(" Multiple files built %o, only the first one is preserved", path);
118 continue;
119 }
120 files[path] = rawFiles[path];
121 }
122 }
123 const rendered = Object.keys(files).length;
124 debugRender(`done, %o ${rendered > 1 ? "files" : "file"} rendered`, rendered);
125 return files;
126 }
127 /**
128 * Writes a map of rendered Akte files to the specified `outDir`, or the app
129 * specified one (defaults to `"dist"`).
130 *
131 * @param args - A map of rendered Akte files, and an optional `outDir`
132 * @experimental Programmatic API might still change not following SemVer.
133 */
134 async writeAll(args) {
135 var _a;
136 debugWrite("writing all files...");
137 const outDir = args.outDir ?? ((_a = this.config.build) == null ? void 0 : _a.outDir) ?? "dist";
138 const outDirPath = resolve(outDir);
139 const controller = new AbortController();
140 const write = async (path, content) => {
141 const filePath = join(outDirPath, path);
142 const fileDir = dirname(filePath);
143 try {
144 await mkdir(fileDir, { recursive: true });
145 await writeFile(filePath, content, {
146 encoding: "utf-8",
147 signal: controller.signal
148 });
149 } catch (error) {
150 if (controller.signal.aborted) {
151 return;
152 }
153 controller.abort();
154 debug.error("Akte → Failed to write %o\n", path);
155 throw error;
156 }
157 debugWrite("%o", path);
158 debugWrite.log(" %o", path);
159 };
160 const promises = [];
161 for (const path in args.files) {
162 promises.push(write(path, args.files[path]));
163 }
164 await Promise.all(promises);
165 debugWrite(`done, %o ${promises.length > 1 ? "files" : "file"} written`, promises.length);
166 }
167 /**
168 * Build (renders and write) all Akte files to the specified `outDir`, or the
169 * app specified one (defaults to `"dist"`).
170 *
171 * @param args - An optional `outDir`
172 * @returns Built files array.
173 * @experimental Programmatic API might still change not following SemVer.
174 */
175 async buildAll(args) {
176 const files = await this.renderAll();
177 await this.writeAll({ ...args, files });
178 return Object.keys(files);
179 }
180 /**
181 * Akte caches all `globalData`, `data`, `bulkData` calls for performance.
182 * This method can be used to clear the cache.
183 *
184 * @param alsoClearFileCache - Also clear cache on all registered Akte files.
185 * @experimental Programmatic API might still change not following SemVer.
186 */
187 clearCache(alsoClearFileCache = false) {
188 debugCache("clearing...");
189 this._globalDataPromise = void 0;
190 this._router = void 0;
191 if (alsoClearFileCache) {
192 for (const file of this.config.files) {
193 file.clearCache();
194 }
195 }
196 debugCache("cleared");
197 }
198 getGlobalDataPromise() {
199 var _a, _b;
200 if (!this._globalDataPromise) {
201 debugCache("retrieving global data...");
202 const globalDataPromise = ((_b = (_a = this.config).globalData) == null ? void 0 : _b.call(_a)) ?? void 0;
203 if (globalDataPromise instanceof Promise) {
204 globalDataPromise.then(() => {
205 debugCache("retrieved global data");
206 });
207 } else {
208 debugCache("retrieved global data");
209 }
210 this._globalDataPromise = globalDataPromise;
211 } else {
212 debugCache("using cached global data");
213 }
214 return this._globalDataPromise;
215 }
216 getRouter() {
217 if (!this._router) {
218 debugCache("creating router...");
219 const router = createRouter();
220 for (const file of this.config.files) {
221 const path = pathToRouterPath(file.path);
222 router.insert(pathToRouterPath(file.path), { file });
223 debugRouter("registered %o", path);
224 if (file.path.endsWith("/**")) {
225 const catchAllPath = pathToRouterPath(file.path.replace(/\/\*\*$/, ""));
226 router.insert(catchAllPath, {
227 file
228 });
229 debugRouter("registered %o", catchAllPath);
230 debugCache(pathToRouterPath(file.path.replace(/\/\*\*$/, "")));
231 }
232 }
233 this._router = router;
234 debugCache("created router");
235 } else {
236 debugCache("using cached router");
237 }
238 return this._router;
239 }
240}
241export {
242 AkteApp
243};
244//# sourceMappingURL=AkteApp.js.map