strata-storage 1.0.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 +113 -0
- package/android/src/main/java/com/strata/storage/EncryptedStorage.java +65 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +147 -0
- package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +74 -0
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +256 -0
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts +46 -0
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/FilesystemAdapter.js +162 -0
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts +46 -0
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/PreferencesAdapter.js +162 -0
- package/dist/adapters/capacitor/SecureAdapter.d.ts +69 -0
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/SecureAdapter.js +214 -0
- package/dist/adapters/capacitor/SqliteAdapter.d.ts +68 -0
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitor/SqliteAdapter.js +277 -0
- package/dist/adapters/capacitor/index.d.ts +9 -0
- package/dist/adapters/capacitor/index.d.ts.map +1 -0
- package/dist/adapters/capacitor/index.js +8 -0
- package/dist/adapters/web/CacheAdapter.d.ts +91 -0
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -0
- package/dist/adapters/web/CacheAdapter.js +291 -0
- package/dist/adapters/web/CookieAdapter.d.ts +77 -0
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -0
- package/dist/adapters/web/CookieAdapter.js +260 -0
- package/dist/adapters/web/IndexedDBAdapter.d.ts +78 -0
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -0
- package/dist/adapters/web/IndexedDBAdapter.js +371 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts +63 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/web/LocalStorageAdapter.js +238 -0
- package/dist/adapters/web/MemoryAdapter.d.ts +69 -0
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -0
- package/dist/adapters/web/MemoryAdapter.js +165 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts +53 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/web/SessionStorageAdapter.js +180 -0
- package/dist/adapters/web/index.d.ts +10 -0
- package/dist/adapters/web/index.d.ts.map +1 -0
- package/dist/adapters/web/index.js +9 -0
- package/dist/core/AdapterRegistry.d.ts +52 -0
- package/dist/core/AdapterRegistry.d.ts.map +1 -0
- package/dist/core/AdapterRegistry.js +102 -0
- package/dist/core/BaseAdapter.d.ts +79 -0
- package/dist/core/BaseAdapter.d.ts.map +1 -0
- package/dist/core/BaseAdapter.js +197 -0
- package/dist/core/StorageStrategy.d.ts +55 -0
- package/dist/core/StorageStrategy.d.ts.map +1 -0
- package/dist/core/StorageStrategy.js +199 -0
- package/dist/core/Strata.d.ts +122 -0
- package/dist/core/Strata.d.ts.map +1 -0
- package/dist/core/Strata.js +568 -0
- package/dist/features/compression.d.ts +65 -0
- package/dist/features/compression.d.ts.map +1 -0
- package/dist/features/compression.js +205 -0
- package/dist/features/encryption.d.ts +68 -0
- package/dist/features/encryption.d.ts.map +1 -0
- package/dist/features/encryption.js +172 -0
- package/dist/features/migration.d.ts +17 -0
- package/dist/features/migration.d.ts.map +1 -0
- package/dist/features/migration.js +43 -0
- package/dist/features/query.d.ts +75 -0
- package/dist/features/query.d.ts.map +1 -0
- package/dist/features/query.js +305 -0
- package/dist/features/sync.d.ts +87 -0
- package/dist/features/sync.d.ts.map +1 -0
- package/dist/features/sync.js +233 -0
- package/dist/features/ttl.d.ts +124 -0
- package/dist/features/ttl.d.ts.map +1 -0
- package/dist/features/ttl.js +236 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/package.json +60 -0
- package/dist/plugin/definitions.d.ts +219 -0
- package/dist/plugin/definitions.d.ts.map +1 -0
- package/dist/plugin/definitions.js +5 -0
- package/dist/plugin/index.d.ts +8 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +27 -0
- package/dist/plugin/web.d.ts +24 -0
- package/dist/plugin/web.d.ts.map +1 -0
- package/dist/plugin/web.js +35 -0
- package/dist/types/index.d.ts +558 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/utils/errors.d.ts +92 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +153 -0
- package/dist/utils/index.d.ts +105 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +329 -0
- package/ios/Plugin/KeychainStorage.swift +87 -0
- package/ios/Plugin/SQLiteStorage.swift +167 -0
- package/ios/Plugin/StrataStoragePlugin.swift +204 -0
- package/ios/Plugin/UserDefaultsStorage.swift +44 -0
- package/package.json +126 -0
- package/scripts/build.js +52 -0
- package/scripts/cli.js +60 -0
- package/scripts/configure.js +444 -0
- package/scripts/postinstall.js +33 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error classes for Strata Storage
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Base error class for all Strata errors
|
|
6
|
+
*/
|
|
7
|
+
export class StrataError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(message, code, details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'StrataError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
// Maintains proper stack trace for where our error was thrown
|
|
16
|
+
if (Error.captureStackTrace) {
|
|
17
|
+
Error.captureStackTrace(this, this.constructor);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when storage quota is exceeded
|
|
23
|
+
*/
|
|
24
|
+
export class QuotaExceededError extends StrataError {
|
|
25
|
+
constructor(message = 'Storage quota exceeded', details) {
|
|
26
|
+
super(message, 'QUOTA_EXCEEDED', details);
|
|
27
|
+
this.name = 'QuotaExceededError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when adapter is not available
|
|
32
|
+
*/
|
|
33
|
+
export class AdapterNotAvailableError extends StrataError {
|
|
34
|
+
constructor(adapterName, details) {
|
|
35
|
+
super(`Storage adapter '${adapterName}' is not available`, 'ADAPTER_NOT_AVAILABLE', details);
|
|
36
|
+
this.name = 'AdapterNotAvailableError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Error thrown when operation is not supported
|
|
41
|
+
*/
|
|
42
|
+
export class NotSupportedError extends StrataError {
|
|
43
|
+
constructor(operation, adapterName, details) {
|
|
44
|
+
const message = adapterName
|
|
45
|
+
? `Operation '${operation}' is not supported by ${adapterName} adapter`
|
|
46
|
+
: `Operation '${operation}' is not supported`;
|
|
47
|
+
super(message, 'NOT_SUPPORTED', details);
|
|
48
|
+
this.name = 'NotSupportedError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Error thrown when encryption fails
|
|
53
|
+
*/
|
|
54
|
+
export class EncryptionError extends StrataError {
|
|
55
|
+
constructor(message = 'Encryption operation failed', details) {
|
|
56
|
+
super(message, 'ENCRYPTION_ERROR', details);
|
|
57
|
+
this.name = 'EncryptionError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Error thrown when compression fails
|
|
62
|
+
*/
|
|
63
|
+
export class CompressionError extends StrataError {
|
|
64
|
+
constructor(message = 'Compression operation failed', details) {
|
|
65
|
+
super(message, 'COMPRESSION_ERROR', details);
|
|
66
|
+
this.name = 'CompressionError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Error thrown when serialization fails
|
|
71
|
+
*/
|
|
72
|
+
export class SerializationError extends StrataError {
|
|
73
|
+
constructor(message = 'Serialization failed', details) {
|
|
74
|
+
super(message, 'SERIALIZATION_ERROR', details);
|
|
75
|
+
this.name = 'SerializationError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Error thrown when validation fails
|
|
80
|
+
*/
|
|
81
|
+
export class ValidationError extends StrataError {
|
|
82
|
+
constructor(message, details) {
|
|
83
|
+
super(message, 'VALIDATION_ERROR', details);
|
|
84
|
+
this.name = 'ValidationError';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Error thrown when transaction fails
|
|
89
|
+
*/
|
|
90
|
+
export class TransactionError extends StrataError {
|
|
91
|
+
constructor(message = 'Transaction failed', details) {
|
|
92
|
+
super(message, 'TRANSACTION_ERROR', details);
|
|
93
|
+
this.name = 'TransactionError';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Error thrown when migration fails
|
|
98
|
+
*/
|
|
99
|
+
export class MigrationError extends StrataError {
|
|
100
|
+
constructor(message, details) {
|
|
101
|
+
super(message, 'MIGRATION_ERROR', details);
|
|
102
|
+
this.name = 'MigrationError';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Error thrown when sync operation fails
|
|
107
|
+
*/
|
|
108
|
+
export class SyncError extends StrataError {
|
|
109
|
+
constructor(message = 'Sync operation failed', details) {
|
|
110
|
+
super(message, 'SYNC_ERROR', details);
|
|
111
|
+
this.name = 'SyncError';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if error is a Strata error
|
|
116
|
+
*/
|
|
117
|
+
export function isStrataError(error) {
|
|
118
|
+
return error instanceof StrataError;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* General storage error
|
|
122
|
+
*/
|
|
123
|
+
export class StorageError extends StrataError {
|
|
124
|
+
constructor(message, details) {
|
|
125
|
+
super(message, 'STORAGE_ERROR', details);
|
|
126
|
+
this.name = 'StorageError';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Error thrown when a key is not found
|
|
131
|
+
*/
|
|
132
|
+
export class NotFoundError extends StrataError {
|
|
133
|
+
constructor(key, details) {
|
|
134
|
+
super(`Key '${key}' not found`, 'NOT_FOUND', details);
|
|
135
|
+
this.name = 'NotFoundError';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Check if error is a quota exceeded error
|
|
140
|
+
*/
|
|
141
|
+
export function isQuotaError(error) {
|
|
142
|
+
if (error instanceof QuotaExceededError)
|
|
143
|
+
return true;
|
|
144
|
+
// Check for native quota errors
|
|
145
|
+
if (error instanceof Error) {
|
|
146
|
+
const message = error.message.toLowerCase();
|
|
147
|
+
return (message.includes('quota') ||
|
|
148
|
+
message.includes('storage exhausted') ||
|
|
149
|
+
error.name === 'QuotaExceededError' ||
|
|
150
|
+
error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for Strata Storage
|
|
3
|
+
* Zero dependencies - all utilities implemented from scratch
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if code is running in a browser environment
|
|
7
|
+
*/
|
|
8
|
+
export declare function isBrowser(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Check if code is running in Node.js
|
|
11
|
+
*/
|
|
12
|
+
export declare function isNode(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Check if code is running in a Web Worker
|
|
15
|
+
*/
|
|
16
|
+
export declare function isWebWorker(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check if code is running in Capacitor
|
|
19
|
+
*/
|
|
20
|
+
export declare function isCapacitor(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Deep clone an object (no dependencies!)
|
|
23
|
+
*/
|
|
24
|
+
export declare function deepClone<T>(obj: T): T;
|
|
25
|
+
/**
|
|
26
|
+
* Deep merge objects
|
|
27
|
+
*/
|
|
28
|
+
export declare function deepMerge<T extends Record<string, unknown>>(target: T, ...sources: Partial<T>[]): T;
|
|
29
|
+
/**
|
|
30
|
+
* Check if value is a plain object
|
|
31
|
+
*/
|
|
32
|
+
export declare function isObject(item: unknown): item is Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Generate a unique ID
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateId(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Simple glob pattern matching
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchGlob(pattern: string, str: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Format bytes to human readable
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatBytes(bytes: number, decimals?: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* Parse size string to bytes
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseSize(size: string | number): number;
|
|
49
|
+
/**
|
|
50
|
+
* Debounce function
|
|
51
|
+
*/
|
|
52
|
+
export declare function debounce<T extends (...args: unknown[]) => unknown>(func: T, wait: number): (...args: Parameters<T>) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Throttle function
|
|
55
|
+
*/
|
|
56
|
+
export declare function throttle<T extends (...args: unknown[]) => unknown>(func: T, limit: number): (...args: Parameters<T>) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Promise with timeout
|
|
59
|
+
*/
|
|
60
|
+
export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage?: string): Promise<T>;
|
|
61
|
+
/**
|
|
62
|
+
* Retry with exponential backoff
|
|
63
|
+
*/
|
|
64
|
+
export declare function retry<T>(fn: () => Promise<T>, options?: {
|
|
65
|
+
maxRetries?: number;
|
|
66
|
+
initialDelay?: number;
|
|
67
|
+
maxDelay?: number;
|
|
68
|
+
factor?: number;
|
|
69
|
+
}): Promise<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Sleep for specified milliseconds
|
|
72
|
+
*/
|
|
73
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Create a deferred promise
|
|
76
|
+
*/
|
|
77
|
+
export declare function createDeferred<T>(): {
|
|
78
|
+
promise: Promise<T>;
|
|
79
|
+
resolve: (value: T) => void;
|
|
80
|
+
reject: (reason?: unknown) => void;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Serialize value to JSON with support for special types
|
|
84
|
+
*/
|
|
85
|
+
export declare function serialize(value: unknown): string;
|
|
86
|
+
/**
|
|
87
|
+
* Deserialize JSON with support for special types
|
|
88
|
+
*/
|
|
89
|
+
export declare function deserialize(json: string): unknown;
|
|
90
|
+
/**
|
|
91
|
+
* Calculate object size in bytes (rough estimate)
|
|
92
|
+
*/
|
|
93
|
+
export declare function getObjectSize(obj: unknown): number;
|
|
94
|
+
/**
|
|
95
|
+
* Create a simple event emitter
|
|
96
|
+
*/
|
|
97
|
+
export declare class EventEmitter {
|
|
98
|
+
private events;
|
|
99
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
100
|
+
off(event: string, handler: (...args: unknown[]) => void): void;
|
|
101
|
+
emit(event: string, ...args: unknown[]): void;
|
|
102
|
+
once(event: string, handler: (...args: unknown[]) => void): void;
|
|
103
|
+
removeAllListeners(event?: string): void;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for Strata Storage
|
|
3
|
+
* Zero dependencies - all utilities implemented from scratch
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if code is running in a browser environment
|
|
7
|
+
*/
|
|
8
|
+
export function isBrowser() {
|
|
9
|
+
return typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check if code is running in Node.js
|
|
13
|
+
*/
|
|
14
|
+
export function isNode() {
|
|
15
|
+
return (typeof process !== 'undefined' &&
|
|
16
|
+
process.versions !== undefined &&
|
|
17
|
+
process.versions.node !== undefined);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Check if code is running in a Web Worker
|
|
21
|
+
*/
|
|
22
|
+
export function isWebWorker() {
|
|
23
|
+
return (typeof self !== 'undefined' &&
|
|
24
|
+
typeof self.importScripts === 'function');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if code is running in Capacitor
|
|
28
|
+
*/
|
|
29
|
+
export function isCapacitor() {
|
|
30
|
+
return typeof window?.Capacitor !== 'undefined';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deep clone an object (no dependencies!)
|
|
34
|
+
*/
|
|
35
|
+
export function deepClone(obj) {
|
|
36
|
+
if (obj === null || typeof obj !== 'object')
|
|
37
|
+
return obj;
|
|
38
|
+
if (obj instanceof Date)
|
|
39
|
+
return new Date(obj.getTime());
|
|
40
|
+
if (obj instanceof Array)
|
|
41
|
+
return obj.map((item) => deepClone(item));
|
|
42
|
+
if (obj instanceof RegExp)
|
|
43
|
+
return new RegExp(obj.source, obj.flags);
|
|
44
|
+
const clonedObj = {};
|
|
45
|
+
for (const key in obj) {
|
|
46
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
47
|
+
clonedObj[key] = deepClone(obj[key]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return clonedObj;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Deep merge objects
|
|
54
|
+
*/
|
|
55
|
+
export function deepMerge(target, ...sources) {
|
|
56
|
+
if (!sources.length)
|
|
57
|
+
return target;
|
|
58
|
+
const source = sources.shift();
|
|
59
|
+
if (isObject(target) && isObject(source)) {
|
|
60
|
+
for (const key in source) {
|
|
61
|
+
if (isObject(source[key])) {
|
|
62
|
+
if (!target[key])
|
|
63
|
+
Object.assign(target, { [key]: {} });
|
|
64
|
+
deepMerge(target[key], source[key]);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
Object.assign(target, { [key]: source[key] });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return deepMerge(target, ...sources);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if value is a plain object
|
|
75
|
+
*/
|
|
76
|
+
export function isObject(item) {
|
|
77
|
+
return item !== null && typeof item === 'object' && item.constructor === Object;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Generate a unique ID
|
|
81
|
+
*/
|
|
82
|
+
export function generateId() {
|
|
83
|
+
const timestamp = Date.now().toString(36);
|
|
84
|
+
const randomPart = Math.random().toString(36).substring(2, 9);
|
|
85
|
+
return `${timestamp}-${randomPart}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Simple glob pattern matching
|
|
89
|
+
*/
|
|
90
|
+
export function matchGlob(pattern, str) {
|
|
91
|
+
const regexPattern = pattern
|
|
92
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
93
|
+
.replace(/\*/g, '.*')
|
|
94
|
+
.replace(/\?/g, '.');
|
|
95
|
+
return new RegExp(`^${regexPattern}$`).test(str);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Format bytes to human readable
|
|
99
|
+
*/
|
|
100
|
+
export function formatBytes(bytes, decimals = 2) {
|
|
101
|
+
if (bytes === 0)
|
|
102
|
+
return '0 Bytes';
|
|
103
|
+
const k = 1024;
|
|
104
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
105
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
106
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
107
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse size string to bytes
|
|
111
|
+
*/
|
|
112
|
+
export function parseSize(size) {
|
|
113
|
+
if (typeof size === 'number')
|
|
114
|
+
return size;
|
|
115
|
+
const units = {
|
|
116
|
+
b: 1,
|
|
117
|
+
byte: 1,
|
|
118
|
+
bytes: 1,
|
|
119
|
+
kb: 1024,
|
|
120
|
+
mb: 1024 * 1024,
|
|
121
|
+
gb: 1024 * 1024 * 1024,
|
|
122
|
+
tb: 1024 * 1024 * 1024 * 1024,
|
|
123
|
+
};
|
|
124
|
+
const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)?$/);
|
|
125
|
+
if (!match)
|
|
126
|
+
throw new Error(`Invalid size format: ${size}`);
|
|
127
|
+
const [, num, unit = 'b'] = match;
|
|
128
|
+
const multiplier = units[unit] || 1;
|
|
129
|
+
return parseFloat(num) * multiplier;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Debounce function
|
|
133
|
+
*/
|
|
134
|
+
export function debounce(func, wait) {
|
|
135
|
+
let timeout = null;
|
|
136
|
+
return function executedFunction(...args) {
|
|
137
|
+
const later = () => {
|
|
138
|
+
timeout = null;
|
|
139
|
+
func(...args);
|
|
140
|
+
};
|
|
141
|
+
if (timeout)
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
timeout = setTimeout(later, wait);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Throttle function
|
|
148
|
+
*/
|
|
149
|
+
export function throttle(func, limit) {
|
|
150
|
+
let inThrottle = false;
|
|
151
|
+
return function executedFunction(...args) {
|
|
152
|
+
if (!inThrottle) {
|
|
153
|
+
func(...args);
|
|
154
|
+
inThrottle = true;
|
|
155
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Promise with timeout
|
|
161
|
+
*/
|
|
162
|
+
export function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
reject(new Error(errorMessage));
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
promise
|
|
168
|
+
.then(resolve)
|
|
169
|
+
.catch(reject)
|
|
170
|
+
.finally(() => clearTimeout(timer));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Retry with exponential backoff
|
|
175
|
+
*/
|
|
176
|
+
export async function retry(fn, options = {}) {
|
|
177
|
+
const { maxRetries = 3, initialDelay = 100, maxDelay = 10000, factor = 2 } = options;
|
|
178
|
+
let lastError;
|
|
179
|
+
let delay = initialDelay;
|
|
180
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
181
|
+
try {
|
|
182
|
+
return await fn();
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
lastError = error;
|
|
186
|
+
if (i === maxRetries)
|
|
187
|
+
break;
|
|
188
|
+
await sleep(delay);
|
|
189
|
+
delay = Math.min(delay * factor, maxDelay);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw lastError;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Sleep for specified milliseconds
|
|
196
|
+
*/
|
|
197
|
+
export function sleep(ms) {
|
|
198
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Create a deferred promise
|
|
202
|
+
*/
|
|
203
|
+
export function createDeferred() {
|
|
204
|
+
let resolve;
|
|
205
|
+
let reject;
|
|
206
|
+
const promise = new Promise((res, rej) => {
|
|
207
|
+
resolve = res;
|
|
208
|
+
reject = rej;
|
|
209
|
+
});
|
|
210
|
+
return { promise, resolve, reject };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Serialize value to JSON with support for special types
|
|
214
|
+
*/
|
|
215
|
+
export function serialize(value) {
|
|
216
|
+
return JSON.stringify(value, (_, val) => {
|
|
217
|
+
if (val instanceof Date)
|
|
218
|
+
return { __type: 'Date', value: val.toISOString() };
|
|
219
|
+
if (val instanceof RegExp)
|
|
220
|
+
return { __type: 'RegExp', value: val.toString() };
|
|
221
|
+
if (val instanceof Map)
|
|
222
|
+
return { __type: 'Map', value: Array.from(val.entries()) };
|
|
223
|
+
if (val instanceof Set)
|
|
224
|
+
return { __type: 'Set', value: Array.from(val) };
|
|
225
|
+
if (typeof val === 'bigint')
|
|
226
|
+
return { __type: 'BigInt', value: val.toString() };
|
|
227
|
+
return val;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Deserialize JSON with support for special types
|
|
232
|
+
*/
|
|
233
|
+
export function deserialize(json) {
|
|
234
|
+
return JSON.parse(json, (_, val) => {
|
|
235
|
+
if (val && typeof val === 'object' && '__type' in val) {
|
|
236
|
+
switch (val.__type) {
|
|
237
|
+
case 'Date':
|
|
238
|
+
return new Date(val.value);
|
|
239
|
+
case 'RegExp': {
|
|
240
|
+
const match = val.value.match(/^\/(.*)\/([gimuy]*)$/);
|
|
241
|
+
return match ? new RegExp(match[1], match[2]) : new RegExp(val.value);
|
|
242
|
+
}
|
|
243
|
+
case 'Map':
|
|
244
|
+
return new Map(val.value);
|
|
245
|
+
case 'Set':
|
|
246
|
+
return new Set(val.value);
|
|
247
|
+
case 'BigInt':
|
|
248
|
+
return BigInt(val.value);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return val;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Calculate object size in bytes (rough estimate)
|
|
256
|
+
*/
|
|
257
|
+
export function getObjectSize(obj) {
|
|
258
|
+
const seen = new WeakSet();
|
|
259
|
+
function calculateSize(item) {
|
|
260
|
+
if (item === null || item === undefined)
|
|
261
|
+
return 0;
|
|
262
|
+
const type = typeof item;
|
|
263
|
+
switch (type) {
|
|
264
|
+
case 'boolean':
|
|
265
|
+
return 4;
|
|
266
|
+
case 'number':
|
|
267
|
+
return 8;
|
|
268
|
+
case 'string':
|
|
269
|
+
return item.length * 2; // UTF-16
|
|
270
|
+
case 'bigint':
|
|
271
|
+
return item.toString().length;
|
|
272
|
+
case 'object':
|
|
273
|
+
if (seen.has(item))
|
|
274
|
+
return 0;
|
|
275
|
+
seen.add(item);
|
|
276
|
+
if (item instanceof Date)
|
|
277
|
+
return 8;
|
|
278
|
+
if (item instanceof RegExp)
|
|
279
|
+
return item.toString().length * 2;
|
|
280
|
+
if (Array.isArray(item)) {
|
|
281
|
+
return item.reduce((sum, val) => sum + calculateSize(val), 0);
|
|
282
|
+
}
|
|
283
|
+
return Object.entries(item).reduce((sum, [key, val]) => sum + key.length * 2 + calculateSize(val), 0);
|
|
284
|
+
default:
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return calculateSize(obj);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Create a simple event emitter
|
|
292
|
+
*/
|
|
293
|
+
export class EventEmitter {
|
|
294
|
+
events = new Map();
|
|
295
|
+
on(event, handler) {
|
|
296
|
+
if (!this.events.has(event)) {
|
|
297
|
+
this.events.set(event, new Set());
|
|
298
|
+
}
|
|
299
|
+
this.events.get(event).add(handler);
|
|
300
|
+
}
|
|
301
|
+
off(event, handler) {
|
|
302
|
+
this.events.get(event)?.delete(handler);
|
|
303
|
+
}
|
|
304
|
+
emit(event, ...args) {
|
|
305
|
+
this.events.get(event)?.forEach((handler) => {
|
|
306
|
+
try {
|
|
307
|
+
handler(...args);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
console.error(`Error in event handler for ${event}:`, error);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
once(event, handler) {
|
|
315
|
+
const onceHandler = (...args) => {
|
|
316
|
+
handler(...args);
|
|
317
|
+
this.off(event, onceHandler);
|
|
318
|
+
};
|
|
319
|
+
this.on(event, onceHandler);
|
|
320
|
+
}
|
|
321
|
+
removeAllListeners(event) {
|
|
322
|
+
if (event) {
|
|
323
|
+
this.events.delete(event);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
this.events.clear();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
@objc public class KeychainStorage: NSObject {
|
|
5
|
+
private let service: String
|
|
6
|
+
private let accessGroup: String?
|
|
7
|
+
|
|
8
|
+
@objc public init(service: String? = nil, accessGroup: String? = nil) {
|
|
9
|
+
self.service = service ?? Bundle.main.bundleIdentifier ?? "StrataStorage"
|
|
10
|
+
self.accessGroup = accessGroup
|
|
11
|
+
super.init()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@objc public func set(key: String, value: Data) -> Bool {
|
|
15
|
+
let query = createQuery(key: key)
|
|
16
|
+
SecItemDelete(query as CFDictionary)
|
|
17
|
+
|
|
18
|
+
var newItem = query
|
|
19
|
+
newItem[kSecValueData as String] = value
|
|
20
|
+
|
|
21
|
+
let status = SecItemAdd(newItem as CFDictionary, nil)
|
|
22
|
+
return status == errSecSuccess
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@objc public func get(key: String) -> Data? {
|
|
26
|
+
var query = createQuery(key: key)
|
|
27
|
+
query[kSecReturnData as String] = true
|
|
28
|
+
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
29
|
+
|
|
30
|
+
var result: AnyObject?
|
|
31
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
32
|
+
|
|
33
|
+
guard status == errSecSuccess else { return nil }
|
|
34
|
+
return result as? Data
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc public func remove(key: String) -> Bool {
|
|
38
|
+
let query = createQuery(key: key)
|
|
39
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
40
|
+
return status == errSecSuccess
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@objc public func clear() -> Bool {
|
|
44
|
+
let query: [String: Any] = [
|
|
45
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
46
|
+
kSecAttrService as String: service
|
|
47
|
+
]
|
|
48
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
49
|
+
return status == errSecSuccess || status == errSecItemNotFound
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@objc public func keys() -> [String] {
|
|
53
|
+
var query: [String: Any] = [
|
|
54
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
55
|
+
kSecAttrService as String: service,
|
|
56
|
+
kSecReturnAttributes as String: true,
|
|
57
|
+
kSecMatchLimit as String: kSecMatchLimitAll
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
if let accessGroup = accessGroup {
|
|
61
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var result: AnyObject?
|
|
65
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
66
|
+
|
|
67
|
+
guard status == errSecSuccess,
|
|
68
|
+
let items = result as? [[String: Any]] else { return [] }
|
|
69
|
+
|
|
70
|
+
return items.compactMap { $0[kSecAttrAccount as String] as? String }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func createQuery(key: String) -> [String: Any] {
|
|
74
|
+
var query: [String: Any] = [
|
|
75
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
76
|
+
kSecAttrService as String: service,
|
|
77
|
+
kSecAttrAccount as String: key,
|
|
78
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
if let accessGroup = accessGroup {
|
|
82
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return query
|
|
86
|
+
}
|
|
87
|
+
}
|