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/CLAUDE.md +51 -14
- package/demo/docs.json +2 -2
- package/dist/index.js +80 -78
- package/dist/index.js.map +6 -6
- package/dist/src/lang/runtime.d.ts +14 -0
- package/dist/tjs-full.js +80 -78
- package/dist/tjs-full.js.map +6 -6
- package/package.json +1 -1
- package/src/cli/tjs.ts +1 -1
- package/src/lang/emitters/dts.ts +14 -1
- package/src/lang/emitters/from-ts.ts +4 -2
- package/src/lang/emitters/js.ts +10 -2
- package/src/lang/runtime.ts +39 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tjs-lang",
|
|
3
|
-
"version": "0.6.
|
|
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
package/src/lang/emitters/dts.ts
CHANGED
|
@@ -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' —
|
|
1265
|
+
// 'any' and 'undefined' — preserve original TS body for DTS round-tripping
|
|
1266
1266
|
if (example === 'any' || example === 'undefined') {
|
|
1267
|
-
|
|
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
|
package/src/lang/emitters/js.ts
CHANGED
|
@@ -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
|
package/src/lang/runtime.ts
CHANGED
|
@@ -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
|
/**
|