strata-storage 2.4.3 → 2.6.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/AI-INTEGRATION-GUIDE.md +115 -261
- package/README.md +426 -182
- package/android/AGENTS.md +51 -0
- package/android/CLAUDE.md +89 -0
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
- package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
- package/dist/README.md +426 -182
- package/dist/adapters/capacitor/FilesystemAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/FilesystemAdapter.js +2 -1
- package/dist/adapters/capacitor/PreferencesAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/PreferencesAdapter.js +2 -1
- package/dist/adapters/capacitor/SecureAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SecureAdapter.js +2 -1
- package/dist/adapters/capacitor/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/capacitor/SqliteAdapter.js +2 -1
- package/dist/adapters/web/CacheAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CacheAdapter.js +11 -3
- package/dist/adapters/web/CookieAdapter.d.ts +37 -1
- package/dist/adapters/web/CookieAdapter.d.ts.map +1 -1
- package/dist/adapters/web/CookieAdapter.js +89 -9
- package/dist/adapters/web/IndexedDBAdapter.d.ts.map +1 -1
- package/dist/adapters/web/IndexedDBAdapter.js +10 -2
- package/dist/adapters/web/LocalStorageAdapter.d.ts +31 -0
- package/dist/adapters/web/LocalStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/LocalStorageAdapter.js +92 -19
- package/dist/adapters/web/MemoryAdapter.d.ts +24 -0
- package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/web/MemoryAdapter.js +69 -18
- package/dist/adapters/web/SessionStorageAdapter.d.ts +24 -0
- package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/web/SessionStorageAdapter.js +71 -9
- package/dist/adapters/web/URLAdapter.d.ts +59 -0
- package/dist/adapters/web/URLAdapter.d.ts.map +1 -0
- package/dist/adapters/web/URLAdapter.js +234 -0
- package/dist/adapters/web/index.d.ts +1 -0
- package/dist/adapters/web/index.d.ts.map +1 -1
- package/dist/adapters/web/index.js +1 -0
- package/dist/android/AGENTS.md +51 -0
- package/dist/android/CLAUDE.md +89 -0
- package/dist/android/build.gradle +1 -1
- package/dist/android/src/main/java/com/strata/storage/FilesystemStorage.java +287 -0
- package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +260 -203
- package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +357 -69
- package/dist/capacitor.d.ts.map +1 -1
- package/dist/capacitor.js +2 -1
- package/dist/core/BaseAdapter.d.ts +8 -0
- package/dist/core/BaseAdapter.d.ts.map +1 -1
- package/dist/core/BaseAdapter.js +34 -14
- package/dist/core/Strata.d.ts +56 -2
- package/dist/core/Strata.d.ts.map +1 -1
- package/dist/core/Strata.js +501 -53
- package/dist/features/encryption.d.ts.map +1 -1
- package/dist/features/encryption.js +3 -2
- package/dist/features/integrity.d.ts +16 -0
- package/dist/features/integrity.d.ts.map +1 -0
- package/dist/features/integrity.js +28 -0
- package/dist/features/observer.d.ts.map +1 -1
- package/dist/features/observer.js +2 -1
- package/dist/features/query.d.ts +7 -1
- package/dist/features/query.d.ts.map +1 -1
- package/dist/features/query.js +9 -2
- package/dist/features/sync.d.ts.map +1 -1
- package/dist/features/sync.js +4 -3
- package/dist/index.d.ts +35 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -30
- package/dist/integrations/angular/index.d.ts +158 -0
- package/dist/integrations/angular/index.d.ts.map +1 -0
- package/dist/integrations/angular/index.js +395 -0
- package/dist/integrations/index.d.ts +15 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +18 -0
- package/dist/integrations/react/index.d.ts +75 -0
- package/dist/integrations/react/index.d.ts.map +1 -0
- package/dist/integrations/react/index.js +191 -0
- package/dist/integrations/vue/index.d.ts +103 -0
- package/dist/integrations/vue/index.d.ts.map +1 -0
- package/dist/integrations/vue/index.js +274 -0
- package/dist/ios/AGENTS.md +48 -0
- package/dist/ios/CLAUDE.md +84 -0
- package/dist/ios/Plugin/FilesystemStorage.swift +218 -0
- package/dist/ios/Plugin/KeychainStorage.swift +139 -50
- package/dist/ios/Plugin/SQLiteStorage.swift +279 -147
- package/dist/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/dist/ios/Plugin/StrataStoragePlugin.swift +272 -65
- package/dist/package.json +21 -5
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -1
- package/dist/types/index.d.ts +58 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -13
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +15 -3
- package/dist/utils/index.d.ts +63 -5
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +109 -16
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +63 -0
- package/ios/AGENTS.md +48 -0
- package/ios/CLAUDE.md +84 -0
- package/ios/Plugin/FilesystemStorage.swift +218 -0
- package/ios/Plugin/KeychainStorage.swift +139 -50
- package/ios/Plugin/SQLiteStorage.swift +279 -147
- package/ios/Plugin/StrataStoragePlugin.m +23 -0
- package/ios/Plugin/StrataStoragePlugin.swift +272 -65
- package/package.json +31 -20
- package/scripts/build.js +16 -5
- package/scripts/configure.js +2 -6
- package/scripts/postinstall.js +2 -2
- package/Readme.md +0 -271
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* React integration for Strata Storage.
|
|
4
|
+
*
|
|
5
|
+
* Two usage styles, both fully supported:
|
|
6
|
+
*
|
|
7
|
+
* 1. No-provider (Zustand-style, recommended): create an instance anywhere and
|
|
8
|
+
* bind hooks to it with `createStrataHooks` — no Provider required.
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { defineStorage } from 'strata-storage';
|
|
12
|
+
* import { createStrataHooks } from 'strata-storage/react';
|
|
13
|
+
* const storage = defineStorage();
|
|
14
|
+
* export const { useStorage } = createStrataHooks(storage);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* 2. Provider-based (classic): wrap the tree in <StrataProvider> and call the
|
|
18
|
+
* exported hooks, which read the instance from context.
|
|
19
|
+
*/
|
|
20
|
+
import { createContext, useContext, useEffect, useState, useCallback, useMemo } from 'react';
|
|
21
|
+
import { Strata } from "../../core/Strata.js";
|
|
22
|
+
import { ValidationError } from "../../utils/errors.js";
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Shared hook implementations (the Strata instance is passed in explicitly).
|
|
25
|
+
// Both the no-provider hooks and the Provider-based hooks delegate here, so the
|
|
26
|
+
// logic lives in exactly one place.
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function useStorageImpl(strata, key, defaultValue, options) {
|
|
29
|
+
const [value, setValue] = useState(null);
|
|
30
|
+
const [loading, setLoading] = useState(true);
|
|
31
|
+
// Load the initial value.
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
let active = true;
|
|
34
|
+
strata.get(key, options).then((val) => {
|
|
35
|
+
if (!active)
|
|
36
|
+
return;
|
|
37
|
+
setValue(val ?? defaultValue ?? null);
|
|
38
|
+
setLoading(false);
|
|
39
|
+
});
|
|
40
|
+
return () => {
|
|
41
|
+
active = false;
|
|
42
|
+
};
|
|
43
|
+
}, [key, strata, options, defaultValue]);
|
|
44
|
+
// Subscribe to changes for this key.
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
return strata.subscribe((change) => {
|
|
47
|
+
if (change.key === key) {
|
|
48
|
+
setValue(change.newValue);
|
|
49
|
+
}
|
|
50
|
+
}, options);
|
|
51
|
+
}, [key, strata, options]);
|
|
52
|
+
const updateValue = useCallback(async (newValue, updateOptions) => {
|
|
53
|
+
if (newValue === null) {
|
|
54
|
+
await strata.remove(key, updateOptions ?? options);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await strata.set(key, newValue, updateOptions ?? options);
|
|
58
|
+
}
|
|
59
|
+
setValue(newValue);
|
|
60
|
+
}, [key, strata, options]);
|
|
61
|
+
return [value, updateValue, loading];
|
|
62
|
+
}
|
|
63
|
+
function useStorageQueryImpl(strata, condition, options) {
|
|
64
|
+
const [data, setData] = useState([]);
|
|
65
|
+
const [loading, setLoading] = useState(true);
|
|
66
|
+
const conditionKey = useMemo(() => JSON.stringify(condition), [condition]);
|
|
67
|
+
const refetch = useCallback(async () => {
|
|
68
|
+
setLoading(true);
|
|
69
|
+
try {
|
|
70
|
+
setData(await strata.query(condition, options));
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
setLoading(false);
|
|
74
|
+
}
|
|
75
|
+
}, [strata, condition, options]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
void refetch();
|
|
78
|
+
}, [conditionKey, refetch]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
return strata.subscribe(() => {
|
|
81
|
+
void refetch();
|
|
82
|
+
}, options);
|
|
83
|
+
}, [strata, refetch, options]);
|
|
84
|
+
return { data, loading, refetch };
|
|
85
|
+
}
|
|
86
|
+
function useStorageTTLImpl(strata, key, options) {
|
|
87
|
+
const [ttl, setTTL] = useState(null);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
let active = true;
|
|
90
|
+
const checkTTL = async () => {
|
|
91
|
+
const remaining = await strata.getTTL(key, options);
|
|
92
|
+
if (active)
|
|
93
|
+
setTTL(remaining);
|
|
94
|
+
};
|
|
95
|
+
void checkTTL();
|
|
96
|
+
const interval = setInterval(() => void checkTTL(), 1000);
|
|
97
|
+
return () => {
|
|
98
|
+
active = false;
|
|
99
|
+
clearInterval(interval);
|
|
100
|
+
};
|
|
101
|
+
}, [key, strata, options]);
|
|
102
|
+
const extendTTL = useCallback(async (extension) => {
|
|
103
|
+
await strata.extendTTL(key, extension, options);
|
|
104
|
+
setTTL(await strata.getTTL(key, options));
|
|
105
|
+
}, [key, strata, options]);
|
|
106
|
+
const persist = useCallback(async () => {
|
|
107
|
+
await strata.persist(key, options);
|
|
108
|
+
setTTL(null);
|
|
109
|
+
}, [key, strata, options]);
|
|
110
|
+
return { ttl, extendTTL, persist };
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// No-provider API — bind the hooks to an explicit instance (Zustand-style).
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
/**
|
|
116
|
+
* Bind the Strata hooks to a specific instance — the framework-agnostic,
|
|
117
|
+
* no-Provider pattern (mirrors Zustand's `create`). Call once at module scope
|
|
118
|
+
* and use the returned hooks in any component, no Provider needed.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* const storage = defineStorage();
|
|
123
|
+
* export const { useStorage, useStorageQuery, useStorageTTL } =
|
|
124
|
+
* createStrataHooks(storage);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function createStrataHooks(strata) {
|
|
128
|
+
function useStorage(key, defaultValue, options) {
|
|
129
|
+
return useStorageImpl(strata, key, defaultValue, options);
|
|
130
|
+
}
|
|
131
|
+
function useStorageQuery(condition, options) {
|
|
132
|
+
return useStorageQueryImpl(strata, condition, options);
|
|
133
|
+
}
|
|
134
|
+
function useStorageTTL(key, options) {
|
|
135
|
+
return useStorageTTLImpl(strata, key, options);
|
|
136
|
+
}
|
|
137
|
+
return { useStorage, useStorageQuery, useStorageTTL };
|
|
138
|
+
}
|
|
139
|
+
const StrataContext = createContext({
|
|
140
|
+
strata: null,
|
|
141
|
+
initialized: false,
|
|
142
|
+
});
|
|
143
|
+
export function StrataProvider({ children, config, instance, loadingComponent, fallback, }) {
|
|
144
|
+
const [strata] = useState(() => instance ?? new Strata(config));
|
|
145
|
+
const [initialized, setInitialized] = useState(strata.isInitialized);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
let active = true;
|
|
148
|
+
strata.initialize().then(() => {
|
|
149
|
+
if (active)
|
|
150
|
+
setInitialized(true);
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
active = false;
|
|
154
|
+
// Only close instances this provider created — never a caller-owned one.
|
|
155
|
+
if (!instance) {
|
|
156
|
+
void strata.close();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}, [strata, instance]);
|
|
160
|
+
const value = useMemo(() => ({
|
|
161
|
+
strata: initialized ? strata : null,
|
|
162
|
+
initialized,
|
|
163
|
+
}), [strata, initialized]);
|
|
164
|
+
if (!initialized && (loadingComponent || fallback)) {
|
|
165
|
+
return (loadingComponent ?? fallback);
|
|
166
|
+
}
|
|
167
|
+
return _jsx(StrataContext.Provider, { value: value, children: children });
|
|
168
|
+
}
|
|
169
|
+
export function useStrata() {
|
|
170
|
+
const { strata, initialized } = useContext(StrataContext);
|
|
171
|
+
if (!initialized || !strata) {
|
|
172
|
+
throw new ValidationError('useStrata must be used within <StrataProvider>. For provider-free usage, ' +
|
|
173
|
+
'create an instance with defineStorage() and bind hooks via createStrataHooks(instance).', {
|
|
174
|
+
hook: 'useStrata',
|
|
175
|
+
requiredProvider: 'StrataProvider',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return strata;
|
|
179
|
+
}
|
|
180
|
+
export function useStrataInitialized() {
|
|
181
|
+
return useContext(StrataContext).initialized;
|
|
182
|
+
}
|
|
183
|
+
export function useStorage(key, defaultValue, options) {
|
|
184
|
+
return useStorageImpl(useStrata(), key, defaultValue, options);
|
|
185
|
+
}
|
|
186
|
+
export function useStorageQuery(condition, options) {
|
|
187
|
+
return useStorageQueryImpl(useStrata(), condition, options);
|
|
188
|
+
}
|
|
189
|
+
export function useStorageTTL(key, options) {
|
|
190
|
+
return useStorageTTLImpl(useStrata(), key, options);
|
|
191
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue 3 integration for Strata Storage
|
|
3
|
+
*/
|
|
4
|
+
import type { Ref, ComputedRef, App } from 'vue';
|
|
5
|
+
import { Strata } from '@/core/Strata';
|
|
6
|
+
import type { StrataConfig, StorageOptions, QueryCondition } from '@/types';
|
|
7
|
+
export declare const StrataPlugin: {
|
|
8
|
+
install(app: App, config?: StrataConfig): void;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Use Strata instance
|
|
12
|
+
*/
|
|
13
|
+
export declare function useStrata(instance?: Strata): Strata;
|
|
14
|
+
/**
|
|
15
|
+
* Storage composable with reactive state
|
|
16
|
+
*/
|
|
17
|
+
export declare function useStorage<T = unknown>(key: string, defaultValue?: T, options?: StorageOptions, instance?: Strata): {
|
|
18
|
+
value: Ref<T | null>;
|
|
19
|
+
loading: Ref<boolean>;
|
|
20
|
+
error: Ref<Error | null>;
|
|
21
|
+
refresh: () => Promise<void>;
|
|
22
|
+
update: (value: T | null, options?: StorageOptions) => Promise<void>;
|
|
23
|
+
remove: () => Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Query composable
|
|
27
|
+
*/
|
|
28
|
+
export declare function useStorageQuery<T = unknown>(condition: QueryCondition, options?: StorageOptions, instance?: Strata): {
|
|
29
|
+
data: Ref<Array<{
|
|
30
|
+
key: string;
|
|
31
|
+
value: T;
|
|
32
|
+
}>>;
|
|
33
|
+
loading: Ref<boolean>;
|
|
34
|
+
error: Ref<Error | null>;
|
|
35
|
+
refetch: () => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* TTL composable
|
|
39
|
+
*/
|
|
40
|
+
export declare function useStorageTTL(key: string, options?: StorageOptions, instance?: Strata): {
|
|
41
|
+
ttl: ComputedRef<string | null>;
|
|
42
|
+
milliseconds: Ref<number | null>;
|
|
43
|
+
extendTTL: (extension: number) => Promise<void>;
|
|
44
|
+
persist: () => Promise<void>;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Storage size composable
|
|
48
|
+
*/
|
|
49
|
+
export declare function useStorageSize(autoRefresh?: boolean, refreshInterval?: number, instance?: Strata): {
|
|
50
|
+
size: Ref<number>;
|
|
51
|
+
count: Ref<number>;
|
|
52
|
+
formatted: ComputedRef<string>;
|
|
53
|
+
refresh: () => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Bind the Strata composables to a specific instance — the provider-free,
|
|
57
|
+
* no-plugin pattern. Use the returned composables in any component's setup()
|
|
58
|
+
* without installing StrataPlugin.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { defineStorage } from 'strata-storage';
|
|
63
|
+
* import { createStrataComposables } from 'strata-storage/vue';
|
|
64
|
+
* const storage = defineStorage();
|
|
65
|
+
* export const { useStorage } = createStrataComposables(storage);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function createStrataComposables(strata: Strata): {
|
|
69
|
+
useStorage: <T = unknown>(key: string, defaultValue?: T, options?: StorageOptions) => {
|
|
70
|
+
value: Ref<T | null, T | null>;
|
|
71
|
+
loading: Ref<boolean>;
|
|
72
|
+
error: Ref<Error | null>;
|
|
73
|
+
refresh: () => Promise<void>;
|
|
74
|
+
update: (value: T | null, options?: StorageOptions) => Promise<void>;
|
|
75
|
+
remove: () => Promise<void>;
|
|
76
|
+
};
|
|
77
|
+
useStorageQuery: <T = unknown>(condition: QueryCondition, options?: StorageOptions) => {
|
|
78
|
+
data: Ref<{
|
|
79
|
+
key: string;
|
|
80
|
+
value: T;
|
|
81
|
+
}[], {
|
|
82
|
+
key: string;
|
|
83
|
+
value: T;
|
|
84
|
+
}[]>;
|
|
85
|
+
loading: Ref<boolean>;
|
|
86
|
+
error: Ref<Error | null>;
|
|
87
|
+
refetch: () => Promise<void>;
|
|
88
|
+
};
|
|
89
|
+
useStorageTTL: (key: string, options?: StorageOptions) => {
|
|
90
|
+
ttl: ComputedRef<string | null>;
|
|
91
|
+
milliseconds: Ref<number | null>;
|
|
92
|
+
extendTTL: (extension: number) => Promise<void>;
|
|
93
|
+
persist: () => Promise<void>;
|
|
94
|
+
};
|
|
95
|
+
useStorageSize: (autoRefresh?: boolean, refreshInterval?: number) => {
|
|
96
|
+
size: Ref<number>;
|
|
97
|
+
count: Ref<number>;
|
|
98
|
+
formatted: ComputedRef<string>;
|
|
99
|
+
refresh: () => Promise<void>;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
export type { Strata, StrataConfig, StorageOptions };
|
|
103
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/vue/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue 3 integration for Strata Storage
|
|
3
|
+
*/
|
|
4
|
+
import { ref, computed, watch, onMounted, onUnmounted, inject } from 'vue';
|
|
5
|
+
import { Strata } from "../../core/Strata.js";
|
|
6
|
+
import { ValidationError } from "../../utils/errors.js";
|
|
7
|
+
import { logger } from "../../utils/logger.js";
|
|
8
|
+
// Injection key
|
|
9
|
+
const StrataKey = Symbol('strata');
|
|
10
|
+
// Plugin installation
|
|
11
|
+
export const StrataPlugin = {
|
|
12
|
+
install(app, config) {
|
|
13
|
+
const strata = new Strata(config);
|
|
14
|
+
// Initialize asynchronously
|
|
15
|
+
strata.initialize().catch(logger.error);
|
|
16
|
+
// Provide globally
|
|
17
|
+
app.provide(StrataKey, strata);
|
|
18
|
+
// Add global properties
|
|
19
|
+
app.config.globalProperties.$strata = strata;
|
|
20
|
+
// Clean up on app unmount
|
|
21
|
+
app.unmount = new Proxy(app.unmount, {
|
|
22
|
+
apply(target, thisArg, argList) {
|
|
23
|
+
strata.close();
|
|
24
|
+
return Reflect.apply(target, thisArg, argList);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
// Composition API
|
|
30
|
+
/**
|
|
31
|
+
* Use Strata instance
|
|
32
|
+
*/
|
|
33
|
+
export function useStrata(instance) {
|
|
34
|
+
if (instance)
|
|
35
|
+
return instance;
|
|
36
|
+
const strata = inject(StrataKey);
|
|
37
|
+
if (!strata) {
|
|
38
|
+
throw new ValidationError('Strata not provided. Install the plugin (app.use(StrataPlugin)) or, for ' +
|
|
39
|
+
'provider-free usage, pass an instance or use createStrataComposables(instance).', { required: 'app.use(StrataPlugin, { config })' });
|
|
40
|
+
}
|
|
41
|
+
return strata;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Storage composable with reactive state
|
|
45
|
+
*/
|
|
46
|
+
export function useStorage(key, defaultValue, options, instance) {
|
|
47
|
+
const strata = useStrata(instance);
|
|
48
|
+
const value = ref(null);
|
|
49
|
+
const loading = ref(true);
|
|
50
|
+
const error = ref(null);
|
|
51
|
+
// Load initial value
|
|
52
|
+
const refresh = async () => {
|
|
53
|
+
loading.value = true;
|
|
54
|
+
error.value = null;
|
|
55
|
+
try {
|
|
56
|
+
const result = await strata.get(key, options);
|
|
57
|
+
value.value = result ?? defaultValue ?? null;
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
error.value = e;
|
|
61
|
+
value.value = defaultValue ?? null;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
loading.value = false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// Update value
|
|
68
|
+
const update = async (newValue, updateOptions) => {
|
|
69
|
+
loading.value = true;
|
|
70
|
+
error.value = null;
|
|
71
|
+
try {
|
|
72
|
+
if (newValue === null) {
|
|
73
|
+
await strata.remove(key, updateOptions ?? options);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await strata.set(key, newValue, updateOptions ?? options);
|
|
77
|
+
}
|
|
78
|
+
value.value = newValue;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
error.value = e;
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
loading.value = false;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// Remove value
|
|
89
|
+
const remove = async () => {
|
|
90
|
+
await update(null);
|
|
91
|
+
};
|
|
92
|
+
// Subscribe to changes
|
|
93
|
+
onMounted(() => {
|
|
94
|
+
refresh();
|
|
95
|
+
const unsubscribe = strata.subscribe((change) => {
|
|
96
|
+
if (change.key === key) {
|
|
97
|
+
value.value = change.newValue;
|
|
98
|
+
}
|
|
99
|
+
}, options);
|
|
100
|
+
onUnmounted(unsubscribe);
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
value,
|
|
104
|
+
loading,
|
|
105
|
+
error,
|
|
106
|
+
refresh,
|
|
107
|
+
update,
|
|
108
|
+
remove,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Query composable
|
|
113
|
+
*/
|
|
114
|
+
export function useStorageQuery(condition, options, instance) {
|
|
115
|
+
const strata = useStrata(instance);
|
|
116
|
+
const data = ref([]);
|
|
117
|
+
const loading = ref(true);
|
|
118
|
+
const error = ref(null);
|
|
119
|
+
const refetch = async () => {
|
|
120
|
+
loading.value = true;
|
|
121
|
+
error.value = null;
|
|
122
|
+
try {
|
|
123
|
+
const results = await strata.query(condition, options);
|
|
124
|
+
data.value = results;
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
error.value = e;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
loading.value = false;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
// Watch for condition changes
|
|
134
|
+
watch(() => JSON.stringify(condition), () => refetch(), { immediate: true });
|
|
135
|
+
// Subscribe to any storage changes
|
|
136
|
+
onMounted(() => {
|
|
137
|
+
const unsubscribe = strata.subscribe(() => {
|
|
138
|
+
refetch();
|
|
139
|
+
}, options);
|
|
140
|
+
onUnmounted(unsubscribe);
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
data,
|
|
144
|
+
loading,
|
|
145
|
+
error,
|
|
146
|
+
refetch,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* TTL composable
|
|
151
|
+
*/
|
|
152
|
+
export function useStorageTTL(key, options, instance) {
|
|
153
|
+
const strata = useStrata(instance);
|
|
154
|
+
const milliseconds = ref(null);
|
|
155
|
+
let intervalId;
|
|
156
|
+
const ttl = computed(() => {
|
|
157
|
+
if (milliseconds.value === null || milliseconds.value <= 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return formatTTL(milliseconds.value);
|
|
161
|
+
});
|
|
162
|
+
const updateTTL = async () => {
|
|
163
|
+
try {
|
|
164
|
+
const remaining = await strata.getTTL(key, options);
|
|
165
|
+
milliseconds.value = remaining;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
milliseconds.value = null;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
const extendTTL = async (extension) => {
|
|
172
|
+
await strata.extendTTL(key, extension, options);
|
|
173
|
+
await updateTTL();
|
|
174
|
+
};
|
|
175
|
+
const persist = async () => {
|
|
176
|
+
await strata.persist(key, options);
|
|
177
|
+
milliseconds.value = null;
|
|
178
|
+
};
|
|
179
|
+
onMounted(() => {
|
|
180
|
+
updateTTL();
|
|
181
|
+
intervalId = setInterval(updateTTL, 1000);
|
|
182
|
+
});
|
|
183
|
+
onUnmounted(() => {
|
|
184
|
+
if (intervalId) {
|
|
185
|
+
clearInterval(intervalId);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
ttl,
|
|
190
|
+
milliseconds,
|
|
191
|
+
extendTTL,
|
|
192
|
+
persist,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Storage size composable
|
|
197
|
+
*/
|
|
198
|
+
export function useStorageSize(autoRefresh = false, refreshInterval = 5000, instance) {
|
|
199
|
+
const strata = useStrata(instance);
|
|
200
|
+
const size = ref(0);
|
|
201
|
+
const count = ref(0);
|
|
202
|
+
let intervalId;
|
|
203
|
+
const formatted = computed(() => formatBytes(size.value));
|
|
204
|
+
const refresh = async () => {
|
|
205
|
+
const info = await strata.size();
|
|
206
|
+
size.value = info.total;
|
|
207
|
+
count.value = info.count;
|
|
208
|
+
};
|
|
209
|
+
onMounted(() => {
|
|
210
|
+
refresh();
|
|
211
|
+
if (autoRefresh) {
|
|
212
|
+
intervalId = setInterval(refresh, refreshInterval);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
onUnmounted(() => {
|
|
216
|
+
if (intervalId) {
|
|
217
|
+
clearInterval(intervalId);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
size,
|
|
222
|
+
count,
|
|
223
|
+
formatted,
|
|
224
|
+
refresh,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Bind the Strata composables to a specific instance — the provider-free,
|
|
229
|
+
* no-plugin pattern. Use the returned composables in any component's setup()
|
|
230
|
+
* without installing StrataPlugin.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* import { defineStorage } from 'strata-storage';
|
|
235
|
+
* import { createStrataComposables } from 'strata-storage/vue';
|
|
236
|
+
* const storage = defineStorage();
|
|
237
|
+
* export const { useStorage } = createStrataComposables(storage);
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function createStrataComposables(strata) {
|
|
241
|
+
return {
|
|
242
|
+
useStorage: (key, defaultValue, options) => useStorage(key, defaultValue, options, strata),
|
|
243
|
+
useStorageQuery: (condition, options) => useStorageQuery(condition, options, strata),
|
|
244
|
+
useStorageTTL: (key, options) => useStorageTTL(key, options, strata),
|
|
245
|
+
useStorageSize: (autoRefresh, refreshInterval) => useStorageSize(autoRefresh, refreshInterval, strata),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Helper functions
|
|
249
|
+
function formatTTL(milliseconds) {
|
|
250
|
+
const seconds = Math.floor(milliseconds / 1000);
|
|
251
|
+
const minutes = Math.floor(seconds / 60);
|
|
252
|
+
const hours = Math.floor(minutes / 60);
|
|
253
|
+
const days = Math.floor(hours / 24);
|
|
254
|
+
if (days > 0) {
|
|
255
|
+
return `${days}d ${hours % 24}h`;
|
|
256
|
+
}
|
|
257
|
+
else if (hours > 0) {
|
|
258
|
+
return `${hours}h ${minutes % 60}m`;
|
|
259
|
+
}
|
|
260
|
+
else if (minutes > 0) {
|
|
261
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
return `${seconds}s`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function formatBytes(bytes) {
|
|
268
|
+
if (bytes === 0)
|
|
269
|
+
return '0 Bytes';
|
|
270
|
+
const k = 1024;
|
|
271
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
272
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
273
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
274
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# AGENTS.md — ios/
|
|
2
|
+
|
|
3
|
+
Last Updated: 2026-05-27
|
|
4
|
+
|
|
5
|
+
> Agent instructions for iOS native development.
|
|
6
|
+
|
|
7
|
+
## Files
|
|
8
|
+
|
|
9
|
+
| File | Purpose |
|
|
10
|
+
|------|---------|
|
|
11
|
+
| `Plugin/StrataStoragePlugin.swift` | Capacitor plugin entry |
|
|
12
|
+
| `Plugin/UserDefaultsStorage.swift` | General key-value storage |
|
|
13
|
+
| `Plugin/KeychainStorage.swift` | Secure storage |
|
|
14
|
+
| `Plugin/SQLiteStorage.swift` | SQLite database storage — multi-store (v2.6.0), `database`/`table` options honoured, full `StorageValue` wrapper round-trip, `size(detailed)` supported |
|
|
15
|
+
| `Plugin/FilesystemStorage.swift` | File-per-key native storage under `NSDocumentsDirectory/strata_storage/` (new in v2.6.0); atomic writes via staging rename; `isAvailable()` returns `true` |
|
|
16
|
+
|
|
17
|
+
## v2.6.0 Notes
|
|
18
|
+
|
|
19
|
+
- **SQLite multi-store:** `database` option → distinct `.db` file. `table` option → sanitised table identifier `[A-Za-z0-9_]`. Previously both were ignored and all adapters shared one table.
|
|
20
|
+
- **SQLite value shape:** `get` now returns the full `StorageValue` wrapper (`value`, `created`, `updated`, `expires`, `tags`, `metadata`). Corrupt rows are treated as a miss.
|
|
21
|
+
- **SQLite bind safety:** text/blob binds use `SQLITE_TRANSIENT` — removes a latent use-after-free for transient Swift buffers.
|
|
22
|
+
- **FilesystemStorage.swift:** new file. Writes are atomic (staging temp → rename). `keys()` excludes `.staging/` artifacts. `size(detailed: true)` returns `{ keys, values, metadata }`.
|
|
23
|
+
- **Pending on-device verification** — see `docs/guides/platforms/device-verification.md`.
|
|
24
|
+
|
|
25
|
+
## Agent Rules
|
|
26
|
+
|
|
27
|
+
### Security (IRON-SOLID)
|
|
28
|
+
- Sensitive data MUST use `KeychainStorage`
|
|
29
|
+
- NEVER store secrets in `UserDefaultsStorage`
|
|
30
|
+
|
|
31
|
+
### SQL Safety
|
|
32
|
+
- ALWAYS use parameterized queries in `SQLiteStorage`
|
|
33
|
+
- NEVER string-interpolate SQL values
|
|
34
|
+
- Use `SQLITE_TRANSIENT` for text/blob binds
|
|
35
|
+
|
|
36
|
+
### Filesystem Safety
|
|
37
|
+
- NEVER read from `strata_storage/.staging/` — staging files are transient
|
|
38
|
+
- Key sanitisation must be consistent between `set`, `get`, `remove`, and `keys`
|
|
39
|
+
|
|
40
|
+
### Plugin Architecture
|
|
41
|
+
- Methods exposed through `StrataStoragePlugin.swift`
|
|
42
|
+
- Each storage backend is a separate Swift file
|
|
43
|
+
- Bridges Capacitor JS calls to native Swift
|
|
44
|
+
- The `CAP_PLUGIN` macro must include every callable method name
|
|
45
|
+
|
|
46
|
+
### Before Modifying
|
|
47
|
+
- Understand Capacitor plugin protocol
|
|
48
|
+
- Test on iOS simulator after changes; follow device-verification guide
|