@adukiorg/anza 0.2.0 → 0.2.2
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 +81 -4
- package/README.md +97 -133
- package/bin/anza/anza +0 -0
- package/bin/anza/anza.exe +0 -0
- package/bin/anza/find.js +35 -0
- package/bin/anza/index.js +34 -0
- package/bin/anza/launch.js +19 -0
- package/bin/common/index.js +7 -0
- package/bin/common/logs.js +62 -0
- package/bin/create/copy.js +18 -0
- package/bin/create/index.js +45 -0
- package/bin/create/run.js +210 -0
- package/bin/create/write.js +19 -0
- package/importmap.json +4 -0
- package/package.json +16 -10
- package/src/core/offline/{usage.md → notes/usage.md} +11 -1
- package/src/core/router/boot.js +82 -0
- package/src/core/router/cascade.js +76 -0
- package/src/core/router/container.js +63 -72
- package/src/core/router/graph.js +144 -0
- package/src/core/router/index.js +12 -2
- package/src/core/router/intercept.js +26 -7
- package/src/core/router/lca.js +58 -0
- package/src/core/router/match.js +49 -36
- package/src/core/router/notes/audit-old.md +887 -0
- package/src/core/router/notes/audti.md +773 -0
- package/src/core/router/notes/tasks.md +473 -0
- package/src/core/router/{usage.md → notes/usage.md} +57 -35
- package/src/core/router/sync/tab.js +6 -4
- package/src/core/router/transitions.js +35 -8
- package/src/core/router/trie.js +130 -0
- package/src/core/security/{usage.md → notes/usage.md} +1 -2
- package/src/core/storage/{usage.md → notes/usage.md} +6 -6
- package/src/core/theme/index.js +78 -0
- package/src/core/ui/define/index.js +2 -1
- package/src/core/ui/define/orchestrator.js +10 -4
- package/src/core/ui/defs/dock.js +134 -0
- package/src/core/ui/defs/index.js +20 -0
- package/src/core/ui/defs/page.js +89 -0
- package/src/core/ui/defs/part.js +28 -0
- package/src/core/ui/defs/spec.js +96 -0
- package/src/core/ui/defs/view.js +23 -0
- package/src/core/ui/index.js +16 -3
- package/src/core/ui/notes/definations.md +979 -0
- package/src/tokens/index.css +1 -0
- package/src/tokens/semantic/contrast.css +18 -0
- package/src/tokens/semantic/transitions.css +32 -0
- package/types/core/platform/index.d.ts +39 -10
- package/types/core/router/index.d.ts +9 -0
- package/types/core/theme/index.d.ts +18 -0
- package/types/core/ui/index.d.ts +11 -0
- package/types/index.d.ts +1 -0
- package/bin/anza.js +0 -63
- package/bin/create.js +0 -150
- package/src/core/api/plan.md +0 -209
- package/src/core/events/missing.md +0 -103
- package/src/core/events/plan.md +0 -177
- package/src/core/offline/missing.md +0 -89
- package/src/core/offline/plan.md +0 -143
- package/src/core/platform/missing.md +0 -119
- package/src/core/platform/platform.d.ts +0 -88
- package/src/core/router/missing.md +0 -716
- package/src/core/router/outlet.js +0 -139
- package/src/core/router/plan.md +0 -370
- package/src/core/security/missing.md +0 -97
- package/src/core/state/missing.md +0 -165
- package/src/core/storage/missing.md +0 -165
- package/src/core/storage/plan.md +0 -69
- package/src/core/ui/implementation.md +0 -170
- package/src/core/ui/plan.md +0 -510
- package/src/core/ui/ui.types.md +0 -890
- /package/src/core/animations/{usage.md → notes/usage.md} +0 -0
- /package/src/core/api/{usage.md → notes/usage.md} +0 -0
- /package/src/core/events/{usage.md → notes/usage.md} +0 -0
- /package/src/core/platform/{usage.md → notes/usage.md} +0 -0
- /package/src/core/state/{usage.md → notes/usage.md} +0 -0
- /package/src/core/ui/{usage.md → notes/usage.md} +0 -0
- /package/src/core/ui/{watch.md → notes/watch.md} +0 -0
- /package/src/core/workers/{plan.md → notes/plan.md} +0 -0
- /package/src/core/workers/{usage.md → notes/usage.md} +0 -0
package/src/tokens/index.css
CHANGED
|
@@ -30,4 +30,22 @@
|
|
|
30
30
|
--color-border-default: var(--color-neutral-1000);
|
|
31
31
|
--color-border-strong: var(--color-neutral-1000);
|
|
32
32
|
--color-border-focus: var(--color-brand-900);
|
|
33
|
+
|
|
34
|
+
/* Automatic theme morph — same timing as light theme */
|
|
35
|
+
transition:
|
|
36
|
+
--color-surface-page 220ms var(--ease-out),
|
|
37
|
+
--color-surface-card 220ms var(--ease-out),
|
|
38
|
+
--color-surface-elevated 220ms var(--ease-out),
|
|
39
|
+
--color-surface-inverse 220ms var(--ease-out),
|
|
40
|
+
--color-content-primary 180ms var(--ease-out),
|
|
41
|
+
--color-content-secondary 180ms var(--ease-out),
|
|
42
|
+
--color-content-disabled 180ms var(--ease-out),
|
|
43
|
+
--color-content-inverse 180ms var(--ease-out),
|
|
44
|
+
--color-content-link 180ms var(--ease-out),
|
|
45
|
+
--color-interactive 180ms var(--ease-out),
|
|
46
|
+
--color-interactive-hover 180ms var(--ease-out),
|
|
47
|
+
--color-interactive-active 180ms var(--ease-out),
|
|
48
|
+
--color-border-default 180ms var(--ease-out),
|
|
49
|
+
--color-border-strong 180ms var(--ease-out),
|
|
50
|
+
--color-border-focus 180ms var(--ease-out);
|
|
33
51
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tokens/semantic/transitions.css
|
|
3
|
+
*
|
|
4
|
+
* View Transition pseudo-element theming.
|
|
5
|
+
* Connects the browser's CSS View Transitions API to the semantic token system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
:root {
|
|
9
|
+
/* Backdrop behind old and new page snapshots during a transition */
|
|
10
|
+
--transition-bg: var(--color-surface-page);
|
|
11
|
+
|
|
12
|
+
/* Timing tokens used by JS when calling startViewTransition */
|
|
13
|
+
--transition-duration: var(--duration-normal);
|
|
14
|
+
--transition-easing: var(--ease-out);
|
|
15
|
+
|
|
16
|
+
/* Directional modifiers — pushed pages feel different from popped pages */
|
|
17
|
+
--transition-push: var(--ease-out);
|
|
18
|
+
--transition-pop: var(--ease-in);
|
|
19
|
+
--transition-replace: var(--ease-in-out);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
::view-transition-old(root),
|
|
23
|
+
::view-transition-new(root) {
|
|
24
|
+
background: var(--transition-bg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-reduced-motion: reduce) {
|
|
28
|
+
:root {
|
|
29
|
+
--transition-duration: 0ms;
|
|
30
|
+
--transition-easing: linear;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -4,57 +4,86 @@
|
|
|
4
4
|
* TypeScript declarations for browser-native feature detection flags and platform guards.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export interface Supports {
|
|
8
|
+
// --- Routing ---
|
|
8
9
|
readonly navigationAPI: boolean;
|
|
9
10
|
readonly urlPattern: boolean;
|
|
11
|
+
|
|
12
|
+
// --- Component Model ---
|
|
10
13
|
readonly declarativeShadowDOM: boolean;
|
|
11
14
|
readonly customStatePseudo: boolean;
|
|
12
15
|
readonly formAssociated: boolean;
|
|
16
|
+
|
|
17
|
+
// --- Overlay / Popover ---
|
|
13
18
|
readonly popoverAPI: boolean;
|
|
14
19
|
readonly anchorPositioning: boolean;
|
|
20
|
+
|
|
21
|
+
// --- Animation ---
|
|
15
22
|
readonly viewTransitions: boolean;
|
|
16
23
|
readonly scrollTimeline: boolean;
|
|
17
24
|
readonly viewTimeline: boolean;
|
|
25
|
+
|
|
26
|
+
// --- Scheduling ---
|
|
18
27
|
readonly schedulerPostTask: boolean;
|
|
19
28
|
readonly schedulerYield: boolean;
|
|
29
|
+
|
|
30
|
+
// --- CSS ---
|
|
20
31
|
readonly contentVisibility: boolean;
|
|
21
32
|
readonly cssScope: boolean;
|
|
22
33
|
readonly cssLayer: boolean;
|
|
23
34
|
readonly cssModuleScripts: boolean;
|
|
35
|
+
|
|
36
|
+
// --- Module System ---
|
|
24
37
|
readonly importMaps: boolean;
|
|
38
|
+
|
|
39
|
+
// --- Security ---
|
|
25
40
|
readonly sanitizerAPI: boolean;
|
|
26
41
|
readonly trustedTypes: boolean;
|
|
27
42
|
readonly subtleCrypto: boolean;
|
|
43
|
+
|
|
44
|
+
// --- Storage ---
|
|
28
45
|
readonly opfs: boolean;
|
|
29
46
|
readonly storageManager: boolean;
|
|
30
47
|
readonly fileSystemPickers: boolean;
|
|
31
48
|
readonly compression: boolean;
|
|
32
49
|
readonly storagePersistence: boolean;
|
|
50
|
+
|
|
51
|
+
// --- Networking / Workers ---
|
|
33
52
|
readonly backgroundSync: boolean;
|
|
34
53
|
readonly speculationRules: boolean;
|
|
35
54
|
readonly sharedWorker: boolean;
|
|
36
55
|
readonly webLocks: boolean;
|
|
37
56
|
readonly offscreenCanvas: boolean;
|
|
57
|
+
|
|
58
|
+
// --- Notifications / Push ---
|
|
38
59
|
readonly pushAPI: boolean;
|
|
39
60
|
readonly notificationsAPI: boolean;
|
|
61
|
+
|
|
62
|
+
// --- Device ---
|
|
40
63
|
readonly screenWakeLock: boolean;
|
|
41
64
|
readonly idleDetection: boolean;
|
|
42
65
|
readonly webAuthn: boolean;
|
|
43
|
-
}
|
|
66
|
+
}
|
|
44
67
|
|
|
45
|
-
export
|
|
68
|
+
export const supports: Supports;
|
|
69
|
+
|
|
70
|
+
export function reset(key: keyof Supports): void;
|
|
71
|
+
|
|
72
|
+
export function typeGuard(key: keyof Supports, message?: string): void;
|
|
46
73
|
|
|
47
74
|
export interface SanitizerWrapper {
|
|
48
75
|
sanitizeToString(input: string): string;
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
export
|
|
52
|
-
urlPattern(): Promise<
|
|
53
|
-
navigation(): Promise<
|
|
78
|
+
export interface Guard {
|
|
79
|
+
urlPattern(): Promise<typeof URLPattern>;
|
|
80
|
+
navigation(): Promise<Navigation>;
|
|
54
81
|
popover(): Promise<void>;
|
|
55
|
-
shadow(root?:
|
|
56
|
-
anchor(floating: HTMLElement, anchorEl: HTMLElement, options?:
|
|
82
|
+
shadow(root?: Document | ShadowRoot): Promise<void>;
|
|
83
|
+
anchor(floating: HTMLElement, anchorEl: HTMLElement, options?: object): Promise<void>;
|
|
57
84
|
sanitizer(): Promise<SanitizerWrapper>;
|
|
58
|
-
scheduler(): Promise<
|
|
85
|
+
scheduler(): Promise<Scheduler>;
|
|
59
86
|
yield(): Promise<void>;
|
|
60
|
-
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const guard: Guard;
|
|
@@ -192,6 +192,15 @@ export function unregisterContainer(name: string): void;
|
|
|
192
192
|
export function getContainer(name: string): Element | null;
|
|
193
193
|
export function clearContainers(): void;
|
|
194
194
|
|
|
195
|
+
export interface TransitionsApi {
|
|
196
|
+
run(
|
|
197
|
+
updateDOM: () => void | Promise<void>,
|
|
198
|
+
options?: { sourceElement?: HTMLElement; name?: string }
|
|
199
|
+
): Promise<void>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const transitions: TransitionsApi;
|
|
203
|
+
|
|
195
204
|
export class RouteOutlet extends HTMLElement {
|
|
196
205
|
update(detail: { chain: ChainSegment[]; query: Record<string, string>; hash: string }): Promise<void>;
|
|
197
206
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types/core/theme/index.d.ts
|
|
3
|
+
*
|
|
4
|
+
* TypeScript declarations for the anza theme API.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ThemeApi {
|
|
8
|
+
/** Return the active theme name: light, dark, contrast, or auto. */
|
|
9
|
+
get(): string;
|
|
10
|
+
|
|
11
|
+
/** Apply a theme name and persist it. */
|
|
12
|
+
set(name: string): void;
|
|
13
|
+
|
|
14
|
+
/** Toggle between light and dark. */
|
|
15
|
+
toggle(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const theme: ThemeApi;
|
package/types/core/ui/index.d.ts
CHANGED
|
@@ -420,6 +420,16 @@ export function scheduleFrame<T>(fn: () => T): Promise<T>;
|
|
|
420
420
|
|
|
421
421
|
export function yieldTask(): Promise<void>;
|
|
422
422
|
|
|
423
|
+
export function dock(
|
|
424
|
+
name: string,
|
|
425
|
+
config?: {
|
|
426
|
+
tag?: string;
|
|
427
|
+
parent?: string;
|
|
428
|
+
template?: { html?: string; css?: string; shadow?: boolean };
|
|
429
|
+
},
|
|
430
|
+
base?: string | URL
|
|
431
|
+
): void;
|
|
432
|
+
|
|
423
433
|
export function transition<T>(
|
|
424
434
|
fn: () => T
|
|
425
435
|
): Promise<ViewTransitionLike<T>>;
|
|
@@ -435,6 +445,7 @@ export interface UiApi {
|
|
|
435
445
|
define: typeof define;
|
|
436
446
|
element: typeof element;
|
|
437
447
|
container: typeof container;
|
|
448
|
+
dock: typeof dock;
|
|
438
449
|
schedule: typeof schedule;
|
|
439
450
|
scheduleFrame: typeof scheduleFrame;
|
|
440
451
|
yield: typeof yieldTask;
|
package/types/index.d.ts
CHANGED
|
@@ -15,4 +15,5 @@ export * as ui from './core/ui/index.d.ts';
|
|
|
15
15
|
export * as security from './core/security/index.d.ts';
|
|
16
16
|
export * as offline from './core/offline/index.d.ts';
|
|
17
17
|
export * as animations from './core/animations/index.d.ts';
|
|
18
|
+
export * as theme from './core/theme/index.d.ts';
|
|
18
19
|
export * as elements from './elements/index.d.ts';
|
package/bin/anza.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* library/bin/anza.js
|
|
5
|
-
*
|
|
6
|
-
* Node.js wrapper for the anza Rust binary.
|
|
7
|
-
* Detects the platform and spawns the correct pre-built binary.
|
|
8
|
-
*
|
|
9
|
-
* In development: uses tools/target/release/anza (two levels up).
|
|
10
|
-
* In npm install: uses pre-built platform binary from bin/.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import { dirname, join } from 'path';
|
|
15
|
-
import { existsSync } from 'fs';
|
|
16
|
-
import { spawn } from 'child_process';
|
|
17
|
-
|
|
18
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
-
const __dirname = dirname(__filename);
|
|
20
|
-
|
|
21
|
-
// library/bin/ → library/ → repo root
|
|
22
|
-
const library = join(__dirname, '..');
|
|
23
|
-
const root = join(library, '..');
|
|
24
|
-
|
|
25
|
-
function binary() {
|
|
26
|
-
// Development: repo binary (built by `node scripts/build.js`)
|
|
27
|
-
const dev = join(root, 'tools', 'target', 'release', 'anza');
|
|
28
|
-
if (existsSync(dev)) return dev;
|
|
29
|
-
|
|
30
|
-
// npm install: pre-built platform binary alongside this script
|
|
31
|
-
const platform = process.platform;
|
|
32
|
-
const arch = process.arch;
|
|
33
|
-
|
|
34
|
-
const map = {
|
|
35
|
-
darwin: { x64: 'anza-macos-x64', arm64: 'anza-macos-arm64' },
|
|
36
|
-
linux: { x64: 'anza-linux-x64', arm64: 'anza-linux-arm64' },
|
|
37
|
-
win32: { x64: 'anza-windows-x64.exe', arm64: 'anza-windows-arm64.exe' },
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const name = map[platform]?.[arch];
|
|
41
|
-
if (!name) {
|
|
42
|
-
console.error(`Unsupported platform: ${platform} ${arch}`);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const prebuilt = join(__dirname, name);
|
|
47
|
-
if (existsSync(prebuilt)) return prebuilt;
|
|
48
|
-
|
|
49
|
-
console.error(
|
|
50
|
-
'anza binary not found.\n' +
|
|
51
|
-
'Run: node scripts/build.js (from the repo root)'
|
|
52
|
-
);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const child = spawn(binary(), process.argv.slice(2), { stdio: 'inherit' });
|
|
57
|
-
|
|
58
|
-
child.on('error', (err) => {
|
|
59
|
-
console.error('Failed to spawn anza:', err.message);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
child.on('exit', (code) => process.exit(code ?? 0));
|
package/bin/create.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* scripts/create.js (also used as library/bin/create.js → npx anza-create)
|
|
4
|
-
*
|
|
5
|
-
* Scaffolds a new anza app project in the given directory.
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx anza-create myapp
|
|
8
|
-
* npx anza-create . (current directory)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
12
|
-
import { join, resolve, basename } from 'path';
|
|
13
|
-
|
|
14
|
-
const arg = process.argv[2];
|
|
15
|
-
|
|
16
|
-
if (!arg || arg === '--help' || arg === '-h') {
|
|
17
|
-
console.log('Usage: npx anza-create <name>');
|
|
18
|
-
console.log(' npx anza-create . (scaffold in current dir)');
|
|
19
|
-
process.exit(arg ? 0 : 1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const target = resolve(arg);
|
|
23
|
-
const name = arg === '.' ? basename(process.cwd()) : basename(target);
|
|
24
|
-
|
|
25
|
-
if (existsSync(target) && arg !== '.') {
|
|
26
|
-
console.error(`Error: '${target}' already exists.`);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
console.log(`Scaffolding anza app: ${name}\n`);
|
|
31
|
-
|
|
32
|
-
// Directory structure
|
|
33
|
-
const dirs = [
|
|
34
|
-
'',
|
|
35
|
-
'src',
|
|
36
|
-
'src/elements',
|
|
37
|
-
'src/styles',
|
|
38
|
-
'src/tokens',
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
for (const dir of dirs) {
|
|
42
|
-
mkdirSync(join(target, dir), { recursive: true });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/index.html
|
|
46
|
-
writeFileSync(join(target, 'src', 'index.html'), `<!DOCTYPE html>
|
|
47
|
-
<html lang="en">
|
|
48
|
-
<head>
|
|
49
|
-
<meta charset="utf-8" />
|
|
50
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
51
|
-
<title>${name}</title>
|
|
52
|
-
|
|
53
|
-
<!-- Tokens & global styles -->
|
|
54
|
-
<link rel="stylesheet" href="/dist/tokens/index.css" />
|
|
55
|
-
<link rel="stylesheet" href="/dist/styles/global.css" />
|
|
56
|
-
|
|
57
|
-
<!-- App entry -->
|
|
58
|
-
<script type="module" src="/dist/index.js"></script>
|
|
59
|
-
</head>
|
|
60
|
-
<body>
|
|
61
|
-
<!-- App root -->
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
64
|
-
`);
|
|
65
|
-
|
|
66
|
-
// src/index.js
|
|
67
|
-
writeFileSync(join(target, 'src', 'index.js'), `/**
|
|
68
|
-
* src/index.js — app entry point
|
|
69
|
-
*/
|
|
70
|
-
import '@adukiorg/anza/ui';
|
|
71
|
-
|
|
72
|
-
// Import your elements
|
|
73
|
-
// import './elements/dashboard/index.js';
|
|
74
|
-
`);
|
|
75
|
-
|
|
76
|
-
// src/styles/global.css
|
|
77
|
-
writeFileSync(join(target, 'src', 'styles', 'global.css'), `/* global.css — global app styles */
|
|
78
|
-
|
|
79
|
-
*,
|
|
80
|
-
*::before,
|
|
81
|
-
*::after {
|
|
82
|
-
box-sizing: border-box;
|
|
83
|
-
margin: 0;
|
|
84
|
-
padding: 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
body {
|
|
88
|
-
font-family: system-ui, sans-serif;
|
|
89
|
-
line-height: 1.5;
|
|
90
|
-
}
|
|
91
|
-
`);
|
|
92
|
-
|
|
93
|
-
// src/tokens/index.css
|
|
94
|
-
writeFileSync(join(target, 'src', 'tokens', 'index.css'), `/* tokens/index.css — design tokens */
|
|
95
|
-
|
|
96
|
-
:root {
|
|
97
|
-
/* Colors */
|
|
98
|
-
--color-primary: #6366f1;
|
|
99
|
-
--color-surface: #ffffff;
|
|
100
|
-
--color-text: #111827;
|
|
101
|
-
|
|
102
|
-
/* Spacing */
|
|
103
|
-
--space-1: 0.25rem;
|
|
104
|
-
--space-2: 0.5rem;
|
|
105
|
-
--space-4: 1rem;
|
|
106
|
-
--space-8: 2rem;
|
|
107
|
-
|
|
108
|
-
/* Radius */
|
|
109
|
-
--radius-sm: 0.25rem;
|
|
110
|
-
--radius-md: 0.5rem;
|
|
111
|
-
--radius-lg: 1rem;
|
|
112
|
-
|
|
113
|
-
/* Shadow */
|
|
114
|
-
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
|
|
115
|
-
--shadow-md: 0 4px 6px rgb(0 0 0 / 0.07);
|
|
116
|
-
}
|
|
117
|
-
`);
|
|
118
|
-
|
|
119
|
-
// package.json
|
|
120
|
-
writeFileSync(join(target, 'package.json'), JSON.stringify({
|
|
121
|
-
name,
|
|
122
|
-
version: '0.1.0',
|
|
123
|
-
private: true,
|
|
124
|
-
type: 'module',
|
|
125
|
-
scripts: {
|
|
126
|
-
dev: 'anza dev',
|
|
127
|
-
build: 'anza build',
|
|
128
|
-
},
|
|
129
|
-
devDependencies: {
|
|
130
|
-
'@adukiorg/anza': 'latest',
|
|
131
|
-
}
|
|
132
|
-
}, null, 2));
|
|
133
|
-
|
|
134
|
-
// .gitignore
|
|
135
|
-
writeFileSync(join(target, '.gitignore'), `node_modules/
|
|
136
|
-
dist/
|
|
137
|
-
.anza-build-cache.json
|
|
138
|
-
`);
|
|
139
|
-
|
|
140
|
-
console.log('Created:');
|
|
141
|
-
for (const dir of dirs.filter(Boolean)) {
|
|
142
|
-
console.log(` ${name}/${dir}/`);
|
|
143
|
-
}
|
|
144
|
-
console.log(` ${name}/src/index.html`);
|
|
145
|
-
console.log(` ${name}/src/index.js`);
|
|
146
|
-
console.log(` ${name}/package.json`);
|
|
147
|
-
console.log(`\nNext steps:`);
|
|
148
|
-
console.log(` cd ${arg === '.' ? '.' : name}`);
|
|
149
|
-
console.log(` npm install`);
|
|
150
|
-
console.log(` npm run dev`);
|
package/src/core/api/plan.md
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# API Client & Cache Enhancement Plan
|
|
2
|
-
|
|
3
|
-
This document outlines the blueprint for enhancing the `core.api` networking client with localized, fine-grained caching, prefix registration, namespace-level invalidation, and custom network/status-code events.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Architectural Architecture & Requirements
|
|
8
|
-
|
|
9
|
-
We will extend `core.api` to provide:
|
|
10
|
-
|
|
11
|
-
1. **Default Zero-Cache Policy:** All calls default to no cache, running directly against the network.
|
|
12
|
-
2. **TTL/Expiry-based API Caching:** Active caching when `expiry` or `ttl` (Time-To-Live) options are provided. Hits cache first, falls back to the network on a miss, and caches successful responses.
|
|
13
|
-
3. **Namespace-level Glob Invalidation:** Support for clearing the entire cache, a single URL, or a glob namespace pattern (e.g. `/user/*`).
|
|
14
|
-
4. **Outbound Prefix Resolution:** Initializing base prefixes once (optionally) to cleanly rewrite and resolve endpoint URLs.
|
|
15
|
-
5. **Network Event Hub:** An integrated event listener system firing on generic network failures (errors, timeouts) or specific responses (status codes like `401`, `500`, or content types like `json`, `text`).
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 2. Structural & Folder Layout
|
|
20
|
-
|
|
21
|
-
In accordance with our naming and folder conventions (`RULE[user_global]`), we will group caching and events into highly structured subfolders under `src/core/api/`:
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
src/core/api/
|
|
25
|
-
├── caches/ # Subfolder for caching adapters & glob matching
|
|
26
|
-
│ ├── glob.js # Glob/Namespace pattern matching helper
|
|
27
|
-
│ └── index.js # Unified local API cache client
|
|
28
|
-
├── events/ # Subfolder for network telemetry events
|
|
29
|
-
│ └── index.js # API event emitter implementation
|
|
30
|
-
├── prefixes/ # Subfolder for prefix/base URL resolving
|
|
31
|
-
│ └── index.js # Prefix store and path normalization
|
|
32
|
-
├── index.js # Core API client entry point
|
|
33
|
-
├── fetch.js # Network request executor
|
|
34
|
-
├── pipeline.js # Inbound/Outbound pipeline coordinator
|
|
35
|
-
├── retry.js # Exponential backoff retry handler
|
|
36
|
-
├── stream.js # Streams and NDJSON transform pipeline
|
|
37
|
-
└── upload.js # XMLHttpRequest-based upload gateway
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 3. Technical Blueprint & Interface Design
|
|
43
|
-
|
|
44
|
-
### Caches & Glob Purging (`src/core/api/caches/`)
|
|
45
|
-
|
|
46
|
-
The API cache uses the native browser Cache API with custom header tags to track record creation times and TTL.
|
|
47
|
-
|
|
48
|
-
```javascript
|
|
49
|
-
// src/core/api/caches/index.js
|
|
50
|
-
export class ApiCache {
|
|
51
|
-
constructor(name = 'platform-api-cache') {
|
|
52
|
-
this.name = name;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async get(url) {
|
|
56
|
-
if (typeof caches === 'undefined') return null;
|
|
57
|
-
const store = await caches.open(this.name);
|
|
58
|
-
const cached = await store.match(url);
|
|
59
|
-
if (!cached) return null;
|
|
60
|
-
|
|
61
|
-
const expiresAt = cached.headers.get('x-expires-at');
|
|
62
|
-
if (expiresAt && Date.now() > Number(expiresAt)) {
|
|
63
|
-
await store.delete(url);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
return cached.clone();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async set(url, response, ttlMs) {
|
|
70
|
-
if (typeof caches === 'undefined') return;
|
|
71
|
-
const store = await caches.open(this.name);
|
|
72
|
-
const headers = new Headers(response.headers);
|
|
73
|
-
headers.set('x-expires-at', String(Date.now() + ttlMs));
|
|
74
|
-
|
|
75
|
-
const cloned = new Response(response.body ? response.clone().body : null, {
|
|
76
|
-
status: response.status,
|
|
77
|
-
statusText: response.statusText,
|
|
78
|
-
headers
|
|
79
|
-
});
|
|
80
|
-
await store.put(url, cloned);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async delete(pattern) {
|
|
84
|
-
if (typeof caches === 'undefined') return;
|
|
85
|
-
const store = await caches.open(this.name);
|
|
86
|
-
|
|
87
|
-
if (pattern.includes('*')) {
|
|
88
|
-
const regex = globToRegex(pattern);
|
|
89
|
-
const keys = await store.keys();
|
|
90
|
-
for (const req of keys) {
|
|
91
|
-
if (regex.test(req.url) || regex.test(new URL(req.url).pathname)) {
|
|
92
|
-
await store.delete(req);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
await store.delete(pattern);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async clear() {
|
|
101
|
-
if (typeof caches === 'undefined') return;
|
|
102
|
-
await caches.delete(this.name);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Glob to Regex conversion helper:
|
|
108
|
-
|
|
109
|
-
```javascript
|
|
110
|
-
// src/core/api/caches/glob.js
|
|
111
|
-
export function globToRegex(pattern) {
|
|
112
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
113
|
-
const wildcarded = escaped.replace(/\*/g, '.*');
|
|
114
|
-
return new RegExp(`^${wildcarded}$`);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Prefix Resolver (`src/core/api/prefixes/`)
|
|
119
|
-
|
|
120
|
-
```javascript
|
|
121
|
-
// src/core/api/prefixes/index.js
|
|
122
|
-
export class PrefixRegistry {
|
|
123
|
-
#prefixes = new Map();
|
|
124
|
-
|
|
125
|
-
add(name, value) {
|
|
126
|
-
this.#prefixes.set(name, value);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
resolve(url) {
|
|
130
|
-
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
131
|
-
return url;
|
|
132
|
-
}
|
|
133
|
-
for (const [prefix, base] of this.#prefixes.entries()) {
|
|
134
|
-
if (url.startsWith(`/${prefix}/`)) {
|
|
135
|
-
return base + url.slice(prefix.length + 1);
|
|
136
|
-
}
|
|
137
|
-
if (url.startsWith(`${prefix}/`)) {
|
|
138
|
-
return base + '/' + url.slice(prefix.length);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// Default fallback to registered root or baseline domain
|
|
142
|
-
const root = this.#prefixes.get('root') || this.#prefixes.get('default');
|
|
143
|
-
if (root) {
|
|
144
|
-
return root + (url.startsWith('/') ? url : '/' + url);
|
|
145
|
-
}
|
|
146
|
-
return url;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Telemetry Events (`src/core/api/events/`)
|
|
152
|
-
|
|
153
|
-
```javascript
|
|
154
|
-
// src/core/api/events/index.js
|
|
155
|
-
export class ApiEventEmitter {
|
|
156
|
-
#listeners = new Map();
|
|
157
|
-
|
|
158
|
-
on(event, handler, signal) {
|
|
159
|
-
if (signal?.aborted) return () => {};
|
|
160
|
-
if (!this.#listeners.has(event)) {
|
|
161
|
-
this.#listeners.set(event, new Set());
|
|
162
|
-
}
|
|
163
|
-
const listener = { handler };
|
|
164
|
-
this.#listeners.get(event).add(listener);
|
|
165
|
-
|
|
166
|
-
const dispose = () => {
|
|
167
|
-
const set = this.#listeners.get(event);
|
|
168
|
-
if (set) {
|
|
169
|
-
set.delete(listener);
|
|
170
|
-
if (set.size === 0) this.#listeners.delete(event);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
if (signal) {
|
|
175
|
-
signal.addEventListener('abort', dispose);
|
|
176
|
-
}
|
|
177
|
-
return dispose;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
emit(event, detail) {
|
|
181
|
-
const set = this.#listeners.get(event);
|
|
182
|
-
if (!set) return;
|
|
183
|
-
const customEvent = { type: event, detail };
|
|
184
|
-
for (const listener of [...set]) {
|
|
185
|
-
try {
|
|
186
|
-
listener.handler(customEvent);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
console.error(`Error in API event listener for "${event}":`, err);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
## 4. Verification Plan
|
|
198
|
-
|
|
199
|
-
### Automated Verification
|
|
200
|
-
|
|
201
|
-
- Write comprehensive test coverage validating:
|
|
202
|
-
- Cache hits on active TTL/Expiry options.
|
|
203
|
-
- Glob namespace clearing matches (e.g. `/user/*` successfully purges `/user/profile` and `/user/settings`).
|
|
204
|
-
- Outbound path prefix expansion.
|
|
205
|
-
- Correct event dispatching for generic `'error'`, `'timeout'`, specific codes (`status:401`), and content types (`type:json`).
|
|
206
|
-
|
|
207
|
-
### Integration Check
|
|
208
|
-
|
|
209
|
-
- Assert network trace outputs and test page lifecycle triggers inside the browser environment.
|