├── .babelrc ├── .editorconfig ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── lib ├── AggRoot.js ├── EventFactory.js ├── httpClient │ ├── admin │ │ ├── sendScavengeCommand.js │ │ └── sendShutdownCommand.js │ ├── checkStreamExists.js │ ├── deleteStream.js │ ├── getAllStreamEvents.js │ ├── getEvents.js │ ├── getEventsByType.js │ ├── index.js │ ├── persistentSubscriptions │ │ ├── assert.js │ │ ├── getAllSubscriptionsInfo.js │ │ ├── getEvents.js │ │ ├── getStreamSubscriptionsInfo.js │ │ ├── getSubscriptionInfo.js │ │ └── remove.js │ ├── ping.js │ ├── projections │ │ ├── assert.js │ │ ├── config.js │ │ ├── disableAll.js │ │ ├── enableAll.js │ │ ├── getAllProjectionsInfo.js │ │ ├── getInfo.js │ │ ├── getResult.js │ │ ├── getState.js │ │ ├── remove.js │ │ ├── reset.js │ │ ├── start.js │ │ └── stop.js │ ├── readEvents.js │ ├── utilities │ │ └── mapEvents.js │ ├── writeEvent.js │ └── writeEvents.js ├── index.js ├── tcpClient │ ├── checkStreamExists.js │ ├── connectionManager.js │ ├── deleteStream.js │ ├── eventEnumerator.js │ ├── getAllStreamEvents.js │ ├── getEvents.js │ ├── getEventsByType.js │ ├── index.js │ ├── readEvents.js │ ├── subscribeToStream.js │ ├── subscribeToStreamFrom.js │ ├── utilities │ │ └── mapEvents.js │ ├── writeEvent.js │ └── writeEvents.js └── utilities │ ├── chunkArray.js │ ├── createHttpClient.js │ ├── flattenArray.js │ └── generateEventId.js ├── package.json ├── tests ├── _globalHooks.js ├── http.checkStreamExists.js ├── http.config.js ├── http.deleteStream.js ├── http.getAllStreamEvents.js ├── http.getEvents.js ├── http.persistentSubscriptions.js ├── http.ping.js ├── http.projections.js ├── http.readEvents.js ├── http.sendScavengeCommand.js ├── http.writeEvent.js ├── http.writeEvents.js ├── support │ ├── cluster │ │ ├── docker-compose-insecure.yml │ │ ├── docker-compose-secure.yml │ │ └── vars.env │ ├── getHttpConfig.js │ ├── getTcpConfig.js │ ├── getTcpConfigCustomConnectionName.js │ ├── getTcpConfigDNSDiscoveryCluster.js │ ├── getTcpConfigGossipCluster.js │ ├── single │ │ ├── docker-compose-insecure.yml │ │ └── docker-compose-secure.yml │ ├── testPartitionedProjection.js │ └── testProjection.js ├── tcp.checkStreamExists.js ├── tcp.cluster.js ├── tcp.config.js ├── tcp.connection.js ├── tcp.deleteStream.js ├── tcp.eventEnumerator.js ├── tcp.getAllStreamEvents.js ├── tcp.getEvents.js ├── tcp.readEvents.js ├── tcp.stressTests.js ├── tcp.subscribeToStream.js ├── tcp.subscribeToStreamFrom.js ├── tcp.writeEvent.js ├── tcp.writeEvents.js └── utilities │ └── sleep.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": ["@babel/plugin-transform-runtime", "add-module-exports"] 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 4 5 | indent_style = tab -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # tgz 6 | *.tgz 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | #nyc output 33 | .nyc_output 34 | 35 | # Distribution 36 | **/dist/* 37 | 38 | # Tests 39 | **/certs/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/*.tgz 3 | **/*.lock 4 | node_modules/ 5 | tests/ 6 | coverage/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 4.0.1 (2021-09-28) 2 | 3 | ## TCP 4 | 5 | - Fix - handle closed connection when `closed` event never emitted 6 | 7 | #### Dependencies 8 | 9 | - Update packages 10 | 11 | # 4.0.0 (2021-05-22) 12 | 13 | #### Features 14 | 15 | - Added support for `v20` and `v21` 16 | - Added support for secured EventStoreDB clusters and single instances 17 | 18 | #### Breaking Changes 19 | 20 | - Removed deprecated legacy instance creation: `geteventstore.tcp(config)`, `geteventstore.http(config)` and `geteventstore.eventFactory` 21 | 22 | ## TCP 23 | 24 | ##### Features 25 | 26 | - Added `connectionNameGenerator` function to config that allows custom TCP connection names 27 | 28 | ## Tests 29 | 30 | - Tests now solely uses docker 31 | - Added `test:secure` and `test:insecure` scripts 32 | - `test` will now run all tests with secured and insecure EventStoreDB's 33 | 34 | # 3.3.0 (2021-02-22) 35 | 36 | ## TCP 37 | 38 | #### Changes 39 | 40 | - Destroy connection on connection close 41 | - Subscription connection pools now tracked uniquely 42 | - `close` connection pool will now drain pool first 43 | 44 | #### Fixes 45 | 46 | - `subscribeToStream` - `close` will now release and close subscription connection pool 47 | - `subscribeToStreamFrom` - added `close` function that will release and close subscription connection pool 48 | 49 | #### Dependencies 50 | 51 | - Update packages 52 | 53 | # 3.2.5 (2021-01-29) 54 | 55 | #### Dependencies 56 | 57 | - Update packages 58 | 59 | # 3.2.4 (2020-11-04) 60 | 61 | #### Fix 62 | 63 | - projections - `assert`, `trackEmittedStreams` setter. Thanks [@maniolias](https://github.com/maniolias) 64 | 65 | # 3.2.3 (2020-10-20) 66 | 67 | - projections - `config` query added. Thanks [@maniolias](https://github.com/maniolias) 68 | - projections - `getInfo`, `includeConfig` in result set added. Thanks [@maniolias](https://github.com/maniolias) 69 | - projections - `assert`, `trackEmittedStreams` added. Thanks [@maniolias](https://github.com/maniolias) 70 | 71 | # 3.2.2 (2020-10-05) 72 | 73 | #### Fix 74 | 75 | - persistentSubscriptions.assert - `resolveLinkTos` not applying due to typo. Thanks [@maniolias](https://github.com/maniolias) 76 | - persistentSubscriptions.getEvents - 401 due to missing auth headers. Thanks [@maniolias](https://github.com/maniolias) 77 | 78 | #### Dependencies 79 | 80 | - Update packages 81 | 82 | # 3.2.1 (2020-02-27) 83 | 84 | #### Dependencies 85 | 86 | - Update packages 87 | 88 | # 3.2.0 (2019-10-07) 89 | 90 | #### Breaking Changes 91 | 92 | - subscriptions - onEventAppeared aligned with `node-eventstore-client`, previously: `onEventAppeared(ev)` now: `onEventAppeared(subscription, ev)`. Thanks [@adebisi-fa](https://github.com/adebisi-fa) 93 | 94 | #### Features 95 | 96 | - projections - `result` query added. Thanks [@set4812](https://github.com/set4812) 97 | 98 | # 3.1.3 (2019-09-09) 99 | 100 | #### Fix 101 | 102 | - Typings - Metadata and eventId is optional. Thanks [@spontoreau](https://github.com/spontoreau) 103 | 104 | #### Dependencies 105 | 106 | - Update packages 107 | 108 | # 3.1.2 (2019-07-29) 109 | 110 | #### Misc 111 | 112 | - Typings - Inherit base TCP config from 'node-eventstore-client'. Thanks [@adebisi-fa](https://github.com/adebisi-fa) 113 | 114 | #### Dependencies 115 | 116 | - Update packages 117 | 118 | # 3.1.1 (2019-02-14) 119 | 120 | #### Fix 121 | 122 | - TCPReadResult typescript definition 123 | 124 | # 3.1.0 (2019-02-07) 125 | 126 | #### Features 127 | 128 | - Add readEventsForward and readEventsBackward returning read metadata + events 129 | 130 | #### Misc 131 | 132 | - Rename "length" parameter to "count" 133 | 134 | # 3.0.3 (2019-02-06) 135 | 136 | #### TCP Client 137 | 138 | - Fix - mapping of non-json events 139 | 140 | # 3.0.2 (2019-01-02) 141 | 142 | #### Fix 143 | 144 | - Expected version on writes defaulting to -2 when 0 provided. Thanks [@amaghfur](https://github.com/amaghfur) 145 | 146 | #### Dependencies 147 | 148 | - Update packages 149 | 150 | #### Misc 151 | 152 | - Change folder structure 153 | 154 | # 3.0.1 (2018-09-17) 155 | 156 | #### Misc 157 | 158 | - Fix - General Typescript definition issues 159 | 160 | # 3.0.0 (2018-09-13) 161 | 162 | #### Features 163 | 164 | - Typescript definitions added 165 | - Package exports now exposed as classes 166 | 167 | ##### Previous Usage (Deprecated) 168 | ```javascript 169 | const eventstore = require('geteventstore-promise'); 170 | const httpClient = eventstore.http(...config); 171 | const tcpClient = eventstore.tcp(...config); 172 | const newEvent = eventstore.eventFactory.NewEvent(...args); 173 | ``` 174 | 175 | ##### New Usage 176 | ```javascript 177 | const EventStore = require('geteventstore-promise'); 178 | const httpClient = new EventStore.HTTPClient(...config); 179 | const tcpClient = new EventStore.TCPClient(...config); 180 | const newEvent = new EventStore.EventFactory().newEvent(...args); 181 | ``` 182 | 183 | #### Dependencies 184 | 185 | - Remove - bluebird 186 | - Remove - lodash 187 | - Replace - request-promise with axios 188 | 189 | #### Breaking Changes 190 | 191 | ##### General 192 | 193 | - Promises - '.finally()' will not be available anymore due to the removal of bluebird 194 | 195 | ##### HTTP Client 196 | 197 | - Errors returned from HTTP calls might differ slightly from removed request-promise package vs the new axios implementation 198 | 199 | # 2.0.2 (2018-09-11) 200 | 201 | #### TCP Client 202 | 203 | - Feature - Add support for connecting to a cluster using gossip seeds or dns discovery (https://github.com/RemoteMetering/geteventstore-promise#config-example) 204 | 205 | #### Misc 206 | 207 | - Update dependencies 208 | 209 | # 2.0.1 (2018-06-04) 210 | 211 | #### TCP Client 212 | 213 | - Fix - edge case when eventNumber is not coming back as a long 214 | 215 | # 2.0.0 (2018-06-02) 216 | 217 | #### TCP Client 218 | 219 | - Feature - Implemented connection pooling(defaulting to 1 connection) using [https://github.com/coopernurse/node-pool](https://github.com/coopernurse/node-pool), please see config in library and pass config as "poolOptions" when initing TCP client.
Example: ` { ..., poolOptions: { min: 1, max: 10 } } ` 220 | 221 | - Change - subscriptions now use [https://github.com/nicdex/node-eventstore-client](https://github.com/nicdex/node-eventstore-client) for subscriptions - Causes breaking changes 222 | 223 | #### Breaking Changes 224 | 225 | #### TCP Client 226 | 227 | - Replacement - 'closeConnections' with 'close', which will close connection pool 228 | - Subscriptions - now return subscription object from tcp library instead of connection 229 | - Subscriptions - now return events in same format as normal getEvents 230 | - Subscriptions - onDropped arguments -> onDropped(subscription, reason, error) 231 | - subscribeToStream - no longer has "onConfirm" handler 232 | 233 | # 1.4.0 (2018-05-29) 234 | 235 | #### TCP Client 236 | 237 | - "created" property on read events will now return as a ISO-8601 string instead of date object 238 | 239 | #### Breaking Changes 240 | 241 | - TCP: To bring both HTTP and TCP read events results inline, "created" will now return as a ISO-8601 string 242 | 243 | # 1.3.3 (2018-05-29) 244 | 245 | #### HTTP Client 246 | 247 | - Add "created" property to events on read, as TCP client returns 248 | 249 | #### Dependencies 250 | 251 | - Use latest packages 252 | 253 | # 1.3.2 (2018-04-24) 254 | 255 | #### Dependencies 256 | 257 | - Use latest packages 258 | 259 | # 1.3.1 (2017-10-27) 260 | 261 | #### HTTP Client 262 | 263 | - Remove redundant url parsing logic, by setting base url on client create 264 | 265 | # 1.3.0 (2017-10-27) 266 | 267 | #### Dependencies 268 | 269 | - Use latest packages 270 | - TCP: upgrade node-eventstore-client from 0.1.7 to 0.1.9 271 | 272 | #### Dev 273 | 274 | - Requires nodejs >= v.7.6 275 | 276 | #### Misc 277 | 278 | - Convert library source to use es6 modules, and async/await 279 | - Use babel latest preset 280 | 281 | # 1.2.8 (2017-08-11) 282 | 283 | #### TCP Client 284 | 285 | - Improve tcp connection on error logging 286 | 287 | # 1.2.7 (2017-08-11) 288 | 289 | #### TCP Client 290 | 291 | - Update to latest version of newly named node-eventstore-client from eventstore-node 292 | 293 | # 1.2.6 (2017-07-26) 294 | 295 | #### HTTP Client 296 | 297 | - Feature: add embed option to getEvents and getAllStreamEvents. Options: 'body' and 'rich', defaults to 'body' as per previous versions 298 | 299 | # 1.2.5 (2017-04-18) 300 | 301 | #### TCP Client 302 | 303 | - Fix: deleting of projected streams(Expected version to any) 304 | 305 | # 1.2.4 (2017-04-18) 306 | 307 | #### TCP Client 308 | 309 | - Fix: add eventId and positionCreated properties to mapped events 310 | 311 | # 1.2.3 (2017-04-18) 312 | 313 | #### TCP Client 314 | 315 | - Feature: add deleteStream 316 | 317 | # 1.2.2 (2017-03-29) 318 | 319 | #### TCP Client 320 | 321 | - Fix: convert metadata in mapping 322 | 323 | # 1.2.1 (2017-03-29) 324 | 325 | #### TCP Client 326 | 327 | - Fix: filter deleted events on projected streams 328 | 329 | #### Breaking Changes 330 | 331 | - TCP: events, rename property eventStreamId to streamId 332 | 333 | # 1.2.0 (2017-03-29) 334 | 335 | #### Source 336 | 337 | - Convert to ES6 338 | 339 | #### Misc. 340 | 341 | - Fix: debug logs doing unnecessary stringify, increases performance all around 342 | 343 | #### Breaking Changes 344 | 345 | - None 346 | 347 | # 1.1.26 (2017-03-27) 348 | 349 | #### TCP Client 350 | 351 | - Add check stream exits 352 | 353 | # 1.1.25 (2017-03-27) 354 | 355 | #### TCP Client 356 | 357 | - Update to latest version of eventstore-node that inclues some fixes 358 | 359 | # 1.1.25 (2017-03-22) 360 | 361 | #### TCP Client 362 | 363 | - Changed write+read backend to [https://github.com/nicdex/eventstore-node](https://github.com/nicdex/eventstore-node) 364 | - New Feature: connection pooling so calls use single open connection 365 | - New Feature: ablility to close connections and get connections 366 | 367 | # 1.1.24 (2017-03-15) 368 | 369 | #### HTTP Client 370 | 371 | - New Feature: persistent subscriptions v1 372 | - Fix: deleteStream, return error object on stream 404 373 | 374 | # 1.1.23 (2017-03-15) 375 | 376 | #### HTTP Client 377 | 378 | - Fix checkStreamExists, return rejected promise on any error other than a 404 379 | 380 | #### TCP Client 381 | 382 | - Use latest event-store-client 383 | 384 | #### EventFactory 385 | 386 | - Added support for custom eventId(thanks @krazar) 387 | 388 | #### Dependencies 389 | 390 | - bluebird, 3.4.6 > 3.5.0 391 | - debug, 2.2.0 > 2.6.3 392 | - event-store-client, 0.0.10 > 0.0.11 393 | - lodash, 4.15.0 > 4.17.4 394 | - request-promise, 2.0.1 > 4.1.1 (requires request 2.81.0) 395 | - uuid, 3.0.0 > 3.0.1 396 | 397 | #### Misc 398 | 399 | - added missing debug logs 400 | 401 | 402 | # 1.1.22 (2017-03-09) 403 | 404 | #### HTTP Client 405 | 406 | - add timeout option 407 | 408 | # 1.1.21 (2017-01-04) 409 | 410 | #### All Clients 411 | 412 | - add resolveLinkTos optional param for all read functions 413 | 414 | # 1.1.20 (2016-12-22) 415 | 416 | #### HTTP Client 417 | 418 | - deleteStream, added option to hard delete streams(thanks @mjaric) 419 | 420 | #### TCP Client 421 | 422 | - SubscribeToStreamFrom, added missing event-store-client settings(maxLiveQueueSize, readBatchSize, debug) 423 | 424 | # 1.1.19 (2016-12-08) 425 | 426 | #### Dependencies 427 | 428 | - 'q' promise library replaced by bluebird (3.4.6) 429 | 430 | # 1.1.18 (2016-11-23) 431 | 432 | #### Dependencies 433 | 434 | - 'node-uuid' got deprecated and renamed to 'uuid'(3.0.0) 435 | 436 | # 1.1.17 (2016-11-17) 437 | 438 | #### TCP Client 439 | 440 | - clean up console log on live subscription 441 | 442 | # 1.1.16 (2016-11-10) 443 | 444 | #### TCP Client 445 | 446 | - Add Subcribe to stream to start a live subscription to a stream 447 | 448 | # 1.1.15 (2016-09-22) 449 | 450 | #### Aggregate Root 451 | 452 | - Fix: version of aggregrate not setting on event 0 453 | 454 | #### TCP Client 455 | 456 | - Upgrade to lastest event-store-client library(0.0.10) 457 | 458 | #### Misc. 459 | 460 | - Update to latest lodash(4.15.0) 461 | 462 | # 1.1.14 (2016-08-24) 463 | 464 | #### HTTP Client 465 | 466 | - Fix: GetEvents: When passing starting position of 0 for backward read, only event 0 should be returned. Was starting read over from the back of the stream(Potential breaking change) 467 | 468 | # 1.1.13 (2016-07-26) 469 | 470 | #### TCP Client 471 | 472 | - Fix: Create local references of events when writing 473 | 474 | # 1.1.12 (2016-07-26) 475 | 476 | #### HTTP Client 477 | 478 | - Fix: Only parse event data when defined 479 | - Fix: Return full error object on getAllStreamEvents 480 | 481 | #### TCP Client 482 | 483 | - Fix: Return full errors 484 | - Upgrade to lastest event-store-client library(0.0.9) 485 | 486 | # 1.1.11 (2016-07-18) 487 | 488 | #### TCP Client 489 | 490 | - Upgrade to lastest event-store-client library(0.0.8) 491 | 492 | # 1.1.10 (2016-06-28) 493 | 494 | #### TCP Client 495 | 496 | - Feature: subscribeToStreamFrom to allow resolveLinkTos setting 497 | 498 | # 1.1.9 (2016-06-28) 499 | 500 | #### TCP Client 501 | 502 | - Feature: add subscribeToStreamFrom 503 | 504 | #### HTTP Client 505 | 506 | - Feature: get state of partitioned projection 507 | 508 | #### Dependencies 509 | 510 | - replace underscore with lodash 511 | - upgrade version event-store-client 0.0.7 512 | 513 | # 1.1.8 (2016-06-20) 514 | 515 | #### HTTP Client 516 | 517 | - Fix: writeEvents return successful if empty array given 518 | - Fix: any get events function will default to 4096 count if greater is requested (warning also displayed) 519 | - Feature: add getAllStreamEvents function 520 | 521 | #### TCP Client 522 | 523 | - Feature: added start event number on getAllStreamEvents 524 | - Fix: any get events function will default to 4096 count if greater is requested (warning also displayed) 525 | - Change: default chunkSize of reads from 250 to 1000 526 | 527 | #### Tests 528 | 529 | - Added tests to TCP and HTTP client to check for undefined, empty array in writeEvents 530 | 531 | # 1.1.7 (2016-06-08) 532 | 533 | #### HTTP Client 534 | 535 | - Ping: returns successful if ping can be called, rejects if not 536 | 537 | # 1.1.6 (2016-06-07) 538 | 539 | #### HTTP Client 540 | 541 | - DeleteStream: deletes an existing stream, rejects if the stream does not exist 542 | 543 | # 1.1.5 (2016-06-07) 544 | 545 | #### HTTP Client 546 | 547 | - GetEvents return events in the correct order. Forwards and Backwards now return as expected. Reverse of what it used to be. 548 | 549 | # 1.1.4 (2016-04-15) 550 | 551 | #### TCP Client 552 | 553 | - Return rejected promise on failure to connect to Event Store instead of just logging it 554 | 555 | # 1.1.3 (2016-04-06) 556 | 557 | #### HTTP Client 558 | 559 | - Make checkStreamExists more accurate 560 | - Fix request-promise usage to include 'embed=body' as query string object(mono fix) 561 | 562 | # 1.1.2 (2016-04-04) 563 | 564 | #### TCP Client 565 | 566 | - Fix tcp client adding invalid 'host' property to config 567 | 568 | # 1.1.1 (2016-03-15) 569 | 570 | ## Breaking Changes 571 | 572 | #### HTTP client 573 | 574 | - 'getProjectionState' moved to 'projections.getState' 575 | - 'getAllProjectionsInfo' moved to 'projections.getAllProjectionsInfo' 576 | 577 | # 1.1.0 (2016-03-14) 578 | 579 | ## Breaking Changes 580 | 581 | #### Configuration 582 | 583 | - Removed wrapping `http` and `tcp` configuration properties 584 | - Removed protocol property, assigned internally 585 | 586 | ##### Previous Usage 587 | ```javascript 588 | var eventstore = require('geteventstore-promise'); 589 | 590 | var client = eventstore.http({ 591 | http:{ 592 | hostname: 'localhost', 593 | protocol: 'http', 594 | port: 2113, 595 | credentials: { 596 | username: 'admin', 597 | password: 'changeit' 598 | } 599 | } 600 | }); 601 | 602 | ``` 603 | 604 | ##### New Usage 605 | ```javascript 606 | var eventstore = require('geteventstore-promise'); 607 | 608 | var client = eventstore.http({ 609 | hostname: 'localhost', 610 | port: 2113, 611 | credentials: { 612 | username: 'admin', 613 | password: 'changeit' 614 | } 615 | }); 616 | 617 | ``` 618 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 RemoteMetering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EventStoreSubscription, 3 | WriteResult as TCPWriteResult, 4 | DeleteResult as TCPDeleteResult, 5 | LiveProcessingStartedCallback, 6 | SubscriptionDroppedCallback, 7 | ConnectionSettings 8 | } from "node-eventstore-client"; 9 | 10 | import { 11 | Options as TCPPoolOptions, 12 | Pool as TCPPool 13 | } from "generic-pool"; 14 | 15 | export interface NewEvent { 16 | eventId: string; 17 | eventType: string; 18 | data: object; 19 | metadata?: object; 20 | } 21 | 22 | export interface Event { 23 | streamId: string; 24 | eventId: string; 25 | eventNumber: number; 26 | eventType: string; 27 | created: string; 28 | data: object; 29 | metadata?: object; 30 | isJson?: boolean; 31 | positionStreamId?: string; 32 | positionEventId?: string; 33 | positionEventNumber?: number; 34 | positionCreated?: string; 35 | } 36 | 37 | export type ProjectionMode = "onetime" | "continuous"; 38 | 39 | export type ReadDirection = "forward" | "backward"; 40 | 41 | export type EmbedType = "body" | "rich" | "PrettyBody" | "TryHarder"; 42 | 43 | export interface UserCredentials { 44 | readonly username: string; 45 | readonly password: string; 46 | } 47 | 48 | export interface GossipSeed { 49 | readonly hostname: string; 50 | readonly port: number; 51 | } 52 | 53 | export interface HTTPConfig { 54 | protocol?: string; 55 | hostname: string; 56 | port: number; 57 | validateServer?: boolean; 58 | credentials: UserCredentials; 59 | } 60 | 61 | export interface TCPConfig extends ConnectionSettings { 62 | protocol?: string; 63 | hostname?: string; 64 | port?: number; 65 | useSslConnection?: boolean; 66 | validateServer?: boolean; 67 | gossipSeeds?: GossipSeed[]; 68 | credentials: UserCredentials; 69 | poolOptions?: TCPPoolOptions; 70 | connectionNameGenerator?: () => string | Promise; 71 | } 72 | 73 | export interface HTTPWriteEventOptions { 74 | expectedVersion?: number; 75 | } 76 | 77 | export interface TCPWriteEventOptions { 78 | expectedVersion?: number; 79 | } 80 | 81 | export interface TCPWriteEventsOptions extends TCPWriteEventOptions { 82 | transactionWriteSize?: number; 83 | } 84 | 85 | export interface TCPReadResult { 86 | status: string; 87 | stream: string; 88 | fromEventNumber: Long; 89 | readDirection: string; 90 | events: Event[]; 91 | nextEventNumber: Long; 92 | lastEventNumber: Long; 93 | isEndOfStream: boolean; 94 | } 95 | 96 | export interface HTTPReadResultAuthor { 97 | name: string; 98 | } 99 | 100 | export interface HTTPReadResultLink { 101 | uri: string; 102 | relation: string; 103 | } 104 | 105 | export interface HTTPReadResult { 106 | title: string, 107 | id: string, 108 | updated: string, 109 | streamId: string, 110 | author: HTTPReadResultAuthor, 111 | headOfStream: boolean, 112 | isEndOfStream: boolean, 113 | readDirection: ReadDirection, 114 | fromEventNumber: number; 115 | nextEventNumber: number; 116 | selfUrl: string, 117 | links: HTTPReadResultLink[], 118 | events: Event[] 119 | } 120 | 121 | export interface ProjectionStateOptions { 122 | partition?: string; 123 | } 124 | 125 | export interface PersistentSubscriptionOptions { 126 | resolveLinkTos?: boolean; 127 | startFrom?: number; 128 | extraStatistics?: boolean; 129 | messageTimeout?: number; 130 | maxRetryCount?: number; 131 | liveBufferSize?: number; 132 | readBatchSize?: number; 133 | historyBufferSize?: number; 134 | checkPointAfter?: number; 135 | minCheckPointCount?: number; 136 | maxCheckPointCount?: number; 137 | maxSubscriberCount?: number; 138 | namedConsumerStrategy?: string; 139 | } 140 | 141 | export interface PersistentSubscriptionAssertResult { 142 | correlationId: string; 143 | reason: string; 144 | result: string; 145 | msgTypeId: number; 146 | } 147 | 148 | export interface EventStoreCatchUpSubscription { 149 | stop(): void; 150 | close(): Promise; 151 | } 152 | 153 | export interface SubscribeToStreamFromSettings { 154 | resolveLinkTos?: boolean; 155 | readBatchSize?: number; 156 | } 157 | 158 | export interface MappedEventAppearedCallback { 159 | (subscription: TSubscription, event: Event): void | Promise; 160 | } 161 | 162 | export interface EventEnumeratorResult { 163 | isEndOfStream: boolean; 164 | events: Event[]; 165 | } 166 | 167 | export class EventFactory { 168 | newEvent: (eventType: string, data: object, metadata?: object, eventId?: string) => NewEvent; 169 | } 170 | 171 | export class HTTPClient { 172 | constructor(config: HTTPConfig); 173 | checkStreamExists(streamName: string): Promise; 174 | writeEvent(streamName: string, eventType: string, data: object, metaData?: object, options?: HTTPWriteEventOptions): Promise; 175 | writeEvents(streamName: string, events: NewEvent[], options?: HTTPWriteEventOptions): Promise; 176 | getAllStreamEvents(streamName: string, chunkSize?: number, startPosition?: number, resolveLinkTos?: boolean, embed?: EmbedType): Promise; 177 | getEvents(streamName: string, startPosition?: number, count?: number, direction?: ReadDirection, resolveLinkTos?: boolean, embed?: EmbedType): Promise; 178 | getEventsByType(streamName: string, eventTypes: string[], startPosition?: number, count?: number, direction?: ReadDirection, resolveLinkTos?: boolean): Promise; 179 | readEventsForward(streamName: string, startPosition?: number, count?: number, resolveLinkTos?: boolean, embed?: EmbedType): Promise; 180 | readEventsBackward(streamName: string, startPosition?: number, count?: number, resolveLinkTos?: boolean, embed?: EmbedType): Promise; 181 | deleteStream(streamName: string, hardDelete?: boolean): Promise; 182 | ping(): Promise; 183 | admin: { 184 | scavenge(): Promise; 185 | shutdown(): Promise; 186 | }; 187 | projections: { 188 | start(name: string): Promise; 189 | stop(name: string): Promise; 190 | reset(name: string): Promise; 191 | assert(name: string, projectionContent: string, mode?: ProjectionMode, enabled?: boolean, checkpointsEnabled?: boolean, emitEnabled?: boolean, trackEmittedStreams?: boolean): Promise; 192 | remove(name: string, deleteCheckpointStream?: boolean, deleteStateStream?: boolean): Promise; 193 | config(name: string): Promise; 194 | getState(name: string, options?: ProjectionStateOptions): Promise; 195 | getResult(name: string, options?: ProjectionStateOptions): Promise; 196 | getInfo(name: string, includeConfig?: boolean): Promise; 197 | getAllProjectionsInfo(): Promise; 198 | disableAll(): Promise; 199 | enableAll(): Promise; 200 | }; 201 | persistentSubscriptions: { 202 | assert(name: string, streamName: string, options?: PersistentSubscriptionOptions): Promise; 203 | remove(name: string, streamName: string): Promise; 204 | getEvents(name: string, streamName: string, count?: number, embed?: EmbedType): Promise; 205 | getSubscriptionInfo(name: string, streamName: string): Promise; 206 | getAllSubscriptionsInfo(): Promise; 207 | getStreamSubscriptionsInfo(streamName: string): Promise; 208 | }; 209 | } 210 | 211 | export class TCPClient { 212 | constructor(config: TCPConfig); 213 | checkStreamExists(streamName: string): Promise; 214 | writeEvent(streamName: string, eventType: string, data: object, metaData?: object, options?: TCPWriteEventOptions): Promise; 215 | writeEvents(streamName: string, events: NewEvent[], options?: TCPWriteEventsOptions): Promise; 216 | getAllStreamEvents(streamName: string, chunkSize?: number, startPosition?: number, resolveLinkTos?: boolean): Promise; 217 | getEvents(streamName: string, startPosition?: number, count?: number, direction?: ReadDirection, resolveLinkTos?: boolean): Promise; 218 | getEventsByType(streamName: string, eventTypes: string[], startPosition?: number, count?: number, direction?: ReadDirection, resolveLinkTos?: boolean): Promise; 219 | readEventsForward(streamName: string, startPosition?: number, count?: number, resolveLinkTos?: boolean): Promise; 220 | readEventsBackward(streamName: string, startPosition?: number, count?: number, resolveLinkTos?: boolean): Promise; 221 | deleteStream(streamName: string, hardDelete?: boolean): Promise; 222 | eventEnumerator(streamName: string, direction?: ReadDirection, resolveLinkTos?: boolean): { 223 | first(count: number): Promise; 224 | last(count: number): Promise; 225 | previous(count: number): Promise; 226 | next(count: number): Promise; 227 | }; 228 | subscribeToStream(streamName: string, onEventAppeared?: MappedEventAppearedCallback, onDropped?: SubscriptionDroppedCallback, resolveLinkTos?: boolean): Promise; 229 | subscribeToStreamFrom(streamName: string, fromEventNumber?: number, onEventAppeared?: MappedEventAppearedCallback, onLiveProcessingStarted?: LiveProcessingStartedCallback, onDropped?: SubscriptionDroppedCallback, settings?: SubscribeToStreamFromSettings): Promise; 230 | close(): Promise; 231 | getPool(): Promise>; 232 | closeAllPools(): Promise; 233 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist'); -------------------------------------------------------------------------------- /lib/AggRoot.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:aggRoot'); 4 | 5 | const AggRoot = function (when) { 6 | const eventHandlers = when; 7 | 8 | this.handle = function (events) { 9 | debug('', 'Handling Events: %j', events); 10 | const self = this; 11 | if (events.length > 0) { 12 | for (let i = 0; i < events.length; i++) { 13 | const ev = events[i]; 14 | if (eventHandlers[ev.eventType] !== undefined) { 15 | eventHandlers[ev.eventType].call(self, ev); 16 | if (ev.eventNumber !== undefined) { 17 | self._version = ev.eventNumber; 18 | } 19 | } 20 | } 21 | } 22 | }; 23 | }; 24 | 25 | export default AggRoot; -------------------------------------------------------------------------------- /lib/EventFactory.js: -------------------------------------------------------------------------------- 1 | import generateEventId from './utilities/generateEventId'; 2 | import assert from 'assert'; 3 | 4 | export default class EventFactory { 5 | newEvent(eventType, data, metadata, eventId) { 6 | assert(eventType); 7 | assert(data); 8 | 9 | const event = { 10 | eventId: eventId || generateEventId(), 11 | eventType, 12 | data 13 | }; 14 | 15 | if (metadata !== undefined) event.metadata = metadata; 16 | return event; 17 | } 18 | } -------------------------------------------------------------------------------- /lib/httpClient/admin/sendScavengeCommand.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:sendScavengeCommand'); 4 | 5 | export default (config, httpClient) => async () => { 6 | const response = await httpClient.post(`${config.baseUrl}/admin/scavenge`); 7 | debug('', 'Response: %j', response.data); 8 | return response.data; 9 | }; -------------------------------------------------------------------------------- /lib/httpClient/admin/sendShutdownCommand.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:sendShutdownCommand'); 4 | 5 | export default (config, httpClient) => async () => { 6 | const response = await httpClient.post(`${config.baseUrl}/admin/shutdown`); 7 | debug('', 'Response: %j', response.data); 8 | return response.data; 9 | }; -------------------------------------------------------------------------------- /lib/httpClient/checkStreamExists.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:checkStreamExists'); 5 | const baseErr = 'Check Stream Exists - '; 6 | 7 | export default (config, httpClient) => async (streamName) => { 8 | assert(streamName, `${baseErr}Stream Name not provided`); 9 | 10 | const options = { 11 | url: `${config.baseUrl}/streams/${streamName}/head/backward/1`, 12 | method: 'GET', 13 | headers: { 14 | "Content-Type": "application/vnd.eventstore.events+json" 15 | }, 16 | timeout: config.timeout 17 | }; 18 | 19 | debug('', 'Options: %j', options); 20 | try { 21 | const response = await httpClient(options); 22 | debug('', 'Response: %j', response.data); 23 | return true; 24 | } catch (err) { 25 | if (!err.response || err.response.status !== 404) throw err; 26 | return false; 27 | } 28 | }; -------------------------------------------------------------------------------- /lib/httpClient/deleteStream.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:deleteStream'); 5 | const baseErr = 'Delete Stream - '; 6 | 7 | export default (config, httpClient, checkStreamExists) => async (streamName, hardDelete) => { 8 | assert(streamName, `${baseErr}Stream Name not provided`); 9 | 10 | const exists = await checkStreamExists(streamName); 11 | if (!exists) throw new Error('Stream does not exist'); 12 | 13 | const options = { 14 | url: `${config.baseUrl}/streams/${streamName}`, 15 | method: 'DELETE', 16 | timeout: config.timeout 17 | }; 18 | 19 | if (hardDelete) { 20 | options.headers = { 21 | "ES-HardDelete": "true" 22 | }; 23 | } 24 | 25 | debug('', 'Options: %j', options); 26 | const response = await httpClient(options); 27 | debug('', 'Response: %j', response.data); 28 | return response.data; 29 | }; -------------------------------------------------------------------------------- /lib/httpClient/getAllStreamEvents.js: -------------------------------------------------------------------------------- 1 | import flattenArray from '../utilities/flattenArray'; 2 | import mapEvents from './utilities/mapEvents'; 3 | import debugModule from 'debug'; 4 | import assert from 'assert'; 5 | 6 | const debug = debugModule('geteventstore:getAllStreamEvents'); 7 | const baseErr = 'Get All Stream Events - '; 8 | 9 | const buildOptions = (config, streamName, startPosition, chunkSize, resolveLinkTos, embed) => ({ 10 | url: `${config.baseUrl}/streams/${streamName}/${startPosition}/forward/${chunkSize}`, 11 | method: 'GET', 12 | headers: { 13 | "Content-Type": "application/vnd.eventstore.events+json", 14 | "ES-ResolveLinkTos": resolveLinkTos.toString() 15 | }, 16 | params: { 17 | embed 18 | }, 19 | timeout: config.timeout 20 | }); 21 | 22 | export default (config, httpClient) => async (streamName, chunkSize, startPosition, resolveLinkTos, embed = 'body') => { 23 | assert(streamName, `${baseErr}Stream Name not provided`); 24 | 25 | startPosition = startPosition || 0; 26 | chunkSize = chunkSize || 1000; 27 | resolveLinkTos = resolveLinkTos === undefined ? true : resolveLinkTos; 28 | 29 | if (chunkSize > 4096) { 30 | console.warn('WARNING: Max event chunk size exceeded. Using the max of 4096'); 31 | chunkSize = 4096; 32 | } 33 | 34 | const events = []; 35 | const getNextChunk = async () => { 36 | const options = buildOptions(config, streamName, startPosition, chunkSize, resolveLinkTos, embed); 37 | 38 | const response = await httpClient(options); 39 | debug('', 'Result: %j', response.data); 40 | 41 | if (embed === 'body') { 42 | const totalEntries = response.data.entries.length; 43 | for (let i = 0; i < totalEntries; i++) { 44 | if (response.data.entries[i].data) response.data.entries[i].data = JSON.parse(response.data.entries[i].data); 45 | } 46 | } 47 | 48 | events.push(response.data.entries.reverse()); 49 | 50 | if (response.data.headOfStream === true) { 51 | return mapEvents(flattenArray(events)); 52 | } 53 | 54 | startPosition += chunkSize; 55 | return getNextChunk(); 56 | }; 57 | 58 | return getNextChunk(); 59 | }; -------------------------------------------------------------------------------- /lib/httpClient/getEvents.js: -------------------------------------------------------------------------------- 1 | export default (readEventsForward, readEventsBackward) => async (streamName, startPosition, count, direction, resolveLinkTos, embed = 'body') => { 2 | const readEvents = direction === 'backward' ? readEventsBackward : readEventsForward; 3 | return (await readEvents(streamName, startPosition, count, resolveLinkTos, embed)).events; 4 | }; -------------------------------------------------------------------------------- /lib/httpClient/getEventsByType.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const baseErr = 'Get Events by Type - '; 4 | 5 | export default (getEvents) => async (streamName, eventTypes, startPosition, count, direction, resolveLinkTos) => { 6 | assert(eventTypes, `${baseErr}Event Types not provided`); 7 | 8 | const events = await getEvents(streamName, startPosition, count, direction, resolveLinkTos); 9 | return events.filter(event => eventTypes.includes(event.eventType)); 10 | }; -------------------------------------------------------------------------------- /lib/httpClient/index.js: -------------------------------------------------------------------------------- 1 | import persistentSubscriptionGetStreamSubscriptionsInfo from './persistentSubscriptions/getStreamSubscriptionsInfo'; 2 | import persistentSubscriptionGetAllSubscriptionsInfo from './persistentSubscriptions/getAllSubscriptionsInfo'; 3 | import persistentSubscriptionGetSubscriptionInfo from './persistentSubscriptions/getSubscriptionInfo'; 4 | import persistentSubscriptionGetEvents from './persistentSubscriptions/getEvents'; 5 | import projectionGetAllProjectionsInfo from './projections/getAllProjectionsInfo'; 6 | import persistentSubscriptionAssert from './persistentSubscriptions/assert'; 7 | import persistentSubscriptionRemove from './persistentSubscriptions/remove'; 8 | import sendScavengeCommand from './admin/sendScavengeCommand'; 9 | import sendShutdownCommand from './admin/sendShutdownCommand'; 10 | import createHttpClient from '../utilities/createHttpClient'; 11 | import projectionDisableAll from './projections/disableAll'; 12 | import projectionEnableAll from './projections/enableAll'; 13 | import projectionGetState from './projections/getState'; 14 | import projectionGetResult from './projections/getResult'; 15 | import getAllStreamEvents from './getAllStreamEvents'; 16 | import projectionGetInfo from './projections/getInfo'; 17 | import checkStreamExists from './checkStreamExists'; 18 | import projectionAssert from './projections/assert'; 19 | import projectionRemove from './projections/remove'; 20 | import projectionConfig from './projections/config'; 21 | import projectionStart from './projections/start'; 22 | import projectionReset from './projections/reset'; 23 | import projectionStop from './projections/stop'; 24 | import getEventsByType from './getEventsByType'; 25 | import deleteStream from './deleteStream'; 26 | import writeEvents from './writeEvents'; 27 | import writeEvent from './writeEvent'; 28 | import readEvents from './readEvents'; 29 | import getEvents from './getEvents'; 30 | import ping from './ping'; 31 | 32 | import cloneDeep from 'lodash.clonedeep'; 33 | import assert from 'assert'; 34 | import url from 'url'; 35 | 36 | const baseErr = 'geteventstore-promise - HTTP client - '; 37 | 38 | export default class HTTPClient { 39 | constructor(config) { 40 | assert(config, `${baseErr}config not provided`); 41 | assert(config.hostname, `${baseErr}hostname property not provided`); 42 | assert(config.port, `${baseErr}port property not provided`); 43 | assert(config.credentials, `${baseErr}credentials property not provided`); 44 | assert(config.credentials.username, `${baseErr}credentials.username property not provided`); 45 | assert(config.credentials.password, `${baseErr}credentials.password property not provided`); 46 | if (config.timeout) assert(typeof config.timeout === 'number', `${baseErr}timeout not defined`); 47 | 48 | //Add additional internal configuration properties 49 | const _config = cloneDeep(config); 50 | _config.protocol = _config.protocol || 'http'; 51 | _config.auth = `${_config.credentials.username}:${_config.credentials.password}`; 52 | _config.baseUrl = url.format(_config); 53 | if (_config.protocol === 'https') _config.validateServer = _config.validateServer === undefined || _config.validateServer === null ? true : _config.validateServer; 54 | 55 | const httpClient = createHttpClient(_config); 56 | 57 | const _getAllProjectionsInfo = projectionGetAllProjectionsInfo(_config, httpClient); 58 | const _getConfig = projectionConfig(_config, httpClient); 59 | const _startProjection = projectionStart(_config, httpClient); 60 | const _stopProjection = projectionStop(_config, httpClient); 61 | 62 | this.checkStreamExists = checkStreamExists(_config, httpClient); 63 | this.writeEvent = writeEvent(_config, httpClient); 64 | this.writeEvents = writeEvents(_config, httpClient); 65 | this.getAllStreamEvents = getAllStreamEvents(_config, httpClient); 66 | this.readEventsForward = readEvents(_config, httpClient, 'forward'); 67 | this.readEventsBackward = readEvents(_config, httpClient, 'backward'); 68 | this.getEvents = getEvents(this.readEventsForward, this.readEventsBackward); 69 | this.getEventsByType = getEventsByType(this.getEvents); 70 | this.deleteStream = deleteStream(_config, httpClient, this.checkStreamExists); 71 | this.ping = ping(_config, httpClient); 72 | this.admin = { 73 | scavenge: sendScavengeCommand(_config, httpClient), 74 | shutdown: sendShutdownCommand(_config, httpClient) 75 | }; 76 | this.projections = { 77 | start: _startProjection, 78 | stop: _stopProjection, 79 | reset: projectionReset(_config, httpClient), 80 | remove: projectionRemove(_config, httpClient), 81 | getAllProjectionsInfo: _getAllProjectionsInfo, 82 | getState: projectionGetState(_config, httpClient), 83 | getResult: projectionGetResult(_config, httpClient), 84 | config: projectionConfig(_config, httpClient), 85 | getInfo: projectionGetInfo(_getAllProjectionsInfo, _getConfig), 86 | assert: projectionAssert(_config, httpClient, _getAllProjectionsInfo), 87 | disableAll: projectionDisableAll(_getAllProjectionsInfo, _stopProjection), 88 | enableAll: projectionEnableAll(_getAllProjectionsInfo, _startProjection) 89 | }; 90 | this.persistentSubscriptions = { 91 | assert: persistentSubscriptionAssert(_config, httpClient), 92 | remove: persistentSubscriptionRemove(_config, httpClient), 93 | getEvents: persistentSubscriptionGetEvents(_config, httpClient), 94 | getSubscriptionInfo: persistentSubscriptionGetSubscriptionInfo(_config, httpClient), 95 | getAllSubscriptionsInfo: persistentSubscriptionGetAllSubscriptionsInfo(_config, httpClient), 96 | getStreamSubscriptionsInfo: persistentSubscriptionGetStreamSubscriptionsInfo(_config, httpClient) 97 | }; 98 | } 99 | } -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/assert.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:assertPersistentSubscription'); 5 | const baseErr = 'Assert persistent subscriptions - '; 6 | 7 | const createPersistentSubscriptionRequest = (config, name, streamName, options) => { 8 | return { 9 | url: `${config.baseUrl}/subscriptions/${streamName}/${name}`, 10 | method: 'PUT', 11 | data: options 12 | }; 13 | }; 14 | 15 | const createPersistentSubscriptionOptions = (options) => { 16 | options = options || {}; 17 | 18 | return { 19 | resolveLinkTos: options.resolveLinkTos, 20 | startFrom: options.startFrom === undefined ? 0 : options.startFrom, 21 | extraStatistics: options.extraStatistics, 22 | checkPointAfterMilliseconds: options.checkPointAfterMilliseconds, 23 | liveBufferSize: options.liveBufferSize, 24 | readBatchSize: options.readBatchSize, 25 | bufferSize: options.bufferSize, 26 | maxCheckPointCount: options.maxCheckPointCount, 27 | maxRetryCount: options.maxRetryCount, 28 | maxSubscriberCount: options.maxSubscriberCount, 29 | messageTimeoutMilliseconds: options.messageTimeoutMilliseconds, 30 | minCheckPointCount: options.minCheckPointCount, 31 | namedConsumerStrategy: options.namedConsumerStrategy, 32 | }; 33 | }; 34 | 35 | export default (config, httpClient) => async (name, streamName, options) => { 36 | assert(name, `${baseErr}Persistent Subscription Name not provided`); 37 | assert(streamName, `${baseErr}Stream Name not provided`); 38 | 39 | const persistentSubscriptionOptions = createPersistentSubscriptionOptions(options); 40 | const createRequest = createPersistentSubscriptionRequest(config, name, streamName, persistentSubscriptionOptions); 41 | 42 | try { 43 | debug('', 'Options: %j', createRequest); 44 | const response = await httpClient(createRequest); 45 | debug('', 'Response: %j', response.data); 46 | return response.data; 47 | } catch (err) { 48 | if (err.response.status !== 409) throw err; 49 | 50 | const updateRequest = createPersistentSubscriptionRequest(config, name, streamName, persistentSubscriptionOptions); 51 | updateRequest.method = 'post'; 52 | 53 | debug('', 'Update Options: %j', updateRequest); 54 | const response = await httpClient(updateRequest); 55 | debug('', 'Response: %j', response.data); 56 | return response.data; 57 | } 58 | }; -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/getAllSubscriptionsInfo.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:getAllSubscriptionsInfo'); 4 | 5 | export default (config, httpClient) => async () => { 6 | const response = await httpClient.get(`${config.baseUrl}/subscriptions`); 7 | debug('', 'Response: %j', response.data); 8 | return response.data; 9 | }; -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/getEvents.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:getEventsPersistentSubscription'); 5 | const baseErr = 'Get persistent subscriptions events - '; 6 | 7 | const createRequest = (config, name, streamName, count, embed) => { 8 | const request = { 9 | url: `${config.baseUrl}/subscriptions/${streamName}/${name}/${count}?embed=${embed}`, 10 | method: 'GET', 11 | headers: { 12 | 'Accept': 'application/vnd.eventstore.competingatom+json', 13 | } 14 | }; 15 | return request; 16 | }; 17 | 18 | const appendLinkFunctions = (config, httpClient, resultObject, links) => { 19 | links.forEach( 20 | (link) => 21 | (resultObject[link.relation] = () => 22 | httpClient.post( 23 | link.uri, {}, { 24 | auth: { 25 | username: config.credentials.username, 26 | password: config.credentials.password, 27 | }, 28 | } 29 | ) 30 | ) 31 | ); 32 | }; 33 | 34 | const buildResultObject = (config, httpClient, response) => { 35 | const result = { entries: [] }; 36 | 37 | appendLinkFunctions(config, httpClient, result, response.links); 38 | 39 | result.entries = response.entries.map(entry => { 40 | if (entry.data) entry.data = JSON.parse(entry.data); 41 | 42 | const formattedEntry = { event: entry }; 43 | appendLinkFunctions(config, httpClient, formattedEntry, entry.links); 44 | return formattedEntry; 45 | }); 46 | 47 | debug('', 'Result: %j', result); 48 | return result; 49 | }; 50 | 51 | export default (config, httpClient) => async (name, streamName, count, embed) => { 52 | assert(name, `${baseErr}Persistent Subscription Name not provided`); 53 | assert(streamName, `${baseErr}Stream Name not provided`); 54 | 55 | count = count === undefined ? 1 : count; 56 | embed = embed || 'Body'; 57 | 58 | const options = createRequest(config, name, streamName, count, embed); 59 | debug('', 'Options: %j', options); 60 | const response = await httpClient(options); 61 | debug('', 'Response: %j', response.data); 62 | return buildResultObject(config, httpClient, response.data); 63 | }; -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/getStreamSubscriptionsInfo.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:getStreamSubscriptionsInfo'); 5 | const baseError = 'Get Stream Subscriptions Info - '; 6 | 7 | export default (config, httpClient) => async (streamName) => { 8 | assert(streamName, `${baseError}Stream name not provided`); 9 | 10 | const response = await httpClient.get(`${config.baseUrl}/subscriptions/${streamName}`); 11 | debug('', 'Response: %j', response.data); 12 | return response.data; 13 | }; -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/getSubscriptionInfo.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:getSubscriptionInfo'); 5 | const baseError = 'Get Stream Subscriptions Info - '; 6 | 7 | export default (config, httpClient) => async (name, streamName) => { 8 | assert(name, `${baseError}Persistent Subscription Name not provided`); 9 | assert(streamName, `${baseError}Stream name not provided`); 10 | 11 | const response = await httpClient.get(`${config.baseUrl}/subscriptions/${streamName}/${name}/info`); 12 | debug('', 'Response: %j', response.data); 13 | return response.data; 14 | }; -------------------------------------------------------------------------------- /lib/httpClient/persistentSubscriptions/remove.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:removePersistentSubscription'); 5 | const baseErr = 'Remove persistent subscriptions - '; 6 | 7 | export default (config, httpClient) => async (name, streamName) => { 8 | assert(name, `${baseErr}Persistent Subscription Name not provided`); 9 | assert(streamName, `${baseErr}Stream Name not provided`); 10 | 11 | const response = await httpClient.delete(`${config.baseUrl}/subscriptions/${streamName}/${name}`); 12 | debug('', 'Response: %j', response.data); 13 | return response.data; 14 | }; -------------------------------------------------------------------------------- /lib/httpClient/ping.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:ping'); 4 | 5 | export default (config, httpClient) => async () => { 6 | const options = { 7 | url: `${config.baseUrl}/ping`, 8 | method: 'GET', 9 | timeout: config.timeout 10 | }; 11 | 12 | debug('', 'Options: %j', options); 13 | const response = await httpClient(options); 14 | debug('', 'Response: %j', response.data); 15 | return response.data; 16 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/assert.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:assertProjection'); 5 | const baseErr = 'Assert Projection - '; 6 | 7 | const doesProjectionExist = async (projectionsInfo, name) => { 8 | const projection = projectionsInfo.projections.find(projection => projection.name === name); 9 | if (projection) return true; 10 | return false; 11 | }; 12 | 13 | const buildCreateOptions = ( 14 | config, 15 | name, 16 | projectionContent, 17 | mode, 18 | enabled, 19 | emitEnabled, 20 | checkpointsEnabled, 21 | trackEmittedStreams 22 | ) => { 23 | const options = { 24 | url: `${config.baseUrl}/projections/${mode}`, 25 | method: 'POST', 26 | params: { 27 | name, 28 | enabled: enabled ? 'yes' : 'no', 29 | emit: emitEnabled ? 'yes' : 'no', 30 | checkpoints: checkpointsEnabled ? 'yes' : 'no', 31 | trackemittedstreams: trackEmittedStreams ? 'yes' : 'no', 32 | }, 33 | data: projectionContent 34 | }; 35 | 36 | return options; 37 | }; 38 | 39 | const buildUpdateOptions = (config, name, projectionContent, emitEnabled) => { 40 | const options = { 41 | url: `${config.baseUrl}/projection/${name}/query`, 42 | method: 'PUT', 43 | params: { 44 | emit: emitEnabled ? 'yes' : 'no' 45 | }, 46 | data: projectionContent 47 | }; 48 | 49 | return options; 50 | }; 51 | 52 | export default (config, httpClient, getAllProjectionsInfo) => async (name, projectionContent, mode, enabled, checkpointsEnabled, emitEnabled, trackEmittedStreams) => { 53 | assert(name, `${baseErr}Name not provided`); 54 | assert(projectionContent, `${baseErr}Projection Content not provided`); 55 | 56 | mode = mode || 'continuous'; 57 | enabled = enabled || true; 58 | checkpointsEnabled = mode === 'continuous' ? true : checkpointsEnabled || false; 59 | emitEnabled = emitEnabled || false; 60 | trackEmittedStreams = trackEmittedStreams || false; 61 | 62 | const projectionsInfo = await getAllProjectionsInfo(); 63 | const projectionExists = await doesProjectionExist(projectionsInfo, name); 64 | debug('', 'Projection Exists: %j', projectionExists); 65 | 66 | let options = {}; 67 | if (!projectionExists) options = buildCreateOptions(config, name, projectionContent, mode, enabled, emitEnabled, checkpointsEnabled, trackEmittedStreams); 68 | else options = buildUpdateOptions(config, name, projectionContent, emitEnabled); 69 | 70 | debug('', 'Options: %j', options); 71 | const response = await httpClient(options); 72 | debug('', 'Response: %j', response.data); 73 | return response.data; 74 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/config.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule("geteventstore:getProjectionConfig"); 5 | const baseErr = "Get Projection Config - "; 6 | 7 | export default (config, httpClient) => async (name) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const response = await httpClient.get( 11 | `${config.baseUrl}/projection/${name}/config` 12 | ); 13 | debug("", "Response: %j", response.data); 14 | 15 | const projectionConfig = { 16 | ...response.data, 17 | ...{ 18 | emitEnabled: 19 | response.data.emitEnabled === undefined 20 | ? false 21 | : response.data.emitEnabled, 22 | trackEmittedStreams: 23 | response.data.trackEmittedStreams === undefined 24 | ? false 25 | : response.data.trackEmittedStreams, 26 | }, 27 | }; 28 | 29 | return projectionConfig; 30 | }; 31 | -------------------------------------------------------------------------------- /lib/httpClient/projections/disableAll.js: -------------------------------------------------------------------------------- 1 | export default (getAllProjectionsInfo, stopProjection) => async () => { 2 | const projectionsInfo = await getAllProjectionsInfo(); 3 | return Promise.all(projectionsInfo.projections.map(projection => stopProjection(projection.name))); 4 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/enableAll.js: -------------------------------------------------------------------------------- 1 | export default (getAllProjectionsInfo, startProjection) => async () => { 2 | const projectionsInfo = await getAllProjectionsInfo(); 3 | return Promise.all(projectionsInfo.projections.map(projection => startProjection(projection.name))); 4 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/getAllProjectionsInfo.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | 3 | const debug = debugModule('geteventstore:getAllProjectionsInfo'); 4 | 5 | export default (config, httpClient) => async () => { 6 | const response = await httpClient.get(`${config.baseUrl}/projections/all-non-transient`); 7 | debug('', 'Response: %j', response.data); 8 | return response.data; 9 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/getInfo.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule("geteventstore:getProjectionInfo"); 5 | const baseErr = "Get Projection Info - "; 6 | 7 | export default (getAllProjectionsInfo, getConfig) => async ( 8 | name, 9 | includeConfig 10 | ) => { 11 | assert(name, `${baseErr}Name not provided`); 12 | 13 | const projectionsInfo = await getAllProjectionsInfo(); 14 | const projectionInfo = projectionsInfo.projections.find( 15 | (projection) => projection.name === name 16 | ); 17 | debug("", "Projection Info: %j", projectionInfo); 18 | 19 | if (includeConfig === true) { 20 | const config = await getConfig(name); 21 | debug("", "Config: %j", config); 22 | return { ...projectionInfo, config }; 23 | } 24 | 25 | return projectionInfo; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/httpClient/projections/getResult.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:getResult'); 5 | const baseErr = 'Get Projection Result - '; 6 | 7 | export default (config, httpClient) => async (name, options) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const params = {}; 11 | options = options || {}; 12 | 13 | if (options.partition) params.partition = options.partition; 14 | const urlOptions = { 15 | url: `${config.baseUrl}/projection/${name}/result`, 16 | headers: { 17 | "Content-Type": "application/vnd.eventstore.events+json" 18 | }, 19 | method: 'GET', 20 | params 21 | }; 22 | debug('', 'Options: %j', options); 23 | const response = await httpClient(urlOptions); 24 | debug('', 'Response: %j', response.data); 25 | return response.data; 26 | }; 27 | -------------------------------------------------------------------------------- /lib/httpClient/projections/getState.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:getProjectionState'); 5 | const baseErr = 'Get Projection State - '; 6 | 7 | export default (config, httpClient) => async (name, options) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const params = {}; 11 | options = options || {}; 12 | 13 | if (options.partition) params.partition = options.partition; 14 | const urlOptions = { 15 | url: `${config.baseUrl}/projection/${name}/state`, 16 | headers: { 17 | "Content-Type": "application/vnd.eventstore.events+json" 18 | }, 19 | method: 'GET', 20 | params 21 | }; 22 | debug('', 'Options: %j', options); 23 | const response = await httpClient(urlOptions); 24 | debug('', 'Response: %j', response.data); 25 | return response.data; 26 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/remove.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:removeProjection'); 5 | const baseErr = 'Remove Projection - '; 6 | 7 | export default (config, httpClient) => async (name, deleteCheckpointStream, deleteStateStream) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | deleteCheckpointStream = deleteCheckpointStream || false; 11 | deleteStateStream = deleteStateStream || false; 12 | 13 | const options = { 14 | url: `${config.baseUrl}/projection/${name}`, 15 | method: 'DELETE', 16 | params: { 17 | deleteCheckpointStream: deleteCheckpointStream ? 'yes' : 'no', 18 | deleteStateStream: deleteStateStream ? 'yes' : 'no' 19 | } 20 | }; 21 | 22 | debug('', 'Options: %j', options); 23 | const response = await httpClient(options); 24 | debug('', 'Response: %j', response.data); 25 | return response.data; 26 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/reset.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:resetProjection'); 5 | const baseErr = 'Reset Projection - '; 6 | 7 | export default (config, httpClient) => async (name) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const response = await httpClient.post(`${config.baseUrl}/projection/${name}/command/reset`); 11 | debug('', 'Response: %j', response.data); 12 | return response.data; 13 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/start.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:startProjection'); 5 | const baseErr = 'Start Projection - '; 6 | 7 | export default (config, httpClient) => async (name) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const response = await httpClient.post(`${config.baseUrl}/projection/${name}/command/enable`); 11 | debug('', 'Response: %j', response.data); 12 | return response.data; 13 | }; -------------------------------------------------------------------------------- /lib/httpClient/projections/stop.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:stopProjection'); 5 | const baseErr = 'Stop Projection - '; 6 | 7 | export default (config, httpClient) => async (name) => { 8 | assert(name, `${baseErr}Name not provided`); 9 | 10 | const response = await httpClient.post(`${config.baseUrl}/projection/${name}/command/disable`); 11 | debug('', 'Response: %j', response.data); 12 | return response.data; 13 | }; -------------------------------------------------------------------------------- /lib/httpClient/readEvents.js: -------------------------------------------------------------------------------- 1 | import mapEvents from './utilities/mapEvents'; 2 | import debugModule from 'debug'; 3 | import assert from 'assert'; 4 | 5 | const debug = debugModule('geteventstore:readEvents'); 6 | const baseErr = 'Read Events - '; 7 | 8 | export default (config, httpClient, direction) => async (streamName, startPosition, count, resolveLinkTos, embed = 'body') => { 9 | assert(streamName, `${baseErr}Stream Name not provided`); 10 | 11 | count = count || 1000; 12 | 13 | if (count > 4096) { 14 | console.warn('WARNING: Max event count exceeded. Using the max of 4096'); 15 | count = 4096; 16 | } 17 | 18 | direction = direction || 'forward'; 19 | startPosition = startPosition === undefined && direction === 'backward' ? 'head' : startPosition || 0; 20 | resolveLinkTos = resolveLinkTos === undefined ? true : resolveLinkTos; 21 | 22 | const options = { 23 | url: `${config.baseUrl}/streams/${streamName}/${startPosition}/${direction}/${count}`, 24 | method: 'GET', 25 | headers: { 26 | "Content-Type": "application/vnd.eventstore.events+json", 27 | "ES-ResolveLinkTos": resolveLinkTos.toString() 28 | }, 29 | params: { 30 | embed 31 | }, 32 | timeout: config.timeout 33 | }; 34 | 35 | debug('', 'Options: ', options); 36 | const response = await httpClient(options); 37 | if (embed === 'body') { 38 | const totalEntries = response.data.entries.length; 39 | for (let i = 0; i < totalEntries; i++) { 40 | if (response.data.entries[i].data) response.data.entries[i].data = JSON.parse(response.data.entries[i].data); 41 | } 42 | } 43 | 44 | const mappedEvents = mapEvents(direction === 'forward' ? response.data.entries.reverse() : response.data.entries); 45 | delete response.data.entries; 46 | response.data.isEndOfStream = response.data.headOfStream; 47 | response.data.readDirection = direction; 48 | response.data.fromEventNumber = startPosition; 49 | response.data.nextEventNumber = !response.data.headOfStream && mappedEvents.length > 0 ? mappedEvents[mappedEvents.length - 1].eventNumber + (direction === 'forward' ? 1 : -1) : 0; 50 | response.data.events = mappedEvents; 51 | return response.data; 52 | }; -------------------------------------------------------------------------------- /lib/httpClient/utilities/mapEvents.js: -------------------------------------------------------------------------------- 1 | export default (events) => events.map(ev => { 2 | ev.created = ev.updated; 3 | return ev; 4 | }); -------------------------------------------------------------------------------- /lib/httpClient/writeEvent.js: -------------------------------------------------------------------------------- 1 | import EventFactory from '../EventFactory'; 2 | import debugModule from 'debug'; 3 | import assert from 'assert'; 4 | 5 | const debug = debugModule('geteventstore:writeEvent'); 6 | const eventFactory = new EventFactory(); 7 | const baseErr = 'Write Event - '; 8 | 9 | export default (config, httpClient) => async (streamName, eventType, data, metaData, options) => { 10 | assert(streamName, `${baseErr}Stream Name not provided`); 11 | assert(eventType, `${baseErr}Event Type not provided`); 12 | assert(data, `${baseErr}Event Data not provided`); 13 | 14 | options = options || {}; 15 | options.expectedVersion = !Number.isInteger(options.expectedVersion) ? -2 : options.expectedVersion; 16 | 17 | const events = [eventFactory.newEvent(eventType, data, metaData)]; 18 | 19 | const reqOptions = { 20 | url: `${config.baseUrl}/streams/${streamName}`, 21 | headers: { 22 | "Content-Type": "application/vnd.eventstore.events+json", 23 | "ES-ExpectedVersion": options.expectedVersion 24 | }, 25 | method: 'POST', 26 | data: events, 27 | timeout: config.timeout 28 | }; 29 | 30 | debug('', 'Write Event: %j', reqOptions); 31 | const response = await httpClient(reqOptions); 32 | debug('', 'Response: %j', response.data); 33 | return response.data; 34 | }; -------------------------------------------------------------------------------- /lib/httpClient/writeEvents.js: -------------------------------------------------------------------------------- 1 | import debugModule from 'debug'; 2 | import assert from 'assert'; 3 | 4 | const debug = debugModule('geteventstore:writeEvents'); 5 | const baseErr = 'Write Events - '; 6 | 7 | export default (config, httpClient) => async (streamName, events, options) => { 8 | assert(streamName, `${baseErr}Stream Name not provided`); 9 | assert(events, `${baseErr}Events not provided`); 10 | assert.equal(true, events.constructor === Array, `${baseErr}Events should be an array`); 11 | 12 | if (events.length === 0) return; 13 | 14 | options = options || {}; 15 | options.expectedVersion = !Number.isInteger(options.expectedVersion) ? -2 : options.expectedVersion; 16 | 17 | const reqOptions = { 18 | url: `${config.baseUrl}/streams/${streamName}`, 19 | headers: { 20 | "Content-Type": "application/vnd.eventstore.events+json", 21 | "ES-ExpectedVersion": options.expectedVersion 22 | }, 23 | method: 'POST', 24 | data: events, 25 | timeout: config.timeout 26 | }; 27 | 28 | debug('', 'Write events: %j', reqOptions); 29 | const response = await httpClient(reqOptions); 30 | debug('', 'Response: %j', response.data); 31 | return response.data; 32 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import EventFactory from './EventFactory'; 2 | import HTTPClient from './httpClient'; 3 | import TCPClient from './tcpClient'; 4 | 5 | export { 6 | EventFactory, 7 | HTTPClient, 8 | TCPClient 9 | }; 10 | 11 | export default { 12 | EventFactory, 13 | HTTPClient, 14 | TCPClient 15 | }; -------------------------------------------------------------------------------- /lib/tcpClient/checkStreamExists.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import esClient from 'node-eventstore-client'; 3 | import assert from 'assert'; 4 | 5 | const baseErr = 'Check stream exits - '; 6 | 7 | export default (config) => async (streamName) => { 8 | assert(streamName, `${baseErr}Stream Name not provided`); 9 | 10 | const connection = await connectionManager.create(config); 11 | try { 12 | const slice = await connection.readStreamEventsForward(streamName, 0, 1, true, config.credentials); 13 | if (slice.status === esClient.sliceReadStatus.StreamDeleted) throw new Error(`Stream hard deleted`); 14 | if (slice.status === esClient.sliceReadStatus.StreamNotFound) return false; 15 | return true; 16 | } finally { 17 | connection.releaseConnection(); 18 | } 19 | }; -------------------------------------------------------------------------------- /lib/tcpClient/connectionManager.js: -------------------------------------------------------------------------------- 1 | import { createConnection } from 'node-eventstore-client'; 2 | import genericPool from 'generic-pool'; 3 | import debugModule from 'debug'; 4 | import https from 'https'; 5 | 6 | const debug = debugModule('geteventstore:connectionManager'); 7 | const _uniqueConfigConnectionPools = []; 8 | 9 | // TODO: Remove terrible temporary hack till workaround can be found or tcp library support is added 10 | const connectWithoutValidation = async (client) => { 11 | const httpsRejectUnauthorizedPriorValue = https.globalAgent.options.rejectUnauthorized; 12 | try { 13 | https.globalAgent.options.rejectUnauthorized = false; 14 | await client.connect(); 15 | } finally { 16 | https.globalAgent.options.rejectUnauthorized = httpsRejectUnauthorizedPriorValue; 17 | } 18 | }; 19 | 20 | const isConnectionClosed = (client) => client._handler._state === 'closed'; 21 | 22 | const createConnectionPool = async (config, onConnected, isSubscription) => { 23 | const opts = config.poolOptions || { autostart: false }; 24 | if (isSubscription) { 25 | opts.min = 0; 26 | opts.max = 1; 27 | } 28 | 29 | const connectionPool = { 30 | config, 31 | onConnected, 32 | pool: genericPool.createPool({ 33 | async create() { 34 | const client = createConnection(config, (config.gossipSeeds || `${config.protocol}://${config.hostname}:${config.port}`), config.connectionNameGenerator && await config.connectionNameGenerator()); 35 | // TODO: remove temporary hack when tcp library supports https 36 | if (config.useSslConnection) client._endpointDiscoverer._httpService = https; 37 | 38 | if (isSubscription) { 39 | connectionPool.connectionName = client._connectionName; 40 | _uniqueConfigConnectionPools.push(connectionPool); 41 | } 42 | 43 | client.releaseConnection = () => connectionPool.pool.release(client); 44 | if (onConnected) { 45 | client.once('connected', () => { 46 | debug('', `${client._connectionName} - Connection Connected`); 47 | return onConnected(); 48 | }); 49 | } else { 50 | client.on('connected', () => debug('', `${client._connectionName} - Connection Connected`)); 51 | } 52 | client.on('disconnected', () => debug('', `${client._connectionName} - Connection Disconnected`)); 53 | client.on('reconnecting', () => debug('', `${client._connectionName} - Connection Reconnecting...`)); 54 | client.on('closed', reason => { 55 | debug('', `${client._connectionName} - Connection Closed: ${reason}`); 56 | connectionPool.pool.destroy(client).catch(() => {}); //Do Nothing 57 | }); 58 | client.on('error', err => { 59 | debug('', `${client._connectionName} - Connection Error: ${err.stack}`); 60 | console.error(`${client._connectionName} - EventStore: ${err.stack}`); 61 | try { 62 | this.destroy(client); 63 | } catch (ex) { 64 | //Do nothing 65 | } 66 | }); 67 | 68 | if (config.useSslConnection && !config.validateServer) await connectWithoutValidation(client); 69 | else await client.connect(); 70 | 71 | return client; 72 | }, 73 | destroy(client) { 74 | return client.close(); 75 | } 76 | }, opts) 77 | }; 78 | 79 | if (!isSubscription) _uniqueConfigConnectionPools.push(connectionPool); 80 | return connectionPool; 81 | }; 82 | 83 | export default { 84 | async create(config, onConnected, isSubscription = false) { 85 | let connectionPool = _uniqueConfigConnectionPools.find(pool => pool.config === config && pool.onConnected === onConnected); 86 | if (!connectionPool || isSubscription) connectionPool = await createConnectionPool(config, onConnected, isSubscription); 87 | 88 | let client = await connectionPool.pool.acquire(); 89 | //Handle closed connection when 'closed' event never emitted 90 | if (isConnectionClosed(client)) { 91 | try { 92 | await connectionPool.pool.destroy(client); 93 | } catch (err) { 94 | //do nothing 95 | } 96 | client = await connectionPool.pool.acquire(); 97 | } 98 | return client; 99 | }, 100 | async closeAllPools() { 101 | await Promise.all(_uniqueConfigConnectionPools.map(connectionPool => connectionPool.pool.clear())); 102 | _uniqueConfigConnectionPools.splice(0, _uniqueConfigConnectionPools.length); 103 | }, 104 | close(config) { 105 | return async (connectionName) => { 106 | const pool = await this.getPool(config)(connectionName); 107 | await pool.drain(); 108 | await pool.clear(); 109 | 110 | const poolIndex = _uniqueConfigConnectionPools.findIndex(_connectionPool => _connectionPool.pool === pool); 111 | if (poolIndex !== -1) _uniqueConfigConnectionPools.splice(poolIndex, 1); 112 | }; 113 | }, 114 | getPool(config) { 115 | return async (connectionName) => { 116 | const connectionPool = _uniqueConfigConnectionPools.find(pool => pool.config === config || (pool.connectionName && pool.connectionName === connectionName)); 117 | if (!connectionPool) throw new Error(`Connection Pool not found`); 118 | return connectionPool.pool; 119 | }; 120 | } 121 | }; -------------------------------------------------------------------------------- /lib/tcpClient/deleteStream.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import assert from 'assert'; 3 | 4 | const baseErr = 'Delete stream - '; 5 | 6 | export default (config) => async (streamName, hardDelete) => { 7 | assert(streamName, `${baseErr}Stream Name not provided`); 8 | 9 | hardDelete = hardDelete === undefined ? false : hardDelete; 10 | 11 | const connection = await connectionManager.create(config); 12 | try { 13 | return await connection.deleteStream(streamName, -2, hardDelete, config.credentials); 14 | } finally { 15 | connection.releaseConnection(); 16 | } 17 | }; -------------------------------------------------------------------------------- /lib/tcpClient/eventEnumerator.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import mapEvents from './utilities/mapEvents'; 3 | import debugModule from 'debug'; 4 | import assert from 'assert'; 5 | 6 | const debug = debugModule('geteventstore:eventEnumerator'); 7 | const baseErr = 'Event Enumerator - '; 8 | 9 | const getNextBatch = async (config, streamName, state, count, direction, resolveLinkTos) => { 10 | state.isFirstEnumeration = false; 11 | assert(streamName, `${baseErr}Stream Name not provided`); 12 | 13 | const connection = await connectionManager.create(config); 14 | 15 | function handleResult(result) { 16 | debug('', 'Result: %j', result); 17 | 18 | state.nextEventNumber = result.nextEventNumber.toNumber ? result.nextEventNumber.toNumber() : result.nextEventNumber; 19 | return { 20 | isEndOfStream: result.isEndOfStream, 21 | events: mapEvents(result.events) 22 | }; 23 | } 24 | 25 | try { 26 | if (direction === 'forward') return await connection.readStreamEventsForward(streamName, state.nextEventNumber, count, resolveLinkTos, config.credentials).then(handleResult); 27 | return await connection.readStreamEventsBackward(streamName, state.nextEventNumber, count, resolveLinkTos, config.credentials).then(handleResult); 28 | } finally { 29 | connection.releaseConnection(); 30 | } 31 | }; 32 | 33 | const esDirectionWorkaroundHandler = direction => { 34 | let wasSwopped = false; 35 | 36 | if (direction === 'forward') { 37 | wasSwopped = true; 38 | direction = 'backward'; 39 | } 40 | 41 | return { 42 | direction, 43 | swopResult(state, count, result) { 44 | if (wasSwopped) { 45 | state.nextEventNumber += count + 1; 46 | result.events.reverse(); 47 | } 48 | return result; 49 | } 50 | }; 51 | }; 52 | 53 | const stateHandler = direction => { 54 | class Handler { 55 | constructor() { 56 | this.isFirstEnumeration = true; 57 | this.setToFirst(); 58 | } 59 | 60 | setToFirst() { 61 | this.nextEventNumber = direction === 'forward' ? 0 : -1; 62 | } 63 | 64 | setToLast(count) { 65 | this.nextEventNumber = direction === 'forward' ? -1 : count - 1; 66 | } 67 | 68 | setToPrevious(count) { 69 | if (!this.isFirstEnumeration) 70 | this.adjustByLength(count); 71 | } 72 | 73 | keepInBoundsAdjustment(count) { 74 | if (direction === 'backward') 75 | return count; 76 | 77 | let adjustment = count; 78 | if (this.nextEventNumber < -1) { 79 | adjustment -= Math.abs(this.nextEventNumber); 80 | this.nextEventNumber = 0; 81 | } 82 | 83 | return adjustment; 84 | } 85 | 86 | adjustByLength(count) { 87 | this.nextEventNumber += direction === 'forward' ? count * -1 : count; 88 | } 89 | } 90 | 91 | return new Handler(); 92 | }; 93 | 94 | export default (config) => (streamName, direction, resolveLinkTos) => { 95 | direction = direction || 'forward'; 96 | resolveLinkTos = resolveLinkTos === undefined ? true : resolveLinkTos; 97 | const state = stateHandler(direction); 98 | 99 | return { 100 | first(count) { 101 | state.setToFirst(); 102 | return getNextBatch(config, streamName, state, count, direction, resolveLinkTos); 103 | }, 104 | last(count) { 105 | state.setToLast(count); 106 | 107 | const handler = esDirectionWorkaroundHandler(direction); 108 | return getNextBatch(config, streamName, state, count, handler.direction, resolveLinkTos).then(result => handler.swopResult(state, count, result)); 109 | }, 110 | previous(count) { 111 | state.setToPrevious(count); 112 | count = state.keepInBoundsAdjustment(count); 113 | 114 | return getNextBatch(config, streamName, state, count, direction, resolveLinkTos).then(result => { 115 | state.adjustByLength(count); 116 | return result; 117 | }); 118 | }, 119 | next(count) { 120 | return getNextBatch(config, streamName, state, count, direction, resolveLinkTos); 121 | } 122 | }; 123 | }; -------------------------------------------------------------------------------- /lib/tcpClient/getAllStreamEvents.js: -------------------------------------------------------------------------------- 1 | import flattenArray from '../utilities/flattenArray'; 2 | import connectionManager from './connectionManager'; 3 | import mapEvents from './utilities/mapEvents'; 4 | import debugModule from 'debug'; 5 | import assert from 'assert'; 6 | 7 | const debug = debugModule('geteventstore:getAllStreamEvents'); 8 | const baseErr = 'Get All Stream Events - '; 9 | 10 | export default (config) => async (streamName, chunkSize, startPosition, resolveLinkTos) => { 11 | assert(streamName, `${baseErr}Stream Name not provided`); 12 | 13 | chunkSize = chunkSize || 1000; 14 | if (chunkSize > 4096) { 15 | console.warn('WARNING: Max event chunk size exceeded. Using the max of 4096'); 16 | chunkSize = 4096; 17 | } 18 | resolveLinkTos = resolveLinkTos === undefined ? true : resolveLinkTos; 19 | 20 | const connection = await connectionManager.create(config); 21 | const events = []; 22 | async function getNextChunk(startPosition) { 23 | const result = await connection.readStreamEventsForward(streamName, startPosition, chunkSize, resolveLinkTos, config.credentials); 24 | debug('', 'Result: %j', result); 25 | 26 | if (result.error) { 27 | throw new Error(result.error); 28 | } 29 | 30 | events.push(result.events); 31 | 32 | if (result.isEndOfStream === false) { 33 | return getNextChunk(result.nextEventNumber); 34 | } 35 | return mapEvents(flattenArray(events)); 36 | } 37 | 38 | try { 39 | return await getNextChunk(startPosition || 0); 40 | } finally { 41 | connection.releaseConnection(); 42 | } 43 | }; -------------------------------------------------------------------------------- /lib/tcpClient/getEvents.js: -------------------------------------------------------------------------------- 1 | export default (readEventsForward, readEventsBackward) => async (streamName, startPosition, count, direction, resolveLinkTos) => { 2 | const readEvents = direction === 'backward' ? readEventsBackward : readEventsForward; 3 | return (await readEvents(streamName, startPosition, count, resolveLinkTos)).events; 4 | }; -------------------------------------------------------------------------------- /lib/tcpClient/getEventsByType.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | const baseErr = 'Get Events by Type - '; 4 | 5 | export default (getEvents) => async (streamName, eventTypes, startPosition, count, direction, resolveLinkTos) => { 6 | assert(eventTypes, `${baseErr}Event Types not provided`); 7 | 8 | const events = await getEvents(streamName, startPosition, count, direction, resolveLinkTos); 9 | return events.filter(event => eventTypes.includes(event.eventType)); 10 | }; -------------------------------------------------------------------------------- /lib/tcpClient/index.js: -------------------------------------------------------------------------------- 1 | import subscribeToStreamFrom from './subscribeToStreamFrom'; 2 | import getAllStreamEvents from './getAllStreamEvents'; 3 | import checkStreamExists from './checkStreamExists'; 4 | import subscribeToStream from './subscribeToStream'; 5 | import connectionManager from './connectionManager'; 6 | import getEventsByType from './getEventsByType'; 7 | import eventEnumerator from './eventEnumerator'; 8 | import deleteStream from './deleteStream'; 9 | import writeEvents from './writeEvents'; 10 | import writeEvent from './writeEvent'; 11 | import readEvents from './readEvents'; 12 | import getEvents from './getEvents'; 13 | 14 | import esClient from 'node-eventstore-client'; 15 | import cloneDeep from 'lodash.clonedeep'; 16 | import assert from 'assert'; 17 | import url from 'url'; 18 | 19 | const baseErr = 'geteventstore-promise - TCP client - '; 20 | 21 | export default class TCPClient { 22 | constructor(config) { 23 | assert(config, `${baseErr}config not provided`); 24 | if (config.gossipSeeds) { 25 | assert(Array.isArray(config.gossipSeeds), `${baseErr}gossipSeeds must be an array`); 26 | assert(config.gossipSeeds.length >= 3, `${baseErr}at least 3 gossipSeeds are required`); 27 | 28 | config.gossipSeeds.forEach(seed => { 29 | assert(seed.hostname, `${baseErr}gossip seed must have a hostname`); 30 | assert(seed.port, `${baseErr}gossip seed must have a port`); 31 | }); 32 | } else { 33 | assert(config.hostname, `${baseErr}hostname property not provided`); 34 | assert(config.port, `${baseErr}port property not provided`); 35 | } 36 | assert(config.credentials, `${baseErr}credentials property not provided`); 37 | assert(config.credentials.username, `${baseErr}credentials.username property not provided`); 38 | assert(config.credentials.password, `${baseErr}credentials.password property not provided`); 39 | if (config.connectionNameGenerator) assert(typeof config.connectionNameGenerator === 'function', `${baseErr}connectionNameGenerator must be a function`); 40 | 41 | //Add additional internal configuration properties 42 | const _config = cloneDeep(config); 43 | _config.protocol = _config.protocol || 'tcp'; 44 | _config.host = _config.hostname; 45 | _config.auth = `${_config.credentials.username}:${_config.credentials.password}`; 46 | _config.baseUrl = url.format(_config); 47 | 48 | if (_config.useSslConnection) _config.validateServer = _config.validateServer === undefined || _config.validateServer === null ? true : _config.validateServer; 49 | if (_config.gossipSeeds && _config.gossipSeeds.length > 0) { 50 | _config.gossipSeeds = _config.gossipSeeds.map(seed => new esClient.GossipSeed({ host: seed.hostname, port: seed.port }, seed.hostHeader)); 51 | } 52 | 53 | this.checkStreamExists = checkStreamExists(_config); 54 | this.writeEvent = writeEvent(_config); 55 | this.writeEvents = writeEvents(_config); 56 | this.getAllStreamEvents = getAllStreamEvents(_config); 57 | this.readEventsForward = readEvents(_config, 'forward'); 58 | this.readEventsBackward = readEvents(_config, 'backward'); 59 | this.getEvents = getEvents(this.readEventsForward, this.readEventsBackward); 60 | this.getEventsByType = getEventsByType(this.getEvents); 61 | this.deleteStream = deleteStream(_config); 62 | this.eventEnumerator = eventEnumerator(_config); 63 | this.subscribeToStream = subscribeToStream(_config); 64 | this.subscribeToStreamFrom = subscribeToStreamFrom(_config); 65 | this.close = connectionManager.close(_config); 66 | this.getPool = connectionManager.getPool(_config); 67 | this.closeAllPools = connectionManager.closeAllPools; 68 | } 69 | } -------------------------------------------------------------------------------- /lib/tcpClient/readEvents.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import mapEvents from './utilities/mapEvents'; 3 | import debugModule from 'debug'; 4 | import assert from 'assert'; 5 | 6 | const debug = debugModule('geteventstore:readEvents'); 7 | const baseErr = 'Read Events - '; 8 | 9 | export default (config, direction) => async (streamName, startPosition, count, resolveLinkTos) => { 10 | assert(streamName, `${baseErr}Stream Name not provided`); 11 | 12 | direction = direction || 'forward'; 13 | startPosition = startPosition === undefined && direction === 'backward' ? -1 : startPosition || 0; 14 | count = count || 1000; 15 | resolveLinkTos = resolveLinkTos === undefined ? true : resolveLinkTos; 16 | 17 | if (count > 4096) { 18 | console.warn('WARNING: Max event count exceeded. Using the max of 4096'); 19 | count = 4096; 20 | } 21 | 22 | const connection = await connectionManager.create(config); 23 | 24 | function handleResult(readResult) { 25 | debug('', 'Result: %j', readResult); 26 | if (readResult.error) throw new Error(readResult.error); 27 | 28 | const result = {}; 29 | for (let key in readResult) { 30 | if (result.key !== 'events') result[key] = readResult[key]; 31 | } 32 | result.events = mapEvents(readResult.events); 33 | return result; 34 | } 35 | 36 | try { 37 | if (direction === 'forward') return await connection.readStreamEventsForward(streamName, startPosition, count, resolveLinkTos, config.credentials).then(handleResult); 38 | return await connection.readStreamEventsBackward(streamName, startPosition, count, resolveLinkTos, config.credentials).then(handleResult); 39 | } finally { 40 | connection.releaseConnection(); 41 | } 42 | }; -------------------------------------------------------------------------------- /lib/tcpClient/subscribeToStream.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import mapEvents from './utilities/mapEvents'; 3 | import client from 'node-eventstore-client'; 4 | import debugModule from 'debug'; 5 | import assert from 'assert'; 6 | 7 | const debug = debugModule('geteventstore:subscribeToStream'); 8 | const baseErr = 'Subscribe to Stream - '; 9 | 10 | export default (config) => ( 11 | streamName, 12 | onEventAppeared, 13 | onDropped, 14 | resolveLinkTos = false 15 | ) => new Promise(async (resolve, reject) => { 16 | assert(streamName, `${baseErr}Stream Name not provided`); 17 | 18 | let connection; 19 | const onEvent = (sub, ev) => { 20 | const mappedEvent = mapEvents([ev])[0]; 21 | if (mappedEvent) onEventAppeared(sub, mappedEvent); 22 | }; 23 | 24 | const onConnected = async () => { 25 | try { 26 | const subscription = await connection.subscribeToStream(streamName, resolveLinkTos, onEvent, onDropped, new client.UserCredentials(config.credentials.username, config.credentials.password)); 27 | 28 | const originalClose = subscription.close; 29 | subscription.close = async function releaseSubConnectionFromPool() { 30 | await originalClose.call(subscription); 31 | await connection.releaseConnection(); 32 | await (connectionManager.close(config))(connection._connectionName); 33 | }; 34 | debug('', 'Subscription: %j', subscription); 35 | resolve(subscription); 36 | } catch (ex) { 37 | reject(ex); 38 | } 39 | }; 40 | 41 | try { 42 | connection = await connectionManager.create(config, onConnected, true); 43 | } catch (ex) { 44 | reject(ex); 45 | } 46 | }); -------------------------------------------------------------------------------- /lib/tcpClient/subscribeToStreamFrom.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import mapEvents from './utilities/mapEvents'; 3 | import client from 'node-eventstore-client'; 4 | import debugModule from 'debug'; 5 | import assert from 'assert'; 6 | 7 | const debug = debugModule('geteventstore:subscribeToStreamFrom'); 8 | const baseErr = 'Subscribe to Stream From - '; 9 | 10 | export default (config) => ( 11 | streamName, 12 | fromEventNumber, 13 | onEventAppeared, 14 | onLiveProcessingStarted, 15 | onDropped, 16 | settings 17 | ) => { 18 | settings = settings || {}; 19 | return new Promise(async (resolve, reject) => { 20 | assert(streamName, `${baseErr}Stream Name not provided`); 21 | if (!fromEventNumber) fromEventNumber = -1; 22 | 23 | let connection; 24 | const onEvent = (sub, ev) => { 25 | const mappedEvent = mapEvents([ev])[0]; 26 | if (mappedEvent) onEventAppeared(sub, mappedEvent); 27 | }; 28 | 29 | const onConnected = async () => { 30 | try { 31 | const subscription = await connection.subscribeToStreamFrom(streamName, fromEventNumber, settings.resolveLinkTos, onEvent, onLiveProcessingStarted, onDropped, new client.UserCredentials(config.credentials.username, config.credentials.password), settings.readBatchSize); 32 | subscription.close = async () => { 33 | subscription.stop(); 34 | await connection.releaseConnection(); 35 | await connectionManager.close(config)(subscription._connection._connectionName); 36 | }; 37 | debug('', 'Subscription: %j', subscription); 38 | resolve(subscription); 39 | } catch (ex) { 40 | reject(ex); 41 | } 42 | }; 43 | 44 | try { 45 | connection = await connectionManager.create(config, onConnected, true); 46 | } catch (ex) { 47 | reject(ex); 48 | } 49 | }); 50 | }; -------------------------------------------------------------------------------- /lib/tcpClient/utilities/mapEvents.js: -------------------------------------------------------------------------------- 1 | export default (events) => events.filter(ev => ev.event).map(ev => { 2 | const event = ev.event; 3 | const link = ev.link; 4 | 5 | const mappedEvent = { 6 | streamId: event.eventStreamId, 7 | eventId: event.eventId, 8 | eventNumber: event.eventNumber.toNumber ? event.eventNumber.toNumber() : event.eventNumber, 9 | eventType: event.eventType, 10 | created: event.created.toISOString(), 11 | metadata: event.metadata.toString(), 12 | isJson: event.isJson, 13 | data: event.isJson ? JSON.parse(event.data.toString()) : event.data.toString() 14 | }; 15 | 16 | if (mappedEvent.metadata) mappedEvent.metadata = JSON.parse(mappedEvent.metadata); 17 | 18 | if (link) { 19 | mappedEvent.positionStreamId = link.eventStreamId; 20 | mappedEvent.positionEventId = link.eventId; 21 | mappedEvent.positionEventNumber = link.eventNumber.toNumber ? link.eventNumber.toNumber() : link.eventNumber; 22 | mappedEvent.positionCreated = link.created; 23 | } 24 | return mappedEvent; 25 | }); -------------------------------------------------------------------------------- /lib/tcpClient/writeEvent.js: -------------------------------------------------------------------------------- 1 | import generateEventId from '../utilities/generateEventId'; 2 | import connectionManager from './connectionManager'; 3 | import client from 'node-eventstore-client'; 4 | import debugModule from 'debug'; 5 | import assert from 'assert'; 6 | 7 | const debug = debugModule('geteventstore:writeEvent'); 8 | const baseErr = 'Write Event - '; 9 | 10 | export default (config) => async (streamName, eventType, data, metaData, options) => { 11 | assert(streamName, `${baseErr}Stream Name not provided`); 12 | assert(eventType, `${baseErr}Event Type not provided`); 13 | assert(data, `${baseErr}Event Data not provided`); 14 | 15 | options = options || {}; 16 | options.expectedVersion = !Number.isInteger(options.expectedVersion) ? -2 : options.expectedVersion; 17 | 18 | const event = client.createJsonEventData(generateEventId(), data, metaData, eventType); 19 | const connection = await connectionManager.create(config); 20 | 21 | try { 22 | const result = await connection.appendToStream(streamName, options.expectedVersion, [event], config.credentials); 23 | debug('', 'Result: %j', result); 24 | if (result.error) throw new Error(result.error); 25 | return result; 26 | } finally { 27 | connection.releaseConnection(); 28 | } 29 | }; -------------------------------------------------------------------------------- /lib/tcpClient/writeEvents.js: -------------------------------------------------------------------------------- 1 | import connectionManager from './connectionManager'; 2 | import chunkArray from '../utilities/chunkArray'; 3 | import client from 'node-eventstore-client'; 4 | import debugModule from 'debug'; 5 | import assert from 'assert'; 6 | 7 | const debug = debugModule('geteventstore:writeEvents'); 8 | const baseErr = 'Write Events - '; 9 | 10 | export default (config) => async (streamName, events, options) => { 11 | assert(streamName, `${baseErr}Stream Name not provided`); 12 | assert(events, `${baseErr}Events not provided`); 13 | assert.equal(true, events.constructor === Array, `${baseErr}Events should be an array`); 14 | 15 | if (events.length === 0) return; 16 | 17 | options = options || {}; 18 | options.transactionWriteSize = options.transactionWriteSize || 250; 19 | options.expectedVersion = !Number.isInteger(options.expectedVersion) ? -2 : options.expectedVersion; 20 | 21 | const eventsToWrite = events.map(ev => client.createJsonEventData(ev.eventId, ev.data, ev.metadata, ev.eventType)); 22 | const connection = await connectionManager.create(config); 23 | let transaction; 24 | try { 25 | transaction = await connection.startTransaction(streamName, options.expectedVersion, config.credentials); 26 | 27 | const eventChunks = chunkArray(eventsToWrite, options.transactionWriteSize); 28 | for (let i = 0; i < eventChunks.length; i++) { 29 | await transaction.write(eventChunks[i]); 30 | } 31 | 32 | const result = await transaction.commit(); 33 | debug('', 'Result: %j', result); 34 | return result; 35 | } catch (err) { 36 | if (transaction) await transaction.rollback(); 37 | throw err; 38 | } finally { 39 | connection.releaseConnection(); 40 | } 41 | }; -------------------------------------------------------------------------------- /lib/utilities/chunkArray.js: -------------------------------------------------------------------------------- 1 | export default (array, chunkSize) => Array(Math.ceil(array.length / chunkSize)).fill().map((_, index) => index * chunkSize).map(begin => array.slice(begin, begin + chunkSize)); -------------------------------------------------------------------------------- /lib/utilities/createHttpClient.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import https from 'https'; 3 | 4 | export default (config) => axios.create({ 5 | headers: { Accept: 'application/json' }, 6 | httpsAgent: !config.validateServer && new https.Agent({ rejectUnauthorized: false }) 7 | }); -------------------------------------------------------------------------------- /lib/utilities/flattenArray.js: -------------------------------------------------------------------------------- 1 | export default (arr) => arr.reduce((flat, next) => flat.concat(next), []); -------------------------------------------------------------------------------- /lib/utilities/generateEventId.js: -------------------------------------------------------------------------------- 1 | import { v4 as generateId } from 'uuid'; 2 | export default () => generateId(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geteventstore-promise", 3 | "version": "4.0.1", 4 | "description": "GetEventStore client wrapper using promises", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "yarn test:insecure && yarn test:secure", 8 | "test:insecure": "cross-env NODE_ENV=test mocha --exit --require @babel/register tests/", 9 | "test:secure": "cross-env NODE_ENV=test TESTS_RUN_SECURE=true mocha --exit --require @babel/register tests/", 10 | "coverage": "nyc yarn test", 11 | "build": "babel --delete-dir-on-start -d dist/ lib" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/RemoteMetering/geteventstore-promise.git" 16 | }, 17 | "keywords": [ 18 | "node", 19 | "client", 20 | "event store", 21 | "geteventstore", 22 | "eventstore", 23 | "promise" 24 | ], 25 | "author": "Remote Metering Solutions Team", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/RemoteMetering/geteventstore-promise/issues" 29 | }, 30 | "homepage": "https://github.com/RemoteMetering/geteventstore-promise", 31 | "dependencies": { 32 | "@babel/runtime": "^7.18.3", 33 | "axios": "^0.27.2", 34 | "debug": "^4.3.4", 35 | "generic-pool": "^3.8.2", 36 | "lodash.clonedeep": "^4.5.0", 37 | "node-eventstore-client": "^0.2.18", 38 | "uuid": "^8.3.2" 39 | }, 40 | "devDependencies": { 41 | "@babel/cli": "^7.17.10", 42 | "@babel/core": "^7.18.2", 43 | "@babel/plugin-transform-runtime": "^7.18.2", 44 | "@babel/preset-env": "^7.18.2", 45 | "@babel/register": "^7.17.7", 46 | "babel-plugin-add-module-exports": "^1.0.4", 47 | "cross-env": "^7.0.3", 48 | "mocha": "^10.0.0", 49 | "nyc": "^15.1.0" 50 | }, 51 | "nyc": { 52 | "reporter": [ 53 | "lcov", 54 | "text", 55 | "html" 56 | ], 57 | "include": [ 58 | "lib/*" 59 | ], 60 | "exclude": [ 61 | "lib/AggRoot.js" 62 | ], 63 | "all": true 64 | } 65 | } -------------------------------------------------------------------------------- /tests/_globalHooks.js: -------------------------------------------------------------------------------- 1 | import sleep from './utilities/sleep'; 2 | import { spawn } from 'child_process'; 3 | import path from 'path'; 4 | 5 | global.runningTestsInSecureMode = process.env.TESTS_RUN_SECURE === 'true'; 6 | const securityMode = global.runningTestsInSecureMode ? 'secure' : 'insecure'; 7 | 8 | console.log(`Running tests in \x1b[36m${securityMode}\x1b[0m mode...`); 9 | 10 | const singleComposeFileLocation = path.join(__dirname, 'support', 'single', `docker-compose-${securityMode}.yml`); 11 | const clusterComposeFileLocation = path.join(__dirname, 'support', 'cluster', `docker-compose-${securityMode}.yml`); 12 | let eventstore; 13 | 14 | const startStack = async (filePath) => new Promise((resolve, reject) => { 15 | const proc = spawn('docker-compose', ['--file', filePath, 'up', '-d'], { 16 | cwd: undefined, 17 | stdio: ['ignore', 'ignore', process.stderr] 18 | }); 19 | 20 | proc.on('close', code => code === 0 ? resolve() : reject(code)); 21 | }); 22 | 23 | const removeStack = async (filePath) => new Promise((resolve, reject) => { 24 | const proc = spawn('docker-compose', ['--file', filePath, 'down', '--remove-orphans'], { 25 | cwd: undefined, 26 | stdio: ['ignore', 'ignore', process.stderr] 27 | }); 28 | 29 | proc.on('close', code => { 30 | if (code === 0) resolve(); 31 | else reject(); 32 | }); 33 | }); 34 | 35 | const isContainerReady = async (containerName, readyOutputMatch) => new Promise((resolve) => { 36 | const proc = spawn('docker', ['logs', containerName], { cwd: undefined }); 37 | proc.stdout.on('data', line => line.toString().includes(readyOutputMatch) && resolve(true)); 38 | proc.on('close', () => resolve(false)); 39 | }); 40 | 41 | before(async function () { 42 | this.timeout(60 * 1000); 43 | if (eventstore) return; 44 | 45 | console.log('Starting EventStoreDB stacks...'); 46 | 47 | await Promise.all([removeStack(singleComposeFileLocation), removeStack(clusterComposeFileLocation)]); 48 | await Promise.all([startStack(singleComposeFileLocation), startStack(clusterComposeFileLocation)]); 49 | 50 | while (true) { 51 | const [isSingleReady, isClusterReady] = await Promise.all([ 52 | isContainerReady('geteventstore_promise_test_single.eventstore', `'"$streams"' projection source has been written`), 53 | isContainerReady('geteventstore_promise_test_cluster_node1.eventstore', ' [Leader') 54 | ]); 55 | if (isSingleReady && isClusterReady) break; 56 | await sleep(100); 57 | } 58 | await sleep(1000); 59 | }); 60 | 61 | after(async function () { 62 | this.timeout(60 * 1000); 63 | console.log('Killing EventStoreDB stacks...'); 64 | await Promise.all([removeStack(singleComposeFileLocation), removeStack(clusterComposeFileLocation)]); 65 | }); -------------------------------------------------------------------------------- /tests/http.checkStreamExists.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | describe('Http Client - Check Stream Exist', () => { 9 | it('Should return true when a stream exists', async () => { 10 | const client = new EventStore.HTTPClient(getHttpConfig()); 11 | 12 | const testStream = `TestStream-${generateEventId()}`; 13 | await client.writeEvent(testStream, 'TestEventType', { 14 | something: '123' 15 | }); 16 | 17 | assert.equal(await client.checkStreamExists(testStream), true); 18 | }).timeout(5000); 19 | 20 | it('Should return false when a stream does not exist', async () => { 21 | const client = new EventStore.HTTPClient(getHttpConfig()); 22 | assert.equal(await client.checkStreamExists('Non_existentStream'), false); 23 | }); 24 | 25 | it('Should return rejected promise when the request error is anything other than a 404', callback => { 26 | const httpConfig = getHttpConfig(); 27 | httpConfig.port = 1; 28 | const client = new EventStore.HTTPClient(httpConfig); 29 | 30 | client.checkStreamExists('Non_existentStream_wrong_port_config').then(() => { 31 | callback('Should not have returned successful promise'); 32 | }).catch(err => { 33 | assert(err, 'No error received'); 34 | assert(err.message.includes('ECONNREFUSED'), 'Connection refused error expected'); 35 | callback(); 36 | }); 37 | }).timeout(5000); 38 | 39 | it('Should throw an exception when timeout is reached', callback => { 40 | const httpConfig = getHttpConfig(); 41 | httpConfig.timeout = 0.00001; 42 | 43 | const client = new EventStore.HTTPClient(httpConfig); 44 | const testStream = `TestStream-${generateEventId()}`; 45 | client.writeEvent(testStream, 'TestEventType', { 46 | something: '123' 47 | }).then(() => client.checkStreamExists(testStream).then(() => { 48 | callback('Expected to fail'); 49 | })).catch(err => { 50 | if (err.message.includes('timeout')) callback(); 51 | else callback('Time out error expected'); 52 | }); 53 | }); 54 | }); -------------------------------------------------------------------------------- /tests/http.config.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getHttpConfig from './support/getHttpConfig'; 4 | import EventStore from '../lib'; 5 | import assert from 'assert'; 6 | 7 | describe('Http Client - Config', () => { 8 | it('Should return assertion error when config is undefined', done => { 9 | try { 10 | new EventStore.HTTPClient(); 11 | done('Config should not pass assertion'); 12 | } catch (err) { 13 | assert.equal(err === undefined, false); 14 | assert.equal(err.message, 'geteventstore-promise - HTTP client - config not provided'); 15 | done(); 16 | } 17 | }); 18 | 19 | it('Should return assertion error when hostname is undefined', done => { 20 | try { 21 | const config = { 22 | port: 2113, 23 | credentials: { 24 | username: 'admin', 25 | password: 'changeit' 26 | } 27 | }; 28 | new EventStore.HTTPClient(config); 29 | done(); 30 | } catch (err) { 31 | assert.equal(err === undefined, false); 32 | assert.equal(err.message, 'geteventstore-promise - HTTP client - hostname property not provided'); 33 | done(); 34 | } 35 | }); 36 | 37 | it('Should return assertion error when credentials are undefined', done => { 38 | try { 39 | const config = { 40 | hostname: 'localhost', 41 | port: 2113 42 | }; 43 | new EventStore.HTTPClient(config); 44 | done(); 45 | } catch (err) { 46 | assert.equal(err === undefined, false); 47 | assert.equal(err.message, 'geteventstore-promise - HTTP client - credentials property not provided'); 48 | done(); 49 | } 50 | }); 51 | 52 | it('Should return http client when config is complete', done => { 53 | try { 54 | const client = new EventStore.HTTPClient(getHttpConfig()); 55 | assert.equal(client !== undefined, true); 56 | done(); 57 | } catch (err) { 58 | done(err); 59 | } 60 | }); 61 | }); -------------------------------------------------------------------------------- /tests/http.deleteStream.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | describe('Http Client - Delete stream', () => { 10 | it('Should return successful on stream delete', async () => { 11 | const client = new EventStore.HTTPClient(getHttpConfig()); 12 | const testStream = `TestStream-${generateEventId()}`; 13 | 14 | await client.writeEvent(testStream, 'TestEventType', { 15 | something: '123' 16 | }); 17 | await client.deleteStream(testStream); 18 | await sleep(100); 19 | assert.equal(await client.checkStreamExists(testStream), false); 20 | }); 21 | 22 | it('Should return successful on projected stream delete', async () => { 23 | const client = new EventStore.HTTPClient(getHttpConfig()); 24 | const testStream = `TestDeletedStream-${generateEventId()}`; 25 | 26 | await client.writeEvent(testStream, 'TestEventType', { 27 | something: '123' 28 | }); 29 | await sleep(150); 30 | await client.deleteStream(`$ce-TestDeletedStream`); 31 | await sleep(100); 32 | assert.equal(await client.checkStreamExists(`$ce-TestDeletedStream`), false); 33 | }); 34 | 35 | it('Should return successful on writing to a stream that has been soft deleted', async () => { 36 | const client = new EventStore.HTTPClient(getHttpConfig()); 37 | const testStream = `TestStream-${generateEventId()}`; 38 | 39 | await client.writeEvent(testStream, 'TestEventType', { 40 | something: '123' 41 | }); 42 | await client.deleteStream(testStream); 43 | await sleep(100); 44 | return client.writeEvent(testStream, 'TestEventType', { 45 | something: '456' 46 | }); 47 | }); 48 | 49 | it('Should return successful on stream delete hard delete', callback => { 50 | const client = new EventStore.HTTPClient(getHttpConfig()); 51 | const testStream = `TestStream-${generateEventId()}`; 52 | 53 | client.writeEvent(testStream, 'TestEventType', { 54 | something: '123' 55 | }).then(() => client.deleteStream(testStream, true).then(() => client.checkStreamExists(testStream).then(() => { 56 | callback('Should not have returned resolved promise'); 57 | }).catch(err => { 58 | assert(err.message.includes('410'), 'Expected http 410'); 59 | callback(); 60 | })).catch(callback)).catch(callback); 61 | }); 62 | 63 | it('Should fail when a stream does not exist', () => { 64 | const client = new EventStore.HTTPClient(getHttpConfig()); 65 | const testStream = `TestStream-${generateEventId()}`; 66 | 67 | return client.deleteStream(testStream).then(() => { 68 | assert.fail('Should have failed because stream does not exist'); 69 | }).catch(err => { 70 | assert(err); 71 | }); 72 | }); 73 | 74 | it('Should return HTTP 410 when a writing to a stream that has been hard deleted', () => { 75 | const client = new EventStore.HTTPClient(getHttpConfig()); 76 | const testStream = `TestStream-${generateEventId()}`; 77 | 78 | return client.writeEvent(testStream, 'TestEventType', { 79 | something: '123' 80 | }).then(() => client.deleteStream(testStream, true).then(() => client.writeEvent(testStream, 'TestEventType', { 81 | something: '456' 82 | }).then(() => { 83 | assert.fail('Should have failed because stream does not exist'); 84 | })).catch(err => { 85 | assert.equal(410, err.response.status); 86 | })); 87 | }); 88 | }); -------------------------------------------------------------------------------- /tests/http.getAllStreamEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('Http Client - Get All Stream Events', () => { 11 | it('Should write events and read back all stream events', async() => { 12 | const client = new EventStore.HTTPClient(getHttpConfig()); 13 | 14 | const events = []; 15 | for (let k = 0; k < 1000; k++) { 16 | events.push(eventFactory.newEvent('TestEventType', { 17 | id: k 18 | })); 19 | } 20 | 21 | const testStream = `TestStream-${generateEventId()}`; 22 | await client.writeEvents(testStream, events); 23 | const allEvents = await client.getAllStreamEvents(testStream); 24 | assert.equal(allEvents.length, 1000); 25 | assert(allEvents[0].created, 'Created should be defined'); 26 | assert.equal(allEvents[0].data.id, 0); 27 | assert.equal(allEvents[999].data.id, 999); 28 | }).timeout(5000); 29 | 30 | it('Should write events and read back all events from start event', async() => { 31 | const client = new EventStore.HTTPClient(getHttpConfig()); 32 | 33 | const events = []; 34 | for (let k = 0; k < 1000; k++) { 35 | events.push(eventFactory.newEvent('TestEventType', { 36 | id: k 37 | })); 38 | } 39 | 40 | const testStream = `TestStream-${generateEventId()}`; 41 | await client.writeEvents(testStream, events); 42 | const allEvents = await client.getAllStreamEvents(testStream, 250, 500); 43 | assert.equal(allEvents.length, 500); 44 | assert.equal(allEvents[0].data.id, 500); 45 | assert.equal(allEvents[499].data.id, 999); 46 | }).timeout(5000); 47 | 48 | it('Should write events and read back stream events with embed type rich', async() => { 49 | const client = new EventStore.HTTPClient(getHttpConfig()); 50 | 51 | const events = []; 52 | for (let k = 0; k < 1000; k++) { 53 | events.push(eventFactory.newEvent('TestEventType', { 54 | id: k 55 | })); 56 | } 57 | 58 | const testStream = `TestStream-${generateEventId()}`; 59 | await client.writeEvents(testStream, events); 60 | const allEvents = await client.getAllStreamEvents(testStream, 1000, 0, true, 'rich'); 61 | assert.equal(allEvents.length, 1000); 62 | assert.equal(allEvents[0].data, undefined); 63 | }).timeout(5000); 64 | }); -------------------------------------------------------------------------------- /tests/http.getEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('Http Client - Get Events', () => { 11 | const testStream = `TestStream-${generateEventId()}`; 12 | const numberOfEvents = 10; 13 | 14 | before(() => { 15 | const client = new EventStore.HTTPClient(getHttpConfig()); 16 | 17 | const events = []; 18 | for (let i = 1; i <= numberOfEvents; i++) { 19 | events.push(eventFactory.newEvent('TestEventType', { 20 | something: i 21 | })); 22 | } 23 | return client.writeEvents(testStream, events); 24 | }); 25 | 26 | it('Should get events reading forward', async () => { 27 | const client = new EventStore.HTTPClient(getHttpConfig()); 28 | 29 | const events = await client.getEvents(testStream, undefined, undefined, 'forward'); 30 | assert.equal(events.length, 10); 31 | assert(events[0].created, 'Created should be defined'); 32 | assert.equal(events[0].data.something, 1); 33 | }); 34 | 35 | it('Should get events reading backward', async () => { 36 | const client = new EventStore.HTTPClient(getHttpConfig()); 37 | 38 | const events = await client.getEvents(testStream, 'head', undefined, 'backward'); 39 | assert.equal(events.length, 10); 40 | assert.equal(events[0].data.something, 10); 41 | }); 42 | }); -------------------------------------------------------------------------------- /tests/http.persistentSubscriptions.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import chunkArray from '../lib/utilities/chunkArray'; 5 | import getHttpConfig from './support/getHttpConfig'; 6 | import sleep from './utilities/sleep'; 7 | import EventStore from '../lib'; 8 | import assert from 'assert'; 9 | 10 | const eventFactory = new EventStore.EventFactory(); 11 | 12 | describe('HTTP Client - Persistent Subscription', () => { 13 | it('Should get and ack first batch of events written to a stream', async function() { 14 | this.timeout(15 * 1000); 15 | const client = new EventStore.HTTPClient(getHttpConfig()); 16 | const testStream = `TestStream-${generateEventId()}`; 17 | 18 | const events = []; 19 | for (let k = 0; k < 10; k++) { 20 | events.push(eventFactory.newEvent('TestEventType', { 21 | id: k 22 | })); 23 | } 24 | 25 | const testSubscriptionName = testStream; 26 | 27 | await client.writeEvents(testStream, events); 28 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 29 | 30 | const result = await client.persistentSubscriptions.getEvents(testSubscriptionName, testStream, 10); 31 | assert.equal(10, result.entries.length); 32 | await result.ackAll(); 33 | 34 | const psEvents = await client.persistentSubscriptions.getEvents(testSubscriptionName, testStream, 10); 35 | assert.equal(0, psEvents.entries.length); 36 | }); 37 | 38 | it('Should ack and nack messages individually', async function() { 39 | this.timeout(20 * 1000); 40 | const client = new EventStore.HTTPClient(getHttpConfig()); 41 | const testStream = `TestStream-${generateEventId()}`; 42 | 43 | const events = []; 44 | for (let k = 0; k < 12; k++) { 45 | events.push(eventFactory.newEvent('TestEventType', { 46 | id: k 47 | })); 48 | } 49 | 50 | const testSubscriptionName = testStream; 51 | 52 | await client.writeEvents(testStream, events); 53 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 54 | await sleep(1000); 55 | const result = await client.persistentSubscriptions.getEvents(testSubscriptionName, testStream, 100); 56 | assert.equal(12, result.entries.length); 57 | const chunks = chunkArray(result.entries, 4); 58 | 59 | await Promise.all(chunks[0].map(entry => entry.nack())); 60 | await Promise.all(chunks[1].map(entry => entry.ack())); 61 | await Promise.all(chunks[2].map(entry => entry.nack())); 62 | 63 | const psEvents = await client.persistentSubscriptions.getEvents(testSubscriptionName, testStream, 10); 64 | assert.equal(8, psEvents.entries.length); 65 | assert.equal(0, psEvents.entries[7].event.data.id); 66 | assert.equal(1, psEvents.entries[6].event.data.id); 67 | assert.equal(2, psEvents.entries[5].event.data.id); 68 | assert.equal(3, psEvents.entries[4].event.data.id); 69 | assert.equal(8, psEvents.entries[3].event.data.id); 70 | assert.equal(9, psEvents.entries[2].event.data.id); 71 | assert.equal(10, psEvents.entries[1].event.data.id); 72 | assert.equal(11, psEvents.entries[0].event.data.id); 73 | }); 74 | 75 | it('Should update persistent subscription ', async function() { 76 | this.timeout(15 * 1000); 77 | const client = new EventStore.HTTPClient(getHttpConfig()); 78 | const testStream = `TestStream-${generateEventId()}`; 79 | 80 | const events = []; 81 | for (let k = 0; k < 12; k++) { 82 | events.push(eventFactory.newEvent('TestEventType', { 83 | id: k 84 | })); 85 | } 86 | 87 | const testSubscriptionName = testStream; 88 | 89 | await client.writeEvents(testStream, events); 90 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 91 | 92 | const options = { 93 | readBatchSize: 121, 94 | resolveLinkTos: true 95 | }; 96 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream, options); 97 | 98 | const result = await client.persistentSubscriptions.getSubscriptionInfo(testSubscriptionName, testStream); 99 | assert.equal(options.readBatchSize, result.config.readBatchSize); 100 | assert.equal(options.resolveLinkTos, result.config.resolveLinktos); 101 | }); 102 | 103 | it('Should delete persistent subscription', function(done) { 104 | this.timeout(15 * 1000); 105 | const client = new EventStore.HTTPClient(getHttpConfig()); 106 | const testStream = `TestStream-${generateEventId()}`; 107 | 108 | const events = []; 109 | for (let k = 0; k < 12; k++) { 110 | events.push(eventFactory.newEvent('TestEventType', { 111 | id: k 112 | })); 113 | } 114 | 115 | const testSubscriptionName = testStream; 116 | 117 | client.writeEvents(testStream, events).then(() => client.persistentSubscriptions.assert(testSubscriptionName, testStream).then(() => client.persistentSubscriptions.remove(testSubscriptionName, testStream).then(() => client.persistentSubscriptions.getEvents(testSubscriptionName, testStream, 10).then(() => { 118 | done('Should have not gotten events'); 119 | }).catch(err => { 120 | try { 121 | assert.equal(404, err.response.status, 'Should have received 404'); 122 | } catch (ex) { 123 | return done(ex.message); 124 | } 125 | done(); 126 | })))).catch(done); 127 | }); 128 | 129 | it('Should return persistent subscription info', async function() { 130 | this.timeout(15 * 1000); 131 | const client = new EventStore.HTTPClient(getHttpConfig()); 132 | const testStream = `TestStream-${generateEventId()}`; 133 | 134 | const events = []; 135 | for (let k = 0; k < 10; k++) { 136 | events.push(eventFactory.newEvent('TestEventType', { 137 | id: k 138 | })); 139 | } 140 | 141 | const testSubscriptionName = testStream; 142 | 143 | await client.writeEvents(testStream, events); 144 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 145 | 146 | const result = await client.persistentSubscriptions.getSubscriptionInfo(testSubscriptionName, testStream); 147 | assert.equal(testSubscriptionName, result.groupName); 148 | assert.equal(testStream, result.eventStreamId); 149 | }); 150 | 151 | it('Should return persistent subscriptions info for a stream', async function() { 152 | this.timeout(15 * 1000); 153 | const client = new EventStore.HTTPClient(getHttpConfig()); 154 | const testStream = `TestStream-${generateEventId()}`; 155 | 156 | const events = []; 157 | for (let k = 0; k < 10; k++) { 158 | events.push(eventFactory.newEvent('TestEventType', { 159 | id: k 160 | })); 161 | } 162 | 163 | const testSubscriptionName = testStream; 164 | 165 | await client.writeEvents(testStream, events); 166 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 167 | 168 | const results = await client.persistentSubscriptions.getStreamSubscriptionsInfo(testStream); 169 | assert.equal(1, results.length); 170 | }); 171 | 172 | it('Should return persistent subscriptions info for all', async function() { 173 | this.timeout(15 * 1000); 174 | const client = new EventStore.HTTPClient(getHttpConfig()); 175 | const testStream = `TestStream-${generateEventId()}`; 176 | 177 | const events = []; 178 | for (let k = 0; k < 10; k++) { 179 | events.push(eventFactory.newEvent('TestEventType', { 180 | id: k 181 | })); 182 | } 183 | 184 | const testSubscriptionName = testStream; 185 | 186 | await client.writeEvents(testStream, events); 187 | await client.persistentSubscriptions.assert(testSubscriptionName, testStream); 188 | const results = await client.persistentSubscriptions.getAllSubscriptionsInfo(); 189 | assert.equal(6, results.length); 190 | }); 191 | }); -------------------------------------------------------------------------------- /tests/http.ping.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getHttpConfig from './support/getHttpConfig'; 4 | import EventStore from '../lib'; 5 | import assert from 'assert'; 6 | 7 | describe('Http Client - Ping', () => { 8 | it('Should return successful when OK', () => { 9 | const client = new EventStore.HTTPClient(getHttpConfig()); 10 | return client.ping(); 11 | }); 12 | 13 | it('Should fail when not OK', function() { 14 | this.timeout(30000); 15 | const config = getHttpConfig(); 16 | config.hostname = 'MadeToFailHostName'; 17 | 18 | const client = new EventStore.HTTPClient(config); 19 | 20 | return client.ping().then(() => { 21 | throw new Error('Should not succeed'); 22 | }).catch(err => { 23 | assert(err, 'Error expected'); 24 | assert(err.message, 'Error Message Expected'); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /tests/http.projections.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import assert from 'assert'; 4 | import fs from 'fs'; 5 | 6 | import EventStore from '../lib'; 7 | import generateEventId from '../lib/utilities/generateEventId'; 8 | import getHttpConfig from './support/getHttpConfig'; 9 | import sleep from './utilities/sleep'; 10 | 11 | describe('Projections', () => { 12 | describe('Default Settings', () => { 13 | const assertionProjection = generateEventId(); 14 | const assertionProjectionContent = fs.readFileSync(`${__dirname}/support/testProjection.js`, { 15 | encoding: 'utf8' 16 | }); 17 | 18 | it('Should create continuous projection', async function () { 19 | this.timeout(10 * 1000); 20 | const client = new EventStore.HTTPClient(getHttpConfig()); 21 | 22 | const response = await client.projections.assert(assertionProjection, assertionProjectionContent); 23 | assert.equal(response.name, assertionProjection); 24 | 25 | const responseWithTrackEmittedStreamsEnabled = await client.projections.getInfo(assertionProjection, true); 26 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.emitEnabled, false); 27 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.trackEmittedStreams, false); 28 | }); 29 | 30 | it('Should update existing projection', async function () { 31 | this.timeout(10 * 1000); 32 | const client = new EventStore.HTTPClient(getHttpConfig()); 33 | 34 | const response = await client.projections.assert(assertionProjection, assertionProjectionContent); 35 | assert.equal(response.name, assertionProjection); 36 | }); 37 | 38 | it('Should stop projection', async function () { 39 | this.timeout(10 * 1000); 40 | const client = new EventStore.HTTPClient(getHttpConfig()); 41 | 42 | const response = await client.projections.stop(assertionProjection); 43 | assert.equal(response.name, assertionProjection); 44 | const projectionInfo = await client.projections.getInfo(assertionProjection); 45 | assert.equal(projectionInfo.status, 'Stopped'); 46 | }); 47 | 48 | it('Should start projection', async function () { 49 | this.timeout(10 * 1000); 50 | const client = new EventStore.HTTPClient(getHttpConfig()); 51 | 52 | const response = await client.projections.start(assertionProjection); 53 | assert.equal(response.name, assertionProjection); 54 | const projectionInfo = await client.projections.getInfo(assertionProjection); 55 | assert.equal(projectionInfo.status, 'Running'); 56 | }); 57 | 58 | it('Should reset projection', async function () { 59 | this.timeout(10 * 1000); 60 | const client = new EventStore.HTTPClient(getHttpConfig()); 61 | 62 | const response = await client.projections.reset(assertionProjection); 63 | assert.equal(response.name, assertionProjection); 64 | const projectionInfo = await client.projections.getInfo(assertionProjection); 65 | assert(['Preparing/Stopped', 'Running'].includes(projectionInfo.status), `Invalid status after reset: ${projectionInfo.status}`); 66 | }); 67 | 68 | it('Should remove continuous projection', async function () { 69 | this.timeout(10 * 1000); 70 | const client = new EventStore.HTTPClient(getHttpConfig()); 71 | 72 | const stopResponse = await client.projections.stop(assertionProjection); 73 | await sleep(1000); 74 | 75 | assert.equal(stopResponse.name, assertionProjection); 76 | const removeResponse = await client.projections.remove(assertionProjection); 77 | assert.equal(removeResponse.name, assertionProjection); 78 | }); 79 | }); 80 | 81 | describe('Custom Settings', () => { 82 | const assertionProjection = generateEventId(); 83 | const assertionProjectionContent = fs.readFileSync(`${__dirname}/support/testProjection.js`, { 84 | encoding: 'utf8' 85 | }); 86 | 87 | it('Should create one-time projection with all settings enabled', async function () { 88 | this.timeout(10 * 1000); 89 | const client = new EventStore.HTTPClient(getHttpConfig()); 90 | 91 | const response = await client.projections.assert(assertionProjection, assertionProjectionContent, 'onetime', true, true, true, true); 92 | await sleep(2000); 93 | assert.equal(response.name, assertionProjection); 94 | const responseWithTrackEmittedStreamsEnabled = await client.projections.getInfo(assertionProjection, true); 95 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.trackEmittedStreams, true); 96 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.emitEnabled, true); 97 | }); 98 | 99 | it('Should get config for continuous projection', async function () { 100 | this.timeout(10 * 1000); 101 | const client = new EventStore.HTTPClient(getHttpConfig()); 102 | 103 | const projectionConfig = await client.projections.config(assertionProjection); 104 | await sleep(1000); 105 | 106 | assert.equal(projectionConfig.emitEnabled, true); 107 | assert.equal(projectionConfig.trackEmittedStreams, true); 108 | assert.equal(projectionConfig.msgTypeId, 300); 109 | assert.equal(projectionConfig.checkpointHandledThreshold, 4000); 110 | assert.equal(projectionConfig.checkpointUnhandledBytesThreshold, 10000000); 111 | assert.equal(projectionConfig.pendingEventsThreshold, 5000); 112 | assert.equal(projectionConfig.maxWriteBatchLength, 500); 113 | }); 114 | 115 | it('Should remove one-time projection', async function () { 116 | this.timeout(10 * 1000); 117 | const client = new EventStore.HTTPClient(getHttpConfig()); 118 | 119 | const stopResponse = await client.projections.stop(assertionProjection); 120 | assert.equal(stopResponse.name, assertionProjection); 121 | const removeResponse = await client.projections.remove(assertionProjection); 122 | assert.equal(removeResponse.name, assertionProjection); 123 | }); 124 | 125 | it('Should create one-time projection with emits enabled but trackEmittedStreams disabled then remove it', async function () { 126 | this.timeout(10 * 1000); 127 | const client = new EventStore.HTTPClient(getHttpConfig()); 128 | 129 | const response = await client.projections.assert(assertionProjection, assertionProjectionContent, 'onetime', true, true, true); 130 | await sleep(2000); 131 | assert.equal(response.name, assertionProjection); 132 | const responseWithTrackEmittedStreamsEnabled = await client.projections.getInfo(assertionProjection, true); 133 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.trackEmittedStreams, false); 134 | assert.equal(responseWithTrackEmittedStreamsEnabled.config.emitEnabled, true); 135 | 136 | const stopResponse = await client.projections.stop(assertionProjection); 137 | assert.equal(stopResponse.name, assertionProjection); 138 | const removeResponse = await client.projections.remove(assertionProjection); 139 | assert.equal(removeResponse.name, assertionProjection); 140 | }); 141 | }); 142 | 143 | describe('Global Projections Operations', () => { 144 | it('Should enable all projections', async function () { 145 | this.timeout(10 * 1000); 146 | const client = new EventStore.HTTPClient(getHttpConfig()); 147 | 148 | await client.projections.enableAll(); 149 | await sleep(1000); 150 | 151 | const projectionsInfo = await client.projections.getAllProjectionsInfo(); 152 | projectionsInfo.projections.forEach(projection => { 153 | assert.equal(projection.status.toLowerCase().includes('running'), true); 154 | }); 155 | }); 156 | 157 | it('Should disable all projections', async function () { 158 | this.timeout(1000 * 10); 159 | const client = new EventStore.HTTPClient(getHttpConfig()); 160 | 161 | await client.projections.disableAll(); 162 | await sleep(1000); 163 | 164 | const projectionsInfo = await client.projections.getAllProjectionsInfo(); 165 | projectionsInfo.projections.forEach(projection => { 166 | assert.equal(projection.status.toLowerCase().includes('stopped'), true); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('General', () => { 172 | it('Should return all eventstore projections information', async function () { 173 | this.timeout(10 * 1000); 174 | const client = new EventStore.HTTPClient(getHttpConfig()); 175 | 176 | const projectionsInfo = await client.projections.getAllProjectionsInfo(); 177 | assert.notEqual(projectionsInfo, undefined); 178 | assert(projectionsInfo.projections.length > 0); 179 | }); 180 | 181 | it('Should return state for test projection', async function () { 182 | this.timeout(10 * 1000); 183 | const client = new EventStore.HTTPClient(getHttpConfig()); 184 | 185 | const projectionName = 'TestProjection'; 186 | const projectionContent = fs.readFileSync(`${__dirname}/support/testProjection.js`, { 187 | encoding: 'utf8' 188 | }); 189 | 190 | await client.projections.assert(projectionName, projectionContent); 191 | await sleep(500); 192 | 193 | const testStream = `TestProjectionStream-${generateEventId()}`; 194 | await client.writeEvent(testStream, 'TestProjectionEventType', { 195 | something: '123' 196 | }); 197 | 198 | await sleep(1000); 199 | const projectionState = await client.projections.getState(projectionName); 200 | assert.equal(projectionState.data.something, '123'); 201 | 202 | const stopResponse = await client.projections.stop(projectionName); 203 | assert.equal(stopResponse.name, projectionName); 204 | const removeResponse = await client.projections.remove(projectionName); 205 | assert.equal(removeResponse.name, projectionName); 206 | }); 207 | 208 | it('Should return state for partitioned test projection', async function () { 209 | this.timeout(1000 * 10); 210 | const client = new EventStore.HTTPClient(getHttpConfig()); 211 | 212 | const projectionName = `TestProjection${generateEventId()}`; 213 | const projectionContent = fs.readFileSync(`${__dirname}/support/testPartitionedProjection.js`, { 214 | encoding: 'utf8' 215 | }); 216 | 217 | await client.projections.assert(projectionName, projectionContent); 218 | await sleep(500); 219 | 220 | const testStream = `TestProjectionStream-${generateEventId()}`; 221 | await client.writeEvent(testStream, 'TestProjectionEventType', { 222 | something: '123' 223 | }); 224 | 225 | await sleep(4000); 226 | const options = { 227 | partition: testStream 228 | }; 229 | 230 | const projectionState = await client.projections.getState(projectionName, options); 231 | assert.equal(projectionState.data.something, '123'); 232 | 233 | const stopResponse = await client.projections.stop(projectionName); 234 | assert.equal(stopResponse.name, projectionName); 235 | const removeResponse = await client.projections.remove(projectionName); 236 | assert.equal(removeResponse.name, projectionName); 237 | }); 238 | 239 | it('Should return 404 for non-existent projection when requesting state', function () { 240 | this.timeout(10 * 1000); 241 | const client = new EventStore.HTTPClient(getHttpConfig()); 242 | return client.projections.getState('SomeProjectionNameThatDoesNotExist').catch(err => assert(err.response.status, 404)); 243 | }); 244 | 245 | it('Should return result for test projection', async function () { 246 | this.timeout(10 * 1000); 247 | const client = new EventStore.HTTPClient(getHttpConfig()); 248 | 249 | const projectionName = 'TestProjection'; 250 | const projectionContent = fs.readFileSync(`${__dirname}/support/testProjection.js`, { 251 | encoding: 'utf8' 252 | }); 253 | 254 | await client.projections.assert(projectionName, projectionContent); 255 | await sleep(500); 256 | 257 | const testStream = `TestProjectionStream-${generateEventId()}`; 258 | await client.writeEvent(testStream, 'TestProjectionEventType', { 259 | something: '123' 260 | }); 261 | 262 | await sleep(1000); 263 | const projectionState = await client.projections.getResult(projectionName); 264 | assert.equal(projectionState.data, '321'); 265 | 266 | const stopResponse = await client.projections.stop(projectionName); 267 | assert.equal(stopResponse.name, projectionName); 268 | const removeResponse = await client.projections.remove(projectionName); 269 | assert.equal(removeResponse.name, projectionName); 270 | }); 271 | 272 | it('Should return result for partitioned test projection', async function () { 273 | this.timeout(1000 * 10); 274 | const client = new EventStore.HTTPClient(getHttpConfig()); 275 | 276 | const projectionName = `TestProjection${generateEventId()}`; 277 | const projectionContent = fs.readFileSync(`${__dirname}/support/testPartitionedProjection.js`, { 278 | encoding: 'utf8' 279 | }); 280 | 281 | await client.projections.assert(projectionName, projectionContent); 282 | await sleep(500); 283 | 284 | const testStream = `TestProjectionStream-${generateEventId()}`; 285 | await client.writeEvent(testStream, 'TestProjectionEventType', { 286 | something: '123' 287 | }); 288 | 289 | await sleep(4000); 290 | const options = { 291 | partition: testStream 292 | }; 293 | 294 | const projectionState = await client.projections.getResult(projectionName, options); 295 | assert.equal(projectionState.data, '321'); 296 | 297 | const stopResponse = await client.projections.stop(projectionName); 298 | assert.equal(stopResponse.name, projectionName); 299 | const removeResponse = await client.projections.remove(projectionName); 300 | assert.equal(removeResponse.name, projectionName); 301 | }); 302 | 303 | it('Should return 404 for non-existent projection when requesting result', function () { 304 | this.timeout(10 * 1000); 305 | const client = new EventStore.HTTPClient(getHttpConfig()); 306 | return client.projections.getResult('SomeProjectionNameThatDoesNotExist').catch(err => assert(err.response.status, 404)); 307 | }); 308 | }); 309 | }); 310 | -------------------------------------------------------------------------------- /tests/http.readEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('Http Client - Read Events', () => { 12 | const testStream = `TestStream-${generateEventId()}`; 13 | const numberOfEvents = 10; 14 | 15 | before(() => { 16 | const client = new EventStore.HTTPClient(getHttpConfig()); 17 | 18 | const events = []; 19 | for (let i = 1; i <= numberOfEvents; i++) { 20 | events.push(eventFactory.newEvent('TestEventType', { 21 | something: i 22 | })); 23 | } 24 | return client.writeEvents(testStream, events); 25 | }); 26 | 27 | it('Should read events reading forward', async () => { 28 | const client = new EventStore.HTTPClient(getHttpConfig()); 29 | 30 | const result = await client.readEventsForward(testStream); 31 | assert.equal(result.events.length, 10); 32 | assert(result.events[0].created, 'Created should be defined'); 33 | assert.equal(result.events[0].data.something, 1); 34 | }); 35 | 36 | it('Should read events reading backward', async () => { 37 | const client = new EventStore.HTTPClient(getHttpConfig()); 38 | 39 | const result = await client.readEventsBackward(testStream); 40 | assert.equal(result.events.length, 10); 41 | assert.equal(result.events[0].data.something, 10); 42 | }); 43 | 44 | it('Should read last event reading backward with larger size than events', async () => { 45 | const client = new EventStore.HTTPClient(getHttpConfig()); 46 | 47 | const result = await client.readEventsBackward(testStream, 0, 250); 48 | assert.equal(result.events.length, 1); 49 | assert.equal(result.events[0].data.something, 1); 50 | }); 51 | 52 | it('Should not get any events when start event is greater than the stream length', async () => { 53 | const client = new EventStore.HTTPClient(getHttpConfig()); 54 | 55 | const result = await client.readEventsForward(testStream, 11); 56 | assert.equal(result.events.length, 0); 57 | }); 58 | 59 | it('Should read events reading backward from a start position', async () => { 60 | const client = new EventStore.HTTPClient(getHttpConfig()); 61 | 62 | const result = await client.readEventsBackward(testStream, 2); 63 | assert.equal(result.events.length, 3); 64 | assert.equal(result.events[0].data.something, 3); 65 | }); 66 | 67 | it('Should read events reading backward with a count greater than the stream length', async () => { 68 | const client = new EventStore.HTTPClient(getHttpConfig()); 69 | 70 | const result = await client.readEventsBackward(testStream, undefined, 10000); 71 | assert.equal(result.events.length, 10); 72 | assert.equal(result.events[0].data.something, 10); 73 | }); 74 | 75 | it('Should read events reading forward with a count greater than the stream length return a maximum of 4096', async function () { 76 | this.timeout(10000); 77 | const client = new EventStore.HTTPClient(getHttpConfig()); 78 | 79 | const testStream = `TestStream-${generateEventId()}`; 80 | const numberOfEvents = 5000; 81 | const events = []; 82 | 83 | for (let i = 1; i <= numberOfEvents; i++) { 84 | events.push(eventFactory.newEvent('TestEventType', { 85 | something: i 86 | })); 87 | } 88 | 89 | await client.writeEvents(testStream, events); 90 | const result = await client.readEventsForward(testStream, undefined, 5000); 91 | assert.equal(result.events.length, 4096); 92 | assert.equal(result.events[0].data.something, 1); 93 | assert.equal(result.events[4095].data.something, 4096); 94 | }); 95 | 96 | it('Should read linked to events and map correctly', async () => { 97 | const client = new EventStore.HTTPClient(getHttpConfig()); 98 | 99 | const result = await client.readEventsForward('$ce-TestStream', 0, 1); 100 | assert.equal(result.events.length, 1); 101 | assert(result.events[0].data.something); 102 | assert.equal(0, result.events[0].positionEventNumber); 103 | assert.equal('$ce-TestStream', result.events[0].positionStreamId); 104 | }); 105 | 106 | it('Should read system and deleted events without resolveLinkTos', async () => { 107 | const client = new EventStore.HTTPClient(getHttpConfig()); 108 | 109 | const deletedStream = 'TestStreamDeleted'; 110 | await client.writeEvent(deletedStream, 'TestEventType', { something: 1 }); 111 | 112 | await sleep(500); 113 | 114 | const result = await client.readEventsForward('$streams', 0, 4096, false); 115 | assert(result.events.length > 0, 'More than 0 events expected'); 116 | }); 117 | 118 | it('Should read events reading backward with embed type rich', async () => { 119 | const client = new EventStore.HTTPClient(getHttpConfig()); 120 | 121 | const result = await client.readEventsBackward(testStream, 2, undefined, true, 'rich'); 122 | assert.equal(result.events.length, 3); 123 | assert.equal(result.events[0].data, undefined); 124 | }); 125 | 126 | it('Should read events and set additional meta properties', async () => { 127 | const client = new EventStore.HTTPClient(getHttpConfig()); 128 | 129 | let result = await client.readEventsForward(testStream, 2, 2); 130 | assert.equal(result.readDirection, 'forward'); 131 | assert.equal(result.fromEventNumber, 2); 132 | assert.equal(result.nextEventNumber, 4); 133 | 134 | result = await client.readEventsBackward(testStream, 3, 2); 135 | assert.equal(result.readDirection, 'backward'); 136 | assert.equal(result.fromEventNumber, 3); 137 | assert.equal(result.nextEventNumber, 1); 138 | }); 139 | 140 | describe('Http Client - Get Events Failure', () => { 141 | const testStream = `TestStream-${generateEventId()}`; 142 | 143 | it('Should return 404 when stream does not exist', () => { 144 | const client = new EventStore.HTTPClient(getHttpConfig()); 145 | 146 | return client.readEventsForward(testStream).then(() => { 147 | throw new Error('Should not have received events'); 148 | }).catch(err => { 149 | assert.equal(404, err.response.status, 'Should have received 404'); 150 | }); 151 | }); 152 | 153 | it('Should not return 404 when stream does not exist if ignore 404 is set on config', () => { 154 | const httpConfigWithIgnore = getHttpConfig(); 155 | httpConfigWithIgnore.ignore = [404]; 156 | 157 | const client = new EventStore.HTTPClient(httpConfigWithIgnore); 158 | 159 | return client.readEventsForward(testStream).then(() => { 160 | throw new Error('Should not have received events'); 161 | }).catch(err => { 162 | assert.equal(404, err.response.status, 'Should have received 404'); 163 | }); 164 | }); 165 | }); 166 | }); -------------------------------------------------------------------------------- /tests/http.sendScavengeCommand.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getHttpConfig from './support/getHttpConfig'; 4 | import EventStore from '../lib'; 5 | 6 | describe('Http Client - Send Scavenge Command', () => { 7 | it('Should send scavenge command', () => { 8 | const client = new EventStore.HTTPClient(getHttpConfig()); 9 | return client.admin.scavenge(); 10 | }); 11 | }); -------------------------------------------------------------------------------- /tests/http.writeEvent.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | describe('Http Client - Write Event', () => { 9 | it('Write to a new stream and read the event', async () => { 10 | const client = new EventStore.HTTPClient(getHttpConfig()); 11 | const testStream = `TestStream-${generateEventId()}`; 12 | 13 | await client.writeEvent(testStream, 'TestEventType', { 14 | something: '123' 15 | }); 16 | 17 | const events = await client.getEvents(testStream); 18 | assert.equal(events[0].data.something, '123'); 19 | }); 20 | 21 | it('Should fail promise if no event data provided', async () => { 22 | const client = new EventStore.HTTPClient(getHttpConfig()); 23 | const testStream = `TestStream-${generateEventId()}`; 24 | 25 | try { 26 | await client.writeEvent(testStream, 'TestEventType'); 27 | } catch (err) { 28 | assert(err, 'Error expected'); 29 | assert(err.message, 'Error Message Expected'); 30 | return; 31 | } 32 | assert.fail('Write should not have succeeded'); 33 | }); 34 | }); 35 | 36 | describe('Http Client - Write Event to pre-populated stream', () => { 37 | let client; 38 | let testStream; 39 | beforeEach(async () => { 40 | client = new EventStore.HTTPClient(getHttpConfig()); 41 | testStream = `TestStream-${generateEventId()}`; 42 | 43 | await client.writeEvent(testStream, 'TestEventType', { 44 | something: '123' 45 | }); 46 | 47 | await client.writeEvent(testStream, 'TestEventType', { 48 | something: '456' 49 | }, null, { 50 | expectedVersion: 0 51 | }); 52 | 53 | await client.writeEvent(testStream, 'TestEventType', { 54 | something: '789' 55 | }, null, { 56 | expectedVersion: 1 57 | }); 58 | }); 59 | 60 | it('Should fail promise if passed in wrong expectedVersion (covering edge case of expectedVersion=0)', async () => { 61 | try { 62 | await client.writeEvent(testStream, 'TestEventType', { 63 | something: 'abc' 64 | }, null, { 65 | expectedVersion: 0 66 | }); 67 | } catch (err) { 68 | assert(err, 'Error expected'); 69 | assert(err.message, 'Error Message Expected'); 70 | return; 71 | } 72 | assert.fail('Write should not have succeeded'); 73 | }); 74 | 75 | it('Should write event if expectedVersion=null', async () => { 76 | try { 77 | await client.writeEvent(testStream, 'TestEventType', { 78 | something: 'abc' 79 | }, null, { 80 | expectedVersion: null 81 | }); 82 | } catch (err) { 83 | assert.fail('Write should not have failed'); 84 | } 85 | }); 86 | }); -------------------------------------------------------------------------------- /tests/http.writeEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getHttpConfig from './support/getHttpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('Http Client - Write Events', () => { 11 | it('Write to a new stream and read the events', async () => { 12 | const client = new EventStore.HTTPClient(getHttpConfig()); 13 | 14 | const events = [eventFactory.newEvent('TestEventType', { 15 | something: '456' 16 | })]; 17 | 18 | const testStream = `TestStream-${generateEventId()}`; 19 | await client.writeEvents(testStream, events); 20 | const evs = await client.getEvents(testStream); 21 | assert.equal(evs[0].data.something, '456'); 22 | }); 23 | 24 | it('Write to a new stream and read the events by type', async () => { 25 | const client = new EventStore.HTTPClient(getHttpConfig()); 26 | 27 | const events = [eventFactory.newEvent('TestEventType', { 28 | something: '456' 29 | }), eventFactory.newEvent('ToBeIgnoredType', { 30 | something: '789' 31 | })]; 32 | 33 | const testStream = `TestStream-${generateEventId()}`; 34 | await client.writeEvents(testStream, events); 35 | 36 | const evs = await client.getEventsByType(testStream, ['TestEventType']); 37 | assert.equal(evs.length, 1); 38 | assert.equal(evs[0].eventType, 'TestEventType'); 39 | assert.equal(evs[0].data.something, '456'); 40 | }); 41 | 42 | it('Should not fail promise if no events provided', () => { 43 | const client = new EventStore.HTTPClient(getHttpConfig()); 44 | 45 | const events = []; 46 | 47 | const testStream = `TestStream-${generateEventId()}`; 48 | return client.writeEvents(testStream, events); 49 | }); 50 | 51 | it('Should fail promise if non array provided', () => { 52 | const client = new EventStore.HTTPClient(getHttpConfig()); 53 | 54 | const events = { 55 | something: 'here' 56 | }; 57 | 58 | const testStream = `TestStream-${generateEventId()}`; 59 | return client.writeEvents(testStream, events).then(() => { 60 | assert.fail('should not have succeeded'); 61 | }).catch(err => { 62 | assert(err, 'Error expected'); 63 | assert(err.message, 'Error Message Expected'); 64 | }); 65 | }); 66 | }); 67 | 68 | describe('Http Client - Write Events to pre-populated stream', () => { 69 | let client, 70 | testStream, 71 | events, 72 | events2; 73 | 74 | beforeEach(async () => { 75 | client = new EventStore.HTTPClient(getHttpConfig()); 76 | 77 | events = [eventFactory.newEvent('TestEventType', { 78 | something: '456' 79 | }), eventFactory.newEvent('ToBeIgnoredType', { 80 | something: '789' 81 | })]; 82 | 83 | testStream = `TestStream-${generateEventId()}`; 84 | await client.writeEvents(testStream, events); 85 | 86 | events2 = [eventFactory.newEvent('TestEventType', { 87 | something: 'abc' 88 | })]; 89 | }); 90 | 91 | it('Should fail promise if passed in wrong expectedVersion (covering edge case of expectedVersion=0)', async () => { 92 | try { 93 | await client.writeEvents(testStream, events2, { 94 | expectedVersion: 0 95 | }); 96 | } catch (err) { 97 | assert(err, 'Error expected'); 98 | assert(err.message, 'Error Message Expected'); 99 | return; 100 | } 101 | assert.fail('Write should not have succeeded'); 102 | }); 103 | 104 | it('Should write event if expectedVersion=null', async () => { 105 | try { 106 | await client.writeEvents(testStream, events2, { 107 | expectedVersion: null 108 | }); 109 | } catch (err) { 110 | assert.fail('Write should not have failed'); 111 | } 112 | }); 113 | }); -------------------------------------------------------------------------------- /tests/support/cluster/docker-compose-insecure.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | geteventstore_promise_test_cluster_node1.eventstore: &template 5 | image: eventstore/eventstore:21.10.0-buster-slim 6 | container_name: geteventstore_promise_test_cluster_node1.eventstore 7 | env_file: 8 | - vars.env 9 | environment: 10 | - EVENTSTORE_INSECURE=true 11 | - EVENTSTORE_INT_IP=172.30.240.11 12 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22137 13 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11136 14 | - EVENTSTORE_GOSSIP_SEED=172.30.240.12:2113,172.30.240.13:2113 15 | healthcheck: 16 | test: 17 | [ 18 | "CMD-SHELL", 19 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node1.eventstore:2113/health/live || exit 1", 20 | ] 21 | interval: 5s 22 | timeout: 5s 23 | retries: 24 24 | ports: 25 | - "11136:1113" 26 | - "22137:2113" 27 | restart: always 28 | networks: 29 | clusternetwork: 30 | ipv4_address: 172.30.240.11 31 | 32 | geteventstore_promise_test_cluster_node2.eventstore: 33 | <<: *template 34 | container_name: geteventstore_promise_test_cluster_node2.eventstore 35 | environment: 36 | - EVENTSTORE_INSECURE=true 37 | - EVENTSTORE_INT_IP=172.30.240.12 38 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22157 39 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11156 40 | - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.13:2113 41 | healthcheck: 42 | test: 43 | [ 44 | "CMD-SHELL", 45 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node2.eventstore:2113/health/live || exit 1", 46 | ] 47 | interval: 5s 48 | timeout: 5s 49 | retries: 24 50 | ports: 51 | - "11156:1113" 52 | - "22157:2113" 53 | networks: 54 | clusternetwork: 55 | ipv4_address: 172.30.240.12 56 | 57 | geteventstore_promise_test_cluster_node3.eventstore: 58 | <<: *template 59 | container_name: geteventstore_promise_test_cluster_node3.eventstore 60 | environment: 61 | - EVENTSTORE_INSECURE=true 62 | - EVENTSTORE_INT_IP=172.30.240.13 63 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22177 64 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11176 65 | - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:2113 66 | healthcheck: 67 | test: 68 | [ 69 | "CMD-SHELL", 70 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node3.eventstore:2113/health/live || exit 1", 71 | ] 72 | interval: 5s 73 | timeout: 5s 74 | retries: 24 75 | ports: 76 | - "11176:1113" 77 | - "22177:2113" 78 | networks: 79 | clusternetwork: 80 | ipv4_address: 172.30.240.13 81 | 82 | networks: 83 | clusternetwork: 84 | name: geteventstore_promise_test_cluster_eventstoredb.local 85 | driver: bridge 86 | ipam: 87 | driver: default 88 | config: 89 | - subnet: 172.30.240.0/24 -------------------------------------------------------------------------------- /tests/support/cluster/docker-compose-secure.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | geteventstore_promise_test_setup_certs_cluster: 5 | image: eventstore/es-gencert-cli:1.0.2 6 | entrypoint: bash 7 | user: "0:0" 8 | command: > 9 | -c "mkdir -p ./certs && cd /certs 10 | && es-gencert-cli create-ca 11 | && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.30.240.11 -dns-names localhost 12 | && es-gencert-cli create-node -out ./node2 -ip-addresses 127.0.0.1,172.30.240.12 -dns-names localhost 13 | && es-gencert-cli create-node -out ./node3 -ip-addresses 127.0.0.1,172.30.240.13 -dns-names localhost 14 | && find . -type f -print0 | xargs -0 chmod 666" 15 | container_name: geteventstore_promise_test_setup_certs_cluster 16 | volumes: 17 | - ./certs:/certs 18 | networks: 19 | clusternetwork: 20 | ipv4_address: 172.30.240.10 21 | 22 | geteventstore_promise_test_cluster_node1.eventstore: &template 23 | image: eventstore/eventstore:21.10.0-buster-slim 24 | container_name: geteventstore_promise_test_cluster_node1.eventstore 25 | command: --node-priority=1 26 | env_file: 27 | - vars.env 28 | environment: 29 | - EVENTSTORE_INT_IP=172.30.240.11 30 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22137 31 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11136 32 | - EVENTSTORE_GOSSIP_SEED=172.30.240.12:2113,172.30.240.13:2113 33 | - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca 34 | - EVENTSTORE_CERTIFICATE_FILE=/certs/node1/node.crt 35 | - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node1/node.key 36 | healthcheck: 37 | test: 38 | [ 39 | "CMD-SHELL", 40 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node1.eventstore:2113/health/live || exit 1", 41 | ] 42 | interval: 5s 43 | timeout: 5s 44 | retries: 24 45 | ports: 46 | - "11136:1113" 47 | - "22137:2113" 48 | volumes: 49 | - ./certs:/certs 50 | depends_on: 51 | - geteventstore_promise_test_setup_certs_cluster 52 | restart: always 53 | networks: 54 | clusternetwork: 55 | ipv4_address: 172.30.240.11 56 | 57 | geteventstore_promise_test_cluster_node2.eventstore: 58 | <<: *template 59 | container_name: geteventstore_promise_test_cluster_node2.eventstore 60 | command: --node-priority=2 61 | environment: 62 | - EVENTSTORE_INT_IP=172.30.240.12 63 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22157 64 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11156 65 | - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.13:2113 66 | - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca 67 | - EVENTSTORE_CERTIFICATE_FILE=/certs/node2/node.crt 68 | - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node2/node.key 69 | healthcheck: 70 | test: 71 | [ 72 | "CMD-SHELL", 73 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node2.eventstore:2113/health/live || exit 1", 74 | ] 75 | interval: 5s 76 | timeout: 5s 77 | retries: 24 78 | ports: 79 | - "11156:1113" 80 | - "22157:2113" 81 | networks: 82 | clusternetwork: 83 | ipv4_address: 172.30.240.12 84 | 85 | geteventstore_promise_test_cluster_node3.eventstore: 86 | <<: *template 87 | container_name: geteventstore_promise_test_cluster_node3.eventstore 88 | command: --node-priority=3 89 | environment: 90 | - EVENTSTORE_INT_IP=172.30.240.13 91 | - EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=22177 92 | - EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=11176 93 | - EVENTSTORE_GOSSIP_SEED=172.30.240.11:2113,172.30.240.12:2113 94 | - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca 95 | - EVENTSTORE_CERTIFICATE_FILE=/certs/node3/node.crt 96 | - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/node3/node.key 97 | healthcheck: 98 | test: 99 | [ 100 | "CMD-SHELL", 101 | "curl --fail --insecure http://geteventstore_promise_test_cluster_node3.eventstore:2113/health/live || exit 1", 102 | ] 103 | interval: 5s 104 | timeout: 5s 105 | retries: 24 106 | ports: 107 | - "11176:1113" 108 | - "22177:2113" 109 | networks: 110 | clusternetwork: 111 | ipv4_address: 172.30.240.13 112 | 113 | networks: 114 | clusternetwork: 115 | name: geteventstore_promise_test_cluster_eventstoredb.local 116 | driver: bridge 117 | ipam: 118 | driver: default 119 | config: 120 | - subnet: 172.30.240.0/24 -------------------------------------------------------------------------------- /tests/support/cluster/vars.env: -------------------------------------------------------------------------------- 1 | EVENTSTORE_MEM_DB=true 2 | EVENTSTORE_CLUSTER_SIZE=3 3 | EVENTSTORE_RUN_PROJECTIONS=All 4 | EVENTSTORE_START_STANDARD_PROJECTIONS=true 5 | EVENTSTORE_DISCOVER_VIA_DNS=false 6 | EVENTSTORE_ENABLE_EXTERNAL_TCP=true 7 | EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true 8 | EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 -------------------------------------------------------------------------------- /tests/support/getHttpConfig.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | protocol: global.runningTestsInSecureMode ? 'https' : 'http', 3 | hostname: process.env.ES_HOST || 'localhost', 4 | validateServer: !global.runningTestsInSecureMode, 5 | port: 22117, 6 | credentials: { 7 | username: 'admin', 8 | password: 'changeit' 9 | } 10 | }); -------------------------------------------------------------------------------- /tests/support/getTcpConfig.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | hostname: process.env.ES_HOST || 'localhost', 3 | port: 11116, 4 | useSslConnection: global.runningTestsInSecureMode, 5 | validateServer: !global.runningTestsInSecureMode, 6 | credentials: { 7 | username: 'admin', 8 | password: 'changeit' 9 | }, 10 | poolOptions: { 11 | autostart: false, 12 | max: 10, 13 | min: 0 14 | } 15 | }); -------------------------------------------------------------------------------- /tests/support/getTcpConfigCustomConnectionName.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | hostname: process.env.ES_HOST || 'localhost', 3 | port: 11116, 4 | useSslConnection: global.runningTestsInSecureMode, 5 | validateServer: !global.runningTestsInSecureMode, 6 | credentials: { 7 | username: 'admin', 8 | password: 'changeit' 9 | }, 10 | poolOptions: { 11 | autostart: false, 12 | max: 10, 13 | min: 0 14 | }, 15 | connectionNameGenerator: () => `CUSTOM_TCP_CONNECTION_NAME_${new Date().getTime()}` 16 | }); -------------------------------------------------------------------------------- /tests/support/getTcpConfigDNSDiscoveryCluster.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | protocol: 'discover', 3 | hostname: process.env.ES_HOST || 'localhost', 4 | port: 22137, 5 | useSslConnection: global.runningTestsInSecureMode, 6 | validateServer: !global.runningTestsInSecureMode, 7 | credentials: { 8 | username: 'admin', 9 | password: 'changeit' 10 | }, 11 | poolOptions: { 12 | autostart: false, 13 | max: 10, 14 | min: 0 15 | } 16 | }); -------------------------------------------------------------------------------- /tests/support/getTcpConfigGossipCluster.js: -------------------------------------------------------------------------------- 1 | export default () => ({ 2 | useSslConnection: global.runningTestsInSecureMode, 3 | validateServer: !global.runningTestsInSecureMode, 4 | gossipSeeds: [ 5 | { hostname: process.env.ES_HOST || 'localhost', port: 22137 }, 6 | { hostname: process.env.ES_HOST || 'localhost', port: 22157 }, 7 | { hostname: process.env.ES_HOST || 'localhost', port: 22177 } 8 | ], 9 | credentials: { 10 | username: 'admin', 11 | password: 'changeit' 12 | }, 13 | poolOptions: { 14 | autostart: false, 15 | max: 10, 16 | min: 0 17 | } 18 | }); -------------------------------------------------------------------------------- /tests/support/single/docker-compose-insecure.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | geteventstore_promise_test_single.eventstore: 5 | image: eventstore/eventstore:21.10.0-buster-slim 6 | container_name: geteventstore_promise_test_single.eventstore 7 | environment: 8 | - EVENTSTORE_INSECURE=true 9 | - EVENTSTORE_MEM_DB=true 10 | - EVENTSTORE_CLUSTER_SIZE=1 11 | - EVENTSTORE_RUN_PROJECTIONS=All 12 | - EVENTSTORE_START_STANDARD_PROJECTIONS=true 13 | - EVENTSTORE_INT_IP=172.30.239.11 14 | - EVENTSTORE_ENABLE_EXTERNAL_TCP=true 15 | - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true 16 | - EVENTSTORE_DISCOVER_VIA_DNS=false 17 | - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 18 | - EVENTSTORE_HTTP_PORT_ADVERTISE_AS=22117 19 | # Added for when testing v5 20 | # - EVENTSTORE_EXT_HTTP_PORT_ADVERTISE_AS=2117 21 | ports: 22 | - "11116:1113" 23 | - "22117:2113" 24 | networks: 25 | singleclusternetwork: 26 | ipv4_address: 172.30.239.11 27 | 28 | networks: 29 | singleclusternetwork: 30 | name: geteventstore_promise_test_single_eventstoredb.local 31 | driver: bridge 32 | ipam: 33 | driver: default 34 | config: 35 | - subnet: 172.30.239.0/24 -------------------------------------------------------------------------------- /tests/support/single/docker-compose-secure.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | geteventstore_promise_test_setup_certs_single: 5 | image: eventstore/es-gencert-cli:1.0.2 6 | entrypoint: bash 7 | user: "0:0" 8 | command: > 9 | -c "mkdir -p ./certs && cd /certs 10 | && es-gencert-cli create-ca 11 | && es-gencert-cli create-node -out ./single -ip-addresses 127.0.0.1,172.30.239.11 -dns-names localhost 12 | && find . -type f -print0 | xargs -0 chmod 666" 13 | container_name: geteventstore_promise_test_setup_certs_single 14 | volumes: 15 | - ./certs:/certs 16 | networks: 17 | singleclusternetwork: 18 | ipv4_address: 172.30.239.10 19 | 20 | geteventstore_promise_test_single.eventstore: 21 | image: eventstore/eventstore:21.10.0-buster-slim 22 | container_name: geteventstore_promise_test_single.eventstore 23 | environment: 24 | - EVENTSTORE_MEM_DB=true 25 | - EVENTSTORE_CLUSTER_SIZE=1 26 | - EVENTSTORE_RUN_PROJECTIONS=All 27 | - EVENTSTORE_START_STANDARD_PROJECTIONS=true 28 | - EVENTSTORE_INT_IP=172.30.239.11 29 | - EVENTSTORE_ENABLE_EXTERNAL_TCP=true 30 | - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true 31 | - EVENTSTORE_DISCOVER_VIA_DNS=false 32 | - EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=127.0.0.1 33 | - EVENTSTORE_HTTP_PORT_ADVERTISE_AS=22117 34 | - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/certs/ca 35 | - EVENTSTORE_CERTIFICATE_FILE=/certs/single/node.crt 36 | - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/certs/single/node.key 37 | # Added for when testing v5 38 | # - EVENTSTORE_EXT_HTTP_PORT_ADVERTISE_AS=2117 39 | ports: 40 | - "11116:1113" 41 | - "22117:2113" 42 | volumes: 43 | - ./certs:/certs 44 | depends_on: 45 | - geteventstore_promise_test_setup_certs_single 46 | restart: always 47 | networks: 48 | singleclusternetwork: 49 | ipv4_address: 172.30.239.11 50 | 51 | networks: 52 | singleclusternetwork: 53 | name: geteventstore_promise_test_single_eventstoredb.local 54 | driver: bridge 55 | ipam: 56 | driver: default 57 | config: 58 | - subnet: 172.30.239.0/24 -------------------------------------------------------------------------------- /tests/support/testPartitionedProjection.js: -------------------------------------------------------------------------------- 1 | fromAll() 2 | .foreachStream() 3 | .when({ 4 | $init() { 5 | return { 6 | data: {} 7 | }; 8 | }, 9 | TestProjectionEventType(state, ev) { 10 | state.data = ev.data; 11 | } 12 | }).transformBy(function(state) { 13 | state.data = 321; 14 | return state; 15 | }); 16 | -------------------------------------------------------------------------------- /tests/support/testProjection.js: -------------------------------------------------------------------------------- 1 | fromAll() 2 | .when({ 3 | $init() { 4 | return { 5 | data: {} 6 | }; 7 | }, 8 | TestProjectionEventType(state, ev) { 9 | state.data = ev.data; 10 | } 11 | }).transformBy(function(state) { 12 | state.data = 321; 13 | return state; 14 | }); 15 | -------------------------------------------------------------------------------- /tests/tcp.checkStreamExists.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | describe('TCP Client - Check Stream Exist', () => { 9 | it('Should return true when a stream exists', async function() { 10 | this.timeout(5000); 11 | const client = new EventStore.TCPClient(getTcpConfig()); 12 | 13 | const testStream = `TestStream-${generateEventId()}`; 14 | await client.writeEvent(testStream, 'TestEventType', { 15 | something: '123' 16 | }); 17 | assert.equal(await client.checkStreamExists(testStream), true); 18 | 19 | await client.close(); 20 | }); 21 | 22 | it('Should return false when a stream does not exist', async function() { 23 | this.timeout(5000); 24 | const client = new EventStore.TCPClient(getTcpConfig()); 25 | 26 | assert.equal(await client.checkStreamExists('Non_existentStream'), false); 27 | 28 | await client.close(); 29 | }); 30 | }); -------------------------------------------------------------------------------- /tests/tcp.cluster.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getTcpConfigDNSDiscoveryCluster from './support/getTcpConfigDNSDiscoveryCluster'; 4 | import getTcpConfigGossipCluster from './support/getTcpConfigGossipCluster'; 5 | import generateEventId from '../lib/utilities/generateEventId'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('TCP Client - Cluster', () => { 12 | it('Write and read events using gossip seeds', async function () { 13 | this.timeout(5 * 1000); 14 | const config = getTcpConfigGossipCluster(); 15 | const client = new EventStore.TCPClient(config); 16 | 17 | const events = [eventFactory.newEvent('TestEventType', { something: '456' })]; 18 | const testStream = `TestStream-${generateEventId()}`; 19 | await client.writeEvents(testStream, events); 20 | 21 | const evs = await client.getEvents(testStream); 22 | assert.equal(evs[0].data.something, '456'); 23 | 24 | await client.close(); 25 | }); 26 | 27 | it('Write and read events using DNS discovery', async function () { 28 | this.timeout(5 * 1000); 29 | const config = getTcpConfigDNSDiscoveryCluster(); 30 | const client = new EventStore.TCPClient(config); 31 | 32 | const events = [eventFactory.newEvent('TestEventType', { something: '456' })]; 33 | const testStream = `TestStream-${generateEventId()}`; 34 | await client.writeEvents(testStream, events); 35 | 36 | const evs = await client.getEvents(testStream); 37 | assert.equal(evs[0].data.something, '456'); 38 | 39 | await client.close(); 40 | }); 41 | }); -------------------------------------------------------------------------------- /tests/tcp.config.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getTcpConfig from './support/getTcpConfig'; 4 | import EventStore from '../lib'; 5 | import assert from 'assert'; 6 | 7 | describe('TCP Client - Config', () => { 8 | it('Should return assertion error when config is undefined', done => { 9 | try { 10 | new EventStore.TCPClient(); 11 | done('Config should not pass assertion'); 12 | } catch (err) { 13 | assert.equal(err === undefined, false); 14 | assert.equal(err.message, 'geteventstore-promise - TCP client - config not provided'); 15 | done(); 16 | } 17 | }); 18 | 19 | it('Should return assertion error when hostname is undefined', done => { 20 | try { 21 | const config = { 22 | port: 1113, 23 | credentials: { 24 | username: 'admin', 25 | password: 'changeit' 26 | } 27 | }; 28 | new EventStore.TCPClient(config); 29 | done(); 30 | } catch (err) { 31 | assert.equal(err === undefined, false); 32 | assert.equal(err.message, 'geteventstore-promise - TCP client - hostname property not provided'); 33 | done(); 34 | } 35 | }); 36 | 37 | it('Should return assertion error when credentials are undefined', done => { 38 | try { 39 | const config = { 40 | hostname: 'localhost', 41 | port: 1113 42 | }; 43 | new EventStore.TCPClient(config); 44 | done(); 45 | } catch (err) { 46 | assert.equal(err === undefined, false); 47 | assert.equal(err.message, 'geteventstore-promise - TCP client - credentials property not provided'); 48 | done(); 49 | } 50 | }); 51 | 52 | it('Should return tcp client when config is complete', done => { 53 | try { 54 | const client = new EventStore.TCPClient(getTcpConfig()); 55 | assert.equal(client !== undefined, true); 56 | done(); 57 | } catch (err) { 58 | done(err); 59 | } 60 | }); 61 | }); -------------------------------------------------------------------------------- /tests/tcp.connection.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import getTcpConfigCustomConnectionName from './support/getTcpConfigCustomConnectionName'; 4 | import generateEventId from '../lib/utilities/generateEventId'; 5 | import getTcpConfig from './support/getTcpConfig'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('TCP Client - Test Connection', () => { 12 | const writeEventsInParallel = async (client, numberOfEvents = 20) => { 13 | const events = []; 14 | for (let i = 1; i <= numberOfEvents; i++) { 15 | events.push(eventFactory.newEvent('TestEventType', { 16 | something: i 17 | })); 18 | } 19 | await Promise.all(events.map(ev => client.writeEvent(`TestStream-${generateEventId()}`, ev.eventType, ev.data))); 20 | }; 21 | 22 | it('Should connect and write event on correct connection properties', async () => { 23 | const client = new EventStore.TCPClient(getTcpConfig()); 24 | const testStream = `TestStream-${generateEventId()}`; 25 | await client.writeEvent(testStream, 'TestEventType', { 26 | something: '123' 27 | }); 28 | 29 | await client.close(); 30 | }); 31 | 32 | it('Should connect and write event with custom connection name', async function () { 33 | const client = new EventStore.TCPClient(getTcpConfigCustomConnectionName()); 34 | 35 | const testStream = `TestStream-${generateEventId()}`; 36 | await client.writeEvent(testStream, 'TestEventType', { 37 | something: '123' 38 | }); 39 | 40 | const pool = await client.getPool(); 41 | assert.equal(1, pool._allObjects.size); 42 | 43 | const connection = await pool.acquire(); 44 | assert(connection._connectionName.startsWith('CUSTOM_TCP_CONNECTION_NAME_'), `Expected connection name to start with 'CUSTOM_TCP_CONNECTION_NAME_', got '${connection._connectionName}'`); 45 | await pool.release(connection); 46 | 47 | await client.close(); 48 | }); 49 | 50 | it('Should not connect on incorrect hostname', function () { 51 | this.timeout(60 * 1000); 52 | const config = getTcpConfig(); 53 | config.maxReconnections = 2; 54 | config.hostname = 'madetofailhostname.fakedomain.af'; 55 | 56 | const client = new EventStore.TCPClient(config); 57 | 58 | const testStream = `TestStream-${generateEventId()}`; 59 | return client.writeEvent(testStream, 'TestEventType', { 60 | something: '123' 61 | }).then(() => { 62 | assert.fail('Should not have written event successfully'); 63 | }).catch(err => { 64 | assert.notEqual(err.message, 'Should not have written event successfully'); 65 | }).finally(() => client.close()); 66 | }); 67 | 68 | it('Should not connect on incorrect port', function () { 69 | this.timeout(60 * 1000); 70 | const config = getTcpConfig(); 71 | config.maxReconnections = 2; 72 | config.port = 9999; 73 | 74 | const client = new EventStore.TCPClient(config); 75 | 76 | const testStream = `TestStream-${generateEventId()}`; 77 | return client.writeEvent(testStream, 'TestEventType', { 78 | something: '123' 79 | }).then(() => { 80 | assert.fail('Should not have written event successfully'); 81 | }).catch(err => { 82 | assert.notEqual(err.message, 'Should not have written event successfully'); 83 | }).finally(() => client.close()); 84 | }); 85 | 86 | it('Should default to only one connection with no pool options provided', async function () { 87 | this.timeout(60 * 1000); 88 | const config = getTcpConfig(); 89 | delete config.poolOptions; 90 | config.makeConfigUniqueWithThis = new Date().getTime(); 91 | const client = new EventStore.TCPClient(config); 92 | 93 | await writeEventsInParallel(client); 94 | 95 | const pool = await client.getPool(); 96 | assert.equal(1, pool._allObjects.size); 97 | 98 | await client.close(); 99 | }); 100 | 101 | it('Should fill up pool connections to provided max', async function () { 102 | this.timeout(60 * 1000); 103 | const config = getTcpConfig(); 104 | config.poolOptions.max = 7; 105 | config.makeConfigUniqueWithThis = new Date().getTime(); 106 | const client = new EventStore.TCPClient(config); 107 | 108 | await writeEventsInParallel(client); 109 | 110 | const pool = await client.getPool(); 111 | assert.equal(7, pool._allObjects.size); 112 | 113 | await client.close(); 114 | }); 115 | 116 | it('Should close pool', async function () { 117 | this.timeout(60 * 1000); 118 | const config = getTcpConfig(); 119 | const client = new EventStore.TCPClient(config); 120 | 121 | const testStream = `TestStream-${generateEventId()}`; 122 | await client.writeEvent(testStream, 'TestEventType', { 123 | something: '123' 124 | }); 125 | await client.close(); 126 | 127 | let failed = false; 128 | try { 129 | await client.getPool(); 130 | failed = true; 131 | } catch (err) { 132 | if (failed) throw new Error('Connection Pool should not exist'); 133 | assert.equal(err.message, 'Connection Pool not found'); 134 | } 135 | }); 136 | 137 | it('Should close all pools', async function () { 138 | this.timeout(60 * 1000); 139 | const config = getTcpConfig(); 140 | const client = new EventStore.TCPClient(config); 141 | 142 | await client.closeAllPools(); 143 | 144 | let failed = false; 145 | try { 146 | await client.getPool(); 147 | failed = true; 148 | } catch (err) { 149 | if (failed) throw new Error('Connection Pool should not exist'); 150 | assert.equal(err.message, 'Connection Pool not found'); 151 | } 152 | }); 153 | }); -------------------------------------------------------------------------------- /tests/tcp.deleteStream.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | describe('TCP Client - Delete stream', () => { 10 | it('Should return successful on stream delete', () => { 11 | const client = new EventStore.TCPClient(getTcpConfig()); 12 | 13 | const testStream = `TestStream-${generateEventId()}`; 14 | return client.writeEvent(testStream, 'TestEventType', { 15 | something: '123' 16 | }).then(() => client.deleteStream(testStream).then(() => client.checkStreamExists(testStream).then(exists => { 17 | assert.equal(false, exists); 18 | })).catch(err => { 19 | assert.fail(err.message); 20 | })).finally(() => client.close()); 21 | }); 22 | 23 | it('Should return successful on projected stream delete', async () => { 24 | const client = new EventStore.TCPClient(getTcpConfig()); 25 | 26 | const testStream = `TestDeletedStream-${generateEventId()}`; 27 | await client.writeEvent(testStream, 'TestEventType', { 28 | something: '123' 29 | }); 30 | 31 | await sleep(150); 32 | await client.deleteStream(`$ce-TestDeletedStream`); 33 | assert.equal(await client.checkStreamExists(`$ce-TestDeletedStream`), false); 34 | 35 | await client.close(); 36 | }); 37 | 38 | it('Should return successful on writing to a stream that has been soft deleted', () => { 39 | const client = new EventStore.TCPClient(getTcpConfig()); 40 | 41 | const testStream = `TestStream-${generateEventId()}`; 42 | 43 | return client.writeEvent(testStream, 'TestEventType', { 44 | something: '123' 45 | }).then(() => client.deleteStream(testStream).then(() => client.writeEvent(testStream, 'TestEventType', { 46 | something: '456' 47 | })).catch(err => { 48 | assert.fail(err.message); 49 | })).finally(() => client.close()); 50 | }); 51 | 52 | it('Should return successful on stream delete hard delete', callback => { 53 | const client = new EventStore.TCPClient(getTcpConfig()); 54 | 55 | const testStream = `TestStream-${generateEventId()}`; 56 | client.writeEvent(testStream, 'TestEventType', { 57 | something: '123' 58 | }).then(() => client.deleteStream(testStream, true) 59 | .then(() => client.checkStreamExists(testStream)) 60 | .then(() => { 61 | callback('Should not have returned resolved promise'); 62 | }).catch(err => { 63 | assert(err.message.includes('hard deleted'), 'Expected "hard deleted"'); 64 | callback(); 65 | }).catch(callback)).catch(callback).finally(() => client.close()); 66 | }); 67 | 68 | it('Should fail when a stream does not exist', () => { 69 | const client = new EventStore.TCPClient(getTcpConfig()); 70 | 71 | const testStream = `TestStream-${generateEventId()}`; 72 | 73 | return client.deleteStream(testStream).then(() => { 74 | assert.fail('Should have failed because stream does not exist'); 75 | }).catch(err => { 76 | assert(err); 77 | }).finally(() => client.close()); 78 | }); 79 | 80 | it('Should return "StreamDeletedError" when a writing to a stream that has been hard deleted', () => { 81 | const client = new EventStore.TCPClient(getTcpConfig()); 82 | 83 | const testStream = `TestStream-${generateEventId()}`; 84 | 85 | return client.writeEvent(testStream, 'TestEventType', { 86 | something: '123' 87 | }).then(() => client.deleteStream(testStream, true).then(() => client.writeEvent(testStream, 'TestEventType', { 88 | something: '456' 89 | }).then(() => { 90 | assert.fail('Should have failed because stream does not exist'); 91 | })).catch(err => { 92 | assert.equal('StreamDeletedError', err.name); 93 | })).finally(() => client.close()); 94 | }); 95 | }); -------------------------------------------------------------------------------- /tests/tcp.eventEnumerator.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('TCP Client - Event Enumerator', () => { 11 | describe('Forward: Reading events', () => { 12 | it('Read next events', async() => { 13 | const client = new EventStore.TCPClient(getTcpConfig()); 14 | 15 | const events = []; 16 | for (let k = 0; k < 100; k++) { 17 | events.push(eventFactory.newEvent('TestEventType', { 18 | id: k 19 | })); 20 | } 21 | 22 | const testStream = `TestStream-${generateEventId()}`; 23 | await client.writeEvents(testStream, events); 24 | 25 | const enumerator = client.eventEnumerator(testStream); 26 | let result = await enumerator.next(20); 27 | assert.equal(result.events.length, 20); 28 | assert.equal(result.events[0].data.id, 0); 29 | assert.equal(result.events[19].data.id, 19); 30 | 31 | result = await enumerator.next(20); 32 | assert.equal(result.events.length, 20); 33 | assert.equal(result.events[0].data.id, 20); 34 | assert.equal(result.events[19].data.id, 39); 35 | 36 | await client.close(); 37 | }); 38 | 39 | it('Read first 10 events, next 20 events, previous 30 events', async() => { 40 | const client = new EventStore.TCPClient(getTcpConfig()); 41 | 42 | const events = []; 43 | for (let k = 0; k < 100; k++) { 44 | events.push(eventFactory.newEvent('TestEventType', { 45 | id: k 46 | })); 47 | } 48 | 49 | const testStream = `TestStream-${generateEventId()}`; 50 | await client.writeEvents(testStream, events); 51 | 52 | const enumerator = client.eventEnumerator(testStream); 53 | let result = await enumerator.first(10); 54 | assert.equal(result.events.length, 10); 55 | assert.equal(result.events[0].data.id, 0); 56 | assert.equal(result.events[9].data.id, 9); 57 | 58 | result = await enumerator.next(20); 59 | assert.equal(result.events.length, 20); 60 | assert.equal(result.events[0].data.id, 10); 61 | assert.equal(result.events[19].data.id, 29); 62 | 63 | result = await enumerator.previous(30); 64 | assert.equal(result.events.length, 30); 65 | assert.equal(result.events[0].data.id, 0); 66 | assert.equal(result.events[29].data.id, 29); 67 | 68 | await client.close(); 69 | }); 70 | 71 | it('Read last 10 events, previous 30 events, next 30 events', async() => { 72 | const client = new EventStore.TCPClient(getTcpConfig()); 73 | 74 | const events = []; 75 | for (let k = 0; k < 100; k++) { 76 | events.push(eventFactory.newEvent('TestEventType', { 77 | id: k 78 | })); 79 | } 80 | 81 | const testStream = `TestStream-${generateEventId()}`; 82 | await client.writeEvents(testStream, events); 83 | 84 | const enumerator = client.eventEnumerator(testStream); 85 | let result = await enumerator.last(10); 86 | assert.equal(result.events.length, 10); 87 | assert.equal(result.events[0].data.id, 90); 88 | assert.equal(result.events[9].data.id, 99); 89 | 90 | result = await enumerator.previous(30); 91 | assert.equal(result.events.length, 30); 92 | assert.equal(result.events[0].data.id, 70); 93 | assert.equal(result.events[29].data.id, 99); 94 | 95 | result = await enumerator.next(30); 96 | assert.equal(result.events.length, 30); 97 | assert.equal(result.events[0].data.id, 70); 98 | assert.equal(result.events[29].data.id, 99); 99 | 100 | await client.close(); 101 | }); 102 | 103 | it('Read first and last batch', async() => { 104 | const client = new EventStore.TCPClient(getTcpConfig()); 105 | 106 | const events = []; 107 | for (let k = 0; k < 100; k++) { 108 | events.push(eventFactory.newEvent('TestEventType', { 109 | id: k 110 | })); 111 | } 112 | 113 | const testStream = `TestStream-${generateEventId()}`; 114 | await client.writeEvents(testStream, events); 115 | 116 | const enumerator = client.eventEnumerator(testStream); 117 | let result = await enumerator.first(20); 118 | assert.equal(result.events.length, 20); 119 | assert.equal(result.events[0].data.id, 0); 120 | assert.equal(result.events[19].data.id, 19); 121 | 122 | result = await enumerator.last(20); 123 | assert.equal(result.events.length, 20); 124 | assert.equal(result.events[0].data.id, 80); 125 | assert.equal(result.events[19].data.id, 99); 126 | 127 | await client.close(); 128 | }); 129 | 130 | it('Handle out of bounds Enumeration Request ', async() => { 131 | const client = new EventStore.TCPClient(getTcpConfig()); 132 | 133 | const events = []; 134 | for (let k = 0; k < 100; k++) { 135 | events.push(eventFactory.newEvent('TestEventType', { 136 | id: k 137 | })); 138 | } 139 | 140 | const testStream = `TestStream-${generateEventId()}`; 141 | await client.writeEvents(testStream, events); 142 | 143 | const enumerator = client.eventEnumerator(testStream); 144 | let result = await enumerator.first(95); 145 | assert.equal(result.events.length, 95); 146 | assert.equal(result.events[0].data.id, 0); 147 | assert.equal(result.events[94].data.id, 94); 148 | 149 | result = await enumerator.next(20); 150 | assert.equal(result.events.length, 5); 151 | assert.equal(result.events[0].data.id, 95); 152 | assert.equal(result.events[4].data.id, 99); 153 | 154 | result = await enumerator.first(10); 155 | assert.equal(result.events.length, 10); 156 | assert.equal(result.events[0].data.id, 0); 157 | assert.equal(result.events[9].data.id, 9); 158 | 159 | result = await enumerator.previous(20); 160 | assert.equal(result.events.length, 10); 161 | assert.equal(result.events[0].data.id, 0); 162 | assert.equal(result.events[9].data.id, 9); 163 | 164 | await client.close(); 165 | }); 166 | }); 167 | 168 | describe('Backward: Reading events', () => { 169 | it('Read next events', async() => { 170 | const client = new EventStore.TCPClient(getTcpConfig()); 171 | 172 | const events = []; 173 | for (let k = 0; k < 100; k++) { 174 | events.push(eventFactory.newEvent('TestEventType', { 175 | id: k 176 | })); 177 | } 178 | 179 | const testStream = `TestStream-${generateEventId()}`; 180 | await client.writeEvents(testStream, events); 181 | 182 | const enumerator = client.eventEnumerator(testStream, 'backward'); 183 | let result = await enumerator.next(20); 184 | assert.equal(result.events.length, 20); 185 | assert.equal(result.events[0].data.id, 99); 186 | assert.equal(result.events[19].data.id, 80); 187 | 188 | result = await enumerator.next(20); 189 | assert.equal(result.events.length, 20); 190 | assert.equal(result.events[0].data.id, 79); 191 | assert.equal(result.events[19].data.id, 60); 192 | 193 | await client.close(); 194 | }); 195 | 196 | it('Read first 10 events, next 20 events, previous 30 events', async() => { 197 | const client = new EventStore.TCPClient(getTcpConfig()); 198 | 199 | const events = []; 200 | for (let k = 0; k < 100; k++) { 201 | events.push(eventFactory.newEvent('TestEventType', { 202 | id: k 203 | })); 204 | } 205 | 206 | const testStream = `TestStream-${generateEventId()}`; 207 | await client.writeEvents(testStream, events); 208 | 209 | const enumerator = client.eventEnumerator(testStream, 'backward'); 210 | let result = await enumerator.first(10); 211 | assert.equal(result.events.length, 10); 212 | assert.equal(result.events[0].data.id, 99); 213 | assert.equal(result.events[9].data.id, 90); 214 | 215 | result = await enumerator.next(20); 216 | assert.equal(result.events.length, 20); 217 | assert.equal(result.events[0].data.id, 89); 218 | assert.equal(result.events[19].data.id, 70); 219 | 220 | result = await enumerator.previous(30); 221 | assert.equal(result.events.length, 30); 222 | assert.equal(result.events[0].data.id, 99); 223 | assert.equal(result.events[29].data.id, 70); 224 | 225 | await client.close(); 226 | }); 227 | 228 | it('Read last 10 events, previous 20 events, next 30 events', async() => { 229 | const client = new EventStore.TCPClient(getTcpConfig()); 230 | 231 | const events = []; 232 | for (let k = 0; k < 100; k++) { 233 | events.push(eventFactory.newEvent('TestEventType', { 234 | id: k 235 | })); 236 | } 237 | 238 | const testStream = `TestStream-${generateEventId()}`; 239 | await client.writeEvents(testStream, events); 240 | 241 | const enumerator = client.eventEnumerator(testStream, 'backward'); 242 | let result = await enumerator.last(10); 243 | assert.equal(result.events.length, 10); 244 | assert.equal(result.events[0].data.id, 9); 245 | assert.equal(result.events[9].data.id, 0); 246 | 247 | result = await enumerator.previous(30); 248 | assert.equal(result.events.length, 30); 249 | assert.equal(result.events[0].data.id, 29); 250 | assert.equal(result.events[29].data.id, 0); 251 | 252 | result = await enumerator.next(30); 253 | assert.equal(result.events.length, 30); 254 | assert.equal(result.events[0].data.id, 29); 255 | assert.equal(result.events[29].data.id, 0); 256 | 257 | await client.close(); 258 | }); 259 | 260 | it('Read first and last batch', async() => { 261 | const client = new EventStore.TCPClient(getTcpConfig()); 262 | 263 | const events = []; 264 | for (let k = 0; k < 100; k++) { 265 | events.push(eventFactory.newEvent('TestEventType', { 266 | id: k 267 | })); 268 | } 269 | 270 | const testStream = `TestStream-${generateEventId()}`; 271 | await client.writeEvents(testStream, events); 272 | 273 | const enumerator = client.eventEnumerator(testStream, 'backward'); 274 | let result = await enumerator.first(20); 275 | assert.equal(result.events.length, 20); 276 | assert.equal(result.events[0].data.id, 99); 277 | assert.equal(result.events[19].data.id, 80); 278 | 279 | result = await enumerator.last(20); 280 | assert.equal(result.events.length, 20); 281 | assert.equal(result.events[0].data.id, 19); 282 | assert.equal(result.events[19].data.id, 0); 283 | 284 | await client.close(); 285 | }); 286 | 287 | it('Handle out of bounds Enumeration Request ', async() => { 288 | const client = new EventStore.TCPClient(getTcpConfig()); 289 | 290 | const events = []; 291 | for (let k = 0; k < 100; k++) { 292 | events.push(eventFactory.newEvent('TestEventType', { 293 | id: k 294 | })); 295 | } 296 | 297 | const testStream = `TestStream-${generateEventId()}`; 298 | await client.writeEvents(testStream, events); 299 | const enumerator = client.eventEnumerator(testStream, 'backward'); 300 | let result = await enumerator.first(95); 301 | assert.equal(result.events.length, 95); 302 | assert.equal(result.events[0].data.id, 99); 303 | assert.equal(result.events[94].data.id, 5); 304 | 305 | result = await enumerator.next(20); 306 | assert.equal(result.events.length, 5); 307 | assert.equal(result.events[0].data.id, 4); 308 | assert.equal(result.events[4].data.id, 0); 309 | 310 | result = await enumerator.first(10); 311 | assert.equal(result.events.length, 10); 312 | assert.equal(result.events[0].data.id, 99); 313 | assert.equal(result.events[9].data.id, 90); 314 | 315 | result = await enumerator.previous(20); 316 | assert.equal(result.events.length, 10); 317 | assert.equal(result.events[0].data.id, 99); 318 | assert.equal(result.events[9].data.id, 90); 319 | 320 | await client.close(); 321 | }); 322 | }); 323 | }); -------------------------------------------------------------------------------- /tests/tcp.getAllStreamEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('TCP Client - Get All Stream Events', () => { 11 | it('Should write events and read back all stream events', async () => { 12 | const client = new EventStore.TCPClient(getTcpConfig()); 13 | 14 | const events = []; 15 | for (let k = 0; k < 1000; k++) { 16 | events.push(eventFactory.newEvent('TestEventType', { 17 | id: k 18 | })); 19 | } 20 | 21 | const testStream = `TestStream-${generateEventId()}`; 22 | await client.writeEvents(testStream, events); 23 | 24 | const evs = await client.getAllStreamEvents(testStream); 25 | assert.equal(evs.length, 1000); 26 | assert.equal(evs[0].data.id, 0); 27 | assert.equal(evs[999].data.id, 999); 28 | 29 | await client.close(); 30 | }).timeout(5000); 31 | 32 | it('Should write events and read back all events from start event', async () => { 33 | const client = new EventStore.TCPClient(getTcpConfig()); 34 | 35 | const events = []; 36 | for (let k = 0; k < 1000; k++) { 37 | events.push(eventFactory.newEvent('TestEventType', { 38 | id: k 39 | })); 40 | } 41 | 42 | const testStream = `TestStream-${generateEventId()}`; 43 | await client.writeEvents(testStream, events); 44 | 45 | const evs = await client.getAllStreamEvents(testStream, 250, 500); 46 | assert.equal(evs.length, 500); 47 | assert.equal(evs[0].data.id, 500); 48 | assert.equal(evs[499].data.id, 999); 49 | 50 | await client.close(); 51 | }).timeout(5000); 52 | }); -------------------------------------------------------------------------------- /tests/tcp.getEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('TCP Client - Get Events', () => { 11 | const testStream = `TestStream-${generateEventId()}`; 12 | const numberOfEvents = 10; 13 | 14 | before(async () => { 15 | const client = new EventStore.TCPClient(getTcpConfig()); 16 | 17 | const events = []; 18 | 19 | for (let i = 1; i <= numberOfEvents; i++) { 20 | events.push(eventFactory.newEvent('TestEventType', { 21 | something: i 22 | })); 23 | } 24 | 25 | await client.writeEvents(testStream, events); 26 | 27 | await client.close(); 28 | }); 29 | 30 | it('Should get events reading forward', async () => { 31 | const client = new EventStore.TCPClient(getTcpConfig()); 32 | 33 | const events = await client.getEvents(testStream, undefined, undefined, 'forward'); 34 | assert.equal(events.length, 10); 35 | assert.equal(events[0].data.something, 1); 36 | assert.equal('TestEventType', events[0].eventType); 37 | assert(events[0].created); 38 | assert(events[0].metadata !== undefined); 39 | assert(events[0].isJson !== undefined); 40 | assert(events[0].isJson !== undefined); 41 | assert(typeof events[0].eventNumber === 'number', 'event number should be a number'); 42 | 43 | await client.close(); 44 | }); 45 | 46 | it('Should get events reading backward', async () => { 47 | const client = new EventStore.TCPClient(getTcpConfig()); 48 | 49 | const events = await client.getEvents(testStream, undefined, undefined, 'backward'); 50 | assert.equal(events.length, 10); 51 | assert.equal(events[0].data.something, 10); 52 | assert(typeof events[0].eventNumber === 'number', 'event number should be a number'); 53 | 54 | await client.close(); 55 | }); 56 | }); -------------------------------------------------------------------------------- /tests/tcp.readEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('TCP Client - Get Events', () => { 12 | const testStream = `TestStream-${generateEventId()}`; 13 | const numberOfEvents = 10; 14 | 15 | before(async () => { 16 | const client = new EventStore.TCPClient(getTcpConfig()); 17 | 18 | const events = []; 19 | 20 | for (let i = 1; i <= numberOfEvents; i++) { 21 | events.push(eventFactory.newEvent('TestEventType', { 22 | something: i 23 | })); 24 | } 25 | 26 | await client.writeEvents(testStream, events); 27 | 28 | await client.close(); 29 | }); 30 | 31 | it('Should read events reading forward', async () => { 32 | const client = new EventStore.TCPClient(getTcpConfig()); 33 | 34 | const result = await client.readEventsForward(testStream); 35 | assert.equal(result.events.length, 10); 36 | assert.equal(result.events[0].data.something, 1); 37 | assert.equal('TestEventType', result.events[0].eventType); 38 | assert(result.events[0].created); 39 | assert(result.events[0].metadata !== undefined); 40 | assert(result.events[0].isJson !== undefined); 41 | assert(result.events[0].isJson !== undefined); 42 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 43 | 44 | await client.close(); 45 | }); 46 | 47 | it('Should read events reading backward', async () => { 48 | const client = new EventStore.TCPClient(getTcpConfig()); 49 | 50 | const result = await client.readEventsBackward(testStream); 51 | assert.equal(result.events.length, 10); 52 | assert.equal(result.events[0].data.something, 10); 53 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 54 | 55 | await client.close(); 56 | }); 57 | 58 | it('Should read last event reading backward with larger size than events', async () => { 59 | const client = new EventStore.TCPClient(getTcpConfig()); 60 | 61 | const result = await client.readEventsBackward(testStream, 0, 250); 62 | assert.equal(result.events.length, 1); 63 | assert.equal(result.events[0].data.something, 1); 64 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 65 | 66 | await client.close(); 67 | }); 68 | 69 | it('Should not get any events when start event is greater than the stream length', async () => { 70 | const client = new EventStore.TCPClient(getTcpConfig()); 71 | 72 | const result = await client.readEventsForward(testStream, 11); 73 | assert.equal(result.events.length, 0); 74 | 75 | await client.close(); 76 | }); 77 | 78 | it('Should read events reading backward from a start position', async () => { 79 | const client = new EventStore.TCPClient(getTcpConfig()); 80 | 81 | const result = await client.readEventsBackward(testStream, 2); 82 | assert.equal(result.events.length, 3); 83 | assert.equal(result.events[0].data.something, 3); 84 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 85 | 86 | await client.close(); 87 | }); 88 | 89 | it('Should read events reading backward with a count greater than the stream length', async () => { 90 | const client = new EventStore.TCPClient(getTcpConfig()); 91 | 92 | const result = await client.readEventsBackward(testStream, undefined, 10000); 93 | assert.equal(result.events.length, 10); 94 | assert.equal(result.events[0].data.something, 10); 95 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 96 | 97 | await client.close(); 98 | }); 99 | 100 | it('Should read events reading forward with a count greater than the stream length return a maximum of 4096', async function () { 101 | this.timeout(40000); 102 | const client = new EventStore.TCPClient(getTcpConfig()); 103 | 104 | const testStream = `TestStream-${generateEventId()}`; 105 | const numberOfEvents = 5000; 106 | const events = []; 107 | 108 | for (let i = 1; i <= numberOfEvents; i++) { 109 | events.push(eventFactory.newEvent('TestEventType', { 110 | something: i 111 | })); 112 | } 113 | 114 | await client.writeEvents(testStream, events); 115 | const result = await client.readEventsForward(testStream, undefined, 5000); 116 | assert.equal(result.events.length, 4096); 117 | assert.equal(result.events[0].data.something, 1); 118 | assert.equal(result.events[4095].data.something, 4096); 119 | 120 | await client.close(); 121 | }); 122 | 123 | it('Should read linked to events and map correctly', async () => { 124 | const client = new EventStore.TCPClient(getTcpConfig()); 125 | 126 | const result = await client.readEventsForward('$ce-TestStream', 0, 1); 127 | assert.equal(result.events.length, 1); 128 | assert(result.events[0].data.something); 129 | assert(typeof result.events[0].eventNumber === 'number', 'event number should be a number'); 130 | assert(typeof result.events[0].positionEventNumber === 'number', 'position event number should be a number'); 131 | assert.equal(0, result.events[0].positionEventNumber, 'Position event number should be a number'); 132 | assert.equal('$ce-TestStream', result.events[0].positionStreamId); 133 | 134 | await client.close(); 135 | }); 136 | 137 | it('Should read system and deleted events without resolveLinkTos', async () => { 138 | const client = new EventStore.TCPClient(getTcpConfig()); 139 | 140 | const deletedStream = 'TestStreamDeleted'; 141 | await client.writeEvent(deletedStream, 'TestEventType', { something: 1 }); 142 | 143 | await sleep(500); 144 | 145 | const result = await client.readEventsForward('$streams', 0, 4096, false); 146 | assert(result.events.length > 0, 'More than 0 events expected'); 147 | 148 | await client.close(); 149 | }); 150 | }); -------------------------------------------------------------------------------- /tests/tcp.stressTests.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('TCP Client - Stress Tests', () => { 11 | it('Should handle parallel writes', async function () { 12 | this.timeout(20000); 13 | const client = new EventStore.TCPClient(getTcpConfig()); 14 | 15 | const testStream = `TestStream-${generateEventId()}`; 16 | const numberOfEvents = 5000; 17 | const events = []; 18 | 19 | for (let i = 1; i <= numberOfEvents; i++) { 20 | events.push(eventFactory.newEvent('TestEventType', { 21 | something: i 22 | })); 23 | } 24 | 25 | await Promise.all(events.map(ev => client.writeEvent(testStream, ev.eventType, ev.data))); 26 | const evs = await client.getEvents(testStream, undefined, 5000); 27 | assert.equal(evs.length, 4096); 28 | 29 | await client.close(); 30 | }); 31 | 32 | it('Should handle parallel reads and writes', function (callback) { 33 | this.timeout(60000); 34 | const client = new EventStore.TCPClient(getTcpConfig()); 35 | 36 | const testStream = `TestStream-${generateEventId()}`; 37 | const numberOfEvents = 5000; 38 | const events = []; 39 | let writeCount = 0; 40 | let readCount = 0; 41 | 42 | for (let i = 1; i <= numberOfEvents; i++) { 43 | events.push(eventFactory.newEvent('TestEventType', { 44 | something: i 45 | })); 46 | } 47 | 48 | const checkCounts = async () => { 49 | if (readCount === numberOfEvents && writeCount === numberOfEvents && writeCount === readCount) { 50 | await client.close(); 51 | callback(); 52 | } 53 | }; 54 | 55 | events.forEach(ev => { 56 | client.writeEvent(testStream, ev.eventType, ev.data).then(() => { 57 | writeCount++; 58 | checkCounts(); 59 | }); 60 | }); 61 | events.forEach(() => { 62 | client.getEvents(testStream, undefined, 10).then(() => { 63 | readCount++; 64 | checkCounts(); 65 | }); 66 | }); 67 | }); 68 | }); -------------------------------------------------------------------------------- /tests/tcp.subscribeToStream.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('TCP Client - Subscribe To Stream', () => { 12 | it('Should get all events written to a subscription stream after subscription is started', function (done) { 13 | this.timeout(15 * 1000); 14 | const client = new EventStore.TCPClient(getTcpConfig()); 15 | const testStream = `TestStream-${generateEventId()}`; 16 | let processedEventCount = 0; 17 | let hasPassed = false; 18 | 19 | function onEventAppeared() { 20 | processedEventCount++; 21 | } 22 | 23 | async function onDropped() { 24 | if (!hasPassed) { 25 | await client.closeAllPools(); 26 | done('should not drop'); 27 | } 28 | } 29 | 30 | const initialEvents = []; 31 | 32 | for (let k = 0; k < 10; k++) { 33 | initialEvents.push(eventFactory.newEvent('TestEventType', { 34 | id: k 35 | })); 36 | } 37 | 38 | client.writeEvents(testStream, initialEvents).then(() => { 39 | client.subscribeToStream(testStream, onEventAppeared, onDropped, false).then(subscription => sleep(3000).then(async () => { 40 | assert.equal(10, processedEventCount, 'expect processed events to be 10'); 41 | assert(subscription, 'Subscription Expected'); 42 | hasPassed = true; 43 | await subscription.close(); 44 | await client.close(); 45 | done(); 46 | })); 47 | const events = []; 48 | for (let k = 0; k < 10; k++) { 49 | events.push(eventFactory.newEvent('TestEventType', { 50 | id: k 51 | })); 52 | } 53 | return sleep(100).then(() => client.writeEvents(testStream, events)); 54 | }).catch(done); 55 | }); 56 | 57 | it('Should be able to start multiple subscriptions from single client instance', async function () { 58 | this.timeout(15 * 1000); 59 | const client = new EventStore.TCPClient(getTcpConfig()); 60 | 61 | const testStream = `TestStream-${generateEventId()}`; 62 | const events = []; 63 | for (let k = 0; k < 10; k++) events.push(eventFactory.newEvent('TestEventType', { id: k })); 64 | 65 | let processedEventCount1 = 0; 66 | let processedEventCount2 = 0; 67 | const onEv1 = () => processedEventCount1++; 68 | const onEv2 = () => processedEventCount2++; 69 | 70 | const sub1 = await client.subscribeToStream(testStream, onEv1, () => {}); 71 | const sub2 = await client.subscribeToStream(testStream, onEv2, () => {}); 72 | await sleep(1000); 73 | await client.writeEvents(testStream, events); 74 | await sleep(3000); 75 | 76 | assert.equal(10, processedEventCount1, 'Expect processed events to be 10 for subscription 1'); 77 | assert.equal(10, processedEventCount2, 'Expect processed events to be 10 for subscription 2'); 78 | 79 | await sub1.close(); 80 | await sub2.close(); 81 | await client.closeAllPools(); 82 | }); 83 | }); -------------------------------------------------------------------------------- /tests/tcp.subscribeToStreamFrom.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import sleep from './utilities/sleep'; 6 | import EventStore from '../lib'; 7 | import assert from 'assert'; 8 | 9 | const eventFactory = new EventStore.EventFactory(); 10 | 11 | describe('TCP Client - Subscribe To Stream From', () => { 12 | it('Should get all events written to a subscription stream', function (done) { 13 | this.timeout(15 * 1000); 14 | const client = new EventStore.TCPClient(getTcpConfig()); 15 | const testStream = `TestStream-${generateEventId()}`; 16 | let processedEventCount = 0; 17 | let hasPassed = false; 18 | 19 | function onEventAppeared() { 20 | processedEventCount++; 21 | } 22 | 23 | function onDropped() { 24 | if (!hasPassed) done('should not drop'); 25 | } 26 | 27 | const events = []; 28 | for (let k = 0; k < 10; k++) { 29 | events.push(eventFactory.newEvent('TestEventType', { id: k })); 30 | } 31 | 32 | client.writeEvents(testStream, events).then(() => 33 | client.subscribeToStreamFrom(testStream, 0, onEventAppeared, undefined, onDropped) 34 | .then(sub => sleep(3000).then(() => { 35 | assert.equal(10, processedEventCount); 36 | assert(sub, 'Subscription Expected'); 37 | hasPassed = true; 38 | return sub.close().then(() => done()); 39 | })) 40 | ).catch(done).finally(() => client.closeAllPools()); 41 | }); 42 | 43 | it('Should get all resolved events read from middle of a linked stream', function (done) { 44 | this.timeout(9 * 1000); 45 | 46 | const client = new EventStore.TCPClient(getTcpConfig()); 47 | const testStream = `TestStream-${generateEventId()}`; 48 | let hasProcessedEvents = false; 49 | let hasPassed = false; 50 | let hasReachAssert = false; 51 | 52 | function onEventAppeared(sub, ev) { 53 | assert(ev.positionEventId, 'Position link event id expected'); 54 | hasProcessedEvents = true; 55 | } 56 | 57 | async function onDropped() { 58 | if (!hasPassed) { 59 | await client.close(); 60 | if (!hasReachAssert) done('should not drop during test'); 61 | } 62 | } 63 | 64 | const events = []; 65 | for (let k = 0; k < 10; k++) { 66 | events.push(eventFactory.newEvent('TestEventType', { id: k })); 67 | } 68 | 69 | client.writeEvents(testStream, events).then(() => { 70 | const settings = { 71 | resolveLinkTos: true 72 | }; 73 | return client.subscribeToStreamFrom(`$ce-TestStream`, 5, onEventAppeared, undefined, onDropped, settings) 74 | .then(sub => sleep(3000).then(() => { 75 | hasReachAssert = true; 76 | assert(hasProcessedEvents, `Should have processed events`); 77 | assert(sub, 'Subscription Expected'); 78 | hasPassed = true; 79 | return sub.close().then(() => done()); 80 | })); 81 | }).catch(done).finally(() => client.closeAllPools()); 82 | }); 83 | }); -------------------------------------------------------------------------------- /tests/tcp.writeEvent.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | describe('TCP Client - Write Event', () => { 9 | it('Write to a new stream and read the event', async () => { 10 | const client = new EventStore.TCPClient(getTcpConfig()); 11 | 12 | const testStream = `TestStream-${generateEventId()}`; 13 | await client.writeEvent(testStream, 'TestEventType', { 14 | something: '123' 15 | }); 16 | 17 | const events = await client.getEvents(testStream); 18 | assert.equal(events[0].data.something, '123'); 19 | 20 | await client.close(); 21 | }); 22 | 23 | it('Should fail promise if no event data provided', () => { 24 | const client = new EventStore.TCPClient(getTcpConfig()); 25 | 26 | const testStream = `TestStream-${generateEventId()}`; 27 | return client.writeEvent(testStream, 'TestEventType').then(async () => { 28 | await client.close(); 29 | assert.fail('write should not have succeeded'); 30 | }).catch(err => { 31 | assert(err, 'error should have been returned'); 32 | }); 33 | }); 34 | }); 35 | 36 | describe('TCP Client - Write Event to pre-populated stream', () => { 37 | let client; 38 | let testStream; 39 | beforeEach(async () => { 40 | client = new EventStore.TCPClient(getTcpConfig()); 41 | testStream = `TestStream-${generateEventId()}`; 42 | 43 | await client.writeEvent(testStream, 'TestEventType', { 44 | something: '123' 45 | }); 46 | 47 | await client.writeEvent(testStream, 'TestEventType', { 48 | something: '456' 49 | }, null, { 50 | expectedVersion: 0 51 | }); 52 | 53 | await client.writeEvent(testStream, 'TestEventType', { 54 | something: '789' 55 | }, null, { 56 | expectedVersion: 1 57 | }); 58 | }); 59 | 60 | afterEach(async () => { 61 | await client.close(); 62 | }); 63 | 64 | it('Should fail promise if passed in wrong expectedVersion (covering edge case of expectedVersion=0)', async () => { 65 | try { 66 | await client.writeEvent(testStream, 'TestEventType', { 67 | something: 'abc' 68 | }, null, { 69 | expectedVersion: 0 70 | }); 71 | } catch (err) { 72 | assert(err, 'Error expected'); 73 | assert(err.message, 'Error Message Expected'); 74 | return; 75 | } 76 | assert.fail('Write should not have succeeded'); 77 | }); 78 | 79 | it('Should write event if expectedVersion=null', async () => { 80 | try { 81 | await client.writeEvent(testStream, 'TestEventType', { 82 | something: 'abc' 83 | }, null, { 84 | expectedVersion: null 85 | }); 86 | } catch (err) { 87 | assert.fail('Write should not have failed'); 88 | } 89 | }); 90 | }); -------------------------------------------------------------------------------- /tests/tcp.writeEvents.js: -------------------------------------------------------------------------------- 1 | import './_globalHooks'; 2 | 3 | import generateEventId from '../lib/utilities/generateEventId'; 4 | import getTcpConfig from './support/getTcpConfig'; 5 | import EventStore from '../lib'; 6 | import assert from 'assert'; 7 | 8 | const eventFactory = new EventStore.EventFactory(); 9 | 10 | describe('TCP Client - Write Events', () => { 11 | it('Write to a new stream and read the events', async () => { 12 | const client = new EventStore.TCPClient(getTcpConfig()); 13 | 14 | const events = [eventFactory.newEvent('TestEventType', { 15 | something: '456' 16 | })]; 17 | 18 | const testStream = `TestStream-${generateEventId()}`; 19 | await client.writeEvents(testStream, events); 20 | 21 | const evs = await client.getEvents(testStream); 22 | assert.equal(evs[0].data.something, '456'); 23 | 24 | await client.close(); 25 | }); 26 | 27 | it('Write to a new stream and read the events by type', async () => { 28 | const client = new EventStore.TCPClient(getTcpConfig()); 29 | 30 | const events = [eventFactory.newEvent('TestEventType', { 31 | something: '456' 32 | }), eventFactory.newEvent('ToBeIgnoredType', { 33 | something: '789' 34 | })]; 35 | 36 | const testStream = `TestStream-${generateEventId()}`; 37 | await client.writeEvents(testStream, events); 38 | 39 | const evs = await client.getEventsByType(testStream, ['TestEventType']); 40 | assert.equal(evs.length, 1); 41 | assert.equal(evs[0].eventType, 'TestEventType'); 42 | assert.equal(evs[0].data.something, '456'); 43 | 44 | await client.close(); 45 | }); 46 | 47 | it('Should not fail promise if no events provided', async () => { 48 | const client = new EventStore.TCPClient(getTcpConfig()); 49 | 50 | const events = []; 51 | const testStream = `TestStream-${generateEventId()}`; 52 | await client.writeEvents(testStream, events); 53 | }); 54 | 55 | it('Should fail promise if non array provided', () => { 56 | const client = new EventStore.TCPClient(getTcpConfig()); 57 | 58 | const events = { 59 | something: 'here' 60 | }; 61 | 62 | const testStream = `TestStream-${generateEventId()}`; 63 | return client.writeEvents(testStream, events).then(async () => { 64 | await client.close(); 65 | assert.fail('should not have succeeded'); 66 | }).catch(err => { 67 | assert(err, 'error expected'); 68 | }); 69 | }); 70 | }); 71 | 72 | describe('TCP Client - Write Events to pre-populated stream', () => { 73 | let client, 74 | testStream, 75 | events, 76 | events2; 77 | 78 | beforeEach(async () => { 79 | client = new EventStore.TCPClient(getTcpConfig()); 80 | 81 | events = [eventFactory.newEvent('TestEventType', { 82 | something: '456' 83 | }), eventFactory.newEvent('ToBeIgnoredType', { 84 | something: '789' 85 | })]; 86 | 87 | testStream = `TestStream-${generateEventId()}`; 88 | await client.writeEvents(testStream, events); 89 | 90 | events2 = [eventFactory.newEvent('TestEventType', { 91 | something: 'abc' 92 | })]; 93 | }); 94 | 95 | afterEach(async () => { 96 | await client.close(); 97 | }); 98 | 99 | it('Should fail promise if passed in wrong expectedVersion (covering edge case of expectedVersion=0)', async () => { 100 | try { 101 | await client.writeEvents(testStream, events2, { 102 | expectedVersion: 0 103 | }); 104 | } catch (err) { 105 | assert(err, 'Error expected'); 106 | assert(err.message, 'Error Message Expected'); 107 | return; 108 | } 109 | assert.fail('Write should not have succeeded'); 110 | }); 111 | 112 | it('Should write event if expectedVersion=null', async () => { 113 | try { 114 | await client.writeEvents(testStream, events2, { 115 | expectedVersion: null 116 | }); 117 | } catch (err) { 118 | assert.fail('Write should not have failed'); 119 | } 120 | }); 121 | }); -------------------------------------------------------------------------------- /tests/utilities/sleep.js: -------------------------------------------------------------------------------- 1 | export default (delay) => new Promise(resolve => setTimeout(resolve, delay)); --------------------------------------------------------------------------------