@homedev/objector 1.3.9 → 1.3.10
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 +608 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# @homedev/objector
|
|
2
|
+
|
|
3
|
+
Object processing library for YAML/JSON with support for includes, field processors, conditional logic, and extensibility.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Objector is a comprehensive tool for loading, processing, and transforming YAML/JSON documents with advanced features like:
|
|
8
|
+
|
|
9
|
+
- 🔄 Document includes and composition
|
|
10
|
+
- 🔌 Extensible field processors and data sources
|
|
11
|
+
- 🎯 Conditional logic and flow control
|
|
12
|
+
- 📦 Module system with custom functions
|
|
13
|
+
- 🔍 Variable substitution and selectors
|
|
14
|
+
- 📝 Output generation and file writing
|
|
15
|
+
- 🎨 Template inheritance and extensions
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun add @homedev/objector
|
|
21
|
+
# or
|
|
22
|
+
npm install @homedev/objector
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @homedev/objector
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Objector } from '@homedev/objector'
|
|
31
|
+
|
|
32
|
+
const objector = new Objector()
|
|
33
|
+
|
|
34
|
+
// Load and process a YAML/JSON file
|
|
35
|
+
const result = await objector.load('config.yaml')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { Objector } from '@homedev/objector'
|
|
40
|
+
|
|
41
|
+
const objector = new Objector()
|
|
42
|
+
.variables({ env: 'production', version: '1.0.0' }) // Add custom variables
|
|
43
|
+
.include('settings.yaml') // Include additional files
|
|
44
|
+
|
|
45
|
+
// Process the document
|
|
46
|
+
const processed = await objector.load('main.yaml')
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Core Features
|
|
50
|
+
|
|
51
|
+
### 1. Document Loading and Includes
|
|
52
|
+
|
|
53
|
+
Load YAML/JSON files with automatic format detection and includes:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const objector = new Objector()
|
|
57
|
+
|
|
58
|
+
// Set include directories
|
|
59
|
+
objector.includeDirectory('./config', './templates')
|
|
60
|
+
|
|
61
|
+
// Load a file (automatically resolves from include directories)
|
|
62
|
+
const config = await objector.load('app-config.yaml')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**In your YAML/JSON:**
|
|
66
|
+
```yaml
|
|
67
|
+
includes:
|
|
68
|
+
- base-config.yaml
|
|
69
|
+
- settings/*.yaml
|
|
70
|
+
- ../shared/defaults.yaml
|
|
71
|
+
|
|
72
|
+
myConfig:
|
|
73
|
+
setting: value
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Variables and Substitution
|
|
77
|
+
|
|
78
|
+
Inject and reference variables throughout your documents:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
objector.variables({
|
|
82
|
+
env: 'production',
|
|
83
|
+
apiUrl: 'https://api.example.com',
|
|
84
|
+
version: '2.1.0'
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Usage in documents:**
|
|
89
|
+
```yaml
|
|
90
|
+
app:
|
|
91
|
+
environment: $(env)
|
|
92
|
+
api: $(apiUrl)
|
|
93
|
+
version: $(version)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 3. Conditional Logic
|
|
97
|
+
|
|
98
|
+
Control document structure with conditions:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
features:
|
|
102
|
+
- name: cache
|
|
103
|
+
.if: $(eq:env, "production")
|
|
104
|
+
.then:
|
|
105
|
+
enabled: true
|
|
106
|
+
ttl: 3600
|
|
107
|
+
.else:
|
|
108
|
+
enabled: false
|
|
109
|
+
|
|
110
|
+
- name: debug
|
|
111
|
+
.skip: $(eq:env, "production")
|
|
112
|
+
logging: verbose
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 4. Loops and Iteration
|
|
116
|
+
|
|
117
|
+
Generate repeated structures with `each`:
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
environments:
|
|
121
|
+
.each:
|
|
122
|
+
.from:
|
|
123
|
+
- dev
|
|
124
|
+
- staging
|
|
125
|
+
- prod
|
|
126
|
+
.model:
|
|
127
|
+
name: $(item)
|
|
128
|
+
url: https://$(item).example.com
|
|
129
|
+
replicas: $(switch:
|
|
130
|
+
from: $(item)
|
|
131
|
+
cases:
|
|
132
|
+
prod: 5
|
|
133
|
+
staging: 2
|
|
134
|
+
default: 1)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 5. Template Inheritance
|
|
138
|
+
|
|
139
|
+
Extend and merge templates:
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
baseTemplate:
|
|
143
|
+
server:
|
|
144
|
+
port: 8080
|
|
145
|
+
timeout: 30
|
|
146
|
+
|
|
147
|
+
production:
|
|
148
|
+
extends: .baseTemplate
|
|
149
|
+
server:
|
|
150
|
+
port: 443
|
|
151
|
+
ssl: true
|
|
152
|
+
# Merged result: port=443, timeout=30, ssl=true
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 6. File Operations
|
|
156
|
+
|
|
157
|
+
Read files and scan directories:
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
certificate: $(file:./certs/server.crt)
|
|
161
|
+
|
|
162
|
+
migrations:
|
|
163
|
+
.from: $(scan:"*.sql", "./migrations")
|
|
164
|
+
.model:
|
|
165
|
+
name: $(item)
|
|
166
|
+
content: $(file:$(item))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 7. Custom Modules
|
|
170
|
+
|
|
171
|
+
Add JavaScript functions as data sources:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
modules:
|
|
175
|
+
utils: |
|
|
176
|
+
export function formatDate(ctx, date) {
|
|
177
|
+
return new Date(date).toISOString()
|
|
178
|
+
}
|
|
179
|
+
export function generateId(ctx, prefix) {
|
|
180
|
+
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
records:
|
|
184
|
+
- id: $(utils.generateId:"rec")
|
|
185
|
+
created: $(utils.formatDate:"2024-01-01")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 8. Output Generation
|
|
189
|
+
|
|
190
|
+
Generate multiple output files from one source:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
output:
|
|
194
|
+
- file: dist/config.json
|
|
195
|
+
content:
|
|
196
|
+
version: $(version)
|
|
197
|
+
settings: $(config)
|
|
198
|
+
|
|
199
|
+
- file: dist/README.md
|
|
200
|
+
content: |
|
|
201
|
+
# Configuration
|
|
202
|
+
Version: $(version)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Write all outputs to disk
|
|
207
|
+
await objector.writeAll()
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Built-in Keys
|
|
211
|
+
|
|
212
|
+
Keys are object properties prefixed with `.` and are used for structure/flow control.
|
|
213
|
+
|
|
214
|
+
- `.output` - Register output payload(s)
|
|
215
|
+
- `.load` - Load one or more additional files
|
|
216
|
+
- `.each` - Repeat a model over a source list
|
|
217
|
+
- `.map` - Map a source list into a transformed list
|
|
218
|
+
- `.from` - Resolve a selector/reference into current node
|
|
219
|
+
- `.concat` - Concatenate arrays from selectors
|
|
220
|
+
- `.extends` - Merge from one or many templates
|
|
221
|
+
- `.group` - Expand grouped items into parent list
|
|
222
|
+
- `.if` - Conditional branch using `.then` / `.else`
|
|
223
|
+
- `.switch` - Case-based branching with `cases` / `default`
|
|
224
|
+
- `.skip` - Remove parent when condition is true
|
|
225
|
+
- `.metadata` - Attach metadata for a path
|
|
226
|
+
- `.modules` - Register source functions from inline JS modules
|
|
227
|
+
- `.try` - Guard expression with optional `.catch`
|
|
228
|
+
- `.let` - Introduce temporary variables into context
|
|
229
|
+
- `.tap` - Debug current path/value to console
|
|
230
|
+
|
|
231
|
+
## Built-in Data Sources
|
|
232
|
+
|
|
233
|
+
Sources are used with `$()` syntax.
|
|
234
|
+
|
|
235
|
+
### Core Sources
|
|
236
|
+
- Files: `include`, `exists`, `file`, `scan`
|
|
237
|
+
- String/format: `substring`, `repeat`, `pad`, `formatAs`
|
|
238
|
+
- List: `merge`, `join`, `range`, `filter`
|
|
239
|
+
- Conditions: `if`, `check`, `and`, `or`, `xor`, `true`, `false`, `eq`, `ne`, `lt`, `le`, `gt`, `ge`, `in`, `notin`, `contains`, `notcontains`, `containsi`, `notcontainsi`, `null`, `notnull`, `empty`, `notempty`, `nullorempty`, `notnullorempty`
|
|
240
|
+
- Types: `convert`, `isType`, `typeOf`
|
|
241
|
+
- Flow helpers: `each`, `switch`, `default`, `section`
|
|
242
|
+
|
|
243
|
+
### Built-in Macro Sources
|
|
244
|
+
These are also available as `$()` sources by default.
|
|
245
|
+
|
|
246
|
+
- String/text: `env`, `uuid`, `pascal`, `camel`, `capital`, `snake`, `kebab`, `len`, `reverse`, `concat`, `trim`, `ltrim`, `rtrim`, `lower`, `upper`, `encode`, `decode`
|
|
247
|
+
- Collection/object: `list`, `object`, `unique`, `groupBy`, `chunk`, `flatten`, `compact`, `head`, `tail`
|
|
248
|
+
- Path/filepath: `pathJoin`, `resolve`, `dirname`, `basename`, `normalize`, `extname`, `relative`, `isAbsolute`, `segments`
|
|
249
|
+
- Hash/encoding: `md5`, `sha1`, `sha256`, `sha512`, `hash`, `hmac`, `base64Encode`, `base64Decode`, `hexEncode`, `hexDecode`
|
|
250
|
+
- Math: `add`, `subtract`, `multiply`, `divide`, `modulo`, `power`, `sqrt`, `abs`, `floor`, `ceil`, `round`, `min`, `max`, `clamp`, `random`
|
|
251
|
+
- Date/time: `timestamp`, `iso`, `utc`, `formatDate`, `parseDate`, `year`, `month`, `day`, `dayOfWeek`, `hours`, `minutes`, `seconds`
|
|
252
|
+
- Regex: `regexTest`, `regexMatch`, `regexMatchAll`, `regexReplace`, `regexReplaceAll`, `regexSplit`, `regexExec`, `regexSearch`
|
|
253
|
+
- Validation/predicates: `isEmail`, `isUrl`, `isJson`, `isUuid`, `minLength`, `maxLength`, `lengthEquals`, `isNumber`, `isString`, `isBoolean`, `isArray`, `isObject`, `isEmpty`, `isTruthy`, `isFalsy`, `inRange`, `matchesPattern`, `includes`, `startsWith`, `endsWith`
|
|
254
|
+
- Debug: `log`
|
|
255
|
+
|
|
256
|
+
## API Reference
|
|
257
|
+
|
|
258
|
+
### Objector Class
|
|
259
|
+
|
|
260
|
+
#### Constructor
|
|
261
|
+
```typescript
|
|
262
|
+
const objector = new Objector()
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### Configuration Methods
|
|
266
|
+
|
|
267
|
+
- **`use(handler: (o: Objector) => void): this`**
|
|
268
|
+
Apply a configuration handler
|
|
269
|
+
|
|
270
|
+
- **`includeDirectory(...dirs: string[]): this`**
|
|
271
|
+
Add directories to search for includes
|
|
272
|
+
|
|
273
|
+
- **`include(...files: string[]): this`**
|
|
274
|
+
Add include files directly
|
|
275
|
+
|
|
276
|
+
- **`keys(keys: FieldProcessorMap): this`**
|
|
277
|
+
Add or override key processors
|
|
278
|
+
|
|
279
|
+
- **`sources(sources: FieldProcessorMap): this`**
|
|
280
|
+
Add or override data sources
|
|
281
|
+
|
|
282
|
+
- **`sections(sections: Record<string, FieldProcessorSectionFunc>): this`**
|
|
283
|
+
Register section handlers by type
|
|
284
|
+
|
|
285
|
+
- **`variables(vars: Record<string, any>): this`**
|
|
286
|
+
Set global variables
|
|
287
|
+
|
|
288
|
+
- **`functions(funcs: Record<string, AsyncFunction>): this`**
|
|
289
|
+
Register callable functions for expressions and script sections
|
|
290
|
+
|
|
291
|
+
- **`fieldOptions(options: ProcessFieldsOptions): this`**
|
|
292
|
+
Customize field processing behavior
|
|
293
|
+
|
|
294
|
+
- **`filter(f: RegExp): this`**
|
|
295
|
+
Add a filter for field processing
|
|
296
|
+
|
|
297
|
+
- **`output(o: Output): this`**
|
|
298
|
+
Add an output definition
|
|
299
|
+
|
|
300
|
+
- **`metadata(path: string, value: unknown): this`**
|
|
301
|
+
Store metadata for a path
|
|
302
|
+
|
|
303
|
+
- **`section(section: FieldProcessorSection): this`**
|
|
304
|
+
Register a named section
|
|
305
|
+
|
|
306
|
+
- **`encoders(encoders: Record<string, Encoder>): this`**
|
|
307
|
+
Add output encoders (used by `formatAs`)
|
|
308
|
+
|
|
309
|
+
- **`decoders(decoders: Record<string, LoadFormatParser>): this`**
|
|
310
|
+
Add input decoders for `load()`
|
|
311
|
+
|
|
312
|
+
- **`setCacheMaxSize(size: number): this`**
|
|
313
|
+
Set include/content cache size
|
|
314
|
+
|
|
315
|
+
- **`clearCache(): this`**
|
|
316
|
+
Clear cached loaded content
|
|
317
|
+
|
|
318
|
+
#### Processing Methods
|
|
319
|
+
|
|
320
|
+
- **`async load<T>(fileName: string, cwd?: string, container?: any, noProcess?: boolean): Promise<T | null>`**
|
|
321
|
+
Load and process a file
|
|
322
|
+
|
|
323
|
+
- **`async loadObject<T>(data: T, container?: any, options?: LoadObjectOptions): Promise<T>`**
|
|
324
|
+
Process an object with field processors
|
|
325
|
+
|
|
326
|
+
- **`async writeAll(options?: WriteOutputsOption): Promise<void>`**
|
|
327
|
+
Write all registered outputs to disk
|
|
328
|
+
|
|
329
|
+
#### Query Methods
|
|
330
|
+
|
|
331
|
+
- **`getIncludeDirectories(): string[]`**
|
|
332
|
+
Get all include directories
|
|
333
|
+
|
|
334
|
+
- **`getFunctions(): Record<string, AsyncFunction>`**
|
|
335
|
+
Get registered functions
|
|
336
|
+
|
|
337
|
+
- **`getOutputs(): Output[]`**
|
|
338
|
+
Get all registered outputs
|
|
339
|
+
|
|
340
|
+
- **`getMetadata(path: string): any`**
|
|
341
|
+
Get metadata for a path
|
|
342
|
+
|
|
343
|
+
- **`getAllMetadata(): Record<string, any>`**
|
|
344
|
+
Get all metadata
|
|
345
|
+
|
|
346
|
+
- **`getCurrentContext(): LoadContext | null`**
|
|
347
|
+
Get current processing context
|
|
348
|
+
|
|
349
|
+
- **`getSection(name: string): FieldProcessorSection | undefined`**
|
|
350
|
+
Get a stored section by name
|
|
351
|
+
|
|
352
|
+
- **`getEncoder(name: string): Encoder | undefined`**
|
|
353
|
+
Get an encoder by name
|
|
354
|
+
|
|
355
|
+
- **`getDecoder(name: string): LoadFormatParser | undefined`**
|
|
356
|
+
Get a decoder by name
|
|
357
|
+
|
|
358
|
+
## Advanced Usage
|
|
359
|
+
|
|
360
|
+
### Custom Data Sources
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
import { Objector } from '@homedev/objector'
|
|
364
|
+
|
|
365
|
+
const objector = new Objector()
|
|
366
|
+
|
|
367
|
+
objector.sources({
|
|
368
|
+
// Simple value source
|
|
369
|
+
timestamp: () => Date.now(),
|
|
370
|
+
|
|
371
|
+
// Source with arguments
|
|
372
|
+
add: (ctx) => {
|
|
373
|
+
const [a, b] = ctx.args
|
|
374
|
+
return a + b
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
// Async source
|
|
378
|
+
fetchData: async (ctx) => {
|
|
379
|
+
const url = ctx.args[0]
|
|
380
|
+
const response = await fetch(url)
|
|
381
|
+
return response.json()
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Usage:**
|
|
387
|
+
```yaml
|
|
388
|
+
data:
|
|
389
|
+
created: $(timestamp)
|
|
390
|
+
sum: $(add:10, 20)
|
|
391
|
+
remote: $(fetchData:"https://api.example.com/data")
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Custom Key Processors
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
objector.keys({
|
|
398
|
+
// Custom transformation key
|
|
399
|
+
uppercase: (ctx) => {
|
|
400
|
+
return ctx.value.toUpperCase()
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
// Conditional key
|
|
404
|
+
when: (ctx) => {
|
|
405
|
+
const condition = ctx.value
|
|
406
|
+
if (!condition) {
|
|
407
|
+
return NavigateResult.DeleteParent()
|
|
408
|
+
}
|
|
409
|
+
return NavigateResult.DeleteItem()
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**Usage:**
|
|
415
|
+
```yaml
|
|
416
|
+
settings:
|
|
417
|
+
name:
|
|
418
|
+
.uppercase: hello world
|
|
419
|
+
# Result: "HELLO WORLD"
|
|
420
|
+
|
|
421
|
+
feature:
|
|
422
|
+
.when: $(eq:env, "production")
|
|
423
|
+
enabled: true
|
|
424
|
+
# Only included when condition is true
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Metadata Collection
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const objector = new Objector()
|
|
431
|
+
|
|
432
|
+
// Metadata is collected during processing
|
|
433
|
+
await objector.load('config.yaml')
|
|
434
|
+
|
|
435
|
+
// Retrieve metadata
|
|
436
|
+
const metadata = objector.getMetadata('some.path')
|
|
437
|
+
const allMetadata = objector.getAllMetadata()
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**In documents:**
|
|
441
|
+
```yaml
|
|
442
|
+
service:
|
|
443
|
+
.metadata:
|
|
444
|
+
version: 1.0
|
|
445
|
+
author: team-a
|
|
446
|
+
name: api
|
|
447
|
+
port: 8080
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Section Blocks (`<@ section ... @>`)
|
|
451
|
+
|
|
452
|
+
Objector can process inline section blocks inside strings. This is useful for generated text files (Markdown, scripts, templates).
|
|
453
|
+
|
|
454
|
+
### Basic Syntax
|
|
455
|
+
|
|
456
|
+
```txt
|
|
457
|
+
<@ section[:type] @>
|
|
458
|
+
# optional YAML config
|
|
459
|
+
---
|
|
460
|
+
# section body
|
|
461
|
+
<@ /section @>
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
- `:type` is optional. Example: `section:script`.
|
|
465
|
+
- `---` separates YAML config from section content.
|
|
466
|
+
- If no `---` is present, the block is treated as content-only.
|
|
467
|
+
|
|
468
|
+
### Script Sections
|
|
469
|
+
|
|
470
|
+
`script` is the default built-in section type.
|
|
471
|
+
|
|
472
|
+
```yaml
|
|
473
|
+
doc: |
|
|
474
|
+
<@ section:script @>
|
|
475
|
+
using: [shout, project]
|
|
476
|
+
condition: context.enabled
|
|
477
|
+
each: context.items
|
|
478
|
+
---
|
|
479
|
+
section.writeLine(`# ${project}`)
|
|
480
|
+
return shout(String(item))
|
|
481
|
+
<@ /section @>
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
For `section:script`, the body runs as async JavaScript with access to:
|
|
485
|
+
|
|
486
|
+
- `section`: helper object (`write`, `writeLine`, `prepend`, `clear`, `setOptions`, `getLines`)
|
|
487
|
+
- `context`: current root/context object
|
|
488
|
+
- `config`: section YAML config
|
|
489
|
+
- `system`: current `Objector` instance
|
|
490
|
+
- `item`: current item when `each` is used
|
|
491
|
+
- values listed in `using` (from registered `functions()` or root variables)
|
|
492
|
+
|
|
493
|
+
`condition` can skip rendering, and `each` can iterate an array expression. If the script returns a string, that value becomes the rendered section output.
|
|
494
|
+
|
|
495
|
+
### Reusing Named Sections
|
|
496
|
+
|
|
497
|
+
You can store a section by name and render it later with `$(section:name)`:
|
|
498
|
+
|
|
499
|
+
```yaml
|
|
500
|
+
doc: |
|
|
501
|
+
<@ section @>
|
|
502
|
+
name: greeting
|
|
503
|
+
show: true
|
|
504
|
+
---
|
|
505
|
+
Hello $(user)
|
|
506
|
+
<@ /section @>
|
|
507
|
+
|
|
508
|
+
footer: $(section:greeting)
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Examples
|
|
512
|
+
|
|
513
|
+
### Example 1: Multi-Environment Configuration
|
|
514
|
+
|
|
515
|
+
```yaml
|
|
516
|
+
# base.yaml
|
|
517
|
+
includes:
|
|
518
|
+
- vars-$(env).yaml
|
|
519
|
+
|
|
520
|
+
application:
|
|
521
|
+
name: myapp
|
|
522
|
+
version: 1.0.0
|
|
523
|
+
|
|
524
|
+
database:
|
|
525
|
+
host: $(db.host)
|
|
526
|
+
port: $(db.port)
|
|
527
|
+
name: $(db.name)
|
|
528
|
+
|
|
529
|
+
features:
|
|
530
|
+
- name: caching
|
|
531
|
+
.if: $(eq:env, "production")
|
|
532
|
+
.then:
|
|
533
|
+
enabled: true
|
|
534
|
+
ttl: 3600
|
|
535
|
+
.else:
|
|
536
|
+
enabled: false
|
|
537
|
+
|
|
538
|
+
- name: logging
|
|
539
|
+
level: $(switch:
|
|
540
|
+
from: $(env)
|
|
541
|
+
cases:
|
|
542
|
+
production: warn
|
|
543
|
+
staging: info
|
|
544
|
+
default: debug)
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Example 2: Service Generation
|
|
548
|
+
|
|
549
|
+
```yaml
|
|
550
|
+
services:
|
|
551
|
+
.each:
|
|
552
|
+
.from:
|
|
553
|
+
- name: api
|
|
554
|
+
port: 8080
|
|
555
|
+
replicas: 3
|
|
556
|
+
- name: worker
|
|
557
|
+
port: 8081
|
|
558
|
+
replicas: 5
|
|
559
|
+
- name: web
|
|
560
|
+
port: 3000
|
|
561
|
+
replicas: 2
|
|
562
|
+
.model:
|
|
563
|
+
name: $(item.name)
|
|
564
|
+
type: deployment
|
|
565
|
+
spec:
|
|
566
|
+
replicas: $(item.replicas)
|
|
567
|
+
template:
|
|
568
|
+
containers:
|
|
569
|
+
- name: $(item.name)
|
|
570
|
+
image: myapp/$(item.name):$(version)
|
|
571
|
+
ports:
|
|
572
|
+
- containerPort: $(item.port)
|
|
573
|
+
env:
|
|
574
|
+
- name: NODE_ENV
|
|
575
|
+
value: $(env)
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Example 3: Dynamic Output Generation
|
|
579
|
+
|
|
580
|
+
```yaml
|
|
581
|
+
outputs:
|
|
582
|
+
.each:
|
|
583
|
+
.from: $(scan:"*.template.yaml", "./templates")
|
|
584
|
+
.model:
|
|
585
|
+
.output:
|
|
586
|
+
file: dist/$(replace:$(item), ".template.yaml", ".yaml")
|
|
587
|
+
content: $(file:$(item))
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## FAQ
|
|
591
|
+
|
|
592
|
+
**Q: How do I debug field processing?**
|
|
593
|
+
A: Use the `$(log:message)` data source to print values during processing.
|
|
594
|
+
|
|
595
|
+
**Q: Can I use async operations in custom sources?**
|
|
596
|
+
A: Yes! All field processors and data sources support async/await.
|
|
597
|
+
|
|
598
|
+
**Q: How do I handle circular includes?**
|
|
599
|
+
A: The system tracks included files to prevent circular dependencies.
|
|
600
|
+
|
|
601
|
+
**Q: Can I extend the built-in processors?**
|
|
602
|
+
A: Yes, use `objector.keys()` and `objector.sources()` to add or override processors.
|
|
603
|
+
|
|
604
|
+
**Q: How do I access nested values?**
|
|
605
|
+
A: Use the selector syntax: `$(some.nested.value)` or `$(@root.some.value)` for root access.
|
|
606
|
+
|
|
607
|
+
**Q: What's the difference between keys and sources?**
|
|
608
|
+
A: Keys are prefixed with `.` and control structure/flow (`.if`, `.each`). Sources use `$()` syntax and provide values (`$(env:VAR)`, `$(file:path)`).
|