@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.
Files changed (190) hide show
  1. package/droid.mjs +2312 -74
  2. package/index.d.ts +32 -0
  3. package/lib/agents/api-types.d.ts +121 -0
  4. package/lib/agents/api-types.d.ts.map +1 -0
  5. package/lib/agents/auth/init.d.ts +8 -0
  6. package/lib/agents/auth/init.d.ts.map +1 -0
  7. package/lib/agents/cache.d.ts +9 -0
  8. package/lib/agents/cache.d.ts.map +1 -0
  9. package/lib/agents/client/callGuildVault.d.ts +3 -0
  10. package/lib/agents/client/callGuildVault.d.ts.map +1 -0
  11. package/lib/agents/client/callJson.d.ts +3 -0
  12. package/lib/agents/client/callJson.d.ts.map +1 -0
  13. package/lib/agents/config.d.ts +36 -0
  14. package/lib/agents/config.d.ts.map +1 -0
  15. package/lib/agents/constants.d.ts +11 -0
  16. package/lib/agents/constants.d.ts.map +1 -0
  17. package/lib/agents/createAgents.d.ts +4 -0
  18. package/lib/agents/createAgents.d.ts.map +1 -0
  19. package/lib/agents/ctx.d.ts +46 -0
  20. package/lib/agents/ctx.d.ts.map +1 -0
  21. package/lib/agents/discord/bot.d.ts +21 -0
  22. package/lib/agents/discord/bot.d.ts.map +1 -0
  23. package/lib/agents/discord/guilds.d.ts +8 -0
  24. package/lib/agents/discord/guilds.d.ts.map +1 -0
  25. package/lib/agents/formDrafts.d.ts +5 -0
  26. package/lib/agents/formDrafts.d.ts.map +1 -0
  27. package/lib/agents/generated/agents-schema.d.ts +73 -0
  28. package/lib/agents/generated/agents-schema.d.ts.map +1 -0
  29. package/lib/agents/generated/discordsh-agents-schema.d.ts +71 -0
  30. package/lib/agents/generated/discordsh-agents-schema.d.ts.map +1 -0
  31. package/lib/agents/github/backfill.d.ts +23 -0
  32. package/lib/agents/github/backfill.d.ts.map +1 -0
  33. package/lib/agents/github/events.d.ts +10 -0
  34. package/lib/agents/github/events.d.ts.map +1 -0
  35. package/lib/agents/github/pat.d.ts +13 -0
  36. package/lib/agents/github/pat.d.ts.map +1 -0
  37. package/lib/agents/github/repoConfig.d.ts +25 -0
  38. package/lib/agents/github/repoConfig.d.ts.map +1 -0
  39. package/lib/agents/github/tokens.d.ts +22 -0
  40. package/lib/agents/github/tokens.d.ts.map +1 -0
  41. package/lib/agents/github/webhook.d.ts +37 -0
  42. package/lib/agents/github/webhook.d.ts.map +1 -0
  43. package/lib/agents/index.d.ts +8 -0
  44. package/lib/agents/index.d.ts.map +1 -0
  45. package/lib/agents/state/atoms.d.ts +122 -0
  46. package/lib/agents/state/atoms.d.ts.map +1 -0
  47. package/lib/agents/types.d.ts +65 -0
  48. package/lib/agents/types.d.ts.map +1 -0
  49. package/lib/gateway/SupabaseGateway.d.ts +38 -0
  50. package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
  51. package/lib/gateway/WorkerCommunication.d.ts +39 -0
  52. package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
  53. package/lib/gateway/WorkerPool.d.ts +55 -0
  54. package/lib/gateway/WorkerPool.d.ts.map +1 -0
  55. package/lib/gateway/capabilities.d.ts +23 -0
  56. package/lib/gateway/capabilities.d.ts.map +1 -0
  57. package/lib/gateway/index.d.ts +9 -0
  58. package/lib/gateway/index.d.ts.map +1 -0
  59. package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
  60. package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
  61. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
  62. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
  63. package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
  64. package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
  65. package/lib/gateway/types.d.ts +81 -0
  66. package/lib/gateway/types.d.ts.map +1 -0
  67. package/lib/workers/canvas-ui-renderer.d.ts +25 -0
  68. package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
  69. package/lib/workers/canvas-worker.d.ts +14 -0
  70. package/lib/workers/canvas-worker.d.ts.map +1 -0
  71. package/lib/workers/canvas-worker.js +5 -302
  72. package/lib/workers/data.d.ts +40 -0
  73. package/lib/workers/data.d.ts.map +1 -0
  74. package/lib/workers/db-worker.d.ts +69 -0
  75. package/lib/workers/db-worker.d.ts.map +1 -0
  76. package/lib/workers/db-worker.js +13 -3636
  77. package/lib/workers/events.d.ts +11 -0
  78. package/lib/workers/events.d.ts.map +1 -0
  79. package/lib/workers/flexbuilder.d.ts +4 -0
  80. package/lib/workers/flexbuilder.d.ts.map +1 -0
  81. package/lib/workers/init.d.ts +11 -0
  82. package/lib/workers/init.d.ts.map +1 -0
  83. package/lib/workers/main.d.ts +105 -0
  84. package/lib/workers/main.d.ts.map +1 -0
  85. package/lib/workers/supabase-db-worker.d.ts +2 -0
  86. package/lib/workers/supabase-db-worker.d.ts.map +1 -0
  87. package/lib/workers/supabase-db-worker.js +23 -0
  88. package/lib/workers/supabase-shared-worker.d.ts +2 -0
  89. package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
  90. package/lib/workers/supabase-shared-worker.js +25 -0
  91. package/lib/workers/tools.d.ts +4 -0
  92. package/lib/workers/tools.d.ts.map +1 -0
  93. package/lib/workers/validate.d.ts +15 -0
  94. package/lib/workers/validate.d.ts.map +1 -0
  95. package/lib/workers/ws-worker.d.ts +10 -0
  96. package/lib/workers/ws-worker.d.ts.map +1 -0
  97. package/lib/workers/ws-worker.js +5 -273
  98. package/main.js +5711 -0
  99. package/package.json +18 -6
  100. package/src/index.ts +41 -13
  101. package/src/lib/agents/api-types.ts +170 -0
  102. package/src/lib/agents/auth/init.ts +200 -0
  103. package/src/lib/agents/cache.ts +127 -0
  104. package/src/lib/agents/client/callGuildVault.ts +71 -0
  105. package/src/lib/agents/client/callJson.ts +64 -0
  106. package/src/lib/agents/config.ts +83 -0
  107. package/src/lib/agents/constants.ts +11 -0
  108. package/src/lib/agents/createAgents.ts +62 -0
  109. package/src/lib/agents/ctx.ts +63 -0
  110. package/src/lib/agents/discord/bot.ts +169 -0
  111. package/src/lib/agents/discord/guilds.ts +106 -0
  112. package/src/lib/agents/formDrafts.ts +55 -0
  113. package/src/lib/agents/generated/agents-schema.ts +155 -0
  114. package/src/lib/agents/generated/discordsh-agents-schema.ts +96 -0
  115. package/src/lib/agents/github/backfill.ts +126 -0
  116. package/src/lib/agents/github/events.ts +131 -0
  117. package/src/lib/agents/github/pat.ts +150 -0
  118. package/src/lib/agents/github/repoConfig.ts +240 -0
  119. package/src/lib/agents/github/tokens.ts +222 -0
  120. package/src/lib/agents/github/webhook.ts +349 -0
  121. package/src/lib/agents/index.ts +35 -0
  122. package/src/lib/agents/state/atoms.ts +122 -0
  123. package/src/lib/agents/types.ts +83 -0
  124. package/src/lib/api.ts +71 -0
  125. package/src/lib/droid.spec.ts +14 -19
  126. package/src/lib/droid.ts +48 -4
  127. package/src/lib/gateway/SupabaseGateway.ts +236 -0
  128. package/src/lib/gateway/WorkerCommunication.ts +132 -0
  129. package/src/lib/gateway/WorkerPool.ts +304 -0
  130. package/src/lib/gateway/capabilities.spec.ts +144 -0
  131. package/src/lib/gateway/capabilities.ts +80 -0
  132. package/src/lib/gateway/index.ts +15 -0
  133. package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
  134. package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
  135. package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
  136. package/src/lib/gateway/types.ts +139 -0
  137. package/src/lib/mod/mod-manager.ts +13 -8
  138. package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
  139. package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
  140. package/src/lib/mod/module/supabase/mod-supabase.ts +73 -0
  141. package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
  142. package/src/lib/state/auth.spec.ts +58 -0
  143. package/src/lib/state/auth.ts +148 -0
  144. package/src/lib/state/bento.spec.ts +76 -0
  145. package/src/lib/state/bento.ts +51 -0
  146. package/src/lib/state/index.ts +98 -0
  147. package/src/lib/state/overlay-manager.ts +125 -0
  148. package/src/lib/state/profile-sync.ts +214 -0
  149. package/src/lib/state/profile.ts +155 -0
  150. package/src/lib/state/router.ts +24 -0
  151. package/src/lib/state/staff.ts +128 -0
  152. package/src/lib/state/sync-bus.ts +109 -0
  153. package/src/lib/state/theme-sync.ts +75 -0
  154. package/src/lib/state/toasts.spec.ts +106 -0
  155. package/src/lib/state/toasts.ts +34 -0
  156. package/src/lib/state/ui.spec.ts +148 -0
  157. package/src/lib/state/ui.ts +45 -0
  158. package/src/lib/state/welcome-toast.spec.ts +101 -0
  159. package/src/lib/state/welcome-toast.ts +76 -0
  160. package/src/lib/sw-recovery.spec.ts +165 -0
  161. package/src/lib/sw-recovery.ts +141 -0
  162. package/src/lib/types/bento.spec.ts +116 -0
  163. package/src/lib/types/bento.ts +94 -55
  164. package/src/lib/types/event-schemas.spec.ts +120 -0
  165. package/src/lib/types/event-types.spec.ts +96 -0
  166. package/src/lib/types/event-types.ts +113 -3
  167. package/src/lib/types/modules.ts +12 -5
  168. package/src/lib/types/ui-event-types.ts +66 -0
  169. package/src/lib/workers/canvas-ui-renderer.ts +266 -0
  170. package/src/lib/workers/canvas-worker.ts +94 -9
  171. package/src/lib/workers/data.ts +64 -37
  172. package/src/lib/workers/db-worker.ts +105 -32
  173. package/src/lib/workers/events.spec.ts +208 -0
  174. package/src/lib/workers/events.ts +27 -14
  175. package/src/lib/workers/flexbuilder.ts +2 -2
  176. package/src/lib/workers/init.ts +25 -17
  177. package/src/lib/workers/main.ts +506 -327
  178. package/src/lib/workers/supabase-db-worker.ts +243 -0
  179. package/src/lib/workers/supabase-shared-worker.ts +607 -0
  180. package/src/lib/workers/validate.ts +64 -0
  181. package/src/lib/workers/ws-worker.spec.ts +41 -0
  182. package/src/lib/workers/ws-worker.ts +255 -31
  183. package/src/setup-vitest.ts +4 -2
  184. package/src/types.ts +30 -0
  185. package/src/vite-env.d.ts +1 -0
  186. package/workers/main.js +2 -1125
  187. package/comlink.js +0 -253
  188. package/index.js +0 -2972
  189. package/reference.js +0 -259
  190. package/src/types.d.ts +0 -31
@@ -0,0 +1,278 @@
1
+ // Strategy for desktop browsers with SharedWorker support
2
+
3
+ import type {
4
+ ISupabaseStrategy,
5
+ SelectOptions,
6
+ SessionResponse,
7
+ WebSocketStatus,
8
+ } from '../types';
9
+ import { WorkerPool } from '../WorkerPool';
10
+ import { getWorkerCommunication } from '../WorkerCommunication';
11
+
12
+ /** Union of all possible messages arriving on the SharedWorker port. */
13
+ interface SharedWorkerEvent {
14
+ id?: string;
15
+ type?: string;
16
+ ok?: boolean;
17
+ data?: unknown;
18
+ error?: string;
19
+ key?: string;
20
+ payload?: unknown;
21
+ }
22
+
23
+ /**
24
+ * SharedWorkerStrategy: Uses SharedWorker for WebSocket + DB Worker Pool
25
+ *
26
+ * Architecture:
27
+ * - SharedWorker handles WebSocket, auth, and realtime (cross-tab sharing)
28
+ * - Worker pool (3 workers) handles database operations (parallel execution)
29
+ * - BroadcastChannel connects SharedWorker to worker pool
30
+ *
31
+ * Best for: Desktop Chrome, Firefox, Edge
32
+ */
33
+ export class SharedWorkerStrategy implements ISupabaseStrategy {
34
+ private worker?: SharedWorker;
35
+ private port?: MessagePort;
36
+ private workerPool?: WorkerPool;
37
+ private pending = new Map<
38
+ string,
39
+ {
40
+ resolve: (value: unknown) => void;
41
+ reject: (reason?: unknown) => void;
42
+ }
43
+ >();
44
+ private listeners = new Map<string, Set<(p: unknown) => void>>();
45
+ private comm = getWorkerCommunication();
46
+ private sharedWorkerUrl: string | URL;
47
+ private dbWorkerUrl: string | URL;
48
+ private poolSize: number;
49
+
50
+ constructor(
51
+ sharedWorkerUrl: string | URL,
52
+ dbWorkerUrl: string | URL,
53
+ poolSize = 3,
54
+ ) {
55
+ this.sharedWorkerUrl = sharedWorkerUrl;
56
+ this.dbWorkerUrl = dbWorkerUrl;
57
+ this.poolSize = poolSize;
58
+
59
+ if (typeof window === 'undefined') return;
60
+ if (!('SharedWorker' in window)) {
61
+ throw new Error('SharedWorker not supported');
62
+ }
63
+ }
64
+
65
+ async init(
66
+ url: string,
67
+ anonKey: string,
68
+ options?: Record<string, unknown>,
69
+ ): Promise<SessionResponse> {
70
+ console.log('[SharedWorkerStrategy] Initializing...');
71
+
72
+ this.worker = new SharedWorker(this.sharedWorkerUrl, {
73
+ type: 'module',
74
+ name: 'supabase-shared',
75
+ });
76
+ this.port = this.worker.port;
77
+ this.port.onmessage = (e: MessageEvent<SharedWorkerEvent>) =>
78
+ this.onMessage(e.data);
79
+ this.port.start();
80
+
81
+ this.workerPool = new WorkerPool({
82
+ size: this.poolSize,
83
+ workerUrl: this.dbWorkerUrl,
84
+ });
85
+ await this.workerPool.init();
86
+
87
+ const sharedWorkerInit = this.send<SessionResponse>('init', {
88
+ url,
89
+ anonKey,
90
+ options,
91
+ });
92
+
93
+ const poolInitPromises = Array.from({ length: this.poolSize }, () =>
94
+ this.workerPool?.send('init', { url, anonKey, options }),
95
+ );
96
+
97
+ await Promise.all([sharedWorkerInit, ...poolInitPromises]);
98
+
99
+ console.log(
100
+ `[SharedWorkerStrategy] Initialized (SharedWorker + ${this.poolSize} DB workers)`,
101
+ );
102
+
103
+ return sharedWorkerInit;
104
+ }
105
+
106
+ private onMessage(msg: SharedWorkerEvent) {
107
+ if (msg?.type === 'ready' || msg?.type === 'auth') {
108
+ this.emit(msg.type, msg);
109
+ return;
110
+ }
111
+ if (msg?.type === 'realtime' && msg.key) {
112
+ this.emit(`realtime:${msg.key}`, msg.payload);
113
+ return;
114
+ }
115
+ if (msg?.type === 'ws.message') {
116
+ this.emit('ws.message', msg.data);
117
+ return;
118
+ }
119
+ if (msg?.type === 'ws.status') {
120
+ this.emit('ws.status', msg);
121
+ return;
122
+ }
123
+
124
+ const { id, ok, data, error } = msg ?? {};
125
+ if (id && this.pending.has(id)) {
126
+ const pending = this.pending.get(id);
127
+ if (!pending) return;
128
+ const { resolve, reject } = pending;
129
+ this.pending.delete(id);
130
+ ok ? resolve(data) : reject(new Error(error));
131
+ }
132
+ }
133
+
134
+ private send<T>(type: string, payload?: unknown): Promise<T> {
135
+ const id = crypto.randomUUID();
136
+ return new Promise<T>((resolve, reject) => {
137
+ if (!this.port)
138
+ return reject(new Error('SharedWorker unavailable'));
139
+ this.pending.set(id, {
140
+ resolve: resolve as (value: unknown) => void,
141
+ reject,
142
+ });
143
+ this.port.postMessage({ id, type, payload });
144
+ });
145
+ }
146
+
147
+ on(event: string, callback: (payload: unknown) => void): () => void {
148
+ if (!this.listeners.has(event)) {
149
+ this.listeners.set(event, new Set());
150
+ }
151
+ this.listeners.get(event)?.add(callback);
152
+
153
+ return () => {
154
+ const set = this.listeners.get(event);
155
+ if (set) {
156
+ set.delete(callback);
157
+ if (set.size === 0) this.listeners.delete(event);
158
+ }
159
+ };
160
+ }
161
+
162
+ private emit(event: string, payload: unknown) {
163
+ this.listeners.get(event)?.forEach((cb) => cb(payload));
164
+ }
165
+
166
+ // Auth (via SharedWorker)
167
+ getSession(): Promise<SessionResponse> {
168
+ return this.send<SessionResponse>('getSession');
169
+ }
170
+
171
+ signInWithPassword(
172
+ email: string,
173
+ password: string,
174
+ ): Promise<SessionResponse> {
175
+ return this.send<SessionResponse>('signInWithPassword', {
176
+ email,
177
+ password,
178
+ });
179
+ }
180
+
181
+ signOut(): Promise<void> {
182
+ return this.send('signOut');
183
+ }
184
+
185
+ // Database (via worker pool)
186
+ select(table: string, opts?: SelectOptions): Promise<unknown[]> {
187
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
188
+ return this.workerPool.send('from.select', { table, ...(opts ?? {}) });
189
+ }
190
+
191
+ insert(
192
+ table: string,
193
+ data: Record<string, unknown> | Record<string, unknown>[],
194
+ ): Promise<unknown[]> {
195
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
196
+ return this.workerPool.send('from.insert', { table, data });
197
+ }
198
+
199
+ update(
200
+ table: string,
201
+ data: Record<string, unknown>,
202
+ match: Record<string, unknown>,
203
+ ): Promise<unknown[]> {
204
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
205
+ return this.workerPool.send('from.update', { table, data, match });
206
+ }
207
+
208
+ upsert(
209
+ table: string,
210
+ data: Record<string, unknown> | Record<string, unknown>[],
211
+ ): Promise<unknown[]> {
212
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
213
+ return this.workerPool.send('from.upsert', { table, data });
214
+ }
215
+
216
+ delete(table: string, match: Record<string, unknown>): Promise<unknown[]> {
217
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
218
+ return this.workerPool.send('from.delete', { table, match });
219
+ }
220
+
221
+ rpc(fn: string, args?: Record<string, unknown>): Promise<unknown> {
222
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
223
+ return this.workerPool.send('rpc', { fn, args });
224
+ }
225
+
226
+ // Realtime (via SharedWorker)
227
+ subscribePostgres(
228
+ key: string,
229
+ params: Record<string, unknown>,
230
+ callback: (payload: unknown) => void,
231
+ ): () => void {
232
+ const off = this.on(`realtime:${key}`, callback);
233
+ this.send('realtime.subscribe', { key, params }).catch((e) => {
234
+ off();
235
+ console.error(e);
236
+ });
237
+ return () => {
238
+ off();
239
+ this.send('realtime.unsubscribe', { key }).catch(() => {
240
+ /* best-effort */
241
+ });
242
+ };
243
+ }
244
+
245
+ // WebSocket (via SharedWorker)
246
+ connectWebSocket(wsUrl?: string): Promise<WebSocketStatus> {
247
+ return this.send<WebSocketStatus>('ws.connect', { wsUrl });
248
+ }
249
+
250
+ disconnectWebSocket(): Promise<void> {
251
+ return this.send('ws.disconnect');
252
+ }
253
+
254
+ sendWebSocketMessage(data: unknown): Promise<void> {
255
+ return this.send('ws.send', { data });
256
+ }
257
+
258
+ getWebSocketStatus(): Promise<WebSocketStatus> {
259
+ return this.send<WebSocketStatus>('ws.status');
260
+ }
261
+
262
+ onWebSocketMessage(callback: (message: unknown) => void): () => void {
263
+ return this.on('ws.message', callback);
264
+ }
265
+
266
+ onWebSocketStatus(callback: (status: WebSocketStatus) => void): () => void {
267
+ return this.on('ws.status', (payload) =>
268
+ callback(payload as WebSocketStatus),
269
+ );
270
+ }
271
+
272
+ terminate() {
273
+ this.workerPool?.terminate();
274
+ this.comm.close();
275
+ this.listeners.clear();
276
+ this.pending.clear();
277
+ }
278
+ }
@@ -0,0 +1,278 @@
1
+ // Strategy for browsers without SharedWorker (Android, Safari)
2
+
3
+ import type {
4
+ BroadcastEvent,
5
+ ISupabaseStrategy,
6
+ SelectOptions,
7
+ SessionResponse,
8
+ WebSocketStatus,
9
+ } from '../types';
10
+ import { WorkerPool } from '../WorkerPool';
11
+ import { getWorkerCommunication } from '../WorkerCommunication';
12
+
13
+ /**
14
+ * WebWorkerStrategy: Uses dedicated WebSocket worker + DB Worker Pool
15
+ *
16
+ * Architecture:
17
+ * - Dedicated WebSocket worker handles WebSocket, auth, and realtime (no cross-tab)
18
+ * - Worker pool (3 workers) handles database operations (parallel execution)
19
+ * - BroadcastChannel connects WebSocket worker to worker pool
20
+ *
21
+ * Best for: Android, Safari, browsers without SharedWorker
22
+ */
23
+ export class WebWorkerStrategy implements ISupabaseStrategy {
24
+ private wsWorker?: Worker;
25
+ private workerPool?: WorkerPool;
26
+ private pending = new Map<
27
+ string,
28
+ {
29
+ resolve: (value: unknown) => void;
30
+ reject: (reason?: unknown) => void;
31
+ }
32
+ >();
33
+ private listeners = new Map<string, Set<(p: unknown) => void>>();
34
+ private comm = getWorkerCommunication();
35
+ private wsWorkerUrl: string | URL;
36
+ private dbWorkerUrl: string | URL;
37
+ private poolSize: number;
38
+
39
+ constructor(
40
+ wsWorkerUrl: string | URL,
41
+ dbWorkerUrl: string | URL,
42
+ poolSize = 3,
43
+ ) {
44
+ this.wsWorkerUrl = wsWorkerUrl;
45
+ this.dbWorkerUrl = dbWorkerUrl;
46
+ this.poolSize = poolSize;
47
+
48
+ if (typeof window === 'undefined') return;
49
+ if (!('Worker' in window)) {
50
+ throw new Error('Worker not supported');
51
+ }
52
+ }
53
+
54
+ async init(
55
+ url: string,
56
+ anonKey: string,
57
+ options?: Record<string, unknown>,
58
+ ): Promise<SessionResponse> {
59
+ console.log('[WebWorkerStrategy] Initializing...');
60
+
61
+ this.wsWorker = new Worker(this.wsWorkerUrl, {
62
+ type: 'module',
63
+ name: 'websocket-worker',
64
+ });
65
+ this.wsWorker.onmessage = (e) => this.onMessage(e.data);
66
+ this.wsWorker.onerror = (err) =>
67
+ console.error('[WebWorkerStrategy] WebSocket worker error:', err);
68
+
69
+ this.workerPool = new WorkerPool({
70
+ size: this.poolSize,
71
+ workerUrl: this.dbWorkerUrl,
72
+ });
73
+ await this.workerPool.init();
74
+
75
+ const wsWorkerInit = this.send<SessionResponse>('init', {
76
+ url,
77
+ anonKey,
78
+ options,
79
+ });
80
+
81
+ const poolInitPromises = Array.from({ length: this.poolSize }, () =>
82
+ this.workerPool?.send('init', { url, anonKey, options }),
83
+ );
84
+
85
+ await Promise.all([wsWorkerInit, ...poolInitPromises]);
86
+
87
+ console.log(
88
+ `[WebWorkerStrategy] Initialized (WebSocket worker + ${this.poolSize} DB workers)`,
89
+ );
90
+
91
+ return wsWorkerInit;
92
+ }
93
+
94
+ private onMessage(msg: unknown) {
95
+ const message = msg as
96
+ | (BroadcastEvent & {
97
+ id?: string;
98
+ ok?: boolean;
99
+ data?: unknown;
100
+ error?: string;
101
+ key?: string;
102
+ })
103
+ | null
104
+ | undefined;
105
+ if (message?.type === 'ready' || message?.type === 'auth') {
106
+ this.emit(message.type, message);
107
+ return;
108
+ }
109
+ if (message?.type === 'realtime' && message.key) {
110
+ this.emit(`realtime:${message.key}`, message.payload);
111
+ return;
112
+ }
113
+ if (message?.type === 'ws.message') {
114
+ this.emit('ws.message', message.data);
115
+ return;
116
+ }
117
+ if (message?.type === 'ws.status') {
118
+ this.emit('ws.status', message);
119
+ return;
120
+ }
121
+
122
+ const id = message?.id;
123
+ const ok = message?.ok;
124
+ const data = message?.data;
125
+ const error = message?.error;
126
+ if (id && this.pending.has(id)) {
127
+ const pending = this.pending.get(id);
128
+ if (!pending) return;
129
+ const { resolve, reject } = pending;
130
+ this.pending.delete(id);
131
+ ok ? resolve(data) : reject(new Error(error));
132
+ }
133
+ }
134
+
135
+ private send<T>(type: string, payload?: unknown): Promise<T> {
136
+ const id = crypto.randomUUID();
137
+ return new Promise<T>((resolve, reject) => {
138
+ if (!this.wsWorker)
139
+ return reject(new Error('WebSocket worker unavailable'));
140
+ this.pending.set(id, {
141
+ resolve: resolve as (value: unknown) => void,
142
+ reject,
143
+ });
144
+ this.wsWorker.postMessage({ id, type, payload });
145
+ });
146
+ }
147
+
148
+ on(event: string, callback: (payload: unknown) => void): () => void {
149
+ if (!this.listeners.has(event)) {
150
+ this.listeners.set(event, new Set());
151
+ }
152
+ this.listeners.get(event)?.add(callback);
153
+
154
+ return () => {
155
+ const set = this.listeners.get(event);
156
+ if (set) {
157
+ set.delete(callback);
158
+ if (set.size === 0) this.listeners.delete(event);
159
+ }
160
+ };
161
+ }
162
+
163
+ private emit(event: string, payload: unknown) {
164
+ this.listeners.get(event)?.forEach((cb) => cb(payload));
165
+ }
166
+
167
+ // Auth (via WebSocket worker)
168
+ getSession(): Promise<SessionResponse> {
169
+ return this.send<SessionResponse>('getSession');
170
+ }
171
+
172
+ signInWithPassword(
173
+ email: string,
174
+ password: string,
175
+ ): Promise<SessionResponse> {
176
+ return this.send<SessionResponse>('signInWithPassword', {
177
+ email,
178
+ password,
179
+ });
180
+ }
181
+
182
+ signOut(): Promise<void> {
183
+ return this.send('signOut');
184
+ }
185
+
186
+ // Database (via worker pool)
187
+ select(table: string, opts?: SelectOptions): Promise<unknown[]> {
188
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
189
+ return this.workerPool.send('from.select', { table, ...(opts ?? {}) });
190
+ }
191
+
192
+ insert(
193
+ table: string,
194
+ data: Record<string, unknown> | Record<string, unknown>[],
195
+ ): Promise<unknown[]> {
196
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
197
+ return this.workerPool.send('from.insert', { table, data });
198
+ }
199
+
200
+ update(
201
+ table: string,
202
+ data: Record<string, unknown>,
203
+ match: Record<string, unknown>,
204
+ ): Promise<unknown[]> {
205
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
206
+ return this.workerPool.send('from.update', { table, data, match });
207
+ }
208
+
209
+ upsert(
210
+ table: string,
211
+ data: Record<string, unknown> | Record<string, unknown>[],
212
+ ): Promise<unknown[]> {
213
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
214
+ return this.workerPool.send('from.upsert', { table, data });
215
+ }
216
+
217
+ delete(table: string, match: Record<string, unknown>): Promise<unknown[]> {
218
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
219
+ return this.workerPool.send('from.delete', { table, match });
220
+ }
221
+
222
+ rpc(fn: string, args?: Record<string, unknown>): Promise<unknown> {
223
+ if (!this.workerPool) throw new Error('Worker pool not initialized');
224
+ return this.workerPool.send('rpc', { fn, args });
225
+ }
226
+
227
+ // Realtime (via WebSocket worker)
228
+ subscribePostgres(
229
+ key: string,
230
+ params: Record<string, unknown>,
231
+ callback: (payload: unknown) => void,
232
+ ): () => void {
233
+ const off = this.on(`realtime:${key}`, callback);
234
+ this.send('realtime.subscribe', { key, params }).catch((e) => {
235
+ off();
236
+ console.error(e);
237
+ });
238
+ return () => {
239
+ off();
240
+ this.send('realtime.unsubscribe', { key }).catch(() => {
241
+ /* best-effort */
242
+ });
243
+ };
244
+ }
245
+
246
+ // WebSocket (via dedicated worker)
247
+ connectWebSocket(wsUrl?: string): Promise<WebSocketStatus> {
248
+ return this.send<WebSocketStatus>('ws.connect', { wsUrl });
249
+ }
250
+
251
+ disconnectWebSocket(): Promise<void> {
252
+ return this.send('ws.disconnect');
253
+ }
254
+
255
+ sendWebSocketMessage(data: unknown): Promise<void> {
256
+ return this.send('ws.send', { data });
257
+ }
258
+
259
+ getWebSocketStatus(): Promise<WebSocketStatus> {
260
+ return this.send<WebSocketStatus>('ws.status');
261
+ }
262
+
263
+ onWebSocketMessage(callback: (message: unknown) => void): () => void {
264
+ return this.on('ws.message', callback);
265
+ }
266
+
267
+ onWebSocketStatus(callback: (status: WebSocketStatus) => void): () => void {
268
+ return this.on('ws.status', callback as (payload: unknown) => void);
269
+ }
270
+
271
+ terminate() {
272
+ this.wsWorker?.terminate();
273
+ this.workerPool?.terminate();
274
+ this.comm.close();
275
+ this.listeners.clear();
276
+ this.pending.clear();
277
+ }
278
+ }
@@ -0,0 +1,139 @@
1
+ // Shared types for all gateway strategies
2
+
3
+ export interface SupabaseConfig {
4
+ url: string;
5
+ anonKey: string;
6
+ options?: Record<string, unknown>;
7
+ }
8
+
9
+ export interface Session {
10
+ access_token: string;
11
+ refresh_token: string;
12
+ user: Record<string, unknown>;
13
+ expires_at?: number;
14
+ }
15
+
16
+ export interface SessionResponse {
17
+ session: Session | null;
18
+ user?: Record<string, unknown>;
19
+ }
20
+
21
+ export interface WebSocketStatus {
22
+ status: 'connected' | 'disconnected' | 'connecting' | 'error';
23
+ readyState: number | null;
24
+ error?: string;
25
+ }
26
+
27
+ // Unified API that all strategies must implement
28
+ export interface ISupabaseStrategy {
29
+ // Lifecycle
30
+ init(
31
+ url: string,
32
+ anonKey: string,
33
+ options?: Record<string, unknown>,
34
+ ): Promise<SessionResponse>;
35
+
36
+ // Event handling
37
+ on(event: string, callback: (payload: unknown) => void): () => void;
38
+
39
+ // Auth
40
+ getSession(): Promise<SessionResponse>;
41
+ signInWithPassword(
42
+ email: string,
43
+ password: string,
44
+ ): Promise<SessionResponse>;
45
+ signOut(): Promise<void>;
46
+
47
+ // Database
48
+ select(table: string, opts?: SelectOptions): Promise<unknown[]>;
49
+ insert(
50
+ table: string,
51
+ data: Record<string, unknown> | Record<string, unknown>[],
52
+ ): Promise<unknown[]>;
53
+ update(
54
+ table: string,
55
+ data: Record<string, unknown>,
56
+ match: Record<string, unknown>,
57
+ ): Promise<unknown[]>;
58
+ upsert(
59
+ table: string,
60
+ data: Record<string, unknown> | Record<string, unknown>[],
61
+ ): Promise<unknown[]>;
62
+ delete(table: string, match: Record<string, unknown>): Promise<unknown[]>;
63
+ rpc(fn: string, args?: Record<string, unknown>): Promise<unknown>;
64
+
65
+ // Realtime
66
+ subscribePostgres(
67
+ key: string,
68
+ params: Record<string, unknown>,
69
+ callback: (payload: unknown) => void,
70
+ ): () => void;
71
+
72
+ // WebSocket
73
+ connectWebSocket(wsUrl?: string): Promise<WebSocketStatus>;
74
+ disconnectWebSocket(): Promise<void>;
75
+ sendWebSocketMessage(data: unknown): Promise<void>;
76
+ getWebSocketStatus(): Promise<WebSocketStatus>;
77
+ onWebSocketMessage(callback: (message: unknown) => void): () => void;
78
+ onWebSocketStatus(callback: (status: WebSocketStatus) => void): () => void;
79
+
80
+ // Cleanup
81
+ terminate(): void;
82
+ }
83
+
84
+ export interface SelectOptions {
85
+ columns?: string;
86
+ match?: Record<string, unknown>;
87
+ limit?: number;
88
+ }
89
+
90
+ // Worker communication messages
91
+ export interface WorkerMessage {
92
+ id: string;
93
+ type: string;
94
+ payload?: unknown;
95
+ }
96
+
97
+ export interface WorkerResponse {
98
+ id: string;
99
+ ok: boolean;
100
+ data?: unknown;
101
+ error?: string;
102
+ }
103
+
104
+ // BroadcastChannel event types
105
+ export interface BroadcastEvent {
106
+ type:
107
+ | 'auth'
108
+ | 'ws.message'
109
+ | 'ws.status'
110
+ | 'realtime'
111
+ | 'ready'
112
+ | 'worker-error';
113
+ data?: unknown;
114
+ key?: string;
115
+ payload?: unknown;
116
+ }
117
+
118
+ // Strategy types
119
+ export type StrategyType = 'shared-worker' | 'web-worker' | 'direct';
120
+
121
+ // Browser capabilities
122
+ export interface BrowserCapabilities {
123
+ hasSharedWorker: boolean;
124
+ hasWorker: boolean;
125
+ hasBroadcastChannel: boolean;
126
+ isAndroid: boolean;
127
+ isSafari: boolean;
128
+ }
129
+
130
+ // Gateway configuration
131
+ export interface GatewayConfig {
132
+ workerUrls?: {
133
+ sharedWorker?: string | URL;
134
+ dbWorker?: string | URL;
135
+ webSocketWorker?: string | URL;
136
+ };
137
+ forceStrategy?: StrategyType;
138
+ poolSize?: number;
139
+ }