@adukiorg/anza 0.2.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/CHANGELOG.md +137 -0
- package/README.md +215 -0
- package/bin/anza.js +63 -0
- package/bin/create.js +150 -0
- package/importmap.json +72 -0
- package/package.json +100 -0
- package/src/core/animations/index.js +55 -0
- package/src/core/animations/play.js +111 -0
- package/src/core/animations/registry.js +54 -0
- package/src/core/animations/scroll.js +50 -0
- package/src/core/animations/tokens.js +58 -0
- package/src/core/animations/usage.md +301 -0
- package/src/core/animations/waapi.js +86 -0
- package/src/core/api/cache.js +120 -0
- package/src/core/api/caches/glob.js +24 -0
- package/src/core/api/caches/index.js +118 -0
- package/src/core/api/events/index.js +75 -0
- package/src/core/api/fetch.js +99 -0
- package/src/core/api/index.js +158 -0
- package/src/core/api/pipeline.js +98 -0
- package/src/core/api/plan.md +209 -0
- package/src/core/api/prefixes/index.js +66 -0
- package/src/core/api/retry.js +69 -0
- package/src/core/api/stream.js +127 -0
- package/src/core/api/upload.js +180 -0
- package/src/core/api/usage.md +206 -0
- package/src/core/events/bus.js +38 -0
- package/src/core/events/delegate.js +79 -0
- package/src/core/events/index.js +26 -0
- package/src/core/events/listen.js +50 -0
- package/src/core/events/missing.md +103 -0
- package/src/core/events/once.js +49 -0
- package/src/core/events/plan.md +177 -0
- package/src/core/events/types/index.js +34 -0
- package/src/core/events/usage.md +107 -0
- package/src/core/offline/bridge.js +51 -0
- package/src/core/offline/clock.js +100 -0
- package/src/core/offline/connectivity.js +116 -0
- package/src/core/offline/index.js +41 -0
- package/src/core/offline/missing.md +89 -0
- package/src/core/offline/plan.md +143 -0
- package/src/core/offline/queue.js +168 -0
- package/src/core/offline/state.js +18 -0
- package/src/core/offline/sync.js +106 -0
- package/src/core/offline/usage.md +273 -0
- package/src/core/platform/guard.js +104 -0
- package/src/core/platform/index.js +42 -0
- package/src/core/platform/missing.md +119 -0
- package/src/core/platform/platform.d.ts +88 -0
- package/src/core/platform/polyfills/anchor.js +79 -0
- package/src/core/platform/polyfills/navigation.js +142 -0
- package/src/core/platform/polyfills/popover.js +142 -0
- package/src/core/platform/polyfills/scheduler.js +194 -0
- package/src/core/platform/polyfills/shadow.js +35 -0
- package/src/core/platform/polyfills/urlpattern.js +119 -0
- package/src/core/platform/supports.js +186 -0
- package/src/core/platform/usage.md +287 -0
- package/src/core/router/cache.js +95 -0
- package/src/core/router/container.js +146 -0
- package/src/core/router/handler.js +52 -0
- package/src/core/router/history.js +120 -0
- package/src/core/router/index.js +158 -0
- package/src/core/router/intercept.js +376 -0
- package/src/core/router/match.js +145 -0
- package/src/core/router/missing.md +716 -0
- package/src/core/router/outlet.js +139 -0
- package/src/core/router/plan.md +370 -0
- package/src/core/router/sync/index.js +16 -0
- package/src/core/router/sync/tab.js +115 -0
- package/src/core/router/sync/transport.js +139 -0
- package/src/core/router/transitions.js +59 -0
- package/src/core/router/usage.md +773 -0
- package/src/core/security/crypto.js +159 -0
- package/src/core/security/index.js +49 -0
- package/src/core/security/missing.md +97 -0
- package/src/core/security/permissions.js +64 -0
- package/src/core/security/sanitize.js +100 -0
- package/src/core/security/usage.md +283 -0
- package/src/core/state/derived.js +117 -0
- package/src/core/state/index.js +23 -0
- package/src/core/state/missing.md +165 -0
- package/src/core/state/persist.js +284 -0
- package/src/core/state/store.js +308 -0
- package/src/core/state/sync.js +46 -0
- package/src/core/state/usage.md +440 -0
- package/src/core/storage/cache.js +83 -0
- package/src/core/storage/idb.js +196 -0
- package/src/core/storage/index.js +373 -0
- package/src/core/storage/lru.js +107 -0
- package/src/core/storage/missing.md +165 -0
- package/src/core/storage/opfs.js +190 -0
- package/src/core/storage/plan.md +69 -0
- package/src/core/storage/quota.js +69 -0
- package/src/core/storage/usage.md +226 -0
- package/src/core/ui/base.js +50 -0
- package/src/core/ui/define/container.js +82 -0
- package/src/core/ui/define/define.js +12 -0
- package/src/core/ui/define/element.js +390 -0
- package/src/core/ui/define/index.js +9 -0
- package/src/core/ui/define/orchestrator.js +105 -0
- package/src/core/ui/define/proxy.js +644 -0
- package/src/core/ui/define/state.js +6 -0
- package/src/core/ui/define/utils.js +134 -0
- package/src/core/ui/implementation.md +170 -0
- package/src/core/ui/index.js +41 -0
- package/src/core/ui/observe.js +117 -0
- package/src/core/ui/plan.md +510 -0
- package/src/core/ui/schedule.js +60 -0
- package/src/core/ui/template.js +37 -0
- package/src/core/ui/transitions.js +37 -0
- package/src/core/ui/ui.types.md +890 -0
- package/src/core/ui/usage.md +1124 -0
- package/src/core/ui/watch.md +346 -0
- package/src/core/workers/broadcast.js +138 -0
- package/src/core/workers/dedicated.js +153 -0
- package/src/core/workers/index.js +169 -0
- package/src/core/workers/locks.js +160 -0
- package/src/core/workers/offscreen.js +166 -0
- package/src/core/workers/plan.md +381 -0
- package/src/core/workers/pool.js +267 -0
- package/src/core/workers/shared.js +137 -0
- package/src/core/workers/usage.md +622 -0
- package/src/elements/base.js +12 -0
- package/src/elements/data/card/index.html +9 -0
- package/src/elements/data/card/index.js +19 -0
- package/src/elements/data/card/index.tags.json +1 -0
- package/src/elements/data/card/style.css +46 -0
- package/src/elements/data/chart/index.html +1 -0
- package/src/elements/data/chart/index.js +143 -0
- package/src/elements/data/chart/index.tags.json +1 -0
- package/src/elements/data/chart/style.css +13 -0
- package/src/elements/data/list/index.html +3 -0
- package/src/elements/data/list/index.js +19 -0
- package/src/elements/data/list/index.tags.json +1 -0
- package/src/elements/data/list/style.css +39 -0
- package/src/elements/data/stat/index.html +9 -0
- package/src/elements/data/stat/index.js +19 -0
- package/src/elements/data/stat/index.tags.json +1 -0
- package/src/elements/data/stat/style.css +50 -0
- package/src/elements/data/table/index.html +1 -0
- package/src/elements/data/table/index.js +16 -0
- package/src/elements/data/table/index.tags.json +1 -0
- package/src/elements/data/table/style.css +50 -0
- package/src/elements/feedback/alert/index.html +11 -0
- package/src/elements/feedback/alert/index.js +28 -0
- package/src/elements/feedback/alert/index.tags.json +1 -0
- package/src/elements/feedback/alert/style.css +75 -0
- package/src/elements/feedback/empty/index.html +13 -0
- package/src/elements/feedback/empty/index.js +34 -0
- package/src/elements/feedback/empty/index.tags.json +1 -0
- package/src/elements/feedback/empty/style.css +45 -0
- package/src/elements/feedback/progress/index.html +7 -0
- package/src/elements/feedback/progress/index.js +46 -0
- package/src/elements/feedback/progress/index.tags.json +1 -0
- package/src/elements/feedback/progress/style.css +36 -0
- package/src/elements/feedback/skeleton/index.html +1 -0
- package/src/elements/feedback/skeleton/index.js +78 -0
- package/src/elements/feedback/skeleton/index.tags.json +1 -0
- package/src/elements/feedback/skeleton/style.css +28 -0
- package/src/elements/feedback/toast/index.html +3 -0
- package/src/elements/feedback/toast/index.js +65 -0
- package/src/elements/feedback/toast/index.tags.json +1 -0
- package/src/elements/feedback/toast/style.css +36 -0
- package/src/elements/forms/checkbox/index.html +7 -0
- package/src/elements/forms/checkbox/index.js +104 -0
- package/src/elements/forms/checkbox/index.tags.json +1 -0
- package/src/elements/forms/checkbox/style.css +86 -0
- package/src/elements/forms/field/index.html +13 -0
- package/src/elements/forms/field/index.js +42 -0
- package/src/elements/forms/field/index.tags.json +1 -0
- package/src/elements/forms/field/style.css +42 -0
- package/src/elements/forms/form/index.html +3 -0
- package/src/elements/forms/form/index.js +122 -0
- package/src/elements/forms/form/index.tags.json +1 -0
- package/src/elements/forms/form/style.css +11 -0
- package/src/elements/forms/input/index.html +4 -0
- package/src/elements/forms/input/index.js +103 -0
- package/src/elements/forms/input/index.tags.json +1 -0
- package/src/elements/forms/input/style.css +39 -0
- package/src/elements/forms/radio/index.html +4 -0
- package/src/elements/forms/radio/index.js +109 -0
- package/src/elements/forms/radio/index.tags.json +1 -0
- package/src/elements/forms/radio/style.css +65 -0
- package/src/elements/forms/select/index.html +9 -0
- package/src/elements/forms/select/index.js +114 -0
- package/src/elements/forms/select/index.tags.json +1 -0
- package/src/elements/forms/select/style.css +95 -0
- package/src/elements/forms/textarea/index.html +4 -0
- package/src/elements/forms/textarea/index.js +115 -0
- package/src/elements/forms/textarea/index.tags.json +1 -0
- package/src/elements/forms/textarea/style.css +46 -0
- package/src/elements/forms/toggle/index.html +4 -0
- package/src/elements/forms/toggle/index.js +89 -0
- package/src/elements/forms/toggle/index.tags.json +1 -0
- package/src/elements/forms/toggle/style.css +63 -0
- package/src/elements/forms/upload/index.html +13 -0
- package/src/elements/forms/upload/index.js +120 -0
- package/src/elements/forms/upload/index.tags.json +1 -0
- package/src/elements/forms/upload/style.css +61 -0
- package/src/elements/index.js +71 -0
- package/src/elements/layout/app/index.html +7 -0
- package/src/elements/layout/app/index.js +16 -0
- package/src/elements/layout/app/index.tags.json +1 -0
- package/src/elements/layout/app/style.css +41 -0
- package/src/elements/layout/grid/index.html +3 -0
- package/src/elements/layout/grid/index.js +41 -0
- package/src/elements/layout/grid/index.tags.json +1 -0
- package/src/elements/layout/grid/style.css +12 -0
- package/src/elements/layout/header/index.html +8 -0
- package/src/elements/layout/header/index.js +16 -0
- package/src/elements/layout/header/index.tags.json +1 -0
- package/src/elements/layout/header/style.css +28 -0
- package/src/elements/layout/scroll/index.html +3 -0
- package/src/elements/layout/scroll/index.js +19 -0
- package/src/elements/layout/scroll/index.tags.json +1 -0
- package/src/elements/layout/scroll/style.css +24 -0
- package/src/elements/layout/sidebar/index.html +3 -0
- package/src/elements/layout/sidebar/index.js +24 -0
- package/src/elements/layout/sidebar/index.tags.json +1 -0
- package/src/elements/layout/sidebar/style.css +30 -0
- package/src/elements/layout/split/index.html +3 -0
- package/src/elements/layout/split/index.js +18 -0
- package/src/elements/layout/split/index.tags.json +1 -0
- package/src/elements/layout/split/style.css +28 -0
- package/src/elements/layout/stack/index.html +3 -0
- package/src/elements/layout/stack/index.js +31 -0
- package/src/elements/layout/stack/index.tags.json +1 -0
- package/src/elements/layout/stack/style.css +15 -0
- package/src/elements/layout/surface/index.html +3 -0
- package/src/elements/layout/surface/index.js +19 -0
- package/src/elements/layout/surface/index.tags.json +1 -0
- package/src/elements/layout/surface/style.css +29 -0
- package/src/elements/navigation/breadcrumb/index.html +5 -0
- package/src/elements/navigation/breadcrumb/index.js +16 -0
- package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
- package/src/elements/navigation/breadcrumb/style.css +36 -0
- package/src/elements/navigation/nav/index.html +3 -0
- package/src/elements/navigation/nav/index.js +24 -0
- package/src/elements/navigation/nav/index.tags.json +1 -0
- package/src/elements/navigation/nav/style.css +38 -0
- package/src/elements/navigation/pagination/index.html +3 -0
- package/src/elements/navigation/pagination/index.js +94 -0
- package/src/elements/navigation/pagination/index.tags.json +1 -0
- package/src/elements/navigation/pagination/style.css +39 -0
- package/src/elements/navigation/steps/index.html +6 -0
- package/src/elements/navigation/steps/index.js +64 -0
- package/src/elements/navigation/steps/index.tags.json +1 -0
- package/src/elements/navigation/steps/style.css +78 -0
- package/src/elements/navigation/tabs/index.html +6 -0
- package/src/elements/navigation/tabs/index.js +132 -0
- package/src/elements/navigation/tabs/index.tags.json +1 -0
- package/src/elements/navigation/tabs/style.css +52 -0
- package/src/elements/overlay/dialog/index.html +5 -0
- package/src/elements/overlay/dialog/index.js +57 -0
- package/src/elements/overlay/dialog/index.tags.json +1 -0
- package/src/elements/overlay/dialog/style.css +31 -0
- package/src/elements/overlay/drawer/index.html +3 -0
- package/src/elements/overlay/drawer/index.js +56 -0
- package/src/elements/overlay/drawer/index.tags.json +1 -0
- package/src/elements/overlay/drawer/style.css +48 -0
- package/src/elements/overlay/menu/index.html +3 -0
- package/src/elements/overlay/menu/index.js +107 -0
- package/src/elements/overlay/menu/index.tags.json +1 -0
- package/src/elements/overlay/menu/style.css +43 -0
- package/src/elements/overlay/popover/index.html +3 -0
- package/src/elements/overlay/popover/index.js +44 -0
- package/src/elements/overlay/popover/index.tags.json +1 -0
- package/src/elements/overlay/popover/style.css +21 -0
- package/src/elements/overlay/sheet/index.html +8 -0
- package/src/elements/overlay/sheet/index.js +105 -0
- package/src/elements/overlay/sheet/index.tags.json +1 -0
- package/src/elements/overlay/sheet/style.css +64 -0
- package/src/elements/overlay/tooltip/index.html +6 -0
- package/src/elements/overlay/tooltip/index.js +16 -0
- package/src/elements/overlay/tooltip/index.tags.json +1 -0
- package/src/elements/overlay/tooltip/style.css +41 -0
- package/src/elements/primitives/avatar/index.html +2 -0
- package/src/elements/primitives/avatar/index.js +79 -0
- package/src/elements/primitives/avatar/index.tags.json +1 -0
- package/src/elements/primitives/avatar/style.css +36 -0
- package/src/elements/primitives/badge/index.html +3 -0
- package/src/elements/primitives/badge/index.js +20 -0
- package/src/elements/primitives/badge/index.tags.json +1 -0
- package/src/elements/primitives/badge/style.css +67 -0
- package/src/elements/primitives/button/index.html +3 -0
- package/src/elements/primitives/button/index.js +61 -0
- package/src/elements/primitives/button/index.tags.json +1 -0
- package/src/elements/primitives/button/style.css +66 -0
- package/src/elements/primitives/divider/index.html +1 -0
- package/src/elements/primitives/divider/index.js +43 -0
- package/src/elements/primitives/divider/index.tags.json +1 -0
- package/src/elements/primitives/divider/style.css +39 -0
- package/src/elements/primitives/icon/index.html +3 -0
- package/src/elements/primitives/icon/index.js +66 -0
- package/src/elements/primitives/icon/index.tags.json +1 -0
- package/src/elements/primitives/icon/style.css +20 -0
- package/src/elements/primitives/link/index.html +3 -0
- package/src/elements/primitives/link/index.js +129 -0
- package/src/elements/primitives/link/index.tags.json +1 -0
- package/src/elements/primitives/link/style.css +40 -0
- package/src/elements/primitives/spinner/index.html +1 -0
- package/src/elements/primitives/spinner/index.js +62 -0
- package/src/elements/primitives/spinner/index.tags.json +1 -0
- package/src/elements/primitives/spinner/style.css +20 -0
- package/src/elements/primitives/text/index.html +1 -0
- package/src/elements/primitives/text/index.js +79 -0
- package/src/elements/primitives/text/index.tags.json +1 -0
- package/src/elements/primitives/text/style.css +25 -0
- package/src/index.js +23 -0
- package/src/styles/base.css +66 -0
- package/src/styles/index.css +10 -0
- package/src/styles/layers.css +9 -0
- package/src/styles/reset.css +66 -0
- package/src/sw/activate.js +51 -0
- package/src/sw/expire.js +47 -0
- package/src/sw/index.js +28 -0
- package/src/sw/install.js +35 -0
- package/src/sw/push.js +58 -0
- package/src/sw/queue.js +60 -0
- package/src/sw/routes.js +71 -0
- package/src/sw/strategies.js +247 -0
- package/src/sw/sync.js +80 -0
- package/src/tokens/index.css +26 -0
- package/src/tokens/primitives/colors.css +54 -0
- package/src/tokens/primitives/motion.css +34 -0
- package/src/tokens/primitives/radius.css +16 -0
- package/src/tokens/primitives/shadow.css +34 -0
- package/src/tokens/primitives/spacing.css +27 -0
- package/src/tokens/primitives/typography.css +46 -0
- package/src/tokens/primitives/zindex.css +18 -0
- package/src/tokens/registered/colors.css +133 -0
- package/src/tokens/registered/dimensions.css +31 -0
- package/src/tokens/semantic/components.css +125 -0
- package/src/tokens/semantic/contrast.css +33 -0
- package/src/tokens/semantic/dark.css +61 -0
- package/src/tokens/semantic/light.css +64 -0
- package/types/core/animations/index.d.ts +52 -0
- package/types/core/api/index.d.ts +68 -0
- package/types/core/events/index.d.ts +50 -0
- package/types/core/offline/index.d.ts +68 -0
- package/types/core/platform/index.d.ts +60 -0
- package/types/core/router/index.d.ts +203 -0
- package/types/core/security/index.d.ts +33 -0
- package/types/core/state/index.d.ts +68 -0
- package/types/core/storage/index.d.ts +40 -0
- package/types/core/ui/index.d.ts +446 -0
- package/types/core/workers/index.d.ts +221 -0
- package/types/elements/index.d.ts +150 -0
- package/types/index.d.ts +18 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Native Security Usage Guide
|
|
2
|
+
|
|
3
|
+
The Native Security layer provides a unified, zero-dependency browser-native security façade covering Web Cryptography, HTML sanitization, Trusted Types, and the Permissions API. All cryptographic operations delegate to the browser's optimized `SubtleCrypto` thread pools. No third-party libraries are required.
|
|
4
|
+
|
|
5
|
+
Import from the security entry point:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { security } from '@adukiorg/anza/security';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or import individual utilities directly:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { uuid, hash, generateKey, encrypt, decrypt, seal, unseal, sign, verify, sanitize, permission, watchPermission } from '@adukiorg/anza/security';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Choosing an API
|
|
20
|
+
|
|
21
|
+
| Need | API | Characteristics |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Unique correlation ID | `uuid()` | Synchronous, `crypto.randomUUID()`, centralized and mockable |
|
|
24
|
+
| Content hashing | `hash(data, algo)` | `SubtleCrypto.digest`, returns `ArrayBuffer` |
|
|
25
|
+
| AES-GCM encryption | `generateKey`, `encrypt`, `decrypt` | Non-extractable by default, fresh 12-byte IV per encrypt |
|
|
26
|
+
| String encryption (base64) | `seal`, `unseal` | Convenience wrappers over `encrypt`/`decrypt` returning base64 strings |
|
|
27
|
+
| Password-derived key | `deriveKey(pw, salt, iters)` | PBKDF2 with 600,000 iterations, AES-GCM output |
|
|
28
|
+
| Integrity / signing | `sign(key, data)` | HMAC or ECDSA depending on key algorithm |
|
|
29
|
+
| Signature verification | `verify(key, sig, data)` | HMAC or ECDSA depending on key algorithm |
|
|
30
|
+
| Safe HTML output | `sanitize(html)` | DOMParser fallback, Trusted Types when available |
|
|
31
|
+
| Permission query | `permission(name)` | Resolves to `'granted'` \| `'denied'` \| `'prompt'` |
|
|
32
|
+
| Permission watcher | `watchPermission(name, fn, signal)` | AbortSignal-gated change listener |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Unique Identifiers
|
|
37
|
+
|
|
38
|
+
`uuid()` is the centralized source of cryptographically secure UUIDs across all platform modules. Using it instead of calling `crypto.randomUUID()` directly makes the system mockable in tests.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
import { uuid } from '@adukiorg/anza/security';
|
|
42
|
+
|
|
43
|
+
const id = uuid(); // e.g. "550e8400-e29b-41d4-a716-446655440000"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 3. Hashing
|
|
49
|
+
|
|
50
|
+
Compute a SHA-256 (or other algorithm) digest of any data:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
import { hash } from '@adukiorg/anza/security';
|
|
54
|
+
|
|
55
|
+
// Hash a string
|
|
56
|
+
const digest = await hash('my-payload');
|
|
57
|
+
console.log(digest instanceof ArrayBuffer); // true — 32 bytes for SHA-256
|
|
58
|
+
|
|
59
|
+
// Hash binary data with SHA-512
|
|
60
|
+
const buf = new TextEncoder().encode('data');
|
|
61
|
+
const sha512 = await hash(buf, 'SHA-512');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 4. Symmetric Encryption (AES-GCM)
|
|
67
|
+
|
|
68
|
+
Generates a non-extractable 256-bit AES-GCM key, encrypts data with a fresh 12-byte IV, and prepends the IV to the ciphertext for portability:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import { generateKey, encrypt, decrypt } from '@adukiorg/anza/security';
|
|
72
|
+
|
|
73
|
+
// Generate a key (non-extractable, in-memory only)
|
|
74
|
+
const key = await generateKey('AES-GCM');
|
|
75
|
+
|
|
76
|
+
// Encrypt
|
|
77
|
+
const cipher = await encrypt(key, 'Secret message'); // returns ArrayBuffer (IV + ciphertext)
|
|
78
|
+
|
|
79
|
+
// Decrypt
|
|
80
|
+
const plainBuf = await decrypt(key, cipher);
|
|
81
|
+
const plain = new TextDecoder().decode(plainBuf);
|
|
82
|
+
console.log(plain); // 'Secret message'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 5. String Encryption (Base64 Convenience)
|
|
88
|
+
|
|
89
|
+
`seal` and `unseal` are string-friendly wrappers over `encrypt`/`decrypt` that return and accept base64-encoded payloads. They are ideal for storing encrypted data in JSON, localStorage, or IndexedDB:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
import { generateKey, seal, unseal } from '@adukiorg/anza/security';
|
|
93
|
+
|
|
94
|
+
const key = await generateKey('AES-GCM');
|
|
95
|
+
|
|
96
|
+
// Encrypt to a base64 string (IV + ciphertext)
|
|
97
|
+
const sealed = await seal(key, 'Secret message');
|
|
98
|
+
// e.g. "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="
|
|
99
|
+
|
|
100
|
+
// Decrypt from base64 string
|
|
101
|
+
const plain = await unseal(key, sealed);
|
|
102
|
+
console.log(plain); // 'Secret message'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The base64 format is portable and can be safely stored as JSON strings:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
await storage.set('secure:session', sealed, 'idb');
|
|
109
|
+
const retrieved = await storage.get('secure:session', 'idb');
|
|
110
|
+
const plain = await unseal(key, retrieved);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 6. Password-Based Key Derivation (PBKDF2)
|
|
116
|
+
|
|
117
|
+
Derive an AES-GCM key from a user password and a salt using 600,000 PBKDF2 iterations (NIST-recommended):
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import { deriveKey, encrypt, decrypt } from '@adukiorg/anza/security';
|
|
121
|
+
|
|
122
|
+
const key = await deriveKey('UserPassword!', 'unique-salt-string');
|
|
123
|
+
|
|
124
|
+
// Use the derived key normally
|
|
125
|
+
const cipher = await encrypt(key, 'protected data');
|
|
126
|
+
const buf = await decrypt(key, cipher);
|
|
127
|
+
const text = new TextDecoder().decode(buf);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> [!IMPORTANT]
|
|
131
|
+
> The salt must be unique per user or credential and stored alongside the ciphertext. Never reuse salts across different passwords.
|
|
132
|
+
|
|
133
|
+
For fast test execution, reduce iterations:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
const key = await deriveKey('pw', 'salt', 1000); // for tests only
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 7. Signing and Verification
|
|
142
|
+
|
|
143
|
+
### HMAC (Symmetric Integrity)
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
import { generateKey, sign, verify } from '@adukiorg/anza/security';
|
|
147
|
+
|
|
148
|
+
const key = await generateKey('HMAC', ['sign', 'verify']);
|
|
149
|
+
const data = 'message to protect';
|
|
150
|
+
|
|
151
|
+
const sig = await sign(key, data); // ArrayBuffer
|
|
152
|
+
const valid = await verify(key, sig, data); // true
|
|
153
|
+
|
|
154
|
+
const tampered = await verify(key, sig, data + '!'); // false
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### ECDSA (Asymmetric Signing)
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// Generate ECDSA key pair using SubtleCrypto directly
|
|
161
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
162
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
163
|
+
false,
|
|
164
|
+
['sign', 'verify']
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const data = 'document-to-sign';
|
|
168
|
+
const sig = await sign(keyPair.privateKey, data);
|
|
169
|
+
const valid = await verify(keyPair.publicKey, sig, data); // true
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 8. HTML Sanitization (Trusted Types)
|
|
175
|
+
|
|
176
|
+
`sanitize()` strips all disallowed tags, event handler attributes, and `javascript:` href links. When the browser supports the Trusted Types API, it wraps the output in a named `TrustedHTML` object (`core-sanitize` policy) to satisfy strict CSP directives:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
import { sanitize } from '@adukiorg/anza/security';
|
|
180
|
+
|
|
181
|
+
// Safe input passes through cleanly
|
|
182
|
+
const safe = String(sanitize('<p class="note"><strong>Hello</strong></p>'));
|
|
183
|
+
// '<p class="note"><strong>Hello</strong></p>'
|
|
184
|
+
|
|
185
|
+
// Malicious input is cleaned
|
|
186
|
+
const unsafe = String(sanitize('<div><script>alert("xss")</script><img onerror="hack()"></div>'));
|
|
187
|
+
// '<div></div>'
|
|
188
|
+
|
|
189
|
+
// javascript: href is stripped
|
|
190
|
+
const link = String(sanitize('<a href="javascript:void(0)">click</a>'));
|
|
191
|
+
// '<a>click</a>'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> [!NOTE]
|
|
195
|
+
> The return type is `TrustedHTML | string`. Use `String(sanitize(...))` when you only need the raw string. Pass the raw result directly to DOM sinks that accept `TrustedHTML` under a Trusted Types policy.
|
|
196
|
+
|
|
197
|
+
> [!WARNING]
|
|
198
|
+
> `sanitize()` is client-side only. It returns the input unchanged when called outside a browser (e.g. Node.js SSR).
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 9. Permissions API
|
|
203
|
+
|
|
204
|
+
### Query Current State
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
import { permission } from '@adukiorg/anza/security';
|
|
208
|
+
|
|
209
|
+
const state = await permission('geolocation');
|
|
210
|
+
// 'granted' | 'denied' | 'prompt'
|
|
211
|
+
|
|
212
|
+
const cam = await permission('camera');
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Returns `'denied'` safely for any unrecognized or unsupported permission name.
|
|
216
|
+
|
|
217
|
+
### Watch for Changes
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
import { watchPermission } from '@adukiorg/anza/security';
|
|
221
|
+
|
|
222
|
+
const ctrl = new AbortController();
|
|
223
|
+
|
|
224
|
+
const dispose = watchPermission('geolocation', (state) => {
|
|
225
|
+
console.log('Permission changed:', state);
|
|
226
|
+
}, ctrl.signal);
|
|
227
|
+
|
|
228
|
+
// Later, tear down cleanly:
|
|
229
|
+
ctrl.abort(); // or dispose();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
> [!TIP]
|
|
233
|
+
> Always pass an `AbortSignal` to `watchPermission` inside components or views so the watcher is removed automatically when the component is destroyed.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 9. Integration Patterns
|
|
238
|
+
|
|
239
|
+
### Lifecycle-Gated Watcher in a Custom Element
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
class MyLocation extends HTMLElement {
|
|
243
|
+
#ctrl = new AbortController();
|
|
244
|
+
|
|
245
|
+
connectedCallback() {
|
|
246
|
+
watchPermission('geolocation', (state) => {
|
|
247
|
+
this.dataset.geo = state;
|
|
248
|
+
}, this.#ctrl.signal);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
disconnectedCallback() {
|
|
252
|
+
this.#ctrl.abort();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Encrypting and Storing Sensitive Data
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
import { generateKey, seal, unseal } from '@adukiorg/anza/security';
|
|
261
|
+
import { storage } from '@adukiorg/anza/storage';
|
|
262
|
+
|
|
263
|
+
// Derive and persist key
|
|
264
|
+
const key = await generateKey('AES-GCM');
|
|
265
|
+
const sealed = await seal(key, JSON.stringify({ token: 'secret' }));
|
|
266
|
+
await storage.set('secure:session', sealed, 'idb');
|
|
267
|
+
|
|
268
|
+
// Retrieve and decrypt
|
|
269
|
+
const retrieved = await storage.get('secure:session', 'idb');
|
|
270
|
+
const plain = await unseal(key, retrieved);
|
|
271
|
+
const { token } = JSON.parse(plain);
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Sanitize Before Setting innerHTML
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
import { sanitize } from '@adukiorg/anza/security';
|
|
278
|
+
|
|
279
|
+
function render(el, userHtml) {
|
|
280
|
+
// TrustedHTML satisfies Trusted Types sinks
|
|
281
|
+
el.innerHTML = sanitize(userHtml);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/state/derived.js
|
|
3
|
+
*
|
|
4
|
+
* Fine-grained reactive derived state (computed nodes).
|
|
5
|
+
* Evaluates computations lazily, memoizes results, and dynamically collects
|
|
6
|
+
* dependency keys by intercepting read accesses during execution.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 08 — State Management §7, §8
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { setActiveSubscriber, getActiveSubscriber } from './store.js';
|
|
12
|
+
|
|
13
|
+
export class DerivedValue {
|
|
14
|
+
#compute;
|
|
15
|
+
#value;
|
|
16
|
+
#dependencies = new Set();
|
|
17
|
+
#dirty = true;
|
|
18
|
+
#listeners = new Set();
|
|
19
|
+
#disposers = [];
|
|
20
|
+
|
|
21
|
+
constructor(compute) {
|
|
22
|
+
if (typeof compute !== 'function') {
|
|
23
|
+
throw new Error('DerivedValue requires a valid compute function');
|
|
24
|
+
}
|
|
25
|
+
this.#compute = compute;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Retrieves the current computed value (evaluates lazily and memoizes result).
|
|
30
|
+
*/
|
|
31
|
+
get value() {
|
|
32
|
+
// Propagate dependency mapping up to parents (for nested derived nodes)
|
|
33
|
+
const outerSubscriber = getActiveSubscriber();
|
|
34
|
+
if (outerSubscriber) {
|
|
35
|
+
for (const dep of this.#dependencies) {
|
|
36
|
+
outerSubscriber.add(dep);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.#dirty) {
|
|
41
|
+
this.#recompute();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return this.#value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#recompute() {
|
|
48
|
+
// Revoke previous listeners
|
|
49
|
+
for (const dispose of this.#disposers) {
|
|
50
|
+
dispose();
|
|
51
|
+
}
|
|
52
|
+
this.#disposers = [];
|
|
53
|
+
this.#dependencies.clear();
|
|
54
|
+
|
|
55
|
+
// Trap active gets inside execution context
|
|
56
|
+
const previous = getActiveSubscriber();
|
|
57
|
+
const tracked = new Set();
|
|
58
|
+
setActiveSubscriber(tracked);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
this.#value = this.#compute();
|
|
62
|
+
this.#dirty = false;
|
|
63
|
+
this.#dependencies = tracked;
|
|
64
|
+
|
|
65
|
+
// Reactively attach to all newly resolved dependency nodes
|
|
66
|
+
for (const dep of this.#dependencies) {
|
|
67
|
+
const dispose = dep.store.subscribe(dep.key, () => {
|
|
68
|
+
this.#markDirty();
|
|
69
|
+
});
|
|
70
|
+
this.#disposers.push(dispose);
|
|
71
|
+
}
|
|
72
|
+
} finally {
|
|
73
|
+
setActiveSubscriber(previous);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#markDirty() {
|
|
78
|
+
if (this.#dirty) return;
|
|
79
|
+
this.#dirty = true;
|
|
80
|
+
|
|
81
|
+
for (const callback of this.#listeners) {
|
|
82
|
+
try {
|
|
83
|
+
callback();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error('Error executing derived state change subscription:', err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Subscribes to updates on this derived state node.
|
|
92
|
+
*/
|
|
93
|
+
subscribe(callback) {
|
|
94
|
+
this.#listeners.add(callback);
|
|
95
|
+
return () => {
|
|
96
|
+
this.#listeners.delete(callback);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Releases and cleans up active subscriptions.
|
|
102
|
+
*/
|
|
103
|
+
dispose() {
|
|
104
|
+
for (const dispose of this.#disposers) {
|
|
105
|
+
dispose();
|
|
106
|
+
}
|
|
107
|
+
this.#disposers = [];
|
|
108
|
+
this.#listeners.clear();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Creates a memoized derived state node.
|
|
114
|
+
*/
|
|
115
|
+
export function derived(compute) {
|
|
116
|
+
return new DerivedValue(compute);
|
|
117
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/state/index.js
|
|
3
|
+
*
|
|
4
|
+
* Public state management namespace.
|
|
5
|
+
* Bundles reactive in-memory stores, lazy derived derivations,
|
|
6
|
+
* cross-tab replications, and transactional IndexedDB persistence.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 08 — State Management §2, §15
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ReactiveStore, setActiveSubscriber, getActiveSubscriber } from './store.js';
|
|
12
|
+
import { derived } from './derived.js';
|
|
13
|
+
import { sync } from './sync.js';
|
|
14
|
+
import { PlatformStorage, storage } from './persist.js';
|
|
15
|
+
|
|
16
|
+
export const state = {
|
|
17
|
+
create: (initial, options) => new ReactiveStore(initial, options),
|
|
18
|
+
derived,
|
|
19
|
+
sync,
|
|
20
|
+
storage
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { ReactiveStore, setActiveSubscriber, getActiveSubscriber, derived, sync, PlatformStorage, storage };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# State Missing Support
|
|
2
|
+
|
|
3
|
+
This document tracks remaining support, runtime bugs, type mismatches, and performance optimization work for `src/core/state`. Implemented behavior belongs in `usage.md` (or core documentation); unsupported, untyped, or under-tested behavior belongs here until it is built and verified.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 0. Toolchain and Naming Rules
|
|
8
|
+
|
|
9
|
+
Heavy validation, static analysis, and code generation belong in `tools/`, not in browser runtime modules.
|
|
10
|
+
|
|
11
|
+
Current toolchain state support requirements:
|
|
12
|
+
- Analyze `state` initialization and usage patterns statically.
|
|
13
|
+
- Detect mutations violating the immutability discipline.
|
|
14
|
+
- Generate unified type definitions for state domains.
|
|
15
|
+
|
|
16
|
+
Naming rules for state support:
|
|
17
|
+
- Prefer one-word files: `store.js`, `derived.js`, `persist.js`, `sync.js`, `index.js`.
|
|
18
|
+
- Plural folders: `tests/`, `types/`.
|
|
19
|
+
- Single-word methods: `get`, `set`, `batch`, `sync`, `derived`, `clear`, `reset`, `estimate`, `persist`.
|
|
20
|
+
- Single-word events: `change`, `hydrate`, `quota`, `error`.
|
|
21
|
+
- Avoid compound names where single words carry full meaning in context.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. Critical Runtime and Type Gaps
|
|
26
|
+
|
|
27
|
+
### 1.1. Storage Type Mismatches
|
|
28
|
+
- **Status**: Critical mismatch.
|
|
29
|
+
- **Files**:
|
|
30
|
+
- `types/core/state/index.d.ts`
|
|
31
|
+
- `src/core/state/persist.js`
|
|
32
|
+
- **Problem**:
|
|
33
|
+
The type declaration file defines `storage` with `persist(store, options)` and `hydrate(store, name)`. The actual runtime `storage` export is an instance of `PlatformStorage` exposing CRUD and system queries: `get(store, key)`, `set(store, key, val)`, `delete(store, key)`, `query(store, filter)`, `estimate()`, `persist()`, and `isPersisted()`.
|
|
34
|
+
- **Expected Support**:
|
|
35
|
+
- Align `types/core/state/index.d.ts` to expose the true `PlatformStorage` interface.
|
|
36
|
+
- Implement helper hooks in the store or `persist.js` to simplify store-wide persistence if required, but maintain structural parity.
|
|
37
|
+
|
|
38
|
+
### 1.2. Writable Store Event and Helper Omissions
|
|
39
|
+
- **Status**: Runtime gap.
|
|
40
|
+
- **Files**:
|
|
41
|
+
- `src/core/state/store.js`
|
|
42
|
+
- `src/core/state/index.js`
|
|
43
|
+
- **Problem**:
|
|
44
|
+
The State Management specification (§15) defines `store.broadcast(channelName)` as a method directly on the store instance for cross-tab replication. Currently, this functionality is decoupled into a standalone `sync(store, keys, channelName)` function. Similarly, `store.derived(keys, compute)` is specified but not present on the store instance class.
|
|
45
|
+
- **Expected Support**:
|
|
46
|
+
- Add a delegate helper `broadcast(channelName, keys?)` to `ReactiveStore` mapping directly to the `sync` utility.
|
|
47
|
+
- Add a delegate helper `derived(keys, compute)` to `ReactiveStore` mapping to `derived(compute)` with key-bound invalidation if preferred, or clean up the API specification.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 2. Persistence Gaps (Storage Quota and Eviction)
|
|
52
|
+
|
|
53
|
+
### 2.1. Proactive Eviction and Quota Warnings
|
|
54
|
+
- **Status**: Planned but missing.
|
|
55
|
+
- **File**:
|
|
56
|
+
- `src/core/state/persist.js`
|
|
57
|
+
- **Problem**:
|
|
58
|
+
The storage engine does not monitor available quota or execute proactive eviction. Large applications risk throwing unhandled `QuotaExceededError` exceptions when writing data.
|
|
59
|
+
- **Expected Support**:
|
|
60
|
+
- Implement a quota check wrapper on `set()`.
|
|
61
|
+
- Establish a warning threshold (80% of estimated quota).
|
|
62
|
+
- Add proactive eviction stages:
|
|
63
|
+
1. Prune expired cache records based on TTL metadata.
|
|
64
|
+
2. Evict low-recency records (LRU) using a `lastAccessed` timestamp.
|
|
65
|
+
- Dispatch a global `quota` warning event if space remains critical.
|
|
66
|
+
|
|
67
|
+
### 2.2. Write-Through and Write-Ahead Transaction Queues
|
|
68
|
+
- **Status**: Missing support.
|
|
69
|
+
- **File**:
|
|
70
|
+
- `src/core/state/persist.js`
|
|
71
|
+
- **Problem**:
|
|
72
|
+
Mutating state is not atomic. A crash between memory writing and database writing leaves inconsistent schemas.
|
|
73
|
+
- **Expected Support**:
|
|
74
|
+
- Add a transaction-safe queue to handle write-ahead serialization (log first, modify memory on confirmation).
|
|
75
|
+
- Implement retry logic in the write queue on transient storage lockups or quota exceptions.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 3. Reactivity Performance and Deep Store Support
|
|
80
|
+
|
|
81
|
+
### 3.1. Deep Reactive Stores
|
|
82
|
+
- **Status**: Enhancement.
|
|
83
|
+
- **File**:
|
|
84
|
+
- `src/core/state/store.js`
|
|
85
|
+
- **Problem**:
|
|
86
|
+
`ReactiveStore` only intercepts top-level properties (shallow). Mutating nested objects (e.g., `store.get('user').name = 'alice'`) bypasses reactive traps. Manual structural sharing (e.g., `store.set('user', { ...user, name: 'alice' })`) is error-prone.
|
|
87
|
+
- **Expected Support**:
|
|
88
|
+
- Add opt-in deep reactivity configuration on store instantiation.
|
|
89
|
+
- Recursively wrap accessed nested object fields in reactive `Proxy` instances, returning mapped pathways.
|
|
90
|
+
- Validate that circular references do not crash proxy wrappers.
|
|
91
|
+
|
|
92
|
+
### 3.2. Optimized Snapshots
|
|
93
|
+
- **Status**: Performance enhancement.
|
|
94
|
+
- **File**:
|
|
95
|
+
- `src/core/state/store.js`
|
|
96
|
+
- **Problem**:
|
|
97
|
+
`store.snapshot()` falls back to `JSON.parse(JSON.stringify(...))` when `structuredClone` is missing or if non-serializable objects (like `Map` or `Set`) are stored. This degrades speed in performance-sensitive contexts.
|
|
98
|
+
- **Expected Support**:
|
|
99
|
+
- Use custom fast-cloning paths for pure data objects.
|
|
100
|
+
- Delegate serialization for custom collection types within the store configuration.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 4. Build-Time Static Analysis
|
|
105
|
+
|
|
106
|
+
### 4.1. Immutability Violation Scan
|
|
107
|
+
- **Status**: Planned.
|
|
108
|
+
- **Files**:
|
|
109
|
+
- `tools/src/extract/runner.rs`
|
|
110
|
+
- **Problem**:
|
|
111
|
+
In-place mutations of objects retrieved from reactive stores (e.g. `store.get('user').name = 'bob'`) bypasses the `Object.is` check and stops reactive updates. This is hard to debug at runtime.
|
|
112
|
+
- **Expected Support**:
|
|
113
|
+
- Implement a AST-based checker in `anza` to scan JavaScript sources for store retrieval variables followed by mutation assignments.
|
|
114
|
+
- Log lint warnings during `scan` and `dev` runs.
|
|
115
|
+
|
|
116
|
+
### 4.2. Schema Type Generation
|
|
117
|
+
- **Status**: Enhancement.
|
|
118
|
+
- **Files**:
|
|
119
|
+
- `tools/src/extract/runner.rs`
|
|
120
|
+
- **Expected Support**:
|
|
121
|
+
- Statically analyze store initializations to output a typed state manifest schema (`state.d.ts`).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 5. Test Coverage Gaps
|
|
126
|
+
|
|
127
|
+
### 5.1. No Persistence Tests
|
|
128
|
+
- **Status**: Missing coverage.
|
|
129
|
+
- **File**:
|
|
130
|
+
- `tests/core/state/persist.test.js`
|
|
131
|
+
- **Needed Coverage**:
|
|
132
|
+
- Database instantiation and migrations.
|
|
133
|
+
- CRUD operations (`get`, `set`, `delete`, `query`).
|
|
134
|
+
- Quota estimate mock and persistence hooks.
|
|
135
|
+
- Eviction stages validation.
|
|
136
|
+
|
|
137
|
+
### 5.2. Tab Sync Robustness
|
|
138
|
+
- **Status**: Weak coverage.
|
|
139
|
+
- **File**:
|
|
140
|
+
- `tests/core/state/sync.test.js`
|
|
141
|
+
- **Needed Coverage**:
|
|
142
|
+
- Re-entrancy and circular echo avoidance.
|
|
143
|
+
- Proper disposal of broadcast channels.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 6. Suggested Implementation Order
|
|
148
|
+
|
|
149
|
+
1. Align `types/core/state/index.d.ts` with runtime `PlatformStorage`.
|
|
150
|
+
2. Add `tests/core/state/persist.test.js` to cover basic IndexedDB CRUD operations.
|
|
151
|
+
3. Expose `store.broadcast()` and `store.derived()` delegate methods.
|
|
152
|
+
4. Implement recursive deep proxy wrapper options in `ReactiveStore`.
|
|
153
|
+
5. Implement quota warning thresholds and basic recency-based eviction.
|
|
154
|
+
6. Add build-time immutability violation scanner to `anza`.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 7. Done Criteria
|
|
159
|
+
|
|
160
|
+
This missing-support list is complete when:
|
|
161
|
+
- Type declarations for `storage` match runtime methods.
|
|
162
|
+
- Persistent local store has comprehensive tests covering IndexedDB operations.
|
|
163
|
+
- Deep reactive stores are optionally configurable and verified.
|
|
164
|
+
- Static scanning tools warn developers of inline store mutations.
|
|
165
|
+
- Eviction and warning systems prevent silent quota failures.
|