Skip to content

Commit 1577418

Browse files
committed
Added Database#close
1 parent a00eca3 commit 1577418

File tree

6 files changed

+137
-81
lines changed

6 files changed

+137
-81
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1313

1414
### Added
1515

16+
- Added `database.close` method
17+
1618
- Added `opts` parameter to `EdgeCollection#save`
1719

1820
## [6.3.0] - 2018-06-20

docs/Drivers/JS/Reference/Database/README.md

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ If _config_ is a string, it will be interpreted as _config.url_.
1010

1111
**Arguments**
1212

13-
* **config**: `Object` (optional)
13+
- **config**: `Object` (optional)
1414

1515
An object with the following properties:
1616

17-
* **url**: `string | Array<string>` (Default: `http://localhost:8529`)
17+
- **url**: `string | Array<string>` (Default: `http://localhost:8529`)
1818

1919
Base URL of the ArangoDB server or list of server URLs.
2020

@@ -36,14 +36,14 @@ If _config_ is a string, it will be interpreted as _config.url_.
3636
}
3737
```
3838

39-
* **isAbsolute**: `boolean` (Default: `false`)
39+
- **isAbsolute**: `boolean` (Default: `false`)
4040

4141
If this option is explicitly set to `true`, the _url_ will be treated as the
4242
absolute database path. This is an escape hatch to allow using arangojs with
4343
database APIs exposed with a reverse proxy and makes it impossible to switch
4444
databases with _useDatabase_ or using _acquireHostList_.
4545

46-
* **arangoVersion**: `number` (Default: `30000`)
46+
- **arangoVersion**: `number` (Default: `30000`)
4747

4848
Value of the `x-arango-version` header. This should match the lowest
4949
version of ArangoDB you expect to be using. The format is defined as
@@ -57,14 +57,14 @@ If _config_ is a string, it will be interpreted as _config.url_.
5757
not available on every major version of ArangoDB as indicated in their
5858
descriptions below (e.g. _collection.first_, _collection.bulkUpdate_).
5959

60-
* **headers**: `Object` (optional)
60+
- **headers**: `Object` (optional)
6161

6262
An object with additional headers to send with every request.
6363

6464
Header names should always be lowercase. If an `"authorization"` header is
6565
provided, it will be overridden when using _useBasicAuth_ or _useBearerAuth_.
6666

67-
* **agent**: `Agent` (optional)
67+
- **agent**: `Agent` (optional)
6868

6969
An http Agent instance to use for connections.
7070

@@ -74,7 +74,7 @@ If _config_ is a string, it will be interpreted as _config.url_.
7474

7575
This option has no effect when using the browser version of arangojs.
7676

77-
* **agentOptions**: `Object` (Default: see below)
77+
- **agentOptions**: `Object` (Default: see below)
7878

7979
An object with options for the agent. This will be ignored if _agent_ is
8080
also provided.
@@ -91,15 +91,41 @@ If _config_ is a string, it will be interpreted as _config.url_.
9191
additional options to the underlying calls of the
9292
[`xhr`](https://www.npmjs.com/package/xhr) module.
9393
94-
* **loadBalancingStrategy**: `string` (Default: `"NONE"`)
94+
- **loadBalancingStrategy**: `string` (Default: `"NONE"`)
9595
96-
Determines the behaviour when multiple URLs are provided:
96+
Determines the behavior when multiple URLs are provided:
9797
98-
* `NONE`: No load balancing. All requests will be handled by the first
98+
- `NONE`: No load balancing. All requests will be handled by the first
9999
URL in the list until a network error is encountered. On network error,
100100
arangojs will advance to using the next URL in the list.
101101
102-
* `ONE_RANDOM`: Randomly picks one URL from the list initially, then
102+
- `ONE_RANDOM`: Randomly picks one URL from the list initially, then
103103
behaves like `NONE`.
104104
105-
* `ROUND_ROBIN`: Every sequential request uses the next URL in the list.
105+
- `ROUND_ROBIN`: Every sequential request uses the next URL in the list.
106+
107+
## database.close
108+
109+
`database.close(): void`
110+
111+
Closes all active connections of the database instance.
112+
Can be used to clean up idling connections during longer periods of inactivity.
113+
114+
**Note**: This method currently has no effect in the browser version of arangojs.
115+
116+
**Examples**
117+
118+
```js
119+
const db = new Database();
120+
const sessions = db.collection("sessions");
121+
// Clean up expired sessions once per hour
122+
setInterval(async () => {
123+
await db.query(aql`
124+
FOR session IN ${sessions}
125+
FILTER session.expires < DATE_NOW()
126+
REMOVE session IN ${sessions}
127+
`);
128+
// Make sure to close the connections because they're no longer used
129+
db.close();
130+
}, 1000 * 60 * 60);
131+
```

src/connection.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import { stringify as querystringify } from "querystring";
12
import { ArangoError, HttpError } from "./error";
3+
import { byteLength } from "./util/bytelength";
24
import {
35
ArangojsResponse,
4-
RequestFunction,
56
createRequest,
6-
isBrowser
7+
isBrowser,
8+
RequestFunction
79
} from "./util/request";
810

9-
import { byteLength } from "./util/bytelength";
10-
import { stringify as querystringify } from "querystring";
11-
1211
const LinkedList = require("linkedlist/lib/linkedlist") as typeof Array;
1312

1413
const MIME_JSON = /\/(json|javascript)(\W|$)/;
@@ -58,14 +57,14 @@ export type Config =
5857
isAbsolute: boolean;
5958
arangoVersion: number;
6059
loadBalancingStrategy: LoadBalancingStrategy;
61-
agent: Function;
60+
agent: any;
6261
agentOptions: { [key: string]: any };
6362
headers: { [key: string]: string };
6463
}>;
6564

6665
export class Connection {
6766
private _activeTasks: number = 0;
68-
private _agent?: Function;
67+
private _agent?: any;
6968
private _agentOptions: { [key: string]: any };
7069
private _arangoVersion: number = 30000;
7170
private _databaseName: string | false = "_system";
@@ -105,7 +104,9 @@ export class Connection {
105104
this._useFailOver = this._loadBalancingStrategy !== "ROUND_ROBIN";
106105

107106
const urls = config.url
108-
? Array.isArray(config.url) ? config.url : [config.url]
107+
? Array.isArray(config.url)
108+
? config.url
109+
: [config.url]
109110
: ["http://localhost:8529"];
110111
this.addToHostList(urls);
111112

@@ -221,6 +222,12 @@ export class Connection {
221222
this._headers[key] = value;
222223
}
223224

225+
close() {
226+
for (const host of this._hosts) {
227+
if (host.close) host.close();
228+
}
229+
}
230+
224231
request<T = ArangojsResponse>(
225232
{
226233
host,

src/database.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ export class Database {
7070
this._connection.addToHostList(urls);
7171
}
7272

73+
close () {
74+
this._connection.close();
75+
}
76+
7377
// Database manipulation
7478

7579
useDatabase(databaseName: string) {

src/test/00-basics.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1+
import { expect } from "chai";
12
import * as http from "http";
23
import * as https from "https";
3-
44
import arangojs, { Database } from "../arangojs";
5-
65
import { Connection } from "../connection";
7-
import { expect } from "chai";
86

97
describe("Creating a Database", () => {
108
describe("using the factory", () => {
@@ -132,22 +130,22 @@ describe("Configuring the driver", () => {
132130
(https as any).request = _httpsRequest;
133131
});
134132
it("passes the agent to the request function", () => {
135-
let agent = function() {};
133+
let agent = Symbol("agent");
136134
let conn;
137135
conn = new Connection({ agent }); // default: http
138136
conn.request({ headers: {} }, () => {});
139137
expect(options).to.have.property("agent", agent);
140-
agent = function() {};
138+
agent = Symbol("agent");
141139
conn = new Connection({ agent, url: "https://localhost:8529" });
142140
conn.request({ headers: {} }, () => {});
143141
expect(options).to.have.property("agent", agent);
144-
agent = function() {};
142+
agent = Symbol("agent");
145143
conn = new Connection({ agent, url: "http://localhost:8529" });
146144
conn.request({ headers: {} }, () => {});
147145
expect(options).to.have.property("agent", agent);
148146
});
149147
it("uses the request function for the protocol", () => {
150-
const agent = function() {};
148+
const agent = Symbol("agent");
151149
let conn;
152150
conn = new Connection({ agent }); // default: http
153151
conn.request({ headers: {} }, () => {});
@@ -159,5 +157,17 @@ describe("Configuring the driver", () => {
159157
conn.request({ headers: {} }, () => {});
160158
expect(protocol).to.equal("http");
161159
});
160+
it("calls Agent#destroy when the connection is closed", () => {
161+
const agent = {
162+
_destroyed: false,
163+
destroy() {
164+
this._destroyed = true;
165+
}
166+
};
167+
const conn = new Connection({ agent });
168+
expect(agent._destroyed).to.equal(false);
169+
conn.close();
170+
expect(agent._destroyed).to.equal(true);
171+
});
162172
});
163173
});

src/util/request.node.ts

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
request as httpRequest
66
} from "http";
77
import { Agent as HttpsAgent, request as httpsRequest } from "https";
8-
import { Url, parse as parseUrl } from "url";
8+
import { parse as parseUrl, Url } from "url";
99
import { joinPath } from "./joinPath";
1010
import { Errback } from "./types";
1111

@@ -18,18 +18,18 @@ export type ArangojsError = Error & {
1818
request: ClientRequest;
1919
};
2020

21-
export type RequestOptions = {
21+
export interface RequestOptions {
2222
method: string;
2323
url: Url;
2424
headers: { [key: string]: string };
2525
body: any;
2626
expectBinary: boolean;
27-
};
27+
}
2828

29-
export type RequestFunction = (
30-
opts: RequestOptions,
31-
cb: Errback<ArangojsResponse>
32-
) => void;
29+
export interface RequestFunction {
30+
(opts: RequestOptions, cb: Errback<ArangojsResponse>): void;
31+
close?: () => void;
32+
}
3333

3434
export const isBrowser = false;
3535

@@ -44,54 +44,61 @@ export function createRequest(
4444
if (isTls) agent = new HttpsAgent(agentOptions);
4545
else agent = new HttpAgent(agentOptions);
4646
}
47-
return function request(
48-
{ method, url, headers, body }: RequestOptions,
49-
callback: Errback<ArangojsResponse>
50-
) {
51-
let path = baseUrlParts.pathname
52-
? url.pathname
53-
? joinPath(baseUrlParts.pathname, url.pathname)
54-
: baseUrlParts.pathname
55-
: url.pathname;
56-
const search = url.search
57-
? baseUrlParts.search
58-
? `${baseUrlParts.search}&${url.search.slice(1)}`
59-
: url.search
60-
: baseUrlParts.search;
61-
if (search) path += search;
62-
const options: any = { path, method, headers, agent };
63-
options.hostname = baseUrlParts.hostname;
64-
options.port = baseUrlParts.port;
65-
options.auth = baseUrlParts.auth;
66-
let called = false;
67-
try {
68-
const req = (isTls ? httpsRequest : httpRequest)(
69-
options,
70-
(res: IncomingMessage) => {
71-
const data: Buffer[] = [];
72-
res.on("data", chunk => data.push(chunk as Buffer));
73-
res.on("end", () => {
74-
const result = res as ArangojsResponse;
75-
result.body = Buffer.concat(data);
76-
if (called) return;
77-
called = true;
78-
callback(null, result);
79-
});
80-
}
81-
);
82-
req.on("error", err => {
83-
const error = err as ArangojsError;
84-
error.request = req;
47+
return Object.assign(
48+
function request(
49+
{ method, url, headers, body }: RequestOptions,
50+
callback: Errback<ArangojsResponse>
51+
) {
52+
let path = baseUrlParts.pathname
53+
? url.pathname
54+
? joinPath(baseUrlParts.pathname, url.pathname)
55+
: baseUrlParts.pathname
56+
: url.pathname;
57+
const search = url.search
58+
? baseUrlParts.search
59+
? `${baseUrlParts.search}&${url.search.slice(1)}`
60+
: url.search
61+
: baseUrlParts.search;
62+
if (search) path += search;
63+
const options: any = { path, method, headers, agent };
64+
options.hostname = baseUrlParts.hostname;
65+
options.port = baseUrlParts.port;
66+
options.auth = baseUrlParts.auth;
67+
let called = false;
68+
try {
69+
const req = (isTls ? httpsRequest : httpRequest)(
70+
options,
71+
(res: IncomingMessage) => {
72+
const data: Buffer[] = [];
73+
res.on("data", chunk => data.push(chunk as Buffer));
74+
res.on("end", () => {
75+
const result = res as ArangojsResponse;
76+
result.body = Buffer.concat(data);
77+
if (called) return;
78+
called = true;
79+
callback(null, result);
80+
});
81+
}
82+
);
83+
req.on("error", err => {
84+
const error = err as ArangojsError;
85+
error.request = req;
86+
if (called) return;
87+
called = true;
88+
callback(err);
89+
});
90+
if (body) req.write(body);
91+
req.end();
92+
} catch (e) {
8593
if (called) return;
8694
called = true;
87-
callback(err);
88-
});
89-
if (body) req.write(body);
90-
req.end();
91-
} catch (e) {
92-
if (called) return;
93-
called = true;
94-
callback(e);
95+
callback(e);
96+
}
97+
},
98+
{
99+
close() {
100+
agent.destroy();
101+
}
95102
}
96-
};
103+
);
97104
}

0 commit comments

Comments
 (0)