@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.
- package/dist/account.d.ts +35 -0
- package/dist/account.d.ts.map +1 -1
- package/dist/account.js +18 -0
- package/dist/account.js.map +1 -1
- package/dist/company.d.ts +3 -0
- package/dist/company.d.ts.map +1 -1
- package/dist/company.js +3 -0
- package/dist/company.js.map +1 -1
- package/dist/emailDomainLinker.d.ts +16 -0
- package/dist/emailDomainLinker.d.ts.map +1 -0
- package/dist/emailDomainLinker.js +11 -0
- package/dist/emailDomainLinker.js.map +1 -0
- package/dist/importExport.d.ts +34 -3
- package/dist/importExport.d.ts.map +1 -1
- package/dist/importExport.js +28 -4
- package/dist/importExport.js.map +1 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +34 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +35 -0
- package/dist/list.js.map +1 -0
- package/dist/listMember.d.ts +24 -0
- package/dist/listMember.d.ts.map +1 -0
- package/dist/listMember.js +27 -0
- package/dist/listMember.js.map +1 -0
- package/dist/listMembership.d.ts +20 -0
- package/dist/listMembership.d.ts.map +1 -0
- package/dist/listMembership.js +23 -0
- package/dist/listMembership.js.map +1 -0
- package/dist/publicConfig.d.ts +28 -0
- package/dist/publicConfig.d.ts.map +1 -0
- package/dist/publicConfig.js +11 -0
- package/dist/publicConfig.js.map +1 -0
- package/dist/sampleData.d.ts +22 -0
- package/dist/sampleData.d.ts.map +1 -0
- package/dist/sampleData.js +29 -0
- package/dist/sampleData.js.map +1 -0
- package/dist/values.d.ts +2 -0
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +10 -0
- package/dist/values.js.map +1 -1
- package/package.json +1 -1
- package/src/account.ts +56 -0
- package/src/company.ts +6 -0
- package/src/emailDomainLinker.ts +28 -0
- package/src/importExport.ts +48 -4
- package/src/index.ts +22 -0
- package/src/list.ts +73 -0
- package/src/listMember.ts +54 -0
- package/src/listMembership.ts +45 -0
- package/src/publicConfig.ts +31 -0
- package/src/sampleData.ts +52 -0
- package/src/values.ts +11 -0
package/src/importExport.ts
CHANGED
|
@@ -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
|
|
106
|
-
*
|
|
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:
|
|
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
|
*
|