bruce-models 2.1.3 → 2.1.4
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/dist/bruce-models.es5.js +138 -2924
- package/dist/bruce-models.es5.js.map +1 -1
- package/dist/bruce-models.umd.js +138 -2924
- package/dist/bruce-models.umd.js.map +1 -1
- package/dist/lib/common/cache.js +137 -108
- package/dist/lib/common/cache.js.map +1 -1
- package/dist/types/common/cache.d.ts +16 -7
- package/package.json +1 -1
package/dist/lib/common/cache.js
CHANGED
|
@@ -1,136 +1,165 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.CacheControl = void 0;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
25
|
-
if (value instanceof Promise) {
|
|
26
|
-
this.inMemoryPromises[key] = value;
|
|
27
|
-
const currentVersion = (this.keyVersions[key] = (this.keyVersions[key] || 0) + 1);
|
|
28
|
-
value
|
|
29
|
-
.then((resolvedValue) => {
|
|
30
|
-
if (this.keyVersions[key] === currentVersion) {
|
|
31
|
-
this.Set(key, resolvedValue, duration);
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
.catch((error) => {
|
|
35
|
-
if (this.keyVersions[key] === currentVersion) {
|
|
36
|
-
this.Set(key, error, duration);
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
.finally(() => {
|
|
40
|
-
delete this.inMemoryPromises[key];
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const item = {
|
|
45
|
-
value,
|
|
46
|
-
expiry: Date.now() + duration,
|
|
47
|
-
};
|
|
48
|
-
yield this.store.setItem(key, item);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
Get(key) {
|
|
53
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
-
if (this.inMemoryPromises[key]) {
|
|
55
|
-
return this.inMemoryPromises[key];
|
|
56
|
-
}
|
|
57
|
-
const item = (yield this.store.getItem(key));
|
|
58
|
-
if (!item) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
if (Date.now() > item.expiry) {
|
|
62
|
-
yield this.store.removeItem(key);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return item.value;
|
|
66
|
-
});
|
|
4
|
+
class CacheItem {
|
|
5
|
+
/**
|
|
6
|
+
* @param id
|
|
7
|
+
* @param data
|
|
8
|
+
* @param duration in seconds until considered expired.
|
|
9
|
+
*/
|
|
10
|
+
constructor(id, data, duration) {
|
|
11
|
+
this.Id = id;
|
|
12
|
+
this.Data = data;
|
|
13
|
+
this.Duration = duration;
|
|
14
|
+
this._created = duration > -1 ? new Date() : null;
|
|
67
15
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
contains: (key) => key.includes(pattern),
|
|
74
|
-
startswith: (key) => key.startsWith(pattern),
|
|
75
|
-
};
|
|
76
|
-
const check = checkFuncs[mode];
|
|
77
|
-
if (!check) {
|
|
78
|
-
throw new Error("Invalid mode");
|
|
79
|
-
}
|
|
80
|
-
for (const key of keys) {
|
|
81
|
-
if (check(key)) {
|
|
82
|
-
yield this.store.removeItem(key);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
for (const key in this.inMemoryPromises) {
|
|
86
|
-
if (check(key)) {
|
|
87
|
-
delete this.inMemoryPromises[key];
|
|
88
|
-
this.keyVersions[key]++;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
16
|
+
IsExpired() {
|
|
17
|
+
if (this._created == null) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return (new Date().getTime() - this._created.getTime()) / 1000 > this.Duration;
|
|
92
21
|
}
|
|
93
22
|
}
|
|
23
|
+
// Milliseconds between each data cleaning.
|
|
24
|
+
const CLEANER_INTERVAL = 5000;
|
|
25
|
+
// Max number of items to clean in each cleaning.
|
|
26
|
+
const MAX_CLEAN_BATCH = 100;
|
|
27
|
+
// (Default) Max idle time to keep the cleaner alive.
|
|
28
|
+
// Set to default API cache duration by default.
|
|
29
|
+
const DEFAULT_MAX_CLEAN_IDLE = 60 * 5;
|
|
30
|
+
/**
|
|
31
|
+
* Simple local cache controller.
|
|
32
|
+
*/
|
|
94
33
|
class CacheControl {
|
|
95
34
|
constructor(id) {
|
|
35
|
+
this.data = {};
|
|
96
36
|
this.Disabled = false;
|
|
97
|
-
this.
|
|
37
|
+
this.cleanerInterval = null;
|
|
38
|
+
// Max idle time to keep the cleaner alive.
|
|
39
|
+
// This value is increased when a new item is added with a larger cache duration.
|
|
40
|
+
this.maxCleanIdle = DEFAULT_MAX_CLEAN_IDLE;
|
|
98
41
|
}
|
|
99
42
|
/**
|
|
100
43
|
* @param id
|
|
101
44
|
* @param data
|
|
102
45
|
* @param duration seconds to keep the data in cache. -1 for infinite.
|
|
103
46
|
*/
|
|
104
|
-
Set(id, data, duration =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
47
|
+
Set(id, data, duration = -1) {
|
|
48
|
+
this.data[id + ""] = new CacheItem(id + "", data, duration);
|
|
49
|
+
if (duration > -1) {
|
|
50
|
+
this.startCleaning();
|
|
51
|
+
if (this.maxCleanIdle < duration) {
|
|
52
|
+
// Add 5 seconds to be sure cleaner stays alive to get it.
|
|
53
|
+
this.maxCleanIdle = duration + 5;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
109
56
|
}
|
|
110
57
|
Get(id) {
|
|
111
|
-
|
|
112
|
-
return
|
|
113
|
-
}
|
|
58
|
+
if (this.Disabled) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const item = this.data[id + ""];
|
|
62
|
+
if (item == null) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (item.IsExpired()) {
|
|
66
|
+
delete this.data[id + ""];
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return item.Data;
|
|
114
70
|
}
|
|
115
71
|
Clear() {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
});
|
|
72
|
+
this.data = {};
|
|
73
|
+
this.stopCleaning();
|
|
119
74
|
}
|
|
120
75
|
Remove(id) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
76
|
+
delete this.data[id + ""];
|
|
77
|
+
if (Object.keys(this.data).length <= 0) {
|
|
78
|
+
this.stopCleaning();
|
|
79
|
+
}
|
|
124
80
|
}
|
|
125
81
|
RemoveByStartsWith(text) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
82
|
+
for (const key in this.data) {
|
|
83
|
+
if (key.startsWith(text)) {
|
|
84
|
+
delete this.data[key];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (Object.keys(this.data).length <= 0) {
|
|
88
|
+
this.stopCleaning();
|
|
89
|
+
}
|
|
129
90
|
}
|
|
130
91
|
RemoveByContains(text) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
92
|
+
for (const key in this.data) {
|
|
93
|
+
if (key.includes(text)) {
|
|
94
|
+
delete this.data[key];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (Object.keys(this.data).length <= 0) {
|
|
98
|
+
this.stopCleaning();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
GetKeys() {
|
|
102
|
+
if (this.Disabled) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return Object.keys(this.data);
|
|
106
|
+
}
|
|
107
|
+
GetValues() {
|
|
108
|
+
if (this.Disabled) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const values = [];
|
|
112
|
+
for (const key in this.data) {
|
|
113
|
+
const data = this.Get(key);
|
|
114
|
+
if (data != null) {
|
|
115
|
+
values.push(data);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return values;
|
|
119
|
+
}
|
|
120
|
+
startCleaning() {
|
|
121
|
+
if (this.cleanerInterval == null) {
|
|
122
|
+
let cleanIdleStart = null;
|
|
123
|
+
this.cleanerInterval = setInterval(() => {
|
|
124
|
+
let cleaned = 0;
|
|
125
|
+
for (const key in this.data) {
|
|
126
|
+
const item = this.data[key];
|
|
127
|
+
if (item.IsExpired()) {
|
|
128
|
+
delete this.data[key];
|
|
129
|
+
cleaned += 1;
|
|
130
|
+
}
|
|
131
|
+
if (cleaned >= MAX_CLEAN_BATCH) {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (cleaned <= 0) {
|
|
136
|
+
// Stop cleaner if nothing in cache.
|
|
137
|
+
if (Object.keys(this.data).length <= 0) {
|
|
138
|
+
this.stopCleaning();
|
|
139
|
+
}
|
|
140
|
+
// Stop cleaner if idle time is reached.
|
|
141
|
+
else {
|
|
142
|
+
if (cleanIdleStart == null) {
|
|
143
|
+
cleanIdleStart = new Date().getTime();
|
|
144
|
+
}
|
|
145
|
+
const now = new Date().getTime();
|
|
146
|
+
if (now - cleanIdleStart > this.maxCleanIdle * 1000) {
|
|
147
|
+
this.stopCleaning();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
cleanIdleStart = null;
|
|
153
|
+
}
|
|
154
|
+
}, CLEANER_INTERVAL);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
stopCleaning() {
|
|
158
|
+
if (this.cleanerInterval != null) {
|
|
159
|
+
clearInterval(this.cleanerInterval);
|
|
160
|
+
this.cleanerInterval = null;
|
|
161
|
+
this.maxCleanIdle = DEFAULT_MAX_CLEAN_IDLE;
|
|
162
|
+
}
|
|
134
163
|
}
|
|
135
164
|
}
|
|
136
165
|
exports.CacheControl = CacheControl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/common/cache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/common/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
declare type Key = string | number;
|
|
2
|
+
/**
|
|
3
|
+
* Simple local cache controller.
|
|
4
|
+
*/
|
|
2
5
|
export declare class CacheControl<T> {
|
|
3
6
|
private data;
|
|
4
7
|
Disabled: boolean;
|
|
5
|
-
|
|
8
|
+
private cleanerInterval;
|
|
9
|
+
private maxCleanIdle;
|
|
10
|
+
constructor(id: string);
|
|
6
11
|
/**
|
|
7
12
|
* @param id
|
|
8
13
|
* @param data
|
|
9
14
|
* @param duration seconds to keep the data in cache. -1 for infinite.
|
|
10
15
|
*/
|
|
11
|
-
Set(id: Key, data: T, duration?: number):
|
|
12
|
-
Get(id: Key):
|
|
13
|
-
Clear():
|
|
14
|
-
Remove(id: Key):
|
|
15
|
-
RemoveByStartsWith(text: string):
|
|
16
|
-
RemoveByContains(text: string):
|
|
16
|
+
Set(id: Key, data: T, duration?: number): void;
|
|
17
|
+
Get(id: Key): T;
|
|
18
|
+
Clear(): void;
|
|
19
|
+
Remove(id: Key): void;
|
|
20
|
+
RemoveByStartsWith(text: string): void;
|
|
21
|
+
RemoveByContains(text: string): void;
|
|
22
|
+
GetKeys(): string[];
|
|
23
|
+
GetValues(): T[];
|
|
24
|
+
private startCleaning;
|
|
25
|
+
private stopCleaning;
|
|
17
26
|
}
|
|
18
27
|
export {};
|