{"_id":"@aeye/gin","_rev":"9-fc7041ca1e09931e49ea57c70563875b","name":"@aeye/gin","dist-tags":{"latest":"0.4.4"},"versions":{"0.3.9":{"name":"@aeye/gin","version":"0.3.9","license":"GPL-3.0","_id":"@aeye/gin@0.3.9","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"e19d540b1471d0f0f1a7818b9c889c152b3896ca","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.3.9.tgz","fileCount":9,"integrity":"sha512-jDe/lvK6AITKX6yttn2B9ACKWoRpIqRgr6Fao3zAbF8RrMCaWQDj5wdjsasuOCpcaIGS+bVvdQ7wEey9YkSABw==","signatures":[{"sig":"MEYCIQDTD5woopgTqk4QiFPNMMvLsrD+ksvYy4k11WpxpoaGigIhAMQxqCiQ9LygMBLOoe38C5YT6wHWT2+SqAv8FnJXlFNi","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":655791},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"7df3bae899c5a30cf17f4925fb7057e9a61e7332","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.3.9_1783858097850_0.7672501401357918","host":"s3://npm-registry-packages-npm-production"}},"0.3.10":{"name":"@aeye/gin","version":"0.3.10","license":"GPL-3.0","_id":"@aeye/gin@0.3.10","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"5883a5fb9e233b3e3e95b614bc05236db3ab1fd5","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.3.10.tgz","fileCount":9,"integrity":"sha512-wE8kNaDcJ7yLWMiEBbZQpi3oRqB2Z8vzxEzTuya0gpFhfsTtO9ZPftH1g+R+UoITlM47pFfKkI8IppDh7puFeA==","signatures":[{"sig":"MEQCIDcIS5Q4oFbjPvrDw4Mys+c3xDmugXKL82R5u5DwRwIUAiAuv66zhc3pynGmskI5Z1GNdB8eMLc9mOckZnb1N+Wl4A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":668549},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"5c2e0bb3b9df195e5ccb687573df26a63326e9ef","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.3.10_1786149167615_0.7015079430922315","host":"s3://npm-registry-packages-npm-production"}},"0.3.11":{"name":"@aeye/gin","version":"0.3.11","license":"GPL-3.0","_id":"@aeye/gin@0.3.11","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"e42af52710065021b271c71d3a918df148d2bfb2","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.3.11.tgz","fileCount":9,"integrity":"sha512-/T/3fHSj2s8owJsDaCzeICqr5gE37OmudHRqKqTcUFUzTgFX2j6Q4MMq7ZdbXc6tg9PpvNrQ0sRSyNogK2bFRA==","signatures":[{"sig":"MEYCIQCIxEjChiufJWAAIO9bjeFebSF94iszhwobL7MqtdttWwIhAKL5oI86YUrtooH7z3MIq/ioN+UgpwyXdai1dbLsR5dZ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":689272},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"ba19fe8e8b03856e1dce8a61f7458ac7a4d89d13","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.3.11_1786202262150_0.9823329353703951","host":"s3://npm-registry-packages-npm-production"}},"0.3.12":{"name":"@aeye/gin","version":"0.3.12","license":"GPL-3.0","_id":"@aeye/gin@0.3.12","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"ec3b0eec42dad138c6825127812134438553d232","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.3.12.tgz","fileCount":9,"integrity":"sha512-f12Nf8Fle0j0dMjPBSo/uBoZiss+PH2FNTw/kXiskFHWoMht9UuCjppzc9mHotSRaxKjEoeT4t40bH84XmvYJA==","signatures":[{"sig":"MEUCIQD5XL68gU0h+gPUCiYzAvGwKEMD6o5buTHHqW/6EL1LrgIgKcAnrGlNX0zwuZcnn4NIAUM1iD+Qoo+GajMi+y4w9m4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":718946},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"8d7ef0aabcc20186066db0a84f9f979a8539b5e4","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.3.12_1786664233503_0.44851431366085137","host":"s3://npm-registry-packages-npm-production"}},"0.3.13":{"name":"@aeye/gin","version":"0.3.13","license":"GPL-3.0","_id":"@aeye/gin@0.3.13","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"06a6e1da977ed2e829bcd73a78af81dbbd5ae140","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.3.13.tgz","fileCount":9,"integrity":"sha512-gXCa8MkLhWh3bmBqiQHq24EjeX3kEIvD4CuR2LzQhxS40AJ7BylR2HKknqhrmEIUOjs0OFfpo+LabcHAuHPG8w==","signatures":[{"sig":"MEUCIBddwrBqtHJrpcTrdv9khbJQLvwMxjMjuwHS+S4MlM/WAiEA7JDq8aERfk0/aSOV8YvalR/C4WTP/DqmY/eiRw1o3aU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":725093},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"0313eeecb06aabc1eaf7cfd873ebc53220367142","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.3.13_1786717176032_0.671302544758464","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@aeye/gin","version":"0.4.0","license":"GPL-3.0","_id":"@aeye/gin@0.4.0","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"227fd86371c186c33831639ece040e6e9ca85cf7","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.4.0.tgz","fileCount":9,"integrity":"sha512-6qcrvlJCh4t76+J2ouvlALM05wAvdARIk2nhL8oZbvxkuC95FN0lfML5THYMjEZK54BAd+Ojo25R/1F1nDgN1A==","signatures":[{"sig":"MEUCIQDD1oWqPxR55AIiCKTttP1dhP4CcK4B8F/iZ3XVcfq9XAIgalDr84lS9JJM0RDTYXmqaRlOFaQ/3oMTvhakkKVYDdw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":752722},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"4b19b4756c15c2f465a95072b2696a2942a81332","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.4.0_1786757108838_0.7305144769931569","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@aeye/gin","version":"0.4.1","license":"GPL-3.0","_id":"@aeye/gin@0.4.1","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"6e101c717505a1944f52a62174da1388974afb93","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.4.1.tgz","fileCount":9,"integrity":"sha512-OSFLOWCf93LbSUyBcXhJI412vTb/P25WcwqX88UpomYPIb4bhOppv/zEDgvEFOD/w4TusxjmDPN8EVYxSO0KwQ==","signatures":[{"sig":"MEUCIFqQst2AoAsoazN0PTppyEYMmm5LYOusAVcG5H49i3AJAiEAgY5/D9s1mZ8R1Ydg98E7ZOn4tGHqX2YJRj2wgf6FOX0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":808492},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"84eb6cba3a2237d92f6830ebf7a831626014e360","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.4.1_1786900203355_0.5180139223661422","host":"s3://npm-registry-packages-npm-production"}},"0.4.2":{"name":"@aeye/gin","version":"0.4.2","license":"GPL-3.0","_id":"@aeye/gin@0.4.2","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"dist":{"shasum":"64f53486fba35a8b1be3d332715ebc6801595cd5","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.4.2.tgz","fileCount":9,"integrity":"sha512-53/bHC/JZHi8I0ufRejeTM0UzlAA7HgLsWkfVTQhR5kMkjG5HjeKGLTQ7ko/zatiZFWZQmhdfZdd7XlRtfYzTA==","signatures":[{"sig":"MEQCIDzhyP7a04C+EVKKkU/TxmNhSn3Kmk0qrbFzFM6yC4aSAiAWWysClFQmnT0NeABvnO94AL0zuRNwmuMe3AcnSVlQMg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":821772},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"source":"./src/index.ts","default":"./dist/index.js"}},"gitHead":"6bede4ec0ec7eb50b69e7c9e5bb5c73acf036eb4","scripts":{"test":"vitest run","build":"tsup src/index.ts --format esm --dts","clean":"rimraf dist","dump-code":"tsx scripts/dump-code.ts","typecheck":"tsc --noEmit","test:watch":"vitest","dump-schema":"tsx scripts/dump-schema.ts","integration":"tsx integration/run.ts","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","integration:check":"tsx integration/run.ts --check"},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"_npmVersion":"10.9.8","description":"Gin - A type & expression system for LLM agents","directories":{},"_nodeVersion":"22.22.3","dependencies":{"zod":"^4.1.12"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","tsup":"^8.0.0","vitest":"^3.0.0","@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","typescript":"^5.9.3","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9"},"_npmOperationalInternal":{"tmp":"tmp/gin_0.4.2_1786906288621_0.6503252402793254","host":"s3://npm-registry-packages-npm-production"}},"0.4.4":{"name":"@aeye/gin","version":"0.4.4","description":"Gin - A type & expression system for LLM agents","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"source":"./src/index.ts","import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"default":"./dist/index.js"}},"scripts":{"build":"tsup src/index.ts --format esm --dts","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","dump-schema":"tsx scripts/dump-schema.ts","dump-code":"tsx scripts/dump-code.ts","integration":"tsx integration/run.ts","integration:check":"tsx integration/run.ts --check","clean":"rimraf dist","prepublishOnly":"npm run build"},"dependencies":{"zod":"^4.1.12"},"devDependencies":{"@aeye/ai":"^0.3.9","@aeye/core":"^0.3.9","@aeye/models":"^0.3.9","@aeye/openrouter":"^0.3.9","tsup":"^8.0.0","tsx":"^4.19.0","vitest":"^3.0.0","typescript":"^5.9.3"},"license":"GPL-3.0","_id":"@aeye/gin@0.4.4","gitHead":"a746b8bab9ef4bb62c178e008aeaf88c6e8a1f88","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-LAEy4/m2NFyH6ciBZatGfkfPE1zPVJa4WMcMgTupiiWXL5Btv0Q//Io9UMTZsGF0PUm5TIRLmaHZpsKvMcLwqQ==","shasum":"b39bfd4f6f2b2df6e0d57d9df3fd92c0bc997a60","tarball":"https://registry.npmjs.org/@aeye/gin/-/gin-0.4.4.tgz","fileCount":307,"unpackedSize":2472171,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICo+UUMkiYzNJd8JP8aYL3kfVAFGXeBhyAnCiahlUAsdAiEAwHfEsOBCFtcqK1aTi/5a4fztbuu2RkzPJ1HPe63rueo="}]},"_npmUser":{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"},"directories":{},"maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/gin_0.4.4_1788092957184_0.6545595264238271"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-12T12:08:17.741Z","modified":"2026-08-30T12:29:17.565Z","0.3.9":"2026-07-12T12:08:18.054Z","0.3.10":"2026-08-08T00:32:47.769Z","0.3.11":"2026-08-08T15:17:42.396Z","0.3.12":"2026-08-13T23:37:13.671Z","0.3.13":"2026-08-14T14:19:36.204Z","0.4.0":"2026-08-15T01:25:09.006Z","0.4.1":"2026-08-16T17:10:03.494Z","0.4.2":"2026-08-16T18:51:28.793Z","0.4.4":"2026-08-30T12:29:17.384Z"},"license":"GPL-3.0","description":"Gin - A type & expression system for LLM agents","maintainers":[{"name":"philip.diffenderfer","email":"pdiffenderfer@gmail.com"}],"readme":"# gin\r\n\r\n> A JSON-based programming language and type system designed for LLMs to author, validate, and execute typed programs at runtime.\r\n\r\n`gin` gives an LLM a real type system with proper generics, structural\r\ncompatibility, and extension-based inheritance — plus an expression\r\nlanguage serialized as plain JSON. Programs survive round-trips through\r\n`JSON.stringify` / `JSON.parse`, can be introspected and validated\r\nwithout running them, and can be executed in-process against a\r\npluggable registry of native functions.\r\n\r\n```bash\r\nnpm install @aeye/gin zod\r\n```\r\n\r\n---\r\n\r\n## The type system\r\n\r\nEvery Type — built-in or developer-defined — exposes up to four\r\nsurfaces. These are the only knobs you have for shaping runtime\r\nbehavior:\r\n\r\n### `props` — named methods and fields\r\n\r\nA type's `props` map is the static surface accessed by name. Each prop\r\nis one of:\r\n\r\n- A **value-typed prop** — `length: num` on `text`, `r: num` on `color`.\r\n  Read by walking a path step `{prop: 'length'}`.\r\n- A **method** — `add(other: num): num` on `num`, `slice(start, end?): text`\r\n  on `text`. A method is just a prop whose type is a `function` —\r\n  invoking it via `[{prop: 'add'}, {args: {other: 3}}]` runs the\r\n  underlying expression / native.\r\n\r\nThe same path step `{prop: 'name'}` works for both — a method just has\r\na callable type, so you follow it with a `{args: ...}` step.\r\n\r\n### `get` — keyed access (and looping)\r\n\r\nWhen a type defines `get`, it supports `[key]` access. The `GetSet`\r\nspec carries:\r\n\r\n- `key` — the type a key must satisfy (`num` for lists, the field-name\r\n  union for `obj`, `text` for `map<text, V>`, ...).\r\n- `value` — what indexed access produces.\r\n- Optional `loop` expression — drives `loop` iteration. When present, the\r\n  type is iterable via `{kind: 'loop', over: <this value>, body: ...}`.\r\n  The loop expression runs with `this` (the iterable) and `yield` (a\r\n  callable taking `{key, value}`) bound in scope, and calls `yield`\r\n  once per pair. Native loops live in `gin/src/natives/*.ts`; you can\r\n  register custom ones via augmentation.\r\n- Optional `loopDynamic: true` — flags while-loop semantics (the\r\n  `over` expression is re-evaluated each iteration). `bool` uses this.\r\n\r\n### `call` — make the type callable\r\n\r\nWhen a type defines `call`, values of that type can be invoked. The\r\n`Call` spec carries `args` (an obj-shaped type), `returns` (the\r\nresult type), optional `throws`, and optional `get`/`set` expressions\r\nthat implement the call. `function` is the obvious example, but\r\naugmentation can make any type callable.\r\n\r\n### `init` — constructor for `new`\r\n\r\nWhen `init` is defined, `{kind: 'new', type: T, value: <args>}` parses\r\n`<args>` against `init.args` and runs `init.run` with `{this, args}`\r\nin scope — `this` is a default-constructed value and `args` is the\r\nparsed input. The expression returns either a fresh value (if the run\r\nreturns one) or the mutated `this`. Without `init`, `new T(value)`\r\njust runs `T.parse(value)` directly. `duration` and `color` ship with\r\ninit defined; the LLM authors `new color({r: 255, g: 0, b: 0})` and\r\nthe constructor packs the channels into a 32-bit integer.\r\n\r\nThe `value` slot of a `new` expression automatically reflects\r\n`init.args` in the LLM-facing schema — devs don't write per-type\r\n`toNewSchema` overrides for that case.\r\n\r\n---\r\n\r\n## Generics\r\n\r\nA type can declare `generic` parameters — each entry's value is a\r\n**constraint**, not a default. Bare `{name: 'R'}` inside the\r\nsignature is an unresolved placeholder (gin's `AliasType`); concrete\r\nresolution happens when a call site supplies a binding.\r\n\r\n- `R: any` — no constraint. Any type accepted as a binding.\r\n- `R: text | obj` — bindings must be assignable to `text | obj`.\r\n  Anything else is rejected at the call site with a clear error.\r\n- `R: <interface>` — structural constraint. Bindings must satisfy\r\n  the interface (every prop / get / call the interface declares\r\n  exists on the binding with a compatible type).\r\n- `R: alias('R')` — self-reference. Equivalent to \"no constraint\";\r\n  the satisfies check is skipped.\r\n\r\nBindings are validated when a `CallStep` provides them. There is no\r\nimplicit default — if you don't bind, the parameter stays a\r\nplaceholder and downstream type checks against it are permissive.\r\n\r\nGenerics show up natively in fn types (`<R: ...>(args): R`), in\r\nparameterized types (`list<V>`, `map<K, V>`, `optional<T>`), and on\r\nmethods that introduce their own type parameters (`list.map<R>(fn): list<R>`).\r\n\r\n---\r\n\r\n## Type compatibility\r\n\r\n`a.compatible(b)` answers \"every value of b is also a valid value of\r\na\" — i.e. `b` is assignable to `a`. Used by:\r\n\r\n- **path validation** — a method call's args must be compatible with\r\n  the called fn's args type.\r\n- **structural interface satisfaction** — does this object have all\r\n  the props an interface requires?\r\n- **edit safety** — can this new type definition replace the old one\r\n  without breaking callers? Check both directions.\r\n\r\nFor obj specifically: `a.compatible(b)` requires every required\r\nfield of `a` to exist on `b` with a compatible per-field type.\r\nOptional fields on `a` may be absent from `b` (the missing field\r\ndefaults to undefined, which optional accepts). Extra fields on `b`\r\nare ignored. `opts.exact` tightens this to exact field-set match.\r\n\r\nFor fn: bivariant on args (matches TypeScript's default method-arg\r\nrule), covariant on returns. Most code wants the bivariant form;\r\nedit-compat tooling splits args + returns and checks each side\r\ndirectionally to enforce strict TS-style variance.\r\n\r\nAn `or` on the RIGHT is descended for every type: `a.compatible(or<X,\r\nY>)` is `a.compatible(X) && a.compatible(Y)`, so `num.compatible(or<num,\r\nnum>)` is true and `num.compatible(or<num, text>)` is false. Concrete\r\ntypes implement `compatibleType` — the per-class half, which never sees\r\na union; `compatible` is the base-class method everything calls.\r\n\r\n---\r\n\r\n## Extensions\r\n\r\n`registry.extend(base, { name, ... })` creates a named type that\r\noverlays additions on a base. Extensions can:\r\n\r\n- **Add props** — new fields and methods.\r\n- **Override `get` / `call` / `init`** — replace any of the base's\r\n  surfaces.\r\n- **Narrow options** — `Email` extending `text({pattern: ...})`\r\n  carries the tighter pattern at runtime.\r\n- **Add a constraint Expr** — a runtime predicate every value must\r\n  satisfy. Evaluated on `engine.validateValue(v)`; runs with `this`\r\n  bound to the value.\r\n- **Declare `generic`** — extensions can have their own type params.\r\n\r\nExtensions delegate everything to the base via `Type.compatible`,\r\n`Type.props` composition, etc. `Email extends text` is a real\r\nsubtype: every Email is a valid text; tighter tests pass on\r\nEmail-only values.\r\n\r\n---\r\n\r\n## Augmentations\r\n\r\n`registry.augment(name, { props?, get?, call?, init? })` adds to an\r\nEXISTING type by name — works for built-ins (`'num'`, `'text'`,\r\n`'date'`, `'timestamp'`, ...) and registered named types. Augmentation\r\nis gentler than extension:\r\n\r\n- `props` are MERGED into the type's existing props. Intrinsic names\r\n  win on conflict — you can't override `num.add` by augmenting num.\r\n- `get` / `call` / `init` are applied IFF the type has none of its\r\n  own. Augmentation FILLS GAPS — give `date` a `get` so it iterates,\r\n  make `timestamp` callable, give `text` a constructor — but never\r\n  overrides what's already there.\r\n\r\nThe augmented surface flows through every consumer: path-walks\r\ndispatch against augmented props; static analysis sees them; code\r\nrendering shows them — in the `toCodeDefinition` body of a built-in\r\nAND of a named Extension, alongside that Extension's own members\r\n(0.3.13 fixed the Extension arm, which used to drop them from the\r\nprint). No subclassing or wrapper required.\r\n\r\nAugmentation is REGISTRY-SIDE surface, never wire shape: it stays out\r\nof `toJSON()` and out of `toValueSchema()` / `toNewSchema()`. So it is\r\nthe slot for methods on a type whose VALUE contract must stay closed —\r\na `resource` handle that must keep parsing from a bare `{ id }` gets\r\nits `markdown()` / `url()` here, not as local props.\r\n\r\nWhen you want to genuinely REPLACE behavior (not just add), use an\r\nExtension — extensions own their entire surface and can override\r\nfreely.\r\n\r\n---\r\n\r\n## The 12 expression kinds\r\n\r\nA gin program is a tree of `Expr` JSON objects. Every node has\r\n`kind: '...'` plus the fields that kind declares.\r\n\r\n### `new` — construct a value of a given type\r\n\r\n`{ kind: 'new', type: <TypeDef>, value?: <raw or args> }`\r\n\r\nIf the type has `init`, `value` is parsed as `init.args` and the\r\nconstructor runs. Otherwise `value` is parsed as `type` directly. With\r\nno `value`, returns `Value(type, type.create())` — the type's default.\r\n`create()` honours the type's own constraints, so `T.parse(T.create())`\r\nsucceeds for every inhabitable `T` (`num{max:-3}.create()` is `-3`,\r\n`text{minLength:2}.create()` is two chars, `and<num,num{min=3}>` is `3`).\r\nThe exceptions are uninhabitable types (`not<any>`, `and<num,text>`) and\r\nconstraints with no derivable witness (a regex `pattern`, a `fn` value).\r\n\r\n### `get` — read through a path\r\n\r\n`{ kind: 'get', path: [<step>, <step>, ...] }`\r\n\r\nSteps walk left-to-right. Each step is `{prop: 'name'}` (named\r\naccess), `{args: {...}}` (call the previous step — used after a\r\nmethod or any callable), or `{key: <Expr>}` (indexed access). The\r\nfirst step is always `{prop: '<scopeVar>'}`. Result is the final\r\nstep's value. A step names exactly ONE of those forms — a call is its\r\nown step, and the fused `{prop, args}` spelling is an error.\r\n\r\n### `set` — write through a path\r\n\r\n`{ kind: 'set', path: [<step>, ...], value: <Expr> }`\r\n\r\nSame path grammar as `get`, but the tail step writes. Returns `bool`:\r\ntrue on success, false if a safe-nav null/undefined short-circuited\r\nthe walk.\r\n\r\n### `define` — bind locals into a child scope\r\n\r\n`{ kind: 'define', vars: [{ name, type?, value }, ...], body: <Expr> }`\r\n\r\nEach var is added to scope BEFORE the next var's value is evaluated,\r\nso later vars can reference earlier ones. The body runs with all\r\nvars in scope; its result is the define's value.\r\n\r\n### `block` — sequence of expressions\r\n\r\n`{ kind: 'block', lines: [<Expr>, ...] }`\r\n\r\nLines run in order. Earlier lines are evaluated for their side\r\neffects (set, native calls, fns); the block's value is the LAST\r\nline's value. An empty block returns void.\r\n\r\n### `if` — conditional branching\r\n\r\n`{ kind: 'if', ifs: [{ condition, body }, ...], else?: <Expr> }`\r\n\r\nEach condition must be `bool`-typed. First branch whose condition is\r\ntrue wins. Without an else, a no-match if-expression returns void.\r\n\r\n### `switch` — value-based branching\r\n\r\n`{ kind: 'switch', value: <Expr>, cases: [{ equals: [<Expr>...], body }], else?: <Expr> }`\r\n\r\nThe case wins if `value` equals ANY one of `equals`. Cases are NOT\r\nfall-through; only the matching case's body runs.\r\n\r\n### `loop` — iterate any iterable\r\n\r\n`{ kind: 'loop', over: <Expr>, body: <Expr>, key?: string, value?: string, parallel?: {...} }`\r\n\r\nTwo evaluation modes by `over`'s static type:\r\n- **Iterable** (`get().loop` defined): walked once. `key` / `value`\r\n  bind to scope under those names (override defaults via the optional\r\n  fields).\r\n- **Bool while-loop** (`get().loopDynamic === true`): `over` is\r\n  RE-EVALUATED each iteration. The loop continues while truthy and\r\n  exits the moment it becomes false. `bool` uses this.\r\n\r\nOptional `parallel: { concurrent?, rate? }` fans body execution out:\r\n`concurrent` caps simultaneous bodies, `rate` paces start times. The\r\nnative iterator just calls `yield(k, v)`; the parallel orchestration\r\nsits in `LoopExpr.evaluate` so every iterable inherits it for free.\r\n\r\nParallel composes with the dynamic mode too: `bool over` plus\r\n`parallel: { concurrent: 3 }` fans the body out up to 3 in-flight,\r\nand `over` is re-evaluated against the outer scope every time a task\r\nCOMPLETES (not when it starts). So accumulating side effects from\r\nthe prior batch decide whether more tasks spawn.\r\n\r\n### `lambda` — callable closure over the lexical scope\r\n\r\n`{ kind: 'lambda', type: <fn TypeDef>, body: <Expr>, constraint?: <Expr> }`\r\n\r\nInside the body, `args` is the call-site arguments obj and `recurse`\r\nis this lambda (for self-calls). Optional `constraint` runs before\r\nthe body each call (must return `bool`); throws on false.\r\n\r\n### `template` — string interpolation\r\n\r\n`{ kind: 'template', template: '<string>', params: <Expr returning obj> }`\r\n\r\nEach `{name}` placeholder in the string is replaced with the\r\nstringified `params.name`. Compiles to a JS template literal in\r\n`toCode` rendering when params is a `new obj` literal.\r\n\r\n### `flow` — non-local control flow\r\n\r\n`{ kind: 'flow', action: 'break' | 'continue' | 'return' | 'exit' | 'throw', value?, error? }`\r\n\r\n- `break` / `continue` — only valid inside a `loop`.\r\n- `return` — unwinds to the enclosing lambda or fn body; `value`\r\n  becomes the result.\r\n- `exit` — unwinds all the way to `engine.run`; `value` becomes the\r\n  program result.\r\n- `throw` — raises `error`; caught by a path step's `catch:` handler.\r\n\r\n### `native` — escape hatch to a registered native impl\r\n\r\n`{ kind: 'native', id: '<nativeId>', type?: <TypeDef> }`\r\n\r\nCalls into a JS/TS function registered via `registry.setNative(id,\r\nimpl)`. Most natives are referenced indirectly — `num.add`'s prop\r\ntype carries `{kind: 'native', id: 'num.add'}` as its get expression,\r\nso a path call to `.add` dispatches without any explicit `native`\r\nnode in user code. You'd hand-write a `native` node when authoring a\r\ncustom loop ExprDef or a method whose impl lives outside gin.\r\n\r\n---\r\n\r\n## Parsing\r\n\r\ngin has TWO levels of parsing — they compose:\r\n\r\n1. **JSON → runtime objects.** `registry.parse(typeDef)` turns a\r\n   `TypeDef` JSON into a `Type` instance; `registry.parseExpr(exprDef,\r\n   scope?)` turns an `ExprDef` into an `Expr`. Inverse:\r\n   `type.toJSON()` / `expr.toJSON()`. Round-trips losslessly.\r\n\r\n   `parse` is STRICT about keys. A `TypeDef` may carry only `name`,\r\n   `docs`, `extends`, `satisfies`, `generic`, `options`, `init`,\r\n   `props`, `get`, `call`, `constraint`, and each class declares what\r\n   may appear inside its `generic` and its `options`. Anything else is\r\n   an error rather than an ignored key, because every slot has a\r\n   silent default: `{name:'list', options:{item: T}}` — the element\r\n   type belongs in `generic.V` — would otherwise parse to `list<any>`\r\n   without complaint. The error names the offending key and, where it\r\n   can, the construct that was meant: the nearest valid key on a typo,\r\n   the `generic` parameter a stray TypeDef belongs in, or the `enum` a\r\n   closed set of constants should have been.\r\n\r\n   Since 0.4.0 the same refusal covers the shapes NESTED in a def — a\r\n   `PropDef` (`docs`, `type`, `get`, `default`, `set`), a `GetSetDef`\r\n   (`docs`, `key`, `value`, `get`, `set`, `loop`, `loopDynamic`), a\r\n   `CallDef` (`docs`, `types`, `args`, `returns`, `throws`, `get`,\r\n   `set`), an init (`docs`, `args`, `run`) — and a PATH STEP, which\r\n   must name exactly one of `{prop}` / `{args, generic?, catch?}` /\r\n   `{key}`. A misspelt `returns` used to produce a fn with no return\r\n   type; a fused `{prop, args}` step used to parse as a bare prop read\r\n   with the arguments dropped.\r\n\r\n2. **Runtime data → typed values.** Once you have a `Type`, calling\r\n   `type.parse(jsonData)` validates the data and returns a `Value<T>`\r\n   — the runtime currency. A `Value` is a `{type, raw}` pair where\r\n   `raw` is the JS storage shape. `value.toJSON()` produces the JSON\r\n   shape; `type.encode(value.raw)` does the same at the type level.\r\n\r\nBoth levels are scope-aware. Generic placeholders (`AliasType`)\r\nresolve through the scope passed to parse — that's how a `CallStep`'s\r\n`generic: { R: <type> }` map flows into the called signature without\r\nrebuilding the type tree.\r\n\r\n`registry.scope({ X: type })` builds a `LocalScope` OVERLAY for the\r\nsame mechanism: `X` resolves inside that scope, the registry is not\r\nmutated, and `parse({name:'X'}).toJSON()` is still `{name:'X'}` — a\r\nreference stays a reference. Contrast `register(X)`, where the name\r\nresolves to the instance and the next `toJSON()` writes the whole\r\ndefinition INLINE where the reference used to be. Use the overlay for\r\na name that is true for one session, execution or request; use\r\n`register` for a type the registry owns. `toValueSchema({ scope })`\r\ntakes the same scope, so a value gate built from a signature that\r\nnames a type is actually enforced rather than degrading to `z.any()`.\r\n\r\n---\r\n\r\n## The Registry — the only class you really need\r\n\r\n`Registry` is your interface. Every other class (`Type`, `Expr`,\r\n`Engine`, `Value`, `Path`, ...) is reachable through it. You'll rarely\r\nconstruct one yourself — `createRegistry()` ships with every built-in\r\ntype, native, and Expr class pre-registered.\r\n\r\nKey methods:\r\n\r\n| Method | Purpose |\r\n|---|---|\r\n| `parse(def)` / `parseExpr(def, scope?)` | TypeDef / ExprDef → runtime |\r\n| `define(cls)` | Register a built-in Type class for JSON dispatch |\r\n| `register(type)` | Register a named Type instance (typically an Extension) — resolves to the INSTANCE, whose `toJSON()` inlines the definition |\r\n| `scope(bindings?)` | An overlay above the registry: names that resolve for one session and still serialize as `{name}`, without mutating the registry |\r\n| `lookup(name)` | Look up a Type by name (registered → built-in fallback) |\r\n| `setNative(id, impl)` | Wire a JS function as a gin native |\r\n| `getNative(id)` | Read it back |\r\n| `defineExpr(cls)` | Register an ExprClass (12 ship; you rarely add more) |\r\n| `extend(base, { name, ... })` | Create a named Extension |\r\n| `augment(name, { props?, get?, call?, init? })` | Add to an existing type by name |\r\n| `augmentation(name)` | Read augmentation back |\r\n| `like(type)` | Pick a registered concrete type compatible with a constraint |\r\n\r\nThe builder methods (`r.num()`, `r.text()`, `r.list(item)`, `r.obj({...})`,\r\n`r.fn(args, returns, throws?, generic?)`, `r.iface({...})`,\r\n`r.method(args, returns, nativeId)`, `r.prop(type, nativeId)`, ...) are\r\nsugar for parse — they construct runtime types without going through\r\nJSON.\r\n\r\n`createEngine(registry)` builds an Engine that owns evaluation,\r\nvalidation, and type-inference walks. Programs run via `engine.run(expr,\r\nextras?)`; static analysis via `engine.validate(expr)` /\r\n`engine.typeOf(expr)`.\r\n\r\n---\r\n\r\n## Built-in type catalog\r\n\r\nBelow is what `createRegistry()` ships with — the surface every gin\r\nprogram starts with. Each type's section is the same `toCodeDefinition`\r\noutput an LLM sees in its prompt.\r\n\r\n```\r\ntype any {\r\n  toAny(): any\r\n  typeOf(): text\r\n  is<T>(): bool\r\n  as<T>(): optional<T>\r\n  toText(): text\r\n  toBool(): bool\r\n  eq(other: any): bool\r\n  neq(other: any): bool\r\n}\r\n\r\ntype void {\r\n  toAny(): any\r\n  toText(): text\r\n  toBool(): bool\r\n}\r\n\r\ntype null {\r\n  toAny(): any\r\n  toText(): text\r\n  toBool(): bool\r\n}\r\n\r\ntype bool {\r\n  [key: num{whole=true, min=0}]: bool\r\n  toAny(): any\r\n  eq(other: bool): bool\r\n  neq(other: bool): bool\r\n  and(other: bool): bool\r\n  or(other: bool): bool\r\n  xor(other: bool): bool\r\n  not(): bool\r\n  toText(): text\r\n  toNum(): num\r\n}\r\n\r\ntype num {\r\n  [key: num{whole=true, min=0}]: num\r\n  toAny(): any\r\n  eq(other: num, epsilon?: num): bool\r\n  neq(other: num, epsilon?: num): bool\r\n  lt(other: num): bool\r\n  lte(other: num): bool\r\n  gt(other: num): bool\r\n  gte(other: num): bool\r\n  add(other: num): num\r\n  sub(other: num): num\r\n  mul(other: num): num\r\n  div(other: num): num\r\n  mod(other: num): num\r\n  pow(other: num): num\r\n  abs(): num\r\n  neg(): num\r\n  sign(): num\r\n  sqrt(): num\r\n  min(other: num): num\r\n  max(other: num): num\r\n  clamp(min: num, max: num): num\r\n  floor(): num\r\n  ceil(): num\r\n  round(): num\r\n  isZero(): bool\r\n  isPositive(): bool\r\n  isNegative(): bool\r\n  isInteger(): bool\r\n  isEven(): bool\r\n  isOdd(): bool\r\n  toText(precision?: num): text\r\n  toBool(): bool\r\n}\r\n\r\ntype text {\r\n  [key: num]: text{minLength=1, maxLength=1}\r\n  toAny(): any\r\n  length: num\r\n  eq(other: text): bool\r\n  neq(other: text): bool\r\n  contains(search: text): bool\r\n  startsWith(prefix: text): bool\r\n  endsWith(suffix: text): bool\r\n  trim(): text\r\n  trimStart(): text\r\n  trimEnd(): text\r\n  upper(): text\r\n  lower(): text\r\n  slice(start: num, end?: num): text\r\n  replace(search: text, replacement: text): text\r\n  split(separator: text): list<text>\r\n  concat(other: text): text\r\n  repeat(count: num): text\r\n  indexOf(search: text, from?: num): num\r\n  lastIndexOf(search: text, from?: num): num\r\n  match(pattern: text): list<text>\r\n  test(pattern: text): bool\r\n  isEmpty(): bool\r\n  isNotEmpty(): bool\r\n  toNum(): num\r\n  toBool(): bool\r\n}\r\n\r\ntype list<V> {\r\n  [key: num{whole=true, min=0}]: V\r\n  length: num\r\n  at(index: num): optional<V>\r\n  push(value: V): void\r\n  pop(): optional<V>\r\n  shift(): optional<V>\r\n  unshift(value: V): void\r\n  insert(index: num, value: V): void\r\n  remove(index: num): V\r\n  clear(): void\r\n  slice(start?: num, end?: num): list<V>\r\n  concat(other: list<V>): list<V>\r\n  reverse(): list<V>\r\n  join(separator?: text): text\r\n  indexOf(value: V): num\r\n  contains(value: V): bool\r\n  unique(): list<V>\r\n  duplicates(): list<V>\r\n  map<R>(fn: (value: V, index: num): R): list<R>\r\n  filter(fn: (value: V, index: num): bool): list<V>\r\n  find(fn: (value: V, index: num): bool): optional<V>\r\n  reduce<R>(fn: (acc: R, value: V, index: num): R, initial: R): R\r\n  some(fn: (value: V, index: num): bool): bool\r\n  every(fn: (value: V, index: num): bool): bool\r\n  sort(fn?: (a: V, b: V): num): list<V>\r\n  isEmpty(): bool\r\n  isNotEmpty(): bool\r\n  first?: V\r\n  last?: V\r\n}\r\n\r\ntype map<K, V> {\r\n  [key: K]: V\r\n  size: num\r\n  at(key: K): optional<V>\r\n  has(key: K): bool\r\n  delete(key: K): bool\r\n  clear(): void\r\n  keys(): list<K>\r\n  values(): list<V>\r\n  isEmpty(): bool\r\n  isNotEmpty(): bool\r\n}\r\n\r\ntype tuple<...elements> {\r\n  [key: num]: <element-union>\r\n  length: num\r\n  first: <head>\r\n  last: <tail>\r\n  toList(): list<element-union>\r\n}\r\n\r\ntype obj {\r\n  keys(): list<text>\r\n  values(): list<any>\r\n  entries(): list<tuple<text, any>>\r\n  has(key: text): bool\r\n  eq(other: any): bool\r\n  neq(other: any): bool\r\n  toText(): text\r\n}\r\n\r\ntype optional<T> {\r\n  value: T\r\n  has(): bool\r\n  or(fallback: T): T\r\n  map<R>(fn: (value: T): R): optional<R>\r\n}\r\n\r\ntype nullable<T> {\r\n  value: T\r\n  isNull(): bool\r\n  or(fallback: T): T\r\n  map<R>(fn: (value: T): R): nullable<R>\r\n}\r\n\r\ntype or<...variants>      // union; props/get/call when ALL variants share them\r\ntype and<...parts>        // intersection; props from ANY part. ALL-object parts\r\n                          //   merge (and<obj{a},obj{b}> ≡ obj{a,b}); otherwise\r\n                          //   the first part parses and the rest constrain.\r\ntype not<excluded>        // any value EXCEPT one matching excluded\r\ntype literal<T>           // one specific constant value of T\r\ntype enum<V>              // named constants of value type V\r\ntype function             // see \"call\" — args/returns/throws/generic\r\ntype interface            // structural contract; props/get/call only\r\ntype typ<T>               // a value that IS a Type, constrained by T\r\ntype alias                // bare-name reference / generic placeholder\r\n\r\ntype date {\r\n  year, month, day, dayOfWeek, dayOfYear   // num\r\n  eq, neq, before, after                    // (other: date) → bool\r\n  addDays/Months/Years, diffDays/Months/Years\r\n  toText(format?): text\r\n}\r\n\r\ntype timestamp {\r\n  year..millisecond                         // num\r\n  eq, before, after                         // (other: timestamp) → bool\r\n  addDuration, subDuration, diff\r\n  toDate(): date\r\n  toEpoch(): num\r\n  toText(format?): text\r\n}\r\n\r\ntype duration {\r\n  new(days?, hours?, minutes?, seconds?, ms?)\r\n  totalSeconds, totalMinutes, totalHours, totalDays\r\n  days, hours, minutes, seconds, ms\r\n  toText(format?): text\r\n}\r\n\r\ntype color {\r\n  new(r, g, b, a?)\r\n  r, g, b, a, hue, saturation, lightness    // num\r\n  eq, neq                                    // (other: color) → bool\r\n  lighten, darken, saturate, desaturate, opacity, invert, mix, complement\r\n  toHex, toRgb, toHsl, toText               // → text\r\n}\r\n```\r\n\r\n---\r\n\r\n## Putting it together\r\n\r\nA single example demonstrating the four developer-facing surfaces:\r\n\r\n```ts\r\nimport { createRegistry, createEngine, GetSet, Init, val, Value } from '@aeye/gin';\r\n\r\nconst r = createRegistry();\r\n\r\n// 1. Extension — a real subtype with its own surface.\r\nconst Email = r.extend(\r\n  r.text({ pattern: '^[^@]+@[^@]+$', minLength: 3 }),\r\n  {\r\n    name: 'Email',\r\n    docs: 'A text value matching a basic email shape',\r\n    props: {\r\n      domain: r.method({}, r.text(), 'Email.domain'),\r\n    },\r\n  },\r\n);\r\nr.register(Email);\r\n\r\n// 2. Native — the JS implementation of Email.domain. Natives access\r\n//    `this` via scope.get('this').\r\nr.setNative('Email.domain', (scope, reg) => {\r\n  const self = scope.get('this')!.raw as string;\r\n  return val(reg.text(), self.split('@')[1] ?? '');\r\n});\r\n\r\n// 3. Augmentation — give the existing `num` type a `clamp01` method,\r\n//    AND a constructor so `new num({percent})` produces a 0–1 num.\r\nr.augment('num', {\r\n  props: {\r\n    clamp01: r.method({}, r.num({ min: 0, max: 1 }), 'num.clamp01'),\r\n  },\r\n  init: new Init({\r\n    args: r.obj({ percent: { type: r.num({ min: 0, max: 100 }) } }) as any,\r\n    run: { kind: 'native', id: 'num.fromPercent' },\r\n  }),\r\n});\r\n\r\nr.setNative('num.clamp01', (scope, reg) => {\r\n  const n = scope.get('this')!.raw as number;\r\n  return val(reg.num({ min: 0, max: 1 }), Math.max(0, Math.min(1, n)));\r\n});\r\nr.setNative('num.fromPercent', (scope, reg) => {\r\n  const args = scope.get('args')!.raw as Record<string, Value>;\r\n  const pct = args['percent']!.raw as number;\r\n  return val(reg.num({ min: 0, max: 1 }), pct / 100);\r\n});\r\n\r\n// 4. Run a program. (Programs are JSON — typically authored by an LLM,\r\n//    not hand-written. Here we hand-write one for illustration.)\r\nconst engine = createEngine(r);\r\n\r\nconst program = {\r\n  kind: 'block',\r\n  lines: [\r\n    {\r\n      kind: 'define',\r\n      vars: [\r\n        // `new num({percent: 75})` — augmented init runs; result is 0.75.\r\n        { name: 'opacity', value: {\r\n          kind: 'new',\r\n          type: { name: 'num' },\r\n          value: { percent: 75 },\r\n        } },\r\n        { name: 'address', value: {\r\n          kind: 'new',\r\n          type: { name: 'Email' },\r\n          value: 'team@example.com',\r\n        } },\r\n      ],\r\n      body: {\r\n        kind: 'block',\r\n        lines: [\r\n          // Augmented method: opacity.clamp01() — already in [0,1].\r\n          { kind: 'get', path: [{ prop: 'opacity' }, { prop: 'clamp01' }, { args: {} }] },\r\n          // Extension method: address.domain() → 'example.com'.\r\n          { kind: 'get', path: [{ prop: 'address' }, { prop: 'domain' }, { args: {} }] },\r\n        ],\r\n      },\r\n    },\r\n  ],\r\n};\r\n\r\nconst result = await engine.run(program);\r\nconsole.log(result.raw); // 'example.com'\r\n```\r\n\r\nWhat this exercises:\r\n\r\n- `r.extend(...)` produces `Email`, a real subtype of `text` with a\r\n  custom prop. Static analysis treats Email as text everywhere text\r\n  is expected.\r\n- `r.augment('num', ...)` adds `clamp01` AND `init` to the canonical\r\n  `num` type. Every num — including extensions over num — picks them\r\n  up. `new num({percent: 75})` flows through the augmented init.\r\n- `r.setNative(id, impl)` wires the JS implementations. Any path call\r\n  that references those native ids dispatches through them.\r\n- `engine.run(program)` evaluates the JSON tree, validating types as\r\n  it walks.\r\n\r\nAugmentations and extensions live on the registry. Pass that registry\r\nto the engine — and to any prompt schema generator (`buildSchemas(r)`)\r\n— so the LLM authoring programs sees the full surface.\r\n\r\n---\r\n\r\n## License\r\n\r\nGPL-3.0\r\n","readmeFilename":"README.md"}