@goatlab/typesense
Version:
Modern TypeScript wrapper for Typesense search engine API
227 lines • 8.08 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CollectionSchemaManager = void 0;
class LRUCache {
cache = new Map();
maxSize;
constructor(maxSize) {
this.maxSize = maxSize;
}
get(key) {
const value = this.cache.get(key);
if (value !== undefined) {
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, value);
}
return value;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
else if (this.cache.size >= this.maxSize) {
// Remove least recently used (first item)
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
has(key) {
return this.cache.has(key);
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
}
class CollectionSchemaManager {
schemaCache;
options;
constructor(options = {}) {
this.options = {
cacheSize: options.cacheSize || 100,
cacheTtl: options.cacheTtl || 300000, // 5 minutes
enableNestedFields: options.enableNestedFields ?? false,
typesenseVersion: options.typesenseVersion || '0.24.0',
suppressLogs: options.suppressLogs ?? false
};
this.schemaCache = new LRUCache(this.options.cacheSize);
}
isVersionSupported(feature, minVersion) {
const currentVersion = this.options.typesenseVersion;
return this.compareVersions(currentVersion, minVersion) >= 0;
}
compareVersions(version1, version2) {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
const v1Part = v1Parts[i] || 0;
const v2Part = v2Parts[i] || 0;
if (v1Part > v2Part)
return 1;
if (v1Part < v2Part)
return -1;
}
return 0;
}
getCachedSchema(collectionName) {
const cacheKey = `schema:${collectionName}`;
const cached = this.schemaCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < this.options.cacheTtl) {
return cached.schema;
}
if (cached) {
this.schemaCache.delete(cacheKey);
}
return null;
}
setCachedSchema(collectionName, schema) {
const cacheKey = `schema:${collectionName}`;
this.schemaCache.set(cacheKey, {
schema,
timestamp: Date.now()
});
}
deleteCachedSchema(collectionName) {
const cacheKey = `schema:${collectionName}`;
this.schemaCache.delete(cacheKey);
}
inferSchemaFromDocument(document, collectionName) {
const fields = [];
const processedFields = new Set();
const inferFieldType = (key, value, path = []) => {
if (processedFields.has(key))
return;
const field = { name: key };
if (value === null || value === undefined) {
field.type = 'string';
field.optional = true;
}
else if (typeof value === 'string') {
field.type = 'string';
}
else if (typeof value === 'number') {
field.type = Number.isInteger(value) ? 'int64' : 'float';
}
else if (typeof value === 'boolean') {
field.type = 'bool';
}
else if (Array.isArray(value)) {
if (value.length === 0) {
field.type = 'string[]';
}
else {
// Check if array is homogeneous by sampling elements
const sampleSize = Math.min(value.length, 10);
const types = new Set();
for (let i = 0; i < sampleSize; i++) {
const item = value[i];
if (typeof item === 'string')
types.add('string');
else if (typeof item === 'number') {
types.add(Number.isInteger(item) ? 'int64' : 'float');
}
else if (typeof item === 'boolean')
types.add('bool');
else
types.add('object');
}
if (types.size === 1) {
const type = Array.from(types)[0];
field.type = `${type}[]`;
}
else {
// Heterogeneous array - fall back to auto
field.type = 'auto';
}
}
}
else if (typeof value === 'object') {
// Check if nested fields are supported
if (this.isVersionSupported('nested_fields', '0.25.0') && this.options.enableNestedFields) {
field.type = 'object';
// Could recursively process nested fields here
}
else {
field.type = 'string'; // Store as JSON string
}
}
else {
field.type = 'auto';
}
processedFields.add(key);
fields.push(field);
};
// Process document fields
Object.entries(document).forEach(([key, value]) => {
inferFieldType(key, value);
});
// Ensure id field exists
if (!processedFields.has('id')) {
fields.unshift({ name: 'id', type: 'string' });
}
const collection = {
name: collectionName,
fields
};
// Add version-gated features
if (this.isVersionSupported('nested_fields', '0.25.0') && this.options.enableNestedFields) {
collection.enable_nested_fields = true;
}
return collection;
}
validateSchema(schema) {
const errors = [];
if (!schema.name) {
errors.push('Collection name is required');
}
if (!schema.fields || schema.fields.length === 0) {
errors.push('At least one field is required');
}
const hasIdField = schema.fields?.some(field => field.name === 'id');
if (!hasIdField) {
errors.push('Collection must have an "id" field');
}
// Validate field types
const validTypes = [
'string', 'string[]', 'int32', 'int32[]', 'int64', 'int64[]',
'float', 'float[]', 'bool', 'bool[]', 'geopoint', 'geopoint[]',
'object', 'object[]', 'auto', 'image'
];
schema.fields?.forEach(field => {
if (!validTypes.includes(field.type)) {
errors.push(`Invalid field type "${field.type}" for field "${field.name}"`);
}
});
// Check version-specific features
if (schema.enable_nested_fields && !this.isVersionSupported('nested_fields', '0.25.0')) {
errors.push('enable_nested_fields requires Typesense v0.25.0 or higher');
}
return {
valid: errors.length === 0,
errors
};
}
clearCache() {
this.schemaCache.clear();
}
getCacheStats() {
return {
size: this.schemaCache.size(),
maxSize: this.options.cacheSize
};
}
setTypesenseVersion(version) {
this.options.typesenseVersion = version;
// Clear cache when version changes as feature availability may change
this.clearCache();
}
}
exports.CollectionSchemaManager = CollectionSchemaManager;
//# sourceMappingURL=schema-manager.js.map