@nxtedition/lib 26.0.6 → 26.0.8
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/cache.js +90 -38
- package/http.js +3 -1
- package/package.json +1 -1
package/cache.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite'
|
|
1
2
|
import { LRUCache } from 'lru-cache'
|
|
3
|
+
import { fastNow } from './time.js'
|
|
4
|
+
|
|
5
|
+
function noop() {}
|
|
2
6
|
|
|
3
7
|
export class AsyncCache {
|
|
4
|
-
/** @type LRUCache<string, {
|
|
8
|
+
/** @type LRUCache<string, { ttl: number, stale: number, value: any } **/
|
|
5
9
|
#lru
|
|
6
10
|
#valueSelector
|
|
7
11
|
#keySelector
|
|
@@ -9,9 +13,12 @@ export class AsyncCache {
|
|
|
9
13
|
#ttl
|
|
10
14
|
#stale
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
#db
|
|
17
|
+
#getQuery
|
|
18
|
+
#setQuery
|
|
19
|
+
#delQuery
|
|
14
20
|
|
|
21
|
+
constructor(location, valueSelector, keySelector, opts) {
|
|
15
22
|
if (typeof location === 'string') {
|
|
16
23
|
// Do nothing...
|
|
17
24
|
} else {
|
|
@@ -47,6 +54,33 @@ export class AsyncCache {
|
|
|
47
54
|
} else {
|
|
48
55
|
throw new TypeError('stale must be a undefined, number or a function')
|
|
49
56
|
}
|
|
57
|
+
|
|
58
|
+
this.#lru =
|
|
59
|
+
opts?.lru === false || opts?.lru === null ? null : new LRUCache({ max: 4096, ...opts?.lru })
|
|
60
|
+
this.#db = new DatabaseSync(location, { timeout: 20, ...opts?.db })
|
|
61
|
+
this.#db.exec(`
|
|
62
|
+
PRAGMA journal_mode = WAL;
|
|
63
|
+
PRAGMA synchronous = NORMAL;
|
|
64
|
+
PRAGMA temp_store = memory;
|
|
65
|
+
PRAGMA optimize;
|
|
66
|
+
|
|
67
|
+
CREATE TABLE IF NOT EXISTS cache (
|
|
68
|
+
key TEXT PRIMARY KEY NOT NULL,
|
|
69
|
+
val TEXT NOT NULL,
|
|
70
|
+
ttl INTEGER NOT NULL,
|
|
71
|
+
stale INTEGER NOT NULL
|
|
72
|
+
);
|
|
73
|
+
`)
|
|
74
|
+
|
|
75
|
+
this.#getQuery = this.#db.prepare(`SELECT val, ttl, stale FROM cache WHERE key = ?`)
|
|
76
|
+
this.#setQuery = this.#db.prepare(
|
|
77
|
+
`INSERT OR REPLACE INTO cache (key, val, ttl, stale) VALUES (?, ?, ?, ?)`,
|
|
78
|
+
)
|
|
79
|
+
this.#delQuery = this.#db.prepare(`DELETE FROM cache WHERE key = ?`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
close() {
|
|
83
|
+
this.#db?.close()
|
|
50
84
|
}
|
|
51
85
|
|
|
52
86
|
/**
|
|
@@ -60,54 +94,66 @@ export class AsyncCache {
|
|
|
60
94
|
throw new TypeError('keySelector must return a non-empty string')
|
|
61
95
|
}
|
|
62
96
|
|
|
63
|
-
|
|
97
|
+
const now = fastNow()
|
|
98
|
+
|
|
99
|
+
let cached = this.#lru?.get(key)
|
|
64
100
|
|
|
65
|
-
if (cached) {
|
|
66
|
-
|
|
101
|
+
if (cached === undefined) {
|
|
102
|
+
try {
|
|
103
|
+
const ret = this.#getQuery?.get(key)
|
|
104
|
+
if (ret !== undefined) {
|
|
105
|
+
cached = {
|
|
106
|
+
ttl: ret.ttl,
|
|
107
|
+
stale: ret.stale,
|
|
108
|
+
value: JSON.parse(ret.val),
|
|
109
|
+
}
|
|
110
|
+
this.#lru?.set(key, cached)
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Do nothing...
|
|
114
|
+
}
|
|
115
|
+
}
|
|
67
116
|
|
|
68
|
-
|
|
117
|
+
if (cached !== undefined) {
|
|
118
|
+
if (now < cached.ttl) {
|
|
69
119
|
return { value: cached.value, async: false }
|
|
70
120
|
}
|
|
71
121
|
|
|
72
|
-
if (now
|
|
73
|
-
// stale-while-revalidate has
|
|
74
|
-
|
|
122
|
+
if (now > cached.stale) {
|
|
123
|
+
// stale-while-revalidate has ttld, purge cached value.
|
|
124
|
+
this.#lru?.delete(key)
|
|
125
|
+
try {
|
|
126
|
+
this.#delQuery?.run(key)
|
|
127
|
+
} catch {
|
|
128
|
+
// Do nothing...
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
cached = undefined
|
|
75
132
|
}
|
|
76
133
|
}
|
|
77
134
|
|
|
78
135
|
let promise
|
|
79
|
-
|
|
80
136
|
if (this.#valueSelector) {
|
|
81
137
|
promise = this.#dedupe.get(key)
|
|
82
|
-
if (
|
|
138
|
+
if (promise === undefined) {
|
|
83
139
|
promise = this.#valueSelector(...args).then(
|
|
84
140
|
(value) => {
|
|
85
141
|
this.set(key, value)
|
|
86
|
-
return
|
|
142
|
+
return value
|
|
87
143
|
},
|
|
88
144
|
(err) => {
|
|
89
145
|
this.delete(key)
|
|
90
|
-
|
|
146
|
+
throw err
|
|
91
147
|
},
|
|
92
148
|
)
|
|
149
|
+
promise.catch(noop)
|
|
93
150
|
this.#dedupe.set(key, promise)
|
|
94
151
|
}
|
|
95
152
|
}
|
|
96
153
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
value: promise.then(([err, val]) => {
|
|
103
|
-
if (err) {
|
|
104
|
-
throw err
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return val
|
|
108
|
-
}),
|
|
109
|
-
async: true,
|
|
110
|
-
}
|
|
154
|
+
return cached !== undefined
|
|
155
|
+
? { value: cached.value, async: false }
|
|
156
|
+
: { value: promise, async: true }
|
|
111
157
|
}
|
|
112
158
|
|
|
113
159
|
/**
|
|
@@ -119,28 +165,34 @@ export class AsyncCache {
|
|
|
119
165
|
throw new TypeError('key must be a non-empty string')
|
|
120
166
|
}
|
|
121
167
|
|
|
168
|
+
const now = fastNow()
|
|
122
169
|
const cached = {
|
|
123
|
-
|
|
170
|
+
ttl: now + this.#ttl(value, key),
|
|
171
|
+
stale: now + this.#stale(value, key),
|
|
124
172
|
value,
|
|
125
173
|
}
|
|
126
174
|
|
|
127
|
-
this.#lru
|
|
175
|
+
this.#lru?.set(key, cached)
|
|
128
176
|
this.#dedupe.delete(key)
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
this.#setQuery?.run(key, JSON.stringify(value), cached.ttl, cached.stale)
|
|
180
|
+
} catch {
|
|
181
|
+
// Do nothing...
|
|
182
|
+
}
|
|
129
183
|
}
|
|
130
184
|
|
|
131
|
-
/**
|
|
132
|
-
* @param {string} key
|
|
133
|
-
*/
|
|
134
185
|
delete(key) {
|
|
135
186
|
if (typeof key !== 'string' || key.length === 0) {
|
|
136
187
|
throw new TypeError('key must be a non-empty string')
|
|
137
188
|
}
|
|
138
189
|
|
|
139
|
-
this.#lru
|
|
140
|
-
this.#dedupe.delete(key)
|
|
141
|
-
}
|
|
190
|
+
this.#lru?.delete(key)
|
|
142
191
|
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
try {
|
|
193
|
+
this.#delQuery?.run(key)
|
|
194
|
+
} catch {
|
|
195
|
+
// Do nothing...
|
|
196
|
+
}
|
|
145
197
|
}
|
|
146
198
|
}
|
package/http.js
CHANGED
|
@@ -166,7 +166,9 @@ export async function upgradeMiddleware(ctx, next) {
|
|
|
166
166
|
await thenable
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
if (!socket.destroyed && !socket.writableEnded) {
|
|
170
|
+
throw new Error('Stream not completed')
|
|
171
|
+
}
|
|
170
172
|
|
|
171
173
|
const elapsedTime = performance.now() - startTime
|
|
172
174
|
|