@workglow/indexeddb 0.3.14 → 0.3.16

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.
@@ -323,7 +323,8 @@ import {
323
323
  BaseTabularStorage,
324
324
  HybridSubscriptionManager,
325
325
  isSearchCondition,
326
- pickCoveringIndex
326
+ pickCoveringIndex,
327
+ safeEmit
327
328
  } from "@workglow/storage";
328
329
  import { createServiceToken, deepEqual as deepEqual2, makeFingerprint, uuid4 } from "@workglow/util";
329
330
 
@@ -743,8 +744,8 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
743
744
  hybridManager = null;
744
745
  hybridOptions;
745
746
  cursorSafeIndexes;
746
- constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], migrationOptions = {}, clientProvidedKeys = "if-missing", tabularMigrations) {
747
- super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, table);
747
+ constructor(table = "tabular_store", schema, primaryKeyNames, indexes = [], migrationOptions = {}, clientProvidedKeys = "if-missing", tabularMigrations, uniqueIndexes = []) {
748
+ super(schema, primaryKeyNames, indexes, clientProvidedKeys, tabularMigrations, table, uniqueIndexes);
748
749
  this.table = table;
749
750
  this.migrationOptions = migrationOptions;
750
751
  this.hybridOptions = {
@@ -824,6 +825,15 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
824
825
  options: { unique: false }
825
826
  });
826
827
  }
828
+ for (const columns of this.uniqueIndexes) {
829
+ const columnNames = columns.map((col) => String(col));
830
+ const indexName = columnNames.join("_");
831
+ expectedIndexes.push({
832
+ name: indexName,
833
+ keyPath: columnNames.length === 1 ? columnNames[0] : columnNames,
834
+ options: { unique: true }
835
+ });
836
+ }
827
837
  const primaryKey = pkColumns.length === 1 ? pkColumns[0] : pkColumns;
828
838
  const useAutoIncrement = this.hasAutoGeneratedKey() && this.autoGeneratedKeyStrategy === "autoincrement" && pkColumns.length === 1;
829
839
  return await ensureIndexedDbTable(this.table, primaryKey, expectedIndexes, this.migrationOptions, useAutoIncrement);
@@ -841,39 +851,43 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
841
851
  }
842
852
  throw new Error(`IndexedDB autoincrement keys are generated by the database, not client-side. Column: ${columnName}`);
843
853
  }
844
- async put(record) {
845
- const db = await this.getDb();
854
+ prepareRecordForPut(record) {
846
855
  let recordToStore = record;
847
- if (this.hasAutoGeneratedKey() && this.autoGeneratedKeyName) {
848
- const keyName = String(this.autoGeneratedKeyName);
849
- const clientProvidedValue = record[keyName];
850
- const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;
851
- if (this.autoGeneratedKeyStrategy === "uuid") {
852
- let shouldGenerate = false;
853
- if (this.clientProvidedKeys === "never") {
854
- shouldGenerate = true;
855
- } else if (this.clientProvidedKeys === "always") {
856
- if (!hasClientValue) {
857
- throw new Error(`Auto-generated key "${keyName}" is required when clientProvidedKeys is "always"`);
858
- }
859
- shouldGenerate = false;
860
- } else {
861
- shouldGenerate = !hasClientValue;
862
- }
863
- if (shouldGenerate) {
864
- const generatedValue = this.generateKeyValue(keyName, "uuid");
865
- recordToStore = { ...record, [keyName]: generatedValue };
866
- }
867
- } else if (this.autoGeneratedKeyStrategy === "autoincrement") {
868
- if (this.clientProvidedKeys === "always" && !hasClientValue) {
856
+ if (!this.hasAutoGeneratedKey() || !this.autoGeneratedKeyName)
857
+ return recordToStore;
858
+ const keyName = String(this.autoGeneratedKeyName);
859
+ const clientProvidedValue = record[keyName];
860
+ const hasClientValue = clientProvidedValue !== undefined && clientProvidedValue !== null;
861
+ if (this.autoGeneratedKeyStrategy === "uuid") {
862
+ let shouldGenerate = false;
863
+ if (this.clientProvidedKeys === "never") {
864
+ shouldGenerate = true;
865
+ } else if (this.clientProvidedKeys === "always") {
866
+ if (!hasClientValue) {
869
867
  throw new Error(`Auto-generated key "${keyName}" is required when clientProvidedKeys is "always"`);
870
868
  }
871
- if (this.clientProvidedKeys === "never") {
872
- const { [keyName]: _, ...rest } = record;
873
- recordToStore = rest;
874
- }
869
+ shouldGenerate = false;
870
+ } else {
871
+ shouldGenerate = !hasClientValue;
872
+ }
873
+ if (shouldGenerate) {
874
+ const generatedValue = this.generateKeyValue(keyName, "uuid");
875
+ recordToStore = { ...record, [keyName]: generatedValue };
876
+ }
877
+ } else if (this.autoGeneratedKeyStrategy === "autoincrement") {
878
+ if (this.clientProvidedKeys === "always" && !hasClientValue) {
879
+ throw new Error(`Auto-generated key "${keyName}" is required when clientProvidedKeys is "always"`);
880
+ }
881
+ if (this.clientProvidedKeys === "never") {
882
+ const { [keyName]: _, ...rest } = record;
883
+ recordToStore = rest;
875
884
  }
876
885
  }
886
+ return recordToStore;
887
+ }
888
+ async put(record) {
889
+ const db = await this.getDb();
890
+ let recordToStore = this.prepareRecordForPut(record);
877
891
  return new Promise((resolve, reject) => {
878
892
  const transaction = db.transaction(this.table, "readwrite");
879
893
  const store = transaction.objectStore(this.table);
@@ -899,6 +913,78 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
899
913
  async putBulk(records) {
900
914
  return await Promise.all(records.map((record) => this.put(record)));
901
915
  }
916
+ async putBulkInTransaction(records) {
917
+ if (records.length === 0)
918
+ return [];
919
+ const prepared = records.map((r) => this.prepareRecordForPut(r));
920
+ const run = async () => {
921
+ const db = await this.getDb();
922
+ return new Promise((resolve, reject) => {
923
+ let tx;
924
+ try {
925
+ tx = db.transaction(this.table, "readwrite");
926
+ } catch (err) {
927
+ reject(err);
928
+ return;
929
+ }
930
+ const store = tx.objectStore(this.table);
931
+ const results = new Array(prepared.length);
932
+ const useAutoincrement = this.hasAutoGeneratedKey() && this.autoGeneratedKeyName !== undefined && this.autoGeneratedKeyStrategy === "autoincrement";
933
+ const keyName = useAutoincrement ? String(this.autoGeneratedKeyName) : undefined;
934
+ tx.oncomplete = () => {
935
+ for (const r of results)
936
+ safeEmit(this.events, "put", r);
937
+ this.hybridManager?.notifyLocalChange();
938
+ resolve(results);
939
+ };
940
+ tx.onerror = () => reject(tx.error);
941
+ tx.onabort = () => reject(tx.error ?? new Error("putBulk transaction aborted"));
942
+ try {
943
+ for (let i = 0;i < prepared.length; i++) {
944
+ const recordToStore = prepared[i];
945
+ const request = store.put(recordToStore);
946
+ request.onsuccess = () => {
947
+ let finalRecord = recordToStore;
948
+ if (useAutoincrement && keyName !== undefined) {
949
+ if (finalRecord[keyName] === undefined) {
950
+ finalRecord = { ...finalRecord, [keyName]: request.result };
951
+ }
952
+ }
953
+ results[i] = finalRecord;
954
+ };
955
+ request.onerror = () => {};
956
+ }
957
+ } catch (err) {
958
+ try {
959
+ tx.abort();
960
+ } catch {}
961
+ reject(err);
962
+ }
963
+ });
964
+ };
965
+ try {
966
+ return await run();
967
+ } catch (err) {
968
+ if (err instanceof DOMException && err.name === "InvalidStateError") {
969
+ try {
970
+ this.db?.close();
971
+ } catch {}
972
+ this.db = undefined;
973
+ try {
974
+ return await run();
975
+ } catch (retryErr) {
976
+ safeEmit(this.events, "rollback", {
977
+ op: "putBulkInTransaction",
978
+ error: retryErr,
979
+ ids: []
980
+ });
981
+ throw retryErr;
982
+ }
983
+ }
984
+ safeEmit(this.events, "rollback", { op: "putBulkInTransaction", error: err, ids: [] });
985
+ throw err;
986
+ }
987
+ }
902
988
  getPrimaryKeyAsOrderedArray(key) {
903
989
  return super.getPrimaryKeyAsOrderedArray(key).map((value) => typeof value === "bigint" ? value.toString() : value);
904
990
  }
@@ -1013,7 +1099,7 @@ class IndexedDbTabularStorage extends BaseTabularStorage {
1013
1099
  if (this.cursorSafeIndexes)
1014
1100
  return this.cursorSafeIndexes;
1015
1101
  const required = new Set(this.schema.required ?? []);
1016
- this.cursorSafeIndexes = this.indexes.filter((columns) => columns.every((column) => required.has(String(column))));
1102
+ this.cursorSafeIndexes = [...this.indexes, ...this.uniqueIndexes].filter((columns) => columns.every((column) => required.has(String(column))));
1017
1103
  return this.cursorSafeIndexes;
1018
1104
  }
1019
1105
  createIndexedRange(store, criteria) {
@@ -1506,18 +1592,16 @@ class IndexedDbKvStorage extends KvViaTabularStorage {
1506
1592
  }
1507
1593
  }
1508
1594
  // src/storage/IndexedDbVectorStorage.ts
1509
- import { getMetadataProperty, getVectorProperty } from "@workglow/storage";
1595
+ import {
1596
+ assertVectorShape,
1597
+ getMetadataProperty,
1598
+ getVectorProperty,
1599
+ matchesFilter,
1600
+ validateVectorEntities
1601
+ } from "@workglow/storage";
1510
1602
  import { createServiceToken as createServiceToken3 } from "@workglow/util";
1511
1603
  import { cosineSimilarity } from "@workglow/util/schema";
1512
1604
  var IDB_VECTOR_REPOSITORY = createServiceToken3("storage.vectorRepository.indexedDb");
1513
- function matchesFilter(metadata, filter) {
1514
- for (const [key, value] of Object.entries(filter)) {
1515
- if (metadata[key] !== value) {
1516
- return false;
1517
- }
1518
- }
1519
- return true;
1520
- }
1521
1605
 
1522
1606
  class IndexedDbVectorStorage extends IndexedDbTabularStorage {
1523
1607
  vectorDimensions;
@@ -1536,7 +1620,16 @@ class IndexedDbVectorStorage extends IndexedDbTabularStorage {
1536
1620
  getVectorDimensions() {
1537
1621
  return this.vectorDimensions;
1538
1622
  }
1623
+ async put(record) {
1624
+ assertVectorShape(record[this.vectorPropertyName], this.vectorDimensions, "write");
1625
+ return super.put(record);
1626
+ }
1627
+ async putBulk(records) {
1628
+ validateVectorEntities(records, this.vectorPropertyName, this.vectorDimensions);
1629
+ return this.putBulkInTransaction(records);
1630
+ }
1539
1631
  async similaritySearch(query, options = {}) {
1632
+ assertVectorShape(query, this.vectorDimensions, "query");
1540
1633
  const { topK = 10, filter, scoreThreshold = 0 } = options;
1541
1634
  const results = [];
1542
1635
  const allEntities = await this.getAll() || [];
@@ -1577,4 +1670,4 @@ export {
1577
1670
  IDB_KV_REPOSITORY
1578
1671
  };
1579
1672
 
1580
- //# debugId=638D72A774579A8B64756E2164756E21
1673
+ //# debugId=226E2B2B48CE7E8A64756E2164756E21