@vesk/compiler 0.1.1 → 0.2.8

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.
Files changed (51) hide show
  1. package/dist/api-routes.d.ts +6 -1
  2. package/dist/api-routes.d.ts.map +1 -1
  3. package/dist/api-routes.js +52 -7
  4. package/dist/client-codegen.d.ts +15 -0
  5. package/dist/client-codegen.d.ts.map +1 -1
  6. package/dist/client-codegen.js +73 -10
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +25 -0
  10. package/dist/errors.d.ts.map +1 -1
  11. package/dist/errors.js +33 -4
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -0
  15. package/dist/ir-generator.d.ts.map +1 -1
  16. package/dist/ir-generator.js +109 -1
  17. package/dist/md-inline.d.ts +17 -0
  18. package/dist/md-inline.d.ts.map +1 -0
  19. package/dist/md-inline.js +149 -0
  20. package/dist/middleware.d.ts.map +1 -1
  21. package/dist/middleware.js +42 -7
  22. package/dist/router.d.ts +2 -0
  23. package/dist/router.d.ts.map +1 -1
  24. package/dist/router.js +48 -3
  25. package/dist/server-codegen.d.ts +1 -1
  26. package/dist/server-codegen.d.ts.map +1 -1
  27. package/dist/server-codegen.js +1 -1
  28. package/dist/server-jsgen.d.ts.map +1 -1
  29. package/dist/server-jsgen.js +4 -3
  30. package/dist/server-render.d.ts.map +1 -1
  31. package/dist/server-render.js +16 -8
  32. package/dist/server-utils.d.ts +31 -0
  33. package/dist/server-utils.d.ts.map +1 -1
  34. package/dist/server-utils.js +478 -52
  35. package/dist/ssr-store.d.ts +1 -1
  36. package/dist/ssr-store.d.ts.map +1 -1
  37. package/dist/ssr-store.js +3 -0
  38. package/dist/typecheck.d.ts +2 -0
  39. package/dist/typecheck.d.ts.map +1 -1
  40. package/dist/typecheck.js +452 -29
  41. package/dist/types.d.ts +4 -129
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js +1 -0
  44. package/dist/vesk-plugin.d.ts +2 -0
  45. package/dist/vesk-plugin.d.ts.map +1 -1
  46. package/dist/vesk-plugin.js +152 -0
  47. package/dist/vsk-tsx.d.ts +62 -0
  48. package/dist/vsk-tsx.d.ts.map +1 -1
  49. package/dist/vsk-tsx.js +844 -362
  50. package/package.json +2 -1
  51. package/dist/package.json +0 -15
@@ -22,9 +22,14 @@ export type BuildWebRequestInput = {
22
22
  * `json()`/`text()` are patched accordingly. A `cookies` getter is attached
23
23
  * (NextRequest-compatible).
24
24
  */
25
- export declare function buildWebRequest(nodeReq: BuildWebRequestInput, url: string, body?: BodyInit | null): Request & {
25
+ export declare function buildWebRequest(nodeReq: BuildWebRequestInput, url: string, body?: BodyInit | null, options?: {
26
+ maxBodyBytes?: number;
27
+ }): Request & {
26
28
  cookies: Record<string, string>;
27
29
  query: Record<string, string>;
28
30
  };
31
+ export declare function tooLargeError(max: number): Error & {
32
+ status: number;
33
+ };
29
34
  export declare function executeApiRoute(filePath: string, method: string, request: Request, params?: Record<string, string>, locals?: Record<string, unknown>, devCache?: DevCache): Promise<Response | null>;
30
35
  //# sourceMappingURL=api-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../src/api-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAG5D;AA+ED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAoDjI;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE;AAED,MAAM,WAAW,QAAS,SAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAAG;AAExD,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;CAC/E,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,GACrB,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAqC9E;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAkI1B"}
1
+ {"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../src/api-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAW7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAG5D;AA+ED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CA0DjI;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE;AAED,MAAM,WAAW,QAAS,SAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAAG;AAExD,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;CAC/E,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,EACtB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CA4C9E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIrE;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA0J1B"}
@@ -1,5 +1,7 @@
1
1
  import { readdirSync, statSync, existsSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { collapseSlashes } from '@vesk/compiler/src/router';
4
+ import { assertSameOrigin, DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-utils';
3
5
  function basename(p) {
4
6
  const idx = p.lastIndexOf('/');
5
7
  return idx === -1 ? p : p.slice(idx + 1);
@@ -69,7 +71,7 @@ function scanApiDir(rootDir, dir, parentPath) {
69
71
  : (parentPath || '/');
70
72
  const node = {
71
73
  path: seg,
72
- fullPath: fullPath.replace(/\/+/g, '/') || '/',
74
+ fullPath: collapseSlashes(fullPath) || '/',
73
75
  isDynamic,
74
76
  isCatchAll,
75
77
  filePath: hasRoute && routeFileName ? join(dir, routeFileName) : null,
@@ -95,7 +97,14 @@ function scanApiDir(rootDir, dir, parentPath) {
95
97
  return nodes;
96
98
  }
97
99
  export function matchApiUrl(tree, pathname) {
98
- const normalized = (pathname.split('?')[0].split('#')[0]).replace(/^\/api(\/|$)/, '/');
100
+ let normalized = pathname.split('?')[0].split('#')[0];
101
+ // strip a leading /api segment (exact or followed by /) without regex
102
+ if (normalized === '/api') {
103
+ normalized = '/';
104
+ }
105
+ else if (normalized.startsWith('/api/') && !normalized.startsWith('//')) {
106
+ normalized = normalized.slice(4);
107
+ }
99
108
  const parts = normalized.split('/').filter(Boolean);
100
109
  const params = {};
101
110
  function matchNodes(nodes, partIndex) {
@@ -169,7 +178,7 @@ export function parseCookies(str) {
169
178
  * `json()`/`text()` are patched accordingly. A `cookies` getter is attached
170
179
  * (NextRequest-compatible).
171
180
  */
172
- export function buildWebRequest(nodeReq, url, body) {
181
+ export function buildWebRequest(nodeReq, url, body, options) {
173
182
  const parsedUrl = new URL(url, `http://${nodeReq.headers?.host || 'localhost'}`);
174
183
  const method = nodeReq.method || 'GET';
175
184
  const headers = {};
@@ -183,8 +192,15 @@ export function buildWebRequest(nodeReq, url, body) {
183
192
  if (_bodyBuffer)
184
193
  return _bodyBuffer;
185
194
  const chunks = [];
186
- for await (const chunk of nodeReq)
187
- chunks.push(chunk);
195
+ let total = 0;
196
+ const max = options?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
197
+ for await (const chunk of nodeReq) {
198
+ const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
199
+ total += bytes.byteLength;
200
+ if (total > max)
201
+ throw tooLargeError(max);
202
+ chunks.push(bytes);
203
+ }
188
204
  _bodyBuffer = Buffer.concat(chunks);
189
205
  return _bodyBuffer;
190
206
  };
@@ -210,6 +226,11 @@ export function buildWebRequest(nodeReq, url, body) {
210
226
  Object.defineProperty(webRequest, 'query', { get: () => query, enumerable: true });
211
227
  return webRequest;
212
228
  }
229
+ export function tooLargeError(max) {
230
+ const err = new Error(`Request body exceeds limit (${max} bytes)`);
231
+ err.status = 413;
232
+ return err;
233
+ }
213
234
  export async function executeApiRoute(filePath, method, request, params = {}, locals = {}, devCache) {
214
235
  let mod;
215
236
  try {
@@ -224,11 +245,33 @@ export async function executeApiRoute(filePath, method, request, params = {}, lo
224
245
  }
225
246
  }
226
247
  catch (e) {
227
- return new Response(JSON.stringify({ error: 'Failed to load route module', details: e.message }), {
248
+ // details only in dev (devCache present); prod gets a generic message
249
+ const body = devCache
250
+ ? { error: 'Failed to load route module', details: e.message }
251
+ : { error: 'Failed to load route module' };
252
+ return new Response(JSON.stringify(body), {
228
253
  status: 500, headers: { 'Content-Type': 'application/json' },
229
254
  });
230
255
  }
231
256
  const routeConfig = (mod.config || {});
257
+ // Default CSRF defense for mutating API calls: same-origin check unless the
258
+ // route opts out via `config.csrf = false`.
259
+ {
260
+ const methodUpper = method.toUpperCase();
261
+ if (routeConfig.csrf !== false && methodUpper !== 'GET' && methodUpper !== 'HEAD' && methodUpper !== 'OPTIONS') {
262
+ try {
263
+ assertSameOrigin({
264
+ method: methodUpper,
265
+ headers: Object.fromEntries([...request.headers.entries()].map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])),
266
+ });
267
+ }
268
+ catch {
269
+ return new Response(JSON.stringify({ error: 'Cross-origin request blocked' }), {
270
+ status: 403, headers: { 'Content-Type': 'application/json' },
271
+ });
272
+ }
273
+ }
274
+ }
232
275
  const handler = mod[method];
233
276
  if (!handler) {
234
277
  if (method === 'OPTIONS') {
@@ -326,7 +369,9 @@ export async function executeApiRoute(filePath, method, request, params = {}, lo
326
369
  status: 404, headers: { 'Content-Type': 'application/json' },
327
370
  });
328
371
  }
329
- return new Response(JSON.stringify({ error: err.message }), {
372
+ // never leak internal error messages in production responses
373
+ const body = devCache ? { error: err.message } : { error: 'Internal Server Error' };
374
+ return new Response(JSON.stringify(body), {
330
375
  status: 500, headers: { 'Content-Type': 'application/json' },
331
376
  });
332
377
  }
@@ -14,6 +14,21 @@ export declare function compileClient(source: string, _componentName: string | n
14
14
  forceClient?: boolean;
15
15
  hydrate?: boolean;
16
16
  includeTopLevel?: boolean;
17
+ sourcePath?: string;
18
+ mdRoots?: string[];
17
19
  }): string;
20
+ /**
21
+ * Compiles a component source in BOTH client modes (plain + hydrate)
22
+ * sharing a single parse/IR pass. Used by the dev-server hot path, where
23
+ * every edit otherwise pays the full acorn+TS parse twice. Also returns
24
+ * the resolved component name using the exact selection order of
25
+ * `resolveComponentName`, so callers don't need a second full parse just
26
+ * to learn the name.
27
+ */
28
+ export declare function compileClientBoth(source: string, _componentName: string | null): {
29
+ comp: string;
30
+ hyd: string;
31
+ name: string | null;
32
+ };
18
33
  export { compileClient as compile, isStaticIR };
19
34
  //# sourceMappingURL=client-codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAsCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAqjCD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CA0D1K;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAuCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAoBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAqjCD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AA2DD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,MAAM,CAOnN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBnI;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -1,12 +1,14 @@
1
1
  import { walk } from 'zimmerframe';
2
2
  import { print } from 'esrap';
3
3
  import ts from 'esrap/languages/ts';
4
+ import tsx from 'esrap/languages/tsx';
4
5
  import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, } from '@vesk/compiler/src/ir';
5
6
  import { parse } from '@vesk/compiler/src/parser';
6
7
  import { generateIR } from '@vesk/compiler/src/ir-generator';
7
8
  import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
8
9
  import { extractRuntimeNames } from '@vesk/compiler/src/server-utils';
9
10
  import { stripTrackGeneric } from '@vesk/compiler/src/scan';
11
+ import { inlineMdImportsFrom } from '@vesk/compiler/src/md-inline';
10
12
  import { stripTsTypes, hasTsSyntax } from '@vesk/compiler/src/strip-ts';
11
13
  function memberExpr(object, property) {
12
14
  return {
@@ -44,6 +46,28 @@ export function semicolonizeStatement(code) {
44
46
  return code;
45
47
  return code + ';';
46
48
  }
49
+ function containsJsx(node, depth = 0) {
50
+ if (!node || typeof node !== 'object' || depth > 40)
51
+ return false;
52
+ if (Array.isArray(node)) {
53
+ for (const item of node) {
54
+ if (containsJsx(item, depth + 1))
55
+ return true;
56
+ }
57
+ return false;
58
+ }
59
+ if (typeof node.type === 'string' && (node.type === 'JSXElement' || node.type === 'JSXFragment'))
60
+ return true;
61
+ for (const key of Object.keys(node)) {
62
+ const v = node[key];
63
+ if (v && typeof v === 'object' && containsJsx(v, depth + 1))
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ function printAst(ast) {
69
+ return print(ast, containsJsx(ast) ? tsx() : ts()).code;
70
+ }
47
71
  export function transformTracked(irNode, tracked) {
48
72
  const ast = irNode.ast;
49
73
  if (!ast)
@@ -51,7 +75,7 @@ export function transformTracked(irNode, tracked) {
51
75
  if (tracked.size === 0) {
52
76
  if (!hasTsSyntax(ast))
53
77
  return irNode.raw;
54
- return print(stripTsTypes(ast), ts()).code;
78
+ return printAst(stripTsTypes(ast));
55
79
  }
56
80
  const stripped = stripTsTypes(ast);
57
81
  const transformed = walk(stripped, tracked, {
@@ -139,7 +163,7 @@ export function transformTracked(irNode, tracked) {
139
163
  return context.next();
140
164
  },
141
165
  });
142
- return print(transformed, ts()).code;
166
+ return print(transformed, containsJsx(transformed) ? tsx() : ts()).code;
143
167
  }
144
168
  export function collectTrackedNames(body) {
145
169
  const names = new Map();
@@ -1458,15 +1482,13 @@ function hasKeyedMap(nodes) {
1458
1482
  }
1459
1483
  export function escapeHtml(str) {
1460
1484
  return str
1461
- .replace(/&/g, '&amp;')
1462
- .replace(/</g, '&lt;')
1463
- .replace(/>/g, '&gt;')
1464
- .replace(/"/g, '&quot;')
1465
- .replace(/'/g, '&#39;');
1485
+ .split('&').join('&amp;')
1486
+ .split('<').join('&lt;')
1487
+ .split('>').join('&gt;')
1488
+ .split('"').join('&quot;')
1489
+ .split("'").join('&#39;');
1466
1490
  }
1467
- export function compileClient(source, _componentName, options = {}) {
1468
- const ast = parse(source);
1469
- const ir = generateIR(ast, source);
1491
+ function emitClientFromIR(ir, options) {
1470
1492
  const needsClient = ir.components.some((c) => c.isClient || !isStaticComponent(c));
1471
1493
  if (!options.forceClient && !needsClient) {
1472
1494
  return '';
@@ -1520,4 +1542,45 @@ ${exportCode}
1520
1542
  `;
1521
1543
  return moduleCode.trim();
1522
1544
  }
1545
+ export function compileClient(source, _componentName, options = {}) {
1546
+ if (options.sourcePath) {
1547
+ source = inlineMdImportsFrom(source, options.sourcePath, options.mdRoots || []);
1548
+ }
1549
+ const ast = parse(source);
1550
+ const ir = generateIR(ast, source);
1551
+ return emitClientFromIR(ir, options);
1552
+ }
1553
+ /**
1554
+ * Compiles a component source in BOTH client modes (plain + hydrate)
1555
+ * sharing a single parse/IR pass. Used by the dev-server hot path, where
1556
+ * every edit otherwise pays the full acorn+TS parse twice. Also returns
1557
+ * the resolved component name using the exact selection order of
1558
+ * `resolveComponentName`, so callers don't need a second full parse just
1559
+ * to learn the name.
1560
+ */
1561
+ export function compileClientBoth(source, _componentName) {
1562
+ const ast = parse(source);
1563
+ // Downstream type-stripping mutates AST nodes in place (stripTsTypes),
1564
+ // so each emit mode needs its own tree. Cloning is far cheaper than the
1565
+ // second full acorn+TS parse this replaces.
1566
+ const hydAst = structuredClone(ast);
1567
+ const ir = generateIR(ast, source);
1568
+ const irHyd = generateIR(hydAst, source);
1569
+ const defaultComp = ir.components.find((c) => c.defaultExport);
1570
+ let name = null;
1571
+ if (defaultComp)
1572
+ name = defaultComp.name;
1573
+ else if (ir.components.length > 0)
1574
+ name = ir.components[0].name;
1575
+ else {
1576
+ const exportedComp = ir.components.find((c) => c.exported);
1577
+ if (exportedComp)
1578
+ name = exportedComp.name;
1579
+ }
1580
+ return {
1581
+ comp: emitClientFromIR(ir, { forceClient: true }),
1582
+ hyd: emitClientFromIR(irHyd, { forceClient: true, hydrate: true, includeTopLevel: false }),
1583
+ name,
1584
+ };
1585
+ }
1523
1586
  export { compileClient as compile, isStaticIR };
package/dist/config.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { VeskConfig, VeskPlugin, VeskSecurity } from '@vesk/compiler/src/types';
2
+ /** Tags allowed by default when md.html = 'allowlist' and no allowTags given. */
3
+ export declare const MD_DEFAULT_ALLOW_TAGS: string[];
2
4
  export declare function preset(name: string, overrides?: VeskSecurity): VeskSecurity;
3
5
  export declare function definePlugin<P extends VeskPlugin>(plugin: P): P;
4
6
  export declare function defineConfig(config: VeskConfig): VeskConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAsB,MAAM,0BAA0B,CAAC;AA0BzG,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,YAAiB,GAAG,YAAY,CAY/E;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQ/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAqB3D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAkC7D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAgC,MAAM,0BAA0B,CAAC;AAGnH,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,UAIjC,CAAC;AA6CF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,YAAiB,GAAG,YAAY,CAY/E;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQ/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAuB3D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAkC7D"}
package/dist/config.js CHANGED
@@ -1,4 +1,26 @@
1
1
  import { VeskError } from '@vesk/compiler/src/errors';
2
+ /** Tags allowed by default when md.html = 'allowlist' and no allowTags given. */
3
+ export const MD_DEFAULT_ALLOW_TAGS = [
4
+ 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'del', 'dfn', 'em',
5
+ 'i', 'ins', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span',
6
+ 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr',
7
+ ];
8
+ const MD_HTML_MODES = ['escape', 'allow', 'allowlist'];
9
+ function normalizeMdConfig(md) {
10
+ if (!md || typeof md !== 'object')
11
+ return md;
12
+ if (md.html !== undefined && !MD_HTML_MODES.includes(md.html)) {
13
+ throw VeskError.configError(`Unknown md.html mode: "${md.html}".`, MD_HTML_MODES);
14
+ }
15
+ const out = { ...md };
16
+ if (out.allowTags) {
17
+ if (!Array.isArray(out.allowTags)) {
18
+ throw VeskError.configError('md.allowTags must be an array of tag names.', []);
19
+ }
20
+ out.allowTags = out.allowTags.map((t) => String(t).toLowerCase().replace(/[^a-z0-9-]/g, '')).filter(Boolean);
21
+ }
22
+ return out;
23
+ }
2
24
  const SECURITY_PRESETS = {
3
25
  strict: {
4
26
  autoEscape: true,
@@ -71,6 +93,9 @@ export function defineConfig(config) {
71
93
  sec.redactLogs = true;
72
94
  if (config.routeDataCache === undefined)
73
95
  config.routeDataCache = 0;
96
+ const md = normalizeMdConfig(config.md);
97
+ if (md !== undefined)
98
+ config.md = md;
74
99
  return config;
75
100
  }
76
101
  export function validateConfig(config) {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAWD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAeD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA6BnG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAWvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkB/G,QAAQ,IAAI,MAAM;IAgBlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA6BnG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAWvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkB/G,QAAQ,IAAI,MAAM;IAgBlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
package/dist/errors.js CHANGED
@@ -31,19 +31,48 @@ export function didYouMean(name, candidates, maxDistance = 3) {
31
31
  return best;
32
32
  }
33
33
  function extractLineColumn(message) {
34
- const lineMatch = message.match(/(?:line|at\s+line)\s*(\d+)/i);
35
- const colMatch = message.match(/(?:column|col)\s*(\d+)/i);
36
34
  return {
37
- line: lineMatch ? parseInt(lineMatch[1]) : 0,
38
- column: colMatch ? parseInt(colMatch[1]) : 0,
35
+ line: findKeywordNumber(message, ['at line', 'line']),
36
+ column: findKeywordNumber(message, ['column', 'col']),
39
37
  };
40
38
  }
39
+ /**
40
+ * Finds `<keyword><whitespace>*<digits>` in `message`, trying longer keyword
41
+ * variants first so 'column' is not matched as 'col' + 'umn'. Char-scan only.
42
+ */
43
+ function findKeywordNumber(message, keywords) {
44
+ const lower = message.toLowerCase();
45
+ for (const kw of keywords) {
46
+ let from = 0;
47
+ while (from <= lower.length - kw.length) {
48
+ const idx = lower.indexOf(kw, from);
49
+ if (idx === -1)
50
+ break;
51
+ // word boundary before the keyword
52
+ const before = idx > 0 ? lower[idx - 1] : ' ';
53
+ if (!(before >= 'a' && before <= 'z')) {
54
+ let j = idx + kw.length;
55
+ while (j < message.length && (message[j] === ' ' || message[j] === '\t'))
56
+ j++;
57
+ if (message[j] >= '0' && message[j] <= '9') {
58
+ let end = j;
59
+ while (end < message.length && message[end] >= '0' && message[end] <= '9')
60
+ end++;
61
+ return parseInt(message.slice(j, end));
62
+ }
63
+ }
64
+ from = idx + 1;
65
+ }
66
+ }
67
+ return 0;
68
+ }
41
69
  const VESK_BUILTINS = [
42
70
  'useFetch', 'useRouter', 'useParams', 'usePathname', 'useSearchParams',
43
71
  'useNavigate', 'useHead', 'useTitle',
44
72
  'Form', 'Field', 'Link', 'NavLink', 'Outlet',
45
73
  'Image', 'Portal',
46
74
  'Experiment',
75
+ 'LoadingIndicator', 'useLoadingIndicator',
47
76
  'required', 'email', 'minLength', 'maxLength', 'pattern', 'custom',
48
77
  'track', 'get', 'set', 'derived', 'effect', 'batch', 'untrack',
49
78
  'cookies', 'headers', 'locals',
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { parse, createBaseParser } from '@vesk/compiler/src/parser';
2
2
  export { VeskParserPlugin } from '@vesk/compiler/src/vesk-plugin';
3
3
  export { generateIR } from '@vesk/compiler/src/ir-generator';
4
4
  export { render } from '@vesk/compiler/src/server-codegen';
5
+ export { AMBIENT, RUNTIME_OVERRIDE } from '@vesk/compiler/src/typecheck';
5
6
  export { compileClient, compile } from '@vesk/compiler/src/client-codegen';
6
7
  export { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, ComponentCall, Expression, } from '@vesk/compiler/src/ir';
7
8
  export type { IRNode } from '@vesk/compiler/src/ir';
@@ -13,4 +14,6 @@ export type { VeskCors } from '@vesk/compiler/src/types';
13
14
  export type { VeskRateLimit } from '@vesk/compiler/src/types';
14
15
  export { defineConfig, validateConfig, preset, definePlugin } from '@vesk/compiler/src/config';
15
16
  export { scanRoutes, scanComponents, collectSources, matchUrl } from '@vesk/compiler/src/router';
17
+ export { compileVskCodegen } from '@vesk/compiler/src/vsk-tsx';
18
+ export type { CodeInfo, CodeMapping, StyleRegion, VskCodegenError, VskCodegenOptions, VskCodegenResult, } from '@vesk/compiler/src/vsk-tsx';
16
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EACL,MAAM,EACN,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EACL,MAAM,EACN,WAAW,EACX,UAAU,EACV,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAEpD,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -2,7 +2,9 @@ export { parse, createBaseParser } from '@vesk/compiler/src/parser';
2
2
  export { VeskParserPlugin } from '@vesk/compiler/src/vesk-plugin';
3
3
  export { generateIR } from '@vesk/compiler/src/ir-generator';
4
4
  export { render } from '@vesk/compiler/src/server-codegen';
5
+ export { AMBIENT, RUNTIME_OVERRIDE } from '@vesk/compiler/src/typecheck';
5
6
  export { compileClient, compile } from '@vesk/compiler/src/client-codegen';
6
7
  export { IRRoot, ComponentIR, StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, ComponentCall, Expression, } from '@vesk/compiler/src/ir';
7
8
  export { defineConfig, validateConfig, preset, definePlugin } from '@vesk/compiler/src/config';
8
9
  export { scanRoutes, scanComponents, collectSources, matchUrl } from '@vesk/compiler/src/router';
10
+ export { compileVskCodegen } from '@vesk/compiler/src/vsk-tsx';
@@ -1 +1 @@
1
- {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;AA+I/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAylBD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAwN3D"}
1
+ {"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;AA+I/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAmsBD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA0N3D"}
@@ -238,6 +238,20 @@ function processAttribute(source, attr) {
238
238
  }
239
239
  return { name, value: '' };
240
240
  }
241
+ /**
242
+ * Recognizes a statement-mode `if (cond)` opening among JSX children.
243
+ * Returns the condition source, or null when `text` does not open an if
244
+ * header (keyword + balanced parens).
245
+ */
246
+ function extractIfHeader(text) {
247
+ if (!startsWithIdentifier(text, 'if'))
248
+ return null;
249
+ let i = skipWhitespace(text, 2);
250
+ if (text[i] !== '(')
251
+ return null;
252
+ const end = findBalancedEnd(text, i);
253
+ return text.slice(i + 1, end).trim();
254
+ }
241
255
  function extractForHeader(text) {
242
256
  if (!startsWithIdentifier(text, 'for'))
243
257
  return null;
@@ -271,6 +285,67 @@ function processJSXChildren(source, children) {
271
285
  i++;
272
286
  continue;
273
287
  }
288
+ // ── statement-mode if / else-if / else among element children ──
289
+ const ifCond = extractIfHeader(trimmed);
290
+ const seqNext = children[i + 1];
291
+ if (ifCond !== null &&
292
+ seqNext !== undefined &&
293
+ seqNext.type === 'JSXExpressionContainer' &&
294
+ seqNext.expression.type !== 'JSXEmptyExpression') {
295
+ const links = [];
296
+ let alternate = null;
297
+ const containerToNodes = (container) => {
298
+ const exprNode = container.expression;
299
+ if (exprNode.type === 'JSXFragment') {
300
+ const inner = [];
301
+ for (const c of exprNode.children)
302
+ inner.push(...processJSXChildren(source, [c]));
303
+ return inner;
304
+ }
305
+ if (exprNode.type === 'JSXElement')
306
+ return processJSXElement(source, exprNode);
307
+ return exprToIR(source, exprNode);
308
+ };
309
+ links.push({
310
+ cond: new Expression(ifCond, [], parseExprNode(ifCond), null),
311
+ nodes: containerToNodes(seqNext),
312
+ });
313
+ let j = i + 2;
314
+ while (j < children.length) {
315
+ const sep = children[j];
316
+ if (sep === undefined || sep.type !== 'JSXText')
317
+ break;
318
+ const sv = sep.value.trim();
319
+ const cont = children[j + 1];
320
+ if (sv.startsWith('} else if') || sv.startsWith('else if')) {
321
+ const ifIdx = sv.indexOf('if');
322
+ const header = extractIfHeader(sv.slice(ifIdx));
323
+ if (header === null || cont === undefined || cont.type !== 'JSXExpressionContainer')
324
+ break;
325
+ links.push({
326
+ cond: new Expression(header, [], parseExprNode(header), null),
327
+ nodes: containerToNodes(cont),
328
+ });
329
+ j += 2;
330
+ continue;
331
+ }
332
+ if (sv.startsWith('} else') || sv === 'else') {
333
+ if (cont === undefined || cont.type !== 'JSXExpressionContainer')
334
+ break;
335
+ alternate = containerToNodes(cont);
336
+ j += 2;
337
+ }
338
+ break;
339
+ }
340
+ // Fold right-to-left: earlier conditions wrap later ones as alternate.
341
+ let tail = alternate || [];
342
+ for (let k = links.length - 1; k >= 0; k--) {
343
+ tail = [new OpaqueDynamicRegion(links[k].cond, links[k].nodes, tail)];
344
+ }
345
+ result.push(...tail);
346
+ i = j;
347
+ continue;
348
+ }
274
349
  const forDecl = extractForHeader(trimmed);
275
350
  const nextChild = children[i + 1];
276
351
  if (forDecl !== null && nextChild !== undefined && (nextChild.type === 'JSXExpressionContainer' ||
@@ -835,6 +910,37 @@ function processEnum(node, source, exported) {
835
910
  const prefix = exported ? `export const ${name}` : `const ${name}`;
836
911
  return `${prefix} = { ${allPairs} };`;
837
912
  }
913
+ /**
914
+ * Normalizes any `@vesk/runtime/<subpath>` import specifier to the bare
915
+ * `'@vesk/runtime'` form. The runtime resolves all subpaths onto the same
916
+ * module graph, and downstream consumers (client scope injection, chunk
917
+ * stripping) key off the canonical bare specifier. Char-scan based — no
918
+ * regex, per repo rule.
919
+ */
920
+ function normalizeRuntimeSpecifier(stmt) {
921
+ const fromIdx = stmt.indexOf(' from ');
922
+ if (fromIdx === -1)
923
+ return stmt;
924
+ const q1 = idxOfQuote(stmt, fromIdx);
925
+ if (q1 === -1)
926
+ return stmt;
927
+ const quote = stmt[q1];
928
+ const q2 = stmt.indexOf(quote, q1 + 1);
929
+ if (q2 === -1)
930
+ return stmt;
931
+ const spec = stmt.slice(q1 + 1, q2);
932
+ if (spec === '@vesk/runtime' || !spec.startsWith('@vesk/runtime/'))
933
+ return stmt;
934
+ return stmt.slice(0, q1 + 1) + '@vesk/runtime' + stmt.slice(q2);
935
+ }
936
+ function idxOfQuote(s, from) {
937
+ for (let i = from; i < s.length; i++) {
938
+ const c = s[i];
939
+ if (c === "'" || c === '"')
940
+ return i;
941
+ }
942
+ return -1;
943
+ }
838
944
  export function generateIR(ast, source) {
839
945
  __vskAnnotations = ast.__vskAnnotations ?? [];
840
946
  const components = [];
@@ -846,9 +952,10 @@ export function generateIR(ast, source) {
846
952
  for (const node of ast.body) {
847
953
  if (node.type === 'ImportDeclaration') {
848
954
  const raw = getSource(source, node);
849
- const cleaned = stripTypeImport(raw);
955
+ let cleaned = stripTypeImport(raw);
850
956
  if (cleaned === null)
851
957
  continue;
958
+ cleaned = normalizeRuntimeSpecifier(cleaned);
852
959
  imports.push(cleaned);
853
960
  for (const spec of node.specifiers) {
854
961
  if (spec.importKind === 'type')
@@ -991,6 +1098,7 @@ export function generateIR(ast, source) {
991
1098
  'Link', 'NavLink', 'Outlet', 'Redirect',
992
1099
  'Image', 'Portal',
993
1100
  'Experiment',
1101
+ 'LoadingIndicator', 'useLoadingIndicator',
994
1102
  'JsonLd', 'ArticleSchema', 'ProductSchema', 'FAQPageSchema', 'BreadcrumbListSchema',
995
1103
  'OrganizationSchema', 'LocalBusinessSchema', 'VideoSchema',
996
1104
  'effect', 'derived', 'untrack', 'peek', 'tick', 'flushSync', 'on_destroy',
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Rewrites static `content="<file>.md"` attributes on <Md> elements into
3
+ * inlined constants. Returns the transformed source; when nothing matches,
4
+ * the original string is returned untouched.
5
+ *
6
+ * `mdRoots` are candidate project roots for absolute-style specifiers
7
+ * ("/x.md"); each root is tried as <root>/public<x> and <root><x>.
8
+ */
9
+ export declare function inlineMdContentAttrs(source: string, importerDir: string | null, mdRoots?: string[]): string;
10
+ /**
11
+ * Candidate project roots for absolute-style specifiers: walks up from
12
+ * `dir` collecting directories that contain a package.json (bounded).
13
+ */
14
+ export declare function guessProjectRoots(dir: string | null): string[];
15
+ /** Convenience wrapper used by pipelines that already know the file dir. */
16
+ export declare function inlineMdImportsFrom(source: string, importerFile: string | null, mdRoots?: string[]): string;
17
+ //# sourceMappingURL=md-inline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"md-inline.d.ts","sourceRoot":"","sources":["../src/md-inline.ts"],"names":[],"mappings":"AA2CA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,GAAE,MAAM,EAAO,GACpB,MAAM,CAoDR;AAoBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAY9D;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE,MAAM,EAAO,GAAG,MAAM,CAG/G"}