tjs-lang 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/docs.json +15 -15
- package/demo/src/tjs-playground.ts +18 -27
- package/dist/index.js +108 -160
- package/dist/index.js.map +6 -6
- package/dist/src/lang/emitters/js.d.ts +34 -2
- package/dist/src/lang/index.d.ts +1 -1
- package/dist/tjs-full.js +108 -160
- package/dist/tjs-full.js.map +6 -6
- package/dist/tjs-transpiler.js +87 -55
- package/dist/tjs-transpiler.js.map +5 -4
- package/docs/docs.json +792 -0
- package/docs/index.js +1962 -1875
- package/docs/index.js.map +7 -7
- package/package.json +1 -1
- package/src/lang/emitters/js.ts +154 -4
- package/src/lang/index.ts +0 -3
- package/src/lang/lang.test.ts +38 -19
- package/src/lang/roundtrip.test.ts +155 -0
- package/src/lang/wasm.test.ts +20 -0
- package/src/lang/wasm.ts +143 -0
package/demo/docs.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "example",
|
|
44
44
|
"group": "advanced",
|
|
45
45
|
"order": 15,
|
|
46
|
-
"code": "/**\n * # The Universal Endpoint\n *\n * This is the entire backend industry in 50 lines.\n *\n * What this replaces:\n * - GraphQL servers\n * - REST API forests\n * - Firebase/Lambda/Vercel Functions\n * - Kubernetes deployments\n * - The backend priesthood\n *\n * How it works:\n * 1. Client sends logic (not just data)\n * 2. Server executes it with bounded resources\n * 3. 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\
|
|
46
|
+
"code": "/**\n * # The Universal Endpoint\n *\n * This is the entire backend industry in 50 lines.\n *\n * What this replaces:\n * - GraphQL servers\n * - REST API forests\n * - Firebase/Lambda/Vercel Functions\n * - Kubernetes deployments\n * - The backend priesthood\n *\n * How it works:\n * 1. Client sends logic (not just data)\n * 2. Server executes it with bounded resources\n * 3. 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": "/**\n * # Testing Private Functions\n *\n * This is the killer feature of inline tests:\n * You can test functions WITHOUT exporting them.\n *\n * Traditional 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 *\n * TJS inline tests have full access to the module scope.\n * Test 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 = /^[
|
|
58
|
+
"code": "/**\n * # Testing Private Functions\n *\n * This is the killer feature of inline tests:\n * You can test functions WITHOUT exporting them.\n *\n * Traditional 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 *\n * TJS inline tests have full access to the module scope.\n * Test 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: '') -> true {\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: '') -> '' {\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: '' })\n -> { 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": "/*#\nThe classic first function in any language.\n\nDemonstrates:\n- Type annotations via examples (
|
|
70
|
+
"code": "/*#\nThe classic first function in any language.\n\nDemonstrates:\n- Type annotations via examples (name: 'World')\n- Return type example (-> 'Hello, World') - tests the signature!\n- Inline tests with test blocks\n- Markdown documentation via /*# comments\n*/\ntest 'greet says hello' {\n expect(greet('TJS')).toBe('Hello, TJS!')\n}\n\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}",
|
|
71
71
|
"language": "tjs",
|
|
72
72
|
"description": "Simple typed greeting function with docs and tests"
|
|
73
73
|
},
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"type": "example",
|
|
80
80
|
"group": "basics",
|
|
81
81
|
"order": 2,
|
|
82
|
-
"code": "/*#\n## Required vs Optional Parameters\n\nIn TJS, the punctuation tells you everything:\n\n| Syntax | Meaning |\n|--------|---------|\n|
|
|
82
|
+
"code": "/*#\n## Required vs Optional Parameters\n\nIn TJS, the punctuation tells you everything:\n\n| Syntax | Meaning |\n|--------|---------|\n| `param: 'value'` | **Required** - must be provided |\n| `param = 'value'` | **Optional** - defaults to value |\n\nThe example value after `:` or `=` defines the type.\n*/\ntest 'requires name and email' {\n const user = createUser('Alice', 'alice@test.com')\n expect(user.name).toBe('Alice')\n expect(user.age).toBe(0) // default\n}\n\nfunction createUser(\n name: 'anonymous',\n email: 'user@example.com',\n age = 0,\n admin = false\n) -> { name: '', email: '', age: 0, admin: false } {\n return { name, email, age, admin }\n}\n\n// Check the metadata\nconsole.log('Params:', createUser.__tjs.params)\ncreateUser('Alice', 'alice@example.com')",
|
|
83
83
|
"language": "tjs",
|
|
84
84
|
"description": "Difference between : and = in parameters"
|
|
85
85
|
},
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"type": "example",
|
|
92
92
|
"group": "basics",
|
|
93
93
|
"order": 3,
|
|
94
|
-
"code": "/*#\n## Object Types\n\nObject shapes are defined by example:\n
|
|
94
|
+
"code": "/*#\n## Object Types\n\nObject shapes are defined by example:\n`{ first: '', last: '' }` means an object with string properties.\n\nThe return type `-> { x: 0, y: 0 }` is tested at transpile time!\n*/\ntest 'createPoint returns correct structure' {\n const p = createPoint(5, 10)\n expect(p.x).toBe(5)\n expect(p.y).toBe(10)\n}\n\nfunction getFullName(person: { first: '', last: '' }) -> 'Jane Doe' {\n return person.first + ' ' + person.last\n}\n\nfunction createPoint(x: 0, y: 0) -> { x: 0, y: 0 } {\n return { x, y }\n}\n\nfunction distance(p1: { x: 0, y: 0 }, p2: { x: 0, y: 0 }) -> 5 {\n const dx = p2.x - p1.x\n const dy = p2.y - p1.y\n return Math.sqrt(dx * dx + dy * dy)\n}\n\n// Usage - signature tests verify these at transpile time\nconst name = getFullName({ first: 'Jane', last: 'Doe' }) // -> 'Jane Doe'\nconst dist = distance({ x: 0, y: 0 }, { x: 3, y: 4 }) // -> 5\n\nconsole.log('Name:', name)\nconsole.log('Distance:', dist)",
|
|
95
95
|
"language": "tjs",
|
|
96
96
|
"description": "Typed object parameters and returns"
|
|
97
97
|
},
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"type": "example",
|
|
104
104
|
"group": "basics",
|
|
105
105
|
"order": 4,
|
|
106
|
-
"code": "/*#\n## Array Types\n\nArray types use a single-element example:\n-
|
|
106
|
+
"code": "/*#\n## Array Types\n\nArray types use a single-element example:\n- `[0]` = array of numbers\n- `['']` = array of strings\n- `[{ x: 0 }]` = array of objects with shape { x: number }\n*/\ntest 'sum adds numbers' {\n expect(sum([1, 2, 3, 4])).toBe(10)\n}\n\ntest 'stats calculates correctly' {\n const s = stats([10, 20, 30])\n expect(s.min).toBe(10)\n expect(s.max).toBe(30)\n expect(s.avg).toBe(20)\n}\n\nfunction sum(numbers: [0]) -> 10 {\n return numbers.reduce((a, b) => a + b, 0)\n}\n\nfunction average(numbers: [0]) -> 20 {\n if (numbers.length === 0) return 0\n return sum(numbers) / numbers.length\n}\n\nfunction stats(data: [0]) -> { min: 10, max: 30, avg: 20 } {\n if (data.length === 0) {\n return { min: 0, max: 0, avg: 0 }\n }\n return {\n min: Math.min(...data),\n max: Math.max(...data),\n avg: average(data)\n }\n}\n\n// Signature test: stats([10, 20, 30]) -> { min: 10, max: 30, avg: 20 }\nstats([10, 20, 30])",
|
|
107
107
|
"language": "tjs",
|
|
108
108
|
"description": "Working with typed arrays"
|
|
109
109
|
},
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"type": "example",
|
|
126
126
|
"group": "featured",
|
|
127
127
|
"order": 0,
|
|
128
|
-
"code": "/*#\n# Space Flythrough Demo\n\nA Star Trek-style starfield with parallax depth and nebula clouds.\nParticle position updates use `wasm { } fallback { }` blocks.\n\n**Controls:**\n-
|
|
128
|
+
"code": "/*#\n# Space Flythrough Demo\n\nA Star Trek-style starfield with parallax depth and nebula clouds.\nParticle position updates use `wasm { } fallback { }` blocks.\n\n**Note:** This is a proof-of-concept for the WASM compilation pipeline.\nWithout SIMD, scalar WASM operations show no meaningful speedup vs JS JIT.\nSIMD support (v128) is planned, which will enable real performance gains\nfor workloads like vector search, audio processing, and physics.\n\n**Controls:**\n- Mouse up = fast forward\n- Mouse down = slow/reverse\n- Toggle **WASM** to compare (similar performance expected)\n*/\n\n// Configuration - high particle count to stress test WASM vs JS\nconst NUM_STARS = 50000\nconst NUM_NEBULA = 5000\nconst MAX_DEPTH = 1000\nconst BASE_SPEED = 12\n\n// Particle arrays (passed to WASM)\nconst starX = new Float32Array(NUM_STARS)\nconst starY = new Float32Array(NUM_STARS)\nconst starZ = new Float32Array(NUM_STARS)\n\nconst nebulaX = new Float32Array(NUM_NEBULA)\nconst nebulaY = new Float32Array(NUM_NEBULA)\nconst nebulaZ = new Float32Array(NUM_NEBULA)\nconst nebulaR = new Float32Array(NUM_NEBULA)\nconst nebulaG = new Float32Array(NUM_NEBULA)\nconst nebulaB = new Float32Array(NUM_NEBULA)\n\n// Initialize stars\nfor (let i = 0; i < NUM_STARS; i++) {\n starX[i] = (Math.random() - 0.5) * 2000\n starY[i] = (Math.random() - 0.5) * 2000\n starZ[i] = Math.random() * MAX_DEPTH\n}\n\n// Initialize nebula with random positions and colors\nfor (let i = 0; i < NUM_NEBULA; i++) {\n nebulaX[i] = (Math.random() - 0.5) * 1500\n nebulaY[i] = (Math.random() - 0.5) * 1500\n nebulaZ[i] = Math.random() * MAX_DEPTH\n // Random pastel-ish colors\n nebulaR[i] = 0.3 + Math.random() * 0.7\n nebulaG[i] = 0.2 + Math.random() * 0.6\n nebulaB[i] = 0.4 + Math.random() * 0.6\n}\n\n// Move particles - WASM accelerated (just motion, no respawn)\nfunction moveParticles(xs: Float32Array, ys: Float32Array, zs: Float32Array, len: 0, speed: 0.0) {\n wasm {\n for (let i = 0; i < len; i++) {\n zs[i] -= speed\n }\n } fallback {\n for (let i = 0; i < len; i++) {\n zs[i] -= speed\n }\n }\n}\n\n// Respawn stars that passed camera or went too far (JS only - needs Math.random)\nfunction respawnStars(xs, ys, zs, len, maxDepth) {\n for (let i = 0; i < len; i++) {\n if (zs[i] < 1 || zs[i] > maxDepth) {\n // Respawn at opposite end depending on direction\n if (zs[i] < 1) {\n // Passed camera - respawn at back\n zs[i] = maxDepth * (0.7 + Math.random() * 0.3)\n } else {\n // Too far (going backwards) - respawn near camera\n zs[i] = 10 + Math.random() * 50\n }\n xs[i] = (Math.random() - 0.5) * 2000\n ys[i] = (Math.random() - 0.5) * 2000\n }\n }\n}\n\n// Respawn nebula particles (JS only)\nfunction respawnNebula(xs, ys, zs, len, maxDepth) {\n for (let i = 0; i < len; i++) {\n if (zs[i] < 1 || zs[i] > maxDepth) {\n if (zs[i] < 1) {\n zs[i] = maxDepth * (0.5 + Math.random() * 0.5)\n } else {\n zs[i] = 50 + Math.random() * 100\n }\n xs[i] = (Math.random() - 0.5) * 1500\n ys[i] = (Math.random() - 0.5) * 1500\n }\n }\n}\n\n// Create canvas\nconst canvas = document.createElement('canvas')\ncanvas.style.background = '#000'\ncanvas.style.display = 'block'\ncanvas.style.cursor = 'crosshair'\ncanvas.style.width = '100%'\ncanvas.style.height = '100%'\ncanvas.style.position = 'absolute'\ncanvas.style.top = '0'\ncanvas.style.left = '0'\n\nconst ctx = canvas.getContext('2d')\n\n// Dynamic sizing\nlet width, height, halfW, halfH, fov\n\nfunction resize() {\n width = canvas.width = canvas.offsetWidth || 800\n height = canvas.height = canvas.offsetHeight || 500\n halfW = width / 2\n halfH = height / 2\n fov = width * 0.7\n}\n\n// Add canvas to document first so offsetWidth/Height work\ndocument.body.appendChild(canvas)\n\nresize()\nwindow.addEventListener('resize', resize)\n\n// Mouse state - vertical position controls speed\nlet speedMultiplier = 1.0\n\ncanvas.addEventListener('mousemove', (e) => {\n const rect = canvas.getBoundingClientRect()\n const mouseY = (e.clientY - rect.top) / height\n // Top = fast forward, bottom = reverse\n speedMultiplier = 2.0 - mouseY * 3.0\n})\n\n// FPS tracking\nlet frameCount = 0\nlet fps = 0\nlet lastFpsTime = performance.now()\n\n// Animation\nfunction animate() {\n // FPS\n frameCount++\n const now = performance.now()\n if (now - lastFpsTime > 1000) {\n fps = frameCount\n frameCount = 0\n lastFpsTime = now\n }\n\n // Speed from mouse position\n const speed = BASE_SPEED * speedMultiplier\n\n // Clear\n ctx.fillStyle = '#08080f'\n ctx.fillRect(0, 0, width, height)\n\n // Update particles - WASM handles motion, JS handles respawn\n moveParticles(nebulaX, nebulaY, nebulaZ, NUM_NEBULA, speed * 0.25)\n respawnNebula(nebulaX, nebulaY, nebulaZ, NUM_NEBULA, MAX_DEPTH)\n moveParticles(starX, starY, starZ, NUM_STARS, speed)\n respawnStars(starX, starY, starZ, NUM_STARS, MAX_DEPTH)\n\n // Draw nebula (behind stars)\n for (let i = 0; i < NUM_NEBULA; i++) {\n const z = nebulaZ[i]\n if (z < 1) continue\n\n const x = (nebulaX[i] / z) * fov + halfW\n const y = (nebulaY[i] / z) * fov + halfH\n\n if (x < -100 || x > width + 100 || y < -100 || y > height + 100) continue\n\n const depth = 1 - z / MAX_DEPTH\n const alpha = depth * depth * 0.08\n const size = depth * 60 + 30\n\n const r = Math.floor(nebulaR[i] * 255)\n const g = Math.floor(nebulaG[i] * 255)\n const b = Math.floor(nebulaB[i] * 255)\n\n const gradient = ctx.createRadialGradient(x, y, 0, x, y, size)\n gradient.addColorStop(0, `rgba(${r},${g},${b},${alpha})`)\n gradient.addColorStop(0.5, `rgba(${r},${g},${b},${alpha * 0.3})`)\n gradient.addColorStop(1, 'rgba(0,0,0,0)')\n ctx.fillStyle = gradient\n ctx.beginPath()\n ctx.arc(x, y, size, 0, Math.PI * 2)\n ctx.fill()\n }\n\n // Draw stars\n for (let i = 0; i < NUM_STARS; i++) {\n const z = starZ[i]\n if (z < 1) continue\n\n const x = (starX[i] / z) * fov + halfW\n const y = (starY[i] / z) * fov + halfH\n\n if (x < 0 || x > width || y < 0 || y > height) continue\n\n const depth = 1 - z / MAX_DEPTH\n const brightness = depth * depth\n const size = brightness * 2 + 0.5\n\n // Brighter stars\n const c = Math.floor(180 + brightness * 75)\n ctx.fillStyle = `rgba(${c},${c},255,${0.6 + brightness * 0.4})`\n ctx.beginPath()\n ctx.arc(x, y, size, 0, Math.PI * 2)\n ctx.fill()\n\n // Glow for close stars\n if (brightness > 0.5) {\n ctx.fillStyle = `rgba(220,230,255,${(brightness - 0.5) * 0.4})`\n ctx.beginPath()\n ctx.arc(x, y, size * 2.5, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n\n // Subtle vignette\n const vignette = ctx.createRadialGradient(halfW, halfH, height * 0.3, halfW, halfH, height * 0.8)\n vignette.addColorStop(0, 'rgba(0,0,0,0)')\n vignette.addColorStop(1, 'rgba(0,0,0,0.4)')\n ctx.fillStyle = vignette\n ctx.fillRect(0, 0, width, height)\n\n // HUD\n ctx.fillStyle = 'rgba(100,180,255,0.5)'\n ctx.font = '11px monospace'\n ctx.fillText(`FPS: ${fps} | Stars: ${NUM_STARS.toLocaleString()} | Nebulae: ${NUM_NEBULA.toLocaleString()}`, 10, 18)\n ctx.fillText('Mouse up/down to control speed', 10, height - 10)\n\n requestAnimationFrame(animate)\n}\n\nanimate()",
|
|
129
129
|
"language": "tjs",
|
|
130
130
|
"description": "Interactive space flythrough with parallax stars and nebula clouds"
|
|
131
131
|
},
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
"type": "example",
|
|
138
138
|
"group": "featured",
|
|
139
139
|
"order": 0,
|
|
140
|
-
"code": "/*#\n# TJS Grammar Reference\n\nThis example exercises **every TJS feature**. Run it to see\ntests pass and signature validation in action.\n\n## Parameter Syntax\n| Syntax | Meaning |\n|--------|---------|\n|
|
|
140
|
+
"code": "/*#\n# TJS Grammar Reference\n\nThis example exercises **every TJS feature**. Run it to see\ntests pass and signature validation in action.\n\n## Parameter Syntax\n| Syntax | Meaning |\n|--------|---------|\n| `x: 0` | Required number |\n| `x = 0` | Optional, defaults to 0 |\n| `(? x: 0)` | Force input validation |\n| `(! x: 0)` | Skip input validation |\n\n## Return Type Syntax\n| Syntax | Meaning |\n|--------|---------|\n| `-> 10` | Signature test runs at transpile |\n| `-? 10` | + runtime output validation |\n| `-! 10` | Skip signature test |\n*/\n\n// ─────────────────────────────────────────────────────────\n// SIGNATURE TESTS: -> runs at transpile time\n// ─────────────────────────────────────────────────────────\n\n/*#\nDouble a number. The `-> 10` means: double(5) must return 10.\nThis is verified when you save/transpile!\n*/\nfunction double(x: 5) -> 10 {\n return x * 2\n}\n\n/*#\nConcatenate first and last name.\n*/\nfunction fullName(first: 'Jane', last: 'Doe') -> 'Jane Doe' {\n return first + ' ' + last\n}\n\n// ─────────────────────────────────────────────────────────\n// SKIP SIGNATURE TEST: -! when return varies\n// ─────────────────────────────────────────────────────────\n\n/*#\nDivision with error handling. Uses `-!` because the error\npath returns a different shape than success.\n*/\nfunction divide(a: 10, b: 2) -! { ok: true, value: 5 } {\n if (b === 0) {\n return { ok: false, value: 0, error: 'div by zero' }\n }\n return { ok: true, value: a / b }\n}\n\n// ─────────────────────────────────────────────────────────\n// EXPLICIT TESTS: test 'description' { }\n// ─────────────────────────────────────────────────────────\n\ntest 'double works' {\n expect(double(7)).toBe(14)\n expect(double(0)).toBe(0)\n}\n\ntest 'fullName concatenates' {\n expect(fullName('John', 'Smith')).toBe('John Smith')\n}\n\ntest 'divide handles zero' {\n const result = divide(10, 0)\n expect(result.ok).toBe(false)\n}\n\ntest 'divide works normally' {\n const result = divide(20, 4)\n expect(result.ok).toBe(true)\n expect(result.value).toBe(5)\n}\n\n// ─────────────────────────────────────────────────────────\n// UNSAFE FUNCTIONS: (!) skips input validation\n// ─────────────────────────────────────────────────────────\n\n/*#\nFast path - no runtime type checks on inputs.\nUse when you trust the caller (internal code).\n*/\nfunction fastAdd(! a: 0, b: 0) -> 0 {\n return a + b\n}\n\n// ─────────────────────────────────────────────────────────\n// SAFE FUNCTIONS: (?) forces input validation\n// ─────────────────────────────────────────────────────────\n\n/*#\nCritical path - always validate inputs even in unsafe blocks.\n*/\nfunction safeAdd(? a: 0, b: 0) -> 0 {\n return a + b\n}\n\n// ─────────────────────────────────────────────────────────\n// COMPLEX TYPES\n// ─────────────────────────────────────────────────────────\n\n/*#\nObject types are defined by example shape.\n*/\nfunction createPoint(x: 3, y: 4) -> { x: 3, y: 4 } {\n return { x, y }\n}\n\n/*#\nArray types use single-element example.\n*/\nfunction sum(nums: [1, 2, 3]) -> 6 {\n return nums.reduce((a, b) => a + b, 0)\n}\n\ntest 'createPoint returns structure' {\n const p = createPoint(10, 20)\n expect(p.x).toBe(10)\n expect(p.y).toBe(20)\n}\n\ntest 'sum adds array' {\n expect(sum([1, 2, 3, 4])).toBe(10)\n}\n\n// ─────────────────────────────────────────────────────────\n// OUTPUT\n// ─────────────────────────────────────────────────────────\n\nconsole.log('All signature tests passed at transpile time!')\nconsole.log('double.__tjs:', double.__tjs)\nconsole.log('Result:', double(21))",
|
|
141
141
|
"language": "tjs",
|
|
142
142
|
"description": "Comprehensive example exercising all TJS syntax features"
|
|
143
143
|
},
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
"type": "example",
|
|
162
162
|
"group": "fullstack",
|
|
163
163
|
"order": 13,
|
|
164
|
-
"code": "/**\n * # Client Application\n *\n * A 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 *\n * This 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(
|
|
164
|
+
"code": "/**\n * # Client Application\n *\n * A 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 *\n * This 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.$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()",
|
|
165
165
|
"language": "tjs",
|
|
166
166
|
"description": "Frontend that calls the User Service - run after saving user-service!"
|
|
167
167
|
},
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"type": "example",
|
|
210
210
|
"group": "patterns",
|
|
211
211
|
"order": 7,
|
|
212
|
-
"code": "/*#\n## Monadic Error Handling\n\nTJS uses the Result pattern - errors are values, not exceptions.\nThis makes error handling explicit and type-safe.\n\nNote: Using
|
|
212
|
+
"code": "/*#\n## Monadic Error Handling\n\nTJS uses the Result pattern - errors are values, not exceptions.\nThis makes error handling explicit and type-safe.\n\nNote: Using `-!` to skip signature test since error paths\nreturn different shapes.\n*/\ntest 'divide handles zero' {\n const result = divide(10, 0)\n expect(result.ok).toBe(false)\n expect(result.error).toBe('Division by zero')\n}\n\ntest 'divide works normally' {\n const result = divide(10, 2)\n expect(result.ok).toBe(true)\n expect(result.value).toBe(5)\n}\n\nfunction divide(a: 10, b: 2) -! { ok: true, value: 5, error: '' } {\n if (b === 0) {\n return { ok: false, value: 0, error: 'Division by zero' }\n }\n return { ok: true, value: a / b, error: '' }\n}\n\nfunction safeParse(json: '{\"x\":1}') -! { ok: true, data: null, error: '' } {\n try {\n return { ok: true, data: JSON.parse(json), error: '' }\n } catch (e) {\n return { ok: false, data: null, error: e.message }\n }\n}\n\n// Usage - errors are values you can inspect\nconst result = divide(10, 0)\nif (result.ok) {\n console.log('Result:', result.value)\n} else {\n console.log('Error:', result.error)\n}",
|
|
213
213
|
"language": "tjs",
|
|
214
214
|
"description": "Type-safe error handling patterns"
|
|
215
215
|
},
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
"type": "example",
|
|
234
234
|
"group": "patterns",
|
|
235
235
|
"order": 9,
|
|
236
|
-
"code": "/**\n * # Date Formatting with Imports\n *\n * This 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:
|
|
236
|
+
"code": "/**\n * # Date Formatting with Imports\n *\n * This 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')}\\",
|
|
237
237
|
"language": "tjs",
|
|
238
238
|
"description": "Uses date-fns for date formatting via ESM import"
|
|
239
239
|
},
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
"type": "example",
|
|
246
246
|
"group": "patterns",
|
|
247
247
|
"order": 10,
|
|
248
|
-
"code": "/*#\n# Local Module Imports\n\nYou can import from modules saved in the playground!\n\n## How it works:\n1. Save a module (use the Save button, give it a name like \"math\")\n2. Import it by name from another file\n\n## Try it:\n1. First, create and save a module named \"mymath\"
|
|
248
|
+
"code": "/*#\n# Local Module Imports\n\nYou can import from modules saved in the playground!\n\n## How it works:\n1. Save a module (use the Save button, give it a name like \"math\")\n2. Import it by name from another file\n\n## Try it:\n1. First, create and save a module named \"mymath\":",
|
|
249
249
|
"language": "tjs",
|
|
250
250
|
"description": "Import from modules you save in the playground"
|
|
251
251
|
},
|
|
@@ -607,7 +607,7 @@
|
|
|
607
607
|
"title": "CLAUDE.md",
|
|
608
608
|
"filename": "CLAUDE.md",
|
|
609
609
|
"path": "CLAUDE.md",
|
|
610
|
-
"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 type-safe virtual machine (~33KB) for safe execution of untrusted code in any JavaScript environment. It compiles logic chains and AI agents to JSON-serializable ASTs that run sandboxed with fuel (gas) limits.\n\nKey concept: Code travels to data (rather than shipping data to code). Agents are defined as data, not deployed code.\n\n**Two languages in one platform:**\n\n- **TJS** — TypeScript-like syntax with runtime type validation for writing your platform\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\n\n# Testing\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\n\n# Other\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run show-size # Show gzipped bundle size\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 (~2700 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 (~2900 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\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/inference.ts` - Type inference from 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/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 (27 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert commands\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\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\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 'tosijs/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 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsCode = tjs(tjsSource)\n// Generates JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/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**CLI Commands:**\n\n```bash\n# Convert TypeScript to TJS\nbun src/cli/tjs.ts convert input.ts --emit-tjs > output.tjs\n\n# Emit TJS to JavaScript\nbun src/cli/tjs.ts emit input.tjs > output.js\n\n# Run TJS file directly (transpiles and executes)\nbun src/cli/tjs.ts run input.tjs\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\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`, not thrown (prevents exception exploits)\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## 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 in `src/lang/lang.test.ts` (~46KB comprehensive)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\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 `npm 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 Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 || null) { } // number or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\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// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### Equality Operators\n\nTJS redefines equality to be structural by default, fixing JavaScript's confusing `==` vs `===` semantics.\n\n**Normal TJS Mode (default):**\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator handles `==`/`!=` with structural semantics at runtime\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\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"
|
|
610
|
+
"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 type-safe virtual machine (~33KB) for safe execution of untrusted code in any JavaScript environment. It compiles logic chains and AI agents to JSON-serializable ASTs that run sandboxed with fuel (gas) limits.\n\nKey concept: Code travels to data (rather than shipping data to code). Agents are defined as data, not deployed code.\n\n**Two languages in one platform:**\n\n- **TJS** — TypeScript-like syntax with runtime type validation for writing your platform\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\n\n# Testing\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\n\n# Other\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run show-size # Show gzipped bundle size\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 (~2700 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 (~2900 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\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/inference.ts` - Type inference from 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 (27 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert commands\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\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\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 'tosijs/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 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsCode = tjs(tjsSource)\n// Generates JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/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**CLI Commands:**\n\n```bash\n# Convert TypeScript to TJS\nbun src/cli/tjs.ts convert input.ts --emit-tjs > output.tjs\n\n# Emit TJS to JavaScript\nbun src/cli/tjs.ts emit input.tjs > output.js\n\n# Run TJS file directly (transpiles and executes)\nbun src/cli/tjs.ts run input.tjs\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\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`, not thrown (prevents exception exploits)\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## 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 in `src/lang/lang.test.ts` (~46KB comprehensive)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\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 `npm 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 Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 || null) { } // number or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\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// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### Equality Operators\n\nTJS redefines equality to be structural by default, fixing JavaScript's confusing `==` vs `===` semantics.\n\n**Normal TJS Mode (default):**\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator handles `==`/`!=` with structural semantics at runtime\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), c => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### Current Limitations\n\n- No SIMD support yet (planned - see TODO.md)\n- Memory operations require manual management\n- No imports/exports beyond the function itself\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"
|
|
611
611
|
},
|
|
612
612
|
{
|
|
613
613
|
"title": "Context: Working with tosijs-schema",
|
|
@@ -775,7 +775,7 @@
|
|
|
775
775
|
"title": "TJS-Lang TODO",
|
|
776
776
|
"filename": "TODO.md",
|
|
777
777
|
"path": "TODO.md",
|
|
778
|
-
"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- [ ] 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- [ ]
|
|
778
|
+
"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- [ ] 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- [ ] WASM SIMD support (v128)\n - Without SIMD, WASM shows no meaningful speedup vs JS JIT for scalar operations\n - SIMD enables 2x f64 or 4x f32 operations per instruction\n - Key for: particle systems, audio processing, image manipulation, physics\n - Requires: v128 type, f64x2/f32x4 ops, loop vectorization detection\n - This is the main justification for WASM in compute-heavy workloads\n- [ ] WASM SIMD vector search (batteries)\n - Replace JS vectorSearch battery with WASM SIMD implementation\n - Dot product / cosine similarity maps perfectly to f32x4 ops\n - Process 4 dimensions per instruction vs 1 in scalar\n - Auto-detect SIMD support, fallback to JS\n - Real-world use case: RAG, semantic search, embeddings in browser\n - Expected speedup: 3-4x for similarity calculations\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- [x] Remove bd (beads) issue tracker, replace with TODO.md\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 (JS is now the default)\n\n- [x] Invert mode system - JS semantics are default, improvements opt-in\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"
|
|
779
779
|
},
|
|
780
780
|
{
|
|
781
781
|
"title": "TJS: Typed JavaScript",
|
|
@@ -789,4 +789,4 @@
|
|
|
789
789
|
"path": ".haltija.md",
|
|
790
790
|
"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"
|
|
791
791
|
}
|
|
792
|
-
]
|
|
792
|
+
]
|
|
@@ -23,12 +23,7 @@ import {
|
|
|
23
23
|
// Available modules for autocomplete introspection
|
|
24
24
|
// These are the actual runtime values that can be introspected
|
|
25
25
|
import { codeMirror, CodeMirror } from '../../editors/codemirror/component'
|
|
26
|
-
import {
|
|
27
|
-
tjs,
|
|
28
|
-
compileWasmBlocksForIframe,
|
|
29
|
-
generateWasmInstantiationCode,
|
|
30
|
-
type TJSTranspileOptions,
|
|
31
|
-
} from '../../src/lang'
|
|
26
|
+
import { tjs, type TJSTranspileOptions } from '../../src/lang'
|
|
32
27
|
import { generateDocsMarkdown } from './docs-utils'
|
|
33
28
|
import { extractImports, generateImportMap, resolveImports } from './imports'
|
|
34
29
|
import { ModuleStore, type ValidationResult } from './module-store'
|
|
@@ -1059,27 +1054,20 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
1059
1054
|
|
|
1060
1055
|
this.parts.statusBar.textContent = 'Running...'
|
|
1061
1056
|
|
|
1062
|
-
// Compile WASM blocks if present and WASM is enabled
|
|
1063
|
-
let wasmInstantiationCode = ''
|
|
1064
1057
|
try {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
this.log(`WASM: ${wasmResult.failed} failed (using JS fallback)`)
|
|
1058
|
+
// Log WASM compilation results (WASM is now compiled at transpile time)
|
|
1059
|
+
const wasmCompiled = this.lastTranspileResult.wasmCompiled
|
|
1060
|
+
if (wasmCompiled && wasmCompiled.length > 0) {
|
|
1061
|
+
const success = wasmCompiled.filter((w) => w.success).length
|
|
1062
|
+
const failed = wasmCompiled.filter((w) => !w.success).length
|
|
1063
|
+
if (success > 0) {
|
|
1064
|
+
this.log(`WASM: ${success} block(s) compiled at transpile time`)
|
|
1065
|
+
}
|
|
1066
|
+
if (failed > 0) {
|
|
1067
|
+
this.log(`WASM: ${failed} failed (using JS fallback)`)
|
|
1068
|
+
for (const w of wasmCompiled.filter((w) => !w.success)) {
|
|
1069
|
+
this.log(` ${w.id}: ${w.error}`)
|
|
1078
1070
|
}
|
|
1079
|
-
} else {
|
|
1080
|
-
this.log(
|
|
1081
|
-
`WASM disabled - using JS fallback for ${wasmBlocks.length} block(s)`
|
|
1082
|
-
)
|
|
1083
1071
|
}
|
|
1084
1072
|
}
|
|
1085
1073
|
|
|
@@ -1138,6 +1126,9 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
1138
1126
|
if (parent.runAgent) window.runAgent = parent.runAgent.bind(parent);
|
|
1139
1127
|
if (parent.getIdToken) window.getIdToken = parent.getIdToken.bind(parent);
|
|
1140
1128
|
|
|
1129
|
+
// TJS runtime stub - must stay in sync with src/lang/runtime.ts
|
|
1130
|
+
// TODO: Eliminate this once transpiler emits self-contained code
|
|
1131
|
+
// See: src/lang/emitters/js.ts for the plan to inline runtime functions
|
|
1141
1132
|
globalThis.__tjs = {
|
|
1142
1133
|
version: '0.0.0',
|
|
1143
1134
|
pushStack: () => {},
|
|
@@ -1189,8 +1180,8 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
1189
1180
|
};
|
|
1190
1181
|
|
|
1191
1182
|
try {
|
|
1192
|
-
//
|
|
1193
|
-
|
|
1183
|
+
// WASM blocks are pre-compiled and embedded in the transpiled code
|
|
1184
|
+
// They auto-instantiate via the async IIFE at the top of the code
|
|
1194
1185
|
|
|
1195
1186
|
const __execStart = performance.now();
|
|
1196
1187
|
${codeWithoutImports}
|