Skip to content

Commit f53d451

Browse files
authored
Create a collection and point it to alias (#68)
1 parent 85d23ea commit f53d451

17 files changed

+230
-52
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ ex_typesense-*.tar
2525
# Temporary files, for example, from tests.
2626
/tmp/
2727

28+
*.env
29+
2830
# Zed editor
2931
/.zed/
3032

@@ -34,4 +36,4 @@ ex_typesense-*.tar
3436

3537
# Misc.
3638
.elixir_ls
37-
**.DS_Store
39+
*.DS_Store

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## major.minor.patch (yyyy.mm.dd)
99

10+
## 1.1.0 (2025.03.02)
11+
12+
### Added
13+
14+
* `Collection.create_collection_with_alias/1`.
15+
* Allow module name (atom) to be passed as alias name in `Collection.create_collection_with_alias/1`.
16+
* Typespec to pass also omodule name (atom) for `Collection.create_collection_with_alias/1`.
17+
* Separate dev and test environments.
18+
19+
### Fixed
20+
21+
* Typespecs for `Search.multi_search_ecto`.
22+
* Return values of `Search.multi_search_ecto` for `search_test.exs`.
23+
24+
### Deprecated
25+
26+
* `Collection.get_collection_name/1`
27+
1028
## 1.0.2 (2025.01.30)
1129

1230
### Changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Add `:ex_typesense` to your list of dependencies in the Elixir project config fi
8585
def deps do
8686
[
8787
# From default Hex package manager
88-
{:ex_typesense, "~> 1.0"}
88+
{:ex_typesense, "~> 1.1"}
8989

9090
# Or from GitHub repository, if you want the latest greatest from main branch
9191
{:ex_typesense, git: "https://github.com/jaeyson/ex_typesense.git"}

config/config.exs

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import Config
22

3-
if config_env() in [:dev, :test] do
4-
config :open_api_typesense,
5-
api_key: "xyz",
6-
host: "localhost",
7-
port: 8108,
8-
scheme: "http"
9-
end
3+
# Import environment specific config. This must remain at the bottom
4+
# of this file so it overrides the configuration defined above.
5+
import_config "#{config_env()}.exs"

config/dev.exs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Config
2+
3+
config :open_api_typesense,
4+
api_key: "xyz",
5+
host: "localhost",
6+
port: 8108,
7+
scheme: "http"

config/test.exs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Config
2+
3+
config :open_api_typesense,
4+
api_key: "xyz",
5+
host: "localhost",
6+
port: 8108,
7+
scheme: "http",
8+
max_retries: 0,
9+
retry: false

lib/ex_typesense.ex

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
defmodule ExTypesense do
2-
@moduledoc since: "0.1.0"
2+
@moduledoc since: "1.0.0"
33

44
@moduledoc """
55
Public API functions to interact with Typesense.
@@ -103,9 +103,13 @@ defmodule ExTypesense do
103103
defdelegate list_collections(conn, opts), to: ExTypesense.Collection
104104

105105
defdelegate create_collection(schema), to: ExTypesense.Collection
106-
defdelegate create_collection(conn_or_schema, schema_or_opts), to: ExTypesense.Collection
106+
defdelegate create_collection(conn, schema), to: ExTypesense.Collection
107107
defdelegate create_collection(conn, schema, opts), to: ExTypesense.Collection
108108

109+
defdelegate create_collection_with_alias(schema), to: ExTypesense.Collection
110+
defdelegate create_collection_with_alias(conn, schema), to: ExTypesense.Collection
111+
defdelegate create_collection_with_alias(conn, schema, opts), to: ExTypesense.Collection
112+
109113
defdelegate clone_collection(src_coll, new_coll), to: ExTypesense.Collection
110114
defdelegate clone_collection(conn, src_coll, new_coll), to: ExTypesense.Collection
111115
defdelegate clone_collection(conn, src_coll, new_coll, opts), to: ExTypesense.Collection
@@ -114,8 +118,13 @@ defmodule ExTypesense do
114118
defdelegate get_collection(conn, coll_name), to: ExTypesense.Collection
115119
defdelegate get_collection(conn, coll_name, opts), to: ExTypesense.Collection
116120

121+
@deprecated "Use Collection.get_collection_alias/1 instead"
117122
defdelegate get_collection_name(alias_name), to: ExTypesense.Collection
123+
124+
@deprecated "Use Collection.get_collection_alias/2 instead"
118125
defdelegate get_collection_name(conn, alias_name), to: ExTypesense.Collection
126+
127+
@deprecated "Use Collection.get_collection_alias/3 instead"
119128
defdelegate get_collection_name(conn, alias_name, opts), to: ExTypesense.Collection
120129

121130
defdelegate drop_collection(name), to: ExTypesense.Collection

lib/ex_typesense/cluster.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ defmodule ExTypesense.Cluster do
355355
The follower node that you run this operation against will become
356356
the new leader, once this command succeeds.
357357
"""
358-
@spec vote :: {:ok, SuccessStatus.t()} | :error
358+
@spec vote :: {:ok, OpenApiTypesense.SuccessStatus.t()} | :error
359359
def vote do
360360
vote([])
361361
end

lib/ex_typesense/collection.ex

+101-8
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,80 @@ defmodule ExTypesense.Collection do
6969
OpenApiTypesense.Collections.get_collections(conn, opts)
7070
end
7171

72+
@doc """
73+
Creates collection with timestamped name and points to an alias.
74+
75+
> #### Use case {: .warning}
76+
>
77+
> When using this function, it will append
78+
> a timestamp in name and adds an alias based on schema name.
79+
> E.g. if table name is "bricks", then collection name is "bricks-1738558695"
80+
> and alias name is "bricks". The reason for this addition can be useful
81+
> when encountering like [full re-indexing](https://typesense.org/docs/guide/syncing-data-into-typesense.html#full-re-indexing)
82+
83+
> One common use-case for aliases is to reindex your data in the
84+
> background on a new collection and then switch your application
85+
> to it without any changes to your code. [Source](https://typesense.org/docs/latest/api/collection-alias.html#use-case)
86+
"""
87+
@doc since: "1.1.0"
88+
@spec create_collection_with_alias(map() | module()) ::
89+
{:ok, OpenApiTypesense.CollectionResponse.t()}
90+
| {:error, OpenApiTypesense.ApiResponse.t()}
91+
def create_collection_with_alias(schema) do
92+
create_collection_with_alias(schema, [])
93+
end
94+
95+
@doc """
96+
Same as [create_collection_with_alias/1](`create_collection_with_alias/1`) but passes another connection or opts.
97+
98+
```elixir
99+
ExTypesense.create_collection_with_alias(%{api_key: xyz, host: ...}, schema)
100+
101+
ExTypesense.create_collection_with_alias(OpenApiTypesense.Connection.new(), MyModule.Context)
102+
103+
ExTypesense.create_collection_with_alias(schema, src_name: "companies")
104+
```
105+
"""
106+
@doc since: "1.1.0"
107+
@spec create_collection_with_alias(map() | Connection.t(), map() | module() | keyword()) ::
108+
{:ok, OpenApiTypesense.CollectionResponse.t()}
109+
| {:error, OpenApiTypesense.ApiResponse.t()}
110+
def create_collection_with_alias(schema, opts) when is_list(opts) do
111+
Connection.new() |> create_collection_with_alias(schema, opts)
112+
end
113+
114+
def create_collection_with_alias(conn, schema) do
115+
create_collection_with_alias(conn, schema, [])
116+
end
117+
118+
@doc """
119+
Same as [create_collection_with_alias/2](`create_collection_with_alias/2`) but explicitly passes all arguments.
120+
121+
```elixir
122+
ExTypesense.create_collection_with_alias(%{api_key: xyz, host: ...}, schema, opts)
123+
124+
ExTypesense.create_collection_with_alias(OpenApiTypesense.Connection.new(), MyModule.Context.Schema, opts)
125+
```
126+
"""
127+
@doc since: "1.1.0"
128+
@spec create_collection_with_alias(map() | Connection.t(), map() | module(), keyword()) ::
129+
{:ok, OpenApiTypesense.CollectionResponse.t()}
130+
| {:error, OpenApiTypesense.ApiResponse.t()}
131+
def create_collection_with_alias(conn, module, opts) when is_atom(module) do
132+
schema = module.get_field_types()
133+
create_collection_with_alias(conn, schema, opts)
134+
end
135+
136+
def create_collection_with_alias(conn, schema, opts) do
137+
coll_name = Map.get(schema, "name") || Map.get(schema, :name)
138+
coll_name_ts = "#{coll_name}-#{DateTime.utc_now() |> DateTime.to_unix()}"
139+
updated_schema = Map.put(schema, :name, coll_name_ts)
140+
coll = create_collection(conn, updated_schema, opts)
141+
upsert_collection_alias(conn, coll_name, coll_name_ts)
142+
143+
coll
144+
end
145+
72146
@doc """
73147
Create collection from a map, or module name. Collection name
74148
is matched on table name if using Ecto schema by default.
@@ -306,6 +380,7 @@ defmodule ExTypesense.Collection do
306380
Get the collection name by alias.
307381
"""
308382
@doc since: "1.0.0"
383+
@deprecated "Use Collection.get_collection_alias/1 instead"
309384
@spec get_collection_name(String.t()) ::
310385
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
311386
def get_collection_name(alias_name) do
@@ -322,6 +397,7 @@ defmodule ExTypesense.Collection do
322397
```
323398
"""
324399
@doc since: "1.0.0"
400+
@deprecated "Use Collection.get_collection_alias/2 instead"
325401
@spec get_collection_name(map() | Connection.t() | String.t(), String.t() | keyword()) ::
326402
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
327403
def get_collection_name(alias_name, opts) when is_list(opts) do
@@ -342,6 +418,7 @@ defmodule ExTypesense.Collection do
342418
```
343419
"""
344420
@doc since: "1.0.0"
421+
@deprecated "Use Collection.get_collection_alias/3 instead"
345422
@spec get_collection_name(map() | Connection.t(), String.t(), keyword()) ::
346423
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
347424
def get_collection_name(conn, alias_name, opts) when is_binary(alias_name) do
@@ -402,7 +479,11 @@ defmodule ExTypesense.Collection do
402479
```
403480
"""
404481
@doc since: "1.0.0"
405-
@spec update_collection_fields(map() | Connection.t(), String.t() | module(), map() | keyword()) ::
482+
@spec update_collection_fields(
483+
map() | Connection.t() | String.t() | module(),
484+
String.t() | module() | map(),
485+
map() | keyword()
486+
) ::
406487
{:ok, OpenApiTypesense.CollectionUpdateSchema.t()}
407488
| {:error, OpenApiTypesense.ApiResponse.t()}
408489
def update_collection_fields(name, fields, opts) when is_list(opts) do
@@ -547,10 +628,10 @@ defmodule ExTypesense.Collection do
547628
end
548629

549630
@doc """
550-
Get a specific collection alias.
631+
Get a specific collection by alias.
551632
"""
552633
@doc since: "1.0.0"
553-
@spec get_collection_alias(String.t()) ::
634+
@spec get_collection_alias(String.t() | module()) ::
554635
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
555636
def get_collection_alias(alias_name) do
556637
get_collection_alias(alias_name, [])
@@ -568,7 +649,10 @@ defmodule ExTypesense.Collection do
568649
```
569650
"""
570651
@doc since: "1.0.0"
571-
@spec get_collection_alias(map() | Connection.t() | String.t(), String.t() | keyword()) ::
652+
@spec get_collection_alias(
653+
map() | Connection.t() | String.t() | module(),
654+
String.t() | module() | keyword()
655+
) ::
572656
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
573657
def get_collection_alias(alias_name, opts) when is_list(opts) do
574658
Connection.new() |> get_collection_alias(alias_name, opts)
@@ -588,8 +672,13 @@ defmodule ExTypesense.Collection do
588672
```
589673
"""
590674
@doc since: "1.0.0"
591-
@spec get_collection_alias(map() | Connection.t(), String.t(), keyword()) ::
675+
@spec get_collection_alias(map() | Connection.t(), String.t() | module(), keyword()) ::
592676
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
677+
def get_collection_alias(conn, module, opts) when is_atom(module) do
678+
alias_name = module.__schema__(:source)
679+
get_collection_alias(conn, alias_name, opts)
680+
end
681+
593682
def get_collection_alias(conn, alias_name, opts) do
594683
OpenApiTypesense.Collections.get_alias(conn, alias_name, opts)
595684
end
@@ -623,14 +712,18 @@ defmodule ExTypesense.Collection do
623712
```
624713
"""
625714
@doc since: "1.0.0"
626-
@spec upsert_collection_alias(map() | Connection.t(), String.t(), String.t() | module()) ::
715+
@spec upsert_collection_alias(
716+
map() | Connection.t() | String.t(),
717+
String.t() | module(),
718+
String.t() | module() | keyword()
719+
) ::
627720
{:ok, OpenApiTypesense.CollectionAlias.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
628721
def upsert_collection_alias(alias_name, coll_name, opts) when is_list(opts) do
629722
Connection.new() |> upsert_collection_alias(alias_name, coll_name, opts)
630723
end
631724

632-
def upsert_collection_alias(conn, alias_name, collection_name) do
633-
upsert_collection_alias(conn, alias_name, collection_name, [])
725+
def upsert_collection_alias(conn, alias_name, coll_name) do
726+
upsert_collection_alias(conn, alias_name, coll_name, [])
634727
end
635728

636729
@doc """

lib/ex_typesense/conversation.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ defmodule ExTypesense.Conversation do
120120
```
121121
"""
122122
@doc since: "1.0.0"
123-
@spec get_model(map() | ConnString.t(), String.t(), keyword()) ::
123+
@spec get_model(map() | Connection.t(), String.t(), keyword()) ::
124124
{:ok, OpenApiTypesense.ConversationModelSchema.t()} | :error
125125
def get_model(conn, model_id, opts) do
126126
OpenApiTypesense.Conversations.retrieve_conversation_model(conn, model_id, opts)

lib/ex_typesense/document.ex

+8-8
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ defmodule ExTypesense.Document do
142142
...> ExTypesense.import_documents("posts", posts)
143143
"""
144144
@doc since: "1.0.0"
145-
@spec import_documents(String.t() | module(), list(struct()) | list(map())) ::
146-
{:ok, String.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
145+
@spec import_documents(String.t() | module(), [struct()] | [map()]) ::
146+
{:ok, [map()]} | {:error, OpenApiTypesense.ApiResponse.t()}
147147
def import_documents(coll_name, docs) do
148148
import_documents(coll_name, docs, [])
149149
end
@@ -166,15 +166,15 @@ defmodule ExTypesense.Document do
166166
@doc since: "1.0.0"
167167
@spec import_documents(
168168
map() | Connection.t() | String.t() | module(),
169-
String.t() | list(struct()) | list(map()),
170-
list(struct()) | list(map()) | keyword()
169+
String.t() | [struct()] | [map()],
170+
[struct()] | [map()] | keyword()
171171
) ::
172-
{:ok, String.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
172+
{:ok, [map()]} | {:error, OpenApiTypesense.ApiResponse.t()}
173173
def import_documents(conn, coll_name, documents) when is_map(conn) and is_list(documents) do
174174
import_documents(conn, coll_name, documents, [])
175175
end
176176

177-
def import_documents(coll_name, docs, opts) when is_list(opts) do
177+
def import_documents(coll_name, docs, opts) when is_list(docs) and is_list(opts) do
178178
Connection.new() |> import_documents(coll_name, docs, opts)
179179
end
180180

@@ -191,10 +191,10 @@ defmodule ExTypesense.Document do
191191
@spec import_documents(
192192
map() | Connection.t(),
193193
String.t() | module(),
194-
list(struct()) | list(map()),
194+
[struct()] | [map()],
195195
keyword()
196196
) ::
197-
{:ok, String.t()} | {:error, OpenApiTypesense.ApiResponse.t()}
197+
{:ok, [map()]} | {:error, OpenApiTypesense.ApiResponse.t()}
198198
def import_documents(conn, module, docs, opts) when is_atom(module) do
199199
coll_name = module.__schema__(:source)
200200
import_documents(conn, coll_name, docs, opts)

0 commit comments

Comments
 (0)