{"_id":"adequate-little-templates","_rev":"3-3b8bbd7dc731f584bf78db68f8103c93","name":"adequate-little-templates","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"adequate-little-templates","version":"1.0.0","keywords":["template","templating","csp-safe","minimal","tiny"],"author":{"name":"Liam Bigelow"},"license":"MIT","_id":"adequate-little-templates@1.0.0","maintainers":[{"name":"bglw","email":"liam@bigelow.io"}],"homepage":"https://github.com/bglw/adequate-little-templates#readme","bugs":{"url":"https://github.com/bglw/adequate-little-templates/issues"},"dist":{"shasum":"029e1b91d5834ca3e18ff9d5f3b894db028ee6b8","tarball":"https://registry.npmjs.org/adequate-little-templates/-/adequate-little-templates-1.0.0.tgz","fileCount":6,"integrity":"sha512-7LU+o//53P4na+y/1ROaVqXULGjplA8m0CzJ4XB9+uFiWMiEWFY84gnaESoUmEqiAbfviS+AfZkeofhHTr1O+A==","signatures":[{"sig":"MEYCIQCNrTKnzg1VtTKH2uLNJUANjR/LEJJiXhYcHi0AqE64+gIhANfi2sji+/1UlZTgV8rOgJQ0EMx7LPyck7BoZIib22eH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37624},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=14"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"de871123e3ca6f13e184196d33577c1b3582805c","scripts":{"size":"gzip -c dist/index.js | wc -c","test":"ava","build":"tsc && cp dist/index.js dist/index.mjs","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"bglw","email":"liam@bigelow.io"},"repository":{"url":"git+https://github.com/bglw/adequate-little-templates.git","type":"git"},"_npmVersion":"10.9.2","description":"A minimal, CSP-safe templating language","directories":{},"sideEffects":false,"_nodeVersion":"22.15.1","_hasShrinkwrap":false,"devDependencies":{"ava":"^6.4.1","typescript":"^5.3.0"},"_npmOperationalInternal":{"tmp":"tmp/adequate-little-templates_1.0.0_1767861980195_0.9546073214095911","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"adequate-little-templates","version":"1.0.1","keywords":["template","templating","csp-safe","minimal","tiny"],"author":{"name":"Liam Bigelow"},"license":"MIT","_id":"adequate-little-templates@1.0.1","maintainers":[{"name":"bglw","email":"liam@bigelow.io"}],"homepage":"https://github.com/bglw/adequate-little-templates#readme","bugs":{"url":"https://github.com/bglw/adequate-little-templates/issues"},"dist":{"shasum":"405e362cfe713f4069df02d47071580befdf073d","tarball":"https://registry.npmjs.org/adequate-little-templates/-/adequate-little-templates-1.0.1.tgz","fileCount":6,"integrity":"sha512-WF/Pe9bIAMWlr3sQ5qRcP9+jtlOmSLOvWEcpjt/kVUQDZKJWiY8oHrRq8v6g3G5m/xDb6K1peLiZ8ekv05qjfA==","signatures":[{"sig":"MEUCIQC+LKEdbQOxC4B9WKsOwqPsfzvC/TQfkDM5Wi5Pg8jV5wIgHHTOgKifv0hs1XUZNn/s0hJ8p/Hldovg8o0kUf6+Lgo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37794},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=14"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"54543690bdc575767f413e17d0dca33896b2690b","scripts":{"size":"gzip -c dist/index.js | wc -c","test":"ava","build":"tsc && cp dist/index.js dist/index.mjs","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"bglw","email":"liam@bigelow.io"},"repository":{"url":"git+https://github.com/bglw/adequate-little-templates.git","type":"git"},"_npmVersion":"10.9.2","description":"A minimal, CSP-safe templating language","directories":{},"sideEffects":false,"_nodeVersion":"22.15.1","_hasShrinkwrap":false,"devDependencies":{"ava":"^6.4.1","typescript":"^5.3.0"},"_npmOperationalInternal":{"tmp":"tmp/adequate-little-templates_1.0.1_1767864459230_0.558101044812628","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"adequate-little-templates","version":"1.0.2","description":"A minimal, CSP-safe templating language","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"sideEffects":false,"scripts":{"build":"tsc && cp dist/index.js dist/index.mjs","test":"ava","size":"gzip -c dist/index.js | wc -c","prepublishOnly":"npm run build && npm test"},"keywords":["template","templating","csp-safe","minimal","tiny"],"repository":{"type":"git","url":"git+https://github.com/bglw/adequate-little-templates.git"},"homepage":"https://github.com/bglw/adequate-little-templates#readme","bugs":{"url":"https://github.com/bglw/adequate-little-templates/issues"},"author":{"name":"Liam Bigelow"},"license":"MIT","engines":{"node":">=14"},"devDependencies":{"ava":"^6.4.1","typescript":"^5.3.0"},"_id":"adequate-little-templates@1.0.2","gitHead":"47d207b7840a5e7ea847e1f630454d81ee3ad16e","_nodeVersion":"22.15.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-d0tFFG538l3Y4CElRKtticzrMr/OGohs32/nf3Ewn8rbTMBYwkVNe8mPdXWrDnMlz+ZVUFQjsTmsBD5zCtU4wg==","shasum":"50773b3b7b1e6bde038455562d38a9aea96f8803","tarball":"https://registry.npmjs.org/adequate-little-templates/-/adequate-little-templates-1.0.2.tgz","fileCount":6,"unpackedSize":38699,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDRP4/oCAFfnSn6VGA4jCsIQAXXjh6B5vQFGn+H82Ly8AIhAPnDWBYBgE4m8rgLV/pIFZ8iA2z81LPvv0i9IG+5dDNg"}]},"_npmUser":{"name":"bglw","email":"liam@bigelow.io"},"directories":{},"maintainers":[{"name":"bglw","email":"liam@bigelow.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/adequate-little-templates_1.0.2_1768088109761_0.5333790749315568"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-08T08:46:20.195Z","modified":"2026-01-10T23:35:10.463Z","1.0.0":"2026-01-08T08:46:20.337Z","1.0.1":"2026-01-08T09:27:39.379Z","1.0.2":"2026-01-10T23:35:09.908Z"},"bugs":{"url":"https://github.com/bglw/adequate-little-templates/issues"},"author":{"name":"Liam Bigelow"},"license":"MIT","homepage":"https://github.com/bglw/adequate-little-templates#readme","keywords":["template","templating","csp-safe","minimal","tiny"],"repository":{"type":"git","url":"git+https://github.com/bglw/adequate-little-templates.git"},"description":"A minimal, CSP-safe templating language","maintainers":[{"name":"bglw","email":"liam@bigelow.io"}],"readme":"# Adequate Little Templates\n\n[![npm version](https://img.shields.io/npm/v/adequate-little-templates.svg)](https://www.npmjs.com/package/adequate-little-templates)\n\nA minimal, CSP-safe templating language. Unlike the micro templates I could find, this does not compile to a JavaScript function requiring `eval` or equivalent, so it will run in more contexts and be safer for untrusted input.\n\nCurrently: ~3KB gzipped.\n\n## Quick Start\n\n```bash\nnpm i adequate-little-templates\n```\n\n```javascript\nimport { render, compile, registerFunction } from 'adequate-little-templates';\n\n// One-shot render\nrender('Hello {{ name }}!', { name: 'World' });\n// -> \"Hello World!\"\n\n// Compile for reuse\nconst template = compile('Hello {{ name }}!');\ntemplate({ name: 'World' }); // -> \"Hello World!\"\ntemplate({ name: 'Again' }); // -> \"Hello Again!\"\n```\n\n## Syntax\n\n### Variable Interpolation\n\nOutput values, HTML escaped:\n\n```handlebars\n{{ title }}\n{{ meta.author }}\n{{ deeply.nested.value }}\n```\n\n### Raw Output\n\nOutput without HTML escaping (for trusted HTML like highlighted excerpts):\n\n```\n{{+ excerpt +}}\n{{+ meta.description +}}\n```\n\n### Pipes and Functions\n\nTransform values with pipes:\n\n```handlebars\n{{ title | lowercase }}\n{{ title | uppercase }}\n{{ title | truncate(50) }}\n{{ title | truncate(50, \"…\") }}\n{{ name | lowercase | truncate(20) }}\n```\n\nOr use function call syntax:\n\n```handlebars\n{{ truncate(title, 50) }}\n{{ replace(title, \"foo\", \"bar\") }}\n```\n\nPipes are sugar for function calls. These are equivalent:\n\n```handlebars\n{{ title | truncate(50) }}\n{{ truncate(title, 50) }}\n```\n\n### Conditionals\n\n```handlebars\n{{#if author}}\n  <span>By {{ author }}</span>\n{{/if}}\n\n{{#if image}}\n  <img src=\"{{ image }}\">\n{{:else}}\n  <div class=\"placeholder\"></div>\n{{/if}}\n\n{{#if eq(status, \"published\")}}\n  <span class=\"green\">Published</span>\n{{:else if eq(status, \"draft\")}}\n  <span class=\"yellow\">Draft</span>\n{{:else}}\n  <span class=\"gray\">Unknown</span>\n{{/if}}\n```\n\n### Iteration\n\n```handlebars\n{{#each items as item}}\n  <li>{{ item.name }}</li>\n{{/each}}\n\n{{#each items as item, index}}\n  <li>{{ index }}: {{ item.name }}</li>\n{{/each}}\n\n{{#each items as item}}\n  <li>{{ item }}</li>\n{{:else}}\n  <li>No items found</li>\n{{/each}}\n\n{{#each items | limit(3) as item}}\n  <li>{{ item.name }}</li>\n{{/each}}\n```\n\n### Escaping Delimiters\n\nOutput literal `{{` with a backslash:\n\n```handlebars\n\\{{ this is not a template tag }}\n```\n\n## Built-in Functions\n\n### Comparison\n\n| Function | Description |\n|----------|-------------|\n| `eq(a, b)` | Equal |\n| `ne(a, b)` | Not equal |\n| `gt(a, b)` | Greater than |\n| `lt(a, b)` | Less than |\n| `gte(a, b)` | Greater than or equal |\n| `lte(a, b)` | Less than or equal |\n\n### Boolean\n\n| Function | Description |\n|----------|-------------|\n| `and(a, b, ...)` | Logical AND |\n| `or(a, b, ...)` | Logical OR |\n| `not(a)` | Logical NOT |\n\n### String\n\n| Function | Description |\n|----------|-------------|\n| `lowercase(s)` | Convert to lowercase |\n| `uppercase(s)` | Convert to uppercase |\n| `trim(s)` | Remove leading/trailing whitespace |\n| `truncate(s, n)` | Truncate to n chars with \"...\" |\n| `truncate(s, n, suffix)` | Truncate with custom suffix |\n| `replace(s, find, rep)` | Replace all occurrences |\n\n### Array\n\n| Function | Description |\n|----------|-------------|\n| `limit(arr, n)` | Take first n items |\n| `first(arr)` | First item |\n| `last(arr)` | Last item |\n| `length(arr)` | Array (or string) length |\n| `join(arr, sep)` | Join with separator |\n\n### Misc\n\n| Function | Description |\n|----------|-------------|\n| `default(val, fallback)` | Use fallback if val is falsy |\n| `safeUrl(url)` | Sanitize URL, blocking dangerous protocols |\n\n### URL Sanitization\n\nUse `safeUrl` to protect against `javascript:` and other dangerous URL schemes when rendering user-provided URLs:\n\n```handlebars\n<a href=\"{{ meta.url | safeUrl }}\">{{ title }}</a>\n```\n\nAllowed:\n- Relative URLs: `/path`, `./path`, `../path`, `?query`, `#hash`\n- HTTP(S): `https://example.com`\n- FTP: `ftp://example.com/file.txt`\n- Email: `mailto:user@example.com`\n- Phone: `tel:+1234567890`\n\nBlocked (returns empty string):\n- `javascript:alert('xss')`\n- `data:text/html,...`\n- `vbscript:...`\n- Any other protocol\n\n## Custom Functions\n\nRegister your own functions:\n\n```javascript\nimport { registerFunction, render } from 'adequate-little-templates';\n\nregisterFunction('double', (n) => n * 2);\nregisterFunction('formatDate', (ts) => new Date(ts).toLocaleDateString());\nregisterFunction('pluralize', (count, singular, plural) =>\n  count === 1 ? singular : plural\n);\n\nrender('{{ double(5) }}', {});\n// -> \"10\"\n\nrender('{{ count }} {{ pluralize(count, \"item\", \"items\") }}', { count: 3 });\n// -> \"3 items\"\n\n// Works with pipes too\nrender('{{ num | double }}', { num: 21 });\n// -> \"42\"\n```\n\n## Truthiness\n\nThe following values are **falsy**:\n\n- `null`\n- `undefined`\n- `false`\n- `0`\n- `\"\"` (empty string)\n- `NaN`\n- `[]` (empty array)\n- `{}` (empty object)\n\nEverything else is **truthy**.\n\n## Error Handling\n\nErrors render inline for easy debugging:\n\n```text\n[Error: use #each for arrays]\n[Error: cannot render object]\n[Error: unknown foo()]\n[Error: #each needs array]\n[Error: join() needs 2 args]\n```\n\n## API\n\n### Functions\n\n```typescript\nrender(template: string, data: TemplateData): string\n```\nParse and render a template in one step.\n\n```typescript\ncompile(template: string): Template\n```\nParse a template and return a reusable render function. Use this when rendering the same template multiple times.\n\n```typescript\nregisterFunction(name: string, fn: CustomFn): void\n```\nRegister a custom function for use in templates.\n\n### TypeScript\n\nIf you need explicit type annotations, all types are exported:\n\n```typescript\nimport { render, TemplateData, Value, Template, CustomFn } from 'adequate-little-templates';\n\nconst data: TemplateData = { title: 'Hello', count: 42 };\nconst myFn: CustomFn = (a, b) => Number(a) + Number(b);\n```\n","readmeFilename":"README.md"}