Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2491024
chore: bump deps
tharropoulos Jan 16, 2026
11ae8d7
feat(stopwords): add stopwords models and schemas
tharropoulos Jan 16, 2026
fdb4842
feat(stopwords): add stopwords api client
tharropoulos Jan 16, 2026
53a14cd
feat(stopwords): expose stopwords support on client
tharropoulos Jan 16, 2026
5f16e1c
test(stopwords): add stopwords integration tests
tharropoulos Jan 16, 2026
b9ff0e9
feat(curation): add curation sets models
tharropoulos Jan 16, 2026
ded8eed
feat(api): add list-based api call support with caching
tharropoulos Jan 16, 2026
01b9ef6
feat(curation): add curation set api client
tharropoulos Jan 16, 2026
b1851a4
feat(curation): integrate curation sets into client
tharropoulos Jan 16, 2026
5858475
test(curation): add integration tests for curation sets
tharropoulos Jan 16, 2026
2e2c908
feat(synonym-sets): add models for synonym sets
tharropoulos Jan 16, 2026
1b3e76f
feat(synonym-sets): add synonym set classes
tharropoulos Jan 16, 2026
320d6e4
feat(synonym-sets): integrate synonym sets into client
tharropoulos Jan 16, 2026
b75f899
test(synonym-sets): add integration tests for synonym sets
tharropoulos Jan 16, 2026
acb44ef
feat(stemming): add stemming models and schemas
tharropoulos Jan 16, 2026
fa729f0
feat(stemming): add stemming api client
tharropoulos Jan 16, 2026
2c4087c
feat(stemming): expose stemming to client
tharropoulos Jan 16, 2026
c8fb4cc
feat(api): add post raw method to base api call
tharropoulos Jan 16, 2026
60457cc
test(stemming): add integration tests for stemming
tharropoulos Jan 16, 2026
7136e1d
feat(convo): add conversation models and schemas
tharropoulos Jan 16, 2026
f72e4a9
feat(convo): add conversation model api client
tharropoulos Jan 16, 2026
ae14d2a
feat(convo): expose conversation models to client
tharropoulos Jan 16, 2026
4bbca93
test(convo): add integration tests for conversation models
tharropoulos Jan 16, 2026
8858c88
test(nl-search): add integration tests for nl search
tharropoulos Jan 16, 2026
35ee30b
feat(nl-search): add nl search model api client
tharropoulos Jan 16, 2026
602fa76
feat(nl-search): register nl search models to client
tharropoulos Jan 16, 2026
bb20d79
test(nl-search): add tests for nl-search
tharropoulos Jan 16, 2026
e9a76ea
feat(convo): add conversation models
tharropoulos Jan 16, 2026
1e67e66
feat(convo): add conversations api
tharropoulos Jan 16, 2026
5b59f4a
feat(convo): add conversations to client
tharropoulos Jan 16, 2026
4778f2c
feat(analytics): add analytics models
tharropoulos Jan 16, 2026
a0c29a9
feat(analytics): add analytics api
tharropoulos Jan 16, 2026
3bb6458
feat(analytics): add analytics to client
tharropoulos Jan 16, 2026
c19afe9
test(analytics): add tests for analytics
tharropoulos Jan 16, 2026
ce3bf2c
fix(synonyms): add deprecation message to old synonyms
tharropoulos Jan 16, 2026
5450cb0
ci: add typesense v30.0 to ci for integration tests
tharropoulos Jan 16, 2026
02e3286
fix(curation): add missing parameters for curation sets
tharropoulos Jan 22, 2026
55791db
refactor(analytics): add createMany api for analytics rules
tharropoulos Jan 22, 2026
175aff5
chore: lint
tharropoulos Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/dart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Start Typesense
run: |
docker run -d \
-p 8108:8108 \
--name typesense \
-v /tmp/typesense-data:/data \
-v /tmp/typesense-analytics-data:/analytics-data \
typesense/typesense:30.0.rca37 \
--api-key=xyz \
--data-dir=/data \
--enable-search-analytics=true \
--analytics-dir=/analytics-data \
--analytics-flush-interval=60 \
--analytics-minute-rate-limit=50 \
--enable-cors

- name: Wait for Typesense
run: |
timeout 20 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8108/health)" != "200" ]]; do sleep 1; done' || false

- uses: actions/checkout@v3

# Note: This workflow uses the latest stable version of the Dart SDK.
Expand Down
27 changes: 27 additions & 0 deletions lib/src/analytics.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'services/api_call.dart';
import 'analytics_rules.dart';
import 'analytics_rule.dart';
import 'analytics_events.dart';

class Analytics {
final ApiCall _apiCall;
final AnalyticsRules _rules;
final AnalyticsEvents _events;
final _individualRules = <String, AnalyticsRule>{};

Analytics(ApiCall apiCall)
: _apiCall = apiCall,
_rules = AnalyticsRules(apiCall),
_events = AnalyticsEvents(apiCall);

AnalyticsRules rules() => _rules;

AnalyticsRule rule(String name) {
if (!_individualRules.containsKey(name)) {
_individualRules[name] = AnalyticsRule(name, _apiCall);
}
return _individualRules[name]!;
}

AnalyticsEvents events() => _events;
}
44 changes: 44 additions & 0 deletions lib/src/analytics_events.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'models/models.dart';
import 'services/api_call.dart';

class AnalyticsEvents {
final ApiCall _apiCall;
static const String eventsPath = '/analytics/events';
static const String flushPath = '/analytics/flush';
static const String statusPath = '/analytics/status';

AnalyticsEvents(ApiCall apiCall) : _apiCall = apiCall;

Future<AnalyticsEventCreateResponse> create(
AnalyticsEventCreateSchema event) async {
final response =
await _apiCall.post(eventsPath, bodyParameters: event.toJson());
return AnalyticsEventCreateResponse.fromJson(response);
}

Future<AnalyticsEventsRetrieveSchema> retrieve({
required String userId,
required String name,
required int n,
Comment thread
happy-san marked this conversation as resolved.
}) async {
final response = await _apiCall.get(
eventsPath,
queryParams: {
'user_id': userId,
'name': name,
'n': n.toString(),
},
);
return AnalyticsEventsRetrieveSchema.fromJson(response);
}

Future<AnalyticsEventCreateResponse> flush() async {
final response = await _apiCall.post(flushPath, bodyParameters: {});
return AnalyticsEventCreateResponse.fromJson(response);
}

Future<AnalyticsStatus> status() async {
final response = await _apiCall.get(statusPath);
return AnalyticsStatus.fromJson(response);
}
}
25 changes: 25 additions & 0 deletions lib/src/analytics_rule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'models/models.dart';
import 'services/api_call.dart';
import 'analytics_rules.dart';

class AnalyticsRule {
final String _name;
final ApiCall _apiCall;

AnalyticsRule(String name, ApiCall apiCall)
: _name = name,
_apiCall = apiCall;

Future<AnalyticsRuleSchema> retrieve() async {
final response = await _apiCall.get(_endpointPath);
return AnalyticsRuleSchema.fromJson(response);
}

Future<AnalyticsRuleDeleteSchema> delete() async {
final response = await _apiCall.delete(_endpointPath);
return AnalyticsRuleDeleteSchema.fromJson(response);
}

String get _endpointPath =>
'${AnalyticsRules.resourcepath}/${Uri.encodeComponent(_name)}';
}
72 changes: 72 additions & 0 deletions lib/src/analytics_rules.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'dart:convert';

import 'models/models.dart';
import 'services/api_call.dart';
import 'analytics_rule.dart';

class AnalyticsRules {
final ApiCall _apiCall;
static const String resourcepath = '/analytics/rules';
final _individualRules = <String, AnalyticsRule>{};

AnalyticsRules(ApiCall apiCall) : _apiCall = apiCall;

/// Creates a single analytics rule.
Future<AnalyticsRuleSchema> create(AnalyticsRuleCreateSchema rule) async {
final response = await _apiCall.post(
resourcepath,
bodyParameters: rule.toJson(),
);
return AnalyticsRuleSchema.fromJson(
Map<String, dynamic>.from(response as Map),
);
}

/// Creates multiple analytics rules.
Future<List<AnalyticsRuleSchema>> createMany(
List<AnalyticsRuleCreateSchema> rules) async {
final body = rules.map((item) => item.toJson()).toList();
final encodedBody = json.encode(body);
final response = await _apiCall.sendList((node) => node.client!.post(
_apiCall.getRequestUri(node, resourcepath),
headers: _apiCall.defaultHeaders,
body: encodedBody,
));
return response.map((item) {
return AnalyticsRuleSchema.fromJson(
Map<String, dynamic>.from(item as Map),
);
}).toList();
}

Future<List<AnalyticsRuleSchema>> retrieve({String? ruleTag}) async {
final query = <String, String>{};
if (ruleTag != null) {
query['rule_tag'] = ruleTag;
}
final response = await _apiCall.getList(
resourcepath,
queryParams: query.isEmpty ? null : query,
);
return response
.map((item) =>
AnalyticsRuleSchema.fromJson(Map<String, dynamic>.from(item)))
.toList();
}

Future<AnalyticsRuleSchema> upsert(
String ruleName, AnalyticsRuleUpsertSchema update) async {
final response = await _apiCall.put(
'$resourcepath/${Uri.encodeComponent(ruleName)}',
bodyParameters: update.toJson(),
);
return AnalyticsRuleSchema.fromJson(response);
}

AnalyticsRule operator [](String ruleName) {
if (!_individualRules.containsKey(ruleName)) {
_individualRules[ruleName] = AnalyticsRule(ruleName, _apiCall);
}
return _individualRules[ruleName]!;
}
}
87 changes: 84 additions & 3 deletions lib/src/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ import 'presets.dart';
import 'metrics.dart';
import 'operations.dart';
import 'multi_search.dart';
import 'stopword.dart';
import 'stopwords.dart';
import 'curation_sets.dart';
import 'curation_set.dart';
import 'synonym_sets.dart';
import 'synonym_set.dart';
import 'stemming.dart';
import 'conversations_models.dart';
import 'conversation_model.dart';
import 'nl_search_models.dart';
import 'nl_search_model.dart';
import 'conversations.dart';
import 'conversation.dart';
import 'analytics.dart';

class Client {
final Configuration config;
Expand All @@ -35,10 +49,22 @@ class Client {
final Metrics metrics;
final Operations operations;
final MultiSearch multiSearch;
final Stopwords stopwords;
final CurationSets curationSets;
final SynonymSets synonymSets;
final Stemming stemming;
final ConversationsModels conversationsModels;
final NLSearchModels nlSearchModels;
final Conversations conversations;
final Analytics analytics;
final _individualCollections = HashMap<String, Collection>(),
_individualAliases = HashMap<String, Alias>(),
_individualKeys = HashMap<int, Key>(),
_individualPresets = HashMap<String, Preset>();
_individualPresets = HashMap<String, Preset>(),
_individualStopwords = HashMap<String, Stopword>(),
_individualCurationSets = HashMap<String, CurationSet>(),
_individualSynonymSets = HashMap<String, SynonymSet>();
final _individualConversations = HashMap<String, Conversation>();

Client._(
this.config,
Expand All @@ -53,7 +79,15 @@ class Client {
this.health,
this.metrics,
this.operations,
this.multiSearch);
this.multiSearch,
this.stopwords,
this.curationSets,
this.synonymSets,
this.stemming,
this.conversationsModels,
this.nlSearchModels,
this.conversations,
this.analytics);

factory Client(Configuration config) {
// ApiCall, DocumentsApiCall, and CollectionsApiCall share the same NodePool.
Expand All @@ -79,7 +113,15 @@ class Client {
Health(apiCall),
Metrics(apiCall),
Operations(apiCall),
MultiSearch(apiCall));
MultiSearch(apiCall),
Stopwords(apiCall),
CurationSets(apiCall),
SynonymSets(apiCall),
Stemming(apiCall),
ConversationsModels(apiCall),
NLSearchModels(apiCall),
Conversations(apiCall),
Analytics(apiCall));
}

/// Perform operation on an individual collection having [collectionName].
Expand Down Expand Up @@ -113,4 +155,43 @@ class Client {
}
return _individualPresets[presetName]!;
}

/// Perform operation on an individual stopwords set having [stopwordId].
Stopword stopword(String stopwordId) {
if (!_individualStopwords.containsKey(stopwordId)) {
_individualStopwords[stopwordId] = Stopword(stopwordId, _apiCall);
}
return _individualStopwords[stopwordId]!;
}

/// Perform operation on an individual curation set having [name].
CurationSet curationSet(String name) {
if (!_individualCurationSets.containsKey(name)) {
_individualCurationSets[name] = CurationSet(name, _apiCall);
}
return _individualCurationSets[name]!;
}

/// Perform operation on an individual synonym set having [name].
SynonymSet synonymSet(String name) {
if (!_individualSynonymSets.containsKey(name)) {
_individualSynonymSets[name] = SynonymSet(name, _apiCall);
}
return _individualSynonymSets[name]!;
}

ConversationModel conversationModel(String modelId) {
return conversationsModels[modelId];
}

NLSearchModel nlSearchModel(String modelId) {
return nlSearchModels[modelId];
}

Conversation conversation(String id) {
if (!_individualConversations.containsKey(id)) {
_individualConversations[id] = Conversation(id, _apiCall);
}
return _individualConversations[id]!;
}
}
35 changes: 35 additions & 0 deletions lib/src/conversation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'models/models.dart';
import 'services/api_call.dart';
import 'conversations.dart';

class Conversation {
final String _id;
final ApiCall _apiCall;

Conversation(String id, ApiCall apiCall)
: _id = id,
_apiCall = apiCall;

Future<List<ConversationSchema>> retrieve() async {
final response = await _apiCall.getList(_endpointPath);
return response
.map((item) =>
ConversationSchema.fromJson(Map<String, dynamic>.from(item)))
.toList();
}

Future<ConversationUpdateSchema> update(
ConversationUpdateSchema params) async {
final response =
await _apiCall.put(_endpointPath, bodyParameters: params.toJson());
return ConversationUpdateSchema.fromJson(response);
}

Future<ConversationDeleteSchema> delete() async {
final response = await _apiCall.delete(_endpointPath);
return ConversationDeleteSchema.fromJson(response);
}

String get _endpointPath =>
'${Conversations.resourcepath}/${Uri.encodeComponent(_id)}';
}
32 changes: 32 additions & 0 deletions lib/src/conversation_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'models/models.dart';
import 'services/api_call.dart';
import 'conversations_models.dart';

class ConversationModel {
final String _id;
final ApiCall _apiCall;

ConversationModel(String id, ApiCall apiCall)
: _id = id,
_apiCall = apiCall;

Future<ConversationModelCreateSchema> update(
ConversationModelCreateSchema params) async {
final response =
await _apiCall.put(_endpointPath, bodyParameters: params.toJson());
return ConversationModelCreateSchema.fromJson(response);
}

Future<ConversationModelSchema> retrieve() async {
final response = await _apiCall.get(_endpointPath);
return ConversationModelSchema.fromJson(response);
}

Future<ConversationModelDeleteSchema> delete() async {
final response = await _apiCall.delete(_endpointPath);
return ConversationModelDeleteSchema.fromJson(response);
}

String get _endpointPath =>
'${ConversationsModels.resourcepath}/${Uri.encodeComponent(_id)}';
}
Loading