@nocobase/cache 0.15.0-alpha.4 → 0.16.0-alpha.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/lib/cache-manager.d.ts +35 -0
- package/lib/cache-manager.js +131 -0
- package/lib/cache.d.ts +29 -0
- package/lib/cache.js +104 -0
- package/lib/index.d.ts +2 -30
- package/lib/index.js +5 -44
- package/package.json +5 -5
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { FactoryStore, Store } from 'cache-manager';
|
|
2
|
+
import { Cache } from './cache';
|
|
3
|
+
type StoreOptions = {
|
|
4
|
+
store?: 'memory' | FactoryStore<Store, any>;
|
|
5
|
+
close?: (store: Store) => Promise<void>;
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
};
|
|
8
|
+
export type CacheManagerOptions = Partial<{
|
|
9
|
+
defaultStore: string;
|
|
10
|
+
stores: {
|
|
11
|
+
[storeType: string]: StoreOptions;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
export declare class CacheManager {
|
|
15
|
+
defaultStore: string;
|
|
16
|
+
private stores;
|
|
17
|
+
storeTypes: Map<string, StoreOptions>;
|
|
18
|
+
caches: Map<string, Cache>;
|
|
19
|
+
constructor(options?: CacheManagerOptions);
|
|
20
|
+
private createStore;
|
|
21
|
+
registerStore(options: {
|
|
22
|
+
name: string;
|
|
23
|
+
} & StoreOptions): void;
|
|
24
|
+
private newCache;
|
|
25
|
+
createCache(options: {
|
|
26
|
+
name: string;
|
|
27
|
+
prefix?: string;
|
|
28
|
+
store?: string;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}): Promise<Cache>;
|
|
31
|
+
getCache(name: string): Cache;
|
|
32
|
+
flushAll(): Promise<void>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var cache_manager_exports = {};
|
|
30
|
+
__export(cache_manager_exports, {
|
|
31
|
+
CacheManager: () => CacheManager
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(cache_manager_exports);
|
|
34
|
+
var import_cache_manager = require("cache-manager");
|
|
35
|
+
var import_cache = require("./cache");
|
|
36
|
+
var import_lodash = __toESM(require("lodash"));
|
|
37
|
+
var import_cache_manager_redis_yet = require("cache-manager-redis-yet");
|
|
38
|
+
var import_deepmerge = __toESM(require("deepmerge"));
|
|
39
|
+
const _CacheManager = class _CacheManager {
|
|
40
|
+
defaultStore;
|
|
41
|
+
stores = /* @__PURE__ */ new Map();
|
|
42
|
+
storeTypes = /* @__PURE__ */ new Map();
|
|
43
|
+
caches = /* @__PURE__ */ new Map();
|
|
44
|
+
constructor(options) {
|
|
45
|
+
const defaultOptions = {
|
|
46
|
+
defaultStore: "memory",
|
|
47
|
+
stores: {
|
|
48
|
+
memory: {
|
|
49
|
+
store: "memory",
|
|
50
|
+
// global config
|
|
51
|
+
max: 2e3
|
|
52
|
+
},
|
|
53
|
+
redis: {
|
|
54
|
+
store: import_cache_manager_redis_yet.redisStore,
|
|
55
|
+
close: async (redis) => {
|
|
56
|
+
await redis.client.quit();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const cacheOptions = (0, import_deepmerge.default)(defaultOptions, options || {});
|
|
62
|
+
const { defaultStore = "memory", stores } = cacheOptions;
|
|
63
|
+
this.defaultStore = defaultStore;
|
|
64
|
+
for (const [name, store] of Object.entries(stores)) {
|
|
65
|
+
const { store: s, ...globalConfig } = store;
|
|
66
|
+
this.registerStore({ name, store: s, ...globalConfig });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async createStore(options) {
|
|
70
|
+
const { name, storeType: type, ...config } = options;
|
|
71
|
+
const storeType = this.storeTypes.get(type);
|
|
72
|
+
if (!storeType) {
|
|
73
|
+
throw new Error(`Create cache failed, store type [${type}] is unavailable or not registered`);
|
|
74
|
+
}
|
|
75
|
+
const { store: s, close, ...globalConfig } = storeType;
|
|
76
|
+
const store = await (0, import_cache_manager.caching)(s, { ...globalConfig, ...config });
|
|
77
|
+
this.stores.set(name, { close, store });
|
|
78
|
+
return store;
|
|
79
|
+
}
|
|
80
|
+
registerStore(options) {
|
|
81
|
+
const { name, ...rest } = options;
|
|
82
|
+
this.storeTypes.set(name, rest);
|
|
83
|
+
}
|
|
84
|
+
newCache(options) {
|
|
85
|
+
const { name, prefix, store } = options;
|
|
86
|
+
const cache = new import_cache.Cache({ name, prefix, store });
|
|
87
|
+
this.caches.set(name, cache);
|
|
88
|
+
return cache;
|
|
89
|
+
}
|
|
90
|
+
async createCache(options) {
|
|
91
|
+
const { name, prefix, store = this.defaultStore, ...config } = options;
|
|
92
|
+
if (!import_lodash.default.isEmpty(config)) {
|
|
93
|
+
const newStore = await this.createStore({ name, storeType: store, ...config });
|
|
94
|
+
return this.newCache({ name, prefix, store: newStore });
|
|
95
|
+
}
|
|
96
|
+
const s = this.stores.get(store);
|
|
97
|
+
if (!s) {
|
|
98
|
+
const defaultStore = await this.createStore({ name: store, storeType: store });
|
|
99
|
+
return this.newCache({ name, prefix, store: defaultStore });
|
|
100
|
+
}
|
|
101
|
+
return this.newCache({ name, prefix, store: s.store });
|
|
102
|
+
}
|
|
103
|
+
getCache(name) {
|
|
104
|
+
const cache = this.caches.get(name);
|
|
105
|
+
if (!cache) {
|
|
106
|
+
throw new Error(`Get cache failed, ${name} is not found`);
|
|
107
|
+
}
|
|
108
|
+
return cache;
|
|
109
|
+
}
|
|
110
|
+
async flushAll() {
|
|
111
|
+
const promises = [];
|
|
112
|
+
for (const cache of this.caches.values()) {
|
|
113
|
+
promises.push(cache.reset());
|
|
114
|
+
}
|
|
115
|
+
await Promise.all(promises);
|
|
116
|
+
}
|
|
117
|
+
async close() {
|
|
118
|
+
const promises = [];
|
|
119
|
+
for (const s of this.stores.values()) {
|
|
120
|
+
const { close, store } = s;
|
|
121
|
+
close && promises.push(close(store.store));
|
|
122
|
+
}
|
|
123
|
+
await Promise.all(promises);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
__name(_CacheManager, "CacheManager");
|
|
127
|
+
let CacheManager = _CacheManager;
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
CacheManager
|
|
131
|
+
});
|
package/lib/cache.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Cache as BasicCache, Milliseconds } from 'cache-manager';
|
|
2
|
+
export declare class Cache {
|
|
3
|
+
name: string;
|
|
4
|
+
prefix?: string;
|
|
5
|
+
store: BasicCache;
|
|
6
|
+
constructor({ name, prefix, store }: {
|
|
7
|
+
name: string;
|
|
8
|
+
store: BasicCache;
|
|
9
|
+
prefix?: string;
|
|
10
|
+
});
|
|
11
|
+
key(key: string): string;
|
|
12
|
+
set(key: string, value: unknown, ttl?: Milliseconds): Promise<void>;
|
|
13
|
+
get<T>(key: string): Promise<T>;
|
|
14
|
+
del(key: string): Promise<void>;
|
|
15
|
+
reset(): Promise<void>;
|
|
16
|
+
wrap<T>(key: string, fn: () => Promise<T>, ttl?: Milliseconds): Promise<T>;
|
|
17
|
+
wrapWithCondition<T>(key: string, fn: () => T | Promise<T>, options?: {
|
|
18
|
+
useCache?: boolean;
|
|
19
|
+
isCacheable?: (val: unknown) => boolean | Promise<boolean>;
|
|
20
|
+
ttl?: Milliseconds;
|
|
21
|
+
}): Promise<T>;
|
|
22
|
+
mset(args: [string, unknown][], ttl?: Milliseconds): Promise<void>;
|
|
23
|
+
mget(...args: string[]): Promise<unknown[]>;
|
|
24
|
+
mdel(...args: string[]): Promise<void>;
|
|
25
|
+
keys(pattern?: string): Promise<string[]>;
|
|
26
|
+
ttl(key: string): Promise<number>;
|
|
27
|
+
setValueInObject(key: string, objectKey: string, value: unknown): Promise<void>;
|
|
28
|
+
getValueInObject(key: string, objectKey: string): Promise<any>;
|
|
29
|
+
}
|
package/lib/cache.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var cache_exports = {};
|
|
20
|
+
__export(cache_exports, {
|
|
21
|
+
Cache: () => Cache
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(cache_exports);
|
|
24
|
+
const _Cache = class _Cache {
|
|
25
|
+
name;
|
|
26
|
+
prefix;
|
|
27
|
+
store;
|
|
28
|
+
constructor({ name, prefix, store }) {
|
|
29
|
+
this.name = name;
|
|
30
|
+
this.prefix = prefix;
|
|
31
|
+
this.store = store;
|
|
32
|
+
}
|
|
33
|
+
key(key) {
|
|
34
|
+
return this.prefix ? `${this.prefix}:${key}` : key;
|
|
35
|
+
}
|
|
36
|
+
async set(key, value, ttl) {
|
|
37
|
+
await this.store.set(this.key(key), value, ttl);
|
|
38
|
+
}
|
|
39
|
+
async get(key) {
|
|
40
|
+
return await this.store.get(this.key(key));
|
|
41
|
+
}
|
|
42
|
+
async del(key) {
|
|
43
|
+
await this.store.del(this.key(key));
|
|
44
|
+
}
|
|
45
|
+
async reset() {
|
|
46
|
+
await this.store.reset();
|
|
47
|
+
}
|
|
48
|
+
async wrap(key, fn, ttl) {
|
|
49
|
+
return await this.store.wrap(this.key(key), fn, ttl);
|
|
50
|
+
}
|
|
51
|
+
async wrapWithCondition(key, fn, options) {
|
|
52
|
+
const { useCache, isCacheable, ttl } = options || {};
|
|
53
|
+
if (useCache === false) {
|
|
54
|
+
return await fn();
|
|
55
|
+
}
|
|
56
|
+
const value = await this.get(key);
|
|
57
|
+
if (value) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
const result = await fn();
|
|
61
|
+
const cacheable = isCacheable ? await isCacheable(result) : result;
|
|
62
|
+
if (!cacheable) {
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
await this.set(key, result, ttl);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
async mset(args, ttl) {
|
|
69
|
+
await this.store.store.mset(
|
|
70
|
+
args.map(([key, value]) => [this.key(key), value]),
|
|
71
|
+
ttl
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
async mget(...args) {
|
|
75
|
+
args = args.map((key) => this.key(key));
|
|
76
|
+
return await this.store.store.mget(...args);
|
|
77
|
+
}
|
|
78
|
+
async mdel(...args) {
|
|
79
|
+
args = args.map((key) => this.key(key));
|
|
80
|
+
await this.store.store.mdel(...args);
|
|
81
|
+
}
|
|
82
|
+
async keys(pattern) {
|
|
83
|
+
const keys = await this.store.store.keys(pattern);
|
|
84
|
+
return keys.map((key) => key.replace(`${this.name}:`, ""));
|
|
85
|
+
}
|
|
86
|
+
async ttl(key) {
|
|
87
|
+
return await this.store.store.ttl(this.key(key));
|
|
88
|
+
}
|
|
89
|
+
async setValueInObject(key, objectKey, value) {
|
|
90
|
+
const object = await this.get(key) || {};
|
|
91
|
+
object[objectKey] = value;
|
|
92
|
+
await this.set(key, object);
|
|
93
|
+
}
|
|
94
|
+
async getValueInObject(key, objectKey) {
|
|
95
|
+
const object = await this.get(key) || {};
|
|
96
|
+
return object[objectKey];
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
__name(_Cache, "Cache");
|
|
100
|
+
let Cache = _Cache;
|
|
101
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
102
|
+
0 && (module.exports = {
|
|
103
|
+
Cache
|
|
104
|
+
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,30 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* be used for create cache {@link createCache}
|
|
4
|
-
*/
|
|
5
|
-
export type ICacheConfig = StoreConfig & CacheOptions & {
|
|
6
|
-
storePackage?: string;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* create a default cache config object
|
|
10
|
-
* @returns {ICacheConfig}
|
|
11
|
-
*/
|
|
12
|
-
export declare function createDefaultCacheConfig(): ICacheConfig;
|
|
13
|
-
/**
|
|
14
|
-
* cache and multi cache common method and only keep promise method
|
|
15
|
-
*/
|
|
16
|
-
export interface Cache {
|
|
17
|
-
set<T>(key: string, value: T, options?: CachingConfig): Promise<T>;
|
|
18
|
-
set<T>(key: string, value: T, ttl: number): Promise<T>;
|
|
19
|
-
wrap<T>(...args: WrapArgsType<T>[]): Promise<T>;
|
|
20
|
-
get<T>(key: string): Promise<T | undefined>;
|
|
21
|
-
del(key: string): Promise<any>;
|
|
22
|
-
reset(): Promise<void>;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* create cache
|
|
26
|
-
* <br/> if cacheConfig is array and length gt 1 then will be return multi cache, else will be return cache
|
|
27
|
-
* @param {ICacheConfig | ICacheConfig[]} cacheConfig
|
|
28
|
-
* @returns {Cache}
|
|
29
|
-
*/
|
|
30
|
-
export declare function createCache(cacheConfig?: ICacheConfig | ICacheConfig[]): Cache;
|
|
1
|
+
export * from './cache-manager';
|
|
2
|
+
export * from './cache';
|
package/lib/index.js
CHANGED
|
@@ -2,11 +2,6 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
5
|
var __copyProps = (to, from, except, desc) => {
|
|
11
6
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
7
|
for (let key of __getOwnPropNames(from))
|
|
@@ -15,48 +10,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
10
|
}
|
|
16
11
|
return to;
|
|
17
12
|
};
|
|
13
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
18
14
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
15
|
var src_exports = {};
|
|
20
|
-
__export(src_exports, {
|
|
21
|
-
createCache: () => createCache,
|
|
22
|
-
createDefaultCacheConfig: () => createDefaultCacheConfig
|
|
23
|
-
});
|
|
24
16
|
module.exports = __toCommonJS(src_exports);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
ttl: 86400,
|
|
29
|
-
// seconds
|
|
30
|
-
max: 1e3,
|
|
31
|
-
store: "memory"
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
__name(createDefaultCacheConfig, "createDefaultCacheConfig");
|
|
35
|
-
function createCache(cacheConfig = createDefaultCacheConfig()) {
|
|
36
|
-
if (Array.isArray(cacheConfig)) {
|
|
37
|
-
if (cacheConfig.length === 1) {
|
|
38
|
-
return createCacheByICacheConfig(cacheConfig[0]);
|
|
39
|
-
} else {
|
|
40
|
-
const caches = [];
|
|
41
|
-
for (const cacheConfigEle of cacheConfig) {
|
|
42
|
-
caches.push(createCacheByICacheConfig(cacheConfigEle));
|
|
43
|
-
}
|
|
44
|
-
return (0, import_cache_manager.multiCaching)(caches);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
return createCacheByICacheConfig(cacheConfig);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
__name(createCache, "createCache");
|
|
51
|
-
function createCacheByICacheConfig(cacheConfig) {
|
|
52
|
-
if (cacheConfig.storePackage) {
|
|
53
|
-
cacheConfig.store = require(cacheConfig.storePackage);
|
|
54
|
-
}
|
|
55
|
-
return (0, import_cache_manager.caching)(cacheConfig);
|
|
56
|
-
}
|
|
57
|
-
__name(createCacheByICacheConfig, "createCacheByICacheConfig");
|
|
17
|
+
__reExport(src_exports, require("./cache-manager"), module.exports);
|
|
18
|
+
__reExport(src_exports, require("./cache"), module.exports);
|
|
58
19
|
// Annotate the CommonJS export names for ESM import in node:
|
|
59
20
|
0 && (module.exports = {
|
|
60
|
-
|
|
61
|
-
|
|
21
|
+
...require("./cache-manager"),
|
|
22
|
+
...require("./cache")
|
|
62
23
|
});
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0-alpha.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"cache-manager": "^
|
|
9
|
+
"cache-manager": "^5.2.4",
|
|
10
|
+
"cache-manager-redis-yet": "^4.1.2"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {
|
|
12
|
-
"
|
|
13
|
-
"cache-manager-fs-hash": "^1.0.0"
|
|
13
|
+
"redis": "^4.6.10"
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
|
18
18
|
"directory": "packages/cache"
|
|
19
19
|
},
|
|
20
|
-
"gitHead": "
|
|
20
|
+
"gitHead": "e8aaf48d169448376a06d87f090786cd7649255e"
|
|
21
21
|
}
|