@zenweb/cache 5.2.0 → 5.3.0

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 CHANGED
@@ -8,6 +8,8 @@
8
8
  - JSON 序列直接输出
9
9
  - 防止缓存击穿
10
10
  - 单例执行
11
+ - 支持自定义序列化方式
12
+ - 支持自定义压缩方式
11
13
 
12
14
  ## 安装
13
15
 
@@ -83,3 +85,44 @@ export class CacheController {
83
85
  }
84
86
  }
85
87
  ```
88
+
89
+ ## 进阶优化
90
+ 使用 msgpackr 序列化 + snappy 压缩
91
+
92
+ ```ts
93
+ import { create } from 'zenweb';
94
+ import modCache from '@zenweb/cache';
95
+ import { Packr } from 'msgpackr';
96
+ import * as snappy from 'snappy';
97
+
98
+ const SNAPPY_MAGIC_HEADER = Buffer.from('SNAPPY');
99
+ const packer = new Packr();
100
+
101
+ const app = create();
102
+
103
+ app.setup(cache({
104
+ serializer: {
105
+ deserialize(data) {
106
+ return packer.unpack(data)
107
+ },
108
+ serialize(data) {
109
+ return packer.pack(data)
110
+ },
111
+ },
112
+ compressor: {
113
+ isCompressed(data) {
114
+ return SNAPPY_MAGIC_HEADER.equals(data.subarray(0, SNAPPY_MAGIC_HEADER.length));
115
+ },
116
+ async compress(data) {
117
+ const compressed = await snappy.compress(data);
118
+ return Buffer.concat([SNAPPY_MAGIC_HEADER, compressed]);
119
+ },
120
+ async decompress(data) {
121
+ const result = await snappy.uncompress(data.subarray(SNAPPY_MAGIC_HEADER.length));
122
+ return typeof result === 'string' ? Buffer.from(result) : result;
123
+ },
124
+ },
125
+ }));
126
+
127
+ app.start();
128
+ ```
package/dist/cache.js CHANGED
@@ -1,10 +1,5 @@
1
- import { gzip, unzip } from 'node:zlib';
2
- import { promisify } from 'node:util';
3
1
  import { Locker } from './locker.js';
4
2
  import { debug, sleep } from './utils.js';
5
- const compress = promisify(gzip);
6
- const decompress = promisify(unzip);
7
- const GZ_HEADER = Buffer.from([0x1F, 0x8B, 0x08]);
8
3
  const _getDebug = debug.extend('get');
9
4
  const _setDebug = debug.extend('set');
10
5
  const _lockDebug = debug.extend('lock');
@@ -65,14 +60,14 @@ export class Cache {
65
60
  */
66
61
  async set(key, value, ttlopt) {
67
62
  let compressed;
68
- let data = Buffer.isBuffer(value) ? value : this.option.serializer.serialize(value);
63
+ let data = Buffer.isBuffer(value) ? value : await this.option.serializer.serialize(value);
69
64
  const opt = Object.assign({}, this.option.set, typeof ttlopt === 'object' ? ttlopt : undefined);
70
65
  if (typeof ttlopt === 'number') {
71
66
  opt.ttl = ttlopt;
72
67
  }
73
68
  _setDebug('[%s] data length:', key, data.length);
74
69
  if (opt.compressMinLength && data.length >= opt.compressMinLength) {
75
- let _compressed = await compress(data, { level: opt.compressLevel });
70
+ let _compressed = await this.option.compressor.compress(data);
76
71
  _setDebug('[%s] compressed length: %d', key, _compressed.length);
77
72
  if (_compressed.length < (data.length * (opt.compressStoreRatio || 0.95))) {
78
73
  compressed = _compressed;
@@ -95,19 +90,19 @@ export class Cache {
95
90
  let _opt = Object.assign({ parse: true, decompress: true }, opt);
96
91
  if (data) {
97
92
  // 解压处理
98
- let compressed = GZ_HEADER.equals(data.subarray(0, GZ_HEADER.length));
93
+ let compressed = await this.option.compressor.isCompressed(data);
99
94
  if (compressed) {
100
95
  _getDebug('[%s] is compressed', key);
101
96
  if (_opt.parse || _opt.decompress) {
102
97
  _getDebug('[%s] decompress', key);
103
- data = await decompress(data);
98
+ data = await this.option.compressor.decompress(data);
104
99
  compressed = false;
105
100
  }
106
101
  }
107
102
  // 解析处理
108
103
  if (_opt.parse) {
109
104
  _getDebug('[%s] parse', key);
110
- return this.option.serializer.deserialize(data);
105
+ return await this.option.serializer.deserialize(data);
111
106
  }
112
107
  return new CacheResult(compressed, data);
113
108
  }
@@ -0,0 +1,9 @@
1
+ import { ZlibOptions } from 'node:zlib';
2
+ import { DataCompressor } from "./types.js";
3
+ export declare class GzCompressor implements DataCompressor {
4
+ private opt?;
5
+ constructor(opt?: ZlibOptions | undefined);
6
+ isCompressed(data: Buffer): boolean;
7
+ compress(data: Buffer): Promise<Buffer<ArrayBufferLike>>;
8
+ decompress(data: Buffer): Promise<Buffer<ArrayBufferLike>>;
9
+ }
@@ -0,0 +1,20 @@
1
+ import { gzip, unzip } from 'node:zlib';
2
+ import { promisify } from 'node:util';
3
+ const compress = promisify(gzip);
4
+ const decompress = promisify(unzip);
5
+ const GZ_HEADER = Buffer.from([0x1F, 0x8B, 0x08]);
6
+ export class GzCompressor {
7
+ opt;
8
+ constructor(opt) {
9
+ this.opt = opt;
10
+ }
11
+ isCompressed(data) {
12
+ return GZ_HEADER.equals(data.subarray(0, GZ_HEADER.length));
13
+ }
14
+ compress(data) {
15
+ return compress(data, this.opt);
16
+ }
17
+ decompress(data) {
18
+ return decompress(data, this.opt);
19
+ }
20
+ }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ import { Redis } from 'ioredis';
5
5
  export * from './global.js';
6
6
  export * from './types.js';
7
7
  export * from './utils.js';
8
+ export { GzCompressor } from './compressor.js';
9
+ export { JSONSerializer } from './serializer.js';
8
10
  export { Locker } from './locker.js';
9
11
  export { cached } from './middleware.js';
10
12
  export { CacheHelper } from './helper.js';
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { defaultSetupOption } from './options.js';
4
4
  export * from './global.js';
5
5
  export * from './types.js';
6
6
  export * from './utils.js';
7
+ export { GzCompressor } from './compressor.js';
8
+ export { JSONSerializer } from './serializer.js';
7
9
  export { Locker } from './locker.js';
8
10
  export { cached } from './middleware.js';
9
11
  export { CacheHelper } from './helper.js';
package/dist/options.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import type { RedisOptions } from "ioredis";
2
- import type { LockGetOption, ObjectSerializer, SetOption, SetupOption } from "./types.js";
2
+ import type { LockGetOption, SetOption, SetupOption } from "./types.js";
3
+ import { JSONSerializer } from "./serializer.js";
4
+ import { GzCompressor } from "./compressor.js";
3
5
  export declare const defaultRedisOption: RedisOptions;
4
6
  export declare const defaultSetOption: SetOption;
5
7
  export declare const defaultLockGetOption: LockGetOption;
6
- export declare const defaultSerializer: ObjectSerializer;
8
+ export declare const jsonSerializer: JSONSerializer;
9
+ export declare const gzCompressor: GzCompressor;
7
10
  export declare const defaultSetupOption: Required<SetupOption>;
package/dist/options.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { JSONSerializer } from "./serializer.js";
2
+ import { GzCompressor } from "./compressor.js";
1
3
  export const defaultRedisOption = {
2
4
  host: process.env.REDIS_HOST || '127.0.0.1',
3
5
  port: parseInt(process.env.REDIS_PORT || '') || 6379,
@@ -7,8 +9,7 @@ export const defaultRedisOption = {
7
9
  export const defaultSetOption = {
8
10
  ttl: 60,
9
11
  compressMinLength: 1024,
10
- compressStoreRatio: 0.95,
11
- compressLevel: 1,
12
+ compressStoreRatio: 0.8,
12
13
  };
13
14
  export const defaultLockGetOption = {
14
15
  retryTimeout: 5000,
@@ -17,17 +18,12 @@ export const defaultLockGetOption = {
17
18
  refresh: false,
18
19
  localStore: undefined,
19
20
  };
20
- export const defaultSerializer = {
21
- serialize: (data) => {
22
- return Buffer.from(JSON.stringify(data));
23
- },
24
- deserialize: (data) => {
25
- return JSON.parse(data.toString());
26
- },
27
- };
21
+ export const jsonSerializer = new JSONSerializer();
22
+ export const gzCompressor = new GzCompressor({ level: 1 });
28
23
  export const defaultSetupOption = {
29
24
  redis: defaultRedisOption,
30
25
  set: defaultSetOption,
31
26
  lockGet: defaultLockGetOption,
32
- serializer: defaultSerializer,
27
+ serializer: jsonSerializer,
28
+ compressor: gzCompressor,
33
29
  };
@@ -0,0 +1,5 @@
1
+ import { ObjectSerializer } from "./types.js";
2
+ export declare class JSONSerializer implements ObjectSerializer {
3
+ serialize(data: object): Buffer<ArrayBuffer>;
4
+ deserialize(data: Buffer): any;
5
+ }
@@ -0,0 +1,8 @@
1
+ export class JSONSerializer {
2
+ serialize(data) {
3
+ return Buffer.from(JSON.stringify(data));
4
+ }
5
+ deserialize(data) {
6
+ return JSON.parse(data.toString());
7
+ }
8
+ }
package/dist/types.d.ts CHANGED
@@ -23,22 +23,45 @@ export interface SetOption {
23
23
  * - 压缩完成后的长度对比压缩之前的长度所缩减的比率
24
24
  * - 压缩率不满足要求则不存储压缩数据
25
25
  * - 相对于低比率的压缩数据再解压节省的空间十分有限又浪费计算资源
26
- * - 默认 0.95, 至少压缩率达到 95% 才有价值
27
- * @default 0.95
26
+ * - 默认 0.8, 至少压缩率达到 95% 才有价值
27
+ * @default 0.8
28
28
  */
29
29
  compressStoreRatio?: number;
30
- /**
31
- * 压缩级别
32
- * @default 1
33
- */
34
- compressLevel?: number;
35
30
  }
36
31
  /**
37
32
  * 对象序列化
38
33
  */
39
34
  export interface ObjectSerializer {
40
- serialize(data: object): Buffer;
41
- deserialize(data: Buffer): object;
35
+ /**
36
+ * 序列化
37
+ * @param data 需要被序列化的对象
38
+ */
39
+ serialize(data: object): Buffer | Promise<Buffer>;
40
+ /**
41
+ * 发序列化
42
+ * @param data 序列化数据
43
+ */
44
+ deserialize(data: Buffer): object | Promise<object>;
45
+ }
46
+ /**
47
+ * 数据压缩器
48
+ */
49
+ export interface DataCompressor {
50
+ /**
51
+ * 是否为压缩数据
52
+ * @param data 压缩数据
53
+ */
54
+ isCompressed(data: Buffer): boolean | Promise<boolean>;
55
+ /**
56
+ * 压缩数据
57
+ * @param data 原始数据
58
+ */
59
+ compress(data: Buffer): Buffer | Promise<Buffer>;
60
+ /**
61
+ * 解压缩数据
62
+ * @param data 压缩数据
63
+ */
64
+ decompress(data: Buffer): Buffer | Promise<Buffer>;
42
65
  }
43
66
  export interface SetupOption {
44
67
  /**
@@ -58,6 +81,11 @@ export interface SetupOption {
58
81
  * - 默认为 JSON 序列化
59
82
  */
60
83
  serializer?: ObjectSerializer;
84
+ /**
85
+ * 数据压缩器
86
+ * - 默认为 Gz 压缩器
87
+ */
88
+ compressor?: DataCompressor;
61
89
  }
62
90
  /**
63
91
  * 取得缓存选项
@@ -145,6 +173,6 @@ export interface LockGetOption extends GetOption, SetOption, LockOption {
145
173
  */
146
174
  noWait?: boolean;
147
175
  }
148
- export type CacheKeyType = string | number | object;
176
+ export type CacheKeyType = string | number | object | undefined | null | boolean;
149
177
  export type CacheHelperOption = Omit<LockGetOption, 'parse' | 'noWait'>;
150
178
  export type CacheHelperKey<P extends CacheKeyType[]> = string | ((...param: P) => string | Promise<string>);
package/dist/utils.js CHANGED
@@ -72,12 +72,14 @@ function plainifyOrHash(obj) {
72
72
  */
73
73
  export function cacheKey(...key) {
74
74
  return key.map(i => {
75
- if (typeof i === 'string')
76
- return i;
77
- if (typeof i === 'number')
78
- return String(i);
79
- if (typeof i === 'undefined')
80
- return '';
75
+ if (i === null)
76
+ return 'N';
77
+ switch (typeof i) {
78
+ case 'string': return i;
79
+ case 'number': return String(i);
80
+ case 'boolean': return i ? 'T' : 'F';
81
+ case 'undefined': return '';
82
+ }
81
83
  if (i instanceof Date)
82
84
  return i.toJSON();
83
85
  return plainifyOrHash(i);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zenweb/cache",
3
3
  "type": "module",
4
- "version": "5.2.0",
4
+ "version": "5.3.0",
5
5
  "description": "Zenweb Cache module",
6
6
  "exports": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -37,6 +37,7 @@
37
37
  "mocha": "^10.2.0",
38
38
  "msgpackr": "^1.11.0",
39
39
  "rimraf": "^4.3.1",
40
+ "snappy": "^7.3.3",
40
41
  "ts-node": "^10.9.1",
41
42
  "typescript": "^5.6.3",
42
43
  "zenweb": "^5.1.0"