├── .github ├── dependabot.yml └── workflows │ ├── generated-pr.yml │ ├── js-test-and-release.yml │ └── stale.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── package.json ├── src └── index.js ├── test ├── test-basics.spec.js └── ts-use │ ├── .gitignore │ ├── package.json │ ├── src │ └── main.ts │ └── tsconfig.json └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | commit-message: 10 | prefix: "deps" 11 | prefix-development: "deps(dev)" 12 | -------------------------------------------------------------------------------- /.github/workflows/generated-pr.yml: -------------------------------------------------------------------------------- 1 | name: Close Generated PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-generated-pr.yml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/js-test-and-release.yml: -------------------------------------------------------------------------------- 1 | name: test & maybe release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | id-token: write 13 | packages: write 14 | pull-requests: write 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | js-test-and-release: 22 | uses: ipdxco/unified-github-workflows/.github/workflows/js-test-and-release.yml@v1.0 23 | secrets: 24 | DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} 25 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 26 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 27 | UCI_GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN }} 28 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | jobs: 13 | stale: 14 | uses: ipdxco/unified-github-workflows/.github/workflows/reusable-stale-issue.yml@v1 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .nyc_output 4 | coverage 5 | .coverage 6 | dist 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [10.2.5](https://github.com/ipld/js-dag-json/compare/v10.2.4...v10.2.5) (2025-05-22) 2 | 3 | ### Bug Fixes 4 | 5 | * address lint errors ([b288139](https://github.com/ipld/js-dag-json/commit/b288139ba8393d6e72e85017186f0f89deeb66ad)) 6 | 7 | ### Trivial Changes 8 | 9 | * ignore package-lock ([1f84813](https://github.com/ipld/js-dag-json/commit/1f84813e8f2e379f297b2e44b604dbc258d4a1b3)) 10 | * remove package-lock.json ([f904f26](https://github.com/ipld/js-dag-json/commit/f904f26718cbfbfee2d8d8454a845e3dddfa6760)) 11 | 12 | ### Dependencies 13 | 14 | * **dev:** bump aegir from 46.0.5 to 47.0.10 ([655d471](https://github.com/ipld/js-dag-json/commit/655d47104728f98b72c911dd518d7d6cc2c01705)) 15 | 16 | ## [10.2.4](https://github.com/ipld/js-dag-json/compare/v10.2.3...v10.2.4) (2025-05-08) 17 | 18 | ### Dependencies 19 | 20 | * **dev:** bump aegir from 45.2.1 to 46.0.0 ([8a67347](https://github.com/ipld/js-dag-json/commit/8a67347f7d79c365c375f31a0ddc691d46784489)) 21 | 22 | ## [10.2.3](https://github.com/ipld/js-dag-json/compare/v10.2.2...v10.2.3) (2024-10-29) 23 | 24 | ### Dependencies 25 | 26 | * **dev:** bump aegir from 44.1.4 to 45.0.1 ([250d25d](https://github.com/ipld/js-dag-json/commit/250d25d8f00cfea36b4f79f5bcc2b609bc54592d)) 27 | 28 | ## [10.2.2](https://github.com/ipld/js-dag-json/compare/v10.2.1...v10.2.2) (2024-06-24) 29 | 30 | ### Dependencies 31 | 32 | * **dev:** bump aegir from 43.0.3 to 44.0.0 ([c69a787](https://github.com/ipld/js-dag-json/commit/c69a787a81af7e40b19b5027f496277145b8c1e6)) 33 | 34 | ## [10.2.1](https://github.com/ipld/js-dag-json/compare/v10.2.0...v10.2.1) (2024-06-01) 35 | 36 | 37 | ### Dependencies 38 | 39 | * **dev:** bump aegir from 42.2.11 to 43.0.1 ([6c660c2](https://github.com/ipld/js-dag-json/commit/6c660c2ce83c06530b1cf18160dcbd56e149de21)) 40 | 41 | ## [10.2.0](https://github.com/ipld/js-dag-json/compare/v10.1.7...v10.2.0) (2024-02-16) 42 | 43 | 44 | ### Features 45 | 46 | * support decoding ArrayBuffers ([#127](https://github.com/ipld/js-dag-json/issues/127)) ([1e6cc99](https://github.com/ipld/js-dag-json/commit/1e6cc99fa3a7d6c7c7a43597a28ff66be35e2eb0)) 47 | 48 | ## [10.1.7](https://github.com/ipld/js-dag-json/compare/v10.1.6...v10.1.7) (2024-01-10) 49 | 50 | 51 | ### Dependencies 52 | 53 | * **dev:** bump aegir from 41.3.5 to 42.1.0 ([20a7afe](https://github.com/ipld/js-dag-json/commit/20a7afebf7dd50b7d537733582be660b3b51a945)) 54 | 55 | ## [10.1.6](https://github.com/ipld/js-dag-json/compare/v10.1.5...v10.1.6) (2023-12-30) 56 | 57 | 58 | ### Dependencies 59 | 60 | * bump multiformats from 12.1.3 to 13.0.0 ([#124](https://github.com/ipld/js-dag-json/issues/124)) ([aa07066](https://github.com/ipld/js-dag-json/commit/aa07066ed36bf9a8d1956b07f1880860b3709af2)) 61 | 62 | ## [10.1.5](https://github.com/ipld/js-dag-json/compare/v10.1.4...v10.1.5) (2023-10-03) 63 | 64 | 65 | ### Dependencies 66 | 67 | * **dev:** bump aegir from 40.0.13 to 41.0.0 ([9ca7d3b](https://github.com/ipld/js-dag-json/commit/9ca7d3b426105562b780a707efd06072ba7a3e0c)) 68 | 69 | ## [10.1.4](https://github.com/ipld/js-dag-json/compare/v10.1.3...v10.1.4) (2023-09-12) 70 | 71 | 72 | ### Trivial Changes 73 | 74 | * add or force update .github/workflows/js-test-and-release.yml ([043a680](https://github.com/ipld/js-dag-json/commit/043a680ebc0b56b21d57fcfcf9b87bd780a1924b)) 75 | * delete templates [skip ci] ([#117](https://github.com/ipld/js-dag-json/issues/117)) ([94ee02b](https://github.com/ipld/js-dag-json/commit/94ee02b09482ca0a471c0f4220df0601e2729348)) 76 | 77 | 78 | ### Dependencies 79 | 80 | * bump cborg from 2.0.5 to 4.0.0 ([29452a4](https://github.com/ipld/js-dag-json/commit/29452a479fce2b139c84a4ae65d6b2ed29d59f1a)) 81 | 82 | ## [10.1.3](https://github.com/ipld/js-dag-json/compare/v10.1.2...v10.1.3) (2023-08-08) 83 | 84 | 85 | ### Dependencies 86 | 87 | * **dev:** bump aegir from 39.0.13 to 40.0.8 ([363da2a](https://github.com/ipld/js-dag-json/commit/363da2a9c9ce9739cae35add8a74c36b5ed4a836)) 88 | 89 | ## [10.1.2](https://github.com/ipld/js-dag-json/compare/v10.1.1...v10.1.2) (2023-06-19) 90 | 91 | 92 | ### Dependencies 93 | 94 | * bump multiformats from 11.0.2 to 12.0.1 ([8db6696](https://github.com/ipld/js-dag-json/commit/8db669695f8c1f213c29536c3f57c77cb57d76c8)) 95 | 96 | ## [10.1.1](https://github.com/ipld/js-dag-json/compare/v10.1.0...v10.1.1) (2023-06-14) 97 | 98 | 99 | ### Dependencies 100 | 101 | * bump cborg from 1.10.2 to 2.0.1 ([91c3e3f](https://github.com/ipld/js-dag-json/commit/91c3e3fe1ad813b3a804e846a25be7a4d5fed7eb)) 102 | 103 | ## [10.1.0](https://github.com/ipld/js-dag-json/compare/v10.0.1...v10.1.0) (2023-05-23) 104 | 105 | 106 | ### Features 107 | 108 | * TypedArray type conversions (and address other new lint complaints) ([94bd7ef](https://github.com/ipld/js-dag-json/commit/94bd7ef76b3effefbd12f4d2be2fe37a100ffa1e)) 109 | 110 | 111 | ### Dependencies 112 | 113 | * **dev:** bump aegir from 38.1.8 to 39.0.7 ([71b54c6](https://github.com/ipld/js-dag-json/commit/71b54c6ca38ad6dbc90eb5f11f1340ea97031750)) 114 | 115 | ## [10.0.1](https://github.com/ipld/js-dag-json/compare/v10.0.0...v10.0.1) (2023-02-08) 116 | 117 | 118 | ### Dependencies 119 | 120 | * **dev:** bump aegir from 37.12.1 to 38.1.2 ([7385c6e](https://github.com/ipld/js-dag-json/commit/7385c6e747a13baae885501f3773cd041e914027)) 121 | 122 | ## [10.0.0](https://github.com/ipld/js-dag-json/compare/v9.1.1...v10.0.0) (2023-01-06) 123 | 124 | 125 | ### ⚠ BREAKING CHANGES 126 | 127 | * reject duplicate map keys 128 | 129 | ### Bug Fixes 130 | 131 | * reject duplicate map keys ([65a37bc](https://github.com/ipld/js-dag-json/commit/65a37bc17fd8cea11b36d6e30dcd6d16df462884)) 132 | 133 | ## [9.1.1](https://github.com/ipld/js-dag-json/compare/v9.1.0...v9.1.1) (2023-01-03) 134 | 135 | 136 | ### Dependencies 137 | 138 | * bump multiformats from 10.0.3 to 11.0.0 ([ba9c3a4](https://github.com/ipld/js-dag-json/commit/ba9c3a4fe43e5474786a2cf1d95b5375b8578177)) 139 | 140 | ## [9.1.0](https://github.com/ipld/js-dag-json/compare/v9.0.1...v9.1.0) (2022-12-19) 141 | 142 | 143 | ### Features 144 | 145 | * export parse / stringify functions ([#88](https://github.com/ipld/js-dag-json/issues/88)) ([1ae3f70](https://github.com/ipld/js-dag-json/commit/1ae3f708a624d5aaa5548a3360d7ccd8004e46e5)) 146 | 147 | ## [9.0.1](https://github.com/ipld/js-dag-json/compare/v9.0.0...v9.0.1) (2022-10-24) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * bring back asCID check for backward compat with multiformats@<10 ([#83](https://github.com/ipld/js-dag-json/issues/83)) ([fe19208](https://github.com/ipld/js-dag-json/commit/fe19208aef881769470d5e9730ba434a3076346e)) 153 | 154 | ## [9.0.0](https://github.com/ipld/js-dag-json/compare/v8.0.11...v9.0.0) (2022-10-19) 155 | 156 | 157 | ### ⚠ BREAKING CHANGES 158 | 159 | * publish as esm-only (#80) 160 | 161 | ### Features 162 | 163 | * publish as esm-only ([#80](https://github.com/ipld/js-dag-json/issues/80)) ([131ea8e](https://github.com/ipld/js-dag-json/commit/131ea8e91fba1845ff755f8e8e6672fdf0919ca3)) 164 | 165 | 166 | ### Trivial Changes 167 | 168 | * **no-release:** bump actions/setup-node from 3.4.1 to 3.5.0 ([#76](https://github.com/ipld/js-dag-json/issues/76)) ([30578b4](https://github.com/ipld/js-dag-json/commit/30578b4f3dc7f2d3b1517ba69965115d17d74e16)) 169 | * **no-release:** bump actions/setup-node from 3.5.0 to 3.5.1 ([#79](https://github.com/ipld/js-dag-json/issues/79)) ([a59b710](https://github.com/ipld/js-dag-json/commit/a59b710d3585ea6f07cbc4890a1c4d100a8bec4e)) 170 | * **no-release:** bump ipld-garbage from 4.0.10 to 5.0.0 ([#72](https://github.com/ipld/js-dag-json/issues/72)) ([6e428e1](https://github.com/ipld/js-dag-json/commit/6e428e1ad953495c2c6546b020faeb898ab812d7)) 171 | * update project config ([cf08c02](https://github.com/ipld/js-dag-json/commit/cf08c02c1ada814e43c34c6df24d464718d581d8)) 172 | 173 | ## [8.0.11](https://github.com/ipld/js-dag-json/compare/v8.0.10...v8.0.11) (2022-08-27) 174 | 175 | 176 | ### Trivial Changes 177 | 178 | * **deps-dev:** bump typescript from 4.7.4 to 4.8.2 ([#75](https://github.com/ipld/js-dag-json/issues/75)) ([fbd68b9](https://github.com/ipld/js-dag-json/commit/fbd68b92b6169f1bb27e3f05befe80827de752d1)) 179 | * **no-release:** bump actions/setup-node from 3.2.0 to 3.3.0 ([#71](https://github.com/ipld/js-dag-json/issues/71)) ([26cd28a](https://github.com/ipld/js-dag-json/commit/26cd28a6bfe8a895b1c102587402dfb558ae30e5)) 180 | * **no-release:** bump actions/setup-node from 3.3.0 to 3.4.0 ([#73](https://github.com/ipld/js-dag-json/issues/73)) ([4734dec](https://github.com/ipld/js-dag-json/commit/4734dec409786ec00479d3988a6d471f20b348cd)) 181 | * **no-release:** bump actions/setup-node from 3.4.0 to 3.4.1 ([#74](https://github.com/ipld/js-dag-json/issues/74)) ([546502c](https://github.com/ipld/js-dag-json/commit/546502ca9fc03ae0799a9b47c999c66612ea2035)) 182 | 183 | ### [8.0.10](https://github.com/ipld/js-dag-json/compare/v8.0.9...v8.0.10) (2022-05-25) 184 | 185 | 186 | ### Trivial Changes 187 | 188 | * **deps-dev:** bump typescript from 4.6.4 to 4.7.2 ([645255c](https://github.com/ipld/js-dag-json/commit/645255c916104dc5129f1d701eac40119bebf996)) 189 | * **no-release:** bump actions/setup-node from 3.1.0 to 3.1.1 ([#64](https://github.com/ipld/js-dag-json/issues/64)) ([9e3eb91](https://github.com/ipld/js-dag-json/commit/9e3eb91079105d435129de7bf364d75f60dfbb91)) 190 | * **no-release:** bump actions/setup-node from 3.1.1 to 3.2.0 ([#69](https://github.com/ipld/js-dag-json/issues/69)) ([46bf66d](https://github.com/ipld/js-dag-json/commit/46bf66d1d9f6e03fd3e051c61abb9f7625b1c664)) 191 | * **no-release:** bump mocha from 9.2.2 to 10.0.0 ([#66](https://github.com/ipld/js-dag-json/issues/66)) ([cf98d70](https://github.com/ipld/js-dag-json/commit/cf98d701a650baf50ddbe30d777aad1c71ec8867)) 192 | * **no-release:** bump polendina from 2.0.15 to 3.0.0 ([#67](https://github.com/ipld/js-dag-json/issues/67)) ([3993975](https://github.com/ipld/js-dag-json/commit/3993975dff22b013664348caf94df83e3976a8d5)) 193 | * **no-release:** bump polendina from 3.0.0 to 3.1.0 ([#68](https://github.com/ipld/js-dag-json/issues/68)) ([3724268](https://github.com/ipld/js-dag-json/commit/37242680dde12d83f0de38d181312298f80469d8)) 194 | * **no-release:** bump standard from 16.0.4 to 17.0.0 ([#65](https://github.com/ipld/js-dag-json/issues/65)) ([80dcb64](https://github.com/ipld/js-dag-json/commit/80dcb64ef7502d1b353707d9e58fc9fe939cc263)) 195 | 196 | ### [8.0.9](https://github.com/ipld/js-dag-json/compare/v8.0.8...v8.0.9) (2022-04-05) 197 | 198 | 199 | ### Bug Fixes 200 | 201 | * typo in the readme ([#63](https://github.com/ipld/js-dag-json/issues/63)) ([1168651](https://github.com/ipld/js-dag-json/commit/1168651de0b0977b369faaac0a39495eb39e63b1)) 202 | 203 | 204 | ### Trivial Changes 205 | 206 | * **no-release:** bump actions/setup-node from 3.0.0 to 3.1.0 ([#62](https://github.com/ipld/js-dag-json/issues/62)) ([67c71d6](https://github.com/ipld/js-dag-json/commit/67c71d6a5f60d202c5dd4ace5b3766d4c812f3c6)) 207 | 208 | ### [8.0.8](https://github.com/ipld/js-dag-json/compare/v8.0.7...v8.0.8) (2022-03-02) 209 | 210 | 211 | ### Trivial Changes 212 | 213 | * **deps-dev:** bump typescript from 4.5.5 to 4.6.2 ([#61](https://github.com/ipld/js-dag-json/issues/61)) ([6b2d04f](https://github.com/ipld/js-dag-json/commit/6b2d04fd6a3429f5e10adc7f3d8094785be6a08a)) 214 | * **no-release:** bump actions/checkout from 2.4.0 to 3 ([#60](https://github.com/ipld/js-dag-json/issues/60)) ([2e69f96](https://github.com/ipld/js-dag-json/commit/2e69f965c08882b3a31db52341662d8ca51582f1)) 215 | * **no-release:** bump actions/setup-node from 2.5.0 to 2.5.1 ([#57](https://github.com/ipld/js-dag-json/issues/57)) ([fd88425](https://github.com/ipld/js-dag-json/commit/fd884252f6cf655234f4bd0323140cc1394adf87)) 216 | * **no-release:** bump actions/setup-node from 2.5.1 to 3.0.0 ([#59](https://github.com/ipld/js-dag-json/issues/59)) ([91d068c](https://github.com/ipld/js-dag-json/commit/91d068cbfa1dc621fdf65f5871da54458a55c5f4)) 217 | 218 | ### [8.0.7](https://github.com/ipld/js-dag-json/compare/v8.0.6...v8.0.7) (2021-12-10) 219 | 220 | 221 | ### Bug Fixes 222 | 223 | * bad double-release ([22fc1fe](https://github.com/ipld/js-dag-json/commit/22fc1fee8a494290670919b403cf2cc6ad0a14d6)) 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is dual licensed under MIT and Apache-2.0. 2 | 3 | MIT: https://www.opensource.org/licenses/mit 4 | Apache-2.0: https://www.apache.org/licenses/license-2.0 5 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 2 | 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @ipld/dag-json 2 | 3 | [![codecov](https://img.shields.io/codecov/c/github/ipld/js-dag-json.svg?style=flat-square)](https://codecov.io/gh/ipld/js-dag-json) 4 | [![CI](https://img.shields.io/github/workflow/status/ipld/js-dag-json/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipld/js-dag-json/actions/workflows/js-test-and-release.yml) 5 | 6 | > JS implementation of DAG-JSON 7 | 8 | ## Table of contents 9 | 10 | - [Install](#install) 11 | - [Example](#example) 12 | - [Usage](#usage) 13 | - [License](#license) 14 | - [Contribute](#contribute) 15 | 16 | ## Install 17 | 18 | ```console 19 | $ npm i @ipld/dag-json 20 | ``` 21 | 22 | ## Example 23 | 24 | ```javascript 25 | import { encode, decode } from '@ipld/dag-json' 26 | import { CID } from 'multiformats' 27 | 28 | const obj = { 29 | x: 1, 30 | /* CID instances are encoded as links */ 31 | y: [2, 3, CID.parse('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4')], 32 | z: { 33 | a: CID.parse('QmaozNR7DZHQK1ZcU9p7QdrshMvXqWK6gpu5rmrkPdT3L4'), 34 | b: null, 35 | c: 'string' 36 | } 37 | } 38 | 39 | let data = encode(obj) 40 | let decoded = decode(data) 41 | decoded.y[0] // 2 42 | CID.asCID(decoded.z.a) // cid instance 43 | ``` 44 | 45 | ## Usage 46 | 47 | `@ipld/dag-json` is designed to be used within multiformats but can be used separately. `encode()`, `decode()` are available as exports, as are `name` and `code` to match with the corresponding DAG-JSON [multicodec](https://github.com/multiformats/multicodec/). 48 | 49 | ## License 50 | 51 | Licensed under either of 52 | 53 | - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / ) 54 | - MIT ([LICENSE-MIT](LICENSE-MIT) / ) 55 | 56 | ## Contribute 57 | 58 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ipld/dag-json", 3 | "version": "10.2.5", 4 | "description": "JS implementation of DAG-JSON", 5 | "author": "Rod (http://r.va.gg/)", 6 | "license": "Apache-2.0 OR MIT", 7 | "homepage": "https://github.com/ipld/js-dag-json#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ipld/js-dag-json.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/ipld/js-dag-json/issues" 14 | }, 15 | "keywords": [ 16 | "ipfs", 17 | "ipld", 18 | "multiformats" 19 | ], 20 | "engines": { 21 | "node": ">=16.0.0", 22 | "npm": ">=7.0.0" 23 | }, 24 | "type": "module", 25 | "types": "./dist/src/index.d.ts", 26 | "typesVersions": { 27 | "*": { 28 | "*": [ 29 | "*", 30 | "dist/*", 31 | "dist/src/*", 32 | "dist/src/*/index" 33 | ], 34 | "src/*": [ 35 | "*", 36 | "dist/*", 37 | "dist/src/*", 38 | "dist/src/*/index" 39 | ] 40 | } 41 | }, 42 | "files": [ 43 | "src", 44 | "dist", 45 | "!dist/test", 46 | "!**/*.tsbuildinfo" 47 | ], 48 | "exports": { 49 | ".": { 50 | "types": "./dist/src/index.d.ts", 51 | "import": "./src/index.js" 52 | } 53 | }, 54 | "eslintConfig": { 55 | "extends": "ipfs", 56 | "parserOptions": { 57 | "sourceType": "module" 58 | }, 59 | "ignorePatterns": [ 60 | "test/ts-use" 61 | ] 62 | }, 63 | "release": { 64 | "branches": [ 65 | "master" 66 | ], 67 | "plugins": [ 68 | [ 69 | "@semantic-release/commit-analyzer", 70 | { 71 | "preset": "conventionalcommits", 72 | "releaseRules": [ 73 | { 74 | "breaking": true, 75 | "release": "major" 76 | }, 77 | { 78 | "revert": true, 79 | "release": "patch" 80 | }, 81 | { 82 | "type": "feat", 83 | "release": "minor" 84 | }, 85 | { 86 | "type": "fix", 87 | "release": "patch" 88 | }, 89 | { 90 | "type": "docs", 91 | "release": "patch" 92 | }, 93 | { 94 | "type": "test", 95 | "release": "patch" 96 | }, 97 | { 98 | "type": "deps", 99 | "release": "patch" 100 | }, 101 | { 102 | "scope": "no-release", 103 | "release": false 104 | } 105 | ] 106 | } 107 | ], 108 | [ 109 | "@semantic-release/release-notes-generator", 110 | { 111 | "preset": "conventionalcommits", 112 | "presetConfig": { 113 | "types": [ 114 | { 115 | "type": "feat", 116 | "section": "Features" 117 | }, 118 | { 119 | "type": "fix", 120 | "section": "Bug Fixes" 121 | }, 122 | { 123 | "type": "chore", 124 | "section": "Trivial Changes" 125 | }, 126 | { 127 | "type": "docs", 128 | "section": "Documentation" 129 | }, 130 | { 131 | "type": "deps", 132 | "section": "Dependencies" 133 | }, 134 | { 135 | "type": "test", 136 | "section": "Tests" 137 | } 138 | ] 139 | } 140 | } 141 | ], 142 | "@semantic-release/changelog", 143 | "@semantic-release/npm", 144 | "@semantic-release/github", 145 | [ 146 | "@semantic-release/git", 147 | { 148 | "assets": [ 149 | "CHANGELOG.md", 150 | "package.json" 151 | ] 152 | } 153 | ] 154 | ] 155 | }, 156 | "scripts": { 157 | "clean": "aegir clean", 158 | "lint": "aegir lint", 159 | "build": "aegir build", 160 | "release": "aegir release", 161 | "test": "npm run lint && aegir test", 162 | "test:ts": "npm run test --prefix test/ts-use", 163 | "test:node": "aegir test -t node --cov", 164 | "test:chrome": "aegir test -t browser --cov", 165 | "test:chrome-webworker": "aegir test -t webworker", 166 | "test:firefox": "aegir test -t browser -- --browser firefox", 167 | "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", 168 | "test:electron-main": "aegir test -t electron-main", 169 | "dep-check": "aegir dep-check" 170 | }, 171 | "dependencies": { 172 | "cborg": "^4.0.0", 173 | "multiformats": "^13.1.0" 174 | }, 175 | "devDependencies": { 176 | "@ipld/garbage": "^6.0.0", 177 | "aegir": "^47.0.10" 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint max-depth: ["error", 7] */ 2 | import { Token, Type } from 'cborg' 3 | import * as cborgJson from 'cborg/json' 4 | import { CID } from 'multiformats' 5 | import { base64 } from 'multiformats/bases/base64' 6 | 7 | /** 8 | * @template T 9 | * @typedef {import('multiformats/codecs/interface').ByteView} ByteView 10 | */ 11 | /** 12 | * @template T 13 | * @typedef {import('multiformats/codecs/interface').ArrayBufferView} ArrayBufferView 14 | */ 15 | /** 16 | * @template T 17 | * @typedef {import('multiformats').ToString} ToString 18 | */ 19 | /** 20 | * @typedef {import('cborg/interface').DecodeTokenizer} DecodeTokenizer 21 | */ 22 | 23 | /** 24 | * @template T 25 | * @param {ByteView | ArrayBufferView} buf 26 | * @returns {ByteView} 27 | */ 28 | function toByteView (buf) { 29 | if (buf instanceof ArrayBuffer) { 30 | return new Uint8Array(buf, 0, buf.byteLength) 31 | } 32 | 33 | return buf 34 | } 35 | 36 | /** 37 | * cidEncoder will receive all Objects during encode, it needs to filter out 38 | * anything that's not a CID and return `null` for that so it's encoded as 39 | * normal. Encoding a CID means replacing it with a `{"/":"}` 40 | * object as per the DAG-JSON spec. 41 | * 42 | * @param {any} obj 43 | * @returns {Token[]|null} 44 | */ 45 | function cidEncoder (obj) { 46 | if (obj.asCID !== obj && obj['/'] !== obj.bytes) { 47 | return null // any other kind of object 48 | } 49 | const cid = CID.asCID(obj) 50 | /* c8 ignore next 4 */ 51 | // very unlikely case, and it'll probably throw a recursion error in cborg 52 | if (!cid) { 53 | return null 54 | } 55 | const cidString = cid.toString() 56 | 57 | return [ 58 | new Token(Type.map, Infinity, 1), 59 | new Token(Type.string, '/', 1), // key 60 | new Token(Type.string, cidString, cidString.length), // value 61 | new Token(Type.break, undefined, 1) 62 | ] 63 | } 64 | 65 | /** 66 | * bytesEncoder will receive all Uint8Arrays (and friends) during encode, it 67 | * needs to replace it with a `{"/":{"bytes":"Base64ByteString"}}` object as 68 | * per the DAG-JSON spec. 69 | * 70 | * @param {Uint8Array} bytes 71 | * @returns {Token[]|null} 72 | */ 73 | function bytesEncoder (bytes) { 74 | const bytesString = base64.encode(bytes).slice(1) // no mbase prefix 75 | return [ 76 | new Token(Type.map, Infinity, 1), 77 | new Token(Type.string, '/', 1), // key 78 | new Token(Type.map, Infinity, 1), // value 79 | new Token(Type.string, 'bytes', 5), // inner key 80 | new Token(Type.string, bytesString, bytesString.length), // inner value 81 | new Token(Type.break, undefined, 1), 82 | new Token(Type.break, undefined, 1) 83 | ] 84 | } 85 | 86 | /** 87 | * taBytesEncoder wraps bytesEncoder() but for the more exotic typed arrays so 88 | * that we access the underlying ArrayBuffer data 89 | * 90 | * @param {Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|Uint8ClampedArray|BigInt64Array|BigUint64Array} obj 91 | * @returns {Token[]|null} 92 | */ 93 | function taBytesEncoder (obj) { 94 | return bytesEncoder(new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength)) 95 | } 96 | 97 | /** 98 | * abBytesEncoder wraps bytesEncoder() but for plain ArrayBuffers 99 | * 100 | * @param {ArrayBuffer} ab 101 | * @returns {Token[]|null} 102 | */ 103 | function abBytesEncoder (ab) { 104 | return bytesEncoder(new Uint8Array(ab)) 105 | } 106 | 107 | // eslint-disable-next-line jsdoc/require-returns-check 108 | /** 109 | * Intercept all `undefined` values from an object walk and reject the entire 110 | * object if we find one. 111 | * 112 | * @returns {null} 113 | */ 114 | function undefinedEncoder () { 115 | throw new Error('`undefined` is not supported by the IPLD Data Model and cannot be encoded') 116 | } 117 | 118 | /** 119 | * Intercept all `number` values from an object walk and reject the entire 120 | * object if we find something that doesn't fit the IPLD data model (NaN & 121 | * Infinity). 122 | * 123 | * @param {number} num 124 | * @returns {null} 125 | */ 126 | function numberEncoder (num) { 127 | if (Number.isNaN(num)) { 128 | throw new Error('`NaN` is not supported by the IPLD Data Model and cannot be encoded') 129 | } 130 | if (num === Infinity || num === -Infinity) { 131 | throw new Error('`Infinity` and `-Infinity` is not supported by the IPLD Data Model and cannot be encoded') 132 | } 133 | return null // process with standard number encoder 134 | } 135 | 136 | const encodeOptions = { 137 | typeEncoders: { 138 | Object: cidEncoder, 139 | Buffer: bytesEncoder, 140 | Uint8Array: bytesEncoder, 141 | Int8Array: taBytesEncoder, 142 | Uint16Array: taBytesEncoder, 143 | Int16Array: taBytesEncoder, 144 | Uint32Array: taBytesEncoder, 145 | Int32Array: taBytesEncoder, 146 | Float32Array: taBytesEncoder, 147 | Float64Array: taBytesEncoder, 148 | Uint8ClampedArray: taBytesEncoder, 149 | BigInt64Array: taBytesEncoder, 150 | BigUint64Array: taBytesEncoder, 151 | DataView: taBytesEncoder, 152 | ArrayBuffer: abBytesEncoder, 153 | undefined: undefinedEncoder, 154 | number: numberEncoder 155 | } 156 | } 157 | 158 | /** 159 | * @implements {DecodeTokenizer} 160 | */ 161 | class DagJsonTokenizer extends cborgJson.Tokenizer { 162 | /** 163 | * @param {Uint8Array} data 164 | * @param {object} [options] 165 | */ 166 | constructor (data, options) { 167 | super(data, options) 168 | /** @type {Token[]} */ 169 | this.tokenBuffer = [] 170 | } 171 | 172 | /** 173 | * @returns {boolean} 174 | */ 175 | done () { 176 | return this.tokenBuffer.length === 0 && super.done() 177 | } 178 | 179 | /** 180 | * @returns {Token} 181 | */ 182 | _next () { 183 | if (this.tokenBuffer.length > 0) { 184 | // @ts-ignore https://github.com/Microsoft/TypeScript/issues/30406 185 | return this.tokenBuffer.pop() 186 | } 187 | return super.next() 188 | } 189 | 190 | /** 191 | * Implements rules outlined in https://github.com/ipld/specs/pull/356 192 | * 193 | * @returns {Token} 194 | */ 195 | next () { 196 | const token = this._next() 197 | 198 | if (token.type === Type.map) { 199 | const keyToken = this._next() 200 | if (keyToken.type === Type.string && keyToken.value === '/') { 201 | const valueToken = this._next() 202 | if (valueToken.type === Type.string) { // *must* be a CID 203 | const breakToken = this._next() // swallow the end-of-map token 204 | if (breakToken.type !== Type.break) { 205 | throw new Error('Invalid encoded CID form') 206 | } 207 | this.tokenBuffer.push(valueToken) // CID.parse will pick this up after our tag token 208 | return new Token(Type.tag, 42, 0) 209 | } 210 | if (valueToken.type === Type.map) { 211 | const innerKeyToken = this._next() 212 | if (innerKeyToken.type === Type.string && innerKeyToken.value === 'bytes') { 213 | const innerValueToken = this._next() 214 | if (innerValueToken.type === Type.string) { // *must* be Bytes 215 | for (let i = 0; i < 2; i++) { 216 | const breakToken = this._next() // swallow two end-of-map tokens 217 | if (breakToken.type !== Type.break) { 218 | throw new Error('Invalid encoded Bytes form') 219 | } 220 | } 221 | const bytes = base64.decode(`m${innerValueToken.value}`) 222 | return new Token(Type.bytes, bytes, innerValueToken.value.length) 223 | } 224 | this.tokenBuffer.push(innerValueToken) // bail 225 | } 226 | this.tokenBuffer.push(innerKeyToken) // bail 227 | } 228 | this.tokenBuffer.push(valueToken) // bail 229 | } 230 | this.tokenBuffer.push(keyToken) // bail 231 | } 232 | return token 233 | } 234 | } 235 | 236 | const decodeOptions = { 237 | allowIndefinite: false, 238 | allowUndefined: false, 239 | allowNaN: false, 240 | allowInfinity: false, 241 | allowBigInt: true, // this will lead to BigInt for ints outside of 242 | // safe-integer range, which may surprise users 243 | strict: true, 244 | useMaps: false, 245 | rejectDuplicateMapKeys: true, 246 | /** @type {import('cborg').TagDecoder[]} */ 247 | tags: [] 248 | } 249 | 250 | // we're going to get TAG(42)STRING("bafy...") from the tokenizer so we only need 251 | // to deal with the STRING("bafy...") at this point 252 | decodeOptions.tags[42] = CID.parse 253 | 254 | export const name = 'dag-json' 255 | export const code = 0x0129 256 | 257 | /** 258 | * @template T 259 | * @param {T} node 260 | * @returns {ByteView} 261 | */ 262 | export const encode = (node) => cborgJson.encode(node, encodeOptions) 263 | 264 | /** 265 | * @template T 266 | * @param {ByteView | ArrayBufferView} data 267 | * @returns {T} 268 | */ 269 | export const decode = (data) => { 270 | const buf = toByteView(data) 271 | // the tokenizer is stateful so we need a single instance of it 272 | const options = Object.assign(decodeOptions, { tokenizer: new DagJsonTokenizer(buf, decodeOptions) }) 273 | return cborgJson.decode(buf, options) 274 | } 275 | 276 | /** 277 | * @template T 278 | * @param {T} node 279 | * @returns {ToString} 280 | */ 281 | export const format = (node) => utf8Decoder.decode(encode(node)) 282 | export { format as stringify } 283 | const utf8Decoder = new TextDecoder() 284 | 285 | /** 286 | * @template T 287 | * @param {ToString} data 288 | * @returns {T} 289 | */ 290 | export const parse = (data) => decode(utf8Encoder.encode(data)) 291 | const utf8Encoder = new TextEncoder() 292 | -------------------------------------------------------------------------------- /test/test-basics.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | import { garbage } from '@ipld/garbage' 3 | import { assert } from 'aegir/chai' 4 | import { bytes, CID } from 'multiformats' 5 | import { base64 } from 'multiformats/bases/base64' 6 | import { encode, decode, stringify, parse } from '../src/index.js' 7 | 8 | const same = assert.deepStrictEqual 9 | 10 | const recode = (/** @type {Uint8Array} */ byts) => encode(decode(byts)) 11 | 12 | const link = CID.parse('bafyreifepiu23okq5zuyvyhsoiazv2icw2van3s7ko6d3ixl5jx2yj2yhu') 13 | 14 | describe('basic dag-json', () => { 15 | it('encode decode', () => { 16 | const byts = encode({ hello: 'world' }) 17 | same(JSON.parse(bytes.toString(recode(byts))), { hello: 'world' }) 18 | const o = { link, byts: bytes.fromString('asdf'), n: null, o: {} } 19 | const byts2 = encode(o) 20 | same(decode(byts2), o) 21 | same(bytes.isBinary(decode(byts2).byts), true) 22 | }) 23 | 24 | it('encode decode 2', () => { 25 | // mirrors a go-ipld-prime test, but with sorted keys 26 | const obj = { plain: 'olde string', bytes: utf8Encode('deadbeef') } 27 | const expected = '{"bytes":{"/":{"bytes":"ZGVhZGJlZWY"}},"plain":"olde string"}' 28 | const byts = encode(obj) 29 | same(JSON.parse(bytes.toString(recode(byts))), JSON.parse(expected)) 30 | same(bytes.toString(recode(byts)), expected) 31 | }) 32 | 33 | it('encode decode with ArrayBuffer', () => { 34 | const byts = encode({ hello: 'world' }) 35 | same(JSON.parse(bytes.toString(recode(byts))), { hello: 'world' }) 36 | const o = { link, byts: bytes.fromString('asdf'), n: null, o: {} } 37 | const byts2 = encode(o) 38 | same(decode(byts2), o) 39 | same(bytes.isBinary(decode(byts2).byts.buffer), true) 40 | }) 41 | 42 | describe('reserved space', () => { 43 | it('allow alternative types', () => { 44 | // wrong types 45 | for (const obj of [true, false, null, 1, -1, 1.1, { blip: 'bop' }, ['foo']]) { 46 | same(decode(encode({ '/': obj })), { '/': obj }) 47 | same(decode(encode({ '/': { bytes: obj } })), { '/': { bytes: obj } }) 48 | } 49 | }) 50 | 51 | it('allow specials within reserved space', () => { 52 | // can we put slash-objects within slashes? 53 | same(decode(encode({ '/': bytes.fromString('asdf') })), { '/': bytes.fromString('asdf') }) 54 | same(new TextDecoder().decode(encode({ '/': bytes.fromString('asdf') })), '{"/":{"/":{"bytes":"YXNkZg"}}}') 55 | same(decode(encode({ '/': link })), { '/': link }) 56 | same(new TextDecoder().decode(encode({ '/': link })), '{"/":{"/":"bafyreifepiu23okq5zuyvyhsoiazv2icw2van3s7ko6d3ixl5jx2yj2yhu"}}') 57 | }) 58 | 59 | it('disallow extraneous tokens', () => { 60 | // encode() shouldn't allow this but it currently does 61 | // https://github.com/ipld/js-dag-json/issues/91 should change this test 62 | // into two parts, encode and decode 63 | assert.throws(() => decode(encode({ '/': link.toString(), x: 'bip' }))) 64 | assert.throws(() => decode(encode({ '/': { bytes: 'mS7ldeA', x: 'bip' } }))) 65 | assert.throws(() => decode(encode({ '/': { bytes: 'mS7ldeA' }, x: 'bip' }))) 66 | assert.throws(() => decode(encode({ '/': { bytes: 'mS7ldeA', x: 'bip' }, bop: 'bip' }))) 67 | }) 68 | }) 69 | 70 | it('native types', () => { 71 | const flip = (/** @type {any} */ obj) => decode(encode(obj)) 72 | same(flip('test'), 'test') 73 | same(flip(null), null) 74 | same(flip(12), 12) 75 | same(flip(-1), -1) 76 | same(flip(1.2), 1.2) 77 | same(flip(true), true) 78 | same(flip(false), false) 79 | same(flip([]), []) 80 | same(flip(['asdf']), ['asdf']) 81 | same(decode(utf8Encode('10.0')), 10) 82 | same(decode(utf8Encode('[-10.0, 1.0, 0.0, 100.0]')), [-10, 1, 0, 100]) 83 | }) 84 | 85 | it('stable map key sorting', () => { 86 | const s1 = bytes.toString(encode({ a: 1, b: 2, bb: 2.2, c: 3, c_: 3.3 })) 87 | const s2 = bytes.toString(encode({ c_: 3.3, bb: 2.2, b: 2, c: 3, a: 1 })) 88 | same('{"a":1,"b":2,"bb":2.2,"c":3,"c_":3.3}', s1) 89 | same('{"a":1,"b":2,"bb":2.2,"c":3,"c_":3.3}', s2) 90 | }) 91 | 92 | it('bigints', () => { 93 | const verify = (/** @type {number | bigint} **/ inp) => { 94 | assert.strictEqual(decode(utf8Encode(String(inp))), inp) 95 | } 96 | 97 | // plain Number objects 98 | verify(0) 99 | verify(1) 100 | verify(-1) 101 | verify(Math.pow(2, 50)) 102 | verify(-Math.pow(2, 50)) 103 | verify(Number.MAX_SAFE_INTEGER) 104 | verify(-Number.MAX_SAFE_INTEGER) 105 | // should round-trip as BigInts 106 | verify(BigInt('9007199254740992')) // Number.MAX_SAFE_INTEGER+1 107 | verify(BigInt('9007199254740993')) 108 | verify(BigInt('11959030306112471731')) 109 | verify(BigInt('18446744073709551615')) // max uint64 110 | verify(BigInt('9223372036854775807')) // max int64 111 | verify(BigInt('-9007199254740992')) 112 | verify(BigInt('-9007199254740993')) 113 | verify(BigInt('-9223372036854776000')) // min int64 114 | verify(BigInt('-11959030306112471732')) 115 | verify(BigInt('-18446744073709551616')) // min -uint64 116 | }) 117 | 118 | it('error on circular references', () => { 119 | const circularObj = {} 120 | circularObj.a = circularObj 121 | assert.throws(() => encode(circularObj), /object contains circular references/) 122 | const circularArr = [circularObj] 123 | circularObj.a = circularArr 124 | assert.throws(() => encode(circularArr), /object contains circular references/) 125 | }) 126 | 127 | it('error on encoding undefined', () => { 128 | assert.throws(() => encode(undefined), /\Wundefined\W.*not supported/) 129 | const objWithUndefined = { a: 'a', b: undefined } 130 | assert.throws(() => encode(objWithUndefined), /\Wundefined\W.*not supported/) 131 | }) 132 | 133 | it('error on encoding IEEE 754 specials', () => { 134 | for (const special of [NaN, Infinity, -Infinity]) { 135 | assert.throws(() => encode(special), new RegExp(`\\W${String(special)}\\W.*not supported`)) 136 | const objWithSpecial = { a: 'a', b: special } 137 | assert.throws(() => encode(objWithSpecial), new RegExp(`\\W${String(special)}\\W.*not supported`)) 138 | const arrWithSpecial = [1, 1.1, -1, -1.1, Number.MAX_SAFE_INTEGER, special, Number.MIN_SAFE_INTEGER] 139 | assert.throws(() => encode(arrWithSpecial), new RegExp(`\\W${String(special)}\\W.*not supported`)) 140 | } 141 | }) 142 | 143 | it('fuzz serialize and deserialize with garbage', function () { 144 | // filter out fuzz garbage for objects that are disqualified by DAG-JSON rules 145 | /** @type {(obj: any) => boolean} */ 146 | const checkObj = (obj) => { 147 | if (Array.isArray(obj)) { 148 | return obj.every(checkObj) 149 | } 150 | if (obj && typeof obj === 'object') { 151 | for (const [key, value] of Object.entries(obj)) { 152 | if (key === '/') { 153 | if (typeof value === 'string') { 154 | return false 155 | } 156 | if (value && typeof value === 'object' && value.bytes !== undefined) { 157 | return false 158 | } 159 | } 160 | if (!checkObj(value)) { 161 | return false 162 | } 163 | } 164 | } 165 | return true 166 | } 167 | 168 | this.timeout(5000) 169 | for (let ii = 0; ii < 1000; ii++) { 170 | const original = garbage(300) 171 | if (!checkObj(original)) { 172 | continue 173 | } 174 | const encoded = encode(original) 175 | const decoded = decode(encoded) 176 | same(decoded, original) 177 | } 178 | }) 179 | 180 | it('serialize', () => { 181 | same(stringify({ hello: 'world' }), JSON.stringify({ hello: 'world' })) 182 | 183 | const input = { link, bytes: bytes.fromString('asdf'), n: null, o: {} } 184 | same(stringify(input), JSON.stringify({ 185 | bytes: { '/': { bytes: base64.baseEncode(input.bytes) } }, 186 | link: { '/': link.toString() }, 187 | n: null, 188 | o: {} 189 | })) 190 | 191 | const output = parse(stringify(input)) 192 | same(input, output) 193 | same(bytes.isBinary(output.bytes), true) 194 | same(CID.asCID(output.link), output.link) 195 | }) 196 | 197 | describe('typed arrays', () => { 198 | // This mirrors what ships by default in dag-cbor: https://github.com/rvagg/cborg/blob/968fdac4af22c9b22fe5b40c2a6f9bb855780a4a/test/test-2bytes.js#L121 199 | // It's an open question whether this should slip by without error or not, 200 | // for now we silently convert them to plain bytes and they all round trip 201 | // as Uint8Arrays. 202 | const cases = /** @type {{obj: ArrayBufferView, expected: string}[]} */([ 203 | { 204 | obj: Uint8Array.from([1, 2, 3]), 205 | expected: '{"/":{"bytes":"AQID"}}' 206 | }, 207 | { 208 | obj: Uint8ClampedArray.from([1, 2, 3]), 209 | expected: '{"/":{"bytes":"AQID"}}' 210 | }, 211 | { 212 | obj: Uint16Array.from([1, 2, 3]), 213 | expected: '{"/":{"bytes":"AQACAAMA"}}' 214 | }, 215 | { 216 | obj: Uint32Array.from([1, 2, 3]), 217 | expected: '{"/":{"bytes":"AQAAAAIAAAADAAAA"}}' 218 | }, 219 | { 220 | obj: Int8Array.from([1, 2, -3]), 221 | expected: '{"/":{"bytes":"AQL9"}}' 222 | }, 223 | { 224 | obj: Int16Array.from([1, 2, -3]), 225 | expected: '{"/":{"bytes":"AQACAP3/"}}' 226 | }, 227 | { 228 | obj: Int32Array.from([1, 2, -3]), 229 | expected: '{"/":{"bytes":"AQAAAAIAAAD9////"}}' 230 | }, 231 | { 232 | obj: Float32Array.from([1, 2, -3]), 233 | expected: '{"/":{"bytes":"AACAPwAAAEAAAEDA"}}' 234 | }, 235 | { 236 | obj: Float64Array.from([1, 2, -3]), 237 | expected: '{"/":{"bytes":"AAAAAAAA8D8AAAAAAAAAQAAAAAAAAAjA"}}' 238 | }, 239 | { 240 | obj: BigUint64Array.from([BigInt(1), BigInt(2), BigInt(3)]), 241 | expected: '{"/":{"bytes":"AQAAAAAAAAACAAAAAAAAAAMAAAAAAAAA"}}' 242 | }, 243 | { 244 | obj: BigInt64Array.from([BigInt(1), BigInt(2), BigInt(-3)]), 245 | expected: '{"/":{"bytes":"AQAAAAAAAAACAAAAAAAAAP3/////////"}}' 246 | }, 247 | { 248 | obj: new DataView(Uint8Array.from([1, 2, 3]).buffer), 249 | expected: '{"/":{"bytes":"AQID"}}' 250 | }, 251 | { 252 | obj: Uint8Array.from([1, 2, 3]).buffer, 253 | expected: '{"/":{"bytes":"AQID"}}' 254 | } 255 | ]) 256 | 257 | for (const testCase of cases) { 258 | it(testCase.obj.constructor.name, () => { 259 | same(utf8Decode(encode(testCase.obj)), testCase.expected) 260 | const decoded = decode(utf8Encode(testCase.expected)) 261 | assert.instanceOf(decoded, Uint8Array) 262 | if (testCase.obj instanceof ArrayBuffer) { 263 | same(decoded, new Uint8Array(testCase.obj)) 264 | } else { 265 | same(decoded, new Uint8Array(testCase.obj.buffer, testCase.obj.byteOffset, testCase.obj.byteLength)) 266 | } 267 | }) 268 | } 269 | }) 270 | 271 | it('reject duplicate map keys', () => { 272 | assert.throws(() => decode(utf8Encode('{"foo":1,"foo":2,"bar":3}')), /found repeat map key "foo"/) 273 | }) 274 | }) 275 | 276 | /** 277 | * @param {string} s 278 | * @returns {Uint8Array} 279 | */ 280 | function utf8Encode (s) { 281 | return new TextEncoder().encode(s) 282 | } 283 | 284 | /** 285 | * @param {Uint8Array} b 286 | * @returns {string} 287 | */ 288 | function utf8Decode (b) { 289 | return new TextDecoder().decode(b) 290 | } 291 | -------------------------------------------------------------------------------- /test/ts-use/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/main.js 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /test/ts-use/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-use", 3 | "private": true, 4 | "dependencies": { 5 | "@ipld/dag-json": "file:../../dist/", 6 | "multiformats": "file:../../node_modules/multiformats" 7 | }, 8 | "scripts": { 9 | "test": "npm install && npx -p typescript tsc && node src/main.js" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/ts-use/src/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { deepStrictEqual } from 'assert' 4 | import * as dagJson from '@ipld/dag-json' 5 | import { BlockEncoder, BlockDecoder, BlockCodec } from 'multiformats/codecs/interface' 6 | 7 | const main = (): void => { 8 | // make sure we have a full CodecFeature 9 | useCodecFeature(dagJson) 10 | } 11 | 12 | function useCodecFeature (codec: BlockCodec<297, any>): void { 13 | // use only as a BlockEncoder 14 | useEncoder(codec) 15 | 16 | // use only as a BlockDecoder 17 | useDecoder(codec) 18 | 19 | // use with ArrayBuffer input type 20 | useDecoderWithArrayBuffer(codec) 21 | 22 | // use as a full BlockCodec which does both BlockEncoder & BlockDecoder 23 | useBlockCodec(codec) 24 | } 25 | 26 | function useEncoder (encoder: BlockEncoder): void { 27 | deepStrictEqual(encoder.code, 297) 28 | deepStrictEqual(encoder.name, 'dag-json') 29 | deepStrictEqual(Array.from(encoder.encode('blip')), [34, 98, 108, 105, 112, 34]) 30 | console.log('[TS] ✓ { encoder: BlockEncoder }') 31 | } 32 | 33 | function useDecoder (decoder: BlockDecoder): void { 34 | deepStrictEqual(decoder.code, 297) 35 | deepStrictEqual(decoder.decode(Uint8Array.from([34, 98, 108, 105, 112, 34])), 'blip') 36 | console.log('[TS] ✓ { decoder: BlockDecoder }') 37 | } 38 | 39 | function useDecoderWithArrayBuffer (decoder: BlockDecoder): void { 40 | deepStrictEqual(decoder.code, 297) 41 | deepStrictEqual(decoder.decode(Uint8Array.from([34, 98, 108, 105, 112, 34]).buffer), 'blip') 42 | console.log('[TS] ✓ { decoder: BlockDecoder }') 43 | } 44 | 45 | function useBlockCodec (blockCodec: BlockCodec): void { 46 | deepStrictEqual(blockCodec.code, 297) 47 | deepStrictEqual(blockCodec.name, 'dag-json') 48 | deepStrictEqual(Array.from(blockCodec.encode('blip')), [34, 98, 108, 105, 112, 34]) 49 | deepStrictEqual(blockCodec.decode(Uint8Array.from([34, 98, 108, 105, 112, 34])), 'blip') 50 | console.log('[TS] ✓ {}:BlockCodec') 51 | } 52 | 53 | main() 54 | 55 | export default main 56 | -------------------------------------------------------------------------------- /test/ts-use/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "moduleResolution": "node", 5 | "noImplicitAny": true, 6 | "skipLibCheck": false, 7 | "incremental": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "aegir/src/config/tsconfig.aegir.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "emitDeclarationOnly": true 6 | }, 7 | "include": [ 8 | "src", 9 | "test" 10 | ], 11 | "exclude": [ 12 | "test/ts-use" 13 | ] 14 | } 15 | --------------------------------------------------------------------------------