@kuratchi/js 0.0.15 → 0.0.17

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 (70) hide show
  1. package/README.md +160 -1
  2. package/dist/cli.js +78 -45
  3. package/dist/compiler/api-route-pipeline.d.ts +8 -0
  4. package/dist/compiler/api-route-pipeline.js +23 -0
  5. package/dist/compiler/asset-pipeline.d.ts +7 -0
  6. package/dist/compiler/asset-pipeline.js +33 -0
  7. package/dist/compiler/client-module-pipeline.d.ts +25 -0
  8. package/dist/compiler/client-module-pipeline.js +257 -0
  9. package/dist/compiler/compiler-shared.d.ts +73 -0
  10. package/dist/compiler/compiler-shared.js +4 -0
  11. package/dist/compiler/component-pipeline.d.ts +15 -0
  12. package/dist/compiler/component-pipeline.js +158 -0
  13. package/dist/compiler/config-reading.d.ts +12 -0
  14. package/dist/compiler/config-reading.js +380 -0
  15. package/dist/compiler/convention-discovery.d.ts +9 -0
  16. package/dist/compiler/convention-discovery.js +83 -0
  17. package/dist/compiler/durable-object-pipeline.d.ts +9 -0
  18. package/dist/compiler/durable-object-pipeline.js +255 -0
  19. package/dist/compiler/error-page-pipeline.d.ts +1 -0
  20. package/dist/compiler/error-page-pipeline.js +16 -0
  21. package/dist/compiler/import-linking.d.ts +36 -0
  22. package/dist/compiler/import-linking.js +140 -0
  23. package/dist/compiler/index.d.ts +7 -7
  24. package/dist/compiler/index.js +181 -3321
  25. package/dist/compiler/layout-pipeline.d.ts +31 -0
  26. package/dist/compiler/layout-pipeline.js +155 -0
  27. package/dist/compiler/page-route-pipeline.d.ts +16 -0
  28. package/dist/compiler/page-route-pipeline.js +62 -0
  29. package/dist/compiler/parser.d.ts +4 -0
  30. package/dist/compiler/parser.js +436 -55
  31. package/dist/compiler/root-layout-pipeline.d.ts +10 -0
  32. package/dist/compiler/root-layout-pipeline.js +532 -0
  33. package/dist/compiler/route-discovery.d.ts +7 -0
  34. package/dist/compiler/route-discovery.js +87 -0
  35. package/dist/compiler/route-pipeline.d.ts +57 -0
  36. package/dist/compiler/route-pipeline.js +291 -0
  37. package/dist/compiler/route-state-pipeline.d.ts +26 -0
  38. package/dist/compiler/route-state-pipeline.js +139 -0
  39. package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
  40. package/dist/compiler/routes-module-feature-blocks.js +330 -0
  41. package/dist/compiler/routes-module-pipeline.d.ts +2 -0
  42. package/dist/compiler/routes-module-pipeline.js +6 -0
  43. package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
  44. package/dist/compiler/routes-module-runtime-shell.js +91 -0
  45. package/dist/compiler/routes-module-types.d.ts +45 -0
  46. package/dist/compiler/routes-module-types.js +1 -0
  47. package/dist/compiler/script-transform.d.ts +16 -0
  48. package/dist/compiler/script-transform.js +218 -0
  49. package/dist/compiler/server-module-pipeline.d.ts +13 -0
  50. package/dist/compiler/server-module-pipeline.js +124 -0
  51. package/dist/compiler/template.d.ts +13 -1
  52. package/dist/compiler/template.js +337 -71
  53. package/dist/compiler/worker-output-pipeline.d.ts +13 -0
  54. package/dist/compiler/worker-output-pipeline.js +37 -0
  55. package/dist/compiler/wrangler-sync.d.ts +14 -0
  56. package/dist/compiler/wrangler-sync.js +185 -0
  57. package/dist/runtime/app.js +15 -3
  58. package/dist/runtime/context.d.ts +4 -0
  59. package/dist/runtime/context.js +40 -2
  60. package/dist/runtime/do.js +21 -6
  61. package/dist/runtime/generated-worker.d.ts +55 -0
  62. package/dist/runtime/generated-worker.js +543 -0
  63. package/dist/runtime/index.d.ts +4 -1
  64. package/dist/runtime/index.js +2 -0
  65. package/dist/runtime/router.d.ts +6 -1
  66. package/dist/runtime/router.js +125 -31
  67. package/dist/runtime/security.d.ts +101 -0
  68. package/dist/runtime/security.js +298 -0
  69. package/dist/runtime/types.d.ts +29 -2
  70. package/package.json +5 -1
package/README.md CHANGED
@@ -28,6 +28,8 @@ bun run dev
28
28
 
29
29
  Point wrangler at the entry and you're done. **No `src/index.ts` needed.**
30
30
 
31
+ For the framework's internal compiler/runtime orchestration and tracked implementation roadmap, see [ARCHITECTURE.md](./ARCHITECTURE.md).
32
+
31
33
  ```jsonc
32
34
  // wrangler.jsonc
33
35
  {
@@ -764,9 +766,166 @@ Kuratchi also exposes a framework build-mode flag:
764
766
  - `dev` is compile-time framework state, not a generic process env var
765
767
  - `@kuratchi/js/environment` is intended for server route code, not client `$:` scripts
766
768
 
769
+ ## Security
770
+
771
+ Kuratchi includes built-in security features that are enabled by default or configurable via `kuratchi.config.ts`.
772
+
773
+ ### Default Security Headers
774
+
775
+ All responses include these headers automatically:
776
+
777
+ - `X-Content-Type-Options: nosniff`
778
+ - `X-Frame-Options: DENY`
779
+ - `Referrer-Policy: strict-origin-when-cross-origin`
780
+
781
+ ### CSRF Protection
782
+
783
+ CSRF protection is **enabled by default** for all form actions and RPC calls.
784
+
785
+ **How it works:**
786
+ 1. A cryptographically random token is generated per session and stored in a cookie
787
+ 2. The compiler auto-injects a hidden `_csrf` field into forms with `action={fn}`
788
+ 3. The client bridge includes the CSRF token header in fetch action requests
789
+ 4. Server validates the token using timing-safe comparison
790
+
791
+ No configuration required — it just works.
792
+
793
+ ```html
794
+ <!-- CSRF token is auto-injected -->
795
+ <form action={submitForm}>
796
+ <input type="text" name="email" />
797
+ <button type="submit">Submit</button>
798
+ </form>
799
+ ```
800
+
801
+ ### Authentication Enforcement
802
+
803
+ Optionally require authentication for all RPC calls or form actions:
804
+
805
+ ```ts
806
+ // kuratchi.config.ts
807
+ export default defineConfig({
808
+ security: {
809
+ rpcRequireAuth: true, // Require auth for all RPC calls (default: false)
810
+ actionRequireAuth: true, // Require auth for all form actions (default: false)
811
+ },
812
+ });
813
+ ```
814
+
815
+ When enabled, unauthenticated requests return `401 Authentication required`. The check looks for `locals.user` or `locals.session.user`, which is populated by `@kuratchi/auth`.
816
+
817
+ For per-function control, use guards in individual functions instead:
818
+
819
+ ```ts
820
+ import { requireAuth } from '@kuratchi/auth';
821
+
822
+ export async function deleteItem(formData: FormData) {
823
+ await requireAuth(); // Throws 401 if not authenticated
824
+ // ... action logic
825
+ }
826
+ ```
827
+
828
+ ### Configurable Security Headers
829
+
830
+ Add CSP, HSTS, and Permissions-Policy headers:
831
+
832
+ ```ts
833
+ // kuratchi.config.ts
834
+ export default defineConfig({
835
+ security: {
836
+ contentSecurityPolicy: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'",
837
+ strictTransportSecurity: "max-age=31536000; includeSubDomains",
838
+ permissionsPolicy: "camera=(), microphone=(), geolocation=()",
839
+ },
840
+ });
841
+ ```
842
+
843
+ ### HTML Sanitization
844
+
845
+ The `{@html}` directive automatically sanitizes output to prevent XSS:
846
+
847
+ - Removes dangerous elements: `<script>`, `<iframe>`, `<object>`, `<embed>`, `<style>`, `<template>`, etc.
848
+ - Strips all `on*` event handlers
849
+ - Neutralizes `javascript:` and `vbscript:` URLs
850
+ - Removes `data:` URLs from `src` attributes
851
+
852
+ For user-generated HTML, we recommend using DOMPurify on the client side for maximum security.
853
+
854
+ ### Fragment Refresh Security
855
+
856
+ Fragment IDs used for `data-poll` are automatically signed to prevent attackers from probing for data:
857
+
858
+ - Fragment IDs are signed at render time with the session's CSRF token
859
+ - Server validates signatures before returning fragment content
860
+ - Invalid or unsigned fragments return 403 when CSRF is enabled
861
+
862
+ This is automatic — no configuration required.
863
+
864
+ ### Query Override Protection
865
+
866
+ Query function calls via `x-kuratchi-query-fn` headers are validated against a whitelist:
867
+
868
+ - Only query functions registered for the current route can be called
869
+ - Prevents attackers from invoking arbitrary RPC functions
870
+ - Returns 403 for unauthorized query function calls
871
+
872
+ This is automatic — no configuration required.
873
+
874
+ ### Client Bridge Security
875
+
876
+ Client-side handler invocation is protected against injection attacks:
877
+
878
+ - Route and handler IDs are validated against safe patterns
879
+ - Prototype pollution attempts are blocked (`__proto__`, `constructor`, `prototype`)
880
+ - Uses `hasOwnProperty` checks to prevent prototype chain traversal
881
+
882
+ This is automatic — no configuration required.
883
+
884
+ ### Error Information Protection
885
+
886
+ Error messages are sanitized to prevent information leakage in production:
887
+
888
+ - Generic errors show full details in dev mode only
889
+ - Production uses safe fallback messages ("Internal Server Error", "Action failed")
890
+ - `ActionError` and `PageError` messages are always shown (developer-controlled)
891
+
892
+ ```ts
893
+ // Safe to show - developer-controlled message
894
+ throw new ActionError('Invalid email format');
895
+
896
+ // In production: "Internal Server Error" (details hidden)
897
+ // In dev mode: Full error message for debugging
898
+ throw new Error('Database connection failed at line 42');
899
+ ```
900
+
901
+ ### Full Security Configuration
902
+
903
+ ```ts
904
+ // kuratchi.config.ts
905
+ export default defineConfig({
906
+ security: {
907
+ // CSRF Protection (enabled by default)
908
+ csrfEnabled: true,
909
+ csrfCookieName: '__kuratchi_csrf',
910
+ csrfHeaderName: 'x-kuratchi-csrf',
911
+
912
+ // Authentication Enforcement
913
+ rpcRequireAuth: false, // Require auth for RPC calls
914
+ actionRequireAuth: false, // Require auth for form actions
915
+
916
+ // Security Headers
917
+ contentSecurityPolicy: "default-src 'self'",
918
+ strictTransportSecurity: "max-age=31536000; includeSubDomains",
919
+ permissionsPolicy: "camera=(), microphone=()",
920
+ },
921
+ });
922
+ ```
923
+
924
+ For a comprehensive security analysis and roadmap, see [SECURITY.md](./SECURITY.md).
925
+
767
926
  ## `kuratchi.config.ts`
768
927
 
769
- Optional. Required only when using framework integrations (ORM, auth, UI).
928
+ Optional. Required only when using framework integrations (ORM, auth, UI, security).
770
929
 
771
930
  **Durable Objects are auto-discovered** — no config needed unless you need `stubId` for auth integration.
772
931
 
package/dist/cli.js CHANGED
@@ -6,25 +6,31 @@ import { compile } from './compiler/index.js';
6
6
  import * as path from 'node:path';
7
7
  import * as fs from 'node:fs';
8
8
  import * as net from 'node:net';
9
+ import { createRequire } from 'node:module';
9
10
  import { spawn } from 'node:child_process';
10
11
  const args = process.argv.slice(2);
11
12
  const command = args[0];
12
13
  const projectDir = process.cwd();
13
- switch (command) {
14
- case 'build':
15
- runBuild();
16
- break;
17
- case 'watch':
18
- runWatch(false);
19
- break;
20
- case 'dev':
21
- runWatch(true);
22
- break;
23
- case 'create':
24
- runCreate();
25
- break;
26
- default:
27
- console.log(`
14
+ void main().catch((err) => {
15
+ console.error(`[kuratchi] ${err?.message ?? err}`);
16
+ process.exit(1);
17
+ });
18
+ async function main() {
19
+ switch (command) {
20
+ case 'build':
21
+ runBuild();
22
+ return;
23
+ case 'watch':
24
+ await runWatch(false);
25
+ return;
26
+ case 'dev':
27
+ await runWatch(true);
28
+ return;
29
+ case 'create':
30
+ await runCreate();
31
+ return;
32
+ default:
33
+ console.log(`
28
34
  KuratchiJS CLI
29
35
 
30
36
  Usage:
@@ -33,7 +39,8 @@ Usage:
33
39
  kuratchi dev Compile, watch for changes, and start wrangler dev server
34
40
  kuratchi watch Compile + watch only (no wrangler — for custom setups)
35
41
  `);
36
- process.exit(1);
42
+ process.exit(1);
43
+ }
37
44
  }
38
45
  async function runCreate() {
39
46
  const { create } = await import('./create.js');
@@ -42,10 +49,10 @@ async function runCreate() {
42
49
  const positional = remaining.filter(a => !a.startsWith('-'));
43
50
  await create(positional[0], flags);
44
51
  }
45
- function runBuild(isDev = false) {
52
+ async function runBuild(isDev = false) {
46
53
  console.log('[kuratchi] Compiling...');
47
54
  try {
48
- const outFile = compile({ projectDir, isDev });
55
+ const outFile = await compile({ projectDir, isDev });
49
56
  console.log(`[kuratchi] Built → ${path.relative(projectDir, outFile)}`);
50
57
  }
51
58
  catch (err) {
@@ -53,8 +60,8 @@ function runBuild(isDev = false) {
53
60
  process.exit(1);
54
61
  }
55
62
  }
56
- function runWatch(withWrangler = false) {
57
- runBuild(true);
63
+ async function runWatch(withWrangler = false) {
64
+ await runBuild(true);
58
65
  const routesDir = path.join(projectDir, 'src', 'routes');
59
66
  const serverDir = path.join(projectDir, 'src', 'server');
60
67
  const watchDirs = [routesDir, serverDir].filter(d => fs.existsSync(d));
@@ -62,10 +69,10 @@ function runWatch(withWrangler = false) {
62
69
  const triggerRebuild = () => {
63
70
  if (rebuildTimeout)
64
71
  clearTimeout(rebuildTimeout);
65
- rebuildTimeout = setTimeout(() => {
72
+ rebuildTimeout = setTimeout(async () => {
66
73
  console.log('[kuratchi] File changed, rebuilding...');
67
74
  try {
68
- compile({ projectDir, isDev: true });
75
+ await compile({ projectDir, isDev: true });
69
76
  console.log('[kuratchi] Rebuilt.');
70
77
  }
71
78
  catch (err) {
@@ -80,11 +87,10 @@ function runWatch(withWrangler = false) {
80
87
  // `kuratchi dev` also starts the wrangler dev server.
81
88
  // `kuratchi watch` is the compiler-only mode for custom setups.
82
89
  if (withWrangler) {
83
- startWranglerDev().catch((err) => {
84
- console.error(`[kuratchi] Failed to start wrangler dev: ${err?.message ?? err}`);
85
- process.exit(1);
86
- });
90
+ await startWranglerDev();
91
+ return;
87
92
  }
93
+ await new Promise(() => { });
88
94
  }
89
95
  function hasPortFlag(inputArgs) {
90
96
  for (let i = 0; i < inputArgs.length; i++) {
@@ -117,25 +123,13 @@ async function findOpenPort(start = 8787, end = 8899) {
117
123
  }
118
124
  async function startWranglerDev() {
119
125
  const passthroughArgs = args.slice(1);
120
- const wranglerArgs = ['wrangler', 'dev', ...passthroughArgs];
126
+ const wranglerArgs = ['dev', ...passthroughArgs];
121
127
  if (!hasPortFlag(passthroughArgs)) {
122
128
  const port = await findOpenPort();
123
129
  wranglerArgs.push('--port', String(port));
124
130
  console.log(`[kuratchi] Starting wrangler dev on port ${port}`);
125
131
  }
126
- // Use 'pipe' for stdin so wrangler doesn't detect stdin EOF and exit
127
- // prematurely when launched via a script runner (e.g. `bun run dev`).
128
- const isWin = process.platform === 'win32';
129
- const wrangler = isWin
130
- ? spawn('npx ' + wranglerArgs.join(' '), {
131
- cwd: projectDir,
132
- stdio: ['pipe', 'inherit', 'inherit'],
133
- shell: true,
134
- })
135
- : spawn('npx', wranglerArgs, {
136
- cwd: projectDir,
137
- stdio: ['pipe', 'inherit', 'inherit'],
138
- });
132
+ const wrangler = spawnWranglerProcess(wranglerArgs);
139
133
  const cleanup = () => {
140
134
  if (!wrangler.killed)
141
135
  wrangler.kill();
@@ -143,10 +137,49 @@ async function startWranglerDev() {
143
137
  process.on('exit', cleanup);
144
138
  process.on('SIGINT', () => { cleanup(); process.exit(0); });
145
139
  process.on('SIGTERM', () => { cleanup(); process.exit(0); });
146
- wrangler.on('exit', (code) => {
147
- if (code !== 0 && code !== null) {
148
- console.error(`[kuratchi] wrangler exited with code ${code}`);
149
- }
150
- process.exit(code ?? 0);
140
+ await new Promise((resolve, reject) => {
141
+ wrangler.on('exit', (code) => {
142
+ if (code !== 0 && code !== null) {
143
+ reject(new Error(`wrangler exited with code ${code}`));
144
+ return;
145
+ }
146
+ resolve();
147
+ });
148
+ wrangler.on('error', (err) => {
149
+ reject(err);
150
+ });
151
+ }).catch((err) => {
152
+ console.error(`[kuratchi] Failed to start wrangler dev: ${err?.message ?? err}`);
153
+ process.exit(1);
154
+ });
155
+ }
156
+ function resolveWranglerBin() {
157
+ try {
158
+ const projectPackageJson = path.join(projectDir, 'package.json');
159
+ const projectRequire = createRequire(projectPackageJson);
160
+ return projectRequire.resolve('wrangler/bin/wrangler.js');
161
+ }
162
+ catch {
163
+ return null;
164
+ }
165
+ }
166
+ function getNodeExecutable() {
167
+ if (!process.versions.bun)
168
+ return process.execPath;
169
+ return 'node';
170
+ }
171
+ function spawnWranglerProcess(wranglerArgs) {
172
+ const localWranglerBin = resolveWranglerBin();
173
+ const stdio = ['pipe', 'inherit', 'inherit'];
174
+ if (localWranglerBin) {
175
+ return spawn(getNodeExecutable(), [localWranglerBin, ...wranglerArgs], {
176
+ cwd: projectDir,
177
+ stdio,
178
+ });
179
+ }
180
+ const fallbackCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
181
+ return spawn(fallbackCommand, ['wrangler', ...wranglerArgs], {
182
+ cwd: projectDir,
183
+ stdio,
151
184
  });
152
185
  }
@@ -0,0 +1,8 @@
1
+ export declare function compileApiRoute(opts: {
2
+ pattern: string;
3
+ fullPath: string;
4
+ projectDir: string;
5
+ transformModule: (entryAbsPath: string) => string;
6
+ allocateModuleId: () => string;
7
+ pushImport: (statement: string) => void;
8
+ }): string;
@@ -0,0 +1,23 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ const ALL_API_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
4
+ export function compileApiRoute(opts) {
5
+ const outFileDir = path.join(opts.projectDir, '.kuratchi');
6
+ const absRoutePath = opts.transformModule(opts.fullPath);
7
+ let importPath = path.relative(outFileDir, absRoutePath).replace(/\\/g, '/');
8
+ if (!importPath.startsWith('.'))
9
+ importPath = './' + importPath;
10
+ const moduleId = opts.allocateModuleId();
11
+ opts.pushImport(`import * as ${moduleId} from '${importPath}';`);
12
+ const apiSource = fs.readFileSync(opts.fullPath, 'utf-8');
13
+ const exportedMethods = ALL_API_METHODS.filter((method) => {
14
+ const fnPattern = new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\b`);
15
+ const reExportPattern = new RegExp(`export\\s*\\{[^}]*\\b\\w+\\s+as\\s+${method}\\b`);
16
+ const namedExportPattern = new RegExp(`export\\s*\\{[^}]*\\b${method}\\b`);
17
+ return fnPattern.test(apiSource) || reExportPattern.test(apiSource) || namedExportPattern.test(apiSource);
18
+ });
19
+ const methodEntries = exportedMethods
20
+ .map((method) => `${method}: ${moduleId}.${method}`)
21
+ .join(', ');
22
+ return `{ pattern: '${opts.pattern}', __api: true, ${methodEntries} }`;
23
+ }
@@ -0,0 +1,7 @@
1
+ export interface CompiledAsset {
2
+ name: string;
3
+ content: string;
4
+ mime: string;
5
+ etag: string;
6
+ }
7
+ export declare function compileAssets(assetsDir: string): CompiledAsset[];
@@ -0,0 +1,33 @@
1
+ import * as crypto from 'node:crypto';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ const MIME_TYPES = {
5
+ '.css': 'text/css; charset=utf-8',
6
+ '.js': 'text/javascript; charset=utf-8',
7
+ '.json': 'application/json; charset=utf-8',
8
+ '.svg': 'image/svg+xml',
9
+ '.txt': 'text/plain; charset=utf-8',
10
+ };
11
+ export function compileAssets(assetsDir) {
12
+ const compiledAssets = [];
13
+ if (!fs.existsSync(assetsDir))
14
+ return compiledAssets;
15
+ const scanAssets = (dir, prefix) => {
16
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
17
+ if (entry.isDirectory()) {
18
+ scanAssets(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
19
+ continue;
20
+ }
21
+ const ext = path.extname(entry.name).toLowerCase();
22
+ const mime = MIME_TYPES[ext];
23
+ if (!mime)
24
+ continue;
25
+ const content = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
26
+ const etag = '"' + crypto.createHash('md5').update(content).digest('hex').slice(0, 12) + '"';
27
+ const name = prefix ? `${prefix}/${entry.name}` : entry.name;
28
+ compiledAssets.push({ name, content, mime, etag });
29
+ }
30
+ };
31
+ scanAssets(assetsDir, '');
32
+ return compiledAssets;
33
+ }
@@ -0,0 +1,25 @@
1
+ import type { CompiledAsset } from './asset-pipeline.js';
2
+ import { type RouteImportEntry } from './import-linking.js';
3
+ export interface ClientEventRegistration {
4
+ routeId: string;
5
+ handlerId: string;
6
+ argsExpr: string | null;
7
+ }
8
+ export interface ClientModuleCompiler {
9
+ createRegistry(scopeId: string, importEntries: RouteImportEntry[]): ClientRouteRegistry;
10
+ createRouteRegistry(routeIndex: number, importEntries: RouteImportEntry[]): ClientRouteRegistry;
11
+ getCompiledAssets(): CompiledAsset[];
12
+ }
13
+ export interface ClientRouteRegistry {
14
+ hasBindings(): boolean;
15
+ hasBindingReference(expression: string): boolean;
16
+ registerEventHandler(eventName: string, expression: string): ClientEventRegistration | null;
17
+ buildEntryAsset(): {
18
+ assetName: string;
19
+ asset: CompiledAsset;
20
+ } | null;
21
+ }
22
+ export declare function createClientModuleCompiler(opts: {
23
+ projectDir: string;
24
+ srcDir: string;
25
+ }): ClientModuleCompiler;