From de38f1fca92299c201f536dd77de53d54551a8b3 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Fri, 30 Jan 2026 17:28:43 +0200 Subject: [PATCH] feat(schema): add missing field and collection schema properties - add field properties: stem, store, range_index, token_separators, symbols_to_index, stem_dictionary, truncate_len - add schema properties: created_at, num_memory_shards, curation_sets, synonym_sets, symbols_to_index, token_separators - add image type to Field Type enum --- lib/src/models/field.dart | 114 ++++++++++++++++++++++++++++++++++++- lib/src/models/schema.dart | 90 ++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 3 deletions(-) diff --git a/lib/src/models/field.dart b/lib/src/models/field.dart index 1a1388d..6032f25 100644 --- a/lib/src/models/field.dart +++ b/lib/src/models/field.dart @@ -48,6 +48,39 @@ class Field { /// Example value: `ReferencedCollectionName.fieldName`. final String? reference; + /// Enable stemming for this string field. + final bool stem; + + /// Whether to store the field value on disk. + /// + /// When set to `false`, the field value won't be stored on disk. + final bool store; + + /// Enable range index for this numerical field. + /// + /// Useful for efficient range queries on numerical fields. + final bool rangeIndex; + + /// List of characters to use as token separators for this field. + /// + /// Overrides the collection-level token_separators setting for this field. + final List? tokenSeparators; + + /// List of special characters to index as part of tokens for this field. + /// + /// Overrides the collection-level symbols_to_index setting for this field. + final List? symbolsToIndex; + + /// Custom stem dictionary for this field. + /// + /// Allows specifying custom stemming rules for this field. + final String? stemDictionary; + + /// Maximum length to truncate the field value to. + /// + /// Token values longer than this will be truncated. + final int? truncateLen; + Field( this.name, { this.type, @@ -60,6 +93,13 @@ class Field { this.sort = false, this.enableInfixSearch = false, this.reference, + this.stem = false, + this.store = true, + this.rangeIndex = false, + this.tokenSeparators, + this.symbolsToIndex, + this.stemDictionary, + this.truncateLen, }) { if (name.isEmpty) { throw ArgumentError('Ensure Field.name is not empty'); @@ -84,6 +124,13 @@ class Field { sort: map['sort'] ?? false, enableInfixSearch: map['infix'] ?? false, reference: map['reference'], + stem: map['stem'] ?? false, + store: map['store'] ?? true, + rangeIndex: map['range_index'] ?? false, + tokenSeparators: (map['token_separators'] as List?)?.cast(), + symbolsToIndex: (map['symbols_to_index'] as List?)?.cast(), + stemDictionary: map['stem_dictionary'], + truncateLen: map['truncate_len'], ); } @@ -117,6 +164,27 @@ class Field { if (reference != null) { map['reference'] = reference; } + if (stem) { + map['stem'] = true; + } + if (!store) { + map['store'] = false; + } + if (rangeIndex) { + map['range_index'] = true; + } + if (tokenSeparators != null) { + map['token_separators'] = tokenSeparators; + } + if (symbolsToIndex != null) { + map['symbols_to_index'] = symbolsToIndex; + } + if (stemDictionary != null && stemDictionary!.isNotEmpty) { + map['stem_dictionary'] = stemDictionary; + } + if (truncateLen != null) { + map['truncate_len'] = truncateLen; + } return map; } @@ -137,7 +205,14 @@ class Field { locale.hashCode ^ sort.hashCode ^ enableInfixSearch.hashCode ^ - reference.hashCode; + reference.hashCode ^ + stem.hashCode ^ + store.hashCode ^ + rangeIndex.hashCode ^ + tokenSeparators.hashCode ^ + symbolsToIndex.hashCode ^ + stemDictionary.hashCode ^ + truncateLen.hashCode; @override bool operator ==(Object other) { @@ -153,8 +228,25 @@ class Field { other.locale == locale && other.sort == sort && other.enableInfixSearch == enableInfixSearch && - other.reference == reference; + other.reference == reference && + other.stem == stem && + other.store == store && + other.rangeIndex == rangeIndex && + _listEquals(other.tokenSeparators, tokenSeparators) && + _listEquals(other.symbolsToIndex, symbolsToIndex) && + other.stemDictionary == stemDictionary && + other.truncateLen == truncateLen; + } +} + +bool _listEquals(List? a, List? b) { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) return false; } + return true; } /// Used to update a colletion's fields. @@ -177,6 +269,14 @@ class UpdateField extends Field { super.locale, super.sort, super.enableInfixSearch, + super.reference, + super.stem, + super.store, + super.rangeIndex, + super.tokenSeparators, + super.symbolsToIndex, + super.stemDictionary, + super.truncateLen, this.shouldDrop = false, }); @@ -203,6 +303,14 @@ class UpdateField extends Field { locale: field.locale, sort: field.sort, enableInfixSearch: field.enableInfixSearch, + reference: field.reference, + stem: field.stem, + store: field.store, + rangeIndex: field.rangeIndex, + tokenSeparators: field.tokenSeparators, + symbolsToIndex: field.symbolsToIndex, + stemDictionary: field.stemDictionary, + truncateLen: field.truncateLen, shouldDrop: map['drop'] ?? false, ); } @@ -263,6 +371,7 @@ enum Type { geopoint, geopolygon, object, + image, } extension _Type on Type { @@ -276,6 +385,7 @@ extension _Type on Type { case Type.geopoint: case Type.geopolygon: case Type.object: + case Type.image: final description = toString(), indexOfDot = description.indexOf('.'), value = description.substring(indexOfDot + 1); diff --git a/lib/src/models/schema.dart b/lib/src/models/schema.dart index e3cf49e..2e8cd94 100644 --- a/lib/src/models/schema.dart +++ b/lib/src/models/schema.dart @@ -31,12 +31,36 @@ class Schema extends BaseSchema { /// Boolean to enable nested fields on the schema, only available for typesense 0.24 or more final bool? enableNestedFields; + /// Timestamp when the collection was created. + final int? createdAt; + + /// Number of memory shards used by the collection. + final int? numMemoryShards; + + /// List of curation set names associated with this collection. + final List? curationSets; + + /// List of synonym set names associated with this collection. + final List? synonymSets; + + /// List of special characters to index at the collection level. + final List? symbolsToIndex; + + /// List of characters to use as token separators at the collection level. + final List? tokenSeparators; + Schema( this.name, super.fields, { this.defaultSortingField, this.documentCount, this.enableNestedFields, + this.createdAt, + this.numMemoryShards, + this.curationSets, + this.synonymSets, + this.symbolsToIndex, + this.tokenSeparators, }); factory Schema.fromMap(Map map) { @@ -66,6 +90,12 @@ class Schema extends BaseSchema { documentCount: map['num_documents'] ?? 0, defaultSortingField: defaultSortingField, enableNestedFields: enableNestedFields, + createdAt: map['created_at'], + numMemoryShards: map['num_memory_shards'], + curationSets: (map['curation_sets'] as List?)?.cast(), + synonymSets: (map['synonym_sets'] as List?)?.cast(), + symbolsToIndex: (map['symbols_to_index'] as List?)?.cast(), + tokenSeparators: (map['token_separators'] as List?)?.cast(), ); } @@ -82,12 +112,48 @@ class Schema extends BaseSchema { if (enableNestedFields != null) { map['enable_nested_fields'] = enableNestedFields; } + if (createdAt != null) { + map['created_at'] = createdAt; + } + if (numMemoryShards != null) { + map['num_memory_shards'] = numMemoryShards; + } + if (curationSets != null) { + map['curation_sets'] = curationSets; + } + if (synonymSets != null) { + map['synonym_sets'] = synonymSets; + } + if (symbolsToIndex != null) { + map['symbols_to_index'] = symbolsToIndex; + } + if (tokenSeparators != null) { + map['token_separators'] = tokenSeparators; + } return map; } } class UpdateSchema extends BaseSchema { - UpdateSchema(Set super.fields); + /// List of synonym set IDs to associate with the collection. + final List? synonymSets; + + /// List of curation set IDs to associate with the collection. + final List? curationSets; + + /// List of special characters to index at the collection level. + final List? symbolsToIndex; + + /// List of characters to use as token separators at the collection level. + final List? tokenSeparators; + + UpdateSchema( + Set super.fields, { + this.synonymSets, + this.curationSets, + this.symbolsToIndex, + this.tokenSeparators, + }); factory UpdateSchema.fromMap(Map map) { final Set fields = (map['fields'] != null) @@ -98,8 +164,30 @@ class UpdateSchema extends BaseSchema { return UpdateSchema( fields, + synonymSets: (map['synonym_sets'] as List?)?.cast(), + curationSets: (map['curation_sets'] as List?)?.cast(), + symbolsToIndex: (map['symbols_to_index'] as List?)?.cast(), + tokenSeparators: (map['token_separators'] as List?)?.cast(), ); } + + @override + Map toMap() { + final map = super.toMap(); + if (synonymSets != null) { + map['synonym_sets'] = synonymSets; + } + if (curationSets != null) { + map['curation_sets'] = curationSets; + } + if (symbolsToIndex != null) { + map['symbols_to_index'] = symbolsToIndex; + } + if (tokenSeparators != null) { + map['token_separators'] = tokenSeparators; + } + return map; + } } ArgumentError _defaultSortingFieldNotInSchema(String name) =>