@kelpie/schemas 0.7.0 → 0.9.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 (111) 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 +11 -0
  6. package/dist/company.d.ts.map +1 -1
  7. package/dist/company.js +7 -0
  8. package/dist/company.js.map +1 -1
  9. package/dist/consentPurpose.d.ts +49 -0
  10. package/dist/consentPurpose.d.ts.map +1 -0
  11. package/dist/consentPurpose.js +40 -0
  12. package/dist/consentPurpose.js.map +1 -0
  13. package/dist/customField.d.ts +99 -0
  14. package/dist/customField.d.ts.map +1 -0
  15. package/dist/customField.js +133 -0
  16. package/dist/customField.js.map +1 -0
  17. package/dist/deal.d.ts +8 -0
  18. package/dist/deal.d.ts.map +1 -1
  19. package/dist/deal.js +4 -0
  20. package/dist/deal.js.map +1 -1
  21. package/dist/emailDomainLinker.d.ts +16 -0
  22. package/dist/emailDomainLinker.d.ts.map +1 -0
  23. package/dist/emailDomainLinker.js +11 -0
  24. package/dist/emailDomainLinker.js.map +1 -0
  25. package/dist/enquiry.d.ts +43 -0
  26. package/dist/enquiry.d.ts.map +1 -0
  27. package/dist/enquiry.js +47 -0
  28. package/dist/enquiry.js.map +1 -0
  29. package/dist/form.d.ts +117 -2
  30. package/dist/form.d.ts.map +1 -1
  31. package/dist/form.js +102 -1
  32. package/dist/form.js.map +1 -1
  33. package/dist/formSubmission.d.ts +29 -0
  34. package/dist/formSubmission.d.ts.map +1 -1
  35. package/dist/formSubmission.js +34 -0
  36. package/dist/formSubmission.js.map +1 -1
  37. package/dist/importExport.d.ts +28 -4
  38. package/dist/importExport.d.ts.map +1 -1
  39. package/dist/importExport.js +25 -2
  40. package/dist/importExport.js.map +1 -1
  41. package/dist/index.d.ts +27 -7
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +13 -3
  44. package/dist/index.js.map +1 -1
  45. package/dist/list.d.ts +44 -0
  46. package/dist/list.d.ts.map +1 -0
  47. package/dist/list.js +41 -0
  48. package/dist/list.js.map +1 -0
  49. package/dist/listMember.d.ts +24 -0
  50. package/dist/listMember.d.ts.map +1 -0
  51. package/dist/listMember.js +27 -0
  52. package/dist/listMember.js.map +1 -0
  53. package/dist/listMembership.d.ts +20 -0
  54. package/dist/listMembership.d.ts.map +1 -0
  55. package/dist/listMembership.js +23 -0
  56. package/dist/listMembership.js.map +1 -0
  57. package/dist/names.d.ts +43 -0
  58. package/dist/names.d.ts.map +1 -0
  59. package/dist/names.js +35 -0
  60. package/dist/names.js.map +1 -0
  61. package/dist/opportunity.d.ts +10 -0
  62. package/dist/opportunity.d.ts.map +1 -1
  63. package/dist/opportunity.js +7 -0
  64. package/dist/opportunity.js.map +1 -1
  65. package/dist/partnership.d.ts +8 -0
  66. package/dist/partnership.d.ts.map +1 -1
  67. package/dist/partnership.js +4 -0
  68. package/dist/partnership.js.map +1 -1
  69. package/dist/person.d.ts +42 -1
  70. package/dist/person.d.ts.map +1 -1
  71. package/dist/person.js +23 -0
  72. package/dist/person.js.map +1 -1
  73. package/dist/personConsent.d.ts +31 -0
  74. package/dist/personConsent.d.ts.map +1 -0
  75. package/dist/personConsent.js +27 -0
  76. package/dist/personConsent.js.map +1 -0
  77. package/dist/raise.d.ts +8 -0
  78. package/dist/raise.d.ts.map +1 -1
  79. package/dist/raise.js +4 -0
  80. package/dist/raise.js.map +1 -1
  81. package/dist/sampleData.d.ts +23 -0
  82. package/dist/sampleData.d.ts.map +1 -0
  83. package/dist/sampleData.js +31 -0
  84. package/dist/sampleData.js.map +1 -0
  85. package/dist/values.d.ts +88 -8
  86. package/dist/values.d.ts.map +1 -1
  87. package/dist/values.js +150 -3
  88. package/dist/values.js.map +1 -1
  89. package/package.json +1 -1
  90. package/src/account.ts +56 -0
  91. package/src/company.ts +19 -0
  92. package/src/consentPurpose.ts +95 -0
  93. package/src/customField.ts +211 -0
  94. package/src/deal.ts +13 -0
  95. package/src/emailDomainLinker.ts +28 -0
  96. package/src/enquiry.ts +94 -0
  97. package/src/form.ts +230 -2
  98. package/src/formSubmission.ts +63 -0
  99. package/src/importExport.ts +45 -6
  100. package/src/index.ts +86 -2
  101. package/src/list.ts +89 -0
  102. package/src/listMember.ts +54 -0
  103. package/src/listMembership.ts +45 -0
  104. package/src/names.ts +49 -0
  105. package/src/opportunity.ts +18 -0
  106. package/src/partnership.ts +13 -0
  107. package/src/person.ts +69 -1
  108. package/src/personConsent.ts +60 -0
  109. package/src/raise.ts +13 -0
  110. package/src/sampleData.ts +55 -0
  111. package/src/values.ts +180 -3
package/src/index.ts CHANGED
@@ -24,8 +24,20 @@ export {
24
24
  CANDIDATE_STATUS_LABELS,
25
25
  CANDIDATE_STATUSES,
26
26
  COMPANY_STAGES,
27
+ CONSENT_PURPOSE_STATUS_LABELS,
28
+ CONSENT_PURPOSE_STATUSES,
29
+ CONSENT_SOURCE_KINDS,
30
+ CONSENT_STATUS_LABELS,
31
+ CONSENT_STATUSES,
32
+ CUSTOM_FIELD_OBJECT_TYPE_LABELS,
33
+ CUSTOM_FIELD_OBJECT_TYPES,
34
+ CUSTOM_FIELD_TYPE_LABELS,
35
+ CUSTOM_FIELD_TYPES,
36
+ CUSTOM_FIELD_TYPES_WITH_OPTIONS,
27
37
  EXTENSIBLE_RECORD_TYPES,
28
38
  FIRST_INTERVIEW_STAGE,
39
+ FORM_ACTION_STATUSES,
40
+ FORM_BOOLEAN_FIELD_TYPES,
29
41
  FORM_FIELD_MAP_TARGET_LABELS,
30
42
  FORM_FIELD_MAP_TARGETS,
31
43
  FORM_FIELD_TYPES,
@@ -40,12 +52,14 @@ export {
40
52
  INVITE_STATUSES,
41
53
  MEMBER_ROLES,
42
54
  OPEN_PLAN_ITEM_STATUSES,
55
+ PERSON_CONSENT_TARGET,
43
56
  PERSON_EMAIL_TARGET,
44
57
  PIPELINE_KIND_LABELS,
45
58
  PIPELINE_KINDS,
46
59
  PLAN_ITEM_STATUS_LABELS,
47
60
  PLAN_ITEM_STATUSES,
48
61
  PREFERRED_CHANNELS,
62
+ RECORD_TARGET_TYPE_LABELS,
49
63
  RECORD_TARGET_TYPES,
50
64
  RELATIONSHIP_LEVELS,
51
65
  ROLE_STATUS_LABELS,
@@ -70,7 +84,15 @@ export type {
70
84
  ApiKeyKind,
71
85
  CandidateStatus,
72
86
  CompanyStage,
87
+ ConsentPurposeStatus,
88
+ ConsentSourceKind,
89
+ ConsentStatus,
90
+ CustomFieldObjectType,
91
+ CustomFieldType,
92
+ CustomFieldTypeWithOptions,
73
93
  ExtensibleRecordType,
94
+ FormActionStatus,
95
+ FormBooleanFieldType,
74
96
  FormFieldMapTarget,
75
97
  FormFieldType,
76
98
  FormOptionValueType,
@@ -100,6 +122,38 @@ export type {
100
122
  export { definedFields, idSchema, nullableTimestampSchema, timestampSchema } from './wire.ts'
101
123
  export type { RecordTimestamps } from './wire.ts'
102
124
 
125
+ export {
126
+ consentPurposeBody,
127
+ consentPurposeSchema,
128
+ createConsentPurposeBody,
129
+ } from './consentPurpose.ts'
130
+ export type {
131
+ ConsentPurpose,
132
+ ConsentPurposeInput,
133
+ CreateConsentPurposeInput,
134
+ } from './consentPurpose.ts'
135
+
136
+ export { personConsentSchema, personConsentWriteBody } from './personConsent.ts'
137
+ export type { PersonConsent, PersonConsentInput } from './personConsent.ts'
138
+
139
+ export {
140
+ createCustomFieldDefinitionBody,
141
+ customFieldDefinitionBody,
142
+ customFieldDefinitionSchema,
143
+ customFieldValuesBody,
144
+ customFieldValuesSchema,
145
+ customFieldWireValue,
146
+ customFieldsPatchShape,
147
+ } from './customField.ts'
148
+ export type {
149
+ CreateCustomFieldDefinitionInput,
150
+ CustomFieldDefinition,
151
+ CustomFieldDefinitionInput,
152
+ CustomFieldValue,
153
+ CustomFieldValues,
154
+ CustomFieldWireValue,
155
+ } from './customField.ts'
156
+
103
157
  export {
104
158
  eventActorSchema,
105
159
  eventEnvelopeSchema,
@@ -108,6 +162,8 @@ export {
108
162
  } from './events.ts'
109
163
  export type { EventActor, EventTarget, KelpieEvent, UpdateEventData } from './events.ts'
110
164
 
165
+ export { composeName } from './names.ts'
166
+ export type { NameParts } from './names.ts'
111
167
  export { personBody, personSchema } from './person.ts'
112
168
  export type { Person, PersonInput, SocialProfile } from './person.ts'
113
169
 
@@ -123,6 +179,9 @@ export type { Deal, DealInput } from './deal.ts'
123
179
  export { opportunityBody, opportunitySchema } from './opportunity.ts'
124
180
  export type { Opportunity, OpportunityInput } from './opportunity.ts'
125
181
 
182
+ export { enquiryBody, enquirySchema } from './enquiry.ts'
183
+ export type { Enquiry, EnquiryInput } from './enquiry.ts'
184
+
126
185
  export { partnershipBody, partnershipSchema } from './partnership.ts'
127
186
  export type { Partnership, PartnershipInput } from './partnership.ts'
128
187
 
@@ -149,6 +208,15 @@ export type {
149
208
  export { createNoteBody, noteBody, noteSchema } from './note.ts'
150
209
  export type { CreateNoteInput, Note, NoteInput } from './note.ts'
151
210
 
211
+ export { createListBody, listBody, listSchema } from './list.ts'
212
+ export type { CreateListInput, List, ListInput } from './list.ts'
213
+
214
+ export { addListMemberBody, listMemberSchema } from './listMember.ts'
215
+ export type { AddListMemberInput, ListMember } from './listMember.ts'
216
+
217
+ export { listMembershipSchema } from './listMembership.ts'
218
+ export type { ListMembership } from './listMembership.ts'
219
+
152
220
  export { createPlanItemBody, planItemBody, planItemSchema } from './planItem.ts'
153
221
  export type { CreatePlanItemInput, PlanItem, PlanItemInput } from './planItem.ts'
154
222
 
@@ -171,6 +239,7 @@ export { createFormBody, formBody, formSchema } from './form.ts'
171
239
  export type {
172
240
  CreateFormInput,
173
241
  Form,
242
+ FormAttachTarget,
174
243
  FormField,
175
244
  FormFieldInput,
176
245
  FormFieldOption,
@@ -178,8 +247,17 @@ export type {
178
247
  FormInput,
179
248
  } from './form.ts'
180
249
 
181
- export { formSubmissionSchema, formSubmitResultSchema } from './formSubmission.ts'
182
- export type { FormSubmission, FormSubmitResult } from './formSubmission.ts'
250
+ export {
251
+ FORM_SUBMISSION_LINK_TARGETS,
252
+ formSubmissionSchema,
253
+ formSubmitResultSchema,
254
+ } from './formSubmission.ts'
255
+ export type {
256
+ FormSubmission,
257
+ FormSubmissionActionEntry,
258
+ FormSubmissionLinkTarget,
259
+ FormSubmitResult,
260
+ } from './formSubmission.ts'
183
261
 
184
262
  export {
185
263
  CONFLICT_MODE_LABELS,
@@ -291,6 +369,10 @@ export type { Member, UpdateMemberRoleInput } from './member.ts'
291
369
 
292
370
  export { moduleSettingSchema, updateModuleSettingBody } from './moduleSetting.ts'
293
371
  export type { ModuleSetting, UpdateModuleSettingInput } from './moduleSetting.ts'
372
+ export { sampleDataCountsSchema } from './sampleData.ts'
373
+ export type { SampleDataCounts } from './sampleData.ts'
374
+ export { relinkEmailDomainsCountsSchema } from './emailDomainLinker.ts'
375
+ export type { RelinkEmailDomainsCounts } from './emailDomainLinker.ts'
294
376
 
295
377
  export { mcpToolSchema } from './mcpTool.ts'
296
378
  export type { McpTool } from './mcpTool.ts'
@@ -329,6 +411,7 @@ export {
329
411
  accountSchema,
330
412
  accountSessionSchema,
331
413
  changePasswordBody,
414
+ listViewPreferenceSchema,
332
415
  updateAccountBody,
333
416
  updateAccountPreferencesBody,
334
417
  } from './account.ts'
@@ -337,6 +420,7 @@ export type {
337
420
  AccountPreferences,
338
421
  AccountSession,
339
422
  ChangePasswordInput,
423
+ ListViewPreference,
340
424
  UpdateAccountInput,
341
425
  UpdateAccountPreferencesInput,
342
426
  } from './account.ts'
package/src/list.ts ADDED
@@ -0,0 +1,89 @@
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
+ * The consent purpose captured when a form submit adds a person to this
27
+ * list. Person lists only. Manual and API list adds capture nothing.
28
+ */
29
+ readonly consentPurposeId: string | null
30
+ /** The named purpose's slug, denormalised so a reader needs no second call. */
31
+ readonly consentPurposeSlug: string | null
32
+ }
33
+
34
+ export const listSchema: z.ZodType<List, unknown> = z
35
+ .object({
36
+ id: idSchema,
37
+ name: z.string(),
38
+ description: z.string().nullable(),
39
+ target_type: z.enum(RECORD_TARGET_TYPES),
40
+ member_count: z.number().int().nonnegative(),
41
+ consent_purpose_id: idSchema.nullable(),
42
+ consent_purpose_slug: z.string().nullable(),
43
+ ...recordTimestamps,
44
+ })
45
+ .transform(
46
+ (wire): List => ({
47
+ id: wire.id,
48
+ name: wire.name,
49
+ description: wire.description,
50
+ targetType: wire.target_type,
51
+ memberCount: wire.member_count,
52
+ consentPurposeId: wire.consent_purpose_id,
53
+ consentPurposeSlug: wire.consent_purpose_slug,
54
+ createdAt: wire.created_at,
55
+ updatedAt: wire.updated_at,
56
+ }),
57
+ )
58
+
59
+ export interface CreateListInput {
60
+ readonly name: string
61
+ readonly targetType: RecordTargetType
62
+ readonly description?: string | null
63
+ /** Person lists only. `null` on a company list is fine; a non-null value is `422`. */
64
+ readonly consentPurposeId?: string | null
65
+ }
66
+
67
+ export function createListBody(input: CreateListInput): Record<string, unknown> {
68
+ return definedFields({
69
+ name: input.name,
70
+ target_type: input.targetType,
71
+ description: input.description,
72
+ consent_purpose_id: input.consentPurposeId,
73
+ })
74
+ }
75
+
76
+ /** `targetType` is fixed for the life of a list, so it is not editable here. */
77
+ export interface ListInput {
78
+ readonly name?: string
79
+ readonly description?: string | null
80
+ readonly consentPurposeId?: string | null
81
+ }
82
+
83
+ export function listBody(input: ListInput): Record<string, unknown> {
84
+ return definedFields({
85
+ name: input.name,
86
+ description: input.description,
87
+ consent_purpose_id: input.consentPurposeId,
88
+ })
89
+ }
@@ -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
+ )
package/src/names.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Building a person's display name out of its parts.
3
+ *
4
+ * One direction only. A name is composed when a caller supplied parts and no
5
+ * name; parts are never derived by splitting a name. Splitting is a guess, and a
6
+ * stored guess is indistinguishable from something a person typed: "van der
7
+ * Berg" is one surname written as three words, "Ursula K. Le Guin" is four
8
+ * tokens over two names, a mononym is neither a first nor a last name, and half
9
+ * the world writes the family name first. `name` stays the record's canonical
10
+ * display string, which is why every consumer that renders a person keeps
11
+ * reading it and nothing else.
12
+ */
13
+
14
+ /**
15
+ * The parts a display name is built from, in the order they are written.
16
+ *
17
+ * Each accepts `undefined` as well as `null` so that a caller holding either
18
+ * shape — a nullable column, an absent CSV cell, an omitted request field — can
19
+ * pass what it has without first flattening the two. Both mean the same thing
20
+ * here: nothing to contribute.
21
+ */
22
+ export interface NameParts {
23
+ readonly firstName?: string | null | undefined
24
+ readonly lastName?: string | null | undefined
25
+ readonly suffix?: string | null | undefined
26
+ }
27
+
28
+ /**
29
+ * The parts joined, single-spaced.
30
+ *
31
+ * `salutation` is deliberately not a parameter. "Mr" is a form of address rather
32
+ * than part of the name, and a list of people reading "Mr John Smith" is a list
33
+ * nobody asked for. vCard draws the same line: honorific prefixes sit in `N`,
34
+ * away from the `FN` a client displays.
35
+ *
36
+ * The order is fixed rather than per-locale, because this composes a *fallback*
37
+ * for a record that arrived without a display name. A workspace that writes the
38
+ * family name first sets `name` itself, which is the whole reason that field is
39
+ * the canonical one.
40
+ *
41
+ * @returns The composed name, or `''` when no part carried anything. An empty
42
+ * answer is the caller's signal that there is nothing here to fall back to.
43
+ */
44
+ export function composeName(parts: NameParts): string {
45
+ return [parts.firstName, parts.lastName, parts.suffix]
46
+ .map((part) => (part ?? '').trim())
47
+ .filter((part) => part.length > 0)
48
+ .join(' ')
49
+ }
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod'
2
2
 
3
+ import { customFieldValuesBody, customFieldValuesSchema } from './customField.ts'
4
+ import type { CustomFieldValue, CustomFieldValues } from './customField.ts'
3
5
  import { definedFields, idSchema, recordTimestamps } from './wire.ts'
4
6
  import type { RecordTimestamps } from './wire.ts'
5
7
 
@@ -16,8 +18,11 @@ export interface Opportunity extends RecordTimestamps {
16
18
  readonly ownerId: string | null
17
19
  /** `YYYY-MM-DD`, per `api.md` date-only fields. */
18
20
  readonly expectedClose: string | null
21
+ readonly personIds: readonly string[]
19
22
  readonly summary: string
20
23
  readonly tags: readonly string[]
24
+ /** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
25
+ readonly customFields: CustomFieldValues
21
26
  }
22
27
 
23
28
  export const opportunitySchema: z.ZodType<Opportunity, unknown> = z
@@ -29,8 +34,10 @@ export const opportunitySchema: z.ZodType<Opportunity, unknown> = z
29
34
  company_id: idSchema.nullable(),
30
35
  owner_id: idSchema.nullable(),
31
36
  expected_close: z.string().nullable(),
37
+ person_ids: z.array(idSchema),
32
38
  summary: z.string(),
33
39
  tags: z.array(z.string()),
40
+ custom_fields: customFieldValuesSchema,
34
41
  ...recordTimestamps,
35
42
  })
36
43
  .transform(
@@ -42,8 +49,10 @@ export const opportunitySchema: z.ZodType<Opportunity, unknown> = z
42
49
  companyId: wire.company_id,
43
50
  ownerId: wire.owner_id,
44
51
  expectedClose: wire.expected_close,
52
+ personIds: wire.person_ids,
45
53
  summary: wire.summary,
46
54
  tags: wire.tags,
55
+ customFields: wire.custom_fields,
47
56
  createdAt: wire.created_at,
48
57
  updatedAt: wire.updated_at,
49
58
  }),
@@ -56,8 +65,14 @@ export interface OpportunityInput {
56
65
  readonly companyId?: string | null
57
66
  readonly ownerId?: string | null
58
67
  readonly expectedClose?: string | null
68
+ readonly personIds?: readonly string[]
59
69
  readonly summary?: string
60
70
  readonly tags?: readonly string[]
71
+ /**
72
+ * Partial merge patch: sent keys change, `null` clears a key, absent keys are
73
+ * left alone. Unknown keys are rejected at `422`.
74
+ */
75
+ readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
61
76
  }
62
77
 
63
78
  export function opportunityBody(input: OpportunityInput): Record<string, unknown> {
@@ -68,7 +83,10 @@ export function opportunityBody(input: OpportunityInput): Record<string, unknown
68
83
  company_id: input.companyId,
69
84
  owner_id: input.ownerId,
70
85
  expected_close: input.expectedClose,
86
+ person_ids: input.personIds,
71
87
  summary: input.summary,
72
88
  tags: input.tags,
89
+ custom_fields:
90
+ input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
73
91
  })
74
92
  }
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod'
2
2
 
3
+ import { customFieldValuesBody, customFieldValuesSchema } from './customField.ts'
4
+ import type { CustomFieldValue, CustomFieldValues } from './customField.ts'
3
5
  import { definedFields, idSchema, recordTimestamps } from './wire.ts'
4
6
  import type { RecordTimestamps } from './wire.ts'
5
7
 
@@ -21,6 +23,8 @@ export interface Partnership extends RecordTimestamps {
21
23
  readonly personIds: readonly string[]
22
24
  readonly summary: string
23
25
  readonly tags: readonly string[]
26
+ /** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
27
+ readonly customFields: CustomFieldValues
24
28
  }
25
29
 
26
30
  export const partnershipSchema: z.ZodType<Partnership, unknown> = z
@@ -37,6 +41,7 @@ export const partnershipSchema: z.ZodType<Partnership, unknown> = z
37
41
  person_ids: z.array(idSchema),
38
42
  summary: z.string(),
39
43
  tags: z.array(z.string()),
44
+ custom_fields: customFieldValuesSchema,
40
45
  ...recordTimestamps,
41
46
  })
42
47
  .transform(
@@ -53,6 +58,7 @@ export const partnershipSchema: z.ZodType<Partnership, unknown> = z
53
58
  personIds: wire.person_ids,
54
59
  summary: wire.summary,
55
60
  tags: wire.tags,
61
+ customFields: wire.custom_fields,
56
62
  createdAt: wire.created_at,
57
63
  updatedAt: wire.updated_at,
58
64
  }),
@@ -70,6 +76,11 @@ export interface PartnershipInput {
70
76
  readonly personIds?: readonly string[]
71
77
  readonly summary?: string
72
78
  readonly tags?: readonly string[]
79
+ /**
80
+ * Partial merge patch: sent keys change, `null` clears a key, absent keys are
81
+ * left alone. Unknown keys are rejected at `422`.
82
+ */
83
+ readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
73
84
  }
74
85
 
75
86
  export function partnershipBody(input: PartnershipInput): Record<string, unknown> {
@@ -85,5 +96,7 @@ export function partnershipBody(input: PartnershipInput): Record<string, unknown
85
96
  person_ids: input.personIds,
86
97
  summary: input.summary,
87
98
  tags: input.tags,
99
+ custom_fields:
100
+ input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
88
101
  })
89
102
  }
package/src/person.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { z } from 'zod'
2
2
 
3
+ import {
4
+ customFieldValuesBody,
5
+ customFieldValuesSchema,
6
+ } from './customField.ts'
7
+ import type { CustomFieldValue, CustomFieldValues } from './customField.ts'
8
+ import { personConsentSchema, personConsentWriteBody } from './personConsent.ts'
9
+ import type { PersonConsent, PersonConsentInput } from './personConsent.ts'
3
10
  import {
4
11
  INFLUENCE_LEVELS,
5
12
  PREFERRED_CHANNELS,
@@ -19,7 +26,19 @@ export interface SocialProfile {
19
26
 
20
27
  export interface Person extends RecordTimestamps {
21
28
  readonly id: string
29
+ /**
30
+ * What to call this person: the canonical display string, and the only name
31
+ * field every consumer reads. The parts below are optional detail beside it,
32
+ * never a replacement for it, so a mononym and a four-part name are both just
33
+ * a `name`.
34
+ */
22
35
  readonly name: string
36
+ /** A form of address — "Dr", "Ms". Not a job title: that is on Position. */
37
+ readonly salutation: string | null
38
+ readonly firstName: string | null
39
+ readonly lastName: string | null
40
+ /** A generational or post-nominal suffix — "Jr", "III", "PhD". */
41
+ readonly suffix: string | null
23
42
  readonly email: string | null
24
43
  readonly phones: readonly string[]
25
44
  readonly socialProfiles: readonly SocialProfile[]
@@ -31,6 +50,16 @@ export interface Person extends RecordTimestamps {
31
50
  readonly summary: string
32
51
  readonly tags: readonly string[]
33
52
  readonly lastContactedAt: Date | null
53
+ /** The Article 21 objection. Global, independent of consent purposes. */
54
+ readonly doNotContact: boolean
55
+ /**
56
+ * One entry per workspace consent purpose, with the effective status
57
+ * (an explicit `person_consents` row if one exists, else the purpose's
58
+ * `default_status`). Ordered by the purpose's `sort_order`.
59
+ */
60
+ readonly consents: readonly PersonConsent[]
61
+ /** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
62
+ readonly customFields: CustomFieldValues
34
63
  }
35
64
 
36
65
  const socialProfileSchema = z.object({
@@ -42,6 +71,10 @@ export const personSchema: z.ZodType<Person, unknown> = z
42
71
  .object({
43
72
  id: idSchema,
44
73
  name: z.string(),
74
+ salutation: z.string().nullable(),
75
+ first_name: z.string().nullable(),
76
+ last_name: z.string().nullable(),
77
+ suffix: z.string().nullable(),
45
78
  email: z.string().nullable(),
46
79
  phones: z.array(z.string()),
47
80
  social_profiles: z.array(socialProfileSchema),
@@ -53,12 +86,19 @@ export const personSchema: z.ZodType<Person, unknown> = z
53
86
  summary: z.string(),
54
87
  tags: z.array(z.string()),
55
88
  last_contacted_at: nullableTimestampSchema,
89
+ do_not_contact: z.boolean(),
90
+ consents: z.array(personConsentSchema),
91
+ custom_fields: customFieldValuesSchema,
56
92
  ...recordTimestamps,
57
93
  })
58
94
  .transform(
59
95
  (wire): Person => ({
60
96
  id: wire.id,
61
97
  name: wire.name,
98
+ salutation: wire.salutation,
99
+ firstName: wire.first_name,
100
+ lastName: wire.last_name,
101
+ suffix: wire.suffix,
62
102
  email: wire.email,
63
103
  phones: wire.phones,
64
104
  socialProfiles: wire.social_profiles,
@@ -70,6 +110,9 @@ export const personSchema: z.ZodType<Person, unknown> = z
70
110
  summary: wire.summary,
71
111
  tags: wire.tags,
72
112
  lastContactedAt: wire.last_contacted_at,
113
+ doNotContact: wire.do_not_contact,
114
+ consents: wire.consents,
115
+ customFields: wire.custom_fields,
73
116
  createdAt: wire.created_at,
74
117
  updatedAt: wire.updated_at,
75
118
  }),
@@ -78,10 +121,15 @@ export const personSchema: z.ZodType<Person, unknown> = z
78
121
  /**
79
122
  * A create or update body. Every field is optional because `POST` defaults all
80
123
  * but `name` server-side and `PATCH` sends only what changed; `name` is required
81
- * on create by the route, not by this type.
124
+ * on create by the route, not by this type — and a create may send `first_name`
125
+ * and `last_name` instead, which the route composes a `name` from.
82
126
  */
83
127
  export interface PersonInput {
84
128
  readonly name?: string
129
+ readonly salutation?: string | null
130
+ readonly firstName?: string | null
131
+ readonly lastName?: string | null
132
+ readonly suffix?: string | null
85
133
  readonly email?: string | null
86
134
  readonly phones?: readonly string[]
87
135
  readonly socialProfiles?: readonly SocialProfile[]
@@ -93,11 +141,27 @@ export interface PersonInput {
93
141
  readonly summary?: string
94
142
  readonly tags?: readonly string[]
95
143
  readonly lastContactedAt?: Date | null
144
+ readonly doNotContact?: boolean
145
+ /**
146
+ * Manual override of one or more consent purposes. Each entry upserts a
147
+ * `person_consents` row (`source: manual`); `status: null` clears the row
148
+ * and inherits the purpose's default. Absent purposes are left alone.
149
+ */
150
+ readonly consents?: readonly PersonConsentInput[]
151
+ /**
152
+ * Partial merge patch: sent keys change, `null` clears a key, absent keys are
153
+ * left alone. Unknown keys are rejected at `422`.
154
+ */
155
+ readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
96
156
  }
97
157
 
98
158
  export function personBody(input: PersonInput): Record<string, unknown> {
99
159
  return definedFields({
100
160
  name: input.name,
161
+ salutation: input.salutation,
162
+ first_name: input.firstName,
163
+ last_name: input.lastName,
164
+ suffix: input.suffix,
101
165
  email: input.email,
102
166
  phones: input.phones,
103
167
  social_profiles: input.socialProfiles,
@@ -112,5 +176,9 @@ export function personBody(input: PersonInput): Record<string, unknown> {
112
176
  input.lastContactedAt === undefined || input.lastContactedAt === null
113
177
  ? input.lastContactedAt
114
178
  : input.lastContactedAt.toISOString(),
179
+ do_not_contact: input.doNotContact,
180
+ consents: input.consents?.map(personConsentWriteBody),
181
+ custom_fields:
182
+ input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
115
183
  })
116
184
  }