{"id":25909147,"url":"https://github.com/marcelwaldvogel/vcard4-ts","last_synced_at":"2025-03-03T08:16:24.587Z","repository":{"id":57391062,"uuid":"421499307","full_name":"MarcelWaldvogel/vcard4-ts","owner":"MarcelWaldvogel","description":"A vCard 4.0 library with type safety first","archived":false,"fork":false,"pushed_at":"2021-12-05T14:33:06.000Z","size":262,"stargazers_count":21,"open_issues_count":4,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-26T11:38:54.690Z","etag":null,"topics":["typescript","vcard","vcard-parser","vcard4"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/MarcelWaldvogel.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-26T16:21:01.000Z","updated_at":"2025-01-28T09:08:35.000Z","dependencies_parsed_at":"2022-09-19T05:41:07.279Z","dependency_job_id":null,"html_url":"https://github.com/MarcelWaldvogel/vcard4-ts","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelWaldvogel%2Fvcard4-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelWaldvogel%2Fvcard4-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelWaldvogel%2Fvcard4-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarcelWaldvogel%2Fvcard4-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarcelWaldvogel","download_url":"https://codeload.github.com/MarcelWaldvogel/vcard4-ts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241629768,"owners_count":19993710,"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":["typescript","vcard","vcard-parser","vcard4"],"created_at":"2025-03-03T08:16:23.745Z","updated_at":"2025-03-03T08:16:24.574Z","avatar_url":"https://github.com/MarcelWaldvogel.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![vcard4-ts](./assets/vCard4-ts.svg)\n\n# vcard4-ts — A vCard 4.0 library with type safety first\n\n![Coverage badge](./assets/coverage-badge.svg)\n\n`vcard4-ts` was designed with the following goals:\n\n- Compliant with [RFC 6350](https://datatracker.ietf.org/doc/html/rfc6350) and\n  its extensions\n\n- TypeScript (and type safety) from the ground up\n\n- Avoid mistakes, DRY (Don't Repeat Yourself)\n\n  - The data structure definition, created from RFC 6350, contains instructions\n    for the parser\n\n- The returned data structure is easy to use\n  - The decisions to be made by the calling code should be as few and as simple\n    as possible. Everything that can be delegated to the IDE (while writing your\n    code) and TypeScript compile time should be handled there. E.g., no need to\n    check whether there is a single or multiple values: if something can occur\n    multiple times, the item is always in an array.\n\nIn addition to RFC6350, the following RFCs are implemented:\n\n- [RFC 6474](https://datatracker.ietf.org/doc/html/rfc6474/): `BIRTHPLACE`,\n  `DEATHPLACE`, and `DEATHDATE` properties\n- [RFC 6715](https://datatracker.ietf.org/doc/html/rfc6715/): `EXPERTISE`,\n  `INTEREST`, `HOBBY`, `ORG-DIRECTORY` properties and `LEVEL`, `INDEX`\n  parameters\n- [RFC 8605](https://datatracker.ietf.org/doc/html/rfc8605/): `CONTACT-URI`\n  property and `CC` (two-letter country code) parameter\n\n`vcard4-ts` is compatible to the following RFCs, as it does not impose any\nlimitation on string-valued parameters and values:\n\n- [RFC 6473](https://datatracker.ietf.org/doc/html/rfc6473): The\n  `KIND:application` property\n- [RFC 7852](https://datatracker.ietf.org/doc/html/rfc7852/): The\n  `TYPE=main-number` parameter\n\n## Installation\n\n`yarn add vcard4-ts` or `npm i vcard4-ts`. No dependencies (except\ndevDependencies). And only about 10 kB (compressed) will end up in your code,\nthe rest is tests, alternatives, debugging information, …\n\n## Usage\n\n### Simple example\n\nBasic usage is straightforward:\n\n```ts\nimport { parseVCards } from 'vcard4-ts';\nimport { readFileSync } from 'fs';\n\nconst vcf = readFileSync('example.vcf').toString();\n\nconst cards = parseVCards(vcf);\nif (cards.vCards) {\n  for (const card of cards.vCards) {\n    console.log('Card found for ' + card.FN[0].value[0]);\n  }\n} else {\n  console.error('No valid vCards in file');\n}\n```\n\nWe can see two basic principles in action:\n\n1. The types are always clear, no expensive run-time testing whether there is\n   just a single value or there are multiple values. (This is the prime\n   directive.)\n2. There are no `null` or `undefined` (aka nullish) values; and any arrays will\n   always have at least one element. This is the secondary directive.\n\nAs a result of these principles, the following rules apply:\n\n1. Mandatory properties (`BEGIN`, `END`, `VERSION`, and `FN`) always do exist\n   and are never `null` or `undefined` (\"nullish\").\n2. Optional properties (all the others defined in RFC 6350) only exist, if they\n   do appear in the file. I.e., if they exist, they also have a value and are\n   never nullish. (However, the strings may still be empty.)\n3. To match the prime directive, any property, whether mandatory or optional,\n   that _may_ appear more than once, is [_always_ an array of values](#arrays).\n\nThese rules make software development more predictable and thus faster, less\nerror-prone:\n\n- Typescript can verify type correctness.\n- Autocompletion and type inference in IDEs such as VSCode/VSCodium works and is\n  very helpful.\n\n### More elaborate example\n\nThis example demonstrates the access to parsing errors and warnings, to\nstructured information, and non-RFC properties. Explanations are in the\n[design](#design) and [reference](#reference) sections below.\n\n```ts\nif (cards.nags) {\n  // There were global problems, e.g. because the file did seem to contain invalid vCards.\n  // Those cards can be obtained by passing `keepDefective: true` to `parseVCards()`.\n  for (const nag of cards.nags) {\n    if (nag.isError) {\n      console.error(`${nag.key} (${nag.description}): ${nag.attributes}`);\n    } else {\n      console.warn(`${nag.key} (${nag.description}): ${nag.attributes}`);\n    }\n  }\n}\nfor (let card of cards.vCards) {\n  // If you would like element 0 to correspond to the most PREFerred item:\n  sortByPREF(card);\n\n  // You're guaranteed to have all these (required) properties,\n  // no need to check their existence first. Also, the editor will\n  // auto-complete and know the type.\n  console.log('Found vCard with version ' + card.VERSION.value);\n  console.log('Full name: ' + card.FN[0].value[0]);\n\n  // Maybe some optional (any-cardinality) RFC6350 property is present?\n  if (card.EMAIL) {\n    // There might be multiple EMAIL property lines, but as the EMAIL field\n    // is present, we're guaranteed to have at least one value. See\n    // https://netfuture.ch/2021/11/array-thickening-more-can-be-less/\n    console.log('Emailable at: ' + card.EMAIL[0].value);\n    // Is it known whether it is a work or home address?\n    if (card.EMAIL[0].parameters?.TYPE) {\n      console.log('It is of type: ' + card.EMAIL[0].parameters.TYPE[0]);\n    }\n  }\n\n  // The same with a structured any-cardinality property\n  if (card.ADR) {\n    // All elements of the address, including the locality, can have multiple\n    // values. And we still could have multiple addresses (e.g., work and\n    // home). We'll just print the first.\n    console.log('Living in: ' + card.ADR[0].value.locality[0]);\n  }\n\n  // Any property not in the standard (and its extension RFCs)?\n  // (Their name should be prefixed with `X-`)\n  if (card.x) {\n    for (const [k, v] of Object.entries(card.x)) {\n      console.log('Non-RFC6350 property ' + k + ', with ' + JSON.stringify(v));\n    }\n  }\n\n  // Any problems found while parsing the vCard?\n  if (card.nags) {\n    console.log(\n      'While parsing this card, the following was noticed ' +\n        '(and either the problematic part dropped or ignored)',\n    );\n    for (const nag of card.nags) {\n      if (nag.isError) {\n        console.error(`Global ${nag.key} (${nag.description})`);\n      } else {\n        console.warn(`Global ${nag.key} (${nag.description})`);\n      }\n    }\n\n    // Some of these problems might be unparseable lines. They are archived\n    // here.\n    if (card.unparseable) {\n      console.log('The following unparseable lines were encountered:');\n      for (const line of card.unparseable) {\n        console.log(line);\n      }\n    }\n  }\n}\n```\n\n## Design\n\nThe prime design goal is to avoid mistakes in the code and enable calling code\nto avoid mistakes as well. Designing for (type) safety is achieved by\n[Don't Repeat Yourself](#dry), [Parse, don't validate](#parse-dont-validate),\nand [Array thickening](#array-thickening).\n\n### DRY\n\n[Don't Repeat Yourself](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)\nwas a basic design principle while developing the module. The description of the\ndata structure is centralized. The goal was to have only a single authoritative\nsource of type information, from which both compile-time type information and\nruntime parsing instructions would be derived. As TypeScript transpilation\noutput no longer contains the type information, it was necessary to jump through\nhoops. (Luckily, [Colin McDonnell's Zod](https://colinhacks.com/essays/zod) was\na great resource for educating about hoop-jumping.)\n\n### Parse, don't validate\n\nThe idea of\n[parsing instead of validation](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)\nwas introduced by Alexis King, for the Haskell ecosystem. The gist of it:\nDirectly parse the source data into the required (type-safe) format, instead of\nfirst parsing it into an (essentially) untyped format and then validating it to\nbe of the right type. This assures that type safety starts earlier and is\nguaranteed to be consistent throughout the entire codebase.\n\nIn `vcard4-ts`, data structures are created and filled type-safe from the start.\nBecause properties will be added on a line-by-line basis, required properties\ncannot be ensured to exist from the start. Therefore, as an exception to this\nrule, the existance of required fields is only ensured at the end.\n\n### Array thickening\n\nThe advantage of always having an array IMHO greatly outweighs the\ndisadvantages. Calling code can always assume that the contents _are_ an array.\nI.e., arrays with just a single value are _never_ flattened (therefore the\nname). If you are only interested in one value, just use the one at index 0,\nwhich will always exist. If you want to deal with multiple values, use array\nmethods such as `map()` and `join()`, which you can always use, because it is\nalways an array. Yes, this results in more time and space spent during the\ncreation of the data structure.\n\nMore importantly, this relieves calling code from performing case distinctions\non every single access. Instead, the existence of the property can be asserted\nonce and every reference to it later already knows how to deal with it. It is\neven possible to combine assertion and access with\n[optional chaining](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining).\n\n[Array thickening](https://netfuture.ch/2021/11/array-thickening-more-can-be-less/)\nresults in less code for the caller, which often also results in less code\ncoverage, i.e., the uncommon case is not tested. In other words, array\nthickening turns the general case (whether common or uncommon) into the only\ncase.\n\n## API\n\n- `parseVCards(vcf: string, keepDefective?: boolean = false): ParsedVCards`:\n  Parse a string into possibly multiple VCards. Details below.\n- `sortByPREF\u003cT extends Partial\u003cVCard4\u003e\u003e(vcard: T)`: Sort properties which exist\n  multiple times by their preference parameter (1…100; the ones without `PREF`\n  are sorted last).\n- `groupVCard\u003cT extends Partial\u003cVCard4\u003e\u003e(vcard: T): GroupedVCard`: Group\n  properties with group labels into their named group (all non-lowercase names).\n  Anything without an explicit group label will end up in the `top`.\n  (`GroupedVCard` is `Record\u003cUppercase\u003cstring\u003e | 'top', Partial\u003cVCard4\u003e\u003e`).\n\nSorting and grouping are separate functions, not methods of an object, to ensure\nthat their code will only be included if you call them.\n\nIf you need sorting _and_ grouping, use the following sequence:\n\n```ts\nconst cards = parseVCards(vcf);\nif (cards.vCards) {\n  for (const card of cards.vCards) {\n    sortByPREF(card);\n    const grouped = groupVCard(card);\n    // Process the PREF-sorted groups here\n  }\n}\n```\n\n## Reference\n\n### Property/parameter names\n\nAll vCard properties and parameters in the data structures are uppercase and\ndashes have been converted to underscores. This makes them clearly visible and\neasily accessible as JavaScript/TypeScript properties, avoiding the\nharder-to-type hash/array notation (i.e., `card.SORT_AS` instead of\n`card['SORT-AS']`).\n\nLowercase JavaScript/TypeScript properties are maintained by the parser.\n\n### Property cardinality\n\n- `BEGIN`, `END`, and `VERSION` exist exactly once (cardinality `1` in RFC6350;\n  required value in TypeScript)\n- `FN` (full name) exists at least once (`1*` in RFC6350; optional array in\n  TypeScript)\n- `PRODID`, `UID`, `REV`, `KIND`, `N` (name), `BDAY`, `BIRTHPLACE`, `DEATHDATE`,\n  `DEATHPLACE`, `ANNIVERSARY`, and `GENDER` are optional (`*1` in RFC6350;\n  optional value in TypeScript)\n- [All others](src/vcard4Types.ts) can occur any number of times (`*` in\n  RFC6350; optional array in TypeScript)\n\n### Property value type\n\n- `N` is an object with the following properties: `familyNames`, `givenNames`,\n  `additionalNames`, `honorificPrefixes`, `honorificSuffixes`; each a required\n  `string[]`. Remember that arrays are guaranteed to always have at least one\n  element, i.e., the an empty `honorificPrefixes` property will be encoded as an\n  array consisting of an empty string `['']`.\n- `ADR` is similar to `N`, but with the following string array fields:\n  `postOfficeBox`, `extendedAddress`, `streetAddress`, `locality` (city),\n  `region`, `postalCode`, and `countryName`.\n- `GENDER` consists of two `string`s, a required `sex` and an optional\n  explanatory `text`. `sex` is required by RFC6350 to be one of `M`, `F`, `O`,\n  `N`, `U`, or the empty string. However, this is not checked by `vcard4-ts`.\n- `CLIENTPIDMAP` consists of `pidRef`, a `number`, and a `uri`, a `string`.\n- All other properties' values are mapped to a single `string`, even if they are\n  defined as more structured types, such as dates or URIs.\n\n### Property parameters\n\nProperties can have\n[(mostly optional) parameters](https://datatracker.ietf.org/doc/html/rfc6350#section-5):\n\n- `PREF` is a `number`. It is not asserted whether it is in the range [1…100]\n  required by the RFC; non-numeric values are returned as `NaN`.\n- `INDEX` is a `number`. It is not asserted whether it is a strictly positive\n  integer as mandated by RFC6715; non-numeric values are returned as `NaN`.\n- `PID`, `TYPE`, and `SORT_AS` (`SORT-AS` in the VCF) are `string[]`s, again\n  with a guaranteed minimum array length of 1. (Please note that\n  [the example in the RFC](https://datatracker.ietf.org/doc/html/rfc6350#section-8)\n  quotes the enumeration of `TYPE`s, which seems inconsistent with the\n  [`TYPE` definition](https://datatracker.ietf.org/doc/html/rfc6350#section-5.6),\n  so you may want to apply `split(',')` to all `TYPE` values first.)\n- All others are single `string`s.\n\n### Non-RFC properties and parameters\n\nAny property or parameter whose type is not explicitely given in RFC6350 and the\nRFCs that extend it, including those prefixed by `X-`, are not included at the\nsame level as the rest of the properties. One reason is that\n[TypeScript does not really allow default types on object properties](https://basarat.gitbook.io/typescript/type-system/index-signatures#excluding-certain-properties-from-the-index-signature)\nand therefore,\n[nested index signatures](https://basarat.gitbook.io/typescript/type-system/index-signatures#design-pattern-nested-index-signature)\nare recommended for this.\n\nInstead, non-RFC properties and parameters are put into an `x` object property.\nThe actual value will be a plain, unprocessed `string`. If it has more\nstructure, you need to extract it yourselves, e.g. using\n\n- `scan1DValue()`, which unescapes and splits at the specified `splitChar` (`,`,\n  as used for `PID` or `TYPE` parameters; or `;`, as used for the `GENDER`\n  value); or\n- `scan2DValue()`, which splits into a `string[][]` at `;` _and_ `,` (used for\n  `ADR` and `N` values).\n\nFor example, the `string` value of an `X-ABUID` property in card `card` would be\navailable as `card.x.X_ABUID.value`.\n\n### Handling errors\n\n**Your application can just ignore the errors, if it does not want to bother.**\n\nOne of the design goals so obvious that it was not specifically mentioned above,\nis that `vcard4-ts` should be as easy to use as possible. Anyone who ever had to\ndeal with user-specified input can tell horror stories about what can go wrong.\nLast but not least, ensuring\n[user-specified input fulfills certain requirements is also a matter of security](https://owasp.org/Top10/A03_2021-Injection/).\n\nTherefore, `parseVCards()` returns the information in a format as consistent as\npossible, minimizing doubt and variability. In general, any line that cannot be\nparsed is ignored, and any vCard which does not fulfill minimum criteria is\ndiscarded.\n\nThis process is documented in the `nags` property of the returned object(s). The\n`nags` property is an array of warnings and errors that occurred during the\nprocessing.\n\n#### Warnings and errors\n\nA _warning_ indicates that even though the input does not fulfill an RFC6350\ncriteria, the parser believes that it could safely correct the problem and that\nthe data returned is probably exactly what its originator meant it to be.\n\nAn _error_, on the other hand, indicates that some information was dropped, or,\nalternatively, that some required information was added. The resulting parsed\ndata is not the same as originally provided, but it is the best the parser could\ndo to achieve RFC6350 conformance.\n\nIf at least one actual error (not just warnings) is included in the nags,\n`hasErrors` is set to `true`. Depending on the policy of the calling code,\n\n- data can be accepted as returned by the parser (most lenient),\n- data can be refused if `hasErrors` is `true` (it always exists, but hopefully\n  is `false`), or\n- data can be refused if `nags` exists (i.e., any errors or warnings occured;\n  the most strict policy).\n\n#### Global, local, and mixed nags\n\n_Local_ nags are specific to a vCard and are stored there, alongside the\nproperties.\n\nLocal nags have the following type:\n\n```ts\n{\n  key: string; // A short string to match against in the code\n  description: string; // A longer english-language description to display to the user\n  isError: boolean; // Error or warning?\n  attributes: {\n    property: string; // The property it occurred at (or '', if there was a property name parsing problem)\n    parameter?: string; // If the problem occurred while parsing a parameter, this is its name\n    line?: string; // The first few characters of the line on which this error occurred\n  }\n}\n```\n\n_Global_ nags are set at the top level of the returned structure, alongside the\n`vCards` field, if it exists. They indicate problems not related to a vCard, or\nrelated to a vCard which was not included because it was considered too bad to\nbe returned.\n\nGlobal nags use the same type as local nags above, but without the `attributes`.\n\n_Mixed_ nags are used to indicate errors affecting an entire vCard (there are no\nmixed warnings). If `parseVCards()` detects a major problem with a vCard\n(`VCARD_BAD_TYPE` or `VCARD_NOT_BEGIN`), then—by default—this vCard is dropped\nand the error—unable to be stored _in_ the vCard itself—is bubbled up to the\n_global_ level. However, if `keepDefective=true` is passed as an optional\nargument, these vCards are not dropped and the error is stored in the vCard\nitself.\n\n#### The nags\n\n- `FILE_EMPTY`: A global error.\n- `FILE_CRLF`: A global warning, that lines did not end in carriage return+line\n  feed as specified in RFC6350, but just with line feeds. (This only checks the\n  first line end and is therefore subject to false negatives, if line ends are\n  not consistent.)\n- `VCARD_BAD_TYPE`: A mixed error resulting in a defective card. The `BEGIN` or\n  `END` property does not have the required `VCARD` value.\n- `VCARD_NOT_BEGIN`: A mixed error resulting in a defective card. The first\n  property of the vCard is not a `BEGIN` property.\n- `VCARD_MISSING_PROP`: A local error. A required property is missing and has\n  been added with a default value. The default for `VERSION` is `4.0`; for `FN`,\n  the empty string.\n- `PROP_NAME_EMPTY`: A local error. The property has an empty name.\n- `PROP_NAME_EOL`: A local error. The property name is terminated by the end of\n  line, i.e., colon and value are missing.\n- `PROP_DUPLICATE`: A local error. property which may not appear more than once\n  has been seen a second time.\n- `PARAM_UNCLOSED_QUOTE`: A local error. A parameter had a quoted value, but the\n  quote was unbalanced.\n- `PARAM_MISSING_EQUALS`: A local error. A parameter name was not terminated by\n  an equals sign.\n- `PARAM_INVALID_NUMBER`: A local error. The parameter value should have been a\n  number but wasn't.\n- `PARAM_DUPLICATE`: A local error. A parameter that can only have a single\n  value was specified more than once.\n- `PARAM_UNESCAPED_COMMA`: A local warning. A parameter accepting only a single\n  value contained an unescaped comma. This may indicate incomplete character\n  escaping or trying to provide multiple values where they are not allowed.\n- `PARAM_BAD_BACKSLASH`: A local warning. In a double-quoted parameter value, a\n  backslash was found. Escaping in quoted parameter values should be according\n  to RFC6868, using circumflexes (`^`). This indicates a possible problem in the\n  input file; the backslash was not treated as a special character.\n- `PARAM_BAD_CIRCUMFLEX`: A local warning. In a double-quoted parameter value, a\n  circumflex (`^`) was found, which was not part of an escape sequence. This\n  indicates a possible problem in the input file; that circumflex was not\n  treated as a special character.\n- `VALUE_INVALID`: A local error. A property with a required value had a\n  different value.\n- `VALUE_UNESCAPED_COMMA`: A local warning. A property accepting only a single\n  value contained an unescaped comma. This may indicate old-style (vCard3)\n  value, e.g. for `PHOTO`, which is considered incomplete character escaping in\n  vCard4.\n\n#### Unparseable lines\n\nIf any lines in the current vCard left the parser speechless, they are stored\nessentially unmodified in the `unparseable` array. The only modification is that\nwrapped lines have been unwrapped, as this happens before parsing. You most\nlikely want to ignore those lines, unless you want to re-export the vCard as\nfaithfully as possible, even if that violates the standard (and might cause\nerrors for other parsers).\n\n## Related work\n\n- Searching for `vcard` on [NPM](https://npmjs.org) results in mostly vCard\n  generators or converters to/from other formats. Notable exceptions:\n\n  - [vcard4](https://github.com/kelseykm/vcard4) is a vCard 4.0 generator which\n    also includes parsing capabilities.  \n    Trying to create type annotations for `vcard4` turned out to be hard. The\n    resulting types for the parser would be so lax as not to help when writing a\n    program processing it further, requiring runtime type verification in the\n    application. Also, their design decision to transform arrays with a single\n    member into requires every access to verify the field's structure.\n    Furthermore, it has some minor issues with its RFC 6350 compliance (lack of\n    proper property group support or incomplete unescaping rules) and the IETF's\n    general\n    [Robustness principle](https://en.wikipedia.org/wiki/Robustness_principle)\n    (i.e., not accepting bare newlines).\n  - [vdata-parser](https://github.com/floriangosse/vdata-parser) is a generic\n    vCard/vCalendar parser, handling multiple cards in a single file.  \n    Similar to `vcard4` above, it does not seem amenable to reasonably tight\n    types and mixes elements and arrays. Furthermore, it is unaware of the\n    expected parameter/property structure and does not handle escaped data.\n\n- The runtime type introspection required for DRY is modeled after\n  [Zod](https://github.com/colinhacks/zod/).  \n  Zod was even used for an early prototype. However, a ultra-lightweight,\n  tailored alternative to Zod was created (clocking in at under 200 bytes\n  minified/gzipped). Zod would have created overhead (additional dependencies,\n  bundle size, but especially the amount of code needed to define and query the\n  schema, while having to touch Zod internals which might change in the future),\n  while providing little benefit. For example, Zod's `transform` seemed to be\n  impossible to apply to parsing directly. So, Zod's would just have been used\n  to duplicate work that had already been performed\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelwaldvogel%2Fvcard4-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcelwaldvogel%2Fvcard4-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcelwaldvogel%2Fvcard4-ts/lists"}