@leejpsd/nextjs-cache-handler 0.1.0-rc.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/CHANGELOG.md +51 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/cache-components/index.cjs +1053 -0
- package/dist/cache-components/index.cjs.map +1 -0
- package/dist/cache-components/index.d.cts +85 -0
- package/dist/cache-components/index.d.ts +85 -0
- package/dist/cache-components/index.js +1049 -0
- package/dist/cache-components/index.js.map +1 -0
- package/dist/incremental/index.cjs +734 -0
- package/dist/incremental/index.cjs.map +1 -0
- package/dist/incremental/index.d.cts +131 -0
- package/dist/incremental/index.d.ts +131 -0
- package/dist/incremental/index.js +731 -0
- package/dist/incremental/index.js.map +1 -0
- package/dist/index.cjs +1408 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +1397 -0
- package/dist/index.js.map +1 -0
- package/dist/ops/index.cjs +19 -0
- package/dist/ops/index.cjs.map +1 -0
- package/dist/ops/index.d.cts +22 -0
- package/dist/ops/index.d.ts +22 -0
- package/dist/ops/index.js +17 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/shared/client/adapter-ioredis.cjs +130 -0
- package/dist/shared/client/adapter-ioredis.cjs.map +1 -0
- package/dist/shared/client/adapter-ioredis.d.cts +22 -0
- package/dist/shared/client/adapter-ioredis.d.ts +22 -0
- package/dist/shared/client/adapter-ioredis.js +125 -0
- package/dist/shared/client/adapter-ioredis.js.map +1 -0
- package/dist/shared/client/adapter-redis.cjs +32 -0
- package/dist/shared/client/adapter-redis.cjs.map +1 -0
- package/dist/shared/client/adapter-redis.d.cts +19 -0
- package/dist/shared/client/adapter-redis.d.ts +19 -0
- package/dist/shared/client/adapter-redis.js +29 -0
- package/dist/shared/client/adapter-redis.js.map +1 -0
- package/dist/types-CgriT7My.d.cts +193 -0
- package/dist/types-CgriT7My.d.ts +193 -0
- package/package.json +178 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/shared/client/adapter-ioredis.ts
|
|
9
|
+
function adapt(client, isClusterClient = false) {
|
|
10
|
+
return {
|
|
11
|
+
get isOpen() {
|
|
12
|
+
return client.status === "ready";
|
|
13
|
+
},
|
|
14
|
+
connect: async () => {
|
|
15
|
+
if (client.status !== "ready" && client.status !== "connecting") {
|
|
16
|
+
await client.connect();
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
get: (k) => client.get(k),
|
|
20
|
+
set: (k, v, opts) => opts?.EX !== void 0 ? client.set(k, v, "EX", opts.EX) : client.set(k, v),
|
|
21
|
+
del: async (keys) => {
|
|
22
|
+
const arr = Array.isArray(keys) ? keys : [keys];
|
|
23
|
+
if (arr.length === 0) return 0;
|
|
24
|
+
return client.del(...arr);
|
|
25
|
+
},
|
|
26
|
+
sAdd: async (k, m) => {
|
|
27
|
+
const arr = Array.isArray(m) ? m : [m];
|
|
28
|
+
if (arr.length === 0) return 0;
|
|
29
|
+
return client.sadd(k, ...arr);
|
|
30
|
+
},
|
|
31
|
+
sMembers: (k) => client.smembers(k),
|
|
32
|
+
expire: async (k, s) => {
|
|
33
|
+
const r = await client.expire(k, s);
|
|
34
|
+
return r;
|
|
35
|
+
},
|
|
36
|
+
mGet: async (keys) => {
|
|
37
|
+
if (keys.length === 0) return [];
|
|
38
|
+
return client.mget(...keys);
|
|
39
|
+
},
|
|
40
|
+
eval: (script, opts) => client.eval(
|
|
41
|
+
script,
|
|
42
|
+
opts.keys.length,
|
|
43
|
+
...opts.keys,
|
|
44
|
+
...opts.arguments
|
|
45
|
+
),
|
|
46
|
+
evalSha: (sha, opts) => client.evalsha(
|
|
47
|
+
sha,
|
|
48
|
+
opts.keys.length,
|
|
49
|
+
...opts.keys,
|
|
50
|
+
...opts.arguments
|
|
51
|
+
),
|
|
52
|
+
scriptLoad: async (script) => await client.script("LOAD", script),
|
|
53
|
+
scanIterator: async function* ({ MATCH, COUNT }) {
|
|
54
|
+
if (isClusterClient) {
|
|
55
|
+
const cluster = client;
|
|
56
|
+
for (const node of cluster.nodes("master")) {
|
|
57
|
+
let cursor = "0";
|
|
58
|
+
do {
|
|
59
|
+
const [next, batch] = await node.scan(
|
|
60
|
+
cursor,
|
|
61
|
+
"MATCH",
|
|
62
|
+
MATCH,
|
|
63
|
+
"COUNT",
|
|
64
|
+
COUNT ?? 100
|
|
65
|
+
);
|
|
66
|
+
cursor = next;
|
|
67
|
+
if (batch.length > 0) yield batch;
|
|
68
|
+
} while (cursor !== "0");
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
const redis = client;
|
|
72
|
+
let cursor = "0";
|
|
73
|
+
do {
|
|
74
|
+
const [next, batch] = await redis.scan(
|
|
75
|
+
cursor,
|
|
76
|
+
"MATCH",
|
|
77
|
+
MATCH,
|
|
78
|
+
"COUNT",
|
|
79
|
+
COUNT ?? 100
|
|
80
|
+
);
|
|
81
|
+
cursor = next;
|
|
82
|
+
if (batch.length > 0) yield batch;
|
|
83
|
+
} while (cursor !== "0");
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
ping: () => client.ping(),
|
|
87
|
+
on: (event, listener) => {
|
|
88
|
+
client.on(event, listener);
|
|
89
|
+
return client;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function adaptIoredis(client) {
|
|
94
|
+
return adapt(client, false);
|
|
95
|
+
}
|
|
96
|
+
function adaptCluster(cluster) {
|
|
97
|
+
return adapt(cluster, true);
|
|
98
|
+
}
|
|
99
|
+
function createIoredisClient(config) {
|
|
100
|
+
const IORedis = __require("ioredis");
|
|
101
|
+
const Ctor = IORedis.default ?? IORedis;
|
|
102
|
+
return new Ctor(config.url, {
|
|
103
|
+
password: config.password,
|
|
104
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
105
|
+
tls: config.tls ? {} : void 0,
|
|
106
|
+
lazyConnect: true,
|
|
107
|
+
maxRetriesPerRequest: 1
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function createIoredisCluster(config) {
|
|
111
|
+
const IORedis = __require("ioredis");
|
|
112
|
+
const ClusterCtor = IORedis.Cluster ?? IORedis.default.Cluster;
|
|
113
|
+
return new ClusterCtor(config.nodes, {
|
|
114
|
+
redisOptions: {
|
|
115
|
+
password: config.password,
|
|
116
|
+
tls: config.tls ? {} : void 0
|
|
117
|
+
},
|
|
118
|
+
enableReadyCheck: true,
|
|
119
|
+
lazyConnect: true
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { adaptCluster, adaptIoredis, createIoredisClient, createIoredisCluster };
|
|
124
|
+
//# sourceMappingURL=adapter-ioredis.js.map
|
|
125
|
+
//# sourceMappingURL=adapter-ioredis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/shared/client/adapter-ioredis.ts"],"names":[],"mappings":"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","file":"adapter-ioredis.js","sourcesContent":["/**\n * Adapter for `ioredis` (and `ioredis.Cluster`).\n *\n * ioredis uses snake_case method names (`sadd`, `smembers`, `mget`) and a\n * stream-based scan. We normalize both into the camelCase / async-iterable\n * `RedisClientLike` contract so the handlers don't need to know which client\n * is underneath.\n */\n\nimport type { Cluster, Redis } from \"ioredis\";\n\nimport type { RedisClientConfig, RedisClientLike } from \"../../types.js\";\n\ntype AnyRedis = Redis | Cluster;\n\nfunction adapt(client: AnyRedis, isClusterClient = false): RedisClientLike {\n return {\n get isOpen() {\n // ioredis exposes a `status` string. Both single-node and cluster\n // report \"ready\" once connected.\n return client.status === \"ready\";\n },\n connect: async () => {\n if (client.status !== \"ready\" && client.status !== \"connecting\") {\n await client.connect();\n }\n },\n get: (k) => client.get(k),\n set: (k, v, opts) =>\n opts?.EX !== undefined\n ? client.set(k, v, \"EX\", opts.EX)\n : client.set(k, v),\n del: async (keys) => {\n const arr = Array.isArray(keys) ? keys : [keys];\n if (arr.length === 0) return 0;\n return client.del(...arr);\n },\n sAdd: async (k, m) => {\n const arr = Array.isArray(m) ? m : [m];\n if (arr.length === 0) return 0;\n return client.sadd(k, ...arr);\n },\n sMembers: (k) => client.smembers(k),\n expire: async (k, s) => {\n const r = await client.expire(k, s);\n return r;\n },\n mGet: async (keys) => {\n if (keys.length === 0) return [];\n return client.mget(...keys);\n },\n eval: (script, opts) =>\n client.eval(\n script,\n opts.keys.length,\n ...opts.keys,\n ...opts.arguments\n ) as Promise<unknown>,\n evalSha: (sha, opts) =>\n client.evalsha(\n sha,\n opts.keys.length,\n ...opts.keys,\n ...opts.arguments\n ) as Promise<unknown>,\n scriptLoad: async (script) =>\n (await client.script(\"LOAD\", script)) as string,\n scanIterator: async function* ({ MATCH, COUNT }) {\n // Cluster does not support a single SCAN cursor across slots; use\n // ioredis's per-node iterator there. Single-node: standard SCAN cursor.\n if (isClusterClient) {\n const cluster = client as Cluster;\n for (const node of cluster.nodes(\"master\")) {\n let cursor = \"0\";\n do {\n const [next, batch]: [string, string[]] = await node.scan(\n cursor,\n \"MATCH\",\n MATCH,\n \"COUNT\",\n COUNT ?? 100\n );\n cursor = next;\n if (batch.length > 0) yield batch;\n } while (cursor !== \"0\");\n }\n } else {\n const redis = client as Redis;\n let cursor = \"0\";\n do {\n const [next, batch]: [string, string[]] = await redis.scan(\n cursor,\n \"MATCH\",\n MATCH,\n \"COUNT\",\n COUNT ?? 100\n );\n cursor = next;\n if (batch.length > 0) yield batch;\n } while (cursor !== \"0\");\n }\n },\n ping: () => client.ping(),\n on: (event, listener) => {\n client.on(event, listener);\n return client;\n },\n };\n}\n\nexport function adaptIoredis(client: Redis): RedisClientLike {\n return adapt(client, false);\n}\n\nexport function adaptCluster(cluster: Cluster): RedisClientLike {\n return adapt(cluster, true);\n}\n\nexport function createIoredisClient(\n config: Extract<RedisClientConfig, { type: \"ioredis\" }>\n): Redis {\n \n const IORedis = require(\"ioredis\") as typeof import(\"ioredis\");\n // ioredis exports the class as both `default` and a named property.\n // Hitting the default works for both ESM and CJS consumers.\n const Ctor = IORedis.default ?? (IORedis as unknown as typeof import(\"ioredis\").default);\n return new Ctor(config.url, {\n password: config.password,\n connectTimeout: config.connectTimeout ?? 1500,\n tls: config.tls ? {} : undefined,\n lazyConnect: true,\n maxRetriesPerRequest: 1,\n });\n}\n\nexport function createIoredisCluster(\n config: Extract<RedisClientConfig, { type: \"cluster\" }>\n): Cluster {\n \n const IORedis = require(\"ioredis\") as typeof import(\"ioredis\");\n const ClusterCtor =\n IORedis.Cluster ??\n (IORedis.default as unknown as { Cluster: typeof import(\"ioredis\").Cluster })\n .Cluster;\n return new ClusterCtor(config.nodes, {\n redisOptions: {\n password: config.password,\n tls: config.tls ? {} : undefined,\n },\n enableReadyCheck: true,\n lazyConnect: true,\n });\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// src/shared/client/adapter-redis.ts
|
|
11
|
+
function adaptRedisV5(client) {
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
14
|
+
function createRedisV5Client(config) {
|
|
15
|
+
const { createClient } = __require("redis");
|
|
16
|
+
const useTls = config.tls ?? config.url.startsWith("rediss://");
|
|
17
|
+
const opts = {
|
|
18
|
+
url: config.url,
|
|
19
|
+
socket: {
|
|
20
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
21
|
+
reconnectStrategy: false,
|
|
22
|
+
...useTls ? { tls: true } : {}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
if (config.password !== void 0) opts.password = config.password;
|
|
26
|
+
return createClient(opts);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.adaptRedisV5 = adaptRedisV5;
|
|
30
|
+
exports.createRedisV5Client = createRedisV5Client;
|
|
31
|
+
//# sourceMappingURL=adapter-redis.cjs.map
|
|
32
|
+
//# sourceMappingURL=adapter-redis.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/shared/client/adapter-redis.ts"],"names":[],"mappings":";;;;;;;;;;AAcO,SAAS,aAAa,MAAA,EAA0C;AACrE,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,oBACd,MAAA,EACiB;AAKjB,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,SAAA,CAAQ,OAAO,CAAA;AACxC,EAAA,MAAM,SAAS,MAAA,CAAO,GAAA,IAAO,MAAA,CAAO,GAAA,CAAI,WAAW,WAAW,CAAA;AAM9D,EAAA,MAAM,IAAA,GAAY;AAAA,IAChB,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ,MAAA,EAAQ;AAAA,MACN,cAAA,EAAgB,OAAO,cAAA,IAAkB,IAAA;AAAA,MACzC,iBAAA,EAAmB,KAAA;AAAA,MACnB,GAAI,MAAA,GAAS,EAAE,GAAA,EAAK,IAAA,KAAS;AAAC;AAChC,GACF;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,KAAa,MAAA,EAAW,IAAA,CAAK,WAAW,MAAA,CAAO,QAAA;AAE1D,EAAA,OAAO,aAAa,IAAI,CAAA;AAC1B","file":"adapter-redis.cjs","sourcesContent":["/**\n * Adapter for `redis@5` (the official `node-redis` client).\n *\n * v5 already uses camelCase methods that match `RedisClientLike` almost\n * exactly, so this adapter is mostly a type assertion plus the\n * `createRedisV5Client` factory that codifies the \"1.5s connectTimeout,\n * reconnectStrategy: false\" defaults validated in production by the reference\n * implementation.\n */\n\nimport type { RedisClientType } from \"redis\";\n\nimport type { RedisClientConfig, RedisClientLike } from \"../../types.js\";\n\nexport function adaptRedisV5(client: RedisClientType): RedisClientLike {\n return client as unknown as RedisClientLike;\n}\n\nexport function createRedisV5Client(\n config: Extract<RedisClientConfig, { type: \"redis\" }>\n): RedisClientType {\n // Lazy require so consumers using ioredis only don't pay the cost.\n // We do `require` instead of `await import` here because the parent\n // `buildClient` is already in an async context that handles awaiting.\n \n const { createClient } = require(\"redis\") as typeof import(\"redis\");\n const useTls = config.tls ?? config.url.startsWith(\"rediss://\");\n\n // redis@5 has a discriminated socket union (TCP / TLS / IPC) and disallows\n // explicit `undefined` under exactOptionalPropertyTypes. Build the options\n // object incrementally so optional fields stay absent when not set.\n \n const opts: any = {\n url: config.url,\n socket: {\n connectTimeout: config.connectTimeout ?? 1500,\n reconnectStrategy: false,\n ...(useTls ? { tls: true } : {}),\n },\n };\n if (config.password !== undefined) opts.password = config.password;\n\n return createClient(opts) as RedisClientType;\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RedisClientType } from 'redis';
|
|
2
|
+
import { R as RedisClientLike, a as RedisClientConfig } from '../../types-CgriT7My.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adapter for `redis@5` (the official `node-redis` client).
|
|
6
|
+
*
|
|
7
|
+
* v5 already uses camelCase methods that match `RedisClientLike` almost
|
|
8
|
+
* exactly, so this adapter is mostly a type assertion plus the
|
|
9
|
+
* `createRedisV5Client` factory that codifies the "1.5s connectTimeout,
|
|
10
|
+
* reconnectStrategy: false" defaults validated in production by the reference
|
|
11
|
+
* implementation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare function adaptRedisV5(client: RedisClientType): RedisClientLike;
|
|
15
|
+
declare function createRedisV5Client(config: Extract<RedisClientConfig, {
|
|
16
|
+
type: "redis";
|
|
17
|
+
}>): RedisClientType;
|
|
18
|
+
|
|
19
|
+
export { adaptRedisV5, createRedisV5Client };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RedisClientType } from 'redis';
|
|
2
|
+
import { R as RedisClientLike, a as RedisClientConfig } from '../../types-CgriT7My.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adapter for `redis@5` (the official `node-redis` client).
|
|
6
|
+
*
|
|
7
|
+
* v5 already uses camelCase methods that match `RedisClientLike` almost
|
|
8
|
+
* exactly, so this adapter is mostly a type assertion plus the
|
|
9
|
+
* `createRedisV5Client` factory that codifies the "1.5s connectTimeout,
|
|
10
|
+
* reconnectStrategy: false" defaults validated in production by the reference
|
|
11
|
+
* implementation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
declare function adaptRedisV5(client: RedisClientType): RedisClientLike;
|
|
15
|
+
declare function createRedisV5Client(config: Extract<RedisClientConfig, {
|
|
16
|
+
type: "redis";
|
|
17
|
+
}>): RedisClientType;
|
|
18
|
+
|
|
19
|
+
export { adaptRedisV5, createRedisV5Client };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/shared/client/adapter-redis.ts
|
|
9
|
+
function adaptRedisV5(client) {
|
|
10
|
+
return client;
|
|
11
|
+
}
|
|
12
|
+
function createRedisV5Client(config) {
|
|
13
|
+
const { createClient } = __require("redis");
|
|
14
|
+
const useTls = config.tls ?? config.url.startsWith("rediss://");
|
|
15
|
+
const opts = {
|
|
16
|
+
url: config.url,
|
|
17
|
+
socket: {
|
|
18
|
+
connectTimeout: config.connectTimeout ?? 1500,
|
|
19
|
+
reconnectStrategy: false,
|
|
20
|
+
...useTls ? { tls: true } : {}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
if (config.password !== void 0) opts.password = config.password;
|
|
24
|
+
return createClient(opts);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { adaptRedisV5, createRedisV5Client };
|
|
28
|
+
//# sourceMappingURL=adapter-redis.js.map
|
|
29
|
+
//# sourceMappingURL=adapter-redis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/shared/client/adapter-redis.ts"],"names":[],"mappings":";;;;;;;;AAcO,SAAS,aAAa,MAAA,EAA0C;AACrE,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,oBACd,MAAA,EACiB;AAKjB,EAAA,MAAM,EAAE,YAAA,EAAa,GAAI,SAAA,CAAQ,OAAO,CAAA;AACxC,EAAA,MAAM,SAAS,MAAA,CAAO,GAAA,IAAO,MAAA,CAAO,GAAA,CAAI,WAAW,WAAW,CAAA;AAM9D,EAAA,MAAM,IAAA,GAAY;AAAA,IAChB,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ,MAAA,EAAQ;AAAA,MACN,cAAA,EAAgB,OAAO,cAAA,IAAkB,IAAA;AAAA,MACzC,iBAAA,EAAmB,KAAA;AAAA,MACnB,GAAI,MAAA,GAAS,EAAE,GAAA,EAAK,IAAA,KAAS;AAAC;AAChC,GACF;AACA,EAAA,IAAI,MAAA,CAAO,QAAA,KAAa,MAAA,EAAW,IAAA,CAAK,WAAW,MAAA,CAAO,QAAA;AAE1D,EAAA,OAAO,aAAa,IAAI,CAAA;AAC1B","file":"adapter-redis.js","sourcesContent":["/**\n * Adapter for `redis@5` (the official `node-redis` client).\n *\n * v5 already uses camelCase methods that match `RedisClientLike` almost\n * exactly, so this adapter is mostly a type assertion plus the\n * `createRedisV5Client` factory that codifies the \"1.5s connectTimeout,\n * reconnectStrategy: false\" defaults validated in production by the reference\n * implementation.\n */\n\nimport type { RedisClientType } from \"redis\";\n\nimport type { RedisClientConfig, RedisClientLike } from \"../../types.js\";\n\nexport function adaptRedisV5(client: RedisClientType): RedisClientLike {\n return client as unknown as RedisClientLike;\n}\n\nexport function createRedisV5Client(\n config: Extract<RedisClientConfig, { type: \"redis\" }>\n): RedisClientType {\n // Lazy require so consumers using ioredis only don't pay the cost.\n // We do `require` instead of `await import` here because the parent\n // `buildClient` is already in an async context that handles awaiting.\n \n const { createClient } = require(\"redis\") as typeof import(\"redis\");\n const useTls = config.tls ?? config.url.startsWith(\"rediss://\");\n\n // redis@5 has a discriminated socket union (TCP / TLS / IPC) and disallows\n // explicit `undefined` under exactOptionalPropertyTypes. Build the options\n // object incrementally so optional fields stay absent when not set.\n \n const opts: any = {\n url: config.url,\n socket: {\n connectTimeout: config.connectTimeout ?? 1500,\n reconnectStrategy: false,\n ...(useTls ? { tls: true } : {}),\n },\n };\n if (config.password !== undefined) opts.password = config.password;\n\n return createClient(opts) as RedisClientType;\n}\n"]}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Redis client surface used by both handlers. Adapters in
|
|
3
|
+
* `src/shared/client/` translate redis@5 / ioredis / Cluster to this shape.
|
|
4
|
+
*
|
|
5
|
+
* Methods follow `redis@5` (camelCase) naming. The ioredis adapter normalizes
|
|
6
|
+
* snake_case calls into this contract.
|
|
7
|
+
*/
|
|
8
|
+
interface RedisClientLike {
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
connect(): Promise<unknown>;
|
|
11
|
+
get(key: string): Promise<string | null>;
|
|
12
|
+
set(key: string, value: string, opts?: {
|
|
13
|
+
EX?: number;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
del(keys: string | string[]): Promise<number>;
|
|
16
|
+
sAdd(key: string, member: string | string[]): Promise<number>;
|
|
17
|
+
sMembers(key: string): Promise<string[]>;
|
|
18
|
+
expire(key: string, seconds: number): Promise<number | boolean>;
|
|
19
|
+
mGet(keys: string[]): Promise<(string | null)[]>;
|
|
20
|
+
eval(script: string, opts: {
|
|
21
|
+
keys: string[];
|
|
22
|
+
arguments: string[];
|
|
23
|
+
}): Promise<unknown>;
|
|
24
|
+
evalSha?(sha: string, opts: {
|
|
25
|
+
keys: string[];
|
|
26
|
+
arguments: string[];
|
|
27
|
+
}): Promise<unknown>;
|
|
28
|
+
scriptLoad?(script: string): Promise<string>;
|
|
29
|
+
scanIterator(opts: {
|
|
30
|
+
MATCH: string;
|
|
31
|
+
COUNT?: number;
|
|
32
|
+
}): AsyncIterable<string[] | string>;
|
|
33
|
+
ping(): Promise<string>;
|
|
34
|
+
on(event: "error", listener: (err: Error) => void): unknown;
|
|
35
|
+
}
|
|
36
|
+
type RedisClientFactory = () => RedisClientLike | Promise<RedisClientLike>;
|
|
37
|
+
type RedisClientConfig = {
|
|
38
|
+
type: "redis";
|
|
39
|
+
url: string;
|
|
40
|
+
password?: string;
|
|
41
|
+
tls?: boolean;
|
|
42
|
+
connectTimeout?: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: "ioredis";
|
|
45
|
+
url: string;
|
|
46
|
+
password?: string;
|
|
47
|
+
tls?: boolean;
|
|
48
|
+
connectTimeout?: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: "cluster";
|
|
51
|
+
nodes: Array<{
|
|
52
|
+
host: string;
|
|
53
|
+
port: number;
|
|
54
|
+
}>;
|
|
55
|
+
password?: string;
|
|
56
|
+
tls?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Fallback strategy when Redis is unavailable.
|
|
60
|
+
*
|
|
61
|
+
* - `auto` (default): try Redis; on connection failure or timeout, transparently
|
|
62
|
+
* fall back to an in-memory store. Multi-instance correctness is degraded but
|
|
63
|
+
* the app stays up.
|
|
64
|
+
* - `always`: never connect to Redis. Useful for `next build` or local
|
|
65
|
+
* development. Equivalent to legacy `CACHE_HANDLER_FALLBACK=memory` env.
|
|
66
|
+
* - `never`: connection failures are surfaced as cache misses. No memory fallback.
|
|
67
|
+
*/
|
|
68
|
+
type FallbackStrategy = "auto" | "always" | "never";
|
|
69
|
+
/**
|
|
70
|
+
* Telemetry hook. Called from inside the handlers on every meaningful event.
|
|
71
|
+
* Optional — when not provided, only the process-local counters in
|
|
72
|
+
* `src/shared/metrics.ts` are updated.
|
|
73
|
+
*/
|
|
74
|
+
type MetricEventType = "cache.hit" | "cache.miss" | "cache.stale" | "cache.set" | "cache.set.failed" | "tag.invalidate.soft" | "tag.invalidate.hard" | "tag.expiration.read" | "redis.connect.failed" | "redis.timeout" | "fallback.activated" | "build_phase.skip";
|
|
75
|
+
interface MetricEvent {
|
|
76
|
+
type: MetricEventType;
|
|
77
|
+
/** Operation latency in milliseconds, when measurable. */
|
|
78
|
+
ms?: number;
|
|
79
|
+
/** Free-form tags. Keep cardinality low (no user IDs, no cache keys). */
|
|
80
|
+
meta?: Record<string, string | number | boolean>;
|
|
81
|
+
}
|
|
82
|
+
type MetricEmitter = (event: MetricEvent) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Logger interface, compatible with `console`. Levels follow standard severity.
|
|
85
|
+
*/
|
|
86
|
+
interface Logger {
|
|
87
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
88
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
89
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
90
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options shared by both `createCacheComponentsHandler` and
|
|
94
|
+
* `createIncrementalCacheHandler`.
|
|
95
|
+
*/
|
|
96
|
+
interface CacheHandlerOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Redis client. Either:
|
|
99
|
+
* - a factory function returning a connected (or connectable) `RedisClientLike`
|
|
100
|
+
* - a config object describing how to construct one (lazy)
|
|
101
|
+
*/
|
|
102
|
+
client: RedisClientFactory | RedisClientConfig;
|
|
103
|
+
/**
|
|
104
|
+
* Key prefix for namespace isolation. Defaults differ per handler:
|
|
105
|
+
* - cacheHandlers: `next-cache:`
|
|
106
|
+
* - incremental: `next-incremental:`
|
|
107
|
+
*
|
|
108
|
+
* Useful when multiple Next.js apps share one Redis.
|
|
109
|
+
*/
|
|
110
|
+
keyPrefix?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Deployment-isolation namespace. Combined with `keyPrefix` so entries from
|
|
113
|
+
* different deployments never collide.
|
|
114
|
+
*
|
|
115
|
+
* Resolution order if not provided:
|
|
116
|
+
* process.env.DEPLOYMENT_VERSION
|
|
117
|
+
* ?? process.env.GIT_HASH
|
|
118
|
+
* ?? "unversioned"
|
|
119
|
+
*
|
|
120
|
+
* Pass a function to defer reading process.env until first use (useful when
|
|
121
|
+
* env is set after module load).
|
|
122
|
+
*/
|
|
123
|
+
buildNamespace?: string | (() => string);
|
|
124
|
+
/**
|
|
125
|
+
* Per-operation timeout in milliseconds. Wraps every Redis call in an
|
|
126
|
+
* AbortController. Default: 1500ms (matches the validated `pingRedis` value
|
|
127
|
+
* from the reference implementation).
|
|
128
|
+
*
|
|
129
|
+
* On timeout:
|
|
130
|
+
* - `get` / `getExpiration` → cache miss (graceful)
|
|
131
|
+
* - `set` / `refreshTags` → ignored (best-effort)
|
|
132
|
+
* - `updateTags` (hard expire) → propagated (revalidation must not be silent)
|
|
133
|
+
*/
|
|
134
|
+
abortTimeoutMs?: number;
|
|
135
|
+
/** Fallback strategy. See {@link FallbackStrategy}. */
|
|
136
|
+
fallback?: FallbackStrategy;
|
|
137
|
+
/**
|
|
138
|
+
* Whether to keep returning a stale entry in the SWR window
|
|
139
|
+
* (`revalidate < age < expire`) while Next.js triggers a background refresh.
|
|
140
|
+
*
|
|
141
|
+
* Default: `true`. Set to `false` to revert to "expire on revalidate" behavior.
|
|
142
|
+
*/
|
|
143
|
+
staleWhileRevalidate?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Override the build-phase detector. Default checks
|
|
146
|
+
* `process.env.NEXT_PHASE === "phase-production-build"`.
|
|
147
|
+
*
|
|
148
|
+
* During build phase, all Redis calls are skipped and the in-memory fallback
|
|
149
|
+
* is used. Prevents PR #207-style ECONNREFUSED at build time.
|
|
150
|
+
*/
|
|
151
|
+
isBuildPhase?: () => boolean;
|
|
152
|
+
/** Metrics emitter. */
|
|
153
|
+
onMetric?: MetricEmitter;
|
|
154
|
+
/** Custom logger. Defaults to a `console` adapter (warn level and above). */
|
|
155
|
+
logger?: Logger;
|
|
156
|
+
/**
|
|
157
|
+
* Use Redis Cluster hash tags in keys (`next-cache:{ns}:entry:...`). Required
|
|
158
|
+
* when running against a cluster so multi-key Lua scripts land on the same
|
|
159
|
+
* slot. Default: `false`.
|
|
160
|
+
*/
|
|
161
|
+
hashTag?: boolean;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Cache entry shape used by `cacheHandlers` (the `'use cache'` interface).
|
|
165
|
+
* Mirrors Next.js 16's internal CacheEntry type.
|
|
166
|
+
*/
|
|
167
|
+
interface CacheComponentsEntry {
|
|
168
|
+
/** Rendered RSC payload. Streamed during write, materialized to base64 in storage. */
|
|
169
|
+
value: ReadableStream<Uint8Array>;
|
|
170
|
+
tags: string[];
|
|
171
|
+
/** SWR window start (seconds). */
|
|
172
|
+
stale: number;
|
|
173
|
+
/** Creation time (ms since epoch). */
|
|
174
|
+
timestamp: number;
|
|
175
|
+
/** Hard expiration (seconds since timestamp). */
|
|
176
|
+
expire: number;
|
|
177
|
+
/** Background refresh trigger time (seconds since timestamp). */
|
|
178
|
+
revalidate: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Cache data shape used by the legacy `cacheHandler` (singular) interface for
|
|
182
|
+
* ISR / route handlers. Value is a fully materialized buffer, not a stream.
|
|
183
|
+
*/
|
|
184
|
+
interface IncrementalCacheData {
|
|
185
|
+
value: Buffer | string | {
|
|
186
|
+
kind: string;
|
|
187
|
+
[key: string]: unknown;
|
|
188
|
+
} | null;
|
|
189
|
+
lastModified?: number;
|
|
190
|
+
tags?: string[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type { CacheComponentsEntry as C, FallbackStrategy as F, IncrementalCacheData as I, Logger as L, MetricEmitter as M, RedisClientLike as R, RedisClientConfig as a, CacheHandlerOptions as b, MetricEvent as c, MetricEventType as d, RedisClientFactory as e };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Redis client surface used by both handlers. Adapters in
|
|
3
|
+
* `src/shared/client/` translate redis@5 / ioredis / Cluster to this shape.
|
|
4
|
+
*
|
|
5
|
+
* Methods follow `redis@5` (camelCase) naming. The ioredis adapter normalizes
|
|
6
|
+
* snake_case calls into this contract.
|
|
7
|
+
*/
|
|
8
|
+
interface RedisClientLike {
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
connect(): Promise<unknown>;
|
|
11
|
+
get(key: string): Promise<string | null>;
|
|
12
|
+
set(key: string, value: string, opts?: {
|
|
13
|
+
EX?: number;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
del(keys: string | string[]): Promise<number>;
|
|
16
|
+
sAdd(key: string, member: string | string[]): Promise<number>;
|
|
17
|
+
sMembers(key: string): Promise<string[]>;
|
|
18
|
+
expire(key: string, seconds: number): Promise<number | boolean>;
|
|
19
|
+
mGet(keys: string[]): Promise<(string | null)[]>;
|
|
20
|
+
eval(script: string, opts: {
|
|
21
|
+
keys: string[];
|
|
22
|
+
arguments: string[];
|
|
23
|
+
}): Promise<unknown>;
|
|
24
|
+
evalSha?(sha: string, opts: {
|
|
25
|
+
keys: string[];
|
|
26
|
+
arguments: string[];
|
|
27
|
+
}): Promise<unknown>;
|
|
28
|
+
scriptLoad?(script: string): Promise<string>;
|
|
29
|
+
scanIterator(opts: {
|
|
30
|
+
MATCH: string;
|
|
31
|
+
COUNT?: number;
|
|
32
|
+
}): AsyncIterable<string[] | string>;
|
|
33
|
+
ping(): Promise<string>;
|
|
34
|
+
on(event: "error", listener: (err: Error) => void): unknown;
|
|
35
|
+
}
|
|
36
|
+
type RedisClientFactory = () => RedisClientLike | Promise<RedisClientLike>;
|
|
37
|
+
type RedisClientConfig = {
|
|
38
|
+
type: "redis";
|
|
39
|
+
url: string;
|
|
40
|
+
password?: string;
|
|
41
|
+
tls?: boolean;
|
|
42
|
+
connectTimeout?: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: "ioredis";
|
|
45
|
+
url: string;
|
|
46
|
+
password?: string;
|
|
47
|
+
tls?: boolean;
|
|
48
|
+
connectTimeout?: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: "cluster";
|
|
51
|
+
nodes: Array<{
|
|
52
|
+
host: string;
|
|
53
|
+
port: number;
|
|
54
|
+
}>;
|
|
55
|
+
password?: string;
|
|
56
|
+
tls?: boolean;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Fallback strategy when Redis is unavailable.
|
|
60
|
+
*
|
|
61
|
+
* - `auto` (default): try Redis; on connection failure or timeout, transparently
|
|
62
|
+
* fall back to an in-memory store. Multi-instance correctness is degraded but
|
|
63
|
+
* the app stays up.
|
|
64
|
+
* - `always`: never connect to Redis. Useful for `next build` or local
|
|
65
|
+
* development. Equivalent to legacy `CACHE_HANDLER_FALLBACK=memory` env.
|
|
66
|
+
* - `never`: connection failures are surfaced as cache misses. No memory fallback.
|
|
67
|
+
*/
|
|
68
|
+
type FallbackStrategy = "auto" | "always" | "never";
|
|
69
|
+
/**
|
|
70
|
+
* Telemetry hook. Called from inside the handlers on every meaningful event.
|
|
71
|
+
* Optional — when not provided, only the process-local counters in
|
|
72
|
+
* `src/shared/metrics.ts` are updated.
|
|
73
|
+
*/
|
|
74
|
+
type MetricEventType = "cache.hit" | "cache.miss" | "cache.stale" | "cache.set" | "cache.set.failed" | "tag.invalidate.soft" | "tag.invalidate.hard" | "tag.expiration.read" | "redis.connect.failed" | "redis.timeout" | "fallback.activated" | "build_phase.skip";
|
|
75
|
+
interface MetricEvent {
|
|
76
|
+
type: MetricEventType;
|
|
77
|
+
/** Operation latency in milliseconds, when measurable. */
|
|
78
|
+
ms?: number;
|
|
79
|
+
/** Free-form tags. Keep cardinality low (no user IDs, no cache keys). */
|
|
80
|
+
meta?: Record<string, string | number | boolean>;
|
|
81
|
+
}
|
|
82
|
+
type MetricEmitter = (event: MetricEvent) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Logger interface, compatible with `console`. Levels follow standard severity.
|
|
85
|
+
*/
|
|
86
|
+
interface Logger {
|
|
87
|
+
debug: (message: string, meta?: Record<string, unknown>) => void;
|
|
88
|
+
info: (message: string, meta?: Record<string, unknown>) => void;
|
|
89
|
+
warn: (message: string, meta?: Record<string, unknown>) => void;
|
|
90
|
+
error: (message: string, meta?: Record<string, unknown>) => void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options shared by both `createCacheComponentsHandler` and
|
|
94
|
+
* `createIncrementalCacheHandler`.
|
|
95
|
+
*/
|
|
96
|
+
interface CacheHandlerOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Redis client. Either:
|
|
99
|
+
* - a factory function returning a connected (or connectable) `RedisClientLike`
|
|
100
|
+
* - a config object describing how to construct one (lazy)
|
|
101
|
+
*/
|
|
102
|
+
client: RedisClientFactory | RedisClientConfig;
|
|
103
|
+
/**
|
|
104
|
+
* Key prefix for namespace isolation. Defaults differ per handler:
|
|
105
|
+
* - cacheHandlers: `next-cache:`
|
|
106
|
+
* - incremental: `next-incremental:`
|
|
107
|
+
*
|
|
108
|
+
* Useful when multiple Next.js apps share one Redis.
|
|
109
|
+
*/
|
|
110
|
+
keyPrefix?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Deployment-isolation namespace. Combined with `keyPrefix` so entries from
|
|
113
|
+
* different deployments never collide.
|
|
114
|
+
*
|
|
115
|
+
* Resolution order if not provided:
|
|
116
|
+
* process.env.DEPLOYMENT_VERSION
|
|
117
|
+
* ?? process.env.GIT_HASH
|
|
118
|
+
* ?? "unversioned"
|
|
119
|
+
*
|
|
120
|
+
* Pass a function to defer reading process.env until first use (useful when
|
|
121
|
+
* env is set after module load).
|
|
122
|
+
*/
|
|
123
|
+
buildNamespace?: string | (() => string);
|
|
124
|
+
/**
|
|
125
|
+
* Per-operation timeout in milliseconds. Wraps every Redis call in an
|
|
126
|
+
* AbortController. Default: 1500ms (matches the validated `pingRedis` value
|
|
127
|
+
* from the reference implementation).
|
|
128
|
+
*
|
|
129
|
+
* On timeout:
|
|
130
|
+
* - `get` / `getExpiration` → cache miss (graceful)
|
|
131
|
+
* - `set` / `refreshTags` → ignored (best-effort)
|
|
132
|
+
* - `updateTags` (hard expire) → propagated (revalidation must not be silent)
|
|
133
|
+
*/
|
|
134
|
+
abortTimeoutMs?: number;
|
|
135
|
+
/** Fallback strategy. See {@link FallbackStrategy}. */
|
|
136
|
+
fallback?: FallbackStrategy;
|
|
137
|
+
/**
|
|
138
|
+
* Whether to keep returning a stale entry in the SWR window
|
|
139
|
+
* (`revalidate < age < expire`) while Next.js triggers a background refresh.
|
|
140
|
+
*
|
|
141
|
+
* Default: `true`. Set to `false` to revert to "expire on revalidate" behavior.
|
|
142
|
+
*/
|
|
143
|
+
staleWhileRevalidate?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Override the build-phase detector. Default checks
|
|
146
|
+
* `process.env.NEXT_PHASE === "phase-production-build"`.
|
|
147
|
+
*
|
|
148
|
+
* During build phase, all Redis calls are skipped and the in-memory fallback
|
|
149
|
+
* is used. Prevents PR #207-style ECONNREFUSED at build time.
|
|
150
|
+
*/
|
|
151
|
+
isBuildPhase?: () => boolean;
|
|
152
|
+
/** Metrics emitter. */
|
|
153
|
+
onMetric?: MetricEmitter;
|
|
154
|
+
/** Custom logger. Defaults to a `console` adapter (warn level and above). */
|
|
155
|
+
logger?: Logger;
|
|
156
|
+
/**
|
|
157
|
+
* Use Redis Cluster hash tags in keys (`next-cache:{ns}:entry:...`). Required
|
|
158
|
+
* when running against a cluster so multi-key Lua scripts land on the same
|
|
159
|
+
* slot. Default: `false`.
|
|
160
|
+
*/
|
|
161
|
+
hashTag?: boolean;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Cache entry shape used by `cacheHandlers` (the `'use cache'` interface).
|
|
165
|
+
* Mirrors Next.js 16's internal CacheEntry type.
|
|
166
|
+
*/
|
|
167
|
+
interface CacheComponentsEntry {
|
|
168
|
+
/** Rendered RSC payload. Streamed during write, materialized to base64 in storage. */
|
|
169
|
+
value: ReadableStream<Uint8Array>;
|
|
170
|
+
tags: string[];
|
|
171
|
+
/** SWR window start (seconds). */
|
|
172
|
+
stale: number;
|
|
173
|
+
/** Creation time (ms since epoch). */
|
|
174
|
+
timestamp: number;
|
|
175
|
+
/** Hard expiration (seconds since timestamp). */
|
|
176
|
+
expire: number;
|
|
177
|
+
/** Background refresh trigger time (seconds since timestamp). */
|
|
178
|
+
revalidate: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Cache data shape used by the legacy `cacheHandler` (singular) interface for
|
|
182
|
+
* ISR / route handlers. Value is a fully materialized buffer, not a stream.
|
|
183
|
+
*/
|
|
184
|
+
interface IncrementalCacheData {
|
|
185
|
+
value: Buffer | string | {
|
|
186
|
+
kind: string;
|
|
187
|
+
[key: string]: unknown;
|
|
188
|
+
} | null;
|
|
189
|
+
lastModified?: number;
|
|
190
|
+
tags?: string[];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type { CacheComponentsEntry as C, FallbackStrategy as F, IncrementalCacheData as I, Logger as L, MetricEmitter as M, RedisClientLike as R, RedisClientConfig as a, CacheHandlerOptions as b, MetricEvent as c, MetricEventType as d, RedisClientFactory as e };
|