binja 0.9.0 → 0.9.2

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/README.md CHANGED
@@ -5,18 +5,25 @@
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
+ <a href="https://egeominotti.github.io/binja/"><strong>📚 Documentation</strong></a> •
8
9
  <a href="#installation">Installation</a> •
9
10
  <a href="#quick-start">Quick Start</a> •
10
- <a href="#features">Features</a> •
11
- <a href="#benchmarks">Benchmarks</a> •
12
- <a href="#filters">Filters</a>
11
+ <a href="#framework-adapters">Hono/Elysia</a> •
12
+ <a href="#multi-engine-support">Multi-Engine</a> •
13
+ <a href="#filters-84-built-in">Filters</a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://www.npmjs.com/package/binja"><img src="https://img.shields.io/npm/v/binja?label=npm&color=10b981" alt="npm version"></a>
18
+ <a href="https://www.npmjs.com/package/binja"><img src="https://img.shields.io/npm/dm/binja?color=10b981" alt="npm downloads"></a>
19
+ <a href="https://github.com/egeominotti/binja/actions"><img src="https://github.com/egeominotti/binja/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
20
+ <a href="https://github.com/egeominotti/binja/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg" alt="BSD-3-Clause License"></a>
13
21
  </p>
14
22
 
15
23
  <p align="center">
16
24
  <img src="https://img.shields.io/badge/bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
17
25
  <img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
18
26
  <img src="https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white" alt="Django Compatible" />
19
- <img src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=for-the-badge" alt="BSD-3-Clause License" />
20
27
  </p>
21
28
 
22
29
  ---
@@ -27,7 +34,8 @@
27
34
  |---------|-----------|------------------|
28
35
  | **Runtime Performance** | ✅ 2-4x faster | ❌ |
29
36
  | **AOT Compilation** | ✅ 160x faster | ❌ |
30
- | **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid | ❌ |
37
+ | **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid, Twig | ❌ |
38
+ | **Framework Adapters** | ✅ Hono, Elysia | ❌ |
31
39
  | Django DTL Compatible | ✅ 100% | ❌ Partial |
32
40
  | Jinja2 Compatible | ✅ Full | ⚠️ Limited |
33
41
  | Template Inheritance | ✅ | ⚠️ |
@@ -430,6 +438,7 @@ Binja supports multiple template engines through a unified API. All engines pars
430
438
  | **Jinja2/DTL** | `{{ var }}` `{% if %}` | Default, Python/Django compatibility |
431
439
  | **Handlebars** | `{{var}}` `{{#if}}` | JavaScript ecosystem, Ember.js |
432
440
  | **Liquid** | `{{ var }}` `{% if %}` | Shopify, Jekyll, static sites |
441
+ | **Twig** | `{{ var }}` `{% if %}` | PHP/Symfony, Drupal, Craft CMS |
433
442
 
434
443
  ### Usage
435
444
 
@@ -437,6 +446,7 @@ Binja supports multiple template engines through a unified API. All engines pars
437
446
  // Direct engine imports
438
447
  import * as handlebars from 'binja/engines/handlebars'
439
448
  import * as liquid from 'binja/engines/liquid'
449
+ import * as twig from 'binja/engines/twig'
440
450
 
441
451
  // Handlebars
442
452
  await handlebars.render('Hello {{name}}!', { name: 'World' })
@@ -447,6 +457,11 @@ await handlebars.render('{{{html}}}', { html: '<b>unescaped</b>' })
447
457
  await liquid.render('Hello {{ name }}!', { name: 'World' })
448
458
  await liquid.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
449
459
  await liquid.render('{% assign x = "value" %}{{ x }}', {})
460
+
461
+ // Twig (Symfony)
462
+ await twig.render('Hello {{ name }}!', { name: 'World' })
463
+ await twig.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
464
+ await twig.render('{{ name|upper }}', { name: 'world' })
450
465
  ```
451
466
 
452
467
  ### MultiEngine API
@@ -459,26 +474,117 @@ const engine = new MultiEngine()
459
474
  // Render with any engine
460
475
  await engine.render('Hello {{name}}!', { name: 'World' }, 'handlebars')
461
476
  await engine.render('Hello {{ name }}!', { name: 'World' }, 'liquid')
477
+ await engine.render('Hello {{ name }}!', { name: 'World' }, 'twig')
462
478
  await engine.render('Hello {{ name }}!', { name: 'World' }, 'jinja2')
463
479
 
464
480
  // Auto-detect from file extension
465
481
  import { detectEngine } from 'binja/engines'
466
- const eng = detectEngine('template.hbs') // Returns Handlebars engine
467
- const eng2 = detectEngine('page.liquid') // Returns Liquid engine
482
+ const eng = detectEngine('template.hbs') // Returns Handlebars engine
483
+ const eng2 = detectEngine('page.liquid') // Returns Liquid engine
484
+ const eng3 = detectEngine('page.twig') // Returns Twig engine
468
485
  ```
469
486
 
470
487
  ### Engine Feature Matrix
471
488
 
472
- | Feature | Jinja2 | Handlebars | Liquid |
473
- |---------|--------|------------|--------|
474
- | Variables | `{{ x }}` | `{{x}}` | `{{ x }}` |
475
- | Conditionals | `{% if %}` | `{{#if}}` | `{% if %}` |
476
- | Loops | `{% for %}` | `{{#each}}` | `{% for %}` |
477
- | Filters | `{{ x\|filter }}` | `{{ x }}` | `{{ x \| filter }}` |
478
- | Raw output | `{% raw %}` | - | `{% raw %}` |
479
- | Comments | `{# #}` | `{{! }}` | `{% comment %}` |
480
- | Assignment | `{% set %}` | - | `{% assign %}` |
481
- | Unescaped | `{{ x\|safe }}` | `{{{x}}}` | - |
489
+ | Feature | Jinja2 | Handlebars | Liquid | Twig |
490
+ |---------|--------|------------|--------|------|
491
+ | Variables | `{{ x }}` | `{{x}}` | `{{ x }}` | `{{ x }}` |
492
+ | Conditionals | `{% if %}` | `{{#if}}` | `{% if %}` | `{% if %}` |
493
+ | Loops | `{% for %}` | `{{#each}}` | `{% for %}` | `{% for %}` |
494
+ | Filters | `{{ x\|filter }}` | `{{ x }}` | `{{ x \| filter }}` | `{{ x\|filter }}` |
495
+ | Raw output | `{% raw %}` | - | `{% raw %}` | `{% raw %}` |
496
+ | Comments | `{# #}` | `{{! }}` | `{% comment %}` | `{# #}` |
497
+ | Assignment | `{% set %}` | - | `{% assign %}` | `{% set %}` |
498
+ | Unescaped | `{{ x\|safe }}` | `{{{x}}}` | - | `{{ x\|raw }}` |
499
+
500
+ ---
501
+
502
+ ## Framework Adapters
503
+
504
+ Binja provides first-class integration with Bun's most popular web frameworks.
505
+
506
+ ### Hono
507
+
508
+ ```typescript
509
+ import { Hono } from 'hono'
510
+ import { binja } from 'binja/hono'
511
+
512
+ const app = new Hono()
513
+
514
+ // Add binja middleware
515
+ app.use(binja({
516
+ root: './views', // Template directory
517
+ extension: '.html', // Default extension
518
+ engine: 'jinja2', // jinja2 | handlebars | liquid | twig
519
+ cache: true, // Cache compiled templates
520
+ globals: { siteName: 'My App' }, // Global context
521
+ layout: 'layouts/base', // Optional layout template
522
+ }))
523
+
524
+ // Render templates with c.render()
525
+ app.get('/', (c) => c.render('index', { title: 'Home' }))
526
+ app.get('/users/:id', async (c) => {
527
+ const user = await getUser(c.req.param('id'))
528
+ return c.render('users/profile', { user })
529
+ })
530
+
531
+ export default app
532
+ ```
533
+
534
+ ### Elysia
535
+
536
+ ```typescript
537
+ import { Elysia } from 'elysia'
538
+ import { binja } from 'binja/elysia'
539
+
540
+ const app = new Elysia()
541
+ // Add binja plugin
542
+ .use(binja({
543
+ root: './views',
544
+ extension: '.html',
545
+ engine: 'jinja2',
546
+ cache: true,
547
+ globals: { siteName: 'My App' },
548
+ layout: 'layouts/base',
549
+ }))
550
+ // Render templates with render()
551
+ .get('/', ({ render }) => render('index', { title: 'Home' }))
552
+ .get('/users/:id', async ({ render, params }) => {
553
+ const user = await getUser(params.id)
554
+ return render('users/profile', { user })
555
+ })
556
+ .listen(3000)
557
+
558
+ console.log('Server running at http://localhost:3000')
559
+ ```
560
+
561
+ ### Adapter Options
562
+
563
+ | Option | Type | Default | Description |
564
+ |--------|------|---------|-------------|
565
+ | `root` | `string` | `./views` | Template directory |
566
+ | `extension` | `string` | `.html` | Default file extension |
567
+ | `engine` | `string` | `jinja2` | Template engine (`jinja2`, `handlebars`, `liquid`, `twig`) |
568
+ | `cache` | `boolean` | `true` (prod) | Cache compiled templates |
569
+ | `debug` | `boolean` | `false` | Show error details |
570
+ | `globals` | `object` | `{}` | Global context variables |
571
+ | `layout` | `string` | - | Layout template path |
572
+ | `contentVar` | `string` | `content` | Content variable name in layout |
573
+
574
+ ### Cache Management
575
+
576
+ ```typescript
577
+ import { clearCache, getCacheStats } from 'binja/hono'
578
+ // or
579
+ import { clearCache, getCacheStats } from 'binja/elysia'
580
+
581
+ // Clear all cached templates
582
+ clearCache()
583
+
584
+ // Get cache statistics
585
+ const stats = getCacheStats()
586
+ console.log(stats) // { size: 10, keys: ['jinja2:./views/index.html', ...] }
587
+ ```
482
588
 
483
589
  ---
484
590
 
@@ -609,7 +715,7 @@ debugOptions: {
609
715
 
610
716
  ## CLI Tool
611
717
 
612
- Binja includes a CLI for template pre-compilation:
718
+ Binja includes a CLI for template pre-compilation and linting:
613
719
 
614
720
  ```bash
615
721
  # Compile all templates to JavaScript
@@ -620,6 +726,15 @@ binja check ./templates
620
726
 
621
727
  # Watch mode for development
622
728
  binja watch ./templates -o ./dist
729
+
730
+ # Lint templates (syntax check)
731
+ binja lint ./templates
732
+
733
+ # Lint with AI analysis (requires API key)
734
+ binja lint ./templates --ai
735
+
736
+ # Lint with specific AI provider
737
+ binja lint ./templates --ai=ollama
623
738
  ```
624
739
 
625
740
  ### Pre-compiled Templates
@@ -633,6 +748,104 @@ const html = render({ title: 'Home', items: [...] })
633
748
 
634
749
  ---
635
750
 
751
+ ## AI-Powered Linting (Optional)
752
+
753
+ Binja includes an optional AI-powered linting module that detects security issues, performance problems, accessibility concerns, and best practice violations.
754
+
755
+ ### Installation
756
+
757
+ The AI module is opt-in. Install the SDK for your preferred provider:
758
+
759
+ ```bash
760
+ # For Claude (Anthropic)
761
+ bun add @anthropic-ai/sdk
762
+
763
+ # For OpenAI
764
+ bun add openai
765
+
766
+ # For Ollama (local) - no package needed
767
+ # For Groq - no package needed
768
+ ```
769
+
770
+ ### Configuration
771
+
772
+ Set the API key for your provider:
773
+
774
+ ```bash
775
+ # Anthropic
776
+ export ANTHROPIC_API_KEY=sk-ant-...
777
+
778
+ # OpenAI
779
+ export OPENAI_API_KEY=sk-...
780
+
781
+ # Groq (free tier available)
782
+ export GROQ_API_KEY=gsk_...
783
+
784
+ # Ollama - no key needed, just run: ollama serve
785
+ ```
786
+
787
+ ### Usage
788
+
789
+ #### CLI
790
+
791
+ ```bash
792
+ # Lint with AI (auto-detect provider)
793
+ binja lint ./templates --ai
794
+
795
+ # Use specific provider
796
+ binja lint ./templates --ai=anthropic
797
+ binja lint ./templates --ai=openai
798
+ binja lint ./templates --ai=ollama
799
+ binja lint ./templates --ai=groq
800
+
801
+ # JSON output for CI/CD
802
+ binja lint ./templates --ai --format=json
803
+ ```
804
+
805
+ #### Programmatic
806
+
807
+ ```typescript
808
+ import { lint } from 'binja/ai'
809
+
810
+ // Auto-detect provider from environment
811
+ const result = await lint(template)
812
+
813
+ // Specify provider and API key directly
814
+ const result = await lint(template, {
815
+ provider: 'anthropic',
816
+ apiKey: 'sk-ant-...',
817
+ model: 'claude-sonnet-4-20250514'
818
+ })
819
+
820
+ // Check results
821
+ console.log(result.errors) // Syntax errors
822
+ console.log(result.warnings) // Security, performance issues
823
+ console.log(result.suggestions) // Best practice recommendations
824
+ console.log(result.provider) // Which AI was used
825
+ ```
826
+
827
+ ### What It Detects
828
+
829
+ | Category | Examples |
830
+ |----------|----------|
831
+ | **Security** | XSS vulnerabilities, `\|safe` on user input, sensitive data exposure |
832
+ | **Performance** | Heavy filters in loops, repeated calculations |
833
+ | **Accessibility** | Missing alt text, forms without labels |
834
+ | **Best Practices** | `{% for %}` without `{% empty %}`, deep nesting |
835
+
836
+ ### Provider Comparison
837
+
838
+ | Provider | API Key | Speed | Cost |
839
+ |----------|---------|-------|------|
840
+ | **Anthropic** | `ANTHROPIC_API_KEY` | Fast | Paid |
841
+ | **OpenAI** | `OPENAI_API_KEY` | Fast | Paid |
842
+ | **Groq** | `GROQ_API_KEY` | Very Fast | Free tier |
843
+ | **Ollama** | None (local) | Varies | Free |
844
+
845
+ Auto-detect priority: Anthropic → OpenAI → Groq → Ollama
846
+
847
+ ---
848
+
636
849
  ## Raw/Verbatim Tag
637
850
 
638
851
  Output template syntax without processing: