@nocobase/cache 1.6.0-beta.3 → 1.6.0-beta.5

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.
@@ -6,7 +6,6 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { RedisStore } from 'cache-manager-redis-yet';
10
9
  import { BloomFilter } from '.';
11
10
  import { Cache } from '../cache';
12
11
  /**
@@ -15,7 +14,7 @@ import { Cache } from '../cache';
15
14
  export declare class RedisBloomFilter implements BloomFilter {
16
15
  cache: Cache;
17
16
  constructor(cache: Cache);
18
- getStore(): RedisStore<import("redis").RedisClientType>;
17
+ private get store();
19
18
  reserve(key: string, errorRate: number, capacity: number): Promise<void>;
20
19
  add(key: string, value: string): Promise<void>;
21
20
  mAdd(key: string, values: string[]): Promise<void>;
@@ -36,24 +36,27 @@ const _RedisBloomFilter = class _RedisBloomFilter {
36
36
  constructor(cache) {
37
37
  this.cache = cache;
38
38
  }
39
- getStore() {
39
+ get store() {
40
40
  return this.cache.store.store;
41
41
  }
42
42
  async reserve(key, errorRate, capacity) {
43
- const store = this.getStore();
44
- await store.client.bf.reserve(key, errorRate, capacity);
43
+ try {
44
+ await this.store.client.bf.reserve(key, errorRate, capacity);
45
+ } catch (error) {
46
+ if (error.message.includes("ERR item exists")) {
47
+ return;
48
+ }
49
+ throw error;
50
+ }
45
51
  }
46
52
  async add(key, value) {
47
- const store = this.getStore();
48
- await store.client.bf.add(key, value);
53
+ await this.store.client.bf.add(key, value);
49
54
  }
50
55
  async mAdd(key, values) {
51
- const store = this.getStore();
52
- await store.client.bf.mAdd(key, values);
56
+ await this.store.client.bf.mAdd(key, values);
53
57
  }
54
58
  async exists(key, value) {
55
- const store = this.getStore();
56
- return await store.client.bf.exists(key, value);
59
+ return this.store.client.bf.exists(key, value);
57
60
  }
58
61
  };
59
62
  __name(_RedisBloomFilter, "RedisBloomFilter");
@@ -9,6 +9,8 @@
9
9
  import { FactoryStore, Store } from 'cache-manager';
10
10
  import { Cache } from './cache';
11
11
  import { BloomFilter } from './bloom-filter';
12
+ import { Counter } from './counter';
13
+ import { LockManager } from '@nocobase/lock-manager';
12
14
  type StoreOptions = {
13
15
  store?: 'memory' | FactoryStore<Store, any>;
14
16
  close?: (store: Store) => Promise<void>;
@@ -19,9 +21,11 @@ export type CacheManagerOptions = Partial<{
19
21
  stores: {
20
22
  [storeType: string]: StoreOptions;
21
23
  };
24
+ prefix: string;
22
25
  }>;
23
26
  export declare class CacheManager {
24
27
  defaultStore: string;
28
+ prefix?: string;
25
29
  private stores;
26
30
  /**
27
31
  * @internal
@@ -52,5 +56,13 @@ export declare class CacheManager {
52
56
  createBloomFilter(options?: {
53
57
  store?: string;
54
58
  }): Promise<BloomFilter>;
59
+ /**
60
+ * @experimental
61
+ */
62
+ createCounter(options: {
63
+ name: string;
64
+ prefix?: string;
65
+ store?: string;
66
+ }, lockManager?: LockManager): Promise<Counter>;
55
67
  }
56
68
  export {};
@@ -47,8 +47,10 @@ var import_cache_manager_redis_yet = require("cache-manager-redis-yet");
47
47
  var import_deepmerge = __toESM(require("deepmerge"));
48
48
  var import_memory_bloom_filter = require("./bloom-filter/memory-bloom-filter");
49
49
  var import_redis_bloom_filter = require("./bloom-filter/redis-bloom-filter");
50
+ var import_counter = require("./counter");
50
51
  const _CacheManager = class _CacheManager {
51
52
  defaultStore;
53
+ prefix;
52
54
  stores = /* @__PURE__ */ new Map();
53
55
  /**
54
56
  * @internal
@@ -80,8 +82,9 @@ const _CacheManager = class _CacheManager {
80
82
  }
81
83
  };
82
84
  const cacheOptions = (0, import_deepmerge.default)(defaultOptions, options || {});
83
- const { defaultStore = "memory", stores } = cacheOptions;
85
+ const { defaultStore = "memory", stores, prefix } = cacheOptions;
84
86
  this.defaultStore = defaultStore;
87
+ this.prefix = prefix;
85
88
  for (const [name, store] of Object.entries(stores)) {
86
89
  const { store: s, ...globalConfig } = store;
87
90
  this.registerStore({ name, store: s, ...globalConfig });
@@ -109,7 +112,9 @@ const _CacheManager = class _CacheManager {
109
112
  return cache;
110
113
  }
111
114
  async createCache(options) {
112
- const { name, prefix, store = this.defaultStore, ...config } = options;
115
+ const { name, store = this.defaultStore, ...config } = options;
116
+ let { prefix } = options;
117
+ prefix = this.prefix ? prefix ? `${this.prefix}:${prefix}` : this.prefix : prefix;
113
118
  if (!import_lodash.default.isEmpty(config) || store === "memory") {
114
119
  const newStore = await this.createStore({ name, storeType: store, ...config });
115
120
  return this.newCache({ name, prefix, store: newStore });
@@ -164,6 +169,31 @@ const _CacheManager = class _CacheManager {
164
169
  throw new Error(`BloomFilter store [${store}] is not supported`);
165
170
  }
166
171
  }
172
+ /**
173
+ * @experimental
174
+ */
175
+ async createCounter(options, lockManager) {
176
+ const { store = this.defaultStore, name, prefix } = options || {};
177
+ let cache;
178
+ if (store !== "memory") {
179
+ try {
180
+ cache = this.getCache(name);
181
+ } catch (error) {
182
+ cache = await this.createCache({ name, store, prefix });
183
+ }
184
+ }
185
+ switch (store) {
186
+ case "memory":
187
+ return new import_counter.MemoryCounter();
188
+ case "redis":
189
+ return new import_counter.RedisCounter(cache);
190
+ default:
191
+ if (!lockManager) {
192
+ throw new Error(`Counter store [${store}] is not supported`);
193
+ }
194
+ return new import_counter.LockCounter(cache, lockManager);
195
+ }
196
+ }
167
197
  };
168
198
  __name(_CacheManager, "CacheManager");
169
199
  let CacheManager = _CacheManager;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ /**
10
+ * @experimental
11
+ * atomic counter
12
+ */
13
+ export interface Counter {
14
+ get(key: string): Promise<number>;
15
+ incr(key: string): Promise<number>;
16
+ incr(key: string, ttl: number): Promise<number>;
17
+ incrby(key: string, val: number): Promise<number>;
18
+ incrby(key: string, val: number, ttl: number): Promise<number>;
19
+ reset(key: string): Promise<void>;
20
+ }
21
+ export { MemoryCounter } from './memory-counter';
22
+ export { RedisCounter } from './redis-counter';
23
+ export { LockCounter } from './lock-counter';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var counter_exports = {};
28
+ __export(counter_exports, {
29
+ LockCounter: () => import_lock_counter.LockCounter,
30
+ MemoryCounter: () => import_memory_counter.MemoryCounter,
31
+ RedisCounter: () => import_redis_counter.RedisCounter
32
+ });
33
+ module.exports = __toCommonJS(counter_exports);
34
+ var import_memory_counter = require("./memory-counter");
35
+ var import_redis_counter = require("./redis-counter");
36
+ var import_lock_counter = require("./lock-counter");
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ LockCounter,
40
+ MemoryCounter,
41
+ RedisCounter
42
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { LockManager } from '@nocobase/lock-manager';
10
+ import { Counter as ICounter } from '.';
11
+ import { Cache } from '../cache';
12
+ /**
13
+ * @experimental
14
+ */
15
+ export declare class LockCounter implements ICounter {
16
+ cache: Cache;
17
+ lockManager: LockManager;
18
+ constructor(cache: Cache, lockManager: LockManager);
19
+ get(key: string): Promise<number>;
20
+ incr(key: string, ttl?: number): Promise<number>;
21
+ incrby(key: string, value: number, ttl?: number): Promise<number>;
22
+ reset(key: string): Promise<void>;
23
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var lock_counter_exports = {};
29
+ __export(lock_counter_exports, {
30
+ LockCounter: () => LockCounter
31
+ });
32
+ module.exports = __toCommonJS(lock_counter_exports);
33
+ const _LockCounter = class _LockCounter {
34
+ cache;
35
+ lockManager;
36
+ constructor(cache, lockManager) {
37
+ this.cache = cache;
38
+ this.lockManager = lockManager;
39
+ }
40
+ async get(key) {
41
+ return await this.cache.get(key) || 0;
42
+ }
43
+ async incr(key, ttl) {
44
+ return this.incrby(key, 1, ttl);
45
+ }
46
+ async incrby(key, value, ttl) {
47
+ const lockKey = `lock:${key}`;
48
+ const release = await this.lockManager.acquire(lockKey, 3e3);
49
+ try {
50
+ const v = await this.cache.get(key);
51
+ const n = v || 0;
52
+ const newValue = n + value;
53
+ await this.cache.set(key, newValue, ttl);
54
+ return newValue;
55
+ } catch (error) {
56
+ throw error;
57
+ } finally {
58
+ await release();
59
+ }
60
+ }
61
+ async reset(key) {
62
+ return this.cache.del(key);
63
+ }
64
+ };
65
+ __name(_LockCounter, "LockCounter");
66
+ let LockCounter = _LockCounter;
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ LockCounter
70
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Counter as ICounter } from '.';
10
+ declare class Cache {
11
+ data: Map<any, any>;
12
+ timers: Map<any, any>;
13
+ set(k: string, v: any, ttl?: number): void;
14
+ get(k: string): any;
15
+ del(k: string): boolean;
16
+ }
17
+ /**
18
+ * @experimental
19
+ */
20
+ export declare class MemoryCounter implements ICounter {
21
+ cache: Cache;
22
+ get(key: string): Promise<any>;
23
+ incr(key: string, ttl?: number): Promise<any>;
24
+ incrby(key: string, value: number, ttl?: number): Promise<any>;
25
+ reset(key: string): Promise<void>;
26
+ }
27
+ export {};
@@ -0,0 +1,89 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var memory_counter_exports = {};
29
+ __export(memory_counter_exports, {
30
+ MemoryCounter: () => MemoryCounter
31
+ });
32
+ module.exports = __toCommonJS(memory_counter_exports);
33
+ const _Cache = class _Cache {
34
+ data = /* @__PURE__ */ new Map();
35
+ timers = /* @__PURE__ */ new Map();
36
+ set(k, v, ttl) {
37
+ if (ttl) {
38
+ if (this.timers.has(k)) {
39
+ clearTimeout(this.timers.get(k));
40
+ }
41
+ this.timers.set(
42
+ k,
43
+ setTimeout(() => this.del(k), ttl)
44
+ );
45
+ }
46
+ this.data.set(k, v);
47
+ }
48
+ get(k) {
49
+ return this.data.get(k);
50
+ }
51
+ del(k) {
52
+ if (this.timers.has(k)) {
53
+ clearTimeout(this.timers.get(k));
54
+ }
55
+ this.timers.delete(k);
56
+ return this.data.delete(k);
57
+ }
58
+ };
59
+ __name(_Cache, "Cache");
60
+ let Cache = _Cache;
61
+ const _MemoryCounter = class _MemoryCounter {
62
+ cache = new Cache();
63
+ async get(key) {
64
+ return this.cache.get(key) || 0;
65
+ }
66
+ async incr(key, ttl) {
67
+ return this.incrby(key, 1, ttl);
68
+ }
69
+ async incrby(key, value, ttl) {
70
+ const v = this.cache.get(key);
71
+ const n = v || 0;
72
+ const newValue = n + value;
73
+ if (!v) {
74
+ this.cache.set(key, newValue, ttl);
75
+ } else {
76
+ this.cache.set(key, newValue);
77
+ }
78
+ return newValue;
79
+ }
80
+ async reset(key) {
81
+ this.cache.del(key);
82
+ }
83
+ };
84
+ __name(_MemoryCounter, "MemoryCounter");
85
+ let MemoryCounter = _MemoryCounter;
86
+ // Annotate the CommonJS export names for ESM import in node:
87
+ 0 && (module.exports = {
88
+ MemoryCounter
89
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Counter as ICounter } from '.';
10
+ import { Cache } from '../cache';
11
+ /**
12
+ * @experimental
13
+ */
14
+ export declare class RedisCounter implements ICounter {
15
+ cache: Cache;
16
+ scriptSha: string;
17
+ constructor(cache: Cache);
18
+ private get store();
19
+ get(key: string): Promise<number>;
20
+ incr(key: string, ttl?: number): Promise<number>;
21
+ incrby(key: string, value: number, ttl?: number): Promise<number>;
22
+ reset(key: string): Promise<void>;
23
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var redis_counter_exports = {};
29
+ __export(redis_counter_exports, {
30
+ RedisCounter: () => RedisCounter
31
+ });
32
+ module.exports = __toCommonJS(redis_counter_exports);
33
+ const script = `
34
+ local key = KEYS[1]
35
+ local value = tonumber(ARGV[1]) or 1
36
+ local ttl = tonumber(ARGV[2])
37
+ local current = redis.call('INCRBY', key, value)
38
+ if tonumber(current) == value and ttl then
39
+ redis.call('PEXPIRE', key, ttl)
40
+ end
41
+ return current
42
+ `;
43
+ const _RedisCounter = class _RedisCounter {
44
+ cache;
45
+ scriptSha;
46
+ constructor(cache) {
47
+ this.cache = cache;
48
+ }
49
+ get store() {
50
+ return this.cache.store.store;
51
+ }
52
+ async get(key) {
53
+ return await this.cache.get(key) || 0;
54
+ }
55
+ async incr(key, ttl) {
56
+ return this.incrby(key, 1, ttl);
57
+ }
58
+ async incrby(key, value, ttl) {
59
+ if (!this.scriptSha) {
60
+ this.scriptSha = await this.store.client.scriptLoad(script);
61
+ }
62
+ const result = await this.store.client.evalSha(this.scriptSha, {
63
+ keys: [this.cache.key(key)],
64
+ arguments: [value, ttl].map((v) => v ? v.toString() : "")
65
+ });
66
+ return Number(result);
67
+ }
68
+ async reset(key) {
69
+ return this.cache.del(key);
70
+ }
71
+ };
72
+ __name(_RedisCounter, "RedisCounter");
73
+ let RedisCounter = _RedisCounter;
74
+ // Annotate the CommonJS export names for ESM import in node:
75
+ 0 && (module.exports = {
76
+ RedisCounter
77
+ });
package/lib/index.d.ts CHANGED
@@ -9,3 +9,4 @@
9
9
  export * from './cache-manager';
10
10
  export * from './cache';
11
11
  export * from './bloom-filter';
12
+ export * from './counter';
package/lib/index.js CHANGED
@@ -26,9 +26,11 @@ module.exports = __toCommonJS(src_exports);
26
26
  __reExport(src_exports, require("./cache-manager"), module.exports);
27
27
  __reExport(src_exports, require("./cache"), module.exports);
28
28
  __reExport(src_exports, require("./bloom-filter"), module.exports);
29
+ __reExport(src_exports, require("./counter"), module.exports);
29
30
  // Annotate the CommonJS export names for ESM import in node:
30
31
  0 && (module.exports = {
31
32
  ...require("./cache-manager"),
32
33
  ...require("./cache"),
33
- ...require("./bloom-filter")
34
+ ...require("./bloom-filter"),
35
+ ...require("./counter")
34
36
  });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@nocobase/cache",
3
- "version": "1.6.0-beta.3",
3
+ "version": "1.6.0-beta.5",
4
4
  "description": "",
5
5
  "license": "AGPL-3.0",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
8
8
  "dependencies": {
9
+ "@nocobase/lock-manager": "1.6.0-alpha.6",
9
10
  "bloom-filters": "^3.0.1",
10
11
  "cache-manager": "^5.2.4",
11
12
  "cache-manager-redis-yet": "^4.1.2"
@@ -18,5 +19,5 @@
18
19
  "url": "git+https://github.com/nocobase/nocobase.git",
19
20
  "directory": "packages/cache"
20
21
  },
21
- "gitHead": "19a659c49d7eedd3d57e46f9df2e8540f55c99b7"
22
+ "gitHead": "66e7c7239b29361bc60543886d107ba0e00085d9"
22
23
  }