tjs-lang 0.5.1 → 0.5.3

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 CHANGED
@@ -67,9 +67,9 @@
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 (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}",
70
+ "code": "/*#\n## Types by Example\n\nIn TJS, the example value after `:` IS the type:\n\n| Syntax | Type |\n|--------|------|\n| `name: 'Alice'` | string (required) |\n| `count: 42` | integer |\n| `rate: 3.14` | number (float) |\n| `index: +0` | non-negative integer |\n| `name = 'default'` | string (optional, defaults to 'default') |\n| `data: { x: 0, y: 0 }` | object with shape |\n*/\n\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n\n// Numeric type narrowing — all valid JS syntax\nfunction clampIndex(index: +0, max: +0) -> +0 {\n return Math.min(index, max)\n}\n\nfunction mix(a: 0.0, b: 0.0, t: 0.0) -> 0.0 {\n return a + (b - a) * t\n}\n\ntest 'greet says hello' {\n expect(greet('TJS')).toBe('Hello, TJS!')\n}\n\ntest 'type errors are values, not exceptions' {\n const err = greet(42)\n expect(err instanceof Error).toBe(true)\n}\n\ntest 'numeric types are precise' {\n expect(clampIndex(5, 10)).toBe(5)\n // negative fails non-negative integer check\n expect(clampIndex(-1, 10) instanceof Error).toBe(true)\n // float fails integer check\n expect(clampIndex(3.5, 10) instanceof Error).toBe(true)\n}\n\ntest 'floats accept any number' {\n expect(mix(0, 100, 0.5)).toBe(50)\n}",
71
71
  "language": "tjs",
72
- "description": "Simple typed greeting function with docs and tests"
72
+ "description": "Types-by-example: the value IS the type annotation"
73
73
  },
74
74
  {
75
75
  "title": "Required vs Optional",
@@ -151,7 +151,7 @@
151
151
  "group": "docs",
152
152
  "order": 0,
153
153
  "navTitle": "Documentation",
154
- "text": "<!--{\"section\": \"tjs\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"Documentation\"}-->\n\n# TJS: Typed JavaScript\n\n_Types as Examples. Zero Build. Runtime Metadata._\n\n---\n\n## What is TJS?\n\nTJS is a typed superset of JavaScript where **types are concrete values**, not abstract annotations.\n\n```typescript\n// TypeScript: abstract type annotation\nfunction greet(name: string): string\n\n// TJS: concrete example value\nfunction greet(name: 'World') -> '' { return `Hello, ${name}!` }\n```\n\nThe example `'World'` tells TJS that `name` is a string. The example `''` tells TJS the return type is a string. Types are inferred from the examples you provide.\n\nTJS transpiles to JavaScript with embedded `__tjs` metadata, enabling runtime type checking, autocomplete from live objects, and documentation generation.\n\n---\n\n## The Compiler\n\nTJS compiles in the browser. No webpack, no node_modules, no build server.\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst code = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n\n// Returns transpiled JavaScript with __tjs metadata\n```\n\nYou can also use the CLI:\n\n```bash\nbun src/cli/tjs.ts check file.tjs # Parse and type check\nbun src/cli/tjs.ts run file.tjs # Transpile and execute\nbun src/cli/tjs.ts emit file.tjs # Output transpiled JS\nbun src/cli/tjs.ts types file.tjs # Output type metadata\n```\n\n---\n\n## Syntax\n\n### Parameter Types (Colon Syntax)\n\n> **Not TypeScript.** TJS colon syntax looks like TypeScript but has different\n> semantics. The value after `:` is a **concrete example**, not a type name.\n> Write `name: 'Alice'` (example value), not `name: string` (type name).\n> TJS infers the type from the example: `'Alice'` → string, `0` → integer,\n> `true` → boolean.\n\nRequired parameters use colon syntax with an example value:\n\n```typescript\nfunction greet(name: 'Alice') {} // name is required, type: string\nfunction calculate(value: 0) {} // value is required, type: integer\nfunction measure(rate: 0.0) {} // rate is required, type: number (float)\nfunction count(n: +0) {} // n is required, type: non-negative integer\nfunction toggle(flag: true) {} // flag is required, type: boolean\n```\n\n### Numeric Types\n\nTJS distinguishes three numeric types using valid JavaScript syntax:\n\n```typescript\nfunction process(\n rate: 3.14, // number (float) -- has a decimal point\n count: 42, // integer -- whole number, no decimal\n index: +0 // non-negative integer -- prefixed with +\n) {}\n```\n\n| You Write | Type Inferred | Runtime Validation |\n| --------- | ---------------------- | ------------------------------- |\n| `3.14` | `number` (float) | `typeof x === 'number'` |\n| `0.0` | `number` (float) | `typeof x === 'number'` |\n| `42` | `integer` | `Number.isInteger(x)` |\n| `0` | `integer` | `Number.isInteger(x)` |\n| `+20` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `+0` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `-5` | `integer` | `Number.isInteger(x)` |\n| `-3.5` | `number` (float) | `typeof x === 'number'` |\n\nAll of these are valid JavaScript expressions. TJS reads the syntax more\ncarefully to give you finer-grained type checking than JS or TypeScript\nprovide natively.\n\n### Optional Parameters (Default Values)\n\nOptional parameters use `=` with a default value:\n\n```typescript\nfunction greet(name = 'World') {} // name is optional, defaults to 'World'\nfunction calculate(value = 0) {} // value is optional, defaults to 0 (integer)\n```\n\n### TypeScript-Style Optional\n\nYou can also use `?:` syntax:\n\n```typescript\nfunction greet(name?: '') {} // same as name = ''\n```\n\n### Object Parameters\n\nObject shapes are defined by example:\n\n```typescript\nfunction createUser(user: { name: ''; age: 0 }) {}\n// user must be an object with string name and number age\n```\n\n### Nullable Types\n\nUse `||` for union with null:\n\n```typescript\nfunction find(id: 0 || null) { } // number or null\n```\n\n### Return Types (Arrow Syntax)\n\nReturn types use `->`:\n\n```typescript\nfunction add(a: 0, b: 0) -> 0 {\n return a + b\n}\n\nfunction getUser(id: 0) -> { name: '', age: 0 } {\n return { name: 'Alice', age: 30 }\n}\n```\n\n### Array Types\n\nArrays use bracket syntax with an example element:\n\n```typescript\nfunction sum(numbers: [0]) -> 0 { // array of numbers\n return numbers.reduce((a, b) => a + b, 0)\n}\n\nfunction names(users: [{ name: '' }]) { // array of objects\n return users.map(u => u.name)\n}\n```\n\n---\n\n## Safety Markers\n\n### Unsafe Functions\n\nSkip validation for hot paths:\n\n```typescript\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n```\n\nThe `!` marker after the function name skips input validation.\n\n### Safe Functions\n\nExplicit validation (for emphasis):\n\n```typescript\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n```\n\n### Unsafe Blocks\n\nSkip validation for a block of code:\n\n```typescript\nunsafe {\n fastPath(data)\n anotherHotFunction(moreData)\n}\n```\n\n### Module Safety Directive\n\nSet the default validation level for an entire file:\n\n```typescript\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n---\n\n## Type System\n\n### Type()\n\nDefine named types with predicates:\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate\nType PositiveNumber {\n description: 'a positive number'\n example: 1\n predicate(x) { return x > 0 }\n}\n```\n\nTypes can be used in function signatures:\n\n```typescript\nfunction greet(name: Name) -> '' {\n return `Hello, ${name}!`\n}\n```\n\n### Generic()\n\nRuntime-checkable generics:\n\n```typescript\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// 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### Union()\n\nDiscriminated unions:\n\n```typescript\nconst Shape = Union('kind', {\n circle: { radius: 0 },\n rectangle: { width: 0, height: 0 }\n})\n\nfunction area(shape: Shape) -> 0 {\n if (shape.kind === 'circle') {\n return Math.PI * shape.radius ** 2\n }\n return shape.width * shape.height\n}\n```\n\n### Enum()\n\nString or numeric enums:\n\n```typescript\nconst Status = Enum(['pending', 'active', 'completed'])\nconst Priority = Enum({ low: 1, medium: 2, high: 3 })\n\nfunction setStatus(status: Status) {}\n```\n\n---\n\n## Structural Equality: Is / IsNot\n\nJavaScript's `==` is broken (type coercion). TJS provides structural equality:\n\n```typescript\n// Structural comparison - no coercion\n[1, 2] Is [1, 2] // true\n5 Is \"5\" // false (different types)\n{ a: 1 } Is { a: 1 } // true\n\n// Arrays compared element-by-element\n[1, [2, 3]] Is [1, [2, 3]] // true\n\n// Negation\n5 IsNot \"5\" // true\n```\n\n### Custom Equality\n\nObjects can define custom equality in two ways:\n\n**1. `[tjsEquals]` symbol protocol** (preferred for Proxies and advanced use):\n\n```typescript\nimport { tjsEquals } from 'tjs-lang/lang'\n\n// A proxy that delegates equality to its target\nconst target = { x: 1, y: 2 }\nconst proxy = new Proxy({\n [tjsEquals](other) { return target Is other }\n}, {})\n\nproxy == { x: 1, y: 2 } // true — delegates to target\n```\n\n**2. `.Equals` method** (simple, works on any object or class):\n\n```typescript\nclass Point {\n constructor(x: 0, y: 0) { this.x = x; this.y = y }\n Equals(other) { return this.x === other.x && this.y === other.y }\n}\n\nPoint(1, 2) Is Point(1, 2) // true (uses .Equals)\n```\n\n**Priority:** `[tjsEquals]` symbol > `.Equals` method > structural comparison.\n\nThe symbol is `Symbol.for('tjs.equals')`, so it works across realms. Access it\nvia `import { tjsEquals } from 'tjs-lang/lang'` or `__tjs.tjsEquals` at runtime.\n\n---\n\n## Classes\n\n### Callable Without `new`\n\nTJS classes are callable without the `new` keyword:\n\n```typescript\nclass User {\n constructor(name: '') {\n this.name = name\n }\n}\n\n// Both work identically:\nconst u1 = User('Alice') // TJS way - clean\nconst u2 = new User('Alice') // Also works (linter warns)\n```\n\n### Private Fields\n\nUse `#` for private fields:\n\n```typescript\nclass Counter {\n #count = 0\n\n increment() {\n this.#count++\n }\n get value() {\n return this.#count\n }\n}\n```\n\nWhen converting from TypeScript, `private foo` becomes `#foo`.\n\n### Getters and Setters\n\nAsymmetric types are captured:\n\n```typescript\nclass Timestamp {\n #value\n\n constructor(initial: '' | 0 | null) {\n this.#value = initial === null ? new Date() : new Date(initial)\n }\n\n set value(v: '' | 0 | null) {\n this.#value = v === null ? new Date() : new Date(v)\n }\n\n get value() {\n return this.#value\n }\n}\n\nconst ts = Timestamp('2024-01-15')\nts.value = 0 // SET accepts: string | number | null\nts.value // GET returns: Date\n```\n\n---\n\n## Polymorphic Functions\n\nMultiple function declarations with the same name are automatically merged into a dispatcher that routes by argument count and type:\n\n```typescript\nfunction describe(value: 0) {\n return 'number: ' + value\n}\nfunction describe(value: '') {\n return 'string: ' + value\n}\nfunction describe(value: { name: '' }) {\n return 'object: ' + value.name\n}\n\ndescribe(42) // 'number: 42'\ndescribe('hello') // 'string: hello'\ndescribe({ name: 'world' }) // 'object: world'\ndescribe(true) // MonadicError: no matching overload\n```\n\n### Dispatch Order\n\n1. **Arity** first (number of arguments)\n2. **Type specificity** within same arity: `integer` > `number` > `any`; objects before primitives\n3. **Declaration order** as tiebreaker\n\n### Polymorphic Constructors\n\nClasses can have multiple constructor signatures. The first becomes the real JS constructor; additional variants become factory functions:\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1: two numbers\nPoint({ x: 10, y: 20 }) // variant 2: object\n```\n\nAll variants produce correct `instanceof` results.\n\n### Compile-Time Validation\n\nTJS catches these errors at transpile time:\n\n- **Ambiguous signatures**: Two variants with identical types at every position\n- **Rest parameters**: `...args` not supported in polymorphic functions\n- **Mixed async/sync**: All variants must agree\n\n---\n\n## Local Class Extensions\n\nAdd methods to built-in types without polluting prototypes:\n\n```typescript\nextend String {\n capitalize() {\n return this[0].toUpperCase() + this.slice(1)\n }\n words() {\n return this.split(/\\s+/)\n }\n}\n\n'hello world'.capitalize() // 'Hello world'\n'foo bar baz'.words() // ['foo', 'bar', 'baz']\n```\n\n### How It Works\n\nFor known-type receivers (literals, typed variables), calls are rewritten at transpile time to `.call()` — zero runtime overhead:\n\n```javascript\n// TJS source:\n'hello'.capitalize()\n\n// Generated JS:\n__ext_String.capitalize.call('hello')\n```\n\nFor unknown types, a runtime registry (`registerExtension` / `resolveExtension`) provides fallback dispatch.\n\n### Supported Types\n\nExtensions work on any type: `String`, `Number`, `Array`, `Boolean`, custom classes, and DOM classes like `HTMLElement`. Multiple `extend` blocks for the same type merge left-to-right (later declarations can override earlier methods).\n\n### Rules\n\n- Arrow functions are **not allowed** in extend blocks (they don't bind `this`)\n- Extensions are **file-local** — they don't leak across modules\n- Prototypes are **never modified** — `String.prototype.capitalize` remains `undefined`\n\n---\n\n## Runtime Features\n\n### `__tjs` Metadata\n\nEvery TJS function carries its type information:\n\n```typescript\nfunction createUser(input: { name: '', age: 0 }) -> { id: 0 } {\n return { id: 123 }\n}\n\nconsole.log(createUser.__tjs)\n// {\n// params: {\n// input: { type: { kind: 'object', shape: { name: 'string', age: 'number' } } }\n// },\n// returns: { kind: 'object', shape: { id: 'number' } }\n// }\n```\n\nThis enables:\n\n- Autocomplete from live objects\n- Runtime type validation\n- Automatic documentation generation\n\n### Monadic Errors\n\nType validation failures return `MonadicError` instances (extends `Error`),\nnot thrown exceptions:\n\n```typescript\nimport { isMonadicError } from 'tjs-lang/lang'\n\nconst result = createUser({ name: 123 }) // wrong type\n// MonadicError: Expected string for 'createUser.name', got number\n\nif (isMonadicError(result)) {\n console.log(result.message) // \"Expected string for 'createUser.name', got number\"\n console.log(result.path) // \"createUser.name\"\n console.log(result.expected) // \"string\"\n console.log(result.actual) // \"number\"\n}\n```\n\nNo try/catch gambling. The host survives invalid inputs.\n\nFor general-purpose error values (not type errors), use the `error()` helper\nwhich returns plain `{ $error: true, message }` objects checkable with `isError()`.\n\n### Inline Tests\n\nTests live next to code:\n\n```typescript\nfunction double(x: 0) -> 0 { return x * 2 }\n\ntest('doubles numbers') {\n expect(double(5)).toBe(10)\n expect(double(-3)).toBe(-6)\n}\n```\n\nTests are extracted at compile time and can be:\n\n- Run during transpilation\n- Stripped in production builds\n- Used for documentation generation\n\n### WASM Blocks\n\nDrop into WebAssembly for compute-heavy code:\n\n```typescript\nfunction vectorDot(a: [0], b: [0]) -> 0 {\n let sum = 0\n wasm {\n for (let i = 0; i < a.length; i++) {\n sum = sum + a[i] * b[i]\n }\n }\n return sum\n}\n```\n\nVariables are captured automatically. Falls back to JS if WASM unavailable.\n\n#### SIMD Intrinsics (f32x4)\n\nFor compute-heavy workloads, use f32x4 SIMD intrinsics to process 4 float32 values per instruction:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable intrinsics:\n\n| Intrinsic | Description |\n|-----------|-------------|\n| `f32x4_load(ptr, byteOffset)` | Load 4 floats from memory into v128 |\n| `f32x4_store(ptr, byteOffset, vec)` | Store v128 as 4 floats to memory |\n| `f32x4_splat(scalar)` | Fill all 4 lanes with a scalar value |\n| `f32x4_extract_lane(vec, N)` | Extract float from lane 0-3 |\n| `f32x4_replace_lane(vec, N, val)` | Replace one lane, return new v128 |\n| `f32x4_add(a, b)` | Lane-wise addition |\n| `f32x4_sub(a, b)` | Lane-wise subtraction |\n| `f32x4_mul(a, b)` | Lane-wise multiplication |\n| `f32x4_div(a, b)` | Lane-wise division |\n| `f32x4_neg(v)` | Negate all lanes |\n| `f32x4_sqrt(v)` | Square root of all lanes |\n\nThis mirrors C/C++ SIMD intrinsics (`_mm_add_ps`, etc.) — explicit, predictable, no auto-vectorization magic.\n\n#### Zero-Copy Arrays: `wasmBuffer()`\n\nBy default, typed arrays passed to WASM blocks are copied into WASM memory before the call and copied back out after. For large arrays called frequently, this overhead can negate WASM's speed advantage.\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. These arrays work like normal typed arrays from JavaScript, but when passed to a `wasm {}` block, they're zero-copy — the data is already there.\n\n```typescript\n// Allocate particle positions in WASM memory\nconst starX = wasmBuffer(Float32Array, 50000)\nconst starY = wasmBuffer(Float32Array, 50000)\n\n// Use from JS like normal arrays\nfor (let i = 0; i < 50000; i++) {\n starX[i] = (Math.random() - 0.5) * 2000\n starY[i] = (Math.random() - 0.5) * 2000\n}\n\n// Zero-copy SIMD processing\nfunction moveParticles(! xs: Float32Array, ys: Float32Array, len: 0, dx: 0.0, dy: 0.0) {\n wasm {\n let vdx = f32x4_splat(dx)\n let vdy = f32x4_splat(dy)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vdx))\n f32x4_store(ys, off, f32x4_add(f32x4_load(ys, off), vdy))\n }\n } fallback {\n for (let i = 0; i < len; i++) { xs[i] += dx; ys[i] += dy }\n }\n}\n\n// After WASM runs, JS sees the mutations immediately\nmoveParticles(starX, starY, 50000, 1.0, 0.5)\nconsole.log(starX[0]) // updated in place, no copy\n```\n\nKey points:\n- Supported constructors: `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n- Uses a bump allocator — allocations persist for program lifetime\n- All WASM blocks in a file share one 64MB memory\n- Regular typed arrays still work (copy in/out as before)\n- Use `!` (unsafe) on hot-path functions to skip runtime type checks\n\n---\n\n## Module System\n\n### Imports\n\nTJS supports URL imports:\n\n```typescript\nimport { Button } from 'https://cdn.example.com/ui-kit.tjs'\nimport { validate } from './utils/validation.tjs'\n```\n\nModules are:\n\n- Fetched on demand\n- Transpiled in the browser\n- Cached independently (IndexedDB + service worker)\n\n### CDN Integration\n\nExternal packages from esm.sh with pinned versions:\n\n```typescript\nimport lodash from 'https://esm.sh/lodash@4.17.21'\n```\n\n---\n\n## TypeScript Compatibility\n\n### TS → TJS Converter\n\nConvert existing TypeScript:\n\n```bash\nbun src/cli/tjs.ts convert file.ts\n```\n\n```typescript\n// TypeScript\nfunction greet(name: string, age?: number): string { ... }\n\n// Converts to TJS\nfunction greet(name: '', age = 0) -> '' { ... }\n```\n\n### What Gets Converted\n\n| TypeScript | TJS |\n| -------------------------- | ------------------- |\n| `name: string` | `name: ''` |\n| `age: number` | `age: 0.0` |\n| `flag: boolean` | `flag: true` |\n| `items: string[]` | `items: ['']` |\n| `age?: number` | `age = 0.0` |\n| `private foo` | `#foo` |\n| `interface User` | `Type User` |\n| `type Status = 'a' \\| 'b'` | `Union(['a', 'b'])` |\n| `enum Color` | `Enum(...)` |\n\n---\n\n## Performance\n\n| Mode | Overhead | Use Case |\n| --------------- | --------- | ------------------------------- |\n| `safety none` | **1.0x** | Metadata only, no validation |\n| `safety inputs` | **~1.5x** | Production (single-arg objects) |\n| `(!) unsafe` | **1.0x** | Hot paths |\n| `wasm {}` | **<1.0x** | Compute-heavy code |\n\n### Why 1.5x, Not 25x\n\nMost validators interpret schemas at runtime (~25x overhead). TJS generates inline checks at transpile time:\n\n```typescript\n// Generated (JIT-friendly)\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.name !== 'string' ||\n typeof input.age !== 'number'\n) {\n return { $error: true, message: 'Invalid input', path: 'fn.input' }\n}\n```\n\nNo schema interpretation. No object iteration. The JIT inlines these completely.\n\n---\n\n## Bare Assignments\n\nUppercase identifiers automatically get `const`:\n\n```typescript\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n---\n\n## Limitations\n\n### What TJS Doesn't Do\n\n- **No gradual typing** - types are all-or-nothing per function\n- **No complex type inference** - you provide examples, not constraints\n- **No declaration files** - types live in the code, not `.d.ts`\n- **No type-level computation** - no conditional types, mapped types, etc.\n\n### What TJS Intentionally Avoids\n\n- Build steps\n- External type checkers\n- Complex tooling configuration\n- Separation of types from runtime\n\n---\n\n## Learn More\n\n- [AJS Documentation](DOCS-AJS.md) — The agent runtime\n- [Builder's Manifesto](MANIFESTO-BUILDER.md) — Why TJS is fun\n- [Enterprise Guide](MANIFESTO-ENTERPRISE.md) — Why TJS is safe\n- [Technical Context](CONTEXT.md) — Architecture deep dive\n"
154
+ "text": "<!--{\"section\": \"tjs\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"Documentation\"}-->\n\n# TJS: Typed JavaScript\n\n_Types as Examples. Zero Build. Runtime Metadata._\n\n---\n\n## What is TJS?\n\nTJS is a typed superset of JavaScript where **types are concrete values**, not abstract annotations.\n\n```typescript\n// TypeScript: abstract type annotation\nfunction greet(name: string): string\n\n// TJS: concrete example value\nfunction greet(name: 'World') -> '' { return `Hello, ${name}!` }\n```\n\nThe example `'World'` tells TJS that `name` is a string. The example `''` tells TJS the return type is a string. Types are inferred from the examples you provide.\n\nTJS transpiles to JavaScript with embedded `__tjs` metadata, enabling runtime type checking, autocomplete from live objects, and documentation generation.\n\n---\n\n## The Compiler\n\nTJS compiles in the browser. No webpack, no node_modules, no build server.\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst code = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n\n// Returns transpiled JavaScript with __tjs metadata\n```\n\nYou can also use the CLI:\n\n```bash\nbun src/cli/tjs.ts check file.tjs # Parse and type check\nbun src/cli/tjs.ts run file.tjs # Transpile and execute\nbun src/cli/tjs.ts emit file.tjs # Output transpiled JS\nbun src/cli/tjs.ts types file.tjs # Output type metadata\n```\n\n---\n\n## Syntax\n\n### Parameter Types (Colon Syntax)\n\n> **Not TypeScript.** TJS colon syntax looks like TypeScript but has different\n> semantics. The value after `:` is a **concrete example**, not a type name.\n> Write `name: 'Alice'` (example value), not `name: string` (type name).\n> TJS infers the type from the example: `'Alice'` → string, `0` → integer,\n> `true` → boolean.\n\nRequired parameters use colon syntax with an example value:\n\n```typescript\nfunction greet(name: 'Alice') {} // name is required, type: string\nfunction calculate(value: 0) {} // value is required, type: integer\nfunction measure(rate: 0.0) {} // rate is required, type: number (float)\nfunction count(n: +0) {} // n is required, type: non-negative integer\nfunction toggle(flag: true) {} // flag is required, type: boolean\n```\n\n### Numeric Types\n\nTJS distinguishes three numeric types using valid JavaScript syntax:\n\n```typescript\nfunction process(\n rate: 3.14, // number (float) -- has a decimal point\n count: 42, // integer -- whole number, no decimal\n index: +0 // non-negative integer -- prefixed with +\n) {}\n```\n\n| You Write | Type Inferred | Runtime Validation |\n| --------- | ---------------------- | ------------------------------- |\n| `3.14` | `number` (float) | `typeof x === 'number'` |\n| `0.0` | `number` (float) | `typeof x === 'number'` |\n| `42` | `integer` | `Number.isInteger(x)` |\n| `0` | `integer` | `Number.isInteger(x)` |\n| `+20` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `+0` | `non-negative integer` | `Number.isInteger(x) && x >= 0` |\n| `-5` | `integer` | `Number.isInteger(x)` |\n| `-3.5` | `number` (float) | `typeof x === 'number'` |\n\nAll of these are valid JavaScript expressions. TJS reads the syntax more\ncarefully to give you finer-grained type checking than JS or TypeScript\nprovide natively.\n\n### Optional Parameters (Default Values)\n\nOptional parameters use `=` with a default value:\n\n```typescript\nfunction greet(name = 'World') {} // name is optional, defaults to 'World'\nfunction calculate(value = 0) {} // value is optional, defaults to 0 (integer)\n```\n\n### TypeScript-Style Optional\n\nYou can also use `?:` syntax:\n\n```typescript\nfunction greet(name?: '') {} // same as name = ''\n```\n\n### Object Parameters\n\nObject shapes are defined by example:\n\n```typescript\nfunction createUser(user: { name: ''; age: 0 }) {}\n// user must be an object with string name and number age\n```\n\n### Nullable Types\n\nUse `||` for union with null:\n\n```typescript\nfunction find(id: 0 || null) { } // number or null\n```\n\n### Return Types (Arrow Syntax)\n\nReturn types use `->`:\n\n```typescript\nfunction add(a: 0, b: 0) -> 0 {\n return a + b\n}\n\nfunction getUser(id: 0) -> { name: '', age: 0 } {\n return { name: 'Alice', age: 30 }\n}\n```\n\n### Array Types\n\nArrays use bracket syntax with an example element:\n\n```typescript\nfunction sum(numbers: [0]) -> 0 { // array of numbers\n return numbers.reduce((a, b) => a + b, 0)\n}\n\nfunction names(users: [{ name: '' }]) { // array of objects\n return users.map(u => u.name)\n}\n```\n\n---\n\n## Safety Markers\n\n### Unsafe Functions\n\nSkip validation for hot paths:\n\n```typescript\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n```\n\nThe `!` marker after the function name skips input validation.\n\n### Safe Functions\n\nExplicit validation (for emphasis):\n\n```typescript\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n```\n\n### Unsafe Blocks\n\nSkip validation for a block of code:\n\n```typescript\nunsafe {\n fastPath(data)\n anotherHotFunction(moreData)\n}\n```\n\n### Module Safety Directive\n\nSet the default validation level for an entire file:\n\n```typescript\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n---\n\n## Type System\n\n### Type()\n\nDefine named types with predicates:\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate\nType PositiveNumber {\n description: 'a positive number'\n example: 1\n predicate(x) { return x > 0 }\n}\n```\n\nTypes can be used in function signatures:\n\n```typescript\nfunction greet(name: Name) -> '' {\n return `Hello, ${name}!`\n}\n```\n\n### Generic()\n\nRuntime-checkable generics:\n\n```typescript\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// 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### Union()\n\nDiscriminated unions:\n\n```typescript\nconst Shape = Union('kind', {\n circle: { radius: 0 },\n rectangle: { width: 0, height: 0 }\n})\n\nfunction area(shape: Shape) -> 0 {\n if (shape.kind === 'circle') {\n return Math.PI * shape.radius ** 2\n }\n return shape.width * shape.height\n}\n```\n\n### Enum()\n\nString or numeric enums:\n\n```typescript\nconst Status = Enum(['pending', 'active', 'completed'])\nconst Priority = Enum({ low: 1, medium: 2, high: 3 })\n\nfunction setStatus(status: Status) {}\n```\n\n---\n\n## Structural Equality: Is / IsNot\n\nJavaScript's `==` is broken (type coercion). TJS provides structural equality:\n\n```typescript\n// Structural comparison - no coercion\n[1, 2] Is [1, 2] // true\n5 Is \"5\" // false (different types)\n{ a: 1 } Is { a: 1 } // true\n\n// Arrays compared element-by-element\n[1, [2, 3]] Is [1, [2, 3]] // true\n\n// Negation\n5 IsNot \"5\" // true\n```\n\n### Custom Equality\n\nObjects can define custom equality in two ways:\n\n**1. `[tjsEquals]` symbol protocol** (preferred for Proxies and advanced use):\n\n```typescript\nimport { tjsEquals } from 'tjs-lang/lang'\n\n// A proxy that delegates equality to its target\nconst target = { x: 1, y: 2 }\nconst proxy = new Proxy({\n [tjsEquals](other) { return target Is other }\n}, {})\n\nproxy == { x: 1, y: 2 } // true — delegates to target\n```\n\n**2. `.Equals` method** (simple, works on any object or class):\n\n```typescript\nclass Point {\n constructor(x: 0, y: 0) { this.x = x; this.y = y }\n Equals(other) { return this.x === other.x && this.y === other.y }\n}\n\nPoint(1, 2) Is Point(1, 2) // true (uses .Equals)\n```\n\n**Priority:** `[tjsEquals]` symbol > `.Equals` method > structural comparison.\n\nThe symbol is `Symbol.for('tjs.equals')`, so it works across realms. Access it\nvia `import { tjsEquals } from 'tjs-lang/lang'` or `__tjs.tjsEquals` at runtime.\n\n---\n\n## Classes\n\n### Callable Without `new`\n\nTJS classes are callable without the `new` keyword:\n\n```typescript\nclass User {\n constructor(name: '') {\n this.name = name\n }\n}\n\n// Both work identically:\nconst u1 = User('Alice') // TJS way - clean\nconst u2 = new User('Alice') // Also works (linter warns)\n```\n\n### Private Fields\n\nUse `#` for private fields:\n\n```typescript\nclass Counter {\n #count = 0\n\n increment() {\n this.#count++\n }\n get value() {\n return this.#count\n }\n}\n```\n\nWhen converting from TypeScript, `private foo` becomes `#foo`.\n\n### Getters and Setters\n\nAsymmetric types are captured:\n\n```typescript\nclass Timestamp {\n #value\n\n constructor(initial: '' | 0 | null) {\n this.#value = initial === null ? new Date() : new Date(initial)\n }\n\n set value(v: '' | 0 | null) {\n this.#value = v === null ? new Date() : new Date(v)\n }\n\n get value() {\n return this.#value\n }\n}\n\nconst ts = Timestamp('2024-01-15')\nts.value = 0 // SET accepts: string | number | null\nts.value // GET returns: Date\n```\n\n---\n\n## Polymorphic Functions\n\nMultiple function declarations with the same name are automatically merged into a dispatcher that routes by argument count and type:\n\n```typescript\nfunction describe(value: 0) {\n return 'number: ' + value\n}\nfunction describe(value: '') {\n return 'string: ' + value\n}\nfunction describe(value: { name: '' }) {\n return 'object: ' + value.name\n}\n\ndescribe(42) // 'number: 42'\ndescribe('hello') // 'string: hello'\ndescribe({ name: 'world' }) // 'object: world'\ndescribe(true) // MonadicError: no matching overload\n```\n\n### Dispatch Order\n\n1. **Arity** first (number of arguments)\n2. **Type specificity** within same arity: `integer` > `number` > `any`; objects before primitives\n3. **Declaration order** as tiebreaker\n\n### Polymorphic Constructors\n\nClasses can have multiple constructor signatures. The first becomes the real JS constructor; additional variants become factory functions:\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1: two numbers\nPoint({ x: 10, y: 20 }) // variant 2: object\n```\n\nAll variants produce correct `instanceof` results.\n\n### Compile-Time Validation\n\nTJS catches these errors at transpile time:\n\n- **Ambiguous signatures**: Two variants with identical types at every position\n- **Rest parameters**: `...args` not supported in polymorphic functions\n- **Mixed async/sync**: All variants must agree\n\n---\n\n## Local Class Extensions\n\nAdd methods to built-in types without polluting prototypes:\n\n```typescript\nextend String {\n capitalize() {\n return this[0].toUpperCase() + this.slice(1)\n }\n words() {\n return this.split(/\\s+/)\n }\n}\n\n'hello world'.capitalize() // 'Hello world'\n'foo bar baz'.words() // ['foo', 'bar', 'baz']\n```\n\n### How It Works\n\nFor known-type receivers (literals, typed variables), calls are rewritten at transpile time to `.call()` — zero runtime overhead:\n\n```javascript\n// TJS source:\n'hello'.capitalize()\n\n// Generated JS:\n__ext_String.capitalize.call('hello')\n```\n\nFor unknown types, a runtime registry (`registerExtension` / `resolveExtension`) provides fallback dispatch.\n\n### Supported Types\n\nExtensions work on any type: `String`, `Number`, `Array`, `Boolean`, custom classes, and DOM classes like `HTMLElement`. Multiple `extend` blocks for the same type merge left-to-right (later declarations can override earlier methods).\n\n### Rules\n\n- Arrow functions are **not allowed** in extend blocks (they don't bind `this`)\n- Extensions are **file-local** — they don't leak across modules\n- Prototypes are **never modified** — `String.prototype.capitalize` remains `undefined`\n\n---\n\n## Runtime Features\n\n### `__tjs` Metadata\n\nEvery TJS function carries its type information:\n\n```typescript\nfunction createUser(input: { name: '', age: 0 }) -> { id: 0 } {\n return { id: 123 }\n}\n\nconsole.log(createUser.__tjs)\n// {\n// params: {\n// input: { type: { kind: 'object', shape: { name: 'string', age: 'number' } } }\n// },\n// returns: { kind: 'object', shape: { id: 'number' } }\n// }\n```\n\nThis enables:\n\n- Autocomplete from live objects\n- Runtime type validation\n- Automatic documentation generation\n\n### Monadic Errors\n\nType validation failures return `MonadicError` instances (extends `Error`),\nnot thrown exceptions:\n\n```typescript\nimport { isMonadicError } from 'tjs-lang/lang'\n\nconst result = createUser({ name: 123 }) // wrong type\n// MonadicError: Expected string for 'createUser.name', got number\n\nif (isMonadicError(result)) {\n console.log(result.message) // \"Expected string for 'createUser.name', got number\"\n console.log(result.path) // \"createUser.name\"\n console.log(result.expected) // \"string\"\n console.log(result.actual) // \"number\"\n}\n```\n\nNo try/catch gambling. The host survives invalid inputs.\n\nFor general-purpose error values (not type errors), use the `error()` helper\nwhich returns plain `{ $error: true, message }` objects checkable with `isError()`.\n\n### Inline Tests\n\nTests live next to code:\n\n```typescript\nfunction double(x: 0) -> 0 { return x * 2 }\n\ntest('doubles numbers') {\n expect(double(5)).toBe(10)\n expect(double(-3)).toBe(-6)\n}\n```\n\nTests are extracted at compile time and can be:\n\n- Run during transpilation\n- Stripped in production builds\n- Used for documentation generation\n\n### WASM Blocks\n\nDrop into WebAssembly for compute-heavy code:\n\n```typescript\nfunction vectorDot(a: [0], b: [0]) -> 0 {\n let sum = 0\n wasm {\n for (let i = 0; i < a.length; i++) {\n sum = sum + a[i] * b[i]\n }\n }\n return sum\n}\n```\n\nVariables are captured automatically. Falls back to JS if WASM unavailable.\n\n#### SIMD Intrinsics (f32x4)\n\nFor compute-heavy workloads, use f32x4 SIMD intrinsics to process 4 float32 values per instruction:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable intrinsics:\n\n| Intrinsic | Description |\n|-----------|-------------|\n| `f32x4_load(ptr, byteOffset)` | Load 4 floats from memory into v128 |\n| `f32x4_store(ptr, byteOffset, vec)` | Store v128 as 4 floats to memory |\n| `f32x4_splat(scalar)` | Fill all 4 lanes with a scalar value |\n| `f32x4_extract_lane(vec, N)` | Extract float from lane 0-3 |\n| `f32x4_replace_lane(vec, N, val)` | Replace one lane, return new v128 |\n| `f32x4_add(a, b)` | Lane-wise addition |\n| `f32x4_sub(a, b)` | Lane-wise subtraction |\n| `f32x4_mul(a, b)` | Lane-wise multiplication |\n| `f32x4_div(a, b)` | Lane-wise division |\n| `f32x4_neg(v)` | Negate all lanes |\n| `f32x4_sqrt(v)` | Square root of all lanes |\n\nThis mirrors C/C++ SIMD intrinsics (`_mm_add_ps`, etc.) — explicit, predictable, no auto-vectorization magic.\n\n#### Zero-Copy Arrays: `wasmBuffer()`\n\nBy default, typed arrays passed to WASM blocks are copied into WASM memory before the call and copied back out after. For large arrays called frequently, this overhead can negate WASM's speed advantage.\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. These arrays work like normal typed arrays from JavaScript, but when passed to a `wasm {}` block, they're zero-copy — the data is already there.\n\n```typescript\n// Allocate particle positions in WASM memory\nconst starX = wasmBuffer(Float32Array, 50000)\nconst starY = wasmBuffer(Float32Array, 50000)\n\n// Use from JS like normal arrays\nfor (let i = 0; i < 50000; i++) {\n starX[i] = (Math.random() - 0.5) * 2000\n starY[i] = (Math.random() - 0.5) * 2000\n}\n\n// Zero-copy SIMD processing\nfunction moveParticles(! xs: Float32Array, ys: Float32Array, len: 0, dx: 0.0, dy: 0.0) {\n wasm {\n let vdx = f32x4_splat(dx)\n let vdy = f32x4_splat(dy)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vdx))\n f32x4_store(ys, off, f32x4_add(f32x4_load(ys, off), vdy))\n }\n } fallback {\n for (let i = 0; i < len; i++) { xs[i] += dx; ys[i] += dy }\n }\n}\n\n// After WASM runs, JS sees the mutations immediately\nmoveParticles(starX, starY, 50000, 1.0, 0.5)\nconsole.log(starX[0]) // updated in place, no copy\n```\n\nKey points:\n- Supported constructors: `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n- Uses a bump allocator — allocations persist for program lifetime\n- All WASM blocks in a file share one 64MB memory\n- Regular typed arrays still work (copy in/out as before)\n- Use `!` (unsafe) on hot-path functions to skip runtime type checks\n\n---\n\n## Module System\n\n### Imports\n\nTJS supports URL imports:\n\n```typescript\nimport { Button } from 'https://cdn.example.com/ui-kit.tjs'\nimport { validate } from './utils/validation.tjs'\n```\n\nModules are:\n\n- Fetched on demand\n- Transpiled in the browser\n- Cached independently (IndexedDB + service worker)\n\n### CDN Integration\n\nExternal packages from esm.sh with pinned versions:\n\n```typescript\nimport lodash from 'https://esm.sh/lodash@4.17.21'\n```\n\n---\n\n## TypeScript Compatibility\n\n### TS → TJS Converter\n\nConvert existing TypeScript:\n\n```bash\nbun src/cli/tjs.ts convert file.ts\n```\n\n```typescript\n// TypeScript\nfunction greet(name: string, age?: number): string { ... }\n\n// Converts to TJS\nfunction greet(name: '', age = 0) -> '' { ... }\n```\n\n### What Gets Converted\n\n| TypeScript | TJS |\n| -------------------------- | ------------------- |\n| `name: string` | `name: ''` |\n| `age: number` | `age: 0.0` |\n| `flag: boolean` | `flag: false` |\n| `items: string[]` | `items: ['']` |\n| `age?: number` | `age = 0.0` |\n| `private foo` | `#foo` |\n| `interface User` | `Type User` |\n| `type Status = 'a' \\| 'b'` | `Union(['a', 'b'])` |\n| `enum Color` | `Enum(...)` |\n\n> **Lossy boolean conversion:** TypeScript `x?: boolean` becomes TJS `x = false`.\n> This collapses \"not passed\" (`undefined`) and \"passed as `false`\" into the same\n> default value. Code that distinguishes the three states (`true` / `false` /\n> `undefined`) may break. A future version may emit `x: false || null` for\n> optional booleans to preserve the `undefined` state.\n\n---\n\n## Performance\n\n| Mode | Overhead | Use Case |\n| --------------- | --------- | ------------------------------- |\n| `safety none` | **1.0x** | Metadata only, no validation |\n| `safety inputs` | **~1.5x** | Production (single-arg objects) |\n| `(!) unsafe` | **1.0x** | Hot paths |\n| `wasm {}` | **<1.0x** | Compute-heavy code |\n\n### Why 1.5x, Not 25x\n\nMost validators interpret schemas at runtime (~25x overhead). TJS generates inline checks at transpile time:\n\n```typescript\n// Generated (JIT-friendly)\nif (\n typeof input !== 'object' ||\n input === null ||\n typeof input.name !== 'string' ||\n typeof input.age !== 'number'\n) {\n return { $error: true, message: 'Invalid input', path: 'fn.input' }\n}\n```\n\nNo schema interpretation. No object iteration. The JIT inlines these completely.\n\n---\n\n## Bare Assignments\n\nUppercase identifiers automatically get `const`:\n\n```typescript\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n---\n\n## Limitations\n\n### What TJS Doesn't Do\n\n- **No gradual typing** - types are all-or-nothing per function\n- **No complex type inference** - you provide examples, not constraints\n- **No declaration files** - types live in the code, not `.d.ts`\n- **No type-level computation** - no conditional types, mapped types, etc.\n\n### What TJS Intentionally Avoids\n\n- Build steps\n- External type checkers\n- Complex tooling configuration\n- Separation of types from runtime\n\n---\n\n## Learn More\n\n- [AJS Documentation](DOCS-AJS.md) — The agent runtime\n- [Builder's Manifesto](MANIFESTO-BUILDER.md) — Why TJS is fun\n- [Enterprise Guide](MANIFESTO-ENTERPRISE.md) — Why TJS is safe\n- [Technical Context](CONTEXT.md) — Architecture deep dive\n"
155
155
  },
156
156
  {
157
157
  "title": "Starfield",
@@ -173,7 +173,7 @@
173
173
  "type": "example",
174
174
  "group": "featured",
175
175
  "order": 0,
176
- "code": "// ═══════════════════════════════════════════════════════════\n// 1. SAFETY DIRECTIVE & TJS MODES\n// These must appear before any other code.\n// ═══════════════════════════════════════════════════════════\n\nsafety inputs\n\nTjsEquals\nTjsClass\n\n/*#\n# TJS Grammar Reference\n\nA runnable reference for TJS syntax. Each section demonstrates a\nfeature with a test proving it works.\n\n## Quick Index\n| Feature | Section |\n|---------|---------|\n| Safety & modes | `safety`, `TjsEquals`, `TjsClass` |\n| Parameters | Colon `:`, optional `=`, destructured `{}` |\n| Numeric narrowing | `42` int, `3.14` float, `+0` non-negative |\n| Return types | `->`, `-?`, `-!` |\n| Safety markers | `(! ...)` unsafe, `(? ...)` safe |\n| Type/Generic/Enum/Union | See above (requires full runtime) |\n| Bare assignments | `Uppercase = ...` |\n| Classes | Callable without `new` |\n| Polymorphic functions | Same name, different signatures |\n| Polymorphic constructors | Multiple `constructor()` |\n| Local extensions | `extend String { ... }` |\n| Equality | `==` structural, `===` identity, `Is`/`IsNot` |\n| Try without catch | Monadic error conversion |\n| Inline tests | Test blocks |\n| TDoc comments | Slash-star-hash markdown blocks |\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 2. PARAMETER SYNTAX\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Parameters\n\nColon `:` = required (example value infers type).\nEquals `=` = optional (default value).\nQuestion mark `?:` = optional (TS-style).\n*/\n\n// Required params (colon shorthand)\nfunction greet(name: 'Alice') -> 'Hello, Alice' {\n return 'Hello, ' + name\n}\n\n// Optional params (equals = default)\nfunction greetOpt(name = 'World') -> 'Hello, World' {\n return 'Hello, ' + name\n}\n\n// Destructured object params (colon = required, equals = optional)\nfunction createUser({ name: 'Anon', role = 'user' }) -> { name: '', role: '' } {\n return { name, role }\n}\n\n// Numeric type narrowing: 42 = integer, 3.14 = float, +0 = non-negative int\nfunction calc(count: 42, rate: 3.14, index: +0) -> 0.0 {\n return (count + index) * rate\n}\n\ntest 'parameter syntax' {\n expect(greet('Bob')).toBe('Hello, Bob')\n expect(greetOpt()).toBe('Hello, World')\n expect(createUser({ name: 'Eve' })).toEqual({ name: 'Eve', role: 'user' })\n expect(calc(10, 1.5, 2)).toBe(18)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 3. RETURN TYPES\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Return Types\n\n`->` signature test at transpile time.\n`-?` signature test + runtime output validation.\n`-!` skip signature test entirely.\n*/\n\n// -> : transpile-time check (double(5) must equal 10)\nfunction double(x: 5) -> 10 {\n return x * 2\n}\n\n// -! : skip test (useful when return shape varies)\nfunction safeDivide(a: 10, b: 2) -! 5 {\n if (b === 0) return { error: 'div by zero' }\n return a / b\n}\n\ntest 'return types' {\n expect(double(7)).toBe(14)\n expect(safeDivide(10, 0)).toEqual({ error: 'div by zero' })\n}\n\n// ═══════════════════════════════════════════════════════════\n// 4. SAFETY MARKERS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Safety Markers\n\n`!` = unsafe (skip input validation). Fast path for trusted callers.\n`?` = safe (force validation even inside `unsafe` blocks).\n*/\n\nfunction fastAdd(! a: 0, b: 0) -> 0 {\n return a + b\n}\n\nfunction safeAdd(? a: 0, b: 0) -> 0 {\n return a + b\n}\n\ntest 'safety markers' {\n expect(fastAdd(3, 4)).toBe(7)\n expect(safeAdd(3, 4)).toBe(7)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 5. BARE ASSIGNMENTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Bare Assignments\n\nUppercase identifiers auto-get `const`.\n*/\n\nGreeting = 'Hello'\nMaxRetries = 3\n\ntest 'bare assignments' {\n expect(Greeting).toBe('Hello')\n expect(MaxRetries).toBe(3)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 7. CLASSES (callable without new)\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Classes\n\nWith `TjsClass` enabled, classes are callable without `new`.\n*/\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n\n magnitude() {\n return Math.sqrt(this.x * this.x + this.y * this.y)\n }\n}\n\ntest 'classes callable without new' {\n const p = Point(3, 4)\n expect(p instanceof Point).toBe(true)\n expect(p.magnitude()).toBe(5)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 8. POLYMORPHIC FUNCTIONS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Polymorphic Functions\n\nSame name, different signatures. Dispatched by arity/type.\nSee the **Polymorphic Functions** example for more.\n*/\n\nfunction describe(value: 0) {\n return 'number: ' + value\n}\n\nfunction describe(first: '', last: '') {\n return first + ' ' + last\n}\n\ntest 'polymorphic dispatch by arity' {\n expect(describe(42)).toBe('number: 42')\n expect(describe('Jane', 'Doe')).toBe('Jane Doe')\n}\n\n// ═══════════════════════════════════════════════════════════\n// 9. POLYMORPHIC CONSTRUCTORS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Polymorphic Constructors\n\nMultiple `constructor()` declarations in a class.\nSee the **Polymorphic Constructors** example for more.\n*/\n\nclass Vec2 {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n\n constructor(obj: { x: 0.0, y: 0.0 }) {\n this.x = obj.x\n this.y = obj.y\n }\n}\n\ntest 'polymorphic constructors' {\n const a = Vec2(1, 2)\n const b = Vec2({ x: 1, y: 2 })\n expect(a.x).toBe(b.x)\n expect(a.y).toBe(b.y)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 10. LOCAL EXTENSIONS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Local Extensions\n\nAdd methods to built-in types without prototype pollution.\nRewritten to `.call()` at transpile time for known types.\nSee the **Local Extensions** example for a runnable demo.\n\n extend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n }\n\n extend Array {\n last() { return this[this.length - 1] }\n }\n\n 'hello'.capitalize() // 'Hello'\n [1, 2, 3].last() // 3\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 11. EQUALITY OPERATORS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Equality\n\nWith `TjsEquals` enabled (at the top of this file):\n- `==` / `!=` use structural comparison (deep value equality)\n- `===` / `!==` are identity checks (same reference)\n- `Is` / `IsNot` are explicit structural operators (any mode)\n\n const a = { x: 1, y: [2, 3] }\n const b = { x: 1, y: [2, 3] }\n a == b // true (structural: same shape)\n a === b // false (identity: different objects)\n a Is b // true (explicit structural)\n a IsNot b // false\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 12. TRY WITHOUT CATCH\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Try Without Catch\n\nA bare `try` block auto-converts exceptions to monadic errors.\n*/\n\nfunction parseJSON(s: '{\"a\":1}') -! { a: 1 } {\n try {\n return JSON.parse(s)\n }\n}\n\ntest 'try without catch' {\n expect(parseJSON('{\"ok\":true}')).toEqual({ ok: true })\n const bad = parseJSON('not json')\n expect(bad.$error).toBe(true)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 13. INLINE TESTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Inline Tests\n\nTest blocks run at transpile time and are stripped from\noutput. They have full access to the module scope, so you\ncan test private functions without exporting them.\n*/\n\nfunction _private(x: 0) -> 0 {\n return x * x\n}\n\ntest 'inline tests can reach private functions' {\n expect(_private(5)).toBe(25)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 14. MODULE EXPORTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Module Exports\n\nStandard ES module syntax works. Functions and values\ncan be exported for use by other modules.\n*/\n\nexport function publicHelper(x: 0) -> 0 {\n return x + 1\n}\n\n// ═══════════════════════════════════════════════════════════\n// 15. TDOC COMMENTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## TDoc Comments\n\nThese comment blocks (opened with slash-star-hash) contain\nmarkdown that becomes rich documentation in the playground\nand API docs. Every such block you've seen above is a TDoc.\n*/\n\n// ═══════════════════════════════════════════════════════════\n// OUTPUT\n// ═══════════════════════════════════════════════════════════\n\nconsole.log('TJS Grammar Reference — all tests passed!')\nconsole.log('Features demonstrated:', [\n 'safety directive', 'TJS modes', 'colon params', 'optional params',\n 'destructured params', 'numeric narrowing',\n 'return types (-> -? -!)', 'safety markers (! ?)',\n 'Type', 'Generic', 'Enum', 'Union', 'bare assignments',\n 'classes', 'polymorphic functions', 'polymorphic constructors',\n 'local extensions', 'structural equality', 'Is/IsNot',\n 'try without catch', 'inline tests', 'module exports', 'TDoc'\n].join(', '))",
176
+ "code": "// ═══════════════════════════════════════════════════════════\n// 1. SAFETY DIRECTIVE & TJS MODES\n// These must appear before any other code.\n// ═══════════════════════════════════════════════════════════\n\nsafety inputs\n\nTjsEquals\nTjsClass\n\n/*#\n# TJS Grammar Reference\n\nA runnable reference for TJS syntax. Each section demonstrates a\nfeature with a test proving it works.\n\n## Quick Index\n| Feature | Section |\n|---------|---------|\n| Safety & modes | `safety`, `TjsEquals`, `TjsClass` |\n| Parameters | Colon `:`, optional `=`, destructured `{}` |\n| Numeric narrowing | `42` int, `3.14` float, `+0` non-negative |\n| Return types | `->`, `-?`, `-!` |\n| Safety markers | `(! ...)` unsafe, `(? ...)` safe |\n| Type/Generic/Enum/Union | See above (requires full runtime) |\n| Bare assignments | `Uppercase = ...` |\n| Classes | Callable without `new` |\n| Polymorphic functions | Same name, different signatures |\n| Polymorphic constructors | Multiple `constructor()` |\n| Local extensions | `extend String { ... }` |\n| Equality | `==` structural, `===` identity, `Is`/`IsNot` |\n| Try without catch | Monadic error conversion |\n| Inline tests | Test blocks |\n| TDoc comments | Slash-star-hash markdown blocks |\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 2. PARAMETER SYNTAX\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Parameters\n\nColon `:` = required (example value infers type).\nEquals `=` = optional (default value).\nQuestion mark `?:` = optional (TS-style).\n*/\n\n// Required params (colon shorthand)\nfunction greet(name: 'Alice') -> 'Hello, Alice' {\n return 'Hello, ' + name\n}\n\n// Optional params (equals = default)\nfunction greetOpt(name = 'World') -> 'Hello, World' {\n return 'Hello, ' + name\n}\n\n// Destructured object params (colon = required, equals = optional)\nfunction createUser({ name: 'Anon', role = 'user' }) -> { name: '', role: '' } {\n return { name, role }\n}\n\n// Numeric type narrowing: 42 = integer, 3.14 = float, +0 = non-negative int\nfunction calc(count: 42, rate: 3.14, index: +0) -> 0.0 {\n return (count + index) * rate\n}\n\ntest 'parameter syntax' {\n expect(greet('Bob')).toBe('Hello, Bob')\n expect(greetOpt()).toBe('Hello, World')\n expect(createUser({ name: 'Eve' })).toEqual({ name: 'Eve', role: 'user' })\n expect(calc(10, 1.5, 2)).toBe(18)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 3. RETURN TYPES\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Return Types\n\n`->` signature test at transpile time.\n`-?` signature test + runtime output validation.\n`-!` skip signature test entirely.\n*/\n\n// -> : transpile-time check (double(5) must equal 10)\nfunction double(x: 5) -> 10 {\n return x * 2\n}\n\n// -! : skip test (useful when return shape varies)\nfunction safeDivide(a: 10, b: 2) -! 5 {\n if (b === 0) return { error: 'div by zero' }\n return a / b\n}\n\ntest 'return types' {\n expect(double(7)).toBe(14)\n expect(safeDivide(10, 0)).toEqual({ error: 'div by zero' })\n}\n\n// ═══════════════════════════════════════════════════════════\n// 4. SAFETY MARKERS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Safety Markers\n\n`!` = unsafe (skip input validation). Fast path for trusted callers.\n`?` = safe (force validation even inside `unsafe` blocks).\n*/\n\nfunction fastAdd(! a: 0, b: 0) -> 0 {\n return a + b\n}\n\nfunction safeAdd(? a: 0, b: 0) -> 0 {\n return a + b\n}\n\ntest 'safety markers' {\n expect(fastAdd(3, 4)).toBe(7)\n expect(safeAdd(3, 4)).toBe(7)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 5. BARE ASSIGNMENTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Bare Assignments\n\nUppercase identifiers auto-get `const`.\n*/\n\nGreeting = 'Hello'\nMaxRetries = 3\n\ntest 'bare assignments' {\n expect(Greeting).toBe('Hello')\n expect(MaxRetries).toBe(3)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 7. CLASSES (callable without new)\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Classes\n\nWith `TjsClass` enabled, classes are callable without `new`.\n*/\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n\n magnitude() {\n return Math.sqrt(this.x * this.x + this.y * this.y)\n }\n}\n\ntest 'classes callable without new' {\n const p = Point(3, 4)\n expect(p instanceof Point).toBe(true)\n expect(p.magnitude()).toBe(5)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 8. POLYMORPHIC FUNCTIONS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Polymorphic Functions\n\nSame name, different signatures. Dispatched by arity/type.\nSee the **Polymorphic Functions** example for more.\n*/\n\nfunction describe(value: 0) {\n return 'number: ' + value\n}\n\nfunction describe(first: '', last: '') {\n return first + ' ' + last\n}\n\ntest 'polymorphic dispatch by arity' {\n expect(describe(42)).toBe('number: 42')\n expect(describe('Jane', 'Doe')).toBe('Jane Doe')\n}\n\n// ═══════════════════════════════════════════════════════════\n// 9. POLYMORPHIC CONSTRUCTORS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Polymorphic Constructors\n\nMultiple `constructor()` declarations in a class.\nSee the **Polymorphic Constructors** example for more.\n*/\n\nclass Vec2 {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n\n constructor(obj: { x: 0.0, y: 0.0 }) {\n this.x = obj.x\n this.y = obj.y\n }\n}\n\ntest 'polymorphic constructors' {\n const a = Vec2(1, 2)\n const b = Vec2({ x: 1, y: 2 })\n expect(a.x).toBe(b.x)\n expect(a.y).toBe(b.y)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 10. LOCAL EXTENSIONS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Local Extensions\n\nAdd methods to built-in types without prototype pollution.\nRewritten to `.call()` at transpile time for known types.\nSee the **Local Extensions** example for a runnable demo.\n\n extend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n }\n\n extend Array {\n last() { return this[this.length - 1] }\n }\n\n 'hello'.capitalize() // 'Hello'\n [1, 2, 3].last() // 3\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 11. EQUALITY OPERATORS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Equality\n\nWith `TjsEquals` enabled (at the top of this file):\n- `==` / `!=` use structural comparison (deep value equality)\n- `===` / `!==` are identity checks (same reference)\n- `Is` / `IsNot` are explicit structural operators (any mode)\n\n const a = { x: 1, y: [2, 3] }\n const b = { x: 1, y: [2, 3] }\n a == b // true (structural: same shape)\n a === b // false (identity: different objects)\n a Is b // true (explicit structural)\n a IsNot b // false\n*/\n\n// ═══════════════════════════════════════════════════════════\n// 12. TRY WITHOUT CATCH\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Try Without Catch\n\nA bare `try` block auto-converts exceptions to monadic errors.\n*/\n\nfunction parseJSON(s: '{\"a\":1}') -! { a: 1 } {\n try {\n return JSON.parse(s)\n }\n}\n\ntest 'try without catch' {\n expect(parseJSON('{\"ok\":true}')).toEqual({ ok: true })\n const bad = parseJSON('not json')\n expect(bad instanceof Error).toBe(true)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 13. INLINE TESTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Inline Tests\n\nTest blocks run at transpile time and are stripped from\noutput. They have full access to the module scope, so you\ncan test private functions without exporting them.\n*/\n\nfunction _private(x: 0) -> 0 {\n return x * x\n}\n\ntest 'inline tests can reach private functions' {\n expect(_private(5)).toBe(25)\n}\n\n// ═══════════════════════════════════════════════════════════\n// 14. MODULE EXPORTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## Module Exports\n\nStandard ES module syntax works. Functions and values\ncan be exported for use by other modules.\n*/\n\nexport function publicHelper(x: 0) -> 0 {\n return x + 1\n}\n\n// ═══════════════════════════════════════════════════════════\n// 15. TDOC COMMENTS\n// ═══════════════════════════════════════════════════════════\n\n/*#\n## TDoc Comments\n\nThese comment blocks (opened with slash-star-hash) contain\nmarkdown that becomes rich documentation in the playground\nand API docs. Every such block you've seen above is a TDoc.\n*/\n\n// ═══════════════════════════════════════════════════════════\n// OUTPUT\n// ═══════════════════════════════════════════════════════════\n\nconsole.log('TJS Grammar Reference — all tests passed!')\nconsole.log('Features demonstrated:', [\n 'safety directive', 'TJS modes', 'colon params', 'optional params',\n 'destructured params', 'numeric narrowing',\n 'return types (-> -? -!)', 'safety markers (! ?)',\n 'Type', 'Generic', 'Enum', 'Union', 'bare assignments',\n 'classes', 'polymorphic functions', 'polymorphic constructors',\n 'local extensions', 'structural equality', 'Is/IsNot',\n 'try without catch', 'inline tests', 'module exports', 'TDoc'\n].join(', '))",
177
177
  "language": "tjs",
178
178
  "description": "Comprehensive reference covering all major TJS syntax features. **Type declarations** (require full `tjs-lang` runtime — shown here for reference): Type Name = 'Alice' Type Age 'a non-negative age' { example: 25 predicate(x) { return typeof x === 'number' && x >= 0 } } Generic Pair<A, B> { description: 'a pair of values' predicate(obj, A, B) { ... } } Enum Direction 'cardinal direction' { North, East, South, West } Enum Color 'CSS color' { Red = 'red', Green = 'green', Blue = 'blue' } Union Status 'task status' 'pending' | 'active' | 'done' All other features are exercised in the runnable code below:"
179
179
  },
@@ -209,7 +209,7 @@
209
209
  "type": "example",
210
210
  "group": "fullstack",
211
211
  "order": 13,
212
- "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()",
212
+ "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 instanceof Error) {\n console.log('Caught type error:', badResult.message)\n }\n\n console.log('\\\\n=== Full-Stack Demo Complete ===')\n console.log('Everything ran in the browser. No server. No build step.')\n}\n\nmain()",
213
213
  "language": "tjs",
214
214
  "description": "Frontend that calls the User Service - run after saving user-service!"
215
215
  },
@@ -257,9 +257,9 @@
257
257
  "type": "example",
258
258
  "group": "patterns",
259
259
  "order": 7,
260
- "code": "/*#\n## Monadic Error Handling\n\nTJS uses the Result patternerrors are values, not exceptions.\nThis makes error handling explicit and type-safe.\n*/\ntest 'divide handles zero' {\n const result = divide(10, 0)\n expect(result.error).toBe('Division by zero')\n}\n\ntest 'divide works normally' {\n const result = divide(10, 2)\n expect(result.value).toBe(5)\n}\n\nfunction divide(a: 10, b: 2) -> { value: 0, error = '' } {\n if (b === 0) {\n return { value: NaN, error: 'Division by zero' }\n }\n return { value: a / b }\n}\n\nfunction safeParse(json: '{\"x\":1}') -! { data: null, error = '' } {\n try {\n return { data: JSON.parse(json) }\n } catch (e) {\n return { data: null, error: e.message }\n }\n}\n\n// Usage errors are values you can inspect\nconst result = divide(10, 0)\nif (!result.error) {\n console.log('Result:', result.value)\n} else {\n console.log('Error:', result.error)\n}",
260
+ "code": "/*#\n## Monadic Error Propagation\n\nType errors are values (MonadicError), not exceptions. They propagate\nautomatically through function chains if any function receives an\nerror as input, it short-circuits and returns the error immediately.\n\nNo try/catch needed. No manual error checking between calls.\n*/\n\n// --- Error propagation through a pipeline ---\n\nfunction validate(name: '') -> '' {\n return name.trim()\n}\n\nfunction greet(name: '') -> '' {\n return `Hello, ${name}!`\n}\n\nfunction shout(text: '') -> '' {\n return text.toUpperCase()\n}\n\ntest 'valid input flows through the pipeline' {\n // Each function's output feeds the next function's input\n const result = shout(greet(validate('alice')))\n expect(result).toBe('HELLO, ALICE!')\n}\n\ntest 'type error propagates through the entire chain' {\n // validate(42) returns a MonadicError (42 is not a string)\n // greet() receives the error, short-circuits, returns it\n // shout() receives the error, short-circuits, returns it\n const result = shout(greet(validate(42)))\n expect(result instanceof Error).toBe(true)\n expect(result.message.includes('string')).toBe(true)\n}\n\ntest 'error identity is preserved (same object, not a copy)' {\n const err = validate(42)\n expect(greet(err)).toBe(err)\n expect(shout(err)).toBe(err)\n}\n\n// --- Result pattern for domain errors ---\n\nfunction divide(a: 10, b: 2) -> { value: 0, error = '' } {\n if (b === 0) {\n return { value: NaN, error: 'Division by zero' }\n }\n return { value: a / b }\n}\n\ntest 'divide handles zero' {\n const result = divide(10, 0)\n expect(result.error).toBe('Division by zero')\n}\n\ntest 'divide works normally' {\n const result = divide(10, 2)\n expect(result.value).toBe(5)\n}\n\n// Errors propagate when passed as arguments to TJS functions.\n// If you use a potentially-error value in a JS expression (e.g. result.length),\n// check it first: if (result instanceof Error) return result\n// In debug mode, errors include a callStack showing the full call chain.",
261
261
  "language": "tjs",
262
- "description": "Type-safe error handling patterns"
262
+ "description": "Monadic error propagation and type-safe error patterns"
263
263
  },
264
264
  {
265
265
  "title": "Schema Validation",
@@ -269,9 +269,9 @@
269
269
  "type": "example",
270
270
  "group": "patterns",
271
271
  "order": 8,
272
- "code": "// TJS integrates with Schema for validation\nimport { Schema } from 'tosijs-schema'\n\n// Define a schema\nconst UserSchema = Schema({\n name: 'anonymous',\n email: 'user@example.com',\n age: 0\n})\n\n// Validate data\nfunction validateUser(data: { name: '', email: '', age: 0 }) -> { valid: true, errors: [''] } {\n const errors = []\n\n if (!UserSchema.validate(data)) {\n errors.push('Invalid user structure')\n }\n\n return {\n valid: errors.length === 0,\n errors\n }\n}\n\nvalidateUser({ name: 'Alice', email: 'alice@test.com', age: 30 })",
272
+ "code": "/*#\n## Runtime Types\n\nEvery TJS function carries `__tjs` metadata with full type information.\nThis enables runtime validation, auto-generated docs, and introspection\n all from the same type annotations you already write.\n*/\n\nfunction createUser(\n name: 'anonymous',\n email: 'user@example.com',\n age: +0\n) -> { name: '', email: '', age: 0 } {\n return { name, email, age }\n}\n\nfunction transfer(\n from: '',\n to: '',\n amount: 0.0\n) -> { from: '', to: '', amount: 0.0 } {\n return { from, to, amount }\n}\n\ntest 'functions validate at runtime' {\n const user = createUser('Alice', 'alice@test.com', 30)\n expect(user.name).toBe('Alice')\n\n // Wrong type — returns error, no exception\n const err = createUser('Alice', 'alice@test.com', -1)\n expect(err instanceof Error).toBe(true)\n}\n\ntest 'metadata is introspectable' {\n const meta = createUser.__tjs\n expect(meta.params.name.type.kind).toBe('string')\n expect(meta.params.age.type.kind).toBe('non-negative-integer')\n expect(meta.returns.type.kind).toBe('object')\n}\n\n// Inspect live metadata\nconsole.log('createUser params:', createUser.__tjs.params)\nconsole.log('transfer params:', transfer.__tjs.params)\nconsole.log('transfer returns:', transfer.__tjs.returns)",
273
273
  "language": "tjs",
274
- "description": "Using Schema for runtime type checking"
274
+ "description": "Types persist into runtime inspect, validate, and document at zero extra cost"
275
275
  },
276
276
  {
277
277
  "title": "Date Formatting (with import)",
@@ -394,7 +394,7 @@
394
394
  "group": "advanced",
395
395
  "order": 19,
396
396
  "requiresApi": true,
397
- "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(/",
397
+ "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- Functions take a destructured object param: function foo({ a, b })\\n- MUST return an object. WRONG: return 42. RIGHT: return { result: 42 }\\n- NO: async, await, new, class, this, var, for loops\\n- Use let for variables, while for loops\\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(/",
398
398
  "language": "ajs",
399
399
  "description": "LLM writes and runs code to solve a problem (requires llm capability)"
400
400
  },
@@ -407,7 +407,7 @@
407
407
  "group": "advanced",
408
408
  "order": 20,
409
409
  "requiresApi": true,
410
- "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(/",
410
+ "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- Functions take a destructured object param: function foo({ a, b })\\n- MUST return an object. WRONG: return 42. RIGHT: return { result: 42 }\\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\\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(/",
411
411
  "language": "ajs",
412
412
  "description": "LLM writes AsyncJS code from a description (requires llm capability)"
413
413
  },
@@ -539,7 +539,7 @@
539
539
  "group": "docs",
540
540
  "order": 0,
541
541
  "navTitle": "Documentation",
542
- "text": "<!--{\"section\": \"ajs\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"Documentation\"}-->\n\n# AJS: The Agent Language\n\n_Code as Data. Safe. Async. Sandboxed._\n\n---\n\n## What is AJS?\n\nAJS (AsyncJS) is a JavaScript subset that compiles to a **JSON AST**. It's designed for untrusted code—user scripts, LLM-generated agents, remote logic.\n\n```javascript\nfunction searchAndSummarize({ query }) {\n let results = httpFetch({ url: `https://api.example.com/search?q=${query}` })\n let summary = llmPredict({ prompt: `Summarize: ${JSON.stringify(results)}` })\n return { query, summary }\n}\n```\n\nThis compiles to JSON that can be:\n\n- Stored in a database\n- Sent over the network\n- Executed in a sandboxed VM\n- Audited before running\n\n---\n\n## The VM\n\nAJS runs in a gas-limited, isolated VM with strict resource controls.\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function process({ url }) {\n let data = httpFetch({ url })\n return { fetched: data }\n }\n`\n\nconst vm = new AgentVM()\nconst result = await vm.run(\n agent,\n { url: 'https://api.example.com' },\n {\n fuel: 1000, // CPU budget\n timeoutMs: 5000, // Wall-clock limit\n }\n)\n```\n\n### Fuel Metering\n\nEvery operation costs fuel:\n\n| Operation | Cost |\n| ------------------------ | ---- |\n| Expression evaluation | 0.01 |\n| Variable set/get | 0.1 |\n| Control flow (if, while) | 0.5 |\n| HTTP fetch | 10 |\n| LLM predict | 100 |\n\nWhen fuel runs out, execution stops safely:\n\n```typescript\nif (result.fuelExhausted) {\n // Agent tried to run forever - stopped safely\n}\n```\n\n### Timeout Enforcement\n\nFuel protects against CPU abuse. Timeouts protect against I/O abuse:\n\n```typescript\nawait vm.run(agent, args, {\n fuel: 1000,\n timeoutMs: 5000, // Hard 5-second limit\n})\n```\n\nSlow network calls can't hang your servers.\n\n### Capability Injection\n\nThe VM starts with **zero capabilities**. You grant what each agent needs:\n\n```typescript\nconst capabilities = {\n fetch: createFetchCapability({\n allowedHosts: ['api.example.com'],\n }),\n store: createReadOnlyStore(),\n // No llm - this agent can't call AI\n}\n\nawait vm.run(agent, args, { capabilities })\n```\n\n---\n\n## Syntax\n\nAJS is a JavaScript subset. Familiar syntax, restricted features.\n\n### What's Allowed\n\n```javascript\n// Functions\nfunction process({ input }) {\n return { output: input * 2 }\n}\n\n// Variables\nlet x = 10\nconst y = 'hello'\n\n// Conditionals\nif (x > 5) {\n return 'big'\n} else {\n return 'small'\n}\n\n// Loops\nfor (let i = 0; i < 10; i++) {\n total = total + i\n}\n\nfor (let item of items) {\n results.push(item.name)\n}\n\nwhile (count > 0) {\n count = count - 1\n}\n\n// Try/catch\ntry {\n riskyOperation()\n} catch (e) {\n return { error: e.message }\n}\n\n// Template literals\nlet message = `Hello, ${name}!`\n\n// Object/array literals\nlet obj = { a: 1, b: 2 }\nlet arr = [1, 2, 3]\n\n// Destructuring\nlet { name, age } = user\nlet [first, second] = items\n\n// Spread\nlet merged = { ...defaults, ...overrides }\nlet combined = [...arr1, ...arr2]\n\n// Ternary\nlet result = x > 0 ? 'positive' : 'non-positive'\n\n// Logical operators\nlet value = a && b\nlet fallback = a || defaultValue\nlet nullish = a ?? defaultValue\n```\n\n### What's Forbidden\n\n| Feature | Why Forbidden |\n| -------------------------- | ------------------------------------------------- |\n| `class` | Too complex for LLMs, enables prototype pollution |\n| `new` | Arbitrary object construction |\n| `this` | Implicit context, hard to sandbox |\n| Closures | State escapes the sandbox |\n| `async`/`await` | VM handles async internally |\n| `eval`, `Function` | Code injection |\n| `__proto__`, `constructor` | Prototype pollution |\n| `import`/`export` | Module system handled by host |\n\nAJS is intentionally simple—simple enough for 4B parameter LLMs to generate correctly.\n\n### Differences from JavaScript\n\nAJS expressions differ from standard JavaScript in a few important ways:\n\n**Null-safe member access.** All member access uses optional chaining internally. Accessing a property on `null` or `undefined` returns `undefined` instead of throwing `TypeError`:\n\n```javascript\nlet x = null\nlet y = x.foo.bar // undefined (no error)\n```\n\nThis is a deliberate safety choice — agents shouldn't crash on missing data.\n\n**No computed member access with variables.** You can use literal indices (`items[0]`, `obj[\"key\"]`) but not variable indices (`items[i]`). This is rejected at transpile time:\n\n```javascript\n// Works\nlet first = items[0]\nlet name = user['name']\n\n// Fails: \"Computed member access with variables not yet supported\"\nlet item = items[i]\n```\n\nWorkaround: use array atoms like `map`, `reduce`, or `for...of` loops instead of index-based access.\n\n**Structural equality.** `==` and `!=` perform deep structural comparison, not reference or coerced equality. No type coercion: `'1' == 1` is `false`. Use `===` and `!==` for identity (reference) checks:\n\n```javascript\n[1, 2] == [1, 2] // true (structural)\n[1, 2] === [1, 2] // false (different objects)\n{ a: 1 } == { a: 1 } // true (structural)\n'1' == 1 // false (no coercion, unlike JS)\nnull == undefined // true (nullish equality preserved)\n```\n\nObjects with a `.Equals` method or `[Symbol.for('tjs.equals')]` handler get custom comparison behavior.\n\n---\n\n## Atoms\n\nAtoms are the built-in operations. Each atom has a defined cost, input schema, and output schema.\n\n### Flow Control\n\n| Atom | Description |\n| -------- | ------------------------------ |\n| `seq` | Execute operations in sequence |\n| `if` | Conditional branching |\n| `while` | Loop with condition |\n| `return` | Return a value |\n| `try` | Error handling |\n\n### State Management\n\n| Atom | Description |\n| ------------ | -------------------------- |\n| `varSet` | Set a variable |\n| `varGet` | Get a variable |\n| `varsLet` | Batch variable declaration |\n| `varsImport` | Import from arguments |\n| `varsExport` | Export as result |\n| `scope` | Create a local scope |\n\n### I/O\n\n| Atom | Description |\n| ----------- | ------------------------------------------- |\n| `httpFetch` | HTTP requests (requires `fetch` capability) |\n\n### Storage (Core)\n\n| Atom | Description |\n| ------------- | ------------------------ |\n| `storeGet` | Get from key-value store |\n| `storeSet` | Set in key-value store |\n| `storeSearch` | Vector similarity search |\n\n### Storage (Battery)\n\n| Atom | Description |\n| ----------------------- | ------------------------------------- |\n| `storeVectorize` | Generate embeddings from text |\n| `storeCreateCollection` | Create a vector store collection |\n| `storeVectorAdd` | Add a document to a vector collection |\n\n### AI (Core)\n\n| Atom | Description |\n| ------------ | ------------------------------------------ |\n| `llmPredict` | Simple LLM inference (`prompt` → `string`) |\n| `agentRun` | Run a sub-agent |\n\n### AI (Battery)\n\n| Atom | Description |\n| ------------------- | ---------------------------------------------- |\n| `llmPredictBattery` | Chat completion (system/user → message object) |\n| `llmVision` | Analyze images using a vision-capable model |\n\n### Procedures\n\n| Atom | Description |\n| ------------------------ | ------------------------------ |\n| `storeProcedure` | Store an AST as callable token |\n| `releaseProcedure` | Delete a stored procedure |\n| `clearExpiredProcedures` | Clean up expired tokens |\n\n### Utilities\n\n| Atom | Description |\n| --------- | ------------------------ |\n| `random` | Random number generation |\n| `uuid` | Generate UUIDs |\n| `hash` | Compute hashes |\n| `memoize` | In-memory memoization |\n| `cache` | Persistent caching |\n\n---\n\n## Battery Atoms Reference\n\nBattery atoms provide LLM, embedding, and vector store capabilities. They\nrequire a separate import and capability setup.\n\n### Setup\n\n```javascript\nimport { AgentVM } from 'tjs-lang'\nimport { batteryAtoms, getBatteries } from 'tjs-lang'\n\nconst vm = new AgentVM(batteryAtoms)\nconst batteries = await getBatteries() // auto-detects LM Studio models\n\nconst { result } = await vm.run(agent, args, {\n fuel: 1000,\n capabilities: batteries,\n})\n```\n\nThe `getBatteries()` function auto-detects LM Studio and returns:\n\n```javascript\n{\n vector: { embed }, // embedding function (undefined if no LM Studio)\n store: { ... }, // key-value + vector store (always present)\n llmBattery: { predict, embed }, // LLM chat + embeddings (null if no LM Studio)\n models: { ... }, // detected model info (null if no LM Studio)\n}\n```\n\n**Important:** `vector` and `llmBattery` will be `undefined`/`null` if LM Studio\nisn't running or the connection is made over HTTPS (local LLM calls are blocked\nfrom HTTPS contexts for security). Always check for availability or handle\nthe atom's \"missing capability\" error.\n\n### Capability Keys\n\nBattery atoms look up capabilities by specific keys that differ from the base\n`Capabilities` interface:\n\n| Capability key | Used by atoms | Contains |\n| -------------- | -------------------------------------------------------- | ------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | `{ predict, embed }` (full LLM) |\n| `vector` | `storeVectorize` | `{ embed }` only |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store operations |\n| `llm` | `llmPredict` (core atom) | `{ predict }` (simple) |\n| `fetch` | `httpFetch` (core atom) | fetch function |\n\nThe split exists because `storeVectorize` only needs the embedding function,\nwhile `llmPredictBattery` needs the full chat API. If you're providing your own\ncapabilities (not using `getBatteries()`), wire the keys accordingly.\n\n### `llmPredict` vs `llmPredictBattery`\n\nThere are two LLM atoms with different interfaces:\n\n| Atom | Input | Output | Capability |\n| ------------------- | ----------------------- | -------------- | ------------------------- |\n| `llmPredict` | `{ prompt }` | `string` | `capabilities.llm` |\n| `llmPredictBattery` | `{ system, user, ... }` | message object | `capabilities.llmBattery` |\n\nUse `llmPredict` for simple prompts. Use `llmPredictBattery` when you need\nsystem prompts, tool calling, or structured output.\n\n### `llmPredictBattery`\n\nChat completion with system prompt, tool calling, and structured output support.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ---------------- | -------- | -------- | --------------------------------------------- |\n| `system` | `string` | No | System prompt (defaults to helpful assistant) |\n| `user` | `string` | Yes | User message |\n| `tools` | `any[]` | No | Tool definitions (OpenAI format) |\n| `responseFormat` | `any` | No | Structured output format |\n\n**Output:** OpenAI chat message object:\n\n```javascript\n{\n role: 'assistant',\n content: 'The answer is 42.', // null when using tool calls\n tool_calls: [...] // present when tools are invoked\n}\n```\n\n**Example:**\n\n```javascript\nlet response = llmPredictBattery({\n system: 'You are a helpful assistant.',\n user: 'What is the capital of France?',\n})\n// response.content === 'Paris is the capital of France.'\n```\n\n**Cost:** 100 fuel\n\n### `llmVision`\n\nAnalyze images using a vision-capable model.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ---------------- | ---------- | -------- | ----------------------------------------------- |\n| `system` | `string` | No | System prompt |\n| `prompt` | `string` | Yes | Text prompt describing what to analyze |\n| `images` | `string[]` | Yes | URLs or data URIs (`data:image/...;base64,...`) |\n| `responseFormat` | `any` | No | Structured output format |\n\n**Output:** Same as `llmPredictBattery` (message object with `role`, `content`, `tool_calls`).\n\n**Example:**\n\n```javascript\nlet analysis = llmVision({\n prompt: 'Describe what you see in this image.',\n images: ['https://example.com/photo.jpg'],\n})\n// analysis.content === 'The image shows a sunset over the ocean...'\n```\n\n**Cost:** 150 fuel | **Timeout:** 120 seconds\n\n### `storeVectorize`\n\nGenerate embeddings from text using the vector battery.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------- | -------- | -------- | ---------------------- |\n| `text` | `string` | Yes | Text to embed |\n| `model` | `string` | No | Embedding model to use |\n\n**Output:** `number[]` — the embedding vector.\n\n**Example:**\n\n```javascript\nlet embedding = storeVectorize({ text: 'TJS is a typed JavaScript' })\n// embedding === [0.023, -0.412, 0.891, ...]\n```\n\n**Cost:** 20 fuel | **Capability:** `vector`\n\n### `storeCreateCollection`\n\nCreate a vector store collection for similarity search.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------ | -------- | -------- | -------------------------------- |\n| `collection` | `string` | Yes | Collection name |\n| `dimension` | `number` | No | Vector dimension (auto-detected) |\n\n**Output:** None.\n\n**Cost:** 5 fuel | **Capability:** `store`\n\n### `storeVectorAdd`\n\nAdd a document to a vector store collection. The document is automatically\nembedded and indexed.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------ | -------- | -------- | ----------------- |\n| `collection` | `string` | Yes | Collection name |\n| `doc` | `any` | Yes | Document to store |\n\n**Output:** None.\n\n**Example:**\n\n```javascript\nstoreVectorAdd({\n collection: 'articles',\n doc: { title: 'Intro to TJS', content: 'TJS is...', embedding: [...] }\n})\n```\n\n**Cost:** 5 fuel | **Capability:** `store`\n\n### `storeSearch`\n\nSearch a vector store collection by similarity.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------- | ---------- | -------- | ------------------------------ |\n| `collection` | `string` | Yes | Collection name |\n| `queryVector` | `number[]` | Yes | Query embedding vector |\n| `k` | `number` | No | Number of results (default: 5) |\n| `filter` | `object` | No | Metadata filter |\n\n**Output:** `any[]` — array of matching documents, sorted by similarity.\n\n**Example:**\n\n```javascript\nlet query = storeVectorize({ text: 'How does type checking work?' })\nlet results = storeSearch({\n collection: 'articles',\n queryVector: query,\n k: 3,\n})\n// results === [{ title: 'Type System', content: '...' }, ...]\n```\n\n**Cost:** 5 + k fuel (dynamic) | **Capability:** `store`\n\n---\n\n## Expression Builtins\n\nAJS expressions have access to safe built-in objects:\n\n### Math\n\nAll standard math functions:\n\n```javascript\nMath.abs(-5) // 5\nMath.floor(3.7) // 3\nMath.sqrt(16) // 4\nMath.sin(Math.PI) // ~0\nMath.random() // 0-1\nMath.max(1, 2, 3) // 3\nMath.min(1, 2, 3) // 1\n```\n\n### JSON\n\nParse and stringify:\n\n```javascript\nJSON.parse('{\"a\": 1}') // { a: 1 }\nJSON.stringify({ a: 1 }) // '{\"a\": 1}'\n```\n\n### Array\n\nStatic methods:\n\n```javascript\nArray.isArray([1, 2]) // true\nArray.from('abc') // ['a', 'b', 'c']\nArray.of(1, 2, 3) // [1, 2, 3]\n```\n\n### Object\n\nStatic methods:\n\n```javascript\nObject.keys({ a: 1 }) // ['a']\nObject.values({ a: 1 }) // [1]\nObject.entries({ a: 1 }) // [['a', 1]]\nObject.fromEntries([['a', 1]]) // { a: 1 }\nObject.assign({}, a, b) // merged object\n```\n\n### String\n\nStatic methods:\n\n```javascript\nString.fromCharCode(65) // 'A'\nString.fromCodePoint(128512) // emoji\n```\n\n### Number\n\nConstants and checks:\n\n```javascript\nNumber.MAX_VALUE\nNumber.isNaN(NaN) // true\nNumber.isFinite(100) // true\nNumber.parseInt('42') // 42\nNumber.parseFloat('3.14') // 3.14\n```\n\n### Set Operations\n\nSet-like operations:\n\n```javascript\nSet.add([1, 2], 3) // [1, 2, 3]\nSet.remove([1, 2, 3], 2) // [1, 3]\nSet.union([1, 2], [2, 3]) // [1, 2, 3]\nSet.intersection([1, 2], [2, 3]) // [2]\nSet.diff([1, 2, 3], [2]) // [1, 3]\n```\n\n### Date\n\nDate factory with arithmetic:\n\n```javascript\nDate.now() // timestamp\nDate.create('2024-01-15') // Date object\nDate.add(date, 1, 'day') // new Date\nDate.format(date, 'YYYY-MM-DD')\n```\n\n### Schema\n\nBuild JSON schemas for structured LLM outputs:\n\n```javascript\n// From example\nlet schema = Schema.response('person', { name: '', age: 0 })\n\n// With constraints\nlet schema = Schema.response(\n 'user',\n Schema.object({\n email: Schema.string.email,\n age: Schema.number.int.min(0).max(150).optional,\n role: Schema.enum(['admin', 'user', 'guest']),\n })\n)\n```\n\n---\n\n## JSON AST Format\n\nAJS compiles to a JSON AST. Here's what it looks like:\n\n### Sequence\n\n```json\n{\n \"$seq\": [\n { \"$op\": \"varSet\", \"key\": \"x\", \"value\": 10 },\n { \"$op\": \"varSet\", \"key\": \"y\", \"value\": 20 },\n {\n \"$op\": \"return\",\n \"value\": { \"$expr\": \"binary\", \"op\": \"+\", \"left\": \"x\", \"right\": \"y\" }\n }\n ]\n}\n```\n\n### Expressions\n\n```json\n// Literal\n{ \"$expr\": \"literal\", \"value\": 42 }\n\n// Identifier\n{ \"$expr\": \"ident\", \"name\": \"varName\" }\n\n// Binary operation\n{ \"$expr\": \"binary\", \"op\": \"+\", \"left\": {...}, \"right\": {...} }\n\n// Member access\n{ \"$expr\": \"member\", \"object\": {...}, \"property\": \"foo\" }\n\n// Template literal\n{ \"$expr\": \"template\", \"tmpl\": \"Hello, ${name}!\" }\n```\n\n### Conditionals\n\n```json\n{\n \"$op\": \"if\",\n \"cond\": { \"$expr\": \"binary\", \"op\": \">\", \"left\": \"x\", \"right\": 0 },\n \"then\": { \"$seq\": [...] },\n \"else\": { \"$seq\": [...] }\n}\n```\n\n### Loops\n\n```json\n{\n \"$op\": \"while\",\n \"cond\": { \"$expr\": \"binary\", \"op\": \">\", \"left\": \"count\", \"right\": 0 },\n \"body\": { \"$seq\": [...] }\n}\n```\n\n---\n\n## Security Model\n\n### Zero Capabilities by Default\n\nThe VM can't do anything unless you allow it:\n\n```typescript\n// This agent can only compute - no I/O\nawait vm.run(agent, args, { capabilities: {} })\n\n// This agent can fetch from one domain\nawait vm.run(agent, args, {\n capabilities: {\n fetch: createFetchCapability({ allowedHosts: ['api.example.com'] }),\n },\n})\n```\n\n### Forbidden Properties\n\nThese property names are blocked to prevent prototype pollution:\n\n- `__proto__`\n- `constructor`\n- `prototype`\n\n### SSRF Protection\n\nThe `httpFetch` atom can be configured with:\n\n- Allowlisted hosts only\n- Blocked private IP ranges\n- Request signing requirements\n\n### ReDoS Protection\n\nSuspicious regex patterns are rejected before execution.\n\n### Execution Tracing\n\nEvery agent run can produce an audit trail:\n\n```typescript\nconst { result, trace } = await vm.run(agent, args, { trace: true })\n\n// trace: [\n// { op: 'varSet', key: 'x', fuelBefore: 1000, fuelAfter: 999.9 },\n// { op: 'httpFetch', url: '...', fuelBefore: 999.9, fuelAfter: 989.9 },\n// ...\n// ]\n```\n\n---\n\n## Use Cases\n\n### AI Agents\n\n```javascript\nfunction researchAgent({ topic }) {\n let searchResults = httpFetch({\n url: `https://api.search.com?q=${topic}`,\n })\n\n let summary = llmPredict({\n system: 'You are a research assistant.',\n user: `Summarize these results about ${topic}: ${searchResults}`,\n })\n\n return { topic, summary }\n}\n```\n\n### Rule Engines\n\n```javascript\nfunction applyDiscounts({ cart, userTier }) {\n let discount = 0\n\n if (userTier === 'gold') {\n discount = 0.2\n } else if (userTier === 'silver') {\n discount = 0.1\n }\n\n if (cart.total > 100) {\n discount = discount + 0.05\n }\n\n return {\n originalTotal: cart.total,\n discount: discount,\n finalTotal: cart.total * (1 - discount),\n }\n}\n```\n\n### Smart Configuration\n\n```javascript\nfunction routeRequest({ request, config }) {\n for (let rule of config.rules) {\n if (request.path.startsWith(rule.prefix)) {\n return { backend: rule.backend, timeout: rule.timeout }\n }\n }\n return { backend: config.defaultBackend, timeout: 30000 }\n}\n```\n\n### Remote Jobs\n\n```javascript\nfunction processDataBatch({ items, transform }) {\n let results = []\n for (let item of items) {\n let processed = applyTransform(item, transform)\n results.push(processed)\n }\n return { processed: results.length, results }\n}\n```\n\n---\n\n## Custom Atoms\n\nExtend the runtime with your own operations:\n\n```typescript\nimport { defineAtom, AgentVM, s } from 'tjs-lang'\n\nconst myScraper = defineAtom(\n 'scrape', // OpCode\n s.object({ url: s.string }), // Input Schema\n s.string, // Output Schema\n async ({ url }, ctx) => {\n const res = await ctx.capabilities.fetch(url)\n return await res.text()\n },\n { cost: 5 } // Fuel cost\n)\n\nconst myVM = new AgentVM({ scrape: myScraper })\n```\n\nAtoms must:\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access I/O only via `ctx.capabilities`\n\n---\n\n## Builder API\n\nFor programmatic AST construction:\n\n```typescript\nimport { Agent, s } from 'tjs-lang'\n\nconst agent = Agent.take(s.object({ price: s.number, taxRate: s.number }))\n .varSet({ key: 'total', value: Agent.expr('price * (1 + taxRate)') })\n .return(s.object({ total: s.number }))\n\nconst ast = agent.toJSON() // JSON-serializable AST\n```\n\nThe builder is lower-level but gives full control over AST construction.\n\n---\n\n## Limitations\n\n### What AJS Doesn't Do\n\n- **No closures** - functions can't capture outer scope\n- **No classes** - use plain objects\n- **No async/await syntax** - the VM handles async internally\n- **No modules** - logic is self-contained\n- **No direct DOM access** - everything goes through capabilities\n- **No computed member access with variables** - `items[i]` is rejected; use `items[0]` (literal) or `for...of` loops\n\n### What AJS Intentionally Avoids\n\n- Complex language features that enable escape from the sandbox\n- Syntax that LLMs frequently hallucinate incorrectly\n- Patterns that make code hard to audit\n\n---\n\n## Performance\n\n- **100 agents in ~6ms** (torture test benchmark)\n- **~0.01 fuel per expression**\n- **Proportional memory charging** prevents runaway allocations\n\nAJS is interpreted (JSON AST), so it's slower than native JS. But:\n\n- Execution is predictable and bounded\n- I/O dominates most agent workloads\n- Tracing is free (built into the VM)\n\nFor compute-heavy operations in your platform code, use TJS with `wasm {}` blocks.\n\n---\n\n## Learn More\n\n- [TJS Documentation](DOCS-TJS.md) — The host language\n- [Builder's Manifesto](MANIFESTO-BUILDER.md) — Why AJS is fun\n- [Enterprise Guide](MANIFESTO-ENTERPRISE.md) — Why AJS is safe\n- [Technical Context](CONTEXT.md) — Architecture deep dive\n"
542
+ "text": "<!--{\"section\": \"ajs\", \"group\": \"docs\", \"order\": 0, \"navTitle\": \"Documentation\"}-->\n\n# AJS: The Agent Language\n\n_Code as Data. Safe. Async. Sandboxed._\n\n---\n\n## What is AJS?\n\nAJS (AsyncJS) is a JavaScript subset that compiles to a **JSON AST**. It's designed for untrusted code—user scripts, LLM-generated agents, remote logic.\n\n```javascript\nfunction searchAndSummarize({ query }) {\n let results = httpFetch({ url: `https://api.example.com/search?q=${query}` })\n let summary = llmPredict({ prompt: `Summarize: ${JSON.stringify(results)}` })\n return { query, summary }\n}\n```\n\nThis compiles to JSON that can be:\n\n- Stored in a database\n- Sent over the network\n- Executed in a sandboxed VM\n- Audited before running\n\n---\n\n## The VM\n\nAJS runs in a gas-limited, isolated VM with strict resource controls.\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function process({ url }) {\n let data = httpFetch({ url })\n return { fetched: data }\n }\n`\n\nconst vm = new AgentVM()\nconst result = await vm.run(\n agent,\n { url: 'https://api.example.com' },\n {\n fuel: 1000, // CPU budget\n timeoutMs: 5000, // Wall-clock limit\n }\n)\n```\n\n### Fuel Metering\n\nEvery operation costs fuel:\n\n| Operation | Cost |\n| ------------------------ | ---- |\n| Expression evaluation | 0.01 |\n| Variable set/get | 0.1 |\n| Control flow (if, while) | 0.5 |\n| HTTP fetch | 10 |\n| LLM predict | 100 |\n\nWhen fuel runs out, execution stops safely:\n\n```typescript\nif (result.fuelExhausted) {\n // Agent tried to run forever - stopped safely\n}\n```\n\n### Timeout Enforcement\n\nFuel protects against CPU abuse. Timeouts protect against I/O abuse:\n\n```typescript\nawait vm.run(agent, args, {\n fuel: 1000,\n timeoutMs: 5000, // Hard 5-second limit\n})\n```\n\nSlow network calls can't hang your servers.\n\n### Capability Injection\n\nThe VM starts with **zero capabilities**. You grant what each agent needs:\n\n```typescript\nconst capabilities = {\n fetch: createFetchCapability({\n allowedHosts: ['api.example.com'],\n }),\n store: createReadOnlyStore(),\n // No llm - this agent can't call AI\n}\n\nawait vm.run(agent, args, { capabilities })\n```\n\n---\n\n## Input/Output Contract\n\nAJS agents are composable — one agent's output feeds into another's input. To ensure this works reliably:\n\n- **Functions take a single destructured object parameter:** `function process({ input })`\n- **Functions must return a plain object:** `return { result }`, `return { summary, count }`\n- **Non-object returns produce an AgentError:** `return 42` or `return 'hello'` will fail\n- **Bare `return` is allowed** for void functions (no output)\n\n```javascript\n// CORRECT — object in, object out\nfunction add({ a, b }) {\n return { sum: a + b }\n}\n\n// WRONG — non-object returns are errors\nfunction add({ a, b }) {\n return a + b // AgentError: must return an object\n}\n```\n\n---\n\n## Syntax\n\nAJS is a JavaScript subset. Familiar syntax, restricted features.\n\n### What's Allowed\n\n```javascript\n// Functions\nfunction process({ input }) {\n return { output: input * 2 }\n}\n\n// Variables\nlet x = 10\nconst y = 'hello'\n\n// Conditionals\nif (x > 5) {\n return { size: 'big' }\n} else {\n return { size: 'small' }\n}\n\n// Loops\nfor (let i = 0; i < 10; i++) {\n total = total + i\n}\n\nfor (let item of items) {\n results.push(item.name)\n}\n\nwhile (count > 0) {\n count = count - 1\n}\n\n// Try/catch\ntry {\n riskyOperation()\n} catch (e) {\n return { error: e.message }\n}\n\n// Template literals\nlet message = `Hello, ${name}!`\n\n// Object/array literals\nlet obj = { a: 1, b: 2 }\nlet arr = [1, 2, 3]\n\n// Destructuring\nlet { name, age } = user\nlet [first, second] = items\n\n// Spread\nlet merged = { ...defaults, ...overrides }\nlet combined = [...arr1, ...arr2]\n\n// Ternary\nlet result = x > 0 ? 'positive' : 'non-positive'\n\n// Logical operators\nlet value = a && b\nlet fallback = a || defaultValue\nlet nullish = a ?? defaultValue\n```\n\n### What's Forbidden\n\n| Feature | Why Forbidden |\n| -------------------------- | ------------------------------------------------- |\n| `class` | Too complex for LLMs, enables prototype pollution |\n| `new` | Arbitrary object construction |\n| `this` | Implicit context, hard to sandbox |\n| Closures | State escapes the sandbox |\n| `async`/`await` | VM handles async internally |\n| `eval`, `Function` | Code injection |\n| `__proto__`, `constructor` | Prototype pollution |\n| `import`/`export` | Module system handled by host |\n\nAJS is intentionally simple—simple enough for 4B parameter LLMs to generate correctly.\n\n### Differences from JavaScript\n\nAJS expressions differ from standard JavaScript in a few important ways:\n\n**Null-safe member access.** All member access uses optional chaining internally. Accessing a property on `null` or `undefined` returns `undefined` instead of throwing `TypeError`:\n\n```javascript\nlet x = null\nlet y = x.foo.bar // undefined (no error)\n```\n\nThis is a deliberate safety choice — agents shouldn't crash on missing data.\n\n**No computed member access with variables.** You can use literal indices (`items[0]`, `obj[\"key\"]`) but not variable indices (`items[i]`). This is rejected at transpile time:\n\n```javascript\n// Works\nlet first = items[0]\nlet name = user['name']\n\n// Fails: \"Computed member access with variables not yet supported\"\nlet item = items[i]\n```\n\nWorkaround: use array atoms like `map`, `reduce`, or `for...of` loops instead of index-based access.\n\n**Structural equality.** `==` and `!=` perform deep structural comparison, not reference or coerced equality. No type coercion: `'1' == 1` is `false`. Use `===` and `!==` for identity (reference) checks:\n\n```javascript\n[1, 2] == [1, 2] // true (structural)\n[1, 2] === [1, 2] // false (different objects)\n{ a: 1 } == { a: 1 } // true (structural)\n'1' == 1 // false (no coercion, unlike JS)\nnull == undefined // true (nullish equality preserved)\n```\n\nObjects with a `.Equals` method or `[Symbol.for('tjs.equals')]` handler get custom comparison behavior.\n\n---\n\n## Atoms\n\nAtoms are the built-in operations. Each atom has a defined cost, input schema, and output schema.\n\n### Flow Control\n\n| Atom | Description |\n| -------- | ------------------------------ |\n| `seq` | Execute operations in sequence |\n| `if` | Conditional branching |\n| `while` | Loop with condition |\n| `return` | Return a value |\n| `try` | Error handling |\n\n### State Management\n\n| Atom | Description |\n| ------------ | -------------------------- |\n| `varSet` | Set a variable |\n| `varGet` | Get a variable |\n| `varsLet` | Batch variable declaration |\n| `varsImport` | Import from arguments |\n| `varsExport` | Export as result |\n| `scope` | Create a local scope |\n\n### I/O\n\n| Atom | Description |\n| ----------- | ------------------------------------------- |\n| `httpFetch` | HTTP requests (requires `fetch` capability) |\n\n### Storage (Core)\n\n| Atom | Description |\n| ------------- | ------------------------ |\n| `storeGet` | Get from key-value store |\n| `storeSet` | Set in key-value store |\n| `storeSearch` | Vector similarity search |\n\n### Storage (Battery)\n\n| Atom | Description |\n| ----------------------- | ------------------------------------- |\n| `storeVectorize` | Generate embeddings from text |\n| `storeCreateCollection` | Create a vector store collection |\n| `storeVectorAdd` | Add a document to a vector collection |\n\n### AI (Core)\n\n| Atom | Description |\n| ------------ | ------------------------------------------ |\n| `llmPredict` | Simple LLM inference (`prompt` → `string`) |\n| `agentRun` | Run a sub-agent |\n\n### AI (Battery)\n\n| Atom | Description |\n| ------------------- | ---------------------------------------------- |\n| `llmPredictBattery` | Chat completion (system/user → message object) |\n| `llmVision` | Analyze images using a vision-capable model |\n\n### Procedures\n\n| Atom | Description |\n| ------------------------ | ------------------------------ |\n| `storeProcedure` | Store an AST as callable token |\n| `releaseProcedure` | Delete a stored procedure |\n| `clearExpiredProcedures` | Clean up expired tokens |\n\n### Utilities\n\n| Atom | Description |\n| --------- | ------------------------ |\n| `random` | Random number generation |\n| `uuid` | Generate UUIDs |\n| `hash` | Compute hashes |\n| `memoize` | In-memory memoization |\n| `cache` | Persistent caching |\n\n---\n\n## Battery Atoms Reference\n\nBattery atoms provide LLM, embedding, and vector store capabilities. They\nrequire a separate import and capability setup.\n\n### Setup\n\n```javascript\nimport { AgentVM } from 'tjs-lang'\nimport { batteryAtoms, getBatteries } from 'tjs-lang'\n\nconst vm = new AgentVM(batteryAtoms)\nconst batteries = await getBatteries() // auto-detects LM Studio models\n\nconst { result } = await vm.run(agent, args, {\n fuel: 1000,\n capabilities: batteries,\n})\n```\n\nThe `getBatteries()` function auto-detects LM Studio and returns:\n\n```javascript\n{\n vector: { embed }, // embedding function (undefined if no LM Studio)\n store: { ... }, // key-value + vector store (always present)\n llmBattery: { predict, embed }, // LLM chat + embeddings (null if no LM Studio)\n models: { ... }, // detected model info (null if no LM Studio)\n}\n```\n\n**Important:** `vector` and `llmBattery` will be `undefined`/`null` if LM Studio\nisn't running or the connection is made over HTTPS (local LLM calls are blocked\nfrom HTTPS contexts for security). Always check for availability or handle\nthe atom's \"missing capability\" error.\n\n### Capability Keys\n\nBattery atoms look up capabilities by specific keys that differ from the base\n`Capabilities` interface:\n\n| Capability key | Used by atoms | Contains |\n| -------------- | -------------------------------------------------------- | ------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | `{ predict, embed }` (full LLM) |\n| `vector` | `storeVectorize` | `{ embed }` only |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store operations |\n| `llm` | `llmPredict` (core atom) | `{ predict }` (simple) |\n| `fetch` | `httpFetch` (core atom) | fetch function |\n\nThe split exists because `storeVectorize` only needs the embedding function,\nwhile `llmPredictBattery` needs the full chat API. If you're providing your own\ncapabilities (not using `getBatteries()`), wire the keys accordingly.\n\n### `llmPredict` vs `llmPredictBattery`\n\nThere are two LLM atoms with different interfaces:\n\n| Atom | Input | Output | Capability |\n| ------------------- | ----------------------- | -------------- | ------------------------- |\n| `llmPredict` | `{ prompt }` | `string` | `capabilities.llm` |\n| `llmPredictBattery` | `{ system, user, ... }` | message object | `capabilities.llmBattery` |\n\nUse `llmPredict` for simple prompts. Use `llmPredictBattery` when you need\nsystem prompts, tool calling, or structured output.\n\n### `llmPredictBattery`\n\nChat completion with system prompt, tool calling, and structured output support.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ---------------- | -------- | -------- | --------------------------------------------- |\n| `system` | `string` | No | System prompt (defaults to helpful assistant) |\n| `user` | `string` | Yes | User message |\n| `tools` | `any[]` | No | Tool definitions (OpenAI format) |\n| `responseFormat` | `any` | No | Structured output format |\n\n**Output:** OpenAI chat message object:\n\n```javascript\n{\n role: 'assistant',\n content: 'The answer is 42.', // null when using tool calls\n tool_calls: [...] // present when tools are invoked\n}\n```\n\n**Example:**\n\n```javascript\nlet response = llmPredictBattery({\n system: 'You are a helpful assistant.',\n user: 'What is the capital of France?',\n})\n// response.content === 'Paris is the capital of France.'\n```\n\n**Cost:** 100 fuel\n\n### `llmVision`\n\nAnalyze images using a vision-capable model.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ---------------- | ---------- | -------- | ----------------------------------------------- |\n| `system` | `string` | No | System prompt |\n| `prompt` | `string` | Yes | Text prompt describing what to analyze |\n| `images` | `string[]` | Yes | URLs or data URIs (`data:image/...;base64,...`) |\n| `responseFormat` | `any` | No | Structured output format |\n\n**Output:** Same as `llmPredictBattery` (message object with `role`, `content`, `tool_calls`).\n\n**Example:**\n\n```javascript\nlet analysis = llmVision({\n prompt: 'Describe what you see in this image.',\n images: ['https://example.com/photo.jpg'],\n})\n// analysis.content === 'The image shows a sunset over the ocean...'\n```\n\n**Cost:** 150 fuel | **Timeout:** 120 seconds\n\n### `storeVectorize`\n\nGenerate embeddings from text using the vector battery.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------- | -------- | -------- | ---------------------- |\n| `text` | `string` | Yes | Text to embed |\n| `model` | `string` | No | Embedding model to use |\n\n**Output:** `number[]` — the embedding vector.\n\n**Example:**\n\n```javascript\nlet embedding = storeVectorize({ text: 'TJS is a typed JavaScript' })\n// embedding === [0.023, -0.412, 0.891, ...]\n```\n\n**Cost:** 20 fuel | **Capability:** `vector`\n\n### `storeCreateCollection`\n\nCreate a vector store collection for similarity search.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------ | -------- | -------- | -------------------------------- |\n| `collection` | `string` | Yes | Collection name |\n| `dimension` | `number` | No | Vector dimension (auto-detected) |\n\n**Output:** None.\n\n**Cost:** 5 fuel | **Capability:** `store`\n\n### `storeVectorAdd`\n\nAdd a document to a vector store collection. The document is automatically\nembedded and indexed.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------ | -------- | -------- | ----------------- |\n| `collection` | `string` | Yes | Collection name |\n| `doc` | `any` | Yes | Document to store |\n\n**Output:** None.\n\n**Example:**\n\n```javascript\nstoreVectorAdd({\n collection: 'articles',\n doc: { title: 'Intro to TJS', content: 'TJS is...', embedding: [...] }\n})\n```\n\n**Cost:** 5 fuel | **Capability:** `store`\n\n### `storeSearch`\n\nSearch a vector store collection by similarity.\n\n**Input:**\n\n| Field | Type | Required | Description |\n| ------------- | ---------- | -------- | ------------------------------ |\n| `collection` | `string` | Yes | Collection name |\n| `queryVector` | `number[]` | Yes | Query embedding vector |\n| `k` | `number` | No | Number of results (default: 5) |\n| `filter` | `object` | No | Metadata filter |\n\n**Output:** `any[]` — array of matching documents, sorted by similarity.\n\n**Example:**\n\n```javascript\nlet query = storeVectorize({ text: 'How does type checking work?' })\nlet results = storeSearch({\n collection: 'articles',\n queryVector: query,\n k: 3,\n})\n// results === [{ title: 'Type System', content: '...' }, ...]\n```\n\n**Cost:** 5 + k fuel (dynamic) | **Capability:** `store`\n\n---\n\n## Expression Builtins\n\nAJS expressions have access to safe built-in objects:\n\n### Math\n\nAll standard math functions:\n\n```javascript\nMath.abs(-5) // 5\nMath.floor(3.7) // 3\nMath.sqrt(16) // 4\nMath.sin(Math.PI) // ~0\nMath.random() // 0-1\nMath.max(1, 2, 3) // 3\nMath.min(1, 2, 3) // 1\n```\n\n### JSON\n\nParse and stringify:\n\n```javascript\nJSON.parse('{\"a\": 1}') // { a: 1 }\nJSON.stringify({ a: 1 }) // '{\"a\": 1}'\n```\n\n### Array\n\nStatic methods:\n\n```javascript\nArray.isArray([1, 2]) // true\nArray.from('abc') // ['a', 'b', 'c']\nArray.of(1, 2, 3) // [1, 2, 3]\n```\n\n### Object\n\nStatic methods:\n\n```javascript\nObject.keys({ a: 1 }) // ['a']\nObject.values({ a: 1 }) // [1]\nObject.entries({ a: 1 }) // [['a', 1]]\nObject.fromEntries([['a', 1]]) // { a: 1 }\nObject.assign({}, a, b) // merged object\n```\n\n### String\n\nStatic methods:\n\n```javascript\nString.fromCharCode(65) // 'A'\nString.fromCodePoint(128512) // emoji\n```\n\n### Number\n\nConstants and checks:\n\n```javascript\nNumber.MAX_VALUE\nNumber.isNaN(NaN) // true\nNumber.isFinite(100) // true\nNumber.parseInt('42') // 42\nNumber.parseFloat('3.14') // 3.14\n```\n\n### Set Operations\n\nSet-like operations:\n\n```javascript\nSet.add([1, 2], 3) // [1, 2, 3]\nSet.remove([1, 2, 3], 2) // [1, 3]\nSet.union([1, 2], [2, 3]) // [1, 2, 3]\nSet.intersection([1, 2], [2, 3]) // [2]\nSet.diff([1, 2, 3], [2]) // [1, 3]\n```\n\n### Date\n\nDate factory with arithmetic:\n\n```javascript\nDate.now() // timestamp\nDate.create('2024-01-15') // Date object\nDate.add(date, 1, 'day') // new Date\nDate.format(date, 'YYYY-MM-DD')\n```\n\n### Schema\n\nBuild JSON schemas for structured LLM outputs:\n\n```javascript\n// From example\nlet schema = Schema.response('person', { name: '', age: 0 })\n\n// With constraints\nlet schema = Schema.response(\n 'user',\n Schema.object({\n email: Schema.string.email,\n age: Schema.number.int.min(0).max(150).optional,\n role: Schema.enum(['admin', 'user', 'guest']),\n })\n)\n```\n\n---\n\n## JSON AST Format\n\nAJS compiles to a JSON AST. Here's what it looks like:\n\n### Sequence\n\n```json\n{\n \"$seq\": [\n { \"$op\": \"varSet\", \"key\": \"x\", \"value\": 10 },\n { \"$op\": \"varSet\", \"key\": \"y\", \"value\": 20 },\n {\n \"$op\": \"return\",\n \"value\": { \"$expr\": \"binary\", \"op\": \"+\", \"left\": \"x\", \"right\": \"y\" }\n }\n ]\n}\n```\n\n### Expressions\n\n```json\n// Literal\n{ \"$expr\": \"literal\", \"value\": 42 }\n\n// Identifier\n{ \"$expr\": \"ident\", \"name\": \"varName\" }\n\n// Binary operation\n{ \"$expr\": \"binary\", \"op\": \"+\", \"left\": {...}, \"right\": {...} }\n\n// Member access\n{ \"$expr\": \"member\", \"object\": {...}, \"property\": \"foo\" }\n\n// Template literal\n{ \"$expr\": \"template\", \"tmpl\": \"Hello, ${name}!\" }\n```\n\n### Conditionals\n\n```json\n{\n \"$op\": \"if\",\n \"cond\": { \"$expr\": \"binary\", \"op\": \">\", \"left\": \"x\", \"right\": 0 },\n \"then\": { \"$seq\": [...] },\n \"else\": { \"$seq\": [...] }\n}\n```\n\n### Loops\n\n```json\n{\n \"$op\": \"while\",\n \"cond\": { \"$expr\": \"binary\", \"op\": \">\", \"left\": \"count\", \"right\": 0 },\n \"body\": { \"$seq\": [...] }\n}\n```\n\n---\n\n## Security Model\n\n### Zero Capabilities by Default\n\nThe VM can't do anything unless you allow it:\n\n```typescript\n// This agent can only compute - no I/O\nawait vm.run(agent, args, { capabilities: {} })\n\n// This agent can fetch from one domain\nawait vm.run(agent, args, {\n capabilities: {\n fetch: createFetchCapability({ allowedHosts: ['api.example.com'] }),\n },\n})\n```\n\n### Forbidden Properties\n\nThese property names are blocked to prevent prototype pollution:\n\n- `__proto__`\n- `constructor`\n- `prototype`\n\n### SSRF Protection\n\nThe `httpFetch` atom can be configured with:\n\n- Allowlisted hosts only\n- Blocked private IP ranges\n- Request signing requirements\n\n### ReDoS Protection\n\nSuspicious regex patterns are rejected before execution.\n\n### Execution Tracing\n\nEvery agent run can produce an audit trail:\n\n```typescript\nconst { result, trace } = await vm.run(agent, args, { trace: true })\n\n// trace: [\n// { op: 'varSet', key: 'x', fuelBefore: 1000, fuelAfter: 999.9 },\n// { op: 'httpFetch', url: '...', fuelBefore: 999.9, fuelAfter: 989.9 },\n// ...\n// ]\n```\n\n---\n\n## Use Cases\n\n### AI Agents\n\n```javascript\nfunction researchAgent({ topic }) {\n let searchResults = httpFetch({\n url: `https://api.search.com?q=${topic}`,\n })\n\n let summary = llmPredict({\n system: 'You are a research assistant.',\n user: `Summarize these results about ${topic}: ${searchResults}`,\n })\n\n return { topic, summary }\n}\n```\n\n### Rule Engines\n\n```javascript\nfunction applyDiscounts({ cart, userTier }) {\n let discount = 0\n\n if (userTier === 'gold') {\n discount = 0.2\n } else if (userTier === 'silver') {\n discount = 0.1\n }\n\n if (cart.total > 100) {\n discount = discount + 0.05\n }\n\n return {\n originalTotal: cart.total,\n discount: discount,\n finalTotal: cart.total * (1 - discount),\n }\n}\n```\n\n### Smart Configuration\n\n```javascript\nfunction routeRequest({ request, config }) {\n for (let rule of config.rules) {\n if (request.path.startsWith(rule.prefix)) {\n return { backend: rule.backend, timeout: rule.timeout }\n }\n }\n return { backend: config.defaultBackend, timeout: 30000 }\n}\n```\n\n### Remote Jobs\n\n```javascript\nfunction processDataBatch({ items, transform }) {\n let results = []\n for (let item of items) {\n let processed = applyTransform(item, transform)\n results.push(processed)\n }\n return { processed: results.length, results }\n}\n```\n\n---\n\n## Custom Atoms\n\nExtend the runtime with your own operations:\n\n```typescript\nimport { defineAtom, AgentVM, s } from 'tjs-lang'\n\nconst myScraper = defineAtom(\n 'scrape', // OpCode\n s.object({ url: s.string }), // Input Schema\n s.string, // Output Schema\n async ({ url }, ctx) => {\n const res = await ctx.capabilities.fetch(url)\n return await res.text()\n },\n { cost: 5 } // Fuel cost\n)\n\nconst myVM = new AgentVM({ scrape: myScraper })\n```\n\nAtoms must:\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access I/O only via `ctx.capabilities`\n\n---\n\n## Builder API\n\nFor programmatic AST construction:\n\n```typescript\nimport { Agent, s } from 'tjs-lang'\n\nconst agent = Agent.take(s.object({ price: s.number, taxRate: s.number }))\n .varSet({ key: 'total', value: Agent.expr('price * (1 + taxRate)') })\n .return(s.object({ total: s.number }))\n\nconst ast = agent.toJSON() // JSON-serializable AST\n```\n\nThe builder is lower-level but gives full control over AST construction.\n\n---\n\n## Limitations\n\n### What AJS Doesn't Do\n\n- **No closures** - functions can't capture outer scope\n- **No classes** - use plain objects\n- **No async/await syntax** - the VM handles async internally\n- **No modules** - logic is self-contained\n- **No direct DOM access** - everything goes through capabilities\n- **No computed member access with variables** - `items[i]` is rejected; use `items[0]` (literal) or `for...of` loops\n\n### What AJS Intentionally Avoids\n\n- Complex language features that enable escape from the sandbox\n- Syntax that LLMs frequently hallucinate incorrectly\n- Patterns that make code hard to audit\n\n---\n\n## Performance\n\n- **100 agents in ~6ms** (torture test benchmark)\n- **~0.01 fuel per expression**\n- **Proportional memory charging** prevents runaway allocations\n\nAJS is interpreted (JSON AST), so it's slower than native JS. But:\n\n- Execution is predictable and bounded\n- I/O dominates most agent workloads\n- Tracing is free (built into the VM)\n\nFor compute-heavy operations in your platform code, use TJS with `wasm {}` blocks.\n\n---\n\n## Learn More\n\n- [TJS Documentation](DOCS-TJS.md) — The host language\n- [Builder's Manifesto](MANIFESTO-BUILDER.md) — Why AJS is fun\n- [Enterprise Guide](MANIFESTO-ENTERPRISE.md) — Why AJS is safe\n- [Technical Context](CONTEXT.md) — Architecture deep dive\n"
543
543
  },
544
544
  {
545
545
  "title": "tjs-lang Technical Context",
@@ -631,7 +631,7 @@
631
631
  "title": "AJS LLM System Prompt",
632
632
  "filename": "ajs-llm-prompt.md",
633
633
  "path": "guides/ajs-llm-prompt.md",
634
- "text": "# AJS LLM System Prompt\n\n> **Maintenance Note:** This prompt must be updated when [ajs.md](./ajs.md) changes.\n> Key areas to sync: type syntax, built-ins (Set/Date), control flow, and forbidden constructs.\n\nUse this system prompt when asking an LLM to generate AJS code.\n\n---\n\n## System Prompt\n\n````\nYou are an expert code generator for **AJS**, a specialized subset of JavaScript for AI Agents.\nAJS looks like JavaScript but has strict differences. You must adhere to these rules:\n\n### 1. SYNTAX & TYPES\n- **Types by Example:** Do NOT use TypeScript types. Use \"Example Types\" where the value implies the type.\n - WRONG: `function search(query: string, limit?: number)`\n - RIGHT: `function search(query: 'search term', limit = 10)`\n - `name: 'value'` means REQUIRED string. `count: 5` means REQUIRED number. `name = 'value'` means OPTIONAL.\n- **Number Parameters:** Use ACTUAL NUMBER LITERALS, never strings or type names.\n - WRONG: `function add(a: 'number', b: 'number')` - 'number' is a STRING!\n - WRONG: `function add(a: '5', b: '10')` - these are STRINGS in quotes!\n - RIGHT: `function add(a: 0, b: 0)` - bare numbers, no quotes\n - RIGHT: `function factorial(n: 5)` - bare number literal\n- **No Return Type Annotations:** Do NOT add return types after the parameter list.\n - WRONG: `function foo(x: 0): number { ... }`\n - WRONG: `function foo(x: 0) -> number { ... }`\n - RIGHT: `function foo(x: 0) { ... }`\n- **No Classes:** Do NOT use `class`, `new`, `this`, or `prototype`.\n- **No Async/Await:** Do NOT use `async` or `await`. All functions are implicitly asynchronous.\n - WRONG: `let x = await fetch(...)`\n - RIGHT: `let x = httpFetch({ url: '...' })`\n\n### 2. BUILT-INS & FACTORIES\n- **No `new` Keyword:** Never use `new`. Use factory functions.\n - WRONG: `new Date()`, `new Set()`, `new Array()`\n - RIGHT: `Date()`, `Set([1,2])`, `['a','b']`\n- **Date Objects:** `Date()` returns an **immutable** object.\n - Months are 1-indexed (1=Jan, not 0=Jan).\n - Methods like `.add({ days: 5 })` return a NEW Date object.\n - Access components: `.year`, `.month`, `.day`, `.hours`, `.minutes`, `.seconds`\n - Format: `.format('date')`, `.format('iso')`, `.format('YYYY-MM-DD')`\n- **Set Objects:** `Set([items])` returns an object with:\n - Mutable: `.add(x)`, `.remove(x)`, `.clear()`\n - Immutable algebra: `.union(other)`, `.intersection(other)`, `.diff(other)` - return NEW Sets\n - Query: `.has(x)`, `.size`, `.toArray()`\n- **Optional Chaining:** Use `?.` for safe property access: `obj?.nested?.value`\n- **Schema Filtering:** `filter(data, schema)` strips extra properties:\n - `filter({ a: 1, b: 2, extra: 3 }, { a: 0, b: 0 })` returns `{ a: 1, b: 2 }`\n - Useful for sanitizing LLM outputs or API responses\n\n### 3. ATOMS VS. BUILT-INS\n- **Atoms (External Tools):** ALWAYS accept a single object argument.\n - Pattern: `atomName({ param: value })`\n - Examples: `search({ query: topic })`, `llmPredict({ system: '...', user: '...' })`\n - **template atom:** `template({ tmpl: 'Hello, {{name}}!', vars: { name } })` - for string interpolation\n - IMPORTANT: Use SINGLE QUOTES for tmpl, NOT backticks! Backticks cause parse errors.\n - WRONG: `template({ tmpl: \\`{{name}}\\`, vars: { name } })`\n - RIGHT: `template({ tmpl: '{{name}}', vars: { name } })`\n- **Built-ins (Math, JSON, String, Array):** Use standard JS syntax.\n - `Math.max(1, 2)`, `JSON.parse(str)`, `str.split(',')`, `arr.map(x => x * 2)`\n\n### 4. ERROR HANDLING\n- Errors propagate automatically (Monadic flow). If one step fails, subsequent steps are skipped.\n- Only use `try/catch` if you need to recover from a failure and continue.\n\n### 5. FORBIDDEN CONSTRUCTS\nThese will cause transpile errors:\n- `async`, `await` - not needed, all calls are implicitly async\n- `new` - use factory functions instead\n- `class`, `this` - use plain functions and objects\n- `var` - use `let` instead\n- `import`, `require` - atoms must be registered with the VM\n- `console.log` - use trace capabilities if needed\n\n### EXAMPLES\n\n**Example 1: Search Agent**\n```javascript\nfunction researchAgent(topic: 'quantum computing') {\n let searchResults = search({ query: topic, limit: 5 })\n if (searchResults?.length == 0) {\n return { error: 'No results found' }\n }\n let summary = summarize({ text: JSON.stringify(searchResults), length: 'short' })\n return { summary }\n}\n```\n\n**Example 2: Factorial with while loop (number parameter)**\n```javascript\nfunction factorial(n: 5) {\n let result = 1\n let i = n\n while (i > 1) {\n result = result * i\n i = i - 1\n }\n return { result }\n}\n```\n\n**Example 3: Math with multiple number parameters**\n```javascript\nfunction calculateVolume(width: 2, height: 3, depth: 4) {\n let volume = width * height * depth\n return { volume }\n}\n```\nNote: width, height, depth are BARE NUMBERS (2, 3, 4), NOT strings like '2' or 'number'!\n\n**Example 4: Greeting with template atom**\n```javascript\nfunction greet(name: 'World', greeting = 'Hello') {\n let message = template({ tmpl: '{{greeting}}, {{name}}!', vars: { greeting, name } })\n return { message }\n}\n```\n````\n\n```\n\n---\n\n## Self-Correction Loop\n\nWhen testing with local LLMs, implement error feedback:\n\n1. Run the LLM with this prompt\n2. If output contains `async`, `await`, `new`, `class`, or `this`, feed back:\n > \"Error: You used '[keyword]'. AJS forbids '[keyword]'. [Alternative].\"\n3. The model typically fixes it on the second attempt\n\nExample corrections:\n- `new Date()` → \"Use `Date()` factory function instead\"\n- `await fetch()` → \"Remove `await`, use `httpFetch({ url })` - all calls are implicitly async\"\n- `class Agent` → \"Use plain functions, AJS is purely functional\"\n\n---\n\n## Compact Version (for context-limited models)\n\n```\n\nYou generate AJS code. Rules:\n\n1. Types by example: `fn(name: 'string', count: 10)` - string in quotes, numbers BARE (no quotes!)\n - WRONG: `fn(x: 'number')` or `fn(x: '5')` - these are STRINGS\n - RIGHT: `fn(x: 0)` or `fn(x: 5)` - bare number literals\n2. NO: async/await, new, class, this, var, import, return type annotations\n3. Atoms use object args: `search({ query: x })`. Built-ins normal: `Math.max(1,2)`\n4. Factories: `Date()`, `Set([1,2])` - no `new` keyword\n5. Date is immutable, months 1-12. Set has .add/.remove (mutable) and .union/.diff (immutable)\n6. Use `?.` for optional chaining: `obj?.prop?.value`\n7. Use `filter(data, schema)` to strip extra properties from objects\n\n```\n\n```\n"
634
+ "text": "# AJS LLM System Prompt\n\n> **Maintenance Note:** This prompt must be updated when [ajs.md](./ajs.md) changes.\n> Key areas to sync: type syntax, built-ins (Set/Date), control flow, and forbidden constructs.\n\nUse this system prompt when asking an LLM to generate AJS code.\n\n---\n\n## System Prompt\n\n````\nYou are an expert code generator for **AJS**, a specialized subset of JavaScript for AI Agents.\nAJS looks like JavaScript but has strict differences. You must adhere to these rules:\n\n### 1. INPUT/OUTPUT CONTRACT\n- Functions take a SINGLE **destructured object** parameter: `function foo({ a, b })`\n - WRONG: `function foo(a, b)` — positional params not supported\n - RIGHT: `function foo({ a, b })` — destructured object\n- Functions MUST return a **plain object**: `return { result }`, `return { summary, count }`\n - WRONG: `return 42`, `return 'hello'`, `return [1, 2]`\n - RIGHT: `return { result: 42 }`, `return { message: 'hello' }`, `return { items: [1, 2] }`\n - Bare `return` (no value) is allowed for void functions\n- Non-object returns produce a runtime error (AgentError)\n\n### 2. SYNTAX & TYPES\n- **Types by Example:** Do NOT use TypeScript types. Use \"Example Types\" where the value implies the type.\n - WRONG: `function search(query: string, limit?: number)`\n - RIGHT: `function search(query: 'search term', limit = 10)`\n - `name: 'value'` means REQUIRED string. `count: 5` means REQUIRED number. `name = 'value'` means OPTIONAL.\n- **Number Parameters:** Use ACTUAL NUMBER LITERALS, never strings or type names.\n - WRONG: `function add(a: 'number', b: 'number')` - 'number' is a STRING!\n - WRONG: `function add(a: '5', b: '10')` - these are STRINGS in quotes!\n - RIGHT: `function add(a: 0, b: 0)` - bare numbers, no quotes\n - RIGHT: `function factorial(n: 5)` - bare number literal\n- **No Return Type Annotations:** Do NOT add return types after the parameter list.\n - WRONG: `function foo(x: 0): number { ... }`\n - WRONG: `function foo(x: 0) -> number { ... }`\n - RIGHT: `function foo(x: 0) { ... }`\n- **No Classes:** Do NOT use `class`, `new`, `this`, or `prototype`.\n- **No Async/Await:** Do NOT use `async` or `await`. All functions are implicitly asynchronous.\n - WRONG: `let x = await fetch(...)`\n - RIGHT: `let x = httpFetch({ url: '...' })`\n\n### 3. BUILT-INS & FACTORIES\n- **No `new` Keyword:** Never use `new`. Use factory functions.\n - WRONG: `new Date()`, `new Set()`, `new Array()`\n - RIGHT: `Date()`, `Set([1,2])`, `['a','b']`\n- **Date Objects:** `Date()` returns an **immutable** object.\n - Months are 1-indexed (1=Jan, not 0=Jan).\n - Methods like `.add({ days: 5 })` return a NEW Date object.\n - Access components: `.year`, `.month`, `.day`, `.hours`, `.minutes`, `.seconds`\n - Format: `.format('date')`, `.format('iso')`, `.format('YYYY-MM-DD')`\n- **Set Objects:** `Set([items])` returns an object with:\n - Mutable: `.add(x)`, `.remove(x)`, `.clear()`\n - Immutable algebra: `.union(other)`, `.intersection(other)`, `.diff(other)` - return NEW Sets\n - Query: `.has(x)`, `.size`, `.toArray()`\n- **Optional Chaining:** Use `?.` for safe property access: `obj?.nested?.value`\n- **Schema Filtering:** `filter(data, schema)` strips extra properties:\n - `filter({ a: 1, b: 2, extra: 3 }, { a: 0, b: 0 })` returns `{ a: 1, b: 2 }`\n - Useful for sanitizing LLM outputs or API responses\n\n### 4. ATOMS VS. BUILT-INS\n- **Atoms (External Tools):** ALWAYS accept a single object argument.\n - Pattern: `atomName({ param: value })`\n - Examples: `search({ query: topic })`, `llmPredict({ system: '...', user: '...' })`\n - **template atom:** `template({ tmpl: 'Hello, {{name}}!', vars: { name } })` - for string interpolation\n - IMPORTANT: Use SINGLE QUOTES for tmpl, NOT backticks! Backticks cause parse errors.\n - WRONG: `template({ tmpl: \\`{{name}}\\`, vars: { name } })`\n - RIGHT: `template({ tmpl: '{{name}}', vars: { name } })`\n- **Built-ins (Math, JSON, String, Array):** Use standard JS syntax.\n - `Math.max(1, 2)`, `JSON.parse(str)`, `str.split(',')`, `arr.map(x => x * 2)`\n\n### 5. ERROR HANDLING\n- Errors propagate automatically (Monadic flow). If one step fails, subsequent steps are skipped.\n- Only use `try/catch` if you need to recover from a failure and continue.\n\n### 6. FORBIDDEN CONSTRUCTS\nThese will cause transpile errors:\n- `async`, `await` - not needed, all calls are implicitly async\n- `new` - use factory functions instead\n- `class`, `this` - use plain functions and objects\n- `var` - use `let` instead\n- `import`, `require` - atoms must be registered with the VM\n- `console.log` - use trace capabilities if needed\n\n### EXAMPLES\n\n**Example 1: Search Agent**\n```javascript\nfunction researchAgent(topic: 'quantum computing') {\n let searchResults = search({ query: topic, limit: 5 })\n if (searchResults?.length == 0) {\n return { error: 'No results found' }\n }\n let summary = summarize({ text: JSON.stringify(searchResults), length: 'short' })\n return { summary }\n}\n```\n\n**Example 2: Factorial with while loop (number parameter)**\n```javascript\nfunction factorial(n: 5) {\n let result = 1\n let i = n\n while (i > 1) {\n result = result * i\n i = i - 1\n }\n return { result }\n}\n```\n\n**Example 3: Math with multiple number parameters**\n```javascript\nfunction calculateVolume(width: 2, height: 3, depth: 4) {\n let volume = width * height * depth\n return { volume }\n}\n```\nNote: width, height, depth are BARE NUMBERS (2, 3, 4), NOT strings like '2' or 'number'!\n\n**Example 4: Greeting with template atom**\n```javascript\nfunction greet(name: 'World', greeting = 'Hello') {\n let message = template({ tmpl: '{{greeting}}, {{name}}!', vars: { greeting, name } })\n return { message }\n}\n```\n````\n\n```\n\n---\n\n## Self-Correction Loop\n\nWhen testing with local LLMs, implement error feedback:\n\n1. Run the LLM with this prompt\n2. If output contains `async`, `await`, `new`, `class`, or `this`, feed back:\n > \"Error: You used '[keyword]'. AJS forbids '[keyword]'. [Alternative].\"\n3. The model typically fixes it on the second attempt\n\nExample corrections:\n- `new Date()` → \"Use `Date()` factory function instead\"\n- `await fetch()` → \"Remove `await`, use `httpFetch({ url })` - all calls are implicitly async\"\n- `class Agent` → \"Use plain functions, AJS is purely functional\"\n\n---\n\n## Compact Version (for context-limited models)\n\n```\n\nYou generate AJS code. Rules:\n\n1. Functions take a destructured object param and MUST return objects: `function foo({ a, b }) { return { result: a + b } }`\n - WRONG: `return 42` or `return 'hello'` — non-object returns are errors\n2. Types by example: `fn({ name: 'string', count: 10 })` - string in quotes, numbers BARE (no quotes!)\n - WRONG: `fn({ x: 'number' })` or `fn({ x: '5' })` - these are STRINGS\n - RIGHT: `fn({ x: 0 })` or `fn({ x: 5 })` - bare number literals\n3. NO: async/await, new, class, this, var, import, return type annotations\n4. Atoms use object args: `search({ query: x })`. Built-ins normal: `Math.max(1,2)`\n5. Factories: `Date()`, `Set([1,2])` - no `new` keyword\n6. Date is immutable, months 1-12. Set has .add/.remove (mutable) and .union/.diff (immutable)\n7. Use `?.` for optional chaining: `obj?.prop?.value`\n8. Use `filter(data, schema)` to strip extra properties from objects\n\n```\n\n```\n"
635
635
  },
636
636
  {
637
637
  "title": "AJS Patterns",
@@ -90,7 +90,7 @@ export class DemoNav extends Component {
90
90
  private mdViewer: MarkdownViewer | null = null
91
91
 
92
92
  // Track current selection for highlighting
93
- private _currentView: 'home' | 'ajs' | 'tjs' = 'home'
93
+ private _currentView: 'home' | 'ajs' | 'tjs' | 'ts' = 'home'
94
94
  private _currentExample: string | null = null
95
95
 
96
96
  // Computed example arrays from docs
@@ -113,13 +113,15 @@ export class DemoNav extends Component {
113
113
  return this._currentView
114
114
  }
115
115
 
116
- set currentView(value: 'home' | 'ajs' | 'tjs') {
116
+ set currentView(value: 'home' | 'ajs' | 'tjs' | 'ts') {
117
117
  this._currentView = value
118
118
  // Auto-open the appropriate section
119
119
  if (value === 'ajs') {
120
120
  this.openSection = 'ajs-demos'
121
121
  } else if (value === 'tjs') {
122
122
  this.openSection = 'tjs-demos'
123
+ } else if (value === 'ts') {
124
+ this.openSection = 'ts-demos'
123
125
  }
124
126
  this.rebuildNav()
125
127
  // Update indicator after rebuild (DOM now exists)
@@ -164,11 +166,16 @@ export class DemoNav extends Component {
164
166
  } else if (view === 'tjs') {
165
167
  this._currentView = 'tjs'
166
168
  this.openSection = 'tjs-demos'
169
+ } else if (view === 'ts') {
170
+ this._currentView = 'ts'
171
+ this.openSection = 'ts-demos'
167
172
  } else if (view === 'home') {
168
173
  this._currentView = 'home'
169
174
  } else if (
170
175
  section &&
171
- ['ajs-demos', 'tjs-demos', 'ajs-docs', 'tjs-docs'].includes(section)
176
+ ['ajs-demos', 'tjs-demos', 'ts-demos', 'ajs-docs', 'tjs-docs'].includes(
177
+ section
178
+ )
172
179
  ) {
173
180
  this.openSection = section
174
181
  }
@@ -147,11 +147,11 @@ export class ServiceHost {
147
147
  // Call with TJS validation (built into the transpiled code)
148
148
  const result = fn(args)
149
149
 
150
- // Check for monadic error
151
- if (result && result.$error) {
150
+ // Check for monadic error (MonadicError extends Error)
151
+ if (result instanceof Error) {
152
152
  return {
153
153
  success: false,
154
- error: { message: result.message, path: result.path },
154
+ error: { message: result.message, path: (result as any).path },
155
155
  fuel: 1, // minimal fuel for failed validation
156
156
  duration: performance.now() - start,
157
157
  }
@@ -163,8 +163,9 @@ console.log('10 / 0 =', divide(10, 0))
163
163
  const badResult = divide('ten' as any, 2)
164
164
  console.log('divide("ten", 2) =', badResult)
165
165
 
166
- if (badResult && badResult.$error) {
167
- console.log(' ^ This is a validation error, not a crash!')
166
+ if (badResult instanceof Error) {
167
+ console.log(' ^ This is a MonadicError, not a crash!')
168
+ console.log(' message:', badResult.message)
168
169
  }
169
170
  `,
170
171
  },
@@ -468,7 +468,7 @@ export class TSPlayground extends Component<TSPlaygroundParts> {
468
468
 
469
469
  run = async () => {
470
470
  this.clearConsole()
471
- this.transpile()
471
+ await this.transpile()
472
472
 
473
473
  if (!this.lastJsCode) {
474
474
  this.log('Cannot run - transpilation failed')
@@ -548,7 +548,7 @@ export class TSPlayground extends Component<TSPlaygroundParts> {
548
548
  }
549
549
 
550
550
  // Public method to set source code (auto-runs when examples are loaded)
551
- setSource(code: string, exampleName?: string) {
551
+ async setSource(code: string, exampleName?: string) {
552
552
  // Save current edits before switching
553
553
  if (this.currentExampleName) {
554
554
  this.editorCache.set(this.currentExampleName, this.parts.tsEditor.value)
@@ -565,9 +565,9 @@ export class TSPlayground extends Component<TSPlaygroundParts> {
565
565
  // Update revert button visibility
566
566
  this.updateRevertButton()
567
567
 
568
- this.transpile()
568
+ await this.transpile()
569
569
  // Auto-run when source is loaded externally (e.g., from example selection)
570
- this.run()
570
+ await this.run()
571
571
  }
572
572
 
573
573
  // Revert to the original example code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tjs-lang",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Type-safe JavaScript dialect with runtime validation, sandboxed VM execution, and AI agent orchestration. Transpiles TypeScript to validated JS with fuel-metered execution for untrusted code.",
5
5
  "keywords": [
6
6
  "typescript",