tjs-lang 0.8.4 → 0.8.5

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/llms.txt CHANGED
@@ -44,6 +44,8 @@ This file is a navigation index for AI agents. It does not contain the docs them
44
44
  - `tjs-lang` → `src/index.ts` — main entry: `Agent`, `AgentVM`, `ajs`, `tjs`.
45
45
  - `tjs-lang/lang` → `src/lang/transpiler.ts` — language tools only: `tjs`, `transpile`.
46
46
  - `tjs-lang/lang/from-ts` → `src/lang/emitters/from-ts.ts` — TypeScript → TJS/JS transpilation.
47
+ - `tjs-lang/browser` → `src/lang/browser.ts` (dist `tjs-browser.js`) — **self-contained** TJS/AJS transpiler (acorn + tosijs-schema inlined); `import()` from any CDN, zero config.
48
+ - `tjs-lang/browser/from-ts` → `src/lang/browser-from-ts.ts` (dist `tjs-browser-from-ts.js`) — browser TS→TJS; lazy-loads the `typescript` compiler from a CDN (default `https://esm.sh/typescript@5` — the only CDN that serves it reliably; jsDelivr `+esm`/esm.run time out). Override with `fromTS(src, { typescriptUrl })` or preload `globalThis.__TJS_TS__`.
47
49
  - `tjs-lang/vm` → `src/vm/index.ts` — VM and atoms.
48
50
  - `tjs-lang/eval` → `src/lang/eval.ts` — `Eval`, `SafeFunction`.
49
51
  - `tjs-lang/batteries` → `src/batteries/index.ts` — LM Studio integration (lazy, local-only).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tjs-lang",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
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",
@@ -33,6 +33,12 @@
33
33
  "lang/from-ts": [
34
34
  "./dist/src/lang/emitters/from-ts.d.ts"
35
35
  ],
36
+ "browser": [
37
+ "./dist/src/lang/browser.d.ts"
38
+ ],
39
+ "browser/from-ts": [
40
+ "./dist/src/lang/browser-from-ts.d.ts"
41
+ ],
36
42
  "vm": [
37
43
  "./dist/src/vm/index.d.ts"
38
44
  ],
@@ -62,6 +68,14 @@
62
68
  "types": "./dist/src/lang/emitters/from-ts.d.ts",
63
69
  "default": "./dist/tjs-from-ts.js"
64
70
  },
71
+ "./browser": {
72
+ "types": "./dist/src/lang/browser.d.ts",
73
+ "default": "./dist/tjs-browser.js"
74
+ },
75
+ "./browser/from-ts": {
76
+ "types": "./dist/src/lang/browser-from-ts.d.ts",
77
+ "default": "./dist/tjs-browser-from-ts.js"
78
+ },
65
79
  "./vm": {
66
80
  "bun": "./src/vm/index.ts",
67
81
  "types": "./dist/src/vm/index.d.ts",
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Guards the browser bundles' defining property: they must be **self-contained**
3
+ * (no bare/node imports) so a single `import('https://cdn/.../tjs-browser.js')`
4
+ * works on any CDN with zero import-map/config. Builds in-test with esbuild, so
5
+ * it doesn't depend on `dist/` existing.
6
+ *
7
+ * CDN reality (verified in a real headless browser, 2026-06): the self-contained
8
+ * TJS/AJS bundle loads from ANY CDN; for the TypeScript path, the compiler is
9
+ * lazy-loaded and **esm.sh is the only CDN that reliably serves `typescript`**
10
+ * (jsDelivr `+esm` / esm.run time out on its ~10MB CJS; skypack is dead).
11
+ */
12
+ import { describe, it, expect } from 'bun:test'
13
+ import { buildSync } from 'esbuild'
14
+ import { DEFAULT_TYPESCRIPT_URL } from './browser-from-ts'
15
+
16
+ const HERE = import.meta.dir
17
+
18
+ function bundle(entry: string, alias?: Record<string, string>): string {
19
+ const r = buildSync({
20
+ entryPoints: [`${HERE}/${entry}`],
21
+ bundle: true,
22
+ format: 'esm',
23
+ platform: 'neutral',
24
+ write: false,
25
+ external: [],
26
+ alias,
27
+ })
28
+ return r.outputFiles[0].text
29
+ }
30
+
31
+ /** Bare (non-relative, non-URL) static + dynamic import specifiers. */
32
+ function bareImports(src: string): string[] {
33
+ const found: string[] = []
34
+ for (const m of src.matchAll(
35
+ /(?:^|[;\n}])import\s*(?:[^"';]*?from)?\s*["']([^"']+)["']/g
36
+ ))
37
+ found.push(m[1])
38
+ for (const m of src.matchAll(/import\(\s*["']([^"']+)["']\s*\)/g))
39
+ found.push(m[1])
40
+ return [...new Set(found)].filter(
41
+ (s) => !s.startsWith('.') && !s.startsWith('http')
42
+ )
43
+ }
44
+
45
+ describe('browser bundles are self-contained (CDN drop-in)', () => {
46
+ it('tjs-browser inlines all deps — no bare/node imports', () => {
47
+ const out = bundle('browser.ts')
48
+ // bareImports([]) already implies no `node:` imports (they'd be bare too);
49
+ // a raw `out.includes('node:')` would false-positive on `{node: ...}` props.
50
+ expect(bareImports(out)).toEqual([])
51
+ // sanity: acorn really got inlined (parser internals present)
52
+ expect(out.length).toBeGreaterThan(100_000)
53
+ })
54
+
55
+ it('tjs-browser-from-ts: no bare imports, TS lazy (not inlined)', () => {
56
+ const out = bundle('browser-from-ts.ts', {
57
+ typescript: `${HERE}/ts-cdn-shim.ts`,
58
+ })
59
+ expect(bareImports(out)).toEqual([])
60
+ // typescript must NOT be bundled in (it's ~10MB) — the shim swaps it out
61
+ expect(out.length).toBeLessThan(300_000)
62
+ // the only runtime dependency is the configurable compiler URL
63
+ expect(out).toContain('esm.sh/typescript')
64
+ })
65
+
66
+ it('default TypeScript CDN is esm.sh (the only one that serves it reliably)', () => {
67
+ expect(DEFAULT_TYPESCRIPT_URL).toBe('https://esm.sh/typescript@5')
68
+ })
69
+ })
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Browser TypeScript→TJS — lazy-loads the TypeScript compiler from a CDN on
3
+ * demand, so the bundle stays small and only pays the (~MB) compiler download
4
+ * when you actually transpile TS. `acorn` + `tosijs-schema` are bundled in; the
5
+ * `typescript` import in `from-ts.ts` is aliased (at build time) to a Proxy that
6
+ * forwards to the lazily-loaded compiler (see `ts-cdn-shim.ts`).
7
+ *
8
+ * const { fromTS } = await import('https://cdn.jsdelivr.net/npm/tjs-lang/dist/tjs-browser-from-ts.js')
9
+ * const { code } = await fromTS('const x: number = 1', { emitTJS: true })
10
+ *
11
+ * Zero config by default. Override the compiler source with `typescriptUrl` (or
12
+ * preload it yourself onto `globalThis.__TJS_TS__`).
13
+ */
14
+ import type { FromTSOptions, FromTSResult } from './emitters/from-ts'
15
+
16
+ /**
17
+ * Default CDN for the TypeScript compiler. **esm.sh** — verified the only CDN
18
+ * that reliably serves `typescript` as ESM (default export = the compiler
19
+ * namespace), in ~700ms. The on-the-fly bundlers choke on typescript's ~10MB
20
+ * CommonJS size: jsDelivr `+esm`, esm.run → timeout; skypack → dead. So esm.sh
21
+ * it is. Override via `BrowserFromTSOptions.typescriptUrl` (e.g. a self-hosted
22
+ * copy) or preload your own compiler onto `globalThis.__TJS_TS__`.
23
+ *
24
+ * NOTE: only the *TypeScript* path depends on this. The TJS/AJS transpiler
25
+ * (`tjs-lang/browser`) is fully self-contained and loads from ANY CDN.
26
+ */
27
+ export const DEFAULT_TYPESCRIPT_URL = 'https://esm.sh/typescript@5'
28
+
29
+ const TS_GLOBAL = '__TJS_TS__'
30
+
31
+ export interface BrowserFromTSOptions extends FromTSOptions {
32
+ /** Override the CDN URL the TypeScript compiler is lazy-loaded from. */
33
+ typescriptUrl?: string
34
+ }
35
+
36
+ let loading: Promise<void> | null = null
37
+
38
+ /** Lazy-load the TypeScript compiler (once) onto `globalThis.__TJS_TS__`. */
39
+ export function loadTypeScript(
40
+ url: string = DEFAULT_TYPESCRIPT_URL
41
+ ): Promise<void> {
42
+ if ((globalThis as any)[TS_GLOBAL]) return Promise.resolve()
43
+ if (!loading) {
44
+ loading = import(/* @vite-ignore */ /* webpackIgnore: true */ url).then(
45
+ (mod) => {
46
+ const ts = mod?.default ?? mod
47
+ if (!ts || typeof ts.createSourceFile !== 'function') {
48
+ loading = null
49
+ throw new Error(
50
+ `Loaded ${url} but it is not a usable TypeScript compiler ` +
51
+ `(no createSourceFile). Try a different typescriptUrl.`
52
+ )
53
+ }
54
+ ;(globalThis as any)[TS_GLOBAL] = ts
55
+ },
56
+ (e) => {
57
+ loading = null
58
+ throw e
59
+ }
60
+ )
61
+ }
62
+ return loading
63
+ }
64
+
65
+ /**
66
+ * Transpile TypeScript → TJS (or JS) in the browser. Lazy-loads the TypeScript
67
+ * compiler on first call, then runs the same `fromTS` logic as Node.
68
+ */
69
+ export async function fromTS(
70
+ source: string,
71
+ options: BrowserFromTSOptions = {}
72
+ ): Promise<FromTSResult> {
73
+ const { typescriptUrl, ...rest } = options
74
+ await loadTypeScript(typescriptUrl)
75
+ // Imported after the compiler is set; from-ts's `typescript` import is aliased
76
+ // to the Proxy shim, which now forwards to the loaded compiler.
77
+ const { fromTS: nodeFromTS } = await import('./emitters/from-ts')
78
+ return nodeFromTS(source, rest)
79
+ }
80
+
81
+ export type { FromTSOptions, FromTSResult } from './emitters/from-ts'
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Self-contained browser entry — the TJS/AJS transpiler with `acorn` and
3
+ * `tosijs-schema` bundled in (no external/bare imports). Build target
4
+ * `tjs-browser` (see `scripts/build.ts`) emits a single ESM file you can
5
+ * `import()` from any CDN with zero import-map / config:
6
+ *
7
+ * const { tjs } = await import('https://cdn.jsdelivr.net/npm/tjs-lang/dist/tjs-browser.js')
8
+ * const { code } = tjs("function greet(name: 'x'): '' { return 'hi' }")
9
+ *
10
+ * For TypeScript→TJS in the browser, use `tjs-lang/browser/from-ts` (which
11
+ * lazy-loads the TypeScript compiler from a CDN on demand).
12
+ */
13
+ export * from './transpiler'
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Build-time stand-in for the `typescript` module, used ONLY by the
3
+ * `tjs-browser-from-ts` bundle (via an esbuild `alias`). `from-ts.ts` does
4
+ * `import ts from 'typescript'` and accesses `ts.<x>` at call time; this Proxy
5
+ * forwards every access to the compiler that `browser-from-ts.ts` lazy-loads
6
+ * from a CDN and stashes on `globalThis.__TJS_TS__`.
7
+ *
8
+ * Because all of from-ts's `ts.<x>` uses are inside functions (run when `fromTS`
9
+ * is called, never at module load), the access is always deferred to after the
10
+ * compiler is loaded — so the swap needs no changes to `from-ts.ts`. tsc still
11
+ * typechecks `from-ts.ts` against the real `typescript` types; only the browser
12
+ * BUILD aliases the runtime to this shim.
13
+ */
14
+ const TS_GLOBAL = '__TJS_TS__'
15
+
16
+ function compiler(): any {
17
+ const ts = (globalThis as any)[TS_GLOBAL]
18
+ if (!ts) {
19
+ throw new Error(
20
+ 'TypeScript not loaded. Use fromTS() from tjs-lang/browser/from-ts, ' +
21
+ 'which lazy-loads the compiler before transpiling.'
22
+ )
23
+ }
24
+ return ts
25
+ }
26
+
27
+ const shim: any = new Proxy(
28
+ {},
29
+ {
30
+ get: (_t, prop) => compiler()[prop],
31
+ has: (_t, prop) => prop in compiler(),
32
+ }
33
+ )
34
+
35
+ export default shim