@kbve/droid 0.0.6 → 0.1.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/droid.mjs +2312 -74
- package/index.d.ts +32 -0
- package/lib/agents/api-types.d.ts +121 -0
- package/lib/agents/api-types.d.ts.map +1 -0
- package/lib/agents/auth/init.d.ts +8 -0
- package/lib/agents/auth/init.d.ts.map +1 -0
- package/lib/agents/cache.d.ts +9 -0
- package/lib/agents/cache.d.ts.map +1 -0
- package/lib/agents/client/callGuildVault.d.ts +3 -0
- package/lib/agents/client/callGuildVault.d.ts.map +1 -0
- package/lib/agents/client/callJson.d.ts +3 -0
- package/lib/agents/client/callJson.d.ts.map +1 -0
- package/lib/agents/config.d.ts +36 -0
- package/lib/agents/config.d.ts.map +1 -0
- package/lib/agents/constants.d.ts +11 -0
- package/lib/agents/constants.d.ts.map +1 -0
- package/lib/agents/createAgents.d.ts +4 -0
- package/lib/agents/createAgents.d.ts.map +1 -0
- package/lib/agents/ctx.d.ts +46 -0
- package/lib/agents/ctx.d.ts.map +1 -0
- package/lib/agents/discord/bot.d.ts +21 -0
- package/lib/agents/discord/bot.d.ts.map +1 -0
- package/lib/agents/discord/guilds.d.ts +8 -0
- package/lib/agents/discord/guilds.d.ts.map +1 -0
- package/lib/agents/formDrafts.d.ts +5 -0
- package/lib/agents/formDrafts.d.ts.map +1 -0
- package/lib/agents/generated/agents-schema.d.ts +73 -0
- package/lib/agents/generated/agents-schema.d.ts.map +1 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts +71 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts.map +1 -0
- package/lib/agents/github/backfill.d.ts +23 -0
- package/lib/agents/github/backfill.d.ts.map +1 -0
- package/lib/agents/github/events.d.ts +10 -0
- package/lib/agents/github/events.d.ts.map +1 -0
- package/lib/agents/github/pat.d.ts +13 -0
- package/lib/agents/github/pat.d.ts.map +1 -0
- package/lib/agents/github/repoConfig.d.ts +25 -0
- package/lib/agents/github/repoConfig.d.ts.map +1 -0
- package/lib/agents/github/tokens.d.ts +22 -0
- package/lib/agents/github/tokens.d.ts.map +1 -0
- package/lib/agents/github/webhook.d.ts +37 -0
- package/lib/agents/github/webhook.d.ts.map +1 -0
- package/lib/agents/index.d.ts +8 -0
- package/lib/agents/index.d.ts.map +1 -0
- package/lib/agents/state/atoms.d.ts +122 -0
- package/lib/agents/state/atoms.d.ts.map +1 -0
- package/lib/agents/types.d.ts +65 -0
- package/lib/agents/types.d.ts.map +1 -0
- package/lib/gateway/SupabaseGateway.d.ts +38 -0
- package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
- package/lib/gateway/WorkerCommunication.d.ts +39 -0
- package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
- package/lib/gateway/WorkerPool.d.ts +55 -0
- package/lib/gateway/WorkerPool.d.ts.map +1 -0
- package/lib/gateway/capabilities.d.ts +23 -0
- package/lib/gateway/capabilities.d.ts.map +1 -0
- package/lib/gateway/index.d.ts +9 -0
- package/lib/gateway/index.d.ts.map +1 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/types.d.ts +81 -0
- package/lib/gateway/types.d.ts.map +1 -0
- package/lib/workers/canvas-ui-renderer.d.ts +25 -0
- package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
- package/lib/workers/canvas-worker.d.ts +14 -0
- package/lib/workers/canvas-worker.d.ts.map +1 -0
- package/lib/workers/canvas-worker.js +5 -302
- package/lib/workers/data.d.ts +40 -0
- package/lib/workers/data.d.ts.map +1 -0
- package/lib/workers/db-worker.d.ts +69 -0
- package/lib/workers/db-worker.d.ts.map +1 -0
- package/lib/workers/db-worker.js +13 -3636
- package/lib/workers/events.d.ts +11 -0
- package/lib/workers/events.d.ts.map +1 -0
- package/lib/workers/flexbuilder.d.ts +4 -0
- package/lib/workers/flexbuilder.d.ts.map +1 -0
- package/lib/workers/init.d.ts +11 -0
- package/lib/workers/init.d.ts.map +1 -0
- package/lib/workers/main.d.ts +105 -0
- package/lib/workers/main.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.d.ts +2 -0
- package/lib/workers/supabase-db-worker.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.js +23 -0
- package/lib/workers/supabase-shared-worker.d.ts +2 -0
- package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
- package/lib/workers/supabase-shared-worker.js +25 -0
- package/lib/workers/tools.d.ts +4 -0
- package/lib/workers/tools.d.ts.map +1 -0
- package/lib/workers/validate.d.ts +15 -0
- package/lib/workers/validate.d.ts.map +1 -0
- package/lib/workers/ws-worker.d.ts +10 -0
- package/lib/workers/ws-worker.d.ts.map +1 -0
- package/lib/workers/ws-worker.js +5 -273
- package/main.js +5711 -0
- package/package.json +18 -6
- package/src/index.ts +41 -13
- package/src/lib/agents/api-types.ts +170 -0
- package/src/lib/agents/auth/init.ts +200 -0
- package/src/lib/agents/cache.ts +127 -0
- package/src/lib/agents/client/callGuildVault.ts +71 -0
- package/src/lib/agents/client/callJson.ts +64 -0
- package/src/lib/agents/config.ts +83 -0
- package/src/lib/agents/constants.ts +11 -0
- package/src/lib/agents/createAgents.ts +62 -0
- package/src/lib/agents/ctx.ts +63 -0
- package/src/lib/agents/discord/bot.ts +169 -0
- package/src/lib/agents/discord/guilds.ts +106 -0
- package/src/lib/agents/formDrafts.ts +55 -0
- package/src/lib/agents/generated/agents-schema.ts +155 -0
- package/src/lib/agents/generated/discordsh-agents-schema.ts +96 -0
- package/src/lib/agents/github/backfill.ts +126 -0
- package/src/lib/agents/github/events.ts +131 -0
- package/src/lib/agents/github/pat.ts +150 -0
- package/src/lib/agents/github/repoConfig.ts +240 -0
- package/src/lib/agents/github/tokens.ts +222 -0
- package/src/lib/agents/github/webhook.ts +349 -0
- package/src/lib/agents/index.ts +35 -0
- package/src/lib/agents/state/atoms.ts +122 -0
- package/src/lib/agents/types.ts +83 -0
- package/src/lib/api.ts +71 -0
- package/src/lib/droid.spec.ts +14 -19
- package/src/lib/droid.ts +48 -4
- package/src/lib/gateway/SupabaseGateway.ts +236 -0
- package/src/lib/gateway/WorkerCommunication.ts +132 -0
- package/src/lib/gateway/WorkerPool.ts +304 -0
- package/src/lib/gateway/capabilities.spec.ts +144 -0
- package/src/lib/gateway/capabilities.ts +80 -0
- package/src/lib/gateway/index.ts +15 -0
- package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
- package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
- package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
- package/src/lib/gateway/types.ts +139 -0
- package/src/lib/mod/mod-manager.ts +13 -8
- package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
- package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
- package/src/lib/mod/module/supabase/mod-supabase.ts +73 -0
- package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
- package/src/lib/state/auth.spec.ts +58 -0
- package/src/lib/state/auth.ts +148 -0
- package/src/lib/state/bento.spec.ts +76 -0
- package/src/lib/state/bento.ts +51 -0
- package/src/lib/state/index.ts +98 -0
- package/src/lib/state/overlay-manager.ts +125 -0
- package/src/lib/state/profile-sync.ts +214 -0
- package/src/lib/state/profile.ts +155 -0
- package/src/lib/state/router.ts +24 -0
- package/src/lib/state/staff.ts +128 -0
- package/src/lib/state/sync-bus.ts +109 -0
- package/src/lib/state/theme-sync.ts +75 -0
- package/src/lib/state/toasts.spec.ts +106 -0
- package/src/lib/state/toasts.ts +34 -0
- package/src/lib/state/ui.spec.ts +148 -0
- package/src/lib/state/ui.ts +45 -0
- package/src/lib/state/welcome-toast.spec.ts +101 -0
- package/src/lib/state/welcome-toast.ts +76 -0
- package/src/lib/sw-recovery.spec.ts +165 -0
- package/src/lib/sw-recovery.ts +141 -0
- package/src/lib/types/bento.spec.ts +116 -0
- package/src/lib/types/bento.ts +94 -55
- package/src/lib/types/event-schemas.spec.ts +120 -0
- package/src/lib/types/event-types.spec.ts +96 -0
- package/src/lib/types/event-types.ts +113 -3
- package/src/lib/types/modules.ts +12 -5
- package/src/lib/types/ui-event-types.ts +66 -0
- package/src/lib/workers/canvas-ui-renderer.ts +266 -0
- package/src/lib/workers/canvas-worker.ts +94 -9
- package/src/lib/workers/data.ts +64 -37
- package/src/lib/workers/db-worker.ts +105 -32
- package/src/lib/workers/events.spec.ts +208 -0
- package/src/lib/workers/events.ts +27 -14
- package/src/lib/workers/flexbuilder.ts +2 -2
- package/src/lib/workers/init.ts +25 -17
- package/src/lib/workers/main.ts +506 -327
- package/src/lib/workers/supabase-db-worker.ts +243 -0
- package/src/lib/workers/supabase-shared-worker.ts +607 -0
- package/src/lib/workers/validate.ts +64 -0
- package/src/lib/workers/ws-worker.spec.ts +41 -0
- package/src/lib/workers/ws-worker.ts +255 -31
- package/src/setup-vitest.ts +4 -2
- package/src/types.ts +30 -0
- package/src/vite-env.d.ts +1 -0
- package/workers/main.js +2 -1125
- package/comlink.js +0 -253
- package/index.js +0 -2972
- package/reference.js +0 -259
- package/src/types.d.ts +0 -31
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { swRecovery } from './sw-recovery';
|
|
3
|
+
|
|
4
|
+
type Reg = {
|
|
5
|
+
scriptURL: string;
|
|
6
|
+
unregister: ReturnType<typeof vi.fn>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function makeReg(scriptURL: string, unregisterReturn = true): Reg {
|
|
10
|
+
return {
|
|
11
|
+
scriptURL,
|
|
12
|
+
unregister: vi.fn().mockResolvedValue(unregisterReturn),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function installNavigator(regs: Reg[]) {
|
|
17
|
+
const registrations = regs.map((r) => ({
|
|
18
|
+
active: { scriptURL: r.scriptURL },
|
|
19
|
+
installing: null,
|
|
20
|
+
waiting: null,
|
|
21
|
+
unregister: r.unregister,
|
|
22
|
+
})) as unknown as ServiceWorkerRegistration[];
|
|
23
|
+
|
|
24
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
25
|
+
value: {
|
|
26
|
+
serviceWorker: {
|
|
27
|
+
getRegistrations: vi.fn().mockResolvedValue(registrations),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function installCaches(keys: string[]) {
|
|
36
|
+
const cacheStore: Record<string, boolean> = Object.fromEntries(
|
|
37
|
+
keys.map((k) => [k, true]),
|
|
38
|
+
);
|
|
39
|
+
(globalThis as unknown as { caches: CacheStorage }).caches = {
|
|
40
|
+
keys: vi.fn().mockResolvedValue([...keys]),
|
|
41
|
+
delete: vi.fn(async (key: string) => {
|
|
42
|
+
const had = !!cacheStore[key];
|
|
43
|
+
delete cacheStore[key];
|
|
44
|
+
return had;
|
|
45
|
+
}),
|
|
46
|
+
match: vi.fn(),
|
|
47
|
+
has: vi.fn(),
|
|
48
|
+
open: vi.fn(),
|
|
49
|
+
} as unknown as CacheStorage;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function clearGlobals() {
|
|
53
|
+
delete (globalThis as unknown as { navigator?: unknown }).navigator;
|
|
54
|
+
delete (globalThis as unknown as { caches?: unknown }).caches;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
clearGlobals();
|
|
59
|
+
vi.spyOn(console, 'info').mockImplementation(vi.fn());
|
|
60
|
+
vi.spyOn(console, 'warn').mockImplementation(vi.fn());
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
clearGlobals();
|
|
65
|
+
vi.restoreAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('swRecovery', () => {
|
|
69
|
+
it('returns unsupported when navigator has no serviceWorker', async () => {
|
|
70
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
71
|
+
value: {},
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
});
|
|
75
|
+
const result = await swRecovery();
|
|
76
|
+
expect(result.supported).toBe(false);
|
|
77
|
+
expect(result.unregistered).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('unregisters all SWs when allowedScripts is empty', async () => {
|
|
81
|
+
const stale = makeReg('https://chat.kbve.com/sw.js');
|
|
82
|
+
const other = makeReg('https://chat.kbve.com/old-sw.js');
|
|
83
|
+
installNavigator([stale, other]);
|
|
84
|
+
installCaches(['a', 'b']);
|
|
85
|
+
|
|
86
|
+
const result = await swRecovery();
|
|
87
|
+
expect(result.supported).toBe(true);
|
|
88
|
+
expect(result.unregistered).toEqual([
|
|
89
|
+
'https://chat.kbve.com/sw.js',
|
|
90
|
+
'https://chat.kbve.com/old-sw.js',
|
|
91
|
+
]);
|
|
92
|
+
expect(result.keptScripts).toEqual([]);
|
|
93
|
+
expect(result.cachesCleared).toEqual(['a', 'b']);
|
|
94
|
+
expect(stale.unregister).toHaveBeenCalledTimes(1);
|
|
95
|
+
expect(other.unregister).toHaveBeenCalledTimes(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('keeps SWs whose absolute scriptURL is in allowedScripts', async () => {
|
|
99
|
+
const keep = makeReg('https://chat.kbve.com/sw.js');
|
|
100
|
+
const drop = makeReg('https://chat.kbve.com/legacy.js');
|
|
101
|
+
installNavigator([keep, drop]);
|
|
102
|
+
installCaches([]);
|
|
103
|
+
|
|
104
|
+
const result = await swRecovery({
|
|
105
|
+
allowedScripts: ['https://chat.kbve.com/sw.js'],
|
|
106
|
+
});
|
|
107
|
+
expect(result.keptScripts).toEqual(['https://chat.kbve.com/sw.js']);
|
|
108
|
+
expect(result.unregistered).toEqual([
|
|
109
|
+
'https://chat.kbve.com/legacy.js',
|
|
110
|
+
]);
|
|
111
|
+
expect(keep.unregister).not.toHaveBeenCalled();
|
|
112
|
+
expect(drop.unregister).toHaveBeenCalledTimes(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('keeps SWs whose pathname matches an allowed pathname rule', async () => {
|
|
116
|
+
const keep = makeReg('https://chat.kbve.com/sw.js');
|
|
117
|
+
installNavigator([keep]);
|
|
118
|
+
installCaches([]);
|
|
119
|
+
|
|
120
|
+
const result = await swRecovery({
|
|
121
|
+
allowedScripts: ['/sw.js'],
|
|
122
|
+
});
|
|
123
|
+
expect(result.keptScripts).toEqual(['https://chat.kbve.com/sw.js']);
|
|
124
|
+
expect(keep.unregister).not.toHaveBeenCalled();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('skips cache clearing when clearCaches is false', async () => {
|
|
128
|
+
installNavigator([makeReg('https://chat.kbve.com/old.js')]);
|
|
129
|
+
installCaches(['x']);
|
|
130
|
+
|
|
131
|
+
const result = await swRecovery({ clearCaches: false });
|
|
132
|
+
expect(result.cachesCleared).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('does not reload when nothing was removed', async () => {
|
|
136
|
+
installNavigator([]);
|
|
137
|
+
installCaches([]);
|
|
138
|
+
|
|
139
|
+
const reload = vi.fn();
|
|
140
|
+
Object.defineProperty(globalThis, 'window', {
|
|
141
|
+
value: { location: { reload } },
|
|
142
|
+
configurable: true,
|
|
143
|
+
writable: true,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const result = await swRecovery({ reloadOnCleanup: true });
|
|
147
|
+
expect(result.reloaded).toBe(false);
|
|
148
|
+
expect(reload).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('continues when an individual unregister rejects', async () => {
|
|
152
|
+
const ok = makeReg('https://chat.kbve.com/a.js');
|
|
153
|
+
const bad: Reg = {
|
|
154
|
+
scriptURL: 'https://chat.kbve.com/b.js',
|
|
155
|
+
unregister: vi.fn().mockRejectedValue(new Error('boom')),
|
|
156
|
+
};
|
|
157
|
+
installNavigator([ok, bad]);
|
|
158
|
+
installCaches([]);
|
|
159
|
+
|
|
160
|
+
const result = await swRecovery();
|
|
161
|
+
expect(result.unregistered).toEqual(['https://chat.kbve.com/a.js']);
|
|
162
|
+
expect(ok.unregister).toHaveBeenCalled();
|
|
163
|
+
expect(bad.unregister).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export interface SwRecoveryOptions {
|
|
2
|
+
allowedScripts?: readonly string[];
|
|
3
|
+
clearCaches?: boolean;
|
|
4
|
+
reloadOnCleanup?: boolean;
|
|
5
|
+
logPrefix?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SwRecoveryResult {
|
|
9
|
+
supported: boolean;
|
|
10
|
+
unregistered: string[];
|
|
11
|
+
keptScripts: string[];
|
|
12
|
+
cachesCleared: string[];
|
|
13
|
+
reloaded: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DEFAULT_OPTS: Required<Omit<SwRecoveryOptions, 'allowedScripts'>> & {
|
|
17
|
+
allowedScripts: readonly string[];
|
|
18
|
+
} = {
|
|
19
|
+
allowedScripts: [],
|
|
20
|
+
clearCaches: true,
|
|
21
|
+
reloadOnCleanup: false,
|
|
22
|
+
logPrefix: '[sw-recovery]',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function getScriptURL(reg: ServiceWorkerRegistration): string | null {
|
|
26
|
+
return (
|
|
27
|
+
reg.active?.scriptURL ??
|
|
28
|
+
reg.installing?.scriptURL ??
|
|
29
|
+
reg.waiting?.scriptURL ??
|
|
30
|
+
null
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function scriptIsAllowed(
|
|
35
|
+
scriptURL: string | null,
|
|
36
|
+
allowed: readonly string[],
|
|
37
|
+
): boolean {
|
|
38
|
+
if (!scriptURL) return false;
|
|
39
|
+
if (allowed.length === 0) return false;
|
|
40
|
+
return allowed.some((rule) => {
|
|
41
|
+
if (rule === scriptURL) return true;
|
|
42
|
+
try {
|
|
43
|
+
return new URL(scriptURL).pathname === rule;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function swRecovery(
|
|
51
|
+
opts: SwRecoveryOptions = {},
|
|
52
|
+
): Promise<SwRecoveryResult> {
|
|
53
|
+
const cfg = { ...DEFAULT_OPTS, ...opts };
|
|
54
|
+
const result: SwRecoveryResult = {
|
|
55
|
+
supported: false,
|
|
56
|
+
unregistered: [],
|
|
57
|
+
keptScripts: [],
|
|
58
|
+
cachesCleared: [],
|
|
59
|
+
reloaded: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
typeof navigator === 'undefined' ||
|
|
64
|
+
!('serviceWorker' in navigator) ||
|
|
65
|
+
typeof navigator.serviceWorker?.getRegistrations !== 'function'
|
|
66
|
+
) {
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
result.supported = true;
|
|
71
|
+
|
|
72
|
+
let registrations: readonly ServiceWorkerRegistration[] = [];
|
|
73
|
+
try {
|
|
74
|
+
registrations = await navigator.serviceWorker.getRegistrations();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn(`${cfg.logPrefix} getRegistrations failed:`, err);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const reg of registrations) {
|
|
81
|
+
const scriptURL = getScriptURL(reg);
|
|
82
|
+
if (scriptIsAllowed(scriptURL, cfg.allowedScripts)) {
|
|
83
|
+
result.keptScripts.push(scriptURL as string);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const ok = await reg.unregister();
|
|
89
|
+
if (ok) {
|
|
90
|
+
result.unregistered.push(scriptURL ?? '<unknown>');
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`${cfg.logPrefix} unregister failed for ${scriptURL}:`,
|
|
95
|
+
err,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (cfg.clearCaches && typeof caches !== 'undefined') {
|
|
101
|
+
try {
|
|
102
|
+
const keys = await caches.keys();
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
try {
|
|
105
|
+
const ok = await caches.delete(key);
|
|
106
|
+
if (ok) result.cachesCleared.push(key);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`${cfg.logPrefix} cache delete failed for ${key}:`,
|
|
110
|
+
err,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.warn(`${cfg.logPrefix} caches.keys failed:`, err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const removedAnything =
|
|
120
|
+
result.unregistered.length > 0 || result.cachesCleared.length > 0;
|
|
121
|
+
|
|
122
|
+
if (removedAnything) {
|
|
123
|
+
console.info(`${cfg.logPrefix} cleaned`, {
|
|
124
|
+
unregistered: result.unregistered,
|
|
125
|
+
kept: result.keptScripts,
|
|
126
|
+
caches: result.cachesCleared,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (
|
|
131
|
+
cfg.reloadOnCleanup &&
|
|
132
|
+
removedAnything &&
|
|
133
|
+
typeof window !== 'undefined' &&
|
|
134
|
+
typeof window.location?.reload === 'function'
|
|
135
|
+
) {
|
|
136
|
+
result.reloaded = true;
|
|
137
|
+
window.location.reload();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
BentoTileSchema,
|
|
4
|
+
BENTO_VARIANT_CLASS_MAP,
|
|
5
|
+
BENTO_ANIMATION_CLASS_MAP,
|
|
6
|
+
BENTO_BADGE_CLASS_MAP,
|
|
7
|
+
} from './bento';
|
|
8
|
+
|
|
9
|
+
const validTile = {
|
|
10
|
+
title: 'Test Tile',
|
|
11
|
+
primaryColor: 'blue-500',
|
|
12
|
+
secondaryColor: 'red-300',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('BentoTileSchema', () => {
|
|
16
|
+
it('accepts a minimal valid tile', () => {
|
|
17
|
+
const result = BentoTileSchema.safeParse(validTile);
|
|
18
|
+
expect(result.success).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('rejects empty title', () => {
|
|
22
|
+
const result = BentoTileSchema.safeParse({
|
|
23
|
+
...validTile,
|
|
24
|
+
title: '',
|
|
25
|
+
});
|
|
26
|
+
expect(result.success).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rejects invalid primary color format', () => {
|
|
30
|
+
const result = BentoTileSchema.safeParse({
|
|
31
|
+
...validTile,
|
|
32
|
+
primaryColor: 'invalid',
|
|
33
|
+
});
|
|
34
|
+
expect(result.success).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('accepts valid ULID id', () => {
|
|
38
|
+
const result = BentoTileSchema.safeParse({
|
|
39
|
+
...validTile,
|
|
40
|
+
id: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
|
|
41
|
+
});
|
|
42
|
+
expect(result.success).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('rejects invalid ULID id', () => {
|
|
46
|
+
const result = BentoTileSchema.safeParse({
|
|
47
|
+
...validTile,
|
|
48
|
+
id: 'not-a-ulid',
|
|
49
|
+
});
|
|
50
|
+
expect(result.success).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('accepts valid span format', () => {
|
|
54
|
+
const result = BentoTileSchema.safeParse({
|
|
55
|
+
...validTile,
|
|
56
|
+
span: 'col-span-2',
|
|
57
|
+
});
|
|
58
|
+
expect(result.success).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('requires target when href is provided', () => {
|
|
62
|
+
const result = BentoTileSchema.safeParse({
|
|
63
|
+
...validTile,
|
|
64
|
+
href: 'https://example.com',
|
|
65
|
+
});
|
|
66
|
+
expect(result.success).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('accepts href with target', () => {
|
|
70
|
+
const result = BentoTileSchema.safeParse({
|
|
71
|
+
...validTile,
|
|
72
|
+
href: 'https://example.com',
|
|
73
|
+
target: '_blank',
|
|
74
|
+
});
|
|
75
|
+
expect(result.success).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('requires badge text when badgeType is set', () => {
|
|
79
|
+
const result = BentoTileSchema.safeParse({
|
|
80
|
+
...validTile,
|
|
81
|
+
badgeType: 'success',
|
|
82
|
+
});
|
|
83
|
+
expect(result.success).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('accepts badge with badgeType', () => {
|
|
87
|
+
const result = BentoTileSchema.safeParse({
|
|
88
|
+
...validTile,
|
|
89
|
+
badge: 'New',
|
|
90
|
+
badgeType: 'success',
|
|
91
|
+
});
|
|
92
|
+
expect(result.success).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Bento class maps', () => {
|
|
97
|
+
it('BENTO_VARIANT_CLASS_MAP has all variants', () => {
|
|
98
|
+
expect(BENTO_VARIANT_CLASS_MAP).toHaveProperty('default');
|
|
99
|
+
expect(BENTO_VARIANT_CLASS_MAP).toHaveProperty('minimal');
|
|
100
|
+
expect(BENTO_VARIANT_CLASS_MAP).toHaveProperty('image-heavy');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('BENTO_ANIMATION_CLASS_MAP has all animations', () => {
|
|
104
|
+
expect(BENTO_ANIMATION_CLASS_MAP).toHaveProperty('fade-in');
|
|
105
|
+
expect(BENTO_ANIMATION_CLASS_MAP).toHaveProperty('slide-up');
|
|
106
|
+
expect(BENTO_ANIMATION_CLASS_MAP).toHaveProperty('none');
|
|
107
|
+
expect(BENTO_ANIMATION_CLASS_MAP['none']).toBe('');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('BENTO_BADGE_CLASS_MAP has all badge types', () => {
|
|
111
|
+
expect(BENTO_BADGE_CLASS_MAP).toHaveProperty('default');
|
|
112
|
+
expect(BENTO_BADGE_CLASS_MAP).toHaveProperty('success');
|
|
113
|
+
expect(BENTO_BADGE_CLASS_MAP).toHaveProperty('warning');
|
|
114
|
+
expect(BENTO_BADGE_CLASS_MAP).toHaveProperty('error');
|
|
115
|
+
});
|
|
116
|
+
});
|
package/src/lib/types/bento.ts
CHANGED
|
@@ -5,7 +5,12 @@ export const BENTO_VARIANTS = ['default', 'minimal', 'image-heavy'] as const;
|
|
|
5
5
|
export const BENTO_ANIMATIONS = ['fade-in', 'slide-up', 'none'] as const;
|
|
6
6
|
export const BENTO_TARGETS = ['_self', '_blank', '_parent', '_top'] as const;
|
|
7
7
|
export const BENTO_ROLES = ['button', 'link', 'region', 'article'] as const;
|
|
8
|
-
export const BENTO_BADGE_TYPES = [
|
|
8
|
+
export const BENTO_BADGE_TYPES = [
|
|
9
|
+
'default',
|
|
10
|
+
'success',
|
|
11
|
+
'warning',
|
|
12
|
+
'error',
|
|
13
|
+
] as const;
|
|
9
14
|
|
|
10
15
|
// Regex validators
|
|
11
16
|
const tailwindColorRegex = /^[a-z]+-(100|200|300|400|500|600|700|800|900)$/;
|
|
@@ -13,68 +18,102 @@ const spanRegex = /^(?:(?:[a-z]+:)?(col|row)-span-\d+\s*)+$/;
|
|
|
13
18
|
const ulidRegex = /^01[0-9A-HJKMNP-TV-Z]{24}$/;
|
|
14
19
|
|
|
15
20
|
// Schema
|
|
16
|
-
export const BentoTileSchema = z
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
21
|
+
export const BentoTileSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
id: z.string().regex(ulidRegex, 'Invalid ULID format').optional(),
|
|
24
|
+
|
|
25
|
+
title: z.string().min(1, 'Title is required'),
|
|
26
|
+
subtitle: z.string().optional(),
|
|
27
|
+
description: z.string().optional(),
|
|
28
|
+
span: z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(
|
|
31
|
+
spanRegex,
|
|
32
|
+
'Invalid span format (e.g., col-span-2, row-span-1)',
|
|
33
|
+
)
|
|
34
|
+
.optional(),
|
|
35
|
+
|
|
36
|
+
primaryColor: z
|
|
37
|
+
.string()
|
|
38
|
+
.regex(
|
|
39
|
+
tailwindColorRegex,
|
|
40
|
+
'Invalid Tailwind color format (e.g., blue-500)',
|
|
41
|
+
),
|
|
42
|
+
secondaryColor: z
|
|
43
|
+
.string()
|
|
44
|
+
.regex(
|
|
45
|
+
tailwindColorRegex,
|
|
46
|
+
'Invalid Tailwind color format (e.g., blue-500)',
|
|
47
|
+
),
|
|
48
|
+
|
|
49
|
+
icon: z.string().optional(),
|
|
50
|
+
backgroundImage: z.string().url().optional(),
|
|
51
|
+
|
|
52
|
+
onclick: z.string().optional(),
|
|
53
|
+
href: z.string().url().optional(),
|
|
54
|
+
target: z.enum(BENTO_TARGETS).optional(),
|
|
55
|
+
|
|
56
|
+
ariaLabel: z.string().optional(),
|
|
57
|
+
role: z.enum(BENTO_ROLES).optional(),
|
|
58
|
+
|
|
59
|
+
variant: z.enum(BENTO_VARIANTS).optional(),
|
|
60
|
+
animation: z.enum(BENTO_ANIMATIONS).optional(),
|
|
61
|
+
|
|
62
|
+
badge: z.string().optional(),
|
|
63
|
+
badgeType: z.enum(BENTO_BADGE_TYPES).optional(),
|
|
64
|
+
|
|
65
|
+
priority: z.number().int().min(0).optional(),
|
|
66
|
+
tooltip: z.string().optional(),
|
|
67
|
+
disabled: z.boolean().optional(),
|
|
68
|
+
|
|
69
|
+
meta: z.record(z.string(), z.any()).optional(),
|
|
70
|
+
dataset: z.record(z.string(), z.string()).optional(),
|
|
71
|
+
className: z.string().optional(),
|
|
72
|
+
})
|
|
73
|
+
.refine((data) => !data.href || (data.href && data.target), {
|
|
74
|
+
message: 'target is required when href is provided',
|
|
75
|
+
path: ['target'],
|
|
76
|
+
})
|
|
77
|
+
.refine(
|
|
78
|
+
(data) => !data.badgeType || (data.badge && data.badge.length > 0),
|
|
79
|
+
{
|
|
80
|
+
message: 'badge must be provided if badgeType is set',
|
|
81
|
+
path: ['badge'],
|
|
82
|
+
},
|
|
83
|
+
);
|
|
57
84
|
|
|
58
85
|
// Types
|
|
59
86
|
export type BentoTile = z.infer<typeof BentoTileSchema>;
|
|
60
87
|
export type BentoVariantClass = { base: string; hover?: string };
|
|
61
88
|
|
|
62
89
|
// Mappings
|
|
63
|
-
export const BENTO_VARIANT_CLASS_MAP: Record<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
export const BENTO_VARIANT_CLASS_MAP: Record<
|
|
91
|
+
(typeof BENTO_VARIANTS)[number],
|
|
92
|
+
BentoVariantClass
|
|
93
|
+
> = {
|
|
94
|
+
default: { base: 'p-4 flex flex-col justify-between' },
|
|
95
|
+
minimal: {
|
|
96
|
+
base: 'p-2 bg-opacity-30 backdrop-blur-sm',
|
|
97
|
+
hover: 'bg-opacity-50',
|
|
98
|
+
},
|
|
99
|
+
'image-heavy': { base: 'p-0 overflow-hidden', hover: 'scale-105' },
|
|
67
100
|
};
|
|
68
101
|
|
|
69
|
-
export const BENTO_ANIMATION_CLASS_MAP: Record<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
102
|
+
export const BENTO_ANIMATION_CLASS_MAP: Record<
|
|
103
|
+
(typeof BENTO_ANIMATIONS)[number],
|
|
104
|
+
string
|
|
105
|
+
> = {
|
|
106
|
+
'fade-in': 'animate-fade-in',
|
|
107
|
+
'slide-up': 'animate-slide-up',
|
|
108
|
+
none: '',
|
|
73
109
|
};
|
|
74
110
|
|
|
75
|
-
export const BENTO_BADGE_CLASS_MAP: Record<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
export const BENTO_BADGE_CLASS_MAP: Record<
|
|
112
|
+
(typeof BENTO_BADGE_TYPES)[number],
|
|
113
|
+
string
|
|
114
|
+
> = {
|
|
115
|
+
default: 'bg-white/30 text-white',
|
|
116
|
+
success: 'bg-green-500/80 text-white',
|
|
117
|
+
warning: 'bg-yellow-500/80 text-black',
|
|
118
|
+
error: 'bg-red-500/80 text-white',
|
|
80
119
|
};
|