@xnetjs/data 0.1.0 → 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.
@@ -3413,6 +3413,7 @@ var DEFAULT_QUERY_VERIFICATION = {
3413
3413
  logFailures: true
3414
3414
  };
3415
3415
  var QUERY_TELEMETRY_FLUSH_THRESHOLD = 50;
3416
+ var COMPILED_QUERY_DIAGNOSTICS_MEMO_LIMIT = 512;
3416
3417
  var SQLITE_BIND_PARAMETER_BATCH_SIZE = 900;
3417
3418
  var SQLITE_HYDRATE_NODE_BATCH_SIZE = Math.floor(SQLITE_BIND_PARAMETER_BATCH_SIZE / 2);
3418
3419
  var SQL_HYDRATE_ARITY_BUCKETS = [1, 10, 50, 150, SQLITE_HYDRATE_NODE_BATCH_SIZE];
@@ -3474,6 +3475,14 @@ var SQLiteNodeStorageAdapter = class {
3474
3475
  pendingQueryTelemetryHits = 0;
3475
3476
  adaptiveIndexBudgetColumnsReady = false;
3476
3477
  storageCapabilitiesPromise;
3478
+ /**
3479
+ * Plan diagnostics memoized per compiled SQL shape (the string EXPLAINed),
3480
+ * shared across executions AND concurrent callers. Cleared when this adapter
3481
+ * creates/drops an adaptive index, since that changes plans. Debug mode must
3482
+ * not distort what it measures: per-execution EXPLAIN + index-inventory
3483
+ * round-trips convoyed the single serial worker at boot.
3484
+ */
3485
+ compiledQueryDiagnosticsMemo = /* @__PURE__ */ new Map();
3477
3486
  spatialTablesState = "unknown";
3478
3487
  fullTextSearchTablesState = "unknown";
3479
3488
  writeQueue = Promise.resolve();
@@ -5741,7 +5750,34 @@ CREATE INDEX IF NOT EXISTS idx_node_spatial_ids_schema
5741
5750
  isQueryDiagnosticsEnabled() {
5742
5751
  return this.queryDiagnostics || this.isQueryDebugEnabled();
5743
5752
  }
5744
- async collectCompiledQueryDiagnostics(compiled) {
5753
+ /**
5754
+ * Throttled: one collection per unique compiled SQL shape per session.
5755
+ * EXPLAIN plans depend on the statement, not the bound values, so keyset
5756
+ * pages of the same query share one entry. Concurrent callers share the
5757
+ * in-flight promise; failed collections are not memoized.
5758
+ */
5759
+ collectCompiledQueryDiagnostics(compiled) {
5760
+ const memoKey = compiled.sql;
5761
+ const memoized = this.compiledQueryDiagnosticsMemo.get(memoKey);
5762
+ if (memoized) {
5763
+ return memoized;
5764
+ }
5765
+ const pending = this.computeCompiledQueryDiagnostics(compiled).then((diagnostics) => {
5766
+ if (diagnostics.diagnosticsError) {
5767
+ this.compiledQueryDiagnosticsMemo.delete(memoKey);
5768
+ }
5769
+ return diagnostics;
5770
+ });
5771
+ if (this.compiledQueryDiagnosticsMemo.size >= COMPILED_QUERY_DIAGNOSTICS_MEMO_LIMIT) {
5772
+ const oldest = this.compiledQueryDiagnosticsMemo.keys().next().value;
5773
+ if (oldest !== void 0) {
5774
+ this.compiledQueryDiagnosticsMemo.delete(oldest);
5775
+ }
5776
+ }
5777
+ this.compiledQueryDiagnosticsMemo.set(memoKey, pending);
5778
+ return pending;
5779
+ }
5780
+ async computeCompiledQueryDiagnostics(compiled) {
5745
5781
  try {
5746
5782
  const [analysis, indexes, storageCapabilities] = await Promise.all([
5747
5783
  analyzeQuery(this.db, compiled.sql, compiled.params),
@@ -5965,6 +6001,7 @@ CREATE INDEX IF NOT EXISTS idx_node_spatial_ids_schema
5965
6001
  if (createdIndex) {
5966
6002
  await runAnalyze(this.db, "node_property_scalars");
5967
6003
  await this.db.exec("PRAGMA optimize");
6004
+ this.compiledQueryDiagnosticsMemo.clear();
5968
6005
  }
5969
6006
  return touchedIndexNames;
5970
6007
  }
@@ -6113,6 +6150,7 @@ CREATE INDEX IF NOT EXISTS idx_node_spatial_ids_schema
6113
6150
  async dropAdaptiveIndex(indexName, reason) {
6114
6151
  await this.db.exec(`DROP INDEX IF EXISTS ${this.quoteSqlIdentifier(indexName)}`);
6115
6152
  await this.db.run("DELETE FROM query_index_candidates WHERE index_name = ?", [indexName]);
6153
+ this.compiledQueryDiagnosticsMemo.clear();
6116
6154
  this.debugAdaptiveIndex("drop", { indexName, reason });
6117
6155
  }
6118
6156
  debugAdaptiveIndex(action, details) {
package/dist/index.js CHANGED
@@ -605,7 +605,7 @@ import {
605
605
  sum,
606
606
  validateQueryAST,
607
607
  validateSavedViewDescriptor
608
- } from "./chunk-UDWKWTAX.js";
608
+ } from "./chunk-EI5CY2FZ.js";
609
609
  import {
610
610
  applyNodeQueryDescriptor,
611
611
  createNodeQueryDescriptor,
@@ -120,9 +120,13 @@ interface SQLiteNodeStorageAdapterOptions {
120
120
  adaptiveIndexing?: SQLiteAdaptiveIndexingOptions;
121
121
  queryVerification?: SQLiteQueryVerificationOptions;
122
122
  /**
123
- * Collect EXPLAIN QUERY PLAN + index inventory per query. Costs extra
124
- * round trips per query, so it is off unless explicitly enabled or the
125
- * `xnet:query:debug` localStorage flag is set.
123
+ * Collect EXPLAIN QUERY PLAN + index inventory for queries. Costs extra
124
+ * round trips, so it is off unless explicitly enabled or the
125
+ * `xnet:query:debug` localStorage flag is set. Diagnostics are collected
126
+ * once per unique compiled SQL shape per session (invalidated when the
127
+ * adapter itself runs DDL) — per-execution collection convoyed the serial
128
+ * worker and delayed the very queries being measured by 18-20s at boot
129
+ * (2026-07-05 capture).
126
130
  */
127
131
  queryDiagnostics?: boolean;
128
132
  /**
@@ -175,6 +179,14 @@ declare class SQLiteNodeStorageAdapter implements NodeStorageAdapter {
175
179
  private pendingQueryTelemetryHits;
176
180
  private adaptiveIndexBudgetColumnsReady;
177
181
  private storageCapabilitiesPromise?;
182
+ /**
183
+ * Plan diagnostics memoized per compiled SQL shape (the string EXPLAINed),
184
+ * shared across executions AND concurrent callers. Cleared when this adapter
185
+ * creates/drops an adaptive index, since that changes plans. Debug mode must
186
+ * not distort what it measures: per-execution EXPLAIN + index-inventory
187
+ * round-trips convoyed the single serial worker at boot.
188
+ */
189
+ private compiledQueryDiagnosticsMemo;
178
190
  private spatialTablesState;
179
191
  private fullTextSearchTablesState;
180
192
  private writeQueue;
@@ -449,7 +461,14 @@ declare class SQLiteNodeStorageAdapter implements NodeStorageAdapter {
449
461
  flushQueryTelemetry(): Promise<void>;
450
462
  private writeQueryTelemetryRow;
451
463
  private isQueryDiagnosticsEnabled;
464
+ /**
465
+ * Throttled: one collection per unique compiled SQL shape per session.
466
+ * EXPLAIN plans depend on the statement, not the bound values, so keyset
467
+ * pages of the same query share one entry. Concurrent callers share the
468
+ * in-flight promise; failed collections are not memoized.
469
+ */
452
470
  private collectCompiledQueryDiagnostics;
471
+ private computeCompiledQueryDiagnostics;
453
472
  private getStorageCapabilities;
454
473
  private debugQueryPlan;
455
474
  private isQueryDebugEnabled;
@@ -49,7 +49,7 @@ import {
49
49
  sum,
50
50
  validateQueryAST,
51
51
  validateSavedViewDescriptor
52
- } from "../chunk-UDWKWTAX.js";
52
+ } from "../chunk-EI5CY2FZ.js";
53
53
  import {
54
54
  applyNodeQueryDescriptor,
55
55
  createNodeQueryDescriptor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/data",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -57,12 +57,12 @@
57
57
  "nanoid": "^5.1.6",
58
58
  "y-protocols": "^1.0.6",
59
59
  "yjs": "^13.6.24",
60
- "@xnetjs/core": "0.1.0",
61
- "@xnetjs/crypto": "0.1.0",
62
- "@xnetjs/identity": "0.1.0",
63
- "@xnetjs/sqlite": "0.1.0",
64
- "@xnetjs/storage": "0.1.0",
65
- "@xnetjs/sync": "0.1.0"
60
+ "@xnetjs/core": "0.1.1",
61
+ "@xnetjs/crypto": "0.1.1",
62
+ "@xnetjs/identity": "0.1.1",
63
+ "@xnetjs/sqlite": "0.1.1",
64
+ "@xnetjs/storage": "0.1.1",
65
+ "@xnetjs/sync": "0.1.1"
66
66
  },
67
67
  "devDependencies": {
68
68
  "tsup": "^8.0.0",