@buildinternet/releases-api-types 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -1
- package/src/api-types.ts +268 -304
- package/src/schemas/orgs.ts +91 -0
- package/src/schemas/products.ts +146 -0
- package/src/schemas/shared.ts +80 -0
- package/src/schemas/sources.ts +267 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildinternet/releases-api-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Wire protocol — request/response types for the Releases registry API. Shared by web, workers, and clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"src",
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@buildinternet/releases-core": "^0.18.0",
|
|
21
|
+
"zod": "^4.3.6"
|
|
22
|
+
},
|
|
19
23
|
"publishConfig": {
|
|
20
24
|
"access": "public"
|
|
21
25
|
}
|
package/src/api-types.ts
CHANGED
|
@@ -1,44 +1,132 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared API response types.
|
|
3
|
-
*
|
|
4
|
-
* This is the single source of truth for all API response shapes.
|
|
5
|
-
* Consumed by: web frontend, CLI client, and (optionally) API worker routes.
|
|
2
|
+
* Shared API response types — single source of truth for the wire protocol.
|
|
3
|
+
* Consumed by: web frontend, MCP worker, OSS CLI, and the API worker.
|
|
6
4
|
*/
|
|
7
5
|
|
|
6
|
+
import type { z } from "zod";
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
SourceType,
|
|
10
|
+
SourceDiscovery,
|
|
11
|
+
SourceFetchPriority,
|
|
12
|
+
} from "@buildinternet/releases-core/source-enums";
|
|
13
|
+
import type {
|
|
14
|
+
MediaItemSchema,
|
|
15
|
+
PaginationSchema,
|
|
16
|
+
StatsSchema,
|
|
17
|
+
ErrorResponseSchema,
|
|
18
|
+
ReleaseTypeSchema,
|
|
19
|
+
ReleaseItemSchema,
|
|
20
|
+
ReleaseSummaryItemSchema,
|
|
21
|
+
OverviewPageItemSchema,
|
|
22
|
+
} from "./schemas/shared.js";
|
|
23
|
+
import type {
|
|
24
|
+
OrgListItemSchema,
|
|
25
|
+
OrgListResponseSchema,
|
|
26
|
+
OrgAccountItemSchema,
|
|
27
|
+
OrgAccountsResponseSchema,
|
|
28
|
+
OrgTagsResponseSchema,
|
|
29
|
+
CreateOrgBodySchema,
|
|
30
|
+
UpdateOrgBodySchema,
|
|
31
|
+
OrgDetailSchema,
|
|
32
|
+
} from "./schemas/orgs.js";
|
|
33
|
+
import type {
|
|
34
|
+
SourceListItemSchema,
|
|
35
|
+
SourceWithOrgSchema,
|
|
36
|
+
SourceListResponseSchema,
|
|
37
|
+
SourceListResultSchema,
|
|
38
|
+
SourceDetailSchema,
|
|
39
|
+
SourceMutationResponseSchema,
|
|
40
|
+
SourceOrgRefSchema,
|
|
41
|
+
SourcePatchInputSchema,
|
|
42
|
+
CreateSourceBodySchema,
|
|
43
|
+
ChangelogFileSummarySchema,
|
|
44
|
+
SourceChangelogResponseSchema,
|
|
45
|
+
} from "./schemas/sources.js";
|
|
46
|
+
import type {
|
|
47
|
+
ProductRowSchema,
|
|
48
|
+
ProductListItemSchema,
|
|
49
|
+
ProductListResponseSchema,
|
|
50
|
+
ProductDetailSourceSchema,
|
|
51
|
+
ProductDetailSchema,
|
|
52
|
+
CreateProductBodySchema,
|
|
53
|
+
UpdateProductBodySchema,
|
|
54
|
+
AdoptProductBodySchema,
|
|
55
|
+
ProductAdoptResultSchema,
|
|
56
|
+
ProductAdoptDryRunSchema,
|
|
57
|
+
ProductAdoptResponseSchema,
|
|
58
|
+
ProductDeleteResponseSchema,
|
|
59
|
+
} from "./schemas/products.js";
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
MediaItemSchema,
|
|
63
|
+
PaginationSchema,
|
|
64
|
+
ListResponseSchema,
|
|
65
|
+
StatsSchema,
|
|
66
|
+
ErrorResponseSchema,
|
|
67
|
+
ReleaseTypeSchema,
|
|
68
|
+
ReleaseItemSchema,
|
|
69
|
+
ReleaseSummaryItemSchema,
|
|
70
|
+
OverviewPageItemSchema,
|
|
71
|
+
CategorySchema,
|
|
72
|
+
} from "./schemas/shared.js";
|
|
73
|
+
export {
|
|
74
|
+
OrgListItemSchema,
|
|
75
|
+
OrgListResponseSchema,
|
|
76
|
+
OrgAccountItemSchema,
|
|
77
|
+
OrgAccountsResponseSchema,
|
|
78
|
+
OrgTagsResponseSchema,
|
|
79
|
+
CreateOrgBodySchema,
|
|
80
|
+
UpdateOrgBodySchema,
|
|
81
|
+
OrgDetailSchema,
|
|
82
|
+
} from "./schemas/orgs.js";
|
|
83
|
+
export {
|
|
84
|
+
SourceListItemSchema,
|
|
85
|
+
SourceWithOrgSchema,
|
|
86
|
+
SourceListResponseSchema,
|
|
87
|
+
SourceListResultSchema,
|
|
88
|
+
SourceDetailSchema,
|
|
89
|
+
SourceMutationResponseSchema,
|
|
90
|
+
SourceOrgRefSchema,
|
|
91
|
+
SourcePatchInputSchema,
|
|
92
|
+
CreateSourceBodySchema,
|
|
93
|
+
ChangelogFileSummarySchema,
|
|
94
|
+
SourceChangelogResponseSchema,
|
|
95
|
+
} from "./schemas/sources.js";
|
|
96
|
+
export {
|
|
97
|
+
ProductRowSchema,
|
|
98
|
+
ProductListItemSchema,
|
|
99
|
+
ProductListResponseSchema,
|
|
100
|
+
ProductDetailSourceSchema,
|
|
101
|
+
ProductDetailSchema,
|
|
102
|
+
CreateProductBodySchema,
|
|
103
|
+
UpdateProductBodySchema,
|
|
104
|
+
AdoptProductBodySchema,
|
|
105
|
+
ProductAdoptResultSchema,
|
|
106
|
+
ProductAdoptDryRunSchema,
|
|
107
|
+
ProductAdoptResponseSchema,
|
|
108
|
+
ProductDeleteResponseSchema,
|
|
109
|
+
} from "./schemas/products.js";
|
|
110
|
+
|
|
8
111
|
// ── Media ──
|
|
9
112
|
|
|
10
|
-
export
|
|
11
|
-
type: "image" | "video" | "gif";
|
|
12
|
-
url: string;
|
|
13
|
-
alt?: string;
|
|
14
|
-
r2Url?: string;
|
|
15
|
-
}
|
|
113
|
+
export type MediaItem = z.infer<typeof MediaItemSchema>;
|
|
16
114
|
|
|
17
115
|
// ── Stats ──
|
|
18
116
|
|
|
19
|
-
export
|
|
20
|
-
orgs: number;
|
|
21
|
-
sources: number;
|
|
22
|
-
releases: number;
|
|
23
|
-
products: number;
|
|
24
|
-
}
|
|
117
|
+
export type Stats = z.infer<typeof StatsSchema>;
|
|
25
118
|
|
|
26
119
|
// ── Pagination ──
|
|
27
120
|
|
|
28
|
-
export
|
|
29
|
-
page: number;
|
|
30
|
-
pageSize: number;
|
|
31
|
-
returned: number;
|
|
32
|
-
totalItems?: number;
|
|
33
|
-
totalPages?: number;
|
|
34
|
-
hasMore: boolean;
|
|
35
|
-
}
|
|
121
|
+
export type Pagination = z.infer<typeof PaginationSchema>;
|
|
36
122
|
|
|
37
123
|
export interface ListResponse<T> {
|
|
38
124
|
items: T[];
|
|
39
125
|
pagination: Pagination;
|
|
40
126
|
}
|
|
41
127
|
|
|
128
|
+
export type ErrorResponse = z.infer<typeof ErrorResponseSchema>;
|
|
129
|
+
|
|
42
130
|
// ── Sitemap (bulk URL emission) ──
|
|
43
131
|
|
|
44
132
|
export interface SitemapPayload {
|
|
@@ -49,227 +137,29 @@ export interface SitemapPayload {
|
|
|
49
137
|
|
|
50
138
|
// ── Organizations ──
|
|
51
139
|
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
releaseCount: number;
|
|
60
|
-
recentReleaseCount: number;
|
|
61
|
-
lastActivity: string | null;
|
|
62
|
-
topProducts: string[];
|
|
63
|
-
sparkline: number[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export type OrgListResponse = ListResponse<OrgListItem>;
|
|
140
|
+
export type OrgListItem = z.infer<typeof OrgListItemSchema>;
|
|
141
|
+
export type OrgListResponse = z.infer<typeof OrgListResponseSchema>;
|
|
142
|
+
export type OrgAccountItem = z.infer<typeof OrgAccountItemSchema>;
|
|
143
|
+
export type OrgAccountsResponse = z.infer<typeof OrgAccountsResponseSchema>;
|
|
144
|
+
export type OrgTagsResponse = z.infer<typeof OrgTagsResponseSchema>;
|
|
145
|
+
export type CreateOrgBody = z.infer<typeof CreateOrgBodySchema>;
|
|
146
|
+
export type UpdateOrgBody = z.infer<typeof UpdateOrgBodySchema>;
|
|
67
147
|
|
|
68
|
-
export
|
|
69
|
-
platform: string;
|
|
70
|
-
handle: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type OrgAccountsResponse = ListResponse<OrgAccountItem>;
|
|
74
|
-
export type OrgTagsResponse = ListResponse<string>;
|
|
75
|
-
|
|
76
|
-
export interface OrgDetail {
|
|
77
|
-
id?: string;
|
|
78
|
-
slug: string;
|
|
79
|
-
name: string;
|
|
80
|
-
domain: string | null;
|
|
81
|
-
description?: string | null;
|
|
82
|
-
category?: string | null;
|
|
83
|
-
avatarUrl: string | null;
|
|
84
|
-
tags?: string[];
|
|
85
|
-
sourceCount: number;
|
|
86
|
-
releaseCount: number;
|
|
87
|
-
releasesLast30Days: number;
|
|
88
|
-
avgReleasesPerWeek: number;
|
|
89
|
-
lastFetchedAt: string | null;
|
|
90
|
-
lastPolledAt: string | null;
|
|
91
|
-
trackingSince: string;
|
|
92
|
-
aliases?: string[];
|
|
93
|
-
accounts: OrgAccountItem[];
|
|
94
|
-
products: Array<{
|
|
95
|
-
id: string;
|
|
96
|
-
slug: string;
|
|
97
|
-
name: string;
|
|
98
|
-
url: string | null;
|
|
99
|
-
description: string | null;
|
|
100
|
-
sourceCount: number;
|
|
101
|
-
}>;
|
|
102
|
-
sources: SourceListItem[];
|
|
103
|
-
overview?: OverviewPageItem | null;
|
|
104
|
-
playbook?: { scope: "playbook"; content: string; updatedAt: string } | null;
|
|
105
|
-
}
|
|
148
|
+
export type OrgDetail = z.infer<typeof OrgDetailSchema>;
|
|
106
149
|
|
|
107
150
|
// ── Sources ──
|
|
108
151
|
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
isHidden?: boolean;
|
|
121
|
-
/**
|
|
122
|
-
* How the row was created. Optional on the wire so older API responses
|
|
123
|
-
* (mid-deploy or pinned old workers) degrade gracefully — consumers that
|
|
124
|
-
* see `undefined` should treat it as `"curated"`.
|
|
125
|
-
*/
|
|
126
|
-
discovery?: "curated" | "agent" | "on_demand";
|
|
127
|
-
fetchPriority?: "normal" | "low" | "paused" | null;
|
|
128
|
-
lastFetchedAt?: string | null;
|
|
129
|
-
lastPolledAt?: string | null;
|
|
130
|
-
changeDetectedAt?: string | null;
|
|
131
|
-
consecutiveNoChange?: number | null;
|
|
132
|
-
consecutiveErrors?: number | null;
|
|
133
|
-
nextFetchAfter?: string | null;
|
|
134
|
-
medianGapDays?: number | null;
|
|
135
|
-
lastRetieredAt?: string | null;
|
|
136
|
-
metadata?: string | null;
|
|
137
|
-
productName?: string | null;
|
|
138
|
-
productSlug?: string | null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Canonical shape returned by GET /v1/sources (list) and used by both
|
|
143
|
-
* local-mode queries and the remote API client. Superset of SourceListItem
|
|
144
|
-
* with required fields (id, orgName, orgSlug) that the CLI `list` command needs.
|
|
145
|
-
*/
|
|
146
|
-
export interface SourceWithOrg {
|
|
147
|
-
id: string;
|
|
148
|
-
name: string;
|
|
149
|
-
slug: string;
|
|
150
|
-
type: string;
|
|
151
|
-
url: string;
|
|
152
|
-
orgName: string | null;
|
|
153
|
-
orgSlug: string | null;
|
|
154
|
-
productName: string | null;
|
|
155
|
-
productSlug: string | null;
|
|
156
|
-
isPrimary: boolean;
|
|
157
|
-
isHidden: boolean | null;
|
|
158
|
-
discovery?: "curated" | "agent" | "on_demand";
|
|
159
|
-
metadata: string | null;
|
|
160
|
-
releaseCount: number;
|
|
161
|
-
latestVersion: string | null;
|
|
162
|
-
latestDate: string | null;
|
|
163
|
-
lastFetchedAt: string | null;
|
|
164
|
-
lastPolledAt: string | null;
|
|
165
|
-
fetchPriority: string | null;
|
|
166
|
-
changeDetectedAt: string | null;
|
|
167
|
-
consecutiveNoChange: number | null;
|
|
168
|
-
consecutiveErrors: number | null;
|
|
169
|
-
nextFetchAfter: string | null;
|
|
170
|
-
medianGapDays: number | null;
|
|
171
|
-
lastRetieredAt: string | null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Fields accepted by PATCH /v1/sources/:slug. */
|
|
175
|
-
export interface SourcePatchInput {
|
|
176
|
-
name?: string;
|
|
177
|
-
url?: string;
|
|
178
|
-
type?: string;
|
|
179
|
-
slug?: string;
|
|
180
|
-
metadata?: string;
|
|
181
|
-
orgId?: string | null;
|
|
182
|
-
productId?: string | null;
|
|
183
|
-
lastFetchedAt?: string | null;
|
|
184
|
-
lastContentHash?: string | null;
|
|
185
|
-
fetchPriority?: string;
|
|
186
|
-
consecutiveNoChange?: number;
|
|
187
|
-
consecutiveErrors?: number;
|
|
188
|
-
nextFetchAfter?: string | null;
|
|
189
|
-
isPrimary?: boolean;
|
|
190
|
-
isHidden?: boolean;
|
|
191
|
-
changeDetectedAt?: string | null;
|
|
192
|
-
lastPolledAt?: string | null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Lightweight summary of a changelog file — used for the file index. */
|
|
196
|
-
export interface ChangelogFileSummary {
|
|
197
|
-
path: string;
|
|
198
|
-
filename: string;
|
|
199
|
-
url: string;
|
|
200
|
-
bytes: number;
|
|
201
|
-
fetchedAt: string;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface SourceChangelogResponse {
|
|
205
|
-
path: string;
|
|
206
|
-
filename: string;
|
|
207
|
-
url: string;
|
|
208
|
-
rawUrl: string;
|
|
209
|
-
content: string;
|
|
210
|
-
bytes: number;
|
|
211
|
-
fetchedAt: string;
|
|
212
|
-
/** Character offset of the first character in `content` within the full file. */
|
|
213
|
-
offset: number;
|
|
214
|
-
/** The limit (in chars) that was applied to produce this slice. */
|
|
215
|
-
limit: number;
|
|
216
|
-
/** Next offset to request for the next slice, or null if `content` is the tail. */
|
|
217
|
-
nextOffset: number | null;
|
|
218
|
-
/** Total length of the full file in characters. */
|
|
219
|
-
totalChars: number;
|
|
220
|
-
/** Requested token budget when in token mode (cl100k_base). */
|
|
221
|
-
tokens?: number;
|
|
222
|
-
/** Encoded token count of the returned `content`. Set in token mode. */
|
|
223
|
-
sliceTokens?: number;
|
|
224
|
-
/** Full-file token count (cl100k_base). Always populated. */
|
|
225
|
-
totalTokens: number;
|
|
226
|
-
/** True when the upstream file exceeded the 1MB cap and content was sliced. */
|
|
227
|
-
truncated: boolean;
|
|
228
|
-
/** Byte offset where the file was truncated, or null when not truncated. */
|
|
229
|
-
truncatedAt: number | null;
|
|
230
|
-
/**
|
|
231
|
-
* Index of every changelog file tracked for this source (root plus any
|
|
232
|
-
* discovered per-package files). Always present even for single-file
|
|
233
|
-
* sources so clients can lazily render a file picker.
|
|
234
|
-
*/
|
|
235
|
-
files: ChangelogFileSummary[];
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export interface SourceDetail {
|
|
239
|
-
slug: string;
|
|
240
|
-
name: string;
|
|
241
|
-
type: string;
|
|
242
|
-
url: string;
|
|
243
|
-
/**
|
|
244
|
-
* Hidden sources are reachable by direct URL but excluded from listings,
|
|
245
|
-
* sitemap, and AI features. Set on on-demand lookups and admin-suppressed
|
|
246
|
-
* rows; absent on canonical curated/agent sources.
|
|
247
|
-
*/
|
|
248
|
-
isHidden?: boolean;
|
|
249
|
-
/**
|
|
250
|
-
* Pairs with `isHidden` to distinguish admin-suppressed rows (`curated` /
|
|
251
|
-
* `agent` + hidden) from rows materialized by `/v1/lookups` (`on_demand`).
|
|
252
|
-
* Optional for graceful degradation against older API responses.
|
|
253
|
-
*/
|
|
254
|
-
discovery?: "curated" | "agent" | "on_demand";
|
|
255
|
-
changelogUrl?: string | null;
|
|
256
|
-
hasChangelogFile?: boolean;
|
|
257
|
-
org: { slug: string; name: string } | null;
|
|
258
|
-
releaseCount: number;
|
|
259
|
-
releasesLast30Days: number;
|
|
260
|
-
avgReleasesPerWeek: number;
|
|
261
|
-
latestVersion: string | null;
|
|
262
|
-
latestDate: string | null;
|
|
263
|
-
lastFetchedAt: string | null;
|
|
264
|
-
lastPolledAt: string | null;
|
|
265
|
-
trackingSince: string;
|
|
266
|
-
releases: ReleaseItem[];
|
|
267
|
-
pagination: Pagination;
|
|
268
|
-
summaries: {
|
|
269
|
-
rolling: ReleaseSummaryItem | null;
|
|
270
|
-
monthly: ReleaseSummaryItem[];
|
|
271
|
-
};
|
|
272
|
-
}
|
|
152
|
+
export type SourceListItem = z.infer<typeof SourceListItemSchema>;
|
|
153
|
+
export type SourceWithOrg = z.infer<typeof SourceWithOrgSchema>;
|
|
154
|
+
export type SourceListResponse = z.infer<typeof SourceListResponseSchema>;
|
|
155
|
+
export type SourceListResult = z.infer<typeof SourceListResultSchema>;
|
|
156
|
+
export type SourcePatchInput = z.infer<typeof SourcePatchInputSchema>;
|
|
157
|
+
export type CreateSourceBody = z.infer<typeof CreateSourceBodySchema>;
|
|
158
|
+
export type ChangelogFileSummary = z.infer<typeof ChangelogFileSummarySchema>;
|
|
159
|
+
export type SourceChangelogResponse = z.infer<typeof SourceChangelogResponseSchema>;
|
|
160
|
+
export type SourceDetail = z.infer<typeof SourceDetailSchema>;
|
|
161
|
+
export type SourceMutationResponse = z.infer<typeof SourceMutationResponseSchema>;
|
|
162
|
+
export type SourceOrgRef = z.infer<typeof SourceOrgRefSchema>;
|
|
273
163
|
|
|
274
164
|
// ── Admin telemetry: orgs rollup ──
|
|
275
165
|
|
|
@@ -305,20 +195,9 @@ export interface OrgsRollupResponse extends ListResponse<OrgsRollupRow> {
|
|
|
305
195
|
* Optional on the wire so older API responses (mid-deploy or pinned old workers)
|
|
306
196
|
* degrade gracefully — consumers that see `undefined` should treat it as `"feature"`.
|
|
307
197
|
*/
|
|
308
|
-
export type ReleaseType =
|
|
198
|
+
export type ReleaseType = z.infer<typeof ReleaseTypeSchema>;
|
|
309
199
|
|
|
310
|
-
export
|
|
311
|
-
id?: string;
|
|
312
|
-
version: string | null;
|
|
313
|
-
title: string;
|
|
314
|
-
summary: string;
|
|
315
|
-
content?: string;
|
|
316
|
-
publishedAt: string | null;
|
|
317
|
-
url: string | null;
|
|
318
|
-
media?: MediaItem[];
|
|
319
|
-
/** Release type. See {@link ReleaseType}. */
|
|
320
|
-
type?: ReleaseType;
|
|
321
|
-
}
|
|
200
|
+
export type ReleaseItem = z.infer<typeof ReleaseItemSchema>;
|
|
322
201
|
|
|
323
202
|
export interface ReleaseDetail {
|
|
324
203
|
id: string;
|
|
@@ -352,14 +231,7 @@ export type ReleaseCoverageResponse =
|
|
|
352
231
|
| { role: "coverage"; canonical: ReleaseCoverageRow; covers: [] }
|
|
353
232
|
| { role: "canonical"; canonical: null; covers: ReleaseCoverageRow[] };
|
|
354
233
|
|
|
355
|
-
export
|
|
356
|
-
year?: number | null;
|
|
357
|
-
month?: number | null;
|
|
358
|
-
windowDays?: number | null;
|
|
359
|
-
summary: string;
|
|
360
|
-
releaseCount: number;
|
|
361
|
-
generatedAt: string;
|
|
362
|
-
}
|
|
234
|
+
export type ReleaseSummaryItem = z.infer<typeof ReleaseSummaryItemSchema>;
|
|
363
235
|
|
|
364
236
|
// ── Search ──
|
|
365
237
|
|
|
@@ -497,6 +369,50 @@ export interface SearchChunkHit {
|
|
|
497
369
|
score: number;
|
|
498
370
|
}
|
|
499
371
|
|
|
372
|
+
// ── Lookup by domain (entity resolution) ──
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Returned by GET /v1/lookups/by-domain. Pure resolution: the server
|
|
376
|
+
* normalizes the input domain, exact-matches against
|
|
377
|
+
* `organizations.domain` (primary) and `domain_aliases.domain` (alias for
|
|
378
|
+
* either an org or a product), and returns whatever it finds. Unknown
|
|
379
|
+
* domains 404 — there is no on-demand probing for domains, unlike the
|
|
380
|
+
* GitHub coordinate path.
|
|
381
|
+
*/
|
|
382
|
+
export interface DomainLookupResponse {
|
|
383
|
+
/** Normalized domain that was matched (lowercased, no scheme/path/www). */
|
|
384
|
+
domain: string;
|
|
385
|
+
/**
|
|
386
|
+
* Owning organization, if one exists. `matchedVia` distinguishes a hit
|
|
387
|
+
* on `organizations.domain` (`"primary"`) from an alias hit
|
|
388
|
+
* (`"alias"`) so the UI can render the difference.
|
|
389
|
+
*/
|
|
390
|
+
org: {
|
|
391
|
+
id: string;
|
|
392
|
+
slug: string;
|
|
393
|
+
name: string;
|
|
394
|
+
domain: string | null;
|
|
395
|
+
description: string | null;
|
|
396
|
+
category: string | null;
|
|
397
|
+
avatarUrl: string | null;
|
|
398
|
+
matchedVia: "primary" | "alias";
|
|
399
|
+
} | null;
|
|
400
|
+
/**
|
|
401
|
+
* Products whose alias points at this domain. Multiple are possible
|
|
402
|
+
* when an org owns several products that all set the same alias —
|
|
403
|
+
* unusual, but allowed.
|
|
404
|
+
*/
|
|
405
|
+
products: Array<{
|
|
406
|
+
id: string;
|
|
407
|
+
slug: string;
|
|
408
|
+
name: string;
|
|
409
|
+
orgId: string;
|
|
410
|
+
orgSlug: string;
|
|
411
|
+
orgName: string;
|
|
412
|
+
category: string | null;
|
|
413
|
+
}>;
|
|
414
|
+
}
|
|
415
|
+
|
|
500
416
|
// ── Lookup (on-demand GitHub index) ──
|
|
501
417
|
|
|
502
418
|
export type LookupStatus = "indexed" | "existing" | "empty" | "not_found" | "deferred";
|
|
@@ -535,6 +451,21 @@ export interface LookupResultPayload {
|
|
|
535
451
|
|
|
536
452
|
export interface UnifiedSearchResponse {
|
|
537
453
|
query: string;
|
|
454
|
+
/**
|
|
455
|
+
* Normalized form of `?domain=` when the caller passed it. The server
|
|
456
|
+
* does the normalization once and echoes the canonical form back so
|
|
457
|
+
* clients don't have to re-normalize for analytics or display. Absent
|
|
458
|
+
* when no domain filter was applied.
|
|
459
|
+
*/
|
|
460
|
+
domain?: string;
|
|
461
|
+
/**
|
|
462
|
+
* Outcome of the `?domain=` resolution step. `"matched"` means the
|
|
463
|
+
* domain resolved to a known org and results below are scoped to it.
|
|
464
|
+
* `"not_found"` means the domain didn't match anything; in that case
|
|
465
|
+
* orgs/catalog/releases will all be empty arrays — the caller can
|
|
466
|
+
* distinguish "no hits in scope" from "scope didn't exist."
|
|
467
|
+
*/
|
|
468
|
+
domainStatus?: "matched" | "not_found";
|
|
538
469
|
orgs: SearchOrgHit[];
|
|
539
470
|
/** Products and standalone sources folded into a single list. */
|
|
540
471
|
catalog: SearchCatalogHit[];
|
|
@@ -559,16 +490,7 @@ export interface UnifiedSearchResponse {
|
|
|
559
490
|
|
|
560
491
|
// ── Overview Pages ──
|
|
561
492
|
|
|
562
|
-
export
|
|
563
|
-
scope: "org" | "product";
|
|
564
|
-
orgSlug?: string | null;
|
|
565
|
-
productSlug?: string | null;
|
|
566
|
-
content: string;
|
|
567
|
-
releaseCount: number;
|
|
568
|
-
lastContributingReleaseAt: string | null;
|
|
569
|
-
generatedAt: string;
|
|
570
|
-
updatedAt: string;
|
|
571
|
-
}
|
|
493
|
+
export type OverviewPageItem = z.infer<typeof OverviewPageItemSchema>;
|
|
572
494
|
|
|
573
495
|
/** @deprecated Use OverviewPageItem */
|
|
574
496
|
export type KnowledgePageItem = OverviewPageItem;
|
|
@@ -698,41 +620,29 @@ export interface OrgReleasesResponse {
|
|
|
698
620
|
pagination: { nextCursor: string | null; limit: number };
|
|
699
621
|
}
|
|
700
622
|
|
|
701
|
-
//
|
|
702
|
-
|
|
703
|
-
export interface
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
slug: string;
|
|
707
|
-
orgId: string;
|
|
708
|
-
url: string | null;
|
|
709
|
-
description: string | null;
|
|
710
|
-
category: string | null;
|
|
711
|
-
createdAt: string;
|
|
712
|
-
sourceCount: number;
|
|
623
|
+
// Source-scoped release feed — the source identity is encoded in the URL, so
|
|
624
|
+
// items omit the redundant `source` block carried by the org feed.
|
|
625
|
+
export interface SourceReleasesResponse {
|
|
626
|
+
releases: ReleaseItem[];
|
|
627
|
+
pagination: { nextCursor: string | null; limit: number };
|
|
713
628
|
}
|
|
714
629
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
export interface ProductDetail {
|
|
718
|
-
id: string;
|
|
719
|
-
name: string;
|
|
720
|
-
slug: string;
|
|
721
|
-
orgId: string;
|
|
722
|
-
url: string | null;
|
|
723
|
-
description: string | null;
|
|
724
|
-
category: string | null;
|
|
725
|
-
createdAt: string;
|
|
726
|
-
sources: Array<{ id: string; slug: string; name: string; type: string; url: string }>;
|
|
727
|
-
tags: string[];
|
|
728
|
-
}
|
|
630
|
+
// ── Products ──
|
|
729
631
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
632
|
+
// `Category` lives in @buildinternet/releases-core/categories — import it from
|
|
633
|
+
// there. CategorySchema is re-exported here for OpenAPI / Zod consumers only.
|
|
634
|
+
export type ProductRow = z.infer<typeof ProductRowSchema>;
|
|
635
|
+
export type ProductListItem = z.infer<typeof ProductListItemSchema>;
|
|
636
|
+
export type ProductListResponse = z.infer<typeof ProductListResponseSchema>;
|
|
637
|
+
export type ProductDetailSource = z.infer<typeof ProductDetailSourceSchema>;
|
|
638
|
+
export type ProductDetail = z.infer<typeof ProductDetailSchema>;
|
|
639
|
+
export type CreateProductBody = z.infer<typeof CreateProductBodySchema>;
|
|
640
|
+
export type UpdateProductBody = z.infer<typeof UpdateProductBodySchema>;
|
|
641
|
+
export type AdoptProductBody = z.infer<typeof AdoptProductBodySchema>;
|
|
642
|
+
export type ProductAdoptResult = z.infer<typeof ProductAdoptResultSchema>;
|
|
643
|
+
export type ProductAdoptDryRun = z.infer<typeof ProductAdoptDryRunSchema>;
|
|
644
|
+
export type ProductAdoptResponse = z.infer<typeof ProductAdoptResponseSchema>;
|
|
645
|
+
export type ProductDeleteResponse = z.infer<typeof ProductDeleteResponseSchema>;
|
|
736
646
|
|
|
737
647
|
// ── Taxonomy (categories + tags) ──
|
|
738
648
|
|
|
@@ -877,8 +787,28 @@ export interface Session {
|
|
|
877
787
|
sessionId: string;
|
|
878
788
|
company: string;
|
|
879
789
|
type: "onboard" | "update";
|
|
790
|
+
/**
|
|
791
|
+
* Sub-agent **role** label that ran this session — `"coordinator"` is the
|
|
792
|
+
* parent orchestrator (typically Sonnet) when a multi-agent session
|
|
793
|
+
* delegates, `"sonnet"` / `"haiku"` are direct single-agent runs.
|
|
794
|
+
* Surfaces on the detail GET.
|
|
795
|
+
*
|
|
796
|
+
* This is a logical role label, not the runtime model identifier. The
|
|
797
|
+
* resolved Anthropic model string (e.g. `claude-sonnet-4-6`,
|
|
798
|
+
* `claude-haiku-4-5`) lives on `usage.model` when the session reported
|
|
799
|
+
* one — consult that field for the concrete model.
|
|
800
|
+
*/
|
|
801
|
+
agent?: "sonnet" | "haiku" | "coordinator";
|
|
802
|
+
/** Identifies the client that started this session (e.g. hostname). */
|
|
803
|
+
runner?: string;
|
|
804
|
+
/** Correlation ID for end-to-end tracing across CLI → API → managed agent. */
|
|
805
|
+
correlationId?: string;
|
|
806
|
+
/** Anthropic session ID for linking to console logs. */
|
|
807
|
+
anthropicSessionId?: string;
|
|
880
808
|
status: "running" | "complete" | "error" | "cancelled";
|
|
881
809
|
step?: string;
|
|
810
|
+
sourcesFound?: number;
|
|
811
|
+
sourcesValidated?: number;
|
|
882
812
|
totalSources?: number;
|
|
883
813
|
sourcesFetched?: number;
|
|
884
814
|
releasesFound?: number;
|
|
@@ -895,6 +825,40 @@ export interface Session {
|
|
|
895
825
|
stopReason?: string;
|
|
896
826
|
/** Number of provider `session.error` events observed before terminal. */
|
|
897
827
|
retryCount?: number;
|
|
828
|
+
/** Non-fatal warnings collected during the session. */
|
|
829
|
+
warnings?: string[];
|
|
830
|
+
/**
|
|
831
|
+
* Token usage + estimated cost from the managed-agents session. `estimatedUsd`
|
|
832
|
+
* is a snapshot of Anthropic list prices at session-completion time.
|
|
833
|
+
*/
|
|
834
|
+
usage?: {
|
|
835
|
+
inputTokens?: number;
|
|
836
|
+
outputTokens?: number;
|
|
837
|
+
cacheWriteTokens?: number;
|
|
838
|
+
cacheReadTokens?: number;
|
|
839
|
+
model?: string;
|
|
840
|
+
estimatedUsd?: number;
|
|
841
|
+
};
|
|
842
|
+
/**
|
|
843
|
+
* Final agent-reported state for terminal `onboard` sessions — the JSON
|
|
844
|
+
* blob the agent passed to its `releases_report_state` tool, plus an
|
|
845
|
+
* `agentSessionId` field stitched in server-side. Empty on `update`
|
|
846
|
+
* sessions and pre-report errors. Typed as `Record<string, unknown>`
|
|
847
|
+
* because the shape is owned by the discovery system prompt and may
|
|
848
|
+
* grow new fields without a wire bump; the keys produced today are:
|
|
849
|
+
*
|
|
850
|
+
* - `product` (`string`) — company name
|
|
851
|
+
* - `domain` (`string | null`) — discovered domain
|
|
852
|
+
* - `githubOrg` (`string | null`) — discovered GitHub org
|
|
853
|
+
* - `startedAt`, `updatedAt` (ISO strings)
|
|
854
|
+
* - `status` (e.g. `"awaiting_review"`)
|
|
855
|
+
* - `sources` (`Array<{ url, type, slug, label, confidence,
|
|
856
|
+
* validated, validationError?, releaseCount, releasesFetched,
|
|
857
|
+
* fetched, contentDepth }>`)
|
|
858
|
+
* - `agentSessionId` (`string`) — Anthropic session ID, useful for
|
|
859
|
+
* cross-referencing console logs
|
|
860
|
+
*/
|
|
861
|
+
result?: Record<string, unknown>;
|
|
898
862
|
activeSources?: string[];
|
|
899
863
|
cancelRequested?: boolean;
|
|
900
864
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CategorySchema, ListResponseSchema, OverviewPageItemSchema } from "./shared.js";
|
|
3
|
+
import { SourceListItemSchema } from "./sources.js";
|
|
4
|
+
import { ProductListItemSchema } from "./products.js";
|
|
5
|
+
|
|
6
|
+
export const OrgListItemSchema = z.object({
|
|
7
|
+
id: z.string(),
|
|
8
|
+
slug: z.string(),
|
|
9
|
+
name: z.string(),
|
|
10
|
+
domain: z.string().nullable(),
|
|
11
|
+
description: z.string().nullable(),
|
|
12
|
+
category: CategorySchema.nullable(),
|
|
13
|
+
avatarUrl: z.string().nullable(),
|
|
14
|
+
sourceCount: z.number().int().min(0),
|
|
15
|
+
releaseCount: z.number().int().min(0),
|
|
16
|
+
recentReleaseCount: z.number().int().min(0),
|
|
17
|
+
lastActivity: z.string().nullable(),
|
|
18
|
+
topProducts: z.array(z.string()),
|
|
19
|
+
sparkline: z.array(z.number().int().min(0)).length(30),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const OrgListResponseSchema = ListResponseSchema(OrgListItemSchema);
|
|
23
|
+
|
|
24
|
+
export const OrgAccountItemSchema = z.object({
|
|
25
|
+
platform: z.string(),
|
|
26
|
+
handle: z.string(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const OrgAccountsResponseSchema = ListResponseSchema(OrgAccountItemSchema);
|
|
30
|
+
export const OrgTagsResponseSchema = ListResponseSchema(z.string());
|
|
31
|
+
|
|
32
|
+
export const CreateOrgBodySchema = z.object({
|
|
33
|
+
name: z.string().min(1),
|
|
34
|
+
slug: z.string().optional(),
|
|
35
|
+
domain: z.string().optional(),
|
|
36
|
+
description: z.string().optional(),
|
|
37
|
+
category: CategorySchema.optional(),
|
|
38
|
+
tags: z.array(z.string()).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const UpdateOrgBodySchema = z.object({
|
|
42
|
+
name: z.string().optional(),
|
|
43
|
+
slug: z.string().optional(),
|
|
44
|
+
domain: z.string().nullable().optional(),
|
|
45
|
+
description: z.string().nullable().optional(),
|
|
46
|
+
category: CategorySchema.nullable().optional(),
|
|
47
|
+
tags: z.array(z.string()).optional(),
|
|
48
|
+
aliases: z.array(z.string()).optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Org detail's products query selects a strict subset of `ProductListItem` —
|
|
52
|
+
// no category/orgId/createdAt because the parent already names the org and
|
|
53
|
+
// the org detail UI doesn't surface those columns.
|
|
54
|
+
const OrgDetailProductSchema = ProductListItemSchema.pick({
|
|
55
|
+
id: true,
|
|
56
|
+
slug: true,
|
|
57
|
+
name: true,
|
|
58
|
+
url: true,
|
|
59
|
+
description: true,
|
|
60
|
+
sourceCount: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const OrgDetailPlaybookSchema = z.object({
|
|
64
|
+
scope: z.literal("playbook"),
|
|
65
|
+
content: z.string(),
|
|
66
|
+
updatedAt: z.string(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const OrgDetailSchema = z.object({
|
|
70
|
+
id: z.string().optional(),
|
|
71
|
+
slug: z.string(),
|
|
72
|
+
name: z.string(),
|
|
73
|
+
domain: z.string().nullable(),
|
|
74
|
+
description: z.string().nullable().optional(),
|
|
75
|
+
category: CategorySchema.nullable().optional(),
|
|
76
|
+
avatarUrl: z.string().nullable(),
|
|
77
|
+
tags: z.array(z.string()).optional(),
|
|
78
|
+
sourceCount: z.number().int().min(0),
|
|
79
|
+
releaseCount: z.number().int().min(0),
|
|
80
|
+
releasesLast30Days: z.number().int().min(0),
|
|
81
|
+
avgReleasesPerWeek: z.number(),
|
|
82
|
+
lastFetchedAt: z.string().nullable(),
|
|
83
|
+
lastPolledAt: z.string().nullable(),
|
|
84
|
+
trackingSince: z.string(),
|
|
85
|
+
aliases: z.array(z.string()).optional(),
|
|
86
|
+
accounts: z.array(OrgAccountItemSchema),
|
|
87
|
+
products: z.array(OrgDetailProductSchema),
|
|
88
|
+
sources: z.array(SourceListItemSchema),
|
|
89
|
+
overview: OverviewPageItemSchema.nullable().optional(),
|
|
90
|
+
playbook: OrgDetailPlaybookSchema.nullable().optional(),
|
|
91
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CategorySchema, ListResponseSchema } from "./shared.js";
|
|
3
|
+
import { SourceTypeSchema } from "./sources.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Raw `products` table row, returned by `POST /v1/products` and
|
|
7
|
+
* `PATCH /v1/products/:slug`. The OSS CLI types these responses against
|
|
8
|
+
* `Product` from `@buildinternet/releases-core/schema` (the drizzle row
|
|
9
|
+
* type), so `embeddedAt` and `deletedAt` stay on the wire even though
|
|
10
|
+
* they're internal columns. `deletedAt` is always `null` on these paths
|
|
11
|
+
* (live rows only); `embeddedAt` reflects when the product was last
|
|
12
|
+
* indexed for semantic search.
|
|
13
|
+
*/
|
|
14
|
+
export const ProductRowSchema = z.object({
|
|
15
|
+
id: z.string(),
|
|
16
|
+
name: z.string(),
|
|
17
|
+
slug: z.string(),
|
|
18
|
+
orgId: z.string(),
|
|
19
|
+
url: z.string().nullable(),
|
|
20
|
+
description: z.string().nullable(),
|
|
21
|
+
category: CategorySchema.nullable(),
|
|
22
|
+
createdAt: z.string(),
|
|
23
|
+
embeddedAt: z.string().nullable(),
|
|
24
|
+
deletedAt: z.string().nullable(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Per-product row returned by `GET /v1/products`. Adds `sourceCount` to
|
|
29
|
+
* the row shape but omits the internal `embeddedAt` / `deletedAt` columns
|
|
30
|
+
* (the list handler explicitly selects only the user-facing fields).
|
|
31
|
+
*/
|
|
32
|
+
export const ProductListItemSchema = z.object({
|
|
33
|
+
id: z.string(),
|
|
34
|
+
name: z.string(),
|
|
35
|
+
slug: z.string(),
|
|
36
|
+
orgId: z.string(),
|
|
37
|
+
url: z.string().nullable(),
|
|
38
|
+
description: z.string().nullable(),
|
|
39
|
+
category: CategorySchema.nullable(),
|
|
40
|
+
createdAt: z.string(),
|
|
41
|
+
sourceCount: z.number().int().min(0),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const ProductListResponseSchema = ListResponseSchema(ProductListItemSchema);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Embedded source row returned in `ProductDetail.sources`. The detail
|
|
48
|
+
* handler explicitly selects only this small subset; reuses the shared
|
|
49
|
+
* `SourceTypeSchema` enum so source types stay in one place.
|
|
50
|
+
*/
|
|
51
|
+
export const ProductDetailSourceSchema = z.object({
|
|
52
|
+
id: z.string(),
|
|
53
|
+
slug: z.string(),
|
|
54
|
+
name: z.string(),
|
|
55
|
+
type: SourceTypeSchema,
|
|
56
|
+
url: z.string(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returned by `GET /v1/products/:identifier` (and the org-scoped twin).
|
|
61
|
+
* Spreads the raw product row and adds `sources`, `tags`, `aliases`.
|
|
62
|
+
*/
|
|
63
|
+
export const ProductDetailSchema = ProductRowSchema.extend({
|
|
64
|
+
sources: z.array(ProductDetailSourceSchema),
|
|
65
|
+
tags: z.array(z.string()),
|
|
66
|
+
aliases: z.array(z.string()),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** Body accepted by `POST /v1/products`. */
|
|
70
|
+
export const CreateProductBodySchema = z.object({
|
|
71
|
+
name: z.string().min(1),
|
|
72
|
+
orgId: z.string().optional(),
|
|
73
|
+
orgSlug: z.string().optional(),
|
|
74
|
+
slug: z.string().optional(),
|
|
75
|
+
url: z.string().optional(),
|
|
76
|
+
description: z.string().optional(),
|
|
77
|
+
category: CategorySchema.optional(),
|
|
78
|
+
tags: z.array(z.string()).optional(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** Body accepted by `PATCH /v1/products/:slug`. */
|
|
82
|
+
export const UpdateProductBodySchema = z.object({
|
|
83
|
+
name: z.string().optional(),
|
|
84
|
+
url: z.string().nullable().optional(),
|
|
85
|
+
description: z.string().nullable().optional(),
|
|
86
|
+
category: CategorySchema.nullable().optional(),
|
|
87
|
+
tags: z.array(z.string()).optional(),
|
|
88
|
+
aliases: z.array(z.string()).optional(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Body accepted by `POST /v1/products/adopt`.
|
|
93
|
+
*
|
|
94
|
+
* `mergeInto` (slug or `prod_…` ID) folds the source org into an existing
|
|
95
|
+
* product under `targetOrgSlug` instead of creating a new one. When set,
|
|
96
|
+
* `slug` and `url` must be omitted (the existing product's values stand).
|
|
97
|
+
*/
|
|
98
|
+
export const AdoptProductBodySchema = z.object({
|
|
99
|
+
sourceOrgSlug: z.string().min(1),
|
|
100
|
+
targetOrgSlug: z.string().min(1),
|
|
101
|
+
slug: z.string().optional(),
|
|
102
|
+
url: z.string().optional(),
|
|
103
|
+
mergeInto: z.string().optional(),
|
|
104
|
+
dryRun: z.boolean().optional(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Live (non-dryRun) result from `POST /v1/products/adopt`.
|
|
109
|
+
*
|
|
110
|
+
* `mergedInto` is set when the request used `mergeInto` — its value is the
|
|
111
|
+
* existing product's slug, signalling that no new product was created.
|
|
112
|
+
*/
|
|
113
|
+
export const ProductAdoptResultSchema = z.object({
|
|
114
|
+
product: ProductRowSchema,
|
|
115
|
+
mergedInto: z.string().optional(),
|
|
116
|
+
sourcesMoved: z.number().int().min(0),
|
|
117
|
+
accountsMoved: z.number().int().min(0),
|
|
118
|
+
sourceOrgDeleted: z.string(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/** Dry-run preview from `POST /v1/products/adopt` with `dryRun: true`. */
|
|
122
|
+
export const ProductAdoptDryRunSchema = z.object({
|
|
123
|
+
dryRun: z.literal(true),
|
|
124
|
+
mergeInto: z.string().optional(),
|
|
125
|
+
product: z.object({
|
|
126
|
+
name: z.string(),
|
|
127
|
+
slug: z.string(),
|
|
128
|
+
url: z.string().nullable(),
|
|
129
|
+
orgSlug: z.string(),
|
|
130
|
+
}),
|
|
131
|
+
sourcesToMove: z.array(z.string()),
|
|
132
|
+
sourceOrgToDelete: z.string(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/** Response shape returned by `POST /v1/products/adopt` (union of live + dry-run). */
|
|
136
|
+
export const ProductAdoptResponseSchema = z.union([
|
|
137
|
+
ProductAdoptResultSchema,
|
|
138
|
+
ProductAdoptDryRunSchema,
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
/** Response shape returned by `DELETE /v1/products/:identifier`. */
|
|
142
|
+
export const ProductDeleteResponseSchema = z.object({
|
|
143
|
+
deleted: z.literal(true),
|
|
144
|
+
hard: z.literal(true).optional(),
|
|
145
|
+
deletedAt: z.string().optional(),
|
|
146
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { RELEASE_TYPES } from "@buildinternet/releases-core/schema";
|
|
3
|
+
import { CATEGORIES } from "@buildinternet/releases-core/categories";
|
|
4
|
+
|
|
5
|
+
export const CategorySchema = z.enum(CATEGORIES);
|
|
6
|
+
|
|
7
|
+
export const MediaItemSchema = z.object({
|
|
8
|
+
type: z.enum(["image", "video", "gif"]),
|
|
9
|
+
url: z.string(),
|
|
10
|
+
alt: z.string().optional(),
|
|
11
|
+
r2Url: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const PaginationSchema = z.object({
|
|
15
|
+
page: z.number().int().min(1),
|
|
16
|
+
pageSize: z.number().int().min(1),
|
|
17
|
+
returned: z.number().int().min(0),
|
|
18
|
+
totalItems: z.number().int().min(0).optional(),
|
|
19
|
+
totalPages: z.number().int().min(0).optional(),
|
|
20
|
+
hasMore: z.boolean(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const ListResponseSchema = <T extends z.ZodTypeAny>(item: T) =>
|
|
24
|
+
z.object({
|
|
25
|
+
items: z.array(item),
|
|
26
|
+
pagination: PaginationSchema,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const ErrorResponseSchema = z.object({
|
|
30
|
+
error: z.string(),
|
|
31
|
+
message: z.string(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const StatsSchema = z.object({
|
|
35
|
+
orgs: z.number().int().min(0),
|
|
36
|
+
sources: z.number().int().min(0),
|
|
37
|
+
releases: z.number().int().min(0),
|
|
38
|
+
products: z.number().int().min(0),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const ReleaseTypeSchema = z.enum(RELEASE_TYPES);
|
|
42
|
+
|
|
43
|
+
export const ReleaseItemSchema = z.object({
|
|
44
|
+
id: z.string().optional(),
|
|
45
|
+
version: z.string().nullable(),
|
|
46
|
+
title: z.string(),
|
|
47
|
+
summary: z.string(),
|
|
48
|
+
content: z.string().optional(),
|
|
49
|
+
publishedAt: z.string().nullable(),
|
|
50
|
+
url: z.string().nullable(),
|
|
51
|
+
media: z.array(MediaItemSchema).optional(),
|
|
52
|
+
// `.optional()` — older API responses (mid-deploy or pinned old workers)
|
|
53
|
+
// may omit `type`; consumers should treat `undefined` as `"feature"`.
|
|
54
|
+
type: ReleaseTypeSchema.optional(),
|
|
55
|
+
// Pre-release flag (alpha/beta/rc/preview/nightly). Computed at ingest —
|
|
56
|
+
// GitHub uses the API's authoritative `prerelease` field; other adapters
|
|
57
|
+
// fall back to a SemVer-prerelease regex. `.optional()` for the same
|
|
58
|
+
// older-response degrade-gracefully reason as `type`.
|
|
59
|
+
prerelease: z.boolean().optional(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const ReleaseSummaryItemSchema = z.object({
|
|
63
|
+
year: z.number().int().nullable().optional(),
|
|
64
|
+
month: z.number().int().nullable().optional(),
|
|
65
|
+
windowDays: z.number().int().nullable().optional(),
|
|
66
|
+
summary: z.string(),
|
|
67
|
+
releaseCount: z.number().int().min(0),
|
|
68
|
+
generatedAt: z.string(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const OverviewPageItemSchema = z.object({
|
|
72
|
+
scope: z.enum(["org", "product"]),
|
|
73
|
+
orgSlug: z.string().nullable().optional(),
|
|
74
|
+
productSlug: z.string().nullable().optional(),
|
|
75
|
+
content: z.string(),
|
|
76
|
+
releaseCount: z.number().int().min(0),
|
|
77
|
+
lastContributingReleaseAt: z.string().nullable(),
|
|
78
|
+
generatedAt: z.string(),
|
|
79
|
+
updatedAt: z.string(),
|
|
80
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
SOURCE_TYPES,
|
|
4
|
+
SOURCE_DISCOVERY,
|
|
5
|
+
SOURCE_FETCH_PRIORITIES,
|
|
6
|
+
} from "@buildinternet/releases-core/source-enums";
|
|
7
|
+
import {
|
|
8
|
+
ListResponseSchema,
|
|
9
|
+
PaginationSchema,
|
|
10
|
+
ReleaseItemSchema,
|
|
11
|
+
ReleaseSummaryItemSchema,
|
|
12
|
+
} from "./shared.js";
|
|
13
|
+
|
|
14
|
+
export const SourceTypeSchema = z.enum(SOURCE_TYPES);
|
|
15
|
+
export const SourceDiscoverySchema = z.enum(SOURCE_DISCOVERY);
|
|
16
|
+
export const SourceFetchPrioritySchema = z.enum(SOURCE_FETCH_PRIORITIES);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Per-source row used in list/embedded contexts (e.g. nested inside OrgDetail).
|
|
20
|
+
* Most fields are optional — embedded shapes (org detail) only carry the
|
|
21
|
+
* subset needed for that view.
|
|
22
|
+
*/
|
|
23
|
+
export const SourceListItemSchema = z.object({
|
|
24
|
+
id: z.string().optional(),
|
|
25
|
+
slug: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
type: SourceTypeSchema,
|
|
28
|
+
url: z.string().optional(),
|
|
29
|
+
// `orgSlug` is populated on the standalone `/v1/sources` list path; the
|
|
30
|
+
// `OrgDetail.sources` embedded path omits it (the parent already names the org).
|
|
31
|
+
orgSlug: z.string().nullable().optional(),
|
|
32
|
+
releaseCount: z.number().int().min(0),
|
|
33
|
+
latestVersion: z.string().nullable(),
|
|
34
|
+
latestDate: z.string().nullable(),
|
|
35
|
+
latestAddedAt: z.string().nullable().optional(),
|
|
36
|
+
isPrimary: z.boolean().optional(),
|
|
37
|
+
isHidden: z.boolean().optional(),
|
|
38
|
+
/**
|
|
39
|
+
* How the row was created. Optional on the wire so older API responses
|
|
40
|
+
* (mid-deploy or pinned old workers) degrade gracefully — consumers that
|
|
41
|
+
* see `undefined` should treat it as `"curated"`.
|
|
42
|
+
*/
|
|
43
|
+
discovery: SourceDiscoverySchema.optional(),
|
|
44
|
+
fetchPriority: SourceFetchPrioritySchema.nullable().optional(),
|
|
45
|
+
lastFetchedAt: z.string().nullable().optional(),
|
|
46
|
+
lastPolledAt: z.string().nullable().optional(),
|
|
47
|
+
changeDetectedAt: z.string().nullable().optional(),
|
|
48
|
+
consecutiveNoChange: z.number().int().nullable().optional(),
|
|
49
|
+
consecutiveErrors: z.number().int().nullable().optional(),
|
|
50
|
+
nextFetchAfter: z.string().nullable().optional(),
|
|
51
|
+
medianGapDays: z.number().nullable().optional(),
|
|
52
|
+
lastRetieredAt: z.string().nullable().optional(),
|
|
53
|
+
metadata: z.string().nullable().optional(),
|
|
54
|
+
productName: z.string().nullable().optional(),
|
|
55
|
+
productSlug: z.string().nullable().optional(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Canonical row returned by `GET /v1/sources` (list). Superset of
|
|
60
|
+
* `SourceListItem` with required `id` / `orgName` / `orgSlug` fields used by
|
|
61
|
+
* the CLI `list` command.
|
|
62
|
+
*/
|
|
63
|
+
export const SourceWithOrgSchema = z.object({
|
|
64
|
+
id: z.string(),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
slug: z.string(),
|
|
67
|
+
type: SourceTypeSchema,
|
|
68
|
+
url: z.string(),
|
|
69
|
+
orgName: z.string().nullable(),
|
|
70
|
+
orgSlug: z.string().nullable(),
|
|
71
|
+
productName: z.string().nullable(),
|
|
72
|
+
productSlug: z.string().nullable(),
|
|
73
|
+
isPrimary: z.boolean(),
|
|
74
|
+
isHidden: z.boolean().nullable(),
|
|
75
|
+
discovery: SourceDiscoverySchema.optional(),
|
|
76
|
+
metadata: z.string().nullable(),
|
|
77
|
+
releaseCount: z.number().int().min(0),
|
|
78
|
+
latestVersion: z.string().nullable(),
|
|
79
|
+
latestDate: z.string().nullable(),
|
|
80
|
+
lastFetchedAt: z.string().nullable(),
|
|
81
|
+
lastPolledAt: z.string().nullable(),
|
|
82
|
+
fetchPriority: SourceFetchPrioritySchema.nullable(),
|
|
83
|
+
changeDetectedAt: z.string().nullable(),
|
|
84
|
+
consecutiveNoChange: z.number().int().nullable(),
|
|
85
|
+
consecutiveErrors: z.number().int().nullable(),
|
|
86
|
+
nextFetchAfter: z.string().nullable(),
|
|
87
|
+
medianGapDays: z.number().nullable(),
|
|
88
|
+
lastRetieredAt: z.string().nullable(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const SourceListResponseSchema = ListResponseSchema(SourceWithOrgSchema);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `GET /v1/sources` returns a bare array by default and an envelope when
|
|
95
|
+
* `?envelope=true` is passed. The OpenAPI spec advertises the union so
|
|
96
|
+
* either client expectation is documented.
|
|
97
|
+
*/
|
|
98
|
+
export const SourceListResultSchema = z.union([
|
|
99
|
+
z.array(SourceWithOrgSchema),
|
|
100
|
+
SourceListResponseSchema,
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const SourceDetailSummariesSchema = z.object({
|
|
104
|
+
rolling: ReleaseSummaryItemSchema.nullable(),
|
|
105
|
+
monthly: z.array(ReleaseSummaryItemSchema),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolved attribution block returned alongside a source row. `id` is
|
|
110
|
+
* additive vs the legacy `{ slug, name }` shape — older clients that ignored
|
|
111
|
+
* it still parse cleanly. `productId` / `productSlug` are sibling top-level
|
|
112
|
+
* fields on the source response (kept flat to mirror `orgId`), not nested
|
|
113
|
+
* inside this block.
|
|
114
|
+
*/
|
|
115
|
+
export const SourceOrgRefSchema = z.object({
|
|
116
|
+
id: z.string(),
|
|
117
|
+
slug: z.string(),
|
|
118
|
+
name: z.string(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const SourceDetailSchema = z.object({
|
|
122
|
+
id: z.string(),
|
|
123
|
+
slug: z.string(),
|
|
124
|
+
name: z.string(),
|
|
125
|
+
type: SourceTypeSchema,
|
|
126
|
+
url: z.string(),
|
|
127
|
+
orgId: z.string().nullable(),
|
|
128
|
+
productId: z.string().nullable(),
|
|
129
|
+
productSlug: z.string().nullable(),
|
|
130
|
+
// Hidden sources are reachable by direct URL but excluded from listings,
|
|
131
|
+
// sitemap, and AI features. On-demand lookups and admin-suppressed rows
|
|
132
|
+
// set this true; canonical curated/agent sources leave it false.
|
|
133
|
+
isHidden: z.boolean(),
|
|
134
|
+
// Pairs with `isHidden` to distinguish admin-suppressed rows (`curated` /
|
|
135
|
+
// `agent` + hidden) from rows materialized by `/v1/lookups` (`on_demand`).
|
|
136
|
+
// Optional for graceful degradation against older API responses.
|
|
137
|
+
discovery: SourceDiscoverySchema.optional(),
|
|
138
|
+
isPrimary: z.boolean(),
|
|
139
|
+
metadata: z.string(),
|
|
140
|
+
changelogUrl: z.string().nullable().optional(),
|
|
141
|
+
hasChangelogFile: z.boolean().optional(),
|
|
142
|
+
org: SourceOrgRefSchema.nullable(),
|
|
143
|
+
releaseCount: z.number().int().min(0),
|
|
144
|
+
releasesLast30Days: z.number().int().min(0),
|
|
145
|
+
avgReleasesPerWeek: z.number(),
|
|
146
|
+
latestVersion: z.string().nullable(),
|
|
147
|
+
latestDate: z.string().nullable(),
|
|
148
|
+
lastFetchedAt: z.string().nullable(),
|
|
149
|
+
lastPolledAt: z.string().nullable(),
|
|
150
|
+
trackingSince: z.string(),
|
|
151
|
+
releases: z.array(ReleaseItemSchema),
|
|
152
|
+
pagination: PaginationSchema,
|
|
153
|
+
summaries: SourceDetailSummariesSchema,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Response returned by `POST /v1/sources` and `PATCH /v1/sources/:slug`.
|
|
158
|
+
*
|
|
159
|
+
* Pre-#794 these endpoints returned the bare drizzle row, leaving callers to
|
|
160
|
+
* round-trip a separate `GET` to confirm the resolved org/product attribution.
|
|
161
|
+
* Now they return the same source row plus a resolved `org { id, slug, name }`
|
|
162
|
+
* block and `productSlug`, so an agent can answer "did the write take?" from
|
|
163
|
+
* the response alone.
|
|
164
|
+
*
|
|
165
|
+
* Wire fields are loosely-typed (`z.unknown()`) for the timestamps/counters
|
|
166
|
+
* that aren't load-bearing for callers — the row is large and we don't want
|
|
167
|
+
* to hand-mirror every column. Stable, agent-relevant fields are typed.
|
|
168
|
+
*/
|
|
169
|
+
export const SourceMutationResponseSchema = z.looseObject({
|
|
170
|
+
id: z.string(),
|
|
171
|
+
slug: z.string(),
|
|
172
|
+
name: z.string(),
|
|
173
|
+
type: SourceTypeSchema,
|
|
174
|
+
url: z.string(),
|
|
175
|
+
orgId: z.string().nullable(),
|
|
176
|
+
productId: z.string().nullable(),
|
|
177
|
+
productSlug: z.string().nullable(),
|
|
178
|
+
org: SourceOrgRefSchema.nullable(),
|
|
179
|
+
metadata: z.string().nullable(),
|
|
180
|
+
isPrimary: z.boolean().nullable().optional(),
|
|
181
|
+
isHidden: z.boolean().nullable().optional(),
|
|
182
|
+
discovery: SourceDiscoverySchema.optional(),
|
|
183
|
+
createdAt: z.string().optional(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/** Body accepted by `PATCH /v1/sources/:slug`. */
|
|
187
|
+
export const SourcePatchInputSchema = z.object({
|
|
188
|
+
name: z.string().optional(),
|
|
189
|
+
url: z.string().optional(),
|
|
190
|
+
type: SourceTypeSchema.optional(),
|
|
191
|
+
slug: z.string().optional(),
|
|
192
|
+
metadata: z.string().optional(),
|
|
193
|
+
orgId: z.string().nullable().optional(),
|
|
194
|
+
productId: z.string().nullable().optional(),
|
|
195
|
+
lastFetchedAt: z.string().nullable().optional(),
|
|
196
|
+
lastContentHash: z.string().nullable().optional(),
|
|
197
|
+
fetchPriority: SourceFetchPrioritySchema.optional(),
|
|
198
|
+
consecutiveNoChange: z.number().int().optional(),
|
|
199
|
+
consecutiveErrors: z.number().int().optional(),
|
|
200
|
+
nextFetchAfter: z.string().nullable().optional(),
|
|
201
|
+
isPrimary: z.boolean().optional(),
|
|
202
|
+
isHidden: z.boolean().optional(),
|
|
203
|
+
changeDetectedAt: z.string().nullable().optional(),
|
|
204
|
+
lastPolledAt: z.string().nullable().optional(),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
/** Body accepted by `POST /v1/sources`. */
|
|
208
|
+
export const CreateSourceBodySchema = z.object({
|
|
209
|
+
name: z.string().min(1),
|
|
210
|
+
url: z.string().min(1),
|
|
211
|
+
type: SourceTypeSchema.optional(),
|
|
212
|
+
slug: z.string().optional(),
|
|
213
|
+
orgId: z.string().optional(),
|
|
214
|
+
orgSlug: z.string().optional(),
|
|
215
|
+
/**
|
|
216
|
+
* Either accepted; `productId` wins when both are supplied. `productSlug`
|
|
217
|
+
* resolves within the requested org (slug uniqueness is per-org). The
|
|
218
|
+
* legacy `productId` ingestion path is preserved for callers that already
|
|
219
|
+
* resolve products client-side; new agent flows should pass `productSlug`.
|
|
220
|
+
*/
|
|
221
|
+
productId: z.string().optional(),
|
|
222
|
+
productSlug: z.string().optional(),
|
|
223
|
+
metadata: z.string().optional(),
|
|
224
|
+
isPrimary: z.boolean().optional(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
export const ChangelogFileSummarySchema = z.object({
|
|
228
|
+
path: z.string(),
|
|
229
|
+
filename: z.string(),
|
|
230
|
+
url: z.string(),
|
|
231
|
+
bytes: z.number().int().min(0),
|
|
232
|
+
fetchedAt: z.string(),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export const SourceChangelogResponseSchema = z.object({
|
|
236
|
+
path: z.string(),
|
|
237
|
+
filename: z.string(),
|
|
238
|
+
url: z.string(),
|
|
239
|
+
rawUrl: z.string(),
|
|
240
|
+
content: z.string(),
|
|
241
|
+
bytes: z.number().int().min(0),
|
|
242
|
+
fetchedAt: z.string(),
|
|
243
|
+
/** Character offset of the first character in `content` within the full file. */
|
|
244
|
+
offset: z.number().int().min(0),
|
|
245
|
+
/** The limit (in chars) that was applied to produce this slice. */
|
|
246
|
+
limit: z.number().int().min(0),
|
|
247
|
+
/** Next offset to request for the next slice, or null if `content` is the tail. */
|
|
248
|
+
nextOffset: z.number().int().nullable(),
|
|
249
|
+
/** Total length of the full file in characters. */
|
|
250
|
+
totalChars: z.number().int().min(0),
|
|
251
|
+
/** Requested token budget when in token mode (cl100k_base). */
|
|
252
|
+
tokens: z.number().int().optional(),
|
|
253
|
+
/** Encoded token count of the returned `content`. Set in token mode. */
|
|
254
|
+
sliceTokens: z.number().int().optional(),
|
|
255
|
+
/** Full-file token count (cl100k_base). Always populated. */
|
|
256
|
+
totalTokens: z.number().int().min(0),
|
|
257
|
+
/** True when the upstream file exceeded the 1MB cap and content was sliced. */
|
|
258
|
+
truncated: z.boolean(),
|
|
259
|
+
/** Byte offset where the file was truncated, or null when not truncated. */
|
|
260
|
+
truncatedAt: z.number().int().nullable(),
|
|
261
|
+
/**
|
|
262
|
+
* Index of every changelog file tracked for this source (root plus any
|
|
263
|
+
* discovered per-package files). Always present even for single-file
|
|
264
|
+
* sources so clients can lazily render a file picker.
|
|
265
|
+
*/
|
|
266
|
+
files: z.array(ChangelogFileSummarySchema),
|
|
267
|
+
});
|