Compare commits

..

175 Commits

Author SHA1 Message Date
Nicolas Dextraze
80f2e7e50f Merge branch 'release-22.10' 2023-01-30 16:17:53 -05:00
Nicolas Dextraze
9bbc961d52 version 22.10.0 2023-01-30 16:17:22 -05:00
Nicolas Dextraze
3a2e30281f Merge branch 'release-22.10' 2023-01-30 15:42:07 -05:00
Nicolas Dextraze
a48e4fa386 compatibility for 22.x 2023-01-30 15:37:10 -05:00
Nicolas Dextraze
c422057f5f start with release-21.10 code changes 2023-01-30 15:08:59 -05:00
6159cd56b8
Merge pull request #94 from jwulf/https
Support https for projections
2022-10-19 13:48:32 -04:00
cbf3c7bb5e
Update README.md 2022-10-14 11:06:04 -04:00
Nicolas Dextraze
b17ab0c77a
Update README.md 2020-12-16 11:05:13 -05:00
Nicolas Dextraze
c67d0d25a2
Merge pull request #102 from PrestaShopCorp/fixShuffle
Fix for shuffle method and remove unused code
2020-10-02 16:54:50 -07:00
maniolias
5bc2aaa093 refactor: delete unused code 2020-10-01 16:27:39 +02:00
maniolias
401f6c3190 fix(shuffle): fix shuffle methode for array with length <= 1 2020-10-01 16:23:42 +02:00
Nicolas Dextraze
40736d9da3 relese version to 0.2.17 2020-09-28 17:29:02 -07:00
Nicolas Dextraze
853ae875b8
Merge pull request #101 from PrestaShopCorp/feature/cluster
Rework on cluster discovering
2020-09-28 17:15:19 -07:00
maniolias
da6d059547 feat(cluster): create way to test single/gossip/cluster
* docker-compose files to easily setup cluster or single node eventstore
* programmatic tests for single/gossip/cluster
2020-09-17 10:33:49 +02:00
maniolias
e9834daa31 feat(tcp): add timeout when trying to connect to a TCP endpoint
* the timeout parameters in options will trigger a timeout only when the socket is connected (cf. https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback). To handle connection timeout we have to handle it manually
2020-09-17 09:52:11 +02:00
maniolias
f79a0444f6 feat(cluster): rewrite cluster discovering
* Discovering process adapted from EventStore scala client
* Use DNS for first discover but also for reconnoctions (the aim is to be able to reconnect even if all nodes have new IP addresses eg. after rolling update in a cloud environment)
* Being able to perform proper unit testing
2020-09-17 09:52:11 +02:00
Nicolas Dextraze
eb56e077f9
Merge pull request #95 from aliabbasjaffri/HA-Cluster-Connectivity-Issue
🐛 Adding object reference to externalTcpIp
2020-03-02 08:46:39 -08:00
Ali Abbas Jaffri
11f882501f 🐛 Adding object reference to externalTcpIp: Solves locating cluster via gossip seeds 2020-03-02 15:39:32 +01:00
Josh Wulf
7b7ccc2eef Correct protocol detection 2019-12-11 02:54:47 +10:00
Josh Wulf
6b75d241fc Support https for projections 2019-12-11 02:47:41 +10:00
Josh Wulf
7da398eae8 Support https for projections 2019-12-11 02:39:37 +10:00
195a6d40ce Attempt at fixing Typescript dependencies issues 2019-11-02 16:13:51 -07:00
55b9ddd856 Minor improvement to protobuf isolation 2019-11-02 16:02:07 -07:00
fe82aebfec Fix #91 2019-11-02 15:40:02 -07:00
Nicolas Dextraze
3f7798d46e Update package-lock.json and yarn.lock 2019-11-01 13:12:19 -07:00
Nicolas Dextraze
6ad2d34114 Added some more tests around RecordedEvent properties type 2019-11-01 12:57:54 -07:00
Nicolas Dextraze
67dab18d53 Fixed Buffer deprecated warnings when using node >= v10 2019-11-01 12:53:24 -07:00
Nicolas Dextraze
78677ba53e Add missing created to RecordedEvent typescript definition 2019-11-01 12:47:03 -07:00
Nicolas Dextraze
bbf25ee8b5 Update dev dependencies
Set idea language to ES6
Run tests against dist
2019-11-01 11:30:06 -07:00
Nicolas Dextraze
732b691e40 Move typescript dependencies back to dev 2019-11-01 11:13:04 -07:00
Nicolas Dextraze
941f671ed2 Fix missing warn in Loggers 2019-11-01 11:10:16 -07:00
Nicolas Dextraze
343eaa85df
Merge pull request #86 from MajorBreakfast/patch-1
Make type definition of expectedVersion match implementation
2019-11-01 10:48:57 -07:00
Josef Brandl
e0fcf9a72d
Make type definition of expectedVersion match implementation 2019-08-11 18:54:06 +02:00
Nicolas Dextraze
02642c5cfb Fix appendToStream with expectedVersion any and same event not working due to protobufjs bug 2019-08-02 22:34:38 -07:00
Nicolas Dextraze
d64f181dbb release version 0.2.12 2019-06-06 12:22:00 -07:00
Nicolas Dextraze
9e31b17c44
Merge pull request #80 from jdextraze/jdextraze/simple-queued-handler-log-issue
Fix error with logging in SimpleQueuedHandler handler
2019-06-06 12:16:53 -07:00
Jonathan Dextraze
4ae4c1e010 Fix error with logging in SimpleQueuedHandler handler 2019-06-06 11:42:22 -07:00
76a405ace2 Publish version 0.2.11
Fixing #72, #73, #74
2019-05-20 13:06:20 -07:00
Nicolas Dextraze
930b900426
Merge pull request #78 from Poimen/fix-issue-76
Adds extra options to delete
2019-05-20 13:01:26 -07:00
Nicolas Dextraze
934251d7fa
Merge pull request #77 from Poimen/fix-issue-75
Add reset endpoint to projection manager
2019-05-20 13:00:44 -07:00
Richard Shephard
1449b43b8d Fix missing = in delete uri 2019-05-17 17:48:29 +02:00
Richard Shephard
2f4850680f Fix incorrect variables used after undefined removal 2019-05-16 17:01:30 +02:00
Richard Shephard
8f68923008 Remove undefine's check as the options will need to be provided anyways... 2019-05-16 17:00:47 +02:00
Richard Shephard
8e54a22cd2 Fix uri for reset 2019-05-16 16:52:11 +02:00
Richard Shephard
6663b42903 Fix sendDelete call to provide empty data so header is correct 2019-05-16 16:18:09 +02:00
Richard Shephard
ad8d16d112 Fix documentation 2019-05-16 15:37:58 +02:00
Richard Shephard
bebd04a398 Fix client endpoint used 2019-05-16 15:36:05 +02:00
Richard Shephard
4d981596eb Fix typings for delete (there is no deleteQuery) in projection manager 2019-05-16 15:35:20 +02:00
Richard Shephard
7ccced45aa Add reset endpoint 2019-05-16 13:57:11 +02:00
Richard Shephard
88df807508 Update documentation for strict-event-emitter-types dependency 2019-05-16 11:59:56 +02:00
Richard Shephard
35f720f94f Added extra parameters to delete projections 2019-05-16 11:56:25 +02:00
Nicolas Dextraze
6d49f51e7d fix issue #74 - projections manager create continuous trackEmittedStreams parameter should be lowercase 2019-05-15 10:23:48 -07:00
Nicolas Dextraze
d8baed4c5d fix issue #73 - move types dependencies from dev dependencies so they install for dependant 2019-05-15 09:52:57 -07:00
Nicolas Dextraze
7e5327c278 Published version 0.2.10 and moved types dependencies to devDependencies 2019-02-19 09:03:17 -08:00
Nicolas Dextraze
bb53c01fe8
Merge pull request #69 from sebastianneb/68-debug
Fix for issue #68 by using self._log_debug instead of this._log.debug
2019-02-19 08:26:20 -08:00
Neb, Sebastian
20818869f1 Fix for issue #68 by using self._log_debug instead of this._log.debug 2019-02-19 09:55:49 +01:00
Nicolas Dextraze
16f172c6f2
Merge pull request #66 from Ulrikop/ts-declarations
Adds EventEmitter declarations to EventStoreNodeConnection
2019-02-18 09:16:11 -08:00
Nicolas Dextraze
7db060af6e Removed some console
Fixed issue 60
2019-02-18 09:12:28 -08:00
Nicolas Dextraze
2c272a19f5
Merge pull request #67 from JurJean/remove-subscription-started-log
Remove subscription started log
2019-02-14 20:44:20 -08:00
Jur Jean
7dc4658719 Merge branch 'master' of github.com:nicdex/node-eventstore-client into remove-subscription-started-log 2019-02-13 12:51:07 +01:00
Jur Jean
811368c1af Removed the console log about subscription started 2019-02-13 12:50:32 +01:00
Ulrikop
fcf27fba20 Changes ts declaration for EventStoreNodeConnection so that it offers all EventEmitter methods. 2019-02-09 13:16:07 +01:00
Nicolas Dextraze
e9d86a3c4d Fix issue #63 - export PersistentSubscriptionNakEventAction 2019-01-17 10:00:49 -08:00
Nicolas Dextraze
0db2b93b79
Merge pull request #62 from JurJean/invalid-missing-content-type
Send Content-Length to eliminate missing Content-Type error in some cases
2018-12-05 08:59:53 -08:00
Jur Jean
ef26ed636f Send Content-Length to eliminate missing Content-Type error in some cases 2018-12-04 14:05:44 +01:00
2893454e8a Updated messages.proto to have optional event for ResolvedEvent and ResolvedIndexedEvent and regenarated protobuf code 2018-10-27 16:00:44 -07:00
a0642ab081 Re-add optional (allowing null) on ResolvedEvent event property in protobuf 2018-10-27 15:50:11 -07:00
760d31cc31 Published 0.2.5 to npm 2018-10-21 14:55:05 -07:00
c1f17a7d99 #61 fixed subscriptionDropped being called only once
Added subscribe-all-from-events sample to test
2018-10-21 14:47:25 -07:00
96145fbe49 Published 0.2.4, adding package-lock.json 2018-09-01 14:06:24 -07:00
Nicolas Dextraze
5ac4a206ad
Merge pull request #59 from sledorze/master
Fix definitions of PersistentSubscriptions
2018-09-01 13:51:41 -07:00
Stéphane Le Dorze
e1f99cecbc
Fix definitions of PersistenSubscriptions
`userCredentials` is now correctly typed as `UserCredentials`
2018-08-21 10:17:46 +02:00
Nicolas Dextraze
0a64b37b6f Fix issue with fake-server 2018-07-11 16:10:53 -07:00
Nicolas Dextraze
3421d41068 Adding a fake server for testing purpose 2018-07-10 09:22:21 -07:00
215708014c Add Object.freeze on private enums
Improve code readability
2018-07-09 10:27:12 -07:00
Nicolas Dextraze
c3a63ff8b7
Merge pull request #58 from cdimitroulas/improve-docs
improve docs for createConnection
2018-07-09 09:12:43 -07:00
Christos Dimitroulas
d8c0bfb03a improve docs for createConnection 2018-07-09 11:58:39 +01:00
34e8262dc8 Make Ssl tests opt-in 2018-04-12 09:42:49 -07:00
bc2fbe14e3 Adding SSL support, release 0.2.3 2018-03-11 15:25:44 -07:00
9fc5d64ceb Adding start/end as static Position
Add the streamPosition constants
Move Projections into the expose classes section
Version 0.2.2
2018-02-19 10:06:33 -08:00
Nicolas Dextraze
2bdd74e041
Merge pull request #53 from paullucas/master
Upgrade jsdoc to version 3.5.5
2018-02-18 17:03:07 -08:00
Paul
fde6ad2e77 Use jsdoc version 3.5.5 2018-02-18 16:50:59 -08:00
4a2065c0a7 Release npm package version to 0.2.1 2018-01-17 11:31:25 -08:00
67dd275b5d Fix potential error handling bug due to missing EventEmitter.call(this) in EventStoreNodeConnection and EventStoreConnectionLogicHandler 2018-01-17 11:29:24 -08:00
8997a8c398 Update README 2017-11-08 13:06:16 -08:00
a5807483a2 Merge branch 'v4_proto' 2017-11-08 12:47:30 -08:00
e79ad8f9c4 Use Long in Stream catchup subscription
Use Long in tests
Bump version to 2.0
2017-11-08 12:45:22 -08:00
Nicolas Dextraze
bf89354564 Merge pull request #50 from sledorze/master
fix noopLogger typo fix #49
2017-10-26 10:25:24 -07:00
Stéphane Le Dorze
254ddda8a2 fix noopLogger typo fix #49 2017-10-23 22:11:11 +02:00
Nicolas Dextraze
16c081020a Add ProjectionsManager 2017-10-18 14:39:25 -07:00
Nicolas Dextraze
4584517ede Update tests, typescript declaration 2017-10-17 15:55:07 -07:00
Nicolas Dextraze
e6af223f0d Fix identify client serialize 2017-10-17 14:36:00 -07:00
Nicolas Dextraze
990351738c Update proto 2017-10-17 14:31:26 -07:00
18e35c56f9 Adding speed tests 2017-09-03 12:37:41 -07:00
20de4c3715 remove useless new Buffer(guidParse.parse(...)) since it's now returning a buffer 2017-09-03 12:36:39 -07:00
60d2a1e5ee Release version 0.1.8 2017-09-03 09:57:22 -07:00
a8081a280a Fix GUID bytes ordering 2017-09-02 15:30:19 -07:00
7c94e26055 handle eventAppeared callback returning Promise 2017-07-16 17:11:54 -07:00
Nicolas Dextraze
0b968c40e8 Released version 0.1.5 2017-06-30 19:20:24 -07:00
Nicolas Dextraze
21ab49845f Adding persistent subscription API to typescript definition 2017-06-30 18:54:56 -07:00
Nicolas Dextraze
6634dee5f3 Fix unhandled rejection issue with connectToPersistentSubscription #44 2017-06-30 18:54:25 -07:00
1d21facd2c Fix #41 2017-06-24 11:32:04 -07:00
Nicolas Dextraze
8735b23bf9 Remove strictness of UUID 2017-06-05 15:23:27 -07:00
Nicolas Dextraze
f66decdfb3 Rename project to node-eventstore-client 2017-05-17 12:46:27 -07:00
Nicolas Dextraze
121e248dd8 #35 implement heartbeatInfo event on connection 2017-05-15 12:55:14 -07:00
Nicolas Dextraze
917b89cf3d Minor fixes after merging PR #33, adding cluster connection example in samples/simple.js 2017-04-26 18:10:27 -07:00
Nicolas Dextraze
a1b9d6db9b Merge pull request #33 from paullucas/master
Refactor samples
2017-04-26 17:54:03 -07:00
Paul
83b88fc015 Add optional require statement for client 2017-04-20 19:40:09 -07:00
Paul
4130ab646c Refactor samples 2017-04-20 19:32:53 -07:00
83cfa8fe1c Updated protobufjs to latest version 2017-04-16 15:51:09 -07:00
e0446cfb3e Current features have been stable for a while, so I'm bumping version to 0.1.0 2017-04-16 13:11:56 -07:00
0302099a16 Published version 0.0.32 2017-04-16 12:55:56 -07:00
f7c13634cc Fixed failing samples
Updated uuid/webpack packages
Added froze on objects publicly exposed
Removed remaining while loops for actions/events processing
2017-04-16 12:51:17 -07:00
56c2dee6d6 Fix #30, publish version 0.0.31 2017-04-01 14:56:34 -07:00
Nicolas Dextraze
85814ed03f Fix binary string issue in Large event tests 2017-03-29 13:29:17 -07:00
Nicolas Dextraze
361330629d Merge pull request #28 from MarshallRJ/test-largeEvents
Test large events
2017-03-29 13:00:46 -07:00
Rob
883e89abfe [test] read a large event from the stream 2017-03-29 09:59:54 +02:00
Rob
e39280a2dc [test] append Large event to a stream 2017-03-29 09:59:20 +02:00
Nicolas Dextraze
05b36f22bc Published version 0.0.30 2017-03-24 19:05:51 -07:00
Nicolas Dextraze
217c1c6bb8 Fix a bug in tcpConnection that was blocking sending of packet > 64k. Fixes #27 2017-03-24 19:04:01 -07:00
Nicolas Dextraze
eaec15fe74 Merge pull request #26 from MarshallRJ/test-AppendToStream
[test] append to stream stress test
2017-03-24 17:51:24 -07:00
Rob
d71d565197 [test] append to stream stress test 2017-03-22 10:12:53 +02:00
9e29e60cfa Adding test around live/read events 2017-03-11 14:31:24 -08:00
Nicolas Dextraze
158ca2d0a7 v0.0.28 2017-02-25 14:12:13 -08:00
Nicolas Dextraze
34e928c440 Expose Position, publish version 0.0.27 2017-02-18 23:10:15 -08:00
212dd10ce2 Fix TypeScript definitions 2017-02-02 18:02:14 -08:00
Nicolas Dextraze
cf196b9cc2 Update README and publish 0.0.25 2017-01-29 11:09:09 -08:00
Nicolas Dextraze
b9de9568a5 Adding code style for jetbrains IDEs 2017-01-29 11:01:12 -08:00
Nicolas Dextraze
e2459321f6 Fixing small bugs in the reconnection logic 2017-01-29 11:00:40 -08:00
Nicolas Dextraze
5d6ec8b43f 19: Implement cluster discovery using dns. Adding missing file. 2017-01-29 10:59:55 -08:00
Nicolas Dextraze
1558918692 19: Implement cluster discovery using dns 2017-01-29 10:57:59 -08:00
Nicolas Dextraze
0463d85cfe Switch back non-cluster tests to localhost 2017-01-28 18:35:35 -08:00
Nicolas Dextraze
faed60b8ac Updated README, bumped to version 0.0.24 2017-01-28 18:28:36 -08:00
Nicolas Dextraze
006b5b4791 18 - Changing all hostname to host, fixing some async issues in tests 2017-01-28 18:04:58 -08:00
Nicolas Dextraze
f97b7fff8e Adding Vagrantfile for a vagrant machine running EventStore 2017-01-09 15:18:56 -08:00
cc0b6259d1 Fix missing return value in TypeScript definition.
Published version 0.0.23
2017-01-04 08:58:00 -08:00
a5a9f7c1ee Fixing extraneous deps issue 2017-01-03 11:02:54 -08:00
8d7996e547 Fix always connecting to localhost issue 2017-01-03 10:44:39 -08:00
3ae8086e8d Fix issue #15 - completed operation not removed from operation manager 2016-12-07 18:14:31 -08:00
c45155811e Published v0.0.18 2016-12-06 18:59:22 -08:00
3acfaafb2a 14 - fix creationConnection with tcp://host:port failing 2016-12-06 18:56:06 -08:00
52dee3584e Removing .idea/misc.xml file 2016-12-06 18:36:49 -08:00
dfd3ede298 Removed @types/es6-promise dev dependencies 2016-11-23 10:23:04 -08:00
c929681bbd Removing es6-promise reference from TypeScript declaration.
See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/5015 for explanation.
2016-11-23 10:15:37 -08:00
4a28a23e20 Fixing constructor/class definition for GossipSeed and UserCrendentials 2016-11-22 09:20:55 -08:00
eb5f95975b Fixing EventAppeared callback definition for TypeScript 2016-11-21 15:56:04 -08:00
Nicolas Dextraze
51adf5bcc8 Adding index.d.ts to files, version 0.0.13 2016-11-14 22:49:49 -08:00
Nicolas Dextraze
0fbbecea06 Merge branch '13-create-typescript-declarations' 2016-11-14 22:44:14 -08:00
Nicolas Dextraze
9b9e202cec Completing TypeScript declaration file (only missing persistent subscriptions) 2016-11-14 22:33:33 -08:00
60f1d7252b Minimalistic TypeScript declaration file, wip for issue #13 2016-11-14 21:38:24 -08:00
Nicolas Dextraze
9909bc053a Adding .idea files 2016-11-14 21:31:43 -08:00
Nicolas Dextraze
269c89be29 Add Documentation section to README 2016-11-07 22:09:57 -08:00
Nicolas Dextraze
99d9322d2a Merge branch '12-create-docs' 2016-11-07 21:56:04 -08:00
Nicolas Dextraze
39e47396b2 Published version 0.10 with docs 2016-11-07 21:55:52 -08:00
454b150fb6 Working on documentation (wip) 2016-11-06 22:58:11 -08:00
90e7ef1a9d Released npm package version 0.0.9 2016-11-06 16:28:24 -08:00
Nicolas Dextraze
9eb37ef42e Merge pull request #11 from nicdex/9-undefined-connectionId
9 - add missing connectionId to function parameter
2016-11-06 16:24:56 -08:00
Nicolas Dextraze
14a05606ab Merge pull request #10 from nicdex/8-fix-creation-time
using toNumber instead of toInt fixes the creation time issue
2016-11-06 16:24:24 -08:00
7243163022 9 - add missing connectionId to function parameter 2016-11-06 16:23:24 -08:00
ed65fc8742 using toNumber instead of toInt fixes the creation time issue 2016-11-06 16:14:40 -08:00
c6a22739f3 Share InteliJ settings 2016-11-06 16:12:49 -08:00
Nicolas Dextraze
4a593053ad Merge pull request #7 from Adaptech/master
Samples for subscriptions, README.
2016-11-01 09:20:32 -07:00
Robert on Asus 305
5569dffaf5 Subscription samples. README. 2016-11-01 08:24:38 -07:00
Robert on Asus 305
bc66e4bc3c Samples: Subscriptions.
Subscribe to all events & subscribe to all events, catching up from the beginning.
2016-11-01 08:20:39 -07:00
Robert on Asus 305
39d5188f14 Samples: Storing events and subscribing. 2016-10-31 09:25:27 -07:00
Nicolas Dextraze
93e61434dd Update README for tests and fix tcp port for default 2016-10-30 09:44:29 -07:00
Nicolas Dextraze
29ee990a0a Merge pull request #6 from Adaptech/master
Getting Started - example
2016-10-30 09:33:08 -07:00
Robert on Asus 305
4f066bf00e Brevity. 2016-10-29 16:41:08 -07:00
Robert on Asus 305
69842b8d44 Getting Started. 2016-10-29 14:34:29 -07:00
Robert on Asus 305
51d5782387 Getting started example 2016-10-29 14:30:39 -07:00
Nicolas Dextraze
830a317f0d Bumping version to 0.0.7, exposing loggers, minor bug fixes, cluster connection (wip) 2016-10-20 04:40:46 -07:00
Nicolas Dextraze
f951a625f4 Performance improvement by using strict equality, fixed heartbeat issue in connection stage 2016-10-17 21:58:28 -07:00
Nicolas Dextraze
dd1302f641 Implemented connection to cluster using gossip seeds 2016-10-15 15:41:25 -07:00
Nicolas Dextraze
4ea996781f Adding tests 2016-10-14 22:53:23 -07:00
Nicolas Dextraze
b2504749ce Adding transaction tests 2016-03-18 14:04:07 -07:00
142 changed files with 32173 additions and 8948 deletions

12
.gitignore vendored
View File

@ -33,4 +33,14 @@ node_modules
.node_repl_history
# InteliJ
.idea
.idea/workspace.xml
.idea/shelf
.vscode
lib/
.eslintrc.json
jsconfig.json
docs/
# Vagrant
.vagrant

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
eventstore-node

17
.idea/codeStyleSettings.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<codeStyleSettings language="JavaScript">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>

11
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,11 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="JavaScript">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

3
.idea/copyright/profiles_settings.xml generated Normal file
View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

6
.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

12
.idea/eventstore-node.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/docs" />
<excludeFolder url="file://$MODULE_DIR$/lib" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/eventstore-node.iml" filepath="$PROJECT_DIR$/.idea/eventstore-node.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v14.21.2

191
README.md
View File

@ -1,65 +1,190 @@
# eventstore-node
# node-eventstore-client
A port of the EventStore .Net ClientAPI to Node.js
## Dependencies
## Learning
- Node.js >= 0.12
- Modules: [long](https://www.npmjs.org/package/long), [protobufjs](https://www.npmjs.org/package/protobufjs), [uuid](https://www.npmjs.org/package/uuid) (installed via `npm install`)
If you want to learn more about EventSourcing/EventModeling, you can join one of the monthly virtual workshops offered by my employer Adaptech Group, see info at [https://adaptechgroup.com/#workshop](https://adaptechgroup.com/#workshop).
## Status
Unstable
### Missing features:
- Ssl connection
- Cluster connection
- Set system settings
### Incomplete
### Areas to improve
- Typed errors: currently most errors are direct instance of Error, which is not practical for error handling
- Performance: there's still some while loop in the code that could be problematic with node.js
- Tests: tests are only covering happy path scenarios for now
- Errors
- Use codes or types to differentiate between errors
- Performance
- Performance hasn't been tested yet
- Tests
- Can always do with more tests
## Getting started
Install the client using npm
Install using `npm install node-eventstore-client`
npm install eventstore-node
Examples
Versions to install to match EventStore versions:
TODO
- 0.1.x for ES prior to 3.9.4
- 0.2.x for ES from 3.9.4 to 5.x
- 20.10.x for ES 20.10 LTS
- 21.10.x for ES 21.10 LTS
- 22.10.x for ES 22.10 LTS
### Dependencies
- Node.js >= 14
- Modules: [long](https://www.npmjs.org/package/long), [protobufjs](https://www.npmjs.org/package/protobufjs), [uuid](https://www.npmjs.org/package/uuid), [strict-event-emitter-types](https://www.npmjs.com/package/strict-event-emitter-types) (installed via `npm install`)
### Install and run an Eventstore on localhost
See https://eventstore.com
*Note: If you are using a version of EventStore prior to 3.9.4, you need to use version 0.1.x of this package `npm install node-eventstore-client@^0.1`.*
## Porting .Net Task to Node.js
### API Documentation
I used Promise to replace .Net Task, so when executing an async command, i.e. appendToStream you'll have to wait for result/error like this:
#### Offline
connection
.appendToStream('myStream', client.expectedVersion.any, events, userCredentials)
.then(function(result) {
//Do something with the WriteResult here
})
.catch(function(err) {
//Handle error here
});
The offline documentation can be found in the module folder `./node_modules/node-eventstore-client/docs`.
#### Online
The online documentation can be found at [https://dev.nicdex.com/node-eventstore-client/docs/](https://dev.nicdex.com/node-eventstore-client/docs/)
### Example: Storing an event
Save to ```app.js:```
```javascript
var esClient = require('node-eventstore-client');
var uuid = require('uuid');
var streamName = "testStream";
/*
Connecting to a single node using "tcp://localhost:1113"
- to connect to a cluster via dns discovery use "discover://my.host:2113"
- to connect to a cluster via gossip seeds use
[
new esClient.GossipSeed({host: '192.168.1.10', port: 2113}),
new esClient.GossipSeed({host: '192.168.1.11', port: 2113}),
new esClient.GossipSeed({host: '192.168.1.12', port: 2113})
]
*/
var connSettings = {}; // Use defaults
var esConnection = esClient.createConnection(connSettings, "tcp://localhost:1113");
esConnection.connect();
esConnection.once('connected', function (tcpEndPoint) {
console.log('Connected to eventstore at ' + tcpEndPoint.host + ":" + tcpEndPoint.port);
});
var eventId = uuid.v4();
var eventData = {
a : Math.random(),
b: uuid.v4()
};
var event = esClient.createJsonEventData(eventId, eventData, null, 'testEvent');
console.log("Appending...");
esConnection.appendToStream(streamName, esClient.expectedVersion.any, event)
.then(function(result) {
console.log("Stored event:", eventId);
console.log("Look for it at: http://localhost:2113/web/index.html#/streams/testStream");
esConnection.close();
})
.catch(function(err) {
console.log(err);
});
```
Run:
```json
npm install uuid
npm install node-eventstore-client
node app.js
```
### Example: Subscribing to events
```cd samples```
To subscribe to all events from now on (includes example of a filter which ignores events which we aren't interested in):
```node subscribe-all-events.js```
To catch up on all events ever and subscribe to all new ones from now on:
```node subscribe-catchup-all-events.js```
To generate a test event, open a separate console and run:
```node store-event.js```
## Running the tests
To run the tests you will need
### Local testing
- To install the dependencies (`npm install`)
- To install nodeunit (`npm install -g nodeunit`)
- Run an instance of EventStore >= 3.3.0 (competing consumers are required for test) on localhost:1113 (Download [here](https://geteventstore.com/downloads/))
To run the tests it is recommended that you use an in-memory instance of the eventstore so you don't pollute your dev instance.
To execute the tests suites simply run test with npm
EventStore.ClusterNode.exe --run-projections=all --memdb certificate-file=yourcert.pfx
or
./run-node.sh --run-projections=all --memdb certificate-file=yourcert.p12
You can also use docker-compose :
```bash
# start the single node cluster
npm run compose:single:start
# if you want to wait for the cluster to be available
npm run compose:wait
# run the tests
npm run test
# to cleanup (stop containres, delete volumes)
npm run compose:single:stop
```
For SSL setup see:
https://eventstore.org/docs/server/setting_up_ssl/
or
https://eventstore.org/docs/server/setting_up_ssl_linux/
To execute the tests suites simply run
npm test
### Isolated environment
To be able to run the tests for different connection types (tcp, gossip, cluster) docker-compose files are available to setup the environment and run the tests.
#### Prerequisites
* docker
* docker-compose
#### Run
To execute the tests suites for single node cluster (tcp connection) simply run
npm run test:single
To execute the tests suites for multiple nodes cluster (gossip connection) simply run
npm run test:gossip
To execute the tests suites for multiple nodes cluster (dns discovery connection) simply run
npm run test:cluster
## Porting .Net Task to Node.js
Any async commands returns a [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) object in replacement of .Net Task.
## License
Ported code is released under the MIT license, see [LICENSE](https://github.com/nicdex/eventstore-node/blob/master/LICENSE).
Ported code is released under the MIT license, see [LICENSE](https://github.com/nicdex/node-eventstore-client/blob/master/LICENSE).
Original code is released under the EventStore license and can be found at https://github.com/eventstore/eventstore.

87
Vagrantfile vendored Normal file
View File

@ -0,0 +1,87 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
config.vm.define "es_cluster_1" do |es_cluster_1|
es_cluster_1.vm.box = "ubuntu/trusty64"
es_cluster_1.vm.box_check_update = false
es_cluster_1.vm.network "private_network", ip: "192.168.33.10"
es_cluster_1.vm.provider "virtualbox" do |vb|
vb.memory = "512"
end
es_cluster_1.vm.provision "shell", inline: <<-SHELL
curl -s https://packagecloud.io/install/repositories/EventStore/EventStore-OSS/script.deb.sh | sudo bash
sudo apt install EventStore-OSS
sudo echo "---" > /etc/eventstore/eventstore.conf
sudo echo "RunProjections: None" >> /etc/eventstore/eventstore.conf
sudo echo "IntIp: 192.168.33.10" >> /etc/eventstore/eventstore.conf
sudo echo "ExtIp: 192.168.33.10" >> /etc/eventstore/eventstore.conf
sudo echo "ClusterSize: 3" >> /etc/eventstore/eventstore.conf
sudo echo "DiscoverViaDns: False" >> /etc/eventstore/eventstore.conf
sudo echo "GossipSeed: ['192.168.33.11:2112','192.168.33.12:2112']" >> /etc/eventstore/eventstore.conf
sudo service eventstore start
SHELL
end
config.vm.define "es_cluster_2" do |es_cluster_2|
es_cluster_2.vm.box = "ubuntu/trusty64"
es_cluster_2.vm.box_check_update = false
es_cluster_2.vm.network "private_network", ip: "192.168.33.11"
es_cluster_2.vm.provider "virtualbox" do |vb|
vb.memory = "512"
end
es_cluster_2.vm.provision "shell", inline: <<-SHELL
curl -s https://packagecloud.io/install/repositories/EventStore/EventStore-OSS/script.deb.sh | sudo bash
sudo apt install EventStore-OSS
sudo echo "---" > /etc/eventstore/eventstore.conf
sudo echo "RunProjections: None" >> /etc/eventstore/eventstore.conf
sudo echo "IntIp: 192.168.33.11" >> /etc/eventstore/eventstore.conf
sudo echo "ExtIp: 192.168.33.11" >> /etc/eventstore/eventstore.conf
sudo echo "ClusterSize: 3" >> /etc/eventstore/eventstore.conf
sudo echo "DiscoverViaDns: False" >> /etc/eventstore/eventstore.conf
sudo echo "GossipSeed: ['192.168.33.10:2112','192.168.33.12:2112']" >> /etc/eventstore/eventstore.conf
sudo service eventstore start
SHELL
end
config.vm.define "es_cluster_3" do |es_cluster_3|
es_cluster_3.vm.box = "ubuntu/trusty64"
es_cluster_3.vm.box_check_update = false
es_cluster_3.vm.network "private_network", ip: "192.168.33.12"
es_cluster_3.vm.provider "virtualbox" do |vb|
vb.memory = "512"
end
es_cluster_3.vm.provision "shell", inline: <<-SHELL
curl -s https://packagecloud.io/install/repositories/EventStore/EventStore-OSS/script.deb.sh | sudo bash
sudo apt install EventStore-OSS
sudo echo "---" > /etc/eventstore/eventstore.conf
sudo echo "RunProjections: None" >> /etc/eventstore/eventstore.conf
sudo echo "IntIp: 192.168.33.12" >> /etc/eventstore/eventstore.conf
sudo echo "ExtIp: 192.168.33.12" >> /etc/eventstore/eventstore.conf
sudo echo "ClusterSize: 3" >> /etc/eventstore/eventstore.conf
sudo echo "DiscoverViaDns: False" >> /etc/eventstore/eventstore.conf
sudo echo "GossipSeed: ['192.168.33.10:2112','192.168.33.11:2112']" >> /etc/eventstore/eventstore.conf
sudo service eventstore start
SHELL
end
end

11
build-and-test.sh Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
if [[ -d /var/code/src ]]; then
cp -R /var/code/{src,test,*.js,*.json} .
fi
npm i
npm run build
npm run test-debug

23
certs/ca/ca.crt Normal file
View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDzTCCArWgAwIBAgIRAJD0c8z8ITssbEkzjx4ZmRowDQYJKoZIhvcNAQELBQAw
YjELMAkGA1UEBhMCVUsxGDAWBgNVBAoTD0V2ZW50IFN0b3JlIEx0ZDE5MDcGA1UE
AxMwRXZlbnRTdG9yZURCIENBIDkwZjQ3M2NjZmMyMTNiMmM2YzQ5MzM4ZjFlMTk5
OTFhMB4XDTIyMTAyMjEzNTA1N1oXDTI3MTAyMjEzNTA1N1owYjELMAkGA1UEBhMC
VUsxGDAWBgNVBAoTD0V2ZW50IFN0b3JlIEx0ZDE5MDcGA1UEAxMwRXZlbnRTdG9y
ZURCIENBIDkwZjQ3M2NjZmMyMTNiMmM2YzQ5MzM4ZjFlMTk5OTFhMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA17JM4jAG4fM4dtA6mpatLVkYhS42wwHC
RbG7dQINSE9wMKpoy2u4sDvZQrsudOvdeclPkIL1JgUtk7lDlqRMXZ6HnP7zTCKn
SQCXzvc3IIU0cGlkffuIS/kuAGBePvmmTwFE3ByDneydlHpSUGEvQY1KEAKwpgSO
LX1NtthP3kKT+bq0NdQlgOjm9dEkSYWwfCM7Of9cJdh8bCO4zJ1taGqchxluWlpo
DJL6TTHsZDvDjhfzBxhjuRax4S8B8xLaq6LirTFEFGpqFBhqzGGhRl42ZylKqVo9
fKhhywwq81ItROmB14OGKa8gyJi7pWGSKRI4M6Ngn89D50Q49K1QdQIDAQABo34w
fDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjApBgNVHQ4EIgQg
/3vmZ5JvcSlcqyaNXgrQZ5FOXlZ9HvYr9x+Tbrs3Mx4wKwYDVR0jBCQwIoAg/3vm
Z5JvcSlcqyaNXgrQZ5FOXlZ9HvYr9x+Tbrs3Mx4wDQYJKoZIhvcNAQELBQADggEB
AIMQR/3BHnVYAuG6DSdDPVYkPR9Fvwi9G9U1nybMHY9uSnEOp6yASrRc/cD7pIbP
Ax7cvD5f0829fAUFupXtp2T/yNOA7/eis7l6Y0ZZf+QEk9XhZXHVTFLFDb9Fyc6r
+i/lXnatGsYwOns0RFSG6tzxi2lxUPwlXauacwTBW4jrphtn0vC3q/n85JOEi2ib
aIlXaXUTREJFiFnS22/SX4dSRaeimL34rrd9AjX53Ne6pUiVBGgd2aSUkBh65EYk
VCIZwuWLx0jgcT8NI/EeId7FN4CWq73dze5eY2QU00rXidyYDeC2aZcU5e2RTQqn
5dGqFGAd/xCopGYdWfusqKM=
-----END CERTIFICATE-----

27
certs/ca/ca.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA17JM4jAG4fM4dtA6mpatLVkYhS42wwHCRbG7dQINSE9wMKpo
y2u4sDvZQrsudOvdeclPkIL1JgUtk7lDlqRMXZ6HnP7zTCKnSQCXzvc3IIU0cGlk
ffuIS/kuAGBePvmmTwFE3ByDneydlHpSUGEvQY1KEAKwpgSOLX1NtthP3kKT+bq0
NdQlgOjm9dEkSYWwfCM7Of9cJdh8bCO4zJ1taGqchxluWlpoDJL6TTHsZDvDjhfz
BxhjuRax4S8B8xLaq6LirTFEFGpqFBhqzGGhRl42ZylKqVo9fKhhywwq81ItROmB
14OGKa8gyJi7pWGSKRI4M6Ngn89D50Q49K1QdQIDAQABAoIBAQCbeiKBSC0t4avp
uN8FX2clMPvDxDQlDLeElUCSbxAz907fRXaUaumN3xYUS+iIzxuuFbYMbNlYtpLL
0NcvVu7U7NjECeJGFxSOQqcqwHg0MNBD1zRgu1bbrJnEG7LTohCHElsYblVRGKXd
yO5ckjEGB1pDgCzk/pOdppzGF9Y6Vl/6WfZQgdGV6B1Qh9Vxs+1CGbK/6YqMM3av
9AJTEXgXbHOPyqNwixjxihpz79hXx0lOznL/CeosHKj7+ZY/knWZcDBmNq3ZA3Nz
w3p0fDkBtY7zQglh5nTSYoLtpcJvzkAI8uD6eJ4ThwIGUbLlvq0c3yaHZwIqrmEq
py09FA8BAoGBAPnuy+ehTBXV5k8icPZjI50Bjr5Alres01OjHxAA5bZJACwVbjMQ
a2464yb6DMA6NDrUevfHvKn3rw8OoceNrlaEua0qE5IweRpROf+ENqW9Yhcg4O1G
bSUPf5M1JusCSznAJu+gKsqHq0pTtdssh8Xb655xgd3gNMi0lLPlw5+tAoGBANzu
vidC8LRH+4JRrb+7PUZi1AR8vtCpabkLuVe7AZJl1wXFvYJTyFcW/e0WTvETcclf
9dhy1c0lH44571L4kpKRRKF4U3lcESbiLPDwjvfnls1rYzfzjY1YWoERwGYxL54H
2BBYPjKocnHZOnyiBxYn4ysK2O3dYlFxgJy/1GzpAoGBAM4HzW1HqRoC91Va1fY2
aaA/LXWmdtg5DwhOWwftRXAMI6kzjWFo8rauspxp2uMH2BB7Ildp/xu6A0SNUk3X
fNTzmsCmA/YqIVrRhNcfHZFpWhgo0WKoAMdhTFCEq21lua+EuFQnG79TMaAW+74O
qmovzb8SYPUPo1OkkUTvbmENAoGAODTg2Zo1JJhfiyL+ghW/nAWOjOmzneBLzlEG
QSSJXNZd4tEQynRINqfZyBRx1vEsdJ/eKsfkTsGQCp4AneoxV+pvPGkhS3G5x1up
NU7uAHHBstcMMfLbY0dEFywN97GxvJGgTWA1yquCGia6s3SBOwjAyXWEBNfaqH+o
ybgf8ekCgYEA3/Vx/CZ/VB0Fr47cpZGckJWH6lDWkzBEcaAT22VYlhL4CZ0CRDWj
6+LSiQDuI3+JwEhZlIkTvfZSo5bhNeUHoEL+15yg2RvsWFzorDxBfvZ//29BQg/Q
bN+vlKHpn7V7T9LQg/Tp107mfW6Z/TG4UYIVJyxY/JUjXjQLcFPvTVE=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSzCCAjMCFAxEesn/oIrU2T7DOU9cWCyoCCqfMA0GCSqGSIb3DQEBCwUAMGIx
CzAJBgNVBAYTAkNBMQswCQYDVQQIDAJCQzESMBAGA1UEBwwJVmFuY291dmVyMRcw
FQYDVQQKDA5BZGFwdGVjaCBHcm91cDEZMBcGA1UEAwwQZXZlbnRzdG9yZS5sb2Nh
bDAeFw0yMTA1MDYyMDAyMzZaFw0zMTA1MDQyMDAyMzZaMGIxCzAJBgNVBAYTAkNB
MQswCQYDVQQIDAJCQzESMBAGA1UEBwwJVmFuY291dmVyMRcwFQYDVQQKDA5BZGFw
dGVjaCBHcm91cDEZMBcGA1UEAwwQZXZlbnRzdG9yZS5sb2NhbDCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANbNrVQ91TFVsrfFV39/1MjxM/UIP78FaX+S
kxAzcpAJKP9PbHLwFXxkl0Bam+3ekE6+hc3qT8o4ksOdzwz+dJbdZF8gblLda7vq
JnPjkAZuhojRLPfXvaJZbManEkB30+JIvb/XWhMf34+kvyZaOnKqidzNAuLAFQ+6
KCCXYnTUFimumr6AsrZvA9UzN3edrk1pqxMYNT+8MBOUnNfkdHX78aTzdWvq4+Ro
+58MT3NpqdI+1cY423JlG2qj8PAqPsgdE4XvmtqDtLndH+VflR1M1Rkn41cofgSh
lo7PE2SNn4hSv8m+1HJMfcegJEXKE3LIGHJtV6qT6S+pRtJd9mcCAwEAATANBgkq
hkiG9w0BAQsFAAOCAQEAlhKziHmYm81vrx+EHqIZXvmoUp3I1A9STvFqUdHrv9sa
8tKD7wW0owTVaxXBW3380ZFRpG3JVJFUgOyFIhi4IccWoqVsBCE0BBR96tlRXrKT
UZTAHLEk/SdLL+qfhKgY7F7+M9Wtf5HldiRfNoTqYn6YVXMJMgcwehhHho/fCHzj
tzOMfKFjGAq+n8cScLrTaW92JbbxjFPJJ7uQDH4OjAHx6pT885+RJf0rNqboAErA
lRLcEjtihslWKD7nelOlJE1egkum/m7MLVFpZPqEBp+x1zXdwacENwIhiRrQQhKi
8Qw36BuSCZ3CZK18kGyhtxl/uUP/wx7XXc1U0W2Wtw==
-----END CERTIFICATE-----

BIN
certs/eventstore.p12 Normal file

Binary file not shown.

24
certs/node1/node1.crt Normal file
View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIECDCCAvCgAwIBAgIQI83qjs8Vn2iLbeUZbTPRcTANBgkqhkiG9w0BAQsFADBi
MQswCQYDVQQGEwJVSzEYMBYGA1UEChMPRXZlbnQgU3RvcmUgTHRkMTkwNwYDVQQD
EzBFdmVudFN0b3JlREIgQ0EgOTBmNDczY2NmYzIxM2IyYzZjNDkzMzhmMWUxOTk5
MWEwHhcNMjMwMTMwMTUyMzM0WhcNMjQwMTMwMTUyMzM0WjAcMRowGAYDVQQDExFl
dmVudHN0b3JlZGItbm9kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AL3CBeNLxZDvUA4WMRACrM+kxvrlD8ahs9MOFfAJHCNLoNR4mWAvh+BUDHsG7E56
4I5Y7XbC0cCIIER3/QB6Jz80kcJ4y745d9RCbYSqTeUnptrLdcXDNiwbI3bAjyuJ
tEqg+iPjrz2E6bSlZ4GSxKSTC95Dm80UUU/CG6+H1FesN8JP+ruYey4S5bYASy6o
z1NtYaPDGqSqDGvLhuWQNKoJAAc4g1R/UcVFbuI2asnCkFGjDF7gcRKkHpw0DNhN
TMQWMdQ2+I8it5UwNzYpsCZj/le5a54mNZqNYj52nEpURDsdJy4BXJy5dUwADZHz
ffN+ciWEToQz2hZmnliIG98CAwEAAaOB/zCB/DAOBgNVHQ8BAf8EBAMCBaAwHQYD
VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0O
BCIEIFRmt7SsPXFStSAcZDS9OCP03JyevPMV6/1R0Utg8UA3MCsGA1UdIwQkMCKA
IP975meSb3EpXKsmjV4K0GeRTl5WfR72K/cfk267NzMeMGUGA1UdEQReMFyCKmNs
dXN0ZXIudGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIIobm9kZTEu
dGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIcEwKghCjANBgkqhkiG
9w0BAQsFAAOCAQEATHxhyf27pPFJRegPB9ozm4P06MkvZMm4syxmoR1jJohm+7Xp
7jd9ENATFApZNxV+/MYGgryML9Y+Md/Bd1fttP8j0MUR5euJX8zehrnCufaIw2If
/a9YalJ2/a+WElAgTHM1sCpdvL+WwsQ6IjWPVfGIu7VIotQXIqllZUjMd3rFI4Ip
9CkMgXbZzls8OhWH1FnPkFFjolqITwcgSmn3NWbQkl6qFYFjAZ8pFcjhdNay7yZx
+Pe9gEPbi6iyP50xb0LQtJXQQvFhwmDQbR5v0xiBI6ONK8mha3fQkxmV1wMPJdvQ
TOva/iFP4qmjyewyzOd752DN6r4qFlpb39Hpmw==
-----END CERTIFICATE-----

27
certs/node1/node1.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvcIF40vFkO9QDhYxEAKsz6TG+uUPxqGz0w4V8AkcI0ug1HiZ
YC+H4FQMewbsTnrgjljtdsLRwIggRHf9AHonPzSRwnjLvjl31EJthKpN5Sem2st1
xcM2LBsjdsCPK4m0SqD6I+OvPYTptKVngZLEpJML3kObzRRRT8Ibr4fUV6w3wk/6
u5h7LhLltgBLLqjPU21ho8MapKoMa8uG5ZA0qgkABziDVH9RxUVu4jZqycKQUaMM
XuBxEqQenDQM2E1MxBYx1Db4jyK3lTA3NimwJmP+V7lrniY1mo1iPnacSlREOx0n
LgFcnLl1TAANkfN9835yJYROhDPaFmaeWIgb3wIDAQABAoIBAFO5a8eiAwWz0Wql
t/gMdhsX5QOGVbOaXTFeIafLHTMlh0y3+VRLJHSCIQ+XOmRWKwYjjoK76rg8V6Be
IK85Nl/Y3gckLSE9UfXBaXkDe2MEX8ZhxxD+1MQZJUicJY9cLsROyo9TQlqubj9a
S5UMIN9MUgw7YF7X/Bho2LZWBAa6Hz3LSyndwArvqZeUf8ovmJnWEUvJB8amVhwB
DoYqyNjiqIEJYWRfCeGfLEOmotmwCfYeJFyr84r3wfS6qmaesKM8Nbz8eSSNg7n3
5LcAL2Bv3hvsMmUEZQl3J41AtBVsU5DgbTf6/seuKvfVOcqVp9AtZp2rg3xY4pNI
7gN4wiECgYEA1lBh13LdjM0rOg9096ee2hQKjFvwVki0xzZigmf94CXoAJ19qwOe
J0WXfk1g5GP1bg5pdVhKdS+LEIqet9ipaall5hWotaYJdyOtWiQan9wRRa3sk6pl
K29LBF+XzZWAKE7C2371rVyMXViHzv056wasw0RSgxd6HBi1mOpcveMCgYEA4qrj
Ha24Qh+/b883FaMphYrl8H8nMgAdGmi7HiLnJx6jzPzalsBI5+qnDY8/oqeAie66
k8c6uRKOsEFrsbK/w3+21rGuZb9t9PupDONFOW9D94744HDFu0Clnr5S2ABKM9D6
hUL2qQedAqZx5CNWbpM/wdV0OBKhveIb9OyjytUCgYA21jCC0ThRK7owy/2w1Kn3
wQMF8Qxo94eSJZ9FINi7/tcDOUkLdgyEnfDqd2AgcFc/Rt7DEMlUfMrHe8Cq5/xJ
NfgFzGrzt71aR722f+B0IPomax9KlWH3oC2Au/j6uJMnPSfsw+qW5kRgKiPt2at1
MKsJ97RpKJutDsGAlA8Q4wKBgQCUyjBD7yEg5s43iwJRxcUf0EmYUR9dhw2TpGQQ
KEEsQMStbt3eT2KMOlBmaj4Pe3y5DmiTURiPHEtmD8ut3fRbVJ6k2AXu7HY1dm1F
9HogdZVcEBH24pAplwog5jZja+1Jjkik6YwY8NJ0RuZEJ544/5e+mWus0y4xAxfu
8teh8QKBgQDQm6vKAsldQStgj9gwRiTAUrMPTriVQTUd4/8lof+RoIJWM7CARLos
9MYkAefv4MgjvwWNn8RbFjN91NBX4qpCjgm62P25npFSKQAK+JQlvj7MbSw54jyx
2/uj4JEMM1rOocJr9tvYAeC2nnZMOuhesLVy+SHF9TJ2WtalDOTZ1A==
-----END RSA PRIVATE KEY-----

24
certs/node2/node2.crt Normal file
View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIECDCCAvCgAwIBAgIQdipz5iGZgwtJYjBK4mjFijANBgkqhkiG9w0BAQsFADBi
MQswCQYDVQQGEwJVSzEYMBYGA1UEChMPRXZlbnQgU3RvcmUgTHRkMTkwNwYDVQQD
EzBFdmVudFN0b3JlREIgQ0EgOTBmNDczY2NmYzIxM2IyYzZjNDkzMzhmMWUxOTk5
MWEwHhcNMjMwMTMwMTUyMzQwWhcNMjQwMTMwMTUyMzQwWjAcMRowGAYDVQQDExFl
dmVudHN0b3JlZGItbm9kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AOJZaF5skFJeNOgkqG8B38lV6lhKwvrmspxZvw7tP8dhzV7R+EUdTXYS86ximW4c
cdK5zqXRgZG6WaaFFN4BeCLqum+602l9Bw88K5Ww68v5R9VP8sgt7w4UsqvVjTJ4
60R59PvfrwkIL1DFkKBM4B7CmdLR20VZiJQ9YghR03TBbjjvIhaDyprmxu0vDsF7
f3zNmcnBF+14Z0b/exevtiVbyvNQ6nQENSo7Whz/kDV1a2i/BsVlsbFc43ywmsHL
Ks1z2rSctTgfYmK/yUFaR4B1mkzhQf9/CfJxJQLekC9Zv85ah9tMGwl/pbDfZh7q
NUJQHjJMm4I/HeeTQPd1CXcCAwEAAaOB/zCB/DAOBgNVHQ8BAf8EBAMCBaAwHQYD
VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0O
BCIEIDlz3IFnAja157ly4cFxgZTfYrwlqAhgtbEu4TVhO/yPMCsGA1UdIwQkMCKA
IP975meSb3EpXKsmjV4K0GeRTl5WfR72K/cfk267NzMeMGUGA1UdEQReMFyCKmNs
dXN0ZXIudGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIIobm9kZTIu
dGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIcEwKghCzANBgkqhkiG
9w0BAQsFAAOCAQEAvj0ZjZkKdqwVdfXJAhMPGZwMf8gfZRpSCJe95FL0yePUk/jm
di2qk2sb2+W9prPR1Nbs1hfLnHHBw32yrxekvFSUC6Qc23X8S9QU01FOO7uEVtnM
fdEzyuZcdPBK2SAL/CxN0WH/yUxNWkWnqQKegZxkvpMVxMAsA0l3twWwfkEQtjhA
V/dcS5WwZxyyvCQ0qAVZ4+Zd/huvmsNIaXplvCs/P8yU6SR0TUdahJpfxJeKkKIf
k2h2/G36i26lTTm2TyxO4A2v8PotRO0xhjJkVKVEs4ZuY+lK6JUoTaM9Z8FubDqE
K2KH+YCBdER5jQK2YbmZDRh58aKMpCXtCx8RqQ==
-----END CERTIFICATE-----

27
certs/node2/node2.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA4lloXmyQUl406CSobwHfyVXqWErC+uaynFm/Du0/x2HNXtH4
RR1NdhLzrGKZbhxx0rnOpdGBkbpZpoUU3gF4Iuq6b7rTaX0HDzwrlbDry/lH1U/y
yC3vDhSyq9WNMnjrRHn0+9+vCQgvUMWQoEzgHsKZ0tHbRVmIlD1iCFHTdMFuOO8i
FoPKmubG7S8OwXt/fM2ZycEX7XhnRv97F6+2JVvK81DqdAQ1KjtaHP+QNXVraL8G
xWWxsVzjfLCawcsqzXPatJy1OB9iYr/JQVpHgHWaTOFB/38J8nElAt6QL1m/zlqH
20wbCX+lsN9mHuo1QlAeMkybgj8d55NA93UJdwIDAQABAoIBAQDR3UvjoIz/kdFE
HBj/M6GmLz6ktkyqDRQ12xBGarrjxk6AR/YFPvH/M/syLglMCpuOYjCzldiooTtW
JbX/USVuWmyaA4LZH89X1NYkaHF4IZz0rrfP2qb3S0GOCOZtkaexSdJXZkIIAcq8
/TDnL9/tJUkyKtk/J5B2waMPS67fZGoZ6wx3sHZ7/CCcvdtHSvCT5+taVoHGUDPi
kToGcJAIDKAOakZfv1bfzYFyln18NA55V+133tGfZYJrILmEwLcU2Y7BAaoD+yme
+BWU3eKgYRCsvA+HzdgDetEaqp78Ax/G29OUK9NWdXk2hwhmkpZgSLV3g7OlfNve
6pmuEpcBAoGBAOPIMM+uYQJmRIapXZADQOc7VkF55sSjh3h25tjS8iHIEGQ9gb8n
h4iNUDC73NN9Yfw6/vKrwcpIq5nQKRRUQ+NJFxurtN6XBBlYNf336tg6S/65oe3W
R3m35/w72qcct1aOASDafuxdoEV/kbflpao57PxRZAXGdMPJANDckujXAoGBAP5j
x4NZvYI73oL2OI/2CJytA5uksfsh0ymusNKRaBhqLwFIE1Ud/CHj5eG1Bo9ox8Yl
e5AdkMmSSYFIe3B/P1apFI22/GKc1on2mFXKRY8zAMVLGbsLJarQz4xIX36bafUh
1O9JM1d6PWDITAzoptIJDF7U41jxbbGMic6gIbBhAoGAMOUkNTCFenJhe1aX0vYf
1IMUPPHh64v+5IkBfMGrYD0F576cP2BI47MR56Qr/uoHQpGXcJgV/UokK9xXNRnx
iLSD1VjCdpWMN7SLwnyZWNkKvx8x90/r/+DOV4KxqPWtFVNeQqMkY7ClWsXLjeIp
8aSFjpQaGHNFVVGsbykqD/kCgYEA3BZ/eoidiJs7e5qCfSqjcMnPIU1wHpG5G4rj
uInMwbK8UCOHdlCm3rtJG+l+b3x7ulabfKfXqE1EAvIuubmdluBZw/HYkYiXpG0Q
hsilPasms4qyDD0srYhSyE+lLuhO16IZk5yFKt27VIx2H4T5LyZc6X7N4xmdicrJ
P1cTToECgYEAlW2xWDl90suSAw1Tv23BVEX6ol/UColtGY8VDIHsXPDBYImtGiFt
MibOuyzWsvPw0HZDzWw9otU0735clhK34v+E1WyPFvOAo18orcCS4A6Fi42tp5oG
sNVxiYJFFUn+nnHUoGZNuKlB5PZR1Wxus6wXY2rZdoQ6QsBD0XjquCE=
-----END RSA PRIVATE KEY-----

24
certs/node3/node3.crt Normal file
View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIECDCCAvCgAwIBAgIQecOoexYVeIUtLwNQp179vzANBgkqhkiG9w0BAQsFADBi
MQswCQYDVQQGEwJVSzEYMBYGA1UEChMPRXZlbnQgU3RvcmUgTHRkMTkwNwYDVQQD
EzBFdmVudFN0b3JlREIgQ0EgOTBmNDczY2NmYzIxM2IyYzZjNDkzMzhmMWUxOTk5
MWEwHhcNMjMwMTMwMTUyMzQ2WhcNMjQwMTMwMTUyMzQ2WjAcMRowGAYDVQQDExFl
dmVudHN0b3JlZGItbm9kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN8iXoWZjjxOqJJLTHmHOy89lbk2pBo/ca7qPZ/WC+I52A4Lgw58/O7tV/RHFyao
X8lS2hAVMlRfToUqj7ILAVBeF3t827W0qttbv+wdnxvIWqpq1HdtV5xgLIUXbyiQ
SI97Tgn7zDJOvm41sHV0xsPWKlritliDByXlGMOabzV53yxa1c0cU2bP5n7nc9wW
KOH03OU64qCj6MJTdi35peKDhaAcRtGmr5ZggUCP4n9GUzJ1FATAkcrLMOjoh1v1
K6auqOQZ8f+ns+3bQ4G96rZxwwrSzI/leUFY4u/lzjLUlEuLALJquPrB6RiwzTwb
BOaTYG6mgWORWPBupjpj67cCAwEAAaOB/zCB/DAOBgNVHQ8BAf8EBAMCBaAwHQYD
VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0O
BCIEIMdKDLFiWvROUmnmDVing8S3lLisBr+vfrrM+A0t3yycMCsGA1UdIwQkMCKA
IP975meSb3EpXKsmjV4K0GeRTl5WfR72K/cfk267NzMeMGUGA1UdEQReMFyCKmNs
dXN0ZXIudGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIIobm9kZTMu
dGVzdHMubm9kZS1ldmVudHN0b3JlLWNsaWVudC5sb2NhbIcEwKghDDANBgkqhkiG
9w0BAQsFAAOCAQEAEJmQloXSfxqWGIdGFi7UFDNr3oTxuQsEJRztKzA1rRPbvFhp
jJ1uPZKRv0SBnmaQa6+PW1zphP+kklmMq9XqiFqq7D7FmYgr2err08DbQfMxyEhG
yHmaBFzEyI/rm8jRn3K5DMV6cDCB3jTn5eud3oVVAZwTsLcR3mB2UpSJ7lAJgRFs
5pfFFgHKCQ4YQcFIkNtMeda7KpVv4YxoltJMSISDGvo4fcNhCH62H6/PDJFBi/e9
L4vB7eZHaO//8UQQxV24VSDLDBP1MQ4ucLZEVam9HQj4qK+DiwDlH6WmQs22FqqE
i7aLjEEu9Ko2zG7hzTp1jxDfyPw5iByax1JdXw==
-----END CERTIFICATE-----

27
certs/node3/node3.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3yJehZmOPE6okktMeYc7Lz2VuTakGj9xruo9n9YL4jnYDguD
Dnz87u1X9EcXJqhfyVLaEBUyVF9OhSqPsgsBUF4Xe3zbtbSq21u/7B2fG8haqmrU
d21XnGAshRdvKJBIj3tOCfvMMk6+bjWwdXTGw9YqWuK2WIMHJeUYw5pvNXnfLFrV
zRxTZs/mfudz3BYo4fTc5TrioKPowlN2Lfml4oOFoBxG0aavlmCBQI/if0ZTMnUU
BMCRyssw6OiHW/Urpq6o5Bnx/6ez7dtDgb3qtnHDCtLMj+V5QVji7+XOMtSUS4sA
smq4+sHpGLDNPBsE5pNgbqaBY5FY8G6mOmPrtwIDAQABAoIBADk571QEVd5RrhoX
Bb4FWMB84vFleiDhNJ7PpI5hrxqOC0NO4IXOuG/unygfM2jOtgJEJXkKdqX8635r
s/7No8Cg4RTiqNapR0iK92XvEudlMxuR2nvmpxmL8N8Ra4jTuEhUL2eQFIaye1R4
oJD/Khr/yk0U5/87W5H0d61y5iKobF87QbI9xxkBGGQygsvoEni4sHchN1se/Ha8
y9nb6B/9sYU8lfSSaRhS/sqpH/ppnptWr1hwxj6jxI21uMB+cWN+rs8j1F0Zizex
+TTDqhyuYoqh1x2J9UFCjipiKVnA1563eFmtQZk2nrNdjvN6K9qWa0iF8IgTGvdh
m+9NNEECgYEA6sYqugSzeVCqBqHKAQKESsO6XOQjwkDZQhgeDG8Sal5RArlNsup6
hh/ZONPCL/6zxz5oKEyxzsTg4tJI9F3uqkpF5rcM1UGCc3maywXmLUd70NuTWM2G
hz+R4IhKyuQsFhWzFRBh10rorSvjIflkKiviZp+lNiDp1UnY0pOhxWUCgYEA807M
i60fuTTZsvGKhExORHUOnfs3USst1zHODDAaQYyjUYJP7ix/azf1ciHeeNCZ9rzF
9xBlDKChRSvKPnU0K07C49a+WI4rdgw8zS+8dqKdvaczPas0InPzXs+oW/O6I86I
ANEanw9fgrAPEL4/8TLMLhjTV3msdrJ04KzVWOsCgYEAtc9Ax01cr1YIVZp1FNMe
H8fzSnxiNMyDcuu6WWaqnDTuB234/dUXjUacsaVLfF+CWYm6n3rFN5q/jOfMF3Gm
obli6IhZJ8zTeRlzBkZLOkVhXRytvtdqltJj5b2mLfkFKxUi2e0V1+PqzDBwdJ7t
MrtJWK/JAWH7s3C8uFYbsQECgYEAsxD2d4H1zA/6fLlKEa47GCTymhCumJ1nwm4U
UGICoOWJuU5n10mGiXmIufP1UQNtnOs342p8hDugFVFyKl+GAq2Xv6lnM7OGhv3g
rb6JPOLXpyHsE2HyH70oi0nXhb0t7HZvH/LFXfUpfPxoShFK7BBPt0lsGYAiDsWu
QUDST/sCgYAVljJlvlEx8C8HmBrSeeh+Zec9t9HDjl04jNAwenY4x5hD1l1u2ij5
OJqVodZd1s/To7Dm9Xc6w7r6c9x8xsok4Kc3izc/KQXlxPvRnxF76J4KsMw8kEU1
LnQN+igffijohdln2Q8cQnEZylM1N4e+sNat5ufEzTk7MBn1676aHA==
-----END RSA PRIVATE KEY-----

134
docker-compose-cluster.yaml Normal file
View File

@ -0,0 +1,134 @@
version: '3.7'
services:
eventstore1:
image: eventstore/eventstore:22.10.0-buster-slim
environment:
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_CLUSTER_DNS=cluster.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=node1.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1113
# - EVENTSTORE_DISCOVER_VIA_DNS=False
# - EVENTSTORE_GOSSIP_SEED=192.168.33.10:2113,192.168.33.11:2113,192.168.33.12:2113
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_ENABLE_EXTERNAL_TCP=true
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INT_IP=192.168.33.10
- EVENTSTORE_EXT_IP=192.168.33.10
- EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node1/node1.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node1/node1.key
- EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca
expose:
- "1112"
- "1113"
- "2113"
ports:
- "1112:1112"
- "1113:1113"
- "2113:2113"
networks:
app_net:
aliases:
- cluster.tests.node-eventstore-client.local
- node1.tests.node-eventstore-client.local
ipv4_address: 192.168.33.10
volumes:
- eventstore1-data:/var/lib/eventstore
- ./certs:/etc/eventstore/certs:ro
eventstore2:
image: eventstore/eventstore:22.10.0-buster-slim
environment:
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_CLUSTER_DNS=cluster.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=node2.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1113
# - EVENTSTORE_DISCOVER_VIA_DNS=False
# - EVENTSTORE_GOSSIP_SEED=192.168.33.10:2113,192.168.33.11:2113,192.168.33.12:2113
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_ENABLE_EXTERNAL_TCP=true
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INT_IP=192.168.33.11
- EVENTSTORE_EXT_IP=192.168.33.11
- EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node2/node2.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node2/node2.key
- EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca
expose:
- "1113"
- "1112"
- "2113"
networks:
app_net:
aliases:
- cluster.tests.node-eventstore-client.local
- node2.tests.node-eventstore-client.local
ipv4_address: 192.168.33.11
volumes:
- eventstore2-data:/var/lib/eventstore
- ./certs:/etc/eventstore/certs:ro
eventstore3:
image: eventstore/eventstore:22.10.0-buster-slim
environment:
- EVENTSTORE_CLUSTER_SIZE=3
- EVENTSTORE_CLUSTER_DNS=cluster.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HOST_TO_CLIENT_AS=node3.tests.node-eventstore-client.local
- EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS=2113
- EVENTSTORE_ADVERTISE_TCP_PORT_TO_CLIENT_AS=1113
# - EVENTSTORE_DISCOVER_VIA_DNS=False
# - EVENTSTORE_GOSSIP_SEED=192.168.33.10:2113,192.168.33.11:2113,192.168.33.12:2113
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_ENABLE_EXTERNAL_TCP=true
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INT_IP=192.168.33.12
- EVENTSTORE_EXT_IP=192.168.33.12
- EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node3/node3.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node3/node3.key
- EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca
expose:
- "1113"
- "1112"
- "2113"
networks:
app_net:
aliases:
- cluster.tests.node-eventstore-client.local
- node3.tests.node-eventstore-client.local
ipv4_address: 192.168.33.12
volumes:
- eventstore3-data:/var/lib/eventstore-data
- ./certs:/etc/eventstore/certs:ro
nodejs:
image: node:14
working_dir: /var/build
volumes:
- ./:/var/code:ro
- ./build-and-test.sh:/var/build/build-and-test.sh:ro
- ./certs:/var/certs:ro
environment:
- NODE_EXTRA_CA_CERTS=/var/certs/ca/ca.crt
command: bash -c "tail -f /dev/null"
networks:
app_net:
ipv4_address: 192.168.33.5
volumes:
eventstore1-data:
eventstore2-data:
eventstore3-data:
networks:
app_net:
ipam:
driver: default
config:
- subnet: "192.168.33.0/24"

View File

@ -0,0 +1,54 @@
version: '3.4'
services:
eventstore:
image: eventstore/eventstore:22.10.0-buster-slim
environment:
- EVENTSTORE_CLUSTER_SIZE=1
- EVENTSTORE_RUN_PROJECTIONS=All
- EVENTSTORE_START_STANDARD_PROJECTIONS=True
- EVENTSTORE_ENABLE_EXTERNAL_TCP=true
- EVENTSTORE_EXT_TCP_PORT=1113
- EVENTSTORE_HTTP_PORT=2113
- EVENTSTORE_INT_IP=192.168.33.10
- EVENTSTORE_EXT_IP=192.168.33.10
- EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node1/node1.crt
- EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node1/node1.key
- EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca
ports:
- "1113:1113"
- "2113:2113"
tmpfs:
- /var/log/eventstore
volumes:
- eventstore-data:/var/lib/eventstore
- ./certs:/etc/eventstore/certs:ro
networks:
app_net:
aliases:
- node1.tests.node-eventstore-client.local
ipv4_address: 192.168.33.10
nodejs:
image: node:14
working_dir: /var/build
volumes:
- ./:/var/code:ro
- ./build-and-test.sh:/var/build/build-and-test.sh:ro
- ./certs:/var/certs:ro
environment:
- NODE_EXTRA_CA_CERTS=/var/certs/ca/ca.crt
command: bash -c "tail -f /dev/null"
networks:
app_net:
ipv4_address: 192.168.33.5
volumes:
eventstore-data:
networks:
app_net:
ipam:
driver: default
config:
- subnet: "192.168.33.0/24"

391
index.d.ts vendored Normal file
View File

@ -0,0 +1,391 @@
/// <reference types="node" />
/// <reference types="Long" />
import { EventEmitter } from 'events';
import { StrictEventEmitter } from 'strict-event-emitter-types';
// Expose classes
export class Position {
constructor(commitPosition: number|Long, preparePosition: number|Long);
readonly commitPosition: Long;
readonly preparePosition: Long;
static readonly start: number;
static readonly end: number;
}
export class UserCredentials {
constructor(username: string, password: string);
readonly username: string;
readonly password: string;
}
export class PersistentSubscriptionSettings {
constructor(resolveLinkTos: boolean, startFrom: Long|number, extraStatistics: boolean, messageTimeout: number,
maxRetryCount: number, liveBufferSize: number, readBatchSize: number, historyBufferSize: number,
checkPointAfter: number, minCheckPointCount: number, maxCheckPointCount: number,
maxSubscriberCount: number, namedConsumerStrategy: string)
static create(): PersistentSubscriptionSettings;
}
export namespace SystemConsumerStrategies {
const DispatchToSingle: string;
const RoundRobin: string;
const Pinned: string;
}
export class GossipSeed {
constructor(endPoint: TcpEndPoint, hostHeader: string);
readonly endPoint: TcpEndPoint;
readonly hostHeader: string;
}
export interface ProjectionDetails {
readonly coreProcessingTime: number,
readonly version: number,
readonly epoch: number,
readonly effectiveName: string,
readonly writesInProgress: number,
readonly readsInProgress: number,
readonly partitionsCached: number,
readonly status: string,
readonly stateReason: string,
readonly name: string,
readonly mode: string,
readonly position: string,
readonly progress: number,
readonly lastCheckpoint: string,
readonly eventsProcessedAfterRestart: number,
readonly statusUrl: string,
readonly stateUrl: string,
readonly resultUrl: string,
readonly queryUrl: string,
readonly enableCommandUrl: string,
readonly disableCommandUrl: string,
readonly checkpointStatus: string,
readonly bufferedEvents: number,
readonly writePendingEventsBeforeCheckpoint: number,
readonly writePendingEventsAfterCheckpoint: number
}
export class ProjectionsManager {
constructor(log: Logger, httpEndPoint: string, operationTimeout: number);
enable(name: string, userCredentials: UserCredentials): Promise<void>;
disable(name: string, userCredentials: UserCredentials): Promise<void>;
abort(name: string, userCredentials: UserCredentials): Promise<void>;
reset(name: string, userCredentials: UserCredentials): Promise<void>;
createOneTime(query: string, userCredentials: UserCredentials): Promise<void>;
createTransient(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
createContinuous(name: string, query: string, trackEmittedStreams: boolean, userCredentials: UserCredentials): Promise<void>;
listAll(userCredentials: UserCredentials): Promise<ProjectionDetails[]>;
listOneTime(userCredentials: UserCredentials): Promise<ProjectionDetails[]>;
listContinuous(userCredentials: UserCredentials): Promise<ProjectionDetails[]>;
getStatus(name: string, userCredentials: UserCredentials): Promise<string>;
getState(name: string, userCredentials: UserCredentials): Promise<string>;
getPartitionState(name: string, partitionId: string, userCredentials: UserCredentials): Promise<string>;
getResult(name: string, userCredentials: UserCredentials): Promise<string>;
getPartitionResult(name: string, partitionId: string, userCredentials: UserCredentials): Promise<string>;
getStatistics(name: string, userCredentials: UserCredentials): Promise<string>;
getQuery(name: string, userCredentials: UserCredentials): Promise<string>;
getState(name: string, userCredentials: UserCredentials): Promise<string>;
updateQuery(name: string, query: string, userCredentials: UserCredentials): Promise<void>;
delete(name: string, deleteEmittedStreams: boolean, deleteStateStream: boolean, deleteCheckpointStream: boolean, userCredentials: UserCredentials): Promise<void>;
}
// Expose errors
export class WrongExpectedVersionError {
readonly name: string;
readonly action: string;
readonly message: string;
readonly stream?: string;
readonly expectedVersion?: Long;
readonly transactionId?: Long;
}
export class StreamDeletedError {
readonly message: string;
readonly stream?: string;
readonly transactionId?: Long;
}
export class AccessDeniedError {
readonly name: string;
readonly action: string;
readonly message: string;
readonly stream?: string;
readonly transactionId?: Long;
}
export class ProjectionCommandFailedError {
readonly httpStatusCode: number;
readonly message: string;
}
// Expose enums/constants
export namespace expectedVersion {
const any: number;
const noStream: number;
const emptyStream: number;
const streamExists: number;
}
export namespace positions {
const start: Position;
const end: Position;
}
export namespace streamPosition {
const start: number;
const end: number;
}
//TODO
// systemMetadata
// eventReadStatus
// sliceReadStatus
// Expose loggers
export interface Logger {
debug(fmt: string, ...args: any[]): void;
info(fmt: string, ...args: any[]): void;
error(fmt: string, ...args: any[]): void;
}
export class NoopLogger implements Logger {
constructor()
debug(fmt: string, ...args: any[]): void;
info(fmt: string, ...args: any[]): void;
error(fmt: string, ...args: any[]): void;
}
export class FileLogger implements Logger {
constructor(filePath: string, append: boolean);
debug(fmt: string, ...args: any[]): void;
info(fmt: string, ...args: any[]): void;
error(fmt: string, ...args: any[]): void;
}
// Expose results
export interface WriteResult {
readonly nextExpectedVersion: Long;
readonly logPosition: Position;
}
export interface RecordedEvent {
readonly eventStreamId: string;
readonly eventId: string;
readonly eventNumber: Long;
readonly eventType: string;
readonly created: Date;
readonly createdEpoch: number;
readonly data?: Buffer;
readonly metadata?: Buffer;
readonly isJson: boolean;
}
export interface ResolvedEvent {
readonly event?: RecordedEvent;
readonly link?: RecordedEvent;
readonly originalEvent?: RecordedEvent;
readonly isResolved: boolean;
readonly originalPosition?: Position;
readonly originalStreamId: string;
readonly originalEventNumber: Long;
}
export interface StreamEventsSlice {
readonly status: string; // TODO: enum
readonly stream: string;
readonly fromEventNumber: Long;
readonly readDirection: string; // TODO: enum
readonly events: ResolvedEvent[];
readonly nextEventNumber: Long;
readonly lastEventNumber: Long;
readonly isEndOfStream: boolean;
}
export interface AllEventsSlice {
readonly readDirection: string; // TODO enum
readonly fromPosition: Position;
readonly nextPosition: Position;
readonly events: ResolvedEvent[];
readonly isEndOfStream: boolean;
}
export interface DeleteResult {
readonly logPosition: Position;
}
export interface EventStoreTransaction {
readonly transactionId: number;
commit(): Promise<WriteResult>;
write(eventOrEvents: EventData | EventData[]): Promise<void>;
rollback(): void;
}
export interface EventReadResult {
readonly status: string;
readonly stream: string;
readonly eventNumber: Long;
readonly event: ResolvedEvent | null;
}
export interface EventStoreSubscription {
readonly isSubscribedToAll: boolean;
readonly streamId: string;
readonly lastCommitPosition: Position;
readonly lastEventNumber: Long;
close(): void;
unsubscribe(): void;
}
export interface EventStoreCatchUpSubscription {
stop(): void;
}
export enum PersistentSubscriptionNakEventAction {
Unknown = 0,
Park = 1,
Retry = 2,
Skip = 3,
Stop = 4
}
export interface EventStorePersistentSubscription {
acknowledge(events: ResolvedEvent | ResolvedEvent[]): void;
fail(events: ResolvedEvent | ResolvedEvent[], action: PersistentSubscriptionNakEventAction, reason: string): void;
stop(): void;
}
export interface RawStreamMetadataResult {
readonly stream: string;
readonly isStreamDeleted: boolean;
readonly metastreamVersion: Long;
readonly streamMetadata: any;
}
export interface PersistentSubscriptionCreateResult {
readonly status: string;
}
export interface PersistentSubscriptionUpdateResult {
readonly status: string;
}
export interface PersistentSubscriptionDeleteResult {
readonly status: string;
}
// Callbacks
export interface EventAppearedCallback<TSubscription> {
(subscription: TSubscription, event: ResolvedEvent): void | Promise<void>;
}
export interface LiveProcessingStartedCallback {
(subscription: EventStoreCatchUpSubscription): void;
}
export interface SubscriptionDroppedCallback<TSubscription> {
(subscription: TSubscription, reason: string, error?: Error): void;
}
export interface TcpEndPoint {
port: number;
host: string;
}
export interface HeartbeatInfo {
readonly connectionId: string;
readonly remoteEndPoint: TcpEndPoint;
readonly requestSentAt: number;
readonly requestPkgNumber: number;
readonly responseReceivedAt: number;
readonly responsePkgNumber: number;
}
export interface EventData {
readonly eventId: string;
readonly type: string;
readonly isJson: boolean;
readonly data: Buffer;
readonly metadata: Buffer;
}
interface EventStoreNodeConnectionEvents {
connected: TcpEndPoint;
disconnected: TcpEndPoint;
reconnecting: void;
closed:string;
error: Error;
heartbeatInfo: HeartbeatInfo;
}
type EventStoreNodeConnectionEventEmitter = StrictEventEmitter<EventEmitter, EventStoreNodeConnectionEvents>;
export class EventStoreNodeConnection extends (EventEmitter as { new(): EventStoreNodeConnectionEventEmitter }) {
connect(): Promise<void>;
close(): void;
// write actions
deleteStream(stream: string, expectedVersion: Long|number, hardDelete?: boolean, userCredentials?: UserCredentials): Promise<DeleteResult>;
appendToStream(stream: string, expectedVersion: Long|number, eventOrEvents: EventData | EventData[], userCredentials?: UserCredentials): Promise<WriteResult>;
startTransaction(stream: string, expectedVersion: Long|number, userCredentials?: UserCredentials): Promise<EventStoreTransaction>;
continueTransaction(transactionId: number, userCredentials?: UserCredentials): EventStoreTransaction;
// read actions
readEvent(stream: string, eventNumber: Long|number, resolveLinkTos?: boolean, userCredentials?: UserCredentials): Promise<EventReadResult>;
readStreamEventsForward(stream: string, start: Long|number, count: number, resolveLinkTos?: boolean, userCredentials?: UserCredentials): Promise<StreamEventsSlice>;
readStreamEventsBackward(stream: string, start: Long|number, count: number, resolveLinkTos?: boolean, userCredentials?: UserCredentials): Promise<StreamEventsSlice>;
readAllEventsForward(position: Position, maxCount: number, resolveLinkTos?: boolean, userCredentials?: UserCredentials): Promise<AllEventsSlice>;
readAllEventsBackward(position: Position, maxCount: number, resolveLinkTos?: boolean, userCredentials?: UserCredentials): Promise<AllEventsSlice>;
// subscription actions
subscribeToStream(stream: string, resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreSubscription>, userCredentials?: UserCredentials): Promise<EventStoreSubscription>;
subscribeToStreamFrom(stream: string, lastCheckpoint: Long|number|null, resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreCatchUpSubscription>, liveProcessingStarted?: LiveProcessingStartedCallback, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreCatchUpSubscription>, userCredentials?: UserCredentials, readBatchSize?: number): EventStoreCatchUpSubscription;
subscribeToAll(resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreSubscription>, userCredentials?: UserCredentials): Promise<EventStoreSubscription>;
subscribeToAllFrom(lastCheckpoint: Position|null, resolveLinkTos: boolean, eventAppeared: EventAppearedCallback<EventStoreCatchUpSubscription>, liveProcessingStarted?: LiveProcessingStartedCallback, subscriptionDropped?: SubscriptionDroppedCallback<EventStoreCatchUpSubscription>, userCredentials?: UserCredentials, readBatchSize?: number): EventStoreCatchUpSubscription;
// persistent subscriptions
createPersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: UserCredentials): Promise<PersistentSubscriptionCreateResult>;
updatePersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: UserCredentials): Promise<PersistentSubscriptionUpdateResult>;
deletePersistentSubscription(stream: string, groupName: string, userCredentials?: UserCredentials): Promise<PersistentSubscriptionDeleteResult>
connectToPersistentSubscription(stream: string, groupName: string, eventAppeared: EventAppearedCallback<EventStorePersistentSubscription>, subscriptionDropped?: SubscriptionDroppedCallback<EventStorePersistentSubscription>, userCredentials?: UserCredentials, bufferSize?: number, autoAck?: boolean): Promise<EventStorePersistentSubscription>;
// metadata actions
setStreamMetadataRaw(stream: string, expectedMetastreamVersion: Long|number, metadata: any, userCredentials?: UserCredentials): Promise<WriteResult>;
getStreamMetadataRaw(stream: string, userCredentials?: UserCredentials): Promise<RawStreamMetadataResult>;
}
// Expose helper functions
export interface ConnectionSettings {
log?: Logger,
verboseLogging?: boolean,
maxQueueSize?: number,
maxConcurrentItems?: number,
maxRetries?: number,
maxReconnections?: number,
requireMaster?: boolean,
reconnectionDelay?: number,
operationTimeout?: number,
operationTimeoutCheckPeriod?: number,
defaultUserCredentials?: UserCredentials,
useSslConnection?: boolean,
targetHost?: TcpEndPoint,
validateServer?: boolean,
failOnNoServerResponse?: boolean,
heartbeatInterval?: number,
heartbeatTimeout?: number,
clientConnectionTimeout?: number,
// Cluster Settings
clusterDns?: string,
maxDiscoverAttempts?: number,
discoverDelay?: number,
externalGossipPort?: number,
gossipTimeout?: number
}
// Expose Helper functions
export function createConnection(settings: ConnectionSettings, endPointOrGossipSeed: string | TcpEndPoint | GossipSeed[], connectionName?: string): EventStoreNodeConnection;
export function createJsonEventData(eventId: string, event: any, metadata?: any, type?: string): EventData;
export function createEventData(eventId: string, type: string, isJson: boolean, data: Buffer, metadata?: Buffer): EventData;

View File

@ -1,11 +1,6 @@
/**
* eventstore-node A port of EventStore .Net ClientAPI to Node.js
* node-eventstore-client A port of EventStore .Net ClientAPI to Node.js
* see README.md for more details
* see LICENSE for license info
*/
/**
* TODO:
* library is heavy on number of files so it could have negative impact on load time
* we need a compiled (single file) version of the library
*/
module.exports = require('./lib/dist.js');

File diff suppressed because it is too large Load Diff

6891
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,51 @@
{
"name": "eventstore-node",
"version": "0.0.2",
"name": "node-eventstore-client",
"version": "22.10.0",
"description": "A port of the EventStore .Net ClientAPI to Node.js",
"main": "index.js",
"types": "index.d.ts",
"engines": {
"node": ">=14",
"npm": ">=6"
},
"engineStrict": true,
"scripts": {
"test": "nodeunit"
"clean": "rm -f lib/dist.js",
"prebuild": "npm run clean",
"build": "webpack",
"pretest": "npm run build",
"test": "nodeunit",
"test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit",
"test:jest:watch": "jest --watch --coverage",
"test:single": "npm run compose:single:start && npm run compose:wait && npm run compose:single:test ; npm run compose:single:stop",
"test:gossip": "npm run compose:cluster:start && npm run compose:wait && npm run compose:gossip:test ; npm run compose:cluster:stop",
"test:cluster": "npm run compose:cluster:start && npm run compose:wait && npm run compose:cluster:test ; npm run compose:cluster:stop",
"compose:single:start": "docker-compose -f docker-compose-single.yaml up --build -d",
"compose:cluster:start": "docker-compose -f docker-compose-cluster.yaml up --build -d",
"compose:single:stop": "docker-compose -f docker-compose-single.yaml down -v --remove-orphans",
"compose:cluster:stop": "docker-compose -f docker-compose-cluster.yaml down -v --remove-orphans",
"compose:wait": "while [ \"$(curl -s -o /dev/null -w ''%{http_code}'' -k https://localhost:2113/ping)\" != \"200\" ]; do sleep 5; done",
"compose:single:test": "docker-compose -f docker-compose-single.yaml exec nodejs bash -c \"EVENTSTORE_CONNECTION_TYPE=tcp EVENTSTORE_HOST=192.168.33.10 ./build-and-test.sh\"",
"compose:gossip:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"EVENTSTORE_CONNECTION_TYPE=gossip EVENTSTORE_HOST=192.168.33.10 EVENTSTORE_HOST_1=192.168.33.10 EVENTSTORE_HOST_2=192.168.33.11 EVENTSTORE_HOST_3=192.168.33.12 ./build-and-test.sh\"",
"compose:cluster:test": "docker-compose -f docker-compose-cluster.yaml exec nodejs bash -c \"EVENTSTORE_CONNECTION_TYPE=dns EVENTSTORE_HOST=cluster.tests.node-eventstore-client.local ./build-and-test.sh\"",
"prepublishOnly": "npm run build && npm run gendocs",
"gendocs": "rm -rf docs && jsdoc src -r -d docs"
},
"files": [
"lib",
"docs",
"src",
"index.js",
"index.d.ts"
],
"directories": {
"lib": "lib",
"doc": "docs",
"example": "samples"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nicdex/eventstore-node.git"
"url": "git+https://github.com/nicdex/node-eventstore-client.git"
},
"keywords": [
"eventstore",
@ -18,16 +55,22 @@
"author": "Nicolas Dextraze",
"license": "MIT",
"bugs": {
"url": "https://github.com/nicdex/eventstore-node/issues"
"url": "https://github.com/nicdex/node-eventstore-client/issues"
},
"homepage": "https://github.com/nicdex/eventstore-node#readme",
"homepage": "https://github.com/nicdex/node-eventstore-client#readme",
"dependencies": {
"long": "^3.0.3",
"protobufjs": "^5.0.1",
"uuid": "^2.0.1"
"@types/node": ">=14",
"long": "^5.2.0",
"strict-event-emitter-types": "^1.2.0",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=0.12"
},
"engineStrict": true
"devDependencies": {
"jest": "^29.4.1",
"jsdoc": "^4.0.0",
"nodeunit": "^0.11.3",
"protobufjs": "^7.2.0",
"protobufjs-cli": "^1.1.0",
"webpack": "^5.75.0",
"webpack-cli": "^3.3.10"
}
}

62
samples/issue-60.js Normal file
View File

@ -0,0 +1,62 @@
const client = require('../src/client'); // RWM: Import from npm installed package rather than from src
//const client = require("node-eventstore-client");
const resolveLinkTos = true;
function resumeEvent(event) {
return [
event.originalEvent.eventType,
[event.originalEventNumber.toNumber(), event.originalStreamId].join('@'),
event.originalPosition
].join(" ")
}
const eventAppeared = (subscription, event) => console.log("Event received", resumeEvent(event));
const subscriptionDropped = (subscription, reason, error) => console.log("Subscription dropped", reason, error);
const libeProcessingStarted = () => console.log("Live processing started.");
const credentials = new client.UserCredentials("admin", "changeit");
const settings = {
maxReconnections: 10,
reconnectionDelay: 1000, // RWM: slow down the reconnection attempts. 10 seconds to restore connection.
};
if (process.env.ENABLE_LOGGING) settings.log = console;
if (process.env.VERBOSE) settings.verboseLogging = true;
const endpoint = "tcp://localhost:1113";
const connection = client.createConnection(settings, endpoint);
connection.connect().catch(err => console.log("Connection failed", err));
connection.on('heartbeatInfo', heartbeatInfo =>
console.log('Heartbeat latency', heartbeatInfo.responseReceivedAt - heartbeatInfo.requestSentAt, 'ms')
);
connection.once("connected", tcpEndPoint => {
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`);
// RWM: subscribe Stream instead of All
connection.subscribeToStreamFrom(
"test", // RWM: Stream to subscribe to
null,
resolveLinkTos,
eventAppeared,
libeProcessingStarted,
subscriptionDropped,
credentials
);
});
connection.on("error", error =>
console.log(`Error occurred on connection: ${error}`)
)
connection.on("closed", reason =>
console.log(`Connection closed, reason: ${reason}`)
)
// RWM: Handle the reconnecting event, for better awareness of what's happening
connection.on("reconnecting", msg =>
console.log(`Reconnecting, msg: ${JSON.stringify(msg, null, 4)}`)
)

55
samples/simple.js Normal file
View File

@ -0,0 +1,55 @@
const client = require('../src/client')
// const client = require("node-eventstore-client")
const uuid = require("uuid")
const settings = {
verboseLogging: true,
log: new client.FileLogger("./simple-verbose.log")
}
/*
// connecting to Cluster using hard-coded gossip seeds
const gossipSeeds = [
new client.GossipSeed({host: "192.168.33.10", port: 2113}),
new client.GossipSeed({host: "192.168.33.11", port: 2113}),
new client.GossipSeed({host: "192.168.33.12", port: 2113})
]
const connection = client.createConnection(settings, gossipSeeds)
*/
/*
// connecting to Cluster using dns discovery, note that cluster gossip over external http port not tcp port
const connection = client.createConnection(settings, 'discover://my.dns:2113')
*/
const connection = client.createConnection(settings, 'tcp://localhost:1113')
connection.connect().catch(err => console.log(err))
connection.on("connected", tcpEndPoint => {
console.log(`connected to endPoint ${tcpEndPoint.host}:${tcpEndPoint.port}`)
setInterval(() => {
connection.appendToStream(
`test-${uuid.v4()}`,
client.expectedVersion.noStream,
[
client.createJsonEventData(
uuid.v4(),
{ abc: 123 },
null,
"MyEvent"
)
]
).then(writeResult => console.log(writeResult))
}, 1000)
})
connection.on("error", error =>
console.log(`Error occurred on connection: ${error}`)
)
connection.on("closed", reason =>
console.log(`Connection closed, reason: ${reason}`)
)
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on("data", process.exit.bind(process, 0))

108
samples/speed-tests.js Normal file
View File

@ -0,0 +1,108 @@
//NOTE: this script require node.js v8+ to run (native async/await)
var uuid = require('uuid');
var client = require('../src/client');
var conn = client.createConnection({}, 'tcp://localhost:1113');
conn.on('connected', onConnected);
var batchSize = parseInt(process.argv[2], 10) || 500;
const MB = 1024*1024;
const adminCreds = new client.UserCredentials("admin", "changeit");
const nbEvents = 50000;
const events = [];
const streams = [];
const writeOneStream = 'testWrite-' + uuid.v4();
for(var i = 0; i < nbEvents; i++) {
streams.push('testWrite-' + uuid.v4());
events.push(client.createJsonEventData(uuid.v4(), {a:i, b:i+1, c:i+2}, null, 'myEvent'))
}
conn.connect();
function rssMB() {
return (process.memoryUsage().rss / MB).toFixed(2);
}
function reportResult(action, nbEvents, elapsedMs) {
console.log(action, nbEvents, 'events took', elapsedMs, 'ms, avg', (nbEvents/(elapsedMs/1000)).toFixed(2), '/s');
console.log('Memory usage:', rssMB(), 'MB\n');
}
async function testWriteOneStreamAnyVersion(batchSize) {
console.log(`Test Write One Stream Any Version (batchSize = ${batchSize})...`);
const start = Date.now();
const promises = [];
for(let i = 0; i < nbEvents; i += batchSize) {
promises.push(conn.appendToStream(writeOneStream, client.expectedVersion.any, events.slice(i, i + batchSize)))
}
await Promise.all(promises);
var diff = Date.now() - start;
reportResult("Writing", nbEvents, diff)
}
async function testWriteOneStreamWithVersion(batchSize) {
console.log(`Test Write One Stream With Version (batchSize = ${batchSize})...`);
const writeStream = 'testWrite-' + uuid.v4();
const start = Date.now();
const promises = [];
for(let i = 0; i < nbEvents; i += batchSize) {
promises.push(conn.appendToStream(writeStream, i-1, events.slice(i, i + batchSize)))
}
await Promise.all(promises);
var diff = Date.now() - start;
reportResult("Writing", nbEvents, diff)
}
async function testWriteMultipleStream() {
console.log('Test Write Multiple Streams...');
var start = Date.now();
var promises = [];
for(var i = 0; i < nbEvents; i++) {
promises.push(conn.appendToStream(streams[i], client.expectedVersion.emptyStream, events[i]))
}
await Promise.all(promises);
const diff = Date.now() - start;
reportResult("Writing", nbEvents, diff)
}
async function testRead(batchSize) {
console.log(`Test Read One Stream (batchSize = ${batchSize})...`);
const start = Date.now();
const promises = [];
for(let i = 0; i < nbEvents; i += batchSize) {
promises.push(conn.readStreamEventsForward(writeOneStream, i, batchSize, false));
}
const results = await Promise.all(promises);
const diff = Date.now() - start;
const readEvents = results.reduce((x,y) => x + y.events.length, 0);
reportResult("Reading", readEvents, diff)
}
async function testReadAll(batchSize) {
console.log(`Test Read from $all (batchSize = ${batchSize})...`);
const start = Date.now();
let pos = client.positions.start;
let eventsCount = 0;
for(;;) {
var result = await conn.readAllEventsForward(pos, batchSize, false, adminCreds);
pos = result.nextPosition;
eventsCount += result.events.length;
if (result.isEndOfStream) break;
}
const diff = Date.now() - start;
reportResult("Reading", eventsCount, diff)
}
async function onConnected() {
try {
await testWriteOneStreamAnyVersion(1);
await testWriteOneStreamAnyVersion(batchSize);
await testWriteOneStreamWithVersion(1);
await testWriteOneStreamWithVersion(batchSize);
await testWriteMultipleStream();
await testRead(1);
await testRead(batchSize);
await testReadAll(batchSize);
conn.close();
} catch (e) {
console.log('ERROR', e);
}
}

53
samples/store-event.js Normal file
View File

@ -0,0 +1,53 @@
const client = require('../src/client')
// const client = require("node-eventstore-client")
const uuid = require("uuid")
const settings = {}
const endpoint = "tcp://localhost:1113"
const connection = client.createConnection(settings, endpoint)
connection.connect().catch(err => console.log(err))
connection.once("connected", tcpEndPoint => {
const userId = uuid.v4()
const userCreatedEvent = {
id: userId,
username: `user${uuid.v4().substring(0,6)}`,
password: Math.random().toString()
}
const eventId = uuid.v4();
const event = client.createJsonEventData(
eventId,
userCreatedEvent,
null,
"UserCreated"
)
// Every user has their own stream of events:
const streamName = `user-${userId}`
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`)
console.log(`Storing event ${eventId}. Look for it at http://localhost:2113/web/index.html#/streams/user-${userId}`)
connection.appendToStream(streamName, client.expectedVersion.any, event)
.then(result => {
console.log("Event stored.")
process.exit(0)
})
.catch(error => {
console.log(error)
process.exit(-1)
})
})
connection.on("error", error => {
console.log(`Error occurred on connection: ${error}`)
process.exit(-1)
})
connection.on("closed", reason => {
console.log(`Connection closed, reason: ${reason}`)
process.exit(-1)
})

View File

@ -0,0 +1,56 @@
// Subscribe to all new events on the $all stream. Filter out any which aren"t about "user" aggregates.
const client = require('../src/client')
// const client = require("node-eventstore-client")
const resolveLinkTos = false
const belongsToAUserAggregate = event =>
event.originalEvent.eventStreamId.startsWith("user-")
const eventAppeared = (subscription, event) => {
if (belongsToAUserAggregate(event)) {
const aggregateId = event.originalEvent.eventStreamId
const eventId = event.originalEvent.eventId
const eventType = event.originalEvent.eventType
console.log(aggregateId, eventType, eventId)
console.log(event.originalEvent.data.toString())
}
}
const subscriptionDropped = (subscription, reason, error) =>
console.log(error ? error : "Subscription dropped.")
const credentials = new client.UserCredentials("admin", "changeit")
const settings = {}
const endpoint = "tcp://localhost:1113"
const connection = client.createConnection(settings, endpoint)
connection.connect().catch(err => console.log(err))
connection.on('heartbeatInfo', heartbeatInfo => {
console.log('Connected to endpoint', heartbeatInfo.remoteEndPoint)
console.log('Heartbeat latency', heartbeatInfo.responseReceivedAt - heartbeatInfo.requestSentAt)
})
connection.once("connected", tcpEndPoint => {
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`)
connection.subscribeToAll(
resolveLinkTos,
eventAppeared,
subscriptionDropped,
credentials
).then(subscription => {
console.log(`subscription.isSubscribedToAll: ${subscription.isSubscribedToAll}`),
console.log("(To generate a test event, try running 'node store-event.js' in a separate console.)")
})
})
connection.on("error", error =>
console.log(`Error occurred on connection: ${error}`)
)
connection.on("closed", reason =>
console.log(`Connection closed, reason: ${reason}`)
)

View File

@ -0,0 +1,50 @@
const client = require('../src/client');
//const client = require("node-eventstore-client");
const resolveLinkTos = true;
function resumeEvent(event) {
return [
event.originalEvent.eventType,
[event.originalEventNumber.toNumber(), event.originalStreamId].join('@'),
event.originalPosition
].join(" ")
}
const eventAppeared = (subscription, event) => console.log("Event received", resumeEvent(event));
const subscriptionDropped = (subscription, reason, error) => console.log("Subscription dropped", reason, error);
const libeProcessingStarted = () => console.log("Live processing started.");
const credentials = new client.UserCredentials("admin", "changeit");
const settings = {};
const endpoint = "tcp://localhost:1113";
const connection = client.createConnection(settings, endpoint);
connection.connect().catch(err => console.log("Connection failed", err));
connection.on('heartbeatInfo', heartbeatInfo =>
console.log('Heartbeat latency', heartbeatInfo.responseReceivedAt - heartbeatInfo.requestSentAt, 'ms')
);
connection.once("connected", tcpEndPoint => {
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`);
connection.subscribeToAllFrom(
null,
resolveLinkTos,
eventAppeared,
libeProcessingStarted,
subscriptionDropped,
credentials
);
});
connection.on("error", error =>
console.log(`Error occurred on connection: ${error}`)
)
connection.on("closed", reason =>
console.log(`Connection closed, reason: ${reason}`)
)

View File

@ -0,0 +1,49 @@
// Subscribe to all events on the $all stream. Catch up from the beginning, then listen for any new events as they occur.
// This could be used for subscribers which populate read models.
const client = require('../src/client')
// const client = require("node-eventstore-client")
const eventAppeared = (stream, event) =>
console.log(
event.originalEvent.eventStreamId,
event.originalEvent.eventId,
event.originalEvent.eventType
)
const liveProcessingStarted = () => {
console.log("Caught up with previously stored events. Listening for new events.")
console.log("(To generate a test event, try running 'node store-event.js' in a separate console.)")
}
const subscriptionDropped = (subscription, reason, error) =>
console.log(error ? error : "Subscription dropped.")
const credentials = new client.UserCredentials("admin", "changeit")
const settings = {}
const endpoint = "tcp://localhost:1113"
const connection = client.createConnection(settings, endpoint)
connection.connect().catch(err => console.log(err))
connection.once("connected", tcpEndPoint => {
const subscription = connection.subscribeToAllFrom(
null,
true,
eventAppeared,
liveProcessingStarted,
subscriptionDropped,
credentials
)
console.log(`Connected to eventstore at ${tcpEndPoint.host}:${tcpEndPoint.port}`)
console.log(`subscription.isSubscribedToAll: ${subscription.isSubscribedToAll}`)
})
connection.on("error", err =>
console.log(`Error occurred on connection: ${err}`)
)
connection.on("closed", reason =>
console.log(`Connection closed, reason: ${reason}`)
)

View File

@ -4,57 +4,80 @@ var results = require('./results');
const expectedVersion = {
any: -2,
noStream: -1,
emptyStream: -1
emptyStream: -1,
streamExists: -4
};
Object.freeze(expectedVersion);
const positions = {
start: new results.Position(0, 0),
end: new results.Position(-1, -1)
};
Object.freeze(positions);
const streamPosition = {
start: 0,
end: -1
};
Object.freeze(streamPosition);
/**
* @param {string} eventId
* @param {object} event
* @param {object} [metadata]
* @param {string} [type]
* Create an EventData object from JavaScript event/metadata that will be serialized as json
* @public
* @param {string} eventId Event UUID
* @param {object} event Event object
* @param {object} [metadata] Event metadata
* @param {string} [type] Event type
* @returns {EventData}
*/
function jsonEventDataFactory(eventId, event, metadata, type) {
function createJsonEventData(eventId, event, metadata, type) {
if (!event || typeof event !== 'object') throw new TypeError("data must be an object.");
var eventBuf = new Buffer(JSON.stringify(event));
var metaBuf = metadata ? new Buffer(JSON.stringify(metadata)) : null;
var eventBuf = Buffer.from(JSON.stringify(event));
var metaBuf = metadata ? Buffer.from(JSON.stringify(metadata)) : null;
return new EventData(eventId, type || event.constructor.name, true, eventBuf, metaBuf);
}
/**
* @param {string} eventId
* @param {string} type
* @param {boolean} isJson
* @param {Buffer} data
* @param {Buffer} [metadata]
* Create an EventData object from event/metadata buffer(s)
* @public
* @param {string} eventId Event UUID
* @param {string} type Event type
* @param {boolean} isJson is buffer(s) content json
* @param {Buffer} data Data buffer
* @param {Buffer} [metadata] Metadata buffer
* @returns {EventData}
*/
function eventDataFactory(eventId, type, isJson, data, metadata) {
function createEventData(eventId, type, isJson, data, metadata) {
return new EventData(eventId, type, isJson, data, metadata);
}
// Exporting classes
module.exports.EventStoreConnection = require('./eventStoreConnection');
module.exports.UserCredentials = require('./systemData/userCredentials');
module.exports.EventData = EventData;
module.exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings');
module.exports.SystemConsumerStrategies = require('./systemConsumerStrategies');
// Exporting errors
module.exports.WrongExpectedVersionError = require('./errors/wrongExpectedVersionError');
module.exports.StreamDeletedError = require('./errors/streamDeletedError');
module.exports.AccessDeniedError = require('./errors/accessDeniedError');
// Exporting enums/constants
module.exports.expectedVersion = expectedVersion;
module.exports.positions = positions;
module.exports.systemMetadata = require('./common/systemMetadata');
module.exports.eventReadStatus = results.EventReadStatus;
module.exports.sliceReadStatus = require('./sliceReadStatus');
// Helper functions
module.exports.createConnection = module.exports.EventStoreConnection.create;
module.exports.createEventData = eventDataFactory;
module.exports.createJsonEventData = jsonEventDataFactory;
// Expose classes
exports.Position = results.Position;
exports.UserCredentials = require('./systemData/userCredentials');
exports.PersistentSubscriptionSettings = require('./persistentSubscriptionSettings');
exports.SystemConsumerStrategies = require('./systemConsumerStrategies');
exports.GossipSeed = require('./gossipSeed');
exports.EventStoreConnection = require('./eventStoreConnection');
exports.ProjectionsManager = require('./projections/projectionsManager');
// Expose errors
exports.WrongExpectedVersionError = require('./errors/wrongExpectedVersionError');
exports.StreamDeletedError = require('./errors/streamDeletedError');
exports.AccessDeniedError = require('./errors/accessDeniedError');
exports.ProjectionCommandFailedError = require('./errors/projectionCommandFailedError');
// Expose enums/constants
exports.expectedVersion = expectedVersion;
exports.positions = positions;
exports.streamPosition = streamPosition;
exports.systemMetadata = require('./common/systemMetadata');
exports.eventReadStatus = results.EventReadStatus;
exports.sliceReadStatus = require('./sliceReadStatus');
exports.persistentSubscriptionNakEventAction =
exports.PersistentSubscriptionNakEventAction = require('./persistentSubscriptionNakEventAction');
// Expose loggers
exports.NoopLogger = require('./common/log/noopLogger');
exports.FileLogger = require('./common/log/fileLogger');
// Expose Helper functions
exports.createConnection = require('./eventStoreConnection').create;
exports.createJsonEventData = createJsonEventData;
exports.createEventData = createEventData;

View File

@ -1,5 +1,5 @@
var util = require('util');
var uuid = require('uuid');
var guidParse = require('../common/guid-parse');
var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision');
@ -26,25 +26,25 @@ util.inherits(AppendToStreamOperation, OperationBase);
AppendToStreamOperation.prototype._createRequestDto = function() {
var dtos = this._events.map(function(ev) {
var eventId = new Buffer(uuid.parse(ev.eventId));
return new ClientMessage.NewEvent({
event_id: eventId, event_type: ev.type,
data_content_type: ev.isJson ? 1 : 0, metadata_content_type: 0,
data: ev.data, metadata: ev.metadata});
var eventId = guidParse.parse(ev.eventId);
return {
eventId: eventId, eventType: ev.type,
dataContentType: ev.isJson ? 1 : 0, metadataContentType: 0,
data: ev.data, metadata: ev.metadata
};
});
return new ClientMessage.WriteEvents({
event_stream_id: this._stream,
expected_version: this._expectedVersion,
eventStreamId: this._stream,
expectedVersion: this._expectedVersion,
events: dtos,
require_master: this._requireMaster});
requireLeader: this._requireMaster});
};
AppendToStreamOperation.prototype._inspectResponse = function(response) {
switch (response.result)
{
case ClientMessage.OperationResult.Success:
if (this._wasCommitTimeout)
this.log.debug("IDEMPOTENT WRITE SUCCEEDED FOR %s.", this);
if (this._wasCommitTimeout) this.log.debug("IDEMPOTENT WRITE SUCCEEDED FOR %s.", this);
this._succeed();
return new InspectionResult(InspectionDecision.EndOperation, "Success");
case ClientMessage.OperationResult.PrepareTimeout:
@ -72,7 +72,7 @@ AppendToStreamOperation.prototype._inspectResponse = function(response) {
};
AppendToStreamOperation.prototype._transformResponse = function(response) {
return new WriteResult(response.last_event_number, new Position(response.prepare_position || -1, response.commit_position || -1));
return new WriteResult(response.lastEventNumber, new Position(response.preparePosition || -1, response.commitPosition || -1));
};
AppendToStreamOperation.prototype.toString = function() {

View File

@ -1,11 +1,13 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision');
var InspectionResult = require('./../systemData/inspectionResult');
var ClientMessage = require('../messages/clientMessage');
var results = require('../results');
var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError');
var StreamDeletedError = require('../errors/streamDeletedError');
var AccessDeniedError = require('../errors/accessDeniedError');
var OperationBase = require('../clientOperations/operationBase');
@ -20,7 +22,10 @@ function CommitTransactionOperation(log, cb, requireMaster, transactionId, userC
util.inherits(CommitTransactionOperation, OperationBase);
CommitTransactionOperation.prototype._createRequestDto = function() {
return new ClientMessage.TransactionCommit(this._transactionId, this._requireMaster);
return new ClientMessage.TransactionCommit({
transactionId: this._transactionId,
requireLeader: this._requireMaster
});
};
CommitTransactionOperation.prototype._inspectResponse = function(response) {
@ -36,17 +41,16 @@ CommitTransactionOperation.prototype._inspectResponse = function(response) {
case ClientMessage.OperationResult.ForwardTimeout:
return new InspectionResult(InspectionDecision.Retry, "ForwardTimeout");
case ClientMessage.OperationResult.WrongExpectedVersion:
var err = util.format("Commit transaction failed due to WrongExpectedVersion. TransactionID: %d.", this._transactionId);
this.fail(new Error(err));
this.fail(new WrongExpectedVersionError("Commit", this._transactionId));
return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion");
case ClientMessage.OperationResult.StreamDeleted:
this.fail(new Error("Stream deleted."));
this.fail(new StreamDeletedError(this._transactionId));
return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted");
case ClientMessage.OperationResult.InvalidTransaction:
this.fail(new Error("Invalid transaction."));
return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction");
case ClientMessage.OperationResult.AccessDenied:
this.fail(new Error("Write access denied."));
this.fail(new AccessDeniedError("Write", this._transactionId));
return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied");
default:
throw new Error(util.format("Unexpected OperationResult: %s.", response.result));
@ -54,12 +58,12 @@ CommitTransactionOperation.prototype._inspectResponse = function(response) {
};
CommitTransactionOperation.prototype._transformResponse = function(response) {
var logPosition = new results.Position(response.prepare_position || -1, response.commit_position || -1);
return new results.WriteResult(response.last_event_number, logPosition);
var logPosition = new results.Position(response.preparePosition || -1, response.commitPosition || -1);
return new results.WriteResult(response.lastEventNumber, logPosition);
};
CommitTransactionOperation.prototype.toString = function() {
return util.format("TransactionId: %s", this._transactionId);
};
module.exports = CommitTransactionOperation;
module.exports = CommitTransactionOperation;

View File

@ -1,5 +1,5 @@
var util = require('util');
var uuid = require('uuid');
var guidParse = require('../common/guid-parse');
var SubscriptionOperation = require('./subscriptionOperation');
var ClientMessage = require('../messages/clientMessage');
@ -27,48 +27,52 @@ function ConnectToPersistentSubscriptionOperation(
util.inherits(ConnectToPersistentSubscriptionOperation, SubscriptionOperation);
ConnectToPersistentSubscriptionOperation.prototype._createSubscriptionPackage = function() {
var dto = new ClientMessage.ConnectToPersistentSubscription(this._groupName, this._streamId, this._bufferSize);
var dto = new ClientMessage.ConnectToPersistentSubscription({
subscriptionId: this._groupName,
eventStreamId: this._streamId,
allowedInFlightMessages: this._bufferSize
});
return new TcpPackage(TcpCommand.ConnectToPersistentSubscription,
this._userCredentials !== null ? TcpFlags.Authenticated : TcpFlags.None,
this._correlationId,
this._userCredentials !== null ? this._userCredentials.username : null,
this._userCredentials !== null ? this._userCredentials.password : null,
createBufferSegment(dto.toBuffer()));
createBufferSegment(ClientMessage.ConnectToPersistentSubscription.encode(dto).finish()));
};
ConnectToPersistentSubscriptionOperation.prototype._inspectPackage = function(pkg) {
if (pkg.command == TcpCommand.PersistentSubscriptionConfirmation)
if (pkg.command === TcpCommand.PersistentSubscriptionConfirmation)
{
var dto = ClientMessage.PersistentSubscriptionConfirmation.decode(pkg.data.toBuffer());
this._confirmSubscription(dto.last_commit_position, dto.last_event_number);
this._subscriptionId = dto.subscription_id;
this._confirmSubscription(dto.lastCommitPosition, dto.lastEventNumber);
this._subscriptionId = dto.subscriptionId;
return new InspectionResult(InspectionDecision.Subscribed, "SubscriptionConfirmation");
}
if (pkg.command == TcpCommand.PersistentSubscriptionStreamEventAppeared)
if (pkg.command === TcpCommand.PersistentSubscriptionStreamEventAppeared)
{
var dto = ClientMessage.PersistentSubscriptionStreamEventAppeared.decode(pkg.data.toBuffer());
this._onEventAppeared(new results.ResolvedEvent(dto.event));
return new InspectionResult(InspectionDecision.DoNothing, "StreamEventAppeared");
}
if (pkg.command == TcpCommand.SubscriptionDropped)
if (pkg.command === TcpCommand.SubscriptionDropped)
{
var dto = ClientMessage.SubscriptionDropped.decode(pkg.data.toBuffer());
if (dto.reason == ClientMessage.SubscriptionDropped.SubscriptionDropReason.AccessDenied)
if (dto.reason === ClientMessage.SubscriptionDropped.SubscriptionDropReason.AccessDenied)
{
this.dropSubscription(SubscriptionDropReason.AccessDenied, new Error("You do not have access to the stream."));
return new InspectionResult(InspectionDecision.EndOperation, "SubscriptionDropped");
}
if (dto.reason == ClientMessage.SubscriptionDropped.SubscriptionDropReason.NotFound)
if (dto.reason === ClientMessage.SubscriptionDropped.SubscriptionDropReason.NotFound)
{
this.dropSubscription(SubscriptionDropReason.NotFound, new Error("Subscription not found"));
return new InspectionResult(InspectionDecision.EndOperation, "SubscriptionDropped");
}
if (dto.reason == ClientMessage.SubscriptionDropped.SubscriptionDropReason.PersistentSubscriptionDeleted)
if (dto.reason === ClientMessage.SubscriptionDropped.SubscriptionDropReason.PersistentSubscriptionDeleted)
{
this.dropSubscription(SubscriptionDropReason.PersistentSubscriptionDeleted, new Error("Persistent subscription deleted."));
return new InspectionResult(InspectionDecision.EndOperation, "SubscriptionDropped");
}
if (dto.reason == ClientMessage.SubscriptionDropped.SubscriptionDropReason.SubscriberMaxCountReached)
if (dto.reason === ClientMessage.SubscriptionDropped.SubscriptionDropReason.SubscriberMaxCountReached)
{
this.dropSubscription(SubscriptionDropReason.MaxSubscribersReached, new Error("Maximum subscribers reached."));
return new InspectionResult(InspectionDecision.EndOperation, "SubscriptionDropped");
@ -86,9 +90,9 @@ ConnectToPersistentSubscriptionOperation.prototype._createSubscriptionObject = f
ConnectToPersistentSubscriptionOperation.prototype.notifyEventsProcessed = function(processedEvents) {
ensure.notNull(processedEvents, "processedEvents");
var dto = new ClientMessage.PersistentSubscriptionAckEvents({
subscription_id: this._subscriptionId,
processed_event_ids: processedEvents.map(function (x) {
return new Buffer(uuid.parse(x));
subscriptionId: this._subscriptionId,
processedEventIds: processedEvents.map(function (x) {
return guidParse.parse(x);
})
});
@ -97,26 +101,27 @@ ConnectToPersistentSubscriptionOperation.prototype.notifyEventsProcessed = funct
this._correlationId,
this._userCredentials !== null ? this._userCredentials.username : null,
this._userCredentials !== null ? this._userCredentials.password : null,
createBufferSegment(dto.encode().toBuffer()));
createBufferSegment(ClientMessage.PersistentSubscriptionAckEvents.encode(dto).finish()));
this._enqueueSend(pkg);
};
ConnectToPersistentSubscriptionOperation.prototype.notifyEventsFailed = function(processedEvents, action, reason) {
ensure.notNull(processedEvents, "processedEvents");
ensure.notNull(reason, "reason");
var dto = new ClientMessage.PersistentSubscriptionNakEvents(
this._subscriptionId,
processedEvents.map(function(x) { return new Buffer(uuid.parse(x)); }),
reason,
action);
var dto = new ClientMessage.PersistentSubscriptionNakEvents({
subscriptionId: this._subscriptionId,
processedEventIds: processedEvents.map(function(x) { return guidParse.parse(x); }),
message: reason,
action: action
});
var pkg = new TcpPackage(TcpCommand.PersistentSubscriptionNakEvents,
this._userCredentials != null ? TcpFlags.Authenticated : TcpFlags.None,
this._userCredentials !== null ? TcpFlags.Authenticated : TcpFlags.None,
this._correlationId,
this._userCredentials != null ? this._userCredentials.username : null,
this._userCredentials != null ? this._userCredentials.password : null,
createBufferSegment(dto.toBuffer()));
this._userCredentials !== null ? this._userCredentials.username : null,
this._userCredentials !== null ? this._userCredentials.password : null,
createBufferSegment(ClientMessage.PersistentSubscriptionNakEvents.encode(dto).finish()));
this._enqueueSend(pkg);
};
module.exports = ConnectToPersistentSubscriptionOperation;
module.exports = ConnectToPersistentSubscriptionOperation;

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var ensure = require('../common/utils/ensure');
var OperationBase = require('../clientOperations/operationBase');
@ -36,11 +35,24 @@ function CreatePersistentSubscriptionOperation(log, cb, stream, groupName, setti
util.inherits(CreatePersistentSubscriptionOperation, OperationBase);
CreatePersistentSubscriptionOperation.prototype._createRequestDto = function() {
return new ClientMessage.CreatePersistentSubscription(this._groupName, this._stream, this._resolveLinkTos,
this._startFromBeginning, this._messageTimeoutMilliseconds, this._recordStatistics, this._liveBufferSize,
this._readBatchSize, this._bufferSize, this._maxRetryCount,
this._namedConsumerStrategy == SystemConsumerStrategies.RoundRobin, this._checkPointAfter,
this._maxCheckPointCount, this._minCheckPointCount, this._maxSubscriberCount, this._namedConsumerStrategy);
return new ClientMessage.CreatePersistentSubscription({
subscriptionGroupName: this._groupName,
eventStreamId: this._stream,
resolveLinkTos: this._resolveLinkTos,
startFrom: this._startFromBeginning,
messageTimeoutMilliseconds: this._messageTimeoutMilliseconds,
recordStatistics: this._recordStatistics,
liveBufferSize: this._liveBufferSize,
readBatchSize: this._readBatchSize,
bufferSize: this._bufferSize,
maxRetryCount: this._maxRetryCount,
preferRoundRobin: this._namedConsumerStrategy === SystemConsumerStrategies.RoundRobin,
checkpointAfterTime: this._checkPointAfter,
checkpointMaxCount: this._maxCheckPointCount,
checkpointMinCount: this._minCheckPointCount,
subscriberMaxCount: this._maxSubscriberCount,
namedConsumerStrategy: this._namedConsumerStrategy
});
};
CreatePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {
@ -71,4 +83,4 @@ CreatePersistentSubscriptionOperation.prototype.toString = function() {
return util.format("Stream: %s, Group Name: %s", this._stream, this._groupName);
};
module.exports = CreatePersistentSubscriptionOperation;
module.exports = CreatePersistentSubscriptionOperation;

View File

@ -1,7 +1,5 @@
var util = require('util');
var uuid = require('uuid');
var ensure = require('../common/utils/ensure');
var OperationBase = require('../clientOperations/operationBase');
var TcpCommand = require('../systemData/tcpCommand');
var ClientMessage = require('../messages/clientMessage');
@ -21,7 +19,10 @@ function DeletePersistentSubscriptionOperation(log, cb, stream, groupName, userC
util.inherits(DeletePersistentSubscriptionOperation, OperationBase);
DeletePersistentSubscriptionOperation.prototype._createRequestDto = function() {
return new ClientMessage.DeletePersistentSubscription(this._groupName, this._stream);
return new ClientMessage.DeletePersistentSubscription({
subscriptionGroupName: this._groupName,
eventStreamId: this._stream
});
};
DeletePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {
@ -52,4 +53,4 @@ DeletePersistentSubscriptionOperation.prototype.toString = function() {
return util.format("Stream: %s, Group Name: %s", this._stream, this._groupName);
};
module.exports = DeletePersistentSubscriptionOperation;
module.exports = DeletePersistentSubscriptionOperation;

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision');
@ -25,7 +24,12 @@ function DeleteStreamOperation(log, cb, requireMaster, stream, expectedVersion,
util.inherits(DeleteStreamOperation, OperationBase);
DeleteStreamOperation.prototype._createRequestDto = function() {
return new ClientMessage.DeleteStream(this._stream, this._expectedVersion, this._requireMaster, this._hardDelete);
return new ClientMessage.DeleteStream({
eventStreamId: this._stream,
expectedVersion: this._expectedVersion,
requireLeader: this._requireMaster,
hardDelete: this._hardDelete
});
};
DeleteStreamOperation.prototype._inspectResponse = function(response) {
@ -58,11 +62,11 @@ DeleteStreamOperation.prototype._inspectResponse = function(response) {
};
DeleteStreamOperation.prototype._transformResponse = function(response) {
return new results.DeleteResult(new results.Position(response.prepare_position || -1, response.commit_position || -1));
return new results.DeleteResult(new results.Position(response.preparePosition || -1, response.commitPosition || -1));
};
DeleteStreamOperation.prototype.toString = function() {
return util.format("Stream: %s, ExpectedVersion: %s.", this._stream, this._expectedVersion);
};
module.exports = DeleteStreamOperation;
module.exports = DeleteStreamOperation;

View File

@ -42,16 +42,17 @@ OperationBase.prototype._succeed = function() {
if (!this._completed) {
this._completed = true;
if (this._response != null)
if (this._response) {
this._cb(null, this._transformResponse(this._response));
else
} else {
this._cb(new Error("No result."))
}
}
};
OperationBase.prototype.createNetworkPackage = function(correlationId) {
var dto = this._createRequestDto();
var buf = dto.toBuffer();
var buf = dto.constructor.encode(dto).finish();
return new TcpPackage(
this._requestCommand,
this.userCredentials ? TcpFlags.Authenticated : TcpFlags.None,
@ -75,7 +76,7 @@ OperationBase.prototype.inspectPackage = function(pkg) {
case TcpCommand.NotHandled:
return this._inspectNotHandled(pkg);
default:
return this._inspectUnexpectedCommand(package, this._responseCommand);
return this._inspectUnexpectedCommand(pkg, this._responseCommand);
}
} catch(e) {
this.fail(e);
@ -116,11 +117,11 @@ OperationBase.prototype._inspectNotHandled = function(pkg)
case ClientMessage.NotHandled.NotHandledReason.TooBusy:
return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy");
case ClientMessage.NotHandled.NotHandledReason.NotMaster:
var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additional_info);
return new new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster",
{host: masterInfo.external_tcp_address, port: masterInfo.external_tcp_port},
{host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port});
case ClientMessage.NotHandled.NotHandledReason.NotLeader:
var masterInfo = ClientMessage.NotHandled.LeaderInfo.decode(message.additionalInfo);
return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotLeader",
{host: masterInfo.externalTcpAddress, port: masterInfo.externalTcpPort},
{host: masterInfo.externalSecureTcpAddress, port: masterInfo.externalSecureTcpPort});
default:
this.log.error("Unknown NotHandledReason: %s.", message.reason);
@ -130,13 +131,12 @@ OperationBase.prototype._inspectNotHandled = function(pkg)
OperationBase.prototype._inspectUnexpectedCommand = function(pkg, expectedCommand)
{
if (pkg.command == expectedCommand)
throw new Error("Command shouldn't be " + TcpCommand.getName(pkg.command));
if (pkg.command === expectedCommand) throw new Error("Command shouldn't be " + TcpCommand.getName(pkg.command));
this.log.error("Unexpected TcpCommand received.\n"
+ "Expected: %s, Actual: %s, Flags: %s, CorrelationId: %s\n"
+ "Operation (%s): %s\n"
+"TcpPackage Data Dump:\n%j",
+ "TcpPackage Data Dump:\n%j",
expectedCommand, TcpCommand.getName(pkg.command), pkg.flags, pkg.correlationId,
this.constructor.name, this, pkg.data);

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var ClientMessage = require('../messages/clientMessage');
@ -25,7 +24,13 @@ function ReadAllEventsBackwardOperation(
util.inherits(ReadAllEventsBackwardOperation, OperationBase);
ReadAllEventsBackwardOperation.prototype._createRequestDto = function() {
return new ClientMessage.ReadAllEvents(this._position.commitPosition, this._position.preparePosition, this._maxCount, this._resolveLinkTos, this._requireMaster);
return new ClientMessage.ReadAllEvents({
commitPosition: this._position.commitPosition,
preparePosition: this._position.preparePosition,
maxCount: this._maxCount,
resolveLinkTos: this._resolveLinkTos,
requireLeader: this._requireMaster
});
};
ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) {
@ -48,8 +53,8 @@ ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) {
ReadAllEventsBackwardOperation.prototype._transformResponse = function(response) {
return new results.AllEventsSlice(
ReadDirection.Backward,
new results.Position(response.commit_position, response.prepare_position),
new results.Position(response.next_commit_position, response.next_prepare_position),
new results.Position(response.commitPosition, response.preparePosition),
new results.Position(response.nextCommitPosition, response.nextPreparePosition),
response.events
)
};

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var ClientMessage = require('../messages/clientMessage');
@ -25,7 +24,13 @@ function ReadAllEventsForwardOperation(
util.inherits(ReadAllEventsForwardOperation, OperationBase);
ReadAllEventsForwardOperation.prototype._createRequestDto = function() {
return new ClientMessage.ReadAllEvents(this._position.commitPosition, this._position.preparePosition, this._maxCount, this._resolveLinkTos, this._requireMaster);
return new ClientMessage.ReadAllEvents({
commitPosition: this._position.commitPosition,
preparePosition: this._position.preparePosition,
maxCount: this._maxCount,
resolveLinkTos: this._resolveLinkTos,
requireLeader: this._requireMaster
});
};
ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) {
@ -48,8 +53,8 @@ ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) {
ReadAllEventsForwardOperation.prototype._transformResponse = function(response) {
return new results.AllEventsSlice(
ReadDirection.Forward,
new results.Position(response.commit_position, response.prepare_position),
new results.Position(response.next_commit_position, response.next_prepare_position),
new results.Position(response.commitPosition, response.preparePosition),
new results.Position(response.nextCommitPosition, response.nextPreparePosition),
response.events
)
};

View File

@ -21,7 +21,12 @@ function ReadEventOperation(log, cb, stream, eventNumber, resolveLinkTos, requir
util.inherits(ReadEventOperation, OperationBase);
ReadEventOperation.prototype._createRequestDto = function() {
return new ClientMessage.ReadEvent(this._stream, this._eventNumber, this._resolveLinkTos, this._requireMaster);
return new ClientMessage.ReadEvent({
eventStreamId: this._stream,
eventNumber: this._eventNumber,
resolveLinkTos: this._resolveLinkTos,
requireLeader: this._requireMaster
});
};
ReadEventOperation.prototype._inspectResponse = function(response) {
@ -54,7 +59,6 @@ ReadEventOperation.prototype._transformResponse = function(response) {
return new results.EventReadResult(convert(response.result), this._stream, this._eventNumber, response.event);
};
function convert(result)
{
switch (result)

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var ClientMessage = require('../messages/clientMessage');
@ -27,7 +26,13 @@ function ReadStreamEventsBackwardOperation(
util.inherits(ReadStreamEventsBackwardOperation, OperationBase);
ReadStreamEventsBackwardOperation.prototype._createRequestDto = function() {
return new ClientMessage.ReadStreamEvents(this._stream, this._fromEventNumber, this._maxCount, this._resolveLinkTos, this._requireMaster);
return new ClientMessage.ReadStreamEvents({
eventStreamId: this._stream,
fromEventNumber: this._fromEventNumber,
maxCount: this._maxCount,
resolveLinkTos: this._resolveLinkTos,
requireLeader: this._requireMaster
});
};
ReadStreamEventsBackwardOperation.prototype._inspectResponse = function(response) {
@ -60,9 +65,9 @@ ReadStreamEventsBackwardOperation.prototype._transformResponse = function(respon
this._fromEventNumber,
ReadDirection.Backward,
response.events,
response.next_event_number,
response.last_event_number,
response.is_end_of_stream
response.nextEventNumber,
response.lastEventNumber,
response.isEndOfStream
)
};

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var ClientMessage = require('../messages/clientMessage');
@ -27,7 +26,13 @@ function ReadStreamEventsForwardOperation(
util.inherits(ReadStreamEventsForwardOperation, OperationBase);
ReadStreamEventsForwardOperation.prototype._createRequestDto = function() {
return new ClientMessage.ReadStreamEvents(this._stream, this._fromEventNumber, this._maxCount, this._resolveLinkTos, this._requireMaster);
return new ClientMessage.ReadStreamEvents({
eventStreamId: this._stream,
fromEventNumber: this._fromEventNumber,
maxCount: this._maxCount,
resolveLinkTos: this._resolveLinkTos,
requireLeader: this._requireMaster
});
};
ReadStreamEventsForwardOperation.prototype._inspectResponse = function(response) {
@ -60,9 +65,9 @@ ReadStreamEventsForwardOperation.prototype._transformResponse = function(respons
this._fromEventNumber,
ReadDirection.Forward,
response.events,
response.next_event_number,
response.last_event_number,
response.is_end_of_stream
response.nextEventNumber,
response.lastEventNumber,
response.isEndOfStream
)
};

View File

@ -1,13 +1,13 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision');
var InspectionResult = require('./../systemData/inspectionResult');
var ClientMessage = require('../messages/clientMessage');
var EventStoreTransaction = require('../eventStoreTransaction');
var results = require('../results');
var AccessDeniedError = require('../errors/accessDeniedError');
var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError');
var StreamDeletedError = require('../errors/streamDeletedError');
var OperationBase = require('../clientOperations/operationBase');
function StartTransactionOperation(log, cb, requireMaster, stream, expectedVersion, parentConnection, userCredentials) {
@ -22,7 +22,11 @@ function StartTransactionOperation(log, cb, requireMaster, stream, expectedVersi
util.inherits(StartTransactionOperation, OperationBase);
StartTransactionOperation.prototype._createRequestDto = function() {
return new ClientMessage.TransactionStart(this._stream, this._expectedVersion, this._requireMaster);
return new ClientMessage.TransactionStart({
eventStreamId: this._stream,
expectedVersion: this._expectedVersion,
requireLeader: this._requireMaster
});
};
StartTransactionOperation.prototype._inspectResponse = function(response) {
@ -38,17 +42,16 @@ StartTransactionOperation.prototype._inspectResponse = function(response) {
case ClientMessage.OperationResult.ForwardTimeout:
return new InspectionResult(InspectionDecision.Retry, "ForwardTimeout");
case ClientMessage.OperationResult.WrongExpectedVersion:
var err = util.format("Start transaction failed due to WrongExpectedVersion. Stream: %s, Expected version: %d.", this._stream, this._expectedVersion);
this.fail(new Error(err));
this.fail(new WrongExpectedVersionError("Start transaction", this._stream, this._expectedVersion));
return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion");
case ClientMessage.OperationResult.StreamDeleted:
this.fail(new Error("Stream deleted: " + this._stream));
this.fail(new StreamDeletedError(this._stream));
return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted");
case ClientMessage.OperationResult.InvalidTransaction:
this.fail(new Error("Invalid transaction."));
return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction");
case ClientMessage.OperationResult.AccessDenied:
this.fail(new Error(util.format("Write access denied for stream '%s'.", this._stream)));
this.fail(new AccessDeniedError("Write", this._stream));
return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied");
default:
throw new Error(util.format("Unexpected OperationResult: %s.", response.result));
@ -56,7 +59,7 @@ StartTransactionOperation.prototype._inspectResponse = function(response) {
};
StartTransactionOperation.prototype._transformResponse = function(response) {
return new EventStoreTransaction(response.transaction_id, this.userCredentials, this._parentConnection);
return new EventStoreTransaction(response.transactionId, this.userCredentials, this._parentConnection);
};
StartTransactionOperation.prototype.toString = function() {

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var TcpCommand = require('../systemData/tcpCommand');
var TcpFlags = require('../systemData/tcpFlags');
@ -46,8 +45,7 @@ SubscriptionOperation.prototype._enqueueSend = function(pkg) {
SubscriptionOperation.prototype.subscribe = function(correlationId, connection) {
if (connection === null) throw new TypeError("connection is null.");
if (this._subscription != null || this._unsubscribed != 0)
return false;
if (this._subscription !== null || this._unsubscribed) return false;
this._correlationId = correlationId;
connection.enqueueSend(this._createSubscriptionPackage());
@ -64,7 +62,7 @@ SubscriptionOperation.prototype.unsubscribe = function() {
SubscriptionOperation.prototype._createUnsubscriptionPackage = function() {
var msg = new ClientMessage.UnsubscribeFromStream();
var data = new BufferSegment(msg.toBuffer());
var data = new BufferSegment(ClientMessage.UnsubscribeFromStream.encode(msg).finish());
return new TcpPackage(TcpCommand.UnsubscribeFromStream, TcpFlags.None, this._correlationId, null, null, data);
};
@ -76,9 +74,7 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
try
{
var result = this._inspectPackage(pkg);
if (result !== null) {
return result;
}
if (result !== null) return result;
switch (pkg.command)
{
@ -128,8 +124,9 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
case TcpCommand.NotHandled:
{
if (this._subscription != null)
if (this._subscription !== null) {
throw new Error("NotHandled command appeared while we already subscribed.");
}
var message = ClientMessage.NotHandled.decode(pkg.data.toBuffer());
switch (message.reason)
@ -140,11 +137,11 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
case ClientMessage.NotHandled.NotHandledReason.TooBusy:
return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy");
case ClientMessage.NotHandled.NotHandledReason.NotMaster:
var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additional_info);
return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster",
{host: masterInfo.external_tcp_address, port: masterInfo.external_tcp_port},
{host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port});
case ClientMessage.NotHandled.NotHandledReason.NotLeader:
var masterInfo = ClientMessage.NotHandled.LeaderInfo.decode(message.additionalInfo);
return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotLeader",
{host: masterInfo.externalTcpAddress, port: masterInfo.externalTcpPort},
{host: masterInfo.externalSecureTcpAddress, port: masterInfo.externalSecureTcpPort});
default:
this._log.error("Unknown NotHandledReason: %s.", message.reason);
@ -172,8 +169,7 @@ SubscriptionOperation.prototype.connectionClosed = function() {
};
SubscriptionOperation.prototype.timeOutSubscription = function() {
if (this._subscription !== null)
return false;
if (this._subscription !== null) return false;
this.dropSubscription(SubscriptionDropReason.SubscribingError, null);
return true;
};
@ -182,36 +178,43 @@ SubscriptionOperation.prototype.dropSubscription = function(reason, err, connect
if (!this._unsubscribed)
{
this._unsubscribed = true;
if (this._verboseLogging)
if (this._verboseLogging) {
this._log.debug("Subscription %s to %s: closing subscription, reason: %s, exception: %s...",
this._correlationId, this._streamId || "<all>", reason, err);
this._correlationId, this._streamId || "<all>", reason, err);
}
if (reason !== SubscriptionDropReason.UserInitiated && this._subscription === null)
{
if (err === null) throw new Error(util.format("No exception provided for subscription drop reason '%s", reason));
//TODO: this should be last thing to execute
this._cb(err);
return;
}
if (reason === SubscriptionDropReason.UserInitiated && this._subscription !== null && connection !== null)
if (reason === SubscriptionDropReason.UserInitiated && this._subscription !== null && connection !== null) {
connection.enqueueSend(this._createUnsubscriptionPackage());
}
var self = this;
if (this._subscription !== null)
this._executeAction(function() { self._subscriptionDropped(self._subscription, reason, err); });
if (this._subscription !== null) {
this._executeAction(function () {
self._subscriptionDropped(self._subscription, reason, err);
});
}
}
};
SubscriptionOperation.prototype._confirmSubscription = function(lastCommitPosition, lastEventNumber) {
if (lastCommitPosition < -1)
if (lastCommitPosition < -1) {
throw new Error(util.format("Invalid lastCommitPosition %s on subscription confirmation.", lastCommitPosition));
if (this._subscription !== null)
}
if (this._subscription !== null) {
throw new Error("Double confirmation of subscription.");
}
if (this._verboseLogging)
if (this._verboseLogging) {
this._log.debug("Subscription %s to %s: subscribed at CommitPosition: %d, EventNumber: %d.",
this._correlationId, this._streamId || "<all>", lastCommitPosition, lastEventNumber);
this._correlationId, this._streamId || "<all>", lastCommitPosition, lastEventNumber);
}
this._subscription = this._createSubscriptionObject(lastCommitPosition, lastEventNumber);
this._cb(null, this._subscription);
@ -222,18 +225,18 @@ SubscriptionOperation.prototype._createSubscriptionObject = function(lastCommitP
};
SubscriptionOperation.prototype._onEventAppeared = function(e) {
if (this._unsubscribed)
return;
if (this._unsubscribed) return;
if (this._subscription === null) throw new Error("Subscription not confirmed, but event appeared!");
if (this._verboseLogging)
if (this._verboseLogging) {
this._log.debug("Subscription %s to %s: event appeared (%s, %d, %s @ %s).",
this._correlationId, this._streamId || "<all>",
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
this._correlationId, this._streamId || "<all>",
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
}
var self = this;
this._executeAction(function() { self._eventAppeared(self._subscription, e); });
this._executeAction(function() { return self._eventAppeared(self._subscription, e); });
};
SubscriptionOperation.prototype._executeAction = function(action) {
@ -245,21 +248,30 @@ SubscriptionOperation.prototype._executeAction = function(action) {
};
SubscriptionOperation.prototype._executeActions = function() {
//TODO: possible blocking loop for node.js
var action = this._actionQueue.shift();
while (action)
{
try
{
action();
}
catch (err)
{
this._log.error(err, "Exception during executing user callback: %s.", err.message);
}
action = this._actionQueue.shift();
if (!action) {
this._actionExecuting = false;
return;
}
var promise;
try
{
promise = action();
}
catch (err)
{
this._log.error(err, "Exception during executing user callback: %s.", err.message);
}
if (promise && promise.then) {
var self = this;
promise
.catch(function (err) {
self._log.error(err, "Exception during executing user callback: %s.", err.message);
})
.then(this._executeActions.bind(this));
} else {
setImmediate(this._executeActions.bind(this));
}
this._actionExecuting = false;
};
SubscriptionOperation.prototype.toString = function() {
@ -267,4 +279,4 @@ SubscriptionOperation.prototype.toString = function() {
};
module.exports = SubscriptionOperation;
module.exports = SubscriptionOperation;

View File

@ -1,10 +1,11 @@
var util = require('util');
var uuid = require('uuid');
var guidParse = require('../common/guid-parse');
var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision');
var InspectionResult = require('./../systemData/inspectionResult');
var ClientMessage = require('../messages/clientMessage');
var AccessDeniedError = require('../errors/accessDeniedError');
var OperationBase = require('../clientOperations/operationBase');
@ -21,13 +22,18 @@ util.inherits(TransactionalWriteOperation, OperationBase);
TransactionalWriteOperation.prototype._createRequestDto = function() {
var dtos = this._events.map(function(ev) {
var eventId = new Buffer(uuid.parse(ev.eventId));
return new ClientMessage.NewEvent({
event_id: eventId, event_type: ev.type,
data_content_type: ev.isJson ? 1 : 0, metadata_content_type: 0,
data: ev.data, metadata: ev.metadata});
var eventId = guidParse.parse(ev.eventId);
return {
eventId: eventId, eventType: ev.type,
dataContentType: ev.isJson ? 1 : 0, metadataContentType: 0,
data: ev.data, metadata: ev.metadata
};
});
return new ClientMessage.TransactionWrite({
transactionId: this._transactionId,
events: dtos,
requireLeader: this._requireMaster
});
return new ClientMessage.TransactionWrite(this._transactionId, dtos, this._requireMaster);
};
TransactionalWriteOperation.prototype._inspectResponse = function(response) {
@ -43,7 +49,7 @@ TransactionalWriteOperation.prototype._inspectResponse = function(response) {
case ClientMessage.OperationResult.ForwardTimeout:
return new InspectionResult(InspectionDecision.Retry, "ForwardTimeout");
case ClientMessage.OperationResult.AccessDenied:
this.fail(new Error("Write access denied."));
this.fail(new AccessDeniedError("Write", "trx:" + this._transactionId));
return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied");
default:
throw new Error(util.format("Unexpected OperationResult: %s.", response.result));
@ -59,4 +65,3 @@ TransactionalWriteOperation.prototype.toString = function() {
};
module.exports = TransactionalWriteOperation;

View File

@ -1,5 +1,4 @@
var util = require('util');
var uuid = require('uuid');
var ensure = require('../common/utils/ensure');
var OperationBase = require('../clientOperations/operationBase');
@ -36,11 +35,24 @@ function UpdatePersistentSubscriptionOperation(log, cb, stream, groupName, setti
util.inherits(UpdatePersistentSubscriptionOperation, OperationBase);
UpdatePersistentSubscriptionOperation.prototype._createRequestDto = function() {
return new ClientMessage.UpdatePersistentSubscription(this._groupName, this._stream, this._resolveLinkTos,
this._startFromBeginning, this._messageTimeoutMilliseconds, this._recordStatistics, this._liveBufferSize,
this._readBatchSize, this._bufferSize, this._maxRetryCount,
this._namedConsumerStrategy == SystemConsumerStrategies.RoundRobin, this._checkPointAfter,
this._maxCheckPointCount, this._minCheckPointCount, this._maxSubscriberCount, this._namedConsumerStrategy);
return new ClientMessage.UpdatePersistentSubscription({
subscriptionGroupName: this._groupName,
eventStreamId: this._stream,
resolveLinkTos: this._resolveLinkTos,
startFrom: this._startFromBeginning,
messageTimeoutMilliseconds: this._messageTimeoutMilliseconds,
recordStatistics: this._recordStatistics,
liveBufferSize: this._liveBufferSize,
readBatchSize: this._readBatchSize,
bufferSize: this._bufferSize,
maxRetryCount: this._maxRetryCount,
preferRoundRobin: this._namedConsumerStrategy === SystemConsumerStrategies.RoundRobin,
checkpointAfterTime: this._checkPointAfter,
checkpointMaxCount: this._maxCheckPointCount,
checkpointMinCount: this._minCheckPointCount,
subscriberMaxCount: this._maxSubscriberCount,
namedConsumerStrategy: this._namedConsumerStrategy
});
};
UpdatePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {
@ -71,4 +83,4 @@ UpdatePersistentSubscriptionOperation.prototype.toString = function() {
return util.format("Stream: %s, Group Name: %s", this._stream, this._groupName);
};
module.exports = UpdatePersistentSubscriptionOperation;
module.exports = UpdatePersistentSubscriptionOperation;

View File

@ -9,7 +9,7 @@ var BufferSegment = require('../common/bufferSegment');
var InspectionDecision = require('../systemData/inspectionDecision');
var InspectionResult = require('./../systemData/inspectionResult');
var results = require('../results');
var VolatileEventStoreSubscription = require('../volatileEventStoreConnection');
var VolatileEventStoreSubscription = require('../volatileEventStoreSubscription');
function VolatileSubscriptionOperation(
log, cb, streamId, resolveLinkTos, userCredentials, eventAppeared,
@ -20,30 +20,33 @@ function VolatileSubscriptionOperation(
util.inherits(VolatileSubscriptionOperation, SubscriptionOperation);
VolatileSubscriptionOperation.prototype._createSubscriptionPackage = function() {
var dto = new ClientMessage.SubscribeToStream(this._streamId, this._resolveLinkTos);
var dto = new ClientMessage.SubscribeToStream({
eventStreamId: this._streamId,
resolveLinkTos: this._resolveLinkTos
});
return new TcpPackage(TcpCommand.SubscribeToStream,
this._userCredentials != null ? TcpFlags.Authenticated : TcpFlags.None,
this._userCredentials !== null ? TcpFlags.Authenticated : TcpFlags.None,
this._correlationId,
this._userCredentials != null ? this._userCredentials.username : null,
this._userCredentials != null ? this._userCredentials.password : null,
new BufferSegment(dto.toBuffer()));
this._userCredentials !== null ? this._userCredentials.username : null,
this._userCredentials !== null ? this._userCredentials.password : null,
new BufferSegment(ClientMessage.SubscribeToStream.encode(dto).finish()));
};
VolatileSubscriptionOperation.prototype._inspectPackage = function(pkg) {
try {
if (pkg.command == TcpCommand.SubscriptionConfirmation) {
if (pkg.command === TcpCommand.SubscriptionConfirmation) {
var dto = ClientMessage.SubscriptionConfirmation.decode(pkg.data.toBuffer());
this._confirmSubscription(dto.last_commit_position, dto.last_event_number);
this._confirmSubscription(dto.lastCommitPosition, dto.lastEventNumber);
return new InspectionResult(InspectionDecision.Subscribed, "SubscriptionConfirmation");
}
if (pkg.command == TcpCommand.StreamEventAppeared) {
if (pkg.command === TcpCommand.StreamEventAppeared) {
var dto = ClientMessage.StreamEventAppeared.decode(pkg.data.toBuffer());
this._onEventAppeared(new results.ResolvedEvent(dto.event));
return new InspectionResult(InspectionDecision.DoNothing, "StreamEventAppeared");
}
return null;
} catch(e) {
console.log(e.stack);
this._log.warn(e.stack);
return null;
}
};
@ -52,4 +55,4 @@ VolatileSubscriptionOperation.prototype._createSubscriptionObject = function(las
return new VolatileEventStoreSubscription(this, this._streamId, lastCommitPosition, lastEventNumber);
};
module.exports = VolatileSubscriptionOperation;
module.exports = VolatileSubscriptionOperation;

View File

@ -1,5 +1,6 @@
/**
* Create a buffer segment
* @private
* @param {Buffer} buf
* @param {number} [offset]
* @param {number} [count]
@ -18,8 +19,7 @@ BufferSegment.prototype.toString = function() {
};
BufferSegment.prototype.toBuffer = function() {
if (this.offset === 0 && this.count === this.buffer.length)
return this.buffer;
if (this.offset === 0 && this.count === this.buffer.length) return this.buffer;
return this.buffer.slice(this.offset, this.offset + this.count);
};

47
src/common/guid-parse.js Normal file
View File

@ -0,0 +1,47 @@
'use strict';
// Maps for number <-> hex string conversion
var _byteToHex = [];
var _hexToByte = {};
for (var i = 0; i < 256; i++) {
_byteToHex[i] = (i + 0x100).toString(16).substr(1);
_hexToByte[_byteToHex[i]] = i;
}
// **`parse()` - Parse a UUID into it's component bytes**
function parse(s, buf, offset) {
const i = (buf && offset) || 0;
var ii = 0;
if (buf) buf.fill(0, i, i + 16);
buf = buf || Buffer.alloc(16);
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToByte[oct];
}
});
var buf2 = Buffer.from(buf.slice(i, i + 16));
buf[i + 0] = buf2[3];
buf[i + 1] = buf2[2];
buf[i + 2] = buf2[1];
buf[i + 3] = buf2[0];
buf[i + 4] = buf2[5];
buf[i + 5] = buf2[4];
buf[i + 6] = buf2[7];
buf[i + 7] = buf2[6];
return buf;
}
// **`unparse()` - Convert UUID byte array (ala parse()) into a string**
function unparse(buf, offset) {
var i = offset || 0;
return '03020100-0504-0706-0809-101112131415'.replace(/\d{2}/g, function (num) {
var j = parseInt(num, 10);
return _byteToHex[buf[i+j]];
})
}
exports.parse = parse;
exports.unparse = unparse;

View File

@ -1,4 +1,5 @@
/**
* @private
* @constructor
* @property {number} length
*/
@ -38,4 +39,4 @@ Hash.prototype.remove = function(key) {
};
module.exports = Hash;
module.exports = Hash;

View File

@ -6,7 +6,7 @@ function FileLogger(filePath, append) {
this._filePath = filePath;
if (!append) {
try {
fs.unlink(filePath);
fs.unlinkSync(filePath);
} catch(e) {}
}
}
@ -26,6 +26,11 @@ FileLogger.prototype.info = function() {
fs.appendFileSync(this._filePath, line);
};
FileLogger.prototype.warn = function() {
var line = createLine('WARN', arguments, 0);
fs.appendFileSync(this._filePath, line);
};
FileLogger.prototype.error = function(e) {
var hasError = e instanceof Error;
var line = createLine('ERROR', arguments, hasError ? 1 : 0);
@ -36,4 +41,4 @@ FileLogger.prototype.error = function(e) {
};
module.exports = FileLogger;
module.exports = FileLogger;

View File

@ -1,7 +1,8 @@
function NoopLogger() {
}
NoopLogger.prototype.error = function() {};
NoopLogger.prototype.debug = function() {};
NoopLogger.prototype.info = function() {};
NoopLogger.prototype.warn = function() {};
NoopLogger.prototype.error = function() {};
module.exports = NoopLogger;
module.exports = NoopLogger;

View File

@ -1,5 +1,5 @@
const SystemEventTypes = {
const SystemEventTypes = Object.freeze({
StreamMetadata: '$metadata'
};
});
module.exports = SystemEventTypes;
module.exports = SystemEventTypes;

View File

@ -1,4 +1,4 @@
const SystemMetadata = {
const SystemMetadata = Object.freeze({
maxAge: '$maxAge',
maxCount: '$maxCount',
truncateBefore: '$tb',
@ -11,6 +11,6 @@ const SystemMetadata = {
aclMetaWrite: '$mw',
userStreamAcl: '$userStreamAcl',
systemStreamAcl: '$systemStreamAcl'
};
});
module.exports = SystemMetadata;

View File

@ -1,6 +1,6 @@
module.exports.metastreamOf = function(stream) {
exports.metastreamOf = function(stream) {
return '$$' + stream;
};
module.exports.isMetastream = function(stream) {
exports.isMetastream = function(stream) {
return stream.indexOf('$$') === 0;
};

View File

@ -1,38 +1,45 @@
var Long = require('long');
module.exports.notNullOrEmpty = function(value, name) {
if (value === null)
throw new TypeError(name + " should not be null.");
if (value === '')
throw new Error(name + " should not be empty.");
if (value === null) throw new TypeError(name + " should not be null.");
if (value === '') throw new Error(name + " should not be empty.");
};
module.exports.notNull = function(value, name) {
if (value === null)
throw new TypeError(name + " should not be null.");
if (value === null) throw new TypeError(name + " should not be null.");
};
module.exports.isInteger = function(value, name) {
if (typeof value !== 'number' || value % 1 !== 0)
throw new TypeError(name + " should be an integer.");
function isInteger(value, name) {
if (typeof value !== 'number' || value % 1 !== 0) throw new TypeError(name + " should be an integer.");
}
module.exports.isInteger = isInteger;
module.exports.isLongOrInteger = function(value, name) {
if (typeof value === 'number') {
return isInteger(value, name);
} else if (!Long.isLong(value)) {
throw new TypeError(name + " should be a Long|number.");
}
};
module.exports.isArrayOf = function(expectedType, value, name) {
if (!Array.isArray(value))
throw new TypeError(name + " should be an array.");
if (!value.every(function(x) { return x instanceof expectedType; }))
throw new TypeError([name, " should be an array of ", expectedType, "."].join(""));
if (!Array.isArray(value)) throw new TypeError(name + " should be an array.");
if (!value.every(function(x) { return x instanceof expectedType; })) {
throw new TypeError([name, " should be an array of ", expectedType.name, "."].join(""));
}
};
module.exports.isTypeOf = function(expectedType, value, name) {
if (!(value instanceof expectedType))
throw new TypeError([name, " should be of type '", expectedType, "'."].join(""));
module.exports.isTypeOf = function(expectedType, value, name, nullAllowed) {
if (nullAllowed && value === null) return;
if (!(value instanceof expectedType)) {
throw new TypeError([name, " should be of type '", expectedType.name, "'", nullAllowed ? " or null" : "", "."].join(""));
}
};
module.exports.positive = function(value, name) {
if (value <= 0)
throw new Error(name + " should be positive.");
if (value <= 0) throw new Error(name + " should be positive.");
};
module.exports.nonNegative = function(value, name) {
if (value < 0)
throw new Error(name + " should be non-negative.");
if (value < 0) throw new Error(name + " should be non-negative.");
};

View File

@ -0,0 +1,28 @@
function rndNext(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function shuffle (arr, from, to) {
if (arr.length <= 1){
return arr;
}
if (!from) {
from = 0;
}
if (!to) {
to = arr.length - 1;
}
const newArr = [...arr];
if (from >= to) return;
for (var current = from; current <= to; ++current) {
var index = rndNext(current, to + 1);
var tmp = newArr[index];
newArr[index] = newArr[current];
newArr[current] = tmp;
}
return newArr;
};
module.exports = shuffle;

View File

@ -0,0 +1,165 @@
const ClusterInfo = require('./clusterInfo');
const GossipSeed = require('../../gossipSeed');
const NodeEndPoints = require('./nodeEndpoints');
const shuffle = require('../../common/utils/shuffle');
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* ClusterDiscoverer
* @constructor
* @class
* @param {Logger} log - Logger instance
* @param {Object} settings - Settings object
* @param {Object} dnsService - DNS service to perform DNS lookup
* @param {Object} httpService - HTTP service to perform http requests
*/
function ClusterDiscoverer(log, settings, dnsService, httpService) {
if (!settings.clusterDns && (!settings.seeds || settings.seeds.length === 0))
throw new Error('Both clusterDns and seeds are null/empty.');
this._log = log;
this._settings = settings;
this._dnsService = dnsService;
this._httpService = httpService;
}
/**
* Discover Cluster endpoints
* @param {Object} failedTcpEndPoint - The failed TCP endpoint which were used by the handler
* @returns {Promise.<NodeEndPoints>}
*/
ClusterDiscoverer.prototype.discover = async function (failedTcpEndPoint) {
let attempts = 0;
while (attempts++ < this._settings.maxDiscoverAttempts) {
try {
const candidates = await this._getGossipCandidates(this._settings.managerExternalHttpPort);
const gossipSeeds = candidates.filter(
(candidate) =>
!failedTcpEndPoint ||
!(candidate.endPoint.host === failedTcpEndPoint.host && candidate.endPoint.port === failedTcpEndPoint.port)
);
let gossipSeedsIndex = 0;
let clusterInfo;
do {
try {
clusterInfo = await this._clusterInfo(gossipSeeds[gossipSeedsIndex], this._settings.gossipTimeout);
if (!clusterInfo.bestNode) {
this._log.info(
`Discovering attempt ${attempts}/${this._settings.maxDiscoverAttempts} failed: no candidate found.`
);
continue;
}
} catch (err) {}
} while (++gossipSeedsIndex < gossipSeeds.length);
if (clusterInfo) {
return NodeEndPoints.createFromGossipMember(clusterInfo.bestNode);
}
} catch (err) {
this._log.info(
`Discovering attempt ${attempts}/${this._settings.maxDiscoverAttempts} failed with error: ${err}.\n${err.stack}`
);
}
await wait(this._settings.discoverDelay);
}
throw new Error(`Failed to discover candidate in ${this._settings.maxDiscoverAttempts} attempts.`);
};
/**
* Get gossip candidates either from DNS or from gossipSeeds settings
* @private
* @param {Number} managerExternalHttpPort - Http port of the manager (or the http port of the node for OSS clusters)
* @returns {Promise.<GossipSeed[]>}
*/
ClusterDiscoverer.prototype._getGossipCandidates = async function (managerExternalHttpPort) {
const gossipSeeds =
this._settings.seeds && this._settings.seeds.length > 0
? this._settings.seeds
: (await this._resolveDns(this._settings.clusterDns)).map(
(address) => new GossipSeed({ host: address, port: managerExternalHttpPort }, address, this._settings.clusterDns)
);
return shuffle(gossipSeeds);
};
/**
* Resolve the cluster DNS discovery address to retrieve belonging ip addresses
* @private
* @param {String} clusterDns - Cluster DNS discovery address
* @returns {Promise.<String[]>}
*/
ClusterDiscoverer.prototype._resolveDns = async function (clusterDns) {
const dnsOptions = {
family: 4,
hints: this._dnsService.ADDRCONFIG | this._dnsService.V4MAPPED,
all: true,
};
const result = await this._dnsService.lookup(clusterDns, dnsOptions);
if (!result || result.length === 0) {
throw new Error(`No result from dns lookup for ${clusterDns}`);
}
return result.map((address) => address.address);
};
/**
* Get cluster informations (gossip members)
* @param {GossipSeed} candidate - candidate to get informations from
* @param {Number} timeout - timeout for the http request
* @returns {Promise.<ClusterInfo>}
*/
ClusterDiscoverer.prototype._clusterInfo = async function (candidate, timeout) {
var self = this;
return new Promise((resolve, reject) => {
const options = {
host: candidate.endPoint.host,
port: candidate.endPoint.port,
path: '/gossip?format=json',
timeout: timeout,
rejectUnauthorized: self._settings.rejectUnauthorized
};
if (candidate.hostHeader) {
options.headers = {
Host: candidate.hostHeader,
};
}
const request = this._httpService.request(options, (res) => {
if (res.statusCode !== 200) {
this._log.info('Trying to get gossip from', candidate, 'failed with status code:', res.statusCode);
reject(new Error(`Gossip candidate returns a ${res.statusCode} error`));
return;
}
let result = '';
res.on('data', (chunk) => {
result += chunk.toString();
});
res.on('end', function () {
try {
result = JSON.parse(result);
} catch (e) {
reject(new Error('Unable to parse gossip response'));
}
resolve(new ClusterInfo(result.members));
});
});
request.setTimeout(timeout);
request.on('timeout', () => {
this._log.info('Trying to get gossip from', candidate, 'timed out.');
request.destroy();
reject(new Error('Connection to gossip timed out'));
});
request.on('error', (error) => {
this._log.info('Trying to get gossip from', candidate, 'errored', error);
request.destroy();
reject(new Error('Connection to gossip errored'));
});
request.end();
});
};
module.exports = ClusterDiscoverer;

View File

@ -0,0 +1,41 @@
const MemberInfo = require('./memberInfo.js');
const VNodeStates = Object.freeze({
Initializing: 0,
DiscoverLeader: 1,
Unknown: 2,
PreReplica: 3,
CatchingUp: 4,
Clone: 5,
Follower: 6,
PreLeader: 7,
Leader: 8,
Manager: 9,
ShuttingDown: 10,
Shutdown: 11,
ReadOnlyLeaderless: 12,
PreReadOnlyReplica: 13,
ReadOnlyReplica: 14,
ResigningLeader: 15,
});
function ClusterInfo(members) {
this._members = members.map(member => new MemberInfo(member));
Object.defineProperty(this, 'bestNode', {
enumerable: true,
get: function () {
return this._getBestNode();
}
});
}
ClusterInfo.prototype._getBestNode = function () {
return this._members
.filter(member => member.isAlive && member.isAllowedToConnect)
.sort(function (a, b) {
return VNodeStates[b.state] - VNodeStates[a.state];
})[0];
}
module.exports = ClusterInfo;

View File

@ -0,0 +1,73 @@
const NOT_ALLOWED_STATES = [
'Manager',
'ShuttingDown',
'Shutdown'
];
function MemberInfo(informations) {
this._instanceId = informations.instanceId;
this._timeStamp = informations.timeStamp;
this._state = informations.state;
this._isAlive = informations.isAlive;
this._internalTcpIp = informations.internalTcpIp;
this._internalTcpPort = informations.internalTcpPort;
this._internalSecureTcpPort = informations.internalSecureTcpPort;
this._externalTcpIp = informations.externalTcpIp;
this._externalTcpPort = informations.externalTcpPort;
this._externalSecureTcpPort = informations.externalSecureTcpPort;
this._internalHttpIp = informations.internalHttpIp;
this._internalHttpPort = informations.internalHttpPort;
this._externalHttpIp = informations.externalHttpIp;
this._externalHttpPort = informations.externalHttpPort;
this._lastCommitPosition = informations.lastCommitPosition;
this._writerCheckpoint = informations.writerCheckpoint;
this._chaserCheckpoint = informations.chaserCheckpoint;
this._epochPosition = informations.epochPosition;
this._epochNumber = informations.epochNumber;
this._epochId = informations.epochId;
this._nodePriority = informations.nodePriority;
Object.defineProperty(this, 'state', {
enumerable: true,
get: function () {
return this._state;
}
});
Object.defineProperty(this, 'isAllowedToConnect', {
enumerable: true,
get: function () {
return !NOT_ALLOWED_STATES.includes(this._state);
}
});
Object.defineProperty(this, 'isAlive', {
enumerable: true,
get: function () {
return this._isAlive;
}
});
Object.defineProperty(this, 'externalTcpIp', {
enumerable: true,
get: function () {
return this._externalTcpIp;
}
});
Object.defineProperty(this, 'externalTcpPort', {
enumerable: true,
get: function () {
return this._externalTcpPort;
}
});
Object.defineProperty(this, 'externalSecureTcpPort', {
enumerable: true,
get: function () {
return this._externalSecureTcpPort;
}
});
}
module.exports = MemberInfo;

View File

@ -0,0 +1,23 @@
function NodeEndPoints(tcpEndPoint, secureTcpEndPoint) {
if (tcpEndPoint === null && secureTcpEndPoint === null) throw new Error('Both endpoints are null.');
Object.defineProperties(this, {
tcpEndPoint: {
enumerable: true,
value: tcpEndPoint
},
secureTcpEndPoint: {
enumerable: true,
value: secureTcpEndPoint
}
});
}
NodeEndPoints.createFromGossipMember = function (member) {
const normTcp = { host: member.externalTcpIp, port: member.externalTcpPort };
const secTcp = member.externalSecureTcpPort > 0
? { host: member.externalTcpIp, port: member.externalSecureTcpPort }
: null;
return new NodeEndPoints(normTcp, secTcp);
}
module.exports = NodeEndPoints

View File

@ -9,42 +9,48 @@ var SubscriptionsManager = require('./subscriptionsManager');
var VolatileSubscriptionOperation = require('../clientOperations/volatileSubscriptionOperation');
var ConnectToPersistentSubscriptionOperation = require('../clientOperations/connectToPersistentSubscriptionOperation');
var messages = require('./messages');
var ClientMessage = require('../messages/clientMessage');
var createBufferSegment = require('../common/bufferSegment');
var TcpPackage = require('../systemData/tcpPackage');
var TcpCommand = require('../systemData/tcpCommand');
var TcpFlags = require('../systemData/tcpFlags');
var InspectionDecision = require('../systemData/inspectionDecision');
const ConnectionState = {
const ConnectionState = Object.freeze({
Init: 'init',
Connecting: 'connecting',
Connected: 'connected',
Closed: 'closed'
};
});
const ConnectingPhase = {
const ConnectingPhase = Object.freeze({
Invalid: 'invalid',
Reconnecting: 'reconnecting',
EndPointDiscovery: 'endpointDiscovery',
ConnectionEstablishing: 'connectionEstablishing',
Authentication: 'authentication',
Identification: 'identification',
Connected: 'connected'
};
});
const TimerPeriod = 200;
const TimerTickMessage = new messages.TimerTickMessage();
const EmptyGuid = '00000000-0000-0000-0000-000000000000';
const ClientVersion = 1;
/**
* @private
* @param {EventStoreNodeConnection} esConnection
* @param {Object} settings
* @constructor
* @property {Number} totalOperationCount
*/
function EventStoreConnectionLogicHandler(esConnection, settings) {
EventEmitter.call(this);
this._esConnection = esConnection;
this._settings = settings;
this._queue = new SimpleQueuedHandler();
this._queue = new SimpleQueuedHandler(this._settings.log);
this._state = ConnectionState.Init;
this._connectingPhase = ConnectingPhase.Invalid;
this._endpointDiscoverer = null;
@ -165,7 +171,7 @@ EventStoreConnectionLogicHandler.prototype._startConnection = function(cb, endpo
* @private
*/
EventStoreConnectionLogicHandler.prototype._closeConnection = function(reason, error) {
if (this._state == ConnectionState.Closed) {
if (this._state === ConnectionState.Closed) {
this._logDebug("CloseConnection IGNORED because is ESConnection is CLOSED, reason %s, error %s.", reason, error ? error.stack : '');
return;
}
@ -181,15 +187,14 @@ EventStoreConnectionLogicHandler.prototype._closeConnection = function(reason, e
this._logInfo("Closed. Reason: %s", reason);
if (error)
this.emit('error', error);
if (error) this.emit('error', error);
this.emit('closed', reason);
};
EventStoreConnectionLogicHandler.prototype._closeTcpConnection = function(reason) {
if (!this._connection) {
this._logDebug("CloseTcpConnection IGNORED because _connection == null");
this._logDebug("CloseTcpConnection IGNORED because _connection === null");
return;
}
@ -199,7 +204,7 @@ EventStoreConnectionLogicHandler.prototype._closeTcpConnection = function(reason
this._connection = null;
};
var _nextSeqNo = -1;
var _nextSeqNo = 0;
function createOperationItem(operation, maxRetries, timeout) {
var operationItem = {
seqNo: _nextSeqNo++,
@ -277,10 +282,11 @@ EventStoreConnectionLogicHandler.prototype._startSubscription = function(msg) {
this._state === ConnectionState.Connected ? "fire" : "enqueue",
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
if (this._state === ConnectionState.Connecting)
if (this._state === ConnectionState.Connecting) {
this._subscriptions.enqueueSubscription(subscription);
else
} else {
this._subscriptions.startSubscription(subscription, this._connection);
}
break;
case ConnectionState.Closed:
msg.cb(new Error("Connection closed. Connection: " + this._esConnection.connectionName));
@ -306,10 +312,11 @@ EventStoreConnectionLogicHandler.prototype._startPersistentSubscription = functi
this._state === ConnectionState.Connected ? "fire" : "enqueue",
operation.constructor.name, operation, msg.maxRetries, msg.timeout);
var subscription = createSubscriptionItem(operation, msg.maxRetries, msg.timeout);
if (this._state === ConnectionState.Connecting)
if (this._state === ConnectionState.Connecting) {
this._subscriptions.enqueueSubscription(subscription);
else
} else {
this._subscriptions.startSubscription(subscription, this._connection);
}
break;
case ConnectionState.Closed:
msg.cb(new Error("Connection closed. " + this._esConnection.connectionName));
@ -320,7 +327,7 @@ EventStoreConnectionLogicHandler.prototype._startPersistentSubscription = functi
EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(endPoints) {
var endPoint = this._settings.useSslConnection ? endPoints.secureTcpEndPoint : endPoints.tcpEndPoint;
if (endPoint == null)
if (endPoint === null)
{
this._closeConnection("No end point to node specified.");
return;
@ -328,8 +335,8 @@ EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(en
this._logDebug("EstablishTcpConnection to [%j]", endPoint);
if (this._state != ConnectionState.Connecting) return;
if (this._connectingPhase != ConnectingPhase.EndPointDiscovery) return;
if (this._state !== ConnectionState.Connecting) return;
if (this._connectingPhase !== ConnectingPhase.EndPointDiscovery) return;
var self = this;
this._connectingPhase = ConnectingPhase.ConnectionEstablishing;
@ -358,10 +365,10 @@ EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(en
};
EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(connection) {
if (this._state != ConnectionState.Connecting || !this._connection.equals(connection) || connection.isClosed)
if (this._state !== ConnectionState.Connecting || !this._connection || !this._connection.equals(connection) || connection.isClosed)
{
this._logDebug("IGNORED (_state %s, _conn.Id %s, conn.Id %s, conn.closed %s): TCP connection to [%j, L%j] established.",
this._state, this._connection == null ? EmptyGuid : this._connection.connectionId, connection.connectionId,
this._state, this._connection === null ? EmptyGuid : this._connection.connectionId, connection.connectionId,
connection.isClosed, connection.remoteEndPoint, connection.localEndPoint);
return;
}
@ -373,7 +380,7 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(
timeStamp: Date.now()
};
if (this._settings.defaultUserCredentials != null)
if (this._settings.defaultUserCredentials !== null)
{
this._connectingPhase = ConnectingPhase.Authentication;
@ -390,10 +397,21 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(
}
else
{
this._goToConnectedState();
this._goToIdentifiedState();
}
};
EventStoreConnectionLogicHandler.prototype._goToIdentifiedState = function() {
this._connectingPhase = ConnectingPhase.Identification;
this._identityInfo = {
correlationId: uuid.v4(),
timeStamp: Date.now()
};
var dto = new ClientMessage.IdentifyClient({version: ClientVersion, connectionName: this._esConnection.connectionName});
var buf = dto.constructor.encode(dto).finish();
this._connection.enqueueSend(new TcpPackage(TcpCommand.IdentifyClient, TcpFlags.None, this._identityInfo.correlationId, null, null, createBufferSegment(buf)))
};
EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
this._state = ConnectionState.Connected;
this._connectingPhase = ConnectingPhase.Connected;
@ -402,7 +420,7 @@ EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
this.emit('connected', this._connection.remoteEndPoint);
if (Date.now() - this._lastTimeoutsTimeStamp >= this._settings.operationTimeoutCheckPeriod)
if ((Date.now() - this._lastTimeoutsTimeStamp) >= this._settings.operationTimeoutCheckPeriod)
{
this._operations.checkTimeoutsAndRetry(this._connection);
this._subscriptions.checkTimeoutsAndRetry(this._connection);
@ -411,19 +429,19 @@ EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
};
EventStoreConnectionLogicHandler.prototype._tcpConnectionError = function(connection, error) {
if (this._connection != connection) return;
if (this._state == ConnectionState.Closed) return;
if (!this._connection || !this._connection.equals(connection)) return;
if (this._state === ConnectionState.Closed) return;
this._logDebug("TcpConnectionError connId %s, exc %s.", connection.connectionId, error);
this._closeConnection("TCP connection error occurred.", error);
};
EventStoreConnectionLogicHandler.prototype._tcpConnectionClosed = function(connection, error) {
if (this._state == ConnectionState.Init) throw new Error();
if (this._state == ConnectionState.Closed || !this._connection.equals(connection))
if (this._state === ConnectionState.Init) throw new Error();
if (this._state === ConnectionState.Closed || !this._connection || !this._connection.equals(connection))
{
this._logDebug("IGNORED (_state: %s, _conn.ID: %s, conn.ID: %s): TCP connection to [%j, L%j] closed.",
this._state, this._connection == null ? EmptyGuid : this._connection.connectionId, connection.connectionId,
this._state, this._connection === null ? EmptyGuid : this._connection.connectionId, connection.connectionId,
connection.remoteEndPoint, connection.localEndPoint);
return;
}
@ -447,7 +465,7 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionClosed = function(conne
};
EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connection, pkg) {
if (!connection.equals(this._connection) || this._state == ConnectionState.Closed || this._state == ConnectionState.Init)
if (!connection.equals(this._connection) || this._state === ConnectionState.Closed || this._state === ConnectionState.Init)
{
this._logDebug("IGNORED: HandleTcpPackage connId %s, package %s, %s.",
connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId);
@ -458,9 +476,28 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
this._connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId);
this._packageNumber += 1;
if (pkg.command == TcpCommand.HeartbeatResponseCommand)
if (pkg.command === TcpCommand.HeartbeatResponseCommand)
{
if (pkg.correlationId === this._heartbeatInfo.correlationId)
{
var now = Date.now();
var heartbeatEvent = {
connectionId: this._connection.connectionId,
remoteEndPoint: this._connection.remoteEndPoint,
requestSentAt: this._heartbeatInfo.timeStamp,
requestPkgNumber: this._heartbeatInfo.lastPackageNumber,
responseReceivedAt: now,
responsePkgNumber: this._packageNumber
};
try {
this.emit('heartbeatInfo', heartbeatEvent);
} catch(e) {
this._logDebug("IGNORED: emit heartbeat event failed.\n%s", e.stack);
}
}
return;
if (pkg.command == TcpCommand.HeartbeatRequestCommand)
}
if (pkg.command === TcpCommand.HeartbeatRequestCommand)
{
this._connection.enqueueSend(new TcpPackage(
TcpCommand.HeartbeatResponseCommand,
@ -469,21 +506,31 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
return;
}
if (pkg.command == TcpCommand.Authenticated || pkg.command == TcpCommand.NotAuthenticated)
if (pkg.command === TcpCommand.Authenticated || pkg.command === TcpCommand.NotAuthenticated)
{
if (this._state == ConnectionState.Connecting
&& this._connectingPhase == ConnectingPhase.Authentication
&& this._authInfo.correlationId == pkg.correlationId)
if (this._state === ConnectionState.Connecting
&& this._connectingPhase === ConnectingPhase.Authentication
&& this._authInfo.correlationId === pkg.correlationId)
{
if (pkg.command == TcpCommand.NotAuthenticated)
if (pkg.command === TcpCommand.NotAuthenticated)
this.emit('authenticationFailed', "Not authenticated");
this._goToIdentifiedState();
return;
}
}
if (pkg.command === TcpCommand.ClientIdentified)
{
if (this._state === ConnectionState.Connecting
&& this._identityInfo.correlationId === pkg.correlationId)
{
this._goToConnectedState();
return;
}
}
if (pkg.command == TcpCommand.BadRequest && pkg.correlationId == EmptyGuid)
if (pkg.command === TcpCommand.BadRequest && pkg.correlationId === EmptyGuid)
{
var message = "<no message>";
try {
@ -515,8 +562,9 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
default:
throw new Error("Unknown InspectionDecision: " + result.decision);
}
if (this._state == ConnectionState.Connected)
if (this._state === ConnectionState.Connected) {
this._operations.scheduleWaitingOperations(connection);
}
return;
}
@ -557,14 +605,13 @@ EventStoreConnectionLogicHandler.prototype._reconnectTo = function(endPoints) {
var endPoint = this._settings.useSslConnection
? endPoints.secureTcpEndPoint
: endPoints.tcpEndPoint;
if (endPoint == null)
if (endPoint === null)
{
this._closeConnection("No end point is specified while trying to reconnect.");
return;
}
if (this._state != ConnectionState.Connected || this._connection.remoteEndPoint == endPoint)
return;
if (this._state !== ConnectionState.Connected || this._connection.remoteEndPoint === endPoint) return;
var msg = util.format("EventStoreConnection '%s': going to reconnect to [%j]. Current endpoint: [%j, L%j].",
this._esConnection.connectionName, endPoint, this._connection.remoteEndPoint, this._connection.localEndPoint);
@ -581,30 +628,30 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
{
case ConnectionState.Init: break;
case ConnectionState.Connecting:
if (this._connectingPhase == ConnectingPhase.Reconnecting && Date.now() - this._reconnInfo.timeStamp >= this._settings.reconnectionDelay)
{
if (this._connectingPhase === ConnectingPhase.Reconnecting && (Date.now() - this._reconnInfo.timeStamp) >= this._settings.reconnectionDelay) {
this._logDebug("TimerTick checking reconnection...");
this._reconnInfo = {reconnectionAttempt: this._reconnInfo.reconnectionAttempt + 1, timeStamp: Date.now()};
if (this._settings.maxReconnections >= 0 && this._reconnInfo.reconnectionAttempt > this._settings.maxReconnections)
if (this._settings.maxReconnections >= 0 && this._reconnInfo.reconnectionAttempt > this._settings.maxReconnections) {
this._closeConnection("Reconnection limit reached.");
else
{
} else {
this.emit('reconnecting', {});
this._discoverEndpoint(null);
}
}
else if (this._connectingPhase == ConnectingPhase.Authentication && Date.now() - this._authInfo.timeStamp >= this._settings.operationTimeout)
{
} else if (this._connectingPhase === ConnectingPhase.Authentication && (Date.now() - this._authInfo.timeStamp) >= this._settings.operationTimeout) {
this.emit('authenticationFailed', "Authentication timed out.");
this._goToConnectedState();
}
else if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing)
if (this._clientVersion === 1) {
this._goToIdentifiedState();
} else {
this._goToConnectedState();
}
} else if (this._connectingPhase === ConnectingPhase.Authentication || this._connectingPhase === ConnectingPhase.Connected) {
this._manageHeartbeats();
}
break;
case ConnectionState.Connected:
// operations timeouts are checked only if connection is established and check period time passed
if (Date.now() - this._lastTimeoutsTimeStamp >= this._settings.operationTimeoutCheckPeriod)
if ((Date.now() - this._lastTimeoutsTimeStamp) >= this._settings.operationTimeoutCheckPeriod)
{
// On mono even impossible connection first says that it is established
// so clearing of reconnection count on ConnectionEstablished event causes infinite reconnections.
@ -624,14 +671,13 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
};
EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
if (this._connection == null) throw new Error();
if (this._connection === null) return;
var timeout = this._heartbeatInfo.isIntervalStage ? this._settings.heartbeatInterval : this._settings.heartbeatTimeout;
if (Date.now() - this._heartbeatInfo.timeStamp < timeout)
return;
if ((Date.now() - this._heartbeatInfo.timeStamp) < timeout) return;
var packageNumber = this._packageNumber;
if (this._heartbeatInfo.lastPackageNumber != packageNumber)
if (this._heartbeatInfo.lastPackageNumber !== packageNumber)
{
this._heartbeatInfo = {lastPackageNumber: packageNumber, isIntervalStage: true, timeStamp: Date.now()};
return;
@ -639,12 +685,13 @@ EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
if (this._heartbeatInfo.isIntervalStage)
{
var correlationId = uuid.v4();
// TcpMessage.Heartbeat analog
this._connection.enqueueSend(new TcpPackage(
TcpCommand.HeartbeatRequestCommand,
TcpFlags.None,
uuid.v4()));
this._heartbeatInfo = {lastPackageNumber: this._heartbeatInfo.lastPackageNumber, isIntervalStage: false, timeStamp: Date.now()};
correlationId));
this._heartbeatInfo = {correlationId: correlationId, lastPackageNumber: this._heartbeatInfo.lastPackageNumber, isIntervalStage: false, timeStamp: Date.now()};
}
else
{
@ -660,17 +707,19 @@ EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
EventStoreConnectionLogicHandler.prototype._logDebug = function(message) {
if (!this._settings.verboseLogging) return;
if (arguments.length > 1)
message = util.format.apply(util, Array.prototype.slice.call(arguments));
if (arguments.length > 1) {
message = util.format.apply(util, Array.prototype.slice.call(arguments));
}
this._settings.log.debug("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
};
EventStoreConnectionLogicHandler.prototype._logInfo = function(message){
if (arguments.length > 1)
if (arguments.length > 1) {
message = util.format.apply(util, Array.prototype.slice.call(arguments));
}
this._settings.log.info("EventStoreConnection '%s': %s", this._esConnection.connectionName, message);
};
module.exports = EventStoreConnectionLogicHandler;
module.exports = EventStoreConnectionLogicHandler;

View File

@ -1,5 +1,4 @@
var util = require('util');
var ensure = require('../common/utils/ensure');
function Message() {
}
@ -41,6 +40,7 @@ function StartSubscriptionMessage(
util.inherits(StartSubscriptionMessage, Message);
/**
* @private
* @constructor
* @property {object} endPoints
* @property {object} endPoints.secureTcpEndPoint

View File

@ -5,6 +5,7 @@ var Hash = require('../common/hash');
var TcpCommand = require('../systemData/tcpCommand');
/**
* @private
* @param {string} connectionName
* @param {object} settings
* @constructor
@ -29,17 +30,20 @@ OperationsManager.prototype.getActiveOperation = function(correlationId) {
return this._activeOperations.get(correlationId);
};
OperationsManager.prototype.cleanUp = function() {
var connectionClosedError = new Error(util.format("Connection '%s' was closed.", this._connectionName));
function cleanUpError(connName, state, operation) {
return new Error(util.format("Connection '%s' was closed. %s %s.", connName, state, operation.toString()));
}
OperationsManager.prototype.cleanUp = function() {
var self = this;
this._activeOperations.forEach(function(correlationId, operation){
operation.operation.fail(connectionClosedError);
operation.operation.fail(cleanUpError(self._connectionName, 'Active', operation));
});
this._waitingOperations.forEach(function(operation) {
operation.operation.fail(connectionClosedError);
operation.operation.fail(cleanUpError(self._connectionName, 'Waiting', operation));
});
this._retryPendingOperations.forEach(function(operation) {
operation.operation.fail(connectionClosedError);
operation.operation.fail(cleanUpError(self._connectionName, 'Pending', operation));
});
this._activeOperations.clear();
@ -55,7 +59,7 @@ OperationsManager.prototype.checkTimeoutsAndRetry = function(connection) {
var removeOperations = [];
var self = this;
this._activeOperations.forEach(function(correlationId, operation) {
if (operation.connectionId != connection.connectionId)
if (operation.connectionId !== connection.connectionId)
{
retryOperations.push(operation);
}
@ -106,8 +110,7 @@ OperationsManager.prototype.checkTimeoutsAndRetry = function(connection) {
};
OperationsManager.prototype.scheduleOperationRetry = function(operation) {
if (!this.removeOperation(operation))
return;
if (!this.removeOperation(operation)) return;
this._logDebug("ScheduleOperationRetry for %s.", operation);
if (operation.maxRetries >= 0 && operation.retryCount >= operation.maxRetries)
@ -120,7 +123,7 @@ OperationsManager.prototype.scheduleOperationRetry = function(operation) {
};
OperationsManager.prototype.removeOperation = function(operation) {
this._activeOperations.remove(operation.connectionId);
this._activeOperations.remove(operation.correlationId);
this._logDebug("RemoveOperation SUCCEEDED for %s.", operation);
this._totalOperationCount = this._activeOperations.length + this._waitingOperations.length;
return true;
@ -162,10 +165,11 @@ OperationsManager.prototype.scheduleOperation = function(operation, connection)
OperationsManager.prototype._logDebug = function(message) {
if (!this._settings.verboseLogging) return;
if (arguments.length > 1)
if (arguments.length > 1) {
message = util.format.apply(util, Array.prototype.slice.call(arguments));
}
this._settings.log.debug("EventStoreConnection '%s': %s.", this._connectionName, message);
};
module.exports = OperationsManager;
module.exports = OperationsManager;

View File

@ -1,20 +1,26 @@
function typeName(t) {
if (typeof t === 'function')
return t.name;
if (typeof t === 'object')
return t.constructor.name;
if (typeof t === 'function') return t.name;
if (typeof t === 'object') return t.constructor.name;
throw new TypeError('type must be a function or object, not ' + typeof t);
}
function SimpleQueuedHandler() {
function SimpleQueuedHandler(log) {
this._handlers = {};
this._messages = [];
this._isProcessing = false;
this._log = log;
}
SimpleQueuedHandler.prototype.registerHandler = function(type, handler) {
type = typeName(type);
this._handlers[type] = handler;
var typeId = typeName(type);
var log = this._log;
this._handlers[typeId] = function (msg) {
try {
handler(msg);
} catch(e) {
log.error('handle for', type, 'failed:', e.stack);
}
};
};
SimpleQueuedHandler.prototype.enqueueMessage = function(msg) {
@ -28,14 +34,13 @@ SimpleQueuedHandler.prototype.enqueueMessage = function(msg) {
SimpleQueuedHandler.prototype._processQueue = function() {
var message = this._messages.shift();
while(message) {
var type = typeName(message);
var handler = this._handlers[type];
if (!handler)
throw new Error("No handler registered for message " + type);
var typeId = typeName(message);
var handler = this._handlers[typeId];
if (!handler) throw new Error("No handler registered for message " + typeId);
setImmediate(handler, message);
message = this._messages.shift();
}
this._isProcessing = false;
};
module.exports = SimpleQueuedHandler;
module.exports = SimpleQueuedHandler;

View File

@ -38,11 +38,11 @@ SubscriptionsManager.prototype.cleanUp = function() {
this._retryPendingSubscriptions = [];
};
SubscriptionsManager.prototype.purgeSubscribedAndDroppedSubscriptions = function() {
SubscriptionsManager.prototype.purgeSubscribedAndDroppedSubscriptions = function(connectionId) {
var self = this;
var subscriptionsToRemove = [];
this._activeSubscriptions.forEach(function(_, subscription) {
if (subscription.isSubscribed && subscription.connectionId == connectionId) {
if (subscription.isSubscribed && subscription.connectionId === connectionId) {
subscription.operation.connectionClosed();
subscriptionsToRemove.push(subscription);
}
@ -60,7 +60,7 @@ SubscriptionsManager.prototype.checkTimeoutsAndRetry = function(connection) {
var removeSubscriptions = [];
this._activeSubscriptions.forEach(function(_, subscription) {
if (subscription.isSubscribed) return;
if (subscription.connectionId != connection.connectionId)
if (subscription.connectionId !== connection.connectionId)
{
retrySubscriptions.push(subscription);
}
@ -165,7 +165,7 @@ SubscriptionsManager.prototype._logDebug = function(message) {
if (!this._settings.verboseLogging) return;
var parameters = Array.prototype.slice.call(arguments, 1);
this._settings.log.debug("EventStoreConnection '%s': %s.", this._connectionName, parameters.length == 0 ? message : util.format(message, parameters));
this._settings.log.debug("EventStoreConnection '%s': %s.", this._connectionName, parameters.length === 0 ? message : util.format(message, parameters));
};
module.exports = SubscriptionsManager;

View File

@ -1,11 +1,23 @@
var util = require('util');
var Long = require('long');
function AccessDeniedError(action, stream) {
function AccessDeniedError(action, streamOrTransactionId) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = util.format("%s access denied for stream '%s'.", action, stream);
this.action = action;
this.stream = stream;
if (typeof streamOrTransactionId === 'string') {
this.message = util.format("%s access denied for stream '%s'.", action, streamOrTransactionId);
this.stream = streamOrTransactionId;
Object.freeze(this);
return;
}
if (Long.isLong(streamOrTransactionId)) {
this.message = util.format("%s access denied for transaction %s.", action, streamOrTransactionId);
this.transactionId = streamOrTransactionId;
Object.freeze(this);
return;
}
throw new TypeError("second argument must be a stream name or transaction Id.");
}
util.inherits(AccessDeniedError, Error);

View File

@ -0,0 +1,10 @@
const util = require('util');
function ProjectionCommandFailedError(httpStatusCode, message) {
Error.captureStackTrace(this, this.constructor);
this.httpStatusCode = httpStatusCode;
this.message = message;
}
util.inherits(ProjectionCommandFailedError, Error);
module.exports = ProjectionCommandFailedError;

View File

@ -1,10 +1,22 @@
var util = require('util');
var Long = require('long');
function StreamDeletedError(stream) {
function StreamDeletedError(streamOrTransactionId) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = util.format("Event stream '%s' is deleted.", stream);
this.stream = stream;
if (typeof streamOrTransactionId === 'string') {
this.message = util.format("Event stream '%s' is deleted.", streamOrTransactionId);
this.stream = streamOrTransactionId;
Object.freeze(this);
return;
}
if (Long.isLong(streamOrTransactionId)) {
this.message = util.format("Stream is deleted for transaction %s.", streamOrTransactionId);
this.transactionId = streamOrTransactionId;
Object.freeze(this);
return;
}
throw new TypeError("second argument must be a stream name or transaction Id.");
}
util.inherits(StreamDeletedError, Error);

View File

@ -1,12 +1,24 @@
var util = require('util');
var Long = require('long');
function WrongExpectedVersionError(action, stream, expectedVersion) {
function WrongExpectedVersionError(action, streamOrTransactionId, expectedVersion) {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.message = util.format("%s failed due to WrongExpectedVersion. Stream: %s Expected version: %d.", action, stream, expectedVersion);
this.action = action;
this.stream = stream;
this.expectedVersion = expectedVersion;
if (typeof streamOrTransactionId === 'string') {
this.message = util.format("%s failed due to WrongExpectedVersion. Stream: %s Expected version: %d.", action, streamOrTransactionId, expectedVersion);
this.stream = streamOrTransactionId;
this.expectedVersion = expectedVersion;
Object.freeze(this);
return;
}
if (Long.isLong(streamOrTransactionId)) {
this.message = util.format("%s transaction failed due to WrongExpectedVersion. Transaction Id: %s.", action, streamOrTransactionId);
this.transactionId = streamOrTransactionId;
Object.freeze(this);
return;
}
throw new TypeError("second argument must be a stream name or a transaction Id.");
}
util.inherits(WrongExpectedVersionError, Error);

View File

@ -1,17 +1,12 @@
var uuid = require('uuid');
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function isValidId(id) {
if (typeof id !== 'string') return false;
var buf = uuid.parse(id);
var valid = false;
for(var i=0;i<buf.length;i++)
if (buf[i] !== 0)
valid = true;
return valid;
return uuidRegex.test(id);
}
/**
* Create an EventData
* @private
* @param {string} eventId
* @param {string} type
* @param {boolean} [isJson]
@ -29,8 +24,9 @@ function EventData(eventId, type, isJson, data, metadata) {
this.eventId = eventId;
this.type = type;
this.isJson = isJson || false;
this.data = data || new Buffer(0);
this.metadata = metadata || new Buffer(0);
this.data = data || Buffer.alloc(0);
this.metadata = metadata || Buffer.alloc(0);
Object.freeze(this);
}
module.exports = EventData;

View File

@ -24,14 +24,10 @@ EventStoreAllCatchUpSubscription.prototype._readEventsTill = function(
var self = this;
function processEvents(events, index) {
index = index || 0;
if (index >= events.length) return Promise.resolve();
if (events[index].originalPosition === null) throw new Error("Subscription event came up with no OriginalPosition.");
return new Promise(function(resolve, reject) {
self._tryProcess(events[index]);
resolve();
})
return self._tryProcess(events[index])
.then(function() {
return processEvents(events, index + 1);
});
@ -40,20 +36,16 @@ EventStoreAllCatchUpSubscription.prototype._readEventsTill = function(
function readNext() {
return connection.readAllEventsForward(self._nextReadPosition, self.readBatchSize, resolveLinkTos, userCredentials)
.then(function(slice) {
return processEvents(slice.events)
return processEvents(slice.events, 0)
.then(function() {
self._nextReadPosition = slice.nextPosition;
var done = lastCommitPosition === null
return (lastCommitPosition === null)
? slice.isEndOfStream
: slice.nextPosition.compareTo(new results.Position(lastCommitPosition, lastCommitPosition)) >= 0;
if (!done && slice.isEndOfStream)
return Promise.resolve(done).delay(10);
return Promise.resolve(done);
});
})
.then(function(done) {
if (done || self._shouldStop)
return;
if (done || self._shouldStop) return;
return readNext();
});
}
@ -69,16 +61,19 @@ EventStoreAllCatchUpSubscription.prototype._readEventsTill = function(
EventStoreAllCatchUpSubscription.prototype._tryProcess = function(e) {
var processed = false;
var promise;
if (e.originalPosition.compareTo(this._lastProcessedPosition) > 0)
{
this._eventAppeared(this, e);
promise = this._eventAppeared(this, e);
this._lastProcessedPosition = e.originalPosition;
processed = true;
}
if (this._verbose)
if (this._verbose) {
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %s).",
this.streamId || '<all>', processed ? "processed" : "skipping",
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalPosition);
this.streamId || '<all>', processed ? "processed" : "skipping",
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalPosition);
}
return (promise && promise.then) ? promise : Promise.resolve();
};
module.exports = EventStoreAllCatchUpSubscription;

View File

@ -1,7 +1,6 @@
var util = require('util');
var SubscriptionDropReason = require('./subscriptionDropReason');
var results = require('./results');
const DefaultReadBatchSize = 500;
const DefaultMaxPushQueueSize = 10000;
@ -68,9 +67,9 @@ function EventStoreCatchUpSubscription(
var self = this;
this._onReconnect = function() {
if (self._verbose) self._log.debug("Catch-up Subscription to %s: recovering after reconnection.", self._streamId || '<all>');
if (self._verbose) self._log.debug("Catch-up Subscription to %s: unhooking from connection.Connected.", self._streamId || '<all>');
self._connection.removeListener('connected', self._onReconnect);
if (self._verbose) self._log.debug("Catch-up Subscription to %s: recovering after reconnection.", self._streamId || '<all>');
self._runSubscription();
}
}
@ -128,15 +127,18 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
var self = this;
this._stopped = false;
this._isDropped = false;
this._dropData = null;
if (this._verbose) this._log.debug("Catch-up Subscription to %s: pulling events...", logStreamName);
this._readEventsTill(this._connection, this._resolveLinkTos, this._userCredentials, null, null)
.then(function() {
if (self._shouldStop) return;
if (self._verbose) self._log.debug("Catch-up Subscription to %s: subscribing...", logStreamName);
if (self._streamId === '')
if (self._streamId === '') {
return self._connection.subscribeToAll(self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
else
} else {
return self._connection.subscribeToStream(self._streamId, self._resolveLinkTos, self._enqueuePushedEvent.bind(self), self._serverSubscriptionDropped.bind(self), self._userCredentials);
}
})
.then(function(subscription) {
if (subscription === undefined) return;
@ -155,12 +157,13 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
return;
}
if (self._verbose) self._log.debug("Catch-up Subscription to %s: processing live events...", logStreamName);
if (self._liveProcessingStarted)
if (self._liveProcessingStarted) {
try {
self._liveProcessingStarted(self);
} catch(e) {
} catch (e) {
self._log.error(e, "Catch-up Subscription to %s: liveProcessingStarted callback failed.", logStreamName);
}
}
if (self._verbose) self._log.debug("Catch-up Subscription to %s: hooking to connection.Connected", logStreamName);
self._connection.on('connected', self._onReconnect);
self._allowProcessing = true;
@ -169,10 +172,11 @@ EventStoreCatchUpSubscription.prototype._runSubscription = function() {
};
EventStoreCatchUpSubscription.prototype._enqueuePushedEvent = function(subscription, e) {
if (this._verbose)
if (this._verbose) {
this._log.debug("Catch-up Subscription to %s: event appeared (%s, %d, %s @ %s).",
this._streamId || '<all>',
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
this._streamId || '<all>',
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
}
if (this._liveQueue.length >= this.maxPushQueueSize)
{
@ -183,8 +187,7 @@ EventStoreCatchUpSubscription.prototype._enqueuePushedEvent = function(subscript
this._liveQueue.push(e);
if (this._allowProcessing)
this._ensureProcessingPushQueue();
if (this._allowProcessing) this._ensureProcessingPushQueue();
};
EventStoreCatchUpSubscription.prototype._serverSubscriptionDropped = function(subscription, reason, err) {
@ -196,8 +199,7 @@ EventStoreCatchUpSubscription.prototype._enqueueSubscriptionDropNotification = f
if (this._dropData) return;
this._dropData = {reason: reason, error: error};
this._liveQueue.push(new DropSubscriptionEvent());
if (this._allowProcessing)
this._ensureProcessingPushQueue();
if (this._allowProcessing) this._ensureProcessingPushQueue();
};
EventStoreCatchUpSubscription.prototype._ensureProcessingPushQueue = function() {
@ -209,44 +211,54 @@ EventStoreCatchUpSubscription.prototype._ensureProcessingPushQueue = function()
EventStoreCatchUpSubscription.prototype._processLiveQueue = function() {
var ev = this._liveQueue.shift();
//TODO: possible blocking while, use when
while(ev) {
if (ev instanceof DropSubscriptionEvent) {
if (!this._dropData) this._dropData = {reason: SubscriptionDropReason.Unknown, error: new Error("Drop reason not specified.")};
this._dropSubscription(this._dropData.reason, this._dropData.error);
this._isProcessing = false;
return;
}
try {
this._tryProcess(ev);
}
catch(err) {
this._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
return;
}
ev = this._liveQueue.shift();
if (!ev) {
this._isProcessing = false;
return;
}
if (ev instanceof DropSubscriptionEvent) {
if (!this._dropData) this._dropData = {reason: SubscriptionDropReason.Unknown, error: new Error("Drop reason not specified.")};
this._dropSubscription(this._dropData.reason, this._dropData.error);
this._isProcessing = false;
return;
}
var promise;
try {
promise = this._tryProcess(ev);
}
catch(err) {
this._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
this._isProcessing = false;
return;
}
if (promise && promise.then) {
var self = this;
promise
.then(this._processLiveQueue.bind(this), function(err) {
self._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
self._isProcessing = false;
});
} else {
setImmediate(this._processLiveQueue.bind(this));
}
this._isProcessing = false;
};
EventStoreCatchUpSubscription.prototype._dropSubscription = function(reason, error) {
if (this._isDropped) return;
this._isDropped = true;
if (this._verbose)
if (this._verbose) {
this._log.debug("Catch-up Subscription to %s: dropping subscription, reason: %s %s.",
this._streamId || '<all>', reason, error);
this._streamId || '<all>', reason, error);
}
if (this._subscription)
this._subscription.unsubscribe();
if (this._subscriptionDropped)
if (this._subscription) this._subscription.unsubscribe();
if (this._subscriptionDropped) {
try {
this._subscriptionDropped(this, reason, error);
} catch(e) {
} catch (e) {
this._log.error(e, "Catch-up Subscription to %s: subscriptionDropped callback failed.", this._streamId || '<all>');
}
}
this._stopped = true;
};

View File

@ -1,8 +1,21 @@
var EventStoreNodeConnection = require('./eventStoreNodeConnection');
var StaticEndpointDiscoverer = require('./core/staticEndpointDiscoverer');
var ClusterDiscoverer = require('./core/cluster/clusterDiscoverer');
var NoopLogger = require('./common/log/noopLogger');
var ensure = require('./common/utils/ensure');
var defaultConnectionSettings = {
const util = require('util');
const http = require('http');
const https = require('https');
const dns = require('dns');
const dnsService = {
lookup : util.promisify(dns.lookup),
ADDRCONFIG: dns.ADDRCONFIG,
V4MAPPED: dns.V4MAPPED
};
var defaultConnectionSettings = Object.freeze({
log: new NoopLogger(),
verboseLogging: false,
@ -25,8 +38,15 @@ var defaultConnectionSettings = {
failOnNoServerResponse: false,
heartbeatInterval: 750,
heartbeatTimeout: 1500,
clientConnectionTimeout: 1000
};
clientConnectionTimeout: 1000,
// Cluster Settings
clusterDns: '',
maxDiscoverAttempts: 10,
discoverDelay: 500,
externalGossipPort: 0,
gossipTimeout: 1000
});
function merge(a,b) {
@ -40,16 +60,109 @@ function merge(a,b) {
return c;
}
function createFromTcpEndpoint(settings, tcpEndpoint, connectionName) {
if (!tcpEndpoint.port || !tcpEndpoint.host) throw new TypeError('endPoint object must have host and port properties.');
var mergedSettings = merge(defaultConnectionSettings, settings || {});
var endpointDiscoverer = new StaticEndpointDiscoverer(tcpEndpoint, settings.useSslConnection);
return new EventStoreNodeConnection(mergedSettings, null, endpointDiscoverer, connectionName || null);
}
function createFromStringEndpoint(settings, endPoint, connectionName) {
var m = endPoint.match(/^(tcp|discover):\/\/([^:]+):?(\d+)?$/);
if (!m) throw new Error('endPoint string must be tcp://hostname[:port] or discover://dns[:port]');
var scheme = m[1];
var host = m[2] || null;
var port = m[3] ? parseInt(m[3]) : null;
if (scheme === 'tcp') {
var tcpEndpoint = {
host: host,
port: port || 1113
};
return createFromTcpEndpoint(settings, tcpEndpoint, connectionName);
}
if (scheme === 'discover') {
return createFromClusterDns(settings, host, port || 2113, connectionName);
}
throw new Error('Invalid scheme for endPoint: ' + scheme);
}
function createFromClusterDns(connectionSettings, clusterDns, externalGossipPort, connectionName) {
ensure.notNull(connectionSettings, "connectionSettings");
ensure.notNull(clusterDns, "clusterDns");
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
var clusterSettings = {
clusterDns: clusterDns,
seeds: null,
managerExternalHttpPort: externalGossipPort,
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
discoverDelay: mergedSettings.discoverDelay,
gossipTimeout: mergedSettings.gossipTimeout,
rejectUnauthorized: connectionSettings.useSslConnection ? connectionSettings.validateServer : undefined
};
var endPointDiscoverer = new ClusterDiscoverer(
mergedSettings.log,
clusterSettings,
dnsService,
connectionSettings.useSslConnection ? https : http
);
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
}
function createFromGossipSeeds(connectionSettings, gossipSeeds, connectionName) {
ensure.notNull(connectionSettings, "connectionSettings");
ensure.notNull(gossipSeeds, "gossipSeeds");
var mergedSettings = merge(defaultConnectionSettings, connectionSettings || {});
var clusterSettings = {
clusterDns: '',
seeds: gossipSeeds,
externalGossipPort: 0,
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
discoverDelay: mergedSettings.discoverDelay,
gossipTimeout: mergedSettings.gossipTimeout,
rejectUnauthorized: connectionSettings.useSslConnection ? connectionSettings.validateServer : undefined
};
var endPointDiscoverer = new ClusterDiscoverer(
mergedSettings.log,
clusterSettings,
dnsService,
connectionSettings.useSslConnection ? https : http
);
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
}
/**
* Create an EventStore connection
* @public
* @alias createConnection
* @param {object} settings
* @param {object} tcpEndPoint
* @param {boolean} [settings.verboseLogging=false]
* @param {number} [settings.maxQueueSize=5000]
* @param {number} [settings.maxConcurrentItems=5000]
* @param {number} [settings.maxRetries=10]
* @param {number} [settings.maxReconnections=10]
* @param {boolean} [settings.requireMaster=true]
* @param {number} [settings.reconnectionDelay=100]
* @param {number} [settings.operationTimeout=7000]
* @param {number} [settings.operationTimeoutCheckPeriod=1000]
* @param {object} [settings.defaultUserCredentials=null] - The default user credentials to use for requests
* @param {boolean} [settings.useSslConnection=false] - Whether to use SSL or not
* @param {object} [settings.targetHost=null]
* @param {boolean} [settings.validateServer=false]
* @param {boolean} [settings.failOnNoServerResponse=false]
* @param {number} [settings.heartbeatInterval=750]
* @param {number} [settings.heartbeatTimeout=1500]
* @param {number} [settings.clientConnectionTimeout=1000]
* @param {string} [settings.clusterDns='']
* @param {number} [settings.maxDiscoverAttempts=10]
* @param {number} [settings.externalGossipPort=0]
* @param {number} [settings.gossipTimeout=1000]
* @param {string|object|array} endPointOrGossipSeeds
* @param {string} [connectionName]
* @returns {EventStoreNodeConnection}
*/
module.exports.create = function(settings, tcpEndPoint, connectionName) {
//TODO: cluster connection
var mergedSettings = merge(defaultConnectionSettings, settings || {});
var endpointDiscoverer = new StaticEndpointDiscoverer(tcpEndPoint, settings.useSslConnection);
return new EventStoreNodeConnection(mergedSettings, endpointDiscoverer, connectionName || null);
};
module.exports.create = function(settings, endPointOrGossipSeeds, connectionName) {
if (Array.isArray(endPointOrGossipSeeds)) return createFromGossipSeeds(settings, endPointOrGossipSeeds, connectionName);
if (typeof endPointOrGossipSeeds === 'object') return createFromTcpEndpoint(settings, endPointOrGossipSeeds, connectionName);
if (typeof endPointOrGossipSeeds === 'string') return createFromStringEndpoint(settings, endPointOrGossipSeeds, connectionName);
throw new TypeError('endPointOrGossipSeeds must be an object, a string or an array.');
};

View File

@ -1,5 +1,6 @@
var util = require('util');
var uuid = require('uuid');
var Long = require('long');
var EventEmitter = require('events').EventEmitter;
var ensure = require('./common/utils/ensure');
@ -33,15 +34,14 @@ var EventData = require('./eventData');
const MaxReadSize = 4096;
/**
* @param settings
* @param endpointDiscoverer
* @param connectionName
* @protected
* @constructor
* @property {string} connectionName
*/
function EventStoreNodeConnection(settings, endpointDiscoverer, connectionName) {
function EventStoreNodeConnection(settings, clusterSettings, endpointDiscoverer, connectionName) {
EventEmitter.call(this);
this._connectionName = connectionName || ['ES-', uuid.v4()].join('');
this._settings = settings;
this._clusterSettings = clusterSettings;
this._endpointDiscoverer = endpointDiscoverer;
this._handler = new EventStoreConnectionLogicHandler(this, settings);
@ -61,6 +61,9 @@ function EventStoreNodeConnection(settings, endpointDiscoverer, connectionName)
this._handler.on('error', function(e) {
self.emit('error', e);
});
this._handler.on('heartbeatInfo', function(e) {
self.emit('heartbeatInfo', e);
});
}
util.inherits(EventStoreNodeConnection, EventEmitter);
@ -71,6 +74,8 @@ Object.defineProperty(EventStoreNodeConnection.prototype, 'connectionName', {
});
/**
* Start connection task
* @public
* @returns {Promise}
*/
EventStoreNodeConnection.prototype.connect = function() {
@ -85,22 +90,28 @@ EventStoreNodeConnection.prototype.connect = function() {
});
};
/**
* Close connection
* @public
*/
EventStoreNodeConnection.prototype.close = function() {
this._handler.enqueueMessage(new messages.CloseConnectionMessage("Connection close requested by client.", null));
};
/**
* Delete a stream (async)
* @public
* @param {string} stream
* @param {number} expectedVersion
* @param {Long|number} expectedVersion
* @param {boolean} [hardDelete]
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<DeleteResult>}
*/
EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) {
ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(expectedVersion, "expectedVersion");
hardDelete = !!hardDelete;
ensure.isLongOrInteger(expectedVersion, "expectedVersion");
expectedVersion = Long.fromValue(expectedVersion);
hardDelete = Boolean(hardDelete);
userCredentials = userCredentials || null;
var self = this;
@ -118,17 +129,18 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi
/**
* Append events to a stream (async)
* @public
* @param {string} stream The name of the stream to which to append.
* @param {number} expectedVersion The version at which we currently expect the stream to be in order that an optimistic concurrency check can be performed.
* @param {Long|number} expectedVersion The version at which we currently expect the stream to be in order that an optimistic concurrency check can be performed.
* @param {EventData[]|EventData} events The event(s) to append.
* @param {UserCredentials} [userCredentials] User credentials
* @returns {Promise.<WriteResult>}
*/
EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) {
ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(expectedVersion, "expectedVersion");
if (!Array.isArray(events))
events = [events];
ensure.isLongOrInteger(expectedVersion, "expectedVersion");
expectedVersion = Long.fromValue(expectedVersion);
if (!Array.isArray(events)) events = [events];
ensure.isArrayOf(EventData, events, "events");
userCredentials = userCredentials || null;
@ -146,13 +158,18 @@ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVer
/**
* Start a transaction (async)
* @public
* @param {string} stream
* @param {number} expectedVersion
* @param {Long|number} expectedVersion
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<EventStoreTransaction>}
*/
EventStoreNodeConnection.prototype.startTransaction = function(stream, expectedVersion, userCredentials) {
//TODO validations
ensure.notNullOrEmpty(stream, "stream");
ensure.isLongOrInteger(expectedVersion, "expectedVersion");
expectedVersion = Long.fromValue(expectedVersion);
userCredentials = userCredentials || null;
var self = this;
return new Promise(function(resolve, reject) {
function cb(err, result) {
@ -160,23 +177,29 @@ EventStoreNodeConnection.prototype.startTransaction = function(stream, expectedV
resolve(result);
}
var operation = new StartTransactionOperation(self._settings.log, cb, self._settings.requireMaster, stream,
expectedVersion, self, userCredentials || null);
expectedVersion, self, userCredentials);
self._enqueueOperation(operation);
});
};
/**
* Continue a transaction
* @public
* @param {number} transactionId
* @param {UserCredentials} userCredentials
* @returns {EventStoreTransaction}
*/
EventStoreNodeConnection.prototype.continueTransaction = function(transactionId, userCredentials) {
//TODO validations
ensure.nonNegative(transactionId, "transactionId");
return new EventStoreTransaction(transactionId, userCredentials, this);
};
EventStoreNodeConnection.prototype.transactionalWrite = function(transaction, events, userCredentials) {
ensure.isTypeOf(EventStoreTransaction, transaction, "transaction");
ensure.isArrayOf(EventData, events, "events");
userCredentials = userCredentials || null;
var self = this;
return new Promise(function(resolve, reject) {
function cb(err) {
@ -189,7 +212,16 @@ EventStoreNodeConnection.prototype.transactionalWrite = function(transaction, ev
});
};
/**
* Commit a transaction
* @public
* @param transaction
* @param userCredentials
* @returns {Promise.<WriteResult>}
*/
EventStoreNodeConnection.prototype.commitTransaction = function(transaction, userCredentials) {
ensure.isTypeOf(EventStoreTransaction, transaction, "transaction");
var self = this;
return new Promise(function(resolve, reject) {
function cb(err, result) {
@ -204,22 +236,22 @@ EventStoreNodeConnection.prototype.commitTransaction = function(transaction, use
/**
* Read a single event (async)
* @public
* @param {string} stream
* @param {number} eventNumber
* @param {Long|number} eventNumber
* @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<EventReadResult>}
*/
EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, resolveLinkTos, userCredentials) {
ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(eventNumber, "eventNumber");
if (eventNumber < -1) throw new Error("eventNumber out of range.");
resolveLinkTos = !!resolveLinkTos;
ensure.isLongOrInteger(eventNumber, "eventNumber");
eventNumber = Long.fromValue(eventNumber);
resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null;
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string.");
if (typeof eventNumber !== 'number' || eventNumber % 1 !== 0) throw new TypeError("eventNumber must be an integer.");
if (eventNumber < -1) throw new Error("eventNumber out of range.");
if (eventNumber.compare(-1) < 0) throw new Error("eventNumber out of range.");
if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean.");
var self = this;
@ -236,8 +268,9 @@ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, res
/**
* Reading a specific stream forwards (async)
* @public
* @param {string} stream
* @param {number} start
* @param {Long|number} start
* @param {number} count
* @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials]
@ -247,12 +280,13 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function(
stream, start, count, resolveLinkTos, userCredentials
) {
ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(start, "start");
ensure.isLongOrInteger(start, "start");
start = Long.fromValue(start);
ensure.nonNegative(start, "start");
ensure.isInteger(count, "count");
ensure.positive(count, "count");
if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize));
resolveLinkTos = !!resolveLinkTos;
resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null;
var self = this;
@ -269,8 +303,9 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function(
/**
* Reading a specific stream backwards (async)
* @public
* @param {string} stream
* @param {number} start
* @param {Long|number} start
* @param {number} count
* @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials]
@ -280,11 +315,12 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function(
stream, start, count, resolveLinkTos, userCredentials
) {
ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(start, "start");
ensure.isLongOrInteger(start, "start");
start = Long.fromValue(start);
ensure.isInteger(count, "count");
ensure.positive(count, "count");
if (count > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize));
resolveLinkTos = !!resolveLinkTos;
resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null;
var self = this;
@ -301,6 +337,7 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function(
/**
* Reading all events forwards (async)
* @public
* @param {Position} position
* @param {number} maxCount
* @param {boolean} [resolveLinkTos]
@ -314,7 +351,7 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function(
ensure.isInteger(maxCount, "maxCount");
ensure.positive(maxCount, "maxCount");
if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize));
resolveLinkTos = !!resolveLinkTos;
resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null;
var self = this;
@ -331,6 +368,7 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function(
/**
* Reading all events backwards (async)
* @public
* @param {Position} position
* @param {number} maxCount
* @param {boolean} [resolveLinkTos]
@ -344,7 +382,7 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
ensure.isInteger(maxCount, "maxCount");
ensure.positive(maxCount, "maxCount");
if (maxCount > MaxReadSize) throw new Error(util.format("Count should be less than %d. For larger reads you should page.", MaxReadSize));
resolveLinkTos = !!resolveLinkTos;
resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null;
var self = this;
@ -361,6 +399,7 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
/**
* Subscribe to a stream (async)
* @public
* @param {!string} stream
* @param {!boolean} resolveLinkTos
* @param {function} eventAppeared
@ -371,8 +410,9 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
EventStoreNodeConnection.prototype.subscribeToStream = function(
stream, resolveLinkTos, eventAppeared, subscriptionDropped, userCredentials
) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be a non-empty string.");
if (typeof eventAppeared !== 'function') throw new TypeError("eventAppeared must be a function.");
ensure.notNullOrEmpty(stream, "stream");
ensure.isTypeOf(Function, eventAppeared, "eventAppeared");
if (subscriptionDropped) ensure.isTypeOf(Function, subscriptionDropped, "subscriptionDropped");
var self = this;
return new Promise(function(resolve,reject) {
@ -388,8 +428,10 @@ EventStoreNodeConnection.prototype.subscribeToStream = function(
};
/**
* Subscribe to a stream from position
* @public
* @param {!string} stream
* @param {?number} lastCheckpoint
* @param {?number|Position} lastCheckpoint
* @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared
* @param {function} [liveProcessingStarted]
@ -403,6 +445,10 @@ EventStoreNodeConnection.prototype.subscribeToStreamFrom = function(
userCredentials, readBatchSize
) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be a non-empty string.");
if (lastCheckpoint !== null) {
ensure.isLongOrInteger(lastCheckpoint);
lastCheckpoint = Long.fromValue(lastCheckpoint);
}
if (typeof eventAppeared !== 'function') throw new TypeError("eventAppeared must be a function.");
var catchUpSubscription =
@ -416,6 +462,7 @@ EventStoreNodeConnection.prototype.subscribeToStreamFrom = function(
/**
* Subscribe to all (async)
* @public
* @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared
* @param {function} [subscriptionDropped]
@ -442,6 +489,7 @@ EventStoreNodeConnection.prototype.subscribeToAll = function(
/**
* Subscribe to all from
* @public
* @param {?Position} lastCheckpoint
* @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared
@ -455,7 +503,10 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
lastCheckpoint, resolveLinkTos, eventAppeared, liveProcessingStarted, subscriptionDropped,
userCredentials, readBatchSize
) {
if (typeof eventAppeared !== 'function') throw new TypeError("eventAppeared must be a function.");
ensure.isTypeOf(results.Position, lastCheckpoint, "lastCheckpoint", true);
ensure.isTypeOf(Function, eventAppeared, "eventAppeared", false);
if (liveProcessingStarted) ensure.isTypeOf(Function, liveProcessingStarted, "liveProcessingStarted", false);
if (subscriptionDropped) ensure.isTypeOf(Function, subscriptionDropped, "subscriptionDropped", false);
var catchUpSubscription =
new EventStoreAllCatchUpSubscription(this, this._settings.log, lastCheckpoint, resolveLinkTos,
@ -467,6 +518,7 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
/**
* Subscribe to a persistent subscription
* @public
* @param {string} stream
* @param {string} groupName
* @param {function} eventAppeared
@ -474,6 +526,7 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
* @param {UserCredentials} [userCredentials]
* @param {number} [bufferSize]
* @param {boolean} [autoAck]
* @return {Promise<EventStorePersistentSubscription>}
*/
EventStoreNodeConnection.prototype.connectToPersistentSubscription = function(
stream, groupName, eventAppeared, subscriptionDropped, userCredentials, bufferSize, autoAck
@ -485,17 +538,17 @@ EventStoreNodeConnection.prototype.connectToPersistentSubscription = function(
subscriptionDropped = subscriptionDropped || null;
userCredentials = userCredentials || null;
bufferSize = bufferSize === undefined ? 10 : bufferSize;
autoAck = autoAck === undefined ? true : !!autoAck;
autoAck = autoAck === undefined ? true : Boolean(autoAck);
var subscription = new EventStorePersistentSubscription(
groupName, stream, eventAppeared, subscriptionDropped, userCredentials, this._settings.log,
this._settings.verboseLogging, this._settings, this._handler, bufferSize, autoAck);
subscription.start();
return subscription;
return subscription.start();
};
/**
* Create a persistent subscription
* @public
* @param {string} stream
* @param {string} groupName
* @param {PersistentSubscriptionSettings} settings
@ -519,9 +572,11 @@ EventStoreNodeConnection.prototype.createPersistentSubscription = function(strea
};
/**
* Update a persistent subscription
* @public
* @param {string} stream
* @param {string} groupName
* @param {string} settings
* @param {PersistentSubscriptionSettings} settings
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<PersistentSubscriptionUpdateResult>}
*/
@ -541,6 +596,8 @@ EventStoreNodeConnection.prototype.updatePersistentSubscription = function(strea
};
/**
* Delete a persistent subscription
* @public
* @param {string} stream
* @param {string} groupName
* @param {UserCredentials} [userCredentials]
@ -560,6 +617,10 @@ EventStoreNodeConnection.prototype.deletePersistentSubscription = function(strea
});
};
/**
* Set stream metadata
* @private
*/
EventStoreNodeConnection.prototype.setStreamMetadata = function() {
//TODO: set stream metadata (non-raw)
throw new Error("Not implemented.");
@ -567,8 +628,9 @@ EventStoreNodeConnection.prototype.setStreamMetadata = function() {
/**
* Set stream metadata with raw object (async)
* @public
* @param {string} stream
* @param {number} expectedMetastreamVersion
* @param {Long|number} expectedMetastreamVersion
* @param {object} metadata
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<WriteResult>}
@ -577,15 +639,18 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
stream, expectedMetastreamVersion, metadata, userCredentials
) {
ensure.notNullOrEmpty(stream, "stream");
if (systemStreams.isMetastream(stream))
if (systemStreams.isMetastream(stream)) {
throw new Error(util.format("Setting metadata for metastream '%s' is not supported.", stream));
}
ensure.isLongOrInteger(expectedMetastreamVersion, "expectedMetastreamVersion");
expectedMetastreamVersion = Long.fromValue(expectedMetastreamVersion);
var self = this;
return new Promise(function(resolve, reject) {
function cb(err, result) {
if (err) return reject(err);
resolve(result);
}
var data = metadata ? new Buffer(JSON.stringify(metadata)) : null;
var data = metadata ? Buffer.from(JSON.stringify(metadata)) : null;
var metaevent = new EventData(uuid.v4(), systemEventTypes.StreamMetadata, true, data, null);
self._enqueueOperation(
new AppendToStreamOperation(self._settings.log, cb, self._settings.requireMaster,
@ -594,6 +659,12 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
});
};
/**
* Get stream metadata
* @private
* @param stream
* @param userCredentials
*/
EventStoreNodeConnection.prototype.getStreamMetadata = function(stream, userCredentials) {
//TODO: get stream metadata (non-raw)
throw new Error("Not implemented.");
@ -601,6 +672,7 @@ EventStoreNodeConnection.prototype.getStreamMetadata = function(stream, userCred
/**
* Get stream metadata as raw object (async)
* @public
* @param {string} stream
* @param {UserCredentials} [userCredentials]
* @returns {Promise.<RawStreamMetadataResult>}
@ -614,18 +686,22 @@ EventStoreNodeConnection.prototype.getStreamMetadataRaw = function(stream, userC
var evnt = res.event.originalEvent;
var version = evnt ? evnt.eventNumber : -1;
var data = evnt ? JSON.parse(evnt.data.toString()) : null;
return new results.RawStreamMetadataResult(stream, false, version, data);
return new results.RawStreamMetadataResult(stream, false, Long.fromValue(version), data);
case results.EventReadStatus.NotFound:
case results.EventReadStatus.NoStream:
return new results.RawStreamMetadataResult(stream, false, -1, null);
return new results.RawStreamMetadataResult(stream, false, Long.fromValue(-1), null);
case results.EventReadStatus.StreamDeleted:
return new results.RawStreamMetadataResult(stream, true, 0x7fffffff, null);
return new results.RawStreamMetadataResult(stream, true, Long.fromValue(0x7fffffff), null);
default:
throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status));
}
});
};
/**
* Set system settings
* @private
*/
EventStoreNodeConnection.prototype.setSystemSettings = function() {
//TODO: set system settings
throw new Error("Not implemented.");
@ -636,7 +712,7 @@ EventStoreNodeConnection.prototype._enqueueOperation = function(operation) {
var message = new messages.StartOperationMessage(operation, self._settings.maxRetries, self._settings.operationTimeout);
function tryEnqueue() {
if (self._handler.totalOperationCount >= self._settings.maxQueueSize) {
setImmediate(tryEnqueue);
setTimeout(tryEnqueue, 0);
return;
}
self._handler.enqueueMessage(message);

View File

@ -35,10 +35,12 @@ EventStorePersistentSubscriptionBase.prototype.start = function() {
this._stopped = false;
var self = this;
this._startSubscription(this._subscriptionId, this._streamId, this._bufferSize, this._userCredentials,
return this._startSubscription(this._subscriptionId, this._streamId, this._bufferSize, this._userCredentials,
this._onEventAppeared.bind(this), this._onSubscriptionDropped.bind(this), this._settings)
.then(function(subscription) {
self._log.debug('Subscription started.');
self._subscription = subscription;
return self;
});
};
@ -54,8 +56,7 @@ EventStorePersistentSubscriptionBase.prototype.acknowledge = function(events) {
ensure.notNull(events, "events");
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
if (!Array.isArray(events))
events = [events];
if (!Array.isArray(events)) events = [events];
var ids = events.map(function(x) { return x.originalEvent.eventId; });
this._subscription.notifyEventsProcessed(ids);
};
@ -71,12 +72,12 @@ EventStorePersistentSubscriptionBase.prototype.fail = function(events, action, r
ensure.notNull(reason, "reason");
if (this._subscription === null) throw new Error("Invalid operation. Subscription is not active yet.");
if (!Array.isArray(events))
events = [events];
if (!Array.isArray(events)) events = [events];
var ids = events.map(function(x) { return x.originalEvent.eventId; });
this._subscription.notifyEventsFailed(ids, action, reason);
};
//TODO: this should return a promise
EventStorePersistentSubscriptionBase.prototype.stop = function() {
if (this._verbose) this._log.debug("Persistent Subscription to %s: requesting stop...", this._streamId);
this._enqueueSubscriptionDropNotification(SubscriptionDropReason.UserInitiated, null);
@ -109,57 +110,75 @@ EventStorePersistentSubscriptionBase.prototype._enqueue = function(resolvedEvent
}
};
function runAsync(fn) {
try {
return Promise.resolve(fn());
} catch(e) {
return Promise.reject(e);
}
}
EventStorePersistentSubscriptionBase.prototype._processQueue = function() {
//do
//{
var e = this._queue.shift();
while (e)
{
if (e instanceof DropSubscriptionEvent) // drop subscription artificial ResolvedEvent
{
if (this._dropData === null) throw new Error("Drop reason not specified.");
this._dropSubscription(this._dropData.reason, this._dropData.error);
return;
}
if (this._dropData !== null)
{
this._dropSubscription(this._dropData.reason, this._dropData.error);
return;
}
try
{
this._eventAppeared(this, e);
if(this._autoAck)
this._subscription.notifyEventsProcessed([e.originalEvent.eventId]);
if (this._verbose)
this._log.debug("Persistent Subscription to %s: processed event (%s, %d, %s @ %d).",
this._streamId, e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType,
e.originalEventNumber);
}
catch (err)
{
//TODO GFY should we autonak here?
this._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
return;
}
e = this._queue.shift();
}
var ev = this._queue.shift();
if (!ev) {
this._isProcessing = false;
//} while (_queue.Count > 0 && Interlocked.CompareExchange(ref _isProcessing, 1, 0) == 0);
return;
}
if (ev instanceof DropSubscriptionEvent) // drop subscription artificial ResolvedEvent
{
if (this._dropData === null) throw new Error("Drop reason not specified.");
this._dropSubscription(this._dropData.reason, this._dropData.error);
this._isProcessing = false;
return;
}
if (this._dropData !== null)
{
this._dropSubscription(this._dropData.reason, this._dropData.error);
this._isProcessing = false;
return;
}
var self = this;
runAsync(function() {
return self._eventAppeared(self, ev);
})
.then(function() {
if(self._autoAck) self._subscription.notifyEventsProcessed([ev.originalEvent.eventId]);
if (self._verbose) {
self._log.debug("Persistent Subscription to %s: processed event (%s, %d, %s @ %d).",
self._streamId, ev.originalEvent.eventStreamId, ev.originalEvent.eventNumber, ev.originalEvent.eventType,
ev.originalEventNumber);
}
return false;
}, function(err) {
//TODO GFY should we autonak here?
self._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
self._isProcessing = false;
return true;
})
.then(function (faulted) {
if (faulted) return;
self._processQueue();
});
};
EventStorePersistentSubscriptionBase.prototype._dropSubscription = function(reason, error) {
if (!this._isDropped)
{
this._isDropped = true;
if (this._verbose)
if (this._verbose) {
this._log.debug("Persistent Subscription to %s: dropping subscription, reason: %s %s.",
this._streamId, reason, error);
this._streamId, reason, error);
}
if (this._subscription !== null)
this._subscription.unsubscribe();
if (this._subscriptionDropped !== null)
this._subscriptionDropped(this, reason, error);
if (this._subscription !== null) this._subscription.unsubscribe();
if (this._subscriptionDropped !== null) {
try {
this._subscriptionDropped(this, reason, error);
} catch (e) {
this._log.error(e, "Persistent Subscription to %s: subscriptionDropped callback failed.", this._streamId);
}
}
this._stopped = true;
}
};

View File

@ -1,4 +1,5 @@
var util = require('util');
var Long = require('long');
var EventStoreCatchUpSubscription = require('./eventStoreCatchUpSubscription');
var SliceReadStatus = require('./sliceReadStatus');
@ -14,24 +15,26 @@ function EventStoreStreamCatchUpSubscription(
//Ensure.NotNullOrEmpty(streamId, "streamId");
this._lastProcessedEventNumber = fromEventNumberExclusive || -1;
this._nextReadEventNumber = fromEventNumberExclusive || 0;
this._lastProcessedEventNumber = fromEventNumberExclusive === null ? Long.fromNumber(-1) : fromEventNumberExclusive;
this._nextReadEventNumber = fromEventNumberExclusive === null ? Long.fromNumber(0) : fromEventNumberExclusive.add(1);
}
util.inherits(EventStoreStreamCatchUpSubscription, EventStoreCatchUpSubscription);
function delay(ms, result) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms, result);
})
}
EventStoreStreamCatchUpSubscription.prototype._readEventsTill = function(
connection, resolveLinkTos, userCredentials, lastCommitPosition, lastEventNumber
) {
var self = this;
function processEvents(events, index) {
index = index || 0;
if (index >= events.length) return Promise.resolve();
return new Promise(function(resolve, reject) {
self._tryProcess(events[index]);
resolve();
})
return self._tryProcess(events[index])
.then(function() {
return processEvents(events, index + 1);
});
@ -42,23 +45,22 @@ EventStoreStreamCatchUpSubscription.prototype._readEventsTill = function(
.then(function(slice) {
switch(slice.status) {
case SliceReadStatus.Success:
return processEvents(slice.events)
return processEvents(slice.events, 0)
.then(function() {
self._nextReadEventNumber = slice.nextEventNumber;
var done = Promise.resolve(lastEventNumber === null ? slice.isEndOfStream : slice.nextEventNumber > lastEventNumber);
if (!done && slice.isEndOfStream)
return done.delay(10);
var done = Promise.resolve(lastEventNumber === null ? slice.isEndOfStream : slice.nextEventNumber.compare(lastEventNumber) > 0);
if (!done && slice.isEndOfStream) return delay(100, false);
return done;
});
break;
case SliceReadStatus.StreamNotFound:
if (lastEventNumber && lastEventNumber !== -1)
if (lastEventNumber && lastEventNumber.compare(-1) !== 0) {
throw new Error(util.format("Impossible: stream %s disappeared in the middle of catching up subscription.", self.streamId));
}
return true;
case SliceReadStatus.StreamDeleted:
throw new Error("Stream deleted: " + self.streamId);
default:
throw new Error("Unexpected StreamEventsSlice.Status: %s.", slice.status);
throw new Error(util.format("Unexpected StreamEventsSlice.Status: %s.", slice.status));
}
})
.then(function(done) {
@ -69,24 +71,28 @@ EventStoreStreamCatchUpSubscription.prototype._readEventsTill = function(
}
return readNext()
.then(function() {
if (self._verbose)
if (self._verbose) {
self._log.debug("Catch-up Subscription to %s: finished reading events, nextReadEventNumber = %d.",
self.isSubscribedToAll ? '<all>' : self.streamId, self._nextReadEventNumber);
self.isSubscribedToAll ? '<all>' : self.streamId, self._nextReadEventNumber);
}
});
};
EventStoreStreamCatchUpSubscription.prototype._tryProcess = function(e) {
var processed = false;
if (e.originalEventNumber > this._lastProcessedEventNumber) {
this._eventAppeared(this, e);
var promise;
if (e.originalEventNumber.compare(this._lastProcessedEventNumber) > 0) {
promise = this._eventAppeared(this, e);
this._lastProcessedEventNumber = e.originalEventNumber;
processed = true;
}
if (this._verbose)
if (this._verbose) {
this._log.debug("Catch-up Subscription to %s: %s event (%s, %d, %s @ %d).",
this.isSubscribedToAll ? '<all>' : this.streamId, processed ? "processed" : "skipping",
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalEventNumber)
this.isSubscribedToAll ? '<all>' : this.streamId, processed ? "processed" : "skipping",
e.originalEvent.eventStreamId, e.originalEvent.eventNumber, e.originalEvent.eventType, e.originalEventNumber);
}
return (promise && promise.then) ? promise : Promise.resolve();
};
module.exports = EventStoreStreamCatchUpSubscription;
module.exports = EventStoreStreamCatchUpSubscription;

View File

@ -41,4 +41,4 @@ EventStoreSubscription.prototype.unsubscribe = function() {
throw new Error("EventStoreSubscription.unsubscribe abstract method called." + this.constructor.name);
};
module.exports = EventStoreSubscription;
module.exports = EventStoreSubscription;

View File

@ -33,13 +33,13 @@ EventStoreTransaction.prototype.commit = function() {
/**
* Write events (async)
* @param {Array.<EventData>} events
* @param {EventData|EventData[]} eventOrEvents
* @returns {Promise}
*/
EventStoreTransaction.prototype.write = function(events) {
EventStoreTransaction.prototype.write = function(eventOrEvents) {
if (this._isRolledBack) throw new Error("can't write to a rolledback transaction");
if (this._isCommitted) throw new Error("Transaction is already committed");
if (!Array.isArray(events)) throw new Error("events must be an array.");
var events = Array.isArray(eventOrEvents) ? eventOrEvents : [eventOrEvents];
return this._connection.transactionalWrite(this, events);
};

9
src/gossipSeed.js Normal file
View File

@ -0,0 +1,9 @@
function GossipSeed(endPoint, hostName, hostHeader) {
if (typeof endPoint !== 'object' || !endPoint.host || !endPoint.port) throw new TypeError('endPoint must be have host and port properties.');
this.endPoint = endPoint;
this.hostName = hostName;
this.hostHeader = hostHeader;
Object.freeze(this);
}
module.exports = GossipSeed;

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
syntax = "proto3";
package EventStore.Client.Messages;
enum OperationResult
@ -13,113 +15,115 @@ enum OperationResult
}
message NewEvent {
required bytes event_id = 1;
required string event_type = 2;
required int32 data_content_type = 3;
required int32 metadata_content_type = 4;
required bytes data = 5;
optional bytes metadata = 6;
bytes event_id = 1;
string event_type = 2;
int32 data_content_type = 3;
int32 metadata_content_type = 4;
bytes data = 5;
bytes metadata = 6;
}
message EventRecord {
required string event_stream_id = 1;
required int32 event_number = 2;
required bytes event_id = 3;
required string event_type = 4;
required int32 data_content_type = 5;
required int32 metadata_content_type = 6;
required bytes data = 7;
optional bytes metadata = 8;
optional int64 created = 9;
optional int64 created_epoch = 10;
string event_stream_id = 1;
int64 event_number = 2;
bytes event_id = 3;
string event_type = 4;
int32 data_content_type = 5;
int32 metadata_content_type = 6;
bytes data = 7;
bytes metadata = 8;
int64 created = 9;
int64 created_epoch = 10;
}
message ResolvedIndexedEvent {
required EventRecord event = 1;
optional EventRecord link = 2;
EventRecord event = 1;
EventRecord link = 2;
}
message ResolvedEvent {
required EventRecord event = 1;
optional EventRecord link = 2;
required int64 commit_position = 3;
required int64 prepare_position = 4;
EventRecord event = 1;
EventRecord link = 2;
int64 commit_position = 3;
int64 prepare_position = 4;
}
message WriteEvents {
required string event_stream_id = 1;
required int32 expected_version = 2;
string event_stream_id = 1;
int64 expected_version = 2;
repeated NewEvent events = 3;
required bool require_master = 4;
bool require_leader = 4;
}
message WriteEventsCompleted {
required OperationResult result = 1;
optional string message = 2;
required int32 first_event_number = 3;
required int32 last_event_number = 4;
optional int64 prepare_position = 5;
optional int64 commit_position = 6;
OperationResult result = 1;
string message = 2;
int64 first_event_number = 3;
int64 last_event_number = 4;
int64 prepare_position = 5;
int64 commit_position = 6;
int64 current_version = 7;
}
message DeleteStream {
required string event_stream_id = 1;
required int32 expected_version = 2;
required bool require_master = 3;
optional bool hard_delete = 4;
string event_stream_id = 1;
int64 expected_version = 2;
bool require_leader = 3;
bool hard_delete = 4;
}
message DeleteStreamCompleted {
required OperationResult result = 1;
optional string message = 2;
optional int64 prepare_position = 3;
optional int64 commit_position = 4;
OperationResult result = 1;
string message = 2;
int64 prepare_position = 3;
int64 commit_position = 4;
int64 current_version = 5;
}
message TransactionStart {
required string event_stream_id = 1;
required int32 expected_version = 2;
required bool require_master = 3;
string event_stream_id = 1;
int64 expected_version = 2;
bool require_leader = 3;
}
message TransactionStartCompleted {
required int64 transaction_id = 1;
required OperationResult result = 2;
optional string message = 3;
int64 transaction_id = 1;
OperationResult result = 2;
string message = 3;
}
message TransactionWrite {
required int64 transaction_id = 1;
int64 transaction_id = 1;
repeated NewEvent events = 2;
required bool require_master = 3;
bool require_leader = 3;
}
message TransactionWriteCompleted {
required int64 transaction_id = 1;
required OperationResult result = 2;
optional string message = 3;
int64 transaction_id = 1;
OperationResult result = 2;
string message = 3;
}
message TransactionCommit {
required int64 transaction_id = 1;
required bool require_master = 2;
int64 transaction_id = 1;
bool require_leader = 2;
}
message TransactionCommitCompleted {
required int64 transaction_id = 1;
required OperationResult result = 2;
optional string message = 3;
required int32 first_event_number = 4;
required int32 last_event_number = 5;
optional int64 prepare_position = 6;
optional int64 commit_position = 7;
int64 transaction_id = 1;
OperationResult result = 2;
string message = 3;
int64 first_event_number = 4;
int64 last_event_number = 5;
int64 prepare_position = 6;
int64 commit_position = 7;
}
message ReadEvent {
required string event_stream_id = 1;
required int32 event_number = 2;
required bool resolve_link_tos = 3;
required bool require_master = 4;
string event_stream_id = 1;
int64 event_number = 2;
bool resolve_link_tos = 3;
bool require_leader = 4;
}
message ReadEventCompleted {
@ -133,18 +137,18 @@ message ReadEventCompleted {
AccessDenied = 5;
}
required ReadEventResult result = 1;
required ResolvedIndexedEvent event = 2;
ReadEventResult result = 1;
ResolvedIndexedEvent event = 2;
optional string error = 3;
string error = 3;
}
message ReadStreamEvents {
required string event_stream_id = 1;
required int32 from_event_number = 2;
required int32 max_count = 3;
required bool resolve_link_tos = 4;
required bool require_master = 5;
string event_stream_id = 1;
int64 from_event_number = 2;
int32 max_count = 3;
bool resolve_link_tos = 4;
bool require_leader = 5;
}
message ReadStreamEventsCompleted {
@ -159,21 +163,21 @@ message ReadStreamEventsCompleted {
}
repeated ResolvedIndexedEvent events = 1;
required ReadStreamResult result = 2;
required int32 next_event_number = 3;
required int32 last_event_number = 4;
required bool is_end_of_stream = 5;
required int64 last_commit_position = 6;
ReadStreamResult result = 2;
int64 next_event_number = 3;
int64 last_event_number = 4;
bool is_end_of_stream = 5;
int64 last_commit_position = 6;
optional string error = 7;
string error = 7;
}
message ReadAllEvents {
required int64 commit_position = 1;
required int64 prepare_position = 2;
required int32 max_count = 3;
required bool resolve_link_tos = 4;
required bool require_master = 5;
int64 commit_position = 1;
int64 prepare_position = 2;
int32 max_count = 3;
bool resolve_link_tos = 4;
bool require_leader = 5;
}
message ReadAllEventsCompleted {
@ -185,57 +189,105 @@ message ReadAllEventsCompleted {
AccessDenied = 3;
}
required int64 commit_position = 1;
required int64 prepare_position = 2;
int64 commit_position = 1;
int64 prepare_position = 2;
repeated ResolvedEvent events = 3;
required int64 next_commit_position = 4;
required int64 next_prepare_position = 5;
int64 next_commit_position = 4;
int64 next_prepare_position = 5;
optional ReadAllResult result = 6 [default = Success];
optional string error = 7;
ReadAllResult result = 6;
string error = 7;
}
message Filter{
enum FilterContext {
StreamId = 0;
EventType = 1;
}
enum FilterType {
Regex = 0;
Prefix = 1;
}
FilterContext context = 1;
FilterType type = 2;
repeated string data = 3;
}
message FilteredReadAllEvents {
int64 commit_position = 1;
int64 prepare_position = 2;
int32 max_count = 3;
int32 max_search_window = 4;
bool resolve_link_tos = 5;
bool require_leader = 6;
Filter filter = 7;
}
message FilteredReadAllEventsCompleted {
enum FilteredReadAllResult {
Success = 0;
NotModified = 1;
Error = 2;
AccessDenied = 3;
}
int64 commit_position = 1;
int64 prepare_position = 2;
repeated ResolvedEvent events = 3;
int64 next_commit_position = 4;
int64 next_prepare_position = 5;
bool is_end_of_stream = 6;
FilteredReadAllResult result = 7;
string error = 8;
}
message CreatePersistentSubscription {
required string subscription_group_name = 1;
required string event_stream_id = 2;
required bool resolve_link_tos = 3;
required int32 start_from = 4;
required int32 message_timeout_milliseconds = 5;
required bool record_statistics = 6;
required int32 live_buffer_size = 7;
required int32 read_batch_size = 8;
required int32 buffer_size = 9;
required int32 max_retry_count = 10;
required bool prefer_round_robin = 11;
required int32 checkpoint_after_time = 12;
required int32 checkpoint_max_count = 13;
required int32 checkpoint_min_count = 14;
required int32 subscriber_max_count = 15;
optional string named_consumer_strategy = 16;
string subscription_group_name = 1;
string event_stream_id = 2;
bool resolve_link_tos = 3;
int64 start_from = 4;
int32 message_timeout_milliseconds = 5;
bool record_statistics = 6;
int32 live_buffer_size = 7;
int32 read_batch_size = 8;
int32 buffer_size = 9;
int32 max_retry_count = 10;
bool prefer_round_robin = 11;
int32 checkpoint_after_time = 12;
int32 checkpoint_max_count = 13;
int32 checkpoint_min_count = 14;
int32 subscriber_max_count = 15;
string named_consumer_strategy = 16;
}
message DeletePersistentSubscription {
required string subscription_group_name = 1;
required string event_stream_id = 2;
string subscription_group_name = 1;
string event_stream_id = 2;
}
message UpdatePersistentSubscription {
required string subscription_group_name = 1;
required string event_stream_id = 2;
required bool resolve_link_tos = 3;
required int32 start_from = 4;
required int32 message_timeout_milliseconds = 5;
required bool record_statistics = 6;
required int32 live_buffer_size = 7;
required int32 read_batch_size = 8;
required int32 buffer_size = 9;
required int32 max_retry_count = 10;
required bool prefer_round_robin = 11;
required int32 checkpoint_after_time = 12;
required int32 checkpoint_max_count = 13;
required int32 checkpoint_min_count = 14;
required int32 subscriber_max_count = 15;
optional string named_consumer_strategy = 16;
string subscription_group_name = 1;
string event_stream_id = 2;
bool resolve_link_tos = 3;
int64 start_from = 4;
int32 message_timeout_milliseconds = 5;
bool record_statistics = 6;
int32 live_buffer_size = 7;
int32 read_batch_size = 8;
int32 buffer_size = 9;
int32 max_retry_count = 10;
bool prefer_round_robin = 11;
int32 checkpoint_after_time = 12;
int32 checkpoint_max_count = 13;
int32 checkpoint_min_count = 14;
int32 subscriber_max_count = 15;
string named_consumer_strategy = 16;
}
message UpdatePersistentSubscriptionCompleted {
@ -245,8 +297,8 @@ message UpdatePersistentSubscriptionCompleted {
Fail = 2;
AccessDenied=3;
}
required UpdatePersistentSubscriptionResult result = 1 [default = Success];
optional string reason = 2;
UpdatePersistentSubscriptionResult result = 1;
string reason = 2;
}
message CreatePersistentSubscriptionCompleted {
@ -256,8 +308,8 @@ message CreatePersistentSubscriptionCompleted {
Fail = 2;
AccessDenied=3;
}
required CreatePersistentSubscriptionResult result = 1 [default = Success];
optional string reason = 2;
CreatePersistentSubscriptionResult result = 1;
string reason = 2;
}
message DeletePersistentSubscriptionCompleted {
@ -267,19 +319,19 @@ message DeletePersistentSubscriptionCompleted {
Fail = 2;
AccessDenied = 3;
}
required DeletePersistentSubscriptionResult result = 1 [default = Success];
optional string reason = 2;
DeletePersistentSubscriptionResult result = 1;
string reason = 2;
}
message ConnectToPersistentSubscription {
required string subscription_id = 1;
required string event_stream_id = 2;
required int32 allowed_in_flight_messages = 3;
string subscription_id = 1;
string event_stream_id = 2;
int32 allowed_in_flight_messages = 3;
}
message PersistentSubscriptionAckEvents {
required string subscription_id = 1;
string subscription_id = 1;
repeated bytes processed_event_ids = 2;
}
@ -292,34 +344,47 @@ message PersistentSubscriptionNakEvents {
Stop = 4;
}
required string subscription_id = 1;
string subscription_id = 1;
repeated bytes processed_event_ids = 2;
optional string message = 3;
required NakAction action = 4 [default = Unknown];
string message = 3;
NakAction action = 4;
}
message PersistentSubscriptionConfirmation {
required int64 last_commit_position = 1;
required string subscription_id = 2;
optional int32 last_event_number = 3;
int64 last_commit_position = 1;
string subscription_id = 2;
int64 last_event_number = 3;
}
message PersistentSubscriptionStreamEventAppeared {
required ResolvedIndexedEvent event = 1;
ResolvedIndexedEvent event = 1;
int32 retryCount = 2;
}
message SubscribeToStream {
required string event_stream_id = 1;
required bool resolve_link_tos = 2;
string event_stream_id = 1;
bool resolve_link_tos = 2;
}
message FilteredSubscribeToStream {
string event_stream_id = 1;
bool resolve_link_tos = 2;
Filter filter = 3;
int32 checkpoint_interval = 4;
}
message CheckpointReached {
int64 commit_position = 1;
int64 prepare_position = 2;
}
message SubscriptionConfirmation {
required int64 last_commit_position = 1;
optional int32 last_event_number = 2;
int64 last_commit_position = 1;
int64 last_event_number = 2;
}
message StreamEventAppeared {
required ResolvedEvent event = 1;
ResolvedEvent event = 1;
}
message UnsubscribeFromStream {
@ -334,44 +399,52 @@ message SubscriptionDropped {
PersistentSubscriptionDeleted=3;
SubscriberMaxCountReached=4;
}
optional SubscriptionDropReason reason = 1 [default = Unsubscribed];
SubscriptionDropReason reason = 1;
}
message NotHandled {
enum NotHandledReason {
NotReady = 0;
TooBusy = 1;
NotMaster = 2;
NotLeader = 2;
IsReadOnly = 3;
}
NotHandledReason reason = 1;
bytes additional_info = 2;
required NotHandledReason reason = 1;
optional bytes additional_info = 2;
message MasterInfo {
required string external_tcp_address = 1;
required int32 external_tcp_port = 2;
required string external_http_address = 3;
required int32 external_http_port = 4;
optional string external_secure_tcp_address = 5;
optional int32 external_secure_tcp_port = 6;
message LeaderInfo {
string external_tcp_address = 1;
int32 external_tcp_port = 2;
string http_address = 3;
int32 http_port = 4;
string external_secure_tcp_address = 5;
int32 external_secure_tcp_port = 6;
}
}
message ScavengeDatabase {
}
message ScavengeDatabaseCompleted {
message ScavengeDatabaseResponse {
enum ScavengeResult {
Success = 0;
Started = 0;
InProgress = 1;
Failed = 2;
Unauthorized = 2;
}
required ScavengeResult result = 1;
optional string error = 2;
required int32 total_time_ms = 3;
required int64 total_space_saved = 4;
ScavengeResult result = 1;
string scavengeId = 2;
}
message IdentifyClient {
int32 version = 1;
string connection_name = 2;
}
message ClientIdentified {
}

View File

@ -22,4 +22,4 @@ PersistentEventStoreSubscription.prototype.notifyEventsFailed = function(process
this._subscriptionOperation.notifyEventsFailed(processedEvents, action, reason);
};
module.exports = PersistentEventStoreSubscription;
module.exports = PersistentEventStoreSubscription;

View File

@ -1,14 +1,15 @@
const PersistentSubscriptionNakEventAction = {
const PersistentSubscriptionNakEventAction = Object.freeze({
Unknown: 0,
Park: 1,
Retry: 2,
Skip: 3,
Stop: 4
};
Stop: 4,
isValid: function(value) {
for(var k in PersistentSubscriptionNakEventAction) {
if (PersistentSubscriptionNakEventAction[k] === value) return true;
}
return false;
}
});
module.exports = PersistentSubscriptionNakEventAction;
module.exports.isValid = function(value) {
for(var k in PersistentSubscriptionNakEventAction)
if (PersistentSubscriptionNakEventAction[k] === value) return true;
return false;
};
module.exports = PersistentSubscriptionNakEventAction;

View File

@ -1,4 +1,6 @@
var SystemConsumerStrategies = require('./systemConsumerStrategies');
var ensure = require('./common/utils/ensure');
var Long = require('long');
function PersistentSubscriptionSettings(
resolveLinkTos, startFrom, extraStatistics, messageTimeout,
@ -6,6 +8,9 @@ function PersistentSubscriptionSettings(
checkPointAfter, minCheckPointCount, maxCheckPointCount,
maxSubscriberCount, namedConsumerStrategy
) {
ensure.isLongOrInteger(startFrom);
startFrom = Long.fromValue(startFrom);
this.resolveLinkTos = resolveLinkTos;
this.startFrom = startFrom;
this.extraStatistics = extraStatistics;
@ -21,6 +26,8 @@ function PersistentSubscriptionSettings(
this.namedConsumerStrategy = namedConsumerStrategy;
}
module.exports.create = function() {
PersistentSubscriptionSettings.create = function() {
return new PersistentSubscriptionSettings(false, -1, false, 30000, 500, 500, 10, 20, 2000, 10, 1000, 0, SystemConsumerStrategies.RoundRobin);
};
};
module.exports = PersistentSubscriptionSettings;

View File

@ -0,0 +1,56 @@
function ProjectionDetails(
coreProcessingTime,
version,
epoch,
effectiveName,
writesInProgress,
readsInProgress,
partitionsCached,
status,
stateReason,
name,
mode,
position,
progress,
lastCheckpoint,
eventsProcessedAfterRestart,
statusUrl,
stateUrl,
resultUrl,
queryUrl,
enableCommandUrl,
disableCommandUrl,
checkpointStatus,
bufferedEvents,
writePendingEventsBeforeCheckpoint,
writePendingEventsAfterCheckpoint
) {
this.coreProcessingTime = coreProcessingTime;
this.version = version;
this.epoch = epoch;
this.effectiveName = effectiveName;
this.writesInProgress = writesInProgress;
this.readsInProgress = readsInProgress;
this.partitionsCached = partitionsCached;
this.status = status;
this.stateReason = stateReason;
this.name = name;
this.mode = mode;
this.position = position;
this.progress = progress;
this.lastCheckpoint = lastCheckpoint;
this.eventsProcessedAfterRestart = eventsProcessedAfterRestart;
this.statusUrl = statusUrl;
this.stateUrl = stateUrl;
this.resultUrl = resultUrl;
this.queryUrl = queryUrl;
this.enableCommandUrl = enableCommandUrl;
this.disableCommandUrl = disableCommandUrl;
this.checkpointStatus = checkpointStatus;
this.bufferedEvents = bufferedEvents;
this.writePendingEventsBeforeCheckpoint = writePendingEventsBeforeCheckpoint;
this.writePendingEventsAfterCheckpoint = writePendingEventsAfterCheckpoint;
Object.freeze(this);
}
module.exports = ProjectionDetails;

View File

@ -0,0 +1,181 @@
const http = require('http');
const https = require('https');
const url = require('url');
const util = require('util');
const ProjectionCommandFailedError = require('../errors/projectionCommandFailedError');
const HTTP_OK = 200;
const HTTP_CREATED = 201;
function safeParseJson(json) {
try {
return JSON.parse(json);
} catch(e) {
return null;
}
}
function ProjectionsClient(log, operationTimeout, rejectUnauthorized) {
this._log = log;
this._operationTimeout = operationTimeout;
this._rejectUnauthorized = typeof rejectUnauthorized === 'undefined' ? true : !!rejectUnauthorized;
}
ProjectionsClient.prototype.enable = function(httpEndPoint, name, userCredentials) {
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/enable', '', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.disable = function(httpEndPoint, name, userCredentials) {
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/disable', '', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.abort = function(httpEndPoint, name, userCredentials) {
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/abort', '', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.reset = function(httpEndPoint, name, userCredentials) {
return this.sendPost(httpEndPoint + '/projection/' + name + '/command/reset', '', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.createOneTime = function(httpEndPoint, query, userCredentials) {
return this.sendPost(httpEndPoint + '/projections/onetime?type=JS', query, userCredentials, HTTP_CREATED);
};
ProjectionsClient.prototype.createTransient = function(httpEndPoint, name, query, userCredentials) {
return this.sendPost(httpEndPoint + '/projections/transient?name=' + name + '&type=JS', query, userCredentials, HTTP_CREATED);
};
ProjectionsClient.prototype.createContinuous = function(httpEndPoint, name, query, trackEmittedStreams, userCredentials) {
return this.sendPost(httpEndPoint + '/projections/continuous?name=' + name + '&type=JS&emit=1&trackemittedstreams=' + trackEmittedStreams, query, userCredentials, HTTP_CREATED);
};
ProjectionsClient.prototype.listAll = function(httpEndPoint, userCredentials) {
return this.sendGet(httpEndPoint + '/projections/any', userCredentials, HTTP_OK)
.then(function (json) {
var r = safeParseJson(json);
if (r && r.projections) return r.projections;
return null;
});
};
ProjectionsClient.prototype.listOneTime = function(httpEndPoint, userCredentials) {
return this.sendGet(httpEndPoint + '/projections/onetime', userCredentials, HTTP_OK)
.then(function (json) {
var r = safeParseJson(json);
if (r && r.projections) return r.projections;
return null;
});
};
ProjectionsClient.prototype.listContinuous = function(httpEndPoint, userCredentials) {
return this.sendGet(httpEndPoint + '/projections/continuous', userCredentials, HTTP_OK)
.then(function (json) {
var r = safeParseJson(json);
if (r && r.projections) return r.projections;
return null;
});
};
ProjectionsClient.prototype.getStatus = function(httpEndPoint, name, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name, userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getState = function(httpEndPoint, name, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/state', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getPartitionState = function(httpEndPoint, name, partitionId, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/state?partition=' + partitionId, userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getResult = function(httpEndPoint, name, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/result', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getPartitionResult = function(httpEndPoint, name, partitionId, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/result?partition=' + partitionId, userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getStatistics = function(httpEndPoint, name, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/statistics', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.getQuery = function(httpEndPoint, name, userCredentials) {
return this.sendGet(httpEndPoint + '/projection/' + name + '/query', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.updateQuery = function(httpEndPoint, name, query, userCredentials) {
return this.sendPut(httpEndPoint + '/projection/' + name + '/query?type=JS', query, userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.delete = function(httpEndPoint, name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials) {
return this.sendDelete(httpEndPoint + '/projection/' + name + '?deleteStateStream=' + deleteStateStream + '&deleteCheckpointStream=' + deleteCheckpointStream + '&deleteEmittedStreams=' + deleteEmittedStreams, '', userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.request = function(method, _url, data, userCredentials, expectedCode) {
const options = url.parse(_url);
const httplib = options.protocol === 'https:' ? https : http;
options.method = method;
if (userCredentials) {
options.auth = [userCredentials.username, userCredentials.password].join(':');
}
if (!this._rejectUnauthorized) {
options.rejectUnauthorized = false;
}
var self = this;
return new Promise(function (resolve, reject) {
const timeout = setTimeout(function () {
reject(new Error(util.format('Request timed out for %s on %s', method, _url)))
}, self._operationTimeout);
const req = httplib.request(options, function (res) {
const hasExpectedCode = res.statusCode === expectedCode;
var result = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
result += chunk;
});
res.on('end', function () {
if (hasExpectedCode) {
clearTimeout(timeout);
resolve(result);
} else {
clearTimeout(timeout);
reject(new ProjectionCommandFailedError(
res.statusCode,
util.format('Server returned %d (%s) for %s on %s', res.statusCode, res.statusMessage, method, _url)
));
}
});
});
req.on('error', reject);
if (data) {
req.setHeader('Content-Length', data.length);
req.setHeader('Content-Type', 'application/json');
req.write(data);
}
req.end();
});
};
function voidResult() {}
ProjectionsClient.prototype.sendGet = function(_url, userCredentials, expectedCode) {
return this.request('GET', _url, null, userCredentials, expectedCode);
};
ProjectionsClient.prototype.sendPost = function(_url, data, userCredentials, expectedCode) {
return this.request('POST', _url, data, userCredentials, expectedCode)
.then(voidResult);
};
ProjectionsClient.prototype.sendPut = function(_url, data, userCredentials, expectedCode) {
return this.request('PUT', _url, data, userCredentials, expectedCode)
.then(voidResult);
};
ProjectionsClient.prototype.sendDelete = function(_url, data, userCredentials, expectedCode) {
return this.request('DELETE', _url, data, userCredentials, expectedCode)
.then(voidResult);
};
module.exports = ProjectionsClient;

View File

@ -0,0 +1,215 @@
const ensure = require('../common/utils/ensure');
const ProjectionsClient = require('./projectionsClient');
/**
* Creates a new instance of ProjectionsManager.
* @param {Logger} log Instance of Logger to use for logging.
* @param {string} httpEndPoint HTTP endpoint of an Event Store server.
* @param {number} operationTimeout Operation timeout in milliseconds.
* @param {boolean} [rejectUnauthorized] Reject authorized SSL certs (if using SSL) - set to false is using self-signed certs
* @constructor
*/
function ProjectionsManager(log, httpEndPoint, operationTimeout, rejectUnauthorized) {
ensure.notNull(log, "log");
ensure.notNull(httpEndPoint, "httpEndPoint");
this._client = new ProjectionsClient(log, operationTimeout, rejectUnauthorized);
this._httpEndPoint = httpEndPoint;
}
/**
* Enables a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for a user with permission to enable a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.enable = function(name, userCredentials) {
return this._client.enable(this._httpEndPoint, name, userCredentials);
};
/**
* Aborts and disables a projection without writing a checkpoint.
* @param name The name of the projection.
* @param userCredentials Credentials for a user with permission to disable a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.disable = function(name, userCredentials) {
return this._client.disable(this._httpEndPoint, name, userCredentials);
};
/**
* Disables a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for a user with permission to disable a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.abort = function(name, userCredentials) {
return this._client.abort(this._httpEndPoint, name, userCredentials);
};
/**
* Reset a projection. (This will re-emit events, streams that are written to from the projection will also be soft deleted)
* @param name The name of the projection.
* @param userCredentials Credentials for a user with permission to reset a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.reset = function(name, userCredentials) {
return this._client.reset(this._httpEndPoint, name, userCredentials);
};
/**
* Creates a one-time query.
* @param query The JavaScript source code for the query.
* @param userCredentials Credentials for a user with permission to create a query.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.createOneTime = function(query, userCredentials) {
return this._client.createOneTime(this._httpEndPoint, query, userCredentials);
};
/**
* Creates a one-time query.
* @param name A name for the query.
* @param query The JavaScript source code for the query.
* @param userCredentials Credentials for a user with permission to create a query.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.createTransient = function(name, query, userCredentials) {
return this._client.createTransient(this._httpEndPoint, query, userCredentials);
};
/**
* Creates a one-time query.
* @param name The name of the projection.
* @param query The JavaScript source code for the query.
* @param trackEmittedStreams Whether the streams emitted by this projection should be tracked.
* @param userCredentials Credentials for a user with permission to create a query.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.createContinuous = function(name, query, trackEmittedStreams, userCredentials) {
return this._client.createContinuous(this._httpEndPoint, name, query, trackEmittedStreams, userCredentials);
};
/**
* Lists the status of all projections.
* @param userCredentials Credentials for the operation.
* @returns {Promise<ProjectionDetails[]>}
*/
ProjectionsManager.prototype.listAll = function(userCredentials) {
return this._client.listAll(this._httpEndPoint, userCredentials);
};
/**
* Lists the status of all one-time projections.
* @param userCredentials Credentials for the operation.
* @returns {Promise<ProjectionDetails[]>}
*/
ProjectionsManager.prototype.listOneTime = function(userCredentials) {
return this._client.listOneTime(this._httpEndPoint, userCredentials);
};
/**
* Lists the status of all continuous projections.
* @param userCredentials Credentials for the operation.
* @returns {Promise<ProjectionDetails[]>}
*/
ProjectionsManager.prototype.listContinuous = function(userCredentials) {
return this._client.listContinuous(this._httpEndPoint, userCredentials);
};
/**
* Gets the status of a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection status.
*/
ProjectionsManager.prototype.getStatus = function(name, userCredentials) {
return this._client.getStatus(this._httpEndPoint, name, userCredentials);
};
/**
* Gets the state of a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection state.
*/
ProjectionsManager.prototype.getState = function(name, userCredentials) {
return this._client.getState(this._httpEndPoint, name, userCredentials);
};
/**
* Gets the state of a projection for a specified partition.
* @param name The name of the projection.
* @param partitionId The id of the partition.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection state.
*/
ProjectionsManager.prototype.getPartitionState = function(name, partitionId, userCredentials) {
return this._client.getPartitionState(this._httpEndPoint, name, partitionId, userCredentials);
};
/**
* Gets the state of a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection state.
*/
ProjectionsManager.prototype.getResult = function(name, userCredentials) {
return this._client.getResult(this._httpEndPoint, name, userCredentials);
};
/**
* Gets the state of a projection for a specified partition.
* @param name The name of the projection.
* @param partitionId The id of the partition.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection state.
*/
ProjectionsManager.prototype.getPartitionResult = function(name, partitionId, userCredentials) {
return this._client.getPartitionResult(this._httpEndPoint, name, partitionId, userCredentials);
};
/**
* Gets the statistics of a projection.
* @param name The name of the projection.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing projection statistics.
*/
ProjectionsManager.prototype.getStatistics = function(name, userCredentials) {
return this._client.getStatistics(this._httpEndPoint, name, userCredentials);
};
/**
* Gets the status of a query.
* @param name The name of the query.
* @param userCredentials Credentials for the operation.
* @returns {Promise<string>} String of JSON containing query status.
*/
ProjectionsManager.prototype.getQuery = function(name, userCredentials) {
return this._client.getQuery(this._httpEndPoint, name, userCredentials);
};
/**
* Updates the definition of a query.
* @param name The name of the query.
* @param query The JavaScript source code for the query.
* @param userCredentials Credentials for the operation.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.updateQuery = function(name, query, userCredentials) {
return this._client.updateQuery(this._httpEndPoint, name, query, userCredentials);
};
/**
* Updates the definition of a query.
* @param name The name of the projection.
* @param deleteEmittedStreams Whether to delete the streams that were emitted by this projection.
* @param deleteStateStream Where to delete the state stream for this projection
* @param deleteCheckpointStream Where to delete the checkpoint stream for this projection
* @param userCredentials Credentials for a user with permission to delete a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.delete = function(name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials) {
return this._client.delete(this._httpEndPoint, name, deleteEmittedStreams, deleteStateStream, deleteCheckpointStream, userCredentials);
};
module.exports = ProjectionsManager;

View File

@ -1,6 +1,6 @@
const ReadDirection = {
const ReadDirection = Object.freeze({
Forward: 'forward',
Backward: 'backward'
};
});
module.exports = ReadDirection;
module.exports = ReadDirection;

Some files were not shown because too many files have changed in this diff Show More