@kelpie/schemas 0.8.0 → 0.10.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/company.d.ts +8 -0
- package/dist/company.d.ts.map +1 -1
- package/dist/company.js +4 -0
- package/dist/company.js.map +1 -1
- package/dist/consentPurpose.d.ts +49 -0
- package/dist/consentPurpose.d.ts.map +1 -0
- package/dist/consentPurpose.js +40 -0
- package/dist/consentPurpose.js.map +1 -0
- package/dist/customField.d.ts +99 -0
- package/dist/customField.d.ts.map +1 -0
- package/dist/customField.js +133 -0
- package/dist/customField.js.map +1 -0
- package/dist/deal.d.ts +8 -0
- package/dist/deal.d.ts.map +1 -1
- package/dist/deal.js +4 -0
- package/dist/deal.js.map +1 -1
- package/dist/enquiry.d.ts +43 -0
- package/dist/enquiry.d.ts.map +1 -0
- package/dist/enquiry.js +47 -0
- package/dist/enquiry.js.map +1 -0
- package/dist/form.d.ts +117 -2
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +102 -1
- package/dist/form.js.map +1 -1
- package/dist/formSubmission.d.ts +29 -0
- package/dist/formSubmission.d.ts.map +1 -1
- package/dist/formSubmission.js +34 -0
- package/dist/formSubmission.js.map +1 -1
- package/dist/importExport.d.ts +28 -4
- package/dist/importExport.d.ts.map +1 -1
- package/dist/importExport.js +24 -1
- package/dist/importExport.js.map +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +10 -0
- package/dist/list.d.ts.map +1 -1
- package/dist/list.js +6 -0
- package/dist/list.js.map +1 -1
- package/dist/names.d.ts +43 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +35 -0
- package/dist/names.js.map +1 -0
- package/dist/opportunity.d.ts +10 -0
- package/dist/opportunity.d.ts.map +1 -1
- package/dist/opportunity.js +7 -0
- package/dist/opportunity.js.map +1 -1
- package/dist/partnership.d.ts +8 -0
- package/dist/partnership.d.ts.map +1 -1
- package/dist/partnership.js +4 -0
- package/dist/partnership.js.map +1 -1
- package/dist/person.d.ts +42 -1
- package/dist/person.d.ts.map +1 -1
- package/dist/person.js +23 -0
- package/dist/person.js.map +1 -1
- package/dist/personConsent.d.ts +31 -0
- package/dist/personConsent.d.ts.map +1 -0
- package/dist/personConsent.js +27 -0
- package/dist/personConsent.js.map +1 -0
- package/dist/raise.d.ts +8 -0
- package/dist/raise.d.ts.map +1 -1
- package/dist/raise.js +4 -0
- package/dist/raise.js.map +1 -1
- package/dist/sampleData.d.ts +1 -0
- package/dist/sampleData.d.ts.map +1 -1
- package/dist/sampleData.js +2 -0
- package/dist/sampleData.js.map +1 -1
- package/dist/values.d.ts +86 -8
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +140 -3
- package/dist/values.js.map +1 -1
- package/package.json +1 -1
- package/src/company.ts +13 -0
- package/src/consentPurpose.ts +95 -0
- package/src/customField.ts +211 -0
- package/src/deal.ts +13 -0
- package/src/enquiry.ts +94 -0
- package/src/form.ts +230 -2
- package/src/formSubmission.ts +63 -0
- package/src/importExport.ts +44 -5
- package/src/index.ts +70 -2
- package/src/list.ts +16 -0
- package/src/names.ts +49 -0
- package/src/opportunity.ts +18 -0
- package/src/partnership.ts +13 -0
- package/src/person.ts +69 -1
- package/src/personConsent.ts +60 -0
- package/src/raise.ts +13 -0
- package/src/sampleData.ts +3 -0
- package/src/values.ts +169 -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,6 +52,7 @@ 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,
|
|
@@ -71,7 +84,15 @@ export type {
|
|
|
71
84
|
ApiKeyKind,
|
|
72
85
|
CandidateStatus,
|
|
73
86
|
CompanyStage,
|
|
87
|
+
ConsentPurposeStatus,
|
|
88
|
+
ConsentSourceKind,
|
|
89
|
+
ConsentStatus,
|
|
90
|
+
CustomFieldObjectType,
|
|
91
|
+
CustomFieldType,
|
|
92
|
+
CustomFieldTypeWithOptions,
|
|
74
93
|
ExtensibleRecordType,
|
|
94
|
+
FormActionStatus,
|
|
95
|
+
FormBooleanFieldType,
|
|
75
96
|
FormFieldMapTarget,
|
|
76
97
|
FormFieldType,
|
|
77
98
|
FormOptionValueType,
|
|
@@ -101,6 +122,38 @@ export type {
|
|
|
101
122
|
export { definedFields, idSchema, nullableTimestampSchema, timestampSchema } from './wire.ts'
|
|
102
123
|
export type { RecordTimestamps } from './wire.ts'
|
|
103
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
|
+
|
|
104
157
|
export {
|
|
105
158
|
eventActorSchema,
|
|
106
159
|
eventEnvelopeSchema,
|
|
@@ -109,6 +162,8 @@ export {
|
|
|
109
162
|
} from './events.ts'
|
|
110
163
|
export type { EventActor, EventTarget, KelpieEvent, UpdateEventData } from './events.ts'
|
|
111
164
|
|
|
165
|
+
export { composeName } from './names.ts'
|
|
166
|
+
export type { NameParts } from './names.ts'
|
|
112
167
|
export { personBody, personSchema } from './person.ts'
|
|
113
168
|
export type { Person, PersonInput, SocialProfile } from './person.ts'
|
|
114
169
|
|
|
@@ -124,6 +179,9 @@ export type { Deal, DealInput } from './deal.ts'
|
|
|
124
179
|
export { opportunityBody, opportunitySchema } from './opportunity.ts'
|
|
125
180
|
export type { Opportunity, OpportunityInput } from './opportunity.ts'
|
|
126
181
|
|
|
182
|
+
export { enquiryBody, enquirySchema } from './enquiry.ts'
|
|
183
|
+
export type { Enquiry, EnquiryInput } from './enquiry.ts'
|
|
184
|
+
|
|
127
185
|
export { partnershipBody, partnershipSchema } from './partnership.ts'
|
|
128
186
|
export type { Partnership, PartnershipInput } from './partnership.ts'
|
|
129
187
|
|
|
@@ -181,6 +239,7 @@ export { createFormBody, formBody, formSchema } from './form.ts'
|
|
|
181
239
|
export type {
|
|
182
240
|
CreateFormInput,
|
|
183
241
|
Form,
|
|
242
|
+
FormAttachTarget,
|
|
184
243
|
FormField,
|
|
185
244
|
FormFieldInput,
|
|
186
245
|
FormFieldOption,
|
|
@@ -188,8 +247,17 @@ export type {
|
|
|
188
247
|
FormInput,
|
|
189
248
|
} from './form.ts'
|
|
190
249
|
|
|
191
|
-
export {
|
|
192
|
-
|
|
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'
|
|
193
261
|
|
|
194
262
|
export {
|
|
195
263
|
CONFLICT_MODE_LABELS,
|
package/src/list.ts
CHANGED
|
@@ -22,6 +22,13 @@ export interface List extends RecordTimestamps {
|
|
|
22
22
|
readonly description: string | null
|
|
23
23
|
readonly targetType: RecordTargetType
|
|
24
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
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export const listSchema: z.ZodType<List, unknown> = z
|
|
@@ -31,6 +38,8 @@ export const listSchema: z.ZodType<List, unknown> = z
|
|
|
31
38
|
description: z.string().nullable(),
|
|
32
39
|
target_type: z.enum(RECORD_TARGET_TYPES),
|
|
33
40
|
member_count: z.number().int().nonnegative(),
|
|
41
|
+
consent_purpose_id: idSchema.nullable(),
|
|
42
|
+
consent_purpose_slug: z.string().nullable(),
|
|
34
43
|
...recordTimestamps,
|
|
35
44
|
})
|
|
36
45
|
.transform(
|
|
@@ -40,6 +49,8 @@ export const listSchema: z.ZodType<List, unknown> = z
|
|
|
40
49
|
description: wire.description,
|
|
41
50
|
targetType: wire.target_type,
|
|
42
51
|
memberCount: wire.member_count,
|
|
52
|
+
consentPurposeId: wire.consent_purpose_id,
|
|
53
|
+
consentPurposeSlug: wire.consent_purpose_slug,
|
|
43
54
|
createdAt: wire.created_at,
|
|
44
55
|
updatedAt: wire.updated_at,
|
|
45
56
|
}),
|
|
@@ -49,6 +60,8 @@ export interface CreateListInput {
|
|
|
49
60
|
readonly name: string
|
|
50
61
|
readonly targetType: RecordTargetType
|
|
51
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
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
export function createListBody(input: CreateListInput): Record<string, unknown> {
|
|
@@ -56,6 +69,7 @@ export function createListBody(input: CreateListInput): Record<string, unknown>
|
|
|
56
69
|
name: input.name,
|
|
57
70
|
target_type: input.targetType,
|
|
58
71
|
description: input.description,
|
|
72
|
+
consent_purpose_id: input.consentPurposeId,
|
|
59
73
|
})
|
|
60
74
|
}
|
|
61
75
|
|
|
@@ -63,11 +77,13 @@ export function createListBody(input: CreateListInput): Record<string, unknown>
|
|
|
63
77
|
export interface ListInput {
|
|
64
78
|
readonly name?: string
|
|
65
79
|
readonly description?: string | null
|
|
80
|
+
readonly consentPurposeId?: string | null
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
export function listBody(input: ListInput): Record<string, unknown> {
|
|
69
84
|
return definedFields({
|
|
70
85
|
name: input.name,
|
|
71
86
|
description: input.description,
|
|
87
|
+
consent_purpose_id: input.consentPurposeId,
|
|
72
88
|
})
|
|
73
89
|
}
|
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
|
+
}
|
package/src/opportunity.ts
CHANGED
|
@@ -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
|
}
|
package/src/partnership.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { CONSENT_PURPOSE_STATUSES } from './values.ts'
|
|
4
|
+
import type { ConsentPurposeStatus, ConsentStatus } from './values.ts'
|
|
5
|
+
import { definedFields, nullableTimestampSchema } from './wire.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The effective consent status for a (person, purpose) pair.
|
|
9
|
+
*
|
|
10
|
+
* `inherited: true` means the person has no explicit `person_consents` row for
|
|
11
|
+
* the purpose and the reader is looking at the purpose's `default_status`. In
|
|
12
|
+
* that case `source` and `notedAt` are null. `inherited: false` means the row
|
|
13
|
+
* exists and carries its own `granted | withdrawn`, `source`, and `notedAt`.
|
|
14
|
+
*/
|
|
15
|
+
export interface PersonConsent {
|
|
16
|
+
/** The purpose's slug — the identity that survives a rename of its label. */
|
|
17
|
+
readonly purposeSlug: string
|
|
18
|
+
readonly purposeLabel: string
|
|
19
|
+
readonly status: ConsentPurposeStatus
|
|
20
|
+
readonly source: string | null
|
|
21
|
+
readonly notedAt: Date | null
|
|
22
|
+
readonly inherited: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const personConsentSchema: z.ZodType<PersonConsent, unknown> = z
|
|
26
|
+
.object({
|
|
27
|
+
purpose_slug: z.string(),
|
|
28
|
+
purpose_label: z.string(),
|
|
29
|
+
status: z.enum(CONSENT_PURPOSE_STATUSES),
|
|
30
|
+
source: z.string().nullable(),
|
|
31
|
+
noted_at: nullableTimestampSchema,
|
|
32
|
+
inherited: z.boolean(),
|
|
33
|
+
})
|
|
34
|
+
.transform(
|
|
35
|
+
(wire): PersonConsent => ({
|
|
36
|
+
purposeSlug: wire.purpose_slug,
|
|
37
|
+
purposeLabel: wire.purpose_label,
|
|
38
|
+
status: wire.status,
|
|
39
|
+
source: wire.source,
|
|
40
|
+
notedAt: wire.noted_at,
|
|
41
|
+
inherited: wire.inherited,
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* What a manual override writes for one (person, purpose) pair. A `status` of
|
|
47
|
+
* `granted` or `withdrawn` upserts the row (`source: manual`); a `null` status
|
|
48
|
+
* clears the row and falls the pair back to the purpose's default.
|
|
49
|
+
*/
|
|
50
|
+
export interface PersonConsentInput {
|
|
51
|
+
readonly purposeSlug: string
|
|
52
|
+
readonly status: ConsentStatus | null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function personConsentWriteBody(input: PersonConsentInput): Record<string, unknown> {
|
|
56
|
+
return definedFields({
|
|
57
|
+
purpose_slug: input.purposeSlug,
|
|
58
|
+
status: input.status,
|
|
59
|
+
})
|
|
60
|
+
}
|
package/src/raise.ts
CHANGED
|
@@ -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
|
|
|
@@ -22,6 +24,8 @@ export interface Raise extends RecordTimestamps {
|
|
|
22
24
|
readonly personIds: readonly string[]
|
|
23
25
|
readonly summary: string
|
|
24
26
|
readonly tags: readonly string[]
|
|
27
|
+
/** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
|
|
28
|
+
readonly customFields: CustomFieldValues
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export const raiseSchema: z.ZodType<Raise, unknown> = z
|
|
@@ -39,6 +43,7 @@ export const raiseSchema: z.ZodType<Raise, unknown> = z
|
|
|
39
43
|
person_ids: z.array(idSchema),
|
|
40
44
|
summary: z.string(),
|
|
41
45
|
tags: z.array(z.string()),
|
|
46
|
+
custom_fields: customFieldValuesSchema,
|
|
42
47
|
...recordTimestamps,
|
|
43
48
|
})
|
|
44
49
|
.transform(
|
|
@@ -56,6 +61,7 @@ export const raiseSchema: z.ZodType<Raise, unknown> = z
|
|
|
56
61
|
personIds: wire.person_ids,
|
|
57
62
|
summary: wire.summary,
|
|
58
63
|
tags: wire.tags,
|
|
64
|
+
customFields: wire.custom_fields,
|
|
59
65
|
createdAt: wire.created_at,
|
|
60
66
|
updatedAt: wire.updated_at,
|
|
61
67
|
}),
|
|
@@ -74,6 +80,11 @@ export interface RaiseInput {
|
|
|
74
80
|
readonly personIds?: readonly string[]
|
|
75
81
|
readonly summary?: string
|
|
76
82
|
readonly tags?: readonly string[]
|
|
83
|
+
/**
|
|
84
|
+
* Partial merge patch: sent keys change, `null` clears a key, absent keys are
|
|
85
|
+
* left alone. Unknown keys are rejected at `422`.
|
|
86
|
+
*/
|
|
87
|
+
readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
export function raiseBody(input: RaiseInput): Record<string, unknown> {
|
|
@@ -90,5 +101,7 @@ export function raiseBody(input: RaiseInput): Record<string, unknown> {
|
|
|
90
101
|
person_ids: input.personIds,
|
|
91
102
|
summary: input.summary,
|
|
92
103
|
tags: input.tags,
|
|
104
|
+
custom_fields:
|
|
105
|
+
input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
|
|
93
106
|
})
|
|
94
107
|
}
|
package/src/sampleData.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface SampleDataCounts {
|
|
|
17
17
|
readonly opportunities: number
|
|
18
18
|
readonly raises: number
|
|
19
19
|
readonly partnerships: number
|
|
20
|
+
readonly enquiries: number
|
|
20
21
|
readonly roles: number
|
|
21
22
|
readonly candidates: number
|
|
22
23
|
}
|
|
@@ -32,6 +33,7 @@ export const sampleDataCountsSchema: z.ZodType<SampleDataCounts, unknown> = z
|
|
|
32
33
|
opportunities: z.number().int().nonnegative(),
|
|
33
34
|
raises: z.number().int().nonnegative(),
|
|
34
35
|
partnerships: z.number().int().nonnegative(),
|
|
36
|
+
enquiries: z.number().int().nonnegative(),
|
|
35
37
|
roles: z.number().int().nonnegative(),
|
|
36
38
|
candidates: z.number().int().nonnegative(),
|
|
37
39
|
})
|
|
@@ -46,6 +48,7 @@ export const sampleDataCountsSchema: z.ZodType<SampleDataCounts, unknown> = z
|
|
|
46
48
|
opportunities: wire.opportunities,
|
|
47
49
|
raises: wire.raises,
|
|
48
50
|
partnerships: wire.partnerships,
|
|
51
|
+
enquiries: wire.enquiries,
|
|
49
52
|
roles: wire.roles,
|
|
50
53
|
candidates: wire.candidates,
|
|
51
54
|
}),
|