@kbve/droid 0.0.5 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +3 -1
  2. package/comlink.js +1 -7
  3. package/droid.mjs +191 -61
  4. package/index.d.ts +29 -0
  5. package/index.js +9054 -2763
  6. package/lib/gateway/SupabaseGateway.d.ts +38 -0
  7. package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
  8. package/lib/gateway/WorkerCommunication.d.ts +39 -0
  9. package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
  10. package/lib/gateway/WorkerPool.d.ts +55 -0
  11. package/lib/gateway/WorkerPool.d.ts.map +1 -0
  12. package/lib/gateway/capabilities.d.ts +23 -0
  13. package/lib/gateway/capabilities.d.ts.map +1 -0
  14. package/lib/gateway/index.d.ts +9 -0
  15. package/lib/gateway/index.d.ts.map +1 -0
  16. package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
  17. package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
  18. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
  19. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
  20. package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
  21. package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
  22. package/lib/gateway/types.d.ts +81 -0
  23. package/lib/gateway/types.d.ts.map +1 -0
  24. package/lib/workers/canvas-ui-renderer.d.ts +25 -0
  25. package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
  26. package/lib/workers/canvas-worker.d.ts +14 -0
  27. package/lib/workers/canvas-worker.d.ts.map +1 -0
  28. package/lib/workers/canvas-worker.js +291 -145
  29. package/lib/workers/data.d.ts +40 -0
  30. package/lib/workers/data.d.ts.map +1 -0
  31. package/lib/workers/db-worker.d.ts +69 -0
  32. package/lib/workers/db-worker.d.ts.map +1 -0
  33. package/lib/workers/db-worker.js +2680 -2147
  34. package/lib/workers/events.d.ts +11 -0
  35. package/lib/workers/events.d.ts.map +1 -0
  36. package/lib/workers/flexbuilder.d.ts +4 -0
  37. package/lib/workers/flexbuilder.d.ts.map +1 -0
  38. package/lib/workers/init.d.ts +11 -0
  39. package/lib/workers/init.d.ts.map +1 -0
  40. package/lib/workers/main.d.ts +107 -0
  41. package/lib/workers/main.d.ts.map +1 -0
  42. package/lib/workers/supabase-db-worker.d.ts +2 -0
  43. package/lib/workers/supabase-db-worker.d.ts.map +1 -0
  44. package/lib/workers/supabase-db-worker.js +9337 -0
  45. package/lib/workers/supabase-shared-worker.d.ts +2 -0
  46. package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
  47. package/lib/workers/supabase-shared-worker.js +12635 -0
  48. package/lib/workers/tools.d.ts +4 -0
  49. package/lib/workers/tools.d.ts.map +1 -0
  50. package/lib/workers/validate.d.ts +15 -0
  51. package/lib/workers/validate.d.ts.map +1 -0
  52. package/lib/workers/ws-worker.d.ts +11 -0
  53. package/lib/workers/ws-worker.d.ts.map +1 -0
  54. package/lib/workers/ws-worker.js +174 -115
  55. package/main.js +4875 -0
  56. package/package.json +17 -6
  57. package/reference.js +688 -228
  58. package/src/index.ts +38 -13
  59. package/src/lib/droid.spec.ts +14 -19
  60. package/src/lib/droid.ts +48 -4
  61. package/src/lib/gateway/SupabaseGateway.ts +183 -0
  62. package/src/lib/gateway/WorkerCommunication.ts +132 -0
  63. package/src/lib/gateway/WorkerPool.ts +304 -0
  64. package/src/lib/gateway/capabilities.ts +80 -0
  65. package/src/lib/gateway/index.ts +15 -0
  66. package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
  67. package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
  68. package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
  69. package/src/lib/gateway/types.ts +112 -0
  70. package/src/lib/mod/mod-manager.ts +13 -8
  71. package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
  72. package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
  73. package/src/lib/mod/module/supabase/mod-supabase.ts +68 -0
  74. package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
  75. package/src/lib/state/auth.spec.ts +58 -0
  76. package/src/lib/state/auth.ts +30 -0
  77. package/src/lib/state/index.ts +34 -0
  78. package/src/lib/state/overlay-manager.ts +125 -0
  79. package/src/lib/state/router.ts +24 -0
  80. package/src/lib/state/theme-sync.ts +75 -0
  81. package/src/lib/state/toasts.spec.ts +106 -0
  82. package/src/lib/state/toasts.ts +34 -0
  83. package/src/lib/state/ui.spec.ts +148 -0
  84. package/src/lib/state/ui.ts +45 -0
  85. package/src/lib/state/welcome-toast.ts +76 -0
  86. package/src/lib/types/bento.spec.ts +116 -0
  87. package/src/lib/types/bento.ts +94 -55
  88. package/src/lib/types/event-types.spec.ts +93 -0
  89. package/src/lib/types/event-types.ts +39 -3
  90. package/src/lib/types/modules.ts +12 -5
  91. package/src/lib/types/ui-event-types.ts +66 -0
  92. package/src/lib/workers/canvas-ui-renderer.ts +266 -0
  93. package/src/lib/workers/canvas-worker.ts +94 -9
  94. package/src/lib/workers/data.ts +59 -36
  95. package/src/lib/workers/db-worker.ts +79 -32
  96. package/src/lib/workers/events.spec.ts +208 -0
  97. package/src/lib/workers/events.ts +27 -14
  98. package/src/lib/workers/flexbuilder.ts +2 -2
  99. package/src/lib/workers/init.ts +25 -17
  100. package/src/lib/workers/main.ts +509 -272
  101. package/src/lib/workers/supabase-db-worker.ts +221 -0
  102. package/src/lib/workers/supabase-shared-worker.ts +572 -0
  103. package/src/lib/workers/validate.ts +64 -0
  104. package/src/lib/workers/ws-worker.ts +156 -25
  105. package/src/setup-vitest.ts +4 -2
  106. package/src/types.ts +30 -0
  107. package/src/vite-env.d.ts +1 -0
  108. package/workers/main.js +10 -1089
  109. package/src/types.d.ts +0 -31
@@ -9,18 +9,19 @@ interface SharedWorkerGlobalScope extends Worker {
9
9
  declare const self: SharedWorkerGlobalScope;
10
10
 
11
11
  class AppDexie extends Dexie {
12
- settings!: Table<{ id: string; value: any }, string>;
13
- meta!: Table<{ key: string; value: any }, string>;
14
- i18n!: Table<{ key: string; value: string }, string>;
15
- htmlservers!: Table<{ key: string; value: string }, string>;
16
- servers!: Table<DiscordServer, string>;
17
- tags!: Table<DiscordTag, string>;
18
- profiles!: Table<Profile, string>;
19
- ws_messages!: Table<{ key: string; message: any }, string>;
12
+ settings!: Table<{ id: string; value: unknown }>;
13
+ meta!: Table<{ key: string; value: unknown }>;
14
+ i18n!: Table<{ key: string; value: string }>;
15
+ htmlservers!: Table<{ key: string; value: string }>;
16
+ servers!: Table<DiscordServer>;
17
+ tags!: Table<DiscordTag>;
18
+ profiles!: Table<Profile>;
19
+ ws_messages!: Table<{ key: string; message: Record<string, unknown> }>;
20
+ auth_tokens!: Table<{ key: string; value: string; expires_at?: number }>;
20
21
 
21
22
  constructor() {
22
23
  super('AppStorage');
23
- this.version(3).stores({
24
+ this.version(4).stores({
24
25
  settings: '&id',
25
26
  meta: '&key',
26
27
  i18n: '&key',
@@ -29,15 +30,12 @@ class AppDexie extends Dexie {
29
30
  tags: '&tag_id',
30
31
  profiles: '&profile_id',
31
32
  ws_messages: '&key',
33
+ auth_tokens: '&key',
32
34
  });
33
35
  }
34
36
  }
35
37
  const db = new AppDexie();
36
38
 
37
- function delay(ms: number) {
38
- return new Promise((res) => setTimeout(res, ms));
39
- }
40
-
41
39
  function renderHtmlForServer(server: DiscordServer): string {
42
40
  return `
43
41
  <div class="flex flex-col gap-2 p-2">
@@ -51,19 +49,23 @@ function renderHtmlForServer(server: DiscordServer): string {
51
49
 
52
50
  // --- Comlink-Safe API ---
53
51
  const storageAPI = {
54
-
55
52
  // WebSocket
56
53
 
57
54
  async storeWsMessage(key: string, buffer: ArrayBuffer) {
58
- const decoded = toReference(buffer).toObject();
55
+ const decoded = toReference(buffer).toObject() as Record<
56
+ string,
57
+ unknown
58
+ >;
59
59
  await db.ws_messages.put({ key, message: decoded });
60
60
  },
61
61
 
62
62
  async getWsMessage(key: string) {
63
63
  return (await db.ws_messages.get(key))?.message ?? null;
64
64
  },
65
-
66
- async getAllWsMessages(): Promise<{ key: string; message: any }[]> {
65
+
66
+ async getAllWsMessages(): Promise<
67
+ { key: string; message: Record<string, unknown> }[]
68
+ > {
67
69
  const raw = await db.ws_messages.toArray();
68
70
  return raw.sort((a, b) => {
69
71
  const at = Number(a.key.split(':')[1]);
@@ -71,7 +73,7 @@ const storageAPI = {
71
73
  return bt - at;
72
74
  });
73
75
  },
74
-
76
+
75
77
  async clearWsMessages() {
76
78
  await db.ws_messages.clear();
77
79
  },
@@ -95,20 +97,31 @@ const storageAPI = {
95
97
  }));
96
98
  await db.i18n.bulkPut(entries);
97
99
  },
98
- async loadI18nFromJSON(path = 'https://discord.sh/i18n/db.json') {
99
- const res = await fetch(path);
100
- const data = await res.json();
101
- await storageAPI.putI18nBatch(data);
100
+ async loadI18nFromJSON(path: string) {
101
+ const res = await fetch(path);
102
+ if (!res.ok) {
103
+ console.warn(
104
+ `[db-worker] i18n fetch failed: ${res.status} ${res.statusText}`,
105
+ );
106
+ return;
107
+ }
108
+ const data = await res.json();
109
+ await storageAPI.putI18nBatch(data);
102
110
  },
103
111
  async getAllI18nKeys(): Promise<string[]> {
104
- const all = await db.i18n.toCollection().primaryKeys();
105
- return all as string[];
112
+ return (await db.i18n.toCollection().primaryKeys()) as string[];
106
113
  },
107
114
 
108
- async loadServersFromJSON(path = 'https://discord.sh/data/servers.json') {
115
+ async loadServersFromJSON(path: string) {
109
116
  const res = await fetch(path);
117
+ if (!res.ok) {
118
+ console.warn(
119
+ `[db-worker] servers fetch failed: ${res.status} ${res.statusText}`,
120
+ );
121
+ return;
122
+ }
110
123
  const data = await res.json();
111
-
124
+
112
125
  // The JSON should be a Record<string, DiscordServer>
113
126
  const servers: DiscordServer[] = Object.values(data);
114
127
  await storageAPI.putServers(servers);
@@ -118,26 +131,32 @@ const storageAPI = {
118
131
  // HTML PreRender
119
132
  async syncHtmlFromServers() {
120
133
  const allServers = await db.servers.toArray();
121
- const existingHtmlKeys = await db.htmlservers.toCollection().primaryKeys();
134
+ const existingHtmlKeys = await db.htmlservers
135
+ .toCollection()
136
+ .primaryKeys();
122
137
  const missing: { key: string; value: string }[] = [];
123
-
138
+
124
139
  for (const server of allServers) {
125
140
  if (!existingHtmlKeys.includes(server.server_id)) {
126
141
  const html = renderHtmlForServer(server);
127
142
  missing.push({ key: server.server_id, value: html });
128
143
  }
129
144
  }
130
-
145
+
131
146
  if (missing.length > 0) {
132
147
  await db.htmlservers.bulkPut(missing);
133
- console.info(`[syncHtmlFromServers] Generated ${missing.length} HTML cards.`);
148
+ console.info(
149
+ `[syncHtmlFromServers] Generated ${missing.length} HTML cards.`,
150
+ );
134
151
  } else {
135
- console.info('[syncHtmlFromServers] All servers already have HTML cards.');
152
+ console.info(
153
+ '[syncHtmlFromServers] All servers already have HTML cards.',
154
+ );
136
155
  }
137
156
  },
138
157
 
139
158
  // Settings
140
- async dbSet(id: string, value: any) {
159
+ async dbSet(id: string, value: unknown) {
141
160
  await db.settings.put({ id, value });
142
161
  },
143
162
  async dbGet(id: string) {
@@ -192,12 +211,40 @@ const storageAPI = {
192
211
  async getAllProfiles() {
193
212
  return await db.profiles.toArray();
194
213
  },
214
+
215
+ // Auth Tokens (IDB-backed auth storage)
216
+ async setAuthToken(key: string, value: string, expiresAt?: number) {
217
+ await db.auth_tokens.put({ key, value, expires_at: expiresAt });
218
+ },
219
+ async getAuthToken(key: string): Promise<string | null> {
220
+ const record = await db.auth_tokens.get(key);
221
+ if (!record) return null;
222
+ if (record.expires_at && Date.now() > record.expires_at) {
223
+ await db.auth_tokens.delete(key);
224
+ return null;
225
+ }
226
+ return record.value;
227
+ },
228
+ async removeAuthToken(key: string) {
229
+ await db.auth_tokens.delete(key);
230
+ },
231
+ async clearAuthTokens() {
232
+ await db.auth_tokens.clear();
233
+ },
195
234
  };
196
235
 
197
236
  export type LocalStorageAPI = typeof storageAPI;
198
237
 
238
+ const ports = new Set<MessagePort>();
239
+
199
240
  self.onconnect = (event: MessageEvent) => {
200
241
  const port = event.ports[0];
242
+ const isFirst = ports.size === 0;
243
+ ports.add(port);
201
244
  port.start();
245
+ port.postMessage({ type: isFirst ? 'first-connect' : 'reconnect' });
246
+ port.addEventListener('message', (e: MessageEvent) => {
247
+ if (e.data?.type === 'close') ports.delete(port);
248
+ });
202
249
  expose(storageAPI, port);
203
250
  };
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ // We need a fresh DroidEventBus for each test, so import the class-creating module
4
+ // and re-instantiate. Since the module exports a singleton, we'll test via dynamic import.
5
+ // Actually, let's just test the singleton and reset between tests.
6
+
7
+ // The module exports a singleton `DroidEvents`. We can still test it
8
+ // by carefully managing listener cleanup.
9
+
10
+ import { DroidEvents } from './events';
11
+
12
+ beforeEach(() => {
13
+ // Remove all listeners by re-creating internal state isn't possible
14
+ // with the singleton, so we rely on off() in each test.
15
+ });
16
+
17
+ describe('DroidEventBus', () => {
18
+ it('should register and fire a listener via on/emit', () => {
19
+ const handler = vi.fn();
20
+ DroidEvents.on('droid-ready', handler);
21
+
22
+ DroidEvents.emit('droid-ready', { timestamp: 123 });
23
+
24
+ expect(handler).toHaveBeenCalledOnce();
25
+ expect(handler).toHaveBeenCalledWith({ timestamp: 123 });
26
+
27
+ DroidEvents.off('droid-ready', handler);
28
+ });
29
+
30
+ it('should unregister a listener via off', () => {
31
+ const handler = vi.fn();
32
+ DroidEvents.on('droid-ready', handler);
33
+ DroidEvents.off('droid-ready', handler);
34
+
35
+ DroidEvents.emit('droid-ready', { timestamp: 1 });
36
+
37
+ expect(handler).not.toHaveBeenCalled();
38
+ });
39
+
40
+ it('should support multiple listeners on the same event', () => {
41
+ const h1 = vi.fn();
42
+ const h2 = vi.fn();
43
+ DroidEvents.on('droid-ready', h1);
44
+ DroidEvents.on('droid-ready', h2);
45
+
46
+ DroidEvents.emit('droid-ready', { timestamp: 1 });
47
+
48
+ expect(h1).toHaveBeenCalledOnce();
49
+ expect(h2).toHaveBeenCalledOnce();
50
+
51
+ DroidEvents.off('droid-ready', h1);
52
+ DroidEvents.off('droid-ready', h2);
53
+ });
54
+
55
+ it('should not fail when removing a listener that was never added', () => {
56
+ const handler = vi.fn();
57
+ expect(() => DroidEvents.off('droid-ready', handler)).not.toThrow();
58
+ });
59
+
60
+ it('should validate payload via Zod and reject invalid payloads', () => {
61
+ const handler = vi.fn();
62
+ DroidEvents.on('droid-ready', handler);
63
+
64
+ const consoleSpy = vi
65
+ .spyOn(console, 'error')
66
+ .mockImplementation(() => undefined);
67
+
68
+ // @ts-expect-error intentionally passing invalid payload
69
+ DroidEvents.emit('droid-ready', { timestamp: 'not-a-number' });
70
+
71
+ expect(handler).not.toHaveBeenCalled();
72
+ expect(consoleSpy).toHaveBeenCalledWith(
73
+ expect.stringContaining('[DroidEventBus] Invalid payload'),
74
+ expect.anything(),
75
+ );
76
+
77
+ consoleSpy.mockRestore();
78
+ DroidEvents.off('droid-ready', handler);
79
+ });
80
+
81
+ it('should dispatch a CustomEvent on window', () => {
82
+ const windowHandler = vi.fn();
83
+ window.addEventListener('droid-ready', windowHandler);
84
+
85
+ DroidEvents.emit('droid-ready', { timestamp: 42 });
86
+
87
+ expect(windowHandler).toHaveBeenCalledOnce();
88
+ const event = windowHandler.mock.calls[0][0] as CustomEvent;
89
+ expect(event.detail).toEqual({ timestamp: 42 });
90
+
91
+ window.removeEventListener('droid-ready', windowHandler);
92
+ });
93
+
94
+ it('should catch and log listener errors without breaking other listeners', () => {
95
+ const badHandler = vi.fn(() => {
96
+ throw new Error('boom');
97
+ });
98
+ const goodHandler = vi.fn();
99
+
100
+ DroidEvents.on('droid-ready', badHandler);
101
+ DroidEvents.on('droid-ready', goodHandler);
102
+
103
+ const consoleSpy = vi
104
+ .spyOn(console, 'error')
105
+ .mockImplementation(() => undefined);
106
+
107
+ DroidEvents.emit('droid-ready', { timestamp: 1 });
108
+
109
+ expect(badHandler).toHaveBeenCalledOnce();
110
+ expect(goodHandler).toHaveBeenCalledOnce();
111
+ expect(consoleSpy).toHaveBeenCalledWith(
112
+ expect.stringContaining('[DroidEventBus] Listener error'),
113
+ expect.anything(),
114
+ );
115
+
116
+ consoleSpy.mockRestore();
117
+ DroidEvents.off('droid-ready', badHandler);
118
+ DroidEvents.off('droid-ready', goodHandler);
119
+ });
120
+
121
+ it('should resolve wait() when the event fires', async () => {
122
+ const promise = DroidEvents.wait('droid-ready');
123
+
124
+ DroidEvents.emit('droid-ready', { timestamp: 999 });
125
+
126
+ const result = await promise;
127
+ expect(result).toEqual({ timestamp: 999 });
128
+ });
129
+
130
+ it('wait() should auto-unregister after resolving', async () => {
131
+ const promise = DroidEvents.wait('droid-ready');
132
+ DroidEvents.emit('droid-ready', { timestamp: 1 });
133
+ await promise;
134
+
135
+ // Emit again — the wait handler should not fire again
136
+ // (no way to directly test, but ensures no memory leak)
137
+ const handler = vi.fn();
138
+ DroidEvents.on('droid-ready', handler);
139
+ DroidEvents.emit('droid-ready', { timestamp: 2 });
140
+ expect(handler).toHaveBeenCalledOnce();
141
+ DroidEvents.off('droid-ready', handler);
142
+ });
143
+
144
+ it('should emit droid-downscale event with level payload', () => {
145
+ const handler = vi.fn();
146
+ DroidEvents.on('droid-downscale', handler);
147
+
148
+ DroidEvents.emit('droid-downscale', {
149
+ timestamp: Date.now(),
150
+ level: 'minimal',
151
+ });
152
+
153
+ expect(handler).toHaveBeenCalledOnce();
154
+ expect(handler.mock.calls[0][0].level).toBe('minimal');
155
+
156
+ DroidEvents.off('droid-downscale', handler);
157
+ });
158
+
159
+ it('should emit droid-upscale event with level payload', () => {
160
+ const handler = vi.fn();
161
+ DroidEvents.on('droid-upscale', handler);
162
+
163
+ DroidEvents.emit('droid-upscale', {
164
+ timestamp: Date.now(),
165
+ level: 'full',
166
+ });
167
+
168
+ expect(handler).toHaveBeenCalledOnce();
169
+ expect(handler.mock.calls[0][0].level).toBe('full');
170
+
171
+ DroidEvents.off('droid-upscale', handler);
172
+ });
173
+
174
+ it('should emit panel-open and panel-close events', () => {
175
+ const openHandler = vi.fn();
176
+ const closeHandler = vi.fn();
177
+ DroidEvents.on('panel-open', openHandler);
178
+ DroidEvents.on('panel-close', closeHandler);
179
+
180
+ DroidEvents.emit('panel-open', { id: 'left' });
181
+ DroidEvents.emit('panel-close', { id: 'right' });
182
+
183
+ expect(openHandler).toHaveBeenCalledWith({ id: 'left' });
184
+ expect(closeHandler).toHaveBeenCalledWith({ id: 'right' });
185
+
186
+ DroidEvents.off('panel-open', openHandler);
187
+ DroidEvents.off('panel-close', closeHandler);
188
+ });
189
+
190
+ it('should emit toast events', () => {
191
+ const handler = vi.fn();
192
+ DroidEvents.on('toast-added', handler);
193
+
194
+ DroidEvents.emit('toast-added', {
195
+ id: 'toast-1',
196
+ message: 'Hello',
197
+ severity: 'info',
198
+ });
199
+
200
+ expect(handler).toHaveBeenCalledWith({
201
+ id: 'toast-1',
202
+ message: 'Hello',
203
+ severity: 'info',
204
+ });
205
+
206
+ DroidEvents.off('toast-added', handler);
207
+ });
208
+ });
@@ -1,23 +1,26 @@
1
1
  import { DroidEventSchemas } from '../types/event-types';
2
- import type { DroidEventMap, EventKey, EventHandler } from '../types/event-types';
3
-
4
- import { z } from 'zod';
5
-
2
+ import type {
3
+ DroidEventMap,
4
+ EventKey,
5
+ EventHandler,
6
+ } from '../types/event-types';
6
7
 
7
8
  class DroidEventBus {
8
- private listeners = new Map<EventKey, Set<(payload: unknown) => void>>();
9
+ private listeners = new Map<EventKey, Set<(payload: unknown) => void>>();
9
10
 
10
- on<K extends EventKey>(event: K, handler: EventHandler<K>) {
11
+ on<K extends EventKey>(event: K, handler: EventHandler<K>) {
11
12
  let handlers = this.listeners.get(event);
12
- if (!handlers) {
13
- handlers = new Set();
14
- this.listeners.set(event, handlers);
15
- }
16
- handlers.add(handler as (payload: unknown) => void);
13
+ if (!handlers) {
14
+ handlers = new Set();
15
+ this.listeners.set(event, handlers);
16
+ }
17
+ handlers.add(handler as (payload: unknown) => void);
17
18
  }
18
19
 
19
20
  off<K extends EventKey>(event: K, handler: EventHandler<K>) {
20
- this.listeners.get(event)?.delete(handler as (payload: unknown) => void);
21
+ this.listeners
22
+ .get(event)
23
+ ?.delete(handler as (payload: unknown) => void);
21
24
  }
22
25
 
23
26
  emit<K extends EventKey>(event: K, payload: DroidEventMap[K]) {
@@ -26,7 +29,10 @@ class DroidEventBus {
26
29
  try {
27
30
  schema.parse(payload);
28
31
  } catch (err) {
29
- console.error(`[DroidEventBus] Invalid payload for ${event}:`, err);
32
+ console.error(
33
+ `[DroidEventBus] Invalid payload for ${event}:`,
34
+ err,
35
+ );
30
36
  return;
31
37
  }
32
38
  }
@@ -34,7 +40,14 @@ class DroidEventBus {
34
40
  window.dispatchEvent(new CustomEvent(event, { detail: payload }));
35
41
 
36
42
  for (const handler of this.listeners.get(event) ?? []) {
37
- (handler as EventHandler<K>)(payload);
43
+ try {
44
+ (handler as EventHandler<K>)(payload);
45
+ } catch (err) {
46
+ console.error(
47
+ `[DroidEventBus] Listener error for ${event}:`,
48
+ err,
49
+ );
50
+ }
38
51
  }
39
52
  }
40
53
 
@@ -1,5 +1,5 @@
1
- import { Builder as FlexBuilder } from 'flatbuffers/ts/flexbuffers/builder';
2
- export { toReference } from 'flatbuffers/ts/flexbuffers/reference';
1
+ import { Builder as FlexBuilder } from 'flatbuffers/js/flexbuffers/builder';
2
+ export { toReference } from 'flatbuffers/js/flexbuffers/reference';
3
3
 
4
4
  export function builder(): FlexBuilder {
5
5
  return new FlexBuilder();
@@ -1,40 +1,48 @@
1
- import type { Remote } from 'comlink'
2
- import type { LocalStorageAPI } from './db-worker'
1
+ import type { Remote } from 'comlink';
2
+ import type { LocalStorageAPI } from './db-worker';
3
3
 
4
4
  export interface InitWorkerOptions {
5
- version: string
6
- i18nPath?: string
7
- locale?: string
8
- defaults?: Record<string, any>
5
+ version: string;
6
+ i18nPath?: string;
7
+ dataPath?: string;
8
+ locale?: string;
9
+ defaults?: Record<string, string>;
9
10
  }
10
11
 
11
12
  export async function initializeWorkerDatabase(
12
13
  worker: Remote<LocalStorageAPI>,
13
14
  {
14
15
  version,
15
- i18nPath = 'https://discord.sh/i18n/db.json',
16
+ i18nPath,
17
+ dataPath,
16
18
  locale = 'en',
17
19
  defaults = {
18
20
  welcome: 'Welcome to the app!',
19
21
  },
20
- }: InitWorkerOptions
22
+ }: InitWorkerOptions,
21
23
  ) {
22
- console.log('[init-worker] Initializing database...')
24
+ console.log('[init-worker] Initializing database...');
23
25
 
24
26
  try {
25
- await worker.loadI18nFromJSON(i18nPath)
26
- console.log(`[init-worker] i18n loaded from ${i18nPath}`)
27
+ if (i18nPath) {
28
+ await worker.loadI18nFromJSON(i18nPath);
29
+ console.log(`[init-worker] i18n loaded from ${i18nPath}`);
30
+ }
27
31
 
28
- await worker.dbSet('locale', locale)
32
+ await worker.dbSet('locale', locale);
29
33
 
30
34
  for (const [key, value] of Object.entries(defaults)) {
31
- await worker.dbSet(key, value)
35
+ await worker.dbSet(key, value);
36
+ }
37
+
38
+ await worker.setVersion(version);
39
+
40
+ if (dataPath) {
41
+ void worker.loadServersFromJSON(dataPath);
32
42
  }
33
43
 
34
- await worker.setVersion(version)
35
- void worker.loadServersFromJSON(); // await worker.loadServersFromJSON();
36
- console.log(`[init-worker] DB initialized to version ${version}`)
44
+ console.log(`[init-worker] DB initialized to version ${version}`);
37
45
  } catch (err) {
38
- console.error('[init-worker] Initialization failed:', err)
46
+ console.error('[init-worker] Initialization failed:', err);
39
47
  }
40
48
  }