@klum-db/lobby 0.2.0-pre.32 → 0.2.0-pre.33

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.
@@ -1,4 +1,4 @@
1
- import { V as VaultGroup } from '../vault-group-BchRCDm5.cjs';
1
+ import { V as VaultGroup } from '../vault-group-SfkQJ3CM.cjs';
2
2
  import '@noy-db/hub/kernel';
3
3
  import '@noy-db/hub';
4
4
  import '@noy-db/hub/bundle';
@@ -1,4 +1,4 @@
1
- import { V as VaultGroup } from '../vault-group-BchRCDm5.js';
1
+ import { V as VaultGroup } from '../vault-group-SfkQJ3CM.js';
2
2
  import '@noy-db/hub/kernel';
3
3
  import '@noy-db/hub';
4
4
  import '@noy-db/hub/bundle';
package/dist/index.cjs CHANGED
@@ -1223,28 +1223,33 @@ var init_insight_auto_push = __esm({
1223
1223
  "src/federation/insight-auto-push.ts"() {
1224
1224
  "use strict";
1225
1225
  InsightAutoPush = class {
1226
- constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err)) {
1226
+ constructor(recompute, isSource, onError = (err, pk) => console.warn(`[klum-db] insight auto-push failed for shard "${pk}":`, err), debounceMs) {
1227
1227
  this.recompute = recompute;
1228
1228
  this.isSource = isSource;
1229
1229
  this.onError = onError;
1230
+ this.debounceMs = debounceMs;
1230
1231
  }
1231
1232
  recompute;
1232
1233
  isSource;
1233
1234
  onError;
1234
- /** Partition keys awaiting recompute. */
1235
+ debounceMs;
1235
1236
  dirty = /* @__PURE__ */ new Set();
1236
- /** The in-flight flush, or null when idle. */
1237
1237
  pending = null;
1238
+ timer = null;
1239
+ resolvePending = null;
1240
+ flushing = false;
1238
1241
  /** Called from a shard's onAfterWrite hook. Marks the shard dirty + schedules a flush. */
1239
1242
  noteWrite(partitionKey, collection) {
1240
1243
  if (!this.isSource(collection)) return;
1241
1244
  this.dirty.add(partitionKey);
1242
- this.schedule();
1245
+ if (this.debounceMs !== void 0) this.scheduleDebounced();
1246
+ else this.schedule();
1243
1247
  }
1244
1248
  /** Resolve once no flush is pending (drains rescheduled flushes too). */
1245
1249
  async whenSettled() {
1246
1250
  while (this.pending) await this.pending;
1247
1251
  }
1252
+ // ── microtask path (unchanged default) ──
1248
1253
  schedule() {
1249
1254
  if (this.pending) return;
1250
1255
  this.pending = this.runFlush().finally(() => {
@@ -1252,6 +1257,29 @@ var init_insight_auto_push = __esm({
1252
1257
  if (this.dirty.size > 0) this.schedule();
1253
1258
  });
1254
1259
  }
1260
+ // ── debounce path (#13) ──
1261
+ scheduleDebounced() {
1262
+ if (!this.pending) this.pending = new Promise((r) => {
1263
+ this.resolvePending = r;
1264
+ });
1265
+ if (this.timer) clearTimeout(this.timer);
1266
+ this.timer = setTimeout(() => {
1267
+ this.timer = null;
1268
+ if (this.flushing) return;
1269
+ this.flushing = true;
1270
+ void this.runFlush().finally(() => {
1271
+ this.flushing = false;
1272
+ if (this.dirty.size > 0) {
1273
+ this.scheduleDebounced();
1274
+ } else {
1275
+ const resolve = this.resolvePending;
1276
+ this.resolvePending = null;
1277
+ this.pending = null;
1278
+ resolve?.();
1279
+ }
1280
+ });
1281
+ }, this.debounceMs);
1282
+ }
1255
1283
  async runFlush() {
1256
1284
  await Promise.resolve();
1257
1285
  const pks = [...this.dirty];
@@ -1491,6 +1519,10 @@ __export(vault_group_exports, {
1491
1519
  ShardedQuery: () => ShardedQuery,
1492
1520
  VaultGroup: () => VaultGroup
1493
1521
  });
1522
+ function autoPushConfig(spec) {
1523
+ if (!spec.autoPush) return null;
1524
+ return spec.autoPush === true ? {} : spec.autoPush;
1525
+ }
1494
1526
  function assertSafePartitionKey(partitionKey) {
1495
1527
  if (partitionKey.length === 0) {
1496
1528
  throw new import_kernel11.ValidationError("partitionKey must be a non-empty string");
@@ -1755,9 +1787,12 @@ var init_vault_group = __esm({
1755
1787
  }
1756
1788
  this.crossVaultDerivations.push(spec);
1757
1789
  if (spec.autoPush && !this.insightAutoPush) {
1790
+ const cfg = autoPushConfig(spec);
1758
1791
  const controller = new InsightAutoPush(
1759
1792
  (pk) => this._recomputeShardInsights(pk),
1760
- (collection) => this.crossVaultDerivations.some((s) => s.autoPush && s.source === collection)
1793
+ (collection) => this.crossVaultDerivations.some((s) => s.autoPush && s.source === collection),
1794
+ void 0,
1795
+ cfg?.debounceMs
1761
1796
  );
1762
1797
  this.insightAutoPush = controller;
1763
1798
  const prefix = `${this.name}${SHARD_SEPARATOR}`;
@@ -1838,6 +1873,8 @@ var init_vault_group = __esm({
1838
1873
  };
1839
1874
  for (const spec of this.crossVaultDerivations) {
1840
1875
  if (!spec.autoPush) continue;
1876
+ const min = autoPushConfig(spec)?.minVersion;
1877
+ if (min !== void 0 && row.schemaVersion < min) continue;
1841
1878
  const records = await shard.collection(spec.source).list();
1842
1879
  const summary = spec.derive(records, ctx);
1843
1880
  const insight = await this.db.openVault(spec.target.vault);
@@ -2118,23 +2155,23 @@ var init_vault_group = __esm({
2118
2155
  isRelevant: (e) => e.collection === collectionName && e.vault.startsWith(`${group.name}--`)
2119
2156
  };
2120
2157
  }
2121
- /** @internal — joined queries don't support reactive/aggregate surfaces in v1. */
2122
- assertNoJoinLegs(surface) {
2123
- if (this.coPartitionedLegs.length || this.broadcastLegs.length) {
2124
- throw new import_kernel11.CrossShardJoinError(
2125
- `${surface}() is not supported on a ShardedQuery with crossShardJoin/broadcastJoin legs in v1. Use toArray() for joined cross-shard queries.`
2126
- );
2127
- }
2128
- }
2129
- /** Returns a reactive cross-shard live query — a facade over CrossVaultLive. */
2158
+ /**
2159
+ * Returns a reactive cross-shard live query — a facade over CrossVaultLive.
2160
+ *
2161
+ * Joined queries (crossShardJoin / broadcastJoin) are supported (#14): the live
2162
+ * value reflects the fully-joined rows. **v1 limitation:** recomputes only on
2163
+ * writes to the primary (left) collection; writes to a co-partitioned right
2164
+ * collection or a broadcast-dimension collection do NOT trigger a recompute —
2165
+ * re-run the query to pick those up.
2166
+ */
2130
2167
  live(options = {}) {
2131
- this.assertNoJoinLegs("live");
2132
2168
  const bind = this.liveBinding();
2133
2169
  const core = new CrossVaultLive({
2134
2170
  ...bind,
2135
2171
  compute: async () => {
2136
2172
  const { records, skippedVaults } = await this.fanoutRecords(options);
2137
- return { records, skipped: skippedVaults };
2173
+ const joined = await applyBroadcastLegs(records, this.broadcastLegs);
2174
+ return { records: joined, skipped: skippedVaults };
2138
2175
  },
2139
2176
  initialSnapshot: { records: [], skipped: [] },
2140
2177
  ...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {}
@@ -2154,14 +2191,28 @@ var init_vault_group = __esm({
2154
2191
  stop: () => core.stop()
2155
2192
  };
2156
2193
  }
2194
+ /**
2195
+ * @internal — the FanoutRecordSource for aggregate surfaces. With no join legs,
2196
+ * returns `this` (partial-reduce-eligible, #8). With join legs, returns a
2197
+ * toArray-backed source (fully-joined rows) and NO fanoutReduce, forcing
2198
+ * central-reduce over the joined rows (partial-reduce can't span the central
2199
+ * broadcast join). Used by scalar `aggregate()` and `ShardedGroupedQuery`.
2200
+ */
2201
+ aggregateSource() {
2202
+ if (!this.coPartitionedLegs.length && !this.broadcastLegs.length) return this;
2203
+ return {
2204
+ fanoutRecords: async (o) => {
2205
+ const { results, skippedVaults } = await this.toArray(o);
2206
+ return { records: results, skippedVaults };
2207
+ }
2208
+ };
2209
+ }
2157
2210
  /** One-shot distributed aggregate — central reduce over all shard records. */
2158
2211
  aggregate(spec) {
2159
- this.assertNoJoinLegs("aggregate");
2160
- return new CrossVaultAggregation(this, spec, this.liveBinding());
2212
+ return new CrossVaultAggregation(this.aggregateSource(), spec, this.liveBinding());
2161
2213
  }
2162
2214
  /** Begin a grouped cross-shard aggregate. */
2163
2215
  groupBy(field) {
2164
- this.assertNoJoinLegs("groupBy");
2165
2216
  return new ShardedGroupedQuery(this, field);
2166
2217
  }
2167
2218
  };
@@ -2174,7 +2225,7 @@ var init_vault_group = __esm({
2174
2225
  field;
2175
2226
  aggregate(spec) {
2176
2227
  return new CrossVaultGroupedAggregation(
2177
- { fanoutRecords: (o) => this.query.fanoutRecords(o) },
2228
+ this.query.aggregateSource(),
2178
2229
  this.field,
2179
2230
  spec,
2180
2231
  this.query.liveBinding()