kfg 0.0.1 → 1.1.0

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/dist/index.js ADDED
@@ -0,0 +1,1152 @@
1
+ // src/kfg-fs.ts
2
+ import * as fs from "node:fs";
3
+
4
+ // src/utils/object.ts
5
+ function getProperty(obj, path3) {
6
+ return path3.split(".").reduce((acc, key) => acc?.[key], obj);
7
+ }
8
+ function setProperty(obj, path3, value) {
9
+ const keys = path3.split(".");
10
+ const lastKey = keys.pop();
11
+ let target = obj;
12
+ for (const key of keys) {
13
+ if (target[key] === void 0 || target[key] === null) {
14
+ target[key] = {};
15
+ } else if (typeof target[key] !== "object") {
16
+ throw new Error(`Cannot set property on non-object at path: ${key}`);
17
+ }
18
+ target = target[key];
19
+ }
20
+ target[lastKey] = value;
21
+ }
22
+ function isObject(item) {
23
+ return item !== null && typeof item === "object" && !Array.isArray(item);
24
+ }
25
+ function deepMerge(target, source) {
26
+ const output = { ...target };
27
+ if (isObject(target) && isObject(source)) {
28
+ Object.keys(source).forEach((key) => {
29
+ const sourceValue = source[key];
30
+ const targetValue = target[key];
31
+ if (isObject(sourceValue) && isObject(targetValue)) {
32
+ output[key] = deepMerge(
33
+ targetValue,
34
+ sourceValue
35
+ );
36
+ } else {
37
+ output[key] = sourceValue;
38
+ }
39
+ });
40
+ }
41
+ return output;
42
+ }
43
+ function flattenObject(obj, prefix = "") {
44
+ return Object.keys(obj).reduce(
45
+ (acc, k2) => {
46
+ const pre = prefix.length ? `${prefix}.` : "";
47
+ if (isObject(obj[k2])) {
48
+ Object.assign(acc, flattenObject(obj[k2], pre + k2));
49
+ } else {
50
+ acc[pre + k2] = obj[k2];
51
+ }
52
+ return acc;
53
+ },
54
+ {}
55
+ );
56
+ }
57
+ function unflattenObject(obj) {
58
+ const result = {};
59
+ for (const key in obj) {
60
+ setProperty(result, key, obj[key]);
61
+ }
62
+ return result;
63
+ }
64
+ function deleteProperty(obj, path3) {
65
+ const keys = path3.split(".");
66
+ const lastKey = keys.pop();
67
+ let target = obj;
68
+ for (const key of keys) {
69
+ if (target?.[key] === void 0) {
70
+ return false;
71
+ }
72
+ target = target[key];
73
+ }
74
+ if (typeof target === "object" && target !== null) {
75
+ return delete target[lastKey];
76
+ }
77
+ return false;
78
+ }
79
+
80
+ // src/utils/schema.ts
81
+ import {
82
+ Type
83
+ } from "@sinclair/typebox";
84
+ import { Value } from "@sinclair/typebox/value";
85
+ function addSmartDefaults(schemaNode) {
86
+ if (schemaNode.type !== "object" || !schemaNode.properties) {
87
+ return;
88
+ }
89
+ let allChildrenOptional = true;
90
+ for (const key in schemaNode.properties) {
91
+ const prop = schemaNode.properties[key];
92
+ if (prop[Symbol.for("TypeBox.Kind")] === "Unsafe") {
93
+ continue;
94
+ }
95
+ if (prop.type === "object" && prop[Symbol.for("TypeBox.Kind")]) {
96
+ addSmartDefaults(prop);
97
+ }
98
+ const hasDefault = prop.default !== void 0;
99
+ const isOptional = Value.Check(Type.Object({ temp: prop }), {});
100
+ if (!hasDefault && !isOptional) {
101
+ allChildrenOptional = false;
102
+ }
103
+ }
104
+ if (allChildrenOptional && schemaNode.default === void 0) {
105
+ schemaNode.default = {};
106
+ }
107
+ }
108
+ function buildTypeBoxSchema(definition) {
109
+ if (definition[Symbol.for("TypeBox.Kind")] === "Object") {
110
+ return definition;
111
+ }
112
+ const properties = {};
113
+ for (const key in definition) {
114
+ const value = definition[key];
115
+ const isObject2 = typeof value === "object" && value !== null && !value[Symbol.for("TypeBox.Kind")];
116
+ if (isObject2) {
117
+ properties[key] = buildTypeBoxSchema(value);
118
+ } else {
119
+ properties[key] = value;
120
+ }
121
+ }
122
+ return Type.Object(properties, { additionalProperties: true });
123
+ }
124
+ function buildDefaultObject(definition) {
125
+ const schema = buildTypeBoxSchema(definition);
126
+ addSmartDefaults(schema);
127
+ return Value.Default(schema, {});
128
+ }
129
+ function makeSchemaOptional(definition) {
130
+ const newDefinition = {};
131
+ for (const key in definition) {
132
+ const value = definition[key];
133
+ if (value?.[Symbol.for("TypeBox.Kind")]) {
134
+ const schema = value;
135
+ const isOptional = Value.Check(Type.Object({ temp: schema }), {});
136
+ if (schema.important || isOptional) {
137
+ newDefinition[key] = schema;
138
+ } else {
139
+ newDefinition[key] = Type.Optional(schema);
140
+ }
141
+ } else if (typeof value === "object" && value !== null) {
142
+ newDefinition[key] = makeSchemaOptional(value);
143
+ } else {
144
+ newDefinition[key] = value;
145
+ }
146
+ }
147
+ return newDefinition;
148
+ }
149
+
150
+ // src/kfg.ts
151
+ var Kfg = class {
152
+ driver;
153
+ schema;
154
+ loaded = false;
155
+ _lastOptions;
156
+ /**
157
+ * Creates a new instance of Kfg.
158
+ * @param driver The driver to use for loading and saving the configuration.
159
+ * @param schema The schema to use for validating the configuration.
160
+ */
161
+ constructor(driver, schema) {
162
+ this.driver = driver.clone();
163
+ this.schema = schema;
164
+ }
165
+ /**
166
+ * Reloads the configuration.
167
+ * @param options - The loading options.
168
+ */
169
+ reload(options) {
170
+ this.loaded = false;
171
+ return this.load(options || this._lastOptions);
172
+ }
173
+ /**
174
+ * Loads the configuration.
175
+ * @param options - The loading options.
176
+ */
177
+ load(options) {
178
+ this._lastOptions = options;
179
+ let schemaToLoad = this.schema;
180
+ if (options?.only_importants) {
181
+ schemaToLoad = makeSchemaOptional(this.schema);
182
+ }
183
+ const result = this.driver.load(schemaToLoad, options);
184
+ if (this.driver.async) {
185
+ return result.then(() => {
186
+ this.loaded = true;
187
+ });
188
+ }
189
+ this.loaded = true;
190
+ return result;
191
+ }
192
+ /**
193
+ * Gets a value from the configuration.
194
+ * @param path The path to the value.
195
+ * @returns The value at the given path.
196
+ */
197
+ get(path3) {
198
+ if (!this.loaded) {
199
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
200
+ }
201
+ return this.driver.get(path3);
202
+ }
203
+ /**
204
+ * Checks if a value exists in the configuration.
205
+ * @param paths The paths to the values.
206
+ * @returns True if all values exist, false otherwise.
207
+ */
208
+ has(...paths) {
209
+ if (!this.loaded) {
210
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
211
+ }
212
+ return this.driver.has(...paths);
213
+ }
214
+ /**
215
+ * Gets a value from the configuration.
216
+ * @param path The path to the value.
217
+ * @returns The value at the given path.
218
+ */
219
+ root(path3) {
220
+ if (!this.loaded) {
221
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
222
+ }
223
+ return this.driver.get(path3);
224
+ }
225
+ /**
226
+ * Sets a value in the configuration.
227
+ * @param path The path to the value.
228
+ * @param value The new value.
229
+ * @param options The options for setting the value.
230
+ */
231
+ set(path3, value, options) {
232
+ if (!this.loaded) {
233
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
234
+ }
235
+ return this.driver.set(path3, value, options);
236
+ }
237
+ /**
238
+ * Inserts a partial value into an object in the configuration.
239
+ * @param path The path to the object.
240
+ * @param partial The partial value to insert.
241
+ */
242
+ insert(path3, partial) {
243
+ if (!this.loaded) {
244
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
245
+ }
246
+ return this.driver.insert(path3, partial);
247
+ }
248
+ /**
249
+ * Injects a partial value directly into the root configuration object.
250
+ * @param data The partial data to inject.
251
+ */
252
+ inject(data) {
253
+ if (!this.loaded) {
254
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
255
+ }
256
+ return this.driver.inject(data);
257
+ }
258
+ /**
259
+ * Deletes a value from the configuration.
260
+ * @param path The path to the value.
261
+ */
262
+ del(path3) {
263
+ if (!this.loaded) {
264
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
265
+ }
266
+ return this.driver.del(path3);
267
+ }
268
+ /**
269
+ * Gets the schema for a given path.
270
+ * @param path The path to the schema.
271
+ * @returns The schema at the given path.
272
+ */
273
+ conf(path3) {
274
+ if (!this.loaded) {
275
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
276
+ }
277
+ return getProperty(this.schema, path3);
278
+ }
279
+ /**
280
+ * Returns the schema definition for a given path.
281
+ * @param path The path to the schema.
282
+ * @returns The schema at the given path.
283
+ */
284
+ schematic(path3) {
285
+ return this.conf(path3);
286
+ }
287
+ /**
288
+ * Returns cached data
289
+ * @returns
290
+ */
291
+ async toJSON() {
292
+ if (!this.loaded) {
293
+ throw new Error("[Kfg] Config not loaded. Call load() first.");
294
+ }
295
+ if (this.driver.async) {
296
+ return Promise.resolve(this.driver.data);
297
+ }
298
+ return this.driver.data;
299
+ }
300
+ };
301
+
302
+ // src/fs-factory.ts
303
+ import { Type as Type2 } from "@sinclair/typebox";
304
+ var KFS_MANY_SYMBOL = Symbol.for("Kfg.many");
305
+ var KFS_JOIN_SYMBOL = Symbol.for("Kfg.join");
306
+ var _cfs = {
307
+ /**
308
+ * Creates a many-to-many relation in a schema.
309
+ * @param kfgFs The KfgFS instance to relate to.
310
+ * @param options The schema options.
311
+ */
312
+ many: (kfgFs, options) => Type2.Unsafe(
313
+ Type2.Array(Type2.String(), {
314
+ ...options,
315
+ [KFS_MANY_SYMBOL]: {
316
+ kfgFs
317
+ }
318
+ })
319
+ ),
320
+ join: (kfgFs, options) => Type2.Unsafe(
321
+ Type2.Object(
322
+ {},
323
+ {
324
+ ...options,
325
+ [KFS_JOIN_SYMBOL]: {
326
+ kfgFs,
327
+ fk: options?.fk
328
+ }
329
+ }
330
+ )
331
+ )
332
+ };
333
+ var cfs = {
334
+ ..._cfs
335
+ };
336
+ var kfs = cfs;
337
+
338
+ // src/kfg-fs.ts
339
+ var KfgFileFS = class extends Kfg {
340
+ /**
341
+ * Creates a new instance of KfgFileFS.
342
+ * @param driver The driver to use for loading and saving the configuration.
343
+ * @param schema The schema to use for validating the configuration.
344
+ * @param filePath The path to the configuration file.
345
+ */
346
+ constructor(driver, schema, filePath) {
347
+ super(driver, schema);
348
+ this.filePath = filePath;
349
+ }
350
+ /**
351
+ * Loads the configuration from the file.
352
+ * @param options The loading options.
353
+ */
354
+ load(options) {
355
+ const loadOptions = { ...options, path: this.filePath };
356
+ return super.load(loadOptions);
357
+ }
358
+ /**
359
+ * Sets a value in the configuration.
360
+ * @param path The path to the value.
361
+ * @param value The new value.
362
+ * @param options The options for setting the value.
363
+ */
364
+ //@ts-ignore KfgFS change internal logic of Kfg, ignore this
365
+ set(path3, value, options) {
366
+ return super.set(path3, value, options);
367
+ }
368
+ /**
369
+ * Inserts a partial value into an object in the configuration.
370
+ * @param path The path to the object.
371
+ * @param partial The partial value to insert.
372
+ */
373
+ //@ts-ignore KfgFS change internal logic of Kfg, ignore this
374
+ insert(path3, value) {
375
+ return super.insert(path3, value);
376
+ }
377
+ /**
378
+ * Gets a value from the configuration.
379
+ * @param path The path to the value.
380
+ * @returns The value at the given path.
381
+ */
382
+ //@ts-ignore KfgFS change internal logic of Kfg, ignore this
383
+ root(path3) {
384
+ return super.root(path3);
385
+ }
386
+ /**
387
+ * Checks if a value exists in the configuration.
388
+ * @param paths The paths to the values.
389
+ * @returns True if all values exist, false otherwise.
390
+ */
391
+ has(...paths) {
392
+ return super.has(...paths);
393
+ }
394
+ /**
395
+ * Gets the schema for a given path.
396
+ * @param path The path to the schema.
397
+ * @returns The schema at the given path.
398
+ */
399
+ conf(path3) {
400
+ return super.conf(path3);
401
+ }
402
+ /**
403
+ * Returns cached data
404
+ * @returns
405
+ */
406
+ //@ts-ignore more recursive types, ignore
407
+ toJSON() {
408
+ return super.toJSON();
409
+ }
410
+ /**
411
+ * Saves the configuration to the file.
412
+ */
413
+ save() {
414
+ return this.driver.set(null, this.driver.data);
415
+ }
416
+ /**
417
+ * Gets the related configurations for a many-to-many relation.
418
+ * @param path The path to the relation.
419
+ * @returns An array of related configurations.
420
+ */
421
+ getMany(path3) {
422
+ const schema = this.conf(path3);
423
+ const manyInfo = schema[KFS_MANY_SYMBOL];
424
+ if (!manyInfo) {
425
+ throw new Error(`[KfgFS] '${path3}' is not a many-relation field.`);
426
+ }
427
+ const ids = this.get(path3);
428
+ if (!ids || !Array.isArray(ids)) {
429
+ return void 0;
430
+ }
431
+ const related = manyInfo.kfgFs;
432
+ const files = ids.map((id) => related.file(id));
433
+ if (this.driver.async) {
434
+ return Promise.all(files);
435
+ }
436
+ return files;
437
+ }
438
+ /**
439
+ * Gets the related configuration for a one-to-one relation.
440
+ * @param path The path to the relation.
441
+ * @returns The related configuration.
442
+ */
443
+ getJoin(path3) {
444
+ const schema = this.conf(path3);
445
+ const joinInfo = schema[KFS_JOIN_SYMBOL];
446
+ if (!joinInfo) {
447
+ throw new Error(`[KfgFS] '${path3}' is not a join-relation field.`);
448
+ }
449
+ const fkValue = this.get(joinInfo.fk);
450
+ if (!fkValue) {
451
+ return void 0;
452
+ }
453
+ const related = joinInfo.kfgFs;
454
+ const instance = related.file(fkValue);
455
+ if (this.driver.async) {
456
+ return instance.then(
457
+ async (instance2) => {
458
+ await instance2.load();
459
+ return instance2;
460
+ }
461
+ );
462
+ }
463
+ const loadedInstance = instance;
464
+ loadedInstance.load();
465
+ return loadedInstance;
466
+ }
467
+ /**
468
+ * Returns the file path of the configuration.
469
+ */
470
+ toString() {
471
+ return this.filePath;
472
+ }
473
+ };
474
+ var KfgFS = class {
475
+ /**
476
+ * Creates a new instance of KfgFS.
477
+ * @param driver The driver to use for loading and saving the configurations.
478
+ * @param schema The schema to use for validating the configurations.
479
+ * @param config The configuration options.
480
+ */
481
+ constructor(driver, schema, config) {
482
+ this.driver = driver;
483
+ this.schema = schema;
484
+ this.config = config;
485
+ }
486
+ pathFn;
487
+ /**
488
+ * Initializes the KfgFS instance with a path function.
489
+ * @param pathFn A function that returns the file path for a given ID.
490
+ */
491
+ init(pathFn) {
492
+ this.pathFn = pathFn;
493
+ }
494
+ /**
495
+ * Gets the file path for a given ID.
496
+ * @param id The ID of the configuration file.
497
+ * @returns The file path.
498
+ */
499
+ getPath(id) {
500
+ if (!this.pathFn) {
501
+ throw new Error(
502
+ "[KfgFS] KfgFS not initialized. Call init() first."
503
+ );
504
+ }
505
+ return this.pathFn(id);
506
+ }
507
+ /**
508
+ * Checks if a configuration file exists for a given ID.
509
+ * @param id The ID of the configuration file.
510
+ * @returns True if the file exists, false otherwise.
511
+ */
512
+ exist(id) {
513
+ const filePath = this.getPath(id);
514
+ return fs.existsSync(filePath);
515
+ }
516
+ /**
517
+ * Creates a new configuration file for a given ID.
518
+ * @param id The ID of the configuration file.
519
+ * @param data Optional initial data for the configuration.
520
+ * @returns The created KfgFileFS instance.
521
+ */
522
+ create(id, data) {
523
+ if (this.exist(id)) {
524
+ throw new Error(`[KfgFS] Config with id '${id}' already exists.`);
525
+ }
526
+ const instanceOrPromise = this.file(id);
527
+ const initialize = (instance) => {
528
+ if (data) {
529
+ instance.inject(data);
530
+ }
531
+ const saveResult = instance.save();
532
+ if (this.driver.async) {
533
+ return saveResult.then(() => instance);
534
+ }
535
+ return instance;
536
+ };
537
+ if (this.driver.async) {
538
+ return instanceOrPromise.then(
539
+ initialize
540
+ );
541
+ } else {
542
+ return initialize(instanceOrPromise);
543
+ }
544
+ }
545
+ /**
546
+ * Gets a file-based configuration for a given ID.
547
+ * @param id The ID of the configuration file.
548
+ * @returns A KfgFileFS instance.
549
+ */
550
+ file(id) {
551
+ const filePath = this.getPath(id);
552
+ const newDriver = new this.driver.constructor(
553
+ this.driver.options
554
+ );
555
+ const fileInstance = new KfgFileFS(newDriver, this.schema, filePath);
556
+ const loadResult = fileInstance.load(this.config);
557
+ if (this.driver.async) {
558
+ return loadResult.then(
559
+ () => fileInstance
560
+ );
561
+ }
562
+ return fileInstance;
563
+ }
564
+ /**
565
+ * Deletes a configuration file.
566
+ * @param id The ID of the configuration file.
567
+ */
568
+ del(id) {
569
+ const filePath = this.getPath(id);
570
+ if (fs.existsSync(filePath)) {
571
+ fs.unlinkSync(filePath);
572
+ }
573
+ }
574
+ /**
575
+ * Copies a configuration file.
576
+ * @param fromId The ID of the source configuration file.
577
+ * @param toId The ID of the destination configuration file.
578
+ */
579
+ copy(fromId, toId) {
580
+ const fromPath = this.getPath(fromId);
581
+ const toPath = this.getPath(toId);
582
+ fs.copyFileSync(fromPath, toPath);
583
+ }
584
+ /**
585
+ * Gets the configuration data for a given ID.
586
+ * @param id The ID of the configuration file.
587
+ * @returns The configuration data.
588
+ */
589
+ toJSON(id) {
590
+ const fileInstance = this.file(id);
591
+ if (fileInstance instanceof Promise) {
592
+ return fileInstance.then((i) => i.toJSON());
593
+ } else {
594
+ return fileInstance.toJSON();
595
+ }
596
+ }
597
+ };
598
+
599
+ // src/kfg-driver.ts
600
+ import { Value as Value2 } from "@sinclair/typebox/value";
601
+ var KfgDriver = class {
602
+ /**
603
+ * Creates a new instance of KfgDriver.
604
+ * @param options The driver options.
605
+ */
606
+ constructor(options) {
607
+ this.options = options;
608
+ this.identify = options.identify;
609
+ this.async = options.async;
610
+ this.config = options.config || {};
611
+ this._onLoad = options.onLoad;
612
+ this._onSet = options.onSet;
613
+ this._onDel = options.onDel;
614
+ }
615
+ identify;
616
+ async = void 0;
617
+ config;
618
+ data = {};
619
+ comments;
620
+ compiledSchema;
621
+ store = {};
622
+ _onSet;
623
+ _onDel;
624
+ // Utilities passed to drivers
625
+ buildDefaultObject = buildDefaultObject;
626
+ deepMerge = deepMerge;
627
+ /**
628
+ * Clones the driver.
629
+ * @returns A new instance of the driver with the same options.
630
+ */
631
+ clone() {
632
+ const Constructor = this.constructor;
633
+ return new Constructor(this.options);
634
+ }
635
+ /**
636
+ * Injects data directly into the driver's data store.
637
+ * This data is merged with the existing data.
638
+ * @param data The data to inject.
639
+ */
640
+ inject(data) {
641
+ this.data = this.deepMerge(this.data, data);
642
+ return this.async ? Promise.resolve() : void 0;
643
+ }
644
+ /**
645
+ * Loads the configuration.
646
+ * @param schema The schema to use for validating the configuration.
647
+ * @param options The loading options.
648
+ */
649
+ load(schema, options = {}) {
650
+ this.compiledSchema = buildTypeBoxSchema(schema);
651
+ addSmartDefaults(this.compiledSchema);
652
+ this.config = { ...this.config, ...options };
653
+ const processResult = (result) => {
654
+ this.data = result;
655
+ this.validate(this.data);
656
+ };
657
+ if (this._onLoad) {
658
+ const loadResult = this._onLoad.call(this, schema, this.config);
659
+ if (this.async) {
660
+ return loadResult.then(processResult);
661
+ }
662
+ processResult(loadResult);
663
+ }
664
+ return void 0;
665
+ }
666
+ /**
667
+ * Gets a value from the configuration.
668
+ * @param path The path to the value.
669
+ * @returns The value at the given path.
670
+ */
671
+ get(path3) {
672
+ const value = getProperty(this.data, path3);
673
+ if (this.async) {
674
+ return Promise.resolve(value);
675
+ }
676
+ return value;
677
+ }
678
+ /**
679
+ * Checks if a value exists in the configuration.
680
+ * @param paths The paths to the values.
681
+ * @returns True if all values exist, false otherwise.
682
+ */
683
+ has(...paths) {
684
+ const hasAllProps = paths.every(
685
+ (path3) => getProperty(this.data, path3) !== void 0
686
+ );
687
+ if (this.async) {
688
+ return Promise.resolve(hasAllProps);
689
+ }
690
+ return hasAllProps;
691
+ }
692
+ /**
693
+ * Sets a value in the configuration.
694
+ * @param path The path to the value.
695
+ * @param value The new value.
696
+ * @param options The options for setting the value.
697
+ */
698
+ set(path3, value, options) {
699
+ if (path3) {
700
+ setProperty(this.data, path3, value);
701
+ }
702
+ if (this._onSet) {
703
+ return this._onSet.call(this, path3, value, options);
704
+ }
705
+ return this.async ? Promise.resolve() : void 0;
706
+ }
707
+ /**
708
+ * Inserts a partial value into an object in the configuration.
709
+ * @param path The path to the object.
710
+ * @param partial The partial value to insert.
711
+ */
712
+ insert(path3, partial) {
713
+ const currentObject = getProperty(this.data, path3);
714
+ if (typeof currentObject !== "object" || currentObject === null) {
715
+ throw new Error(`Cannot insert into non-object at path: ${path3}`);
716
+ }
717
+ Object.assign(currentObject, partial);
718
+ return this.set(path3, currentObject);
719
+ }
720
+ /**
721
+ * Deletes a value from the configuration.
722
+ * @param path The path to the value.
723
+ */
724
+ del(path3) {
725
+ deleteProperty(this.data, path3);
726
+ if (this._onDel) {
727
+ return this._onDel.call(this, path3);
728
+ }
729
+ return this.async ? Promise.resolve() : void 0;
730
+ }
731
+ /**
732
+ * Validates the configuration against the schema.
733
+ * @param config The configuration to validate.
734
+ */
735
+ validate(config = this.data) {
736
+ if (!this.compiledSchema) return;
737
+ const configWithDefaults = Value2.Default(this.compiledSchema, config);
738
+ Value2.Convert(this.compiledSchema, configWithDefaults);
739
+ if (!Value2.Check(this.compiledSchema, configWithDefaults)) {
740
+ const errors = [...Value2.Errors(this.compiledSchema, configWithDefaults)];
741
+ throw new Error(
742
+ `[Kfg] Validation failed:
743
+ ${errors.map((e) => `- ${e.path}: ${e.message}`).join("\n")}`
744
+ // Corrected: escaped backtick in template literal
745
+ );
746
+ }
747
+ this.data = configWithDefaults;
748
+ }
749
+ };
750
+
751
+ // src/old.ts
752
+ var ConfigJS = Kfg;
753
+ var ConfigFS = KfgFS;
754
+ var FileFSConfigJS = KfgFileFS;
755
+ var ConfigJSDriver = KfgDriver;
756
+
757
+ // src/drivers/env-driver.ts
758
+ import * as fs2 from "node:fs";
759
+ import * as path from "node:path";
760
+
761
+ // src/utils/env.ts
762
+ function parse(content) {
763
+ const result = {};
764
+ const lines = content.split(/\r?\n/);
765
+ for (const line of lines) {
766
+ const trimmedLine = line.trim();
767
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
768
+ continue;
769
+ }
770
+ const match = trimmedLine.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$/);
771
+ if (!match) continue;
772
+ const [, key, rawValue] = match;
773
+ let value = rawValue.trim();
774
+ if (!value.startsWith('"') && !value.startsWith("'")) {
775
+ const commentIndex = value.indexOf("#");
776
+ if (commentIndex > -1) {
777
+ value = value.substring(0, commentIndex).trim();
778
+ }
779
+ }
780
+ if (value.startsWith("'") && value.endsWith("'")) {
781
+ value = value.substring(1, value.length - 1);
782
+ } else if (value.startsWith('"') && value.endsWith('"')) {
783
+ value = value.substring(1, value.length - 1);
784
+ }
785
+ result[key] = value;
786
+ }
787
+ return result;
788
+ }
789
+ function updateEnvContent(content, key, value, description) {
790
+ const lines = content.split(/\r?\n/);
791
+ let keyFound = false;
792
+ const newLines = [...lines];
793
+ let formattedValue;
794
+ if (Array.isArray(value)) {
795
+ formattedValue = JSON.stringify(value);
796
+ } else {
797
+ const stringValue = String(value);
798
+ formattedValue = /[\s"'#]/.test(stringValue) ? `"${stringValue.replace(/"/g, '"').replace(/\n/g, "\\n")}"` : stringValue;
799
+ }
800
+ let lineIndex = -1;
801
+ for (let i = 0; i < lines.length; i++) {
802
+ if (new RegExp(`^s*${key}s*=s*`).test(lines[i])) {
803
+ keyFound = true;
804
+ lineIndex = i;
805
+ break;
806
+ }
807
+ }
808
+ if (keyFound) {
809
+ newLines[lineIndex] = `${key}=${formattedValue}`;
810
+ if (description) {
811
+ const comment = `# ${description}`;
812
+ if (lineIndex === 0 || !newLines[lineIndex - 1].trim().startsWith(comment)) {
813
+ if (lineIndex > 0 && !newLines[lineIndex - 1].trim().startsWith("#")) {
814
+ newLines.splice(lineIndex, 0, comment);
815
+ }
816
+ }
817
+ }
818
+ } else {
819
+ if (newLines[newLines.length - 1] !== "") {
820
+ newLines.push("");
821
+ }
822
+ if (description) {
823
+ newLines.push(`# ${description}`);
824
+ }
825
+ newLines.push(`${key}=${formattedValue}`);
826
+ }
827
+ return newLines.join("\n");
828
+ }
829
+ function removeEnvKey(content, key) {
830
+ const lines = content.split(/\r?\n/);
831
+ const keyRegex = new RegExp(`^\\s*${key}\\s*=\\s*`);
832
+ const newLines = [];
833
+ for (const line of lines) {
834
+ if (keyRegex.test(line)) {
835
+ if (newLines.length > 0 && newLines[newLines.length - 1].trim().startsWith("#")) {
836
+ newLines.pop();
837
+ }
838
+ } else {
839
+ newLines.push(line);
840
+ }
841
+ }
842
+ return newLines.join("\n");
843
+ }
844
+
845
+ // src/drivers/env-driver.ts
846
+ function getFilePath(config) {
847
+ return path.resolve(process.cwd(), config.path || ".env");
848
+ }
849
+ function coerceType(value, schema) {
850
+ if (value === void 0) return void 0;
851
+ const type = schema.type;
852
+ if (type === "number") return Number(value);
853
+ if (type === "boolean") return String(value).toLowerCase() === "true";
854
+ if (type === "array" && typeof value === "string") {
855
+ const trimmedValue = value.trim();
856
+ if (trimmedValue.startsWith("[") && trimmedValue.endsWith("]")) {
857
+ try {
858
+ return JSON.parse(trimmedValue);
859
+ } catch {
860
+ }
861
+ }
862
+ }
863
+ return value;
864
+ }
865
+ function traverseSchema(schema, envValues, prefix = []) {
866
+ const builtConfig = {};
867
+ for (const key in schema) {
868
+ const currentPath = [...prefix, key];
869
+ const definition = schema[key];
870
+ const isTypeBoxSchema = (def) => !!def[Symbol.for("TypeBox.Kind")];
871
+ if (isTypeBoxSchema(definition)) {
872
+ const prop = definition.prop;
873
+ const envKey = prop || currentPath.join("_").toUpperCase();
874
+ let value = envValues[envKey];
875
+ if (value === void 0) {
876
+ value = definition.default;
877
+ }
878
+ if (value !== void 0) {
879
+ builtConfig[key] = coerceType(value, definition);
880
+ }
881
+ } else if (typeof definition === "object" && definition !== null) {
882
+ const nestedConfig = traverseSchema(
883
+ definition,
884
+ envValues,
885
+ currentPath
886
+ );
887
+ builtConfig[key] = nestedConfig;
888
+ }
889
+ }
890
+ return builtConfig;
891
+ }
892
+ var envDriver = new KfgDriver({
893
+ identify: "env-driver",
894
+ async: false,
895
+ config: { path: ".env" },
896
+ onLoad(schema, _opts) {
897
+ const filePath = getFilePath(this.config);
898
+ const fileContent = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8") : "";
899
+ const envFileValues = parse(fileContent);
900
+ const processEnv = Object.fromEntries(
901
+ Object.entries(process.env).filter(([, v]) => v !== void 0)
902
+ );
903
+ const allEnvValues = { ...processEnv, ...envFileValues };
904
+ const envData = traverseSchema(schema, allEnvValues);
905
+ const defaultData = this.buildDefaultObject(schema);
906
+ this.store = this.deepMerge(defaultData, envData);
907
+ return this.store;
908
+ },
909
+ onSet(key, value, options) {
910
+ const envKey = key.replace(/\./g, "_").toUpperCase();
911
+ const filePath = getFilePath(this.config);
912
+ const currentContent = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8") : "";
913
+ const newContent = updateEnvContent(
914
+ currentContent,
915
+ envKey,
916
+ value,
917
+ options?.description
918
+ );
919
+ fs2.writeFileSync(filePath, newContent);
920
+ },
921
+ onDel(key) {
922
+ const envKey = key.replace(/\./g, "_").toUpperCase();
923
+ const filePath = getFilePath(this.config);
924
+ if (!fs2.existsSync(filePath)) {
925
+ return;
926
+ }
927
+ const currentContent = fs2.readFileSync(filePath, "utf-8");
928
+ const newContent = removeEnvKey(currentContent, envKey);
929
+ fs2.writeFileSync(filePath, newContent);
930
+ }
931
+ });
932
+
933
+ // src/drivers/json-driver.ts
934
+ import * as fs3 from "node:fs";
935
+ import * as path2 from "node:path";
936
+ function stripComments(data) {
937
+ const comments = {};
938
+ function recurse(currentData, prefix = "") {
939
+ const keys = Object.keys(currentData);
940
+ for (const key of keys) {
941
+ if (key.endsWith(":comment")) {
942
+ const dataKey = key.replace(/:comment$/, "");
943
+ const commentPath = prefix ? `${prefix}.${dataKey}` : dataKey;
944
+ comments[commentPath] = currentData[key];
945
+ delete currentData[key];
946
+ }
947
+ }
948
+ for (const key of keys) {
949
+ if (typeof currentData[key] === "object" && currentData[key] !== null && !key.endsWith(":comment")) {
950
+ const nestedPrefix = prefix ? `${prefix}.${key}` : key;
951
+ recurse(currentData[key], nestedPrefix);
952
+ }
953
+ }
954
+ }
955
+ recurse(data);
956
+ return comments;
957
+ }
958
+ function getFilePath2(config) {
959
+ return path2.resolve(process.cwd(), config.path || "config.json");
960
+ }
961
+ var jsonDriver = new KfgDriver({
962
+ identify: "json-driver",
963
+ async: false,
964
+ config: { path: "config.json", keyroot: false },
965
+ onLoad(schema, _opts) {
966
+ this.comments = this.comments || {};
967
+ const defaultData = this.buildDefaultObject(schema);
968
+ const filePath = getFilePath2(this.config);
969
+ let loadedData = {};
970
+ if (fs3.existsSync(filePath)) {
971
+ try {
972
+ const fileContent = fs3.readFileSync(filePath, "utf-8");
973
+ if (fileContent) {
974
+ loadedData = JSON.parse(fileContent);
975
+ }
976
+ } catch (_e) {
977
+ }
978
+ }
979
+ if (this.config.keyroot) {
980
+ const flatData = loadedData;
981
+ const comments = {};
982
+ const data = {};
983
+ for (const key in flatData) {
984
+ if (key.endsWith(":comment")) {
985
+ comments[key.replace(/:comment$/, "")] = flatData[key];
986
+ } else {
987
+ data[key] = flatData[key];
988
+ }
989
+ }
990
+ this.comments = comments;
991
+ loadedData = unflattenObject(data);
992
+ } else {
993
+ this.comments = stripComments(loadedData);
994
+ }
995
+ this.store = this.deepMerge(defaultData, loadedData);
996
+ return this.store;
997
+ },
998
+ onSet(key, _value, options) {
999
+ if (key) {
1000
+ this.comments = this.comments || {};
1001
+ if (options?.description) {
1002
+ this.comments[key] = options.description;
1003
+ }
1004
+ }
1005
+ let dataToSave;
1006
+ if (this.config.keyroot) {
1007
+ dataToSave = flattenObject(this.data);
1008
+ for (const path3 in this.comments) {
1009
+ dataToSave[`${path3}:comment`] = this.comments[path3];
1010
+ }
1011
+ } else {
1012
+ const dataWithComments = JSON.parse(JSON.stringify(this.data));
1013
+ for (const path3 in this.comments) {
1014
+ const keys = path3.split(".");
1015
+ const propName = keys.pop();
1016
+ const parentPath = keys.join(".");
1017
+ const parentObject = parentPath ? getProperty(dataWithComments, parentPath) : dataWithComments;
1018
+ if (typeof parentObject === "object" && parentObject !== null) {
1019
+ parentObject[`${propName}:comment`] = this.comments[path3];
1020
+ }
1021
+ }
1022
+ dataToSave = dataWithComments;
1023
+ }
1024
+ const filePath = getFilePath2(this.config);
1025
+ fs3.writeFileSync(filePath, JSON.stringify(dataToSave, null, 2));
1026
+ },
1027
+ onDel(key) {
1028
+ if (this.comments?.[key]) {
1029
+ delete this.comments[key];
1030
+ }
1031
+ let dataToSave;
1032
+ if (this.config.keyroot) {
1033
+ dataToSave = flattenObject(this.data);
1034
+ for (const path3 in this.comments) {
1035
+ dataToSave[`${path3}:comment`] = this.comments[path3];
1036
+ }
1037
+ } else {
1038
+ const dataWithComments = JSON.parse(JSON.stringify(this.data));
1039
+ for (const path3 in this.comments) {
1040
+ const keys = path3.split(".");
1041
+ const propName = keys.pop();
1042
+ const parentPath = keys.join(".");
1043
+ const parentObject = parentPath ? getProperty(dataWithComments, parentPath) : dataWithComments;
1044
+ if (typeof parentObject === "object" && parentObject !== null) {
1045
+ parentObject[`${propName}:comment`] = this.comments[path3];
1046
+ }
1047
+ }
1048
+ dataToSave = dataWithComments;
1049
+ }
1050
+ const filePath = getFilePath2(this.config);
1051
+ fs3.writeFileSync(filePath, JSON.stringify(dataToSave, null, 2));
1052
+ }
1053
+ });
1054
+
1055
+ // src/factory.ts
1056
+ import {
1057
+ Type as Type3
1058
+ } from "@sinclair/typebox";
1059
+ function getEnumValues(values) {
1060
+ if (Array.isArray(values)) {
1061
+ return values;
1062
+ }
1063
+ const isNumericEnum = Object.values(values).some(
1064
+ (v) => typeof v === "number"
1065
+ );
1066
+ if (isNumericEnum) {
1067
+ return Object.values(values).filter(
1068
+ (v) => typeof v === "number"
1069
+ );
1070
+ }
1071
+ return Object.values(values).filter((v) => typeof v === "string");
1072
+ }
1073
+ var _c = {
1074
+ /** Creates a String schema. */
1075
+ String: (options) => Type3.String(options),
1076
+ /** Creates a Number schema. */
1077
+ Number: (options) => Type3.Number(options),
1078
+ /** Creates a Boolean schema. */
1079
+ Boolean: (options) => Type3.Boolean(options),
1080
+ /** Creates an Object schema. */
1081
+ Object: (properties, options) => Type3.Object(properties, options),
1082
+ /** Creates an Array schema. */
1083
+ Array: (items, options) => Type3.Array(items, options),
1084
+ /** Creates a Record schema. */
1085
+ Record: (key, value, options) => Type3.Record(key, value, options),
1086
+ /** Creates a Union of Literals from a string array, const array, or a TypeScript enum. */
1087
+ Enum: (values, options) => {
1088
+ const enumValues = getEnumValues(values);
1089
+ return Type3.Union(
1090
+ enumValues.map((v) => Type3.Literal(v)),
1091
+ options
1092
+ //@ts-expect-error ignore
1093
+ );
1094
+ },
1095
+ /** Creates a string schema with 'ipv4' format. */
1096
+ IP: (options) => Type3.String({ ...options, format: "ipv4" }),
1097
+ /** Creates a string schema with 'ipv6' format. */
1098
+ IPv6: (options) => Type3.String({ ...options, format: "ipv6" }),
1099
+ /** Creates a string schema with 'email' format. */
1100
+ Email: (options) => Type3.String({ ...options, format: "email" }),
1101
+ /** Creates a string schema with 'uri' format. */
1102
+ URL: (options) => Type3.String({ ...options, format: "uri" }),
1103
+ /** Creates an Any schema. */
1104
+ Any: () => Type3.Any(),
1105
+ /** Creates an Optional schema. */
1106
+ Optional: (schema) => Type3.Optional(schema),
1107
+ /** Creates a Number schema that defaults to a random value if not provided. */
1108
+ Random: (options) => {
1109
+ const { max = 100, ...rest } = options || {};
1110
+ return Type3.Number({
1111
+ ...rest,
1112
+ [Symbol.for("isRandom")]: true,
1113
+ max
1114
+ });
1115
+ }
1116
+ };
1117
+ var c = {
1118
+ ..._c,
1119
+ string: _c.String,
1120
+ number: _c.Number,
1121
+ boolean: _c.Boolean,
1122
+ object: _c.Object,
1123
+ array: _c.Array,
1124
+ record: _c.Record,
1125
+ enum: _c.Enum,
1126
+ ip: _c.IP,
1127
+ ipv6: _c.IPv6,
1128
+ email: _c.Email,
1129
+ url: _c.URL,
1130
+ any: _c.Any,
1131
+ optional: _c.Optional,
1132
+ random: _c.Random
1133
+ };
1134
+ var k = c;
1135
+ export {
1136
+ ConfigFS,
1137
+ ConfigJS,
1138
+ ConfigJSDriver,
1139
+ FileFSConfigJS,
1140
+ KFS_JOIN_SYMBOL,
1141
+ KFS_MANY_SYMBOL,
1142
+ Kfg,
1143
+ KfgDriver,
1144
+ KfgFS,
1145
+ KfgFileFS,
1146
+ c,
1147
+ cfs,
1148
+ envDriver,
1149
+ jsonDriver,
1150
+ k,
1151
+ kfs
1152
+ };