sqlcache 2.9.0 → 2.9.1
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/.env.example +31 -17
- package/{readme.md → README.md} +106 -28
- package/core/autoInvalidate.js +14 -4
- package/core/autoKey.js +44 -8
- package/dbConnector.js +418 -65
- package/docs/API.md +161 -61
- package/docs/CONFIGURATION.md +155 -47
- package/docs/CONTRIBUTING.md +39 -29
- package/docs/EXAMPLES.md +70 -29
- package/docs/MIGRATION.md +141 -42
- package/docs/README.tr.md +75 -27
- package/docs/USE-CASE-BULK.md +48 -17
- package/docs/USE-CASE-ECOMMERCE.md +8 -2
- package/docs/USE-CASE-REALTIME.md +18 -6
- package/docs/USE-CASE-SAAS.md +24 -15
- package/index.d.ts +163 -50
- package/index.js +50 -23
- package/package.json +11 -5
- package/redis.Connector.js +98 -5
package/docs/CONFIGURATION.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# Configuration Guide
|
|
2
2
|
|
|
3
|
-
Complete configuration reference for
|
|
3
|
+
Complete configuration reference for sqlcache.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Environment Variables
|
|
8
8
|
|
|
9
|
+
sqlcache reads its settings from `process.env` when it is first required. It loads `.env` from the
|
|
10
|
+
working directory (only that file, and `${VAR}` references are not expanded); variables already set
|
|
11
|
+
in the environment take precedence. A commented template is in `node_modules/sqlcache/.env.example`.
|
|
12
|
+
|
|
9
13
|
### Required Configuration
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
16
|
# MySQL Database
|
|
13
17
|
DB_HOST=localhost # MySQL host
|
|
14
18
|
DB_USERNAME=root # MySQL username
|
|
15
|
-
DB_PASSWORD=secret # MySQL password
|
|
16
19
|
DB_NAME=mydb # Database name
|
|
17
20
|
|
|
18
|
-
# Redis Cache
|
|
21
|
+
# Redis Cache (required unless REDIS_ENABLED=false)
|
|
19
22
|
REDIS_SERVER=localhost # Redis host
|
|
20
|
-
REDIS_PORT=6379 # Redis port
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
### Optional Configuration
|
|
@@ -25,23 +27,33 @@ REDIS_PORT=6379 # Redis port
|
|
|
25
27
|
#### Database Settings
|
|
26
28
|
|
|
27
29
|
```bash
|
|
30
|
+
DB_PASSWORD=secret # MySQL password (default: empty)
|
|
28
31
|
DB_PORT=3306 # MySQL port (default: 3306)
|
|
29
|
-
DB_CONNECTION_LIMIT=10 # Max connections in pool (default: 151)
|
|
32
|
+
DB_CONNECTION_LIMIT=10 # Max connections in pool, per process (default: 151)
|
|
30
33
|
DB_QUEUE_LIMIT=0 # Max queued requests (default: 0 = unlimited)
|
|
31
34
|
DB_CONNECT_TIMEOUT=10000 # Connection timeout in ms (default: 10000)
|
|
32
|
-
DB_MULTIPLE_STATEMENTS=false # Allow multiple statements (default: true, for backward compatibility)
|
|
35
|
+
DB_MULTIPLE_STATEMENTS=false # Allow multiple statements (default: true, for backward compatibility; false is recommended for new projects)
|
|
33
36
|
DB_COALESCE_QUERIES=true # Concurrent identical cache misses share one query (default: true)
|
|
37
|
+
DB_COALESCE_MAX_WAIT=10000 # Max wait (ms) for such a shared query before running a fresh one (default: 10000, v2.9.1)
|
|
34
38
|
DB_PAGINATION_REUSE_TOTAL=false # Reuse a query's pagination total across pages (default: false)
|
|
35
39
|
DB_MAX_IDLE=10 # Idle connections kept in the pool (default: 10)
|
|
36
40
|
DB_IDLE_TIMEOUT=60000 # Idle time (ms) before connections above DB_MAX_IDLE close (default: 60000)
|
|
37
41
|
TIMEZONE=+00:00 # Timezone offset (default: +00:00)
|
|
38
42
|
```
|
|
39
43
|
|
|
44
|
+
`DB_MULTIPLE_STATEMENTS=true` lets an SQL injection run additional statements; new projects should
|
|
45
|
+
set it to `false` unless they send several statements in one query. The default stays `true` in 2.x.
|
|
46
|
+
|
|
47
|
+
Size the pool for the whole deployment: instances × workers × `DB_CONNECTION_LIMIT`, plus any other
|
|
48
|
+
clients, must stay below MySQL's `max_connections`. MySQL's default `max_connections` is 151, the same
|
|
49
|
+
as sqlcache's default limit, so a second process with default settings can already exceed it.
|
|
50
|
+
|
|
40
51
|
#### Redis Settings
|
|
41
52
|
|
|
42
53
|
```bash
|
|
54
|
+
REDIS_PORT=6379 # Redis port (default: 6379)
|
|
43
55
|
REDIS_PASSWORD=secret # Redis password (if required)
|
|
44
|
-
REDIS_VHOST=my_app # Redis key namespace/prefix
|
|
56
|
+
REDIS_VHOST=my_app # Redis key namespace/prefix, keys become my_app:<key> (set once per process)
|
|
45
57
|
REDIS_ENABLED=true # Enable/disable Redis (default: true)
|
|
46
58
|
```
|
|
47
59
|
|
|
@@ -61,18 +73,25 @@ replayed once it is ready again. See [MIGRATION.md](MIGRATION.md#v28x--v290-perf
|
|
|
61
73
|
for the full list of v2.9.0 changes and how to restore v2.8.x behavior.
|
|
62
74
|
|
|
63
75
|
With `REDIS_ENABLED=false` no Redis connection is opened unless a Redis function
|
|
64
|
-
(`getArrayItem`, `getRedisClient`, ...) is called directly.
|
|
76
|
+
(`getArrayItem`, `getRedisClient`, ...) is called directly. Since v2.9.1 `isRedisConnected()` then
|
|
77
|
+
returns `false` without opening one.
|
|
78
|
+
|
|
79
|
+
`REDIS_VHOST` applies to the whole process. Changing it at runtime (for example per tenant request)
|
|
80
|
+
mixes the cache entries of concurrent requests; v2.9.1 logs a one-time warning when it changes.
|
|
81
|
+
|
|
82
|
+
Redis is a trusted store: cached rows are returned as stored. Protect it with AUTH
|
|
83
|
+
(`REDIS_PASSWORD`) and a private network, and give each application its own `REDIS_VHOST` or Redis
|
|
84
|
+
database.
|
|
65
85
|
|
|
66
86
|
#### Smart Features (v2.6.0)
|
|
67
87
|
|
|
68
88
|
```bash
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# Or enable individually
|
|
73
|
-
CORE_AUTO_INVALIDATION=true # Auto cache invalidation only
|
|
89
|
+
CORE_AUTO_FEATURES=true # Auto cache keys only (does not enable invalidation)
|
|
90
|
+
CORE_AUTO_INVALIDATION=true # Auto cache invalidation
|
|
74
91
|
```
|
|
75
92
|
|
|
93
|
+
Set both to use automatic keys together with automatic invalidation.
|
|
94
|
+
|
|
76
95
|
---
|
|
77
96
|
|
|
78
97
|
## Programmatic Configuration
|
|
@@ -89,14 +108,17 @@ configure({
|
|
|
89
108
|
autoInvalidation: {
|
|
90
109
|
enabled: true,
|
|
91
110
|
tables: {
|
|
92
|
-
users: ['users_*', 'profiles_*', 'sessions_*'],
|
|
93
|
-
orders: ['orders_*', 'cart_*', 'analytics_*'],
|
|
94
|
-
products: ['products_*', 'catalog_*']
|
|
111
|
+
users: ['users:*', 'users_*', 'profiles_*', 'sessions_*'],
|
|
112
|
+
orders: ['orders:*', 'orders_*', 'cart_*', 'analytics_*'],
|
|
113
|
+
products: ['products:*', 'products_*', 'catalog_*']
|
|
95
114
|
}
|
|
96
115
|
}
|
|
97
116
|
});
|
|
98
117
|
```
|
|
99
118
|
|
|
119
|
+
A `tables` rule replaces the default `<table>_*` and `<table>:*` patterns for that table. Automatic
|
|
120
|
+
keys start with `<table>:`, so keep `<table>:*` in the rule when you use them.
|
|
121
|
+
|
|
100
122
|
### Individual Feature Configuration
|
|
101
123
|
|
|
102
124
|
```javascript
|
|
@@ -109,16 +131,36 @@ enableAutoKey({ enabled: true });
|
|
|
109
131
|
enableAutoInvalidation({
|
|
110
132
|
enabled: true,
|
|
111
133
|
tables: {
|
|
112
|
-
users: 'users_*',
|
|
113
|
-
orders: ['orders_*', 'analytics_*']
|
|
134
|
+
users: ['users:*', 'users_*'],
|
|
135
|
+
orders: ['orders:*', 'orders_*', 'analytics_*']
|
|
114
136
|
}
|
|
115
137
|
});
|
|
138
|
+
|
|
139
|
+
// Turn features off again (`false` works since v2.9.1; earlier versions turned the feature on for it)
|
|
140
|
+
enableAutoKey(false);
|
|
141
|
+
enableAutoInvalidation({ enabled: false });
|
|
116
142
|
```
|
|
117
143
|
|
|
144
|
+
`enableAutoKey`, `enableAutoInvalidation` and the `autoKey` / `autoInvalidation` options of
|
|
145
|
+
`configure` accept `true`/`false` as well as an options object.
|
|
146
|
+
|
|
118
147
|
---
|
|
119
148
|
|
|
120
149
|
## Configuration by Environment
|
|
121
150
|
|
|
151
|
+
sqlcache itself only loads `.env`. To keep one file per environment, load it yourself before the
|
|
152
|
+
first `require('sqlcache')` (with `dotenv` in your own dependencies); values that are already set
|
|
153
|
+
are not overwritten by `.env`:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
require('dotenv').config({ path: `.env.${process.env.NODE_ENV}` });
|
|
157
|
+
const { getCacheQuery } = require('sqlcache');
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Secrets such as `DB_PASSWORD` and `REDIS_PASSWORD` are best set in the process environment by your
|
|
161
|
+
deployment. `${VAR}` references in these files are not expanded: `DB_PASSWORD=${PROD_DB_PASSWORD}`
|
|
162
|
+
would be used literally.
|
|
163
|
+
|
|
122
164
|
### Development
|
|
123
165
|
|
|
124
166
|
```bash
|
|
@@ -131,44 +173,48 @@ DB_CONNECTION_LIMIT=5
|
|
|
131
173
|
REDIS_SERVER=localhost
|
|
132
174
|
REDIS_PORT=6379
|
|
133
175
|
CORE_AUTO_FEATURES=true
|
|
176
|
+
CORE_AUTO_INVALIDATION=true
|
|
134
177
|
```
|
|
135
178
|
|
|
136
179
|
### Staging
|
|
137
180
|
|
|
138
181
|
```bash
|
|
139
|
-
# .env.staging
|
|
182
|
+
# .env.staging (DB_PASSWORD and REDIS_PASSWORD come from the environment)
|
|
140
183
|
DB_HOST=staging-db.example.com
|
|
141
184
|
DB_USERNAME=app_user
|
|
142
|
-
DB_PASSWORD=${STAGING_DB_PASSWORD}
|
|
143
185
|
DB_NAME=myapp_staging
|
|
144
186
|
DB_CONNECTION_LIMIT=20
|
|
187
|
+
DB_MULTIPLE_STATEMENTS=false
|
|
145
188
|
REDIS_SERVER=staging-redis.example.com
|
|
146
189
|
REDIS_PORT=6379
|
|
147
|
-
REDIS_PASSWORD=${STAGING_REDIS_PASSWORD}
|
|
148
190
|
CORE_AUTO_FEATURES=true
|
|
191
|
+
CORE_AUTO_INVALIDATION=true
|
|
149
192
|
```
|
|
150
193
|
|
|
151
194
|
### Production
|
|
152
195
|
|
|
153
196
|
```bash
|
|
154
|
-
# .env.production
|
|
197
|
+
# .env.production (DB_PASSWORD and REDIS_PASSWORD come from the environment)
|
|
155
198
|
DB_HOST=prod-db.example.com
|
|
156
199
|
DB_USERNAME=app_user
|
|
157
|
-
DB_PASSWORD=${PROD_DB_PASSWORD}
|
|
158
200
|
DB_NAME=myapp_prod
|
|
159
201
|
DB_CONNECTION_LIMIT=50
|
|
160
202
|
DB_CONNECT_TIMEOUT=5000
|
|
203
|
+
DB_MULTIPLE_STATEMENTS=false
|
|
161
204
|
REDIS_SERVER=prod-redis.example.com
|
|
162
205
|
REDIS_PORT=6379
|
|
163
|
-
REDIS_PASSWORD=${PROD_REDIS_PASSWORD}
|
|
164
206
|
REDIS_VHOST=myapp_prod
|
|
165
207
|
CORE_AUTO_FEATURES=true
|
|
208
|
+
CORE_AUTO_INVALIDATION=true
|
|
166
209
|
```
|
|
167
210
|
|
|
168
211
|
---
|
|
169
212
|
|
|
170
213
|
## Connection Pool Tuning
|
|
171
214
|
|
|
215
|
+
The limits below are per process. Multiply by the number of instances and workers, add other
|
|
216
|
+
clients, and keep the sum below MySQL's `max_connections` (default 151).
|
|
217
|
+
|
|
172
218
|
### Small Application (< 100 concurrent users)
|
|
173
219
|
|
|
174
220
|
```bash
|
|
@@ -207,8 +253,10 @@ DB_MAX_IDLE=50
|
|
|
207
253
|
|
|
208
254
|
### Large Result Sets and Deep Pages
|
|
209
255
|
|
|
210
|
-
- Cached results are stored as JSON
|
|
211
|
-
|
|
256
|
+
- Cached results are stored as JSON, and serializing and parsing grow with the number of rows (see
|
|
257
|
+
the README's Performance section), so prefer `getCacheQueryPagination` over caching very large
|
|
258
|
+
results. On a cache hit a `Date` is an ISO string and a `Buffer` is `{ type, data }`, while a miss
|
|
259
|
+
returns mysql2's types; normalize such columns in your code.
|
|
212
260
|
- Pagination uses `LIMIT offset, size`; MySQL reads and skips `offset` rows, so very deep pages cost
|
|
213
261
|
more. For long lists, an indexed `WHERE id > ?` (keyset) condition in the SQL stays fast at any depth.
|
|
214
262
|
- `DB_PAGINATION_REUSE_TOTAL=true` avoids recounting the total on every page miss of large lists.
|
|
@@ -224,12 +272,13 @@ setInterval(() => {
|
|
|
224
272
|
total: stats.totalConnections,
|
|
225
273
|
active: stats.activeConnections,
|
|
226
274
|
free: stats.freeConnections,
|
|
227
|
-
queued: stats.queuedRequests
|
|
228
|
-
utilization: (stats.activeConnections / stats.totalConnections * 100).toFixed(2) + '%'
|
|
275
|
+
queued: stats.queuedRequests
|
|
229
276
|
});
|
|
230
277
|
|
|
231
|
-
|
|
232
|
-
|
|
278
|
+
// Connections are opened on demand, so an idle pool has 0 of 0 connections in use.
|
|
279
|
+
// Requests waiting for a connection are what shows an exhausted pool.
|
|
280
|
+
if (stats.queuedRequests > 0) {
|
|
281
|
+
console.warn('⚠️ Connection pool exhausted: requests are waiting for a connection');
|
|
233
282
|
}
|
|
234
283
|
}, 60000); // Check every minute
|
|
235
284
|
```
|
|
@@ -267,19 +316,26 @@ When auto-invalidation is enabled, default patterns are:
|
|
|
267
316
|
// - Clears: users:*
|
|
268
317
|
```
|
|
269
318
|
|
|
319
|
+
They apply to `QuaryCache` and `tx.query` writes without a `resetCacheName`, and since v2.9.1 also to
|
|
320
|
+
`bulkInsert` without one. A `resetCacheName` replaces the automatic patterns for that call.
|
|
321
|
+
|
|
270
322
|
#### Custom Patterns
|
|
271
323
|
|
|
324
|
+
A rule replaces the defaults for its table, so keep `{table}:*` when you use automatic keys:
|
|
325
|
+
|
|
272
326
|
```javascript
|
|
273
327
|
enableAutoInvalidation({
|
|
274
328
|
enabled: true,
|
|
275
329
|
tables: {
|
|
276
330
|
users: [
|
|
331
|
+
'users:*', // Automatic keys
|
|
277
332
|
'users_*', // All user caches
|
|
278
333
|
'profiles_*', // User profiles
|
|
279
334
|
'sessions_*', // User sessions
|
|
280
335
|
'feed:*' // User feeds
|
|
281
336
|
],
|
|
282
337
|
orders: [
|
|
338
|
+
'orders:*',
|
|
283
339
|
'orders_*',
|
|
284
340
|
'cart_*',
|
|
285
341
|
'analytics_orders_*'
|
|
@@ -288,6 +344,9 @@ enableAutoInvalidation({
|
|
|
288
344
|
});
|
|
289
345
|
```
|
|
290
346
|
|
|
347
|
+
Patterns are Redis glob patterns used as prefixes: escape `*`, `?`, `[`, `]` and `\` in any part that
|
|
348
|
+
comes from user input. An empty prefix without `REDIS_VHOST` deletes every key in that Redis database.
|
|
349
|
+
|
|
291
350
|
### Disable Redis
|
|
292
351
|
|
|
293
352
|
```bash
|
|
@@ -296,14 +355,17 @@ REDIS_ENABLED=false
|
|
|
296
355
|
|
|
297
356
|
```javascript
|
|
298
357
|
// Queries execute normally without caching
|
|
299
|
-
const users = await getCacheQuery('SELECT * FROM users', []);
|
|
300
|
-
// No Redis involved
|
|
358
|
+
const users = await getCacheQuery('SELECT * FROM users', [], 'users:all');
|
|
359
|
+
// No Redis involved (a cache name is still required unless automatic keys are enabled)
|
|
301
360
|
```
|
|
302
361
|
|
|
303
362
|
---
|
|
304
363
|
|
|
305
364
|
## Auto Key Generation Strategy
|
|
306
365
|
|
|
366
|
+
Automatic keys are used when a call has no cache name and `CORE_AUTO_FEATURES=true` (or
|
|
367
|
+
`enableAutoKey()`) is set.
|
|
368
|
+
|
|
307
369
|
### Detailed Strategy (1-3 parameters)
|
|
308
370
|
|
|
309
371
|
```javascript
|
|
@@ -324,6 +386,20 @@ SELECT * FROM orders WHERE a = ? AND b = ? AND c = ? AND d = ?
|
|
|
324
386
|
orders:9f2ac12b
|
|
325
387
|
```
|
|
326
388
|
|
|
389
|
+
### Other Keys
|
|
390
|
+
|
|
391
|
+
- No `WHERE` clause: `<table>:all`, whatever the rest of the SQL.
|
|
392
|
+
- No detectable table (`CALL ...`, `SELECT fn(?)`): `query:<sqlhash>:<paramhash>` since v2.9.1
|
|
393
|
+
(earlier versions left the parameters out).
|
|
394
|
+
- Calls with a `database` argument: the key ends with `@<database>`.
|
|
395
|
+
|
|
396
|
+
### Limits
|
|
397
|
+
|
|
398
|
+
The select list, `ORDER BY`, `LIMIT`, literal values and joined tables are not part of the key, and
|
|
399
|
+
the parameter hash is 32-bit, so different queries can share a key. Use explicit cache names for
|
|
400
|
+
aggregates, joins, `LIMIT`/`ORDER BY` variants, time-relative SQL (`NOW()`, `CURDATE()`) and anything
|
|
401
|
+
user-specific or sensitive.
|
|
402
|
+
|
|
327
403
|
### Manual Override
|
|
328
404
|
|
|
329
405
|
```javascript
|
|
@@ -344,11 +420,11 @@ const users = await getCacheQuery('SELECT * FROM users WHERE id = ?', [123], 'my
|
|
|
344
420
|
# NEVER commit passwords to git
|
|
345
421
|
# Use environment variables or secrets management
|
|
346
422
|
|
|
347
|
-
# Good
|
|
348
|
-
DB_PASSWORD=${DATABASE_PASSWORD}
|
|
349
|
-
|
|
423
|
+
# Good: set DB_PASSWORD and REDIS_PASSWORD in the process environment (e.g. container secrets);
|
|
424
|
+
# they take precedence over .env. A line like DB_PASSWORD=${DATABASE_PASSWORD} in .env is
|
|
425
|
+
# NOT expanded and would be used literally.
|
|
350
426
|
|
|
351
|
-
# Bad
|
|
427
|
+
# Bad: a real password in a committed .env file
|
|
352
428
|
DB_PASSWORD=mypassword123
|
|
353
429
|
```
|
|
354
430
|
|
|
@@ -356,12 +432,30 @@ DB_PASSWORD=mypassword123
|
|
|
356
432
|
|
|
357
433
|
```javascript
|
|
358
434
|
// ✅ Good - Use parameterized queries
|
|
359
|
-
await getCacheQuery('SELECT * FROM users WHERE id = ?', [userId]);
|
|
435
|
+
await getCacheQuery('SELECT * FROM users WHERE id = ?', [userId], `user:${userId}`);
|
|
360
436
|
|
|
361
437
|
// ❌ Bad - String interpolation
|
|
362
|
-
await getCacheQuery(`SELECT * FROM users WHERE id = ${userId}`);
|
|
438
|
+
await getCacheQuery(`SELECT * FROM users WHERE id = ${userId}`, [], `user:${userId}`);
|
|
363
439
|
```
|
|
364
440
|
|
|
441
|
+
- Set `DB_MULTIPLE_STATEMENTS=false` in new projects, so an injection cannot run extra statements.
|
|
442
|
+
- `bulkInsert` puts the table name and the keys of the first record into the SQL as-is (v2.9.1
|
|
443
|
+
rejects invalid names before anything is sent). Map request or CSV input onto a fixed list of
|
|
444
|
+
columns; never pass untrusted keys.
|
|
445
|
+
- Cache-name prefixes are Redis glob patterns: escape `*`, `?`, `[`, `]` and `\` in user input.
|
|
446
|
+
|
|
447
|
+
### Redis
|
|
448
|
+
|
|
449
|
+
- Redis is a trusted store: cached rows are returned as stored. Use AUTH, a private network, and a
|
|
450
|
+
`REDIS_VHOST` or Redis database of the application's own.
|
|
451
|
+
- `REDIS_VHOST` is per process. Do not change it per request; for tenants, pass the tenant's
|
|
452
|
+
`database` per call and put the tenant in explicit cache names.
|
|
453
|
+
|
|
454
|
+
### Error Details
|
|
455
|
+
|
|
456
|
+
mysql2 errors carry `err.sql` with the parameter values filled in. Do not send whole error objects to
|
|
457
|
+
logs or clients you do not trust.
|
|
458
|
+
|
|
365
459
|
### Connection Limits
|
|
366
460
|
|
|
367
461
|
```bash
|
|
@@ -385,27 +479,37 @@ mysql -u root -p
|
|
|
385
479
|
redis-cli ping
|
|
386
480
|
```
|
|
387
481
|
|
|
388
|
-
###
|
|
482
|
+
### Too Many Connections
|
|
389
483
|
|
|
390
484
|
```bash
|
|
391
|
-
# Error: Too many connections
|
|
392
|
-
#
|
|
485
|
+
# Error: Too many connections (ER_CON_COUNT_ERROR)
|
|
486
|
+
# Cause: all clients together exceed MySQL's max_connections (default 151)
|
|
487
|
+
# Solution: lower DB_CONNECTION_LIMIT so that instances x workers x limit + other clients
|
|
488
|
+
# stays below max_connections, or raise max_connections on the server
|
|
393
489
|
|
|
394
|
-
DB_CONNECTION_LIMIT=
|
|
490
|
+
DB_CONNECTION_LIMIT=30
|
|
395
491
|
```
|
|
396
492
|
|
|
493
|
+
When the pool itself is exhausted, requests wait for a free connection (`queuedRequests` in
|
|
494
|
+
`getPoolStats()`), or fail once more than `DB_QUEUE_LIMIT` are waiting.
|
|
495
|
+
|
|
397
496
|
### Timeout Issues
|
|
398
497
|
|
|
399
498
|
```bash
|
|
400
499
|
# Error: Query timeout exceeded
|
|
401
|
-
#
|
|
500
|
+
# Cause: a getCacheQueryWithTimeout() call took longer than its timeout option (default 30000 ms)
|
|
501
|
+
# Solution: pass a larger timeout, or optimize the query; DB_CONNECT_TIMEOUT does not affect it
|
|
502
|
+
```
|
|
402
503
|
|
|
403
|
-
|
|
504
|
+
```javascript
|
|
505
|
+
await getCacheQueryWithTimeout(sql, params, 'report:daily', { timeout: 60000 });
|
|
404
506
|
```
|
|
405
507
|
|
|
406
508
|
### Redis Outage Log Lines
|
|
407
509
|
|
|
408
510
|
```bash
|
|
511
|
+
# "Redis reconnecting... (attempt 3, delay 2000ms)"
|
|
512
|
+
# One line per reconnect attempt (v2.9.1) while Redis is unreachable.
|
|
409
513
|
# "Redis operation failed: Redis unavailable for 10234ms — serving cache misses without waiting until it recovers"
|
|
410
514
|
# Redis has been unreachable for REDIS_WAIT_TIMEOUT: cache reads/writes fall back immediately
|
|
411
515
|
# (once per outage). Restore the old wait-on-every-call behavior with REDIS_FAIL_FAST=false.
|
|
@@ -417,14 +521,18 @@ DB_CONNECT_TIMEOUT=30000
|
|
|
417
521
|
# Deletes that failed during the outage are being applied now that Redis is back.
|
|
418
522
|
```
|
|
419
523
|
|
|
524
|
+
Since v2.9.1 a line is also logged when more than 1000 invalidations are missed during an outage
|
|
525
|
+
(only the most recent 1000 are replayed), and a one-time warning when `REDIS_VHOST` changes at
|
|
526
|
+
runtime.
|
|
527
|
+
|
|
420
528
|
### Cache Issues
|
|
421
529
|
|
|
422
530
|
```bash
|
|
423
|
-
# Clear all cache
|
|
531
|
+
# Clear all cache (FLUSHALL deletes every key on that Redis server, not only sqlcache's)
|
|
424
532
|
redis-cli FLUSHALL
|
|
425
533
|
|
|
426
|
-
# Clear specific pattern
|
|
427
|
-
redis-cli --scan --pattern "users_*" | xargs redis-cli DEL
|
|
534
|
+
# Clear specific pattern (with REDIS_VHOST=my_app the keys start with "my_app:")
|
|
535
|
+
redis-cli --scan --pattern "my_app:users_*" | xargs redis-cli DEL
|
|
428
536
|
```
|
|
429
537
|
|
|
430
538
|
---
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Contributing to
|
|
1
|
+
# Contributing to sqlcache
|
|
2
2
|
|
|
3
3
|
Thank you for your interest in contributing! We welcome contributions from the community.
|
|
4
4
|
|
|
@@ -16,7 +16,7 @@ Thank you for your interest in contributing! We welcome contributions from the c
|
|
|
16
16
|
|
|
17
17
|
### Suggesting Features
|
|
18
18
|
|
|
19
|
-
1. Check existing issues
|
|
19
|
+
1. Check existing issues
|
|
20
20
|
2. Create a new issue with:
|
|
21
21
|
- Clear use case description
|
|
22
22
|
- Why this feature is needed
|
|
@@ -29,7 +29,7 @@ Thank you for your interest in contributing! We welcome contributions from the c
|
|
|
29
29
|
```bash
|
|
30
30
|
git clone https://github.com/YOUR_USERNAME/sqlcache.git
|
|
31
31
|
cd sqlcache
|
|
32
|
-
npm
|
|
32
|
+
npm ci
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
2. **Create a feature branch**
|
|
@@ -47,6 +47,7 @@ Thank you for your interest in contributing! We welcome contributions from the c
|
|
|
47
47
|
```bash
|
|
48
48
|
npm test
|
|
49
49
|
npm run coverage
|
|
50
|
+
npx -p typescript@5 tsc -p test/types # type definitions check
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
5. **Commit your changes**
|
|
@@ -68,9 +69,8 @@ Thank you for your interest in contributing! We welcome contributions from the c
|
|
|
68
69
|
|
|
69
70
|
### Prerequisites
|
|
70
71
|
|
|
71
|
-
- Node.js 14+
|
|
72
|
-
- MySQL 5.7+
|
|
73
|
-
- Redis 3+
|
|
72
|
+
- Node.js 14+ for the package; the test suite runs in CI on Node 18, 20, 22, 24 and 26
|
|
73
|
+
- For integration tests: MySQL 5.7+ (CI uses 8.0) and Redis 2.8+ (CI uses 7), in Docker
|
|
74
74
|
|
|
75
75
|
### Local Setup
|
|
76
76
|
|
|
@@ -79,24 +79,24 @@ Thank you for your interest in contributing! We welcome contributions from the c
|
|
|
79
79
|
git clone https://github.com/hayatialikeles/sqlcache.git
|
|
80
80
|
cd sqlcache
|
|
81
81
|
|
|
82
|
-
# Install dependencies
|
|
83
|
-
npm
|
|
82
|
+
# Install dependencies (node_modules is not committed)
|
|
83
|
+
npm ci
|
|
84
84
|
|
|
85
|
-
#
|
|
86
|
-
cp .env.example .env
|
|
87
|
-
|
|
88
|
-
# Edit .env with your database credentials
|
|
89
|
-
nano .env
|
|
90
|
-
|
|
91
|
-
# Run tests
|
|
85
|
+
# Unit tests (no MySQL or Redis needed)
|
|
92
86
|
npm test
|
|
93
87
|
|
|
94
88
|
# Run with coverage
|
|
95
89
|
npm run coverage
|
|
90
|
+
|
|
91
|
+
# Type definitions check, as run in CI
|
|
92
|
+
npx -p typescript@5 tsc -p test/types
|
|
96
93
|
```
|
|
97
94
|
|
|
98
95
|
### Environment Configuration
|
|
99
96
|
|
|
97
|
+
Unit and integration tests set the variables they need themselves. To try the package against your
|
|
98
|
+
own servers, copy `.env.example` to `.env` and edit it:
|
|
99
|
+
|
|
100
100
|
```bash
|
|
101
101
|
# Database
|
|
102
102
|
DB_HOST=localhost
|
|
@@ -111,7 +111,8 @@ REDIS_PORT=6379
|
|
|
111
111
|
REDIS_PASSWORD=
|
|
112
112
|
|
|
113
113
|
# Features
|
|
114
|
-
CORE_AUTO_FEATURES=true
|
|
114
|
+
CORE_AUTO_FEATURES=true # automatic cache keys
|
|
115
|
+
CORE_AUTO_INVALIDATION=true # automatic invalidation
|
|
115
116
|
```
|
|
116
117
|
|
|
117
118
|
## ✅ Testing Guidelines
|
|
@@ -156,8 +157,20 @@ npm run coverage
|
|
|
156
157
|
|
|
157
158
|
# Run specific test file
|
|
158
159
|
npx mocha test/transaction.test.js
|
|
160
|
+
|
|
161
|
+
# Integration tests: Redis on 127.0.0.1:6399, MySQL on 127.0.0.1:3399 (root / testpass / testdb).
|
|
162
|
+
# Container commands use DOCKER_BIN (default: docker) and REDIS_CONTAINER (default: test-redis).
|
|
163
|
+
INTEGRATION=1 npm test
|
|
164
|
+
|
|
165
|
+
# Chaos suites (Docker containers test-redis / test-mysql)
|
|
166
|
+
INTEGRATION=1 node test/chaos.test.js
|
|
167
|
+
INTEGRATION=1 node test/chaos-mysql.test.js
|
|
159
168
|
```
|
|
160
169
|
|
|
170
|
+
CI (`.github/workflows/ci.yml`) runs the unit tests on Node 18, 20, 22, 24 and 26, once more against
|
|
171
|
+
the minimum dependency versions (mysql2 3.23.1, redis 3.1.2, dotenv 16.0.3), the type definitions
|
|
172
|
+
check, and the integration tests against MySQL 8.0 and Redis 7.
|
|
173
|
+
|
|
161
174
|
## 📚 Documentation Guidelines
|
|
162
175
|
|
|
163
176
|
### Code Comments
|
|
@@ -223,12 +236,10 @@ Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
|
|
223
236
|
|
|
224
237
|
## 🐛 Debugging
|
|
225
238
|
|
|
226
|
-
###
|
|
239
|
+
### Logs
|
|
227
240
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
process.env.DEBUG = 'mysql-cache:*';
|
|
231
|
-
```
|
|
241
|
+
sqlcache has no debug switch. It logs connection events (Redis connects, reconnects and outages,
|
|
242
|
+
pool shutdown) with `console.log` / `console.error`.
|
|
232
243
|
|
|
233
244
|
### Common Issues
|
|
234
245
|
|
|
@@ -243,17 +254,17 @@ redis-cli ping
|
|
|
243
254
|
|
|
244
255
|
**Test Failures**
|
|
245
256
|
```bash
|
|
246
|
-
# Clear
|
|
247
|
-
redis-cli FLUSHALL
|
|
257
|
+
# Clear the test Redis (integration tests use port 6399)
|
|
258
|
+
redis-cli -p 6399 FLUSHALL
|
|
248
259
|
|
|
249
|
-
#
|
|
250
|
-
mysql -u root -p test_db < schema.sql
|
|
260
|
+
# The integration tests create and drop their own table (test_users) in testdb
|
|
251
261
|
```
|
|
252
262
|
|
|
253
263
|
## 📋 Checklist Before PR
|
|
254
264
|
|
|
255
265
|
- [ ] All tests pass (`npm test`)
|
|
256
266
|
- [ ] Coverage is >95% (`npm run coverage`)
|
|
267
|
+
- [ ] Type definitions check passes (`npx -p typescript@5 tsc -p test/types`)
|
|
257
268
|
- [ ] Documentation updated
|
|
258
269
|
- [ ] TypeScript definitions updated
|
|
259
270
|
- [ ] Commit messages follow convention
|
|
@@ -265,7 +276,7 @@ mysql -u root -p test_db < schema.sql
|
|
|
265
276
|
(For maintainers only)
|
|
266
277
|
|
|
267
278
|
1. Update version in `package.json`
|
|
268
|
-
2.
|
|
279
|
+
2. Describe the changes in `docs/MIGRATION.md`
|
|
269
280
|
3. Run tests: `npm test`
|
|
270
281
|
4. Commit: `git commit -m "chore: release v2.x.x"`
|
|
271
282
|
5. Tag: `git tag v2.x.x`
|
|
@@ -274,9 +285,8 @@ mysql -u root -p test_db < schema.sql
|
|
|
274
285
|
|
|
275
286
|
## 💬 Communication
|
|
276
287
|
|
|
277
|
-
- **Issues**: Bug reports
|
|
278
|
-
- **
|
|
279
|
-
- **Email**: For security issues only
|
|
288
|
+
- **Issues**: Bug reports, feature requests and questions
|
|
289
|
+
- **Security issues**: please do not open a public issue; contact the maintainer privately
|
|
280
290
|
|
|
281
291
|
## 📜 License
|
|
282
292
|
|