@camstack/system 1.2.51 → 1.2.52

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.
@@ -995,11 +995,26 @@ var SqliteVectorIndex = class {
995
995
  }
996
996
  return deleted;
997
997
  }
998
+ /**
999
+ * Delete every row the filter admits.
1000
+ *
1001
+ * When every clause is pushable, this is ONE bulk statement. When the filter
1002
+ * touches a non-promoted key it falls back to select-then-delete, because
1003
+ * those clauses live in the JSON remainder and only JS can evaluate them —
1004
+ * and an unapplied clause would delete rows the caller wanted kept, which is
1005
+ * the one failure mode a delete must not have.
1006
+ */
998
1007
  async deleteByFilter(index, filter) {
1008
+ await this.resolveSpec(index);
1009
+ const sqlFilter = buildSqlFilter(filter);
1010
+ if (isFullyPushable(filter)) return (await this.store.deleteWhere({
1011
+ collection: collectionFor(index),
1012
+ filter: sqlFilter
1013
+ })).deleted;
999
1014
  const ids = (await this.store.query({
1000
1015
  collection: collectionFor(index),
1001
1016
  filter: {
1002
- ...buildSqlFilter(filter),
1017
+ ...sqlFilter,
1003
1018
  limit: MAX_SCAN_ROWS
1004
1019
  }
1005
1020
  })).filter((r) => {
@@ -1101,6 +1116,12 @@ function readRow(data) {
1101
1116
  function isMetadata(value) {
1102
1117
  return typeof value === "object" && value !== null && !Array.isArray(value);
1103
1118
  }
1119
+ /** True when every clause of the filter maps to a promoted column. */
1120
+ function isFullyPushable(filter) {
1121
+ for (const key of Object.keys(filter.equals ?? {})) if (!isPromoted(key)) return false;
1122
+ for (const key of Object.keys(filter.between ?? {})) if (!isPromoted(key)) return false;
1123
+ return true;
1124
+ }
1104
1125
  /** Narrow a stored metric string back to the enum. */
1105
1126
  function isMetric(value) {
1106
1127
  return value === "cosine" || value === "dot" || value === "euclidean";
@@ -989,11 +989,26 @@ var SqliteVectorIndex = class {
989
989
  }
990
990
  return deleted;
991
991
  }
992
+ /**
993
+ * Delete every row the filter admits.
994
+ *
995
+ * When every clause is pushable, this is ONE bulk statement. When the filter
996
+ * touches a non-promoted key it falls back to select-then-delete, because
997
+ * those clauses live in the JSON remainder and only JS can evaluate them —
998
+ * and an unapplied clause would delete rows the caller wanted kept, which is
999
+ * the one failure mode a delete must not have.
1000
+ */
992
1001
  async deleteByFilter(index, filter) {
1002
+ await this.resolveSpec(index);
1003
+ const sqlFilter = buildSqlFilter(filter);
1004
+ if (isFullyPushable(filter)) return (await this.store.deleteWhere({
1005
+ collection: collectionFor(index),
1006
+ filter: sqlFilter
1007
+ })).deleted;
993
1008
  const ids = (await this.store.query({
994
1009
  collection: collectionFor(index),
995
1010
  filter: {
996
- ...buildSqlFilter(filter),
1011
+ ...sqlFilter,
997
1012
  limit: MAX_SCAN_ROWS
998
1013
  }
999
1014
  })).filter((r) => {
@@ -1095,6 +1110,12 @@ function readRow(data) {
1095
1110
  function isMetadata(value) {
1096
1111
  return typeof value === "object" && value !== null && !Array.isArray(value);
1097
1112
  }
1113
+ /** True when every clause of the filter maps to a promoted column. */
1114
+ function isFullyPushable(filter) {
1115
+ for (const key of Object.keys(filter.equals ?? {})) if (!isPromoted(key)) return false;
1116
+ for (const key of Object.keys(filter.between ?? {})) if (!isPromoted(key)) return false;
1117
+ return true;
1118
+ }
1098
1119
  /** Narrow a stored metric string back to the enum. */
1099
1120
  function isMetric(value) {
1100
1121
  return value === "cosine" || value === "dot" || value === "euclidean";
@@ -35,6 +35,20 @@ export interface VectorBackend {
35
35
  collection: string;
36
36
  filter?: StoreQueryFilter;
37
37
  }): Promise<readonly StoreRecord[]>;
38
+ /**
39
+ * Bulk delete in ONE statement.
40
+ *
41
+ * `deleteByFilter` used to collect ids and delete them one at a time, which
42
+ * on a live index of 8,686 rows blew through a 60s UDS timeout mid-way and
43
+ * left the index half-emptied. The engine has always had this; using it is
44
+ * the difference between one statement and eight thousand.
45
+ */
46
+ deleteWhere(input: {
47
+ collection: string;
48
+ filter: StoreQueryFilter;
49
+ }): Promise<{
50
+ deleted: number;
51
+ }>;
38
52
  }
39
53
  /** Column spec accepted by `declareCollection`. */
40
54
  export interface TableColumn {
@@ -135,6 +149,15 @@ export declare class SqliteVectorIndex {
135
149
  metadata: VectorMetadata;
136
150
  }>>;
137
151
  deleteByIds(index: string, ids: readonly string[]): Promise<number>;
152
+ /**
153
+ * Delete every row the filter admits.
154
+ *
155
+ * When every clause is pushable, this is ONE bulk statement. When the filter
156
+ * touches a non-promoted key it falls back to select-then-delete, because
157
+ * those clauses live in the JSON remainder and only JS can evaluate them —
158
+ * and an unapplied clause would delete rows the caller wanted kept, which is
159
+ * the one failure mode a delete must not have.
160
+ */
138
161
  deleteByFilter(index: string, filter: VectorFilter): Promise<number>;
139
162
  stats(index: string): Promise<{
140
163
  backend: string;
@@ -167,6 +190,8 @@ export declare function buildSqlFilter(filter: VectorFilter | undefined): {
167
190
  * unapplied filter returns rows the caller will treat as matches.
168
191
  */
169
192
  export declare function passesUnpushedFilter(row: StoredRow, filter: VectorFilter | undefined): boolean;
193
+ /** True when every clause of the filter maps to a promoted column. */
194
+ export declare function isFullyPushable(filter: VectorFilter): boolean;
170
195
  /** Score two vectors under the index's metric. Higher is nearer in every case. */
171
196
  export declare function scoreFor(metric: VectorMetric, a: Float32Array, b: Float32Array): number;
172
197
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.51",
3
+ "version": "1.2.52",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",