tjs-lang 0.6.27 → 0.6.31

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/bin/dev.ts CHANGED
@@ -77,8 +77,8 @@ async function buildDemo() {
77
77
  naming: 'tjs-runtime.js',
78
78
  })
79
79
 
80
- // Copy static files
81
- await $`cp demo/index.html demo/static/favicon.svg demo/static/photo-*.jpg tjs-lang.svg .demo/`
80
+ // Copy static files (including TFS service worker — must not be bundled)
81
+ await $`cp demo/index.html demo/static/favicon.svg demo/static/photo-*.jpg tjs-lang.svg demo/src/tfs-worker.js .demo/`
82
82
  await $`cp -r demo/static/texts .demo/`
83
83
 
84
84
  console.log('Build complete!')
@@ -195,6 +195,111 @@ const server = Bun.serve({
195
195
  })
196
196
  }
197
197
 
198
+ // TFS proxy — resolve npm packages from jsdelivr CDN
199
+ // This is the server-side fallback when the service worker can't intercept
200
+ // (e.g. blob iframes, first load before SW is active)
201
+ if (pathname.startsWith('/tfs/')) {
202
+ const tfsPath = pathname.slice(5)
203
+ const CDN_BASE = 'https://cdn.jsdelivr.net/npm'
204
+
205
+ // Parse package@version/subpath
206
+ let name: string, version: string, subpath: string
207
+ if (tfsPath.startsWith('@')) {
208
+ const match = tfsPath.match(/^(@[^/]+\/[^/@]+)(?:@([^/]+))?(\/.*)?$/)
209
+ if (match) {
210
+ name = match[1]
211
+ version = match[2] || 'latest'
212
+ subpath = match[3] || ''
213
+ } else {
214
+ return new Response('invalid tfs path', { status: 400 })
215
+ }
216
+ } else {
217
+ const match = tfsPath.match(/^([^/@]+)(?:@([^/]+))?(\/.*)?$/)
218
+ if (match) {
219
+ name = match[1]
220
+ version = match[2] || 'latest'
221
+ subpath = match[3] || ''
222
+ } else {
223
+ return new Response('invalid tfs path', { status: 400 })
224
+ }
225
+ }
226
+
227
+ try {
228
+ // If no subpath, resolve ESM entry point from package.json
229
+ if (!subpath) {
230
+ const pkgRes = await fetch(
231
+ `${CDN_BASE}/${name}@${version}/package.json`
232
+ )
233
+ if (pkgRes.ok) {
234
+ const pkg = await pkgRes.json()
235
+ const exp = pkg.exports
236
+ let entryPath: string | null = null
237
+
238
+ if (exp) {
239
+ // exports can be { ".": { import: "..." } } or { import: "..." }
240
+ const dot = exp['.'] ?? exp
241
+ if (typeof dot === 'string') entryPath = dot
242
+ else if (dot?.import)
243
+ entryPath =
244
+ typeof dot.import === 'string'
245
+ ? dot.import
246
+ : dot.import?.default
247
+ else if (dot?.default) entryPath = dot.default
248
+ }
249
+ if (!entryPath) entryPath = pkg.module || pkg.main || '/index.js'
250
+ subpath = entryPath!.startsWith('/')
251
+ ? entryPath!
252
+ : entryPath!.startsWith('./')
253
+ ? entryPath!.slice(1)
254
+ : `/${entryPath}`
255
+ }
256
+ }
257
+
258
+ const cdnUrl = `${CDN_BASE}/${name}@${version}${subpath}`
259
+ const cdnRes = await fetch(cdnUrl)
260
+ if (!cdnRes.ok) {
261
+ return new Response(`package not found: ${name}@${version}`, {
262
+ status: 404,
263
+ })
264
+ }
265
+
266
+ let body = await cdnRes.text()
267
+ const origin = new URL(req.url).origin
268
+ const pkgBase = `${CDN_BASE}/${name}@${version}`
269
+
270
+ // Rewrite imports in the fetched module:
271
+ // - Bare specifiers → /tfs/ (transitive deps)
272
+ // - Relative imports → absolute CDN URLs (sibling files)
273
+ body = body.replace(
274
+ /((?:import|export)\s+(?:[\w\s{},*]+\s+from\s+)?)(['"])([^'"]+)\2/g,
275
+ (match: string, prefix: string, quote: string, spec: string) => {
276
+ if (spec.startsWith('http://') || spec.startsWith('https://'))
277
+ return match
278
+ if (spec.startsWith('./') || spec.startsWith('../')) {
279
+ // Relative import → resolve against CDN package path
280
+ const dir = subpath ? subpath.replace(/\/[^/]*$/, '') : '/dist'
281
+ // Add .js extension if missing (CDN requires it)
282
+ const specWithExt = /\.\w+$/.test(spec) ? spec : `${spec}.js`
283
+ const resolved = new URL(specWithExt, `${pkgBase}${dir}/`).href
284
+ return `${prefix}${quote}${resolved}${quote}`
285
+ }
286
+ if (spec.startsWith('/')) return match
287
+ // Bare specifier → route through /tfs/
288
+ return `${prefix}${quote}${origin}/tfs/${spec}${quote}`
289
+ }
290
+ )
291
+
292
+ return new Response(body, {
293
+ headers: {
294
+ 'Content-Type': 'application/javascript',
295
+ 'Access-Control-Allow-Origin': '*',
296
+ },
297
+ })
298
+ } catch (err: any) {
299
+ return new Response(`tfs error: ${err.message}`, { status: 502 })
300
+ }
301
+ }
302
+
198
303
  // For SPA routing, serve index.html for unknown paths
199
304
  const indexFile = Bun.file(join(DOCS_DIR, 'index.html'))
200
305
  if (await indexFile.exists()) {
package/demo/docs.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "type": "example",
116
116
  "group": "basics",
117
117
  "order": 15,
118
- "code": "TjsEquals\n\n/*#\n## The Problem with JavaScript ==\n\nJavaScript's `==` does type coercion, producing surprises:\n\n 0 == '' // true in JS (!)\n false == [] // true in JS (!)\n '' == false // true in JS (!)\n null == 0 // false in JS (but null == undefined is true)\n\nJavaScript's `===` fixes coercion but can't compare values:\n\n new String('hi') === 'hi' // false in JS (different types)\n new Boolean(false) === false // false in JS (object vs primitive)\n\n## TJS Equality (TjsEquals)\n\n`==` becomes **honest equality**: no coercion, but unwraps\nboxed primitives. Fast O(1) — no deep comparison.\n\n`Is` / `IsNot` are **structural equality**: deep comparison\nfor when you explicitly need it. O(n) cost is visible.\n*/\n\n// --- Honest equality (==) fixes coercion ---\nconsole.log('== fixes JS coercion:')\nconsole.log(' 0 == \"\":', 0 == '') // false (JS: true)\nconsole.log(' false == []:', false == []) // false (JS: true)\nconsole.log(' false == \"\":', false == '') // false (JS: true)\nconsole.log(' 1 == \"1\":', 1 == '1') // false (JS: true)\n\n// --- Boxed primitives unwrap ---\nconsole.log('')\nconsole.log('== unwraps boxed primitives:')\nconsole.log(' new String(\"hi\") == \"hi\":', new String('hi') == 'hi') // true\nconsole.log(' new Boolean(false) == false:', new Boolean(false) == false) // true\nconsole.log(' new Number(42) == 42:', new Number(42) == 42) // true\n\n// --- Nullish equality preserved ---\nconsole.log('')\nconsole.log('Nullish equality (useful pattern preserved):')\nconsole.log(' null == undefined:', null == undefined) // true\nconsole.log(' null == 0:', null == 0) // false\nconsole.log(' null == \"\":', null == '') // false\n\n// --- Objects/arrays: reference equality (fast, O(1)) ---\nconsole.log('')\nconsole.log('== on objects/arrays is reference equality (fast):')\nconst obj = {x: 1}\nconsole.log(' obj == obj:', obj == obj) // true (same ref)\nconsole.log(' {x:1} == {x:1}:', {x: 1} == {x: 1}) // false (different refs)\nconsole.log(' [1,2] == [1,2]:', [1, 2] == [1, 2]) // false (different refs)\n\n// --- Is/IsNot: explicit deep structural comparison ---\nconsole.log('')\nconsole.log('Is/IsNot for deep structural comparison (explicit):')\nconsole.log(' {x:1} Is {x:1}:', Is({x: 1}, {x: 1})) // true\nconsole.log(' [1,2,3] Is [1,2,3]:', Is([1,2,3], [1,2,3])) // true\nconsole.log(' [1,2] Is [2,1]:', Is([1,2], [2,1])) // false (order matters)\n\n// Sets compare by membership, not order\nconsole.log(' Set([1,2]) Is Set([2,1]):', Is(new Set([1,2]), new Set([2,1]))) // true\n\n// --- typeof null fixed ---\nconsole.log('')\nconsole.log('typeof null fixed:')\nconsole.log(' typeof null:', typeof null) // 'null' (JS: 'object')\nconsole.log(' typeof undefined:', typeof undefined) // 'undefined'\nconsole.log(' typeof 42:', typeof 42) // 'number' (unchanged)\n\n// --- === unchanged: identity comparison ---\nconsole.log('')\nconsole.log('=== is unchanged (identity):')\nconsole.log(' obj === obj:', obj === obj) // true\nconsole.log(' {x:1} === {x:1}:', {x: 1} === {x: 1}) // false\n\ntest 'typeof null is null, not object' {\n expect(TypeOf(null)).toBe('null')\n expect(TypeOf(undefined)).toBe('undefined')\n expect(TypeOf(42)).toBe('number')\n expect(TypeOf('hi')).toBe('string')\n expect(TypeOf(true)).toBe('boolean')\n expect(TypeOf({})).toBe('object')\n}\n\ntest 'Eq fixes coercion' {\n expect(Eq(0, '')).toBe(false)\n expect(Eq(false, [])).toBe(false)\n expect(Eq('', false)).toBe(false)\n}\n\ntest 'Eq unwraps boxed primitives' {\n expect(Eq(new String('hi'), 'hi')).toBe(true)\n expect(Eq(new Boolean(false), false)).toBe(true)\n expect(Eq(new Number(42), 42)).toBe(true)\n}\n\ntest 'Eq preserves nullish equality' {\n expect(Eq(null, undefined)).toBe(true)\n expect(Eq(null, 0)).toBe(false)\n}\n\ntest 'Is does deep structural comparison' {\n expect(Is({a: 1, b: 2}, {a: 1, b: 2})).toBe(true)\n expect(Is([1, 2, 3], [1, 2, 3])).toBe(true)\n expect(Is([1, 2], [2, 1])).toBe(false)\n}",
118
+ "code": "TjsEquals\n\n/*#\n## The Problem with JavaScript ==\n\nJavaScript's `==` does type coercion, producing surprises:\n\n 0 == '' // true in JS (!)\n false == [] // true in JS (!)\n '' == false // true in JS (!)\n null == 0 // false in JS (but null == undefined is true)\n\nJavaScript's `===` fixes coercion but can't compare values:\n\n new String('hi') === 'hi' // false in JS (different types)\n new Boolean(false) === false // false in JS (object vs primitive)\n\n## TJS Equality (TjsEquals)\n\n`==` becomes **honest equality**: no coercion, but unwraps\nboxed primitives. Fast O(1) — no deep comparison.\n\n`Is` / `IsNot` are **structural equality**: deep comparison\nfor when you explicitly need it. O(n) cost is visible.\n*/\n\n// --- Honest equality (==) fixes coercion ---\nconsole.log('== fixes JS coercion:')\nconsole.log(' [] == ![]:', [] == ![]) // false (JS: true!)\nconsole.log(' 0 == \"\":', 0 == '') // false (JS: true)\nconsole.log(' false == []:', false == []) // false (JS: true)\nconsole.log(' false == \"\":', false == '') // false (JS: true)\nconsole.log(' 1 == \"1\":', 1 == '1') // false (JS: true)\n\n// --- Boxed primitives unwrap ---\nconsole.log('')\nconsole.log('== unwraps boxed primitives:')\nconsole.log(' new String(\"hi\") == \"hi\":', new String('hi') == 'hi') // true\nconsole.log(' new Boolean(false) == false:', new Boolean(false) == false) // true\nconsole.log(' new Number(42) == 42:', new Number(42) == 42) // true\n\n// --- Nullish equality preserved ---\nconsole.log('')\nconsole.log('Nullish equality (useful pattern preserved):')\nconsole.log(' null == undefined:', null == undefined) // true\nconsole.log(' null == 0:', null == 0) // false\nconsole.log(' null == \"\":', null == '') // false\n\n// --- Objects/arrays: reference equality (fast, O(1)) ---\nconsole.log('')\nconsole.log('== on objects/arrays is reference equality (fast):')\nconst obj = {x: 1}\nconsole.log(' obj == obj:', obj == obj) // true (same ref)\nconsole.log(' {x:1} == {x:1}:', {x: 1} == {x: 1}) // false (different refs)\nconsole.log(' [1,2] == [1,2]:', [1, 2] == [1, 2]) // false (different refs)\n\n// --- Is/IsNot: explicit deep structural comparison ---\nconsole.log('')\nconsole.log('Is/IsNot for deep structural comparison (explicit):')\nconsole.log(' {x:1} Is {x:1}:', Is({x: 1}, {x: 1})) // true\nconsole.log(' [1,2,3] Is [1,2,3]:', Is([1,2,3], [1,2,3])) // true\nconsole.log(' [1,2] Is [2,1]:', Is([1,2], [2,1])) // false (order matters)\n\n// Sets compare by membership, not order\nconsole.log(' Set([1,2]) Is Set([2,1]):', Is(new Set([1,2]), new Set([2,1]))) // true\n\n// --- typeof null fixed ---\nconsole.log('')\nconsole.log('typeof null fixed:')\nconsole.log(' typeof null:', typeof null) // 'null' (JS: 'object')\nconsole.log(' typeof undefined:', typeof undefined) // 'undefined'\nconsole.log(' typeof 42:', typeof 42) // 'number' (unchanged)\n\n// --- === unchanged: identity comparison ---\nconsole.log('')\nconsole.log('=== is unchanged (identity):')\nconsole.log(' obj === obj:', obj === obj) // true\nconsole.log(' {x:1} === {x:1}:', {x: 1} === {x: 1}) // false\n\ntest 'typeof null is null, not object' {\n expect(TypeOf(null)).toBe('null')\n expect(TypeOf(undefined)).toBe('undefined')\n expect(TypeOf(42)).toBe('number')\n expect(TypeOf('hi')).toBe('string')\n expect(TypeOf(true)).toBe('boolean')\n expect(TypeOf({})).toBe('object')\n}\n\ntest 'Eq fixes coercion' {\n expect(Eq(0, '')).toBe(false)\n expect(Eq(false, [])).toBe(false)\n expect(Eq('', false)).toBe(false)\n}\n\ntest 'Eq unwraps boxed primitives' {\n expect(Eq(new String('hi'), 'hi')).toBe(true)\n expect(Eq(new Boolean(false), false)).toBe(true)\n expect(Eq(new Number(42), 42)).toBe(true)\n}\n\ntest 'Eq preserves nullish equality' {\n expect(Eq(null, undefined)).toBe(true)\n expect(Eq(null, 0)).toBe(false)\n}\n\ntest 'Is does deep structural comparison' {\n expect(Is({a: 1, b: 2}, {a: 1, b: 2})).toBe(true)\n expect(Is([1, 2, 3], [1, 2, 3])).toBe(true)\n expect(Is([1, 2], [2, 1])).toBe(false)\n}",
119
119
  "language": "tjs",
120
120
  "description": "JavaScript `==` is broken. TJS fixes it without breaking anything."
121
121
  },
@@ -357,6 +357,42 @@
357
357
  "language": "tjs",
358
358
  "description": "Named types with runtime validation. Type, Generic, FunctionPredicate, Enum, Union."
359
359
  },
360
+ {
361
+ "title": "Inline WASM",
362
+ "filename": "wasm-basics.md",
363
+ "path": "guides/examples/tjs/wasm-basics.md",
364
+ "section": "tjs",
365
+ "type": "example",
366
+ "group": "patterns",
367
+ "order": 25,
368
+ "code": "/*#\n## The Problem\n\nUsing WebAssembly in JavaScript requires:\n1. Write WAT or compile from C/Rust\n2. Load a separate .wasm file\n3. Instantiate the module async\n4. Marshal data between JS and WASM heaps\n\nTJS does all of this for you. Write WASM inline using JS-like\nsyntax, it compiles at transpile time and embeds as base64.\n\n## Syntax\n\n function fast(x: 0, y: 0) {\n wasm {\n // JS-like syntax compiled to WASM bytecode\n } fallback {\n // JS fallback if WASM unavailable\n return x + y\n }\n }\n\n // Or with a return value:\n function compute(x: 0) {\n return wasm {\n x * x + 1\n } fallback {\n return x * x + 1\n }\n }\n\nParam types: `i32` (integer), `f32`/`f64` (float), `Float32Array`, etc.\n*/\n\n// --- Basic: integer math in WASM ---\n\nfunction addInts(! a: 0, b: 0) -! 0 {\n return wasm {\n a + b\n } fallback {\n return a + b\n }\n}\n\nfunction factorial(! n: 0) -! 0 {\n return wasm {\n let result = 1\n for (let i = 2; i <= n; i++) {\n result = result * i\n }\n } fallback {\n let result = 1\n for (let i = 2; i <= n; i++) result *= i\n return result\n }\n}\n\n// --- Float math ---\n\nfunction lerp(! a: 0.0, b: 0.0, t: 0.0) -! 0.0 {\n return wasm {\n a + (b - a) * t\n } fallback {\n return a + (b - a) * t\n }\n}\n\n// --- Array processing ---\n\nfunction sumArray(! arr: Float32Array, len: 0) -! 0.0 {\n return wasm {\n let sum = 0.0\n for (let i = 0; i < len; i++) {\n let off = i * 4\n sum = sum + f32x4_extract_lane(f32x4_load(arr, off), 0)\n }\n } fallback {\n let sum = 0\n for (let i = 0; i < len; i++) sum += arr[i]\n return sum\n }\n}\n\nconsole.log('addInts(3, 4):', addInts(3, 4))\nconsole.log('factorial(10):', factorial(10))\nconsole.log('lerp(0, 100, 0.25):', lerp(0.0, 100.0, 0.25))\nconsole.log('sumArray([1,2,3,4]):', sumArray(new Float32Array([1, 2, 3, 4]), 4))",
369
+ "language": "tjs",
370
+ "description": "Write WebAssembly inline — compiled at transpile time, embedded in the output."
371
+ },
372
+ {
373
+ "title": "WASM SIMD",
374
+ "filename": "wasm-simd.md",
375
+ "path": "guides/examples/tjs/wasm-simd.md",
376
+ "section": "tjs",
377
+ "type": "example",
378
+ "group": "patterns",
379
+ "order": 26,
380
+ "code": "/*#\n## SIMD: Single Instruction, Multiple Data\n\nSIMD processes 4 float values per instruction — a 4x throughput\nimprovement for vectorized math. TJS provides SIMD via `f32x4_*`\nintrinsics that compile directly to WASM SIMD opcodes.\n\n### Available Intrinsics\n\n| Intrinsic | Operation |\n|-----------|-----------|\n| `f32x4_load(arr, offset)` | Load 4 floats from array |\n| `f32x4_store(arr, offset, vec)` | Store 4 floats to array |\n| `f32x4_splat(value)` | Fill all 4 lanes with one value |\n| `f32x4_add(a, b)` | Add 4 pairs |\n| `f32x4_sub(a, b)` | Subtract 4 pairs |\n| `f32x4_mul(a, b)` | Multiply 4 pairs |\n| `f32x4_div(a, b)` | Divide 4 pairs |\n| `f32x4_neg(a)` | Negate 4 values |\n| `f32x4_sqrt(a)` | Square root of 4 values |\n| `f32x4_extract_lane(vec, lane)` | Get one float (0-3) |\n| `f32x4_replace_lane(vec, lane, val)` | Set one float |\n*/\n\n// --- Scale an array by a constant (SIMD: 4 elements per step) ---\n\nfunction scale(! arr: Float32Array, len: 0, factor: 0.0) {\n wasm {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n } fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n }\n}\n\n// --- Dot product (sum of element-wise products) ---\n\nfunction dot(! a: Float32Array, b: Float32Array, len: 0) -! 0.0 {\n return wasm {\n let acc = f32x4_splat(0.0)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let va = f32x4_load(a, off)\n let vb = f32x4_load(b, off)\n acc = f32x4_add(acc, f32x4_mul(va, vb))\n }\n // Sum the 4 lanes\n f32x4_extract_lane(acc, 0)\n + f32x4_extract_lane(acc, 1)\n + f32x4_extract_lane(acc, 2)\n + f32x4_extract_lane(acc, 3)\n } fallback {\n let sum = 0\n for (let i = 0; i < len; i++) sum += a[i] * b[i]\n return sum\n }\n}\n\n// --- Demo ---\n\nconst SIZE = 1024\n\n// Create test arrays\nconst arr = new Float32Array(SIZE)\nconst a = new Float32Array(SIZE)\nconst b = new Float32Array(SIZE)\n\nfor (let i = 0; i < SIZE; i++) {\n arr[i] = i + 1\n a[i] = 1.0\n b[i] = 2.0\n}\n\n// Scale\nscale(arr, SIZE, 0.5)\nconsole.log('scale([1..1024], 0.5) first 4:', arr[0], arr[1], arr[2], arr[3])\n\n// Dot product: 1.0 * 2.0 * 1024 = 2048\nconst d = dot(a, b, SIZE)\nconsole.log('dot([1,1,...], [2,2,...], 1024):', d)\n\n// Benchmark\nconst iters = 1000\nconst t0 = performance.now()\nfor (let i = 0; i < iters; i++) scale(arr, SIZE, 1.001)\nconst elapsed = performance.now() - t0\nconsole.log(`${iters} scale ops on ${SIZE} floats: ${elapsed.toFixed(1)}ms`)",
381
+ "language": "tjs",
382
+ "description": "Process 4 floats per instruction. No setup, no toolchain."
383
+ },
384
+ {
385
+ "title": "WASM Memory",
386
+ "filename": "wasm-memory.md",
387
+ "path": "guides/examples/tjs/wasm-memory.md",
388
+ "section": "tjs",
389
+ "type": "example",
390
+ "group": "patterns",
391
+ "order": 27,
392
+ "code": "/*#\n## How Data Moves Between JS and WASM\n\nThe #1 WebAssembly question: \"How do I get my data into WASM?\"\nTJS handles it automatically. Three modes:\n\n### 1. Scalars — pass through\n`i32`, `f32`, `f64` go directly as WASM parameters. No marshaling.\n\n### 2. Regular typed arrays — transparent copy\nPass a normal `Float32Array` and TJS copies it into WASM memory\nbefore the call, then copies results back out after. You don't\nhave to think about it.\n\n### 3. `wasmBuffer()` — zero-copy shared memory\nAllocate directly in WASM memory. Both JS and WASM see the same\nbytes. No copy in, no copy out. Mutations are instantly visible.\n\n const xs = wasmBuffer(Float32Array, 50000)\n xs[0] = 3.14 // JS writes to WASM memory\n wasmFunction(xs) // WASM reads/writes the same memory\n console.log(xs[0]) // JS sees WASM's mutations immediately\n\n### Supported types\n`Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### How it works internally\nAll WASM blocks in a file share one `WebAssembly.Memory` (64MB).\n`wasmBuffer` is a bump allocator — it hands out slices of this memory.\nWhen a typed array argument's `.buffer === wasmMemory.buffer`, the\nwrapper skips the copy and passes the byte offset directly.\n*/\n\n// --- Regular arrays: transparent copy ---\n\nfunction addOne(! arr: Float32Array, len: 0) {\n wasm {\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n let ones = f32x4_splat(1.0)\n f32x4_store(arr, off, f32x4_add(v, ones))\n }\n } fallback {\n for (let i = 0; i < len; i++) arr[i] += 1\n }\n}\n\n// Regular Float32Array — TJS copies in before, copies out after\nconst regular = new Float32Array([10, 20, 30, 40])\naddOne(regular, 4)\nconsole.log('Regular array after WASM:', Array.from(regular))\n// [11, 21, 31, 41] — changes visible in JS\n\n// --- wasmBuffer: zero-copy shared memory ---\n\nconst shared = wasmBuffer(Float32Array, 4)\nshared[0] = 100\nshared[1] = 200\nshared[2] = 300\nshared[3] = 400\n\naddOne(shared, 4)\nconsole.log('wasmBuffer after WASM:', Array.from(shared))\n// [101, 201, 301, 401] — zero copy, same memory\n\n// --- Practical: large array processing ---\n\nconst SIZE = 10000\nconst data = wasmBuffer(Float32Array, SIZE)\nfor (let i = 0; i < SIZE; i++) data[i] = i * 0.01\n\n// Process in WASM — no marshaling overhead\nfunction normalize(! arr: Float32Array, len: 0) {\n wasm {\n // Find max (scalar — SIMD max needs horizontal reduction)\n let max = 0.0\n for (let i = 0; i < len; i++) {\n let off = i * 4\n let v = f32x4_extract_lane(f32x4_load(arr, off), 0)\n if (v > max) { max = v }\n }\n // Scale to [0, 1]\n if (max > 0.0) {\n let inv = f32x4_splat(1.0 / max)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(arr, off, f32x4_mul(f32x4_load(arr, off), inv))\n }\n }\n } fallback {\n let max = 0\n for (let i = 0; i < len; i++) if (arr[i] > max) max = arr[i]\n if (max > 0) for (let i = 0; i < len; i++) arr[i] /= max\n }\n}\n\nconst t0 = performance.now()\nnormalize(data, SIZE)\nconst elapsed = performance.now() - t0\n\nconsole.log(`Normalized ${SIZE} floats in ${elapsed.toFixed(2)}ms`)\nconsole.log('First 4:', data[0].toFixed(4), data[1].toFixed(4), data[2].toFixed(4), data[3].toFixed(4))\nconsole.log('Last:', data[SIZE - 1].toFixed(4))",
393
+ "language": "tjs",
394
+ "description": "Zero-copy arrays and automatic data marshaling between JS and WASM."
395
+ },
360
396
  {
361
397
  "title": "React Todo (Comparison)",
362
398
  "filename": "react-todo-comparison.md",
@@ -1,206 +1,91 @@
1
1
  /**
2
- * Unit tests for import resolution infrastructure
3
- *
4
- * Tests the synchronous functions that don't require browser/network.
2
+ * Unit tests for TFS import resolution
5
3
  */
6
4
 
7
5
  import { describe, it, expect } from 'bun:test'
8
- import {
9
- extractImports,
10
- getCDNUrl,
11
- generateImportMap,
12
- generateImportMapScript,
13
- wrapAsModule,
14
- clearModuleCache,
15
- getCacheStats,
16
- } from './imports'
6
+ import { extractImports, rewriteImports } from './imports'
17
7
 
18
8
  describe('extractImports', () => {
19
9
  it('should extract named imports', () => {
20
- const source = `import { foo, bar } from 'some-package'`
21
- expect(extractImports(source)).toEqual(['some-package'])
10
+ expect(extractImports(`import { foo } from 'pkg'`)).toEqual(['pkg'])
22
11
  })
23
12
 
24
13
  it('should extract default imports', () => {
25
- const source = `import React from 'react'`
26
- expect(extractImports(source)).toEqual(['react'])
27
- })
28
-
29
- it('should extract namespace imports', () => {
30
- const source = `import * as lodash from 'lodash'`
31
- expect(extractImports(source)).toEqual(['lodash'])
32
- })
33
-
34
- it('should extract side-effect imports', () => {
35
- const source = `import 'polyfill'`
36
- expect(extractImports(source)).toEqual(['polyfill'])
37
- })
38
-
39
- it('should extract re-exports', () => {
40
- const source = `export { foo } from 'some-package'`
41
- expect(extractImports(source)).toEqual(['some-package'])
42
- })
43
-
44
- it('should handle multiple imports', () => {
45
- const source = `
46
- import { add } from 'lodash'
47
- import { format } from 'date-fns'
48
- import React from 'react'
49
- `
50
- expect(extractImports(source)).toEqual(['lodash', 'date-fns', 'react'])
51
- })
52
-
53
- it('should deduplicate imports', () => {
54
- const source = `
55
- import { add } from 'lodash'
56
- import { subtract } from 'lodash'
57
- `
58
- expect(extractImports(source)).toEqual(['lodash'])
14
+ expect(extractImports(`import React from 'react'`)).toEqual(['react'])
59
15
  })
60
16
 
61
17
  it('should ignore relative imports', () => {
62
- const source = `
63
- import { foo } from './local'
64
- import { bar } from '../parent'
65
- import { baz } from '/absolute'
66
- import { qux } from 'npm-package'
67
- `
68
- expect(extractImports(source)).toEqual(['npm-package'])
69
- })
70
-
71
- it('should handle subpath imports', () => {
72
- const source = `import { debounce } from 'lodash/debounce'`
73
- expect(extractImports(source)).toEqual(['lodash/debounce'])
74
- })
75
-
76
- it('should handle scoped packages', () => {
77
- const source = `import { something } from '@scope/package'`
78
- expect(extractImports(source)).toEqual(['@scope/package'])
18
+ expect(
19
+ extractImports(`import { a } from './local'\nimport { b } from 'pkg'`)
20
+ ).toEqual(['pkg'])
79
21
  })
80
22
 
81
- it('should handle scoped packages with subpaths', () => {
82
- const source = `import { util } from '@scope/package/utils'`
83
- expect(extractImports(source)).toEqual(['@scope/package/utils'])
23
+ it('should handle versioned specifiers', () => {
24
+ expect(extractImports(`import { x } from 'tosijs@1.3.11'`)).toEqual([
25
+ 'tosijs@1.3.11',
26
+ ])
84
27
  })
85
28
 
86
- it('should handle double quotes', () => {
87
- const source = `import { foo } from "some-package"`
88
- expect(extractImports(source)).toEqual(['some-package'])
89
- })
90
-
91
- it('should return empty array for no imports', () => {
92
- const source = `const x = 1; function foo() { return x }`
93
- expect(extractImports(source)).toEqual([])
29
+ it('should deduplicate', () => {
30
+ expect(
31
+ extractImports(`import { a } from 'pkg'\nimport { b } from 'pkg'`)
32
+ ).toEqual(['pkg'])
94
33
  })
95
34
  })
96
35
 
97
- describe('getCDNUrl', () => {
98
- it('should generate URL for simple package', () => {
99
- expect(getCDNUrl('some-unknown-package')).toBe(
100
- 'https://cdn.jsdelivr.net/npm/some-unknown-package'
36
+ describe('rewriteImports', () => {
37
+ it('should rewrite bare specifiers to /tfs/', () => {
38
+ expect(rewriteImports(`import { foo } from 'tosijs'`)).toBe(
39
+ `import { foo } from '/tfs/tosijs'`
101
40
  )
102
41
  })
103
42
 
104
- it('should use pinned version and path for known packages', () => {
105
- // tosijs has pinned version and path
106
- expect(getCDNUrl('tosijs')).toBe(
107
- 'https://cdn.jsdelivr.net/npm/tosijs@1.2.0/dist/module.js'
108
- )
109
- // date-fns has pinned version but no path
110
- expect(getCDNUrl('date-fns')).toBe(
111
- 'https://cdn.jsdelivr.net/npm/date-fns@3.6.0'
43
+ it('should handle versioned specifiers', () => {
44
+ expect(rewriteImports(`import { x } from 'tosijs@1.3.11'`)).toBe(
45
+ `import { x } from '/tfs/tosijs@1.3.11'`
112
46
  )
113
47
  })
114
48
 
115
- it('should handle subpath imports with pinned version', () => {
116
- expect(getCDNUrl('lodash-es/debounce')).toBe(
117
- 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/debounce'
118
- )
49
+ it('should handle subpath imports', () => {
50
+ expect(
51
+ rewriteImports(`import { debounce } from 'lodash-es/debounce'`)
52
+ ).toBe(`import { debounce } from '/tfs/lodash-es/debounce'`)
119
53
  })
120
54
 
121
55
  it('should handle scoped packages', () => {
122
- expect(getCDNUrl('@scope/package')).toBe(
123
- 'https://cdn.jsdelivr.net/npm/@scope/package'
56
+ expect(rewriteImports(`import { x } from '@scope/pkg'`)).toBe(
57
+ `import { x } from '/tfs/@scope/pkg'`
124
58
  )
125
59
  })
126
60
 
127
- it('should handle scoped packages with subpaths', () => {
128
- expect(getCDNUrl('@scope/package/utils')).toBe(
129
- 'https://cdn.jsdelivr.net/npm/@scope/package/utils'
61
+ it('should not rewrite relative imports', () => {
62
+ expect(rewriteImports(`import { x } from './local'`)).toBe(
63
+ `import { x } from './local'`
130
64
  )
131
65
  })
132
66
 
133
- it('should handle packages with pinned version but no path', () => {
134
- // lodash-es is pinned but has no explicit path
135
- expect(getCDNUrl('lodash-es')).toBe(
136
- 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21'
67
+ it('should not rewrite absolute imports', () => {
68
+ expect(rewriteImports(`import { x } from '/abs/path'`)).toBe(
69
+ `import { x } from '/abs/path'`
137
70
  )
138
71
  })
139
- })
140
-
141
- describe('generateImportMap', () => {
142
- it('should generate import map for specifiers', () => {
143
- const result = generateImportMap(['tosijs', 'date-fns'])
144
- expect(result).toEqual({
145
- imports: {
146
- tosijs: 'https://cdn.jsdelivr.net/npm/tosijs@1.2.0/dist/module.js',
147
- 'date-fns': 'https://cdn.jsdelivr.net/npm/date-fns@3.6.0',
148
- },
149
- })
150
- })
151
72
 
152
- it('should handle empty array', () => {
153
- expect(generateImportMap([])).toEqual({ imports: {} })
73
+ it('should not rewrite http imports', () => {
74
+ expect(
75
+ rewriteImports(`import { x } from 'https://cdn.example.com/pkg.js'`)
76
+ ).toBe(`import { x } from 'https://cdn.example.com/pkg.js'`)
154
77
  })
155
78
 
156
- it('should handle subpath imports', () => {
157
- const result = generateImportMap(['lodash-es/debounce'])
158
- expect(result.imports['lodash-es/debounce']).toBe(
159
- 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/debounce'
160
- )
79
+ it('should handle multiple imports', () => {
80
+ const source = `import { a } from 'pkg-a'\nimport { b } from 'pkg-b'`
81
+ const result = rewriteImports(source)
82
+ expect(result).toContain("from '/tfs/pkg-a'")
83
+ expect(result).toContain("from '/tfs/pkg-b'")
161
84
  })
162
- })
163
-
164
- describe('generateImportMapScript', () => {
165
- it('should generate script tag with import map', () => {
166
- const importMap = {
167
- imports: {
168
- tosijs: 'https://cdn.jsdelivr.net/npm/tosijs@1.0.10/dist/module.js',
169
- },
170
- }
171
- const script = generateImportMapScript(importMap)
172
85
 
173
- expect(script).toContain('<script type="importmap">')
174
- expect(script).toContain('</script>')
175
- expect(script).toContain('"tosijs"')
176
- expect(script).toContain(
177
- 'https://cdn.jsdelivr.net/npm/tosijs@1.0.10/dist/module.js'
86
+ it('should handle re-exports', () => {
87
+ expect(rewriteImports(`export { foo } from 'pkg'`)).toBe(
88
+ `export { foo } from '/tfs/pkg'`
178
89
  )
179
90
  })
180
91
  })
181
-
182
- describe('wrapAsModule', () => {
183
- it('should wrap code in module script tag', () => {
184
- const code = 'console.log("hello")'
185
- const wrapped = wrapAsModule(code)
186
-
187
- expect(wrapped).toContain('<script type="module">')
188
- expect(wrapped).toContain('</script>')
189
- expect(wrapped).toContain(code)
190
- })
191
- })
192
-
193
- describe('module cache', () => {
194
- it('should start empty', () => {
195
- clearModuleCache()
196
- const stats = getCacheStats()
197
- expect(stats.size).toBe(0)
198
- expect(stats.entries).toEqual([])
199
- })
200
-
201
- it('should clear cache', () => {
202
- // Just verify it doesn't throw
203
- clearModuleCache()
204
- expect(getCacheStats().size).toBe(0)
205
- })
206
- })