s3db.js 12.2.2 → 12.2.4
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.
- package/README.md +915 -1669
- package/dist/s3db.cjs.js +308 -33
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +308 -33
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/costs.plugin.js +38 -32
- package/src/plugins/vector.plugin.js +361 -9
package/dist/s3db.es.js
CHANGED
|
@@ -6976,18 +6976,13 @@ class CachePlugin extends Plugin {
|
|
|
6976
6976
|
}
|
|
6977
6977
|
}
|
|
6978
6978
|
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
considerFreeTier: false,
|
|
6987
|
-
// Flag to consider AWS free tier in calculations
|
|
6988
|
-
region: "us-east-1",
|
|
6989
|
-
// AWS region for pricing (future use)
|
|
6990
|
-
...options
|
|
6979
|
+
class CostsPlugin extends Plugin {
|
|
6980
|
+
constructor(config = {}) {
|
|
6981
|
+
super(config);
|
|
6982
|
+
this.config = {
|
|
6983
|
+
considerFreeTier: config.considerFreeTier !== void 0 ? config.considerFreeTier : false,
|
|
6984
|
+
region: config.region || "us-east-1",
|
|
6985
|
+
...config
|
|
6991
6986
|
};
|
|
6992
6987
|
this.map = {
|
|
6993
6988
|
PutObjectCommand: "put",
|
|
@@ -7081,14 +7076,20 @@ const CostsPlugin = {
|
|
|
7081
7076
|
// Data transfer out cost
|
|
7082
7077
|
}
|
|
7083
7078
|
};
|
|
7079
|
+
}
|
|
7080
|
+
async onInstall() {
|
|
7081
|
+
if (!this.database || !this.database.client) {
|
|
7082
|
+
return;
|
|
7083
|
+
}
|
|
7084
|
+
this.client = this.database.client;
|
|
7084
7085
|
this.client.costs = JSON.parse(JSON.stringify(this.costs));
|
|
7085
|
-
}
|
|
7086
|
-
async
|
|
7086
|
+
}
|
|
7087
|
+
async onStart() {
|
|
7087
7088
|
if (this.client) {
|
|
7088
7089
|
this.client.on("command.response", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
7089
7090
|
this.client.on("command.error", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
7090
7091
|
}
|
|
7091
|
-
}
|
|
7092
|
+
}
|
|
7092
7093
|
addRequest(name, method, response = {}, input = {}) {
|
|
7093
7094
|
if (!method) return;
|
|
7094
7095
|
this.costs.requests.totalEvents++;
|
|
@@ -7128,7 +7129,7 @@ const CostsPlugin = {
|
|
|
7128
7129
|
this.client.costs.requests.subtotal += requestCost;
|
|
7129
7130
|
}
|
|
7130
7131
|
this.updateTotal();
|
|
7131
|
-
}
|
|
7132
|
+
}
|
|
7132
7133
|
trackStorage(bytes) {
|
|
7133
7134
|
this.costs.storage.totalBytes += bytes;
|
|
7134
7135
|
this.costs.storage.totalGB = this.costs.storage.totalBytes / (1024 * 1024 * 1024);
|
|
@@ -7139,7 +7140,7 @@ const CostsPlugin = {
|
|
|
7139
7140
|
this.client.costs.storage.subtotal = this.calculateStorageCost(this.client.costs.storage);
|
|
7140
7141
|
}
|
|
7141
7142
|
this.updateTotal();
|
|
7142
|
-
}
|
|
7143
|
+
}
|
|
7143
7144
|
trackDataTransferIn(bytes) {
|
|
7144
7145
|
this.costs.dataTransfer.inBytes += bytes;
|
|
7145
7146
|
this.costs.dataTransfer.inGB = this.costs.dataTransfer.inBytes / (1024 * 1024 * 1024);
|
|
@@ -7148,7 +7149,7 @@ const CostsPlugin = {
|
|
|
7148
7149
|
this.client.costs.dataTransfer.inGB = this.client.costs.dataTransfer.inBytes / (1024 * 1024 * 1024);
|
|
7149
7150
|
}
|
|
7150
7151
|
this.updateTotal();
|
|
7151
|
-
}
|
|
7152
|
+
}
|
|
7152
7153
|
trackDataTransferOut(bytes) {
|
|
7153
7154
|
this.costs.dataTransfer.outBytes += bytes;
|
|
7154
7155
|
this.costs.dataTransfer.outGB = this.costs.dataTransfer.outBytes / (1024 * 1024 * 1024);
|
|
@@ -7159,7 +7160,7 @@ const CostsPlugin = {
|
|
|
7159
7160
|
this.client.costs.dataTransfer.subtotal = this.calculateDataTransferCost(this.client.costs.dataTransfer);
|
|
7160
7161
|
}
|
|
7161
7162
|
this.updateTotal();
|
|
7162
|
-
}
|
|
7163
|
+
}
|
|
7163
7164
|
calculateStorageCost(storage) {
|
|
7164
7165
|
const totalGB = storage.totalGB;
|
|
7165
7166
|
let cost = 0;
|
|
@@ -7178,11 +7179,11 @@ const CostsPlugin = {
|
|
|
7178
7179
|
}
|
|
7179
7180
|
}
|
|
7180
7181
|
return cost;
|
|
7181
|
-
}
|
|
7182
|
+
}
|
|
7182
7183
|
calculateDataTransferCost(dataTransfer) {
|
|
7183
7184
|
let totalGB = dataTransfer.outGB;
|
|
7184
7185
|
let cost = 0;
|
|
7185
|
-
if (this.
|
|
7186
|
+
if (this.config && this.config.considerFreeTier) {
|
|
7186
7187
|
const freeTierRemaining = dataTransfer.freeTierGB - dataTransfer.freeTierUsed;
|
|
7187
7188
|
if (freeTierRemaining > 0 && totalGB > 0) {
|
|
7188
7189
|
const gbToDeduct = Math.min(totalGB, freeTierRemaining);
|
|
@@ -7205,14 +7206,14 @@ const CostsPlugin = {
|
|
|
7205
7206
|
}
|
|
7206
7207
|
}
|
|
7207
7208
|
return cost;
|
|
7208
|
-
}
|
|
7209
|
+
}
|
|
7209
7210
|
updateTotal() {
|
|
7210
7211
|
this.costs.total = this.costs.requests.subtotal + this.costs.storage.subtotal + this.costs.dataTransfer.subtotal;
|
|
7211
7212
|
if (this.client && this.client.costs) {
|
|
7212
7213
|
this.client.costs.total = this.client.costs.requests.subtotal + this.client.costs.storage.subtotal + this.client.costs.dataTransfer.subtotal;
|
|
7213
7214
|
}
|
|
7214
7215
|
}
|
|
7215
|
-
}
|
|
7216
|
+
}
|
|
7216
7217
|
|
|
7217
7218
|
function createConfig(options, detectedTimezone) {
|
|
7218
7219
|
const consolidation = options.consolidation || {};
|
|
@@ -21030,7 +21031,7 @@ class Database extends EventEmitter {
|
|
|
21030
21031
|
this.id = idGenerator(7);
|
|
21031
21032
|
this.version = "1";
|
|
21032
21033
|
this.s3dbVersion = (() => {
|
|
21033
|
-
const [ok, err, version] = tryFn(() => true ? "12.2.
|
|
21034
|
+
const [ok, err, version] = tryFn(() => true ? "12.2.4" : "latest");
|
|
21034
21035
|
return ok ? version : "latest";
|
|
21035
21036
|
})();
|
|
21036
21037
|
this._resourcesMap = {};
|
|
@@ -36855,6 +36856,7 @@ class VectorPlugin extends Plugin {
|
|
|
36855
36856
|
*
|
|
36856
36857
|
* Detects large vector fields and warns if proper behavior is not set.
|
|
36857
36858
|
* Can optionally auto-fix by setting body-overflow behavior.
|
|
36859
|
+
* Auto-creates partitions for optional embedding fields to enable O(1) filtering.
|
|
36858
36860
|
*/
|
|
36859
36861
|
validateVectorStorage() {
|
|
36860
36862
|
for (const resource of Object.values(this.database.resources)) {
|
|
@@ -36891,7 +36893,216 @@ class VectorPlugin extends Plugin {
|
|
|
36891
36893
|
}
|
|
36892
36894
|
}
|
|
36893
36895
|
}
|
|
36896
|
+
this.setupEmbeddingPartitions(resource, vectorFields);
|
|
36897
|
+
}
|
|
36898
|
+
}
|
|
36899
|
+
/**
|
|
36900
|
+
* Setup automatic partitions for optional embedding fields
|
|
36901
|
+
*
|
|
36902
|
+
* Creates a partition that separates records with embeddings from those without.
|
|
36903
|
+
* This enables O(1) filtering instead of O(n) full scans when searching/clustering.
|
|
36904
|
+
*
|
|
36905
|
+
* @param {Resource} resource - Resource instance
|
|
36906
|
+
* @param {Array} vectorFields - Detected vector fields with metadata
|
|
36907
|
+
*/
|
|
36908
|
+
setupEmbeddingPartitions(resource, vectorFields) {
|
|
36909
|
+
if (!resource.config) return;
|
|
36910
|
+
for (const vectorField of vectorFields) {
|
|
36911
|
+
const isOptional = this.isFieldOptional(resource.schema.attributes, vectorField.name);
|
|
36912
|
+
if (!isOptional) continue;
|
|
36913
|
+
const partitionName = `byHas${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
36914
|
+
const trackingFieldName = `_has${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
36915
|
+
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
36916
|
+
this.emit("vector:partition-exists", {
|
|
36917
|
+
resource: resource.name,
|
|
36918
|
+
vectorField: vectorField.name,
|
|
36919
|
+
partition: partitionName,
|
|
36920
|
+
timestamp: Date.now()
|
|
36921
|
+
});
|
|
36922
|
+
continue;
|
|
36923
|
+
}
|
|
36924
|
+
if (!resource.config.partitions) {
|
|
36925
|
+
resource.config.partitions = {};
|
|
36926
|
+
}
|
|
36927
|
+
resource.config.partitions[partitionName] = {
|
|
36928
|
+
fields: {
|
|
36929
|
+
[trackingFieldName]: "boolean"
|
|
36930
|
+
}
|
|
36931
|
+
};
|
|
36932
|
+
if (!resource.schema.attributes[trackingFieldName]) {
|
|
36933
|
+
resource.schema.attributes[trackingFieldName] = {
|
|
36934
|
+
type: "boolean",
|
|
36935
|
+
optional: true,
|
|
36936
|
+
default: false
|
|
36937
|
+
};
|
|
36938
|
+
}
|
|
36939
|
+
this.emit("vector:partition-created", {
|
|
36940
|
+
resource: resource.name,
|
|
36941
|
+
vectorField: vectorField.name,
|
|
36942
|
+
partition: partitionName,
|
|
36943
|
+
trackingField: trackingFieldName,
|
|
36944
|
+
timestamp: Date.now()
|
|
36945
|
+
});
|
|
36946
|
+
console.log(`\u2705 VectorPlugin: Created partition '${partitionName}' for optional embedding field '${vectorField.name}' in resource '${resource.name}'`);
|
|
36947
|
+
this.installEmbeddingHooks(resource, vectorField.name, trackingFieldName);
|
|
36948
|
+
}
|
|
36949
|
+
}
|
|
36950
|
+
/**
|
|
36951
|
+
* Check if a field is optional in the schema
|
|
36952
|
+
*
|
|
36953
|
+
* @param {Object} attributes - Resource attributes
|
|
36954
|
+
* @param {string} fieldPath - Field path (supports dot notation)
|
|
36955
|
+
* @returns {boolean} True if field is optional
|
|
36956
|
+
*/
|
|
36957
|
+
isFieldOptional(attributes, fieldPath) {
|
|
36958
|
+
const parts = fieldPath.split(".");
|
|
36959
|
+
let current = attributes;
|
|
36960
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36961
|
+
const part = parts[i];
|
|
36962
|
+
const attr = current[part];
|
|
36963
|
+
if (!attr) return true;
|
|
36964
|
+
if (typeof attr === "string") {
|
|
36965
|
+
const flags = attr.split("|");
|
|
36966
|
+
if (flags.includes("required")) return false;
|
|
36967
|
+
if (flags.includes("optional") || flags.some((f) => f.startsWith("optional:"))) return true;
|
|
36968
|
+
return !flags.includes("required");
|
|
36969
|
+
}
|
|
36970
|
+
if (typeof attr === "object") {
|
|
36971
|
+
if (i === parts.length - 1) {
|
|
36972
|
+
if (attr.optional === true) return true;
|
|
36973
|
+
if (attr.optional === false) return false;
|
|
36974
|
+
return attr.optional !== false;
|
|
36975
|
+
}
|
|
36976
|
+
if (attr.type === "object" && attr.props) {
|
|
36977
|
+
current = attr.props;
|
|
36978
|
+
} else {
|
|
36979
|
+
return true;
|
|
36980
|
+
}
|
|
36981
|
+
}
|
|
36982
|
+
}
|
|
36983
|
+
return true;
|
|
36984
|
+
}
|
|
36985
|
+
/**
|
|
36986
|
+
* Capitalize first letter of string
|
|
36987
|
+
*
|
|
36988
|
+
* @param {string} str - Input string
|
|
36989
|
+
* @returns {string} Capitalized string
|
|
36990
|
+
*/
|
|
36991
|
+
capitalize(str) {
|
|
36992
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
36993
|
+
}
|
|
36994
|
+
/**
|
|
36995
|
+
* Install hooks to maintain embedding partition tracking field
|
|
36996
|
+
*
|
|
36997
|
+
* @param {Resource} resource - Resource instance
|
|
36998
|
+
* @param {string} vectorField - Vector field name
|
|
36999
|
+
* @param {string} trackingField - Tracking field name
|
|
37000
|
+
*/
|
|
37001
|
+
installEmbeddingHooks(resource, vectorField, trackingField) {
|
|
37002
|
+
resource.registerHook("beforeInsert", async (data) => {
|
|
37003
|
+
const hasVector = this.hasVectorValue(data, vectorField);
|
|
37004
|
+
this.setNestedValue(data, trackingField, hasVector);
|
|
37005
|
+
return data;
|
|
37006
|
+
});
|
|
37007
|
+
resource.registerHook("beforeUpdate", async (id, updates) => {
|
|
37008
|
+
if (vectorField in updates || this.hasNestedKey(updates, vectorField)) {
|
|
37009
|
+
const hasVector = this.hasVectorValue(updates, vectorField);
|
|
37010
|
+
this.setNestedValue(updates, trackingField, hasVector);
|
|
37011
|
+
}
|
|
37012
|
+
return updates;
|
|
37013
|
+
});
|
|
37014
|
+
this.emit("vector:hooks-installed", {
|
|
37015
|
+
resource: resource.name,
|
|
37016
|
+
vectorField,
|
|
37017
|
+
trackingField,
|
|
37018
|
+
hooks: ["beforeInsert", "beforeUpdate"],
|
|
37019
|
+
timestamp: Date.now()
|
|
37020
|
+
});
|
|
37021
|
+
}
|
|
37022
|
+
/**
|
|
37023
|
+
* Check if data has a valid vector value for the given field
|
|
37024
|
+
*
|
|
37025
|
+
* @param {Object} data - Data object
|
|
37026
|
+
* @param {string} fieldPath - Field path (supports dot notation)
|
|
37027
|
+
* @returns {boolean} True if vector exists and is valid
|
|
37028
|
+
*/
|
|
37029
|
+
hasVectorValue(data, fieldPath) {
|
|
37030
|
+
const value = this.getNestedValue(data, fieldPath);
|
|
37031
|
+
return value != null && Array.isArray(value) && value.length > 0;
|
|
37032
|
+
}
|
|
37033
|
+
/**
|
|
37034
|
+
* Check if object has a nested key
|
|
37035
|
+
*
|
|
37036
|
+
* @param {Object} obj - Object to check
|
|
37037
|
+
* @param {string} path - Dot-notation path
|
|
37038
|
+
* @returns {boolean} True if key exists
|
|
37039
|
+
*/
|
|
37040
|
+
hasNestedKey(obj, path) {
|
|
37041
|
+
const parts = path.split(".");
|
|
37042
|
+
let current = obj;
|
|
37043
|
+
for (const part of parts) {
|
|
37044
|
+
if (current == null || typeof current !== "object") return false;
|
|
37045
|
+
if (!(part in current)) return false;
|
|
37046
|
+
current = current[part];
|
|
36894
37047
|
}
|
|
37048
|
+
return true;
|
|
37049
|
+
}
|
|
37050
|
+
/**
|
|
37051
|
+
* Get nested value from object using dot notation
|
|
37052
|
+
*
|
|
37053
|
+
* @param {Object} obj - Object to traverse
|
|
37054
|
+
* @param {string} path - Dot-notation path
|
|
37055
|
+
* @returns {*} Value at path or undefined
|
|
37056
|
+
*/
|
|
37057
|
+
getNestedValue(obj, path) {
|
|
37058
|
+
const parts = path.split(".");
|
|
37059
|
+
let current = obj;
|
|
37060
|
+
for (const part of parts) {
|
|
37061
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
37062
|
+
current = current[part];
|
|
37063
|
+
}
|
|
37064
|
+
return current;
|
|
37065
|
+
}
|
|
37066
|
+
/**
|
|
37067
|
+
* Set nested value in object using dot notation
|
|
37068
|
+
*
|
|
37069
|
+
* @param {Object} obj - Object to modify
|
|
37070
|
+
* @param {string} path - Dot-notation path
|
|
37071
|
+
* @param {*} value - Value to set
|
|
37072
|
+
*/
|
|
37073
|
+
setNestedValue(obj, path, value) {
|
|
37074
|
+
const parts = path.split(".");
|
|
37075
|
+
let current = obj;
|
|
37076
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
37077
|
+
const part = parts[i];
|
|
37078
|
+
if (!(part in current) || typeof current[part] !== "object") {
|
|
37079
|
+
current[part] = {};
|
|
37080
|
+
}
|
|
37081
|
+
current = current[part];
|
|
37082
|
+
}
|
|
37083
|
+
current[parts[parts.length - 1]] = value;
|
|
37084
|
+
}
|
|
37085
|
+
/**
|
|
37086
|
+
* Get auto-created embedding partition for a vector field
|
|
37087
|
+
*
|
|
37088
|
+
* Returns partition configuration if an auto-partition exists for the given vector field.
|
|
37089
|
+
* Auto-partitions enable O(1) filtering to only records with embeddings.
|
|
37090
|
+
*
|
|
37091
|
+
* @param {Resource} resource - Resource instance
|
|
37092
|
+
* @param {string} vectorField - Vector field name
|
|
37093
|
+
* @returns {Object|null} Partition config or null
|
|
37094
|
+
*/
|
|
37095
|
+
getAutoEmbeddingPartition(resource, vectorField) {
|
|
37096
|
+
if (!resource.config) return null;
|
|
37097
|
+
const partitionName = `byHas${this.capitalize(vectorField.replace(/\./g, "_"))}`;
|
|
37098
|
+
const trackingFieldName = `_has${this.capitalize(vectorField.replace(/\./g, "_"))}`;
|
|
37099
|
+
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
37100
|
+
return {
|
|
37101
|
+
partitionName,
|
|
37102
|
+
partitionValues: { [trackingFieldName]: true }
|
|
37103
|
+
};
|
|
37104
|
+
}
|
|
37105
|
+
return null;
|
|
36895
37106
|
}
|
|
36896
37107
|
/**
|
|
36897
37108
|
* Auto-detect vector field from resource schema
|
|
@@ -37030,11 +37241,12 @@ class VectorPlugin extends Plugin {
|
|
|
37030
37241
|
} else if (!vectorField) {
|
|
37031
37242
|
vectorField = "vector";
|
|
37032
37243
|
}
|
|
37033
|
-
|
|
37244
|
+
let {
|
|
37034
37245
|
limit = 10,
|
|
37035
37246
|
distanceMetric = this.config.distanceMetric,
|
|
37036
37247
|
threshold = null,
|
|
37037
|
-
partition = null
|
|
37248
|
+
partition = null,
|
|
37249
|
+
partitionValues = null
|
|
37038
37250
|
} = options;
|
|
37039
37251
|
const distanceFn = this.distanceFunctions[distanceMetric];
|
|
37040
37252
|
if (!distanceFn) {
|
|
@@ -37050,31 +37262,61 @@ class VectorPlugin extends Plugin {
|
|
|
37050
37262
|
});
|
|
37051
37263
|
throw error;
|
|
37052
37264
|
}
|
|
37265
|
+
if (!partition) {
|
|
37266
|
+
const autoPartition = this.getAutoEmbeddingPartition(resource, vectorField);
|
|
37267
|
+
if (autoPartition) {
|
|
37268
|
+
partition = autoPartition.partitionName;
|
|
37269
|
+
partitionValues = autoPartition.partitionValues;
|
|
37270
|
+
this._emitEvent("vector:auto-partition-used", {
|
|
37271
|
+
resource: resource.name,
|
|
37272
|
+
vectorField,
|
|
37273
|
+
partition,
|
|
37274
|
+
partitionValues,
|
|
37275
|
+
timestamp: Date.now()
|
|
37276
|
+
});
|
|
37277
|
+
}
|
|
37278
|
+
}
|
|
37053
37279
|
this._emitEvent("vector:search-start", {
|
|
37054
37280
|
resource: resource.name,
|
|
37055
37281
|
vectorField,
|
|
37056
37282
|
limit,
|
|
37057
37283
|
distanceMetric,
|
|
37058
37284
|
partition,
|
|
37285
|
+
partitionValues,
|
|
37059
37286
|
threshold,
|
|
37060
37287
|
queryDimensions: queryVector.length,
|
|
37061
37288
|
timestamp: startTime
|
|
37062
37289
|
});
|
|
37063
37290
|
try {
|
|
37064
37291
|
let allRecords;
|
|
37065
|
-
if (partition) {
|
|
37292
|
+
if (partition && partitionValues) {
|
|
37066
37293
|
this._emitEvent("vector:partition-filter", {
|
|
37067
37294
|
resource: resource.name,
|
|
37068
37295
|
partition,
|
|
37296
|
+
partitionValues,
|
|
37069
37297
|
timestamp: Date.now()
|
|
37070
37298
|
});
|
|
37071
|
-
allRecords = await resource.list({ partition, partitionValues
|
|
37299
|
+
allRecords = await resource.list({ partition, partitionValues });
|
|
37072
37300
|
} else {
|
|
37073
|
-
allRecords = await resource.getAll();
|
|
37301
|
+
allRecords = resource.getAll ? await resource.getAll() : await resource.list();
|
|
37074
37302
|
}
|
|
37075
37303
|
const totalRecords = allRecords.length;
|
|
37076
37304
|
let processedRecords = 0;
|
|
37077
37305
|
let dimensionMismatches = 0;
|
|
37306
|
+
if (!partition && totalRecords > 1e3) {
|
|
37307
|
+
const warning = {
|
|
37308
|
+
resource: resource.name,
|
|
37309
|
+
operation: "vectorSearch",
|
|
37310
|
+
totalRecords,
|
|
37311
|
+
vectorField,
|
|
37312
|
+
recommendation: "Use partitions to filter data before vector search for better performance"
|
|
37313
|
+
};
|
|
37314
|
+
this._emitEvent("vector:performance-warning", warning);
|
|
37315
|
+
console.warn(`\u26A0\uFE0F VectorPlugin: Performing vectorSearch on ${totalRecords} records without partition filter`);
|
|
37316
|
+
console.warn(` Resource: '${resource.name}'`);
|
|
37317
|
+
console.warn(` Recommendation: Use partition parameter to reduce search space`);
|
|
37318
|
+
console.warn(` Example: resource.vectorSearch(vector, { partition: 'byCategory', partitionValues: { category: 'books' } })`);
|
|
37319
|
+
}
|
|
37078
37320
|
const results = allRecords.filter((record) => record[vectorField] && Array.isArray(record[vectorField])).map((record, index) => {
|
|
37079
37321
|
try {
|
|
37080
37322
|
const distance = distanceFn(queryVector, record[vectorField]);
|
|
@@ -37158,10 +37400,11 @@ class VectorPlugin extends Plugin {
|
|
|
37158
37400
|
} else if (!vectorField) {
|
|
37159
37401
|
vectorField = "vector";
|
|
37160
37402
|
}
|
|
37161
|
-
|
|
37403
|
+
let {
|
|
37162
37404
|
k = 5,
|
|
37163
37405
|
distanceMetric = this.config.distanceMetric,
|
|
37164
37406
|
partition = null,
|
|
37407
|
+
partitionValues = null,
|
|
37165
37408
|
...kmeansOptions
|
|
37166
37409
|
} = options;
|
|
37167
37410
|
const distanceFn = this.distanceFunctions[distanceMetric];
|
|
@@ -37178,30 +37421,62 @@ class VectorPlugin extends Plugin {
|
|
|
37178
37421
|
});
|
|
37179
37422
|
throw error;
|
|
37180
37423
|
}
|
|
37424
|
+
if (!partition) {
|
|
37425
|
+
const autoPartition = this.getAutoEmbeddingPartition(resource, vectorField);
|
|
37426
|
+
if (autoPartition) {
|
|
37427
|
+
partition = autoPartition.partitionName;
|
|
37428
|
+
partitionValues = autoPartition.partitionValues;
|
|
37429
|
+
this._emitEvent("vector:auto-partition-used", {
|
|
37430
|
+
resource: resource.name,
|
|
37431
|
+
vectorField,
|
|
37432
|
+
partition,
|
|
37433
|
+
partitionValues,
|
|
37434
|
+
timestamp: Date.now()
|
|
37435
|
+
});
|
|
37436
|
+
}
|
|
37437
|
+
}
|
|
37181
37438
|
this._emitEvent("vector:cluster-start", {
|
|
37182
37439
|
resource: resource.name,
|
|
37183
37440
|
vectorField,
|
|
37184
37441
|
k,
|
|
37185
37442
|
distanceMetric,
|
|
37186
37443
|
partition,
|
|
37444
|
+
partitionValues,
|
|
37187
37445
|
maxIterations: kmeansOptions.maxIterations || 100,
|
|
37188
37446
|
timestamp: startTime
|
|
37189
37447
|
});
|
|
37190
37448
|
try {
|
|
37191
37449
|
let allRecords;
|
|
37192
|
-
if (partition) {
|
|
37450
|
+
if (partition && partitionValues) {
|
|
37193
37451
|
this._emitEvent("vector:partition-filter", {
|
|
37194
37452
|
resource: resource.name,
|
|
37195
37453
|
partition,
|
|
37454
|
+
partitionValues,
|
|
37196
37455
|
timestamp: Date.now()
|
|
37197
37456
|
});
|
|
37198
|
-
allRecords = await resource.list({ partition, partitionValues
|
|
37457
|
+
allRecords = await resource.list({ partition, partitionValues });
|
|
37199
37458
|
} else {
|
|
37200
|
-
allRecords = await resource.getAll();
|
|
37459
|
+
allRecords = resource.getAll ? await resource.getAll() : await resource.list();
|
|
37201
37460
|
}
|
|
37202
37461
|
const recordsWithVectors = allRecords.filter(
|
|
37203
37462
|
(record) => record[vectorField] && Array.isArray(record[vectorField])
|
|
37204
37463
|
);
|
|
37464
|
+
if (!partition && allRecords.length > 1e3) {
|
|
37465
|
+
const warning = {
|
|
37466
|
+
resource: resource.name,
|
|
37467
|
+
operation: "cluster",
|
|
37468
|
+
totalRecords: allRecords.length,
|
|
37469
|
+
recordsWithVectors: recordsWithVectors.length,
|
|
37470
|
+
vectorField,
|
|
37471
|
+
recommendation: "Use partitions to filter data before clustering for better performance"
|
|
37472
|
+
};
|
|
37473
|
+
this._emitEvent("vector:performance-warning", warning);
|
|
37474
|
+
console.warn(`\u26A0\uFE0F VectorPlugin: Performing clustering on ${allRecords.length} records without partition filter`);
|
|
37475
|
+
console.warn(` Resource: '${resource.name}'`);
|
|
37476
|
+
console.warn(` Records with vectors: ${recordsWithVectors.length}`);
|
|
37477
|
+
console.warn(` Recommendation: Use partition parameter to reduce clustering space`);
|
|
37478
|
+
console.warn(` Example: resource.cluster({ k: 5, partition: 'byCategory', partitionValues: { category: 'books' } })`);
|
|
37479
|
+
}
|
|
37205
37480
|
if (recordsWithVectors.length === 0) {
|
|
37206
37481
|
const error = new VectorError("No vectors found in resource", {
|
|
37207
37482
|
operation: "cluster",
|