@forge/cache 0.8.0-next.0 → 0.8.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/__test__/tunnel.test.d.ts +2 -0
- package/out/__test__/tunnel.test.d.ts.map +1 -0
- package/out/__test__/tunnel.test.js +168 -0
- package/out/cache.d.ts +4 -2
- package/out/cache.d.ts.map +1 -1
- package/out/cache.js +5 -0
- package/out/interfaces/cache.d.ts +28 -0
- package/out/interfaces/cache.d.ts.map +1 -0
- package/out/interfaces/cache.js +2 -0
- package/out/tunnel.d.ts +33 -0
- package/out/tunnel.d.ts.map +1 -0
- package/out/tunnel.js +118 -0
- package/out/utils/__test__/validator.test.d.ts +2 -0
- package/out/utils/__test__/validator.test.d.ts.map +1 -0
- package/out/utils/__test__/validator.test.js +38 -0
- package/out/utils/validator.d.ts +11 -0
- package/out/utils/validator.d.ts.map +1 -0
- package/out/utils/validator.js +71 -0
- package/package.json +4 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.test.d.ts","sourceRoot":"","sources":["../../src/__test__/tunnel.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tunnel_1 = require("../tunnel");
|
|
4
|
+
const validator_1 = require("../utils/validator");
|
|
5
|
+
const parseSuccessResponse = (response) => {
|
|
6
|
+
return { response };
|
|
7
|
+
};
|
|
8
|
+
describe('Forge Cache Tunnel', () => {
|
|
9
|
+
let cache;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
const __localStore = new Map();
|
|
12
|
+
cache = new tunnel_1.TunnelCache(__localStore);
|
|
13
|
+
});
|
|
14
|
+
test('set method', async () => {
|
|
15
|
+
const response = await cache.set('key', 'value');
|
|
16
|
+
expect(response).toStrictEqual(parseSuccessResponse('OK'));
|
|
17
|
+
});
|
|
18
|
+
test('get method', async () => {
|
|
19
|
+
await cache.set('key', 'value');
|
|
20
|
+
const response = await cache.get('key');
|
|
21
|
+
expect(response).toStrictEqual(parseSuccessResponse('value'));
|
|
22
|
+
});
|
|
23
|
+
test('get method throws error for list value', async () => {
|
|
24
|
+
cache.__localStore.set('key', ['value1', 'value2']);
|
|
25
|
+
try {
|
|
26
|
+
await cache.get('key');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
test('setIfNotExists method', async () => {
|
|
33
|
+
const response = await cache.setIfNotExists('key', 'value');
|
|
34
|
+
expect(response).toStrictEqual(parseSuccessResponse('OK'));
|
|
35
|
+
const newResponse = await cache.setIfNotExists('key', 'new value');
|
|
36
|
+
expect(newResponse).toStrictEqual(parseSuccessResponse(null));
|
|
37
|
+
});
|
|
38
|
+
test('getAndSet method', async () => {
|
|
39
|
+
await cache.set('key', 'value');
|
|
40
|
+
const response = await cache.getAndSet('key', 'new value');
|
|
41
|
+
expect(response).toStrictEqual(parseSuccessResponse('value'));
|
|
42
|
+
const newResponse = await cache.get('key');
|
|
43
|
+
expect(newResponse).toStrictEqual(parseSuccessResponse('new value'));
|
|
44
|
+
});
|
|
45
|
+
test('delete method', async () => {
|
|
46
|
+
await cache.set('key', 'value');
|
|
47
|
+
const response = await cache.delete('key');
|
|
48
|
+
expect(response).toStrictEqual(parseSuccessResponse(1));
|
|
49
|
+
const newResponse = await cache.get('key');
|
|
50
|
+
expect(newResponse).toStrictEqual(parseSuccessResponse(null));
|
|
51
|
+
});
|
|
52
|
+
test('incrementAndGet method', async () => {
|
|
53
|
+
await cache.set('key', '5');
|
|
54
|
+
const response = await cache.incrementAndGet('key');
|
|
55
|
+
expect(response).toStrictEqual(parseSuccessResponse(6));
|
|
56
|
+
});
|
|
57
|
+
test('leftPush method', async () => {
|
|
58
|
+
cache.__localStore.set('key', ['value1', 'value2']);
|
|
59
|
+
const response = await cache.leftPush('key', 'value3');
|
|
60
|
+
const result = ['value3', 'value1', 'value2'].length;
|
|
61
|
+
expect(response).toStrictEqual(parseSuccessResponse(result));
|
|
62
|
+
});
|
|
63
|
+
test('rightPop method', async () => {
|
|
64
|
+
await cache.leftPush('key', 'value1');
|
|
65
|
+
await cache.leftPush('key', 'value2');
|
|
66
|
+
const response = await cache.rightPop('key');
|
|
67
|
+
expect(response).toStrictEqual(parseSuccessResponse('value1'));
|
|
68
|
+
});
|
|
69
|
+
test('listLength method', async () => {
|
|
70
|
+
await cache.leftPush('key', 'value1');
|
|
71
|
+
await cache.leftPush('key', 'value2');
|
|
72
|
+
const response = await cache.listLength('key');
|
|
73
|
+
expect(response).toStrictEqual(parseSuccessResponse(2));
|
|
74
|
+
});
|
|
75
|
+
test('decrementAndGet method', async () => {
|
|
76
|
+
await cache.set('key', '5');
|
|
77
|
+
const response = await cache.decrementAndGet('key');
|
|
78
|
+
expect(response).toStrictEqual(parseSuccessResponse(4));
|
|
79
|
+
});
|
|
80
|
+
test('incrementAndGet method throws error when key holds a list value', async () => {
|
|
81
|
+
cache.__localStore.set('key', ['value1', 'value2']);
|
|
82
|
+
try {
|
|
83
|
+
await cache.incrementAndGet('key');
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
test('leftPush method throws error when key holds a non-list value', async () => {
|
|
90
|
+
await cache.set('key', 'value');
|
|
91
|
+
try {
|
|
92
|
+
await cache.leftPush('key', 'newValue');
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
test('rightPop method throws error when key holds a non-list value', async () => {
|
|
99
|
+
await cache.set('key', 'value');
|
|
100
|
+
try {
|
|
101
|
+
await cache.rightPop('key');
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
test('listLength method throws error when key holds a non-list value', async () => {
|
|
108
|
+
await cache.set('key', 'value');
|
|
109
|
+
try {
|
|
110
|
+
await cache.listLength('key');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
test('decrementAndGet method throws error when key holds a list value', async () => {
|
|
117
|
+
cache.__localStore.set('key', ['value1', 'value2']);
|
|
118
|
+
try {
|
|
119
|
+
await cache.decrementAndGet('key');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
expect(error).toEqual(new validator_1.ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.'));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
describe('scan method', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
const __localStore = new Map();
|
|
128
|
+
cache = new tunnel_1.TunnelCache(__localStore);
|
|
129
|
+
});
|
|
130
|
+
test('scan method returns empty array when no matching keys', async () => {
|
|
131
|
+
cache.__localStore.set('key1', 'value1');
|
|
132
|
+
cache.__localStore.set('key2', 'value2');
|
|
133
|
+
cache.__localStore.set('key3', 'value3');
|
|
134
|
+
cache.__localStore.set('key4', 'value4');
|
|
135
|
+
cache.__localStore.set('key5', 'value5');
|
|
136
|
+
const scanResult = await cache.scan('nonexistent*');
|
|
137
|
+
expect(scanResult.keys).toEqual([]);
|
|
138
|
+
});
|
|
139
|
+
test('scan method returns keys starting with "key" prefix', async () => {
|
|
140
|
+
cache.__localStore.set('key1', 'value1');
|
|
141
|
+
cache.__localStore.set('key2', 'value2');
|
|
142
|
+
cache.__localStore.set('otherKey1', 'value3');
|
|
143
|
+
cache.__localStore.set('otherKey2', 'value4');
|
|
144
|
+
cache.__localStore.set('key3', 'value5');
|
|
145
|
+
const scanResult = await cache.scan('key*');
|
|
146
|
+
expect(scanResult.keys).toEqual(['key1', 'key2', 'key3']);
|
|
147
|
+
});
|
|
148
|
+
test('scan method returns keys with "2" in the middle', async () => {
|
|
149
|
+
cache.__localStore.set('key1', 'value1');
|
|
150
|
+
cache.__localStore.set('key2', 'value2');
|
|
151
|
+
cache.__localStore.set('otherKey1', 'value3');
|
|
152
|
+
cache.__localStore.set('otherKey2', 'value4');
|
|
153
|
+
cache.__localStore.set('key3', 'value5');
|
|
154
|
+
const scanResult = await cache.scan('*2*');
|
|
155
|
+
expect(scanResult.keys).toEqual(['key2', 'otherKey2']);
|
|
156
|
+
});
|
|
157
|
+
test('scan method returns keys with "l?st*" redis pattern', async () => {
|
|
158
|
+
cache.__localStore.set('list1', 'value1');
|
|
159
|
+
cache.__localStore.set('list2', 'value2');
|
|
160
|
+
cache.__localStore.set('list3', 'value3');
|
|
161
|
+
cache.__localStore.set('list4', 'value4');
|
|
162
|
+
cache.__localStore.set('lest5', 'value5');
|
|
163
|
+
cache.__localStore.set('key1', 'value1');
|
|
164
|
+
const scanResult = await cache.scan('l?st*');
|
|
165
|
+
expect(scanResult.keys).toEqual(['list1', 'list2', 'list3', 'list4', 'lest5']);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
package/out/cache.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { RequestInit, Response as nodeFetchResponse } from 'node-fetch';
|
|
2
|
+
import { TunnelCache } from './tunnel';
|
|
3
|
+
import { IForgeCache } from './interfaces/cache';
|
|
2
4
|
export declare type Response = Pick<nodeFetchResponse, 'text' | 'ok' | 'status'>;
|
|
3
5
|
export declare type ScanResult = {
|
|
4
6
|
cursor: string;
|
|
5
7
|
keys: string[];
|
|
6
8
|
};
|
|
7
9
|
export declare function getResponseBody(response: Response): Promise<any>;
|
|
8
|
-
export declare class Cache {
|
|
10
|
+
export declare class Cache implements IForgeCache {
|
|
9
11
|
private client;
|
|
10
12
|
constructor(client: (path: string, options?: RequestInit) => Promise<Response>);
|
|
11
13
|
private buildRequest;
|
|
@@ -36,5 +38,5 @@ export declare class Cache {
|
|
|
36
38
|
}
|
|
37
39
|
export declare function getFetchRmsRuntimeV1(): ((path: string, options?: RequestInit) => Promise<Response>) | undefined;
|
|
38
40
|
export declare function createFetchRmsRuntimeV2(): (path: string, options?: RequestInit) => Promise<Response>;
|
|
39
|
-
export declare function connect(): Cache;
|
|
41
|
+
export declare function connect(): Cache | TunnelCache;
|
|
40
42
|
//# sourceMappingURL=cache.d.ts.map
|
package/out/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AACA,OAAkB,EAAE,WAAW,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AACA,OAAkB,EAAE,WAAW,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnF,OAAO,EAAE,WAAW,EAAmB,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOjD,oBAAY,QAAQ,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;AAEzE,oBAAY,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAGF,wBAAsB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAYtE;AAED,qBAAa,KAAM,YAAW,WAAW;IAC3B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAGtF,OAAO,CAAC,YAAY;IAUP,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAM5E,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAO9F,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAOxC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAO3F,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3E,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAO3E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOpC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAOrF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAO7C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAMtD;AAED,wBAAgB,oBAAoB,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAG/G;AAID,wBAAgB,uBAAuB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CA0BpG;AAED,wBAAgB,OAAO,IAAI,KAAK,GAAG,WAAW,CAS7C"}
|
package/out/cache.js
CHANGED
|
@@ -5,6 +5,8 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const https_1 = require("https");
|
|
6
6
|
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
7
7
|
const api_1 = require("@forge/api");
|
|
8
|
+
const tunnel_1 = require("./tunnel");
|
|
9
|
+
const __localStore = new Map();
|
|
8
10
|
async function getResponseBody(response) {
|
|
9
11
|
const responseText = await response.text();
|
|
10
12
|
if (!response.ok) {
|
|
@@ -107,6 +109,9 @@ function createFetchRmsRuntimeV2() {
|
|
|
107
109
|
exports.createFetchRmsRuntimeV2 = createFetchRmsRuntimeV2;
|
|
108
110
|
function connect() {
|
|
109
111
|
var _a;
|
|
112
|
+
if (global.__forge_tunnel__) {
|
|
113
|
+
return new tunnel_1.TunnelCache(__localStore);
|
|
114
|
+
}
|
|
110
115
|
const fetch = (_a = getFetchRmsRuntimeV1()) !== null && _a !== void 0 ? _a : createFetchRmsRuntimeV2();
|
|
111
116
|
return new Cache(fetch);
|
|
112
117
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ScanResult } from '../cache';
|
|
2
|
+
export interface IForgeCache {
|
|
3
|
+
set(key: string, value: string, opt?: {
|
|
4
|
+
ttlSeconds: number;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
get(key: string): Promise<string | null>;
|
|
7
|
+
setIfNotExists(key: string, value: string, opt?: {
|
|
8
|
+
ttlSeconds: number;
|
|
9
|
+
}): Promise<'OK' | null>;
|
|
10
|
+
getAndSet(key: string, value: string, opt?: {
|
|
11
|
+
ttlSeconds: number;
|
|
12
|
+
}): Promise<string | null>;
|
|
13
|
+
delete(key: string): Promise<number>;
|
|
14
|
+
incrementAndGet(key: string, opt?: {
|
|
15
|
+
ttlSeconds: number;
|
|
16
|
+
}): Promise<number>;
|
|
17
|
+
leftPush(key: string, value: string): Promise<number>;
|
|
18
|
+
rightPop(key: string): Promise<string | null>;
|
|
19
|
+
listLength(key: string): Promise<number>;
|
|
20
|
+
decrementAndGet(key: string, opt?: {
|
|
21
|
+
ttlSeconds: number;
|
|
22
|
+
}): Promise<number>;
|
|
23
|
+
scan(pattern: string, opt?: {
|
|
24
|
+
cursor?: string;
|
|
25
|
+
count?: number;
|
|
26
|
+
}): Promise<ScanResult>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/interfaces/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC/F,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5F,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvF"}
|
package/out/tunnel.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ScanResult } from './cache';
|
|
2
|
+
import { IForgeCache } from './interfaces/cache';
|
|
3
|
+
export declare type TunnelStoreType = Map<string, string | undefined | string[]>;
|
|
4
|
+
export declare class TunnelCache implements IForgeCache {
|
|
5
|
+
__localStore: TunnelStoreType;
|
|
6
|
+
constructor(store: TunnelStoreType);
|
|
7
|
+
set(key: string, value: string, opt?: {
|
|
8
|
+
ttlSeconds: number;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
get(key: string): Promise<string | null>;
|
|
11
|
+
setIfNotExists(key: string, value: string, opt?: {
|
|
12
|
+
ttlSeconds: number;
|
|
13
|
+
}): Promise<'OK' | null>;
|
|
14
|
+
getAndSet(key: string, value: string, opt?: {
|
|
15
|
+
ttlSeconds: number;
|
|
16
|
+
}): Promise<string | null>;
|
|
17
|
+
delete(key: string): Promise<number>;
|
|
18
|
+
incrementAndGet(key: string, opt?: {
|
|
19
|
+
ttlSeconds: number;
|
|
20
|
+
}): Promise<number>;
|
|
21
|
+
leftPush(key: string, value: string): Promise<number>;
|
|
22
|
+
rightPop(key: string): Promise<string | null>;
|
|
23
|
+
listLength(key: string): Promise<number>;
|
|
24
|
+
decrementAndGet(key: string, opt?: {
|
|
25
|
+
ttlSeconds: number;
|
|
26
|
+
}): Promise<number>;
|
|
27
|
+
scan(pattern: string, opt?: {
|
|
28
|
+
cursor?: string;
|
|
29
|
+
count?: number;
|
|
30
|
+
}): Promise<ScanResult>;
|
|
31
|
+
parseSuccessResponse(response: string | number | null | string[]): Promise<any>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAQjD,oBAAY,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAAC,CAAC;AAEzE,qBAAa,WAAY,YAAW,WAAW;IACtC,YAAY,EAAE,eAAe,CAAC;gBACzB,KAAK,EAAE,eAAe;IAKrB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQxC,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAW9F,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAU3F,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASpC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAS3E,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAcrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAW7C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWxC,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAW3E,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAsBlG,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC;CAKhF"}
|
package/out/tunnel.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TunnelCache = void 0;
|
|
4
|
+
const validator_1 = require("./utils/validator");
|
|
5
|
+
class TunnelCache {
|
|
6
|
+
constructor(store) {
|
|
7
|
+
this.__localStore = store;
|
|
8
|
+
}
|
|
9
|
+
async set(key, value, opt) {
|
|
10
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
11
|
+
(0, validator_1.assertIsValidValue)(value);
|
|
12
|
+
(0, validator_1.assertIsSizedValue)(value);
|
|
13
|
+
this.__localStore.set(key, value);
|
|
14
|
+
return await this.parseSuccessResponse('OK');
|
|
15
|
+
}
|
|
16
|
+
async get(key) {
|
|
17
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
18
|
+
const value = this.__localStore.get(key);
|
|
19
|
+
(0, validator_1.assertAndThrowIfArray)(value);
|
|
20
|
+
return await this.parseSuccessResponse(value !== null && value !== void 0 ? value : null);
|
|
21
|
+
}
|
|
22
|
+
async setIfNotExists(key, value, opt) {
|
|
23
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
24
|
+
(0, validator_1.assertIsValidValue)(value);
|
|
25
|
+
(0, validator_1.assertIsSizedValue)(value);
|
|
26
|
+
if (this.__localStore.get(key)) {
|
|
27
|
+
return await this.parseSuccessResponse(null);
|
|
28
|
+
}
|
|
29
|
+
this.__localStore.set(key, value);
|
|
30
|
+
return await this.parseSuccessResponse('OK');
|
|
31
|
+
}
|
|
32
|
+
async getAndSet(key, value, opt) {
|
|
33
|
+
var _a;
|
|
34
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
35
|
+
(0, validator_1.assertIsValidValue)(value);
|
|
36
|
+
(0, validator_1.assertIsSizedValue)(value);
|
|
37
|
+
const oldValue = (_a = this.__localStore.get(key)) !== null && _a !== void 0 ? _a : null;
|
|
38
|
+
this.__localStore.set(key, value);
|
|
39
|
+
return await this.parseSuccessResponse(oldValue);
|
|
40
|
+
}
|
|
41
|
+
async delete(key) {
|
|
42
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
43
|
+
if (this.__localStore.get(key)) {
|
|
44
|
+
this.__localStore.delete(key);
|
|
45
|
+
return await this.parseSuccessResponse(1);
|
|
46
|
+
}
|
|
47
|
+
return await this.parseSuccessResponse(0);
|
|
48
|
+
}
|
|
49
|
+
async incrementAndGet(key, opt) {
|
|
50
|
+
var _a;
|
|
51
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
52
|
+
const oldValue = this.__localStore.get(key);
|
|
53
|
+
(0, validator_1.assertAndThrowIfArray)(oldValue);
|
|
54
|
+
const newValue = Number.parseInt((_a = oldValue) !== null && _a !== void 0 ? _a : '0') + 1;
|
|
55
|
+
this.__localStore.set(key, newValue.toString());
|
|
56
|
+
return await this.parseSuccessResponse(newValue);
|
|
57
|
+
}
|
|
58
|
+
async leftPush(key, value) {
|
|
59
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
60
|
+
(0, validator_1.assertIsValidValue)(value);
|
|
61
|
+
(0, validator_1.assertIsSizedValue)(value);
|
|
62
|
+
const oldValue = this.__localStore.get(key);
|
|
63
|
+
(0, validator_1.assertAndThrowIfNotArray)(oldValue);
|
|
64
|
+
if (!oldValue) {
|
|
65
|
+
this.__localStore.set(key, []);
|
|
66
|
+
}
|
|
67
|
+
this.__localStore.get(key).unshift(value);
|
|
68
|
+
return await this.parseSuccessResponse(this.__localStore.get(key).length);
|
|
69
|
+
}
|
|
70
|
+
async rightPop(key) {
|
|
71
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
72
|
+
const oldValue = this.__localStore.get(key);
|
|
73
|
+
if (!oldValue) {
|
|
74
|
+
return await this.parseSuccessResponse(null);
|
|
75
|
+
}
|
|
76
|
+
(0, validator_1.assertAndThrowIfNotArray)(oldValue);
|
|
77
|
+
return await this.parseSuccessResponse(this.__localStore.get(key).pop() || null);
|
|
78
|
+
}
|
|
79
|
+
async listLength(key) {
|
|
80
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
81
|
+
const oldValue = this.__localStore.get(key);
|
|
82
|
+
if (!oldValue) {
|
|
83
|
+
return await this.parseSuccessResponse(0);
|
|
84
|
+
}
|
|
85
|
+
(0, validator_1.assertAndThrowIfNotArray)(oldValue);
|
|
86
|
+
return await this.parseSuccessResponse(this.__localStore.get(key).length);
|
|
87
|
+
}
|
|
88
|
+
async decrementAndGet(key, opt) {
|
|
89
|
+
var _a;
|
|
90
|
+
(0, validator_1.assertIsValidKey)(key);
|
|
91
|
+
const oldValue = this.__localStore.get(key);
|
|
92
|
+
(0, validator_1.assertAndThrowIfArray)(oldValue);
|
|
93
|
+
const newValue = Number.parseInt((_a = oldValue) !== null && _a !== void 0 ? _a : '0') - 1;
|
|
94
|
+
this.__localStore.set(key, newValue.toString());
|
|
95
|
+
return await this.parseSuccessResponse(newValue);
|
|
96
|
+
}
|
|
97
|
+
async scan(pattern, opt) {
|
|
98
|
+
const cursor = (opt === null || opt === void 0 ? void 0 : opt.cursor) ? Number(opt.cursor) : 0;
|
|
99
|
+
const count = (opt === null || opt === void 0 ? void 0 : opt.count) || 10;
|
|
100
|
+
const escapedPattern = pattern.replace(/([.+^${}()|\\])/g, '\\$1');
|
|
101
|
+
const regexPattern = escapedPattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
102
|
+
const regex = new RegExp(regexPattern);
|
|
103
|
+
const keys = this.__localStore.keys();
|
|
104
|
+
const matchedKeys = Array.from(keys).filter((key) => regex.test(key));
|
|
105
|
+
const resultKeys = matchedKeys.slice(cursor, cursor + count);
|
|
106
|
+
const nextCursor = cursor + resultKeys.length < matchedKeys.length ? String(cursor + count) : '0';
|
|
107
|
+
return {
|
|
108
|
+
cursor: nextCursor,
|
|
109
|
+
keys: resultKeys
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
parseSuccessResponse(response) {
|
|
113
|
+
return Promise.resolve({
|
|
114
|
+
response: response !== null && response !== void 0 ? response : null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.TunnelCache = TunnelCache;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.test.d.ts","sourceRoot":"","sources":["../../../src/utils/__test__/validator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const validator_1 = require("../validator");
|
|
4
|
+
describe('Forge Cache Tunnel Validators', () => {
|
|
5
|
+
test('should not throw an error for valid keys', () => {
|
|
6
|
+
const validKeys = ['key', 'anotherKey', 'KEY', 'key123', '123', 'key-key'];
|
|
7
|
+
validKeys.forEach((key) => {
|
|
8
|
+
expect(() => (0, validator_1.assertIsValidKey)(key)).not.toThrow();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
test('should throw an error for invalid keys', () => {
|
|
12
|
+
const invalidKeys = ['', ' ', null, undefined, {}, [], '@username', 'Hello, World!', 'password&username'];
|
|
13
|
+
invalidKeys.forEach((key) => {
|
|
14
|
+
expect(() => (0, validator_1.assertIsValidKey)(key)).toThrow();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it('should throw an error if value is undefined or null', () => {
|
|
18
|
+
const invalidKeys = [null, undefined];
|
|
19
|
+
invalidKeys.forEach((key) => {
|
|
20
|
+
expect(() => (0, validator_1.assertIsValidValue)(key)).toThrow();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('should throw an error if value is not a string', () => {
|
|
24
|
+
const invalidKeys = [123, {}];
|
|
25
|
+
invalidKeys.forEach((key) => {
|
|
26
|
+
expect(() => (0, validator_1.assertIsValidValue)(key)).toThrow();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
it('should not throw an error if value is a non-empty string', () => {
|
|
30
|
+
expect(() => (0, validator_1.assertIsValidValue)('test')).not.toThrow();
|
|
31
|
+
});
|
|
32
|
+
it('should not throw an error if value is within valid range ', () => {
|
|
33
|
+
expect(() => (0, validator_1.assertIsSizedValue)('this-is-test-value')).not.toThrow();
|
|
34
|
+
});
|
|
35
|
+
it('should throw an error if value is too large ', () => {
|
|
36
|
+
expect(() => (0, validator_1.assertIsSizedValue)('a'.repeat(240 * 1024))).toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class ForgeCacheTunnelError extends Error {
|
|
2
|
+
readonly message: string;
|
|
3
|
+
readonly reason?: any;
|
|
4
|
+
constructor(message: string, reason?: any);
|
|
5
|
+
}
|
|
6
|
+
export declare function assertIsValidValue(value: unknown): void;
|
|
7
|
+
export declare function assertAndThrowIfArray(value: string | number | undefined | string[]): void;
|
|
8
|
+
export declare function assertAndThrowIfNotArray(value: string | number | undefined | string[]): void;
|
|
9
|
+
export declare function assertIsValidKey(key: unknown): void;
|
|
10
|
+
export declare function assertIsSizedValue(value: unknown): void;
|
|
11
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/utils/validator.ts"],"names":[],"mappings":"AASA,qBAAa,qBAAsB,SAAQ,KAAK;IAE5C,QAAQ,CAAC,OAAO,EAAE,MAAM;IACxB,QAAQ,CAAC,MAAM,CAAC;gBADP,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,KAAK;CAIxB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,QAOhD;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,QAIlF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,QAIrF;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,QAsB5C;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,QAOhD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertIsSizedValue = exports.assertIsValidKey = exports.assertAndThrowIfNotArray = exports.assertAndThrowIfArray = exports.assertIsValidValue = exports.ForgeCacheTunnelError = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const object_sizeof_1 = tslib_1.__importDefault(require("object-sizeof"));
|
|
6
|
+
const MIN_VALID_KEY_LENGTH = 1;
|
|
7
|
+
const MAX_VALID_KEY_LENGTH = 500;
|
|
8
|
+
const VALID_KEY_REGEX = /^(?!\s+$)[a-zA-Z0-9:._\s-#]+$/;
|
|
9
|
+
const KiB = 1024;
|
|
10
|
+
const MAX_VALID_VALUE_LENGTH = 240 * KiB;
|
|
11
|
+
class ForgeCacheTunnelError extends Error {
|
|
12
|
+
constructor(message, reason) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.message = message;
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ForgeCacheTunnelError = ForgeCacheTunnelError;
|
|
19
|
+
function assertIsValidValue(value) {
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
throw new ForgeCacheTunnelError('Provided value is required');
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string') {
|
|
24
|
+
throw new ForgeCacheTunnelError('Provided value must be a string', { type: typeof value });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.assertIsValidValue = assertIsValidValue;
|
|
28
|
+
function assertAndThrowIfArray(value) {
|
|
29
|
+
if (value && Array.isArray(value)) {
|
|
30
|
+
throw new ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.assertAndThrowIfArray = assertAndThrowIfArray;
|
|
34
|
+
function assertAndThrowIfNotArray(value) {
|
|
35
|
+
if (value && !Array.isArray(value)) {
|
|
36
|
+
throw new ForgeCacheTunnelError('WRONGTYPE Operation against a key holding the wrong kind of value.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.assertAndThrowIfNotArray = assertAndThrowIfNotArray;
|
|
40
|
+
function assertIsValidKey(key) {
|
|
41
|
+
if (typeof key !== 'string') {
|
|
42
|
+
throw new Error('Provided key must be a string');
|
|
43
|
+
}
|
|
44
|
+
if (key.length < MIN_VALID_KEY_LENGTH) {
|
|
45
|
+
throw new ForgeCacheTunnelError('Provided key length was under the minimum valid key length', {
|
|
46
|
+
length: key.length,
|
|
47
|
+
minLength: MIN_VALID_KEY_LENGTH
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (key.length > MAX_VALID_KEY_LENGTH) {
|
|
51
|
+
throw new ForgeCacheTunnelError('Provided key length was above the maximum valid key length', {
|
|
52
|
+
length: key.length,
|
|
53
|
+
maxLength: MAX_VALID_KEY_LENGTH
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (!VALID_KEY_REGEX.test(key)) {
|
|
57
|
+
throw new ForgeCacheTunnelError('Provided key must match regex', {
|
|
58
|
+
regex: VALID_KEY_REGEX
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.assertIsValidKey = assertIsValidKey;
|
|
63
|
+
function assertIsSizedValue(value) {
|
|
64
|
+
if ((0, object_sizeof_1.default)(value) > MAX_VALID_VALUE_LENGTH) {
|
|
65
|
+
throw new ForgeCacheTunnelError('Provided value bytes was over maximum valid value size', {
|
|
66
|
+
bytes: (0, object_sizeof_1.default)(value),
|
|
67
|
+
maxBytes: MAX_VALID_VALUE_LENGTH
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.assertIsSizedValue = assertIsSizedValue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/cache",
|
|
3
|
-
"version": "0.8.0-next.
|
|
3
|
+
"version": "0.8.0-next.2",
|
|
4
4
|
"description": "Forge Cache methods",
|
|
5
5
|
"author": "Atlassian",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"node-fetch": "2.7.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@forge/api": "^3.
|
|
23
|
+
"@forge/api": "^3.6.0-next.0",
|
|
24
|
+
"object-sizeof": "^2.6.4"
|
|
24
25
|
}
|
|
25
|
-
}
|
|
26
|
+
}
|