{"id":14155189,"url":"https://github.com/tjjfvi/subshape","last_synced_at":"2025-08-17T07:08:06.378Z","repository":{"id":36995322,"uuid":"477754132","full_name":"tjjfvi/subshape","owner":"tjjfvi","description":"composable shapes for cohesive code","archived":false,"fork":false,"pushed_at":"2024-01-29T17:55:36.000Z","size":492,"stargazers_count":61,"open_issues_count":17,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-08-04T01:09:53.422Z","etag":null,"topics":["deno","reflection","serialization","substrate","typescript","typing","validation"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tjjfvi.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}},"created_at":"2022-04-04T15:09:24.000Z","updated_at":"2025-06-07T01:45:35.000Z","dependencies_parsed_at":"2023-01-17T12:01:01.661Z","dependency_job_id":"f0c6b68b-68d1-4dec-8fc9-f92c5f49db16","html_url":"https://github.com/tjjfvi/subshape","commit_stats":null,"previous_names":["paritytech/parity-scale-codec-ts","paritytech/scale-ts","paritytech/subshape"],"tags_count":50,"template":false,"template_full_name":null,"purl":"pkg:github/tjjfvi/subshape","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjjfvi%2Fsubshape","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjjfvi%2Fsubshape/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjjfvi%2Fsubshape/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjjfvi%2Fsubshape/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tjjfvi","download_url":"https://codeload.github.com/tjjfvi/subshape/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tjjfvi%2Fsubshape/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270817199,"owners_count":24650948,"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-08-17T02:00:09.016Z","response_time":129,"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":["deno","reflection","serialization","substrate","typescript","typing","validation"],"created_at":"2024-08-17T08:02:25.762Z","updated_at":"2025-08-17T07:08:06.354Z","avatar_url":"https://github.com/tjjfvi.png","language":"TypeScript","funding_links":[],"categories":["typescript"],"sub_categories":[],"readme":"# subShape \u0026nbsp;\u003csub\u003e\u003csup\u003ecomposable shapes for cohesive code\u003c/sup\u003e\u003c/sub\u003e\n\n\u003e ### *one shape can do them all; one shape defined them*\n\nsubShape provides primitives and patterns for crafting composable shapes\nfeaturing cohesive typing, validation, serialization, and reflection.\n\n## Setup\n\n### Deno\n\n```ts\nimport * as $ from \"https://deno.land/x/subshape/mod.ts\"\n```\n\n### Node\n\n```\nnpm install subshape\n```\n\n```ts\nimport * as $ from \"subshape\"\n```\n\n## Demo\n\n### Craft a Composable Shape\n\n```ts\nimport * as $ from \"https://deno.land/x/subshape/mod.ts\"\n\nconst $superhero = $.object(\n  $.field(\"pseudonym\", $.str),\n  $.optionalField(\"secretIdentity\", $.str),\n  $.field(\"superpowers\", $.array($.str)),\n)\n```\n\n### And Get...\n\n#### Typing\n\n```ts\ntype Superhero = $.Output\u003ctypeof $superhero\u003e\n// type Superhero = {\n//   pseudonym: string;\n//   secretIdentity?: string | undefined;\n//   superpowers: string[];\n// }\n```\n\n#### Validation\n\n```ts\nconst spiderMan = {\n  pseudonym: \"Spider-Man\",\n  secretIdentity: \"Peter Parker\",\n  superpowers: [\"does whatever a spider can\"],\n}\n\n$.assert($superhero, spiderMan) // ok!\n\nconst bob = {\n  pseudonym: \"Bob\",\n  secretIdentity: \"Robert\",\n  superpowers: null,\n}\n\n$.assert($superhero, bob) // ShapeAssertError: !(value.superpowers instanceof Array)\n```\n\n#### Serialization\n\n```ts\nconst encoded = $superhero.encode(spiderMan)\n// encoded: Uint8Array\n\nconst decoded = $superhero.decode(encoded)\n// decoded: Superhero\n\nconsole.log(decoded)\n// Prints:\n//   {\n//     pseudonym: \"Spider-Man\",\n//     secretIdentity: \"Peter Parker\",\n//     superpowers: [ \"does whatever a spider can\" ]\n//   }\n```\n\n#### Reflection\n\n```ts\n$superhero.metadata // Metadata\u003cSuperhero\u003e\n\nconsole.log($superhero)\n// Prints:\n//   $.object(\n//     $.field(\"pseudonym\", $.str),\n//     $.optionalField(\"secretIdentity\", $.str),\n//     $.field(\"superpowers\", $.array($.str))\n//   )\n```\n\n## Examples\n\nFurther examples can be found in the\n[`examples`](https://github.com/paritytech/scale-ts/tree/main/examples)\ndirectory.\n\n## Shape Naming Convention\n\nThis library adopts a convention of denoting shapes with a `$` – `$.foo` for\nbuilt-in shapes, and `$foo` for user-defined shapes. This makes shapes easily\ndistinguishable from other values, and makes it easier to have shapes in scope\nwith other variables:\n\n```ts\ninterface Person { ... }\nconst $person = $.object(...)\nconst person = { ... }\n```\n\nHere, the type, shape, and a value can all coexist without clashing, without\nhaving to resort to wordy workarounds like `personShape`.\n\nThe main other library this could possibly clash with is jQuery, and its usage\nhas waned enough that this is not a serious problem.\n\nWhile we recommend following this convention for consistency, you can, of\ncourse, adopt an alternative convention if the `$` is problematic – `$.foo` can\neasily become `s.foo` or `subshape.foo` with an alternate import name.\n\n## Asynchronous Encoding\n\nSome shapes require asynchronous encoding. Calling `.encode()` on a shape will\nthrow if it or another shape it calls is asynchronous. In this case, you must\ncall `.encodeAsync()` instead, which returns a `Promise\u003cUint8Array\u003e`. You can\ncall `.encodeAsync()` on any shape; if it is a synchronous shape, it will simply\nresolve immediately.\n\nAsynchronous decoding is not supported.\n\n## Custom Shapes\n\nIf your encoding/decoding logic is more complicated, you can create custom\nshapes with `createShape`:\n\n```ts\nconst $foo = $.createShape\u003cFoo\u003e({\n  metadata: $.metadata(\"$foo\"),\n\n  // A static estimation of the encoded size, in bytes.\n  // This can be either an under- or over- estimate.\n  staticSize: 123,\n  subEncode(buffer, value) {\n    // Encode `value` into `buffer.array`, starting at `buffer.index`.\n    // A `DataView` is also supplied as `buffer.view`.\n    // At first, you may only write at most as many bytes as `staticSize`.\n    // After you write bytes, you must update `buffer.index` to be the first unwritten byte.\n\n    // If you need to write more bytes, call `buffer.pushAlloc(size)`.\n    // If you do this, you can then write at most `size` bytes,\n    // and then you must call `buffer.popAlloc()`.\n\n    // You can also call `buffer.insertArray()` to insert an array without consuming any bytes.\n\n    // You can delegate to another shape by calling `$bar.subEncode(buffer, bar)`.\n    // Before doing so, you must ensure that `$bar.staticSize` bytes are free,\n    // either by including it in `staticSize` or by calling `buffer.pushAlloc()`.\n    // Note that you should use `subEncode` and not `encode`.\n\n    // See the `EncodeBuffer` class for information on other methods.\n\n    // ...\n  },\n\n  subDecode(buffer) {\n    // Decode `value` from `buffer.array`, starting at `buffer.index`.\n    // A `DataView` is also supplied as `buffer.view`.\n    // After you read bytes, you must update `buffer.index` to be the first unread byte.\n\n    // You can delegate to another shape by calling `$bar.subDecode(buffer)`.\n    // Note that you should use `subDecode` and not `decode`.\n\n    // ...\n    return value\n  },\n\n  subAssert(assert) {\n    // Validate that `assert.value` is valid for this shape.\n    // `assert` exposes various utility methods, such as `assert.instanceof`.\n    // See the `AssertState` class for information on other methods.\n\n    // You can delegate to another shape by calling `$bar.subAssert(assert)` or `$bar.subAssert(assert.access(\"key\"))`.\n    // Any errors thrown should be an instance of `$.ShapeAssertError`, and should use `assert.path`.\n\n    // ...\n  },\n})\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjjfvi%2Fsubshape","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftjjfvi%2Fsubshape","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftjjfvi%2Fsubshape/lists"}