@tsctl/cli 0.2.0
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 +735 -0
- package/bin/tsctl.js +2 -0
- package/package.json +65 -0
- package/src/__tests__/analyticsrules.test.ts +303 -0
- package/src/__tests__/apikeys.test.ts +223 -0
- package/src/__tests__/apply.test.ts +245 -0
- package/src/__tests__/client.test.ts +48 -0
- package/src/__tests__/collection-advanced.test.ts +274 -0
- package/src/__tests__/config-loader.test.ts +217 -0
- package/src/__tests__/curationsets.test.ts +190 -0
- package/src/__tests__/helpers.ts +17 -0
- package/src/__tests__/import-drift.test.ts +231 -0
- package/src/__tests__/migrate-advanced.test.ts +197 -0
- package/src/__tests__/migrate.test.ts +220 -0
- package/src/__tests__/plan-new-resources.test.ts +258 -0
- package/src/__tests__/plan.test.ts +337 -0
- package/src/__tests__/presets.test.ts +97 -0
- package/src/__tests__/resources.test.ts +592 -0
- package/src/__tests__/setup.ts +77 -0
- package/src/__tests__/state.test.ts +312 -0
- package/src/__tests__/stemmingdictionaries.test.ts +111 -0
- package/src/__tests__/stopwords.test.ts +109 -0
- package/src/__tests__/synonymsets.test.ts +170 -0
- package/src/__tests__/types.test.ts +416 -0
- package/src/apply/index.ts +336 -0
- package/src/cli/index.ts +1106 -0
- package/src/client/index.ts +55 -0
- package/src/config/loader.ts +158 -0
- package/src/index.ts +45 -0
- package/src/migrate/index.ts +220 -0
- package/src/plan/index.ts +1333 -0
- package/src/resources/alias.ts +59 -0
- package/src/resources/analyticsrule.ts +134 -0
- package/src/resources/apikey.ts +203 -0
- package/src/resources/collection.ts +424 -0
- package/src/resources/curationset.ts +155 -0
- package/src/resources/index.ts +11 -0
- package/src/resources/override.ts +174 -0
- package/src/resources/preset.ts +83 -0
- package/src/resources/stemmingdictionary.ts +103 -0
- package/src/resources/stopword.ts +100 -0
- package/src/resources/synonym.ts +152 -0
- package/src/resources/synonymset.ts +144 -0
- package/src/state/index.ts +206 -0
- package/src/types/index.ts +451 -0
package/README.md
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
# tsctl - Terraform-like CLI for Typesense
|
|
2
|
+
|
|
3
|
+
A declarative infrastructure-as-code CLI for managing Typesense collections, aliases, synonyms, curations, analytics, API keys, stopwords, presets, and more.
|
|
4
|
+
|
|
5
|
+
Supports **Typesense v27+** and **v30+** with full backward compatibility.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Declarative configuration**: Define your Typesense schema in TypeScript, JSON, or YAML config files
|
|
10
|
+
- **Plan/Apply workflow**: See what will change before applying
|
|
11
|
+
- **State management**: State stored in Typesense itself—no external dependencies
|
|
12
|
+
- **Type-safe**: Full TypeScript support with autocomplete and validation
|
|
13
|
+
- **Import existing**: Import existing Typesense resources into managed state
|
|
14
|
+
- **Drift detection**: Detect changes made outside of tsctl
|
|
15
|
+
- **Blue/green migrations**: Zero-downtime collection schema updates
|
|
16
|
+
- **Multi-environment**: Manage development, staging, and production environments
|
|
17
|
+
- **v29/v30 compatible**: Supports both legacy per-collection synonyms/overrides and v30 global synonym sets/curation sets
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @tsctl/cli
|
|
23
|
+
# or
|
|
24
|
+
npx @tsctl/cli
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Initialize a project
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tsctl init
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This creates:
|
|
36
|
+
- `tsctl.config.ts` - Your infrastructure definition
|
|
37
|
+
- `.env` - Connection settings
|
|
38
|
+
|
|
39
|
+
### 2. Configure connection
|
|
40
|
+
|
|
41
|
+
Edit `.env`:
|
|
42
|
+
|
|
43
|
+
```env
|
|
44
|
+
TYPESENSE_HOST=localhost
|
|
45
|
+
TYPESENSE_PORT=8108
|
|
46
|
+
TYPESENSE_PROTOCOL=http
|
|
47
|
+
TYPESENSE_API_KEY=your-api-key-here
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 3. Define your schema
|
|
51
|
+
|
|
52
|
+
Edit `tsctl.config.ts`:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { defineConfig } from "@tsctl/cli";
|
|
56
|
+
|
|
57
|
+
export default defineConfig({
|
|
58
|
+
collections: [
|
|
59
|
+
{
|
|
60
|
+
name: "products",
|
|
61
|
+
fields: [
|
|
62
|
+
{ name: "name", type: "string" },
|
|
63
|
+
{ name: "description", type: "string", optional: true },
|
|
64
|
+
{ name: "price", type: "float" },
|
|
65
|
+
{ name: "category", type: "string", facet: true },
|
|
66
|
+
{ name: "tags", type: "string[]", facet: true, optional: true },
|
|
67
|
+
],
|
|
68
|
+
default_sorting_field: "price",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
aliases: [
|
|
72
|
+
{
|
|
73
|
+
name: "products_live",
|
|
74
|
+
collection: "products",
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 4. Plan changes
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
tsctl plan
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Output:
|
|
87
|
+
```
|
|
88
|
+
Typesense Plan:
|
|
89
|
+
|
|
90
|
+
+ collection.products (create)
|
|
91
|
+
+ name: "products"
|
|
92
|
+
+ fields: [...]
|
|
93
|
+
|
|
94
|
+
+ alias.products_live (create)
|
|
95
|
+
+ name: "products_live"
|
|
96
|
+
+ collection: "products"
|
|
97
|
+
|
|
98
|
+
Summary:
|
|
99
|
+
2 to create, 0 to update, 0 to delete, 0 unchanged
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 5. Apply changes
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
tsctl apply
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Commands
|
|
109
|
+
|
|
110
|
+
| Command | Description |
|
|
111
|
+
|---------|-------------|
|
|
112
|
+
| `tsctl init` | Initialize a new project |
|
|
113
|
+
| `tsctl validate` | Validate config file |
|
|
114
|
+
| `tsctl plan` | Show planned changes |
|
|
115
|
+
| `tsctl apply` | Apply changes to Typesense |
|
|
116
|
+
| `tsctl destroy` | Destroy all managed resources |
|
|
117
|
+
| `tsctl import` | Import existing resources |
|
|
118
|
+
| `tsctl state list` | List managed resources |
|
|
119
|
+
| `tsctl state show` | Show full state JSON |
|
|
120
|
+
| `tsctl state clear` | Clear state (keeps resources) |
|
|
121
|
+
| `tsctl env list` | List available environments |
|
|
122
|
+
| `tsctl env show` | Show current environment config |
|
|
123
|
+
| `tsctl drift` | Detect changes made outside of tsctl |
|
|
124
|
+
| `tsctl migrate` | Blue/green migration for collections |
|
|
125
|
+
|
|
126
|
+
**Global Options:**
|
|
127
|
+
- `--env <name>` - Use environment-specific `.env.<name>` file
|
|
128
|
+
|
|
129
|
+
## Configuration Files
|
|
130
|
+
|
|
131
|
+
tsctl supports multiple configuration file formats and locations. Files are searched in the following order:
|
|
132
|
+
|
|
133
|
+
| File | Format |
|
|
134
|
+
|------|--------|
|
|
135
|
+
| `package.json` | `"tsctl"` property |
|
|
136
|
+
| `.tsctlrc` | JSON or YAML |
|
|
137
|
+
| `.tsctlrc.json` | JSON |
|
|
138
|
+
| `.tsctlrc.yaml` / `.tsctlrc.yml` | YAML |
|
|
139
|
+
| `.tsctlrc.js` / `.tsctlrc.cjs` / `.tsctlrc.mjs` | JavaScript |
|
|
140
|
+
| `.tsctlrc.ts` / `.tsctlrc.cts` / `.tsctlrc.mts` | TypeScript |
|
|
141
|
+
| `tsctl.config.js` / `tsctl.config.cjs` / `tsctl.config.mjs` | JavaScript |
|
|
142
|
+
| `tsctl.config.ts` / `tsctl.config.cts` / `tsctl.config.mts` | TypeScript |
|
|
143
|
+
| `tsctl.config.json` | JSON |
|
|
144
|
+
| `tsctl.config.yaml` / `tsctl.config.yml` | YAML |
|
|
145
|
+
| `typesense.config.*` | Legacy (all formats) |
|
|
146
|
+
|
|
147
|
+
### Examples
|
|
148
|
+
|
|
149
|
+
**TypeScript (recommended):**
|
|
150
|
+
```typescript
|
|
151
|
+
// tsctl.config.ts
|
|
152
|
+
import { defineConfig } from "@tsctl/cli";
|
|
153
|
+
|
|
154
|
+
export default defineConfig({
|
|
155
|
+
collections: [{ name: "products", fields: [...] }],
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**JSON:**
|
|
160
|
+
```json
|
|
161
|
+
// tsctl.config.json
|
|
162
|
+
{
|
|
163
|
+
"collections": [{ "name": "products", "fields": [...] }]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**YAML:**
|
|
168
|
+
```yaml
|
|
169
|
+
# tsctl.config.yaml
|
|
170
|
+
collections:
|
|
171
|
+
- name: products
|
|
172
|
+
fields:
|
|
173
|
+
- name: title
|
|
174
|
+
type: string
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**package.json:**
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"name": "my-app",
|
|
181
|
+
"tsctl": {
|
|
182
|
+
"collections": [{ "name": "products", "fields": [...] }]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Configuration Reference
|
|
188
|
+
|
|
189
|
+
### Collections
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
{
|
|
193
|
+
name: "products",
|
|
194
|
+
fields: [
|
|
195
|
+
{
|
|
196
|
+
name: "title",
|
|
197
|
+
type: "string", // Required
|
|
198
|
+
optional: true, // Allow null/missing
|
|
199
|
+
facet: true, // Enable faceting
|
|
200
|
+
index: true, // Index for search (default: true)
|
|
201
|
+
sort: true, // Enable sorting
|
|
202
|
+
infix: true, // Enable infix search
|
|
203
|
+
locale: "en", // Language for stemming
|
|
204
|
+
stem: true, // Enable stemming
|
|
205
|
+
stem_dictionary: "en-plurals", // Custom stemming dictionary
|
|
206
|
+
store: true, // Store original value
|
|
207
|
+
num_dim: 384, // Vector dimensions
|
|
208
|
+
vec_dist: "cosine", // Vector distance metric
|
|
209
|
+
reference: "users.id", // JOINs
|
|
210
|
+
range_index: true, // For numeric range queries
|
|
211
|
+
truncate_len: 200, // Max chars per token (default: 100)
|
|
212
|
+
token_separators: ["-"], // Field-level token separators
|
|
213
|
+
symbols_to_index: ["#"], // Field-level symbols to index
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
default_sorting_field: "created_at",
|
|
217
|
+
token_separators: ["-", "/"],
|
|
218
|
+
symbols_to_index: ["#", "@"],
|
|
219
|
+
enable_nested_fields: true,
|
|
220
|
+
metadata: { team: "search" }, // Custom metadata
|
|
221
|
+
synonym_sets: ["clothing-synonyms"], // Link synonym sets (v30+)
|
|
222
|
+
curation_sets: ["product-curations"], // Link curation sets (v30+)
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Field Types
|
|
227
|
+
|
|
228
|
+
- `string`, `string[]` - Text
|
|
229
|
+
- `int32`, `int32[]`, `int64`, `int64[]` - Integers
|
|
230
|
+
- `float`, `float[]` - Decimals
|
|
231
|
+
- `bool`, `bool[]` - Booleans
|
|
232
|
+
- `geopoint`, `geopoint[]` - Coordinates
|
|
233
|
+
- `geopolygon` - Geographic polygon
|
|
234
|
+
- `object`, `object[]` - Nested objects
|
|
235
|
+
- `auto` - Auto-detect type
|
|
236
|
+
- `string*` - Auto-embedding
|
|
237
|
+
- `image` - Image embedding
|
|
238
|
+
|
|
239
|
+
### Aliases
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
{
|
|
243
|
+
name: "products_live",
|
|
244
|
+
collection: "products",
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Synonyms (Legacy - Typesense < 30.0)
|
|
249
|
+
|
|
250
|
+
Per-collection synonyms for Typesense versions before 30.0.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
{
|
|
254
|
+
id: "smartphone-synonyms",
|
|
255
|
+
collection: "products",
|
|
256
|
+
synonyms: ["phone", "mobile", "smartphone", "cell phone"],
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
For one-way synonyms (root word):
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
{
|
|
264
|
+
id: "tv-synonym",
|
|
265
|
+
collection: "products",
|
|
266
|
+
root: "television",
|
|
267
|
+
synonyms: ["tv", "telly", "television set"],
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Synonym Sets (Typesense 30.0+ - Global)
|
|
272
|
+
|
|
273
|
+
Global synonym sets that can be shared across collections.
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
{
|
|
277
|
+
name: "clothing-synonyms",
|
|
278
|
+
items: [
|
|
279
|
+
{ id: "pants", synonyms: ["pants", "trousers", "slacks"] },
|
|
280
|
+
{ id: "shirt", synonyms: ["shirt", "top", "blouse"] },
|
|
281
|
+
{ id: "tv", root: "television", synonyms: ["tv", "telly"] },
|
|
282
|
+
],
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Link to collections:
|
|
287
|
+
```typescript
|
|
288
|
+
{
|
|
289
|
+
name: "products",
|
|
290
|
+
fields: [...],
|
|
291
|
+
synonym_sets: ["clothing-synonyms"],
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Overrides/Curations (Legacy - Typesense < 30.0)
|
|
296
|
+
|
|
297
|
+
Per-collection overrides for Typesense versions before 30.0.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
{
|
|
301
|
+
id: "pin-featured",
|
|
302
|
+
collection: "products",
|
|
303
|
+
rule: {
|
|
304
|
+
query: "featured",
|
|
305
|
+
match: "exact",
|
|
306
|
+
},
|
|
307
|
+
includes: [
|
|
308
|
+
{ id: "product-123", position: 1 },
|
|
309
|
+
{ id: "product-456", position: 2 },
|
|
310
|
+
],
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Curation Sets (Typesense 30.0+ - Global)
|
|
315
|
+
|
|
316
|
+
Global curation rules that can be shared across collections.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
{
|
|
320
|
+
name: "product-curations",
|
|
321
|
+
items: [
|
|
322
|
+
{
|
|
323
|
+
id: "pin-featured",
|
|
324
|
+
rule: { query: "featured", match: "exact" },
|
|
325
|
+
includes: [{ id: "product-123", position: 1 }],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "boost-shoes",
|
|
329
|
+
rule: { query: "shoes", match: "contains" },
|
|
330
|
+
filter_by: "category:=footwear",
|
|
331
|
+
sort_by: "popularity:desc",
|
|
332
|
+
remove_matched_tokens: true,
|
|
333
|
+
effective_from_ts: 1672531200,
|
|
334
|
+
effective_to_ts: 1704067200,
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Link to collections:
|
|
341
|
+
```typescript
|
|
342
|
+
{
|
|
343
|
+
name: "products",
|
|
344
|
+
fields: [...],
|
|
345
|
+
curation_sets: ["product-curations"],
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Stopwords
|
|
350
|
+
|
|
351
|
+
Define stopword sets to remove common words from search queries.
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
{
|
|
355
|
+
id: "english-stopwords",
|
|
356
|
+
stopwords: ["the", "a", "an", "is", "are", "was", "were"],
|
|
357
|
+
locale: "en", // optional
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Search Presets
|
|
362
|
+
|
|
363
|
+
Store reusable search parameter configurations.
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
{
|
|
367
|
+
name: "listing_view",
|
|
368
|
+
value: {
|
|
369
|
+
searches: [
|
|
370
|
+
{
|
|
371
|
+
collection: "products",
|
|
372
|
+
q: "*",
|
|
373
|
+
sort_by: "popularity:desc",
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Analytics Rules
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
{
|
|
384
|
+
name: "popular-queries",
|
|
385
|
+
type: "popular_queries", // popular_queries | nohits_queries | counter | log
|
|
386
|
+
collection: "products",
|
|
387
|
+
event_type: "search", // search | click | conversion | visit | custom
|
|
388
|
+
params: {
|
|
389
|
+
destination_collection: "popular_queries",
|
|
390
|
+
limit: 1000,
|
|
391
|
+
expand_query: true,
|
|
392
|
+
},
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### API Keys
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
{
|
|
400
|
+
description: "Search-only key for frontend",
|
|
401
|
+
actions: ["documents:search"],
|
|
402
|
+
collections: ["products", "categories"],
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
With expiration:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
{
|
|
410
|
+
description: "Temporary admin key",
|
|
411
|
+
actions: ["*"],
|
|
412
|
+
collections: ["*"],
|
|
413
|
+
expires_at: 1735689600, // Unix timestamp
|
|
414
|
+
autodelete: true,
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Stemming Dictionaries
|
|
419
|
+
|
|
420
|
+
Custom word-to-root mappings for stemming.
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
{
|
|
424
|
+
id: "english-plurals",
|
|
425
|
+
words: [
|
|
426
|
+
{ word: "dogs", root: "dog" },
|
|
427
|
+
{ word: "cats", root: "cat" },
|
|
428
|
+
{ word: "mice", root: "mouse" },
|
|
429
|
+
],
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Reference from fields:
|
|
434
|
+
```typescript
|
|
435
|
+
{
|
|
436
|
+
name: "title",
|
|
437
|
+
type: "string",
|
|
438
|
+
stem_dictionary: "english-plurals",
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
## Full Configuration Example
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { defineConfig } from "@tsctl/cli";
|
|
446
|
+
|
|
447
|
+
export default defineConfig({
|
|
448
|
+
collections: [
|
|
449
|
+
{
|
|
450
|
+
name: "products",
|
|
451
|
+
fields: [
|
|
452
|
+
{ name: "name", type: "string" },
|
|
453
|
+
{ name: "description", type: "string", optional: true },
|
|
454
|
+
{ name: "price", type: "float" },
|
|
455
|
+
{ name: "category", type: "string", facet: true },
|
|
456
|
+
],
|
|
457
|
+
default_sorting_field: "price",
|
|
458
|
+
synonym_sets: ["product-synonyms"],
|
|
459
|
+
curation_sets: ["product-curations"],
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
|
|
463
|
+
aliases: [
|
|
464
|
+
{ name: "products_live", collection: "products" },
|
|
465
|
+
],
|
|
466
|
+
|
|
467
|
+
// v30+ global synonym sets
|
|
468
|
+
synonymSets: [
|
|
469
|
+
{
|
|
470
|
+
name: "product-synonyms",
|
|
471
|
+
items: [
|
|
472
|
+
{ id: "phones", synonyms: ["phone", "mobile", "smartphone"] },
|
|
473
|
+
],
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
|
|
477
|
+
// v30+ global curation sets
|
|
478
|
+
curationSets: [
|
|
479
|
+
{
|
|
480
|
+
name: "product-curations",
|
|
481
|
+
items: [
|
|
482
|
+
{
|
|
483
|
+
id: "featured",
|
|
484
|
+
rule: { query: "featured", match: "exact" },
|
|
485
|
+
includes: [{ id: "product-1", position: 1 }],
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
|
|
491
|
+
stopwords: [
|
|
492
|
+
{ id: "english", stopwords: ["the", "a", "an"] },
|
|
493
|
+
],
|
|
494
|
+
|
|
495
|
+
presets: [
|
|
496
|
+
{ name: "default_search", value: { q: "*", sort_by: "price:asc" } },
|
|
497
|
+
],
|
|
498
|
+
|
|
499
|
+
apiKeys: [
|
|
500
|
+
{
|
|
501
|
+
description: "Search-only key",
|
|
502
|
+
actions: ["documents:search"],
|
|
503
|
+
collections: ["products"],
|
|
504
|
+
},
|
|
505
|
+
],
|
|
506
|
+
|
|
507
|
+
stemmingDictionaries: [
|
|
508
|
+
{
|
|
509
|
+
id: "en-plurals",
|
|
510
|
+
words: [{ word: "shoes", root: "shoe" }],
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## State Management
|
|
517
|
+
|
|
518
|
+
State is stored in a special Typesense collection (`_tsctl_state`). This means:
|
|
519
|
+
|
|
520
|
+
- No external state storage needed
|
|
521
|
+
- State travels with your Typesense instance
|
|
522
|
+
- Easy backup/restore with Typesense snapshots
|
|
523
|
+
|
|
524
|
+
### Import Existing Resources
|
|
525
|
+
|
|
526
|
+
If you have existing collections/aliases:
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
tsctl import
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
This will:
|
|
533
|
+
1. Scan your Typesense instance for all resource types
|
|
534
|
+
2. Generate a `tsctl.imported.config.ts` file
|
|
535
|
+
3. Save the current state
|
|
536
|
+
|
|
537
|
+
Review the generated config, then rename it to `tsctl.config.ts`.
|
|
538
|
+
|
|
539
|
+
## Environment Variables
|
|
540
|
+
|
|
541
|
+
| Variable | Default | Description |
|
|
542
|
+
|----------|---------|-------------|
|
|
543
|
+
| `TYPESENSE_HOST` | `localhost` | Typesense host |
|
|
544
|
+
| `TYPESENSE_PORT` | `8108` | Typesense port |
|
|
545
|
+
| `TYPESENSE_PROTOCOL` | `http` | `http` or `https` |
|
|
546
|
+
| `TYPESENSE_API_KEY` | - | API key (required) |
|
|
547
|
+
|
|
548
|
+
## Multi-Environment Support
|
|
549
|
+
|
|
550
|
+
Manage multiple Typesense environments (development, staging, production) using environment-specific `.env` files.
|
|
551
|
+
|
|
552
|
+
### Setup
|
|
553
|
+
|
|
554
|
+
Initialize with environment files:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
tsctl init --with-environments
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
This creates:
|
|
561
|
+
- `.env` - Default/development settings
|
|
562
|
+
- `.env.development` - Development environment
|
|
563
|
+
- `.env.staging` - Staging environment
|
|
564
|
+
- `.env.production` - Production environment
|
|
565
|
+
|
|
566
|
+
### Usage
|
|
567
|
+
|
|
568
|
+
Use the `--env` flag to target a specific environment:
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
# Plan changes against staging
|
|
572
|
+
tsctl plan --env staging
|
|
573
|
+
|
|
574
|
+
# Apply to production
|
|
575
|
+
tsctl apply --env production
|
|
576
|
+
|
|
577
|
+
# Import from development
|
|
578
|
+
tsctl import --env development
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
## Drift Detection
|
|
582
|
+
|
|
583
|
+
Detect when resources have been modified outside of tsctl (e.g., via Typesense dashboard or API).
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
tsctl drift
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Output shows:
|
|
590
|
+
- **Modified**: Resources changed outside of tsctl
|
|
591
|
+
- **Deleted**: Resources removed outside of tsctl
|
|
592
|
+
- **Unmanaged**: Resources that exist but aren't in your config
|
|
593
|
+
|
|
594
|
+
Drift detection covers all resource types: collections, aliases, stopwords, presets, curation sets, and more.
|
|
595
|
+
|
|
596
|
+
### CI/CD Integration
|
|
597
|
+
|
|
598
|
+
The `drift` command exits with code 1 if drift is detected, making it useful for CI pipelines:
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
# In your CI pipeline
|
|
602
|
+
tsctl drift --env production || echo "Drift detected!"
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### JSON Output
|
|
606
|
+
|
|
607
|
+
For programmatic use:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
tsctl drift --json
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Blue/Green Migrations
|
|
614
|
+
|
|
615
|
+
Perform zero-downtime collection schema updates using the blue/green deployment pattern.
|
|
616
|
+
|
|
617
|
+
### How It Works
|
|
618
|
+
|
|
619
|
+
1. **Create** a new versioned collection (e.g., `products_1706486400000`)
|
|
620
|
+
2. **Index** your data to the new collection
|
|
621
|
+
3. **Switch** the alias to point to the new collection
|
|
622
|
+
4. **Cleanup** the old collection when ready
|
|
623
|
+
|
|
624
|
+
### Quick Migration
|
|
625
|
+
|
|
626
|
+
Full migration in one command:
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
tsctl migrate -a products_live -c tsctl.config.ts
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### Step-by-Step Migration
|
|
633
|
+
|
|
634
|
+
For more control, migrate in stages:
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
# Step 1: Create the new collection
|
|
638
|
+
tsctl migrate -a products_live -c tsctl.config.ts --create-only
|
|
639
|
+
|
|
640
|
+
# Step 2: Index your data to the new collection
|
|
641
|
+
# (use your own indexing process)
|
|
642
|
+
|
|
643
|
+
# Step 3: Switch the alias to the new collection
|
|
644
|
+
tsctl migrate -a products_live -c tsctl.config.ts --switch-only
|
|
645
|
+
|
|
646
|
+
# Step 4: Delete the old collection when ready
|
|
647
|
+
tsctl migrate -a products_live -c tsctl.config.ts --cleanup products_1706486400000
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Options
|
|
651
|
+
|
|
652
|
+
| Option | Description |
|
|
653
|
+
|--------|-------------|
|
|
654
|
+
| `-a, --alias <name>` | Alias to migrate (required) |
|
|
655
|
+
| `-c, --config <path>` | Path to config file (required) |
|
|
656
|
+
| `--collection <name>` | Collection from config (if multiple) |
|
|
657
|
+
| `--skip-delete` | Keep old collection for rollback |
|
|
658
|
+
| `--create-only` | Only create new collection |
|
|
659
|
+
| `--switch-only` | Only switch alias |
|
|
660
|
+
| `--cleanup <name>` | Delete old collection |
|
|
661
|
+
|
|
662
|
+
## Typesense Version Compatibility
|
|
663
|
+
|
|
664
|
+
| Feature | v27 | v28 | v29 | v30+ |
|
|
665
|
+
|---------|-----|-----|-----|------|
|
|
666
|
+
| Collections | Yes | Yes | Yes | Yes |
|
|
667
|
+
| Aliases | Yes | Yes | Yes | Yes |
|
|
668
|
+
| Per-collection Synonyms | Yes | Yes | Yes | Deprecated* |
|
|
669
|
+
| Per-collection Overrides | Yes | Yes | Yes | Deprecated* |
|
|
670
|
+
| Global Synonym Sets | - | Yes | Yes | Yes |
|
|
671
|
+
| Global Curation Sets | - | - | - | Yes |
|
|
672
|
+
| API Keys | Yes | Yes | Yes | Yes |
|
|
673
|
+
| Analytics Rules | Yes | Yes | Yes | Yes |
|
|
674
|
+
| Stopwords | Yes | Yes | Yes | Yes |
|
|
675
|
+
| Presets | Yes | Yes | Yes | Yes |
|
|
676
|
+
| Stemming Dictionaries | - | Yes | Yes | Yes |
|
|
677
|
+
|
|
678
|
+
\* Auto-migrated to global sets on upgrade to v30
|
|
679
|
+
|
|
680
|
+
## Development
|
|
681
|
+
|
|
682
|
+
### Prerequisites
|
|
683
|
+
|
|
684
|
+
- [Bun](https://bun.sh) v1.0+
|
|
685
|
+
- [Docker](https://www.docker.com/) (for running Typesense locally)
|
|
686
|
+
|
|
687
|
+
### Setup
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
# Clone the repo
|
|
691
|
+
git clone https://github.com/akshitkrnagpal/tsctl.git
|
|
692
|
+
cd tsctl
|
|
693
|
+
|
|
694
|
+
# Install dependencies
|
|
695
|
+
bun install
|
|
696
|
+
|
|
697
|
+
# Start Typesense (v27)
|
|
698
|
+
docker compose up -d
|
|
699
|
+
|
|
700
|
+
# Or start Typesense v30
|
|
701
|
+
docker compose -f docker-compose.v30.yml up -d
|
|
702
|
+
|
|
703
|
+
# Run tests
|
|
704
|
+
bun test
|
|
705
|
+
|
|
706
|
+
# Type check
|
|
707
|
+
bun run typecheck
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### Running Tests
|
|
711
|
+
|
|
712
|
+
Tests run against a live Typesense instance. Start one with Docker Compose before running tests:
|
|
713
|
+
|
|
714
|
+
```bash
|
|
715
|
+
# Test against Typesense v27 (default)
|
|
716
|
+
docker compose up -d
|
|
717
|
+
bun test
|
|
718
|
+
|
|
719
|
+
# Test against Typesense v30
|
|
720
|
+
docker compose -f docker-compose.v30.yml up -d
|
|
721
|
+
bun test
|
|
722
|
+
|
|
723
|
+
# Stop Typesense
|
|
724
|
+
docker compose down -v
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Tests are version-aware and automatically skip tests for features not available in the running Typesense version.
|
|
728
|
+
|
|
729
|
+
### CI/CD
|
|
730
|
+
|
|
731
|
+
The project uses GitHub Actions to run tests against both Typesense v27 and v30 on every push and pull request. See `.github/workflows/ci.yml`.
|
|
732
|
+
|
|
733
|
+
## License
|
|
734
|
+
|
|
735
|
+
MIT
|