tjs-lang 0.7.8 → 0.8.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/CLAUDE.md +9 -0
- package/demo/docs.json +64 -16
- package/demo/src/ts-examples.ts +8 -8
- package/package.json +7 -3
- package/src/lang/docs.test.ts +148 -0
- package/src/lang/docs.ts +49 -15
- package/src/lang/emitters/js-wasm.ts +57 -65
- package/src/lang/emitters/js.ts +16 -1
- package/src/lang/features.test.ts +4 -3
- package/src/lang/index.ts +9 -0
- package/src/lang/module-loader.test.ts +318 -0
- package/src/lang/module-loader.ts +419 -0
- package/src/lang/parser-transforms.ts +336 -0
- package/src/lang/parser-types.ts +33 -0
- package/src/lang/parser.ts +43 -2
- package/src/lang/wasm.test.ts +1293 -2
- package/src/lang/wasm.ts +470 -87
- package/src/linalg/index.tjs +119 -0
- package/src/linalg/linalg.test.ts +294 -0
- package/src/linalg/vector-search.bench.test.ts +395 -0
package/demo/docs.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "example",
|
|
44
44
|
"group": "advanced",
|
|
45
45
|
"order": 15,
|
|
46
|
-
"code": "
|
|
46
|
+
"code": "/*#\n## The Universal Endpoint\n\nThis is the entire backend industry in 50 lines.\n\nWhat this replaces:\n- GraphQL servers\n- REST API forests\n- Firebase/Lambda/Vercel Functions\n- Kubernetes deployments\n- The backend priesthood\n\nHow it works:\n1. Client sends logic (not just data)\n2. Server executes it with bounded resources\n3. That's it. That's the whole thing.\n*/\n\nimport { AgentVM, ajs, coreAtoms } from 'tjs-lang'\n\n// ============================================================\n// THE UNIVERSAL ENDPOINT (This is the entire backend)\n// ============================================================\n\nasync function post(req: {\n body: {\n agent: '', // The logic to execute (AJS source)\n args: {}, // Input data\n fuel: 1000 // Max compute units (like gas)\n },\n headers: { authorization: '' }\n}): { result: {}, fuelUsed: 0, status: '' } | { error: '', fuelUsed: 0 } {\n\n // 1. Parse the agent (it's just code as data)\n let ast\n try {\n ast = ajs(req.body.agent)\n } catch (e) {\n return { error: `Parse error: \\${e.message}`, fuelUsed: 0 }\n }\n\n // 2. Create VM with capabilities (this is what you monetize)\n const vm = new AgentVM({\n // Your database, your auth, your AI - exposed as capabilities\n // Agents can only do what you allow\n })\n\n // 3. Execute with bounded resources\n const result = await vm.run(ast, req.body.args, {\n fuel: Math.min(req.body.fuel, 10000), // Cap fuel\n timeoutMs: 5000 // Cap time\n })\n\n // 4. Return result (or error - but we didn't crash)\n if (result.error) {\n return {\n error: result.error.message,\n fuelUsed: result.fuelUsed,\n }\n }\n\n return {\n result: result.result,\n fuelUsed: result.fuelUsed,\n status: 'success'\n }\n}\n\n// ============================================================\n// DEMO: Let's use it\n// ============================================================\n\n// Simulate the endpoint\nconst endpoint = post\n\n// --- TEST 1: Simple computation (Success) ---\nconsole.log('═══════════════════════════════════════════')\nconsole.log('TEST 1: Simple Agent')\nconsole.log('═══════════════════════════════════════════')\n\nconst simpleAgent = `\n function compute({ x, y }) {\n let sum = x + y\n let product = x * y\n return { sum, product, message: 'Math is easy' }\n }\n`\n\nconst result1 = await endpoint({\n body: { agent: simpleAgent, args: { x: 7, y: 6 }, fuel: 100 },\n headers: { authorization: 'token_123' }\n})\n\nconsole.log('Agent: compute({ x: 7, y: 6 })')\nconsole.log('Result:', result1.result)\nconsole.log('Fuel used:', result1.fuelUsed)\nconsole.log('Status: ✓ Success')\n\n\n// --- TEST 2: Infinite loop (Fuel Exhausted) ---\nconsole.log('\\\\n═══════════════════════════════════════════')\nconsole.log('TEST 2: Malicious Agent (Infinite Loop)')\nconsole.log('═══════════════════════════════════════════')\n\nconst maliciousAgent = `\n function attack({ }) {\n let i = 0\n while (true) {\n i = i + 1\n // This would hang your Express server forever\n // This would cost you $10,000 on Lambda\n // This would crash your Kubernetes pod\n }\n return { i }\n }\n`\n\nconst result2 = await endpoint({\n body: { agent: maliciousAgent, args: {}, fuel: 50 },\n headers: { authorization: 'token_123' }\n})\n\nconsole.log('Agent: while (true) { ... }')\nconsole.log('Error:', result2.error)\nconsole.log('Fuel used:', result2.fuelUsed, '(exhausted at limit)')\nconsole.log('Status: ✗ Safely terminated')\n\n\n// --- TEST 3: Complex computation (Metered) ---\nconsole.log('\\\\n═══════════════════════════════════════════')\nconsole.log('TEST 3: Complex Agent (Metered)')\nconsole.log('═══════════════════════════════════════════')\n\nconst complexAgent = `\n function fibonacci({ n }) {\n if (n <= 1) return { result: n }\n\n let a = 0\n let b = 1\n let i = 2\n while (i <= n) {\n let temp = a + b\n a = b\n b = temp\n i = i + 1\n }\n return { result: b, iterations: n }\n }\n`\n\nconst result3 = await endpoint({\n body: { agent: complexAgent, args: { n: 20 }, fuel: 500 },\n headers: { authorization: 'token_123' }\n})\n\nconsole.log('Agent: fibonacci({ n: 20 })')\nconsole.log('Result:', result3.result)\nconsole.log('Fuel used:', result3.fuelUsed)\nconsole.log('Status: ✓ Success (metered)')\n\n\n// --- THE PUNCHLINE ---\nconsole.log('\\\\n═══════════════════════════════════════════')\nconsole.log('THE PUNCHLINE')\nconsole.log('═══════════════════════════════════════════')\nconsole.log(`\nYour current backend?\n - The infinite loop would have HUNG your server\n - Or cost you THOUSANDS on Lambda\n - Or crashed your Kubernetes pod\n - Or required a \"senior engineer\" to add timeout logic\n\nTosi?\n - Charged 50 fuel units\n - Returned an error\n - Kept running\n - Total code: 50 lines\n\nThis is the entire backend industry.\n\nOne endpoint.\nAny logic.\nZero deployment.\nEveryone is full stack now.\n`)",
|
|
47
47
|
"language": "tjs",
|
|
48
48
|
"description": "One endpoint. Any logic. Zero deployment. This is the whole thing."
|
|
49
49
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"type": "example",
|
|
56
56
|
"group": "advanced",
|
|
57
57
|
"order": 16,
|
|
58
|
-
"code": "
|
|
58
|
+
"code": "/*#\n## Testing Private Functions\n\nThis is the killer feature of inline tests:\nYou can test functions WITHOUT exporting them.\n\nTraditional testing requires you to either:\n- Export internal helpers (pollutes your API)\n- Test only through public interface (incomplete coverage)\n- Use hacks like rewire/proxyquire (brittle)\n\nTJS inline tests have full access to the module scope.\nTest everything. Export only what you need.\n*/\n\n// ============================================================\n// PRIVATE HELPERS (not exported, but fully testable!)\n// ============================================================\n\n// Private: Email validation regex\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n// Private: Validate email format\nfunction isValidEmail(email: ''): false {\n return EMAIL_REGEX.test(email)\n}\n\n// Private: Sanitize user input\nfunction sanitize(input: ''): '' {\n return input.trim().toLowerCase()\n}\n\n// Private: Generate a unique ID\nfunction generateId(prefix: 'user'):! '' {\n return prefix + '_' + Math.random().toString(36).slice(2, 10)\n}\n\n// Private: Hash password (simplified for demo)\nfunction hashPassword(password: ''): 'hashed_0' {\n let hash = 0\n for (let i = 0; i < password.length; i++) {\n hash = ((hash << 5) - hash) + password.charCodeAt(i)\n hash = hash & hash\n }\n return 'hashed_' + Math.abs(hash).toString(16)\n}\n\n// Private: Check password strength\nfunction isStrongPassword(password: ''):! { strong: true, issues: [''] } {\n const issues = []\n if (password.length < 8) issues.push('Must be at least 8 characters')\n if (!/[A-Z]/.test(password)) issues.push('Must contain uppercase letter')\n if (!/[a-z]/.test(password)) issues.push('Must contain lowercase letter')\n if (!/[0-9]/.test(password)) issues.push('Must contain a number')\n return { strong: issues.length === 0, issues }\n}\n\n// ============================================================\n// PUBLIC API (this is all that gets exported)\n// ============================================================\n\nexport function createUser(input: { email: '', password: '' }):! { id: '', email: '', passwordHash: '' } | { error: '', code: 0 } {\n\n // Validate email (using private helper)\n const cleanEmail = sanitize(input.email)\n if (!isValidEmail(cleanEmail)) {\n return { error: 'Invalid email format', code: 400 }\n }\n\n // Validate password (using private helper)\n const strength = isStrongPassword(input.password)\n if (!strength.strong) {\n return { error: strength.issues.join(', '), code: 400 }\n }\n\n // Create user (using private helpers)\n return {\n id: generateId('user'),\n email: cleanEmail,\n passwordHash: hashPassword(input.password)\n }\n}\n\n// ============================================================\n// TESTS - Full access to private functions!\n// ============================================================\n\n// --- Test private email validation ---\ntest 'isValidEmail accepts valid emails' {\n expect(isValidEmail('test@example.com')).toBe(true)\n expect(isValidEmail('user.name+tag@domain.co.uk')).toBe(true)\n}\n\ntest 'isValidEmail rejects invalid emails' {\n expect(isValidEmail('not-an-email')).toBe(false)\n expect(isValidEmail('@nodomain.com')).toBe(false)\n expect(isValidEmail('spaces in@email.com')).toBe(false)\n}\n\n// --- Test private sanitization ---\ntest 'sanitize trims and lowercases' {\n expect(sanitize(' HELLO ')).toBe('hello')\n expect(sanitize(' Test@Email.COM ')).toBe('test@email.com')\n}\n\n// --- Test private ID generation ---\ntest 'generateId creates prefixed unique IDs' {\n const id1 = generateId('user')\n const id2 = generateId('user')\n expect(id1.startsWith('user_')).toBe(true)\n expect(id1 !== id2).toBe(true) // unique each time\n}\n\ntest 'generateId respects prefix' {\n expect(generateId('post').startsWith('post_')).toBe(true)\n expect(generateId('comment').startsWith('comment_')).toBe(true)\n}\n\n// --- Test private password hashing ---\ntest 'hashPassword is deterministic' {\n const hash1 = hashPassword('secret123')\n const hash2 = hashPassword('secret123')\n expect(hash1).toBe(hash2)\n}\n\ntest 'hashPassword produces different hashes for different inputs' {\n const hash1 = hashPassword('password1')\n const hash2 = hashPassword('password2')\n expect(hash1 !== hash2).toBe(true)\n}\n\n// --- Test private password strength checker ---\ntest 'isStrongPassword rejects weak passwords' {\n const result = isStrongPassword('weak')\n expect(result.strong).toBe(false)\n expect(result.issues.length).toBeGreaterThan(0)\n}\n\ntest 'isStrongPassword accepts strong passwords' {\n const result = isStrongPassword('MyStr0ngP@ss!')\n expect(result.strong).toBe(true)\n expect(result.issues.length).toBe(0)\n}\n\ntest 'isStrongPassword lists specific issues' {\n const noUpper = isStrongPassword('lowercase123')\n expect(noUpper.issues).toContain('Must contain uppercase letter')\n\n const noLower = isStrongPassword('UPPERCASE123')\n expect(noLower.issues).toContain('Must contain lowercase letter')\n\n const noNumber = isStrongPassword('NoNumbers!')\n expect(noNumber.issues).toContain('Must contain a number')\n\n const tooShort = isStrongPassword('Ab1!')\n expect(tooShort.issues).toContain('Must be at least 8 characters')\n}\n\n// --- Test the public API (integration) ---\ntest 'createUser validates email' {\n const result = createUser({ email: 'invalid', password: 'StrongPass1!' })\n expect(result.error).toBe('Invalid email format')\n}\n\ntest 'createUser validates password strength' {\n const result = createUser({ email: 'test@test.com', password: 'weak' })\n expect(result.error).toBeTruthy()\n}\n\ntest 'createUser succeeds with valid input' {\n const result = createUser({\n email: ' Test@Example.COM ',\n password: 'MyStr0ngPass!'\n })\n expect(result.id).toBeTruthy()\n expect(result.email).toBe('test@example.com') // sanitized\n expect(result.passwordHash.startsWith('hashed_')).toBe(true)\n}\n\n// ============================================================\n// DEMO OUTPUT\n// ============================================================\n\nconsole.log('=== Testing Private Functions Demo ===\\n')\nconsole.log('The functions isValidEmail, sanitize, generateId,')\nconsole.log('hashPassword, and isStrongPassword are all PRIVATE.')\nconsole.log('They are NOT exported. But we tested them all!\\n')\n\nconsole.log(\"Try this in Jest/Vitest without exporting them. You can't.\")\nconsole.log(\"You'd have to either pollute your API or leave them untested.\\n\")\n\nconsole.log('TJS inline tests: Full coverage. Clean exports.\\n')\n\n// Show the public API working\nconst user = createUser({ email: 'demo@example.com', password: 'SecurePass123!' })\nconsole.log('Created user:', user)",
|
|
59
59
|
"language": "tjs",
|
|
60
60
|
"description": "Test internals without exporting them - the killer feature"
|
|
61
61
|
},
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"type": "example",
|
|
68
68
|
"group": "basics",
|
|
69
69
|
"order": 1,
|
|
70
|
-
"code": "/*#\n## Types by Example\n\nIn TJS, the example value after `:` IS the type:\n\n| Syntax | Type |\n|--------|------|\n| `name: 'Alice'` | string (required) |\n| `count: 42` | integer |\n| `rate: 3.14` | number (float) |\n| `index: +0` | non-negative integer |\n| `name = 'default'` | string (optional, defaults to 'default') |\n| `data: { x: 0, y: 0 }` | object with shape |\n| `...nums: [0]` | rest param, array of integers |\n*/\n\nfunction greet(name: 'World'): 'Hello, World!' {\n return `Hello, ${name}!`\n}\n\n// Numeric type narrowing — all valid JS syntax\nfunction clampIndex(index: +0, max: +0): +0 {\n return Math.min(index, max)\n}\n\nfunction mix(a: 0.0, b: 0.0, t: 0.0): 0.0 {\n return a + (b - a) * t\n}\n\ntest 'greet says hello' {\n expect(greet('TJS')).toBe('Hello, TJS!')\n}\n\ntest 'type errors are values, not exceptions' {\n const err = greet(42)\n expect(err instanceof Error).toBe(true)\n}\n\ntest 'numeric types are precise' {\n expect(clampIndex(5, 10)).toBe(5)\n // negative fails non-negative integer check\n expect(clampIndex(-1, 10) instanceof Error).toBe(true)\n // float fails integer check\n expect(clampIndex(3.5, 10) instanceof Error).toBe(true)\n}\n\ntest 'floats accept any number' {\n expect(mix(0, 100, 0.5)).toBe(50)\n}\n\n// Rest params — the array example IS the type\nfunction sum(...nums: [1, 2, 3]): 6 {\n return nums.reduce((a = 0, b: 0) => a + b, 0)\n}\n\nfunction mean(...values: [1.0, 2.0, 3.0, 2.0]): 2.0 {\n return values.length\n ? values.reduce((sum = 0.0, x: 1.0) => sum + x) / values.length\n : 0.0\n}\n\ntest 'sum integers' {\n expect(sum(1, 2, 3)).toBe(6)\n}\n\ntest 'mean of floats' {\n expect(mean(10, 20, 30)).toBe(20)\n}\n\ntest 'mean of empty' {\n expect(mean()).toBe(0)\n}",
|
|
70
|
+
"code": "/*#\n## Types by Example\n\nIn TJS, the example value after `:` IS the type:\n\n| Syntax | Type |\n|--------|------|\n| `name: 'Alice'` | string (required) |\n| `count: 42` | integer |\n| `rate: 3.14` | number (float) |\n| `index: +0` | non-negative integer |\n| `name = 'default'` | string (optional, defaults to 'default') |\n| `data: { x: 0, y: 0 }` | object with shape |\n| `...nums: [0]` | rest param, array of integers |\n\nIncidentally, you're looking at inline markdown docs...\n*/\n\n/**\n * But **jsDoc** is also supported.\n */\n\nfunction greet(name: 'World'): 'Hello, World!' {\n return `Hello, ${name}!`\n}\n\n// Numeric type narrowing — all valid JS syntax\nfunction clampIndex(index: +0, max: +0): +0 {\n return Math.min(index, max)\n}\n\nfunction mix(a: 0.0, b: 0.0, t: 0.0): 0.0 {\n return a + (b - a) * t\n}\n\ntest 'greet says hello' {\n expect(greet('TJS')).toBe('Hello, TJS!')\n}\n\ntest 'type errors are values, not exceptions' {\n const err = greet(42)\n expect(err instanceof Error).toBe(true)\n}\n\ntest 'numeric types are precise' {\n expect(clampIndex(5, 10)).toBe(5)\n // negative fails non-negative integer check\n expect(clampIndex(-1, 10) instanceof Error).toBe(true)\n // float fails integer check\n expect(clampIndex(3.5, 10) instanceof Error).toBe(true)\n}\n\ntest 'floats accept any number' {\n expect(mix(0, 100, 0.5)).toBe(50)\n}\n\n// Rest params — the array example IS the type\nfunction sum(...nums: [1, 2, 3]): 6 {\n return nums.reduce((a = 0, b: 0) => a + b, 0)\n}\n\nfunction mean(...values: [1.0, 2.0, 3.0, 2.0]): 2.0 {\n return values.length\n ? values.reduce((sum = 0.0, x: 1.0) => sum + x) / values.length\n : 0.0\n}\n\ntest 'sum integers' {\n expect(sum(1, 2, 3)).toBe(6)\n}\n\ntest 'mean of floats' {\n expect(mean(10, 20, 30)).toBe(20)\n}\n\ntest 'mean of empty' {\n expect(mean()).toBe(0)\n}",
|
|
71
71
|
"language": "tjs",
|
|
72
72
|
"description": "Types-by-example: the value IS the type annotation"
|
|
73
73
|
},
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
"type": "example",
|
|
256
256
|
"group": "featured",
|
|
257
257
|
"order": 1,
|
|
258
|
-
"code": "/*#\n# WASM SIMD Vector Search Benchmark\n\nCompares **WASM SIMD** (f32x4) cosine similarity against **scalar JavaScript**\nfor vector search workloads. This is the core operation behind semantic search,\nRAG pipelines, and embedding-based retrieval.\n\nThe corpus is packed into a single `wasmBuffer` — zero-copy shared memory\nbetween JS and WASM. The SIMD search kernel processes 4 vector dimensions\nper instruction and loops over the entire corpus in one WASM call.\n\nClick **Run Benchmark** to generate random vectors and measure throughput.\n*/\n\n// SIMD corpus search — single WASM call over entire corpus\n// corpus is a flat Float32Array of count*dim elements\n// Returns the index of the most similar vector\nfunction simdSearch(corpus: Float32Array, query: Float32Array, count: 0, dim: 0) {\n return wasm {\n let bestIdx = 0\n let bestScore = -1.0\n\n for (let v = 0; v < count; v++) {\n let dotAcc = f32x4_splat(0.0)\n let magAAcc = f32x4_splat(0.0)\n let magBAcc = f32x4_splat(0.0)\n\n for (let j = 0; j < dim; j += 4) {\n let qOff = j * 4\n let cOff = (v * dim + j) * 4\n let a = f32x4_load(query, qOff)\n let b = f32x4_load(corpus, cOff)\n dotAcc = f32x4_add(dotAcc, f32x4_mul(a, b))\n magAAcc = f32x4_add(magAAcc, f32x4_mul(a, a))\n magBAcc = f32x4_add(magBAcc, f32x4_mul(b, b))\n }\n\n let dot = f32x4_extract_lane(dotAcc, 0) + f32x4_extract_lane(dotAcc, 1)\n + f32x4_extract_lane(dotAcc, 2) + f32x4_extract_lane(dotAcc, 3)\n let magA = f32x4_extract_lane(magAAcc, 0) + f32x4_extract_lane(magAAcc, 1)\n + f32x4_extract_lane(magAAcc, 2) + f32x4_extract_lane(magAAcc, 3)\n let magB = f32x4_extract_lane(magBAcc, 0) + f32x4_extract_lane(magBAcc, 1)\n + f32x4_extract_lane(magBAcc, 2) + f32x4_extract_lane(magBAcc, 3)\n\n let mA = Math.sqrt(magA)\n let mB = Math.sqrt(magB)\n if (mA > 0.000001) {\n if (mB > 0.000001) {\n let score = dot / (mA * mB)\n if (score > bestScore) {\n bestScore = score\n bestIdx = v\n }\n }\n }\n }\n return bestIdx\n } fallback {\n let bestIdx = 0\n let bestScore = -1\n for (let v = 0; v < count; v++) {\n let dot = 0, magA = 0, magB = 0\n for (let j = 0; j < dim; j++) {\n let ci = v * dim + j\n dot += query[j] * corpus[ci]\n magA += query[j] * query[j]\n magB += corpus[ci] * corpus[ci]\n }\n magA = Math.sqrt(magA)\n magB = Math.sqrt(magB)\n if (magA > 0 && magB > 0) {\n let score = dot / (magA * magB)\n if (score > bestScore) { bestScore = score; bestIdx = v }\n }\n }\n return bestIdx\n }\n}\n\n// Scalar JS search (baseline)\nfunction jsSearch(corpus, query, count, dim) {\n let bestIdx = 0, bestScore = -1\n for (let v = 0; v < count; v++) {\n let dot = 0, magA = 0, magB = 0\n for (let j = 0; j < dim; j++) {\n const ci = v * dim + j\n dot += query[j] * corpus[ci]\n magA += query[j] * query[j]\n magB += corpus[ci] * corpus[ci]\n }\n magA = Math.sqrt(magA)\n magB = Math.sqrt(magB)\n if (magA > 0 && magB > 0) {\n const score = dot / (magA * magB)\n if (score > bestScore) { bestScore = score; bestIdx = v }\n }\n }\n return bestIdx\n}\n\n// UI setup\nconst container = document.createElement('div')\ncontainer.style.cssText = 'font-family:monospace;color:#c0d8ff;background:#0a0a14;padding:24px;position:absolute;inset:0;overflow:auto'\ndocument.body.appendChild(container)\n\nconst title = document.createElement('h2')\ntitle.textContent = 'Vector Search: WASM SIMD vs JS Scalar'\ntitle.style.cssText = 'margin:0 0 4px 0;font-size:18px'\ncontainer.appendChild(title)\n\nconst subtitle = document.createElement('div')\nsubtitle.textContent = 'Cosine similarity over Float32Array embeddings (wasmBuffer = zero-copy)'\nsubtitle.style.cssText = 'color:#6090c0;font-size:12px;margin-bottom:16px'\ncontainer.appendChild(subtitle)\n\nconst btn = document.createElement('button')\nbtn.textContent = 'Run Benchmark'\nbtn.style.cssText = 'background:#2060a0;color:#fff;border:none;padding:8px 20px;font:bold 14px monospace;cursor:pointer;margin-bottom:16px'\ncontainer.appendChild(btn)\n\nconst status = document.createElement('div')\nstatus.style.cssText = 'color:#6090c0;margin-bottom:12px;font-size:13px'\ncontainer.appendChild(status)\n\nconst table = document.createElement('table')\ntable.style.cssText = 'border-collapse:collapse;width:100%;max-width:700px'\ncontainer.appendChild(table)\n\nconst configs = [\n { dim: 128, count: 10000, label: '10K x 128d' },\n { dim: 256, count: 10000, label: '10K x 256d' },\n { dim: 512, count: 10000, label: '10K x 512d' },\n { dim: 128, count: 50000, label: '50K x 128d' },\n]\n\n// Pre-allocate max-sized buffers once (bump allocator never frees)\n// Largest config: 50K x 128d = 6.4M floats = 25.6MB\nconst MAX_CORPUS = 50000 * 128\nconst MAX_DIM = 512\nconst corpus = wasmBuffer(Float32Array, MAX_CORPUS)\nconst query = wasmBuffer(Float32Array, MAX_DIM)\n\nfunction renderTable(results) {\n const cellStyle = 'padding:8px 12px;border-bottom:1px solid #1a2a3a;text-align:right'\n const headerStyle = cellStyle + ';color:#6090c0;text-align:right;font-weight:bold'\n const labelStyle = cellStyle + ';text-align:left;color:#c0d8ff'\n\n let html = '<tr>'\n html += `<th style=\"${headerStyle};text-align:left\">Config</th>`\n html += `<th style=\"${headerStyle}\">SIMD (ms)</th>`\n html += `<th style=\"${headerStyle}\">JS (ms)</th>`\n html += `<th style=\"${headerStyle}\">Speedup</th>`\n html += `<th style=\"${headerStyle}\">Match</th>`\n html += '</tr>'\n\n for (const r of results) {\n const color = r.speedup >= 1 ? '#40d080' : '#d04040'\n html += '<tr>'\n html += `<td style=\"${labelStyle}\">${r.label}</td>`\n html += `<td style=\"${cellStyle};color:#80b0e0\">${r.simdTime.toFixed(1)}</td>`\n html += `<td style=\"${cellStyle};color:#e0a060\">${r.jsTime.toFixed(1)}</td>`\n html += `<td style=\"${cellStyle};color:${color};font-weight:bold\">${r.speedup.toFixed(2)}x</td>`\n html += `<td style=\"${cellStyle};color:${r.match ? '#40d080' : '#d04040'}\">${r.match ? 'yes' : 'no'}</td>`\n html += '</tr>'\n }\n\n if (results.length === configs.length) {\n const avg = results.reduce((s, r) => s + r.speedup, 0) / results.length\n const color = avg >= 1 ? '#40d080' : '#d04040'\n html += '<tr>'\n html += `<td style=\"${labelStyle};font-weight:bold\">Average</td>`\n html += `<td style=\"${cellStyle}\"></td><td style=\"${cellStyle}\"></td>`\n html += `<td style=\"${cellStyle};color:${color};font-weight:bold\">${avg.toFixed(2)}x</td>`\n html += `<td style=\"${cellStyle}\"></td>`\n html += '</tr>'\n }\n\n table.innerHTML = html\n}\n\nasync function runConfig(cfg) {\n const size = cfg.count * cfg.dim\n\n // Also create regular JS arrays for the scalar baseline\n const jsCorpus = new Float32Array(size)\n const jsQuery = new Float32Array(cfg.dim)\n\n // Fill pre-allocated wasmBuffer corpus + JS copy with same random data\n for (let i = 0; i < size; i++) {\n const v = Math.random() * 2 - 1\n corpus[i] = v\n jsCorpus[i] = v\n }\n for (let i = 0; i < cfg.dim; i++) {\n const v = Math.random() * 2 - 1\n query[i] = v\n jsQuery[i] = v\n }\n\n // Warm up (ensures WASM JIT is hot before timing)\n for (let w = 0; w < 3; w++) {\n simdSearch(corpus, query, Math.min(500, cfg.count), cfg.dim)\n jsSearch(jsCorpus, jsQuery, Math.min(500, cfg.count), cfg.dim)\n }\n\n // Time SIMD (uses pre-allocated wasmBuffer — zero copy)\n const simdStart = performance.now()\n const simdIdx = simdSearch(corpus, query, cfg.count, cfg.dim)\n const simdTime = performance.now() - simdStart\n\n // Time JS\n const jsStart = performance.now()\n const jsIdx = jsSearch(jsCorpus, jsQuery, cfg.count, cfg.dim)\n const jsTime = performance.now() - jsStart\n\n return {\n label: cfg.label,\n simdTime,\n jsTime,\n speedup: jsTime / simdTime,\n match: simdIdx === jsIdx,\n }\n}\n\nlet running = false\n\nbtn.addEventListener('click', async () => {\n if (running) return\n running = true\n btn.textContent = 'Running...'\n btn.style.background = '#334'\n const results = []\n\n for (let i = 0; i < configs.length; i++) {\n status.textContent = `Running ${configs[i].label}...`\n await new Promise(r => setTimeout(r, 50))\n results.push(await runConfig(configs[i]))\n renderTable(results)\n }\n\n status.textContent = 'Done.'\n btn.textContent = 'Run Benchmark'\n btn.style.background = '#2060a0'\n running = false\n})\n\nstatus.textContent = 'Click \"Run Benchmark\" to start.'",
|
|
258
|
+
"code": "/*#\n# WASM SIMD Vector Search Benchmark\n\nCompares **WASM SIMD** (f32x4) cosine similarity against **scalar JavaScript**\nfor vector search workloads. This is the core operation behind semantic search,\nRAG pipelines, and embedding-based retrieval.\n\nThe corpus is packed into a single `wasmBuffer` — zero-copy shared memory\nbetween JS and WASM. The SIMD search kernel processes 4 vector dimensions\nper instruction and loops over the entire corpus in one WASM call.\n\nClick **Run Benchmark** to generate random vectors and measure throughput.\n\n**Cross-file equivalent:** today this example computes `dot`, `magA`, and `magB`\ninline inside one `wasm {}` block. The same workload can also be expressed by\nimporting `tjs-lang/linalg` and using `dot` + `norm_sq` from a JS outer loop —\nproven correct in `src/linalg/vector-search.bench.test.ts` (same `bestIdx`\nacross configs, composed-not-imported module shape verified).\n\n**The honest perf tradeoff:** in the composed form the outer loop is JS, so\neach corpus row costs 2 JS↔wasm boundary crossings (`dot` + `norm_sq`). The\ninline form does the whole workload in one wasm call. So composed runs\nroughly 1.5–10× slower for fine-grained kernel usage, even though the wasm\nmodule shape is identical. For maximum throughput on hot inner loops, keep\nthis example's inline form; for modularity / reuse / correctness, the\nimported form works fine. A `cosine_search` kernel in `tjs-lang/linalg` would\ncombine both wins — that's the natural next step. See\n[`wasm-library-plan.md`](https://github.com/tonioloewald/tjs-lang/blob/main/wasm-library-plan.md)\n§ \"Canonical end-to-end demo\" for the full story.\n*/\n\n// SIMD corpus search — single WASM call over entire corpus\n// corpus is a flat Float32Array of count*dim elements\n// Returns the index of the most similar vector\nfunction simdSearch(corpus: Float32Array, query: Float32Array, count: 0, dim: 0) {\n return wasm {\n let bestIdx = 0\n let bestScore = -1.0\n\n for (let v = 0; v < count; v++) {\n let dotAcc = f32x4_splat(0.0)\n let magAAcc = f32x4_splat(0.0)\n let magBAcc = f32x4_splat(0.0)\n\n for (let j = 0; j < dim; j += 4) {\n let qOff = j * 4\n let cOff = (v * dim + j) * 4\n let a = f32x4_load(query, qOff)\n let b = f32x4_load(corpus, cOff)\n dotAcc = f32x4_add(dotAcc, f32x4_mul(a, b))\n magAAcc = f32x4_add(magAAcc, f32x4_mul(a, a))\n magBAcc = f32x4_add(magBAcc, f32x4_mul(b, b))\n }\n\n let dot = f32x4_extract_lane(dotAcc, 0) + f32x4_extract_lane(dotAcc, 1)\n + f32x4_extract_lane(dotAcc, 2) + f32x4_extract_lane(dotAcc, 3)\n let magA = f32x4_extract_lane(magAAcc, 0) + f32x4_extract_lane(magAAcc, 1)\n + f32x4_extract_lane(magAAcc, 2) + f32x4_extract_lane(magAAcc, 3)\n let magB = f32x4_extract_lane(magBAcc, 0) + f32x4_extract_lane(magBAcc, 1)\n + f32x4_extract_lane(magBAcc, 2) + f32x4_extract_lane(magBAcc, 3)\n\n let mA = Math.sqrt(magA)\n let mB = Math.sqrt(magB)\n if (mA > 0.000001) {\n if (mB > 0.000001) {\n let score = dot / (mA * mB)\n if (score > bestScore) {\n bestScore = score\n bestIdx = v\n }\n }\n }\n }\n return bestIdx\n } fallback {\n let bestIdx = 0\n let bestScore = -1\n for (let v = 0; v < count; v++) {\n let dot = 0, magA = 0, magB = 0\n for (let j = 0; j < dim; j++) {\n let ci = v * dim + j\n dot += query[j] * corpus[ci]\n magA += query[j] * query[j]\n magB += corpus[ci] * corpus[ci]\n }\n magA = Math.sqrt(magA)\n magB = Math.sqrt(magB)\n if (magA > 0 && magB > 0) {\n let score = dot / (magA * magB)\n if (score > bestScore) { bestScore = score; bestIdx = v }\n }\n }\n return bestIdx\n }\n}\n\n// Scalar JS search (baseline)\nfunction jsSearch(corpus, query, count, dim) {\n let bestIdx = 0, bestScore = -1\n for (let v = 0; v < count; v++) {\n let dot = 0, magA = 0, magB = 0\n for (let j = 0; j < dim; j++) {\n const ci = v * dim + j\n dot += query[j] * corpus[ci]\n magA += query[j] * query[j]\n magB += corpus[ci] * corpus[ci]\n }\n magA = Math.sqrt(magA)\n magB = Math.sqrt(magB)\n if (magA > 0 && magB > 0) {\n const score = dot / (magA * magB)\n if (score > bestScore) { bestScore = score; bestIdx = v }\n }\n }\n return bestIdx\n}\n\n// UI setup\nconst container = document.createElement('div')\ncontainer.style.cssText = 'font-family:monospace;color:#c0d8ff;background:#0a0a14;padding:24px;position:absolute;inset:0;overflow:auto'\ndocument.body.appendChild(container)\n\nconst title = document.createElement('h2')\ntitle.textContent = 'Vector Search: WASM SIMD vs JS Scalar'\ntitle.style.cssText = 'margin:0 0 4px 0;font-size:18px'\ncontainer.appendChild(title)\n\nconst subtitle = document.createElement('div')\nsubtitle.textContent = 'Cosine similarity over Float32Array embeddings (wasmBuffer = zero-copy)'\nsubtitle.style.cssText = 'color:#6090c0;font-size:12px;margin-bottom:16px'\ncontainer.appendChild(subtitle)\n\nconst btn = document.createElement('button')\nbtn.textContent = 'Run Benchmark'\nbtn.style.cssText = 'background:#2060a0;color:#fff;border:none;padding:8px 20px;font:bold 14px monospace;cursor:pointer;margin-bottom:16px'\ncontainer.appendChild(btn)\n\nconst status = document.createElement('div')\nstatus.style.cssText = 'color:#6090c0;margin-bottom:12px;font-size:13px'\ncontainer.appendChild(status)\n\nconst table = document.createElement('table')\ntable.style.cssText = 'border-collapse:collapse;width:100%;max-width:700px'\ncontainer.appendChild(table)\n\nconst configs = [\n { dim: 128, count: 10000, label: '10K x 128d' },\n { dim: 256, count: 10000, label: '10K x 256d' },\n { dim: 512, count: 10000, label: '10K x 512d' },\n { dim: 128, count: 50000, label: '50K x 128d' },\n]\n\n// Pre-allocate max-sized buffers once (bump allocator never frees)\n// Largest config: 50K x 128d = 6.4M floats = 25.6MB\nconst MAX_CORPUS = 50000 * 128\nconst MAX_DIM = 512\nconst corpus = wasmBuffer(Float32Array, MAX_CORPUS)\nconst query = wasmBuffer(Float32Array, MAX_DIM)\n\nfunction renderTable(results) {\n const cellStyle = 'padding:8px 12px;border-bottom:1px solid #1a2a3a;text-align:right'\n const headerStyle = cellStyle + ';color:#6090c0;text-align:right;font-weight:bold'\n const labelStyle = cellStyle + ';text-align:left;color:#c0d8ff'\n\n let html = '<tr>'\n html += `<th style=\"${headerStyle};text-align:left\">Config</th>`\n html += `<th style=\"${headerStyle}\">SIMD (ms)</th>`\n html += `<th style=\"${headerStyle}\">JS (ms)</th>`\n html += `<th style=\"${headerStyle}\">Speedup</th>`\n html += `<th style=\"${headerStyle}\">Match</th>`\n html += '</tr>'\n\n for (const r of results) {\n const color = r.speedup >= 1 ? '#40d080' : '#d04040'\n html += '<tr>'\n html += `<td style=\"${labelStyle}\">${r.label}</td>`\n html += `<td style=\"${cellStyle};color:#80b0e0\">${r.simdTime.toFixed(1)}</td>`\n html += `<td style=\"${cellStyle};color:#e0a060\">${r.jsTime.toFixed(1)}</td>`\n html += `<td style=\"${cellStyle};color:${color};font-weight:bold\">${r.speedup.toFixed(2)}x</td>`\n html += `<td style=\"${cellStyle};color:${r.match ? '#40d080' : '#d04040'}\">${r.match ? 'yes' : 'no'}</td>`\n html += '</tr>'\n }\n\n if (results.length === configs.length) {\n const avg = results.reduce((s, r) => s + r.speedup, 0) / results.length\n const color = avg >= 1 ? '#40d080' : '#d04040'\n html += '<tr>'\n html += `<td style=\"${labelStyle};font-weight:bold\">Average</td>`\n html += `<td style=\"${cellStyle}\"></td><td style=\"${cellStyle}\"></td>`\n html += `<td style=\"${cellStyle};color:${color};font-weight:bold\">${avg.toFixed(2)}x</td>`\n html += `<td style=\"${cellStyle}\"></td>`\n html += '</tr>'\n }\n\n table.innerHTML = html\n}\n\nasync function runConfig(cfg) {\n const size = cfg.count * cfg.dim\n\n // Also create regular JS arrays for the scalar baseline\n const jsCorpus = new Float32Array(size)\n const jsQuery = new Float32Array(cfg.dim)\n\n // Fill pre-allocated wasmBuffer corpus + JS copy with same random data\n for (let i = 0; i < size; i++) {\n const v = Math.random() * 2 - 1\n corpus[i] = v\n jsCorpus[i] = v\n }\n for (let i = 0; i < cfg.dim; i++) {\n const v = Math.random() * 2 - 1\n query[i] = v\n jsQuery[i] = v\n }\n\n // Warm up (ensures WASM JIT is hot before timing)\n for (let w = 0; w < 3; w++) {\n simdSearch(corpus, query, Math.min(500, cfg.count), cfg.dim)\n jsSearch(jsCorpus, jsQuery, Math.min(500, cfg.count), cfg.dim)\n }\n\n // Time SIMD (uses pre-allocated wasmBuffer — zero copy)\n const simdStart = performance.now()\n const simdIdx = simdSearch(corpus, query, cfg.count, cfg.dim)\n const simdTime = performance.now() - simdStart\n\n // Time JS\n const jsStart = performance.now()\n const jsIdx = jsSearch(jsCorpus, jsQuery, cfg.count, cfg.dim)\n const jsTime = performance.now() - jsStart\n\n return {\n label: cfg.label,\n simdTime,\n jsTime,\n speedup: jsTime / simdTime,\n match: simdIdx === jsIdx,\n }\n}\n\nlet running = false\n\nbtn.addEventListener('click', async () => {\n if (running) return\n running = true\n btn.textContent = 'Running...'\n btn.style.background = '#334'\n const results = []\n\n for (let i = 0; i < configs.length; i++) {\n status.textContent = `Running ${configs[i].label}...`\n await new Promise(r => setTimeout(r, 50))\n results.push(await runConfig(configs[i]))\n renderTable(results)\n }\n\n status.textContent = 'Done.'\n btn.textContent = 'Run Benchmark'\n btn.style.background = '#2060a0'\n running = false\n})\n\nstatus.textContent = 'Click \"Run Benchmark\" to start.'",
|
|
259
259
|
"language": "tjs",
|
|
260
260
|
"description": "> New to WASM in TJS? Start with the **[WASM Quick Start](https://github.com/tonioloewald/tjs-lang/blob/main/docs/WASM-QUICKSTART.md)**. WASM SIMD vs scalar JavaScript cosine similarity"
|
|
261
261
|
},
|
|
@@ -267,7 +267,7 @@
|
|
|
267
267
|
"type": "example",
|
|
268
268
|
"group": "fullstack",
|
|
269
269
|
"order": 12,
|
|
270
|
-
"code": "TjsCompat\n
|
|
270
|
+
"code": "TjsCompat\n/*#\n## User Service\n\nA complete backend service running in the browser.\nSave this module as \"user-service\", then run the client example.\n\nFeatures:\n- Type-safe endpoints with validation\n- In-memory data store\n- Full CRUD operations\n*/\n\n// In-memory store (would be a real DB in production)\nconst users = new Map()\nlet nextId = 1\n\n// Create a new user\nexport function createUser(input: {\n name: 'Alice',\n email: 'alice@example.com'\n}):! { id: 0, name: '', email: '', createdAt: '' } {\n const user = {\n id: nextId++,\n name: input.name,\n email: input.email,\n createdAt: new Date().toISOString()\n }\n users.set(user.id, user)\n return user\n}\n\n// Get user by ID (returns empty object if not found - union types not yet supported)\nexport function getUser(input: { id: 1 }):! { id: 0, name: '', email: '', createdAt: '' } {\n return users.get(input.id) || { id: 0, name: '', email: '', createdAt: '' }\n}\n\n// Update a user (returns empty object if not found - union types not yet supported)\nexport function updateUser(input: {\n id: 1,\n name: 'Alice',\n email: 'alice@example.com'\n}):! { id: 0, name: '', email: '', createdAt: '' } {\n const existing = users.get(input.id)\n if (!existing) return { id: 0, name: '', email: '', createdAt: '' }\n\n const updated = { ...existing, name: input.name, email: input.email }\n users.set(input.id, updated)\n return updated\n}\n\n// Delete a user\nexport function deleteUser(input: { id: 1 }):! { success: true, deleted: 0 } {\n const existed = users.has(input.id)\n users.delete(input.id)\n return { success: existed, deleted: existed ? input.id : 0 }\n}\n\n// List all users\nexport function listUsers(input: { limit: 10, offset: 0 }):! { users: [{ id: 0, name: '', email: '', createdAt: '' }], total: 0 } {\n const all = [...users.values()]\n const slice = all.slice(input.offset, input.offset + input.limit)\n return { users: slice, total: all.length }\n}\n\n// Search users by name\nexport function searchUsers(input: { query: '' }):! { users: [{ id: 0, name: '', email: '', createdAt: '' }] } {\n const query = input.query.toLowerCase()\n const matches = [...users.values()].filter(u =>\n u.name.toLowerCase().includes(query)\n )\n return { users: matches }\n}\n\n// Test the service\ntest('createUser creates user with ID') {\n const user = createUser({ name: 'Test', email: 'test@test.com' })\n expect(user.id).toBeGreaterThan(0)\n expect(user.name).toBe('Test')\n}\n\ntest('getUser returns created user') {\n const created = createUser({ name: 'Bob', email: 'bob@test.com' })\n const fetched = getUser({ id: created.id })\n expect(fetched?.name).toBe('Bob')\n}\n\ntest('updateUser modifies user') {\n const user = createUser({ name: 'Original', email: 'orig@test.com' })\n const updated = updateUser({ id: user.id, name: 'Updated', email: 'new@test.com' })\n expect(updated?.name).toBe('Updated')\n}\n\ntest('deleteUser removes user') {\n const user = createUser({ name: 'ToDelete', email: 'del@test.com' })\n const result = deleteUser({ id: user.id })\n expect(result.success).toBe(true)\n // getUser returns empty object when not found (not null)\n expect(getUser({ id: user.id }).name).toBe('')\n}\n\n// Demo\nconsole.log('=== User Service Demo ===\\\\n')\n\nconst alice = createUser({ name: 'Alice', email: 'alice@company.com' })\nconsole.log('Created:', alice)\n\nconst bob = createUser({ name: 'Bob', email: 'bob@company.com' })\nconsole.log('Created:', bob)\n\nconst carol = createUser({ name: 'Carol', email: 'carol@company.com' })\nconsole.log('Created:', carol)\n\nconsole.log('\\\\nAll users:', listUsers({ limit: 10, offset: 0 }))\nconsole.log('\\\\nSearch \"ob\":', searchUsers({ query: 'ob' }))\n\n// Type validation in action (safe mode catches wrong types)\nconsole.log('\\\\nType validation test:')\nconsole.log('createUser({ name: 123 }) would return a type error in safe mode')",
|
|
271
271
|
"language": "tjs",
|
|
272
272
|
"description": "A complete backend service with typed endpoints - save this first!"
|
|
273
273
|
},
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
"type": "example",
|
|
280
280
|
"group": "fullstack",
|
|
281
281
|
"order": 13,
|
|
282
|
-
"code": "
|
|
282
|
+
"code": "/*#\n## Client Application\n\nA frontend that calls the User Service.\n\n**First:** Run the \"User Service\" example and save it as \"user-service\"\n**Then:** Run this client to see full-stack in action\n\nThis demonstrates:\n- Importing local TJS modules\n- Type-safe service calls\n- Error handling\n*/\n\n// Import from local module (saved in playground)\nimport { createUser, getUser, listUsers, searchUsers } from 'user-service'\n\n// Helper to display results\nfunction display(label: '', data: {}) {\n console.log(`\\\\n\\${label}:`)\n console.log(JSON.stringify(data, null, 2))\n}\n\n// Main app\nasync function main() {\n console.log('=== Client App ===')\n console.log('Connecting to user-service...\\\\n')\n\n // Create some users\n const user1 = createUser({ name: 'Dave', email: 'dave@startup.io' })\n display('Created user', user1)\n\n const user2 = createUser({ name: 'Eve', email: 'eve@startup.io' })\n display('Created user', user2)\n\n // Fetch a user\n const fetched = getUser({ id: user1.id })\n display('Fetched user', fetched)\n\n // List all\n const all = listUsers({ limit: 100, offset: 0 })\n display('All users', all)\n\n // Search\n const results = searchUsers({ query: 'eve' })\n display('Search results for \"eve\"', results)\n\n // Type error handling\n console.log('\\\\n--- Type Validation Demo ---')\n const badResult = createUser({ name: 999 })\n if (badResult instanceof Error) {\n console.log('Caught type error:', badResult.message)\n }\n\n console.log('\\\\n=== Full-Stack Demo Complete ===')\n console.log('Everything ran in the browser. No server. No build step.')\n}\n\nmain()",
|
|
283
283
|
"language": "tjs",
|
|
284
284
|
"description": "Frontend that calls the User Service - run after saving user-service!"
|
|
285
285
|
},
|
|
@@ -291,7 +291,7 @@
|
|
|
291
291
|
"type": "example",
|
|
292
292
|
"group": "fullstack",
|
|
293
293
|
"order": 14,
|
|
294
|
-
"code": "TjsCompat\n
|
|
294
|
+
"code": "TjsCompat\n/*#\n## Todo API Service\n\nA REST-style API for todo management.\nDemonstrates a more complete service pattern.\n*/\n\n// Simulated persistence layer\nconst todos = new Map()\nlet nextId = 1\n\n// Types are inferred from function signatures below\n// Todo: { id: 0, title: '', completed: false, createdAt: '' }\n// CreateInput: { title: 'Buy milk' }\n// UpdateInput: { id: 1, title: 'Buy milk', completed: false }\n\n// POST /todos - Create\nexport function createTodo(input: { title: 'New todo' }):! { id: 0, title: '', completed: false, createdAt: '' } {\n const todo = {\n id: nextId++,\n title: input.title,\n completed: false,\n createdAt: new Date().toISOString()\n }\n todos.set(todo.id, todo)\n return todo\n}\n\n// GET /todos/:id - Read one (returns empty if not found)\nexport function getTodo(input: { id: 1 }):! { id: 0, title: '', completed: false, createdAt: '' } {\n return todos.get(input.id) || { id: 0, title: '', completed: false, createdAt: '' }\n}\n\n// GET /todos - Read all (with optional filter)\nexport function listTodos(input = { completed: false }):! { todos: [{ id: 0, title: '', completed: false, createdAt: '' }] } {\n let items = [...todos.values()]\n\n if (input.completed !== undefined) {\n items = items.filter(t => t.completed === input.completed)\n }\n\n return { todos: items }\n}\n\n// PUT /todos/:id - Update (returns empty if not found)\nexport function updateTodo(input: { id: 1, title: '', completed: false }):! { id: 0, title: '', completed: false, createdAt: '' } {\n const existing = todos.get(input.id)\n if (!existing) return { id: 0, title: '', completed: false, createdAt: '' }\n\n const updated = {\n ...existing,\n title: input.title ?? existing.title,\n completed: input.completed ?? existing.completed\n }\n todos.set(input.id, updated)\n return updated\n}\n\n// DELETE /todos/:id - Delete\nexport function deleteTodo(input: { id: 1 }):! { deleted: true } {\n const existed = todos.has(input.id)\n todos.delete(input.id)\n return { deleted: existed }\n}\n\n// PATCH /todos/:id/toggle - Toggle completion (returns empty if not found)\nexport function toggleTodo(input: { id: 1 }):! { id: 0, title: '', completed: false, createdAt: '' } {\n const todo = todos.get(input.id)\n if (!todo) return { id: 0, title: '', completed: false, createdAt: '' }\n\n todo.completed = !todo.completed\n return todo\n}\n\n// DELETE /todos/completed - Clear completed\nexport function clearCompleted(input: {}):! { cleared: 0 } {\n let cleared = 0\n for (const [id, todo] of todos) {\n if (todo.completed) {\n todos.delete(id)\n cleared++\n }\n }\n return { cleared }\n}\n\n// Tests\ntest('CRUD operations work') {\n const todo = createTodo({ title: 'Test todo' })\n expect(todo.id).toBeGreaterThan(0)\n expect(todo.completed).toBe(false)\n\n const fetched = getTodo({ id: todo.id })\n expect(fetched?.title).toBe('Test todo')\n\n const toggled = toggleTodo({ id: todo.id })\n expect(toggled?.completed).toBe(true)\n\n const deleted = deleteTodo({ id: todo.id })\n expect(deleted.deleted).toBe(true)\n}\n\n// Demo\nconsole.log('=== Todo API Demo ===\\\\n')\n\n// Create todos\ncreateTodo({ title: 'Learn TJS' })\ncreateTodo({ title: 'Build something cool' })\ncreateTodo({ title: 'Ship it' })\n\nconsole.log('Created 3 todos')\nconsole.log('All:', listTodos({}))\n\n// Complete first one\nconst first = listTodos({}).todos[0]\ntoggleTodo({ id: first.id })\nconsole.log('\\\\nToggled first todo')\nconsole.log('Completed:', listTodos({ completed: true }))\nconsole.log('Pending:', listTodos({ completed: false }))\n\n// Clear completed\nconsole.log('\\\\nClearing completed...')\nconsole.log(clearCompleted({}))\nconsole.log('Remaining:', listTodos({}))",
|
|
295
295
|
"language": "tjs",
|
|
296
296
|
"description": "Complete REST-style Todo API with persistence"
|
|
297
297
|
},
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
"type": "example",
|
|
364
364
|
"group": "patterns",
|
|
365
365
|
"order": 9,
|
|
366
|
-
"code": "TjsCompat\n
|
|
366
|
+
"code": "TjsCompat\n/*#\n## Date Formatting with Imports\n\nThis example demonstrates importing an external ESM module\n(date-fns) and using it with TJS type safety.\n*/\n\nimport { format, formatDistance, addDays, parseISO } from 'date-fns'\n\n// Format a date with various patterns\nfunction formatDate(date: '2024-01-15', pattern: 'yyyy-MM-dd'): '' {\n const parsed = parseISO(date)\n return format(parsed, pattern)\n}\n\n// Get human-readable relative time\nfunction timeAgo(date: '2024-01-15'): '' {\n const parsed = parseISO(date)\n return formatDistance(parsed, new Date(), { addSuffix: true })\n}\n\n// Add days to a date\nfunction addWorkdays(date: '2024-01-15', days: 5): '' {\n const parsed = parseISO(date)\n const result = addDays(parsed, days)\n return format(result, 'yyyy-MM-dd')\n}\n\n// Complex date operation with validation\nfunction createEvent(input: {\n title: 'Meeting',\n startDate: '2024-01-15',\n durationDays: 1\n}): { title: '', start: '', end: '', formatted: '' } {\n const start = parseISO(input.startDate)\n const end = addDays(start, input.durationDays)\n\n return {\n title: input.title,\n start: format(start, 'yyyy-MM-dd'),\n end: format(end, 'yyyy-MM-dd'),\n formatted: `${input.title}: ${format(start, 'MMM d')} - ${format(end, 'MMM d, yyyy')}`\n }\n}",
|
|
367
367
|
"language": "tjs",
|
|
368
368
|
"description": "Uses date-fns for date formatting via ESM import"
|
|
369
369
|
},
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
"type": "example",
|
|
388
388
|
"group": "patterns",
|
|
389
389
|
"order": 11,
|
|
390
|
-
"code": "
|
|
390
|
+
"code": "/*#\n## Lodash Utilities with Type Safety\n\nDemonstrates using lodash-es with TJS runtime validation.\n*/\n\nimport { groupBy, sortBy, uniqBy, debounce, chunk } from 'lodash-es'\n\n// Group items by a key\nfunction groupUsers(users: [{ name: '', dept: '' }], key: 'dept'): { [key: '']: [{ name: '', dept: '' }] } {\n return groupBy(users, key)\n}\n\n// Sort items by property\nfunction sortByAge(users: [{ name: '', age: 0 }]): [{ name: '', age: 0 }] {\n return sortBy(users, ['age'])\n}\n\n// Remove duplicates by property\nfunction uniqueByEmail(users: [{ email: '', name: '' }]): [{ email: '', name: '' }] {\n return uniqBy(users, 'email')\n}\n\n// Chunk array into smaller arrays\n// Note: nested array types like [['']] aren't supported yet, so we omit return type\nfunction paginate(items: [''], pageSize: 10) {\n return chunk(items, pageSize)\n}\n\n// Process data pipeline\nfunction processUserData(input: {\n users: [{ id: 0, name: '', email: '', dept: '' }]\n}): {\n byDept: { [key: '']: [{ id: 0, name: '', email: '', dept: '' }] },\n unique: [{ id: 0, name: '', email: '', dept: '' }],\n count: 0\n} {\n const unique = uniqBy(input.users, 'email')\n const byDept = groupBy(unique, 'dept')\n\n return {\n byDept,\n unique: sortBy(unique, ['name']),\n count: unique.length\n }\n}\n\ntest('groupUsers groups by department') {\n const users = [\n { name: 'Alice', dept: 'eng' },\n { name: 'Bob', dept: 'sales' },\n { name: 'Carol', dept: 'eng' }\n ]\n const grouped = groupUsers(users, 'dept')\n expect(grouped.eng.length).toBe(2)\n expect(grouped.sales.length).toBe(1)\n}\n\ntest('sortByAge sorts correctly') {\n const users = [\n { name: 'Alice', age: 30 },\n { name: 'Bob', age: 25 },\n { name: 'Carol', age: 35 }\n ]\n const sorted = sortByAge(users)\n expect(sorted[0].name).toBe('Bob')\n expect(sorted[2].name).toBe('Carol')\n}\n\ntest('uniqueByEmail deduplicates') {\n const users = [\n { email: 'a@b.com', name: 'Alice' },\n { email: 'a@b.com', name: 'Alice2' },\n { email: 'c@d.com', name: 'Carol' }\n ]\n const unique = uniqueByEmail(users)\n expect(unique.length).toBe(2)\n}\n\ntest('paginate chunks correctly') {\n const items = ['a', 'b', 'c', 'd', 'e']\n const pages = paginate(items, 2)\n expect(pages.length).toBe(3)\n expect(pages[0]).toEqual(['a', 'b'])\n expect(pages[2]).toEqual(['e'])\n}\n\n// Run example\nconst users = [\n { id: 1, name: 'Alice', email: 'alice@co.com', dept: 'Engineering' },\n { id: 2, name: 'Bob', email: 'bob@co.com', dept: 'Sales' },\n { id: 3, name: 'Carol', email: 'carol@co.com', dept: 'Engineering' },\n { id: 4, name: 'Dave', email: 'alice@co.com', dept: 'Marketing' }, // dupe email\n]\n\nconst result = processUserData({ users })\nconsole.log('Unique users:', result.count)\nconsole.log('Departments:', Object.keys(result.byDept))\nconsole.log('Engineering team:', result.byDept['Engineering']?.map(u => u.name))",
|
|
391
391
|
"language": "tjs",
|
|
392
392
|
"description": "Uses lodash-es for utility functions via ESM import"
|
|
393
393
|
},
|
|
@@ -439,6 +439,18 @@
|
|
|
439
439
|
"language": "tjs",
|
|
440
440
|
"description": "> New to WASM in TJS? Start with the **[WASM Quick Start](https://github.com/tonioloewald/tjs-lang/blob/main/docs/WASM-QUICKSTART.md)**. Zero-copy arrays and automatic data marshaling between JS and WASM."
|
|
441
441
|
},
|
|
442
|
+
{
|
|
443
|
+
"title": "WASM Functions (reusable kernels)",
|
|
444
|
+
"filename": "wasm-functions.md",
|
|
445
|
+
"path": "guides/examples/tjs/wasm-functions.md",
|
|
446
|
+
"section": "tjs",
|
|
447
|
+
"type": "example",
|
|
448
|
+
"group": "patterns",
|
|
449
|
+
"order": 28,
|
|
450
|
+
"code": "/*#\n## `wasm function` — reusable WebAssembly kernels\n\nInline `wasm { ... }` blocks live inside a specific JS function. They're\nconvenient for one-off accelerations but they're not reusable.\n\n`wasm function NAME(...)` at module scope is the reusable form:\n\n- Declared at top level with explicit parameters and return type\n- Compiles into the file's single `WebAssembly.Module`\n- Other files can `import { NAME } from '...'` (cross-file composition,\n via the moduleLoader path)\n- Same source produces both \"composed\" (tjs-to-tjs) and \"boundary\" (.js)\n distribution forms — see DOCS-WASM.md\n\nParameters use WASM type names (`i32`, `f64`, `Float32Array`, etc.), not\nTJS example-based syntax. Return types must be `f64` or omitted in v1.\n*/\n\n// A scalar dot product — for educational reference.\n// Real production code would use the SIMD version below.\nwasm function dot_scalar(a: Float32Array, b: Float32Array, n: i32): f64 {\n let acc = 0.0\n for (let i = 0; i < n; i++) {\n acc = acc + a[i] * b[i]\n }\n return acc\n}\n\n// SIMD f32x4 version — processes 4 elements per instruction\nwasm function dot_simd(a: Float32Array, b: Float32Array, n: i32): f64 {\n let acc = f32x4_splat(0.0)\n for (let i = 0; i < n; i += 4) {\n let off = i * 4\n let av = f32x4_load(a, off)\n let bv = f32x4_load(b, off)\n acc = f32x4_add(acc, f32x4_mul(av, bv))\n }\n return f32x4_extract_lane(acc, 0)\n + f32x4_extract_lane(acc, 1)\n + f32x4_extract_lane(acc, 2)\n + f32x4_extract_lane(acc, 3)\n}\n\n// Both functions are now callable from JS. The transpiler emits a wrapper:\n// function dot_scalar(a, b, n) { return globalThis.__tjs_wasm_dot_scalar(a, b, n) }\n// that handles type marshaling automatically. Float32Array args use a\n// zero-copy fast path when allocated via wasmBuffer().\n\n// Demo: build two random vectors and compare the two implementations\nconst N = 128\nconst a = wasmBuffer(Float32Array, N)\nconst b = wasmBuffer(Float32Array, N)\nfor (let i = 0; i < N; i++) {\n a[i] = Math.random()\n b[i] = Math.random()\n}\n\nconst scalarResult = dot_scalar(a, b, N)\nconst simdResult = dot_simd(a, b, N)\n\nconsole.log('dot_scalar:', scalarResult.toFixed(4))\nconsole.log('dot_simd: ', simdResult.toFixed(4))\nconsole.log('match: ', Math.abs(scalarResult - simdResult) < 1e-3)\n\n// The real version of this lives in tjs-lang/linalg — just `import { dot }`\n// and you get the same SIMD kernel, ready to use.",
|
|
451
|
+
"language": "tjs",
|
|
452
|
+
"description": "Top-level `wasm function` declarations — the building block for cross-file WASM libraries."
|
|
453
|
+
},
|
|
442
454
|
{
|
|
443
455
|
"title": "React Todo (Comparison)",
|
|
444
456
|
"filename": "react-todo-comparison.md",
|
|
@@ -471,10 +483,34 @@
|
|
|
471
483
|
"type": "example",
|
|
472
484
|
"group": "unbundled",
|
|
473
485
|
"order": 5,
|
|
474
|
-
"code": "TjsCompat\n
|
|
486
|
+
"code": "TjsCompat\n/*#\n## Per-import CDN hints\n\nThe first path segment of the import spec selects the CDN:\n\n| Hint | Resolves to |\n|------|-------------|\n| `jsdelivr/<spec>` | JSDelivr `/+esm` (the default) |\n| `esmsh/<spec>` | esm.sh (peer-dep dedup, e.g. React) |\n| `unpkg/<spec>` | UNPKG with `?module` |\n| `github/<user>/<repo>/<path>` | esm.sh's `/gh/` route |\n\nNo hint = default routing (JSDelivr, with esm.sh allowlist for React).\n\nFor full URLs (your own host, raw.githubusercontent.com, etc.) just\nwrite the URL — it's left untouched.\n*/\n\n// Default routing: JSDelivr\nimport { tosi, elements } from 'tosijs'\n\n// Force a specific CDN\nimport { html, render } from 'unpkg/lit'\nimport _ from 'jsdelivr/lodash-es'\n\n// Pin a version\nimport { format } from 'jsdelivr/date-fns@3.0.0'\n\n// Load straight from a GitHub repo (esm.sh handles fetching/transform)\n// Format: github/<user>/<repo>[@ref]/<path>\n// (Comment out — the example repo is illustrative)\n// import { thing } from 'github/preactjs/preact@10.22.0/dist/preact.module.js'\n\n// Or, for the truly DIY case, paste the full URL\n// import { x } from 'https://my.com/dist/index.js'\n\nconst { div, h1, button } = elements\n\nconst state = tosi({\n count: 0,\n inc() { state.count++ },\n})\n\ndocument.body.append(\n div(\n h1({ bindText: state.count }),\n button({ onClick: state.inc }, '+1')\n )\n)\n\n// date-fns (loaded via JSDelivr hint) used directly\nconsole.log('Today:', format(new Date(), 'yyyy-MM-dd'))",
|
|
475
487
|
"language": "tjs",
|
|
476
488
|
"description": "By default the playground resolves bare imports through JSDelivr's `/+esm` bundles. When you need a specific CDN — to test an unreleased build, work around a transform issue, or load a module straight from GitHub — prefix the spec with a CDN hint."
|
|
477
489
|
},
|
|
490
|
+
{
|
|
491
|
+
"title": "WASM Library: SIMD Linalg",
|
|
492
|
+
"filename": "wasm-library-author.md",
|
|
493
|
+
"path": "guides/examples/tjs/wasm-library-author.md",
|
|
494
|
+
"section": "tjs",
|
|
495
|
+
"type": "example",
|
|
496
|
+
"group": "wasmlib",
|
|
497
|
+
"order": 0,
|
|
498
|
+
"code": "/*#\n## A reusable WASM library\n\nThis file is the **library half** of a two-example demo. Save it in the\nplayground as a local module named `mylinalg`, then run the companion\nexample (\"Using a WASM Library\") which imports from it.\n\nThe exports below are real `wasm function` declarations — top-level\nWebAssembly kernels with explicit parameters and SIMD f32x4 bodies.\nEach is compiled to wasm bytecode at transpile time and exported as a\nregular JS function that the consumer can call.\n\n**Note:** TJS ships a fuller version of these (and more) in\n`tjs-lang/linalg`. This example mirrors the source so you can read\nexactly what a wasm library looks like.\n*/\n\n/**\n * Dot product of two f32 vectors of length `n`.\n * Returns the sum of element-wise products as f64.\n * `n` must be a multiple of 4 (the SIMD lane width).\n */\nexport wasm function dot(a: Float32Array, b: Float32Array, n: i32): f64 {\n let acc = f32x4_splat(0.0)\n for (let i = 0; i < n; i += 4) {\n let off = i * 4\n let av = f32x4_load(a, off)\n let bv = f32x4_load(b, off)\n acc = f32x4_add(acc, f32x4_mul(av, bv))\n }\n return f32x4_extract_lane(acc, 0)\n + f32x4_extract_lane(acc, 1)\n + f32x4_extract_lane(acc, 2)\n + f32x4_extract_lane(acc, 3)\n}\n\n/**\n * Sum-of-squares of an f32 vector of length `n`.\n * For the L2 norm, take `Math.sqrt(norm_sq(a, n))` on the JS side.\n * Returning the squared value saves a sqrt; cosine similarity divides\n * by `sqrt(norm_sq(a) * norm_sq(b))`, which is one sqrt instead of two.\n */\nexport wasm function norm_sq(a: Float32Array, n: i32): f64 {\n let acc = f32x4_splat(0.0)\n for (let i = 0; i < n; i += 4) {\n let off = i * 4\n let av = f32x4_load(a, off)\n acc = f32x4_add(acc, f32x4_mul(av, av))\n }\n return f32x4_extract_lane(acc, 0)\n + f32x4_extract_lane(acc, 1)\n + f32x4_extract_lane(acc, 2)\n + f32x4_extract_lane(acc, 3)\n}\n\n// Sanity check before saving as a module\nconst a = wasmBuffer(Float32Array, 8)\nconst b = wasmBuffer(Float32Array, 8)\nfor (let i = 0; i < 8; i++) {\n a[i] = i + 1 // [1, 2, 3, 4, 5, 6, 7, 8]\n b[i] = i + 1\n}\n\n// dot([1..8], [1..8]) = 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 = 204\nconsole.log('dot:', dot(a, b, 8))\n\n// norm_sq([1..8]) = same as dot([1..8], [1..8]) = 204\nconsole.log('norm_sq:', norm_sq(a, 8))\n\nconsole.log('\\nLooks good! Save this as `mylinalg` and run the consumer example next.')",
|
|
499
|
+
"language": "tjs",
|
|
500
|
+
"description": "A reusable WASM library exporting f32x4 SIMD vector kernels. Save this as `mylinalg`, then run the \"Using a WASM Library\" example."
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"title": "Using a WASM Library",
|
|
504
|
+
"filename": "wasm-library-consumer.md",
|
|
505
|
+
"path": "guides/examples/tjs/wasm-library-consumer.md",
|
|
506
|
+
"section": "tjs",
|
|
507
|
+
"type": "example",
|
|
508
|
+
"group": "wasmlib",
|
|
509
|
+
"order": 1,
|
|
510
|
+
"code": "/*#\n## Using a WASM library\n\nThis file is the **consumer half** of the wasm-library demo. It imports\ntwo `wasm function`s — `dot` and `norm_sq` — from a sibling module\ncalled `mylinalg` (which you should have saved in the playground from\nthe \"WASM Library: SIMD Linalg\" example).\n\nThe import is plain ESM. The library was authored as TJS source with\n`export wasm function ...` declarations; the playground's loader\nresolves `mylinalg` to the saved module and the wasm bootstrap runs\nwhen this consumer loads.\n\n### Caveat: composition isn't wired into the playground yet\n\nIn the integration tests, TJS pulls imported `wasm function`s into the\n**consumer's own** `WebAssembly.Module` at transpile time (Phase 3\ncomposition) — wasm-to-wasm calls cost single-digit nanoseconds and\nthere's no JS↔wasm boundary on the inner loop. This is the perf\noptimization documented in `wasm-library-plan.md`.\n\nThe playground itself doesn't yet pass a `ModuleLoader` to the\ntranspiler, so cross-file imports are resolved at *runtime* via the\nlocal-module store. Each call from the consumer into `dot`/`norm_sq`\nstill crosses the JS↔wasm boundary (the \"boundary form\" from Phase 4\nof the plan). Correctness is identical; only the per-call cost differs.\n\nWiring the playground's `tjs()` invocation with a `ModuleLoader` is\ntracked in `TODO.md` under \"Playground - Module Management.\"\n*/\n\nimport { dot, norm_sq } from 'mylinalg'\n\n// Cosine similarity between two f32 vectors of length `n`.\n// `n` must be a multiple of 4. (No type annotations on this thin wrapper —\n// the wasm functions it calls already enforce types at the boundary.)\nfunction cosine(a, b, n) {\n const d = dot(a, b, n)\n const ma = norm_sq(a, n)\n const mb = norm_sq(b, n)\n if (ma <= 0 || mb <= 0) return 0\n return d / Math.sqrt(ma * mb)\n}\n\n// Build a small corpus: 4 vectors of dim 8\nconst DIM = 8\nconst corpus = [\n // identical to query → cosine 1\n [1, 2, 3, 4, 5, 6, 7, 8],\n // orthogonal-ish → low cosine\n [-8, 7, -6, 5, -4, 3, -2, 1],\n // anti-parallel → cosine -1\n [-1, -2, -3, -4, -5, -6, -7, -8],\n // partially-aligned → moderate cosine\n [2, 1, 4, 3, 6, 5, 8, 7],\n]\nconst query = [1, 2, 3, 4, 5, 6, 7, 8]\n\n// Use wasmBuffer for zero-copy memory sharing with wasm\nconst queryBuf = wasmBuffer(Float32Array, DIM)\nfor (let i = 0; i < DIM; i++) queryBuf[i] = query[i]\n\nconsole.log('Cosine similarity of query against each row:')\nfor (let i = 0; i < corpus.length; i++) {\n const rowBuf = wasmBuffer(Float32Array, DIM)\n for (let j = 0; j < DIM; j++) rowBuf[j] = corpus[i][j]\n const sim = cosine(queryBuf, rowBuf, DIM)\n console.log(` row ${i}: ${sim.toFixed(4)}`)\n}\n\n// Expected output:\n// row 0: 1.0000 (identical)\n// row 1: 0.1111 (near-orthogonal)\n// row 2: -1.0000 (anti-parallel)\n// row 3: 0.9706 (mostly-aligned)",
|
|
511
|
+
"language": "tjs",
|
|
512
|
+
"description": "Imports `dot` and `norm_sq` from `mylinalg` and computes cosine similarity. Run **after** saving the \"WASM Library: SIMD Linalg\" example as `mylinalg`."
|
|
513
|
+
},
|
|
478
514
|
{
|
|
479
515
|
"title": "LLM with Tool",
|
|
480
516
|
"filename": "llm-with-tool.md",
|
|
@@ -1253,7 +1289,7 @@
|
|
|
1253
1289
|
"title": "CLAUDE.md",
|
|
1254
1290
|
"filename": "CLAUDE.md",
|
|
1255
1291
|
"path": "CLAUDE.md",
|
|
1256
|
-
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n> **TJS syntax is NOT TypeScript.** Full reference: [`CLAUDE-TJS-SYNTAX.md`](CLAUDE-TJS-SYNTAX.md). The single most common LLM mistake is treating `function foo(x: 'default')` as a TypeScript string-literal type. It is _not_ — the colon value is an **example**, and `'default'` widens to `string`. See the syntax doc before writing or modifying TJS source.\n\n## Common Commands\n\n```bash\n# Development\nbun run format # ESLint fix + Prettier\nbun run test:fast # Core tests (skips LLM & benchmarks)\nbun run make # Full build (clean, format, grammars, tsc, esbuild)\nbun run dev # Development server with file watcher\nbun run start # Build demo + start dev server\nbun run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nbun run typecheck # tsc --noEmit (type check without emitting)\nbun run test:llm # LM Studio integration tests\nbun run bench # Vector search benchmarks\nbun run docs # Generate documentation\n\n# Build standalone CLI binaries\nbun run build:cli # Compiles tjs + tjsx to dist/\n\n# Compatibility testing — see scripts/compat-*.ts (zod, effect, radash, superstruct, ts-pattern, kysely)\n\n# Deployment (Firebase)\nbun run deploy # Build demo + deploy functions + hosting\nbun run deploy:hosting # Hosting only (serves from .demo/)\nbun run functions:deploy # Cloud functions only\nbun run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~3024 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3024 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~247 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~757 lines / ~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/parser-transforms.ts` - Type, Generic, and FunctionPredicate block/function form transforms\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/emitters/dts.ts` - .d.ts declaration file generator from TJS transpilation results\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/json-schema.ts` - JSON Schema generation from TypeDescriptors and example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (30 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n\n// JSON Schema\nType('user', { name: '', age: 0 }).toJSONSchema() // → JSON Schema object\nType('user', { name: '', age: 0 }).strip(value) // → strip extra fields\nfunctionMetaToJSONSchema(fn.__tjs) // → { input, output } schemas\n\n// Error History (on by default, zero cost on happy path)\n__tjs.errors() // → recent MonadicErrors (ring buffer, max 64)\n__tjs.clearErrors() // → returns and clears\n__tjs.getErrorCount() // → total since last clear\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\nimport { AgentVM } from 'tjs-lang/vm' // VM only (smaller bundle)\nimport { batteryAtoms } from 'tjs-lang/batteries' // LM Studio batteries\n// Editor integrations: 'tjs-lang/editors/monaco', '/codemirror', '/ace'\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tjs-lang/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: ''): '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tjs-lang/lang'\n\nconst tjsSource = `\nfunction greet(name: ''): '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tjs-lang/lang/from-ts'\nimport { tjs } from 'tjs-lang/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n- Constrained generics (`<T extends { id: number }>`) use the constraint as the example value instead of `any`\n- Generic defaults (`<T = string>`) use the default as the example value\n- Unconstrained generics (`<T>`) degrade to `any` — there's no information to use\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 17 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `bun run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Syntax Reference\n\nFull syntax documentation is in [`CLAUDE-TJS-SYNTAX.md`](CLAUDE-TJS-SYNTAX.md). Key concepts:\n\n- **Colon shorthand**: `function foo(x: 'hello')` — colon value is an _example_, not a type. This is the most common LLM mistake.\n- **Numeric narrowing**: `3.14` = float, `42` = integer, `+0` = non-negative integer\n- **Return types**: `function add(a: 0, b: 0): 0 { ... }` (colon syntax, same as TypeScript)\n- **Safety markers**: `!` = unsafe (skip validation), `?` = safe (explicit validation)\n- **Mode defaults**: Native TJS has all modes ON by default (`TjsEquals`, `TjsClass`, `TjsDate`, `TjsNoeval`, `TjsNoVar`, `TjsStandard`). TS-originated code (`fromTS`) and AJS/VM code get modes OFF. `TjsCompat` directive explicitly disables all modes. `TjsStrict` enables all modes (useful for TS-originated code opting in).\n- **Bang access**: `x!.foo` — returns MonadicError if `x` is null/undefined, otherwise bare `x.foo`. Chains propagate: `x!.foo!.bar`.\n- **Type/Generic/FunctionPredicate**: Three declaration forms for runtime type predicates\n- **`const!`**: Compile-time immutability, zero runtime cost\n- **Equality**: `==`/`!=` = structural equality by default in native TJS (via `Is`/`IsNot`), `===`/`!==` = identity\n- **Polymorphic functions**: Multiple same-name declarations merge into arity/type dispatcher\n- **`extend` blocks**: Local class extensions without prototype pollution\n- **WASM blocks**: Inline WebAssembly compiled at transpile time, with SIMD intrinsics and `wasmBuffer()` zero-copy arrays\n- **`@tjs` annotations**: `/* @tjs ... */` comments in TS files enrich TJS output\n\n#### Runtime Configuration\n\n```typescript\nimport { configure } from 'tjs-lang/lang'\n\nconfigure({ logTypeErrors: true }) // Log type errors to console\nconfigure({ throwTypeErrors: true }) // Throw instead of return (debugging)\nconfigure({ callStacks: true }) // Track call stacks in errors (~2x overhead)\nconfigure({ trackErrors: false }) // Disable error history (on by default)\n```\n\n#### Error History\n\nType errors are tracked in a ring buffer (on by default, zero cost on happy path):\n\n```typescript\n__tjs.errors() // → recent MonadicErrors (newest last, max 64)\n__tjs.clearErrors() // → returns and clears the buffer\n__tjs.getErrorCount() // → total since last clear (survives buffer wrap)\n```\n\nUse for debugging (find silent failures), testing (`clearErrors()` → run → check), and monitoring.\n\n#### Standalone JS Output\n\nEmitted `.js` files work without any runtime setup. Each file includes an inline\nminimal runtime as fallback — only the functions actually used are included (~500\nbytes for a basic validated function). If `globalThis.__tjs` exists (shared runtime),\nit's used instead.\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun (transpiled on-the-fly). It also aliases `tjs-lang` to `./src/index.ts` for local development, so `import { tjs } from 'tjs-lang'` resolves to the source tree without needing `npm link` or a published package.\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `bun run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Playground Examples\n\nThe playground (https://tjs-platform.web.app) shows interactive TJS and AJS examples in a navigable sidebar. Examples live as markdown files with embedded code blocks, NOT as raw `.tjs` files.\n\n**Where they live:**\n\n- TJS playground examples: `guides/examples/tjs/<slug>.md`\n- AJS playground examples: `guides/examples/ajs/<slug>.md` (assumed parallel structure)\n\n**File format:**\n\n<!-- prettier-ignore -->\n```markdown\n<!--{\"section\":\"tjs\",\"type\":\"example\",\"group\":\"basics\",\"order\":16}-->\n\n# Example Title\n\nShort intro paragraph (plain markdown).\n\n```tjs\n/*#\n## Optional H2 — markdown rendered above the code in the playground\nExplain the concept here. Use markdown freely.\n*/\n\n// Then the actual TJS code\nfunction demo() { ... }\n\ntest 'a description' {\n expect(...).toBe(...)\n}\n```\n```\n\nFrontmatter fields: `section` (`tjs`/`ajs`), `type: \"example\"`, `group` (`basics`/`advanced`/etc.), `order` (numeric, controls sidebar position). The H1 becomes the example title in the nav.\n\n**Registration:**\n\nExamples are auto-discovered by `bin/docs.js` (run via `bun run docs`), which walks the markdown tree, parses frontmatter, extracts the `tjs`/`ajs` code block, and writes the result to `demo/docs.json`. The demo loads `docs.json` at runtime — no other registration step.\n\n**After adding/editing an example:** run `bun run docs` and commit the regenerated `demo/docs.json` alongside the `.md` file. (The docs builder also runs as part of `bun run build:demo` and `bun run deploy`.)\n\n**Testing playground examples:**\n\nThe CLI (`bun src/cli/tjs.ts run`) does NOT inject the test-block `expect` harness — that's a playground-only thing. So running an extracted code block via the CLI prints \"expect is not defined\" for any `test { expect(...) }` blocks even though they pass in the playground. To verify an example:\n\n1. **Console-log behavior** (works via CLI): extract the `tjs` code block and run it.\n\n ````bash\n awk '/^```tjs$/{flag=1; next} /^```$/{flag=0} flag' \\\n guides/examples/tjs/<slug>.md > /tmp/example.tjs\n bun src/cli/tjs.ts run /tmp/example.tjs\n ````\n\n Verify the printed output matches the expected behavior shown in the example's comments.\n\n2. **Test blocks**: spin up the dev server (`bun run start`) and load the example in the playground UI to confirm tests pass under the real `expect` harness.\n\n3. **Frontmatter / registration**: after `bun run docs`, grep `demo/docs.json` for the slug to confirm it was picked up with the right `section`/`group`/`order`.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `llms.txt` — agent-facing navigation index (ships in npm bundle); points to docs and source entry points\n- `guides/footguns.md` — JS footguns TJS fixes (boxed-primitive truthiness, `==` coercion, `typeof null`, uninitialized `let`, etc.). Demo: `examples/js-footguns-fixed.tjs`.\n- `guides/playground-imports.md` — how the playground/dev-server resolves bare imports: TFS service worker, default JSDelivr `/+esm` routing, esm.sh allowlist for peer-dep packages (React), CDN hints (`jsdelivr/`, `esmsh/`, `unpkg/`, `github/`), and full-URL passthrough.\n- `README.md` — Project intro, install, quick start\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `TJS-FOR-JS.md` — TJS guide for JavaScript developers (syntax differences, gotchas)\n- `TJS-FOR-TS.md` — TJS guide for TypeScript developers (migration, interop)\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (session-completion checklist, push-before-done rule)\n- `TODO.md` — Open work, organized by area; move items to the **Completed** section when done\n- `PLAN.md` — Roadmap\n- `MANIFESTO-BUILDER.md` / `MANIFESTO-ENTERPRISE.md` — Positioning docs (audience-targeted pitches)\n- `benchmarks.md` — Top-level benchmark results (separate from `guides/benchmarks.md`)\n\n### Keeping This File and `llms.txt` Current\n\nUpdate both files when you change something an agent needs to discover:\n\n- **New top-level markdown doc** → add to \"Additional Documentation\" here AND to the appropriate section of `llms.txt`.\n- **New package entry point** (subpath export in `package.json`) → add to \"Package Entry Points\" here AND to \"Package entry points\" in `llms.txt`.\n- **New CLI command or `bun run` script** → add to \"Common Commands\".\n- **Renamed or moved key source file** → update \"Key Source Files\" here AND \"Source map\" in `llms.txt`.\n- **New language mode / safety directive** → add to the TJS Syntax Reference section.\n- **New playground example** → add to `guides/examples/{tjs,ajs}/<slug>.md`, then `bun run docs` to regenerate `demo/docs.json`. See \"Playground Examples\" above.\n\nSkip stale-prone precision (exact line counts, file sizes) for new entries — they drift silently. The existing `~3024` etc. are kept current opportunistically, not on every commit.\n\n### Tracking Work\n\nWork is tracked in plain markdown — no external issue tracker. Open items live in `TODO.md` (organized by area). When you start a task, find or add the relevant entry; when you finish, check the box and (for substantial work) move it to the Completed section with a short note.\n\n### Landing the Plane (Session Completion Checklist)\n\nSee `AGENTS.md` for the canonical session-completion checklist. Hard rule: work is not complete until `git push` succeeds — never stop before pushing, never `--no-verify` to bypass hooks.\n\n### Common Gotchas\n\n- **`tjs(source)` returns an object, not a string.** It returns `{ code, types, metadata, testResults, ... }` — use `.code` for the transpiled JS string.\n- **Prettier mangles bare-expression code blocks in markdown.** Code blocks tagged ` ```js` get reformatted; bare expressions like `'5' == 5` and `[1] == 1` on consecutive lines collapse into one expression with ASI guards. Use `<!-- prettier-ignore -->` directly above the code fence to preserve hand-formatted JS examples (or tag the block as `text`/`tjs`/`ts` instead — Prettier ignores those).\n- **`tjs-lang` package alias only works inside the project** (set in `bunfig.toml`). Test scripts written in `/tmp` won't resolve `import { tjs } from 'tjs-lang/lang'` to the local source — they'll resolve to whatever's in `node_modules`. For ad-hoc experiments outside the repo, use absolute paths: `import { tjs } from '/Users/.../tjs-lang/src/lang/index'`.\n\n### Running Emitted TJS Code\n\nEmitted JS works standalone — no setup required. Each file includes an inline\nruntime fallback. If you want the shared runtime (e.g. for `isMonadicError` to\nwork across files), install it first:\n\n```typescript\nimport { installRuntime, createRuntime } from '../lang/runtime'\ninstallRuntime() // or: globalThis.__tjs = createRuntime()\n\nconst fn = new Function(result.code + '\\nreturn fnName')()\nfn('valid') // works\nfn(42) // returns MonadicError (not thrown)\n```\n"
|
|
1292
|
+
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n> **TJS syntax is NOT TypeScript.** Full reference: [`CLAUDE-TJS-SYNTAX.md`](CLAUDE-TJS-SYNTAX.md). The single most common LLM mistake is treating `function foo(x: 'default')` as a TypeScript string-literal type. It is _not_ — the colon value is an **example**, and `'default'` widens to `string`. See the syntax doc before writing or modifying TJS source.\n\n## Common Commands\n\n```bash\n# Development\nbun run format # ESLint fix + Prettier\nbun run test:fast # Core tests (skips LLM & benchmarks)\nbun run make # Full build (clean, format, grammars, tsc, esbuild)\nbun run dev # Development server with file watcher\nbun run start # Build demo + start dev server\nbun run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nbun run typecheck # tsc --noEmit (type check without emitting)\nbun run test:llm # LM Studio integration tests\nbun run bench # Vector search benchmarks\nbun run docs # Generate documentation\n\n# Build standalone CLI binaries\nbun run build:cli # Compiles tjs + tjsx to dist/\n\n# Compatibility testing — see scripts/compat-*.ts (zod, effect, radash, superstruct, ts-pattern, kysely)\n\n# Deployment (Firebase)\nbun run deploy # Build demo + deploy functions + hosting\nbun run deploy:hosting # Hosting only (serves from .demo/)\nbun run functions:deploy # Cloud functions only\nbun run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~3024 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3024 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~247 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~757 lines / ~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/parser-transforms.ts` - Type, Generic, and FunctionPredicate block/function form transforms\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/emitters/dts.ts` - .d.ts declaration file generator from TJS transpilation results\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/json-schema.ts` - JSON Schema generation from TypeDescriptors and example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (30 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n\n// JSON Schema\nType('user', { name: '', age: 0 }).toJSONSchema() // → JSON Schema object\nType('user', { name: '', age: 0 }).strip(value) // → strip extra fields\nfunctionMetaToJSONSchema(fn.__tjs) // → { input, output } schemas\n\n// Error History (on by default, zero cost on happy path)\n__tjs.errors() // → recent MonadicErrors (ring buffer, max 64)\n__tjs.clearErrors() // → returns and clears\n__tjs.getErrorCount() // → total since last clear\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\nimport { AgentVM } from 'tjs-lang/vm' // VM only (smaller bundle)\nimport { batteryAtoms } from 'tjs-lang/batteries' // LM Studio batteries\nimport { dot, norm_sq } from 'tjs-lang/linalg' // SIMD linear-algebra kernels\n// Editor integrations: 'tjs-lang/editors/monaco', '/codemirror', '/ace'\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tjs-lang/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: ''): '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tjs-lang/lang'\n\nconst tjsSource = `\nfunction greet(name: ''): '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tjs-lang/lang/from-ts'\nimport { tjs } from 'tjs-lang/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n- Constrained generics (`<T extends { id: number }>`) use the constraint as the example value instead of `any`\n- Generic defaults (`<T = string>`) use the default as the example value\n- Unconstrained generics (`<T>`) degrade to `any` — there's no information to use\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 17 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `bun run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Syntax Reference\n\nFull syntax documentation is in [`CLAUDE-TJS-SYNTAX.md`](CLAUDE-TJS-SYNTAX.md). Key concepts:\n\n- **Colon shorthand**: `function foo(x: 'hello')` — colon value is an _example_, not a type. This is the most common LLM mistake.\n- **Numeric narrowing**: `3.14` = float, `42` = integer, `+0` = non-negative integer\n- **Return types**: `function add(a: 0, b: 0): 0 { ... }` (colon syntax, same as TypeScript)\n- **Safety markers**: `!` = unsafe (skip validation), `?` = safe (explicit validation)\n- **Mode defaults**: Native TJS has all modes ON by default (`TjsEquals`, `TjsClass`, `TjsDate`, `TjsNoeval`, `TjsNoVar`, `TjsStandard`). TS-originated code (`fromTS`) and AJS/VM code get modes OFF. `TjsCompat` directive explicitly disables all modes. `TjsStrict` enables all modes (useful for TS-originated code opting in).\n- **Bang access**: `x!.foo` — returns MonadicError if `x` is null/undefined, otherwise bare `x.foo`. Chains propagate: `x!.foo!.bar`.\n- **Type/Generic/FunctionPredicate**: Three declaration forms for runtime type predicates\n- **`const!`**: Compile-time immutability, zero runtime cost\n- **Equality**: `==`/`!=` = structural equality by default in native TJS (via `Is`/`IsNot`), `===`/`!==` = identity\n- **Polymorphic functions**: Multiple same-name declarations merge into arity/type dispatcher\n- **`extend` blocks**: Local class extensions without prototype pollution\n- **WASM blocks**: Inline WebAssembly compiled at transpile time, with SIMD intrinsics and `wasmBuffer()` zero-copy arrays\n- **`@tjs` annotations**: `/* @tjs ... */` comments in TS files enrich TJS output\n\n#### Runtime Configuration\n\n```typescript\nimport { configure } from 'tjs-lang/lang'\n\nconfigure({ logTypeErrors: true }) // Log type errors to console\nconfigure({ throwTypeErrors: true }) // Throw instead of return (debugging)\nconfigure({ callStacks: true }) // Track call stacks in errors (~2x overhead)\nconfigure({ trackErrors: false }) // Disable error history (on by default)\n```\n\n#### Error History\n\nType errors are tracked in a ring buffer (on by default, zero cost on happy path):\n\n```typescript\n__tjs.errors() // → recent MonadicErrors (newest last, max 64)\n__tjs.clearErrors() // → returns and clears the buffer\n__tjs.getErrorCount() // → total since last clear (survives buffer wrap)\n```\n\nUse for debugging (find silent failures), testing (`clearErrors()` → run → check), and monitoring.\n\n#### Standalone JS Output\n\nEmitted `.js` files work without any runtime setup. Each file includes an inline\nminimal runtime as fallback — only the functions actually used are included (~500\nbytes for a basic validated function). If `globalThis.__tjs` exists (shared runtime),\nit's used instead.\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun (transpiled on-the-fly). It also aliases `tjs-lang` to `./src/index.ts` for local development, so `import { tjs } from 'tjs-lang'` resolves to the source tree without needing `npm link` or a published package.\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `bun run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Playground Examples\n\nThe playground (https://tjs-platform.web.app) shows interactive TJS and AJS examples in a navigable sidebar. Examples live as markdown files with embedded code blocks, NOT as raw `.tjs` files.\n\n**Where they live:**\n\n- TJS playground examples: `guides/examples/tjs/<slug>.md`\n- AJS playground examples: `guides/examples/ajs/<slug>.md` (assumed parallel structure)\n\n**File format:**\n\n<!-- prettier-ignore -->\n```markdown\n<!--{\"section\":\"tjs\",\"type\":\"example\",\"group\":\"basics\",\"order\":16}-->\n\n# Example Title\n\nShort intro paragraph (plain markdown).\n\n```tjs\n/*#\n## Optional H2 — markdown rendered above the code in the playground\nExplain the concept here. Use markdown freely.\n*/\n\n// Then the actual TJS code\nfunction demo() { ... }\n\n/**\n * JSDoc-style /** ... */ blocks are also extracted as docs.\n * Leading ` * ` is stripped from each line; the rest renders as markdown.\n * Use this when porting TS source where JSDoc is already idiomatic.\n */\n\ntest 'a description' {\n expect(...).toBe(...)\n}\n```\n```\n\nFrontmatter fields: `section` (`tjs`/`ajs`), `type: \"example\"`, `group` (`basics`/`advanced`/etc.), `order` (numeric, controls sidebar position). The H1 becomes the example title in the nav.\n\n**Registration:**\n\nExamples are auto-discovered by `bin/docs.js` (run via `bun run docs`), which walks the markdown tree, parses frontmatter, extracts the `tjs`/`ajs` code block, and writes the result to `demo/docs.json`. The demo loads `docs.json` at runtime — no other registration step.\n\n**After adding/editing an example:** run `bun run docs` and commit the regenerated `demo/docs.json` alongside the `.md` file. (The docs builder also runs as part of `bun run build:demo` and `bun run deploy`.)\n\n**Testing playground examples:**\n\nThe CLI (`bun src/cli/tjs.ts run`) does NOT inject the test-block `expect` harness — that's a playground-only thing. So running an extracted code block via the CLI prints \"expect is not defined\" for any `test { expect(...) }` blocks even though they pass in the playground. To verify an example:\n\n1. **Console-log behavior** (works via CLI): extract the `tjs` code block and run it.\n\n ````bash\n awk '/^```tjs$/{flag=1; next} /^```$/{flag=0} flag' \\\n guides/examples/tjs/<slug>.md > /tmp/example.tjs\n bun src/cli/tjs.ts run /tmp/example.tjs\n ````\n\n Verify the printed output matches the expected behavior shown in the example's comments.\n\n2. **Test blocks**: spin up the dev server (`bun run start`) and load the example in the playground UI to confirm tests pass under the real `expect` harness.\n\n3. **Frontmatter / registration**: after `bun run docs`, grep `demo/docs.json` for the slug to confirm it was picked up with the right `section`/`group`/`order`.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `llms.txt` — agent-facing navigation index (ships in npm bundle); points to docs and source entry points\n- `guides/footguns.md` — JS footguns TJS fixes (boxed-primitive truthiness, `==` coercion, `typeof null`, uninitialized `let`, etc.). Demo: `examples/js-footguns-fixed.tjs`.\n- `guides/playground-imports.md` — how the playground/dev-server resolves bare imports: TFS service worker, default JSDelivr `/+esm` routing, esm.sh allowlist for peer-dep packages (React), CDN hints (`jsdelivr/`, `esmsh/`, `unpkg/`, `github/`), and full-URL passthrough.\n- `README.md` — Project intro, install, quick start\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `TJS-FOR-JS.md` — TJS guide for JavaScript developers (syntax differences, gotchas)\n- `TJS-FOR-TS.md` — TJS guide for TypeScript developers (migration, interop)\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (session-completion checklist, push-before-done rule)\n- `TODO.md` — Open work, organized by area; move items to the **Completed** section when done\n- `PLAN.md` — Roadmap\n- `DOCS-WASM.md` — Canonical WASM reference: inline blocks, `wasm function` declarations, memory model, cross-file composition, `tjs-lang/linalg`, current limitations\n- `wasm-library-plan.md` — Cross-file WASM library design (composable `wasm function` declarations, transpile-time module composition, linalg stdlib). Phases 0–5 complete; see plan for current status.\n- `MANIFESTO-BUILDER.md` / `MANIFESTO-ENTERPRISE.md` — Positioning docs (audience-targeted pitches)\n- `benchmarks.md` — Top-level benchmark results (separate from `guides/benchmarks.md`)\n\n### Keeping This File and `llms.txt` Current\n\nUpdate both files when you change something an agent needs to discover:\n\n- **New top-level markdown doc** → add to \"Additional Documentation\" here AND to the appropriate section of `llms.txt`.\n- **New package entry point** (subpath export in `package.json`) → add to \"Package Entry Points\" here AND to \"Package entry points\" in `llms.txt`.\n- **New CLI command or `bun run` script** → add to \"Common Commands\".\n- **Renamed or moved key source file** → update \"Key Source Files\" here AND \"Source map\" in `llms.txt`.\n- **New language mode / safety directive** → add to the TJS Syntax Reference section.\n- **New playground example** → add to `guides/examples/{tjs,ajs}/<slug>.md`, then `bun run docs` to regenerate `demo/docs.json`. See \"Playground Examples\" above.\n\nSkip stale-prone precision (exact line counts, file sizes) for new entries — they drift silently. The existing `~3024` etc. are kept current opportunistically, not on every commit.\n\n### Tracking Work\n\nWork is tracked in plain markdown — no external issue tracker. Open items live in `TODO.md` (organized by area). When you start a task, find or add the relevant entry; when you finish, check the box and (for substantial work) move it to the Completed section with a short note.\n\n### Landing the Plane (Session Completion Checklist)\n\nSee `AGENTS.md` for the canonical session-completion checklist. Hard rule: work is not complete until `git push` succeeds — never stop before pushing, never `--no-verify` to bypass hooks.\n\n### Common Gotchas\n\n- **`tjs(source)` returns an object, not a string.** It returns `{ code, types, metadata, testResults, ... }` — use `.code` for the transpiled JS string.\n- **Prettier mangles bare-expression code blocks in markdown.** Code blocks tagged ` ```js` get reformatted; bare expressions like `'5' == 5` and `[1] == 1` on consecutive lines collapse into one expression with ASI guards. Use `<!-- prettier-ignore -->` directly above the code fence to preserve hand-formatted JS examples (or tag the block as `text`/`tjs`/`ts` instead — Prettier ignores those).\n- **`tjs-lang` package alias only works inside the project** (set in `bunfig.toml`). Test scripts written in `/tmp` won't resolve `import { tjs } from 'tjs-lang/lang'` to the local source — they'll resolve to whatever's in `node_modules`. For ad-hoc experiments outside the repo, use absolute paths: `import { tjs } from '/Users/.../tjs-lang/src/lang/index'`.\n\n### Running Emitted TJS Code\n\nEmitted JS works standalone — no setup required. Each file includes an inline\nruntime fallback. If you want the shared runtime (e.g. for `isMonadicError` to\nwork across files), install it first:\n\n```typescript\nimport { installRuntime, createRuntime } from '../lang/runtime'\ninstallRuntime() // or: globalThis.__tjs = createRuntime()\n\nconst fn = new Function(result.code + '\\nreturn fnName')()\nfn('valid') // works\nfn(42) // returns MonadicError (not thrown)\n```\n"
|
|
1257
1293
|
},
|
|
1258
1294
|
{
|
|
1259
1295
|
"title": "Context: Working with tosijs-schema",
|
|
@@ -1304,13 +1340,13 @@
|
|
|
1304
1340
|
"text": "## Description\n\nPlease provide a detailed description of the changes and the intent behind them :)\n\n## Checklist\n\n- [ ] Changes are covered by tests if behavior has been changed or added\n- [ ] Tests have 100% coverage\n- [ ] If code changes were made, the version in `package.json` has been bumped (matching semver)\n- [ ] If code changes were made, the `yarn build` command has been run and to update the `cdn` directory\n- [ ] If code changes were made, the documentation (in the `/docs` directory) has been updated\n\n## Resolves\n\nIf the PR resolves an open issue tag it here. For example, `Resolves #34`\n"
|
|
1305
1341
|
},
|
|
1306
1342
|
{
|
|
1307
|
-
"text": "...\n\n---\n\n... *\\/ comments render as markdown\n * - Functions render with signature and detailed type info\n *\n * @param source - TJS or TypeScript source code\n * @param types - Type metadata from transpiler (result.types)\n * @returns Formatted markdown documentation\n *\n * @example\n * ```typescript\n * const result = tjs(source)\n * const docs = generateDocsMarkdown(source, result.types)\n * ```",
|
|
1343
|
+
"text": "...\n\n---\n\n...\n\n---\n\n... *\\/ comments render as markdown\n * - Functions render with signature and detailed type info\n *\n * @param source - TJS or TypeScript source code\n * @param types - Type metadata from transpiler (result.types)\n * @returns Formatted markdown documentation\n *\n * @example\n * ```typescript\n * const result = tjs(source)\n * const docs = generateDocsMarkdown(source, result.types)\n * ```",
|
|
1308
1344
|
"title": "docs (inline docs)",
|
|
1309
1345
|
"filename": "docs.ts",
|
|
1310
1346
|
"path": "src/lang/docs.ts"
|
|
1311
1347
|
},
|
|
1312
1348
|
{
|
|
1313
|
-
"text": "# Module Header\n\nThis is the intro.\n\n---\n\nMore docs here.\n\n---\n\n# First Section\n\n---\n\n# Second Section\n\n---\n\n# Heading\n\n- List item 1\n- List item 2\n\n\\`code example\\`\n\n---\n\n## Parameter Syntax\n| Syntax | Meaning |\n|--------|---------|\n| \\`x: 0\\` | Required number |\n| \\`x = 0\\` | Optional, defaults to 0 |\n\n---\n\nIndented content.\n More indented.\n\n---\n\n# Hello World\n\nThis is documentation.\n\n---\n\nIntro\n\n---\n\nMiddle\n\n---\n\n# Math Utilities\n\nA collection of math functions.\n\n---\n\n## Notes\n\nThese functions are pure.\n\n---\n\nHello World\n\n---\n\nIntro\n\n---\n\nSection 1\n\n---\n\nSection 2\n\n---\n\nConclusion\n\n---\n\nTypeScript Example\n\n---\n\nModule header\n\n---\n\nComment inside first - should be ignored\n\n---\n\nBetween first and second\n\n---\n\nComment inside second - should be ignored\n\n---\n\nAfter all functions\n\n---\n\n\n\n---\n\n## Example\nDon't write this:\n\n class FakeClass { constructor(x) {} }\n\n---\n\n\n\n---\n\nDon't write this:\n\n function fakeFn() {}\n\n---\n\n# Module\nSome intro.",
|
|
1349
|
+
"text": "# Module Header\n\nThis is the intro.\n\n---\n\nMore docs here.\n\n---\n\n# First Section\n\n---\n\n# Second Section\n\n---\n\n# Heading\n\n- List item 1\n- List item 2\n\n\\`code example\\`\n\n---\n\n## Parameter Syntax\n| Syntax | Meaning |\n|--------|---------|\n| \\`x: 0\\` | Required number |\n| \\`x = 0\\` | Optional, defaults to 0 |\n\n---\n\nIndented content.\n More indented.\n\n---\n\nblocks in the same file', () => {\n const source = `\n/*#\n## TJS-native\n\n---\n\n# Hello World\n\nThis is documentation.\n\n---\n\nIntro\n\n---\n\nMiddle\n\n---\n\n# Math Utilities\n\nA collection of math functions.\n\n---\n\n## Notes\n\nThese functions are pure.\n\n---\n\nHello World\n\n---\n\nIntro\n\n---\n\nSection 1\n\n---\n\nSection 2\n\n---\n\nConclusion\n\n---\n\nTypeScript Example\n\n---\n\nModule header\n\n---\n\nComment inside first - should be ignored\n\n---\n\nBetween first and second\n\n---\n\nComment inside second - should be ignored\n\n---\n\nAfter all functions\n\n---\n\n\n\n---\n\n## Example\nDon't write this:\n\n class FakeClass { constructor(x) {} }\n\n---\n\n\n\n---\n\nDon't write this:\n\n function fakeFn() {}\n\n---\n\n# Module\nSome intro.",
|
|
1314
1350
|
"title": "docs.test (inline docs)",
|
|
1315
1351
|
"filename": "docs.test.ts",
|
|
1316
1352
|
"path": "src/lang/docs.test.ts"
|
|
@@ -1559,7 +1595,7 @@
|
|
|
1559
1595
|
"title": "TJS Roadmap",
|
|
1560
1596
|
"filename": "PLAN.md",
|
|
1561
1597
|
"path": "PLAN.md",
|
|
1562
|
-
"text": "# TJS Roadmap\n\n## Philosophy\n\nTJS is a practical language that targets multiple runtimes. The type system is _descriptive_ rather than _prescriptive_ - types explain what they are, validate at runtime, and degrade gracefully. No TypeScript gymnastics.\n\nThe runtime is JavaScript today, but it's _our_ JavaScript - the sandboxed expression evaluator, the fuel-metered VM. When we target LLVM or SwiftUI, we compile our AST, not arbitrary JS.\n\n---\n\n## Executive Summary\n\nTJS delivers **runtime type safety with near-zero overhead**. The key insight: single structured arguments enable inline validation that's 20x faster than schema interpretation.\n\n### The Performance Story\n\n| Mode | Overhead | Use Case |\n| --------------- | -------------- | --------------------------------------- |\n| `safety none` | **1.0x** | Production - metadata only, no wrappers |\n| `safety inputs` | **~1.15-1.3x** | Production with validation |\n| `safety all` | ~14x | Debug - validates inputs and outputs |\n| `(!) unsafe` | **1.0x** | Hot paths - explicit opt-out |\n| WASM blocks | **<1.0x** | Heavy computation - faster than JS |\n\nInline validation = **~1.15x overhead** on real-world functions with full runtime type checking. Trivial functions (e.g. `x * 2`) show ~1.3x because the `typeof`/`instanceof` checks dominate.\n\n### Why Inline Validation Wins\n\n```typescript\n// TJS: pleasant syntax, fast validation (1.5x)\nfunction createUser(input: { name: 'Alice'; email: 'a@b.com'; age: 30 }) {\n return save(input)\n}\n\n// TypeScript: painful syntax, no runtime safety (1.0x but unsafe)\nfunction createUser({\n name,\n email,\n age,\n}: {\n name: string\n email: string\n age: number\n}) {\n return save({ name, email, age })\n}\n```\n\nTJS generates inline type checks at transpile time:\n\n```javascript\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.name !== 'string' ||\n typeof input.email !== 'string' ||\n typeof input.age !== 'number'\n) {\n return { $error: true, message: 'Invalid input', path: 'createUser.input' }\n}\n```\n\nNo schema interpretation. JIT-friendly. **20x faster** than Zod/io-ts style validation.\n\n### What You Get\n\n- **Runtime safety in production** - ~1.15x overhead on real functions\n- **Autocomplete always works** - `__tjs` metadata attached regardless of safety\n- **Monadic errors** - type failures return error objects, not exceptions\n- **Escape hatches** - `(!)` for hot functions, `unsafe {}` for hot blocks\n- **WASM acceleration** - `wasm {}` blocks for compute-heavy code\n\n### The Design Alignment\n\nThe idiomatic way to write TJS (single structured argument) is also the fastest way. Language design and performance goals are aligned - you don't have to choose between clean code and fast code.\n\n### Future: Compile to LLVM\n\nThe AST is the source of truth. Today we emit JavaScript. Tomorrow:\n\n- LLVM IR for native binaries\n- Compete with Go and Rust on performance\n- Same type safety, same developer experience\n\n---\n\n## Technical Aspects\n\n### Performance\n\n**Runtime Validation Overhead:**\n\n```\nPlain function call: 1.2ms / 1M calls (baseline)\nsafety: 'none': 1.2ms / 1M calls (~1.0x) - no wrapper\nsafety: 'inputs': 1.4ms / 1M calls (~1.15x) - inline validation*\nsafety: 'all': ~14x / 1M calls - validates args + return\n\n* Inline typeof/instanceof checks, no try/finally, no schema interpretation\n ~1.15x for functions with real work, ~1.3x for trivial functions\n```\n\n**Why inline validation is fast:**\n\n```typescript\n// The happy path - single structured argument\nfunction process(input: { x: 0; y: 0; name: 'default' }) {\n return input.x + input.y\n}\n\n// Generates inline type checks (20x faster than schema interpretation):\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.x !== 'number' ||\n typeof input.y !== 'number' ||\n typeof input.name !== 'string'\n) {\n return { $error: true, message: 'Invalid input', path: 'process.input' }\n}\n```\n\nThis makes `safety: 'inputs'` viable for **production**.\n\n**Why `safety: 'none'` is free:**\n\n- `wrap()` attaches `__tjs` metadata but returns original function\n- No wrapper function, no `fn.apply()`, no argument spreading\n- Introspection/autocomplete still works - metadata is always there\n\n**The `(!) unsafe` marker:**\n\n```typescript\nfunction hot(! x: 0): 0 { return x * 2 }\n```\n\n- Returns original function even with `safety: inputs`\n- Use for hot paths where validation cost matters\n- Autocomplete still works (metadata attached)\n\n**WASM blocks:**\n\n```typescript\nfunction compute(x: 0, y: 0) {\n const scale = 2\n return wasm {\n return x * y * scale // Compiles to WebAssembly\n }\n}\n// Variables (x, y, scale) captured automatically from scope\n// Same code runs as JS fallback if WASM unavailable\n```\n\nWith explicit fallback (when WASM and JS need different code):\n\n```typescript\nfunction transform(arr: []) {\n wasm {\n for (let i = 0; i < arr.length; i++) { arr[i] *= 2 }\n } fallback {\n return arr.map(x => x * 2) // Different JS implementation\n }\n}\n```\n\nWASM compilation is fully implemented:\n\n- Parser extracts `wasm { }` blocks with automatic variable capture\n- Compiler generates valid WebAssembly binary embedded as base64 in output\n- SIMD intrinsics (`f32x4_*`) for 4x float throughput\n- `wasmBuffer()` for zero-copy typed array sharing between JS and WASM\n- `fallback { }` provides JS path when WASM unavailable\n- See the [WASM Quick Start](https://github.com/tonioloewald/tjs-lang/blob/main/docs/WASM-QUICKSTART.md) for details\n\n### Debugging\n\n**Source locations in errors:**\n\n```typescript\n{\n $error: true,\n message: 'Expected string but got number',\n path: 'greet.name', // which parameter\n loc: { start: 15, end: 29 }, // source position\n stack: ['main', 'processUser', 'greet.name'] // call chain (debug mode)\n}\n```\n\n**Debug mode:**\n\n```typescript\nconfigure({ debug: true })\n// Errors now include full call stacks\n```\n\n**The `--debug` flag (planned):**\n\n- Functions know where they're defined\n- Errors include source file and line\n- No source maps needed - metadata is inline\n\n### For Human Coding\n\n**Intuitive syntax:**\n\n```typescript\n// Types ARE examples - self-documenting\nfunction greet(name: 'World', times: 3): '' {\n return (name + '!').repeat(times)\n}\n\n// Autocomplete shows: greet(name: string, times: number): string\n// With examples: greet('World', 3)\n```\n\n**Module-level safety:**\n\n```typescript\nsafety none // This module skips validation\n\nfunction hot(x: 0): 0 {\n return x * 2 // No wrapper, but autocomplete still works\n}\n```\n\n**Escape hatches:**\n\n```typescript\n// Per-function: skip validation for this function\nfunction critical(! data: object) { ... }\n\n// Per-block: skip validation for calls inside\nunsafe {\n for (let i = 0; i < 1000000; i++) {\n hot(i) // No validation overhead\n }\n}\n```\n\n### For Agent Coding\n\n**Introspectable functions:**\n\n```typescript\ngreet.__tjs = {\n params: { name: { type: 'string', required: true, example: 'World' } },\n returns: { type: 'string' },\n}\n\n// Agents can read this to understand function signatures\n// LLMs can generate function call schemas automatically\n```\n\n**Monadic errors:**\n\n```typescript\nconst result = riskyOperation()\nif (isMonadicError(result)) {\n // Error is a value, not an exception\n // Agent can inspect and handle gracefully\n}\n```\n\n**Fuel metering:**\n\n```typescript\n// Agents run with fuel limits - can't run forever\nvm.run(agentCode, { fuel: 10000 })\n```\n\n---\n\n## 1. Type() Builtin\n\nA new builtin for defining types with descriptions and runtime validation.\n\n### Forms\n\n```typescript\n// Full form: description + predicate\nconst ZipCode = Type('5-digit US zip code', (s) => /^\\d{5}$/.test(s))\nconst PositiveInt = Type(\n 'positive integer',\n (n) => Number.isInteger(n) && n > 0\n)\nconst MatchingPasswords = Type(\n 'passwords must match',\n (o) => o.password === o.confirmPassword\n)\n\n// Schema shorthand (common case)\nconst Email = Type('valid email', s.string.email)\nconst Age = Type(s.number.min(0).max(150))\n\n// Description optional when schema is self-explanatory\nconst UserId = Type(s.string.uuid)\n```\n\n### Why\n\n- **Self-documenting**: description IS the type for humans and LLMs\n- **Runtime-checkable**: predicate runs when needed\n- **Composable**: union/intersection combine predicates\n- **Replaces regex-as-type**: more expressive, leaves regexes for actual patterns\n- **Escapes TypeScript corner cases**: no `Pick<Omit<Partial<Required<...>>>>`\n\n### Predicates\n\nPredicates are sync JS functions that run in our runtime:\n\n- Pure expression evaluation (same `$expr` nodes we have)\n- No async, no IO - type checks are in the hot path\n- Sandboxed: no prototype access, no globals\n- Portable: can be translated to any target\n\n### Simple Syntax Sugar Remains\n\n```typescript\n// These still work - Type() is the escape hatch, not the default\nfunction greet(name: 'World', times = 1) { ... }\nfunction delay(ms = 1000) { ... }\nfunction fetch(url: '', timeout = +5000) { ... }\n```\n\n## 2. Conditional Compilation with target()\n\nExplicit target blocks for platform-specific code.\n\n```typescript\ntarget(browser) {\n document.body.appendChild(el)\n}\n\ntarget(node) {\n process.stdout.write(str)\n}\n\ntarget(browser & debug) {\n console.log('Debug mode in browser')\n}\n\ntarget(browser | node) {\n // Runs in either\n}\n```\n\n### Targets\n\n**Current:**\n\n- `browser`\n- `node`\n- `bun`\n- `debug`\n- `production`\n\n**Future:**\n\n- `swiftui`\n- `android`\n- `ios`\n- `win64`\n- `llvm`\n\n### Composition\n\n- `&` - both must match\n- `|` - either matches\n- `target(production)` strips `test {}` blocks and debug code\n\n## 3. Monadic Errors and Debug Mode\n\n### try {} Without catch\n\nBare `try` blocks convert to monadic error returns:\n\n```typescript\ntry {\n let data = riskyOperation()\n process(data)\n}\n// No catch - transforms to:\n// if error, return { $error: true, message, op, cause }\n```\n\nErrors become values, not exceptions. Subsequent code is skipped (monadic flow).\n\n### AgentError Introspection\n\nErrors carry full context:\n\n```typescript\n{\n $error: true,\n message: 'Connection refused',\n op: 'httpFetch', // which atom failed\n cause: <original exception>, // for debugging\n // With --debug:\n source: 'orders.tjs:47:3', // exact location\n callStack: [ // how we got here\n 'ship() at orders.tjs:47:3',\n 'processOrder() at checkout.tjs:123:5',\n 'handleSubmit() at form.tjs:89:12'\n ]\n}\n```\n\n### --debug Flag\n\nWhen transpiled with `--debug`:\n\n- Functions know what they were called and where they came from\n- Errors include source locations and call stacks\n- Runtime can reconstruct the full path to failure\n\n```typescript\n// With --debug, errors show:\n// Error: Invalid ZipCode at ship() (orders.tjs:47:3)\n// called from processOrder() (checkout.tjs:123:5)\n// called from handleSubmit() (form.tjs:89:12)\n```\n\n### Current State\n\n- ✅ Monadic errors (AgentError class)\n- ✅ try {} without catch transforms\n- ⏳ Error introspection (op and message, not full stack)\n- ❌ --debug source mapping\n- ❌ Call stack in errors\n\n## 4. test('description') {} Blocks\n\nInline tests that hoist to bottom of file for execution.\n\n```typescript\nconst ZipCode = Type('5-digit US zip code', (s) => /^\\d{5}$/.test(s))\n\ntest('ZipCode validates correctly') {\n assert(ZipCode.check('12345'))\n assert(!ZipCode.check('1234'))\n assert(!ZipCode.check('123456'))\n assert(!ZipCode.check('abcde'), 'letters should fail')\n}\n\nfunction ship(to: ZipCode, quantity: PositiveInt) {\n // ...\n}\n\ntest('ship requires valid zip and quantity') {\n ship('12345', 1) // ok\n assertThrows(() => ship('bad', 1))\n}\n```\n\n### Failure Output\n\n```\nFAIL: ZipCode validates correctly\n assert(!ZipCode.check('1234')) ← auto-generated from source\n\nFAIL: ship requires valid zip and quantity\n letters should fail ← custom message when provided\n assert(!ZipCode.check('abcde'), 'letters should fail')\n```\n\n### Rules\n\n- `test('description')` - description required, explains what's being tested\n- `assert(expr)` - auto-describes from source code on failure\n- `assert(expr, 'reason')` - custom message overrides auto-description\n- Tests live next to the code they test\n- Hoisted to bottom for execution order\n- Stripped in `target(production)`\n- Like Rust's `#[test]` but inline\n\n## 5. Pragmatic Native Types\n\nTrust constructor names for platform types:\n\n```typescript\n// Instead of shipping 50KB of DOM type definitions:\n// - el instanceof HTMLElement checks constructor.name\n// - If someone lies about their constructor, that's on them\n```\n\nThis applies to:\n\n- DOM types (HTMLElement, Event, etc.)\n- Node types (Buffer, Stream, etc.)\n- Platform types (SwiftUI views, Android widgets)\n\n## 6. Future: Multi-Target Emission\n\nThe same TJS source compiles to:\n\n- JavaScript (current)\n- LLVM IR (native binaries)\n- Swift (iOS/macOS)\n- Kotlin (Android)\n\nPlatform builtins vary by target:\n\n- `browser`: `document`, `window`, `fetch`\n- `swiftui`: `VStack`, `HStack`, `Text`, `Button`\n- `android`: `View`, `TextView`, `LinearLayout`\n\nThe AST is the source of truth. Targets are just emission strategies.\n\n---\n\n## Implementation Status\n\n| # | Feature | Status | Notes |\n| --- | ---------------------- | ------ | --------------------------------------------------------------- |\n| 1 | Type() | ✅ | Full form with description + predicate, Union, Generic, Enum |\n| 2 | target() | ❌ | Conditional compilation |\n| 3 | Monadic Errors | ✅ | MonadicError with path, expected, actual, callStack |\n| 4 | test() blocks | ✅ | extractTests, assert/expect, mock blocks, CLI |\n| 5 | Pragmatic natives | ⏳ | Some constructor checks exist |\n| 6 | Multi-target | ❌ | Future - JS only for now |\n| 7 | Safety levels | ✅ | none/inputs/all + (!)/(?) + unsafe {} |\n| 8 | Module-level safety | ✅ | `safety none` directive parsed and passed |\n| 9 | Single-pass | ✅ | Bun plugin: direct `bun file.tjs` execution |\n| 10 | Module system | ✅ | IndexedDB store, esm.sh CDN, pinned versions |\n| 11 | Autocomplete | ✅ | CodeMirror integration, globals, introspection |\n| 12 | Eval() / SafeFunction | ✅ | Both exported and tested in runtime |\n| 13 | Function introspection | ✅ | \\_\\_tjs metadata with params, returns, examples |\n| 14 | Generic() | ✅ | Runtime-checkable generics with TPair, TRecord |\n| 15 | Asymmetric get/set | ✅ | JS native get/set captures asymmetric types |\n| 16 | `==` that works | ✅ | Eq/NotEq + Is/IsNot, on by default in native TJS, honest typeof |\n| 17 | WASM blocks | ✅ | Full: SIMD intrinsics, wasmBuffer, fallback, base64 embed |\n| 18 | Death to `new` | ✅ | wrapClass + no-explicit-new lint rule |\n| 19 | Linter | ✅ | unused vars, unreachable code, no-explicit-new |\n| 20 | TS→TJS converter | ✅ | `tjs convert` — proven on Zod, Effect, Radash, etc. |\n| 21 | Docs generation | ✅ | Auto-generated with emit, --no-docs, --docs-dir |\n| 22 | Class support | ✅ | TS→TJS class conversion, private→#, Proxy wrap |\n| 23 | JSON Schema | ✅ | Type.toJSONSchema(), Type.strip(), fn.\\_\\_tjs.schema() |\n| 24 | Error history | ✅ | Ring buffer of recent MonadicErrors, on by default |\n| 25 | Polymorphic functions | ✅ | Multiple same-name declarations merge into dispatcher |\n| 26 | Local class extensions | ✅ | `extend String { }` without prototype pollution |\n| 27 | `const!` | ✅ | Compile-time immutability, zero runtime cost |\n| 28 | FunctionPredicate | ✅ | First-class function types with params/returns/contract |\n| 29 | `.d.ts` generation | ✅ | `generateDTS()` from TJS transpilation results |\n| 30 | `@tjs` annotations | ✅ | `/* @tjs ... */` comments enrich TS→TJS output |\n\n### fromTS Transpiler Compatibility\n\nProven against real-world TypeScript libraries (zero test regressions):\n\n| Library | Files | LOC | Tests | Status |\n| ----------- | ----- | ---- | ---------- | ------------- |\n| Radash | 10 | ~3K | 340/340 | ✅ 100% |\n| Superstruct | 8 | 1.8K | 225/225 | ✅ 100% |\n| ts-pattern | 17 | 5.5K | 453/453 | ✅ 100% |\n| Zod | 114 | ~30K | 1842/1842 | ✅ 100% |\n| Kysely | 279 | ~20K | (DB req'd) | ✅ transpiles |\n| Effect | 363 | 120K | (not run) | ✅ transpiles |\n\nScripts: `bun scripts/compat-radash.ts`, `compat-superstruct.ts`, `compat-ts-pattern.ts`, `compat-zod.ts`, `compat-kysely.ts`, `compat-effect.ts`\n\n## Next Up\n\n| Priority | Feature | Why |\n| -------- | -------------------------- | ---------------------------------------------------------- |\n| 1 | **JSON Schema from types** | Full schema emission for API contracts, OpenAPI generation |\n| 2 | **Tacit proxies** | Transpiler-assisted implicit namespaces (see Ideas) |\n| 3 | **target()** | Conditional compilation for build flags |\n| 4 | **Multi-target emission** | LLVM, SwiftUI, Android (long-term) |\n\n## 7. Safety Levels and Flags\n\n### Defaults: Safe and Correct\n\nBy default, TJS is strict:\n\n- All type contracts enforced\n- Lint errors block compilation\n- Unknown types are errors\n\n### Escape Hatches\n\n```bash\ntjs build app.tjs # strict, safe defaults\ntjs build app.tjs --allow-unsafe # let nasty TS libs pass through\ntjs build app.tjs --yolo # bypass all safeguards (--just-fucking-doit)\n```\n\n- `--allow-unsafe`: Complex/unknown types become best-effort runtime checks, warnings not errors\n- `--yolo`: Skip all validation, emit anyway (for when you know what you're doing)\n\n### Lint Integration\n\nLint errors block safe builds. Not warnings - errors. If you want to ship broken code, use `--yolo`.\n\n## 8. Single-Pass Pipeline\n\nOne command does everything:\n\n```bash\ntjs build app.tjs\n```\n\nIn a single pass:\n\n1. **Lint** - catch errors early\n2. **Transpile** - emit target code\n3. **Test** - run inline tests (unless `--no-test`)\n4. **Docs** - extract documentation from types and descriptions\n\nNo separate `tjs lint && tjs build && tjs test && tjs docs`. One pass, all the information is right there.\n\n## 9. Module System ✅\n\n### Local Module Store (IndexedDB)\n\nThe playground provides persistent module storage:\n\n```typescript\n// Save a module\nawait store.save({ name: 'my-utils', type: 'tjs', code: source })\n\n// Import it in another module\nimport { helper } from 'my-utils'\n```\n\n- Modules stored in IndexedDB (persistent across sessions)\n- Validation on save (transpilation + inline tests)\n- Version tracking and timestamps\n- Local modules resolved first, then CDN\n\n### CDN Integration (esm.sh)\n\nnpm packages resolve via esm.sh with pinned versions:\n\n```typescript\nimport { debounce } from 'lodash' // -> https://esm.sh/lodash@4.17.21\nimport { z } from 'zod' // -> https://esm.sh/zod@3.22.0\n```\n\n- Common packages have pinned versions for stability\n- Service Worker caches fetched modules\n- Import maps generated at runtime for browser\n\n### Bundler Compatibility\n\nTJS also works inside conventional bundlers:\n\n- Emits standard ES modules\n- Bun plugin for direct `.tjs` execution\n- Or use playground's zero-build approach\n\nYour choice. We don't force either approach.\n\n## 10. Autocomplete by Introspection\n\nIDE support via runtime introspection, not static `.d.ts` files.\n\n### Heuristic Levels (Progressive Fallback)\n\n**Level 0: Local symbols** (instant, always)\n\n- Scan current file for identifiers\n- Function names, variable names, parameter names\n- Known atoms\n- Like BBEdit - fast, useful, no dependencies\n\n**Level 1: Type-aware** (fast, from syntax)\n\n- Parameter `name: 'Sarah'` → string, offer string methods\n- Variable `x: 17` → number\n- No runtime needed, just syntax analysis\n\n**Level 2: Runtime introspection** (when idle)\n\n- Actually run code with mocks up to cursor\n- Get real shapes from execution\n- Nice to have, not blocking\n\n### Strategy\n\n- Start fast (Level 0+1), upgrade async in background\n- Typing rapidly? Stay at Level 0\n- Paused 200ms? Try Level 1\n- Paused 500ms? Try Level 2\n- Cache aggressively - same signature = same completions\n\n### CSS: Use the Browser\n\nCSS autocomplete is pathological to implement manually - hundreds of properties, thousands of values, vendor prefixes. The browser already knows all of this.\n\n```typescript\n// Let the browser do the work\nconst style = document.createElement('div').style\nObject.keys(style) // Every CSS property\nCSS.supports('display', 'grid') // Validate values\n```\n\nDon't ship CSS type definitions. Query the browser at runtime for:\n\n- Property names\n- Valid values for each property\n- Vendor prefix variants\n\n### Versioned Imports Make This Insane\n\n```typescript\nimport { ship } from 'https://pkg.example.com/shipping@2.0.0/mod.tjs'\n```\n\n- Module already transpiled (cached by URL+version)\n- Already introspected (we know its exports)\n- Immutable (version pinned, never changes)\n- Autocomplete for `ship.` is instant forever\n\nNo node_modules crawling. No LSP server eating 4GB RAM. One file, one unit, instant knowledge.\n\n### Non-Goals\n\n- External LSP dependencies\n- TypeScript language server\n- Crawling dependency graphs\n\n## 11. Eval() - Safe Expression Evaluation\n\nA builtin for evaluating expressions with fuel limits:\n\n```typescript\n// Low default fuel - won't run away\nEval('2 + 2') // ~100 fuel default\n\n// Explicitly allow more for complex work\nEval('fibonacci(20)', { fuel: 1000 })\n\n// Restrict for untrusted input\nEval(userInput, { fuel: 10 })\n```\n\n### Why\n\n- Same sandboxed evaluator we already have, exposed as builtin\n- Safe by default - low fuel limit prevents runaway computation\n- No `eval()` - this is AST evaluation, not string execution\n- Fuel exhaustion returns error, doesn't throw\n\n### Options\n\n```typescript\nEval(expression, {\n fuel: 100, // max fuel (default: 100)\n context: {}, // variables available to expression\n timeout: 1000, // ms timeout (default: fuel * 10)\n})\n```\n\n## Ideas Parking Lot\n\n### Type Flow Optimization (Compile-Time)\n\nSkip redundant type checks when types are already proven. The transpiler tracks type information through the call graph:\n\n**Scenario 1: Chained Functions**\n\n```typescript\nfunction validate(x: 0): 0 {\n return x * 2\n}\nfunction process(x: 0): 0 {\n return x + 1\n}\n\n// Source\nconst result = process(validate(input))\n\n// Naive: validate checks input, process checks validate's output\n// Optimized: validate's return type matches process's input - skip second check\n\n// Transpiled (optimized)\nconst _v = validate(input) // validates input once\nconst result = process.__unchecked(_v) // skips redundant check\n```\n\n**Scenario 2: Loop Bodies**\n\n```typescript\nfunction double(x: 0): 0 {\n return x * 2\n}\nconst nums = [1, 2, 3]\n\n// Source\nnums.map(double)\n\n// Naive: double validates x on every iteration (3 checks)\n// Optimized: nums is number[], so each element is number - skip all checks\n\n// Transpiled (optimized)\nnums.map(double.__unchecked) // zero validation overhead in loop\n```\n\n**Scenario 3: Subtype Relationships**\n\n```typescript\nconst PositiveInt = Type(\n 'positive integer',\n (n) => Number.isInteger(n) && n > 0\n)\nfunction increment(x: 0): 0 {\n return x + 1\n}\n\nconst val: PositiveInt = 5\nincrement(val) // PositiveInt is subtype of number - skip check\n```\n\n**Implementation:**\n\n1. Track return types through call graph\n2. Generate `fn.__unchecked` variants that skip input validation\n3. Emit unchecked calls when input type is proven\n4. Array/iterable element types flow into loop bodies\n5. Subtype relationships allow broader → narrower without checks\n\n**Performance Target:**\n\n- Current inline validation: ~1.15-1.3x overhead\n- With type flow: ~1.0x overhead (skip checks when types proven)\n- Hot loops: 0x overhead (unchecked path)\n\n### JIT-Compiled Type Predicates\n\nWe own the language, so we can optimize hot type checks:\n\n1. **Interpreted mode** (default): Predicate runs as-is\n2. **Compiled mode** (hot path): If a Type validates thousands of times, JIT-compile it\n\n```typescript\nconst ZipCode = Type('5-digit zip', (s) => /^\\d{5}$/.test(s))\n\n// First N calls: interpreted, collecting stats\n// Call N+1: \"this is hot, compile it\"\n// Now it's TypeBox-fast without ahead-of-time compilation\n```\n\nFor `target(production)`, we could inline validators entirely:\n\n```typescript\n// Source\nfunction ship(to: ZipCode) { ... }\n\n// Transpiled (production)\nfunction ship(to) {\n if (typeof to !== 'string' || !/^\\d{5}$/.test(to))\n throw new TypeError('expected 5-digit zip')\n ...\n}\n```\n\nNo runtime Type object, no .check() call - just inlined validation.\n\nUnlike TypeBox (which precompiles via eval and can't handle dynamic types), we can do both interpreted and compiled because we control the compiler.\n\n---\n\n## 12. Function Introspection\n\nFunctions are self-describing. A single signature provides types, examples, and tests:\n\n```typescript\nfunction checkAge(name: 'Anne', age = 17): { canDrink: false } {\n return { canDrink: age >= 21 }\n}\n```\n\nFrom this you get:\n\n| Extracted | Value |\n| ----------------- | -------------------------------------------------------------- |\n| **Types** | `name: string`, `age: number`, returns `{ canDrink: boolean }` |\n| **Examples** | `name = 'Anne'`, `age = 17`, output `{ canDrink: false }` |\n| **Implicit test** | `checkAge('Anne', 17)` should return `{ canDrink: false }` |\n| **Docs** | The signature IS the documentation |\n\n### Runtime Metadata\n\nEvery function carries introspectable metadata:\n\n```typescript\ncheckAge.__tjs\n// {\n// params: {\n// name: { type: { kind: 'string' }, required: true },\n// age: { type: { kind: 'integer' }, required: false, default: 17 }\n// },\n// returns: { type: { kind: 'object' } },\n// source: 'users.tjs:42'\n// }\n```\n\n### Debug Builds\n\nWith `--debug`, functions know where they are and where they were called from:\n\n```typescript\n// Error output includes full trace:\n// Error: Invalid ZipCode at ship() (orders.tjs:47:3)\n// called from processOrder() (checkout.tjs:123:5)\n// called from handleSubmit() (form.tjs:89:12)\n```\n\n### No Source Maps\n\nSource maps are a hack - external files that get out of sync, break in large builds, and require tooling support. TJS replaces them entirely:\n\n- The function _knows_ where it's from (`fn.__tjs.source`)\n- Can't get out of sync (it's part of the function)\n- No external files, no tooling required\n- Works in production without `.map` files\n\n### Why This Matters\n\n- **Auto-generated tests**: Run with examples, expect example output\n- **API documentation**: Always accurate, extracted from source\n- **LLM tool schemas**: Generate OpenAI function calling format automatically\n- **Debug traces**: Full path to failure with source locations\n- **Zero extra effort**: You write the function, you get all of this\n\n## 13. Generic() Builtin\n\nTuring completeness by design, not by accident. TypeScript's generics grew into an unreadable type-level programming language. TJS assumes Turing completeness from the start - the predicate is just code:\n\nFollowing the `Type()` pattern, generics are runtime-inspectable and predicate-validated:\n\n```typescript\nconst List = Generic(\n 'homogeneous list of items',\n [T],\n (x, [T]) => Array.isArray(x) && x.every(item => T.check(item))\n)\n\nconst Map = Generic(\n 'key-value mapping',\n [K, V = any],\n (x, [K, V]) => x instanceof Map && [...x.keys()].every(k => K.check(k))\n)\n\n// Usage\nconst strings: List(string) = ['a', 'b', 'c']\nconst lookup: Map(string, number) = new Map([['age', 42]])\n```\n\n### Why\n\n- **Runtime-checkable**: Not erased like TypeScript generics\n- **Self-documenting**: Description for humans and LLMs\n- **Composable**: Predicates can do real validation\n- **Practical**: Makes complex generics achievable without gymnastics\n\nConverting convoluted TypeScript generics (`Pick<Omit<Partial<...>>>`) is nice-to-have, not a priority.\n\n## 14. Asymmetric Get/Set ✅\n\nProperties that accept a broader type on write but return a narrower type on read. TJS uses JavaScript's native getter/setter syntax which naturally captures asymmetric types:\n\n```typescript\nclass Timestamp {\n #value\n\n constructor(initial: '' | 0 | null) {\n this.#value = initial === null ? new Date() : new Date(initial)\n }\n\n // Setter accepts string, number, or null\n set value(v: '' | 0 | null) {\n this.#value = v === null ? new Date() : new Date(v)\n }\n\n // Getter always returns Date\n get value() {\n return this.#value\n }\n}\n\nconst ts = Timestamp('2024-01-15')\nts.value = 0 // SET accepts: string | number | null\nts.value // GET returns: Date (always normalized)\n```\n\nThe type metadata captures the asymmetry:\n\n- Setter param type: `'' | 0 | null` (union of string, number, null)\n- Getter return type: `Date` (inferred from implementation)\n\nThis matches real-world APIs (DOM, dates, etc.) without TypeScript's painful workarounds.\n\n## 15. `==` That Works\n\nJavaScript's `==` is broken (type coercion chaos). In native TJS, `==`/`!=` use honest equality by default (no coercion, unwraps boxed primitives). TS-originated code retains JS semantics unless `TjsEquals` or `TjsStrict` is added. `Is`/`IsNot` provide explicit deep structural comparison:\n\n| Operator | Behavior |\n| -------- | ----------------------------------------------------------------------------------------- |\n| `Is` | **Value equality** - structural comparison for arrays/objects, calls `.Equals` if defined |\n| `IsNot` | **Value inequality** - negation of Is |\n| `===` | **Identity** - same object reference (rarely needed) |\n\n```typescript\n// Infix syntax - clean and readable\n[1, 2] Is [1, 2] // true (structural)\n[1, 2] IsNot [1, 2, 3] // true (different length)\n5 Is \"5\" // false (no coercion - different types)\n\n// Custom equality via .Equals hook\nconst p1 = {\n x: 1,\n Equals(o) {\n return this.x === o.x\n },\n}\nconst p2 = { x: 1 }\np1 Is p2 // true (via .Equals hook)\np1 === p2 // false (different objects)\n```\n\n### Rules\n\n1. If left has `.Equals`, call `left.Equals(right)`\n2. If right has `.Equals`, call `right.Equals(left)`\n3. Arrays/objects: recursive structural comparison\n4. Primitives: strict equality (no coercion)\n\n### Implementation Status\n\n- ✅ `Eq()`/`NotEq()` honest equality (== and != — enabled by default in native TJS)\n- ✅ `Is()`/`IsNot()` deep structural comparison\n- ✅ Infix syntax transformation (`a Is b` → `Is(a, b)`)\n- ✅ Custom equality protocol (`[tjsEquals]` symbol and `.Equals` method)\n- ✅ Honest `typeof` (`typeof null` → `'null'` — enabled by default in native TJS)\n- ✅ TS-originated code retains JS semantics unless `TjsEquals` or `TjsStrict` added\n\n## 16. Death to Semicolons (`TjsStandard`)\n\nIn native TJS, `TjsStandard` is on by default, so newlines are meaningful. TS-originated code retains JS semantics unless `TjsStandard` or `TjsStrict` is added. This:\n\n```typescript\nfoo()\n```\n\nIs **two statements** (`foo` and `()`), not a function call `foo()`.\n\nThis eliminates:\n\n- Defensive semicolons\n- ASI gotchas\n- The entire \"semicolon debate\"\n\nThe only code this breaks is pathological formatting that nobody writes intentionally.\n\n## 17. Polyglot Blocks (WASM, Shaders, etc.)\n\nTarget-specific code blocks with automatic variable capture and fallback:\n\n```typescript\n// WASM for performance-critical path - variables captured automatically\nfunction matmul(vertices: Float32Array, matrix: Float32Array) {\n wasm {\n for (let i = 0; i < vertices.length; i += 3) {\n // matrix multiply using vertices and matrix from scope\n }\n }\n // Body runs as JS if WASM unavailable\n}\n\n// With explicit fallback when implementations differ:\nfunction transform(data: Float32Array) {\n wasm {\n // WASM-optimized in-place mutation\n for (let i = 0; i < data.length; i++) { data[i] *= 2 }\n } fallback {\n // JS uses different approach\n return data.map(x => x * 2)\n }\n}\n\n// GPU shader (future)\nglShader {\n gl_Position = projection * view * vec4(position, 1.0)\n fragColor = color\n} fallback {\n // CPU fallback\n}\n\n// Debug-only code (stripped in production)\ndebug {\n console.log('state:', state)\n validateInvariants()\n}\n// No fallback needed - just doesn't run in production\n```\n\n### Pattern\n\n```\ntarget(args?) {\n // target-specific code (compiled/translated)\n} fallback? {\n // universal TJS fallback (optional for some targets)\n}\n```\n\n### Targets\n\n| Target | Compiles to | Fallback | Use case |\n| ---------- | ---------------------- | -------- | ------------------------- |\n| `wasm` | WebAssembly | Required | CPU-intensive computation |\n| `glShader` | GLSL | Required | GPU graphics |\n| `metal` | Metal Shading Language | Required | Apple GPU |\n| `debug` | TJS (stripped in prod) | None | Debugging, invariants |\n\n### Why\n\n- **Performance**: WASM/GPU where it matters, TJS everywhere else\n- **Graceful degradation**: Fallback ensures code always runs\n- **Single source**: Don't maintain separate WASM/shader files\n- **Type-safe boundary**: Args translated automatically at the boundary\n\n## 18. Classes and Components\n\nTJS embraces classes, but eliminates JS footguns and enables cross-platform UI components.\n\n### Death to `new` ✅\n\nThe `new` keyword is redundant ceremony. TJS handles it automatically:\n\n```typescript\nclass User {\n constructor(public name: string) {}\n}\n\n// Both work identically in TJS:\nconst u1 = User('Alice') // TJS way - clean\nconst u2 = new User('Alice') // Lint warning: \"use User() instead of new User()\"\n```\n\nIf you call `Foo()` and `Foo` is a class, TJS calls it with `new` internally. No more \"Cannot call a class as a function\" errors.\n\n**Implementation:**\n\n- `wrapClass()` in `src/lang/runtime.ts` - wraps classes with Proxy for callable behavior\n- `emitClassWrapper()` generates wrapper code for transpiled classes\n- `no-explicit-new` lint rule warns about unnecessary `new` keyword usage\n\n### Component Base Class\n\n`Component` is the platform-agnostic UI primitive:\n\n```typescript\nclass MyDropdown extends Component {\n // Shared logic - runs everywhere\n items: string[] = []\n selectedIndex: number = 0\n\n select(index: number) {\n this.selectedIndex = index\n this.emit('change', this.items[index])\n }\n\n // Platform-specific blocks\n web() {\n // CSS, DOM events, ARIA attributes\n this.style = `\n .dropdown { position: relative; }\n .dropdown-menu { position: absolute; }\n `\n }\n\n swift() {\n // SwiftUI modifiers, gestures\n Menu {\n ForEach(items) { item in\n Button(item) { select(items.indexOf(item)) }\n }\n }\n }\n\n android() {\n // Jetpack Compose\n DropdownMenu(expanded = expanded) {\n items.forEach { item ->\n DropdownMenuItem(onClick = { select(items.indexOf(item)) }) {\n Text(item)\n }\n }\n }\n }\n}\n```\n\n### Web Components (HTMLElement)\n\nFor web, `extends HTMLElement` auto-registers custom elements:\n\n```typescript\nclass MyDropdown extends HTMLElement {\n // Automatically registers <my-dropdown>\n}\n\nclass UserCard extends HTMLElement {\n // Automatically registers <user-card>\n}\n\n// Error: can't infer tag name\nclass Thang extends HTMLElement {} // \"can't infer tag-name from 'Thang'\"\n\n// OK: modest names work\nclass MyThang extends HTMLElement {} // <my-thang>\n```\n\n**Key features:**\n\n1. **Auto-registration**: Class name → tag name (`MyDropdown` → `my-dropdown`)\n2. **Inferrable names required**: Must be PascalCase with multiple words\n3. **Hot-reloadable**: Components are hollow shells - redefining rebuilds all instances\n4. **Smart inheritance**: ARIA roles and behaviors wired automatically\n\n### Why Hollow Components?\n\nThe web component registry is a source of pain - you can't redefine elements. TJS sidesteps this:\n\n```typescript\n// First definition\nclass MyButton extends HTMLElement {\n render() {\n return '<button>v1</button>'\n }\n}\n\n// Later redefinition (hot reload, live coding)\nclass MyButton extends HTMLElement {\n render() {\n return '<button>v2</button>'\n }\n}\n// All existing <my-button> elements rebuild with new implementation\n```\n\nThe registered element is a hollow proxy that delegates to the current class definition.\n\n### Platform Adapters\n\nThe `Component` class compiles to platform-native code:\n\n| TJS Source | Web Output | SwiftUI Output | Compose Output |\n| ----------------------------- | ----------------- | ------------------ | ----------------------- |\n| `class Foo extends Component` | Custom Element | `struct Foo: View` | `@Composable fun Foo()` |\n| `this.state = x` | Reactive update | `@State var state` | `mutableStateOf()` |\n| `this.emit('click')` | `dispatchEvent()` | Callback closure | Lambda |\n| `web { }` | Compiled | Stripped | Stripped |\n| `swift { }` | Stripped | Compiled | Stripped |\n\nThe class definition is the source of truth. Platform blocks contain native code for each target - no CSS-in-JS gymnastics trying to map everywhere.\n\n### Tacit Proxies — Implicit Namespaces\n\nA `tacit` directive that tells the transpiler: \"unresolved identifiers matching this pattern should be looked up on this object at runtime.\" No Proxy needed at runtime — pure transpile-time rewriting.\n\n```typescript\ntacit elements as UPPERCASE\n\n// LABEL is undefined, but matches the UPPERCASE pattern\n// Transpiler rewrites to: elements.label(...)\nLABEL('Edit me', INPUT({ placeholder: 'foo' }))\n```\n\nThis gives you JSX-like ergonomics without special syntax, without build magic, and it's general-purpose — DOM elements, SQL builders, test DSLs, anything where you want a namespace of functions without explicit imports.\n\n**Key design constraints:**\n\n- Pattern matching (e.g., UPPERCASE) provides visual signal — you see `LABEL` and know it's tacit\n- File-scoped only — no cross-module leaking (same as `extend` blocks)\n- Zero runtime cost — transpiler rewrites to property access at compile time\n- Falls back to normal resolution — if an identifier IS defined, it takes precedence\n\n**Related prior art:** Python's `from module import *`, Ruby's `method_missing`, Kotlin's scope functions. TJS version is explicit (you declare the pattern) and compile-time (no runtime dispatch).\n\n### JSON Schema Deepening\n\nCurrent: `Type.toJSONSchema()`, `fn.__tjs.schema()`, `functionMetaToJSONSchema()`.\n\nNext steps:\n\n- CLI command: `tjs schema <file>` dumps JSON Schema for all exported types and functions\n- OpenAPI generation from function signatures\n- Schema validation at API boundaries (request/response matching)\n- Round-trip: JSON Schema → TJS Type (for consuming external schemas)\n\n## Non-Goals\n\n- Full JS semantics (we're a subset that's portable)\n- Convoluted TS type gymnastics (maximum effort - best-effort conversion, ignore what we can't handle)\n"
|
|
1598
|
+
"text": "# TJS Roadmap\n\n## Philosophy\n\nTJS is a practical language that targets multiple runtimes. The type system is _descriptive_ rather than _prescriptive_ - types explain what they are, validate at runtime, and degrade gracefully. No TypeScript gymnastics.\n\nThe runtime is JavaScript today, but it's _our_ JavaScript - the sandboxed expression evaluator, the fuel-metered VM. When we target LLVM or SwiftUI, we compile our AST, not arbitrary JS.\n\n---\n\n## Executive Summary\n\nTJS delivers **runtime type safety with near-zero overhead**. The key insight: single structured arguments enable inline validation that's 20x faster than schema interpretation.\n\n### The Performance Story\n\n| Mode | Overhead | Use Case |\n| --------------- | -------------- | --------------------------------------- |\n| `safety none` | **1.0x** | Production - metadata only, no wrappers |\n| `safety inputs` | **~1.15-1.3x** | Production with validation |\n| `safety all` | ~14x | Debug - validates inputs and outputs |\n| `(!) unsafe` | **1.0x** | Hot paths - explicit opt-out |\n| WASM blocks | **<1.0x** | Heavy computation - faster than JS |\n\nInline validation = **~1.15x overhead** on real-world functions with full runtime type checking. Trivial functions (e.g. `x * 2`) show ~1.3x because the `typeof`/`instanceof` checks dominate.\n\n### Why Inline Validation Wins\n\n```typescript\n// TJS: pleasant syntax, fast validation (1.5x)\nfunction createUser(input: { name: 'Alice'; email: 'a@b.com'; age: 30 }) {\n return save(input)\n}\n\n// TypeScript: painful syntax, no runtime safety (1.0x but unsafe)\nfunction createUser({\n name,\n email,\n age,\n}: {\n name: string\n email: string\n age: number\n}) {\n return save({ name, email, age })\n}\n```\n\nTJS generates inline type checks at transpile time:\n\n```javascript\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.name !== 'string' ||\n typeof input.email !== 'string' ||\n typeof input.age !== 'number'\n) {\n return { $error: true, message: 'Invalid input', path: 'createUser.input' }\n}\n```\n\nNo schema interpretation. JIT-friendly. **20x faster** than Zod/io-ts style validation.\n\n### What You Get\n\n- **Runtime safety in production** - ~1.15x overhead on real functions\n- **Autocomplete always works** - `__tjs` metadata attached regardless of safety\n- **Monadic errors** - type failures return error objects, not exceptions\n- **Escape hatches** - `(!)` for hot functions, `unsafe {}` for hot blocks\n- **WASM acceleration** - `wasm {}` blocks for compute-heavy code\n\n### The Design Alignment\n\nThe idiomatic way to write TJS (single structured argument) is also the fastest way. Language design and performance goals are aligned - you don't have to choose between clean code and fast code.\n\n### Future: Compile to LLVM\n\nThe AST is the source of truth. Today we emit JavaScript. Tomorrow:\n\n- LLVM IR for native binaries\n- Compete with Go and Rust on performance\n- Same type safety, same developer experience\n\n---\n\n## Technical Aspects\n\n### Performance\n\n**Runtime Validation Overhead:**\n\n```\nPlain function call: 1.2ms / 1M calls (baseline)\nsafety: 'none': 1.2ms / 1M calls (~1.0x) - no wrapper\nsafety: 'inputs': 1.4ms / 1M calls (~1.15x) - inline validation*\nsafety: 'all': ~14x / 1M calls - validates args + return\n\n* Inline typeof/instanceof checks, no try/finally, no schema interpretation\n ~1.15x for functions with real work, ~1.3x for trivial functions\n```\n\n**Why inline validation is fast:**\n\n```typescript\n// The happy path - single structured argument\nfunction process(input: { x: 0; y: 0; name: 'default' }) {\n return input.x + input.y\n}\n\n// Generates inline type checks (20x faster than schema interpretation):\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.x !== 'number' ||\n typeof input.y !== 'number' ||\n typeof input.name !== 'string'\n) {\n return { $error: true, message: 'Invalid input', path: 'process.input' }\n}\n```\n\nThis makes `safety: 'inputs'` viable for **production**.\n\n**Why `safety: 'none'` is free:**\n\n- `wrap()` attaches `__tjs` metadata but returns original function\n- No wrapper function, no `fn.apply()`, no argument spreading\n- Introspection/autocomplete still works - metadata is always there\n\n**The `(!) unsafe` marker:**\n\n```typescript\nfunction hot(! x: 0): 0 { return x * 2 }\n```\n\n- Returns original function even with `safety: inputs`\n- Use for hot paths where validation cost matters\n- Autocomplete still works (metadata attached)\n\n**WASM blocks:**\n\n```typescript\nfunction compute(x: 0, y: 0) {\n const scale = 2\n return wasm {\n return x * y * scale // Compiles to WebAssembly\n }\n}\n// Variables (x, y, scale) captured automatically from scope\n// Same code runs as JS fallback if WASM unavailable\n```\n\nWith explicit fallback (when WASM and JS need different code):\n\n```typescript\nfunction transform(arr: []) {\n wasm {\n for (let i = 0; i < arr.length; i++) { arr[i] *= 2 }\n } fallback {\n return arr.map(x => x * 2) // Different JS implementation\n }\n}\n```\n\nWASM compilation is fully implemented:\n\n- Parser extracts `wasm { }` blocks with automatic variable capture\n- Compiler generates valid WebAssembly binary embedded as base64 in output\n- SIMD intrinsics (`f32x4_*`) for 4x float throughput\n- `wasmBuffer()` for zero-copy typed array sharing between JS and WASM\n- `fallback { }` provides JS path when WASM unavailable\n- See the [WASM Quick Start](https://github.com/tonioloewald/tjs-lang/blob/main/docs/WASM-QUICKSTART.md) for details\n\n### Debugging\n\n**Source locations in errors:**\n\n```typescript\n{\n $error: true,\n message: 'Expected string but got number',\n path: 'greet.name', // which parameter\n loc: { start: 15, end: 29 }, // source position\n stack: ['main', 'processUser', 'greet.name'] // call chain (debug mode)\n}\n```\n\n**Debug mode:**\n\n```typescript\nconfigure({ debug: true })\n// Errors now include full call stacks\n```\n\n**The `--debug` flag (planned):**\n\n- Functions know where they're defined\n- Errors include source file and line\n- No source maps needed - metadata is inline\n\n### For Human Coding\n\n**Intuitive syntax:**\n\n```typescript\n// Types ARE examples - self-documenting\nfunction greet(name: 'World', times: 3): '' {\n return (name + '!').repeat(times)\n}\n\n// Autocomplete shows: greet(name: string, times: number): string\n// With examples: greet('World', 3)\n```\n\n**Module-level safety:**\n\n```typescript\nsafety none // This module skips validation\n\nfunction hot(x: 0): 0 {\n return x * 2 // No wrapper, but autocomplete still works\n}\n```\n\n**Escape hatches:**\n\n```typescript\n// Per-function: skip validation for this function\nfunction critical(! data: object) { ... }\n\n// Per-block: skip validation for calls inside\nunsafe {\n for (let i = 0; i < 1000000; i++) {\n hot(i) // No validation overhead\n }\n}\n```\n\n### For Agent Coding\n\n**Introspectable functions:**\n\n```typescript\ngreet.__tjs = {\n params: { name: { type: 'string', required: true, example: 'World' } },\n returns: { type: 'string' },\n}\n\n// Agents can read this to understand function signatures\n// LLMs can generate function call schemas automatically\n```\n\n**Monadic errors:**\n\n```typescript\nconst result = riskyOperation()\nif (isMonadicError(result)) {\n // Error is a value, not an exception\n // Agent can inspect and handle gracefully\n}\n```\n\n**Fuel metering:**\n\n```typescript\n// Agents run with fuel limits - can't run forever\nvm.run(agentCode, { fuel: 10000 })\n```\n\n---\n\n## 1. Type() Builtin\n\nA new builtin for defining types with descriptions and runtime validation.\n\n### Forms\n\n```typescript\n// Full form: description + predicate\nconst ZipCode = Type('5-digit US zip code', (s) => /^\\d{5}$/.test(s))\nconst PositiveInt = Type(\n 'positive integer',\n (n) => Number.isInteger(n) && n > 0\n)\nconst MatchingPasswords = Type(\n 'passwords must match',\n (o) => o.password === o.confirmPassword\n)\n\n// Schema shorthand (common case)\nconst Email = Type('valid email', s.string.email)\nconst Age = Type(s.number.min(0).max(150))\n\n// Description optional when schema is self-explanatory\nconst UserId = Type(s.string.uuid)\n```\n\n### Why\n\n- **Self-documenting**: description IS the type for humans and LLMs\n- **Runtime-checkable**: predicate runs when needed\n- **Composable**: union/intersection combine predicates\n- **Replaces regex-as-type**: more expressive, leaves regexes for actual patterns\n- **Escapes TypeScript corner cases**: no `Pick<Omit<Partial<Required<...>>>>`\n\n### Predicates\n\nPredicates are sync JS functions that run in our runtime:\n\n- Pure expression evaluation (same `$expr` nodes we have)\n- No async, no IO - type checks are in the hot path\n- Sandboxed: no prototype access, no globals\n- Portable: can be translated to any target\n\n### Simple Syntax Sugar Remains\n\n```typescript\n// These still work - Type() is the escape hatch, not the default\nfunction greet(name: 'World', times = 1) { ... }\nfunction delay(ms = 1000) { ... }\nfunction fetch(url: '', timeout = +5000) { ... }\n```\n\n## 2. Conditional Compilation with target()\n\nExplicit target blocks for platform-specific code.\n\n```typescript\ntarget(browser) {\n document.body.appendChild(el)\n}\n\ntarget(node) {\n process.stdout.write(str)\n}\n\ntarget(browser & debug) {\n console.log('Debug mode in browser')\n}\n\ntarget(browser | node) {\n // Runs in either\n}\n```\n\n### Targets\n\n**Current:**\n\n- `browser`\n- `node`\n- `bun`\n- `debug`\n- `production`\n\n**Future:**\n\n- `swiftui`\n- `android`\n- `ios`\n- `win64`\n- `llvm`\n\n### Composition\n\n- `&` - both must match\n- `|` - either matches\n- `target(production)` strips `test {}` blocks and debug code\n\n## 3. Monadic Errors and Debug Mode\n\n### try {} Without catch\n\nBare `try` blocks convert to monadic error returns:\n\n```typescript\ntry {\n let data = riskyOperation()\n process(data)\n}\n// No catch - transforms to:\n// if error, return { $error: true, message, op, cause }\n```\n\nErrors become values, not exceptions. Subsequent code is skipped (monadic flow).\n\n### AgentError Introspection\n\nErrors carry full context:\n\n```typescript\n{\n $error: true,\n message: 'Connection refused',\n op: 'httpFetch', // which atom failed\n cause: <original exception>, // for debugging\n // With --debug:\n source: 'orders.tjs:47:3', // exact location\n callStack: [ // how we got here\n 'ship() at orders.tjs:47:3',\n 'processOrder() at checkout.tjs:123:5',\n 'handleSubmit() at form.tjs:89:12'\n ]\n}\n```\n\n### --debug Flag\n\nWhen transpiled with `--debug`:\n\n- Functions know what they were called and where they came from\n- Errors include source locations and call stacks\n- Runtime can reconstruct the full path to failure\n\n```typescript\n// With --debug, errors show:\n// Error: Invalid ZipCode at ship() (orders.tjs:47:3)\n// called from processOrder() (checkout.tjs:123:5)\n// called from handleSubmit() (form.tjs:89:12)\n```\n\n### Current State\n\n- ✅ Monadic errors (AgentError class)\n- ✅ try {} without catch transforms\n- ⏳ Error introspection (op and message, not full stack)\n- ❌ --debug source mapping\n- ❌ Call stack in errors\n\n## 4. test('description') {} Blocks\n\nInline tests that hoist to bottom of file for execution.\n\n```typescript\nconst ZipCode = Type('5-digit US zip code', (s) => /^\\d{5}$/.test(s))\n\ntest('ZipCode validates correctly') {\n assert(ZipCode.check('12345'))\n assert(!ZipCode.check('1234'))\n assert(!ZipCode.check('123456'))\n assert(!ZipCode.check('abcde'), 'letters should fail')\n}\n\nfunction ship(to: ZipCode, quantity: PositiveInt) {\n // ...\n}\n\ntest('ship requires valid zip and quantity') {\n ship('12345', 1) // ok\n assertThrows(() => ship('bad', 1))\n}\n```\n\n### Failure Output\n\n```\nFAIL: ZipCode validates correctly\n assert(!ZipCode.check('1234')) ← auto-generated from source\n\nFAIL: ship requires valid zip and quantity\n letters should fail ← custom message when provided\n assert(!ZipCode.check('abcde'), 'letters should fail')\n```\n\n### Rules\n\n- `test('description')` - description required, explains what's being tested\n- `assert(expr)` - auto-describes from source code on failure\n- `assert(expr, 'reason')` - custom message overrides auto-description\n- Tests live next to the code they test\n- Hoisted to bottom for execution order\n- Stripped in `target(production)`\n- Like Rust's `#[test]` but inline\n\n## 5. Pragmatic Native Types\n\nTrust constructor names for platform types:\n\n```typescript\n// Instead of shipping 50KB of DOM type definitions:\n// - el instanceof HTMLElement checks constructor.name\n// - If someone lies about their constructor, that's on them\n```\n\nThis applies to:\n\n- DOM types (HTMLElement, Event, etc.)\n- Node types (Buffer, Stream, etc.)\n- Platform types (SwiftUI views, Android widgets)\n\n## 6. Future: Multi-Target Emission\n\nThe same TJS source compiles to:\n\n- JavaScript (current)\n- LLVM IR (native binaries)\n- Swift (iOS/macOS)\n- Kotlin (Android)\n\nPlatform builtins vary by target:\n\n- `browser`: `document`, `window`, `fetch`\n- `swiftui`: `VStack`, `HStack`, `Text`, `Button`\n- `android`: `View`, `TextView`, `LinearLayout`\n\nThe AST is the source of truth. Targets are just emission strategies.\n\n---\n\n## Implementation Status\n\n| # | Feature | Status | Notes |\n| --- | ---------------------- | ------ | --------------------------------------------------------------- |\n| 1 | Type() | ✅ | Full form with description + predicate, Union, Generic, Enum |\n| 2 | target() | ❌ | Conditional compilation |\n| 3 | Monadic Errors | ✅ | MonadicError with path, expected, actual, callStack |\n| 4 | test() blocks | ✅ | extractTests, assert/expect, mock blocks, CLI |\n| 5 | Pragmatic natives | ⏳ | Some constructor checks exist |\n| 6 | Multi-target | ❌ | Future - JS only for now |\n| 7 | Safety levels | ✅ | none/inputs/all + (!)/(?) + unsafe {} |\n| 8 | Module-level safety | ✅ | `safety none` directive parsed and passed |\n| 9 | Single-pass | ✅ | Bun plugin: direct `bun file.tjs` execution |\n| 10 | Module system | ✅ | IndexedDB store, esm.sh CDN, pinned versions |\n| 11 | Autocomplete | ✅ | CodeMirror integration, globals, introspection |\n| 12 | Eval() / SafeFunction | ✅ | Both exported and tested in runtime |\n| 13 | Function introspection | ✅ | \\_\\_tjs metadata with params, returns, examples |\n| 14 | Generic() | ✅ | Runtime-checkable generics with TPair, TRecord |\n| 15 | Asymmetric get/set | ✅ | JS native get/set captures asymmetric types |\n| 16 | `==` that works | ✅ | Eq/NotEq + Is/IsNot, on by default in native TJS, honest typeof |\n| 17 | WASM blocks | ✅ | Full: SIMD intrinsics, wasmBuffer, fallback, base64 embed |\n| 18 | Death to `new` | ✅ | wrapClass + no-explicit-new lint rule |\n| 19 | Linter | ✅ | unused vars, unreachable code, no-explicit-new |\n| 20 | TS→TJS converter | ✅ | `tjs convert` — proven on Zod, Effect, Radash, etc. |\n| 21 | Docs generation | ✅ | Auto-generated with emit, --no-docs, --docs-dir |\n| 22 | Class support | ✅ | TS→TJS class conversion, private→#, Proxy wrap |\n| 23 | JSON Schema | ✅ | Type.toJSONSchema(), Type.strip(), fn.\\_\\_tjs.schema() |\n| 24 | Error history | ✅ | Ring buffer of recent MonadicErrors, on by default |\n| 25 | Polymorphic functions | ✅ | Multiple same-name declarations merge into dispatcher |\n| 26 | Local class extensions | ✅ | `extend String { }` without prototype pollution |\n| 27 | `const!` | ✅ | Compile-time immutability, zero runtime cost |\n| 28 | FunctionPredicate | ✅ | First-class function types with params/returns/contract |\n| 29 | `.d.ts` generation | ✅ | `generateDTS()` from TJS transpilation results |\n| 30 | `@tjs` annotations | ✅ | `/* @tjs ... */` comments enrich TS→TJS output |\n\n### fromTS Transpiler Compatibility\n\nProven against real-world TypeScript libraries (zero test regressions):\n\n| Library | Files | LOC | Tests | Status |\n| ----------- | ----- | ---- | ---------- | ------------- |\n| Radash | 10 | ~3K | 340/340 | ✅ 100% |\n| Superstruct | 8 | 1.8K | 225/225 | ✅ 100% |\n| ts-pattern | 17 | 5.5K | 453/453 | ✅ 100% |\n| Zod | 114 | ~30K | 1842/1842 | ✅ 100% |\n| Kysely | 279 | ~20K | (DB req'd) | ✅ transpiles |\n| Effect | 363 | 120K | (not run) | ✅ transpiles |\n\nScripts: `bun scripts/compat-radash.ts`, `compat-superstruct.ts`, `compat-ts-pattern.ts`, `compat-zod.ts`, `compat-kysely.ts`, `compat-effect.ts`\n\n## Next Up\n\n| Priority | Feature | Why |\n| -------- | -------------------------- | ---------------------------------------------------------- |\n| 1 | **JSON Schema from types** | Full schema emission for API contracts, OpenAPI generation |\n| 2 | **Tacit proxies** | Transpiler-assisted implicit namespaces (see Ideas) |\n| 3 | **target()** | Conditional compilation for build flags |\n| 4 | **Multi-target emission** | LLVM, SwiftUI, Android (long-term) |\n\n## 7. Safety Levels and Flags\n\n### Defaults: Safe and Correct\n\nBy default, TJS is strict:\n\n- All type contracts enforced\n- Lint errors block compilation\n- Unknown types are errors\n\n### Escape Hatches\n\n```bash\ntjs build app.tjs # strict, safe defaults\ntjs build app.tjs --allow-unsafe # let nasty TS libs pass through\ntjs build app.tjs --yolo # bypass all safeguards (--just-fucking-doit)\n```\n\n- `--allow-unsafe`: Complex/unknown types become best-effort runtime checks, warnings not errors\n- `--yolo`: Skip all validation, emit anyway (for when you know what you're doing)\n\n### Lint Integration\n\nLint errors block safe builds. Not warnings - errors. If you want to ship broken code, use `--yolo`.\n\n## 8. Single-Pass Pipeline\n\nOne command does everything:\n\n```bash\ntjs build app.tjs\n```\n\nIn a single pass:\n\n1. **Lint** - catch errors early\n2. **Transpile** - emit target code\n3. **Test** - run inline tests (unless `--no-test`)\n4. **Docs** - extract documentation from types and descriptions\n\nNo separate `tjs lint && tjs build && tjs test && tjs docs`. One pass, all the information is right there.\n\n## 9. Module System ✅\n\n### Local Module Store (IndexedDB)\n\nThe playground provides persistent module storage:\n\n```typescript\n// Save a module\nawait store.save({ name: 'my-utils', type: 'tjs', code: source })\n\n// Import it in another module\nimport { helper } from 'my-utils'\n```\n\n- Modules stored in IndexedDB (persistent across sessions)\n- Validation on save (transpilation + inline tests)\n- Version tracking and timestamps\n- Local modules resolved first, then CDN\n\n### CDN Integration (esm.sh)\n\nnpm packages resolve via esm.sh with pinned versions:\n\n```typescript\nimport { debounce } from 'lodash' // -> https://esm.sh/lodash@4.17.21\nimport { z } from 'zod' // -> https://esm.sh/zod@3.22.0\n```\n\n- Common packages have pinned versions for stability\n- Service Worker caches fetched modules\n- Import maps generated at runtime for browser\n\n### Bundler Compatibility\n\nTJS also works inside conventional bundlers:\n\n- Emits standard ES modules\n- Bun plugin for direct `.tjs` execution\n- Or use playground's zero-build approach\n\nYour choice. We don't force either approach.\n\n## 10. Autocomplete by Introspection\n\nIDE support via runtime introspection, not static `.d.ts` files.\n\n### Heuristic Levels (Progressive Fallback)\n\n**Level 0: Local symbols** (instant, always)\n\n- Scan current file for identifiers\n- Function names, variable names, parameter names\n- Known atoms\n- Like BBEdit - fast, useful, no dependencies\n\n**Level 1: Type-aware** (fast, from syntax)\n\n- Parameter `name: 'Sarah'` → string, offer string methods\n- Variable `x: 17` → number\n- No runtime needed, just syntax analysis\n\n**Level 2: Runtime introspection** (when idle)\n\n- Actually run code with mocks up to cursor\n- Get real shapes from execution\n- Nice to have, not blocking\n\n### Strategy\n\n- Start fast (Level 0+1), upgrade async in background\n- Typing rapidly? Stay at Level 0\n- Paused 200ms? Try Level 1\n- Paused 500ms? Try Level 2\n- Cache aggressively - same signature = same completions\n\n### CSS: Use the Browser\n\nCSS autocomplete is pathological to implement manually - hundreds of properties, thousands of values, vendor prefixes. The browser already knows all of this.\n\n```typescript\n// Let the browser do the work\nconst style = document.createElement('div').style\nObject.keys(style) // Every CSS property\nCSS.supports('display', 'grid') // Validate values\n```\n\nDon't ship CSS type definitions. Query the browser at runtime for:\n\n- Property names\n- Valid values for each property\n- Vendor prefix variants\n\n### Versioned Imports Make This Insane\n\n```typescript\nimport { ship } from 'https://pkg.example.com/shipping@2.0.0/mod.tjs'\n```\n\n- Module already transpiled (cached by URL+version)\n- Already introspected (we know its exports)\n- Immutable (version pinned, never changes)\n- Autocomplete for `ship.` is instant forever\n\nNo node_modules crawling. No LSP server eating 4GB RAM. One file, one unit, instant knowledge.\n\n### Non-Goals\n\n- External LSP dependencies\n- TypeScript language server\n- Crawling dependency graphs\n\n## 11. Eval() - Safe Expression Evaluation\n\nA builtin for evaluating expressions with fuel limits:\n\n```typescript\n// Low default fuel - won't run away\nEval('2 + 2') // ~100 fuel default\n\n// Explicitly allow more for complex work\nEval('fibonacci(20)', { fuel: 1000 })\n\n// Restrict for untrusted input\nEval(userInput, { fuel: 10 })\n```\n\n### Why\n\n- Same sandboxed evaluator we already have, exposed as builtin\n- Safe by default - low fuel limit prevents runaway computation\n- No `eval()` - this is AST evaluation, not string execution\n- Fuel exhaustion returns error, doesn't throw\n\n### Options\n\n```typescript\nEval(expression, {\n fuel: 100, // max fuel (default: 100)\n context: {}, // variables available to expression\n timeout: 1000, // ms timeout (default: fuel * 10)\n})\n```\n\n## Ideas Parking Lot\n\n### Type Flow Optimization (Compile-Time)\n\nSkip redundant type checks when types are already proven. The transpiler tracks type information through the call graph:\n\n**Scenario 1: Chained Functions**\n\n```typescript\nfunction validate(x: 0): 0 {\n return x * 2\n}\nfunction process(x: 0): 0 {\n return x + 1\n}\n\n// Source\nconst result = process(validate(input))\n\n// Naive: validate checks input, process checks validate's output\n// Optimized: validate's return type matches process's input - skip second check\n\n// Transpiled (optimized)\nconst _v = validate(input) // validates input once\nconst result = process.__unchecked(_v) // skips redundant check\n```\n\n**Scenario 2: Loop Bodies**\n\n```typescript\nfunction double(x: 0): 0 {\n return x * 2\n}\nconst nums = [1, 2, 3]\n\n// Source\nnums.map(double)\n\n// Naive: double validates x on every iteration (3 checks)\n// Optimized: nums is number[], so each element is number - skip all checks\n\n// Transpiled (optimized)\nnums.map(double.__unchecked) // zero validation overhead in loop\n```\n\n**Scenario 3: Subtype Relationships**\n\n```typescript\nconst PositiveInt = Type(\n 'positive integer',\n (n) => Number.isInteger(n) && n > 0\n)\nfunction increment(x: 0): 0 {\n return x + 1\n}\n\nconst val: PositiveInt = 5\nincrement(val) // PositiveInt is subtype of number - skip check\n```\n\n**Implementation:**\n\n1. Track return types through call graph\n2. Generate `fn.__unchecked` variants that skip input validation\n3. Emit unchecked calls when input type is proven\n4. Array/iterable element types flow into loop bodies\n5. Subtype relationships allow broader → narrower without checks\n\n**Performance Target:**\n\n- Current inline validation: ~1.15-1.3x overhead\n- With type flow: ~1.0x overhead (skip checks when types proven)\n- Hot loops: 0x overhead (unchecked path)\n\n### JIT-Compiled Type Predicates\n\nWe own the language, so we can optimize hot type checks:\n\n1. **Interpreted mode** (default): Predicate runs as-is\n2. **Compiled mode** (hot path): If a Type validates thousands of times, JIT-compile it\n\n```typescript\nconst ZipCode = Type('5-digit zip', (s) => /^\\d{5}$/.test(s))\n\n// First N calls: interpreted, collecting stats\n// Call N+1: \"this is hot, compile it\"\n// Now it's TypeBox-fast without ahead-of-time compilation\n```\n\nFor `target(production)`, we could inline validators entirely:\n\n```typescript\n// Source\nfunction ship(to: ZipCode) { ... }\n\n// Transpiled (production)\nfunction ship(to) {\n if (typeof to !== 'string' || !/^\\d{5}$/.test(to))\n throw new TypeError('expected 5-digit zip')\n ...\n}\n```\n\nNo runtime Type object, no .check() call - just inlined validation.\n\nUnlike TypeBox (which precompiles via eval and can't handle dynamic types), we can do both interpreted and compiled because we control the compiler.\n\n---\n\n## 12. Function Introspection\n\nFunctions are self-describing. A single signature provides types, examples, and tests:\n\n```typescript\nfunction checkAge(name: 'Anne', age = 17): { canDrink: false } {\n return { canDrink: age >= 21 }\n}\n```\n\nFrom this you get:\n\n| Extracted | Value |\n| ----------------- | -------------------------------------------------------------- |\n| **Types** | `name: string`, `age: number`, returns `{ canDrink: boolean }` |\n| **Examples** | `name = 'Anne'`, `age = 17`, output `{ canDrink: false }` |\n| **Implicit test** | `checkAge('Anne', 17)` should return `{ canDrink: false }` |\n| **Docs** | The signature IS the documentation |\n\n### Runtime Metadata\n\nEvery function carries introspectable metadata:\n\n```typescript\ncheckAge.__tjs\n// {\n// params: {\n// name: { type: { kind: 'string' }, required: true },\n// age: { type: { kind: 'integer' }, required: false, default: 17 }\n// },\n// returns: { type: { kind: 'object' } },\n// source: 'users.tjs:42'\n// }\n```\n\n### Debug Builds\n\nWith `--debug`, functions know where they are and where they were called from:\n\n```typescript\n// Error output includes full trace:\n// Error: Invalid ZipCode at ship() (orders.tjs:47:3)\n// called from processOrder() (checkout.tjs:123:5)\n// called from handleSubmit() (form.tjs:89:12)\n```\n\n### No Source Maps\n\nSource maps are a hack - external files that get out of sync, break in large builds, and require tooling support. TJS replaces them entirely:\n\n- The function _knows_ where it's from (`fn.__tjs.source`)\n- Can't get out of sync (it's part of the function)\n- No external files, no tooling required\n- Works in production without `.map` files\n\n### Why This Matters\n\n- **Auto-generated tests**: Run with examples, expect example output\n- **API documentation**: Always accurate, extracted from source\n- **LLM tool schemas**: Generate OpenAI function calling format automatically\n- **Debug traces**: Full path to failure with source locations\n- **Zero extra effort**: You write the function, you get all of this\n\n## 13. Generic() Builtin\n\nTuring completeness by design, not by accident. TypeScript's generics grew into an unreadable type-level programming language. TJS assumes Turing completeness from the start - the predicate is just code:\n\nFollowing the `Type()` pattern, generics are runtime-inspectable and predicate-validated:\n\n```typescript\nconst List = Generic(\n 'homogeneous list of items',\n [T],\n (x, [T]) => Array.isArray(x) && x.every(item => T.check(item))\n)\n\nconst Map = Generic(\n 'key-value mapping',\n [K, V = any],\n (x, [K, V]) => x instanceof Map && [...x.keys()].every(k => K.check(k))\n)\n\n// Usage\nconst strings: List(string) = ['a', 'b', 'c']\nconst lookup: Map(string, number) = new Map([['age', 42]])\n```\n\n### Why\n\n- **Runtime-checkable**: Not erased like TypeScript generics\n- **Self-documenting**: Description for humans and LLMs\n- **Composable**: Predicates can do real validation\n- **Practical**: Makes complex generics achievable without gymnastics\n\nConverting convoluted TypeScript generics (`Pick<Omit<Partial<...>>>`) is nice-to-have, not a priority.\n\n## 14. Asymmetric Get/Set ✅\n\nProperties that accept a broader type on write but return a narrower type on read. TJS uses JavaScript's native getter/setter syntax which naturally captures asymmetric types:\n\n```typescript\nclass Timestamp {\n #value\n\n constructor(initial: '' | 0 | null) {\n this.#value = initial === null ? new Date() : new Date(initial)\n }\n\n // Setter accepts string, number, or null\n set value(v: '' | 0 | null) {\n this.#value = v === null ? new Date() : new Date(v)\n }\n\n // Getter always returns Date\n get value() {\n return this.#value\n }\n}\n\nconst ts = Timestamp('2024-01-15')\nts.value = 0 // SET accepts: string | number | null\nts.value // GET returns: Date (always normalized)\n```\n\nThe type metadata captures the asymmetry:\n\n- Setter param type: `'' | 0 | null` (union of string, number, null)\n- Getter return type: `Date` (inferred from implementation)\n\nThis matches real-world APIs (DOM, dates, etc.) without TypeScript's painful workarounds.\n\n## 15. `==` That Works\n\nJavaScript's `==` is broken (type coercion chaos). In native TJS, `==`/`!=` use honest equality by default (no coercion, unwraps boxed primitives). TS-originated code retains JS semantics unless `TjsEquals` or `TjsStrict` is added. `Is`/`IsNot` provide explicit deep structural comparison:\n\n| Operator | Behavior |\n| -------- | ----------------------------------------------------------------------------------------- |\n| `Is` | **Value equality** - structural comparison for arrays/objects, calls `.Equals` if defined |\n| `IsNot` | **Value inequality** - negation of Is |\n| `===` | **Identity** - same object reference (rarely needed) |\n\n```typescript\n// Infix syntax - clean and readable\n[1, 2] Is [1, 2] // true (structural)\n[1, 2] IsNot [1, 2, 3] // true (different length)\n5 Is \"5\" // false (no coercion - different types)\n\n// Custom equality via .Equals hook\nconst p1 = {\n x: 1,\n Equals(o) {\n return this.x === o.x\n },\n}\nconst p2 = { x: 1 }\np1 Is p2 // true (via .Equals hook)\np1 === p2 // false (different objects)\n```\n\n### Rules\n\n1. If left has `.Equals`, call `left.Equals(right)`\n2. If right has `.Equals`, call `right.Equals(left)`\n3. Arrays/objects: recursive structural comparison\n4. Primitives: strict equality (no coercion)\n\n### Implementation Status\n\n- ✅ `Eq()`/`NotEq()` honest equality (== and != — enabled by default in native TJS)\n- ✅ `Is()`/`IsNot()` deep structural comparison\n- ✅ Infix syntax transformation (`a Is b` → `Is(a, b)`)\n- ✅ Custom equality protocol (`[tjsEquals]` symbol and `.Equals` method)\n- ✅ Honest `typeof` (`typeof null` → `'null'` — enabled by default in native TJS)\n- ✅ TS-originated code retains JS semantics unless `TjsEquals` or `TjsStrict` added\n\n## 16. Death to Semicolons (`TjsStandard`)\n\nIn native TJS, `TjsStandard` is on by default, so newlines are meaningful. TS-originated code retains JS semantics unless `TjsStandard` or `TjsStrict` is added. This:\n\n```typescript\nfoo()\n```\n\nIs **two statements** (`foo` and `()`), not a function call `foo()`.\n\nThis eliminates:\n\n- Defensive semicolons\n- ASI gotchas\n- The entire \"semicolon debate\"\n\nThe only code this breaks is pathological formatting that nobody writes intentionally.\n\n## 17. Polyglot Blocks (WASM, Shaders, etc.)\n\nTarget-specific code blocks with automatic variable capture and fallback:\n\n```typescript\n// WASM for performance-critical path - variables captured automatically\nfunction matmul(vertices: Float32Array, matrix: Float32Array) {\n wasm {\n for (let i = 0; i < vertices.length; i += 3) {\n // matrix multiply using vertices and matrix from scope\n }\n }\n // Body runs as JS if WASM unavailable\n}\n\n// With explicit fallback when implementations differ:\nfunction transform(data: Float32Array) {\n wasm {\n // WASM-optimized in-place mutation\n for (let i = 0; i < data.length; i++) { data[i] *= 2 }\n } fallback {\n // JS uses different approach\n return data.map(x => x * 2)\n }\n}\n\n// GPU shader (future)\nglShader {\n gl_Position = projection * view * vec4(position, 1.0)\n fragColor = color\n} fallback {\n // CPU fallback\n}\n\n// Debug-only code (stripped in production)\ndebug {\n console.log('state:', state)\n validateInvariants()\n}\n// No fallback needed - just doesn't run in production\n```\n\n### Pattern\n\n```\ntarget(args?) {\n // target-specific code (compiled/translated)\n} fallback? {\n // universal TJS fallback (optional for some targets)\n}\n```\n\n### Targets\n\n| Target | Compiles to | Fallback | Use case |\n| ---------- | ---------------------- | -------- | ------------------------- |\n| `wasm` | WebAssembly | Required | CPU-intensive computation |\n| `glShader` | GLSL | Required | GPU graphics |\n| `metal` | Metal Shading Language | Required | Apple GPU |\n| `debug` | TJS (stripped in prod) | None | Debugging, invariants |\n\n### Why\n\n- **Performance**: WASM/GPU where it matters, TJS everywhere else\n- **Graceful degradation**: Fallback ensures code always runs\n- **Single source**: Don't maintain separate WASM/shader files\n- **Type-safe boundary**: Args translated automatically at the boundary\n\n### WASM Libraries (cross-file, composable)\n\nThe current `wasm {}` block model is single-file — wasm runs inline inside the function that wraps it. The next step is letting tjs source files declare reusable `wasm function`s that other files can import, with module composition at transpile time so intra-library calls stay inside the wasm module (no JS↔wasm boundary). First stdlib target: `tjs-lang/linalg`.\n\nFull design and phased implementation plan: [`wasm-library-plan.md`](./wasm-library-plan.md). Phase 0 (assumption verification against the current codebase) is complete; the prerequisite work is module consolidation (one module per file, currently N) and a transpile-time module loader.\n\n## 18. Classes and Components\n\nTJS embraces classes, but eliminates JS footguns and enables cross-platform UI components.\n\n### Death to `new` ✅\n\nThe `new` keyword is redundant ceremony. TJS handles it automatically:\n\n```typescript\nclass User {\n constructor(public name: string) {}\n}\n\n// Both work identically in TJS:\nconst u1 = User('Alice') // TJS way - clean\nconst u2 = new User('Alice') // Lint warning: \"use User() instead of new User()\"\n```\n\nIf you call `Foo()` and `Foo` is a class, TJS calls it with `new` internally. No more \"Cannot call a class as a function\" errors.\n\n**Implementation:**\n\n- `wrapClass()` in `src/lang/runtime.ts` - wraps classes with Proxy for callable behavior\n- `emitClassWrapper()` generates wrapper code for transpiled classes\n- `no-explicit-new` lint rule warns about unnecessary `new` keyword usage\n\n### Component Base Class\n\n`Component` is the platform-agnostic UI primitive:\n\n```typescript\nclass MyDropdown extends Component {\n // Shared logic - runs everywhere\n items: string[] = []\n selectedIndex: number = 0\n\n select(index: number) {\n this.selectedIndex = index\n this.emit('change', this.items[index])\n }\n\n // Platform-specific blocks\n web() {\n // CSS, DOM events, ARIA attributes\n this.style = `\n .dropdown { position: relative; }\n .dropdown-menu { position: absolute; }\n `\n }\n\n swift() {\n // SwiftUI modifiers, gestures\n Menu {\n ForEach(items) { item in\n Button(item) { select(items.indexOf(item)) }\n }\n }\n }\n\n android() {\n // Jetpack Compose\n DropdownMenu(expanded = expanded) {\n items.forEach { item ->\n DropdownMenuItem(onClick = { select(items.indexOf(item)) }) {\n Text(item)\n }\n }\n }\n }\n}\n```\n\n### Web Components (HTMLElement)\n\nFor web, `extends HTMLElement` auto-registers custom elements:\n\n```typescript\nclass MyDropdown extends HTMLElement {\n // Automatically registers <my-dropdown>\n}\n\nclass UserCard extends HTMLElement {\n // Automatically registers <user-card>\n}\n\n// Error: can't infer tag name\nclass Thang extends HTMLElement {} // \"can't infer tag-name from 'Thang'\"\n\n// OK: modest names work\nclass MyThang extends HTMLElement {} // <my-thang>\n```\n\n**Key features:**\n\n1. **Auto-registration**: Class name → tag name (`MyDropdown` → `my-dropdown`)\n2. **Inferrable names required**: Must be PascalCase with multiple words\n3. **Hot-reloadable**: Components are hollow shells - redefining rebuilds all instances\n4. **Smart inheritance**: ARIA roles and behaviors wired automatically\n\n### Why Hollow Components?\n\nThe web component registry is a source of pain - you can't redefine elements. TJS sidesteps this:\n\n```typescript\n// First definition\nclass MyButton extends HTMLElement {\n render() {\n return '<button>v1</button>'\n }\n}\n\n// Later redefinition (hot reload, live coding)\nclass MyButton extends HTMLElement {\n render() {\n return '<button>v2</button>'\n }\n}\n// All existing <my-button> elements rebuild with new implementation\n```\n\nThe registered element is a hollow proxy that delegates to the current class definition.\n\n### Platform Adapters\n\nThe `Component` class compiles to platform-native code:\n\n| TJS Source | Web Output | SwiftUI Output | Compose Output |\n| ----------------------------- | ----------------- | ------------------ | ----------------------- |\n| `class Foo extends Component` | Custom Element | `struct Foo: View` | `@Composable fun Foo()` |\n| `this.state = x` | Reactive update | `@State var state` | `mutableStateOf()` |\n| `this.emit('click')` | `dispatchEvent()` | Callback closure | Lambda |\n| `web { }` | Compiled | Stripped | Stripped |\n| `swift { }` | Stripped | Compiled | Stripped |\n\nThe class definition is the source of truth. Platform blocks contain native code for each target - no CSS-in-JS gymnastics trying to map everywhere.\n\n### Tacit Proxies — Implicit Namespaces\n\nA `tacit` directive that tells the transpiler: \"unresolved identifiers matching this pattern should be looked up on this object at runtime.\" No Proxy needed at runtime — pure transpile-time rewriting.\n\n```typescript\ntacit elements as UPPERCASE\n\n// LABEL is undefined, but matches the UPPERCASE pattern\n// Transpiler rewrites to: elements.label(...)\nLABEL('Edit me', INPUT({ placeholder: 'foo' }))\n```\n\nThis gives you JSX-like ergonomics without special syntax, without build magic, and it's general-purpose — DOM elements, SQL builders, test DSLs, anything where you want a namespace of functions without explicit imports.\n\n**Key design constraints:**\n\n- Pattern matching (e.g., UPPERCASE) provides visual signal — you see `LABEL` and know it's tacit\n- File-scoped only — no cross-module leaking (same as `extend` blocks)\n- Zero runtime cost — transpiler rewrites to property access at compile time\n- Falls back to normal resolution — if an identifier IS defined, it takes precedence\n\n**Related prior art:** Python's `from module import *`, Ruby's `method_missing`, Kotlin's scope functions. TJS version is explicit (you declare the pattern) and compile-time (no runtime dispatch).\n\n### JSON Schema Deepening\n\nCurrent: `Type.toJSONSchema()`, `fn.__tjs.schema()`, `functionMetaToJSONSchema()`.\n\nNext steps:\n\n- CLI command: `tjs schema <file>` dumps JSON Schema for all exported types and functions\n- OpenAPI generation from function signatures\n- Schema validation at API boundaries (request/response matching)\n- Round-trip: JSON Schema → TJS Type (for consuming external schemas)\n\n## Non-Goals\n\n- Full JS semantics (we're a subset that's portable)\n- Convoluted TS type gymnastics (maximum effort - best-effort conversion, ignore what we can't handle)\n"
|
|
1563
1599
|
},
|
|
1564
1600
|
{
|
|
1565
1601
|
"title": "TJS Syntax Reference",
|
|
@@ -1577,7 +1613,7 @@
|
|
|
1577
1613
|
"title": "TJS-Lang TODO",
|
|
1578
1614
|
"filename": "TODO.md",
|
|
1579
1615
|
"path": "TODO.md",
|
|
1580
|
-
"text": "# TJS-Lang TODO\n\n## Playground - Error Navigation\n\n- [ ] Test errors: click should navigate to source location\n- [ ] Console errors: click should navigate to source location\n- [ ] Error in imported module: click through to source\n\n## Playground - Module Management\n\n- [ ] Import one example from another in playground\n- [ ] Save/Load TS examples (consistency with TJS examples)\n- [ ] File name should be linked to example name\n- [ ] New example button in playground\n- [ ] UI for managing stored modules (browse/delete IndexedDB)\n- [ ] Auto-discover and build local dependencies in module resolution\n\n## Language Features\n\n- [x] Honest boolean coercion (TjsStandard) — `Boolean(new Boolean(false))` and friends now return false. Source rewriter wraps every truthiness context (`if`/`while`/`for`/`do`/`!`/`&&`/`||`/`?:`, `Boolean(x)` calls) with `__tjs.toBool` which unwraps boxed primitives. Always-on under `TjsStandard`. Demo: `examples/js-footguns-fixed.tjs`. Doc: `guides/footguns.md`.\n- [ ] Intra-function type safety — bring TJS to parity with TS / good linters\n - [ ] **Tier 1 (lint):** `TjsTypedLet` mode — warn/error on `let` without type annotation. Follows the `TjsNoVar` precedent (`src/lang/parser.ts:214`). Severity gated by mode (info under `TjsStandard`, error under `TjsStrict`). ~30 lines in `src/lang/linter.ts`.\n - [ ] **Tier 2 (compile-time inference):** infer `TypeDescriptor` from initializer (already have `src/lang/inference.ts`), store per-decl in scope, walk subsequent `AssignmentExpression` nodes, warn on type-incompatible reassignment. ~200–300 lines, linter-only, no codegen changes.\n - [ ] **Tier 3 (runtime checks, long-term):** rewrite `let x = e` / `x = e` in the JS emitter to `__tjs.checkType(...)` so out-of-band assignments return MonadicError. Open design questions: closed-over `let`s, uninitialized `let x`, perf cost of per-assignment call. Defer until we see how Tier 1+2 land.\n- [ ] Audit monadic-error propagation when an error is nested inside a parameter (esp. arrays)\n - Rule: a MonadicError reaching a checked boundary should surface as ONE error, not as data containing an error (e.g. `[5, <error>, 7]`).\n - Caveat: if the function never inspects the param, no error needs to fire — propagation is on-check, not eager.\n - Partial coverage today: input-validation in emitted JS scans top-level array params for an embedded MonadicError and re-propagates it (commit `3db372d`). Other paths likely miss this — return values, deeper nesting (object fields, arrays-of-arrays), function-typed params whose callbacks return arrays containing errors, etc.\n - Investigate: where does a MonadicError survive past a boundary as data? Audit `checkType` in `src/lang/runtime.ts`, the emitted-JS validation prefix in `src/lang/emitters/js.ts`, and `checkFnShape` interaction with array returns.\n- [ ] Portable Type predicates - expression-only AJS subset (no loops, no async, serializable)\n- [ ] Sync AJS / AJS-to-JS compilation - for type-checked AJS that passes static analysis, transpile to native JS with fuel injection points. Enables both type safety guarantees AND native performance for RBAC rules, predicates, etc.\n- [ ] Self-contained transpiler output (no runtime dependency)\n - Currently transpiled code references `globalThis.__tjs` for pushStack/popStack, typeError, Is/IsNot\n - Requires runtime to be installed or a stub (see playground's manual \\_\\_tjs stub)\n - Goal: TJS produces completely independent code, only needing semantic dependencies\n - Options: inline minimal runtime (~1KB), `{ standalone: true }` option, or tree-shake\n - See: src/lang/emitters/js.ts TODO comment for details\n- [x] WASM compilation at transpile time (not runtime)\n - [x] Compile wasm {} blocks during transpilation\n - [x] Embed base64-encoded WASM bytes in output\n - [x] Include WAT disassembly as comment for debugging/learning\n - [x] Self-contained async instantiation (no separate compileWasmBlocksForIframe)\n- [x] Expand WASM support beyond POC\n - [x] For loops with numeric bounds\n - [x] Conditionals (if/else)\n - [x] Local variables within block\n - [x] Typed array access (Float32Array, Float64Array, Int32Array, Uint8Array)\n - [x] Memory operations\n - [x] Continue/break statements\n - [x] Logical expressions (&& / ||)\n - [x] Math functions (sqrt, abs, floor, ceil, min, max, sin, cos, log, exp, pow)\n- [x] WASM SIMD support (v128/f32x4)\n - 12 f32x4 intrinsics: load, store, splat, extract_lane, replace_lane, add, sub, mul, div, neg, sqrt\n - Explicit intrinsic approach (users call f32x4\\_\\* in wasm blocks)\n - Disassembler handles 0xfd prefix with LEB128 sub-opcodes\n - 16-byte aligned memory for v128 loads/stores\n - Demos: starfield SIMD rotation, vector search cosine similarity\n- [ ] WASM SIMD vector search (batteries)\n - Replace JS vectorSearch battery with WASM SIMD implementation\n - SIMD cosine similarity demonstrated in vector search demo\n - TODO: integrate as a battery atom with auto-detect + fallback\n\n## Editor\n\n- [ ] Embedded AJS syntax highlighting\n\n## Documentation / Examples\n\n- [ ] Create an endpoint example\n- [ ] Fold docs and tests into one panel, with passing tests collapsed by default (ts -> tjs inserts test; tjs -> js turns test blocks into documentation along with outcomes).\n- [ ] Dim/hide the preview tab if nothing ever changed it\n- [ ] Single source of truth for version number. I note the badge displayed in console is not matching the version. No hardwired versions -- version number is pulled from package.json and written to version.ts somewhere and that is the single source of truth.\n\n## Infrastructure\n\n- [ ] Make playground components reusable for others\n- [ ] Web worker for transpiles (freezer - not needed yet)\n- [x] Retarget Firebase as host platform (vs GitHub Pages)\n- [ ] Universal LLM endpoint with real LLMs (OpenAI, Anthropic, etc.)\n- [ ] ESM-as-a-service: versioned library endpoints\n- [ ] User accounts (Google sign-in) for API key storage\n- [ ] AJS-based Firestore and Storage security rules\n- [ ] npx tjs-playground - run playground locally with LM Studio\n- [ ] Virtual subdomains for user apps (yourapp.tjs.land)\n - [ ] Wildcard DNS to Firebase\n - [ ] Subdomain routing in Cloud Function\n - [ ] Deploy button in playground\n - [ ] Public/private visibility toggle\n- [ ] Rate limiting / abuse prevention for LLM endpoint\n- [ ] Usage tracking / billing foundation (for future paid tiers)\n\n---\n\n## Completed (this session)\n\n### Project Rename\n\n- [x] Rename from tosijs-agent to tjs-lang\n- [x] Update all references in package.json, docs, scripts\n\n### Timestamp & LegalDate Utilities\n\n- [x] Timestamp - pure functions, 1-based months, no Date warts (53 tests)\n - now, from, parse, tryParse\n - addDays/Hours/Minutes/Seconds/Weeks/Months/Years\n - diff, diffSeconds/Minutes/Hours/Days\n - year/month/day/hour/minute/second/millisecond/dayOfWeek\n - toLocal, format, formatDate, formatTime, toDate\n - isBefore/isAfter/isEqual/min/max\n - startOf/endOf Day/Month/Year\n- [x] LegalDate - pure functions, YYYY-MM-DD strings (55 tests)\n - today, todayIn, from, parse, tryParse\n - addDays/Weeks/Months/Years\n - diff, diffMonths, diffYears\n - year/month/day/dayOfWeek/weekOfYear/dayOfYear/quarter\n - isLeapYear, daysInMonth, daysInYear\n - toTimestamp, toUnix, fromUnix\n - format, formatLong, formatShort\n - isBefore/isAfter/isEqual/min/max/isBetween\n - startOf/endOf Month/Quarter/Year/Week\n- [x] Portable predicate helpers: isValidUrl, isValidTimestamp, isValidLegalDate\n\n### TJS Mode System (native TJS has all modes ON by default; TS-originated code defaults to OFF)\n\n- [x] Invert mode system - native TJS enables all modes; TS-originated/AJS code defaults to JS semantics\n- [x] TjsEquals directive - structural == and != (null == undefined)\n- [x] TjsClass directive - classes callable without new\n- [x] TjsDate directive - bans Date constructor/methods\n- [x] TjsNoeval directive - bans eval() and new Function()\n- [x] TjsStrict directive - enables all of the above\n- [x] TjsSafeEval directive - includes Eval/SafeFunction for dynamic code execution\n- [x] Updated Is() for nullish equality (null == undefined)\n- [x] Added Is/IsNot tests (structural equality, nullish handling)\n- [x] TjsStandard directive - newlines as statement terminators (prevents ASI footguns)\n- [x] WASM POC - wasm {} blocks with parsing, fallback mechanism, basic numeric compilation\n- [x] Eval/SafeFunction - proper VM-backed implementation with fuel metering and capabilities\n\n### Bundle Size Optimization\n\n- [x] Separated Eval/SafeFunction into standalone module (eval.ts)\n- [x] Created core.ts - AJS transpiler without TypeScript dependency\n- [x] Fixed tjs-transpiler bundle: 4.14MB → 88.9KB (27KB gzipped)\n- [x] Runtime is now ~5KB gzipped (just Is/IsNot, wrap, Type, etc.)\n- [x] Eval adds ~27KB gzipped (VM + AJS transpiler, no TypeScript)\n- [x] TypeScript only bundled in playground (5.8MB) for real-time TS transpilation\n"
|
|
1616
|
+
"text": "# TJS-Lang TODO\n\n## Playground - Error Navigation\n\n- [ ] Test errors: click should navigate to source location\n- [ ] Console errors: click should navigate to source location\n- [ ] Error in imported module: click through to source\n\n## Playground - Module Management\n\n- [ ] Import one example from another in playground\n- [ ] Save/Load TS examples (consistency with TJS examples)\n- [ ] File name should be linked to example name\n- [ ] New example button in playground\n- [ ] UI for managing stored modules (browse/delete IndexedDB)\n- [ ] Auto-discover and build local dependencies in module resolution\n- [ ] **Wire `ModuleLoader` into the playground's `tjs()` invocation** for transpile-time cross-file `wasm function` composition (Phase 3 of the wasm-library plan). Today the playground resolves imports at runtime via the local-module store — correct but uses the \"boundary form\" with a JS↔wasm crossing per call. With a ModuleLoader, imported `wasm function`s would be composed into the consumer's own `WebAssembly.Module` at transpile time, enabling wasm-to-wasm calls (single-digit nanosecond per-call cost). The `wasm-library-consumer.md` example flags this as a known gap. See `src/lang/module-loader.ts` (already shipped) and `wasm-library-plan.md` § Phase 3.\n\n## Language Features\n\n- [x] Honest boolean coercion (TjsStandard) — `Boolean(new Boolean(false))` and friends now return false. Source rewriter wraps every truthiness context (`if`/`while`/`for`/`do`/`!`/`&&`/`||`/`?:`, `Boolean(x)` calls) with `__tjs.toBool` which unwraps boxed primitives. Always-on under `TjsStandard`. Demo: `examples/js-footguns-fixed.tjs`. Doc: `guides/footguns.md`.\n- [ ] Intra-function type safety — bring TJS to parity with TS / good linters\n - [ ] **Tier 1 (lint):** `TjsTypedLet` mode — warn/error on `let` without type annotation. Follows the `TjsNoVar` precedent (`src/lang/parser.ts:214`). Severity gated by mode (info under `TjsStandard`, error under `TjsStrict`). ~30 lines in `src/lang/linter.ts`.\n - [ ] **Tier 2 (compile-time inference):** infer `TypeDescriptor` from initializer (already have `src/lang/inference.ts`), store per-decl in scope, walk subsequent `AssignmentExpression` nodes, warn on type-incompatible reassignment. ~200–300 lines, linter-only, no codegen changes.\n - [ ] **Tier 3 (runtime checks, long-term):** rewrite `let x = e` / `x = e` in the JS emitter to `__tjs.checkType(...)` so out-of-band assignments return MonadicError. Open design questions: closed-over `let`s, uninitialized `let x`, perf cost of per-assignment call. Defer until we see how Tier 1+2 land.\n- [ ] Audit monadic-error propagation when an error is nested inside a parameter (esp. arrays)\n - Rule: a MonadicError reaching a checked boundary should surface as ONE error, not as data containing an error (e.g. `[5, <error>, 7]`).\n - Caveat: if the function never inspects the param, no error needs to fire — propagation is on-check, not eager.\n - Partial coverage today: input-validation in emitted JS scans top-level array params for an embedded MonadicError and re-propagates it (commit `3db372d`). Other paths likely miss this — return values, deeper nesting (object fields, arrays-of-arrays), function-typed params whose callbacks return arrays containing errors, etc.\n - Investigate: where does a MonadicError survive past a boundary as data? Audit `checkType` in `src/lang/runtime.ts`, the emitted-JS validation prefix in `src/lang/emitters/js.ts`, and `checkFnShape` interaction with array returns.\n- [ ] Portable Type predicates - expression-only AJS subset (no loops, no async, serializable)\n- [ ] Sync AJS / AJS-to-JS compilation - for type-checked AJS that passes static analysis, transpile to native JS with fuel injection points. Enables both type safety guarantees AND native performance for RBAC rules, predicates, etc.\n- [ ] Self-contained transpiler output (no runtime dependency)\n - Currently transpiled code references `globalThis.__tjs` for pushStack/popStack, typeError, Is/IsNot\n - Requires runtime to be installed or a stub (see playground's manual \\_\\_tjs stub)\n - Goal: TJS produces completely independent code, only needing semantic dependencies\n - Options: inline minimal runtime (~1KB), `{ standalone: true }` option, or tree-shake\n - See: src/lang/emitters/js.ts TODO comment for details\n- [x] WASM compilation at transpile time (not runtime)\n - [x] Compile wasm {} blocks during transpilation\n - [x] Embed base64-encoded WASM bytes in output\n - [x] Include WAT disassembly as comment for debugging/learning\n - [x] Self-contained async instantiation (no separate compileWasmBlocksForIframe)\n- [x] Expand WASM support beyond POC\n - [x] For loops with numeric bounds\n - [x] Conditionals (if/else)\n - [x] Local variables within block\n - [x] Typed array access (Float32Array, Float64Array, Int32Array, Uint8Array)\n - [x] Memory operations\n - [x] Continue/break statements\n - [x] Logical expressions (&& / ||)\n - [x] Math functions (sqrt, abs, floor, ceil, min, max, sin, cos, log, exp, pow)\n- [x] WASM SIMD support (v128/f32x4)\n - 12 f32x4 intrinsics: load, store, splat, extract_lane, replace_lane, add, sub, mul, div, neg, sqrt\n - Explicit intrinsic approach (users call f32x4\\_\\* in wasm blocks)\n - Disassembler handles 0xfd prefix with LEB128 sub-opcodes\n - 16-byte aligned memory for v128 loads/stores\n - Demos: starfield SIMD rotation, vector search cosine similarity\n- [ ] WASM SIMD vector search (batteries)\n - Replace JS vectorSearch battery with WASM SIMD implementation\n - SIMD cosine similarity demonstrated in vector search demo\n - TODO: integrate as a battery atom with auto-detect + fallback\n\n## Editor\n\n- [ ] Embedded AJS syntax highlighting\n\n## Documentation / Examples\n\n- [ ] Create an endpoint example\n- [ ] Fold docs and tests into one panel, with passing tests collapsed by default (ts -> tjs inserts test; tjs -> js turns test blocks into documentation along with outcomes).\n- [ ] Dim/hide the preview tab if nothing ever changed it\n- [ ] Single source of truth for version number. I note the badge displayed in console is not matching the version. No hardwired versions -- version number is pulled from package.json and written to version.ts somewhere and that is the single source of truth.\n\n## Infrastructure\n\n- [ ] Make playground components reusable for others\n- [ ] Web worker for transpiles (freezer - not needed yet)\n- [x] Retarget Firebase as host platform (vs GitHub Pages)\n- [ ] Universal LLM endpoint with real LLMs (OpenAI, Anthropic, etc.)\n- [ ] ESM-as-a-service: versioned library endpoints\n- [ ] User accounts (Google sign-in) for API key storage\n- [ ] AJS-based Firestore and Storage security rules\n- [ ] npx tjs-playground - run playground locally with LM Studio\n- [ ] Virtual subdomains for user apps (yourapp.tjs.land)\n - [ ] Wildcard DNS to Firebase\n - [ ] Subdomain routing in Cloud Function\n - [ ] Deploy button in playground\n - [ ] Public/private visibility toggle\n- [ ] Rate limiting / abuse prevention for LLM endpoint\n- [ ] Usage tracking / billing foundation (for future paid tiers)\n\n---\n\n## Completed (this session)\n\n### Project Rename\n\n- [x] Rename from tosijs-agent to tjs-lang\n- [x] Update all references in package.json, docs, scripts\n\n### Timestamp & LegalDate Utilities\n\n- [x] Timestamp - pure functions, 1-based months, no Date warts (53 tests)\n - now, from, parse, tryParse\n - addDays/Hours/Minutes/Seconds/Weeks/Months/Years\n - diff, diffSeconds/Minutes/Hours/Days\n - year/month/day/hour/minute/second/millisecond/dayOfWeek\n - toLocal, format, formatDate, formatTime, toDate\n - isBefore/isAfter/isEqual/min/max\n - startOf/endOf Day/Month/Year\n- [x] LegalDate - pure functions, YYYY-MM-DD strings (55 tests)\n - today, todayIn, from, parse, tryParse\n - addDays/Weeks/Months/Years\n - diff, diffMonths, diffYears\n - year/month/day/dayOfWeek/weekOfYear/dayOfYear/quarter\n - isLeapYear, daysInMonth, daysInYear\n - toTimestamp, toUnix, fromUnix\n - format, formatLong, formatShort\n - isBefore/isAfter/isEqual/min/max/isBetween\n - startOf/endOf Month/Quarter/Year/Week\n- [x] Portable predicate helpers: isValidUrl, isValidTimestamp, isValidLegalDate\n\n### TJS Mode System (native TJS has all modes ON by default; TS-originated code defaults to OFF)\n\n- [x] Invert mode system - native TJS enables all modes; TS-originated/AJS code defaults to JS semantics\n- [x] TjsEquals directive - structural == and != (null == undefined)\n- [x] TjsClass directive - classes callable without new\n- [x] TjsDate directive - bans Date constructor/methods\n- [x] TjsNoeval directive - bans eval() and new Function()\n- [x] TjsStrict directive - enables all of the above\n- [x] TjsSafeEval directive - includes Eval/SafeFunction for dynamic code execution\n- [x] Updated Is() for nullish equality (null == undefined)\n- [x] Added Is/IsNot tests (structural equality, nullish handling)\n- [x] TjsStandard directive - newlines as statement terminators (prevents ASI footguns)\n- [x] WASM POC - wasm {} blocks with parsing, fallback mechanism, basic numeric compilation\n- [x] Eval/SafeFunction - proper VM-backed implementation with fuel metering and capabilities\n\n### Bundle Size Optimization\n\n- [x] Separated Eval/SafeFunction into standalone module (eval.ts)\n- [x] Created core.ts - AJS transpiler without TypeScript dependency\n- [x] Fixed tjs-transpiler bundle: 4.14MB → 88.9KB (27KB gzipped)\n- [x] Runtime is now ~5KB gzipped (just Is/IsNot, wrap, Type, etc.)\n- [x] Eval adds ~27KB gzipped (VM + AJS transpiler, no TypeScript)\n- [x] TypeScript only bundled in playground (5.8MB) for real-time TS transpilation\n"
|
|
1581
1617
|
},
|
|
1582
1618
|
{
|
|
1583
1619
|
"title": "TJS: Typed JavaScript",
|
|
@@ -1603,6 +1639,18 @@
|
|
|
1603
1639
|
"path": ".haltija.md",
|
|
1604
1640
|
"text": "# Using Haltija with tjs-lang\n\nThis project uses [Haltija](https://github.com/tonioloewald/haltija) for AI-assisted browser debugging and testing.\n\n## Quick Start\n\n```bash\n# Terminal 1: Start tjs-lang playground\nbun run dev\n\n# Terminal 2: Start haltija\ncd ~/Documents/GitHub/haltija\nbunx haltija\n```\n\nThen add this one-liner to your browser console or the app's entry point:\n\n```javascript\n;/^localhost$|^127\\./.test(location.hostname) &&\n import('http://localhost:8700/dev.js')\n```\n\n## Why We Use It\n\nBefore haltija, debugging frontend issues was painful:\n\n1. Describe what's wrong → AI guesses → doesn't work → repeat\n2. Screenshots, copy-pasted HTML, \"it's still not working\"\n3. Context lost, velocity destroyed\n\nWith haltija:\n\n1. AI sees the actual DOM via `/tree`\n2. Understands immediately: \"the selector expects `.tab-bar > .active` but structure is `.tab-bar > .tab-container > .tab.active`\"\n3. Fixes it in one shot\n\n## Common Commands\n\n```bash\n# What page am I on?\ncurl http://localhost:8700/location\n\n# See DOM structure\ncurl -X POST http://localhost:8700/tree -d '{\"selector\":\"body\",\"depth\":3}'\n\n# Find all buttons\ncurl -X POST http://localhost:8700/inspectAll -d '{\"selector\":\"button\"}'\n\n# Click something\ncurl -X POST http://localhost:8700/click -d '{\"selector\":\"#submit\"}'\n\n# Watch for DOM changes\ncurl -X POST http://localhost:8700/mutations/watch -d '{\"preset\":\"smart\"}'\ncurl http://localhost:8700/messages\n\n# Full docs\ncurl http://localhost:8700/docs\n```\n\n## Semantic Events (for debugging)\n\nInstead of raw DOM events, haltija captures meaningful actions:\n\n- `input:typed` - \"user typed 'hello@example.com'\"\n- `interaction:click` - \"user clicked Submit button\"\n- `navigation:navigate` - \"user went from /login to /dashboard\"\n\n```bash\ncurl -X POST http://localhost:8700/events/watch -d '{\"preset\":\"interactive\"}'\ncurl http://localhost:8700/events\n```\n\n## Reference Docs\n\n```bash\n# List available docs\ncurl http://localhost:8700/docs/list\n\n# UX anti-patterns database\ncurl http://localhost:8700/docs/ux-crimes\n```\n\n## Repo Location\n\nHaltija source: `~/Documents/GitHub/haltija`\n"
|
|
1605
1641
|
},
|
|
1642
|
+
{
|
|
1643
|
+
"title": "WASM in TJS",
|
|
1644
|
+
"filename": "DOCS-WASM.md",
|
|
1645
|
+
"path": "DOCS-WASM.md",
|
|
1646
|
+
"text": "# WASM in TJS\n\nCanonical reference for WebAssembly in TJS — what you can write, how it's\ncompiled, and how cross-file composition works.\n\nFor a 5-minute introduction, start with [`docs/WASM-QUICKSTART.md`](docs/WASM-QUICKSTART.md).\nFor the cross-file libraries design rationale, see [`wasm-library-plan.md`](wasm-library-plan.md).\n\n---\n\n## Two flavors\n\nTJS supports two related ways of writing WebAssembly:\n\n| Flavor | Syntax | Use when |\n|---|---|---|\n| **Inline block** | `wasm { ... }` inside a regular function | One-off acceleration of a hot inner loop in a single function; you want a JS fallback |\n| **Top-level declaration** | `wasm function NAME(...): T { ... }` at module scope | Reusable kernel that other files (or this one) may want to import |\n\nBoth flavors compile to the same `WebAssembly.Module` per file (one module per\nfile, multiple exported functions), and both share one linear memory.\n\n### Inline blocks\n\nThe original form. Captures (free variables) are auto-detected from the\nenclosing scope and become wasm function parameters. Optional `fallback { ... }`\nprovides a JS path when SIMD or other features are unavailable.\n\n```tjs\nfunction double(arr: Float32Array, len: 0) {\n wasm {\n for (let i = 0; i < len; i++) {\n arr[i] = arr[i] * 2.0\n }\n } fallback {\n for (let i = 0; i < len; i++) arr[i] *= 2\n }\n}\n```\n\nSee `docs/WASM-QUICKSTART.md` for a thorough walkthrough.\n\n### Top-level declarations\n\nThe Phase 1+ form. Declared with explicit parameters and return type, at module\nscope. The function name is what consumers (in this file or other files) call.\nNo `fallback {}` block — these are pure wasm kernels.\n\n```tjs\nexport wasm function dot(a: Float32Array, b: Float32Array, n: i32): f64 {\n let acc = f32x4_splat(0.0)\n for (let i = 0; i < n; i += 4) {\n let off = i * 4\n let av = f32x4_load(a, off)\n let bv = f32x4_load(b, off)\n acc = f32x4_add(acc, f32x4_mul(av, bv))\n }\n return f32x4_extract_lane(acc, 0)\n + f32x4_extract_lane(acc, 1)\n + f32x4_extract_lane(acc, 2)\n + f32x4_extract_lane(acc, 3)\n}\n```\n\nThe transpiler replaces the declaration with a regular JS wrapper that forwards\nto the wasm export. Callers see a normal JavaScript function.\n\n---\n\n## Parameter types\n\n`wasm function` parameters use wasm type names, **not** TJS example-based\nsyntax. (Inside a `wasm function`, `name: 'hello'` would be a type error, not\nan example value.)\n\n| Annotation | WASM type | Notes |\n|---|---|---|\n| `name: i32` | `i32` | 32-bit signed integer |\n| `name: i64` | `i64` | 64-bit signed integer |\n| `name: f32` | `f32` | 32-bit float |\n| `name: f64` | `f64` | 64-bit float |\n| `name: number` | `f64` | Alias |\n| `name: int` | `i32` | Alias |\n| `name: Float32Array` | `i32` (pointer) | Pointer to f32 array in linear memory |\n| `name: Float64Array` | `i32` (pointer) | Pointer to f64 array |\n| `name: Int32Array` | `i32` (pointer) | Pointer to i32 array |\n| `name: Uint8Array` | `i32` (pointer) | Pointer to u8 array |\n\nThe JS-side wrapper auto-marshals typed arrays: if you pass a regular\n`Float32Array`, the wrapper copies it into wasm memory and back; if you pass a\n`wasmBuffer(Float32Array, n)`, the wrapper detects the shared backing buffer\nand skips the copy (zero-copy fast path).\n\n## Return types\n\n**Current backend limitation:** wasm functions return `f64` or void only.\n\n`: f64` and omitted-return both work today. Other return types (`: i32`,\n`: f32`, `: v128`) are parsed but not yet driving the emitted bytecode; the\nbackend always emits f64. This is a known limitation tracked in\n`wasm-library-plan.md` as \"Phase 1.5 work.\"\n\nPractically: if your function would naturally return f32 (e.g., a SIMD reduce),\nreturn f64 and let the caller downcast. If you need integer results, return\nf64 and cast on the JS side.\n\n## SIMD intrinsics\n\nv1 of the wasm-library plan **requires SIMD baseline**. All the f32x4\nintrinsics from inline blocks work in `wasm function` bodies:\n\n`f32x4_splat`, `f32x4_load`, `f32x4_store`, `f32x4_extract_lane`,\n`f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`,\n`f32x4_neg`, `f32x4_sqrt`.\n\nLengths must be multiples of 4 for SIMD loops. Callers pad as needed.\n\n---\n\n## Memory discipline\n\n**JS allocates every byte; wasm functions never allocate.**\n\nThis is the load-bearing invariant of the whole system. Wasm functions receive\ntyped-array pointers and operate on memory regions reachable from arguments.\nThe JS-side wrapper (or the caller, for `wasmBuffer`-backed arrays) is\nresponsible for allocation and lifetime.\n\n### `wasmBuffer()`\n\nAllocate a typed array backed by wasm linear memory:\n\n```tjs\nconst xs = wasmBuffer(Float32Array, 50000)\nxs[0] = 1.0 // writes directly into wasm memory\ndot(xs, ys, 50000) // zero-copy: wrapper detects the shared buffer\n```\n\n### Bump allocator caveat\n\n`wasmBuffer` uses a bump allocator with **no free** — allocations persist for\nprogram lifetime. This is deliberate: it eliminates lifetime/ownership\nquestions across library boundaries. The cost: long-running programs that\nallocate per-call would leak.\n\nThe stdlib API shape (Phase 5+) follows from this: every function that would\nproduce a buffer takes an `out` parameter instead, so the caller allocates\nonce and reuses. Functions returning scalars (`dot`, `norm_sq`) are fine\nas-is.\n\n### What this eliminates\n\n- Cross-block allocator coordination\n- Lifetime / ownership across library boundaries\n- Fragmentation\n- Inline-vs-boundary semantic divergence\n\n---\n\n## Cross-file composition (Phase 3)\n\nA file can declare `wasm function`s; another file can import and use them.\nThe import is resolved **at transpile time** and the imported function's body\nis composed into the consumer's wasm module as a local function — no\nJS↔wasm boundary on intra-module calls.\n\n### Library file\n\n```tjs\n// my-lib/index.tjs\nexport wasm function dot(a: Float32Array, b: Float32Array, n: i32): f64 {\n // ... SIMD dot product ...\n}\n```\n\n### Consumer file\n\n```tjs\n// app.tjs\nimport { dot } from './my-lib/index.tjs'\n\nconst a = wasmBuffer(Float32Array, 128)\nconst b = wasmBuffer(Float32Array, 128)\n// ... populate ...\nconst d = dot(a, b, 128) // calls into the composed wasm module\n```\n\nThe consumer's transpiled output contains the wasm bytes for `dot` as a local\nfunction in its single `WebAssembly.Module`. No separate library bundle is\nfetched at runtime.\n\n### Enabling composition\n\nThe composition pass is opt-in: pass a `ModuleLoader` to `tjs()` or\n`transpileToJS()`. Without one, imports are preserved verbatim and the\nruntime resolves them as before.\n\n```typescript\nimport { tjs, ModuleLoader } from 'tjs-lang/lang'\n\nconst loader = new ModuleLoader({ baseDir: '/path/to/project' })\nconst result = tjs(appSource, {\n moduleLoader: loader,\n filename: '/path/to/project/app.tjs',\n})\n```\n\n---\n\n## Two distribution forms\n\nSame source produces two outputs depending on how the library is consumed:\n\n**Composed form** (tjs-to-tjs, when the consumer transpiles with a loader):\n- Library's wasm functions become local exports in the consumer's module\n- One `WebAssembly.compile` per consumer file\n- Intra-module calls between the consumer's own wasm and the imported wasm are\n pure wasm calls (no JS↔wasm boundary)\n\n**Boundary form** (when the library is consumed as a published `.js`):\n- Library transpiles to a self-contained `.js` with embedded wasm + JS wrappers\n- Consumer imports the `.js` via normal ESM\n- Each call from JS into a library wrapper crosses the JS↔wasm boundary\n- Per-call cost is single-digit nanoseconds for small functions — usually\n negligible after JIT warmup\n\nBoth forms compile from the same TJS source and produce identical results.\n\nA published library typically ships both:\n\n```\nmy-lib/\n src/\n index.tjs ← TJS source, used by tjs consumers (composed form)\n dist/\n index.js ← Transpiled, used by everyone else (boundary form)\n```\n\n`package.json` `exports`:\n```json\n\"exports\": {\n \"./my-lib\": {\n \"bun\": \"./src/index.tjs\",\n \"default\": \"./dist/index.js\"\n }\n}\n```\n\n---\n\n## Purity and the `(!)` unsafe marker\n\nA `wasm function` may not call any host function. This rule is enforced by\nthe backend: the bytecode builder has no support for emitting host imports,\nso any attempt (e.g. `Math.sin`, which would require a JS-side trampoline)\nfails at compile time. Math functions with wasm intrinsics — `sqrt`, `abs`,\n`floor`, `ceil`, `min`, `max` — work fine.\n\nThe `(!)` marker is **reserved syntax** for a future \"unsafe wasm function\"\nvariant that would relax purity (allow host imports, side-effecting globals).\nWriting `wasm function (! name(...)` today produces a clear error directing\nyou to either remove the bang or wait for the unsafe variant to be implemented.\n\n---\n\n## tjs-lang/linalg\n\nThe first stdlib library built on this infrastructure ships two SIMD\nkernels:\n\n```tjs\nimport { dot, norm_sq } from 'tjs-lang/linalg'\n\nconst a = wasmBuffer(Float32Array, 128)\nconst b = wasmBuffer(Float32Array, 128)\n// ... populate ...\nconst cos = dot(a, b, 128) / Math.sqrt(norm_sq(a, 128) * norm_sq(b, 128))\n```\n\n**Preconditions:** `n` must be a multiple of 4 (the SIMD lane width). Pad\nshorter vectors with zeros.\n\n**Future additions** (planned, not yet shipped): `norm`, `normalize`,\n`add`, `sub`, `scale`, `lerp`, `matmul`, `transpose`, `cross`, `quat_mul`,\n`look_at`, `perspective`. See `wasm-library-plan.md` § 5.\n\n---\n\n## Current limitations (v1)\n\nThese are real constraints worth knowing about. Most are scoped follow-ups\nfrom the wasm-library plan.\n\n- **Return types:** `f64` and void only. `i32`, `f32`, `v128` returns are\n parsed but not yet emitted as the bytecode return type.\n- **SIMD lengths:** SIMD kernels assume `n % 4 == 0`. No tail-loop yet.\n- **Bump allocator:** `wasmBuffer` allocations persist forever. Use `out`\n parameters for repeated allocations; design APIs around buffer reuse.\n- **Unsafe marker reserved:** `(!)` syntax is parsed but rejected pending the\n unsafe variant implementation (Phase 7).\n- **One file = one module:** the consumer's transpiled output has exactly one\n `WebAssembly.Module`, period. Cross-module wasm-to-wasm calls aren't a thing\n — they're composed at the source level into the same module.\n- **Boundary form per-call cost:** when a library is consumed via its\n transpiled `.js`, each call crosses the JS↔wasm boundary. Engines inline\n small functions at JIT time, but the boundary still costs single-digit\n nanoseconds. Composed form (tjs-to-tjs) avoids this.\n- **Library duplication across `.js` deps:** if two third-party libraries both\n embed `linalg` in their `.js` distribution, the consumer carries two copies.\n Matches JS ecosystem norms — a future \"shared wasm module registry\" could\n dedup at the cost of new infrastructure.\n\n---\n\n## References\n\n- `docs/WASM-QUICKSTART.md` — 5-minute introduction (inline blocks)\n- `wasm-library-plan.md` — Design rationale and phased implementation plan\n- `CLAUDE-TJS-SYNTAX.md` § \"WASM Blocks\" — Inline block reference\n- `src/linalg/index.tjs` — First stdlib library, readable as source\n"
|
|
1647
|
+
},
|
|
1648
|
+
{
|
|
1649
|
+
"title": "WASM Libraries in tjs — Design & Plan",
|
|
1650
|
+
"filename": "wasm-library-plan.md",
|
|
1651
|
+
"path": "wasm-library-plan.md",
|
|
1652
|
+
"text": "# WASM Libraries in tjs — Design & Plan\n\n## Phase 0 status: COMPLETE (2026-05-04)\n\nAssumptions A1–A7 verified against `src/lang/wasm.ts`, `src/lang/emitters/js-wasm.ts`,\n`src/lang/parser.ts`, `src/lang/index.ts`, `src/lang/transpiler.test.ts`,\n`src/bun-plugin/tjs-plugin.ts`, and `CLAUDE-TJS-SYNTAX.md`. Two assumptions came back\nfalse (A3, A4) and one partly false (A2). The plan below reflects those findings —\nsee \"Implementation plan\" for the new Phase 0.5 / Phase 0.75 prereqs.\n\n## Base assumptions (verified)\n\n| # | Assumption | Status | Finding (with citation) |\n|---|---|---|---|\n| A1 | `wasm {}` blocks today are inline expressions inside a tjs function, not standalone exportable units. There is no `wasm function name(...)` declaration syntax yet. | ✅ true | Current form is inline `wasm { ... }` block inside a regular function, with optional `fallback { ... }` (`wasm.test.ts:1518`). Captures (free variables) become wasm function params automatically (`emitters/js-wasm.ts:89`). The `const x = wasm(...){...}` expression form mentioned in `CLAUDE-TJS-SYNTAX.md:381` doesn't appear in tests — likely vestigial. **Phase 1 (declaration syntax) is real new work.** |\n| A2 | All `wasm {}` content in a tjs source file is currently compiled into a single `WebAssembly.Module` per file. Linear memory is shared within a file. | ⚠️ partly false | Each `wasm {}` block compiles to its **own** `WebAssembly.Module` — they're separate modules instantiated independently (`js-wasm.ts:97-103`). They *do* share one linear memory (64MB / 1024 pages, `js-wasm.ts:92`) by importing it via `{env: {memory: __wasmMem}}`. So: **N modules per file, 1 memory per file.** Need a new \"consolidate to one module\" step — see Phase 0.5. |\n| A3 | `wasmBuffer()` is the JS-side primitive for getting a typed-array view backed by the wasm module's linear memory. Allocation lifetime is tied to JS GC. | ❌ false on lifetime | `wasmBuffer()` uses a **bump allocator** with no free (`__woff` only increments — `js-wasm.ts:94`). Allocations are permanent for program lifetime. JS owns the *view* (typed array), but the underlying bytes never get reclaimed — there is no GC link. `CLAUDE-TJS-SYNTAX.md:465` confirms: \"Uses a bump allocator — allocations persist for program lifetime (no deallocation).\" **Implication: §6 wording is wrong, and the stdlib API must be allocate-once-mutate-in-place.** |\n| A4 | The transpiler can resolve `import` statements at transpile time and read the imported `.tjs` source. Cross-file dependency tracking already exists for non-wasm symbols. | ❌ false | Transpiler preserves imports verbatim — no cross-file resolution. `transpiler.test.ts:355` confirms: `expect(result.code).toContain(\"import { add } from './math.tjs'\")`. The bun plugin (`src/bun-plugin/tjs-plugin.ts`) re-transpiles each `.tjs` file independently at runtime when imported. **There is no transpile-time module loader.** This is the real long pole — see Phase 0.75. |\n| A5 | Transpiled `.js` output already supports embedding `WebAssembly.Module` bytes (base64 or fetched). This is how single-file wasm blocks reach the browser/node today. | ✅ true | `js-wasm.ts:36-44` produces base64-embedded wasm in self-contained JS. The \"boundary distribution form\" the plan describes IS the current form. |\n| A6 | `(! ...)` after the open paren is the established tjs unsafe-function marker. Same semantics here. | ✅ notation only | Fine. |\n| A7 | tjs has no existing multi-module wasm linking, no shared `WebAssembly.Memory` import, no allocator. Greenfield. | ✅ true | No `WebAssembly.Table`, no real linking, no cross-module calls. Memory is shared because it's imported into each module separately — but each module is otherwise standalone. Greenfield for composition. |\n\n---\n\n## Goals\n\n1. **Reusable wasm functions across files.** `import { dot } from 'tjs-lang/linalg'` — and it works.\n2. **Zero ceremony for consumers.** No init, no memory management, no awareness of the wasm/JS boundary. Same call site whether the consumer is tjs or plain JS.\n3. **Two-tier performance.** tjs consumers compose imported wasm functions into a single module (no JS↔wasm boundary on intra-library calls; engine JIT inlines hot paths). Non-tjs consumers get a normal JS lib with a baked-in `WebAssembly.Module`. Same source, two distribution forms.\n4. **Stdlib as educational artifact.** Linalg first. Each function readable as TJS wrapper → scalar wasm → SIMD wasm progression.\n5. **Safety by construction.** Wasm functions are pure compute. The transpiler enforces it. Escape hatch exists (`(! ...)`) but is rare and visible.\n\n## Canonical end-to-end demo\n\nThe acceptance test for Phases 1, 0.75, and 3 — and the conceptual goal of the whole plan — is the **vector search refactor**.\n\nThe existing playground example `guides/examples/tjs/wasm-vector-search.md` computes cosine similarity over a corpus of f32 embeddings inside a single inline `wasm {}` block (`dot`, `magA`, `magB` all in one big SIMD loop). That's the baseline.\n\nThe cross-file version replaces the kernel body with three calls into `tjs-lang/linalg`:\n\n```ts\nimport { dot, norm_sq } from 'tjs-lang/linalg'\n\nfunction simdSearch(corpus: Ptr<f32>, query: Ptr<f32>, count: i32, dim: i32): i32 {\n // ... outer loop over rows of corpus ...\n const d = dot(query, corpusRow, dim)\n const ma = norm_sq(query, dim)\n const mb = norm_sq(corpusRow, dim)\n const score = d / Math.sqrt(ma * mb)\n // ... track best score, return best index ...\n}\n```\n\n**Status (2026-05-14): three of four criteria proven; perf criterion revised.** See `src/linalg/vector-search.bench.test.ts` for the working comparison.\n\n1. ✅ **Correctness.** Library version returns the same `bestIdx` as the inline baseline across the configs measured (500×128, 500×256, 2000×128). Asserted in the bench test.\n2. ✅ **Performance.** *Originally measured as 1.5–10× slower for the composed-JS-loop form, then resolved by Phase 1.5.* The 3-way `vector-search.bench.test.ts` benchmark measures three implementations of the same workload — inline single-block (no boundary crossings), composed-JS-loop (2 crossings per row), and composed-WASM-loop (single entry crossing for the whole workload, intra-module wasm-to-wasm calls in the loop). Observed ratios:\n\n | Config | Inline | Composed-JS | Composed-WASM |\n |---|---|---|---|\n | 500×128 | baseline | 5.47× slower | **1.00× (parity)** |\n | 500×256 | baseline | 1.46× slower | **0.27× (3.7× faster)** |\n | 2000×128 | baseline | 11.75× slower | **1.01× (parity)** |\n\n The composed-WASM-loop form matches inline performance within engine variance — and sometimes beats it (likely because the JIT inlines small wasm-internal calls more aggressively than the single-block inline form's larger function body). **The abstraction is genuinely free when the consumer's outer loop is also `wasm function`.**\n\n Required to make this work: `dot_at(corpus, startIdx, query, n)` and `norm_sq_at(arr, startIdx, n)` variants in `tjs-lang/linalg` (Float32Array params lower to i32 pointers in our wasm ABI; the `_at` variants let an in-wasm outer loop pass row slices without constructing a subarray view, which we can't do inside wasm). These are shipped alongside the benchmark.\n3. ✅ **Module shape.** The consumer's emitted wasm module contains `dot` and `norm_sq` as local functions, not imports. `WebAssembly.Module.imports()` shows zero function imports. Asserted in the Phase 3 tests.\n4. ✅ **Boundary form works.** `tjs-lang/linalg` distributed as transpiled `.js` (Phase 4) works for non-tjs consumers; boundary and composed forms return identical numeric results. Asserted in the Phase 4 + Phase 5 tests.\n\nThe cross-file wasm story is real: composable small-primitive APIs *and* inline-baseline performance, simultaneously. The choice between JS outer loop and wasm outer loop is genuinely up to the consumer — there's no architectural force pushing toward macro-kernels. (Macro-kernels remain a valid library design for callers who can't or don't want to write their outer loop as wasm, but they're no longer the *only* way to recover performance.)\n\n---\n\n## Design\n\n### 1. Compilation model\n\n**One `WebAssembly.Module` per tjs source file.** All `wasm {}` blocks and `wasm function` declarations in a file lower into the same module. Single linear memory, shared `wasmBuffer`, shared function table (if/when needed).\n\nToday this is N modules per file sharing one memory; Phase 0.5 unifies to one module per file. After that, the rest of the design lands cleanly on the existing memory model.\n\nCross-file: each `.tjs` consumer file produces *its own* module. Library wasm functions are composed into the consumer's module at transpile time, not linked at runtime.\n\n### 2. Authoring a wasm library\n\n```ts\n// tjs-lang/linalg/vec.tjs\n\n/** Dot product of two f32 vectors. */\nexport wasm function dot(a: Ptr<f32>, b: Ptr<f32>, n: i32): f32 {\n // ... wasm body, SIMD (v1 requires baseline SIMD; see §7)\n}\n\n/** Sum of squares. */\nexport wasm function norm_sq(a: Ptr<f32>, n: i32): f32 {\n // ... can call dot internally — same module after composition\n}\n\n// TJS-facing wrapper. This is what `import { dot } from ...` actually returns.\n// The wasm function is module-internal (auto-mangled to e.g. `_dot`) and the\n// JS-facing wrapper is what consumers see and import.\nexport function dot(a: F32Array, b: F32Array): 0.0 {\n if (a.length !== b.length) return MonadicError('length mismatch')\n return _dot(a, b, a.length)\n}\n```\n\n**Pointer typing:** `Ptr<T>` is a real type. It lowers to `i32` in wasm but carries `T` (the element type) through the type system, so the JS-facing wrapper knows what typed-array view to construct. The existing `Float32Array → i32 ptr` mapping in `js-wasm.ts:89` already does the underlying lowering — `Ptr<f32>` formalizes it. Naming conventions like `aPtr` are not relied upon.\n\n**Metadata:** `wasm function`s appear in `__tjs` metadata exactly like regular functions — signature, source location, the works. Introspection works uniformly across both.\n\nConstraints enforced on `wasm function`:\n\n- No allocator calls. No `malloc`-style anything.\n- No reads/writes to module-level globals (other than reading constants).\n- No mutation outside memory regions reachable from arguments.\n- Calls to other `wasm function`s in the same library are fine (they end up in the same composed module and call each other directly).\n\n**Practical purity check (simplified from earlier draft):** a non-`!` `wasm function` may not import or call any host function. That's the same constraint, expressed at the syntactic boundary the parser already enforces — no static analysis of \"allocator-shaped intrinsics\" needed.\n\nViolations are transpile errors with a hint to the escape hatch.\n\n### 3. Escape hatch\n\n```ts\nexport wasm function weird_thing(! aPtr: i32, n: i32): i32 {\n // unsafe: may touch globals, may use allocator, transpiler trusts you\n}\n```\n\nTwo consequences of `(! ...)` on a wasm function:\n\n1. **Never composed.** The function is *not* emitted into the consumer's module. Instead, it ships as its own separate `WebAssembly.Module` and is called across the wasm/JS boundary, even when the consumer is tjs. This matters because unsafe functions may carry module-level state (allocator heap, globals) — composing them into each consumer's module would give every consumer its own private copy of that state, with subtly different behavior depending on who imported what. Keeping unsafe functions in their own module means there's one shared instance with one consistent state.\n2. **Visible at import.** The transpiler refuses to import `weird_thing` without acknowledging unsafety:\n\n```ts\nimport { weird_thing! } from 'some-lib/dsp' // bang at import = consumer consent\n```\n\nRationale: the user pays a complexity tax (writing `!`), but in return gets predictable behavior and a clear audit trail of where unsafety enters the program.\n\n**Reserve the syntax in v1; defer implementation.** YAGNI until the stdlib hits a wall. Linalg as designed will not.\n\n### 4. Import resolution and module composition\n\n> **Design note (revised):** earlier drafts called this \"inlining\" and described\n> splicing imported function bodies into the consumer's module at the bytecode\n> level. That was unnecessarily complex. The actual operation is *module\n> composition*: imported `wasm function`s are emitted as ordinary functions\n> inside the consumer's single module, with cross-file references rewritten to\n> local function indices. The wasm engine's JIT (V8/SpiderMonkey/JSC) handles\n> any actual body inlining at runtime, and intra-module call overhead is\n> single-digit nanoseconds after warmup. We do not try to beat the JIT.\n\nWhen tjs consumer A imports `dot` from library B:\n\n1. Transpiler reads B's source (or its sidecar metadata file). **Prerequisite: Phase 0.75.**\n2. Identifies `dot` as a wasm function.\n3. Walks `dot`'s call graph within B, transitively, collecting all reachable `wasm function` declarations.\n4. Emits each reached function as a regular function inside A's single `WebAssembly.Module`. Rewrites `call $linalg_dot`-style references to local function indices in the composed module. Renames as needed to avoid collisions.\n5. Generates the same JS-facing wrapper as B's source declares.\n\nDeduplication falls out for free: if A imports both `stats` and `linalg`, and `stats` internally calls `linalg/dot`, the symbol resolves once and `dot` is emitted once.\n\nWhen the consumer is **plain JS** (or has only the transpiled `.js` of B available):\n\n1. No transpile step on consumer side.\n2. B's transpiled `.js` already contains a baked `WebAssembly.Module` and JS wrappers.\n3. Consumer imports the JS wrappers normally. Calls cross the wasm/JS boundary as usual.\n4. Functionally identical, ~10–100ns per-call slower depending on argument shape.\n\nThe two distribution forms are now literally the same wasm module bytes with different wrappers around them — boundary-mode wrappers go through `WebAssembly.Instance.exports`, in-module wrappers call the local function index directly. \"Same source, two forms\" is true at the bytecode level, not just semantically.\n\n### 5. Distribution\n\nA published library ships **both forms**:\n\n```\ntjs-lang/linalg/\n src/\n vec.tjs ← TJS source for tjs consumers (gets module composition)\n matrix.tjs\n dist/\n index.js ← Transpiled JS with embedded WASM (everyone else)\n index.d.ts\n```\n\n`package.json` `exports` field routes:\n- tjs transpiler → prefers `src/*.tjs`\n- everything else → resolves to `dist/index.js`\n\n### 6. Memory discipline (the rule that makes it all work)\n\n**JS allocates every byte; wasm functions never allocate.** Wasm functions receive `(ptr, len, ...)` and operate on memory regions reachable from arguments. The TJS wrapper allocates output and scratch buffers via `wasmBuffer()`, calls the wasm function, and returns a typed-array view onto the result.\n\n**Important:** `wasmBuffer()` is a **bump allocator** with no free (`js-wasm.ts:94`, `CLAUDE-TJS-SYNTAX.md:465`). Allocations persist for program lifetime. Because of this, the stdlib API is **allocate-once-mutate-in-place**: every operation that produces a buffer takes an `out` parameter rather than allocating a new one. This matches gl-matrix and the performance-oriented JS linalg ecosystem.\n\n```ts\n// Linalg API shape — out parameter, no per-call allocation\nadd(a, b, out) // out is preallocated by caller, mutated in place\nmatmul(a, b, out)\ndot(a, b) // returns scalar — no buffer needed\n```\n\nAllocate-per-call APIs (`const result = add(a, b)`) are explicitly *not* offered. That convenience would silently leak memory in any long-running program. The price of zero-cost zero-copy is that the caller manages buffers — it's the right tradeoff for a compute kernel library, the wrong one for general-purpose code.\n\nThe \"JS allocates\" invariant eliminates:\n- Cross-block allocator coordination\n- Lifetime / ownership questions across library boundaries\n- Fragmentation across imported libraries\n- Inline-vs-boundary semantic divergence\n- A whole category of teaching material the stdlib has no business teaching\n\n### 7. SIMD baseline\n\n**v1 requires SIMD.** Wasm SIMD has been stable in V8 (Chrome 91, May 2021), SpiderMonkey (Firefox 89), and JSC (Safari 16.4) for years. Writing a non-SIMD scalar fallback alongside every SIMD function would double the maintenance surface and educational complexity for a vanishingly small audience. If this proves wrong — or if a runtime emerges where this matters — we revisit; the cost of changing later is lower than the cost of carrying scalar duplicates from the start.\n\nThe three-layer educational story (TJS wrapper → scalar wasm → SIMD wasm) is preserved in source for *readability* — authors write the scalar version first, then the SIMD version side-by-side. Only the SIMD path ships as runtime code.\n\n---\n\n## Stdlib v1: `tjs-lang/linalg`\n\nInitial surface area, sized to be \"useful, not exhaustive\":\n\n| Group | Functions |\n|---|---|\n| Vector | `dot`, `norm`, `normalize`, `add`, `sub`, `scale`, `lerp` |\n| Matrix | `matmul`, `transpose`, `identity`, `inverse_3x3`, `inverse_4x4` |\n| 3D-specific | `cross`, `quat_mul`, `mat4_from_quat`, `look_at`, `perspective` |\n\nEach ships with three layers visible in source:\n\n1. TJS wrapper (ergonomic API, type-checked, takes `out` buffer)\n2. Scalar wasm function (the loop, readable)\n3. SIMD wasm function (the optimized version, behind a feature check)\n\nPlayground examples link these three side-by-side as the educational artifact.\n\n---\n\n## Implementation plan\n\n### ✅ Phase 0 — verify assumptions (½ day, complete)\nDone 2026-05-04. See assumption table above.\n\n### ✅ Phase 0.5 — module consolidation (complete, 2026-05-04)\nDone. Refactored `src/lang/wasm.ts` to expose `compileBlocksToModule(blocks)` which produces one `WebAssembly.Module` with N exports (named `compute_0`, `compute_1`, ...) sharing one imported memory. `src/lang/emitters/js-wasm.ts` now emits a single `WebAssembly.compile` + `WebAssembly.instantiate` per file regardless of how many `wasm {}` blocks are present.\n\n- New public API: `compileBlocksToModule(blocks: WasmBlock[]): MultiBlockCompileResult`\n- Internal refactor: extracted `compileBlockToFunction` + `buildMultiFunctionModule`; legacy `compileToWasm` now delegates (single-function case)\n- 7 new tests in `wasm.test.ts` under `describe('module consolidation (Phase 0.5)')` covering multi-export modules, failure isolation, void/value mixing, and bootstrap-emits-one-compile-call\n- All 1913 fast-suite tests pass\n- Iframe variant (`generateWasmInstantiationCode`) left as-is for now — single-block API used by playground; consistency cleanup deferred\n\n### ✅ Phase 0.75 — transpile-time module loader (complete, 2026-05-07)\nDone. New `src/lang/module-loader.ts` provides a `ModuleLoader` class with `resolve(spec, importerPath)` and `load(spec, importerPath)`. Resolution rules: URLs / data: → null (runtime resolves these); relative/absolute paths → fs lookup with `.tjs` / `.ts` / `.js` extension fallback and `index.<ext>` directory resolution; bare specifiers (`tjs-lang/linalg`) → walk up looking for `node_modules/<spec>`, with optional `bareSpecifierRoots` for monorepos and tests. Loaded modules expose `imports` (specifier + local + imported + namespace flag) and `exports` (name + kind: function/class/variable/re-export/unknown).\n\n- New public exports from `src/lang/index.ts`: `ModuleLoader`, `inMemoryFileSystem`, plus types\n- `inMemoryFileSystem(files)` helper for hermetic tests\n- LRU-style cache with configurable limit; `clearCache()` for forced reload\n- 24 tests in `module-loader.test.ts` covering relative/absolute/parent paths, extension fallback, directory imports, node_modules walk, bareSpecifierRoots, URL rejection, missing-file null, parse-failure null, cache hit/miss, eviction\n- Pure additive — transpiler behavior unchanged. Phase 3 will be the first caller\n- All 1938 fast-suite tests pass\n\n**Caveat (worth flagging for Phase 1):** `parseTjs` runs the full preprocessor before we see the AST, which means `export class Foo {}` shows up as a variable export (the preprocessor rewrites class declarations into `wrapClass(class)` form). For Phase 3's needs this is fine — we'll be looking at function/wasm-function bodies, not class structure. If we ever need to surface the original class shape, the loader can expose the pre-preprocessor source alongside the parsed AST.\n\n### ✅ Phase 1 — `wasm function` declaration syntax (complete, 2026-05-12)\nDone. New `extractWasmFunctions` transform in `parser-transforms.ts` recognizes top-level `(export)? wasm function NAME(params): RetType { body }` declarations. The body is the wasm-subset source; captures are the function's parameters with their type annotations (using existing wasm type names: `i32`/`i64`/`f32`/`f64`/`Float32Array`/`Float64Array`/`Int32Array`/`Uint8Array`).\n\n- Each `wasm function` declaration is replaced in source with a regular JS wrapper: `function NAME(...args) { return globalThis.__tjs_wasm_NAME(...args) }`, preserving the `export` modifier when present\n- The wrapper goes through the regular tjs pipeline (monadic error guards, `__tjs` metadata, etc.); the body is stored on the block and compiled via the Phase 0.5 `compileBlocksToModule` path\n- **Critical ordering fix:** the new extractor runs BEFORE `transformParenExpressions`. `wasm function` params use wasm-specific type syntax (`name: i32`, etc.) and must not be rewritten into tjs's example-based `name = example` default-value form. The inline `wasm {}` extractor still runs later in the pipeline as before\n- 8 new tests in `wasm.test.ts` under `describe('wasm function declarations (Phase 1)')`: extraction, wrapper emission, export modifier, end-to-end call, Float32Array params (zero-copy), coexistence with inline blocks (one consolidated module), no-params functions, and identifier-boundary check (so `mywasm` isn't matched)\n- All 1946 fast-suite tests pass\n\n**Backend limitation (Phase 1.5 work):** the wasm bytecode builder still emits f64 or void return types only — the `: RetType` annotation is parsed and stored but not yet driving the emitted return-type encoding. `: f64` and omitted-return work today; `: i32`/`: f32`/`: v128` returns will be supported when the bytecode builder grows per-function return-type emission. Linalg's `dot`/`norm_sq` (returning scalars) already work fine in f64; the vector-search milestone is unblocked.\n\n### ✅ Phase 2 — purity enforcement (complete, 2026-05-14)\nDone. Two pieces:\n\n1. **Unsafe-marker reservation.** `extractWasmFunctions` now detects `(!` at the start of a `wasm function`'s param list and throws a clear `SyntaxError` directing the user to either remove the bang or wait for the unsafe variant to be implemented. Source location is reported. The marker is parsed (not silently dropped) so users can't accidentally write unsafe code that compiles as safe.\n2. **Purity verification.** The wasm bytecode builder already errors on host-import calls (e.g. `Math.sin`, `Math.cos` → \"Math.<x> requires JS import (not yet implemented)\"). A test confirms this property: a `wasm function` calling `Math.sin` fails with that error, while `Math.sqrt` (which compiles to a wasm intrinsic, no host import) succeeds. The \"purity check\" is therefore enforced automatically by the backend's host-import absence — no separate static-analysis pass needed.\n\n4 new tests under `describe('wasm function purity & unsafe marker (Phase 2)')`. All 1950 fast-suite tests pass.\n\n### ✅ Phase 3 — cross-file module composition (complete, 2026-05-14)\nDone. New `composeImportedWasmFunctions(source, { loader, importerPath })` in `parser-transforms.ts`. When a `ModuleLoader` is provided to `tjs()` / `transpileToJS()`, the preprocessor scans the consumer's `import { ... } from 'spec'` statements, resolves each spec via the loader, and pulls in any matching `wasm function` declarations from the source module. Composed functions become local exports in the consumer's single `WebAssembly.Module` (Phase 0.5 path); the satisfied import bindings are rewritten as local JS wrappers and removed from the import statement.\n\n- Opt-in design: no loader supplied → no behavior change (default verbatim-import preservation kept for existing flows)\n- Mixed imports work: in `import { fast, slow }`, only `fast` (a wasm function) is composed; `slow` (a regular function) remains imported normally\n- Symbol dedup: the same imported wasm function isn't pulled in twice if reached through multiple specifiers\n- WasmBlock got a new optional `name` field to enable matching imported binding names against named declarations (inline `wasm{}` blocks have no name and don't participate in composition)\n\n**Acceptance criteria (all four pass):**\n1. **Correctness:** imported wasm function runs end-to-end and returns identical results to a direct call — verified by an end-to-end test that imports `add` and `mul` from a virtual library and calls them\n2. **Module shape:** the composed wasm module has the imported function as a *local* export, NOT a wasm-level import. `WebAssembly.Module.imports(mod)` returns no function imports beyond `env.memory` (only when needed)\n3. **One module per file:** the smoking-gun static check that exactly one `WebAssembly.compile(` call appears in the emitted output, regardless of how many wasm functions were composed\n4. **Boundary form works too:** deferred until Phase 4\n\n7 new tests in `wasm.test.ts` under `describe('cross-file wasm composition (Phase 3)')`. All 1957 fast-suite tests pass.\n\n**Wiring change worth flagging:** `transpileToJS` previously called `preprocess(cleanSource)` without options, separately from the `parse()` call that handled options. Phase 3 needed the loader to reach the standalone preprocess call too, so it now passes `{ moduleLoader, filename }`. This makes the two preprocess invocations consistent.\n\n### ✅ Phase 4 — JS distribution form (complete, 2026-05-14)\nDone — and it turned out to require essentially no new emitter code, because the boundary form was already what Phases 0.5 and 1 produce. A library file with `export wasm function dot(...)` transpiles to a self-contained ES module: bootstrap at top (one `WebAssembly.compile` + `instantiate`), `globalThis.__tjs_wasm_<name>` set per export, and `export function <name>(...)` wrappers that forward through `globalThis`. Same bytes, two wrappers — exactly the \"same source, two forms\" claim made it true at the bytecode level.\n\nTests (4 new under `describe('boundary distribution form (Phase 4)')`):\n1. **Self-contained output:** transpiled library has exported wrappers, base64-embedded wasm module, no external runtime setup required (inline `__tjs` fallback covers everything used).\n2. **Real dynamic import:** library written to a tmp `.mjs` file, loaded via `await import(path)`, exported functions are callable and return correct results — this is the most authentic test of a real ESM consumer.\n3. **Equivalence:** same library source consumed two ways (boundary form via dynamic import, composed form via Phase 3 moduleLoader). Both return identical numeric results for `dot3(1,2,3,4,5,6)`. This is the Phase 4 acceptance criterion #4 from the canonical demo.\n4. **Plain JS consumer:** library imported and used from a plain JS function (no tjs runtime involvement); the wasm bootstrap runs, the wrapper is callable, results are correct.\n\n**package.json `exports` routing** is not new infrastructure — it's just how a published library declares the dual layout (`src/*.tjs` for tjs consumers, `dist/index.js` for everyone else). The transpilation that produces `dist/index.js` is what `tjs(librarySource)` does today. No code changes were needed in Phase 4 for the routing itself; documentation and an example layout can be added when `tjs-lang/linalg` ships (Phase 5).\n\nAll 1961 fast-suite tests pass.\n\n### ⏳ Phase 5 — linalg stdlib (MVP complete, 2026-05-14; expansion in progress)\n\n**MVP done.** `src/linalg/index.tjs` ships with two `wasm function`s — `dot(a, b, n)` and `norm_sq(a, n)`, both f32x4 SIMD implementations operating on `Float32Array` inputs. These are the minimum needed to unlock the canonical vector-search demo (cosine similarity = `dot(a,b) / sqrt(norm_sq(a) * norm_sq(b))`). Subpath export `tjs-lang/linalg` added to `package.json` (bun → `.tjs` source; production `.js` bundle path declared, build to be wired in a follow-up).\n\n5 tests in `src/linalg/linalg.test.ts`:\n1. Source transpiles cleanly, both functions compile, one consolidated `WebAssembly.Module`\n2. Boundary form: dynamic ESM import works; library is self-contained\n3. Correctness against a JS scalar reference across multiple sizes (4, 16, 64, 128, 256)\n4. Phase 3 composition end-to-end: consumer importing `dot` + `norm_sq` and building cosine similarity returns 1.0 for identical vectors and ~0 for orthogonal ones\n5. Equivalence: boundary and composed forms return identical numeric results for the same library source\n\n**Caveats / follow-ups for the full Phase 5 surface:**\n\n- **`n` must be a multiple of 4.** Current SIMD kernels assume aligned vector lengths. Documented in the source; callers pad as needed. A future tail-loop variant could relax this.\n- **Returns are f64.** Per the Phase 1.5 backend limitation, all returns are f64 (the only scalar return type the bytecode builder emits today). Sufficient for `dot` / `norm_sq` (caller can downcast to f32 if needed) and for cosine similarity.\n- **No `out` parameters yet.** The two MVP functions return scalars, so the `out`-parameter API (for vector/matrix results that can't be scalar) is not yet exercised. Will land with `add`/`sub`/`scale`/`matmul`.\n- **No package.json `exports` `dist/` bundle yet.** The `bun` resolution works today (via the `.tjs` plugin); production `.js` bundling for non-bun consumers needs a one-line addition to `scripts/build.ts`. Follow-up.\n\n**Remaining work (deferred from MVP):**\n- Vector: `norm`, `normalize`, `add`, `sub`, `scale`, `lerp`\n- Matrix: `matmul`, `transpose`, `identity`, `inverse_3x3`, `inverse_4x4`\n- 3D: `cross`, `quat_mul`, `mat4_from_quat`, `look_at`, `perspective`\n- Playground examples with the three-layer layout (wrapper → scalar → SIMD)\n- Benchmark vs gl-matrix\n- Production `.js` bundle for `dist/tjs-linalg.js`\n\n### ✅ Phase 1.5 — wasm-to-wasm function calls (complete, 2026-05-14, originally deferred)\n\nDone. A `wasm function` body can now call other wasm functions in the same composed module via wasm `call <index>` instructions — no JS↔wasm boundary on intra-module calls.\n\n**The architectural payoff:** with Phase 3 composition + Phase 1.5 wasm-to-wasm, both the library kernels and any outer-loop logic the consumer writes can compile to wasm and stay inside the boundary. Library APIs can be small composable primitives (the idiomatic shape) AND the consumer's hot inner loop can use them at zero per-call cost. This closes the architectural critique that the canonical-demo perf finding raised — composed wasm is no longer forced to choose between idiomatic API granularity and performance.\n\n**Mechanism:**\n- `compileBlocksToModule` does a pre-pass over the input blocks, assigning function indices and building a `Map<name, {index, params, hasReturn}>` for every NAMED `wasm function`. This runs BEFORE compiling any body, so forward references and mutual recursion work\n- `WasmBlock` gained an optional `returnType` field, populated by `extractWasmFunctions`. The pre-pass uses presence/absence to determine `hasReturn` without compiling the body\n- Failed blocks become stub functions (same signature, trivial body returning 0 / no-op for void) so call indices stay valid across the module\n- `compileCall` extended: when the callee is an `Identifier` matching a known wasm function, emit args + `Op.call` + function-index instead of erroring\n- Argument types are validated against the called function's signature; mismatches get auto-converted via the standard wasm conversion opcodes (`f64_promote_f32`, `i32_trunc_f64_s`, etc.) when safe, or reported as errors when not\n- Void-returning calls push a dummy `i32.const 0` so `ExpressionStatement`'s automatic `drop` has something to discard (same pattern used by `f32x4_store`)\n\n**Critical ordering fix made while shipping this:** `extractWasmFunctions` now runs even earlier in `preprocess()` — before `transformIsOperators` and `transformEqualityToStructural`. Previously, `n == 0` inside a wasm function body was rewritten to `Eq(n, 0)` (TJS structural equality) before the extractor pulled the body, then the wasm compiler couldn't compile the unknown `Eq` call. Inline `wasm{}` blocks have the same pre-existing bug; they're a follow-up.\n\n6 new tests in `wasm.test.ts` under `describe('wasm-to-wasm calls (Phase 1.5)')`: single-file calls, forward references (caller before callee in source), mutual recursion (isEven/isOdd), cross-file calls via Phase 3 composition, type-conversion auto-insertion (f64 arg → i32 param via truncate), arg-count mismatch with a clear error message.\n\nAll 1977 fast-suite tests pass.\n\n### ✅ Phase 6 — docs (complete, 2026-05-14)\n\nDone. Three docs touched:\n\n- **`DOCS-WASM.md`** — new canonical WASM reference. Covers both flavors (inline blocks and `wasm function` declarations) side-by-side, parameter and return type rules, SIMD intrinsics, the JS-owns-memory invariant and bump-allocator caveat, the cross-file composition story (Phase 3 ModuleLoader path), the two distribution forms (composed vs. boundary), purity + reserved `(!)` marker, the `tjs-lang/linalg` quick-reference, and the full list of v1 limitations as a single \"current limitations\" section.\n- **`TJS-FOR-JS.md`** § \"WASM Blocks\" — added a \"Reusable WASM Functions\" subsection showing the `export wasm function` form, cross-file imports, and the `tjs-lang/linalg` one-liner usage example. Links to DOCS-WASM.md for the full story.\n- **`guides/examples/tjs/wasm-functions.md`** — new playground example showing both `dot_scalar` and `dot_simd` as `wasm function` declarations side-by-side. Demonstrates the three-layer educational pattern from § 5 (TJS wrapper auto-generated, scalar wasm readable, SIMD wasm optimized) inside a single file. Verifies the two implementations agree numerically. Registered in `demo/docs.json` (entry #39).\n\n`CLAUDE.md` Additional Documentation list updated to include DOCS-WASM.md. `wasm-library-plan.md` is the design doc; DOCS-WASM.md is the user-facing reference; `docs/WASM-QUICKSTART.md` is the original 5-minute introduction (for inline blocks).\n\nAll 1970 fast-suite tests pass.\n\n### Phase 7 (deferred) — escape hatch implementation\nOnly if stdlib hits a real wall. Implement `(! ...)` per §3, including the import-site bang requirement.\n\n---\n\n## Resolved decisions\n\n- **SIMD baseline (was Q1).** v1 requires SIMD. See §7.\n- **Pointer typing (was Q2).** Real `Ptr<T>` type. See §2.\n- **`__tjs` metadata for wasm functions (was Q3).** Yes, uniform with regular functions. See §2.\n- **Symbol dedup under transitive imports (was Q4).** Falls out of the module composition pass — symbols resolve once per consumer module. See §4.\n- **Wasm-level tree-shaking + transitive walking (was Q2 — now proven).** Phase 3's composer pulls in **only** the explicitly-imported names PLUS any wasm functions they transitively call within the same source module. Implementation: after pulling in a block, scan its body with a word-boundary regex over the source module's known wasm-function names; recursively pull in any matches. Forward references, mutual recursion, and multi-step chains (`a → b → c`) all work. Word-boundary anchors prevent false matches on substrings (`_add` doesn't trigger `add`). 5 tests in `wasm.test.ts` under `describe('tree-shaking & transitive deps')` cover all the cases. False positives from method calls (`obj.fn(x)`) only cause bloat, not correctness issues — and TJS wasm bodies don't use method calls anyway.\n\n## Open questions\n\n1. **Library duplication across boundary-form deps.** If an app imports two third-party libraries A and B (both shipped as transpiled `.js` per Phase 4), and both internally use `linalg`, each carries its own embedded copy of `linalg`'s wasm. There's no cross-package dedup — same as how every JS dep carries its own copy of `lodash` unless explicitly hoisted. **For v1, accept this.** It matches existing JS ecosystem behavior, and the tjs-to-tjs path (composition) already gives the win where it matters most. A future \"shared wasm module registry\" (runtime-side, peer-dep style) could dedup at the cost of new infrastructure — defer until someone hits a real bundle-size wall.\n2. **Versioning collisions.** If two transitive deps in a tjs consumer's import graph resolve to *different* versions of `linalg` (one v1.2, one v1.3), how does composition handle it? Strict separate copies (correct, larger output) vs. error-out (forces the user to dedup) vs. pick-one (silent risk). Probably \"error out with a clear message\" until someone has a concrete need otherwise.\n\n---\n\n## What this design explicitly is not\n\n- Not a general-purpose wasm linker. No multi-module runtime, no shared memory imports, no `WebAssembly.Table` coordination. The transpile-time module composition approach sidesteps all of it.\n- Not a substitute for hand-written wasm. Authors who need an arena allocator, threads, or shared memory should write their own `.wasm` and import it as a regular WebAssembly module — outside this system.\n- Not a path to non-tjs authors writing wasm libraries for tjs consumers. Library *authors* must use tjs. Library *consumers* can use anything.\n"
|
|
1653
|
+
},
|
|
1606
1654
|
{
|
|
1607
1655
|
"title": "Workflows",
|
|
1608
1656
|
"filename": "README.md",
|
|
@@ -1641,7 +1689,7 @@
|
|
|
1641
1689
|
"group": "docs",
|
|
1642
1690
|
"order": 0,
|
|
1643
1691
|
"navTitle": "TJS for JS Devs",
|
|
1644
|
-
"text": "<!--{\"section\": \"tjs-for-js\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"TJS for JS Devs\"}-->\n\n# TJS for JavaScript Programmers\n\n_Everything you already know, plus the things you always wished JavaScript had._\n\n---\n\n## The One-Sentence Pitch\n\nTJS is JavaScript with types that actually exist at runtime, equality that actually works, and errors that don't crash your program.\n\n---\n\n## What Stays the Same\n\nTJS is a superset of JavaScript. All of this works exactly as you expect:\n\n```javascript\nconst x = 42\nlet name = 'Alice'\nconst items = [1, 2, 3].map((n) => n * 2)\nconst user = { name: 'Bob', age: 30 }\nconst { name: userName, age } = user\nconst merged = { ...defaults, ...overrides }\nconst greeting = `Hello, ${name}!`\n\nfor (const item of items) {\n console.log(item)\n}\nwhile (condition) {\n /* ... */\n}\nif (x > 0) {\n /* ... */\n} else {\n /* ... */\n}\n\ntry {\n riskyThing()\n} catch (e) {\n handleError(e)\n}\n\nclass Dog {\n #name\n constructor(name) {\n this.#name = name\n }\n get name() {\n return this.#name\n }\n}\n\nimport { something } from './module.js'\nexport function myFunction() {\n /* ... */\n}\n```\n\nIf you don't use any TJS features, your code is just JavaScript. No lock-in.\n\nThis extends to advanced patterns too: **Proxies**, **WeakMap/WeakSet**, **Symbols**, **generators**, **async iterators**, **`Object.defineProperty`** — all work identically. TJS adds type checks at function boundaries; it doesn't wrap or intercept any JS runtime behavior.\n\n---\n\n## What's Different\n\n### 1. Types Are Example Values\n\nIn JavaScript, there are no types. In TJS, you annotate parameters with\nan example of a valid value:\n\n```javascript\n// JavaScript\nfunction greet(name) {\n return `Hello, ${name}!`\n}\n\n// TJS - name is required and must be a string (like 'World')\nfunction greet(name: 'World'): '' {\n return `Hello, ${name}!`\n}\n```\n\nThe `: 'World'` means \"required, must be a string, here's an example.\" The `: ''` means \"returns a string.\" These aren't abstract type annotations -- they're concrete values the system can use for testing, documentation, and validation.\n\n| You Write | TJS Infers |\n| ----------------- | ----------------------------- |\n| `name: 'Alice'` | Required string |\n| `count: 42` | Required integer |\n| `rate: 3.14` | Required number (float) |\n| `age: +20` | Required non-negative integer |\n| `flag: true` | Required boolean |\n| `items: [0]` | Required integer[] |\n| `values: [0.0]` | Required number[] |\n| `user: { n: '' }` | Required object |\n| `id: 0 \\|\\| null` | integer or null |\n\nAll of these are valid JavaScript expressions. `42` vs `42.0` vs `+42` are\nall legal JS -- TJS leverages this to distinguish integer, float, and\nnon-negative integer types at the syntax level.\n\nNote: `|| null` means the value accepts the base type _or_ `null`, but not\n`undefined`. TJS treats `null` and `undefined` as distinct types\n(`typeOf(null) === 'null'`, `typeOf(undefined) === 'undefined'`).\n\n**Optional** parameters use `=` (just like JS default values):\n\n```javascript\nfunction greet(name = 'World') {\n /* ... */\n} // optional, defaults to 'World'\nfunction retry(count = 3) {\n /* ... */\n} // optional, defaults to 3 (integer)\n```\n\n**The difference:** `: value` means required. `= value` means optional with a default. In plain JS, both would be written as `= value`.\n\n### 2. Numeric Type Narrowing\n\nTJS distinguishes three numeric types using valid JavaScript syntax:\n\n```javascript\nfunction process(\n rate: 3.14, // number (float) -- has a decimal point\n count: 42, // integer -- whole number, no decimal\n index: +0 // non-negative integer -- prefixed with +\n) { /* ... */ }\n```\n\n| You Write | Type Inferred | Validates |\n| --------- | ---------------------- | ------------------------------- |\n| `3.14` | `number` (float) | Any number |\n| `0.0` | `number` (float) | Any number |\n| `42` | `integer` | `Number.isInteger(x)` |\n| `0` | `integer` | `Number.isInteger(x)` |\n| `+20` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `+0` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `-5` | `integer` | `Number.isInteger(x)` |\n| `-3.5` | `number` (float) | Any number |\n\nThese are all valid JavaScript expressions -- TJS just reads the syntax more\ncarefully than JS does. At runtime, passing `3.14` to a parameter typed as\ninteger returns a monadic error.\n\n### 3. Return Type Annotations\n\nTJS uses `:` for return types (same as TypeScript):\n\n```javascript\n// Returns an integer\nfunction add(a: 0, b: 0): 0 {\n return a + b\n}\n\n// Returns an object with specific shape\nfunction getUser(id: 0): { name: '', age: 0 } {\n return { name: 'Alice', age: 30 }\n}\n```\n\nThe return type example doubles as an automatic test. When you write `: 0`, TJS will call `add(0, 0)` at transpile time and verify the result is a number.\n\n### 4. Equality That Works\n\nJavaScript's `==` is notoriously broken (type coercion). Its `===` doesn't do structural comparison. TJS fixes this:\n\n```javascript\n// JavaScript\n[1, 2] === [1, 2] // false (different references)\n{ a: 1 } === { a: 1 } // false (different references)\n\n// TJS (structural equality is on by default)\n[1, 2] == [1, 2] // true (same structure)\n{ a: 1 } == { a: 1 } // true (same structure)\n[1, 2] === [1, 2] // false (different references, identity check)\n```\n\nTJS redefines the operators:\n\n| Operator | JavaScript | TJS |\n| -------- | ------------------- | --------------------- |\n| `==` | Coercive equality | Structural equality |\n| `!=` | Coercive inequality | Structural inequality |\n| `===` | Strict equality | Identity (same ref) |\n| `!==` | Strict inequality | Not same reference |\n\nYou can also use the explicit forms `Is` and `IsNot`:\n\n```javascript\nuser Is expectedUser // structural deep equality\nresult IsNot errorValue // structural deep inequality\n```\n\nClasses can define custom equality:\n\n```javascript\nclass Point {\n constructor(x: 0, y: 0) { this.x = x; this.y = y }\n Equals(other) { return this.x === other.x && this.y === other.y }\n}\n\nPoint(1, 2) Is Point(1, 2) // true, uses .Equals\n```\n\nNote: structural equality does not handle circular references. Use `===`\nfor objects that might be circular, or define an `.Equals` method.\n\n### 5. Errors Are Values, Not Exceptions\n\nIn JavaScript, type errors crash your program. In TJS, they're values:\n\n```javascript\nfunction double(x: 0): 0 {\n return x * 2\n}\n\ndouble(5) // 10\ndouble('oops') // { $error: true, message: \"Expected number for 'double.x', got string\" }\n```\n\nNo `try`/`catch`. No crashes. The caller gets a clear error value they can inspect:\n\n```javascript\nconst result = double(input)\nif (result?.$error) {\n console.log(result.message) // handle gracefully\n} else {\n useResult(result)\n}\n```\n\nErrors propagate automatically through function calls. If you pass a monadic error to another TJS function, it passes through without executing:\n\n```javascript\nconst a = step1(badInput) // MonadicError\nconst b = step2(a) // skips execution, returns the same error\nconst c = step3(b) // skips again -- error flows to the surface\n```\n\nThis is sometimes called \"railway-oriented programming.\"\n\n### 6. Classes Without `new`\n\nTJS classes are callable as functions:\n\n```javascript\n// JavaScript\nconst p = new Point(10, 20)\n\n// TJS - both work, but the clean form is preferred\nconst p1 = Point(10, 20) // works\nconst p2 = new Point(10, 20) // also works (linter warns)\n```\n\nThis is cleaner and more consistent with functional style. Under the hood, TJS uses a Proxy to auto-construct when you call a class as a function.\n\n### 7. `const` for Free\n\nUppercase identifiers automatically get `const`:\n\n```javascript\n// TJS\nConfig = { debug: true, version: '1.0' }\nMaxRetries = 3\n\n// Transpiles to\nconst Config = { debug: true, version: '1.0' }\nconst MaxRetries = 3\n```\n\nLowercase identifiers behave normally.\n\n---\n\n## The Type System\n\nJavaScript has no type system. Libraries like Zod and Ajv bolt one on,\nbut they're separate from your function signatures -- a second source of\ntruth that drifts. TJS's `Type()` built-in gives you as much narrowing\nand specificity as you want, in one place, using plain functions you\nalready know how to write.\n\n### From Simple to Sophisticated\n\n```javascript\n// Simple -- infer type from an example value\nType Name 'Alice' // any string\n\n// Descriptive -- add documentation\nType User {\n description: 'a registered user'\n example: { name: '', age: 0, email: '' }\n}\n\n// Constrained -- add a predicate for narrowing\nType PositiveNumber {\n description: 'a number greater than zero'\n example: 1\n predicate(x) { return typeof x === 'number' && x > 0 }\n}\n\n// Domain-specific -- real business rules\nType USZipCode {\n description: '5-digit US zip code'\n example: '90210'\n predicate(v) { return typeof v === 'string' && /^\\d{5}$/.test(v) }\n}\n\nType Email {\n description: 'email address'\n example: 'user@example.com'\n predicate(v) { return typeof v === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v) }\n}\n```\n\nThe key insight: a `predicate` is just a function that returns `true` or\n`false`. If you can express a constraint as a boolean check, you can make\nit a type. No schema DSL to learn, no special syntax -- it's JavaScript\nall the way down.\n\nTypes work in function signatures:\n\n```javascript\nfunction sendWelcome(email: Email, name: Name): '' {\n return `Welcome, ${name}! Confirmation sent to ${email}.`\n}\n\nsendWelcome('alice@example.com', 'Alice') // works\nsendWelcome('not-an-email', 'Alice') // MonadicError\n```\n\nTJS also ships common types out of the box: `TString`, `TNumber`,\n`TBoolean`, `TInteger`, `TPositiveInt`, `TNonEmptyString`, `TEmail`,\n`TUrl`, `TUuid`, `Timestamp`, `LegalDate`. No imports from a validation\nlibrary needed.\n\n### Combinators\n\nCompose types from other types:\n\n```javascript\nType OptionalEmail Nullable(Email) // Email | null\nType UserIds TArray(TPositiveInt) // array of positive integers\n```\n\n### Unions and Enums\n\n```javascript\n// Union of string literals\nUnion Status 'task status' 'pending' | 'active' | 'done'\n\n// Enum with named members\nEnum Color 'CSS color' {\n Red = 'red'\n Green = 'green'\n Blue = 'blue'\n}\n\n// Discriminated union -- like tagged unions or sum types\nconst Shape = Union('kind', {\n circle: { radius: 0 },\n rectangle: { width: 0, height: 0 }\n})\n```\n\n### Generics\n\nRuntime-checkable generic types:\n\n```javascript\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Instantiate with a concrete type\nconst NumberBox = Box(TNumber)\nNumberBox.check({ value: 42 }) // true\nNumberBox.check({ value: 'nope' }) // false\n```\n\n---\n\n## Runtime Metadata\n\nEvery TJS function carries its type information at runtime via `__tjs`:\n\n```javascript\nfunction createUser(input: { name: '', age: 0 }): { id: 0 } {\n return { id: 123 }\n}\n\ncreateUser.__tjs\n// {\n// params: { input: { type: { kind: 'object', shape: {...} }, required: true } },\n// returns: { type: { kind: 'object', shape: { id: { kind: 'number' } } } }\n// }\n```\n\nThis metadata enables autocomplete from live objects, automatic documentation\ngeneration, and runtime reflection -- things that require build tools and\nexternal libraries in vanilla JavaScript.\n\n---\n\n## Safety Levels\n\nYou control how much validation TJS applies:\n\n```javascript\n// Per-module (top of file)\nsafety none // Metadata only -- no runtime checks (fastest)\nsafety inputs // Validate inputs only (default)\nsafety all // Validate everything (debug mode)\n\n// Per-function\nfunction fastPath(! x: 0) { /* ... */ } // Skip validation\nfunction safePath(? x: 0) { /* ... */ } // Force validation\n```\n\nUse `!` (skip validation) only in hot loops where every microsecond counts\nand the data source is already trusted. In all other cases, the ~1.5x\noverhead of `safety inputs` is negligible compared to the bugs it catches.\n\n### Unsafe Blocks\n\nSkip validation for a hot inner loop:\n\n```javascript\nunsafe {\n for (let i = 0; i < million; i++) {\n hotFunction(data[i])\n }\n}\n```\n\n---\n\n## Inline Tests\n\nTests live next to the code they test and run at transpile time:\n\n```javascript\nfunction isPrime(n: 2): true {\n if (n < 2) return false\n for (let i = 2; i * i <= n; i++) {\n if (n % i === 0) return false\n }\n return true\n}\n\ntest 'prime detection' {\n expect(isPrime(2)).toBe(true)\n expect(isPrime(4)).toBe(false)\n expect(isPrime(17)).toBe(true)\n}\n```\n\nTests are stripped from production output. They're also generated\nautomatically from return type annotations -- `: true` means TJS will call\n`isPrime(2)` and verify it returns a boolean.\n\n---\n\n## WASM Blocks\n\nFor compute-heavy code, drop into WebAssembly:\n\n```javascript\nconst add = wasm (a: i32, b: i32): i32 {\n local.get $a\n local.get $b\n i32.add\n}\n\nadd(1, 2) // 3, runs as native WASM\n```\n\nWASM is compiled at transpile time and embedded as base64 in the output.\nNo separate `.wasm` files.\n\n---\n\n## Safe Eval\n\nThe killer feature for many use cases. Run untrusted code safely:\n\n```javascript\nimport { Eval, SafeFunction } from 'tjs-lang/eval'\n\n// One-shot evaluation\nconst { result } = await Eval({\n code: 'return items.filter(x => x > threshold)',\n context: { items: [1, 5, 10, 15], threshold: 7 },\n fuel: 1000,\n})\n// result: [10, 15]\n\n// Reusable safe function\nconst transform = await SafeFunction({\n body: 'return x * multiplier',\n params: ['x'],\n fuel: 500,\n})\n\nawait transform(21) // { result: 42, fuelUsed: 8 }\n```\n\n- Gas-limited (fuel runs out = execution stops, no infinite loops)\n- Capability-based (no I/O unless you grant it)\n- No `eval()`, no CSP violations, no containers\n\n---\n\n## What You Give Up\n\nTJS is opinionated. Here's what changes:\n\n| JavaScript | TJS | Why |\n| -------------------------- | ----------------------------- | ------------------------------- |\n| `==` (type coercion) | `==` (structural equality) | Coercion is a bug factory |\n| Exceptions for type bugs | Monadic error values | Exceptions escape, values don't |\n| `new ClassName()` | `ClassName()` preferred | Cleaner, more functional |\n| No runtime types | `__tjs` metadata on functions | Types should exist at runtime |\n| `typeof null === 'object'` | `typeOf(null) === 'null'` | JS got this wrong in 1995 |\n\nNothing is taken away. `new` still works. `===` still works. You can write\nplain JavaScript in a `.tjs` file and it works. The type-related additions\nuse explicit syntax (`:` annotations, `:` return types, `Type` declarations).\nBehavioral modes like structural equality, callable classes, and honest\n`typeof` are enabled by default in native TJS files. Use `TjsCompat` at the\ntop of a file to disable all modes for gradual migration or JS interop.\n\n---\n\n## Getting Started\n\n```bash\nnpm install tjs-lang\n```\n\n### Try It in the Browser\n\nThe playground runs the full TJS compiler in your browser -- no backend needed:\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n### CLI\n\n```bash\nbun src/cli/tjs.ts check file.tjs # Parse and type check\nbun src/cli/tjs.ts run file.tjs # Transpile and execute\nbun src/cli/tjs.ts emit file.tjs # Output transpiled JS\nbun src/cli/tjs.ts test file.tjs # Run inline tests\n```\n\n### From Code\n\n```javascript\nimport { tjs } from 'tjs-lang'\n\nconst output = tjs`\n function add(a: 0, b: 0): 0 {\n return a + b\n }\n`\n```\n\n---\n\n## Runtime Traceability\n\nEvery TJS function carries its source identity in `__tjs` metadata.\nWhen validation fails, the error tells you exactly which function and\nparameter failed, in which source file:\n\n```javascript\nfunction add(a: 0, b: 0): 0 {\n return a + b\n}\n\nadd.__tjs.source // \"mymodule.tjs:3\"\nadd('oops', 1) // MonadicError { path: \"mymodule.tjs:3:add.a\", expected: \"integer\", actual: \"string\" }\n```\n\nNo source maps. No build artifacts. The function _knows where it came from_.\n\n---\n\n## Learn More\n\n- [TJS Language Reference](DOCS-TJS.md) -- Full syntax and features\n- [AJS Agent Language](DOCS-AJS.md) -- The sandboxed agent VM\n- [TJS for TypeScript Programmers](TJS-FOR-TS.md) -- Coming from TypeScript?\n- [Playground](https://tjs-platform.web.app) -- Try it live\n"
|
|
1692
|
+
"text": "<!--{\"section\": \"tjs-for-js\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"TJS for JS Devs\"}-->\n\n# TJS for JavaScript Programmers\n\n_Everything you already know, plus the things you always wished JavaScript had._\n\n---\n\n## The One-Sentence Pitch\n\nTJS is JavaScript with types that actually exist at runtime, equality that actually works, and errors that don't crash your program.\n\n---\n\n## What Stays the Same\n\nTJS is a superset of JavaScript. All of this works exactly as you expect:\n\n```javascript\nconst x = 42\nlet name = 'Alice'\nconst items = [1, 2, 3].map((n) => n * 2)\nconst user = { name: 'Bob', age: 30 }\nconst { name: userName, age } = user\nconst merged = { ...defaults, ...overrides }\nconst greeting = `Hello, ${name}!`\n\nfor (const item of items) {\n console.log(item)\n}\nwhile (condition) {\n /* ... */\n}\nif (x > 0) {\n /* ... */\n} else {\n /* ... */\n}\n\ntry {\n riskyThing()\n} catch (e) {\n handleError(e)\n}\n\nclass Dog {\n #name\n constructor(name) {\n this.#name = name\n }\n get name() {\n return this.#name\n }\n}\n\nimport { something } from './module.js'\nexport function myFunction() {\n /* ... */\n}\n```\n\nIf you don't use any TJS features, your code is just JavaScript. No lock-in.\n\nThis extends to advanced patterns too: **Proxies**, **WeakMap/WeakSet**, **Symbols**, **generators**, **async iterators**, **`Object.defineProperty`** — all work identically. TJS adds type checks at function boundaries; it doesn't wrap or intercept any JS runtime behavior.\n\n---\n\n## What's Different\n\n### 1. Types Are Example Values\n\nIn JavaScript, there are no types. In TJS, you annotate parameters with\nan example of a valid value:\n\n```javascript\n// JavaScript\nfunction greet(name) {\n return `Hello, ${name}!`\n}\n\n// TJS - name is required and must be a string (like 'World')\nfunction greet(name: 'World'): '' {\n return `Hello, ${name}!`\n}\n```\n\nThe `: 'World'` means \"required, must be a string, here's an example.\" The `: ''` means \"returns a string.\" These aren't abstract type annotations -- they're concrete values the system can use for testing, documentation, and validation.\n\n| You Write | TJS Infers |\n| ----------------- | ----------------------------- |\n| `name: 'Alice'` | Required string |\n| `count: 42` | Required integer |\n| `rate: 3.14` | Required number (float) |\n| `age: +20` | Required non-negative integer |\n| `flag: true` | Required boolean |\n| `items: [0]` | Required integer[] |\n| `values: [0.0]` | Required number[] |\n| `user: { n: '' }` | Required object |\n| `id: 0 \\|\\| null` | integer or null |\n\nAll of these are valid JavaScript expressions. `42` vs `42.0` vs `+42` are\nall legal JS -- TJS leverages this to distinguish integer, float, and\nnon-negative integer types at the syntax level.\n\nNote: `|| null` means the value accepts the base type _or_ `null`, but not\n`undefined`. TJS treats `null` and `undefined` as distinct types\n(`typeOf(null) === 'null'`, `typeOf(undefined) === 'undefined'`).\n\n**Optional** parameters use `=` (just like JS default values):\n\n```javascript\nfunction greet(name = 'World') {\n /* ... */\n} // optional, defaults to 'World'\nfunction retry(count = 3) {\n /* ... */\n} // optional, defaults to 3 (integer)\n```\n\n**The difference:** `: value` means required. `= value` means optional with a default. In plain JS, both would be written as `= value`.\n\n### 2. Numeric Type Narrowing\n\nTJS distinguishes three numeric types using valid JavaScript syntax:\n\n```javascript\nfunction process(\n rate: 3.14, // number (float) -- has a decimal point\n count: 42, // integer -- whole number, no decimal\n index: +0 // non-negative integer -- prefixed with +\n) { /* ... */ }\n```\n\n| You Write | Type Inferred | Validates |\n| --------- | ---------------------- | ------------------------------- |\n| `3.14` | `number` (float) | Any number |\n| `0.0` | `number` (float) | Any number |\n| `42` | `integer` | `Number.isInteger(x)` |\n| `0` | `integer` | `Number.isInteger(x)` |\n| `+20` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `+0` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `-5` | `integer` | `Number.isInteger(x)` |\n| `-3.5` | `number` (float) | Any number |\n\nThese are all valid JavaScript expressions -- TJS just reads the syntax more\ncarefully than JS does. At runtime, passing `3.14` to a parameter typed as\ninteger returns a monadic error.\n\n### 3. Return Type Annotations\n\nTJS uses `:` for return types (same as TypeScript):\n\n```javascript\n// Returns an integer\nfunction add(a: 0, b: 0): 0 {\n return a + b\n}\n\n// Returns an object with specific shape\nfunction getUser(id: 0): { name: '', age: 0 } {\n return { name: 'Alice', age: 30 }\n}\n```\n\nThe return type example doubles as an automatic test. When you write `: 0`, TJS will call `add(0, 0)` at transpile time and verify the result is a number.\n\n### 4. Equality That Works\n\nJavaScript's `==` is notoriously broken (type coercion). Its `===` doesn't do structural comparison. TJS fixes this:\n\n```javascript\n// JavaScript\n[1, 2] === [1, 2] // false (different references)\n{ a: 1 } === { a: 1 } // false (different references)\n\n// TJS (structural equality is on by default)\n[1, 2] == [1, 2] // true (same structure)\n{ a: 1 } == { a: 1 } // true (same structure)\n[1, 2] === [1, 2] // false (different references, identity check)\n```\n\nTJS redefines the operators:\n\n| Operator | JavaScript | TJS |\n| -------- | ------------------- | --------------------- |\n| `==` | Coercive equality | Structural equality |\n| `!=` | Coercive inequality | Structural inequality |\n| `===` | Strict equality | Identity (same ref) |\n| `!==` | Strict inequality | Not same reference |\n\nYou can also use the explicit forms `Is` and `IsNot`:\n\n```javascript\nuser Is expectedUser // structural deep equality\nresult IsNot errorValue // structural deep inequality\n```\n\nClasses can define custom equality:\n\n```javascript\nclass Point {\n constructor(x: 0, y: 0) { this.x = x; this.y = y }\n Equals(other) { return this.x === other.x && this.y === other.y }\n}\n\nPoint(1, 2) Is Point(1, 2) // true, uses .Equals\n```\n\nNote: structural equality does not handle circular references. Use `===`\nfor objects that might be circular, or define an `.Equals` method.\n\n### 5. Errors Are Values, Not Exceptions\n\nIn JavaScript, type errors crash your program. In TJS, they're values:\n\n```javascript\nfunction double(x: 0): 0 {\n return x * 2\n}\n\ndouble(5) // 10\ndouble('oops') // { $error: true, message: \"Expected number for 'double.x', got string\" }\n```\n\nNo `try`/`catch`. No crashes. The caller gets a clear error value they can inspect:\n\n```javascript\nconst result = double(input)\nif (result?.$error) {\n console.log(result.message) // handle gracefully\n} else {\n useResult(result)\n}\n```\n\nErrors propagate automatically through function calls. If you pass a monadic error to another TJS function, it passes through without executing:\n\n```javascript\nconst a = step1(badInput) // MonadicError\nconst b = step2(a) // skips execution, returns the same error\nconst c = step3(b) // skips again -- error flows to the surface\n```\n\nThis is sometimes called \"railway-oriented programming.\"\n\n### 6. Classes Without `new`\n\nTJS classes are callable as functions:\n\n```javascript\n// JavaScript\nconst p = new Point(10, 20)\n\n// TJS - both work, but the clean form is preferred\nconst p1 = Point(10, 20) // works\nconst p2 = new Point(10, 20) // also works (linter warns)\n```\n\nThis is cleaner and more consistent with functional style. Under the hood, TJS uses a Proxy to auto-construct when you call a class as a function.\n\n### 7. `const` for Free\n\nUppercase identifiers automatically get `const`:\n\n```javascript\n// TJS\nConfig = { debug: true, version: '1.0' }\nMaxRetries = 3\n\n// Transpiles to\nconst Config = { debug: true, version: '1.0' }\nconst MaxRetries = 3\n```\n\nLowercase identifiers behave normally.\n\n---\n\n## The Type System\n\nJavaScript has no type system. Libraries like Zod and Ajv bolt one on,\nbut they're separate from your function signatures -- a second source of\ntruth that drifts. TJS's `Type()` built-in gives you as much narrowing\nand specificity as you want, in one place, using plain functions you\nalready know how to write.\n\n### From Simple to Sophisticated\n\n```javascript\n// Simple -- infer type from an example value\nType Name 'Alice' // any string\n\n// Descriptive -- add documentation\nType User {\n description: 'a registered user'\n example: { name: '', age: 0, email: '' }\n}\n\n// Constrained -- add a predicate for narrowing\nType PositiveNumber {\n description: 'a number greater than zero'\n example: 1\n predicate(x) { return typeof x === 'number' && x > 0 }\n}\n\n// Domain-specific -- real business rules\nType USZipCode {\n description: '5-digit US zip code'\n example: '90210'\n predicate(v) { return typeof v === 'string' && /^\\d{5}$/.test(v) }\n}\n\nType Email {\n description: 'email address'\n example: 'user@example.com'\n predicate(v) { return typeof v === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(v) }\n}\n```\n\nThe key insight: a `predicate` is just a function that returns `true` or\n`false`. If you can express a constraint as a boolean check, you can make\nit a type. No schema DSL to learn, no special syntax -- it's JavaScript\nall the way down.\n\nTypes work in function signatures:\n\n```javascript\nfunction sendWelcome(email: Email, name: Name): '' {\n return `Welcome, ${name}! Confirmation sent to ${email}.`\n}\n\nsendWelcome('alice@example.com', 'Alice') // works\nsendWelcome('not-an-email', 'Alice') // MonadicError\n```\n\nTJS also ships common types out of the box: `TString`, `TNumber`,\n`TBoolean`, `TInteger`, `TPositiveInt`, `TNonEmptyString`, `TEmail`,\n`TUrl`, `TUuid`, `Timestamp`, `LegalDate`. No imports from a validation\nlibrary needed.\n\n### Combinators\n\nCompose types from other types:\n\n```javascript\nType OptionalEmail Nullable(Email) // Email | null\nType UserIds TArray(TPositiveInt) // array of positive integers\n```\n\n### Unions and Enums\n\n```javascript\n// Union of string literals\nUnion Status 'task status' 'pending' | 'active' | 'done'\n\n// Enum with named members\nEnum Color 'CSS color' {\n Red = 'red'\n Green = 'green'\n Blue = 'blue'\n}\n\n// Discriminated union -- like tagged unions or sum types\nconst Shape = Union('kind', {\n circle: { radius: 0 },\n rectangle: { width: 0, height: 0 }\n})\n```\n\n### Generics\n\nRuntime-checkable generic types:\n\n```javascript\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Instantiate with a concrete type\nconst NumberBox = Box(TNumber)\nNumberBox.check({ value: 42 }) // true\nNumberBox.check({ value: 'nope' }) // false\n```\n\n---\n\n## Runtime Metadata\n\nEvery TJS function carries its type information at runtime via `__tjs`:\n\n```javascript\nfunction createUser(input: { name: '', age: 0 }): { id: 0 } {\n return { id: 123 }\n}\n\ncreateUser.__tjs\n// {\n// params: { input: { type: { kind: 'object', shape: {...} }, required: true } },\n// returns: { type: { kind: 'object', shape: { id: { kind: 'number' } } } }\n// }\n```\n\nThis metadata enables autocomplete from live objects, automatic documentation\ngeneration, and runtime reflection -- things that require build tools and\nexternal libraries in vanilla JavaScript.\n\n---\n\n## Safety Levels\n\nYou control how much validation TJS applies:\n\n```javascript\n// Per-module (top of file)\nsafety none // Metadata only -- no runtime checks (fastest)\nsafety inputs // Validate inputs only (default)\nsafety all // Validate everything (debug mode)\n\n// Per-function\nfunction fastPath(! x: 0) { /* ... */ } // Skip validation\nfunction safePath(? x: 0) { /* ... */ } // Force validation\n```\n\nUse `!` (skip validation) only in hot loops where every microsecond counts\nand the data source is already trusted. In all other cases, the ~1.5x\noverhead of `safety inputs` is negligible compared to the bugs it catches.\n\n### Unsafe Blocks\n\nSkip validation for a hot inner loop:\n\n```javascript\nunsafe {\n for (let i = 0; i < million; i++) {\n hotFunction(data[i])\n }\n}\n```\n\n---\n\n## Inline Tests\n\nTests live next to the code they test and run at transpile time:\n\n```javascript\nfunction isPrime(n: 2): true {\n if (n < 2) return false\n for (let i = 2; i * i <= n; i++) {\n if (n % i === 0) return false\n }\n return true\n}\n\ntest 'prime detection' {\n expect(isPrime(2)).toBe(true)\n expect(isPrime(4)).toBe(false)\n expect(isPrime(17)).toBe(true)\n}\n```\n\nTests are stripped from production output. They're also generated\nautomatically from return type annotations -- `: true` means TJS will call\n`isPrime(2)` and verify it returns a boolean.\n\n---\n\n## WASM Blocks\n\nFor compute-heavy code, drop into WebAssembly:\n\n```javascript\nconst add = wasm (a: i32, b: i32): i32 {\n local.get $a\n local.get $b\n i32.add\n}\n\nadd(1, 2) // 3, runs as native WASM\n```\n\nWASM is compiled at transpile time and embedded as base64 in the output.\nNo separate `.wasm` files.\n\n### Reusable WASM Functions\n\nFor a kernel that other files can import, use a top-level `wasm function`\ndeclaration:\n\n```javascript\nexport wasm function dot(a: Float32Array, b: Float32Array, n: i32): f64 {\n // ... SIMD f32x4 implementation ...\n}\n```\n\nOther files can import it directly:\n\n```javascript\nimport { dot } from './my-lib.tjs'\n// `dot` is composed into your file's wasm module at transpile time\n// — no JS↔WASM boundary on intra-library calls\n```\n\nThe first stdlib built on this is `tjs-lang/linalg` — SIMD vector kernels\nready to use:\n\n```javascript\nimport { dot, norm_sq } from 'tjs-lang/linalg'\nconst cos = dot(a, b, n) / Math.sqrt(norm_sq(a, n) * norm_sq(b, n))\n```\n\nSee [`DOCS-WASM.md`](DOCS-WASM.md) for the full story — declaration syntax,\nthe JS-owns-memory model, cross-file composition, and the two distribution\nforms (composed vs. boundary).\n\n---\n\n## Safe Eval\n\nThe killer feature for many use cases. Run untrusted code safely:\n\n```javascript\nimport { Eval, SafeFunction } from 'tjs-lang/eval'\n\n// One-shot evaluation\nconst { result } = await Eval({\n code: 'return items.filter(x => x > threshold)',\n context: { items: [1, 5, 10, 15], threshold: 7 },\n fuel: 1000,\n})\n// result: [10, 15]\n\n// Reusable safe function\nconst transform = await SafeFunction({\n body: 'return x * multiplier',\n params: ['x'],\n fuel: 500,\n})\n\nawait transform(21) // { result: 42, fuelUsed: 8 }\n```\n\n- Gas-limited (fuel runs out = execution stops, no infinite loops)\n- Capability-based (no I/O unless you grant it)\n- No `eval()`, no CSP violations, no containers\n\n---\n\n## What You Give Up\n\nTJS is opinionated. Here's what changes:\n\n| JavaScript | TJS | Why |\n| -------------------------- | ----------------------------- | ------------------------------- |\n| `==` (type coercion) | `==` (structural equality) | Coercion is a bug factory |\n| Exceptions for type bugs | Monadic error values | Exceptions escape, values don't |\n| `new ClassName()` | `ClassName()` preferred | Cleaner, more functional |\n| No runtime types | `__tjs` metadata on functions | Types should exist at runtime |\n| `typeof null === 'object'` | `typeOf(null) === 'null'` | JS got this wrong in 1995 |\n\nNothing is taken away. `new` still works. `===` still works. You can write\nplain JavaScript in a `.tjs` file and it works. The type-related additions\nuse explicit syntax (`:` annotations, `:` return types, `Type` declarations).\nBehavioral modes like structural equality, callable classes, and honest\n`typeof` are enabled by default in native TJS files. Use `TjsCompat` at the\ntop of a file to disable all modes for gradual migration or JS interop.\n\n---\n\n## Getting Started\n\n```bash\nnpm install tjs-lang\n```\n\n### Try It in the Browser\n\nThe playground runs the full TJS compiler in your browser -- no backend needed:\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n### CLI\n\n```bash\nbun src/cli/tjs.ts check file.tjs # Parse and type check\nbun src/cli/tjs.ts run file.tjs # Transpile and execute\nbun src/cli/tjs.ts emit file.tjs # Output transpiled JS\nbun src/cli/tjs.ts test file.tjs # Run inline tests\n```\n\n### From Code\n\n```javascript\nimport { tjs } from 'tjs-lang'\n\nconst output = tjs`\n function add(a: 0, b: 0): 0 {\n return a + b\n }\n`\n```\n\n---\n\n## Runtime Traceability\n\nEvery TJS function carries its source identity in `__tjs` metadata.\nWhen validation fails, the error tells you exactly which function and\nparameter failed, in which source file:\n\n```javascript\nfunction add(a: 0, b: 0): 0 {\n return a + b\n}\n\nadd.__tjs.source // \"mymodule.tjs:3\"\nadd('oops', 1) // MonadicError { path: \"mymodule.tjs:3:add.a\", expected: \"integer\", actual: \"string\" }\n```\n\nNo source maps. No build artifacts. The function _knows where it came from_.\n\n---\n\n## Learn More\n\n- [TJS Language Reference](DOCS-TJS.md) -- Full syntax and features\n- [AJS Agent Language](DOCS-AJS.md) -- The sandboxed agent VM\n- [TJS for TypeScript Programmers](TJS-FOR-TS.md) -- Coming from TypeScript?\n- [Playground](https://tjs-platform.web.app) -- Try it live\n"
|
|
1645
1693
|
},
|
|
1646
1694
|
{
|
|
1647
1695
|
"title": "TJS for TypeScript Programmers",
|