@burgantech/context-store 0.1.0 → 0.1.1
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 +205 -0
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# @burgantech/context-store
|
|
2
|
+
|
|
3
|
+
Centralized, boundary-based state management SDK for web applications. Framework-agnostic — works with Angular, React, Vue, or plain TypeScript.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Boundary isolation** — `device`, `user`, and `subject` scopes keep data cleanly separated
|
|
8
|
+
- **Multiple storage backends** — `secureStorage`, `secureStorageEncrypted`, `memory`, `localStorage`
|
|
9
|
+
- **Envelope metadata** — every entry is wrapped with `createdAt`, `updatedAt`, `expiry`, `appName`, `appVersion`, `sdkVersion`
|
|
10
|
+
- **TTL & auto-expiry** — time-to-live with lazy cleanup on read and proactive `cleanup()` sweeps
|
|
11
|
+
- **Server time sync** — pluggable HTTP delegate fetches authoritative time; falls back to device time on failure
|
|
12
|
+
- **Encryption** — AES-256-GCM spec; encryption key held in memory only, never persisted
|
|
13
|
+
- **Observability** — `observeData` (Observable) and `addListener` / `removeListener` (callback) — no framework dependency
|
|
14
|
+
- **Nested access** — `dataPath` option for deep property read/write (e.g. `profile.address.city`)
|
|
15
|
+
- **Batch operations** — `batchSet`, `batchGet` for bulk reads and writes
|
|
16
|
+
- **Export / Import** — `exportData` and `importData` for migration and backup
|
|
17
|
+
- **Zero dependencies**
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @burgantech/context-store
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { ContextStore, Boundary, Storage } from '@burgantech/context-store';
|
|
29
|
+
|
|
30
|
+
const store = ContextStore.create({
|
|
31
|
+
timeServerUrls: ['https://your-api.com/time'],
|
|
32
|
+
onRequestServerTime: async (url, timeout) => {
|
|
33
|
+
const res = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(timeout) });
|
|
34
|
+
const d = res.headers.get('date');
|
|
35
|
+
return d ? new Date(d) : null;
|
|
36
|
+
},
|
|
37
|
+
onLog: (level, message) => console.log(`[${level}] ${message}`),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Identity
|
|
41
|
+
store.activeUser = 'user-42';
|
|
42
|
+
store.activeSubject = 'customer-7';
|
|
43
|
+
|
|
44
|
+
// Write
|
|
45
|
+
store.setData(Boundary.user, 'profile', { name: 'Ada', role: 'admin' });
|
|
46
|
+
|
|
47
|
+
// Read
|
|
48
|
+
const profile = store.getData(Boundary.user, 'profile');
|
|
49
|
+
|
|
50
|
+
// Nested access
|
|
51
|
+
store.setData(Boundary.user, 'profile', 'Lovelace', { dataPath: 'surname' });
|
|
52
|
+
const surname = store.getData(Boundary.user, 'profile', { dataPath: 'surname' });
|
|
53
|
+
|
|
54
|
+
// TTL (expires in 5 minutes)
|
|
55
|
+
store.setData(Boundary.user, 'otp', '123456', { ttl: 5 * 60_000 });
|
|
56
|
+
|
|
57
|
+
// Observe changes
|
|
58
|
+
const sub = store.observeData(Boundary.user, 'profile').subscribe((value) => {
|
|
59
|
+
console.log('profile changed:', value);
|
|
60
|
+
});
|
|
61
|
+
// later: sub.unsubscribe();
|
|
62
|
+
|
|
63
|
+
// Listener
|
|
64
|
+
store.addListener('my-listener', Boundary.user, 'profile', (value) => {
|
|
65
|
+
console.log('listener fired:', value);
|
|
66
|
+
});
|
|
67
|
+
store.removeListener('my-listener');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Encryption
|
|
71
|
+
|
|
72
|
+
The SDK accepts an encryption key at runtime for `secureStorageEncrypted` storage. The key is held in memory only and never written to disk.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// After authenticating, set the key provided by your backend
|
|
76
|
+
store.setEncryptionKey('backend-provided-key');
|
|
77
|
+
|
|
78
|
+
// Write encrypted
|
|
79
|
+
store.setData(Boundary.user, 'secret', { pin: '1234' }, { storage: Storage.secureStorageEncrypted });
|
|
80
|
+
|
|
81
|
+
// Read encrypted
|
|
82
|
+
const secret = store.getData(Boundary.user, 'secret', { storage: Storage.secureStorageEncrypted });
|
|
83
|
+
|
|
84
|
+
// On logout
|
|
85
|
+
store.revokeEncryptionKey();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Server Time
|
|
89
|
+
|
|
90
|
+
The SDK never trusts device time for TTL calculations. It fetches authoritative time via a delegate you provide and caches the result.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const serverTime = await store.getServerTime();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Option | Default | Description |
|
|
97
|
+
|--------|---------|-------------|
|
|
98
|
+
| `timeServerUrls` | — | URL list for HEAD requests |
|
|
99
|
+
| `onRequestServerTime` | — | Async delegate: `(url, timeout) => Promise<Date \| null>` |
|
|
100
|
+
| `serverTimeTtl` | 1 800 000 (30 min) | Cache duration in ms |
|
|
101
|
+
| `requestServerTimeTimeout` | 5 000 | Per-request timeout in ms |
|
|
102
|
+
|
|
103
|
+
If all fetches fail, the SDK falls back to device time and reports the error via `onLog`.
|
|
104
|
+
|
|
105
|
+
## API Overview
|
|
106
|
+
|
|
107
|
+
### Identity
|
|
108
|
+
|
|
109
|
+
| Property / Method | Type | Description |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `activeDevice` | `string \| null` | Read-only. SDK-generated per session (web) or from platform API (native). |
|
|
112
|
+
| `activeUser` | `string \| null` | Get/set. Application-managed user identity. |
|
|
113
|
+
| `activeSubject` | `string \| null` | Get/set. Sub-user scope (customer, tenant, etc.). |
|
|
114
|
+
| `getServerTime()` | `Promise<Date>` | Authoritative time; re-fetches when cache expires. |
|
|
115
|
+
|
|
116
|
+
### Data Operations
|
|
117
|
+
|
|
118
|
+
| Method | Description |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `setData(boundary, key, value, options?)` | Write data. Options: `storage`, `ttl`, `dataPath`. |
|
|
121
|
+
| `getData<T>(boundary, key, options?)` | Read data. Returns `undefined` if missing or expired. |
|
|
122
|
+
| `getDataMetadata(boundary, key, options?)` | Read full envelope (metadata + data). |
|
|
123
|
+
| `deleteData(boundary, key, options?)` | Delete a key. Supports `dataPath` for nested removal. |
|
|
124
|
+
| `batchSet(operations)` | Bulk write. |
|
|
125
|
+
| `batchGet(operations)` | Bulk read. |
|
|
126
|
+
|
|
127
|
+
### Observability
|
|
128
|
+
|
|
129
|
+
| Method | Description |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `observeData(boundary, key, options?)` | Returns `Observable<T>`. Emits on every `setData` for that key. |
|
|
132
|
+
| `addListener(id, boundary, key, callback, options?)` | Register a named callback. |
|
|
133
|
+
| `removeListener(id)` | Remove a named callback. |
|
|
134
|
+
| `clearAllListeners()` | Remove all callbacks. |
|
|
135
|
+
|
|
136
|
+
### Housekeeping
|
|
137
|
+
|
|
138
|
+
| Method | Description |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `findKeys(boundary, partialKey, options?)` | Search keys by prefix. |
|
|
141
|
+
| `clearData(boundary, options?)` | Clear data for a boundary. Optional `partialKey` filter. |
|
|
142
|
+
| `exportData(boundary, options?)` | Export raw envelopes as a record. |
|
|
143
|
+
| `importData(boundary, data, options?)` | Import envelopes. `overwrite` flag controls conflict resolution. |
|
|
144
|
+
| `cleanup(options?)` | Remove expired entries. Filterable by `boundary` and `storage`. |
|
|
145
|
+
|
|
146
|
+
### Encryption Key
|
|
147
|
+
|
|
148
|
+
| Method | Description |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `setEncryptionKey(key)` | Set the encryption key (in-memory only). |
|
|
151
|
+
| `isEncryptionKeySet` | `boolean` — whether a key is currently loaded. |
|
|
152
|
+
| `revokeEncryptionKey()` | Clear the key from memory. |
|
|
153
|
+
|
|
154
|
+
## Enums
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
enum Boundary {
|
|
158
|
+
device = 'device',
|
|
159
|
+
user = 'user',
|
|
160
|
+
subject = 'subject',
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
enum Storage {
|
|
164
|
+
secureStorage = 'secureStorage',
|
|
165
|
+
secureStorageEncrypted = 'secureStorageEncrypted',
|
|
166
|
+
memory = 'memory',
|
|
167
|
+
localStorage = 'localStorage',
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Types
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
type Envelope = {
|
|
175
|
+
data: any;
|
|
176
|
+
expiry: string | null;
|
|
177
|
+
createdAt: string;
|
|
178
|
+
updatedAt: string;
|
|
179
|
+
appName: string;
|
|
180
|
+
appVersion: string;
|
|
181
|
+
sdkVersion: string;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type Subscription = { unsubscribe(): void };
|
|
185
|
+
type Observable<T> = { subscribe(callback: (value: T) => void): Subscription };
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Error Behaviour
|
|
189
|
+
|
|
190
|
+
| Scenario | Behaviour |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `secureStorageEncrypted` access without key | Returns `undefined`, logs warning |
|
|
193
|
+
| Invalid key or boundary | Returns `undefined` |
|
|
194
|
+
| Storage write failure | Logs error via `onLog` |
|
|
195
|
+
| Server time fetch failure | Falls back to device time, logs error |
|
|
196
|
+
| Decryption with wrong key | Returns `undefined`, logs error |
|
|
197
|
+
|
|
198
|
+
## Requirements
|
|
199
|
+
|
|
200
|
+
- TypeScript ≥ 5.4
|
|
201
|
+
- ES2022 target (uses `crypto.randomUUID`, `structuredClone`)
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
UNLICENSED — proprietary software.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burgantech/context-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Centralized state store SDK — boundary-based, encrypted, observable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|