@carddb/browser 0.1.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 +82 -0
- package/dist/browser/index.js +330 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.cjs +127 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Cache } from '@carddb/core';
|
|
2
|
+
export * from '@carddb/client';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Browser-based cache implementations using localStorage/sessionStorage
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type StorageType = 'localStorage' | 'sessionStorage';
|
|
9
|
+
/**
|
|
10
|
+
* Browser cache using localStorage or sessionStorage.
|
|
11
|
+
*
|
|
12
|
+
* Uses localStorage by default for persistent caching across sessions.
|
|
13
|
+
* Use sessionStorage for per-session caching that clears when the tab is closed.
|
|
14
|
+
*
|
|
15
|
+
* @example localStorage (default, persistent)
|
|
16
|
+
* import { CardDBClient, BrowserCache } from '@carddb/browser'
|
|
17
|
+
*
|
|
18
|
+
* const client = new CardDBClient({
|
|
19
|
+
* cache: new BrowserCache(),
|
|
20
|
+
* cacheTtl: 300 // 5 minutes
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* @example sessionStorage (per-session)
|
|
24
|
+
* import { CardDBClient, BrowserCache } from '@carddb/browser'
|
|
25
|
+
*
|
|
26
|
+
* const client = new CardDBClient({
|
|
27
|
+
* cache: new BrowserCache({ storage: 'sessionStorage' }),
|
|
28
|
+
* cacheTtl: 300
|
|
29
|
+
* })
|
|
30
|
+
*
|
|
31
|
+
* @example Custom prefix
|
|
32
|
+
* const cache = new BrowserCache({ prefix: 'myapp:carddb:' })
|
|
33
|
+
*/
|
|
34
|
+
declare class BrowserCache implements Cache {
|
|
35
|
+
private storage;
|
|
36
|
+
private prefix;
|
|
37
|
+
constructor(options?: {
|
|
38
|
+
storage?: StorageType;
|
|
39
|
+
prefix?: string;
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Get a value from the cache
|
|
43
|
+
*/
|
|
44
|
+
get<T>(key: string): T | null;
|
|
45
|
+
/**
|
|
46
|
+
* Set a value in the cache
|
|
47
|
+
*
|
|
48
|
+
* @param key - The cache key
|
|
49
|
+
* @param value - The value to cache (must be JSON-serializable)
|
|
50
|
+
* @param ttl - Time to live in seconds (optional, undefined = no expiry)
|
|
51
|
+
*/
|
|
52
|
+
set<T>(key: string, value: T, ttl?: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* Delete a value from the cache
|
|
55
|
+
*/
|
|
56
|
+
delete(key: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clear all CardDB cached values (only keys with our prefix)
|
|
59
|
+
*/
|
|
60
|
+
clear(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a key exists and is not expired
|
|
63
|
+
*/
|
|
64
|
+
has(key: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Get approximate number of cached entries (only counts our prefixed keys)
|
|
67
|
+
*/
|
|
68
|
+
get size(): number;
|
|
69
|
+
/**
|
|
70
|
+
* Clean up expired entries
|
|
71
|
+
* Call this periodically if you want to reclaim storage space from expired entries
|
|
72
|
+
*/
|
|
73
|
+
cleanup(): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { BrowserCache };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export * from '@carddb/client';
|
|
2
|
+
|
|
3
|
+
// src/cache.ts
|
|
4
|
+
var BrowserCache = class {
|
|
5
|
+
storage;
|
|
6
|
+
prefix;
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
const storageType = options.storage ?? "localStorage";
|
|
9
|
+
this.storage = storageType === "sessionStorage" ? globalThis.sessionStorage : globalThis.localStorage;
|
|
10
|
+
this.prefix = options.prefix ?? "carddb:";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get a value from the cache
|
|
14
|
+
*/
|
|
15
|
+
get(key) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = this.storage.getItem(this.prefix + key);
|
|
18
|
+
if (!raw) return null;
|
|
19
|
+
const entry = JSON.parse(raw);
|
|
20
|
+
if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {
|
|
21
|
+
this.storage.removeItem(this.prefix + key);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return entry.value;
|
|
25
|
+
} catch {
|
|
26
|
+
this.storage.removeItem(this.prefix + key);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Set a value in the cache
|
|
32
|
+
*
|
|
33
|
+
* @param key - The cache key
|
|
34
|
+
* @param value - The value to cache (must be JSON-serializable)
|
|
35
|
+
* @param ttl - Time to live in seconds (optional, undefined = no expiry)
|
|
36
|
+
*/
|
|
37
|
+
set(key, value, ttl) {
|
|
38
|
+
try {
|
|
39
|
+
const entry = {
|
|
40
|
+
value,
|
|
41
|
+
expiresAt: ttl !== void 0 ? Date.now() + ttl * 1e3 : null
|
|
42
|
+
};
|
|
43
|
+
this.storage.setItem(this.prefix + key, JSON.stringify(entry));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn("[CardDB] Failed to write to cache:", e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Delete a value from the cache
|
|
50
|
+
*/
|
|
51
|
+
delete(key) {
|
|
52
|
+
this.storage.removeItem(this.prefix + key);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Clear all CardDB cached values (only keys with our prefix)
|
|
56
|
+
*/
|
|
57
|
+
clear() {
|
|
58
|
+
const keysToRemove = [];
|
|
59
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
60
|
+
const key = this.storage.key(i);
|
|
61
|
+
if (key && key.startsWith(this.prefix)) {
|
|
62
|
+
keysToRemove.push(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const key of keysToRemove) {
|
|
66
|
+
this.storage.removeItem(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a key exists and is not expired
|
|
71
|
+
*/
|
|
72
|
+
has(key) {
|
|
73
|
+
return this.get(key) !== null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get approximate number of cached entries (only counts our prefixed keys)
|
|
77
|
+
*/
|
|
78
|
+
get size() {
|
|
79
|
+
let count = 0;
|
|
80
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
81
|
+
const key = this.storage.key(i);
|
|
82
|
+
if (key && key.startsWith(this.prefix)) {
|
|
83
|
+
count++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return count;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Clean up expired entries
|
|
90
|
+
* Call this periodically if you want to reclaim storage space from expired entries
|
|
91
|
+
*/
|
|
92
|
+
cleanup() {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const keysToRemove = [];
|
|
95
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
96
|
+
const fullKey = this.storage.key(i);
|
|
97
|
+
if (fullKey && fullKey.startsWith(this.prefix)) {
|
|
98
|
+
try {
|
|
99
|
+
const raw = this.storage.getItem(fullKey);
|
|
100
|
+
if (raw) {
|
|
101
|
+
const entry = JSON.parse(raw);
|
|
102
|
+
if (entry.expiresAt !== null && now > entry.expiresAt) {
|
|
103
|
+
keysToRemove.push(fullKey);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
keysToRemove.push(fullKey);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const key of keysToRemove) {
|
|
112
|
+
this.storage.removeItem(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export { BrowserCache };
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
119
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cache.ts"],"names":[],"mappings":";;;AAsCO,IAAM,eAAN,MAAoC;AAAA,EACjC,OAAA;AAAA,EACA,MAAA;AAAA,EAER,WAAA,CAAY,OAAA,GAAsD,EAAC,EAAG;AACpE,IAAA,MAAM,WAAA,GAAc,QAAQ,OAAA,IAAW,cAAA;AACvC,IAAA,IAAA,CAAK,OAAA,GACH,WAAA,KAAgB,gBAAA,GAAmB,UAAA,CAAW,iBAAiB,UAAA,CAAW,YAAA;AAC5E,IAAA,IAAA,CAAK,MAAA,GAAS,QAAQ,MAAA,IAAU,SAAA;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAO,GAAA,EAAuB;AAC5B,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,SAAS,GAAG,CAAA;AAClD,MAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAEjB,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAG5B,MAAA,IAAI,MAAM,SAAA,KAAc,IAAA,IAAQ,KAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AAC5D,QAAA,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AACzC,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,OAAO,KAAA,CAAM,KAAA;AAAA,IACf,CAAA,CAAA,MAAQ;AAEN,MAAA,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AACzC,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,GAAA,CAAO,GAAA,EAAa,KAAA,EAAU,GAAA,EAAoB;AAChD,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAuB;AAAA,QAC3B,KAAA;AAAA,QACA,WAAW,GAAA,KAAQ,KAAA,CAAA,GAAY,KAAK,GAAA,EAAI,GAAI,MAAM,GAAA,GAAO;AAAA,OAC3D;AACA,MAAA,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,MAAA,GAAS,KAAK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,IAC/D,SAAS,CAAA,EAAG;AAGV,MAAA,OAAA,CAAQ,IAAA,CAAK,sCAAsC,CAAC,CAAA;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,GAAA,EAAmB;AACxB,IAAA,IAAA,CAAK,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,MAAM,eAAyB,EAAC;AAChC,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AAC5C,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA;AAC9B,MAAA,IAAI,GAAA,IAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA,EAAG;AACtC,QAAA,YAAA,CAAa,KAAK,GAAG,CAAA;AAAA,MACvB;AAAA,IACF;AACA,IAAA,KAAA,MAAW,OAAO,YAAA,EAAc;AAC9B,MAAA,IAAA,CAAK,OAAA,CAAQ,WAAW,GAAG,CAAA;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,GAAA,EAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,KAAM,IAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,GAAe;AACjB,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AAC5C,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA;AAC9B,MAAA,IAAI,GAAA,IAAO,GAAA,CAAI,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA,EAAG;AACtC,QAAA,KAAA,EAAA;AAAA,MACF;AAAA,IACF;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,IAAA,MAAM,eAAyB,EAAC;AAEhC,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,EAAA,EAAK;AAC5C,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA;AAClC,MAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,UAAA,CAAW,IAAA,CAAK,MAAM,CAAA,EAAG;AAC9C,QAAA,IAAI;AACF,UAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AACxC,UAAA,IAAI,GAAA,EAAK;AACP,YAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC5B,YAAA,IAAI,KAAA,CAAM,SAAA,KAAc,IAAA,IAAQ,GAAA,GAAM,MAAM,SAAA,EAAW;AACrD,cAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AAAA,YAC3B;AAAA,UACF;AAAA,QACF,CAAA,CAAA,MAAQ;AAEN,UAAA,YAAA,CAAa,KAAK,OAAO,CAAA;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,OAAO,YAAA,EAAc;AAC9B,MAAA,IAAA,CAAK,OAAA,CAAQ,WAAW,GAAG,CAAA;AAAA,IAC7B;AAAA,EACF;AACF","file":"index.js","sourcesContent":["/**\n * Browser-based cache implementations using localStorage/sessionStorage\n */\n\nimport type { Cache } from '@carddb/core'\n\ninterface CacheEntry<T> {\n value: T\n expiresAt: number | null\n}\n\ntype StorageType = 'localStorage' | 'sessionStorage'\n\n/**\n * Browser cache using localStorage or sessionStorage.\n *\n * Uses localStorage by default for persistent caching across sessions.\n * Use sessionStorage for per-session caching that clears when the tab is closed.\n *\n * @example localStorage (default, persistent)\n * import { CardDBClient, BrowserCache } from '@carddb/browser'\n *\n * const client = new CardDBClient({\n * cache: new BrowserCache(),\n * cacheTtl: 300 // 5 minutes\n * })\n *\n * @example sessionStorage (per-session)\n * import { CardDBClient, BrowserCache } from '@carddb/browser'\n *\n * const client = new CardDBClient({\n * cache: new BrowserCache({ storage: 'sessionStorage' }),\n * cacheTtl: 300\n * })\n *\n * @example Custom prefix\n * const cache = new BrowserCache({ prefix: 'myapp:carddb:' })\n */\nexport class BrowserCache implements Cache {\n private storage: Storage\n private prefix: string\n\n constructor(options: { storage?: StorageType; prefix?: string } = {}) {\n const storageType = options.storage ?? 'localStorage'\n this.storage =\n storageType === 'sessionStorage' ? globalThis.sessionStorage : globalThis.localStorage\n this.prefix = options.prefix ?? 'carddb:'\n }\n\n /**\n * Get a value from the cache\n */\n get<T>(key: string): T | null {\n try {\n const raw = this.storage.getItem(this.prefix + key)\n if (!raw) return null\n\n const entry = JSON.parse(raw) as CacheEntry<T>\n\n // Check if expired\n if (entry.expiresAt !== null && Date.now() > entry.expiresAt) {\n this.storage.removeItem(this.prefix + key)\n return null\n }\n\n return entry.value\n } catch {\n // If parsing fails, remove the invalid entry\n this.storage.removeItem(this.prefix + key)\n return null\n }\n }\n\n /**\n * Set a value in the cache\n *\n * @param key - The cache key\n * @param value - The value to cache (must be JSON-serializable)\n * @param ttl - Time to live in seconds (optional, undefined = no expiry)\n */\n set<T>(key: string, value: T, ttl?: number): void {\n try {\n const entry: CacheEntry<T> = {\n value,\n expiresAt: ttl !== undefined ? Date.now() + ttl * 1000 : null,\n }\n this.storage.setItem(this.prefix + key, JSON.stringify(entry))\n } catch (e) {\n // Storage might be full or disabled - silently fail\n // In the future, we could implement LRU eviction here\n console.warn('[CardDB] Failed to write to cache:', e)\n }\n }\n\n /**\n * Delete a value from the cache\n */\n delete(key: string): void {\n this.storage.removeItem(this.prefix + key)\n }\n\n /**\n * Clear all CardDB cached values (only keys with our prefix)\n */\n clear(): void {\n const keysToRemove: string[] = []\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i)\n if (key && key.startsWith(this.prefix)) {\n keysToRemove.push(key)\n }\n }\n for (const key of keysToRemove) {\n this.storage.removeItem(key)\n }\n }\n\n /**\n * Check if a key exists and is not expired\n */\n has(key: string): boolean {\n return this.get(key) !== null\n }\n\n /**\n * Get approximate number of cached entries (only counts our prefixed keys)\n */\n get size(): number {\n let count = 0\n for (let i = 0; i < this.storage.length; i++) {\n const key = this.storage.key(i)\n if (key && key.startsWith(this.prefix)) {\n count++\n }\n }\n return count\n }\n\n /**\n * Clean up expired entries\n * Call this periodically if you want to reclaim storage space from expired entries\n */\n cleanup(): void {\n const now = Date.now()\n const keysToRemove: string[] = []\n\n for (let i = 0; i < this.storage.length; i++) {\n const fullKey = this.storage.key(i)\n if (fullKey && fullKey.startsWith(this.prefix)) {\n try {\n const raw = this.storage.getItem(fullKey)\n if (raw) {\n const entry = JSON.parse(raw) as CacheEntry<unknown>\n if (entry.expiresAt !== null && now > entry.expiresAt) {\n keysToRemove.push(fullKey)\n }\n }\n } catch {\n // Invalid entry, remove it\n keysToRemove.push(fullKey)\n }\n }\n }\n\n for (const key of keysToRemove) {\n this.storage.removeItem(key)\n }\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carddb/browser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CardDB client for browsers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "bun test",
|
|
27
|
+
"lint": "tsc --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"keywords": ["carddb", "api", "client", "graphql", "card-games", "browser", "web"],
|
|
30
|
+
"author": "CardDB Team",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/xtda/carddb-js.git",
|
|
35
|
+
"directory": "packages/browser"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/xtda/carddb-js#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/xtda/carddb-js/issues"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@carddb/client": "0.1.0",
|
|
43
|
+
"@carddb/core": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"sideEffects": false
|
|
46
|
+
}
|