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.cjs.js
CHANGED
|
@@ -6999,18 +6999,13 @@ class CachePlugin extends Plugin {
|
|
|
6999
6999
|
}
|
|
7000
7000
|
}
|
|
7001
7001
|
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
considerFreeTier: false,
|
|
7010
|
-
// Flag to consider AWS free tier in calculations
|
|
7011
|
-
region: "us-east-1",
|
|
7012
|
-
// AWS region for pricing (future use)
|
|
7013
|
-
...options
|
|
7002
|
+
class CostsPlugin extends Plugin {
|
|
7003
|
+
constructor(config = {}) {
|
|
7004
|
+
super(config);
|
|
7005
|
+
this.config = {
|
|
7006
|
+
considerFreeTier: config.considerFreeTier !== void 0 ? config.considerFreeTier : false,
|
|
7007
|
+
region: config.region || "us-east-1",
|
|
7008
|
+
...config
|
|
7014
7009
|
};
|
|
7015
7010
|
this.map = {
|
|
7016
7011
|
PutObjectCommand: "put",
|
|
@@ -7104,14 +7099,20 @@ const CostsPlugin = {
|
|
|
7104
7099
|
// Data transfer out cost
|
|
7105
7100
|
}
|
|
7106
7101
|
};
|
|
7102
|
+
}
|
|
7103
|
+
async onInstall() {
|
|
7104
|
+
if (!this.database || !this.database.client) {
|
|
7105
|
+
return;
|
|
7106
|
+
}
|
|
7107
|
+
this.client = this.database.client;
|
|
7107
7108
|
this.client.costs = JSON.parse(JSON.stringify(this.costs));
|
|
7108
|
-
}
|
|
7109
|
-
async
|
|
7109
|
+
}
|
|
7110
|
+
async onStart() {
|
|
7110
7111
|
if (this.client) {
|
|
7111
7112
|
this.client.on("command.response", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
7112
7113
|
this.client.on("command.error", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
7113
7114
|
}
|
|
7114
|
-
}
|
|
7115
|
+
}
|
|
7115
7116
|
addRequest(name, method, response = {}, input = {}) {
|
|
7116
7117
|
if (!method) return;
|
|
7117
7118
|
this.costs.requests.totalEvents++;
|
|
@@ -7151,7 +7152,7 @@ const CostsPlugin = {
|
|
|
7151
7152
|
this.client.costs.requests.subtotal += requestCost;
|
|
7152
7153
|
}
|
|
7153
7154
|
this.updateTotal();
|
|
7154
|
-
}
|
|
7155
|
+
}
|
|
7155
7156
|
trackStorage(bytes) {
|
|
7156
7157
|
this.costs.storage.totalBytes += bytes;
|
|
7157
7158
|
this.costs.storage.totalGB = this.costs.storage.totalBytes / (1024 * 1024 * 1024);
|
|
@@ -7162,7 +7163,7 @@ const CostsPlugin = {
|
|
|
7162
7163
|
this.client.costs.storage.subtotal = this.calculateStorageCost(this.client.costs.storage);
|
|
7163
7164
|
}
|
|
7164
7165
|
this.updateTotal();
|
|
7165
|
-
}
|
|
7166
|
+
}
|
|
7166
7167
|
trackDataTransferIn(bytes) {
|
|
7167
7168
|
this.costs.dataTransfer.inBytes += bytes;
|
|
7168
7169
|
this.costs.dataTransfer.inGB = this.costs.dataTransfer.inBytes / (1024 * 1024 * 1024);
|
|
@@ -7171,7 +7172,7 @@ const CostsPlugin = {
|
|
|
7171
7172
|
this.client.costs.dataTransfer.inGB = this.client.costs.dataTransfer.inBytes / (1024 * 1024 * 1024);
|
|
7172
7173
|
}
|
|
7173
7174
|
this.updateTotal();
|
|
7174
|
-
}
|
|
7175
|
+
}
|
|
7175
7176
|
trackDataTransferOut(bytes) {
|
|
7176
7177
|
this.costs.dataTransfer.outBytes += bytes;
|
|
7177
7178
|
this.costs.dataTransfer.outGB = this.costs.dataTransfer.outBytes / (1024 * 1024 * 1024);
|
|
@@ -7182,7 +7183,7 @@ const CostsPlugin = {
|
|
|
7182
7183
|
this.client.costs.dataTransfer.subtotal = this.calculateDataTransferCost(this.client.costs.dataTransfer);
|
|
7183
7184
|
}
|
|
7184
7185
|
this.updateTotal();
|
|
7185
|
-
}
|
|
7186
|
+
}
|
|
7186
7187
|
calculateStorageCost(storage) {
|
|
7187
7188
|
const totalGB = storage.totalGB;
|
|
7188
7189
|
let cost = 0;
|
|
@@ -7201,11 +7202,11 @@ const CostsPlugin = {
|
|
|
7201
7202
|
}
|
|
7202
7203
|
}
|
|
7203
7204
|
return cost;
|
|
7204
|
-
}
|
|
7205
|
+
}
|
|
7205
7206
|
calculateDataTransferCost(dataTransfer) {
|
|
7206
7207
|
let totalGB = dataTransfer.outGB;
|
|
7207
7208
|
let cost = 0;
|
|
7208
|
-
if (this.
|
|
7209
|
+
if (this.config && this.config.considerFreeTier) {
|
|
7209
7210
|
const freeTierRemaining = dataTransfer.freeTierGB - dataTransfer.freeTierUsed;
|
|
7210
7211
|
if (freeTierRemaining > 0 && totalGB > 0) {
|
|
7211
7212
|
const gbToDeduct = Math.min(totalGB, freeTierRemaining);
|
|
@@ -7228,14 +7229,14 @@ const CostsPlugin = {
|
|
|
7228
7229
|
}
|
|
7229
7230
|
}
|
|
7230
7231
|
return cost;
|
|
7231
|
-
}
|
|
7232
|
+
}
|
|
7232
7233
|
updateTotal() {
|
|
7233
7234
|
this.costs.total = this.costs.requests.subtotal + this.costs.storage.subtotal + this.costs.dataTransfer.subtotal;
|
|
7234
7235
|
if (this.client && this.client.costs) {
|
|
7235
7236
|
this.client.costs.total = this.client.costs.requests.subtotal + this.client.costs.storage.subtotal + this.client.costs.dataTransfer.subtotal;
|
|
7236
7237
|
}
|
|
7237
7238
|
}
|
|
7238
|
-
}
|
|
7239
|
+
}
|
|
7239
7240
|
|
|
7240
7241
|
function createConfig(options, detectedTimezone) {
|
|
7241
7242
|
const consolidation = options.consolidation || {};
|
|
@@ -21053,7 +21054,7 @@ class Database extends EventEmitter {
|
|
|
21053
21054
|
this.id = idGenerator(7);
|
|
21054
21055
|
this.version = "1";
|
|
21055
21056
|
this.s3dbVersion = (() => {
|
|
21056
|
-
const [ok, err, version] = tryFn(() => true ? "12.2.
|
|
21057
|
+
const [ok, err, version] = tryFn(() => true ? "12.2.4" : "latest");
|
|
21057
21058
|
return ok ? version : "latest";
|
|
21058
21059
|
})();
|
|
21059
21060
|
this._resourcesMap = {};
|
|
@@ -36878,6 +36879,7 @@ class VectorPlugin extends Plugin {
|
|
|
36878
36879
|
*
|
|
36879
36880
|
* Detects large vector fields and warns if proper behavior is not set.
|
|
36880
36881
|
* Can optionally auto-fix by setting body-overflow behavior.
|
|
36882
|
+
* Auto-creates partitions for optional embedding fields to enable O(1) filtering.
|
|
36881
36883
|
*/
|
|
36882
36884
|
validateVectorStorage() {
|
|
36883
36885
|
for (const resource of Object.values(this.database.resources)) {
|
|
@@ -36914,7 +36916,216 @@ class VectorPlugin extends Plugin {
|
|
|
36914
36916
|
}
|
|
36915
36917
|
}
|
|
36916
36918
|
}
|
|
36919
|
+
this.setupEmbeddingPartitions(resource, vectorFields);
|
|
36920
|
+
}
|
|
36921
|
+
}
|
|
36922
|
+
/**
|
|
36923
|
+
* Setup automatic partitions for optional embedding fields
|
|
36924
|
+
*
|
|
36925
|
+
* Creates a partition that separates records with embeddings from those without.
|
|
36926
|
+
* This enables O(1) filtering instead of O(n) full scans when searching/clustering.
|
|
36927
|
+
*
|
|
36928
|
+
* @param {Resource} resource - Resource instance
|
|
36929
|
+
* @param {Array} vectorFields - Detected vector fields with metadata
|
|
36930
|
+
*/
|
|
36931
|
+
setupEmbeddingPartitions(resource, vectorFields) {
|
|
36932
|
+
if (!resource.config) return;
|
|
36933
|
+
for (const vectorField of vectorFields) {
|
|
36934
|
+
const isOptional = this.isFieldOptional(resource.schema.attributes, vectorField.name);
|
|
36935
|
+
if (!isOptional) continue;
|
|
36936
|
+
const partitionName = `byHas${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
36937
|
+
const trackingFieldName = `_has${this.capitalize(vectorField.name.replace(/\./g, "_"))}`;
|
|
36938
|
+
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
36939
|
+
this.emit("vector:partition-exists", {
|
|
36940
|
+
resource: resource.name,
|
|
36941
|
+
vectorField: vectorField.name,
|
|
36942
|
+
partition: partitionName,
|
|
36943
|
+
timestamp: Date.now()
|
|
36944
|
+
});
|
|
36945
|
+
continue;
|
|
36946
|
+
}
|
|
36947
|
+
if (!resource.config.partitions) {
|
|
36948
|
+
resource.config.partitions = {};
|
|
36949
|
+
}
|
|
36950
|
+
resource.config.partitions[partitionName] = {
|
|
36951
|
+
fields: {
|
|
36952
|
+
[trackingFieldName]: "boolean"
|
|
36953
|
+
}
|
|
36954
|
+
};
|
|
36955
|
+
if (!resource.schema.attributes[trackingFieldName]) {
|
|
36956
|
+
resource.schema.attributes[trackingFieldName] = {
|
|
36957
|
+
type: "boolean",
|
|
36958
|
+
optional: true,
|
|
36959
|
+
default: false
|
|
36960
|
+
};
|
|
36961
|
+
}
|
|
36962
|
+
this.emit("vector:partition-created", {
|
|
36963
|
+
resource: resource.name,
|
|
36964
|
+
vectorField: vectorField.name,
|
|
36965
|
+
partition: partitionName,
|
|
36966
|
+
trackingField: trackingFieldName,
|
|
36967
|
+
timestamp: Date.now()
|
|
36968
|
+
});
|
|
36969
|
+
console.log(`\u2705 VectorPlugin: Created partition '${partitionName}' for optional embedding field '${vectorField.name}' in resource '${resource.name}'`);
|
|
36970
|
+
this.installEmbeddingHooks(resource, vectorField.name, trackingFieldName);
|
|
36971
|
+
}
|
|
36972
|
+
}
|
|
36973
|
+
/**
|
|
36974
|
+
* Check if a field is optional in the schema
|
|
36975
|
+
*
|
|
36976
|
+
* @param {Object} attributes - Resource attributes
|
|
36977
|
+
* @param {string} fieldPath - Field path (supports dot notation)
|
|
36978
|
+
* @returns {boolean} True if field is optional
|
|
36979
|
+
*/
|
|
36980
|
+
isFieldOptional(attributes, fieldPath) {
|
|
36981
|
+
const parts = fieldPath.split(".");
|
|
36982
|
+
let current = attributes;
|
|
36983
|
+
for (let i = 0; i < parts.length; i++) {
|
|
36984
|
+
const part = parts[i];
|
|
36985
|
+
const attr = current[part];
|
|
36986
|
+
if (!attr) return true;
|
|
36987
|
+
if (typeof attr === "string") {
|
|
36988
|
+
const flags = attr.split("|");
|
|
36989
|
+
if (flags.includes("required")) return false;
|
|
36990
|
+
if (flags.includes("optional") || flags.some((f) => f.startsWith("optional:"))) return true;
|
|
36991
|
+
return !flags.includes("required");
|
|
36992
|
+
}
|
|
36993
|
+
if (typeof attr === "object") {
|
|
36994
|
+
if (i === parts.length - 1) {
|
|
36995
|
+
if (attr.optional === true) return true;
|
|
36996
|
+
if (attr.optional === false) return false;
|
|
36997
|
+
return attr.optional !== false;
|
|
36998
|
+
}
|
|
36999
|
+
if (attr.type === "object" && attr.props) {
|
|
37000
|
+
current = attr.props;
|
|
37001
|
+
} else {
|
|
37002
|
+
return true;
|
|
37003
|
+
}
|
|
37004
|
+
}
|
|
37005
|
+
}
|
|
37006
|
+
return true;
|
|
37007
|
+
}
|
|
37008
|
+
/**
|
|
37009
|
+
* Capitalize first letter of string
|
|
37010
|
+
*
|
|
37011
|
+
* @param {string} str - Input string
|
|
37012
|
+
* @returns {string} Capitalized string
|
|
37013
|
+
*/
|
|
37014
|
+
capitalize(str) {
|
|
37015
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
37016
|
+
}
|
|
37017
|
+
/**
|
|
37018
|
+
* Install hooks to maintain embedding partition tracking field
|
|
37019
|
+
*
|
|
37020
|
+
* @param {Resource} resource - Resource instance
|
|
37021
|
+
* @param {string} vectorField - Vector field name
|
|
37022
|
+
* @param {string} trackingField - Tracking field name
|
|
37023
|
+
*/
|
|
37024
|
+
installEmbeddingHooks(resource, vectorField, trackingField) {
|
|
37025
|
+
resource.registerHook("beforeInsert", async (data) => {
|
|
37026
|
+
const hasVector = this.hasVectorValue(data, vectorField);
|
|
37027
|
+
this.setNestedValue(data, trackingField, hasVector);
|
|
37028
|
+
return data;
|
|
37029
|
+
});
|
|
37030
|
+
resource.registerHook("beforeUpdate", async (id, updates) => {
|
|
37031
|
+
if (vectorField in updates || this.hasNestedKey(updates, vectorField)) {
|
|
37032
|
+
const hasVector = this.hasVectorValue(updates, vectorField);
|
|
37033
|
+
this.setNestedValue(updates, trackingField, hasVector);
|
|
37034
|
+
}
|
|
37035
|
+
return updates;
|
|
37036
|
+
});
|
|
37037
|
+
this.emit("vector:hooks-installed", {
|
|
37038
|
+
resource: resource.name,
|
|
37039
|
+
vectorField,
|
|
37040
|
+
trackingField,
|
|
37041
|
+
hooks: ["beforeInsert", "beforeUpdate"],
|
|
37042
|
+
timestamp: Date.now()
|
|
37043
|
+
});
|
|
37044
|
+
}
|
|
37045
|
+
/**
|
|
37046
|
+
* Check if data has a valid vector value for the given field
|
|
37047
|
+
*
|
|
37048
|
+
* @param {Object} data - Data object
|
|
37049
|
+
* @param {string} fieldPath - Field path (supports dot notation)
|
|
37050
|
+
* @returns {boolean} True if vector exists and is valid
|
|
37051
|
+
*/
|
|
37052
|
+
hasVectorValue(data, fieldPath) {
|
|
37053
|
+
const value = this.getNestedValue(data, fieldPath);
|
|
37054
|
+
return value != null && Array.isArray(value) && value.length > 0;
|
|
37055
|
+
}
|
|
37056
|
+
/**
|
|
37057
|
+
* Check if object has a nested key
|
|
37058
|
+
*
|
|
37059
|
+
* @param {Object} obj - Object to check
|
|
37060
|
+
* @param {string} path - Dot-notation path
|
|
37061
|
+
* @returns {boolean} True if key exists
|
|
37062
|
+
*/
|
|
37063
|
+
hasNestedKey(obj, path) {
|
|
37064
|
+
const parts = path.split(".");
|
|
37065
|
+
let current = obj;
|
|
37066
|
+
for (const part of parts) {
|
|
37067
|
+
if (current == null || typeof current !== "object") return false;
|
|
37068
|
+
if (!(part in current)) return false;
|
|
37069
|
+
current = current[part];
|
|
36917
37070
|
}
|
|
37071
|
+
return true;
|
|
37072
|
+
}
|
|
37073
|
+
/**
|
|
37074
|
+
* Get nested value from object using dot notation
|
|
37075
|
+
*
|
|
37076
|
+
* @param {Object} obj - Object to traverse
|
|
37077
|
+
* @param {string} path - Dot-notation path
|
|
37078
|
+
* @returns {*} Value at path or undefined
|
|
37079
|
+
*/
|
|
37080
|
+
getNestedValue(obj, path) {
|
|
37081
|
+
const parts = path.split(".");
|
|
37082
|
+
let current = obj;
|
|
37083
|
+
for (const part of parts) {
|
|
37084
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
37085
|
+
current = current[part];
|
|
37086
|
+
}
|
|
37087
|
+
return current;
|
|
37088
|
+
}
|
|
37089
|
+
/**
|
|
37090
|
+
* Set nested value in object using dot notation
|
|
37091
|
+
*
|
|
37092
|
+
* @param {Object} obj - Object to modify
|
|
37093
|
+
* @param {string} path - Dot-notation path
|
|
37094
|
+
* @param {*} value - Value to set
|
|
37095
|
+
*/
|
|
37096
|
+
setNestedValue(obj, path, value) {
|
|
37097
|
+
const parts = path.split(".");
|
|
37098
|
+
let current = obj;
|
|
37099
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
37100
|
+
const part = parts[i];
|
|
37101
|
+
if (!(part in current) || typeof current[part] !== "object") {
|
|
37102
|
+
current[part] = {};
|
|
37103
|
+
}
|
|
37104
|
+
current = current[part];
|
|
37105
|
+
}
|
|
37106
|
+
current[parts[parts.length - 1]] = value;
|
|
37107
|
+
}
|
|
37108
|
+
/**
|
|
37109
|
+
* Get auto-created embedding partition for a vector field
|
|
37110
|
+
*
|
|
37111
|
+
* Returns partition configuration if an auto-partition exists for the given vector field.
|
|
37112
|
+
* Auto-partitions enable O(1) filtering to only records with embeddings.
|
|
37113
|
+
*
|
|
37114
|
+
* @param {Resource} resource - Resource instance
|
|
37115
|
+
* @param {string} vectorField - Vector field name
|
|
37116
|
+
* @returns {Object|null} Partition config or null
|
|
37117
|
+
*/
|
|
37118
|
+
getAutoEmbeddingPartition(resource, vectorField) {
|
|
37119
|
+
if (!resource.config) return null;
|
|
37120
|
+
const partitionName = `byHas${this.capitalize(vectorField.replace(/\./g, "_"))}`;
|
|
37121
|
+
const trackingFieldName = `_has${this.capitalize(vectorField.replace(/\./g, "_"))}`;
|
|
37122
|
+
if (resource.config.partitions && resource.config.partitions[partitionName]) {
|
|
37123
|
+
return {
|
|
37124
|
+
partitionName,
|
|
37125
|
+
partitionValues: { [trackingFieldName]: true }
|
|
37126
|
+
};
|
|
37127
|
+
}
|
|
37128
|
+
return null;
|
|
36918
37129
|
}
|
|
36919
37130
|
/**
|
|
36920
37131
|
* Auto-detect vector field from resource schema
|
|
@@ -37053,11 +37264,12 @@ class VectorPlugin extends Plugin {
|
|
|
37053
37264
|
} else if (!vectorField) {
|
|
37054
37265
|
vectorField = "vector";
|
|
37055
37266
|
}
|
|
37056
|
-
|
|
37267
|
+
let {
|
|
37057
37268
|
limit = 10,
|
|
37058
37269
|
distanceMetric = this.config.distanceMetric,
|
|
37059
37270
|
threshold = null,
|
|
37060
|
-
partition = null
|
|
37271
|
+
partition = null,
|
|
37272
|
+
partitionValues = null
|
|
37061
37273
|
} = options;
|
|
37062
37274
|
const distanceFn = this.distanceFunctions[distanceMetric];
|
|
37063
37275
|
if (!distanceFn) {
|
|
@@ -37073,31 +37285,61 @@ class VectorPlugin extends Plugin {
|
|
|
37073
37285
|
});
|
|
37074
37286
|
throw error;
|
|
37075
37287
|
}
|
|
37288
|
+
if (!partition) {
|
|
37289
|
+
const autoPartition = this.getAutoEmbeddingPartition(resource, vectorField);
|
|
37290
|
+
if (autoPartition) {
|
|
37291
|
+
partition = autoPartition.partitionName;
|
|
37292
|
+
partitionValues = autoPartition.partitionValues;
|
|
37293
|
+
this._emitEvent("vector:auto-partition-used", {
|
|
37294
|
+
resource: resource.name,
|
|
37295
|
+
vectorField,
|
|
37296
|
+
partition,
|
|
37297
|
+
partitionValues,
|
|
37298
|
+
timestamp: Date.now()
|
|
37299
|
+
});
|
|
37300
|
+
}
|
|
37301
|
+
}
|
|
37076
37302
|
this._emitEvent("vector:search-start", {
|
|
37077
37303
|
resource: resource.name,
|
|
37078
37304
|
vectorField,
|
|
37079
37305
|
limit,
|
|
37080
37306
|
distanceMetric,
|
|
37081
37307
|
partition,
|
|
37308
|
+
partitionValues,
|
|
37082
37309
|
threshold,
|
|
37083
37310
|
queryDimensions: queryVector.length,
|
|
37084
37311
|
timestamp: startTime
|
|
37085
37312
|
});
|
|
37086
37313
|
try {
|
|
37087
37314
|
let allRecords;
|
|
37088
|
-
if (partition) {
|
|
37315
|
+
if (partition && partitionValues) {
|
|
37089
37316
|
this._emitEvent("vector:partition-filter", {
|
|
37090
37317
|
resource: resource.name,
|
|
37091
37318
|
partition,
|
|
37319
|
+
partitionValues,
|
|
37092
37320
|
timestamp: Date.now()
|
|
37093
37321
|
});
|
|
37094
|
-
allRecords = await resource.list({ partition, partitionValues
|
|
37322
|
+
allRecords = await resource.list({ partition, partitionValues });
|
|
37095
37323
|
} else {
|
|
37096
|
-
allRecords = await resource.getAll();
|
|
37324
|
+
allRecords = resource.getAll ? await resource.getAll() : await resource.list();
|
|
37097
37325
|
}
|
|
37098
37326
|
const totalRecords = allRecords.length;
|
|
37099
37327
|
let processedRecords = 0;
|
|
37100
37328
|
let dimensionMismatches = 0;
|
|
37329
|
+
if (!partition && totalRecords > 1e3) {
|
|
37330
|
+
const warning = {
|
|
37331
|
+
resource: resource.name,
|
|
37332
|
+
operation: "vectorSearch",
|
|
37333
|
+
totalRecords,
|
|
37334
|
+
vectorField,
|
|
37335
|
+
recommendation: "Use partitions to filter data before vector search for better performance"
|
|
37336
|
+
};
|
|
37337
|
+
this._emitEvent("vector:performance-warning", warning);
|
|
37338
|
+
console.warn(`\u26A0\uFE0F VectorPlugin: Performing vectorSearch on ${totalRecords} records without partition filter`);
|
|
37339
|
+
console.warn(` Resource: '${resource.name}'`);
|
|
37340
|
+
console.warn(` Recommendation: Use partition parameter to reduce search space`);
|
|
37341
|
+
console.warn(` Example: resource.vectorSearch(vector, { partition: 'byCategory', partitionValues: { category: 'books' } })`);
|
|
37342
|
+
}
|
|
37101
37343
|
const results = allRecords.filter((record) => record[vectorField] && Array.isArray(record[vectorField])).map((record, index) => {
|
|
37102
37344
|
try {
|
|
37103
37345
|
const distance = distanceFn(queryVector, record[vectorField]);
|
|
@@ -37181,10 +37423,11 @@ class VectorPlugin extends Plugin {
|
|
|
37181
37423
|
} else if (!vectorField) {
|
|
37182
37424
|
vectorField = "vector";
|
|
37183
37425
|
}
|
|
37184
|
-
|
|
37426
|
+
let {
|
|
37185
37427
|
k = 5,
|
|
37186
37428
|
distanceMetric = this.config.distanceMetric,
|
|
37187
37429
|
partition = null,
|
|
37430
|
+
partitionValues = null,
|
|
37188
37431
|
...kmeansOptions
|
|
37189
37432
|
} = options;
|
|
37190
37433
|
const distanceFn = this.distanceFunctions[distanceMetric];
|
|
@@ -37201,30 +37444,62 @@ class VectorPlugin extends Plugin {
|
|
|
37201
37444
|
});
|
|
37202
37445
|
throw error;
|
|
37203
37446
|
}
|
|
37447
|
+
if (!partition) {
|
|
37448
|
+
const autoPartition = this.getAutoEmbeddingPartition(resource, vectorField);
|
|
37449
|
+
if (autoPartition) {
|
|
37450
|
+
partition = autoPartition.partitionName;
|
|
37451
|
+
partitionValues = autoPartition.partitionValues;
|
|
37452
|
+
this._emitEvent("vector:auto-partition-used", {
|
|
37453
|
+
resource: resource.name,
|
|
37454
|
+
vectorField,
|
|
37455
|
+
partition,
|
|
37456
|
+
partitionValues,
|
|
37457
|
+
timestamp: Date.now()
|
|
37458
|
+
});
|
|
37459
|
+
}
|
|
37460
|
+
}
|
|
37204
37461
|
this._emitEvent("vector:cluster-start", {
|
|
37205
37462
|
resource: resource.name,
|
|
37206
37463
|
vectorField,
|
|
37207
37464
|
k,
|
|
37208
37465
|
distanceMetric,
|
|
37209
37466
|
partition,
|
|
37467
|
+
partitionValues,
|
|
37210
37468
|
maxIterations: kmeansOptions.maxIterations || 100,
|
|
37211
37469
|
timestamp: startTime
|
|
37212
37470
|
});
|
|
37213
37471
|
try {
|
|
37214
37472
|
let allRecords;
|
|
37215
|
-
if (partition) {
|
|
37473
|
+
if (partition && partitionValues) {
|
|
37216
37474
|
this._emitEvent("vector:partition-filter", {
|
|
37217
37475
|
resource: resource.name,
|
|
37218
37476
|
partition,
|
|
37477
|
+
partitionValues,
|
|
37219
37478
|
timestamp: Date.now()
|
|
37220
37479
|
});
|
|
37221
|
-
allRecords = await resource.list({ partition, partitionValues
|
|
37480
|
+
allRecords = await resource.list({ partition, partitionValues });
|
|
37222
37481
|
} else {
|
|
37223
|
-
allRecords = await resource.getAll();
|
|
37482
|
+
allRecords = resource.getAll ? await resource.getAll() : await resource.list();
|
|
37224
37483
|
}
|
|
37225
37484
|
const recordsWithVectors = allRecords.filter(
|
|
37226
37485
|
(record) => record[vectorField] && Array.isArray(record[vectorField])
|
|
37227
37486
|
);
|
|
37487
|
+
if (!partition && allRecords.length > 1e3) {
|
|
37488
|
+
const warning = {
|
|
37489
|
+
resource: resource.name,
|
|
37490
|
+
operation: "cluster",
|
|
37491
|
+
totalRecords: allRecords.length,
|
|
37492
|
+
recordsWithVectors: recordsWithVectors.length,
|
|
37493
|
+
vectorField,
|
|
37494
|
+
recommendation: "Use partitions to filter data before clustering for better performance"
|
|
37495
|
+
};
|
|
37496
|
+
this._emitEvent("vector:performance-warning", warning);
|
|
37497
|
+
console.warn(`\u26A0\uFE0F VectorPlugin: Performing clustering on ${allRecords.length} records without partition filter`);
|
|
37498
|
+
console.warn(` Resource: '${resource.name}'`);
|
|
37499
|
+
console.warn(` Records with vectors: ${recordsWithVectors.length}`);
|
|
37500
|
+
console.warn(` Recommendation: Use partition parameter to reduce clustering space`);
|
|
37501
|
+
console.warn(` Example: resource.cluster({ k: 5, partition: 'byCategory', partitionValues: { category: 'books' } })`);
|
|
37502
|
+
}
|
|
37228
37503
|
if (recordsWithVectors.length === 0) {
|
|
37229
37504
|
const error = new VectorError("No vectors found in resource", {
|
|
37230
37505
|
operation: "cluster",
|