{"id":21019415,"url":"https://github.com/ceramicnetwork/codeco","last_synced_at":"2025-09-14T16:48:37.056Z","repository":{"id":152408279,"uuid":"625988382","full_name":"ceramicnetwork/codeco","owner":"ceramicnetwork","description":"Minimalistic yet feature-rich IO decoding and encoding","archived":false,"fork":false,"pushed_at":"2024-08-12T14:30:35.000Z","size":198,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-09-05T10:43:51.323Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ceramicnetwork.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2023-04-10T14:58:35.000Z","updated_at":"2024-09-09T23:08:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"a84585c3-add7-455a-ac7f-a146cee5ecbe","html_url":"https://github.com/ceramicnetwork/codeco","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/ceramicnetwork/codeco","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fcodeco","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fcodeco/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fcodeco/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fcodeco/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ceramicnetwork","download_url":"https://codeload.github.com/ceramicnetwork/codeco/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ceramicnetwork%2Fcodeco/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275136278,"owners_count":25411706,"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","status":"online","status_checked_at":"2025-09-14T02:00:10.474Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2024-11-19T10:31:54.951Z","updated_at":"2025-09-14T16:48:37.028Z","avatar_url":"https://github.com/ceramicnetwork.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Codeco\n\n\u003e Lightweight TypeScript-first encoding and decoding of complex objects.\n\n## Idea\n\nA value of type `Codec\u003cA, O, I\u003e` (called \"codec\") is the runtime representation of the static type `A`.\n\nA codec can:\n\n- decode inputs of type `I`,\n- encode values to type `O`,\n- be used as a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates).\n\n```typescript\nexport abstract class Codec\u003cA, O = A, I = unknown\u003e {\n  protected constructor(readonly name: string) {}\n\n  abstract is(input: unknown): input is A;\n  abstract encode(value: A): O;\n  abstract decode(input: I): Either\u003cError, A\u003e;\n}\n```\n\nAs an example, here is a codec for integer encoded as a string:\n\n```typescript\n// Represents integer `number`, the first type parameter.\n// If we encode a known number, it will turn into `string` (the second type parameter).\n// If we want to receive a number, the codec can accept `string` as input to parse (the third type parameter).\n// To decode `unknown` input do something like `string.pipe(numberAsString)`.\nclass IntAsStringCodec extends Codec\u003cnumber, string, string\u003e {\n  constructor() {\n    super(`IntAsString`);\n  }\n\n  // Similar to `instanceof`.\n  is(input: unknown): input is number {\n    return typeof input === \"number\";\n  }\n\n  decode(input: string, context: Context): Validation\u003cnumber\u003e {\n    const supposedlyInt = parseInt(input, 10);\n    // If an integer\n    if (supposedlyInt.toString() === input) {\n      // Return value\n      // Beware: do not return plain value, wrap it in `context.success`\n      return context.success(supposedlyInt);\n    } else {\n      // If anything is wrong, signal failure by returning `context.failure`.\n      // Whatever happens, **do not throw an error**.\n      return context.failure(`Not an integer`);\n    }\n  }\n\n  // Encode known value to string output.\n  encode(value: number): string {\n    return value.toString();\n  }\n}\n\nconst intAsString = new IntAsStringCodec();\n```\n\nIn most cases though, creating codecs this way is an overkill.\nCodec combinators [provided by the library](#implemented-types) are enough for 90% of use cases.\n\nThe `Either` type represents a value of one of two possible types (a disjoint union):\n\n- `Left` meaning _success_,\n- `Right` meaning _failure_.\n\n```typescript\ntype Either\u003cTError, TValue\u003e =\n  | {\n      readonly _tag: \"Left\";\n      readonly left: TError;\n    }\n  | {\n      readonly _tag: \"Right\";\n      readonly right: TValue;\n    };\n```\n\nYou could check a result of validation using `isValid` or `isError` helpers:\n\n```typescript\nimport { string, refinement, validate, isError } from \"codeco\";\n\nconst longString = refinement(string, (s) =\u003e s.length \u003e= 100);\nconst validation = validate(longString, \"short input\");\nif (isError(validation)) {\n  console.log(\"Validation errorr\", validation.left);\n}\nconst valid = validation.right; // Here goes proper long string\n```\n\n## Implemented types\n\n| Description           | TypeScript                  | codec                                                                      |\n| --------------------- | --------------------------- | -------------------------------------------------------------------------- |\n| null                  | `null`                      | `cs.null` or `cs.nullCodec`                                                |\n| undefined             | `undefined`                 | `cs.undefined`                                                             |\n| void                  | `void`                      | `cs.void`                                                                  |\n| string                | `string`                    | `cs.string`                                                                |\n| number                | `number`                    | `cs.number`                                                                |\n| boolean               | `boolean`                   | `cs.boolean`                                                               |\n| BigInt                | `bigint`                    | `cs.bigint`                                                                |\n| unknown               | `unknown`                   | `cs.unknown`                                                               |\n| literal               | `'s'`                       | `cs.literal('s')`                                                          |\n| array of unknown      | `Array\u003cunknown\u003e`            | `cs.unknownArray`                                                          |\n| dictionary of unknown | `Record\u003cstring, unknown\u003e`   | `cs.unknownDictionary`                                                     |\n| array of type         | `Array\u003cA\u003e`                  | `cs.array(A)`                                                              |\n| any                   | `any`                       | `cs.any`                                                                   |\n| never                 | `never`                     | `cs.never`                                                                 |\n| dictionary            | `Record\u003cstring, A\u003e`         | `cs.dictionary(A)`                                                         |\n| record of type        | `Record\u003cK, A\u003e`              | `cs.record(K, A)`                                                          |\n| partial               | `Partial\u003c{ name: string }\u003e` | `cs.partial({ name: cs.string })`                                          |\n| readonly              | `Readonly\u003cA\u003e`               | `cs.readonly(A)`                                                           |\n| type alias            | `type T = { name: A }`      | `cs.type({ name: A })`                                                     |\n| tuple                 | `[A, B]`                    | `cs.tuple([ A, B ])`                                                       |\n| union                 | `A \\| B`                    | `cs.union([ A, B ])`                                                       |\n| intersection          | `A \u0026 B`                     | `cs.intersection([ A, B ])`                                                |\n| keyof                 | `keyof M`                   | `cs.keyof(M)` (**only supports string keys**)                              |\n| recursive types       |                             | `cs.recursive(name, definition)`                                           |\n| exact types           | ✘                           | `cs.exact(type)` (no unknown extra properties)                             |\n| strict                | ✘                           | `cs.strict({ name: A })` (an alias of `cs.exact(cs.type({ name: A })))`    |\n| sparse                | ✘                           | `cs.sparse({ name: A })` similar to `cs.intersect(cs.type(), cs.partial()` |\n| replacement           | ✘                           | `cs.replacement(A, altInput)`                                              |\n| optional              | `A \\| undefined`            | `cs.optional(A)`                                                           |\n\n## Linear parsing\n\nIn addition to structural encoding/decoding, we provide linear _parsing_ functions in form of Parser Combinators\navailable from 'codeco/linear':\n\n```typescript\nimport * as P from \"codeco/linear\";\nimport { getOrThrow } from \"codeco\";\n\nconst line = P.seq(P.literal(\"My name is \"), P.match(/\\w+/));\nconst name = P.map(line, (parsed) =\u003e parsed[1]); // `map` combinator\nconst input = new P.StringTape(\"My name is Marvin\"); // Prepare input for consumption\nconst decodedName = getOrThrow(P.parseAll(input)); // Would throw if input does not conform to expected format\n```\n\nProvided combinators:\n\n- `literal(\"string-value\")` - literal value\n- `map(combinator, mapFn)` - map return value of `combinator` to something else,\n- `mapFold(combinator, mapFn)` - map return value of `combinator` to something else as `Either`, so optionally indicating failure,\n- `match(regexp)` - like `literal`, but matches a RegExp,\n- `seq(combinatorA, combinatorB, ...)` - match combinators and return array of their results,\n- `join(combinators)` - match combinators and their results as a single string,\n- `joinSeq(combinators)` - shortcut for `join(seq(combinatros))`,\n- `option(combinator, value)` - try matching `combinator`, return `value` if the combinator does not match,\n- `choice(combinatorA, combinatorB, ...)` - match any of the passed combinators,\n- `sepBy(combinator, separator, min = 1, max = Infinity)` - match sequence of 1 or more `combinator`s separated by `separator`, like `A`, `A + A`, `A + A + A`, etc.\n- `many(combinator, min = 1, max = Infinity)` - array of combinators of length `[min, max)`,\n- `parseAll(combinator)` - make sure all the input is consumed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicnetwork%2Fcodeco","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceramicnetwork%2Fcodeco","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceramicnetwork%2Fcodeco/lists"}