tjs-lang 0.6.20 → 0.6.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +29 -24
- package/bin/dev.ts +9 -0
- package/demo/docs.json +49 -1
- package/demo/src/playground-shared.ts +8 -39
- package/demo/src/tjs-playground.ts +8 -0
- package/demo/src/tjs-runtime-iframe.ts +10 -0
- package/demo/src/ts-playground.ts +8 -0
- package/dist/index.js +97 -100
- package/dist/index.js.map +8 -8
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/tjs-full.js +97 -100
- package/dist/tjs-full.js.map +8 -8
- package/dist/tjs-vm.js +3 -3
- package/dist/tjs-vm.js.map +3 -3
- package/package.json +1 -1
- package/src/cli/commands/run.ts +4 -1
- package/src/cli/tjs.ts +1 -1
- package/src/lang/codegen.test.ts +26 -27
- package/src/lang/emitters/js-tests.ts +13 -19
- package/src/lang/emitters/js.ts +42 -7
- package/src/lang/parser-transforms.ts +24 -5
- package/src/lang/runtime.ts +104 -1
package/CLAUDE.md
CHANGED
|
@@ -446,7 +446,7 @@ JavaScript semantics are the default. TJS improvements are opt-in via file-level
|
|
|
446
446
|
```typescript
|
|
447
447
|
TjsStrict // Enables ALL modes below at once
|
|
448
448
|
|
|
449
|
-
TjsEquals // == and != use
|
|
449
|
+
TjsEquals // == and != use honest equality (Eq/NotEq) — no coercion, unwraps boxed primitives
|
|
450
450
|
TjsClass // Classes callable without new, explicit new is banned
|
|
451
451
|
TjsDate // Date is banned, use Timestamp/LegalDate instead
|
|
452
452
|
TjsNoeval // eval() and new Function() are banned
|
|
@@ -458,38 +458,43 @@ Multiple directives can be combined. Place them at the top of the file before an
|
|
|
458
458
|
|
|
459
459
|
#### Equality Operators
|
|
460
460
|
|
|
461
|
-
With `TjsEquals` (or `TjsStrict`), TJS
|
|
461
|
+
With `TjsEquals` (or `TjsStrict`), TJS fixes JavaScript's confusing `==` coercion without the performance cost of deep structural comparison.
|
|
462
462
|
|
|
463
|
-
| Operator | Meaning
|
|
464
|
-
| ----------- |
|
|
465
|
-
| `==` |
|
|
466
|
-
| `!=` |
|
|
467
|
-
| `===` | Identity (same reference)
|
|
468
|
-
| `!==` | Not same reference
|
|
469
|
-
| `a Is b` |
|
|
470
|
-
| `a IsNot b` |
|
|
463
|
+
| Operator | Meaning | Example |
|
|
464
|
+
| ----------- | -------------------------------------------- | ---------------------------------- |
|
|
465
|
+
| `==` | Honest equality (no coercion, unwraps boxed) | `new String('x') == 'x'` is `true` |
|
|
466
|
+
| `!=` | Honest inequality | `0 != ''` is `true` (no coercion) |
|
|
467
|
+
| `===` | Identity (same reference) | `obj === obj` is `true` |
|
|
468
|
+
| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |
|
|
469
|
+
| `a Is b` | Deep structural equality (explicit) | `{a:1} Is {a:1}` is `true` |
|
|
470
|
+
| `a IsNot b` | Deep structural inequality (explicit) | `[1,2] IsNot [2,1]` is `true` |
|
|
471
471
|
|
|
472
472
|
```typescript
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
|
|
473
|
+
// == is honest: no coercion, unwraps boxed primitives
|
|
474
|
+
'foo' == 'foo' // true
|
|
475
|
+
new String('foo') == 'foo' // true (unwraps)
|
|
476
|
+
new Boolean(false) == false // true (unwraps)
|
|
477
|
+
null == undefined // true (nullish equality preserved)
|
|
478
|
+
0 == '' // false (no coercion!)
|
|
479
|
+
false == [] // false (no coercion!)
|
|
480
|
+
|
|
481
|
+
// == is fast: objects/arrays use reference equality (O(1))
|
|
482
|
+
{a:1} == {a:1} // false (different refs)
|
|
483
|
+
[1,2] == [1,2] // false (different refs)
|
|
484
|
+
|
|
485
|
+
// Is/IsNot for explicit deep structural comparison (O(n))
|
|
486
|
+
{a:1} Is {a:1} // true
|
|
487
|
+
[1,2,3] Is [1,2,3] // true
|
|
488
|
+
new Set([1,2]) Is new Set([2,1]) // true (Sets are order-independent)
|
|
485
489
|
```
|
|
486
490
|
|
|
487
491
|
**Implementation Notes:**
|
|
488
492
|
|
|
489
493
|
- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`
|
|
490
|
-
- **TJS (browser/Node)**: Source transformation converts `==` to `
|
|
494
|
+
- **TJS (browser/Node)**: Source transformation converts `==` to `Eq()` and `!=` to `NotEq()` calls
|
|
491
495
|
- **`===` and `!==`**: Always preserved as identity checks, never transformed
|
|
492
|
-
-
|
|
496
|
+
- `Eq()`/`NotEq()` — fast honest equality (unwraps boxed primitives, nullish equality, reference for objects)
|
|
497
|
+
- `Is()`/`IsNot()` — deep structural comparison (arrays, objects, Sets, Maps, Dates, RegExps)
|
|
493
498
|
|
|
494
499
|
**Custom Equality Protocol:**
|
|
495
500
|
|
package/bin/dev.ts
CHANGED
|
@@ -68,6 +68,15 @@ async function buildDemo() {
|
|
|
68
68
|
return
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// Build standalone TJS runtime for iframe injection
|
|
72
|
+
await Bun.build({
|
|
73
|
+
entrypoints: ['./demo/src/tjs-runtime-iframe.ts'],
|
|
74
|
+
outdir: './.demo',
|
|
75
|
+
minify: false,
|
|
76
|
+
target: 'browser',
|
|
77
|
+
naming: 'tjs-runtime.js',
|
|
78
|
+
})
|
|
79
|
+
|
|
71
80
|
// Copy static files
|
|
72
81
|
await $`cp demo/index.html demo/static/favicon.svg demo/static/photo-*.jpg tjs-lang.svg .demo/`
|
|
73
82
|
await $`cp -r demo/static/texts .demo/`
|
package/demo/docs.json
CHANGED
|
@@ -107,6 +107,18 @@
|
|
|
107
107
|
"language": "tjs",
|
|
108
108
|
"description": "Working with typed arrays"
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
"title": "Honest Equality",
|
|
112
|
+
"filename": "honest-equality.md",
|
|
113
|
+
"path": "guides/examples/tjs/honest-equality.md",
|
|
114
|
+
"section": "tjs",
|
|
115
|
+
"type": "example",
|
|
116
|
+
"group": "basics",
|
|
117
|
+
"order": 15,
|
|
118
|
+
"code": "TjsEquals\n\n/*#\n## The Problem with JavaScript ==\n\nJavaScript's `==` does type coercion, producing surprises:\n\n 0 == '' // true in JS (!)\n false == [] // true in JS (!)\n '' == false // true in JS (!)\n null == 0 // false in JS (but null == undefined is true)\n\nJavaScript's `===` fixes coercion but can't compare values:\n\n new String('hi') === 'hi' // false in JS (different types)\n new Boolean(false) === false // false in JS (object vs primitive)\n\n## TJS Equality (TjsEquals)\n\n`==` becomes **honest equality**: no coercion, but unwraps\nboxed primitives. Fast O(1) — no deep comparison.\n\n`Is` / `IsNot` are **structural equality**: deep comparison\nfor when you explicitly need it. O(n) cost is visible.\n*/\n\n// --- Honest equality (==) fixes coercion ---\nconsole.log('== fixes JS coercion:')\nconsole.log(' 0 == \"\":', 0 == '') // false (JS: true)\nconsole.log(' false == []:', false == []) // false (JS: true)\nconsole.log(' false == \"\":', false == '') // false (JS: true)\nconsole.log(' 1 == \"1\":', 1 == '1') // false (JS: true)\n\n// --- Boxed primitives unwrap ---\nconsole.log('')\nconsole.log('== unwraps boxed primitives:')\nconsole.log(' new String(\"hi\") == \"hi\":', new String('hi') == 'hi') // true\nconsole.log(' new Boolean(false) == false:', new Boolean(false) == false) // true\nconsole.log(' new Number(42) == 42:', new Number(42) == 42) // true\n\n// --- Nullish equality preserved ---\nconsole.log('')\nconsole.log('Nullish equality (useful pattern preserved):')\nconsole.log(' null == undefined:', null == undefined) // true\nconsole.log(' null == 0:', null == 0) // false\nconsole.log(' null == \"\":', null == '') // false\n\n// --- Objects/arrays: reference equality (fast, O(1)) ---\nconsole.log('')\nconsole.log('== on objects/arrays is reference equality (fast):')\nconst obj = {x: 1}\nconsole.log(' obj == obj:', obj == obj) // true (same ref)\nconsole.log(' {x:1} == {x:1}:', {x: 1} == {x: 1}) // false (different refs)\nconsole.log(' [1,2] == [1,2]:', [1, 2] == [1, 2]) // false (different refs)\n\n// --- Is/IsNot: explicit deep structural comparison ---\nconsole.log('')\nconsole.log('Is/IsNot for deep structural comparison (explicit):')\nconsole.log(' {x:1} Is {x:1}:', Is({x: 1}, {x: 1})) // true\nconsole.log(' [1,2,3] Is [1,2,3]:', Is([1,2,3], [1,2,3])) // true\nconsole.log(' [1,2] Is [2,1]:', Is([1,2], [2,1])) // false (order matters)\n\n// Sets compare by membership, not order\nconsole.log(' Set([1,2]) Is Set([2,1]):', Is(new Set([1,2]), new Set([2,1]))) // true\n\n// --- typeof null fixed ---\nconsole.log('')\nconsole.log('typeof null fixed:')\nconsole.log(' typeof null:', typeof null) // 'null' (JS: 'object')\nconsole.log(' typeof undefined:', typeof undefined) // 'undefined'\nconsole.log(' typeof 42:', typeof 42) // 'number' (unchanged)\n\n// --- === unchanged: identity comparison ---\nconsole.log('')\nconsole.log('=== is unchanged (identity):')\nconsole.log(' obj === obj:', obj === obj) // true\nconsole.log(' {x:1} === {x:1}:', {x: 1} === {x: 1}) // false\n\ntest 'typeof null is null, not object' {\n expect(TypeOf(null)).toBe('null')\n expect(TypeOf(undefined)).toBe('undefined')\n expect(TypeOf(42)).toBe('number')\n expect(TypeOf('hi')).toBe('string')\n expect(TypeOf(true)).toBe('boolean')\n expect(TypeOf({})).toBe('object')\n}\n\ntest 'Eq fixes coercion' {\n expect(Eq(0, '')).toBe(false)\n expect(Eq(false, [])).toBe(false)\n expect(Eq('', false)).toBe(false)\n}\n\ntest 'Eq unwraps boxed primitives' {\n expect(Eq(new String('hi'), 'hi')).toBe(true)\n expect(Eq(new Boolean(false), false)).toBe(true)\n expect(Eq(new Number(42), 42)).toBe(true)\n}\n\ntest 'Eq preserves nullish equality' {\n expect(Eq(null, undefined)).toBe(true)\n expect(Eq(null, 0)).toBe(false)\n}\n\ntest 'Is does deep structural comparison' {\n expect(Is({a: 1, b: 2}, {a: 1, b: 2})).toBe(true)\n expect(Is([1, 2, 3], [1, 2, 3])).toBe(true)\n expect(Is([1, 2], [2, 1])).toBe(false)\n}",
|
|
119
|
+
"language": "tjs",
|
|
120
|
+
"description": "JavaScript `==` is broken. TJS fixes it without breaking anything."
|
|
121
|
+
},
|
|
110
122
|
{
|
|
111
123
|
"title": "Polymorphic Functions",
|
|
112
124
|
"filename": "polymorphic-functions.md",
|
|
@@ -131,6 +143,30 @@
|
|
|
131
143
|
"language": "tjs",
|
|
132
144
|
"description": "Classes with multiple constructor signatures, dispatched automatically"
|
|
133
145
|
},
|
|
146
|
+
{
|
|
147
|
+
"title": "Safety & Validation",
|
|
148
|
+
"filename": "safety-validation.md",
|
|
149
|
+
"path": "guides/examples/tjs/safety-validation.md",
|
|
150
|
+
"section": "tjs",
|
|
151
|
+
"type": "example",
|
|
152
|
+
"group": "basics",
|
|
153
|
+
"order": 16,
|
|
154
|
+
"code": "safety inputs\n\n/*#\n## The Problem\n\nJavaScript functions silently accept wrong types:\n\n function greet(name) { return `Hello, ${name}!` }\n greet(42) // \"Hello, 42!\" — no error, just wrong\n\nTypeScript catches this at compile time but the checks\nvanish at runtime. Production gets no protection.\n\n## TJS Safety Levels\n\nThe `safety` directive sets the default for the whole file:\n\n| Directive | Validates | Use case |\n|-----------|-----------|----------|\n| `safety none` | Nothing | Metadata only, zero overhead |\n| `safety inputs` | Function inputs | Default — catches bad callers |\n| `safety all` | Inputs + outputs | Debug mode, catches internal bugs |\n\nPer-function overrides:\n- `!` = unsafe (skip validation): `function fast(! x: 0) { }`\n- `?` = safe (force validation): `function careful(? x: 0) { }`\n*/\n\n// --- Input validation catches bad callers ---\n\nfunction sayHello(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n\ntest 'valid input works normally' {\n expect(sayHello('Alice')).toBe('Hello, Alice!')\n}\n\ntest 'wrong type returns an error, not garbage' {\n const result = sayHello(42)\n // Not \"Hello, 42!\" — an actual error value\n expect(result instanceof Error).toBe(true)\n}\n\ntest 'null returns an error' {\n const result = sayHello(null)\n expect(result instanceof Error).toBe(true)\n}\n\n// --- Numeric narrowing catches subtle bugs ---\n\nfunction setAge(age: +0) -> +0 {\n return age\n}\n\ntest 'positive integer accepted' {\n expect(setAge(25)).toBe(25)\n}\n\ntest 'negative rejected' {\n expect(setAge(-1) instanceof Error).toBe(true)\n}\n\ntest 'float rejected (age must be integer)' {\n expect(setAge(25.5) instanceof Error).toBe(true)\n}\n\n// --- Unsafe functions skip validation (fast path) ---\n\nfunction fastAdd(! a: 0, b: 0) -> 0 {\n return a + b\n}\n\ntest 'unsafe function skips checks (trusted callers only)' {\n expect(fastAdd(3, 4)).toBe(7)\n // No type error even with wrong type — validation skipped\n expect(fastAdd('a', 'b')).toBe('ab')\n}\n\n// --- Errors propagate through function chains ---\n\nfunction cleanName(name: '') -> '' {\n return name.trim()\n}\n\nfunction upperCase(name: '') -> '' {\n return name.toUpperCase()\n}\n\ntest 'errors propagate automatically' {\n // cleanName(42) returns error -> upperCase receives error -> returns error\n const result = upperCase(cleanName(42))\n expect(result instanceof Error).toBe(true)\n}\n\ntest 'valid input flows through the chain' {\n expect(upperCase(cleanName('alice'))).toBe('ALICE')\n}\n\n// --- Try without catch: convert exceptions to errors ---\n\nfunction parseJSON(s: '{}') -! {} {\n try {\n return JSON.parse(s)\n }\n}\n\ntest 'valid JSON parses normally' {\n const result = parseJSON('{\"ok\":true}')\n expect(result.ok).toBe(true)\n}\n\ntest 'invalid JSON returns error instead of throwing' {\n const result = parseJSON('not json')\n expect(result instanceof Error).toBe(true)\n // No try/catch needed by the caller!\n}\n\nconsole.log('Valid:', sayHello('World'))\nconsole.log('Invalid:', sayHello(42))\nconsole.log('Parse ok:', parseJSON('{\"x\":1}'))\nconsole.log('Parse bad:', parseJSON('nope'))",
|
|
155
|
+
"language": "tjs",
|
|
156
|
+
"description": "Three levels of runtime validation. Choose per-file or per-function."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"title": "Better Classes",
|
|
160
|
+
"filename": "better-classes.md",
|
|
161
|
+
"path": "guides/examples/tjs/better-classes.md",
|
|
162
|
+
"section": "tjs",
|
|
163
|
+
"type": "example",
|
|
164
|
+
"group": "basics",
|
|
165
|
+
"order": 17,
|
|
166
|
+
"code": "TjsClass\n\n/*#\n## The Problem\n\nJavaScript requires `new` for classes, but it's easy to forget:\n\n class Point { constructor(x, y) { this.x = x; this.y = y } }\n const p = Point(1, 2) // undefined! No error, just broken.\n\nAnd you can't have multiple constructors:\n\n // Want Point(1, 2) AND Point({x: 1, y: 2})? Too bad.\n\n## TJS Classes (TjsClass)\n\nWith `TjsClass`, classes are callable without `new`.\nMultiple `constructor()` declarations dispatch by signature.\n*/\n\n// --- Classes are callable without new ---\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 toString() {\n return `(${this.x}, ${this.y})`\n }\n}\n\ntest 'callable without new' {\n const p = Point(3, 4)\n expect(p instanceof Point).toBe(true)\n expect(p.x).toBe(3)\n expect(p.y).toBe(4)\n}\n\ntest 'methods work normally' {\n expect(Point(3, 4).magnitude()).toBe(5)\n}\n\n// --- Multiple constructors ---\n\nclass Color {\n constructor(r: +0, g: +0, b: +0) {\n this.r = r\n this.g = g\n this.b = b\n }\n\n constructor(hex: '#000000') {\n const n = parseInt(hex.slice(1), 16)\n this.r = (n >> 16) & 255\n this.g = (n >> 8) & 255\n this.b = n & 255\n }\n\n toString() {\n return `rgb(${this.r}, ${this.g}, ${this.b})`\n }\n}\n\ntest 'construct from RGB' {\n const c = Color(255, 128, 0)\n expect(c.r).toBe(255)\n expect(c.g).toBe(128)\n expect(c instanceof Color).toBe(true)\n}\n\ntest 'construct from hex' {\n const c = Color('#ff8000')\n expect(c.r).toBe(255)\n expect(c.g).toBe(128)\n expect(c.b).toBe(0)\n expect(c instanceof Color).toBe(true)\n}\n\ntest 'both forms produce same result' {\n const a = Color(255, 0, 0)\n const b = Color('#ff0000')\n expect(a.r).toBe(b.r)\n expect(a.g).toBe(b.g)\n expect(a.b).toBe(b.b)\n}\n\nconsole.log('Point:', Point(3, 4).toString(), ' magnitude:', Point(3, 4).magnitude())\nconsole.log('RGB:', Color(255, 128, 0).toString())\nconsole.log('Hex:', Color('#ff8000').toString())",
|
|
167
|
+
"language": "tjs",
|
|
168
|
+
"description": "Classes you can call like functions. Multiple constructors."
|
|
169
|
+
},
|
|
134
170
|
{
|
|
135
171
|
"title": "Local Class Extensions",
|
|
136
172
|
"filename": "local-extensions.md",
|
|
@@ -309,6 +345,18 @@
|
|
|
309
345
|
"language": "tjs",
|
|
310
346
|
"description": "Uses lodash-es for utility functions via ESM import"
|
|
311
347
|
},
|
|
348
|
+
{
|
|
349
|
+
"title": "Type Declarations",
|
|
350
|
+
"filename": "type-declarations.md",
|
|
351
|
+
"path": "guides/examples/tjs/type-declarations.md",
|
|
352
|
+
"section": "tjs",
|
|
353
|
+
"type": "example",
|
|
354
|
+
"group": "patterns",
|
|
355
|
+
"order": 20,
|
|
356
|
+
"code": "/*#\n## Runtime Types\n\nTypeScript types vanish at runtime. TJS types survive as\ncallable validators with `.check()`, `.description`, and a\ndefault example value.\n\n| Declaration | Creates | Use case |\n|-------------|---------|----------|\n| `Type` | Value shape validator | Simple types with examples |\n| `Generic` | Parameterized validator factory | Container types |\n| `FunctionPredicate` | Function signature type | Callback contracts |\n| `Enum` | String/number enum | Finite value sets |\n| `Union` | Literal union | Status codes, directions |\n*/\n\n// --- Type: runtime type from example value ---\n\nType Email {\n description: 'a valid email address'\n example: 'user@example.com'\n predicate(x) { return typeof x === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(x) }\n}\n\ntest 'Email validates at runtime' {\n expect(Email.check('alice@test.com')).toBe(true)\n expect(Email.check('not-an-email')).toBe(false)\n expect(Email.check(42)).toBe(false)\n}\n\n// --- Generic: parameterized type factory ---\n\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\ntest 'Generic Box is a factory' {\n const AnyBox = Box(() => true)\n expect(AnyBox.check({ value: 'hello' })).toBe(true)\n expect(AnyBox.check(null)).toBe(false)\n expect(AnyBox.check('not an object')).toBe(false)\n}\n\n// --- FunctionPredicate: function signature type ---\n\nFunctionPredicate Formatter {\n params: { input: '' }\n returns: ''\n}\n\ntest 'FunctionPredicate validates callables' {\n expect(Formatter.check(x => x.toUpperCase())).toBe(true)\n expect(Formatter.check('not a function')).toBe(false)\n expect(Formatter.check(42)).toBe(false)\n}\n\n// --- Generic FunctionPredicate ---\n\nFunctionPredicate Mapper<T, U> {\n params: { input: T }\n returns: U\n}\n\ntest 'Generic FunctionPredicate is a factory' {\n const StringToNum = Mapper('', 0)\n expect(StringToNum.check(x => x.length)).toBe(true)\n expect(StringToNum.check(42)).toBe(false)\n}\n\n// --- Enum: named value sets ---\n\nEnum Status 'request status' {\n Pending = 'pending'\n Active = 'active'\n Done = 'done'\n}\n\ntest 'Enum validates membership' {\n expect(Status.check('pending')).toBe(true)\n expect(Status.check('active')).toBe(true)\n expect(Status.check('invalid')).toBe(false)\n}\n\n// --- Union: literal union types ---\n\nUnion Direction 'cardinal' 'north' | 'south' | 'east' | 'west'\n\ntest 'Union validates literals' {\n expect(Direction.check('north')).toBe(true)\n expect(Direction.check('northeast')).toBe(false)\n}\n\nconsole.log('Email valid:', Email.check('test@test.com'))\nconsole.log('Email invalid:', Email.check('nope'))\nconsole.log('Box valid:', Box(() => true).check({ value: 42 }))\nconsole.log('Status:', Status.check('pending'), Status.check('bogus'))\nconsole.log('Direction:', Direction.check('north'), Direction.check('up'))",
|
|
357
|
+
"language": "tjs",
|
|
358
|
+
"description": "Named types with runtime validation. Type, Generic, FunctionPredicate, Enum, Union."
|
|
359
|
+
},
|
|
312
360
|
{
|
|
313
361
|
"title": "React Todo (Comparison)",
|
|
314
362
|
"filename": "react-todo-comparison.md",
|
|
@@ -655,7 +703,7 @@
|
|
|
655
703
|
"title": "CLAUDE.md",
|
|
656
704
|
"filename": "CLAUDE.md",
|
|
657
705
|
"path": "CLAUDE.md",
|
|
658
|
-
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n## Common Commands\n\n```bash\n# Development\nbun run format # ESLint fix + Prettier\nbun run test:fast # Core tests (skips LLM & benchmarks)\nbun run make # Full build (clean, format, grammars, tsc, esbuild)\nbun run dev # Development server with file watcher\nbun run start # Build demo + start dev server\nbun run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nbun run typecheck # tsc --noEmit (type check without emitting)\nbun run test:llm # LM Studio integration tests\nbun run bench # Vector search benchmarks\nbun run docs # Generate documentation\n\n# Build standalone CLI binaries\nbun run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nbun run deploy # Build demo + deploy functions + hosting\nbun run deploy:hosting # Hosting only (serves from .demo/)\nbun run functions:deploy # Cloud functions only\nbun run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/parser-transforms.ts` - Type, Generic, and FunctionPredicate block/function form transforms\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/emitters/dts.ts` - .d.ts declaration file generator from TJS transpilation results\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n- Constrained generics (`<T extends { id: number }>`) use the constraint as the example value instead of `any`\n- Generic defaults (`<T = string>`) use the default as the example value\n- Unconstrained generics (`<T>`) degrade to `any` — there's no information to use\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `bun run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct. Only `class` declarations in `.tjs` files with `TjsClass` are wrapped — built-in constructors (`Boolean`, `Number`, `String`, etc.) and old-style `function` + `prototype` constructors are never touched because they may have intentional dual behavior (e.g., `Boolean(0)` returns `false` but `new Boolean(0)` returns a truthy wrapper object).\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n\n// Rest parameters — array example is the type (annotation stripped in JS output)\nfunction sum(...nums: [0]) { } // nums: array of integers\nfunction log(...args: ['', 0, true]) { } // args: array<string | integer | boolean>\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n\n// Generic with declaration block (for .d.ts emission)\n// The declaration block contains TypeScript syntax emitted verbatim into .d.ts\n// It is stripped from runtime JS output\nGeneric BoxedProxy<T> {\n predicate(x, T) { return typeof x === 'object' && T(x.value) }\n declaration {\n value: T\n path: string\n observe(cb: (path: string) => void): void\n }\n}\n```\n\n#### FunctionPredicate Declarations\n\nFirst-class function types, completing the Type/Generic/FunctionPredicate triad:\n\n```typescript\n// Block form — declare a function type shape\nFunctionPredicate Callback {\n params: { x: 0, y: 0 }\n returns: ''\n}\n\n// Function form — extract signature from existing function\nFunctionPredicate Handler(existingFn, 'description')\n\n// Return contracts:\n// -> returns (standard)\n// -! assertReturns (throws on mismatch)\n// -? checkedReturns (wraps in MonadicError)\n```\n\nRuntime creates a `RuntimeType` that checks `typeof === 'function'`. The spec includes params, returns, and returnContract. In `fromTS`, TS function type aliases (`type Cb = (x: number) => void`) emit FunctionPredicate declarations automatically.\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use structural equality (Is/IsNot)\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS redefines equality to be structural, fixing JavaScript's confusing `==` vs `===` semantics.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\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\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\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: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\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), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `bun run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: `tjs()` Returns an Object, Not a String\n\n`tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.\n\n### Known Gotcha: Running Emitted TJS Code in Tests\n\nTranspiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:\n\n```typescript\nimport { createRuntime } from '../lang/runtime'\n\nconst saved = globalThis.__tjs\nglobalThis.__tjs = createRuntime()\ntry {\n const fn = new Function(result.code + '\\nreturn fnName')()\n // ... test fn\n} finally {\n globalThis.__tjs = saved\n}\n```\n\nA `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
|
|
706
|
+
"text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n## Common Commands\n\n```bash\n# Development\nbun run format # ESLint fix + Prettier\nbun run test:fast # Core tests (skips LLM & benchmarks)\nbun run make # Full build (clean, format, grammars, tsc, esbuild)\nbun run dev # Development server with file watcher\nbun run start # Build demo + start dev server\nbun run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nbun run typecheck # tsc --noEmit (type check without emitting)\nbun run test:llm # LM Studio integration tests\nbun run bench # Vector search benchmarks\nbun run docs # Generate documentation\n\n# Build standalone CLI binaries\nbun run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nbun run deploy # Build demo + deploy functions + hosting\nbun run deploy:hosting # Hosting only (serves from .demo/)\nbun run functions:deploy # Cloud functions only\nbun run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/parser-transforms.ts` - Type, Generic, and FunctionPredicate block/function form transforms\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/emitters/dts.ts` - .d.ts declaration file generator from TJS transpilation results\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n- Constrained generics (`<T extends { id: number }>`) use the constraint as the example value instead of `any`\n- Generic defaults (`<T = string>`) use the default as the example value\n- Unconstrained generics (`<T>`) degrade to `any` — there's no information to use\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `bun run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct. Only `class` declarations in `.tjs` files with `TjsClass` are wrapped — built-in constructors (`Boolean`, `Number`, `String`, etc.) and old-style `function` + `prototype` constructors are never touched because they may have intentional dual behavior (e.g., `Boolean(0)` returns `false` but `new Boolean(0)` returns a truthy wrapper object).\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n\n// Rest parameters — array example is the type (annotation stripped in JS output)\nfunction sum(...nums: [0]) { } // nums: array of integers\nfunction log(...args: ['', 0, true]) { } // args: array<string | integer | boolean>\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n\n// Generic with declaration block (for .d.ts emission)\n// The declaration block contains TypeScript syntax emitted verbatim into .d.ts\n// It is stripped from runtime JS output\nGeneric BoxedProxy<T> {\n predicate(x, T) { return typeof x === 'object' && T(x.value) }\n declaration {\n value: T\n path: string\n observe(cb: (path: string) => void): void\n }\n}\n```\n\n#### FunctionPredicate Declarations\n\nFirst-class function types, completing the Type/Generic/FunctionPredicate triad:\n\n```typescript\n// Block form — declare a function type shape\nFunctionPredicate Callback {\n params: { x: 0, y: 0 }\n returns: ''\n}\n\n// Function form — extract signature from existing function\nFunctionPredicate Handler(existingFn, 'description')\n\n// Return contracts:\n// -> returns (standard)\n// -! assertReturns (throws on mismatch)\n// -? checkedReturns (wraps in MonadicError)\n```\n\nRuntime creates a `RuntimeType` that checks `typeof === 'function'`. The spec includes params, returns, and returnContract. In `fromTS`, TS function type aliases (`type Cb = (x: number) => void`) emit FunctionPredicate declarations automatically.\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use honest equality (Eq/NotEq) — no coercion, unwraps boxed primitives\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS fixes JavaScript's confusing `==` coercion without the performance cost of deep structural comparison.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------------------- | ---------------------------------- |\n| `==` | Honest equality (no coercion, unwraps boxed) | `new String('x') == 'x'` is `true` |\n| `!=` | Honest inequality | `0 != ''` is `true` (no coercion) |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Deep structural equality (explicit) | `{a:1} Is {a:1}` is `true` |\n| `a IsNot b` | Deep structural inequality (explicit) | `[1,2] IsNot [2,1]` is `true` |\n\n```typescript\n// == is honest: no coercion, unwraps boxed primitives\n'foo' == 'foo' // true\nnew String('foo') == 'foo' // true (unwraps)\nnew Boolean(false) == false // true (unwraps)\nnull == undefined // true (nullish equality preserved)\n0 == '' // false (no coercion!)\nfalse == [] // false (no coercion!)\n\n// == is fast: objects/arrays use reference equality (O(1))\n{a:1} == {a:1} // false (different refs)\n[1,2] == [1,2] // false (different refs)\n\n// Is/IsNot for explicit deep structural comparison (O(n))\n{a:1} Is {a:1} // true\n[1,2,3] Is [1,2,3] // true\nnew Set([1,2]) Is new Set([2,1]) // true (Sets are order-independent)\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Eq()` and `!=` to `NotEq()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- `Eq()`/`NotEq()` — fast honest equality (unwraps boxed primitives, nullish equality, reference for objects)\n- `Is()`/`IsNot()` — deep structural comparison (arrays, objects, Sets, Maps, Dates, RegExps)\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\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\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\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: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\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), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `bun run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: `tjs()` Returns an Object, Not a String\n\n`tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.\n\n### Known Gotcha: Running Emitted TJS Code in Tests\n\nTranspiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:\n\n```typescript\nimport { createRuntime } from '../lang/runtime'\n\nconst saved = globalThis.__tjs\nglobalThis.__tjs = createRuntime()\ntry {\n const fn = new Function(result.code + '\\nreturn fnName')()\n // ... test fn\n} finally {\n globalThis.__tjs = saved\n}\n```\n\nA `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
|
|
659
707
|
},
|
|
660
708
|
{
|
|
661
709
|
"title": "Context: Working with tosijs-schema",
|
|
@@ -8,43 +8,10 @@
|
|
|
8
8
|
import type { CodeMirror } from '../../editors/codemirror/component'
|
|
9
9
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
|
-
// TJS Runtime
|
|
11
|
+
// TJS Runtime for iframe — loaded from a built file, not a hand-maintained stub.
|
|
12
|
+
// The URL is resolved at runtime from the parent origin so it works in blob iframes.
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
|
|
14
|
-
/** The globalThis.__tjs runtime stub for iframe execution. Must stay in sync with src/lang/runtime.ts */
|
|
15
|
-
export const TJS_RUNTIME_STUB = `
|
|
16
|
-
globalThis.__tjs = {
|
|
17
|
-
version: '0.0.0',
|
|
18
|
-
pushStack: () => {},
|
|
19
|
-
popStack: () => {},
|
|
20
|
-
getStack: () => [],
|
|
21
|
-
typeError: (path, expected, value) => {
|
|
22
|
-
const actual = value === null ? 'null' : typeof value;
|
|
23
|
-
const err = new Error("Expected " + expected + " for '" + path + "', got " + actual);
|
|
24
|
-
err.name = 'MonadicError';
|
|
25
|
-
err.path = path;
|
|
26
|
-
err.expected = expected;
|
|
27
|
-
err.actual = actual;
|
|
28
|
-
return err;
|
|
29
|
-
},
|
|
30
|
-
createRuntime: function() { return this; },
|
|
31
|
-
Is: (a, b) => {
|
|
32
|
-
if (a === b) return true;
|
|
33
|
-
if (a === null || b === null) return a === b;
|
|
34
|
-
if (typeof a !== typeof b) return false;
|
|
35
|
-
if (typeof a !== 'object') return false;
|
|
36
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
37
|
-
if (a.length !== b.length) return false;
|
|
38
|
-
return a.every((v, i) => globalThis.__tjs.Is(v, b[i]));
|
|
39
|
-
}
|
|
40
|
-
const keysA = Object.keys(a);
|
|
41
|
-
const keysB = Object.keys(b);
|
|
42
|
-
if (keysA.length !== keysB.length) return false;
|
|
43
|
-
return keysA.every(k => globalThis.__tjs.Is(a[k], b[k]));
|
|
44
|
-
},
|
|
45
|
-
IsNot: (a, b) => !globalThis.__tjs.Is(a, b),
|
|
46
|
-
};`
|
|
47
|
-
|
|
48
15
|
// ---------------------------------------------------------------------------
|
|
49
16
|
// Console capture script (injected into iframe <script>)
|
|
50
17
|
// ---------------------------------------------------------------------------
|
|
@@ -89,6 +56,9 @@ export interface IframeDocOptions {
|
|
|
89
56
|
* execution timing, and error boundary.
|
|
90
57
|
*/
|
|
91
58
|
export function buildIframeDoc(options: IframeDocOptions): string {
|
|
59
|
+
// Build absolute URL for the runtime script (blob iframes have no origin)
|
|
60
|
+
const runtimeScriptTag = `<script src="${location.origin}/tjs-runtime.js"></script>`
|
|
61
|
+
|
|
92
62
|
const {
|
|
93
63
|
cssContent,
|
|
94
64
|
htmlContent,
|
|
@@ -157,9 +127,8 @@ export function buildIframeDoc(options: IframeDocOptions): string {
|
|
|
157
127
|
</head>
|
|
158
128
|
<body>
|
|
159
129
|
${htmlContent}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</script>
|
|
130
|
+
${runtimeScriptTag}
|
|
131
|
+
<script>${parentBindingsScript}</script>
|
|
163
132
|
<script type="module">
|
|
164
133
|
${importStatements.join('\n ')}
|
|
165
134
|
${CONSOLE_CAPTURE_SCRIPT}
|
|
@@ -186,8 +155,8 @@ ${CONSOLE_CAPTURE_SCRIPT}
|
|
|
186
155
|
</head>
|
|
187
156
|
<body>
|
|
188
157
|
${htmlContent}
|
|
158
|
+
${runtimeScriptTag}
|
|
189
159
|
<script type="module">${parentBindingsScript}
|
|
190
|
-
${TJS_RUNTIME_STUB}
|
|
191
160
|
${CONSOLE_CAPTURE_SCRIPT}
|
|
192
161
|
|
|
193
162
|
const __childrenBefore = document.body.childNodes.length;
|
|
@@ -654,6 +654,14 @@ export class TJSPlayground extends Component<TJSPlaygroundParts> {
|
|
|
654
654
|
this.transpile()
|
|
655
655
|
}, 0)
|
|
656
656
|
|
|
657
|
+
// Cmd/Ctrl+R runs the current example instead of refreshing
|
|
658
|
+
this.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
659
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'r') {
|
|
660
|
+
e.preventDefault()
|
|
661
|
+
this.run()
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
|
|
657
665
|
// Listen for changes (debounced to avoid excessive transpilation)
|
|
658
666
|
let debounceTimer: ReturnType<typeof setTimeout>
|
|
659
667
|
this.parts.tjsEditor.addEventListener('change', () => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TJS Runtime for iframe execution
|
|
3
|
+
*
|
|
4
|
+
* Built as a standalone script and loaded via <script src="tjs-runtime.js">
|
|
5
|
+
* in playground iframes. This ensures the iframe gets the real runtime,
|
|
6
|
+
* not a hand-maintained stub that drifts out of sync.
|
|
7
|
+
*/
|
|
8
|
+
import { installRuntime } from '../../src/lang/runtime'
|
|
9
|
+
|
|
10
|
+
installRuntime()
|
|
@@ -487,6 +487,14 @@ export class TSPlayground extends Component<TSPlaygroundParts> {
|
|
|
487
487
|
this.transpile()
|
|
488
488
|
}, 0)
|
|
489
489
|
|
|
490
|
+
// Cmd/Ctrl+R runs the current example instead of refreshing
|
|
491
|
+
this.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
492
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'r') {
|
|
493
|
+
e.preventDefault()
|
|
494
|
+
this.run()
|
|
495
|
+
}
|
|
496
|
+
})
|
|
497
|
+
|
|
490
498
|
// Listen for changes (debounced)
|
|
491
499
|
let debounceTimer: ReturnType<typeof setTimeout>
|
|
492
500
|
this.parts.tsEditor.addEventListener('change', () => {
|