@theunwalked/cardigantime 0.0.15 → 0.0.16

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.
@@ -1444,22 +1444,22 @@ class ConfigurationError extends Error {
1444
1444
 
1445
1445
  /**
1446
1446
  * Recursively extracts all keys from a Zod schema in dot notation.
1447
- *
1447
+ *
1448
1448
  * This function traverses a Zod schema structure and builds a flat list
1449
1449
  * of all possible keys, using dot notation for nested objects. It handles
1450
1450
  * optional/nullable types by unwrapping them and supports arrays by
1451
1451
  * introspecting their element type.
1452
- *
1452
+ *
1453
1453
  * Special handling for:
1454
1454
  * - ZodOptional/ZodNullable: Unwraps to get the underlying type
1455
1455
  * - ZodAny/ZodRecord: Accepts any keys, returns the prefix or empty array
1456
1456
  * - ZodArray: Introspects the element type
1457
1457
  * - ZodObject: Recursively processes all shape properties
1458
- *
1458
+ *
1459
1459
  * @param schema - The Zod schema to introspect
1460
1460
  * @param prefix - Internal parameter for building nested key paths
1461
1461
  * @returns Array of strings representing all possible keys in dot notation
1462
- *
1462
+ *
1463
1463
  * @example
1464
1464
  * ```typescript
1465
1465
  * const schema = z.object({
@@ -1469,32 +1469,26 @@ class ConfigurationError extends Error {
1469
1469
  * }),
1470
1470
  * debug: z.boolean()
1471
1471
  * });
1472
- *
1472
+ *
1473
1473
  * const keys = listZodKeys(schema);
1474
1474
  * // Returns: ['user.name', 'user.settings.theme', 'debug']
1475
1475
  * ```
1476
1476
  */ const listZodKeys = (schema, prefix = '')=>{
1477
- // Check if schema has unwrap method (which both ZodOptional and ZodNullable have)
1478
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1479
- // Use type assertion to handle the unwrap method
1480
- const unwrappable = schema;
1481
- return listZodKeys(unwrappable.unwrap(), prefix);
1477
+ // Handle ZodOptional and ZodNullable - unwrap to get the underlying type
1478
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1479
+ return listZodKeys(schema.unwrap(), prefix);
1482
1480
  }
1483
1481
  // Handle ZodAny and ZodRecord - these accept any keys, so don't introspect
1484
- if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
1482
+ if (schema instanceof zod.z.ZodAny || schema instanceof zod.z.ZodRecord) {
1485
1483
  return prefix ? [
1486
1484
  prefix
1487
1485
  ] : [];
1488
1486
  }
1489
- if (schema._def && schema._def.typeName === 'ZodArray') {
1490
- // Use type assertion to handle the element property
1491
- const arraySchema = schema;
1492
- return listZodKeys(arraySchema.element, prefix);
1487
+ if (schema instanceof zod.z.ZodArray) {
1488
+ return listZodKeys(schema.element, prefix);
1493
1489
  }
1494
- if (schema._def && schema._def.typeName === 'ZodObject') {
1495
- // Use type assertion to handle the shape property
1496
- const objectSchema = schema;
1497
- return Object.entries(objectSchema.shape).flatMap(([key, subschema])=>{
1490
+ if (schema instanceof zod.z.ZodObject) {
1491
+ return Object.entries(schema.shape).flatMap(([key, subschema])=>{
1498
1492
  const fullKey = prefix ? `${prefix}.${key}` : key;
1499
1493
  const nested = listZodKeys(subschema, fullKey);
1500
1494
  return nested.length ? nested : fullKey;
@@ -1504,7 +1498,7 @@ class ConfigurationError extends Error {
1504
1498
  };
1505
1499
  /**
1506
1500
  * Type guard to check if a value is a plain object (not array, null, or other types).
1507
- *
1501
+ *
1508
1502
  * @param value - The value to check
1509
1503
  * @returns True if the value is a plain object
1510
1504
  */ const isPlainObject = (value)=>{
@@ -1552,26 +1546,26 @@ class ConfigurationError extends Error {
1552
1546
  };
1553
1547
  /**
1554
1548
  * Validates that the configuration object contains only keys allowed by the schema.
1555
- *
1549
+ *
1556
1550
  * This function prevents configuration errors by detecting typos or extra keys
1557
1551
  * that aren't defined in the Zod schema. It intelligently handles:
1558
1552
  * - ZodRecord types that accept arbitrary keys
1559
1553
  * - Nested objects and their key structures
1560
1554
  * - Arrays and their element key structures
1561
- *
1555
+ *
1562
1556
  * The function throws a ConfigurationError if extra keys are found, providing
1563
1557
  * helpful information about what keys are allowed vs. what was found.
1564
- *
1558
+ *
1565
1559
  * @param mergedSources - The merged configuration object to validate
1566
1560
  * @param fullSchema - The complete Zod schema including base and user schemas
1567
1561
  * @param logger - Logger for error reporting
1568
1562
  * @throws {ConfigurationError} When extra keys are found that aren't in the schema
1569
- *
1563
+ *
1570
1564
  * @example
1571
1565
  * ```typescript
1572
1566
  * const schema = z.object({ name: z.string(), age: z.number() });
1573
1567
  * const config = { name: 'John', age: 30, typo: 'invalid' };
1574
- *
1568
+ *
1575
1569
  * checkForExtraKeys(config, schema, console);
1576
1570
  * // Throws: ConfigurationError with details about 'typo' being an extra key
1577
1571
  * ```
@@ -1582,18 +1576,16 @@ class ConfigurationError extends Error {
1582
1576
  const recordPrefixes = new Set();
1583
1577
  // Find all prefixes that are ZodRecord types
1584
1578
  const findRecordPrefixes = (schema, prefix = '')=>{
1585
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1586
- const unwrappable = schema;
1587
- findRecordPrefixes(unwrappable.unwrap(), prefix);
1579
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1580
+ findRecordPrefixes(schema.unwrap(), prefix);
1588
1581
  return;
1589
1582
  }
1590
- if (schema._def && (schema._def.typeName === 'ZodAny' || schema._def.typeName === 'ZodRecord')) {
1583
+ if (schema instanceof zod.z.ZodAny || schema instanceof zod.z.ZodRecord) {
1591
1584
  if (prefix) recordPrefixes.add(prefix);
1592
1585
  return;
1593
1586
  }
1594
- if (schema._def && schema._def.typeName === 'ZodObject') {
1595
- const objectSchema = schema;
1596
- Object.entries(objectSchema.shape).forEach(([key, subschema])=>{
1587
+ if (schema instanceof zod.z.ZodObject) {
1588
+ Object.entries(schema.shape).forEach(([key, subschema])=>{
1597
1589
  const fullKey = prefix ? `${prefix}.${key}` : key;
1598
1590
  findRecordPrefixes(subschema, fullKey);
1599
1591
  });
@@ -1620,11 +1612,11 @@ class ConfigurationError extends Error {
1620
1612
  };
1621
1613
  /**
1622
1614
  * Validates that a configuration directory exists and is accessible.
1623
- *
1615
+ *
1624
1616
  * This function performs file system checks to ensure the configuration
1625
1617
  * directory can be used. It handles the isRequired flag to determine
1626
1618
  * whether a missing directory should cause an error or be silently ignored.
1627
- *
1619
+ *
1628
1620
  * @param configDirectory - Path to the configuration directory
1629
1621
  * @param isRequired - Whether the directory must exist
1630
1622
  * @param logger - Optional logger for debug information
@@ -1647,30 +1639,30 @@ class ConfigurationError extends Error {
1647
1639
  };
1648
1640
  /**
1649
1641
  * Validates a configuration object against the combined Zod schema.
1650
- *
1642
+ *
1651
1643
  * This is the main validation function that:
1652
1644
  * 1. Validates the configuration directory (if config feature enabled)
1653
1645
  * 2. Combines the base ConfigSchema with user-provided schema shape
1654
1646
  * 3. Checks for extra keys not defined in the schema
1655
1647
  * 4. Validates all values against their schema definitions
1656
1648
  * 5. Provides detailed error reporting for validation failures
1657
- *
1649
+ *
1658
1650
  * The validation is comprehensive and catches common configuration errors
1659
1651
  * including typos, missing required fields, wrong types, and invalid values.
1660
- *
1652
+ *
1661
1653
  * @template T - The Zod schema shape type for configuration validation
1662
1654
  * @param config - The merged configuration object to validate
1663
1655
  * @param options - Cardigantime options containing schema, defaults, and logger
1664
1656
  * @throws {ConfigurationError} When configuration validation fails
1665
1657
  * @throws {FileSystemError} When configuration directory validation fails
1666
- *
1658
+ *
1667
1659
  * @example
1668
1660
  * ```typescript
1669
1661
  * const schema = z.object({
1670
1662
  * apiKey: z.string().min(1),
1671
1663
  * timeout: z.number().positive(),
1672
1664
  * });
1673
- *
1665
+ *
1674
1666
  * await validate(config, {
1675
1667
  * configShape: schema.shape,
1676
1668
  * defaults: { configDirectory: './config', isRequired: true },
@@ -1703,18 +1695,17 @@ class ConfigurationError extends Error {
1703
1695
  };
1704
1696
 
1705
1697
  /**
1706
- * Extracts default values from a Zod schema recursively.
1707
- *
1708
- * This function traverses a Zod schema and builds an object containing
1709
- * all the default values defined in the schema. It handles:
1710
- * - ZodDefault types with explicit default values
1711
- * - ZodOptional/ZodNullable types by unwrapping them
1712
- * - ZodObject types by recursively processing their shape
1713
- * - ZodArray types by providing an empty array as default
1714
- *
1698
+ * Extracts default values from a Zod schema recursively using Zod v4's parsing mechanisms.
1699
+ *
1700
+ * This function leverages Zod's own parsing behavior to extract defaults rather than
1701
+ * accessing internal properties. It works by:
1702
+ * 1. For ZodDefault types: parsing undefined to trigger the default
1703
+ * 2. For ZodObject types: creating a minimal object and parsing to get all defaults
1704
+ * 3. For wrapped types: unwrapping and recursing
1705
+ *
1715
1706
  * @param schema - The Zod schema to extract defaults from
1716
1707
  * @returns An object containing all default values from the schema
1717
- *
1708
+ *
1718
1709
  * @example
1719
1710
  * ```typescript
1720
1711
  * const schema = z.object({
@@ -1726,68 +1717,72 @@ class ConfigurationError extends Error {
1726
1717
  * port: z.number().default(5432)
1727
1718
  * })
1728
1719
  * });
1729
- *
1720
+ *
1730
1721
  * const defaults = extractSchemaDefaults(schema);
1731
1722
  * // Returns: { name: 'app', port: 3000, debug: false, database: { host: 'localhost', port: 5432 } }
1732
1723
  * ```
1733
1724
  */ const extractSchemaDefaults = (schema)=>{
1734
- // Handle ZodDefault - extract the default value
1735
- if (schema._def && schema._def.typeName === 'ZodDefault') {
1736
- const defaultSchema = schema;
1737
- return defaultSchema._def.defaultValue();
1738
- }
1739
- // Handle ZodOptional and ZodNullable - only recurse if there's an explicit default
1740
- if (schema._def && (schema._def.typeName === 'ZodOptional' || schema._def.typeName === 'ZodNullable')) {
1741
- const unwrappable = schema;
1742
- const unwrapped = unwrappable.unwrap();
1743
- // Only provide defaults if the unwrapped schema has explicit defaults
1744
- // This prevents optional arrays/objects from automatically getting [] or {} defaults
1745
- if (unwrapped._def && unwrapped._def.typeName === 'ZodDefault') {
1746
- return extractSchemaDefaults(unwrapped);
1725
+ // Handle ZodDefault - parse undefined to get the default value
1726
+ if (schema instanceof zod.z.ZodDefault) {
1727
+ try {
1728
+ return schema.parse(undefined);
1729
+ } catch {
1730
+ // If parsing undefined fails, return undefined
1731
+ return undefined;
1747
1732
  }
1748
- // For optional fields without explicit defaults, return undefined
1749
- return undefined;
1750
- }
1751
- // Handle ZodObject - recursively process shape
1752
- if (schema._def && schema._def.typeName === 'ZodObject') {
1753
- const objectSchema = schema;
1754
- const result = {};
1755
- for (const [key, subschema] of Object.entries(objectSchema.shape)){
1733
+ }
1734
+ // Handle ZodOptional and ZodNullable by unwrapping
1735
+ if (schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodNullable) {
1736
+ return extractSchemaDefaults(schema.unwrap());
1737
+ }
1738
+ // Handle ZodObject - create an object with defaults by parsing an empty object
1739
+ if (schema instanceof zod.z.ZodObject) {
1740
+ const defaults = {};
1741
+ const shape = schema.shape;
1742
+ // First, try to extract defaults from individual fields
1743
+ for (const [key, subschema] of Object.entries(shape)){
1756
1744
  const defaultValue = extractSchemaDefaults(subschema);
1757
1745
  if (defaultValue !== undefined) {
1758
- result[key] = defaultValue;
1746
+ defaults[key] = defaultValue;
1759
1747
  }
1760
1748
  }
1761
- return Object.keys(result).length > 0 ? result : undefined;
1749
+ // Then parse an empty object to trigger any schema-level defaults
1750
+ const result = schema.safeParse({});
1751
+ if (result.success) {
1752
+ // Merge the parsed result with our extracted defaults
1753
+ return {
1754
+ ...defaults,
1755
+ ...result.data
1756
+ };
1757
+ }
1758
+ return Object.keys(defaults).length > 0 ? defaults : undefined;
1762
1759
  }
1763
- // Handle ZodArray - provide empty array as default
1764
- if (schema._def && schema._def.typeName === 'ZodArray') {
1765
- const arraySchema = schema;
1766
- const elementDefaults = extractSchemaDefaults(arraySchema.element);
1767
- // Return an empty array, or an array with one example element if it has defaults
1760
+ // Handle ZodArray - return empty array as a reasonable default
1761
+ if (schema instanceof zod.z.ZodArray) {
1762
+ const elementDefaults = extractSchemaDefaults(schema.element);
1768
1763
  return elementDefaults !== undefined ? [
1769
1764
  elementDefaults
1770
1765
  ] : [];
1771
1766
  }
1772
- // Handle ZodRecord - provide empty object as default
1773
- if (schema._def && schema._def.typeName === 'ZodRecord') {
1767
+ // Handle ZodRecord - return empty object as default
1768
+ if (schema instanceof zod.z.ZodRecord) {
1774
1769
  return {};
1775
1770
  }
1776
- // For other types, return undefined (no default available)
1771
+ // No default available for other schema types
1777
1772
  return undefined;
1778
1773
  };
1779
1774
  /**
1780
1775
  * Generates a complete configuration object with all default values populated.
1781
- *
1776
+ *
1782
1777
  * This function combines the base ConfigSchema with a user-provided schema shape
1783
1778
  * and extracts all available default values to create a complete configuration
1784
1779
  * example that can be serialized to YAML.
1785
- *
1780
+ *
1786
1781
  * @template T - The Zod schema shape type
1787
1782
  * @param configShape - The user's configuration schema shape
1788
1783
  * @param configDirectory - The configuration directory to include in the defaults
1789
1784
  * @returns An object containing all default values suitable for YAML serialization
1790
- *
1785
+ *
1791
1786
  * @example
1792
1787
  * ```typescript
1793
1788
  * const shape = z.object({
@@ -1795,7 +1790,7 @@ class ConfigurationError extends Error {
1795
1790
  * timeout: z.number().default(5000).describe('Request timeout in milliseconds'),
1796
1791
  * features: z.array(z.string()).default(['auth', 'logging'])
1797
1792
  * }).shape;
1798
- *
1793
+ *
1799
1794
  * const config = generateDefaultConfig(shape, './config');
1800
1795
  * // Returns: { timeout: 5000, features: ['auth', 'logging'] }
1801
1796
  * // Note: apiKey is not included since it has no default