{"id":29101697,"url":"https://github.com/edjcase/motoko_url_kit","last_synced_at":"2026-02-04T04:04:23.736Z","repository":{"id":300847250,"uuid":"1007219393","full_name":"edjCase/motoko_url_kit","owner":"edjCase","description":"A library with Url helper utilities for parsing and manipulation","archived":false,"fork":false,"pushed_at":"2025-06-23T22:26:39.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-23T23:24:01.059Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Motoko","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edjCase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-23T16:38:28.000Z","updated_at":"2025-06-23T22:26:42.000Z","dependencies_parsed_at":"2025-06-23T23:24:04.314Z","dependency_job_id":"716280db-65e2-4bab-a8fa-bc6d388016f5","html_url":"https://github.com/edjCase/motoko_url_kit","commit_stats":null,"previous_names":["edjcase/motoko_url_kit"],"tags_count":0,"template":false,"template_full_name":"edjCase/motoko-library-template","purl":"pkg:github/edjCase/motoko_url_kit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_url_kit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_url_kit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_url_kit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_url_kit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edjCase","download_url":"https://codeload.github.com/edjCase/motoko_url_kit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edjCase%2Fmotoko_url_kit/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262497268,"owners_count":23320298,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-28T21:07:59.241Z","updated_at":"2026-02-04T04:04:23.729Z","avatar_url":"https://github.com/edjCase.png","language":"Motoko","funding_links":[],"categories":[],"sub_categories":[],"readme":"# URL Kit\n\n[![MOPS](https://img.shields.io/badge/MOPS-url--kit-blue)](https://mops.one/url-kit)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/edjcase/motoko_url_kit/blob/main/LICENSE)\n\nA comprehensive URL parsing and manipulation library for Motoko on the Internet Computer.\n\n## Overview\n\nURL Kit is a robust library designed to handle all aspects of URL processing in Motoko applications. It provides complete RFC-compliant URL parsing, validation, manipulation, and encoding/decoding capabilities with support for various host types, optional domain parsing, and comprehensive error handling.\n\nKey features:\n\n-   🔍 **Complete URL Parsing**: Parse any URL into structured components (scheme, authority, path, query, fragment)\n-   🌐 **Multi-Host Support**: Handle host names, IPv4, and IPv6 addresses with proper validation\n-   🏷️ **Flexible Domain Parsing**: Optional domain parsing with comprehensive or custom suffix lists\n-   🔗 **URL Manipulation**: Add, remove, and modify query parameters with ease\n-   🔄 **Encoding/Decoding**: Proper URL encoding and decoding with UTF-8 support\n-   ⚖️ **Normalization**: Normalize URLs for accurate comparison and deduplication\n-   🛡️ **Validation**: Comprehensive validation with detailed error messages\n-   🎯 **Type Safety**: Strongly typed URL components for compile-time safety\n-   📊 **IPv6 Support**: Full IPv6 address parsing with compression and various formats\n-   🔧 **Path Handling**: Flexible path parsing with custom separators and normalization\n\n## Package\n\n### MOPS\n\n```bash\nmops add url-kit\n```\n\nTo setup MOPS package manager, follow the instructions from the [MOPS Site](https://mops.one)\n\n## Quick Start\n\nHere's a simple example to get started with URL parsing:\n\n```motoko\nimport UrlKit \"mo:url-kit\";\n\n// Parse a URL - no domain parser needed for basic parsing\nlet urlResult = UrlKit.fromText(\"https://api.example.com:8080/users?page=1\u0026limit=10#results\");\n\nlet url = switch (urlResult) {\n    case (#ok(url)) url;\n    case (#err(errorMsg)) {\n        // Handle parsing error\n    };\n};\n\n// Access URL components\n// url = {\n     scheme = ?\"https\";\n     authority = ?{\n        user = null;\n        host = #name(\"api.example.com\");  // Host names are parsed as simple strings\n        port = ?8080;\n     };\n     path = { segments = [\"users\"]; trailingSlash = false };\n     queryParams = [(\"page\", \"1\"), (\"limit\", \"10\")];\n     fragment = ?\"results\"\n// }\n\n// Get specific query parameter\nlet page = UrlKit.getQueryParam(url, \"page\"); // ?\"1\"\n\n// Add query parameters\nlet urlWithAuth = UrlKit.addQueryParam(url, (\"token\", \"abc123\"));\n\n// Convert back to text\nlet urlText = UrlKit.toText(urlWithAuth);\n```\n\n## Comprehensive Example\n\nHere's a more detailed example showing various URL manipulation capabilities:\n\n```motoko\nimport UrlKit \"mo:url-kit\";\nimport Host \"mo:url-kit/Host\";\n\n// Parse different types of URLs\nlet examples = [\n    \"https://user:pass@sub.example.com:8080/api/v1/users?page=1\u0026sort=name#section1\",\n    \"http://192.168.1.1:3000/dashboard\",\n    \"https://[2001:db8::1]:8443/secure\",\n    \"file:///path/to/file.txt\",\n    \"//cdn.example.com/assets/style.css\"\n];\n\nfor (urlText in examples.vals()) {\n    switch (UrlKit.fromText(urlText)) {\n        case (#ok(url)) {\n            // Analyze the URL structure\n            switch (url.authority) {\n                case (?authority) {\n                    // Check host type\n                    switch (authority.host) {\n                        case (#name(hostName)) {\n                            // Host name (domain name, hostname, etc.)\n                            // For domain parsing, use the separate domain parsers\n                        };\n                        case (#ipV4(ip)) {\n                            // IPv4 address: (192, 168, 1, 1)\n                            let hostText = Host.toText(authority.host, authority.port);\n                        };\n                        case (#ipV6(ip)) {\n                            // IPv6 address with proper formatting\n                            let hostText = Host.toText(authority.host, authority.port);\n                        };\n                    };\n\n                    // Check for user authentication\n                    switch (authority.user) {\n                        case (?userInfo) {\n                            let username = userInfo.username;\n                            let password = userInfo.password;\n                        };\n                        case (null) {};\n                    };\n                };\n                case (null) {\n                    // No authority (e.g., mailto:, file: schemes)\n                };\n            };\n\n            // Manipulate query parameters\n            let withParams = url\n                |\u003e UrlKit.addQueryParam(_, (\"timestamp\", \"123456789\"))\n                |\u003e UrlKit.addQueryParamMulti(_, [(\"version\", \"v2\"), (\"format\", \"json\")])\n                |\u003e UrlKit.removeQueryParam(_, \"page\");\n\n            // Normalize for comparison\n            let normalizeOpts = { usernameIsCaseSensitive = false; pathIsCaseSensitive = false; queryKeysAreCaseSensitive = false; removeEmptyPathSegments = true; resolvePathDotSegments = true; preserveTrailingSlash = false };\n            let normalized = UrlKit.normalize(withParams, normalizeOpts);\n\n            // Convert back to string\n            let finalUrl = UrlKit.toText(normalized);\n        };\n        case (#err(error)) {\n            // Handle parsing errors with detailed messages\n        };\n    };\n};\n```\n\n## Core API\n\n### URL Type\n\nThe core `Url` type represents a parsed URL with all its components:\n\n```motoko\npublic type Url = {\n    scheme : ?Text;           // \"https\", \"http\", \"mailto\", etc.\n    authority : ?Authority;   // Host, port, and user info\n    path : Path.Path;        // Path segments with trailing slash flag\n    queryParams : [(Text, Text)]; // Query parameters as key-value pairs\n    fragment : ?Text;        // Fragment identifier\n};\n\npublic type Authority = {\n    user : ?UserInfo;        // Username and password\n    host : Host.Host;        // Host name or IP address\n    port : ?Nat16;          // Port number\n};\n```\n\n### Parsing and Conversion\n\n```motoko\n// Parse URL from text\nUrlKit.fromText(url : Text) : Result.Result\u003cUrl, Text\u003e\n\n// Convert URL back to text\nUrlKit.toText(url : Url) : Text\n\n// Normalize URL for comparison\nUrlKit.normalize(url : Url, options : NormalizationOptions) : Url\n\n// Compare URLs for equality\nUrlKit.equal(url1 : Url, url2 : Url, options : NormalizationOptions) : Bool\n```\n\n### Query Parameter Manipulation\n\n```motoko\n// Get query parameter value\nUrlKit.getQueryParam(url : Url, key : Text) : ?Text\n\n// Add single query parameter\nUrlKit.addQueryParam(url : Url, param : (Text, Text)) : Url\n\n// Add multiple query parameters\nUrlKit.addQueryParamMulti(url : Url, params : [(Text, Text)]) : Url\n\n// Remove query parameter by key\nUrlKit.removeQueryParam(url : Url, key : Text) : Url\n\n// Remove multiple query parameters\nUrlKit.removeQueryParamMulti(url : Url, keys : [Text]) : Url\n```\n\n### Encoding and Decoding\n\n```motoko\n// URL encode text (percent encoding)\nUrlKit.encodeText(value : Text, hexIsUpperCase : Bool) : Text\n\n// URL decode text\nUrlKit.decodeText(value : Text) : Result.Result\u003cText, Text\u003e\n```\n\n## Host Types\n\nURL Kit supports various host types with proper validation:\n\n### Host Names\n\n```motoko\nimport Host \"mo:url-kit/Host\";\n\n// Parse host with optional port\nlet hostResult = Host.fromText(\"example.com:8080\");\n// Result: (#name(\"example.com\"), ?8080)\n\n// Convert host to text\nlet hostText = Host.toText(host);\n\n// Normalize host (lowercase)\nlet normalized = Host.normalize(host);\n```\n\n### IPv4 Addresses\n\n```motoko\nimport IpV4 \"mo:url-kit/IpV4\";\n\n// Parse IPv4 address\nlet ipResult = IpV4.fromText(\"192.168.1.1\");\n// Result: (192, 168, 1, 1)\n\n// Convert back to text\nlet ipText = IpV4.toText(ip); // \"192.168.1.1\"\n```\n\n### IPv6 Addresses\n\n```motoko\nimport IpV6 \"mo:url-kit/IpV6\";\n\n// Parse IPv6 address (supports compression and various formats)\nlet ipResult = IpV6.fromText(\"2001:db8::1\");\n\n// Convert to text with default options (compressed format, lowercase)\nlet ipText = IpV6.toText(ip); // \"2001:db8::1\"\n\n// Convert to text with custom formatting options\nlet full = IpV6.toTextAdvanced(ip, { format = #full; isUpperCase = false });        // \"2001:0db8:0000:0000:0000:0000:0000:0001\"\nlet standard = IpV6.toTextAdvanced(ip, { format = #standard; isUpperCase = false }); // \"2001:db8:0:0:0:0:0:1\"\nlet compressed = IpV6.toTextAdvanced(ip, { format = #compressed; isUpperCase = true }); // \"2001:DB8::1\"\n```\n\n### Host Parsing and Formatting\n\n```motoko\nimport Host \"mo:url-kit/Host\";\n\n// Parse host with port\nlet hostResult = Host.fromText(\"example.com:8080\");\n// Result: (#name(\"example.com\"), ?8080)\n\n// Convert host to text (with optional port)\nlet hostText = Host.toText(host, port);\n\n// Normalize host (lowercase)\nlet normalized = Host.normalize(host);\n```\n\n## Path Handling\n\n## Path Handling\n\n```motoko\nimport Path \"mo:url-kit/Path\";\n\n// Parse path from text\nlet path = Path.fromText(\"/api/v1/users\");\n// Result: { segments = [\"api\", \"v1\", \"users\"]; trailingSlash = false }\n\n// Convert path back to text\nlet pathText = Path.toText(path); // \"/api/v1/users\"\n\n// Join path segments\nlet newPath = Path.join(path, \"123\");\nlet newNewPath = Path.joinMulti(newPath, [\"456\", \"profile\"]);\n// Result: { segments = [\"api\", \"v1\", \"users\", \"123\", \"456\", \"profile\"]; trailingSlash = false }\n\n// Normalize path with options\nlet options = { isCaseSensitive = false; removeEmptySegments = true; resolveDotSegments = true; preserveTrailingSlash = false };\nlet normalized = Path.normalize(path, options);\n```\n\n## URL Examples\n\n### Basic HTTP/HTTPS URLs\n\n```motoko\n// Simple HTTPS URL\n\"https://example.com\"\n\n// URL with port and path\n\"https://api.example.com:8080/v1/users\"\n\n// URL with query parameters\n\"https://example.com/search?q=motoko\u0026type=repo\"\n\n// URL with fragment\n\"https://docs.example.com/guide#installation\"\n\n// Complete URL with all components\n\"https://user:pass@api.example.com:8080/v1/users?page=1\u0026limit=10#results\"\n```\n\n### IP Address URLs\n\n```motoko\n// IPv4 address\n\"http://192.168.1.1:3000/dashboard\"\n\n// IPv6 address (note the brackets)\n\"https://[2001:db8::1]:8443/api\"\n\n// IPv6 with embedded IPv4\n\"http://[::ffff:192.168.1.1]/mixed\"\n```\n\n### Special Schemes\n\n```motoko\n\n// File URLs\n\"file:///path/to/file.txt\"\n\"file://server/share/document.pdf\"\n\n// Custom schemes\n\"custom-protocol://data.example.com/resource\"\n```\n\n### Relative URLs\n\n```motoko\n// Protocol-relative URL\n\"//cdn.example.com/assets/style.css\"\n\n// Absolute path\n\"/api/users/123\"\n\n// Query only\n\"?search=term\"\n\n// Fragment only\n\"#section1\"\n```\n\n## Domain Parsing\n\nAs of v3.0, domain parsing has been separated from basic host parsing. The Host type now simply stores names as strings (`#name`), and domain parsing is handled by dedicated domain parsers when needed.\n\n### Domain Parsers\n\nURL Kit provides two types of domain parsers:\n\n#### Comprehensive Domain Parser (Recommended)\n\nUses the complete Public Suffix List for accurate domain parsing:\n\n```motoko\nimport ComprehensiveDomainParser \"mo:url-kit/ComprehensiveDomainParser\";\nimport Domain \"mo:url-kit/Domain\";\n\n// Create a comprehensive domain parser\nlet domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n\n// Parse a domain name\nlet domainResult = domainParser.parse(\"blog.github.io\");\n// Result: #ok({ name = \"github\"; suffix = \"io\"; subdomains = [\"blog\"] })\n\nswitch (domainResult) {\n    case (#ok(domain)) {\n        let name = domain.name; // \"github\"\n        let suffix = domain.suffix; // \"io\"\n        let subdomains = domain.subdomains; // [\"blog\"]\n    };\n    case (#err(msg)) {\n        // Handle parsing error\n    };\n};\n```\n\n#### Custom Domain Parsing\n\nFor custom domain suffix lists, use the Domain module directly:\n\n```motoko\nimport Domain \"mo:url-kit/Domain\";\n\n// Parse with custom suffixes\nlet customSuffixes = [\"com\", \"org\", \"test\", \"local\"];\nlet domainResult = Domain.fromText(\"api.example.com\", customSuffixes);\n// Result: #ok({ name = \"example\"; suffix = \"com\"; subdomains = [\"api\"] })\n```\n\n#### Custom Domain Parsing\n\nFor custom domain suffix lists, use the Domain module directly:\n\n```motoko\nimport Domain \"mo:url-kit/Domain\";\n\n// Parse with custom suffixes\nlet customSuffixes = [\"com\", \"org\", \"test\", \"local\"];\nlet domainResult = Domain.fromText(\"api.example.com\", customSuffixes);\n// Result: #ok({ name = \"example\"; suffix = \"com\"; subdomains = [\"api\"] })\n```\n\n### Domain Operations\n\n```motoko\nimport Domain \"mo:url-kit/Domain\";\n\n// Parse domain with specified suffixes\nlet domainResult = Domain.fromText(\"blog.github.io\", [\"github.io\", \"io\"]);\n\n// Validate domain structure\nlet validation = Domain.validate(domain);\n\n// Convert domain to text\nlet domainText = Domain.toText(domain);\n\n// Normalize domain (lowercase)\nlet normalized = Domain.normalize(domain);\n```\n\n### Integration with URLs\n\nWhen you need domain parsing for URLs, extract the host name and parse it separately:\n\n```motoko\nimport UrlKit \"mo:url-kit\";\nimport ComprehensiveDomainParser \"mo:url-kit/ComprehensiveDomainParser\";\n\nlet url = switch (UrlKit.fromText(\"https://blog.example.com/path\")) {\n    case (#ok(u)) u;\n    case (#err(_)) return; // Handle error\n};\n\n// Extract host name for domain parsing\nswitch (url.authority) {\n    case (?authority) {\n        switch (authority.host) {\n            case (#name(hostName)) {\n                // Parse the host name as a domain\n                let domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n                let domainResult = domainParser.parse(hostName);\n\n                switch (domainResult) {\n                    case (#ok(domain)) {\n                        // Work with parsed domain components\n                        let rootDomain = domain.name # \".\" # domain.suffix; // \"example.com\"\n                    };\n                    case (#err(_)) {\n                        // Host name is not a valid domain (e.g., IP address, localhost)\n                    };\n                };\n            };\n            case (#ipV4(_) or #ipV6(_)) {\n                // IP addresses don't need domain parsing\n            };\n        };\n    };\n    case (null) {};\n};\n```\n\n## Domain Suffix List\n\nURL Kit includes an automatically generated domain suffix list based on the [Public Suffix List](https://publicsuffix.org/) for accurate domain parsing. The suffix list helps distinguish between domain names and subdomains.\n\nThe [`ComprehensiveDomainParser`](src/ComprehensiveDomainParser.mo) uses this comprehensive list for accurate domain parsing, while you can also create custom parsers with [`Domain.fromText`](src/Domain.mo) for specific use cases.\n\n### Updating the Suffix List\n\nThe domain suffix list should be updated periodically to include new top-level domains and suffixes. Run the provided script to regenerate the list:\n\n```bash\n# Requires Python 3 and internet connection\n./scripts/rebuild_suffix_list.sh\n```\n\nThis script:\n\n1. Downloads the latest Public Suffix List from https://publicsuffix.org/\n2. Processes and filters the data\n3. Generates a new `src/data/DomainSuffixData.mo` file with the current suffixes\n4. Structures the data as an efficient tree for fast lookups\n\nThe generated file contains a compressed tree structure that allows for efficient suffix matching during domain parsing. You should run this script periodically (e.g., monthly) to keep the suffix list current.\n\n## Performance\n\nURL Kit is designed for performance with:\n\n-   **Efficient Domain Matching**: Tree-based suffix lookup for O(log n) domain validation\n-   **Minimal Allocations**: Careful memory management during parsing\n-   **Lazy Evaluation**: Components are parsed only when needed\n-   **Optimized String Operations**: Efficient text processing for encoding/decoding\n-   **Compressed Suffix Data**: Compact representation of the public suffix list\n\n## Testing\n\nRun the comprehensive test suite:\n\n```bash\nmops test\n```\n\nThe test suite covers:\n\n-   URL parsing success and failure cases\n-   All host type variations (domains, IPv4, IPv6, hostnames)\n-   Query parameter manipulation\n-   URL encoding/decoding edge cases\n-   Normalization and equality comparison\n-   IPv6 address formatting variations\n-   Domain parser functionality\n-   Error handling scenarios\n\n## Migration Guide\n\n### Breaking Changes in v3.0\n\n1. **Host Type Simplified**: The Host type has been simplified by consolidating `#domain` and `#hostname` variants into a single `#name` variant:\n\n    ```motoko\n    // Old (v2.x)\n    switch (host) {\n        case (#domain(domain)) { /* domain components */ };\n        case (#hostname(name)) { /* hostname string */ };\n        case (#ipV4(ip)) { /* IPv4 address */ };\n        case (#ipV6(ip)) { /* IPv6 address */ };\n    };\n\n    // New (v3.0)\n    switch (host) {\n        case (#name(hostName)) { /* any host name string */ };\n        case (#ipV4(ip)) { /* IPv4 address */ };\n        case (#ipV6(ip)) { /* IPv6 address */ };\n    };\n    ```\n\n2. **Domain Parser Removed from URL Parsing**: URL parsing no longer requires a domain parser parameter:\n\n    ```motoko\n    // Old (v2.x)\n    import ComprehensiveDomainParser \"mo:url-kit/ComprehensiveDomainParser\";\n    let domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n    UrlKit.fromText(\"https://example.com\", domainParser)\n\n    // New (v3.0)\n    UrlKit.fromText(\"https://example.com\")\n    ```\n\n3. **Separate Domain Parsing**: Domain parsing is now a separate step when needed:\n\n    ```motoko\n    // Old (v2.x) - automatic domain parsing\n    let url = UrlKit.fromText(\"https://blog.example.com\", domainParser);\n    switch (url.authority.host) {\n        case (#domain(domain)) {\n            let name = domain.name; // \"example\"\n            let suffix = domain.suffix; // \"com\"\n            let subdomains = domain.subdomains; // [\"blog\"]\n        };\n    };\n\n    // New (v3.0) - explicit domain parsing when needed\n    let url = UrlKit.fromText(\"https://blog.example.com\");\n    switch (url.authority.host) {\n        case (#name(hostName)) {\n            let domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n            switch (domainParser.parse(hostName)) {\n                case (#ok(domain)) {\n                    let name = domain.name; // \"example\"\n                    let suffix = domain.suffix; // \"com\"\n                    let subdomains = domain.subdomains; // [\"blog\"]\n                };\n                case (#err(_)) { /* not a valid domain */ };\n            };\n        };\n    };\n    ```\n\n### Breaking Changes in v2.0\n\n1. **Domain Parser Required**: All URL parsing functions now require a `domainParser` parameter:\n\n    ```motoko\n    // Old (v1.x)\n    UrlKit.fromText(\"https://example.com\")\n\n    // New (v2.0)\n    import ComprehensiveDomainParser \"mo:url-kit/ComprehensiveDomainParser\";\n    let domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n    UrlKit.fromText(\"https://example.com\", domainParser)\n    ```\n\n2. **Domain Parsing**: Domain parsing is now more flexible with custom suffix support:\n\n    ```motoko\n    // Using comprehensive parser (recommended)\n    let domainParser = ComprehensiveDomainParser.ComprehensiveDomainParser();\n    let result = domainParser.parse(\"example.com\");\n\n    // Using custom suffixes\n    let result = Domain.fromText(\"example.test\", [\"test\", \"local\"]);\n    ```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. When contributing:\n\n1. Add tests for new functionality\n2. Update documentation for API changes\n3. Follow existing code style and patterns\n4. Ensure all tests pass\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedjcase%2Fmotoko_url_kit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedjcase%2Fmotoko_url_kit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedjcase%2Fmotoko_url_kit/lists"}