@kernhq/module-tracker 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 +662 -0
  2. package/README.md +265 -0
  3. package/dist/contract/events.d.ts +140 -0
  4. package/dist/contract/events.d.ts.map +1 -0
  5. package/dist/contract/events.js +51 -0
  6. package/dist/contract/events.js.map +1 -0
  7. package/dist/contract/index.d.ts +6 -0
  8. package/dist/contract/index.d.ts.map +1 -0
  9. package/dist/contract/index.js +6 -0
  10. package/dist/contract/index.js.map +1 -0
  11. package/dist/contract/models.d.ts +2547 -0
  12. package/dist/contract/models.d.ts.map +1 -0
  13. package/dist/contract/models.js +1214 -0
  14. package/dist/contract/models.js.map +1 -0
  15. package/dist/contract/notifications.d.ts +4 -0
  16. package/dist/contract/notifications.d.ts.map +1 -0
  17. package/dist/contract/notifications.js +46 -0
  18. package/dist/contract/notifications.js.map +1 -0
  19. package/dist/contract/permissions.d.ts +161 -0
  20. package/dist/contract/permissions.d.ts.map +1 -0
  21. package/dist/contract/permissions.js +192 -0
  22. package/dist/contract/permissions.js.map +1 -0
  23. package/dist/contract/router.d.ts +9659 -0
  24. package/dist/contract/router.d.ts.map +1 -0
  25. package/dist/contract/router.js +732 -0
  26. package/dist/contract/router.js.map +1 -0
  27. package/dist/kql/ast.d.ts +90 -0
  28. package/dist/kql/ast.d.ts.map +1 -0
  29. package/dist/kql/ast.js +80 -0
  30. package/dist/kql/ast.js.map +1 -0
  31. package/dist/kql/dates.d.ts +16 -0
  32. package/dist/kql/dates.d.ts.map +1 -0
  33. package/dist/kql/dates.js +54 -0
  34. package/dist/kql/dates.js.map +1 -0
  35. package/dist/kql/fields.d.ts +61 -0
  36. package/dist/kql/fields.d.ts.map +1 -0
  37. package/dist/kql/fields.js +106 -0
  38. package/dist/kql/fields.js.map +1 -0
  39. package/dist/kql/index.d.ts +16 -0
  40. package/dist/kql/index.d.ts.map +1 -0
  41. package/dist/kql/index.js +16 -0
  42. package/dist/kql/index.js.map +1 -0
  43. package/dist/kql/lexer.d.ts +28 -0
  44. package/dist/kql/lexer.d.ts.map +1 -0
  45. package/dist/kql/lexer.js +127 -0
  46. package/dist/kql/lexer.js.map +1 -0
  47. package/dist/kql/parser.d.ts +14 -0
  48. package/dist/kql/parser.d.ts.map +1 -0
  49. package/dist/kql/parser.js +290 -0
  50. package/dist/kql/parser.js.map +1 -0
  51. package/dist/kql/suggest.d.ts +8 -0
  52. package/dist/kql/suggest.d.ts.map +1 -0
  53. package/dist/kql/suggest.js +96 -0
  54. package/dist/kql/suggest.js.map +1 -0
  55. package/dist/kql/validate.d.ts +10 -0
  56. package/dist/kql/validate.d.ts.map +1 -0
  57. package/dist/kql/validate.js +84 -0
  58. package/dist/kql/validate.js.map +1 -0
  59. package/dist/server/index.d.ts +17 -0
  60. package/dist/server/index.d.ts.map +1 -0
  61. package/dist/server/index.js +583 -0
  62. package/dist/server/index.js.map +1 -0
  63. package/dist/server/kql/compile.d.ts +49 -0
  64. package/dist/server/kql/compile.d.ts.map +1 -0
  65. package/dist/server/kql/compile.js +372 -0
  66. package/dist/server/kql/compile.js.map +1 -0
  67. package/dist/server/rank.d.ts +23 -0
  68. package/dist/server/rank.d.ts.map +1 -0
  69. package/dist/server/rank.js +66 -0
  70. package/dist/server/rank.js.map +1 -0
  71. package/dist/server/rich.d.ts +30 -0
  72. package/dist/server/rich.d.ts.map +1 -0
  73. package/dist/server/rich.js +97 -0
  74. package/dist/server/rich.js.map +1 -0
  75. package/dist/server/router.d.ts +10589 -0
  76. package/dist/server/router.d.ts.map +1 -0
  77. package/dist/server/router.js +431 -0
  78. package/dist/server/router.js.map +1 -0
  79. package/dist/server/schema.d.ts +6622 -0
  80. package/dist/server/schema.d.ts.map +1 -0
  81. package/dist/server/schema.js +581 -0
  82. package/dist/server/schema.js.map +1 -0
  83. package/dist/server/services/access.d.ts +53 -0
  84. package/dist/server/services/access.d.ts.map +1 -0
  85. package/dist/server/services/access.js +146 -0
  86. package/dist/server/services/access.js.map +1 -0
  87. package/dist/server/services/comments.d.ts +36 -0
  88. package/dist/server/services/comments.d.ts.map +1 -0
  89. package/dist/server/services/comments.js +231 -0
  90. package/dist/server/services/comments.js.map +1 -0
  91. package/dist/server/services/config.d.ts +130 -0
  92. package/dist/server/services/config.d.ts.map +1 -0
  93. package/dist/server/services/config.js +797 -0
  94. package/dist/server/services/config.js.map +1 -0
  95. package/dist/server/services/db.d.ts +93 -0
  96. package/dist/server/services/db.d.ts.map +1 -0
  97. package/dist/server/services/db.js +519 -0
  98. package/dist/server/services/db.js.map +1 -0
  99. package/dist/server/services/imports.d.ts +37 -0
  100. package/dist/server/services/imports.d.ts.map +1 -0
  101. package/dist/server/services/imports.js +324 -0
  102. package/dist/server/services/imports.js.map +1 -0
  103. package/dist/server/services/index.d.ts +36 -0
  104. package/dist/server/services/index.d.ts.map +1 -0
  105. package/dist/server/services/index.js +57 -0
  106. package/dist/server/services/index.js.map +1 -0
  107. package/dist/server/services/intake.d.ts +47 -0
  108. package/dist/server/services/intake.d.ts.map +1 -0
  109. package/dist/server/services/intake.js +268 -0
  110. package/dist/server/services/intake.js.map +1 -0
  111. package/dist/server/services/issues.d.ts +763 -0
  112. package/dist/server/services/issues.d.ts.map +1 -0
  113. package/dist/server/services/issues.js +1381 -0
  114. package/dist/server/services/issues.js.map +1 -0
  115. package/dist/server/services/notify.d.ts +51 -0
  116. package/dist/server/services/notify.d.ts.map +1 -0
  117. package/dist/server/services/notify.js +90 -0
  118. package/dist/server/services/notify.js.map +1 -0
  119. package/dist/server/services/planning.d.ts +62 -0
  120. package/dist/server/services/planning.d.ts.map +1 -0
  121. package/dist/server/services/planning.js +614 -0
  122. package/dist/server/services/planning.js.map +1 -0
  123. package/dist/server/services/projects.d.ts +43 -0
  124. package/dist/server/services/projects.d.ts.map +1 -0
  125. package/dist/server/services/projects.js +375 -0
  126. package/dist/server/services/projects.js.map +1 -0
  127. package/dist/server/services/query.d.ts +24 -0
  128. package/dist/server/services/query.d.ts.map +1 -0
  129. package/dist/server/services/query.js +312 -0
  130. package/dist/server/services/query.js.map +1 -0
  131. package/dist/server/services/reports.d.ts +30 -0
  132. package/dist/server/services/reports.d.ts.map +1 -0
  133. package/dist/server/services/reports.js +295 -0
  134. package/dist/server/services/reports.js.map +1 -0
  135. package/dist/server/services/time.d.ts +53 -0
  136. package/dist/server/services/time.d.ts.map +1 -0
  137. package/dist/server/services/time.js +192 -0
  138. package/dist/server/services/time.js.map +1 -0
  139. package/dist/server/services/transitions.d.ts +61 -0
  140. package/dist/server/services/transitions.d.ts.map +1 -0
  141. package/dist/server/services/transitions.js +493 -0
  142. package/dist/server/services/transitions.js.map +1 -0
  143. package/dist/server/services/views.d.ts +33 -0
  144. package/dist/server/services/views.d.ts.map +1 -0
  145. package/dist/server/services/views.js +195 -0
  146. package/dist/server/services/views.js.map +1 -0
  147. package/migrations/0000_init.sql +529 -0
  148. package/migrations/0001_rls.sql +259 -0
  149. package/migrations/meta/0000_snapshot.json +4140 -0
  150. package/migrations/meta/_journal.json +20 -0
  151. package/package.json +82 -0
  152. package/src/client/api.ts +15 -0
  153. package/src/client/format.ts +164 -0
  154. package/src/client/group.ts +150 -0
  155. package/src/client/index.ts +60 -0
  156. package/src/client/kql.ts +492 -0
  157. package/src/client/rank.ts +87 -0
  158. package/src/client/types.ts +57 -0
@@ -0,0 +1,732 @@
1
+ import { baseContract, Id, PageInput, page, Timestamp, UserId, WorkspaceId } from '@kernhq/contracts';
2
+ import { oc } from '@orpc/contract';
3
+ import { z } from 'zod';
4
+ import { Attachment, AvailableTransition, BulkResult, BurndownReport, CfdReport, Comment, Component, CreatedVsResolvedReport, CreateIssue, CreateProject, CsvMapping, Cycle, DateOnly, FieldDef, FieldScheme, HierarchyRules, ImportJob, ImportSource, IntakeForm, IntakeSubmission, Issue, IssueApproval, IssueHistoryEntry, IssueKey, IssueQueryInput, IssueQueryResult, IssueTemplate, KqlFieldInfo, KqlParseResult, Label, Link, Milestone, Ok, Project, ProjectMember, ProjectRole, ProjectTemplate, RecurringIssue, RelationType, RelationView, RichDoc, StatusHistoryEntry, StatusInfo, TimeReport, Timer, TypeScheme, UpdateIssue, UpdateProject, UpsertComponent, UpsertCycle, UpsertFieldDef, UpsertIssueTemplate, UpsertLabel, UpsertMilestone, UpsertRecurringIssue, UpsertVersion, UpsertView, UpsertWorkflow, UpsertWorkflowScheme, UpsertWorkItemType, UpsertWorklog, VelocityReport, Version, View, Workflow, WorkflowDefinition, WorkflowScheme, WorkItemType, Worklog, } from './models.js';
5
+ const ws = z.object({ workspaceId: WorkspaceId });
6
+ const pr = ws.extend({ projectId: Id });
7
+ const iss = ws.extend({ issueId: Id });
8
+ const t = (...tags) => ({ tags });
9
+ /**
10
+ * oRPC contract of the tracker module, mounted at `/api/tracker`.
11
+ * Every procedure is workspace-scoped unless marked public (intake form endpoints).
12
+ */
13
+ export const trackerContract = {
14
+ // ------------------------------------------------------------------ projects
15
+ projects: {
16
+ list: baseContract
17
+ .route({ method: 'GET', path: '/projects', ...t('projects') })
18
+ .input(ws.extend({ includeArchived: z.boolean().default(false) }))
19
+ .output(z.array(Project)),
20
+ get: baseContract
21
+ .route({ method: 'GET', path: '/projects/{projectId}', ...t('projects') })
22
+ .input(pr)
23
+ .output(Project),
24
+ getByKey: baseContract
25
+ .route({ method: 'GET', path: '/projects/key/{key}', ...t('projects') })
26
+ .input(ws.extend({ key: z.string() }))
27
+ .output(Project),
28
+ create: baseContract
29
+ .route({ method: 'POST', path: '/projects', ...t('projects') })
30
+ .input(ws.extend(CreateProject.shape))
31
+ .output(Project),
32
+ update: baseContract
33
+ .route({ method: 'PATCH', path: '/projects/{projectId}', ...t('projects') })
34
+ .input(pr.extend({ patch: UpdateProject }))
35
+ .output(Project),
36
+ archive: baseContract
37
+ .route({ method: 'POST', path: '/projects/{projectId}/archive', ...t('projects') })
38
+ .input(pr.extend({ archived: z.boolean().default(true) }))
39
+ .output(Project),
40
+ delete: baseContract
41
+ .route({ method: 'DELETE', path: '/projects/{projectId}', ...t('projects') })
42
+ .input(pr)
43
+ .output(Ok),
44
+ /** rotate/enable/disable the public intake token */
45
+ setIntake: baseContract
46
+ .route({ method: 'POST', path: '/projects/{projectId}/intake', ...t('projects', 'intake') })
47
+ .input(pr.extend({ enabled: z.boolean(), rotate: z.boolean().default(false) }))
48
+ .output(z.object({ token: z.string().nullable() })),
49
+ members: {
50
+ list: baseContract
51
+ .route({ method: 'GET', path: '/projects/{projectId}/members', ...t('projects') })
52
+ .input(pr)
53
+ .output(z.array(ProjectMember)),
54
+ add: baseContract
55
+ .route({ method: 'POST', path: '/projects/{projectId}/members', ...t('projects') })
56
+ .input(pr.extend({ userIds: z.array(UserId).min(1).max(200), role: ProjectRole.default('member') }))
57
+ .output(z.array(ProjectMember)),
58
+ remove: baseContract
59
+ .route({ method: 'DELETE', path: '/projects/{projectId}/members/{userId}', ...t('projects') })
60
+ .input(pr.extend({ userId: UserId }))
61
+ .output(Ok),
62
+ setRole: baseContract
63
+ .route({ method: 'PUT', path: '/projects/{projectId}/members/{userId}', ...t('projects') })
64
+ .input(pr.extend({ userId: UserId, role: ProjectRole }))
65
+ .output(ProjectMember),
66
+ },
67
+ templates: {
68
+ list: baseContract
69
+ .route({ method: 'GET', path: '/project-templates', ...t('projects') })
70
+ .input(ws)
71
+ .output(z.array(ProjectTemplate)),
72
+ /** snapshot an existing project (types, workflows, fields, labels, views) as a template */
73
+ saveFromProject: baseContract
74
+ .route({ method: 'POST', path: '/project-templates', ...t('projects') })
75
+ .input(pr.extend({ name: z.string().min(1).max(120), description: z.string().max(1000).optional() }))
76
+ .output(ProjectTemplate),
77
+ delete: baseContract
78
+ .route({ method: 'DELETE', path: '/project-templates/{id}', ...t('projects') })
79
+ .input(ws.extend({ id: Id }))
80
+ .output(Ok),
81
+ },
82
+ },
83
+ // ------------------------------------------------------------ work item types
84
+ types: {
85
+ list: baseContract
86
+ .route({ method: 'GET', path: '/types', ...t('types') })
87
+ .input(ws.extend({ projectId: Id.optional(), includeArchived: z.boolean().default(false) }))
88
+ .output(z.array(WorkItemType)),
89
+ create: baseContract
90
+ .route({ method: 'POST', path: '/types', ...t('types') })
91
+ .input(ws.extend(UpsertWorkItemType.shape))
92
+ .output(WorkItemType),
93
+ update: baseContract
94
+ .route({ method: 'PATCH', path: '/types/{id}', ...t('types') })
95
+ .input(ws.extend({ id: Id, patch: UpsertWorkItemType.partial() }))
96
+ .output(WorkItemType),
97
+ archive: baseContract
98
+ .route({ method: 'POST', path: '/types/{id}/archive', ...t('types') })
99
+ .input(ws.extend({ id: Id, archived: z.boolean().default(true) }))
100
+ .output(WorkItemType),
101
+ hierarchyRules: baseContract
102
+ .route({ method: 'GET', path: '/types/hierarchy-rules', ...t('types') })
103
+ .input(ws)
104
+ .output(HierarchyRules),
105
+ setHierarchyRules: baseContract
106
+ .route({ method: 'PUT', path: '/types/hierarchy-rules', ...t('types') })
107
+ .input(ws.extend({ rules: HierarchyRules }))
108
+ .output(HierarchyRules),
109
+ schemes: {
110
+ list: baseContract
111
+ .route({ method: 'GET', path: '/type-schemes', ...t('types') })
112
+ .input(ws)
113
+ .output(z.array(TypeScheme)),
114
+ create: baseContract
115
+ .route({ method: 'POST', path: '/type-schemes', ...t('types') })
116
+ .input(ws.extend({
117
+ name: z.string().min(1).max(120),
118
+ typeIds: z.array(Id),
119
+ defaultTypeId: Id.nullable().optional(),
120
+ }))
121
+ .output(TypeScheme),
122
+ update: baseContract
123
+ .route({ method: 'PATCH', path: '/type-schemes/{id}', ...t('types') })
124
+ .input(ws.extend({
125
+ id: Id,
126
+ patch: z.object({
127
+ name: z.string().min(1).max(120).optional(),
128
+ typeIds: z.array(Id).optional(),
129
+ defaultTypeId: Id.nullable().optional(),
130
+ }),
131
+ }))
132
+ .output(TypeScheme),
133
+ delete: baseContract
134
+ .route({ method: 'DELETE', path: '/type-schemes/{id}', ...t('types') })
135
+ .input(ws.extend({ id: Id }))
136
+ .output(Ok),
137
+ },
138
+ },
139
+ // -------------------------------------------------------------- custom fields
140
+ fields: {
141
+ list: baseContract
142
+ .route({ method: 'GET', path: '/fields', ...t('fields') })
143
+ .input(ws.extend({ projectId: Id.optional(), includeArchived: z.boolean().default(false) }))
144
+ .output(z.array(FieldDef)),
145
+ create: baseContract
146
+ .route({ method: 'POST', path: '/fields', ...t('fields') })
147
+ .input(ws.extend(UpsertFieldDef.shape))
148
+ .output(FieldDef),
149
+ update: baseContract
150
+ .route({ method: 'PATCH', path: '/fields/{id}', ...t('fields') })
151
+ .input(ws.extend({ id: Id, patch: UpsertFieldDef.partial().omit({ key: true, type: true }) }))
152
+ .output(FieldDef),
153
+ archive: baseContract
154
+ .route({ method: 'POST', path: '/fields/{id}/archive', ...t('fields') })
155
+ .input(ws.extend({ id: Id, archived: z.boolean().default(true) }))
156
+ .output(FieldDef),
157
+ delete: baseContract
158
+ .route({ method: 'DELETE', path: '/fields/{id}', ...t('fields') })
159
+ .input(ws.extend({ id: Id }))
160
+ .output(Ok),
161
+ schemes: {
162
+ list: baseContract
163
+ .route({ method: 'GET', path: '/field-schemes', ...t('fields') })
164
+ .input(ws)
165
+ .output(z.array(FieldScheme)),
166
+ create: baseContract
167
+ .route({ method: 'POST', path: '/field-schemes', ...t('fields') })
168
+ .input(ws.extend({ name: z.string().min(1).max(120), fieldIds: z.array(Id) }))
169
+ .output(FieldScheme),
170
+ update: baseContract
171
+ .route({ method: 'PATCH', path: '/field-schemes/{id}', ...t('fields') })
172
+ .input(ws.extend({
173
+ id: Id,
174
+ patch: z.object({
175
+ name: z.string().min(1).max(120).optional(),
176
+ fieldIds: z.array(Id).optional(),
177
+ }),
178
+ }))
179
+ .output(FieldScheme),
180
+ delete: baseContract
181
+ .route({ method: 'DELETE', path: '/field-schemes/{id}', ...t('fields') })
182
+ .input(ws.extend({ id: Id }))
183
+ .output(Ok),
184
+ },
185
+ },
186
+ // ------------------------------------------------------------------ workflows
187
+ workflows: {
188
+ list: baseContract
189
+ .route({ method: 'GET', path: '/workflows', ...t('workflows') })
190
+ .input(ws.extend({ projectId: Id.optional(), includeArchived: z.boolean().default(false) }))
191
+ .output(z.array(Workflow)),
192
+ get: baseContract
193
+ .route({ method: 'GET', path: '/workflows/{id}', ...t('workflows') })
194
+ .input(ws.extend({ id: Id }))
195
+ .output(Workflow),
196
+ create: baseContract
197
+ .route({ method: 'POST', path: '/workflows', ...t('workflows') })
198
+ .input(ws.extend(UpsertWorkflow.shape))
199
+ .output(Workflow),
200
+ update: baseContract
201
+ .route({ method: 'PATCH', path: '/workflows/{id}', ...t('workflows') })
202
+ .input(ws.extend({ id: Id, patch: UpsertWorkflow.partial() }))
203
+ .output(Workflow),
204
+ archive: baseContract
205
+ .route({ method: 'POST', path: '/workflows/{id}/archive', ...t('workflows') })
206
+ .input(ws.extend({ id: Id, archived: z.boolean().default(true) }))
207
+ .output(Workflow),
208
+ /** validate a definition without saving (structure + rule configs) */
209
+ validate: baseContract
210
+ .route({ method: 'POST', path: '/workflows/validate', ...t('workflows') })
211
+ .input(ws.extend({ definition: z.unknown() }))
212
+ .output(z.object({ ok: z.boolean(), problems: z.array(z.object({ path: z.string(), message: z.string() })) })),
213
+ /** built-in workflow templates (software, kanban, simple) */
214
+ templates: baseContract
215
+ .route({ method: 'GET', path: '/workflows/templates', ...t('workflows') })
216
+ .output(z.array(z.object({ id: z.string(), name: z.string(), definition: WorkflowDefinition }))),
217
+ /** every status of every workflow a project uses (board columns, KQL autocomplete) */
218
+ statuses: baseContract
219
+ .route({ method: 'GET', path: '/workflows/statuses', ...t('workflows') })
220
+ .input(ws.extend({ projectId: Id.optional() }))
221
+ .output(z.array(StatusInfo)),
222
+ schemes: {
223
+ list: baseContract
224
+ .route({ method: 'GET', path: '/workflow-schemes', ...t('workflows') })
225
+ .input(ws)
226
+ .output(z.array(WorkflowScheme)),
227
+ create: baseContract
228
+ .route({ method: 'POST', path: '/workflow-schemes', ...t('workflows') })
229
+ .input(ws.extend(UpsertWorkflowScheme.shape))
230
+ .output(WorkflowScheme),
231
+ update: baseContract
232
+ .route({ method: 'PATCH', path: '/workflow-schemes/{id}', ...t('workflows') })
233
+ .input(ws.extend({ id: Id, patch: UpsertWorkflowScheme.partial() }))
234
+ .output(WorkflowScheme),
235
+ delete: baseContract
236
+ .route({ method: 'DELETE', path: '/workflow-schemes/{id}', ...t('workflows') })
237
+ .input(ws.extend({ id: Id }))
238
+ .output(Ok),
239
+ },
240
+ },
241
+ // --------------------------------------------------------------------- issues
242
+ issues: {
243
+ get: baseContract
244
+ .route({ method: 'GET', path: '/issues/{issueId}', ...t('issues') })
245
+ .input(iss)
246
+ .output(Issue),
247
+ getByKey: baseContract
248
+ .route({ method: 'GET', path: '/issues/key/{key}', ...t('issues') })
249
+ .input(ws.extend({ key: IssueKey }))
250
+ .output(Issue),
251
+ getMany: baseContract
252
+ .route({ method: 'POST', path: '/issues/get-many', ...t('issues') })
253
+ .input(ws.extend({ ids: z.array(Id).max(500) }))
254
+ .output(z.array(Issue)),
255
+ /** KQL-powered listing behind every view/board/report */
256
+ query: baseContract
257
+ .route({ method: 'POST', path: '/issues/query', ...t('issues', 'kql') })
258
+ .input(IssueQueryInput)
259
+ .output(IssueQueryResult),
260
+ create: baseContract
261
+ .route({ method: 'POST', path: '/issues', ...t('issues') })
262
+ .input(ws.extend(CreateIssue.shape))
263
+ .output(Issue),
264
+ update: baseContract
265
+ .route({ method: 'PATCH', path: '/issues/{issueId}', ...t('issues') })
266
+ .input(iss.extend({ patch: UpdateIssue }))
267
+ .output(Issue),
268
+ bulkUpdate: baseContract
269
+ .route({ method: 'POST', path: '/issues/bulk', ...t('issues') })
270
+ .input(ws.extend({ ids: z.array(Id).min(1).max(500), patch: UpdateIssue }))
271
+ .output(BulkResult),
272
+ delete: baseContract
273
+ .route({ method: 'DELETE', path: '/issues/{issueId}', ...t('issues') })
274
+ .input(iss)
275
+ .output(Ok),
276
+ archive: baseContract
277
+ .route({ method: 'POST', path: '/issues/{issueId}/archive', ...t('issues') })
278
+ .input(iss.extend({ archived: z.boolean().default(true) }))
279
+ .output(Issue),
280
+ /** move to another project (re-keys the issue and its sub-items) */
281
+ move: baseContract
282
+ .route({ method: 'POST', path: '/issues/{issueId}/move', ...t('issues') })
283
+ .input(iss.extend({ projectId: Id }))
284
+ .output(Issue),
285
+ /** reorder within rank order; place after and/or before the given issues */
286
+ rank: baseContract
287
+ .route({ method: 'POST', path: '/issues/{issueId}/rank', ...t('issues') })
288
+ .input(iss.extend({ afterId: Id.nullable().optional(), beforeId: Id.nullable().optional() }))
289
+ .output(z.object({ id: Id, rank: z.string() })),
290
+ /** field-level history + status history */
291
+ history: baseContract
292
+ .route({ method: 'GET', path: '/issues/{issueId}/history', ...t('issues') })
293
+ .input(iss.extend(PageInput.shape))
294
+ .output(z.object({
295
+ items: z.array(IssueHistoryEntry),
296
+ statusHistory: z.array(StatusHistoryEntry),
297
+ nextCursor: z.string().nullable(),
298
+ })),
299
+ watchers: {
300
+ add: baseContract
301
+ .route({ method: 'POST', path: '/issues/{issueId}/watchers', ...t('issues') })
302
+ .input(iss.extend({ userId: UserId.optional() }))
303
+ .output(z.object({ watcherIds: z.array(UserId) })),
304
+ remove: baseContract
305
+ .route({ method: 'DELETE', path: '/issues/{issueId}/watchers/{userId}', ...t('issues') })
306
+ .input(iss.extend({ userId: UserId }))
307
+ .output(z.object({ watcherIds: z.array(UserId) })),
308
+ },
309
+ transitions: {
310
+ /** transitions available from the issue's current status, with per-transition blockers */
311
+ available: baseContract
312
+ .route({ method: 'GET', path: '/issues/{issueId}/transitions', ...t('issues', 'workflows') })
313
+ .input(iss)
314
+ .output(z.array(AvailableTransition)),
315
+ apply: baseContract
316
+ .route({
317
+ method: 'POST',
318
+ path: '/issues/{issueId}/transitions/{transitionId}',
319
+ ...t('issues', 'workflows'),
320
+ })
321
+ .input(iss.extend({
322
+ transitionId: z.string(),
323
+ fields: z.record(z.string(), z.unknown()).optional(),
324
+ comment: z.string().max(10000).optional(),
325
+ resolution: z.string().max(64).nullable().optional(),
326
+ }))
327
+ .output(z.object({
328
+ issue: Issue,
329
+ /** set when the transition is now waiting for approval instead of applied */
330
+ approval: IssueApproval.nullable(),
331
+ })),
332
+ },
333
+ approvals: {
334
+ list: baseContract
335
+ .route({ method: 'GET', path: '/issues/{issueId}/approvals', ...t('issues', 'workflows') })
336
+ .input(iss)
337
+ .output(z.array(IssueApproval)),
338
+ decide: baseContract
339
+ .route({
340
+ method: 'POST',
341
+ path: '/issues/{issueId}/approvals/{transitionId}',
342
+ ...t('issues', 'workflows'),
343
+ })
344
+ .input(iss.extend({
345
+ transitionId: z.string(),
346
+ decision: z.enum(['approve', 'reject']),
347
+ comment: z.string().max(2000).optional(),
348
+ }))
349
+ .output(z.object({
350
+ approval: IssueApproval,
351
+ /** when the approval completed and the transition auto-applied */
352
+ issue: Issue.nullable(),
353
+ })),
354
+ },
355
+ comments: {
356
+ list: baseContract
357
+ .route({ method: 'GET', path: '/issues/{issueId}/comments', ...t('comments') })
358
+ .input(iss.extend(PageInput.shape))
359
+ .output(page(Comment)),
360
+ create: baseContract
361
+ .route({ method: 'POST', path: '/issues/{issueId}/comments', ...t('comments') })
362
+ .input(iss.extend({
363
+ body: RichDoc,
364
+ parentId: Id.nullable().optional(),
365
+ internal: z.boolean().default(false),
366
+ }))
367
+ .output(Comment),
368
+ update: baseContract
369
+ .route({ method: 'PATCH', path: '/comments/{commentId}', ...t('comments') })
370
+ .input(ws.extend({ commentId: Id, body: RichDoc }))
371
+ .output(Comment),
372
+ delete: baseContract
373
+ .route({ method: 'DELETE', path: '/comments/{commentId}', ...t('comments') })
374
+ .input(ws.extend({ commentId: Id }))
375
+ .output(Ok),
376
+ /** toggle a reaction */
377
+ react: baseContract
378
+ .route({ method: 'POST', path: '/comments/{commentId}/reactions', ...t('comments') })
379
+ .input(ws.extend({ commentId: Id, emoji: z.string().min(1).max(32) }))
380
+ .output(Comment),
381
+ },
382
+ relations: {
383
+ list: baseContract
384
+ .route({ method: 'GET', path: '/issues/{issueId}/relations', ...t('relations') })
385
+ .input(iss)
386
+ .output(z.array(RelationView)),
387
+ create: baseContract
388
+ .route({ method: 'POST', path: '/issues/{issueId}/relations', ...t('relations') })
389
+ .input(iss.extend({ type: RelationType, targetIssueId: Id }))
390
+ .output(z.array(RelationView)),
391
+ delete: baseContract
392
+ .route({ method: 'DELETE', path: '/relations/{relationId}', ...t('relations') })
393
+ .input(ws.extend({ relationId: Id }))
394
+ .output(Ok),
395
+ },
396
+ attachments: {
397
+ list: baseContract
398
+ .route({ method: 'GET', path: '/issues/{issueId}/attachments', ...t('attachments') })
399
+ .input(iss)
400
+ .output(z.array(Attachment)),
401
+ /** attach an already-uploaded core file */
402
+ add: baseContract
403
+ .route({ method: 'POST', path: '/issues/{issueId}/attachments', ...t('attachments') })
404
+ .input(iss.extend({ fileIds: z.array(Id).min(1).max(50) }))
405
+ .output(z.array(Attachment)),
406
+ remove: baseContract
407
+ .route({ method: 'DELETE', path: '/attachments/{attachmentId}', ...t('attachments') })
408
+ .input(ws.extend({ attachmentId: Id }))
409
+ .output(Ok),
410
+ },
411
+ links: {
412
+ list: baseContract
413
+ .route({ method: 'GET', path: '/issues/{issueId}/links', ...t('links') })
414
+ .input(iss)
415
+ .output(z.array(Link)),
416
+ add: baseContract
417
+ .route({ method: 'POST', path: '/issues/{issueId}/links', ...t('links') })
418
+ .input(iss.extend({
419
+ url: z.string().url(),
420
+ title: z.string().max(300).optional(),
421
+ kind: z.string().max(32).default('generic'),
422
+ }))
423
+ .output(Link),
424
+ remove: baseContract
425
+ .route({ method: 'DELETE', path: '/links/{linkId}', ...t('links') })
426
+ .input(ws.extend({ linkId: Id }))
427
+ .output(Ok),
428
+ },
429
+ templates: {
430
+ list: baseContract
431
+ .route({ method: 'GET', path: '/issue-templates', ...t('templates') })
432
+ .input(ws.extend({ projectId: Id.optional() }))
433
+ .output(z.array(IssueTemplate)),
434
+ create: baseContract
435
+ .route({ method: 'POST', path: '/issue-templates', ...t('templates') })
436
+ .input(ws.extend(UpsertIssueTemplate.shape))
437
+ .output(IssueTemplate),
438
+ update: baseContract
439
+ .route({ method: 'PATCH', path: '/issue-templates/{id}', ...t('templates') })
440
+ .input(ws.extend({ id: Id, patch: UpsertIssueTemplate.partial() }))
441
+ .output(IssueTemplate),
442
+ delete: baseContract
443
+ .route({ method: 'DELETE', path: '/issue-templates/{id}', ...t('templates') })
444
+ .input(ws.extend({ id: Id }))
445
+ .output(Ok),
446
+ },
447
+ recurring: {
448
+ list: baseContract
449
+ .route({ method: 'GET', path: '/recurring', ...t('templates') })
450
+ .input(pr)
451
+ .output(z.array(RecurringIssue)),
452
+ create: baseContract
453
+ .route({ method: 'POST', path: '/recurring', ...t('templates') })
454
+ .input(pr.extend(UpsertRecurringIssue.shape))
455
+ .output(RecurringIssue),
456
+ update: baseContract
457
+ .route({ method: 'PATCH', path: '/recurring/{id}', ...t('templates') })
458
+ .input(ws.extend({ id: Id, patch: UpsertRecurringIssue.partial() }))
459
+ .output(RecurringIssue),
460
+ delete: baseContract
461
+ .route({ method: 'DELETE', path: '/recurring/{id}', ...t('templates') })
462
+ .input(ws.extend({ id: Id }))
463
+ .output(Ok),
464
+ },
465
+ },
466
+ // ------------------------------------------------------------------------ kql
467
+ kql: {
468
+ /** parse + validate a query; returns AST, errors and autocomplete suggestions */
469
+ parse: baseContract
470
+ .route({ method: 'POST', path: '/kql/parse', ...t('kql') })
471
+ .input(ws.extend({ kql: z.string().max(4000), projectIds: z.array(Id).optional() }))
472
+ .output(KqlParseResult),
473
+ /** searchable fields incl. custom fields, with operators and known values */
474
+ fields: baseContract
475
+ .route({ method: 'GET', path: '/kql/fields', ...t('kql') })
476
+ .input(ws.extend({ projectIds: z.array(Id).optional() }))
477
+ .output(z.array(KqlFieldInfo)),
478
+ },
479
+ // ------------------------------------------------------------------- planning
480
+ cycles: {
481
+ list: baseContract
482
+ .route({ method: 'GET', path: '/cycles', ...t('cycles') })
483
+ .input(pr.extend({ status: z.enum(['upcoming', 'active', 'completed']).optional() }))
484
+ .output(z.array(Cycle)),
485
+ get: baseContract
486
+ .route({ method: 'GET', path: '/cycles/{id}', ...t('cycles') })
487
+ .input(ws.extend({ id: Id }))
488
+ .output(Cycle),
489
+ create: baseContract
490
+ .route({ method: 'POST', path: '/cycles', ...t('cycles') })
491
+ .input(pr.extend(UpsertCycle.shape))
492
+ .output(Cycle),
493
+ update: baseContract
494
+ .route({ method: 'PATCH', path: '/cycles/{id}', ...t('cycles') })
495
+ .input(ws.extend({ id: Id, patch: UpsertCycle.partial() }))
496
+ .output(Cycle),
497
+ delete: baseContract
498
+ .route({ method: 'DELETE', path: '/cycles/{id}', ...t('cycles') })
499
+ .input(ws.extend({ id: Id }))
500
+ .output(Ok),
501
+ start: baseContract
502
+ .route({ method: 'POST', path: '/cycles/{id}/start', ...t('cycles') })
503
+ .input(ws.extend({ id: Id }))
504
+ .output(Cycle),
505
+ /** complete; unfinished issues move to `rollTo` (next upcoming by default) or the backlog */
506
+ complete: baseContract
507
+ .route({ method: 'POST', path: '/cycles/{id}/complete', ...t('cycles') })
508
+ .input(ws.extend({ id: Id, rollToCycleId: Id.nullable().optional() }))
509
+ .output(Cycle),
510
+ },
511
+ milestones: {
512
+ list: baseContract
513
+ .route({ method: 'GET', path: '/milestones', ...t('planning') })
514
+ .input(pr)
515
+ .output(z.array(Milestone)),
516
+ create: baseContract
517
+ .route({ method: 'POST', path: '/milestones', ...t('planning') })
518
+ .input(pr.extend(UpsertMilestone.shape))
519
+ .output(Milestone),
520
+ update: baseContract
521
+ .route({ method: 'PATCH', path: '/milestones/{id}', ...t('planning') })
522
+ .input(ws.extend({ id: Id, patch: UpsertMilestone.partial() }))
523
+ .output(Milestone),
524
+ delete: baseContract
525
+ .route({ method: 'DELETE', path: '/milestones/{id}', ...t('planning') })
526
+ .input(ws.extend({ id: Id }))
527
+ .output(Ok),
528
+ },
529
+ versions: {
530
+ list: baseContract
531
+ .route({ method: 'GET', path: '/versions', ...t('planning') })
532
+ .input(pr)
533
+ .output(z.array(Version)),
534
+ create: baseContract
535
+ .route({ method: 'POST', path: '/versions', ...t('planning') })
536
+ .input(pr.extend(UpsertVersion.shape))
537
+ .output(Version),
538
+ update: baseContract
539
+ .route({ method: 'PATCH', path: '/versions/{id}', ...t('planning') })
540
+ .input(ws.extend({ id: Id, patch: UpsertVersion.partial() }))
541
+ .output(Version),
542
+ delete: baseContract
543
+ .route({ method: 'DELETE', path: '/versions/{id}', ...t('planning') })
544
+ .input(ws.extend({ id: Id }))
545
+ .output(Ok),
546
+ release: baseContract
547
+ .route({ method: 'POST', path: '/versions/{id}/release', ...t('planning') })
548
+ .input(ws.extend({ id: Id, released: z.boolean().default(true) }))
549
+ .output(Version),
550
+ },
551
+ components: {
552
+ list: baseContract
553
+ .route({ method: 'GET', path: '/components', ...t('planning') })
554
+ .input(pr)
555
+ .output(z.array(Component)),
556
+ create: baseContract
557
+ .route({ method: 'POST', path: '/components', ...t('planning') })
558
+ .input(pr.extend(UpsertComponent.shape))
559
+ .output(Component),
560
+ update: baseContract
561
+ .route({ method: 'PATCH', path: '/components/{id}', ...t('planning') })
562
+ .input(ws.extend({ id: Id, patch: UpsertComponent.partial() }))
563
+ .output(Component),
564
+ delete: baseContract
565
+ .route({ method: 'DELETE', path: '/components/{id}', ...t('planning') })
566
+ .input(ws.extend({ id: Id }))
567
+ .output(Ok),
568
+ },
569
+ labels: {
570
+ list: baseContract
571
+ .route({ method: 'GET', path: '/labels', ...t('planning') })
572
+ .input(ws.extend({ projectId: Id.optional(), includeArchived: z.boolean().default(false) }))
573
+ .output(z.array(Label)),
574
+ create: baseContract
575
+ .route({ method: 'POST', path: '/labels', ...t('planning') })
576
+ .input(ws.extend(UpsertLabel.shape))
577
+ .output(Label),
578
+ update: baseContract
579
+ .route({ method: 'PATCH', path: '/labels/{id}', ...t('planning') })
580
+ .input(ws.extend({ id: Id, patch: UpsertLabel.partial() }))
581
+ .output(Label),
582
+ delete: baseContract
583
+ .route({ method: 'DELETE', path: '/labels/{id}', ...t('planning') })
584
+ .input(ws.extend({ id: Id }))
585
+ .output(Ok),
586
+ },
587
+ // ---------------------------------------------------------------------- views
588
+ views: {
589
+ list: baseContract
590
+ .route({ method: 'GET', path: '/views', ...t('views') })
591
+ .input(ws.extend({ projectId: Id.nullable().optional() }))
592
+ .output(z.array(View)),
593
+ get: baseContract
594
+ .route({ method: 'GET', path: '/views/{id}', ...t('views') })
595
+ .input(ws.extend({ id: Id }))
596
+ .output(View),
597
+ create: baseContract
598
+ .route({ method: 'POST', path: '/views', ...t('views') })
599
+ .input(ws.extend(UpsertView.shape))
600
+ .output(View),
601
+ update: baseContract
602
+ .route({ method: 'PATCH', path: '/views/{id}', ...t('views') })
603
+ .input(ws.extend({ id: Id, patch: UpsertView.partial() }))
604
+ .output(View),
605
+ delete: baseContract
606
+ .route({ method: 'DELETE', path: '/views/{id}', ...t('views') })
607
+ .input(ws.extend({ id: Id }))
608
+ .output(Ok),
609
+ pin: baseContract
610
+ .route({ method: 'POST', path: '/views/{id}/pin', ...t('views') })
611
+ .input(ws.extend({ id: Id, pinned: z.boolean() }))
612
+ .output(View),
613
+ },
614
+ // ------------------------------------------------------------- intake / triage
615
+ intake: {
616
+ /** PUBLIC: intake form metadata by token (no auth) */
617
+ form: oc
618
+ .route({ method: 'GET', path: '/intake/{token}', ...t('intake') })
619
+ .input(z.object({ token: z.string().min(8) }))
620
+ .output(IntakeForm),
621
+ /** PUBLIC: submit the intake form → triage issue (no auth, honeypot + rate-limited) */
622
+ submit: oc
623
+ .route({ method: 'POST', path: '/intake/{token}', ...t('intake') })
624
+ .input(IntakeSubmission)
625
+ .output(z.object({ ok: z.literal(true), issueKey: IssueKey })),
626
+ },
627
+ triage: {
628
+ /** accept out of triage into the workflow (default: initial non-triage status) */
629
+ accept: baseContract
630
+ .route({ method: 'POST', path: '/triage/{issueId}/accept', ...t('intake') })
631
+ .input(iss.extend({ statusId: z.string().optional() }))
632
+ .output(Issue),
633
+ decline: baseContract
634
+ .route({ method: 'POST', path: '/triage/{issueId}/decline', ...t('intake') })
635
+ .input(iss.extend({ comment: z.string().max(2000).optional() }))
636
+ .output(Issue),
637
+ snooze: baseContract
638
+ .route({ method: 'POST', path: '/triage/{issueId}/snooze', ...t('intake') })
639
+ .input(iss.extend({ until: Timestamp }))
640
+ .output(Issue),
641
+ },
642
+ // -------------------------------------------------------------------- reports
643
+ reports: {
644
+ burndown: baseContract
645
+ .route({ method: 'GET', path: '/reports/burndown/{cycleId}', ...t('reports') })
646
+ .input(ws.extend({ cycleId: Id }))
647
+ .output(BurndownReport),
648
+ velocity: baseContract
649
+ .route({ method: 'GET', path: '/reports/velocity', ...t('reports') })
650
+ .input(pr.extend({ lastN: z.number().int().min(1).max(24).default(6) }))
651
+ .output(VelocityReport),
652
+ cfd: baseContract
653
+ .route({ method: 'GET', path: '/reports/cfd', ...t('reports') })
654
+ .input(pr.extend({ from: DateOnly, to: DateOnly }))
655
+ .output(CfdReport),
656
+ createdVsResolved: baseContract
657
+ .route({ method: 'GET', path: '/reports/created-vs-resolved', ...t('reports') })
658
+ .input(pr.extend({ from: DateOnly, to: DateOnly }))
659
+ .output(CreatedVsResolvedReport),
660
+ time: baseContract
661
+ .route({ method: 'GET', path: '/reports/time', ...t('reports', 'time') })
662
+ .input(ws.extend({
663
+ from: DateOnly,
664
+ to: DateOnly,
665
+ projectId: Id.optional(),
666
+ userId: UserId.optional(),
667
+ billableOnly: z.boolean().default(false),
668
+ }))
669
+ .output(TimeReport),
670
+ },
671
+ // ---------------------------------------------------------------- time tracking
672
+ worklogs: {
673
+ list: baseContract
674
+ .route({ method: 'GET', path: '/issues/{issueId}/worklogs', ...t('time') })
675
+ .input(iss)
676
+ .output(z.array(Worklog)),
677
+ create: baseContract
678
+ .route({ method: 'POST', path: '/issues/{issueId}/worklogs', ...t('time') })
679
+ .input(iss.extend(UpsertWorklog.shape))
680
+ .output(z.object({ worklog: Worklog, issue: Issue })),
681
+ update: baseContract
682
+ .route({ method: 'PATCH', path: '/worklogs/{id}', ...t('time') })
683
+ .input(ws.extend({ id: Id, patch: UpsertWorklog.partial() }))
684
+ .output(Worklog),
685
+ delete: baseContract
686
+ .route({ method: 'DELETE', path: '/worklogs/{id}', ...t('time') })
687
+ .input(ws.extend({ id: Id }))
688
+ .output(Ok),
689
+ timers: {
690
+ /** start a timer on an issue (stops any running timer of the caller first) */
691
+ start: baseContract
692
+ .route({ method: 'POST', path: '/issues/{issueId}/timer', ...t('time') })
693
+ .input(iss.extend({ note: z.string().max(2000).optional() }))
694
+ .output(Timer),
695
+ /** stop the caller's running timer → worklog */
696
+ stop: baseContract
697
+ .route({ method: 'POST', path: '/timer/stop', ...t('time') })
698
+ .input(ws.extend({ discard: z.boolean().default(false) }))
699
+ .output(z.object({ worklog: Worklog.nullable() })),
700
+ current: baseContract
701
+ .route({ method: 'GET', path: '/timer', ...t('time') })
702
+ .input(ws)
703
+ .output(z.object({ timer: Timer.nullable() })),
704
+ },
705
+ },
706
+ // -------------------------------------------------------------------- imports
707
+ imports: {
708
+ /** start an import job (CSV / Jira JSON / Linear JSON export); file must be uploaded to core files first */
709
+ start: baseContract
710
+ .route({ method: 'POST', path: '/imports', ...t('imports') })
711
+ .input(pr.extend({
712
+ source: ImportSource,
713
+ fileId: Id,
714
+ /** CSV: CsvMapping; jira/linear: source-specific options (all optional) */
715
+ mapping: z.union([CsvMapping, z.record(z.string(), z.unknown())]).default({}),
716
+ }))
717
+ .output(ImportJob),
718
+ get: baseContract
719
+ .route({ method: 'GET', path: '/imports/{id}', ...t('imports') })
720
+ .input(ws.extend({ id: Id }))
721
+ .output(ImportJob),
722
+ list: baseContract
723
+ .route({ method: 'GET', path: '/imports', ...t('imports') })
724
+ .input(pr)
725
+ .output(z.array(ImportJob)),
726
+ cancel: baseContract
727
+ .route({ method: 'POST', path: '/imports/{id}/cancel', ...t('imports') })
728
+ .input(ws.extend({ id: Id }))
729
+ .output(ImportJob),
730
+ },
731
+ };
732
+ //# sourceMappingURL=router.js.map