@jxsuite/compiler 0.23.2 → 0.25.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.
@@ -8,16 +8,14 @@
8
8
 
9
9
  import { existsSync } from "node:fs";
10
10
  import { resolve, extname, basename } from "node:path";
11
- import { processImage, buildSrcset, contentHash, configHash } from "./image-optimizer.js";
12
- import { getCached, setCached } from "./image-cache.js";
11
+ import { processImage, buildSrcset, contentHash, configHash } from "./image-optimizer.ts";
12
+ import { getCached, setCached } from "./image-cache.ts";
13
13
 
14
- /**
15
- * @typedef {import("./image-optimizer.js").ImageConfig} ImageConfig
16
- *
17
- * @typedef {import("./image-optimizer.js").ImageManifest} ImageManifest
18
- *
19
- * @typedef {import("./image-cache.js").CacheManifest} CacheManifest
20
- */
14
+ import type { ImageConfig } from "./image-optimizer.ts";
15
+ import type { ImageManifest } from "./image-optimizer.ts";
16
+ import type { JxMutableNode } from "@jxsuite/schema/types";
17
+ import type { CacheManifest } from "./image-cache.ts";
18
+ import type { JxDocument } from "@jxsuite/schema/types";
21
19
 
22
20
  const SKIP_EXTENSIONS = new Set([".svg", ".gif"]);
23
21
  const EXTERNAL_PREFIXES = ["http://", "https://", "data:", "//"];
@@ -30,7 +28,13 @@ const EXTERNAL_PREFIXES = ["http://", "https://", "data:", "//"];
30
28
  * @param {CacheManifest} cache
31
29
  * @returns {Promise<ImageManifest>}
32
30
  */
33
- async function resolveManifest(absoluteSrc, src, config, outDir, cache) {
31
+ async function resolveManifest(
32
+ absoluteSrc: string,
33
+ src: string,
34
+ config: ImageConfig,
35
+ outDir: string,
36
+ cache: CacheManifest,
37
+ ) {
34
38
  const key = `${contentHash(absoluteSrc)}:${configHash(config)}`;
35
39
  const cached = getCached(cache, key);
36
40
 
@@ -51,7 +55,7 @@ async function resolveManifest(absoluteSrc, src, config, outDir, cache) {
51
55
  * @param {string} src
52
56
  * @returns {boolean}
53
57
  */
54
- function shouldSkip(src) {
58
+ function shouldSkip(src: string) {
55
59
  if (typeof src !== "string") return true;
56
60
  if (src.includes("${")) return true;
57
61
  if (EXTERNAL_PREFIXES.some((p) => src.startsWith(p))) return true;
@@ -68,7 +72,7 @@ function shouldSkip(src) {
68
72
  * @param {string} projectRoot
69
73
  * @returns {string}
70
74
  */
71
- function resolveImagePath(src, projectRoot) {
75
+ function resolveImagePath(src: string, projectRoot: string) {
72
76
  if (src.startsWith("/")) {
73
77
  return resolve(projectRoot, "public", src.slice(1));
74
78
  }
@@ -88,9 +92,14 @@ function resolveImagePath(src, projectRoot) {
88
92
  * @param {CacheManifest} cache
89
93
  * @returns {Promise<{ imageRefs: Map<string, ImageManifest> }>}
90
94
  */
91
- export async function transformImageNodes(doc, config, projectRoot, outDir, cache) {
92
- /** @type {Map<string, ImageManifest>} */
93
- const imageRefs = new Map();
95
+ export async function transformImageNodes(
96
+ doc: JxMutableNode | JxDocument,
97
+ config: ImageConfig,
98
+ projectRoot: string,
99
+ outDir: string,
100
+ cache: CacheManifest,
101
+ ) {
102
+ const imageRefs: Map<string, ImageManifest> = new Map();
94
103
 
95
104
  if (!config.optimize) return { imageRefs };
96
105
 
@@ -107,7 +116,14 @@ export async function transformImageNodes(doc, config, projectRoot, outDir, cach
107
116
  * @param {CacheManifest} cache
108
117
  * @param {Map<string, ImageManifest>} imageRefs
109
118
  */
110
- async function walkAndTransform(node, config, projectRoot, outDir, cache, imageRefs) {
119
+ async function walkAndTransform(
120
+ node: JxMutableNode | JxDocument,
121
+ config: ImageConfig,
122
+ projectRoot: string,
123
+ outDir: string,
124
+ cache: CacheManifest,
125
+ imageRefs: Map<string, ImageManifest>,
126
+ ) {
111
127
  if (!node || typeof node !== "object") return;
112
128
 
113
129
  if (node.tagName === "img") {
@@ -141,7 +157,14 @@ async function walkAndTransform(node, config, projectRoot, outDir, cache, imageR
141
157
  * @param {CacheManifest} cache
142
158
  * @param {Map<string, ImageManifest>} imageRefs
143
159
  */
144
- async function transformImgNode(node, config, projectRoot, outDir, cache, imageRefs) {
160
+ async function transformImgNode(
161
+ node: JxMutableNode | JxDocument,
162
+ config: ImageConfig,
163
+ projectRoot: string,
164
+ outDir: string,
165
+ cache: CacheManifest,
166
+ imageRefs: Map<string, ImageManifest>,
167
+ ) {
145
168
  if (!node.attributes) node.attributes = {};
146
169
 
147
170
  const src = node.attributes.src ?? node.src;
@@ -166,10 +189,10 @@ async function transformImgNode(node, config, projectRoot, outDir, cache, imageR
166
189
  node.attributes.sizes = node.attributes.sizes ?? config.sizes;
167
190
  }
168
191
 
169
- if (!node.attributes.width && manifest.original.width) {
192
+ if (!node.attributes.width && manifest.original?.width) {
170
193
  node.attributes.width = String(manifest.original.width);
171
194
  }
172
- if (!node.attributes.height && manifest.original.height) {
195
+ if (!node.attributes.height && manifest.original?.height) {
173
196
  node.attributes.height = String(manifest.original.height);
174
197
  }
175
198
 
@@ -195,7 +218,14 @@ const DATA_NO_OPT_RE = /\bdata-no-optimize\b/;
195
218
  * @param {Map<string, ImageManifest>} imageRefs
196
219
  * @returns {Promise<string>}
197
220
  */
198
- async function transformInnerHtmlImages(html, config, projectRoot, outDir, cache, imageRefs) {
221
+ async function transformInnerHtmlImages(
222
+ html: string,
223
+ config: ImageConfig,
224
+ projectRoot: string,
225
+ outDir: string,
226
+ cache: CacheManifest,
227
+ imageRefs: Map<string, ImageManifest>,
228
+ ) {
199
229
  /** @type {{ match: string; replacement: string }[]} */
200
230
  const replacements = [];
201
231
 
@@ -226,10 +256,10 @@ async function transformInnerHtmlImages(html, config, projectRoot, outDir, cache
226
256
  if (!srcset) continue;
227
257
 
228
258
  let extra = ` srcset="${srcset}" sizes="${config.sizes}"`;
229
- if (!/\bwidth=/.test(attrs) && manifest.original.width) {
259
+ if (!/\bwidth=/.test(attrs) && manifest.original?.width) {
230
260
  extra += ` width="${manifest.original.width}"`;
231
261
  }
232
- if (!/\bheight=/.test(attrs) && manifest.original.height) {
262
+ if (!/\bheight=/.test(attrs) && manifest.original?.height) {
233
263
  extra += ` height="${manifest.original.height}"`;
234
264
  }
235
265
  if (config.lazyLoad && !/\bloading="eager"/.test(attrs)) {
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { readFileSync, existsSync } from "node:fs";
17
17
  import { resolve } from "node:path";
18
+ import type { JxElement, JxDocument } from "@jxsuite/schema/types";
18
19
 
19
20
  /**
20
21
  * Resolve a page's layout, wrapping the page content in the layout structure.
@@ -24,12 +25,14 @@ import { resolve } from "node:path";
24
25
  * @param {string} projectRoot - Project root directory
25
26
  * @returns {JxDocument} The merged document (layout wrapping page content)
26
27
  */
27
- export function resolveLayout(pageDoc, projectConfig, projectRoot) {
28
+ export function resolveLayout(
29
+ pageDoc: JxDocument,
30
+ projectConfig: Record<string, unknown>,
31
+ projectRoot: string,
32
+ ) {
28
33
  // Determine which layout to use
29
34
  const layoutRef =
30
- pageDoc.$layout ??
31
- /** @type {Record<string, unknown>} */ (projectConfig.defaults)?.layout ??
32
- null;
35
+ pageDoc.$layout ?? (projectConfig.defaults as Record<string, unknown>)?.layout ?? null;
33
36
 
34
37
  if (!layoutRef) {
35
38
  // No layout — return page as-is
@@ -37,7 +40,7 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
37
40
  }
38
41
 
39
42
  // Load the layout file
40
- const layoutPath = resolve(projectRoot, /** @type {string} */ (layoutRef));
43
+ const layoutPath = resolve(projectRoot, layoutRef as string);
41
44
  if (!existsSync(layoutPath)) {
42
45
  throw new Error(`Layout not found: ${layoutRef} (resolved to ${layoutPath})`);
43
46
  }
@@ -47,7 +50,7 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
47
50
  try {
48
51
  layoutDoc = JSON.parse(readFileSync(layoutPath, "utf8"));
49
52
  } catch (e) {
50
- const err = /** @type {Error} */ (e);
53
+ const err = e as Error;
51
54
  throw new Error(`Invalid layout JSON at ${layoutPath}: ${err.message}`);
52
55
  }
53
56
 
@@ -58,9 +61,12 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
58
61
 
59
62
  // Distribute page children into layout slots
60
63
  const rawChildren = pageDoc.children ?? [];
61
- const pageChildren = /** @type {(JxElement | string)[]} */ (
62
- typeof rawChildren === "string" ? [rawChildren] : Array.isArray(rawChildren) ? rawChildren : []
63
- );
64
+ const pageChildren =
65
+ typeof rawChildren === "string"
66
+ ? [rawChildren]
67
+ : Array.isArray(rawChildren)
68
+ ? rawChildren
69
+ : ([] as (JxElement | string)[]);
64
70
  const merged = deepClone(layoutDoc);
65
71
 
66
72
  distributeSlots(merged, pageChildren);
@@ -110,21 +116,19 @@ export function resolveLayout(pageDoc, projectConfig, projectRoot) {
110
116
  * @param {JxElement} node - Layout document tree (mutated in place)
111
117
  * @param {(JxElement | string)[]} children - Page children to distribute
112
118
  */
113
- function distributeSlots(node, children) {
119
+ function distributeSlots(node: JxElement, children: (JxElement | string)[]) {
114
120
  if (!node || typeof node !== "object") return;
115
121
  if (!Array.isArray(node.children)) return;
116
122
 
117
123
  // Collect named and default children
118
- /** @type {Map<string, (JxElement | string)[]>} */
119
- const named = new Map(); // slot name → children[]
120
- /** @type {(JxElement | string)[]} */
121
- const defaults = []; // children without a slot target
124
+ const named: Map<string, (JxElement | string)[]> = new Map(); // slot name → children[]
125
+ const defaults: (JxElement | string)[] = []; // children without a slot target
122
126
 
123
127
  for (const child of children) {
124
128
  if (child && typeof child === "object" && child.attributes?.slot) {
125
- const slotName = /** @type {string} */ (child.attributes.slot);
129
+ const slotName = child.attributes.slot as string;
126
130
  if (!named.has(slotName)) named.set(slotName, []);
127
- /** @type {(JxElement | string)[]} */ (named.get(slotName)).push(child);
131
+ (named.get(slotName) as (JxElement | string)[]).push(child);
128
132
  } else {
129
133
  defaults.push(child);
130
134
  }
@@ -141,23 +145,24 @@ function distributeSlots(node, children) {
141
145
  * @param {Map<string, (JxElement | string)[]>} named
142
146
  * @param {(JxElement | string)[]} defaults
143
147
  */
144
- function fillSlots(node, named, defaults) {
148
+ function fillSlots(
149
+ node: JxElement,
150
+ named: Map<string, (JxElement | string)[]>,
151
+ defaults: (JxElement | string)[],
152
+ ) {
145
153
  if (!node || typeof node !== "object") return;
146
154
  if (!Array.isArray(node.children)) return;
147
155
 
148
- /** @type {(JxElement | string)[]} */
149
- const newChildren = [];
156
+ const newChildren: (JxElement | string)[] = [];
150
157
 
151
158
  for (const child of node.children) {
152
159
  if (child && typeof child === "object" && child.tagName === "slot") {
153
160
  const slotName = child.attributes?.name;
154
161
 
155
- if (slotName && named.has(/** @type {string} */ (slotName))) {
162
+ if (slotName && named.has(slotName as string)) {
156
163
  // Named slot — replace with matching children
157
- newChildren.push(
158
- .../** @type {(JxElement | string)[]} */ (named.get(/** @type {string} */ (slotName))),
159
- );
160
- named.delete(/** @type {string} */ (slotName)); // consumed
164
+ newChildren.push(...(named.get(slotName as string) as (JxElement | string)[]));
165
+ named.delete(slotName as string); // consumed
161
166
  } else if (!slotName && defaults.length > 0) {
162
167
  // Default slot — replace with unassigned children
163
168
  newChildren.push(...defaults);
@@ -171,7 +176,7 @@ function fillSlots(node, named, defaults) {
171
176
  }
172
177
  } else {
173
178
  // Not a slot — recurse into it
174
- fillSlots(/** @type {JxElement} */ (child), named, defaults);
179
+ fillSlots(child as JxElement, named, defaults);
175
180
  newChildren.push(child);
176
181
  }
177
182
  }
@@ -185,6 +190,6 @@ function fillSlots(node, named, defaults) {
185
190
  * @param {JxDocument} obj
186
191
  * @returns {JxDocument}
187
192
  */
188
- function deepClone(obj) {
193
+ function deepClone(obj: JxDocument) {
189
194
  return JSON.parse(JSON.stringify(obj));
190
195
  }
@@ -12,18 +12,18 @@
12
12
 
13
13
  import { readdirSync, readFileSync } from "node:fs";
14
14
  import { resolve, relative, extname, join } from "node:path";
15
-
16
- /**
17
- * @typedef {object} Route
18
- * @property {string} urlPattern - URL pattern (e.g. "/blog/:slug")
19
- * @property {string} sourcePath - Absolute path to the .json source file
20
- * @property {string} relativePath - Path relative to pages/ dir
21
- * @property {boolean} isDynamic - Whether route has parameters
22
- * @property {boolean} isCatchAll - Whether route uses [...param] spread
23
- * @property {string[]} params - Parameter names (e.g. ["slug"])
24
- * @property {string | null} $layout - Layout override from page frontmatter, if any
25
- * @property {Record<string, string>} [_pathParams] - Resolved path parameters
26
- */
15
+ import type { ContentLoaderEntry } from "@jxsuite/parser/types";
16
+
17
+ interface Route {
18
+ urlPattern: string; // URL pattern (e.g. "/blog/:slug")
19
+ sourcePath: string; // Absolute path to the .json source file
20
+ relativePath: string; // Path relative to pages/ dir
21
+ isDynamic: boolean; // Whether route has parameters
22
+ isCatchAll: boolean; // Whether route uses [...param] spread
23
+ params: string[]; // Parameter names (e.g. ["slug"])
24
+ $layout: string | null; // Layout override from page frontmatter, if any
25
+ _pathParams?: Record<string, string>; // Resolved path parameters
26
+ }
27
27
 
28
28
  /**
29
29
  * Discover all routable pages in a pages/ directory.
@@ -31,9 +31,8 @@ import { resolve, relative, extname, join } from "node:path";
31
31
  * @param {string} pagesDir - Absolute path to the pages/ directory
32
32
  * @returns {Route[]} Sorted route table (static routes first, then dynamic)
33
33
  */
34
- export function discoverPages(pagesDir) {
35
- /** @type {Route[]} */
36
- const routes = [];
34
+ export function discoverPages(pagesDir: string) {
35
+ const routes: Route[] = [];
37
36
  walkDir(pagesDir, pagesDir, routes);
38
37
 
39
38
  // Sort: static routes first, then by specificity (more segments = more specific)
@@ -53,7 +52,7 @@ export function discoverPages(pagesDir) {
53
52
  * @param {string} pagesRoot
54
53
  * @param {Route[]} routes
55
54
  */
56
- function walkDir(dir, pagesRoot, routes) {
55
+ function walkDir(dir: string, pagesRoot: string, routes: Route[]) {
57
56
  const entries = readdirSync(dir, { withFileTypes: true });
58
57
 
59
58
  for (const entry of entries) {
@@ -86,7 +85,7 @@ function walkDir(dir, pagesRoot, routes) {
86
85
  * @param {string} absolutePath - Full filesystem path
87
86
  * @returns {Route}
88
87
  */
89
- function fileToRoute(relativePath, absolutePath) {
88
+ function fileToRoute(relativePath: string, absolutePath: string) {
90
89
  // Remove .json or .md extension
91
90
  let urlPath = relativePath.replace(/\.(json|md)$/, "");
92
91
 
@@ -104,15 +103,14 @@ function fileToRoute(relativePath, absolutePath) {
104
103
  if (!urlPath.startsWith("/")) urlPath = "/" + urlPath;
105
104
 
106
105
  // Extract parameters from bracket syntax
107
- /** @type {string[]} */
108
- const params = [];
106
+ const params: string[] = [];
109
107
  let isDynamic = false;
110
108
  let isCatchAll = false;
111
109
 
112
110
  // Convert [param] → :param and [...param] → *
113
111
  const urlPattern = urlPath.replace(
114
112
  /\[\.\.\.(\w+)\]|\[(\w+)\]/g,
115
- (/** @type {string} */ match, /** @type {string} */ spread, /** @type {string} */ named) => {
113
+ (match: string, spread: string, named: string) => {
116
114
  if (spread) {
117
115
  isCatchAll = true;
118
116
  isDynamic = true;
@@ -172,9 +170,12 @@ function fileToRoute(relativePath, absolutePath) {
172
170
  * @param {Map<string, any[]>} [contentTypes] - Loaded content types (from content-loader)
173
171
  * @returns {Promise<Route[]>} Expanded routes with concrete paths
174
172
  */
175
- export async function expandDynamicRoutes(routes, projectRoot, contentTypes = new Map()) {
176
- /** @type {Route[]} */
177
- const expanded = [];
173
+ export async function expandDynamicRoutes(
174
+ routes: Route[],
175
+ projectRoot: string,
176
+ contentTypes: Map<string, any[]> = new Map(),
177
+ ) {
178
+ const expanded: Route[] = [];
178
179
 
179
180
  for (const route of routes) {
180
181
  if (!route.isDynamic) {
@@ -202,8 +203,8 @@ export async function expandDynamicRoutes(routes, projectRoot, contentTypes = ne
202
203
  for (const pathEntry of pathEntries) {
203
204
  let concreteUrl = route.urlPattern;
204
205
  for (const [param, value] of Object.entries(pathEntry)) {
205
- concreteUrl = concreteUrl.replace(`:${param}`, /** @type {string} */ (value));
206
- concreteUrl = concreteUrl.replace("*", /** @type {string} */ (value));
206
+ concreteUrl = concreteUrl.replace(`:${param}`, value as string);
207
+ concreteUrl = concreteUrl.replace("*", value as string);
207
208
  }
208
209
 
209
210
  expanded.push({
@@ -228,7 +229,7 @@ export async function expandDynamicRoutes(routes, projectRoot, contentTypes = ne
228
229
  * @param {Map<string, any[]>} contentTypes
229
230
  * @returns {Record<string, any>[]} Array of { paramName: value } objects
230
231
  */
231
- function resolvePathEntries($paths, projectRoot, contentTypes) {
232
+ function resolvePathEntries($paths: any, projectRoot: string, contentTypes: Map<string, any[]>) {
232
233
  // Legacy: array of param objects
233
234
  if (Array.isArray($paths)) {
234
235
  return $paths;
@@ -245,7 +246,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
245
246
  }
246
247
  const param = $paths.param ?? "slug";
247
248
  const field = $paths.field ?? "id";
248
- return entries.map((/** @type {ContentLoaderEntry} */ entry) => ({
249
+ return entries.map((entry: ContentLoaderEntry) => ({
249
250
  [param]: field === "id" ? entry.id : (entry.data[field] ?? entry.id),
250
251
  }));
251
252
  }
@@ -253,7 +254,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
253
254
  // Explicit values: { values: ["en", "fr"], param: "lang" }
254
255
  if (Array.isArray($paths.values)) {
255
256
  const param = $paths.param ?? "value";
256
- return $paths.values.map((/** @type {string} */ v) => ({ [param]: v }));
257
+ return $paths.values.map((v: string) => ({ [param]: v }));
257
258
  }
258
259
 
259
260
  // Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
@@ -264,7 +265,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
264
265
  try {
265
266
  data = JSON.parse(readFileSync(filePath, "utf8"));
266
267
  } catch (e) {
267
- const err = /** @type {Error} */ (e);
268
+ const err = e as Error;
268
269
  console.warn(`Warning: $paths.$ref could not load "${$paths.$ref}": ${err.message}`);
269
270
  return [];
270
271
  }
@@ -274,7 +275,7 @@ function resolvePathEntries($paths, projectRoot, contentTypes) {
274
275
  }
275
276
  const param = $paths.param ?? "id";
276
277
  const field = $paths.field ?? "id";
277
- return data.map((/** @type {Record<string, unknown>} */ item) => ({
278
+ return data.map((item: Record<string, unknown>) => ({
278
279
  [param]: item[field] ?? item.id ?? String(item),
279
280
  }));
280
281
  }
@@ -18,6 +18,7 @@ import { readFileSync } from "node:fs";
18
18
  import { dirname, resolve } from "node:path";
19
19
  import { pathToFileURL } from "node:url";
20
20
  import { createRequire } from "node:module";
21
+ import type { JxDocument, JxMutableNode } from "@jxsuite/schema/types";
21
22
 
22
23
  /**
23
24
  * Prototype names handled elsewhere (builtins + legacy content system). These are skipped by the
@@ -54,7 +55,12 @@ const RESERVED_KEYS = new Set([
54
55
  * @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} [projectContext] -
55
56
  * Project-level context injected into all classes
56
57
  */
57
- export async function resolvePrototypes(doc, route, projectRoot, projectContext = {}) {
58
+ export async function resolvePrototypes(
59
+ doc: JxMutableNode | JxDocument,
60
+ route: { sourcePath?: string; _pathParams?: Record<string, string> },
61
+ projectRoot: string,
62
+ projectContext: { config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> } = {},
63
+ ) {
58
64
  const imports = doc.imports ?? {};
59
65
  const state = doc.state;
60
66
  if (!state) return;
@@ -83,7 +89,7 @@ export async function resolvePrototypes(doc, route, projectRoot, projectContext
83
89
  } catch (err) {
84
90
  console.warn(
85
91
  `prototype-resolver: failed to resolve "${key}" ($prototype: "${def.$prototype}"):`,
86
- /** @type {Error} */ (err).message,
92
+ (err as Error).message,
87
93
  );
88
94
  }
89
95
  }
@@ -99,7 +105,13 @@ export async function resolvePrototypes(doc, route, projectRoot, projectContext
99
105
  * @param {{ config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> }} projectContext
100
106
  * @returns {Promise<any>} The resolved value
101
107
  */
102
- async function resolveClassPrototype(def, route, projectRoot, state, projectContext) {
108
+ async function resolveClassPrototype(
109
+ def: Record<string, any>,
110
+ route: { sourcePath?: string; _pathParams?: Record<string, string> },
111
+ projectRoot: string,
112
+ state: Record<string, unknown>,
113
+ projectContext: { config?: Record<string, unknown>; contentTypes?: Map<string, unknown[]> },
114
+ ) {
103
115
  const src = def.$src;
104
116
 
105
117
  // 1. Resolve .class.json path — handles both npm specifiers and relative paths
@@ -138,8 +150,7 @@ async function resolveClassPrototype(def, route, projectRoot, state, projectCont
138
150
  }
139
151
 
140
152
  // 6. Build config — filter out reserved keys
141
- /** @type {Record<string, any>} */
142
- const config = {};
153
+ const config: Record<string, any> = {};
143
154
  for (const [k, v] of Object.entries(def)) {
144
155
  if (!RESERVED_KEYS.has(k)) config[k] = v;
145
156
  }