@kelpie/schemas 0.1.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 (158) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +33 -0
  3. package/dist/account.d.ts +79 -0
  4. package/dist/account.d.ts.map +1 -0
  5. package/dist/account.js +66 -0
  6. package/dist/account.js.map +1 -0
  7. package/dist/activity.d.ts +23 -0
  8. package/dist/activity.d.ts.map +1 -0
  9. package/dist/activity.js +27 -0
  10. package/dist/activity.js.map +1 -0
  11. package/dist/agentTask.d.ts +97 -0
  12. package/dist/agentTask.d.ts.map +1 -0
  13. package/dist/agentTask.js +127 -0
  14. package/dist/agentTask.js.map +1 -0
  15. package/dist/candidate.d.ts +39 -0
  16. package/dist/candidate.d.ts.map +1 -0
  17. package/dist/candidate.js +40 -0
  18. package/dist/candidate.js.map +1 -0
  19. package/dist/company.d.ts +38 -0
  20. package/dist/company.d.ts.map +1 -0
  21. package/dist/company.js +57 -0
  22. package/dist/company.js.map +1 -0
  23. package/dist/dashboard.d.ts +112 -0
  24. package/dist/dashboard.d.ts.map +1 -0
  25. package/dist/dashboard.js +165 -0
  26. package/dist/dashboard.js.map +1 -0
  27. package/dist/deal.d.ts +40 -0
  28. package/dist/deal.d.ts.map +1 -0
  29. package/dist/deal.js +59 -0
  30. package/dist/deal.js.map +1 -0
  31. package/dist/decision.d.ts +46 -0
  32. package/dist/decision.d.ts.map +1 -0
  33. package/dist/decision.js +48 -0
  34. package/dist/decision.js.map +1 -0
  35. package/dist/form.d.ts +97 -0
  36. package/dist/form.d.ts.map +1 -0
  37. package/dist/form.js +98 -0
  38. package/dist/form.js.map +1 -0
  39. package/dist/formSubmission.d.ts +47 -0
  40. package/dist/formSubmission.d.ts.map +1 -0
  41. package/dist/formSubmission.js +47 -0
  42. package/dist/formSubmission.js.map +1 -0
  43. package/dist/handbookPage.d.ts +52 -0
  44. package/dist/handbookPage.d.ts.map +1 -0
  45. package/dist/handbookPage.js +56 -0
  46. package/dist/handbookPage.js.map +1 -0
  47. package/dist/importExport.d.ts +143 -0
  48. package/dist/importExport.d.ts.map +1 -0
  49. package/dist/importExport.js +239 -0
  50. package/dist/importExport.js.map +1 -0
  51. package/dist/index.d.ts +75 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +45 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/invite.d.ts +35 -0
  56. package/dist/invite.d.ts.map +1 -0
  57. package/dist/invite.js +31 -0
  58. package/dist/invite.js.map +1 -0
  59. package/dist/mcpTool.d.ts +26 -0
  60. package/dist/mcpTool.d.ts.map +1 -0
  61. package/dist/mcpTool.js +13 -0
  62. package/dist/mcpTool.js.map +1 -0
  63. package/dist/member.d.ts +26 -0
  64. package/dist/member.d.ts.map +1 -0
  65. package/dist/member.js +24 -0
  66. package/dist/member.js.map +1 -0
  67. package/dist/moduleSetting.d.ts +19 -0
  68. package/dist/moduleSetting.d.ts.map +1 -0
  69. package/dist/moduleSetting.js +16 -0
  70. package/dist/moduleSetting.js.map +1 -0
  71. package/dist/note.d.ts +34 -0
  72. package/dist/note.d.ts.map +1 -0
  73. package/dist/note.js +35 -0
  74. package/dist/note.js.map +1 -0
  75. package/dist/opportunity.d.ts +30 -0
  76. package/dist/opportunity.d.ts.map +1 -0
  77. package/dist/opportunity.js +41 -0
  78. package/dist/opportunity.js.map +1 -0
  79. package/dist/partnership.d.ts +36 -0
  80. package/dist/partnership.d.ts.map +1 -0
  81. package/dist/partnership.js +50 -0
  82. package/dist/partnership.js.map +1 -0
  83. package/dist/person.d.ts +45 -0
  84. package/dist/person.d.ts.map +1 -0
  85. package/dist/person.js +60 -0
  86. package/dist/person.js.map +1 -0
  87. package/dist/pipelineStage.d.ts +29 -0
  88. package/dist/pipelineStage.d.ts.map +1 -0
  89. package/dist/pipelineStage.js +38 -0
  90. package/dist/pipelineStage.js.map +1 -0
  91. package/dist/planItem.d.ts +43 -0
  92. package/dist/planItem.d.ts.map +1 -0
  93. package/dist/planItem.js +44 -0
  94. package/dist/planItem.js.map +1 -0
  95. package/dist/position.d.ts +22 -0
  96. package/dist/position.d.ts.map +1 -0
  97. package/dist/position.js +25 -0
  98. package/dist/position.js.map +1 -0
  99. package/dist/raise.d.ts +38 -0
  100. package/dist/raise.d.ts.map +1 -0
  101. package/dist/raise.js +53 -0
  102. package/dist/raise.js.map +1 -0
  103. package/dist/role.d.ts +21 -0
  104. package/dist/role.d.ts.map +1 -0
  105. package/dist/role.js +21 -0
  106. package/dist/role.js.map +1 -0
  107. package/dist/search.d.ts +41 -0
  108. package/dist/search.d.ts.map +1 -0
  109. package/dist/search.js +41 -0
  110. package/dist/search.js.map +1 -0
  111. package/dist/session.d.ts +92 -0
  112. package/dist/session.d.ts.map +1 -0
  113. package/dist/session.js +67 -0
  114. package/dist/session.js.map +1 -0
  115. package/dist/values.d.ts +216 -0
  116. package/dist/values.d.ts.map +1 -0
  117. package/dist/values.js +329 -0
  118. package/dist/values.js.map +1 -0
  119. package/dist/webhook.d.ts +85 -0
  120. package/dist/webhook.d.ts.map +1 -0
  121. package/dist/webhook.js +75 -0
  122. package/dist/webhook.js.map +1 -0
  123. package/dist/wire.d.ts +40 -0
  124. package/dist/wire.d.ts.map +1 -0
  125. package/dist/wire.js +40 -0
  126. package/dist/wire.js.map +1 -0
  127. package/package.json +51 -0
  128. package/src/account.ts +149 -0
  129. package/src/activity.ts +52 -0
  130. package/src/agentTask.ts +254 -0
  131. package/src/candidate.ts +83 -0
  132. package/src/company.ts +98 -0
  133. package/src/dashboard.ts +307 -0
  134. package/src/deal.ts +102 -0
  135. package/src/decision.ts +106 -0
  136. package/src/form.ts +217 -0
  137. package/src/formSubmission.ts +99 -0
  138. package/src/handbookPage.ts +99 -0
  139. package/src/importExport.ts +339 -0
  140. package/src/index.ts +324 -0
  141. package/src/invite.ts +69 -0
  142. package/src/mcpTool.ts +32 -0
  143. package/src/member.ts +53 -0
  144. package/src/moduleSetting.ts +37 -0
  145. package/src/note.ts +73 -0
  146. package/src/opportunity.ts +74 -0
  147. package/src/partnership.ts +89 -0
  148. package/src/person.ts +116 -0
  149. package/src/pipelineStage.ts +71 -0
  150. package/src/planItem.ts +91 -0
  151. package/src/position.ts +50 -0
  152. package/src/raise.ts +94 -0
  153. package/src/role.ts +45 -0
  154. package/src/search.ts +86 -0
  155. package/src/session.ts +177 -0
  156. package/src/values.ts +427 -0
  157. package/src/webhook.ts +164 -0
  158. package/src/wire.ts +51 -0
package/src/invite.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod'
2
+
3
+ import { INVITABLE_ROLES, INVITE_STATUSES } from './values.ts'
4
+ import type { InvitableRole } from './values.ts'
5
+ import { idSchema, timestampSchema } from './wire.ts'
6
+
7
+ /**
8
+ * Wire shapes for `/v1/workspaces/:id/invites`.
9
+ *
10
+ * `status` is derived from `expires_at` rather than stored, so a workspace that
11
+ * nobody has looked at still reports its stale invitations correctly. Nothing
12
+ * sweeps the table.
13
+ *
14
+ * Both write bodies carry `invite_url_template`: the service sends the email and
15
+ * has no idea what URL the caller's browser or agent is reachable at, so the
16
+ * caller supplies the address the token goes into.
17
+ */
18
+
19
+ export interface Invite {
20
+ readonly id: string
21
+ readonly email: string
22
+ readonly role: InvitableRole
23
+ readonly status: (typeof INVITE_STATUSES)[number]
24
+ readonly expiresAt: Date
25
+ readonly createdAt: Date
26
+ }
27
+
28
+ export const inviteSchema: z.ZodType<Invite, unknown> = z
29
+ .object({
30
+ id: idSchema,
31
+ email: z.string(),
32
+ role: z.enum(INVITABLE_ROLES),
33
+ status: z.enum(INVITE_STATUSES),
34
+ expires_at: timestampSchema,
35
+ created_at: timestampSchema,
36
+ })
37
+ .transform(
38
+ (wire): Invite => ({
39
+ id: wire.id,
40
+ email: wire.email,
41
+ role: wire.role,
42
+ status: wire.status,
43
+ expiresAt: wire.expires_at,
44
+ createdAt: wire.created_at,
45
+ }),
46
+ )
47
+
48
+ export interface CreateInviteInput {
49
+ readonly email: string
50
+ readonly role: InvitableRole
51
+ /** An absolute URL containing the literal `{token}`, which the email replaces. */
52
+ readonly inviteUrlTemplate: string
53
+ }
54
+
55
+ export function createInviteBody(input: CreateInviteInput): Record<string, unknown> {
56
+ return {
57
+ email: input.email,
58
+ role: input.role,
59
+ invite_url_template: input.inviteUrlTemplate,
60
+ }
61
+ }
62
+
63
+ export interface ResendInviteInput {
64
+ readonly inviteUrlTemplate: string
65
+ }
66
+
67
+ export function resendInviteBody(input: ResendInviteInput): Record<string, unknown> {
68
+ return { invite_url_template: input.inviteUrlTemplate }
69
+ }
package/src/mcpTool.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Wire shape for `GET /v1/mcp/tools`: what this deployment exposes over MCP.
5
+ *
6
+ * The same listing an MCP client gets from `tools/list`, read with ordinary
7
+ * credentials. A browser cannot call `/mcp` — it takes bearer keys only — so
8
+ * this is how the admin page shows what is actually registered rather than a
9
+ * hand-written copy that drifts.
10
+ */
11
+
12
+ export interface McpTool {
13
+ /** `people_list`, `handbook_pages_get`, `import_commit`. Resource, then verb. */
14
+ readonly name: string
15
+ readonly description: string
16
+ /** JSON Schema for the tool's arguments, as the MCP client sees it. */
17
+ readonly inputSchema: Record<string, unknown>
18
+ }
19
+
20
+ export const mcpToolSchema = z
21
+ .strictObject({
22
+ name: z.string().min(1),
23
+ description: z.string(),
24
+ input_schema: z.record(z.string(), z.unknown()),
25
+ })
26
+ .transform(
27
+ (wire): McpTool => ({
28
+ name: wire.name,
29
+ description: wire.description,
30
+ inputSchema: wire.input_schema,
31
+ }),
32
+ )
package/src/member.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod'
2
+
3
+ import { MEMBER_ROLES } from './values.ts'
4
+ import type { MemberRole } from './values.ts'
5
+ import { idSchema, timestampSchema } from './wire.ts'
6
+
7
+ /**
8
+ * Wire shape for `GET /v1/workspaces/:id/members`.
9
+ *
10
+ * `id` is the membership, not the user. Notes and activities attribute to this
11
+ * id, so it is what a panel keys its name lookup on.
12
+ */
13
+
14
+ export interface Member {
15
+ readonly id: string
16
+ readonly userId: string
17
+ readonly name: string
18
+ readonly email: string
19
+ readonly role: MemberRole
20
+ readonly joinedAt: Date
21
+ }
22
+
23
+ export const memberSchema: z.ZodType<Member, unknown> = z
24
+ .object({
25
+ id: idSchema,
26
+ user_id: idSchema,
27
+ name: z.string(),
28
+ email: z.string(),
29
+ role: z.enum(MEMBER_ROLES),
30
+ joined_at: timestampSchema,
31
+ })
32
+ .transform(
33
+ (wire): Member => ({
34
+ id: wire.id,
35
+ userId: wire.user_id,
36
+ name: wire.name,
37
+ email: wire.email,
38
+ role: wire.role,
39
+ joinedAt: wire.joined_at,
40
+ }),
41
+ )
42
+
43
+ export interface UpdateMemberRoleInput {
44
+ /**
45
+ * `owner` transfers ownership: the workspace holds exactly one, so the caller
46
+ * giving it away becomes an admin in the same write.
47
+ */
48
+ readonly role: MemberRole
49
+ }
50
+
51
+ export function updateMemberRoleBody(input: UpdateMemberRoleInput): Record<string, unknown> {
52
+ return { role: input.role }
53
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Wire shapes for `/v1/workspaces/:id/modules`.
5
+ *
6
+ * No `id` or timestamps: a setting is identified by `module_id`, and a reader
7
+ * only ever needs the current value, not its history.
8
+ */
9
+
10
+ export interface ModuleSetting {
11
+ readonly moduleId: string
12
+ readonly enabled: boolean
13
+ /** True when a deploy-time config file holds this value, not the workspace's own choice. */
14
+ readonly locked: boolean
15
+ }
16
+
17
+ export const moduleSettingSchema: z.ZodType<ModuleSetting, unknown> = z
18
+ .object({
19
+ module_id: z.string(),
20
+ enabled: z.boolean(),
21
+ locked: z.boolean(),
22
+ })
23
+ .transform(
24
+ (wire): ModuleSetting => ({
25
+ moduleId: wire.module_id,
26
+ enabled: wire.enabled,
27
+ locked: wire.locked,
28
+ }),
29
+ )
30
+
31
+ export interface UpdateModuleSettingInput {
32
+ readonly enabled: boolean
33
+ }
34
+
35
+ export function updateModuleSettingBody(input: UpdateModuleSettingInput): Record<string, unknown> {
36
+ return { enabled: input.enabled }
37
+ }
package/src/note.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod'
2
+
3
+ import { RECORD_TARGET_TYPES } from './values.ts'
4
+ import type { RecordTargetType } from './values.ts'
5
+ import { definedFields, idSchema, recordTimestamps } from './wire.ts'
6
+ import type { RecordTimestamps } from './wire.ts'
7
+
8
+ /**
9
+ * Wire and write shapes for `/v1/notes`.
10
+ *
11
+ * `authorId` is a workspace member id, not a user id. It is null when no member
12
+ * was behind the write, which today means a workspace API key. Resolving it to a
13
+ * name is the caller's job: `api.md` has no include-expansion, so a panel joins
14
+ * against the workspace member list it already holds.
15
+ */
16
+
17
+ export interface Note extends RecordTimestamps {
18
+ readonly id: string
19
+ readonly targetType: RecordTargetType
20
+ readonly targetId: string
21
+ readonly body: string
22
+ readonly authorId: string | null
23
+ readonly pinned: boolean
24
+ }
25
+
26
+ export const noteSchema: z.ZodType<Note, unknown> = z
27
+ .object({
28
+ id: idSchema,
29
+ target_type: z.enum(RECORD_TARGET_TYPES),
30
+ target_id: idSchema,
31
+ body: z.string(),
32
+ author_id: idSchema.nullable(),
33
+ pinned: z.boolean(),
34
+ ...recordTimestamps,
35
+ })
36
+ .transform(
37
+ (wire): Note => ({
38
+ id: wire.id,
39
+ targetType: wire.target_type,
40
+ targetId: wire.target_id,
41
+ body: wire.body,
42
+ authorId: wire.author_id,
43
+ pinned: wire.pinned,
44
+ createdAt: wire.created_at,
45
+ updatedAt: wire.updated_at,
46
+ }),
47
+ )
48
+
49
+ export interface CreateNoteInput {
50
+ readonly targetType: RecordTargetType
51
+ readonly targetId: string
52
+ readonly body: string
53
+ readonly pinned?: boolean
54
+ }
55
+
56
+ export function createNoteBody(input: CreateNoteInput): Record<string, unknown> {
57
+ return definedFields({
58
+ target_type: input.targetType,
59
+ target_id: input.targetId,
60
+ body: input.body,
61
+ pinned: input.pinned,
62
+ })
63
+ }
64
+
65
+ /** The target never moves: re-filing a note under another record is a delete and a create. */
66
+ export interface NoteInput {
67
+ readonly body?: string
68
+ readonly pinned?: boolean
69
+ }
70
+
71
+ export function noteBody(input: NoteInput): Record<string, unknown> {
72
+ return definedFields({ body: input.body, pinned: input.pinned })
73
+ }
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod'
2
+
3
+ import { definedFields, idSchema, recordTimestamps } from './wire.ts'
4
+ import type { RecordTimestamps } from './wire.ts'
5
+
6
+ /** Wire and write shapes for `/v1/opportunities`. */
7
+
8
+ export interface Opportunity extends RecordTimestamps {
9
+ readonly id: string
10
+ readonly name: string
11
+ /** Free text ("Grant", "Accelerator", …), not an enum: the seed's own kinds vary by workspace. */
12
+ readonly kind: string
13
+ readonly stageId: string
14
+ /** Nullable: a speaking slot or a grant need not belong to a company on file. */
15
+ readonly companyId: string | null
16
+ readonly ownerId: string | null
17
+ /** `YYYY-MM-DD`, per `api.md` date-only fields. */
18
+ readonly expectedClose: string | null
19
+ readonly summary: string
20
+ readonly tags: readonly string[]
21
+ }
22
+
23
+ export const opportunitySchema: z.ZodType<Opportunity, unknown> = z
24
+ .object({
25
+ id: idSchema,
26
+ name: z.string(),
27
+ kind: z.string(),
28
+ stage_id: idSchema,
29
+ company_id: idSchema.nullable(),
30
+ owner_id: idSchema.nullable(),
31
+ expected_close: z.string().nullable(),
32
+ summary: z.string(),
33
+ tags: z.array(z.string()),
34
+ ...recordTimestamps,
35
+ })
36
+ .transform(
37
+ (wire): Opportunity => ({
38
+ id: wire.id,
39
+ name: wire.name,
40
+ kind: wire.kind,
41
+ stageId: wire.stage_id,
42
+ companyId: wire.company_id,
43
+ ownerId: wire.owner_id,
44
+ expectedClose: wire.expected_close,
45
+ summary: wire.summary,
46
+ tags: wire.tags,
47
+ createdAt: wire.created_at,
48
+ updatedAt: wire.updated_at,
49
+ }),
50
+ )
51
+
52
+ export interface OpportunityInput {
53
+ readonly name?: string
54
+ readonly kind?: string
55
+ readonly stageId?: string
56
+ readonly companyId?: string | null
57
+ readonly ownerId?: string | null
58
+ readonly expectedClose?: string | null
59
+ readonly summary?: string
60
+ readonly tags?: readonly string[]
61
+ }
62
+
63
+ export function opportunityBody(input: OpportunityInput): Record<string, unknown> {
64
+ return definedFields({
65
+ name: input.name,
66
+ kind: input.kind,
67
+ stage_id: input.stageId,
68
+ company_id: input.companyId,
69
+ owner_id: input.ownerId,
70
+ expected_close: input.expectedClose,
71
+ summary: input.summary,
72
+ tags: input.tags,
73
+ })
74
+ }
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod'
2
+
3
+ import { definedFields, idSchema, recordTimestamps } from './wire.ts'
4
+ import type { RecordTimestamps } from './wire.ts'
5
+
6
+ /** Wire and write shapes for `/v1/partnerships`. */
7
+
8
+ export interface Partnership extends RecordTimestamps {
9
+ readonly id: string
10
+ readonly name: string
11
+ /** Required: a partnership is with an organisation, unlike an opportunity. */
12
+ readonly companyId: string
13
+ readonly stageId: string
14
+ /** Free text ("Integration", "Co-marketing", …), not an enum: the seed's own kinds vary by workspace. */
15
+ readonly kind: string
16
+ /** `YYYY-MM-DD`, per `api.md` date-only fields. */
17
+ readonly nextTouchpoint: string | null
18
+ readonly ownerId: string | null
19
+ readonly goals: string
20
+ readonly successLooksLike: string
21
+ readonly personIds: readonly string[]
22
+ readonly summary: string
23
+ readonly tags: readonly string[]
24
+ }
25
+
26
+ export const partnershipSchema: z.ZodType<Partnership, unknown> = z
27
+ .object({
28
+ id: idSchema,
29
+ name: z.string(),
30
+ company_id: idSchema,
31
+ stage_id: idSchema,
32
+ kind: z.string(),
33
+ next_touchpoint: z.string().nullable(),
34
+ owner_id: idSchema.nullable(),
35
+ goals: z.string(),
36
+ success_looks_like: z.string(),
37
+ person_ids: z.array(idSchema),
38
+ summary: z.string(),
39
+ tags: z.array(z.string()),
40
+ ...recordTimestamps,
41
+ })
42
+ .transform(
43
+ (wire): Partnership => ({
44
+ id: wire.id,
45
+ name: wire.name,
46
+ companyId: wire.company_id,
47
+ stageId: wire.stage_id,
48
+ kind: wire.kind,
49
+ nextTouchpoint: wire.next_touchpoint,
50
+ ownerId: wire.owner_id,
51
+ goals: wire.goals,
52
+ successLooksLike: wire.success_looks_like,
53
+ personIds: wire.person_ids,
54
+ summary: wire.summary,
55
+ tags: wire.tags,
56
+ createdAt: wire.created_at,
57
+ updatedAt: wire.updated_at,
58
+ }),
59
+ )
60
+
61
+ export interface PartnershipInput {
62
+ readonly name?: string
63
+ readonly companyId?: string
64
+ readonly stageId?: string
65
+ readonly kind?: string
66
+ readonly nextTouchpoint?: string | null
67
+ readonly ownerId?: string | null
68
+ readonly goals?: string
69
+ readonly successLooksLike?: string
70
+ readonly personIds?: readonly string[]
71
+ readonly summary?: string
72
+ readonly tags?: readonly string[]
73
+ }
74
+
75
+ export function partnershipBody(input: PartnershipInput): Record<string, unknown> {
76
+ return definedFields({
77
+ name: input.name,
78
+ company_id: input.companyId,
79
+ stage_id: input.stageId,
80
+ kind: input.kind,
81
+ next_touchpoint: input.nextTouchpoint,
82
+ owner_id: input.ownerId,
83
+ goals: input.goals,
84
+ success_looks_like: input.successLooksLike,
85
+ person_ids: input.personIds,
86
+ summary: input.summary,
87
+ tags: input.tags,
88
+ })
89
+ }
package/src/person.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { z } from 'zod'
2
+
3
+ import {
4
+ INFLUENCE_LEVELS,
5
+ PREFERRED_CHANNELS,
6
+ RELATIONSHIP_LEVELS,
7
+ SOCIAL_NETWORK_IDS,
8
+ } from './values.ts'
9
+ import type { Influence, PreferredChannel, Relationship, SocialNetworkId } from './values.ts'
10
+ import { definedFields, idSchema, nullableTimestampSchema, recordTimestamps } from './wire.ts'
11
+ import type { RecordTimestamps } from './wire.ts'
12
+
13
+ /** Wire and write shapes for `/v1/people`. Job title is not here: it lives on Position. */
14
+
15
+ export interface SocialProfile {
16
+ readonly network: SocialNetworkId
17
+ readonly url: string
18
+ }
19
+
20
+ export interface Person extends RecordTimestamps {
21
+ readonly id: string
22
+ readonly name: string
23
+ readonly email: string | null
24
+ readonly phones: readonly string[]
25
+ readonly socialProfiles: readonly SocialProfile[]
26
+ readonly timezone: string | null
27
+ readonly location: string | null
28
+ readonly preferredChannel: PreferredChannel
29
+ readonly influence: Influence
30
+ readonly relationship: Relationship
31
+ readonly summary: string
32
+ readonly tags: readonly string[]
33
+ readonly lastContactedAt: Date | null
34
+ }
35
+
36
+ const socialProfileSchema = z.object({
37
+ network: z.enum(SOCIAL_NETWORK_IDS),
38
+ url: z.string(),
39
+ })
40
+
41
+ export const personSchema: z.ZodType<Person, unknown> = z
42
+ .object({
43
+ id: idSchema,
44
+ name: z.string(),
45
+ email: z.string().nullable(),
46
+ phones: z.array(z.string()),
47
+ social_profiles: z.array(socialProfileSchema),
48
+ timezone: z.string().nullable(),
49
+ location: z.string().nullable(),
50
+ preferred_channel: z.enum(PREFERRED_CHANNELS),
51
+ influence: z.enum(INFLUENCE_LEVELS),
52
+ relationship: z.enum(RELATIONSHIP_LEVELS),
53
+ summary: z.string(),
54
+ tags: z.array(z.string()),
55
+ last_contacted_at: nullableTimestampSchema,
56
+ ...recordTimestamps,
57
+ })
58
+ .transform(
59
+ (wire): Person => ({
60
+ id: wire.id,
61
+ name: wire.name,
62
+ email: wire.email,
63
+ phones: wire.phones,
64
+ socialProfiles: wire.social_profiles,
65
+ timezone: wire.timezone,
66
+ location: wire.location,
67
+ preferredChannel: wire.preferred_channel,
68
+ influence: wire.influence,
69
+ relationship: wire.relationship,
70
+ summary: wire.summary,
71
+ tags: wire.tags,
72
+ lastContactedAt: wire.last_contacted_at,
73
+ createdAt: wire.created_at,
74
+ updatedAt: wire.updated_at,
75
+ }),
76
+ )
77
+
78
+ /**
79
+ * A create or update body. Every field is optional because `POST` defaults all
80
+ * but `name` server-side and `PATCH` sends only what changed; `name` is required
81
+ * on create by the route, not by this type.
82
+ */
83
+ export interface PersonInput {
84
+ readonly name?: string
85
+ readonly email?: string | null
86
+ readonly phones?: readonly string[]
87
+ readonly socialProfiles?: readonly SocialProfile[]
88
+ readonly timezone?: string | null
89
+ readonly location?: string | null
90
+ readonly preferredChannel?: PreferredChannel
91
+ readonly influence?: Influence
92
+ readonly relationship?: Relationship
93
+ readonly summary?: string
94
+ readonly tags?: readonly string[]
95
+ readonly lastContactedAt?: Date | null
96
+ }
97
+
98
+ export function personBody(input: PersonInput): Record<string, unknown> {
99
+ return definedFields({
100
+ name: input.name,
101
+ email: input.email,
102
+ phones: input.phones,
103
+ social_profiles: input.socialProfiles,
104
+ timezone: input.timezone,
105
+ location: input.location,
106
+ preferred_channel: input.preferredChannel,
107
+ influence: input.influence,
108
+ relationship: input.relationship,
109
+ summary: input.summary,
110
+ tags: input.tags,
111
+ last_contacted_at:
112
+ input.lastContactedAt === undefined || input.lastContactedAt === null
113
+ ? input.lastContactedAt
114
+ : input.lastContactedAt.toISOString(),
115
+ })
116
+ }
@@ -0,0 +1,71 @@
1
+ import { z } from 'zod'
2
+
3
+ import { PIPELINE_KINDS } from './values.ts'
4
+ import type { PipelineKind } from './values.ts'
5
+ import { definedFields, idSchema, recordTimestamps } from './wire.ts'
6
+ import type { RecordTimestamps } from './wire.ts'
7
+
8
+ /** Wire and write shapes for `/v1/pipeline_stages`. */
9
+
10
+ export interface PipelineStage extends RecordTimestamps {
11
+ readonly id: string
12
+ readonly kind: PipelineKind
13
+ /** The stable import-alias id. Renaming the label never moves it. */
14
+ readonly slug: string
15
+ readonly label: string
16
+ /** When false, hidden from the board's Open scope. */
17
+ readonly open: boolean
18
+ readonly sortOrder: number
19
+ }
20
+
21
+ export const pipelineStageSchema: z.ZodType<PipelineStage, unknown> = z
22
+ .object({
23
+ id: idSchema,
24
+ kind: z.enum(PIPELINE_KINDS),
25
+ slug: z.string(),
26
+ label: z.string(),
27
+ open: z.boolean(),
28
+ sort_order: z.number().int(),
29
+ ...recordTimestamps,
30
+ })
31
+ .transform(
32
+ (wire): PipelineStage => ({
33
+ id: wire.id,
34
+ kind: wire.kind,
35
+ slug: wire.slug,
36
+ label: wire.label,
37
+ open: wire.open,
38
+ sortOrder: wire.sort_order,
39
+ createdAt: wire.created_at,
40
+ updatedAt: wire.updated_at,
41
+ }),
42
+ )
43
+
44
+ export interface CreatePipelineStageInput {
45
+ readonly kind: PipelineKind
46
+ readonly label: string
47
+ readonly open?: boolean
48
+ }
49
+
50
+ export function createPipelineStageBody(input: CreatePipelineStageInput): Record<string, unknown> {
51
+ return definedFields({
52
+ kind: input.kind,
53
+ label: input.label,
54
+ open: input.open,
55
+ })
56
+ }
57
+
58
+ export interface UpdatePipelineStageInput {
59
+ readonly label?: string
60
+ readonly open?: boolean
61
+ /** The stage's new 0-based position on its board. */
62
+ readonly sortOrder?: number
63
+ }
64
+
65
+ export function updatePipelineStageBody(input: UpdatePipelineStageInput): Record<string, unknown> {
66
+ return definedFields({
67
+ label: input.label,
68
+ open: input.open,
69
+ sort_order: input.sortOrder,
70
+ })
71
+ }