@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.
Files changed (91) hide show
  1. package/dist/company.d.ts +8 -0
  2. package/dist/company.d.ts.map +1 -1
  3. package/dist/company.js +4 -0
  4. package/dist/company.js.map +1 -1
  5. package/dist/consentPurpose.d.ts +49 -0
  6. package/dist/consentPurpose.d.ts.map +1 -0
  7. package/dist/consentPurpose.js +40 -0
  8. package/dist/consentPurpose.js.map +1 -0
  9. package/dist/customField.d.ts +99 -0
  10. package/dist/customField.d.ts.map +1 -0
  11. package/dist/customField.js +133 -0
  12. package/dist/customField.js.map +1 -0
  13. package/dist/deal.d.ts +8 -0
  14. package/dist/deal.d.ts.map +1 -1
  15. package/dist/deal.js +4 -0
  16. package/dist/deal.js.map +1 -1
  17. package/dist/enquiry.d.ts +43 -0
  18. package/dist/enquiry.d.ts.map +1 -0
  19. package/dist/enquiry.js +47 -0
  20. package/dist/enquiry.js.map +1 -0
  21. package/dist/form.d.ts +117 -2
  22. package/dist/form.d.ts.map +1 -1
  23. package/dist/form.js +102 -1
  24. package/dist/form.js.map +1 -1
  25. package/dist/formSubmission.d.ts +29 -0
  26. package/dist/formSubmission.d.ts.map +1 -1
  27. package/dist/formSubmission.js +34 -0
  28. package/dist/formSubmission.js.map +1 -1
  29. package/dist/importExport.d.ts +28 -4
  30. package/dist/importExport.d.ts.map +1 -1
  31. package/dist/importExport.js +24 -1
  32. package/dist/importExport.js.map +1 -1
  33. package/dist/index.d.ts +15 -5
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/list.d.ts +10 -0
  38. package/dist/list.d.ts.map +1 -1
  39. package/dist/list.js +6 -0
  40. package/dist/list.js.map +1 -1
  41. package/dist/names.d.ts +43 -0
  42. package/dist/names.d.ts.map +1 -0
  43. package/dist/names.js +35 -0
  44. package/dist/names.js.map +1 -0
  45. package/dist/opportunity.d.ts +10 -0
  46. package/dist/opportunity.d.ts.map +1 -1
  47. package/dist/opportunity.js +7 -0
  48. package/dist/opportunity.js.map +1 -1
  49. package/dist/partnership.d.ts +8 -0
  50. package/dist/partnership.d.ts.map +1 -1
  51. package/dist/partnership.js +4 -0
  52. package/dist/partnership.js.map +1 -1
  53. package/dist/person.d.ts +42 -1
  54. package/dist/person.d.ts.map +1 -1
  55. package/dist/person.js +23 -0
  56. package/dist/person.js.map +1 -1
  57. package/dist/personConsent.d.ts +31 -0
  58. package/dist/personConsent.d.ts.map +1 -0
  59. package/dist/personConsent.js +27 -0
  60. package/dist/personConsent.js.map +1 -0
  61. package/dist/raise.d.ts +8 -0
  62. package/dist/raise.d.ts.map +1 -1
  63. package/dist/raise.js +4 -0
  64. package/dist/raise.js.map +1 -1
  65. package/dist/sampleData.d.ts +1 -0
  66. package/dist/sampleData.d.ts.map +1 -1
  67. package/dist/sampleData.js +2 -0
  68. package/dist/sampleData.js.map +1 -1
  69. package/dist/values.d.ts +86 -8
  70. package/dist/values.d.ts.map +1 -1
  71. package/dist/values.js +140 -3
  72. package/dist/values.js.map +1 -1
  73. package/package.json +1 -1
  74. package/src/company.ts +13 -0
  75. package/src/consentPurpose.ts +95 -0
  76. package/src/customField.ts +211 -0
  77. package/src/deal.ts +13 -0
  78. package/src/enquiry.ts +94 -0
  79. package/src/form.ts +230 -2
  80. package/src/formSubmission.ts +63 -0
  81. package/src/importExport.ts +44 -5
  82. package/src/index.ts +70 -2
  83. package/src/list.ts +16 -0
  84. package/src/names.ts +49 -0
  85. package/src/opportunity.ts +18 -0
  86. package/src/partnership.ts +13 -0
  87. package/src/person.ts +69 -1
  88. package/src/personConsent.ts +60 -0
  89. package/src/raise.ts +13 -0
  90. package/src/sampleData.ts +3 -0
  91. package/src/values.ts +169 -3
package/dist/values.js CHANGED
@@ -74,6 +74,7 @@ export const EXTENSIBLE_RECORD_TYPES = [
74
74
  'opportunity',
75
75
  'partnership',
76
76
  'raise',
77
+ 'enquiry',
77
78
  'role',
78
79
  'candidate',
79
80
  ];
@@ -91,6 +92,7 @@ export const RECORD_TARGET_TYPES = [
91
92
  'opportunity',
92
93
  'partnership',
93
94
  'raise',
95
+ 'enquiry',
94
96
  'candidate',
95
97
  ];
96
98
  /** Human labels for `RECORD_TARGET_TYPES`. Used by any picker that names a type. */
@@ -101,6 +103,7 @@ export const RECORD_TARGET_TYPE_LABELS = {
101
103
  opportunity: 'Opportunity',
102
104
  partnership: 'Partnership',
103
105
  raise: 'Raise',
106
+ enquiry: 'Enquiry',
104
107
  candidate: 'Candidate',
105
108
  };
106
109
  /**
@@ -119,6 +122,7 @@ export const SEARCH_COLLECTIONS = [
119
122
  'person',
120
123
  'role',
121
124
  'company',
125
+ 'enquiry',
122
126
  'deal',
123
127
  'opportunity',
124
128
  'raise',
@@ -167,13 +171,20 @@ export const INTERVIEW_STAGE_LABELS = {
167
171
  /** Where a candidate enters the process when no stage is named. */
168
172
  export const FIRST_INTERVIEW_STAGE = INTERVIEW_STAGES[0];
169
173
  /**
170
- * The four pipelines whose board columns live in `pipeline_stages`. A Deal moves
174
+ * The five pipelines whose board columns live in `pipeline_stages`. A Deal moves
171
175
  * through `deal` stages and so on; the kinds are fixed even though the stages
172
176
  * within each are workspace-configurable.
173
177
  */
174
- export const PIPELINE_KINDS = ['deal', 'opportunity', 'raise', 'partnership'];
178
+ export const PIPELINE_KINDS = [
179
+ 'enquiry',
180
+ 'deal',
181
+ 'opportunity',
182
+ 'raise',
183
+ 'partnership',
184
+ ];
175
185
  /** Display names for `PIPELINE_KINDS`. "Fundraising" is what the nav calls a Raise. */
176
186
  export const PIPELINE_KIND_LABELS = {
187
+ enquiry: 'Enquiry',
177
188
  deal: 'Deal',
178
189
  opportunity: 'Opportunity',
179
190
  raise: 'Fundraising',
@@ -243,32 +254,59 @@ export const FORM_STATUSES = ['active', 'paused'];
243
254
  * uploads, multi-page forms and branching out of scope, and every type here is
244
255
  * one `<input>`, `<textarea>` or `<select>`.
245
256
  */
246
- export const FORM_FIELD_TYPES = ['text', 'email', 'textarea', 'select'];
257
+ export const FORM_FIELD_TYPES = ['text', 'email', 'textarea', 'select', 'consent', 'notice'];
258
+ /**
259
+ * The field types whose answer is stored as a boolean, not a string. A
260
+ * `consent` field renders as a checkbox whose label carries the consent
261
+ * statement; the answer records whether the visitor ticked it.
262
+ */
263
+ export const FORM_BOOLEAN_FIELD_TYPES = ['consent'];
247
264
  /**
248
265
  * Where a field's answer lands on submit.
249
266
  *
250
267
  * `position.title` rather than a person field, because a job title belongs to the
251
268
  * Person↔Company link and nowhere else. `submission` stores the answer without
252
269
  * writing any CRM record, which is what a free-text "How can we help?" wants.
270
+ *
271
+ * A form asks for a name the way its author wants it asked: one `person.name`
272
+ * box, or the `person.first_name` / `person.last_name` pair most sign-up forms
273
+ * use. Mapping the pair and no `person.name` composes the display name from the
274
+ * two, so neither arrangement makes a nameless person. `salutation` and `suffix`
275
+ * are storable on a Person but are not offered here — a public form asking for
276
+ * them is rare enough that the choice is not worth the room in this list.
253
277
  */
254
278
  export const FORM_FIELD_MAP_TARGETS = [
255
279
  'person.name',
280
+ 'person.first_name',
281
+ 'person.last_name',
256
282
  'person.email',
283
+ 'person.consent',
257
284
  'company.name',
258
285
  'company.domain',
259
286
  'position.title',
287
+ 'enquiry.name',
260
288
  'deal.name',
289
+ 'opportunity.name',
290
+ 'partnership.name',
261
291
  'submission',
262
292
  ];
263
293
  export const FORM_FIELD_MAP_TARGET_LABELS = {
264
294
  'person.name': 'Person · name',
295
+ 'person.first_name': 'Person · first name',
296
+ 'person.last_name': 'Person · last name',
265
297
  'person.email': 'Person · email',
298
+ 'person.consent': 'Person · consent',
266
299
  'company.name': 'Company · name',
267
300
  'company.domain': 'Company · domain',
268
301
  'position.title': 'Position · title',
302
+ 'enquiry.name': 'Enquiry · name',
269
303
  'deal.name': 'Deal · name',
304
+ 'opportunity.name': 'Opportunity · name',
305
+ 'partnership.name': 'Partnership · name',
270
306
  submission: 'Submission only',
271
307
  };
308
+ /** The map target for a consent field. Repeats per purpose, unlike `person.email`. */
309
+ export const PERSON_CONSENT_TARGET = 'person.consent';
272
310
  /** The one mapping a form cannot process without, and may carry at most once. */
273
311
  export const PERSON_EMAIL_TARGET = 'person.email';
274
312
  /**
@@ -278,6 +316,13 @@ export const PERSON_EMAIL_TARGET = 'person.email';
278
316
  * the string means, so a consumer knows `"true"` was a checkbox and not a word.
279
317
  */
280
318
  export const FORM_OPTION_VALUE_TYPES = ['string', 'number', 'boolean'];
319
+ /**
320
+ * Each post-submit action lands one of these on the submission's `action_log`.
321
+ * `ok` = the action ran; `skipped` = its precondition was absent (a company
322
+ * list on a submit that never resolved one); `error` = the savepoint rolled
323
+ * back so the rest of the submit could continue.
324
+ */
325
+ export const FORM_ACTION_STATUSES = ['ok', 'skipped', 'error'];
281
326
  /**
282
327
  * The domain events a webhook can subscribe to.
283
328
  *
@@ -321,6 +366,7 @@ export const AGENT_TASK_TARGET_TYPES = [
321
366
  'opportunity',
322
367
  'partnership',
323
368
  'raise',
369
+ 'enquiry',
324
370
  'candidate',
325
371
  'role',
326
372
  'handbook',
@@ -341,4 +387,95 @@ export const AGENT_RUN_STATUS_LABELS = {
341
387
  succeeded: 'Succeeded',
342
388
  failed: 'Failed',
343
389
  };
390
+ /**
391
+ * The seven record types a workspace may attach custom field definitions to.
392
+ *
393
+ * The ones that already carry `tags` and agent-oriented fields on the record
394
+ * itself. Role and Candidate are absent on purpose: hiring state hangs off the
395
+ * Candidate link and a Role is a header rather than a rich record, so the demand
396
+ * signal for custom fields on either has not landed. Kept separate from
397
+ * `RECORD_TARGET_TYPES` and `EXTENSIBLE_RECORD_TYPES` so a later addition here
398
+ * cannot silently widen either of those.
399
+ */
400
+ export const CUSTOM_FIELD_OBJECT_TYPES = [
401
+ 'person',
402
+ 'company',
403
+ 'deal',
404
+ 'opportunity',
405
+ 'partnership',
406
+ 'raise',
407
+ 'enquiry',
408
+ ];
409
+ export const CUSTOM_FIELD_OBJECT_TYPE_LABELS = {
410
+ person: 'Person',
411
+ company: 'Company',
412
+ deal: 'Deal',
413
+ opportunity: 'Opportunity',
414
+ partnership: 'Partnership',
415
+ raise: 'Raise',
416
+ enquiry: 'Enquiry',
417
+ };
418
+ /**
419
+ * The nine editor types a custom field can carry.
420
+ *
421
+ * Deliberately distinct from `FORM_FIELD_TYPES`: a form asks a stranger for four
422
+ * kinds of text over one submit; a custom field is a first-class attribute on a
423
+ * record with numeric, date, boolean and currency answers. Sharing one list
424
+ * would drag either surface toward the other's shape.
425
+ *
426
+ * `record_reference` is intentionally absent from v1 — it needs the polymorphic
427
+ * existence check and delete-transaction cleanup that Phase 2 owns.
428
+ */
429
+ export const CUSTOM_FIELD_TYPES = [
430
+ 'text',
431
+ 'long_text',
432
+ 'number',
433
+ 'currency',
434
+ 'date',
435
+ 'checkbox',
436
+ 'select',
437
+ 'multi_select',
438
+ 'url',
439
+ ];
440
+ export const CUSTOM_FIELD_TYPE_LABELS = {
441
+ text: 'Text',
442
+ long_text: 'Long text',
443
+ number: 'Number',
444
+ currency: 'Currency',
445
+ date: 'Date',
446
+ checkbox: 'Checkbox',
447
+ select: 'Select',
448
+ multi_select: 'Multi-select',
449
+ url: 'URL',
450
+ };
451
+ /**
452
+ * The custom-field types whose definition carries an `options` list. Every
453
+ * other type stores its `options` as `[]` and any non-empty write is `422`.
454
+ */
455
+ export const CUSTOM_FIELD_TYPES_WITH_OPTIONS = ['select', 'multi_select'];
456
+ /**
457
+ * The default a consent purpose starts with, and what a person without an
458
+ * explicit `person_consents` row inherits for that purpose. `unknown` is the
459
+ * safest default — silence about a person's wishes is not a grant.
460
+ */
461
+ export const CONSENT_PURPOSE_STATUSES = ['unknown', 'granted', 'withdrawn'];
462
+ export const CONSENT_PURPOSE_STATUS_LABELS = {
463
+ unknown: 'Unknown',
464
+ granted: 'Granted',
465
+ withdrawn: 'Withdrawn',
466
+ };
467
+ /**
468
+ * The explicit status on a `person_consents` row. `unknown` is deliberately
469
+ * absent: no row is the unknown, so a stored row always carries a decision.
470
+ */
471
+ export const CONSENT_STATUSES = ['granted', 'withdrawn'];
472
+ export const CONSENT_STATUS_LABELS = {
473
+ granted: 'Granted',
474
+ withdrawn: 'Withdrawn',
475
+ };
476
+ /**
477
+ * Where a `person_consents` row came from. `form:<form_id>` and `list:<list_id>`
478
+ * are prefixed so the origin is inspectable; `import` and `manual` need no id.
479
+ */
480
+ export const CONSENT_SOURCE_KINDS = ['form', 'list', 'import', 'manual'];
344
481
  //# sourceMappingURL=values.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"values.js","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"values.js","sourceRoot":"","sources":["../src/values.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelpie/schemas",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Wire contract for the Kelpie API, as Zod schemas. Shared by the service, the UI, and any agent decoding /v1 responses.",
5
5
  "keywords": [
6
6
  "kelpie",
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'
@@ -24,6 +26,8 @@ export interface Company extends RecordTimestamps {
24
26
  readonly tags: readonly string[]
25
27
  /** True when this Company represents the workspace itself ("us"). */
26
28
  readonly isOwn: boolean
29
+ /** Workspace-defined fields, keyed by definition key. Always present (default `{}`). */
30
+ readonly customFields: CustomFieldValues
27
31
  }
28
32
 
29
33
  export const companySchema: z.ZodType<Company, unknown> = z
@@ -43,6 +47,7 @@ export const companySchema: z.ZodType<Company, unknown> = z
43
47
  summary: z.string(),
44
48
  tags: z.array(z.string()),
45
49
  is_own: z.boolean(),
50
+ custom_fields: customFieldValuesSchema,
46
51
  ...recordTimestamps,
47
52
  })
48
53
  .transform(
@@ -62,6 +67,7 @@ export const companySchema: z.ZodType<Company, unknown> = z
62
67
  summary: wire.summary,
63
68
  tags: wire.tags,
64
69
  isOwn: wire.is_own,
70
+ customFields: wire.custom_fields,
65
71
  createdAt: wire.created_at,
66
72
  updatedAt: wire.updated_at,
67
73
  }),
@@ -82,6 +88,11 @@ export interface CompanyInput {
82
88
  readonly summary?: string
83
89
  readonly tags?: readonly string[]
84
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>>
85
96
  }
86
97
 
87
98
  export function companyBody(input: CompanyInput): Record<string, unknown> {
@@ -100,5 +111,7 @@ export function companyBody(input: CompanyInput): Record<string, unknown> {
100
111
  summary: input.summary,
101
112
  tags: input.tags,
102
113
  is_own: input.isOwn,
114
+ custom_fields:
115
+ input.customFields === undefined ? undefined : customFieldValuesBody(input.customFields),
103
116
  })
104
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
  }