@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.
Files changed (45) hide show
  1. package/README.md +735 -0
  2. package/bin/tsctl.js +2 -0
  3. package/package.json +65 -0
  4. package/src/__tests__/analyticsrules.test.ts +303 -0
  5. package/src/__tests__/apikeys.test.ts +223 -0
  6. package/src/__tests__/apply.test.ts +245 -0
  7. package/src/__tests__/client.test.ts +48 -0
  8. package/src/__tests__/collection-advanced.test.ts +274 -0
  9. package/src/__tests__/config-loader.test.ts +217 -0
  10. package/src/__tests__/curationsets.test.ts +190 -0
  11. package/src/__tests__/helpers.ts +17 -0
  12. package/src/__tests__/import-drift.test.ts +231 -0
  13. package/src/__tests__/migrate-advanced.test.ts +197 -0
  14. package/src/__tests__/migrate.test.ts +220 -0
  15. package/src/__tests__/plan-new-resources.test.ts +258 -0
  16. package/src/__tests__/plan.test.ts +337 -0
  17. package/src/__tests__/presets.test.ts +97 -0
  18. package/src/__tests__/resources.test.ts +592 -0
  19. package/src/__tests__/setup.ts +77 -0
  20. package/src/__tests__/state.test.ts +312 -0
  21. package/src/__tests__/stemmingdictionaries.test.ts +111 -0
  22. package/src/__tests__/stopwords.test.ts +109 -0
  23. package/src/__tests__/synonymsets.test.ts +170 -0
  24. package/src/__tests__/types.test.ts +416 -0
  25. package/src/apply/index.ts +336 -0
  26. package/src/cli/index.ts +1106 -0
  27. package/src/client/index.ts +55 -0
  28. package/src/config/loader.ts +158 -0
  29. package/src/index.ts +45 -0
  30. package/src/migrate/index.ts +220 -0
  31. package/src/plan/index.ts +1333 -0
  32. package/src/resources/alias.ts +59 -0
  33. package/src/resources/analyticsrule.ts +134 -0
  34. package/src/resources/apikey.ts +203 -0
  35. package/src/resources/collection.ts +424 -0
  36. package/src/resources/curationset.ts +155 -0
  37. package/src/resources/index.ts +11 -0
  38. package/src/resources/override.ts +174 -0
  39. package/src/resources/preset.ts +83 -0
  40. package/src/resources/stemmingdictionary.ts +103 -0
  41. package/src/resources/stopword.ts +100 -0
  42. package/src/resources/synonym.ts +152 -0
  43. package/src/resources/synonymset.ts +144 -0
  44. package/src/state/index.ts +206 -0
  45. package/src/types/index.ts +451 -0
@@ -0,0 +1,451 @@
1
+ import { z } from "zod";
2
+
3
+ // ============================================================================
4
+ // Field Types
5
+ // ============================================================================
6
+
7
+ export const FieldTypeSchema = z.enum([
8
+ "string",
9
+ "string[]",
10
+ "int32",
11
+ "int32[]",
12
+ "int64",
13
+ "int64[]",
14
+ "float",
15
+ "float[]",
16
+ "bool",
17
+ "bool[]",
18
+ "geopoint",
19
+ "geopoint[]",
20
+ "geopolygon",
21
+ "object",
22
+ "object[]",
23
+ "auto",
24
+ "string*",
25
+ "image",
26
+ ]);
27
+
28
+ export type FieldType = z.infer<typeof FieldTypeSchema>;
29
+
30
+ // ============================================================================
31
+ // Collection Field Schema
32
+ // ============================================================================
33
+
34
+ export const FieldSchema = z.object({
35
+ name: z.string(),
36
+ type: FieldTypeSchema,
37
+ optional: z.boolean().optional(),
38
+ facet: z.boolean().optional(),
39
+ index: z.boolean().optional(),
40
+ sort: z.boolean().optional(),
41
+ infix: z.boolean().optional(),
42
+ locale: z.string().optional(),
43
+ stem: z.boolean().optional(),
44
+ store: z.boolean().optional(),
45
+ num_dim: z.number().optional(),
46
+ vec_dist: z.enum(["cosine", "ip"]).optional(),
47
+ reference: z.string().optional(),
48
+ range_index: z.boolean().optional(),
49
+ stem_dictionary: z.string().optional(),
50
+ truncate_len: z.number().optional(),
51
+ token_separators: z.array(z.string()).optional(),
52
+ symbols_to_index: z.array(z.string()).optional(),
53
+ embed: z
54
+ .object({
55
+ from: z.array(z.string()),
56
+ model_config: z.object({
57
+ model_name: z.string(),
58
+ api_key: z.string().optional(),
59
+ indexing_prefix: z.string().optional(),
60
+ query_prefix: z.string().optional(),
61
+ // GCP Vertex AI configuration
62
+ project_id: z.string().optional(),
63
+ service_account: z
64
+ .object({
65
+ client_email: z.string(),
66
+ private_key: z.string(),
67
+ })
68
+ .optional(),
69
+ // GCP OAuth configuration (alternative to service_account)
70
+ access_token: z.string().optional(),
71
+ refresh_token: z.string().optional(),
72
+ client_id: z.string().optional(),
73
+ client_secret: z.string().optional(),
74
+ }),
75
+ })
76
+ .optional(),
77
+ });
78
+
79
+ export type Field = z.infer<typeof FieldSchema>;
80
+
81
+ // ============================================================================
82
+ // Collection Schema
83
+ // ============================================================================
84
+
85
+ export const CollectionConfigSchema = z.object({
86
+ name: z.string(),
87
+ fields: z.array(FieldSchema),
88
+ default_sorting_field: z.string().optional(),
89
+ token_separators: z.array(z.string()).optional(),
90
+ symbols_to_index: z.array(z.string()).optional(),
91
+ enable_nested_fields: z.boolean().optional(),
92
+ // Typesense 30.0+: Link to global synonym sets
93
+ synonym_sets: z.array(z.string()).optional(),
94
+ // Typesense 30.0+: Link to global curation sets
95
+ curation_sets: z.array(z.string()).optional(),
96
+ // Custom metadata object
97
+ metadata: z.record(z.unknown()).optional(),
98
+ });
99
+
100
+ export type CollectionConfig = z.infer<typeof CollectionConfigSchema>;
101
+
102
+ // ============================================================================
103
+ // Alias Schema
104
+ // ============================================================================
105
+
106
+ export const AliasConfigSchema = z.object({
107
+ name: z.string(),
108
+ collection: z.string(),
109
+ });
110
+
111
+ export type AliasConfig = z.infer<typeof AliasConfigSchema>;
112
+
113
+ // ============================================================================
114
+ // Synonym Schema (Legacy - per-collection, for Typesense < 30.0)
115
+ // ============================================================================
116
+
117
+ export const SynonymConfigSchema = z.object({
118
+ id: z.string(),
119
+ collection: z.string(),
120
+ synonyms: z.array(z.string()).optional(),
121
+ root: z.string().optional(),
122
+ symbols_to_index: z.array(z.string()).optional(),
123
+ locale: z.string().optional(),
124
+ });
125
+
126
+ export type SynonymConfig = z.infer<typeof SynonymConfigSchema>;
127
+
128
+ // ============================================================================
129
+ // Synonym Set Schema (Typesense 30.0+ - global, reusable synonym sets)
130
+ // ============================================================================
131
+
132
+ export const SynonymSetItemSchema = z.object({
133
+ id: z.string(),
134
+ synonyms: z.array(z.string()).optional(),
135
+ root: z.string().optional(),
136
+ symbols_to_index: z.array(z.string()).optional(),
137
+ locale: z.string().optional(),
138
+ });
139
+
140
+ export type SynonymSetItem = z.infer<typeof SynonymSetItemSchema>;
141
+
142
+ export const SynonymSetConfigSchema = z.object({
143
+ name: z.string(),
144
+ items: z.array(SynonymSetItemSchema),
145
+ });
146
+
147
+ export type SynonymSetConfig = z.infer<typeof SynonymSetConfigSchema>;
148
+
149
+ // ============================================================================
150
+ // Override/Curation Schema (planned)
151
+ // ============================================================================
152
+
153
+ export const OverrideRuleSchema = z.object({
154
+ query: z.string().optional(),
155
+ match: z.enum(["exact", "contains"]).optional(),
156
+ filter_by: z.string().optional(),
157
+ tags: z.array(z.string()).optional(),
158
+ });
159
+
160
+ export const OverrideConfigSchema = z.object({
161
+ id: z.string(),
162
+ collection: z.string(),
163
+ rule: OverrideRuleSchema,
164
+ includes: z
165
+ .array(
166
+ z.object({
167
+ id: z.string(),
168
+ position: z.number(),
169
+ })
170
+ )
171
+ .optional(),
172
+ excludes: z
173
+ .array(
174
+ z.object({
175
+ id: z.string(),
176
+ })
177
+ )
178
+ .optional(),
179
+ filter_by: z.string().optional(),
180
+ sort_by: z.string().optional(),
181
+ replace_query: z.string().optional(),
182
+ remove_matched_tokens: z.boolean().optional(),
183
+ filter_curated_hits: z.boolean().optional(),
184
+ effective_from_ts: z.number().optional(),
185
+ effective_to_ts: z.number().optional(),
186
+ stop_processing: z.boolean().optional(),
187
+ });
188
+
189
+ export type OverrideConfig = z.infer<typeof OverrideConfigSchema>;
190
+
191
+ // ============================================================================
192
+ // Analytics Rule Schema
193
+ // ============================================================================
194
+
195
+ export const AnalyticsRuleParamsSchema = z.object({
196
+ destination_collection: z.string().optional(),
197
+ // v30+: structured source/destination
198
+ source: z
199
+ .object({
200
+ collections: z.array(z.string()).optional(),
201
+ events: z
202
+ .array(
203
+ z.object({
204
+ type: z.string(),
205
+ name: z.string(),
206
+ weight: z.number().optional(),
207
+ })
208
+ )
209
+ .optional(),
210
+ })
211
+ .optional(),
212
+ destination: z
213
+ .object({
214
+ collection: z.string(),
215
+ })
216
+ .optional(),
217
+ limit: z.number().optional(),
218
+ capture_search_requests: z.boolean().optional(),
219
+ meta_fields: z.array(z.string()).optional(),
220
+ expand_query: z.boolean().optional(),
221
+ counter_field: z.string().optional(),
222
+ weight: z.number().optional(),
223
+ });
224
+
225
+ export const AnalyticsRuleConfigSchema = z.object({
226
+ name: z.string(),
227
+ type: z.enum(["popular_queries", "nohits_queries", "counter", "log"]),
228
+ collection: z.string(),
229
+ event_type: z.enum(["search", "click", "conversion", "visit", "custom"]),
230
+ rule_tag: z.string().optional(),
231
+ params: AnalyticsRuleParamsSchema.optional(),
232
+ });
233
+
234
+ export type AnalyticsRuleConfig = z.infer<typeof AnalyticsRuleConfigSchema>;
235
+
236
+ // ============================================================================
237
+ // API Key Schema
238
+ // ============================================================================
239
+
240
+ export const ApiKeyConfigSchema = z.object({
241
+ description: z.string(),
242
+ actions: z.array(z.string()),
243
+ collections: z.array(z.string()),
244
+ value: z.string().optional(), // Only used when creating, not stored in state
245
+ expires_at: z.number().optional(),
246
+ autodelete: z.boolean().optional(),
247
+ });
248
+
249
+ export type ApiKeyConfig = z.infer<typeof ApiKeyConfigSchema>;
250
+
251
+ // ============================================================================
252
+ // Stopword Set Schema
253
+ // ============================================================================
254
+
255
+ export const StopwordSetConfigSchema = z.object({
256
+ id: z.string(),
257
+ stopwords: z.array(z.string()),
258
+ locale: z.string().optional(),
259
+ });
260
+
261
+ export type StopwordSetConfig = z.infer<typeof StopwordSetConfigSchema>;
262
+
263
+ // ============================================================================
264
+ // Preset Schema
265
+ // ============================================================================
266
+
267
+ export const PresetConfigSchema = z.object({
268
+ name: z.string(),
269
+ value: z.record(z.unknown()),
270
+ });
271
+
272
+ export type PresetConfig = z.infer<typeof PresetConfigSchema>;
273
+
274
+ // ============================================================================
275
+ // Curation Set Schema (Typesense 30.0+ - global curation rules)
276
+ // ============================================================================
277
+
278
+ export const CurationRuleSchema = z.object({
279
+ query: z.string().optional(),
280
+ match: z.enum(["exact", "contains"]).optional(),
281
+ filter_by: z.string().optional(),
282
+ tags: z.array(z.string()).optional(),
283
+ });
284
+
285
+ export const CurationItemSchema = z.object({
286
+ id: z.string(),
287
+ rule: CurationRuleSchema.optional(),
288
+ includes: z
289
+ .array(
290
+ z.object({
291
+ id: z.string(),
292
+ position: z.number(),
293
+ })
294
+ )
295
+ .optional(),
296
+ excludes: z
297
+ .array(
298
+ z.object({
299
+ id: z.string(),
300
+ })
301
+ )
302
+ .optional(),
303
+ filter_by: z.string().optional(),
304
+ sort_by: z.string().optional(),
305
+ replace_query: z.string().optional(),
306
+ remove_matched_tokens: z.boolean().optional(),
307
+ filter_curated_hits: z.boolean().optional(),
308
+ stop_processing: z.boolean().optional(),
309
+ metadata: z.record(z.unknown()).optional(),
310
+ effective_from_ts: z.number().optional(),
311
+ effective_to_ts: z.number().optional(),
312
+ diversity: z
313
+ .object({
314
+ similarity_metric: z.array(
315
+ z.object({
316
+ field: z.string(),
317
+ method: z.enum(["jaccard", "equality", "vector_distance"]),
318
+ weight: z.number().optional(),
319
+ })
320
+ ),
321
+ })
322
+ .optional(),
323
+ });
324
+
325
+ export type CurationItem = z.infer<typeof CurationItemSchema>;
326
+
327
+ export const CurationSetConfigSchema = z.object({
328
+ name: z.string(),
329
+ items: z.array(CurationItemSchema),
330
+ });
331
+
332
+ export type CurationSetConfig = z.infer<typeof CurationSetConfigSchema>;
333
+
334
+ // ============================================================================
335
+ // Stemming Dictionary Schema
336
+ // ============================================================================
337
+
338
+ export const StemmingWordSchema = z.object({
339
+ word: z.string(),
340
+ root: z.string(),
341
+ });
342
+
343
+ export type StemmingWord = z.infer<typeof StemmingWordSchema>;
344
+
345
+ export const StemmingDictionaryConfigSchema = z.object({
346
+ id: z.string(),
347
+ words: z.array(StemmingWordSchema),
348
+ });
349
+
350
+ export type StemmingDictionaryConfig = z.infer<typeof StemmingDictionaryConfigSchema>;
351
+
352
+ // ============================================================================
353
+ // Full Config Schema
354
+ // ============================================================================
355
+
356
+ export const TypesenseConfigSchema = z.object({
357
+ collections: z.array(CollectionConfigSchema).optional(),
358
+ aliases: z.array(AliasConfigSchema).optional(),
359
+ // Legacy per-collection synonyms (Typesense < 30.0)
360
+ synonyms: z.array(SynonymConfigSchema).optional(),
361
+ // Global synonym sets (Typesense 30.0+)
362
+ synonymSets: z.array(SynonymSetConfigSchema).optional(),
363
+ overrides: z.array(OverrideConfigSchema).optional(),
364
+ // Global curation sets (Typesense 30.0+)
365
+ curationSets: z.array(CurationSetConfigSchema).optional(),
366
+ analyticsRules: z.array(AnalyticsRuleConfigSchema).optional(),
367
+ apiKeys: z.array(ApiKeyConfigSchema).optional(),
368
+ stopwords: z.array(StopwordSetConfigSchema).optional(),
369
+ presets: z.array(PresetConfigSchema).optional(),
370
+ stemmingDictionaries: z.array(StemmingDictionaryConfigSchema).optional(),
371
+ });
372
+
373
+ export type TypesenseConfig = z.infer<typeof TypesenseConfigSchema>;
374
+
375
+ // ============================================================================
376
+ // Resource Types for State Management
377
+ // ============================================================================
378
+
379
+ export type ResourceType = "collection" | "alias" | "synonym" | "synonymSet" | "override" | "curationSet" | "analyticsRule" | "apiKey" | "stopword" | "preset" | "stemmingDictionary";
380
+
381
+ export interface ResourceIdentifier {
382
+ type: ResourceType;
383
+ name: string;
384
+ /** For resources scoped to a collection (synonyms, overrides) */
385
+ collection?: string;
386
+ }
387
+
388
+ export interface ManagedResource {
389
+ identifier: ResourceIdentifier;
390
+ config: CollectionConfig | AliasConfig | SynonymConfig | SynonymSetConfig | OverrideConfig | CurationSetConfig | AnalyticsRuleConfig | ApiKeyConfig | StopwordSetConfig | PresetConfig | StemmingDictionaryConfig;
391
+ checksum: string;
392
+ lastUpdated: string;
393
+ }
394
+
395
+ export interface State {
396
+ version: string;
397
+ resources: ManagedResource[];
398
+ }
399
+
400
+ // ============================================================================
401
+ // Plan Types
402
+ // ============================================================================
403
+
404
+ export type ChangeAction = "create" | "update" | "delete" | "no-change";
405
+
406
+ export interface ResourceChange {
407
+ action: ChangeAction;
408
+ identifier: ResourceIdentifier;
409
+ before?: unknown;
410
+ after?: unknown;
411
+ diff?: string;
412
+ }
413
+
414
+ export interface Plan {
415
+ changes: ResourceChange[];
416
+ hasChanges: boolean;
417
+ summary: {
418
+ create: number;
419
+ update: number;
420
+ delete: number;
421
+ noChange: number;
422
+ };
423
+ }
424
+
425
+ // ============================================================================
426
+ // Connection Config
427
+ // ============================================================================
428
+
429
+ export const ConnectionConfigSchema = z.object({
430
+ nodes: z.array(
431
+ z.object({
432
+ host: z.string(),
433
+ port: z.number(),
434
+ protocol: z.enum(["http", "https"]),
435
+ })
436
+ ),
437
+ apiKey: z.string(),
438
+ connectionTimeoutSeconds: z.number().optional(),
439
+ retryIntervalSeconds: z.number().optional(),
440
+ numRetries: z.number().optional(),
441
+ });
442
+
443
+ export type ConnectionConfig = z.infer<typeof ConnectionConfigSchema>;
444
+
445
+ // ============================================================================
446
+ // Define Config Helper
447
+ // ============================================================================
448
+
449
+ export function defineConfig(config: TypesenseConfig): TypesenseConfig {
450
+ return TypesenseConfigSchema.parse(config);
451
+ }