├── .eslintignore ├── .eslintrc.js ├── .github ├── dependabot.yaml ├── images │ ├── banner-dark.svg │ └── banner-light.svg └── workflows │ ├── ci.yml │ ├── dependent_pr.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── Contributing.md ├── LICENSE ├── README.md ├── examples ├── README.md ├── ingest-events.ts ├── ingest-file.ts ├── ingest-string.ts ├── list-datasets.ts ├── logs.json ├── package-lock.json ├── package.json ├── query-legacy.ts ├── query.ts ├── tsconfig.json └── winston.ts ├── jest.config.ts ├── lib ├── client.ts ├── datasets.ts ├── httpClient.ts ├── index.ts ├── limit.ts ├── logger.ts └── users.ts ├── package-lock.json ├── package.json ├── shell.nix ├── tests ├── integration │ ├── client.test.ts │ ├── datasets.test.ts │ └── winston.test.ts └── unit │ ├── client.test.ts │ ├── datasets.test.ts │ └── users.test.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 2015, 5 | sourceType: 'module', 6 | }, 7 | extends: ['prettier'], 8 | rules: { 9 | // "@typescript-eslint/explicit-function-return-type": "off", 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | - package-ecosystem: github-actions 8 | directory: / 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/images/banner-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | name: Build 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node: 18 | - 16.x 19 | - 17.x 20 | - 18.x 21 | - 19.x 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: ${{ matrix.node }} 27 | cache: npm 28 | cache-dependency-path: package-lock.json 29 | - run: npm install 30 | - run: npm run build 31 | 32 | lint: 33 | name: Lint 34 | runs-on: ubuntu-latest 35 | needs: build 36 | strategy: 37 | matrix: 38 | node: 39 | - 16.x 40 | - 17.x 41 | - 18.x 42 | - 19.x 43 | steps: 44 | - uses: actions/checkout@v3 45 | - uses: actions/setup-node@v3 46 | with: 47 | node-version: ${{ matrix.node }} 48 | cache: npm 49 | cache-dependency-path: package-lock.json 50 | - run: npm install 51 | - run: npm run lint 52 | 53 | test-unit: 54 | name: Test (Unit) 55 | runs-on: ubuntu-latest 56 | needs: build 57 | strategy: 58 | matrix: 59 | node: 60 | - 16.x 61 | - 17.x 62 | - 18.x 63 | - 19.x 64 | steps: 65 | - uses: actions/checkout@v3 66 | - uses: actions/setup-node@v3 67 | with: 68 | node-version: ${{ matrix.node }} 69 | cache: npm 70 | cache-dependency-path: package-lock.json 71 | - run: npm install 72 | - run: npm run test 73 | 74 | test-integration: 75 | name: Test (Integration) 76 | runs-on: ubuntu-latest 77 | needs: build 78 | if: github.event.pull_request.head.repo.full_name == github.repository 79 | strategy: 80 | fail-fast: true 81 | matrix: 82 | node: 83 | - 16.x 84 | - 17.x 85 | - 18.x 86 | - 19.x 87 | environment: 88 | - development 89 | - staging 90 | include: 91 | - environment: development 92 | url: TESTING_DEV_API_URL 93 | token: TESTING_DEV_TOKEN 94 | org_id: TESTING_DEV_ORG_ID 95 | - environment: staging 96 | url: TESTING_STAGING_API_URL 97 | token: TESTING_STAGING_TOKEN 98 | org_id: TESTING_STAGING_ORG_ID 99 | max-parallel: 1 100 | steps: 101 | - uses: actions/checkout@v3 102 | - uses: actions/setup-node@v3 103 | with: 104 | node-version: ${{ matrix.node }} 105 | cache: npm 106 | cache-dependency-path: package-lock.json 107 | - run: npm install 108 | - env: 109 | AXIOM_URL: ${{ secrets[matrix.url] }} 110 | AXIOM_TOKEN: ${{ secrets[matrix.token] }} 111 | AXIOM_ORG_ID: ${{ secrets[matrix.org_id] }} 112 | AXIOM_DATASET_SUFFIX: ${{ github.run_id }}-${{ matrix.node }} 113 | run: npm run integration 114 | -------------------------------------------------------------------------------- /.github/workflows/dependent_pr.yml: -------------------------------------------------------------------------------- 1 | name: Dependent PRs 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - edited 8 | - reopened 9 | pull_request_target: 10 | types: 11 | - opened 12 | - edited 13 | - reopened 14 | - synchronize 15 | schedule: 16 | - cron: "0 * * * *" 17 | 18 | jobs: 19 | check: 20 | name: Check 21 | runs-on: ubuntu-latest 22 | if: github.repository_owner == 'axiomhq' 23 | steps: 24 | - uses: z0al/dependent-issues@v1 25 | env: 26 | GITHUB_TOKEN: ${{ github.token }} 27 | GITHUB_READ_TOKEN: ${{ secrets.AXIOM_AUTOMATION_TOKEN }} 28 | with: 29 | label: dependent 30 | keywords: depends on, blocked by, needs 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | NODEVERSION: 17.x 10 | 11 | jobs: 12 | publish: 13 | name: Publish 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions/setup-node@v2 18 | with: 19 | node-version: ${{ env.NODEVERSION }} 20 | cache: "npm" 21 | registry-url: "https://registry.npmjs.org" 22 | - run: npm install 23 | - run: npm publish --access public 24 | env: 25 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }} 26 | - uses: actions/setup-node@v2 27 | with: 28 | registry-url: "https://npm.pkg.github.com" 29 | - run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | - uses: softprops/action-gh-release@v1 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | .vscode 106 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | examples/ 3 | lib/ 4 | node_modules/ 5 | tests/ 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "all", 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 4 7 | }; -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | ## How to Contribute 2 | 3 | 👍🎉 First of all, thank you for your interest in Axiom-node! We'd love to accept your patches and contributions! 🎉👍 4 | 5 | This project accepts contributions. In order to contribute, you should pay attention to a few guidelines: 6 | 7 | ## Reporting Issues 8 | 9 | Bugs, feature requests, and development-related questions should be directed to our GitHub [issue tracker.](https://github.com/axiomhq/axiom-node/issues) 10 | 11 | When reporting a bug, please try and provide as much context as possible such as your operating system, NodeJS version and anything else that might be relevant to the bug. For feature requests, please explain what you're trying to do and how the requested feature would help you do that. 12 | 13 | ## Setup 14 | 15 | [Fork](https://github.com/axiomhq/axiom-node), then clone this repository: 16 | 17 | ``` 18 | git clone https://github.com/axiomhq/axiom-node 19 | cd axiom-node 20 | ``` 21 | 22 | ### Install development dependencies 23 | 24 | ``` 25 | npm install 26 | ``` 27 | 28 | ## Submitting Modifications 29 | 30 | 1. It's generally best to start by opening a new issue describing the bug or feature you're intending to fix. Even if you think it's relatively minor, it's helpful to know what people are working on. Mention in the initial issue that you are planning to work on that bug or feature so that it can be assigned to you. 31 | 32 | 2. Follow the normal process of forking the project, and setup a new branch to work in. It's important that each group of changes be done in separate branches in order to ensure that a pull request only includes the commits related to that bug or feature. 33 | 34 | 3. Make sure that the tests and the linters pass by running: 35 | 36 | ``` 37 | npm run test 38 | npm run lint 39 | ``` 40 | 41 | 4. Do your best to have [well-formated](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) commit messages for each change. This provides consistency throughout the project and ensures that commit messages are able to be formatted properly by various git tools. 42 | 43 | 5. Finally, push the commits to your fork and submit a [pull request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) 44 | 45 | ## Once you've filed the PR: 46 | 47 | - One or more maintainers will use GitHub's review feature to review your PR. 48 | - If the maintainer asks for any changes, edit your changes, push, and ask for another review. 49 | - If the maintainer decides to suggest some improvements or alternatives, modify and make improvements. Once your changes are approved, one of the project maintainers will merge them. 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021, Axiom, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ⚠️ Deprecated, use [axiom-js](https://github.com/axiomhq/axiom-js) instead 2 | 3 | [axiom-js](https://github.com/axiomhq/axiom-js) is our new JavaScript library that works cross-platform and contains packages to integrate with libraries like winston or pino. 4 | 5 | ---- 6 | 7 | ![axiom-node: The official NodeJS bindings for the Axiom API](.github/images/banner-dark.svg#gh-dark-mode-only) 8 | ![axiom-node: The official NodeJS bindings for the Axiom API](.github/images/banner-light.svg#gh-light-mode-only) 9 | 10 |
11 | 12 | [![Workflow][workflow_badge]][workflow] 13 | [![Latest Release][release_badge]][release] 14 | [![License][license_badge]][license] 15 | 16 |
17 | 18 | [Axiom](https://axiom.co) unlocks observability at any scale. 19 | 20 | - **Ingest with ease, store without limits:** Axiom’s next-generation datastore enables ingesting petabytes of data with ultimate efficiency. Ship logs from Kubernetes, AWS, Azure, Google Cloud, DigitalOcean, Nomad, and others. 21 | - **Query everything, all the time:** Whether DevOps, SecOps, or EverythingOps, query all your data no matter its age. No provisioning, no moving data from cold/archive to “hot”, and no worrying about slow queries. All your data, all. the. time. 22 | - **Powerful dashboards, for continuous observability:** Build dashboards to collect related queries and present information that’s quick and easy to digest for you and your team. Dashboards can be kept private or shared with others, and are the perfect way to bring together data from different sources 23 | 24 | For more information check out the [official documentation](https://axiom.co/docs) 25 | and our 26 | [community Discord](https://axiom.co/discord). 27 | 28 | ## Quickstart 29 | 30 | Install using `npm install`: 31 | 32 | ```shell 33 | npm install @axiomhq/axiom-node 34 | ``` 35 | 36 | If you use the [Axiom CLI](https://github.com/axiomhq/cli), run `eval $(axiom config export -f)` to configure your environment variables. 37 | 38 | Otherwise create a personal token in [the Axiom settings](https://app.axiom.co/profile) and export it as `AXIOM_TOKEN`. Set `AXIOM_ORG_ID` to the organization ID from the settings page of the organization you want to access. 39 | 40 | You can also configure the client using options passed to the constructor of the Client: 41 | 42 | ```ts 43 | const client = new Client({ 44 | token: process.env.AXIOM_TOKEN, 45 | orgId: process.env.AXIOM_ORG_ID, 46 | }); 47 | ``` 48 | 49 | Create and use a client like this: 50 | 51 | ```ts 52 | import { Client } from '@axiomhq/axiom-node'; 53 | 54 | async function main() { 55 | const client = new Client(); 56 | 57 | await client.ingestEvents('my-dataset', [{ foo: 'bar' }]); 58 | 59 | const res = await client.query(`['my-dataset'] | where foo == 'bar' | limit 100`); 60 | } 61 | ``` 62 | 63 | ## Using Axiom transport for Winston 64 | 65 | You can use Winston logger to send logs to Axiom. First, install the `winston` and `@axiomhq/axiom-node` packages, then 66 | create an instance of the logger with the AxiomTransport. 67 | 68 | ```ts 69 | import winston from 'winston'; 70 | import { WinstonTransport as AxiomTransport } from '@axiomhq/axiom-node'; 71 | 72 | const logger = winston.createLogger({ 73 | level: 'info', 74 | format: winston.format.json(), 75 | defaultMeta: { service: 'user-service' }, 76 | transports: [ 77 | new AxiomTransport({ 78 | dataset: 'my-dataset', // defaults to process.env.AXIOM_DATASET 79 | token: 'my-token', // defaults to process.env.AXIOM_TOKEN 80 | orgId: 'my-org-id', // defaults to process.env.AXIOM_ORG_ID 81 | }), 82 | ], 83 | }); 84 | ``` 85 | 86 | ### Error, exception and rejection handling 87 | 88 | If you want to log `Error`s, we recommend using the 89 | [`winston.format.errors`](https://github.com/winstonjs/logform#errors) 90 | formatter, for example like this: 91 | 92 | ```ts 93 | import winston from 'winston'; 94 | import { WinstonTransport as AxiomTransport } from '@axiomhq/axiom-node'; 95 | 96 | const { combine, errors, json } = winston.format; 97 | 98 | const axiomTransport = new AxiomTransport({ ... }); 99 | const logger = winston.createLogger({ 100 | // 8<----snip---- 101 | format: combine(errors({ stack: true }), json()), 102 | // 8<----snip---- 103 | }); 104 | ``` 105 | 106 | To automatically log uncaught exceptions and rejections, add the Axiom transport to the 107 | [`exceptionHandlers`](https://github.com/winstonjs/winston#exceptions) and 108 | [`rejectionHandlers`](https://github.com/winstonjs/winston#rejections) like 109 | this: 110 | 111 | ```ts 112 | import winston from 'winston'; 113 | import { WinstonTransport as AxiomTransport } from '@axiomhq/axiom-node'; 114 | 115 | const axiomTransport = new AxiomTransport({ ... }); 116 | const logger = winston.createLogger({ 117 | // 8<----snip---- 118 | transports: [axiomTransport], 119 | exceptionHandlers: [axiomTransport], 120 | rejectionHandlers: [axiomTransport], 121 | // 8<----snip---- 122 | }); 123 | ``` 124 | 125 | For further examples, see the [examples](examples) directory. 126 | 127 | ## License 128 | 129 | Distributed under the [MIT License](LICENSE). 130 | 131 | 132 | 133 | [workflow]: https://github.com/axiomhq/axiom-node/actions/workflows/ci.yml 134 | [workflow_badge]: https://img.shields.io/github/actions/workflow/status/axiomhq/axiom-node/ci.yml?branch=main&ghcache=unused 135 | [release]: https://github.com/axiomhq/axiom-node/releases/latest 136 | [release_badge]: https://img.shields.io/github/release/axiomhq/axiom-node.svg?ghcache=unused 137 | [license]: https://opensource.org/licenses/MIT 138 | [license_badge]: https://img.shields.io/github/license/axiomhq/axiom-node.svg?color=blue&ghcache=unused 139 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Usage 4 | 5 | ```shell 6 | export AXIOM_TOKEN="..." 7 | npx ts-node 8 | ``` 9 | 10 | 11 | ## Examples 12 | 13 | * [ingest-file.ts](ingest-file.ts): How to ingest the contents of a file into 14 | Axiom. 15 | 16 | * [list-datasets.ts](list-datasets.ts): How to retrieve a list of datasets. 17 | 18 | * [query-legacy.ts](query-legacy.ts): How to query a dataset. 19 | 20 | * [query.ts](query.ts): How to query a dataset using the Axiom Processing Language (APL). 21 | 22 | * [winston.ts](winston.ts): How to enable a logger and configure its transports. 23 | -------------------------------------------------------------------------------- /examples/ingest-events.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@axiomhq/axiom-node'; 2 | 3 | const client = new Client(); 4 | 5 | async function ingest() { 6 | const events = [ 7 | { 8 | foo: 'bar', 9 | }, 10 | { 11 | x: 'y', 12 | }, 13 | ]; 14 | 15 | const res = await client.ingestEvents('test', events); 16 | console.log('Ingested %d events with %d failures', res.ingested, res.failed); 17 | // Ingested 2 events with 0 failures 18 | } 19 | 20 | ingest(); 21 | -------------------------------------------------------------------------------- /examples/ingest-file.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { Client, ContentType, ContentEncoding } from '@axiomhq/axiom-node'; 3 | 4 | const client = new Client(); 5 | 6 | async function ingestFile() { 7 | const stream = fs.createReadStream('logs.json'); 8 | const res = await client.ingest('test', stream, ContentType.JSON, ContentEncoding.Identity); 9 | console.log('Ingested %d events with %d failures', res.ingested, res.failed); 10 | // Ingested 3 events with 0 failures 11 | } 12 | 13 | ingestFile(); 14 | -------------------------------------------------------------------------------- /examples/ingest-string.ts: -------------------------------------------------------------------------------- 1 | const { Readable } = require('stream'); 2 | import { Client, ContentType, ContentEncoding } from '@axiomhq/axiom-node'; 3 | 4 | const client = new Client(); 5 | 6 | async function ingestString() { 7 | const str = JSON.stringify([{ foo: 'bar' }, { foo: 'bar' }, { bar: 'baz' }]); 8 | const stream = Readable.from(str); 9 | const res = await client.ingest('test', stream, ContentType.JSON, ContentEncoding.Identity); 10 | console.log('Ingested %d events with %d failures', res.ingested, res.failed); 11 | // Ingested 3 events with 0 failures 12 | } 13 | 14 | ingestString(); 15 | -------------------------------------------------------------------------------- /examples/list-datasets.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@axiomhq/axiom-node'; 2 | 3 | const client = new Client(); 4 | 5 | async function listDatasets() { 6 | const res = await client.datasets.list(); 7 | for (let ds of res) { 8 | console.log(`found dataset: ${ds.name}`); 9 | } 10 | } 11 | 12 | listDatasets(); 13 | -------------------------------------------------------------------------------- /examples/logs.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"foo": "bar"}, 3 | {"foo": "bar"}, 4 | {"bar": "baz"} 5 | ] -------------------------------------------------------------------------------- /examples/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "examples", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "@axiomhq/axiom-node": "../lib", 12 | "winston": "^3.8.2" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^17.0.42", 16 | "ts-node": "^10.8.1", 17 | "typescript": "^4.7.3" 18 | } 19 | }, 20 | "../lib": {}, 21 | "node_modules/@axiomhq/axiom-node": { 22 | "resolved": "../lib", 23 | "link": true 24 | }, 25 | "node_modules/@colors/colors": { 26 | "version": "1.5.0", 27 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", 28 | "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", 29 | "engines": { 30 | "node": ">=0.1.90" 31 | } 32 | }, 33 | "node_modules/@cspotcode/source-map-support": { 34 | "version": "0.8.1", 35 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 36 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 37 | "dev": true, 38 | "dependencies": { 39 | "@jridgewell/trace-mapping": "0.3.9" 40 | }, 41 | "engines": { 42 | "node": ">=12" 43 | } 44 | }, 45 | "node_modules/@dabh/diagnostics": { 46 | "version": "2.0.3", 47 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", 48 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", 49 | "dependencies": { 50 | "colorspace": "1.1.x", 51 | "enabled": "2.0.x", 52 | "kuler": "^2.0.0" 53 | } 54 | }, 55 | "node_modules/@jridgewell/resolve-uri": { 56 | "version": "3.0.7", 57 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", 58 | "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", 59 | "dev": true, 60 | "engines": { 61 | "node": ">=6.0.0" 62 | } 63 | }, 64 | "node_modules/@jridgewell/sourcemap-codec": { 65 | "version": "1.4.13", 66 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", 67 | "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", 68 | "dev": true 69 | }, 70 | "node_modules/@jridgewell/trace-mapping": { 71 | "version": "0.3.9", 72 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 73 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 74 | "dev": true, 75 | "dependencies": { 76 | "@jridgewell/resolve-uri": "^3.0.3", 77 | "@jridgewell/sourcemap-codec": "^1.4.10" 78 | } 79 | }, 80 | "node_modules/@tsconfig/node10": { 81 | "version": "1.0.9", 82 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 83 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 84 | "dev": true 85 | }, 86 | "node_modules/@tsconfig/node12": { 87 | "version": "1.0.10", 88 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", 89 | "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", 90 | "dev": true 91 | }, 92 | "node_modules/@tsconfig/node14": { 93 | "version": "1.0.2", 94 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", 95 | "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", 96 | "dev": true 97 | }, 98 | "node_modules/@tsconfig/node16": { 99 | "version": "1.0.3", 100 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 101 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 102 | "dev": true 103 | }, 104 | "node_modules/@types/node": { 105 | "version": "17.0.42", 106 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", 107 | "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==", 108 | "dev": true 109 | }, 110 | "node_modules/acorn": { 111 | "version": "8.7.1", 112 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 113 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 114 | "dev": true, 115 | "bin": { 116 | "acorn": "bin/acorn" 117 | }, 118 | "engines": { 119 | "node": ">=0.4.0" 120 | } 121 | }, 122 | "node_modules/acorn-walk": { 123 | "version": "8.2.0", 124 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 125 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 126 | "dev": true, 127 | "engines": { 128 | "node": ">=0.4.0" 129 | } 130 | }, 131 | "node_modules/arg": { 132 | "version": "4.1.3", 133 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 134 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 135 | "dev": true 136 | }, 137 | "node_modules/async": { 138 | "version": "3.2.4", 139 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", 140 | "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" 141 | }, 142 | "node_modules/color": { 143 | "version": "3.2.1", 144 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 145 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 146 | "dependencies": { 147 | "color-convert": "^1.9.3", 148 | "color-string": "^1.6.0" 149 | } 150 | }, 151 | "node_modules/color-convert": { 152 | "version": "1.9.3", 153 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 154 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 155 | "dependencies": { 156 | "color-name": "1.1.3" 157 | } 158 | }, 159 | "node_modules/color-name": { 160 | "version": "1.1.3", 161 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 162 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 163 | }, 164 | "node_modules/color-string": { 165 | "version": "1.9.1", 166 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 167 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 168 | "dependencies": { 169 | "color-name": "^1.0.0", 170 | "simple-swizzle": "^0.2.2" 171 | } 172 | }, 173 | "node_modules/colorspace": { 174 | "version": "1.1.4", 175 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", 176 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", 177 | "dependencies": { 178 | "color": "^3.1.3", 179 | "text-hex": "1.0.x" 180 | } 181 | }, 182 | "node_modules/create-require": { 183 | "version": "1.1.1", 184 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 185 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 186 | "dev": true 187 | }, 188 | "node_modules/diff": { 189 | "version": "4.0.2", 190 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 191 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 192 | "dev": true, 193 | "engines": { 194 | "node": ">=0.3.1" 195 | } 196 | }, 197 | "node_modules/enabled": { 198 | "version": "2.0.0", 199 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 200 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" 201 | }, 202 | "node_modules/fecha": { 203 | "version": "4.2.3", 204 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", 205 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" 206 | }, 207 | "node_modules/fn.name": { 208 | "version": "1.1.0", 209 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 210 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" 211 | }, 212 | "node_modules/inherits": { 213 | "version": "2.0.4", 214 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 215 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 216 | }, 217 | "node_modules/is-arrayish": { 218 | "version": "0.3.2", 219 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 220 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 221 | }, 222 | "node_modules/is-stream": { 223 | "version": "2.0.1", 224 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 225 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 226 | "engines": { 227 | "node": ">=8" 228 | }, 229 | "funding": { 230 | "url": "https://github.com/sponsors/sindresorhus" 231 | } 232 | }, 233 | "node_modules/kuler": { 234 | "version": "2.0.0", 235 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 236 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" 237 | }, 238 | "node_modules/logform": { 239 | "version": "2.4.2", 240 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", 241 | "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", 242 | "dependencies": { 243 | "@colors/colors": "1.5.0", 244 | "fecha": "^4.2.0", 245 | "ms": "^2.1.1", 246 | "safe-stable-stringify": "^2.3.1", 247 | "triple-beam": "^1.3.0" 248 | } 249 | }, 250 | "node_modules/make-error": { 251 | "version": "1.3.6", 252 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 253 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 254 | "dev": true 255 | }, 256 | "node_modules/ms": { 257 | "version": "2.1.3", 258 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 259 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 260 | }, 261 | "node_modules/one-time": { 262 | "version": "1.0.0", 263 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 264 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 265 | "dependencies": { 266 | "fn.name": "1.x.x" 267 | } 268 | }, 269 | "node_modules/readable-stream": { 270 | "version": "3.6.0", 271 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 272 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 273 | "dependencies": { 274 | "inherits": "^2.0.3", 275 | "string_decoder": "^1.1.1", 276 | "util-deprecate": "^1.0.1" 277 | }, 278 | "engines": { 279 | "node": ">= 6" 280 | } 281 | }, 282 | "node_modules/safe-buffer": { 283 | "version": "5.2.1", 284 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 285 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 286 | "funding": [ 287 | { 288 | "type": "github", 289 | "url": "https://github.com/sponsors/feross" 290 | }, 291 | { 292 | "type": "patreon", 293 | "url": "https://www.patreon.com/feross" 294 | }, 295 | { 296 | "type": "consulting", 297 | "url": "https://feross.org/support" 298 | } 299 | ] 300 | }, 301 | "node_modules/safe-stable-stringify": { 302 | "version": "2.4.0", 303 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.0.tgz", 304 | "integrity": "sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA==", 305 | "engines": { 306 | "node": ">=10" 307 | } 308 | }, 309 | "node_modules/simple-swizzle": { 310 | "version": "0.2.2", 311 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 312 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 313 | "dependencies": { 314 | "is-arrayish": "^0.3.1" 315 | } 316 | }, 317 | "node_modules/stack-trace": { 318 | "version": "0.0.10", 319 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 320 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", 321 | "engines": { 322 | "node": "*" 323 | } 324 | }, 325 | "node_modules/string_decoder": { 326 | "version": "1.3.0", 327 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 328 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 329 | "dependencies": { 330 | "safe-buffer": "~5.2.0" 331 | } 332 | }, 333 | "node_modules/text-hex": { 334 | "version": "1.0.0", 335 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 336 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 337 | }, 338 | "node_modules/triple-beam": { 339 | "version": "1.3.0", 340 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", 341 | "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" 342 | }, 343 | "node_modules/ts-node": { 344 | "version": "10.8.1", 345 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", 346 | "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", 347 | "dev": true, 348 | "dependencies": { 349 | "@cspotcode/source-map-support": "^0.8.0", 350 | "@tsconfig/node10": "^1.0.7", 351 | "@tsconfig/node12": "^1.0.7", 352 | "@tsconfig/node14": "^1.0.0", 353 | "@tsconfig/node16": "^1.0.2", 354 | "acorn": "^8.4.1", 355 | "acorn-walk": "^8.1.1", 356 | "arg": "^4.1.0", 357 | "create-require": "^1.1.0", 358 | "diff": "^4.0.1", 359 | "make-error": "^1.1.1", 360 | "v8-compile-cache-lib": "^3.0.1", 361 | "yn": "3.1.1" 362 | }, 363 | "bin": { 364 | "ts-node": "dist/bin.js", 365 | "ts-node-cwd": "dist/bin-cwd.js", 366 | "ts-node-esm": "dist/bin-esm.js", 367 | "ts-node-script": "dist/bin-script.js", 368 | "ts-node-transpile-only": "dist/bin-transpile.js", 369 | "ts-script": "dist/bin-script-deprecated.js" 370 | }, 371 | "peerDependencies": { 372 | "@swc/core": ">=1.2.50", 373 | "@swc/wasm": ">=1.2.50", 374 | "@types/node": "*", 375 | "typescript": ">=2.7" 376 | }, 377 | "peerDependenciesMeta": { 378 | "@swc/core": { 379 | "optional": true 380 | }, 381 | "@swc/wasm": { 382 | "optional": true 383 | } 384 | } 385 | }, 386 | "node_modules/typescript": { 387 | "version": "4.7.3", 388 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", 389 | "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", 390 | "dev": true, 391 | "bin": { 392 | "tsc": "bin/tsc", 393 | "tsserver": "bin/tsserver" 394 | }, 395 | "engines": { 396 | "node": ">=4.2.0" 397 | } 398 | }, 399 | "node_modules/util-deprecate": { 400 | "version": "1.0.2", 401 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 402 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 403 | }, 404 | "node_modules/v8-compile-cache-lib": { 405 | "version": "3.0.1", 406 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 407 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 408 | "dev": true 409 | }, 410 | "node_modules/winston": { 411 | "version": "3.8.2", 412 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", 413 | "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", 414 | "dependencies": { 415 | "@colors/colors": "1.5.0", 416 | "@dabh/diagnostics": "^2.0.2", 417 | "async": "^3.2.3", 418 | "is-stream": "^2.0.0", 419 | "logform": "^2.4.0", 420 | "one-time": "^1.0.0", 421 | "readable-stream": "^3.4.0", 422 | "safe-stable-stringify": "^2.3.1", 423 | "stack-trace": "0.0.x", 424 | "triple-beam": "^1.3.0", 425 | "winston-transport": "^4.5.0" 426 | }, 427 | "engines": { 428 | "node": ">= 12.0.0" 429 | } 430 | }, 431 | "node_modules/winston-transport": { 432 | "version": "4.5.0", 433 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", 434 | "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", 435 | "dependencies": { 436 | "logform": "^2.3.2", 437 | "readable-stream": "^3.6.0", 438 | "triple-beam": "^1.3.0" 439 | }, 440 | "engines": { 441 | "node": ">= 6.4.0" 442 | } 443 | }, 444 | "node_modules/yn": { 445 | "version": "3.1.1", 446 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 447 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 448 | "dev": true, 449 | "engines": { 450 | "node": ">=6" 451 | } 452 | } 453 | }, 454 | "dependencies": { 455 | "@axiomhq/axiom-node": { 456 | "version": "file:../lib" 457 | }, 458 | "@colors/colors": { 459 | "version": "1.5.0", 460 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", 461 | "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" 462 | }, 463 | "@cspotcode/source-map-support": { 464 | "version": "0.8.1", 465 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 466 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 467 | "dev": true, 468 | "requires": { 469 | "@jridgewell/trace-mapping": "0.3.9" 470 | } 471 | }, 472 | "@dabh/diagnostics": { 473 | "version": "2.0.3", 474 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", 475 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", 476 | "requires": { 477 | "colorspace": "1.1.x", 478 | "enabled": "2.0.x", 479 | "kuler": "^2.0.0" 480 | } 481 | }, 482 | "@jridgewell/resolve-uri": { 483 | "version": "3.0.7", 484 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", 485 | "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", 486 | "dev": true 487 | }, 488 | "@jridgewell/sourcemap-codec": { 489 | "version": "1.4.13", 490 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", 491 | "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", 492 | "dev": true 493 | }, 494 | "@jridgewell/trace-mapping": { 495 | "version": "0.3.9", 496 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 497 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 498 | "dev": true, 499 | "requires": { 500 | "@jridgewell/resolve-uri": "^3.0.3", 501 | "@jridgewell/sourcemap-codec": "^1.4.10" 502 | } 503 | }, 504 | "@tsconfig/node10": { 505 | "version": "1.0.9", 506 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 507 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 508 | "dev": true 509 | }, 510 | "@tsconfig/node12": { 511 | "version": "1.0.10", 512 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz", 513 | "integrity": "sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==", 514 | "dev": true 515 | }, 516 | "@tsconfig/node14": { 517 | "version": "1.0.2", 518 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz", 519 | "integrity": "sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==", 520 | "dev": true 521 | }, 522 | "@tsconfig/node16": { 523 | "version": "1.0.3", 524 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 525 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", 526 | "dev": true 527 | }, 528 | "@types/node": { 529 | "version": "17.0.42", 530 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", 531 | "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==", 532 | "dev": true 533 | }, 534 | "acorn": { 535 | "version": "8.7.1", 536 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 537 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 538 | "dev": true 539 | }, 540 | "acorn-walk": { 541 | "version": "8.2.0", 542 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 543 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 544 | "dev": true 545 | }, 546 | "arg": { 547 | "version": "4.1.3", 548 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 549 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 550 | "dev": true 551 | }, 552 | "async": { 553 | "version": "3.2.4", 554 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", 555 | "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" 556 | }, 557 | "color": { 558 | "version": "3.2.1", 559 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", 560 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", 561 | "requires": { 562 | "color-convert": "^1.9.3", 563 | "color-string": "^1.6.0" 564 | } 565 | }, 566 | "color-convert": { 567 | "version": "1.9.3", 568 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 569 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 570 | "requires": { 571 | "color-name": "1.1.3" 572 | } 573 | }, 574 | "color-name": { 575 | "version": "1.1.3", 576 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 577 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" 578 | }, 579 | "color-string": { 580 | "version": "1.9.1", 581 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 582 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 583 | "requires": { 584 | "color-name": "^1.0.0", 585 | "simple-swizzle": "^0.2.2" 586 | } 587 | }, 588 | "colorspace": { 589 | "version": "1.1.4", 590 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", 591 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", 592 | "requires": { 593 | "color": "^3.1.3", 594 | "text-hex": "1.0.x" 595 | } 596 | }, 597 | "create-require": { 598 | "version": "1.1.1", 599 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 600 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 601 | "dev": true 602 | }, 603 | "diff": { 604 | "version": "4.0.2", 605 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 606 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 607 | "dev": true 608 | }, 609 | "enabled": { 610 | "version": "2.0.0", 611 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", 612 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" 613 | }, 614 | "fecha": { 615 | "version": "4.2.3", 616 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", 617 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" 618 | }, 619 | "fn.name": { 620 | "version": "1.1.0", 621 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", 622 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" 623 | }, 624 | "inherits": { 625 | "version": "2.0.4", 626 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 627 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 628 | }, 629 | "is-arrayish": { 630 | "version": "0.3.2", 631 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 632 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 633 | }, 634 | "is-stream": { 635 | "version": "2.0.1", 636 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 637 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" 638 | }, 639 | "kuler": { 640 | "version": "2.0.0", 641 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", 642 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" 643 | }, 644 | "logform": { 645 | "version": "2.4.2", 646 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", 647 | "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", 648 | "requires": { 649 | "@colors/colors": "1.5.0", 650 | "fecha": "^4.2.0", 651 | "ms": "^2.1.1", 652 | "safe-stable-stringify": "^2.3.1", 653 | "triple-beam": "^1.3.0" 654 | } 655 | }, 656 | "make-error": { 657 | "version": "1.3.6", 658 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 659 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 660 | "dev": true 661 | }, 662 | "ms": { 663 | "version": "2.1.3", 664 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 665 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 666 | }, 667 | "one-time": { 668 | "version": "1.0.0", 669 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", 670 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", 671 | "requires": { 672 | "fn.name": "1.x.x" 673 | } 674 | }, 675 | "readable-stream": { 676 | "version": "3.6.0", 677 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 678 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 679 | "requires": { 680 | "inherits": "^2.0.3", 681 | "string_decoder": "^1.1.1", 682 | "util-deprecate": "^1.0.1" 683 | } 684 | }, 685 | "safe-buffer": { 686 | "version": "5.2.1", 687 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 688 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 689 | }, 690 | "safe-stable-stringify": { 691 | "version": "2.4.0", 692 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.0.tgz", 693 | "integrity": "sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA==" 694 | }, 695 | "simple-swizzle": { 696 | "version": "0.2.2", 697 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 698 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 699 | "requires": { 700 | "is-arrayish": "^0.3.1" 701 | } 702 | }, 703 | "stack-trace": { 704 | "version": "0.0.10", 705 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 706 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" 707 | }, 708 | "string_decoder": { 709 | "version": "1.3.0", 710 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 711 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 712 | "requires": { 713 | "safe-buffer": "~5.2.0" 714 | } 715 | }, 716 | "text-hex": { 717 | "version": "1.0.0", 718 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 719 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 720 | }, 721 | "triple-beam": { 722 | "version": "1.3.0", 723 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", 724 | "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" 725 | }, 726 | "ts-node": { 727 | "version": "10.8.1", 728 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", 729 | "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", 730 | "dev": true, 731 | "requires": { 732 | "@cspotcode/source-map-support": "^0.8.0", 733 | "@tsconfig/node10": "^1.0.7", 734 | "@tsconfig/node12": "^1.0.7", 735 | "@tsconfig/node14": "^1.0.0", 736 | "@tsconfig/node16": "^1.0.2", 737 | "acorn": "^8.4.1", 738 | "acorn-walk": "^8.1.1", 739 | "arg": "^4.1.0", 740 | "create-require": "^1.1.0", 741 | "diff": "^4.0.1", 742 | "make-error": "^1.1.1", 743 | "v8-compile-cache-lib": "^3.0.1", 744 | "yn": "3.1.1" 745 | } 746 | }, 747 | "typescript": { 748 | "version": "4.7.3", 749 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", 750 | "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", 751 | "dev": true 752 | }, 753 | "util-deprecate": { 754 | "version": "1.0.2", 755 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 756 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 757 | }, 758 | "v8-compile-cache-lib": { 759 | "version": "3.0.1", 760 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 761 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 762 | "dev": true 763 | }, 764 | "winston": { 765 | "version": "3.8.2", 766 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", 767 | "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", 768 | "requires": { 769 | "@colors/colors": "1.5.0", 770 | "@dabh/diagnostics": "^2.0.2", 771 | "async": "^3.2.3", 772 | "is-stream": "^2.0.0", 773 | "logform": "^2.4.0", 774 | "one-time": "^1.0.0", 775 | "readable-stream": "^3.4.0", 776 | "safe-stable-stringify": "^2.3.1", 777 | "stack-trace": "0.0.x", 778 | "triple-beam": "^1.3.0", 779 | "winston-transport": "^4.5.0" 780 | } 781 | }, 782 | "winston-transport": { 783 | "version": "4.5.0", 784 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", 785 | "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", 786 | "requires": { 787 | "logform": "^2.3.2", 788 | "readable-stream": "^3.6.0", 789 | "triple-beam": "^1.3.0" 790 | } 791 | }, 792 | "yn": { 793 | "version": "3.1.1", 794 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 795 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 796 | "dev": true 797 | } 798 | } 799 | } 800 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.0", 4 | "description": "examples on using axiom-node", 5 | "scripts": { 6 | "build": "tsc", 7 | "example": "ts-node" 8 | }, 9 | "dependencies": { 10 | "@axiomhq/axiom-node": "../lib", 11 | "winston": "^3.8.2" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^17.0.42", 15 | "ts-node": "^10.8.1", 16 | "typescript": "^4.7.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/query-legacy.ts: -------------------------------------------------------------------------------- 1 | // The purpose of this example is to show how to query a dataset. 2 | import { Client } from '@axiomhq/axiom-node'; 3 | 4 | const client = new Client(); 5 | 6 | async function query() { 7 | const endTime = new Date(Date.now()).toISOString(); 8 | const startTime = new Date(new Date().getTime() - 1 * 60 * 60).toISOString(); // 1 minute 9 | const query = { 10 | startTime: startTime, 11 | endTime: endTime, 12 | resolution: 'auto', 13 | }; 14 | 15 | const res = await client.queryLegacy('id', query); 16 | if (!res.matches || res.matches?.length === 0) { 17 | console.warn('no matches found'); 18 | return; 19 | } 20 | 21 | for (let matched of res.matches) { 22 | console.log(matched.data); 23 | } 24 | } 25 | 26 | query(); 27 | -------------------------------------------------------------------------------- /examples/query.ts: -------------------------------------------------------------------------------- 1 | // The purpose of this example is to show how to query a dataset using the Axiom 2 | // Processing Language (APL). 3 | import { Client } from '@axiomhq/axiom-node'; 4 | 5 | const client = new Client(); 6 | 7 | async function query() { 8 | const aplQuery = "['my-dataset'] | where status == 500"; 9 | 10 | const res = await client.query(aplQuery); 11 | if (!res.matches || res.matches.length === 0) { 12 | console.warn('no matches found'); 13 | return; 14 | } 15 | 16 | for (let matched of res.matches) { 17 | console.log(matched.data); 18 | } 19 | } 20 | 21 | query(); 22 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "baseUrl": "lib", 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "strict": true, 10 | "esModuleInterop": true 11 | }, 12 | "exclude": [ 13 | ".git", 14 | ".github", 15 | "node_modules" 16 | ], 17 | "include": [ 18 | "**/*" 19 | ] 20 | } -------------------------------------------------------------------------------- /examples/winston.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | import { WinstonTransport as AxiomTransport } from '@axiomhq/axiom-node'; 3 | 4 | const logger = winston.createLogger({ 5 | level: 'info', 6 | format: winston.format.json(), 7 | defaultMeta: { service: 'user-service' }, 8 | transports: [ 9 | // You can pass an option here, if you don't the transport is configured 10 | // using environment variables like `AXIOM_DATASET` and `AXIOM_TOKEN` 11 | new AxiomTransport(), 12 | ], 13 | }); 14 | 15 | // Add the console logger if we're not in production 16 | if (process.env.NODE_ENV != 'production') { 17 | logger.add( 18 | new winston.transports.Console({ 19 | format: winston.format.simple(), 20 | }), 21 | ); 22 | } 23 | 24 | logger.log({ 25 | level: 'info', 26 | message: 'Logger successfuly setup', 27 | }); 28 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { defaults } from 'jest-config' 2 | import type {Config} from '@jest/types'; 3 | 4 | const config: Config.InitialOptions = { 5 | preset: 'ts-jest', 6 | testEnvironment: 'node', 7 | moduleFileExtensions: ['ts', ...defaults.moduleFileExtensions] 8 | }; 9 | export default config; 10 | -------------------------------------------------------------------------------- /lib/client.ts: -------------------------------------------------------------------------------- 1 | import { datasets } from './datasets'; 2 | import { users } from './users'; 3 | import HTTPClient, { ClientOptions } from './httpClient'; 4 | import { gzip } from 'zlib'; 5 | import { promisify } from 'util'; 6 | import { Readable, Stream } from 'stream'; 7 | 8 | export class Client extends HTTPClient { 9 | datasets: datasets.Service; 10 | users: users.Service; 11 | localPath = '/v1'; 12 | 13 | constructor(options?: ClientOptions) { 14 | super(options); 15 | this.datasets = new datasets.Service(options); 16 | this.users = new users.Service(options); 17 | } 18 | 19 | ingest = ( 20 | id: string, 21 | stream: Stream, 22 | contentType: ContentType, 23 | contentEncoding: ContentEncoding, 24 | options?: IngestOptions, 25 | ): Promise => 26 | this.client 27 | .post(this.localPath + '/datasets/' + id + '/ingest', stream, { 28 | headers: { 29 | 'Content-Type': contentType, 30 | 'Content-Encoding': contentEncoding, 31 | }, 32 | params: { 33 | 'timestamp-field': options?.timestampField, 34 | 'timestamp-format': options?.timestampFormat, 35 | 'csv-delimiter': options?.csvDelimiter, 36 | }, 37 | }) 38 | .then((response) => { 39 | return response.data; 40 | }); 41 | 42 | ingestBuffer = ( 43 | id: string, 44 | buffer: Buffer, 45 | contentType: ContentType, 46 | contentEncoding: ContentEncoding, 47 | options?: IngestOptions, 48 | ): Promise => this.ingest(id, Readable.from(buffer), contentType, contentEncoding, options); 49 | 50 | ingestString = ( 51 | id: string, 52 | data: string, 53 | contentType: ContentType, 54 | contentEncoding: ContentEncoding, 55 | options?: IngestOptions, 56 | ): Promise => this.ingest(id, Readable.from(data), contentType, contentEncoding, options); 57 | 58 | ingestEvents = async ( 59 | id: string, 60 | events: Array | object, 61 | options?: IngestOptions, 62 | ): Promise => { 63 | const array = Array.isArray(events) ? events : [events]; 64 | const json = array.map((v) => JSON.stringify(v)).join('\n'); 65 | const encoded = await promisify(gzip)(json); 66 | return this.ingestBuffer(id, encoded, ContentType.NDJSON, ContentEncoding.GZIP, options); 67 | }; 68 | 69 | queryLegacy = (id: string, query: QueryLegacy, options?: QueryOptions): Promise => 70 | this.client 71 | .post(this.localPath + '/datasets/' + id + '/query', query, { 72 | params: { 73 | 'streaming-duration': options?.streamingDuration, 74 | nocache: options?.noCache, 75 | }, 76 | }) 77 | .then((response) => { 78 | return response.data; 79 | }); 80 | 81 | query = (apl: string, options?: QueryOptions): Promise => { 82 | const req: Query = { apl: apl, startTime: options?.startTime, endTime: options?.endTime }; 83 | return this.client 84 | .post(this.localPath + '/datasets/_apl', req, { 85 | params: { 86 | 'streaming-duration': options?.streamingDuration, 87 | nocache: options?.noCache, 88 | format: 'legacy', 89 | }, 90 | }) 91 | .then((response) => { 92 | return response.data; 93 | }); 94 | }; 95 | 96 | aplQuery = (apl: string, options?: QueryOptions): Promise => this.query(apl, options); 97 | } 98 | 99 | export enum ContentType { 100 | JSON = 'application/json', 101 | NDJSON = 'application/x-ndjson', 102 | CSV = 'text/csv', 103 | } 104 | 105 | export enum ContentEncoding { 106 | Identity = '', 107 | GZIP = 'gzip', 108 | } 109 | 110 | export interface IngestOptions { 111 | timestampField?: string; 112 | timestampFormat?: string; 113 | csvDelimiter?: string; 114 | } 115 | 116 | export interface IngestStatus { 117 | ingested: number; 118 | failed: number; 119 | failures?: Array; 120 | processedBytes: number; 121 | blocksCreated: number; 122 | walLength: number; 123 | } 124 | 125 | export interface IngestFailure { 126 | timestamp: string; 127 | error: string; 128 | } 129 | 130 | export interface QueryOptionsBase { 131 | streamingDuration?: string; 132 | noCache?: boolean; 133 | } 134 | 135 | export interface QueryOptions extends QueryOptionsBase { 136 | startTime?: string; 137 | endTime?: string; 138 | } 139 | 140 | export interface QueryLegacy { 141 | aggregations?: Array; 142 | continuationToken?: string; 143 | cursor?: string; 144 | endTime: string; 145 | filter?: Filter; 146 | groupBy?: Array; 147 | includeCursor?: boolean; 148 | limit?: number; 149 | order?: Array; 150 | project?: Array; 151 | resolution: string; 152 | startTime: string; 153 | virtualFields?: Array; 154 | } 155 | 156 | export interface Aggregation { 157 | argument?: any; 158 | field: string; 159 | op: AggregationOp; 160 | } 161 | 162 | export enum AggregationOp { 163 | Count = 'count', 164 | Distinct = 'distinct', 165 | Sum = 'sum', 166 | Avg = 'avg', 167 | Min = 'min', 168 | Max = 'max', 169 | Topk = 'topk', 170 | Percentiles = 'percentiles', 171 | Histogram = 'histogram', 172 | Variance = 'variance', 173 | Stdev = 'stdev', 174 | ArgMin = 'argmin', 175 | ArgMax = 'argmax', 176 | MakeSet = 'makeset', 177 | MakeSetIf = 'makesetif', 178 | CountIf = 'countif', 179 | CountDistinctIf = 'distinctif', 180 | } 181 | 182 | export interface Filter { 183 | caseSensitive?: boolean; 184 | children?: Array; 185 | field: string; 186 | op: FilterOp; 187 | value?: any; 188 | } 189 | 190 | export enum FilterOp { 191 | And = 'and', 192 | Or = 'or', 193 | Not = 'not', 194 | Equal = '==', 195 | NotEqual = '!=', 196 | Exists = 'exists', 197 | NotExists = 'not-exists', 198 | GreaterThan = '>', 199 | GreaterThanOrEqualTo = '>=', 200 | LessThan = '<', 201 | LessThanOrEqualTo = '<=', 202 | Gt = 'gt', 203 | Gte = 'gte', 204 | Lt = 'lt', 205 | Lte = 'lte', 206 | StartsWith = 'starts-with', 207 | NotStartsWith = 'not-starts-with', 208 | EndsWith = 'ends-with', 209 | NotEndsWith = 'not-ends-with', 210 | Contains = 'contains', 211 | NotContains = 'not-contains', 212 | Regexp = 'regexp', 213 | NotRegexp = 'not-regexp', 214 | } 215 | 216 | export interface Order { 217 | desc: boolean; 218 | field: string; 219 | } 220 | 221 | export interface Projection { 222 | alias?: string; 223 | field: string; 224 | } 225 | 226 | export interface VirtualColumn { 227 | alias: string; 228 | expr: string; 229 | } 230 | 231 | export interface QueryLegacyResult { 232 | buckets: Timeseries; 233 | matches?: Array; 234 | status: Status; 235 | } 236 | 237 | export interface QueryResult { 238 | request: QueryLegacy; 239 | 240 | // Copied from QueryResult 241 | buckets: Timeseries; 242 | datasetNames: string[]; 243 | matches?: Array; 244 | status: Status; 245 | } 246 | 247 | export interface Timeseries { 248 | series?: Array; 249 | totals?: Array; 250 | } 251 | 252 | export interface Interval { 253 | endTime: string; 254 | groups?: Array; 255 | startTime: string; 256 | } 257 | 258 | export interface EntryGroup { 259 | aggregations?: Array; 260 | group: { [key: string]: any }; 261 | id: number; 262 | } 263 | 264 | export interface EntryGroupAgg { 265 | op: string; 266 | value: any; 267 | } 268 | 269 | export interface Entry { 270 | _rowId: string; 271 | _sysTime: string; 272 | _time: string; 273 | data: { [key: string]: any }; 274 | } 275 | 276 | export interface Status { 277 | blocksExamined: number; 278 | continuationToken?: string; 279 | elapsedTime: number; 280 | isEstimate?: boolean; 281 | isPartial: boolean; 282 | maxBlockTime: string; 283 | messages?: Array; 284 | minBlockTime: string; 285 | numGroups: number; 286 | rowsExamined: number; 287 | rowsMatched: number; 288 | maxCursor: string; 289 | minCursor: string; 290 | } 291 | 292 | export interface Message { 293 | code?: string; 294 | count: number; 295 | msg: string; 296 | priority: string; 297 | } 298 | 299 | export interface Query { 300 | apl: string; 301 | startTime?: string; 302 | endTime?: string; 303 | } 304 | -------------------------------------------------------------------------------- /lib/datasets.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | import HTTPClient from './httpClient'; 4 | 5 | export namespace datasets { 6 | export const TimestampField = '_time'; 7 | 8 | export interface Dataset { 9 | id: number; 10 | name: string; 11 | description?: string; 12 | who?: string; 13 | created: string; 14 | } 15 | 16 | export interface Field { 17 | name: string; 18 | description: string; 19 | type: string; 20 | unit: string; 21 | hidden: boolean; 22 | } 23 | 24 | export interface TrimResult {} 25 | 26 | export interface CreateRequest { 27 | name: string; 28 | description?: string; 29 | } 30 | 31 | export interface UpdateRequest { 32 | description: string; 33 | } 34 | 35 | interface TrimRequest { 36 | maxDuration: string; 37 | } 38 | 39 | export class Service extends HTTPClient { 40 | private readonly localPath = '/v1/datasets'; 41 | 42 | list = (): Promise<[Dataset]> => 43 | this.client.get<[Dataset]>(this.localPath).then((response) => { 44 | return response.data; 45 | }); 46 | 47 | get = (id: string): Promise => 48 | this.client.get(this.localPath + '/' + id).then((response) => { 49 | return response.data; 50 | }); 51 | 52 | create = (req: CreateRequest): Promise => 53 | this.client.post(this.localPath, req).then((response) => { 54 | return response.data; 55 | }); 56 | 57 | update = (id: string, req: UpdateRequest): Promise => 58 | this.client.put(this.localPath + '/' + id, req).then((response) => { 59 | return response.data; 60 | }); 61 | 62 | delete = (id: string): Promise => this.client.delete(this.localPath + '/' + id); 63 | 64 | trim = (id: string, maxDurationStr: string): Promise => { 65 | // Go's 'time.Duration' uses nanoseconds as its base unit. So parse the 66 | // duration string and convert to nanoseconds. 1ms = 1000000ns. 67 | const req: TrimRequest = { maxDuration: maxDurationStr }; 68 | return this.client.post(this.localPath + '/' + id + '/trim', req).then((response) => { 69 | return response.data; 70 | }); 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/httpClient.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; 2 | import axiosRetry, { isNetworkError, isRetryableError } from 'axios-retry'; 3 | import { Limit, LimitType, LimitScope, parseLimitFromResponse, limitKey } from './limit'; 4 | 5 | const Version = require('../package.json').version; 6 | const AxiomURL = 'https://api.axiom.co'; 7 | 8 | export interface ClientOptions { 9 | token?: string; 10 | url?: string; 11 | orgId?: string; 12 | } 13 | 14 | export default abstract class HTTPClient { 15 | protected readonly client: AxiosInstance; 16 | limits: { [key: string]: Limit } = {}; 17 | 18 | constructor(options: ClientOptions = {}) { 19 | const token = options.token || process.env.AXIOM_TOKEN || ''; 20 | const url = options.url || process.env.AXIOM_URL || AxiomURL; 21 | const orgId = options.orgId || process.env.AXIOM_ORG_ID || ''; 22 | 23 | this.client = axios.create({ 24 | baseURL: url, 25 | timeout: 30000, 26 | }); 27 | 28 | this.client.defaults.headers.common['Accept'] = 'application/json'; 29 | this.client.defaults.headers.common['User-Agent'] = 'axiom-node/' + Version; 30 | this.client.defaults.headers.common['Authorization'] = 'Bearer ' + token; 31 | if (orgId) { 32 | this.client.defaults.headers.common['X-Axiom-Org-Id'] = orgId; 33 | } 34 | 35 | // We should only retry in the case the status code is >= 500, anything below isn't worth retrying. 36 | axiosRetry(this.client, { 37 | retryDelay: axiosRetry.exponentialDelay, 38 | retryCondition: (error: any) => { 39 | return isNetworkError(error) || isRetryableError(error); 40 | }, 41 | }); 42 | 43 | this.client.interceptors.response.use( 44 | (response) => response, 45 | (error) => { 46 | // Some errors don't have a response (i.e. when unit-testing) 47 | if (error.response) { 48 | if (error.response.status == 429) { 49 | const limit = parseLimitFromResponse(error.response); 50 | const key = limitKey(limit.type, limit.scope); 51 | this.limits[key] = limit; 52 | return Promise.reject(new AxiomTooManyRequestsError(limit, error.response)); 53 | } 54 | 55 | const message = error.response.data.message; 56 | if (message) { 57 | return Promise.reject(new Error(message)); 58 | } 59 | } 60 | 61 | return Promise.reject(error); 62 | }, 63 | ); 64 | } 65 | 66 | checkLimit(config: AxiosRequestConfig) { 67 | let limitType = LimitType.api; 68 | if (config.url?.endsWith('/ingest')) { 69 | limitType = LimitType.ingest; 70 | } else if (config.url?.endsWith('/query') || config.url?.endsWith('/_apl')) { 71 | limitType = LimitType.query; 72 | } 73 | 74 | let limit = new Limit(); 75 | let foundLimit = false; 76 | for (let scope of Object.values(LimitScope)) { 77 | const key = limitKey(limitType, scope); 78 | if (this.limits[key]) { 79 | limit = this.limits[key]; 80 | foundLimit = true; 81 | break; 82 | } 83 | } 84 | 85 | // create fake response 86 | const currentTime = new Date().getTime(); 87 | if (foundLimit && limit.remaining == 0 && currentTime < limit.reset.getTime()) { 88 | config.adapter = (config) => 89 | new Promise((_, reject) => { 90 | const res: AxiosResponse = { 91 | data: '', 92 | status: 429, 93 | statusText: 'Too Many Requests', 94 | headers: { 'content-type': 'text/plain; charset=utf-8' }, 95 | config, 96 | request: {}, 97 | }; 98 | 99 | return reject(new AxiomTooManyRequestsError(limit, res, true)); 100 | }); 101 | } 102 | 103 | return config; 104 | } 105 | } 106 | 107 | export class AxiomTooManyRequestsError extends Error { 108 | public message: string = ''; 109 | 110 | constructor(public limit: Limit, public response: AxiosResponse, public shortcircuit = false) { 111 | super(); 112 | const retryIn = this.timeUntilReset(); 113 | this.message = `${limit.type} limit exceeded, not making remote request, try again in ${retryIn.minutes}m${retryIn.seconds}s`; 114 | if (limit.type == LimitType.api) { 115 | this.message = `${limit.scope} ` + this.message; 116 | } 117 | } 118 | 119 | timeUntilReset() { 120 | const total = this.limit.reset.getTime() - new Date().getTime(); 121 | const seconds = Math.floor((total / 1000) % 60); 122 | const minutes = Math.floor((total / 1000 / 60) % 60); 123 | 124 | return { 125 | total, 126 | minutes, 127 | seconds, 128 | }; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | export { datasets } from './datasets'; 3 | export { WinstonTransport } from './logger'; 4 | export { users } from './users'; 5 | -------------------------------------------------------------------------------- /lib/limit.ts: -------------------------------------------------------------------------------- 1 | import { AxiosResponse } from 'axios'; 2 | 3 | export const headerRateScope = 'X-RateLimit-Scope'; 4 | 5 | export const headerAPILimit = 'X-RateLimit-Limit'; 6 | export const headerAPIRateRemaining = 'X-RateLimit-Remaining'; 7 | export const headerAPIRateReset = 'X-RateLimit-Reset'; 8 | 9 | export const headerQueryLimit = 'X-QueryLimit-Limit'; 10 | export const headerQueryRemaining = 'X-QueryLimit-Remaining'; 11 | export const headerQueryReset = 'X-QueryLimit-Reset'; 12 | 13 | export const headerIngestLimit = 'X-IngestLimit-Limit'; 14 | export const headerIngestRemaining = 'X-IngestLimit-Remaining'; 15 | export const headerIngestReset = 'X-IngestLimit-Reset'; 16 | 17 | export enum LimitScope { 18 | unknown = 'unknown', 19 | user = 'user', 20 | organization = 'organization', 21 | anonymous = 'anonymous', 22 | } 23 | export enum LimitType { 24 | api = 'api', 25 | query = 'query', 26 | ingest = 'ingest', 27 | } 28 | 29 | export class Limit { 30 | constructor( 31 | public scope: LimitScope = LimitScope.unknown, 32 | public type: LimitType = LimitType.api, 33 | public value: number = 0, 34 | public remaining: number = -1, 35 | public reset: Date = new Date(), 36 | ) {} 37 | } 38 | 39 | // parse limit headers from axios response and return a limit object 40 | export function parseLimitFromResponse(response: AxiosResponse): Limit { 41 | let limit: Limit; 42 | 43 | if (response.config.url?.endsWith('/ingest')) { 44 | limit = parseLimitFromHeaders(response, '', headerIngestLimit, headerIngestRemaining, headerIngestReset); 45 | limit.type = LimitType.ingest; 46 | } else if (response.config.url?.endsWith('/query') || response.config.url?.endsWith('/_apl')) { 47 | limit = parseLimitFromHeaders(response, '', headerQueryLimit, headerQueryRemaining, headerQueryReset); 48 | limit.type = LimitType.query; 49 | } else { 50 | limit = parseLimitFromHeaders( 51 | response, 52 | headerRateScope, 53 | headerAPILimit, 54 | headerAPIRateRemaining, 55 | headerAPIRateReset, 56 | ); 57 | limit.type = LimitType.api; 58 | } 59 | 60 | return limit; 61 | } 62 | 63 | export const limitKey = (type: LimitType, scope: LimitScope): string => `${type}:${scope}`; 64 | 65 | // parseLimitFromHeaders parses the named headers from a `*http.Response`. 66 | function parseLimitFromHeaders( 67 | response: AxiosResponse, 68 | headerScope: string, 69 | headerLimit: string, 70 | headerRemaining: string, 71 | headerReset: string, 72 | ): Limit { 73 | let limit: Limit = new Limit(); 74 | 75 | const scope: string = response.headers[headerScope.toLowerCase()] || LimitScope.unknown; 76 | limit.scope = LimitScope[scope as keyof typeof LimitScope]; 77 | 78 | const limitValue = response.headers[headerLimit.toLowerCase()] || ''; 79 | const limitValueNumber = parseInt(limitValue, 10); 80 | if (!isNaN(limitValueNumber)) { 81 | limit.value = limitValueNumber; 82 | } 83 | 84 | const remainingValue = response.headers[headerRemaining.toLowerCase()] || ''; 85 | const remainingValueNumber = parseInt(remainingValue, 10); 86 | if (!isNaN(remainingValueNumber)) { 87 | limit.remaining = remainingValueNumber; 88 | } 89 | 90 | const resetValue = response.headers[headerReset.toLowerCase()] || ''; 91 | const resetValueInt = parseInt(resetValue, 10); 92 | if (!isNaN(resetValueInt)) { 93 | limit.reset = new Date(resetValueInt * 1000); 94 | } 95 | 96 | return limit; 97 | } 98 | -------------------------------------------------------------------------------- /lib/logger.ts: -------------------------------------------------------------------------------- 1 | import Transport, { TransportStreamOptions } from 'winston-transport'; 2 | 3 | import { Client } from './client'; 4 | 5 | export interface WinstonOptions extends TransportStreamOptions { 6 | dataset?: string; 7 | token?: string; 8 | orgId?: string; 9 | url?: string; 10 | } 11 | 12 | export class WinstonTransport extends Transport { 13 | client: Client; 14 | dataset: string; 15 | batch: object[] = []; 16 | batchCallback: (err: Error | null) => void = () => {}; 17 | batchTimeoutId?: NodeJS.Timeout; 18 | 19 | constructor(opts?: WinstonOptions) { 20 | super(opts); 21 | this.client = new Client(opts); 22 | this.dataset = opts?.dataset || process.env.AXIOM_DATASET || ''; 23 | } 24 | 25 | log(info: any, callback: () => void) { 26 | this.request(info, (err) => { 27 | if (err) { 28 | this.emit('error', err); 29 | } else { 30 | this.emit('logged', info); 31 | } 32 | }); 33 | 34 | if (callback) { 35 | setImmediate(callback); 36 | } 37 | } 38 | 39 | private request(info: any, callback: (err: Error | null) => void) { 40 | if (!info._time) { 41 | info._time = new Date().toISOString(); 42 | } 43 | this.batch.push(info); 44 | 45 | if (this.batch.length == 1) { 46 | this.batchCallback = callback; 47 | this.batchTimeoutId = setTimeout(() => { 48 | this.flush(); 49 | }, 1000); 50 | callback(null); 51 | } else if (this.batch.length >= 1000) { 52 | this.flush(callback); 53 | } 54 | } 55 | 56 | private flush(callback: (err: Error | null) => void = () => {}) { 57 | const batchCopy = this.batch.slice(); 58 | 59 | clearTimeout(this.batchTimeoutId); 60 | this.batchTimeoutId = undefined; 61 | this.batchCallback = () => {}; 62 | this.batch = []; 63 | 64 | this.client 65 | .ingestEvents(this.dataset, batchCopy) 66 | .then((_res) => callback(null)) 67 | .catch(callback); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/users.ts: -------------------------------------------------------------------------------- 1 | import HTTPClient from './httpClient'; 2 | 3 | export namespace users { 4 | export interface User { 5 | id: string; 6 | name: string; 7 | emails: Array; 8 | } 9 | 10 | export class Service extends HTTPClient { 11 | private readonly localPath = '/v1/users'; 12 | 13 | current = (): Promise => 14 | this.client.get('/v1/user').then((response) => { 15 | return response.data; 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@axiomhq/axiom-node", 3 | "description": "The official NodeJS bindings for the Axiom API", 4 | "version": "0.12.0", 5 | "author": "Axiom, Inc.", 6 | "license": "MIT", 7 | "contributors": [ 8 | "Lukas Malkmus ", 9 | "Islam Schehata ", 10 | "Arne Bahlo " 11 | ], 12 | "engines": { 13 | "node": ">=14" 14 | }, 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "scripts": { 18 | "build": "tsc", 19 | "format": "eslint '*/**/*.{js,ts}' --quiet --fix", 20 | "lint": "eslint '*/**/*.{js,ts}'", 21 | "prepublish": "npm run build", 22 | "test": "jest tests/unit", 23 | "cover": "nyc -r text -e .ts -x 'tests/unit/**/*.test.ts' npm run test", 24 | "integration": "jest tests/integration --testTimeout=10000" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/axiomhq/axiom-node.git" 29 | }, 30 | "keywords": [ 31 | "axiom", 32 | "api", 33 | "rest", 34 | "client" 35 | ], 36 | "bugs": { 37 | "url": "https://github.com/axiomhq/axiom-node/issues" 38 | }, 39 | "homepage": "https://github.com/axiomhq/axiom-node", 40 | "dependencies": { 41 | "axios": "^1.2.0", 42 | "axios-retry": "^3.3.1", 43 | "to-time": "^1.0.2", 44 | "winston-transport": "^4.5.0" 45 | }, 46 | "devDependencies": { 47 | "@jest/globals": "^29.3.1", 48 | "@types/jest": "^27.5.2", 49 | "@types/node": "^20.3.0", 50 | "@typescript-eslint/eslint-plugin": "^5.27.1", 51 | "@typescript-eslint/parser": "^5.27.1", 52 | "jest": "^29.0.0", 53 | "jest-environment-jsdom": "^29.5.0", 54 | "eslint": "^8.17.0", 55 | "eslint-config-prettier": "^8.5.0", 56 | "eslint-plugin-prettier": "^4.0.0", 57 | "nock": "^13.2.6", 58 | "nyc": "^15.1.0", 59 | "prettier": "^2.6.2", 60 | "ts-jest": "^29.0.5", 61 | "ts-node": "^10.8.1", 62 | "typescript": "^4.7.3", 63 | "winston": "^3.8.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let pkgs = import {}; 2 | in pkgs.mkShell { 3 | nativeBuildInputs = (with pkgs; [ 4 | nodejs 5 | ]); 6 | } 7 | -------------------------------------------------------------------------------- /tests/integration/client.test.ts: -------------------------------------------------------------------------------- 1 | import { gzip } from 'zlib'; 2 | 3 | import { Client, ContentType, ContentEncoding } from '../../lib/client'; 4 | 5 | const datasetSuffix = process.env.AXIOM_DATASET_SUFFIX || 'local'; 6 | 7 | describe('Client', () => { 8 | const datasetName = `test-axiom-node-dataset-${datasetSuffix}`; 9 | const client = new Client(); 10 | 11 | beforeAll(async () => { 12 | await client.datasets.create({ 13 | name: datasetName, 14 | description: 'This is a test dataset for datasets integration tests.', 15 | }); 16 | }); 17 | 18 | afterAll(async () => { 19 | const resp = await client.datasets.delete(datasetName); 20 | expect(resp.status).toEqual(204); 21 | }); 22 | 23 | describe('ingest', () => { 24 | it('works with a JSON payload', async () => { 25 | const status = await client.ingestString( 26 | datasetName, 27 | `[{"foo":"bar"},{"bar":"baz"}]`, 28 | ContentType.JSON, 29 | ContentEncoding.Identity, 30 | ); 31 | 32 | expect(status.ingested).toEqual(2); 33 | expect(status.failures?.length).toEqual(0); 34 | }); 35 | 36 | it('works with a NDJSON payload', async () => { 37 | const status = await client.ingestString( 38 | datasetName, 39 | `{"foo":"bar"} 40 | {"bar":"baz"}`, 41 | ContentType.NDJSON, 42 | ContentEncoding.Identity, 43 | ); 44 | 45 | expect(status.ingested).toEqual(2); 46 | expect(status.failures?.length).toEqual(0); 47 | }); 48 | 49 | it('works with a CSV payload', async () => { 50 | const status = await client.ingestString( 51 | datasetName, 52 | `foo 53 | bar 54 | baz`, 55 | ContentType.CSV, 56 | ContentEncoding.Identity, 57 | ); 58 | 59 | expect(status.ingested).toEqual(2); 60 | expect(status.failures?.length).toEqual(0); 61 | }); 62 | 63 | it('works with gzip', async () => { 64 | const encoded: Buffer = await new Promise((resolve, reject) => { 65 | gzip(`[{"foo":"bar"},{"bar":"baz"}]`, (err: Error | null, content: Buffer) => { 66 | if (err) reject(err); 67 | else resolve(content); 68 | }); 69 | }); 70 | 71 | const status = await client.ingestBuffer(datasetName, encoded, ContentType.JSON, ContentEncoding.GZIP); 72 | 73 | expect(status.ingested).toEqual(2); 74 | expect(status.failures?.length).toEqual(0); 75 | }); 76 | 77 | it('works with single event', async () => { 78 | const status = await client.ingestEvents(datasetName, { foo: 'bar' }); 79 | expect(status.ingested).toEqual(1); 80 | expect(status.failures?.length).toEqual(0); 81 | }); 82 | 83 | it('works with two events', async () => { 84 | const status = await client.ingestEvents(datasetName, [{ foo: 'bar' }, { bar: 'baz' }]); 85 | expect(status.ingested).toEqual(2); 86 | expect(status.failures?.length).toEqual(0); 87 | }); 88 | }); 89 | 90 | describe('query', () => { 91 | it('returns a valid response', async () => { 92 | const result = await client.queryLegacy(datasetName, { 93 | startTime: '2018-01-01T00:00:00.000Z', 94 | endTime: '2028-01-01T00:00:00.000Z', 95 | resolution: 'auto', 96 | }); 97 | 98 | // expect(result.status.blocksExamined).toEqual(1); 99 | expect(result.status.rowsExamined).toEqual(11); 100 | expect(result.status.rowsMatched).toEqual(11); 101 | expect(result.matches?.length).toEqual(11); 102 | }); 103 | }); 104 | 105 | describe('apl query', () => { 106 | it('returns a valid response', async () => { 107 | const result = await client.query("['" + datasetName + "']"); 108 | 109 | // expect(result.status.blocksExamined).toEqual(1); 110 | expect(result.status.rowsExamined).toEqual(11); 111 | expect(result.status.rowsMatched).toEqual(11); 112 | expect(result.matches?.length).toEqual(11); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /tests/integration/datasets.test.ts: -------------------------------------------------------------------------------- 1 | import { datasets } from '../../lib/datasets'; 2 | 3 | const datasetSuffix = process.env.AXIOM_DATASET_SUFFIX || 'local'; 4 | 5 | describe('DatasetsService', () => { 6 | const datasetName = `test-axiom-node-dataset-${datasetSuffix}`; 7 | const client = new datasets.Service(); 8 | 9 | beforeAll(async () => { 10 | await client.create({ 11 | name: datasetName, 12 | description: 'This is a test dataset for datasets integration tests.', 13 | }); 14 | }); 15 | 16 | afterAll(async () => { 17 | const resp = await client.delete(datasetName); 18 | expect(resp.status).toEqual(204); 19 | }); 20 | 21 | describe('update', () => { 22 | it('should update the dataset', async () => { 23 | const dataset = await client.update(datasetName, { 24 | description: 'This is a soon to be filled test dataset', 25 | }); 26 | 27 | expect(dataset.description).toEqual('This is a soon to be filled test dataset'); 28 | }); 29 | }); 30 | 31 | describe('get', () => { 32 | it('should get the dataset', async () => { 33 | const dataset = await client.get(datasetName); 34 | 35 | expect(dataset.name).toEqual(datasetName); 36 | }); 37 | }); 38 | 39 | describe('list', () => { 40 | it('should list the datasets', async () => { 41 | const datasets = await client.list(); 42 | 43 | expect(datasets.length).toBeGreaterThan(0); 44 | }); 45 | }); 46 | 47 | describe('trim', () => { 48 | it('returns a valid response', async () => { 49 | const result = await client.trim(datasetName, '1s'); 50 | 51 | expect(result).not.toEqual(null); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tests/integration/winston.test.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | 3 | import { Client } from '../../lib/client'; 4 | import { WinstonTransport as AxiomTransport } from '../../lib/logger'; 5 | 6 | const datasetSuffix = process.env.AXIOM_DATASET_SUFFIX || 'local'; 7 | 8 | describe('WinstonTransport', () => { 9 | const datasetName = `test-axiom-node-winston-${datasetSuffix}`; 10 | const client = new Client(); 11 | const logger = winston.createLogger({ 12 | level: 'info', 13 | format: winston.format.json(), 14 | defaultMeta: { service: 'user-service' }, 15 | transports: [new AxiomTransport({ dataset: datasetName })], 16 | }); 17 | 18 | beforeAll(async () => { 19 | await client.datasets.create({ 20 | name: datasetName, 21 | description: 'This is a test dataset for datasets integration tests.', 22 | }); 23 | }); 24 | 25 | afterAll(async () => { 26 | const resp = await client.datasets.delete(datasetName); 27 | expect(resp.status).toEqual(204); 28 | }); 29 | 30 | it('sends logs to Axiom', async () => { 31 | logger.log({ 32 | level: 'info', 33 | message: 'Hello from winston', 34 | }); 35 | 36 | // Wait for the log to be sent 37 | await new Promise((resolve) => setTimeout(resolve, 1500)); 38 | 39 | const startTime = new Date(new Date().getTime() - 1000 * 60 * 60 * 24).toISOString(); 40 | const endTime = new Date(new Date().getTime() + 1000 * 60 * 60 * 24).toISOString(); 41 | 42 | // const res = await client.datasets.query(`['${datasetName}']`, { 43 | // startTime, endTime, streamingDuration: 'auto', noCache: false, 44 | // }); 45 | 46 | const res = await client.queryLegacy(datasetName, { 47 | resolution: 'auto', 48 | startTime, 49 | endTime, 50 | }); 51 | expect(res.matches).toHaveLength(1); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /tests/unit/client.test.ts: -------------------------------------------------------------------------------- 1 | import { fail } from 'assert'; 2 | import nock from 'nock'; 3 | 4 | import { Client, ContentType, ContentEncoding } from '../../lib/client'; 5 | import { AxiomTooManyRequestsError } from '../../lib/httpClient'; 6 | import { headerAPILimit, headerAPIRateRemaining, headerAPIRateReset, headerRateScope } from '../../lib/limit'; 7 | 8 | const queryLegacyResult = { 9 | status: { 10 | elapsedTime: 542114, 11 | blocksExamined: 4, 12 | rowsExamined: 142655, 13 | rowsMatched: 142655, 14 | numGroups: 0, 15 | isPartial: false, 16 | cacheStatus: 1, 17 | minBlockTime: '2020-11-19T11:06:31.569475746Z', 18 | maxBlockTime: '2020-11-27T12:06:38.966791794Z', 19 | }, 20 | matches: [ 21 | { 22 | _time: '2020-11-19T11:06:31.569475746Z', 23 | _sysTime: '2020-11-19T11:06:31.581384524Z', 24 | _rowId: 'c776x1uafkpu-4918f6cb9000095-0', 25 | data: { 26 | agent: 'Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)', 27 | bytes: 0, 28 | referrer: '-', 29 | remote_ip: '93.180.71.3', 30 | remote_user: '-', 31 | request: 'GET /downloads/product_1 HTTP/1.1', 32 | response: 304, 33 | time: '17/May/2015:08:05:32 +0000', 34 | }, 35 | }, 36 | { 37 | _time: '2020-11-19T11:06:31.569479846Z', 38 | _sysTime: '2020-11-19T11:06:31.581384524Z', 39 | _rowId: 'c776x1uafnvq-4918f6cb9000095-1', 40 | data: { 41 | agent: 'Debian APT-HTTP/1.3 (0.8.16~exp12ubuntu10.21)', 42 | bytes: 0, 43 | referrer: '-', 44 | remote_ip: '93.180.71.3', 45 | remote_user: '-', 46 | request: 'GET /downloads/product_1 HTTP/1.1', 47 | response: 304, 48 | time: '17/May/2015:08:05:23 +0000', 49 | }, 50 | }, 51 | ], 52 | buckets: { 53 | series: [], 54 | totals: [], 55 | }, 56 | }; 57 | 58 | const queryResult = { 59 | request: { 60 | startTime: '2020-11-19T11:06:31.569475746Z', 61 | endTime: '2020-11-27T12:06:38.966791794Z', 62 | resolution: 'auto', 63 | }, 64 | ...queryLegacyResult, 65 | }; 66 | 67 | const clientURL = 'http://axiom-node-retries.dev.local'; 68 | 69 | describe('Client', () => { 70 | let client = new Client({ url: clientURL }); 71 | expect(client).toBeDefined(); 72 | 73 | beforeEach(() => { 74 | // reset client to clear rate limits 75 | client = new Client({ url: clientURL }); 76 | }); 77 | 78 | it('Services', () => { 79 | expect(client.datasets).toBeTruthy(); 80 | expect(client.users).toBeTruthy(); 81 | }); 82 | 83 | it('Retries failed 5xx requests', async () => { 84 | const scope = nock(clientURL); 85 | scope.get('/v1/datasets').reply(500, 'internal server error'); 86 | scope.get('/v1/datasets').reply(500, 'internal server error'); 87 | scope.get('/v1/datasets').reply(200, [{ name: 'test' }]); 88 | 89 | const resp = await client.datasets.list(); 90 | expect(scope.isDone()).toEqual(true); 91 | expect(resp.length).toEqual(1); 92 | }); 93 | 94 | it('Does not retry failed requests < 500', async () => { 95 | const scope = nock(clientURL); 96 | scope.get('/v1/datasets').reply(401, 'Forbidden'); 97 | scope.get('/v1/datasets').reply(200, [{ name: 'test' }]); 98 | 99 | try { 100 | await client.datasets.list(); 101 | fail('response should fail and return 401'); 102 | } catch (err: any) { 103 | expect(err.response.status).toEqual(401); 104 | expect(err.response.data).toEqual('Forbidden'); 105 | // Scope is not done means that not all scope mocks has been consumed 106 | expect(scope.isDone()).toEqual(false); 107 | } 108 | 109 | // create another request to ensure that 110 | // the nock scope was not consumed before 111 | const resp = await client.datasets.list(); 112 | expect(scope.isDone()).toEqual(true); 113 | expect(resp.length).toEqual(1); 114 | }); 115 | 116 | it('No shortcircuit for ingest or query when there is api rate limit', async () => { 117 | const scope = nock(clientURL); 118 | const resetTimeInSeconds = Math.floor(new Date().getTime() / 1000); 119 | const headers: nock.ReplyHeaders = {}; 120 | headers[headerRateScope] = 'anonymous'; 121 | headers[headerAPILimit] = '1000'; 122 | headers[headerAPIRateRemaining] = '0'; 123 | headers[headerAPIRateReset] = resetTimeInSeconds.toString(); 124 | scope.get('/v1/datasets').reply(429, 'Too Many Requests', headers); 125 | scope.post('/v1/datasets/test/ingest').reply(200, {}, headers); 126 | scope.post('/v1/datasets/_apl?format=legacy').reply(200, {}, headers); 127 | 128 | // first api call should fail 129 | try { 130 | await client.datasets.list(); 131 | fail('request should return an error with status 429'); 132 | } catch (err: any) { 133 | expect(err instanceof AxiomTooManyRequestsError).toEqual(true); 134 | } 135 | 136 | // ingest and query should succeed 137 | await client.ingestString( 138 | 'test', 139 | JSON.stringify([{ name: 'test' }]), 140 | ContentType.JSON, 141 | ContentEncoding.Identity, 142 | ); 143 | 144 | await client.query("['test']"); 145 | }); 146 | 147 | it('IngestString', async () => { 148 | const scope = nock(clientURL); 149 | const ingestStatus = { 150 | ingested: 2, 151 | failed: 0, 152 | failures: [], 153 | processedBytes: 630, 154 | blocksCreated: 0, 155 | walLength: 2, 156 | }; 157 | scope.post('/v1/datasets/test/ingest').reply(function (_, body, cb) { 158 | expect(this.req.headers).toHaveProperty('content-type'); 159 | expect(body).toMatchObject([{ foo: 'bar' }, { foo: 'baz' }]); 160 | 161 | cb(null, [200, ingestStatus]); 162 | }); 163 | 164 | const data = `[{"foo": "bar"}, {"foo": "baz"}]`; 165 | const response = await client.ingestString('test', data, ContentType.JSON, ContentEncoding.Identity); 166 | expect(response).not.toEqual('undefined'); 167 | expect(response.ingested).toEqual(2); 168 | expect(response.failed).toEqual(0); 169 | }); 170 | 171 | it('Query', async () => { 172 | const scope = nock(clientURL); 173 | scope.post('/v1/datasets/test/query').reply(200, queryLegacyResult); 174 | scope.post('/v1/datasets/test/query?streaming-duration=1m&nocache=true').reply(200, queryLegacyResult); 175 | // works without options 176 | let query = { 177 | startTime: '2020-11-26T11:18:00Z', 178 | endTime: '2020-11-17T11:18:00Z', 179 | resolution: 'auto', 180 | }; 181 | let response = await client.queryLegacy('test', query); 182 | expect(response).not.toEqual('undefined'); 183 | expect(response.matches).toHaveLength(2); 184 | 185 | // works with options 186 | query = { 187 | startTime: '2020-11-26T11:18:00Z', 188 | endTime: '2020-11-17T11:18:00Z', 189 | resolution: 'auto', 190 | }; 191 | const options = { 192 | streamingDuration: '1m', 193 | noCache: true, 194 | }; 195 | response = await client.queryLegacy('test', query, options); 196 | expect(response).not.toEqual('undefined'); 197 | expect(response.matches).toHaveLength(2); 198 | }); 199 | 200 | it('APL Query', async () => { 201 | const scope = nock(clientURL); 202 | scope.post('/v1/datasets/_apl?format=legacy').reply(200, queryResult); 203 | scope.post('/v1/datasets/_apl?streaming-duration=1m&nocache=true&format=legacy').reply(200, queryResult); 204 | // works without options 205 | let response = await client.query("['test'] | where response == 304"); 206 | expect(response).not.toEqual('undefined'); 207 | expect(response.matches).toHaveLength(2); 208 | 209 | // works with options 210 | const options = { 211 | streamingDuration: '1m', 212 | noCache: true, 213 | }; 214 | response = await client.query("['test'] | where response == 304", options); 215 | expect(response).not.toEqual('undefined'); 216 | expect(response.matches).toHaveLength(2); 217 | }); 218 | }); 219 | -------------------------------------------------------------------------------- /tests/unit/datasets.test.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | 3 | import { datasets } from '../../lib/datasets'; 4 | 5 | describe('DatasetsService', () => { 6 | const client = new datasets.Service({ url: 'http://axiom-node.dev.local' }); 7 | 8 | beforeEach(() => { 9 | const datasets = [ 10 | { 11 | id: 'test', 12 | name: 'test', 13 | description: 'Test dataset', 14 | who: 'f83e245a-afdc-47ad-a765-4addd1994333', 15 | created: '2020-11-17T22:29:00.521238198Z', 16 | }, 17 | { 18 | id: 'test1', 19 | name: 'test1', 20 | description: 'This is a test description', 21 | who: 'f83e245a-afdc-47ad-a765-4addd1994333', 22 | created: '2020-11-17T22:29:00.521238198Z', 23 | }, 24 | ]; 25 | 26 | const scope = nock('http://axiom-node.dev.local'); 27 | 28 | scope.get('/v1/datasets').reply(200, datasets); 29 | scope.get('/v1/datasets/test').reply(200, datasets[0]); 30 | scope.post('/v1/datasets').reply(200, datasets[1]); 31 | scope.put('/v1/datasets/test1').reply(200, datasets[1]); 32 | scope.delete('/v1/datasets/test1').reply(204); 33 | scope.post('/v1/datasets/test1/trim').reply(200, {}); 34 | }); 35 | 36 | it('List', async () => { 37 | const response = await client.list(); 38 | expect(response).not.toEqual('undefined'); 39 | expect(response).toHaveLength(2); 40 | }); 41 | 42 | it('Get', async () => { 43 | const response = await client.get('test'); 44 | expect(response).not.toEqual('undefined'); 45 | expect(response.id).toEqual('test'); 46 | expect(response.description).toEqual('Test dataset'); 47 | }); 48 | 49 | it('Create', async () => { 50 | const request: datasets.CreateRequest = { 51 | name: 'test1', 52 | description: 'This is a test description', 53 | }; 54 | 55 | const response = await client.create(request); 56 | expect(response).not.toEqual('undefined'); 57 | expect(response.id).toEqual('test1'); 58 | expect(response.description).toEqual('This is a test description'); 59 | }); 60 | 61 | it('Update', async () => { 62 | const req: datasets.UpdateRequest = { 63 | description: 'This is a test description', 64 | }; 65 | 66 | const response = await client.update('test1', req); 67 | expect(response).not.toEqual('undefined'); 68 | expect(response.id).toEqual('test1'); 69 | expect(response.description).toEqual('This is a test description'); 70 | }); 71 | 72 | it('Delete', async () => { 73 | const response = await client.delete('test1'); 74 | expect(response).not.toEqual('undefined'); 75 | expect(response.status).toEqual(204); 76 | }); 77 | 78 | it('Trim', async () => { 79 | const response = await client.trim('test1', '30m'); 80 | expect(response).not.toEqual('undefined'); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /tests/unit/users.test.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | 3 | import { users } from '../../lib/users'; 4 | 5 | describe('UsersService', () => { 6 | const client = new users.Service({ url: 'http://axiom-node.dev.local' }); 7 | 8 | beforeEach(() => { 9 | const currentUser = { 10 | id: 'e9cffaad-60e7-4b04-8d27-185e1808c38c', 11 | name: 'Lukas Malkmus', 12 | emails: ['lukas@axiom.co'], 13 | }; 14 | 15 | const users = [ 16 | { 17 | id: '20475220-20e4-4080-b2f4-68315e21f5ec', 18 | name: 'John Doe', 19 | email: 'john@example.com', 20 | role: 'owner', 21 | permissions: [], 22 | }, 23 | ]; 24 | 25 | const scope = nock('http://axiom-node.dev.local'); 26 | 27 | scope.get('/v1/user').reply(200, currentUser); 28 | scope.get('/v1/users/20475220-20e4-4080-b2f4-68315e21f5ec').reply(200, users[0]); 29 | }); 30 | 31 | it('Current', async () => { 32 | const response = await client.current(); 33 | expect(response).not.toEqual('undefined'); 34 | expect(response.name).toEqual('Lukas Malkmus'); 35 | expect(response.emails).toHaveLength(1); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "baseUrl": "lib", 6 | "moduleResolution": "node", 7 | "outDir": "./dist", 8 | "declaration": true, 9 | "strict": true, 10 | "esModuleInterop": true 11 | }, 12 | "exclude": [ 13 | ".git", 14 | ".github", 15 | "node_modules" 16 | ], 17 | "include": [ 18 | "lib/**/*" 19 | ] 20 | } --------------------------------------------------------------------------------