├── .eslintrc.js ├── .github ├── ion-test-driver-issue.md ├── no-response.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── ion-test-driver.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .nojekyll ├── .npmignore ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── NOTICE ├── README.md ├── _config.yml ├── browser └── index.html ├── package.json ├── src ├── AbstractWriter.ts ├── BigIntSerde.ts ├── ComparisonResult.ts ├── IntSize.ts ├── Ion.ts ├── IonBinary.ts ├── IonBinaryReader.ts ├── IonBinaryWriter.ts ├── IonCatalog.ts ├── IonConstants.ts ├── IonDecimal.ts ├── IonImport.ts ├── IonLocalSymbolTable.ts ├── IonLowLevelBinaryWriter.ts ├── IonParserBinaryRaw.ts ├── IonParserTextRaw.ts ├── IonPrettyTextWriter.ts ├── IonReader.ts ├── IonSharedSymbolTable.ts ├── IonSpan.ts ├── IonSubstituteSymbolTable.ts ├── IonSymbol.ts ├── IonSymbolIndex.ts ├── IonSymbolToken.ts ├── IonSymbols.ts ├── IonSystemSymbolTable.ts ├── IonText.ts ├── IonTextReader.ts ├── IonTextWriter.ts ├── IonTimestamp.ts ├── IonType.ts ├── IonTypes.ts ├── IonUnicode.ts ├── IonWriteable.ts ├── IonWriter.ts ├── SignAndMagnitudeInt.ts ├── dom │ ├── Blob.ts │ ├── Boolean.ts │ ├── Clob.ts │ ├── Decimal.ts │ ├── Float.ts │ ├── FromJsConstructor.ts │ ├── Integer.ts │ ├── JsValueConversion.ts │ ├── List.ts │ ├── Lob.ts │ ├── Null.ts │ ├── README.md │ ├── SExpression.ts │ ├── Sequence.ts │ ├── String.ts │ ├── Struct.ts │ ├── Symbol.ts │ ├── Timestamp.ts │ ├── Value.ts │ └── index.ts ├── events │ ├── EventStreamError.ts │ ├── IonEvent.ts │ └── IonEventStream.ts └── util.ts ├── test-driver ├── package.json ├── src │ ├── Cli.ts │ ├── CliCommonArgs.ts │ ├── CliCompareArgs.ts │ ├── CliError.ts │ ├── Compare.ts │ ├── ComparisonContext.ts │ ├── ComparisonReport.ts │ ├── OutputFormat.ts │ └── Process.ts └── tsconfig.json ├── test ├── AbstractWriter.ts ├── IntSize.ts ├── IonAnnotations.ts ├── IonBinaryReader.ts ├── IonBinaryTimestamp.ts ├── IonBinaryWriter.ts ├── IonCatalog.ts ├── IonDecimal.ts ├── IonImport.ts ├── IonLocalSymbolTable.ts ├── IonLowLevelBinaryWriter.ts ├── IonParserBinaryRaw.ts ├── IonParserTextRaw.ts ├── IonReaderConsistency.ts ├── IonReaderStepOutThrows.ts ├── IonText.ts ├── IonTextReader.ts ├── IonTextWriter.ts ├── IonTimestamp.ts ├── IonUnicode.ts ├── IonWriteable.ts ├── IonWriterUndefinedParameters.ts ├── dom │ ├── Value.ts │ ├── dom.ts │ ├── equivalence.ts │ ├── json.ts │ └── propertyShadowing.ts ├── dump.ts ├── exampleValues.ts ├── iontests.ts ├── mocha.opts ├── mochaSupport.ts ├── spans.ts ├── textNulls.ts ├── util.ts └── writeSymbolTokens.ts ├── tsconfig.amd.json ├── tsconfig.es6.json ├── tsconfig.json ├── tsconfig.lint.json └── typedoc.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | parserOptions: { 5 | project: "./tsconfig.lint.json", 6 | }, 7 | plugins: [ 8 | "@typescript-eslint", 9 | ], 10 | extends: [ 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 13 | "prettier", // This config turns off ESLint rules that are handled by Prettier. 14 | ], 15 | rules: { 16 | "@typescript-eslint/no-unused-vars": ["off"], // Not really needed, TypeScript strict checks can be used instead. 17 | // TODO Enable and fix other useful rules like `eqeqeq`. 18 | 19 | // Temporary disabled recommended rules that failed after TSLint -> ESLint migration. 20 | // TODO Enable rules back one by one and fix errors. 21 | "@typescript-eslint/ban-ts-comment": ["off"], 22 | "@typescript-eslint/ban-types": ["off"], 23 | "@typescript-eslint/explicit-module-boundary-types" : ["off"], 24 | "@typescript-eslint/no-empty-function" : ["off"], 25 | "@typescript-eslint/no-empty-interface" : ["off"], 26 | "@typescript-eslint/no-explicit-any" : ["off"], 27 | "@typescript-eslint/no-inferrable-types" : ["off"], 28 | "@typescript-eslint/no-namespace" : ["off"], 29 | "@typescript-eslint/no-non-null-assertion" : ["off"], 30 | "@typescript-eslint/no-this-alias" : ["off"], 31 | "@typescript-eslint/no-unnecessary-type-assertion": ["off"], 32 | "@typescript-eslint/no-unsafe-assignment" : ["off"], 33 | "@typescript-eslint/no-unsafe-call" : ["off"], 34 | "@typescript-eslint/no-unsafe-member-access" : ["off"], 35 | "@typescript-eslint/no-unsafe-return" : ["off"], 36 | "@typescript-eslint/prefer-regexp-exec": ["off"], 37 | "@typescript-eslint/restrict-plus-operands": ["off"], 38 | "@typescript-eslint/restrict-template-expressions": ["off"], 39 | "@typescript-eslint/unbound-method": ["off"], 40 | "prefer-const" : ["off"], 41 | }, 42 | overrides: [{ 43 | files: ["./test/**.*"], 44 | rules: { 45 | "@typescript-eslint/no-empty-function": ["off"], 46 | } 47 | }] 48 | }; 49 | -------------------------------------------------------------------------------- /.github/ion-test-driver-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ion-test-driver issue. 3 | labels: bug 4 | --- 5 | Ion-test-driver complained that behavior changed for the commit {{ env.GITHUB_PR_SHA }} created by `{{ payload.sender.login }}`. 6 | Refer to the [workflow]({{ env.GITHUB_WORKFLOW_URL }}) for more details. 7 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: more-information-needed 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. With only the 11 | information that is currently in the issue, we don't have enough information 12 | to take action. Please reach out if you have or find the answers we need so 13 | that we can investigate further. 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master, v5.0.0-development ] 6 | pull_request: 7 | branches: [ master, v5.0.0-development ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | node-version: [14.x, 16.x, 18.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | submodules: recursive 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm run lint 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '43 4 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/ion-test-driver.yml: -------------------------------------------------------------------------------- 1 | name: ion-test-driver 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | ion-test-driver: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout ion-js 10 | uses: actions/checkout@master 11 | with: 12 | repository: amazon-ion/ion-js 13 | ref: master 14 | path: ion-js 15 | 16 | - name: Checkout ion-test-driver 17 | uses: actions/checkout@master 18 | with: 19 | repository: amazon-ion/ion-test-driver 20 | ref: master 21 | path: ion-test-driver 22 | 23 | - name: Set up python3 env 24 | run: python3 -m venv ion-test-driver/venv && . ion-test-driver/venv/bin/activate 25 | 26 | - name: Pip install 27 | run: pip3 install -r ion-test-driver/requirements.txt && pip3 install -e ion-test-driver 28 | 29 | - name: Get main branch HEAD sha 30 | run: cd ion-js && echo `git rev-parse --short=7 HEAD` && echo "main=`git rev-parse --short=7 HEAD`" >> $GITHUB_ENV 31 | 32 | - name: Get current commit sha 33 | run: cd ion-js && echo `git rev-parse --short=7 ${{ github.event.pull_request.head.sha }}` 34 | && echo "cur=`git rev-parse --short=7 ${{ github.event.pull_request.head.sha }}`" >> $GITHUB_ENV 35 | 36 | - name: Run ion-test-driver 37 | run: python3 ion-test-driver/amazon/iontest/ion_test_driver.py -o output 38 | -i ion-js,${{ github.event.pull_request.head.repo.html_url }},${{ github.event.pull_request.head.sha }} 39 | --replace ion-js,https://github.com/amazon-ion/ion-js.git,$main 40 | 41 | - name: Upload result 42 | uses: actions/upload-artifact@v2 43 | with: 44 | name: ion-test-driver-result.ion 45 | path: output/results/ion-test-driver-results.ion 46 | 47 | - name: Showing result 48 | run: cat output/results/ion-test-driver-results.ion 49 | 50 | - name: Analyze two implementations 51 | continue-on-error: true 52 | id: result-diff 53 | run: python3 ion-test-driver/amazon/iontest/ion_test_driver.py -R 54 | ion-js,$main ion-js,$cur output/results/ion-test-driver-results.ion 55 | 56 | - name: Upload analysis report 57 | uses: actions/upload-artifact@v2 58 | with: 59 | name: analysis-report.ion 60 | path: result.ion 61 | 62 | - name: Showing report 63 | run: cat result.ion 64 | 65 | - name: Check if ion-test-driver fails 66 | if: ${{ steps.result-diff.outcome == 'failure' }} 67 | run: echo 'Implementation behavior changed, Refer to the analysis report in the previous step for the reason.' && exit 1 68 | 69 | open-issue: 70 | runs-on: ubuntu-latest 71 | needs: ion-test-driver 72 | if: ${{ failure() }} 73 | steps: 74 | - uses: actions/checkout@master 75 | - name: Open an issue 76 | uses: JasonEtco/create-an-issue@v2 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | GITHUB_WORKFLOW_URL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}} 80 | GITHUB_PR_SHA: ${{ github.event.pull_request.head.sha }} 81 | with: 82 | assignees: ${{ github.event.sender.login }} 83 | filename: .github/ion-test-driver-issue.md 84 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: ion-js release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | if: startsWith(github.ref, 'refs/tags/v') 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | with: 14 | submodules: recursive 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '16.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | - run: npm install 20 | - name: grunt release 21 | run: ./node_modules/.bin/grunt release 22 | - uses: actions/upload-artifact@v3 23 | with: 24 | name: dist 25 | path: dist/ 26 | - uses: actions/upload-artifact@v3 27 | with: 28 | name: docs 29 | path: docs/ 30 | publish-to-github: 31 | if: startsWith(github.ref, 'refs/tags/v') 32 | runs-on: ubuntu-latest 33 | needs: build 34 | permissions: 35 | checks: write 36 | contents: write 37 | steps: 38 | - name: set env 39 | run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 40 | - uses: actions/checkout@v3 41 | - uses: actions/download-artifact@v4.1.7 42 | with: 43 | name: dist 44 | path: dist/ 45 | - name: create zip file 46 | run: zip -r "ion-js.$RELEASE_TAG-dist.zip" ./dist 47 | - name: upload zip file to github release 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | run: gh release upload "$RELEASE_TAG" "ion-js.$RELEASE_TAG-dist.zip" 51 | publish-to-npm: 52 | if: startsWith(github.ref, 'refs/tags/v') 53 | runs-on: ubuntu-latest 54 | needs: build 55 | steps: 56 | - name: set env 57 | run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 58 | - uses: actions/checkout@v3 59 | - uses: actions/download-artifact@v4.1.7 60 | - run: ls */* | cat 61 | - uses: actions/setup-node@v3 62 | with: 63 | node-version: '16.x' 64 | registry-url: 'https://registry.npmjs.org' 65 | - run: npm install 66 | - name: npm publish 67 | # skip npm publishing if running in a fork 68 | if: github.repository == 'amazon-ion/ion-js' 69 | env: 70 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 71 | run: npm publish 72 | update-docs: 73 | if: startsWith(github.ref, 'refs/tags/v') 74 | runs-on: ubuntu-latest 75 | needs: build 76 | permissions: 77 | contents: write 78 | steps: 79 | - name: set env 80 | run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 81 | - uses: actions/checkout@v3 82 | with: 83 | ref: gh-pages 84 | - uses: actions/download-artifact@v4.1.7 85 | with: 86 | name: docs 87 | path: docs/ 88 | - name: create documentation pull request 89 | env: 90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 91 | run: | 92 | rm -rf api browser 93 | cp -R ./docs/api . 94 | cp -R ./docs/browser . 95 | git add ./api ./browser 96 | git config user.name github-actions 97 | git config user.email github-actions@github.com 98 | git commit -m "adds documentation for $RELEASE_TAG" 99 | git checkout -b "generated-docs-$RELEASE_TAG" 100 | git push --set-upstream origin "generated-docs-$RELEASE_TAG" 101 | REVIEWER="$(gh release view "$RELEASE_TAG" --json author --jq '.author.login')" 102 | gh pr create --fill --base gh-pages -r "$REVIEWER" 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tscache/ 2 | dist/ 3 | test-driver/dist 4 | node_modules/ 5 | **/.baseDir.ts 6 | lcov.info 7 | package-lock.json 8 | browser/scripts 9 | docs/ 10 | coverage-final.json 11 | 12 | ### Emacs ### 13 | # -*- mode: gitignore; -*- 14 | *~ 15 | \#*\# 16 | /.emacs.desktop 17 | /.emacs.desktop.lock 18 | *.elc 19 | auto-save-list 20 | tramp 21 | .\#* 22 | 23 | # Org-mode 24 | .org-id-locations 25 | *_archive 26 | 27 | # flymake-mode 28 | *_flymake.* 29 | 30 | # eshell files 31 | /eshell/history 32 | /eshell/lastdir 33 | 34 | # elpa packages 35 | /elpa/ 36 | 37 | # reftex files 38 | *.rel 39 | 40 | # AUCTeX auto folder 41 | /auto/ 42 | 43 | # cask packages 44 | .cask/ 45 | 46 | # Flycheck 47 | flycheck_*.el 48 | 49 | # server auth directory 50 | /server/ 51 | 52 | # projectiles files 53 | .projectile 54 | 55 | # directory configuration 56 | .dir-locals.el 57 | 58 | ### Intellij ### 59 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 60 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 61 | 62 | # User-specific stuff: 63 | .idea/ 64 | .idea/**/workspace.xml 65 | .idea/**/tasks.xml 66 | 67 | # Sensitive or high-churn files: 68 | .idea/**/dataSources/ 69 | .idea/**/dataSources.ids 70 | .idea/**/dataSources.xml 71 | .idea/**/dataSources.local.xml 72 | .idea/**/sqlDataSources.xml 73 | .idea/**/dynamic.xml 74 | .idea/**/uiDesigner.xml 75 | 76 | # Gradle: 77 | .idea/**/gradle.xml 78 | .idea/**/libraries 79 | 80 | # Mongo Explorer plugin: 81 | .idea/**/mongoSettings.xml 82 | 83 | ## File-based project format: 84 | *.iws 85 | 86 | ## Plugin-specific files: 87 | 88 | # IntelliJ 89 | /out/ 90 | 91 | # mpeltonen/sbt-idea plugin 92 | .idea_modules/ 93 | 94 | # JIRA plugin 95 | atlassian-ide-plugin.xml 96 | 97 | # Crashlytics plugin (for Android Studio and IntelliJ) 98 | com_crashlytics_export_strings.xml 99 | crashlytics.properties 100 | crashlytics-build.properties 101 | fabric.properties 102 | 103 | ### Intellij Patch ### 104 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 105 | 106 | ## Ignoring iml files breaks when using IntelliJ modules. We do not at the moment. 107 | *.iml 108 | .idea 109 | 110 | # modules.xml 111 | # .idea/misc.xml 112 | # *.ipr 113 | 114 | 115 | ### Vim ### 116 | # swap 117 | [._]*.s[a-v][a-z] 118 | [._]*.sw[a-p] 119 | [._]s[a-v][a-z] 120 | [._]sw[a-p] 121 | # session 122 | Session.vim 123 | # temporary 124 | .netrwhist 125 | # auto-generated tag files 126 | tags 127 | 128 | .nyc_output/ 129 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ion-tests"] 2 | path = ion-tests 3 | url = https://github.com/amazon-ion/ion-tests.git 4 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-ion/ion-js/3bee48d30648b29751903652d8e271231bd4ea9b/.nojekyll -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # See https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package 2 | # Use `npm pack` to create a tgz file. View the contents of the .tgz to test your .npmignore 3 | *.map 4 | .baseDir* 5 | .github/ 6 | .gitmodules 7 | .idea 8 | .nojekyll 9 | .nyc_output/ 10 | .prettierignore 11 | .travis.yml 12 | .tscache 13 | Gruntfile.js 14 | _config.yml 15 | browser 16 | docs 17 | ion-tests 18 | src 19 | test 20 | test-driver 21 | tests 22 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/.baseDir.ts -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/amazon-ion/ion-js/issues), or [recently closed](https://github.com/amazon-ion/ion-js/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amazon-ion/ion-js/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/amazon-ion/ion-js/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | 63 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Amazon Ion JavaScript 2 | Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Ion JS in your browser

9 |

10 | and then open the JS console 11 | on your browser. The global variable ion may be used using the API. 12 |

13 | 14 |

15 | A more complete and user friendly tutorial is forthcoming! 16 |

17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-js", 3 | "version": "5.3.0-SNAPSHOT", 4 | "description": "A JavaScript implementation of the Ion data interchange format", 5 | "main": "dist/commonjs/es6/Ion.js", 6 | "types": "dist/commonjs/es6/Ion.d.ts", 7 | "exports": { 8 | "module": "./dist/es6/es6/Ion.js", 9 | "default": "./dist/commonjs/es6/Ion.js" 10 | }, 11 | "scripts": { 12 | "commit": "git-cz", 13 | "prepare": "grunt release", 14 | "lint": "grunt lint && prettier --check 'src/**/*.ts'", 15 | "lint:fix": "prettier --write 'src/**/*.ts'", 16 | "test": "nyc mocha", 17 | "release": "grunt release", 18 | "test-driver": "cd test-driver && npm install", 19 | "build-test-driver": "cd test-driver && npm run build" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/amazon-ion/ion-js.git" 24 | }, 25 | "keywords": [ 26 | "ion", 27 | "JSON", 28 | "data format" 29 | ], 30 | "author": "The Ion Team (https://amazon-ion.github.io/ion-docs/index.html)", 31 | "license": "Apache-2.0", 32 | "bugs": { 33 | "url": "https://github.com/amazon-ion/ion-js/issues" 34 | }, 35 | "homepage": "https://github.com/amazon-ion/ion-js#readme", 36 | "config": { 37 | "commitizen": { 38 | "path": "node_modules/cz-conventional-changelog" 39 | } 40 | }, 41 | "nyc": { 42 | "extends": "@istanbuljs/nyc-config-typescript", 43 | "all": true, 44 | "include": [ 45 | "src/**.ts" 46 | ], 47 | "exclude": [ 48 | "src/.**" 49 | ], 50 | "check-coverage": false 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.18.10", 54 | "@babel/core": "^7.19.1", 55 | "@babel/plugin-transform-object-assign": "^7.18.6", 56 | "@babel/plugin-transform-runtime": "^7.19.1", 57 | "@babel/polyfill": "^7.8.7", 58 | "@babel/preset-env": "^7.10.0", 59 | "@babel/runtime": "^7.10.0", 60 | "@istanbuljs/nyc-config-typescript": "^0.1.3", 61 | "@types/chai": "^4.2.11", 62 | "@types/mocha": "^5.2.7", 63 | "@types/node": "^12.12.42", 64 | "@typescript-eslint/eslint-plugin": "^4.33.0", 65 | "@typescript-eslint/parser": "^4.33.0", 66 | "babelify": "^10.0.0", 67 | "chai": "^4.3.6", 68 | "commitizen": "^4.2.5", 69 | "cz-conventional-changelog": "^3.2.0", 70 | "eslint": "^7.32.0", 71 | "eslint-config-prettier": "^8.5.0", 72 | "grunt": "^1.5.3", 73 | "grunt-babel": "^8.0.0", 74 | "grunt-browserify": "^5.3.0", 75 | "grunt-cli": "^1.3.2", 76 | "grunt-contrib-clean": "^2.0.0", 77 | "grunt-contrib-copy": "^1.0.0", 78 | "grunt-contrib-jshint": "^2.1.0", 79 | "grunt-contrib-uglify": "^5.2.2", 80 | "grunt-eslint": "^23.0.0", 81 | "grunt-shell": "^3.0.1", 82 | "grunt-ts": "^6.0.0-beta.22", 83 | "grunt-typedoc": "^0.2.4", 84 | "mocha": "^6.2.3", 85 | "mocha-typescript": "^1.1.17", 86 | "nyc": "^14.1.1", 87 | "prettier": "2.1.2", 88 | "semantic-release": "^17.2.3", 89 | "source-map-support": "^0.5.19", 90 | "ts-node": "^8.10.1", 91 | "typedoc": "^0.20.37", 92 | "typescript": "Mixed-in classes with functions returning `this` have TS2526 error is DTS file. Refernce issue: https://github.com/microsoft/TypeScript/issues/52687. Upgrade to a latest typescript version once this issue is resolved", 93 | "typescript": "^3.9.10", 94 | "yargs": "^17.5.1" 95 | }, 96 | "browserslist": [ 97 | "Chrome >= 80.0", 98 | "Firefox >= 80.0", 99 | "Opera >= 63.0", 100 | "Edge >= 80.0", 101 | "Safari >= 14.1", 102 | "Samsung >= 12.0" 103 | ] 104 | } 105 | -------------------------------------------------------------------------------- /src/AbstractWriter.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { Reader } from "./IonReader"; 17 | import { IonType } from "./IonType"; 18 | import { IonTypes } from "./IonTypes"; 19 | import { Writer } from "./IonWriter"; 20 | 21 | // TS workaround that avoids the need to copy all Writer method signatures into AbstractWriter 22 | export interface AbstractWriter extends Writer {} 23 | 24 | export abstract class AbstractWriter implements Writer { 25 | protected _annotations: string[] = []; 26 | 27 | addAnnotation(annotation: string): void { 28 | if (!this._isString(annotation)) { 29 | throw new Error("Annotation must be of type string."); 30 | } 31 | this._annotations.push(annotation); 32 | } 33 | 34 | setAnnotations(annotations: string[]): void { 35 | if (annotations === undefined || annotations === null) { 36 | throw new Error("Annotations were undefined or null."); 37 | } else if (!this._validateAnnotations(annotations)) { 38 | throw new Error("Annotations must be of type string[]."); 39 | } else { 40 | this._annotations = annotations; 41 | } 42 | } 43 | 44 | // This method is not part of the Writer interface, but subclasses of AbstractWriter must implement it 45 | // in order to use the default implementations of writeValue() and writeValues() 46 | protected abstract _isInStruct(): boolean; 47 | 48 | writeValues(reader: Reader): void { 49 | this._writeValues(reader); 50 | } 51 | 52 | writeValue(reader: Reader): void { 53 | this._writeValue(reader); 54 | } 55 | 56 | protected _clearAnnotations(): void { 57 | this._annotations = []; 58 | } 59 | 60 | private _writeValues(reader: Reader): void { 61 | let type: IonType | null = reader.type(); 62 | if (type === null) { 63 | type = reader.next(); 64 | } 65 | while (type !== null) { 66 | this._writeValue(reader); 67 | type = reader.next(); 68 | } 69 | } 70 | 71 | private _writeValue(reader: Reader): void { 72 | const type: IonType | null = reader.type(); 73 | if (type === null) { 74 | return; 75 | } 76 | 77 | if (this._isInStruct()) { 78 | const fieldName = reader.fieldName(); 79 | if (fieldName === null) { 80 | throw new Error( 81 | "Cannot call writeValue() when the Writer is in a Struct but the Reader is not." 82 | ); 83 | } 84 | this.writeFieldName(fieldName); 85 | } 86 | 87 | this.setAnnotations(reader.annotations()); 88 | 89 | if (reader.isNull()) { 90 | this.writeNull(type); 91 | return; 92 | } 93 | 94 | switch (type) { 95 | case IonTypes.BOOL: 96 | this.writeBoolean(reader.booleanValue()); 97 | break; 98 | case IonTypes.INT: 99 | this.writeInt(reader.bigIntValue()); 100 | break; 101 | case IonTypes.FLOAT: 102 | this.writeFloat64(reader.numberValue()); 103 | break; 104 | case IonTypes.DECIMAL: 105 | this.writeDecimal(reader.decimalValue()); 106 | break; 107 | case IonTypes.TIMESTAMP: 108 | this.writeTimestamp(reader.timestampValue()); 109 | break; 110 | case IonTypes.SYMBOL: 111 | this.writeSymbol(reader.stringValue()); 112 | break; 113 | case IonTypes.STRING: 114 | this.writeString(reader.stringValue()); 115 | break; 116 | case IonTypes.CLOB: 117 | this.writeClob(reader.uInt8ArrayValue()); 118 | break; 119 | case IonTypes.BLOB: 120 | this.writeBlob(reader.uInt8ArrayValue()); 121 | break; 122 | case IonTypes.LIST: 123 | this.stepIn(IonTypes.LIST); 124 | break; 125 | case IonTypes.SEXP: 126 | this.stepIn(IonTypes.SEXP); 127 | break; 128 | case IonTypes.STRUCT: 129 | this.stepIn(IonTypes.STRUCT); 130 | break; 131 | default: 132 | throw new Error( 133 | "Unrecognized type " + (type !== null ? type.name : type) 134 | ); 135 | } 136 | if (type.isContainer) { 137 | reader.stepIn(); 138 | this._writeValues(reader); 139 | this.stepOut(); 140 | reader.stepOut(); 141 | } 142 | } 143 | 144 | private _validateAnnotations(input: string[]): boolean { 145 | if (!Array.isArray(input)) { 146 | return false; 147 | } 148 | for (let i = 0; i < input.length; i++) { 149 | if (!this._isString(input[i])) { 150 | return false; 151 | } 152 | } 153 | return true; 154 | } 155 | 156 | private _isString(input: string): boolean { 157 | return typeof input === "string"; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/BigIntSerde.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | // TODO: Add fast paths for BigInt values that can fit in a standard Number. 17 | /** 18 | * This class provides logic to convert BigInt values to and from the UInt, Int, VarUInt, and VarInt primitives from the 19 | * Ion spec. 20 | */ 21 | export class BigIntSerde { 22 | private static readonly SERIALIZED_BIGINT_SIZES_TO_PRECOMPUTE = 64; 23 | private static readonly BITS_PER_BYTE = 8n; 24 | private static readonly BYTE_MAX_VALUE = BigInt(0xff); 25 | private static readonly SIZE_THRESHOLDS = BigIntSerde.calculateSizeThresholds(); 26 | 27 | /** 28 | * Encodes the specified BigInt value as a sign-and-magnitude integer. 29 | * 30 | * @param value The integer to encode. 31 | * @param isNegative Whether or not the integer is negative. This cannot be inferred when `value` is zero, 32 | * as the BigInt data type cannot natively represent negative zero. 33 | * @return A byte array containing the encoded Int. 34 | */ 35 | public static toSignedIntBytes( 36 | value: bigint, 37 | isNegative: boolean 38 | ): Uint8Array { 39 | let bytes: Uint8Array = this.toUnsignedIntBytes(value); 40 | if (bytes[0] >= 128) { 41 | const extendedBytes: Uint8Array = new Uint8Array(bytes.length + 1); 42 | extendedBytes.set(bytes, 1); 43 | bytes = extendedBytes; 44 | } 45 | if (isNegative) { 46 | bytes[0] += 0x80; // Flip the sign bit to indicate that it's negative. 47 | } 48 | return bytes; 49 | } 50 | 51 | /** 52 | * Reads the provided byte array as a big-endian, unsigned integer. 53 | * 54 | * @param bytes A byte array containing an encoded UInt. 55 | * @return A BigInt value representing the encoded UInt. 56 | */ 57 | public static fromUnsignedBytes(bytes: Uint8Array): bigint { 58 | let magnitude = 0n; 59 | for (let m = 0; m < bytes.length; m++) { 60 | const byte = BigInt(bytes[m]); 61 | magnitude = magnitude << this.BITS_PER_BYTE; 62 | magnitude = magnitude | byte; 63 | } 64 | return magnitude; 65 | } 66 | 67 | /** 68 | * Encodes the specified BigInt value as a big-endian, unsigned integer. 69 | * @param value The BigInt value to encode. 70 | * @return A byte array containing the encoded UInt. 71 | */ 72 | public static toUnsignedIntBytes(value: bigint): Uint8Array { 73 | // Remove the sign if negative 74 | if (value < 0n) { 75 | value = -value; 76 | } 77 | 78 | const sizeInBytes = this.getUnsignedIntSizeInBytes(value); 79 | const bytes = new Uint8Array(sizeInBytes); 80 | for (let m = sizeInBytes - 1; m >= 0; m--) { 81 | const lastByte = Number(value & this.BYTE_MAX_VALUE); 82 | value = value >> this.BITS_PER_BYTE; 83 | bytes[m] = lastByte; 84 | } 85 | 86 | return bytes; 87 | } 88 | 89 | // Determines how many bytes will be needed to store the UInt encoding of the given BigInt value. 90 | static getUnsignedIntSizeInBytes(value: bigint): number { 91 | // TODO: Profile on real data sets to see if binary search is preferable to a low-to-high linear search. 92 | for (let m = 0; m < this.SIZE_THRESHOLDS.length; m++) { 93 | const threshold = this.SIZE_THRESHOLDS[m]; 94 | if (value <= threshold) { 95 | return m + 1; 96 | } 97 | } 98 | 99 | let sizeInBytes = this.SIZE_THRESHOLDS.length; 100 | let threshold = this.calculateSizeThreshold(sizeInBytes); 101 | while (value > threshold) { 102 | // TODO: Make larger jumps, then refine the search. 103 | sizeInBytes++; 104 | threshold = this.calculateSizeThreshold(sizeInBytes); 105 | } 106 | 107 | return sizeInBytes; 108 | } 109 | 110 | // Called once during initialization. Creates an array of thresholds that can be referred to to determine how many 111 | // bytes will be needed to store the UInt encoding of a given BigInt value. 112 | private static calculateSizeThresholds(): Array { 113 | const thresholds: Array = []; 114 | for (let m = 1; m <= this.SERIALIZED_BIGINT_SIZES_TO_PRECOMPUTE; m++) { 115 | thresholds.push(this.calculateSizeThreshold(m)); 116 | } 117 | return thresholds; 118 | } 119 | 120 | private static calculateSizeThreshold(numberOfBytes: number): bigint { 121 | const exponent = BigInt(numberOfBytes) * this.BITS_PER_BYTE; 122 | const threshold = 2n ** exponent; 123 | return threshold - 1n; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/ComparisonResult.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** Comparison result types for the comparison report 17 | * EQUALS: Indicates the input streams are equal 18 | * NOT_EQUAL: Indicates the input streams are not equal 19 | * ERROR: For all the cases where an error occurs while reading or writing input streams 20 | */ 21 | export enum ComparisonResultType { 22 | EQUAL = "EQUAL", 23 | NOT_EQUAL = "NOT_EQUAL", 24 | ERROR = "ERROR", 25 | } 26 | 27 | /** 28 | * comparison result with event index and message 29 | */ 30 | export class ComparisonResult { 31 | message: string; 32 | result: ComparisonResultType; 33 | actualIndex: number; 34 | expectedIndex: number; 35 | 36 | constructor( 37 | result: ComparisonResultType = ComparisonResultType.EQUAL, 38 | message: string = "", 39 | actualIndex: number = 0, 40 | expectedIndex: number = 0 41 | ) { 42 | this.result = result; 43 | this.message = message; 44 | this.actualIndex = actualIndex; 45 | this.expectedIndex = expectedIndex; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/IntSize.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * Represents whether a given Ion int value will fit safely within a Number or if it requires a BigInt. 18 | */ 19 | export enum IntSize { 20 | /** The Ion int value fits safely in a number. */ 21 | Number, 22 | /** The Ion int value requies a BigInt. */ 23 | BigInt, 24 | } 25 | 26 | export default IntSize; 27 | -------------------------------------------------------------------------------- /src/Ion.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import IntSize from "./IntSize"; 17 | import { BinaryReader } from "./IonBinaryReader"; 18 | import { BinaryWriter } from "./IonBinaryWriter"; 19 | import { Catalog } from "./IonCatalog"; 20 | import { IVM } from "./IonConstants"; 21 | import { defaultLocalSymbolTable } from "./IonLocalSymbolTable"; 22 | import { PrettyTextWriter } from "./IonPrettyTextWriter"; 23 | import { Reader } from "./IonReader"; 24 | import { BinarySpan, StringSpan } from "./IonSpan"; 25 | import { TextReader } from "./IonTextReader"; 26 | import { TextWriter } from "./IonTextWriter"; 27 | import { decodeUtf8 } from "./IonUnicode"; 28 | import { Writeable } from "./IonWriteable"; 29 | import { Writer } from "./IonWriter"; 30 | 31 | /** 32 | * Indicates whether the provided buffer contains binary Ion data. 33 | * 34 | * @param buffer The buffer of data to inspect. 35 | * @returns True if the provided buffer begins with a binary Ion version marker, false otherwise. 36 | */ 37 | function isBinary(buffer: Uint8Array): boolean { 38 | if (buffer.length < 4) { 39 | return false; 40 | } 41 | for (let i = 0; i < 4; i++) { 42 | if (buffer[i] !== IVM.binary[i]) { 43 | return false; 44 | } 45 | } 46 | return true; 47 | } 48 | 49 | /** Octet buffer input types for the Ion reader interface. */ 50 | export type ReaderOctetBuffer = ArrayBufferLike | ArrayLike; 51 | 52 | /** All buffer input types for the Ion reader interface. */ 53 | export type ReaderBuffer = ReaderOctetBuffer | string; 54 | 55 | /** 56 | * Create an {@link Reader} over Ion data in a {@link ReaderBuffer}. 57 | * 58 | * @param buf The Ion data to be used by the reader. Typically a string, UTF-8 encoded buffer (text), or raw 59 | * binary buffer. 60 | * @param catalog Optional catalog to be used by reader to resolve shared symbol table references. 61 | */ 62 | export function makeReader(buf: ReaderBuffer, catalog?: Catalog): Reader { 63 | if (typeof buf === "string") { 64 | return new TextReader(new StringSpan(buf as string), catalog); 65 | } 66 | const bufArray = new Uint8Array(buf as ReaderOctetBuffer); 67 | if (isBinary(bufArray)) { 68 | return new BinaryReader(new BinarySpan(bufArray), catalog); 69 | } else { 70 | return new TextReader(new StringSpan(decodeUtf8(bufArray)), catalog); 71 | } 72 | } 73 | 74 | /** Creates a new Ion Text Writer. */ 75 | export function makeTextWriter(): Writer { 76 | // TODO #384 make LST an optional parameter 77 | return new TextWriter(new Writeable()); 78 | } 79 | 80 | /** Creates a new Ion Text Writer with pretty printing of the text. */ 81 | export function makePrettyWriter(indentSize?: number): Writer { 82 | // TODO #384 make LST an optional parameter 83 | return new PrettyTextWriter(new Writeable(), indentSize); 84 | } 85 | 86 | /** Creates a new Ion Binary Writer. */ 87 | export function makeBinaryWriter(): Writer { 88 | // TODO #384 make LST an optional parameter 89 | const localSymbolTable = defaultLocalSymbolTable(); 90 | return new BinaryWriter(localSymbolTable, new Writeable()); 91 | } 92 | 93 | // Used by the dump*() functions to write each of a sequence of values to the provided Writer. 94 | function _writeAllTo(writer: Writer, values: any[]): Uint8Array { 95 | for (const value of values) { 96 | dom.Value.from(value).writeTo(writer); 97 | } 98 | writer.close(); 99 | return writer.getBytes(); 100 | } 101 | 102 | /** 103 | * Returns a binary Ion representation of the provided values. 104 | * @param values Values to encode in Ion. 105 | */ 106 | export function dumpBinary(...values: any[]): Uint8Array { 107 | return _writeAllTo(makeBinaryWriter(), values); 108 | } 109 | 110 | /** 111 | * Returns a compact text Ion representation of the provided values. 112 | * @param values Values to encode in Ion. 113 | */ 114 | export function dumpText(...values: any[]): string { 115 | return decodeUtf8(_writeAllTo(makeTextWriter(), values)); 116 | } 117 | 118 | /** 119 | * Returns a text Ion representation of the provided values that is generously spaced for 120 | * easier human readability. 121 | * @param values Values to encode in Ion. 122 | */ 123 | export function dumpPrettyText(...values: any[]): string { 124 | return decodeUtf8(_writeAllTo(makePrettyWriter(), values)); 125 | } 126 | 127 | export { Reader, ReaderScalarValue } from "./IonReader"; 128 | export { Writer } from "./IonWriter"; 129 | export { Catalog } from "./IonCatalog"; 130 | export { Decimal } from "./IonDecimal"; 131 | export { defaultLocalSymbolTable } from "./IonLocalSymbolTable"; 132 | export { IntSize }; 133 | export { IonType } from "./IonType"; 134 | export { IonTypes } from "./IonTypes"; 135 | export { SharedSymbolTable } from "./IonSharedSymbolTable"; 136 | export { TimestampPrecision, Timestamp } from "./IonTimestamp"; 137 | export { toBase64 } from "./IonText"; 138 | export { decodeUtf8 } from "./IonUnicode"; 139 | 140 | import * as dom from "./dom"; 141 | export { dom }; 142 | 143 | // Re-export dom convenience methods for easy access via 'ion' 144 | export { load, loadAll } from "./dom"; 145 | 146 | // Events exports and Comparison Result export 147 | export { IonEvent, IonEventType, IonEventFactory } from "./events/IonEvent"; 148 | export { IonEventStream } from "./events/IonEventStream"; 149 | export { EventStreamError } from "./events/EventStreamError"; 150 | export { ComparisonResult, ComparisonResultType } from "./ComparisonResult"; 151 | -------------------------------------------------------------------------------- /src/IonBinary.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** @file Constants and enums for the Ion Binary format */ 17 | 18 | export const NIBBLE_MASK = 0xf; 19 | export const BYTE_MASK = 0xff; 20 | export const TYPE_SHIFT = 4; 21 | export const BYTE_SHIFT = 8; 22 | 23 | export const LEN_MASK = 0xf; 24 | export const LEN_VAR = 14; // 0xe 25 | export const LEN_NULL = 15; // 0xf 26 | 27 | export const TB_NULL = 0; 28 | export const TB_BOOL = 1; 29 | export const TB_INT = 2; 30 | export const TB_NEG_INT = 3; 31 | export const TB_FLOAT = 4; 32 | export const TB_DECIMAL = 5; 33 | export const TB_TIMESTAMP = 6; 34 | export const TB_SYMBOL = 7; 35 | export const TB_STRING = 8; 36 | export const TB_CLOB = 9; 37 | export const TB_BLOB = 10; // 0xa 38 | export const TB_LIST = 11; // 0xb 39 | export const TB_SEXP = 12; // 0xc 40 | export const TB_STRUCT = 13; // 0xd 41 | export const TB_ANNOTATION = 14; // 0xe 42 | -------------------------------------------------------------------------------- /src/IonCatalog.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { SharedSymbolTable } from "./IonSharedSymbolTable"; 17 | import { getSystemSymbolTable } from "./IonSystemSymbolTable"; 18 | 19 | interface SymbolTableIndex { 20 | [name: string]: SharedSymbolTable[]; 21 | } 22 | 23 | function byVersion(x: SharedSymbolTable, y: SharedSymbolTable): number { 24 | return x.version - y.version; 25 | } 26 | 27 | /** 28 | * A catalog holds available shared symbol tables and always includes the system symbol table. 29 | * @see https://amazon-ion.github.io/ion-docs/docs/symbols.html#the-catalog 30 | */ 31 | export class Catalog { 32 | private symbolTables: SymbolTableIndex; 33 | 34 | /** Creates a catalog containing only the system symbol table. */ 35 | constructor() { 36 | this.symbolTables = {}; 37 | this.add(getSystemSymbolTable()); 38 | } 39 | 40 | /** Adds a new shared symbol table to this catalog. */ 41 | add(symbolTable: SharedSymbolTable): void { 42 | if (symbolTable.name === undefined || symbolTable.name === null) { 43 | throw new Error("SymbolTable name must be defined."); 44 | } 45 | const versions = this.symbolTables[symbolTable.name]; 46 | if (versions === undefined) { 47 | this.symbolTables[symbolTable.name] = []; 48 | } 49 | this.symbolTables[symbolTable.name][symbolTable.version] = symbolTable; 50 | } 51 | 52 | /** 53 | * Returns a symbol table by name and version. 54 | * 55 | * @return The symbol table or `null` if it does not exist in the {Catalog}. 56 | */ 57 | getVersion(name: string, version: number): SharedSymbolTable | null { 58 | const tables: SharedSymbolTable[] = this.symbolTables[name]; 59 | if (!tables) { 60 | return null; 61 | } 62 | let table = tables[version]; 63 | if (!table) { 64 | table = tables[tables.length]; 65 | } 66 | return table ? table : null; 67 | } 68 | 69 | /** 70 | * Retrieves the latest version of a symbol table by name. 71 | * 72 | * @return The symbol table or `null` if it does not exist in the {Catalog}. 73 | */ 74 | getTable(name: string): SharedSymbolTable | null { 75 | const versions = this.symbolTables[name]; 76 | if (versions === undefined) { 77 | return null; 78 | } 79 | return versions[versions.length - 1]; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/IonConstants.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | export const EOF = -1; 17 | 18 | export const IVM = { 19 | text: "$ion_1_0", 20 | binary: new Uint8Array([0xe0, 0x01, 0x00, 0xea]), 21 | sid: 2, 22 | }; 23 | -------------------------------------------------------------------------------- /src/IonImport.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { SharedSymbolTable } from "./IonSharedSymbolTable"; 17 | 18 | /** 19 | * A shared symbol table import. 20 | * 21 | * Import order in shared symbol tables is important, so each import also 22 | * references a parent (previous) import (except for the implicit system symbol 23 | * table import, which has no parent). Optionally, the number of symbols to 24 | * import from a given symbol table may be specified as the "length" of the 25 | * import. 26 | * 27 | * @see https://amazon-ion.github.io/ion-docs/symbols.html#imports 28 | */ 29 | export class Import { 30 | private readonly _offset: number; 31 | private readonly _length: number; 32 | private readonly _parent: Import | null; 33 | private readonly _symbolTable: SharedSymbolTable; 34 | 35 | constructor( 36 | parent: Import | null, 37 | symbolTable: SharedSymbolTable, 38 | length?: number | null 39 | ) { 40 | this._parent = parent; 41 | this._symbolTable = symbolTable; 42 | this._offset = this.parent ? this.parent.offset + this.parent.length : 1; 43 | this._length = length || this.symbolTable.numberOfSymbols; 44 | } 45 | 46 | get parent(): Import | null { 47 | return this._parent; 48 | } 49 | 50 | get offset(): number { 51 | return this._offset; 52 | } 53 | 54 | get length(): number { 55 | return this._length; 56 | } 57 | 58 | get symbolTable(): SharedSymbolTable { 59 | return this._symbolTable; 60 | } 61 | 62 | getSymbolText(symbolId: number): string | undefined { 63 | if (this.parent === undefined) { 64 | throw new Error("Illegal parent state."); 65 | } 66 | if (this.parent !== null) { 67 | const parentSymbol = this.parent.getSymbolText(symbolId); 68 | if (parentSymbol) { 69 | return parentSymbol; 70 | } 71 | } 72 | 73 | const index: number = symbolId - this.offset; 74 | if (index >= 0 && index < this.length) { 75 | return this.symbolTable.getSymbolText(index); 76 | } 77 | return undefined; 78 | } 79 | 80 | getSymbolId(symbolText: string): number | undefined { 81 | let symbolId; 82 | if (this.parent !== null) { 83 | symbolId = this.parent.getSymbolId(symbolText); 84 | if (symbolId) { 85 | return symbolId; 86 | } 87 | } 88 | 89 | symbolId = this.symbolTable.getSymbolId(symbolText); 90 | if (symbolId !== null && symbolId !== undefined && symbolId < this.length) { 91 | return symbolId + this.offset; 92 | } 93 | 94 | return undefined; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/IonLocalSymbolTable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { Import } from "./IonImport"; 17 | import { SymbolIndex } from "./IonSymbolIndex"; 18 | import { getSystemSymbolTableImport } from "./IonSystemSymbolTable"; 19 | 20 | /** 21 | * A local symbol table defines all the symbols which aren't included in the system 22 | * symbol table or from a shared symbol table via an import. 23 | */ 24 | export class LocalSymbolTable { 25 | private readonly _import: Import; 26 | private readonly offset: number; 27 | private index: SymbolIndex = Object.create(null); 28 | 29 | constructor(theImport: Import | null, symbols: (string | null)[] = []) { 30 | if (theImport === null) { 31 | this._import = getSystemSymbolTableImport(); 32 | } else { 33 | this._import = theImport; 34 | } 35 | this.offset = this._import.offset + this._import.length; 36 | 37 | for (const symbol_ of symbols) { 38 | this.assignSymbolId(symbol_); 39 | } 40 | } 41 | 42 | private _symbols: (string | null)[] = []; 43 | 44 | get symbols(): (string | null)[] { 45 | return this._symbols; 46 | } 47 | 48 | get maxId(): number { 49 | return this.offset + this._symbols.length - 1; 50 | } 51 | 52 | get import(): Import { 53 | return this._import; 54 | } 55 | 56 | getSymbolId(symbol_: string): number { 57 | return this._import.getSymbolId(symbol_) || this.index[symbol_]; 58 | } 59 | 60 | addSymbol(symbol_: string | null): number { 61 | if (symbol_ !== null) { 62 | const existingSymbolId = this.getSymbolId(symbol_); 63 | if (existingSymbolId !== undefined) { 64 | return existingSymbolId; 65 | } 66 | } 67 | const symbolId = this.offset + this.symbols.length; 68 | this.symbols.push(symbol_); 69 | if (symbol_ !== null) { 70 | this.index[symbol_] = symbolId; 71 | } 72 | return symbolId; 73 | } 74 | 75 | // Used during table initialization. Unlike `addSymbol`, `assignSymbolId` will not discard strings that are already 76 | // in the symbol table. Ignoring duplicate symbols during table construction can cause symbol IDs that have already 77 | // been used to encode data to become invalid. For example, if a stream uses symbols "foo", "bar", "baz" to encode 78 | // its data even though "baz" was also defined in an imported table, discarding "baz" will cause data already encoded 79 | // with that ID to become corrupted. 80 | private assignSymbolId(symbol: string | null): number { 81 | // Push the text onto the end of our array of strings no matter what 82 | const symbolId = this.offset + this.symbols.length; 83 | this.symbols.push(symbol); 84 | // If this text isn't already in our index, go ahead and add it. 85 | if (symbol !== null && this.getSymbolId(symbol) === undefined) { 86 | this.index[symbol] = symbolId; 87 | } 88 | // Return the string's index in the symbol table, even if it isn't the lowest one. 89 | return symbolId; 90 | } 91 | 92 | getSymbolText(symbolId: number): string | null { 93 | if (symbolId > this.maxId) { 94 | throw new Error( 95 | "Symbol $" + symbolId.toString() + " greater than maxID." 96 | ); 97 | } 98 | const importedSymbol: string | undefined = this.import.getSymbolText( 99 | symbolId 100 | ); 101 | if (importedSymbol !== undefined) { 102 | return importedSymbol; 103 | } 104 | const index = symbolId - this.offset; 105 | return this.symbols[index]; 106 | } 107 | 108 | numberOfSymbols(): number { 109 | return this._symbols.length; 110 | } 111 | } 112 | 113 | export function defaultLocalSymbolTable(): LocalSymbolTable { 114 | return new LocalSymbolTable(getSystemSymbolTableImport()); 115 | } 116 | -------------------------------------------------------------------------------- /src/IonLowLevelBinaryWriter.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { BigIntSerde } from "./BigIntSerde"; 17 | import { Writeable } from "./IonWriteable"; 18 | 19 | /** 20 | * Values in the Ion binary format are serialized as a sequence of low-level fields. This 21 | * writer is responsible for emitting those fields in the proper format. 22 | * @see https://amazon-ion.github.io/ion-docs/binary.html#basic-field-formats 23 | */ 24 | export class LowLevelBinaryWriter { 25 | private readonly writeable: Writeable; 26 | 27 | constructor(writeable: Writeable) { 28 | this.writeable = writeable; 29 | } 30 | 31 | static getSignedIntSize(value: number): number { 32 | if (value === 0) { 33 | return 1; 34 | } 35 | const numberOfSignBits = 1; 36 | const magnitude = Math.abs(value); 37 | const numberOfMagnitudeBits = Math.ceil(Math.log2(magnitude + 1)); 38 | const numberOfBits = numberOfMagnitudeBits + numberOfSignBits; 39 | return Math.ceil(numberOfBits / 8); 40 | } 41 | 42 | static getUnsignedIntSize(value: number | bigint): number { 43 | if (typeof value === "bigint") { 44 | return BigIntSerde.getUnsignedIntSizeInBytes(value); 45 | } 46 | if (value === 0) { 47 | return 1; 48 | } 49 | const numberOfBits = Math.floor(Math.log2(value)) + 1; 50 | const numberOfBytes = Math.ceil(numberOfBits / 8); 51 | return numberOfBytes; 52 | } 53 | 54 | static getVariableLengthSignedIntSize(value: number): number { 55 | const absoluteValue: number = Math.abs(value); 56 | if (absoluteValue === 0) { 57 | return 1; 58 | } 59 | const valueBits: number = Math.floor(Math.log2(absoluteValue)) + 1; 60 | const trailingStopBits: number = Math.floor(valueBits / 7); 61 | const leadingStopBit = 1; 62 | const signBit = 1; 63 | return Math.ceil( 64 | (valueBits + trailingStopBits + leadingStopBit + signBit) / 8 65 | ); 66 | } 67 | 68 | static getVariableLengthUnsignedIntSize(value: number): number { 69 | if (value === 0) { 70 | return 1; 71 | } 72 | const valueBits: number = Math.floor(Math.log2(value)) + 1; 73 | const stopBits: number = Math.ceil(valueBits / 7); 74 | return Math.ceil((valueBits + stopBits) / 8); 75 | } 76 | 77 | writeSignedInt(originalValue: number): void { 78 | // TODO this should flip to different modes based on the length calculation because bit shifting will drop to 32 bits. 79 | const length = LowLevelBinaryWriter.getSignedIntSize(originalValue); 80 | let value: number = Math.abs(originalValue); 81 | const tempBuf = new Uint8Array(length); 82 | // Trailing bytes 83 | let i: number = tempBuf.length; 84 | while (value >= 128) { 85 | tempBuf[--i] = value & 0xff; 86 | value >>>= 8; 87 | } 88 | 89 | tempBuf[--i] = value & 0xff; 90 | 91 | // Sign bit 92 | if (1 / originalValue < 0) { 93 | tempBuf[0] |= 0x80; 94 | } 95 | 96 | this.writeable.writeBytes(tempBuf); 97 | } 98 | 99 | writeUnsignedInt(originalValue: number | bigint): void { 100 | if (typeof originalValue === "bigint") { 101 | const encodedBytes = BigIntSerde.toUnsignedIntBytes(originalValue); 102 | this.writeable.writeBytes(encodedBytes); 103 | return; 104 | } 105 | 106 | const length = LowLevelBinaryWriter.getUnsignedIntSize(originalValue); 107 | const tempBuf = new Uint8Array(length); 108 | let value: number = originalValue; 109 | let i: number = tempBuf.length; 110 | 111 | while (value > 0) { 112 | // JavaScript bitwise operators treat operands as 32-bit sequences, 113 | // so we avoid using >>> in order to support values requiring more than 32 bits 114 | tempBuf[--i] = value % 256; 115 | value = Math.trunc(value / 256); 116 | } 117 | 118 | this.writeable.writeBytes(tempBuf); 119 | } 120 | 121 | writeVariableLengthSignedInt(originalValue: number): void { 122 | const tempBuf = new Uint8Array( 123 | LowLevelBinaryWriter.getVariableLengthSignedIntSize(originalValue) 124 | ); 125 | let value: number = Math.abs(originalValue); 126 | let i = tempBuf.length - 1; 127 | 128 | while (value >= 64) { 129 | tempBuf[i--] = value & 0x7f; 130 | value >>>= 7; 131 | } 132 | 133 | // Leading byte 134 | tempBuf[i] = value; 135 | 136 | // Sign bit 137 | if (1 / originalValue < 0) { 138 | tempBuf[i] |= 0x40; 139 | } 140 | 141 | // Stop bit 142 | tempBuf[tempBuf.length - 1] |= 0x80; 143 | 144 | this.writeable.writeBytes(tempBuf); 145 | } 146 | 147 | writeVariableLengthUnsignedInt(originalValue: number): void { 148 | const tempBuf = new Uint8Array( 149 | LowLevelBinaryWriter.getVariableLengthUnsignedIntSize(originalValue) 150 | ); 151 | let value: number = originalValue; 152 | let i = tempBuf.length; 153 | 154 | tempBuf[--i] = (value & 0x7f) | 0x80; 155 | value >>>= 7; 156 | 157 | while (value > 0) { 158 | tempBuf[--i] = value & 0x7f; 159 | value >>>= 7; 160 | } 161 | 162 | this.writeable.writeBytes(tempBuf); 163 | } 164 | 165 | writeByte(byte: number): void { 166 | this.writeable.writeByte(byte); 167 | } 168 | 169 | writeBytes(bytes: Uint8Array): void { 170 | this.writeable.writeBytes(bytes); 171 | } 172 | 173 | getBytes(): Uint8Array { 174 | return this.writeable.getBytes(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/IonSharedSymbolTable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * A shared symbol table. 18 | * @see https://amazon-ion.github.io/ion-docs/docs/symbols.html#shared-symbol-tables 19 | */ 20 | export class SharedSymbolTable { 21 | protected readonly _idsByText: Map; 22 | 23 | constructor( 24 | private readonly _name: string, 25 | private readonly _version: number, 26 | private readonly _symbols: string[] 27 | ) { 28 | this._idsByText = new Map(); 29 | this._numberOfSymbols = this._symbols.length; 30 | // Iterate through the symbol array in reverse order so if the same string appears more than 31 | // once the smaller symbol ID is stored. 32 | for (let m = _symbols.length - 1; m >= 0; m--) { 33 | this._idsByText.set(_symbols[m], m); 34 | } 35 | } 36 | 37 | protected _numberOfSymbols: number; 38 | 39 | get numberOfSymbols(): number { 40 | return this._numberOfSymbols; 41 | } 42 | 43 | get name(): string { 44 | return this._name; 45 | } 46 | 47 | get version(): number { 48 | return this._version; 49 | } 50 | 51 | getSymbolText(symbolId: number): string | undefined { 52 | if (symbolId < 0) { 53 | throw new Error( 54 | `Index ${symbolId} is out of bounds for the SharedSymbolTable name=${this.name}, version=${this.version}` 55 | ); 56 | } 57 | if (symbolId >= this.numberOfSymbols) { 58 | return undefined; 59 | } 60 | return this._symbols[symbolId]; 61 | } 62 | 63 | getSymbolId(text: string): number | undefined { 64 | return this._idsByText.get(text); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/IonSubstituteSymbolTable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { SharedSymbolTable } from "./IonSharedSymbolTable"; 17 | 18 | /** 19 | * A special case of shared symbol table whose entries are all undefined. Used in certain cases 20 | * when an import cannot be satisfied by the current catalog. 21 | * @see https://amazon-ion.github.io/ion-docs/symbols.html#imports 22 | */ 23 | export class SubstituteSymbolTable extends SharedSymbolTable { 24 | constructor(length: number) { 25 | if (length < 0) { 26 | throw new Error( 27 | "Cannot instantiate a SubstituteSymbolTable with a negative length. (" + 28 | length + 29 | ")" 30 | ); 31 | } 32 | super("_substitute", -1, []); 33 | this._numberOfSymbols = length; 34 | } 35 | 36 | getSymbolText(symbolId: number): string | undefined { 37 | if (symbolId < 0) { 38 | throw new Error( 39 | `Index ${symbolId} is out of bounds for the SharedSymbolTable name=${this.name}, version=${this.version}` 40 | ); 41 | } 42 | return undefined; 43 | } 44 | 45 | getSymbolId(text: string): number | undefined { 46 | return undefined; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/IonSymbol.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { asAscii } from "./IonText"; 17 | 18 | export class Symbol { 19 | sid: number; 20 | name: string; 21 | 22 | constructor(id: number, val: string) { 23 | this.sid = id; 24 | this.name = val; 25 | } 26 | 27 | toString(): string { 28 | const s = 29 | "sym::{id:" + asAscii(this.sid) + ',val:"' + asAscii(this.name) + '"'; 30 | return s; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/IonSymbolIndex.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * An interface representing a map of symbol names to symbol ids. 18 | */ 19 | export interface SymbolIndex { 20 | [name: string]: number; 21 | } 22 | -------------------------------------------------------------------------------- /src/IonSymbolToken.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * An Ion symbol token (used to represent field names, annotations, 18 | * and symbol values) providing both the symbol text and the assigned 19 | * symbol ID. 20 | */ 21 | export class SymbolToken { 22 | private static _UNKNOWN_SYMBOL_ID = -1; 23 | 24 | constructor( 25 | private text: string | null, 26 | private sid: number = SymbolToken._UNKNOWN_SYMBOL_ID 27 | ) {} 28 | 29 | /** 30 | * Returns the text of this symbol, or null if the text is unknown. 31 | */ 32 | public getText(): string | null { 33 | return this.text; 34 | } 35 | 36 | /** 37 | * Returns the symbol ID (sid) of this symbol, or -1 if the sid is unknown. 38 | */ 39 | public getSid(): number { 40 | return this.sid; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/IonSymbols.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { Catalog } from "./IonCatalog"; 17 | import { Import } from "./IonImport"; 18 | import { LocalSymbolTable } from "./IonLocalSymbolTable"; 19 | import { Reader } from "./IonReader"; 20 | import { SharedSymbolTable } from "./IonSharedSymbolTable"; 21 | import { SubstituteSymbolTable } from "./IonSubstituteSymbolTable"; 22 | import { getSystemSymbolTableImport } from "./IonSystemSymbolTable"; 23 | import { IonTypes } from "./Ion"; 24 | 25 | export const ion_symbol_table = "$ion_symbol_table"; 26 | export const ion_symbol_table_sid = 3; 27 | 28 | const empty_struct = {}; 29 | 30 | function load_imports(reader: Reader, catalog: Catalog): Import { 31 | let import_: Import = getSystemSymbolTableImport(); 32 | 33 | reader.stepIn(); // into the array 34 | while (reader.next()) { 35 | reader.stepIn(); // into the struct of 1 import 36 | 37 | let name: string | null = null; 38 | let version: number | null = 1; 39 | let maxId: number | null = null; 40 | 41 | while (reader.next()) { 42 | switch (reader.fieldName()) { 43 | case "name": 44 | name = reader.stringValue(); 45 | break; 46 | case "version": 47 | version = reader.numberValue(); 48 | break; 49 | case "max_id": 50 | maxId = reader.numberValue(); 51 | } 52 | } 53 | 54 | if (version === null || version < 1) { 55 | version = 1; 56 | } 57 | 58 | if (name && name !== "$ion") { 59 | let symbolTable: SharedSymbolTable | null = catalog.getVersion( 60 | name, 61 | version! 62 | ); 63 | if (!symbolTable) { 64 | if (maxId === undefined) { 65 | throw new Error( 66 | `No exact match found when trying to import symbol table ${name} version ${version}` 67 | ); 68 | } else { 69 | symbolTable = catalog.getTable(name); 70 | } 71 | } 72 | 73 | if (!symbolTable) { 74 | symbolTable = new SubstituteSymbolTable(maxId!); 75 | } 76 | 77 | import_ = new Import(import_, symbolTable!, maxId); 78 | } 79 | 80 | reader.stepOut(); // out of one import struct 81 | } 82 | reader.stepOut(); // out of the array of imports 83 | 84 | return import_; 85 | } 86 | 87 | function load_symbols(reader: Reader): (string | null)[] { 88 | const symbols: (string | null)[] = []; 89 | 90 | reader.stepIn(); 91 | while (reader.next()) { 92 | symbols.push(reader.stringValue()); 93 | } 94 | reader.stepOut(); 95 | 96 | return symbols; 97 | } 98 | 99 | /** 100 | * Constructs a {LocalSymbolTable} from the given Ion {Reader}. 101 | * 102 | * @param catalog The catalog to resolve imported shared symbol tables from. 103 | * @param reader The Ion {Reader} over the local symbol table in its serialized form. 104 | * @param currentSymbolTable Current local symbol table for the reader. 105 | */ 106 | export function makeSymbolTable( 107 | catalog: Catalog, 108 | reader: Reader, 109 | currentSymbolTable: LocalSymbolTable 110 | ): LocalSymbolTable { 111 | let import_: Import | null = null; 112 | let symbols: (string | null)[] = []; 113 | let foundSymbols: boolean = false; 114 | let foundImports: boolean = false; 115 | let foundLstAppend: boolean = false; 116 | 117 | reader.stepIn(); 118 | while (reader.next()) { 119 | switch (reader.fieldName()) { 120 | case "imports": 121 | if (foundImports) { 122 | throw new Error("Multiple import fields found."); 123 | } 124 | let ion_type = reader.type(); 125 | if ( 126 | ion_type === IonTypes.SYMBOL && 127 | reader.stringValue() === ion_symbol_table 128 | ) { 129 | // this is a local symbol table append 130 | import_ = currentSymbolTable.import; 131 | let symbols_ = symbols; 132 | symbols = currentSymbolTable.symbols; 133 | symbols.push(...symbols_); 134 | foundLstAppend = true; 135 | } else if (ion_type === IonTypes.LIST) { 136 | import_ = load_imports(reader, catalog); 137 | } else { 138 | throw new Error( 139 | `Expected import field name to be a list or symbol found ${ion_type}` 140 | ); 141 | } 142 | foundImports = true; 143 | break; 144 | case "symbols": 145 | if (foundSymbols) { 146 | throw new Error("Multiple symbol fields found."); 147 | } 148 | if (foundLstAppend) { 149 | symbols.push(...load_symbols(reader)); 150 | } else { 151 | symbols = load_symbols(reader); 152 | } 153 | foundSymbols = true; 154 | break; 155 | } 156 | } 157 | reader.stepOut(); 158 | 159 | return new LocalSymbolTable(import_, symbols); 160 | } 161 | -------------------------------------------------------------------------------- /src/IonSystemSymbolTable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * @file Helper methods for obtaining the system symbol table or its Import. 18 | * @see https://amazon-ion.github.io/ion-docs/docs/symbols.html#system-symbols 19 | */ 20 | import { Import } from "./IonImport"; 21 | import { SharedSymbolTable } from "./IonSharedSymbolTable"; 22 | 23 | const systemSymbolTable: SharedSymbolTable = new SharedSymbolTable("$ion", 1, [ 24 | "$ion", 25 | "$ion_1_0", 26 | "$ion_symbol_table", 27 | "name", 28 | "version", 29 | "imports", 30 | "symbols", 31 | "max_id", 32 | "$ion_shared_symbol_table", 33 | ]); 34 | 35 | export function getSystemSymbolTable(): SharedSymbolTable { 36 | return systemSymbolTable; 37 | } 38 | 39 | export function getSystemSymbolTableImport(): Import { 40 | return new Import(null, getSystemSymbolTable()); 41 | } 42 | -------------------------------------------------------------------------------- /src/IonType.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** Ion value type enumeration class. */ 17 | export class IonType { 18 | constructor( 19 | /** The binary type ID for this Ion Type. */ 20 | public readonly binaryTypeId: number, 21 | /** The textual name of this type. */ 22 | public readonly name: string, 23 | /** Whether or not this type is a scalar value. */ 24 | public readonly isScalar: boolean, 25 | /** Whether or not this type is a `clob` or `blob`. */ 26 | public readonly isLob: boolean, 27 | /** Whether or not this type is an `int`, `float`, or `decimal`. */ 28 | public readonly isNumeric: boolean, 29 | /** Whether or not this type is a `list`, `sexp`, or `struct`. */ 30 | public readonly isContainer: boolean 31 | ) {} 32 | } 33 | -------------------------------------------------------------------------------- /src/IonTypes.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { IonType } from "./IonType"; 17 | 18 | /** Enumeration of the Ion types. */ 19 | export const IonTypes = { 20 | NULL: new IonType(0, "null", true, false, false, false), 21 | BOOL: new IonType(1, "bool", true, false, false, false), 22 | // note that INT is actually 0x2 **and** 0x3 in the Ion binary encoding 23 | INT: new IonType(2, "int", true, false, true, false), 24 | FLOAT: new IonType(4, "float", true, false, true, false), 25 | DECIMAL: new IonType(5, "decimal", true, false, false, false), 26 | TIMESTAMP: new IonType(6, "timestamp", true, false, false, false), 27 | SYMBOL: new IonType(7, "symbol", true, false, false, false), 28 | STRING: new IonType(8, "string", true, false, false, false), 29 | CLOB: new IonType(9, "clob", true, true, false, false), 30 | BLOB: new IonType(10, "blob", true, true, false, false), 31 | LIST: new IonType(11, "list", false, false, false, true), 32 | SEXP: new IonType(12, "sexp", false, false, false, true), 33 | STRUCT: new IonType(13, "struct", false, false, false, true), 34 | }; 35 | -------------------------------------------------------------------------------- /src/IonUnicode.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | const JS_DECODER_MAX_BYTES = 512; 17 | 18 | // Check whether this runtime supports the `TextDecoder` feature 19 | let textDecoder; 20 | // @ts-expect-error: Typescript will complain about TextDecoder being undefined 21 | if (typeof TextDecoder !== "undefined") { 22 | // @ts-expect-error: Typescript will complain about TextDecoder being undefined 23 | textDecoder = new TextDecoder("utf8", { fatal: true }); 24 | } else { 25 | textDecoder = null; 26 | } 27 | 28 | /** 29 | * @file Constants and helper methods for Unicode. 30 | * @see https://amazon-ion.github.io/ion-docs/stringclob.html 31 | * @see http://www.unicode.org/versions/Unicode5.0.0/ 32 | */ 33 | export function encodeUtf8(s: string): Uint8Array { 34 | let i = 0, 35 | c; 36 | const bytes = new Uint8Array(s.length * 4); 37 | 38 | for (let ci = 0; ci < s.length; ci++) { 39 | c = s.charCodeAt(ci); 40 | if (c < 128) { 41 | bytes[i++] = c; 42 | continue; 43 | } 44 | if (c < 2048) { 45 | bytes[i++] = (c >> 6) | 192; 46 | } else { 47 | if (c > 0xd7ff && c < 0xdc00) { 48 | if (++ci >= s.length) { 49 | throw new Error("UTF-8 encode: incomplete surrogate pair"); 50 | } 51 | const c2 = s.charCodeAt(ci); 52 | if (c2 < 0xdc00 || c2 > 0xdfff) { 53 | throw new Error( 54 | "UTF-8 encode: second surrogate character 0x" + 55 | c2.toString(16) + 56 | " at index " + 57 | ci + 58 | " out of range" 59 | ); 60 | } 61 | c = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff); 62 | bytes[i++] = (c >> 18) | 240; 63 | bytes[i++] = ((c >> 12) & 63) | 128; 64 | } else { 65 | bytes[i++] = (c >> 12) | 224; 66 | } 67 | bytes[i++] = ((c >> 6) & 63) | 128; 68 | } 69 | bytes[i++] = (c & 63) | 128; 70 | } 71 | return bytes.subarray(0, i); 72 | } 73 | 74 | export function decodeUtf8(bytes: Uint8Array): string { 75 | // for bytes > 512 use TextDecoder method - decode() 76 | if (bytes.length > JS_DECODER_MAX_BYTES && textDecoder != null) { 77 | return textDecoder.decode(bytes); 78 | } 79 | let i = 0, 80 | s = "", 81 | c; 82 | while (i < bytes.length) { 83 | c = bytes[i++]; 84 | if (c > 127) { 85 | if (c > 191 && c < 224) { 86 | if (i >= bytes.length) { 87 | throw new Error("UTF-8 decode: incomplete 2-byte sequence"); 88 | } 89 | c = ((c & 31) << 6) | (bytes[i++] & 63); 90 | } else if (c > 223 && c < 240) { 91 | if (i + 1 >= bytes.length) { 92 | throw new Error("UTF-8 decode: incomplete 3-byte sequence"); 93 | } 94 | c = ((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63); 95 | } else if (c > 239 && c < 248) { 96 | if (i + 2 >= bytes.length) { 97 | throw new Error("UTF-8 decode: incomplete 4-byte sequence"); 98 | } 99 | c = 100 | ((c & 7) << 18) | 101 | ((bytes[i++] & 63) << 12) | 102 | ((bytes[i++] & 63) << 6) | 103 | (bytes[i++] & 63); 104 | } else { 105 | throw new Error( 106 | "UTF-8 decode: unknown multibyte start 0x" + 107 | c.toString(16) + 108 | " at index " + 109 | (i - 1) 110 | ); 111 | } 112 | } 113 | if (c <= 0xffff) { 114 | s += String.fromCharCode(c); 115 | } else if (c <= 0x10ffff) { 116 | c -= 0x10000; 117 | s += String.fromCharCode((c >> 10) | 0xd800); 118 | s += String.fromCharCode((c & 0x3ff) | 0xdc00); 119 | } else { 120 | throw new Error( 121 | "UTF-8 decode: code point 0x" + c.toString(16) + " exceeds UTF-16 reach" 122 | ); 123 | } 124 | } 125 | return s; 126 | } 127 | -------------------------------------------------------------------------------- /src/IonWriteable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * A byte array builder. 18 | * 19 | * This implementation attempts to minimize append and allocate operations by writing into 20 | * a pre-allocated array, although additional arrays are allocated as necessary. 21 | */ 22 | export class Writeable { 23 | private bufferSize: number; 24 | private buffers: Uint8Array[]; 25 | private index: number; 26 | private clean: boolean; 27 | 28 | constructor(bufferSize?: number) { 29 | this.bufferSize = bufferSize ? bufferSize : 4096; 30 | this.buffers = [new Uint8Array(this.bufferSize)]; 31 | this.index = 0; 32 | this.clean = false; 33 | } 34 | 35 | get currentBuffer() { 36 | return this.buffers[this.buffers.length - 1]; 37 | } 38 | 39 | get totalSize() { 40 | let size = 0; 41 | for (let i = 0; i < this.buffers.length - 1; i++) { 42 | size += this.buffers[i].length; 43 | } 44 | return size + this.index; 45 | } 46 | 47 | writeByte(byte: number) { 48 | this.clean = false; 49 | this.currentBuffer[this.index] = byte; 50 | this.index++; 51 | if (this.index === this.bufferSize) { 52 | this.buffers.push(new Uint8Array(this.bufferSize)); 53 | this.index = 0; 54 | } 55 | } 56 | 57 | writeBytes(buf: Uint8Array, offset?: number, length?: number): void { 58 | if (offset === undefined) { 59 | offset = 0; 60 | } 61 | 62 | const writeLength = 63 | length !== undefined 64 | ? Math.min(buf.length - offset, length) 65 | : buf.length - offset; 66 | if (writeLength < this.currentBuffer.length - this.index - 1) { 67 | this.currentBuffer.set( 68 | buf.subarray(offset, offset + writeLength), 69 | this.index 70 | ); 71 | this.index += writeLength; 72 | } else { 73 | this.buffers[this.buffers.length - 1] = this.currentBuffer.slice( 74 | 0, 75 | this.index 76 | ); 77 | this.buffers.push(buf.subarray(offset, length)); 78 | this.buffers.push(new Uint8Array(this.bufferSize)); 79 | this.clean = false; 80 | this.index = 0; 81 | } 82 | } 83 | 84 | getBytes(): Uint8Array { 85 | if (this.clean) { 86 | return this.buffers[0]; 87 | } 88 | const buffer = new Uint8Array(this.totalSize); 89 | let tempLength = 0; 90 | for (let i = 0; i < this.buffers.length - 1; i++) { 91 | buffer.set(this.buffers[i], tempLength); 92 | tempLength += this.buffers[i].length; 93 | } 94 | buffer.set(this.currentBuffer.subarray(0, this.index), tempLength); 95 | this.buffers = [buffer, new Uint8Array(this.bufferSize)]; 96 | this.index = 0; 97 | this.clean = true; 98 | return buffer; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/SignAndMagnitudeInt.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * Represents a signed, arbitrarily sized integer. 18 | * 19 | * BigInts cannot represent negative zero. This class should be used in situations where negative zero is 20 | * a supported value, such as when decoding binary Ion Int/VarInt. 21 | * 22 | * https://amazon-ion.github.io/ion-docs/docs/binary.html#uint-and-int-fields 23 | * 24 | */ 25 | export default class SignAndMagnitudeInt { 26 | constructor( 27 | public readonly _magnitude: bigint, 28 | public readonly _isNegative = _magnitude < 0n 29 | ) {} 30 | 31 | get magnitude(): bigint { 32 | return this._magnitude; 33 | } 34 | 35 | get isNegative(): boolean { 36 | return this._isNegative; 37 | } 38 | 39 | public static fromNumber(value: number): SignAndMagnitudeInt { 40 | const isNegative = value < 0 || Object.is(value, -0); 41 | const absoluteValue = Math.abs(value); 42 | const magnitude = BigInt(absoluteValue); 43 | return new SignAndMagnitudeInt(magnitude, isNegative); 44 | } 45 | 46 | public equals(other: SignAndMagnitudeInt): boolean { 47 | return ( 48 | this._magnitude === other._magnitude && 49 | this._isNegative === other._isNegative 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/dom/Blob.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, toBase64, Writer } from "../Ion"; 2 | import { Lob } from "./Lob"; 3 | 4 | /** 5 | * Represents a blob[1] value in an Ion stream. 6 | * 7 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#blob 8 | */ 9 | export class Blob extends Lob(IonTypes.BLOB) { 10 | /** 11 | * Constructor. 12 | * @param data Raw, unsigned bytes to represent as a blob. 13 | * @param annotations An optional array of strings to associate with `data`. 14 | */ 15 | constructor(data: Uint8Array, annotations: string[] = []) { 16 | super(data, annotations); 17 | } 18 | 19 | /** 20 | * Converts this Blob to a base64-encoded string when being serialized with `JSON.stringify()`. 21 | */ 22 | toJSON() { 23 | return toBase64(this); 24 | } 25 | 26 | writeTo(writer: Writer): void { 27 | writer.setAnnotations(this.getAnnotations()); 28 | writer.writeBlob(this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/dom/Boolean.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | Primitives, 6 | } from "./FromJsConstructor"; 7 | import { _NativeJsBoolean } from "./JsValueConversion"; 8 | import { Value } from "./Value"; 9 | 10 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 11 | .withPrimitives(Primitives.Boolean) 12 | .withClassesToUnbox(_NativeJsBoolean) 13 | .build(); 14 | 15 | /** 16 | * Represents a boolean[1] value in an Ion stream. 17 | * 18 | * Because this class extends Javascript's (big-B) Boolean data type, it is subject to the same 19 | * surprising behavior when used for control flow. 20 | * 21 | * From the Mozilla Developer Network documentation[2]: 22 | * 23 | * > Any object of which the value is not undefined or null, including a Boolean object 24 | * whose value is false, evaluates to true when passed to a conditional statement. 25 | * 26 | * ```javascript 27 | * var b = false; 28 | * if (b) { 29 | * // this code will NOT be executed 30 | * } 31 | * 32 | * b = new Boolean(false); 33 | * if (b) { 34 | * // this code WILL be executed 35 | * } 36 | * ``` 37 | * 38 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#bool 39 | * [2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean#Description 40 | */ 41 | export class Boolean extends Value( 42 | _NativeJsBoolean, 43 | IonTypes.BOOL, 44 | _fromJsConstructor 45 | ) { 46 | /** 47 | * Constructor. 48 | * @param value The boolean value of the new instance. 49 | * @param annotations An optional array of strings to associate with `value`. 50 | */ 51 | constructor(value: boolean, annotations: string[] = []) { 52 | super(value); 53 | this._setAnnotations(annotations); 54 | } 55 | 56 | booleanValue(): boolean { 57 | return this.valueOf() as boolean; 58 | } 59 | 60 | writeTo(writer: Writer): void { 61 | writer.setAnnotations(this.getAnnotations()); 62 | writer.writeBoolean(this.booleanValue()); 63 | } 64 | 65 | _valueEquals( 66 | other: any, 67 | options: { 68 | epsilon?: number | null; 69 | ignoreAnnotations?: boolean; 70 | ignoreTimestampPrecision?: boolean; 71 | onlyCompareIon?: boolean; 72 | } = { 73 | epsilon: null, 74 | ignoreAnnotations: false, 75 | ignoreTimestampPrecision: false, 76 | onlyCompareIon: true, 77 | } 78 | ): boolean { 79 | let isSupportedType: boolean = false; 80 | let valueToCompare: any = null; 81 | // if the provided value is an ion.dom.Boolean instance. 82 | if (other instanceof Boolean) { 83 | isSupportedType = true; 84 | valueToCompare = other.booleanValue(); 85 | } else if (!options.onlyCompareIon) { 86 | // We will consider other Boolean-ish types 87 | if (typeof other === "boolean" || other instanceof _NativeJsBoolean) { 88 | isSupportedType = true; 89 | valueToCompare = other.valueOf(); 90 | } 91 | } 92 | 93 | if (!isSupportedType) { 94 | return false; 95 | } 96 | 97 | if (this.booleanValue() !== valueToCompare) { 98 | return false; 99 | } 100 | return true; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/dom/Clob.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { Lob } from "./Lob"; 3 | 4 | /** 5 | * Represents a clob[1] value in an Ion stream. 6 | * 7 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#clob 8 | */ 9 | export class Clob extends Lob(IonTypes.CLOB) { 10 | /** 11 | * Constructor. 12 | * @param bytes Raw, unsigned bytes to represent as a clob. 13 | * @param annotations An optional array of strings to associate with `bytes`. 14 | */ 15 | constructor(bytes: Uint8Array, annotations: string[] = []) { 16 | super(bytes, annotations); 17 | } 18 | 19 | writeTo(writer: Writer): void { 20 | writer.setAnnotations(this.getAnnotations()); 21 | writer.writeClob(this); 22 | } 23 | 24 | toJSON() { 25 | // Because the text encoding of the bytes stored in this Clob is unknown, 26 | // we write each byte out as a Unicode escape (e.g. 127 -> 0x7f -> \u007f) 27 | // unless it happens to fall within the ASCII range. 28 | // See the Ion cookbook's guide to down-converting to JSON for details: 29 | // https://amazon-ion.github.io/ion-docs/guides/cookbook.html#down-converting-to-json 30 | let encodedText = ""; 31 | for (const byte of this) { 32 | if (byte >= 32 && byte <= 126) { 33 | encodedText += String.fromCharCode(byte); 34 | continue; 35 | } 36 | const hex = byte.toString(16); 37 | if (hex.length == 1) { 38 | encodedText += "\\u000" + hex; 39 | } else { 40 | encodedText += "\\u00" + hex; 41 | } 42 | } 43 | return encodedText; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/dom/Decimal.ts: -------------------------------------------------------------------------------- 1 | import * as ion from "../Ion"; 2 | import { IonTypes } from "../Ion"; 3 | import { Writer } from "../Ion"; 4 | import { 5 | FromJsConstructor, 6 | FromJsConstructorBuilder, 7 | } from "./FromJsConstructor"; 8 | import { Value } from "./Value"; 9 | import { Float } from "./Float"; 10 | 11 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 12 | .withClasses(ion.Decimal) 13 | .build(); 14 | 15 | /** 16 | * Represents a decimal[1] value in an Ion stream. 17 | * 18 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#decimal 19 | */ 20 | export class Decimal extends Value( 21 | Number, 22 | IonTypes.DECIMAL, 23 | _fromJsConstructor 24 | ) { 25 | private readonly _decimalValue: ion.Decimal; 26 | private readonly _numberValue: number; 27 | 28 | /** 29 | * Constructor. 30 | * @param value The Ion decimal value to represent as a decimal. 31 | * @param annotations An optional array of strings to associate with `value`. 32 | */ 33 | constructor(value: ion.Decimal, annotations?: string[]); 34 | 35 | /** 36 | * Constructor. 37 | * @param value The text Ion value to be parsed as a decimal. 38 | * @param annotations An optional array of strings to associate with `value`. 39 | */ 40 | constructor(value: string, annotations?: string[]); 41 | 42 | /** 43 | * Constructor. 44 | * @param value The number value to represent as a decimal. 45 | * @param annotations An optional array of strings to associate with `value`. 46 | */ 47 | constructor(value: number, annotations?: string[]); 48 | 49 | // This is the unified implementation of the above signatures and is not visible to users. 50 | constructor( 51 | value: ion.Decimal | string | number, 52 | annotations: string[] = [] 53 | ) { 54 | if (typeof value === "string") { 55 | let numberValue = Number(value); 56 | super(numberValue); 57 | this._decimalValue = new ion.Decimal(value); 58 | this._numberValue = numberValue; 59 | } else if (value instanceof ion.Decimal) { 60 | super(value.numberValue()); 61 | this._decimalValue = value; 62 | this._numberValue = value.numberValue(); 63 | } else if (typeof value === "number") { 64 | // if value is a number type 65 | super(value); 66 | this._decimalValue = new ion.Decimal("" + value); 67 | this._numberValue = value; 68 | } else { 69 | throw new Error( 70 | "Decimal value can only be created from number, ion.Decimal or string" 71 | ); 72 | } 73 | this._setAnnotations(annotations); 74 | } 75 | 76 | numberValue(): number { 77 | return this._numberValue; 78 | } 79 | 80 | decimalValue(): ion.Decimal { 81 | return this._decimalValue; 82 | } 83 | 84 | toString(): string { 85 | return this._decimalValue.toString(); 86 | } 87 | 88 | valueOf(): number { 89 | return this._numberValue; 90 | } 91 | 92 | writeTo(writer: Writer): void { 93 | writer.setAnnotations(this.getAnnotations()); 94 | writer.writeDecimal(this.decimalValue()); 95 | } 96 | 97 | _valueEquals( 98 | other: any, 99 | options: { 100 | epsilon?: number | null; 101 | ignoreAnnotations?: boolean; 102 | ignoreTimestampPrecision?: boolean; 103 | onlyCompareIon?: boolean; 104 | coerceNumericType: boolean; 105 | } = { 106 | epsilon: null, 107 | ignoreAnnotations: false, 108 | ignoreTimestampPrecision: false, 109 | onlyCompareIon: true, 110 | coerceNumericType: false, 111 | } 112 | ): boolean { 113 | let isSupportedType: boolean = false; 114 | let valueToCompare: any = null; 115 | // if the provided value is an ion.dom.Decimal instance. 116 | if (other instanceof Decimal) { 117 | isSupportedType = true; 118 | valueToCompare = other.decimalValue(); 119 | } else if (options.coerceNumericType === true && other instanceof Float) { 120 | isSupportedType = true; 121 | valueToCompare = new ion.Decimal(other.toString()); 122 | } else if (!options.onlyCompareIon) { 123 | // We will consider other Decimal-ish types 124 | if (other instanceof ion.Decimal) { 125 | // expectedValue is a non-DOM Decimal 126 | isSupportedType = true; 127 | valueToCompare = other; 128 | } else if (other instanceof Number || typeof other === "number") { 129 | isSupportedType = true; 130 | // calling numberValue() on ion.Decimal is lossy and could result in imprecise comparisons 131 | // hence converting number to ion.Decimal for comparison even though it maybe expensive 132 | valueToCompare = new ion.Decimal(other.toString()); 133 | } 134 | } 135 | 136 | if (!isSupportedType) { 137 | return false; 138 | } 139 | 140 | return this.decimalValue().equals(valueToCompare); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/dom/Float.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | Primitives, 6 | } from "./FromJsConstructor"; 7 | import { Value } from "./Value"; 8 | import { Decimal } from "./Decimal"; 9 | import * as ion from "../Ion"; 10 | 11 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 12 | .withPrimitives(Primitives.Number) 13 | .withClassesToUnbox(Number) 14 | .build(); 15 | 16 | /** 17 | * Represents a float[1] value in an Ion stream. 18 | * 19 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#float 20 | */ 21 | export class Float extends Value(Number, IonTypes.FLOAT, _fromJsConstructor) { 22 | /** 23 | * Constructor. 24 | * @param value The numeric value to represent as a float. 25 | * @param annotations An optional array of strings to associate with `value`. 26 | */ 27 | constructor(value: number, annotations: string[] = []) { 28 | super(value); 29 | this._setAnnotations(annotations); 30 | } 31 | 32 | public numberValue(): number { 33 | return +this.valueOf(); 34 | } 35 | 36 | writeTo(writer: Writer): void { 37 | writer.setAnnotations(this.getAnnotations()); 38 | writer.writeFloat64(this.numberValue()); 39 | } 40 | 41 | _valueEquals( 42 | other: any, 43 | options: { 44 | epsilon?: number | null; 45 | ignoreAnnotations?: boolean; 46 | ignoreTimestampPrecision?: boolean; 47 | onlyCompareIon?: boolean; 48 | coerceNumericType: boolean; 49 | } = { 50 | epsilon: null, 51 | ignoreAnnotations: false, 52 | ignoreTimestampPrecision: false, 53 | onlyCompareIon: true, 54 | coerceNumericType: false, 55 | } 56 | ): boolean { 57 | let isSupportedType: boolean = false; 58 | let valueToCompare: any = null; 59 | // if the provided value is an ion.dom.Float instance. 60 | if (other instanceof Float) { 61 | isSupportedType = true; 62 | valueToCompare = other.numberValue(); 63 | } else if (options.coerceNumericType === true && other instanceof Decimal) { 64 | // if other is Decimal convert both values to Decimal for comparison. 65 | let thisValue = new ion.Decimal(other.toString()); 66 | return thisValue!.equals(other.decimalValue()); 67 | } else if (!options.onlyCompareIon) { 68 | // We will consider other Float-ish types 69 | if (other instanceof Number || typeof other === "number") { 70 | isSupportedType = true; 71 | valueToCompare = other.valueOf(); 72 | } 73 | } 74 | 75 | if (!isSupportedType) { 76 | return false; 77 | } 78 | 79 | let result: boolean = Object.is(this.numberValue(), valueToCompare); 80 | 81 | if (options.epsilon != null) { 82 | if ( 83 | result || 84 | Math.abs(this.numberValue() - valueToCompare) <= options.epsilon 85 | ) { 86 | return true; 87 | } 88 | } 89 | return result; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/dom/FromJsConstructor.ts: -------------------------------------------------------------------------------- 1 | import { dom, IonTypes } from "../Ion"; 2 | import { _hasValue } from "../util"; 3 | import { Constructor, Value } from "./Value"; 4 | 5 | // Converts the provided Iterable into a Set. If no iterable is provided, returns an empty set. 6 | function _newSet(values?: Iterable): Set { 7 | if (_hasValue(values)) { 8 | return new Set(values); 9 | } 10 | return new Set(); 11 | } 12 | 13 | /** 14 | * Builder to configure and instantiate FromJsConstructor objects. See the documentation for 15 | * the FromJsConstructor class for a description of each field. 16 | * 17 | * Package-visible for use in dom.Value subclass definitions. 18 | * @private 19 | */ 20 | export class FromJsConstructorBuilder { 21 | private _primitives: Set; 22 | private _classesToUnbox: Set; 23 | private _classes: Set; 24 | 25 | constructor() { 26 | this._primitives = _newSet(); 27 | this._classesToUnbox = _newSet(); 28 | this._classes = _newSet(); 29 | } 30 | 31 | withPrimitives(...primitives: string[]): FromJsConstructorBuilder { 32 | this._primitives = _newSet(primitives); 33 | return this; 34 | } 35 | 36 | withClasses(...classes: Constructor[]): FromJsConstructorBuilder { 37 | this._classes = _newSet(classes); 38 | return this; 39 | } 40 | 41 | withClassesToUnbox(...classes: Constructor[]): FromJsConstructorBuilder { 42 | this._classesToUnbox = _newSet(classes); 43 | return this; 44 | } 45 | 46 | build(): FromJsConstructor { 47 | return new FromJsConstructor( 48 | this._primitives, 49 | this._classesToUnbox, 50 | this._classes 51 | ); 52 | } 53 | } 54 | 55 | /** 56 | * Provides common conversion and validation logic needed to instantiate subclasses of dom.Value 57 | * from a given Javascript value of unknown type (`any`) and an optional annotations array. 58 | * 59 | * Given a Javascript value, will test its type to see whether it is: 60 | * 1. A primitive that is supported by the specified constructor. 61 | * 2. A boxed primitive that is supported by the specified constructor if unboxed via `valueOf()`. 62 | * 3. A class that is supported by the specified constructor as-is, including boxed primitives. 63 | * 64 | * If the value matches any of the above descriptions, the provided constructor will be invoked 65 | * with the value; otherwise, throws an Error. 66 | * 67 | * Constructors are expected to be compatible with the signature: 68 | * 69 | * constructor(value, annotations: string[]): ClassName 70 | * 71 | * See also: Value._fromJsValue() 72 | */ 73 | export class FromJsConstructor { 74 | /** 75 | * Constructor. 76 | * 77 | * @param _primitives Primitive types that will be passed through as-is. 78 | * @param _classesToUnbox Boxed primitive types that will be converted to primitives via 79 | * `valueOf()` and then passed through. 80 | * @param _classes Classes that will be passed through as-is. 81 | */ 82 | constructor( 83 | private readonly _primitives: Set, 84 | private readonly _classesToUnbox: Set, 85 | private readonly _classes: Set 86 | ) {} 87 | 88 | /** 89 | * Invokes the provided constructor if `jsValue` is of a supported data type; otherwise 90 | * throws an Error. 91 | * 92 | * @param constructor A dom.Value subclass's constructor to call. 93 | * @param jsValue A Javascript value to validate/unbox before passing through to 94 | * the constructor. 95 | * @param annotations An optional array of strings to associate with the newly constructed 96 | * dom.Value. 97 | */ 98 | construct(constructor: any, jsValue: any, annotations: string[] = []): Value { 99 | if (jsValue === null) { 100 | return new dom.Null(IonTypes.NULL, annotations); 101 | } 102 | 103 | const jsValueType = typeof jsValue; 104 | if (jsValueType === "object") { 105 | // jsValue is an unsupported boxed primitive, but we can use it if we convert it to a primitive first 106 | if (this._classesToUnbox.has(jsValue.constructor)) { 107 | return new constructor(jsValue.valueOf(), annotations); 108 | } 109 | 110 | // jsValue is an Object of a supported type, including boxed primitives 111 | if (this._classes.has(jsValue.constructor)) { 112 | return new constructor(jsValue, annotations); 113 | } 114 | 115 | throw new Error( 116 | `Unable to construct a(n) ${constructor.name} from a ${jsValue.constructor.name}.` 117 | ); 118 | } 119 | 120 | if (this._primitives.has(jsValueType)) { 121 | return new constructor(jsValue, annotations); 122 | } 123 | 124 | throw new Error( 125 | `Unable to construct a(n) ${constructor.name} from a ${jsValueType}.` 126 | ); 127 | } 128 | } 129 | 130 | // This namespace will be merged with the class definition above. This allows static values to invoke functions in the 131 | // FromJsConstructor class after the class has been fully initialized. 132 | export namespace FromJsConstructor { 133 | // Useful for dom.Value subclasses that do not use a FromJsConstructor (e.g. Struct, List). 134 | // Because it has no supported types, any attempt to use this instance will throw an Error. 135 | export const NONE: FromJsConstructor = new FromJsConstructorBuilder().build(); 136 | } 137 | 138 | /** 139 | * A mapping of primitive types to the corresponding string that will be returned by 140 | * the `typeof` operator. 141 | */ 142 | export const Primitives = { 143 | /* 144 | * Some possible values are not included because they are unsupported. In particular: 145 | * - "object" indicates that a value is not a primitive. 146 | * - The mapping from Javascript's "symbol" to Ion's type system is not yet clear. 147 | * - No constructors accept "undefined" as a parameter. 148 | */ 149 | Boolean: "boolean", 150 | Number: "number", 151 | String: "string", 152 | BigInt: "bigint", 153 | }; 154 | -------------------------------------------------------------------------------- /src/dom/Integer.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | Primitives, 6 | } from "./FromJsConstructor"; 7 | import { Constructor, Value } from "./Value"; 8 | 9 | // BigInt is an irregular class type in that it provides no constructor, only static 10 | // constructor methods. This means that bigint does not conform to the typical Constructor 11 | // interface of new(...args) => any. Because FromJsConstructor will only use it for 12 | // instanceof tests, we can safely cast it as a Constructor to satisfy the compiler. 13 | const _bigintConstructor: Constructor = (BigInt as unknown) as Constructor; 14 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 15 | .withPrimitives(Primitives.Number, Primitives.BigInt) 16 | .withClassesToUnbox(Number) 17 | .withClasses(_bigintConstructor) 18 | .build(); 19 | 20 | /** 21 | * Represents an integer value in an Ion stream. 22 | * 23 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#int 24 | */ 25 | export class Integer extends Value(Number, IonTypes.INT, _fromJsConstructor) { 26 | private _bigIntValue: bigint | null; 27 | private _numberValue: number; 28 | 29 | /** 30 | * Constructor. 31 | * @param value The numeric value to represent as an integer. 32 | * @param annotations An optional array of strings to associate with `value`. 33 | */ 34 | constructor(value: bigint | number, annotations: string[] = []) { 35 | // If the provided value is a JS number, we will defer constructing a BigInt representation 36 | // of it until it's requested later by a call to bigIntValue(). 37 | if (typeof value === "number") { 38 | super(value); 39 | this._numberValue = value; 40 | this._bigIntValue = null; 41 | } else { 42 | const numberValue: number = Number(value); 43 | super(numberValue); 44 | this._bigIntValue = value; 45 | this._numberValue = numberValue; 46 | } 47 | this._setAnnotations(annotations); 48 | } 49 | 50 | bigIntValue(): bigint { 51 | if (this._bigIntValue === null) { 52 | this._bigIntValue = BigInt(this.numberValue()); 53 | } 54 | return this._bigIntValue; 55 | } 56 | 57 | numberValue(): number { 58 | return this._numberValue; 59 | } 60 | 61 | toString(): string { 62 | if (this._bigIntValue === null) { 63 | return this._numberValue.toString(); 64 | } 65 | return this._bigIntValue.toString(); 66 | } 67 | 68 | valueOf() { 69 | return this.numberValue(); 70 | } 71 | 72 | writeTo(writer: Writer): void { 73 | writer.setAnnotations(this.getAnnotations()); 74 | if (this._bigIntValue === null) { 75 | writer.writeInt(this.numberValue()); 76 | } else { 77 | writer.writeInt(this._bigIntValue); 78 | } 79 | } 80 | 81 | _valueEquals( 82 | other: any, 83 | options: { 84 | epsilon?: number | null; 85 | ignoreAnnotations?: boolean; 86 | ignoreTimestampPrecision?: boolean; 87 | onlyCompareIon?: boolean; 88 | } = { 89 | epsilon: null, 90 | ignoreAnnotations: false, 91 | ignoreTimestampPrecision: false, 92 | onlyCompareIon: true, 93 | } 94 | ): boolean { 95 | let isSupportedType: boolean = false; 96 | let valueToCompare: any = null; 97 | 98 | // if the provided value is an ion.dom.Integer instance. 99 | if (other instanceof Integer) { 100 | isSupportedType = true; 101 | if (this._bigIntValue == null && other._bigIntValue == null) { 102 | valueToCompare = other.numberValue(); 103 | } else { 104 | // One of them is a bigint 105 | valueToCompare = other.bigIntValue(); 106 | } 107 | } else if (!options.onlyCompareIon) { 108 | // We will consider other Integer-ish types 109 | if (other instanceof Number || typeof other === "number") { 110 | isSupportedType = true; 111 | if (this.bigIntValue == null) { 112 | valueToCompare = other.valueOf(); 113 | } else { 114 | valueToCompare = BigInt(other.valueOf()); 115 | } 116 | } else if (typeof other === "bigint") { 117 | isSupportedType = true; 118 | valueToCompare = other; 119 | } 120 | } 121 | 122 | if (!isSupportedType) { 123 | return false; 124 | } 125 | 126 | if (typeof valueToCompare === "bigint") { 127 | return this.bigIntValue() === valueToCompare; 128 | } 129 | return this.numberValue() == valueToCompare; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/dom/JsValueConversion.ts: -------------------------------------------------------------------------------- 1 | import { Decimal, dom, Timestamp } from "../Ion"; 2 | import { IonType } from "../IonType"; 3 | import { IonTypes } from "../IonTypes"; 4 | import { _hasValue } from "../util"; 5 | import { Value } from "./Value"; 6 | 7 | // In ./Boolean.ts and ./String.ts, the native Boolean and String types 8 | // are shadowed by the export class defined in the file, but said class 9 | // definitions still need to reference the original class definitions. 10 | // This provides a mechanism for doing so. 11 | export const _NativeJsBoolean = Boolean; 12 | export const _NativeJsString = String; 13 | 14 | // Typescript interfaces can be used to describe classes' static methods; this can 15 | // be a bit surprising as the methods in the interface are not marked 'static' and 16 | // the class definition does not need to indicate that it implements this interface. 17 | // 18 | // This interface describes classes that offer a static constructor that accepts an 19 | // untyped Javascript value and an optional annotations array as its parameter. Because 20 | // the dom.Value mixin provides a default implementation of this method, all dom.Value 21 | // subtypes implicitly implement this interface. 22 | // 23 | // Package visible for testing. 24 | export interface FromJsValue { 25 | _fromJsValue(jsValue: any, annotations: string[]): Value; 26 | } 27 | 28 | // When Typescript is compiling this code, dom/index.ts depends on Value.ts which in turn 29 | // depends on this file. This means that 'dom' is still being defined and we cannot yet 30 | // reference any of the Value subtypes (dom.Integer, dom.String, etc). We can sidestep this 31 | // issue by lazily initializing this mapping, deferring any reference to those types until 32 | // they are first used. 33 | let _domTypesByIonType: Map | null = null; 34 | 35 | // Lazily initializes our IonType -> Dom type constructor mapping. 36 | function _getDomTypesByIonTypeMap(): Map { 37 | if (_domTypesByIonType === null) { 38 | // Clob, SExpression, and Symbol are not included here as _inferType always 39 | // favors Blob, List, and String respectively. 40 | _domTypesByIonType = new Map([ 41 | [IonTypes.NULL, dom.Null], 42 | [IonTypes.BOOL, dom.Boolean], 43 | [IonTypes.INT, dom.Integer], 44 | [IonTypes.FLOAT, dom.Float], 45 | [IonTypes.DECIMAL, dom.Decimal], 46 | [IonTypes.TIMESTAMP, dom.Timestamp], 47 | [IonTypes.STRING, dom.String], 48 | [IonTypes.BLOB, dom.Blob], 49 | [IonTypes.LIST, dom.List], 50 | [IonTypes.STRUCT, dom.Struct], 51 | ]); 52 | } 53 | return _domTypesByIonType; 54 | } 55 | 56 | // Returns a dom type constructor for the provided IonType. 57 | // This function is exported to assist in unit testing but is not visible at the library level. 58 | export function _domConstructorFor(ionType: IonType): FromJsValue { 59 | const domConstructor = _getDomTypesByIonTypeMap().get(ionType)!; 60 | if (!_hasValue(domConstructor)) { 61 | throw new Error( 62 | `No dom type constructor was found for Ion type ${ionType.name}` 63 | ); 64 | } 65 | return domConstructor; 66 | } 67 | 68 | // Examines the provided JS value and returns the IonType best suited to represent it. 69 | // This function will never return Clob, SExpression, and Symbol; it will always 70 | // favor Blob, List, and String respectively. 71 | function _inferType(value: any): IonType { 72 | if (value === undefined) { 73 | throw new Error("Cannot create an Ion value from `undefined`."); 74 | } 75 | if (value === null) { 76 | return IonTypes.NULL; 77 | } 78 | 79 | const valueType: string = typeof value; 80 | switch (valueType) { 81 | case "string": 82 | return IonTypes.STRING; 83 | case "number": 84 | return Number.isInteger(value) ? IonTypes.INT : IonTypes.FLOAT; 85 | case "boolean": 86 | return IonTypes.BOOL; 87 | case "object": 88 | break; 89 | case "bigint": 90 | return IonTypes.INT; 91 | // TODO: Javascript symbols are not a 1:1 match for Ion symbols, but we may wish 92 | // to support them in Value.from() anyway. 93 | // case "symbol": 94 | default: 95 | throw new Error( 96 | `Value.from() does not support the JS primitive type ${valueType}.` 97 | ); 98 | } 99 | 100 | if (value instanceof BigInt) { 101 | return IonTypes.INT; 102 | } 103 | if (value instanceof Number) { 104 | return Number.isInteger(value.valueOf()) ? IonTypes.INT : IonTypes.FLOAT; 105 | } 106 | if (value instanceof Boolean) { 107 | return IonTypes.BOOL; 108 | } 109 | if (value instanceof String) { 110 | return IonTypes.STRING; 111 | } 112 | if (value instanceof Decimal) { 113 | return IonTypes.DECIMAL; 114 | } 115 | if (value instanceof Date || value instanceof Timestamp) { 116 | return IonTypes.TIMESTAMP; 117 | } 118 | if (value instanceof Uint8Array) { 119 | return IonTypes.BLOB; 120 | } 121 | if (value instanceof Array) { 122 | return IonTypes.LIST; 123 | } 124 | return IonTypes.STRUCT; 125 | } 126 | 127 | // Inspects the provided JS value and constructs a new instance of the appropriate Ion 128 | // type using that value. 129 | export function _ionValueFromJsValue( 130 | value: any, 131 | annotations: string[] = [] 132 | ): Value { 133 | const ionType = _inferType(value); 134 | const ionTypeConstructor: FromJsValue = _domConstructorFor(ionType); 135 | return ionTypeConstructor._fromJsValue(value, annotations); 136 | } 137 | -------------------------------------------------------------------------------- /src/dom/List.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes } from "../Ion"; 2 | import { Sequence } from "./Sequence"; 3 | import { Value } from "./Value"; 4 | 5 | /** 6 | * Represents a list value in an Ion stream. 7 | * 8 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#list 9 | */ 10 | export class List extends Sequence(IonTypes.LIST) { 11 | /** 12 | * Constructor. 13 | * @param children Values that will be contained in the new list. 14 | * @param annotations An optional array of strings to associate with the items in `children`. 15 | */ 16 | constructor(children: Value[], annotations: string[] = []) { 17 | super(children, annotations); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/dom/Lob.ts: -------------------------------------------------------------------------------- 1 | import { IonType, IonTypes } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | } from "./FromJsConstructor"; 6 | import { Value } from "./Value"; 7 | 8 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 9 | .withClasses(Uint8Array) 10 | .build(); 11 | 12 | /** 13 | * This mixin constructs a new class that: 14 | * - Extends `DomValue` 15 | * - Extends `Uint8Array` 16 | * - Has the specified `IonType`. 17 | * 18 | * In practice, serves as a common base class for `Blob` and `Clob`. 19 | * 20 | * @param ionType The IonType to associate with the new DomValue subclass. 21 | * @constructor 22 | * @private 23 | */ 24 | export function Lob(ionType: IonType) { 25 | return class 26 | extends Value(Uint8Array, ionType, _fromJsConstructor) 27 | implements Value { 28 | protected constructor(data: Uint8Array, annotations: string[] = []) { 29 | super(data); 30 | this._setAnnotations(annotations); 31 | } 32 | 33 | uInt8ArrayValue(): Uint8Array { 34 | return this; 35 | } 36 | 37 | _valueEquals( 38 | other: any, 39 | options: { 40 | epsilon?: number | null; 41 | ignoreAnnotations?: boolean; 42 | ignoreTimestampPrecision?: boolean; 43 | onlyCompareIon?: boolean; 44 | } = { 45 | epsilon: null, 46 | ignoreAnnotations: false, 47 | ignoreTimestampPrecision: false, 48 | onlyCompareIon: true, 49 | } 50 | ): boolean { 51 | let isSupportedType: boolean = false; 52 | let valueToCompare: any = null; 53 | if (options.onlyCompareIon) { 54 | // `compareOnlyIon` requires that the provided value be an ion.dom.Lob instance. 55 | if ( 56 | other.getType() === IonTypes.CLOB || 57 | other.getType() === IonTypes.BLOB 58 | ) { 59 | isSupportedType = true; 60 | valueToCompare = other.uInt8ArrayValue(); 61 | } 62 | } else { 63 | // We will consider other Lob-ish types 64 | if (other instanceof Uint8Array) { 65 | isSupportedType = true; 66 | valueToCompare = other.valueOf(); 67 | } 68 | } 69 | 70 | if (!isSupportedType) { 71 | return false; 72 | } 73 | 74 | let current = this.uInt8ArrayValue(); 75 | let expected = valueToCompare; 76 | if (current.length !== expected.length) { 77 | return false; 78 | } 79 | for (let i = 0; i < current.length; i++) { 80 | if (current[i] !== expected[i]) { 81 | return false; 82 | } 83 | } 84 | return true; 85 | } 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /src/dom/SExpression.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes } from "../Ion"; 2 | import { Sequence } from "./Sequence"; 3 | import { Value } from "./Value"; 4 | 5 | /** 6 | * Represents an s-expression[1] value in an Ion stream. 7 | * 8 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#sexp 9 | */ 10 | export class SExpression extends Sequence(IonTypes.SEXP) { 11 | /** 12 | * Constructor. 13 | * @param children Values that will be contained in the new s-expression. 14 | * @param annotations An optional array of strings to associate with the items in `children`. 15 | */ 16 | constructor(children: Value[], annotations: string[] = []) { 17 | super(children, annotations); 18 | } 19 | 20 | toString(): string { 21 | return "(" + this.join(" ") + ")"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/dom/Sequence.ts: -------------------------------------------------------------------------------- 1 | import { IonType, IonTypes, Writer } from "../Ion"; 2 | import { FromJsConstructor } from "./FromJsConstructor"; 3 | import { PathElement, Value } from "./Value"; 4 | 5 | /** 6 | * This mixin constructs a new class that: 7 | * - Extends `DomValue` 8 | * - Extends `Array` 9 | * - Has the specified `IonType`. 10 | * 11 | * In practice, serves as a common base class for `List` and `SExpression`. 12 | * 13 | * @param ionType The IonType to associate with the new DomValue subclass. 14 | * @constructor 15 | * @private 16 | */ 17 | export function Sequence(ionType: IonType) { 18 | return class 19 | extends Value(Array, ionType, FromJsConstructor.NONE) 20 | implements Value, Array { 21 | protected constructor(children: Value[], annotations: string[] = []) { 22 | super(); 23 | for (const child of children) { 24 | this.push(child); 25 | } 26 | this._setAnnotations(annotations); 27 | 28 | return new Proxy(this, { 29 | set: function (target, index, value): boolean { 30 | if (!(value instanceof Value)) { 31 | value = Value.from(value); 32 | } 33 | target[index] = value; 34 | return true; // Indicates that the assignment succeeded 35 | }, 36 | }); 37 | } 38 | 39 | get(...pathElements: PathElement[]): Value | null { 40 | if (pathElements.length === 0) { 41 | throw new Error("Value#get requires at least one parameter."); 42 | } 43 | const [pathHead, ...pathTail] = pathElements; 44 | if (typeof pathHead !== "number") { 45 | throw new Error( 46 | `Cannot index into a ${ 47 | this.getType().name 48 | } with a ${typeof pathHead}.` 49 | ); 50 | } 51 | 52 | const children = this as Value[]; 53 | const maybeChild: Value | undefined = children[pathHead]; 54 | const child: Value | null = maybeChild === undefined ? null : maybeChild; 55 | if (pathTail.length === 0 || child === null) { 56 | return child; 57 | } 58 | return child.get(...pathTail); 59 | } 60 | 61 | elements(): Value[] { 62 | return Object.values(this); 63 | } 64 | 65 | toString(): string { 66 | return "[" + this.join(", ") + "]"; 67 | } 68 | 69 | static _fromJsValue(jsValue: any, annotations: string[]): Value { 70 | if (!(jsValue instanceof Array)) { 71 | throw new Error( 72 | `Cannot create a ${this.name} from: ${jsValue.toString()}` 73 | ); 74 | } 75 | const children = jsValue.map((child) => Value.from(child)); 76 | return new this(children, annotations); 77 | } 78 | 79 | writeTo(writer: Writer) { 80 | writer.setAnnotations(this.getAnnotations()); 81 | writer.stepIn(ionType); 82 | for (const child of this) { 83 | child.writeTo(writer); 84 | } 85 | writer.stepOut(); 86 | } 87 | 88 | _valueEquals( 89 | other: any, 90 | options: { 91 | epsilon?: number | null; 92 | ignoreAnnotations?: boolean; 93 | ignoreTimestampPrecision?: boolean; 94 | onlyCompareIon?: boolean; 95 | } = { 96 | epsilon: null, 97 | ignoreAnnotations: false, 98 | ignoreTimestampPrecision: false, 99 | onlyCompareIon: true, 100 | } 101 | ): boolean { 102 | let isSupportedType: boolean = false; 103 | let valueToCompare: any = null; 104 | if (options.onlyCompareIon) { 105 | // `compareOnlyIon` requires that the provided value be an ion.dom.Sequence instance. 106 | if ( 107 | other.getType() === IonTypes.LIST || 108 | other.getType() === IonTypes.SEXP 109 | ) { 110 | isSupportedType = true; 111 | valueToCompare = other.elements(); 112 | } 113 | } else { 114 | // We will consider other Sequence-ish types 115 | if (other instanceof Array) { 116 | isSupportedType = true; 117 | valueToCompare = other; 118 | } 119 | } 120 | 121 | if (!isSupportedType) { 122 | return false; 123 | } 124 | 125 | let actualSequence = this.elements(); 126 | let expectedSequence = valueToCompare; 127 | if (actualSequence.length !== expectedSequence.length) { 128 | return false; 129 | } 130 | for (let i = 0; i < actualSequence.length; i++) { 131 | if (options.onlyCompareIon) { 132 | if (!actualSequence[i].ionEquals(expectedSequence[i], options)) { 133 | return false; 134 | } 135 | } else { 136 | if (!actualSequence[i].equals(expectedSequence[i])) { 137 | return false; 138 | } 139 | } 140 | } 141 | return true; 142 | } 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /src/dom/String.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | Primitives, 6 | } from "./FromJsConstructor"; 7 | import { _NativeJsString } from "./JsValueConversion"; 8 | import { Value } from "./Value"; 9 | 10 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 11 | .withPrimitives(Primitives.String) 12 | .withClassesToUnbox(_NativeJsString) 13 | .build(); 14 | 15 | /** 16 | * Represents a string[1] value in an Ion stream. 17 | * 18 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#string 19 | */ 20 | export class String extends Value( 21 | _NativeJsString, 22 | IonTypes.STRING, 23 | _fromJsConstructor 24 | ) { 25 | /** 26 | * Constructor. 27 | * @param text The text value to represent as a string. 28 | * @param annotations An optional array of strings to associate with the provided text. 29 | */ 30 | constructor(text: string, annotations: string[] = []) { 31 | super(text); 32 | this._setAnnotations(annotations); 33 | } 34 | 35 | stringValue(): string { 36 | return this.toString(); 37 | } 38 | 39 | writeTo(writer: Writer): void { 40 | writer.setAnnotations(this.getAnnotations()); 41 | writer.writeString(this.stringValue()); 42 | } 43 | 44 | _valueEquals( 45 | other: any, 46 | options: { 47 | epsilon?: number | null; 48 | ignoreAnnotations?: boolean; 49 | ignoreTimestampPrecision?: boolean; 50 | onlyCompareIon?: boolean; 51 | } = { 52 | epsilon: null, 53 | ignoreAnnotations: false, 54 | ignoreTimestampPrecision: false, 55 | onlyCompareIon: true, 56 | } 57 | ): boolean { 58 | let isSupportedType: boolean = false; 59 | let valueToCompare: any = null; 60 | 61 | // if the provided value is an ion.dom.String instance. 62 | if (other instanceof String) { 63 | isSupportedType = true; 64 | valueToCompare = other.stringValue(); 65 | } else if (!options.onlyCompareIon) { 66 | // We will consider other String-ish types 67 | if (typeof other === "string" || other instanceof _NativeJsString) { 68 | isSupportedType = true; 69 | valueToCompare = other.valueOf(); 70 | } 71 | } 72 | 73 | if (!isSupportedType) { 74 | return false; 75 | } 76 | 77 | return this.compareValue(valueToCompare) === 0; 78 | } 79 | 80 | compareValue(expectedValue: string): number { 81 | return this.stringValue().localeCompare(expectedValue); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/dom/Symbol.ts: -------------------------------------------------------------------------------- 1 | import { IonTypes, Writer } from "../Ion"; 2 | import { 3 | FromJsConstructor, 4 | FromJsConstructorBuilder, 5 | Primitives, 6 | } from "./FromJsConstructor"; 7 | import { Value } from "./Value"; 8 | 9 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 10 | .withPrimitives(Primitives.String) 11 | .withClassesToUnbox(String) 12 | .build(); 13 | 14 | // TODO: 15 | // This extends 'String' because ion-js does not yet have a SymbolToken construct. 16 | // It is not possible to access the raw Symbol ID via the Reader API, so it cannot be accessed from this class. 17 | 18 | /** 19 | * Represents a symbol[1] value in an Ion stream. 20 | * 21 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#symbol 22 | */ 23 | export class Symbol extends Value(String, IonTypes.SYMBOL, _fromJsConstructor) { 24 | /** 25 | * Constructor. 26 | * @param symbolText The text to represent as a symbol. 27 | * @param annotations An optional array of strings to associate with this symbol. 28 | */ 29 | constructor(symbolText: string, annotations: string[] = []) { 30 | super(symbolText); 31 | this._setAnnotations(annotations); 32 | } 33 | 34 | stringValue(): string { 35 | return this.toString(); 36 | } 37 | 38 | writeTo(writer: Writer): void { 39 | writer.setAnnotations(this.getAnnotations()); 40 | writer.writeSymbol(this.stringValue()); 41 | } 42 | 43 | _valueEquals( 44 | other: any, 45 | options: { 46 | epsilon?: number | null; 47 | ignoreAnnotations?: boolean; 48 | ignoreTimestampPrecision?: boolean; 49 | onlyCompareIon?: boolean; 50 | } = { 51 | epsilon: null, 52 | ignoreAnnotations: false, 53 | ignoreTimestampPrecision: false, 54 | onlyCompareIon: true, 55 | } 56 | ): boolean { 57 | let isSupportedType: boolean = false; 58 | let valueToCompare: any = null; 59 | 60 | //if the provided value is an ion.dom.Symbol instance. 61 | if (other instanceof Symbol) { 62 | isSupportedType = true; 63 | valueToCompare = other.stringValue(); 64 | } else if (!options.onlyCompareIon) { 65 | // We will consider other Symbol-ish types 66 | if (typeof other === "string" || other instanceof String) { 67 | isSupportedType = true; 68 | valueToCompare = other.valueOf(); 69 | } 70 | } 71 | 72 | if (!isSupportedType) { 73 | return false; 74 | } 75 | 76 | return this.compareValue(valueToCompare) === 0; 77 | } 78 | 79 | compareValue(expectedValue: string): number { 80 | return this.stringValue().localeCompare(expectedValue); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/dom/Timestamp.ts: -------------------------------------------------------------------------------- 1 | import * as ion from "../Ion"; 2 | import { Decimal, IonTypes } from "../Ion"; 3 | import { Writer } from "../Ion"; 4 | import { 5 | FromJsConstructor, 6 | FromJsConstructorBuilder, 7 | } from "./FromJsConstructor"; 8 | import { Value } from "./Value"; 9 | 10 | const _fromJsConstructor: FromJsConstructor = new FromJsConstructorBuilder() 11 | .withClasses(Date, ion.Timestamp) 12 | .build(); 13 | 14 | /** 15 | * Represents a timestamp[1] value in an Ion stream. 16 | * 17 | * [1] https://amazon-ion.github.io/ion-docs/docs/spec.html#timestamp 18 | */ 19 | export class Timestamp extends Value( 20 | Date, 21 | IonTypes.TIMESTAMP, 22 | _fromJsConstructor 23 | ) { 24 | protected _timestamp: ion.Timestamp; 25 | protected _date: Date; 26 | 27 | /** 28 | * Constructor. 29 | * @param dateOrTimestamp A `Date` or `Timestamp` to represent as a timestamp. 30 | * @param annotations An optional array of strings to associate with this timestamp. 31 | */ 32 | constructor( 33 | dateOrTimestamp: Date | ion.Timestamp, 34 | annotations: string[] = [] 35 | ) { 36 | let date: Date; 37 | let timestamp: ion.Timestamp; 38 | if (dateOrTimestamp instanceof Date) { 39 | date = dateOrTimestamp; 40 | timestamp = Timestamp._timestampFromDate(date); 41 | } else { 42 | timestamp = dateOrTimestamp; 43 | date = timestamp.getDate(); 44 | } 45 | super(date); 46 | this._date = date; 47 | this._timestamp = timestamp; 48 | this._setAnnotations(annotations); 49 | } 50 | 51 | private static _timestampFromDate(date: Date): ion.Timestamp { 52 | const milliseconds = 53 | date.getUTCSeconds() * 1000 + date.getUTCMilliseconds(); 54 | const fractionalSeconds = new Decimal(milliseconds, -3); 55 | 56 | // `Date` objects do not store a timezone offset. If the offset is requested 57 | // via `.getTimezoneOffset()`, the configured offset of the host computer 58 | // is returned instead of the timezone that was specified when the Date was 59 | // constructed. Since the timezone of the host may or may not be meaningful 60 | // to the user, we store the time in UTC instead. Users wishing to store a 61 | // specific timezone offset on their dom.Timestamp can pass in an ion.Timestamp 62 | // instead of a Date. 63 | return new ion.Timestamp( 64 | 0, 65 | date.getUTCFullYear(), 66 | date.getUTCMonth() + 1, // Timestamp expects a range of 1-12 67 | date.getUTCDate(), 68 | date.getUTCHours(), 69 | date.getUTCMinutes(), 70 | fractionalSeconds 71 | ); 72 | } 73 | 74 | timestampValue(): ion.Timestamp { 75 | return this._timestamp; 76 | } 77 | 78 | dateValue(): Date { 79 | return this._date; 80 | } 81 | 82 | writeTo(writer: Writer): void { 83 | writer.setAnnotations(this.getAnnotations()); 84 | writer.writeTimestamp(this.timestampValue()); 85 | } 86 | 87 | _valueEquals( 88 | other: any, 89 | options: { 90 | epsilon?: number | null; 91 | ignoreAnnotations?: boolean; 92 | ignoreTimestampPrecision?: boolean; 93 | onlyCompareIon?: boolean; 94 | } = { 95 | epsilon: null, 96 | ignoreAnnotations: false, 97 | ignoreTimestampPrecision: false, 98 | onlyCompareIon: true, 99 | } 100 | ): boolean { 101 | let isSupportedType: boolean = false; 102 | let valueToCompare: any = null; 103 | // if the provided value is an ion.dom.Symbol instance. 104 | if (other instanceof Timestamp) { 105 | isSupportedType = true; 106 | valueToCompare = other.timestampValue(); 107 | } else if (!options.onlyCompareIon) { 108 | // We will consider other Timestamp-ish types 109 | if (other instanceof ion.Timestamp) { 110 | // expectedValue is a non-DOM Timestamp 111 | isSupportedType = true; 112 | valueToCompare = other; 113 | } else if (other instanceof Date) { 114 | if (this.dateValue().getTime() === other.getTime()) { 115 | return true; 116 | } else { 117 | return false; 118 | } 119 | } 120 | } 121 | 122 | if (!isSupportedType) { 123 | return false; 124 | } 125 | 126 | if (options.ignoreTimestampPrecision) { 127 | return this.timestampValue().compareTo(valueToCompare) === 0; 128 | } 129 | return this.timestampValue().equals(valueToCompare); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/events/EventStreamError.ts: -------------------------------------------------------------------------------- 1 | import { IonEvent } from "../Ion"; 2 | 3 | export class EventStreamError extends Error { 4 | type: string; 5 | index: number; 6 | eventstream: IonEvent[]; 7 | 8 | constructor( 9 | type: string, 10 | message: string, 11 | index: number, 12 | eventstream: IonEvent[] 13 | ) { 14 | super(); 15 | this.type = type; 16 | this.index = index; 17 | this.message = message; 18 | this.eventstream = eventstream; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | /** 17 | * Returns -1 if x is negative (including -0); otherwise returns 1. 18 | */ 19 | export function _sign(x: number): number { 20 | return x < 0 || (x === 0 && 1 / x === -Infinity) ? -1 : 1; 21 | } 22 | 23 | /** 24 | * Returns false if v is undefined or null; otherwise true. 25 | * @private 26 | */ 27 | export function _hasValue(v: any): boolean { 28 | return v !== undefined && v !== null; 29 | } 30 | 31 | /** 32 | * Throws if value is not defined. 33 | */ 34 | export function _assertDefined(value: any): void { 35 | if (value === undefined) { 36 | throw new Error("Expected value to be defined"); 37 | } 38 | } 39 | 40 | /** 41 | * Indicates whether the provided bigint value is within the range[ Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]. 42 | * 43 | * @param value The integer to test. 44 | * @return True if toNumber(value) can be called without losing any precision. 45 | */ 46 | export function isSafeInteger(value: bigint): boolean { 47 | return value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER; 48 | } 49 | -------------------------------------------------------------------------------- /test-driver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-driver", 3 | "version": "1.0.0", 4 | "description": "A JavaScript implementation of ion-test-driver", 5 | "main": "dist/Cli.js", 6 | "scripts": { 7 | "build": "grunt build:test-driver" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/amazon-ion/ion-js.git" 12 | }, 13 | "author": "The Ion Team (https://amazon-ion.github.io/ion-docs/index.html)", 14 | "license": "Apache-2.0", 15 | "bugs": { 16 | "url": "https://github.com/amazon-ion/ion-js/issues" 17 | }, 18 | "homepage": "https://github.com/amazon-ion/ion-js#readme", 19 | "dependencies": { 20 | "yargs": "^15.4.1", 21 | "ion-js": "../" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test-driver/src/Cli.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import yargs from "yargs"; 17 | 18 | /** 19 | * This will cause `yargs` to look in other .ts/.js files in the same directory for command modules. 20 | * 21 | * For more information, see: 22 | * Command modules: https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module 23 | * commandDir: https://github.com/yargs/yargs/blob/master/docs/advanced.md#commanddirdirectory-opts 24 | */ 25 | yargs 26 | .commandDir(__dirname,{ 27 | extensions: ['ts','js'], 28 | }) 29 | .options({ 30 | 'output': { 31 | alias: 'o', 32 | description: 'Output location. [default: stdout]', 33 | }, 34 | 'output-format': { 35 | alias: 'f', 36 | choices: ['pretty', 'text', 'binary', 'events', 'none'] as const, 37 | default: 'pretty', 38 | description: "Output format, from the set (text | pretty | binary |\n" 39 | + "events | none). 'events' is only available with the\n" 40 | + "'process' command, and outputs a serialized EventStream\n" 41 | + "representing the input Ion stream(s)." 42 | }, 43 | 'error-report': { 44 | alias: 'e', 45 | description: 'ErrorReport location. [default: stderr]', 46 | } 47 | }).argv; 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test-driver/src/CliCommonArgs.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {OutputFormat} from "./OutputFormat"; 17 | import fs from "fs"; 18 | import yargs from "yargs"; 19 | 20 | /** common CLI arguments structure 21 | * for more information: https://github.com/amazon-ion/ion-test-driver#standardized-cli 22 | */ 23 | export class IonCliCommonArgs { 24 | inputFiles: Array; 25 | outputFormatName: OutputFormat; 26 | 27 | // outputFile,errorReportFile: 'any' datatype to use stdout as well as file write stream to write ion data 28 | // for more information : https://github.com/amazon-ion/ion-js/issues/627 29 | outputFile: any; 30 | errorReportFile: any; 31 | 32 | constructor(argv: yargs.Arguments) { 33 | this.outputFile = argv["output"] ? fs.createWriteStream(argv["output"] as string, {flags: 'w'}) : process.stdout; 34 | // create error output stream (DEFAULT: stderr) 35 | this.errorReportFile = argv["error-report"] ? fs.createWriteStream(argv["error-report"] as string, {flags: 'w'}) : process.stderr; 36 | this.outputFormatName = argv["output-format"] as OutputFormat; 37 | this.inputFiles = argv["input-file"] as Array; 38 | } 39 | 40 | getOutputFile(): any { 41 | return this.outputFile; 42 | } 43 | 44 | getErrorReportFile(): any { 45 | return this.errorReportFile; 46 | } 47 | 48 | getInputFiles(): Array { 49 | return this.inputFiles; 50 | } 51 | 52 | getOutputFormatName(): OutputFormat { 53 | return this.outputFormatName; 54 | } 55 | } -------------------------------------------------------------------------------- /test-driver/src/CliCompareArgs.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {ComparisonType} from "./Compare"; 17 | import {IonCliCommonArgs} from "./CliCommonArgs"; 18 | 19 | /** CLI arguments for compare structure */ 20 | export class IonCompareArgs extends IonCliCommonArgs{ 21 | comparisonType : ComparisonType; 22 | 23 | constructor(props) { 24 | super(props); 25 | this.comparisonType = props["comparison-type"] as ComparisonType; 26 | } 27 | 28 | getComparisonType(): ComparisonType { 29 | return this.comparisonType; 30 | } 31 | } -------------------------------------------------------------------------------- /test-driver/src/CliError.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {IonTypes, makeTextWriter} from "ion-js"; 17 | 18 | /** Error Types symbol[READ | WRITE | STATE] */ 19 | export enum ErrorType { 20 | READ = "READ", 21 | WRITE = "WRITE", 22 | STATE = "STATE" 23 | } 24 | 25 | /** Error structure for error report 26 | * for more information: https://github.com/amazon-ion/ion-test-driver#errorreport 27 | */ 28 | export class IonCliError { 29 | errorType: ErrorType; 30 | eventIndex: number; 31 | location: string; 32 | message: string; 33 | errorReportFile: any; 34 | 35 | constructor(errorType: ErrorType, location: string, message: string, errorReportFile: any, eventIndex: number = 0) { 36 | this.errorType = errorType; 37 | this.location = location; 38 | this.message = message; 39 | this.errorReportFile = errorReportFile; 40 | this.eventIndex = eventIndex; 41 | } 42 | 43 | writeErrorReport() { 44 | let writer = makeTextWriter(); 45 | writer.stepIn(IonTypes.STRUCT); 46 | writer.writeFieldName('error_type'); 47 | writer.writeSymbol(this.errorType); 48 | writer.writeFieldName('message'); 49 | writer.writeString(this.message); 50 | writer.writeFieldName('location'); 51 | writer.writeString(this.location); 52 | writer.writeFieldName('event_index'); 53 | writer.writeInt(this.eventIndex); 54 | writer.stepOut(); 55 | this.errorReportFile.write(writer.getBytes()); 56 | this.errorReportFile.write("\n"); 57 | } 58 | } -------------------------------------------------------------------------------- /test-driver/src/ComparisonContext.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {IonEventStream} from "ion-js"; 17 | import {Reader} from "ion-js"; 18 | import {IonTypes, makeReader, Writer} from "ion-js"; 19 | import fs from "fs"; 20 | import {IonCompareArgs} from "./CliCompareArgs"; 21 | import {ErrorType, IonCliError} from "./CliError"; 22 | import {IonEvent, IonEventFactory, IonEventType} from "ion-js"; 23 | 24 | /** 25 | * ComparisonContext to create a structure for lhs and rhs streams 26 | * for more information: https://github.com/amazon-ion/ion-test-driver#comparisonreport 27 | */ 28 | export class ComparisonContext { 29 | location: string; 30 | eventStream: IonEventStream; 31 | 32 | constructor(path: string, args: IonCompareArgs){ 33 | this.location = path; 34 | let ionReader = this.createReadersForComparison(args); 35 | this.setEventStream(ionReader, path, args); 36 | } 37 | 38 | createReadersForComparison(args: IonCompareArgs): Reader { 39 | let ionReader; 40 | try { 41 | ionReader = makeReader(this.getInput(this.getLocation())); 42 | } catch (Error) { 43 | new IonCliError(ErrorType.READ, this.getLocation(), Error.message, args.getErrorReportFile()).writeErrorReport(); 44 | } 45 | return ionReader; 46 | } 47 | 48 | getInput(path: string): string | Buffer { 49 | let options = path.endsWith(".10n") ? null : "utf8"; 50 | return fs.readFileSync(path, options); 51 | } 52 | 53 | setEventStream(ionReader: Reader, path: string, args: IonCompareArgs) { 54 | let events: IonEvent[] = []; 55 | try{ 56 | let eventStream = new IonEventStream(ionReader); 57 | 58 | if(!eventStream.isEventStream) { 59 | // processes input stream(text or binary) into event stream 60 | events = this.collectInputStreamEvents(events, eventStream); 61 | eventStream.events = events; 62 | } 63 | this.eventStream = eventStream; 64 | } catch (EventStreamError) { 65 | if(EventStreamError.eventstream) { 66 | events.push(...EventStreamError.eventstream); 67 | } 68 | if(EventStreamError.type === "READ") { 69 | new IonCliError(ErrorType.READ, path, EventStreamError.message, args.getErrorReportFile(), events.length).writeErrorReport(); 70 | } 71 | else if(EventStreamError.type === "WRITE") { 72 | new IonCliError(ErrorType.WRITE, path, EventStreamError.message, args.getErrorReportFile()).writeErrorReport(); 73 | } 74 | else { 75 | new IonCliError(ErrorType.STATE, path, EventStreamError.message, args.getErrorReportFile()).writeErrorReport(); 76 | } 77 | } 78 | } 79 | 80 | collectInputStreamEvents(events: IonEvent[], eventStream: IonEventStream): IonEvent[] { 81 | for(let i = 0; i < eventStream.events.length; i++) { 82 | let event = eventStream.events[i]; 83 | events.push(event); 84 | if (eventStream.isEmbedded(event)) { 85 | let tempEvents: IonEvent[] = []; 86 | for (let j = 0; j < event.ionValue.length - 1; j++) { 87 | tempEvents.push(...new IonEventStream(makeReader(event.ionValue[j].ionValue)).getEvents()); 88 | } 89 | i = i + (event.ionValue.length - 1); 90 | let eventFactory = new IonEventFactory(); 91 | tempEvents.push(eventFactory.makeEvent(IonEventType.CONTAINER_END, event.ionType!, null, event.depth, [], false, null)); 92 | event.ionValue = tempEvents; 93 | events.push(...tempEvents.slice(0,-1)); 94 | } 95 | } 96 | return events; 97 | } 98 | 99 | getEventStream() { 100 | return this.eventStream; 101 | } 102 | 103 | getLocation(): string { 104 | return this.location; 105 | } 106 | 107 | writeComparisonContext(ionOutputWriter: Writer, field_name: string, event_index: number) { 108 | ionOutputWriter.writeFieldName(field_name); 109 | ionOutputWriter.stepIn(IonTypes.STRUCT); 110 | ionOutputWriter.writeFieldName("location"); 111 | ionOutputWriter.writeString(this.location); 112 | ionOutputWriter.writeFieldName("event"); 113 | if(this.getEventStream().getEvents().length > 0) { 114 | this.getEventStream().getEvents()[event_index].write(ionOutputWriter); 115 | } else { 116 | ionOutputWriter.writeNull(IonTypes.NULL); 117 | } 118 | ionOutputWriter.writeFieldName("event_index"); 119 | ionOutputWriter.writeInt(event_index); 120 | ionOutputWriter.stepOut(); 121 | } 122 | } -------------------------------------------------------------------------------- /test-driver/src/ComparisonReport.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {ComparisonType} from "./Compare"; 17 | import {IonTypes, makeTextWriter} from "ion-js"; 18 | import {ComparisonContext} from "./ComparisonContext"; 19 | import {ComparisonResultType} from "ion-js"; 20 | 21 | /** comparison report structure for compare 22 | * for more information: https://github.com/amazon-ion/ion-test-driver#comparisonreport 23 | */ 24 | export class IonComparisonReport { 25 | lhs: ComparisonContext; 26 | rhs: ComparisonContext; 27 | comparisonReportFile: any; 28 | comparisonType: ComparisonType; 29 | 30 | constructor(lhs: ComparisonContext, rhs: ComparisonContext, comparisonReportFile: any, comparisonType: ComparisonType) { 31 | this.lhs = lhs; 32 | this.rhs = rhs; 33 | this.comparisonReportFile = comparisonReportFile; 34 | this.comparisonType = comparisonType; 35 | } 36 | 37 | writeComparisonReport(result: ComparisonResultType, message: string, event_index_lhs: number, event_index_rhs: number) { 38 | let writer = makeTextWriter(); 39 | writer.stepIn(IonTypes.STRUCT); 40 | writer.writeFieldName('result'); 41 | writer.writeSymbol(result); 42 | this.lhs.writeComparisonContext(writer, "lhs", event_index_lhs); 43 | this.rhs.writeComparisonContext(writer, "rhs", event_index_rhs); 44 | writer.writeFieldName('message'); 45 | writer.writeString(message); 46 | writer.stepOut(); 47 | this.comparisonReportFile.write(writer.getBytes()); 48 | this.comparisonReportFile.write("\n"); 49 | } 50 | } -------------------------------------------------------------------------------- /test-driver/src/OutputFormat.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {Writer} from 'ion-js'; 17 | import { 18 | makePrettyWriter, 19 | makeBinaryWriter, 20 | makeTextWriter 21 | } from 'ion-js'; 22 | 23 | export enum OutputFormat { 24 | PRETTY = "pretty", 25 | TEXT = "text", 26 | BINARY = "binary", 27 | EVENTS = "events", 28 | NONE = "none" 29 | } 30 | 31 | /** gets the writer corresponding to the output format **/ 32 | export namespace OutputFormat { 33 | export function createIonWriter(name: OutputFormat) : Writer | null { 34 | switch (name) { 35 | case OutputFormat.PRETTY: 36 | return makePrettyWriter(); 37 | case OutputFormat.TEXT: 38 | return makeTextWriter(); 39 | case OutputFormat.BINARY: 40 | return makeBinaryWriter(); 41 | case OutputFormat.EVENTS: 42 | return makePrettyWriter(); 43 | case OutputFormat.NONE: 44 | return null; 45 | default: 46 | throw new Error("Output Format " + name + " unexpected."); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /test-driver/tsconfig.json: -------------------------------------------------------------------------------- 1 | // Test-driver Typescript Compiler Configuration 2 | // Targets CommonJS Module system and ES6 feature set. 3 | { 4 | "include": [ 5 | "src/**" 6 | ], 7 | "exclude": [ 8 | "**/*.d.ts" 9 | ], 10 | "compilerOptions": { 11 | "target": "es2020", 12 | "module": "commonjs", 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | "lib": ["ESNext"], 16 | "declaration": false, 17 | "inlineSources": true, 18 | "sourceMap": true, 19 | "strict": false, 20 | "strictNullChecks": true, 21 | "rootDirs": [ 22 | "src" 23 | ], 24 | "outDir": "dist", 25 | "experimentalDecorators": true 26 | } 27 | } -------------------------------------------------------------------------------- /test/IntSize.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { assert } from "chai"; 17 | import IntSize from "../src/IntSize"; 18 | import * as ion from "../src/Ion"; 19 | import { IonTypes } from "../src/Ion"; 20 | 21 | function intToBinaryIonBytes(value: number | bigint): Uint8Array { 22 | let writer = ion.makeBinaryWriter(); 23 | writer.writeInt(value); 24 | writer.close(); 25 | return writer.getBytes(); 26 | } 27 | 28 | describe("IntSize", () => { 29 | describe("text", () => { 30 | it("MAX_SAFE_INTEGER: IntSize.Number", () => { 31 | let reader = ion.makeReader(Number.MAX_SAFE_INTEGER.toString()); 32 | assert.equal(IonTypes.INT, reader.next()); 33 | assert.equal(reader.intSize(), IntSize.Number); 34 | }); 35 | 36 | it("MIN_SAFE_INTEGER: IntSize.Number", () => { 37 | let reader = ion.makeReader(Number.MIN_SAFE_INTEGER.toString()); 38 | assert.equal(IonTypes.INT, reader.next()); 39 | assert.equal(reader.intSize(), IntSize.Number); 40 | }); 41 | 42 | it("> MAX_SAFE_INTEGER: IntSize.BigInt", () => { 43 | let value = BigInt(Number.MAX_SAFE_INTEGER) + 1n; 44 | let reader = ion.makeReader(value.toString()); 45 | assert.equal(IonTypes.INT, reader.next()); 46 | assert.equal(reader.intSize(), IntSize.BigInt); 47 | }); 48 | 49 | it("< MIN_SAFE_INTEGER: IntSize.BigInt", () => { 50 | let value = BigInt(Number.MIN_SAFE_INTEGER) - 1n; 51 | let reader = ion.makeReader(value.toString()); 52 | assert.equal(IonTypes.INT, reader.next()); 53 | assert.equal(reader.intSize(), IntSize.BigInt); 54 | }); 55 | }); 56 | 57 | describe("binary", () => { 58 | it("MAX_SAFE_INTEGER: IntSize.Number", () => { 59 | let reader = ion.makeReader(intToBinaryIonBytes(Number.MAX_SAFE_INTEGER)); 60 | assert.equal(IonTypes.INT, reader.next()); 61 | assert.equal(reader.intSize(), IntSize.Number); 62 | }); 63 | 64 | it("MIN_SAFE_INTEGER: IntSize.Number", () => { 65 | let reader = ion.makeReader(intToBinaryIonBytes(Number.MIN_SAFE_INTEGER)); 66 | assert.equal(IonTypes.INT, reader.next()); 67 | assert.equal(reader.intSize(), IntSize.Number); 68 | }); 69 | 70 | it("> MAX_SAFE_INTEGER: IntSize.BigInt", () => { 71 | let value = BigInt(Number.MAX_SAFE_INTEGER) + 1n; 72 | let reader = ion.makeReader(intToBinaryIonBytes(value)); 73 | assert.equal(IonTypes.INT, reader.next()); 74 | assert.equal(reader.intSize(), IntSize.BigInt); 75 | }); 76 | 77 | it("< MIN_SAFE_INTEGER: IntSize.BigInt", () => { 78 | let value = BigInt(Number.MIN_SAFE_INTEGER) - 1n; 79 | let reader = ion.makeReader(intToBinaryIonBytes(value)); 80 | assert.equal(IonTypes.INT, reader.next()); 81 | assert.equal(reader.intSize(), IntSize.BigInt); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/IonAnnotations.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { assert } from "chai"; 17 | import * as ion from "../src/Ion"; 18 | 19 | function readerToBytes(reader) { 20 | let writer = ion.makeTextWriter(); 21 | writer.writeValues(reader); 22 | writer.close(); 23 | return writer.getBytes(); 24 | } 25 | 26 | function readerToString(reader) { 27 | return ion.decodeUtf8(readerToBytes(reader)); 28 | } 29 | 30 | describe("Annotations", () => { 31 | it("Read annotations", () => { 32 | let data = "a::b::123"; 33 | let reader = ion.makeReader(data); 34 | reader.next(); 35 | assert.deepEqual(reader.annotations(), ["a", "b"]); 36 | assert.equal(reader.value()?.toString(), "123"); 37 | assert.equal(readerToString(reader), "a::b::123"); 38 | }); 39 | 40 | it("Resolves ID annotations", () => { 41 | let data = "$3::123"; 42 | let reader = ion.makeReader(data); 43 | reader.next(); 44 | assert.deepEqual(reader.annotations(), ["$ion_symbol_table"]); 45 | }); 46 | 47 | it("Does not resolve non-ID annotations ($)", () => { 48 | let data = "'$'::123"; 49 | let reader = ion.makeReader(data); 50 | reader.next(); 51 | assert.deepEqual(reader.annotations(), ["$"]); 52 | }); 53 | 54 | it("Does not resolve non-ID annotations ($3)", () => { 55 | let data = "'$3'::123"; 56 | let reader = ion.makeReader(data); 57 | reader.next(); 58 | assert.deepEqual(reader.annotations(), ["$3"]); 59 | }); 60 | 61 | it("Does not resolve non-ID annotations ($1.00)", () => { 62 | let data = "'$1.00'::123"; 63 | let reader = ion.makeReader(data); 64 | reader.next(); 65 | assert.deepEqual(reader.annotations(), ["$1.00"]); 66 | }); 67 | 68 | it("Create annotations", () => { 69 | let data = "123"; 70 | let reader = ion.makeReader(data); 71 | reader.next(); 72 | let writer = ion.makeTextWriter(); 73 | writer.setAnnotations(["a"]); 74 | writer.writeInt(reader.numberValue()); 75 | reader.next(); 76 | writer.writeValues(reader); 77 | assert.equal(String.fromCharCode.apply(null, writer.getBytes()), "a::123"); 78 | }); 79 | 80 | it("Add annotation", () => { 81 | let data = "a::b::123"; 82 | let reader = ion.makeReader(data); 83 | reader.next(); 84 | let writer = ion.makeTextWriter(); 85 | writer.setAnnotations(reader.annotations()); 86 | writer.addAnnotation("c"); 87 | writer.writeInt(reader.numberValue()); 88 | assert.equal( 89 | String.fromCharCode.apply(null, writer.getBytes()), 90 | "a::b::c::123" 91 | ); 92 | }); 93 | 94 | it("Wrap annotation", () => { 95 | let data = "{ x: 1 }"; 96 | let reader = ion.makeReader(data); 97 | let writer = ion.makeTextWriter(); 98 | writer.setAnnotations(["a", "b"]); 99 | writer.stepIn(ion.IonTypes.STRUCT); 100 | 101 | reader.next(); 102 | reader.stepIn(); 103 | reader.next(); 104 | 105 | writer.writeValues(reader); 106 | 107 | reader.stepOut(); 108 | 109 | writer.stepOut(); 110 | assert.equal( 111 | String.fromCharCode.apply(null, writer.getBytes()), 112 | "a::b::{x:1}" 113 | ); 114 | }); 115 | 116 | it("Sid0 text annotation throws", () => { 117 | let test = () => { 118 | let input = "$0::taco"; 119 | let reader = ion.makeReader(input); 120 | reader.next(); 121 | }; 122 | assert.throws(test); 123 | }); 124 | 125 | it("Sid0 binary annotation throws", () => { 126 | let test = () => { 127 | // the following bytes represent $0::1 128 | let input = new Uint8Array([ 129 | 0xe0, 130 | 0x01, 131 | 0x00, 132 | 0xea, 133 | 0xe4, 134 | 0x81, 135 | 0x80, 136 | 0x21, 137 | 0x01, 138 | ]); 139 | let reader = ion.makeReader(input); 140 | reader.next(); 141 | reader.annotations(); 142 | }; 143 | assert.throws(test); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /test/IonBinaryReader.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import * as ion from '../src/Ion'; 18 | import { BinaryWriter } from '../src/IonBinaryWriter'; 19 | import { Writeable } from '../src/IonWriteable'; 20 | import { LocalSymbolTable } from '../src/IonLocalSymbolTable'; 21 | import { getSystemSymbolTable } from '../src/IonSystemSymbolTable'; 22 | import { Import } from '../src/IonImport'; 23 | 24 | describe('Binary Reader', () => { 25 | it('timestamp', () => { 26 | let timestamp = ion.Timestamp.parse('2000-05-04T03:02:01.000789000Z'); 27 | let writer = ion.makeBinaryWriter(); 28 | writer.writeTimestamp(timestamp); 29 | writer.close(); 30 | let reader = ion.makeReader(writer.getBytes()); 31 | reader.next(); 32 | assert.deepEqual(reader.timestampValue(), timestamp); 33 | }); 34 | it('test position', () => { 35 | // In the comments below, the vertical bar '|' indicates the current position of the cursor at each step. 36 | const ionBinary: Uint8Array = ion.dumpBinary(null, 7, -17, "Hello", [1, 2, 3]); 37 | // [|0xE0, 0x1, 0x0, 0xEA, |0xF, |0x21, 0x7, |0x31, 0x11, |0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, |0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3 |] 38 | // Version null 7 -17 "hello" [1,2,3] 39 | const binaryReader = ion.makeReader(ionBinary); 40 | // init pos 0 41 | // [|0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3] 42 | assert.equal(binaryReader.position(), 0); 43 | binaryReader.next(); 44 | // at null 45 | // [0xE0, 0x1, 0x0, 0xEA, 0xF,| 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3] 46 | assert.equal(binaryReader.position(), 5); 47 | binaryReader.next(); 48 | // at 7 49 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7,| 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3] 50 | assert.equal(binaryReader.position(), 6); 51 | binaryReader.next(); 52 | // at 17 53 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11,| 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3] 54 | assert.equal(binaryReader.position(), 8); 55 | binaryReader.next(); 56 | // at hello 57 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48,| 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3] 58 | assert.equal(binaryReader.position(), 10); 59 | binaryReader.next(); 60 | // at [1,2,3] 61 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21,| 0x1, 0x21, 0x2, 0x21, 0x3] 62 | assert.equal(binaryReader.position(), 16); 63 | binaryReader.stepIn(); 64 | binaryReader.next(); 65 | // at 1 66 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1,| 0x21, 0x2, 0x21, 0x3] 67 | assert.equal(binaryReader.position(), 17); 68 | binaryReader.next(); 69 | // at 2 70 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2,| 0x21, 0x3] 71 | assert.equal(binaryReader.position(), 19); 72 | binaryReader.next(); 73 | // at 3 74 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3|] 75 | assert.equal(binaryReader.position(), 21); 76 | binaryReader.stepOut(); 77 | // out of stream 78 | // [0xE0, 0x1, 0x0, 0xEA, 0xF, 0x21, 0x7, 0x31, 0x11, 0x85, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xB6, 0x21, 0x1, 0x21, 0x2, 0x21, 0x3]| 79 | assert.equal(binaryReader.position(), 22); 80 | }); 81 | 82 | it('test catalog', () => { 83 | const symbols = ['id', 'name']; 84 | 85 | // Create a SharedSymbolTable with the desired strings 86 | const sharedSymbolTable = new ion.SharedSymbolTable('foo', 1, symbols); 87 | // Create a symbol table with shared table and system table. 88 | const localSymbolTable = new LocalSymbolTable([ 89 | sharedSymbolTable, 90 | getSystemSymbolTable(), 91 | ].reduceRight((parent, table) => new Import(parent, table), null as (null | Import))) 92 | // dump the symbols as binary. The buffer should not define the symbols in the table. 93 | const writer = new BinaryWriter(localSymbolTable, new Writeable()); 94 | symbols.forEach(symbol => writer.writeSymbol(symbol)); 95 | writer.close(); 96 | const buffer = writer.getBytes(); 97 | 98 | // Create a catalog with shared symbol table 99 | let catalog = new ion.Catalog(); 100 | catalog.add(sharedSymbolTable); 101 | 102 | // Reader with catalog should return correct symbol string values 103 | let reader = ion.makeReader(buffer, catalog); 104 | assert.deepEqual(ion.loadAll(reader), symbols.map(symbol => new ion.dom.Symbol(symbol))) 105 | 106 | // Reader without catalog should error (really this is testing that our buffer references symbols from the table. 107 | assert.throws(() => ion.loadAll(ion.makeReader(buffer)), "symbol is unresolvable") 108 | }) 109 | }); 110 | -------------------------------------------------------------------------------- /test/IonBinaryTimestamp.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import * as ion from '../src/Ion'; 18 | 19 | describe('Binary Timestamp', () => { 20 | it('Round trip', () => { 21 | // First part - writing timestamp into binary datagram 22 | let writer = ion.makeBinaryWriter(); 23 | let timestamp = new ion.Timestamp(0, 2017, 6, 7, 18, 29, ion.Decimal.parse('17.901')); 24 | writer.writeTimestamp(timestamp); 25 | writer.close(); 26 | 27 | /* Datagram content 28 | * { 29 | * test_timestamp:2017-06-07T18:29:17.901Z 30 | * } 31 | */ 32 | 33 | // Second part - reading timestamp from binary datagram created above 34 | let reader = ion.makeReader(writer.getBytes()); 35 | reader.next(); 36 | let timestampValue = reader.value(); 37 | assert.equal(timestamp.toString(), timestampValue!.toString()); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/IonCatalog.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {Catalog, SharedSymbolTable} from '../src/Ion'; 18 | 19 | describe('Catalog', () => { 20 | it('Finds specific version', () => { 21 | let version1 = new SharedSymbolTable('foo', 1, ['a']); 22 | let version2 = new SharedSymbolTable('foo', 2, ['b']); 23 | let version3 = new SharedSymbolTable('foo', 3, ['b']); 24 | let version4 = new SharedSymbolTable('foo', 4, ['b']); 25 | let catalog = new Catalog(); 26 | catalog.add(version1); 27 | catalog.add(version2); 28 | catalog.add(version3); 29 | catalog.add(version4); 30 | let match = catalog.getVersion('foo', 3); 31 | assert.strictEqual(version3, match); 32 | }); 33 | 34 | it('Find specific version returns null if specific version not found', () => { 35 | let version1 = new SharedSymbolTable('foo', 1, ['a']); 36 | let catalog = new Catalog(); 37 | catalog.add(version1); 38 | assert.isNull(catalog.getVersion('foo', 2)); 39 | }); 40 | 41 | it('Find specific version returns null if any version not found', () => { 42 | let catalog = new Catalog(); 43 | assert.isNull(catalog.getVersion('foo', 2)); 44 | }); 45 | 46 | it('Finds latest version', () => { 47 | let version1 = new SharedSymbolTable('foo', 1, ['a']); 48 | let version2 = new SharedSymbolTable('foo', 2, ['b']); 49 | let version3 = new SharedSymbolTable('foo', 3, ['b']); 50 | let version4 = new SharedSymbolTable('foo', 4, ['b']); 51 | let catalog = new Catalog(); 52 | catalog.add(version1); 53 | catalog.add(version2); 54 | catalog.add(version3); 55 | catalog.add(version4); 56 | let match = catalog.getTable('foo'); 57 | assert.isNotNull(match); 58 | assert.strictEqual(4, match!.version); 59 | }); 60 | 61 | it('Find latest version returns null if no version exists', () => { 62 | let catalog = new Catalog(); 63 | assert.isNull(catalog.getTable('foo')); 64 | }); 65 | 66 | it('Adding same symbol table twice overwrites original entry', () => { 67 | let version1a = new SharedSymbolTable('foo', 1, ['a']); 68 | let version1b = new SharedSymbolTable('foo', 1, ['a']); 69 | let catalog = new Catalog(); 70 | catalog.add(version1a); 71 | catalog.add(version1b); 72 | assert.strictEqual(version1b, catalog.getTable('foo')) 73 | }); 74 | }); -------------------------------------------------------------------------------- /test/IonImport.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {SharedSymbolTable} from "../src/IonSharedSymbolTable"; 18 | import {Import} from "../src/IonImport"; 19 | 20 | describe('Import', () => { 21 | it('Orphan import has offset of 1', () => { 22 | let symbolTable = new SharedSymbolTable('foo', 1, ['a']); 23 | let import_ = new Import(null, symbolTable); 24 | assert.equal(import_.getSymbolId('a'), 1); 25 | }); 26 | 27 | it('Child symbol ids start after parent', () => { 28 | let parentSymbolTable = new SharedSymbolTable('foo', 1, ['a', 'b']); 29 | let parentImport = new Import(null, parentSymbolTable); 30 | let childSymbolTable = new SharedSymbolTable('bar', 1, ['c']); 31 | let childImport = new Import(parentImport, childSymbolTable); 32 | 33 | assert.equal(childImport.getSymbolId('a'), 1); 34 | assert.equal(childImport.getSymbolId('b'), 2); 35 | assert.equal(childImport.getSymbolId('c'), 3); 36 | }); 37 | 38 | it('Child import consults parent import first', () => { 39 | let symbolTable = new SharedSymbolTable('foo', 1, ['a', 'b', 'c']); 40 | let parent = new Import(null, symbolTable); 41 | let child = new Import(parent, symbolTable); 42 | 43 | // Sanity check the parent 44 | assert.equal(parent.getSymbolId('a'), 1); 45 | assert.equal(parent.getSymbolId('b'), 2); 46 | assert.equal(parent.getSymbolId('c'), 3); 47 | 48 | // Verify that child ids match parent ids 49 | assert.equal(child.getSymbolId('a'), 1); 50 | assert.equal(child.getSymbolId('b'), 2); 51 | assert.equal(child.getSymbolId('c'), 3); 52 | 53 | // Verify that duplicate symbols are accessible 54 | assert.equal(child.getSymbolText(4), 'a'); 55 | assert.equal(child.getSymbolText(5), 'b'); 56 | assert.equal(child.getSymbolText(6), 'c'); 57 | }); 58 | 59 | it('Short length omits symbols', () => { 60 | let symbolTable = new SharedSymbolTable('foo', 1, ['a', 'b', 'c']); 61 | let parent = new Import(null, symbolTable, 1); 62 | let child = new Import(parent, symbolTable); 63 | 64 | assert.equal(child.getSymbolText(1), 'a'); 65 | assert.equal(child.getSymbolText(2), 'a'); 66 | assert.equal(child.getSymbolText(3), 'b'); 67 | assert.equal(child.getSymbolText(4), 'c'); 68 | }); 69 | 70 | it('Long length pads symbols', () => { 71 | let symbolTable = new SharedSymbolTable('foo', 1, ['a', 'b', 'c']); 72 | let parent = new Import(null, symbolTable, 4); 73 | let child = new Import(parent, symbolTable); 74 | 75 | assert.equal(child.getSymbolText(1), 'a'); 76 | assert.equal(child.getSymbolText(2), 'b'); 77 | assert.equal(child.getSymbolText(3), 'c'); 78 | assert.isUndefined(child.getSymbolText(4)); 79 | assert.equal(child.getSymbolText(5), 'a'); 80 | assert.equal(child.getSymbolText(6), 'b'); 81 | assert.equal(child.getSymbolText(7), 'c'); 82 | }); 83 | }); -------------------------------------------------------------------------------- /test/IonReaderConsistency.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import * as ion from "../src/Ion"; 3 | import { IonType } from "../src/Ion"; 4 | import { IonTypes } from "../src/IonTypes"; 5 | 6 | describe("IonReaderConsistency", () => { 7 | let writerTypes = [ 8 | { name: "Binary", mkInstance: () => ion.makeBinaryWriter() }, 9 | { name: "Text", mkInstance: () => ion.makeTextWriter() }, 10 | { name: "Pretty", mkInstance: () => ion.makePrettyWriter() }, 11 | ]; 12 | 13 | // regression test for https://github.com/amazon-ion/ion-js/issues/514 14 | writerTypes.forEach((writerType) => { 15 | it( 16 | "Reads annotations correctly from structs created by a " + 17 | writerType.name + 18 | " writer", 19 | () => { 20 | let writer = writerType.mkInstance(); 21 | // writes the following values with the provided writer, sums the 'id' ints 22 | // within structs annotated with 'foo', and verifies the sum is 26: 23 | // 24 | // foo::{ quantity: 7 } 25 | // bar::{ name: "x", id: 1 } 26 | // baz::{ items:["thing1", "thing2"] } 27 | // foo::{ quantity: 19 } 28 | // bar::{ name: "y", id: 8 } 29 | 30 | writer.setAnnotations(["foo"]); 31 | writer.stepIn(IonTypes.STRUCT); 32 | writer.writeFieldName("quantity"); 33 | writer.writeInt(7); 34 | writer.stepOut(); 35 | 36 | writer.setAnnotations(["bar"]); 37 | writer.stepIn(IonTypes.STRUCT); 38 | writer.writeFieldName("name"); 39 | writer.writeString("x"); 40 | writer.writeFieldName("id"); 41 | writer.writeInt(1); 42 | writer.stepOut(); 43 | 44 | writer.setAnnotations(["baz"]); 45 | writer.stepIn(IonTypes.STRUCT); 46 | writer.writeFieldName("items"); 47 | writer.stepIn(IonTypes.LIST); 48 | writer.writeString("thing1"); 49 | writer.writeString("thing2"); 50 | writer.stepOut(); 51 | writer.stepOut(); 52 | 53 | writer.setAnnotations(["foo"]); 54 | writer.stepIn(IonTypes.STRUCT); 55 | writer.writeFieldName("quantity"); 56 | writer.writeInt(19); 57 | writer.stepOut(); 58 | 59 | writer.setAnnotations(["bar"]); 60 | writer.stepIn(IonTypes.STRUCT); 61 | writer.writeFieldName("name"); 62 | writer.writeString("y"); 63 | writer.writeFieldName("id"); 64 | writer.writeInt(8); 65 | writer.stepOut(); 66 | 67 | writer.close(); 68 | 69 | let reader = ion.makeReader(writer.getBytes()); 70 | let sum = 0; 71 | let type: IonType | null; 72 | while ((type = reader.next())) { 73 | if (type === IonTypes.STRUCT) { 74 | let annotations = reader.annotations(); 75 | if (annotations.length > 0 && annotations[0] === "foo") { 76 | reader.stepIn(); 77 | while ((type = reader.next())) { 78 | if (reader.fieldName() === "quantity") { 79 | sum += reader.numberValue()!; 80 | break; 81 | } 82 | } 83 | reader.stepOut(); 84 | } 85 | } 86 | } 87 | 88 | assert.equal(sum, 26); 89 | } 90 | ); 91 | 92 | it("Reads big-ints created by a " + writerType.name + " writer", () => { 93 | let writer = writerType.mkInstance(); 94 | writer.writeInt(1); 95 | writer.close(); 96 | let reader = ion.makeReader(writer.getBytes()); 97 | reader.next(); 98 | let result = reader.bigIntValue()!; 99 | assert.isTrue(result === 1n); 100 | }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /test/IonReaderStepOutThrows.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from "chai"; 17 | import * as ion from "../src/Ion"; 18 | import {IonTypes, Writer} from "../src/Ion"; 19 | 20 | describe('IonReaderStepOutThrows', () => { 21 | function writeData(writer: Writer): Uint8Array { 22 | writer.stepIn(IonTypes.STRUCT); 23 | writer.writeFieldName('a'); 24 | writer.writeInt(1); 25 | writer.stepOut(); 26 | writer.close(); 27 | return writer.getBytes(); 28 | } 29 | 30 | let readerTypes = [ 31 | {name: 'Binary', data: writeData(ion.makeBinaryWriter())}, 32 | {name: 'Text', data: writeData(ion.makeTextWriter())}, 33 | ]; 34 | 35 | readerTypes.forEach(readerType => { 36 | it(readerType.name + 'Reader.stepOut() throws when called immediately', () => { 37 | let r = ion.makeReader(readerType.data); 38 | assert.throws(() => { r.stepOut() }); 39 | }); 40 | 41 | it(readerType.name + 'Reader.stepOut() throws when called after next', () => { 42 | let r = ion.makeReader(readerType.data); 43 | r.next(); 44 | assert.throws(() => { r.stepOut() }); 45 | }); 46 | 47 | it(readerType.name + 'Reader.stepOut() throws after returning to top-level', () => { 48 | let r = ion.makeReader(readerType.data); 49 | r.next(); 50 | r.stepIn(); 51 | r.next(); 52 | r.stepOut(); 53 | assert.throws(() => { r.stepOut() }); 54 | }); 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /test/IonText.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import * as ion from '../src/Ion'; 18 | import * as IonText from '../src/IonText'; 19 | 20 | let stringToCharCodes = function (text: string): Uint8Array { 21 | let charCodes = new Uint8Array(text.length); 22 | for (let i = 0; i < text.length; i++) { 23 | charCodes[i] = text.charCodeAt(i); 24 | } 25 | return charCodes; 26 | }; 27 | 28 | let base64Test = function (text: string, expected: string) { 29 | let msg = text + ' encoded in base64 is ' + expected; 30 | it(msg, () => { 31 | let charCodes = stringToCharCodes(text); 32 | assert.equal(ion.toBase64(charCodes), expected); 33 | }); 34 | }; 35 | 36 | let isIdentifierTest = function (text: string, expected) { 37 | let name = text + ' ' + (expected ? 'is' : 'is not') + ' an identifier'; 38 | it(name, () => { 39 | assert.equal(IonText.isIdentifier(text), expected); 40 | }); 41 | }; 42 | 43 | let isOperatorTest = function (text: string, expected) { 44 | let name = text + ' ' + (expected ? 'is' : 'is not') + ' an operator'; 45 | it(name, () => { 46 | assert.equal(IonText.isOperator(text), expected); 47 | }); 48 | }; 49 | 50 | let escapeTest = (ch, expected) => { 51 | let value = String.fromCharCode(ch); 52 | it('escape(chr(' + ch + '), ClobEscapes)', () => { 53 | assert.equal(IonText.escape(value, IonText.ClobEscapes), expected); 54 | }); 55 | it('escape(chr(' + ch + '), StringEscapes)', () => { 56 | assert.equal(IonText.escape(value, IonText.StringEscapes), expected); 57 | }); 58 | it('escape(chr(' + ch + '), SymbolEscapes)', () => { 59 | assert.equal(IonText.escape(value, IonText.SymbolEscapes), expected); 60 | }); 61 | }; 62 | 63 | describe('IonText', () => { 64 | describe('base64 encoding', () => { 65 | base64Test("M", "TQ=="); 66 | base64Test("Ma", "TWE="); 67 | base64Test("Man", "TWFu"); 68 | base64Test("ManM", "TWFuTQ=="); 69 | base64Test("ManMa", "TWFuTWE="); 70 | base64Test("ManMan", "TWFuTWFu"); 71 | }); 72 | describe('Detecting identifiers', () => { 73 | isIdentifierTest('$', true); 74 | isIdentifierTest('0$', false); 75 | isIdentifierTest('abc123', true); 76 | isIdentifierTest('_', true); 77 | isIdentifierTest('{}', false); 78 | }); 79 | describe('Detecting operators', () => { 80 | isOperatorTest('!', true); 81 | isOperatorTest('!!', true); 82 | isOperatorTest('a', false); 83 | isOperatorTest('a!', false); 84 | isOperatorTest('!a', false); 85 | isOperatorTest('<=>', true); 86 | }); 87 | describe('Escaping strings', () => { 88 | for (let i = 0; i < 32; i++) { 89 | let expected; 90 | switch (i) { 91 | case 0: 92 | expected = '\\0'; 93 | break; 94 | case 7: 95 | expected = '\\a'; 96 | break; 97 | case 8: 98 | expected = '\\b'; 99 | break; 100 | case 9: 101 | expected = '\\t'; 102 | break; 103 | case 10: 104 | expected = '\\n'; 105 | break; 106 | case 11: 107 | expected = '\\v'; 108 | break; 109 | case 12: 110 | expected = '\\f'; 111 | break; 112 | case 13: 113 | expected = '\\r'; 114 | break; 115 | default: 116 | let hex = i.toString(16); 117 | expected = '\\x' + '0'.repeat(2 - hex.length) + hex; 118 | } 119 | escapeTest(i, expected); 120 | } 121 | escapeTest(0x7e, '~'); // not escaped 122 | escapeTest(0x7f, '\\x7f'); 123 | escapeTest(0x9f, '\\x9f'); 124 | escapeTest(0xa0, '\xa0'); // not escaped 125 | }); 126 | }); -------------------------------------------------------------------------------- /test/IonUnicode.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {suite, test} from "mocha-typescript"; 18 | import {encodeUtf8} from '../src/IonUnicode'; 19 | 20 | @suite('Unicode') 21 | class UnicodeTests { 22 | @test "Encode dollar sign"() { 23 | assert.deepEqual(encodeUtf8('$'), new Uint8Array([0x24])); 24 | } 25 | 26 | @test "Encode cent sign"() { 27 | assert.deepEqual(encodeUtf8('¢'), new Uint8Array([0xc2, 0xa2])); 28 | } 29 | 30 | @test "Encode euro sign"() { 31 | assert.deepEqual(encodeUtf8('€'), new Uint8Array([0xe2, 0x82, 0xac])); 32 | } 33 | 34 | @test "Gothic letter hwair"() { 35 | assert.deepEqual( 36 | encodeUtf8(String.fromCodePoint(0x10348)), 37 | new Uint8Array([0xf0, 0x90, 0x8d, 0x88]) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/IonWriteable.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {suite, test} from "mocha-typescript"; 18 | import {Writeable} from '../src/IonWriteable'; 19 | 20 | @suite('Writeable') 21 | class WriteableTests { 22 | @test "writePartialArray"() { 23 | let writeable = new Writeable(); 24 | writeable.writeBytes(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]), 4, 4); 25 | assert.deepEqual(writeable.getBytes(), new Uint8Array([5, 6, 7, 8])); 26 | } 27 | } -------------------------------------------------------------------------------- /test/IonWriterUndefinedParameters.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import { assert } from "chai"; 17 | import * as ion from "../src/Ion"; 18 | import { Decimal, IonType, Reader, Timestamp, Writer } from "../src/Ion"; 19 | 20 | // This dummy impl exists as a substitute for reflection directly against 21 | // the Writer interface (which TypeScript doesn't currently support) 22 | class NoopWriter implements Writer { 23 | addAnnotation(annotation: string): void {} 24 | close(): void {} 25 | depth(): number { 26 | return 0; 27 | } 28 | getBytes(): Uint8Array { 29 | return new Uint8Array(); 30 | } 31 | setAnnotations(annotations: string[]): void {} 32 | stepIn(type: IonType): void {} 33 | stepOut(): void {} 34 | writeBlob(value: Uint8Array | null): void {} 35 | writeBoolean(value: boolean | null): void {} 36 | writeClob(value: Uint8Array | null): void {} 37 | writeDecimal(value: Decimal | null): void {} 38 | writeFieldName(fieldName: string): void {} 39 | writeFloat32(value: number | null): void {} 40 | writeFloat64(value: number | null): void {} 41 | writeInt(value: number | bigint | null): void {} 42 | writeNull(type: IonType): void {} 43 | writeString(value: string | null): void {} 44 | writeSymbol(value: string | null): void {} 45 | writeTimestamp(value: Timestamp | null): void {} 46 | writeValue(reader: Reader): void {} 47 | writeValues(reader: Reader): void {} 48 | } 49 | 50 | describe("IonWriterUndefinedParameters", () => { 51 | let testMethods = Reflect.ownKeys(NoopWriter.prototype) 52 | .map((method) => method.toString()) 53 | .filter((name) => name.startsWith("write")) 54 | .filter((name) => name !== "writeNull") 55 | .concat("addAnnotation", "setAnnotations", "stepIn"); 56 | 57 | let writerTypes = [ 58 | { name: "Binary", instance: ion.makeBinaryWriter() }, 59 | { name: "Text", instance: ion.makeTextWriter() }, 60 | { name: "Pretty", instance: ion.makePrettyWriter() }, 61 | ]; 62 | writerTypes.forEach((writerType) => { 63 | let writer = writerType.instance; 64 | describe(writerType.name + " writer", () => { 65 | testMethods.forEach(function (method) { 66 | it(method + "(undefined) throws", function () { 67 | assert.throws(() => writer[method](undefined)); 68 | }); 69 | }); 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/dom/propertyShadowing.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import { dom, IonTypes } from "../../src/Ion"; 3 | 4 | describe("dom.Struct property shadowing", () => { 5 | it("Built-in properties cannot be shadowed", () => { 6 | // Create a struct with field names that conflict with method names 7 | let s = dom.Value.from( 8 | { 9 | getType: "baz", 10 | getAnnotations: 56, 11 | fieldNames: ["dog", "cat", "mouse"], 12 | toString: 10, 13 | greetings: "hi", 14 | age: 7, 15 | }, 16 | ["foo", "bar"] 17 | ); 18 | 19 | // Method names are still directly accessible 20 | assert.equal(s.getType(), IonTypes.STRUCT); 21 | assert.deepEqual(s.getAnnotations(), ["foo", "bar"]); 22 | assert.deepEqual(s.fieldNames(), [ 23 | "getType", 24 | "getAnnotations", 25 | "fieldNames", 26 | "toString", 27 | "greetings", 28 | "age", 29 | ]); 30 | 31 | // Fields with names that would shadow a built-in are accessible via Value#get() 32 | assert.equal(s.get("getType")!.stringValue(), "baz"); 33 | assert.equal(s.get("getAnnotations")!.numberValue(), 56); 34 | assert.equal(s.get("fieldNames", 0)!.stringValue(), "dog"); 35 | assert.equal(s.get("fieldNames", 1)!.stringValue(), "cat"); 36 | assert.equal(s.get("fieldNames", 2)!.stringValue(), "mouse"); 37 | 38 | // deleteProperty proxy method 39 | assert.isTrue(delete s["greetings"]); 40 | assert.deepEqual(s.fieldNames(), [ 41 | "getType", 42 | "getAnnotations", 43 | "fieldNames", 44 | "toString", 45 | "age", 46 | ]); 47 | 48 | // deleteField Struct method 49 | assert.isTrue(s.deleteField("age")); 50 | assert.deepEqual(s.fieldNames(), [ 51 | "getType", 52 | "getAnnotations", 53 | "fieldNames", 54 | "toString", 55 | ]); 56 | 57 | // delete for properties that match built-in 58 | assert.equal(s.get("toString")!.numberValue(), 10); 59 | assert.isTrue(delete (s as any)["toString"]); 60 | assert.equal(typeof s.toString, "function"); 61 | assert.deepEqual(s.fieldNames(), [ 62 | "getType", 63 | "getAnnotations", 64 | "fieldNames", 65 | ]); 66 | 67 | // delete for field that doesn't exist 68 | assert.isFalse(s.deleteField("toString")); 69 | assert.isFalse(s.deleteField("greetings")); 70 | assert.isFalse(s.deleteField("name")); 71 | assert.isUndefined(s["greetings"]); 72 | 73 | // deleteField will throw an error if it's called on a dom.Value that isn't a struct 74 | let l = dom.Value.from([1, 2, 3]); 75 | assert.throws(() => l.deleteField("1"), Error); 76 | 77 | // get() does not return values for properties on `Object` 78 | assert.isNull(s.get("toString")); 79 | assert.isNull(s.get("toLocaleString")); 80 | assert.isNull(s.get("valueOf")); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/dump.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import * as ion from '../src/Ion'; 18 | import {dom, IonTypes} from '../src/Ion'; 19 | import {exampleIonValuesWhere, exampleJsValuesWhere} from "./exampleValues"; 20 | 21 | function roundTripTests(...values: any[]) { 22 | let expectedValues: dom.Value[] = values.map((jsValue) => dom.Value.from(jsValue)); 23 | it(`dumpBinary(${values})`, () => roundTripTest(ion.dumpBinary, values, expectedValues)); 24 | it(`dumpText(${values})`,() => roundTripTest(ion.dumpText, values, expectedValues)); 25 | it(`dumpPrettyText(${values})`, () => roundTripTest(ion.dumpPrettyText, values, expectedValues)); 26 | } 27 | 28 | // Dumps the provided values to Ion using the specified dump*() function, uses loadAll() to read the values 29 | // back in, then compares the input values to the loaded values. 30 | function roundTripTest(dumpFn: (...values: any[]) => any, 31 | inputValues: any[], 32 | expectedValues: dom.Value[]) { 33 | let actualValues: dom.Value[] = dom.loadAll(dumpFn(inputValues)); 34 | expectedValues.forEach((expectedValue, index) => { 35 | let actualValue = actualValues[index]; 36 | //TODO: This tests for a very weak common definition of equality. 37 | // We should make this a stronger test when .equals() is added to the Value interface[1]. 38 | // In the meantime, the core writing/equality logic is more strictly tested by 39 | // test/dom/Value.ts, which evaluates the data produced by the Value.writeTo() function 40 | // at the core of the ion.dump*() methods. 41 | // [1] https://github.com/amazon-ion/ion-js/issues/576 42 | assert.deepEqual(actualValue.getAnnotations(), expectedValue.getAnnotations()); 43 | assert.equal(actualValue.isNull(), expectedValue.isNull()); 44 | assert.equal(actualValue.getType(), expectedValue.getType()); 45 | }); 46 | } 47 | 48 | describe('dump*()', () => { 49 | describe('Round tripping JS values', () => { 50 | roundTripTests(exampleJsValuesWhere()) 51 | }); 52 | describe('Round tripping Ion values', () => { 53 | roundTripTests(exampleIonValuesWhere()) 54 | }); 55 | it('Text stream', () => { 56 | let ionText = ion.dumpText(null, 7, "Hello", [1, 2, 3]); 57 | assert.equal(ionText, 'null\n7\n"Hello"\n[1,2,3]'); 58 | }); 59 | it('Pretty text stream', () => { 60 | let ionPrettyText = ion.dumpPrettyText(null, 7, "Hello", [1, 2, 3]); 61 | assert.equal(ionPrettyText, 'null\n7\n"Hello"\n[\n 1,\n 2,\n 3\n]'); 62 | }); 63 | it('Binary stream', () => { 64 | let ionBinary: Uint8Array = ion.dumpBinary(null, 7, "Hello", [1, 2, 3]); 65 | let writer = ion.makeBinaryWriter(); 66 | writer.writeNull(IonTypes.NULL); 67 | writer.writeInt(7); 68 | writer.writeString("Hello"); 69 | writer.stepIn(IonTypes.LIST); 70 | writer.writeInt(1); 71 | writer.writeInt(2); 72 | writer.writeInt(3); 73 | writer.stepOut(); 74 | writer.close(); 75 | assert.deepEqual(ionBinary, writer.getBytes()); 76 | }); 77 | it('Struct with annotated field large enough to require a VarUInt length', () => { 78 | let ionText = "{a: b:: \"abcdefghijkl\"}"; 79 | let ionValue = ion.load(ionText); 80 | let binaryIonBytes = ion.dumpBinary(ionValue); 81 | let ionBinaryValue = ion.load(binaryIonBytes); 82 | assert.deepEqual(ionValue, ionBinaryValue); 83 | }); 84 | it('Struct with annotated field large enough to require a VarUInt length', () => { 85 | // this ionText is taken from https://github.com/amazon-ion/ion-js/issues/621 86 | let ionText = "'com.example.organization.model.data.ClassName2@1.0'::{\n" + 87 | " values: [\n" + 88 | " 'com.example.organization.model.data.ClassName1@1.0'::{\n" + 89 | " field_name_1: 'com.example.organization.model.types.ClassName1@1.0'::{\n" + 90 | " field_name_2: 'com.example.organization.model.types.ClassName2@1.0'::{\n" + 91 | " field_name_3: 9999999999.00,\n" + 92 | " field_name_4: ABC\n" + 93 | " }\n" + 94 | " },\n" + 95 | " field_name_5: AB\n" + 96 | " },\n" + 97 | " 'com.example.organization.model.data.ClassName1@1.0'::{\n" + 98 | " field_name_1: 'com.example.organization.model.types.ClassName1@1.0'::{\n" + 99 | " field_name_2: 'com.example.organization.model.types.ClassName2@1.0'::{\n" + 100 | " field_name_3: 9999999999.00,\n" + 101 | " field_name_4: DEF\n" + 102 | " }\n" + 103 | " },\n" + 104 | " field_name_5: CD\n" + 105 | " }\n" + 106 | " ]\n" + 107 | "}"; 108 | let ionValue = ion.load(ionText); 109 | let binaryIonBytes = ion.dumpBinary(ionValue); 110 | let ionBinaryValue = ion.load(binaryIonBytes); 111 | assert.deepEqual(ionValue, ionBinaryValue); 112 | }) 113 | }); 114 | -------------------------------------------------------------------------------- /test/exampleValues.ts: -------------------------------------------------------------------------------- 1 | import {Timestamp} from "../src/IonTimestamp"; 2 | import {Decimal} from "../src/IonDecimal"; 3 | import {load, Value} from "../src/dom"; 4 | 5 | // Used as a default filtering predicate in functions below 6 | function acceptAnyValue(_: any) : boolean { 7 | return true; 8 | } 9 | 10 | // A common collection of JS values that can be reduced to a relevant subset using the provided filter function. 11 | export function exampleJsValuesWhere(filter: (v: any) => boolean = acceptAnyValue): any[] { 12 | return [ 13 | null, 14 | true, false, 15 | Number.MIN_SAFE_INTEGER, -7.5, -7, 0, 7, 7.5, Number.MAX_SAFE_INTEGER, 16 | "", "foo", 17 | new Date(0), 18 | Timestamp.parse('1970-01-01T00:00:00Z'), 19 | new Decimal('1.5'), 20 | [], [1, 2, 3], [{foo: "bar"}], 21 | {}, {foo: "bar", baz: 5}, {foo: [1, 2, 3]} 22 | ].filter(filter); 23 | } 24 | 25 | // A common collection of Ion dom.Value instances that can be reduced to a relevant subset using 26 | // the provided filter function. 27 | export function exampleIonValuesWhere(filter: (v: Value) => boolean = acceptAnyValue): Value[] { 28 | return [ 29 | load('null')!, // null 30 | load('null.string')!, // typed null 31 | load('true')!, // boolean 32 | load('1')!, // integer 33 | load('15e-1')!, // float 34 | load('15d-1')!, // decimal 35 | load('1970-01-01T00:00:00.000Z')!, // timestamp 36 | load('"Hello"')!, // string 37 | load('Hello')!, // symbol 38 | load('{{aGVsbG8gd29ybGQ=}}')!, // blob 39 | load('{{"February"}}')!, // clob 40 | load('[1, 2, 3]')!, // list 41 | load('(1 2 3)')!, // s-expression 42 | load('{foo: true, bar: "Hello", baz: 5, qux: null}')! // struct 43 | ].filter(filter); 44 | } 45 | 46 | const _exampleIsoStrings: string[] = [ 47 | "1970-01-01T00:00:00Z", 48 | '2020-02-28T23:00:00.000-01:00', 49 | "2020-02-29T00:00:00Z", 50 | "2020-02-29T00:00:00+01:00", 51 | "2020-02-29T00:00:00-01:00", 52 | '2020-03-01T00:00:00.000+01:00', 53 | "2020-03-19T03:17:59.999Z", 54 | "2020-03-19T03:17:59+03:21", 55 | "2020-03-19T23:59:59-05:00", 56 | "2020-03-19T23:01:01-08:00", 57 | "2020-03-19T11:30:30-08:00", 58 | "2020-03-19T11:30:30.5-08:00", 59 | "2020-03-19T11:30:30.50-08:00", 60 | "2020-03-19T11:30:30.500-08:00", 61 | "2020-03-22T11:30:30.22-08:00", 62 | "2020-03-27T00:00:00Z", 63 | "2020-03-27T00:00:00.000Z", 64 | "2020-03-27T12:00:00-05:00", 65 | "2020-03-27T12:00:00-08:00", 66 | "2020-03-27T12:00:00+01:00", 67 | "2020-03-27T19:00:00-05:00", 68 | "2020-03-27T16:00:00-08:00", 69 | "2020-03-27T16:00:00.5-08:00", 70 | "2020-03-28T01:00:00+01:00", 71 | "2020-03-28T01:00:00.123456+01:00", 72 | "2020-03-28T01:00:00.123456789+01:00", 73 | ]; 74 | 75 | // A common collection of Date values that can be reduced to a relevant subset using 76 | // the provided filter function. 77 | export function exampleDatesWhere(filter: (v: Date) => boolean = acceptAnyValue): Date[] { 78 | return _exampleIsoStrings 79 | .map((isoString) => new Date(isoString)) 80 | .filter(filter); 81 | } 82 | 83 | // A common collection of Timestamp values that can be reduced to a relevant subset using 84 | // the provided filter function. 85 | export function exampleTimestampsWhere(filter: (v: Timestamp) => boolean = acceptAnyValue): Timestamp[] { 86 | return _exampleIsoStrings 87 | .map((isoString) => Timestamp.parse(isoString)!) 88 | .filter(filter); 89 | } -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require ts-node/register 2 | --ui mocha-typescript 3 | --require source-map-support/register 4 | #Increasing test timeout for node v14, refer To: https://github.com/amazon-ion/ion-js/issues/581 5 | --timeout 3000 6 | 7 | test/*.ts 8 | test/dom/*.ts 9 | -------------------------------------------------------------------------------- /test/mochaSupport.ts: -------------------------------------------------------------------------------- 1 | // Generates a mocha-friendly string representation of the provided value that can be used in test names 2 | import {_hasValue} from "../src/util"; 3 | 4 | export function valueName(value: any): string { 5 | if (value === null) { 6 | return 'null'; 7 | } 8 | if (typeof value === "object") { 9 | let typeText = _hasValue(value.constructor) ? value.constructor.name : 'Object'; 10 | let valueText = _hasValue(value.toString) ? `${value}` : JSON.stringify(value); 11 | 12 | return `${typeText}(${valueText})`; 13 | } 14 | return `${typeof value}(${value})`; 15 | } 16 | -------------------------------------------------------------------------------- /test/spans.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {suite, test} from "mocha-typescript"; 18 | import * as IonSpan from '../src/IonSpan'; 19 | 20 | @suite('Spans') 21 | class UtilTests { 22 | @test "null valueAt"() { 23 | let span = new IonSpan.StringSpan("null"); 24 | assert.equal('n'.charCodeAt(0), span.valueAt(0)); 25 | assert.equal('u'.charCodeAt(0), span.valueAt(1)); 26 | assert.equal('l'.charCodeAt(0), span.valueAt(2)); 27 | assert.equal('l'.charCodeAt(0), span.valueAt(3)); 28 | assert.equal(-1, span.valueAt(4)); 29 | } 30 | 31 | @test "null next"() { 32 | let span = new IonSpan.StringSpan("null"); 33 | assert.equal('n'.charCodeAt(0), span.next()); 34 | assert.equal('u'.charCodeAt(0), span.next()); 35 | assert.equal('l'.charCodeAt(0), span.next()); 36 | assert.equal('l'.charCodeAt(0), span.next()); 37 | assert.equal(-1, span.next()); 38 | } 39 | } -------------------------------------------------------------------------------- /test/textNulls.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {suite, test} from "mocha-typescript"; 18 | import * as ion from '../src/Ion'; 19 | 20 | @suite('Reading nulls') 21 | class TextNullTests { 22 | @test "Reading 'null'"() { 23 | let reader = ion.makeReader("null"); 24 | assert.equal(reader.next(), ion.IonTypes.NULL); 25 | assert.equal(reader.next(), undefined); 26 | } 27 | 28 | @test "Stepping into a null container"() { 29 | let reader = ion.makeReader("null.list"); 30 | assert.equal(reader.next(), ion.IonTypes.LIST); 31 | assert.isTrue(reader.isNull()); 32 | 33 | let fail = true; 34 | try { 35 | reader.stepIn(); 36 | } catch (e) { 37 | fail = false; 38 | } 39 | assert.isFalse(fail, "Stepped into null container"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import {suite, test} from "mocha-typescript"; 18 | import * as util from "../src/util"; 19 | 20 | @suite('util') 21 | class UtilTests { 22 | @test "_hasValue(undefined)"() { 23 | assert.equal(util._hasValue(undefined), false); 24 | } 25 | 26 | @test "_hasValue(null)"() { 27 | assert.equal(util._hasValue(null), false); 28 | } 29 | 30 | @test "_hasValue(0)"() { 31 | assert.equal(util._hasValue(0), true); 32 | } 33 | 34 | @test "_hasValue(1)"() { 35 | assert.equal(util._hasValue(1), true); 36 | } 37 | 38 | @test "_sign(-1)"() { 39 | assert.equal(util._sign(-1), -1); 40 | } 41 | 42 | @test "_sign(-0)"() { 43 | assert.equal(util._sign(-0), -1); 44 | } 45 | 46 | @test "_sign(0)"() { 47 | assert.equal(util._sign(0), 1); 48 | } 49 | 50 | @test "_sign(1)"() { 51 | assert.equal(util._sign(1), 1); 52 | } 53 | } -------------------------------------------------------------------------------- /test/writeSymbolTokens.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | import {assert} from 'chai'; 17 | import * as ion from '../src/Ion'; 18 | import * as IonText from '../src/IonText'; 19 | 20 | function verifyQuoteBehavior(symbolText: string, shouldQuote: boolean, shouldQuoteInSexp = true) { 21 | let writer = ion.makeTextWriter(); 22 | 23 | writer.writeSymbol(symbolText); // symbol 24 | 25 | writer.setAnnotations([symbolText]); 26 | writer.writeInt(5); // annotation 27 | 28 | writer.stepIn(ion.IonTypes.STRUCT); 29 | writer.writeFieldName(symbolText); // fieldname 30 | writer.writeInt(5); 31 | writer.stepOut(); 32 | 33 | writer.stepIn(ion.IonTypes.SEXP); 34 | writer.writeSymbol(symbolText); // symbol in sexp (operators should not be quoted) 35 | writer.stepOut(); 36 | 37 | writer.close(); 38 | let actual = String.fromCharCode.apply(null, writer.getBytes()); 39 | 40 | symbolText = IonText.escape(symbolText, IonText.SymbolEscapes); 41 | 42 | let expected = `${symbolText}\n${symbolText}::5\n{${symbolText}:5}\n(${symbolText})`; 43 | if (shouldQuote) { 44 | expected = `'${symbolText}'\n'${symbolText}'::5\n{'${symbolText}':5}\n`; 45 | if (shouldQuoteInSexp) { 46 | expected += `('${symbolText}')`; 47 | } else { 48 | expected += `(${symbolText})`; 49 | } 50 | } 51 | assert.equal(actual, expected); 52 | } 53 | 54 | function verifyQuotesAdded(symbolText: string) { 55 | verifyQuoteBehavior(symbolText, true) 56 | } 57 | 58 | function verifyNoQuotes(symbolText: string) { 59 | verifyQuoteBehavior(symbolText, false) 60 | } 61 | 62 | let symbolsThatNeedQuotes = [ 63 | 'false', 64 | 'nan', 65 | 'null', 66 | 'true', 67 | '+inf', 68 | '-inf', 69 | '', 70 | ' ', 71 | '1', 72 | '1-2', 73 | '1.2', 74 | '-1.2', 75 | '{}', 76 | '[]', 77 | '"', 78 | "'", 79 | '$1', 80 | ]; 81 | 82 | let symbolsThatDoNotNeedQuotes = [ 83 | 'a', 84 | 'a_b', 85 | '$a', 86 | ]; 87 | 88 | describe('Writing text symbol tokens', () => { 89 | describe('Symbols that need quotes', () => { 90 | for (let symbol of symbolsThatNeedQuotes) { 91 | it(symbol, () => verifyQuotesAdded(symbol)); 92 | } 93 | }); 94 | describe("Symbols that don't need quotes", () => { 95 | for (let symbol of symbolsThatDoNotNeedQuotes) { 96 | it(symbol, () => verifyNoQuotes(symbol)); 97 | } 98 | }); 99 | it('Symbols that need quotes outside of an S-Expression', () => verifyQuoteBehavior('+', true, false)) 100 | }); -------------------------------------------------------------------------------- /tsconfig.amd.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "amd", 5 | "outDir": "dist/amd/es6" 6 | } 7 | } -------------------------------------------------------------------------------- /tsconfig.es6.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "outDir": "dist/es6/es6" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // Base Typescript Compiler Configuration 2 | // Targets CommonJS Module system and ES6 feature set. 3 | { 4 | "include": ["src/**/*"], 5 | "exclude": ["**/*.d.ts"], 6 | "compilerOptions": { 7 | "target": "es2020", 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "esModuleInterop": true, 11 | "lib": ["ESNext"], 12 | "declaration": true, 13 | "inlineSources": true, 14 | "sourceMap": true, 15 | // TODO enable this to have stricter checking 16 | "strict": false, 17 | "strictNullChecks": true, 18 | "rootDirs": ["src", "test"], 19 | "outDir": "dist/commonjs/es6", 20 | "experimentalDecorators": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*", 5 | "test/**/*", 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ion-JS Library", 3 | 4 | "excludePrivate": true, 5 | "excludeProtected": true, 6 | 7 | "exclude": [ 8 | "src/AbstractWriter.ts", 9 | "src/IonConstants.ts", 10 | "src/IonImport.ts", 11 | "src/IonSubstituteSymbolTable.ts", 12 | "src/IonSpan.ts", 13 | "src/IonSymbol.ts", 14 | "src/IonSymbolIndex.ts", 15 | "src/IonUnicode.ts", 16 | "src/IonValue.ts", 17 | "src/IonWriteable.ts", 18 | "src/util.ts", 19 | 20 | "src/IonCatalog.ts", 21 | "src/IonSymbols.ts", 22 | "src/Ion*SymbolTable.ts", 23 | 24 | "src/IonBinary*.ts", 25 | "src/Ion+(Pretty|)Text*.ts", 26 | "src/*Raw.ts", 27 | "src/IonLowLevel*.ts", 28 | 29 | "src/IonTest*.ts", 30 | "src/IonEvent*.ts", 31 | 32 | "src/BigIntSerde.ts", 33 | "src/SignAndMagnitudeInt.ts" 34 | ], 35 | 36 | "out": "docs/api/", 37 | 38 | "hideGenerator": true 39 | } 40 | --------------------------------------------------------------------------------