tjs-lang 0.2.7 → 0.3.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/demo/docs.json +32 -26
- package/demo/src/examples.ts +23 -83
- package/demo/src/playground-shared.ts +666 -0
- package/demo/src/tjs-playground.ts +65 -550
- package/demo/src/ts-examples.ts +5 -4
- package/demo/src/ts-playground.ts +50 -414
- package/dist/index.js +143 -160
- package/dist/index.js.map +12 -12
- package/dist/src/lang/emitters/js.d.ts +34 -2
- package/dist/src/lang/index.d.ts +1 -1
- package/dist/src/lang/types.d.ts +1 -1
- package/dist/src/types/Type.d.ts +3 -1
- package/dist/tjs-full.js +143 -160
- package/dist/tjs-full.js.map +12 -12
- package/dist/tjs-transpiler.js +122 -55
- package/dist/tjs-transpiler.js.map +9 -8
- package/dist/tjs-vm.js +14 -14
- package/dist/tjs-vm.js.map +5 -5
- package/docs/docs.json +792 -0
- package/docs/index.js +2652 -2835
- package/docs/index.js.map +11 -10
- package/editors/codemirror/ajs-language.ts +27 -1
- package/editors/codemirror/autocomplete.test.ts +3 -3
- package/package.json +1 -1
- package/src/lang/codegen.test.ts +11 -11
- package/src/lang/emitters/from-ts.ts +1 -1
- package/src/lang/emitters/js.ts +228 -4
- package/src/lang/index.ts +0 -3
- package/src/lang/inference.ts +40 -8
- package/src/lang/lang.test.ts +192 -35
- package/src/lang/roundtrip.test.ts +155 -0
- package/src/lang/runtime.ts +7 -0
- package/src/lang/types.ts +2 -0
- package/src/lang/typescript-syntax.test.ts +6 -4
- package/src/lang/wasm.test.ts +20 -0
- package/src/lang/wasm.ts +143 -0
- package/src/types/Type.test.ts +64 -0
- package/src/types/Type.ts +22 -1
- package/src/use-cases/transpiler-integration.test.ts +10 -10
- package/src/vm/atoms/batteries.ts +2 -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
|
},
|
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
"type": "example",
|
|
174
174
|
"group": "fullstack",
|
|
175
175
|
"order": 14,
|
|
176
|
-
"code": "/**\n * # Todo API Service\n *\n * A REST-style API for todo management.\n * Demonstrates a more complete service pattern.\n */\n\n// Simulated persistence layer\nconst todos = new Map()\nlet nextId = 1\n\n// Types\
|
|
176
|
+
"code": "/**\n * # Todo API Service\n *\n * A REST-style API for todo management.\n * Demonstrates 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' })\n -> { 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 })\n -> { 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: true } | {})\n -> { todos: [{ id: 0, title: '', completed: false, createdAt: '' }] } {\n let items = [...todos.values()]\n\n if ('completed' in input) {\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 })\n -> { 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 })\n -> { 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({}))",
|
|
177
177
|
"language": "tjs",
|
|
178
178
|
"description": "Complete REST-style Todo API with persistence"
|
|
179
179
|
},
|
|
@@ -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')}`\n }\n}",
|
|
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\":\n\n
|
|
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\":\n\n export function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n\n export function multiply(a: 0, b: 0) -> 0 {\n return a * b\n }\n\n2. Then run this code (it imports from your saved module)\n*/\n\n// This imports from a module you saved in the playground\n// Change 'mymath' to match whatever name you used when saving\nimport { add, multiply } from 'mymath'\n\nfunction calculate(x: 0, y: 0) -> 0 {\n // (x + y) * 2\n return multiply(add(x, y), 2)\n}\n\ntest 'calculate combines add and multiply' {\n expect(calculate(3, 4)).toBe(14) // (3 + 4) * 2 = 14\n}\n\nconsole.log('calculate(3, 4) =', calculate(3, 4))\nconsole.log('calculate(10, 5) =', calculate(10, 5))",
|
|
249
249
|
"language": "tjs",
|
|
250
250
|
"description": "Import from modules you save in the playground"
|
|
251
251
|
},
|
|
@@ -294,7 +294,7 @@
|
|
|
294
294
|
"group": "advanced",
|
|
295
295
|
"order": 14,
|
|
296
296
|
"requiresApi": true,
|
|
297
|
-
"code": "function mathAssistant({ question = 'What is 23 * 47 + 156?' }) {\n // First, ask LLM to extract the calculation\n let extractPrompt
|
|
297
|
+
"code": "function mathAssistant({ question = 'What is 23 * 47 + 156?' }) {\n // First, ask LLM to extract the calculation\n let extractPrompt =\n 'Extract the math expression from this question. Return ONLY the expression, nothing else.\\nQuestion: ' +\n question\n let expression = llmPredict({ prompt: extractPrompt })\n\n // Evaluate the expression (simple eval simulation)\n let calcPrompt =\n 'Calculate: ' + expression + '\\nReturn ONLY the numeric result.'\n let calcResult = llmPredict({ prompt: calcPrompt })\n\n // Format the answer\n let answerPrompt =\n 'The user asked: \"' +\n question +\n '\"\\nThe calculated result is: ' +\n calcResult +\n '\\nWrite a brief, friendly response with the answer.'\n let answer = llmPredict({ prompt: answerPrompt })\n\n return {\n question,\n expression: expression.trim(),\n result: calcResult.trim(),\n answer,\n }\n}",
|
|
298
298
|
"language": "javascript",
|
|
299
299
|
"description": "LLM uses a calculator tool (requires llm capability)"
|
|
300
300
|
},
|
|
@@ -307,7 +307,7 @@
|
|
|
307
307
|
"group": "advanced",
|
|
308
308
|
"order": 15,
|
|
309
309
|
"requiresApi": true,
|
|
310
|
-
"code": "function collaborativeWriting({ topic = 'the future of renewable energy' }) {\n // Agent 1: Research Agent - generates key points\n let researchPrompt
|
|
310
|
+
"code": "function collaborativeWriting({ topic = 'the future of renewable energy' }) {\n // Agent 1: Research Agent - generates key points\n let researchPrompt =\n 'You are a research agent. Generate 3 key facts or points about: ' +\n topic +\n '\\nFormat as a numbered list. Be concise.'\n let research = llmPredict({ prompt: researchPrompt })\n\n // Agent 2: Writer Agent - creates content from research\n let writerPrompt =\n 'You are a writer agent. Using these research points:\\n\\n' +\n research +\n '\\n\\nWrite a short, engaging paragraph (2-3 sentences) about ' +\n topic +\n '.\\nMake it informative and accessible.'\n let article = llmPredict({ prompt: writerPrompt })\n\n // Agent 3: Editor Agent - reviews and improves\n let editorPrompt =\n 'You are an editor agent. Review this draft:\\n\\n\"' +\n article +\n '\"\\n\\nSuggest one specific improvement. Then provide the improved version.\\nFormat: \"Suggestion: [your suggestion]\\n\\nImproved: [improved text]\"'\n let edited = llmPredict({ prompt: editorPrompt })\n\n return {\n topic,\n researchPoints: research,\n firstDraft: article,\n editedVersion: edited,\n }\n}",
|
|
311
311
|
"language": "javascript",
|
|
312
312
|
"description": "Two agents collaborate on a task (requires llm capability)"
|
|
313
313
|
},
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
"group": "advanced",
|
|
347
347
|
"order": 19,
|
|
348
348
|
"requiresApi": true,
|
|
349
|
-
"code": "function solveWithCode({ problem = 'Calculate the 10th Fibonacci number' }) {\n // System prompt with AsyncJS rules and example\n let systemContext
|
|
349
|
+
"code": "function solveWithCode({ problem = 'Calculate the 10th Fibonacci number' }) {\n // System prompt with AsyncJS rules and example\n let systemContext =\n 'You write AsyncJS code. AsyncJS is a JavaScript subset.\\n\\nRULES:\\n- NO: async, await, new, class, this, var, for loops\\n- Use let for variables, while for loops\\n- Return an object: return { result }\\n\\nEXAMPLE (factorial):\\nfunction solve() {\\n let result = 1\\n let i = 5\\n while (i > 1) {\\n result = result * i\\n i = i - 1\\n }\\n return { result }\\n}\\n\\nReturn ONLY the function code, nothing else.'\n\n let prompt =\n systemContext + '\\n\\nWrite a function called \"solve\" that: ' + problem\n\n let response = llmPredict({ prompt })\n\n // Clean up code - remove markdown fences, fix escapes, extract function\n let code = response\n code = code.replace(/",
|
|
350
350
|
"language": "javascript",
|
|
351
351
|
"description": "LLM writes and runs code to solve a problem (requires llm capability)"
|
|
352
352
|
},
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
"group": "advanced",
|
|
360
360
|
"order": 20,
|
|
361
361
|
"requiresApi": true,
|
|
362
|
-
"code": "function generateCode({ task = 'Calculate the factorial of n' }) {\n // System prompt with AsyncJS rules and complete example\n let systemContext
|
|
362
|
+
"code": "function generateCode({ task = 'Calculate the factorial of n' }) {\n // System prompt with AsyncJS rules and complete example\n let systemContext =\n 'You write AsyncJS code. AsyncJS is a subset of JavaScript.\\n\\nRULES:\\n- Types by example: fn(n: 5) means required number param with example value 5\\n- NO: async, await, new, class, this, var, for, generator functions (function*)\\n- Use let for variables, while for loops\\n- Return an object: return { result }\\n\\nEXAMPLE - calculating sum of 1 to n:\\nfunction sumTo(n: 10) {\\n let sum = 0\\n let i = 1\\n while (i <= n) {\\n sum = sum + i\\n i = i + 1\\n }\\n return { result: sum }\\n}'\n\n let schema = Schema.response('generated_code', {\n code: '',\n description: '',\n })\n\n let prompt =\n systemContext +\n '\\n\\nWrite an AsyncJS function for: ' +\n task +\n '\\n\\nReturn ONLY valid AsyncJS code in the code field. Must start with \"function\" and use while loops (not for loops).'\n\n let response = llmPredict({ prompt, options: { responseFormat: schema } })\n let result = JSON.parse(response)\n\n // Clean up any markdown fences and fix escaped newlines\n let code = result.code\n code = code.replace(/",
|
|
363
363
|
"language": "javascript",
|
|
364
364
|
"description": "LLM writes AsyncJS code from a description (requires llm capability)"
|
|
365
365
|
},
|
|
@@ -371,7 +371,7 @@
|
|
|
371
371
|
"type": "example",
|
|
372
372
|
"group": "api",
|
|
373
373
|
"order": 7,
|
|
374
|
-
"code": "function getWeather({ lat = 37.7749, lon = -122.4194 }) {\n let url
|
|
374
|
+
"code": "function getWeather({ lat = 37.7749, lon = -122.4194 }) {\n let url =\n 'https://api.open-meteo.com/v1/forecast?latitude=' +\n lat +\n '&longitude=' +\n lon +\n '¤t_weather=true'\n let response = httpFetch({ url, cache: 1800 })\n let weather = response.current_weather\n return { weather }\n}",
|
|
375
375
|
"language": "javascript",
|
|
376
376
|
"description": "Fetch weather data (no API key needed)"
|
|
377
377
|
},
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
"type": "example",
|
|
384
384
|
"group": "api",
|
|
385
385
|
"order": 8,
|
|
386
|
-
"code": "function searchMusic({ query = 'Beatles', limit = 5 }) {\n let url
|
|
386
|
+
"code": "function searchMusic({ query = 'Beatles', limit = 5 }) {\n let url =\n 'https://itunes.apple.com/search?term=' +\n query +\n '&limit=' +\n limit +\n '&media=music'\n let response = httpFetch({ url, cache: 3600 })\n let tracks = response.results.map((x) => ({\n artist: x.artistName,\n track: x.trackName,\n album: x.collectionName,\n }))\n return { tracks }\n}",
|
|
387
387
|
"language": "javascript",
|
|
388
388
|
"description": "Search Apple iTunes catalog"
|
|
389
389
|
},
|
|
@@ -395,7 +395,7 @@
|
|
|
395
395
|
"type": "example",
|
|
396
396
|
"group": "api",
|
|
397
397
|
"order": 9,
|
|
398
|
-
"code": "function searchRepos({ query = 'tosijs', perPage = 5 }) {\n let url
|
|
398
|
+
"code": "function searchRepos({ query = 'tosijs', perPage = 5 }) {\n let url =\n 'https://api.github.com/search/repositories?q=' +\n query +\n '&per_page=' +\n perPage +\n '&sort=stars'\n let response = httpFetch({ url, cache: 300 })\n let repos = response.items.map((x) => ({\n name: x.full_name,\n stars: x.stargazers_count,\n description: x.description,\n }))\n return { repos }\n}",
|
|
399
399
|
"language": "javascript",
|
|
400
400
|
"description": "Search GitHub repositories"
|
|
401
401
|
},
|
|
@@ -512,7 +512,7 @@
|
|
|
512
512
|
"group": "featured",
|
|
513
513
|
"order": 13,
|
|
514
514
|
"requiresApi": true,
|
|
515
|
-
"code": "function findCovers({ song = 'Yesterday', artist = 'Beatles' }) {\n // Search iTunes for the song\n let query = song + ' ' + artist\n let url
|
|
515
|
+
"code": "function findCovers({ song = 'Yesterday', artist = 'Beatles' }) {\n // Search iTunes for the song\n let query = song + ' ' + artist\n let url =\n 'https://itunes.apple.com/search?term=' + query + '&limit=25&media=music'\n let response = httpFetch({ url, cache: 3600 })\n\n // Format results for LLM analysis\n let results = response.results || []\n let tracks = results.map(\n (x) =>\n '\"' + x.trackName + '\" by ' + x.artistName + ' (' + x.collectionName + ')'\n )\n let trackList = tracks.join('\\n')\n\n // Schema.response from example - much cleaner!\n let schema = Schema.response('cover_versions', {\n covers: [{ track: '', artist: '', album: '' }],\n })\n\n let prompt =\n 'Search results for \"' +\n song +\n '\" by ' +\n artist +\n ':\\n\\n' +\n trackList +\n '\\n\\nList cover versions (tracks NOT by ' +\n artist +\n ').'\n\n let llmResponse = llmPredict({ prompt, options: { responseFormat: schema } })\n let parsed = JSON.parse(llmResponse)\n return { originalArtist: artist, song, covers: parsed.covers }\n}",
|
|
516
516
|
"language": "javascript",
|
|
517
517
|
"description": "LLM analyzes API data (requires llm capability)"
|
|
518
518
|
},
|
|
@@ -550,7 +550,7 @@
|
|
|
550
550
|
"group": "llm",
|
|
551
551
|
"order": 11,
|
|
552
552
|
"requiresApi": true,
|
|
553
|
-
"code": "function summarize({ source = 'coffee-origins' }) {\n // Fetch text from our sample documents\n // Options: 'coffee-origins', 'ai-history', 'renewable-energy'\n let url =
|
|
553
|
+
"code": "function summarize({ source = 'coffee-origins' }) {\n // Fetch text from our sample documents\n // Options: 'coffee-origins', 'ai-history', 'renewable-energy'\n let url = '/texts/' + source + '.txt'\n let text = httpFetch({ url })\n\n let prompt = 'Summarize the following text in 2-3 sentences:\\n\\n' + text\n let summary = llmPredict({ prompt })\n return { source, summary }\n}",
|
|
554
554
|
"language": "javascript",
|
|
555
555
|
"description": "Fetch and summarize text (requires llm capability)"
|
|
556
556
|
},
|
|
@@ -563,7 +563,7 @@
|
|
|
563
563
|
"group": "llm",
|
|
564
564
|
"order": 12,
|
|
565
565
|
"requiresApi": true,
|
|
566
|
-
"code": "function extractInfo({
|
|
566
|
+
"code": "function extractInfo({\n text = 'John Smith is a 35-year-old software engineer from San Francisco who loves hiking and photography.',\n}) {\n // Schema.response builds responseFormat from an example\n let schema = Schema.response('person_info', {\n name: '',\n age: 0,\n occupation: '',\n location: '',\n hobbies: [''],\n })\n\n let prompt = 'Extract person info from this text: ' + text\n let response = llmPredict({ prompt, options: { responseFormat: schema } })\n let person = JSON.parse(response)\n return { person }\n}",
|
|
567
567
|
"language": "javascript",
|
|
568
568
|
"description": "Get structured JSON from LLM (requires llm capability)"
|
|
569
569
|
},
|
|
@@ -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",
|
|
@@ -645,6 +645,12 @@
|
|
|
645
645
|
"filename": "from-ts.test.ts",
|
|
646
646
|
"path": "src/lang/from-ts.test.ts"
|
|
647
647
|
},
|
|
648
|
+
{
|
|
649
|
+
"title": "icebox",
|
|
650
|
+
"filename": "tasks-f6d70f.md",
|
|
651
|
+
"path": ".haltija/tasks-f6d70f.md",
|
|
652
|
+
"text": "# icebox\n\n# queued\n\n# in_progress\n\n# blocked\n\n# review\n\n# done\n\n# trash\n"
|
|
653
|
+
},
|
|
648
654
|
{
|
|
649
655
|
"text": "# TJS Platform Cloud Functions\n\nCloud Functions for the TJS Platform.\nModular architecture with separate concerns:\n- crypto.tjs - Encryption/decryption\n- llm.tjs - LLM capability (multi-provider)\n- schema.tjs - JSON schema validation\n- rbac.tjs - Security rules & access shortcuts\n- indexes.tjs - Automatic index management\n- store.tjs - Store capability with RBAC\n- routing.tjs - URL pattern matching & function cache\n\n## Future Work\n- Benchmark `safety none` vs `safety inputs` - likely negligible overhead for small payloads\n\n---\n\n## Get User API Keys\n\nLoads and decrypts the user's API keys from Firestore.\n\n---\n\n## Health Check\n\nSimple endpoint to verify functions are deployed and running.\n\n---\n\n## Agent Run Endpoint\n\nUniversal AJS endpoint - accepts code, args, and fuel limit.\nExecutes the code in a sandboxed VM with user's API keys as capabilities.\n\n---\n\n## REST Agent Endpoint\n\nSame as agentRun but as a simple POST endpoint.\nAuth via Bearer token (Firebase ID token).\n\n---\n\n## Page Endpoint\n\nServes stored functions based on URL routing.\nMatches incoming path against stored function URL patterns.\nExecutes matched function's AJS code and returns with appropriate content-type.",
|
|
650
656
|
"title": "index (inline docs)",
|
|
@@ -775,7 +781,7 @@
|
|
|
775
781
|
"title": "TJS-Lang TODO",
|
|
776
782
|
"filename": "TODO.md",
|
|
777
783
|
"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- [ ]
|
|
784
|
+
"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
785
|
},
|
|
780
786
|
{
|
|
781
787
|
"title": "TJS: Typed JavaScript",
|
|
@@ -789,4 +795,4 @@
|
|
|
789
795
|
"path": ".haltija.md",
|
|
790
796
|
"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
797
|
}
|
|
792
|
-
]
|
|
798
|
+
]
|