├── .circleci └── config.yml ├── .codacy.yml ├── .gitignore ├── CHANGELOG.md ├── README.md ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── a-msgpack │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmrc │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── benchmark-msgpack │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── benchmark-from-msgpack-lite-data.json │ │ ├── decode-string.ts │ │ ├── encode-string.ts │ │ ├── key-decoder.ts │ │ ├── msgpack-benchmark.ts │ │ ├── package.json │ │ ├── profile-decode.ts │ │ ├── profile-encode.ts │ │ ├── profile-pointer.ts │ │ ├── profile-string.ts │ │ └── sample-large.json │ ├── index.ts │ ├── jest.config.js │ ├── last-benchmark-results.txt │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CachedKeyDecoder.ts │ │ ├── Decoder.ts │ │ ├── Encoder.ts │ │ ├── ExtData.ts │ │ ├── ExtensionCodec.ts │ │ ├── decode.ts │ │ ├── encode.ts │ │ ├── index.ts │ │ ├── neat │ │ │ ├── NeatTypes.ts │ │ │ ├── extensions.ts │ │ │ ├── index.ts │ │ │ ├── typeCreators.ts │ │ │ └── utils.ts │ │ └── utils │ │ │ ├── data.ts │ │ │ ├── int.ts │ │ │ ├── prettyByte.ts │ │ │ ├── typedArrays.ts │ │ │ └── utf8.ts │ ├── test │ │ ├── CachedKeyDecoder.spec.ts │ │ ├── ExtensionCodec.spec.ts │ │ ├── codec.spec.ts │ │ ├── codec_tests.yaml │ │ ├── decode-max-length.spec.ts │ │ ├── edge-cases.spec.ts │ │ ├── encoder-setup.js │ │ ├── neat │ │ │ ├── neatTypes.spec.ts │ │ │ └── utils.spec.ts │ │ └── utils │ │ │ ├── int.spec.ts │ │ │ └── typedArrays.spec.ts │ ├── tsconfig-build.json │ ├── tsconfig.json │ └── types │ │ ├── index.ts │ │ └── neat.ts ├── cloudvision-connector │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmrc │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── index.html │ ├── index.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── Connector.ts │ │ ├── Parser.ts │ │ ├── Wrpc.ts │ │ ├── constants.ts │ │ ├── emitter.ts │ │ ├── index.ts │ │ ├── instrumentation.ts │ │ ├── logger.ts │ │ └── utils.ts │ ├── test │ │ ├── Connector.spec.ts │ │ ├── Parser.spec.ts │ │ ├── Wrpc.spec.ts │ │ ├── emitter.spec.ts │ │ ├── external-utils.spec.ts │ │ ├── fixtures.ts │ │ ├── instrumentation.spec.ts │ │ ├── logger.spec.ts │ │ ├── utils.spec.ts │ │ └── websocket-setup.ts │ ├── tsconfig-build.json │ ├── tsconfig.json │ └── types │ │ ├── connection.ts │ │ ├── emitter.ts │ │ ├── global.d.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── notifications.ts │ │ ├── params.ts │ │ └── query.ts └── cloudvision-grpc-web │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmrc │ ├── .prettierrc.json │ ├── CHANGELOG.md │ ├── README.md │ ├── generate.sh │ ├── generated │ ├── arista │ │ └── subscriptions │ │ │ └── subscriptions.ts │ └── google │ │ └── protobuf │ │ ├── any.ts │ │ ├── api.ts │ │ ├── descriptor.ts │ │ ├── duration.ts │ │ ├── empty.ts │ │ ├── field_mask.ts │ │ ├── source_context.ts │ │ ├── struct.ts │ │ ├── timestamp.ts │ │ ├── type.ts │ │ └── wrappers.ts │ ├── index.ts │ ├── jest.config.js │ ├── package-lock.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── grpc │ │ └── index.ts │ ├── index.ts │ ├── operators.ts │ └── resource │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── operators.ts │ │ └── utils.ts │ ├── test │ ├── index.spec.ts │ └── operators.spec.ts │ ├── tsconfig-build.json │ ├── tsconfig.json │ └── types │ ├── grpc.ts │ ├── index.ts │ └── resource.ts ├── renovate.json └── tsconfig.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | node: circleci/node@5.0.3 4 | commands: 5 | configure: 6 | steps: 7 | - run: 8 | name: Git config email 9 | command: git config --global user.email "arastra.bot@gmail.com" 10 | - run: 11 | name: Git config name 12 | command: git config --global user.name "Arastra" 13 | - run: 14 | name: Git config tag push 15 | command: git config --global push.followTags true 16 | protect-branch: 17 | parameters: 18 | enforce: 19 | type: boolean 20 | default: true 21 | steps: 22 | - run: run.py --access-token $GITHUB_TOKEN --branch trunk --owner aristanetworks --repo cloudvision --enforce_admins << parameters.enforce >> 23 | parameters: 24 | deploy_version: 25 | type: boolean 26 | default: false 27 | is_build: 28 | type: boolean 29 | default: true 30 | executors: 31 | cloudvision-builder: 32 | docker: 33 | - image: circleci/node:lts 34 | jobs: 35 | build-and-test: 36 | executor: 37 | name: cloudvision-builder 38 | steps: 39 | - checkout 40 | - node/install-packages 41 | - run: npm run ci 42 | open-branch: 43 | docker: 44 | - image: rufman/branch-protection-bot:release-v1.0.0 45 | steps: 46 | - protect-branch: 47 | enforce: false 48 | protect-branch: 49 | docker: 50 | - image: rufman/branch-protection-bot:release-v1.0.0 51 | steps: 52 | - protect-branch: 53 | enforce: true 54 | publish-docs: 55 | executor: 56 | name: cloudvision-builder 57 | steps: 58 | - checkout 59 | - configure 60 | - add_ssh_keys: 61 | fingerprints: 62 | - $DEPLOY_KEY_FINGERPRINT 63 | - node/install-packages 64 | - run: npm run docs:deploy 65 | version-packages: 66 | executor: 67 | name: cloudvision-builder 68 | steps: 69 | - checkout 70 | - configure 71 | - node/install-packages 72 | - run: npm run bootstrap 73 | - run: git checkout package-lock.json 74 | - run: 75 | name: Version packages and create commit 76 | command: npm run lerna-version 77 | - run: 78 | name: Push version commit with tags to GitHub 79 | command: git push origin 80 | publish: 81 | executor: 82 | name: cloudvision-builder 83 | steps: 84 | - checkout 85 | - node/install-packages 86 | - run: git checkout package-lock.json 87 | - run: 88 | name: Publish to NPM 89 | command: | 90 | npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN 91 | npx lerna publish from-git --yes 92 | workflows: 93 | version: 2 94 | build-and-test: 95 | when: << pipeline.parameters.is_build >> 96 | jobs: 97 | - build-and-test 98 | - publish-docs: 99 | requires: 100 | - build-and-test 101 | filters: 102 | branches: 103 | only: trunk 104 | version-and-push: 105 | when: << pipeline.parameters.deploy_version >> 106 | jobs: 107 | - open-branch 108 | - version-packages: 109 | requires: 110 | - open-branch 111 | - protect-branch: 112 | requires: 113 | - version-packages 114 | tagged-deploy: 115 | jobs: 116 | - publish: 117 | filters: 118 | tags: 119 | only: /v[0-9]+(\.[0-9]+)*/ 120 | branches: 121 | ignore: /.*/ 122 | -------------------------------------------------------------------------------- /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | - "packages/cloudvision-grpc-web/generated/**" 4 | - "packages/cloudvision-connector/CHANGELOG.md" 5 | - "packages/a-msgpack/CHANGELOG.md" 6 | - "CHANGELOG.md" 7 | - "packages/a-msgpack/benchmark-msgpack/CHANGELOG.md" 8 | - "packages/cloudvision-grpc-web/CHANGELOG.md" 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | node_modules 4 | lerna-*.log 5 | npm-*.log 6 | docs 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudVision 2 | 3 | CloudVision is a network management framework supporting workload orchestration, workflow 4 | automation and telemetry. 5 | 6 | ## CloudVision Database 7 | 8 | The CloudVision Database is a large-scale distributed database for generic, semi-structured 9 | state sharing and archival. The database is a core component of CloudVision but may also be 10 | used as a platform to write versatile client applications. 11 | 12 | > Note: The CloudVision Database is undergoing rapid development and the APIs are subject 13 | > to change. 14 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "command": { 3 | "publish": { 4 | "allowBranch": "trunk", 5 | "ignoreChanges": [ 6 | "**/.eslintrc", 7 | "**/.gitignore", 8 | "**/*.md", 9 | "**/test/**" 10 | ] 11 | }, 12 | "version": { 13 | "message": "chore(release): publish %s" 14 | } 15 | }, 16 | "npmClient": "npm", 17 | "packages": [ 18 | "packages/*" 19 | ], 20 | "version": "5.0.2" 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "gh-pages": "4.0.0", 6 | "lerna": "5.6.2", 7 | "typedoc": "0.23.26", 8 | "typescript": "4.8.4" 9 | }, 10 | "scripts": { 11 | "bootstrap": "lerna bootstrap", 12 | "build": "lerna run build", 13 | "check": "lerna run check", 14 | "ci": "npm run check && npm run test && npm run build", 15 | "clean": "lerna run clean", 16 | "docs": "typedoc", 17 | "docs:deploy": "npm run docs && npm run gh-pages", 18 | "gh-pages": "gh-pages -m '[skip ci] Update Docs' -d docs", 19 | "lerna-version": "lerna version --conventional-commits --exact --no-push --yes", 20 | "postinstall": "npm run bootstrap", 21 | "test": "lerna run test:cov" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/a-msgpack/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["**/*.js"], 5 | "extends": ["arista-js"] 6 | }, 7 | { 8 | "files": ["**/*.ts"], 9 | "extends": ["arista-ts"], 10 | "parserOptions": { 11 | "project": "./tsconfig.json" 12 | }, 13 | "rules": { 14 | "no-plusplus": "off", 15 | "no-bitwise": "off" 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/a-msgpack/.gitignore: -------------------------------------------------------------------------------- 1 | .jest-cache 2 | *.log 3 | coverage 4 | dist 5 | docs 6 | es 7 | lib 8 | typings 9 | -------------------------------------------------------------------------------- /packages/a-msgpack/.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/a-msgpack/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@arista/prettier-config" 2 | -------------------------------------------------------------------------------- /packages/a-msgpack/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Arista Networks 4 | Copyright (c) 2016 Joshua Wise 5 | Copyright (c) 2015 Yusuke Kawasaki 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /packages/a-msgpack/README.md: -------------------------------------------------------------------------------- 1 | # a-msgpack 2 | 3 | MessagePack, but for Arista. This is based on the official msgpack library for JS 4 | (@msgpack/msgpack), but implements our specific NEAT protocol. 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm install a-msgpack 10 | ``` 11 | 12 | or 13 | 14 | ```bash 15 | npm install a-msgpack 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```js 21 | import { encode, decode, Codec } from 'a-msgpack'; 22 | 23 | const uint8array = msgpack.encode({ Dodgers: '#1', Astros: 'Cheaters' }, { extensionCodec: Codec }); 24 | const object = msgpack.decode(uint8array); 25 | ``` 26 | 27 | ## Browser Support 28 | 29 | In the browser, `a-msgpack` requires the 30 | [Encoding API](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API) to work a peak 31 | performance. If the Encoding API is unavailable, there is a fallback JS implementation. 32 | 33 | ## Benchmarks 34 | 35 | The lastest code benchmarks and profiling is stored in `last-benchmark-results.txt`. This also 36 | compares this implementation to other msgpack libraries. Note, that the decoding results should be 37 | comparable to @msgpack/msgpack, but encoding will be slower because NEAT requires that map keys be 38 | sorted by binary value. 39 | 40 | ## License 41 | 42 | [MIT](https://github.com/JoshuaWise/tiny-msgpack/blob/trunk/LICENSE) 43 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.0.19](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.18...benchmark-msgpack@0.0.19) (2020-04-06) 7 | 8 | **Note:** Version bump only for package benchmark-msgpack 9 | 10 | 11 | 12 | 13 | 14 | ## [0.0.18](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.17...benchmark-msgpack@0.0.18) (2020-03-30) 15 | 16 | **Note:** Version bump only for package benchmark-msgpack 17 | 18 | 19 | 20 | 21 | 22 | ## [0.0.17](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.16...benchmark-msgpack@0.0.17) (2020-03-23) 23 | 24 | **Note:** Version bump only for package benchmark-msgpack 25 | 26 | 27 | 28 | 29 | 30 | ## [0.0.16](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.15...benchmark-msgpack@0.0.16) (2020-03-20) 31 | 32 | **Note:** Version bump only for package benchmark-msgpack 33 | 34 | 35 | 36 | 37 | 38 | ## [0.0.15](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.14...benchmark-msgpack@0.0.15) (2020-03-20) 39 | 40 | **Note:** Version bump only for package benchmark-msgpack 41 | 42 | 43 | 44 | 45 | 46 | ## [0.0.14](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.13...benchmark-msgpack@0.0.14) (2020-03-19) 47 | 48 | **Note:** Version bump only for package benchmark-msgpack 49 | 50 | 51 | 52 | 53 | 54 | ## [0.0.13](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.12...benchmark-msgpack@0.0.13) (2020-03-16) 55 | 56 | **Note:** Version bump only for package benchmark-msgpack 57 | 58 | 59 | 60 | 61 | 62 | ## [0.0.12](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.11...benchmark-msgpack@0.0.12) (2020-03-09) 63 | 64 | **Note:** Version bump only for package benchmark-msgpack 65 | 66 | 67 | 68 | 69 | 70 | ## [0.0.11](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.10...benchmark-msgpack@0.0.11) (2020-03-04) 71 | 72 | **Note:** Version bump only for package benchmark-msgpack 73 | 74 | 75 | 76 | 77 | 78 | ## [0.0.10](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.9...benchmark-msgpack@0.0.10) (2020-02-26) 79 | 80 | **Note:** Version bump only for package benchmark-msgpack 81 | 82 | 83 | 84 | 85 | 86 | ## [0.0.9](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.8...benchmark-msgpack@0.0.9) (2020-02-25) 87 | 88 | **Note:** Version bump only for package benchmark-msgpack 89 | 90 | 91 | 92 | 93 | 94 | ## [0.0.8](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.7...benchmark-msgpack@0.0.8) (2020-02-24) 95 | 96 | **Note:** Version bump only for package benchmark-msgpack 97 | 98 | 99 | 100 | 101 | 102 | ## [0.0.7](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.6...benchmark-msgpack@0.0.7) (2020-02-17) 103 | 104 | **Note:** Version bump only for package benchmark-msgpack 105 | 106 | 107 | 108 | 109 | 110 | ## [0.0.6](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.5...benchmark-msgpack@0.0.6) (2020-02-10) 111 | 112 | **Note:** Version bump only for package benchmark-msgpack 113 | 114 | 115 | 116 | 117 | 118 | ## [0.0.5](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.4...benchmark-msgpack@0.0.5) (2020-02-03) 119 | 120 | **Note:** Version bump only for package benchmark-msgpack 121 | 122 | 123 | 124 | 125 | 126 | ## [0.0.4](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.3...benchmark-msgpack@0.0.4) (2020-01-27) 127 | 128 | **Note:** Version bump only for package benchmark-msgpack 129 | 130 | 131 | 132 | 133 | 134 | ## [0.0.3](http://gerrit.corp.arista.io:29418/web-components/compare/benchmark-msgpack@0.0.2...benchmark-msgpack@0.0.3) (2020-01-22) 135 | 136 | **Note:** Version bump only for package benchmark-msgpack 137 | 138 | 139 | 140 | 141 | 142 | ## 0.0.2 (2020-01-22) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * **a-msgpack,cloudvision-connector:** improve perf ([3b6992d](http://gerrit.corp.arista.io:29418/web-components/commits/3b6992d783463f8f4f000c3334ceb0693e793083)) 148 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/benchmark-from-msgpack-lite-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "int0": 0, 3 | "int1": 1, 4 | "int1-": -1, 5 | "int8": 255, 6 | "int8-": -255, 7 | "int16": 256, 8 | "int16-": -256, 9 | "int32": 65536, 10 | "int32-": -65536, 11 | "nil": null, 12 | "true": true, 13 | "false": false, 14 | "float": 0.5, 15 | "float-": -0.5, 16 | "string0": "", 17 | "string1": "A", 18 | "string4": "foobarbaz", 19 | "string8": "Omnes viae Romam ducunt.", 20 | "string16": "L’homme n’est qu’un roseau, le plus faible de la nature ; mais c’est un roseau pensant. Il ne faut pas que l’univers entier s’arme pour l’écraser : une vapeur, une goutte d’eau, suffit pour le tuer. Mais, quand l’univers l’écraserait, l’homme serait encore plus noble que ce qui le tue, puisqu’il sait qu’il meurt, et l’avantage que l’univers a sur lui, l’univers n’en sait rien. Toute notre dignité consiste donc en la pensée. C’est de là qu’il faut nous relever et non de l’espace et de la durée, que nous ne saurions remplir. Travaillons donc à bien penser : voilà le principe de la morale.", 21 | "array0": [], 22 | "array1": [ 23 | "foo" 24 | ], 25 | "array8": [ 26 | 1, 27 | 2, 28 | 4, 29 | 8, 30 | 16, 31 | 32, 32 | 64, 33 | 128, 34 | 256, 35 | 512, 36 | 1024, 37 | 2048, 38 | 4096, 39 | 8192, 40 | 16384, 41 | 32768, 42 | 65536, 43 | 131072, 44 | 262144, 45 | 524288, 46 | 1048576 47 | ], 48 | "map0": {}, 49 | "map1": { 50 | "foo": "bar" 51 | } 52 | } -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/decode-string.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Benchmark from "benchmark"; 4 | 5 | import { 6 | utf8EncodeJs, 7 | utf8Count, 8 | utf8DecodeJs, 9 | utf8DecodeTD, 10 | } from "../src/utils/utf8"; 11 | 12 | for (const baseStr of ["A", "あ", "🌏"]) { 13 | const dataSet = [10, 100, 200, 1_000, 10_000, 100_000].map((n) => { 14 | return baseStr.repeat(n); 15 | }); 16 | 17 | for (const str of dataSet) { 18 | const byteLength = utf8Count(str); 19 | const bytes = new Uint8Array(new ArrayBuffer(byteLength)); 20 | utf8EncodeJs(str, bytes, 0); 21 | 22 | console.log( 23 | `\n## string "${baseStr}" x ${str.length} (byteLength=${byteLength})\n` 24 | ); 25 | 26 | const suite = new Benchmark.Suite(); 27 | 28 | suite.add("utf8DecodeJs", () => { 29 | if (utf8DecodeJs(bytes, 0, byteLength) !== str) { 30 | throw new Error("wrong result!"); 31 | } 32 | }); 33 | 34 | suite.add("TextDecoder", () => { 35 | if (utf8DecodeTD(bytes, 0, byteLength) !== str) { 36 | throw new Error("wrong result!"); 37 | } 38 | }); 39 | suite.on("cycle", (event: any) => { 40 | console.log(String(event.target)); 41 | }); 42 | 43 | suite.run(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/encode-string.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import Benchmark from "benchmark"; 4 | 5 | import { utf8EncodeJs, utf8Count, utf8EncodeTE } from "a-msgpack"; 6 | 7 | for (const baseStr of ["A", "あ", "🌏"]) { 8 | const dataSet = [10, 100, 200, 1_000, 10_000, 100_000].map((n) => { 9 | return baseStr.repeat(n); 10 | }); 11 | 12 | for (const str of dataSet) { 13 | const byteLength = utf8Count(str); 14 | const buffer = new Uint8Array(byteLength); 15 | 16 | console.log( 17 | `\n## string "${baseStr}" x ${str.length} (byteLength=${byteLength})\n` 18 | ); 19 | 20 | const suite = new Benchmark.Suite(); 21 | 22 | suite.add("utf8EncodeJs", () => { 23 | utf8EncodeJs(str, buffer, 0); 24 | }); 25 | 26 | suite.add("utf8DecodeTE", () => { 27 | utf8EncodeTE(str, buffer, 0); 28 | }); 29 | suite.on("cycle", (event: any) => { 30 | console.log(String(event.target)); 31 | }); 32 | 33 | suite.run(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/key-decoder.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, @typescript-eslint/no-var-requires */ 2 | 3 | import Benchmark from "benchmark"; 4 | 5 | import { CachedKeyDecoder } from "../src/CachedKeyDecoder"; 6 | import { utf8EncodeJs, utf8Count, utf8DecodeJs } from "../src/utils/utf8"; 7 | 8 | type InputType = { 9 | bytes: Uint8Array; 10 | byteLength: number; 11 | str: string; 12 | }; 13 | 14 | const keys: InputType[] = Object.keys( 15 | require("./benchmark-from-msgpack-lite-data.json") 16 | ).map((str) => { 17 | const byteLength = utf8Count(str); 18 | const bytes = new Uint8Array(new ArrayBuffer(byteLength)); 19 | utf8EncodeJs(str, bytes, 0); 20 | return { bytes, byteLength, str }; 21 | }); 22 | 23 | for (const dataSet of [keys]) { 24 | const keyDecoder = new CachedKeyDecoder(); 25 | // make cache storage full 26 | for (let i = 0; i < keyDecoder.maxKeyLength; i++) { 27 | for (let j = 0; j < keyDecoder.maxLengthPerKey; j++) { 28 | const str = `${j.toString().padStart(i + 1, "0")}`; 29 | const byteLength = utf8Count(str); 30 | const bytes = new Uint8Array(new ArrayBuffer(byteLength)); 31 | utf8EncodeJs(str, bytes, 0); 32 | keyDecoder.decode(bytes, 0, byteLength); // fill 33 | } 34 | } 35 | 36 | // console.dir(keyDecoder, { depth: 100 }); 37 | console.log("## When the cache storage is full."); 38 | 39 | const suite = new Benchmark.Suite(); 40 | 41 | suite.add("utf8DecodeJs", () => { 42 | for (const data of dataSet) { 43 | if (utf8DecodeJs(data.bytes, 0, data.byteLength) !== data.str) { 44 | throw new Error("wrong result!"); 45 | } 46 | } 47 | }); 48 | 49 | suite.add("CachedKeyDecoder", () => { 50 | for (const data of dataSet) { 51 | if (keyDecoder.decode(data.bytes, 0, data.byteLength) !== data.str) { 52 | throw new Error("wrong result!"); 53 | } 54 | } 55 | }); 56 | suite.on("cycle", (event: any) => { 57 | console.log(String(event.target)); 58 | }); 59 | 60 | suite.run(); 61 | } 62 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/msgpack-benchmark.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, global-require, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-var-requires, import/no-dynamic-require */ 2 | 3 | import Benchmark from 'benchmark'; 4 | 5 | interface Implementation { 6 | encode: (d: unknown) => Uint8Array | string; 7 | decode: (d: Uint8Array | string) => unknown; 8 | toDecode?: Uint8Array | string; 9 | } 10 | 11 | const implementations: { 12 | [name: string]: Implementation; 13 | } = { 14 | 'a-msgpack': { 15 | encode: require('../src').encode, 16 | decode: require('../src').decode, 17 | }, 18 | '@msgpack/msgpack': { 19 | encode: require('@msgpack/msgpack').encode, 20 | decode: require('@msgpack/msgpack').decode, 21 | }, 22 | 'msgpack-lite': { 23 | encode: require('msgpack-lite').encode, 24 | decode: require('msgpack-lite').decode, 25 | }, 26 | 'msgpack-js-v5': { 27 | encode: require('msgpack-js-v5').encode, 28 | decode: require('msgpack-js-v5').decode, 29 | }, 30 | 'msgpack-js': { 31 | encode: require('msgpack-js').encode, 32 | decode: require('msgpack-js').decode, 33 | }, 34 | 'notepack.io': { 35 | encode: require('notepack.io/browser/encode'), 36 | decode: require('notepack.io/browser/decode'), 37 | }, 38 | JSON: { 39 | encode: (obj: unknown): string => JSON.stringify(obj), 40 | decode: (str: string | Uint8Array): unknown => JSON.parse(str as string), 41 | }, 42 | }; 43 | 44 | const sampleFiles = ['./sample-large.json']; 45 | 46 | for (const sampleFile of sampleFiles) { 47 | const data = require(sampleFile); 48 | const encodeSuite = new Benchmark.Suite(); 49 | const decodeSuite = new Benchmark.Suite(); 50 | 51 | console.log(''); 52 | console.log('**' + sampleFile + ':** (' + JSON.stringify(data).length + ' bytes in JSON)'); 53 | console.log(''); 54 | 55 | for (const name of Object.keys(implementations)) { 56 | implementations[name].toDecode = implementations[name].encode(data); 57 | encodeSuite.add('(encode) ' + name, () => { 58 | implementations[name].encode(data); 59 | }); 60 | decodeSuite.add('(decode) ' + name, () => { 61 | implementations[name].decode(implementations[name].toDecode!); 62 | }); 63 | } 64 | encodeSuite.on('cycle', (event: Event) => { 65 | console.log(String(event.target)); 66 | }); 67 | 68 | console.log('```'); 69 | encodeSuite.run(); 70 | console.log('```'); 71 | 72 | console.log(''); 73 | 74 | decodeSuite.on('cycle', (event: Event) => { 75 | console.log(String(event.target)); 76 | }); 77 | 78 | console.log('```'); 79 | decodeSuite.run(); 80 | console.log('```'); 81 | } 82 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark-msgpack", 3 | "private": true, 4 | "version": "4.0.0", 5 | "scripts": { 6 | "bench": "npm run bench:lib && npm run bench:string:encode && npm run bench:string:encode && npm run bench:string:encode", 7 | "bench:key-decoder": "ts-node key-decoder.ts", 8 | "bench:lib": "ts-node msgpack-benchmark.ts", 9 | "bench:string:decode": "ts-node decode-string.ts", 10 | "bench:string:encode": "ts-node encode-string.ts", 11 | "profile": "npm run profile:decode && npm run profile:encode && npm run profile:string && npm run profile:pointer", 12 | "profile:decode": "ts-node profile-decode.ts", 13 | "profile:encode": "ts-node profile-encode.ts", 14 | "profile:pointer": "ts-node profile-pointer.ts", 15 | "profile:string": "ts-node profile-string.ts", 16 | "type-check": "tsc --noEmit" 17 | }, 18 | "dependencies": { 19 | "@arista/prettier-config": "1.1.4", 20 | "@msgpack/msgpack": "2.8.0", 21 | "benchmark": "2.1.4", 22 | "lodash": "4.17.21", 23 | "msgpack-js": "0.3.0", 24 | "msgpack-js-v5": "0.3.0-v5", 25 | "msgpack-lite": "0.1.26", 26 | "msgpack-unpack": "2.1.1", 27 | "msgpack5": "6.0.2", 28 | "notepack.io": "3.0.1", 29 | "ts-node": "10.9.1", 30 | "tslib": "2.4.0", 31 | "typescript": "4.8.4" 32 | }, 33 | "devDependencies": { 34 | "@types/benchmark": "2.1.2", 35 | "@types/lodash": "4.17.15" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/profile-decode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, @typescript-eslint/no-var-requires */ 2 | 3 | import _ from 'lodash'; 4 | 5 | import { encode, decode } from '../src'; 6 | 7 | const data = require('./benchmark-from-msgpack-lite-data.json'); 8 | 9 | const dataX = _.cloneDeep(new Array(100).fill(data)); 10 | const encoded = encode(dataX); 11 | 12 | console.log('encoded size:', encoded.byteLength); 13 | 14 | console.time('decode #1'); 15 | for (let i = 0; i < 1000; i++) { 16 | decode(encoded); 17 | } 18 | console.timeEnd('decode #1'); 19 | 20 | console.time('decode #2'); 21 | for (let i = 0; i < 1000; i++) { 22 | decode(encoded); 23 | } 24 | console.timeEnd('decode #2'); 25 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/profile-encode.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console, @typescript-eslint/no-var-requires */ 2 | 3 | import _ from 'lodash'; 4 | 5 | import { encode } from '../src'; 6 | 7 | const data = require('./benchmark-from-msgpack-lite-data.json'); 8 | 9 | const dataX = _.cloneDeep(new Array(100).fill(data)); 10 | 11 | console.time('encode #1'); 12 | for (let i = 0; i < 1000; i++) { 13 | encode(dataX); 14 | } 15 | console.timeEnd('encode #1'); 16 | 17 | console.time('encode #2'); 18 | for (let i = 0; i < 1000; i++) { 19 | encode(dataX); 20 | } 21 | console.timeEnd('encode #2'); 22 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/profile-pointer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { encode, decode, Pointer, Codec } from '../src'; 4 | 5 | const pointer = new Pointer([ 6 | 'The', 7 | 'Dodgers', 8 | 'are', 9 | 'the', 10 | 'best', 11 | 'baseball', 12 | 'team', 13 | 'and', 14 | 'the', 15 | 'Astros', 16 | 'are', 17 | 'cheater', 18 | ]); 19 | 20 | const encoded = encode(pointer, { extensionCodec: Codec }); 21 | 22 | console.log('encoded size:', encoded.byteLength); 23 | 24 | console.time('decode pointer'); 25 | for (let i = 0; i < 1000; i++) { 26 | decode(encoded, { extensionCodec: Codec }); 27 | } 28 | console.timeEnd('decode pointer'); 29 | 30 | console.time('encode pointer'); 31 | for (let i = 0; i < 1000; i++) { 32 | encode(pointer, { extensionCodec: Codec }); 33 | } 34 | console.timeEnd('encode pointer'); 35 | -------------------------------------------------------------------------------- /packages/a-msgpack/benchmark-msgpack/profile-string.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { encode, decode } from '../src'; 4 | 5 | const ascii = 'A'.repeat(40000); 6 | const emoji = '🌏'.repeat(20000); 7 | 8 | { 9 | // warm up ascii 10 | const data = ascii; 11 | const encoded = encode(data); 12 | decode(encoded); 13 | console.log( 14 | `encode / decode ascii data.length=${data.length} encoded.byteLength=${encoded.byteLength}`, 15 | ); 16 | 17 | // run 18 | 19 | console.time('encode ascii'); 20 | for (let i = 0; i < 1000; i++) { 21 | encode(data); 22 | } 23 | console.timeEnd('encode ascii'); 24 | 25 | console.time('decode ascii'); 26 | for (let i = 0; i < 1000; i++) { 27 | decode(encoded); 28 | } 29 | console.timeEnd('decode ascii'); 30 | } 31 | 32 | { 33 | // warm up emoji 34 | const data = emoji; 35 | const encoded = encode(data); 36 | decode(encoded); 37 | 38 | console.log( 39 | `encode / decode emoji data.length=${data.length} encoded.byteLength=${encoded.byteLength}`, 40 | ); 41 | 42 | // run 43 | 44 | console.time('encode emoji'); 45 | for (let i = 0; i < 1000; i++) { 46 | encode(data); 47 | } 48 | console.timeEnd('encode emoji'); 49 | 50 | console.time('decode emoji'); 51 | for (let i = 0; i < 1000; i++) { 52 | decode(encoded); 53 | } 54 | console.timeEnd('decode emoji'); 55 | } 56 | -------------------------------------------------------------------------------- /packages/a-msgpack/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/a-msgpack/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cacheDirectory: '.jest-cache', 3 | coverageThreshold: { 4 | global: { 5 | branches: 98.94, 6 | functions: 100, 7 | lines: 99.22, 8 | statements: 99.25, 9 | }, 10 | }, 11 | coverageReporters: ['html', 'lcov'], 12 | collectCoverageFrom: [ 13 | '**/*.ts', 14 | '!src/index.ts', 15 | '!src/utils/utf8.ts', 16 | '!src/utils/prettyByte.ts', 17 | '!**/node_modules/**', 18 | '!**/lib/**', 19 | '!**/es/**', 20 | '!**/dist/**', 21 | '!**/coverage/**', 22 | '!**/*.spec.ts', 23 | '!**/*.config.js', 24 | ], 25 | transform: { 26 | '^.+\\.ts$': 'ts-jest', 27 | }, 28 | roots: ['/test', '/src'], 29 | setupFiles: ['/test/encoder-setup.js'], 30 | }; 31 | -------------------------------------------------------------------------------- /packages/a-msgpack/last-benchmark-results.txt: -------------------------------------------------------------------------------- 1 | 2 | > benchmark-msgpack@4.0.0 bench /Users/stephane/code/cloudvision/packages/a-msgpack/benchmark-msgpack 3 | > npm run bench:lib && npm run bench:string:encode && npm run bench:string:encode && npm run bench:string:encode 4 | 5 | 6 | > benchmark-msgpack@4.0.0 bench:lib /Users/stephane/code/cloudvision/packages/a-msgpack/benchmark-msgpack 7 | > ts-node msgpack-benchmark.ts 8 | 9 | 10 | **./sample-large.json:** (7598 bytes in JSON) 11 | 12 | ``` 13 | (encode) a-msgpack x 4,361 ops/sec ±1.85% (82 runs sampled) 14 | (encode) @msgpack/msgpack x 19,335 ops/sec ±2.80% (85 runs sampled) 15 | (encode) msgpack-lite x 14,430 ops/sec ±2.64% (84 runs sampled) 16 | (encode) msgpack-js-v5 x 1,783 ops/sec ±94.11% (86 runs sampled) 17 | (encode) msgpack-js x 5,734 ops/sec ±1.39% (92 runs sampled) 18 | (encode) notepack.io x 17,060 ops/sec ±2.16% (88 runs sampled) 19 | (encode) JSON x 46,151 ops/sec ±1.82% (86 runs sampled) 20 | ``` 21 | 22 | ``` 23 | (decode) a-msgpack x 15,818 ops/sec ±1.74% (87 runs sampled) 24 | (decode) @msgpack/msgpack x 15,803 ops/sec ±1.97% (86 runs sampled) 25 | (decode) msgpack-lite x 8,314 ops/sec ±1.38% (93 runs sampled) 26 | (decode) msgpack-js-v5 x 13,903 ops/sec ±1.63% (90 runs sampled) 27 | (decode) msgpack-js x 13,962 ops/sec ±1.64% (92 runs sampled) 28 | (decode) notepack.io x 13,091 ops/sec ±1.45% (92 runs sampled) 29 | (decode) JSON x 53,210 ops/sec ±1.21% (90 runs sampled) 30 | ``` 31 | 32 | > benchmark-msgpack@4.0.0 bench:string:encode /Users/stephane/code/cloudvision/packages/a-msgpack/benchmark-msgpack 33 | > ts-node encode-string.ts 34 | 35 | -------------------------------------------------------------------------------- /packages/a-msgpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-msgpack", 3 | "version": "5.0.2", 4 | "description": "A minimalistic NEAT (MessagePack based) encoder and decoder for JavaScript.", 5 | "author": "Stephane Rufer ", 6 | "homepage": "https://aristanetworks.github.io/cloudvision/modules/a_msgpack.html", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/aristanetworks/cloudvision.git" 10 | }, 11 | "license": "MIT", 12 | "main": "lib/MsgPack.js", 13 | "unpkg": "dist/MsgPack.js", 14 | "module": "es/MsgPack.js", 15 | "typings": "typings/index.d.ts", 16 | "typedocMain": "src/index.ts", 17 | "scripts": { 18 | "bench": "cd ./benchmark-msgpack && npm run bench > ../last-benchmark-results.txt", 19 | "build": "npm run clean && npm run build:es && npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run build:types && npm run docs && npm run docs:md", 20 | "build:commonjs": "cross-env NODE_ENV=cjs rollup -c -o lib/MsgPack.js", 21 | "build:es": "cross-env BABEL_ENV=es NODE_ENV=es rollup -c -o es/MsgPack.js", 22 | "build:types": "rimraf typings && tsc --emitDeclarationOnly -p tsconfig-build.json -outDir ./typings", 23 | "build:umd": "cross-env BABEL_ENV=es NODE_ENV=development rollup -c -o dist/MsgPack.js", 24 | "build:umd:min": "cross-env BABEL_ENV=es NODE_ENV=production rollup -c -o dist/MsgPack.min.js", 25 | "check": "npm run lint && npm run type-check && npm run prettier-check", 26 | "clean": "rimraf dist lib es", 27 | "docs": "typedoc --plugin none --out docs/html", 28 | "docs:md": "typedoc --plugin typedoc-plugin-markdown --theme markdown --out docs/md", 29 | "lint": "eslint --max-warnings 0 --ext .js,.ts *.js src/**/*.ts test/**/*.ts types/**/*.ts", 30 | "prepare": "npm run build", 31 | "prettier-check": "prettier -c *.js src/**/*.ts", 32 | "prettier-fix": "prettier --write *.js src/**/*.ts", 33 | "test": "jest", 34 | "test:all": "npm run test && npm run test:purejs && npm run test:forcete", 35 | "test:clean": "rimraf .jest-cache", 36 | "test:cov": "rimraf coverage && npm run test -- --coverage && npm run test:purejs && npm run test:forcete -- --coverage", 37 | "test:forcete": "cross-env TEXT_ENCODING=force TEXT_DECODING=force jest", 38 | "test:purejs": "cross-env TEXT_ENCODING=never TEXT_DECODING=never jest", 39 | "test:watch": "npm run test -- --watch", 40 | "type-check": "tsc --noEmit" 41 | }, 42 | "keywords": [ 43 | "binary", 44 | "decode", 45 | "encode", 46 | "json", 47 | "messagepack", 48 | "msgpack", 49 | "serialize" 50 | ], 51 | "files": [ 52 | "dist", 53 | "es", 54 | "lib", 55 | "src", 56 | "typings" 57 | ], 58 | "dependencies": { 59 | "base64-js": "1.5.1", 60 | "jsbi": "4.3.0" 61 | }, 62 | "devDependencies": { 63 | "@arista/prettier-config": "1.1.4", 64 | "@rollup/plugin-commonjs": "22.0.2", 65 | "@rollup/plugin-node-resolve": "13.3.0", 66 | "@rollup/plugin-typescript": "8.5.0", 67 | "@types/base64-js": "1.3.0", 68 | "@types/jest": "27.5.2", 69 | "@types/js-yaml": "4.0.5", 70 | "@typescript-eslint/eslint-plugin": "5.32.0", 71 | "@typescript-eslint/parser": "5.32.0", 72 | "babel-eslint": "10.1.0", 73 | "cross-env": "7.0.3", 74 | "eslint": "8.36.0", 75 | "eslint-config-arista-js": "2.0.3", 76 | "eslint-config-arista-ts": "2.0.1", 77 | "eslint-plugin-arista": "0.2.3", 78 | "eslint-plugin-import": "2.27.5", 79 | "jest": "27.5.1", 80 | "js-yaml": "4.1.0", 81 | "prettier": "2.7.1", 82 | "rimraf": "3.0.2", 83 | "rollup": "2.79.1", 84 | "rollup-plugin-terser": "7.0.2", 85 | "ts-jest": "27.1.5", 86 | "tslib": "2.4.0", 87 | "typedoc": "0.23.26", 88 | "typedoc-plugin-markdown": "3.13.6", 89 | "typescript": "4.8.4" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/a-msgpack/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import typescript from '@rollup/plugin-typescript'; 4 | import { terser } from 'rollup-plugin-terser'; 5 | 6 | import packageJson from './package.json'; 7 | 8 | const env = process.env.NODE_ENV; 9 | 10 | const config = { 11 | input: 'src/index.ts', 12 | onwarn: (warning) => { 13 | throw new Error( 14 | `${warning.message} (${warning.loc.file}):${warning.loc.line}:${warning.loc.column}`, 15 | ); 16 | }, 17 | plugins: [resolve(), commonjs(), typescript({ sourceMap: false })], 18 | }; 19 | 20 | const external = Object.keys(packageJson.dependencies); 21 | 22 | const globals = { 23 | 'base64-js': 'base64Js', 24 | 'jsbi': 'jsbi', 25 | }; 26 | 27 | if (env === 'es' || env === 'cjs') { 28 | config.external = external; 29 | config.output = { 30 | exports: 'named', 31 | format: env, 32 | globals, 33 | indent: false, 34 | }; 35 | } 36 | 37 | if (env === 'development' || env === 'production') { 38 | config.external = external; 39 | config.output = { 40 | exports: 'named', 41 | format: 'umd', 42 | indent: false, 43 | globals, 44 | name: 'a-msgpack', 45 | }; 46 | } 47 | 48 | if (env === 'production') { 49 | config.plugins.push(terser()); 50 | } 51 | 52 | export default config; 53 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/CachedKeyDecoder.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-labels */ 2 | 3 | import { utf8DecodeJs } from './utils/utf8'; 4 | 5 | interface KeyCacheRecord { 6 | readonly bytes: Uint8Array; 7 | readonly value: string; 8 | } 9 | 10 | const DEFAULT_MAX_KEY_LENGTH = 16; 11 | const DEFAULT_MAX_LENGTH_PER_KEY = 16; 12 | 13 | export class CachedKeyDecoder { 14 | private readonly caches: KeyCacheRecord[][]; 15 | 16 | constructor( 17 | readonly maxKeyLength = DEFAULT_MAX_KEY_LENGTH, 18 | readonly maxLengthPerKey = DEFAULT_MAX_LENGTH_PER_KEY, 19 | ) { 20 | // avoid `new Array(N)` to create a non-sparse array for performance. 21 | this.caches = []; 22 | for (let i = 0; i < this.maxKeyLength; i++) { 23 | this.caches.push([]); 24 | } 25 | } 26 | 27 | public canBeCached(byteLength: number): boolean { 28 | return byteLength > 0 && byteLength <= this.maxKeyLength; 29 | } 30 | 31 | private get(bytes: Uint8Array, inputOffset: number, byteLength: number): string | null { 32 | const records = this.caches[byteLength - 1]; 33 | const recordsLength = records.length; 34 | 35 | FIND_CHUNK: for (let i = 0; i < recordsLength; i++) { 36 | const record = records[i]; 37 | const recordBytes = record.bytes; 38 | 39 | for (let j = 0; j < byteLength; j++) { 40 | if (recordBytes[j] !== bytes[inputOffset + j]) { 41 | continue FIND_CHUNK; 42 | } 43 | } 44 | return record.value; 45 | } 46 | return null; 47 | } 48 | 49 | private store(bytes: Uint8Array, value: string): void { 50 | const records = this.caches[bytes.length - 1]; 51 | const record: KeyCacheRecord = { bytes, value }; 52 | 53 | if (records.length >= this.maxLengthPerKey) { 54 | // `records` are full! 55 | // Set `record` to a randomized position. 56 | records[(Math.random() * records.length) | 0] = record; 57 | } else { 58 | records.push(record); 59 | } 60 | } 61 | 62 | public decode(bytes: Uint8Array, inputOffset: number, byteLength: number): string { 63 | const cachedValue = this.get(bytes, inputOffset, byteLength); 64 | if (cachedValue) { 65 | return cachedValue; 66 | } 67 | 68 | const value = utf8DecodeJs(bytes, inputOffset, byteLength); 69 | // Ensure to copy a slice of bytes because the byte may be NodeJS Buffer and Buffer#slice() 70 | // returns a reference to its internal ArrayBuffer. 71 | const slicedCopyOfBytes = Uint8Array.prototype.slice.call( 72 | bytes, 73 | inputOffset, 74 | inputOffset + byteLength, 75 | ); 76 | this.store(slicedCopyOfBytes, value); 77 | return value; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/ExtData.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ExtData is used to handle Extension Types that are not registered to ExtensionCodec. 3 | */ 4 | export class ExtData { 5 | constructor(readonly type: number, readonly data: Uint8Array) { 6 | this.type = type; 7 | this.data = data; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/ExtensionCodec.ts: -------------------------------------------------------------------------------- 1 | // ExtensionCodec to handle MessagePack extensions 2 | 3 | import { ExtData } from './ExtData'; 4 | 5 | export type ExtensionDecoderType = (data: Uint8Array, extensionType: number) => unknown; 6 | export type ExtensionEncoderType = (input: T) => Uint8Array; 7 | export type ExtensionIdentifier = (ext: unknown) => boolean; 8 | 9 | export interface ExtensionCodecType { 10 | encode(object: unknown): ExtData; 11 | decode(data: Uint8Array, extType: number): unknown; 12 | } 13 | 14 | export class ExtensionCodec implements ExtensionCodecType { 15 | public static readonly defaultCodec: ExtensionCodecType = new ExtensionCodec(); 16 | 17 | // custom extensions 18 | private readonly encoders: ExtensionEncoderType[] = []; 19 | 20 | private readonly decoders: ExtensionDecoderType[] = []; 21 | 22 | private readonly identifiers: ExtensionIdentifier[] = []; 23 | 24 | public register({ 25 | type, 26 | identifier, 27 | encode, 28 | decode, 29 | }: { 30 | type: number; 31 | identifier: ExtensionIdentifier; 32 | encode: ExtensionEncoderType; 33 | decode: ExtensionDecoderType; 34 | }): void { 35 | this.encoders[type] = encode; 36 | this.decoders[type] = decode; 37 | this.identifiers.push(identifier); 38 | } 39 | 40 | public encode(object: unknown): ExtData { 41 | let type = null; 42 | for (let i = 0; i < this.identifiers.length; i++) { 43 | const id = this.identifiers[i]; 44 | if (id(object)) { 45 | type = i; 46 | } 47 | } 48 | if (type === null) { 49 | throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`); 50 | } 51 | const encoder = this.encoders[type]; 52 | const data = encoder(object); 53 | return new ExtData(type, data); 54 | } 55 | 56 | public decode(data: Uint8Array, type: number): unknown { 57 | const decoder = this.decoders[type]; 58 | return decoder(data, type); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/decode.ts: -------------------------------------------------------------------------------- 1 | import { CachedKeyDecoder } from './CachedKeyDecoder'; 2 | import { Decoder } from './Decoder'; 3 | import { ExtensionCodecType } from './ExtensionCodec'; 4 | 5 | export interface DecodeOptions { 6 | /** 7 | * The extension codec to use. 8 | * Default is none 9 | */ 10 | extensionCodec?: ExtensionCodecType; 11 | 12 | /** 13 | * Maximum string length. 14 | * Default to 4_294_967_295 (UINT32_MAX). 15 | */ 16 | maxStrLength?: number; 17 | /** 18 | * Maximum binary length. 19 | * Default to 4_294_967_295 (UINT32_MAX). 20 | */ 21 | maxBinLength?: number; 22 | /** 23 | * Maximum array length. 24 | * Default to 4_294_967_295 (UINT32_MAX). 25 | */ 26 | maxArrayLength?: number; 27 | /** 28 | * Maximum map length. 29 | * Default to 4_294_967_295 (UINT32_MAX). 30 | */ 31 | maxMapLength?: number; 32 | /** 33 | * Maximum extension length. 34 | * Default to 4_294_967_295 (UINT32_MAX). 35 | */ 36 | maxExtLength?: number; 37 | 38 | /** 39 | * Force using JSBI, even if BigInt is available. 40 | * Default false. 41 | */ 42 | useJSBI?: boolean; 43 | 44 | /** 45 | * Use a different caching implementation for decoding keys. 46 | * Passing null disables caching keys on decode. 47 | * Default CachedKeyDecoder implementation. 48 | */ 49 | cachedKeyDecoder?: CachedKeyDecoder | null; 50 | } 51 | 52 | export const defaultDecodeOptions: DecodeOptions = {}; 53 | 54 | /** 55 | * It decodes a MessagePack-encoded buffer. 56 | * 57 | * This is a synchronous decoding function. See other variants for asynchronous decoding: 58 | * `decodeAsync()`, `decodeStream()`, and `decodeArrayStream()` 59 | */ 60 | export function decode( 61 | buffer: ArrayLike | ArrayBuffer, 62 | options: DecodeOptions = defaultDecodeOptions, 63 | ): unknown { 64 | const decoder = new Decoder( 65 | options.extensionCodec, 66 | options.useJSBI, 67 | options.maxStrLength, 68 | options.maxBinLength, 69 | options.maxArrayLength, 70 | options.maxMapLength, 71 | options.maxExtLength, 72 | options.cachedKeyDecoder, 73 | ); 74 | decoder.setBuffer(buffer); // decodeSync() requires only one buffer 75 | return decoder.decodeSingleSync(); 76 | } 77 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/encode.ts: -------------------------------------------------------------------------------- 1 | import { Encoder } from './Encoder'; 2 | import { ExtensionCodecType } from './ExtensionCodec'; 3 | 4 | export interface EncodeOptions { 5 | /** 6 | * The extension codec to use. 7 | * Default is none 8 | */ 9 | extensionCodec?: ExtensionCodecType; 10 | /** 11 | * The maximum object depth 12 | * Default 100 13 | */ 14 | maxDepth?: number; 15 | /** 16 | * The size of the buffer when beginning encoding. 17 | * This is the minimum amount of memory allocated. 18 | * Default 2048 19 | */ 20 | initialBufferSize?: number; 21 | } 22 | 23 | const defaultEncodeOptions = {}; 24 | 25 | /** 26 | * It encodes `value` in the MessagePack format and 27 | * returns a byte buffer. 28 | * 29 | * The returned buffer is a slice of a larger `ArrayBuffer`, so you have to use its `#byteOffset` 30 | * and `#byteLength` in order to convert it to another typed arrays including NodeJS `Buffer`. 31 | */ 32 | export function encode(value: unknown, options: EncodeOptions = defaultEncodeOptions): Uint8Array { 33 | const encoder = new Encoder(options.extensionCodec, options.maxDepth, options.initialBufferSize); 34 | encoder.encode(value, 1); 35 | return encoder.getUint8Array(); 36 | } 37 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/index.ts: -------------------------------------------------------------------------------- 1 | export { encode } from './encode'; 2 | export { decode } from './decode'; 3 | 4 | export { ExtensionCodec } from './ExtensionCodec'; 5 | export { ExtData } from './ExtData'; 6 | export { isJsbi } from './utils/data'; 7 | export { Bool, Float32, Float64, Int, Nil, Pointer, Str } from './neat/NeatTypes'; 8 | export { createBaseType } from './neat/typeCreators'; 9 | export { NeatTypes, Codec } from './neat'; 10 | export { isNeatType, NeatTypeSerializer } from './neat/utils'; 11 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/neat/NeatTypes.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | /* eslint-disable max-classes-per-file */ 19 | 20 | import JSBI from 'jsbi'; 21 | 22 | import { Element, PathElements } from '../../types/neat'; 23 | import { isJsbi } from '../utils/data'; 24 | 25 | export class Float32 { 26 | public static type: 'float32' = 'float32'; 27 | 28 | public type: 'float32'; 29 | 30 | public value: number; 31 | 32 | /** 33 | * A class to represent an explicit float32 34 | */ 35 | public constructor(value: unknown) { 36 | this.type = Float32.type; 37 | this.value = value ? parseFloat(String(value)) : 0.0; // undefined defaults to 0.0 38 | } 39 | 40 | public toString(): string { 41 | const strValue = this.value.toString(); 42 | const hasDecimal = strValue.includes('.'); 43 | return hasDecimal ? strValue : this.value.toFixed(1); 44 | } 45 | } 46 | 47 | export class Float64 { 48 | public static type: 'float64' = 'float64'; 49 | 50 | public type: 'float64'; 51 | 52 | public value: number; 53 | 54 | /** 55 | * A class to represent an explicit float64 56 | */ 57 | public constructor(value: unknown) { 58 | this.type = Float64.type; 59 | this.value = value ? parseFloat(String(value)) : 0.0; // undefined defaults to 0.0 60 | } 61 | 62 | public toString(): string { 63 | const strValue = this.value.toString(); 64 | const hasDecimal = strValue.includes('.'); 65 | return hasDecimal ? strValue : this.value.toFixed(1); 66 | } 67 | } 68 | 69 | export class Int { 70 | public static type: 'int' = 'int'; 71 | 72 | public type: 'int'; 73 | 74 | public value: number | bigint | JSBI; 75 | 76 | /** 77 | * A class to represent an explicit int 78 | */ 79 | public constructor(value: unknown, forceJSBI = false) { 80 | this.type = Int.type; 81 | if (typeof value === 'string') { 82 | let BI; // This is a type alias for JSBI/BigInt 83 | if (typeof BigInt === 'undefined' || forceJSBI) { 84 | BI = JSBI.BigInt; 85 | } else { 86 | BI = BigInt; 87 | } 88 | 89 | this.value = 90 | parseInt(value, 10) > 0xffffffff || parseInt(value, 10) < -0x80000000 91 | ? BI(value) // eslint-disable-line new-cap 92 | : parseInt(value, 10); 93 | } else if (typeof value === 'bigint' || isJsbi(value)) { 94 | this.value = value as bigint | JSBI; 95 | } else { 96 | this.value = parseInt(String(value), 10); 97 | } 98 | } 99 | 100 | public toString(): string { 101 | return this.value.toString(); 102 | } 103 | } 104 | 105 | export class Bool { 106 | public static type: 'bool' = 'bool'; 107 | 108 | public type: 'bool'; 109 | 110 | public value: boolean; 111 | 112 | /** 113 | * A class to represent an explicit boolean 114 | */ 115 | public constructor(value: unknown) { 116 | this.type = Bool.type; 117 | this.value = !!value; 118 | } 119 | 120 | public toString(): string { 121 | return this.value ? 'true' : 'false'; 122 | } 123 | } 124 | 125 | export class Nil { 126 | public static type: 'nil' = 'nil'; 127 | 128 | public type: 'nil'; 129 | 130 | public value: null; 131 | 132 | /** 133 | * A class to represent an explicit Nil 134 | */ 135 | public constructor() { 136 | this.type = Nil.type; 137 | this.value = null; 138 | } 139 | 140 | public toString(): string { 141 | return 'null'; 142 | } 143 | } 144 | 145 | export class Str { 146 | public static type: 'str' = 'str'; 147 | 148 | public type: 'str'; 149 | 150 | public value: string; 151 | 152 | /** 153 | * A class to represent an explicit String 154 | */ 155 | public constructor(value: unknown) { 156 | this.type = Str.type; 157 | switch (typeof value) { 158 | case 'string': 159 | this.value = value; 160 | break; 161 | case 'bigint': 162 | this.value = value.toString(); 163 | break; 164 | case 'undefined': 165 | this.value = ''; 166 | break; 167 | default: 168 | this.value = JSON.stringify(value); 169 | } 170 | 171 | if (isJsbi(value)) { 172 | this.value = (value as JSBI).toString(); 173 | } 174 | } 175 | 176 | public toString(): string { 177 | return this.value; 178 | } 179 | } 180 | 181 | export class Pointer { 182 | public static type: 'ptr' = 'ptr'; 183 | 184 | public value: PathElements; 185 | 186 | public type: 'ptr'; 187 | 188 | public delimiter: string; 189 | 190 | /** 191 | * A class to represent a Pointer type. 192 | * A Pointer is a pointer from one set of path elements to another. 193 | */ 194 | public constructor(value: PathElements | string | unknown = []) { 195 | this.delimiter = '/'; 196 | this.type = Pointer.type; 197 | let strValue: string; 198 | if (!Array.isArray(value)) { 199 | if (typeof value !== 'string') { 200 | strValue = JSON.stringify(value); 201 | } else { 202 | strValue = value; 203 | } 204 | 205 | const ptrArray: string[] = strValue.split(this.delimiter); 206 | while (ptrArray[0] === '') { 207 | ptrArray.shift(); 208 | } 209 | this.value = ptrArray.map((pathEl): Element => { 210 | try { 211 | return JSON.parse(pathEl); 212 | } catch (e) { 213 | // ignore errors, these are just regular strings 214 | } 215 | 216 | return pathEl; 217 | }); 218 | } else { 219 | this.value = value; 220 | } 221 | } 222 | 223 | public toString(): string { 224 | return this.value 225 | .map((pathEl): string => { 226 | if (typeof pathEl === 'string') { 227 | return pathEl; 228 | } 229 | return JSON.stringify(pathEl); 230 | }) 231 | .join(this.delimiter); 232 | } 233 | } 234 | 235 | export class Wildcard { 236 | public static type: '*' = '*'; 237 | 238 | public value: null; 239 | 240 | public type: '*'; 241 | 242 | /** 243 | * A class to represent a Wildcard type. 244 | * A Wildcard is a type that matches 1 or more path elements 245 | */ 246 | public constructor() { 247 | this.type = Wildcard.type; 248 | this.value = null; 249 | } 250 | 251 | public toString(): string { 252 | return '*'; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/neat/extensions.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | import { ExtensionCodec } from '../ExtensionCodec'; 19 | import { decode } from '../decode'; 20 | import { encode } from '../encode'; 21 | 22 | import { Pointer, Wildcard } from './NeatTypes'; 23 | 24 | type PointerEncoder = (data: Pointer) => Uint8Array; 25 | type PointerDecoder = (buffer: Uint8Array) => Pointer; 26 | type WildcardEncoder = () => Uint8Array; 27 | type WildcardDecoder = () => Wildcard; 28 | 29 | export function encodePointer(codec: ExtensionCodec): PointerEncoder { 30 | return (data: Pointer): Uint8Array => { 31 | return encode(data.value, { extensionCodec: codec }); 32 | }; 33 | } 34 | 35 | export function decodePointer(codec: ExtensionCodec): PointerDecoder { 36 | return (buffer: Uint8Array): Pointer => { 37 | const pointer = decode(buffer, { extensionCodec: codec }); 38 | if (Array.isArray(pointer)) { 39 | return new Pointer(pointer); 40 | } 41 | return new Pointer(); 42 | }; 43 | } 44 | 45 | export function encodeWildcard(): WildcardEncoder { 46 | return (): Uint8Array => { 47 | return new Uint8Array(); 48 | }; 49 | } 50 | 51 | export function decodeWildcard(): WildcardDecoder { 52 | return (): Wildcard => { 53 | return new Wildcard(); 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/neat/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | import { ExtensionCodec } from '../ExtensionCodec'; 19 | 20 | import { Bool, Float32, Float64, Int, Nil, Pointer, Str, Wildcard } from './NeatTypes'; 21 | import { decodePointer, decodeWildcard, encodePointer, encodeWildcard } from './extensions'; 22 | 23 | export const POINTER_TYPE = 0; 24 | export const WILDCARD_TYPE = 1; 25 | 26 | export const NeatTypes = { 27 | Bool, 28 | Float32, 29 | Float64, 30 | Int, 31 | Nil, 32 | Pointer, 33 | Str, 34 | Wildcard, 35 | }; 36 | 37 | export const Codec = new ExtensionCodec(); 38 | 39 | Codec.register({ 40 | type: POINTER_TYPE, 41 | identifier: (data: unknown) => data instanceof Pointer, 42 | encode: encodePointer(Codec), 43 | decode: decodePointer(Codec), 44 | }); 45 | 46 | Codec.register({ 47 | type: WILDCARD_TYPE, 48 | identifier: (data: unknown) => data instanceof Wildcard, 49 | encode: encodeWildcard(), 50 | decode: decodeWildcard(), 51 | }); 52 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/neat/typeCreators.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/prefer-for-of */ 2 | // Allow usage of for loops, since here performance is paramount 3 | 4 | // Copyright (c) 2018, Arista Networks, Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | // and associated documentation files (the "Software"), to deal in the Software without 8 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 9 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 10 | // Software is furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all copies or 13 | // substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | import { PlainObject } from '../../types'; 22 | import { BaseType } from '../../types/neat'; 23 | 24 | import { Bool, Float64, Int, Nil, Str } from './NeatTypes'; 25 | 26 | export function createBaseType(value: unknown): BaseType { 27 | const type = typeof value; 28 | if (type === 'number') { 29 | const decimals = String(value).split('.'); 30 | if (decimals.length > 1) { 31 | return new Float64(value); 32 | } 33 | 34 | return new Int(value); 35 | } 36 | 37 | if (type === 'boolean') { 38 | return new Bool(value); 39 | } 40 | 41 | if (type === 'string') { 42 | return new Str(value); 43 | } 44 | 45 | if (value !== null && type === 'object') { 46 | let proto = value; 47 | while (Object.getPrototypeOf(proto) !== null) { 48 | proto = Object.getPrototypeOf(proto); 49 | } 50 | if (Object.getPrototypeOf(value) === proto) { 51 | // eslint-disable-next-line @typescript-eslint/no-use-before-define 52 | return createTypedMap(value as PlainObject); 53 | } 54 | } 55 | 56 | return new Nil(); 57 | } 58 | 59 | export function createTypedMap(object: PlainObject): Map { 60 | const map = new Map(); 61 | const keys = Object.keys(object); 62 | for (let i = 0; i < keys.length; i += 1) { 63 | const key = keys[i]; 64 | const value = object[key]; 65 | const keyType = createBaseType(key); 66 | const valueType = createBaseType(value); 67 | 68 | map.set(keyType, valueType); 69 | } 70 | 71 | return map; 72 | } 73 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/neat/utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | 3 | import { BasicNeatType, PathElements } from '../../types/neat'; 4 | 5 | import { Bool, Float32, Float64, Int, Nil, Pointer, Str } from './NeatTypes'; 6 | 7 | interface SerializedNeatType { 8 | __neatTypeClass: string; 9 | value: unknown; 10 | } 11 | 12 | interface PointerNeatType { 13 | type: Pointer['type']; 14 | value: PathElements; 15 | delimiter: string; 16 | } 17 | 18 | export function isFloat32(value: BasicNeatType): value is Float32 { 19 | return value.type === 'float32'; 20 | } 21 | 22 | export function isFloat64(value: BasicNeatType): value is Float64 { 23 | return value.type === 'float64'; 24 | } 25 | 26 | export function isInt(value: BasicNeatType): value is Int { 27 | return value.type === 'int'; 28 | } 29 | 30 | export function isStr(value: BasicNeatType): value is Str { 31 | return value.type === 'str'; 32 | } 33 | 34 | export function isBool(value: BasicNeatType): value is Bool { 35 | return value.type === 'bool'; 36 | } 37 | 38 | export function isBasicNeatType(value: unknown): value is BasicNeatType { 39 | if (typeof value === 'object' && value !== null) { 40 | if (Object.keys(value).length === 2 && 'type' in value && 'value' in value) { 41 | const neatValue = value as BasicNeatType; 42 | return [Bool.type, Float32.type, Float64.type, Int.type, Nil.type, Str.type].includes( 43 | neatValue.type, 44 | ); 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | export function isNeatType(value: unknown): boolean { 51 | if (typeof value === 'object' && value !== null) { 52 | if (isBasicNeatType(value)) { 53 | return true; 54 | } 55 | if ( 56 | Object.keys(value).length === 3 && 57 | 'type' in value && 58 | 'value' in value && 59 | 'delimiter' in value 60 | ) { 61 | const neatValue = value as PointerNeatType; 62 | return neatValue.type === Pointer.type; 63 | } 64 | } 65 | return false; 66 | } 67 | 68 | function sortByBinaryValue(a: Uint8Array, b: Uint8Array): number { 69 | const len = Math.min(a.length, b.length); 70 | 71 | if (a === b) { 72 | return 0; 73 | } 74 | 75 | for (let i = 0; i < len; i += 1) { 76 | if (a[i] !== b[i]) { 77 | return a[i] - b[i]; 78 | } 79 | } 80 | 81 | return a.length - b.length; 82 | } 83 | 84 | export function sortMapByKey(a: [Uint8Array, unknown], b: [Uint8Array, unknown]): number { 85 | const aVal = a[0]; 86 | const bVal = b[0]; 87 | 88 | return sortByBinaryValue(aVal, bVal); 89 | } 90 | 91 | export class NeatTypeSerializer { 92 | private static NEAT_TYPE_MAP: { 93 | [type: string]: 94 | | typeof Float32 95 | | typeof Float64 96 | | typeof Int 97 | | typeof Pointer 98 | | typeof Str 99 | | typeof Nil 100 | | typeof Bool; 101 | } = { 102 | float32: Float32, 103 | float64: Float64, 104 | int: Int, 105 | ptr: Pointer, 106 | str: Str, 107 | nil: Nil, 108 | bool: Bool, 109 | }; 110 | 111 | public static serialize(neatTypeInstance: BasicNeatType | PointerNeatType): SerializedNeatType { 112 | return { 113 | __neatTypeClass: neatTypeInstance.type, 114 | value: neatTypeInstance.value, 115 | }; 116 | } 117 | 118 | public static deserialize( 119 | serializedNeatType: SerializedNeatType, 120 | ): BasicNeatType | PointerNeatType { 121 | // eslint-disable-next-line no-underscore-dangle 122 | return new NeatTypeSerializer.NEAT_TYPE_MAP[serializedNeatType.__neatTypeClass]( 123 | serializedNeatType.value, 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/utils/data.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | 3 | /** 4 | * Check if a value looks like a JSBI value. 5 | */ 6 | export function isJsbi(value: unknown): value is JSBI { 7 | // if it has __clzmsd it's probably jsbi 8 | return Array.isArray(value) && '__clzmsd' in value; 9 | } 10 | 11 | export function isPlainObject(object: {}): boolean { 12 | if (Object.getPrototypeOf(object) === null) { 13 | return true; 14 | } 15 | let proto = object; 16 | while (Object.getPrototypeOf(proto) !== null) { 17 | proto = Object.getPrototypeOf(proto); 18 | } 19 | return Object.getPrototypeOf(object) === proto; 20 | } 21 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/utils/int.ts: -------------------------------------------------------------------------------- 1 | import JSBI from 'jsbi'; 2 | 3 | // DataView extension to handle int64 / uint64, 4 | // where the actual range is 53-bits integer (a.k.a. safe integer) 5 | 6 | export function bufferToString(high: number, low: number, unsigned: boolean): string { 7 | let highNum = high; 8 | let lowNum = low; 9 | 10 | const radix = 10; 11 | const sign = !unsigned && high & 0x80000000; 12 | if (sign) { 13 | highNum = ~high; 14 | lowNum = 0x1_0000_0000 - low; 15 | } 16 | let str = ''; 17 | // eslint-disable-next-line no-constant-condition 18 | while (true) { 19 | const mod = (highNum % radix) * 0x1_0000_0000 + lowNum; 20 | highNum = Math.floor(highNum / radix); 21 | lowNum = Math.floor(mod / radix); 22 | str = (mod % radix).toString(radix) + str; 23 | if (!highNum && !lowNum) { 24 | break; 25 | } 26 | } 27 | if (sign) { 28 | str = '-' + str; 29 | } 30 | 31 | return str; 32 | } 33 | 34 | function fromStringToBuffer(view: DataView, offset: number, str: string): void { 35 | const radix = 10; 36 | let pos = 0; 37 | const len = str.length; 38 | let highNum = 0; 39 | let lowNum = 0; 40 | if (str.startsWith('-')) { 41 | pos++; 42 | } 43 | const sign = pos; 44 | while (pos < len) { 45 | const chr = parseInt(str[pos++], radix); 46 | if (Number.isNaN(chr) || chr < 0) { 47 | // NaN 48 | break; 49 | } 50 | lowNum = lowNum * radix + chr; 51 | highNum = highNum * radix + Math.floor(lowNum / 0x1_0000_0000); 52 | lowNum %= 0x1_0000_0000; 53 | } 54 | if (sign) { 55 | highNum = ~highNum; 56 | if (lowNum) { 57 | lowNum = 0x1_0000_0000 - lowNum; 58 | } else { 59 | highNum++; 60 | } 61 | } 62 | view.setUint32(offset, highNum); 63 | view.setUint32(offset + 4, lowNum); 64 | } 65 | 66 | export function setUint64(view: DataView, offset: number, value: bigint | string | number): void { 67 | if (typeof value === 'number') { 68 | const high = value / 0x1_0000_0000; 69 | const low = value; // high bits are truncated by DataView 70 | view.setUint32(offset, high); 71 | view.setUint32(offset + 4, low); 72 | } else if (typeof value === 'bigint') { 73 | view.setBigUint64(offset, value); 74 | } else if (typeof value === 'string') { 75 | fromStringToBuffer(view, offset, value); 76 | } 77 | } 78 | 79 | export function setInt64(view: DataView, offset: number, value: bigint | string | number): void { 80 | if (typeof value === 'number') { 81 | const high = Math.floor(value / 0x1_0000_0000); 82 | const low = value; // high bits are truncated by DataView 83 | view.setUint32(offset, high); 84 | view.setUint32(offset + 4, low); 85 | } else if (typeof value === 'bigint') { 86 | view.setBigInt64(offset, value); 87 | } else if (typeof value === 'string') { 88 | fromStringToBuffer(view, offset, value); 89 | } 90 | } 91 | 92 | export function getInt64(view: DataView, offset: number, useJSBI: boolean): bigint | JSBI { 93 | if (!view.getBigInt64 || useJSBI) { 94 | const high = view.getInt32(offset); 95 | const low = view.getUint32(offset + 4); 96 | return JSBI.BigInt(bufferToString(high, low, false)); 97 | } 98 | return view.getBigInt64(offset); 99 | } 100 | 101 | export function getUint64(view: DataView, offset: number, useJSBI: boolean): bigint | JSBI { 102 | if (!view.getBigUint64 || useJSBI) { 103 | const high = view.getUint32(offset); 104 | const low = view.getUint32(offset + 4); 105 | return JSBI.BigInt(bufferToString(high, low, true)); 106 | } 107 | return view.getBigUint64(offset); 108 | } 109 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/utils/prettyByte.ts: -------------------------------------------------------------------------------- 1 | export function prettyByte(byte: number): string { 2 | return `${byte < 0 ? '-' : ''}0x${Math.abs(byte).toString(16).padStart(2, '0')}`; 3 | } 4 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/utils/typedArrays.ts: -------------------------------------------------------------------------------- 1 | export function ensureUint8Array( 2 | buffer: ArrayLike | Uint8Array | ArrayBufferView | ArrayBuffer, 3 | ): Uint8Array { 4 | if (buffer instanceof Uint8Array) { 5 | return buffer; 6 | } 7 | if (ArrayBuffer.isView(buffer)) { 8 | return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); 9 | } 10 | if (buffer instanceof ArrayBuffer) { 11 | return new Uint8Array(buffer); 12 | } 13 | return Uint8Array.from(buffer); 14 | } 15 | 16 | export function createDataView( 17 | buffer: ArrayLike | ArrayBufferView | ArrayBuffer, 18 | ): DataView { 19 | if (buffer instanceof ArrayBuffer) { 20 | return new DataView(buffer); 21 | } 22 | 23 | const bufferView = ensureUint8Array(buffer); 24 | return new DataView(bufferView.buffer, bufferView.byteOffset, bufferView.byteLength); 25 | } 26 | -------------------------------------------------------------------------------- /packages/a-msgpack/src/utils/utf8.ts: -------------------------------------------------------------------------------- 1 | export const TEXT_ENCODING_AVAILABLE = 2 | process.env.TEXT_ENCODING !== 'never' && 3 | typeof TextEncoder !== 'undefined' && 4 | typeof TextDecoder !== 'undefined'; 5 | 6 | export function utf8Count(str: string): number { 7 | const strLength = str.length; 8 | 9 | let byteLength = 0; 10 | let pos = 0; 11 | while (pos < strLength) { 12 | let value = str.charCodeAt(pos++); 13 | 14 | if ((value & 0xffffff80) === 0) { 15 | // 1-byte 16 | byteLength++; 17 | continue; 18 | } else if ((value & 0xfffff800) === 0) { 19 | // 2-bytes 20 | byteLength += 2; 21 | } else { 22 | // handle surrogate pair 23 | if (value >= 0xd800 && value <= 0xdbff) { 24 | // high surrogate 25 | if (pos < strLength) { 26 | const extra = str.charCodeAt(pos); 27 | if ((extra & 0xfc00) === 0xdc00) { 28 | ++pos; 29 | value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; 30 | } 31 | } 32 | } 33 | 34 | if ((value & 0xffff0000) === 0) { 35 | // 3-byte 36 | byteLength += 3; 37 | } else { 38 | // 4-byte 39 | byteLength += 4; 40 | } 41 | } 42 | } 43 | return byteLength; 44 | } 45 | 46 | export function utf8EncodeJs(str: string, output: Uint8Array, outputOffset: number): void { 47 | const strLength = str.length; 48 | let offset = outputOffset; 49 | let pos = 0; 50 | while (pos < strLength) { 51 | let value = str.charCodeAt(pos++); 52 | 53 | if ((value & 0xffffff80) === 0) { 54 | // 1-byte 55 | output[offset++] = value; 56 | continue; 57 | } else if ((value & 0xfffff800) === 0) { 58 | // 2-bytes 59 | output[offset++] = ((value >> 6) & 0x1f) | 0xc0; 60 | } else { 61 | // handle surrogate pair 62 | if (value >= 0xd800 && value <= 0xdbff) { 63 | // high surrogate 64 | if (pos < strLength) { 65 | const extra = str.charCodeAt(pos); 66 | if ((extra & 0xfc00) === 0xdc00) { 67 | ++pos; 68 | value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; 69 | } 70 | } 71 | } 72 | 73 | if ((value & 0xffff0000) === 0) { 74 | // 3-byte 75 | output[offset++] = ((value >> 12) & 0x0f) | 0xe0; 76 | output[offset++] = ((value >> 6) & 0x3f) | 0x80; 77 | } else { 78 | // 4-byte 79 | output[offset++] = ((value >> 18) & 0x07) | 0xf0; 80 | output[offset++] = ((value >> 12) & 0x3f) | 0x80; 81 | output[offset++] = ((value >> 6) & 0x3f) | 0x80; 82 | } 83 | } 84 | 85 | output[offset++] = (value & 0x3f) | 0x80; 86 | } 87 | } 88 | 89 | const sharedTextEncoder = TEXT_ENCODING_AVAILABLE ? new TextEncoder() : undefined; 90 | export const TEXT_ENCODER_THRESHOLD = process.env.TEXT_ENCODING !== 'force' ? 200 : 0; 91 | 92 | function utf8EncodeTEencode(str: string, output: Uint8Array, outputOffset: number): void { 93 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 94 | output.set(sharedTextEncoder!.encode(str), outputOffset); 95 | } 96 | 97 | function utf8EncodeTEencodeInto(str: string, output: Uint8Array, outputOffset: number): void { 98 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 99 | sharedTextEncoder!.encodeInto(str, output.subarray(outputOffset)); 100 | } 101 | 102 | export const utf8EncodeTE = sharedTextEncoder?.encodeInto 103 | ? utf8EncodeTEencodeInto 104 | : utf8EncodeTEencode; 105 | 106 | const CHUNK_SIZE = 0x1_000; 107 | 108 | export function utf8DecodeJs(bytes: Uint8Array, inputOffset: number, byteLength: number): string { 109 | let offset = inputOffset; 110 | const end = offset + byteLength; 111 | 112 | const units: number[] = []; 113 | let result = ''; 114 | while (offset < end) { 115 | const byte1 = bytes[offset++]; 116 | if ((byte1 & 0x80) === 0) { 117 | // 1 byte 118 | units.push(byte1); 119 | } else if ((byte1 & 0xe0) === 0xc0) { 120 | // 2 bytes 121 | const byte2 = bytes[offset++] & 0x3f; 122 | units.push(((byte1 & 0x1f) << 6) | byte2); 123 | } else if ((byte1 & 0xf0) === 0xe0) { 124 | // 3 bytes 125 | const byte2 = bytes[offset++] & 0x3f; 126 | const byte3 = bytes[offset++] & 0x3f; 127 | units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3); 128 | } else if ((byte1 & 0xf8) === 0xf0) { 129 | // 4 bytes 130 | const byte2 = bytes[offset++] & 0x3f; 131 | const byte3 = bytes[offset++] & 0x3f; 132 | const byte4 = bytes[offset++] & 0x3f; 133 | let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; 134 | if (unit > 0xffff) { 135 | unit -= 0x10000; 136 | units.push(((unit >>> 10) & 0x3ff) | 0xd800); 137 | unit = 0xdc00 | (unit & 0x3ff); 138 | } 139 | units.push(unit); 140 | } else { 141 | units.push(byte1); 142 | } 143 | 144 | if (units.length >= CHUNK_SIZE) { 145 | result += String.fromCharCode(...units); 146 | units.length = 0; 147 | } 148 | } 149 | 150 | if (units.length > 0) { 151 | result += String.fromCharCode(...units); 152 | } 153 | 154 | return result; 155 | } 156 | 157 | const sharedTextDecoder = TEXT_ENCODING_AVAILABLE ? new TextDecoder() : null; 158 | export const TEXT_DECODER_THRESHOLD = process.env.TEXT_DECODER !== 'force' ? 200 : 0; 159 | 160 | export function utf8DecodeTD(bytes: Uint8Array, inputOffset: number, byteLength: number): string { 161 | const stringBytes = bytes.subarray(inputOffset, inputOffset + byteLength); 162 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 163 | return sharedTextDecoder!.decode(stringBytes); 164 | } 165 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/CachedKeyDecoder.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { CachedKeyDecoder } from '../src/CachedKeyDecoder'; 4 | import { utf8EncodeJs, utf8Count } from '../src/utils/utf8'; 5 | 6 | function tryDecode(decoder: CachedKeyDecoder, str: string): string { 7 | const byteLength = utf8Count(str); 8 | const bytes = new Uint8Array(byteLength); 9 | utf8EncodeJs(str, bytes, 0); 10 | if (!decoder.canBeCached(byteLength)) { 11 | throw new Error('Unexpected precondition'); 12 | } 13 | return decoder.decode(bytes, 0, byteLength); 14 | } 15 | 16 | describe('CachedKeyDecoder', () => { 17 | describe('basic behavior', () => { 18 | test('decodes a string', () => { 19 | const decoder = new CachedKeyDecoder(); 20 | 21 | expect(tryDecode(decoder, 'foo')).toEqual('foo'); 22 | expect(tryDecode(decoder, 'foo')).toEqual('foo'); 23 | expect(tryDecode(decoder, 'foo')).toEqual('foo'); 24 | }); 25 | 26 | test('decodes strings', () => { 27 | const decoder = new CachedKeyDecoder(); 28 | 29 | expect(tryDecode(decoder, 'foo')).toEqual('foo'); 30 | expect(tryDecode(decoder, 'bar')).toEqual('bar'); 31 | expect(tryDecode(decoder, 'foo')).toEqual('foo'); 32 | }); 33 | 34 | test('decodes strings with purging records', () => { 35 | const decoder = new CachedKeyDecoder(16, 4); 36 | 37 | for (let i = 0; i < 100; i++) { 38 | expect(tryDecode(decoder, 'foo1')).toEqual('foo1'); 39 | expect(tryDecode(decoder, 'foo2')).toEqual('foo2'); 40 | expect(tryDecode(decoder, 'foo3')).toEqual('foo3'); 41 | expect(tryDecode(decoder, 'foo4')).toEqual('foo4'); 42 | expect(tryDecode(decoder, 'foo5')).toEqual('foo5'); 43 | } 44 | }); 45 | }); 46 | 47 | describe('edge cases', () => { 48 | // len=0 is not supported because it is just an empty string 49 | test('decodes str with len=1', () => { 50 | const decoder = new CachedKeyDecoder(); 51 | 52 | expect(tryDecode(decoder, 'f')).toEqual('f'); 53 | expect(tryDecode(decoder, 'a')).toEqual('a'); 54 | expect(tryDecode(decoder, 'f')).toEqual('f'); 55 | expect(tryDecode(decoder, 'a')).toEqual('a'); 56 | }); 57 | 58 | test('decodes str with len=maxKeyLength', () => { 59 | const decoder = new CachedKeyDecoder(1); 60 | 61 | expect(tryDecode(decoder, 'f')).toEqual('f'); 62 | expect(tryDecode(decoder, 'a')).toEqual('a'); 63 | expect(tryDecode(decoder, 'f')).toEqual('f'); 64 | expect(tryDecode(decoder, 'a')).toEqual('a'); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/ExtensionCodec.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { encode, decode, ExtensionCodec } from '../src'; 4 | 5 | describe('ExtensionCodec', () => { 6 | describe('custom extensions', () => { 7 | const extensionCodec = new ExtensionCodec(); 8 | 9 | // Set 10 | extensionCodec.register({ 11 | type: 0, 12 | identifier: (data) => data instanceof Set, 13 | encode: (object: Set): Uint8Array => { 14 | return encode([...object]); 15 | }, 16 | decode: (data: Uint8Array) => { 17 | const array = decode(data) as unknown[]; 18 | return new Set(array); 19 | }, 20 | }); 21 | 22 | // DateTime 23 | extensionCodec.register({ 24 | type: 1, 25 | identifier: (data) => data instanceof Date, 26 | encode: (object: Date): Uint8Array => { 27 | return encode(object.getTime()); 28 | }, 29 | decode: (data: Uint8Array) => { 30 | const d = decode(data); 31 | return new Date(Number(d)); 32 | }, 33 | }); 34 | 35 | test('encodes and decodes custom data types (synchronously)', () => { 36 | const set = new Set([1, 2, 3]); 37 | const date = new Date(); 38 | const encoded = encode([set, date], { extensionCodec }); 39 | expect(decode(encoded, { extensionCodec })).toEqual([set, date]); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/decode-max-length.spec.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode } from '../src'; 2 | // import { Decoder } from '../src/Decoder'; 3 | import { ExtensionCodec } from '../src/ExtensionCodec'; 4 | import { DecodeOptions } from '../src/decode'; 5 | import { POINTER_TYPE } from '../src/neat'; 6 | import { Pointer } from '../src/neat/NeatTypes'; 7 | import { decodePointer, encodePointer } from '../src/neat/extensions'; 8 | 9 | const Codec = new ExtensionCodec(); 10 | 11 | Codec.register({ 12 | type: POINTER_TYPE, 13 | identifier: (data: unknown) => data instanceof Pointer, 14 | encode: encodePointer(Codec), 15 | decode: decodePointer(Codec), 16 | }); 17 | 18 | describe.each([ 19 | ['maxStrLength', 'foo'], 20 | ['maxBinLength', new Pointer(['Dodgers'])], 21 | ['maxArrayLength', [1, 2, 3]], 22 | ['maxMapLength', { foo: 1, bar: 1, baz: 3 }], 23 | ['maxExtLength', new Pointer(['Dodgers'])], 24 | ])('decode with maxLength specified', (optionName, value) => { 25 | const input = encode(value, { extensionCodec: Codec }); 26 | const options: DecodeOptions = { [optionName]: 1, extensionCodec: Codec }; 27 | 28 | test(`throws errors for ${optionName} (synchronous)`, () => { 29 | expect(() => { 30 | decode(input, options); 31 | }).toThrow(/max length exceeded/i); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/edge-cases.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { encode, decode } from '../src'; 4 | 5 | describe('edge cases', () => { 6 | describe('try to encode cyclic refs', () => { 7 | test('throws errors on arrays', () => { 8 | const cyclicRefs: unknown[] = []; 9 | cyclicRefs.push(cyclicRefs); 10 | expect(() => { 11 | encode(cyclicRefs); 12 | }).toThrow(/too deep/i); 13 | }); 14 | 15 | test('throws errors on objects', () => { 16 | const cyclicRefs: Record = {}; 17 | cyclicRefs.foo = cyclicRefs; 18 | expect(() => { 19 | encode(cyclicRefs); 20 | }).toThrow(/too deep/i); 21 | }); 22 | }); 23 | 24 | describe('try to encode non-encodable objects', () => { 25 | test('throws errors', () => { 26 | expect(() => { 27 | encode(Symbol('Astros are cheaters')); 28 | }).toThrow(/unrecognized object/i); 29 | }); 30 | }); 31 | 32 | describe('try to decode invlid MessagePack binary', () => { 33 | test('throws errors', () => { 34 | const TYPE_NEVER_USED = 0xc1; 35 | 36 | expect(() => { 37 | decode([TYPE_NEVER_USED]); 38 | }).toThrow(/unrecognized type byte/i); 39 | }); 40 | }); 41 | 42 | describe('try to decode insufficient data', () => { 43 | test('throws errors (synchronous)', () => { 44 | expect(() => { 45 | decode([196, 3, 115, 116]); 46 | }).toThrow(/Insufficient data/i); 47 | }); 48 | test('throws errors for extentions (synchronous)', () => { 49 | expect(() => { 50 | decode([213, 0, 145]); 51 | }).toThrow(/Insufficient data/i); 52 | }); 53 | }); 54 | 55 | describe('try to decode data with extra bytes', () => { 56 | test('throws errors (synchronous)', () => { 57 | expect(() => { 58 | decode([ 59 | 0x90, // fixarray size=0 60 | ...encode(null), 61 | ]); 62 | }).toThrow(RangeError); 63 | }); 64 | }); 65 | 66 | describe('MAX_SAFE_INTEGER as float64', () => { 67 | const input = 9007199254740992; 68 | const out = new Uint8Array([203, 67, 64, 0, 0, 0, 0, 0, 0]); 69 | 70 | test('encode', () => { 71 | expect(encode(input)).toEqual(out); 72 | }); 73 | 74 | test('decode', () => { 75 | expect(decode(out)).toEqual(input); 76 | }); 77 | }); 78 | 79 | test('decode without cachedKeyDecoder', () => { 80 | const input = { a: 'b', c: 'd' }; 81 | const out = new Uint8Array([130, 196, 1, 97, 196, 1, 98, 196, 1, 99, 196, 1, 100]); 82 | 83 | expect( 84 | decode(out, { 85 | cachedKeyDecoder: null, 86 | }), 87 | ).toEqual(input); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/encoder-setup.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | 3 | global.TextDecoder = util.TextDecoder; 4 | global.TextEncoder = util.TextEncoder; 5 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/neat/neatTypes.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import JSBI from 'jsbi'; 4 | 5 | import { Bool, Float32, Float64, Int, Nil, Pointer, Str, Wildcard } from '../../src/neat/NeatTypes'; 6 | 7 | describe('NEAT types', () => { 8 | const maxInt = Number.MAX_SAFE_INTEGER; 9 | const maxIntStr = Number.MAX_SAFE_INTEGER + ''; 10 | const maxIntLastDigit = parseInt(maxIntStr[maxIntStr.length - 1], 10); 11 | const moreThanMaxInt = maxIntStr.substr(0, maxIntStr.length - 1) + (maxIntLastDigit + 1); 12 | 13 | test('Bool', () => { 14 | expect(new Bool(true).value).toEqual(true); 15 | expect(new Bool(false).value).toEqual(false); 16 | expect(new Bool(1).value).toEqual(true); 17 | expect(new Bool(0).value).toEqual(false); 18 | expect(new Bool('true').value).toEqual(true); 19 | expect(new Bool('').value).toEqual(false); 20 | 21 | expect(new Bool(true).toString()).toEqual('true'); 22 | expect(new Bool(false).toString()).toEqual('false'); 23 | }); 24 | 25 | test('Float32', () => { 26 | expect(new Float32(1).value).toEqual(1.0); 27 | expect(new Float32(1.2).value).toEqual(1.2); 28 | expect(new Float32(0.3).value).toEqual(0.3); 29 | expect(new Float32(1.0).value).toEqual(1.0); 30 | expect(new Float32('1.2').value).toEqual(1.2); 31 | 32 | expect(new Float32(1).toString()).toEqual('1.0'); 33 | expect(new Float32(1.2).toString()).toEqual('1.2'); 34 | expect(new Float32(0.3).toString()).toEqual('0.3'); 35 | expect(new Float32(1.0).toString()).toEqual('1.0'); 36 | }); 37 | 38 | test('Float64', () => { 39 | expect(new Float64(1).value).toEqual(1.0); 40 | expect(new Float64(1.2).value).toEqual(1.2); 41 | expect(new Float64(0.3).value).toEqual(0.3); 42 | expect(new Float64(1.0).value).toEqual(1.0); 43 | expect(new Float64('1.2').value).toEqual(1.2); 44 | 45 | expect(new Float64(1).toString()).toEqual('1.0'); 46 | expect(new Float64(1.2).toString()).toEqual('1.2'); 47 | expect(new Float64(0.3).toString()).toEqual('0.3'); 48 | expect(new Float64(1.0).toString()).toEqual('1.0'); 49 | }); 50 | 51 | test('Int', () => { 52 | expect(new Int(1.2).value).toEqual(1); 53 | expect(new Int(1).value).toEqual(1); 54 | expect(new Int('1.2').value).toEqual(1); 55 | expect(new Int('1').value).toEqual(1); 56 | expect(new Int(maxInt).value).toEqual(maxInt); 57 | expect(new Int(moreThanMaxInt).value).toEqual(BigInt(moreThanMaxInt)); 58 | expect(new Int(moreThanMaxInt, true).value).toEqual(JSBI.BigInt(moreThanMaxInt)); 59 | expect(new Int(maxInt * -1).value).toEqual(maxInt * -1); 60 | expect(new Int('-' + moreThanMaxInt).value).toEqual(BigInt(moreThanMaxInt) * BigInt(-1)); 61 | 62 | expect(new Int(1.2).toString()).toEqual('1'); 63 | expect(new Int(1).toString()).toEqual('1'); 64 | expect(new Int('1.2').toString()).toEqual('1'); 65 | expect(new Int('1').toString()).toEqual('1'); 66 | expect(new Int(maxInt).toString()).toEqual(maxInt.toString()); 67 | expect(new Int(moreThanMaxInt).toString()).toEqual(moreThanMaxInt); 68 | expect(new Int(moreThanMaxInt, true).toString()).toEqual(moreThanMaxInt); 69 | expect(new Int(maxInt * -1).toString()).toEqual((maxInt * -1).toString()); 70 | expect(new Int('-' + moreThanMaxInt).toString()).toEqual('-' + moreThanMaxInt); 71 | }); 72 | 73 | test('Nil', () => { 74 | // Explicitly test that nil will always return null regardless of what args are passed 75 | // @ts-expect-error Explicitly test nil 76 | expect(new Nil(1).value).toEqual(null); 77 | // @ts-expect-error Explicitly test nil 78 | expect(new Nil(true).value).toEqual(null); 79 | // @ts-expect-error Explicitly test nil 80 | expect(new Nil([]).value).toEqual(null); 81 | // @ts-expect-error Explicitly test nil 82 | expect(new Nil(false).value).toEqual(null); 83 | // @ts-expect-error Explicitly test nil 84 | expect(new Nil({}).value).toEqual(null); 85 | 86 | // @ts-expect-error Explicitly test nil 87 | expect(new Nil(1).toString()).toEqual('null'); 88 | // @ts-expect-error Explicitly test nil 89 | expect(new Nil(true).toString()).toEqual('null'); 90 | // @ts-expect-error Explicitly test nil 91 | expect(new Nil([]).toString()).toEqual('null'); 92 | // @ts-expect-error Explicitly test nil 93 | expect(new Nil(false).toString()).toEqual('null'); 94 | // @ts-expect-error Explicitly test nil 95 | expect(new Nil({}).toString()).toEqual('null'); 96 | }); 97 | 98 | test('Pointer', () => { 99 | const ptrArray = ['pointer', 'to', { some: 'stuff' }]; 100 | const ptrString = 'pointer/to/' + JSON.stringify({ some: 'stuff' }); 101 | 102 | expect(new Pointer(ptrArray).value).toEqual(ptrArray); 103 | expect(new Pointer(ptrString).value).toEqual(ptrArray); 104 | expect(new Pointer([]).value).toEqual([]); 105 | expect(new Pointer('').value).toEqual([]); 106 | expect(new Pointer('/').value).toEqual([]); 107 | expect(new Pointer(true).value).toEqual([true]); 108 | expect(new Pointer({ object: 'result' }).value).toEqual([{ object: 'result' }]); 109 | 110 | expect(new Pointer(ptrArray).toString()).toEqual(ptrString); 111 | expect(new Pointer(ptrString).toString()).toEqual(ptrString); 112 | expect(new Pointer([]).toString()).toEqual(''); 113 | expect(new Pointer('').toString()).toEqual(''); 114 | expect(new Pointer('/').toString()).toEqual(''); 115 | expect(new Pointer(true).toString()).toEqual('true'); 116 | expect(new Pointer({ object: 'result' }).toString()).toEqual('{"object":"result"}'); 117 | }); 118 | 119 | test('Str', () => { 120 | expect(new Str('string').value).toEqual('string'); 121 | expect(new Str(true).value).toEqual('true'); 122 | expect(new Str(false).value).toEqual('false'); 123 | expect(new Str(1).value).toEqual('1'); 124 | expect(new Str(BigInt(moreThanMaxInt)).value).toEqual(moreThanMaxInt); 125 | expect(new Str(JSBI.BigInt(moreThanMaxInt)).value).toEqual(moreThanMaxInt); 126 | expect(new Str(null).value).toEqual('null'); 127 | expect(new Str(undefined).value).toEqual(''); 128 | expect(new Str({ hello: 'world' }).value).toEqual(JSON.stringify({ hello: 'world' })); 129 | expect(new Str([1, 2]).value).toEqual('[1,2]'); 130 | 131 | expect(new Str('string').toString()).toEqual('string'); 132 | expect(new Str(true).toString()).toEqual('true'); 133 | expect(new Str(false).toString()).toEqual('false'); 134 | expect(new Str(1).toString()).toEqual('1'); 135 | expect(new Str(BigInt(moreThanMaxInt)).toString()).toEqual(moreThanMaxInt); 136 | expect(new Str(JSBI.BigInt(moreThanMaxInt)).toString()).toEqual(moreThanMaxInt); 137 | expect(new Str(null).toString()).toEqual('null'); 138 | expect(new Str(undefined).toString()).toEqual(''); 139 | expect(new Str({ hello: 'world' }).toString()).toEqual(JSON.stringify({ hello: 'world' })); 140 | expect(new Str([1, 2]).toString()).toEqual('[1,2]'); 141 | }); 142 | 143 | test('Wildcard', () => { 144 | const wildcardValue = '*'; 145 | // Explicitly test that Wildcard will always return null regardless of what args are passed 146 | // @ts-expect-error Explicitly test wildcard 147 | expect(new Wildcard(1).value).toEqual(null); 148 | // @ts-expect-error Explicitly test wildcard 149 | expect(new Wildcard(true).value).toEqual(null); 150 | // @ts-expect-error Explicitly test wildcard 151 | expect(new Wildcard([]).value).toEqual(null); 152 | // @ts-expect-error Explicitly test wildcard 153 | expect(new Wildcard(false).value).toEqual(null); 154 | // @ts-expect-error Explicitly test wildcard 155 | expect(new Wildcard({}).value).toEqual(null); 156 | 157 | // @ts-expect-error Explicitly test wildcard 158 | expect(new Wildcard(1).toString()).toEqual(wildcardValue); 159 | // @ts-expect-error Explicitly test wildcard 160 | expect(new Wildcard(true).toString()).toEqual(wildcardValue); 161 | // @ts-expect-error Explicitly test wildcard 162 | expect(new Wildcard([]).toString()).toEqual(wildcardValue); 163 | // @ts-expect-error Explicitly test wildcard 164 | expect(new Wildcard(false).toString()).toEqual(wildcardValue); 165 | // @ts-expect-error Explicitly test wildcard 166 | expect(new Wildcard({}).toString()).toEqual(wildcardValue); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/neat/utils.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | /* eslint-disable @typescript-eslint/naming-convention */ 3 | 4 | import { encode } from '../../src'; 5 | import { Bool, Float32, Float64, Int, Nil, Pointer, Str } from '../../src/neat/NeatTypes'; 6 | import { isNeatType, NeatTypeSerializer, sortMapByKey } from '../../src/neat/utils'; 7 | 8 | function testSerializeAndDeserialize( 9 | value: Bool | Float32 | Float64 | Nil | Int | Str | Pointer, 10 | ): void { 11 | const serializedValue = NeatTypeSerializer.serialize(value); 12 | const deserializedValue = NeatTypeSerializer.deserialize(serializedValue); 13 | expect(serializedValue.value).toEqual(value.value); 14 | /* eslint-disable-next-line */ 15 | expect(serializedValue.__neatTypeClass).toEqual(value.type); 16 | expect(deserializedValue.value).toEqual(value.value); 17 | expect(deserializedValue.type).toEqual(value.type); 18 | } 19 | 20 | describe('isNeatType', () => { 21 | test('should validate proper Neat objects', () => { 22 | const exampleStr = new Str('arista'); 23 | const exampleNil = new Nil(); 24 | const exampleBool = new Bool(true); 25 | const exampleInt = new Int(7); 26 | const exampleFloat32 = new Float32(91.3); 27 | const exampleFloat64 = new Float64(90000.25); 28 | const examplePtr = new Pointer(['path', 'to', 'fivepm']); 29 | 30 | expect(isNeatType(exampleStr)).toBeTruthy(); 31 | expect(isNeatType(exampleNil)).toBeTruthy(); 32 | expect(isNeatType(exampleBool)).toBeTruthy(); 33 | expect(isNeatType(exampleInt)).toBeTruthy(); 34 | expect(isNeatType(exampleFloat32)).toBeTruthy(); 35 | expect(isNeatType(exampleFloat64)).toBeTruthy(); 36 | expect(isNeatType(examplePtr)).toBeTruthy(); 37 | }); 38 | test('should reject regular javascript elements', () => { 39 | expect(isNeatType('arista')).toBeFalsy(); 40 | expect(isNeatType(null)).toBeFalsy(); 41 | expect(isNeatType(true)).toBeFalsy(); 42 | expect(isNeatType(7)).toBeFalsy(); 43 | expect(isNeatType(91.3)).toBeFalsy(); 44 | expect(isNeatType(90000.25)).toBeFalsy(); 45 | expect(isNeatType(['path', 'to', 'fivepm'])).toBeFalsy(); 46 | expect(isNeatType({ type: 'device', value: 'red_herring' })).toBeFalsy(); 47 | expect(isNeatType({ type: 'str', value: 'nefarious', valueTwo: 'fake neattype' })).toBeFalsy(); 48 | const fakePtr = { type: 'ptr', value: ['ptr', 'w/o', 'delimiter', 'is', 'just', 'array'] }; 49 | expect(isNeatType(fakePtr)).toBeFalsy(); 50 | }); 51 | }); 52 | 53 | describe('sortMapByKey', () => { 54 | test('should return -1, if value a is smaller', () => { 55 | expect(sortMapByKey([encode('a'), 'a'], [encode('b'), 'b'])).toEqual(-1); 56 | expect(sortMapByKey([encode('a'), 'a'], [encode('aa'), 'aa'])).toEqual(-1); 57 | expect(sortMapByKey([encode(1), 1], [encode(2), 2])).toEqual(-1); 58 | expect( 59 | sortMapByKey( 60 | [new Uint8Array([1, 2, 3]), [1, 2, 3]], 61 | [new Uint8Array([1, 2, 3, 4]), [1, 2, 3, 4]], 62 | ), 63 | ).toEqual(-1); 64 | }); 65 | 66 | test('should return 1, if value a is larger', () => { 67 | expect(sortMapByKey([encode('b'), 'b'], [encode('a'), 'a'])).toEqual(1); 68 | expect(sortMapByKey([encode('bb'), 'bb'], [encode('b'), 'b'])).toEqual(1); 69 | expect(sortMapByKey([encode(2), 2], [encode(1), 1])).toEqual(1); 70 | expect( 71 | sortMapByKey( 72 | [new Uint8Array([1, 2, 3, 4]), [1, 2, 3, 4]], 73 | [new Uint8Array([1, 2, 3]), [1, 2, 3]], 74 | ), 75 | ).toEqual(1); 76 | }); 77 | 78 | test('should return 0, if values are equal', () => { 79 | expect(sortMapByKey([encode('a'), 'a'], [encode('a'), 'a'])).toEqual(0); 80 | expect(sortMapByKey([encode(1), 1], [encode(1), 1])).toEqual(0); 81 | expect( 82 | sortMapByKey([new Uint8Array([1, 2, 3]), [1, 2, 3]], [new Uint8Array([1, 2, 3]), [1, 2, 3]]), 83 | ).toEqual(0); 84 | const samePointer: [Uint8Array, unknown] = [new Uint8Array([1, 2, 3]), [1, 2, 3]]; 85 | expect(sortMapByKey(samePointer, samePointer)).toEqual(0); 86 | }); 87 | }); 88 | 89 | describe('NeatTypeSerializer', () => { 90 | test('Bool', () => { 91 | testSerializeAndDeserialize(new Bool(true)); 92 | testSerializeAndDeserialize(new Bool(false)); 93 | }); 94 | test('Float32', () => { 95 | testSerializeAndDeserialize(new Float32(123.456)); 96 | }); 97 | test('Float64', () => { 98 | testSerializeAndDeserialize(new Float64(8274.51123)); 99 | }); 100 | test('Int', () => { 101 | testSerializeAndDeserialize(new Int(67)); 102 | }); 103 | test('Str', () => { 104 | testSerializeAndDeserialize(new Str('the answer is now')); 105 | }); 106 | test('Pointer', () => { 107 | testSerializeAndDeserialize(new Pointer(['path', 'to', 'glory'])); 108 | }); 109 | test('Nil', () => { 110 | testSerializeAndDeserialize(new Nil()); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/utils/int.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import JSBI from 'jsbi'; 4 | 5 | import { setInt64, getInt64, getUint64, setUint64 } from '../../src/utils/int'; 6 | 7 | describe('codec: int64 / uint64', () => { 8 | describe.each([ 9 | ['ZERO', 0], 10 | ['ONE', 1], 11 | ['MINUS_ONE', -1], 12 | ['X_FF', 0xff], 13 | ['MINUS_X_FF', -0xff], 14 | ['INT32_MAX', 0x7fffffff], 15 | ['INT32_MIN', -0x7fffffff - 1], 16 | ['MAX_SAFE_INTEGER', Number.MAX_SAFE_INTEGER], 17 | ['MIN_SAFE_INTEGER', Number.MIN_SAFE_INTEGER], 18 | ['MAX_SAFE_INTEGER as String', Number.MAX_SAFE_INTEGER.toString()], 19 | ['MIN_SAFE_INTEGER as String', Number.MIN_SAFE_INTEGER.toString()], 20 | ['positive int64 as BigInt', BigInt(Number.MAX_SAFE_INTEGER + 1)], 21 | ['positive int64 as Number', Number.MAX_SAFE_INTEGER + 1], 22 | ['positive int64 as String', (Number.MAX_SAFE_INTEGER + 1).toString()], 23 | ['negative int64 as BigInt', BigInt(Number.MIN_SAFE_INTEGER - 1)], 24 | ['negative int64 as Number', Number.MIN_SAFE_INTEGER - 1], 25 | ['negative int64 as String', (Number.MIN_SAFE_INTEGER - 1).toString()], 26 | ])('int 64', (name, value) => { 27 | test(`sets and gets ${name} ${value}`, () => { 28 | const b = new Uint8Array(8); 29 | const view = new DataView(b.buffer); 30 | setInt64(view, 0, value); 31 | expect(getInt64(view, 0, false)).toEqual(BigInt(value)); 32 | }); 33 | 34 | test(`sets and gets ${name} ${value} as JSBI when forced`, () => { 35 | const b = new Uint8Array(8); 36 | const view = new DataView(b.buffer); 37 | setInt64(view, 0, value); 38 | const result = typeof value === 'bigint' ? value.toString() : value; 39 | expect(getInt64(view, 0, true)).toEqual(JSBI.BigInt(result)); 40 | }); 41 | 42 | test(`sets and gets ${name} ${value} as JSBI when BigInt not available`, () => { 43 | const b = new Uint8Array(8); 44 | const view = new DataView(b.buffer); 45 | // @ts-expect-error This is for testing DataViews that do not implement int64 46 | view.getBigInt64 = undefined; 47 | setInt64(view, 0, value); 48 | const result = typeof value === 'bigint' ? value.toString() : value; 49 | expect(getInt64(view, 0, false)).toEqual(JSBI.BigInt(result)); 50 | }); 51 | }); 52 | 53 | describe.each([ 54 | ['ZERO', 0], 55 | ['ONE', 1], 56 | ['X_FF', 0xff], 57 | ['INT32_MAX', 0x7fffffff], 58 | ['MAX_SAFE_INTEGER', Number.MAX_SAFE_INTEGER], 59 | ['positive int64 as BigInt', BigInt(Number.MAX_SAFE_INTEGER + 1)], 60 | ['positive int64 as Number', Number.MAX_SAFE_INTEGER + 1], 61 | ['positive int64 as String', (Number.MAX_SAFE_INTEGER + 1).toString()], 62 | ])('uint 64', (name, value) => { 63 | test(`sets and gets ${name} ${value}`, () => { 64 | const b = new Uint8Array(8); 65 | const view = new DataView(b.buffer); 66 | setUint64(view, 0, value); 67 | expect(getUint64(view, 0, false)).toEqual(BigInt(value)); 68 | }); 69 | 70 | test(`sets and gets ${name} ${value} as JSBI when forced`, () => { 71 | const b = new Uint8Array(8); 72 | const view = new DataView(b.buffer); 73 | setUint64(view, 0, value); 74 | const result = typeof value === 'bigint' ? value.toString() : value; 75 | expect(getUint64(view, 0, true)).toEqual(JSBI.BigInt(result)); 76 | }); 77 | 78 | test(`sets and gets ${name} ${value} as JSBI when BigInt not available`, () => { 79 | const b = new Uint8Array(8); 80 | const view = new DataView(b.buffer); 81 | // @ts-expect-error This is for testing DataViews that do not implement int64 82 | view.getBigUint64 = undefined; 83 | setUint64(view, 0, value); 84 | const result = typeof value === 'bigint' ? value.toString() : value; 85 | expect(getUint64(view, 0, false)).toEqual(JSBI.BigInt(result)); 86 | }); 87 | }); 88 | 89 | describe.each([ 90 | ['NaN', NaN], 91 | ['NaN as String', 'NaN'], 92 | ['UNDEFINED', undefined], 93 | ['UNDEFINED as String', 'undefined'], 94 | ])('edge cases', (name, value) => { 95 | test(`int64 sets and gets ${name} ${value}`, () => { 96 | const b = new Uint8Array(8); 97 | const view = new DataView(b.buffer); 98 | // @ts-expect-error So that we can test undefined 99 | setInt64(view, 0, value); 100 | expect(getInt64(view, 0, false)).toEqual(BigInt(0)); 101 | }); 102 | 103 | test(`uint64 sets and gets ${name} ${value}`, () => { 104 | const b = new Uint8Array(8); 105 | const view = new DataView(b.buffer); 106 | // @ts-expect-error So that we can test undefined 107 | setUint64(view, 0, value); 108 | expect(getUint64(view, 0, false)).toEqual(BigInt(0)); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /packages/a-msgpack/test/utils/typedArrays.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { ensureUint8Array, createDataView } from '../../src/utils/typedArrays'; 4 | 5 | describe.each([ 6 | [ensureUint8Array, [1, 2], Uint8Array], 7 | [ensureUint8Array, new Uint8Array(2), Uint8Array], 8 | [ensureUint8Array, new ArrayBuffer(2), Uint8Array], 9 | [ensureUint8Array, new Int8Array(), Uint8Array], 10 | [createDataView, new ArrayBuffer(2), DataView], 11 | [createDataView, new Int8Array(), DataView], 12 | ])('typedArrays', (fn, input, type) => { 13 | test(`should return the type ${type.name} for ${fn.name}`, () => { 14 | // @ts-expect-error Easier than typing everything 15 | expect(fn.call(undefined, input)).toBeInstanceOf(type); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/a-msgpack/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true 4 | }, 5 | "extends": "./tsconfig.json", 6 | "exclude": ["benchmark-msgpack/**", "node_modules", "**/*.spec.ts", "test/**"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/a-msgpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "exclude": ["benchmark-msgpack/**"], 4 | "compilerOptions": { 5 | "target": "ES6" 6 | }, 7 | "typedocOptions": { 8 | "cleanOutputDir": true, 9 | "entryPoints": ["src/index.ts"], 10 | "exclude": "test/**", 11 | "excludeExternals": true, 12 | "excludePrivate": true, 13 | "hideGenerator": true, 14 | "out": "docs", 15 | "readme": "./README.md" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/a-msgpack/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface PlainObject { 2 | [key: string]: T; 3 | } 4 | 5 | export * from './neat'; 6 | -------------------------------------------------------------------------------- /packages/a-msgpack/types/neat.ts: -------------------------------------------------------------------------------- 1 | import { Bool, Float32, Float64, Int, Nil, Pointer, Str } from '../src/neat/NeatTypes'; 2 | 3 | export type Element = string | number | Record | {} | unknown[] | boolean; 4 | export type PathElements = readonly Element[]; 5 | 6 | /** 7 | * The epoch timestamp in (milliseconds) 8 | */ 9 | export type EpochTimestamp = number; 10 | 11 | export interface Timestamp { 12 | seconds: number; 13 | nanos?: number; 14 | } 15 | 16 | export type BaseType = Bool | Float32 | Float64 | Int | Nil | Pointer | Str | Map; 17 | export type NeatType = BaseType | Element; 18 | 19 | export interface BasicNeatType { 20 | type: Bool['type'] | Float32['type'] | Float64['type'] | Int['type'] | Nil['type'] | Str['type']; 21 | value: unknown; 22 | } 23 | 24 | export type NeatTypeClass = 25 | | typeof Bool 26 | | typeof Float32 27 | | typeof Float64 28 | | typeof Int 29 | | typeof Nil 30 | | typeof Pointer 31 | | typeof Str; 32 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["**/*.js"], 5 | "extends": ["arista-js"] 6 | }, 7 | { 8 | "files": ["**/*.ts"], 9 | "extends": ["arista-ts"], 10 | "parserOptions": { 11 | "project": "./tsconfig.json" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/.gitignore: -------------------------------------------------------------------------------- 1 | .jest-cache 2 | *.log 3 | coverage 4 | dist 5 | docs 6 | es 7 | lib 8 | typings 9 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@arista/prettier-config" 2 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present, Arista Networks, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER 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 THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './src'; 2 | export * from './src'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cacheDirectory: '.jest-cache', 3 | coverageThreshold: { 4 | global: { 5 | branches: 100, 6 | functions: 100, 7 | lines: 100, 8 | statements: 100, 9 | }, 10 | }, 11 | coverageReporters: ['html', 'lcov'], 12 | collectCoverageFrom: [ 13 | '**/*.ts', 14 | '!src/index.ts', 15 | '!**/node_modules/**', 16 | '!**/lib/**', 17 | '!**/es/**', 18 | '!**/dist/**', 19 | '!**/coverage/**', 20 | '!**/*.spec.ts', 21 | '!**/*.config.js', 22 | ], 23 | transform: { 24 | '^.+\\.ts$': 'ts-jest', 25 | }, 26 | roots: ['/test', '/src'], 27 | setupFiles: ['/test/websocket-setup.ts'], 28 | }; 29 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudvision-connector", 3 | "version": "5.0.2", 4 | "description": "A module to communicate with the CloudVision API server", 5 | "author": "extensions@arista.com", 6 | "homepage": "https://aristanetworks.github.io/cloudvision/modules/cloudvision_connector.html", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/aristanetworks/cloudvision.git" 10 | }, 11 | "license": "MIT", 12 | "main": "lib/cloudvision-connector.js", 13 | "unpkg": "dist/cloudvision-connector.js", 14 | "module": "es/cloudvision-connector.js", 15 | "typings": "typings/index.d.ts", 16 | "typedocMain": "src/index.ts", 17 | "scripts": { 18 | "build": "npm run clean && npm run build:es && npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run docs && npm run docs:md && npm run build:types", 19 | "build:commonjs": "cross-env NODE_ENV=cjs rollup -c -o lib/cloudvision-connector.js", 20 | "build:es": "cross-env NODE_ENV=es rollup -c -o es/cloudvision-connector.js", 21 | "build:try": "cross-env NODE_ENV=development INCLUDE_EXTERNAL=true rollup -c -o dist/cloudvision-connector-full.js", 22 | "build:types": "tsc --emitDeclarationOnly -p tsconfig-build.json", 23 | "build:umd": "cross-env NODE_ENV=development rollup -c -o dist/cloudvision-connector.js", 24 | "build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/cloudvision-connector.min.js", 25 | "check": "npm run lint && npm run type-check && npm run prettier-check", 26 | "clean": "rimraf dist lib coverage es docs typings", 27 | "docs": "typedoc --plugin none --out docs/html", 28 | "docs:md": "typedoc --plugin typedoc-plugin-markdown --theme markdown --out docs/md", 29 | "lint": "eslint --max-warnings 0 --ext .js,.ts *.js src/**/*.ts test/**/*.ts types/**/*.ts", 30 | "prepare": "npm run build", 31 | "prettier-check": "prettier -c *.js src/**/*.ts test/**/*.ts types/**/*.ts", 32 | "prettier-fix": "prettier --write *.js src/**/*.ts test/**/*.ts types/**/*.ts", 33 | "test": "cross-env NODE_ENV=production jest", 34 | "test:clean": "rimraf .jest-cache", 35 | "test:cov": "rimraf coverage && npm run test -- --coverage", 36 | "test:watch": "npm run test -- --watch", 37 | "type-check": "tsc --noEmit" 38 | }, 39 | "files": [ 40 | "dist", 41 | "lib", 42 | "es", 43 | "src", 44 | "typings" 45 | ], 46 | "keywords": [ 47 | "arista", 48 | "cloud", 49 | "cloudvision-api", 50 | "cloudvision-connector", 51 | "cloudvision", 52 | "cvp", 53 | "net-db", 54 | "sdn", 55 | "switch" 56 | ], 57 | "dependencies": { 58 | "a-msgpack": "5.0.2", 59 | "base64-js": "1.5.1", 60 | "jsbi": "4.3.0", 61 | "uuid": "9.0.0" 62 | }, 63 | "devDependencies": { 64 | "@arista/prettier-config": "1.1.4", 65 | "@rollup/plugin-commonjs": "22.0.2", 66 | "@rollup/plugin-node-resolve": "13.3.0", 67 | "@rollup/plugin-replace": "4.0.0", 68 | "@rollup/plugin-typescript": "8.5.0", 69 | "@types/base64-js": "1.3.0", 70 | "@types/imurmurhash": "0.1.1", 71 | "@types/jest": "27.5.2", 72 | "@types/js-yaml": "4.0.5", 73 | "@types/uuid": "8.3.4", 74 | "@typescript-eslint/eslint-plugin": "5.32.0", 75 | "@typescript-eslint/parser": "5.32.0", 76 | "babel-eslint": "10.1.0", 77 | "cross-env": "7.0.3", 78 | "eslint": "8.36.0", 79 | "eslint-config-arista-js": "2.0.3", 80 | "eslint-config-arista-ts": "2.0.1", 81 | "eslint-plugin-arista": "0.2.3", 82 | "eslint-plugin-import": "2.27.5", 83 | "jest": "27.5.1", 84 | "jest-environment-jsdom": "27.5.1", 85 | "js-yaml": "4.1.0", 86 | "mock-socket": "9.1.5", 87 | "prettier": "2.7.1", 88 | "rimraf": "3.0.2", 89 | "rollup": "2.79.1", 90 | "rollup-plugin-terser": "7.0.2", 91 | "ts-jest": "27.1.5", 92 | "tslib": "2.4.0", 93 | "typedoc": "0.23.26", 94 | "typedoc-plugin-markdown": "3.13.6", 95 | "typescript": "4.8.4" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import replace from '@rollup/plugin-replace'; 4 | import typescript from '@rollup/plugin-typescript'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | 7 | import packageJson from './package.json'; 8 | 9 | const env = process.env.NODE_ENV; 10 | 11 | const config = { 12 | input: 'src/index.ts', 13 | onwarn: (warning) => { 14 | throw new Error( 15 | `${warning.message} (${warning.loc.file}):${warning.loc.line}:${warning.loc.column}`, 16 | ); 17 | }, 18 | plugins: [typescript({ sourceMap: false })], 19 | }; 20 | 21 | const external = Object.keys(packageJson.dependencies); 22 | 23 | const globals = { 24 | 'a-msgpack': 'msgpack', 25 | 'base64-js': 'base64-js', 26 | 'uuid': 'uuid', 27 | 'jsbi': 'JSBI', 28 | }; 29 | 30 | // Build preserving environment variables 31 | if (env === 'es' || env === 'cjs') { 32 | config.external = external; 33 | config.output = { 34 | exports: 'named', 35 | format: env, 36 | globals, 37 | indent: false, 38 | }; 39 | config.plugins.push(nodeResolve(), commonjs()); 40 | } 41 | 42 | // Replace NODE_ENV variable 43 | if (env === 'development' || env === 'production') { 44 | if (!process.env.INCLUDE_EXTERNAL) { 45 | config.external = external; 46 | config.plugins.push(nodeResolve()); 47 | } else { 48 | config.plugins.push(nodeResolve({ browser: true })); 49 | } 50 | config.output = { 51 | exports: 'named', 52 | format: 'umd', 53 | globals, 54 | indent: false, 55 | name: 'CloudvisionConnector', 56 | }; 57 | config.plugins.push( 58 | replace({ 59 | preventAssignment: true, 60 | values: { 61 | 'process.env.NODE_ENV': JSON.stringify(env), 62 | 'process.env.TEXT_ENCODING': JSON.stringify('always'), 63 | 'process.env.TEXT_DECODER': JSON.stringify('always'), 64 | }, 65 | }), 66 | commonjs(), 67 | ); 68 | } 69 | 70 | if (env === 'production') { 71 | config.plugins.push(nodeResolve(), terser()); 72 | } 73 | 74 | export default config; 75 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/src/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | import { RequestContext, SearchType } from '../types'; 19 | 20 | export const ERROR = 'ERROR'; 21 | export const INFO = 'INFO'; 22 | export const WARN = 'WARN'; 23 | 24 | export const EOF = 'EOF'; 25 | 26 | /** 27 | * Status code for EOF (End Of File). 28 | */ 29 | export const EOF_CODE = 1001; 30 | 31 | /** 32 | * Status code for Active (when a subscription has been created and is active). 33 | */ 34 | export const ACTIVE_CODE = 3001; 35 | 36 | /** 37 | * Error status sent when a request has finished streaming data. 38 | */ 39 | export const RESPONSE_COMPLETED = 'RESPONSE_COMPLETED'; 40 | 41 | export const GET_REQUEST_COMPLETED = 'GetRequest'; 42 | 43 | export const CLOSE = 'close'; 44 | export const DEVICES_DATASET_ID = 'DEVICES_DATASET_ID'; 45 | export const GET = 'get'; 46 | export const GET_DATASETS = 'getDatasets'; 47 | export const GET_AND_SUBSCRIBE = 'getAndSubscribe'; 48 | export const GET_REGIONS_AND_CLUSTERS = 'getRegionsAndClusters'; 49 | export const PUBLISH = 'publish'; 50 | export const SUBSCRIBE = 'subscribe'; 51 | export const SEARCH = 'alpha/search'; 52 | export const SEARCH_SUBSCRIBE = 'alpha/searchSubscribe'; 53 | export const SERVICE_REQUEST = 'serviceRequest'; 54 | 55 | export const APP_DATASET_TYPE = 'app'; 56 | export const CONFIG_DATASET_TYPE = 'config'; 57 | export const DEVICE_DATASET_TYPE = 'device'; 58 | export const ALL_DATASET_TYPES: string[] = [ 59 | APP_DATASET_TYPE, 60 | CONFIG_DATASET_TYPE, 61 | DEVICE_DATASET_TYPE, 62 | ]; 63 | 64 | export const SEARCH_TYPE_ANY = 'ANY'; 65 | export const SEARCH_TYPE_IP = 'IP'; 66 | export const SEARCH_TYPE_MAC = 'MAC'; 67 | export const ALL_SEARCH_TYPES = new Set([ 68 | SEARCH_TYPE_ANY, 69 | SEARCH_TYPE_IP, 70 | SEARCH_TYPE_MAC, 71 | ]); 72 | 73 | export const CONNECTED = 'connected'; 74 | export const DISCONNECTED = 'disconnected'; 75 | export const ID = 'cloudvision-connector'; 76 | 77 | export const DEFAULT_CONTEXT: RequestContext = { 78 | command: 'NO_COMMAND', 79 | token: 'NO_TOKEN', 80 | encodedParams: 'NO_PARAMS', 81 | }; 82 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/src/emitter.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | import { EventCallback, RequestContext } from '../types'; 19 | import { EmitterEvents, EmitterRequestContext } from '../types/emitter'; 20 | 21 | import { DEFAULT_CONTEXT } from './constants'; 22 | 23 | /** 24 | * A class that tracks event callbacks by event type. 25 | * Callbacks get triggered, when an **emit** event is invoked for a given 26 | * event type. 27 | */ 28 | class Emitter { 29 | private events: EmitterEvents; 30 | 31 | private requestContext: EmitterRequestContext; 32 | 33 | public constructor() { 34 | this.events = new Map(); 35 | this.requestContext = new Map(); 36 | } 37 | 38 | /** 39 | * Returns the `Map` of events 40 | */ 41 | public getEventsMap(): EmitterEvents { 42 | return this.events; 43 | } 44 | 45 | /** 46 | * Returns the `Map` of requestContext 47 | */ 48 | public getRequestContextMap(): EmitterRequestContext { 49 | return this.requestContext; 50 | } 51 | 52 | /** 53 | * Returns true if and only if the emitter has callback(s) of the event type 54 | */ 55 | public has(eventType: string): boolean { 56 | return this.events.has(eventType); 57 | } 58 | 59 | /** 60 | * Adds the given callback to the event type. 61 | * 62 | * @returns The number of callbacks subscribed to the event type 63 | */ 64 | public bind(eventType: string, requestContext: RequestContext, callback: EventCallback): number { 65 | let callbacksForEvent = this.events.get(eventType); 66 | if (callbacksForEvent) { 67 | callbacksForEvent.push(callback); 68 | } else { 69 | callbacksForEvent = [callback]; 70 | this.events.set(eventType, callbacksForEvent); 71 | this.requestContext.set(eventType, requestContext); 72 | } 73 | 74 | return callbacksForEvent.length; 75 | } 76 | 77 | /** 78 | * Removes the given callback from the event type. 79 | * If this is the last callback subscribed to the given event type, then 80 | * the event type is removed from the `Map` of events. 81 | * 82 | * @returns The number of callbacks subscribed to the event type, or null 83 | * if the callback is not present in the `Map` of events. 84 | */ 85 | public unbind(eventType: string, callback: EventCallback): number | null { 86 | const callbacksForEvent = this.events.get(eventType); 87 | if (!callbacksForEvent) { 88 | return null; 89 | } 90 | 91 | const callbackIndex: number = callbacksForEvent.indexOf(callback); 92 | if (callbackIndex !== -1) { 93 | callbacksForEvent.splice(callbackIndex, 1); 94 | this.events.set(eventType, callbacksForEvent); 95 | } 96 | 97 | const callbacksForEventLen: number = callbacksForEvent.length; 98 | 99 | if (callbacksForEventLen === 0) { 100 | this.events.delete(eventType); 101 | this.requestContext.delete(eventType); 102 | } 103 | 104 | return callbacksForEventLen; 105 | } 106 | 107 | /** 108 | * Removes all callbacks for the given event. 109 | * This removes the event type from the `Map` of events. 110 | */ 111 | public unbindAll(eventType: string): void { 112 | this.events.delete(eventType); 113 | this.requestContext.delete(eventType); 114 | } 115 | 116 | /** 117 | * Triggers all callbacks bound to the given event type. The function can 118 | * take extra arguments, which are passed to the callback functions. 119 | */ 120 | public emit(eventType: string, ...args: unknown[]): void { 121 | const callbacksForEvent = this.events.get(eventType); 122 | if (!callbacksForEvent) { 123 | return; 124 | } 125 | 126 | // iterate in reverse order in case callback calls unbind on the eventType 127 | for (let i = callbacksForEvent.length - 1; i >= 0; i -= 1) { 128 | const requestContext = this.requestContext.get(eventType) || DEFAULT_CONTEXT; 129 | callbacksForEvent[i](requestContext, ...args); 130 | } 131 | } 132 | 133 | /** 134 | * Unbinds all callbacks for all events. 135 | * This clears the `Map` of events. 136 | */ 137 | public close(): void { 138 | this.events.clear(); 139 | this.requestContext.clear(); 140 | } 141 | } 142 | 143 | export default Emitter; 144 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, Arista Networks, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | export { 19 | createBaseType, 20 | isJsbi, 21 | isNeatType, 22 | Bool, 23 | Float32, 24 | Float64, 25 | Int, 26 | NeatTypes, 27 | NeatTypeSerializer, 28 | Nil, 29 | Pointer, 30 | Str, 31 | } from 'a-msgpack'; 32 | 33 | export { default } from './Connector'; 34 | export { default as Parser } from './Parser'; 35 | export { 36 | ACTIVE_CODE, 37 | ALL_DATASET_TYPES, 38 | APP_DATASET_TYPE, 39 | CLOSE, 40 | CONFIG_DATASET_TYPE, 41 | CONNECTED, 42 | DEVICE_DATASET_TYPE, 43 | DISCONNECTED, 44 | EOF, 45 | EOF_CODE, 46 | GET, 47 | GET_AND_SUBSCRIBE, 48 | GET_DATASETS, 49 | GET_REQUEST_COMPLETED, 50 | PUBLISH, 51 | SEARCH, 52 | SEARCH_SUBSCRIBE, 53 | SERVICE_REQUEST, 54 | SUBSCRIBE, 55 | } from './constants'; 56 | export { fromBinaryKey, toBinaryKey, sanitizeOptions } from './utils'; 57 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/src/instrumentation.ts: -------------------------------------------------------------------------------- 1 | import { ContextCommand, RequestContext, WsCommand } from '../types'; 2 | 3 | export interface InstrumentationConfig { 4 | commands: WsCommand[]; 5 | start(context: RequestContext): void; 6 | info(context: RequestContext): void; 7 | end(context: RequestContext): void; 8 | } 9 | 10 | interface Info { 11 | error?: string; 12 | message?: string; 13 | } 14 | 15 | // Use empty function as a default placeholder 16 | /* istanbul ignore next */ 17 | function emptyFunction(): void {} // eslint-disable-line @typescript-eslint/no-empty-function 18 | 19 | export default class Instrumentation { 20 | private enableInstrumentation = false; 21 | 22 | private instrumentedCommands: WsCommand[]; 23 | 24 | private startFunction: (context: RequestContext) => void; 25 | 26 | private infoFunction: (context: RequestContext, info: Info) => void; 27 | 28 | private endFunction: (context: RequestContext) => void; 29 | 30 | public constructor(config?: InstrumentationConfig) { 31 | if (config) { 32 | this.enableInstrumentation = true; 33 | } 34 | this.instrumentedCommands = config ? config.commands : []; 35 | this.startFunction = config ? config.start : emptyFunction; 36 | this.infoFunction = config ? config.info : emptyFunction; 37 | this.endFunction = config ? config.end : emptyFunction; 38 | } 39 | 40 | public callStart(command: ContextCommand, context: RequestContext): void { 41 | if (this.enableInstrumentation && this.instrumentedCommands.includes(command as WsCommand)) { 42 | this.startFunction(context); 43 | } 44 | } 45 | 46 | public callInfo(command: ContextCommand, context: RequestContext, info: Info): void { 47 | if (this.enableInstrumentation && this.instrumentedCommands.includes(command as WsCommand)) { 48 | this.infoFunction(context, info); 49 | } 50 | } 51 | 52 | public callEnd(command: ContextCommand, context: RequestContext): void { 53 | if (this.enableInstrumentation && this.instrumentedCommands.includes(command as WsCommand)) { 54 | this.endFunction(context); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/src/logger.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { CloudVisionStatus, LogLevel } from '../types'; 4 | 5 | import { ERROR, WARN } from './constants'; 6 | 7 | /** 8 | * Logs nicely formatted console log errors or warnings. 9 | */ 10 | export function log( 11 | level: LogLevel, 12 | message: string | null, 13 | status?: CloudVisionStatus, 14 | token?: string, 15 | ): void { 16 | const contextualizedMessage = message ? `${level}: ${message}` : `${level}: No message provided`; 17 | const statusMessage = status 18 | ? `Status Code: ${status.code}, Status Message: ${status.message}` 19 | : undefined; 20 | const tokenMessage = token ? `Request Token: ${token}` : undefined; 21 | 22 | console.groupCollapsed('[[ CloudVision Connector ]]'); 23 | switch (level) { 24 | case ERROR: 25 | if (tokenMessage) { 26 | console.error(tokenMessage); 27 | } 28 | console.error(contextualizedMessage); 29 | if (statusMessage) { 30 | console.error(statusMessage); 31 | } 32 | break; 33 | case WARN: 34 | if (tokenMessage) { 35 | console.warn(tokenMessage); 36 | } 37 | console.warn(contextualizedMessage); 38 | if (statusMessage) { 39 | console.warn(statusMessage); 40 | } 41 | break; 42 | default: 43 | if (tokenMessage) { 44 | console.log(tokenMessage); 45 | } 46 | console.log(contextualizedMessage); 47 | if (statusMessage) { 48 | console.log(statusMessage); 49 | } 50 | break; 51 | } 52 | console.groupEnd(); 53 | } 54 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/test/external-utils.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { Pointer } from 'a-msgpack'; 4 | 5 | import { fromBinaryKey, toBinaryKey } from '../src/utils'; 6 | 7 | const binaryKeyTestCases = [ 8 | { 9 | encodedValue: 'xAtEYXRhc2V0SW5mbw==', 10 | decodedValue: 'DatasetInfo', 11 | }, 12 | { 13 | encodedValue: 'gsQETmFtZcQSZnJvbnRUb0JhY2tBaXJmbG93xAVWYWx1ZQE=', 14 | decodedValue: { Name: 'frontToBackAirflow', Value: 1 }, 15 | }, 16 | { 17 | encodedValue: 'xzQAlcQFU3lzZGLEC2Vudmlyb25tZW50xAp0aGVybW9zdGF0xAZzdGF0dXPECWZhbkNvbmZpZw==', 18 | decodedValue: new Pointer(['Sysdb', 'environment', 'thermostat', 'status', 'fanConfig']), 19 | }, 20 | { 21 | encodedValue: 'Kg==', 22 | decodedValue: 42, 23 | }, 24 | { 25 | encodedValue: 'y0BFJmZmZmZm', 26 | decodedValue: 42.3, 27 | }, 28 | { 29 | encodedValue: 'lAEKCAs=', 30 | decodedValue: [1, 10, 8, 11], 31 | }, 32 | { 33 | encodedValue: 'ww==', 34 | decodedValue: true, 35 | }, 36 | { 37 | encodedValue: 'wg==', 38 | decodedValue: false, 39 | }, 40 | ]; 41 | 42 | describe('toBinaryKey', () => { 43 | test('should create the proper binary key given any key', () => { 44 | binaryKeyTestCases.forEach((testCase) => { 45 | expect(toBinaryKey(testCase.decodedValue)).toBe(testCase.encodedValue); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('fromBinaryKey', () => { 51 | test('should create the proper binary key given any key', () => { 52 | binaryKeyTestCases.forEach((testCase) => { 53 | expect(fromBinaryKey(testCase.encodedValue)).toEqual(testCase.decodedValue); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/test/logger.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | 3 | import { ERROR, INFO, WARN } from '../src/constants'; 4 | import { log } from '../src/logger'; 5 | import { LogLevel } from '../types'; 6 | 7 | describe.each<[LogLevel, string]>([ 8 | [ERROR, 'error'], 9 | [INFO, 'log'], 10 | [WARN, 'warn'], 11 | ])('log', (level, fn) => { 12 | const groupSpy = jest.spyOn(console, 'groupCollapsed'); 13 | // @ts-expect-error spyOn uses the string name of the function 14 | const consoleSpy = jest.spyOn(console, fn); 15 | const message = 'Dodgers rule'; 16 | const status = { code: 0, message: 'Giants not that great' }; 17 | const token = 'MLB'; 18 | 19 | beforeEach(() => { 20 | groupSpy.mockReset(); 21 | consoleSpy.mockReset(); 22 | }); 23 | 24 | test(`${level} should log a console group with the default message there is no message`, () => { 25 | log(level, null); 26 | expect(groupSpy).toHaveBeenCalledTimes(1); 27 | expect(consoleSpy).toHaveBeenNthCalledWith(1, `${level}: No message provided`); 28 | }); 29 | 30 | test(`${level} should log a console group for just a message`, () => { 31 | log(level, message); 32 | expect(groupSpy).toHaveBeenCalledTimes(1); 33 | expect(consoleSpy).toHaveBeenNthCalledWith(1, `${level}: ${message}`); 34 | }); 35 | 36 | test(`${level} should log a console group for a message and status`, () => { 37 | log(level, message, status); 38 | expect(groupSpy).toHaveBeenCalledTimes(1); 39 | expect(consoleSpy).toHaveBeenNthCalledWith(1, `${level}: ${message}`); 40 | expect(consoleSpy).toHaveBeenNthCalledWith( 41 | 2, 42 | `Status Code: ${status.code}, Status Message: ${status.message}`, 43 | ); 44 | }); 45 | 46 | test(`${level} should log a console group for a message, status and token`, () => { 47 | log(level, message, status, token); 48 | expect(groupSpy).toHaveBeenCalledTimes(1); 49 | expect(consoleSpy).toHaveBeenNthCalledWith(1, `Request Token: ${token}`); 50 | expect(consoleSpy).toHaveBeenNthCalledWith(2, `${level}: ${message}`); 51 | expect(consoleSpy).toHaveBeenNthCalledWith( 52 | 3, 53 | `Status Code: ${status.code}, Status Message: ${status.message}`, 54 | ); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/test/websocket-setup.ts: -------------------------------------------------------------------------------- 1 | // A setup script to load a mock websocket for tests 2 | 3 | import { WebSocket } from 'mock-socket'; 4 | import util from 'util'; 5 | 6 | // TextDecoder implementation that matches the lib dom API 7 | class TextDE { 8 | private decoder = new util.TextDecoder(); 9 | 10 | public readonly encoding = 'utf-8'; 11 | 12 | public readonly fatal = false; 13 | 14 | public readonly ignoreBOM = true; 15 | 16 | public decode(input?: Uint8Array): string { 17 | return this.decoder.decode(input); 18 | } 19 | } 20 | 21 | global.WebSocket = WebSocket; 22 | 23 | global.TextDecoder = TextDE; 24 | global.TextEncoder = util.TextEncoder; 25 | 26 | const swallowError = (): void => undefined; 27 | 28 | /* eslint-disable no-console */ 29 | console.error = swallowError; 30 | console.warn = swallowError; 31 | /* eslint-enable no-console */ 32 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationDir": "./typings" 6 | }, 7 | "exclude": ["node_modules", "**/*.spec.ts", "test/**"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES6" 5 | }, 6 | "typedocOptions": { 7 | "cleanOutputDir": true, 8 | "entryPoints": ["src/index.ts"], 9 | "exclude": "test/**", 10 | "excludeExternals": true, 11 | "excludePrivate": true, 12 | "hideGenerator": true, 13 | "out": "docs", 14 | "readme": "./README.md" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/connection.ts: -------------------------------------------------------------------------------- 1 | import { WsCommand } from './params'; 2 | 3 | export type ContextCommand = WsCommand | 'connection' | 'NO_COMMAND'; 4 | 5 | export interface RequestContext { 6 | command: ContextCommand; 7 | encodedParams: string; 8 | token: string; 9 | } 10 | 11 | export interface EventCallback { 12 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 13 | (requestContext: RequestContext, ...args: any[]): void; 14 | } 15 | 16 | export interface ConnectionCallback { 17 | (connEvent: string, wsEvent: Event): void; 18 | } 19 | 20 | /** 21 | * Uniquely identifies a `stream` (subscribe) call. Tokens are created based of the command 22 | * and request params, so identical calls with have identical tokens. This 23 | * means we use the callback to identify the call, if there are multiple of 24 | * the same calls. 25 | */ 26 | export interface SubscriptionIdentifier { 27 | callback: EventCallback; 28 | token: string; 29 | } 30 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/emitter.ts: -------------------------------------------------------------------------------- 1 | import { EventCallback, RequestContext } from './connection'; 2 | 3 | export type EmitterEvents = Map; 4 | 5 | export type EmitterRequestContext = Map; 6 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/global.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | declare namespace NodeJS { 3 | export interface Global { 4 | document: Document; 5 | window: Window; 6 | WebSocket: WebSocket; 7 | TextDecoder: typeof TextDecoder; 8 | TextEncoder: typeof TextEncoder; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseType, Element, NeatType, NeatTypeClass, PathElements, Timestamp } from 'a-msgpack'; 2 | 3 | export * from './connection'; 4 | export * from './logger'; 5 | export * from './notifications'; 6 | export * from './params'; 7 | export * from './query'; 8 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/logger.ts: -------------------------------------------------------------------------------- 1 | import { ERROR, INFO, WARN } from '../src/constants'; 2 | 3 | export type LogLevel = typeof ERROR | typeof INFO | typeof WARN; 4 | 5 | /** @deprecated: Use `LogLevel`. */ 6 | export type LogLevels = LogLevel; 7 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/notifications.ts: -------------------------------------------------------------------------------- 1 | import { PathElements, Timestamp } from 'a-msgpack'; 2 | 3 | import { RequestContext } from './connection'; 4 | import { DatasetObject } from './params'; 5 | 6 | export interface CloudVisionMetaData { 7 | [key: string]: V; 8 | } 9 | 10 | export interface CloudVisionDatasets { 11 | metadata: CloudVisionMetaData; 12 | datasets: DatasetObject[]; 13 | } 14 | 15 | export interface CloudVisionStatus { 16 | code?: number; 17 | 18 | message?: string; 19 | } 20 | 21 | export interface CloudVisionDatapoint { 22 | key: K; 23 | value: V; 24 | } 25 | 26 | export interface CloudVisionNotification { 27 | timestamp: T; 28 | nanos?: T; 29 | 30 | delete_all?: boolean; // eslint-disable-line @typescript-eslint/naming-convention 31 | deletes?: D; 32 | path_elements?: PE; // eslint-disable-line @typescript-eslint/naming-convention 33 | updates?: U; 34 | } 35 | 36 | export interface CloudVisionUpdates { 37 | [key: string]: CloudVisionDatapoint; 38 | } 39 | 40 | /** @deprecated: Use `CloudVisionUpdates`. */ 41 | export type CloudVisionUpdate = CloudVisionUpdates; 42 | 43 | export interface CloudVisionDeletes { 44 | [key: string]: { key: K }; 45 | } 46 | 47 | /** @deprecated: Use `CloudVisionDeletes`. */ 48 | export type CloudVisionDelete = CloudVisionDeletes; 49 | 50 | export type ConvertedNotification = CloudVisionNotification< 51 | PathElements, 52 | number, 53 | CloudVisionUpdates, 54 | CloudVisionDeletes 55 | >; 56 | 57 | export type NewConvertedNotification = CloudVisionNotification< 58 | PathElements, 59 | Timestamp, 60 | CloudVisionDatapoint[], 61 | K[] 62 | >; 63 | 64 | export type RawNotification = CloudVisionNotification< 65 | string[], 66 | Timestamp, 67 | CloudVisionDatapoint[], 68 | string[] 69 | >; 70 | 71 | export interface CloudVisionNotifs { 72 | dataset: DatasetObject; 73 | metadata: CloudVisionMetaData; 74 | notifications: ConvertedNotification[]; 75 | } 76 | 77 | export interface CloudVisionRawNotifs { 78 | dataset: DatasetObject; 79 | metadata?: CloudVisionMetaData; 80 | notifications: RawNotification[]; 81 | } 82 | 83 | export interface CloudVisionBatchedNotifications { 84 | dataset: DatasetObject; 85 | metadata: CloudVisionMetaData; 86 | notifications: { 87 | [path: string]: ConvertedNotification[]; 88 | }; 89 | } 90 | 91 | export interface CloudVisionServiceResult { 92 | [key: string]: unknown; 93 | } 94 | 95 | /** 96 | * This can either be the update returned as query result, or update to write 97 | * to the CloudVision API server. 98 | */ 99 | export type CloudVisionResult = CloudVisionNotifs | CloudVisionDatasets; 100 | export type CloudVisionBatchedResult = CloudVisionBatchedNotifications | CloudVisionDatasets; 101 | 102 | export interface CloudVisionMessage { 103 | result: CloudVisionBatchedResult | CloudVisionResult | CloudVisionServiceResult; 104 | status: CloudVisionStatus; 105 | token: string; 106 | 107 | error?: string; 108 | } 109 | 110 | /** 111 | * A function that gets called when a notification associated with the [[Query]] is 112 | * received. 113 | * 114 | * @param error `null` if there is no error. If there is an error this will be the message 115 | * @param result if there is a result in the notification this will be defined 116 | * @param status if there is a status in the notification this will be defined 117 | * @param token the token associated with the notification. If not defined, then there is 118 | * no notification associated with the call. 119 | */ 120 | export interface NotifCallback { 121 | ( 122 | err: string | null, 123 | result?: CloudVisionBatchedResult | CloudVisionResult | CloudVisionServiceResult, 124 | status?: CloudVisionStatus, 125 | token?: string, 126 | requestContext?: RequestContext, 127 | ): void; 128 | } 129 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/params.ts: -------------------------------------------------------------------------------- 1 | import { NeatType, PathElements } from 'a-msgpack'; 2 | 3 | import { 4 | CLOSE, 5 | GET, 6 | GET_AND_SUBSCRIBE, 7 | GET_DATASETS, 8 | GET_REGIONS_AND_CLUSTERS, 9 | PUBLISH, 10 | SEARCH, 11 | SEARCH_SUBSCRIBE, 12 | SEARCH_TYPE_ANY, 13 | SEARCH_TYPE_IP, 14 | SEARCH_TYPE_MAC, 15 | SERVICE_REQUEST, 16 | SUBSCRIBE, 17 | } from '../src/constants'; 18 | 19 | export type GetCommand = 20 | | typeof GET 21 | | typeof GET_DATASETS 22 | | typeof GET_REGIONS_AND_CLUSTERS 23 | | typeof SEARCH; 24 | 25 | /** @deprecated: Use `GetCommand`. */ 26 | export type GetCommands = GetCommand; 27 | 28 | export type StreamCommand = 29 | | typeof SERVICE_REQUEST 30 | | typeof SEARCH_SUBSCRIBE 31 | | typeof SUBSCRIBE 32 | | typeof GET_AND_SUBSCRIBE; 33 | 34 | /** @deprecated: Use `StreamCommand`. */ 35 | export type StreamCommands = StreamCommand; 36 | 37 | export type WsCommand = GetCommand | StreamCommand | typeof CLOSE | typeof PUBLISH; 38 | 39 | export type SearchType = typeof SEARCH_TYPE_ANY | typeof SEARCH_TYPE_IP | typeof SEARCH_TYPE_MAC; 40 | 41 | export interface PathObject { 42 | path_elements: PathElements; // eslint-disable-line @typescript-eslint/naming-convention 43 | 44 | keys?: readonly NeatType[]; 45 | } 46 | 47 | export interface SearchOptions { 48 | search: string; 49 | searchType?: SearchType; 50 | } 51 | 52 | export interface DatasetObject { 53 | name: string; 54 | type: string; 55 | 56 | parent?: DatasetObject; 57 | } 58 | 59 | export interface QueryObject { 60 | dataset: DatasetObject; 61 | paths: PathObject[]; 62 | } 63 | 64 | /** 65 | * A query is a list of one or more objects. Each of these objects has two keys: 66 | * **dataset** (an object with keys **type** and **name**), and **paths** 67 | * (an array of path objects (with keys **type**, **path** and **keys**). 68 | * 69 | * Dataset object: 70 | * type (string): can be 'device' or 'app'. The 'device' type is for physical 71 | * devices (switches), whereas 'app' is for any analytics engines. 72 | * name (string): the name of the dataset. 73 | * 74 | * Path object: 75 | * path_elements (array): An array of path elements representing the path to 76 | * the requested data. 77 | * keys (object): an optional property so that only data with such keys are returned. 78 | * Sample keys object: { 'any_custom_key': { key: 'speed'} } 79 | * 80 | * @example 81 | * ```typescript 82 | * 83 | * [{ 84 | * dataset: { 85 | * type: 'app', 86 | * name: 'analytics' 87 | * }, 88 | * paths: [{ 89 | * path_elements: ['events', 'activeEvents'], 90 | * }], 91 | * }, { 92 | * dataset: { 93 | * type: 'device', 94 | * name: 'JAS11070002' 95 | * }, 96 | * paths: [{ 97 | * path_elements: ['Logs', 'var', 'log', 'messages'], 98 | * keys: { 99 | * logMessage: { 100 | * key: 'text', 101 | * }, 102 | * }, 103 | * }], 104 | * }] 105 | * ``` 106 | */ 107 | export type Query = QueryObject[]; 108 | 109 | export interface QueryParams { 110 | query: Query; 111 | 112 | start?: number; 113 | end?: number; 114 | versions?: number; 115 | } 116 | 117 | export interface DatasetParams { 118 | types?: string[]; 119 | } 120 | 121 | export interface SearchParams { 122 | query: Query; 123 | search: string; 124 | searchType: SearchType; 125 | 126 | end?: number; 127 | start?: number; 128 | } 129 | 130 | /** 131 | * Parameters passed to the [[Connector.close]] request. 132 | */ 133 | export interface CloseParams { 134 | [token: string]: boolean; 135 | } 136 | 137 | export type CloudVisionParams = CloseParams | DatasetParams | QueryParams | SearchParams; 138 | -------------------------------------------------------------------------------- /packages/cloudvision-connector/types/query.ts: -------------------------------------------------------------------------------- 1 | import { EpochTimestamp, PathElements, Timestamp } from 'a-msgpack'; 2 | 3 | import { CloudVisionDatapoint, CloudVisionNotification } from './notifications'; 4 | import { CloudVisionParams, DatasetObject, WsCommand } from './params'; 5 | 6 | /** 7 | * Please refer to the [[CloudVisionNotification]] definition. 8 | */ 9 | export type PublishNotification = CloudVisionNotification< 10 | PathElements, 11 | Timestamp, 12 | CloudVisionDatapoint[], 13 | K[] 14 | >; 15 | 16 | /** 17 | * The request type of a publish command. 18 | * 19 | * @dataset is the root of the path. 20 | * @notifications is the data to write to. For the type 21 | */ 22 | export interface PublishRequest { 23 | dataset: DatasetObject; 24 | notifications: PublishNotification[]; 25 | } 26 | 27 | export interface BatchPublishRequest { 28 | dataset: DatasetObject; 29 | notifications: { 30 | [path: string]: PublishNotification[]; 31 | }; 32 | } 33 | 34 | export interface PublishCallback { 35 | (success: boolean, err?: string): void; 36 | } 37 | 38 | /** 39 | * There are a number of options you can supply which limit the scope of 40 | * the [[Query]]. Options are passed as an object, where the key is the 41 | * name of the option and the value is the value for that option. 42 | * 43 | * There are different combinations of options that are valid, each of them is 44 | * listed below. 45 | * 46 | * @example 47 | * ```typescript 48 | * 49 | * // Range query (returns one or more data points) 50 | * { 51 | * start: 1505760134000, 52 | * end: 1505760184000, 53 | * } 54 | * 55 | * // Point in time query (returns exactly one data point) 56 | * { 57 | * end: 1505760184000, 58 | * } 59 | * 60 | * // Limit query (returns `versions` + 1 number of data points) 61 | * { 62 | * versions: 1, 63 | * end: 1505760184000, 64 | * } 65 | * ``` 66 | */ 67 | export interface Options { 68 | end?: EpochTimestamp; 69 | start?: EpochTimestamp; 70 | versions?: number; 71 | } 72 | 73 | export interface CloudVisionPublishRequest { 74 | batch: PublishRequest | BatchPublishRequest; 75 | sync: boolean; 76 | } 77 | 78 | export interface ServiceRequest { 79 | body: {}; 80 | method: string; 81 | service: string; 82 | } 83 | 84 | export interface CloudVisionQueryMessage { 85 | command: WsCommand; 86 | params: CloudVisionParams | CloudVisionPublishRequest | ServiceRequest; 87 | token: string; 88 | } 89 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["**/*.js"], 5 | "extends": ["arista-js"] 6 | }, 7 | { 8 | "files": ["**/*.ts"], 9 | "extends": ["arista-ts"], 10 | "parserOptions": { 11 | "project": "./tsconfig.json" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .jest-cache 3 | *.log 4 | coverage 5 | dist 6 | docs 7 | es 8 | lib 9 | typings 10 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@arista/prettier-config" 2 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [5.0.2](https://github.com/aristanetworks/cloudvision/compare/v5.0.1...v5.0.2) (2023-01-18) 7 | 8 | **Note:** Version bump only for package cloudvision-grpc-web 9 | 10 | 11 | 12 | 13 | 14 | ## [5.0.1](https://github.com/aristanetworks/cloudvision/compare/v5.0.0...v5.0.1) (2022-07-18) 15 | 16 | **Note:** Version bump only for package cloudvision-grpc-web 17 | 18 | 19 | 20 | 21 | 22 | # [5.0.0](https://github.com/aristanetworks/cloudvision/compare/v4.14.0...v5.0.0) (2022-07-11) 23 | 24 | **Note:** Version bump only for package cloudvision-grpc-web 25 | 26 | 27 | 28 | 29 | 30 | # [4.14.0](https://github.com/aristanetworks/cloudvision/compare/v4.13.1...v4.14.0) (2022-05-30) 31 | 32 | 33 | ### Features 34 | 35 | * **grpc-web:** Refactor fromGrpcInvoke ([#786](https://github.com/aristanetworks/cloudvision/issues/786)) ([e8794e2](https://github.com/aristanetworks/cloudvision/commit/e8794e2f2d17d0884aeb695c7c8c1217323ab4a3)) 36 | 37 | 38 | 39 | 40 | 41 | ## [4.13.1](https://github.com/aristanetworks/cloudvision/compare/v4.13.0...v4.13.1) (2022-05-05) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * **cloudvision-grpc-web:** Defer requests until an observer is listening ([#785](https://github.com/aristanetworks/cloudvision/issues/785)) ([43e4281](https://github.com/aristanetworks/cloudvision/commit/43e4281b4e21ae74076eac6c8d4e7c9d0f760b24)) 47 | 48 | 49 | 50 | 51 | 52 | # [4.13.0](https://github.com/aristanetworks/cloudvision/compare/v4.12.2...v4.13.0) (2022-04-26) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * **cloudvision-grpc-web:** Add a try/catch around request close ([#773](https://github.com/aristanetworks/cloudvision/issues/773)) ([1350e56](https://github.com/aristanetworks/cloudvision/commit/1350e564d384f07440052d86e8d70d3b27137daa)) 58 | 59 | 60 | ### Features 61 | 62 | * **cloudvision-grpc-web:** Add support to close requests on unsubscribe ([#770](https://github.com/aristanetworks/cloudvision/issues/770)) ([4656263](https://github.com/aristanetworks/cloudvision/commit/46562635baedec15d2fb03c233992ec8e663d2a6)) 63 | 64 | 65 | 66 | 67 | 68 | ## [4.12.2](https://github.com/aristanetworks/cloudvision/compare/v4.12.1...v4.12.2) (2022-04-04) 69 | 70 | **Note:** Version bump only for package cloudvision-grpc-web 71 | 72 | 73 | 74 | 75 | 76 | ## [4.12.1](https://github.com/aristanetworks/cloudvision/compare/v4.12.0...v4.12.1) (2022-03-21) 77 | 78 | **Note:** Version bump only for package cloudvision-grpc-web 79 | 80 | 81 | 82 | 83 | 84 | # [4.12.0](https://github.com/aristanetworks/cloudvision/compare/v4.11.0...v4.12.0) (2021-10-05) 85 | 86 | 87 | ### Features 88 | 89 | * **grpc-web:** Export operators ([#578](https://github.com/aristanetworks/cloudvision/issues/578)) ([218a5de](https://github.com/aristanetworks/cloudvision/commit/218a5deac53a6de73eb01c4649710c7d836fbdc4)) 90 | 91 | 92 | 93 | 94 | 95 | # [4.11.0](https://github.com/aristanetworks/cloudvision/compare/v4.10.1...v4.11.0) (2021-10-04) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * **cloudvision-grpc-web:** hanging test in CI ([#563](https://github.com/aristanetworks/cloudvision/issues/563)) ([58a7e66](https://github.com/aristanetworks/cloudvision/commit/58a7e66af159defbec473dfe7ec7b88e262cb308)) 101 | 102 | 103 | ### Features 104 | 105 | * **grpc-web:** Refactor package ([#558](https://github.com/aristanetworks/cloudvision/issues/558)) ([eac7fc3](https://github.com/aristanetworks/cloudvision/commit/eac7fc31b8bd6a926090e30e92e555289a377a41)) 106 | 107 | 108 | 109 | 110 | 111 | ## [4.10.1](https://github.com/aristanetworks/cloudvision/compare/v4.10.0...v4.10.1) (2021-09-17) 112 | 113 | **Note:** Version bump only for package cloudvision-grpc-web 114 | 115 | 116 | 117 | 118 | 119 | # [4.10.0](https://github.com/aristanetworks/cloudvision/compare/v4.9.2...v4.10.0) (2021-09-08) 120 | 121 | 122 | ### Features 123 | 124 | * **cloudvision-grpc-web:** Use Arista fork of protobuf.js ([#541](https://github.com/aristanetworks/cloudvision/issues/541)) ([33d61d4](https://github.com/aristanetworks/cloudvision/commit/33d61d47fb5f7d9a85b829f027391e5b73fb2776)) 125 | 126 | 127 | 128 | 129 | 130 | ## [4.9.2](https://github.com/aristanetworks/cloudvision/compare/v4.9.1...v4.9.2) (2021-08-06) 131 | 132 | **Note:** Version bump only for package cloudvision-grpc-web 133 | 134 | 135 | 136 | 137 | 138 | ## [4.9.1](https://github.com/aristanetworks/cloudvision/compare/v4.9.0...v4.9.1) (2021-07-22) 139 | 140 | 141 | ### Bug Fixes 142 | 143 | * update packages ([#470](https://github.com/aristanetworks/cloudvision/issues/470)) ([264d1e0](https://github.com/aristanetworks/cloudvision/commit/264d1e04045ec9ae2efeaf1ff87cf4b9339626c5)) 144 | 145 | 146 | 147 | 148 | 149 | # [4.9.0](https://github.com/aristanetworks/cloudvision/compare/v4.8.0...v4.9.0) (2021-06-17) 150 | 151 | 152 | ### Features 153 | 154 | * **cloudvision-grpc-web:** Use RxJS Observables instead of Wonka + Use ts-proto + Fix alias resolutions ([#457](https://github.com/aristanetworks/cloudvision/issues/457)) ([6f84cf2](https://github.com/aristanetworks/cloudvision/commit/6f84cf2cfe9ef9da6df677bed9ef121feac6fd4f)) 155 | 156 | 157 | 158 | 159 | 160 | # [4.8.0](https://github.com/aristanetworks/cloudvision/compare/v4.7.0...v4.8.0) (2021-05-18) 161 | 162 | 163 | ### Features 164 | 165 | * **cloudvision-grpc-web:** add grpc-web client ([#407](https://github.com/aristanetworks/cloudvision/issues/407)) ([cc0ee18](https://github.com/aristanetworks/cloudvision/commit/cc0ee180b6d2371e807622fcd85dd81ee4440313)) 166 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/README.md: -------------------------------------------------------------------------------- 1 | # cloudvision-grpc-web 2 | 3 | A grpc-web client for requesting CloudVision data from the frontend. This libraries exposed 4 | functions and utils that convert the grpc-web calls to Observable streams that can be manipulated 5 | using [RXJS](https://rxjs.dev/). 6 | 7 | The package expects protobuf definitions to be generated via [ts-proto](https://github.com/stephenh/ts-proto) 8 | 9 | ## Installation 10 | 11 | ```bash 12 | npm install cloudvision-grpc-web 13 | ``` 14 | 15 | or 16 | 17 | ```bash 18 | yarn install cloudvision-grpc-web 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```js 24 | import { fromResourceGrpcInvoke } from 'cloudvision-grpc-web'; 25 | 26 | import { DeviceServiceGetAllDesc, DeviceStreamRequest } from '../generated/arista/inventory.v1/services.gen'; 27 | 28 | const requestAllMessage = DeviceStreamRequest.fromPartial({}); 29 | 30 | const grpcRequest = fromResourceGrpcInvoke(DeviceServiceGetAllDesc, { 31 | host: 'http://cvphost', 32 | request: { ...requestAllMessage, ...DeviceServiceGetAllDesc.requestType }, 33 | }); 34 | 35 | // Will print out each data message as it arrives 36 | grpcRequest.data.subscribe({ 37 | next: (val) => console.log('data', val)) 38 | }) 39 | 40 | // Will print out any Grpc metadata or errors as they happen 41 | grpcRequest.messages.subscribe({ 42 | next: (val) => console.log('control message', val)) 43 | }) 44 | 45 | ``` 46 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | if [ -z "$1" ] 5 | then 6 | echo "No Go source location supplied. Please enter the location where your go files live." 7 | exit 1 8 | fi 9 | 10 | if [ -z "$2" ] 11 | then 12 | echo "No proto source location supplied. Please enter the location where the protobuf libraries are installed." 13 | exit 1 14 | fi 15 | 16 | 17 | GO_SRC_LOC="$1" 18 | PROTO_LOC="$2" 19 | 20 | # Directory to write generated code to (.js and .d.ts files) 21 | OUT_DIR="./generated" 22 | 23 | mkdir -p "${OUT_DIR}" 24 | 25 | # Path to this plugin, Note this must be an absolute path on Windows 26 | PROTOC_GEN_TS_PATH="./node_modules/.bin/protoc-gen-ts_proto" 27 | 28 | 29 | SUBSCRIPTIONS_PROTO_FILES=$(ls $GO_SRC_LOC/arista/resources/arista/subscriptions/*.proto) 30 | 31 | TS_GEN_OPTIONS=" 32 | --plugin=${PROTOC_GEN_TS_PATH} 33 | --ts_proto_out=${OUT_DIR}/ 34 | --ts_proto_opt=env=browser 35 | --ts_proto_opt=esModuleInterop=true 36 | --ts_proto_opt=outputClientImpl=grpc-web 37 | " 38 | 39 | GOOGLE_PROTO_FILES=$(ls $PROTO_LOC/include/google/**/*.proto) 40 | 41 | protoc \ 42 | $TS_GEN_OPTIONS \ 43 | -I=$PROTO_LOC/include \ 44 | $GOOGLE_PROTO_FILES 45 | 46 | protoc \ 47 | $TS_GEN_OPTIONS \ 48 | -I=$GO_SRC_LOC/arista/resources \ 49 | $SUBSCRIPTIONS_PROTO_FILES 50 | 51 | echo "Use @arista/protobufjs" 52 | find ${OUT_DIR} -type f -name "*.ts" -print0 | xargs -0 sed -i '' -e "s#protobufjs/minimal#@arista/protobufjs/minimal#g" 53 | 54 | echo "Don't type-check generated files" 55 | find ${OUT_DIR} -type f -name "*.ts" -print0 | xargs -0 sed -i '' -e "1s#^#/* eslint-disable @typescript-eslint/ban-ts-comment */\n// @ts-nocheck\n#" 56 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/arista/subscriptions/subscriptions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'arista.subscriptions'; 8 | 9 | export enum Operation { 10 | UNSPECIFIED = 0, 11 | /** 12 | * INITIAL - INITIAL indicates the associated notification is that of the 13 | * current state and a fully-specified Resource is provided. 14 | */ 15 | INITIAL = 10, 16 | /** 17 | * INITIAL_SYNC_COMPLETE - INITIAL_SYNC_COMPLETE indicates all existing-state has been 18 | * streamed to the client. This status will be sent in an 19 | * otherwise-empty message and no subsequent INITIAL messages 20 | * should be expected. 21 | */ 22 | INITIAL_SYNC_COMPLETE = 11, 23 | /** 24 | * UPDATED - UPDATED indicates the associated notification carries 25 | * modification to the last-streamed state. This indicates 26 | * the contained Resource may be a partial diff, though, it 27 | * may contain a fully-specified Resource. 28 | */ 29 | UPDATED = 20, 30 | /** 31 | * DELETED - DETLETED indicates the associated notification carries 32 | * a deletion. The Resource's key will always be set in this case, 33 | * but no other fields should be expected. 34 | */ 35 | DELETED = 30, 36 | UNRECOGNIZED = -1, 37 | } 38 | 39 | export function operationFromJSON(object: any): Operation { 40 | switch (object) { 41 | case 0: 42 | case 'UNSPECIFIED': 43 | return Operation.UNSPECIFIED; 44 | case 10: 45 | case 'INITIAL': 46 | return Operation.INITIAL; 47 | case 11: 48 | case 'INITIAL_SYNC_COMPLETE': 49 | return Operation.INITIAL_SYNC_COMPLETE; 50 | case 20: 51 | case 'UPDATED': 52 | return Operation.UPDATED; 53 | case 30: 54 | case 'DELETED': 55 | return Operation.DELETED; 56 | case -1: 57 | case 'UNRECOGNIZED': 58 | default: 59 | return Operation.UNRECOGNIZED; 60 | } 61 | } 62 | 63 | export function operationToJSON(object: Operation): string { 64 | switch (object) { 65 | case Operation.UNSPECIFIED: 66 | return 'UNSPECIFIED'; 67 | case Operation.INITIAL: 68 | return 'INITIAL'; 69 | case Operation.INITIAL_SYNC_COMPLETE: 70 | return 'INITIAL_SYNC_COMPLETE'; 71 | case Operation.UPDATED: 72 | return 'UPDATED'; 73 | case Operation.DELETED: 74 | return 'DELETED'; 75 | default: 76 | return 'UNKNOWN'; 77 | } 78 | } 79 | 80 | if (_m0.util.Long !== Long) { 81 | _m0.util.Long = Long as any; 82 | _m0.configure(); 83 | } 84 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/any.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * `Any` contains an arbitrary serialized protocol buffer message along with a 11 | * URL that describes the type of the serialized message. 12 | * 13 | * Protobuf library provides support to pack/unpack Any values in the form 14 | * of utility functions or additional generated methods of the Any type. 15 | * 16 | * Example 1: Pack and unpack a message in C++. 17 | * 18 | * Foo foo = ...; 19 | * Any any; 20 | * any.PackFrom(foo); 21 | * ... 22 | * if (any.UnpackTo(&foo)) { 23 | * ... 24 | * } 25 | * 26 | * Example 2: Pack and unpack a message in Java. 27 | * 28 | * Foo foo = ...; 29 | * Any any = Any.pack(foo); 30 | * ... 31 | * if (any.is(Foo.class)) { 32 | * foo = any.unpack(Foo.class); 33 | * } 34 | * 35 | * Example 3: Pack and unpack a message in Python. 36 | * 37 | * foo = Foo(...) 38 | * any = Any() 39 | * any.Pack(foo) 40 | * ... 41 | * if any.Is(Foo.DESCRIPTOR): 42 | * any.Unpack(foo) 43 | * ... 44 | * 45 | * Example 4: Pack and unpack a message in Go 46 | * 47 | * foo := &pb.Foo{...} 48 | * any, err := anypb.New(foo) 49 | * if err != nil { 50 | * ... 51 | * } 52 | * ... 53 | * foo := &pb.Foo{} 54 | * if err := any.UnmarshalTo(foo); err != nil { 55 | * ... 56 | * } 57 | * 58 | * The pack methods provided by protobuf library will by default use 59 | * 'type.googleapis.com/full.type.name' as the type URL and the unpack 60 | * methods only use the fully qualified type name after the last '/' 61 | * in the type URL, for example "foo.bar.com/x/y.z" will yield type 62 | * name "y.z". 63 | * 64 | * 65 | * JSON 66 | * ==== 67 | * The JSON representation of an `Any` value uses the regular 68 | * representation of the deserialized, embedded message, with an 69 | * additional field `@type` which contains the type URL. Example: 70 | * 71 | * package google.profile; 72 | * message Person { 73 | * string first_name = 1; 74 | * string last_name = 2; 75 | * } 76 | * 77 | * { 78 | * "@type": "type.googleapis.com/google.profile.Person", 79 | * "firstName": , 80 | * "lastName": 81 | * } 82 | * 83 | * If the embedded message type is well-known and has a custom JSON 84 | * representation, that representation will be embedded adding a field 85 | * `value` which holds the custom JSON in addition to the `@type` 86 | * field. Example (for message [google.protobuf.Duration][]): 87 | * 88 | * { 89 | * "@type": "type.googleapis.com/google.protobuf.Duration", 90 | * "value": "1.212s" 91 | * } 92 | */ 93 | export interface Any { 94 | /** 95 | * A URL/resource name that uniquely identifies the type of the serialized 96 | * protocol buffer message. This string must contain at least 97 | * one "/" character. The last segment of the URL's path must represent 98 | * the fully qualified name of the type (as in 99 | * `path/google.protobuf.Duration`). The name should be in a canonical form 100 | * (e.g., leading "." is not accepted). 101 | * 102 | * In practice, teams usually precompile into the binary all types that they 103 | * expect it to use in the context of Any. However, for URLs which use the 104 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 105 | * server that maps type URLs to message definitions as follows: 106 | * 107 | * * If no scheme is provided, `https` is assumed. 108 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 109 | * value in binary format, or produce an error. 110 | * * Applications are allowed to cache lookup results based on the 111 | * URL, or have them precompiled into a binary to avoid any 112 | * lookup. Therefore, binary compatibility needs to be preserved 113 | * on changes to types. (Use versioned type names to manage 114 | * breaking changes.) 115 | * 116 | * Note: this functionality is not currently available in the official 117 | * protobuf release, and it is not used for type URLs beginning with 118 | * type.googleapis.com. 119 | * 120 | * Schemes other than `http`, `https` (or the empty scheme) might be 121 | * used with implementation specific semantics. 122 | */ 123 | typeUrl: string; 124 | /** Must be a valid serialized protocol buffer of the above specified type. */ 125 | value: Uint8Array; 126 | } 127 | 128 | const baseAny: object = { typeUrl: '' }; 129 | 130 | export const Any = { 131 | encode(message: Any, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 132 | if (message.typeUrl !== '') { 133 | writer.uint32(10).string(message.typeUrl); 134 | } 135 | if (message.value.length !== 0) { 136 | writer.uint32(18).bytes(message.value); 137 | } 138 | return writer; 139 | }, 140 | 141 | decode(input: _m0.Reader | Uint8Array, length?: number): Any { 142 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 143 | let end = length === undefined ? reader.len : reader.pos + length; 144 | const message = { ...baseAny } as Any; 145 | message.value = new Uint8Array(); 146 | while (reader.pos < end) { 147 | const tag = reader.uint32(); 148 | switch (tag >>> 3) { 149 | case 1: 150 | message.typeUrl = reader.string(); 151 | break; 152 | case 2: 153 | message.value = reader.bytes(); 154 | break; 155 | default: 156 | reader.skipType(tag & 7); 157 | break; 158 | } 159 | } 160 | return message; 161 | }, 162 | 163 | fromJSON(object: any): Any { 164 | const message = { ...baseAny } as Any; 165 | message.value = new Uint8Array(); 166 | if (object.typeUrl !== undefined && object.typeUrl !== null) { 167 | message.typeUrl = String(object.typeUrl); 168 | } else { 169 | message.typeUrl = ''; 170 | } 171 | if (object.value !== undefined && object.value !== null) { 172 | message.value = bytesFromBase64(object.value); 173 | } 174 | return message; 175 | }, 176 | 177 | toJSON(message: Any): unknown { 178 | const obj: any = {}; 179 | message.typeUrl !== undefined && (obj.typeUrl = message.typeUrl); 180 | message.value !== undefined && 181 | (obj.value = base64FromBytes(message.value !== undefined ? message.value : new Uint8Array())); 182 | return obj; 183 | }, 184 | 185 | fromPartial(object: DeepPartial): Any { 186 | const message = { ...baseAny } as Any; 187 | if (object.typeUrl !== undefined && object.typeUrl !== null) { 188 | message.typeUrl = object.typeUrl; 189 | } else { 190 | message.typeUrl = ''; 191 | } 192 | if (object.value !== undefined && object.value !== null) { 193 | message.value = object.value; 194 | } else { 195 | message.value = new Uint8Array(); 196 | } 197 | return message; 198 | }, 199 | }; 200 | 201 | declare var self: any | undefined; 202 | declare var window: any | undefined; 203 | var globalThis: any = (() => { 204 | if (typeof globalThis !== 'undefined') return globalThis; 205 | if (typeof self !== 'undefined') return self; 206 | if (typeof window !== 'undefined') return window; 207 | if (typeof global !== 'undefined') return global; 208 | throw 'Unable to locate global object'; 209 | })(); 210 | 211 | const atob: (b64: string) => string = 212 | globalThis.atob || ((b64) => globalThis.Buffer.from(b64, 'base64').toString('binary')); 213 | function bytesFromBase64(b64: string): Uint8Array { 214 | const bin = atob(b64); 215 | const arr = new Uint8Array(bin.length); 216 | for (let i = 0; i < bin.length; ++i) { 217 | arr[i] = bin.charCodeAt(i); 218 | } 219 | return arr; 220 | } 221 | 222 | const btoa: (bin: string) => string = 223 | globalThis.btoa || ((bin) => globalThis.Buffer.from(bin, 'binary').toString('base64')); 224 | function base64FromBytes(arr: Uint8Array): string { 225 | const bin: string[] = []; 226 | for (const byte of arr) { 227 | bin.push(String.fromCharCode(byte)); 228 | } 229 | return btoa(bin.join('')); 230 | } 231 | 232 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 233 | export type DeepPartial = T extends Builtin 234 | ? T 235 | : T extends Array 236 | ? Array> 237 | : T extends ReadonlyArray 238 | ? ReadonlyArray> 239 | : T extends {} 240 | ? { [K in keyof T]?: DeepPartial } 241 | : Partial; 242 | 243 | if (_m0.util.Long !== Long) { 244 | _m0.util.Long = Long as any; 245 | _m0.configure(); 246 | } 247 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/duration.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * A Duration represents a signed, fixed-length span of time represented 11 | * as a count of seconds and fractions of seconds at nanosecond 12 | * resolution. It is independent of any calendar and concepts like "day" 13 | * or "month". It is related to Timestamp in that the difference between 14 | * two Timestamp values is a Duration and it can be added or subtracted 15 | * from a Timestamp. Range is approximately +-10,000 years. 16 | * 17 | * # Examples 18 | * 19 | * Example 1: Compute Duration from two Timestamps in pseudo code. 20 | * 21 | * Timestamp start = ...; 22 | * Timestamp end = ...; 23 | * Duration duration = ...; 24 | * 25 | * duration.seconds = end.seconds - start.seconds; 26 | * duration.nanos = end.nanos - start.nanos; 27 | * 28 | * if (duration.seconds < 0 && duration.nanos > 0) { 29 | * duration.seconds += 1; 30 | * duration.nanos -= 1000000000; 31 | * } else if (duration.seconds > 0 && duration.nanos < 0) { 32 | * duration.seconds -= 1; 33 | * duration.nanos += 1000000000; 34 | * } 35 | * 36 | * Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 37 | * 38 | * Timestamp start = ...; 39 | * Duration duration = ...; 40 | * Timestamp end = ...; 41 | * 42 | * end.seconds = start.seconds + duration.seconds; 43 | * end.nanos = start.nanos + duration.nanos; 44 | * 45 | * if (end.nanos < 0) { 46 | * end.seconds -= 1; 47 | * end.nanos += 1000000000; 48 | * } else if (end.nanos >= 1000000000) { 49 | * end.seconds += 1; 50 | * end.nanos -= 1000000000; 51 | * } 52 | * 53 | * Example 3: Compute Duration from datetime.timedelta in Python. 54 | * 55 | * td = datetime.timedelta(days=3, minutes=10) 56 | * duration = Duration() 57 | * duration.FromTimedelta(td) 58 | * 59 | * # JSON Mapping 60 | * 61 | * In JSON format, the Duration type is encoded as a string rather than an 62 | * object, where the string ends in the suffix "s" (indicating seconds) and 63 | * is preceded by the number of seconds, with nanoseconds expressed as 64 | * fractional seconds. For example, 3 seconds with 0 nanoseconds should be 65 | * encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 66 | * be expressed in JSON format as "3.000000001s", and 3 seconds and 1 67 | * microsecond should be expressed in JSON format as "3.000001s". 68 | */ 69 | export interface Duration { 70 | /** 71 | * Signed seconds of the span of time. Must be from -315,576,000,000 72 | * to +315,576,000,000 inclusive. Note: these bounds are computed from: 73 | * 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 74 | */ 75 | seconds: number; 76 | /** 77 | * Signed fractions of a second at nanosecond resolution of the span 78 | * of time. Durations less than one second are represented with a 0 79 | * `seconds` field and a positive or negative `nanos` field. For durations 80 | * of one second or more, a non-zero value for the `nanos` field must be 81 | * of the same sign as the `seconds` field. Must be from -999,999,999 82 | * to +999,999,999 inclusive. 83 | */ 84 | nanos: number; 85 | } 86 | 87 | const baseDuration: object = { seconds: 0, nanos: 0 }; 88 | 89 | export const Duration = { 90 | encode(message: Duration, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 91 | if (message.seconds !== 0) { 92 | writer.uint32(8).int64(message.seconds); 93 | } 94 | if (message.nanos !== 0) { 95 | writer.uint32(16).int32(message.nanos); 96 | } 97 | return writer; 98 | }, 99 | 100 | decode(input: _m0.Reader | Uint8Array, length?: number): Duration { 101 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 102 | let end = length === undefined ? reader.len : reader.pos + length; 103 | const message = { ...baseDuration } as Duration; 104 | while (reader.pos < end) { 105 | const tag = reader.uint32(); 106 | switch (tag >>> 3) { 107 | case 1: 108 | message.seconds = longToNumber(reader.int64() as Long); 109 | break; 110 | case 2: 111 | message.nanos = reader.int32(); 112 | break; 113 | default: 114 | reader.skipType(tag & 7); 115 | break; 116 | } 117 | } 118 | return message; 119 | }, 120 | 121 | fromJSON(object: any): Duration { 122 | const message = { ...baseDuration } as Duration; 123 | if (object.seconds !== undefined && object.seconds !== null) { 124 | message.seconds = Number(object.seconds); 125 | } else { 126 | message.seconds = 0; 127 | } 128 | if (object.nanos !== undefined && object.nanos !== null) { 129 | message.nanos = Number(object.nanos); 130 | } else { 131 | message.nanos = 0; 132 | } 133 | return message; 134 | }, 135 | 136 | toJSON(message: Duration): unknown { 137 | const obj: any = {}; 138 | message.seconds !== undefined && (obj.seconds = message.seconds); 139 | message.nanos !== undefined && (obj.nanos = message.nanos); 140 | return obj; 141 | }, 142 | 143 | fromPartial(object: DeepPartial): Duration { 144 | const message = { ...baseDuration } as Duration; 145 | if (object.seconds !== undefined && object.seconds !== null) { 146 | message.seconds = object.seconds; 147 | } else { 148 | message.seconds = 0; 149 | } 150 | if (object.nanos !== undefined && object.nanos !== null) { 151 | message.nanos = object.nanos; 152 | } else { 153 | message.nanos = 0; 154 | } 155 | return message; 156 | }, 157 | }; 158 | 159 | declare var self: any | undefined; 160 | declare var window: any | undefined; 161 | var globalThis: any = (() => { 162 | if (typeof globalThis !== 'undefined') return globalThis; 163 | if (typeof self !== 'undefined') return self; 164 | if (typeof window !== 'undefined') return window; 165 | if (typeof global !== 'undefined') return global; 166 | throw 'Unable to locate global object'; 167 | })(); 168 | 169 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 170 | export type DeepPartial = T extends Builtin 171 | ? T 172 | : T extends Array 173 | ? Array> 174 | : T extends ReadonlyArray 175 | ? ReadonlyArray> 176 | : T extends {} 177 | ? { [K in keyof T]?: DeepPartial } 178 | : Partial; 179 | 180 | function longToNumber(long: Long): number { 181 | if (long.gt(Number.MAX_SAFE_INTEGER)) { 182 | throw new globalThis.Error('Value is larger than Number.MAX_SAFE_INTEGER'); 183 | } 184 | return long.toNumber(); 185 | } 186 | 187 | if (_m0.util.Long !== Long) { 188 | _m0.util.Long = Long as any; 189 | _m0.configure(); 190 | } 191 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/empty.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * A generic empty message that you can re-use to avoid defining duplicated 11 | * empty messages in your APIs. A typical example is to use it as the request 12 | * or the response type of an API method. For instance: 13 | * 14 | * service Foo { 15 | * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 16 | * } 17 | * 18 | * The JSON representation for `Empty` is empty JSON object `{}`. 19 | */ 20 | export interface Empty {} 21 | 22 | const baseEmpty: object = {}; 23 | 24 | export const Empty = { 25 | encode(_: Empty, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 26 | return writer; 27 | }, 28 | 29 | decode(input: _m0.Reader | Uint8Array, length?: number): Empty { 30 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 31 | let end = length === undefined ? reader.len : reader.pos + length; 32 | const message = { ...baseEmpty } as Empty; 33 | while (reader.pos < end) { 34 | const tag = reader.uint32(); 35 | switch (tag >>> 3) { 36 | default: 37 | reader.skipType(tag & 7); 38 | break; 39 | } 40 | } 41 | return message; 42 | }, 43 | 44 | fromJSON(_: any): Empty { 45 | const message = { ...baseEmpty } as Empty; 46 | return message; 47 | }, 48 | 49 | toJSON(_: Empty): unknown { 50 | const obj: any = {}; 51 | return obj; 52 | }, 53 | 54 | fromPartial(_: DeepPartial): Empty { 55 | const message = { ...baseEmpty } as Empty; 56 | return message; 57 | }, 58 | }; 59 | 60 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 61 | export type DeepPartial = T extends Builtin 62 | ? T 63 | : T extends Array 64 | ? Array> 65 | : T extends ReadonlyArray 66 | ? ReadonlyArray> 67 | : T extends {} 68 | ? { [K in keyof T]?: DeepPartial } 69 | : Partial; 70 | 71 | if (_m0.util.Long !== Long) { 72 | _m0.util.Long = Long as any; 73 | _m0.configure(); 74 | } 75 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/field_mask.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * `FieldMask` represents a set of symbolic field paths, for example: 11 | * 12 | * paths: "f.a" 13 | * paths: "f.b.d" 14 | * 15 | * Here `f` represents a field in some root message, `a` and `b` 16 | * fields in the message found in `f`, and `d` a field found in the 17 | * message in `f.b`. 18 | * 19 | * Field masks are used to specify a subset of fields that should be 20 | * returned by a get operation or modified by an update operation. 21 | * Field masks also have a custom JSON encoding (see below). 22 | * 23 | * # Field Masks in Projections 24 | * 25 | * When used in the context of a projection, a response message or 26 | * sub-message is filtered by the API to only contain those fields as 27 | * specified in the mask. For example, if the mask in the previous 28 | * example is applied to a response message as follows: 29 | * 30 | * f { 31 | * a : 22 32 | * b { 33 | * d : 1 34 | * x : 2 35 | * } 36 | * y : 13 37 | * } 38 | * z: 8 39 | * 40 | * The result will not contain specific values for fields x,y and z 41 | * (their value will be set to the default, and omitted in proto text 42 | * output): 43 | * 44 | * 45 | * f { 46 | * a : 22 47 | * b { 48 | * d : 1 49 | * } 50 | * } 51 | * 52 | * A repeated field is not allowed except at the last position of a 53 | * paths string. 54 | * 55 | * If a FieldMask object is not present in a get operation, the 56 | * operation applies to all fields (as if a FieldMask of all fields 57 | * had been specified). 58 | * 59 | * Note that a field mask does not necessarily apply to the 60 | * top-level response message. In case of a REST get operation, the 61 | * field mask applies directly to the response, but in case of a REST 62 | * list operation, the mask instead applies to each individual message 63 | * in the returned resource list. In case of a REST custom method, 64 | * other definitions may be used. Where the mask applies will be 65 | * clearly documented together with its declaration in the API. In 66 | * any case, the effect on the returned resource/resources is required 67 | * behavior for APIs. 68 | * 69 | * # Field Masks in Update Operations 70 | * 71 | * A field mask in update operations specifies which fields of the 72 | * targeted resource are going to be updated. The API is required 73 | * to only change the values of the fields as specified in the mask 74 | * and leave the others untouched. If a resource is passed in to 75 | * describe the updated values, the API ignores the values of all 76 | * fields not covered by the mask. 77 | * 78 | * If a repeated field is specified for an update operation, new values will 79 | * be appended to the existing repeated field in the target resource. Note that 80 | * a repeated field is only allowed in the last position of a `paths` string. 81 | * 82 | * If a sub-message is specified in the last position of the field mask for an 83 | * update operation, then new value will be merged into the existing sub-message 84 | * in the target resource. 85 | * 86 | * For example, given the target message: 87 | * 88 | * f { 89 | * b { 90 | * d: 1 91 | * x: 2 92 | * } 93 | * c: [1] 94 | * } 95 | * 96 | * And an update message: 97 | * 98 | * f { 99 | * b { 100 | * d: 10 101 | * } 102 | * c: [2] 103 | * } 104 | * 105 | * then if the field mask is: 106 | * 107 | * paths: ["f.b", "f.c"] 108 | * 109 | * then the result will be: 110 | * 111 | * f { 112 | * b { 113 | * d: 10 114 | * x: 2 115 | * } 116 | * c: [1, 2] 117 | * } 118 | * 119 | * An implementation may provide options to override this default behavior for 120 | * repeated and message fields. 121 | * 122 | * In order to reset a field's value to the default, the field must 123 | * be in the mask and set to the default value in the provided resource. 124 | * Hence, in order to reset all fields of a resource, provide a default 125 | * instance of the resource and set all fields in the mask, or do 126 | * not provide a mask as described below. 127 | * 128 | * If a field mask is not present on update, the operation applies to 129 | * all fields (as if a field mask of all fields has been specified). 130 | * Note that in the presence of schema evolution, this may mean that 131 | * fields the client does not know and has therefore not filled into 132 | * the request will be reset to their default. If this is unwanted 133 | * behavior, a specific service may require a client to always specify 134 | * a field mask, producing an error if not. 135 | * 136 | * As with get operations, the location of the resource which 137 | * describes the updated values in the request message depends on the 138 | * operation kind. In any case, the effect of the field mask is 139 | * required to be honored by the API. 140 | * 141 | * ## Considerations for HTTP REST 142 | * 143 | * The HTTP kind of an update operation which uses a field mask must 144 | * be set to PATCH instead of PUT in order to satisfy HTTP semantics 145 | * (PUT must only be used for full updates). 146 | * 147 | * # JSON Encoding of Field Masks 148 | * 149 | * In JSON, a field mask is encoded as a single string where paths are 150 | * separated by a comma. Fields name in each path are converted 151 | * to/from lower-camel naming conventions. 152 | * 153 | * As an example, consider the following message declarations: 154 | * 155 | * message Profile { 156 | * User user = 1; 157 | * Photo photo = 2; 158 | * } 159 | * message User { 160 | * string display_name = 1; 161 | * string address = 2; 162 | * } 163 | * 164 | * In proto a field mask for `Profile` may look as such: 165 | * 166 | * mask { 167 | * paths: "user.display_name" 168 | * paths: "photo" 169 | * } 170 | * 171 | * In JSON, the same mask is represented as below: 172 | * 173 | * { 174 | * mask: "user.displayName,photo" 175 | * } 176 | * 177 | * # Field Masks and Oneof Fields 178 | * 179 | * Field masks treat fields in oneofs just as regular fields. Consider the 180 | * following message: 181 | * 182 | * message SampleMessage { 183 | * oneof test_oneof { 184 | * string name = 4; 185 | * SubMessage sub_message = 9; 186 | * } 187 | * } 188 | * 189 | * The field mask can be: 190 | * 191 | * mask { 192 | * paths: "name" 193 | * } 194 | * 195 | * Or: 196 | * 197 | * mask { 198 | * paths: "sub_message" 199 | * } 200 | * 201 | * Note that oneof type names ("test_oneof" in this case) cannot be used in 202 | * paths. 203 | * 204 | * ## Field Mask Verification 205 | * 206 | * The implementation of any API method which has a FieldMask type field in the 207 | * request should verify the included field paths, and return an 208 | * `INVALID_ARGUMENT` error if any path is unmappable. 209 | */ 210 | export interface FieldMask { 211 | /** The set of field mask paths. */ 212 | paths: string[]; 213 | } 214 | 215 | const baseFieldMask: object = { paths: '' }; 216 | 217 | export const FieldMask = { 218 | encode(message: FieldMask, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 219 | for (const v of message.paths) { 220 | writer.uint32(10).string(v!); 221 | } 222 | return writer; 223 | }, 224 | 225 | decode(input: _m0.Reader | Uint8Array, length?: number): FieldMask { 226 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 227 | let end = length === undefined ? reader.len : reader.pos + length; 228 | const message = { ...baseFieldMask } as FieldMask; 229 | message.paths = []; 230 | while (reader.pos < end) { 231 | const tag = reader.uint32(); 232 | switch (tag >>> 3) { 233 | case 1: 234 | message.paths.push(reader.string()); 235 | break; 236 | default: 237 | reader.skipType(tag & 7); 238 | break; 239 | } 240 | } 241 | return message; 242 | }, 243 | 244 | fromJSON(object: any): FieldMask { 245 | const message = { ...baseFieldMask } as FieldMask; 246 | message.paths = []; 247 | if (object.paths !== undefined && object.paths !== null) { 248 | for (const e of object.paths) { 249 | message.paths.push(String(e)); 250 | } 251 | } 252 | return message; 253 | }, 254 | 255 | toJSON(message: FieldMask): unknown { 256 | const obj: any = {}; 257 | if (message.paths) { 258 | obj.paths = message.paths.map((e) => e); 259 | } else { 260 | obj.paths = []; 261 | } 262 | return obj; 263 | }, 264 | 265 | fromPartial(object: DeepPartial): FieldMask { 266 | const message = { ...baseFieldMask } as FieldMask; 267 | message.paths = []; 268 | if (object.paths !== undefined && object.paths !== null) { 269 | for (const e of object.paths) { 270 | message.paths.push(e); 271 | } 272 | } 273 | return message; 274 | }, 275 | }; 276 | 277 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 278 | export type DeepPartial = T extends Builtin 279 | ? T 280 | : T extends Array 281 | ? Array> 282 | : T extends ReadonlyArray 283 | ? ReadonlyArray> 284 | : T extends {} 285 | ? { [K in keyof T]?: DeepPartial } 286 | : Partial; 287 | 288 | if (_m0.util.Long !== Long) { 289 | _m0.util.Long = Long as any; 290 | _m0.configure(); 291 | } 292 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/source_context.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * `SourceContext` represents information about the source of a 11 | * protobuf element, like the file in which it is defined. 12 | */ 13 | export interface SourceContext { 14 | /** 15 | * The path-qualified name of the .proto file that contained the associated 16 | * protobuf element. For example: `"google/protobuf/source_context.proto"`. 17 | */ 18 | fileName: string; 19 | } 20 | 21 | const baseSourceContext: object = { fileName: '' }; 22 | 23 | export const SourceContext = { 24 | encode(message: SourceContext, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 25 | if (message.fileName !== '') { 26 | writer.uint32(10).string(message.fileName); 27 | } 28 | return writer; 29 | }, 30 | 31 | decode(input: _m0.Reader | Uint8Array, length?: number): SourceContext { 32 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 33 | let end = length === undefined ? reader.len : reader.pos + length; 34 | const message = { ...baseSourceContext } as SourceContext; 35 | while (reader.pos < end) { 36 | const tag = reader.uint32(); 37 | switch (tag >>> 3) { 38 | case 1: 39 | message.fileName = reader.string(); 40 | break; 41 | default: 42 | reader.skipType(tag & 7); 43 | break; 44 | } 45 | } 46 | return message; 47 | }, 48 | 49 | fromJSON(object: any): SourceContext { 50 | const message = { ...baseSourceContext } as SourceContext; 51 | if (object.fileName !== undefined && object.fileName !== null) { 52 | message.fileName = String(object.fileName); 53 | } else { 54 | message.fileName = ''; 55 | } 56 | return message; 57 | }, 58 | 59 | toJSON(message: SourceContext): unknown { 60 | const obj: any = {}; 61 | message.fileName !== undefined && (obj.fileName = message.fileName); 62 | return obj; 63 | }, 64 | 65 | fromPartial(object: DeepPartial): SourceContext { 66 | const message = { ...baseSourceContext } as SourceContext; 67 | if (object.fileName !== undefined && object.fileName !== null) { 68 | message.fileName = object.fileName; 69 | } else { 70 | message.fileName = ''; 71 | } 72 | return message; 73 | }, 74 | }; 75 | 76 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 77 | export type DeepPartial = T extends Builtin 78 | ? T 79 | : T extends Array 80 | ? Array> 81 | : T extends ReadonlyArray 82 | ? ReadonlyArray> 83 | : T extends {} 84 | ? { [K in keyof T]?: DeepPartial } 85 | : Partial; 86 | 87 | if (_m0.util.Long !== Long) { 88 | _m0.util.Long = Long as any; 89 | _m0.configure(); 90 | } 91 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/generated/google/protobuf/timestamp.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // @ts-nocheck 3 | /* eslint-disable */ 4 | import Long from 'long'; 5 | import _m0 from '@arista/protobufjs/minimal'; 6 | 7 | export const protobufPackage = 'google.protobuf'; 8 | 9 | /** 10 | * A Timestamp represents a point in time independent of any time zone or local 11 | * calendar, encoded as a count of seconds and fractions of seconds at 12 | * nanosecond resolution. The count is relative to an epoch at UTC midnight on 13 | * January 1, 1970, in the proleptic Gregorian calendar which extends the 14 | * Gregorian calendar backwards to year one. 15 | * 16 | * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 17 | * second table is needed for interpretation, using a [24-hour linear 18 | * smear](https://developers.google.com/time/smear). 19 | * 20 | * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 21 | * restricting to that range, we ensure that we can convert to and from [RFC 22 | * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 23 | * 24 | * # Examples 25 | * 26 | * Example 1: Compute Timestamp from POSIX `time()`. 27 | * 28 | * Timestamp timestamp; 29 | * timestamp.set_seconds(time(NULL)); 30 | * timestamp.set_nanos(0); 31 | * 32 | * Example 2: Compute Timestamp from POSIX `gettimeofday()`. 33 | * 34 | * struct timeval tv; 35 | * gettimeofday(&tv, NULL); 36 | * 37 | * Timestamp timestamp; 38 | * timestamp.set_seconds(tv.tv_sec); 39 | * timestamp.set_nanos(tv.tv_usec * 1000); 40 | * 41 | * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 42 | * 43 | * FILETIME ft; 44 | * GetSystemTimeAsFileTime(&ft); 45 | * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 46 | * 47 | * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 48 | * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 49 | * Timestamp timestamp; 50 | * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 51 | * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 52 | * 53 | * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 54 | * 55 | * long millis = System.currentTimeMillis(); 56 | * 57 | * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 58 | * .setNanos((int) ((millis % 1000) * 1000000)).build(); 59 | * 60 | * 61 | * Example 5: Compute Timestamp from Java `Instant.now()`. 62 | * 63 | * Instant now = Instant.now(); 64 | * 65 | * Timestamp timestamp = 66 | * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 67 | * .setNanos(now.getNano()).build(); 68 | * 69 | * 70 | * Example 6: Compute Timestamp from current time in Python. 71 | * 72 | * timestamp = Timestamp() 73 | * timestamp.GetCurrentTime() 74 | * 75 | * # JSON Mapping 76 | * 77 | * In JSON format, the Timestamp type is encoded as a string in the 78 | * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 79 | * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 80 | * where {year} is always expressed using four digits while {month}, {day}, 81 | * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 82 | * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 83 | * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 84 | * is required. A proto3 JSON serializer should always use UTC (as indicated by 85 | * "Z") when printing the Timestamp type and a proto3 JSON parser should be 86 | * able to accept both UTC and other timezones (as indicated by an offset). 87 | * 88 | * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 89 | * 01:30 UTC on January 15, 2017. 90 | * 91 | * In JavaScript, one can convert a Date object to this format using the 92 | * standard 93 | * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 94 | * method. In Python, a standard `datetime.datetime` object can be converted 95 | * to this format using 96 | * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 97 | * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 98 | * the Joda Time's [`ISODateTimeFormat.dateTime()`]( 99 | * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D 100 | * ) to obtain a formatter capable of generating timestamps in this format. 101 | */ 102 | export interface Timestamp { 103 | /** 104 | * Represents seconds of UTC time since Unix epoch 105 | * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 106 | * 9999-12-31T23:59:59Z inclusive. 107 | */ 108 | seconds: number; 109 | /** 110 | * Non-negative fractions of a second at nanosecond resolution. Negative 111 | * second values with fractions must still have non-negative nanos values 112 | * that count forward in time. Must be from 0 to 999,999,999 113 | * inclusive. 114 | */ 115 | nanos: number; 116 | } 117 | 118 | const baseTimestamp: object = { seconds: 0, nanos: 0 }; 119 | 120 | export const Timestamp = { 121 | encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { 122 | if (message.seconds !== 0) { 123 | writer.uint32(8).int64(message.seconds); 124 | } 125 | if (message.nanos !== 0) { 126 | writer.uint32(16).int32(message.nanos); 127 | } 128 | return writer; 129 | }, 130 | 131 | decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp { 132 | const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); 133 | let end = length === undefined ? reader.len : reader.pos + length; 134 | const message = { ...baseTimestamp } as Timestamp; 135 | while (reader.pos < end) { 136 | const tag = reader.uint32(); 137 | switch (tag >>> 3) { 138 | case 1: 139 | message.seconds = longToNumber(reader.int64() as Long); 140 | break; 141 | case 2: 142 | message.nanos = reader.int32(); 143 | break; 144 | default: 145 | reader.skipType(tag & 7); 146 | break; 147 | } 148 | } 149 | return message; 150 | }, 151 | 152 | fromJSON(object: any): Timestamp { 153 | const message = { ...baseTimestamp } as Timestamp; 154 | if (object.seconds !== undefined && object.seconds !== null) { 155 | message.seconds = Number(object.seconds); 156 | } else { 157 | message.seconds = 0; 158 | } 159 | if (object.nanos !== undefined && object.nanos !== null) { 160 | message.nanos = Number(object.nanos); 161 | } else { 162 | message.nanos = 0; 163 | } 164 | return message; 165 | }, 166 | 167 | toJSON(message: Timestamp): unknown { 168 | const obj: any = {}; 169 | message.seconds !== undefined && (obj.seconds = message.seconds); 170 | message.nanos !== undefined && (obj.nanos = message.nanos); 171 | return obj; 172 | }, 173 | 174 | fromPartial(object: DeepPartial): Timestamp { 175 | const message = { ...baseTimestamp } as Timestamp; 176 | if (object.seconds !== undefined && object.seconds !== null) { 177 | message.seconds = object.seconds; 178 | } else { 179 | message.seconds = 0; 180 | } 181 | if (object.nanos !== undefined && object.nanos !== null) { 182 | message.nanos = object.nanos; 183 | } else { 184 | message.nanos = 0; 185 | } 186 | return message; 187 | }, 188 | }; 189 | 190 | declare var self: any | undefined; 191 | declare var window: any | undefined; 192 | var globalThis: any = (() => { 193 | if (typeof globalThis !== 'undefined') return globalThis; 194 | if (typeof self !== 'undefined') return self; 195 | if (typeof window !== 'undefined') return window; 196 | if (typeof global !== 'undefined') return global; 197 | throw 'Unable to locate global object'; 198 | })(); 199 | 200 | type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; 201 | export type DeepPartial = T extends Builtin 202 | ? T 203 | : T extends Array 204 | ? Array> 205 | : T extends ReadonlyArray 206 | ? ReadonlyArray> 207 | : T extends {} 208 | ? { [K in keyof T]?: DeepPartial } 209 | : Partial; 210 | 211 | function longToNumber(long: Long): number { 212 | if (long.gt(Number.MAX_SAFE_INTEGER)) { 213 | throw new globalThis.Error('Value is larger than Number.MAX_SAFE_INTEGER'); 214 | } 215 | return long.toNumber(); 216 | } 217 | 218 | if (_m0.util.Long !== Long) { 219 | _m0.util.Long = Long as any; 220 | _m0.configure(); 221 | } 222 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src'; 2 | export * from './types'; 3 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | cacheDirectory: '.jest-cache', 3 | coverageThreshold: { 4 | global: { 5 | branches: 0, 6 | functions: 0, 7 | lines: 0, 8 | statements: 0, 9 | }, 10 | }, 11 | coverageReporters: ['html', 'lcov'], 12 | collectCoverageFrom: [ 13 | '**/*.ts', 14 | '!src/index.ts', 15 | '!**/node_modules/**', 16 | '!**/lib/**', 17 | '!**/es/**', 18 | '!**/dist/**', 19 | '!**/coverage/**', 20 | '!**/*.spec.ts', 21 | '!**/*.config.js', 22 | ], 23 | transform: { 24 | '^.+\\.ts$': 'ts-jest', 25 | }, 26 | roots: ['/test', '/src'], 27 | }; 28 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudvision-grpc-web", 3 | "version": "5.0.2", 4 | "description": "A Grpc-Web client for CloudVision", 5 | "keywords": [ 6 | "grpc", 7 | "grpc-web", 8 | "cloud", 9 | "cloudvision-api", 10 | "cloudvision", 11 | "arista", 12 | "cvp", 13 | "net-db", 14 | "sdn", 15 | "switch" 16 | ], 17 | "author": "extensions@arista.com", 18 | "homepage": "https://aristanetworks.github.io/cloudvision/modules/cloudvision_grpc_web.html", 19 | "license": "MIT", 20 | "main": "lib/cloudvision-grpc-web.js", 21 | "unpkg": "dist/cloudvision-grpc-web.js", 22 | "module": "es/cloudvision-grpc-web.js", 23 | "typings": "typings/index.d.ts", 24 | "typedocMain": "src/index.ts", 25 | "files": [ 26 | "dist", 27 | "lib", 28 | "es", 29 | "src", 30 | "typings" 31 | ], 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/aristanetworks/cloudvision.git" 35 | }, 36 | "scripts": { 37 | "build": "npm run clean && npm run build:es && npm run build:commonjs && npm run build:umd && npm run build:umd:min && npm run docs && npm run docs:md && npm run build:types", 38 | "build:commonjs": "cross-env NODE_ENV=cjs rollup -c -o lib/cloudvision-grpc-web.js", 39 | "build:es": "cross-env NODE_ENV=es rollup -c -o es/cloudvision-grpc-web.js", 40 | "build:types": "rimraf typings && tsc --emitDeclarationOnly -p tsconfig-build.json -outDir ./typings && tsc-alias -p tsconfig-build.json", 41 | "build:umd": "cross-env NODE_ENV=development rollup -c -o dist/cloudvision-grpc-web.js", 42 | "build:umd:min": "cross-env NODE_ENV=production rollup -c -o dist/cloudvision-grpc-web.min.js", 43 | "check": "npm run lint && npm run type-check && npm run prettier-check", 44 | "generate": "rimraf generated && bash generate.sh", 45 | "clean": "rimraf dist lib coverage es docs typings", 46 | "docs": "typedoc --plugin none --out docs/html", 47 | "docs:md": "typedoc --plugin typedoc-plugin-markdown --theme markdown --out docs/md", 48 | "lint": "eslint --max-warnings 0 --ext .js,.ts *.js src/**/*.ts test/**/*.ts types/**/*.ts", 49 | "prepare": "npm run build", 50 | "prettier-check": "prettier -c *.js src/**/*.ts test/**/*.ts types/**/*.ts", 51 | "prettier-fix": "prettier --write *.js src/**/*.ts test/**/*.ts types/**/*.ts", 52 | "test": "cross-env NODE_ENV=production jest", 53 | "test:clean": "rimraf .jest-cache", 54 | "test:cov": "rimraf coverage && npm run test -- --coverage", 55 | "test:watch": "npm run test -- --watch", 56 | "type-check": "tsc --noEmit" 57 | }, 58 | "bugs": { 59 | "url": "https://github.com/aristanetworks/cloudvision/issues" 60 | }, 61 | "dependencies": { 62 | "@arista/grpc-web": "0.15.1", 63 | "@arista/protobufjs": "6.11.1", 64 | "google-protobuf": "3.21.2", 65 | "rxjs": "7.5.7" 66 | }, 67 | "devDependencies": { 68 | "@arista/prettier-config": "1.1.4", 69 | "@rollup/plugin-alias": "3.1.9", 70 | "@rollup/plugin-commonjs": "22.0.2", 71 | "@rollup/plugin-node-resolve": "13.3.0", 72 | "@rollup/plugin-replace": "4.0.0", 73 | "@rollup/plugin-typescript": "8.5.0", 74 | "@types/google-protobuf": "3.15.6", 75 | "@types/jest": "27.5.2", 76 | "@typescript-eslint/eslint-plugin": "5.32.0", 77 | "@typescript-eslint/parser": "5.32.0", 78 | "babel-eslint": "10.1.0", 79 | "cross-env": "7.0.3", 80 | "eslint": "8.36.0", 81 | "eslint-config-arista-js": "2.0.3", 82 | "eslint-config-arista-ts": "2.0.1", 83 | "eslint-plugin-arista": "0.2.3", 84 | "eslint-plugin-import": "2.27.5", 85 | "jest": "27.5.1", 86 | "prettier": "2.7.1", 87 | "rimraf": "3.0.2", 88 | "rollup": "2.79.1", 89 | "rollup-plugin-terser": "7.0.2", 90 | "ts-jest": "27.1.5", 91 | "ts-proto": "1.129.0", 92 | "tsc-alias": "1.8.3", 93 | "tslib": "2.4.0", 94 | "typedoc": "0.23.26", 95 | "typedoc-plugin-markdown": "3.13.6", 96 | "typescript": "4.8.4" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/rollup.config.js: -------------------------------------------------------------------------------- 1 | import alias from '@rollup/plugin-alias'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 4 | import replace from '@rollup/plugin-replace'; 5 | import typescript from '@rollup/plugin-typescript'; 6 | import path from 'path'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | 9 | import packageJson from './package.json'; 10 | 11 | const env = process.env.NODE_ENV; 12 | const projectRootDir = path.resolve(__dirname); 13 | 14 | const config = { 15 | input: 'src/index.ts', 16 | onwarn: (warning) => { 17 | if ( 18 | warning.code === 'CIRCULAR_DEPENDENCY' && 19 | warning.importer === 'node_modules/protobufjs/src/util/minimal.js' 20 | ) { 21 | return; 22 | } 23 | if ( 24 | warning.code === 'EVAL' && 25 | (warning.id || '').includes('node_modules/@protobufjs/inquire/index.js') 26 | ) { 27 | return; 28 | } 29 | if (warning.loc) { 30 | throw new Error( 31 | `${warning.message} (${warning.loc.file}):${warning.loc.line}:${warning.loc.column}`, 32 | ); 33 | } 34 | throw new Error(warning.message); 35 | }, 36 | plugins: [ 37 | alias({ 38 | entries: [ 39 | { find: '@generated', replacement: path.resolve(projectRootDir, 'generated') }, 40 | { find: '@types', replacement: path.resolve(projectRootDir, 'types') }, 41 | ], 42 | }), 43 | typescript({ sourceMap: false }), 44 | ], 45 | }; 46 | 47 | const external = Object.keys(packageJson.dependencies); 48 | 49 | const globals = { 50 | '@arista/grpc-web': 'grpc-web', 51 | 'google-protobuf': 'google-protobuf', 52 | 'rxjs': 'rxjs', 53 | }; 54 | 55 | // Build preserving environment variables 56 | if (env === 'es' || env === 'cjs') { 57 | config.external = external; 58 | config.output = { 59 | exports: 'named', 60 | format: env, 61 | // globals, 62 | indent: false, 63 | }; 64 | config.plugins.push(nodeResolve(), commonjs()); 65 | } 66 | 67 | // Replace NODE_ENV variable 68 | if (env === 'development' || env === 'production') { 69 | if (!process.env.INCLUDE_EXTERNAL) { 70 | config.external = external; 71 | config.plugins.push(nodeResolve()); 72 | } else { 73 | config.plugins.push(nodeResolve({ browser: true })); 74 | } 75 | config.output = { 76 | exports: 'named', 77 | format: 'umd', 78 | globals, 79 | indent: false, 80 | name: 'CloudvisionGrpcWeb', 81 | }; 82 | config.plugins.push( 83 | replace({ 84 | preventAssignment: true, 85 | values: { 86 | 'process.env.NODE_ENV': JSON.stringify(env), 87 | 'process.env.TEXT_ENCODING': JSON.stringify('always'), 88 | 'process.env.TEXT_DECODER': JSON.stringify('always'), 89 | }, 90 | }), 91 | commonjs(), 92 | ); 93 | } 94 | 95 | if (env === 'production') { 96 | config.plugins.push(nodeResolve(), terser()); 97 | } 98 | 99 | export default config; 100 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/grpc/index.ts: -------------------------------------------------------------------------------- 1 | import type { GrpcControlMessage, GrpcEnd, GrpcInvoke, GrpcSource, RpcOptions } from '@types'; 2 | import { map, merge, Observable, Subject } from 'rxjs'; 3 | 4 | import { grpc } from '@arista/grpc-web'; 5 | 6 | /** 7 | * RxJs wrapper around `grpc.invoke` that creates an Observable. 8 | * On subscription, the underlying `grpc.invoke` is called. 9 | * On unsubscription, the active request is closed. 10 | */ 11 | export function fromGrpcInvoke( 12 | methodDescriptor: grpc.MethodDefinition, 13 | options: RpcOptions, 14 | ): Observable> { 15 | return new Observable((subscriber) => { 16 | const headers$ = new Subject(); 17 | const message$ = new Subject(); 18 | const end$ = new Subject(); 19 | 20 | const rpcOptions: grpc.InvokeRpcOptions = { 21 | ...options, 22 | onHeaders(headers) { 23 | headers$.next(headers); 24 | }, 25 | onMessage(response) { 26 | message$.next(response); 27 | }, 28 | onEnd(code, message, trailers) { 29 | end$.next({ code, message, trailers }); 30 | 31 | end$.complete(); 32 | headers$.complete(); 33 | message$.complete(); 34 | subscriber.complete(); 35 | }, 36 | }; 37 | 38 | const request = grpc.invoke(methodDescriptor, rpcOptions); 39 | 40 | subscriber.next({ 41 | headers$, 42 | message$, 43 | end$, 44 | }); 45 | 46 | return () => { 47 | try { 48 | request.close(); 49 | } catch (e) { 50 | // Do nothing for now 51 | } 52 | 53 | end$.complete(); 54 | headers$.complete(); 55 | message$.complete(); 56 | }; 57 | }); 58 | } 59 | 60 | export function fromGrpcSource( 61 | methodDescriptor: grpc.MethodDefinition, 62 | options: RpcOptions, 63 | ): Observable> { 64 | return fromGrpcInvoke(methodDescriptor, options).pipe( 65 | // Merge `end$` and `headers$` into a single control message stream 66 | map(({ end$, headers$, message$ }): GrpcSource => { 67 | const controlMessage$ = merge( 68 | headers$.pipe(map((headers): GrpcControlMessage => ({ metadata: headers }))), 69 | end$.pipe(map((end): GrpcControlMessage => ({ error: end }))), 70 | ); 71 | 72 | return { 73 | data$: message$, 74 | message$: controlMessage$, 75 | }; 76 | }), 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './grpc'; 2 | export * from './operators'; 3 | export * from './resource'; 4 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/operators.ts: -------------------------------------------------------------------------------- 1 | import { ObservableInput, OperatorFunction, pipe, takeUntil, toArray } from 'rxjs'; 2 | 3 | /** Similar to `buffer`, except completes after the notifier emits. */ 4 | export function bufferOnce(notifier: ObservableInput): OperatorFunction { 5 | return pipe(takeUntil(notifier), toArray()); 6 | } 7 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/resource/constants.ts: -------------------------------------------------------------------------------- 1 | import type { GrpcControlErrorMessage } from '@types'; 2 | 3 | import { grpc } from '@arista/grpc-web'; 4 | 5 | export const INITIAL_SYNC_COMPLETE = 'INITIAL_SYNC_COMPLETE'; 6 | 7 | export const INITIAL_SYNC_COMPLETE_MESSAGE: GrpcControlErrorMessage = { 8 | error: { 9 | code: grpc.Code.OK, 10 | message: INITIAL_SYNC_COMPLETE, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/resource/index.ts: -------------------------------------------------------------------------------- 1 | import { Operation } from '@generated/arista/subscriptions/subscriptions'; 2 | import type { GrpcSource, ResourceRpcOptions } from '@types'; 3 | import { map, merge, Observable, partition } from 'rxjs'; 4 | 5 | import { grpc } from '@arista/grpc-web'; 6 | 7 | import { fromGrpcSource } from '../grpc'; 8 | 9 | import { INITIAL_SYNC_COMPLETE_MESSAGE } from './constants'; 10 | import { isStreamResponse } from './utils'; 11 | 12 | export * from '@generated/arista/subscriptions/subscriptions'; 13 | 14 | export * from './constants'; 15 | export * from './operators'; 16 | export * from './utils'; 17 | 18 | /** 19 | * Calls `fromGrpcSource` and adds some resource specific handling for stream control messages that 20 | * do not contain data, but are sent as data messages. 21 | * This implements the special handleing for the `INITIAL_SYNC_COMPLETE` message. 22 | * 23 | * @param methodDescriptor The GRPC service method definition to be queried. 24 | * @param options Options to pass to the GRPC call. 25 | */ 26 | export function fromResourceGrpcSource< 27 | Req extends grpc.ProtobufMessage, 28 | Res extends grpc.ProtobufMessage, 29 | >( 30 | methodDescriptor: grpc.MethodDefinition, 31 | options: ResourceRpcOptions, 32 | ): Observable> { 33 | return fromGrpcSource(methodDescriptor, options).pipe( 34 | // Move `INITIAL_SYNC_COMPLETE` response to control message stream 35 | map(({ data$, message$ }): GrpcSource => { 36 | const [initialSyncCompleteData$, otherData$] = partition(data$, (response) => { 37 | return isStreamResponse(response) && response.type === Operation.INITIAL_SYNC_COMPLETE; 38 | }); 39 | const initialSyncCompleteMessage$ = initialSyncCompleteData$.pipe( 40 | map(() => INITIAL_SYNC_COMPLETE_MESSAGE), 41 | ); 42 | 43 | return { 44 | data$: otherData$, 45 | message$: merge(message$, initialSyncCompleteMessage$), 46 | }; 47 | }), 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/resource/operators.ts: -------------------------------------------------------------------------------- 1 | import type { GrpcControlMessage } from '@types'; 2 | import { filter, Observable, OperatorFunction } from 'rxjs'; 3 | 4 | import { bufferOnce } from '../operators'; 5 | 6 | import { isInitialSyncCompleteMessage } from './utils'; 7 | 8 | export function bufferUntilInitialSyncComplete( 9 | message$: Observable, 10 | ): OperatorFunction { 11 | return bufferOnce(message$.pipe(filter(isInitialSyncCompleteMessage))); 12 | } 13 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/src/resource/utils.ts: -------------------------------------------------------------------------------- 1 | import type { GrpcControlMessage, StreamingResourceResponse } from '@types'; 2 | 3 | import { grpc } from '@arista/grpc-web'; 4 | 5 | import { INITIAL_SYNC_COMPLETE_MESSAGE } from './constants'; 6 | 7 | export function isInitialSyncCompleteMessage(message: GrpcControlMessage): boolean { 8 | return ( 9 | 'error' in message && 10 | message.error.code === INITIAL_SYNC_COMPLETE_MESSAGE.error.code && 11 | message.error.message === INITIAL_SYNC_COMPLETE_MESSAGE.error.message 12 | ); 13 | } 14 | 15 | export function isStreamResponse( 16 | response: grpc.ProtobufMessage, 17 | ): response is StreamingResourceResponse { 18 | return 'type' in response; 19 | } 20 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/test/index.spec.ts: -------------------------------------------------------------------------------- 1 | describe('just pass', () => { 2 | test('just pass', () => { 3 | expect(true).toBeTruthy(); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/test/operators.spec.ts: -------------------------------------------------------------------------------- 1 | import { ignoreElements, mergeMap, NEVER, throwError, timer } from 'rxjs'; 2 | import { TestScheduler } from 'rxjs/testing'; 3 | 4 | import { bufferOnce } from '../src/operators'; 5 | 6 | let testScheduler: TestScheduler; 7 | 8 | beforeEach(() => { 9 | testScheduler = new TestScheduler((actual, expected) => expect(actual).toEqual(expected)); 10 | }); 11 | 12 | describe('bufferOnce', () => { 13 | test('should emit buffer on source complete', () => { 14 | testScheduler.run(({ cold, expectObservable }) => { 15 | const source = cold('1-2-3-|'); 16 | const observable = source.pipe(bufferOnce(NEVER)); 17 | 18 | expectObservable(observable).toBe('------(a|)', { a: ['1', '2', '3'] }); 19 | }); 20 | }); 21 | 22 | test('should emit buffer on notifier next', () => { 23 | testScheduler.run(({ cold, expectObservable }) => { 24 | const source = cold('1-2-3-4-5'); 25 | const notifier = timer(5); 26 | const observable = source.pipe(bufferOnce(notifier)); 27 | 28 | expectObservable(observable).toBe('-----(a|)', { a: ['1', '2', '3'] }); 29 | }); 30 | }); 31 | 32 | test('should not emit buffer on notifier complete', () => { 33 | testScheduler.run(({ cold, expectObservable }) => { 34 | const source = cold('1-2-3-4-5'); 35 | const notifier = timer(5).pipe(ignoreElements()); 36 | const observable = source.pipe(bufferOnce(notifier)); 37 | 38 | expectObservable(observable).toBe(''); 39 | }); 40 | }); 41 | 42 | test('should error on source error', () => { 43 | testScheduler.run(({ cold, expectObservable }) => { 44 | const error = 'oh no!'; 45 | 46 | const source = cold('1-2#', undefined, error); 47 | const observable = source.pipe(bufferOnce(NEVER)); 48 | 49 | expectObservable(observable).toBe('---#', undefined, error); 50 | }); 51 | }); 52 | 53 | test('should error on notifier error', () => { 54 | testScheduler.run(({ cold, expectObservable }) => { 55 | const error = 'oh no!'; 56 | 57 | const source = cold('1-2-3-4-5'); 58 | const notifier = timer(5).pipe(mergeMap(() => throwError(() => error))); 59 | const observable = source.pipe(bufferOnce(notifier)); 60 | 61 | expectObservable(observable).toBe('-----#', undefined, error); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "outDir": "typings" 5 | }, 6 | "extends": "./tsconfig.json", 7 | "exclude": ["generated/**", "node_modules", "**/*.spec.ts", "test/**"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "target": "ES6", 6 | "paths": { 7 | "@generated/*": ["./generated/*"], 8 | "@types": ["./types"] 9 | } 10 | }, 11 | "exclude": ["generated/**"], 12 | "typedocOptions": { 13 | "cleanOutputDir": true, 14 | "entryPoints": ["src/index.ts"], 15 | "exclude": "test/**", 16 | "excludeExternals": true, 17 | "excludePrivate": true, 18 | "hideGenerator": true, 19 | "out": "docs", 20 | "readme": "./README.md" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/types/grpc.ts: -------------------------------------------------------------------------------- 1 | import type { Observable } from 'rxjs'; 2 | 3 | import type { grpc } from '@arista/grpc-web'; 4 | 5 | export interface GrpcEnd { 6 | code: grpc.Code; 7 | message: string; 8 | trailers: grpc.Metadata; 9 | } 10 | 11 | export interface GrpcInvoke { 12 | end$: Observable; 13 | headers$: Observable; 14 | message$: Observable; 15 | } 16 | 17 | /** Represents the combination of the GRPC code along with any message detailing the failure. */ 18 | export interface GrpcCodeWithMessage { 19 | code: grpc.Code; 20 | message: string; 21 | 22 | trailers?: grpc.Metadata; 23 | } 24 | 25 | export interface GrpcControlErrorMessage { 26 | error: GrpcCodeWithMessage; 27 | } 28 | 29 | export interface GrpcControlMetaDataMessage { 30 | metadata: grpc.Metadata; 31 | } 32 | 33 | export type GrpcControlMessage = GrpcControlErrorMessage | GrpcControlMetaDataMessage; 34 | 35 | export interface GrpcSource { 36 | /** gRPC response messages. */ 37 | data$: Observable; 38 | /** gRPC control messages. */ 39 | message$: Observable; 40 | } 41 | 42 | /** 43 | * Includes all [grpc invoke options](https://github.com/improbable-eng/grpc-web/blob/master/client/grpc-web/docs/invoke.md#invokerpcoptions) 44 | * except for `onMessage`, `onEnd` and `onHeaders`. 45 | */ 46 | export type RpcOptions = Omit< 47 | grpc.InvokeRpcOptions, 48 | 'onEnd' | 'onHeaders' | 'onMessage' 49 | >; 50 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './grpc'; 2 | export * from './resource'; 3 | -------------------------------------------------------------------------------- /packages/cloudvision-grpc-web/types/resource.ts: -------------------------------------------------------------------------------- 1 | import type { Operation } from '@generated/arista/subscriptions/subscriptions'; 2 | 3 | import type { grpc } from '@arista/grpc-web'; 4 | 5 | import type { RpcOptions } from './grpc'; 6 | 7 | /** 8 | * A GRPC response the represents a response from a streaming resource. 9 | */ 10 | export interface StreamingResourceResponse extends grpc.ProtobufMessage { 11 | type: Operation; 12 | } 13 | 14 | /** 15 | * Includes all [grpc invoke options](https://github.com/improbable-eng/grpc-web/blob/master/client/grpc-web/docs/invoke.md#invokerpcoptions) 16 | * except for `onMessage`, `onEnd` and `onHeaders` for a resource request. 17 | */ 18 | export type ResourceRpcOptions< 19 | Req extends grpc.ProtobufMessage, 20 | Res extends grpc.ProtobufMessage, 21 | > = RpcOptions; 22 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":semanticCommits", 5 | ":semanticCommitTypeAll(chore)" 6 | ], 7 | "labels": ["dependencies"], 8 | "packageRules": [ 9 | { 10 | "updateTypes": ["minor", "patch", "pin", "digest"], 11 | "automerge": true, 12 | "automergeType": "pr" 13 | } 14 | ], 15 | "lockFileMaintenance": { 16 | "enabled": true, 17 | "automerge": true 18 | }, 19 | "schedule": ["after 10pm and before 5am on every weekday", "every weekend"] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "alwaysStrict": true, 5 | // "declaration": true, 6 | // "declarationDir": "./typings", 7 | "esModuleInterop": true, 8 | "importHelpers": true, 9 | "module": "commonjs", 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | // "outDir": "lib", 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "esnext" 18 | // "typeRoots": [ 19 | // "./types", 20 | // "./node_modules/@types", 21 | // "../../node_modules/@types", 22 | // ] 23 | }, 24 | "typedocOptions": { 25 | "cleanOutputDir": true, 26 | "entryPoints": [ 27 | "./packages/a-msgpack", 28 | "./packages/cloudvision-connector", 29 | "./packages/cloudvision-grpc-web" 30 | ], 31 | "entryPointStrategy": "packages", 32 | "exclude": ["**/test/**", "**/benchmark-msgpack/**"], 33 | "excludeExternals": true, 34 | "excludePrivate": true, 35 | "hideGenerator": true, 36 | "name": "CloudVision", 37 | "out": "docs", 38 | "readme": "README.md" 39 | } 40 | } 41 | --------------------------------------------------------------------------------