@ozsarman/clarityjs 0.6.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.
package/src/image.js ADDED
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Clarity.js — Image Optimization
3
+ *
4
+ * next/image / @nuxt/image eşdeğeri.
5
+ * Responsive srcset, lazy loading, blur placeholder, CLS önleme, WebP/AVIF.
6
+ *
7
+ * ── Clarity şablonunda kullanım ───────────────────────────────────────────────
8
+ *
9
+ * import { ClarityImage } from '@ozsarman/clarityjs/image'
10
+ *
11
+ * component Hero() {
12
+ * render {
13
+ * { ClarityImage({
14
+ * src: '/images/hero.jpg',
15
+ * alt: 'Hero görseli',
16
+ * width: 1200,
17
+ * height: 600,
18
+ * priority: true, // LCP görüntüsü — hemen yükle
19
+ * sizes: '100vw',
20
+ * }) }
21
+ *
22
+ * { ClarityImage({
23
+ * src: '/images/avatar.jpg',
24
+ * alt: 'Profil fotoğrafı',
25
+ * width: 80,
26
+ * height: 80,
27
+ * quality: 80,
28
+ * placeholder: 'blur',
29
+ * blurDataURL: 'data:image/jpeg;base64,...',
30
+ * }) }
31
+ * }
32
+ * }
33
+ *
34
+ * ── Vite plugin entegrasyonu (otomatik) ──────────────────────────────────────
35
+ *
36
+ * clarityPlugin({ imageOptimization: true })
37
+ * // Build sırasında: görüntüleri WebP'ye çevirir, birden fazla boyut üretir
38
+ * // Dev: orijinal görüntüyü döndürür
39
+ *
40
+ * ── Loader API (özel CDN) ─────────────────────────────────────────────────────
41
+ *
42
+ * import { setImageLoader } from '@ozsarman/clarityjs/image'
43
+ *
44
+ * setImageLoader(({ src, width, quality }) =>
45
+ * `https://cdn.example.com/${src}?w=${width}&q=${quality}&fmt=webp`
46
+ * );
47
+ *
48
+ * Author: Claude (Anthropic) + Özdemir Sarman
49
+ */
50
+
51
+ // ─── Varsayılan görüntü boyutları (responsive breakpoints) ───────────────────
52
+
53
+ const DEFAULT_SIZES = [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];
54
+ const DEVICE_SIZES = [640, 750, 828, 1080, 1200, 1920, 2048, 3840];
55
+ const IMAGE_SIZES = [16, 32, 48, 64, 96, 128, 256, 384];
56
+
57
+ // ─── Loader ───────────────────────────────────────────────────────────────────
58
+
59
+ let _loader = null;
60
+
61
+ /**
62
+ * Özel görüntü URL oluşturucu tanımla (CDN entegrasyonu için).
63
+ *
64
+ * @param {Function} loaderFn – ({ src, width, quality, format }) => string
65
+ */
66
+ export function setImageLoader(loaderFn) {
67
+ _loader = loaderFn;
68
+ }
69
+
70
+ function _buildUrl(src, width, quality = 75, format = 'webp') {
71
+ if (_loader) return _loader({ src, width, quality, format });
72
+
73
+ // Varsayılan: Clarity image endpoint (geliştirme sunucusu + production)
74
+ if (src.startsWith('http://') || src.startsWith('https://')) {
75
+ // Harici URL — optimize edilemez, doğrudan döndür
76
+ return src;
77
+ }
78
+ return `/_clarity/image?url=${encodeURIComponent(src)}&w=${width}&q=${quality}&fmt=${format}`;
79
+ }
80
+
81
+ // ─── ClarityImage runtime component ──────────────────────────────────────────
82
+
83
+ /**
84
+ * Optimize edilmiş görüntü elementi oluştur.
85
+ *
86
+ * @param {object} props
87
+ * @param {string} props.src – görüntü yolu (zorunlu)
88
+ * @param {string} props.alt – erişilebilirlik metni (zorunlu)
89
+ * @param {number} props.width – görüntü genişliği px (zorunlu — CLS önleme)
90
+ * @param {number} props.height – görüntü yüksekliği px (zorunlu — CLS önleme)
91
+ * @param {string} [props.sizes] – CSS sizes değeri (örn: '(max-width: 768px) 100vw, 50vw')
92
+ * @param {number} [props.quality=75] – sıkıştırma kalitesi 1-100
93
+ * @param {boolean} [props.priority] – LCP görüntüsü: preload ekle, lazy yükleme kapat
94
+ * @param {string} [props.placeholder] – 'blur' | 'empty' (default: 'empty')
95
+ * @param {string} [props.blurDataURL] – base64 blur placeholder
96
+ * @param {string} [props.className] – CSS sınıfı
97
+ * @param {string} [props.style] – inline stil
98
+ * @param {string} [props.objectFit] – CSS object-fit: 'cover' | 'contain' | ...
99
+ * @param {string} [props.objectPosition] – CSS object-position
100
+ * @param {Function} [props.onLoad] – yükleme tamamlandığında callback
101
+ * @param {Function} [props.onError] – hata durumunda callback
102
+ * @returns {HTMLElement}
103
+ */
104
+ export function ClarityImage(props = {}) {
105
+ const {
106
+ src,
107
+ alt = '',
108
+ width,
109
+ height,
110
+ sizes = '100vw',
111
+ quality = 75,
112
+ priority = false,
113
+ placeholder = 'empty',
114
+ blurDataURL = null,
115
+ className = '',
116
+ style = '',
117
+ objectFit = null,
118
+ objectPosition = null,
119
+ onLoad = null,
120
+ onError = null,
121
+ } = props;
122
+
123
+ if (!src) throw new Error('[clarity/image] ClarityImage: src zorunludur');
124
+
125
+ // Wrapper div — CLS önlemek için aspect ratio tutar
126
+ const wrapper = typeof document !== 'undefined'
127
+ ? _createClientImage(props)
128
+ : null;
129
+
130
+ return wrapper || _createSSRImage(props);
131
+ }
132
+
133
+ function _createSSRImage({ src, alt, width, height, sizes, quality, priority, placeholder, blurDataURL, className, style, objectFit, objectPosition }) {
134
+ // SSR: <img> tag HTML string olarak üretilir — ssr.js tarafından kullanılır
135
+ // Bu fonksiyon doğrudan HTMLElement döndürür — server ortamında string tabanlı render
136
+
137
+ // srcset üret
138
+ const relevantSizes = _getRelevantSizes(width);
139
+ const srcset = relevantSizes
140
+ .map(w => `${_buildUrl(src, w, quality)} ${w}w`)
141
+ .join(', ');
142
+
143
+ // Placeholder stili
144
+ let imgStyle = '';
145
+ if (objectFit) imgStyle += `object-fit:${objectFit};`;
146
+ if (objectPosition) imgStyle += `object-position:${objectPosition};`;
147
+ if (style) imgStyle += style;
148
+
149
+ // Blur placeholder
150
+ if (placeholder === 'blur' && blurDataURL) {
151
+ imgStyle += `background-image:url(${blurDataURL});background-size:cover;`;
152
+ }
153
+
154
+ const loading = priority ? 'eager' : 'lazy';
155
+ const decoding = priority ? 'sync' : 'async';
156
+ const fetchpriority = priority ? ' fetchpriority="high"' : '';
157
+
158
+ // SSR için basit string döndür (ssr.js h() fonksiyonuyla entegre olur)
159
+ return {
160
+ __clarityImageSSR: true,
161
+ html: `<img
162
+ src="${_buildUrl(src, width ?? 1200, quality)}"
163
+ srcset="${srcset}"
164
+ sizes="${sizes}"
165
+ alt="${_escAttr(alt)}"
166
+ width="${width || ''}"
167
+ height="${height || ''}"
168
+ loading="${loading}"
169
+ decoding="${decoding}"${fetchpriority}
170
+ ${className ? `class="${_escAttr(className)}"` : ''}
171
+ ${imgStyle ? `style="${_escAttr(imgStyle)}"` : ''}
172
+ />`,
173
+ };
174
+ }
175
+
176
+ function _createClientImage({ src, alt, width, height, sizes, quality, priority, placeholder, blurDataURL, className, style, objectFit, objectPosition, onLoad, onError }) {
177
+ const img = document.createElement('img');
178
+
179
+ // Zorunlu attr
180
+ img.alt = alt;
181
+ if (width) img.width = width;
182
+ if (height) img.height = height;
183
+
184
+ // Stil
185
+ const styles = [];
186
+ if (objectFit) styles.push(`object-fit:${objectFit}`);
187
+ if (objectPosition) styles.push(`object-position:${objectPosition}`);
188
+ if (style) styles.push(style);
189
+ if (styles.length) img.style.cssText = styles.join(';');
190
+
191
+ if (className) img.className = className;
192
+
193
+ // Blur placeholder
194
+ if (placeholder === 'blur' && blurDataURL) {
195
+ img.style.backgroundImage = `url(${blurDataURL})`;
196
+ img.style.backgroundSize = 'cover';
197
+ img.addEventListener('load', () => {
198
+ img.style.backgroundImage = '';
199
+ }, { once: true });
200
+ }
201
+
202
+ // srcset
203
+ const relevantSizes = _getRelevantSizes(width);
204
+ img.srcset = relevantSizes.map(w => `${_buildUrl(src, w, quality)} ${w}w`).join(', ');
205
+ img.sizes = sizes;
206
+ img.src = _buildUrl(src, width ?? 1200, quality);
207
+ img.loading = priority ? 'eager' : 'lazy';
208
+ img.decoding = priority ? 'sync' : 'async';
209
+
210
+ if (priority) {
211
+ img.setAttribute('fetchpriority', 'high');
212
+ // Preload link ekle
213
+ _injectPreload(src, sizes, relevantSizes, quality);
214
+ }
215
+
216
+ if (onLoad) img.addEventListener('load', onLoad);
217
+ if (onError) img.addEventListener('error', onError);
218
+
219
+ return img;
220
+ }
221
+
222
+ // ─── Vite / Dev Server: görüntü endpoint'i ───────────────────────────────────
223
+
224
+ /**
225
+ * Express/Vite için görüntü optimizasyon endpoint'i.
226
+ * Dev server'da `/_clarity/image` isteğini karşılar.
227
+ *
228
+ * @param {object} [opts]
229
+ * @param {boolean} [opts.sharp=false] – sharp kütüphanesi varsa kullan (prod)
230
+ * @returns {Function} Express middleware
231
+ */
232
+ export function createImageMiddleware(opts = {}) {
233
+ const { sharp: useSharp = false } = opts;
234
+
235
+ return async function imageMiddleware(req, res, next) {
236
+ if (!req.path.startsWith('/_clarity/image')) return next();
237
+
238
+ const { url, w, q = '75', fmt = 'webp' } = req.query;
239
+ if (!url) return res.status(400).json({ error: 'url parametresi gerekli' });
240
+
241
+ const width = parseInt(w, 10) || null;
242
+ const quality = Math.min(100, Math.max(1, parseInt(q, 10)));
243
+
244
+ try {
245
+ // Dev modda: orijinal görüntüyü döndür (optimizasyon yok)
246
+ if (!useSharp) {
247
+ const { createReadStream, existsSync } = await import('node:fs');
248
+ const { resolve } = await import('node:path');
249
+ const filePath = resolve('.' + decodeURIComponent(url));
250
+ if (!existsSync(filePath)) return res.status(404).end();
251
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
252
+ return createReadStream(filePath).pipe(res);
253
+ }
254
+
255
+ // Production: sharp ile WebP dönüşümü
256
+ const sharp = await import('sharp');
257
+ const { resolve } = await import('node:path');
258
+ const filePath = resolve('.' + decodeURIComponent(url));
259
+
260
+ let pipeline = sharp.default(filePath);
261
+ if (width) pipeline = pipeline.resize(width);
262
+
263
+ const buffer = await pipeline[fmt === 'avif' ? 'avif' : 'webp']({ quality }).toBuffer();
264
+ res.setHeader('Content-Type', `image/${fmt}`);
265
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
266
+ res.send(buffer);
267
+
268
+ } catch (err) {
269
+ console.error('[clarity/image] middleware hata:', err.message);
270
+ next(err);
271
+ }
272
+ };
273
+ }
274
+
275
+ // ─── Build-time görüntü optimizasyonu (Vite plugin hook) ─────────────────────
276
+
277
+ /**
278
+ * Vite plugin için görüntü optimizasyon hook'u.
279
+ * vite-plugin.js içinde `generateBundle` hook'undan çağrılır.
280
+ *
281
+ * @param {string} publicDir – public/ dizini
282
+ * @param {string} outDir – dist/ dizini
283
+ * @param {object} [opts]
284
+ */
285
+ export async function optimizeImages(publicDir, outDir, opts = {}) {
286
+ const { quality = 80, formats = ['webp'], sizes = DEVICE_SIZES } = opts;
287
+
288
+ let sharp;
289
+ try {
290
+ sharp = (await import('sharp')).default;
291
+ } catch {
292
+ console.warn('[clarity/image] sharp bulunamadı — görüntü optimizasyonu atlandı. npm install sharp');
293
+ return;
294
+ }
295
+
296
+ const { readdir, mkdir: mkdirFn, stat: statFn } = await import('node:fs/promises');
297
+ const { join: joinFn, extname: extnFn } = await import('node:path');
298
+ const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff']);
299
+
300
+ async function processDir(dir, outBase) {
301
+ const entries = await readdir(dir).catch(() => []);
302
+ for (const entry of entries) {
303
+ const srcFull = joinFn(dir, entry);
304
+ const info = await statFn(srcFull).catch(() => null);
305
+ if (!info) continue;
306
+
307
+ if (info.isDirectory()) {
308
+ await processDir(srcFull, joinFn(outBase, entry));
309
+ continue;
310
+ }
311
+
312
+ const ext = extnFn(entry).toLowerCase();
313
+ if (!IMAGE_EXTS.has(ext)) continue;
314
+
315
+ await mkdirFn(joinFn(outBase, '_clarity_images'), { recursive: true });
316
+
317
+ for (const fmt of formats) {
318
+ for (const w of sizes) {
319
+ const outName = `${entry.replace(ext, '')}_${w}.${fmt}`;
320
+ const outFull = joinFn(outBase, '_clarity_images', outName);
321
+ try {
322
+ await sharp(srcFull).resize(w)[fmt]({ quality }).toFile(outFull);
323
+ } catch { /* skip */ }
324
+ }
325
+ }
326
+ }
327
+ }
328
+
329
+ await processDir(publicDir, outDir);
330
+ console.log('[clarity/image] ✅ Görüntü optimizasyonu tamamlandı');
331
+ }
332
+
333
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
334
+
335
+ function _getRelevantSizes(width) {
336
+ const max = width ? width * 2 : 3840;
337
+ return DEFAULT_SIZES.filter(s => s <= max);
338
+ }
339
+
340
+ function _injectPreload(src, sizes, widths, quality) {
341
+ if (typeof document === 'undefined') return;
342
+ const link = document.createElement('link');
343
+ link.rel = 'preload';
344
+ link.as = 'image';
345
+ link.imageSrcset = widths.map(w => `${_buildUrl(src, w, quality)} ${w}w`).join(', ');
346
+ link.imageSizes = sizes;
347
+ document.head.appendChild(link);
348
+ }
349
+
350
+ function _escAttr(str) {
351
+ return String(str ?? '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
352
+ }
package/src/index.js ADDED
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Clarity.js — Public API
3
+ *
4
+ * The full compiler pipeline:
5
+ * source (.clarity) → tokens → AST → JavaScript
6
+ *
7
+ * Author: Claude (Anthropic)
8
+ */
9
+
10
+ import { Lexer, tokenize } from './lexer.js';
11
+ import { Parser, parse } from './parser.js';
12
+ import { CodeGenerator, generate } from './codegen.js';
13
+ import { TypeGenerator, generateTypes } from './typegen.js';
14
+
15
+ export { Lexer, tokenize, T } from './lexer.js';
16
+ export { Parser, parse } from './parser.js';
17
+ export { CodeGenerator, generate } from './codegen.js';
18
+ export { TypeGenerator, generateTypes } from './typegen.js';
19
+
20
+ // Router — re-exported for convenience (also importable from '@ozsarman/clarityjs/router')
21
+ export {
22
+ navigate, navigateReplace, back, forward,
23
+ currentPath, currentQuery, routeParams,
24
+ matchRoute, Route, Link, Switch, Outlet,
25
+ beforeEnter, removeGuard,
26
+ createRouter, setServerPath, createSSRMiddleware, renderWithPath,
27
+ prefetchRoute, registerRouteChunk, injectModulepreloads,
28
+ useViewTransition,
29
+ } from './router.js';
30
+
31
+ // File-based pages router — consumes the Vite `scanPages` virtual module
32
+ export {
33
+ filePathToRoutePattern, routeScore, sortRoutes, selectRoute,
34
+ buildRouteTable, createPagesRouter,
35
+ } from './pages-router.js';
36
+
37
+ // Reactivity extras
38
+ export { reactive, createContext, useContext, _pushContext, _popContext,
39
+ _withComponent, beforeMount, onMount, onCleanup, onUpdate,
40
+ createRef, useRef } from './runtime.js';
41
+
42
+ // AI Agent SDK — audit log + permission enforcement
43
+ export { getAIAuditLog, clearAIAuditLog, onAIAction,
44
+ _aiAuditRecord, _setAIActionContext, _protectedSignal,
45
+ ClarityAIForbiddenError, _aiThrowForbidden } from './runtime.js';
46
+
47
+ // Error boundary
48
+ export { ErrorBoundary } from './runtime.js';
49
+
50
+ // Portal — render children outside the component tree
51
+ export { createPortal } from './runtime.js';
52
+
53
+ // Suspense — show fallback while lazy children load
54
+ export { Suspense } from './runtime.js';
55
+
56
+ // SSR — server-side rendering utilities
57
+ export { renderToString, renderToStringAsync, renderToDocument, renderToStream, escapeHTML, escapeAttr, SSRElement } from './ssr.js';
58
+
59
+ // Hydration — client-side adoption of server-rendered HTML
60
+ export { hydrateRoot, isHydrated, getSSRData } from './hydrate.js';
61
+
62
+ // Component-level SSR data fetching — server fetch + serialize + hydrate
63
+ export { useServerData, renderWithServerData, renderToDocumentWithData } from './server-data.js';
64
+
65
+ // Game / animation loop — requestAnimationFrame with fixed/variable timestep
66
+ export { createGameLoop, useRaf } from './game-loop.js';
67
+
68
+ // Async state — TanStack Query / SWR style reactive data fetching
69
+ export {
70
+ createQuery, useFetch, createMutation,
71
+ invalidateQuery, prefetchQuery, clearQueryCache, getCacheEntry,
72
+ } from './async-state.js';
73
+
74
+ // Transitions — Vue-compatible enter/leave animations + FLIP list animations
75
+ export { Transition, TransitionGroup, useTransition, injectTransitionStyles } from './transitions.js';
76
+
77
+ // Scoped CSS — <style scoped> processor for .clarity files
78
+ export { extractStyles, scopeCSS, injectScopeAttr, collectStyles, hashId, cssModules } from './scoped-css.js';
79
+
80
+ // Global Store — Pinia/Zustand style cross-component reactive state
81
+ export { createStore, getStoreRegistry, resetAllStores, disposeAllStores } from './store.js';
82
+
83
+ // Head / Meta management — reactive <title>, <meta>, <og:*>, <link rel="canonical">
84
+ export { createHead, getHead, useHead, renderHead, _setHeadLifecycleHook } from './head.js';
85
+
86
+ // Image Optimization — next/image eşdeğeri: responsive srcset, lazy, blur placeholder, WebP/AVIF
87
+ export { ClarityImage, setImageLoader, createImageMiddleware, optimizeImages } from './image.js';
88
+
89
+ // Font Optimization — next/font eşdeğeri: Google Fonts self-hosting, lokal font, sıfır layout shift
90
+ export { GoogleFont, LocalFont, SystemFonts, renderFontHead, resetFontRegistry, getRegisteredFonts, downloadGoogleFonts, optimizeFonts, applyFontDisplay } from './font.js';
91
+
92
+ // SSG — Static Site Generation: getStaticProps/getStaticPaths, build-time HTML
93
+ export { generateStaticSite, fetchStaticProps, fetchStaticPaths, createSSGContext } from './ssg.js';
94
+
95
+ // ISR — Incremental Static Regeneration: stale-while-revalidate, on-demand revalidation
96
+ export { revalidatePath, revalidateTag, createISRMiddleware, startISRScheduler } from './isr.js';
97
+
98
+ // Islands Architecture — selective hydration, zero JS server components
99
+ export { createIsland, hydrateIslands, defineServerComponent, isServerComponent, renderPageWithIslands } from './islands.js';
100
+
101
+ // Layout System — nested layouts, definePageMeta, dosya tabanlı _layout.js konvansiyonu
102
+ export { defineLayout, getLayout, resetLayoutRegistry, definePageMeta, buildLayoutChain, wrapWithLayouts, renderWithLayouts, scanAndRegisterLayouts, initLayoutRouter, BlankLayout, HTMLShellLayout } from './layout.js';
103
+
104
+ // Formatter & Linter — clarity format / clarity lint
105
+ export { formatCode, formatFile, lintCode, lintFile, processDirectory, formatLintReport, loadConfig, DEFAULT_FORMAT_OPTIONS, DEFAULT_LINT_RULES } from './linter.js';
106
+
107
+ // File-based routing conventions — loading.js, error.js, not-found.js
108
+ export {
109
+ registerConventions, resolveConventions, resetConventionRegistry,
110
+ wrapWithConventions, scanConventions,
111
+ resolveNotFound,
112
+ DefaultNotFound, DefaultLoading,
113
+ createFileRouter, clarityConventionsPlugin,
114
+ LOADING_FILE, ERROR_FILE, NOT_FOUND_FILE, LAYOUT_FILE, SPECIAL_FILES,
115
+ } from './file-conventions.js';
116
+
117
+ // TypeScript Language Service Plugin + Volar integration
118
+ export { init, createClarityTypeChecker, createClarityVolarPlugin, TSCONFIG_PRESET, AMBIENT_DECLARATIONS } from './ts-plugin.js';
119
+
120
+ // Edge Runtime — Cloudflare Workers, Deno Deploy, Vercel Edge, Bun support
121
+ export {
122
+ detectEdgeRuntime, isEdgeRuntime,
123
+ normalizeRequest,
124
+ htmlResponse, jsonResponse, redirectResponse, streamResponse,
125
+ renderEdge,
126
+ createEdgeHandler, adaptFetchHandler,
127
+ createEdgeCache,
128
+ getGeo, getEnv,
129
+ randomId, hmacSign,
130
+ } from './edge.js';
131
+
132
+ // Server Actions — type-safe client→server RPC, "use server" directive support
133
+ export {
134
+ defineServerAction, listServerActions,
135
+ createActionRouter,
136
+ createServerClient,
137
+ useServerAction,
138
+ registerServerModule,
139
+ composeActionMiddleware, logActionMiddleware, createAuthMiddleware,
140
+ } from './server-actions.js';
141
+
142
+ // Devtools — in-page overlay (Shadow DOM, no external deps)
143
+ export { initDevtools,
144
+ _devRecordSignalUpdate, _devRenderStart, _devRenderEnd, _devClearPerfData } from './devtools.js';
145
+
146
+ // Error Overlay — dev mode compiler + runtime error UI (Shadow DOM)
147
+ export { initErrorOverlay, _overlayShowError, _overlayDismiss } from './error-overlay.js';
148
+
149
+ // i18n — Internationalization with RTL support
150
+ export { createI18n, useI18n, t, getLocale, getIsRTL,
151
+ isRTLLocale, RTL_LOCALES, applyDir, BiDiWrapper } from './i18n.js';
152
+
153
+ /**
154
+ * Full compilation pipeline: .clarity source → JavaScript string
155
+ *
156
+ * @param {string} source - Clarity source code
157
+ * @param {object} options - Compilation options
158
+ * @returns {{ code: string, ast: object, tokens: array }}
159
+ */
160
+ export function compile(source, options = {}) {
161
+ const filename = options.filename ?? '<anonymous>';
162
+
163
+ // Step 1: Lex
164
+ const lexer = new Lexer(source, filename);
165
+ const tokens = lexer.tokenize();
166
+
167
+ // Step 2: Parse
168
+ const parser = new Parser(tokens, source);
169
+ const ast = parser.parse();
170
+
171
+ // Step 3: Generate
172
+ const codegen = new CodeGenerator({
173
+ ...options,
174
+ sourceFile: filename,
175
+ outputFile: filename.replace(/\.clarity$/, '.js'),
176
+ sourceContent: source,
177
+ });
178
+ const { code, map } = codegen.generate(ast);
179
+
180
+ // Optionally generate TypeScript declarations
181
+ const dts = options.types
182
+ ? new TypeGenerator({ runtimePath: options.runtimePath }).generate(ast, { filename })
183
+ : null;
184
+
185
+ return { code, map, dts, ast, tokens };
186
+ }
187
+
188
+ /**
189
+ * Quick compile — just returns the JavaScript string
190
+ */
191
+ export function compileToJS(source, options = {}) {
192
+ return compile(source, options).code;
193
+ }