binja 0.8.1 → 0.9.1

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
@@ -7,9 +7,9 @@
7
7
  <p align="center">
8
8
  <a href="#installation">Installation</a> •
9
9
  <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>
10
+ <a href="#framework-adapters">Hono/Elysia</a> •
11
+ <a href="#multi-engine-support">Multi-Engine</a> •
12
+ <a href="#filters-84-built-in">Filters</a>
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -27,7 +27,8 @@
27
27
  |---------|-----------|------------------|
28
28
  | **Runtime Performance** | ✅ 2-4x faster | ❌ |
29
29
  | **AOT Compilation** | ✅ 160x faster | ❌ |
30
- | **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid | ❌ |
30
+ | **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid, Twig | ❌ |
31
+ | **Framework Adapters** | ✅ Hono, Elysia | ❌ |
31
32
  | Django DTL Compatible | ✅ 100% | ❌ Partial |
32
33
  | Jinja2 Compatible | ✅ Full | ⚠️ Limited |
33
34
  | Template Inheritance | ✅ | ⚠️ |
@@ -430,6 +431,7 @@ Binja supports multiple template engines through a unified API. All engines pars
430
431
  | **Jinja2/DTL** | `{{ var }}` `{% if %}` | Default, Python/Django compatibility |
431
432
  | **Handlebars** | `{{var}}` `{{#if}}` | JavaScript ecosystem, Ember.js |
432
433
  | **Liquid** | `{{ var }}` `{% if %}` | Shopify, Jekyll, static sites |
434
+ | **Twig** | `{{ var }}` `{% if %}` | PHP/Symfony, Drupal, Craft CMS |
433
435
 
434
436
  ### Usage
435
437
 
@@ -437,6 +439,7 @@ Binja supports multiple template engines through a unified API. All engines pars
437
439
  // Direct engine imports
438
440
  import * as handlebars from 'binja/engines/handlebars'
439
441
  import * as liquid from 'binja/engines/liquid'
442
+ import * as twig from 'binja/engines/twig'
440
443
 
441
444
  // Handlebars
442
445
  await handlebars.render('Hello {{name}}!', { name: 'World' })
@@ -447,6 +450,11 @@ await handlebars.render('{{{html}}}', { html: '<b>unescaped</b>' })
447
450
  await liquid.render('Hello {{ name }}!', { name: 'World' })
448
451
  await liquid.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
449
452
  await liquid.render('{% assign x = "value" %}{{ x }}', {})
453
+
454
+ // Twig (Symfony)
455
+ await twig.render('Hello {{ name }}!', { name: 'World' })
456
+ await twig.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
457
+ await twig.render('{{ name|upper }}', { name: 'world' })
450
458
  ```
451
459
 
452
460
  ### MultiEngine API
@@ -459,26 +467,117 @@ const engine = new MultiEngine()
459
467
  // Render with any engine
460
468
  await engine.render('Hello {{name}}!', { name: 'World' }, 'handlebars')
461
469
  await engine.render('Hello {{ name }}!', { name: 'World' }, 'liquid')
470
+ await engine.render('Hello {{ name }}!', { name: 'World' }, 'twig')
462
471
  await engine.render('Hello {{ name }}!', { name: 'World' }, 'jinja2')
463
472
 
464
473
  // Auto-detect from file extension
465
474
  import { detectEngine } from 'binja/engines'
466
- const eng = detectEngine('template.hbs') // Returns Handlebars engine
467
- const eng2 = detectEngine('page.liquid') // Returns Liquid engine
475
+ const eng = detectEngine('template.hbs') // Returns Handlebars engine
476
+ const eng2 = detectEngine('page.liquid') // Returns Liquid engine
477
+ const eng3 = detectEngine('page.twig') // Returns Twig engine
468
478
  ```
469
479
 
470
480
  ### Engine Feature Matrix
471
481
 
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}}}` | - |
482
+ | Feature | Jinja2 | Handlebars | Liquid | Twig |
483
+ |---------|--------|------------|--------|------|
484
+ | Variables | `{{ x }}` | `{{x}}` | `{{ x }}` | `{{ x }}` |
485
+ | Conditionals | `{% if %}` | `{{#if}}` | `{% if %}` | `{% if %}` |
486
+ | Loops | `{% for %}` | `{{#each}}` | `{% for %}` | `{% for %}` |
487
+ | Filters | `{{ x\|filter }}` | `{{ x }}` | `{{ x \| filter }}` | `{{ x\|filter }}` |
488
+ | Raw output | `{% raw %}` | - | `{% raw %}` | `{% raw %}` |
489
+ | Comments | `{# #}` | `{{! }}` | `{% comment %}` | `{# #}` |
490
+ | Assignment | `{% set %}` | - | `{% assign %}` | `{% set %}` |
491
+ | Unescaped | `{{ x\|safe }}` | `{{{x}}}` | - | `{{ x\|raw }}` |
492
+
493
+ ---
494
+
495
+ ## Framework Adapters
496
+
497
+ Binja provides first-class integration with Bun's most popular web frameworks.
498
+
499
+ ### Hono
500
+
501
+ ```typescript
502
+ import { Hono } from 'hono'
503
+ import { binja } from 'binja/hono'
504
+
505
+ const app = new Hono()
506
+
507
+ // Add binja middleware
508
+ app.use(binja({
509
+ root: './views', // Template directory
510
+ extension: '.html', // Default extension
511
+ engine: 'jinja2', // jinja2 | handlebars | liquid | twig
512
+ cache: true, // Cache compiled templates
513
+ globals: { siteName: 'My App' }, // Global context
514
+ layout: 'layouts/base', // Optional layout template
515
+ }))
516
+
517
+ // Render templates with c.render()
518
+ app.get('/', (c) => c.render('index', { title: 'Home' }))
519
+ app.get('/users/:id', async (c) => {
520
+ const user = await getUser(c.req.param('id'))
521
+ return c.render('users/profile', { user })
522
+ })
523
+
524
+ export default app
525
+ ```
526
+
527
+ ### Elysia
528
+
529
+ ```typescript
530
+ import { Elysia } from 'elysia'
531
+ import { binja } from 'binja/elysia'
532
+
533
+ const app = new Elysia()
534
+ // Add binja plugin
535
+ .use(binja({
536
+ root: './views',
537
+ extension: '.html',
538
+ engine: 'jinja2',
539
+ cache: true,
540
+ globals: { siteName: 'My App' },
541
+ layout: 'layouts/base',
542
+ }))
543
+ // Render templates with render()
544
+ .get('/', ({ render }) => render('index', { title: 'Home' }))
545
+ .get('/users/:id', async ({ render, params }) => {
546
+ const user = await getUser(params.id)
547
+ return render('users/profile', { user })
548
+ })
549
+ .listen(3000)
550
+
551
+ console.log('Server running at http://localhost:3000')
552
+ ```
553
+
554
+ ### Adapter Options
555
+
556
+ | Option | Type | Default | Description |
557
+ |--------|------|---------|-------------|
558
+ | `root` | `string` | `./views` | Template directory |
559
+ | `extension` | `string` | `.html` | Default file extension |
560
+ | `engine` | `string` | `jinja2` | Template engine (`jinja2`, `handlebars`, `liquid`, `twig`) |
561
+ | `cache` | `boolean` | `true` (prod) | Cache compiled templates |
562
+ | `debug` | `boolean` | `false` | Show error details |
563
+ | `globals` | `object` | `{}` | Global context variables |
564
+ | `layout` | `string` | - | Layout template path |
565
+ | `contentVar` | `string` | `content` | Content variable name in layout |
566
+
567
+ ### Cache Management
568
+
569
+ ```typescript
570
+ import { clearCache, getCacheStats } from 'binja/hono'
571
+ // or
572
+ import { clearCache, getCacheStats } from 'binja/elysia'
573
+
574
+ // Clear all cached templates
575
+ clearCache()
576
+
577
+ // Get cache statistics
578
+ const stats = getCacheStats()
579
+ console.log(stats) // { size: 10, keys: ['jinja2:./views/index.html', ...] }
580
+ ```
482
581
 
483
582
  ---
484
583
 
@@ -609,7 +708,7 @@ debugOptions: {
609
708
 
610
709
  ## CLI Tool
611
710
 
612
- Binja includes a CLI for template pre-compilation:
711
+ Binja includes a CLI for template pre-compilation and linting:
613
712
 
614
713
  ```bash
615
714
  # Compile all templates to JavaScript
@@ -620,6 +719,15 @@ binja check ./templates
620
719
 
621
720
  # Watch mode for development
622
721
  binja watch ./templates -o ./dist
722
+
723
+ # Lint templates (syntax check)
724
+ binja lint ./templates
725
+
726
+ # Lint with AI analysis (requires API key)
727
+ binja lint ./templates --ai
728
+
729
+ # Lint with specific AI provider
730
+ binja lint ./templates --ai=ollama
623
731
  ```
624
732
 
625
733
  ### Pre-compiled Templates
@@ -633,6 +741,104 @@ const html = render({ title: 'Home', items: [...] })
633
741
 
634
742
  ---
635
743
 
744
+ ## AI-Powered Linting (Optional)
745
+
746
+ Binja includes an optional AI-powered linting module that detects security issues, performance problems, accessibility concerns, and best practice violations.
747
+
748
+ ### Installation
749
+
750
+ The AI module is opt-in. Install the SDK for your preferred provider:
751
+
752
+ ```bash
753
+ # For Claude (Anthropic)
754
+ bun add @anthropic-ai/sdk
755
+
756
+ # For OpenAI
757
+ bun add openai
758
+
759
+ # For Ollama (local) - no package needed
760
+ # For Groq - no package needed
761
+ ```
762
+
763
+ ### Configuration
764
+
765
+ Set the API key for your provider:
766
+
767
+ ```bash
768
+ # Anthropic
769
+ export ANTHROPIC_API_KEY=sk-ant-...
770
+
771
+ # OpenAI
772
+ export OPENAI_API_KEY=sk-...
773
+
774
+ # Groq (free tier available)
775
+ export GROQ_API_KEY=gsk_...
776
+
777
+ # Ollama - no key needed, just run: ollama serve
778
+ ```
779
+
780
+ ### Usage
781
+
782
+ #### CLI
783
+
784
+ ```bash
785
+ # Lint with AI (auto-detect provider)
786
+ binja lint ./templates --ai
787
+
788
+ # Use specific provider
789
+ binja lint ./templates --ai=anthropic
790
+ binja lint ./templates --ai=openai
791
+ binja lint ./templates --ai=ollama
792
+ binja lint ./templates --ai=groq
793
+
794
+ # JSON output for CI/CD
795
+ binja lint ./templates --ai --format=json
796
+ ```
797
+
798
+ #### Programmatic
799
+
800
+ ```typescript
801
+ import { lint } from 'binja/ai'
802
+
803
+ // Auto-detect provider from environment
804
+ const result = await lint(template)
805
+
806
+ // Specify provider and API key directly
807
+ const result = await lint(template, {
808
+ provider: 'anthropic',
809
+ apiKey: 'sk-ant-...',
810
+ model: 'claude-sonnet-4-20250514'
811
+ })
812
+
813
+ // Check results
814
+ console.log(result.errors) // Syntax errors
815
+ console.log(result.warnings) // Security, performance issues
816
+ console.log(result.suggestions) // Best practice recommendations
817
+ console.log(result.provider) // Which AI was used
818
+ ```
819
+
820
+ ### What It Detects
821
+
822
+ | Category | Examples |
823
+ |----------|----------|
824
+ | **Security** | XSS vulnerabilities, `\|safe` on user input, sensitive data exposure |
825
+ | **Performance** | Heavy filters in loops, repeated calculations |
826
+ | **Accessibility** | Missing alt text, forms without labels |
827
+ | **Best Practices** | `{% for %}` without `{% empty %}`, deep nesting |
828
+
829
+ ### Provider Comparison
830
+
831
+ | Provider | API Key | Speed | Cost |
832
+ |----------|---------|-------|------|
833
+ | **Anthropic** | `ANTHROPIC_API_KEY` | Fast | Paid |
834
+ | **OpenAI** | `OPENAI_API_KEY` | Fast | Paid |
835
+ | **Groq** | `GROQ_API_KEY` | Very Fast | Free tier |
836
+ | **Ollama** | None (local) | Varies | Free |
837
+
838
+ Auto-detect priority: Anthropic → OpenAI → Groq → Ollama
839
+
840
+ ---
841
+
636
842
  ## Raw/Verbatim Tag
637
843
 
638
844
  Output template syntax without processing:
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Elysia Adapter for binja
3
+ * Seamless integration with Elysia framework
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * import { Elysia } from 'elysia'
8
+ * import { binja } from 'binja/elysia'
9
+ *
10
+ * const app = new Elysia()
11
+ * .use(binja({ root: './views' }))
12
+ * .get('/', ({ render }) => render('index', { title: 'Home' }))
13
+ * .listen(3000)
14
+ * ```
15
+ */
16
+ import type { Elysia } from 'elysia';
17
+ export interface BinjaElysiaOptions {
18
+ /** Root directory for templates (default: './views') */
19
+ root?: string;
20
+ /** Default file extension (default: '.html') */
21
+ extension?: string;
22
+ /** Template engine: 'jinja2' | 'handlebars' | 'liquid' | 'twig' (default: 'jinja2') */
23
+ engine?: 'jinja2' | 'handlebars' | 'liquid' | 'twig';
24
+ /** Enable debug panel (default: false) */
25
+ debug?: boolean;
26
+ /** Cache compiled templates (default: true in production) */
27
+ cache?: boolean;
28
+ /** Global context data available in all templates */
29
+ globals?: Record<string, any>;
30
+ /** Layout template name (optional) */
31
+ layout?: string;
32
+ /** Content variable name in layout (default: 'content') */
33
+ contentVar?: string;
34
+ }
35
+ /**
36
+ * Create binja plugin for Elysia
37
+ */
38
+ export declare function binja(options?: BinjaElysiaOptions): (app: Elysia) => Elysia<"", {
39
+ decorator: {};
40
+ store: {};
41
+ derive: {};
42
+ resolve: {};
43
+ }, {
44
+ typebox: {};
45
+ error: {};
46
+ }, {
47
+ schema: {};
48
+ standaloneSchema: {};
49
+ macro: {};
50
+ macroFn: {};
51
+ parser: {};
52
+ response: {};
53
+ }, {}, {
54
+ derive: {};
55
+ resolve: {};
56
+ schema: {};
57
+ standaloneSchema: {};
58
+ response: {};
59
+ }, {
60
+ derive: {
61
+ readonly render: (template: string, context?: Record<string, any>) => Promise<Response>;
62
+ };
63
+ resolve: {};
64
+ schema: {};
65
+ standaloneSchema: {};
66
+ response: import("elysia").ExtractErrorFromHandle<{
67
+ readonly render: (template: string, context?: Record<string, any>) => Promise<Response>;
68
+ }>;
69
+ }>;
70
+ /**
71
+ * Clear template cache
72
+ */
73
+ export declare function clearCache(): void;
74
+ /**
75
+ * Get cache stats
76
+ */
77
+ export declare function getCacheStats(): {
78
+ size: number;
79
+ keys: string[];
80
+ };
81
+ export default binja;
82
+ //# sourceMappingURL=elysia.d.ts.map