@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
@@ -0,0 +1,307 @@
1
+ import { z } from 'zod'
2
+
3
+ import {
4
+ ACTIVITY_KINDS,
5
+ PIPELINE_KINDS,
6
+ PLAN_ITEM_STATUSES,
7
+ RECORD_TARGET_TYPES,
8
+ } from './values.ts'
9
+ import type { ActivityKind, PipelineKind, PlanItemStatus, RecordTargetType } from './values.ts'
10
+ import { idSchema, nullableTimestampSchema, timestampSchema } from './wire.ts'
11
+
12
+ /**
13
+ * Wire shape for `GET /v1/dashboard`. Read-only, so there is no body builder.
14
+ *
15
+ * One object rather than a list envelope: the workspace home is several
16
+ * questions answered together, and two of them — a contact going cold, a
17
+ * partnership touchpoint at hand — are not questions any resource list asks.
18
+ *
19
+ * Every attention signal is `{ total, items }`. `total` counts every matching
20
+ * row; `items` holds however many the request's `?limit=` allowed. A page
21
+ * showing four rows still reports the true count beside them.
22
+ *
23
+ * Rows that point at another record carry `targetName` already resolved. The
24
+ * dashboard is the one place where every row points somewhere different, so
25
+ * naming them client-side would mean a request per row.
26
+ */
27
+
28
+ /** A row naming a record it is not itself. `targetName` is null when the target no longer resolves. */
29
+ export interface TargetRef {
30
+ readonly targetType: RecordTargetType
31
+ readonly targetId: string
32
+ readonly targetName: string | null
33
+ }
34
+
35
+ const targetRef = {
36
+ target_type: z.enum(RECORD_TARGET_TYPES),
37
+ target_id: idSchema,
38
+ target_name: z.string().nullable(),
39
+ }
40
+
41
+ function toTargetRef(wire: {
42
+ target_type: RecordTargetType
43
+ target_id: string
44
+ target_name: string | null
45
+ }): TargetRef {
46
+ return {
47
+ targetType: wire.target_type,
48
+ targetId: wire.target_id,
49
+ targetName: wire.target_name,
50
+ }
51
+ }
52
+
53
+ export interface DashboardSignal<Item> {
54
+ readonly total: number
55
+ readonly items: readonly Item[]
56
+ }
57
+
58
+ /** `{ total, items }` over one item schema. Generic so each signal declares only its row. */
59
+ function signalSchema<Item>(item: z.ZodType<Item, unknown>): z.ZodType<DashboardSignal<Item>, unknown> {
60
+ return z.object({ total: z.number().int(), items: z.array(item) })
61
+ }
62
+
63
+ export interface DashboardPipeline {
64
+ readonly kind: PipelineKind
65
+ /** Records sitting in a stage the workspace has marked open. */
66
+ readonly open: number
67
+ }
68
+
69
+ const dashboardPipelineSchema: z.ZodType<DashboardPipeline, unknown> = z.object({
70
+ kind: z.enum(PIPELINE_KINDS),
71
+ open: z.number().int(),
72
+ })
73
+
74
+ /**
75
+ * An open plan item that is late or due inside the week.
76
+ *
77
+ * `targetType` is narrower than a `TargetRef`'s in practice — a plan item only
78
+ * attaches to the four pipelines — but it is decoded against the wider set so
79
+ * one target shape serves every signal here.
80
+ */
81
+ export interface DashboardPlanItem extends TargetRef {
82
+ readonly id: string
83
+ /** `YYYY-MM-DD`, per `api.md` date-only fields. */
84
+ readonly date: string
85
+ readonly title: string
86
+ readonly ownerId: string | null
87
+ readonly status: PlanItemStatus
88
+ }
89
+
90
+ const dashboardPlanItemSchema: z.ZodType<DashboardPlanItem, unknown> = z
91
+ .object({
92
+ ...targetRef,
93
+ id: idSchema,
94
+ date: z.string(),
95
+ title: z.string(),
96
+ owner_id: idSchema.nullable(),
97
+ status: z.enum(PLAN_ITEM_STATUSES),
98
+ })
99
+ .transform((wire) => ({
100
+ ...toTargetRef(wire),
101
+ id: wire.id,
102
+ date: wire.date,
103
+ title: wire.title,
104
+ ownerId: wire.owner_id,
105
+ status: wire.status,
106
+ }))
107
+
108
+ export interface DashboardTouchpoint {
109
+ readonly id: string
110
+ readonly name: string
111
+ readonly companyId: string
112
+ /** `YYYY-MM-DD`. */
113
+ readonly nextTouchpoint: string
114
+ /** Whether the date has already passed. Both are in the list, oldest first. */
115
+ readonly overdue: boolean
116
+ readonly ownerId: string | null
117
+ readonly summary: string
118
+ }
119
+
120
+ const dashboardTouchpointSchema: z.ZodType<DashboardTouchpoint, unknown> = z
121
+ .object({
122
+ id: idSchema,
123
+ name: z.string(),
124
+ company_id: idSchema,
125
+ next_touchpoint: z.string(),
126
+ overdue: z.boolean(),
127
+ owner_id: idSchema.nullable(),
128
+ summary: z.string(),
129
+ })
130
+ .transform((wire) => ({
131
+ id: wire.id,
132
+ name: wire.name,
133
+ companyId: wire.company_id,
134
+ nextTouchpoint: wire.next_touchpoint,
135
+ overdue: wire.overdue,
136
+ ownerId: wire.owner_id,
137
+ summary: wire.summary,
138
+ }))
139
+
140
+ export interface DashboardStaleContact {
141
+ readonly id: string
142
+ readonly name: string
143
+ readonly email: string | null
144
+ readonly lastContactedAt: Date
145
+ /** Whole days from the last contact to today, both read in the workspace's zone. */
146
+ readonly daysSinceContact: number
147
+ readonly summary: string
148
+ }
149
+
150
+ const dashboardStaleContactSchema: z.ZodType<DashboardStaleContact, unknown> = z
151
+ .object({
152
+ id: idSchema,
153
+ name: z.string(),
154
+ email: z.string().nullable(),
155
+ last_contacted_at: timestampSchema,
156
+ days_since_contact: z.number().int(),
157
+ summary: z.string(),
158
+ })
159
+ .transform((wire) => ({
160
+ id: wire.id,
161
+ name: wire.name,
162
+ email: wire.email,
163
+ lastContactedAt: wire.last_contacted_at,
164
+ daysSinceContact: wire.days_since_contact,
165
+ summary: wire.summary,
166
+ }))
167
+
168
+ export interface DashboardActivity extends TargetRef {
169
+ readonly id: string
170
+ readonly kind: ActivityKind
171
+ readonly actorMemberId: string | null
172
+ readonly actorLabel: string | null
173
+ readonly action: string
174
+ readonly detail: string | null
175
+ readonly createdAt: Date
176
+ }
177
+
178
+ const dashboardActivitySchema: z.ZodType<DashboardActivity, unknown> = z
179
+ .object({
180
+ ...targetRef,
181
+ id: idSchema,
182
+ kind: z.enum(ACTIVITY_KINDS),
183
+ actor_member_id: idSchema.nullable(),
184
+ actor_label: z.string().nullable(),
185
+ action: z.string(),
186
+ detail: z.string().nullable(),
187
+ created_at: timestampSchema,
188
+ })
189
+ .transform((wire) => ({
190
+ ...toTargetRef(wire),
191
+ id: wire.id,
192
+ kind: wire.kind,
193
+ actorMemberId: wire.actor_member_id,
194
+ actorLabel: wire.actor_label,
195
+ action: wire.action,
196
+ detail: wire.detail,
197
+ createdAt: wire.created_at,
198
+ }))
199
+
200
+ export interface DashboardNote extends TargetRef {
201
+ readonly id: string
202
+ readonly body: string
203
+ readonly authorId: string | null
204
+ readonly pinned: boolean
205
+ readonly createdAt: Date
206
+ }
207
+
208
+ const dashboardNoteSchema: z.ZodType<DashboardNote, unknown> = z
209
+ .object({
210
+ ...targetRef,
211
+ id: idSchema,
212
+ body: z.string(),
213
+ author_id: idSchema.nullable(),
214
+ pinned: z.boolean(),
215
+ created_at: timestampSchema,
216
+ })
217
+ .transform((wire) => ({
218
+ ...toTargetRef(wire),
219
+ id: wire.id,
220
+ body: wire.body,
221
+ authorId: wire.author_id,
222
+ pinned: wire.pinned,
223
+ createdAt: wire.created_at,
224
+ }))
225
+
226
+ export interface DashboardDecision extends TargetRef {
227
+ readonly id: string
228
+ readonly body: string
229
+ readonly rationale: string | null
230
+ readonly decidedAt: Date
231
+ readonly ownerId: string | null
232
+ readonly dueAt: Date | null
233
+ }
234
+
235
+ const dashboardDecisionSchema: z.ZodType<DashboardDecision, unknown> = z
236
+ .object({
237
+ ...targetRef,
238
+ id: idSchema,
239
+ body: z.string(),
240
+ rationale: z.string().nullable(),
241
+ decided_at: timestampSchema,
242
+ owner_id: idSchema.nullable(),
243
+ due_at: nullableTimestampSchema,
244
+ })
245
+ .transform((wire) => ({
246
+ ...toTargetRef(wire),
247
+ id: wire.id,
248
+ body: wire.body,
249
+ rationale: wire.rationale,
250
+ decidedAt: wire.decided_at,
251
+ ownerId: wire.owner_id,
252
+ dueAt: wire.due_at,
253
+ }))
254
+
255
+ export interface Dashboard {
256
+ readonly generatedAt: Date
257
+ /** The day every signal was measured against, `YYYY-MM-DD`. */
258
+ readonly today: string
259
+ /** The workspace's IANA zone, which is what fixed `today`. */
260
+ readonly timezone: string
261
+ /** How long since a contact before it counts as going cold. */
262
+ readonly staleContactDays: number
263
+ /** How far ahead "due soon" looks. */
264
+ readonly upcomingDays: number
265
+ readonly pipelines: readonly DashboardPipeline[]
266
+ readonly overduePlanItems: DashboardSignal<DashboardPlanItem>
267
+ readonly dueSoonPlanItems: DashboardSignal<DashboardPlanItem>
268
+ readonly partnershipTouchpoints: DashboardSignal<DashboardTouchpoint>
269
+ readonly staleContacts: DashboardSignal<DashboardStaleContact>
270
+ readonly recentActivity: readonly DashboardActivity[]
271
+ readonly recentNotes: readonly DashboardNote[]
272
+ readonly recentDecisions: readonly DashboardDecision[]
273
+ }
274
+
275
+ export const dashboardSchema: z.ZodType<Dashboard, unknown> = z
276
+ .object({
277
+ generated_at: timestampSchema,
278
+ today: z.string(),
279
+ timezone: z.string(),
280
+ stale_contact_days: z.number().int(),
281
+ upcoming_days: z.number().int(),
282
+ pipelines: z.array(dashboardPipelineSchema),
283
+ overdue_plan_items: signalSchema(dashboardPlanItemSchema),
284
+ due_soon_plan_items: signalSchema(dashboardPlanItemSchema),
285
+ partnership_touchpoints: signalSchema(dashboardTouchpointSchema),
286
+ stale_contacts: signalSchema(dashboardStaleContactSchema),
287
+ recent_activity: z.array(dashboardActivitySchema),
288
+ recent_notes: z.array(dashboardNoteSchema),
289
+ recent_decisions: z.array(dashboardDecisionSchema),
290
+ })
291
+ .transform(
292
+ (wire): Dashboard => ({
293
+ generatedAt: wire.generated_at,
294
+ today: wire.today,
295
+ timezone: wire.timezone,
296
+ staleContactDays: wire.stale_contact_days,
297
+ upcomingDays: wire.upcoming_days,
298
+ pipelines: wire.pipelines,
299
+ overduePlanItems: wire.overdue_plan_items,
300
+ dueSoonPlanItems: wire.due_soon_plan_items,
301
+ partnershipTouchpoints: wire.partnership_touchpoints,
302
+ staleContacts: wire.stale_contacts,
303
+ recentActivity: wire.recent_activity,
304
+ recentNotes: wire.recent_notes,
305
+ recentDecisions: wire.recent_decisions,
306
+ }),
307
+ )
package/src/deal.ts ADDED
@@ -0,0 +1,102 @@
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/deals`. */
7
+
8
+ export interface Deal extends RecordTimestamps {
9
+ readonly id: string
10
+ readonly name: string
11
+ readonly companyId: string
12
+ readonly stageId: string
13
+ readonly valueCents: number | null
14
+ readonly currency: string | null
15
+ readonly ownerId: string | null
16
+ /** `YYYY-MM-DD`, per `api.md` date-only fields. */
17
+ readonly expectedClose: string | null
18
+ readonly personIds: readonly string[]
19
+ readonly competitors: readonly string[]
20
+ readonly risks: string
21
+ readonly whyWin: string
22
+ readonly summary: string
23
+ readonly tags: readonly string[]
24
+ readonly externalId: string | null
25
+ }
26
+
27
+ export const dealSchema: z.ZodType<Deal, unknown> = z
28
+ .object({
29
+ id: idSchema,
30
+ name: z.string(),
31
+ company_id: idSchema,
32
+ stage_id: idSchema,
33
+ value_cents: z.number().int().nullable(),
34
+ currency: z.string().nullable(),
35
+ owner_id: idSchema.nullable(),
36
+ expected_close: z.string().nullable(),
37
+ person_ids: z.array(idSchema),
38
+ competitors: z.array(z.string()),
39
+ risks: z.string(),
40
+ why_win: z.string(),
41
+ summary: z.string(),
42
+ tags: z.array(z.string()),
43
+ external_id: z.string().nullable(),
44
+ ...recordTimestamps,
45
+ })
46
+ .transform(
47
+ (wire): Deal => ({
48
+ id: wire.id,
49
+ name: wire.name,
50
+ companyId: wire.company_id,
51
+ stageId: wire.stage_id,
52
+ valueCents: wire.value_cents,
53
+ currency: wire.currency,
54
+ ownerId: wire.owner_id,
55
+ expectedClose: wire.expected_close,
56
+ personIds: wire.person_ids,
57
+ competitors: wire.competitors,
58
+ risks: wire.risks,
59
+ whyWin: wire.why_win,
60
+ summary: wire.summary,
61
+ tags: wire.tags,
62
+ externalId: wire.external_id,
63
+ createdAt: wire.created_at,
64
+ updatedAt: wire.updated_at,
65
+ }),
66
+ )
67
+
68
+ export interface DealInput {
69
+ readonly name?: string
70
+ readonly companyId?: string
71
+ readonly stageId?: string
72
+ readonly valueCents?: number | null
73
+ readonly currency?: string | null
74
+ readonly ownerId?: string | null
75
+ readonly expectedClose?: string | null
76
+ readonly personIds?: readonly string[]
77
+ readonly competitors?: readonly string[]
78
+ readonly risks?: string
79
+ readonly whyWin?: string
80
+ readonly summary?: string
81
+ readonly tags?: readonly string[]
82
+ readonly externalId?: string | null
83
+ }
84
+
85
+ export function dealBody(input: DealInput): Record<string, unknown> {
86
+ return definedFields({
87
+ name: input.name,
88
+ company_id: input.companyId,
89
+ stage_id: input.stageId,
90
+ value_cents: input.valueCents,
91
+ currency: input.currency,
92
+ owner_id: input.ownerId,
93
+ expected_close: input.expectedClose,
94
+ person_ids: input.personIds,
95
+ competitors: input.competitors,
96
+ risks: input.risks,
97
+ why_win: input.whyWin,
98
+ summary: input.summary,
99
+ tags: input.tags,
100
+ external_id: input.externalId,
101
+ })
102
+ }
@@ -0,0 +1,106 @@
1
+ import { z } from 'zod'
2
+
3
+ import { RECORD_TARGET_TYPES } from './values.ts'
4
+ import type { RecordTargetType } from './values.ts'
5
+ import {
6
+ definedFields,
7
+ idSchema,
8
+ nullableTimestampSchema,
9
+ recordTimestamps,
10
+ timestampSchema,
11
+ } from './wire.ts'
12
+ import type { RecordTimestamps } from './wire.ts'
13
+
14
+ /**
15
+ * Wire and write shapes for `/v1/decisions`.
16
+ *
17
+ * A decision is what the workspace decided or promised: a commitment agents
18
+ * must not contradict, kept as a queryable record rather than note text.
19
+ *
20
+ * `ownerId` is a workspace member id, null when nobody carries the commitment.
21
+ * `dueAt` is the optional deadline on it; `decidedAt` is when it was made and is
22
+ * never null.
23
+ */
24
+
25
+ export interface Decision extends RecordTimestamps {
26
+ readonly id: string
27
+ readonly targetType: RecordTargetType
28
+ readonly targetId: string
29
+ readonly body: string
30
+ readonly rationale: string | null
31
+ readonly decidedAt: Date
32
+ readonly ownerId: string | null
33
+ readonly dueAt: Date | null
34
+ }
35
+
36
+ export const decisionSchema: z.ZodType<Decision, unknown> = z
37
+ .object({
38
+ id: idSchema,
39
+ target_type: z.enum(RECORD_TARGET_TYPES),
40
+ target_id: idSchema,
41
+ body: z.string(),
42
+ rationale: z.string().nullable(),
43
+ decided_at: timestampSchema,
44
+ owner_id: idSchema.nullable(),
45
+ due_at: nullableTimestampSchema,
46
+ ...recordTimestamps,
47
+ })
48
+ .transform(
49
+ (wire): Decision => ({
50
+ id: wire.id,
51
+ targetType: wire.target_type,
52
+ targetId: wire.target_id,
53
+ body: wire.body,
54
+ rationale: wire.rationale,
55
+ decidedAt: wire.decided_at,
56
+ ownerId: wire.owner_id,
57
+ dueAt: wire.due_at,
58
+ createdAt: wire.created_at,
59
+ updatedAt: wire.updated_at,
60
+ }),
61
+ )
62
+
63
+ export interface CreateDecisionInput {
64
+ readonly targetType: RecordTargetType
65
+ readonly targetId: string
66
+ readonly body: string
67
+ readonly rationale?: string | null
68
+ /** Absent means now: most decisions are recorded as they are made. */
69
+ readonly decidedAt?: Date
70
+ /** Absent means the caller; null means nobody carries it. */
71
+ readonly ownerId?: string | null
72
+ readonly dueAt?: Date | null
73
+ }
74
+
75
+ export function createDecisionBody(input: CreateDecisionInput): Record<string, unknown> {
76
+ return definedFields({
77
+ target_type: input.targetType,
78
+ target_id: input.targetId,
79
+ body: input.body,
80
+ rationale: input.rationale,
81
+ decided_at: input.decidedAt === undefined ? undefined : input.decidedAt.toISOString(),
82
+ owner_id: input.ownerId,
83
+ due_at:
84
+ input.dueAt === undefined ? undefined : input.dueAt === null ? null : input.dueAt.toISOString(),
85
+ })
86
+ }
87
+
88
+ /** The target never moves: re-filing a decision under another record is a delete and a create. */
89
+ export interface DecisionInput {
90
+ readonly body?: string
91
+ readonly rationale?: string | null
92
+ readonly decidedAt?: Date
93
+ readonly ownerId?: string | null
94
+ readonly dueAt?: Date | null
95
+ }
96
+
97
+ export function decisionBody(input: DecisionInput): Record<string, unknown> {
98
+ return definedFields({
99
+ body: input.body,
100
+ rationale: input.rationale,
101
+ decided_at: input.decidedAt === undefined ? undefined : input.decidedAt.toISOString(),
102
+ owner_id: input.ownerId,
103
+ due_at:
104
+ input.dueAt === undefined ? undefined : input.dueAt === null ? null : input.dueAt.toISOString(),
105
+ })
106
+ }