@theunwalked/cardigantime 0.0.6 → 0.0.8
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/cardigantime.cjs +170 -24
- package/dist/cardigantime.cjs.map +1 -1
- package/dist/cardigantime.d.ts +11 -0
- package/dist/cardigantime.js +11 -0
- package/dist/cardigantime.js.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/read.js +82 -12
- package/dist/read.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.js.map +1 -1
- package/dist/util/hierarchical.d.ts +7 -1
- package/dist/util/hierarchical.js +73 -10
- package/dist/util/hierarchical.js.map +1 -1
- package/dist/validate.js +2 -1
- package/dist/validate.js.map +1 -1
- package/package.json +1 -1
package/dist/cardigantime.cjs
CHANGED
|
@@ -187,7 +187,8 @@ class ArgumentError extends Error {
|
|
|
187
187
|
*/ const DEFAULT_OPTIONS = {
|
|
188
188
|
configFile: DEFAULT_CONFIG_FILE,
|
|
189
189
|
isRequired: false,
|
|
190
|
-
encoding: DEFAULT_ENCODING
|
|
190
|
+
encoding: DEFAULT_ENCODING,
|
|
191
|
+
pathResolution: undefined
|
|
191
192
|
};
|
|
192
193
|
/**
|
|
193
194
|
* Default features enabled when creating a Cardigantime instance.
|
|
@@ -415,6 +416,62 @@ const create$1 = (params)=>{
|
|
|
415
416
|
};
|
|
416
417
|
};
|
|
417
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Resolves relative paths in configuration values relative to the configuration file's directory.
|
|
421
|
+
*/ function resolveConfigPaths$1(config, configDir, pathFields = [], resolvePathArray = []) {
|
|
422
|
+
if (!config || typeof config !== 'object' || pathFields.length === 0) {
|
|
423
|
+
return config;
|
|
424
|
+
}
|
|
425
|
+
const resolvedConfig = {
|
|
426
|
+
...config
|
|
427
|
+
};
|
|
428
|
+
for (const fieldPath of pathFields){
|
|
429
|
+
const value = getNestedValue$1(resolvedConfig, fieldPath);
|
|
430
|
+
if (value !== undefined) {
|
|
431
|
+
const shouldResolveArrayElements = resolvePathArray.includes(fieldPath);
|
|
432
|
+
const resolvedValue = resolvePathValue$1(value, configDir, shouldResolveArrayElements);
|
|
433
|
+
setNestedValue$1(resolvedConfig, fieldPath, resolvedValue);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return resolvedConfig;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Gets a nested value from an object using dot notation.
|
|
440
|
+
*/ function getNestedValue$1(obj, path) {
|
|
441
|
+
return path.split('.').reduce((current, key)=>current === null || current === void 0 ? void 0 : current[key], obj);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Sets a nested value in an object using dot notation.
|
|
445
|
+
*/ function setNestedValue$1(obj, path, value) {
|
|
446
|
+
const keys = path.split('.');
|
|
447
|
+
const lastKey = keys.pop();
|
|
448
|
+
const target = keys.reduce((current, key)=>{
|
|
449
|
+
if (!(key in current)) {
|
|
450
|
+
current[key] = {};
|
|
451
|
+
}
|
|
452
|
+
return current[key];
|
|
453
|
+
}, obj);
|
|
454
|
+
target[lastKey] = value;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Resolves a path value (string or array of strings) relative to the config directory.
|
|
458
|
+
*/ function resolvePathValue$1(value, configDir, resolveArrayElements) {
|
|
459
|
+
if (typeof value === 'string') {
|
|
460
|
+
return resolveSinglePath$1(value, configDir);
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(value) && resolveArrayElements) {
|
|
463
|
+
return value.map((item)=>typeof item === 'string' ? resolveSinglePath$1(item, configDir) : item);
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Resolves a single path string relative to the config directory if it's a relative path.
|
|
469
|
+
*/ function resolveSinglePath$1(pathStr, configDir) {
|
|
470
|
+
if (!pathStr || path.isAbsolute(pathStr)) {
|
|
471
|
+
return pathStr;
|
|
472
|
+
}
|
|
473
|
+
return path.resolve(configDir, pathStr);
|
|
474
|
+
}
|
|
418
475
|
/**
|
|
419
476
|
* Discovers configuration directories by traversing up the directory tree.
|
|
420
477
|
*
|
|
@@ -483,7 +540,7 @@ const create$1 = (params)=>{
|
|
|
483
540
|
currentDir = parentDir;
|
|
484
541
|
level++;
|
|
485
542
|
}
|
|
486
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
543
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose(`Discovery complete. Found ${discoveredDirs.length} config directories`);
|
|
487
544
|
return discoveredDirs;
|
|
488
545
|
}
|
|
489
546
|
/**
|
|
@@ -493,14 +550,16 @@ const create$1 = (params)=>{
|
|
|
493
550
|
* @param configFileName Name of the configuration file
|
|
494
551
|
* @param encoding File encoding
|
|
495
552
|
* @param logger Optional logger
|
|
553
|
+
* @param pathFields Optional array of field names that contain paths to be resolved
|
|
554
|
+
* @param resolvePathArray Optional array of field names whose array elements should all be resolved as paths
|
|
496
555
|
* @returns Promise resolving to parsed configuration object or null if not found
|
|
497
|
-
*/ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger) {
|
|
556
|
+
*/ async function loadConfigFromDirectory(configDir, configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray) {
|
|
498
557
|
const storage = create$1({
|
|
499
558
|
log: (logger === null || logger === void 0 ? void 0 : logger.debug) || (()=>{})
|
|
500
559
|
});
|
|
501
560
|
const configFilePath = path.join(configDir, configFileName);
|
|
502
561
|
try {
|
|
503
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
562
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose(`Attempting to load config file: ${configFilePath}`);
|
|
504
563
|
const exists = await storage.exists(configFilePath);
|
|
505
564
|
if (!exists) {
|
|
506
565
|
logger === null || logger === void 0 ? void 0 : logger.debug(`Config file does not exist: ${configFilePath}`);
|
|
@@ -514,8 +573,13 @@ const create$1 = (params)=>{
|
|
|
514
573
|
const yamlContent = await storage.readFile(configFilePath, encoding);
|
|
515
574
|
const parsedYaml = yaml__namespace.load(yamlContent);
|
|
516
575
|
if (parsedYaml !== null && typeof parsedYaml === 'object') {
|
|
517
|
-
|
|
518
|
-
|
|
576
|
+
let config = parsedYaml;
|
|
577
|
+
// Apply path resolution if configured
|
|
578
|
+
if (pathFields && pathFields.length > 0) {
|
|
579
|
+
config = resolveConfigPaths$1(config, configDir, pathFields, resolvePathArray || []);
|
|
580
|
+
}
|
|
581
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose(`Successfully loaded config from: ${configFilePath}`);
|
|
582
|
+
return config;
|
|
519
583
|
} else {
|
|
520
584
|
logger === null || logger === void 0 ? void 0 : logger.debug(`Config file contains invalid format: ${configFilePath}`);
|
|
521
585
|
return null;
|
|
@@ -622,12 +686,12 @@ const create$1 = (params)=>{
|
|
|
622
686
|
* // result.errors contains any non-fatal errors
|
|
623
687
|
* ```
|
|
624
688
|
*/ async function loadHierarchicalConfig(options) {
|
|
625
|
-
const { configFileName, encoding = 'utf8', logger } = options;
|
|
626
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
689
|
+
const { configFileName, encoding = 'utf8', logger, pathFields, resolvePathArray } = options;
|
|
690
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose('Starting hierarchical configuration loading');
|
|
627
691
|
// Discover all configuration directories
|
|
628
692
|
const discoveredDirs = await discoverConfigDirectories(options);
|
|
629
693
|
if (discoveredDirs.length === 0) {
|
|
630
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
694
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose('No configuration directories found');
|
|
631
695
|
return {
|
|
632
696
|
config: {},
|
|
633
697
|
discoveredDirs: [],
|
|
@@ -643,7 +707,7 @@ const create$1 = (params)=>{
|
|
|
643
707
|
].sort((a, b)=>b.level - a.level);
|
|
644
708
|
for (const dir of sortedDirs){
|
|
645
709
|
try {
|
|
646
|
-
const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger);
|
|
710
|
+
const config = await loadConfigFromDirectory(dir.path, configFileName, encoding, logger, pathFields, resolvePathArray);
|
|
647
711
|
if (config !== null) {
|
|
648
712
|
configs.push(config);
|
|
649
713
|
logger === null || logger === void 0 ? void 0 : logger.debug(`Loaded config from level ${dir.level}: ${dir.path}`);
|
|
@@ -658,7 +722,7 @@ const create$1 = (params)=>{
|
|
|
658
722
|
}
|
|
659
723
|
// Merge all configurations with proper precedence
|
|
660
724
|
const mergedConfig = deepMergeConfigs(configs);
|
|
661
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
725
|
+
logger === null || logger === void 0 ? void 0 : logger.verbose(`Hierarchical loading complete. Merged ${configs.length} configurations`);
|
|
662
726
|
return {
|
|
663
727
|
config: mergedConfig,
|
|
664
728
|
discoveredDirs,
|
|
@@ -675,6 +739,68 @@ const create$1 = (params)=>{
|
|
|
675
739
|
*/ function clean(obj) {
|
|
676
740
|
return Object.fromEntries(Object.entries(obj).filter(([_, v])=>v !== undefined));
|
|
677
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* Resolves relative paths in configuration values relative to the configuration file's directory.
|
|
744
|
+
*
|
|
745
|
+
* @param config - The configuration object to process
|
|
746
|
+
* @param configDir - The directory containing the configuration file
|
|
747
|
+
* @param pathFields - Array of field names (using dot notation) that contain paths to be resolved
|
|
748
|
+
* @param resolvePathArray - Array of field names whose array elements should all be resolved as paths
|
|
749
|
+
* @returns The configuration object with resolved paths
|
|
750
|
+
*/ function resolveConfigPaths(config, configDir, pathFields = [], resolvePathArray = []) {
|
|
751
|
+
if (!config || typeof config !== 'object' || pathFields.length === 0) {
|
|
752
|
+
return config;
|
|
753
|
+
}
|
|
754
|
+
const resolvedConfig = {
|
|
755
|
+
...config
|
|
756
|
+
};
|
|
757
|
+
for (const fieldPath of pathFields){
|
|
758
|
+
const value = getNestedValue(resolvedConfig, fieldPath);
|
|
759
|
+
if (value !== undefined) {
|
|
760
|
+
const shouldResolveArrayElements = resolvePathArray.includes(fieldPath);
|
|
761
|
+
const resolvedValue = resolvePathValue(value, configDir, shouldResolveArrayElements);
|
|
762
|
+
setNestedValue(resolvedConfig, fieldPath, resolvedValue);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return resolvedConfig;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Gets a nested value from an object using dot notation.
|
|
769
|
+
*/ function getNestedValue(obj, path) {
|
|
770
|
+
return path.split('.').reduce((current, key)=>current === null || current === void 0 ? void 0 : current[key], obj);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Sets a nested value in an object using dot notation.
|
|
774
|
+
*/ function setNestedValue(obj, path, value) {
|
|
775
|
+
const keys = path.split('.');
|
|
776
|
+
const lastKey = keys.pop();
|
|
777
|
+
const target = keys.reduce((current, key)=>{
|
|
778
|
+
if (!(key in current)) {
|
|
779
|
+
current[key] = {};
|
|
780
|
+
}
|
|
781
|
+
return current[key];
|
|
782
|
+
}, obj);
|
|
783
|
+
target[lastKey] = value;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Resolves a path value (string or array of strings) relative to the config directory.
|
|
787
|
+
*/ function resolvePathValue(value, configDir, resolveArrayElements) {
|
|
788
|
+
if (typeof value === 'string') {
|
|
789
|
+
return resolveSinglePath(value, configDir);
|
|
790
|
+
}
|
|
791
|
+
if (Array.isArray(value) && resolveArrayElements) {
|
|
792
|
+
return value.map((item)=>typeof item === 'string' ? resolveSinglePath(item, configDir) : item);
|
|
793
|
+
}
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Resolves a single path string relative to the config directory if it's a relative path.
|
|
798
|
+
*/ function resolveSinglePath(pathStr, configDir) {
|
|
799
|
+
if (!pathStr || path__namespace.isAbsolute(pathStr)) {
|
|
800
|
+
return pathStr;
|
|
801
|
+
}
|
|
802
|
+
return path__namespace.resolve(configDir, pathStr);
|
|
803
|
+
}
|
|
678
804
|
/**
|
|
679
805
|
* Validates and secures a user-provided path to prevent path traversal attacks.
|
|
680
806
|
*
|
|
@@ -758,19 +884,20 @@ const create$1 = (params)=>{
|
|
|
758
884
|
* // config is fully typed based on your schema
|
|
759
885
|
* ```
|
|
760
886
|
*/ const read = async (args, options)=>{
|
|
761
|
-
var _options_defaults;
|
|
887
|
+
var _options_defaults, _options_defaults_pathResolution;
|
|
762
888
|
const logger = options.logger;
|
|
763
889
|
const rawConfigDir = args.configDirectory || ((_options_defaults = options.defaults) === null || _options_defaults === void 0 ? void 0 : _options_defaults.configDirectory);
|
|
764
890
|
if (!rawConfigDir) {
|
|
765
891
|
throw new Error('Configuration directory must be specified');
|
|
766
892
|
}
|
|
767
893
|
const resolvedConfigDir = validateConfigDirectory$1(rawConfigDir);
|
|
768
|
-
logger.
|
|
894
|
+
logger.verbose('Resolved config directory');
|
|
769
895
|
let rawFileConfig = {};
|
|
770
896
|
// Check if hierarchical configuration discovery is enabled
|
|
771
897
|
if (options.features.includes('hierarchical')) {
|
|
772
|
-
logger.
|
|
898
|
+
logger.verbose('Hierarchical configuration discovery enabled');
|
|
773
899
|
try {
|
|
900
|
+
var _options_defaults_pathResolution1, _options_defaults_pathResolution2;
|
|
774
901
|
// Extract the config directory name from the path for hierarchical discovery
|
|
775
902
|
const configDirName = path__namespace.basename(resolvedConfigDir);
|
|
776
903
|
const startingDir = path__namespace.dirname(resolvedConfigDir);
|
|
@@ -780,16 +907,18 @@ const create$1 = (params)=>{
|
|
|
780
907
|
configFileName: options.defaults.configFile,
|
|
781
908
|
startingDir,
|
|
782
909
|
encoding: options.defaults.encoding,
|
|
783
|
-
logger
|
|
910
|
+
logger,
|
|
911
|
+
pathFields: (_options_defaults_pathResolution1 = options.defaults.pathResolution) === null || _options_defaults_pathResolution1 === void 0 ? void 0 : _options_defaults_pathResolution1.pathFields,
|
|
912
|
+
resolvePathArray: (_options_defaults_pathResolution2 = options.defaults.pathResolution) === null || _options_defaults_pathResolution2 === void 0 ? void 0 : _options_defaults_pathResolution2.resolvePathArray
|
|
784
913
|
});
|
|
785
914
|
rawFileConfig = hierarchicalResult.config;
|
|
786
915
|
if (hierarchicalResult.discoveredDirs.length > 0) {
|
|
787
|
-
logger.
|
|
916
|
+
logger.verbose(`Hierarchical discovery found ${hierarchicalResult.discoveredDirs.length} configuration directories`);
|
|
788
917
|
hierarchicalResult.discoveredDirs.forEach((dir)=>{
|
|
789
918
|
logger.debug(` Level ${dir.level}: ${dir.path}`);
|
|
790
919
|
});
|
|
791
920
|
} else {
|
|
792
|
-
logger.
|
|
921
|
+
logger.verbose('No configuration directories found in hierarchy');
|
|
793
922
|
}
|
|
794
923
|
if (hierarchicalResult.errors.length > 0) {
|
|
795
924
|
hierarchicalResult.errors.forEach((error)=>logger.warn(`Hierarchical config warning: ${error}`));
|
|
@@ -797,16 +926,21 @@ const create$1 = (params)=>{
|
|
|
797
926
|
} catch (error) {
|
|
798
927
|
logger.error('Hierarchical configuration loading failed: ' + (error.message || 'Unknown error'));
|
|
799
928
|
// Fall back to single directory mode
|
|
800
|
-
logger.
|
|
929
|
+
logger.verbose('Falling back to single directory configuration loading');
|
|
801
930
|
rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
|
|
802
931
|
}
|
|
803
932
|
} else {
|
|
804
933
|
// Use traditional single directory configuration loading
|
|
805
|
-
logger.
|
|
934
|
+
logger.verbose('Using single directory configuration loading');
|
|
806
935
|
rawFileConfig = await loadSingleDirectoryConfig(resolvedConfigDir, options, logger);
|
|
807
936
|
}
|
|
937
|
+
// Apply path resolution if configured
|
|
938
|
+
let processedConfig = rawFileConfig;
|
|
939
|
+
if ((_options_defaults_pathResolution = options.defaults.pathResolution) === null || _options_defaults_pathResolution === void 0 ? void 0 : _options_defaults_pathResolution.pathFields) {
|
|
940
|
+
processedConfig = resolveConfigPaths(rawFileConfig, resolvedConfigDir, options.defaults.pathResolution.pathFields, options.defaults.pathResolution.resolvePathArray || []);
|
|
941
|
+
}
|
|
808
942
|
const config = clean({
|
|
809
|
-
...
|
|
943
|
+
...processedConfig,
|
|
810
944
|
...{
|
|
811
945
|
configDirectory: resolvedConfigDir
|
|
812
946
|
}
|
|
@@ -825,7 +959,7 @@ const create$1 = (params)=>{
|
|
|
825
959
|
log: logger.debug
|
|
826
960
|
});
|
|
827
961
|
const configFile = validatePath(options.defaults.configFile, resolvedConfigDir);
|
|
828
|
-
logger.
|
|
962
|
+
logger.verbose('Attempting to load config file for cardigantime');
|
|
829
963
|
let rawFileConfig = {};
|
|
830
964
|
try {
|
|
831
965
|
const yamlContent = await storage.readFile(configFile, options.defaults.encoding);
|
|
@@ -833,13 +967,13 @@ const create$1 = (params)=>{
|
|
|
833
967
|
const parsedYaml = yaml__namespace.load(yamlContent);
|
|
834
968
|
if (parsedYaml !== null && typeof parsedYaml === 'object') {
|
|
835
969
|
rawFileConfig = parsedYaml;
|
|
836
|
-
logger.
|
|
970
|
+
logger.verbose('Loaded configuration file successfully');
|
|
837
971
|
} else if (parsedYaml !== null) {
|
|
838
972
|
logger.warn('Ignoring invalid configuration format. Expected an object, got ' + typeof parsedYaml);
|
|
839
973
|
}
|
|
840
974
|
} catch (error) {
|
|
841
975
|
if (error.code === 'ENOENT' || /not found|no such file/i.test(error.message)) {
|
|
842
|
-
logger.
|
|
976
|
+
logger.verbose('Configuration file not found. Using empty configuration.');
|
|
843
977
|
} else {
|
|
844
978
|
// SECURITY FIX: Don't expose internal paths or detailed error information
|
|
845
979
|
logger.error('Failed to load or parse configuration file: ' + (error.message || 'Unknown error'));
|
|
@@ -1152,7 +1286,8 @@ class ConfigurationError extends Error {
|
|
|
1152
1286
|
checkForExtraKeys(config, fullSchema, logger);
|
|
1153
1287
|
if (!validationResult.success) {
|
|
1154
1288
|
const formattedError = JSON.stringify(validationResult.error.format(), null, 2);
|
|
1155
|
-
logger.error('Configuration validation failed
|
|
1289
|
+
logger.error('Configuration validation failed. Check logs for details.');
|
|
1290
|
+
logger.silly('Configuration validation failed: %s', formattedError);
|
|
1156
1291
|
throw ConfigurationError.validation('Configuration validation failed. Check logs for details.', validationResult.error);
|
|
1157
1292
|
}
|
|
1158
1293
|
return;
|
|
@@ -1174,6 +1309,7 @@ class ConfigurationError extends Error {
|
|
|
1174
1309
|
* @param pOptions.defaults.configFile - Name of the configuration file (optional, defaults to 'config.yaml')
|
|
1175
1310
|
* @param pOptions.defaults.isRequired - Whether the config directory must exist (optional, defaults to false)
|
|
1176
1311
|
* @param pOptions.defaults.encoding - File encoding for reading config files (optional, defaults to 'utf8')
|
|
1312
|
+
* @param pOptions.defaults.pathResolution - Configuration for resolving relative paths in config values relative to the config file's directory (optional)
|
|
1177
1313
|
* @param pOptions.features - Array of features to enable (optional, defaults to ['config'])
|
|
1178
1314
|
* @param pOptions.configShape - Zod schema shape defining your configuration structure (required)
|
|
1179
1315
|
* @param pOptions.logger - Custom logger implementation (optional, defaults to console logger)
|
|
@@ -1188,15 +1324,25 @@ class ConfigurationError extends Error {
|
|
|
1188
1324
|
* apiKey: z.string().min(1),
|
|
1189
1325
|
* timeout: z.number().default(5000),
|
|
1190
1326
|
* debug: z.boolean().default(false),
|
|
1327
|
+
* contextDirectories: z.array(z.string()).optional(),
|
|
1191
1328
|
* });
|
|
1192
1329
|
*
|
|
1193
1330
|
* const cardigantime = create({
|
|
1194
1331
|
* defaults: {
|
|
1195
1332
|
* configDirectory: './config',
|
|
1196
1333
|
* configFile: 'myapp.yaml',
|
|
1334
|
+
* // Resolve relative paths in contextDirectories relative to config file location
|
|
1335
|
+
* pathResolution: {
|
|
1336
|
+
* pathFields: ['contextDirectories'],
|
|
1337
|
+
* resolvePathArray: ['contextDirectories']
|
|
1338
|
+
* }
|
|
1197
1339
|
* },
|
|
1198
1340
|
* configShape: MyConfigSchema.shape,
|
|
1199
1341
|
* });
|
|
1342
|
+
*
|
|
1343
|
+
* // If config file is at ../config/myapp.yaml and contains:
|
|
1344
|
+
* // contextDirectories: ['./context', './data']
|
|
1345
|
+
* // These paths will be resolved relative to ../config/ directory
|
|
1200
1346
|
* ```
|
|
1201
1347
|
*/ const create = (pOptions)=>{
|
|
1202
1348
|
const defaults = {
|