mutorjs 1.3.0 → 1.3.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
@@ -1,140 +1,1665 @@
1
- # Mutor.js
1
+ # Mutor.js — Official Documentation
2
2
 
3
- > **The Secure, AST-Powered Template Engine.**
3
+ > **Version:** Current Stable
4
+ > **Author:** Onah Victor
5
+ > **Repository:** [github.com/allAboutJS/Mutor.js](https://github.com/allAboutJS/Mutor.js)
6
+ > **Language:** TypeScript (zero runtime dependencies)
7
+ > **License:** See repository
4
8
 
5
- Most template engines force a choice: **Extreme Speed** (using unsafe string manipulation and `eval`) or **Total Security** (using slow interpreters). Mutor.js refuses to compromise. By utilizing an Abstract Syntax Tree (AST) to compile templates into pure, optimized JavaScript, Mutor delivers top-tier performance while running in a strictly sandboxed environment.
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ 1. [Introduction](#1-introduction)
14
+ 2. [Installation](#2-installation)
15
+ 3. [Quick Start](#3-quick-start)
16
+ 4. [Core Concepts](#4-core-concepts)
17
+ 5. [Syntax Guide](#5-syntax-guide)
18
+ 6. [Logic System](#6-logic-system)
19
+ 7. [Expression System](#7-expression-system)
20
+ 8. [Includes](#8-includes)
21
+ 9. [Components](#9-components)
22
+ 10. [Rendering APIs](#10-rendering-apis)
23
+ 11. [Security Architecture](#11-security-architecture)
24
+ 12. [Sandbox System](#12-sandbox-system)
25
+ 13. [Property Access Validation](#13-property-access-validation)
26
+ 14. [Namespace System](#14-namespace-system)
27
+ 15. [Cache System](#15-cache-system)
28
+ 16. [Configuration Reference](#16-configuration-reference)
29
+ 17. [Delimiter Customization](#17-delimiter-customization)
30
+ 18. [Whitespace Control](#18-whitespace-control)
31
+ 19. [Escaping Rules](#19-escaping-rules)
32
+ 20. [Error Handling](#20-error-handling)
33
+ 21. [CLI](#21-cli)
34
+ 22. [Performance](#22-performance)
35
+ 23. [Environment Support](#23-environment-support)
36
+ 24. [Browser Usage](#24-browser-usage)
37
+ 25. [Server Usage](#25-server-usage)
38
+ 26. [Advanced Examples](#26-advanced-examples)
39
+ 27. [Best Practices](#27-best-practices)
40
+ 28. [Security Best Practices](#28-security-best-practices)
41
+ 29. [Real-World Use Cases](#29-real-world-use-cases)
42
+ 30. [Design Tradeoffs](#30-design-tradeoffs)
43
+ 31. [Future Roadmap](#31-future-roadmap)
44
+ 32. [FAQ](#32-faq)
45
+ 33. [API Reference](#33-api-reference)
46
+
47
+ ---
48
+
49
+ ## 1. Introduction
50
+
51
+ Mutor.js is a secure, compiler-oriented template engine written entirely in TypeScript. It is designed for environments where template output correctness, security, and execution speed all matter simultaneously — conditions under which most template engines require a tradeoff.
52
+
53
+ Mutor.js takes a different approach. Rather than performing string interpolation at runtime or relying on `eval`-based tricks, it processes templates through a full compiler pipeline: tokenization, AST construction, semantic analysis, security validation, code generation, and finally compilation into a native JavaScript function. That compiled function is then cached and reused for every subsequent render, making cold-path overhead a one-time cost.
54
+
55
+ **Mutor.js is not:**
56
+
57
+ - A frontend framework.
58
+ - A JSX alternative.
59
+ - An SSR framework or a React-like rendering system.
60
+
61
+ It is a template engine. It accepts a template string and a context object. It returns a rendered string. That output can be HTML, plain text, XML, or any other string-based format. What you build on top of Mutor.js — a static site generator, a server-side rendering layer, an email renderer — is your concern. Mutor.js concerns itself exclusively with doing that one job securely and fast.
62
+
63
+ **Mutor.js is:**
64
+
65
+ - A zero-dependency, TypeScript-native template engine.
66
+ - A compiler that produces JavaScript functions, not an interpreter.
67
+ - A security-first system with layered compile-time and runtime protection.
68
+ - A configurable engine that does not assume your use case.
69
+
70
+ ---
71
+
72
+ ## 2. Installation
73
+
74
+ Mutor.js is distributed as an npm package. It requires Node.js 14+ or any ES6-compatible runtime.
75
+
76
+ ```bash
77
+ npm install mutorjs
78
+ ```
79
+
80
+ ```bash
81
+ yarn add mutorjs
82
+ ```
83
+
84
+ ```bash
85
+ pnpm add mutorjs
86
+ ```
87
+
88
+ Both CommonJS and ESM module formats are supported. The package ships pre-compiled TypeScript output with bundled type declarations.
89
+
90
+ ### Subpath Exports
91
+
92
+ Mutor.js exposes two entry points:
93
+
94
+ | Import Path | Description |
95
+ |---|---|
96
+ | `mutorjs` | Universal entry point. Safe for both browser and Node.js environments. Does not include file-system APIs. |
97
+ | `mutorjs/server` | Server-only entry point. Includes `renderFile` and file-system-dependent functionality. Do not import this in browser bundles. |
98
+
99
+ ```js
100
+ // Universal (browser + server)
101
+ import Mutor from "mutorjs";
102
+
103
+ // Server-only
104
+ import Mutor from "mutorjs/server";
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 3. Quick Start
110
+
111
+ ### Basic Render
112
+
113
+ ```js
114
+ import Mutor from "mutorjs";
115
+
116
+ const mutor = new Mutor();
117
+
118
+ const output = mutor.render(
119
+ `<h1>{{ greeting }}, {{ user.name }}!</h1>`,
120
+ { greeting: "Hello", user: { name: "Onah" } }
121
+ );
122
+
123
+ console.log(output);
124
+ // <h1>Hello, Onah!</h1>
125
+ ```
126
+
127
+ ### Compile Once, Execute Many Times
128
+
129
+ ```js
130
+ import Mutor from "mutorjs";
131
+
132
+ const mutor = new Mutor();
133
+
134
+ const fn = mutor.compile(`<p>{{ item.title }}</p>`);
135
+
136
+ const items = [
137
+ { title: "Alpha" },
138
+ { title: "Beta" },
139
+ { title: "Gamma" },
140
+ ];
141
+
142
+ const rendered = items.map(item => fn({ item }));
143
+ ```
144
+
145
+ ### File Rendering (Server Only)
146
+
147
+ ```js
148
+ import Mutor from "mutorjs/server";
149
+
150
+ const mutor = new Mutor();
151
+
152
+ const html = await mutor.renderFile("./views/index.html", {
153
+ title: "Dashboard",
154
+ user: req.user,
155
+ });
156
+
157
+ res.send(html);
158
+ ```
159
+
160
+ ### Component Rendering
161
+
162
+ ```js
163
+ import Mutor from "mutorjs";
164
+
165
+ const mutor = new Mutor();
166
+
167
+ mutor.registerComponent("card", `
168
+ <div class="card">
169
+ <h2>{{ card.title }}</h2>
170
+ <p>{{ card.body }}</p>
171
+ </div>
172
+ `);
173
+
174
+ const output = mutor.renderComponent("card", {
175
+ card: { title: "Notice", body: "This is a component." }
176
+ });
177
+ ```
178
+
179
+ ---
180
+
181
+ ## 4. Core Concepts
182
+
183
+ ### Templates Are Compiled, Not Interpreted
184
+
185
+ Every template processed by Mutor.js is compiled into a JavaScript function. This happens once per unique template string. On subsequent calls with the same template, the compiled function is retrieved from cache and invoked directly. There is no parsing, no AST traversal, no validation on the hot path — only a function call.
186
+
187
+ This distinction matters at scale. A template engine that interprets its AST on every render pays a traversal cost proportional to the template's complexity on every single request. Mutor.js pays that cost exactly once.
188
+
189
+ ### Security Is Architectural, Not Bolt-On
190
+
191
+ Most template engines treat security as a configuration layer — a set of filters you can optionally enable. In Mutor.js, security is woven into the compilation pipeline itself. The semantic analysis phase validates all property access, identifies potentially unsafe patterns, and either rewrites them into guarded forms or rejects them outright. By the time a compiled function reaches runtime, the security decisions have already been made.
192
+
193
+ This means you cannot accidentally bypass security by misconfiguring a flag. The dangerous paths do not exist in the compiled output.
194
+
195
+ ### Context Is an Isolated Scope
196
+
197
+ Templates execute inside a restricted scope. The context object you provide is the entire world available to the template. There is no access to the global object, no access to `process`, no access to `require`, no access to `window`. The sandbox is enforced at code generation time: the generated function receives only what you pass in.
198
+
199
+ ### Zero Dependencies
200
+
201
+ Mutor.js has no runtime dependencies. It is self-contained TypeScript. This is a deliberate architectural constraint. Every dependency you introduce to a security-critical system is a potential attack surface. Mutor.js owns its entire stack — tokenizer, parser, AST, code generator, runtime, cache — and each of those components is auditable without pulling in third-party code.
202
+
203
+ ---
204
+
205
+ ## 5. Syntax Guide
206
+
207
+ Mutor.js uses a structured tag syntax. It is not raw JavaScript embedded in HTML. The template language is a defined, bounded DSL that the compiler can reason about completely.
208
+
209
+ ### Interpolation
210
+
211
+ The primary mechanism for outputting values is the interpolation block:
212
+
213
+ ```html
214
+ {{ expression }}
215
+ ```
216
+
217
+ The result of the expression is HTML-escaped by default and inserted into the output at that position.
218
+
219
+ ```html
220
+ <p>{{ user.name }}</p>
221
+ <p>{{ product.price * 1.1 }}</p>
222
+ <p>{{ user.bio ?? "No bio provided." }}</p>
223
+ ```
224
+
225
+ ### Whitespace Trimming
226
+
227
+ By default, Mutor.js preserves whitespace around tags exactly as written. The `~` directive instructs the engine to strip whitespace (including newlines) from the relevant side of the tag.
228
+
229
+ ```html
230
+ {{~ expression }} <!-- trim left whitespace -->
231
+ {{ expression ~}} <!-- trim right whitespace -->
232
+ {{~ expression ~}} <!-- trim both sides -->
233
+ ```
234
+
235
+ This is particularly useful when working with `for` loops and `if` blocks to prevent unwanted blank lines in output.
236
+
237
+ ### Comments
238
+
239
+ Comments use the `#` prefix inside the opening delimiter:
240
+
241
+ ```html
242
+ {{# This is a comment. It produces no output. }}
243
+ ```
244
+
245
+ Comments are multiline by default:
246
+
247
+ ```html
248
+ {{#
249
+ This is a block comment.
250
+ It spans multiple lines.
251
+ None of this appears in output.
252
+ }}
253
+ ```
254
+
255
+ ### Escaped Opening Tag
256
+
257
+ To output the literal opening delimiter (e.g., `{{`) without triggering template parsing, prefix it with a backslash:
258
+
259
+ ```html
260
+ \{{ this is rendered literally as {{ }}
261
+ ```
262
+
263
+ Whether the escape character itself is preserved in the output is controlled by `keepOpeningTagEscapeDelimiter` in configuration.
264
+
265
+ ### Tag Summary
266
+
267
+ | Syntax | Purpose |
268
+ |---|---|
269
+ | `{{ expr }}` | Interpolation (auto-escaped) |
270
+ | `{{~ expr }}` | Interpolation, trim left |
271
+ | `{{ expr ~}}` | Interpolation, trim right |
272
+ | `{{~ expr ~}}` | Interpolation, trim both |
273
+ | `{{# ... }}` | Comment (no output) |
274
+ | `\{{` | Escaped literal delimiter |
275
+ | `{{ if ... }}` | Conditional block open |
276
+ | `{{ else if ... }}` | Conditional branch |
277
+ | `{{ else }}` | Default branch |
278
+ | `{{ for x of ... }}` | Iteration (value) |
279
+ | `{{ for x in ... }}` | Iteration (key) |
280
+ | `{{ end }}` | Closes any open block |
281
+
282
+ ---
283
+
284
+ ## 6. Logic System
285
+
286
+ Mutor.js provides structured control flow via `if`, `else if`, `else`, `for`, and `end` tags. These are not arbitrary JavaScript — they are a fixed set of recognized logic directives that the parser handles explicitly.
287
+
288
+ ### Conditionals
289
+
290
+ ```html
291
+ {{ if condition }}
292
+ ...
293
+ {{ end }}
294
+ ```
295
+
296
+ ```html
297
+ {{ if user.role == "admin" }}
298
+ <a href="/admin">Admin Panel</a>
299
+ {{ else if user.role == "moderator" }}
300
+ <a href="/moderate">Moderation Queue</a>
301
+ {{ else }}
302
+ <a href="/dashboard">Dashboard</a>
303
+ {{ end }}
304
+ ```
305
+
306
+ Conditions are arbitrary expressions. Any expression that evaluates to a truthy or falsy value is valid. Comparison operators, boolean operators, nullish coalescing, optional chaining, and ternaries are all valid inside condition expressions.
307
+
308
+ ```html
309
+ {{ if items.length > 0 && user.isActive }}
310
+ ...
311
+ {{ end }}
312
+ ```
313
+
314
+ ### Iteration
315
+
316
+ Mutor.js supports two forms of `for` iteration, mirroring JavaScript's `for...of` and `for...in` semantics:
317
+
318
+ **`for...of` — iterate over values:**
319
+
320
+ ```html
321
+ {{ for item of items }}
322
+ <li>{{ item.name }}</li>
323
+ {{ end }}
324
+ ```
325
+
326
+ **`for...in` — iterate over keys:**
327
+
328
+ ```html
329
+ {{ for key in record }}
330
+ <dt>{{ key }}</dt>
331
+ {{ end }}
332
+ ```
333
+
334
+ **Nested loops:**
335
+
336
+ ```html
337
+ {{ for category of categories }}
338
+ <section>
339
+ <h2>{{ category.name }}</h2>
340
+ {{ for product of category.products }}
341
+ <p>{{ product.title }} — {{ product.price }}</p>
342
+ {{ end }}
343
+ </section>
344
+ {{ end }}
345
+ ```
346
+
347
+ ### Block Termination
348
+
349
+ Every `if` block and every `for` block must be closed with `{{ end }}`. Unclosed blocks are a compile-time error. The compiler tracks block depth and reports the location (line and column) of the unclosed block.
350
+
351
+ > **Note:** There is no `break` or `continue` directive in the current version. Complex filtering logic belongs in the context object, not the template.
352
+
353
+ ---
354
+
355
+ ## 7. Expression System
356
+
357
+ Mutor.js expressions are a defined subset of JavaScript expressions, parsed by Mutor.js's own expression parser — meaning the engine controls exactly what is and is not expressible.
358
+
359
+ ### Supported Expression Constructs
360
+
361
+ | Category | Examples |
362
+ |---|---|
363
+ | Arithmetic | `a + b`, `price * 1.2`, `total / count`, `n % 2`, `2 ** 8` |
364
+ | Comparison | `a == b`, `a != b`, `a > b`, `a >= b`, `a < b`, `a <= b` |
365
+ | Boolean | `a && b`, `a \|\| b`, `!flag` |
366
+ | Ternary | `condition ? valueA : valueB` |
367
+ | Nullish Coalescing | `value ?? "default"` |
368
+ | Optional Chaining | `user?.address?.city` |
369
+ | Property Access | `obj.prop`, `obj["key"]` |
370
+ | Array Access | `arr[0]`, `arr[index]` |
371
+ | Namespace Access | `Math::floor(n)`, `JSON::stringify(data)` |
372
+ | Bitwise | `a & b`, `a \| b`, `a ^ b`, `a >> b`, `a << b` |
373
+ | Function Calls | `fn(args)` — only when `allowFnCalls: true` |
374
+ | String Literals | `'single'`, `"double"`, `` `backtick` `` |
375
+
376
+ ### Operator Precedence
377
+
378
+ Mutor.js follows standard JavaScript operator precedence:
379
+
380
+ | Precedence (high → low) | Operators |
381
+ |---|---|
382
+ | Unary | `!`, unary `-` |
383
+ | Exponentiation | `**` |
384
+ | Multiplicative | `*`, `/`, `%` |
385
+ | Additive | `+`, `-` |
386
+ | Shift | `<<`, `>>` |
387
+ | Relational | `<`, `>`, `<=`, `>=` |
388
+ | Equality | `==`, `!=` |
389
+ | Bitwise AND | `&` |
390
+ | Bitwise XOR | `^` |
391
+ | Bitwise OR | `\|` |
392
+ | Logical AND | `&&` |
393
+ | Logical OR | `\|\|` |
394
+ | Nullish Coalescing | `??` |
395
+ | Conditional (Ternary) | `? :` |
396
+
397
+ When in doubt, use explicit parentheses:
398
+
399
+ ```html
400
+ {{ (a + b) * c }}
401
+ {{ user.age >= 18 ? "adult" : "minor" }}
402
+ ```
403
+
404
+ ### Strings
405
+
406
+ String literals support single quotes, double quotes, and backticks. Backtick strings are treated as static strings — template interpolation (`${...}`) inside strings does not evaluate expressions. The backtick form exists to allow strings containing both single and double quotes conveniently.
407
+
408
+ ```html
409
+ {{ "Hello, world" }}
410
+ {{ 'It\'s fine' }}
411
+ {{ `This has "quotes" and 'apostrophes'` }}
412
+ ```
413
+
414
+ ### Function Calls
415
+
416
+ Function calls are disabled by default. This prevents templates from invoking arbitrary functions that may exist on context objects, which is the correct default for most use cases.
417
+
418
+ To enable function calls:
419
+
420
+ ```js
421
+ const mutor = new Mutor({ allowFnCalls: true });
422
+ ```
423
+
424
+ When enabled, calls like `item.name.toUpperCase()` or `Math::floor(price)` are permitted. Namespace function calls (`Math::floor`, etc.) are permitted regardless of `allowFnCalls` because they are routed through the controlled namespace system.
425
+
426
+ > **Warning:** Enabling `allowFnCalls` expands the trusted surface area of your templates. Ensure the context objects you pass contain only functions you intend to expose. Do not pass raw service objects or request objects as top-level context when `allowFnCalls` is enabled.
427
+
428
+ ---
429
+
430
+ ## 8. Includes
431
+
432
+ Includes allow a template to embed another template file at a specific location in its output. The included template is compiled and cached independently, sharing the same cache as the parent instance.
433
+
434
+ ### Syntax
435
+
436
+ ```html
437
+ {{ Mutor::include("./partials/header.html") }}
438
+ {{ Mutor::include("./partials/nav.html", navLinks) }}
439
+ ```
440
+
441
+ When no context argument is provided, the include inherits the current rendering context automatically. For direct access to the current context, use `Mutor::$$context`:
442
+
443
+ ```html
444
+ {{ Mutor::include("./partials/footer.html", Mutor::$$context) }}
445
+ ```
446
+
447
+ ### Include Resolution
448
+
449
+ Include paths are resolved relative to the file being rendered when using `renderFile`. In a browser environment, include paths are treated as names of components registered with `mutor.registerComponent`. Attempting to include a non-registered component, or a file that does not exist, throws an error.
450
+
451
+ ### Circular Include Detection
452
+
453
+ Mutor.js tracks the include chain across all recursive resolutions. If a template attempts to include a file already in the current include stack, the engine throws rather than entering infinite recursion:
454
+
455
+ ```
456
+ MutorError: Circular include detected: ./partials/a.html → ./partials/b.html → ./partials/a.html
457
+ ```
458
+
459
+ ---
460
+
461
+ ## 9. Components
462
+
463
+ Components are named templates registered on a Mutor instance. They are the browser-compatible alternative to file-based includes, and are also useful on the server when you want to manage templates programmatically.
464
+
465
+ ### Registering a Component
466
+
467
+ ```js
468
+ mutor.registerComponent("alert", `
469
+ <div class="alert alert--{{ alert.type }}">
470
+ <strong>{{ alert.title }}</strong>
471
+ <p>{{ alert.message }}</p>
472
+ </div>
473
+ `);
474
+ ```
475
+
476
+ ### Rendering a Component
477
+
478
+ ```js
479
+ const html = mutor.renderComponent("alert", {
480
+ alert: {
481
+ type: "error",
482
+ title: "Validation Failed",
483
+ message: "Email address is required.",
484
+ }
485
+ });
486
+ ```
487
+
488
+ ### Components vs. Includes
489
+
490
+ | Feature | Components | Includes |
491
+ |---|---|---|
492
+ | Template source | In-memory string | File system |
493
+ | Browser-compatible | Yes | No |
494
+ | Circular detection | Yes | Yes |
495
+ | Cache | Same instance cache | Same instance cache |
496
+ | Context handling | Explicit argument | Explicit or `$$context` |
497
+
498
+ Components are compiled and cached on first `renderComponent` call. Subsequent calls hit the cache directly.
499
+
500
+ ---
501
+
502
+ ## 10. Rendering APIs
503
+
504
+ ### `compile(template: string): (context: object) => string`
505
+
506
+ Compiles a template string into an executable function. The resulting function accepts a context object and returns the rendered string.
507
+
508
+ ```js
509
+ const fn = mutor.compile(`<title>{{ page.title }}</title>`);
510
+ const output = fn({ page: { title: "Home" } });
511
+ ```
512
+
513
+ Calling `compile` with the same template string twice returns the cached compiled function.
514
+
515
+ ### `render(template: string, context: object): string`
516
+
517
+ Convenience wrapper around `compile`. Compiles the template if not already cached, then immediately invokes it with the provided context.
518
+
519
+ ```js
520
+ const output = mutor.render(`<p>{{ msg }}</p>`, { msg: "Hello" });
521
+ ```
522
+
523
+ ### `renderFile(path: string, context: object): Promise<string>`
524
+
525
+ Server-only. Reads the template file from disk, compiles it, and renders it with the given context. The compiled result is cached by resolved file path — repeated calls with the same path do not re-read the file or re-compile.
526
+
527
+ ```js
528
+ const html = await mutor.renderFile("./views/home.html", context);
529
+ ```
530
+
531
+ > This method is not available when importing from `mutorjs` (the universal entry point). Import from `mutorjs/server` to access it.
532
+
533
+ ### `registerComponent(name: string, template: string): void`
534
+
535
+ Registers a named template string as a component on the current Mutor instance. Compilation is deferred to first render.
536
+
537
+ ### `renderComponent(name: string, context: object): string`
538
+
539
+ Renders a previously registered component by name. Throws `MutorComponentError` if the component has not been registered.
540
+
541
+ ### Instance Isolation
542
+
543
+ Each `Mutor` instance maintains its own compilation cache and component registry. Two instances do not share state. This is important in multi-tenant or per-request environments where isolation between rendering contexts is required.
544
+
545
+ ---
546
+
547
+ ## 11. Security Architecture
548
+
549
+ Security in Mutor.js is not an optional layer. It is the reason the compilation pipeline exists in the form it does.
550
+
551
+ ### Threat Model
552
+
553
+ Mutor.js is designed for environments where the template may reference user-controlled data, and in some configurations, where the template itself may originate from less-trusted sources. The primary threats are:
554
+
555
+ - **Cross-site scripting (XSS):** Injecting HTML or JavaScript through interpolated values.
556
+ - **Prototype pollution access:** Using `__proto__`, `constructor`, or `prototype` to escape the context object and reach dangerous properties.
557
+ - **Unintended function invocation:** Calling methods on context objects that were not intended to be exposed to templates.
558
+ - **Global scope leakage:** Accessing `process`, `window`, `global`, `require`, or other environmental globals.
559
+ - **Computed property injection:** Using dynamic property names to bypass static property validation.
560
+
561
+ ### Defense Layers
562
+
563
+ Mutor.js employs defense in depth across three distinct phases:
564
+
565
+ **Layer 1 — Compile-time analysis**
566
+
567
+ Every property access in the template is analyzed during compilation. Static accesses (`obj.prop`, `obj["key"]`) are validated against the forbidden property list. Dynamic accesses (`obj[expression]`) are rewritten into calls to a runtime guard helper. Access to `__proto__`, `constructor`, or `prototype` is unconditionally rejected — there is no configuration override.
568
+
569
+ **Layer 2 — Code generation sandboxing**
570
+
571
+ The generated function runs inside a closed scope. It receives only the context object as its argument. It has no reference to any global, no `this` binding to the environment, and no access to `require` or `import`.
572
+
573
+ **Layer 3 — Runtime context validation**
574
+
575
+ Before the compiled function executes, Mutor.js validates the context object. Prototype chains are checked, and objects with polluted prototypes or non-standard getters and setters on built-in prototype properties are rejected. This prevents an attacker from crafting a context object that passes compile-time checks but exploits runtime behavior.
576
+
577
+ ### Output Escaping
578
+
579
+ All interpolated values are HTML-escaped by default:
580
+
581
+ | Character | Escaped As |
582
+ |---|---|
583
+ | `&` | `&amp;` |
584
+ | `<` | `&lt;` |
585
+ | `>` | `&gt;` |
586
+ | `"` | `&quot;` |
587
+ | `'` | `&#39;` |
588
+
589
+ Auto-escaping is controlled by the `autoEscape` configuration option. Setting `autoEscape: false` disables this behavior globally.
590
+
591
+ > **Warning:** Do not disable `autoEscape` unless you have a specific, justified reason and you understand the full implications. Disabled auto-escaping in combination with user-controlled context data is an XSS vector.
592
+
593
+ ---
594
+
595
+ ## 12. Sandbox System
596
+
597
+ The sandbox ensures compiled templates cannot access anything outside the explicitly provided context.
598
+
599
+ ### What the Sandbox Prevents
600
+
601
+ - Access to JavaScript globals (`window`, `process`, `global`, `require`, `globalThis`)
602
+ - Access to `this` in a way that reaches the outer environment
603
+ - Access to prototype chain escape vectors (`__proto__`, `constructor`, `prototype`)
604
+
605
+ ### Function Call Sandboxing
606
+
607
+ When `allowFnCalls: false` (the default), the compiler rejects any expression that involves a function invocation on a non-namespace identifier. `user.name.toUpperCase()` would be rejected. `Math::floor(price)` is permitted because it routes through the namespace system.
608
+
609
+ When `allowFnCalls: true`, method calls on context properties are permitted. The sandbox still prevents access to globals; it only allows invocation of functions that exist on the context object you explicitly pass in.
610
+
611
+ ### Forbidden Property List
612
+
613
+ The default forbidden properties are `__proto__`, `constructor`, and `prototype`. These are always enforced and cannot be removed. You can extend the list for your specific threat model:
614
+
615
+ ```js
616
+ const mutor = new Mutor({
617
+ forbiddenProps: new Set(["__proto__", "constructor", "prototype", "valueOf", "toString"]),
618
+ });
619
+ ```
620
+
621
+ > **Note:** If you add a property to both `allowedProps` and `forbiddenProps`, `allowedProps` takes precedence and the property will not be blocked.
622
+
623
+ ---
624
+
625
+ ## 13. Property Access Validation
626
+
627
+ ### Static Property Access
628
+
629
+ ```js
630
+ user.name // static — property name known at compile time
631
+ user["email"] // static — property name known at compile time
632
+ ```
633
+
634
+ For static access, the compiler checks the property name against the forbidden list at compile time. If the name is forbidden, compilation fails immediately with a clear error.
635
+
636
+ ### Dynamic Property Access
637
+
638
+ ```js
639
+ obj[someVariable] // dynamic — property name not known until runtime
640
+ ```
641
+
642
+ Dynamic access cannot be fully validated at compile time. Mutor.js handles this by rewriting all dynamic property accesses through a runtime guard that validates the resolved property name against the forbidden list before performing the access. Even if an attacker sets `someVariable = "__proto__"` at runtime, the guard catches it.
643
+
644
+ ---
645
+
646
+ ## 14. Namespace System
647
+
648
+ The namespace system provides controlled access to JavaScript built-in utilities inside templates without exposing the full global scope.
649
+
650
+ ### Syntax
651
+
652
+ ```html
653
+ {{ Namespace::identifier }}
654
+ {{ Namespace::method(args) }}
655
+ ```
656
+
657
+ The `::` operator is Mutor.js-specific. It does not exist in standard JavaScript. It is parsed by Mutor.js and translated into safe, controlled lookups at code generation time.
658
+
659
+ ### Available Namespaces
660
+
661
+ | Namespace | Accessible Members |
662
+ |---|---|
663
+ | `Math` | All `Math` static methods and properties (`floor`, `ceil`, `round`, `max`, `min`, `abs`, `sqrt`, `pow`, `PI`, etc.) |
664
+ | `JSON` | `stringify`, `parse` |
665
+ | `Object` | `keys`, `values`, `entries`, `assign`, `fromEntries` |
666
+ | `Array` | `isArray`, `from` |
667
+ | `String` | `fromCharCode` |
668
+ | `Number` | `isInteger`, `isFinite`, `isNaN`, `parseInt`, `parseFloat` |
669
+ | `Date` | `now`, `getFullYear` (static forms) |
670
+ | `Mutor` | `include`, `$$context` (engine directives) |
671
+
672
+ ### Examples
673
+
674
+ ```html
675
+ <p>Price: {{ Math::floor(product.price) }} USD</p>
676
+ <p>Keys: {{ Object::keys(record).length }}</p>
677
+ <p>Data: {{ JSON::stringify(debug.payload) }}</p>
678
+ <p>Valid: {{ Array::isArray(items) ? "yes" : "no" }}</p>
679
+ ```
680
+
681
+ ### The `Mutor` Namespace
682
+
683
+ The `Mutor` namespace is reserved for engine directives:
684
+
685
+ - `Mutor::include(path)` — includes a template file, inheriting the current context
686
+ - `Mutor::include(path, context)` — includes a template file with an explicit context
687
+ - `Mutor::$$context` — a reference to the current rendering context object
688
+
689
+ ---
690
+
691
+ ## 15. Cache System
692
+
693
+ Every `Mutor` instance maintains an internal LRU (Least Recently Used) cache of compiled template functions.
694
+
695
+ ### Cache Behavior
696
+
697
+ - Templates are cached by their source string (for `compile` and `render`) or by their resolved absolute file path (for `renderFile`).
698
+ - Cache lookups are O(1).
699
+ - When a template is accessed, it is promoted to the most-recently-used position.
700
+ - When the cache reaches its configured `maxSize`, the least-recently-used entry is evicted.
701
+
702
+ ### Cache Configuration
703
+
704
+ ```js
705
+ const mutor = new Mutor({
706
+ cache: {
707
+ active: true,
708
+ maxSize: 50 * 1024 * 1024, // 50MB
709
+ }
710
+ });
711
+ ```
712
+
713
+ | Option | Type | Default | Description |
714
+ |---|---|---|---|
715
+ | `active` | `boolean` | `true` | Whether caching is enabled. Set to `false` to recompile on every render (useful for development). |
716
+ | `maxSize` | `number` | `52428800` (50MB) | Maximum cache memory in bytes. When exceeded, LRU eviction runs. |
717
+
718
+ ### Disabling the Cache
719
+
720
+ ```js
721
+ const mutor = new Mutor({
722
+ cache: { active: false }
723
+ });
724
+ ```
725
+
726
+ With the cache disabled, every `render` or `renderFile` call runs the full compiler pipeline. This is useful in development for picking up template changes without restarting the process, but is not appropriate for production.
727
+
728
+ ### Manual Cache Control
729
+
730
+ ```js
731
+ // Clear the entire cache
732
+ mutor.reset();
733
+
734
+ // Get cache diagnostics (memory usage, entry count)
735
+ const diagnostics = mutor.getDiagnostics();
736
+ ```
737
+
738
+ > **Note:** `reset()` is synchronous and removes all compiled functions from the instance cache. The next render of any template will trigger recompilation.
739
+
740
+ ### Instance Isolation
741
+
742
+ Each instance has an independent cache with its own size limit. If you need strict per-tenant or per-request memory isolation, use separate `Mutor` instances.
743
+
744
+ ---
745
+
746
+ ## 16. Configuration Reference
747
+
748
+ The full configuration object with all defaults:
749
+
750
+ ```ts
751
+ interface MutorConfig {
752
+ build?: {
753
+ include?: Set<string>; // File extensions to scan (server only)
754
+ exclude?: Set<string>; // Directories to exclude from scanning
755
+ };
756
+ autoEscape?: boolean; // Default: true
757
+ allowedProps?: Set<string>; // Explicitly whitelisted property names
758
+ forbiddenProps?: Set<string>; // Additional forbidden property names
759
+ allowFnCalls?: boolean; // Default: false
760
+ delimiters?: {
761
+ openingTag?: string; // Default: "{{"
762
+ closingTag?: string; // Default: "}}"
763
+ openingTagEscape?: string; // Default: "\\"
764
+ whitespaceTrim?: string; // Default: "~"
765
+ commentTag?: string; // Default: "#"
766
+ };
767
+ keepOpeningTagEscapeDelimiter?: boolean; // Default: false
768
+ cache?: {
769
+ active?: boolean; // Default: true
770
+ maxSize?: number; // Default: 50 * 1024 * 1024 (50MB)
771
+ };
772
+ }
773
+ ```
774
+
775
+ ### Default Configuration
776
+
777
+ ```js
778
+ const defaultConfig = {
779
+ build: {
780
+ include: new Set([".html", ".txt"]),
781
+ exclude: new Set(["node_modules", ".git"]),
782
+ },
783
+ autoEscape: true,
784
+ allowedProps: new Set(),
785
+ forbiddenProps: new Set(["__proto__", "constructor", "prototype"]),
786
+ allowFnCalls: false,
787
+ delimiters: {
788
+ openingTag: "{{",
789
+ closingTag: "}}",
790
+ openingTagEscape: "\\",
791
+ whitespaceTrim: "~",
792
+ },
793
+ keepOpeningTagEscapeDelimiter: false,
794
+ cache: {
795
+ active: true,
796
+ maxSize: 50 * 1024 * 1024,
797
+ },
798
+ };
799
+ ```
800
+
801
+ ### `allowedProps`
802
+
803
+ A `Set<string>` of property names that are explicitly permitted regardless of other validation rules.
804
+
805
+ ### `forbiddenProps`
806
+
807
+ A `Set<string>` of additional property names to block. The built-in forbidden set (`__proto__`, `constructor`, `prototype`) is always enforced and merged with any values you provide here.
808
+
809
+ ### `build.include` and `build.exclude`
810
+
811
+ Used by the server-side file scanner when pre-compiling a directory of templates. `include` is a set of file extensions to process. `exclude` is a set of directory names to skip.
812
+
813
+ ---
814
+
815
+ ## 17. Delimiter Customization
816
+
817
+ All delimiter-related strings are fully configurable.
818
+
819
+ ### Changing Delimiters
820
+
821
+ ```js
822
+ const mutor = new Mutor({
823
+ delimiters: {
824
+ openingTag: "<%",
825
+ closingTag: "%>",
826
+ openingTagEscape: "\\",
827
+ whitespaceTrim: "-",
828
+ commandTag: "#",
829
+ }
830
+ });
831
+ ```
832
+
833
+ With this configuration, templates use `<%` and `%>`:
834
+
835
+ ```html
836
+ <p><% user.name %></p>
837
+ <%- if user.isAdmin -%>
838
+ <a href="/admin">Admin</a>
839
+ <%- end -%>
840
+ ```
841
+
842
+ ### Constraints on Delimiter Choice
843
+
844
+ - Opening and closing delimiters must be distinct strings.
845
+ - Delimiters should not be substrings of each other (e.g., `{` and `{{` would cause scanner ambiguity).
846
+ - The whitespace trim character must be a single character.
847
+ - The escape character must be a single character.
848
+
849
+ ### Delimiter Change and Cache Invalidation
850
+
851
+ If you change delimiters at runtime by creating a new `Mutor` instance with different configuration, the new instance has a separate cache. Templates compiled under one delimiter configuration are not compatible with a differently configured instance — the instance and its configuration are inseparable.
852
+
853
+ ---
854
+
855
+ ## 18. Whitespace Control
856
+
857
+ Mutor.js provides precise whitespace control via the `~` directive (or your configured `whitespaceTrim` character).
858
+
859
+ ### Without Whitespace Trimming
860
+
861
+ ```html
862
+ <ul>
863
+ {{ for item of items }}
864
+ <li>{{ item.name }}</li>
865
+ {{ end }}
866
+ </ul>
867
+ ```
868
+
869
+ Output:
870
+
871
+ ```html
872
+ <ul>
873
+
874
+ <li>Alpha</li>
875
+
876
+ <li>Beta</li>
877
+
878
+ </ul>
879
+ ```
880
+
881
+ The blank lines come from the newlines around the `for` and `end` tags.
882
+
883
+ ### With Whitespace Trimming
884
+
885
+ ```html
886
+ <ul>
887
+ {{~ for item of items ~}}
888
+ <li>{{ item.name }}</li>
889
+ {{~ end ~}}
890
+ </ul>
891
+ ```
892
+
893
+ Output:
894
+
895
+ ```html
896
+ <ul>
897
+ <li>Alpha</li>
898
+ <li>Beta</li>
899
+ </ul>
900
+ ```
901
+
902
+ The `~` on the opening tag trims whitespace to the left of that tag; `~` on the closing tag trims whitespace to the right. Trim directives work on interpolation tags as well as logic tags.
903
+
904
+ ---
905
+
906
+ ## 19. Escaping Rules
907
+
908
+ ### Auto-Escape (Default)
909
+
910
+ When `autoEscape: true`, every dynamic value is HTML-escaped before insertion. A function call that returns a value like `<script>alert(1)</script>` renders as:
911
+
912
+ ```
913
+ &lt;script&gt;alert(1)&lt;/script&gt;
914
+ ```
915
+
916
+ ### Disabling Auto-Escape
917
+
918
+ ```js
919
+ const mutor = new Mutor({ autoEscape: false });
920
+ ```
921
+
922
+ With auto-escaping disabled, values are interpolated verbatim. This is appropriate only when generating non-HTML output (e.g., plain text) or when context data has been sanitized elsewhere.
923
+
924
+ ### Escaping the Delimiter
925
+
926
+ To output the literal opening delimiter without triggering parsing:
927
+
928
+ ```html
929
+ \{{ this is not a template tag }}
930
+ ```
931
+
932
+ The `keepOpeningTagEscapeDelimiter` option controls whether the backslash itself appears in output:
933
+
934
+ | `keepOpeningTagEscapeDelimiter` | Template | Output |
935
+ |---|---|---|
936
+ | `false` (default) | `\{{ expr }}` | `{{ expr }}` |
937
+ | `true` | `\{{ expr }}` | `\{{ expr }}` |
938
+
939
+ ---
940
+
941
+ ## 20. Error Handling
942
+
943
+ Mutor.js produces typed errors with detailed source location information.
944
+
945
+ ### Error Types
946
+
947
+ | Error Class | Thrown When |
948
+ |---|---|
949
+ | `MutorParseError` | The tokenizer or parser cannot process the input |
950
+ | `MutorSecurityError` | A forbidden property access or unsafe construct is detected |
951
+ | `MutorCompileError` | Code generation or compilation fails |
952
+ | `MutorRuntimeError` | A runtime context validation failure occurs |
953
+ | `MutorIncludeError` | An include file cannot be found or a circular include is detected |
954
+ | `MutorComponentError` | A component is rendered that has not been registered |
955
+
956
+ ### Error Structure
957
+
958
+ All error types extend a base `MutorError` class:
959
+
960
+ ```ts
961
+ class MutorError extends Error {
962
+ code: string; // Machine-readable error code
963
+ source?: string; // Original template content (truncated)
964
+ location?: {
965
+ line: number;
966
+ column: number;
967
+ offset: number;
968
+ };
969
+ }
970
+ ```
971
+
972
+ ### Error Messages
973
+
974
+ Because every token is position-tracked, errors reference exact source locations:
975
+
976
+ ```
977
+ MutorSecurityError: Forbidden property access: "constructor"
978
+ at line 4, column 12 in template "views/user-profile.html"
979
+
980
+ MutorParseError: Unclosed block: "if" block opened at line 7, column 3 was never closed.
981
+
982
+ MutorIncludeError: Circular include detected.
983
+ Include chain: views/layout.html → views/partials/nav.html → views/layout.html
984
+ ```
985
+
986
+ ### Handling Errors
987
+
988
+ ```js
989
+ import { MutorSecurityError, MutorParseError } from "mutorjs";
990
+
991
+ try {
992
+ const output = mutor.render(template, context);
993
+ } catch (err) {
994
+ if (err instanceof MutorSecurityError) {
995
+ // Log and reject the template — do not attempt to render
996
+ logger.error("Template security violation", { code: err.code, location: err.location });
997
+ throw err;
998
+ }
999
+ if (err instanceof MutorParseError) {
1000
+ // Template has a syntax error — report to developer
1001
+ throw err;
1002
+ }
1003
+ throw err;
1004
+ }
1005
+ ```
1006
+
1007
+ ---
1008
+
1009
+ ## 21. CLI
1010
+
1011
+ Mutor.js ships a command-line interface for compiling and rendering templates outside of JavaScript code.
1012
+
1013
+ ### Installation
1014
+
1015
+ Install the package globally to use the `mutor` command directly:
1016
+
1017
+ ```bash
1018
+ npm install -g mutorjs
1019
+ ```
1020
+
1021
+ Or use it locally via `npx`:
1022
+
1023
+ ```bash
1024
+ npx mutor <command> <input> [options]
1025
+ ```
1026
+
1027
+ ### Commands
1028
+
1029
+ #### `compile <template>`
1030
+
1031
+ Compiles a single template file to its intermediate compiled form.
1032
+
1033
+ ```bash
1034
+ mutor compile ./views/index.html --out ./dist/index.html
1035
+ ```
1036
+
1037
+ If `--out` is omitted, the compiled output is printed to stdout.
1038
+
1039
+ #### `build <dir>`
1040
+
1041
+ Renders all templates in a directory using a JSON data source.
1042
+
1043
+ ```bash
1044
+ mutor build ./views --data ./data.json --out ./dist
1045
+ ```
1046
+
1047
+ Both `--data` and `--out` are required for `build`.
1048
+
1049
+ #### `render <template>`
1050
+
1051
+ Compiles and immediately renders a single template using a JSON data source.
1052
+
1053
+ ```bash
1054
+ mutor render ./views/email.html --data ./context.json --out ./dist/email.html
1055
+ ```
1056
+
1057
+ If `--out` is omitted, the rendered output is printed to stdout.
1058
+
1059
+ ### Options
1060
+
1061
+ | Option | Description |
1062
+ |---|---|
1063
+ | `--out <path>` | Output file or directory. Defaults to stdout for `compile` and `render`. |
1064
+ | `--data <path>` | JSON file to use as the render context. Required for `build` and `render`. |
1065
+ | `--config <path>` | JSON config file passed to the Mutor instance. |
1066
+ | `--version` | Print the installed version and exit. |
1067
+ | `--help` | Print usage information and exit. |
1068
+
1069
+ ### Exit Codes
1070
+
1071
+ | Code | Meaning |
1072
+ |---|---|
1073
+ | `0` | Success |
1074
+ | `1` | Runtime error (I/O failure, render failure, etc.) |
1075
+ | `2` | Argument error (unknown flag, missing required value, wrong file type) |
1076
+
1077
+ ### Configuration via `--config`
1078
+
1079
+ You can pass any `MutorConfig`-compatible JSON file to `--config`:
1080
+
1081
+ ```json
1082
+ {
1083
+ "autoEscape": true,
1084
+ "allowFnCalls": false,
1085
+ "cache": {
1086
+ "active": true,
1087
+ "maxSize": 52428800
1088
+ }
1089
+ }
1090
+ ```
1091
+
1092
+ ```bash
1093
+ mutor build ./views --data ./data.json --out ./dist --config ./mutor.config.json
1094
+ ```
1095
+
1096
+ ### Examples
1097
+
1098
+ ```bash
1099
+ # Compile a template and print to stdout
1100
+ mutor compile ./views/home.html
1101
+
1102
+ # Compile a template to a file
1103
+ mutor compile ./views/home.html --out ./dist/home.html
1104
+
1105
+ # Render a template with data, write to file
1106
+ mutor render ./views/email.html --data ./payload.json --out ./out/email.html
1107
+
1108
+ # Build an entire views directory
1109
+ mutor build ./views --data ./site.json --out ./dist
1110
+
1111
+ # Build with a custom config
1112
+ mutor build ./views --data ./site.json --out ./dist --config ./mutor.config.json
1113
+ ```
1114
+
1115
+ ---
1116
+
1117
+ ## 22. Performance
1118
+
1119
+ ### Compilation vs. Execution
1120
+
1121
+ Mutor.js makes a deliberate tradeoff: spend more time at compile time so that runtime is as fast as possible.
1122
+
1123
+ **Compilation cost** — The full pipeline (tokenize → parse → analyze → validate → generate → compile) is more work than engines like EJS or Eta perform. Mutor.js runs semantic analysis and security validation steps that those engines skip. This cost is paid once per unique template per process lifetime.
1124
+
1125
+ **Execution cost** — The compiled output is a native JavaScript function. Executing it is as fast as executing any other JavaScript function of equivalent complexity. There is no interpreter, no AST walker, no regex match — only a function call.
1126
+
1127
+ The compilation cost is amortized across all renders of that template. For a server process that handles thousands of requests, a single template might be compiled once and executed hundreds of thousands of times. The per-request cost is execution only.
6
1128
 
7
- Developed by Onah Victor, Mutor is engineered for high-concurrency environments and is efficient enough to run flawlessly on anything from modern cloud infrastructure to legacy 1st-generation core processors.
1129
+ ### Benchmarks
1130
+
1131
+ The following benchmarks are indicative, based on internal measurements on a modern machine with Node.js 20:
1132
+
1133
+ **Full pipeline (compile + execute):**
1134
+
1135
+ | Engine | Relative Performance |
1136
+ |---|---|
1137
+ | Eta | 1× (baseline) |
1138
+ | **Mutor.js** | **~0.8–0.9×** |
1139
+ | EJS | ~0.4× |
1140
+ | Nunjucks | ~0.15× |
1141
+ | Handlebars | ~0.12× |
1142
+
1143
+ **Raw execution (cache-warm, pre-compiled):**
1144
+
1145
+ | Engine | Relative Performance |
1146
+ |---|---|
1147
+ | **Mutor.js** | **1× (top tier)** |
1148
+ | Eta | ~0.8–0.9× |
1149
+ | EJS | ~0.3× |
1150
+ | Nunjucks | ~0.08× |
1151
+ | Handlebars | ~0.06× |
1152
+
1153
+ Mutor.js is slightly behind Eta on first-compile benchmarks, which is expected — Eta does minimal validation. On the execution-only path (cache warm), Mutor.js leads. That is the path that matters under production load.
1154
+
1155
+ > **Note:** These benchmarks do not account for real-world I/O or middleware overhead. Profile your specific use case.
1156
+
1157
+ ---
1158
+
1159
+ ## 23. Environment Support
1160
+
1161
+ | Environment | Supported | Notes |
1162
+ |---|---|---|
1163
+ | Node.js 14+ | ✅ | Full support including `renderFile` |
1164
+ | Node.js 18+ (recommended) | ✅ | Best performance |
1165
+ | Browser (modern) | ✅ | Use universal entry point; `renderFile` unavailable |
1166
+ | Deno | ✅ (via npm compat) | `renderFile` available if Deno FS APIs are mapped |
1167
+ | Bun | ✅ | Full support |
1168
+ | ESM | ✅ | Native ESM build available |
1169
+ | CommonJS | ✅ | CJS build available |
1170
+ | TypeScript | ✅ | Type declarations shipped with package |
1171
+ | Minimum requirement | ES6 | Requires `const`, arrow functions, template literals, `Set`, `Map` |
1172
+
1173
+ ---
1174
+
1175
+ ## 24. Browser Usage
1176
+
1177
+ In browser environments, import from the universal entry point:
1178
+
1179
+ ```js
1180
+ import Mutor from "mutorjs";
1181
+ ```
1182
+
1183
+ The universal entry point excludes all file-system APIs. The following methods are available: `compile`, `render`, `registerComponent`, `renderComponent`. The `renderFile` method is not available and will throw a `MutorError` if called.
1184
+
1185
+ ### Managing Templates in the Browser
1186
+
1187
+ Since file-system access is unavailable, templates must be loaded manually and registered as components:
1188
+
1189
+ ```js
1190
+ const templateString = await fetch("/templates/card.html").then(r => r.text());
1191
+
1192
+ mutor.registerComponent("card", templateString);
1193
+
1194
+ const html = mutor.renderComponent("card", { card: data });
1195
+ document.getElementById("app").innerHTML = html;
1196
+ ```
8
1197
 
9
1198
  ---
10
1199
 
11
- ## Why Switch to Mutor?
1200
+ ## 25. Server Usage
1201
+
1202
+ Import from the server entry point:
1203
+
1204
+ ```js
1205
+ import Mutor from "mutorjs/server";
1206
+ ```
1207
+
1208
+ The server entry point includes all functionality from the universal entry point plus `renderFile` and pre-compilation scanning via the `build` configuration.
1209
+
1210
+ ### Express Integration
1211
+
1212
+ ```js
1213
+ import express from "express";
1214
+ import Mutor from "mutorjs/server";
1215
+
1216
+ const app = express();
1217
+ const mutor = new Mutor();
1218
+
1219
+ app.get("/", async (req, res) => {
1220
+ const html = await mutor.renderFile("./views/home.html", {
1221
+ user: req.user,
1222
+ page: { title: "Home" },
1223
+ });
1224
+ res.send(html);
1225
+ });
1226
+ ```
1227
+
1228
+ ### Pre-Warming the Cache
1229
+
1230
+ In production, pre-compile all templates during application startup to eliminate compilation latency on first request:
1231
+
1232
+ ```js
1233
+ import Mutor from "mutorjs/server";
1234
+
1235
+ const mutor = new Mutor();
1236
+
1237
+ async function warmCache(viewsDir) {
1238
+ const files = await getHtmlFiles(viewsDir); // your file enumeration logic
1239
+ await Promise.all(files.map(f => mutor.renderFile(f, {})));
1240
+ console.log(`Warmed ${files.length} templates.`);
1241
+ }
1242
+
1243
+ await warmCache("./views");
1244
+ ```
12
1245
 
13
- * **Raw Speed**: Surpasses Eta by **1.2x to 5.0x** in raw compiled template execution, often tying with native JavaScript.
14
- * **Security by Design**: Completely sandboxed. Forbidden properties (like `__proto__`) are blocked by default. Arbitrary function execution is locked down.
15
- * **Namespace Operator**: Access built-in JS utilities (Math, JSON, String) safely without polluting the global scope.
16
- * **Precision Whitespace Control**: Clean up your output without messy regex workarounds.
17
- * **Developer-Friendly Error Traces**: Because it's AST-based, errors map directly to line and column numbers.
1246
+ Calling `renderFile` with an empty context during startup compiles and caches every template. Subsequent production renders with real context data hit the cache.
18
1247
 
19
1248
  ---
20
1249
 
21
- ## 💡 A Detailed Example
1250
+ ## 26. Advanced Examples
1251
+
1252
+ ### Dashboard Template with Nested Data
1253
+
1254
+ ```html
1255
+ {{# Project dashboard template #}}
1256
+ <main class="dashboard">
1257
+ <header>
1258
+ <h1>{{ org.name }} — Dashboard</h1>
1259
+ <p>Logged in as {{ user.displayName }}</p>
1260
+ </header>
22
1261
 
23
- Mutor handles complex nested logic, data formatting, and whitespace control natively, without requiring arbitrary JS execution.
24
- ```hbs
25
- {{# Manage a project list with precision logic and security #}}
26
- <section class="project-board">
27
1262
  {{~ if projects.length > 0 ~}}
28
- {{ for item of projects }}
29
- <div class="card">
30
- <h3>{{ item.name.toUpperCase() }}</h3>
31
- <p>Budget: {{ Math::floor(item.budget) }} USD</p>
32
-
33
- {{# Securely handle nested logic #}}
34
- {{ if item.status == "active" }}
35
- <span class="status-pill">Last Updated: {{ JSON::stringify(item.meta.date) }}</span>
1263
+ <section class="projects">
1264
+ {{ for project of projects }}
1265
+ <article class="project-card">
1266
+ <h2>{{ project.name }}</h2>
1267
+ <p class="budget">
1268
+ Budget: {{ Math::floor(project.budget) }} / {{ Math::floor(project.budgetTotal) }} USD
1269
+ </p>
1270
+ {{ if project.status == "active" }}
1271
+ <span class="badge badge--active">Active</span>
1272
+ {{ else if project.status == "review" }}
1273
+ <span class="badge badge--review">In Review</span>
1274
+ {{ else }}
1275
+ <span class="badge badge--inactive">Inactive</span>
1276
+ {{ end }}
1277
+ <ul class="tags">
1278
+ {{ for tag of project.tags }}
1279
+ <li class="tag">{{ tag }}</li>
36
1280
  {{ end }}
37
- </div>
1281
+ </ul>
1282
+ </article>
38
1283
  {{ end }}
1284
+ </section>
39
1285
  {{~ else ~}}
40
- <p>No active projects found in {{ Date::getFullYear() }}.</p>
1286
+ <p class="empty-state">No projects found.</p>
41
1287
  {{~ end ~}}
42
- </section>
1288
+ </main>
1289
+ ```
1290
+
1291
+ ### Component with Namespace Utilities
1292
+
1293
+ ```js
1294
+ mutor.registerComponent("data-table", `
1295
+ <table>
1296
+ <thead>
1297
+ <tr>
1298
+ {{ for col of table.columns }}
1299
+ <th>{{ col.label }}</th>
1300
+ {{ end }}
1301
+ </tr>
1302
+ </thead>
1303
+ <tbody>
1304
+ {{ for row of table.rows }}
1305
+ <tr>
1306
+ {{ for col of table.columns }}
1307
+ <td>{{ row[col.key] ?? "—" }}</td>
1308
+ {{ end }}
1309
+ </tr>
1310
+ {{ end }}
1311
+ </tbody>
1312
+ <tfoot>
1313
+ <tr>
1314
+ <td colspan="{{ table.columns.length }}">
1315
+ {{ table.rows.length }} {{ table.rows.length == 1 ? "record" : "records" }}
1316
+ </td>
1317
+ </tr>
1318
+ </tfoot>
1319
+ </table>
1320
+ `);
1321
+ ```
1322
+
1323
+ ### Include with Explicit Context Forwarding
1324
+
1325
+ ```html
1326
+ {{# views/layout.html #}}
1327
+ <!DOCTYPE html>
1328
+ <html lang="en">
1329
+ <head>
1330
+ <title>{{ page.title }} — {{ site.name }}</title>
1331
+ {{ Mutor::include("./partials/head-meta.html", Mutor::$$context) }}
1332
+ </head>
1333
+ <body>
1334
+ {{ Mutor::include("./partials/nav.html", { nav: nav, user: user }) }}
1335
+ <main>
1336
+ {{ Mutor::include(page.template, Mutor::$$context) }}
1337
+ </main>
1338
+ {{ Mutor::include("./partials/footer.html", { site: site }) }}
1339
+ </body>
1340
+ </html>
1341
+ ```
1342
+
1343
+ ### Pre-compilation with Custom Forbidden Properties
1344
+
1345
+ ```js
1346
+ import Mutor from "mutorjs/server";
1347
+
1348
+ const mutor = new Mutor({
1349
+ forbiddenProps: new Set([
1350
+ "__proto__",
1351
+ "constructor",
1352
+ "prototype",
1353
+ "valueOf",
1354
+ "toString",
1355
+ "hasOwnProperty",
1356
+ ]),
1357
+ allowFnCalls: false,
1358
+ cache: {
1359
+ active: true,
1360
+ maxSize: 100 * 1024 * 1024,
1361
+ },
1362
+ });
43
1363
  ```
44
1364
 
45
1365
  ---
46
1366
 
47
- ## 🏗 Architecture & Pipeline
1367
+ ## 27. Best Practices
1368
+
1369
+ **Keep templates thin.** Templates should handle layout and presentation. Business logic, data transformations, filtering, and sorting belong in the context preparation layer, not the template.
1370
+
1371
+ **Pre-warm the cache in production.** Use the startup cache warming pattern to eliminate first-request compilation latency. Templates should be compiled before traffic arrives.
1372
+
1373
+ **Use a single shared `Mutor` instance per process.** Cache efficiency is maximized when all requests share the same compiled template cache. Use separate instances only when you need genuine isolation (e.g., per-tenant configuration differences).
1374
+
1375
+ **Pass minimal context.** Only expose in the context what the template actually needs. Avoid passing entire service objects, database clients, or request objects as context.
1376
+
1377
+ **Validate context data before passing it in.** Mutor.js validates the context's prototype chain but does not validate the semantic meaning of your data. Validate user input before it enters the render context.
1378
+
1379
+ **Use `for...of` for arrays, `for...in` for objects.** This mirrors JavaScript semantics and makes templates readable to any JavaScript developer.
1380
+
1381
+ **Name includes and components descriptively.** Component names and include paths are the template's interface contract. `renderComponent("card")` is readable; `renderComponent("c1")` is not.
1382
+
1383
+ **Test your templates.** Templates are code. Render them with fixture contexts in your test suite to catch regressions in output structure.
1384
+
1385
+ ---
1386
+
1387
+ ## 28. Security Best Practices
1388
+
1389
+ **Never disable `autoEscape` when rendering user-controlled data.** If you must render raw HTML from a trusted source, consider using a separate Mutor instance with `autoEscape: false` specifically for that purpose, so the risk is isolated.
1390
+
1391
+ **Never pass `req`, `res`, database clients, or service objects as context.** These objects have methods and properties that, with `allowFnCalls: true`, could be invoked from a template. Prepare a clean, minimal context object explicitly.
1392
+
1393
+ **Enable `allowFnCalls` only when necessary and with a clear scope.** If you need method calls on specific types (e.g., string formatting), consider the planned custom namespace feature instead.
1394
+
1395
+ **Extend `forbiddenProps` conservatively.** Add property names you know are dangerous in your specific context. `valueOf` and `toString` are candidates in many applications.
48
1396
 
49
- Mutor isn't just a regex replacer; it's a full compiler.
1397
+ **Treat `MutorSecurityError` as a hard stop.** If a security error is thrown during compilation, do not attempt to render the template. Log it, alert on it, and investigate.
50
1398
 
51
- 1. **Tokenize (`src/core/tokenize.ts`)**: Breaks your template string into distinct logical units.
52
- 2. **Parse (`src/core/parse.ts` & `src/core/generate-ast.ts`)**: Converts tokens into a secure Abstract Syntax Tree (AST).
53
- 3. **Compile (`src/core/compile.ts`)**: Transforms the AST into a highly optimized, "pure" JavaScript function.
54
- 4. **Execute**: The compiled function runs within a restricted scope, safely interpolating your context.
1399
+ **Do not use `cache: { active: false }` in production.** Beyond the performance cost, a disabled cache means every render triggers recompilation — and recompilation triggers full security analysis on every request, which while correct, is unnecessary overhead.
1400
+
1401
+ **Audit templates that come from external sources.** If your application allows user-defined templates to be compiled, review them with the same scrutiny as user-provided code. Mutor.js's sandbox is strong, but defense in depth applies here too.
55
1402
 
56
1403
  ---
57
1404
 
58
- ## 🛠 Core Innovations
1405
+ ## 29. Real-World Use Cases
1406
+
1407
+ ### Server-Side HTML Rendering (Node.js)
1408
+
1409
+ The primary use case. A web application that serves HTML pages from templates. Mutor.js compiles the view templates at startup, and each request invokes the compiled functions with a prepared context.
1410
+
1411
+ ### Email Template Rendering
1412
+
1413
+ Email templates have similar requirements to HTML templates: variable interpolation, conditional sections (e.g., showing a section only if a promo code is attached), iteration over lists (e.g., order items). Mutor.js's auto-escaping is important for preventing XSS in email clients that render HTML, and the predictable output makes it easy to test email rendering in CI.
1414
+
1415
+ ### Static Site Generation
1416
+
1417
+ A static site generator can use Mutor.js to compile page templates and render them with content sourced from a CMS, markdown files, or a database. The CLI's `build` command makes this straightforward for file-based workflows. The compilation cache ensures that re-builds are fast: only changed templates need recompilation.
1418
+
1419
+ ### Configuration File Generation
59
1420
 
60
- ### 1. The Namespace Operator (`::`)
61
- Traditional engines either block all JS helpers or give templates full access to your server's global object. Mutor’s Namespace Operator solves this elegantly. Safely tap into built-ins directly in the template:
62
- * `{{ Math::floor(price) }}`
63
- * `{{ JSON::stringify(data) }}`
64
- * `{{ Array::isArray(items) }}`
1421
+ Mutor.js can render structured text formats like JSON, YAML, or NGINX configuration files, not just HTML. Disable `autoEscape` for non-HTML output and use the template language to generate configuration files with environment-specific values.
65
1422
 
66
- ### 2. Perfect Whitespace Control
67
- Use the configurable whitespace directive (`~` by default) exactly where you need it:
68
- * `{{~ name }}` (Trim left)
69
- * `{{ name ~}}` (Trim right)
70
- * `{{~ name ~}}` (Trim both sides)
1423
+ ### Report Generation
71
1424
 
72
- ### 3. Total Flexibility
73
- Don't like `{{` and `}}`? Change them. Mutor allows you to completely redefine the syntax delimiters (e.g., `<%=` and `%>`) via the configuration object.
1425
+ Server-side report generation that produces HTML output for PDF conversion. Mutor.js's deterministic, secure rendering is well-suited for environments where the template must process potentially large and complex data structures into formatted output.
74
1426
 
75
1427
  ---
76
1428
 
77
- ## 📊 Performance Benchmarks
1429
+ ## 30. Design Tradeoffs
78
1430
 
79
- Mutor is built to dominate in scale and high-load scenarios.
1431
+ ### Compilation Cost for Execution Speed
80
1432
 
81
- | Metric | Mutor.js | Eta | EJS | Nunjucks / Handlebars |
82
- | :--- | :--- | :--- | :--- | :--- |
83
- | **Full Pipeline (Compile + Exec)** | **2nd Overall** | 1st | Distant 3rd | Slower |
84
- | **Raw Execution (Compiled)** | **Top Tier (1.2x - 5x lead)** | Competitive | Significantly Slower | Significantly Slower |
1433
+ Mutor.js is slower to compile than Eta because it does substantially more work during compilation. This is acceptable because the compiled output executes faster, and compilation happens once per template per process lifetime.
85
1434
 
86
- *Memory Management*: Mutor includes a built-in LRU cache (defaulting to 50MB) to ensure frequently used templates execute instantly without causing memory leaks.
1435
+ **Impact:** Mutor.js is not ideal for environments where templates change on every request and caching is disabled. It excels in environments with a stable set of templates and many requests.
1436
+
1437
+ ### Bounded Template Language for Security
1438
+
1439
+ Mutor.js does not allow arbitrary JavaScript in templates. You cannot write a `while` loop, a `try/catch`, a variable declaration, or an IIFE. This is a hard constraint from the security architecture.
1440
+
1441
+ **Impact:** Complex logic must live in the context object, not the template. This is the correct design for large applications — templates should be thin views, not business logic containers — but it requires a different authoring mindset.
1442
+
1443
+ ### Zero Dependencies for Auditability
1444
+
1445
+ Mutor.js has no third-party dependencies. Every dependency in a security-critical system is a potential attack surface. Mutor.js owns its entire stack, which makes it fully auditable and immune to supply chain attacks through compromised dependencies.
1446
+
1447
+ **Impact:** Mutor.js does not benefit from improvements in third-party parsing libraries or utility packages. Every capability must be built and maintained internally.
87
1448
 
88
1449
  ---
89
1450
 
90
- ## ⚙️ Out-of-the-Box Configuration
1451
+ ## 31. Future Roadmap
91
1452
 
92
- Mutor works instantly, but exposes deep controls for enterprise needs.
93
- ```typescript
94
- export const defaultConfig: MutorConfig = {
95
- build: {
96
- include: new Set([".html", ".txt"]),
97
- exclude: new Set(["node_modules", ".git"]),
98
- },
1453
+ The following features are planned or under active development and are not available in the current stable release.
1454
+
1455
+ ### Template Source Maps *(Planned)*
1456
+
1457
+ Source map generation so that runtime errors in compiled functions can be mapped back to their originating template locations — the natural next step from the position tracking already in the tokenizer.
1458
+
1459
+ ### Extended Namespace Registry *(Planned)*
1460
+
1461
+ An API to register custom namespaces:
1462
+
1463
+ ```js
1464
+ mutor.registerNamespace("Utils", {
1465
+ formatCurrency: (n) => `$${n.toFixed(2)}`,
1466
+ slugify: (s) => s.toLowerCase().replace(/\s+/g, "-"),
1467
+ });
1468
+ ```
1469
+
1470
+ This would allow project-specific utilities to be exposed into templates via the controlled namespace mechanism rather than requiring `allowFnCalls: true`.
1471
+
1472
+ ### Async Template Rendering *(Under Consideration)*
1473
+
1474
+ Support for templates that include asynchronous expressions — for example, inline data fetching or async namespace methods. Architecturally complex and not yet committed.
1475
+
1476
+ ### Template Language Server Protocol Support *(Future)*
1477
+
1478
+ An LSP implementation for Mutor.js template syntax, enabling editor tooling: syntax highlighting, error diagnostics, completion, and hover docs.
1479
+
1480
+ ### Compiler Plugin API *(Future)*
1481
+
1482
+ An API for hooking into the compilation pipeline at defined extension points (post-parse, post-analyze, post-generate), enabling custom AST transformations, custom security policies, and code generation optimizations without forking the core.
1483
+
1484
+ ---
1485
+
1486
+ ## 32. FAQ
1487
+
1488
+ **Q: Is Mutor.js production-ready?**
1489
+
1490
+ Yes. The core engine — rendering, compilation, caching, security, components, includes — is stable and production-ready. The CLI is also available as of the current release.
1491
+
1492
+ **Q: Can I use Mutor.js with Express/Fastify/Koa?**
1493
+
1494
+ Yes. Mutor.js is a rendering function, not a framework. Any server framework that lets you produce a string response can use it. See §25 Server Usage for an Express example.
1495
+
1496
+ **Q: Why is function calling disabled by default?**
1497
+
1498
+ Templates that invoke arbitrary functions on context objects are harder to reason about and audit than templates that only read data. Disabling function calls by default makes the common case — read-only data rendering — safe without configuration, and forces an explicit opt-in for the more powerful (and potentially more risky) capability.
1499
+
1500
+ **Q: Can I extend the namespace system with my own namespaces?**
1501
+
1502
+ Not in the current version. This is planned as a future feature. For now, all logic that requires custom functions should be called in the context preparation layer and the result passed into the context.
1503
+
1504
+ **Q: Why is template interpolation inside backtick strings not supported?**
1505
+
1506
+ Backtick strings in Mutor.js are static strings, not JavaScript template literals. Supporting `${...}` inside strings would require a nested expression parser inside the string tokenizer, significantly increasing complexity. The use case is better handled outside the string: `prefix + value + suffix` as a concatenation expression.
1507
+
1508
+ **Q: How does Mutor.js handle `null` and `undefined` in interpolation?**
1509
+
1510
+ By default, `null` and `undefined` values render as empty strings. This prevents `undefined` from appearing literally in output. Use the nullish coalescing operator (`??`) to provide explicit defaults when you need them.
1511
+
1512
+ **Q: Can two Mutor instances share a cache?**
1513
+
1514
+ No. Each instance has an isolated cache. If you want cache sharing, use a single instance.
1515
+
1516
+ **Q: Is there a way to render without escaping for a specific tag?**
1517
+
1518
+ In the current version, auto-escaping is a global configuration setting, not per-tag. To render a raw value, either use `autoEscape: false` on the instance, sanitize the value before it enters the context, or use a separate instance configured without auto-escaping.
1519
+
1520
+ ---
1521
+
1522
+ ## 33. API Reference
1523
+
1524
+ ### `new Mutor(config?: MutorConfig)`
1525
+
1526
+ Creates a new Mutor instance with the given configuration merged over the defaults.
1527
+
1528
+ ```ts
1529
+ const mutor = new Mutor({
99
1530
  autoEscape: true,
100
- allowedProps: new Set(),
101
- forbiddenProps: new Set(["__proto__", "constructor", "prototype"]),
102
- allowFnCalls: false, // Prevents unintended side effects
103
- delimiters: {
104
- closingTag: "}}",
105
- openingTag: "{{",
106
- openingTagEscape: "\\",
107
- whitespaceTrim: "~",
108
- },
109
- keepOpeningTagEscapeDelimiter: false,
110
- cache: {
111
- active: true,
112
- maxSize: 50 * 1024 * 1024, // 50MB
113
- },
114
- };
1531
+ allowFnCalls: false,
1532
+ cache: { active: true, maxSize: 50 * 1024 * 1024 },
1533
+ });
1534
+ ```
1535
+
1536
+ ---
1537
+
1538
+ ### `mutor.compile(template: string): CompiledTemplate`
1539
+
1540
+ ```ts
1541
+ type CompiledTemplate = (context: Record<string, unknown>) => string;
115
1542
  ```
116
1543
 
1544
+ Compiles a template string and returns the compiled function. The compilation result is stored in the instance cache.
1545
+
1546
+ **Throws:**
1547
+ - `MutorParseError` — if the template contains a syntax error
1548
+ - `MutorSecurityError` — if the template contains a forbidden construct
1549
+ - `MutorCompileError` — if code generation fails
1550
+
117
1551
  ---
118
1552
 
119
- ## ⚖️ Is Mutor Right For You? (Honest Trade-offs)
1553
+ ### `mutor.render(template: string, context: Record<string, unknown>): string`
120
1554
 
121
- **When to use Mutor:**
122
- * You are rendering user-generated content and cannot risk XSS or prototype pollution.
123
- * You need near-native execution speed for high-traffic applications.
124
- * You want clean, debuggable templates that fail with exact line/column numbers.
125
- * You need to pre-compile entire view directories ahead of time.
1555
+ Compiles (or retrieves from cache) and immediately renders a template with the given context.
126
1556
 
127
- **When NOT to use Mutor:**
128
- * You require the ability to write complex, arbitrary JavaScript directly inside your HTML. Mutor's sandbox will block this.
129
- * You only need simple string replacement for a tiny script where initializing an AST engine is overkill.
1557
+ **Throws:** Same as `compile`, plus `MutorRuntimeError` for runtime context validation failures.
130
1558
 
131
1559
  ---
132
1560
 
133
- ## 🚀 Coming Soon: The CLI Tool
1561
+ ### `mutor.renderFile(path: string, context: Record<string, unknown>): Promise<string>`
134
1562
 
135
- Mutor's core features are ready, and the CLI is currently in active development to streamline your CI/CD pipelines.
136
- ```bash
137
- # Preview CLI Commands
138
- mutor compile ./views/index.html --out ./dist
139
- mutor build ./views --max-mem 50MB
1563
+ *Server entry point only (`mutorjs/server`).*
1564
+
1565
+ Reads, compiles, and renders a template file. The resolved absolute path is used as the cache key.
1566
+
1567
+ **Throws:** Same as `render`, plus `MutorIncludeError` if the file cannot be read.
1568
+
1569
+ ---
1570
+
1571
+ ### `mutor.registerComponent(name: string, template: string): void`
1572
+
1573
+ Registers a named component template. Compilation is deferred to first render.
1574
+
1575
+ **Throws:** `MutorError` if `name` is empty or if `template` is not a string.
1576
+
1577
+ ---
1578
+
1579
+ ### `mutor.renderComponent(name: string, context: Record<string, unknown>): string`
1580
+
1581
+ Renders a registered component by name.
1582
+
1583
+ **Throws:** `MutorComponentError` if the component has not been registered. Same compile/runtime errors as `render` on first call.
1584
+
1585
+ ---
1586
+
1587
+ ### `mutor.clearCache(): void`
1588
+
1589
+ Removes all entries from the instance's compilation cache.
1590
+
1591
+ ---
1592
+
1593
+ ### `mutor.getCacheSize(): number`
1594
+
1595
+ Returns the estimated current memory usage of the cache in bytes.
1596
+
1597
+ ---
1598
+
1599
+ ### `mutor.getCacheEntryCount(): number`
1600
+
1601
+ Returns the number of compiled templates currently stored in the cache.
1602
+
1603
+ ---
1604
+
1605
+ ### `mutor.addConfig(config: Partial<MutorConfig>): void`
1606
+
1607
+ Merges additional configuration into the instance after construction. Useful when configuration is loaded asynchronously (e.g., from a file) after the instance is created.
1608
+
1609
+ ---
1610
+
1611
+ ### Error Classes
1612
+
1613
+ ```ts
1614
+ import {
1615
+ MutorError,
1616
+ MutorParseError,
1617
+ MutorSecurityError,
1618
+ MutorCompileError,
1619
+ MutorRuntimeError,
1620
+ MutorIncludeError,
1621
+ MutorComponentError,
1622
+ } from "mutorjs";
1623
+ ```
1624
+
1625
+ All error classes extend `MutorError extends Error` and carry:
1626
+
1627
+ | Property | Type | Description |
1628
+ |---|---|---|
1629
+ | `code` | `string` | Machine-readable error code |
1630
+ | `message` | `string` | Human-readable description |
1631
+ | `source` | `string \| undefined` | Originating template source (truncated) |
1632
+ | `location` | `SourceLocation \| undefined` | `{ line, column, offset }` |
1633
+
1634
+ ---
1635
+
1636
+ ### `MutorConfig` Type Reference
1637
+
1638
+ ```ts
1639
+ interface MutorConfig {
1640
+ build?: {
1641
+ include?: Set<string>;
1642
+ exclude?: Set<string>;
1643
+ };
1644
+ autoEscape?: boolean;
1645
+ allowedProps?: Set<string>;
1646
+ forbiddenProps?: Set<string>;
1647
+ allowFnCalls?: boolean;
1648
+ delimiters?: {
1649
+ openingTag?: string;
1650
+ closingTag?: string;
1651
+ openingTagEscape?: string;
1652
+ whitespaceTrim?: string;
1653
+ };
1654
+ keepOpeningTagEscapeDelimiter?: boolean;
1655
+ cache?: {
1656
+ active?: boolean;
1657
+ maxSize?: number;
1658
+ };
1659
+ }
140
1660
  ```
1661
+
1662
+ ---
1663
+
1664
+ *Mutor.js — engineered for correctness, security, and speed.*
1665
+ *Built by Onah Victor. Contributions welcome under the project's guidelines.*