@abloatai/humans 0.66.5 → 0.66.6

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.
@@ -141,6 +141,11 @@ export class Database {
141
141
  }
142
142
  }
143
143
  async open(identity, version = 1) {
144
+ if (this.isClosing && this.currentDbInfo && !this.workspaceDb) {
145
+ throw new AbloConnectionError('IndexedDB connection closed; recreate the client', {
146
+ code: 'db_not_opened',
147
+ });
148
+ }
144
149
  this.isClosing = false;
145
150
  if (this.workspaceDb && this.currentDbInfo) {
146
151
  if (!persistenceIdentityMatches(this.currentDbInfo, identity)) {
@@ -177,6 +182,16 @@ export class Database {
177
182
  this.workspaceDb = await this.databaseManager.openWorkspaceDatabase(this.currentDbInfo, async (db) => {
178
183
  await this.storeManager.createStores(db);
179
184
  });
185
+ const openedDb = this.workspaceDb;
186
+ const connectionClosed = () => {
187
+ if (this.workspaceDb !== openedDb)
188
+ return;
189
+ this.workspaceDb = null;
190
+ this.isClosing = true;
191
+ this.storeManager.markAllStoresAsClosing();
192
+ };
193
+ openedDb.addEventListener('versionchange', connectionClosed);
194
+ openedDb.addEventListener('close', connectionClosed);
180
195
  // Initialize stores
181
196
  await this.storeManager.initializeStores(this.workspaceDb);
182
197
  // A live group addition can have widened a previous client's replica after
@@ -119,10 +119,8 @@ export function AbloProvider(props) {
119
119
  if (typeof window === 'undefined')
120
120
  return;
121
121
  const handler = (event) => {
122
- // Best-effort IDB flush on TAB CLOSE — the client is going away with the
123
- // page regardless. This is NOT an unmount teardown: the consumer owns the
124
- // client's lifecycle and the provider never disposes it on unmount.
125
- void engine.dispose();
122
+ // The user may cancel navigation. Keep the client and its IndexedDB
123
+ // connection alive until the page actually goes away.
126
124
  if (preventUnsavedChanges && engine._store.hasUnsyncedChanges) {
127
125
  event.preventDefault();
128
126
  event.returnValue = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/humans",
3
- "version": "0.66.5",
3
+ "version": "0.66.6",
4
4
  "description": "The optional human-facing local-state package for Ablo: presence, live queries, and React bindings.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -85,7 +85,7 @@
85
85
  "directory": "packages/humans"
86
86
  },
87
87
  "dependencies": {
88
- "@abloatai/transaction": "0.66.5",
88
+ "@abloatai/transaction": "0.66.6",
89
89
  "events": "^3.3.0",
90
90
  "mobx": "^6.13.7",
91
91
  "uuid": "^11.1.0",
@@ -223,6 +223,11 @@ export class Database {
223
223
  }
224
224
 
225
225
  async open(identity: PersistenceIdentity, version = 1): Promise<void> {
226
+ if (this.isClosing && this.currentDbInfo && !this.workspaceDb) {
227
+ throw new AbloConnectionError('IndexedDB connection closed; recreate the client', {
228
+ code: 'db_not_opened',
229
+ });
230
+ }
226
231
  this.isClosing = false;
227
232
 
228
233
  if (this.workspaceDb && this.currentDbInfo) {
@@ -281,6 +286,15 @@ export class Database {
281
286
  await this.storeManager.createStores(db);
282
287
  }
283
288
  );
289
+ const openedDb = this.workspaceDb;
290
+ const connectionClosed = () => {
291
+ if (this.workspaceDb !== openedDb) return;
292
+ this.workspaceDb = null;
293
+ this.isClosing = true;
294
+ this.storeManager.markAllStoresAsClosing();
295
+ };
296
+ openedDb.addEventListener('versionchange', connectionClosed);
297
+ openedDb.addEventListener('close', connectionClosed);
284
298
 
285
299
  // Initialize stores
286
300
  await this.storeManager.initializeStores(this.workspaceDb);
@@ -145,10 +145,8 @@ export function AbloProvider<R extends SchemaRecord = SchemaRecord>(
145
145
  useEffect(() => {
146
146
  if (typeof window === 'undefined') return;
147
147
  const handler = (event: BeforeUnloadEvent) => {
148
- // Best-effort IDB flush on TAB CLOSE — the client is going away with the
149
- // page regardless. This is NOT an unmount teardown: the consumer owns the
150
- // client's lifecycle and the provider never disposes it on unmount.
151
- void engine.dispose();
148
+ // The user may cancel navigation. Keep the client and its IndexedDB
149
+ // connection alive until the page actually goes away.
152
150
  if (preventUnsavedChanges && engine._store.hasUnsyncedChanges) {
153
151
  event.preventDefault();
154
152
  event.returnValue = '';