tjs-lang 0.6.43 → 0.6.45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tjs-lang",
3
- "version": "0.6.43",
3
+ "version": "0.6.45",
4
4
  "description": "Type-safe JavaScript dialect with runtime validation, sandboxed VM execution, and AI agent orchestration. Transpiles TypeScript to validated JS with fuel-metered execution for untrusted code.",
5
5
  "keywords": [
6
6
  "typescript",
package/src/cli/tjs.ts CHANGED
@@ -20,7 +20,7 @@ import { emit } from './commands/emit'
20
20
  import { convert } from './commands/convert'
21
21
  import { test } from './commands/test'
22
22
 
23
- const VERSION = '0.6.43'
23
+ const VERSION = '0.6.45'
24
24
 
25
25
  const HELP = `
26
26
  tjs - Typed JavaScript CLI
@@ -453,6 +453,15 @@ function detectTypeDeclarations(source: string): Map<string, string> {
453
453
  result.set(m[1], m[2].trim())
454
454
  }
455
455
 
456
+ // Block with TS type body: Type Name { // TS: original type }
457
+ const tsBodyRe =
458
+ /^[ \t]*(?:export\s+)?Type\s+(\w+)\s*\{[^}]*\/\/\s*TS:\s*(.+?)(?:\n|\s*\})/gm
459
+ while ((m = tsBodyRe.exec(source)) !== null) {
460
+ if (!result.has(m[1])) {
461
+ result.set(m[1], `__ts__:${m[2].trim()}`) // prefix marks TS passthrough
462
+ }
463
+ }
464
+
456
465
  // Empty block: Type Name {} (no example — degraded type, emit as any)
457
466
  const emptyBlockRe = /^[ \t]*(?:export\s+)?Type\s+(\w+)\s*\{\s*\}/gm
458
467
  while ((m = emptyBlockRe.exec(source)) !== null) {
@@ -653,7 +662,11 @@ export function generateDTS(
653
662
  const isExported = hasAnyExport ? !!exportInfo?.exported : true
654
663
  if (!isExported) continue
655
664
 
656
- if (exampleStr === '') {
665
+ if (exampleStr.startsWith('__ts__:')) {
666
+ // Preserved TS type body — emit verbatim as type alias
667
+ const tsBody = exampleStr.slice(7)
668
+ lines.push(`export type ${name} = ${tsBody};`)
669
+ } else if (exampleStr === '') {
657
670
  // Empty Type {} — degraded from TS type alias, emit as type = any
658
671
  lines.push(`export type ${name} = any;`)
659
672
  } else {
@@ -1262,9 +1262,11 @@ function transformTypeAliasToType(
1262
1262
 
1263
1263
  const example = typeToExample(node.type, undefined, warnings)
1264
1264
 
1265
- // 'any' and 'undefined' — skip declaration (undeclared = any in TJS)
1265
+ // 'any' and 'undefined' — preserve original TS body for DTS round-tripping
1266
1266
  if (example === 'any' || example === 'undefined') {
1267
- return `Type ${typeName} {}`
1267
+ const originalType = node.type.getText(sourceFile).trim()
1268
+ // Include the TS type body so the DTS emitter can recover it
1269
+ return `Type ${typeName} {\n // TS: ${originalType}\n}`
1268
1270
  }
1269
1271
 
1270
1272
  // For simple primitive types, use short form
@@ -796,6 +796,7 @@ export function transpileToJS(
796
796
  const needsFunctionPredicate = /\bFunctionPredicate\(/.test(code)
797
797
  const needsEnum = /\bEnum\(/.test(code)
798
798
  const needsUnion = /\bUnion\(/.test(code)
799
+ const needsIsBounded = code.includes('IsBounded(')
799
800
  const needsSafeEval = preprocessed.tjsModes.tjsSafeEval
800
801
 
801
802
  const needsRuntime =
@@ -811,6 +812,7 @@ export function transpileToJS(
811
812
  needsFunctionPredicate ||
812
813
  needsEnum ||
813
814
  needsUnion ||
815
+ needsIsBounded ||
814
816
  needsSafeEval
815
817
 
816
818
  if (needsRuntime) {
@@ -838,7 +840,7 @@ export function transpileToJS(
838
840
  // Eq/NotEq (honest equality)
839
841
  if (needsEq) {
840
842
  inlineParts.push(
841
- `function Eq(a,b){if(a instanceof String||a instanceof Number||a instanceof Boolean)a=a.valueOf();if(b instanceof String||b instanceof Number||b instanceof Boolean)b=b.valueOf();if(a===b)return true;if((a===null||a===undefined)&&(b===null||b===undefined))return true;return false}`
843
+ `function Eq(a,b){if(a instanceof String||a instanceof Number||a instanceof Boolean)a=a.valueOf();if(b instanceof String||b instanceof Number||b instanceof Boolean)b=b.valueOf();if(a===b)return true;if(typeof a==='number'&&typeof b==='number'&&isNaN(a)&&isNaN(b))return true;if((a===null||a===undefined)&&(b===null||b===undefined))return true;return false}`
842
844
  )
843
845
  }
844
846
  if (needsNotEq) {
@@ -853,7 +855,7 @@ export function transpileToJS(
853
855
  // Is/IsNot (structural equality)
854
856
  if (needsIs) {
855
857
  inlineParts.push(
856
- `const tjsEquals=Symbol.for('tjs.equals');function Is(a,b){if(a!=null&&typeof a==='object'&&typeof a[tjsEquals]==='function')return a[tjsEquals](b);if(b!=null&&typeof b==='object'&&typeof b[tjsEquals]==='function')return b[tjsEquals](a);if(a!=null&&typeof a==='object'&&typeof a.Equals==='function')return a.Equals(b);if(b!=null&&typeof b==='object'&&typeof b.Equals==='function')return b.Equals(a);if(a instanceof String||a instanceof Number||a instanceof Boolean)a=a.valueOf();if(b instanceof String||b instanceof Number||b instanceof Boolean)b=b.valueOf();if(a===b)return true;if((a==null)&&(b==null))return true;if(a==null||b==null)return false;if(typeof a!==typeof b)return false;if(typeof a!=='object')return false;if(a instanceof Set&&b instanceof Set){if(a.size!==b.size)return false;for(const v of a)if(!b.has(v))return false;return true}if(a instanceof Map&&b instanceof Map){if(a.size!==b.size)return false;for(const[k,v]of a)if(!b.has(k)||!Is(v,b.get(k)))return false;return true}if(a instanceof Date&&b instanceof Date)return a.getTime()===b.getTime();if(a instanceof RegExp&&b instanceof RegExp)return a.toString()===b.toString();if(Array.isArray(a)&&Array.isArray(b)){if(a.length!==b.length)return false;return a.every((v,i)=>Is(v,b[i]))}if(Array.isArray(a)!==Array.isArray(b))return false;const ka=Object.keys(a),kb=Object.keys(b);if(ka.length!==kb.length)return false;return ka.every(k=>Is(a[k],b[k]))}`
858
+ `const tjsEquals=Symbol.for('tjs.equals');function Is(a,b){if(a!=null&&typeof a==='object'&&typeof a[tjsEquals]==='function')return a[tjsEquals](b);if(b!=null&&typeof b==='object'&&typeof b[tjsEquals]==='function')return b[tjsEquals](a);if(a!=null&&typeof a==='object'&&typeof a.Equals==='function')return a.Equals(b);if(b!=null&&typeof b==='object'&&typeof b.Equals==='function')return b.Equals(a);if(a instanceof String||a instanceof Number||a instanceof Boolean)a=a.valueOf();if(b instanceof String||b instanceof Number||b instanceof Boolean)b=b.valueOf();if(a===b)return true;if(typeof a==='number'&&typeof b==='number'&&isNaN(a)&&isNaN(b))return true;if((a==null)&&(b==null))return true;if(a==null||b==null)return false;if(typeof a!==typeof b)return false;if(typeof a!=='object')return false;if(a instanceof Set&&b instanceof Set){if(a.size!==b.size)return false;for(const v of a)if(!b.has(v))return false;return true}if(a instanceof Map&&b instanceof Map){if(a.size!==b.size)return false;for(const[k,v]of a)if(!b.has(k)||!Is(v,b.get(k)))return false;return true}if(a instanceof Date&&b instanceof Date)return a.getTime()===b.getTime();if(a instanceof RegExp&&b instanceof RegExp)return a.toString()===b.toString();if(Array.isArray(a)&&Array.isArray(b)){if(a.length!==b.length)return false;return a.every((v,i)=>Is(v,b[i]))}if(Array.isArray(a)!==Array.isArray(b))return false;const ka=Object.keys(a),kb=Object.keys(b);if(ka.length!==kb.length)return false;return ka.every(k=>Is(a[k],b[k]))}`
857
859
  )
858
860
  }
859
861
  if (needsIsNot) {
@@ -887,6 +889,11 @@ export function transpileToJS(
887
889
  `function Union(d,...v){const vals=v.flat();return{description:d,check:x=>vals.includes(x),values:vals,__runtimeType:true}}`
888
890
  )
889
891
  }
892
+ if (needsIsBounded) {
893
+ inlineParts.push(
894
+ `function IsBounded(v){return typeof v==='number'&&isFinite(v)&&!isNaN(v)}`
895
+ )
896
+ }
890
897
 
891
898
  // Build preamble: inline functions are declared at module scope,
892
899
  // then __tjs either uses the shared runtime or references the inlined ones.
@@ -907,6 +914,7 @@ export function transpileToJS(
907
914
  if (needsFunctionPredicate) fallbackEntries.push('FunctionPredicate')
908
915
  if (needsEnum) fallbackEntries.push('Enum')
909
916
  if (needsUnion) fallbackEntries.push('Union')
917
+ if (needsIsBounded) fallbackEntries.push('IsBounded')
910
918
 
911
919
  const fallbackObj =
912
920
  fallbackEntries.length > 0
@@ -416,6 +416,16 @@ export function Is(a: unknown, b: unknown): boolean {
416
416
  // Identical references or primitives
417
417
  if (a === b) return true
418
418
 
419
+ // NaN === NaN (JS gets this wrong)
420
+ if (
421
+ typeof a === 'number' &&
422
+ typeof b === 'number' &&
423
+ isNaN(a as number) &&
424
+ isNaN(b as number)
425
+ ) {
426
+ return true
427
+ }
428
+
419
429
  // null and undefined are equal to each other (nullish equality)
420
430
  // This preserves the useful JS pattern: x == null checks for both
421
431
  if ((a === null || a === undefined) && (b === null || b === undefined)) {
@@ -511,6 +521,21 @@ export function TypeOf(value: unknown): string {
511
521
  if (value === null) return 'null'
512
522
  return typeof value
513
523
  }
524
+
525
+ /**
526
+ * Check if a number is bounded (finite and not NaN).
527
+ * The question you're actually asking when you reach for isNaN or isFinite.
528
+ *
529
+ * IsBounded(42) → true
530
+ * IsBounded(3.14) → true
531
+ * IsBounded(NaN) → false
532
+ * IsBounded(Infinity) → false
533
+ * IsBounded(-Infinity) → false
534
+ * IsBounded('hello') → false
535
+ */
536
+ export function IsBounded(value: unknown): boolean {
537
+ return typeof value === 'number' && isFinite(value) && !isNaN(value)
538
+ }
514
539
  export function Eq(a: unknown, b: unknown): boolean {
515
540
  // Unwrap boxed primitives
516
541
  if (a instanceof String || a instanceof Number || a instanceof Boolean) {
@@ -523,6 +548,16 @@ export function Eq(a: unknown, b: unknown): boolean {
523
548
  // Identical references or primitives
524
549
  if (a === b) return true
525
550
 
551
+ // NaN === NaN (JS gets this wrong)
552
+ if (
553
+ typeof a === 'number' &&
554
+ typeof b === 'number' &&
555
+ isNaN(a as number) &&
556
+ isNaN(b as number)
557
+ ) {
558
+ return true
559
+ }
560
+
526
561
  // null and undefined are equal to each other
527
562
  if ((a === null || a === undefined) && (b === null || b === undefined)) {
528
563
  return true
@@ -1329,6 +1364,8 @@ export function createRuntime() {
1329
1364
  NotEq,
1330
1365
  // Honest typeof (typeof with TjsEquals)
1331
1366
  TypeOf,
1367
+ // Number utilities
1368
+ IsBounded,
1332
1369
  tjsEquals,
1333
1370
  // Extensions
1334
1371
  registerExtension: instanceRegisterExtension,
@@ -1410,6 +1447,8 @@ export const runtime = {
1410
1447
  NotEq,
1411
1448
  // Honest typeof (used by typeof with TjsEquals)
1412
1449
  TypeOf,
1450
+ // Number utilities
1451
+ IsBounded,
1413
1452
  }
1414
1453
 
1415
1454
  /**