layercache 1.2.0 → 1.2.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/README.md +71 -5
- package/dist/chunk-46UH7LNM.js +312 -0
- package/dist/{chunk-BWM4MU2X.js → chunk-GF47Y3XR.js} +13 -38
- package/dist/chunk-ZMDB5KOK.js +159 -0
- package/dist/cli.cjs +121 -21
- package/dist/cli.js +57 -2
- package/dist/edge-C1sBhTfv.d.cts +667 -0
- package/dist/edge-C1sBhTfv.d.ts +667 -0
- package/dist/edge.cjs +399 -0
- package/dist/edge.d.cts +2 -0
- package/dist/edge.d.ts +2 -0
- package/dist/edge.js +14 -0
- package/dist/index.cjs +969 -195
- package/dist/index.d.cts +43 -567
- package/dist/index.d.ts +43 -567
- package/dist/index.js +849 -496
- package/package.json +7 -2
- package/packages/nestjs/dist/index.cjs +913 -375
- package/packages/nestjs/dist/index.d.cts +75 -0
- package/packages/nestjs/dist/index.d.ts +75 -0
- package/packages/nestjs/dist/index.js +901 -373
package/dist/cli.cjs
CHANGED
|
@@ -36,41 +36,80 @@ __export(cli_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(cli_exports);
|
|
37
37
|
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
38
38
|
|
|
39
|
+
// src/internal/StoredValue.ts
|
|
40
|
+
function isStoredValueEnvelope(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && "__layercache" in value && value.__layercache === 1 && "kind" in value;
|
|
42
|
+
}
|
|
43
|
+
function resolveStoredValue(stored, now = Date.now()) {
|
|
44
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
45
|
+
return { state: "fresh", value: stored, stored };
|
|
46
|
+
}
|
|
47
|
+
if (stored.freshUntil === null || stored.freshUntil > now) {
|
|
48
|
+
return { state: "fresh", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
49
|
+
}
|
|
50
|
+
if (stored.staleUntil !== null && stored.staleUntil > now) {
|
|
51
|
+
return { state: "stale-while-revalidate", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
52
|
+
}
|
|
53
|
+
if (stored.errorUntil !== null && stored.errorUntil > now) {
|
|
54
|
+
return { state: "stale-if-error", value: unwrapStoredValue(stored), stored, envelope: stored };
|
|
55
|
+
}
|
|
56
|
+
return { state: "expired", value: null, stored, envelope: stored };
|
|
57
|
+
}
|
|
58
|
+
function unwrapStoredValue(stored) {
|
|
59
|
+
if (!isStoredValueEnvelope(stored)) {
|
|
60
|
+
return stored;
|
|
61
|
+
}
|
|
62
|
+
if (stored.kind === "empty") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return stored.value ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
// src/invalidation/PatternMatcher.ts
|
|
40
69
|
var PatternMatcher = class _PatternMatcher {
|
|
41
70
|
/**
|
|
42
71
|
* Tests whether a glob-style pattern matches a value.
|
|
43
72
|
* Supports `*` (any sequence of characters) and `?` (any single character).
|
|
44
|
-
* Uses a
|
|
73
|
+
* Uses a two-pointer algorithm to avoid ReDoS vulnerabilities and
|
|
74
|
+
* quadratic memory usage on long patterns/keys.
|
|
45
75
|
*/
|
|
46
76
|
static matches(pattern, value) {
|
|
47
77
|
return _PatternMatcher.matchLinear(pattern, value);
|
|
48
78
|
}
|
|
49
79
|
/**
|
|
50
|
-
* Linear-time glob matching
|
|
51
|
-
* Avoids catastrophic backtracking that RegExp-based glob matching can cause.
|
|
80
|
+
* Linear-time glob matching with O(1) extra memory.
|
|
52
81
|
*/
|
|
53
82
|
static matchLinear(pattern, value) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
let patternIndex = 0;
|
|
84
|
+
let valueIndex = 0;
|
|
85
|
+
let starIndex = -1;
|
|
86
|
+
let backtrackValueIndex = 0;
|
|
87
|
+
while (valueIndex < value.length) {
|
|
88
|
+
const patternChar = pattern[patternIndex];
|
|
89
|
+
const valueChar = value[valueIndex];
|
|
90
|
+
if (patternChar === "*" && patternIndex < pattern.length) {
|
|
91
|
+
starIndex = patternIndex;
|
|
92
|
+
patternIndex += 1;
|
|
93
|
+
backtrackValueIndex = valueIndex;
|
|
94
|
+
continue;
|
|
61
95
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
if (patternChar === "?" || patternChar === valueChar) {
|
|
97
|
+
patternIndex += 1;
|
|
98
|
+
valueIndex += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (starIndex !== -1) {
|
|
102
|
+
patternIndex = starIndex + 1;
|
|
103
|
+
backtrackValueIndex += 1;
|
|
104
|
+
valueIndex = backtrackValueIndex;
|
|
105
|
+
continue;
|
|
71
106
|
}
|
|
107
|
+
return false;
|
|
72
108
|
}
|
|
73
|
-
|
|
109
|
+
while (patternIndex < pattern.length && pattern[patternIndex] === "*") {
|
|
110
|
+
patternIndex += 1;
|
|
111
|
+
}
|
|
112
|
+
return patternIndex === pattern.length;
|
|
74
113
|
}
|
|
75
114
|
};
|
|
76
115
|
|
|
@@ -118,6 +157,16 @@ var RedisTagIndex = class {
|
|
|
118
157
|
async keysForTag(tag) {
|
|
119
158
|
return this.client.smembers(this.tagKeysKey(tag));
|
|
120
159
|
}
|
|
160
|
+
async keysForPrefix(prefix) {
|
|
161
|
+
const matches = [];
|
|
162
|
+
let cursor = "0";
|
|
163
|
+
do {
|
|
164
|
+
const [nextCursor, keys] = await this.client.sscan(this.knownKeysKey(), cursor, "COUNT", this.scanCount);
|
|
165
|
+
cursor = nextCursor;
|
|
166
|
+
matches.push(...keys.filter((key) => key.startsWith(prefix)));
|
|
167
|
+
} while (cursor !== "0");
|
|
168
|
+
return matches;
|
|
169
|
+
}
|
|
121
170
|
async tagsForKey(key) {
|
|
122
171
|
return this.client.smembers(this.keyTagsKey(key));
|
|
123
172
|
}
|
|
@@ -228,6 +277,31 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
228
277
|
`);
|
|
229
278
|
return;
|
|
230
279
|
}
|
|
280
|
+
if (args.command === "inspect") {
|
|
281
|
+
if (!args.key) {
|
|
282
|
+
throw new Error("inspect requires --key <key>.");
|
|
283
|
+
}
|
|
284
|
+
const payload = await redis.getBuffer(args.key);
|
|
285
|
+
const ttl = await redis.ttl(args.key);
|
|
286
|
+
const decoded = decodeInspectablePayload(payload);
|
|
287
|
+
process.stdout.write(
|
|
288
|
+
`${JSON.stringify(
|
|
289
|
+
{
|
|
290
|
+
key: args.key,
|
|
291
|
+
exists: payload !== null,
|
|
292
|
+
ttlSeconds: ttl >= 0 ? ttl : null,
|
|
293
|
+
sizeBytes: payload?.byteLength ?? 0,
|
|
294
|
+
isEnvelope: isStoredValueEnvelope(decoded),
|
|
295
|
+
state: payload === null ? null : resolveStoredValue(decoded).state,
|
|
296
|
+
preview: summarizeInspectableValue(decoded)
|
|
297
|
+
},
|
|
298
|
+
null,
|
|
299
|
+
2
|
|
300
|
+
)}
|
|
301
|
+
`
|
|
302
|
+
);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
231
305
|
printUsage();
|
|
232
306
|
process.exitCode = 1;
|
|
233
307
|
} catch (error) {
|
|
@@ -268,6 +342,9 @@ function parseArgs(argv) {
|
|
|
268
342
|
} else if (token === "--tag") {
|
|
269
343
|
parsed.tag = value;
|
|
270
344
|
index += 1;
|
|
345
|
+
} else if (token === "--key") {
|
|
346
|
+
parsed.key = value;
|
|
347
|
+
index += 1;
|
|
271
348
|
} else if (token === "--tag-index-prefix") {
|
|
272
349
|
parsed.tagIndexPrefix = value;
|
|
273
350
|
index += 1;
|
|
@@ -294,9 +371,32 @@ async function scanKeys(redis, pattern) {
|
|
|
294
371
|
}
|
|
295
372
|
function printUsage() {
|
|
296
373
|
process.stdout.write(
|
|
297
|
-
"Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
|
|
374
|
+
"Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
|
|
298
375
|
);
|
|
299
376
|
}
|
|
377
|
+
function decodeInspectablePayload(payload) {
|
|
378
|
+
if (payload === null) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const text = payload.toString("utf8");
|
|
382
|
+
try {
|
|
383
|
+
return JSON.parse(text);
|
|
384
|
+
} catch {
|
|
385
|
+
return text.length > 256 ? `${text.slice(0, 256)}...` : text;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function summarizeInspectableValue(value) {
|
|
389
|
+
if (isStoredValueEnvelope(value)) {
|
|
390
|
+
return {
|
|
391
|
+
kind: value.kind,
|
|
392
|
+
value: value.value,
|
|
393
|
+
freshUntil: value.freshUntil,
|
|
394
|
+
staleUntil: value.staleUntil,
|
|
395
|
+
errorUntil: value.errorUntil
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
300
400
|
if (process.argv[1]?.includes("cli.")) {
|
|
301
401
|
void main();
|
|
302
402
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
RedisTagIndex
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GF47Y3XR.js";
|
|
5
|
+
import {
|
|
6
|
+
isStoredValueEnvelope,
|
|
7
|
+
resolveStoredValue
|
|
8
|
+
} from "./chunk-ZMDB5KOK.js";
|
|
5
9
|
|
|
6
10
|
// src/cli.ts
|
|
7
11
|
import Redis from "ioredis";
|
|
@@ -65,6 +69,31 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
65
69
|
`);
|
|
66
70
|
return;
|
|
67
71
|
}
|
|
72
|
+
if (args.command === "inspect") {
|
|
73
|
+
if (!args.key) {
|
|
74
|
+
throw new Error("inspect requires --key <key>.");
|
|
75
|
+
}
|
|
76
|
+
const payload = await redis.getBuffer(args.key);
|
|
77
|
+
const ttl = await redis.ttl(args.key);
|
|
78
|
+
const decoded = decodeInspectablePayload(payload);
|
|
79
|
+
process.stdout.write(
|
|
80
|
+
`${JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
key: args.key,
|
|
83
|
+
exists: payload !== null,
|
|
84
|
+
ttlSeconds: ttl >= 0 ? ttl : null,
|
|
85
|
+
sizeBytes: payload?.byteLength ?? 0,
|
|
86
|
+
isEnvelope: isStoredValueEnvelope(decoded),
|
|
87
|
+
state: payload === null ? null : resolveStoredValue(decoded).state,
|
|
88
|
+
preview: summarizeInspectableValue(decoded)
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2
|
|
92
|
+
)}
|
|
93
|
+
`
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
68
97
|
printUsage();
|
|
69
98
|
process.exitCode = 1;
|
|
70
99
|
} catch (error) {
|
|
@@ -105,6 +134,9 @@ function parseArgs(argv) {
|
|
|
105
134
|
} else if (token === "--tag") {
|
|
106
135
|
parsed.tag = value;
|
|
107
136
|
index += 1;
|
|
137
|
+
} else if (token === "--key") {
|
|
138
|
+
parsed.key = value;
|
|
139
|
+
index += 1;
|
|
108
140
|
} else if (token === "--tag-index-prefix") {
|
|
109
141
|
parsed.tagIndexPrefix = value;
|
|
110
142
|
index += 1;
|
|
@@ -131,9 +163,32 @@ async function scanKeys(redis, pattern) {
|
|
|
131
163
|
}
|
|
132
164
|
function printUsage() {
|
|
133
165
|
process.stdout.write(
|
|
134
|
-
"Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
|
|
166
|
+
"Usage:\n layercache stats --redis <url> [--pattern <glob>]\n layercache keys --redis <url> [--pattern <glob>]\n layercache inspect --redis <url> --key <key>\n layercache invalidate --redis <url> [--pattern <glob> | --tag <tag>] [--tag-index-prefix <prefix>]\n\nOptions:\n --redis <url> Redis connection URL (e.g. redis://localhost:6379)\n --pattern <glob> Glob pattern to filter keys (default: *)\n --key <key> Exact cache key to inspect\n --tag <tag> Invalidate by tag name\n --tag-index-prefix <prefix> Redis key prefix for tag index (default: layercache:tag-index)\n"
|
|
135
167
|
);
|
|
136
168
|
}
|
|
169
|
+
function decodeInspectablePayload(payload) {
|
|
170
|
+
if (payload === null) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const text = payload.toString("utf8");
|
|
174
|
+
try {
|
|
175
|
+
return JSON.parse(text);
|
|
176
|
+
} catch {
|
|
177
|
+
return text.length > 256 ? `${text.slice(0, 256)}...` : text;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function summarizeInspectableValue(value) {
|
|
181
|
+
if (isStoredValueEnvelope(value)) {
|
|
182
|
+
return {
|
|
183
|
+
kind: value.kind,
|
|
184
|
+
value: value.value,
|
|
185
|
+
freshUntil: value.freshUntil,
|
|
186
|
+
staleUntil: value.staleUntil,
|
|
187
|
+
errorUntil: value.errorUntil
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
137
192
|
if (process.argv[1]?.includes("cli.")) {
|
|
138
193
|
void main();
|
|
139
194
|
}
|