@kelpie/schemas 0.6.0 → 0.8.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 (56) hide show
  1. package/dist/account.d.ts +35 -0
  2. package/dist/account.d.ts.map +1 -1
  3. package/dist/account.js +18 -0
  4. package/dist/account.js.map +1 -1
  5. package/dist/company.d.ts +3 -0
  6. package/dist/company.d.ts.map +1 -1
  7. package/dist/company.js +3 -0
  8. package/dist/company.js.map +1 -1
  9. package/dist/emailDomainLinker.d.ts +16 -0
  10. package/dist/emailDomainLinker.d.ts.map +1 -0
  11. package/dist/emailDomainLinker.js +11 -0
  12. package/dist/emailDomainLinker.js.map +1 -0
  13. package/dist/importExport.d.ts +34 -3
  14. package/dist/importExport.d.ts.map +1 -1
  15. package/dist/importExport.js +28 -4
  16. package/dist/importExport.js.map +1 -1
  17. package/dist/index.d.ts +17 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +9 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/list.d.ts +34 -0
  22. package/dist/list.d.ts.map +1 -0
  23. package/dist/list.js +35 -0
  24. package/dist/list.js.map +1 -0
  25. package/dist/listMember.d.ts +24 -0
  26. package/dist/listMember.d.ts.map +1 -0
  27. package/dist/listMember.js +27 -0
  28. package/dist/listMember.js.map +1 -0
  29. package/dist/listMembership.d.ts +20 -0
  30. package/dist/listMembership.d.ts.map +1 -0
  31. package/dist/listMembership.js +23 -0
  32. package/dist/listMembership.js.map +1 -0
  33. package/dist/publicConfig.d.ts +28 -0
  34. package/dist/publicConfig.d.ts.map +1 -0
  35. package/dist/publicConfig.js +11 -0
  36. package/dist/publicConfig.js.map +1 -0
  37. package/dist/sampleData.d.ts +22 -0
  38. package/dist/sampleData.d.ts.map +1 -0
  39. package/dist/sampleData.js +29 -0
  40. package/dist/sampleData.js.map +1 -0
  41. package/dist/values.d.ts +2 -0
  42. package/dist/values.d.ts.map +1 -1
  43. package/dist/values.js +10 -0
  44. package/dist/values.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/account.ts +56 -0
  47. package/src/company.ts +6 -0
  48. package/src/emailDomainLinker.ts +28 -0
  49. package/src/importExport.ts +48 -4
  50. package/src/index.ts +22 -0
  51. package/src/list.ts +73 -0
  52. package/src/listMember.ts +54 -0
  53. package/src/listMembership.ts +45 -0
  54. package/src/publicConfig.ts +31 -0
  55. package/src/sampleData.ts +52 -0
  56. package/src/values.ts +11 -0
@@ -13,10 +13,21 @@ import type { RecordTimestamps } from './wire.ts'
13
13
  * — a caller sends a `column_map` and never has to know how one was derived.
14
14
  */
15
15
 
16
- export const IMPORT_SOURCES = ['custom', 'hubspot', 'salesforce'] as const
16
+ export const IMPORT_SOURCES = ['custom', 'hubspot', 'salesforce', 'attio'] as const
17
17
  export const IMPORT_OBJECTS = ['companies', 'people', 'positions', 'deals'] as const
18
18
  export const IMPORT_CONFLICT_MODES = ['skip', 'update'] as const
19
19
 
20
+ /**
21
+ * What a People import does when a row names a company that is not in the
22
+ * workspace yet.
23
+ *
24
+ * `skip` imports the person and leaves the affiliation unlinked, reporting it as
25
+ * a row warning. `create` invents the company from the row's own domain and name
26
+ * so the position can be linked. Only the People import reads this; the other
27
+ * objects fail a missing company outright, per `import-export.md`.
28
+ */
29
+ export const ON_MISSING_COMPANY = ['skip', 'create'] as const
30
+
20
31
  /**
21
32
  * `pending → validating → ready | failed → committing → completed | failed`.
22
33
  *
@@ -45,6 +56,7 @@ export const IMPORT_ROW_ACTIONS = ['pending', 'create', 'update', 'skip', 'error
45
56
  export type ImportSource = (typeof IMPORT_SOURCES)[number]
46
57
  export type ImportObject = (typeof IMPORT_OBJECTS)[number]
47
58
  export type ImportConflictMode = (typeof IMPORT_CONFLICT_MODES)[number]
59
+ export type OnMissingCompany = (typeof ON_MISSING_COMPANY)[number]
48
60
  export type ImportJobStatus = (typeof IMPORT_JOB_STATUSES)[number]
49
61
  export type ImportRowAction = (typeof IMPORT_ROW_ACTIONS)[number]
50
62
 
@@ -82,6 +94,7 @@ export const SOURCE_LABELS: Readonly<Record<ImportSource, string>> = {
82
94
  custom: 'Custom CSV',
83
95
  hubspot: 'HubSpot CSV pack',
84
96
  salesforce: 'Salesforce CSV pack',
97
+ attio: 'Attio CSV pack',
85
98
  }
86
99
 
87
100
  export const CONFLICT_MODE_LABELS: Readonly<Record<ImportConflictMode, string>> = {
@@ -89,11 +102,25 @@ export const CONFLICT_MODE_LABELS: Readonly<Record<ImportConflictMode, string>>
89
102
  update: 'Update existing',
90
103
  }
91
104
 
105
+ export const ON_MISSING_COMPANY_LABELS: Readonly<Record<OnMissingCompany, string>> = {
106
+ skip: 'Skip and report',
107
+ create: 'Create the company',
108
+ }
109
+
92
110
  export interface CsvColumn {
93
111
  /** The Kelpie CSV header, and the key a `column_map` is keyed by. */
94
112
  readonly key: string
95
113
  readonly label: string
96
114
  readonly required: boolean
115
+ /**
116
+ * A column an import may map but an export never writes.
117
+ *
118
+ * The People affiliation columns are the only ones: a person can hold many
119
+ * positions, so a single company and title on the person row would be lossy on
120
+ * the way out. They map on the way in to drive a Position, and the export and
121
+ * template leave them out. See `headersFor`.
122
+ */
123
+ readonly importOnly?: boolean
97
124
  }
98
125
 
99
126
  /**
@@ -102,13 +129,15 @@ export interface CsvColumn {
102
129
  * Order is the header order of an export and of a template, so a file Kelpie
103
130
  * wrote maps onto itself by exact header match with no preset involved.
104
131
  *
105
- * Job title is absent from Person on purpose: it lives on Position, which is
106
- * what the contact title in a HubSpot or Salesforce export maps to.
132
+ * Job title is never stored on Person: it lives on Position. A People import may
133
+ * still carry `company_domain`, `company_name` and `title`, which drive a
134
+ * Position for the person rather than a field on them. Those three are
135
+ * `importOnly`, so an export and a template leave them out.
107
136
  */
108
137
  export const OBJECT_COLUMNS: Readonly<Record<ImportObject, readonly CsvColumn[]>> = {
109
138
  companies: [
110
139
  { key: 'name', label: 'Name', required: true },
111
- { key: 'domain', label: 'Domain', required: true },
140
+ { key: 'domain', label: 'Domain', required: false },
112
141
  { key: 'industry', label: 'Industry', required: false },
113
142
  { key: 'stage', label: 'Stage', required: false },
114
143
  { key: 'size_band', label: 'Size band', required: false },
@@ -131,6 +160,9 @@ export const OBJECT_COLUMNS: Readonly<Record<ImportObject, readonly CsvColumn[]>
131
160
  { key: 'summary', label: 'Summary', required: false },
132
161
  { key: 'tags', label: 'Tags', required: false },
133
162
  { key: 'phones', label: 'Phones', required: false },
163
+ { key: 'company_domain', label: 'Company domain', required: false, importOnly: true },
164
+ { key: 'company_name', label: 'Company name', required: false, importOnly: true },
165
+ { key: 'title', label: 'Title', required: false, importOnly: true },
134
166
  ],
135
167
  positions: [
136
168
  { key: 'person_email', label: 'Person email', required: true },
@@ -262,6 +294,8 @@ export interface ImportJob extends RecordTimestamps {
262
294
  readonly object: ImportObject
263
295
  readonly status: ImportJobStatus
264
296
  readonly conflictMode: ImportConflictMode
297
+ /** What a People import does with a row naming an absent company. `skip` for every other object. */
298
+ readonly onMissingCompany: OnMissingCompany
265
299
  readonly matchKey: string
266
300
  readonly columnMap: ImportColumnMap
267
301
  /** The headers as they appeared in the uploaded file, in file order. */
@@ -270,6 +304,12 @@ export interface ImportJob extends RecordTimestamps {
270
304
  readonly counts: ImportCounts
271
305
  /** At most `IMPORT_REPORTED_ERRORS`. `counts.error` is the true number of failing rows. */
272
306
  readonly errors: readonly ImportRowError[]
307
+ /**
308
+ * Non-fatal notes about rows that were imported. A People row whose company is
309
+ * absent under `on_missing_company: skip` lands here: the person imported, the
310
+ * position did not. At most `IMPORT_REPORTED_ERRORS`.
311
+ */
312
+ readonly warnings: readonly ImportRowError[]
273
313
  readonly preview: readonly ImportPreviewRow[]
274
314
  }
275
315
 
@@ -305,12 +345,14 @@ export const importJobSchema: z.ZodType<ImportJob, unknown> = z
305
345
  object: z.enum(IMPORT_OBJECTS),
306
346
  status: z.enum(IMPORT_JOB_STATUSES),
307
347
  conflict_mode: z.enum(IMPORT_CONFLICT_MODES),
348
+ on_missing_company: z.enum(ON_MISSING_COMPANY),
308
349
  match_key: z.string(),
309
350
  column_map: z.record(z.string(), z.string().nullable()),
310
351
  source_headers: z.array(z.string()),
311
352
  file_name: z.string().nullable(),
312
353
  counts: countsSchema,
313
354
  errors: z.array(rowErrorSchema),
355
+ warnings: z.array(rowErrorSchema),
314
356
  preview: z.array(previewRowSchema),
315
357
  ...recordTimestamps,
316
358
  })
@@ -321,12 +363,14 @@ export const importJobSchema: z.ZodType<ImportJob, unknown> = z
321
363
  object: wire.object,
322
364
  status: wire.status,
323
365
  conflictMode: wire.conflict_mode,
366
+ onMissingCompany: wire.on_missing_company,
324
367
  matchKey: wire.match_key,
325
368
  columnMap: wire.column_map,
326
369
  sourceHeaders: wire.source_headers,
327
370
  fileName: wire.file_name,
328
371
  counts: wire.counts,
329
372
  errors: wire.errors,
373
+ warnings: wire.warnings,
330
374
  preview: wire.preview,
331
375
  createdAt: wire.created_at,
332
376
  updatedAt: wire.updated_at,
package/src/index.ts CHANGED
@@ -46,6 +46,7 @@ export {
46
46
  PLAN_ITEM_STATUS_LABELS,
47
47
  PLAN_ITEM_STATUSES,
48
48
  PREFERRED_CHANNELS,
49
+ RECORD_TARGET_TYPE_LABELS,
49
50
  RECORD_TARGET_TYPES,
50
51
  RELATIONSHIP_LEVELS,
51
52
  ROLE_STATUS_LABELS,
@@ -149,6 +150,15 @@ export type {
149
150
  export { createNoteBody, noteBody, noteSchema } from './note.ts'
150
151
  export type { CreateNoteInput, Note, NoteInput } from './note.ts'
151
152
 
153
+ export { createListBody, listBody, listSchema } from './list.ts'
154
+ export type { CreateListInput, List, ListInput } from './list.ts'
155
+
156
+ export { addListMemberBody, listMemberSchema } from './listMember.ts'
157
+ export type { AddListMemberInput, ListMember } from './listMember.ts'
158
+
159
+ export { listMembershipSchema } from './listMembership.ts'
160
+ export type { ListMembership } from './listMembership.ts'
161
+
152
162
  export { createPlanItemBody, planItemBody, planItemSchema } from './planItem.ts'
153
163
  export type { CreatePlanItemInput, PlanItem, PlanItemInput } from './planItem.ts'
154
164
 
@@ -196,6 +206,8 @@ export {
196
206
  MAX_IMPORT_ROWS,
197
207
  OBJECT_COLUMNS,
198
208
  OBJECT_LABELS,
209
+ ON_MISSING_COMPANY,
210
+ ON_MISSING_COMPANY_LABELS,
199
211
  SOURCE_LABELS,
200
212
  SYNC_IMPORT_ROWS,
201
213
  defaultMatchKeyId,
@@ -217,6 +229,7 @@ export type {
217
229
  ImportRowError,
218
230
  ImportSource,
219
231
  MatchKeyOption,
232
+ OnMissingCompany,
220
233
  SettledRowAction,
221
234
  } from './importExport.ts'
222
235
 
@@ -288,6 +301,10 @@ export type { Member, UpdateMemberRoleInput } from './member.ts'
288
301
 
289
302
  export { moduleSettingSchema, updateModuleSettingBody } from './moduleSetting.ts'
290
303
  export type { ModuleSetting, UpdateModuleSettingInput } from './moduleSetting.ts'
304
+ export { sampleDataCountsSchema } from './sampleData.ts'
305
+ export type { SampleDataCounts } from './sampleData.ts'
306
+ export { relinkEmailDomainsCountsSchema } from './emailDomainLinker.ts'
307
+ export type { RelinkEmailDomainsCounts } from './emailDomainLinker.ts'
291
308
 
292
309
  export { mcpToolSchema } from './mcpTool.ts'
293
310
  export type { McpTool } from './mcpTool.ts'
@@ -326,6 +343,7 @@ export {
326
343
  accountSchema,
327
344
  accountSessionSchema,
328
345
  changePasswordBody,
346
+ listViewPreferenceSchema,
329
347
  updateAccountBody,
330
348
  updateAccountPreferencesBody,
331
349
  } from './account.ts'
@@ -334,6 +352,10 @@ export type {
334
352
  AccountPreferences,
335
353
  AccountSession,
336
354
  ChangePasswordInput,
355
+ ListViewPreference,
337
356
  UpdateAccountInput,
338
357
  UpdateAccountPreferencesInput,
339
358
  } from './account.ts'
359
+
360
+ export { publicConfigSchema } from './publicConfig.ts'
361
+ export type { PublicConfig, PublicRuntimeMode } from './publicConfig.ts'
package/src/list.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod'
2
+
3
+ import { RECORD_TARGET_TYPES } from './values.ts'
4
+ import type { RecordTargetType } from './values.ts'
5
+ import { definedFields, idSchema, recordTimestamps } from './wire.ts'
6
+ import type { RecordTimestamps } from './wire.ts'
7
+
8
+ /**
9
+ * Wire and write shapes for `/v1/lists`.
10
+ *
11
+ * A list holds records of one type. The type is chosen at creation and never
12
+ * changes; a member added to it must match, and the database rejects a mismatch
13
+ * with a composite foreign key rather than trusting the service to check.
14
+ *
15
+ * `memberCount` is a read-only rollup returned on list responses so an index
16
+ * page can render totals without a follow-up call per row.
17
+ */
18
+
19
+ export interface List extends RecordTimestamps {
20
+ readonly id: string
21
+ readonly name: string
22
+ readonly description: string | null
23
+ readonly targetType: RecordTargetType
24
+ readonly memberCount: number
25
+ }
26
+
27
+ export const listSchema: z.ZodType<List, unknown> = z
28
+ .object({
29
+ id: idSchema,
30
+ name: z.string(),
31
+ description: z.string().nullable(),
32
+ target_type: z.enum(RECORD_TARGET_TYPES),
33
+ member_count: z.number().int().nonnegative(),
34
+ ...recordTimestamps,
35
+ })
36
+ .transform(
37
+ (wire): List => ({
38
+ id: wire.id,
39
+ name: wire.name,
40
+ description: wire.description,
41
+ targetType: wire.target_type,
42
+ memberCount: wire.member_count,
43
+ createdAt: wire.created_at,
44
+ updatedAt: wire.updated_at,
45
+ }),
46
+ )
47
+
48
+ export interface CreateListInput {
49
+ readonly name: string
50
+ readonly targetType: RecordTargetType
51
+ readonly description?: string | null
52
+ }
53
+
54
+ export function createListBody(input: CreateListInput): Record<string, unknown> {
55
+ return definedFields({
56
+ name: input.name,
57
+ target_type: input.targetType,
58
+ description: input.description,
59
+ })
60
+ }
61
+
62
+ /** `targetType` is fixed for the life of a list, so it is not editable here. */
63
+ export interface ListInput {
64
+ readonly name?: string
65
+ readonly description?: string | null
66
+ }
67
+
68
+ export function listBody(input: ListInput): Record<string, unknown> {
69
+ return definedFields({
70
+ name: input.name,
71
+ description: input.description,
72
+ })
73
+ }
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod'
2
+
3
+ import { RECORD_TARGET_TYPES } from './values.ts'
4
+ import type { RecordTargetType } from './values.ts'
5
+ import { definedFields, idSchema, timestampSchema } from './wire.ts'
6
+
7
+ /**
8
+ * Wire and write shapes for `/v1/lists/:id/members`.
9
+ *
10
+ * A membership carries no state of its own: adding or removing a record is the
11
+ * only edit. `targetName` is resolved server-side via `resolveTargetNames` so a
12
+ * heterogeneous members table can render without an N+1.
13
+ */
14
+
15
+ export interface ListMember {
16
+ readonly id: string
17
+ readonly listId: string
18
+ readonly targetType: RecordTargetType
19
+ readonly targetId: string
20
+ readonly targetName: string | null
21
+ readonly addedAt: Date
22
+ }
23
+
24
+ export const listMemberSchema: z.ZodType<ListMember, unknown> = z
25
+ .object({
26
+ id: idSchema,
27
+ list_id: idSchema,
28
+ target_type: z.enum(RECORD_TARGET_TYPES),
29
+ target_id: idSchema,
30
+ target_name: z.string().nullable(),
31
+ added_at: timestampSchema,
32
+ })
33
+ .transform(
34
+ (wire): ListMember => ({
35
+ id: wire.id,
36
+ listId: wire.list_id,
37
+ targetType: wire.target_type,
38
+ targetId: wire.target_id,
39
+ targetName: wire.target_name,
40
+ addedAt: wire.added_at,
41
+ }),
42
+ )
43
+
44
+ export interface AddListMemberInput {
45
+ readonly targetType: RecordTargetType
46
+ readonly targetId: string
47
+ }
48
+
49
+ export function addListMemberBody(input: AddListMemberInput): Record<string, unknown> {
50
+ return definedFields({
51
+ target_type: input.targetType,
52
+ target_id: input.targetId,
53
+ })
54
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod'
2
+
3
+ import { RECORD_TARGET_TYPES } from './values.ts'
4
+ import type { RecordTargetType } from './values.ts'
5
+ import { idSchema, timestampSchema } from './wire.ts'
6
+
7
+ /**
8
+ * "Which lists is this record on?" — a membership joined with its list.
9
+ *
10
+ * Returned by `GET /v1/list-memberships?target_type=X&target_id=Y`. The joined
11
+ * list fields (`listName`, `listTargetType`) come along because a record's
12
+ * detail page wants to render its chips without a follow-up call per list.
13
+ */
14
+
15
+ export interface ListMembership {
16
+ readonly id: string
17
+ readonly listId: string
18
+ readonly listName: string
19
+ readonly listTargetType: RecordTargetType
20
+ readonly targetType: RecordTargetType
21
+ readonly targetId: string
22
+ readonly addedAt: Date
23
+ }
24
+
25
+ export const listMembershipSchema: z.ZodType<ListMembership, unknown> = z
26
+ .object({
27
+ id: idSchema,
28
+ list_id: idSchema,
29
+ list_name: z.string(),
30
+ list_target_type: z.enum(RECORD_TARGET_TYPES),
31
+ target_type: z.enum(RECORD_TARGET_TYPES),
32
+ target_id: idSchema,
33
+ added_at: timestampSchema,
34
+ })
35
+ .transform(
36
+ (wire): ListMembership => ({
37
+ id: wire.id,
38
+ listId: wire.list_id,
39
+ listName: wire.list_name,
40
+ listTargetType: wire.list_target_type,
41
+ targetType: wire.target_type,
42
+ targetId: wire.target_id,
43
+ addedAt: wire.added_at,
44
+ }),
45
+ )
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Wire shape for `GET /v1/public/config`.
5
+ *
6
+ * A public, credential-free endpoint the browser reads once at boot. It names
7
+ * which runtime the server is in, and the human-readable name of this
8
+ * deployment. The UI uses `runtimeMode` to gate a non-production banner and
9
+ * `siteName` to label it. Nothing here is sensitive: `runtimeMode` is
10
+ * observable from any error message, and `siteName` exists to be visible.
11
+ */
12
+
13
+ export type PublicRuntimeMode = 'development' | 'test' | 'production'
14
+
15
+ export interface PublicConfig {
16
+ readonly runtimeMode: PublicRuntimeMode
17
+ /** Undefined when the assembly did not set `KELPIE_SITE_NAME`. */
18
+ readonly siteName: string | null
19
+ }
20
+
21
+ export const publicConfigSchema = z
22
+ .strictObject({
23
+ runtime_mode: z.enum(['development', 'test', 'production']),
24
+ site_name: z.string().min(1).nullable(),
25
+ })
26
+ .transform(
27
+ (wire): PublicConfig => ({
28
+ runtimeMode: wire.runtime_mode,
29
+ siteName: wire.site_name,
30
+ }),
31
+ )
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Wire shape for `POST /v1/workspaces/:id/sample-data`.
5
+ *
6
+ * The endpoint takes no body. The response is a count per object type, so a
7
+ * caller can tell the reader what the button just did without a second query.
8
+ */
9
+
10
+ export interface SampleDataCounts {
11
+ readonly companies: number
12
+ readonly people: number
13
+ readonly positions: number
14
+ readonly deals: number
15
+ readonly planItems: number
16
+ readonly notes: number
17
+ readonly opportunities: number
18
+ readonly raises: number
19
+ readonly partnerships: number
20
+ readonly roles: number
21
+ readonly candidates: number
22
+ }
23
+
24
+ export const sampleDataCountsSchema: z.ZodType<SampleDataCounts, unknown> = z
25
+ .object({
26
+ companies: z.number().int().nonnegative(),
27
+ people: z.number().int().nonnegative(),
28
+ positions: z.number().int().nonnegative(),
29
+ deals: z.number().int().nonnegative(),
30
+ plan_items: z.number().int().nonnegative(),
31
+ notes: z.number().int().nonnegative(),
32
+ opportunities: z.number().int().nonnegative(),
33
+ raises: z.number().int().nonnegative(),
34
+ partnerships: z.number().int().nonnegative(),
35
+ roles: z.number().int().nonnegative(),
36
+ candidates: z.number().int().nonnegative(),
37
+ })
38
+ .transform(
39
+ (wire): SampleDataCounts => ({
40
+ companies: wire.companies,
41
+ people: wire.people,
42
+ positions: wire.positions,
43
+ deals: wire.deals,
44
+ planItems: wire.plan_items,
45
+ notes: wire.notes,
46
+ opportunities: wire.opportunities,
47
+ raises: wire.raises,
48
+ partnerships: wire.partnerships,
49
+ roles: wire.roles,
50
+ candidates: wire.candidates,
51
+ }),
52
+ )
package/src/values.ts CHANGED
@@ -115,6 +115,17 @@ export const RECORD_TARGET_TYPES = [
115
115
 
116
116
  export type RecordTargetType = (typeof RECORD_TARGET_TYPES)[number]
117
117
 
118
+ /** Human labels for `RECORD_TARGET_TYPES`. Used by any picker that names a type. */
119
+ export const RECORD_TARGET_TYPE_LABELS: Readonly<Record<RecordTargetType, string>> = {
120
+ person: 'Person',
121
+ company: 'Company',
122
+ deal: 'Deal',
123
+ opportunity: 'Opportunity',
124
+ partnership: 'Partnership',
125
+ raise: 'Raise',
126
+ candidate: 'Candidate',
127
+ }
128
+
118
129
  /**
119
130
  * What `GET /v1/search` looks through, and the order its groups come back in.
120
131
  *