@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
@@ -0,0 +1,304 @@
1
+ // Worker pool manager with round-robin routing for DB operations
2
+
3
+ import type { WorkerMessage, WorkerResponse } from './types';
4
+
5
+ export interface WorkerPoolConfig {
6
+ size: number;
7
+ workerUrl: string | URL;
8
+ }
9
+
10
+ /**
11
+ * WorkerPool: Manages a pool of workers with round-robin load balancing
12
+ *
13
+ * Features:
14
+ * - Fixed-size pool (default: 3 workers)
15
+ * - Round-robin routing for database operations
16
+ * - Request/response tracking with UUIDs
17
+ * - Automatic error handling with exponential backoff retries
18
+ */
19
+ export class WorkerPool {
20
+ private workers: Worker[] = [];
21
+ private poolSize: number;
22
+ private roundRobinIndex = 0;
23
+ private pending = new Map<
24
+ string,
25
+ {
26
+ resolve: (value: unknown) => void;
27
+ reject: (reason?: unknown) => void;
28
+ workerId: number;
29
+ }
30
+ >();
31
+ private workerUrl: string | URL;
32
+ private workerRetryCount = new Map<number, number>();
33
+ private workerRetryTimers = new Map<
34
+ number,
35
+ ReturnType<typeof setTimeout>
36
+ >();
37
+
38
+ private readonly MAX_RETRY_ATTEMPTS = 3;
39
+ private readonly BASE_RETRY_DELAY_MS = 1000;
40
+ private readonly MAX_RETRY_DELAY_MS = 8000;
41
+ private readonly REQUEST_TIMEOUT_MS = 30000;
42
+
43
+ constructor(config: WorkerPoolConfig) {
44
+ this.poolSize = config.size;
45
+ this.workerUrl = config.workerUrl;
46
+ }
47
+
48
+ /**
49
+ * Initialize the worker pool
50
+ */
51
+ async init(): Promise<void> {
52
+ console.log(
53
+ `[WorkerPool] Initializing pool with ${this.poolSize} workers`,
54
+ );
55
+
56
+ for (let i = 0; i < this.poolSize; i++) {
57
+ try {
58
+ const worker = new Worker(this.workerUrl, {
59
+ type: 'module',
60
+ name: `db-worker-${i}`,
61
+ });
62
+
63
+ worker.onmessage = (e: MessageEvent) =>
64
+ this.handleMessage(e.data, i);
65
+ worker.onerror = (err) => this.handleError(err, i);
66
+
67
+ this.workers.push(worker);
68
+ console.log(`[WorkerPool] Worker ${i} initialized`);
69
+ } catch (err) {
70
+ console.error(
71
+ `[WorkerPool] Failed to create worker ${i}:`,
72
+ err,
73
+ );
74
+ throw err;
75
+ }
76
+ }
77
+
78
+ // Wait for all workers to be ready
79
+ await Promise.all(
80
+ this.workers.map((_worker, i) =>
81
+ this.sendToWorker(i, {
82
+ id: crypto.randomUUID(),
83
+ type: 'ping',
84
+ payload: {},
85
+ }),
86
+ ),
87
+ );
88
+
89
+ console.log('[WorkerPool] All workers ready');
90
+ }
91
+
92
+ private getNextWorker(): number {
93
+ const workerId = this.roundRobinIndex;
94
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % this.poolSize;
95
+ return workerId;
96
+ }
97
+
98
+ private sendToWorker<T>(
99
+ workerId: number,
100
+ message: WorkerMessage,
101
+ ): Promise<T> {
102
+ return new Promise<T>((resolve, reject) => {
103
+ const worker = this.workers[workerId];
104
+ if (!worker) {
105
+ return reject(new Error(`Worker ${workerId} not available`));
106
+ }
107
+
108
+ const timer = setTimeout(() => {
109
+ if (this.pending.has(message.id)) {
110
+ this.pending.delete(message.id);
111
+ reject(
112
+ new Error(
113
+ `Worker ${workerId} request ${message.type} timed out after ${this.REQUEST_TIMEOUT_MS}ms`,
114
+ ),
115
+ );
116
+ }
117
+ }, this.REQUEST_TIMEOUT_MS);
118
+
119
+ this.pending.set(message.id, {
120
+ resolve: (value: unknown) => {
121
+ clearTimeout(timer);
122
+ resolve(value as T);
123
+ },
124
+ reject: (err: unknown) => {
125
+ clearTimeout(timer);
126
+ reject(err);
127
+ },
128
+ workerId,
129
+ });
130
+
131
+ try {
132
+ worker.postMessage(message);
133
+ } catch (err) {
134
+ clearTimeout(timer);
135
+ this.pending.delete(message.id);
136
+ reject(err);
137
+ }
138
+ });
139
+ }
140
+
141
+ /**
142
+ * Send message to next available worker (round-robin)
143
+ */
144
+ send<T>(type: string, payload?: unknown): Promise<T> {
145
+ const workerId = this.getNextWorker();
146
+ const id = crypto.randomUUID();
147
+ const message: WorkerMessage = { id, type, payload };
148
+
149
+ return this.sendToWorker<T>(workerId, message);
150
+ }
151
+
152
+ private handleMessage(response: WorkerResponse, workerId: number) {
153
+ const { id, ok, data, error } = response;
154
+
155
+ if (!id) {
156
+ console.warn(
157
+ `[WorkerPool] Worker ${workerId} sent message without ID:`,
158
+ response,
159
+ );
160
+ return;
161
+ }
162
+
163
+ const pending = this.pending.get(id);
164
+ if (!pending) {
165
+ console.warn(
166
+ `[WorkerPool] Received response for unknown request ${id}`,
167
+ );
168
+ return;
169
+ }
170
+
171
+ this.pending.delete(id);
172
+
173
+ if (ok) {
174
+ pending.resolve(data);
175
+ } else {
176
+ pending.reject(new Error(error || 'Worker request failed'));
177
+ }
178
+ }
179
+
180
+ private handleError(error: ErrorEvent, workerId: number) {
181
+ console.error(`[WorkerPool] Worker ${workerId} error:`, error);
182
+
183
+ const failedRequests: string[] = [];
184
+ for (const [id, pending] of this.pending.entries()) {
185
+ if (pending.workerId === workerId) {
186
+ pending.reject(
187
+ new Error(`Worker ${workerId} crashed: ${error.message}`),
188
+ );
189
+ failedRequests.push(id);
190
+ }
191
+ }
192
+
193
+ failedRequests.forEach((id) => this.pending.delete(id));
194
+
195
+ this.scheduleWorkerRecreation(workerId);
196
+ }
197
+
198
+ private scheduleWorkerRecreation(workerId: number) {
199
+ const retryCount = this.workerRetryCount.get(workerId) || 0;
200
+
201
+ if (retryCount >= this.MAX_RETRY_ATTEMPTS) {
202
+ console.error(
203
+ `[WorkerPool] Worker ${workerId} has failed ${retryCount} times. Giving up.`,
204
+ );
205
+ this.workerRetryCount.delete(workerId);
206
+ return;
207
+ }
208
+
209
+ const delay = Math.min(
210
+ this.BASE_RETRY_DELAY_MS * Math.pow(2, retryCount),
211
+ this.MAX_RETRY_DELAY_MS,
212
+ );
213
+
214
+ console.log(
215
+ `[WorkerPool] Scheduling worker ${workerId} recreation in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRY_ATTEMPTS})`,
216
+ );
217
+
218
+ const existingTimer = this.workerRetryTimers.get(workerId);
219
+ if (existingTimer) {
220
+ clearTimeout(existingTimer);
221
+ }
222
+
223
+ const timer = setTimeout(() => {
224
+ this.workerRetryTimers.delete(workerId);
225
+ this.recreateWorker(workerId);
226
+ }, delay);
227
+
228
+ this.workerRetryTimers.set(workerId, timer);
229
+ this.workerRetryCount.set(workerId, retryCount + 1);
230
+ }
231
+
232
+ private async recreateWorker(workerId: number) {
233
+ const retryCount = this.workerRetryCount.get(workerId) || 0;
234
+ console.log(
235
+ `[WorkerPool] Attempting to recreate worker ${workerId} (attempt ${retryCount}/${this.MAX_RETRY_ATTEMPTS})`,
236
+ );
237
+
238
+ try {
239
+ const worker = new Worker(this.workerUrl, {
240
+ type: 'module',
241
+ name: `db-worker-${workerId}`,
242
+ });
243
+
244
+ worker.onmessage = (e: MessageEvent) =>
245
+ this.handleMessage(e.data, workerId);
246
+ worker.onerror = (err) => this.handleError(err, workerId);
247
+
248
+ this.workers[workerId] = worker;
249
+
250
+ await this.sendToWorker(workerId, {
251
+ id: crypto.randomUUID(),
252
+ type: 'ping',
253
+ payload: {},
254
+ });
255
+
256
+ console.log(
257
+ `[WorkerPool] Worker ${workerId} recreated successfully`,
258
+ );
259
+ this.workerRetryCount.delete(workerId);
260
+ } catch (err) {
261
+ console.error(
262
+ `[WorkerPool] Failed to recreate worker ${workerId}:`,
263
+ err,
264
+ );
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Terminate all workers
270
+ */
271
+ terminate() {
272
+ console.log('[WorkerPool] Terminating all workers');
273
+
274
+ this.workerRetryTimers.forEach((timer) => clearTimeout(timer));
275
+ this.workerRetryTimers.clear();
276
+ this.workerRetryCount.clear();
277
+
278
+ this.workers.forEach((worker, i) => {
279
+ try {
280
+ worker.terminate();
281
+ } catch (err) {
282
+ console.error(
283
+ `[WorkerPool] Error terminating worker ${i}:`,
284
+ err,
285
+ );
286
+ }
287
+ });
288
+
289
+ this.workers = [];
290
+ this.pending.clear();
291
+ }
292
+
293
+ /**
294
+ * Get pool statistics
295
+ */
296
+ getStats() {
297
+ return {
298
+ size: this.poolSize,
299
+ activeWorkers: this.workers.length,
300
+ pendingRequests: this.pending.size,
301
+ nextWorker: this.roundRobinIndex,
302
+ };
303
+ }
304
+ }
@@ -0,0 +1,80 @@
1
+ // Browser capability detection for strategy selection
2
+
3
+ import type { BrowserCapabilities, StrategyType } from './types';
4
+
5
+ /**
6
+ * Detect browser capabilities for worker and communication support
7
+ */
8
+ export function detectCapabilities(): BrowserCapabilities {
9
+ // SSR guard
10
+ if (typeof window === 'undefined') {
11
+ return {
12
+ hasSharedWorker: false,
13
+ hasWorker: false,
14
+ hasBroadcastChannel: false,
15
+ isAndroid: false,
16
+ isSafari: false,
17
+ };
18
+ }
19
+
20
+ const userAgent = navigator.userAgent || '';
21
+
22
+ return {
23
+ hasSharedWorker: typeof SharedWorker !== 'undefined',
24
+ hasWorker: typeof Worker !== 'undefined',
25
+ hasBroadcastChannel: typeof BroadcastChannel !== 'undefined',
26
+ isAndroid: /Android/i.test(userAgent),
27
+ isSafari: /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent),
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Select the best strategy based on browser capabilities
33
+ *
34
+ * Priority:
35
+ * 1. SharedWorker + Worker Pool (desktop Chrome, Firefox, Edge)
36
+ * 2. WebWorker Pool only (Android, Safari, or no SharedWorker)
37
+ * 3. Direct/Main thread (emergency fallback)
38
+ */
39
+ export function selectStrategy(capabilities?: BrowserCapabilities): StrategyType {
40
+ const caps = capabilities || detectCapabilities();
41
+
42
+ // Desktop with SharedWorker support (best performance)
43
+ if (caps.hasSharedWorker && caps.hasBroadcastChannel && !caps.isAndroid) {
44
+ return 'shared-worker';
45
+ }
46
+
47
+ // Android or browsers without SharedWorker (still use worker pool)
48
+ if (caps.hasWorker && caps.hasBroadcastChannel) {
49
+ return 'web-worker';
50
+ }
51
+
52
+ // Fallback to main thread (no workers or BroadcastChannel)
53
+ return 'direct';
54
+ }
55
+
56
+ /**
57
+ * Log detected capabilities for debugging
58
+ */
59
+ export function logCapabilities(capabilities: BrowserCapabilities) {
60
+ console.log('[SupabaseGateway] Browser capabilities:', {
61
+ SharedWorker: capabilities.hasSharedWorker ? 'yes' : 'no',
62
+ Worker: capabilities.hasWorker ? 'yes' : 'no',
63
+ BroadcastChannel: capabilities.hasBroadcastChannel ? 'yes' : 'no',
64
+ Platform: capabilities.isAndroid ? 'Android' : capabilities.isSafari ? 'Safari' : 'Desktop',
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Get a human-readable description of the selected strategy
70
+ */
71
+ export function getStrategyDescription(strategy: StrategyType): string {
72
+ switch (strategy) {
73
+ case 'shared-worker':
74
+ return 'SharedWorker + DB Worker Pool (optimal for desktop)';
75
+ case 'web-worker':
76
+ return 'WebSocket Worker + DB Worker Pool (optimized for mobile)';
77
+ case 'direct':
78
+ return 'Direct/Main thread (compatibility fallback)';
79
+ }
80
+ }
@@ -0,0 +1,15 @@
1
+ // Main exports for Supabase Gateway
2
+
3
+ export { SupabaseGateway } from './SupabaseGateway';
4
+ export { WorkerPool } from './WorkerPool';
5
+ export { getWorkerCommunication, WorkerCommunication } from './WorkerCommunication';
6
+ export {
7
+ detectCapabilities,
8
+ selectStrategy,
9
+ logCapabilities,
10
+ getStrategyDescription
11
+ } from './capabilities';
12
+ export * from './types';
13
+ export { SharedWorkerStrategy } from './strategies/SharedWorkerStrategy';
14
+ export { WebWorkerStrategy } from './strategies/WebWorkerStrategy';
15
+ export { DirectStrategy } from './strategies/DirectStrategy';