chore: update dependencies, modernize build, drop node 8 support

This commit is contained in:
Connor Peet 2020-06-15 21:38:24 -07:00
Родитель 0ffcec9676
Коммит 92f7e69ef6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CF8FD2EA0DBC61BD
68 изменённых файлов: 1238 добавлений и 2893 удалений

Просмотреть файл

@ -1,11 +0,0 @@
# editorconfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
indent_style = space
indent_size = 2

28
.eslintrc.js Normal file
Просмотреть файл

@ -0,0 +1,28 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
module.exports = {
ignorePatterns: ['**/*.d.ts', '**/test/*.*', '**/*.js'],
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended'],
plugins: ['header'],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'header/header': [
'error',
'block',
'---------------------------------------------------------\n * Copyright (C) Microsoft Corporation. All rights reserved.\n *--------------------------------------------------------',
],
// todo: clean these:
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
},
};

1
.gitignore поставляемый
Просмотреть файл

@ -36,4 +36,3 @@ npm-debug.log
/lib
/.nyc_output
/*.etcd/
/.vscode

Просмотреть файл

@ -1,2 +1,3 @@
/*
!/lib
/lib/test

3
.vscode/extensions.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}

25
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Run Tests",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/node_modules/mocha/bin/mocha",
"preLaunchTask": "tsc: build - tsconfig.json",
"args": [
"--timeout",
"60000",
"--require",
"source-map-support/register",
"--require",
"./lib/test/_setup.js",
"\"lib/test/**/*.test.js\""
]
}
]
}

10
.vscode/settings.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
{
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"search.exclude": {
"**/lib": true,
"**/.nyc_output": true,
"**/docs": true,
}
}

Просмотреть файл

@ -6,7 +6,7 @@
*
* Usage:
*
* > node bin/generate-methods proto/rpc.proto > src/methods.ts
* > node bin/generate-methods proto/rpc.proto > src/rpc.ts
*
* protobufjs does have a TypeScript generator but its output isn't very useful
* for grpc, much less this client. Rather than reprocessing it, let's just

Просмотреть файл

@ -1,2 +1,2 @@
export class <%= name %>Client {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}

Просмотреть файл

@ -3,6 +3,7 @@
return this.client
.getConnection('<%= service %>')
.then(({ resource, client, metadata }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (<any> client).<%= _.lowerFirst(name) %>(metadata, options);
stream.on('error', () => this.client.markFailed(resource));
return stream;

Просмотреть файл

@ -3,6 +3,7 @@
return this.client
.getConnection('<%= service %>')
.then(({ resource, client, metadata }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (<any> client).<%= _.lowerFirst(name) %>(metadata, options, <%= req.empty ? '{}' : 'req' %>);
stream.on('error', () => this.client.markFailed(resource));
return stream;

Просмотреть файл

@ -1,15 +1,19 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
// AUTOGENERATED CODE, DO NOT EDIT
// tslint:disable
import * as grpc from 'grpc';
import { StatusMessage } from './grpcTypes';
export interface ICallable<T> {
exec(
exec<T>(
service: keyof typeof Services,
method: string,
params: object,
params: unknown,
options?: grpc.CallOptions,
): Promise<any>;
): Promise<T>;
getConnection(
service: keyof typeof Services,
@ -21,7 +25,7 @@ export interface ICallable<T> {
export interface IResponseStream<T> {
on(event: 'data', fn: (item: T) => void): this;
on(event: 'end', fn: () => void): this;
on(event: 'status', fn: (status: grpc.StatusMessage) => void): this;
on(event: 'status', fn: (status: StatusMessage) => void): this;
on(event: 'error', fn: (err: Error) => void): this;
}

Просмотреть файл

@ -1,3 +1,9 @@
## 1.0.0 TBA
- **breaking**: Node < 10 is no longer supported
- **breaking**: `bignumber.js`, used to handle 64-bit numbers returned from etcd, updated from 5.x to 9.0.0
- **breaking**: TypeScript is updated to 3.9, and the types of some function signatures have been narrowed
## 0.2.13 2019-07-03
- **bug**: fixed comparisons breaking in STM when using namespaces (see [#90](https://github.com/mixer/etcd3/issues/90))

Просмотреть файл

@ -1,8 +0,0 @@
version: '3.6'
services:
etcd:
build:
context: .
ports:
- 2379:2379
- 2380:2380

3491
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -6,18 +6,19 @@
"typings": "lib/src/index.d.ts",
"scripts": {
"test": "npm-run-all --parallel test:lint test:unit",
"test:unit": "mocha",
"test:unit": "mocha --require source-map-support/register --require ./lib/test/_setup.js \"lib/test/**/*.test.js\" --timeout 10000 --exit",
"test:cover": "nyc mocha",
"test:lint": "tslint --project tsconfig.json \"{src,test}/**/*.ts\"",
"test:lint": "eslint \"src/**/*.ts\"",
"watch": "tsc --watch",
"build:proto": "node ./bin/update-proto ./proto && node bin/generate-methods.js ./proto/rpc.proto > src/rpc.ts && npm run fmt:ts",
"build:doc": "rm -rf docs && typedoc --gitRevision `git describe --abbrev=0 --tags` --exclude \"**/test/*\" --excludePrivate --out ./docs ./src/index.ts && node bin/tame-typedoc",
"build:ts": "tsc && cp -R proto lib",
"fmt": "prettier --write \"{src,test}/**/*.{ts,js}\" && npm run -s test:lint -- --fix",
"build:doc": "rimraf docs && typedoc --gitRevision `git describe --abbrev=0 --tags` --exclude \"**/test/*\" --excludePrivate --out ./docs ./src/index.ts && node bin/tame-typedoc",
"build:ts": "tsc",
"fmt": "prettier --write \"src/**/*.{ts,js}\" && npm run -s test:lint -- --fix",
"prepare": "npm run -s build:ts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mixer/etcd3.git"
"url": "git+https://github.com/microsoft/etcd3.git"
},
"nyc": {
"include": [
@ -47,42 +48,47 @@
"author": "Connor Peet <connor@peet.io>",
"license": "MIT",
"bugs": {
"url": "https://github.com/mixer/etcd3/issues"
"url": "https://github.com/microsoft/etcd3/issues"
},
"homepage": "https://github.com/mixer/etcd3#readme",
"homepage": "https://github.com/microsoft/etcd3#readme",
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/chai-as-promised": "^7.1.0",
"@types/chai-subset": "^1.3.2",
"@types/mocha": "^5.2.7",
"@types/node": "^7.0.12",
"@types/sinon": "^7.0.13",
"@types/chai": "^4.2.11",
"@types/chai-as-promised": "^7.1.2",
"@types/chai-subset": "^1.3.3",
"@types/mocha": "^7.0.2",
"@types/node": "^14.0.13",
"@types/sinon": "^9.0.4",
"@typescript-eslint/eslint-plugin": "^3.3.0",
"@typescript-eslint/parser": "^3.3.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.5.0",
"change-case": "^3.1.0",
"lodash": "^4.17.11",
"mocha": "^6.1.4",
"chai-subset": "^1.6.0",
"change-case": "^4.1.1",
"eslint": "^7.2.0",
"eslint-plugin-header": "^3.0.0",
"lodash": "^4.17.15",
"mocha": "^8.0.1",
"ncp": "^2.0.0",
"node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1",
"prettier": "^1.18.2",
"protobufjs": "^6.8.8",
"sinon": "^7.3.2",
"ts-node": "^8.3.0",
"tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0",
"typedoc": "^0.14.2",
"typescript": "^3.5.2"
"nyc": "^15.1.0",
"prettier": "^2.0.5",
"protobufjs": "^6.9.0",
"rimraf": "^3.0.2",
"sinon": "^9.0.2",
"ts-node": "^8.10.2",
"typedoc": "^0.17.7",
"typescript": "^3.9.5"
},
"dependencies": {
"@grpc/proto-loader": "^0.5.1",
"bignumber.js": "^5.0.0",
"grpc": "^1.21.1"
"@grpc/proto-loader": "^0.5.4",
"bignumber.js": "^9.0.0",
"grpc": "^1.24.3"
},
"prettier": {
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
"printWidth": 100,
"arrowParens": "avoid"
}
}

Просмотреть файл

@ -41,6 +41,6 @@ $ docker-compose down
### Contributing
Running tests for this module requires running an etcd3 server locally. The tests try to use the default port initially, and you can configure this by setting the `ETCD_ADDR` environment variable, like `export ETCD_ADDR=localhost:12345`.# Contributing
Running tests for this module requires running an etcd3 server locally. The tests try to use the default port initially, and you can configure this by setting the `ETCD_ADDR` environment variable, like `export ETCD_ADDR=localhost:12345`.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as grpc from 'grpc';
import { Range } from './range';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
export interface IBackoffStrategy {
/**
* getDelay returns the amount of delay of the current backoff.

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { IBackoffStrategy } from './backoff';
/**

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as grpc from 'grpc';
import { Rangable, Range } from './range';
@ -140,7 +143,7 @@ export class SingleRangeBuilder extends RangeBuilder<string | null> {
* Runs the built request and parses the returned key as JSON,
* or returns `null` if it isn't found.
*/
public json(): Promise<object> {
public json(): Promise<unknown> {
return this.string().then(JSON.parse);
}
@ -148,7 +151,7 @@ export class SingleRangeBuilder extends RangeBuilder<string | null> {
* Runs the built request and returns the value of the returned key as a
* string, or `null` if it isn't found.
*/
public string(encoding: string = 'utf8'): Promise<string | null> {
public string(encoding: BufferEncoding = 'utf8'): Promise<string | null> {
return this.exec().then(res =>
res.kvs.length === 0 ? null : res.kvs[0].value.toString(encoding),
);
@ -249,7 +252,7 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
/**
* Keys returns an array of keys matching the query.
*/
public keys(encoding: string = 'utf8'): Promise<string[]> {
public keys(encoding: BufferEncoding = 'utf8'): Promise<string[]> {
this.request.keys_only = true;
return this.exec().then(res => {
return res.kvs.map(kv => kv.key.toString(encoding));
@ -269,7 +272,7 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
/**
* Runs the built request and parses the returned keys as JSON.
*/
public json(): Promise<{ [key: string]: object }> {
public json(): Promise<{ [key: string]: unknown }> {
return this.mapValues(buf => JSON.parse(buf.toString()));
}
@ -277,7 +280,7 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
* Runs the built request and returns the value of the returned key as a
* string, or `null` if it isn't found.
*/
public strings(encoding: string = 'utf8'): Promise<{ [key: string]: string }> {
public strings(encoding: BufferEncoding = 'utf8'): Promise<{ [key: string]: string }> {
return this.mapValues(buf => buf.toString(encoding));
}
@ -560,9 +563,9 @@ export class PutBuilder extends PromiseWrap<RPC.IPutResponse> {
*/
export class ComparatorBuilder {
private request: {
compare: Array<Promise<RPC.ICompare>>;
success: Array<Promise<RPC.IRequestOp>>;
failure: Array<Promise<RPC.IRequestOp>>;
compare: Promise<RPC.ICompare>[];
success: Promise<RPC.IRequestOp>[];
failure: Promise<RPC.IRequestOp>[];
} = { compare: [], success: [], failure: [] };
private callOptions: grpc.CallOptions | undefined;
@ -607,7 +610,7 @@ export class ComparatorBuilder {
* Adds one or more consequent clauses to be executed if the comparison
* is truthy.
*/
public then(...clauses: Array<RPC.IRequestOp | IOperation>): this {
public then(...clauses: (RPC.IRequestOp | IOperation)[]): this {
this.request.success = this.mapOperations(clauses);
return this;
}
@ -616,7 +619,7 @@ export class ComparatorBuilder {
* Adds one or more consequent clauses to be executed if the comparison
* is falsey.
*/
public else(...clauses: Array<RPC.IRequestOp | IOperation>): this {
public else(...clauses: (RPC.IRequestOp | IOperation)[]): this {
this.request.failure = this.mapOperations(clauses);
return this;
}
@ -638,7 +641,7 @@ export class ComparatorBuilder {
/**
* Low-level method to add
*/
public mapOperations(ops: Array<RPC.IRequestOp | IOperation>) {
public mapOperations(ops: (RPC.IRequestOp | IOperation)[]): Promise<RPC.IRequestOp>[] {
return ops.map(op => {
if (typeof (op as IOperation).op === 'function') {
return (op as IOperation).op();

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { loadSync } from '@grpc/proto-loader';
import * as grpc from 'grpc';
@ -44,7 +47,7 @@ function runServiceCall(
metadata: grpc.Metadata,
options: grpc.CallOptions | undefined,
method: string,
payload: object,
payload: unknown,
): Promise<any> {
return new Promise((resolve, reject) => {
(client as any)[method](payload, metadata, options, (err: Error | null, res: any) => {
@ -213,12 +216,12 @@ export class ConnectionPool implements ICallable<Host> {
/**
* @override
*/
public exec(
public exec<T>(
serviceName: keyof typeof Services,
method: string,
payload: object,
payload: unknown,
options?: grpc.CallOptions,
): Promise<any> {
): Promise<T> {
if (this.mockImpl) {
return this.mockImpl.exec(serviceName, method, payload);
}

Просмотреть файл

@ -1,6 +1,7 @@
/**
* Thrown when an internal assertion fails.
*/
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
export class ClientRuntimeError extends Error {
constructor(message: string) {
super(`${message} Please report this error at https://github.com/mixer/etcd3`);

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as grpc from 'grpc';
/**

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { Role, User } from './auth';
import { ConnectionPool } from './connection-pool';
import { Namespace } from './namespace';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { EventEmitter } from 'events';
import * as grpc from 'grpc';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as grpc from 'grpc';
import { ComparatorBuilder, PutBuilder } from './builder';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as grpc from 'grpc';
import * as Builder from './builder';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { ChannelOptions } from './grpcTypes';
import { IBackoffStrategy } from './backoff/backoff';

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { emptyKey, endRangeForPrefix, toBuffer, zeroKey } from './util';
function compare(a: Buffer, b: Buffer) {

Просмотреть файл

@ -1,16 +1,19 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
// AUTOGENERATED CODE, DO NOT EDIT
// tslint:disable
import * as grpc from 'grpc';
import { StatusMessage } from './grpcTypes';
export interface ICallable<T> {
exec(
exec<T>(
service: keyof typeof Services,
method: string,
params: object,
params: unknown,
options?: grpc.CallOptions,
): Promise<any>;
): Promise<T>;
getConnection(
service: keyof typeof Services,
@ -34,7 +37,7 @@ export interface IRequestStream<T> {
export interface IDuplexStream<T, R> extends IRequestStream<T>, IResponseStream<R> {}
export class KVClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* Range gets the keys in the range from the key-value store.
*/
@ -83,7 +86,7 @@ export class KVClient {
}
export class WatchClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* Watch watches for events happening or that have happened. Both input and output
* are streams; the input stream is for creating and canceling watchers and the output
@ -93,6 +96,7 @@ export class WatchClient {
*/
public watch(options?: grpc.CallOptions): Promise<IDuplexStream<IWatchRequest, IWatchResponse>> {
return this.client.getConnection('Watch').then(({ resource, client, metadata }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (<any>client).watch(metadata, options);
stream.on('error', () => this.client.markFailed(resource));
return stream;
@ -101,7 +105,7 @@ export class WatchClient {
}
export class LeaseClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* LeaseGrant creates a lease which expires if the server does not receive a keepAlive
* within a given time to live period. All keys attached to the lease will be expired and
@ -130,6 +134,7 @@ export class LeaseClient {
options?: grpc.CallOptions,
): Promise<IDuplexStream<ILeaseKeepAliveRequest, ILeaseKeepAliveResponse>> {
return this.client.getConnection('Lease').then(({ resource, client, metadata }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (<any>client).leaseKeepAlive(metadata, options);
stream.on('error', () => this.client.markFailed(resource));
return stream;
@ -153,7 +158,7 @@ export class LeaseClient {
}
export class ClusterClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* MemberAdd adds a member into the cluster.
*/
@ -190,7 +195,7 @@ export class ClusterClient {
}
export class MaintenanceClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* Alarm activates, deactivates, and queries alarms regarding cluster health.
*/
@ -228,6 +233,7 @@ export class MaintenanceClient {
*/
public snapshot(options?: grpc.CallOptions): Promise<IResponseStream<ISnapshotResponse>> {
return this.client.getConnection('Maintenance').then(({ resource, client, metadata }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream = (<any>client).snapshot(metadata, options, {});
stream.on('error', () => this.client.markFailed(resource));
return stream;
@ -245,7 +251,7 @@ export class MaintenanceClient {
}
export class AuthClient {
constructor(private client: ICallable<any>) {}
constructor(private client: ICallable<unknown>) {}
/**
* AuthEnable enables authentication.
*/
@ -1167,6 +1173,25 @@ export interface IAuthRoleGrantPermissionResponse {
export interface IAuthRoleRevokePermissionResponse {
header: IResponseHeader;
}
export interface IUser {
name?: Buffer;
password?: Buffer;
roles?: string[];
}
export enum Permission {
Read = 0,
Write = 1,
Readwrite = 2,
}
export interface IPermission {
permType: keyof typeof Permission;
key: Buffer;
range_end: Buffer;
}
export interface IRole {
name?: Buffer;
keyPermission?: IPermission[];
}
export interface IKeyValue {
/**
* key is the first key for the range. If range_end is not given, the request only looks up key.
@ -1205,25 +1230,6 @@ export interface IEvent {
*/
prev_kv: IKeyValue;
}
export interface IUser {
name?: Buffer;
password?: Buffer;
roles?: string[];
}
export enum Permission {
Read = 0,
Write = 1,
Readwrite = 2,
}
export interface IPermission {
permType: keyof typeof Permission;
key: Buffer;
range_end: Buffer;
}
export interface IRole {
name?: Buffer;
keyPermission?: IPermission[];
}
export const Services = {
KV: KVClient,
Watch: WatchClient,

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { IBackoffStrategy } from './backoff/backoff';
import { delay, minBy, sample } from './util';
@ -23,7 +26,7 @@ export class SharedPool<T> {
// tests simpler.
private static deterministicInsertion: false;
private resources: Array<IResourceRecord<T>> = [];
private resources: IResourceRecord<T>[] = [];
private contentionCount = 0;
public constructor(private strategy: IBackoffStrategy) {}

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import BigNumber from 'bignumber.js';
import * as grpc from 'grpc';
@ -91,7 +94,7 @@ function keyValueToResponse(key: string | Buffer, value?: Buffer): RPC.IRangeRes
*/
class ReadSet {
private readonly reads: { [key: string]: Promise<RPC.IRangeResponse> } = Object.create(null);
private readonly completedReads: Array<{ key: Buffer; res: RPC.IRangeResponse }> = [];
private readonly completedReads: { key: Buffer; res: RPC.IRangeResponse }[] = [];
private earliestMod = new BigNumber(Infinity);
/**
@ -329,7 +332,7 @@ class BasicTransaction {
]);
}
protected assertNoOption<T>(req: string, obj: T, keys: Array<keyof T>) {
protected assertNoOption<T>(req: string, obj: T, keys: (keyof T)[]) {
keys.forEach(key => {
if (obj[key] !== undefined) {
throw new Error(`"${key}" is not supported in ${req} requests within STM transactions`);
@ -490,10 +493,7 @@ export class SoftwareTransaction {
const cmp = new Builder.ComparatorBuilder(this.rawKV, NSApplicator.default);
switch (this.options.isolation) {
case Isolation.SerializableSnapshot:
const earliestMod = this.tx.readSet
.earliestModRevision()
.add(1)
.toString();
const earliestMod = this.tx.readSet.earliestModRevision().plus(1).toString();
this.tx.writeSet.addNotChangedChecks(cmp, earliestMod);
this.tx.readSet.addCurrentChecks(cmp);
break;

11
src/test/_setup.ts Normal file
Просмотреть файл

@ -0,0 +1,11 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as chai from 'chai';
import { SharedPool } from '../shared-pool';
chai.use(require('chai-subset')); // tslint:disable-line
chai.use(require('chai-as-promised')); // tslint:disable-line
(SharedPool as any).deterministicInsertion = true;

Просмотреть файл

@ -1,7 +1,10 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { IBackoffStrategy } from '../src/backoff/backoff';
import { ExponentialBackoff } from '../src/backoff/exponential';
import { IBackoffStrategy } from '../backoff/backoff';
import { ExponentialBackoff } from '../backoff/exponential';
describe('backoff strategies', () => {
describe('exponential strategy', () => {

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -1,7 +1,10 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import * as sinon from 'sinon';
import { Etcd3 } from '../src';
import { Etcd3 } from '..';
import { createTestClientAndKeys, tearDownTestClient } from './util';
describe('client', () => {
@ -12,7 +15,7 @@ describe('client', () => {
it('allows mocking', async () => {
const mock = client.mock({
exec: sinon.stub(),
exec: sinon.stub() as any,
getConnection: sinon.stub(),
});

Просмотреть файл

@ -1,7 +1,10 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { GRPCConnectFailedError, IOptions, KVClient } from '../src';
import { ConnectionPool } from '../src/connection-pool';
import { GRPCConnectFailedError, IOptions, KVClient } from '..';
import { ConnectionPool } from '../connection-pool';
import { getHost, getOptions } from './util';
function getOptionsWithBadHost(options: Partial<IOptions> = {}): IOptions {

Просмотреть файл

@ -1,4 +1,4 @@
from quay.io/coreos/etcd:v3.2.13
FROM quay.io/coreos/etcd:v3.2.13
COPY test/certs/certs/etcd0.localhost.crt test/certs/private/etcd0.localhost.key /root/

Просмотреть файл

@ -0,0 +1,9 @@
version: '3.6'
services:
etcd3_2:
build:
context: ../../../
dockerfile: test/containers/3.2/Dockerfile
ports:
- 2379:2379
- 2380:2380

Просмотреть файл

@ -1,6 +1,9 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Etcd3 } from '../src';
import { Etcd3 } from '..';
import { createTestClientAndKeys, tearDownTestClient } from './util';
describe('crud', () => {
@ -27,12 +30,7 @@ describe('crud', () => {
});
it('queries prefixes', async () => {
expect(
await client
.getAll()
.prefix('fo')
.strings(),
).to.deep.equal({
expect(await client.getAll().prefix('fo').strings()).to.deep.equal({
foo1: 'bar1',
foo2: 'bar2',
foo3: '{"value":"bar3"}',
@ -55,21 +53,11 @@ describe('crud', () => {
it('sorts', async () => {
expect(
await client
.getAll()
.prefix('foo')
.sort('Key', 'Ascend')
.limit(2)
.keys(),
await client.getAll().prefix('foo').sort('Key', 'Ascend').limit(2).keys(),
).to.deep.equal(['foo1', 'foo2']);
expect(
await client
.getAll()
.prefix('foo')
.sort('Key', 'Descend')
.limit(2)
.keys(),
await client.getAll().prefix('foo').sort('Key', 'Descend').limit(2).keys(),
).to.deep.equal(['foo3', 'foo2']);
});
});
@ -86,12 +74,7 @@ describe('crud', () => {
});
it('gets previous', async () => {
expect(
await client
.delete()
.key('foo1')
.getPrevious(),
).to.containSubset([
expect(await client.delete().key('foo1').getPrevious()).to.containSubset([
{
key: new Buffer('foo1'),
value: new Buffer('bar1'),
@ -113,12 +96,7 @@ describe('crud', () => {
});
it('includes previous values', async () => {
expect(
await client
.put('foo1')
.value('updated')
.getPrevious(),
).to.containSubset({
expect(await client.put('foo1').value('updated').getPrevious()).to.containSubset({
key: new Buffer('foo1'),
value: new Buffer('bar1'),
});

Просмотреть файл

@ -1,8 +1,11 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import * as sinon from 'sinon';
import { Etcd3, EtcdLeaseInvalidError, GRPCConnectFailedError, Lease } from '../src';
import { onceEvent } from '../src/util';
import { Etcd3, EtcdLeaseInvalidError, GRPCConnectFailedError, Lease } from '..';
import { onceEvent } from '../util';
import { createTestClientAndKeys, getOptions, proxy, tearDownTestClient } from './util';
describe('lease()', () => {

Просмотреть файл

@ -1,6 +1,9 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Etcd3, EtcdLockFailedError } from '../src';
import { Etcd3, EtcdLockFailedError } from '..';
import { createTestClientAndKeys, tearDownTestClient } from './util';
describe('lock()', () => {
@ -37,10 +40,7 @@ describe('lock()', () => {
});
it('allows setting lock TTL before acquiring', async () => {
const lock = await client
.lock('resource')
.ttl(10)
.acquire();
const lock = await client.lock('resource').ttl(10).acquire();
await lock.release();
});

Просмотреть файл

@ -1,6 +1,9 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Etcd3, Namespace } from '../src';
import { Etcd3, Namespace } from '..';
import { createTestClientAndKeys, tearDownTestClient } from './util';
describe('namespacing', () => {
@ -54,10 +57,7 @@ describe('namespacing', () => {
it('runs a simple if', async () => {
await ns.put('foo1').value('potatoes');
await ns
.if('foo1', 'Value', '==', 'potatoes')
.then(ns.put('foo1').value('tomatoes'))
.commit();
await ns.if('foo1', 'Value', '==', 'potatoes').then(ns.put('foo1').value('tomatoes')).commit();
await assertEqualInNamespace('foo1', 'tomatoes');
});

Просмотреть файл

@ -1,5 +1,8 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Range } from '../src/range';
import { Range } from '../range';
describe('Range', () => {
describe('prefix', () => {

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import * as grpc from 'grpc';
@ -11,7 +14,7 @@ import {
EtcdUserExistsError,
EtcdUserNotFoundError,
Role,
} from '../src';
} from '..';
import { createTestClientAndKeys, expectReject, getOptions, tearDownTestClient } from './util';
function wipeAll(things: Promise<Array<{ delete(): any }>>) {
@ -186,13 +189,7 @@ describe('roles and auth', () => {
}),
);
await expectReject(
authedClient
.put('wut')
.value('bar')
.exec(),
EtcdPermissionDeniedError,
);
await expectReject(authedClient.put('wut').value('bar').exec(), EtcdPermissionDeniedError);
authedClient.close();
});
@ -208,10 +205,7 @@ describe('roles and auth', () => {
);
await expectReject(
authedClient
.put('foo')
.value('bar')
.exec(),
authedClient.put('foo').value('bar').exec(),
EtcdAuthenticationFailedError,
);

Просмотреть файл

@ -1,8 +1,11 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import * as sinon from 'sinon';
import { ExponentialBackoff } from '../src/backoff/exponential';
import { SharedPool } from '../src/shared-pool';
import { ExponentialBackoff } from '../backoff/exponential';
import { SharedPool } from '../shared-pool';
describe('shared pool', () => {
let pool: SharedPool<number>;
@ -24,7 +27,7 @@ describe('shared pool', () => {
afterEach(() => clock.restore());
async function getAll(count: number = 3): Promise<number[]> {
async function getAll(count = 3): Promise<number[]> {
const output: number[] = [];
for (let i = 0; i < count; i += 1) {
clock.tick(1);

Просмотреть файл

@ -1,7 +1,10 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Isolation, SoftwareTransaction } from '../src/stm';
import { Isolation, SoftwareTransaction } from '../stm';
import { Etcd3, Namespace, STMConflictError } from '../src';
import { Etcd3, Namespace, STMConflictError } from '..';
import { createTestClient, createTestKeys, tearDownTestClient } from './util';
describe('stm()', () => {
@ -34,7 +37,7 @@ describe('stm()', () => {
const expectRetry = async (
isolation: Isolation,
fn: (tx: SoftwareTransaction, tries: number) => Promise<any>,
retries: number = 2,
retries = 2,
) => {
let tries = 0;
await ns.stm({ isolation }).transact(async tx => fn(tx, ++tries));

Просмотреть файл

@ -1,6 +1,9 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import { Etcd3 } from '../src';
import { Etcd3 } from '..';
import { createTestClientAndKeys, tearDownTestClient } from './util';
describe('transactions', () => {
@ -10,10 +13,7 @@ describe('transactions', () => {
afterEach(async () => await tearDownTestClient(client));
it('runs a simple if', async () => {
await client
.if('foo1', 'Value', '==', 'bar1')
.then(client.put('foo1').value('bar2'))
.commit();
await client.if('foo1', 'Value', '==', 'bar1').then(client.put('foo1').value('bar2')).commit();
expect(await client.get('foo1').string()).to.equal('bar2');
});

Просмотреть файл

Просмотреть файл

@ -1,12 +1,18 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { expect } from 'chai';
import * as fs from 'fs';
import * as tls from 'tls';
import { Etcd3, IOptions, Namespace } from '../src';
import { Etcd3, IOptions, Namespace } from '..';
import { AddressInfo } from 'net';
import { resolve } from 'path';
const rootCertificate = fs.readFileSync(`${__dirname}/certs/certs/ca.crt`);
const tlsCert = fs.readFileSync(`${__dirname}/certs/certs/etcd0.localhost.crt`);
const tlsKey = fs.readFileSync(`${__dirname}/certs/private/etcd0.localhost.key`);
const rootPath = resolve(__dirname, '..', '..');
const rootCertificate = fs.readFileSync(`${rootPath}/src/test/certs/certs/ca.crt`);
const tlsCert = fs.readFileSync(`${rootPath}/src/test/certs/certs/etcd0.localhost.crt`);
const tlsKey = fs.readFileSync(`${rootPath}/src/test/certs/private/etcd0.localhost.key`);
const etcdSourceAddress = process.env.ETCD_ADDR || '127.0.0.1:2379';
const [etcdSourceHost, etcdSourcePort] = etcdSourceAddress.split(':');
@ -34,7 +40,7 @@ export class Proxy {
this.server.listen(0, '127.0.0.1');
this.server.on('listening', () => {
const addr = this.server.address();
const addr = this.server.address() as AddressInfo;
this.host = addr.address;
this.port = addr.port;
this.isActive = true;
@ -82,7 +88,7 @@ export class Proxy {
let serverConnected = false;
const serverBuffer: Buffer[] = [];
const serverCnx = tls.connect(
etcdSourcePort,
Number(etcdSourcePort),
etcdSourceHost,
{
secureContext: tls.createSecureContext({ ca: rootCertificate }),

Просмотреть файл

@ -1,8 +1,11 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import BigNumber from 'bignumber.js';
import { expect } from 'chai';
import { Etcd3, IKeyValue, IWatchResponse, Watcher } from '../src';
import { onceEvent } from '../src/util';
import { Etcd3, IKeyValue, IWatchResponse, Watcher } from '..';
import { onceEvent } from '../util';
import { createTestClientAndKeys, getOptions, proxy, tearDownTestClient } from './util';
describe('watch()', () => {
@ -57,10 +60,7 @@ describe('watch()', () => {
await proxy.activate();
const proxiedClient = await createTestClientAndKeys();
const watcher = await proxiedClient
.watch()
.key('foo1')
.create();
const watcher = await proxiedClient.watch().key('foo1').create();
proxy.pause();
await onceEvent(watcher, 'disconnected');
@ -78,16 +78,13 @@ describe('watch()', () => {
await proxy.activate();
const proxiedClient = await createTestClientAndKeys();
const watcher = await proxiedClient
.watch()
.key('foo1')
.create();
const watcher = await proxiedClient.watch().key('foo1').create();
await Promise.all([
client.put('foo1').value('update 1'),
onceEvent(watcher, 'data').then((res: IWatchResponse) => {
expect(watcher.request.start_revision).to.equal(
new BigNumber(res.header.revision).add(1).toString(),
new BigNumber(res.header.revision).plus(1).toString(),
);
}),
]);
@ -108,10 +105,7 @@ describe('watch()', () => {
await proxy.activate();
const proxiedClient = await createTestClientAndKeys();
const watcher = await proxiedClient
.watch()
.key('foo1')
.create();
const watcher = await proxiedClient.watch().key('foo1').create();
proxy.pause();
await onceEvent(watcher, 'disconnected');
const actualRevision = Number(watcher.request.start_revision);
@ -124,23 +118,14 @@ describe('watch()', () => {
describe('subscription', () => {
it('subscribes before the connection is established', async () => {
const watcher = await client
.watch()
.key('foo1')
.create();
const watcher = await client.watch().key('foo1').create();
await expectWatching(watcher, 'foo1');
expect(getWatchers()).to.deep.equal([watcher]);
});
it('subscribes while the connection is still being established', async () => {
const watcher1 = client
.watch()
.key('foo1')
.create();
const watcher2 = client
.watch()
.key('bar')
.create();
const watcher1 = client.watch().key('foo1').create();
const watcher2 = client.watch().key('bar').create();
const watchers = await Promise.all([
watcher1.then(w => expectWatching(w, 'foo1')),
@ -151,14 +136,8 @@ describe('watch()', () => {
});
it('subscribes in series', async () => {
const watcher1 = client
.watch()
.key('foo1')
.watcher();
const watcher2 = client
.watch()
.key('bar')
.watcher();
const watcher1 = client.watch().key('foo1').watcher();
const watcher2 = client.watch().key('bar').watcher();
const events: string[] = [];
watcher1.on('connecting', () => events.push('connecting1'));
@ -172,31 +151,19 @@ describe('watch()', () => {
});
it('subscribes after the connection is fully established', async () => {
const watcher1 = await client
.watch()
.key('foo1')
.create();
const watcher1 = await client.watch().key('foo1').create();
await expectWatching(watcher1, 'foo1');
const watcher2 = await client
.watch()
.key('bar')
.create();
const watcher2 = await client.watch().key('bar').create();
await expectWatching(watcher2, 'bar');
expect(getWatchers()).to.deep.equal([watcher1, watcher2]);
});
it('allows successive resubscription (issue #51)', async () => {
const watcher1 = await client
.watch()
.key('foo1')
.create();
const watcher1 = await client.watch().key('foo1').create();
await expectWatching(watcher1, 'foo1');
await watcher1.cancel();
const watcher2 = await client
.watch()
.key('foo1')
.create();
const watcher2 = await client.watch().key('foo1').create();
await expectWatching(watcher2, 'foo1');
await watcher2.cancel();
});
@ -204,10 +171,7 @@ describe('watch()', () => {
describe('unsubscribing', () => {
it('unsubscribes while the connection is established', async () => {
const watcher = await client
.watch()
.key('foo1')
.create();
const watcher = await client.watch().key('foo1').create();
await watcher.cancel();
await expectNotWatching(watcher, 'foo1');
expect(getWatchers()).to.deep.equal([]);
@ -217,10 +181,7 @@ describe('watch()', () => {
await proxy.activate();
const proxiedClient = await createTestClientAndKeys();
const watcher = await proxiedClient
.watch()
.key('foo1')
.create();
const watcher = await proxiedClient.watch().key('foo1').create();
proxy.pause();
await watcher.cancel();

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import { EventEmitter } from 'events';
import { ClientRuntimeError } from './errors';
@ -155,7 +158,7 @@ export function forOwn<T>(
obj: T,
iterator: <K extends keyof T>(value: T[K], key: K) => void,
): void {
const keys = Object.keys(obj) as Array<keyof T>;
const keys = Object.keys(obj) as (keyof T)[];
for (const key of keys) {
iterator(obj[key], key);
}
@ -167,7 +170,7 @@ export function forOwn<T>(
*/
export function onceEvent(emitter: EventEmitter, ...events: string[]): Promise<any> {
return new Promise((resolve, reject) => {
const teardown: Array<() => void> = [];
const teardown: (() => void)[] = [];
const handler = (data: any, event: string) => {
teardown.forEach(t => t());

Просмотреть файл

@ -1,3 +1,6 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import BigNumber from 'bignumber.js';
import { EventEmitter } from 'events';
@ -381,7 +384,7 @@ export class WatchBuilder {
/**
* ignore omits certain operation kinds from the watch stream.
*/
public ignore(...operations: Array<keyof typeof operationNames>): this {
public ignore(...operations: (keyof typeof operationNames)[]): this {
this.request.filters = operations.map(op => operationNames[op]);
return this;
}
@ -531,6 +534,6 @@ export class Watcher extends EventEmitter {
* Updates the current revision based on the revision in the watch header.
*/
private updateRevision(req: RPC.IWatchResponse) {
this.request.start_revision = new BigNumber(req.header.revision).add(1).toString();
this.request.start_revision = new BigNumber(req.header.revision).plus(1).toString();
}
}

Просмотреть файл

@ -1,8 +0,0 @@
import * as chai from 'chai';
import { SharedPool } from '../src/shared-pool';
chai.use(require('chai-subset')); // tslint:disable-line
chai.use(require('chai-as-promised')); // tslint:disable-line
(SharedPool as any).deterministicInsertion = true;

Просмотреть файл

@ -1,8 +0,0 @@
--watch-extensions ts
--require source-map-support/register
--require ts-node/register
--require test/_setup.ts
--timeout 20000
--recursive
--exit
test/**/*.test.ts

Просмотреть файл

@ -1,5 +1,6 @@
{
"compilerOptions": {
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"noImplicitReturns": true,
@ -9,15 +10,9 @@
"strictNullChecks": true,
"experimentalDecorators": true,
"module": "commonjs",
"target": "es6",
"target": "ES2018",
"outDir": "lib",
"lib": [
"es5",
"es6"
],
"typeRoots": [
"node_modules/@types"
],
"lib": ["ES2018"],
"types": [
"chai-as-promised",
"chai-subset",
@ -25,14 +20,6 @@
"node"
],
"baseUrl": ".",
"paths": {
"*": [
"src/types/*"
]
}
},
"exclude": [
"node_modules",
"lib"
]
"include": ["src"],
}

Просмотреть файл

@ -1,10 +0,0 @@
{
"$schema": "http://json.schemastore.org/tslint",
"extends": ["tslint:recommended", "tslint-config-prettier"],
"rules": {
"object-literal-sort-keys": false,
"unified-signatures": false,
"max-classes-per-file": false,
"variable-name": false
}
}