@nemigo/storage 1.5.0 → 1.7.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/dist/capacity.d.ts +12 -0
- package/dist/capacity.js +39 -0
- package/dist/cookie.d.ts +21 -27
- package/dist/cookie.js +10 -18
- package/dist/derived/vault.d.ts +66 -0
- package/dist/derived/vault.js +41 -0
- package/dist/internal/veil-prefix.d.ts +13 -0
- package/dist/internal/veil-prefix.js +16 -0
- package/dist/runtime.d.ts +11 -50
- package/dist/runtime.js +14 -93
- package/dist/types.d.ts +33 -13
- package/dist/web.d.ts +12 -20
- package/dist/web.js +12 -21
- package/package.json +12 -8
- package/dist/internal/index.d.ts +0 -1
- package/dist/internal/index.js +0 -10
- package/dist/internal/veil.d.ts +0 -5
- package/dist/internal/veil.js +0 -10
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RuntimeStorage } from "./runtime.js";
|
|
2
|
+
import type { IStorageSetOptions } from "./types.js";
|
|
3
|
+
export interface ConstructCapacityStorage {
|
|
4
|
+
capacity: number;
|
|
5
|
+
ttl_seconds?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class CapacityStorage extends RuntimeStorage {
|
|
8
|
+
capacity: number;
|
|
9
|
+
constructor({ capacity, ttl_seconds }: ConstructCapacityStorage);
|
|
10
|
+
get<T = any>(key: string): T | undefined;
|
|
11
|
+
set(key: string, value: any, options?: IStorageSetOptions): void;
|
|
12
|
+
}
|
package/dist/capacity.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RuntimeStorage } from "./runtime.js";
|
|
2
|
+
export class CapacityStorage extends RuntimeStorage {
|
|
3
|
+
capacity;
|
|
4
|
+
constructor({ capacity, ttl_seconds }) {
|
|
5
|
+
super(ttl_seconds);
|
|
6
|
+
this.capacity = capacity;
|
|
7
|
+
}
|
|
8
|
+
get(key) {
|
|
9
|
+
const entry = super.__get(key);
|
|
10
|
+
if (!entry)
|
|
11
|
+
return;
|
|
12
|
+
// Обновляем порядок использования, чтобы сохранить от LRU-удаления
|
|
13
|
+
this.__storage.delete(key);
|
|
14
|
+
this.__storage.set(key, entry);
|
|
15
|
+
return entry.value;
|
|
16
|
+
}
|
|
17
|
+
set(key, value, options) {
|
|
18
|
+
this.__storage.delete(key);
|
|
19
|
+
if (this.__storage.size >= this.capacity) {
|
|
20
|
+
// Удаляем устаревшие элементы
|
|
21
|
+
for (const existingKey of this.__storage.keys()) {
|
|
22
|
+
const entry = this.__storage.get(existingKey);
|
|
23
|
+
if (!entry || this.__isExpired(entry)) {
|
|
24
|
+
this.__storage.delete(existingKey);
|
|
25
|
+
}
|
|
26
|
+
if (this.__storage.size < this.capacity)
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
// Если всё ещё переполнен — удаляем LRU
|
|
30
|
+
if (this.__storage.size >= this.capacity) {
|
|
31
|
+
const firstKey = this.__storage.keys().next().value;
|
|
32
|
+
if (firstKey !== undefined) {
|
|
33
|
+
this.__storage.delete(firstKey);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
super.set(key, value, options);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/cookie.d.ts
CHANGED
|
@@ -1,48 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { ConstructVeilPrefixStorage } from "./internal/veil-prefix.js";
|
|
2
|
+
import { VeilPrefixStorage } from "./internal/veil-prefix.js";
|
|
3
|
+
import type { IStorage, IStorageValue, IStorageSetOptions } from "./types.js";
|
|
3
4
|
import type { SetDocumentCookieOptions } from "@nemigo/helpers/html/cookie";
|
|
4
|
-
/**
|
|
5
|
-
* Серверные readonly-куки для {@link CookieStorage}
|
|
6
|
-
*/
|
|
7
|
-
export interface ServerCookie {
|
|
8
|
-
name: string;
|
|
9
|
-
value: string;
|
|
10
|
-
}
|
|
11
5
|
/**
|
|
12
6
|
* Опции для {@link CookieStorage.set}
|
|
13
7
|
*/
|
|
14
|
-
export interface
|
|
8
|
+
export interface CookieStorageSetOptions extends SetDocumentCookieOptions, IStorageSetOptions {
|
|
15
9
|
prefix?: string;
|
|
16
10
|
}
|
|
17
11
|
/**
|
|
18
12
|
* Опции для {@link CookieStorage.delete}
|
|
19
13
|
*/
|
|
20
|
-
export interface
|
|
14
|
+
export interface CookieStorageDeleteOptions {
|
|
21
15
|
Path?: string;
|
|
22
16
|
prefix?: string;
|
|
23
17
|
}
|
|
24
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Серверные readonly-куки для {@link CookieStorage}
|
|
20
|
+
*/
|
|
21
|
+
export interface ServerCookie {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ConstructCookieStorage extends ConstructVeilPrefixStorage {
|
|
26
|
+
cookies?: ServerCookie[];
|
|
27
|
+
}
|
|
28
|
+
export declare class CookieStorage extends VeilPrefixStorage implements IStorage<void> {
|
|
25
29
|
__server: Map<string, string>;
|
|
26
|
-
|
|
27
|
-
* TTL (time-to-live) хранилища
|
|
28
|
-
*/
|
|
29
|
-
ttl_seconds?: number | null;
|
|
30
|
-
/**
|
|
31
|
-
* @remarks `options.ttl_seconds === null` делает хранение сессионным!
|
|
32
|
-
*/
|
|
33
|
-
constructor({ cookies, prefix, ttl_seconds }: {
|
|
34
|
-
cookies?: ServerCookie[];
|
|
35
|
-
prefix?: string;
|
|
36
|
-
ttl_seconds?: number | null;
|
|
37
|
-
});
|
|
30
|
+
constructor(ctx?: ConstructCookieStorage);
|
|
38
31
|
fill(cookies?: ServerCookie[]): void;
|
|
39
32
|
__unveil<T>(value?: string | null): T | undefined;
|
|
33
|
+
has(name: string): boolean;
|
|
40
34
|
get<T extends IStorageValue>(name: string, prefix?: string): T | undefined;
|
|
41
35
|
/**
|
|
42
|
-
* @remarks `options.ttl_seconds ===
|
|
36
|
+
* @remarks `options.ttl_seconds === undefined` делает хранение сессионным!
|
|
43
37
|
*/
|
|
44
|
-
set(name: string, value: IStorageValue, options?:
|
|
45
|
-
delete(name: string, options?:
|
|
38
|
+
set(name: string, value: IStorageValue, options?: CookieStorageSetOptions): void;
|
|
39
|
+
delete(name: string, options?: CookieStorageDeleteOptions): void;
|
|
46
40
|
/**
|
|
47
41
|
* Очистка серверных кук
|
|
48
42
|
*/
|
package/dist/cookie.js
CHANGED
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
import { VeilPrefixStorage } from "./internal/veil.js";
|
|
1
|
+
import { VeilPrefixStorage } from "./internal/veil-prefix.js";
|
|
2
2
|
import { isSSR } from "@nemigo/helpers/html";
|
|
3
3
|
import { deleteDocumentCookie, getDocumentCookie, setDocumentCookie } from "@nemigo/helpers/html/cookie";
|
|
4
4
|
import { unveil, veil } from "@nemigo/helpers/veil";
|
|
5
5
|
export class CookieStorage extends VeilPrefixStorage {
|
|
6
6
|
__server = new Map();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
ttl_seconds;
|
|
11
|
-
/**
|
|
12
|
-
* @remarks `options.ttl_seconds === null` делает хранение сессионным!
|
|
13
|
-
*/
|
|
14
|
-
constructor({ cookies, prefix, ttl_seconds }) {
|
|
15
|
-
super(prefix);
|
|
16
|
-
this.fill(cookies);
|
|
17
|
-
this.ttl_seconds = ttl_seconds;
|
|
7
|
+
constructor(ctx = {}) {
|
|
8
|
+
super(ctx);
|
|
9
|
+
this.fill(ctx.cookies);
|
|
18
10
|
}
|
|
19
11
|
fill(cookies = []) {
|
|
20
12
|
for (const item of cookies)
|
|
@@ -24,19 +16,19 @@ export class CookieStorage extends VeilPrefixStorage {
|
|
|
24
16
|
__unveil(value) {
|
|
25
17
|
return value ? unveil(value) : undefined;
|
|
26
18
|
}
|
|
19
|
+
has(name) {
|
|
20
|
+
return this.get(name) !== undefined;
|
|
21
|
+
}
|
|
27
22
|
get(name, prefix) {
|
|
28
23
|
const key = this.__toKey(name, prefix);
|
|
29
24
|
return this.__unveil(isSSR ? this.__server.get(key) : getDocumentCookie(key));
|
|
30
25
|
}
|
|
31
|
-
//...
|
|
32
26
|
/**
|
|
33
|
-
* @remarks `options.ttl_seconds ===
|
|
27
|
+
* @remarks `options.ttl_seconds === undefined` делает хранение сессионным!
|
|
34
28
|
*/
|
|
35
29
|
set(name, value, options = {}) {
|
|
36
|
-
if (isSSR)
|
|
37
|
-
|
|
38
|
-
const MaxAge = options.ttl_seconds !== undefined ? options.ttl_seconds : this.ttl_seconds;
|
|
39
|
-
setDocumentCookie(this.__toKey(name, options.prefix), veil(value), { ...options, MaxAge });
|
|
30
|
+
if (!isSSR)
|
|
31
|
+
setDocumentCookie(this.__toKey(name, options.prefix), veil(value), { ...options, MaxAge: options.ttl_seconds });
|
|
40
32
|
}
|
|
41
33
|
delete(name, options = {}) {
|
|
42
34
|
if (!isSSR)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CanBePromise } from "@nemigo/helpers/types";
|
|
2
|
+
import type { IStorage } from "@nemigo/storage/types";
|
|
3
|
+
export interface VaultItem<PayloadData> {
|
|
4
|
+
/**
|
|
5
|
+
* Полезная нагрузка
|
|
6
|
+
*/
|
|
7
|
+
pld: PayloadData;
|
|
8
|
+
/**
|
|
9
|
+
* Время создания (timestamp в секундах)
|
|
10
|
+
*/
|
|
11
|
+
iat: number;
|
|
12
|
+
/**
|
|
13
|
+
* Абсолютное время истечения (timestamp в секундах). Если не задано — не устаревает
|
|
14
|
+
*/
|
|
15
|
+
exp?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Абсолютное время, до которого нельзя обновлять (timestamp в секундах)
|
|
18
|
+
*/
|
|
19
|
+
upd?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface VaultTimings {
|
|
22
|
+
/**
|
|
23
|
+
* Срок действия данных (в секундах). Если не задано — данные не устаревают
|
|
24
|
+
*/
|
|
25
|
+
exp?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Минимальный интервал до следующего обновления (в секундах).
|
|
28
|
+
* Если не задано — обновление разрешено в любой момент
|
|
29
|
+
*/
|
|
30
|
+
upd?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface VaultRunOptions<PayloadData> extends VaultTimings {
|
|
33
|
+
/**
|
|
34
|
+
* Использовать иные данные вместо генерации
|
|
35
|
+
*/
|
|
36
|
+
overload?: PayloadData;
|
|
37
|
+
/**
|
|
38
|
+
* Принудительно перегенерировать, игнорируя тайминги
|
|
39
|
+
*/
|
|
40
|
+
force?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* TTL в хранилище (в секундах, дельта от текущего момента)
|
|
43
|
+
*/
|
|
44
|
+
ttl_seconds?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface ConstructVault<PayloadData> extends VaultTimings {
|
|
47
|
+
/**
|
|
48
|
+
* Функция генерации полезной нагрузки
|
|
49
|
+
*/
|
|
50
|
+
generate: (delta: VaultTimings, now_seconds: number) => CanBePromise<PayloadData>;
|
|
51
|
+
/**
|
|
52
|
+
* Функция сравнения двух полезных нагрузок на эквивалентность
|
|
53
|
+
*/
|
|
54
|
+
equal: (a: PayloadData, b: PayloadData) => CanBePromise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
export declare class Vault<PayloadData, Storage extends IStorage = IStorage> {
|
|
57
|
+
storage: Storage;
|
|
58
|
+
ctx: ConstructVault<PayloadData>;
|
|
59
|
+
constructor(storage: Storage, ctx: ConstructVault<PayloadData>);
|
|
60
|
+
find(key: string): CanBePromise<VaultItem<PayloadData> | undefined>;
|
|
61
|
+
check(key: string, pld: PayloadData): Promise<boolean>;
|
|
62
|
+
run(key: string, options?: VaultRunOptions<PayloadData>): Promise<{
|
|
63
|
+
generate: boolean;
|
|
64
|
+
item: VaultItem<PayloadData>;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class Vault {
|
|
2
|
+
storage;
|
|
3
|
+
ctx;
|
|
4
|
+
constructor(storage, ctx) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this.ctx = ctx;
|
|
7
|
+
}
|
|
8
|
+
find(key) {
|
|
9
|
+
return this.storage.get(key);
|
|
10
|
+
}
|
|
11
|
+
async check(key, pld) {
|
|
12
|
+
const existed = await this.find(key);
|
|
13
|
+
if (!existed)
|
|
14
|
+
return false;
|
|
15
|
+
return this.ctx.equal(existed.pld, pld);
|
|
16
|
+
}
|
|
17
|
+
async run(key, options = {}) {
|
|
18
|
+
const { ttl_seconds, exp = this.ctx.exp, upd = this.ctx.upd, force = false, overload } = options;
|
|
19
|
+
// работаем в секундах
|
|
20
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
21
|
+
const existed = await this.find(key);
|
|
22
|
+
if (existed && !force) {
|
|
23
|
+
const expired = existed.exp !== undefined && nowSec > existed.exp;
|
|
24
|
+
const tooEarlyToUpdate = existed.upd !== undefined && nowSec < existed.upd;
|
|
25
|
+
if (!expired && !tooEarlyToUpdate) {
|
|
26
|
+
return { generate: false, item: existed };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Вычисляем абсолютные времена для сохранения
|
|
30
|
+
const newExp = exp !== undefined ? nowSec + exp : undefined;
|
|
31
|
+
const newUpd = upd !== undefined ? nowSec + upd : undefined;
|
|
32
|
+
const item = {
|
|
33
|
+
pld: overload ?? (await this.ctx.generate({ exp, upd }, nowSec)),
|
|
34
|
+
iat: nowSec,
|
|
35
|
+
exp: newExp,
|
|
36
|
+
upd: newUpd,
|
|
37
|
+
};
|
|
38
|
+
await this.storage.set(key, item, { ttl_seconds });
|
|
39
|
+
return { generate: true, item };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ConstructVeilPrefixStorage {
|
|
2
|
+
prefix?: string;
|
|
3
|
+
ttl_seconds?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare abstract class VeilPrefixStorage {
|
|
6
|
+
/**
|
|
7
|
+
* TTL хранилища для быстрого использования
|
|
8
|
+
*/
|
|
9
|
+
ttl_seconds?: number;
|
|
10
|
+
prefix: string;
|
|
11
|
+
constructor({ ttl_seconds, prefix }?: ConstructVeilPrefixStorage);
|
|
12
|
+
__toKey(name: string, prefix?: string): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { veil } from "@nemigo/helpers/veil";
|
|
2
|
+
export class VeilPrefixStorage {
|
|
3
|
+
/**
|
|
4
|
+
* TTL хранилища для быстрого использования
|
|
5
|
+
*/
|
|
6
|
+
ttl_seconds;
|
|
7
|
+
prefix;
|
|
8
|
+
// noinspection TypeScriptAbstractClassConstructorCanBeMadeProtected
|
|
9
|
+
constructor({ ttl_seconds, prefix = "" } = {}) {
|
|
10
|
+
this.prefix = prefix;
|
|
11
|
+
this.ttl_seconds = ttl_seconds;
|
|
12
|
+
}
|
|
13
|
+
__toKey(name, prefix = this.prefix) {
|
|
14
|
+
return prefix + veil(name);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,59 +1,20 @@
|
|
|
1
|
-
import type { IStorage, IStorageValue,
|
|
1
|
+
import type { IStorage, IStorageValue, IStorageSetOptions } from "./types.js";
|
|
2
2
|
import type { Timestamp } from "@nemigo/helpers/types";
|
|
3
|
-
interface
|
|
3
|
+
export interface EntryValue {
|
|
4
4
|
value: IStorageValue;
|
|
5
5
|
expired?: Timestamp;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* - Значения удаляются при превышении максимального размера или по истечении заданного TTL
|
|
11
|
-
* - TTL для каждого значения используется из хранилища (если задано), но можно указать и индивидуальное
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```typescript
|
|
15
|
-
* const storage = new RuntimeStorage({ max: 2, ttl_minutes: 5 });
|
|
16
|
-
* storage.set('one', 1);
|
|
17
|
-
* storage.set('two', 2);
|
|
18
|
-
* storage.set('three', 3); // Если хранилище переполнено, удалится наименее недавно использованное или устаревшее значение
|
|
19
|
-
* console.log(storage.get('one')); // undefined, если значение устарело или удалено
|
|
20
|
-
* console.log(storage.get('two')); // 2
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export declare class RuntimeStorage implements IStorage {
|
|
24
|
-
__storage: Map<string, InternalValue>;
|
|
7
|
+
export declare class RuntimeStorage implements IStorage<boolean> {
|
|
8
|
+
__storage: Map<string, EntryValue>;
|
|
25
9
|
/**
|
|
26
|
-
*
|
|
10
|
+
* TTL хранилища для быстрого использования
|
|
27
11
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param options - Настройки хранилища
|
|
35
|
-
* @param [options.max=200] - Максимальное количество значений в хранилище
|
|
36
|
-
* @param [options.ttl_seconds] - TTL (time-to-live) хранилища в **секундах**
|
|
37
|
-
*/
|
|
38
|
-
constructor({ max, ttl_seconds }?: {
|
|
39
|
-
max?: number;
|
|
40
|
-
ttl_seconds?: number | null;
|
|
41
|
-
});
|
|
42
|
-
__isExpired(entry: InternalValue): boolean;
|
|
12
|
+
ttl_seconds?: number;
|
|
13
|
+
constructor(ttl_seconds?: number);
|
|
14
|
+
__isExpired(entry: EntryValue): boolean;
|
|
15
|
+
__get(key: string): EntryValue | undefined;
|
|
16
|
+
has(key: string): boolean;
|
|
43
17
|
get<T = any>(key: string): T | undefined;
|
|
44
|
-
|
|
45
|
-
* Устанавливает значение в кэш
|
|
46
|
-
*
|
|
47
|
-
* - Если индивидуальный TTL в **секундах** не указан, используется TTL хранилища (если задано)
|
|
48
|
-
* - Если ключ уже существует, его значение обновляется и перемещается в конец
|
|
49
|
-
* - Если размер хранимых значений превышает максимальное значение, производится очистка устаревших
|
|
50
|
-
* - Если после очистки хранилище всё ещё переполнено, удаляется наименее недавно использованное значение
|
|
51
|
-
*/
|
|
52
|
-
set(key: string, value: any, options?: SetIStorageOptions): void;
|
|
53
|
-
size(): number;
|
|
54
|
-
shake(): void;
|
|
18
|
+
set(key: string, value: any, options?: IStorageSetOptions): void;
|
|
55
19
|
delete(key: string): boolean;
|
|
56
|
-
clear(): void;
|
|
57
|
-
has(key: string): boolean;
|
|
58
20
|
}
|
|
59
|
-
export {};
|
package/dist/runtime.js
CHANGED
|
@@ -1,116 +1,37 @@
|
|
|
1
|
-
import { getTTL } from "./internal/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* LRU (Least Recently Used) runtime-хранилище данных, имплементирующее интерфейс {@link IStorage}
|
|
4
|
-
*
|
|
5
|
-
* - Значения удаляются при превышении максимального размера или по истечении заданного TTL
|
|
6
|
-
* - TTL для каждого значения используется из хранилища (если задано), но можно указать и индивидуальное
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```typescript
|
|
10
|
-
* const storage = new RuntimeStorage({ max: 2, ttl_minutes: 5 });
|
|
11
|
-
* storage.set('one', 1);
|
|
12
|
-
* storage.set('two', 2);
|
|
13
|
-
* storage.set('three', 3); // Если хранилище переполнено, удалится наименее недавно использованное или устаревшее значение
|
|
14
|
-
* console.log(storage.get('one')); // undefined, если значение устарело или удалено
|
|
15
|
-
* console.log(storage.get('two')); // 2
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
1
|
export class RuntimeStorage {
|
|
19
2
|
__storage;
|
|
20
3
|
/**
|
|
21
|
-
*
|
|
4
|
+
* TTL хранилища для быстрого использования
|
|
22
5
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* TTL (time-to-live) хранилища в миллисекундах
|
|
26
|
-
*/
|
|
27
|
-
ttl;
|
|
28
|
-
/**
|
|
29
|
-
* @param options - Настройки хранилища
|
|
30
|
-
* @param [options.max=200] - Максимальное количество значений в хранилище
|
|
31
|
-
* @param [options.ttl_seconds] - TTL (time-to-live) хранилища в **секундах**
|
|
32
|
-
*/
|
|
33
|
-
constructor({ max = 200, ttl_seconds } = {}) {
|
|
34
|
-
this.max = max;
|
|
35
|
-
this.ttl = ttl_seconds ? ttl_seconds * 1000 : ttl_seconds;
|
|
6
|
+
ttl_seconds;
|
|
7
|
+
constructor(ttl_seconds) {
|
|
36
8
|
this.__storage = new Map();
|
|
9
|
+
this.ttl_seconds = ttl_seconds;
|
|
37
10
|
}
|
|
38
11
|
__isExpired(entry) {
|
|
39
12
|
return Boolean(entry.expired && Date.now() > entry.expired);
|
|
40
13
|
}
|
|
41
|
-
|
|
14
|
+
__get(key) {
|
|
42
15
|
const entry = this.__storage.get(key);
|
|
43
16
|
if (!entry)
|
|
44
|
-
return
|
|
17
|
+
return;
|
|
45
18
|
if (this.__isExpired(entry)) {
|
|
46
19
|
this.__storage.delete(key);
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
// Обновляем порядок использования, чтобы сохранить от LRU-удаления
|
|
50
|
-
this.__storage.delete(key);
|
|
51
|
-
this.__storage.set(key, entry);
|
|
52
|
-
return entry.value;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Устанавливает значение в кэш
|
|
56
|
-
*
|
|
57
|
-
* - Если индивидуальный TTL в **секундах** не указан, используется TTL хранилища (если задано)
|
|
58
|
-
* - Если ключ уже существует, его значение обновляется и перемещается в конец
|
|
59
|
-
* - Если размер хранимых значений превышает максимальное значение, производится очистка устаревших
|
|
60
|
-
* - Если после очистки хранилище всё ещё переполнено, удаляется наименее недавно использованное значение
|
|
61
|
-
*/
|
|
62
|
-
set(key, value, options) {
|
|
63
|
-
if (this.__storage.has(key)) {
|
|
64
|
-
this.__storage.delete(key);
|
|
65
20
|
}
|
|
66
21
|
else {
|
|
67
|
-
|
|
68
|
-
// Удаляем устаревшие элементы
|
|
69
|
-
for (const existingKey of this.__storage.keys()) {
|
|
70
|
-
const entry = this.__storage.get(existingKey);
|
|
71
|
-
if (!entry || this.__isExpired(entry)) {
|
|
72
|
-
this.__storage.delete(existingKey);
|
|
73
|
-
}
|
|
74
|
-
if (this.__storage.size < this.max)
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
// Если всё ещё переполнен — удаляем LRU
|
|
78
|
-
if (this.__storage.size >= this.max) {
|
|
79
|
-
const firstKey = this.__storage.keys().next().value;
|
|
80
|
-
if (firstKey !== undefined) {
|
|
81
|
-
this.__storage.delete(firstKey);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
22
|
+
return entry;
|
|
85
23
|
}
|
|
86
|
-
const expired = getTTL(options?.ttl_seconds, this.ttl);
|
|
87
|
-
this.__storage.set(key, expired ? { value, expired } : { value });
|
|
88
24
|
}
|
|
89
|
-
|
|
90
|
-
return this.
|
|
25
|
+
has(key) {
|
|
26
|
+
return Boolean(this.__get(key));
|
|
27
|
+
}
|
|
28
|
+
get(key) {
|
|
29
|
+
return this.__get(key)?.value;
|
|
91
30
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const entry = this.__storage.get(existingKey);
|
|
95
|
-
if (!entry || this.__isExpired(entry)) {
|
|
96
|
-
this.__storage.delete(existingKey);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
31
|
+
set(key, value, options) {
|
|
32
|
+
this.__storage.set(key, options?.ttl_seconds ? { value, expired: options.ttl_seconds * 1000 } : { value });
|
|
99
33
|
}
|
|
100
34
|
delete(key) {
|
|
101
35
|
return this.__storage.delete(key);
|
|
102
36
|
}
|
|
103
|
-
clear() {
|
|
104
|
-
this.__storage.clear();
|
|
105
|
-
}
|
|
106
|
-
has(key) {
|
|
107
|
-
const entry = this.__storage.get(key);
|
|
108
|
-
if (!entry)
|
|
109
|
-
return false;
|
|
110
|
-
if (this.__isExpired(entry)) {
|
|
111
|
-
this.__storage.delete(key);
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
37
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,33 +6,53 @@ export type IStorageValue = object | string | boolean | number | null;
|
|
|
6
6
|
/**
|
|
7
7
|
* Опции для {@link IStorage.set}
|
|
8
8
|
*/
|
|
9
|
-
export interface
|
|
9
|
+
export interface IStorageSetOptions {
|
|
10
10
|
/**
|
|
11
|
-
* @remarks `
|
|
11
|
+
* @remarks `undefined` поведение может быть особым у разных имплементаций!
|
|
12
12
|
*/
|
|
13
|
-
ttl_seconds?: number
|
|
13
|
+
ttl_seconds?: number;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Интерфейс для синхронных хранилищ данных для полиморфизма
|
|
17
17
|
*
|
|
18
18
|
* Используется только там, где нельзя использовать {@link IStorage}
|
|
19
19
|
*/
|
|
20
|
-
export interface ISyncStorage {
|
|
21
|
-
get<T extends IStorageValue>(
|
|
22
|
-
set(
|
|
20
|
+
export interface ISyncStorage<Delete extends boolean | void = boolean | void> {
|
|
21
|
+
get<T extends IStorageValue>(name: string): T | undefined;
|
|
22
|
+
set(name: string, value: IStorageValue, options?: IStorageSetOptions): void;
|
|
23
23
|
/**
|
|
24
|
-
* @return `true`, если оно существовало. Допускаются имплементации без
|
|
24
|
+
* @return `true`, если оно существовало. Допускаются имплементации без этого
|
|
25
25
|
*/
|
|
26
|
-
delete(
|
|
26
|
+
delete(name: string): Delete;
|
|
27
|
+
/**
|
|
28
|
+
* Обычно просто `Boolean(this.get(name))`, но могут быть более производительные способы
|
|
29
|
+
*/
|
|
30
|
+
has(name: string): boolean;
|
|
27
31
|
}
|
|
28
32
|
/**
|
|
29
33
|
* Интерфейс для всех хранилищ данных для полиморфизма
|
|
30
34
|
*/
|
|
31
|
-
export interface IStorage {
|
|
32
|
-
get<T extends IStorageValue>(
|
|
33
|
-
set(
|
|
35
|
+
export interface IStorage<Delete extends boolean | void = boolean | void> {
|
|
36
|
+
get<T extends IStorageValue>(name: string): CanBePromise<T | undefined>;
|
|
37
|
+
set(name: string, value: IStorageValue, options?: IStorageSetOptions): CanBePromise;
|
|
38
|
+
/**
|
|
39
|
+
* @return `true`, если оно существовало. Допускаются имплементации без этого
|
|
40
|
+
*/
|
|
41
|
+
delete(name: string): CanBePromise<Delete>;
|
|
34
42
|
/**
|
|
35
|
-
*
|
|
43
|
+
* Обычно просто `Boolean(this.get(name))`, но могут быть более производительные способы
|
|
36
44
|
*/
|
|
37
|
-
|
|
45
|
+
has(name: string): CanBePromise<boolean>;
|
|
46
|
+
}
|
|
47
|
+
export interface ISyncStoragePanic<Delete extends boolean | void = boolean | void> {
|
|
48
|
+
onhas?(err: unknown): boolean | never;
|
|
49
|
+
onget?<V extends IStorageValue = IStorageValue>(err: unknown): V | undefined | never;
|
|
50
|
+
ondelete?(err: unknown): Delete | never;
|
|
51
|
+
onset?(err: unknown): undefined | never;
|
|
52
|
+
}
|
|
53
|
+
export interface IStoragePanic<Delete extends boolean | void = boolean | void> {
|
|
54
|
+
onhas?(err: unknown): CanBePromise<boolean | never>;
|
|
55
|
+
onget?<V extends IStorageValue = IStorageValue>(err: unknown): CanBePromise<V | undefined | never>;
|
|
56
|
+
ondelete?(err: unknown): CanBePromise<Delete | never>;
|
|
57
|
+
onset?(err: unknown): CanBePromise<undefined | never>;
|
|
38
58
|
}
|
package/dist/web.d.ts
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import { VeilPrefixStorage } from "./internal/veil.js";
|
|
2
|
-
import type { IStorage, IStorageValue,
|
|
1
|
+
import { VeilPrefixStorage } from "./internal/veil-prefix.js";
|
|
2
|
+
import type { IStorage, IStorageValue, IStorageSetOptions } from "./types.js";
|
|
3
|
+
import type { Timestamp } from "@nemigo/helpers/types";
|
|
3
4
|
export type WebStorageType = "local" | "session";
|
|
4
5
|
export interface WebStorageOptions {
|
|
5
6
|
type?: WebStorageType;
|
|
6
7
|
prefix?: string;
|
|
7
8
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* @param options - Настройки хранилища
|
|
15
|
-
* @param [options.ttl_seconds] - TTL (time-to-live) хранилища в **секундах**
|
|
16
|
-
*/
|
|
17
|
-
constructor({ ttl_seconds, prefix }?: {
|
|
18
|
-
prefix?: string;
|
|
19
|
-
ttl_seconds?: number | null;
|
|
20
|
-
});
|
|
9
|
+
export interface EntryValue {
|
|
10
|
+
value: IStorageValue;
|
|
11
|
+
expired?: Timestamp;
|
|
12
|
+
}
|
|
13
|
+
export declare class WebStorage extends VeilPrefixStorage implements IStorage<void> {
|
|
14
|
+
__isExpired(entry: EntryValue): boolean;
|
|
21
15
|
__get<T>(storage: Storage, key: string): T | undefined;
|
|
16
|
+
has(name: string, options?: WebStorageOptions): boolean;
|
|
22
17
|
get<T extends IStorageValue>(name: string, options?: WebStorageOptions): T | undefined;
|
|
23
|
-
set(name: string, value: IStorageValue, options?:
|
|
24
|
-
delete(name: string, options?:
|
|
25
|
-
type?: WebStorageType;
|
|
26
|
-
prefix?: string;
|
|
27
|
-
}): boolean | void;
|
|
18
|
+
set(name: string, value: IStorageValue, options?: IStorageSetOptions & WebStorageOptions): void;
|
|
19
|
+
delete(name: string, options?: WebStorageOptions): void;
|
|
28
20
|
}
|
package/dist/web.js
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { VeilPrefixStorage } from "./internal/veil.js";
|
|
1
|
+
import { VeilPrefixStorage } from "./internal/veil-prefix.js";
|
|
3
2
|
import { isSSR } from "@nemigo/helpers/html";
|
|
4
3
|
import { unveil, veil } from "@nemigo/helpers/veil";
|
|
5
4
|
export class WebStorage extends VeilPrefixStorage {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
ttl;
|
|
10
|
-
/**
|
|
11
|
-
* @param options - Настройки хранилища
|
|
12
|
-
* @param [options.ttl_seconds] - TTL (time-to-live) хранилища в **секундах**
|
|
13
|
-
*/
|
|
14
|
-
constructor({ ttl_seconds, prefix } = {}) {
|
|
15
|
-
super(prefix);
|
|
16
|
-
this.ttl = ttl_seconds ? ttl_seconds * 1000 : ttl_seconds;
|
|
5
|
+
__isExpired(entry) {
|
|
6
|
+
return Boolean(entry.expired && Date.now() > entry.expired);
|
|
17
7
|
}
|
|
18
8
|
__get(storage, key) {
|
|
19
9
|
const entry = storage.getItem(key);
|
|
20
10
|
if (!entry)
|
|
21
11
|
return;
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
12
|
+
const unveiled = unveil(entry);
|
|
13
|
+
if (this.__isExpired(unveiled)) {
|
|
24
14
|
storage.removeItem(key);
|
|
25
15
|
}
|
|
26
16
|
else {
|
|
27
|
-
return
|
|
17
|
+
return unveiled.value;
|
|
28
18
|
}
|
|
29
19
|
}
|
|
20
|
+
has(name, options) {
|
|
21
|
+
return Boolean(this.get(name, options));
|
|
22
|
+
}
|
|
30
23
|
get(name, options = {}) {
|
|
31
24
|
if (isSSR)
|
|
32
25
|
return;
|
|
@@ -44,13 +37,11 @@ export class WebStorage extends VeilPrefixStorage {
|
|
|
44
37
|
if (isSSR)
|
|
45
38
|
return;
|
|
46
39
|
const key = this.__toKey(name, options.prefix);
|
|
47
|
-
const
|
|
48
|
-
const internalValue = expired ? { value, expired } : { value };
|
|
49
|
-
const _value = veil(internalValue);
|
|
40
|
+
const veiled = veil(options.ttl_seconds ? { value, expired: options.ttl_seconds * 1000 } : { value });
|
|
50
41
|
if (options.type === "session")
|
|
51
|
-
sessionStorage.setItem(key,
|
|
42
|
+
sessionStorage.setItem(key, veiled);
|
|
52
43
|
else
|
|
53
|
-
localStorage.setItem(key,
|
|
44
|
+
localStorage.setItem(key, veiled);
|
|
54
45
|
}
|
|
55
46
|
delete(name, options = {}) {
|
|
56
47
|
if (isSSR)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nemigo/storage",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Vlad Logvin",
|
|
@@ -18,17 +18,21 @@
|
|
|
18
18
|
"format": "prettier --write ./"
|
|
19
19
|
},
|
|
20
20
|
"exports": {
|
|
21
|
+
"./capacity": {
|
|
22
|
+
"types": "./dist/capacity.d.ts",
|
|
23
|
+
"default": "./dist/capacity.js"
|
|
24
|
+
},
|
|
21
25
|
"./cookie": {
|
|
22
26
|
"types": "./dist/cookie.d.ts",
|
|
23
27
|
"default": "./dist/cookie.js"
|
|
24
28
|
},
|
|
25
|
-
"./
|
|
26
|
-
"types": "./dist/
|
|
27
|
-
"default": "./dist/
|
|
29
|
+
"./derived/vault": {
|
|
30
|
+
"types": "./dist/derived/vault.d.ts",
|
|
31
|
+
"default": "./dist/derived/vault.js"
|
|
28
32
|
},
|
|
29
|
-
"./internal/veil": {
|
|
30
|
-
"types": "./dist/internal/veil.d.ts",
|
|
31
|
-
"default": "./dist/internal/veil.js"
|
|
33
|
+
"./internal/veil-prefix": {
|
|
34
|
+
"types": "./dist/internal/veil-prefix.d.ts",
|
|
35
|
+
"default": "./dist/internal/veil-prefix.js"
|
|
32
36
|
},
|
|
33
37
|
"./runtime": {
|
|
34
38
|
"types": "./dist/runtime.d.ts",
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
}
|
|
45
49
|
},
|
|
46
50
|
"peerDependencies": {
|
|
47
|
-
"@nemigo/helpers": ">=1.
|
|
51
|
+
"@nemigo/helpers": ">=1.6.0"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@nemigo/configs": "workspace:*",
|
package/dist/internal/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const getTTL: (entry_in_seconds?: number | null, storage?: number | null) => number | null | undefined;
|
package/dist/internal/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const getTTL = (entry_in_seconds, storage) => {
|
|
2
|
-
if (entry_in_seconds)
|
|
3
|
-
return Date.now() + entry_in_seconds * 1000;
|
|
4
|
-
if (entry_in_seconds === null)
|
|
5
|
-
return null;
|
|
6
|
-
if (storage)
|
|
7
|
-
return Date.now() + storage;
|
|
8
|
-
if (storage === null)
|
|
9
|
-
return null;
|
|
10
|
-
};
|
package/dist/internal/veil.d.ts
DELETED
package/dist/internal/veil.js
DELETED