tjs-lang 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/demo/docs.json +32 -26
  2. package/demo/src/examples.ts +23 -83
  3. package/demo/src/playground-shared.ts +666 -0
  4. package/demo/src/tjs-playground.ts +65 -550
  5. package/demo/src/ts-examples.ts +5 -4
  6. package/demo/src/ts-playground.ts +50 -414
  7. package/dist/index.js +143 -160
  8. package/dist/index.js.map +12 -12
  9. package/dist/src/lang/emitters/js.d.ts +34 -2
  10. package/dist/src/lang/index.d.ts +1 -1
  11. package/dist/src/lang/types.d.ts +1 -1
  12. package/dist/src/types/Type.d.ts +3 -1
  13. package/dist/tjs-full.js +143 -160
  14. package/dist/tjs-full.js.map +12 -12
  15. package/dist/tjs-transpiler.js +122 -55
  16. package/dist/tjs-transpiler.js.map +9 -8
  17. package/dist/tjs-vm.js +14 -14
  18. package/dist/tjs-vm.js.map +5 -5
  19. package/docs/docs.json +792 -0
  20. package/docs/index.js +2652 -2835
  21. package/docs/index.js.map +11 -10
  22. package/editors/codemirror/ajs-language.ts +27 -1
  23. package/editors/codemirror/autocomplete.test.ts +3 -3
  24. package/package.json +1 -1
  25. package/src/lang/codegen.test.ts +11 -11
  26. package/src/lang/emitters/from-ts.ts +1 -1
  27. package/src/lang/emitters/js.ts +228 -4
  28. package/src/lang/index.ts +0 -3
  29. package/src/lang/inference.ts +40 -8
  30. package/src/lang/lang.test.ts +192 -35
  31. package/src/lang/roundtrip.test.ts +155 -0
  32. package/src/lang/runtime.ts +7 -0
  33. package/src/lang/types.ts +2 -0
  34. package/src/lang/typescript-syntax.test.ts +6 -4
  35. package/src/lang/wasm.test.ts +20 -0
  36. package/src/lang/wasm.ts +143 -0
  37. package/src/types/Type.test.ts +64 -0
  38. package/src/types/Type.ts +22 -1
  39. package/src/use-cases/transpiler-integration.test.ts +10 -10
  40. package/src/vm/atoms/batteries.ts +2 -0
@@ -15,6 +15,7 @@ import {
15
15
  wrap,
16
16
  } from './index'
17
17
  import { preprocess } from './parser'
18
+ import { createRuntime, isMonadicError } from './runtime'
18
19
  import { Schema } from './schema'
19
20
 
20
21
  describe('Transpiler', () => {
@@ -293,7 +294,7 @@ test 'always fails' { throw new Error('intentional') }
293
294
  return { count }
294
295
  }
295
296
  `)
296
- expect(signature.parameters.count.type.kind).toBe('number')
297
+ expect(signature.parameters.count.type.kind).toBe('integer')
297
298
  expect(signature.parameters.count.required).toBe(true)
298
299
  })
299
300
 
@@ -303,7 +304,7 @@ test 'always fails' { throw new Error('intentional') }
303
304
  return { limit }
304
305
  }
305
306
  `)
306
- expect(signature.parameters.limit.type.kind).toBe('number')
307
+ expect(signature.parameters.limit.type.kind).toBe('integer')
307
308
  expect(signature.parameters.limit.required).toBe(false)
308
309
  expect(signature.parameters.limit.default).toBe(10)
309
310
  })
@@ -336,7 +337,7 @@ test 'always fails' { throw new Error('intentional') }
336
337
  `)
337
338
  expect(signature.parameters.user.type.kind).toBe('object')
338
339
  expect(signature.parameters.user.type.shape?.name.kind).toBe('string')
339
- expect(signature.parameters.user.type.shape?.age.kind).toBe('number')
340
+ expect(signature.parameters.user.type.shape?.age.kind).toBe('integer')
340
341
  })
341
342
 
342
343
  it('should handle array types with colon syntax', () => {
@@ -357,13 +358,150 @@ test 'always fails' { throw new Error('intentional') }
357
358
  `)
358
359
  expect(signature.parameters.name.type.kind).toBe('string')
359
360
  expect(signature.parameters.name.required).toBe(true)
360
- expect(signature.parameters.count.type.kind).toBe('number')
361
+ expect(signature.parameters.count.type.kind).toBe('integer')
361
362
  expect(signature.parameters.count.required).toBe(true)
362
- expect(signature.parameters.limit.type.kind).toBe('number')
363
+ expect(signature.parameters.limit.type.kind).toBe('integer')
363
364
  expect(signature.parameters.limit.required).toBe(false)
364
365
  })
365
366
  })
366
367
 
368
+ describe('Numeric type narrowing', () => {
369
+ it('should infer integer from whole number literal', () => {
370
+ const { signature } = transpile(`
371
+ function test(count: 42) { return { count } }
372
+ `)
373
+ expect(signature.parameters.count.type.kind).toBe('integer')
374
+ })
375
+
376
+ it('should infer float (number) from decimal literal', () => {
377
+ const { signature } = transpile(`
378
+ function test(rate: 3.14) { return { rate } }
379
+ `)
380
+ expect(signature.parameters.rate.type.kind).toBe('number')
381
+ })
382
+
383
+ it('should infer float from 0.0', () => {
384
+ const { signature } = transpile(`
385
+ function test(value: 0.0) { return { value } }
386
+ `)
387
+ expect(signature.parameters.value.type.kind).toBe('number')
388
+ })
389
+
390
+ it('should infer non-negative-integer from +N syntax', () => {
391
+ const { signature } = transpile(`
392
+ function test(age: +20) { return { age } }
393
+ `)
394
+ expect(signature.parameters.age.type.kind).toBe('non-negative-integer')
395
+ })
396
+
397
+ it('should infer non-negative-integer from +0', () => {
398
+ const { signature } = transpile(`
399
+ function test(index: +0) { return { index } }
400
+ `)
401
+ expect(signature.parameters.index.type.kind).toBe('non-negative-integer')
402
+ })
403
+
404
+ it('should infer integer from negative literal', () => {
405
+ const { signature } = transpile(`
406
+ function test(offset: -5) { return { offset } }
407
+ `)
408
+ expect(signature.parameters.offset.type.kind).toBe('integer')
409
+ })
410
+
411
+ it('should infer number from negative decimal', () => {
412
+ const { signature } = transpile(`
413
+ function test(temp: -3.5) { return { temp } }
414
+ `)
415
+ expect(signature.parameters.temp.type.kind).toBe('number')
416
+ })
417
+
418
+ it('should generate correct runtime validation for integer', () => {
419
+ const result = tjs(`function test(n: 1) -> 0 { return n }`)
420
+ // Should check Number.isInteger
421
+ expect(result.code).toContain('Number.isInteger')
422
+ })
423
+
424
+ it('should generate correct runtime validation for non-negative-integer', () => {
425
+ const result = tjs(`function test(n: +1) -> 0 { return n }`)
426
+ // Should check Number.isInteger AND >= 0
427
+ expect(result.code).toContain('Number.isInteger')
428
+ expect(result.code).toContain('< 0')
429
+ })
430
+
431
+ it('should validate integer at runtime', () => {
432
+ const result = tjs(`function check(n: 1) -> 0 { return n }`)
433
+ const savedTjs = globalThis.__tjs
434
+ globalThis.__tjs = createRuntime()
435
+ try {
436
+ const fn = new Function(result.code + '\nreturn check')()
437
+ // Valid integer
438
+ expect(fn(42)).toBe(42)
439
+ // Float should fail
440
+ const bad = fn(3.14)
441
+ expect(isMonadicError(bad)).toBe(true)
442
+ } finally {
443
+ globalThis.__tjs = savedTjs
444
+ }
445
+ })
446
+
447
+ it('should validate non-negative-integer at runtime', () => {
448
+ const result = tjs(`function check(n: +1) -> 0 { return n }`)
449
+ const savedTjs = globalThis.__tjs
450
+ globalThis.__tjs = createRuntime()
451
+ try {
452
+ const fn = new Function(result.code + '\nreturn check')()
453
+ // Valid non-negative integer
454
+ expect(fn(0)).toBe(0)
455
+ expect(fn(42)).toBe(42)
456
+ // Negative integer should fail
457
+ const negResult = fn(-1)
458
+ expect(isMonadicError(negResult)).toBe(true)
459
+ // Float should fail
460
+ const floatResult = fn(3.14)
461
+ expect(isMonadicError(floatResult)).toBe(true)
462
+ } finally {
463
+ globalThis.__tjs = savedTjs
464
+ }
465
+ })
466
+
467
+ it('should validate float (number) accepts all numbers at runtime', () => {
468
+ const result = tjs(`function check(n: 0.0) -> 0.0 { return n }`)
469
+ const savedTjs = globalThis.__tjs
470
+ globalThis.__tjs = createRuntime()
471
+ try {
472
+ const fn = new Function(result.code + '\nreturn check')()
473
+ // All numbers should pass for float
474
+ expect(fn(42)).toBe(42)
475
+ expect(fn(3.14)).toBe(3.14)
476
+ expect(fn(-5)).toBe(-5)
477
+ expect(fn(0)).toBe(0)
478
+ } finally {
479
+ globalThis.__tjs = savedTjs
480
+ }
481
+ })
482
+
483
+ it('should handle numeric types in object shapes', () => {
484
+ const { signature } = transpile(`
485
+ function test(point: { x: 0.0, y: 0.0, index: 0 }) { return point }
486
+ `)
487
+ expect(signature.parameters.point.type.shape?.x.kind).toBe('number')
488
+ expect(signature.parameters.point.type.shape?.y.kind).toBe('number')
489
+ expect(signature.parameters.point.type.shape?.index.kind).toBe('integer')
490
+ })
491
+
492
+ it('should handle numeric types in array items', () => {
493
+ const { signature } = transpile(`
494
+ function test(counts: [0]) { return counts }
495
+ `)
496
+ expect(signature.parameters.counts.type.items?.kind).toBe('integer')
497
+
498
+ const { signature: sig2 } = transpile(`
499
+ function test(values: [0.0]) { return values }
500
+ `)
501
+ expect(sig2.parameters.values.type.items?.kind).toBe('number')
502
+ })
503
+ })
504
+
367
505
  describe('Basic transpilation', () => {
368
506
  it('should transpile a simple function', () => {
369
507
  const { ast } = transpile(`
@@ -934,7 +1072,7 @@ describe('TJS Emitter', () => {
934
1072
  `)
935
1073
  expect(result.code).not.toContain('->')
936
1074
  expect(result.types.add.returns).toBeDefined()
937
- expect(result.types.add.returns?.kind).toBe('number')
1075
+ expect(result.types.add.returns?.kind).toBe('integer')
938
1076
  })
939
1077
 
940
1078
  it('should mark parameters as required when using colon syntax', () => {
@@ -968,7 +1106,7 @@ describe('TJS Emitter', () => {
968
1106
  'string'
969
1107
  )
970
1108
  expect(result.types.process.params.user.type.shape?.age.kind).toBe(
971
- 'number'
1109
+ 'integer'
972
1110
  )
973
1111
  })
974
1112
 
@@ -979,7 +1117,7 @@ describe('TJS Emitter', () => {
979
1117
  }
980
1118
  `)
981
1119
  expect(result.types.sum.params.numbers.type.kind).toBe('array')
982
- expect(result.types.sum.params.numbers.type.items?.kind).toBe('number')
1120
+ expect(result.types.sum.params.numbers.type.items?.kind).toBe('integer')
983
1121
  })
984
1122
 
985
1123
  it('should generate __tjs metadata object', () => {
@@ -1052,7 +1190,7 @@ function greet(name: 'world') {
1052
1190
  }
1053
1191
  `
1054
1192
  expect(result.code).toContain('function double')
1055
- expect(result.types.double.params.n.type.kind).toBe('number')
1193
+ expect(result.types.double.params.n.type.kind).toBe('integer')
1056
1194
  })
1057
1195
 
1058
1196
  it('should handle interpolation in tagged template', () => {
@@ -1106,7 +1244,7 @@ function greet(name: 'world') {
1106
1244
  `)
1107
1245
  expect(result.types.test.returns).toBeDefined()
1108
1246
  expect(result.types.test.returns?.kind).toBe('object')
1109
- expect(result.types.test.returns?.shape?.result.kind).toBe('number')
1247
+ expect(result.types.test.returns?.shape?.result.kind).toBe('integer')
1110
1248
  })
1111
1249
  })
1112
1250
 
@@ -1155,9 +1293,9 @@ function greet(name: 'world') {
1155
1293
  return x
1156
1294
  }
1157
1295
  `)
1158
- expect(result.types.compute.params.x.type.kind).toBe('number')
1296
+ expect(result.types.compute.params.x.type.kind).toBe('integer')
1159
1297
  expect(result.types.compute.params.y.type.kind).toBe('string')
1160
- expect(result.types.compute.returns?.kind).toBe('number')
1298
+ expect(result.types.compute.returns?.kind).toBe('integer')
1161
1299
  })
1162
1300
 
1163
1301
  // === NEW TESTS: Multi-function and no-function support ===
@@ -1350,8 +1488,8 @@ function greet(name: 'world') {
1350
1488
  `,
1351
1489
  { runTests: false }
1352
1490
  )
1353
- // Should have inline validation
1354
- expect(result.code).toContain("if (typeof a !== 'number')")
1491
+ // Should have inline validation (integer check for integer examples)
1492
+ expect(result.code).toContain('Number.isInteger(a)')
1355
1493
  expect(result.code).toContain('__tjs.typeError')
1356
1494
  })
1357
1495
 
@@ -1482,7 +1620,7 @@ describe('TypeScript to TJS Transpiler', () => {
1482
1620
  `function sum(nums: number[]): number { return 0 }`,
1483
1621
  { emitTJS: true }
1484
1622
  )
1485
- expect(result.code).toContain('nums: [0]')
1623
+ expect(result.code).toContain('nums: [0.0]')
1486
1624
  })
1487
1625
 
1488
1626
  it('should handle object literal types', () => {
@@ -1490,7 +1628,7 @@ describe('TypeScript to TJS Transpiler', () => {
1490
1628
  `function getUser(): { name: string, age: number } { return { name: '', age: 0 } }`,
1491
1629
  { emitTJS: true }
1492
1630
  )
1493
- expect(result.code).toContain("-! { name: '', age: 0 }") // -! for TS-transpiled
1631
+ expect(result.code).toContain("-! { name: '', age: 0.0 }") // -! for TS-transpiled
1494
1632
  })
1495
1633
 
1496
1634
  it('should handle nullable types', () => {
@@ -2816,7 +2954,10 @@ function process(a: [], b: []) {
2816
2954
  expect(result.source).toContain('globalThis.__tjs_wasm_1')
2817
2955
  })
2818
2956
 
2819
- it('should execute fallback when WASM not available', () => {
2957
+ it('should compile WASM at transpile time and embed in output', async () => {
2958
+ const { installRuntime } = require('./runtime')
2959
+ installRuntime()
2960
+
2820
2961
  const result = tjs(`
2821
2962
  function double(x: 0, y: 0) {
2822
2963
  return wasm {
@@ -2824,12 +2965,27 @@ function double(x: 0, y: 0) {
2824
2965
  }
2825
2966
  }`)
2826
2967
 
2827
- // Execute with fallback (no WASM registered)
2828
- const fn = new Function(`${result.code}; return double(3, 4);`)
2829
- expect(fn()).toBe(15) // 3 * 4 + 3 = 15
2968
+ // WASM should be compiled at transpile time
2969
+ expect(result.wasmCompiled).toBeDefined()
2970
+ expect(result.wasmCompiled?.length).toBe(1)
2971
+ expect(result.wasmCompiled?.[0].success).toBe(true)
2972
+ expect(result.wasmCompiled?.[0].byteLength).toBeGreaterThan(0)
2973
+
2974
+ // Output should contain base64-encoded WASM
2975
+ expect(result.code).toContain('__wasmBlocks')
2976
+ expect(result.code).toContain('b64:')
2977
+
2978
+ // Execute with async function to allow WASM instantiation
2979
+ const fn = new Function(
2980
+ 'return (async () => {' + result.code + '; return double(3, 4); })()'
2981
+ )
2982
+ expect(await fn()).toBe(15) // 3 * 4 + 3 = 15
2830
2983
  })
2831
2984
 
2832
- it('should use WASM when available', () => {
2985
+ it('should use WASM compute function when instantiated', async () => {
2986
+ const { installRuntime } = require('./runtime')
2987
+ installRuntime()
2988
+
2833
2989
  const result = tjs(`
2834
2990
  function compute(a: 0, b: 0) {
2835
2991
  return wasm {
@@ -2837,30 +2993,31 @@ function compute(a: 0, b: 0) {
2837
2993
  }
2838
2994
  }`)
2839
2995
 
2840
- // Register a mock WASM implementation
2841
- const code = `
2842
- globalThis.__tjs_wasm_0 = (a, b) => a * b * 100;
2843
- ${result.code}
2844
- const result = compute(3, 4);
2845
- delete globalThis.__tjs_wasm_0;
2846
- return result;
2847
- `
2848
- const fn = new Function(code)
2849
- // Should use the "WASM" version (our mock)
2850
- expect(fn()).toBe(1200) // 3 * 4 * 100
2996
+ // Execute async to allow WASM instantiation
2997
+ const fn = new Function(
2998
+ 'return (async () => {' + result.code + '; return compute(3, 4); })()'
2999
+ )
3000
+ // Should use the actual WASM version
3001
+ expect(await fn()).toBe(7) // 3 + 4 = 7
2851
3002
  })
2852
3003
 
2853
- it('should use explicit fallback when provided', () => {
3004
+ it('should use explicit fallback when WASM compilation fails', () => {
3005
+ // Test with code that can't compile to WASM (array.map)
2854
3006
  const result = tjs(`
2855
3007
  function transform(arr: []) {
2856
3008
  return wasm {
2857
- return arr
3009
+ return arr.map(x => x * 2)
2858
3010
  } fallback {
2859
3011
  return arr.map(x => x * 2)
2860
3012
  }
2861
3013
  }`)
2862
3014
 
2863
- // Fallback should use the explicit fallback code
3015
+ // WASM compilation should fail (array.map not supported)
3016
+ expect(result.wasmCompiled?.[0].success).toBe(false)
3017
+
3018
+ // But code should still work using fallback
3019
+ const { installRuntime } = require('./runtime')
3020
+ installRuntime()
2864
3021
  const fn = new Function(`${result.code}; return transform([1, 2, 3]);`)
2865
3022
  expect(fn()).toEqual([2, 4, 6])
2866
3023
  })
@@ -0,0 +1,155 @@
1
+ /**
2
+ * TJS Roundtrip Tests
3
+ *
4
+ * These tests verify that TJS code "just works" through the full pipeline:
5
+ * 1. Parse TJS source
6
+ * 2. Transpile to JS
7
+ * 3. Execute the result
8
+ *
9
+ * NOTE: Currently transpiled code requires globalThis.__tjs runtime to be set up.
10
+ * This is a known limitation - see TODO.md for "self-contained transpiler output".
11
+ */
12
+
13
+ import { describe, test, expect, beforeAll } from 'bun:test'
14
+ import { tjs } from './index'
15
+ import { Is, IsNot } from './runtime'
16
+
17
+ // Set up the minimal runtime needed for transpiled code
18
+ beforeAll(() => {
19
+ ;(globalThis as any).__tjs = {
20
+ Is,
21
+ IsNot,
22
+ pushStack: () => {},
23
+ popStack: () => {},
24
+ typeError: (path: string, expected: string, got: any) => {
25
+ const err = new Error(
26
+ `Type error at ${path}: expected ${expected}, got ${typeof got}`
27
+ )
28
+ ;(err as any).$error = true
29
+ return err
30
+ },
31
+ createRuntime: () => (globalThis as any).__tjs,
32
+ }
33
+ })
34
+
35
+ /** Helper to execute transpiled code and capture console output */
36
+ function execCode(code: string): any[] {
37
+ const logs: any[] = []
38
+ const mockConsole = { log: (...args: any[]) => logs.push(args) }
39
+ const fn = new Function('console', code)
40
+ fn(mockConsole)
41
+ return logs
42
+ }
43
+
44
+ describe('TJS roundtrip - code should just work', () => {
45
+ test('basic function with type annotations', () => {
46
+ const source = `
47
+ function add(a: 0, b: 0) -> 0 {
48
+ return a + b
49
+ }
50
+ console.log(add(2, 3))
51
+ `
52
+ const result = tjs(source, { runTests: false })
53
+ expect(result.code).toBeDefined()
54
+
55
+ const logs = execCode(result.code)
56
+ expect(logs[0]).toEqual([5])
57
+ })
58
+
59
+ test('template literals (backticks)', () => {
60
+ const source = `
61
+ function greet(name: 'World') -> '' {
62
+ return \`Hello, \${name}!\`
63
+ }
64
+ console.log(greet('TJS'))
65
+ `
66
+ const result = tjs(source, { runTests: false })
67
+ expect(result.code).toContain('Hello')
68
+
69
+ const logs = execCode(result.code)
70
+ expect(logs[0]).toEqual(['Hello, TJS!'])
71
+ })
72
+
73
+ test('inline tests execute at transpile time', () => {
74
+ const source = `
75
+ function double(x: 0) -> 0 {
76
+ return x * 2
77
+ }
78
+
79
+ test 'double works' {
80
+ expect(double(5)).toBe(10)
81
+ }
82
+ `
83
+ // Tests run during transpilation - check the testResults array
84
+ const result = tjs(source, { runTests: 'report' })
85
+
86
+ // testResults contains test outcomes
87
+ expect(result.testResults).toBeDefined()
88
+ expect(result.testResults!.length).toBeGreaterThan(0)
89
+ const allPassed = result.testResults!.every((r: any) => r.passed)
90
+ expect(allPassed).toBe(true)
91
+ })
92
+
93
+ test('apostrophes in strings', () => {
94
+ const source = `
95
+ const msg1 = "You can't do this in Jest"
96
+ const msg2 = "You'd need to export everything"
97
+ console.log(msg1, msg2)
98
+ `
99
+ const result = tjs(source, { runTests: false })
100
+ expect(result.code).toContain("can't")
101
+ expect(result.code).toContain("You'd")
102
+ })
103
+
104
+ test('escaped newlines in strings', () => {
105
+ const source = `
106
+ console.log('Line 1\\nLine 2')
107
+ `
108
+ const result = tjs(source, { runTests: false })
109
+ const logs = execCode(result.code)
110
+ expect(logs[0][0]).toContain('\n')
111
+ })
112
+
113
+ test('regex patterns with escapes', () => {
114
+ const source = `
115
+ const EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/
116
+ console.log(EMAIL_REGEX.test('test@example.com'))
117
+ `
118
+ const result = tjs(source, { runTests: false })
119
+ const logs = execCode(result.code)
120
+ expect(logs[0]).toEqual([true])
121
+ })
122
+
123
+ test('multiline JSDoc comments with backticks', () => {
124
+ const source = `
125
+ /**
126
+ * Returns \`hello\` to the caller
127
+ */
128
+ function myFunc() {
129
+ return 42
130
+ }
131
+ console.log(myFunc())
132
+ `
133
+ const result = tjs(source, { runTests: false })
134
+ const logs = execCode(result.code)
135
+ expect(logs[0]).toEqual([42])
136
+ })
137
+ })
138
+
139
+ describe('TJS imports', () => {
140
+ test('imports pass through unchanged (current behavior)', () => {
141
+ const source = `
142
+ import { AgentVM, ajs } from 'tjs-lang'
143
+
144
+ async function run() {
145
+ const vm = new AgentVM()
146
+ return vm
147
+ }
148
+ `
149
+ const result = tjs(source, { runTests: false })
150
+
151
+ // Currently imports pass through - this means code won't work
152
+ // standalone in browser without import map resolution
153
+ expect(result.code).toContain("import { AgentVM, ajs } from 'tjs-lang'")
154
+ })
155
+ })
@@ -563,6 +563,13 @@ export function checkType(
563
563
  if (expected === 'number' && actual === 'number') return null
564
564
  if (expected === 'integer' && actual === 'number' && Number.isInteger(value))
565
565
  return null
566
+ if (
567
+ expected === 'non-negative-integer' &&
568
+ actual === 'number' &&
569
+ Number.isInteger(value) &&
570
+ (value as number) >= 0
571
+ )
572
+ return null
566
573
 
567
574
  // Object matching (basic)
568
575
  if (expected === 'object' && actual === 'object') return null
package/src/lang/types.ts CHANGED
@@ -14,6 +14,8 @@ export interface TypeDescriptor {
14
14
  kind:
15
15
  | 'string'
16
16
  | 'number'
17
+ | 'integer'
18
+ | 'non-negative-integer'
17
19
  | 'boolean'
18
20
  | 'null'
19
21
  | 'undefined'
@@ -42,7 +42,7 @@ describe('Basic Types', () => {
42
42
  return x * 2
43
43
  }
44
44
  `)
45
- expect(getFirstFunc(metadata).params.x.type.kind).toBe('number')
45
+ expect(getFirstFunc(metadata).params.x.type.kind).toBe('integer')
46
46
  })
47
47
 
48
48
  test('boolean parameter', () => {
@@ -76,7 +76,7 @@ describe('Basic Types', () => {
76
76
  'string'
77
77
  )
78
78
  expect(getFirstFunc(metadata).params.user.type.shape?.age.kind).toBe(
79
- 'number'
79
+ 'integer'
80
80
  )
81
81
  })
82
82
 
@@ -101,7 +101,9 @@ describe('Basic Types', () => {
101
101
  }
102
102
  `)
103
103
  expect(getFirstFunc(metadata).params.nums.type.kind).toBe('array')
104
- expect(getFirstFunc(metadata).params.nums.type.items?.kind).toBe('number')
104
+ expect(getFirstFunc(metadata).params.nums.type.items?.kind).toBe(
105
+ 'integer'
106
+ )
105
107
  })
106
108
 
107
109
  test('array of objects', () => {
@@ -729,7 +731,7 @@ describe('Literal Types', () => {
729
731
  return n
730
732
  }
731
733
  `)
732
- expect(getFirstFunc(metadata).params.n.type.kind).toBe('number')
734
+ expect(getFirstFunc(metadata).params.n.type.kind).toBe('integer')
733
735
  })
734
736
 
735
737
  test('literal union type alias emits TJS Union', () => {
@@ -45,6 +45,26 @@ describe('WASM Compiler', () => {
45
45
  expect(result.success).toBe(true)
46
46
  })
47
47
 
48
+ it('should include WAT disassembly in result', () => {
49
+ const block: WasmBlock = {
50
+ id: '__tjs_wasm_test_wat',
51
+ body: 'return a + b',
52
+ captures: ['a', 'b'],
53
+ start: 0,
54
+ end: 0,
55
+ }
56
+
57
+ const result = compileToWasm(block)
58
+ expect(result.success).toBe(true)
59
+ expect(result.wat).toBeDefined()
60
+ expect(result.wat).toContain('(func (export "compute")')
61
+ expect(result.wat).toContain('(param $a f64)')
62
+ expect(result.wat).toContain('(param $b f64)')
63
+ expect(result.wat).toContain('f64.add')
64
+ expect(result.wat).toContain('local.get $a')
65
+ expect(result.wat).toContain('local.get $b')
66
+ })
67
+
48
68
  it('should compile multiplication', () => {
49
69
  const block: WasmBlock = {
50
70
  id: '__tjs_wasm_test_3',