@workglow/storage 0.2.25 → 0.2.26

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.
Files changed (35) hide show
  1. package/dist/browser.js +298 -1
  2. package/dist/browser.js.map +13 -11
  3. package/dist/bun.js +388 -1
  4. package/dist/bun.js.map +16 -14
  5. package/dist/common.d.ts +3 -0
  6. package/dist/common.d.ts.map +1 -1
  7. package/dist/node.js +388 -1
  8. package/dist/node.js.map +16 -14
  9. package/dist/tabular/BaseTabularStorage.d.ts +15 -1
  10. package/dist/tabular/BaseTabularStorage.d.ts.map +1 -1
  11. package/dist/tabular/CachedTabularStorage.d.ts +2 -1
  12. package/dist/tabular/CachedTabularStorage.d.ts.map +1 -1
  13. package/dist/tabular/CoveringIndexMissingError.d.ts +14 -0
  14. package/dist/tabular/CoveringIndexMissingError.d.ts.map +1 -0
  15. package/dist/tabular/FsFolderTabularStorage.d.ts +6 -1
  16. package/dist/tabular/FsFolderTabularStorage.d.ts.map +1 -1
  17. package/dist/tabular/ITabularStorage.d.ts +19 -1
  18. package/dist/tabular/ITabularStorage.d.ts.map +1 -1
  19. package/dist/tabular/InMemoryTabularStorage.d.ts +7 -1
  20. package/dist/tabular/InMemoryTabularStorage.d.ts.map +1 -1
  21. package/dist/tabular/IndexedDbTabularStorage.d.ts +10 -1
  22. package/dist/tabular/IndexedDbTabularStorage.d.ts.map +1 -1
  23. package/dist/tabular/PostgresTabularStorage.d.ts +11 -1
  24. package/dist/tabular/PostgresTabularStorage.d.ts.map +1 -1
  25. package/dist/tabular/SqliteTabularStorage.d.ts +11 -1
  26. package/dist/tabular/SqliteTabularStorage.d.ts.map +1 -1
  27. package/dist/tabular/SupabaseTabularStorage.d.ts +2 -1
  28. package/dist/tabular/SupabaseTabularStorage.d.ts.map +1 -1
  29. package/dist/tabular/TelemetryTabularStorage.d.ts +2 -1
  30. package/dist/tabular/TelemetryTabularStorage.d.ts.map +1 -1
  31. package/dist/tabular/coveringIndexPicker.d.ts +37 -0
  32. package/dist/tabular/coveringIndexPicker.d.ts.map +1 -0
  33. package/dist/vector/IVectorStorage.d.ts +3 -1
  34. package/dist/vector/IVectorStorage.d.ts.map +1 -1
  35. package/package.json +3 -3
package/dist/browser.js CHANGED
@@ -45,6 +45,21 @@ class StorageUnsupportedError extends StorageError {
45
45
  }
46
46
  }
47
47
 
48
+ // src/tabular/CoveringIndexMissingError.ts
49
+ class CoveringIndexMissingError extends StorageError {
50
+ static type = "CoveringIndexMissingError";
51
+ table;
52
+ requiredColumns;
53
+ registeredIndexes;
54
+ constructor(table, requiredColumns, registeredIndexes) {
55
+ const indexList = registeredIndexes.map((cols) => `[${cols.join(", ")}]`).join(", ");
56
+ super(`No covering index for table "${table}". ` + `Required columns: [${requiredColumns.join(", ")}]. ` + `Registered indexes: ${indexList || "(none)"}.`);
57
+ this.table = table;
58
+ this.requiredColumns = requiredColumns;
59
+ this.registeredIndexes = registeredIndexes;
60
+ }
61
+ }
62
+
48
63
  // src/tabular/BaseTabularStorage.ts
49
64
  var TABULAR_REPOSITORY = createServiceToken("storage.tabularRepository");
50
65
 
@@ -168,6 +183,15 @@ class BaseTabularStorage {
168
183
  const entities = await this.query(criteria);
169
184
  return entities?.length ?? 0;
170
185
  }
186
+ queryIndex(criteria, options) {
187
+ this.validateSelect(options);
188
+ const requiredColumns = [
189
+ ...Object.keys(criteria),
190
+ ...(options.orderBy ?? []).map((o) => String(o.column)),
191
+ ...options.select.map(String)
192
+ ];
193
+ throw new CoveringIndexMissingError(this.constructor.name, requiredColumns, []);
194
+ }
171
195
  async* records(pageSize = 100) {
172
196
  if (pageSize <= 0) {
173
197
  throw new RangeError(`pageSize must be greater than 0, got ${pageSize}`);
@@ -263,6 +287,18 @@ class BaseTabularStorage {
263
287
  }
264
288
  }
265
289
  }
290
+ validateSelect(options) {
291
+ if (!options.select || options.select.length === 0) {
292
+ throw new StorageValidationError("queryIndex requires a non-empty select array");
293
+ }
294
+ const schemaProps = Object.keys(this.schema.properties);
295
+ for (const col of options.select) {
296
+ const colStr = String(col);
297
+ if (!schemaProps.includes(colStr)) {
298
+ throw new StorageValidationError(`queryIndex select column "${colStr}" is not in schema`);
299
+ }
300
+ }
301
+ }
266
302
  primaryKeyColumns() {
267
303
  const columns = [];
268
304
  for (const key of Object.keys(this.primaryKeySchema.properties)) {
@@ -383,6 +419,61 @@ import { createServiceToken as createServiceToken3, getLogger } from "@workglow/
383
419
 
384
420
  // src/tabular/InMemoryTabularStorage.ts
385
421
  import { createServiceToken as createServiceToken2, makeFingerprint as makeFingerprint2, uuid4 } from "@workglow/util";
422
+
423
+ // src/tabular/coveringIndexPicker.ts
424
+ function pickCoveringIndex(input) {
425
+ const { table, indexes, criteriaColumns, orderByColumns, selectColumns, primaryKeyColumns } = input;
426
+ const required = uniqueColumns([
427
+ ...criteriaColumns,
428
+ ...orderByColumns.map((o) => o.column),
429
+ ...selectColumns
430
+ ]);
431
+ const pkSet = new Set(primaryKeyColumns);
432
+ for (const idx of indexes) {
433
+ const fit = tryFitIndex(idx, criteriaColumns, orderByColumns, selectColumns, pkSet);
434
+ if (fit !== undefined) {
435
+ return { name: idx.name, keyPath: idx.keyPath, reverseDirection: fit.reverseDirection };
436
+ }
437
+ }
438
+ throw new CoveringIndexMissingError(table, required, indexes.map((i) => i.keyPath));
439
+ }
440
+ function tryFitIndex(index, criteria, orderBy, select, pkSet) {
441
+ const keyPath = index.keyPath;
442
+ const criteriaSet = new Set(criteria);
443
+ if (criteria.length > keyPath.length)
444
+ return;
445
+ for (let i = 0;i < criteria.length; i++) {
446
+ if (!criteriaSet.has(keyPath[i]))
447
+ return;
448
+ }
449
+ let reverseDirection = false;
450
+ if (orderBy.length > 0) {
451
+ const start = criteria.length;
452
+ if (start + orderBy.length > keyPath.length)
453
+ return;
454
+ for (let i = 0;i < orderBy.length; i++) {
455
+ if (keyPath[start + i] !== orderBy[i].column)
456
+ return;
457
+ }
458
+ const allDesc = orderBy.every((o) => o.direction === "DESC");
459
+ const allAsc = orderBy.every((o) => o.direction === "ASC");
460
+ if (allDesc)
461
+ reverseDirection = true;
462
+ else if (!allAsc)
463
+ return;
464
+ }
465
+ const keyPathSet = new Set(keyPath);
466
+ for (const col of select) {
467
+ if (!keyPathSet.has(col) && !pkSet.has(col))
468
+ return;
469
+ }
470
+ return { reverseDirection };
471
+ }
472
+ function uniqueColumns(cols) {
473
+ return Array.from(new Set(cols));
474
+ }
475
+
476
+ // src/tabular/InMemoryTabularStorage.ts
386
477
  var MEMORY_TABULAR_REPOSITORY = createServiceToken2("storage.tabularRepository.inMemory");
387
478
 
388
479
  class InMemoryTabularStorage extends BaseTabularStorage {
@@ -625,6 +716,38 @@ class InMemoryTabularStorage extends BaseTabularStorage {
625
716
  this.events.emit("query", criteria, result);
626
717
  return result;
627
718
  }
719
+ async queryIndex(criteria, options) {
720
+ this.validateSelect(options);
721
+ this.validateQueryParams(criteria, options);
722
+ const registered = this.indexes.map((cols, i) => ({
723
+ name: `idx_${i}`,
724
+ keyPath: cols
725
+ }));
726
+ pickCoveringIndex({
727
+ table: "InMemoryTabularStorage",
728
+ indexes: registered,
729
+ criteriaColumns: Object.keys(criteria),
730
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
731
+ column: String(o.column),
732
+ direction: o.direction
733
+ })),
734
+ selectColumns: options.select.map(String),
735
+ primaryKeyColumns: this.primaryKeyNames.map(String)
736
+ });
737
+ const full = await this.query(criteria, {
738
+ orderBy: options.orderBy,
739
+ limit: options.limit,
740
+ offset: options.offset
741
+ });
742
+ if (!full)
743
+ return [];
744
+ return full.map((row) => {
745
+ const out = {};
746
+ for (const k of options.select)
747
+ out[k] = row[k];
748
+ return out;
749
+ });
750
+ }
628
751
  subscribeToChanges(callback, options) {
629
752
  const handlePut = (entity) => {
630
753
  callback({ type: this._lastPutWasInsert ? "INSERT" : "UPDATE", new: entity });
@@ -767,6 +890,10 @@ class CachedTabularStorage extends BaseTabularStorage {
767
890
  await this.initializeCache();
768
891
  return await this.cache.query(criteria, options);
769
892
  }
893
+ async queryIndex(criteria, options) {
894
+ await this.initializeCache();
895
+ return await this.cache.queryIndex(criteria, options);
896
+ }
770
897
  async deleteSearch(criteria) {
771
898
  await this.initializeCache();
772
899
  await this.durable.deleteSearch(criteria);
@@ -1207,6 +1334,9 @@ class TelemetryTabularStorage {
1207
1334
  query(criteria, options) {
1208
1335
  return traced("workglow.storage.tabular.query", this.storageName, () => this.inner.query(criteria, options));
1209
1336
  }
1337
+ queryIndex(criteria, options) {
1338
+ return traced("workglow.storage.tabular.queryIndex", this.storageName, () => this.inner.queryIndex(criteria, options));
1339
+ }
1210
1340
  records(pageSize) {
1211
1341
  return this.inner.records(pageSize);
1212
1342
  }
@@ -3196,6 +3326,105 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
3196
3326
  request.onerror = () => reject(request.error);
3197
3327
  });
3198
3328
  }
3329
+ async queryIndex(criteria, options) {
3330
+ this.validateSelect(options);
3331
+ this.validateQueryParams(criteria, options);
3332
+ const registered = this.indexes.map((cols) => {
3333
+ const cs = Array.isArray(cols) ? cols : [cols];
3334
+ return { name: cs.join("_"), keyPath: cs };
3335
+ });
3336
+ const picked = pickCoveringIndex({
3337
+ table: this.table,
3338
+ indexes: registered,
3339
+ criteriaColumns: Object.keys(criteria),
3340
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
3341
+ column: String(o.column),
3342
+ direction: o.direction
3343
+ })),
3344
+ selectColumns: options.select.map(String),
3345
+ primaryKeyColumns: this.primaryKeyColumns().map(String)
3346
+ });
3347
+ const db = await this.getDb();
3348
+ return new Promise((resolve, reject) => {
3349
+ const tx = db.transaction(this.table, "readonly");
3350
+ const store = tx.objectStore(this.table);
3351
+ const idx = store.index(picked.name);
3352
+ const prefix = [];
3353
+ for (const col of picked.keyPath) {
3354
+ const c = criteria[col];
3355
+ if (c === undefined && !(col in criteria))
3356
+ break;
3357
+ if (isSearchCondition(c)) {
3358
+ if (c.operator !== "=")
3359
+ break;
3360
+ prefix.push(c.value);
3361
+ } else {
3362
+ prefix.push(c);
3363
+ }
3364
+ }
3365
+ const range = prefix.length === 0 ? undefined : prefix.length === picked.keyPath.length ? IDBKeyRange.only(prefix.length === 1 ? prefix[0] : prefix) : IDBKeyRange.bound(prefix, [...prefix, []]);
3366
+ const direction = picked.reverseDirection ? "prev" : "next";
3367
+ const request = idx.openKeyCursor(range, direction);
3368
+ const out = [];
3369
+ let toSkip = options.offset ?? 0;
3370
+ const keyPathPositions = new Map;
3371
+ picked.keyPath.forEach((col, i) => keyPathPositions.set(col, i));
3372
+ const pkCols = this.primaryKeyColumns().map(String);
3373
+ const pkPositions = new Map;
3374
+ pkCols.forEach((col, i) => pkPositions.set(col, i));
3375
+ request.onsuccess = () => {
3376
+ const cursor = request.result;
3377
+ if (!cursor) {
3378
+ resolve(out);
3379
+ return;
3380
+ }
3381
+ const key = cursor.key;
3382
+ const row = {};
3383
+ for (const col of options.select) {
3384
+ const colStr = String(col);
3385
+ const pos = keyPathPositions.get(colStr);
3386
+ if (pos !== undefined) {
3387
+ row[colStr] = Array.isArray(key) ? key[pos] : key;
3388
+ } else {
3389
+ if (pkCols.length === 1 && colStr === pkCols[0]) {
3390
+ row[colStr] = cursor.primaryKey;
3391
+ } else {
3392
+ const pkPos = pkPositions.get(colStr);
3393
+ if (pkPos !== undefined) {
3394
+ row[colStr] = Array.isArray(cursor.primaryKey) ? cursor.primaryKey[pkPos] : cursor.primaryKey;
3395
+ }
3396
+ }
3397
+ }
3398
+ }
3399
+ let matches = true;
3400
+ for (const [col, crit] of Object.entries(criteria)) {
3401
+ if (!isSearchCondition(crit))
3402
+ continue;
3403
+ const pos = keyPathPositions.get(col);
3404
+ if (pos === undefined)
3405
+ continue;
3406
+ const valFromKey = Array.isArray(key) ? key[pos] : key;
3407
+ if (!compareWithOperator(valFromKey, crit.operator, crit.value)) {
3408
+ matches = false;
3409
+ break;
3410
+ }
3411
+ }
3412
+ if (matches) {
3413
+ if (toSkip > 0) {
3414
+ toSkip -= 1;
3415
+ } else {
3416
+ out.push(row);
3417
+ if (options.limit !== undefined && out.length >= options.limit) {
3418
+ resolve(out);
3419
+ return;
3420
+ }
3421
+ }
3422
+ }
3423
+ cursor.continue();
3424
+ };
3425
+ request.onerror = () => reject(request.error);
3426
+ });
3427
+ }
3199
3428
  getHybridManager() {
3200
3429
  if (!this.hybridManager) {
3201
3430
  const channelName = `indexeddb-tabular-${this.table}`;
@@ -3233,6 +3462,22 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
3233
3462
  this.db?.close();
3234
3463
  }
3235
3464
  }
3465
+ function compareWithOperator(a, op, b) {
3466
+ const av = a;
3467
+ const bv = b;
3468
+ switch (op) {
3469
+ case "=":
3470
+ return av === bv;
3471
+ case "<":
3472
+ return av !== null && av !== undefined && av < bv;
3473
+ case "<=":
3474
+ return av !== null && av !== undefined && av <= bv;
3475
+ case ">":
3476
+ return av !== null && av !== undefined && av > bv;
3477
+ case ">=":
3478
+ return av !== null && av !== undefined && av >= bv;
3479
+ }
3480
+ }
3236
3481
  // src/tabular/SharedInMemoryTabularStorage.ts
3237
3482
  import { createServiceToken as createServiceToken13 } from "@workglow/util";
3238
3483
  var SHARED_IN_MEMORY_TABULAR_REPOSITORY = createServiceToken13("storage.tabularRepository.sharedInMemory");
@@ -4093,6 +4338,56 @@ class SupabaseTabularStorage extends BaseSqlTabularStorage {
4093
4338
  this.events.emit("query", criteria, undefined);
4094
4339
  return;
4095
4340
  }
4341
+ async queryIndex(criteria, options) {
4342
+ this.validateSelect(options);
4343
+ this.validateQueryParams(criteria, options);
4344
+ const registered = this.indexes.map((cols, i) => {
4345
+ const cs = Array.isArray(cols) ? cols : [cols];
4346
+ return { name: `idx_${i}`, keyPath: cs };
4347
+ });
4348
+ pickCoveringIndex({
4349
+ table: this.table,
4350
+ indexes: registered,
4351
+ criteriaColumns: Object.keys(criteria),
4352
+ orderByColumns: (options.orderBy ?? []).map((o) => ({
4353
+ column: String(o.column),
4354
+ direction: o.direction
4355
+ })),
4356
+ selectColumns: options.select.map(String),
4357
+ primaryKeyColumns: this.primaryKeyNames.map(String)
4358
+ });
4359
+ const colList = options.select.map(String).join(",");
4360
+ let q = this.applyCriteriaToFilter(this.client.from(this.table).select(colList), criteria);
4361
+ if (options.orderBy) {
4362
+ for (const { column, direction } of options.orderBy) {
4363
+ q = q.order(String(column), { ascending: direction === "ASC" });
4364
+ }
4365
+ }
4366
+ if (options.offset !== undefined && options.limit === undefined) {
4367
+ throw new StorageValidationError("queryIndex with offset requires limit (no implicit cap)");
4368
+ }
4369
+ if (options.offset !== undefined || options.limit !== undefined) {
4370
+ const start = options.offset ?? 0;
4371
+ if (options.limit !== undefined) {
4372
+ q = q.range(start, start + options.limit - 1);
4373
+ }
4374
+ }
4375
+ const { data, error } = await q;
4376
+ if (error)
4377
+ throw error;
4378
+ if (!data)
4379
+ return [];
4380
+ const rows = data;
4381
+ const sel = new Set(options.select.map(String));
4382
+ for (const row of rows) {
4383
+ for (const key of Object.keys(row)) {
4384
+ if (sel.has(key)) {
4385
+ row[key] = this.sqlToJsValue(key, row[key]);
4386
+ }
4387
+ }
4388
+ }
4389
+ return rows;
4390
+ }
4096
4391
  convertRealtimeRow(row) {
4097
4392
  const entity = { ...row };
4098
4393
  const record = entity;
@@ -5739,6 +6034,7 @@ class IndexedDbVectorStorage extends IndexedDbTabularStorage {
5739
6034
  export {
5740
6035
  traced,
5741
6036
  registerTabularRepository,
6037
+ pickCoveringIndex,
5742
6038
  isSearchCondition,
5743
6039
  getVectorProperty,
5744
6040
  getTabularRepository,
@@ -5801,9 +6097,10 @@ export {
5801
6097
  EncryptedKvCredentialStore,
5802
6098
  DefaultKeyValueSchema,
5803
6099
  DefaultKeyValueKey,
6100
+ CoveringIndexMissingError,
5804
6101
  CachedTabularStorage,
5805
6102
  CACHED_TABULAR_REPOSITORY,
5806
6103
  BaseTabularStorage
5807
6104
  };
5808
6105
 
5809
- //# debugId=4C0D1FA7EA7C0F0564756E2164756E21
6106
+ //# debugId=2583E6527A9527C764756E2164756E21