93 Commits

Author SHA1 Message Date
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
bf89354564 Merge pull request #50 from sledorze/master
fix noopLogger typo fix #49
2017-10-26 10:25:24 -07:00
254ddda8a2 fix noopLogger typo fix #49 2017-10-23 22:11:11 +02:00
16c081020a Add ProjectionsManager 2017-10-18 14:39:25 -07:00
4584517ede Update tests, typescript declaration 2017-10-17 15:55:07 -07:00
e6af223f0d Fix identify client serialize 2017-10-17 14:36:00 -07:00
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
0b968c40e8 Released version 0.1.5 2017-06-30 19:20:24 -07:00
21ab49845f Adding persistent subscription API to typescript definition 2017-06-30 18:54:56 -07:00
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
8735b23bf9 Remove strictness of UUID 2017-06-05 15:23:27 -07:00
f66decdfb3 Rename project to node-eventstore-client 2017-05-17 12:46:27 -07:00
121e248dd8 #35 implement heartbeatInfo event on connection 2017-05-15 12:55:14 -07:00
917b89cf3d Minor fixes after merging PR #33, adding cluster connection example in samples/simple.js 2017-04-26 18:10:27 -07:00
a1b9d6db9b Merge pull request #33 from paullucas/master
Refactor samples
2017-04-26 17:54:03 -07:00
83b88fc015 Add optional require statement for client 2017-04-20 19:40:09 -07:00
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
85814ed03f Fix binary string issue in Large event tests 2017-03-29 13:29:17 -07:00
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
05b36f22bc Published version 0.0.30 2017-03-24 19:05:51 -07:00
217c1c6bb8 Fix a bug in tcpConnection that was blocking sending of packet > 64k. Fixes #27 2017-03-24 19:04:01 -07:00
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
158ca2d0a7 v0.0.28 2017-02-25 14:12:13 -08:00
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
cf196b9cc2 Update README and publish 0.0.25 2017-01-29 11:09:09 -08:00
b9de9568a5 Adding code style for jetbrains IDEs 2017-01-29 11:01:12 -08:00
e2459321f6 Fixing small bugs in the reconnection logic 2017-01-29 11:00:40 -08:00
5d6ec8b43f 19: Implement cluster discovery using dns. Adding missing file. 2017-01-29 10:59:55 -08:00
1558918692 19: Implement cluster discovery using dns 2017-01-29 10:57:59 -08:00
0463d85cfe Switch back non-cluster tests to localhost 2017-01-28 18:35:35 -08:00
faed60b8ac Updated README, bumped to version 0.0.24 2017-01-28 18:28:36 -08:00
006b5b4791 18 - Changing all hostname to host, fixing some async issues in tests 2017-01-28 18:04:58 -08:00
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
51adf5bcc8 Adding index.d.ts to files, version 0.0.13 2016-11-14 22:49:49 -08:00
0fbbecea06 Merge branch '13-create-typescript-declarations' 2016-11-14 22:44:14 -08:00
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
9909bc053a Adding .idea files 2016-11-14 21:31:43 -08:00
269c89be29 Add Documentation section to README 2016-11-07 22:09:57 -08:00
99d9322d2a Merge branch '12-create-docs' 2016-11-07 21:56:04 -08:00
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
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
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
4a593053ad Merge pull request #7 from Adaptech/master
Samples for subscriptions, README.
2016-11-01 09:20:32 -07:00
5569dffaf5 Subscription samples. README. 2016-11-01 08:24:38 -07:00
bc66e4bc3c Samples: Subscriptions.
Subscribe to all events & subscribe to all events, catching up from the beginning.
2016-11-01 08:20:39 -07:00
39d5188f14 Samples: Storing events and subscribing. 2016-10-31 09:25:27 -07:00
93e61434dd Update README for tests and fix tcp port for default 2016-10-30 09:44:29 -07:00
29ee990a0a Merge pull request #6 from Adaptech/master
Getting Started - example
2016-10-30 09:33:08 -07:00
4f066bf00e Brevity. 2016-10-29 16:41:08 -07:00
69842b8d44 Getting Started. 2016-10-29 14:34:29 -07:00
51d5782387 Getting started example 2016-10-29 14:30:39 -07:00
830a317f0d Bumping version to 0.0.7, exposing loggers, minor bug fixes, cluster connection (wip) 2016-10-20 04:40:46 -07:00
f951a625f4 Performance improvement by using strict equality, fixed heartbeat issue in connection stage 2016-10-17 21:58:28 -07:00
dd1302f641 Implemented connection to cluster using gossip seeds 2016-10-15 15:41:25 -07:00
4ea996781f Adding tests 2016-10-14 22:53:23 -07:00
b2504749ce Adding transaction tests 2016-03-18 14:04:07 -07:00
101 changed files with 17983 additions and 8448 deletions

11
.gitignore vendored
View File

@ -33,4 +33,13 @@ node_modules
.node_repl_history .node_repl_history
# InteliJ # InteliJ
.idea .idea/workspace.xml
.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>

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="ES5" />
</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>

134
README.md
View File

@ -1,65 +1,133 @@
# eventstore-node # node-eventstore-client
A port of the EventStore .Net ClientAPI to Node.js A port of the EventStore .Net ClientAPI to Node.js
## Dependencies
- 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`)
## Status ## Status
Unstable
### Missing features: ### Missing features:
- Ssl connection - Ssl connection
- Cluster connection
- Set system settings - Set system settings
### Incomplete ### Areas to improve
- Typed errors: currently most errors are direct instance of Error, which is not practical for error handling - Errors
- Performance: there's still some while loop in the code that could be problematic with node.js - Use codes or types to differentiate between errors
- Tests: tests are only covering happy path scenarios for now - Performance
- Performance hasn't been tested yet
- Tests
- Can always do with more tests
## Getting started ## Getting started
Install the client using npm Install using `npm install node-eventstore-client`
npm install eventstore-node ### Dependencies
Examples - Node.js >= 4.0
- 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`)
TODO ### Install & run an Eventstore on localhost
See https://eventstore.org/docs/introduction/4.0.2/
*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 The offline documentation can be found in the module folder `./node_modules/node-eventstore-client/docs`.
.appendToStream('myStream', client.expectedVersion.any, events, userCredentials)
.then(function(result) { #### Online
//Do something with the WriteResult here
}) The online documentation can be found at [https://dev.nicdex.com/node-eventstore-client/docs/](https://dev.nicdex.com/node-eventstore-client/docs/)
.catch(function(err) {
//Handle error here ### 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 ## Running the tests
To run the tests you will need 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 install the dependencies (`npm install`) EventStore.ClusterNode.exe --run-projections=all --memdb
- 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 execute the tests suites simply run test with npm To execute the tests suites simply run
npm test npm test
## 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 ## 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. 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

367
index.d.ts vendored Normal file
View File

@ -0,0 +1,367 @@
/// <reference types="node" />
/// <reference types="Long" />
export class Position {
constructor(commitPosition: number|Long, preparePosition: number|Long);
readonly commitPosition: Long;
readonly preparePosition: Long;
}
// Expose classes
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;
}
// 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;
}
// Expose enums/constants
export namespace expectedVersion {
const any: number;
const noStream: number;
const emptyStream: number;
}
export namespace positions {
const start: Position;
const end: Position;
}
// 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 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 {
connectionId: string;
remoteEndPoint: TcpEndPoint;
requestSentAt: number;
requestPkgNumber: number;
responseReceivedAt: number;
responsePkgNumber: number;
}
export interface EventData {
readonly eventId: string;
readonly type: string;
readonly isJson: boolean;
readonly data: Buffer;
readonly metadata: Buffer;
}
export interface EventStoreNodeConnection {
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?: PersistentSubscriptionSettings): Promise<PersistentSubscriptionCreateResult>;
updatePersistentSubscription(stream: string, groupName: string, settings: PersistentSubscriptionSettings, userCredentials?: PersistentSubscriptionSettings): Promise<PersistentSubscriptionUpdateResult>;
deletePersistentSubscription(stream: string, groupName: string, userCredentials?: PersistentSubscriptionSettings): 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>;
on(event: "connected" | "disconnected" | "reconnecting" | "closed" | "error" | "heartbeatInfo", listener: (arg: Error | string | TcpEndPoint | HeartbeatInfo) => void): this;
once(event: "connected" | "disconnected" | "reconnecting" | "closed" | "error" | "heartbeatInfo", listener: (arg: Error | string | TcpEndPoint | HeartbeatInfo) => void): this;
}
// 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,
externalGossipPort?: number,
gossipTimeout?: number
}
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;
// Projections
export interface ProjectionDetails {
coreProcessingTime: number,
version: number,
epoch: number,
effectiveName: string,
writesInProgress: number,
readsInProgress: number,
partitionsCached: number,
status: string,
stateReason: string,
name: string,
mode: string,
position: string,
progress: number,
lastCheckpoint: string,
eventsProcessedAfterRestart: number,
statusUrl: string,
stateUrl: string,
resultUrl: string,
queryUrl: string,
enableCommandUrl: string,
disableCommandUrl: string,
checkpointStatus: string,
bufferedEvents: number,
writePendingEventsBeforeCheckpoint: number,
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>;
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>;
deleteQuery(name: string, deleteEmittedStreams: boolean, userCredentials: UserCredentials): Promise<void>;
}

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 README.md for more details
* see LICENSE for license info * 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'); module.exports = require('./lib/dist.js');

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,37 @@
{ {
"name": "eventstore-node", "name": "node-eventstore-client",
"version": "0.0.2", "version": "0.2.1",
"description": "A port of the EventStore .Net ClientAPI to Node.js", "description": "A port of the EventStore .Net ClientAPI to Node.js",
"main": "index.js", "main": "index.js",
"types": "index.d.ts",
"engines": {
"node": ">=4.0"
},
"engineStrict": true,
"scripts": { "scripts": {
"test": "nodeunit" "clean": "rm lib/dist.js",
"build": "webpack",
"pretest": "npm run build",
"test": "nodeunit",
"test-debug": "TESTS_VERBOSE_LOGGING=1 nodeunit",
"prepublish": "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": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/nicdex/eventstore-node.git" "url": "git+https://github.com/nicdex/node-eventstore-client.git"
}, },
"keywords": [ "keywords": [
"eventstore", "eventstore",
@ -18,16 +41,19 @@
"author": "Nicolas Dextraze", "author": "Nicolas Dextraze",
"license": "MIT", "license": "MIT",
"bugs": { "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": { "dependencies": {
"long": "^3.0.3", "@types/long": "^3.0.31",
"protobufjs": "^5.0.1", "@types/node": "^6.0.47",
"uuid": "^2.0.1" "long": "^3.2",
"protobufjs": "^6.7.3",
"uuid": "^3.0.1"
}, },
"engines": { "devDependencies": {
"node": ">=0.12" "jsdoc": "^3.5.3",
}, "nodeunit": "^0.11.1",
"engineStrict": true "webpack": "^3.3.0"
}
} }

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,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

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

View File

@ -1,5 +1,5 @@
var util = require('util'); var util = require('util');
var uuid = require('uuid'); var guidParse = require('../common/guid-parse');
var TcpCommand = require('../systemData/tcpCommand'); var TcpCommand = require('../systemData/tcpCommand');
var InspectionDecision = require('../systemData/inspectionDecision'); var InspectionDecision = require('../systemData/inspectionDecision');
@ -26,17 +26,18 @@ util.inherits(AppendToStreamOperation, OperationBase);
AppendToStreamOperation.prototype._createRequestDto = function() { AppendToStreamOperation.prototype._createRequestDto = function() {
var dtos = this._events.map(function(ev) { var dtos = this._events.map(function(ev) {
var eventId = new Buffer(uuid.parse(ev.eventId)); var eventId = guidParse.parse(ev.eventId);
return new ClientMessage.NewEvent({ return {
event_id: eventId, event_type: ev.type, eventId: eventId, eventType: ev.type,
data_content_type: ev.isJson ? 1 : 0, metadata_content_type: 0, dataContentType: ev.isJson ? 1 : 0, metadataContentType: 0,
data: ev.data, metadata: ev.metadata}); data: ev.data, metadata: ev.metadata
};
}); });
return new ClientMessage.WriteEvents({ return new ClientMessage.WriteEvents({
event_stream_id: this._stream, eventStreamId: this._stream,
expected_version: this._expectedVersion, expectedVersion: this._expectedVersion,
events: dtos, events: dtos,
require_master: this._requireMaster}); requireMaster: this._requireMaster});
}; };
AppendToStreamOperation.prototype._inspectResponse = function(response) { AppendToStreamOperation.prototype._inspectResponse = function(response) {
@ -72,7 +73,7 @@ AppendToStreamOperation.prototype._inspectResponse = function(response) {
}; };
AppendToStreamOperation.prototype._transformResponse = 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() { AppendToStreamOperation.prototype.toString = function() {

View File

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

View File

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

View File

@ -36,11 +36,24 @@ function CreatePersistentSubscriptionOperation(log, cb, stream, groupName, setti
util.inherits(CreatePersistentSubscriptionOperation, OperationBase); util.inherits(CreatePersistentSubscriptionOperation, OperationBase);
CreatePersistentSubscriptionOperation.prototype._createRequestDto = function() { CreatePersistentSubscriptionOperation.prototype._createRequestDto = function() {
return new ClientMessage.CreatePersistentSubscription(this._groupName, this._stream, this._resolveLinkTos, return new ClientMessage.CreatePersistentSubscription({
this._startFromBeginning, this._messageTimeoutMilliseconds, this._recordStatistics, this._liveBufferSize, subscriptionGroupName: this._groupName,
this._readBatchSize, this._bufferSize, this._maxRetryCount, eventStreamId: this._stream,
this._namedConsumerStrategy == SystemConsumerStrategies.RoundRobin, this._checkPointAfter, resolveLinkTos: this._resolveLinkTos,
this._maxCheckPointCount, this._minCheckPointCount, this._maxSubscriberCount, this._namedConsumerStrategy); 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) { CreatePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {

View File

@ -21,7 +21,10 @@ function DeletePersistentSubscriptionOperation(log, cb, stream, groupName, userC
util.inherits(DeletePersistentSubscriptionOperation, OperationBase); util.inherits(DeletePersistentSubscriptionOperation, OperationBase);
DeletePersistentSubscriptionOperation.prototype._createRequestDto = function() { 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) { DeletePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {

View File

@ -25,7 +25,12 @@ function DeleteStreamOperation(log, cb, requireMaster, stream, expectedVersion,
util.inherits(DeleteStreamOperation, OperationBase); util.inherits(DeleteStreamOperation, OperationBase);
DeleteStreamOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster,
hardDelete: this._hardDelete
});
}; };
DeleteStreamOperation.prototype._inspectResponse = function(response) { DeleteStreamOperation.prototype._inspectResponse = function(response) {
@ -58,7 +63,7 @@ DeleteStreamOperation.prototype._inspectResponse = function(response) {
}; };
DeleteStreamOperation.prototype._transformResponse = 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() { DeleteStreamOperation.prototype.toString = function() {

View File

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

View File

@ -25,7 +25,13 @@ function ReadAllEventsBackwardOperation(
util.inherits(ReadAllEventsBackwardOperation, OperationBase); util.inherits(ReadAllEventsBackwardOperation, OperationBase);
ReadAllEventsBackwardOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster
});
}; };
ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) { ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) {
@ -48,8 +54,8 @@ ReadAllEventsBackwardOperation.prototype._inspectResponse = function(response) {
ReadAllEventsBackwardOperation.prototype._transformResponse = function(response) { ReadAllEventsBackwardOperation.prototype._transformResponse = function(response) {
return new results.AllEventsSlice( return new results.AllEventsSlice(
ReadDirection.Backward, ReadDirection.Backward,
new results.Position(response.commit_position, response.prepare_position), new results.Position(response.commitPosition, response.preparePosition),
new results.Position(response.next_commit_position, response.next_prepare_position), new results.Position(response.nextCommitPosition, response.nextPreparePosition),
response.events response.events
) )
}; };

View File

@ -25,7 +25,13 @@ function ReadAllEventsForwardOperation(
util.inherits(ReadAllEventsForwardOperation, OperationBase); util.inherits(ReadAllEventsForwardOperation, OperationBase);
ReadAllEventsForwardOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster
});
}; };
ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) { ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) {
@ -48,8 +54,8 @@ ReadAllEventsForwardOperation.prototype._inspectResponse = function(response) {
ReadAllEventsForwardOperation.prototype._transformResponse = function(response) { ReadAllEventsForwardOperation.prototype._transformResponse = function(response) {
return new results.AllEventsSlice( return new results.AllEventsSlice(
ReadDirection.Forward, ReadDirection.Forward,
new results.Position(response.commit_position, response.prepare_position), new results.Position(response.commitPosition, response.preparePosition),
new results.Position(response.next_commit_position, response.next_prepare_position), new results.Position(response.nextCommitPosition, response.nextPreparePosition),
response.events response.events
) )
}; };

View File

@ -21,7 +21,12 @@ function ReadEventOperation(log, cb, stream, eventNumber, resolveLinkTos, requir
util.inherits(ReadEventOperation, OperationBase); util.inherits(ReadEventOperation, OperationBase);
ReadEventOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster
});
}; };
ReadEventOperation.prototype._inspectResponse = function(response) { ReadEventOperation.prototype._inspectResponse = function(response) {

View File

@ -27,7 +27,13 @@ function ReadStreamEventsBackwardOperation(
util.inherits(ReadStreamEventsBackwardOperation, OperationBase); util.inherits(ReadStreamEventsBackwardOperation, OperationBase);
ReadStreamEventsBackwardOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster
});
}; };
ReadStreamEventsBackwardOperation.prototype._inspectResponse = function(response) { ReadStreamEventsBackwardOperation.prototype._inspectResponse = function(response) {
@ -60,9 +66,9 @@ ReadStreamEventsBackwardOperation.prototype._transformResponse = function(respon
this._fromEventNumber, this._fromEventNumber,
ReadDirection.Backward, ReadDirection.Backward,
response.events, response.events,
response.next_event_number, response.nextEventNumber,
response.last_event_number, response.lastEventNumber,
response.is_end_of_stream response.isEndOfStream
) )
}; };

View File

@ -27,7 +27,13 @@ function ReadStreamEventsForwardOperation(
util.inherits(ReadStreamEventsForwardOperation, OperationBase); util.inherits(ReadStreamEventsForwardOperation, OperationBase);
ReadStreamEventsForwardOperation.prototype._createRequestDto = function() { 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,
requireMaster: this._requireMaster
});
}; };
ReadStreamEventsForwardOperation.prototype._inspectResponse = function(response) { ReadStreamEventsForwardOperation.prototype._inspectResponse = function(response) {
@ -60,9 +66,9 @@ ReadStreamEventsForwardOperation.prototype._transformResponse = function(respons
this._fromEventNumber, this._fromEventNumber,
ReadDirection.Forward, ReadDirection.Forward,
response.events, response.events,
response.next_event_number, response.nextEventNumber,
response.last_event_number, response.lastEventNumber,
response.is_end_of_stream response.isEndOfStream
) )
}; };

View File

@ -7,7 +7,9 @@ var InspectionResult = require('./../systemData/inspectionResult');
var ClientMessage = require('../messages/clientMessage'); var ClientMessage = require('../messages/clientMessage');
var EventStoreTransaction = require('../eventStoreTransaction'); var EventStoreTransaction = require('../eventStoreTransaction');
var results = require('../results'); var results = require('../results');
var AccessDeniedError = require('../errors/accessDeniedError');
var WrongExpectedVersionError = require('../errors/wrongExpectedVersionError');
var StreamDeletedError = require('../errors/streamDeletedError');
var OperationBase = require('../clientOperations/operationBase'); var OperationBase = require('../clientOperations/operationBase');
function StartTransactionOperation(log, cb, requireMaster, stream, expectedVersion, parentConnection, userCredentials) { function StartTransactionOperation(log, cb, requireMaster, stream, expectedVersion, parentConnection, userCredentials) {
@ -22,7 +24,11 @@ function StartTransactionOperation(log, cb, requireMaster, stream, expectedVersi
util.inherits(StartTransactionOperation, OperationBase); util.inherits(StartTransactionOperation, OperationBase);
StartTransactionOperation.prototype._createRequestDto = function() { StartTransactionOperation.prototype._createRequestDto = function() {
return new ClientMessage.TransactionStart(this._stream, this._expectedVersion, this._requireMaster); return new ClientMessage.TransactionStart({
eventStreamId: this._stream,
expectedVersion: this._expectedVersion,
requireMaster: this._requireMaster
});
}; };
StartTransactionOperation.prototype._inspectResponse = function(response) { StartTransactionOperation.prototype._inspectResponse = function(response) {
@ -38,17 +44,16 @@ StartTransactionOperation.prototype._inspectResponse = function(response) {
case ClientMessage.OperationResult.ForwardTimeout: case ClientMessage.OperationResult.ForwardTimeout:
return new InspectionResult(InspectionDecision.Retry, "ForwardTimeout"); return new InspectionResult(InspectionDecision.Retry, "ForwardTimeout");
case ClientMessage.OperationResult.WrongExpectedVersion: 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 WrongExpectedVersionError("Start transaction", this._stream, this._expectedVersion));
this.fail(new Error(err));
return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion"); return new InspectionResult(InspectionDecision.EndOperation, "WrongExpectedVersion");
case ClientMessage.OperationResult.StreamDeleted: case ClientMessage.OperationResult.StreamDeleted:
this.fail(new Error("Stream deleted: " + this._stream)); this.fail(new StreamDeletedError(this._stream));
return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted"); return new InspectionResult(InspectionDecision.EndOperation, "StreamDeleted");
case ClientMessage.OperationResult.InvalidTransaction: case ClientMessage.OperationResult.InvalidTransaction:
this.fail(new Error("Invalid transaction.")); this.fail(new Error("Invalid transaction."));
return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction"); return new InspectionResult(InspectionDecision.EndOperation, "InvalidTransaction");
case ClientMessage.OperationResult.AccessDenied: 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"); return new InspectionResult(InspectionDecision.EndOperation, "AccessDenied");
default: default:
throw new Error(util.format("Unexpected OperationResult: %s.", response.result)); throw new Error(util.format("Unexpected OperationResult: %s.", response.result));
@ -56,7 +61,7 @@ StartTransactionOperation.prototype._inspectResponse = function(response) {
}; };
StartTransactionOperation.prototype._transformResponse = 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() { StartTransactionOperation.prototype.toString = function() {

View File

@ -46,7 +46,7 @@ SubscriptionOperation.prototype._enqueueSend = function(pkg) {
SubscriptionOperation.prototype.subscribe = function(correlationId, connection) { SubscriptionOperation.prototype.subscribe = function(correlationId, connection) {
if (connection === null) throw new TypeError("connection is null."); if (connection === null) throw new TypeError("connection is null.");
if (this._subscription != null || this._unsubscribed != 0) if (this._subscription !== null || this._unsubscribed)
return false; return false;
this._correlationId = correlationId; this._correlationId = correlationId;
@ -64,7 +64,7 @@ SubscriptionOperation.prototype.unsubscribe = function() {
SubscriptionOperation.prototype._createUnsubscriptionPackage = function() { SubscriptionOperation.prototype._createUnsubscriptionPackage = function() {
var msg = new ClientMessage.UnsubscribeFromStream(); 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); return new TcpPackage(TcpCommand.UnsubscribeFromStream, TcpFlags.None, this._correlationId, null, null, data);
}; };
@ -128,7 +128,7 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
case TcpCommand.NotHandled: case TcpCommand.NotHandled:
{ {
if (this._subscription != null) if (this._subscription !== null)
throw new Error("NotHandled command appeared while we already subscribed."); throw new Error("NotHandled command appeared while we already subscribed.");
var message = ClientMessage.NotHandled.decode(pkg.data.toBuffer()); var message = ClientMessage.NotHandled.decode(pkg.data.toBuffer());
@ -141,10 +141,10 @@ SubscriptionOperation.prototype.inspectPackage = function(pkg) {
return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy"); return new InspectionResult(InspectionDecision.Retry, "NotHandled - TooBusy");
case ClientMessage.NotHandled.NotHandledReason.NotMaster: case ClientMessage.NotHandled.NotHandledReason.NotMaster:
var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additional_info); var masterInfo = ClientMessage.NotHandled.MasterInfo.decode(message.additionalInfo);
return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster", return new InspectionResult(InspectionDecision.Reconnect, "NotHandled - NotMaster",
{host: masterInfo.external_tcp_address, port: masterInfo.external_tcp_port}, {host: masterInfo.externalTcpAddress, port: masterInfo.externalTcpPort},
{host: masterInfo.external_secure_tcp_address, port: masterInfo.external_secure_tcp_port}); {host: masterInfo.externalSecureTcpAddress, port: masterInfo.externalSecureTcpPort});
default: default:
this._log.error("Unknown NotHandledReason: %s.", message.reason); this._log.error("Unknown NotHandledReason: %s.", message.reason);
@ -189,7 +189,6 @@ SubscriptionOperation.prototype.dropSubscription = function(reason, err, connect
if (reason !== SubscriptionDropReason.UserInitiated && this._subscription === null) if (reason !== SubscriptionDropReason.UserInitiated && this._subscription === null)
{ {
if (err === null) throw new Error(util.format("No exception provided for subscription drop reason '%s", reason)); 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); this._cb(err);
return; return;
} }
@ -233,7 +232,7 @@ SubscriptionOperation.prototype._onEventAppeared = function(e) {
e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition); e.originalStreamId, e.originalEventNumber, e.originalEvent.eventType, e.originalPosition);
var self = this; 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) { SubscriptionOperation.prototype._executeAction = function(action) {
@ -245,21 +244,30 @@ SubscriptionOperation.prototype._executeAction = function(action) {
}; };
SubscriptionOperation.prototype._executeActions = function() { SubscriptionOperation.prototype._executeActions = function() {
//TODO: possible blocking loop for node.js
var action = this._actionQueue.shift(); var action = this._actionQueue.shift();
while (action) if (!action) {
{ this._actionExecuting = false;
try return;
{ }
action(); var promise;
} try
catch (err) {
{ promise = action();
this._log.error(err, "Exception during executing user callback: %s.", err.message); }
} catch (err)
action = this._actionQueue.shift(); {
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() { SubscriptionOperation.prototype.toString = function() {

View File

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

View File

@ -36,11 +36,24 @@ function UpdatePersistentSubscriptionOperation(log, cb, stream, groupName, setti
util.inherits(UpdatePersistentSubscriptionOperation, OperationBase); util.inherits(UpdatePersistentSubscriptionOperation, OperationBase);
UpdatePersistentSubscriptionOperation.prototype._createRequestDto = function() { UpdatePersistentSubscriptionOperation.prototype._createRequestDto = function() {
return new ClientMessage.UpdatePersistentSubscription(this._groupName, this._stream, this._resolveLinkTos, return new ClientMessage.UpdatePersistentSubscription({
this._startFromBeginning, this._messageTimeoutMilliseconds, this._recordStatistics, this._liveBufferSize, subscriptionGroupName: this._groupName,
this._readBatchSize, this._bufferSize, this._maxRetryCount, eventStreamId: this._stream,
this._namedConsumerStrategy == SystemConsumerStrategies.RoundRobin, this._checkPointAfter, resolveLinkTos: this._resolveLinkTos,
this._maxCheckPointCount, this._minCheckPointCount, this._maxSubscriberCount, this._namedConsumerStrategy); 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) { UpdatePersistentSubscriptionOperation.prototype._inspectResponse = function(response) {

View File

@ -9,7 +9,7 @@ var BufferSegment = require('../common/bufferSegment');
var InspectionDecision = require('../systemData/inspectionDecision'); var InspectionDecision = require('../systemData/inspectionDecision');
var InspectionResult = require('./../systemData/inspectionResult'); var InspectionResult = require('./../systemData/inspectionResult');
var results = require('../results'); var results = require('../results');
var VolatileEventStoreSubscription = require('../volatileEventStoreConnection'); var VolatileEventStoreSubscription = require('../volatileEventStoreSubscription');
function VolatileSubscriptionOperation( function VolatileSubscriptionOperation(
log, cb, streamId, resolveLinkTos, userCredentials, eventAppeared, log, cb, streamId, resolveLinkTos, userCredentials, eventAppeared,
@ -20,23 +20,26 @@ function VolatileSubscriptionOperation(
util.inherits(VolatileSubscriptionOperation, SubscriptionOperation); util.inherits(VolatileSubscriptionOperation, SubscriptionOperation);
VolatileSubscriptionOperation.prototype._createSubscriptionPackage = function() { 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, return new TcpPackage(TcpCommand.SubscribeToStream,
this._userCredentials != null ? TcpFlags.Authenticated : TcpFlags.None, this._userCredentials !== null ? TcpFlags.Authenticated : TcpFlags.None,
this._correlationId, this._correlationId,
this._userCredentials != null ? this._userCredentials.username : null, this._userCredentials !== null ? this._userCredentials.username : null,
this._userCredentials != null ? this._userCredentials.password : null, this._userCredentials !== null ? this._userCredentials.password : null,
new BufferSegment(dto.toBuffer())); new BufferSegment(ClientMessage.SubscribeToStream.encode(dto).finish()));
}; };
VolatileSubscriptionOperation.prototype._inspectPackage = function(pkg) { VolatileSubscriptionOperation.prototype._inspectPackage = function(pkg) {
try { try {
if (pkg.command == TcpCommand.SubscriptionConfirmation) { if (pkg.command === TcpCommand.SubscriptionConfirmation) {
var dto = ClientMessage.SubscriptionConfirmation.decode(pkg.data.toBuffer()); 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"); return new InspectionResult(InspectionDecision.Subscribed, "SubscriptionConfirmation");
} }
if (pkg.command == TcpCommand.StreamEventAppeared) { if (pkg.command === TcpCommand.StreamEventAppeared) {
var dto = ClientMessage.StreamEventAppeared.decode(pkg.data.toBuffer()); var dto = ClientMessage.StreamEventAppeared.decode(pkg.data.toBuffer());
this._onEventAppeared(new results.ResolvedEvent(dto.event)); this._onEventAppeared(new results.ResolvedEvent(dto.event));
return new InspectionResult(InspectionDecision.DoNothing, "StreamEventAppeared"); return new InspectionResult(InspectionDecision.DoNothing, "StreamEventAppeared");

View File

@ -1,5 +1,6 @@
/** /**
* Create a buffer segment * Create a buffer segment
* @private
* @param {Buffer} buf * @param {Buffer} buf
* @param {number} [offset] * @param {number} [offset]
* @param {number} [count] * @param {number} [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 || new Buffer(16);
s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
if (ii < 16) { // Don't overflow!
buf[i + ii++] = _hexToByte[oct];
}
});
var buf2 = new Buffer(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 * @constructor
* @property {number} length * @property {number} length
*/ */

View File

@ -6,7 +6,7 @@ function FileLogger(filePath, append) {
this._filePath = filePath; this._filePath = filePath;
if (!append) { if (!append) {
try { try {
fs.unlink(filePath); fs.unlinkSync(filePath);
} catch(e) {} } catch(e) {}
} }
} }

View File

@ -12,5 +12,6 @@ const SystemMetadata = {
userStreamAcl: '$userStreamAcl', userStreamAcl: '$userStreamAcl',
systemStreamAcl: '$systemStreamAcl' systemStreamAcl: '$systemStreamAcl'
}; };
Object.freeze(SystemMetadata);
module.exports = SystemMetadata; module.exports = SystemMetadata;

View File

@ -1,3 +1,5 @@
var Long = require('long');
module.exports.notNullOrEmpty = function(value, name) { module.exports.notNullOrEmpty = function(value, name) {
if (value === null) if (value === null)
throw new TypeError(name + " should not be null."); throw new TypeError(name + " should not be null.");
@ -10,21 +12,31 @@ module.exports.notNull = function(value, name) {
throw new TypeError(name + " should not be null."); throw new TypeError(name + " should not be null.");
}; };
module.exports.isInteger = function(value, name) { function isInteger(value, name) {
if (typeof value !== 'number' || value % 1 !== 0) if (typeof value !== 'number' || value % 1 !== 0)
throw new TypeError(name + " should be an integer."); 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) { module.exports.isArrayOf = function(expectedType, value, name) {
if (!Array.isArray(value)) if (!Array.isArray(value))
throw new TypeError(name + " should be an array."); throw new TypeError(name + " should be an array.");
if (!value.every(function(x) { return x instanceof expectedType; })) if (!value.every(function(x) { return x instanceof expectedType; }))
throw new TypeError([name, " should be an array of ", expectedType, "."].join("")); throw new TypeError([name, " should be an array of ", expectedType.name, "."].join(""));
}; };
module.exports.isTypeOf = function(expectedType, value, name) { module.exports.isTypeOf = function(expectedType, value, name, nullAllowed) {
if (nullAllowed && value === null) return;
if (!(value instanceof expectedType)) if (!(value instanceof expectedType))
throw new TypeError([name, " should be of type '", expectedType, "'."].join("")); throw new TypeError([name, " should be of type '", expectedType.name, "'", nullAllowed ? " or null": "", "."].join(""));
}; };
module.exports.positive = function(value, name) { module.exports.positive = function(value, name) {

View File

@ -0,0 +1,254 @@
var http = require('http');
var util = require('util');
var dns = require('dns');
var GossipSeed = require('../gossipSeed');
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
}
});
}
function ClusterDnsEndPointDiscoverer(log, clusterDns, maxDiscoverAttempts, managerExternalHttpPort, gossipSeeds, gossipTimeout) {
if (!clusterDns && (!gossipSeeds || gossipSeeds.length === 0)) throw new Error('Both clusterDns and gossipSeeds are null/empty.');
this._log = log;
this._clusterDns = clusterDns;
this._maxDiscoverAttempts = maxDiscoverAttempts;
this._managerExternalHttpPort = managerExternalHttpPort;
this._gossipSeeds = gossipSeeds;
this._gossipTimeout = gossipTimeout;
this._oldGossip = null;
}
ClusterDnsEndPointDiscoverer.prototype.discover = function(failedTcpEndPoint) {
var attempt = 1;
var self = this;
function discover(resolve, reject) {
self._discoverEndPoint(failedTcpEndPoint)
.then(function (endPoints) {
if (!endPoints)
self._log.info(util.format("Discovering attempt %d/%d failed: no candidate found.", attempt, self._maxDiscoverAttempts));
return endPoints;
})
.catch(function (exc) {
self._log.info(util.format("Discovering attempt %d/%d failed with error: %s.\n%s", attempt, self._maxDiscoverAttempts, exc, exc.stack));
})
.then(function (endPoints) {
if (endPoints)
return resolve(endPoints);
if (attempt++ === self._maxDiscoverAttempts)
return reject(new Error('Failed to discover candidate in ' + self._maxDiscoverAttempts + ' attempts.'));
setTimeout(discover, 500, resolve, reject);
});
}
return new Promise(function (resolve, reject) {
discover(resolve, reject);
});
};
/**
* Discover Cluster endpoints
* @param {Object} failedTcpEndPoint
* @returns {Promise.<NodeEndPoints>}
* @private
*/
ClusterDnsEndPointDiscoverer.prototype._discoverEndPoint = function (failedTcpEndPoint) {
try {
var mainPromise = this._oldGossip
? Promise.resolve(this._getGossipCandidatesFromOldGossip(this._oldGossip, failedTcpEndPoint))
: this._getGossipCandidatesFromDns();
var self = this;
var j = 0;
return mainPromise.then(function (gossipCandidates) {
var loopPromise = Promise.resolve();
for (var i = 0; i < gossipCandidates.length; i++) {
loopPromise = loopPromise.then(function (endPoints) {
if (endPoints) return endPoints;
return self._tryGetGossipFrom(gossipCandidates[j++])
.then(function (gossip) {
if (!gossip || !gossip.members || gossip.members.length === 0)
return;
var bestNode = self._tryDetermineBestNode(gossip.members);
if (bestNode) {
self._oldGossip = gossip.members;
return bestNode;
}
});
});
}
return loopPromise;
});
} catch (e) {
return Promise.reject(e);
}
};
ClusterDnsEndPointDiscoverer.prototype._getGossipCandidatesFromOldGossip = function (oldGossip, failedTcpEndPoint) {
if (!failedTcpEndPoint) return this._arrangeGossipCandidates(oldGossip);
var gossipCandidates = oldGossip.filter(function(x) {
return !(x.externalTcpPort === failedTcpEndPoint.port && x.externalTcpIp === failedTcpEndPoint.host);
});
return this._arrangeGossipCandidates(gossipCandidates);
};
ClusterDnsEndPointDiscoverer.prototype._arrangeGossipCandidates = function (members) {
var result = new Array(members.length);
var i = -1;
var j = members.length;
for (var k = 0; k < members.length; ++k)
{
if (members[k].state === 'Manager')
result[--j] = new GossipSeed({host: members[k].externalHttpIp, port: members[k].externalHttpPort});
else
result[++i] = new GossipSeed({host: members[k].externalHttpIp, port: members[k].externalHttpPort});
}
this._randomShuffle(result, 0, i); // shuffle nodes
this._randomShuffle(result, j, members.length - 1); // shuffle managers
return result;
};
ClusterDnsEndPointDiscoverer.prototype._getGossipCandidatesFromDns = function () {
var self = this;
return new Promise(function (resolve, reject) {
if (self._gossipSeeds && self._gossipSeeds.length > 0) {
var endpoints = self._gossipSeeds;
self._randomShuffle(endpoints, 0, endpoints.length - 1);
resolve(endpoints);
}
else {
const dnsOptions = {
family: 4,
hints: dns.ADDRCONFIG | dns.V4MAPPED,
all: true
};
dns.lookup(self._clusterDns, dnsOptions, function (err, addresses) {
if (err) {
return reject(err);
}
if (!addresses || addresses.length === 0) {
return reject(new Error('No result from dns lookup for ' + self._clusterDns));
}
var endpoints = addresses.map(function (x) {
return new GossipSeed({host: x.address, port: self._managerExternalHttpPort});
});
resolve(endpoints);
});
}
});
};
ClusterDnsEndPointDiscoverer.prototype._tryGetGossipFrom = function (endPoint) {
var options = {
host: endPoint.endPoint.host,
port: endPoint.endPoint.port,
path: '/gossip?format=json'
};
if (endPoint.hostHeader) {
options.headers = {'Host': endPoint.hostHeader};
}
this._log.info('Try get gossip from', endPoint);
var self = this;
return new Promise(function (resolve, reject) {
var timedout = false;
http.request(options, function (res) {
if (timedout) return;
var result = '';
if (res.statusCode !== 200) {
self._log.info('Trying to get gossip from', endPoint, 'failed with status code:', res.statusCode);
resolve();
return;
}
res.on('data', function (chunk) {
result += chunk.toString();
});
res.on('end', function () {
try {
result = JSON.parse(result);
} catch (e) {
return resolve();
}
resolve(result);
});
})
.setTimeout(self._gossipTimeout, function () {
self._log.info('Trying to get gossip from', endPoint, 'timed out.');
timedout = true;
resolve();
})
.on('error', function (e) {
if (timedout) return;
self._log.info('Trying to get gossip from', endPoint, 'failed with error:', e);
resolve();
})
.end();
});
};
const VNodeStates = {
'Initializing': 0,
'Unknown': 1,
'PreReplica': 2,
'CatchingUp': 3,
'Clone': 4,
'Slave': 5,
'PreMaster': 6,
'Master': 7,
'Manager': 8,
'ShuttingDown': 9,
'Shutdown': 10
};
ClusterDnsEndPointDiscoverer.prototype._tryDetermineBestNode = function (members) {
var notAllowedStates = [
'Manager',
'ShuttingDown',
'Shutdown'
];
var node = members
.filter(function (x) {
return (x.isAlive && notAllowedStates.indexOf(x.state) === -1);
})
.sort(function (a, b) {
return VNodeStates[b.state] - VNodeStates[a.state];
})[0];
if (!node)
{
//_log.Info("Unable to locate suitable node. Gossip info:\n{0}.", string.Join("\n", members.Select(x => x.ToString())));
return null;
}
var normTcp = {host: node.externalTcpIp, port: node.externalTcpPort};
var secTcp = node.externalSecureTcpPort > 0
? {host: externalTcpIp, port: node.externalSecureTcpPort}
: null;
this._log.info(util.format("Discovering: found best choice [%j,%j] (%s).", normTcp, secTcp === null ? "n/a" : secTcp, node.state));
return new NodeEndPoints(normTcp, secTcp);
};
function rndNext(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
ClusterDnsEndPointDiscoverer.prototype._randomShuffle = function (arr, i, j) {
if (i >= j)
return;
for (var k = i; k <= j; ++k)
{
var index = rndNext(k, j + 1);
var tmp = arr[index];
arr[index] = arr[k];
arr[k] = tmp;
}
};
module.exports = ClusterDnsEndPointDiscoverer;

View File

@ -9,6 +9,8 @@ var SubscriptionsManager = require('./subscriptionsManager');
var VolatileSubscriptionOperation = require('../clientOperations/volatileSubscriptionOperation'); var VolatileSubscriptionOperation = require('../clientOperations/volatileSubscriptionOperation');
var ConnectToPersistentSubscriptionOperation = require('../clientOperations/connectToPersistentSubscriptionOperation'); var ConnectToPersistentSubscriptionOperation = require('../clientOperations/connectToPersistentSubscriptionOperation');
var messages = require('./messages'); var messages = require('./messages');
var ClientMessage = require('../messages/clientMessage');
var createBufferSegment = require('../common/bufferSegment');
var TcpPackage = require('../systemData/tcpPackage'); var TcpPackage = require('../systemData/tcpPackage');
var TcpCommand = require('../systemData/tcpCommand'); var TcpCommand = require('../systemData/tcpCommand');
@ -28,20 +30,24 @@ const ConnectingPhase = {
EndPointDiscovery: 'endpointDiscovery', EndPointDiscovery: 'endpointDiscovery',
ConnectionEstablishing: 'connectionEstablishing', ConnectionEstablishing: 'connectionEstablishing',
Authentication: 'authentication', Authentication: 'authentication',
Identification: 'identification',
Connected: 'connected' Connected: 'connected'
}; };
const TimerPeriod = 200; const TimerPeriod = 200;
const TimerTickMessage = new messages.TimerTickMessage(); const TimerTickMessage = new messages.TimerTickMessage();
const EmptyGuid = '00000000-0000-0000-0000-000000000000'; const EmptyGuid = '00000000-0000-0000-0000-000000000000';
const ClientVersion = 1;
/** /**
* @private
* @param {EventStoreNodeConnection} esConnection * @param {EventStoreNodeConnection} esConnection
* @param {Object} settings * @param {Object} settings
* @constructor * @constructor
* @property {Number} totalOperationCount * @property {Number} totalOperationCount
*/ */
function EventStoreConnectionLogicHandler(esConnection, settings) { function EventStoreConnectionLogicHandler(esConnection, settings) {
EventEmitter.call(this);
this._esConnection = esConnection; this._esConnection = esConnection;
this._settings = settings; this._settings = settings;
this._queue = new SimpleQueuedHandler(); this._queue = new SimpleQueuedHandler();
@ -165,7 +171,7 @@ EventStoreConnectionLogicHandler.prototype._startConnection = function(cb, endpo
* @private * @private
*/ */
EventStoreConnectionLogicHandler.prototype._closeConnection = function(reason, error) { 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 : ''); this._logDebug("CloseConnection IGNORED because is ESConnection is CLOSED, reason %s, error %s.", reason, error ? error.stack : '');
return; return;
} }
@ -189,7 +195,7 @@ EventStoreConnectionLogicHandler.prototype._closeConnection = function(reason, e
EventStoreConnectionLogicHandler.prototype._closeTcpConnection = function(reason) { EventStoreConnectionLogicHandler.prototype._closeTcpConnection = function(reason) {
if (!this._connection) { if (!this._connection) {
this._logDebug("CloseTcpConnection IGNORED because _connection == null"); this._logDebug("CloseTcpConnection IGNORED because _connection === null");
return; return;
} }
@ -320,7 +326,7 @@ EventStoreConnectionLogicHandler.prototype._startPersistentSubscription = functi
EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(endPoints) { EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(endPoints) {
var endPoint = this._settings.useSslConnection ? endPoints.secureTcpEndPoint : endPoints.tcpEndPoint; var endPoint = this._settings.useSslConnection ? endPoints.secureTcpEndPoint : endPoints.tcpEndPoint;
if (endPoint == null) if (endPoint === null)
{ {
this._closeConnection("No end point to node specified."); this._closeConnection("No end point to node specified.");
return; return;
@ -328,8 +334,8 @@ EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(en
this._logDebug("EstablishTcpConnection to [%j]", endPoint); this._logDebug("EstablishTcpConnection to [%j]", endPoint);
if (this._state != ConnectionState.Connecting) return; if (this._state !== ConnectionState.Connecting) return;
if (this._connectingPhase != ConnectingPhase.EndPointDiscovery) return; if (this._connectingPhase !== ConnectingPhase.EndPointDiscovery) return;
var self = this; var self = this;
this._connectingPhase = ConnectingPhase.ConnectionEstablishing; this._connectingPhase = ConnectingPhase.ConnectionEstablishing;
@ -358,10 +364,10 @@ EventStoreConnectionLogicHandler.prototype._establishTcpConnection = function(en
}; };
EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(connection) { 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._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); connection.isClosed, connection.remoteEndPoint, connection.localEndPoint);
return; return;
} }
@ -373,7 +379,7 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(
timeStamp: Date.now() timeStamp: Date.now()
}; };
if (this._settings.defaultUserCredentials != null) if (this._settings.defaultUserCredentials !== null)
{ {
this._connectingPhase = ConnectingPhase.Authentication; this._connectingPhase = ConnectingPhase.Authentication;
@ -390,10 +396,21 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionEstablished = function(
} }
else 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() { EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
this._state = ConnectionState.Connected; this._state = ConnectionState.Connected;
this._connectingPhase = ConnectingPhase.Connected; this._connectingPhase = ConnectingPhase.Connected;
@ -402,7 +419,7 @@ EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
this.emit('connected', this._connection.remoteEndPoint); 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._operations.checkTimeoutsAndRetry(this._connection);
this._subscriptions.checkTimeoutsAndRetry(this._connection); this._subscriptions.checkTimeoutsAndRetry(this._connection);
@ -411,19 +428,19 @@ EventStoreConnectionLogicHandler.prototype._goToConnectedState = function() {
}; };
EventStoreConnectionLogicHandler.prototype._tcpConnectionError = function(connection, error) { EventStoreConnectionLogicHandler.prototype._tcpConnectionError = function(connection, error) {
if (this._connection != connection) return; if (!this._connection || !this._connection.equals(connection)) return;
if (this._state == ConnectionState.Closed) return; if (this._state === ConnectionState.Closed) return;
this._logDebug("TcpConnectionError connId %s, exc %s.", connection.connectionId, error); this._logDebug("TcpConnectionError connId %s, exc %s.", connection.connectionId, error);
this._closeConnection("TCP connection error occurred.", error); this._closeConnection("TCP connection error occurred.", error);
}; };
EventStoreConnectionLogicHandler.prototype._tcpConnectionClosed = function(connection, error) { EventStoreConnectionLogicHandler.prototype._tcpConnectionClosed = function(connection, error) {
if (this._state == ConnectionState.Init) throw new Error(); if (this._state === ConnectionState.Init) throw new Error();
if (this._state == ConnectionState.Closed || !this._connection.equals(connection)) 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._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); connection.remoteEndPoint, connection.localEndPoint);
return; return;
} }
@ -447,7 +464,7 @@ EventStoreConnectionLogicHandler.prototype._tcpConnectionClosed = function(conne
}; };
EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connection, pkg) { 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.", this._logDebug("IGNORED: HandleTcpPackage connId %s, package %s, %s.",
connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId); connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId);
@ -458,9 +475,28 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
this._connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId); this._connection.connectionId, TcpCommand.getName(pkg.command), pkg.correlationId);
this._packageNumber += 1; 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; return;
if (pkg.command == TcpCommand.HeartbeatRequestCommand) }
if (pkg.command === TcpCommand.HeartbeatRequestCommand)
{ {
this._connection.enqueueSend(new TcpPackage( this._connection.enqueueSend(new TcpPackage(
TcpCommand.HeartbeatResponseCommand, TcpCommand.HeartbeatResponseCommand,
@ -469,21 +505,31 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
return; return;
} }
if (pkg.command == TcpCommand.Authenticated || pkg.command == TcpCommand.NotAuthenticated) if (pkg.command === TcpCommand.Authenticated || pkg.command === TcpCommand.NotAuthenticated)
{ {
if (this._state == ConnectionState.Connecting if (this._state === ConnectionState.Connecting
&& this._connectingPhase == ConnectingPhase.Authentication && this._connectingPhase === ConnectingPhase.Authentication
&& this._authInfo.correlationId == pkg.correlationId) && this._authInfo.correlationId === pkg.correlationId)
{ {
if (pkg.command == TcpCommand.NotAuthenticated) if (pkg.command === TcpCommand.NotAuthenticated)
this.emit('authenticationFailed', "Not authenticated"); 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(); this._goToConnectedState();
return; return;
} }
} }
if (pkg.command == TcpCommand.BadRequest && pkg.correlationId == EmptyGuid) if (pkg.command === TcpCommand.BadRequest && pkg.correlationId === EmptyGuid)
{ {
var message = "<no message>"; var message = "<no message>";
try { try {
@ -515,7 +561,7 @@ EventStoreConnectionLogicHandler.prototype._handleTcpPackage = function(connecti
default: default:
throw new Error("Unknown InspectionDecision: " + result.decision); throw new Error("Unknown InspectionDecision: " + result.decision);
} }
if (this._state == ConnectionState.Connected) if (this._state === ConnectionState.Connected)
this._operations.scheduleWaitingOperations(connection); this._operations.scheduleWaitingOperations(connection);
return; return;
@ -557,13 +603,13 @@ EventStoreConnectionLogicHandler.prototype._reconnectTo = function(endPoints) {
var endPoint = this._settings.useSslConnection var endPoint = this._settings.useSslConnection
? endPoints.secureTcpEndPoint ? endPoints.secureTcpEndPoint
: endPoints.tcpEndPoint; : endPoints.tcpEndPoint;
if (endPoint == null) if (endPoint === null)
{ {
this._closeConnection("No end point is specified while trying to reconnect."); this._closeConnection("No end point is specified while trying to reconnect.");
return; return;
} }
if (this._state != ConnectionState.Connected || this._connection.remoteEndPoint == endPoint) if (this._state !== ConnectionState.Connected || this._connection.remoteEndPoint === endPoint)
return; return;
var msg = util.format("EventStoreConnection '%s': going to reconnect to [%j]. Current endpoint: [%j, L%j].", var msg = util.format("EventStoreConnection '%s': going to reconnect to [%j]. Current endpoint: [%j, L%j].",
@ -581,7 +627,7 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
{ {
case ConnectionState.Init: break; case ConnectionState.Init: break;
case ConnectionState.Connecting: 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._logDebug("TimerTick checking reconnection...");
@ -594,17 +640,21 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
this._discoverEndpoint(null); 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.emit('authenticationFailed', "Authentication timed out.");
this._goToConnectedState(); if (this._clientVersion === 1) {
this._goToIdentifiedState();
} else {
this._goToConnectedState();
}
} }
else if (this._connectingPhase > ConnectingPhase.ConnectionEstablishing) else if (this._connectingPhase === ConnectingPhase.Authentication || this._connectingPhase === ConnectingPhase.Connected)
this._manageHeartbeats(); this._manageHeartbeats();
break; break;
case ConnectionState.Connected: case ConnectionState.Connected:
// operations timeouts are checked only if connection is established and check period time passed // 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 // On mono even impossible connection first says that it is established
// so clearing of reconnection count on ConnectionEstablished event causes infinite reconnections. // so clearing of reconnection count on ConnectionEstablished event causes infinite reconnections.
@ -624,14 +674,14 @@ EventStoreConnectionLogicHandler.prototype._timerTick = function() {
}; };
EventStoreConnectionLogicHandler.prototype._manageHeartbeats = 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; var timeout = this._heartbeatInfo.isIntervalStage ? this._settings.heartbeatInterval : this._settings.heartbeatTimeout;
if (Date.now() - this._heartbeatInfo.timeStamp < timeout) if ((Date.now() - this._heartbeatInfo.timeStamp) < timeout)
return; return;
var packageNumber = this._packageNumber; var packageNumber = this._packageNumber;
if (this._heartbeatInfo.lastPackageNumber != packageNumber) if (this._heartbeatInfo.lastPackageNumber !== packageNumber)
{ {
this._heartbeatInfo = {lastPackageNumber: packageNumber, isIntervalStage: true, timeStamp: Date.now()}; this._heartbeatInfo = {lastPackageNumber: packageNumber, isIntervalStage: true, timeStamp: Date.now()};
return; return;
@ -639,12 +689,13 @@ EventStoreConnectionLogicHandler.prototype._manageHeartbeats = function() {
if (this._heartbeatInfo.isIntervalStage) if (this._heartbeatInfo.isIntervalStage)
{ {
var correlationId = uuid.v4();
// TcpMessage.Heartbeat analog // TcpMessage.Heartbeat analog
this._connection.enqueueSend(new TcpPackage( this._connection.enqueueSend(new TcpPackage(
TcpCommand.HeartbeatRequestCommand, TcpCommand.HeartbeatRequestCommand,
TcpFlags.None, TcpFlags.None,
uuid.v4())); correlationId));
this._heartbeatInfo = {lastPackageNumber: this._heartbeatInfo.lastPackageNumber, isIntervalStage: false, timeStamp: Date.now()}; this._heartbeatInfo = {correlationId: correlationId, lastPackageNumber: this._heartbeatInfo.lastPackageNumber, isIntervalStage: false, timeStamp: Date.now()};
} }
else else
{ {

View File

@ -41,6 +41,7 @@ function StartSubscriptionMessage(
util.inherits(StartSubscriptionMessage, Message); util.inherits(StartSubscriptionMessage, Message);
/** /**
* @private
* @constructor * @constructor
* @property {object} endPoints * @property {object} endPoints
* @property {object} endPoints.secureTcpEndPoint * @property {object} endPoints.secureTcpEndPoint

View File

@ -5,6 +5,7 @@ var Hash = require('../common/hash');
var TcpCommand = require('../systemData/tcpCommand'); var TcpCommand = require('../systemData/tcpCommand');
/** /**
* @private
* @param {string} connectionName * @param {string} connectionName
* @param {object} settings * @param {object} settings
* @constructor * @constructor
@ -29,17 +30,20 @@ OperationsManager.prototype.getActiveOperation = function(correlationId) {
return this._activeOperations.get(correlationId); return this._activeOperations.get(correlationId);
}; };
OperationsManager.prototype.cleanUp = function() { function cleanUpError(connName, state, operation) {
var connectionClosedError = new Error(util.format("Connection '%s' was closed.", this._connectionName)); 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){ this._activeOperations.forEach(function(correlationId, operation){
operation.operation.fail(connectionClosedError); operation.operation.fail(cleanUpError(self._connectionName, 'Active', operation));
}); });
this._waitingOperations.forEach(function(operation) { this._waitingOperations.forEach(function(operation) {
operation.operation.fail(connectionClosedError); operation.operation.fail(cleanUpError(self._connectionName, 'Waiting', operation));
}); });
this._retryPendingOperations.forEach(function(operation) { this._retryPendingOperations.forEach(function(operation) {
operation.operation.fail(connectionClosedError); operation.operation.fail(cleanUpError(self._connectionName, 'Pending', operation));
}); });
this._activeOperations.clear(); this._activeOperations.clear();
@ -55,7 +59,7 @@ OperationsManager.prototype.checkTimeoutsAndRetry = function(connection) {
var removeOperations = []; var removeOperations = [];
var self = this; var self = this;
this._activeOperations.forEach(function(correlationId, operation) { this._activeOperations.forEach(function(correlationId, operation) {
if (operation.connectionId != connection.connectionId) if (operation.connectionId !== connection.connectionId)
{ {
retryOperations.push(operation); retryOperations.push(operation);
} }
@ -120,7 +124,7 @@ OperationsManager.prototype.scheduleOperationRetry = function(operation) {
}; };
OperationsManager.prototype.removeOperation = 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._logDebug("RemoveOperation SUCCEEDED for %s.", operation);
this._totalOperationCount = this._activeOperations.length + this._waitingOperations.length; this._totalOperationCount = this._activeOperations.length + this._waitingOperations.length;
return true; return true;

View File

@ -13,8 +13,14 @@ function SimpleQueuedHandler() {
} }
SimpleQueuedHandler.prototype.registerHandler = function(type, handler) { SimpleQueuedHandler.prototype.registerHandler = function(type, handler) {
type = typeName(type); var typeId = typeName(type);
this._handlers[type] = handler; this._handlers[typeId] = function (msg) {
try {
handler(msg);
} catch(e) {
console.log('ERROR: ', e.stack);
}
};
}; };
SimpleQueuedHandler.prototype.enqueueMessage = function(msg) { SimpleQueuedHandler.prototype.enqueueMessage = function(msg) {
@ -28,10 +34,10 @@ SimpleQueuedHandler.prototype.enqueueMessage = function(msg) {
SimpleQueuedHandler.prototype._processQueue = function() { SimpleQueuedHandler.prototype._processQueue = function() {
var message = this._messages.shift(); var message = this._messages.shift();
while(message) { while(message) {
var type = typeName(message); var typeId = typeName(message);
var handler = this._handlers[type]; var handler = this._handlers[typeId];
if (!handler) if (!handler)
throw new Error("No handler registered for message " + type); throw new Error("No handler registered for message " + typeId);
setImmediate(handler, message); setImmediate(handler, message);
message = this._messages.shift(); message = this._messages.shift();
} }

View File

@ -38,11 +38,11 @@ SubscriptionsManager.prototype.cleanUp = function() {
this._retryPendingSubscriptions = []; this._retryPendingSubscriptions = [];
}; };
SubscriptionsManager.prototype.purgeSubscribedAndDroppedSubscriptions = function() { SubscriptionsManager.prototype.purgeSubscribedAndDroppedSubscriptions = function(connectionId) {
var self = this; var self = this;
var subscriptionsToRemove = []; var subscriptionsToRemove = [];
this._activeSubscriptions.forEach(function(_, subscription) { this._activeSubscriptions.forEach(function(_, subscription) {
if (subscription.isSubscribed && subscription.connectionId == connectionId) { if (subscription.isSubscribed && subscription.connectionId === connectionId) {
subscription.operation.connectionClosed(); subscription.operation.connectionClosed();
subscriptionsToRemove.push(subscription); subscriptionsToRemove.push(subscription);
} }
@ -60,7 +60,7 @@ SubscriptionsManager.prototype.checkTimeoutsAndRetry = function(connection) {
var removeSubscriptions = []; var removeSubscriptions = [];
this._activeSubscriptions.forEach(function(_, subscription) { this._activeSubscriptions.forEach(function(_, subscription) {
if (subscription.isSubscribed) return; if (subscription.isSubscribed) return;
if (subscription.connectionId != connection.connectionId) if (subscription.connectionId !== connection.connectionId)
{ {
retrySubscriptions.push(subscription); retrySubscriptions.push(subscription);
} }
@ -165,7 +165,7 @@ SubscriptionsManager.prototype._logDebug = function(message) {
if (!this._settings.verboseLogging) return; if (!this._settings.verboseLogging) return;
var parameters = Array.prototype.slice.call(arguments, 1); 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; module.exports = SubscriptionsManager;

View File

@ -1,11 +1,23 @@
var util = require('util'); var util = require('util');
var Long = require('long');
function AccessDeniedError(action, stream) { function AccessDeniedError(action, streamOrTransactionId) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.message = util.format("%s access denied for stream '%s'.", action, stream);
this.action = action; 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); 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 util = require('util');
var Long = require('long');
function StreamDeletedError(stream) { function StreamDeletedError(streamOrTransactionId) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; this.name = this.constructor.name;
this.message = util.format("Event stream '%s' is deleted.", stream); if (typeof streamOrTransactionId === 'string') {
this.stream = stream; 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); util.inherits(StreamDeletedError, Error);

View File

@ -1,12 +1,24 @@
var util = require('util'); var util = require('util');
var Long = require('long');
function WrongExpectedVersionError(action, stream, expectedVersion) { function WrongExpectedVersionError(action, streamOrTransactionId, expectedVersion) {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name; 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.action = action;
this.stream = stream; if (typeof streamOrTransactionId === 'string') {
this.expectedVersion = expectedVersion; 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); util.inherits(WrongExpectedVersionError, Error);

View File

@ -1,17 +1,14 @@
var uuid = require('uuid'); 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) { function isValidId(id) {
if (typeof id !== 'string') return false; if (typeof id !== 'string') return false;
var buf = uuid.parse(id); return uuidRegex.test(id);
var valid = false;
for(var i=0;i<buf.length;i++)
if (buf[i] !== 0)
valid = true;
return valid;
} }
/** /**
* Create an EventData * Create an EventData
* @private
* @param {string} eventId * @param {string} eventId
* @param {string} type * @param {string} type
* @param {boolean} [isJson] * @param {boolean} [isJson]
@ -31,6 +28,7 @@ function EventData(eventId, type, isJson, data, metadata) {
this.isJson = isJson || false; this.isJson = isJson || false;
this.data = data || new Buffer(0); this.data = data || new Buffer(0);
this.metadata = metadata || new Buffer(0); this.metadata = metadata || new Buffer(0);
Object.freeze(this);
} }
module.exports = EventData; module.exports = EventData;

View File

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

View File

@ -209,26 +209,35 @@ EventStoreCatchUpSubscription.prototype._ensureProcessingPushQueue = function()
EventStoreCatchUpSubscription.prototype._processLiveQueue = function() { EventStoreCatchUpSubscription.prototype._processLiveQueue = function() {
var ev = this._liveQueue.shift(); var ev = this._liveQueue.shift();
//TODO: possible blocking while, use when if (!ev) {
while(ev) { this._isProcessing = false;
if (ev instanceof DropSubscriptionEvent) { return;
if (!this._dropData) this._dropData = {reason: SubscriptionDropReason.Unknown, error: new Error("Drop reason not specified.")}; }
this._dropSubscription(this._dropData.reason, this._dropData.error); if (ev instanceof DropSubscriptionEvent) {
this._isProcessing = false; if (!this._dropData) this._dropData = {reason: SubscriptionDropReason.Unknown, error: new Error("Drop reason not specified.")};
return; this._dropSubscription(this._dropData.reason, this._dropData.error);
} this._isProcessing = false;
return;
try { }
this._tryProcess(ev); var promise;
} try {
catch(err) { promise = this._tryProcess(ev);
this._dropSubscription(SubscriptionDropReason.EventHandlerException, err); }
return; catch(err) {
} this._dropSubscription(SubscriptionDropReason.EventHandlerException, err);
ev = this._liveQueue.shift(); 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) { EventStoreCatchUpSubscription.prototype._dropSubscription = function(reason, error) {

View File

@ -1,6 +1,8 @@
var EventStoreNodeConnection = require('./eventStoreNodeConnection'); var EventStoreNodeConnection = require('./eventStoreNodeConnection');
var StaticEndpointDiscoverer = require('./core/staticEndpointDiscoverer'); var StaticEndpointDiscoverer = require('./core/staticEndpointDiscoverer');
var ClusterDnsEndPointDiscoverer = require('./core/clusterDnsEndPointDiscoverer');
var NoopLogger = require('./common/log/noopLogger'); var NoopLogger = require('./common/log/noopLogger');
var ensure = require('./common/utils/ensure');
var defaultConnectionSettings = { var defaultConnectionSettings = {
log: new NoopLogger(), log: new NoopLogger(),
@ -25,7 +27,13 @@ var defaultConnectionSettings = {
failOnNoServerResponse: false, failOnNoServerResponse: false,
heartbeatInterval: 750, heartbeatInterval: 750,
heartbeatTimeout: 1500, heartbeatTimeout: 1500,
clientConnectionTimeout: 1000 clientConnectionTimeout: 1000,
// Cluster Settings
clusterDns: '',
maxDiscoverAttempts: 10,
externalGossipPort: 0,
gossipTimeout: 1000
}; };
@ -40,16 +48,86 @@ function merge(a,b) {
return c; 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,
gossipSeeds: null,
externalGossipPort: externalGossipPort,
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
gossipTimeout: mergedSettings.gossipTimeout
};
var endPointDiscoverer = new ClusterDnsEndPointDiscoverer(mergedSettings.log,
clusterSettings.clusterDns,
clusterSettings.maxDiscoverAttempts,
clusterSettings.externalGossipPort,
clusterSettings.gossipSeeds,
clusterSettings.gossipTimeout
);
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: '',
gossipSeeds: gossipSeeds,
externalGossipPort: 0,
maxDiscoverAttempts: mergedSettings.maxDiscoverAttempts,
gossipTimeout: mergedSettings.gossipTimeout
};
var endPointDiscoverer = new ClusterDnsEndPointDiscoverer(mergedSettings.log,
clusterSettings.clusterDns,
clusterSettings.maxDiscoverAttempts,
clusterSettings.externalGossipPort,
clusterSettings.gossipSeeds,
clusterSettings.gossipTimeout
);
return new EventStoreNodeConnection(mergedSettings, clusterSettings, endPointDiscoverer, connectionName);
}
/** /**
* Create an EventStore connection * Create an EventStore connection
* @public
* @alias createConnection
* @param {object} settings * @param {object} settings
* @param {object} tcpEndPoint * @param {string|object|array} endPointOrGossipSeeds
* @param {string} [connectionName] * @param {string} [connectionName]
* @returns {EventStoreNodeConnection} * @returns {EventStoreNodeConnection}
*/ */
module.exports.create = function(settings, tcpEndPoint, connectionName) { module.exports.create = function(settings, endPointOrGossipSeeds, connectionName) {
//TODO: cluster connection if (Array.isArray(endPointOrGossipSeeds)) return createFromGossipSeeds(settings, endPointOrGossipSeeds, connectionName);
var mergedSettings = merge(defaultConnectionSettings, settings || {}); if (typeof endPointOrGossipSeeds === 'object') return createFromTcpEndpoint(settings, endPointOrGossipSeeds, connectionName);
var endpointDiscoverer = new StaticEndpointDiscoverer(tcpEndPoint, settings.useSslConnection); if (typeof endPointOrGossipSeeds === 'string') return createFromStringEndpoint(settings, endPointOrGossipSeeds, connectionName);
return new EventStoreNodeConnection(mergedSettings, endpointDiscoverer, connectionName || null); throw new TypeError('endPointOrGossipSeeds must be an object, a string or an array.');
}; };

View File

@ -1,5 +1,6 @@
var util = require('util'); var util = require('util');
var uuid = require('uuid'); var uuid = require('uuid');
var Long = require('long');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var ensure = require('./common/utils/ensure'); var ensure = require('./common/utils/ensure');
@ -33,15 +34,14 @@ var EventData = require('./eventData');
const MaxReadSize = 4096; const MaxReadSize = 4096;
/** /**
* @param settings * @protected
* @param endpointDiscoverer
* @param connectionName
* @constructor * @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._connectionName = connectionName || ['ES-', uuid.v4()].join('');
this._settings = settings; this._settings = settings;
this._clusterSettings = clusterSettings;
this._endpointDiscoverer = endpointDiscoverer; this._endpointDiscoverer = endpointDiscoverer;
this._handler = new EventStoreConnectionLogicHandler(this, settings); this._handler = new EventStoreConnectionLogicHandler(this, settings);
@ -61,6 +61,9 @@ function EventStoreNodeConnection(settings, endpointDiscoverer, connectionName)
this._handler.on('error', function(e) { this._handler.on('error', function(e) {
self.emit('error', e); self.emit('error', e);
}); });
this._handler.on('heartbeatInfo', function(e) {
self.emit('heartbeatInfo', e);
});
} }
util.inherits(EventStoreNodeConnection, EventEmitter); util.inherits(EventStoreNodeConnection, EventEmitter);
@ -71,6 +74,8 @@ Object.defineProperty(EventStoreNodeConnection.prototype, 'connectionName', {
}); });
/** /**
* Start connection task
* @public
* @returns {Promise} * @returns {Promise}
*/ */
EventStoreNodeConnection.prototype.connect = function() { EventStoreNodeConnection.prototype.connect = function() {
@ -85,22 +90,28 @@ EventStoreNodeConnection.prototype.connect = function() {
}); });
}; };
/**
* Close connection
* @public
*/
EventStoreNodeConnection.prototype.close = function() { EventStoreNodeConnection.prototype.close = function() {
this._handler.enqueueMessage(new messages.CloseConnectionMessage("Connection close requested by client.", null)); this._handler.enqueueMessage(new messages.CloseConnectionMessage("Connection close requested by client.", null));
}; };
/** /**
* Delete a stream (async) * Delete a stream (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} expectedVersion * @param {Long|number} expectedVersion
* @param {boolean} [hardDelete] * @param {boolean} [hardDelete]
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<DeleteResult>} * @returns {Promise.<DeleteResult>}
*/ */
EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) { EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersion, hardDelete, userCredentials) {
ensure.notNullOrEmpty(stream, "stream"); ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(expectedVersion, "expectedVersion"); ensure.isLongOrInteger(expectedVersion, "expectedVersion");
hardDelete = !!hardDelete; expectedVersion = Long.fromValue(expectedVersion);
hardDelete = Boolean(hardDelete);
userCredentials = userCredentials || null; userCredentials = userCredentials || null;
var self = this; var self = this;
@ -118,15 +129,17 @@ EventStoreNodeConnection.prototype.deleteStream = function(stream, expectedVersi
/** /**
* Append events to a stream (async) * Append events to a stream (async)
* @public
* @param {string} stream The name of the stream to which to append. * @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 {EventData[]|EventData} events The event(s) to append.
* @param {UserCredentials} [userCredentials] User credentials * @param {UserCredentials} [userCredentials] User credentials
* @returns {Promise.<WriteResult>} * @returns {Promise.<WriteResult>}
*/ */
EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) { EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVersion, events, userCredentials) {
ensure.notNullOrEmpty(stream, "stream"); ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(expectedVersion, "expectedVersion"); ensure.isLongOrInteger(expectedVersion, "expectedVersion");
expectedVersion = Long.fromValue(expectedVersion);
if (!Array.isArray(events)) if (!Array.isArray(events))
events = [events]; events = [events];
ensure.isArrayOf(EventData, events, "events"); ensure.isArrayOf(EventData, events, "events");
@ -146,13 +159,18 @@ EventStoreNodeConnection.prototype.appendToStream = function(stream, expectedVer
/** /**
* Start a transaction (async) * Start a transaction (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} expectedVersion * @param {Long|number} expectedVersion
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<EventStoreTransaction>} * @returns {Promise.<EventStoreTransaction>}
*/ */
EventStoreNodeConnection.prototype.startTransaction = function(stream, expectedVersion, userCredentials) { 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; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function cb(err, result) { function cb(err, result) {
@ -160,23 +178,29 @@ EventStoreNodeConnection.prototype.startTransaction = function(stream, expectedV
resolve(result); resolve(result);
} }
var operation = new StartTransactionOperation(self._settings.log, cb, self._settings.requireMaster, stream, var operation = new StartTransactionOperation(self._settings.log, cb, self._settings.requireMaster, stream,
expectedVersion, self, userCredentials || null); expectedVersion, self, userCredentials);
self._enqueueOperation(operation); self._enqueueOperation(operation);
}); });
}; };
/** /**
* Continue a transaction * Continue a transaction
* @public
* @param {number} transactionId * @param {number} transactionId
* @param {UserCredentials} userCredentials * @param {UserCredentials} userCredentials
* @returns {EventStoreTransaction} * @returns {EventStoreTransaction}
*/ */
EventStoreNodeConnection.prototype.continueTransaction = function(transactionId, userCredentials) { EventStoreNodeConnection.prototype.continueTransaction = function(transactionId, userCredentials) {
//TODO validations ensure.nonNegative(transactionId, "transactionId");
return new EventStoreTransaction(transactionId, userCredentials, this); return new EventStoreTransaction(transactionId, userCredentials, this);
}; };
EventStoreNodeConnection.prototype.transactionalWrite = function(transaction, events, userCredentials) { EventStoreNodeConnection.prototype.transactionalWrite = function(transaction, events, userCredentials) {
ensure.isTypeOf(EventStoreTransaction, transaction, "transaction");
ensure.isArrayOf(EventData, events, "events");
userCredentials = userCredentials || null;
var self = this; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function cb(err) { function cb(err) {
@ -189,7 +213,16 @@ EventStoreNodeConnection.prototype.transactionalWrite = function(transaction, ev
}); });
}; };
/**
* Commit a transaction
* @public
* @param transaction
* @param userCredentials
* @returns {Promise.<WriteResult>}
*/
EventStoreNodeConnection.prototype.commitTransaction = function(transaction, userCredentials) { EventStoreNodeConnection.prototype.commitTransaction = function(transaction, userCredentials) {
ensure.isTypeOf(EventStoreTransaction, transaction, "transaction");
var self = this; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function cb(err, result) { function cb(err, result) {
@ -204,22 +237,22 @@ EventStoreNodeConnection.prototype.commitTransaction = function(transaction, use
/** /**
* Read a single event (async) * Read a single event (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} eventNumber * @param {Long|number} eventNumber
* @param {boolean} [resolveLinkTos] * @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<EventReadResult>} * @returns {Promise.<EventReadResult>}
*/ */
EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, resolveLinkTos, userCredentials) { EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, resolveLinkTos, userCredentials) {
ensure.notNullOrEmpty(stream, "stream"); ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(eventNumber, "eventNumber"); ensure.isLongOrInteger(eventNumber, "eventNumber");
if (eventNumber < -1) throw new Error("eventNumber out of range."); eventNumber = Long.fromValue(eventNumber);
resolveLinkTos = !!resolveLinkTos; resolveLinkTos = Boolean(resolveLinkTos);
userCredentials = userCredentials || null; userCredentials = userCredentials || null;
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be an non-empty string."); 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.compare(-1) < 0) throw new Error("eventNumber out of range.");
if (eventNumber < -1) throw new Error("eventNumber out of range.");
if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean."); if (resolveLinkTos && typeof resolveLinkTos !== 'boolean') throw new TypeError("resolveLinkTos must be a boolean.");
var self = this; var self = this;
@ -236,8 +269,9 @@ EventStoreNodeConnection.prototype.readEvent = function(stream, eventNumber, res
/** /**
* Reading a specific stream forwards (async) * Reading a specific stream forwards (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} start * @param {Long|number} start
* @param {number} count * @param {number} count
* @param {boolean} [resolveLinkTos] * @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
@ -247,12 +281,13 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function(
stream, start, count, resolveLinkTos, userCredentials stream, start, count, resolveLinkTos, userCredentials
) { ) {
ensure.notNullOrEmpty(stream, "stream"); ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(start, "start"); ensure.isLongOrInteger(start, "start");
start = Long.fromValue(start);
ensure.nonNegative(start, "start"); ensure.nonNegative(start, "start");
ensure.isInteger(count, "count"); ensure.isInteger(count, "count");
ensure.positive(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)); 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; userCredentials = userCredentials || null;
var self = this; var self = this;
@ -269,8 +304,9 @@ EventStoreNodeConnection.prototype.readStreamEventsForward = function(
/** /**
* Reading a specific stream backwards (async) * Reading a specific stream backwards (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} start * @param {Long|number} start
* @param {number} count * @param {number} count
* @param {boolean} [resolveLinkTos] * @param {boolean} [resolveLinkTos]
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
@ -280,11 +316,12 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function(
stream, start, count, resolveLinkTos, userCredentials stream, start, count, resolveLinkTos, userCredentials
) { ) {
ensure.notNullOrEmpty(stream, "stream"); ensure.notNullOrEmpty(stream, "stream");
ensure.isInteger(start, "start"); ensure.isLongOrInteger(start, "start");
start = Long.fromValue(start);
ensure.isInteger(count, "count"); ensure.isInteger(count, "count");
ensure.positive(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)); 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; userCredentials = userCredentials || null;
var self = this; var self = this;
@ -301,6 +338,7 @@ EventStoreNodeConnection.prototype.readStreamEventsBackward = function(
/** /**
* Reading all events forwards (async) * Reading all events forwards (async)
* @public
* @param {Position} position * @param {Position} position
* @param {number} maxCount * @param {number} maxCount
* @param {boolean} [resolveLinkTos] * @param {boolean} [resolveLinkTos]
@ -314,7 +352,7 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function(
ensure.isInteger(maxCount, "maxCount"); ensure.isInteger(maxCount, "maxCount");
ensure.positive(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)); 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; userCredentials = userCredentials || null;
var self = this; var self = this;
@ -331,6 +369,7 @@ EventStoreNodeConnection.prototype.readAllEventsForward = function(
/** /**
* Reading all events backwards (async) * Reading all events backwards (async)
* @public
* @param {Position} position * @param {Position} position
* @param {number} maxCount * @param {number} maxCount
* @param {boolean} [resolveLinkTos] * @param {boolean} [resolveLinkTos]
@ -344,7 +383,7 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
ensure.isInteger(maxCount, "maxCount"); ensure.isInteger(maxCount, "maxCount");
ensure.positive(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)); 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; userCredentials = userCredentials || null;
var self = this; var self = this;
@ -361,6 +400,7 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
/** /**
* Subscribe to a stream (async) * Subscribe to a stream (async)
* @public
* @param {!string} stream * @param {!string} stream
* @param {!boolean} resolveLinkTos * @param {!boolean} resolveLinkTos
* @param {function} eventAppeared * @param {function} eventAppeared
@ -371,8 +411,10 @@ EventStoreNodeConnection.prototype.readAllEventsBackward = function(
EventStoreNodeConnection.prototype.subscribeToStream = function( EventStoreNodeConnection.prototype.subscribeToStream = function(
stream, resolveLinkTos, eventAppeared, subscriptionDropped, userCredentials stream, resolveLinkTos, eventAppeared, subscriptionDropped, userCredentials
) { ) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be a non-empty string."); ensure.notNullOrEmpty(stream, "stream");
if (typeof eventAppeared !== 'function') throw new TypeError("eventAppeared must be a function."); ensure.isTypeOf(Function, eventAppeared, "eventAppeared");
if (subscriptionDropped)
ensure.isTypeOf(Function, subscriptionDropped, "subscriptionDropped");
var self = this; var self = this;
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
@ -388,8 +430,10 @@ EventStoreNodeConnection.prototype.subscribeToStream = function(
}; };
/** /**
* Subscribe to a stream from position
* @public
* @param {!string} stream * @param {!string} stream
* @param {?number} lastCheckpoint * @param {?number|Position} lastCheckpoint
* @param {!boolean} resolveLinkTos * @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared * @param {!function} eventAppeared
* @param {function} [liveProcessingStarted] * @param {function} [liveProcessingStarted]
@ -403,6 +447,10 @@ EventStoreNodeConnection.prototype.subscribeToStreamFrom = function(
userCredentials, readBatchSize userCredentials, readBatchSize
) { ) {
if (typeof stream !== 'string' || stream === '') throw new TypeError("stream must be a non-empty string."); 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."); if (typeof eventAppeared !== 'function') throw new TypeError("eventAppeared must be a function.");
var catchUpSubscription = var catchUpSubscription =
@ -416,6 +464,7 @@ EventStoreNodeConnection.prototype.subscribeToStreamFrom = function(
/** /**
* Subscribe to all (async) * Subscribe to all (async)
* @public
* @param {!boolean} resolveLinkTos * @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared * @param {!function} eventAppeared
* @param {function} [subscriptionDropped] * @param {function} [subscriptionDropped]
@ -442,6 +491,7 @@ EventStoreNodeConnection.prototype.subscribeToAll = function(
/** /**
* Subscribe to all from * Subscribe to all from
* @public
* @param {?Position} lastCheckpoint * @param {?Position} lastCheckpoint
* @param {!boolean} resolveLinkTos * @param {!boolean} resolveLinkTos
* @param {!function} eventAppeared * @param {!function} eventAppeared
@ -455,7 +505,10 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
lastCheckpoint, resolveLinkTos, eventAppeared, liveProcessingStarted, subscriptionDropped, lastCheckpoint, resolveLinkTos, eventAppeared, liveProcessingStarted, subscriptionDropped,
userCredentials, readBatchSize 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 = var catchUpSubscription =
new EventStoreAllCatchUpSubscription(this, this._settings.log, lastCheckpoint, resolveLinkTos, new EventStoreAllCatchUpSubscription(this, this._settings.log, lastCheckpoint, resolveLinkTos,
@ -467,6 +520,7 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
/** /**
* Subscribe to a persistent subscription * Subscribe to a persistent subscription
* @public
* @param {string} stream * @param {string} stream
* @param {string} groupName * @param {string} groupName
* @param {function} eventAppeared * @param {function} eventAppeared
@ -474,6 +528,7 @@ EventStoreNodeConnection.prototype.subscribeToAllFrom = function(
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @param {number} [bufferSize] * @param {number} [bufferSize]
* @param {boolean} [autoAck] * @param {boolean} [autoAck]
* @return {Promise<EventStorePersistentSubscription>}
*/ */
EventStoreNodeConnection.prototype.connectToPersistentSubscription = function( EventStoreNodeConnection.prototype.connectToPersistentSubscription = function(
stream, groupName, eventAppeared, subscriptionDropped, userCredentials, bufferSize, autoAck stream, groupName, eventAppeared, subscriptionDropped, userCredentials, bufferSize, autoAck
@ -485,17 +540,17 @@ EventStoreNodeConnection.prototype.connectToPersistentSubscription = function(
subscriptionDropped = subscriptionDropped || null; subscriptionDropped = subscriptionDropped || null;
userCredentials = userCredentials || null; userCredentials = userCredentials || null;
bufferSize = bufferSize === undefined ? 10 : bufferSize; bufferSize = bufferSize === undefined ? 10 : bufferSize;
autoAck = autoAck === undefined ? true : !!autoAck; autoAck = autoAck === undefined ? true : Boolean(autoAck);
var subscription = new EventStorePersistentSubscription( var subscription = new EventStorePersistentSubscription(
groupName, stream, eventAppeared, subscriptionDropped, userCredentials, this._settings.log, groupName, stream, eventAppeared, subscriptionDropped, userCredentials, this._settings.log,
this._settings.verboseLogging, this._settings, this._handler, bufferSize, autoAck); this._settings.verboseLogging, this._settings, this._handler, bufferSize, autoAck);
subscription.start(); return subscription.start();
return subscription;
}; };
/** /**
* Create a persistent subscription
* @public
* @param {string} stream * @param {string} stream
* @param {string} groupName * @param {string} groupName
* @param {PersistentSubscriptionSettings} settings * @param {PersistentSubscriptionSettings} settings
@ -519,9 +574,11 @@ EventStoreNodeConnection.prototype.createPersistentSubscription = function(strea
}; };
/** /**
* Update a persistent subscription
* @public
* @param {string} stream * @param {string} stream
* @param {string} groupName * @param {string} groupName
* @param {string} settings * @param {PersistentSubscriptionSettings} settings
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<PersistentSubscriptionUpdateResult>} * @returns {Promise.<PersistentSubscriptionUpdateResult>}
*/ */
@ -541,6 +598,8 @@ EventStoreNodeConnection.prototype.updatePersistentSubscription = function(strea
}; };
/** /**
* Delete a persistent subscription
* @public
* @param {string} stream * @param {string} stream
* @param {string} groupName * @param {string} groupName
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
@ -560,6 +619,10 @@ EventStoreNodeConnection.prototype.deletePersistentSubscription = function(strea
}); });
}; };
/**
* Set stream metadata
* @private
*/
EventStoreNodeConnection.prototype.setStreamMetadata = function() { EventStoreNodeConnection.prototype.setStreamMetadata = function() {
//TODO: set stream metadata (non-raw) //TODO: set stream metadata (non-raw)
throw new Error("Not implemented."); throw new Error("Not implemented.");
@ -567,8 +630,9 @@ EventStoreNodeConnection.prototype.setStreamMetadata = function() {
/** /**
* Set stream metadata with raw object (async) * Set stream metadata with raw object (async)
* @public
* @param {string} stream * @param {string} stream
* @param {number} expectedMetastreamVersion * @param {Long|number} expectedMetastreamVersion
* @param {object} metadata * @param {object} metadata
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<WriteResult>} * @returns {Promise.<WriteResult>}
@ -579,6 +643,8 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
ensure.notNullOrEmpty(stream, "stream"); 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)); 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; var self = this;
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function cb(err, result) { function cb(err, result) {
@ -594,6 +660,12 @@ EventStoreNodeConnection.prototype.setStreamMetadataRaw = function(
}); });
}; };
/**
* Get stream metadata
* @private
* @param stream
* @param userCredentials
*/
EventStoreNodeConnection.prototype.getStreamMetadata = function(stream, userCredentials) { EventStoreNodeConnection.prototype.getStreamMetadata = function(stream, userCredentials) {
//TODO: get stream metadata (non-raw) //TODO: get stream metadata (non-raw)
throw new Error("Not implemented."); throw new Error("Not implemented.");
@ -601,6 +673,7 @@ EventStoreNodeConnection.prototype.getStreamMetadata = function(stream, userCred
/** /**
* Get stream metadata as raw object (async) * Get stream metadata as raw object (async)
* @public
* @param {string} stream * @param {string} stream
* @param {UserCredentials} [userCredentials] * @param {UserCredentials} [userCredentials]
* @returns {Promise.<RawStreamMetadataResult>} * @returns {Promise.<RawStreamMetadataResult>}
@ -614,18 +687,22 @@ EventStoreNodeConnection.prototype.getStreamMetadataRaw = function(stream, userC
var evnt = res.event.originalEvent; var evnt = res.event.originalEvent;
var version = evnt ? evnt.eventNumber : -1; var version = evnt ? evnt.eventNumber : -1;
var data = evnt ? JSON.parse(evnt.data.toString()) : null; 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.NotFound:
case results.EventReadStatus.NoStream: 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: case results.EventReadStatus.StreamDeleted:
return new results.RawStreamMetadataResult(stream, true, 0x7fffffff, null); return new results.RawStreamMetadataResult(stream, true, Long.fromValue(0x7fffffff), null);
default: default:
throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status)); throw new Error(util.format("Unexpected ReadEventResult: %s.", res.status));
} }
}); });
}; };
/**
* Set system settings
* @private
*/
EventStoreNodeConnection.prototype.setSystemSettings = function() { EventStoreNodeConnection.prototype.setSystemSettings = function() {
//TODO: set system settings //TODO: set system settings
throw new Error("Not implemented."); throw new Error("Not implemented.");

View File

@ -35,10 +35,12 @@ EventStorePersistentSubscriptionBase.prototype.start = function() {
this._stopped = false; this._stopped = false;
var self = this; 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) this._onEventAppeared.bind(this), this._onSubscriptionDropped.bind(this), this._settings)
.then(function(subscription) { .then(function(subscription) {
console.log('Subscription started.');
self._subscription = subscription; self._subscription = subscription;
return self;
}); });
}; };
@ -77,6 +79,7 @@ EventStorePersistentSubscriptionBase.prototype.fail = function(events, action, r
this._subscription.notifyEventsFailed(ids, action, reason); this._subscription.notifyEventsFailed(ids, action, reason);
}; };
//TODO: this should return a promise
EventStorePersistentSubscriptionBase.prototype.stop = function() { EventStorePersistentSubscriptionBase.prototype.stop = function() {
if (this._verbose) this._log.debug("Persistent Subscription to %s: requesting stop...", this._streamId); if (this._verbose) this._log.debug("Persistent Subscription to %s: requesting stop...", this._streamId);
this._enqueueSubscriptionDropNotification(SubscriptionDropReason.UserInitiated, null); this._enqueueSubscriptionDropNotification(SubscriptionDropReason.UserInitiated, null);
@ -109,43 +112,56 @@ EventStorePersistentSubscriptionBase.prototype._enqueue = function(resolvedEvent
} }
}; };
function runAsync(fn) {
try {
return Promise.resolve(fn());
} catch(e) {
return Promise.reject(e);
}
}
EventStorePersistentSubscriptionBase.prototype._processQueue = function() { EventStorePersistentSubscriptionBase.prototype._processQueue = function() {
//do var ev = this._queue.shift();
//{ if (!ev) {
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();
}
this._isProcessing = false; 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) { EventStorePersistentSubscriptionBase.prototype._dropSubscription = function(reason, error) {
@ -158,8 +174,13 @@ EventStorePersistentSubscriptionBase.prototype._dropSubscription = function(reas
if (this._subscription !== null) if (this._subscription !== null)
this._subscription.unsubscribe(); this._subscription.unsubscribe();
if (this._subscriptionDropped !== null) if (this._subscriptionDropped !== null) {
this._subscriptionDropped(this, reason, error); try {
this._subscriptionDropped(this, reason, error);
} catch (e) {
this._log.error(e, "Persistent Subscription to %s: subscriptionDropped callback failed.", this._streamId);
}
}
this._stopped = true; this._stopped = true;
} }
}; };

View File

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

View File

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

6
src/gossipSeed.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = function GossipSeed(endPoint, hostName) {
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;
Object.freeze(this);
};

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ message NewEvent {
message EventRecord { message EventRecord {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 event_number = 2; required int64 event_number = 2;
required bytes event_id = 3; required bytes event_id = 3;
required string event_type = 4; required string event_type = 4;
required int32 data_content_type = 5; required int32 data_content_type = 5;
@ -48,7 +48,7 @@ message ResolvedEvent {
message WriteEvents { message WriteEvents {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 expected_version = 2; required int64 expected_version = 2;
repeated NewEvent events = 3; repeated NewEvent events = 3;
required bool require_master = 4; required bool require_master = 4;
} }
@ -56,15 +56,16 @@ message WriteEvents {
message WriteEventsCompleted { message WriteEventsCompleted {
required OperationResult result = 1; required OperationResult result = 1;
optional string message = 2; optional string message = 2;
required int32 first_event_number = 3; required int64 first_event_number = 3;
required int32 last_event_number = 4; required int64 last_event_number = 4;
optional int64 prepare_position = 5; optional int64 prepare_position = 5;
optional int64 commit_position = 6; optional int64 commit_position = 6;
optional int64 current_version = 7;
} }
message DeleteStream { message DeleteStream {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 expected_version = 2; required int64 expected_version = 2;
required bool require_master = 3; required bool require_master = 3;
optional bool hard_delete = 4; optional bool hard_delete = 4;
} }
@ -78,7 +79,7 @@ message DeleteStreamCompleted {
message TransactionStart { message TransactionStart {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 expected_version = 2; required int64 expected_version = 2;
required bool require_master = 3; required bool require_master = 3;
} }
@ -109,15 +110,15 @@ message TransactionCommitCompleted {
required int64 transaction_id = 1; required int64 transaction_id = 1;
required OperationResult result = 2; required OperationResult result = 2;
optional string message = 3; optional string message = 3;
required int32 first_event_number = 4; required int64 first_event_number = 4;
required int32 last_event_number = 5; required int64 last_event_number = 5;
optional int64 prepare_position = 6; optional int64 prepare_position = 6;
optional int64 commit_position = 7; optional int64 commit_position = 7;
} }
message ReadEvent { message ReadEvent {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 event_number = 2; required int64 event_number = 2;
required bool resolve_link_tos = 3; required bool resolve_link_tos = 3;
required bool require_master = 4; required bool require_master = 4;
} }
@ -141,7 +142,7 @@ message ReadEventCompleted {
message ReadStreamEvents { message ReadStreamEvents {
required string event_stream_id = 1; required string event_stream_id = 1;
required int32 from_event_number = 2; required int64 from_event_number = 2;
required int32 max_count = 3; required int32 max_count = 3;
required bool resolve_link_tos = 4; required bool resolve_link_tos = 4;
required bool require_master = 5; required bool require_master = 5;
@ -160,8 +161,8 @@ message ReadStreamEventsCompleted {
repeated ResolvedIndexedEvent events = 1; repeated ResolvedIndexedEvent events = 1;
required ReadStreamResult result = 2; required ReadStreamResult result = 2;
required int32 next_event_number = 3; required int64 next_event_number = 3;
required int32 last_event_number = 4; required int64 last_event_number = 4;
required bool is_end_of_stream = 5; required bool is_end_of_stream = 5;
required int64 last_commit_position = 6; required int64 last_commit_position = 6;
@ -199,7 +200,7 @@ message CreatePersistentSubscription {
required string subscription_group_name = 1; required string subscription_group_name = 1;
required string event_stream_id = 2; required string event_stream_id = 2;
required bool resolve_link_tos = 3; required bool resolve_link_tos = 3;
required int32 start_from = 4; required int64 start_from = 4;
required int32 message_timeout_milliseconds = 5; required int32 message_timeout_milliseconds = 5;
required bool record_statistics = 6; required bool record_statistics = 6;
required int32 live_buffer_size = 7; required int32 live_buffer_size = 7;
@ -223,7 +224,7 @@ message UpdatePersistentSubscription {
required string subscription_group_name = 1; required string subscription_group_name = 1;
required string event_stream_id = 2; required string event_stream_id = 2;
required bool resolve_link_tos = 3; required bool resolve_link_tos = 3;
required int32 start_from = 4; required int64 start_from = 4;
required int32 message_timeout_milliseconds = 5; required int32 message_timeout_milliseconds = 5;
required bool record_statistics = 6; required bool record_statistics = 6;
required int32 live_buffer_size = 7; required int32 live_buffer_size = 7;
@ -301,7 +302,7 @@ message PersistentSubscriptionNakEvents {
message PersistentSubscriptionConfirmation { message PersistentSubscriptionConfirmation {
required int64 last_commit_position = 1; required int64 last_commit_position = 1;
required string subscription_id = 2; required string subscription_id = 2;
optional int32 last_event_number = 3; optional int64 last_event_number = 3;
} }
message PersistentSubscriptionStreamEventAppeared { message PersistentSubscriptionStreamEventAppeared {
@ -315,7 +316,7 @@ message SubscribeToStream {
message SubscriptionConfirmation { message SubscriptionConfirmation {
required int64 last_commit_position = 1; required int64 last_commit_position = 1;
optional int32 last_event_number = 2; optional int64 last_event_number = 2;
} }
message StreamEventAppeared { message StreamEventAppeared {
@ -375,3 +376,11 @@ message ScavengeDatabaseCompleted {
required int32 total_time_ms = 3; required int32 total_time_ms = 3;
required int64 total_space_saved = 4; required int64 total_space_saved = 4;
} }
message IdentifyClient {
required int32 version = 1;
optional string connection_name = 2;
}
message ClientIdentified {
}

View File

@ -1,4 +1,6 @@
var SystemConsumerStrategies = require('./systemConsumerStrategies'); var SystemConsumerStrategies = require('./systemConsumerStrategies');
var ensure = require('./common/utils/ensure');
var Long = require('long');
function PersistentSubscriptionSettings( function PersistentSubscriptionSettings(
resolveLinkTos, startFrom, extraStatistics, messageTimeout, resolveLinkTos, startFrom, extraStatistics, messageTimeout,
@ -6,6 +8,9 @@ function PersistentSubscriptionSettings(
checkPointAfter, minCheckPointCount, maxCheckPointCount, checkPointAfter, minCheckPointCount, maxCheckPointCount,
maxSubscriberCount, namedConsumerStrategy maxSubscriberCount, namedConsumerStrategy
) { ) {
ensure.isLongOrInteger(startFrom);
startFrom = Long.fromValue(startFrom);
this.resolveLinkTos = resolveLinkTos; this.resolveLinkTos = resolveLinkTos;
this.startFrom = startFrom; this.startFrom = startFrom;
this.extraStatistics = extraStatistics; this.extraStatistics = extraStatistics;
@ -21,6 +26,8 @@ function PersistentSubscriptionSettings(
this.namedConsumerStrategy = namedConsumerStrategy; 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); 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,170 @@
const http = require('http');
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) {
this._log = log;
this._operationTimeout = operationTimeout;
}
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.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, userCredentials) {
return this.sendDelete(httpEndPoint + '/projection/' + name + '?deleteEmittedStreams=' + deleteEmittedStreams, userCredentials, HTTP_OK);
};
ProjectionsClient.prototype.request = function(method, _url, data, userCredentials, expectedCode) {
const options = url.parse(_url);
options.method = method;
if (userCredentials) {
options.auth = [userCredentials.username, userCredentials.password].join(':');
}
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 = http.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-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,202 @@
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.
* @constructor
*/
function ProjectionsManager(log, httpEndPoint, operationTimeout) {
ensure.notNull(log, "log");
ensure.notNull(httpEndPoint, "httpEndPoint");
this._client = new ProjectionsClient(log, operationTimeout);
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);
};
/**
* 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 userCredentials Credentials for a user with permission to delete a projection.
* @returns {Promise<void>}
*/
ProjectionsManager.prototype.delete = function(name, deleteEmittedStreams, userCredentials) {
return this._client.delete(this._httpEndPoint, name, deleteEmittedStreams, userCredentials);
};
module.exports = ProjectionsManager;

View File

@ -1,9 +1,9 @@
var util = require('util'); var guidParse = require('./common/guid-parse');
var uuid = require('uuid');
var Long = require('long'); var Long = require('long');
var ensure = require('./common/utils/ensure'); var ensure = require('./common/utils/ensure');
/** /**
* @public
* @param {!number|!Long} commitPosition * @param {!number|!Long} commitPosition
* @param {!number|!Long} preparePosition * @param {!number|!Long} preparePosition
* @constructor * @constructor
@ -13,17 +13,9 @@ var ensure = require('./common/utils/ensure');
function Position(commitPosition, preparePosition) { function Position(commitPosition, preparePosition) {
ensure.notNull(commitPosition, "commitPosition"); ensure.notNull(commitPosition, "commitPosition");
ensure.notNull(preparePosition, "preparePosition"); ensure.notNull(preparePosition, "preparePosition");
commitPosition = Long.fromValue(commitPosition); this.commitPosition = Long.fromValue(commitPosition);
preparePosition = Long.fromValue(preparePosition); this.preparePosition = Long.fromValue(preparePosition);
Object.freeze(this);
Object.defineProperties(this, {
commitPosition: {
enumerable: true, value: commitPosition
},
preparePosition: {
enumerable: true, value: preparePosition
}
});
} }
Position.prototype.compareTo = function(other) { Position.prototype.compareTo = function(other) {
@ -45,13 +37,14 @@ const EventReadStatus = {
NoStream: 'noStream', NoStream: 'noStream',
StreamDeleted: 'streamDeleted' StreamDeleted: 'streamDeleted'
}; };
Object.freeze(EventReadStatus);
/** /**
* @param {object} ev * @param {object} ev
* @constructor * @constructor
* @property {string} eventStreamId * @property {string} eventStreamId
* @property {string} eventId * @property {string} eventId
* @property {number} eventNumber * @property {Long} eventNumber
* @property {string} eventType * @property {string} eventType
* @property {number} createdEpoch * @property {number} createdEpoch
* @property {?Buffer} data * @property {?Buffer} data
@ -59,18 +52,16 @@ const EventReadStatus = {
* @property {boolean} isJson * @property {boolean} isJson
*/ */
function RecordedEvent(ev) { function RecordedEvent(ev) {
Object.defineProperties(this, { this.eventStreamId = ev.eventStreamId;
eventStreamId: {enumerable: true, value: ev.event_stream_id}, this.eventId = guidParse.unparse(ev.eventId);
eventId: {enumerable: true, value: uuid.unparse(ev.event_id.buffer, ev.event_id.offset)}, this.eventNumber = ev.eventNumber;
eventNumber: {enumerable: true, value: ev.event_number}, this.eventType = ev.eventType;
eventType: {enumerable: true, value: ev.event_type}, this.created = new Date(ev.createdEpoch ? ev.createdEpoch.toNumber() : 0);
//Javascript doesn't have .Net precision for time, so we use created_epoch for created this.createdEpoch = ev.createdEpoch ? ev.createdEpoch.toNumber() : 0;
created: {enumerable: true, value: new Date(ev.created_epoch ? ev.created_epoch.toInt() : 0)}, this.data = ev.data ? ev.data : new Buffer(0);
createdEpoch: {enumerable: true, value: ev.created_epoch ? ev.created_epoch.toInt() : 0}, this.metadata = ev.metadata ? ev.metadata : new Buffer(0);
data: {enumerable: true, value: ev.data ? ev.data.toBuffer() : new Buffer(0)}, this.isJson = ev.dataContentType === 1;
metadata: {enumerable: true, value: ev.metadata ? ev.metadata.toBuffer() : new Buffer(0)}, Object.freeze(this);
isJson: {enumerable: true, value: ev.data_content_type == 1}
});
} }
/** /**
@ -82,138 +73,83 @@ function RecordedEvent(ev) {
* @property {boolean} isResolved * @property {boolean} isResolved
* @property {?Position} originalPosition * @property {?Position} originalPosition
* @property {string} originalStreamId * @property {string} originalStreamId
* @property {number} originalEventNumber * @property {Long} originalEventNumber
*/ */
function ResolvedEvent(ev) { function ResolvedEvent(ev) {
Object.defineProperties(this, { this.event = ev.event === null ? null : new RecordedEvent(ev.event);
event: { this.link = ev.link === null ? null : new RecordedEvent(ev.link);
enumerable: true, this.originalEvent = this.link || this.event;
value: ev.event === null ? null : new RecordedEvent(ev.event) this.isResolved = this.link !== null && this.event !== null;
}, this.originalPosition = (ev.commitPosition && ev.preparePosition) ? new Position(ev.commitPosition, ev.preparePosition) : null;
link: { this.originalStreamId = this.originalEvent && this.originalEvent.eventStreamId;
enumerable: true, this.originalEventNumber = this.originalEvent && this.originalEvent.eventNumber;
value: ev.link === null ? null : new RecordedEvent(ev.link) Object.freeze(this);
},
originalEvent: {
enumerable: true,
get: function() {
return this.link || this.event;
}
},
isResolved: {
enumerable: true,
get: function() {
return this.link !== null && this.event !== null;
}
},
originalPosition: {
enumerable: true,
value: (ev.commit_position && ev.prepare_position) ? new Position(ev.commit_position, ev.prepare_position) : null
},
originalStreamId: {
enumerable: true,
get: function() {
return this.originalEvent.eventStreamId;
}
},
originalEventNumber: {
enumerable: true,
get: function() {
return this.originalEvent.eventNumber;
}
}
});
} }
/** /**
* *
* @param {string} status * @param {string} status
* @param {string} stream * @param {string} stream
* @param {number} eventNumber * @param {Long} eventNumber
* @param {object} event * @param {object} event
* @constructor * @constructor
* @property {string} status * @property {string} status
* @property {string} stream * @property {string} stream
* @property {number} eventNumber * @property {Long} eventNumber
* @property {ResolvedEvent} event * @property {ResolvedEvent} event
*/ */
function EventReadResult(status, stream, eventNumber, event) { function EventReadResult(status, stream, eventNumber, event) {
Object.defineProperties(this, { this.status = status;
status: {enumerable: true, value: status}, this.stream = stream;
stream: {enumerable: true, value: stream}, this.eventNumber = eventNumber;
eventNumber: {enumerable: true, value: eventNumber}, this.event = status === EventReadStatus.Success ? new ResolvedEvent(event) : null;
event: { Object.freeze(this);
enumerable: true, value: status === EventReadStatus.Success ? new ResolvedEvent(event) : null
}
});
} }
/** /**
* @param {number} nextExpectedVersion * @param {number} nextExpectedVersion
* @param {Position} logPosition * @param {Position} logPosition
* @constructor * @constructor
* @property {number} nextExpectedVersion * @property {Long} nextExpectedVersion
* @property {Position} logPosition * @property {Position} logPosition
*/ */
function WriteResult(nextExpectedVersion, logPosition) { function WriteResult(nextExpectedVersion, logPosition) {
Object.defineProperties(this, { this.nextExpectedVersion = nextExpectedVersion;
nextExpectedVersion: { this.logPosition = logPosition;
enumerable: true, value: nextExpectedVersion Object.freeze(this);
},
logPosition: {
enumerable: true, value: logPosition
}
});
} }
/** /**
* @param {string} status * @param {string} status
* @param {string} stream * @param {string} stream
* @param {number} fromEventNumber * @param {Long} fromEventNumber
* @param {string} readDirection * @param {string} readDirection
* @param {object[]} events * @param {object[]} events
* @param {number} nextEventNumber * @param {Long} nextEventNumber
* @param {number} lastEventNumber * @param {Long} lastEventNumber
* @param {boolean} isEndOfStream * @param {boolean} isEndOfStream
* @constructor * @constructor
* @property {string} status * @property {string} status
* @property {string} stream * @property {string} stream
* @property {number} fromEventNumber * @property {Long} fromEventNumber
* @property {string} readDirection * @property {string} readDirection
* @property {ResolvedEvent[]} events * @property {ResolvedEvent[]} events
* @property {number} nextEventNumber * @property {Long} nextEventNumber
* @property {number} lastEventNumber * @property {Long} lastEventNumber
* @property {boolean} isEndOfStream * @property {boolean} isEndOfStream
*/ */
function StreamEventsSlice( function StreamEventsSlice(
status, stream, fromEventNumber, readDirection, events, nextEventNumber, lastEventNumber, isEndOfStream status, stream, fromEventNumber, readDirection, events, nextEventNumber, lastEventNumber, isEndOfStream
) { ) {
Object.defineProperties(this, { this.status = status;
status: { this.stream = stream;
enumerable: true, value: status this.fromEventNumber = fromEventNumber;
}, this.readDirection = readDirection;
stream: { this.events = events ? events.map(function(ev) { return new ResolvedEvent(ev); }) : [];
enumerable: true, value: stream this.nextEventNumber = nextEventNumber;
}, this.lastEventNumber = lastEventNumber;
fromEventNumber: { this.isEndOfStream = isEndOfStream;
enumerable: true, value: fromEventNumber Object.freeze(this);
},
readDirection: {
enumerable: true, value: readDirection
},
events: {
enumerable: true, value: events ? events.map(function(ev) { return new ResolvedEvent(ev); }) : []
},
nextEventNumber: {
enumerable: true, value: nextEventNumber
},
lastEventNumber: {
enumerable: true, value: lastEventNumber
},
isEndOfStream: {
enumerable: true, value: isEndOfStream
}
})
} }
/** /**
@ -228,23 +164,12 @@ function StreamEventsSlice(
* @property {ResolvedEvent[]} events * @property {ResolvedEvent[]} events
*/ */
function AllEventsSlice(readDirection, fromPosition, nextPosition, events) { function AllEventsSlice(readDirection, fromPosition, nextPosition, events) {
Object.defineProperties(this, { this.readDirection = readDirection;
readDirection: { this.fromPosition = fromPosition;
enumerable: true, value: readDirection this.nextPosition = nextPosition;
}, this.events = events ? events.map(function(ev){ return new ResolvedEvent(ev); }) : [];
fromPosition: { this.isEndOfStream = events === null || events.length === 0;
enumerable: true, value: fromPosition Object.freeze(this);
},
nextPosition: {
enumerable: true, value: nextPosition
},
events: {
enumerable: true, value: events ? events.map(function(ev){ return new ResolvedEvent(ev); }) : []
},
isEndOfStream: {
enumerable: true, value: events === null || events.length === 0
}
});
} }
/** /**
@ -253,32 +178,28 @@ function AllEventsSlice(readDirection, fromPosition, nextPosition, events) {
* @property {Position} logPosition * @property {Position} logPosition
*/ */
function DeleteResult(logPosition) { function DeleteResult(logPosition) {
Object.defineProperties(this, { this.logPosition = logPosition;
logPosition: { Object.freeze(this);
enumerable: true, value: logPosition
}
});
} }
/** /**
* @param {string} stream * @param {string} stream
* @param {boolean} isStreamDeleted * @param {boolean} isStreamDeleted
* @param {number} metastreamVersion * @param {Long} metastreamVersion
* @param {object} streamMetadata * @param {object} streamMetadata
* @constructor * @constructor
* @property {string} stream * @property {string} stream
* @property {boolean} isStreamDeleted * @property {boolean} isStreamDeleted
* @property {number} metastreamVersion * @property {Long} metastreamVersion
* @property {object} streamMetadata * @property {object} streamMetadata
*/ */
function RawStreamMetadataResult(stream, isStreamDeleted, metastreamVersion, streamMetadata) { function RawStreamMetadataResult(stream, isStreamDeleted, metastreamVersion, streamMetadata) {
ensure.notNullOrEmpty(stream); ensure.notNullOrEmpty(stream);
Object.defineProperties(this, { this.stream = stream;
stream: {enumerable: true, value: stream}, this.isStreamDeleted = isStreamDeleted;
isStreamDeleted: {enumerable: true, value: isStreamDeleted}, this.metastreamVersion = metastreamVersion;
metastreamVersion: {enumerable: true, value: metastreamVersion}, this.streamMetadata = streamMetadata;
streamMetadata: {enumerable: true, value: streamMetadata} Object.freeze(this);
});
} }
const PersistentSubscriptionCreateStatus = { const PersistentSubscriptionCreateStatus = {
@ -286,6 +207,7 @@ const PersistentSubscriptionCreateStatus = {
NotFound: 'notFound', NotFound: 'notFound',
Failure: 'failure' Failure: 'failure'
}; };
Object.freeze(PersistentSubscriptionCreateStatus);
/** /**
* @param {string} status * @param {string} status
@ -293,9 +215,8 @@ const PersistentSubscriptionCreateStatus = {
* @property {string} status * @property {string} status
*/ */
function PersistentSubscriptionCreateResult(status) { function PersistentSubscriptionCreateResult(status) {
Object.defineProperties(this, { this.status = status;
status: {enumerable: true, value: status} Object.freeze(this);
});
} }
const PersistentSubscriptionUpdateStatus = { const PersistentSubscriptionUpdateStatus = {
@ -304,6 +225,7 @@ const PersistentSubscriptionUpdateStatus = {
Failure: 'failure', Failure: 'failure',
AccessDenied: 'accessDenied' AccessDenied: 'accessDenied'
}; };
Object.freeze(PersistentSubscriptionUpdateStatus);
/** /**
* @param {string} status * @param {string} status
@ -311,15 +233,15 @@ const PersistentSubscriptionUpdateStatus = {
* @property {string} status * @property {string} status
*/ */
function PersistentSubscriptionUpdateResult(status) { function PersistentSubscriptionUpdateResult(status) {
Object.defineProperties(this, { this.status = status;
status: {enumerable: true, value: status} Object.freeze(this);
});
} }
const PersistentSubscriptionDeleteStatus = { const PersistentSubscriptionDeleteStatus = {
Success: 'success', Success: 'success',
Failure: 'failure' Failure: 'failure'
}; };
Object.freeze(PersistentSubscriptionDeleteStatus);
/** /**
* @param {string} status * @param {string} status
@ -327,9 +249,8 @@ const PersistentSubscriptionDeleteStatus = {
* @property {string} status * @property {string} status
*/ */
function PersistentSubscriptionDeleteResult(status) { function PersistentSubscriptionDeleteResult(status) {
Object.defineProperties(this, { this.status = status;
status: {enumerable: true, value: status} Object.freeze(this);
});
} }
// Exports Constructors // Exports Constructors

View File

@ -3,5 +3,6 @@ const SliceReadStatus = {
StreamNotFound: 'streamNotFound', StreamNotFound: 'streamNotFound',
StreamDeleted: 'streamDeleted' StreamDeleted: 'streamDeleted'
}; };
Object.freeze(SliceReadStatus);
module.exports = SliceReadStatus; module.exports = SliceReadStatus;

View File

@ -3,5 +3,6 @@ const SystemConsumerStrategies = {
RoundRobin: 'RoundRobin', RoundRobin: 'RoundRobin',
Pinned: 'Pinned' Pinned: 'Pinned'
}; };
Object.freeze(SystemConsumerStrategies);
module.exports = SystemConsumerStrategies; module.exports = SystemConsumerStrategies;

View File

@ -71,7 +71,9 @@ const TcpCommand = {
NotHandled: 0xF1, NotHandled: 0xF1,
Authenticate: 0xF2, Authenticate: 0xF2,
Authenticated: 0xF3, Authenticated: 0xF3,
NotAuthenticated: 0xF4 NotAuthenticated: 0xF4,
IdentifyClient: 0xF5,
ClientIdentified: 0xF6
}; };
var _reverseLookup = {}; var _reverseLookup = {};

View File

@ -1,4 +1,4 @@
var uuid = require('uuid'); var guidParse = require('../common/guid-parse');
var createBufferSegment = require('../common/bufferSegment'); var createBufferSegment = require('../common/bufferSegment');
var TcpFlags = require('./tcpFlags'); var TcpFlags = require('./tcpFlags');
@ -25,11 +25,11 @@ TcpPackage.fromBufferSegment = function(data) {
var command = data.buffer[data.offset + CommandOffset]; var command = data.buffer[data.offset + CommandOffset];
var flags = data.buffer[data.offset + FlagsOffset]; var flags = data.buffer[data.offset + FlagsOffset];
var correlationId = uuid.unparse(data.buffer, data.offset + CorrelationOffset); var correlationId = guidParse.unparse(data.buffer, data.offset + CorrelationOffset);
var headerSize = MandatorySize; var headerSize = MandatorySize;
var login = null, pass = null; var login = null, pass = null;
if ((flags & TcpFlags.Authenticated) != 0) if ((flags & TcpFlags.Authenticated) !== 0)
{ {
var loginLen = data.buffer[data.offset + AuthOffset]; var loginLen = data.buffer[data.offset + AuthOffset];
if (AuthOffset + 1 + loginLen + 1 >= data.count) if (AuthOffset + 1 + loginLen + 1 >= data.count)
@ -48,7 +48,7 @@ TcpPackage.fromBufferSegment = function(data) {
}; };
TcpPackage.prototype.asBuffer = function() { TcpPackage.prototype.asBuffer = function() {
if ((this.flags & TcpFlags.Authenticated) != 0) { if ((this.flags & TcpFlags.Authenticated) !== 0) {
var loginBytes = new Buffer(this.login); var loginBytes = new Buffer(this.login);
if (loginBytes.length > 255) throw new Error("Login serialized length should be less than 256 bytes."); if (loginBytes.length > 255) throw new Error("Login serialized length should be less than 256 bytes.");
var passwordBytes = new Buffer(this.password); var passwordBytes = new Buffer(this.password);
@ -57,7 +57,7 @@ TcpPackage.prototype.asBuffer = function() {
var res = new Buffer(MandatorySize + 2 + loginBytes.length + passwordBytes.length + (this.data ? this.data.count : 0)); var res = new Buffer(MandatorySize + 2 + loginBytes.length + passwordBytes.length + (this.data ? this.data.count : 0));
res[CommandOffset] = this.command; res[CommandOffset] = this.command;
res[FlagsOffset] = this.flags; res[FlagsOffset] = this.flags;
uuid.parse(this.correlationId, res, CorrelationOffset); guidParse.parse(this.correlationId, res, CorrelationOffset);
res[AuthOffset] = loginBytes.length; res[AuthOffset] = loginBytes.length;
loginBytes.copy(res, AuthOffset + 1); loginBytes.copy(res, AuthOffset + 1);
@ -72,7 +72,7 @@ TcpPackage.prototype.asBuffer = function() {
var res = new Buffer(MandatorySize + (this.data ? this.data.count : 0)); var res = new Buffer(MandatorySize + (this.data ? this.data.count : 0));
res[CommandOffset] = this.command; res[CommandOffset] = this.command;
res[FlagsOffset] = this.flags; res[FlagsOffset] = this.flags;
uuid.parse(this.correlationId, res, CorrelationOffset); guidParse.parse(this.correlationId, res, CorrelationOffset);
if (this.data) if (this.data)
this.data.copyTo(res, AuthOffset); this.data.copyTo(res, AuthOffset);
return res; return res;

View File

@ -10,11 +10,9 @@ var ensure = require('../common/utils/ensure');
function UserCredentials(username, password) { function UserCredentials(username, password) {
ensure.notNullOrEmpty(username, 'username'); ensure.notNullOrEmpty(username, 'username');
ensure.notNullOrEmpty(password, 'password'); ensure.notNullOrEmpty(password, 'password');
this.username = username;
Object.defineProperties(this, { this.password = password;
username: {enumerable: true, value: username}, Object.freeze(this);
password: {enumerable: true, value: password}
});
} }
module.exports = UserCredentials; module.exports = UserCredentials;

View File

@ -29,7 +29,7 @@ LengthPrefixMessageFramer.prototype._parse = function(bytes) {
{ {
this._packageLength |= (buffer[i] << (this._headerBytes * 8)); // little-endian order this._packageLength |= (buffer[i] << (this._headerBytes * 8)); // little-endian order
++this._headerBytes; ++this._headerBytes;
if (this._headerBytes == HeaderLength) if (this._headerBytes === HeaderLength)
{ {
if (this._packageLength <= 0 || this._packageLength > this._maxPackageSize) if (this._packageLength <= 0 || this._packageLength > this._maxPackageSize)
throw new Error(["Package size is out of bounds: ", this._packageLength, "(max: ", this._maxPackageSize, "."].join('')); throw new Error(["Package size is out of bounds: ", this._packageLength, "(max: ", this._maxPackageSize, "."].join(''));
@ -44,9 +44,9 @@ LengthPrefixMessageFramer.prototype._parse = function(bytes) {
this._bufferIndex += copyCnt; this._bufferIndex += copyCnt;
i += copyCnt - 1; i += copyCnt - 1;
if (this._bufferIndex == this._packageLength) if (this._bufferIndex === this._packageLength)
{ {
if (this._receivedHandler != null) if (this._receivedHandler !== null)
this._receivedHandler(createBufferSegment(this._messageBuffer, 0, this._bufferIndex)); this._receivedHandler(createBufferSegment(this._messageBuffer, 0, this._bufferIndex));
this.reset(); this.reset();
} }

View File

@ -1,7 +1,7 @@
var net = require('net'); var net = require('net');
var createBufferSegment = require('../../common/bufferSegment'); var createBufferSegment = require('../../common/bufferSegment');
const MaxSendPacketSize = 64 * 1000; const MaxSendPacketSize = 64 * 1024;
function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) { function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) {
this._socket = null; this._socket = null;
@ -32,9 +32,13 @@ function TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed) {
TcpConnection.prototype._initSocket = function(socket) { TcpConnection.prototype._initSocket = function(socket) {
this._socket = socket; this._socket = socket;
this._localEndPoint = {host: socket.localAddress, port: socket.localPort}; this._localEndPoint = {host: socket.localAddress, port: socket.localPort};
this._remoteEndPoint.host = socket.remoteAddress;
this._socket.on('drain', this._trySend.bind(this));
this._socket.on('error', this._processError.bind(this)); this._socket.on('error', this._processError.bind(this));
this._socket.on('data', this._processReceive.bind(this)); this._socket.on('data', this._processReceive.bind(this));
this._trySend();
}; };
TcpConnection.prototype.enqueueSend = function(bufSegmentArray) { TcpConnection.prototype.enqueueSend = function(bufSegmentArray) {
@ -53,19 +57,20 @@ TcpConnection.prototype._trySend = function() {
var buffers = []; var buffers = [];
var bytes = 0; var bytes = 0;
var sendPiece = this._sendQueue.shift(); var sendPiece;
while(sendPiece) { while(sendPiece = this._sendQueue.shift()) {
if (bytes + sendPiece.length > MaxSendPacketSize)
break;
buffers.push(sendPiece); buffers.push(sendPiece);
bytes += sendPiece.length; bytes += sendPiece.length;
if (bytes > MaxSendPacketSize)
sendPiece = this._sendQueue.shift(); break;
} }
var joinedBuffers = Buffer.concat(buffers, bytes); var joinedBuffers = Buffer.concat(buffers, bytes);
this._socket.write(joinedBuffers); if (!this._socket.write(joinedBuffers)) {
return;
}
setImmediate(this._trySend.bind(this));
}; };
TcpConnection.prototype._processError = function(err) { TcpConnection.prototype._processError = function(err) {
@ -120,13 +125,13 @@ TcpConnection.prototype._closeInternal = function(err, reason) {
if (this._closed) return; if (this._closed) return;
this._closed = true; this._closed = true;
if (this._socket != null) { if (this._socket !== null) {
this._socket.end(); this._socket.end();
this._socket.unref(); this._socket.unref();
this._socket = null; this._socket = null;
} }
if (this._onConnectionClosed != null) if (this._onConnectionClosed !== null)
this._onConnectionClosed(this, err); this._onConnectionClosed(this, err);
}; };
@ -135,7 +140,7 @@ TcpConnection.createConnectingConnection = function(
onConnectionEstablished, onConnectionFailed, onConnectionClosed onConnectionEstablished, onConnectionFailed, onConnectionClosed
) { ) {
var connection = new TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed); var connection = new TcpConnection(log, connectionId, remoteEndPoint, onConnectionClosed);
var socket = net.connect(remoteEndPoint); var socket = net.connect(remoteEndPoint.port, remoteEndPoint.host);
function onError(err) { function onError(err) {
if (onConnectionFailed) if (onConnectionFailed)
onConnectionFailed(connection, err); onConnectionFailed(connection, err);

View File

@ -7,6 +7,7 @@ var TcpPackage = require('../../systemData/tcpPackage');
var TcpCommand = require('../../systemData/tcpCommand'); var TcpCommand = require('../../systemData/tcpCommand');
/** /**
* @private
* @param log * @param log
* @param remoteEndPoint * @param remoteEndPoint
* @param connectionId * @param connectionId
@ -120,26 +121,26 @@ TcpPackageConnection.prototype._incomingMessageArrived = function(data) {
var message = util.format("TcpPackageConnection: [%j, L%j, %s] ERROR for %s. Connection will be closed.", var message = util.format("TcpPackageConnection: [%j, L%j, %s] ERROR for %s. Connection will be closed.",
this.remoteEndPoint, this.localEndPoint, this._connectionId, this.remoteEndPoint, this.localEndPoint, this._connectionId,
valid ? TcpCommand.getName(pkg.command) : "<invalid package>"); valid ? TcpCommand.getName(pkg.command) : "<invalid package>");
if (this._onError != null) if (this._onError !== null)
this._onError(this, e); this._onError(this, e);
this._log.debug(e, message); this._log.debug(e, message);
} }
}; };
TcpPackageConnection.prototype.startReceiving = function() { TcpPackageConnection.prototype.startReceiving = function() {
if (this._connection == null) if (this._connection === null)
throw new Error("Failed connection."); throw new Error("Failed connection.");
this._connection.receive(this._onRawDataReceived.bind(this)); this._connection.receive(this._onRawDataReceived.bind(this));
}; };
TcpPackageConnection.prototype.enqueueSend = function(pkg) { TcpPackageConnection.prototype.enqueueSend = function(pkg) {
if (this._connection == null) if (this._connection === null)
throw new Error("Failed connection."); throw new Error("Failed connection.");
this._connection.enqueueSend(this._framer.frameData(pkg.asBufferSegment())); this._connection.enqueueSend(this._framer.frameData(pkg.asBufferSegment()));
}; };
TcpPackageConnection.prototype.close = function(reason) { TcpPackageConnection.prototype.close = function(reason) {
if (this._connection == null) if (this._connection === null)
throw new Error("Failed connection."); throw new Error("Failed connection.");
this._connection.close(reason); this._connection.close(reason);
}; };

View File

@ -3,6 +3,7 @@ var util = require('util');
var EventStoreSubsription = require('./eventStoreSubscription'); var EventStoreSubsription = require('./eventStoreSubscription');
/** /**
* @private
* @param {SubscriptionOperation} subscriptionOperation * @param {SubscriptionOperation} subscriptionOperation
* @param {string} streamId * @param {string} streamId
* @param {Position} lastCommitPosition * @param {Position} lastCommitPosition
@ -10,15 +11,15 @@ var EventStoreSubsription = require('./eventStoreSubscription');
* @constructor * @constructor
* @augments {EventStoreSubscription} * @augments {EventStoreSubscription}
*/ */
function VolatileEventStoreConnection(subscriptionOperation, streamId, lastCommitPosition, lastEventNumber) { function VolatileEventStoreSubscription(subscriptionOperation, streamId, lastCommitPosition, lastEventNumber) {
EventStoreSubsription.call(this, streamId, lastCommitPosition, lastEventNumber); EventStoreSubsription.call(this, streamId, lastCommitPosition, lastEventNumber);
this._subscriptionOperation = subscriptionOperation; this._subscriptionOperation = subscriptionOperation;
} }
util.inherits(VolatileEventStoreConnection, EventStoreSubsription); util.inherits(VolatileEventStoreSubscription, EventStoreSubsription);
VolatileEventStoreConnection.prototype.unsubscribe = function() { VolatileEventStoreSubscription.prototype.unsubscribe = function() {
this._subscriptionOperation.unsubscribe(); this._subscriptionOperation.unsubscribe();
}; };
module.exports = VolatileEventStoreConnection; module.exports = VolatileEventStoreSubscription;

View File

@ -1,12 +1,17 @@
var uuid = require('uuid'); var uuid = require('uuid');
var client = require('../src/client'); var client = require('../src/client');
var Long = require('long');
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
module.exports = { module.exports = {
'Append One Event To Stream Happy Path': function(test) { 'Append One Event To Stream Happy Path': function(test) {
test.expect(2);
var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent');
this.conn.appendToStream(this.testStreamName, client.expectedVersion.any, event) this.conn.appendToStream(this.testStreamName, ANY_VERSION, event)
.then(function(result) { .then(function(result) {
test.areEqual("nextExpectedVersion", result.nextExpectedVersion, 0); test.areEqual("nextExpectedVersion", result.nextExpectedVersion, Long.fromNumber(0));
test.ok(result.logPosition, "No log position in result."); test.ok(result.logPosition, "No log position in result.");
test.done(); test.done();
}) })
@ -15,6 +20,7 @@ module.exports = {
}); });
}, },
'Append Multiple Events To Stream Happy Path': function(test) { 'Append Multiple Events To Stream Happy Path': function(test) {
test.expect(2);
const expectedVersion = 25; const expectedVersion = 25;
var events = []; var events = [];
for(var i = 0; i <= expectedVersion; i++) { for(var i = 0; i <= expectedVersion; i++) {
@ -23,9 +29,44 @@ module.exports = {
else else
events.push(client.createJsonEventData(uuid.v4(), {b: Math.random(), a: uuid.v4()}, null, 'otherEvent')); events.push(client.createJsonEventData(uuid.v4(), {b: Math.random(), a: uuid.v4()}, null, 'otherEvent'));
} }
this.conn.appendToStream(this.testStreamName, client.expectedVersion.any, events) this.conn.appendToStream(this.testStreamName, ANY_VERSION, events)
.then(function(result) { .then(function(result) {
test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, expectedVersion); test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, Long.fromNumber(expectedVersion));
test.ok(result.logPosition, "No log position in result.");
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Append Multiple Events To Stream Happy Path Stress Test': function(test) {
test.expect(2);
const expectedVersion = 1000;
var events = [];
for(var i = 0; i <= expectedVersion; i++) {
if (i % 2 === 0)
events.push(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'));
else
events.push(client.createJsonEventData(uuid.v4(), {b: Math.random(), a: uuid.v4()}, null, 'otherEvent'));
}
this.conn.appendToStream(this.testStreamName, ANY_VERSION, events)
.then(function(result) {
test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, Long.fromNumber(expectedVersion));
test.ok(result.logPosition, "No log position in result.");
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Append Large event': function(test) {
test.expect(2);
const largeData = Buffer.alloc(3 * 1024 *1024, " ");
const event = client.createJsonEventData(uuid.v4(), {a: largeData.toString()}, null, 'largePayloadEvent');
this.conn.appendToStream(this.testStreamName, ANY_VERSION, event)
.then(function(result) {
test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, Long.fromNumber(0));
test.ok(result.logPosition, "No log position in result."); test.ok(result.logPosition, "No log position in result.");
test.done(); test.done();
}) })
@ -34,8 +75,9 @@ module.exports = {
}); });
}, },
'Append To Stream Wrong Expected Version': function(test) { 'Append To Stream Wrong Expected Version': function(test) {
test.expect(1);
var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent');
this.conn.appendToStream(this.testStreamName, 10, event) this.conn.appendToStream(this.testStreamName, Long.fromNumber(10), event)
.then(function(result) { .then(function(result) {
test.fail("Append succeeded but should have failed."); test.fail("Append succeeded but should have failed.");
test.done(); test.done();
@ -48,11 +90,12 @@ module.exports = {
}); });
}, },
'Append To Stream Deleted': function(test) { 'Append To Stream Deleted': function(test) {
test.expect(1);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, client.expectedVersion.noStream, true) this.conn.deleteStream(this.testStreamName, NOSTREAM_VERSION, true)
.then(function() { .then(function() {
var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent');
return self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, event) return self.conn.appendToStream(self.testStreamName, ANY_VERSION, event)
}) })
.then(function(result) { .then(function(result) {
test.fail("Append succeeded but should have failed."); test.fail("Append succeeded but should have failed.");
@ -66,12 +109,13 @@ module.exports = {
}); });
}, },
'Append To Stream Access Denied': function(test) { 'Append To Stream Access Denied': function(test) {
test.expect(1);
var self = this; var self = this;
var metadata = {$acl: {$w: "$admins"}}; var metadata = {$acl: {$w: "$admins"}};
this.conn.setStreamMetadataRaw(this.testStreamName, client.expectedVersion.noStream, metadata) this.conn.setStreamMetadataRaw(this.testStreamName, NOSTREAM_VERSION, metadata)
.then(function() { .then(function() {
var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent'); var event = client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'testEvent');
return self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, event) return self.conn.appendToStream(self.testStreamName, ANY_VERSION, event)
}) })
.then(function(result) { .then(function(result) {
test.fail("Append succeeded but should have failed."); test.fail("Append succeeded but should have failed.");

View File

@ -1,326 +0,0 @@
var util = require('util');
var uuid = require('uuid');
var client = require('../src/client');
var NoopLogger = require('../src/common/log/noopLogger');
var consoleLogger = {
debug: function() {
var msg = util.format.apply(util, Array.prototype.slice.call(arguments));
util.log(msg);
},
info: function() {},
error: function() {}
};
function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
}
var testStreamName = 'test-' + uuid.v4();
var userCredentialsForAll = new client.UserCredentials("admin", "changeit");
function testEvent(test, event, expectedVersion) {
if (!event) return;
test.ok(event.event, "Event has no 'event'.");
if (!event.event) return;
test.ok(event.event.eventNumber === expectedVersion, util.format("Wrong expected version. Expected: %d Got: %d", event.event.eventNumber, expectedVersion));
}
module.exports = {
setUp: function(cb) {
var tcpEndPoint = {host: 'localhost', port: 1113};
var settings = {verboseLogging: false, log: new NoopLogger()};
//var settings = {verboseLogging: true, log: consoleLogger};
this.conn = client.EventStoreConnection.create(settings, tcpEndPoint);
this.connError = null;
var self = this;
this.conn.connect()
.catch(function(e) {
self.connError = e;
cb(e);
});
this.conn.on('connected', function() {
cb();
});
},
tearDown: function(cb) {
this.conn.close();
this.conn.on('closed', function() {
cb();
});
this.conn = null;
},
/*
'Test Connection': function(test) {
test.ok(this.connError === null, "Connection error: " + this.connError);
test.done();
},
'Test Append To Stream': function(test) {
var events = [
createRandomEvent()
];
this.conn.appendToStream(testStreamName, client.expectedVersion.any, events)
.then(function(result) {
test.ok(result, "No result.");
test.done();
})
.catch(function (err) {
test.done(err);
});
},*/
'Test Commit Two Events Using Transaction': function(test) {
this.conn.startTransaction(testStreamName, client.expectedVersion.any)
.then(function(trx) {
test.ok(trx, "No transaction.");
return Promise.all([trx, trx.write([createRandomEvent()])]);
})
.then(function(args) {
var trx = args[0];
return Promise.all([trx, trx.write([createRandomEvent()])]);
})
.then(function(args) {
var trx = args[0];
return trx.commit();
})
.then(function(result) {
test.ok(result, "No result.");
test.done();
})
.catch(function(err) {
test.done(err);
});
},
/*
'Test Read One Event': function(test) {
this.conn.readEvent(testStreamName, 0)
.then(function(result) {
test.ok(result, "No result.");
if (result)
test.ok(result.event, "No event. " + result.status);
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Read Stream Forward': function(test) {
this.conn.readStreamEventsForward(testStreamName, 0, 100)
.then(function(result) {
test.ok(result, "No result.");
if (result)
test.ok(result.events.length === 3, "Expecting 3 events, got " + result.events.length);
for(var i = 0; i < 3; i++)
testEvent(test, result.events[i], i);
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Read Stream Backward': function(test) {
this.conn.readStreamEventsBackward(testStreamName, 2, 100)
.then(function(result) {
test.ok(result, "No result.");
if (result)
test.ok(result.events.length === 3, "Expecting 3 events, got " + result.events.length);
for(var i = 0; i < 3; i++)
testEvent(test, result.events[i], 2-i);
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Read All Forward': function(test) {
this.conn.readAllEventsForward(client.positions.start, 100, false, userCredentialsForAll)
.then(function(result) {
test.ok(result, "No result.");
if (result)
test.ok(result.events.length >= 3, "Expecting at least 3 events, got " + result.events.length);
for(var i = 1; i < result.events.length; i++)
test.ok(result.events[i].originalPosition.compareTo(result.events[i-1].originalPosition) > 0,
util.format("event[%d] position is not > event[%d] position.",
result.events[i].originalPosition,
result.events[i-1].originalPosition));
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Read All Backward': function(test) {
this.conn.readAllEventsBackward(client.positions.end, 100, false, userCredentialsForAll)
.then(function(result) {
test.ok(result, "No result.");
if (result)
test.ok(result.events.length >= 3, "Expecting at least 3 events, got " + result.events.length);
for(var i = 1; i < result.events.length; i++)
test.ok(result.events[i].originalPosition.compareTo(result.events[i-1].originalPosition) < 0,
util.format("event[%d] position is not < event[%d] position.",
result.events[i].originalPosition,
result.events[i-1].originalPosition));
test.done();
})
.catch(function(err) {
test.done(err);
});
},*/
'Test Subscribe to Stream': function(test) {
var done = false;
function eventAppeared() {
if (!done) {
done = true;
test.done();
}
}
function subscriptionDropped() {
if (!done) {
done = true;
test.done();
}
}
var conn = this.conn;
this.conn.subscribeToStream(testStreamName, false, eventAppeared, subscriptionDropped)
.then(function(subscription) {
var events = [createRandomEvent()];
return conn.appendToStream(testStreamName, client.expectedVersion.any, events);
})
.catch(function(err) {
done = true;
test.done(err);
})
},
'Test Subscribe to All': function(test) {
var done = false;
function eventAppeared() {
if (!done) {
done = true;
test.done();
}
}
function subscriptionDropped() {
if (!done) {
done = true;
test.done();
}
}
var conn = this.conn;
this.conn.subscribeToAll(false, eventAppeared, subscriptionDropped, userCredentialsForAll)
.then(function(subscription) {
var events = [createRandomEvent()];
return conn.appendToStream(testStreamName, client.expectedVersion.any, events);
})
.catch(function(err) {
done = true;
test.done(err);
});
},
'Test Subscribe to Stream From': function(test) {
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
function eventAppeared(s, e) {
if (liveProcessing) {
liveEvents.push(e);
s.stop();
} else {
catchUpEvents.push(e);
}
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent()];
self.conn.appendToStream('test', client.expectedVersion.any, events);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 1, "Expecting 1 live event, got " + liveEvents.length);
test.ok(catchUpEvents.length > 1, "Expecting at least 1 catchUp event, got " + catchUpEvents.length);
test.done(error);
}
var subscription = this.conn.subscribeToStreamFrom('test', null, false, eventAppeared, liveProcessingStarted, subscriptionDropped);
},
'Test Subscribe to All From': function(test) {
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
function eventAppeared(s, e) {
if (liveProcessing) {
liveEvents.push(e);
s.stop();
} else {
catchUpEvents.push(e);
}
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent()];
self.conn.appendToStream(testStreamName, client.expectedVersion.any, events);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 1, "Expecting 1 live event, got " + liveEvents.length);
test.ok(catchUpEvents.length > 1, "Expecting at least 1 catchUp event, got " + catchUpEvents.length);
test.done(error);
}
var subscription = this.conn.subscribeToAllFrom(null, false, eventAppeared, liveProcessingStarted, subscriptionDropped, userCredentialsForAll);
},
'Test Set Stream Metadata Raw': function(test) {
this.conn.setStreamMetadataRaw(testStreamName, client.expectedVersion.emptyStream, {$maxCount: 100})
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Get Stream Metadata Raw': function(test) {
this.conn.getStreamMetadataRaw(testStreamName)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Create Persistent Subscription': function(test) {
var settings = client.PersistentSubscriptionSettings.create();
this.conn.createPersistentSubscription(testStreamName, 'consumer-1', settings, userCredentialsForAll)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
},
//TODO: Update Persistent Subscription
'Test ConnectTo Persistent Subscription': function(test) {
function eventAppeared(s, e) {
s.stop();
}
function subscriptionDropped(connection, reason, error) {
test.done(error);
}
var subscription = this.conn.connectToPersistentSubscription(testStreamName, 'consumer-1', eventAppeared, subscriptionDropped);
this.conn.appendToStream(testStreamName, client.expectedVersion.any, [createRandomEvent()]);
},
'Test Delete Persistent Subscription': function(test) {
this.conn.deletePersistentSubscription(testStreamName, 'consumer-1', userCredentialsForAll)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
}/*,
'Test Delete Stream': function(test) {
this.conn.deleteStream(testStreamName, client.expectedVersion.any)
.then(function(result) {
test.ok(result, "No result.");
test.done();
})
.catch(function(err) {
test.done(err);
});
}*/
};

32
test/cluster/dns_test.js Normal file
View File

@ -0,0 +1,32 @@
var client = require('../../src/client');
var uuid = require('uuid');
var settings = {
log: {
info: console.log,
error: console.log,
debug: console.log
}
};
var conn = client.createConnection(settings, "discover://es.nicdex.com:2113");
console.log('Connecting...');
conn.on('connected', function (tcpEndPoint) {
console.log('Connected to', tcpEndPoint);
setTimeout(function () {
conn.appendToStream('test-' + uuid.v4(), client.expectedVersion.noStream, [
client.createJsonEventData(uuid.v4(), {abc: 123}, {}, 'myEvent')
]);
}, 5000);
});
conn.on('error', function (err) {
console.log(err.stack);
});
conn.on('closed', function (reason) {
console.log('Connection closed:', reason);
process.exit(-1);
});
conn.connect()
.catch(function (err) {
console.log(err.stack);
process.exit(-1);
});

View File

@ -0,0 +1,37 @@
var client = require('../../src/client');
var uuid = require('uuid');
var settings = {
log: {
info: console.log,
error: console.log,
debug: console.log
}
};
var 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})
];
var conn = client.createConnection(settings, gossipSeeds);
console.log('Connecting...');
conn.on('connected', function (tcpEndPoint) {
console.log('Connected to', tcpEndPoint);
setTimeout(function () {
conn.appendToStream('test-' + uuid.v4(), client.expectedVersion.noStream, [
client.createJsonEventData(uuid.v4(), {abc: 123}, {}, 'myEvent')
]);
}, 5000);
});
conn.on('error', function (err) {
console.log(err.stack);
});
conn.on('closed', function (reason) {
console.log('Connection closed:', reason);
process.exit(-1);
});
conn.connect()
.catch(function (err) {
console.log(err.stack);
process.exit(-1);
});

View File

@ -34,7 +34,8 @@ function setUp(cb) {
settings.log.error(error, "Connection to %j failed.", tcpEndPoint); settings.log.error(error, "Connection to %j failed.", tcpEndPoint);
cb(error); cb(error);
}); });
this.conn.on('connected', function () { this.conn.on('connected', function (tcpEndPoint) {
if (connected) return;
settings.log.debug("Connected to %j.", tcpEndPoint); settings.log.debug("Connected to %j.", tcpEndPoint);
connected = true; connected = true;
cb(); cb();
@ -70,6 +71,32 @@ function eventEqualEventData(name, resolvedEvent, eventData) {
this.ok(Buffer.compare(ev.metadata, eventData.metadata) === 0, name + ".originalEvent.metadata is not equal to original metadata."); this.ok(Buffer.compare(ev.metadata, eventData.metadata) === 0, name + ".originalEvent.metadata is not equal to original metadata.");
} }
function testLiveEvent(name, event, evNumber) {
this.ok(event.event, name + ".event not defined (or null)");
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)");
this.ok(event.isResolved === false, name + ".isResolved should be true");
this.ok(event.originalPosition instanceof client.Position, name + ".originalPosition is not an instance of Position");
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)");
if (typeof evNumber === 'number') {
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber);
} else {
this.ok(typeof event.originalEventNumber === 'number', name + ".originalEventNumber is not a number");
}
}
function testReadEvent(name, event, evNumber) {
this.ok(event.event, name + ".event not defined (or null)");
this.ok(event.originalEvent, name + ".originalEvent not defined (or null)");
this.ok(event.isResolved === false, name + ".isResolved should be true");
this.ok(event.originalPosition === null, name + ".originalPosition is not null");
this.ok(event.originalStreamId, name + ".originalStreamId not defined (or null)");
if (typeof evNumber === 'number') {
this.ok(event.originalEventNumber.toNumber() === evNumber, name + '.originalEventNumber expected ' + evNumber + ' got ' + event.originalEventNumber);
} else {
this.ok(typeof event.originalEventNumber === 'number', name + ".originalEventNumber is not a number");
}
}
var _ = { var _ = {
'setUp': setUp, 'setUp': setUp,
'tearDown': tearDown 'tearDown': tearDown
@ -83,6 +110,8 @@ function wrap(name, testFunc) {
test.areEqual = areEqual.bind(test); test.areEqual = areEqual.bind(test);
test.fail = fail.bind(test); test.fail = fail.bind(test);
test.eventEqualEventData = eventEqualEventData.bind(test); test.eventEqualEventData = eventEqualEventData.bind(test);
test.testLiveEvent = testLiveEvent.bind(test);
test.testReadEvent = testReadEvent.bind(test);
return testFunc.call(this, test); return testFunc.call(this, test);
} }
} }

View File

@ -1,10 +1,12 @@
var client = require('../src/client.js'); var client = require('../src/client.js');
var GossipSeed = require('../src/gossipSeed');
var testBase = require('./common/base_test'); var testBase = require('./common/base_test');
module.exports = { module.exports = {
'Connect To Endpoint Happy Path': function(test) { 'Connect To Endpoint Happy Path': function(test) {
var tcpEndpoint = {hostname: 'localhost', port: 1113}; test.expect(1);
var tcpEndpoint = {host: 'localhost', port: 1113};
var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint); var conn = client.EventStoreConnection.create(testBase.settings(), tcpEndpoint);
conn.connect() conn.connect()
.catch(function(err) { .catch(function(err) {
@ -22,15 +24,16 @@ module.exports = {
test.done(); test.done();
} }
}, },
'Connect To Endpoint That Don\'t Exist': function(test) { 'Connect To Endpoint That Doesn\'t Exist': function(test) {
var tcpEndpoint = {hostname: 'localhost', port: 1114}; test.expect(1);
var tcpEndpoint = {host: 'localhost', port: 11112};
var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections:1}), tcpEndpoint); var conn = client.EventStoreConnection.create(testBase.settings({maxReconnections:1}), tcpEndpoint);
conn.connect() conn.connect()
.catch(function (err) { .catch(function (err) {
test.done(err); test.done(err);
}); });
conn.on('connected', function () { conn.on('connected', function () {
test.fail("Should not be able to connect."); test.ok(false, "Should not be able to connect.");
test.done(); test.done();
}); });
conn.on('error', function (err) { conn.on('error', function (err) {
@ -40,7 +43,36 @@ module.exports = {
test.ok(reason.indexOf("Reconnection limit reached") === 0, "Wrong expected reason."); test.ok(reason.indexOf("Reconnection limit reached") === 0, "Wrong expected reason.");
test.done(); test.done();
}); });
} },
'Create a connection with tcp://host:port string': function(test) {
var conn = client.createConnection({}, 'tcp://localhost:1113');
conn.close();
test.done();
}/*,
'Connect to Cluster using gossip seeds': function (test) {
test.expect(1);
var gossipSeeds = [
new GossipSeed({host: 'localhost', port: 1113}),
new GossipSeed({host: 'localhost', port: 2113}),
new GossipSeed({host: 'localhost', port: 3113})
];
var conn = client.EventStoreConnection.create(testBase.settings(), gossipSeeds);
conn.connect()
.catch(function(err) {
test.done(err);
});
conn.on('connected', function(endPoint){
test.ok(endPoint, "no endpoint");
done();
});
conn.on('error', done);
function done(err) {
conn.close();
if (err) return test.done(err);
test.done();
}
}*/
}; };
testBase.init(module.exports, false); testBase.init(module.exports, false);

View File

@ -1,5 +1,6 @@
var uuid = require('uuid'); var uuid = require('uuid');
var client = require('../src/client'); var client = require('../src/client');
var Long = require('long');
module.exports = { module.exports = {
setUp: function(cb) { setUp: function(cb) {
@ -14,8 +15,9 @@ module.exports = {
.catch(cb); .catch(cb);
}, },
'Test Delete Stream Soft Happy Path': function(test) { 'Test Delete Stream Soft Happy Path': function(test) {
test.expect(4);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, 1, false) this.conn.deleteStream(this.testStreamName, Long.fromNumber(1), false)
.then(function(result) { .then(function(result) {
test.ok(result.logPosition, "No log position in result."); test.ok(result.logPosition, "No log position in result.");
return self.conn.getStreamMetadataRaw(self.testStreamName); return self.conn.getStreamMetadataRaw(self.testStreamName);
@ -31,8 +33,9 @@ module.exports = {
}); });
}, },
'Test Delete Stream Hard Happy Path': function(test) { 'Test Delete Stream Hard Happy Path': function(test) {
test.expect(4);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, 1, true) this.conn.deleteStream(this.testStreamName, Long.fromNumber(1), true)
.then(function(result) { .then(function(result) {
test.ok(result.logPosition, "No log position in result."); test.ok(result.logPosition, "No log position in result.");
return self.conn.getStreamMetadataRaw(self.testStreamName); return self.conn.getStreamMetadataRaw(self.testStreamName);
@ -48,7 +51,8 @@ module.exports = {
}); });
}, },
'Test Delete Stream With Wrong Expected Version': function(test) { 'Test Delete Stream With Wrong Expected Version': function(test) {
this.conn.deleteStream(this.testStreamName, 10) test.expect(1);
this.conn.deleteStream(this.testStreamName, Long.fromNumber(10))
.then(function(result) { .then(function(result) {
test.fail("Delete succeeded but should have failed."); test.fail("Delete succeeded but should have failed.");
test.done(); test.done();
@ -61,10 +65,11 @@ module.exports = {
}); });
}, },
'Test Delete Stream With No Access': function(test) { 'Test Delete Stream With No Access': function(test) {
test.expect(1);
var self = this; var self = this;
this.conn.setStreamMetadataRaw(this.testStreamName, client.expectedVersion.any, {$acl: {$d: "$admins"}}) this.conn.setStreamMetadataRaw(this.testStreamName, client.expectedVersion.any, {$acl: {$d: "$admins"}})
.then(function() { .then(function() {
return self.conn.deleteStream(self.testStreamName, 10); return self.conn.deleteStream(self.testStreamName, Long.fromNumber(10));
}) })
.then(function(result) { .then(function(result) {
test.fail("Delete succeeded but should have failed."); test.fail("Delete succeeded but should have failed.");
@ -78,10 +83,11 @@ module.exports = {
}); });
}, },
'Test Delete Stream Hard When Already Deleted': function(test) { 'Test Delete Stream Hard When Already Deleted': function(test) {
test.expect(1);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, 1, true) this.conn.deleteStream(this.testStreamName, 1, true)
.then(function() { .then(function() {
return self.conn.deleteStream(self.testStreamName, 1, true); return self.conn.deleteStream(self.testStreamName, Long.fromNumber(1), true);
}) })
.then(function(result) { .then(function(result) {
test.fail("Delete succeeded but should have failed."); test.fail("Delete succeeded but should have failed.");

27
test/metadata_test.js Normal file
View File

@ -0,0 +1,27 @@
var client = require('../src/client');
const Long = require('long');
const EMPTY_VERSION = Long.fromNumber(client.expectedVersion.emptyStream);
module.exports = {
'Test Set Stream Metadata Raw': function(test) {
this.conn.setStreamMetadataRaw(this.testStreamName, EMPTY_VERSION, {$maxCount: 100})
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
},
'Test Get Stream Metadata Raw': function(test) {
this.conn.getStreamMetadataRaw(this.testStreamName)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
}
};
require('./common/base_test').init(module.exports);

View File

@ -0,0 +1,81 @@
var util = require('util');
var uuid = require('uuid');
var client = require('../src/client');
const adminCredentials = new client.UserCredentials("admin", "changeit");
function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
}
var testStreamName = 'test-' + uuid.v4();
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = {
'Test Create Persistent Subscription': function(test) {
var settings = client.PersistentSubscriptionSettings.create();
this.conn.createPersistentSubscription(testStreamName, 'consumer-1', settings, adminCredentials)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
},
//TODO: Update Persistent Subscription
'Test ConnectTo Persistent Subscription': function(test) {
test.expect(4);
var receivedEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
_doneCount++;
if (_doneCount < 2) return;
test.done();
}
function eventAppeared(s, e) {
return delayOnlyFirst(receivedEvents.length, function () {
receivedEvents.push(e);
if (receivedEvents.length === 2) s.stop();
});
}
function subscriptionDropped(connection, reason, error) {
if (error) return done(error);
test.ok(receivedEvents[1].originalEventNumber > receivedEvents[0].originalEventNumber, "Received events are out of order.");
done();
}
var self = this;
this.conn.connectToPersistentSubscription(testStreamName, 'consumer-1', eventAppeared, subscriptionDropped)
.then(function(subscription) {
test.ok(subscription, "Subscription is null.");
return self.conn.appendToStream(testStreamName, client.expectedVersion.any, [createRandomEvent(), createRandomEvent()]);
})
.then(function () {
done();
})
.catch(done);
},
'Test Delete Persistent Subscription': function(test) {
this.conn.deletePersistentSubscription(testStreamName, 'consumer-1', adminCredentials)
.then(function(result) {
test.done();
})
.catch(function(err) {
test.done(err);
});
}
};
require('./common/base_test').init(module.exports);

47
test/projections_test.js Normal file
View File

@ -0,0 +1,47 @@
const client = require('../src/client');
const userCredentials = new client.UserCredentials('admin', 'changeit');
const log = new client.NoopLogger();
const httpEndpoint = 'http://127.0.0.1:2113';
const operationTimeout = 5000;
const simpleProjection = "\
fromStream('$stats-127.0.0.1:2113')\
.when({\
$init: function(){\
return {\
count: 0\
}\
},\
$any: function(s,e){\
s.count += 1;\
}\
})\
";
module.exports = {
setUp: function(cb) {
this.projectionsManager = new client.ProjectionsManager(log, httpEndpoint, operationTimeout);
cb();
},
'Create One Time Projection Happy Path': function(test) {
test.expect(1);
this.projectionsManager.createOneTime(simpleProjection, userCredentials)
.then(function (result) {
test.equal(result, undefined);
test.done();
})
.catch(test.done);
},
'List All Happy Path': function(test) {
test.expect(1);
this.projectionsManager.listAll(userCredentials)
.then(function (projections) {
test.ok(projections.length > 0, "no projections");
test.done();
})
.catch(test.done);
}
//TODO: other tests
};

View File

@ -37,6 +37,7 @@ module.exports = {
}) })
}, },
'Read All Events Backward Happy Path': function(test) { 'Read All Events Backward Happy Path': function(test) {
test.expect(4 + maxCount);
var self = this; var self = this;
this.conn.readAllEventsBackward(client.positions.end, maxCount, false, allCredentials) this.conn.readAllEventsBackward(client.positions.end, maxCount, false, allCredentials)
.then(function(slice) { .then(function(slice) {
@ -56,6 +57,7 @@ module.exports = {
}) })
}, },
'Read All Events Backward With No Access': function(test) { 'Read All Events Backward With No Access': function(test) {
test.expect(1);
this.conn.readAllEventsBackward(client.positions.end, maxCount) this.conn.readAllEventsBackward(client.positions.end, maxCount)
.then(function(slice) { .then(function(slice) {
test.fail("readAllEventsBackward succeeded but should have failed."); test.fail("readAllEventsBackward succeeded but should have failed.");

View File

@ -37,6 +37,7 @@ module.exports = {
}) })
}, },
'Read All Events Forward Happy Path': function(test) { 'Read All Events Forward Happy Path': function(test) {
test.expect(5 + maxCount);
this.conn.readAllEventsForward(client.positions.start, maxCount, false, allCredentials) this.conn.readAllEventsForward(client.positions.start, maxCount, false, allCredentials)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.readDirection', slice.readDirection, 'forward'); test.areEqual('slice.readDirection', slice.readDirection, 'forward');
@ -57,6 +58,7 @@ module.exports = {
}) })
}, },
'Read All Events Forward With No Access': function(test) { 'Read All Events Forward With No Access': function(test) {
test.expect(1);
this.conn.readAllEventsForward(client.positions.start, maxCount) this.conn.readAllEventsForward(client.positions.start, maxCount)
.then(function(slice) { .then(function(slice) {
test.fail("readAllEventsForward succeeded but should have failed."); test.fail("readAllEventsForward succeeded but should have failed.");

View File

@ -1,102 +1,145 @@
var util = require('util');
var uuid = require('uuid'); var uuid = require('uuid');
var client = require('../src/client'); var client = require('../src/client');
var Long = require('long');
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
module.exports = { module.exports = {
setUp: function(cb) { setUp: function(cb) {
this.expectedEvent = {a: uuid.v4(), b: Math.random()}; this.expectedEvent = {
a: uuid.v4(),
b: Math.random()
};
this.expectedEventType = 'anEvent'; this.expectedEventType = 'anEvent';
this.expectedEventId = uuid.v4(); this.expectedEventId = uuid.v4();
var event = client.createJsonEventData(this.expectedEventId, this.expectedEvent, null, this.expectedEventType); var event = client.createJsonEventData(this.expectedEventId, this.expectedEvent, null, this.expectedEventType);
this.conn.appendToStream(this.testStreamName, client.expectedVersion.noStream, event) this.conn.appendToStream(this.testStreamName, NOSTREAM_VERSION, event)
.then(function() { .then(function() {
cb(); cb();
}) })
.catch(cb); .catch(cb);
}, },
'Read Event Happy Path': function(test) { 'Read Event Happy Path': function(test) {
test.expect(8);
var self = this; var self = this;
this.conn.readEvent(this.testStreamName, 0) this.conn.readEvent(this.testStreamName, Long.fromNumber(0))
.then(function(result) { .then(function(result) {
test.areEqual('status', result.status, client.eventReadStatus.Success); test.areEqual('status', result.status, client.eventReadStatus.Success);
test.areEqual('stream', result.stream, self.testStreamName); test.areEqual('stream', result.stream, self.testStreamName);
test.areEqual('eventNumber', result.eventNumber, 0); test.areEqual('eventNumber', result.eventNumber, Long.fromNumber(0));
test.ok(result.event !== null, "event is null."); test.ok(result.event !== null, "event is null.");
test.ok(result.event.originalEvent !== null, "event.originalEvent is null."); test.ok(result.event.originalEvent !== null, "event.originalEvent is null.");
var event = JSON.parse(result.event.originalEvent.data.toString()); var event = JSON.parse(result.event.originalEvent.data.toString());
test.areEqual('event.eventId', result.event.originalEvent.eventId, self.expectedEventId); test.areEqual('event.eventId', result.event.originalEvent.eventId, self.expectedEventId);
test.areEqual('event.eventType', result.event.originalEvent.eventType, self.expectedEventType); test.areEqual('event.eventType', result.event.originalEvent.eventType, self.expectedEventType);
test.areEqual('decoded event.data', event, self.expectedEvent); test.areEqual('decoded event.data', event, self.expectedEvent);
test.done(); test.done();
}) })
.catch(function(err) { .catch(function(err) {
test.done(err); test.done(err);
}) })
}, },
'Read Event From Non-Existing Stream': function(test) { 'Read Event From Non-Existing Stream': function(test) {
test.expect(4);
var anotherStream = 'test' + uuid.v4(); var anotherStream = 'test' + uuid.v4();
this.conn.readEvent(anotherStream, 0) this.conn.readEvent(anotherStream, Long.fromNumber(0))
.then(function(result) { .then(function(result) {
test.areEqual('status', result.status, client.eventReadStatus.NoStream); test.areEqual('status', result.status, client.eventReadStatus.NoStream);
test.areEqual('stream', result.stream, anotherStream); test.areEqual('stream', result.stream, anotherStream);
test.areEqual('eventNumber', result.eventNumber, 0); test.areEqual('eventNumber', result.eventNumber, Long.fromNumber(0));
test.areEqual('event', result.event, null); test.areEqual('event', result.event, null);
test.done(); test.done();
}) })
.catch(function(err) { .catch(function(err) {
test.done(err); test.done(err);
}); });
}, },
'Read Event From Deleted Stream': function(test) { 'Read Event From Deleted Stream': function(test) {
test.expect(4);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, 0, true) this.conn.deleteStream(this.testStreamName, 0, true)
.then(function() { .then(function() {
return self.conn.readEvent(self.testStreamName, 0) return self.conn.readEvent(self.testStreamName, Long.fromNumber(0))
}) })
.then(function(result) { .then(function(result) {
test.areEqual('status', result.status, client.eventReadStatus.StreamDeleted); test.areEqual('status', result.status, client.eventReadStatus.StreamDeleted);
test.areEqual('stream', result.stream, self.testStreamName); test.areEqual('stream', result.stream, self.testStreamName);
test.areEqual('eventNumber', result.eventNumber, 0); test.areEqual('eventNumber', result.eventNumber, Long.fromNumber(0));
test.areEqual('event', result.event, null); test.areEqual('event', result.event, null);
test.done(); test.done();
}) })
.catch(function(err) { .catch(function(err) {
test.done(err); test.done(err);
}); });
}, },
'Read Event With Inexisting Version': function(test) { 'Read Event With Inexisting Version': function(test) {
test.expect(4);
var self = this; var self = this;
return self.conn.readEvent(self.testStreamName, 1) return self.conn.readEvent(self.testStreamName, Long.fromNumber(1))
.then(function(result) { .then(function(result) {
test.areEqual('status', result.status, client.eventReadStatus.NotFound); test.areEqual('status', result.status, client.eventReadStatus.NotFound);
test.areEqual('stream', result.stream, self.testStreamName); test.areEqual('stream', result.stream, self.testStreamName);
test.areEqual('eventNumber', result.eventNumber, 1); test.areEqual('eventNumber', result.eventNumber, Long.fromNumber(1));
test.areEqual('event', result.event, null); test.areEqual('event', result.event, null);
test.done(); test.done();
}) })
.catch(function(err) { .catch(function(err) {
test.done(err); test.done(err);
}); });
}, },
'Read Event With No Access': function(test) { 'Read Event With No Access': function(test) {
test.expect(1);
var self = this; var self = this;
var metadata = {$acl: {$r: '$admins'}}; var metadata = {
this.conn.setStreamMetadataRaw(self.testStreamName, client.expectedVersion.noStream, metadata) $acl: {
.then(function(){ $r: '$admins'
return self.conn.readEvent(self.testStreamName, 0); }
}) };
.then(function(result) { this.conn.setStreamMetadataRaw(self.testStreamName, NOSTREAM_VERSION, metadata)
test.fail("readEvent succeeded but should have failed."); .then(function() {
test.done(); return self.conn.readEvent(self.testStreamName, Long.fromNumber(0));
}) })
.catch(function(err) { .then(function(result) {
var isAccessDenied = err instanceof client.AccessDeniedError; test.fail("readEvent succeeded but should have failed.");
test.ok(isAccessDenied, "readEvent should have failed with AccessDeniedError, got " + err.constructor.name); test.done();
if (isAccessDenied) return test.done(); })
test.done(err); .catch(function(err) {
}); var isAccessDenied = err instanceof client.AccessDeniedError;
} test.ok(isAccessDenied, "readEvent should have failed with AccessDeniedError, got " + err.constructor.name);
if (isAccessDenied) return test.done();
test.done(err);
});
},
'Read a Large Event': function(test) {
test.expect(8);
var self = this;
const largeData = Buffer.alloc(3 * 1024 * 1024, " ");
const largeEvent = client.createJsonEventData(uuid.v4(), {
a: largeData.toString()
}, null, 'largePayloadEvent');
this.conn.appendToStream(this.testStreamName, ANY_VERSION, largeEvent)
.then(function(result) {
self.conn.readEvent(self.testStreamName, Long.fromNumber(1))
.then(function(result) {
test.areEqual('status', result.status, client.eventReadStatus.Success);
test.areEqual('stream', result.stream, self.testStreamName);
test.areEqual('eventNumber', result.eventNumber, Long.fromNumber(1));
test.ok(result.event !== null, "event is null.");
test.ok(result.event.originalEvent !== null, "event.originalEvent is null.");
var event = JSON.parse(result.event.originalEvent.data.toString());
test.areEqual('event.eventId', result.event.originalEvent.eventId, largeEvent.eventId);
test.areEqual('event.eventType', result.event.originalEvent.eventType, 'largePayloadEvent');
test.areEqual('decoded event.data', event, JSON.parse(largeEvent.data.toString()));
test.done();
});
})
.catch(function(err) {
test.done(err);
});
},
}; };
require('./common/base_test').init(module.exports); require('./common/base_test').init(module.exports);

View File

@ -1,35 +1,38 @@
var util = require('util');
var uuid = require('uuid'); var uuid = require('uuid');
var client = require('../src/client'); var client = require('../src/client');
var Long = require('long');
const streamSize = 100; const streamSize = 100;
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
module.exports = { module.exports = {
setUp: function(cb) { setUp: function(cb) {
this.eventsData = []; this.eventsData = [];
for(var i = 0; i < streamSize; i++) for(var i = 0; i < streamSize; i++)
this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent'));
this.conn.appendToStream(this.testStreamName, client.expectedVersion.noStream, this.eventsData) this.conn.appendToStream(this.testStreamName, NOSTREAM_VERSION, this.eventsData)
.then(function() { .then(function() {
cb(); cb();
}) })
.catch(cb); .catch(cb);
}, },
'Read Stream Events Backward Happy Path': function(test) { 'Read Stream Events Backward Happy Path': function(test) {
test.expect(7 + (streamSize * 6));
var self = this; var self = this;
this.conn.readStreamEventsBackward(this.testStreamName, streamSize-1, streamSize) this.conn.readStreamEventsBackward(this.testStreamName, Long.fromNumber(streamSize-1), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); test.areEqual('slice.status', slice.status, client.eventReadStatus.Success);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(streamSize-1));
test.areEqual('slice.readDirection', slice.readDirection, 'backward'); test.areEqual('slice.readDirection', slice.readDirection, 'backward');
test.areEqual('slice.nextEventNumber', slice.nextEventNumber, -1); test.areEqual('slice.nextEventNumber', slice.nextEventNumber, Long.fromNumber(-1));
test.areEqual('slice.lastEventNumber', slice.lastEventNumber, streamSize-1); test.areEqual('slice.lastEventNumber', slice.lastEventNumber, Long.fromNumber(streamSize-1));
test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true); test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true);
for(var i = 0; i < streamSize; i++) { for(var i = 0; i < streamSize; i++) {
var reverseIndex = streamSize - i - 1; var reverseIndex = streamSize - i - 1;
test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[reverseIndex]); test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[reverseIndex]);
test.areEqual('slice.events[' + i + '].originalEventNumber', slice.events[i].originalEventNumber, reverseIndex); test.areEqual('slice.events[' + i + '].originalEventNumber', slice.events[i].originalEventNumber, Long.fromNumber(reverseIndex));
} }
test.done(); test.done();
}) })
@ -38,12 +41,13 @@ module.exports = {
}) })
}, },
'Read Stream Events Backward With Non-Existing Stream': function(test) { 'Read Stream Events Backward With Non-Existing Stream': function(test) {
test.expect(4);
var anotherStream = 'test' + uuid.v4(); var anotherStream = 'test' + uuid.v4();
this.conn.readStreamEventsBackward(anotherStream, streamSize-1, streamSize) this.conn.readStreamEventsBackward(anotherStream, Long.fromNumber(streamSize-1), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound); test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound);
test.areEqual('slice.stream', slice.stream, anotherStream); test.areEqual('slice.stream', slice.stream, anotherStream);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(streamSize-1));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -52,15 +56,16 @@ module.exports = {
}); });
}, },
'Read Stream Events Backward With Deleted Stream': function(test) { 'Read Stream Events Backward With Deleted Stream': function(test) {
test.expect(4);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, streamSize-1, true) this.conn.deleteStream(this.testStreamName, Long.fromNumber(streamSize-1), true)
.then(function() { .then(function() {
return self.conn.readStreamEventsBackward(self.testStreamName, streamSize-1, streamSize) return self.conn.readStreamEventsBackward(self.testStreamName, Long.fromNumber(streamSize-1), streamSize)
}) })
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted); test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize-1); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(streamSize-1));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -69,12 +74,13 @@ module.exports = {
}); });
}, },
'Read Stream Events Backward With Inexisting Version': function(test) { 'Read Stream Events Backward With Inexisting Version': function(test) {
test.expect(4);
var self = this; var self = this;
return self.conn.readStreamEventsBackward(self.testStreamName, streamSize * 2, streamSize) return self.conn.readStreamEventsBackward(self.testStreamName, Long.fromNumber(streamSize * 2), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); test.areEqual('slice.status', slice.status, client.eventReadStatus.Success);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize*2); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(streamSize*2));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -83,11 +89,12 @@ module.exports = {
}); });
}, },
'Read Stream Events Backward With No Access': function(test) { 'Read Stream Events Backward With No Access': function(test) {
test.expect(1);
var self = this; var self = this;
var metadata = {$acl: {$r: '$admins'}}; var metadata = {$acl: {$r: '$admins'}};
this.conn.setStreamMetadataRaw(self.testStreamName, client.expectedVersion.noStream, metadata) this.conn.setStreamMetadataRaw(self.testStreamName, NOSTREAM_VERSION, metadata)
.then(function(){ .then(function(){
return self.conn.readStreamEventsBackward(self.testStreamName, streamSize-1, streamSize); return self.conn.readStreamEventsBackward(self.testStreamName, Long.fromNumber(streamSize-1), streamSize);
}) })
.then(function(slice) { .then(function(slice) {
test.fail("readStreamEventsBackward succeeded but should have failed."); test.fail("readStreamEventsBackward succeeded but should have failed.");

View File

@ -1,34 +1,37 @@
var util = require('util');
var uuid = require('uuid'); var uuid = require('uuid');
var client = require('../src/client'); var client = require('../src/client');
var Long = require('long');
const streamSize = 100; const streamSize = 100;
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
module.exports = { module.exports = {
setUp: function(cb) { setUp: function(cb) {
this.eventsData = []; this.eventsData = [];
for(var i = 0; i < streamSize; i++) for(var i = 0; i < streamSize; i++)
this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent')); this.eventsData.push(client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, null, 'anEvent'));
this.conn.appendToStream(this.testStreamName, client.expectedVersion.noStream, this.eventsData) this.conn.appendToStream(this.testStreamName, NOSTREAM_VERSION, this.eventsData)
.then(function() { .then(function() {
cb(); cb();
}) })
.catch(cb); .catch(cb);
}, },
'Read Stream Events Forward Happy Path': function(test) { 'Read Stream Events Forward Happy Path': function(test) {
test.expect(7 + (streamSize * 11));
var self = this; var self = this;
this.conn.readStreamEventsForward(this.testStreamName, 0, streamSize) this.conn.readStreamEventsForward(this.testStreamName, Long.fromNumber(0), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); test.areEqual('slice.status', slice.status, client.eventReadStatus.Success);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(0));
test.areEqual('slice.readDirection', slice.readDirection, 'forward'); test.areEqual('slice.readDirection', slice.readDirection, 'forward');
test.areEqual('slice.nextEventNumber', slice.nextEventNumber, streamSize); test.areEqual('slice.nextEventNumber', slice.nextEventNumber, Long.fromNumber(streamSize));
test.areEqual('slice.lastEventNumber', slice.lastEventNumber, streamSize-1); test.areEqual('slice.lastEventNumber', slice.lastEventNumber, Long.fromNumber(streamSize-1));
test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true); test.areEqual('slice.isEndOfStream', slice.isEndOfStream, true);
for(var i = 0; i < streamSize; i++) { for(var i = 0; i < streamSize; i++) {
test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[i]); test.eventEqualEventData('slice.events[' + i + ']', slice.events[i], self.eventsData[i]);
test.areEqual('slice.events[' + i + '].originalEventNumber', slice.events[i].originalEventNumber, i); test.testReadEvent('slice.events[' + i + ']', slice.events[i], i);
} }
test.done(); test.done();
}) })
@ -37,12 +40,13 @@ module.exports = {
}) })
}, },
'Read Stream Events Forward With Non-Existing Stream': function(test) { 'Read Stream Events Forward With Non-Existing Stream': function(test) {
test.expect(4);
var anotherStream = 'test' + uuid.v4(); var anotherStream = 'test' + uuid.v4();
this.conn.readStreamEventsForward(anotherStream, 0, streamSize) this.conn.readStreamEventsForward(anotherStream, Long.fromNumber(0), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound); test.areEqual('slice.status', slice.status, client.sliceReadStatus.StreamNotFound);
test.areEqual('slice.stream', slice.stream, anotherStream); test.areEqual('slice.stream', slice.stream, anotherStream);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(0));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -51,15 +55,16 @@ module.exports = {
}); });
}, },
'Read Stream Events Forward With Deleted Stream': function(test) { 'Read Stream Events Forward With Deleted Stream': function(test) {
test.expect(4);
var self = this; var self = this;
this.conn.deleteStream(this.testStreamName, streamSize-1, true) this.conn.deleteStream(this.testStreamName, Long.fromNumber(streamSize-1), true)
.then(function() { .then(function() {
return self.conn.readStreamEventsForward(self.testStreamName, 0, streamSize) return self.conn.readStreamEventsForward(self.testStreamName, Long.fromNumber(0), streamSize)
}) })
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted); test.areEqual('slice.status', slice.status, client.eventReadStatus.StreamDeleted);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, 0); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(0));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -68,12 +73,13 @@ module.exports = {
}); });
}, },
'Read Stream Events Forward With Inexisting Version': function(test) { 'Read Stream Events Forward With Inexisting Version': function(test) {
test.expect(4);
var self = this; var self = this;
return self.conn.readStreamEventsForward(self.testStreamName, streamSize * 2, streamSize) return self.conn.readStreamEventsForward(self.testStreamName, Long.fromNumber(streamSize * 2), streamSize)
.then(function(slice) { .then(function(slice) {
test.areEqual('slice.status', slice.status, client.eventReadStatus.Success); test.areEqual('slice.status', slice.status, client.eventReadStatus.Success);
test.areEqual('slice.stream', slice.stream, self.testStreamName); test.areEqual('slice.stream', slice.stream, self.testStreamName);
test.areEqual('slice.fromEventNumber', slice.fromEventNumber, streamSize*2); test.areEqual('slice.fromEventNumber', slice.fromEventNumber, Long.fromNumber(streamSize*2));
test.areEqual('slice.events.length', slice.events.length, 0); test.areEqual('slice.events.length', slice.events.length, 0);
test.done(); test.done();
}) })
@ -82,11 +88,12 @@ module.exports = {
}); });
}, },
'Read Stream Events Forward With No Access': function(test) { 'Read Stream Events Forward With No Access': function(test) {
test.expect(1);
var self = this; var self = this;
var metadata = {$acl: {$r: '$admins'}}; var metadata = {$acl: {$r: '$admins'}};
this.conn.setStreamMetadataRaw(self.testStreamName, client.expectedVersion.noStream, metadata) this.conn.setStreamMetadataRaw(self.testStreamName, NOSTREAM_VERSION, metadata)
.then(function(){ .then(function(){
return self.conn.readStreamEventsForward(self.testStreamName, 0, streamSize); return self.conn.readStreamEventsForward(self.testStreamName, Long.fromNumber(0), streamSize);
}) })
.then(function(slice) { .then(function(slice) {
test.fail("readStreamEventsForward succeeded but should have failed."); test.fail("readStreamEventsForward succeeded but should have failed.");

View File

@ -0,0 +1,183 @@
var util = require('util');
var uuid = require('uuid');
var client = require('../src/client');
const allCredentials = new client.UserCredentials("admin", "changeit");
function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
}
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = {
'Test Subscribe to All From (Start)': function(test) {
test.expect(6);
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
_doneCount++;
if (_doneCount < 2) return;
var catchUpInOrder = true;
for(var i = 1; i < catchUpEvents.length; i++)
catchUpInOrder = catchUpInOrder && (catchUpEvents[i].originalPosition.compareTo(catchUpEvents[i-1].originalPosition) > 0);
test.ok(catchUpInOrder, "Catch-up events are out of order.");
var liveInOrder = true;
for(var j = 1; j < liveEvents.length; j++)
liveInOrder = liveInOrder && (liveEvents[j].originalPosition.compareTo(liveEvents[j-1].originalPosition) > 0);
test.ok(liveInOrder, "Live events are out of order.");
test.done();
}
function eventAppeared(s, e) {
var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) {
liveEvents.push(e);
} else {
catchUpEvents.push(e);
}
if (isLive && liveEvents.length === 2) s.stop();
});
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent(), createRandomEvent()];
self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, events)
.then(function () {
done();
})
.catch(done);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 2, "Expecting 2 live event, got " + liveEvents.length);
test.ok(catchUpEvents.length > 1, "Expecting at least 1 catchUp event, got " + catchUpEvents.length);
done(error);
}
var subscription = this.conn.subscribeToAllFrom(null, false, eventAppeared, liveProcessingStarted, subscriptionDropped, allCredentials);
},
'Test Subscribe to All From (Position)': function(test) {
test.expect(7);
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
_doneCount++;
if (_doneCount < 2) return;
var catchUpInOrder = true;
for(var i = 1; i < catchUpEvents.length; i++)
catchUpInOrder = catchUpInOrder && (catchUpEvents[i].originalPosition.compareTo(catchUpEvents[i-1].originalPosition) > 0);
test.ok(catchUpInOrder, "Catch-up events are out of order.");
var liveInOrder = true;
for(var j = 1; j < liveEvents.length; j++)
liveInOrder = liveInOrder && (liveEvents[j].originalPosition.compareTo(liveEvents[j-1].originalPosition) > 0);
test.ok(liveInOrder, "Live events are out of order.");
test.done();
}
function eventAppeared(s, e) {
var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) {
liveEvents.push(e);
} else {
catchUpEvents.push(e);
}
if (isLive && liveEvents.length === 2) s.stop();
});
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent(), createRandomEvent()];
self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, events)
.then(function () {
done();
})
.catch(done);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 2, "Expecting 2 live event, got " + liveEvents.length);
test.ok(catchUpEvents.length > 1, "Expecting at least 1 catchUp event, got " + catchUpEvents.length);
done(error);
}
this.conn.readAllEventsForward(client.positions.start, 512, true, allCredentials)
.then(function (slice) {
var subscription = self.conn.subscribeToAllFrom(slice.nextPosition, false, eventAppeared, liveProcessingStarted, subscriptionDropped, allCredentials);
test.ok(subscription, "Subscription is null/undefined.");
});
},
'Test Subscribe to All From (End)': function(test) {
test.expect(6);
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
_doneCount++;
if (_doneCount < 2) return;
var liveInOrder = true;
for(var j = 1; j < liveEvents.length; j++)
liveInOrder = liveInOrder && (liveEvents[j].originalPosition.compareTo(liveEvents[j-1].originalPosition) > 0);
test.ok(liveInOrder, "Live events are out of order.");
test.done();
}
function eventAppeared(s, e) {
var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) {
liveEvents.push(e);
} else {
catchUpEvents.push(e);
}
if (isLive && liveEvents.length === 2) s.stop();
});
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent(), createRandomEvent()];
self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, events)
.then(function () {
done();
})
.catch(done);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 2, "Expecting 2 live event, got " + liveEvents.length);
test.ok(catchUpEvents.length === 0, "Expecting 0 catchUp event, got " + catchUpEvents.length);
done(error);
}
this.conn.readAllEventsForward(client.positions.end, 512, true, allCredentials)
.then(function (slice) {
var subscription = self.conn.subscribeToAllFrom(slice.nextPosition, false, eventAppeared, liveProcessingStarted, subscriptionDropped, allCredentials);
test.ok(subscription, "Subscription is null/undefined.");
});
}
};
require('./common/base_test').init(module.exports);

View File

@ -0,0 +1,72 @@
const uuid = require('uuid');
const client = require('../src/client');
const allCredentials = new client.UserCredentials("admin", "changeit");
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = {
'Test Subscribe To All Happy Path': function(test) {
const resolveLinkTos = false;
const numberOfPublishedEvents = 5;
test.expect(numberOfPublishedEvents + 4);
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
if (++_doneCount < 2) return;
test.done();
}
var publishedEvents = [];
for(var i=0;i<numberOfPublishedEvents;i++)
publishedEvents.push(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'anEvent'));
function testAllPublishedEventsAppeared() {
test.areEqual("receivedEvents.length", receivedEvents.length, numberOfPublishedEvents);
}
function testEventsAppearedInCorrectOrder() {
for (var j = 0; j < numberOfPublishedEvents; j++)
test.ok(receivedEvents[j].originalEvent.eventId === publishedEvents[j].eventId,
"receivedEvents[" + j + "] != publishedEvents[" + j + "]");
}
var receivedEvents = [];
function eventAppeared(subscription, event) {
delayOnlyFirst(receivedEvents.length, function() {
receivedEvents.push(event);
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
});
}
function subscriptionDropped(subscription, reason, error) {
if (error) return done(error);
testAllPublishedEventsAppeared();
testEventsAppearedInCorrectOrder();
done();
}
var self = this;
this.conn.subscribeToAll(resolveLinkTos, eventAppeared, subscriptionDropped, allCredentials)
.then(function(subscription) {
test.areEqual("subscription.isSubscribedToAll", subscription.isSubscribedToAll, true);
return self.conn.appendToStream(self.testStreamName, client.expectedVersion.emptyStream, publishedEvents);
})
.then(function () {
done();
})
.catch(test.done)
}
};
require('./common/base_test').init(module.exports);

View File

@ -0,0 +1,137 @@
var uuid = require('uuid');
var client = require('../src/client');
var Long = require('long');
function createRandomEvent() {
return client.createJsonEventData(uuid.v4(), {a: uuid.v4(), b: Math.random()}, {createdAt: Date.now()}, 'testEvent');
}
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = {
'Test Subscribe to Stream From Beginning (null)': function(test) {
test.expect(32);
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
if (++_doneCount < 2) return;
test.done();
}
function eventAppeared(s, e) {
var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) {
liveEvents.push(e);
} else {
catchUpEvents.push(e);
}
if (isLive && liveEvents.length === 2) s.stop();
});
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent(), createRandomEvent()];
self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, events)
.then(function () {
done();
})
.catch(done);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 2, "Expecting 2 live event, got " + liveEvents.length);
test.testLiveEvent('liveEvents[0]', liveEvents[0], 2);
test.testLiveEvent('liveEvents[1]', liveEvents[1], 3);
test.ok(catchUpEvents.length === 2, "Expecting 2 catchUp event, got " + catchUpEvents.length);
test.testReadEvent('catchUpEvents[0]', catchUpEvents[0], 0);
test.testReadEvent('catchUpEvents[1]', catchUpEvents[1], 1);
done(error);
}
var events = [createRandomEvent(), createRandomEvent()];
this.conn.appendToStream(self.testStreamName, client.expectedVersion.noStream, events)
.then(function() {
var subscription = self.conn.subscribeToStreamFrom(self.testStreamName, null, false, eventAppeared, liveProcessingStarted, subscriptionDropped);
test.areEqual("subscription.streamId", subscription.streamId, self.testStreamName);
test.areEqual("subscription.isSubscribedToAll", subscription.isSubscribedToAll, false);
test.areEqual("subscription.readBatchSize", subscription.readBatchSize, 500);
test.areEqual("subscription.maxPushQueueSize", subscription.maxPushQueueSize, 10000);
})
.catch(test.done);
},
'Test Subscribe to Stream From 0': function(test) {
test.expect(26);
var self = this;
var liveProcessing = false;
var catchUpEvents = [];
var liveEvents = [];
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
if (++_doneCount < 2) return;
test.done();
}
function eventAppeared(s, e) {
var isLive = liveProcessing;
delayOnlyFirst(isLive ? liveEvents.length : catchUpEvents.length, function() {
if (isLive) {
liveEvents.push(e);
} else {
catchUpEvents.push(e);
}
if (isLive && liveEvents.length === 2) s.stop();
});
}
function liveProcessingStarted() {
liveProcessing = true;
var events = [createRandomEvent(), createRandomEvent()];
self.conn.appendToStream(self.testStreamName, client.expectedVersion.any, events)
.then(function () {
done();
})
.catch(done);
}
function subscriptionDropped(connection, reason, error) {
test.ok(liveEvents.length === 2, "Expecting 2 live event, got " + liveEvents.length);
test.testLiveEvent('liveEvents[0]', liveEvents[0], 2);
test.testLiveEvent('liveEvents[1]', liveEvents[1], 3);
test.ok(catchUpEvents.length === 1, "Expecting 1 catchUp event, got " + catchUpEvents.length);
test.testReadEvent('catchUpEvents[0]', catchUpEvents[0], 1);
done(error);
}
var events = [createRandomEvent(), createRandomEvent()];
this.conn.appendToStream(self.testStreamName, client.expectedVersion.noStream, events)
.then(function() {
var subscription = self.conn.subscribeToStreamFrom(self.testStreamName, Long.fromNumber(0), false, eventAppeared, liveProcessingStarted, subscriptionDropped);
test.areEqual("subscription.streamId", subscription.streamId, self.testStreamName);
test.areEqual("subscription.isSubscribedToAll", subscription.isSubscribedToAll, false);
test.areEqual("subscription.readBatchSize", subscription.readBatchSize, 500);
test.areEqual("subscription.maxPushQueueSize", subscription.maxPushQueueSize, 10000);
})
.catch(test.done);
}
};
require('./common/base_test').init(module.exports);

View File

@ -0,0 +1,73 @@
const uuid = require('uuid');
const client = require('../src/client');
const Long = require('long');
function delay(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
})
}
function delayOnlyFirst(count, action) {
if (count === 0) return action();
return delay(200)
.then(function () {
action();
})
}
module.exports = {
'Test Subscribe To Stream Happy Path': function(test) {
const resolveLinkTos = false;
const numberOfPublishedEvents = 5;
test.expect(numberOfPublishedEvents + 6);
var publishedEvents = [];
for(var i=0;i<numberOfPublishedEvents;i++)
publishedEvents.push(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'anEvent'));
var _doneCount = 0;
function done(err) {
test.ok(!err, err ? err.stack : '');
if (++_doneCount < 2) return;
test.done();
}
function testAllPublishedEventsAppeared() {
test.areEqual("receivedEvents.length", receivedEvents.length, numberOfPublishedEvents);
}
function testEventsAppearedInCorrectOrder() {
for (var j = 0; j < numberOfPublishedEvents; j++)
test.ok(receivedEvents[j].originalEvent.eventId === publishedEvents[j].eventId,
"receivedEvents[" + j + "] != publishedEvents[" + j + "]");
}
var receivedEvents = [];
function eventAppeared(subscription, event) {
delayOnlyFirst(receivedEvents.length, function () {
receivedEvents.push(event);
if (receivedEvents.length === numberOfPublishedEvents) subscription.close();
});
}
function subscriptionDropped(subscription, reason, error) {
if (error) return done(error);
testAllPublishedEventsAppeared();
testEventsAppearedInCorrectOrder();
done();
}
var self = this;
this.conn.subscribeToStream(this.testStreamName, resolveLinkTos, eventAppeared, subscriptionDropped)
.then(function(subscription) {
test.areEqual("subscription.streamId", subscription.streamId, self.testStreamName);
test.areEqual("subscription.isSubscribedToAll", subscription.isSubscribedToAll, false);
test.areEqual("subscription.lastEventNumber", subscription.lastEventNumber, Long.fromNumber(client.expectedVersion.emptyStream));
return self.conn.appendToStream(self.testStreamName, client.expectedVersion.emptyStream, publishedEvents);
})
.then(function () {
done();
})
.catch(test.done)
}
};
require('./common/base_test').init(module.exports);

195
test/transactions_test.js Normal file
View File

@ -0,0 +1,195 @@
var uuid = require('uuid');
var Long = require('long');
var client = require('../src/client');
var ANY_VERSION = Long.fromNumber(client.expectedVersion.any);
var NOSTREAM_VERSION = Long.fromNumber(client.expectedVersion.noStream);
var EMPTY_VERSION = Long.fromNumber(client.expectedVersion.emptyStream);
module.exports = {
setUp: function(cb) {
cb();
},
'Start A Transaction Happy Path': function(test) {
test.expect(1);
this.conn.startTransaction(this.testStreamName, NOSTREAM_VERSION)
.then(function(trx) {
test.ok(Long.isLong(trx.transactionId), "trx.transactionId should be a Long.");
test.done();
})
.catch(test.done);
},
/*
'Start A Transaction With Wrong Expected Version': function(test) {
this.conn.startTransaction(this.testStreamName, 10)
.then(function(trx) {
test.fail("Start Transaction with wrong expected version succeeded.");
test.done();
})
.catch(function(err) {
var isWrongExpectedVersion = err instanceof client.WrongExpectedVersionError;
if (isWrongExpectedVersion) return test.done();
test.done(err);
});
},
'Start A Transaction With Deleted Stream': function(test) {
var self = this;
this.conn.deleteStream(this.testStreamName, client.expectedVersion.emptyStream)
.then(function() {
return self.conn.startTransaction(self.testStreamName, ANY_VERSION);
})
.then(function(trx) {
test.fail("Start Transaction with deleted stream succeeded.");
test.done();
})
.catch(function(err) {
var isStreamDeleted = err instanceof client.StreamDeletedError;
test.ok(isStreamDeleted, "Expected StreamDeletedError got " + err.constructor.name);
if (isStreamDeleted) return test.done();
test.done(err);
});
},
*/
'Start A Transaction With No Access': function(test) {
test.expect(1);
var self = this;
var metadata = {$acl: {$w: "$admins"}};
this.conn.setStreamMetadataRaw(this.testStreamName, EMPTY_VERSION, metadata)
.then(function() {
return self.conn.startTransaction(self.testStreamName, ANY_VERSION);
})
.then(function(trx) {
test.fail("Start Transaction with no access succeeded.");
test.done();
})
.catch(function(err) {
var isAccessDenied = err instanceof client.AccessDeniedError;
test.ok(isAccessDenied, "Expected AccessDeniedError got " + err.constructor.name);
if (isAccessDenied) return test.done();
test.done(err);
});
},
'Continue A Transaction Happy Path': function(test) {
var self = this;
this.conn.startTransaction(this.testStreamName, EMPTY_VERSION)
.then(function(trx) {
return trx.write(client.createJsonEventData(uuid.v4(), {a: Math.random()}, null, 'anEvent'))
.then(function () {
return self.conn.continueTransaction(trx.transactionId);
});
})
.then(function(trx) {
return trx.write(client.createJsonEventData(uuid.v4(), {a: Math.random()}, null, 'anEvent'))
.then(function() {
return trx.commit();
})
.then(function() {
test.done();
});
})
.catch(test.done);
},
'Write/Commit Transaction Happy Path': function(test) {
test.expect(2);
var self = this;
this.conn.startTransaction(this.testStreamName, EMPTY_VERSION)
.then(function(trx) {
self.events = [];
for(var i = 0; i < 15; i++) {
var event = {a: uuid.v4(), b: Math.random()};
self.events.push(client.createJsonEventData(uuid.v4(), event, null, 'anEvent'));
}
return trx.write(self.events)
.then(function() {
var events = [];
for(var j = 0; j < 9; j++) {
var event = {a: Math.random(), b: uuid.v4()};
events.push(client.createJsonEventData(uuid.v4(), event, null, 'anotherEvent'));
}
Array.prototype.push.apply(self.events, events);
trx.write(events);
})
.then(function() {
return trx.commit();
});
})
.then(function(result) {
test.ok(result.logPosition, "Missing result.logPosition");
test.areEqual("result.nextExpectedVersion", result.nextExpectedVersion, Long.fromNumber(self.events.length-1));
test.done();
})
.catch(test.done);
},
'Write/Commit Transaction With Wrong Expected Version': function(test) {
test.expect(1);
this.conn.startTransaction(this.testStreamName, 10)
.then(function(trx) {
return trx.write(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'anEvent'))
.then(function() {
return trx.commit();
});
})
.then(function() {
test.fail("Commit on transaction with wrong expected version succeeded.");
test.done();
})
.catch(function(err) {
var isWrongExpectedVersion = err instanceof client.WrongExpectedVersionError;
test.ok(isWrongExpectedVersion, "Expected WrongExpectedVersionError, but got " + err.constructor.name);
if (isWrongExpectedVersion) return test.done();
test.done(err);
});
},
'Write/Commit Transaction With Deleted Stream': function(test) {
test.expect(1);
var self = this;
this.conn.deleteStream(this.testStreamName, EMPTY_VERSION, true)
.then(function() {
return self.conn.startTransaction(self.testStreamName, ANY_VERSION);
})
.then(function(trx) {
return trx.write(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'anEvent'))
.then(function() {
return trx.commit();
});
})
.then(function() {
test.fail("Commit on transaction on deleted stream succeeded.");
test.done();
})
.catch(function(err) {
var isStreamDeleted = err instanceof client.StreamDeletedError;
test.ok(isStreamDeleted, "Expected StreamDeletedError, but got " + err.constructor.name);
if (isStreamDeleted) return test.done();
test.done(err);
});
},
'Write/Commit Transaction With No Write Access': function(test) {
test.expect(1);
var self = this;
this.conn.startTransaction(this.testStreamName, ANY_VERSION)
.then(function(trx) {
var metadata = {$acl: {$w: "$admins"}};
return self.conn.setStreamMetadataRaw(self.testStreamName, EMPTY_VERSION, metadata)
.then(function () {
return trx.write(client.createJsonEventData(uuid.v4(), {a: Math.random(), b: uuid.v4()}, null, 'anEvent'))
.then(function () {
return trx.commit();
});
})
})
.then(function() {
test.fail("Commit on transaction on deleted stream succeeded.");
test.done();
})
.catch(function(err) {
var isAccessDenied = err instanceof client.AccessDeniedError;
test.ok(isAccessDenied, "Expected AccessDeniedError, but got " + err.constructor.name);
if (isAccessDenied) return test.done();
test.done(err);
});
}
};
require('./common/base_test').init(module.exports);

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