@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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/offline/sync.js
|
|
3
|
+
*
|
|
4
|
+
* Background Sync Manager.
|
|
5
|
+
* Registers Service Worker Background Sync descriptors for Chromium-based browsers,
|
|
6
|
+
* falling back to custom window online-event triggers on Safari and Firefox.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 13 — Offline and Background §4
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { queue } from './queue.js';
|
|
12
|
+
import { state } from './state.js';
|
|
13
|
+
|
|
14
|
+
export class SyncManager {
|
|
15
|
+
constructor() {
|
|
16
|
+
if (
|
|
17
|
+
typeof navigator !== 'undefined' &&
|
|
18
|
+
'serviceWorker' in navigator
|
|
19
|
+
) {
|
|
20
|
+
navigator.serviceWorker.addEventListener('message', async (event) => {
|
|
21
|
+
const data = event.data;
|
|
22
|
+
if (data && (data.type === 'sync-success' || data.type === 'sync-failed')) {
|
|
23
|
+
try {
|
|
24
|
+
const list = await queue.list();
|
|
25
|
+
state.set('pending', list.length);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error('Failed to sync pending task count on service worker message:', err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Registers a sync tag with the Service Worker Background Sync API, falling back
|
|
36
|
+
* to manual online triggers if unsupported.
|
|
37
|
+
*/
|
|
38
|
+
async register(tag) {
|
|
39
|
+
if (
|
|
40
|
+
typeof navigator !== 'undefined' &&
|
|
41
|
+
'serviceWorker' in navigator
|
|
42
|
+
) {
|
|
43
|
+
try {
|
|
44
|
+
const registration = await navigator.serviceWorker.ready;
|
|
45
|
+
if (registration.sync) {
|
|
46
|
+
await registration.sync.register(tag);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.warn('Background Sync registration failed; falling back to listener:', err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Resilient fallback for Safari and Firefox
|
|
55
|
+
if (typeof window !== 'undefined') {
|
|
56
|
+
const trigger = () => {
|
|
57
|
+
window.removeEventListener('online', trigger);
|
|
58
|
+
// Dispatch a custom event detailing the pending tag
|
|
59
|
+
const event = new CustomEvent('core:sync-fallback', { detail: { tag } });
|
|
60
|
+
window.dispatchEvent(event);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
window.addEventListener('online', trigger);
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Subscribes to manual sync fallback triggers (useful for executing queue replays on Safari/Firefox).
|
|
70
|
+
*/
|
|
71
|
+
onSyncFallback(fn, signal) {
|
|
72
|
+
if (signal?.aborted || typeof window === 'undefined') return () => {};
|
|
73
|
+
|
|
74
|
+
const listener = async (event) => {
|
|
75
|
+
// Coordinated fallback execution using Web Locks
|
|
76
|
+
if (typeof navigator !== 'undefined' && navigator.locks) {
|
|
77
|
+
try {
|
|
78
|
+
await navigator.locks.request('offline:sync', async () => {
|
|
79
|
+
await fn(event.detail.tag);
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('Failed to execute fallback sync under Web Lock:', err);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
await fn(event.detail.tag);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
window.addEventListener('core:sync-fallback', listener);
|
|
89
|
+
|
|
90
|
+
const dispose = () => {
|
|
91
|
+
window.removeEventListener('core:sync-fallback', listener);
|
|
92
|
+
if (signal) {
|
|
93
|
+
signal.removeEventListener('abort', dispose);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (signal) {
|
|
98
|
+
signal.addEventListener('abort', dispose, { once: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return dispose;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const sync = new SyncManager();
|
|
106
|
+
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# Offline & Background Capabilities Usage Guide
|
|
2
|
+
|
|
3
|
+
The Native Offline layer provides tools for building local-first, network-resilient web applications. It encapsulates reactive connectivity state tracking, crash-resilient IndexedDB task queuing with write-ahead journaling, Web Lock-coordinated manual sync fallbacks, direct Service Worker bridge communication, and monotonic logical Lamport clocks for conflict-free replication.
|
|
4
|
+
|
|
5
|
+
Import from the offline entry point:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { offline } from '@adukiorg/anza/offline';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or import individual components directly:
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { check, subscribe, queue, sync, bridge, state, clock } from '@adukiorg/anza/offline';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. Choosing an API
|
|
20
|
+
|
|
21
|
+
| Need | Use |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Query network connectivity | `check` |
|
|
24
|
+
| Subscribe to network status events | `subscribe` |
|
|
25
|
+
| Get current network/queue state | `state.get` or `state.snapshot` |
|
|
26
|
+
| Subscribe to reactive state updates | `state.subscribe` |
|
|
27
|
+
| Enqueue a background sync task | `queue.push` |
|
|
28
|
+
| List chronological tasks in the queue | `queue.list` |
|
|
29
|
+
| Update task status or retry count | `queue.update` |
|
|
30
|
+
| Evict a resolved task from the queue | `queue.delete` |
|
|
31
|
+
| Clear the entire offline queue | `queue.clear` |
|
|
32
|
+
| Register background sync tags | `sync.register` |
|
|
33
|
+
| Coordinate manual fallback sync | `sync.onSyncFallback` |
|
|
34
|
+
| Send messages to Service Worker | `bridge.send` or `offline.send` |
|
|
35
|
+
| Retrieve client persistent actor ID | `clock.actor` |
|
|
36
|
+
| Monotonically tick local clock counter | `clock.tick` |
|
|
37
|
+
| Sync local clock against remote time | `clock.sync` |
|
|
38
|
+
| Generate logical clock stamp | `clock.stamp` |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. Connectivity & State Tracking
|
|
43
|
+
|
|
44
|
+
The connectivity module handles network status monitoring and reachability checks.
|
|
45
|
+
|
|
46
|
+
### Checking Connectivity
|
|
47
|
+
The `check(force?)` function checks network reachability. It automatically rate-limits HEAD probes to `/favicon.ico` (cached for 10 seconds) to avoid thrashing the network. Pass `true` to bypass throttling.
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// Perform a throttled connectivity check
|
|
51
|
+
const online = await check();
|
|
52
|
+
|
|
53
|
+
// Force an immediate probe, bypassing the 10s throttling window
|
|
54
|
+
const absoluteOnline = await check(true);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Event Subscriptions
|
|
58
|
+
Use `subscribe(callback, signal?)` to listen to connectivity status changes. You can pass an optional `AbortSignal` for automated unsubscription.
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
|
|
63
|
+
const dispose = subscribe((online) => {
|
|
64
|
+
console.log(online ? 'Connected' : 'Disconnected');
|
|
65
|
+
}, controller.signal);
|
|
66
|
+
|
|
67
|
+
// Unsubscribe manually
|
|
68
|
+
dispose();
|
|
69
|
+
|
|
70
|
+
// Or automatically clean up by aborting the controller
|
|
71
|
+
controller.abort();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Reactive State Store
|
|
75
|
+
The `state` store is a fine-grained `ReactiveStore` keeping track of network connectivity metrics and task queue length:
|
|
76
|
+
* `online` (boolean): Whether the client is currently online.
|
|
77
|
+
* `status` (string): `'online' | 'offline' | 'unknown'`.
|
|
78
|
+
* `pending` (number): Monitored count of uncompleted tasks in the queue.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
// Subscribe to reactive key mutations
|
|
82
|
+
state.subscribe('pending', (count) => {
|
|
83
|
+
console.log(`Pending offline tasks: ${count}`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Access current state snapshot
|
|
87
|
+
const snapshot = state.snapshot();
|
|
88
|
+
console.log(`Status: ${snapshot.status}, Online: ${snapshot.online}`);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. Persistent Task Queue (`OfflineQueue`)
|
|
94
|
+
|
|
95
|
+
The offline queue is backed by IndexedDB (`platform-offline-queue` / `tasks`). It preserves causal chronological sequencing (FIFO) and is protected by write-ahead journaling.
|
|
96
|
+
|
|
97
|
+
### Enqueuing Tasks
|
|
98
|
+
Use `queue.push(taskName, payload?, options?)` to add tasks to the offline queue. If local client storage usage exceeds 80%, the write is blocked and throws a `QuotaExceededError`.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
try {
|
|
102
|
+
const taskId = await queue.push(
|
|
103
|
+
'order:submit',
|
|
104
|
+
{ itemId: 42, quantity: 2 },
|
|
105
|
+
{
|
|
106
|
+
idempotencyKey: 'order-c104', // Optional: defaults to a random UUID
|
|
107
|
+
maxRetries: 3 // Optional: defaults to 5
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
console.log('Task buffered with ID:', taskId);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err.name === 'QuotaExceededError') {
|
|
113
|
+
console.error('Storage full. Clear space before enqueuing.');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Managing Tasks
|
|
119
|
+
Retrieve, update, and delete tasks from the queue as they are processed:
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
// List all queued tasks sorted chronologically (oldest first)
|
|
123
|
+
const tasks = await queue.list();
|
|
124
|
+
|
|
125
|
+
for (const task of tasks) {
|
|
126
|
+
if (task.failed) {
|
|
127
|
+
console.warn(`Task permanently failed: ${task.error}`);
|
|
128
|
+
await queue.delete(task.id); // Evict from queue
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// Process the task...
|
|
134
|
+
await queue.delete(task.id);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
task.retries++;
|
|
137
|
+
if (task.retries >= task.maxRetries) {
|
|
138
|
+
task.failed = true;
|
|
139
|
+
task.error = err.message;
|
|
140
|
+
}
|
|
141
|
+
await queue.update(task); // Persist updated state
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Clear the entire queue
|
|
146
|
+
await queue.clear();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 4. Background Sync & Fallbacks
|
|
152
|
+
|
|
153
|
+
The `sync` manager coordinates queue replays using native browser Background Sync or event-driven fallbacks.
|
|
154
|
+
|
|
155
|
+
### Registering Sync Events
|
|
156
|
+
Register sync tags with the Service Worker Background Sync API. On browsers without Background Sync support (e.g. Safari, Firefox), the manager registers a custom window-level online fallback listener.
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
const registered = await sync.register('pending');
|
|
160
|
+
if (registered) {
|
|
161
|
+
console.log('Native service worker background sync registered.');
|
|
162
|
+
} else {
|
|
163
|
+
console.log('Safari/Firefox fallback online listener registered.');
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Web Lock Coordinated Fallback
|
|
168
|
+
When a fallback online event fires in multi-tab sessions, all tabs simultaneously receive the event. To prevent concurrent database access and duplicate HTTP requests, the callback is automatically coordinated under an exclusive Web Lock named `"offline:sync"`.
|
|
169
|
+
|
|
170
|
+
Only one active tab will acquire the lock and execute the sync handler:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
const controller = new AbortController();
|
|
174
|
+
|
|
175
|
+
const dispose = sync.onSyncFallback(async (tag) => {
|
|
176
|
+
// Awaiting Web Lock 'offline:sync' is handled internally
|
|
177
|
+
console.log(`Replaying queue for tag: ${tag}`);
|
|
178
|
+
|
|
179
|
+
// Replay tasks chronologically...
|
|
180
|
+
}, controller.signal);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 5. Service Worker Bridge
|
|
186
|
+
|
|
187
|
+
The `bridge` facilitates direct message dispatch to the active Service Worker controller using native `MessageChannel` instances for response routing.
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// Dispatch a task to the service worker and wait for a response
|
|
191
|
+
try {
|
|
192
|
+
const result = await bridge.send('cache:precache', {
|
|
193
|
+
urls: ['/index.html', '/styles.css']
|
|
194
|
+
});
|
|
195
|
+
console.log('Precached files successfully:', result);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error('Service worker message failed:', err.message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Shorthand syntax using the offline facade
|
|
201
|
+
await offline.send('sync:force');
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 6. Logical Lamport Clocks
|
|
207
|
+
|
|
208
|
+
Distributed local-first applications cannot rely on physical clocks due to device clock drift. The `clock` manager implements logical Lamport clocks to stamp offline mutations, ensuring Last-Write-Wins (LWW) resolution is perfectly deterministic.
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
import { clock } from '@adukiorg/anza/offline';
|
|
212
|
+
|
|
213
|
+
// 1. Retrieve the local client's persistent actor UUID
|
|
214
|
+
const clientUUID = await clock.actor();
|
|
215
|
+
|
|
216
|
+
// 2. Monotonically increment and get the local logical clock
|
|
217
|
+
const time = await clock.tick();
|
|
218
|
+
|
|
219
|
+
// 3. Sync the local clock when observing a remote logical counter (e.g. from server response headers)
|
|
220
|
+
// Automatically advances the local clock to be greater than the observed remote counter
|
|
221
|
+
const syncedTime = await clock.sync(120);
|
|
222
|
+
|
|
223
|
+
// 4. Generate a combined logical stamp tuple
|
|
224
|
+
const stamp = await clock.stamp();
|
|
225
|
+
// Returns: { actor: "b9a2-...", clock: 121 }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Deterministic LWW Resolution
|
|
229
|
+
Compare logical stamps during data conflict merges:
|
|
230
|
+
* The stamp with the higher `clock` counter wins.
|
|
231
|
+
* If `clock` counters are equal, the lexicographically greater `actor` UUID acts as the tiebreaker.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 7. Web Component Integration
|
|
236
|
+
|
|
237
|
+
The offline modules integrate seamlessly with the declarative `@adukiorg/anza` UI elements framework. The component's `mount` hook receives an `AbortController` (`ctrl`) that is automatically aborted when the element is disconnected from the DOM, resolving all subscriber memory safety concerns.
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
import { ui } from '@adukiorg/anza/ui';
|
|
241
|
+
import { check, queue, sync, state } from '@adukiorg/anza/offline';
|
|
242
|
+
|
|
243
|
+
ui.element('offline-status-banner', {
|
|
244
|
+
template: `
|
|
245
|
+
<div class="banner">
|
|
246
|
+
<span class="message"></span>
|
|
247
|
+
<span class="count"></span>
|
|
248
|
+
</div>
|
|
249
|
+
`,
|
|
250
|
+
style: `
|
|
251
|
+
.banner { display: none; padding: 1rem; text-align: center; }
|
|
252
|
+
.banner.active { display: block; }
|
|
253
|
+
`,
|
|
254
|
+
|
|
255
|
+
mount({ el, ctrl }) {
|
|
256
|
+
const { signal } = ctrl;
|
|
257
|
+
const container = el.shadowRoot.querySelector('.banner');
|
|
258
|
+
const message = el.shadowRoot.querySelector('.message');
|
|
259
|
+
const countSpan = el.shadowRoot.querySelector('.count');
|
|
260
|
+
|
|
261
|
+
// Subscribe to connectivity status changes reactively
|
|
262
|
+
state.subscribe('online', (online) => {
|
|
263
|
+
container.classList.toggle('active', !online);
|
|
264
|
+
message.textContent = online ? '' : 'You are working offline.';
|
|
265
|
+
}, signal);
|
|
266
|
+
|
|
267
|
+
// Track pending queue count reactively
|
|
268
|
+
state.subscribe('pending', (count) => {
|
|
269
|
+
countSpan.textContent = count > 0 ? `(${count} changes pending)` : '';
|
|
270
|
+
}, signal);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/platform/guard.js
|
|
3
|
+
*
|
|
4
|
+
* Feature-gate wrapper and lazy polyfill loader.
|
|
5
|
+
* Ensures caller experiences uniform APIs regardless of native vs polyfill support.
|
|
6
|
+
* Source: doc 18 §12, library2.md §Phase 1-A
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { supports } from './supports.js';
|
|
10
|
+
|
|
11
|
+
export async function urlPattern() {
|
|
12
|
+
if (supports.urlPattern) {
|
|
13
|
+
return globalThis.URLPattern;
|
|
14
|
+
}
|
|
15
|
+
const { default: polyfill } = await import('./polyfills/urlpattern.js');
|
|
16
|
+
return polyfill;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function navigation() {
|
|
20
|
+
if (supports.navigationAPI) {
|
|
21
|
+
return globalThis.navigation;
|
|
22
|
+
}
|
|
23
|
+
await import('./polyfills/navigation.js');
|
|
24
|
+
return globalThis.navigation;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function popover() {
|
|
28
|
+
if (supports.popoverAPI) return;
|
|
29
|
+
await import('./polyfills/popover.js');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function shadow(root = document) {
|
|
33
|
+
if (supports.declarativeShadowDOM) return;
|
|
34
|
+
const { apply } = await import('./polyfills/shadow.js');
|
|
35
|
+
apply(root);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function anchor(floating, anchorEl, options = {}) {
|
|
39
|
+
if (supports.anchorPositioning) {
|
|
40
|
+
// Native CSS Anchor positioning handles this; no script action needed.
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const { position } = await import('./polyfills/anchor.js');
|
|
44
|
+
position(floating, anchorEl, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function sanitizer() {
|
|
48
|
+
if (supports.sanitizerAPI && typeof globalThis.Sanitizer === 'function') {
|
|
49
|
+
try {
|
|
50
|
+
const s = new globalThis.Sanitizer();
|
|
51
|
+
if (typeof s.sanitize === 'function') {
|
|
52
|
+
return {
|
|
53
|
+
sanitizeToString(input) {
|
|
54
|
+
const temp = document.createElement('div');
|
|
55
|
+
temp.innerHTML = input;
|
|
56
|
+
const fragment = s.sanitize(temp);
|
|
57
|
+
const wrapper = document.createElement('div');
|
|
58
|
+
wrapper.appendChild(fragment);
|
|
59
|
+
return wrapper.innerHTML;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// Fallback if construction fails
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Lightweight DOMPurify / Sanitizer standard fallback
|
|
68
|
+
return {
|
|
69
|
+
sanitizeToString(input) {
|
|
70
|
+
if (typeof DOMPurify !== 'undefined') {
|
|
71
|
+
return DOMPurify.sanitize(input);
|
|
72
|
+
}
|
|
73
|
+
// Fail-safe simple string sanitizer
|
|
74
|
+
const temp = document.createElement('div');
|
|
75
|
+
temp.textContent = input;
|
|
76
|
+
return temp.innerHTML;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function scheduler() {
|
|
82
|
+
if (supports.schedulerPostTask) {
|
|
83
|
+
return globalThis.scheduler;
|
|
84
|
+
}
|
|
85
|
+
await import('./polyfills/scheduler.js');
|
|
86
|
+
return globalThis.scheduler;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function yieldTask() {
|
|
90
|
+
const s = await scheduler();
|
|
91
|
+
return s.yield();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default {
|
|
95
|
+
urlPattern,
|
|
96
|
+
navigation,
|
|
97
|
+
popover,
|
|
98
|
+
shadow,
|
|
99
|
+
anchor,
|
|
100
|
+
sanitizer,
|
|
101
|
+
scheduler,
|
|
102
|
+
yield: yieldTask
|
|
103
|
+
};
|
|
104
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/platform/index.js
|
|
3
|
+
*
|
|
4
|
+
* Public platform module entry.
|
|
5
|
+
* Re-exports supports detection registry and the lazy-load guard,
|
|
6
|
+
* while eagerly bootstrapping necessary global environment polyfills.
|
|
7
|
+
* Source: doc 18 §12, library2.md §Phase 1-A
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { supports, reset, typeGuard } from './supports.js';
|
|
11
|
+
import guard from './guard.js';
|
|
12
|
+
|
|
13
|
+
// Bootstrapping of critical global environment gaps
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
if (!supports.declarativeShadowDOM) {
|
|
16
|
+
import('./polyfills/shadow.js').catch(err => {
|
|
17
|
+
console.error('Failed to eagerly bootstrap shadow polyfill:', err);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (!supports.popoverAPI) {
|
|
21
|
+
import('./polyfills/popover.js').catch(err => {
|
|
22
|
+
console.error('Failed to eagerly bootstrap popover polyfill:', err);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (!supports.urlPattern) {
|
|
26
|
+
import('./polyfills/urlpattern.js').catch(err => {
|
|
27
|
+
console.error('Failed to eagerly bootstrap URLPattern polyfill:', err);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!supports.navigationAPI) {
|
|
31
|
+
import('./polyfills/navigation.js').catch(err => {
|
|
32
|
+
console.error('Failed to eagerly bootstrap Navigation polyfill:', err);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (!supports.schedulerPostTask) {
|
|
36
|
+
import('./polyfills/scheduler.js').catch(err => {
|
|
37
|
+
console.error('Failed to eagerly bootstrap Scheduler polyfill:', err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { supports, reset, typeGuard, guard };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Platform Missing Support
|
|
2
|
+
|
|
3
|
+
This document tracks remaining support, runtime gaps, type mismatches, and storage synchronization improvements for `src/core/platform`. Implemented behavior belongs in `usage.md` (or core documentation); unsupported, under-tested, or performance-gated behavior belongs here until it is built and verified.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 0. Toolchain and Naming Rules
|
|
8
|
+
|
|
9
|
+
Heavy compilation, static analysis, and code generation belong in `tools/`, not in browser runtime modules.
|
|
10
|
+
|
|
11
|
+
Current toolchain platform support requirements:
|
|
12
|
+
|
|
13
|
+
- Statically verify polyfill execution branches and eliminate unused polyfills in production builds.
|
|
14
|
+
- Validate lazy-load wrapper targets at build time to prevent dynamic resolution failures.
|
|
15
|
+
- Detect unsupported browser APIs used in application code without corresponding `supports` check.
|
|
16
|
+
|
|
17
|
+
Naming rules for platform support:
|
|
18
|
+
|
|
19
|
+
- Prefer one-word files: `supports.js`, `guard.js`, `index.js`.
|
|
20
|
+
- Plural folders: `tests/`, `types/`, `polyfills/`.
|
|
21
|
+
- Single-word methods: `yield` (as `yieldTask`), `urlPattern`, `navigation`, `popover`, `shadow`, `anchor`, `sanitizer`, `scheduler`.
|
|
22
|
+
- Avoid compound names where single words carry full meaning in context.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. Critical Runtime and Type Gaps
|
|
27
|
+
|
|
28
|
+
### 1.1. Missing Type Declarations for Guards and Cache Reset
|
|
29
|
+
|
|
30
|
+
- **Status**: Type mismatch.
|
|
31
|
+
- **Files**:
|
|
32
|
+
- `types/core/platform/supports.d.ts`
|
|
33
|
+
- **Problem**:
|
|
34
|
+
The TypeScript definitions are severely limited. They only declare the `supports` object with a small fraction (8 out of 30+) of the actual lazy feature-detection flags. Furthermore, `guard` and `reset` are not typed or exported in the declaration file, despite being public exports in `src/core/platform/index.js`.
|
|
35
|
+
- **Expected Support**:
|
|
36
|
+
- Complete declarations for all 30+ feature detection keys on `supports`.
|
|
37
|
+
- Type definitions for `reset(key: string): void`.
|
|
38
|
+
- Type definitions for the `guard` object, including all feature-gate methods:
|
|
39
|
+
- `urlPattern(): Promise<typeof URLPattern>`
|
|
40
|
+
- `navigation(): Promise<Navigation>`
|
|
41
|
+
- `popover(): Promise<void>`
|
|
42
|
+
- `shadow(root?: ParentNode): Promise<void>`
|
|
43
|
+
- `anchor(floating: HTMLElement, anchorEl: HTMLElement, options?: object): Promise<void>`
|
|
44
|
+
- `sanitizer(): Promise<{ sanitizeToString(input: string): string }>`
|
|
45
|
+
- `scheduler(): Promise<Scheduler>`
|
|
46
|
+
- `yield(): Promise<void>`
|
|
47
|
+
|
|
48
|
+
### 1.2. Missing Critical Storage Feature Flags
|
|
49
|
+
|
|
50
|
+
- **Status**: Runtime gap.
|
|
51
|
+
- **Files**:
|
|
52
|
+
- `src/core/platform/supports.js`
|
|
53
|
+
- **Problem**:
|
|
54
|
+
The `supports` detection registry is missing explicit flags for features utilized by the `storage` gateway, specifically:
|
|
55
|
+
- `compression`: Checks for `CompressionStream` and `DecompressionStream` (required for transparent record compression in `core/storage`).
|
|
56
|
+
- `storagePersistence`: Checks for `navigator.storage.persist` and `navigator.storage.persisted` (required for durable storage requests).
|
|
57
|
+
- **Expected Support**:
|
|
58
|
+
- Implement lazy `compression` detection checking `CompressionStream` and `DecompressionStream` in `globalThis`.
|
|
59
|
+
- Implement lazy `storagePersistence` detection checking `navigator.storage` properties.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. Storage Integration & Synchronization Gaps
|
|
64
|
+
|
|
65
|
+
### 2.1. Direct Browser API Access in Storage Gateway
|
|
66
|
+
|
|
67
|
+
- **Status**: Code smell & consistency gap.
|
|
68
|
+
- **Files**:
|
|
69
|
+
- `src/core/storage/index.js`
|
|
70
|
+
- `src/core/storage/quota.js`
|
|
71
|
+
- `src/core/storage/opfs.js`
|
|
72
|
+
- **Problem**:
|
|
73
|
+
The `storage` gateway accesses raw APIs like `CompressionStream`, `localStorage`, and `navigator.storage` directly without consulting the centralized `platform/supports` registry. This creates duplication and makes it impossible to mock feature support in unit tests via `reset()`.
|
|
74
|
+
- **Expected Support**:
|
|
75
|
+
- Refactor `src/core/storage/index.js` to import `supports` and use `supports.compression` and `supports.storagePersistence`.
|
|
76
|
+
- Refactor `src/core/storage/quota.js` to rely on `supports.storageManager` and `supports.storagePersistence` instead of doing in-line existence checks.
|
|
77
|
+
- Refactor `src/core/storage/opfs.js` to verify `supports.opfs` on the main thread prior to worker instantiation, preventing redundant worker start failure.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Test Coverage Gaps
|
|
82
|
+
|
|
83
|
+
### 3.1. Unified Support Matrix & Reset Verification
|
|
84
|
+
|
|
85
|
+
- **Status**: Test gap.
|
|
86
|
+
- **Files**:
|
|
87
|
+
- `tests/core/platform/supports.test.js`
|
|
88
|
+
- **Needed Coverage**:
|
|
89
|
+
- Add assertions for new lazy flags: `compression` and `storagePersistence`.
|
|
90
|
+
- Verify that mock overrides via `reset()` correctly clear the lazy evaluation cache, permitting feature-detection simulation.
|
|
91
|
+
|
|
92
|
+
### 3.2. Polyfill Dynamic Loading Fallback Verification
|
|
93
|
+
|
|
94
|
+
- **Status**: Test gap.
|
|
95
|
+
- **Files**:
|
|
96
|
+
- `tests/core/platform/guard.test.js`
|
|
97
|
+
- **Needed Coverage**:
|
|
98
|
+
- Mock supports to `false` for URLPattern, Navigation, and Scheduler.
|
|
99
|
+
- Call the guards and verify they successfully load the polyfills dynamically and expose functional fallback behaviors.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 4. Suggested Implementation Order
|
|
104
|
+
|
|
105
|
+
1. Implement `compression` and `storagePersistence` lazy detectors in `src/core/platform/supports.js`.
|
|
106
|
+
2. Clean up direct global checks in `src/core/storage/index.js`, `src/core/storage/quota.js`, and `src/core/storage/opfs.js` to use `supports`.
|
|
107
|
+
3. Update `types/core/platform/supports.d.ts` to fully type all `supports`, `guard`, and `reset` methods.
|
|
108
|
+
4. Add verification tests in `tests/core/platform/supports.test.js` and `tests/core/platform/guard.test.js`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 5. Done Criteria
|
|
113
|
+
|
|
114
|
+
This missing-support list is complete when:
|
|
115
|
+
|
|
116
|
+
- `supports` exposes `compression` and `storagePersistence` capability flags.
|
|
117
|
+
- `core/storage` uses `supports` instead of direct global feature verification.
|
|
118
|
+
- `types/core/platform/supports.d.ts` is fully populated.
|
|
119
|
+
- Dynamic polyfill fallbacks are tested and pass successfully.
|