@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.
- 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 +11 -0
- package/dist/company.d.ts.map +1 -1
- package/dist/company.js +7 -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/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/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 +25 -2
- package/dist/importExport.js.map +1 -1
- package/dist/index.d.ts +27 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +44 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +41 -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/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 +23 -0
- package/dist/sampleData.d.ts.map +1 -0
- package/dist/sampleData.js +31 -0
- package/dist/sampleData.js.map +1 -0
- package/dist/values.d.ts +88 -8
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +150 -3
- package/dist/values.js.map +1 -1
- package/package.json +1 -1
- package/src/account.ts +56 -0
- package/src/company.ts +19 -0
- package/src/consentPurpose.ts +95 -0
- package/src/customField.ts +211 -0
- package/src/deal.ts +13 -0
- package/src/emailDomainLinker.ts +28 -0
- package/src/enquiry.ts +94 -0
- package/src/form.ts +230 -2
- package/src/formSubmission.ts +63 -0
- package/src/importExport.ts +45 -6
- package/src/index.ts +86 -2
- package/src/list.ts +89 -0
- package/src/listMember.ts +54 -0
- package/src/listMembership.ts +45 -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 +55 -0
- package/src/values.ts +180 -3
package/src/company.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 { ACCOUNT_TYPES, COMPANY_STAGES, ICP_FITS, SIZE_BANDS } from './values.ts'
|
|
4
6
|
import type { AccountType, CompanyStage, IcpFit, SizeBand } from './values.ts'
|
|
5
7
|
import { definedFields, idSchema, recordTimestamps } from './wire.ts'
|
|
@@ -22,6 +24,10 @@ export interface Company extends RecordTimestamps {
|
|
|
22
24
|
readonly techStack: readonly string[]
|
|
23
25
|
readonly summary: string
|
|
24
26
|
readonly tags: readonly string[]
|
|
27
|
+
/** True when this Company represents the workspace itself ("us"). */
|
|
28
|
+
readonly isOwn: boolean
|
|
29
|
+
/** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
|
|
30
|
+
readonly customFields: CustomFieldValues
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export const companySchema: z.ZodType<Company, unknown> = z
|
|
@@ -40,6 +46,8 @@ export const companySchema: z.ZodType<Company, unknown> = z
|
|
|
40
46
|
tech_stack: z.array(z.string()),
|
|
41
47
|
summary: z.string(),
|
|
42
48
|
tags: z.array(z.string()),
|
|
49
|
+
is_own: z.boolean(),
|
|
50
|
+
custom_fields: customFieldValuesSchema,
|
|
43
51
|
...recordTimestamps,
|
|
44
52
|
})
|
|
45
53
|
.transform(
|
|
@@ -58,6 +66,8 @@ export const companySchema: z.ZodType<Company, unknown> = z
|
|
|
58
66
|
techStack: wire.tech_stack,
|
|
59
67
|
summary: wire.summary,
|
|
60
68
|
tags: wire.tags,
|
|
69
|
+
isOwn: wire.is_own,
|
|
70
|
+
customFields: wire.custom_fields,
|
|
61
71
|
createdAt: wire.created_at,
|
|
62
72
|
updatedAt: wire.updated_at,
|
|
63
73
|
}),
|
|
@@ -77,6 +87,12 @@ export interface CompanyInput {
|
|
|
77
87
|
readonly techStack?: readonly string[]
|
|
78
88
|
readonly summary?: string
|
|
79
89
|
readonly tags?: readonly string[]
|
|
90
|
+
readonly isOwn?: boolean
|
|
91
|
+
/**
|
|
92
|
+
* Partial merge patch: sent keys change, `null` clears a key, absent keys are
|
|
93
|
+
* left alone. Unknown keys are rejected at `422`.
|
|
94
|
+
*/
|
|
95
|
+
readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
export function companyBody(input: CompanyInput): Record<string, unknown> {
|
|
@@ -94,5 +110,8 @@ export function companyBody(input: CompanyInput): Record<string, unknown> {
|
|
|
94
110
|
tech_stack: input.techStack,
|
|
95
111
|
summary: input.summary,
|
|
96
112
|
tags: input.tags,
|
|
113
|
+
is_own: input.isOwn,
|
|
114
|
+
custom_fields:
|
|
115
|
+
input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
|
|
97
116
|
})
|
|
98
117
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { CONSENT_PURPOSE_STATUSES } from './values.ts'
|
|
4
|
+
import type { ConsentPurposeStatus } 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/consent_purposes`.
|
|
10
|
+
*
|
|
11
|
+
* A workspace defines its own purposes (`contact`, `marketing`, …). Every
|
|
12
|
+
* capture site — forms, lists, imports, the manual override on a Person —
|
|
13
|
+
* names one. Modelled on `custom_field_definitions`: `slug` is immutable and
|
|
14
|
+
* behaves as the key, `default_status` is the workspace default a person
|
|
15
|
+
* without an explicit `person_consents` row inherits, and `sort_order` runs
|
|
16
|
+
* 0..N-1 with the service renumbering on reorder and delete.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** A workspace's definition of one consent purpose. */
|
|
20
|
+
export interface ConsentPurpose extends RecordTimestamps {
|
|
21
|
+
readonly id: string
|
|
22
|
+
/** Immutable after create. Lowercase snake_case, matches `/^[a-z][a-z0-9_]*$/`. */
|
|
23
|
+
readonly slug: string
|
|
24
|
+
readonly label: string
|
|
25
|
+
readonly description: string
|
|
26
|
+
/** The workspace default a person without an explicit row inherits. */
|
|
27
|
+
readonly defaultStatus: ConsentPurposeStatus
|
|
28
|
+
readonly sortOrder: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const consentPurposeSchema: z.ZodType<ConsentPurpose, unknown> = z
|
|
32
|
+
.object({
|
|
33
|
+
id: idSchema,
|
|
34
|
+
slug: z.string(),
|
|
35
|
+
label: z.string(),
|
|
36
|
+
description: z.string(),
|
|
37
|
+
default_status: z.enum(CONSENT_PURPOSE_STATUSES),
|
|
38
|
+
sort_order: z.number().int(),
|
|
39
|
+
...recordTimestamps,
|
|
40
|
+
})
|
|
41
|
+
.transform(
|
|
42
|
+
(wire): ConsentPurpose => ({
|
|
43
|
+
id: wire.id,
|
|
44
|
+
slug: wire.slug,
|
|
45
|
+
label: wire.label,
|
|
46
|
+
description: wire.description,
|
|
47
|
+
defaultStatus: wire.default_status,
|
|
48
|
+
sortOrder: wire.sort_order,
|
|
49
|
+
createdAt: wire.created_at,
|
|
50
|
+
updatedAt: wire.updated_at,
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The body a create takes. `slug` is only written here; a `PATCH` cannot
|
|
56
|
+
* change it — a rename would strand every stored `person_consents` row and
|
|
57
|
+
* every form/list/import that names the purpose.
|
|
58
|
+
*/
|
|
59
|
+
export interface CreateConsentPurposeInput {
|
|
60
|
+
readonly slug: string
|
|
61
|
+
readonly label: string
|
|
62
|
+
readonly description?: string
|
|
63
|
+
readonly defaultStatus?: ConsentPurposeStatus
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createConsentPurposeBody(
|
|
67
|
+
input: CreateConsentPurposeInput,
|
|
68
|
+
): Record<string, unknown> {
|
|
69
|
+
return definedFields({
|
|
70
|
+
slug: input.slug,
|
|
71
|
+
label: input.label,
|
|
72
|
+
description: input.description,
|
|
73
|
+
default_status: input.defaultStatus,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What a `PATCH` may change. `slug` is fixed for the purpose's lifetime and
|
|
79
|
+
* the strict body naturally makes it a `422`.
|
|
80
|
+
*/
|
|
81
|
+
export interface ConsentPurposeInput {
|
|
82
|
+
readonly label?: string
|
|
83
|
+
readonly description?: string
|
|
84
|
+
readonly defaultStatus?: ConsentPurposeStatus
|
|
85
|
+
readonly sortOrder?: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function consentPurposeBody(input: ConsentPurposeInput): Record<string, unknown> {
|
|
89
|
+
return definedFields({
|
|
90
|
+
label: input.label,
|
|
91
|
+
description: input.description,
|
|
92
|
+
default_status: input.defaultStatus,
|
|
93
|
+
sort_order: input.sortOrder,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CUSTOM_FIELD_OBJECT_TYPES,
|
|
5
|
+
CUSTOM_FIELD_TYPES,
|
|
6
|
+
} from './values.ts'
|
|
7
|
+
import type { CustomFieldObjectType, CustomFieldType } from './values.ts'
|
|
8
|
+
import { definedFields, idSchema, recordTimestamps } from './wire.ts'
|
|
9
|
+
import type { RecordTimestamps } from './wire.ts'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wire and write shapes for `/v1/custom_fields`.
|
|
13
|
+
*
|
|
14
|
+
* The definition is workspace-scoped. Every record on the six object types
|
|
15
|
+
* carries a `custom_fields` object keyed by definition key; the values are the
|
|
16
|
+
* shape this file describes. The Zod layer here only checks structure — that a
|
|
17
|
+
* value looks like text / number / boolean / a string list / a money object —
|
|
18
|
+
* because the workspace's definitions are read from the database, not the
|
|
19
|
+
* request. Definition-aware checks (this key exists on this object type, this
|
|
20
|
+
* select value is one of the options) run in the service.
|
|
21
|
+
*
|
|
22
|
+
* Merge semantics on the record's `custom_fields` (documented in `api.md`): a
|
|
23
|
+
* `PATCH` sends the keys that change; `null` clears a key; an unknown key is
|
|
24
|
+
* `422` naming `custom_fields.<key>`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The value union a custom field can carry on the wire. Structural only — the
|
|
29
|
+
* validator in `@kelpie/server` reads the workspace's definitions and refuses a
|
|
30
|
+
* `number` written into a `text` field, an unknown `select` option, and so on.
|
|
31
|
+
*
|
|
32
|
+
* `text`, `long_text`, `url`, `date` and `select` all serialise as strings; a
|
|
33
|
+
* `multi_select` is a `string[]`; a `currency` is a small object; the rest are
|
|
34
|
+
* primitives. Nothing here reads a definition, so the caps below are the
|
|
35
|
+
* absolute ceilings meant to reject a runaway payload before it reaches the
|
|
36
|
+
* database, not the per-type limits (also enforced by the validator).
|
|
37
|
+
*/
|
|
38
|
+
export const customFieldWireValue = z.union([
|
|
39
|
+
z.string().max(65536),
|
|
40
|
+
z.number(),
|
|
41
|
+
z.boolean(),
|
|
42
|
+
z.array(z.string().min(1).max(120)).max(100),
|
|
43
|
+
z.strictObject({
|
|
44
|
+
amount_cents: z.number().int(),
|
|
45
|
+
currency: z.string().regex(/^[A-Z]{3}$/u),
|
|
46
|
+
}),
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
export type CustomFieldWireValue = z.infer<typeof customFieldWireValue>
|
|
50
|
+
|
|
51
|
+
/** A value already parsed and rebuilt as the record the UI holds. */
|
|
52
|
+
export type CustomFieldValue =
|
|
53
|
+
| string
|
|
54
|
+
| number
|
|
55
|
+
| boolean
|
|
56
|
+
| readonly string[]
|
|
57
|
+
| { readonly amountCents: number; readonly currency: string }
|
|
58
|
+
|
|
59
|
+
/** The `custom_fields` object every record on the six object types carries. */
|
|
60
|
+
export type CustomFieldValues = Readonly<Record<string, CustomFieldValue>>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses the wire object into the record shape the UI works with. Currency is
|
|
64
|
+
* the one type that changes shape (`amount_cents` → `amountCents`); everything
|
|
65
|
+
* else is the same value.
|
|
66
|
+
*/
|
|
67
|
+
export const customFieldValuesSchema: z.ZodType<CustomFieldValues, unknown> = z
|
|
68
|
+
.record(z.string(), customFieldWireValue)
|
|
69
|
+
.transform((wire): CustomFieldValues => {
|
|
70
|
+
const out: Record<string, CustomFieldValue> = {}
|
|
71
|
+
for (const [key, value] of Object.entries(wire)) {
|
|
72
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
73
|
+
out[key] = { amountCents: value.amount_cents, currency: value.currency }
|
|
74
|
+
} else {
|
|
75
|
+
out[key] = value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The shape a record's `PATCH` accepts for `custom_fields`. A key's value is
|
|
83
|
+
* the wire value, or `null` to clear that key; unknown keys fail at the service
|
|
84
|
+
* layer with `422 custom_fields.<key>`.
|
|
85
|
+
*/
|
|
86
|
+
export const customFieldsPatchShape: z.ZodType<
|
|
87
|
+
Readonly<Record<string, CustomFieldWireValue | null>>,
|
|
88
|
+
unknown
|
|
89
|
+
> = z
|
|
90
|
+
.record(z.string().min(1).max(64), customFieldWireValue.nullable())
|
|
91
|
+
.describe(
|
|
92
|
+
'Workspace-defined fields keyed by definition key. List the workspace\'s definitions with the custom_fields_list tool before writing. A key set to null clears the value; an unknown key is refused.',
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Turns the UI's record shape back into the wire object for a `PATCH` body.
|
|
97
|
+
* The inverse of `customFieldValuesSchema`.
|
|
98
|
+
*/
|
|
99
|
+
export function customFieldValuesBody(
|
|
100
|
+
values: Readonly<Record<string, CustomFieldValue | null>>,
|
|
101
|
+
): Record<string, CustomFieldWireValue | null> {
|
|
102
|
+
const out: Record<string, CustomFieldWireValue | null> = {}
|
|
103
|
+
for (const [key, value] of Object.entries(values)) {
|
|
104
|
+
out[key] = wireCustomFieldValue(value)
|
|
105
|
+
}
|
|
106
|
+
return out
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function wireCustomFieldValue(value: CustomFieldValue | null): CustomFieldWireValue | null {
|
|
110
|
+
if (value === null) return null
|
|
111
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
112
|
+
return value
|
|
113
|
+
}
|
|
114
|
+
// Array.isArray does not narrow a `readonly` array literal, so branch on the
|
|
115
|
+
// currency object's shape and treat everything else as the string list.
|
|
116
|
+
if ('amountCents' in value && 'currency' in value) {
|
|
117
|
+
return { amount_cents: value.amountCents, currency: value.currency }
|
|
118
|
+
}
|
|
119
|
+
return [...value]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A workspace's definition of one custom field, for one object type. */
|
|
123
|
+
export interface CustomFieldDefinition extends RecordTimestamps {
|
|
124
|
+
readonly id: string
|
|
125
|
+
readonly objectType: CustomFieldObjectType
|
|
126
|
+
/** Immutable after create. Lowercase snake_case, matches `/^[a-z][a-z0-9_]*$/`. */
|
|
127
|
+
readonly key: string
|
|
128
|
+
readonly label: string
|
|
129
|
+
/** Immutable after create. */
|
|
130
|
+
readonly type: CustomFieldType
|
|
131
|
+
/** Non-empty only for `select` and `multi_select`. */
|
|
132
|
+
readonly options: readonly string[]
|
|
133
|
+
readonly description: string
|
|
134
|
+
readonly sortOrder: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const customFieldDefinitionSchema: z.ZodType<CustomFieldDefinition, unknown> = z
|
|
138
|
+
.object({
|
|
139
|
+
id: idSchema,
|
|
140
|
+
object_type: z.enum(CUSTOM_FIELD_OBJECT_TYPES),
|
|
141
|
+
key: z.string(),
|
|
142
|
+
label: z.string(),
|
|
143
|
+
type: z.enum(CUSTOM_FIELD_TYPES),
|
|
144
|
+
options: z.array(z.string()),
|
|
145
|
+
description: z.string(),
|
|
146
|
+
sort_order: z.number().int(),
|
|
147
|
+
...recordTimestamps,
|
|
148
|
+
})
|
|
149
|
+
.transform(
|
|
150
|
+
(wire): CustomFieldDefinition => ({
|
|
151
|
+
id: wire.id,
|
|
152
|
+
objectType: wire.object_type,
|
|
153
|
+
key: wire.key,
|
|
154
|
+
label: wire.label,
|
|
155
|
+
type: wire.type,
|
|
156
|
+
options: wire.options,
|
|
157
|
+
description: wire.description,
|
|
158
|
+
sortOrder: wire.sort_order,
|
|
159
|
+
createdAt: wire.created_at,
|
|
160
|
+
updatedAt: wire.updated_at,
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The body a create takes. `object_type`, `key`, and `type` are only written
|
|
166
|
+
* here; a `PATCH` cannot change any of them (see `CustomFieldDefinitionInput`).
|
|
167
|
+
*/
|
|
168
|
+
export interface CreateCustomFieldDefinitionInput {
|
|
169
|
+
readonly objectType: CustomFieldObjectType
|
|
170
|
+
readonly key: string
|
|
171
|
+
readonly label: string
|
|
172
|
+
readonly type: CustomFieldType
|
|
173
|
+
readonly options?: readonly string[]
|
|
174
|
+
readonly description?: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createCustomFieldDefinitionBody(
|
|
178
|
+
input: CreateCustomFieldDefinitionInput,
|
|
179
|
+
): Record<string, unknown> {
|
|
180
|
+
return definedFields({
|
|
181
|
+
object_type: input.objectType,
|
|
182
|
+
key: input.key,
|
|
183
|
+
label: input.label,
|
|
184
|
+
type: input.type,
|
|
185
|
+
options: input.options,
|
|
186
|
+
description: input.description,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* What a `PATCH` may change. `key` and `type` are fixed for the definition's
|
|
192
|
+
* lifetime — a change would strand records with the wrong value shape — and
|
|
193
|
+
* the strict body naturally makes either a `422`.
|
|
194
|
+
*/
|
|
195
|
+
export interface CustomFieldDefinitionInput {
|
|
196
|
+
readonly label?: string
|
|
197
|
+
readonly options?: readonly string[]
|
|
198
|
+
readonly description?: string
|
|
199
|
+
readonly sortOrder?: number
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function customFieldDefinitionBody(
|
|
203
|
+
input: CustomFieldDefinitionInput,
|
|
204
|
+
): Record<string, unknown> {
|
|
205
|
+
return definedFields({
|
|
206
|
+
label: input.label,
|
|
207
|
+
options: input.options,
|
|
208
|
+
description: input.description,
|
|
209
|
+
sort_order: input.sortOrder,
|
|
210
|
+
})
|
|
211
|
+
}
|
package/src/deal.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 Deal extends RecordTimestamps {
|
|
|
22
24
|
readonly summary: string
|
|
23
25
|
readonly tags: readonly string[]
|
|
24
26
|
readonly externalId: string | null
|
|
27
|
+
/** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
|
|
28
|
+
readonly customFields: CustomFieldValues
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export const dealSchema: z.ZodType<Deal, unknown> = z
|
|
@@ -41,6 +45,7 @@ export const dealSchema: z.ZodType<Deal, unknown> = z
|
|
|
41
45
|
summary: z.string(),
|
|
42
46
|
tags: z.array(z.string()),
|
|
43
47
|
external_id: z.string().nullable(),
|
|
48
|
+
custom_fields: customFieldValuesSchema,
|
|
44
49
|
...recordTimestamps,
|
|
45
50
|
})
|
|
46
51
|
.transform(
|
|
@@ -60,6 +65,7 @@ export const dealSchema: z.ZodType<Deal, unknown> = z
|
|
|
60
65
|
summary: wire.summary,
|
|
61
66
|
tags: wire.tags,
|
|
62
67
|
externalId: wire.external_id,
|
|
68
|
+
customFields: wire.custom_fields,
|
|
63
69
|
createdAt: wire.created_at,
|
|
64
70
|
updatedAt: wire.updated_at,
|
|
65
71
|
}),
|
|
@@ -80,6 +86,11 @@ export interface DealInput {
|
|
|
80
86
|
readonly summary?: string
|
|
81
87
|
readonly tags?: readonly string[]
|
|
82
88
|
readonly externalId?: string | null
|
|
89
|
+
/**
|
|
90
|
+
* Partial merge patch: sent keys change, `null` clears a key, absent keys are
|
|
91
|
+
* left alone. Unknown keys are rejected at `422`.
|
|
92
|
+
*/
|
|
93
|
+
readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
export function dealBody(input: DealInput): Record<string, unknown> {
|
|
@@ -98,5 +109,7 @@ export function dealBody(input: DealInput): Record<string, unknown> {
|
|
|
98
109
|
summary: input.summary,
|
|
99
110
|
tags: input.tags,
|
|
100
111
|
external_id: input.externalId,
|
|
112
|
+
custom_fields:
|
|
113
|
+
input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
|
|
101
114
|
})
|
|
102
115
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wire shape for `POST /v1/workspaces/:id/relink-email-domains`.
|
|
5
|
+
*
|
|
6
|
+
* The endpoint takes no body. The response counts what the sweep did: how
|
|
7
|
+
* many Companies were scanned and how many titleless Positions were newly
|
|
8
|
+
* created. A follow-up run on the same workspace returns the same count for
|
|
9
|
+
* `companies_scanned` and zero for `positions_created` — the sweep is
|
|
10
|
+
* idempotent (a person already linked to that company is skipped).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface RelinkEmailDomainsCounts {
|
|
14
|
+
readonly companiesScanned: number
|
|
15
|
+
readonly positionsCreated: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const relinkEmailDomainsCountsSchema: z.ZodType<RelinkEmailDomainsCounts, unknown> = z
|
|
19
|
+
.object({
|
|
20
|
+
companies_scanned: z.number().int().nonnegative(),
|
|
21
|
+
positions_created: z.number().int().nonnegative(),
|
|
22
|
+
})
|
|
23
|
+
.transform(
|
|
24
|
+
(wire): RelinkEmailDomainsCounts => ({
|
|
25
|
+
companiesScanned: wire.companies_scanned,
|
|
26
|
+
positionsCreated: wire.positions_created,
|
|
27
|
+
}),
|
|
28
|
+
)
|
package/src/enquiry.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import { customFieldValuesBody, customFieldValuesSchema } from './customField.ts'
|
|
4
|
+
import type { CustomFieldValue, CustomFieldValues } from './customField.ts'
|
|
5
|
+
import { definedFields, idSchema, recordTimestamps } from './wire.ts'
|
|
6
|
+
import type { RecordTimestamps } from './wire.ts'
|
|
7
|
+
|
|
8
|
+
/** Wire and write shapes for `/v1/enquiries`. */
|
|
9
|
+
|
|
10
|
+
export interface Enquiry extends RecordTimestamps {
|
|
11
|
+
readonly id: string
|
|
12
|
+
readonly name: string
|
|
13
|
+
/** Free text ("Website", "Email", "Referral", …), like Opportunity's `kind`. */
|
|
14
|
+
readonly source: string
|
|
15
|
+
readonly stageId: string
|
|
16
|
+
/** Nullable: an early enquiry may arrive before a company is on file. */
|
|
17
|
+
readonly companyId: string | null
|
|
18
|
+
readonly ownerId: string | null
|
|
19
|
+
/** Non-null once the enquiry has been converted to a Deal; nulled if that deal is deleted. */
|
|
20
|
+
readonly convertedDealId: string | null
|
|
21
|
+
readonly personIds: readonly string[]
|
|
22
|
+
readonly summary: string
|
|
23
|
+
readonly tags: readonly string[]
|
|
24
|
+
/** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
|
|
25
|
+
readonly customFields: CustomFieldValues
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const enquirySchema: z.ZodType<Enquiry, unknown> = z
|
|
29
|
+
.object({
|
|
30
|
+
id: idSchema,
|
|
31
|
+
name: z.string(),
|
|
32
|
+
source: z.string(),
|
|
33
|
+
stage_id: idSchema,
|
|
34
|
+
company_id: idSchema.nullable(),
|
|
35
|
+
owner_id: idSchema.nullable(),
|
|
36
|
+
converted_deal_id: idSchema.nullable(),
|
|
37
|
+
person_ids: z.array(idSchema),
|
|
38
|
+
summary: z.string(),
|
|
39
|
+
tags: z.array(z.string()),
|
|
40
|
+
custom_fields: customFieldValuesSchema,
|
|
41
|
+
...recordTimestamps,
|
|
42
|
+
})
|
|
43
|
+
.transform(
|
|
44
|
+
(wire): Enquiry => ({
|
|
45
|
+
id: wire.id,
|
|
46
|
+
name: wire.name,
|
|
47
|
+
source: wire.source,
|
|
48
|
+
stageId: wire.stage_id,
|
|
49
|
+
companyId: wire.company_id,
|
|
50
|
+
ownerId: wire.owner_id,
|
|
51
|
+
convertedDealId: wire.converted_deal_id,
|
|
52
|
+
personIds: wire.person_ids,
|
|
53
|
+
summary: wire.summary,
|
|
54
|
+
tags: wire.tags,
|
|
55
|
+
customFields: wire.custom_fields,
|
|
56
|
+
createdAt: wire.created_at,
|
|
57
|
+
updatedAt: wire.updated_at,
|
|
58
|
+
}),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* `convertedDealId` is deliberately absent from the input: it is set only by
|
|
63
|
+
* `POST /v1/enquiries/:id/convert`, and a `PATCH` that carried it is `422`.
|
|
64
|
+
*/
|
|
65
|
+
export interface EnquiryInput {
|
|
66
|
+
readonly name?: string
|
|
67
|
+
readonly source?: string
|
|
68
|
+
readonly stageId?: string
|
|
69
|
+
readonly companyId?: string | null
|
|
70
|
+
readonly ownerId?: string | null
|
|
71
|
+
readonly personIds?: readonly string[]
|
|
72
|
+
readonly summary?: string
|
|
73
|
+
readonly tags?: readonly string[]
|
|
74
|
+
/**
|
|
75
|
+
* Partial merge patch: sent keys change, `null` clears a key, absent keys are
|
|
76
|
+
* left alone. Unknown keys are rejected at `422`.
|
|
77
|
+
*/
|
|
78
|
+
readonly customFields?: Readonly<Record<string, CustomFieldValue | null>>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function enquiryBody(input: EnquiryInput): Record<string, unknown> {
|
|
82
|
+
return definedFields({
|
|
83
|
+
name: input.name,
|
|
84
|
+
source: input.source,
|
|
85
|
+
stage_id: input.stageId,
|
|
86
|
+
company_id: input.companyId,
|
|
87
|
+
owner_id: input.ownerId,
|
|
88
|
+
person_ids: input.personIds,
|
|
89
|
+
summary: input.summary,
|
|
90
|
+
tags: input.tags,
|
|
91
|
+
custom_fields:
|
|
92
|
+
input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
|
|
93
|
+
})
|
|
94
|
+
}
|