-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathmain.js
More file actions
219 lines (191 loc) · 8.19 KB
/
main.js
File metadata and controls
219 lines (191 loc) · 8.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import init, { Database } from '../../pkg/absurder_sql.js';
import { MultiTabDatabase } from '../multi-tab-wrapper.js';
let db;
let isLeader = false;
const app = document.getElementById('app');
// Render UI
function renderUI() {
app.innerHTML = `
<div style="max-width: 800px; margin: 50px auto; font-family: system-ui;">
<h1>Vite + AbsurderSQL Demo <span id="leaderBadge" style="font-size: 0.5em; padding: 4px 12px; border-radius: 12px; background: #gray; color: white;">...</span></h1>
<div id="status" style="padding: 10px; background: #e3f2fd; margin: 20px 0; border-radius: 4px;"></div>
<div style="margin: 20px 0;">
<button id="runTest" style="padding: 10px 20px; background: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;">Run Test</button>
<button id="clear" style="padding: 10px 20px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px;">Clear DB</button>
<button id="requestLeader" style="padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">Request Leadership</button>
</div>
<div style="padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107; margin: 20px 0;">
<strong>Multi-Tab Test:</strong> Open this page in multiple tabs. Only the leader tab can write data. Try writing from different tabs to see the coordination in action!
</div>
<pre id="output" style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 4px; overflow-x: auto;"></pre>
</div>
`;
}
function log(msg) {
const output = document.getElementById('output');
output.textContent += msg + '\n';
}
function status(msg) {
document.getElementById('status').textContent = msg;
}
async function updateLeaderStatus() {
try {
isLeader = await db.isLeader();
const badge = document.getElementById('leaderBadge');
const runBtn = document.getElementById('runTest');
const clearBtn = document.getElementById('clear');
if (isLeader) {
badge.textContent = '[LEADER]';
badge.style.background = '#28a745';
runBtn.disabled = false;
clearBtn.disabled = false;
status('This tab is the LEADER - you can write to the database');
} else {
badge.textContent = '[FOLLOWER]';
badge.style.background = '#6c757d';
runBtn.disabled = true;
clearBtn.disabled = true;
status('This tab is a FOLLOWER - read-only mode (click "Request Leadership" to become leader)');
}
} catch (err) {
console.error('Error updating leader status:', err);
}
}
async function initDB() {
status('Initializing WASM...');
await init();
// Expose Database class on window IMMEDIATELY after init
window.Database = Database;
window.__Database__ = Database; // Also expose for e2e tests
status('Creating multi-tab database...');
db = new MultiTabDatabase(Database, 'vite_example', {
autoSync: true,
waitForLeadership: false
});
// Expose db wrapper on window for testing
window.db = db;
window.__db__ = db; // Test parity with other e2e tests
await db.init();
// Create table (DDL operations allowed from any tab)
await db.execute('CREATE TABLE IF NOT EXISTS items (id INT PRIMARY KEY, name TEXT, value REAL)');
// Set up refresh callback for changes from other tabs
db.onRefresh(async () => {
log('🔄 Data changed in another tab - refreshing...');
try {
await updateLeaderStatus();
} catch (err) {
// Silently ignore borrow conflicts from broadcast messages
if (!err.message?.includes('recursive use')) {
console.error('Error in refresh callback:', err);
}
}
// Optionally reload data here
});
// Update leader status
await updateLeaderStatus();
// Update status every 2 seconds (with error handling for borrow conflicts)
setInterval(async () => {
try {
await updateLeaderStatus();
} catch (err) {
// Silently ignore borrow conflicts during polling
if (!err.message?.includes('recursive use')) {
console.error('Error updating leader status:', err);
}
}
}, 2000);
status('Ready!');
// Signal that database is fully initialized for e2e tests
window.__db__ = db;
}
async function runTest() {
document.getElementById('output').textContent = '';
try {
// Check if we're leader (fresh check, not cached)
const currentlyLeader = await db.isLeader();
if (!currentlyLeader) {
log('❌ Cannot write: This tab is not the leader');
log('Click "Request Leadership" to become leader\n');
status('⚠️ Write failed - not leader');
return;
}
log('Inserting test data...');
await db.write("INSERT INTO items VALUES (?, ?, ?)", [
{ type: 'Integer', value: 1 },
{ type: 'Text', value: 'Widget' },
{ type: 'Real', value: 19.99 }
]);
await db.write("INSERT INTO items VALUES (?, ?, ?)", [
{ type: 'Integer', value: 2 },
{ type: 'Text', value: 'Gadget' },
{ type: 'Real', value: 49.99 }
]);
await db.write("INSERT INTO items VALUES (?, ?, ?)", [
{ type: 'Integer', value: 3 },
{ type: 'Text', value: 'Tool' },
{ type: 'Real', value: 29.99 }
]);
log('✓ Inserted 3 items (auto-synced)\n');
log('Querying data...');
const result = await db.query('SELECT * FROM items');
result.rows.forEach(row => {
const id = row.values[0].value;
const name = row.values[1].value;
const value = row.values[2].value;
log(` ${id}: ${name} - $${value}`);
});
log('\n✓ Test complete! Data persists across page refreshes.');
log('Open another tab to see multi-tab coordination in action!');
status('Test passed!');
} catch (error) {
log('ERROR: ' + error.message);
status('Test failed: ' + error.message);
}
}
async function clearDB() {
try {
// Check if we're leader (fresh check, not cached)
const currentlyLeader = await db.isLeader();
if (!currentlyLeader) {
log('❌ Cannot clear: This tab is not the leader');
status('⚠️ Clear failed - not leader');
return;
}
await db.write('DROP TABLE IF EXISTS items');
await db.execute('CREATE TABLE items (id INT PRIMARY KEY, name TEXT, value REAL)');
document.getElementById('output').textContent = '';
log('✓ Database cleared');
status('Database cleared');
} catch (error) {
log('ERROR: ' + error.message);
status('Clear failed: ' + error.message);
}
}
async function requestLeadership() {
try {
log('Requesting leadership...');
await db.requestLeadership();
// Wait a bit then check once (avoid polling conflicts)
await new Promise(resolve => setTimeout(resolve, 300));
// Single check to update UI
try {
await updateLeaderStatus();
if (isLeader) {
log('✓ Leadership acquired - you can now write!');
} else {
log('⚠️ Leadership request sent (UI will update when leader)');
}
} catch (err) {
// Ignore borrow conflicts during status check
log('Leadership request sent (status will update shortly)');
}
} catch (error) {
log('ERROR: ' + error.message);
}
}
renderUI();
initDB().then(() => {
document.getElementById('runTest').addEventListener('click', runTest);
document.getElementById('clear').addEventListener('click', clearDB);
document.getElementById('requestLeader').addEventListener('click', requestLeadership);
});