@abraca/dabra 2.0.7 → 2.0.8

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.
package/dist/index.d.ts CHANGED
@@ -1089,7 +1089,28 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
1089
1089
  private childAccessTimes;
1090
1090
  /** Pinned children that must not be evicted (e.g. actively viewed docs) */
1091
1091
  private pinnedChildren;
1092
- /** Default cap on simultaneously cached child providers; configurable per-instance via `maxChildren`. */
1092
+ /**
1093
+ * Children explicitly marked as transient (e.g. by a background indexer
1094
+ * that scans the whole tree). They:
1095
+ * - never count toward the LRU `maxChildren` budget
1096
+ * - never cause eviction of OTHER children when they're added
1097
+ * - are eligible for LRU eviction themselves only after they get
1098
+ * unmarked or explicitly unloaded
1099
+ *
1100
+ * The motivating case: `useSearchIndex` / `useFileIndex` walk every doc
1101
+ * in the tree calling `loadChild`. With a default cap of 20 and a tree
1102
+ * of 76+ subdocs, the LRU silently evicted whichever subdoc the UI was
1103
+ * actually showing — a CLOSE storm and dead awareness for the user.
1104
+ * Transient loads sidestep that pool entirely.
1105
+ */
1106
+ private transientChildren;
1107
+ /**
1108
+ * Default cap on simultaneously cached child providers (excluding
1109
+ * transient ones — see `transientChildren`). Configurable per-instance
1110
+ * via `maxChildren`. Bumped from 20 → 64 because real apps routinely
1111
+ * have trees with dozens of subdocs being touched and 20 caused silent
1112
+ * subdoc eviction (with awareness loss) on perfectly normal load.
1113
+ */
1093
1114
  private static readonly DEFAULT_MAX_CHILDREN;
1094
1115
  private readonly maxChildren;
1095
1116
  private abracadabraConfig;
@@ -1166,10 +1187,21 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
1166
1187
  hasChild(childId: string): boolean;
1167
1188
  /**
1168
1189
  * Create (or return cached) a child AbracadabraProvider for a given
1169
- * child document id. Each child opens its own WebSocket connection because
1170
- * the server is document-scoped (one WebSocket ↔ one document).
1171
- */
1172
- loadChild(childId: string): Promise<AbracadabraProvider>;
1190
+ * child document id. Each child shares the parent's WebSocket via
1191
+ * multiplexing.
1192
+ *
1193
+ * `evictable` (default `true`) controls whether the child enters the
1194
+ * LRU pool. Pass `evictable: false` for transient loads — typically
1195
+ * batch indexers (search, file extraction) that touch every doc in
1196
+ * the tree and would otherwise blow out the cache, silently evicting
1197
+ * subdocs the UI is actively using. Transient children are excluded
1198
+ * from the LRU budget AND don't trigger eviction of other children.
1199
+ * Callers MUST pair `loadChild(id, { evictable: false })` with an
1200
+ * explicit `unloadChild(id)` once they're done.
1201
+ */
1202
+ loadChild(childId: string, options?: {
1203
+ evictable?: boolean;
1204
+ }): Promise<AbracadabraProvider>;
1173
1205
  private _doLoadChild;
1174
1206
  unloadChild(childId: string): void;
1175
1207
  /**
@@ -1182,8 +1214,11 @@ declare class AbracadabraProvider extends AbracadabraBaseProvider {
1182
1214
  */
1183
1215
  unpinChild(childId: string): void;
1184
1216
  /**
1185
- * Evict least-recently-used unpinned child providers until the cache is
1186
- * at or below MAX_CHILDREN.
1217
+ * Evict least-recently-used unpinned, non-transient child providers
1218
+ * until the cache is at or below MAX_CHILDREN. Transient children
1219
+ * (loaded with `{ evictable: false }`) are excluded from both the
1220
+ * budget *and* the eviction list — they're invisible to the LRU and
1221
+ * only go away when their loader explicitly calls `unloadChild`.
1187
1222
  */
1188
1223
  private evictLRU;
1189
1224
  /** Return all currently-loaded child providers. */
@@ -2280,6 +2315,15 @@ declare class AbracadabraBaseProvider extends EventEmitter {
2280
2315
  isSynced: boolean;
2281
2316
  unsyncedChanges: number;
2282
2317
  isAuthenticated: boolean;
2318
+ /**
2319
+ * True once this provider has received at least one Authenticated frame
2320
+ * on the current socket lifetime. Used by `permissionDeniedHandler` to
2321
+ * tell apart "the entry doc is wrong / unreachable" (first frame is a
2322
+ * denial → config error, give up) from "a mid-session write was denied"
2323
+ * (server keeps the read subscription alive → don't kill the socket).
2324
+ * Reset on close.
2325
+ */
2326
+ private _hasEverAuthenticated;
2283
2327
  /** Current WebSocket connection status. */
2284
2328
  get connectionStatus(): WebSocketStatus;
2285
2329
  authorizedScope: AuthorizedScope | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abraca/dabra",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "abracadabra provider",
5
5
  "keywords": [
6
6
  "abracadabra",
@@ -519,6 +519,23 @@ export class AbracadabraProvider extends AbracadabraBaseProvider {
519
519
  );
520
520
  }
521
521
 
522
+ // Self-load: caller asked for the doc this provider already owns.
523
+ // Return `this` directly. Without this guard we'd build a sibling
524
+ // AbracadabraProvider with the same `name` and call attach() on the
525
+ // shared AbracadabraWS. The wsp's providerMap is keyed by name, so
526
+ // the sibling silently REPLACES the root in the routing table — its
527
+ // outgoing messages still reach the wire (send doesn't read the
528
+ // map), but inbound frames addressed to that doc go to the sibling.
529
+ // When the borrow ends and the sibling detaches, providerMap is
530
+ // wiped of that name and the root is left orphaned: the UI still
531
+ // reports `connected` + `synced`, but no awareness/sync messages
532
+ // are received. Returning `this` is intentionally not added to
533
+ // `childProviders` so LRU eviction never destroys the root, and
534
+ // `unloadChild(self)` becomes a safe no-op (see below).
535
+ if (childId === this.configuration.name) {
536
+ return Promise.resolve(this);
537
+ }
538
+
522
539
  const evictable = options.evictable !== false;
523
540
 
524
541
  if (this.childProviders.has(childId)) {