UNPKG

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