{"_id":"@aikotools/placeholder","_rev":"5-674e9689ccd29f55af6361d7bcb4179b","name":"@aikotools/placeholder","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@aikotools/placeholder","version":"1.0.0","keywords":["template","placeholder","testing","json","xml","matcher"],"author":"","license":"MIT","_id":"@aikotools/placeholder@1.0.0","maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"dist":{"shasum":"1e23e8a64baafc264dd101d86436fd699f2aaffd","tarball":"https://registry.npmjs.org/@aikotools/placeholder/-/placeholder-1.0.0.tgz","fileCount":36,"integrity":"sha512-kbi0tTNII+O4a6HOO8aA4fYJQxxo9Rzk++XU0w8nPfqQ6ZApBomtEX616769TOG8DfUzAtYnZNKRcHF1nQfLCQ==","signatures":[{"sig":"MEQCIHq7q5P8VUcCQawFkalg+4QbyrlC9kTWnz6OwNevm7W1AiB16izUHO/uxpipHN0oF21KVKARgaix1qBC73Wv6nHwaw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118480},"main":"./dist/aikotools-placeholder.js","type":"module","types":"./dist/index.d.ts","module":"./dist/aikotools-placeholder.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/aikotools-placeholder.js"}},"gitHead":"2e89facf47935e5655a0c2b55ae2a4817bb1301b","scripts":{"dev":"vite","lint":"eslint \"src/**/*.{js,ts}\"","test":"npm run build && vitest run","build":"npm run format && npm run lint && npm run depcheck && tsc && vite build","clean":"rimraf dist","format":"prettier --write \"src/**/*.{js,ts,json}\"","coverage":"vitest run --coverage","depcheck":"depcheck","test:run":"vitest run","docs:build":"bash scripts/build-readme.sh","test:watch":"vitest"},"_npmUser":{"name":"darlenya","email":"torstenlink@gmx.de"},"_npmVersion":"10.9.4","description":"Advanced placeholder template engine with generate and compare modes","directories":{},"_nodeVersion":"24.10.0","dependencies":{"luxon":"^3.4.4","xml2js":"^0.6.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.0.11","eslint":"^9.0.0","rimraf":"^6.0.0","vitest":"^1.2.0","depcheck":"^1.4.7","prettier":"^3.0.0","@vitest/ui":"^1.2.0","typescript":"^5.3.3","@types/node":"^20.11.5","@types/luxon":"^3.4.2","@types/xml2js":"^0.4.14","vite-plugin-dts":"^4.5.4","@vitest/coverage-v8":"^1.2.0","@typescript-eslint/parser":"^8.0.0","@typescript-eslint/eslint-plugin":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/placeholder_1.0.0_1760353461209_0.9607830239563917","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@aikotools/placeholder","version":"1.0.1","keywords":["template","placeholder","testing","json","xml","matcher"],"author":"","license":"MIT","_id":"@aikotools/placeholder@1.0.1","maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"dist":{"shasum":"a285bfeee51ebfc53eecebb07f9768c8ce0227fb","tarball":"https://registry.npmjs.org/@aikotools/placeholder/-/placeholder-1.0.1.tgz","fileCount":36,"integrity":"sha512-Ezq21MZiOvGa1x0Oltoh5QtAHfRYm7p6m7di3Uj1OZIuyP/70Stk4N2J5UzmALq2ObP3oC2Dd0rlkJB7akkwmw==","signatures":[{"sig":"MEUCIDNJrAGsroyOnOKmpa8hPyiWOFyEh3VW1i7pJ1Fh7B1wAiEAnlW0IditOAh+rupfFGlrpL1bQBYv81CNjXGg2ggpKWE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118394},"main":"./dist/aikotools-placeholder.js","type":"module","types":"./dist/index.d.ts","module":"./dist/aikotools-placeholder.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/aikotools-placeholder.js"}},"gitHead":"1983cd6adb87c40a6d1f8d35b65034fdbb2a6651","scripts":{"dev":"vite","lint":"eslint \"src/**/*.{js,ts}\"","test":"npm run build && vitest run --coverage","build":"npm run format && npm run lint && npm run depcheck && tsc && vite build","format":"prettier --write \"src/**/*.{js,ts,json}\"","depcheck":"depcheck","test:run":"vitest run","docs:build":"bash scripts/build-readme.sh","test:watch":"vitest"},"_npmUser":{"name":"darlenya","email":"torstenlink@gmx.de"},"_npmVersion":"11.6.2","description":"Advanced placeholder template engine with generate and compare modes","directories":{},"_nodeVersion":"24.10.0","dependencies":{"luxon":"^3.7.2","xml2js":"^0.6.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.1.11","eslint":"^9.38.0","vitest":"^3.2.4","depcheck":"^1.4.7","prettier":"^3.6.2","@vitest/ui":"^3.2.4","typescript":"^5.9.3","@types/node":"^24.9.1","@types/luxon":"^3.7.1","@types/xml2js":"^0.4.14","vite-plugin-dts":"^4.5.4","@vitest/coverage-v8":"^3.2.4","@typescript-eslint/parser":"^8.46.2","@typescript-eslint/eslint-plugin":"^8.46.2"},"_npmOperationalInternal":{"tmp":"tmp/placeholder_1.0.1_1761141918995_0.825482084904205","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@aikotools/placeholder","version":"1.0.2","keywords":["template","placeholder","testing","json","xml","matcher"],"author":"","license":"MIT","_id":"@aikotools/placeholder@1.0.2","maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"dist":{"shasum":"294b45ddd441085ff1cc9c5768675fe3fe93a369","tarball":"https://registry.npmjs.org/@aikotools/placeholder/-/placeholder-1.0.2.tgz","fileCount":37,"integrity":"sha512-pgpCd/6T4+YKFoixwVGkbkG8fz8QV4udtneSPwv/XiytZmG1lMSeeSlu6BtBfgROf+qSHORB0etoDEaywIonDw==","signatures":[{"sig":"MEYCIQCzK/yZnRAQ2+qphh2puPFoxZm/KRFvokF2V0D3zW1hlQIhALLhV7YLWH2GYnuW8Bg8SmGBbj5s7OIAxZrkRMa2wWP2","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":130586},"main":"./dist/aikotools-placeholder.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/aikotools-placeholder.mjs","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.cjs"}}},"gitHead":"f3686ec097489d16ba67a665a2c9e8ca8186daca","scripts":{"dev":"vite","lint":"eslint \"src/**/*.{js,ts}\"","test":"npm run build && vitest run --coverage","build":"npm run format && npm run lint && npm run depcheck && tsc && vite build","format":"prettier --write \"src/**/*.{js,ts,json}\"","depcheck":"depcheck","test:run":"vitest run","docs:build":"bash scripts/build-readme.sh","test:watch":"vitest"},"_npmUser":{"name":"darlenya","email":"torstenlink@gmx.de"},"_npmVersion":"11.6.2","description":"Advanced placeholder template engine with generate and compare modes","directories":{},"_nodeVersion":"24.10.0","dependencies":{"luxon":"^3.7.2","xml2js":"^0.6.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.1.11","eslint":"^9.38.0","vitest":"^3.2.4","depcheck":"^1.4.7","prettier":"^3.6.2","@vitest/ui":"^3.2.4","typescript":"^5.9.3","@types/node":"^24.9.1","@types/luxon":"^3.7.1","@types/xml2js":"^0.4.14","vite-plugin-dts":"^4.5.4","@vitest/coverage-v8":"^3.2.4","@typescript-eslint/parser":"^8.46.2","@typescript-eslint/eslint-plugin":"^8.46.2"},"_npmOperationalInternal":{"tmp":"tmp/placeholder_1.0.2_1761207451755_0.014508333367251991","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@aikotools/placeholder","version":"1.1.0","keywords":["template","placeholder","testing","json","xml","matcher"],"author":"","license":"MIT","_id":"@aikotools/placeholder@1.1.0","maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"homepage":"https://github.com/aikotools/placeholder#readme","bugs":{"url":"https://github.com/aikotools/placeholder/issues"},"dist":{"shasum":"024c980fb6faa5fa7e7a3e6c7e09fbea7ddab3f3","tarball":"https://registry.npmjs.org/@aikotools/placeholder/-/placeholder-1.1.0.tgz","fileCount":37,"integrity":"sha512-8KVI7tHirzuG9a5x1UTNZ+uXI7n9yrDSAaW/yyZHygmVA/Dr51bt93To50cF3EoHw7N4ANIbQRYEIAkTIIAFoQ==","signatures":[{"sig":"MEUCIAw8ap22SbqEY4oilu7oKOKBPhWmJ8RyWGlrQIv4/dVCAiEAljyv/ASASrZ3a1MOMS2VWX4CqRSlm5D5XVztLMZ0TEw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aikotools%2fplaceholder@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":130716},"main":"./dist/aikotools-placeholder.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/aikotools-placeholder.mjs","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.cjs"}}},"gitHead":"8a22453fa823522caf117df8def1c08ddfa9c582","scripts":{"dev":"vite","lint":"eslint \"src/**/*.{js,ts}\"","test":"npm run build && vitest run --coverage","build":"npm run format && npm run lint && npm run depcheck && tsc && vite build","format":"prettier --write \"src/**/*.{js,ts,json}\"","depcheck":"depcheck","test:run":"vitest run","docs:build":"bash scripts/build-readme.sh","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:43ca815a-e0e8-4819-a27d-ce2c3bd42918"}},"repository":{"url":"git+https://github.com/aikotools/placeholder.git","type":"git"},"_npmVersion":"11.9.0","description":"Advanced placeholder template engine with generate and compare modes","directories":{},"_nodeVersion":"24.13.0","dependencies":{"luxon":"^3.7.2","xml2js":"^0.6.2"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.3.1","eslint":"^9.38.0","vitest":"^4.0.18","depcheck":"^1.4.7","prettier":"^3.8.1","@vitest/ui":"^4.0.18","typescript":"^5.9.3","@types/node":"^25.2.2","@types/luxon":"^3.7.1","@types/xml2js":"^0.4.14","vite-plugin-dts":"^4.5.4","@vitest/coverage-v8":"^4.0.18","@typescript-eslint/parser":"^8.55.0","@typescript-eslint/eslint-plugin":"^8.55.0"},"_npmOperationalInternal":{"tmp":"tmp/placeholder_1.1.0_1770661469361_0.42964038984902775","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@aikotools/placeholder","version":"1.1.1","description":"Advanced placeholder template engine with generate and compare modes","type":"module","main":"./dist/aikotools-placeholder.cjs","module":"./dist/aikotools-placeholder.mjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/aikotools-placeholder.cjs"}}},"repository":{"type":"git","url":"git+https://github.com/aikotools/placeholder.git"},"publishConfig":{"access":"public","provenance":true},"scripts":{"dev":"vite","format":"prettier --write \"src/**/*.{js,ts,json}\"","lint":"eslint \"src/**/*.{js,ts}\"","depcheck":"depcheck","build":"npm run format && npm run lint && npm run depcheck && tsc && vite build","test":"npm run build && vitest run --coverage","test:run":"vitest run","test:watch":"vitest","docs:build":"bash scripts/build-readme.sh"},"keywords":["template","placeholder","testing","json","xml","matcher"],"author":"","license":"MIT","dependencies":{"luxon":"^3.7.2","xml2js":"^0.6.2"},"devDependencies":{"@types/luxon":"^3.7.1","@types/node":"^25.2.3","@types/xml2js":"^0.4.14","@typescript-eslint/eslint-plugin":"^8.56.0","@typescript-eslint/parser":"^8.56.0","@vitest/coverage-v8":"^4.0.18","@vitest/ui":"^4.0.18","depcheck":"^1.4.7","eslint":"^10.0.0","prettier":"^3.8.1","typescript":"^5.9.3","vite":"^7.3.1","vite-plugin-dts":"^4.5.4","vitest":"^4.0.18"},"gitHead":"090d095a51bc503d157e502b14ef4df3a407c2a8","_id":"@aikotools/placeholder@1.1.1","bugs":{"url":"https://github.com/aikotools/placeholder/issues"},"homepage":"https://github.com/aikotools/placeholder#readme","_nodeVersion":"24.13.0","_npmVersion":"11.10.0","dist":{"integrity":"sha512-zWtxC+5bD4DLwjq9vOdO4VvNPMnYi7LHZJ/q0QxkXAGyDezXf1uCq0eYnsXPXLWLtN5P1HN7Ki1SNBq5fFlJxg==","shasum":"656e4cba796ed1ad983b1abc1c6aaae7622e0af2","tarball":"https://registry.npmjs.org/@aikotools/placeholder/-/placeholder-1.1.1.tgz","fileCount":37,"unpackedSize":130716,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aikotools%2fplaceholder@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAU9IFUCP9HqYOmxBuEBBz4Jw1Cwj5R30mGruNtcz1jvAiBnV4Tr9dsosFgBFyienlTGvEEzbo1BP6GNk73gADVHzw=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:43ca815a-e0e8-4819-a27d-ce2c3bd42918"}},"directories":{},"maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/placeholder_1.1.1_1771330048083_0.9242597576118619"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-13T11:04:21.117Z","modified":"2026-02-17T12:07:28.531Z","1.0.0":"2025-10-13T11:04:21.411Z","1.0.1":"2025-10-22T14:05:19.216Z","1.0.2":"2025-10-23T08:17:31.987Z","1.1.0":"2026-02-09T18:24:29.520Z","1.1.1":"2026-02-17T12:07:28.231Z"},"bugs":{"url":"https://github.com/aikotools/placeholder/issues"},"license":"MIT","homepage":"https://github.com/aikotools/placeholder#readme","keywords":["template","placeholder","testing","json","xml","matcher"],"repository":{"type":"git","url":"git+https://github.com/aikotools/placeholder.git"},"description":"Advanced placeholder template engine with generate and compare modes","maintainers":[{"name":"darlenya","email":"torstenlink@gmx.de"}],"readme":"# @aikotools/placeholder\n\nA powerful placeholder template engine with generate and compare modes for E2E testing scenarios.\n\n[![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=flat&logo=github&logoColor=white)](https://github.com/aikotools/placeholder)\n[![npm version](https://badge.fury.io/js/@aikotools%2Fplaceholder.svg)](https://www.npmjs.com/package/@aikotools/placeholder)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n## Table of Contents\n\n- [Introduction](#introduction)\n- [Getting Started](#getting-started)\n- [Core Concepts](#core-concepts)\n- [Plugins](#plugins)\n- [Transforms](#transforms)\n- [API Reference](#api-reference)\n- [Examples](#examples)\n\n---\nBei End-to-End-Tests müssen häufig dynamische Testdaten erzeugt und verglichen werden. Typische Herausforderungen sind:\n\n- **Zeitabhängige Daten**: Timestamps, die relativ zu einem Teststart berechnet werden\n\n- **Generierte IDs**: UUIDs, Zugnummern, die konsistent bleiben müssen\n\n- **Type-Preservation**: JSON-Werte müssen die korrekten Typen behalten (Zahlen, nicht Strings)\n\n- **Verschachtelte Platzhalter**: `{{compare:startsWith:{{time:calc:0:dd.MM.yyyy}}}}`\n\n- **Multi-Phase-Processing**: Erst generieren, dann vergleichen\n\n| Feature                 | Beschreibung                                                                |\n|-------------------------|-----------------------------------------------------------------------------|\n| **Unified Syntax**      | Einheitliche `{{…​}}` Syntax für alle Platzhalter                            |\n| **Type Preservation**   | Automatische Typ-Erhaltung in JSON (numbers bleiben numbers, nicht strings) |\n| **Nested Placeholders** | Beliebig verschachtelte Platzhalter: `{{outer:{{inner:value}}}}`            |\n| **Plugin System**       | Erweiterbar durch eigene Plugins (Time, Generator, Custom)                  |\n| **Transform Pipeline**  | Werte transformieren: `{{gen:string:42|toNumber}}`                          |\n| **Multi-Mode**          | Generate-Mode (Werte erzeugen) und Compare-Mode (Matcher erzeugen)          |\n| **AST-Based JSON**      | Intelligente JSON-Verarbeitung mit Type-Preservation                        |\n| **Multi-Phase**         | Selektive Plugin-Ausführung (Gen → Time → Compare)                          |\n``` highlight\n┌─────────────────────────────────────────────────────┐\n│              PlaceholderEngine                      │\n│  (Orchestriert den gesamten Verarbeitungsprozess)  │\n└─────────────────────────────────────────────────────┘\n                       │\n        ┌──────────────┼──────────────┐\n        ▼              ▼              ▼\n  ┌─────────┐    ┌──────────┐   ┌──────────┐\n  │  JSON   │    │   Text   │   │  Custom  │\n  │Processor│    │Processor │   │Processor │\n  └─────────┘    └──────────┘   └──────────┘\n        │              │              │\n        └──────────────┼──────────────┘\n                       ▼\n        ┌────────────────────────────────┐\n        │     PlaceholderParser          │\n        │  (Parst {{module:action:args}})│\n        └────────────────────────────────┘\n                       │\n        ┌──────────────┼──────────────┐\n        ▼              ▼              ▼\n  ┌──────────┐  ┌───────────┐  ┌──────────┐\n  │   Time   │  │ Generator │  │  Custom  │\n  │  Plugin  │  │  Plugin   │  │  Plugin  │\n  └──────────┘  └───────────┘  └──────────┘\n        │              │              │\n        └──────────────┼──────────────┘\n                       ▼\n        ┌────────────────────────────────┐\n        │        Transforms              │\n        │  (toNumber, toString, etc.)    │\n        └────────────────────────────────┘\n```\n\nErzeuge konsistente Testdaten mit vorhersagbaren Werten:\n``` highlight\n{\n  \"testId\": \"{{gen:uuid:test-12345}}\",\n  \"zugnummer\": \"{{gen:zugnummer:4837}}\",\n  \"timestamp\": \"{{time:calc:0:seconds}}\"\n}\n```\nErgebnis:\n``` highlight\n{\n  \"testId\": \"test-12345\",\n  \"zugnummer\": 4837,\n  \"timestamp\": 1710508545\n}\n```\n\nBerechne Timestamps relativ zum Teststart:\n``` highlight\n{\n  \"startTime\": \"{{time:calc:0:seconds}}\",\n  \"endTime\": \"{{time:calc:300:seconds}}\",\n  \"date\": \"{{time:calc:0:dd.MM.yyyy}}\"\n}\n```\n\nKombiniere multiple Platzhalter in Strings:\n``` highlight\n{\n  \"filename\": \"{{gen:zugnummer:4837}}_RGE_{{time:calc:0:dd.MM.yyyy}}_Start\"\n}\n```\nErgebnis: `\"4837_RGE_15.03.2025_Start\"`\n\n- **TypeScript**: Typ-sichere Entwicklung\n\n- **Luxon**: Robuste DateTime-Operationen mit Timezone-Support\n\n- **Vite**: Schneller Build und Development Server\n\n- **Vitest**: Modernes Testing Framework\n\n``` highlight\nnpm install @aikotools/placeholder\n```\noder mit Yarn:\n``` highlight\nyarn add @aikotools/placeholder\n```\n``` highlight\nimport { PlaceholderEngine, TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\n\nconst engine = new PlaceholderEngine();\n\n// Plugins registrieren\nengine.registerPlugin(new TimePlugin());\nengine.registerPlugin(new GeneratorPlugin());\n\n// Standard-Transforms sind bereits registriert\n// (toNumber, toString, toBoolean)\n```\n``` highlight\nconst template = JSON.stringify({\n  id: '{{gen:uuid:test-123}}',\n  zugnummer: '{{gen:zugnummer:4837}}',\n  timestamp: '{{time:calc:300:seconds}}',\n  date: '{{time:calc:0:dd.MM.yyyy}}'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: Date.now()\n  }\n});\n\nconst data = JSON.parse(result);\nconsole.log(data);\n// {\n//   id: \"test-123\",\n//   zugnummer: 4837,           // Number!\n//   timestamp: 1710508545,      // Number!\n//   date: \"15.03.2025\"         // String\n// }\n```\n``` highlight\nconst text = 'Train {{gen:zugnummer:4837}} departs at {{time:calc:0:HH:mm}}';\n\nconst result = await engine.processGenerate(text, {\n  format: 'text',\n  mode: 'generate',\n  context: {\n    startTimeTest: Date.now()\n  }\n});\n\nconsole.log(result);\n// \"Train 4837 departs at 12:00\"\n```\nAlle Platzhalter folgen dem Schema:\n``` highlight\n{{module:action:arg1:arg2:...|transform}}\n```\n- **module**: Name des Plugins (z.B. `gen`, `time`)\n\n- **action**: Aktion des Plugins (z.B. `uuid`, `calc`)\n\n- **args**: Argumente, getrennt durch `:`\n\n- **transform**: Optional, Transformation (z.B. `toNumber`)\n\nBeispiele:\n``` highlight\n{{gen:uuid:abc123}}\n{{gen:zugnummer:4837}}\n{{time:calc:300:seconds}}\n{{time:calc:0:dd.MM.yyyy}}\n{{gen:string:42|toNumber}}\n```\n\nDer Context enthält Laufzeit-Informationen:\n``` highlight\nconst context = {\n  // Basis-Zeit für time:calc (bevorzugt)\n  startTimeTest: Date.now(),\n\n  // Alternative Basis-Zeit\n  startTimeScript: Date.now(),\n\n  // Custom-Felder\n  testcaseId: 'TC-001',\n  environment: 'test'\n};\n```\n``` highlight\ninterface ProcessOptions {\n  // Format des Templates\n  format: 'json' | 'text';\n\n  // Modus (generate oder compare)\n  mode: 'generate' | 'compare';\n\n  // Kontext für die Verarbeitung\n  context?: Record<string, any>;\n\n  // Nur bestimmte Plugins verwenden\n  includePlugins?: string[];\n\n  // Bestimmte Plugins ausschließen\n  excludePlugins?: string[];\n}\n```\n\n``` highlight\nmy-e2e-tests/\n├── src/\n│   ├── templates/\n│   │   ├── train-expected.json\n│   │   └── train-actual.json\n│   └── tests/\n│       └── train.test.ts\n├── package.json\n└── tsconfig.json\n```\ntemplates/train-expected.json\n\n``` highlight\n{\n  \"testId\": \"{{gen:uuid:test-train-001}}\",\n  \"train\": {\n    \"number\": \"{{gen:zugnummer:4837}}\",\n    \"type\": \"RGE\"\n  },\n  \"timing\": {\n    \"startTime\": \"{{time:calc:0:seconds}}\",\n    \"endTime\": \"{{time:calc:300:seconds}}\",\n    \"date\": \"{{time:calc:0:dd.MM.yyyy}}\"\n  }\n}\n```\ntests/train.test.ts\n\n``` highlight\nimport { describe, it, expect } from 'vitest';\nimport { PlaceholderEngine, TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\nimport * as fs from 'fs/promises';\n\ndescribe('Train E2E Test', () => {\n  let engine: PlaceholderEngine;\n\n  beforeEach(() => {\n    engine = new PlaceholderEngine();\n    engine.registerPlugin(new TimePlugin());\n    engine.registerPlugin(new GeneratorPlugin());\n  });\n\n  it('should generate expected train data', async () => {\n    const template = await fs.readFile('templates/train-expected.json', 'utf-8');\n\n    const result = await engine.processGenerate(template, {\n      format: 'json',\n      mode: 'generate',\n      context: {\n        startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n      }\n    });\n\n    const data = JSON.parse(result);\n\n    expect(data.testId).toBe('test-train-001');\n    expect(data.train.number).toBe(4837);\n    expect(typeof data.train.number).toBe('number');\n    expect(data.timing.date).toBe('15.03.2025');\n  });\n});\n```\n- Lesen Sie die [Kernkonzepte](core-concepts.adoc), um das System besser zu verstehen\n\n- Erkunden Sie die verfügbaren [Plugins](plugins.adoc)\n\n- Lernen Sie [Transforms](transforms.adoc) kennen\n\n- Schauen Sie sich weitere [Beispiele](examples.adoc) an\nEines der wichtigsten Features ist die automatische Typ-Erhaltung in JSON.\n\nBei herkömmlichen Template-Systemen werden alle Werte zu Strings:\n``` highlight\n// Template\n{\n  \"zugnummer\": \"{{gen:zugnummer:4837}}\"\n}\n\n// Falsches Ergebnis (alle Strings!)\n{\n  \"zugnummer\": \"4837\"  // ❌ String statt Number\n}\n```\n\n@aikotools/placeholder nutzt AST-basierte JSON-Verarbeitung:\n``` highlight\n// Template\n{\n  \"zugnummer\": \"{{gen:zugnummer:4837}}\"\n}\n\n// Korrektes Ergebnis\n{\n  \"zugnummer\": 4837  // ✅ Number\n}\n```\n| Template                       | Ergebnis       | Typ                    |\n|--------------------------------|----------------|------------------------|\n| `\"{{gen:number:42}}\"`          | `42`           | number                 |\n| `\"{{gen:string:hello}}\"`       | `\"hello\"`      | string                 |\n| `\"{{gen:boolean:true}}\"`       | `true`         | boolean                |\n| `\"{{time:calc:0:seconds}}\"`    | `1710508545`   | number                 |\n| `\"{{time:calc:0:dd.MM.yyyy}}\"` | `\"15.03.2025\"` | string                 |\n| `\"Value: {{gen:number:42}}\"`   | `\"Value: 42\"`  | string (Interpolation) |\n| `\"{{gen:string:42|toNumber}}\"` | `42`           | number (Transform)     |\n**Wichtig**: Wenn ein Platzhalter **alleine** in einem String steht, wird der Typ des Plugin-Ergebnisses übernommen. Bei String-Interpolation (mehrere Werte im String) bleibt das Ergebnis immer ein String.\n\nPlatzhalter können ineinander verschachtelt werden:\n``` highlight\n{{outer:{{inner:value}}}}\n```\nDas System löst verschachtelte Platzhalter von innen nach außen auf:\n``` highlight\n// Template\n\"{{time:format:{{gen:number:1710508545}}:dd.MM.yyyy}}\"\n\n// Schritt 1: Innerster Platzhalter\n\"{{time:format:1710508545:dd.MM.yyyy}}\"\n\n// Schritt 2: Äußerer Platzhalter\n\"15.03.2024\"\n```\n``` highlight\n{\n  \"filename\": \"train_{{gen:zugnummer:4837}}_{{time:calc:0:yyyy-MM-dd}}.json\"\n}\n```\nErgebnis:\n``` highlight\n{\n  \"filename\": \"train_4837_2025-03-15.json\"\n}\n```\nVerarbeite Templates in mehreren Phasen mit selektiver Plugin-Ausführung.\n\nIn E2E-Tests gibt es oft verschiedene Phasen:\n\n1.  **Gen-Phase**: Generiere Testdaten (UUIDs, IDs)\n\n2.  **Time-Phase**: Berechne Zeitwerte\n\n3.  **Compare-Phase**: Erstelle Matcher für Vergleiche\n\n``` highlight\nconst template = JSON.stringify({\n  id: '{{gen:uuid:test-123}}',\n  timestamp: '{{time:calc:0:seconds}}',\n  data: 'static'\n});\n\n// Phase 1: Nur Gen-Plugins\nconst afterGen = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  includePlugins: ['gen']\n});\n// Result: { id: \"test-123\", timestamp: \"{{time:calc:0:seconds}}\", data: \"static\" }\n\n// Phase 2: Nur Time-Plugins\nconst afterTime = await engine.processGenerate(afterGen, {\n  format: 'json',\n  mode: 'generate',\n  includePlugins: ['time'],\n  context: { startTimeTest: Date.now() }\n});\n// Result: { id: \"test-123\", timestamp: 1710508545, data: \"static\" }\n```\n``` highlight\n// Nur bestimmte Plugins verwenden\n{\n  includePlugins: ['gen', 'time']\n}\n\n// Alle außer bestimmte Plugins verwenden\n{\n  excludePlugins: ['compare']\n}\n```\nDas System unterstützt zwei Modi:\n\nErzeugt konkrete Werte:\n``` highlight\nawait engine.processGenerate(template, {\n  mode: 'generate',\n  // ...\n});\n\n// Ergebnis: Konkrete Werte\n{ zugnummer: 4837, timestamp: 1710508545 }\n```\n\nErzeugt Matcher für Vergleiche (in späteren Phasen):\n``` highlight\nawait engine.processCompare(actual, expected, {\n  mode: 'compare',\n  // ...\n});\n\n// Ergebnis: MatchResult mit success/errors\n```\n<table>\n<colgroup>\n<col style=\"width: 50%\" />\n<col style=\"width: 50%\" />\n</colgroup>\n<tbody>\n<tr class=\"odd\">\nNote\n<td class=\"content\">Compare-Mode ist aktuell in Entwicklung (Phase 6-8 des Projekts).</td>\n</tr>\n</tbody>\n</table>\n\nDer Context ist zentral für zeitabhängige Tests.\n``` highlight\ninterface ProcessContext {\n  // Primäre Basis-Zeit (wird bevorzugt)\n  startTimeTest?: number | string;\n\n  // Alternative Basis-Zeit\n  startTimeScript?: number | string;\n\n  // Custom-Felder für eigene Plugins\n  [key: string]: any;\n}\n```\n\nTimePlugin nutzt Zeitwerte in dieser Reihenfolge:\n\n1.  `context.startTimeTest` (höchste Priorität)\n\n2.  `context.startTimeScript`\n\n3.  Aktuelle Zeit (`DateTime.utc()`)\n``` highlight\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n  }\n});\n```\n\nContext-Zeitwerte können verschiedene Formate haben:\n``` highlight\n// Unix Timestamp (Millisekunden)\n{ startTimeTest: 1710504000000 }\n\n// Unix Timestamp (Sekunden, < 10 Milliarden)\n{ startTimeTest: 1710504000 }\n\n// ISO String\n{ startTimeTest: '2025-03-15T12:00:00Z' }\n\n// Timestamp als String\n{ startTimeTest: '1710504000000' }\n```\nDas System nutzt einen AST (Abstract Syntax Tree) für JSON.\n``` highlight\n1. JSON parsen → AST\n2. AST traversieren (rekursiv)\n3. Bei jedem Node:\n   - Ist es ein String?\n   - Enthält er Platzhalter?\n   - Ist es ein \"Pure Placeholder\" oder \"String Interpolation\"?\n4. Platzhalter ersetzen\n5. Typ anpassen wenn nötig\n6. AST → JSON\n```\n\nDer gesamte String ist ein einzelner Platzhalter:\n``` highlight\n{\n  \"value\": \"{{gen:number:42}}\"\n}\n```\n→ Node-Typ wird geändert zu Number:\n``` highlight\n{\n  \"value\": 42\n}\n```\n\nDer String enthält Text + Platzhalter:\n``` highlight\n{\n  \"value\": \"Count: {{gen:number:42}}\"\n}\n```\n→ Bleibt ein String:\n``` highlight\n{\n  \"value\": \"Count: 42\"\n}\n```\nTransformiere Werte nach der Placeholder-Auflösung.\n``` highlight\n{{module:action:args|transform}}\n```\n| Transform   | Beschreibung          | Beispiel                                 |\n|-------------|-----------------------|------------------------------------------|\n| `toNumber`  | Wandelt in Number um  | `{{gen:string:42|toNumber}}` → `42`      |\n| `toString`  | Wandelt in String um  | `{{gen:number:42|toString}}` → `\"42\"`    |\n| `toBoolean` | Wandelt in Boolean um | `{{gen:string:true|toBoolean}}` → `true` |\n``` highlight\n// String zu Number\n{\n  \"count\": \"{{gen:string:42|toNumber}}\"\n}\n// Result: { count: 42 }\n\n// Number zu String\n{\n  \"id\": \"{{gen:number:12345|toString}}\"\n}\n// Result: { id: \"12345\" }\n\n// String zu Boolean\n{\n  \"active\": \"{{gen:string:true|toBoolean}}\"\n}\n// Result: { active: true }\n```\n\nSie können eigene Transforms registrieren:\n``` highlight\nimport { Transform } from '@aikotools/placeholder';\n\nclass ToUpperTransform implements Transform {\n  readonly name = 'toUpper';\n\n  transform(value: any): any {\n    return String(value).toUpperCase();\n  }\n}\n\nengine.registerTransforms([new ToUpperTransform()]);\n\n// Verwendung\n\"{{gen:string:hello|toUpper}}\" → \"HELLO\"\n```\nPlugins erweitern das System um spezifische Funktionalität. Jedes Plugin hat einen Namen (module) und bietet verschiedene Actions.\nDas TimePlugin bietet Funktionen für Zeit-Berechnungen und -Formatierung.\n\n`time`\nBerechnet einen Zeitpunkt relativ zu einer Basis-Zeit.\n\n**Syntax:**\n``` highlight\n{{time:calc:offset:unit|format}}\n```\n**Parameter:**\n\n- `offset`: Zeitverschiebung (z.B. `300`, `-60`, `0`)\n\n- `unit|format`: Entweder Zeiteinheit ODER Datums-Format\n\n**Zeiteinheiten:**\n\n- `milliseconds`\n\n- `seconds`\n\n- `minutes`\n\n- `hours`\n\n- `days`\n\n- `weeks`\n\n- `months`\n\n- `years`\n\n**Beispiele mit Zeiteinheiten:**\n``` highlight\n{{time:calc:300:seconds}}        → 1710508845 (Unix timestamp in seconds)\n{{time:calc:-60:seconds}}         → 1710508485 (60 Sekunden vor Basis-Zeit)\n{{time:calc:5:minutes}}           → 1710508845000 (Unix timestamp in milliseconds)\n{{time:calc:2:hours}}             → 1710515745000\n{{time:calc:7:days}}              → 1711113145000\n```\n**Beispiele mit Datum-Formatierung:**\n``` highlight\n{{time:calc:0:dd.MM.yyyy}}        → \"15.03.2025\"\n{{time:calc:3600:dd.MM.yyyy HH}}  → \"15.03.2025 13\" (1 Stunde später)\n{{time:calc:-86400:yyyy-MM-dd}}   → \"2025-03-14\" (1 Tag früher)\n{{time:calc:0:HH}}                → \"12\"\n```\n**Datums-Formate:**\n\nDas Plugin nutzt Luxon’s `toFormat()`. Häufige Format-Tokens:\n| Token  | Beschreibung            | Beispiel |\n|--------|-------------------------|----------|\n| `yyyy` | 4-stelliges Jahr        | 2025     |\n| `yy`   | 2-stelliges Jahr        | 25       |\n| `MM`   | Monat (2-stellig)       | 03       |\n| `M`    | Monat                   | 3        |\n| `dd`   | Tag (2-stellig)         | 15       |\n| `d`    | Tag                     | 15       |\n| `HH`   | Stunde (24h, 2-stellig) | 14       |\n| `H`    | Stunde (24h)            | 14       |\n| `mm`   | Minute (2-stellig)      | 30       |\n| `m`    | Minute                  | 30       |\n| `ss`   | Sekunde (2-stellig)     | 45       |\n| `s`    | Sekunde                 | 45       |\n**Wichtig**: Format-Strings mit `:` funktionieren nicht, da `:` als Argument-Trenner genutzt wird. Nutzen Sie Leerzeichen statt `:`:\n``` highlight\n// ❌ Funktioniert NICHT\n{{time:calc:0:HH:mm:ss}}\n\n// ✅ Funktioniert\n{{time:calc:0:HH mm ss}}\n```\n\nFormatiert einen Unix-Timestamp.\n\n**Syntax:**\n``` highlight\n{{time:format:timestamp:format}}\n```\n**Parameter:**\n\n- `timestamp`: Unix-Timestamp (Sekunden \\< 10 Mrd., sonst Millisekunden)\n\n- `format`: Datums-Format-String\n\n**Beispiele:**\n``` highlight\n{{time:format:1710508245:dd.MM.yyyy}}           → \"15.03.2024\"\n{{time:format:1710508245000:dd.MM.yyyy HH}}     → \"15.03.2024 13\"\n{{time:format:1710508245:yyyy-MM-dd}}           → \"2024-03-15\"\n```\nTimePlugin nutzt eine Basis-Zeit für `calc` aus dem Context (Priorität):\n\n1.  `context.startTimeTest` (höchste)\n\n2.  `context.startTimeScript`\n\n3.  Aktuelle Zeit UTC\n\n**Beispiel:**\n``` highlight\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n  }\n});\n```\n\nWICHTIG: Alle Zeit-Operationen erfolgen in UTC, um konsistente Ergebnisse in verschiedenen Umgebungen zu garantieren.\n\n| Action + Params                  | Rückgabe-Typ               |\n|----------------------------------|----------------------------|\n| `calc` mit Zeiteinheit `seconds` | Number (Unix seconds)      |\n| `calc` mit Zeiteinheit (andere)  | Number (Unix milliseconds) |\n| `calc` mit Datums-Format         | String                     |\n| `format`                         | String                     |\nDas GeneratorPlugin erzeugt Testdaten - entweder mit vorgegebenen Werten (vorhersagbar) oder zufällig.\n\n`gen`\nErzeugt oder nutzt eine UUID/ID.\n\n**Syntax:**\n``` highlight\n{{gen:uuid}}              // Zufällige UUID\n{{gen:uuid:my-id-123}}    // Feste ID\n```\n**Beispiele:**\n``` highlight\n{{gen:uuid}}                                    → \"a3f2e1d4-...\" (zufällig)\n{{gen:uuid:test-123}}                          → \"test-123\"\n{{gen:uuid:12345678-1234-1234-1234-123456789012}} → \"12345678-1234-1234-1234-123456789012\"\n```\n**Wichtig**: Das Plugin validiert NICHT das UUID-Format. Sie können beliebige Strings als IDs verwenden, was für Testdaten sehr praktisch ist.\n\n**Rückgabe-Typ**: String\nErzeugt oder nutzt eine Zahl. `zugnummer` ist ein Alias für `number`.\n\n**Syntax:**\n``` highlight\n{{gen:number}}            // Zufällige Zahl (0-9999)\n{{gen:number:42}}         // Feste Zahl\n{{gen:zugnummer:4837}}    // Alias für number\n```\n**Beispiele:**\n``` highlight\n{{gen:number}}          → 7342 (zufällig)\n{{gen:number:42}}       → 42\n{{gen:zugnummer:4837}}  → 4837\n{{gen:number:-100}}     → -100\n{{gen:number:3.14}}     → 3.14\n```\n**Rückgabe-Typ**: Number\nErzeugt oder nutzt einen String.\n\n**Syntax:**\n``` highlight\n{{gen:string}}            // Zufälliger String (8 Zeichen)\n{{gen:string:hello}}      // Fester String\n```\n**Beispiele:**\n``` highlight\n{{gen:string}}          → \"aB3xY9pQ\" (zufällig)\n{{gen:string:hello}}    → \"hello\"\n{{gen:string:test123}}  → \"test123\"\n```\n**Rückgabe-Typ**: String\nErzeugt oder nutzt einen Boolean.\n\n**Syntax:**\n``` highlight\n{{gen:boolean}}           // Zufälliger Boolean\n{{gen:boolean:true}}      // Fester Boolean\n```\n**Akzeptierte Werte für true:**\n\n- `true`, `1`, `yes`\n\n**Akzeptierte Werte für false:**\n\n- `false`, `0`, `no`\n\n**Beispiele:**\n``` highlight\n{{gen:boolean}}           → true (zufällig)\n{{gen:boolean:true}}      → true\n{{gen:boolean:false}}     → false\n{{gen:boolean:1}}         → true\n{{gen:boolean:0}}         → false\n{{gen:boolean:yes}}       → true\n```\n**Rückgabe-Typ**: Boolean\nSie können eigene Plugins erstellen, indem Sie das `PlaceholderPlugin` Interface implementieren.\n``` highlight\ninterface PlaceholderPlugin {\n  // Name des Plugins (module)\n  readonly name: string;\n\n  // Hauptmethode: Placeholder auflösen\n  resolve(request: PluginResolveRequest): PlaceholderResult;\n\n  // Optional: Matcher für Compare-Mode erstellen\n  createMatcher?(request: PluginMatcherRequest): Matcher;\n}\n```\n``` highlight\nimport { PlaceholderPlugin, PluginResolveRequest, PlaceholderResult } from '@aikotools/placeholder';\n\nexport class MathPlugin implements PlaceholderPlugin {\n  readonly name = 'math';\n\n  resolve(request: PluginResolveRequest): PlaceholderResult {\n    const { action, args } = request.placeholder;\n\n    switch (action) {\n      case 'add':\n        return this.handleAdd(args);\n\n      case 'multiply':\n        return this.handleMultiply(args);\n\n      default:\n        throw new Error(`Math plugin: unknown action '${action}'`);\n    }\n  }\n\n  private handleAdd(args: string[]): PlaceholderResult {\n    if (args.length < 2) {\n      throw new Error('Math add: requires 2 arguments');\n    }\n\n    const a = parseFloat(args[0]);\n    const b = parseFloat(args[1]);\n\n    if (isNaN(a) || isNaN(b)) {\n      throw new Error('Math add: invalid numbers');\n    }\n\n    return {\n      value: a + b,\n      type: 'number'\n    };\n  }\n\n  private handleMultiply(args: string[]): PlaceholderResult {\n    if (args.length < 2) {\n      throw new Error('Math multiply: requires 2 arguments');\n    }\n\n    const a = parseFloat(args[0]);\n    const b = parseFloat(args[1]);\n\n    if (isNaN(a) || isNaN(b)) {\n      throw new Error('Math multiply: invalid numbers');\n    }\n\n    return {\n      value: a * b,\n      type: 'number'\n    };\n  }\n}\n```\n``` highlight\nimport { PlaceholderEngine } from '@aikotools/placeholder';\nimport { MathPlugin } from './MathPlugin';\n\nconst engine = new PlaceholderEngine();\nengine.registerPlugin(new MathPlugin());\n\n// Verwendung\n\"{{math:add:5:3}}\"       → 8\n\"{{math:multiply:4:7}}\"  → 28\n```\n\nJedes Plugin muss ein `PlaceholderResult` zurückgeben:\n``` highlight\ninterface PlaceholderResult {\n  // Der Wert\n  value: any;\n\n  // Der Typ (für Type Preservation)\n  type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null';\n}\n```\n**Wichtig**: Der `type` wird für Type Preservation in JSON genutzt.\nIhr Plugin kann auf den Context zugreifen:\n``` highlight\nresolve(request: PluginResolveRequest): PlaceholderResult {\n  const { action, args } = request.placeholder;\n  const { context } = request;\n\n  // Context-Werte nutzen\n  const env = context.environment || 'test';\n  const baseUrl = context.baseUrl || 'http://localhost';\n\n  // ...\n}\n```\nTransforms ermöglichen es, Werte nach der Placeholder-Auflösung zu transformieren. Sie werden mit dem Pipe-Symbol `|` an Platzhalter angehängt.\nWandelt einen Wert in eine Zahl um.\n\n**Syntax:**\n``` highlight\n{{...|toNumber}}\n```\n**Beispiele:**\n``` highlight\n{{gen:string:42|toNumber}}              → 42\n{{gen:string:3.14|toNumber}}            → 3.14\n{{gen:string:-100|toNumber}}            → -100\n```\n**JSON mit Type Preservation:**\n``` highlight\n{\n  \"count\": \"{{gen:string:42|toNumber}}\"\n}\n```\nErgebnis:\n``` highlight\n{\n  \"count\": 42\n}\n```\n**Fehlerbehandlung:**\n\nWenn der Wert nicht in eine Zahl konvertiert werden kann, wird `NaN` zurückgegeben.\nWandelt einen Wert in einen String um.\n\n**Syntax:**\n``` highlight\n{{...|toString}}\n```\n**Beispiele:**\n``` highlight\n{{gen:number:42|toString}}              → \"42\"\n{{gen:boolean:true|toString}}           → \"true\"\n```\n**JSON mit Type Preservation:**\n``` highlight\n{\n  \"id\": \"{{gen:number:12345|toString}}\"\n}\n```\nErgebnis:\n``` highlight\n{\n  \"id\": \"12345\"\n}\n```\n\nWandelt einen Wert in einen Boolean um.\n\n**Syntax:**\n``` highlight\n{{...|toBoolean}}\n```\n**True-Werte:**\n\n- String: `\"true\"`, `\"1\"`, `\"yes\"` (case-insensitive)\n\n- Number: `1`\n\n- Boolean: `true`\n\n**False-Werte:**\n\n- String: `\"false\"`, `\"0\"`, `\"no\"` (case-insensitive)\n\n- Number: `0`\n\n- Boolean: `false`\n\n**Alle anderen Werte:**\n\nWerden als \"truthy\" (true) oder \"falsy\" (false) interpretiert nach JavaScript-Regeln.\n\n**Beispiele:**\n``` highlight\n{{gen:string:true|toBoolean}}           → true\n{{gen:string:false|toBoolean}}          → false\n{{gen:string:yes|toBoolean}}            → true\n{{gen:string:no|toBoolean}}             → false\n{{gen:number:1|toBoolean}}              → true\n{{gen:number:0|toBoolean}}              → false\n{{gen:string:hello|toBoolean}}          → true (truthy)\n```\n**JSON mit Type Preservation:**\n``` highlight\n{\n  \"active\": \"{{gen:string:true|toBoolean}}\"\n}\n```\nErgebnis:\n``` highlight\n{\n  \"active\": true\n}\n```\nTransforms werden in der Reihenfolge angewendet, in der sie angegeben sind:\n``` highlight\n{{gen:string:42|toNumber|toString}}\n```\n1.  Plugin-Auflösung: `\"42\"` (String)\n\n2.  `toNumber`: `42` (Number)\n\n3.  `toString`: `\"42\"` (String)\nSie können eigene Transforms erstellen, indem Sie das `Transform` Interface implementieren.\n``` highlight\ninterface Transform {\n  // Name des Transforms\n  readonly name: string;\n\n  // Transformations-Funktion\n  transform(value: any): any;\n}\n```\n``` highlight\nimport { Transform } from '@aikotools/placeholder';\n\nexport class ToUpperTransform implements Transform {\n  readonly name = 'toUpper';\n\n  transform(value: any): any {\n    // Wandle Wert in String und dann in Uppercase\n    return String(value).toUpperCase();\n  }\n}\n```\n``` highlight\nimport { Transform } from '@aikotools/placeholder';\n\nexport class RoundTransform implements Transform {\n  readonly name = 'round';\n\n  transform(value: any): any {\n    const num = parseFloat(value);\n    if (isNaN(num)) {\n      throw new Error(`Round transform: invalid number '${value}'`);\n    }\n    return Math.round(num);\n  }\n}\n```\n``` highlight\nimport { PlaceholderEngine } from '@aikotools/placeholder';\nimport { ToUpperTransform, RoundTransform } from './transforms';\n\nconst engine = new PlaceholderEngine();\n\n// Einzeln registrieren\nengine.registerTransforms([\n  new ToUpperTransform(),\n  new RoundTransform()\n]);\n\n// Verwendung\n\"{{gen:string:hello|toUpper}}\"    → \"HELLO\"\n\"{{gen:number:3.7|round}}\"         → 4\n```\nAktuell unterstützen Transforms keine direkten Parameter. Wenn Sie parametrisierbare Transformationen benötigen, sollten Sie stattdessen ein Plugin erstellen.\n\n**Beispiel:**\n\nAnstatt `{{value|round:2}}` (funktioniert nicht), nutzen Sie:\n``` highlight\n// Ein Math-Plugin mit round-Action\n{{math:round:3.14159:2}}  → 3.14\n```\n\nNutzen Sie Transforms sparsam. Oft ist es besser, die Logik im Plugin zu haben:\n``` highlight\n// ❌ Nicht optimal\n{{gen:string:42|toNumber}}\n\n// ✅ Besser\n{{gen:number:42}}\n```\n\nTransforms ändern den Typ des Ergebnisses:\n``` highlight\n// Original (ohne Transform): Number\n{\n  \"count\": \"{{gen:number:42}}\"\n}\n\n// Mit Transform: String\n{\n  \"count\": \"{{gen:number:42|toString}}\"\n}\n```\n\nIhre Transforms sollten robuste Fehlerbehandlung haben:\n``` highlight\ntransform(value: any): any {\n  if (value === null || value === undefined) {\n    throw new Error('Transform: value is null or undefined');\n  }\n\n  const num = parseFloat(value);\n  if (isNaN(num)) {\n    throw new Error(`Transform: invalid number '${value}'`);\n  }\n\n  return num;\n}\n```\n\nTransforms sollten idempotent sein (mehrfache Anwendung = einmalige Anwendung):\n``` highlight\n// ✅ Idempotent\ntoUpper(\"hello\")  → \"HELLO\"\ntoUpper(\"HELLO\")  → \"HELLO\"\n\n// ❌ Nicht idempotent (problematisch)\nincrement(5)  → 6\nincrement(6)  → 7\n```\nBei Transform-Fehlern erhalten Sie detaillierte Fehlermeldungen:\n``` highlight\nError: Transform 'toNumber' failed for value 'abc': invalid number\n  at ToNumberTransform.transform (transforms/ToNumberTransform.ts:12)\n  at PlaceholderEngine.applyTransforms (core/PlaceholderEngine.ts:145)\n```\nSie können Transforms auch isoliert testen:\n``` highlight\nimport { ToNumberTransform } from '@aikotools/placeholder';\n\nconst transform = new ToNumberTransform();\n\nconsole.log(transform.transform('42'));    // 42\nconsole.log(transform.transform('3.14'));  // 3.14\nconsole.log(transform.transform('abc'));   // NaN\n```\n\nDie Haupt-Engine-Klasse, die alle Operationen orchestriert.\n``` highlight\nconstructor()\n```\nErstellt eine neue PlaceholderEngine-Instanz mit:\n\n- Standard-Transforms (toNumber, toString, toBoolean)\n\n- Leerer Plugin-Registry\n\n**Beispiel:**\n``` highlight\nimport { PlaceholderEngine } from '@aikotools/placeholder';\n\nconst engine = new PlaceholderEngine();\n```\n\nRegistriert ein Plugin.\n``` highlight\nregisterPlugin(plugin: PlaceholderPlugin): void\n```\n**Parameter:**\n\n- `plugin`: Das zu registrierende Plugin\n\n**Beispiel:**\n``` highlight\nimport { TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\n\nengine.registerPlugin(new TimePlugin());\nengine.registerPlugin(new GeneratorPlugin());\n```\n\nRegistriert mehrere Plugins auf einmal.\n``` highlight\nregisterPlugins(plugins: PlaceholderPlugin[]): void\n```\n**Parameter:**\n\n- `plugins`: Array von Plugins\n\n**Beispiel:**\n``` highlight\nengine.registerPlugins([\n  new TimePlugin(),\n  new GeneratorPlugin(),\n  new CustomPlugin()\n]);\n```\n\nRegistriert Transforms.\n``` highlight\nregisterTransforms(transforms: Transform[]): void\n```\n**Parameter:**\n\n- `transforms`: Array von Transforms\n\n**Beispiel:**\n``` highlight\nimport { ToUpperTransform } from './transforms';\n\nengine.registerTransforms([\n  new ToUpperTransform()\n]);\n```\n\nVerarbeitet ein Template im Generate-Mode.\n``` highlight\nasync processGenerate(\n  input: string,\n  options: ProcessOptions\n): Promise<string>\n```\n**Parameter:**\n\n- `input`: Template-String (JSON oder Text)\n\n- `options`: Verarbeitungs-Optionen\n\n**Rückgabe:**\n\n- Promise mit verarbeitetem String\n\n**Beispiel:**\n``` highlight\nconst result = await engine.processGenerate(\n  JSON.stringify({ id: '{{gen:uuid:test-123}}' }),\n  {\n    format: 'json',\n    mode: 'generate',\n    context: {\n      startTimeTest: Date.now()\n    }\n  }\n);\n\nconsole.log(JSON.parse(result));\n// { id: \"test-123\" }\n```\n\nVerarbeitet Templates im Compare-Mode (in Entwicklung).\n``` highlight\nasync processCompare(\n  actual: any,\n  expected: any,\n  options: CompareOptions\n): Promise<CompareResult>\n```\n<table>\n<colgroup>\n<col style=\"width: 50%\" />\n<col style=\"width: 50%\" />\n</colgroup>\n<tbody>\n<tr class=\"odd\">\nNote\n<td class=\"content\">Diese Methode ist aktuell in Entwicklung (Phase 6-8).</td>\n</tr>\n</tbody>\n</table>\n\nOptionen für die Template-Verarbeitung.\n``` highlight\ninterface ProcessOptions {\n  // Format des Templates\n  format: 'json' | 'text';\n\n  // Verarbeitungs-Modus\n  mode: 'generate' | 'compare';\n\n  // Kontext mit Laufzeit-Daten\n  context?: Record<string, any>;\n\n  // Nur diese Plugins verwenden\n  includePlugins?: string[];\n\n  // Diese Plugins ausschließen\n  excludePlugins?: string[];\n}\n```\nDas Format des Template-Strings.\n\n- `'json'`: JSON-Template mit AST-basierter Verarbeitung\n\n- `'text'`: Einfacher Text-Template\nDer Verarbeitungs-Modus.\n\n- `'generate'`: Erzeugt konkrete Werte\n\n- `'compare'`: Erzeugt Matcher (in Entwicklung)\nKontext-Objekt mit Laufzeit-Daten.\n\n**Standard-Felder:**\n\n- `startTimeTest`: Basis-Zeit für TimePlugin (bevorzugt)\n\n- `startTimeScript`: Alternative Basis-Zeit\n\n**Custom-Felder:**\n\nSie können beliebige Felder hinzufügen, die Ihre Plugins nutzen können.\n\n**Beispiel:**\n``` highlight\n{\n  context: {\n    startTimeTest: Date.now(),\n    environment: 'test',\n    baseUrl: 'http://localhost:3000',\n    testcaseId: 'TC-001'\n  }\n}\n```\n\nArray von Plugin-Namen, die verwendet werden sollen. Alle anderen werden ignoriert.\n\n**Beispiel:**\n``` highlight\n{\n  includePlugins: ['gen', 'time']\n}\n```\n\nArray von Plugin-Namen, die ausgeschlossen werden sollen.\n\n**Beispiel:**\n``` highlight\n{\n  excludePlugins: ['compare']\n}\n```\nInterface für Plugins.\n``` highlight\ninterface PlaceholderPlugin {\n  readonly name: string;\n  resolve(request: PluginResolveRequest): PlaceholderResult;\n  createMatcher?(request: PluginMatcherRequest): Matcher;\n}\n```\nDer Name des Plugins (module-Name in Platzhaltern).\n\n**Typ:** `string`\nLöst einen Platzhalter auf und erzeugt einen Wert.\n``` highlight\nresolve(request: PluginResolveRequest): PlaceholderResult\n```\n**Parameter:**\n\n- `request`: Request-Objekt mit Placeholder und Context\n\n**Rückgabe:**\n\n- `PlaceholderResult` mit value und type\nErzeugt einen Matcher für Compare-Mode (in Entwicklung).\n``` highlight\ncreateMatcher?(request: PluginMatcherRequest): Matcher\n```\nRequest-Objekt für Plugin.resolve().\n``` highlight\ninterface PluginResolveRequest {\n  placeholder: ParsedPlaceholder;\n  context: Record<string, any>;\n  registry: PluginRegistry | null;\n}\n```\nDas geparste Platzhalter-Objekt.\n``` highlight\ninterface ParsedPlaceholder {\n  module: string;        // Plugin-Name (z.B. \"gen\")\n  action: string;        // Action-Name (z.B. \"uuid\")\n  args: string[];        // Argumente\n  transforms: string[];  // Transform-Namen\n  raw: string;          // Original-String\n}\n```\n\nKontext-Objekt mit Laufzeit-Daten (siehe ProcessOptions.context).\nPlugin-Registry (für fortgeschrittene Use Cases).\n\nRückgabe-Objekt von Plugin.resolve().\n``` highlight\ninterface PlaceholderResult {\n  value: any;\n  type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'null';\n}\n```\nDer erzeugte Wert.\n\n**Typ:** `any`\nDer Typ des Werts (für Type Preservation).\n\n**Typ:** `'string' | 'number' | 'boolean' | 'object' | 'array' | 'null'`\n\n**Wichtig:** Dieser Typ wird für Type Preservation in JSON genutzt.\n\nInterface für Transforms.\n``` highlight\ninterface Transform {\n  readonly name: string;\n  transform(value: any): any;\n}\n```\nDer Name des Transforms.\n\n**Typ:** `string`\nTransformiert einen Wert.\n``` highlight\ntransform(value: any): any\n```\n**Parameter:**\n\n- `value`: Der zu transformierende Wert\n\n**Rückgabe:**\n\n- Der transformierte Wert\n\nParser für Platzhalter-Strings.\n``` highlight\nclass PlaceholderParser {\n  parse(input: string): ParsedPlaceholder\n  findPlaceholders(input: string): string[]\n}\n```\nParst einen einzelnen Platzhalter.\n``` highlight\nparse(input: string): ParsedPlaceholder\n```\n**Parameter:**\n\n- `input`: Platzhalter-String (z.B. `\"{{gen:uuid:test}}\"`)\n\n**Rückgabe:**\n\n- `ParsedPlaceholder`-Objekt\n\n**Beispiel:**\n``` highlight\nimport { PlaceholderParser } from '@aikotools/placeholder';\n\nconst parser = new PlaceholderParser();\nconst parsed = parser.parse('{{gen:uuid:test-123|toUpper}}');\n\nconsole.log(parsed);\n// {\n//   module: 'gen',\n//   action: 'uuid',\n//   args: ['test-123'],\n//   transforms: ['toUpper'],\n//   raw: '{{gen:uuid:test-123|toUpper}}'\n// }\n```\n\nFindet alle Platzhalter in einem String.\n``` highlight\nfindPlaceholders(input: string): string[]\n```\n**Parameter:**\n\n- `input`: String mit potentiellen Platzhaltern\n\n**Rückgabe:**\n\n- Array von Platzhalter-Strings\n\n**Beispiel:**\n``` highlight\nconst parser = new PlaceholderParser();\nconst placeholders = parser.findPlaceholders(\n  'ID: {{gen:uuid:test}} at {{time:calc:0:HH:mm}}'\n);\n\nconsole.log(placeholders);\n// ['{{gen:uuid:test}}', '{{time:calc:0:HH:mm}}']\n```\nRegistry für Plugins (normalerweise nicht direkt verwendet).\n``` highlight\nclass PluginRegistry {\n  register(plugin: PlaceholderPlugin): void\n  get(name: string): PlaceholderPlugin | undefined\n  has(name: string): boolean\n  getAll(): PlaceholderPlugin[]\n}\n```\n\nVerarbeitet JSON-Templates mit Type Preservation.\n``` highlight\nclass JsonProcessor {\n  process(\n    input: string,\n    options: ProcessOptions,\n    resolveFn: (placeholder: string) => Promise<any>\n  ): Promise<string>\n}\n```\n<table>\n<colgroup>\n<col style=\"width: 50%\" />\n<col style=\"width: 50%\" />\n</colgroup>\n<tbody>\n<tr class=\"odd\">\nNote\n<td class=\"content\">Wird normalerweise intern von PlaceholderEngine verwendet.</td>\n</tr>\n</tbody>\n</table>\nVerarbeitet Text-Templates.\n``` highlight\nclass TextProcessor {\n  process(\n    input: string,\n    options: ProcessOptions,\n    resolveFn: (placeholder: string) => Promise<any>\n  ): Promise<string>\n}\n```\n<table>\n<colgroup>\n<col style=\"width: 50%\" />\n<col style=\"width: 50%\" />\n</colgroup>\n<tbody>\n<tr class=\"odd\">\nNote\n<td class=\"content\">Wird normalerweise intern von PlaceholderEngine verwendet.</td>\n</tr>\n</tbody>\n</table>\n``` highlight\n// Haupt-Engine\nexport { PlaceholderEngine } from './core/PlaceholderEngine';\n\n// Plugins\nexport { TimePlugin } from './plugins/TimePlugin';\nexport { GeneratorPlugin } from './plugins/GeneratorPlugin';\n\n// Transforms\nexport {\n  ToNumberTransform,\n  ToStringTransform,\n  ToBooleanTransform,\n  createStandardTransforms\n} from './transforms';\n\n// Core\nexport { PlaceholderParser } from './core/PlaceholderParser';\nexport { PluginRegistry } from './core/PluginRegistry';\n\n// Processors\nexport { JsonProcessor } from './formats/JsonProcessor';\nexport { TextProcessor } from './formats/TextProcessor';\n\n// Types\nexport type {\n  PlaceholderPlugin,\n  Transform,\n  Matcher,\n  PlaceholderResult,\n  ParsedPlaceholder,\n  ProcessOptions,\n  // ... weitere Types\n} from './core/types';\n```\n``` highlight\nconst template = JSON.stringify({\n  id: '{{gen:uuid:test-001}}',\n  correlationId: '{{gen:uuid:corr-123}}'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate'\n});\n\nconsole.log(JSON.parse(result));\n// {\n//   id: \"test-001\",\n//   correlationId: \"corr-123\"\n// }\n```\n``` highlight\nimport { DateTime } from 'luxon';\n\nconst baseTime = DateTime.fromISO('2025-03-15T12:00:00Z');\n\nconst template = JSON.stringify({\n  startTime: '{{time:calc:0:seconds}}',\n  endTime: '{{time:calc:300:seconds}}',\n  checkTime: '{{time:calc:150:seconds}}'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: baseTime.toMillis()\n  }\n});\n\nconsole.log(JSON.parse(result));\n// {\n//   startTime: 1710504000,\n//   endTime: 1710504300,\n//   checkTime: 1710504150\n// }\n```\n``` highlight\nconst template = JSON.stringify({\n  date: '{{time:calc:0:dd.MM.yyyy}}',\n  time: '{{time:calc:0:HH}}',\n  datetime: '{{time:calc:0:yyyy-MM-dd HH}}'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T14:30:00Z').getTime()\n  }\n});\n\nconsole.log(JSON.parse(result));\n// {\n//   date: \"15.03.2025\",\n//   time: \"14\",\n//   datetime: \"2025-03-15 14\"\n// }\n```\n\n``` highlight\nimport { PlaceholderEngine, TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\nimport { DateTime } from 'luxon';\n\n// Setup\nconst engine = new PlaceholderEngine();\nengine.registerPlugins([\n  new TimePlugin(),\n  new GeneratorPlugin()\n]);\n\n// Test-Basis-Zeit: 15.03.2025 12:00 UTC\nconst testStartTime = DateTime.fromISO('2025-03-15T12:00:00Z');\n\n// Expected-Template für Zug-Abfahrt\nconst expected = JSON.stringify({\n  testId: '{{gen:uuid:test-train-departure-001}}',\n  train: {\n    number: '{{gen:zugnummer:4837}}',\n    type: 'RGE',\n    operator: 'DB'\n  },\n  departure: {\n    station: 'Berlin Hbf',\n    platform: '7',\n    scheduledTime: '{{time:calc:0:seconds}}',\n    actualTime: '{{time:calc:120:seconds}}',  // 2 Min Verspätung\n    date: '{{time:calc:0:dd.MM.yyyy}}'\n  },\n  destination: {\n    station: 'Hamburg Hbf',\n    arrivalTime: '{{time:calc:5400:seconds}}'  // 90 Min Fahrt\n  },\n  metadata: {\n    created: '{{time:calc:0:yyyy-MM-dd HH}}',\n    version: '{{gen:number:1}}'\n  }\n});\n\n// Verarbeiten\nconst result = await engine.processGenerate(expected, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: testStartTime.toMillis()\n  }\n});\n\nconst data = JSON.parse(result);\n\nconsole.log(data);\n// {\n//   testId: \"test-train-departure-001\",\n//   train: {\n//     number: 4837,\n//     type: \"RGE\",\n//     operator: \"DB\"\n//   },\n//   departure: {\n//     station: \"Berlin Hbf\",\n//     platform: \"7\",\n//     scheduledTime: 1710504000,\n//     actualTime: 1710504120,\n//     date: \"15.03.2025\"\n//   },\n//   destination: {\n//     station: \"Hamburg Hbf\",\n//     arrivalTime: 1710509400\n//   },\n//   metadata: {\n//     created: \"2025-03-15 12\",\n//     version: 1\n//   }\n// }\n\n// Typ-Überprüfungen\nconsole.assert(typeof data.train.number === 'number');\nconsole.assert(typeof data.departure.scheduledTime === 'number');\nconsole.assert(typeof data.metadata.version === 'number');\nconsole.assert(typeof data.departure.date === 'string');\n```\n``` highlight\nconst template = JSON.stringify({\n  filename: '{{gen:zugnummer:4837}}_RGE_{{time:calc:0:dd.MM.yyyy}}_{{gen:uuid:run123}}_Start'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n  }\n});\n\nconsole.log(JSON.parse(result));\n// {\n//   filename: \"4837_RGE_15.03.2025_run123_Start\"\n// }\n```\n``` highlight\nconst template = JSON.stringify({\n  id: '{{gen:uuid:test-123}}',\n  zugnummer: '{{gen:zugnummer:4837}}',\n  timestamp: '{{time:calc:0:seconds}}',\n  static: 'unchanged'\n});\n\n// Phase 1: Nur Gen-Plugins\nconst afterGen = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  includePlugins: ['gen']\n});\n\nconsole.log('After Gen:', JSON.parse(afterGen));\n// {\n//   id: \"test-123\",\n//   zugnummer: 4837,\n//   timestamp: \"{{time:calc:0:seconds}}\",\n//   static: \"unchanged\"\n// }\n\n// Phase 2: Nur Time-Plugins\nconst afterTime = await engine.processGenerate(afterGen, {\n  format: 'json',\n  mode: 'generate',\n  includePlugins: ['time'],\n  context: {\n    startTimeTest: Date.now()\n  }\n});\n\nconsole.log('After Time:', JSON.parse(afterTime));\n// {\n//   id: \"test-123\",\n//   zugnummer: 4837,\n//   timestamp: 1710504000,\n//   static: \"unchanged\"\n// }\n```\n``` highlight\nconst template = JSON.stringify({\n  journey: {\n    id: '{{gen:uuid:journey-001}}',\n    train: {\n      number: '{{gen:zugnummer:4837}}',\n      type: 'RGE',\n      sections: [\n        {\n          from: 'Berlin Hbf',\n          to: 'Hamburg Hbf',\n          departure: '{{time:calc:0:seconds}}',\n          arrival: '{{time:calc:5400:seconds}}'\n        },\n        {\n          from: 'Hamburg Hbf',\n          to: 'Bremen Hbf',\n          departure: '{{time:calc:5700:seconds}}',\n          arrival: '{{time:calc:8100:seconds}}'\n        }\n      ]\n    },\n    passengers: {\n      count: '{{gen:number:42}}',\n      manifest: [\n        { id: '{{gen:uuid:p1}}', seat: '{{gen:number:15}}' },\n        { id: '{{gen:uuid:p2}}', seat: '{{gen:number:16}}' }\n      ]\n    }\n  }\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n  }\n});\n\nconst data = JSON.parse(result);\nconsole.log(data);\n// Vollständig verschachtelte Struktur mit Type Preservation\n```\n``` highlight\nconst template = JSON.stringify({\n  timestamps: [\n    '{{time:calc:0:seconds}}',\n    '{{time:calc:60:seconds}}',\n    '{{time:calc:120:seconds}}'\n  ],\n  ids: [\n    '{{gen:number:1}}',\n    '{{gen:number:2}}',\n    '{{gen:number:3}}'\n  ],\n  status: [\n    '{{gen:boolean:true}}',\n    '{{gen:boolean:false}}',\n    '{{gen:boolean:true}}'\n  ]\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate',\n  context: {\n    startTimeTest: Date.now()\n  }\n});\n\nconst data = JSON.parse(result);\n\n// Alle Timestamps sind Numbers\nconsole.assert(data.timestamps.every(t => typeof t === 'number'));\n\n// Alle IDs sind Numbers\nconsole.assert(data.ids.every(id => typeof id === 'number'));\n\n// Alle Status sind Booleans\nconsole.assert(data.status.every(s => typeof s === 'boolean'));\n```\n\n``` highlight\nconst template = JSON.stringify({\n  // String → Number\n  count: '{{gen:string:42|toNumber}}',\n\n  // Number → String\n  id: '{{gen:number:12345|toString}}',\n\n  // String → Boolean\n  active: '{{gen:string:true|toBoolean}}',\n\n  // Verschachtelt mit Transform\n  timestamp: '{{gen:string:1710504000|toNumber}}'\n});\n\nconst result = await engine.processGenerate(template, {\n  format: 'json',\n  mode: 'generate'\n});\n\nconsole.log(JSON.parse(result));\n// {\n//   count: 42,\n//   id: \"12345\",\n//   active: true,\n//   timestamp: 1710504000\n// }\n```\n\n``` highlight\nconst template = 'Train {{gen:zugnummer:4837}} departs at {{time:calc:0:HH}} from platform 7';\n\nconst result = await engine.processGenerate(template, {\n  format: 'text',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T14:30:00Z').getTime()\n  }\n});\n\nconsole.log(result);\n// \"Train 4837 departs at 14 from platform 7\"\n```\n``` highlight\nconst template = `\nTest Report\n===========\nTest ID: {{gen:uuid:test-001}}\nTrain: {{gen:zugnummer:4837}}\nDate: {{time:calc:0:dd.MM.yyyy}}\nTime: {{time:calc:0:HH}}\nStatus: PASSED\n`.trim();\n\nconst result = await engine.processGenerate(template, {\n  format: 'text',\n  mode: 'generate',\n  context: {\n    startTimeTest: new Date('2025-03-15T12:00:00Z').getTime()\n  }\n});\n\nconsole.log(result);\n// Test Report\n// ===========\n// Test ID: test-001\n// Train: 4837\n// Date: 15.03.2025\n// Time: 12\n// Status: PASSED\n```\n\n``` highlight\nimport { PlaceholderEngine, TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\nimport * as fs from 'fs/promises';\n\nclass TemplateHelper {\n  private engine: PlaceholderEngine;\n\n  constructor() {\n    this.engine = new PlaceholderEngine();\n    this.engine.registerPlugins([\n      new TimePlugin(),\n      new GeneratorPlugin()\n    ]);\n  }\n\n  async loadAndProcess(\n    templatePath: string,\n    context: Record<string, any>\n  ): Promise<any> {\n    const template = await fs.readFile(templatePath, 'utf-8');\n\n    const result = await this.engine.processGenerate(template, {\n      format: 'json',\n      mode: 'generate',\n      context\n    });\n\n    return JSON.parse(result);\n  }\n\n  async createExpectedData(\n    testId: string,\n    zugnummer: number,\n    testStartTime: number\n  ): Promise<any> {\n    const template = JSON.stringify({\n      testId: `{{gen:uuid:${testId}}}`,\n      zugnummer: `{{gen:zugnummer:${zugnummer}}}`,\n      startTime: '{{time:calc:0:seconds}}',\n      endTime: '{{time:calc:300:seconds}}',\n      date: '{{time:calc:0:dd.MM.yyyy}}'\n    });\n\n    const result = await this.engine.processGenerate(template, {\n      format: 'json',\n      mode: 'generate',\n      context: {\n        startTimeTest: testStartTime\n      }\n    });\n\n    return JSON.parse(result);\n  }\n}\n\n// Verwendung\nconst helper = new TemplateHelper();\n\nconst expected = await helper.createExpectedData(\n  'test-001',\n  4837,\n  Date.now()\n);\n\nconsole.log(expected);\n```\n``` highlight\nimport { describe, it, expect, beforeEach } from 'vitest';\nimport { PlaceholderEngine, TimePlugin, GeneratorPlugin } from '@aikotools/placeholder';\nimport { DateTime } from 'luxon';\n\ndescribe('Train Journey Tests', () => {\n  let engine: PlaceholderEngine;\n  let testStartTime: DateTime;\n\n  beforeEach(() => {\n    engine = new PlaceholderEngine();\n    engine.registerPlugins([\n      new TimePlugin(),\n      new GeneratorPlugin()\n    ]);\n\n    testStartTime = DateTime.fromISO('2025-03-15T12:00:00Z');\n  });\n\n  it('should generate expected train departure data', async () => {\n    const template = JSON.stringify({\n      testId: '{{gen:uuid:test-departure}}',\n      train: {\n        number: '{{gen:zugnummer:4837}}',\n        type: 'RGE'\n      },\n      departure: {\n        time: '{{time:calc:0:seconds}}',\n        date: '{{time:calc:0:dd.MM.yyyy}}'\n      }\n    });\n\n    const result = await engine.processGenerate(template, {\n      format: 'json',\n      mode: 'generate',\n      context: {\n        startTimeTest: testStartTime.toMillis()\n      }\n    });\n\n    const data = JSON.parse(result);\n\n    expect(data.testId).toBe('test-departure');\n    expect(data.train.number).toBe(4837);\n    expect(typeof data.train.number).toBe('number');\n    expect(data.departure.time).toBe(testStartTime.toSeconds());\n    expect(data.departure.date).toBe('15.03.2025');\n  });\n\n  it('should handle multi-phase processing', async () => {\n    const template = JSON.stringify({\n      id: '{{gen:uuid:test-123}}',\n      timestamp: '{{time:calc:0:seconds}}'\n    });\n\n    // Phase 1: Gen\n    const afterGen = await engine.processGenerate(template, {\n      format: 'json',\n      mode: 'generate',\n      includePlugins: ['gen']\n    });\n\n    const genData = JSON.parse(afterGen);\n    expect(genData.id).toBe('test-123');\n    expect(genData.timestamp).toBe('{{time:calc:0:seconds}}');\n\n    // Phase 2: Time\n    const afterTime = await engine.processGenerate(afterGen, {\n      format: 'json',\n      mode: 'generate',\n      includePlugins: ['time'],\n      context: {\n        startTimeTest: testStartTime.toMillis()\n      }\n    });\n\n    const timeData = JSON.parse(afterTime);\n    expect(timeData.id).toBe('test-123');\n    expect(timeData.timestamp).toBe(testStartTime.toSeconds());\n  });\n});\n```\n\n``` highlight\nconst template = JSON.stringify({\n  value: '{{unknown:action:arg}}'\n});\n\ntry {\n  await engine.processGenerate(template, {\n    format: 'json',\n    mode: 'generate'\n  });\n} catch (error) {\n  console.error(error.message);\n  // \"Plugin 'unknown' not found. Available plugins: gen, time\"\n}\n```\n``` highlight\nconst template = JSON.stringify({\n  value: '{{gen:invalid:arg}}'\n});\n\ntry {\n  await engine.processGenerate(template, {\n    format: 'json',\n    mode: 'generate'\n  });\n} catch (error) {\n  console.error(error.message);\n  // \"Generator plugin: unknown action 'invalid'. Available: uuid, number, zugnummer, string, boolean\"\n}\n```\n``` highlight\nconst template = JSON.stringify({\n  value: '{{time:calc:300}}'\n});\n\ntry {\n  await engine.processGenerate(template, {\n    format: 'json',\n    mode: 'generate'\n  });\n} catch (error) {\n  console.error(error.message);\n  // \"Time plugin calc: requires 2 arguments (offset, unit/format)\"\n}\n```\n\n---\n\n## License\n\nMIT - See [LICENSE](LICENSE) file for details.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Support\n\nFor issues, questions, or feature requests, please use the [GitHub Issues](https://github.com/aikotools/placeholder/issues) page.\n\n","readmeFilename":"README.md"}