tjs-lang 0.6.44 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CLAUDE.md +85 -422
  2. package/README.md +15 -82
  3. package/bin/benchmarks.ts +7 -7
  4. package/bin/dev.ts +2 -1
  5. package/demo/autocomplete.test.ts +1 -1
  6. package/demo/docs.json +744 -48
  7. package/demo/src/demo-nav.ts +5 -5
  8. package/demo/src/index.ts +28 -36
  9. package/demo/src/module-sw.ts +1 -1
  10. package/demo/src/playground-shared.ts +17 -17
  11. package/demo/src/playground.ts +13 -1
  12. package/demo/src/style.ts +4 -1
  13. package/demo/src/tjs-playground.ts +5 -5
  14. package/demo/src/user-store.ts +2 -1
  15. package/demo/static/favicon.svg +17 -24
  16. package/demo/static/tosi-platform.json +9304 -0
  17. package/dist/index.js +158 -156
  18. package/dist/index.js.map +14 -13
  19. package/dist/scripts/compat-effect.d.ts +16 -0
  20. package/dist/scripts/compat-kysely.d.ts +13 -0
  21. package/dist/scripts/compat-radash.d.ts +13 -0
  22. package/dist/scripts/compat-superstruct.d.ts +13 -0
  23. package/dist/scripts/compat-ts-pattern.d.ts +13 -0
  24. package/dist/scripts/compat-zod.d.ts +12 -0
  25. package/dist/src/lang/emitters/from-ts.d.ts +1 -1
  26. package/dist/src/lang/emitters/js-tests.d.ts +4 -0
  27. package/dist/src/lang/emitters/js.d.ts +2 -2
  28. package/dist/src/lang/index.d.ts +1 -0
  29. package/dist/src/lang/json-schema.d.ts +40 -0
  30. package/dist/src/lang/parser-transforms.d.ts +14 -0
  31. package/dist/src/lang/runtime.d.ts +39 -6
  32. package/dist/src/types/Type.d.ts +5 -0
  33. package/dist/tjs-full.js +158 -156
  34. package/dist/tjs-full.js.map +14 -13
  35. package/dist/tjs-vm.js +44 -43
  36. package/dist/tjs-vm.js.map +5 -5
  37. package/docs/README.md +21 -20
  38. package/docs/WASM-QUICKSTART.md +283 -0
  39. package/docs/diagrams/architecture-shift.svg +117 -0
  40. package/docs/diagrams/compile-runtime.svg +130 -0
  41. package/docs/diagrams/icon-riff-1.svg +55 -0
  42. package/docs/diagrams/icon-riff-2.svg +62 -0
  43. package/docs/diagrams/icon-riff-3.svg +61 -0
  44. package/docs/diagrams/platform-overview.svg +114 -0
  45. package/docs/diagrams/safe-eval.svg +147 -0
  46. package/docs/eval-v4/arch-comparison.svg +277 -0
  47. package/docs/eval-v4/bundler-tree.svg +250 -0
  48. package/docs/eval-v4/http-lifecycle.svg +148 -0
  49. package/docs/function-predicate-design.md +8 -8
  50. package/docs/native-engine-integration.md +2 -2
  51. package/editors/codemirror/autocomplete.test.ts +29 -29
  52. package/package.json +10 -4
  53. package/src/cli/commands/convert.test.ts +11 -8
  54. package/src/cli/tjs.ts +1 -1
  55. package/src/lang/codegen.test.ts +117 -112
  56. package/src/lang/docs.test.ts +22 -22
  57. package/src/lang/docs.ts +5 -8
  58. package/src/lang/emitters/dts.test.ts +13 -13
  59. package/src/lang/emitters/from-ts.ts +36 -9
  60. package/src/lang/emitters/js-tests.ts +143 -28
  61. package/src/lang/emitters/js.ts +49 -28
  62. package/src/lang/features.test.ts +259 -43
  63. package/src/lang/from-ts.test.ts +3 -3
  64. package/src/lang/function-predicate.test.ts +1 -1
  65. package/src/lang/index.ts +8 -47
  66. package/src/lang/json-schema.test.ts +261 -0
  67. package/src/lang/json-schema.ts +167 -0
  68. package/src/lang/parser-params.ts +28 -44
  69. package/src/lang/parser-transforms.ts +255 -0
  70. package/src/lang/parser.test.ts +32 -13
  71. package/src/lang/parser.ts +49 -11
  72. package/src/lang/perf.test.ts +11 -11
  73. package/src/lang/roundtrip.test.ts +3 -3
  74. package/src/lang/runtime.test.ts +167 -0
  75. package/src/lang/runtime.ts +234 -46
  76. package/src/lang/transpiler.test.ts +21 -21
  77. package/src/lang/typescript-syntax.test.ts +11 -9
  78. package/src/types/Type.ts +38 -1
  79. package/src/use-cases/bootstrap.test.ts +7 -7
  80. package/src/use-cases/client-server.test.ts +1 -1
  81. package/src/use-cases/malicious-actor.test.ts +1 -1
  82. package/src/use-cases/rag-processor.test.ts +1 -1
  83. package/src/use-cases/sophisticated-agents.test.ts +2 -2
  84. package/src/use-cases/transpiler-llm.test.ts +1 -1
  85. package/src/use-cases/unbundled-imports.test.ts +9 -9
  86. package/tjs-lang.svg +17 -25
@@ -675,14 +675,14 @@ function greet(name: 'World') {
675
675
 
676
676
  it('safety none should skip validation code in output', () => {
677
677
  // With safety (default) - should have validation
678
- const safe = tjs(`function add(a: 0, b: 0) -> 0 { return a + b }`)
678
+ const safe = tjs(`function add(a: 0, b: 0): 0 { return a + b }`)
679
679
  expect(safe.code).toContain('__tjs.typeError')
680
680
  expect(safe.code).toContain('__tjs.pushStack')
681
681
  expect(safe.code).toContain("typeof a !== 'number'")
682
682
 
683
683
  // Without safety - should NOT have validation
684
684
  const unsafe = tjs(`safety none
685
- function add(a: 0, b: 0) -> 0 { return a + b }`)
685
+ function add(a: 0, b: 0): 0 { return a + b }`)
686
686
  expect(unsafe.code).not.toContain('__tjs.typeError')
687
687
  expect(unsafe.code).not.toContain('__tjs.pushStack')
688
688
  expect(unsafe.code).not.toContain("typeof a !== 'number'")
@@ -693,8 +693,8 @@ function add(a: 0, b: 0) -> 0 { return a + b }`)
693
693
 
694
694
  it('safety none should work with multiple functions', () => {
695
695
  const result = tjs(`safety none
696
- function add(a: 0, b: 0) -> 0 { return a + b }
697
- function multiply(a: 0, b: 0) -> 0 { return a * b }`)
696
+ function add(a: 0, b: 0): 0 { return a + b }
697
+ function multiply(a: 0, b: 0): 0 { return a * b }`)
698
698
 
699
699
  // No validation for either function
700
700
  expect(result.code).not.toContain('__tjs.typeError')
@@ -708,8 +708,8 @@ function multiply(a: 0, b: 0) -> 0 { return a * b }`)
708
708
  describe('unsafe function marker (!)', () => {
709
709
  it('(!) should skip validation for that function only', () => {
710
710
  const result = tjs(`
711
- function safeAdd(a: 0, b: 0) -> 0 { return a + b }
712
- function unsafeAdd(! a: 0, b: 0) -> 0 { return a + b }
711
+ function safeAdd(a: 0, b: 0): 0 { return a + b }
712
+ function unsafeAdd(! a: 0, b: 0): 0 { return a + b }
713
713
  `)
714
714
  // Safe function has validation
715
715
  expect(result.code).toContain('__tjs.pushStack')
@@ -730,7 +730,7 @@ function unsafeAdd(! a: 0, b: 0) -> 0 { return a + b }
730
730
  })
731
731
 
732
732
  it('(!) function metadata should have unsafe: true', () => {
733
- const result = tjs(`function fast(! x: 0) -> 0 { return x * 2 }`)
733
+ const result = tjs(`function fast(! x: 0): 0 { return x * 2 }`)
734
734
  expect(result.code).toContain('"unsafe": true')
735
735
  })
736
736
  })
@@ -738,26 +738,26 @@ function unsafeAdd(! a: 0, b: 0) -> 0 { return a + b }
738
738
  describe('safe vs unsafe comparison', () => {
739
739
  it('safe function should have validation, unsafe should not', () => {
740
740
  // Safe function (default)
741
- const safe = tjs(`function double(x: 0) -> 0 { return x * 2 }`)
741
+ const safe = tjs(`function double(x: 0): 0 { return x * 2 }`)
742
742
  expect(safe.code).toContain("typeof x !== 'number'")
743
743
  expect(safe.code).toContain('__tjs.typeError')
744
744
 
745
745
  // Unsafe via (!) marker
746
- const unsafeMarker = tjs(`function double(! x: 0) -> 0 { return x * 2 }`)
746
+ const unsafeMarker = tjs(`function double(! x: 0): 0 { return x * 2 }`)
747
747
  expect(unsafeMarker.code).not.toContain("typeof x !== 'number'")
748
748
  expect(unsafeMarker.code).not.toContain('__tjs.typeError')
749
749
 
750
750
  // Unsafe via safety none
751
751
  const unsafeModule = tjs(`safety none
752
- function double(x: 0) -> 0 { return x * 2 }`)
752
+ function double(x: 0): 0 { return x * 2 }`)
753
753
  expect(unsafeModule.code).not.toContain("typeof x !== 'number'")
754
754
  expect(unsafeModule.code).not.toContain('__tjs.typeError')
755
755
  })
756
756
 
757
757
  it('both (!) and safety none should produce equivalent unsafe output', () => {
758
- const viaMarker = tjs(`function add(! a: 0, b: 0) -> 0 { return a + b }`)
758
+ const viaMarker = tjs(`function add(! a: 0, b: 0): 0 { return a + b }`)
759
759
  const viaDirective = tjs(`safety none
760
- function add(a: 0, b: 0) -> 0 { return a + b }`)
760
+ function add(a: 0, b: 0): 0 { return a + b }`)
761
761
 
762
762
  // Both should lack validation
763
763
  expect(viaMarker.code).not.toContain('__tjs.pushStack')
@@ -772,7 +772,7 @@ function add(a: 0, b: 0) -> 0 { return a + b }`)
772
772
  describe('safe function syntax (?)', () => {
773
773
  it('should parse (?) function marker', () => {
774
774
  const result = tjs(`
775
- function validated(? x: 0) -> 0 {
775
+ function validated(? x: 0): 0 {
776
776
  return x * 2
777
777
  }
778
778
  `)
@@ -871,7 +871,7 @@ function test() {
871
871
  describe('return type safety arrows', () => {
872
872
  it('should parse -> as normal return type', () => {
873
873
  const result = tjs(`
874
- function add(a: 0, b: 0) -> 0 {
874
+ function add(a: 0, b: 0): 0 {
875
875
  return a + b
876
876
  }
877
877
  `)
@@ -883,7 +883,7 @@ describe('return type safety arrows', () => {
883
883
 
884
884
  it('should parse -? as safe return (force output validation)', () => {
885
885
  const result = tjs(`
886
- function add(a: 0, b: 0) -? 0 {
886
+ function add(a: 0, b: 0):? 0 {
887
887
  return a + b
888
888
  }
889
889
  `)
@@ -894,7 +894,7 @@ describe('return type safety arrows', () => {
894
894
 
895
895
  it('should parse -! as unsafe return (skip output validation)', () => {
896
896
  const result = tjs(`
897
- function add(a: 0, b: 0) -! 0 {
897
+ function add(a: 0, b: 0):! 0 {
898
898
  return a + b
899
899
  }
900
900
  `)
@@ -905,7 +905,7 @@ describe('return type safety arrows', () => {
905
905
 
906
906
  it('should combine (?) with -? for fully safe function', () => {
907
907
  const result = tjs(`
908
- function critical(? x: 0) -? 0 {
908
+ function critical(? x: 0):? 0 {
909
909
  return x * 2
910
910
  }
911
911
  `)
@@ -916,7 +916,7 @@ describe('return type safety arrows', () => {
916
916
 
917
917
  it('should combine (!) with -! for fully unsafe function', () => {
918
918
  const result = tjs(`
919
- function fast(! x: 0) -! 0 {
919
+ function fast(! x: 0):! 0 {
920
920
  return x * 2
921
921
  }
922
922
  `)
@@ -934,7 +934,7 @@ describe('signature tests (transpile-time)', () => {
934
934
 
935
935
  it('-> should run signature test at transpile time', () => {
936
936
  const result = tjs(`
937
- function double(x: 5) -> 10 {
937
+ function double(x: 5): 10 {
938
938
  return x * 2
939
939
  }
940
940
  `)
@@ -947,7 +947,7 @@ describe('signature tests (transpile-time)', () => {
947
947
  // double(5) returns 10, but expected "" — value mismatch
948
948
  expect(() =>
949
949
  tjs(`
950
- function double(x: 5) -> "" {
950
+ function double(x: 5): "" {
951
951
  return x * 2
952
952
  }
953
953
  `)
@@ -956,7 +956,7 @@ describe('signature tests (transpile-time)', () => {
956
956
 
957
957
  it('-? should run signature test at transpile time', () => {
958
958
  const result = tjs(`
959
- function double(x: 5) -? 10 {
959
+ function double(x: 5):? 10 {
960
960
  return x * 2
961
961
  }
962
962
  `)
@@ -967,7 +967,7 @@ describe('signature tests (transpile-time)', () => {
967
967
  it('-? should pass when example is consistent', () => {
968
968
  // double(5) returns 10, -> 10 is the exact expected result
969
969
  const result = tjs(`
970
- function double(x: 5) -? 10 {
970
+ function double(x: 5):? 10 {
971
971
  return x * 2
972
972
  }
973
973
  `)
@@ -978,7 +978,7 @@ describe('signature tests (transpile-time)', () => {
978
978
  // getString(5) returns 10, but expected "" — value mismatch
979
979
  expect(() =>
980
980
  tjs(`
981
- function getString(x: 5) -? "" {
981
+ function getString(x: 5):? "" {
982
982
  return x * 2
983
983
  }
984
984
  `)
@@ -988,7 +988,7 @@ describe('signature tests (transpile-time)', () => {
988
988
  it('-! should skip signature test entirely', () => {
989
989
  // This would fail if tested, but -! skips the test
990
990
  const result = tjs(`
991
- function double(x: 5) -! 999 {
991
+ function double(x: 5):! 999 {
992
992
  return x * 2
993
993
  }
994
994
  `)
@@ -997,7 +997,7 @@ describe('signature tests (transpile-time)', () => {
997
997
 
998
998
  it('-> with object return should test structure', () => {
999
999
  const result = tjs(`
1000
- function getPoint(x: 3, y: 4) -> { x: 3, y: 4 } {
1000
+ function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
1001
1001
  return { x, y }
1002
1002
  }
1003
1003
  `)
@@ -1007,7 +1007,7 @@ describe('signature tests (transpile-time)', () => {
1007
1007
  it('-> with object return should pass when example is consistent', () => {
1008
1008
  // getPoint(3, 4) returns {x: 3, y: 4}, -> matches exactly
1009
1009
  const result = tjs(`
1010
- function getPoint(x: 3, y: 4) -> { x: 3, y: 4 } {
1010
+ function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
1011
1011
  return { x, y }
1012
1012
  }
1013
1013
  `)
@@ -1017,7 +1017,7 @@ describe('signature tests (transpile-time)', () => {
1017
1017
  it('-> with object return should fail on value mismatch', () => {
1018
1018
  expect(() =>
1019
1019
  tjs(`
1020
- function getPoint(x: 3, y: 4) -> { x: "", y: "" } {
1020
+ function getPoint(x: 3, y: 4): { x: "", y: "" } {
1021
1021
  return { x, y }
1022
1022
  }
1023
1023
  `)
@@ -1027,7 +1027,7 @@ describe('signature tests (transpile-time)', () => {
1027
1027
  it('should skip signature tests for async functions', () => {
1028
1028
  const result = tjs(
1029
1029
  `
1030
- async function fetchData(id: 'test-1') -> { name: '', id: '' } {
1030
+ async function fetchData(id: 'test-1'): { name: '', id: '' } {
1031
1031
  return { name: 'Test', id }
1032
1032
  }
1033
1033
  `,
@@ -1042,11 +1042,11 @@ describe('signature tests (transpile-time)', () => {
1042
1042
  it('should handle top-level await in module code during tests', () => {
1043
1043
  const result = tjs(
1044
1044
  `
1045
- function double(x: 5) -> 10 {
1045
+ function double(x: 5): 10 {
1046
1046
  return x * 2
1047
1047
  }
1048
1048
 
1049
- async function fetchThing(id: '') -> '' {
1049
+ async function fetchThing(id: ''): '' {
1050
1050
  return id
1051
1051
  }
1052
1052
 
@@ -1069,7 +1069,7 @@ describe('signature tests (transpile-time)', () => {
1069
1069
 
1070
1070
  const UserSchema = Schema({ name: '', age: 0 })
1071
1071
 
1072
- function validateUser(data: { name: '', age: 0 }) -> { valid: true, errors: [''] } {
1072
+ function validateUser(data: { name: '', age: 0 }): { valid: true, errors: [''] } {
1073
1073
  return { valid: true, errors: [] }
1074
1074
  }
1075
1075
  `,
@@ -1090,7 +1090,7 @@ describe('signature tests (transpile-time)', () => {
1090
1090
  `
1091
1091
  import { parseISO, format } from 'date-fns'
1092
1092
 
1093
- function formatDate(date: '2024-01-15', pattern: 'yyyy-MM-dd') -> '' {
1093
+ function formatDate(date: '2024-01-15', pattern: 'yyyy-MM-dd'): '' {
1094
1094
  const parsed = parseISO(date)
1095
1095
  return format(parsed, pattern)
1096
1096
  }
@@ -1109,11 +1109,11 @@ describe('signature tests (transpile-time)', () => {
1109
1109
  it('should test sync functions alongside async functions', () => {
1110
1110
  const result = tjs(
1111
1111
  `
1112
- function add(a: 2, b: 3) -> 5 {
1112
+ function add(a: 2, b: 3): 5 {
1113
1113
  return a + b
1114
1114
  }
1115
1115
 
1116
- async function fetchSum(a: 0, b: 0) -> 0 {
1116
+ async function fetchSum(a: 0, b: 0): 0 {
1117
1117
  return a + b
1118
1118
  }
1119
1119
  `,
@@ -1126,6 +1126,82 @@ describe('signature tests (transpile-time)', () => {
1126
1126
  )
1127
1127
  expect(addTest?.passed).toBe(true)
1128
1128
  })
1129
+
1130
+ it('should run signature tests for class methods using first constructor', () => {
1131
+ const result = tjs(
1132
+ `
1133
+ class Point {
1134
+ constructor(x: 0.0, y: 0.0) {
1135
+ this.x = x
1136
+ this.y = y
1137
+ }
1138
+
1139
+ distanceTo(other: { x: 3.0, y: 4.0 }): 5.0 {
1140
+ const dx = this.x - other.x
1141
+ const dy = this.y - other.y
1142
+ return Math.sqrt(dx * dx + dy * dy)
1143
+ }
1144
+ }
1145
+ `,
1146
+ { runTests: 'report' }
1147
+ )
1148
+ expect(result.testResults).toBeDefined()
1149
+ const sigTest = result.testResults!.find((t: any) =>
1150
+ t.description.includes('Point.distanceTo')
1151
+ )
1152
+ expect(sigTest).toBeDefined()
1153
+ expect(sigTest?.passed).toBe(true)
1154
+ expect(sigTest?.isSignatureTest).toBe(true)
1155
+ })
1156
+
1157
+ it('should fail class method signature test when return value is wrong', () => {
1158
+ expect(() =>
1159
+ tjs(`
1160
+ class Adder {
1161
+ constructor(base: 10) {
1162
+ this.base = base
1163
+ }
1164
+
1165
+ add(x: 5): 100 {
1166
+ return this.base + x
1167
+ }
1168
+ }
1169
+ `)
1170
+ ).toThrow(/Expected.*got/)
1171
+ })
1172
+
1173
+ it('should run signature tests for methods on classes with multiple constructors', () => {
1174
+ const result = tjs(
1175
+ `
1176
+ TjsClass
1177
+
1178
+ class Point {
1179
+ constructor(x: 0.0, y: 0.0) {
1180
+ this.x = x
1181
+ this.y = y
1182
+ }
1183
+
1184
+ constructor(coords: { x: 0.0, y: 0.0 }) {
1185
+ this.x = coords.x
1186
+ this.y = coords.y
1187
+ }
1188
+
1189
+ distanceTo(other: { x: 3.0, y: 4.0 }): 5.0 {
1190
+ const dx = this.x - other.x
1191
+ const dy = this.y - other.y
1192
+ return Math.sqrt(dx * dx + dy * dy)
1193
+ }
1194
+ }
1195
+ `,
1196
+ { runTests: 'report' }
1197
+ )
1198
+ expect(result.testResults).toBeDefined()
1199
+ const sigTest = result.testResults!.find((t: any) =>
1200
+ t.description.includes('Point.distanceTo')
1201
+ )
1202
+ expect(sigTest).toBeDefined()
1203
+ expect(sigTest?.passed).toBe(true)
1204
+ })
1129
1205
  })
1130
1206
 
1131
1207
  // =============================================================================
@@ -1143,7 +1219,7 @@ describe('signature test canaries — exact value matching', () => {
1143
1219
  // add(2,3) returns 5, but -> says 0 — must fail
1144
1220
  expect(() =>
1145
1221
  tjs(`
1146
- function add(a: 2, b: 3) -> 0 {
1222
+ function add(a: 2, b: 3): 0 {
1147
1223
  return a + b
1148
1224
  }
1149
1225
  `)
@@ -1154,7 +1230,7 @@ describe('signature test canaries — exact value matching', () => {
1154
1230
  // greet('World') returns 'Hello, World!', but -> says '' — must fail
1155
1231
  expect(() =>
1156
1232
  tjs(
1157
- "function greet(name: 'World') -> '' {\n return 'Hello, ' + name + '!'\n}"
1233
+ "function greet(name: 'World'): '' {\n return 'Hello, ' + name + '!'\n}"
1158
1234
  )
1159
1235
  ).toThrow(/Expected.*got/)
1160
1236
  })
@@ -1163,7 +1239,7 @@ describe('signature test canaries — exact value matching', () => {
1163
1239
  // getPoint(3,4) returns {x:3,y:4}, but -> says {x:0,y:0} — must fail
1164
1240
  expect(() =>
1165
1241
  tjs(`
1166
- function getPoint(x: 3, y: 4) -> { x: 0, y: 0 } {
1242
+ function getPoint(x: 3, y: 4): { x: 0, y: 0 } {
1167
1243
  return { x, y }
1168
1244
  }
1169
1245
  `)
@@ -1174,7 +1250,7 @@ describe('signature test canaries — exact value matching', () => {
1174
1250
  // -? runs signature test AND runtime validation
1175
1251
  expect(() =>
1176
1252
  tjs(`
1177
- function double(x: 5) -? 0 {
1253
+ function double(x: 5):? 0 {
1178
1254
  return x * 2
1179
1255
  }
1180
1256
  `)
@@ -1184,7 +1260,7 @@ describe('signature test canaries — exact value matching', () => {
1184
1260
  it('CANARY: -! does NOT run signature test (wrong value is OK)', () => {
1185
1261
  // -! skips the test — wrong return example is just metadata
1186
1262
  const result = tjs(`
1187
- function double(x: 5) -! 999 {
1263
+ function double(x: 5):! 999 {
1188
1264
  return x * 2
1189
1265
  }
1190
1266
  `)
@@ -1193,7 +1269,7 @@ describe('signature test canaries — exact value matching', () => {
1193
1269
 
1194
1270
  it('CANARY: -> passes with correct exact values', () => {
1195
1271
  const result = tjs(`
1196
- function add(a: 2, b: 3) -> 5 {
1272
+ function add(a: 2, b: 3): 5 {
1197
1273
  return a + b
1198
1274
  }
1199
1275
  `)
@@ -1203,7 +1279,7 @@ describe('signature test canaries — exact value matching', () => {
1203
1279
 
1204
1280
  it('CANARY: -> passes with correct string value', () => {
1205
1281
  const result = tjs(
1206
- "function greet(name: 'World') -> 'Hello, World!' {\n return 'Hello, ' + name + '!'\n}"
1282
+ "function greet(name: 'World'): 'Hello, World!' {\n return 'Hello, ' + name + '!'\n}"
1207
1283
  )
1208
1284
  expect(result.testResults).toHaveLength(1)
1209
1285
  expect(result.testResults![0].passed).toBe(true)
@@ -1211,7 +1287,7 @@ describe('signature test canaries — exact value matching', () => {
1211
1287
 
1212
1288
  it('CANARY: -> passes with correct object values', () => {
1213
1289
  const result = tjs(`
1214
- function getPoint(x: 3, y: 4) -> { x: 3, y: 4 } {
1290
+ function getPoint(x: 3, y: 4): { x: 3, y: 4 } {
1215
1291
  return { x, y }
1216
1292
  }
1217
1293
  `)
@@ -1222,7 +1298,7 @@ describe('signature test canaries — exact value matching', () => {
1222
1298
  it('CANARY: -? runtime validation checks type only (not value)', () => {
1223
1299
  // -? validates at runtime that return TYPE matches (number, not 10)
1224
1300
  // The runtime check should pass even if the value differs from annotation
1225
- const result = tjs('function double(x: 5) -? 10 { return x * 2 }', {
1301
+ const result = tjs('function double(x: 5):? 10 { return x * 2 }', {
1226
1302
  runTests: false,
1227
1303
  })
1228
1304
  const savedTjs = globalThis.__tjs
@@ -1552,3 +1628,143 @@ describe('SyntaxError formatting', () => {
1552
1628
  }
1553
1629
  })
1554
1630
  })
1631
+
1632
+ // =============================================================================
1633
+ // BANG ACCESS (!.) — asserted non-null member access
1634
+ // =============================================================================
1635
+
1636
+ describe('bang access (!.)', () => {
1637
+ // Helper to run TJS code and return the result of calling test()
1638
+ function runBang(source: string): unknown {
1639
+ const result = tjs(source, { runTests: false })
1640
+ const fn = new Function(result.code + '\nreturn test')()
1641
+ return fn()
1642
+ }
1643
+
1644
+ it('should access property on non-null object', () => {
1645
+ expect(
1646
+ runBang(`
1647
+ function test() {
1648
+ const x = { foo: 42 }
1649
+ return x!.foo
1650
+ }
1651
+ `)
1652
+ ).toBe(42)
1653
+ })
1654
+
1655
+ it('should return MonadicError on null', () => {
1656
+ const result = runBang(`
1657
+ function test() {
1658
+ const x = null
1659
+ return x!.foo
1660
+ }
1661
+ `)
1662
+ expect(isMonadicError(result)).toBe(true)
1663
+ })
1664
+
1665
+ it('should return MonadicError on undefined', () => {
1666
+ const result = runBang(`
1667
+ function test() {
1668
+ let x
1669
+ return x!.foo
1670
+ }
1671
+ `)
1672
+ expect(isMonadicError(result)).toBe(true)
1673
+ })
1674
+
1675
+ it('should propagate MonadicError through chains', () => {
1676
+ const result = runBang(`
1677
+ function test() {
1678
+ const x = null
1679
+ return x!.foo!.bar
1680
+ }
1681
+ `)
1682
+ expect(isMonadicError(result)).toBe(true)
1683
+ })
1684
+
1685
+ it('should handle member chains before !.', () => {
1686
+ expect(
1687
+ runBang(`
1688
+ function test() {
1689
+ const x = { y: { foo: 99 } }
1690
+ return x.y!.foo
1691
+ }
1692
+ `)
1693
+ ).toBe(99)
1694
+ })
1695
+
1696
+ it('should handle function call before !.', () => {
1697
+ expect(
1698
+ runBang(`
1699
+ function getObj() { return { val: 7 } }
1700
+ function test() {
1701
+ return getObj()!.val
1702
+ }
1703
+ `)
1704
+ ).toBe(7)
1705
+ })
1706
+
1707
+ it('should return MonadicError when function returns null', () => {
1708
+ const result = runBang(`
1709
+ function getNull() { return null }
1710
+ function test() {
1711
+ return getNull()!.val
1712
+ }
1713
+ `)
1714
+ expect(isMonadicError(result)).toBe(true)
1715
+ })
1716
+
1717
+ it('should handle bracket access before !.', () => {
1718
+ expect(
1719
+ runBang(`
1720
+ function test() {
1721
+ const arr = [{ name: 'first' }]
1722
+ return arr[0]!.name
1723
+ }
1724
+ `)
1725
+ ).toBe('first')
1726
+ })
1727
+
1728
+ it('should not transform !. inside strings', () => {
1729
+ const result = tjs(
1730
+ `
1731
+ function test() { return "x!.foo" }
1732
+ `,
1733
+ { runTests: false }
1734
+ )
1735
+ expect(result.code).toContain('"x!.foo"')
1736
+ expect(result.code).not.toContain('__tjs.bang')
1737
+ })
1738
+
1739
+ it('should not transform !. inside comments', () => {
1740
+ const result = tjs(
1741
+ `
1742
+ // x!.foo should not be transformed
1743
+ function test() { return 1 }
1744
+ `,
1745
+ { runTests: false }
1746
+ )
1747
+ expect(result.code).not.toContain('__tjs.bang')
1748
+ })
1749
+
1750
+ it('should work alongside ?. optional chaining', () => {
1751
+ expect(
1752
+ runBang(`
1753
+ function test() {
1754
+ const x = { y: { z: 5 } }
1755
+ return x?.y!.z
1756
+ }
1757
+ `)
1758
+ ).toBe(5)
1759
+ })
1760
+
1761
+ it('emitted standalone JS includes bang inline stub', () => {
1762
+ const result = tjs(
1763
+ `
1764
+ function test() { const x = {}; return x!.foo }
1765
+ `,
1766
+ { runTests: false }
1767
+ )
1768
+ expect(result.code).toContain('function bang(')
1769
+ })
1770
+ })
@@ -33,7 +33,7 @@ describe('TypeScript to TJS Transpiler', () => {
33
33
  `function greet(name: string): string { return name }`,
34
34
  { emitTJS: true }
35
35
  )
36
- expect(result.code).toContain("-! ''") // -! skips signature test for TS-transpiled code
36
+ expect(result.code).toContain(":! ''") // :! skips signature test for TS-transpiled code
37
37
  })
38
38
 
39
39
  it('should handle array types', () => {
@@ -49,7 +49,7 @@ describe('TypeScript to TJS Transpiler', () => {
49
49
  `function getUser(): { name: string, age: number } { return { name: '', age: 0 } }`,
50
50
  { emitTJS: true }
51
51
  )
52
- expect(result.code).toContain("-! { name: '', age: 0.0 }") // -! for TS-transpiled
52
+ expect(result.code).toContain(":! { name: '', age: 0.0 }") // :! for TS-transpiled
53
53
  })
54
54
 
55
55
  it('should handle nullable types', () => {
@@ -57,7 +57,7 @@ describe('TypeScript to TJS Transpiler', () => {
57
57
  `function find(id: string): string | null { return null }`,
58
58
  { emitTJS: true }
59
59
  )
60
- expect(result.code).toContain("-! '' | null") // -! for TS-transpiled
60
+ expect(result.code).toContain(":! '' | null") // :! for TS-transpiled
61
61
  })
62
62
 
63
63
  it('should preserve default values', () => {
@@ -200,7 +200,7 @@ describe('FunctionPredicate regression fixes', () => {
200
200
 
201
201
  it('should handle FunctionPredicate call in return type position', () => {
202
202
  const result = tjs(
203
- "function makeStyle(spec: {}) -! FunctionPredicate('function', { params: { el: {} } }) {\n return (el) => el\n}",
203
+ "function makeStyle(spec: {}):! FunctionPredicate('function', { params: { el: {} } }) {\n return (el) => el\n}",
204
204
  { runTests: false }
205
205
  )
206
206
  // Should not error — FunctionPredicate(...) is a valid return type
package/src/lang/index.ts CHANGED
@@ -53,6 +53,12 @@ export {
53
53
  } from './emitters/from-ts'
54
54
  export * from './inference'
55
55
  export { Schema } from './schema'
56
+ export {
57
+ typeDescriptorToJSONSchema,
58
+ exampleToJSONSchema,
59
+ functionMetaToJSONSchema,
60
+ type JSONSchemaObject,
61
+ } from './json-schema'
56
62
  export { MetadataCache, getGlobalCache, setGlobalCache } from './metadata-cache'
57
63
  export {
58
64
  lint,
@@ -116,6 +122,7 @@ export {
116
122
 
117
123
  // Re-import for local use in this file
118
124
  import { MetadataCache, getGlobalCache } from './metadata-cache'
125
+ import { typeDescriptorToJSONSchema } from './json-schema'
119
126
 
120
127
  /**
121
128
  * Transpile JavaScript source code to Agent99 AST
@@ -464,7 +471,7 @@ export function getToolDefinitions(
464
471
  const required: string[] = []
465
472
 
466
473
  for (const [paramName, param] of Object.entries(sig.parameters)) {
467
- properties[paramName] = typeDescriptorToJsonSchema(param.type)
474
+ properties[paramName] = typeDescriptorToJSONSchema(param.type)
468
475
  if (param.description) {
469
476
  properties[paramName].description = param.description
470
477
  }
@@ -487,49 +494,3 @@ export function getToolDefinitions(
487
494
  }
488
495
  })
489
496
  }
490
-
491
- /**
492
- * Convert TypeDescriptor to JSON Schema
493
- */
494
- function typeDescriptorToJsonSchema(
495
- type: import('./types').TypeDescriptor
496
- ): any {
497
- switch (type.kind) {
498
- case 'string':
499
- return { type: 'string' }
500
- case 'number':
501
- return { type: 'number' }
502
- case 'boolean':
503
- return { type: 'boolean' }
504
- case 'null':
505
- return { type: 'null' }
506
- case 'array':
507
- return {
508
- type: 'array',
509
- items: type.items ? typeDescriptorToJsonSchema(type.items) : {},
510
- }
511
- case 'object':
512
- if (!type.shape) {
513
- return { type: 'object' }
514
- }
515
- return {
516
- type: 'object',
517
- properties: Object.fromEntries(
518
- Object.entries(type.shape).map(([k, v]) => [
519
- k,
520
- typeDescriptorToJsonSchema(v),
521
- ])
522
- ),
523
- }
524
- case 'union':
525
- if (!type.members) {
526
- return {}
527
- }
528
- return {
529
- anyOf: type.members.map(typeDescriptorToJsonSchema),
530
- }
531
- case 'any':
532
- default:
533
- return {}
534
- }
535
- }