@buildinternet/releases-api-types 0.22.0 → 0.24.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.
- package/package.json +1 -1
- package/src/api-types.ts +136 -4
- package/src/schemas/collections.ts +103 -13
- package/src/schemas/feedback.ts +56 -0
- package/src/schemas/orgs.ts +38 -1
- package/src/schemas/products.ts +71 -2
- package/src/schemas/related.ts +4 -0
- package/src/schemas/releases.ts +42 -2
- package/src/schemas/resolve.ts +15 -0
- package/src/schemas/search.ts +6 -2
- package/src/schemas/shared.ts +11 -0
- package/src/schemas/sitemap.ts +6 -0
- package/src/schemas/sources.ts +46 -1
package/package.json
CHANGED
package/src/api-types.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type {
|
|
|
10
10
|
SourceDiscovery,
|
|
11
11
|
SourceFetchPriority,
|
|
12
12
|
} from "@buildinternet/releases-core/source-enums";
|
|
13
|
+
// Re-export above doesn't create local bindings; import for use in interfaces below.
|
|
14
|
+
import type { SourceType, SourceFetchPriority } from "@buildinternet/releases-core/source-enums";
|
|
13
15
|
import type {
|
|
14
16
|
MediaItemSchema,
|
|
15
17
|
PaginationSchema,
|
|
@@ -62,6 +64,7 @@ import type {
|
|
|
62
64
|
SourceDetailSchema,
|
|
63
65
|
SourceFeedPaginationSchema,
|
|
64
66
|
SourceMutationResponseSchema,
|
|
67
|
+
AppStoreMaterializeResponseSchema,
|
|
65
68
|
SourceOrgRefSchema,
|
|
66
69
|
SourcePatchInputSchema,
|
|
67
70
|
CreateSourceBodySchema,
|
|
@@ -95,6 +98,7 @@ import type {
|
|
|
95
98
|
} from "./schemas/sources.js";
|
|
96
99
|
import type {
|
|
97
100
|
ProductRowSchema,
|
|
101
|
+
ProductCreateResponseSchema,
|
|
98
102
|
ProductListItemSchema,
|
|
99
103
|
ProductListResponseSchema,
|
|
100
104
|
ProductDetailSourceSchema,
|
|
@@ -109,11 +113,15 @@ import type {
|
|
|
109
113
|
ProductTagsListResponseSchema,
|
|
110
114
|
ProductTagsBodySchema,
|
|
111
115
|
ProductTagsMutationResponseSchema,
|
|
116
|
+
ProductActivityResponseSchema,
|
|
117
|
+
ProductHeatmapResponseSchema,
|
|
112
118
|
} from "./schemas/products.js";
|
|
113
119
|
import type {
|
|
114
120
|
ReleaseLatestSourceSchema,
|
|
121
|
+
ReleaseLatestProductSchema,
|
|
115
122
|
ReleaseLatestItemSchema,
|
|
116
123
|
ReleaseLatestResponseSchema,
|
|
124
|
+
ReleaseCoverageSiblingSchema,
|
|
117
125
|
ReleaseCoverageRowSchema,
|
|
118
126
|
ReleaseCoverageResponseSchema,
|
|
119
127
|
LinkReleaseCoverageBodySchema,
|
|
@@ -171,6 +179,14 @@ import type {
|
|
|
171
179
|
StatsRecentActivitySchema,
|
|
172
180
|
StatsResponseSchema,
|
|
173
181
|
} from "./schemas/stats.js";
|
|
182
|
+
import type {
|
|
183
|
+
FeedbackTypeSchema,
|
|
184
|
+
FeedbackStatusSchema,
|
|
185
|
+
FeedbackItemSchema,
|
|
186
|
+
FeedbackListResponseSchema,
|
|
187
|
+
FeedbackUpdateBodySchema,
|
|
188
|
+
FeedbackDeleteResponseSchema,
|
|
189
|
+
} from "./schemas/feedback.js";
|
|
174
190
|
import type { SitemapSourceSchema, SitemapPayloadSchema } from "./schemas/sitemap.js";
|
|
175
191
|
import type {
|
|
176
192
|
RelatedScopeSchema,
|
|
@@ -187,6 +203,9 @@ import type {
|
|
|
187
203
|
} from "./schemas/related.js";
|
|
188
204
|
import type {
|
|
189
205
|
CollectionMemberOrgSchema,
|
|
206
|
+
ProductParentOrgSchema,
|
|
207
|
+
CollectionMemberProductSchema,
|
|
208
|
+
CollectionMemberSchema,
|
|
190
209
|
CollectionListItemSchema,
|
|
191
210
|
CollectionListResponseSchema,
|
|
192
211
|
CollectionDetailSchema,
|
|
@@ -271,6 +290,7 @@ export {
|
|
|
271
290
|
SourceDetailSchema,
|
|
272
291
|
SourceFeedPaginationSchema,
|
|
273
292
|
SourceMutationResponseSchema,
|
|
293
|
+
AppStoreMaterializeResponseSchema,
|
|
274
294
|
SourceOrgRefSchema,
|
|
275
295
|
SourcePatchInputSchema,
|
|
276
296
|
CreateSourceBodySchema,
|
|
@@ -304,6 +324,7 @@ export {
|
|
|
304
324
|
} from "./schemas/sources.js";
|
|
305
325
|
export {
|
|
306
326
|
ProductRowSchema,
|
|
327
|
+
ProductCreateResponseSchema,
|
|
307
328
|
ProductListItemSchema,
|
|
308
329
|
ProductListResponseSchema,
|
|
309
330
|
ProductDetailSourceSchema,
|
|
@@ -318,6 +339,8 @@ export {
|
|
|
318
339
|
ProductTagsListResponseSchema,
|
|
319
340
|
ProductTagsBodySchema,
|
|
320
341
|
ProductTagsMutationResponseSchema,
|
|
342
|
+
ProductActivityResponseSchema,
|
|
343
|
+
ProductHeatmapResponseSchema,
|
|
321
344
|
} from "./schemas/products.js";
|
|
322
345
|
export {
|
|
323
346
|
LookupStatusSchema,
|
|
@@ -347,8 +370,10 @@ export {
|
|
|
347
370
|
} from "./schemas/taxonomy.js";
|
|
348
371
|
export {
|
|
349
372
|
ReleaseLatestSourceSchema,
|
|
373
|
+
ReleaseLatestProductSchema,
|
|
350
374
|
ReleaseLatestItemSchema,
|
|
351
375
|
ReleaseLatestResponseSchema,
|
|
376
|
+
ReleaseCoverageSiblingSchema,
|
|
352
377
|
ReleaseCoverageRowSchema,
|
|
353
378
|
ReleaseCoverageResponseSchema,
|
|
354
379
|
LinkReleaseCoverageBodySchema,
|
|
@@ -381,6 +406,14 @@ export {
|
|
|
381
406
|
StatsRecentActivitySchema,
|
|
382
407
|
StatsResponseSchema,
|
|
383
408
|
} from "./schemas/stats.js";
|
|
409
|
+
export {
|
|
410
|
+
FeedbackTypeSchema,
|
|
411
|
+
FeedbackStatusSchema,
|
|
412
|
+
FeedbackItemSchema,
|
|
413
|
+
FeedbackListResponseSchema,
|
|
414
|
+
FeedbackUpdateBodySchema,
|
|
415
|
+
FeedbackDeleteResponseSchema,
|
|
416
|
+
} from "./schemas/feedback.js";
|
|
384
417
|
export { SitemapSourceSchema, SitemapPayloadSchema } from "./schemas/sitemap.js";
|
|
385
418
|
export {
|
|
386
419
|
RelatedScopeSchema,
|
|
@@ -397,6 +430,9 @@ export {
|
|
|
397
430
|
} from "./schemas/related.js";
|
|
398
431
|
export {
|
|
399
432
|
CollectionMemberOrgSchema,
|
|
433
|
+
ProductParentOrgSchema,
|
|
434
|
+
CollectionMemberProductSchema,
|
|
435
|
+
CollectionMemberSchema,
|
|
400
436
|
CollectionListItemSchema,
|
|
401
437
|
CollectionListResponseSchema,
|
|
402
438
|
CollectionDetailSchema,
|
|
@@ -426,6 +462,8 @@ export {
|
|
|
426
462
|
UpdatePlaybookNotesBodySchema,
|
|
427
463
|
UpdatePlaybookNotesResponseSchema,
|
|
428
464
|
} from "./schemas/overviews.js";
|
|
465
|
+
export { ResolveResponseSchema } from "./schemas/resolve.js";
|
|
466
|
+
export type { ResolveResponse } from "./schemas/resolve.js";
|
|
429
467
|
|
|
430
468
|
// ── Media ──
|
|
431
469
|
|
|
@@ -512,6 +550,7 @@ export type SourceChangelogResponse = z.infer<typeof SourceChangelogResponseSche
|
|
|
512
550
|
export type SourceDetail = z.infer<typeof SourceDetailSchema>;
|
|
513
551
|
export type SourceFeedPagination = z.infer<typeof SourceFeedPaginationSchema>;
|
|
514
552
|
export type SourceMutationResponse = z.infer<typeof SourceMutationResponseSchema>;
|
|
553
|
+
export type AppStoreMaterializeResponse = z.infer<typeof AppStoreMaterializeResponseSchema>;
|
|
515
554
|
export type SourceOrgRef = z.infer<typeof SourceOrgRefSchema>;
|
|
516
555
|
export type SourceActivityResponse = z.infer<typeof SourceActivityResponseSchema>;
|
|
517
556
|
export type SourceHeatmapResponse = z.infer<typeof SourceHeatmapResponseSchema>;
|
|
@@ -566,6 +605,61 @@ export interface OrgsRollupResponse extends ListResponse<OrgsRollupRow> {
|
|
|
566
605
|
};
|
|
567
606
|
}
|
|
568
607
|
|
|
608
|
+
// ── Admin telemetry: stuck sources ──
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* A source whose recent fetch history is all errors with no reachability — a
|
|
612
|
+
* candidate for pausing. "Stuck" means: within the last `window` non-`dry_run`
|
|
613
|
+
* fetch attempts, every one was an `error` (zero `success`/`no_change`) and
|
|
614
|
+
* there were at least `minAttempts` of them.
|
|
615
|
+
*
|
|
616
|
+
* This keys off the `fetch_log` error streak rather than
|
|
617
|
+
* `sources.consecutive_errors`, because the scrape/agent fetch path never bumps
|
|
618
|
+
* that column (a source can fail for days with `consecutive_errors = 0`).
|
|
619
|
+
*/
|
|
620
|
+
export interface StuckSource {
|
|
621
|
+
sourceId: string;
|
|
622
|
+
sourceSlug: string;
|
|
623
|
+
name: string;
|
|
624
|
+
type: SourceType;
|
|
625
|
+
url: string;
|
|
626
|
+
kind: string | null;
|
|
627
|
+
orgSlug: string | null;
|
|
628
|
+
orgName: string | null;
|
|
629
|
+
/** Current fetch tier — `paused` rows only appear when `includePaused`. */
|
|
630
|
+
fetchPriority: SourceFetchPriority;
|
|
631
|
+
/** True when this is the org's primary changelog (Firebase's was). */
|
|
632
|
+
isPrimary: boolean;
|
|
633
|
+
isHidden: boolean;
|
|
634
|
+
/** Non-`dry_run` attempts examined in the window (all errors when stuck). */
|
|
635
|
+
recentAttempts: number;
|
|
636
|
+
/** Errors among the examined attempts (equals `recentAttempts` when stuck). */
|
|
637
|
+
recentErrors: number;
|
|
638
|
+
/** Most-recent fetch attempt timestamp (ISO), or null. */
|
|
639
|
+
lastAttemptAt: string | null;
|
|
640
|
+
/** Most-recent error message. */
|
|
641
|
+
lastError: string | null;
|
|
642
|
+
/** Most-recent error category, when the fetcher classified it. */
|
|
643
|
+
lastErrorCategory: string | null;
|
|
644
|
+
/** Last time the source was reachable (`success`/`no_change`), ISO; null = never. */
|
|
645
|
+
lastSuccessAt: string | null;
|
|
646
|
+
/** `sources.last_fetched_at` — null when the source has never fetched. */
|
|
647
|
+
lastFetchedAt: string | null;
|
|
648
|
+
/** When the source row was created (ISO) — proxy for how long it's failed. */
|
|
649
|
+
sourceCreatedAt: string | null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export interface StuckSourcesResponse extends ListResponse<StuckSource> {
|
|
653
|
+
meta: {
|
|
654
|
+
/** Recent non-`dry_run` attempts examined per source. */
|
|
655
|
+
window: number;
|
|
656
|
+
/** Minimum attempts in the window required to flag a source. */
|
|
657
|
+
minAttempts: number;
|
|
658
|
+
/** Whether already-paused sources were included. */
|
|
659
|
+
includePaused: boolean;
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
569
663
|
// ── Releases ──
|
|
570
664
|
|
|
571
665
|
/**
|
|
@@ -607,8 +701,11 @@ export interface ReleaseDetail {
|
|
|
607
701
|
type?: ReleaseType;
|
|
608
702
|
/** Per-category item counts from the AI release-content pass. See {@link ReleaseComposition}. */
|
|
609
703
|
composition?: ReleaseComposition | null;
|
|
704
|
+
/** App Store platform + icon, present only when `sourceType === "appstore"`. */
|
|
705
|
+
appStore?: { platform: "ios" | "macos"; iconUrl: string | null } | null;
|
|
610
706
|
}
|
|
611
707
|
|
|
708
|
+
export type ReleaseCoverageSibling = z.infer<typeof ReleaseCoverageSiblingSchema>;
|
|
612
709
|
export type ReleaseCoverageRow = z.infer<typeof ReleaseCoverageRowSchema>;
|
|
613
710
|
export type ReleaseCoverageResponse = z.infer<typeof ReleaseCoverageResponseSchema>;
|
|
614
711
|
export type LinkReleaseCoverageBody = z.infer<typeof LinkReleaseCoverageBodySchema>;
|
|
@@ -616,6 +713,7 @@ export type LinkReleaseCoverageResponse = z.infer<typeof LinkReleaseCoverageResp
|
|
|
616
713
|
export type UnlinkReleaseCoverageResponse = z.infer<typeof UnlinkReleaseCoverageResponseSchema>;
|
|
617
714
|
|
|
618
715
|
export type ReleaseLatestSource = z.infer<typeof ReleaseLatestSourceSchema>;
|
|
716
|
+
export type ReleaseLatestProduct = z.infer<typeof ReleaseLatestProductSchema>;
|
|
619
717
|
export type ReleaseLatestItem = z.infer<typeof ReleaseLatestItemSchema>;
|
|
620
718
|
export type ReleaseLatestResponse = z.infer<typeof ReleaseLatestResponseSchema>;
|
|
621
719
|
|
|
@@ -861,7 +959,7 @@ export type SourceActivity = SourceActivityResponse;
|
|
|
861
959
|
|
|
862
960
|
// ── Org Activity (Zod-derived aliases) ──
|
|
863
961
|
// The old hand-written OrgActivitySource / OrgActivity / OrgSparklines /
|
|
864
|
-
// OrgHeatmap / OrgReleaseItem
|
|
962
|
+
// OrgHeatmap / OrgReleaseItem interfaces have been
|
|
865
963
|
// replaced by Zod-derived types in the Organizations section above. Keeping
|
|
866
964
|
// the interface names as deprecated aliases so callsites don't need an
|
|
867
965
|
// immediate churn.
|
|
@@ -875,9 +973,6 @@ export type OrgSparklines = OrgSparklinesResponse;
|
|
|
875
973
|
/** @deprecated Use OrgHeatmapResponse */
|
|
876
974
|
export type OrgHeatmap = OrgHeatmapResponse;
|
|
877
975
|
|
|
878
|
-
/** @deprecated Use OrgReleasesFeedResponse */
|
|
879
|
-
export type OrgReleasesResponse = OrgReleasesFeedResponse;
|
|
880
|
-
|
|
881
976
|
// ── Org Sparklines (per-source/product breakdown) ──
|
|
882
977
|
// (covered above)
|
|
883
978
|
|
|
@@ -905,6 +1000,7 @@ export interface SourceReleasesResponse {
|
|
|
905
1000
|
// `Category` lives in @buildinternet/releases-core/categories — import it from
|
|
906
1001
|
// there. CategorySchema is re-exported here for OpenAPI / Zod consumers only.
|
|
907
1002
|
export type ProductRow = z.infer<typeof ProductRowSchema>;
|
|
1003
|
+
export type ProductCreateResponse = z.infer<typeof ProductCreateResponseSchema>;
|
|
908
1004
|
export type ProductListItem = z.infer<typeof ProductListItemSchema>;
|
|
909
1005
|
export type ProductListResponse = z.infer<typeof ProductListResponseSchema>;
|
|
910
1006
|
export type ProductDetailSource = z.infer<typeof ProductDetailSourceSchema>;
|
|
@@ -919,6 +1015,8 @@ export type ProductDeleteResponse = z.infer<typeof ProductDeleteResponseSchema>;
|
|
|
919
1015
|
export type ProductTagsListResponse = z.infer<typeof ProductTagsListResponseSchema>;
|
|
920
1016
|
export type ProductTagsBody = z.infer<typeof ProductTagsBodySchema>;
|
|
921
1017
|
export type ProductTagsMutationResponse = z.infer<typeof ProductTagsMutationResponseSchema>;
|
|
1018
|
+
export type ProductActivityResponse = z.infer<typeof ProductActivityResponseSchema>;
|
|
1019
|
+
export type ProductHeatmapResponse = z.infer<typeof ProductHeatmapResponseSchema>;
|
|
922
1020
|
|
|
923
1021
|
// ── Taxonomy (categories + tags) ──
|
|
924
1022
|
|
|
@@ -947,6 +1045,9 @@ export type CategoryReleaseItem = CollectionReleaseItem;
|
|
|
947
1045
|
// /collections/frontier-ai-labs). Independent of the fixed `category` taxonomy.
|
|
948
1046
|
|
|
949
1047
|
export type CollectionMemberOrg = z.infer<typeof CollectionMemberOrgSchema>;
|
|
1048
|
+
export type ProductParentOrg = z.infer<typeof ProductParentOrgSchema>;
|
|
1049
|
+
export type CollectionMemberProduct = z.infer<typeof CollectionMemberProductSchema>;
|
|
1050
|
+
export type CollectionMember = z.infer<typeof CollectionMemberSchema>;
|
|
950
1051
|
export type CollectionListItem = z.infer<typeof CollectionListItemSchema>;
|
|
951
1052
|
export type CollectionListResponse = z.infer<typeof CollectionListResponseSchema>;
|
|
952
1053
|
export type CollectionDetail = z.infer<typeof CollectionDetailSchema>;
|
|
@@ -1014,6 +1115,12 @@ export interface LatestRelease {
|
|
|
1014
1115
|
*/
|
|
1015
1116
|
contentChars?: number | null;
|
|
1016
1117
|
contentTokens?: number | null;
|
|
1118
|
+
/**
|
|
1119
|
+
* Owning product, when the release's source is grouped under a product.
|
|
1120
|
+
* `null` / `undefined` when the source has no `product_id`. Additive — older
|
|
1121
|
+
* API responses omit this field; treat `undefined` as `null`. #1217.
|
|
1122
|
+
*/
|
|
1123
|
+
product?: { slug: string; name: string } | null;
|
|
1017
1124
|
}
|
|
1018
1125
|
|
|
1019
1126
|
// ── Stats ──
|
|
@@ -1034,6 +1141,14 @@ export type StatsResponse = z.infer<typeof StatsResponseSchema>;
|
|
|
1034
1141
|
*/
|
|
1035
1142
|
export type StatsSummary = Omit<StatsResponse, "orgs" | "sources" | "releases" | "products">;
|
|
1036
1143
|
|
|
1144
|
+
// ── Feedback ──
|
|
1145
|
+
export type FeedbackType = z.infer<typeof FeedbackTypeSchema>;
|
|
1146
|
+
export type FeedbackStatus = z.infer<typeof FeedbackStatusSchema>;
|
|
1147
|
+
export type FeedbackItem = z.infer<typeof FeedbackItemSchema>;
|
|
1148
|
+
export type FeedbackListResponse = z.infer<typeof FeedbackListResponseSchema>;
|
|
1149
|
+
export type FeedbackUpdateBody = z.infer<typeof FeedbackUpdateBodySchema>;
|
|
1150
|
+
export type FeedbackDeleteResponse = z.infer<typeof FeedbackDeleteResponseSchema>;
|
|
1151
|
+
|
|
1037
1152
|
// ── Fetch log ──
|
|
1038
1153
|
|
|
1039
1154
|
export interface FetchLogEntry {
|
|
@@ -1236,3 +1351,20 @@ export interface EvaluationResult {
|
|
|
1236
1351
|
provider?: string;
|
|
1237
1352
|
notes?: string;
|
|
1238
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Identity returned by `GET /v1/tokens/me` — the caller introspecting its own
|
|
1357
|
+
* credential. `kind: "root"` is the static break-glass key (synthetic identity,
|
|
1358
|
+
* no DB row); `kind: "token"` is a DB-backed `relk_` token.
|
|
1359
|
+
*/
|
|
1360
|
+
export interface TokenIdentity {
|
|
1361
|
+
kind: "root" | "token";
|
|
1362
|
+
/** Display label; "root" for the static key, "local-dev" when no secret is bound. */
|
|
1363
|
+
name: string;
|
|
1364
|
+
/** e.g. ["read","write"] or ["*"] for root. */
|
|
1365
|
+
scopes: string[];
|
|
1366
|
+
principalType: "internal" | "agent" | "user";
|
|
1367
|
+
principalId?: string | null;
|
|
1368
|
+
expiresAt?: string | null;
|
|
1369
|
+
lastUsedAt?: string | null;
|
|
1370
|
+
}
|
|
@@ -17,30 +17,81 @@ export const CollectionMemberOrgSchema = z.object({
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Org context attached to a product member so the chip can render the parent
|
|
21
|
+
* org's avatar / handle. Subset of `CollectionMemberOrg` — description is
|
|
22
|
+
* intentionally omitted because the byline comes from the product itself.
|
|
23
|
+
*/
|
|
24
|
+
export const ProductParentOrgSchema = z.object({
|
|
25
|
+
slug: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
domain: z.string().nullable(),
|
|
28
|
+
avatarUrl: z.string().nullable(),
|
|
29
|
+
githubHandle: z.string().nullable(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* One product as it appears inside a collection. Carries the parent org block
|
|
34
|
+
* so the chip can render the org's avatar (products don't have their own).
|
|
35
|
+
*/
|
|
36
|
+
export const CollectionMemberProductSchema = z.object({
|
|
37
|
+
slug: z.string(),
|
|
38
|
+
name: z.string(),
|
|
39
|
+
description: z.string().nullable(),
|
|
40
|
+
org: ProductParentOrgSchema,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Mixed-kind member entry. `kind` discriminates: `org` carries the org block at
|
|
45
|
+
* the top level; `product` carries the product fields plus a `org` parent block.
|
|
46
|
+
* Used by `GET /v1/collections/:slug` (`members` field) and the new
|
|
47
|
+
* `previewMembers` on `GET /v1/collections`.
|
|
48
|
+
*/
|
|
49
|
+
export const CollectionMemberSchema = z.discriminatedUnion("kind", [
|
|
50
|
+
CollectionMemberOrgSchema.extend({ kind: z.literal("org") }),
|
|
51
|
+
CollectionMemberProductSchema.extend({ kind: z.literal("product") }),
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Row on `GET /v1/collections`. `previewMembers` (the new mixed-kind preview)
|
|
56
|
+
* is the first few visible members so the list page can render inline avatars
|
|
57
|
+
* without a second round trip. `previewOrgs` is the legacy org-only preview,
|
|
58
|
+
* retained for back-compat — clients should migrate to `previewMembers`.
|
|
23
59
|
*/
|
|
24
60
|
export const CollectionListItemSchema = z.object({
|
|
25
61
|
slug: z.string(),
|
|
26
62
|
name: z.string(),
|
|
27
63
|
description: z.string().nullable(),
|
|
28
64
|
memberCount: z.number().int().min(0),
|
|
29
|
-
|
|
65
|
+
/** Whether the collection is promoted on the homepage. Filterable via `?featured=1`. */
|
|
66
|
+
isFeatured: z.boolean(),
|
|
67
|
+
/** Mixed-kind preview (org + product). Cap matches `previewOrgs`. */
|
|
68
|
+
previewMembers: z.array(CollectionMemberSchema).optional(),
|
|
69
|
+
/** Legacy org-only preview. Populated with the org-kind subset of `previewMembers`. */
|
|
70
|
+
previewOrgs: z.array(CollectionMemberOrgSchema).optional(),
|
|
30
71
|
});
|
|
31
72
|
|
|
32
73
|
export const CollectionListResponseSchema = z.array(CollectionListItemSchema);
|
|
33
74
|
|
|
34
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Returned by `GET /v1/collections/:slug`.
|
|
77
|
+
*
|
|
78
|
+
* `members` is the mixed-kind canonical list. `orgs` is the legacy org-only
|
|
79
|
+
* field, populated with the org-kind subset of `members` for back-compat —
|
|
80
|
+
* new clients should consume `members`.
|
|
81
|
+
*/
|
|
35
82
|
export const CollectionDetailSchema = z.object({
|
|
36
83
|
slug: z.string(),
|
|
37
84
|
name: z.string(),
|
|
38
85
|
description: z.string().nullable(),
|
|
86
|
+
/** Whether the collection is promoted on the homepage. */
|
|
87
|
+
isFeatured: z.boolean(),
|
|
88
|
+
members: z.array(CollectionMemberSchema),
|
|
89
|
+
/** @deprecated Use `members`. Org-only subset, kept for back-compat. */
|
|
39
90
|
orgs: z.array(CollectionMemberOrgSchema),
|
|
40
91
|
});
|
|
41
92
|
|
|
42
93
|
/**
|
|
43
|
-
* Cross-
|
|
94
|
+
* Cross-member release feed row on `GET /v1/collections/:slug/releases`. Same
|
|
44
95
|
* shape as `OrgReleaseItem` plus the origin `org` block (and optional
|
|
45
96
|
* `product`) so the web's release card can render it identically. `source.type`
|
|
46
97
|
* stays as `z.string()` because the SQL passes the column through unvalidated —
|
|
@@ -56,6 +107,16 @@ export const CollectionReleaseItemSchema = ReleaseItemSchema.extend({
|
|
|
56
107
|
* typecheck — clients should treat missing and `null` identically.
|
|
57
108
|
*/
|
|
58
109
|
product: z.object({ slug: z.string(), name: z.string() }).nullable().optional(),
|
|
110
|
+
/**
|
|
111
|
+
* Server-resolved grouping identity — `COALESCE(product.slug, source.slug)` /
|
|
112
|
+
* `COALESCE(product.name, source.name)`. The web release feeds key and label
|
|
113
|
+
* SDK/package-cluster rollups on these instead of reconstructing
|
|
114
|
+
* `product ?? source` client-side. Optional on the wire: older workers omit
|
|
115
|
+
* them, so clients must fall back to deriving from `product ?? source`. Never
|
|
116
|
+
* null when present (`source` is always set). #1234
|
|
117
|
+
*/
|
|
118
|
+
groupSlug: z.string().optional(),
|
|
119
|
+
groupName: z.string().optional(),
|
|
59
120
|
});
|
|
60
121
|
|
|
61
122
|
/** Cursor pagination shape matches `/v1/orgs/:slug/releases`. */
|
|
@@ -79,6 +140,7 @@ export const CollectionRowSchema = z.object({
|
|
|
79
140
|
slug: z.string(),
|
|
80
141
|
name: z.string(),
|
|
81
142
|
description: z.string().nullable(),
|
|
143
|
+
isFeatured: z.boolean(),
|
|
82
144
|
createdAt: z.string(),
|
|
83
145
|
updatedAt: z.string(),
|
|
84
146
|
});
|
|
@@ -99,15 +161,26 @@ export const UpdateCollectionRequestSchema = z.object({
|
|
|
99
161
|
slug: z.string().optional(),
|
|
100
162
|
name: z.string().min(1).max(200).optional(),
|
|
101
163
|
description: z.string().max(2000).nullable().optional(),
|
|
164
|
+
/** Promote / demote the collection on the homepage. */
|
|
165
|
+
isFeatured: z.boolean().optional(),
|
|
102
166
|
});
|
|
103
167
|
|
|
104
168
|
/**
|
|
105
|
-
* One member entry accepted by the member-write endpoints.
|
|
106
|
-
*
|
|
169
|
+
* One member entry accepted by the member-write endpoints.
|
|
170
|
+
*
|
|
171
|
+
* Exactly one of `orgId` / `orgSlug` / `productId` / `productSlug` must be
|
|
172
|
+
* present. When both an id and a slug are supplied for the same kind, the id
|
|
173
|
+
* wins. Mixing kinds (e.g. `orgId` + `productSlug`) is rejected by the handler.
|
|
174
|
+
*
|
|
175
|
+
* Product slug is resolved within an org — supply either `productId`
|
|
176
|
+
* (`prod_…`, globally unique) or pair `productSlug` with `orgSlug`/`orgId`.
|
|
177
|
+
* `productSlug` alone is rejected.
|
|
107
178
|
*/
|
|
108
179
|
export const CollectionMemberInputSchema = z.object({
|
|
109
180
|
orgId: z.string().optional(),
|
|
110
181
|
orgSlug: z.string().optional(),
|
|
182
|
+
productId: z.string().optional(),
|
|
183
|
+
productSlug: z.string().optional(),
|
|
111
184
|
/** Authoring position (default 0). For PUT, omit to use the array index. */
|
|
112
185
|
position: z.number().int().optional(),
|
|
113
186
|
});
|
|
@@ -115,14 +188,25 @@ export const CollectionMemberInputSchema = z.object({
|
|
|
115
188
|
/** Body for `POST /v1/collections/:slug/members`. */
|
|
116
189
|
export const AddCollectionMemberRequestSchema = CollectionMemberInputSchema;
|
|
117
190
|
|
|
118
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Body for `PUT /v1/collections/:slug/members`. Replaces the full membership
|
|
193
|
+
* atomically. The field is still named `orgs` for back-compat — it accepts
|
|
194
|
+
* any `CollectionMemberInput`, org or product.
|
|
195
|
+
*/
|
|
119
196
|
export const ReplaceCollectionMembersRequestSchema = z.object({
|
|
120
197
|
orgs: z.array(CollectionMemberInputSchema),
|
|
121
198
|
});
|
|
122
199
|
|
|
123
|
-
/**
|
|
200
|
+
/**
|
|
201
|
+
* Resolved member returned by `PUT /v1/collections/:slug/members` and `POST
|
|
202
|
+
* /v1/collections/:slug/members`. `kind` discriminates which id field is
|
|
203
|
+
* populated. `orgId` is also set on legacy callers that only used org
|
|
204
|
+
* membership — for clean dispatch, dispatch on `kind`.
|
|
205
|
+
*/
|
|
124
206
|
export const ResolvedCollectionMemberSchema = z.object({
|
|
125
|
-
|
|
207
|
+
kind: z.enum(["org", "product"]),
|
|
208
|
+
orgId: z.string().optional(),
|
|
209
|
+
productId: z.string().optional(),
|
|
126
210
|
position: z.number().int(),
|
|
127
211
|
});
|
|
128
212
|
|
|
@@ -132,9 +216,15 @@ export const ReplaceCollectionMembersResponseSchema = z.object({
|
|
|
132
216
|
members: z.array(ResolvedCollectionMemberSchema),
|
|
133
217
|
});
|
|
134
218
|
|
|
135
|
-
/**
|
|
219
|
+
/**
|
|
220
|
+
* Response from `POST /v1/collections/:slug/members`. `orgId` stays populated
|
|
221
|
+
* for org-kind members so existing CLI callers don't break; `kind` +
|
|
222
|
+
* `productId` are the canonical fields for new code.
|
|
223
|
+
*/
|
|
136
224
|
export const AddCollectionMemberResponseSchema = z.object({
|
|
137
225
|
collectionSlug: z.string(),
|
|
138
|
-
|
|
226
|
+
kind: z.enum(["org", "product"]),
|
|
227
|
+
orgId: z.string().optional(),
|
|
228
|
+
productId: z.string().optional(),
|
|
139
229
|
position: z.number().int(),
|
|
140
230
|
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { FEEDBACK_TYPES, FEEDBACK_STATUSES } from "@buildinternet/releases-core/schema";
|
|
3
|
+
|
|
4
|
+
/** Feedback category — mirrors `FEEDBACK_TYPES` in `@buildinternet/releases-core/schema`. */
|
|
5
|
+
export const FeedbackTypeSchema = z.enum(FEEDBACK_TYPES);
|
|
6
|
+
|
|
7
|
+
/** Triage lifecycle — mirrors `FEEDBACK_STATUSES` in `@buildinternet/releases-core/schema`. */
|
|
8
|
+
export const FeedbackStatusSchema = z.enum(FEEDBACK_STATUSES);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One submitted feedback row, as returned by `GET /v1/admin/feedback` and by
|
|
12
|
+
* the triage write-path (`PATCH /v1/feedback/:id` echoes the updated row).
|
|
13
|
+
* `archived` soft-removes the row from the default admin read view and is
|
|
14
|
+
* orthogonal to triage `status` — a row can be `closed` and visible, or `new`
|
|
15
|
+
* and archived. `createdAt` is epoch milliseconds.
|
|
16
|
+
*/
|
|
17
|
+
export const FeedbackItemSchema = z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
createdAt: z.number(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
contact: z.string().nullable(),
|
|
22
|
+
type: FeedbackTypeSchema,
|
|
23
|
+
status: FeedbackStatusSchema,
|
|
24
|
+
archived: z.boolean(),
|
|
25
|
+
cliVersion: z.string().nullable(),
|
|
26
|
+
clientKind: z.string(),
|
|
27
|
+
anonId: z.string().nullable(),
|
|
28
|
+
os: z.string().nullable(),
|
|
29
|
+
arch: z.string().nullable(),
|
|
30
|
+
runtime: z.string().nullable(),
|
|
31
|
+
surface: z.string(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `GET /v1/admin/feedback` — cursor-paginated, newest-first. Archived rows are
|
|
36
|
+
* hidden unless the request passes `?includeArchived=true`.
|
|
37
|
+
*/
|
|
38
|
+
export const FeedbackListResponseSchema = z.object({
|
|
39
|
+
items: z.array(FeedbackItemSchema),
|
|
40
|
+
nextCursor: z.string().nullable(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `PATCH /v1/feedback/:id` body — partial triage update. At least one field
|
|
45
|
+
* must be present; the response echoes the updated `FeedbackItem`.
|
|
46
|
+
*/
|
|
47
|
+
export const FeedbackUpdateBodySchema = z.object({
|
|
48
|
+
status: FeedbackStatusSchema.optional(),
|
|
49
|
+
archived: z.boolean().optional(),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/** `DELETE /v1/feedback/:id` response — hard delete of a single row. */
|
|
53
|
+
export const FeedbackDeleteResponseSchema = z.object({
|
|
54
|
+
deleted: z.literal(true),
|
|
55
|
+
id: z.string(),
|
|
56
|
+
});
|
package/src/schemas/orgs.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { KIND_VALUES } from "@buildinternet/releases-core/kinds";
|
|
3
3
|
import {
|
|
4
|
+
AppStoreSourceInfoSchema,
|
|
4
5
|
CategorySchema,
|
|
5
6
|
ListResponseSchema,
|
|
6
7
|
OverviewPageItemSchema,
|
|
@@ -103,6 +104,10 @@ export const UpdateOrgBodySchema = z.object({
|
|
|
103
104
|
aliases: z.array(z.string()).optional(),
|
|
104
105
|
/** Admin-only: pause/unpause all ingest for this org without touching sources (#1057). */
|
|
105
106
|
fetchPaused: z.boolean().optional(),
|
|
107
|
+
/** Admin-only: hide the org from the homepage ticker + /v1/orgs directory. Stays reachable via detail, search, sitemap. */
|
|
108
|
+
isHidden: z.boolean().optional(),
|
|
109
|
+
/** Admin-only: opt the org into automatic AI content — org overviews AND per-release summaries (single backend flag `auto_generate_content`). */
|
|
110
|
+
autoGenerateContent: z.boolean().optional(),
|
|
106
111
|
});
|
|
107
112
|
|
|
108
113
|
// Org detail's products query selects a strict subset of `ProductListItem` —
|
|
@@ -115,6 +120,9 @@ const OrgDetailProductSchema = ProductListItemSchema.pick({
|
|
|
115
120
|
url: true,
|
|
116
121
|
description: true,
|
|
117
122
|
sourceCount: true,
|
|
123
|
+
kind: true,
|
|
124
|
+
}).extend({
|
|
125
|
+
releaseCount: z.number().int().min(0),
|
|
118
126
|
});
|
|
119
127
|
|
|
120
128
|
const OrgDetailPlaybookSchema = z.object({
|
|
@@ -328,7 +336,28 @@ export const OrgSparklinesResponseSchema = z.object({
|
|
|
328
336
|
* with a `source` block identifying the originating source within the org.
|
|
329
337
|
*/
|
|
330
338
|
export const OrgReleaseItemSchema = ReleaseItemSchema.extend({
|
|
331
|
-
source: z.object({
|
|
339
|
+
source: z.object({
|
|
340
|
+
slug: z.string(),
|
|
341
|
+
name: z.string(),
|
|
342
|
+
type: z.string(),
|
|
343
|
+
appStore: AppStoreSourceInfoSchema.optional(),
|
|
344
|
+
}),
|
|
345
|
+
/**
|
|
346
|
+
* Owning product, when the release's source is grouped under a product.
|
|
347
|
+
* `null` when the source has no `product_id`. Additive — older API responses
|
|
348
|
+
* omit this field; treat `undefined` as `null`. #1217.
|
|
349
|
+
*/
|
|
350
|
+
product: z.object({ slug: z.string(), name: z.string() }).nullable().optional(),
|
|
351
|
+
/**
|
|
352
|
+
* Server-resolved grouping identity — `COALESCE(product.slug, source.slug)` /
|
|
353
|
+
* `COALESCE(product.name, source.name)`. The web releases feed keys and labels
|
|
354
|
+
* SDK/package-cluster rollups on these instead of reconstructing
|
|
355
|
+
* `product ?? source` client-side. Optional on the wire: older workers omit
|
|
356
|
+
* them, so clients must fall back to deriving from `product ?? source`. Never
|
|
357
|
+
* null when present (`source` is always set). #1234
|
|
358
|
+
*/
|
|
359
|
+
groupSlug: z.string().optional(),
|
|
360
|
+
groupName: z.string().optional(),
|
|
332
361
|
});
|
|
333
362
|
|
|
334
363
|
/** Cursor pagination shape for the org releases feed. */
|
|
@@ -394,6 +423,14 @@ export const OrgDetailSchema = z.object({
|
|
|
394
423
|
description: z.string().nullable().optional(),
|
|
395
424
|
category: CategorySchema.nullable().optional(),
|
|
396
425
|
avatarUrl: z.string().nullable(),
|
|
426
|
+
/** Admin display flag: true when the org is hidden from listings (homepage + /v1/orgs). Optional on the wire for older workers mid-deploy. */
|
|
427
|
+
isHidden: z.boolean().optional(),
|
|
428
|
+
/** Admin display flag: org is opted into automatic overviews + per-release summaries. Optional on the wire for older workers mid-deploy. */
|
|
429
|
+
autoGenerateContent: z.boolean().optional(),
|
|
430
|
+
/** Admin display flag: all ingest paused for this org. */
|
|
431
|
+
fetchPaused: z.boolean().optional(),
|
|
432
|
+
/** How the org row was created. `on_demand` orgs are excluded from overview generation regardless of `autoGenerateContent`. */
|
|
433
|
+
discovery: z.enum(["curated", "agent", "on_demand"]).optional(),
|
|
397
434
|
tags: z.array(z.string()).optional(),
|
|
398
435
|
sourceCount: z.number().int().min(0),
|
|
399
436
|
releaseCount: z.number().int().min(0),
|
package/src/schemas/products.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { KIND_VALUES } from "@buildinternet/releases-core/kinds";
|
|
3
|
-
import { CategorySchema, ListResponseSchema } from "./shared.js";
|
|
4
|
-
import { SourceTypeSchema } from "./sources.js";
|
|
3
|
+
import { CategorySchema, ListResponseSchema, ReleaseItemSchema } from "./shared.js";
|
|
4
|
+
import { SourceTypeSchema, SourceFeedPaginationSchema } from "./sources.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Raw `products` table row, returned by `POST /v1/products` and
|
|
@@ -21,15 +21,25 @@ export const ProductRowSchema = z.object({
|
|
|
21
21
|
description: z.string().nullable(),
|
|
22
22
|
category: CategorySchema.nullable(),
|
|
23
23
|
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
24
|
+
avatarUrl: z.string().nullable().optional(),
|
|
24
25
|
createdAt: z.string(),
|
|
25
26
|
embeddedAt: z.string().nullable(),
|
|
26
27
|
deletedAt: z.string().nullable(),
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
/** 201 body of POST /v1/products — ProductRow plus an optional non-blocking
|
|
31
|
+
* warning emitted when the new slug shadows an existing same-org source (#1190). */
|
|
32
|
+
export const ProductCreateResponseSchema = ProductRowSchema.extend({
|
|
33
|
+
warning: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
export type ProductCreateResponse = z.infer<typeof ProductCreateResponseSchema>;
|
|
36
|
+
|
|
29
37
|
/**
|
|
30
38
|
* Per-product row returned by `GET /v1/products`. Adds `sourceCount` to
|
|
31
39
|
* the row shape but omits the internal `embeddedAt` / `deletedAt` columns
|
|
32
40
|
* (the list handler explicitly selects only the user-facing fields).
|
|
41
|
+
* `avatarUrl` (the product icon, e.g. an App Store app icon) is included so
|
|
42
|
+
* list surfaces can render the avatar without a per-row detail round-trip.
|
|
33
43
|
*/
|
|
34
44
|
export const ProductListItemSchema = z.object({
|
|
35
45
|
id: z.string(),
|
|
@@ -40,6 +50,7 @@ export const ProductListItemSchema = z.object({
|
|
|
40
50
|
description: z.string().nullable(),
|
|
41
51
|
category: CategorySchema.nullable(),
|
|
42
52
|
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
53
|
+
avatarUrl: z.string().nullable().optional(),
|
|
43
54
|
createdAt: z.string(),
|
|
44
55
|
sourceCount: z.number().int().min(0),
|
|
45
56
|
});
|
|
@@ -57,16 +68,27 @@ export const ProductDetailSourceSchema = z.object({
|
|
|
57
68
|
name: z.string(),
|
|
58
69
|
type: SourceTypeSchema,
|
|
59
70
|
url: z.string(),
|
|
71
|
+
metadata: z.string().nullable().optional(),
|
|
72
|
+
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
60
73
|
});
|
|
61
74
|
|
|
62
75
|
/**
|
|
63
76
|
* Returned by `GET /v1/products/:identifier` (and the org-scoped twin).
|
|
64
77
|
* Spreads the raw product row and adds `sources`, `tags`, `aliases`.
|
|
78
|
+
*
|
|
79
|
+
* `releases` and `pagination` are populated only by the web format adapter
|
|
80
|
+
* (the `.json` / `.md` product route — see PR #1207), NOT by the bare product
|
|
81
|
+
* detail endpoint. They are optional so bare endpoint consumers see a valid
|
|
82
|
+
* `ProductDetail` without the embedded feed.
|
|
65
83
|
*/
|
|
66
84
|
export const ProductDetailSchema = ProductRowSchema.extend({
|
|
67
85
|
sources: z.array(ProductDetailSourceSchema),
|
|
68
86
|
tags: z.array(z.string()),
|
|
69
87
|
aliases: z.array(z.string()),
|
|
88
|
+
/** Embedded release feed — set by the web format adapter (PR #1207), absent on bare product detail. */
|
|
89
|
+
releases: z.array(ReleaseItemSchema).optional(),
|
|
90
|
+
/** Cursor pagination for the embedded release feed — set by the web format adapter (PR #1207), absent on bare product detail. */
|
|
91
|
+
pagination: SourceFeedPaginationSchema.optional(),
|
|
70
92
|
});
|
|
71
93
|
|
|
72
94
|
/**
|
|
@@ -102,6 +124,7 @@ export const UpdateProductBodySchema = z.object({
|
|
|
102
124
|
tags: z.array(z.string()).optional(),
|
|
103
125
|
aliases: z.array(z.string()).optional(),
|
|
104
126
|
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
127
|
+
avatarUrl: z.string().nullable().optional(),
|
|
105
128
|
});
|
|
106
129
|
|
|
107
130
|
/**
|
|
@@ -177,3 +200,49 @@ export const ProductTagsBodySchema = z.object({
|
|
|
177
200
|
export const ProductTagsMutationResponseSchema = z.object({
|
|
178
201
|
ok: z.literal(true),
|
|
179
202
|
});
|
|
203
|
+
|
|
204
|
+
// ── Product activity (/products/:slug/activity, /orgs/:orgSlug/products/:productSlug/activity) ──
|
|
205
|
+
|
|
206
|
+
const ProductActivityWeeklyBucketSchema = z.object({
|
|
207
|
+
weekStart: z.string(),
|
|
208
|
+
count: z.number().int().min(0),
|
|
209
|
+
earliestVersion: z.string().nullable(),
|
|
210
|
+
latestVersion: z.string().nullable(),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const ProductActivitySourceSchema = z.object({
|
|
214
|
+
slug: z.string(),
|
|
215
|
+
name: z.string(),
|
|
216
|
+
releaseCount: z.number().int().min(0),
|
|
217
|
+
avgReleasesPerWeek: z.number(),
|
|
218
|
+
earliestVersion: z.string().nullable(),
|
|
219
|
+
latestVersion: z.string().nullable(),
|
|
220
|
+
latestDate: z.string().nullable(),
|
|
221
|
+
weeklyBuckets: z.array(ProductActivityWeeklyBucketSchema),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Response shape for `GET /v1/products/:slug/activity` and the org-scoped twin.
|
|
226
|
+
* Returns per-source weekly release buckets plus an aggregate rollup — used for
|
|
227
|
+
* timeline visualization on the product detail page.
|
|
228
|
+
*/
|
|
229
|
+
export const ProductActivityResponseSchema = z.object({
|
|
230
|
+
product: z.object({ slug: z.string(), name: z.string() }),
|
|
231
|
+
range: z.object({ from: z.string(), to: z.string() }),
|
|
232
|
+
sources: z.array(ProductActivitySourceSchema),
|
|
233
|
+
aggregateWeekly: z.array(z.object({ weekStart: z.string(), count: z.number().int().min(0) })),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ── Product heatmap (/products/:slug/heatmap, /orgs/:orgSlug/products/:productSlug/heatmap) ──
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Response shape for `GET /v1/products/:slug/heatmap` and the org-scoped twin.
|
|
240
|
+
* Returns daily release counts for the trailing 365 days — used for
|
|
241
|
+
* contribution-graph visualization on the product detail page.
|
|
242
|
+
*/
|
|
243
|
+
export const ProductHeatmapResponseSchema = z.object({
|
|
244
|
+
product: z.object({ slug: z.string(), name: z.string() }),
|
|
245
|
+
range: z.object({ from: z.string(), to: z.string() }),
|
|
246
|
+
dailyCounts: z.array(z.object({ date: z.string(), count: z.number().int().min(0) })),
|
|
247
|
+
total: z.number().int().min(0),
|
|
248
|
+
});
|
package/src/schemas/related.ts
CHANGED
|
@@ -17,8 +17,12 @@ export const RelatedReleaseSourceSchema = z.object({
|
|
|
17
17
|
id: z.string(),
|
|
18
18
|
slug: z.string(),
|
|
19
19
|
name: z.string(),
|
|
20
|
+
/** Parent product name, when the source belongs to one — preferred over the
|
|
21
|
+
* bare source/feed name for display. Null for sources with no product. */
|
|
22
|
+
productName: z.string().nullable(),
|
|
20
23
|
orgSlug: z.string().nullable(),
|
|
21
24
|
orgName: z.string().nullable(),
|
|
25
|
+
orgAvatarUrl: z.string().nullable(),
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
/**
|
package/src/schemas/releases.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SOURCE_TYPES } from "@buildinternet/releases-core/source-enums";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AppStoreSourceInfoSchema,
|
|
5
|
+
MediaItemSchema,
|
|
6
|
+
ReleaseCompositionSchema,
|
|
7
|
+
ReleaseTypeSchema,
|
|
8
|
+
} from "./shared.js";
|
|
4
9
|
|
|
5
10
|
// ReleaseCompositionSchema lives in ./shared.js (single source of truth) — both
|
|
6
11
|
// this file and ReleaseItemSchema reference it from there.
|
|
@@ -22,6 +27,11 @@ export const ReleaseLatestSourceSchema = z.object({
|
|
|
22
27
|
orgSlug: z.string().nullable(),
|
|
23
28
|
});
|
|
24
29
|
|
|
30
|
+
export const ReleaseLatestProductSchema = z.object({
|
|
31
|
+
slug: z.string(),
|
|
32
|
+
name: z.string(),
|
|
33
|
+
});
|
|
34
|
+
|
|
25
35
|
export const ReleaseLatestItemSchema = z.object({
|
|
26
36
|
id: z.string(),
|
|
27
37
|
version: z.string().nullable(),
|
|
@@ -34,6 +44,12 @@ export const ReleaseLatestItemSchema = z.object({
|
|
|
34
44
|
url: z.string().nullable(),
|
|
35
45
|
media: z.array(MediaItemSchema),
|
|
36
46
|
source: ReleaseLatestSourceSchema,
|
|
47
|
+
/**
|
|
48
|
+
* Owning product, when the release's source is grouped under a product.
|
|
49
|
+
* `null` when the source has no `product_id`. Additive — older API responses
|
|
50
|
+
* omit this field; treat `undefined` as `null`. #1217.
|
|
51
|
+
*/
|
|
52
|
+
product: ReleaseLatestProductSchema.nullable().optional(),
|
|
37
53
|
coverageCount: z.number().int().min(0).optional(),
|
|
38
54
|
// Cached release-body size hint — see {@link ReleaseItemSchema} for the
|
|
39
55
|
// same fields on the org / collection feeds. #958.
|
|
@@ -46,9 +62,31 @@ export const ReleaseLatestResponseSchema = z.object({
|
|
|
46
62
|
releases: z.array(ReleaseLatestItemSchema),
|
|
47
63
|
});
|
|
48
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Display fields for the counterpart release a coverage row points at —
|
|
67
|
+
* the canonical for a `coverage`-role response, or each rolled-up coverage
|
|
68
|
+
* release for a `canonical`-role response. Lets `GET /releases/:id/coverage`
|
|
69
|
+
* render a cluster without a follow-up `GET /releases/:id` per sibling.
|
|
70
|
+
* `null` when the counterpart is suppressed or its source was removed.
|
|
71
|
+
*
|
|
72
|
+
* Coverage-side rows are surfaced here on purpose, even though the
|
|
73
|
+
* `releases_visible` view (and thus `GET /releases/:id`) hides them — a
|
|
74
|
+
* cluster view's whole job is to show its members.
|
|
75
|
+
*/
|
|
76
|
+
export const ReleaseCoverageSiblingSchema = z.object({
|
|
77
|
+
id: z.string(),
|
|
78
|
+
version: z.string().nullable(),
|
|
79
|
+
title: z.string(),
|
|
80
|
+
sourceName: z.string(),
|
|
81
|
+
publishedAt: z.string().nullable(),
|
|
82
|
+
org: z.object({ slug: z.string(), name: z.string() }).nullable(),
|
|
83
|
+
});
|
|
84
|
+
|
|
49
85
|
/**
|
|
50
86
|
* Single row of the `release_coverage` join table. Drives the
|
|
51
|
-
* GET /releases/:id/coverage response.
|
|
87
|
+
* GET /releases/:id/coverage response. `sibling` carries the counterpart
|
|
88
|
+
* release's display fields (see {@link ReleaseCoverageSiblingSchema}); it is
|
|
89
|
+
* optional so an older server that doesn't populate it still validates.
|
|
52
90
|
*/
|
|
53
91
|
export const ReleaseCoverageRowSchema = z.object({
|
|
54
92
|
coverageId: z.string(),
|
|
@@ -56,6 +94,7 @@ export const ReleaseCoverageRowSchema = z.object({
|
|
|
56
94
|
reason: z.string().nullable(),
|
|
57
95
|
decidedBy: z.string(),
|
|
58
96
|
decidedAt: z.string(),
|
|
97
|
+
sibling: ReleaseCoverageSiblingSchema.nullable().optional(),
|
|
59
98
|
});
|
|
60
99
|
|
|
61
100
|
/**
|
|
@@ -160,6 +199,7 @@ export const ReleaseDetailResponseSchema = z.object({
|
|
|
160
199
|
sourceType: z.enum(SOURCE_TYPES).nullable(),
|
|
161
200
|
org: ReleaseDetailOrgSchema.nullable(),
|
|
162
201
|
composition: ReleaseCompositionSchema.nullable(),
|
|
202
|
+
appStore: AppStoreSourceInfoSchema.nullable().optional(),
|
|
163
203
|
});
|
|
164
204
|
|
|
165
205
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ProductDetailSchema } from "./products.js";
|
|
3
|
+
import { SourceDetailSchema } from "./sources.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Response of `GET /v1/orgs/:org/resolve/:slug` (#1190). Product-first: when a
|
|
7
|
+
* product and a source share a slug in an org, the product variant is returned.
|
|
8
|
+
* A 404 (ErrorResponse) is returned when neither matches.
|
|
9
|
+
*/
|
|
10
|
+
export const ResolveResponseSchema = z.discriminatedUnion("kind", [
|
|
11
|
+
z.object({ kind: z.literal("product"), product: ProductDetailSchema }),
|
|
12
|
+
z.object({ kind: z.literal("source"), source: SourceDetailSchema }),
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export type ResolveResponse = z.infer<typeof ResolveResponseSchema>;
|
package/src/schemas/search.ts
CHANGED
|
@@ -68,6 +68,9 @@ export const SearchReleaseHitSchema = z.object({
|
|
|
68
68
|
sourceType: z.string().optional(),
|
|
69
69
|
orgSlug: z.string().nullable(),
|
|
70
70
|
orgName: z.string().nullable().optional(),
|
|
71
|
+
/** Owning product slug — present when the source belongs to a product. Lets
|
|
72
|
+
* the web byline link to the product page instead of the source. */
|
|
73
|
+
productSlug: z.string().nullable().optional(),
|
|
71
74
|
version: z.string().nullable(),
|
|
72
75
|
title: z.string(),
|
|
73
76
|
summary: z.string(),
|
|
@@ -113,8 +116,9 @@ export const SearchCollectionHitSchema = z.object({
|
|
|
113
116
|
/**
|
|
114
117
|
* Heading-aware CHANGELOG.md slice returned by hybrid / semantic search.
|
|
115
118
|
* Clients can deep-link to
|
|
116
|
-
*
|
|
117
|
-
* surrounding file content
|
|
119
|
+
* `/<orgSlug>/<sourceSlug>/changelog?offset=<offset>#chunk` to read the
|
|
120
|
+
* surrounding file content (the web `chunkDeepLink` builder; falls back to
|
|
121
|
+
* the `/source/<sourceSlug>` redirect shim when `orgSlug` is null).
|
|
118
122
|
*/
|
|
119
123
|
export const SearchChunkHitSchema = z.object({
|
|
120
124
|
sourceSlug: z.string(),
|
package/src/schemas/shared.ts
CHANGED
|
@@ -11,6 +11,17 @@ export const MediaItemSchema = z.object({
|
|
|
11
11
|
r2Url: z.string().optional(),
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* App Store platform + icon for a `type: "appstore"` source. Threaded onto
|
|
16
|
+
* read surfaces so the UI can render the compact app-update row (app icon +
|
|
17
|
+
* "Available for iOS/macOS") instead of the standard version/notes/thumbnail
|
|
18
|
+
* layout. Sourced from `source.metadata.appStore` server-side. #mobile-appstore-feed-row
|
|
19
|
+
*/
|
|
20
|
+
export const AppStoreSourceInfoSchema = z.object({
|
|
21
|
+
platform: z.enum(["ios", "macos"]),
|
|
22
|
+
iconUrl: z.string().nullable(),
|
|
23
|
+
});
|
|
24
|
+
|
|
14
25
|
export const PaginationSchema = z.object({
|
|
15
26
|
page: z.number().int().min(1),
|
|
16
27
|
pageSize: z.number().int().min(1),
|
package/src/schemas/sitemap.ts
CHANGED
|
@@ -6,6 +6,12 @@ import { z } from "zod";
|
|
|
6
6
|
* fields, but the current handler always populates them (#875).
|
|
7
7
|
*/
|
|
8
8
|
export const SitemapSourceSchema = z.object({
|
|
9
|
+
/**
|
|
10
|
+
* Source id. `.optional()` for backwards compatibility — added #1190 so the
|
|
11
|
+
* web can route shadowed sources to `/sources/:id`; older/cached responses
|
|
12
|
+
* may omit it and the web degrades to the bare URL.
|
|
13
|
+
*/
|
|
14
|
+
id: z.string().optional(),
|
|
9
15
|
orgSlug: z.string(),
|
|
10
16
|
slug: z.string(),
|
|
11
17
|
latestDate: z.string().nullable(),
|
package/src/schemas/sources.ts
CHANGED
|
@@ -194,6 +194,38 @@ export const SourceMutationResponseSchema = z.looseObject({
|
|
|
194
194
|
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Response from `POST /v1/sources/appstore` — materializes an App Store listing
|
|
199
|
+
* into a curated Org → Product → Source → first Release (modeled on the GitHub
|
|
200
|
+
* coordinate `LookupResponse`).
|
|
201
|
+
*
|
|
202
|
+
* Unlike `SourceMutationResponse`, the `source` here is the **bare inserted (or
|
|
203
|
+
* idempotently looked-up) drizzle row** the materialize handler returns verbatim:
|
|
204
|
+
* no resolved `org { id, slug, name }` block and no `productSlug`. `loose` so the
|
|
205
|
+
* untyped timestamp/counter columns on the row pass through.
|
|
206
|
+
*
|
|
207
|
+
* `status: "indexed"` is a brand-new source (HTTP 201); `"existing"` is the
|
|
208
|
+
* idempotent hit on a prior materialize of the same trackId (HTTP 200).
|
|
209
|
+
*/
|
|
210
|
+
export const AppStoreMaterializeResponseSchema = z.object({
|
|
211
|
+
status: z.enum(["indexed", "existing"]),
|
|
212
|
+
source: z.looseObject({
|
|
213
|
+
id: z.string(),
|
|
214
|
+
slug: z.string(),
|
|
215
|
+
name: z.string(),
|
|
216
|
+
type: SourceTypeSchema,
|
|
217
|
+
url: z.string(),
|
|
218
|
+
orgId: z.string().nullable(),
|
|
219
|
+
productId: z.string().nullable(),
|
|
220
|
+
metadata: z.string().nullable(),
|
|
221
|
+
kind: z.enum(KIND_VALUES).nullable().optional(),
|
|
222
|
+
discovery: SourceDiscoverySchema.optional(),
|
|
223
|
+
isHidden: z.boolean().nullable().optional(),
|
|
224
|
+
createdAt: z.string().optional(),
|
|
225
|
+
}),
|
|
226
|
+
releaseCount: z.number(),
|
|
227
|
+
});
|
|
228
|
+
|
|
197
229
|
/** Body accepted by `PATCH /v1/sources/:slug`. */
|
|
198
230
|
export const SourcePatchInputSchema = z.object({
|
|
199
231
|
name: z.string().optional(),
|
|
@@ -459,12 +491,25 @@ export const SourceMetadataResponseSchema = z.object({
|
|
|
459
491
|
|
|
460
492
|
// ── Changelog probe (POST /sources/:slug/changelog/probe) ──
|
|
461
493
|
|
|
494
|
+
/**
|
|
495
|
+
* A discovered CHANGELOG path returned by the probe. Mirrors
|
|
496
|
+
* `DiscoveredChangelogPath` from `@releases/adapters/github-discovery`:
|
|
497
|
+
* `origin` tags where the path came from (repo root, a workspace package, or
|
|
498
|
+
* an explicit `metadata.changelogPaths` override) and `exists` is true when a
|
|
499
|
+
* directory listing confirmed the file on HEAD.
|
|
500
|
+
*/
|
|
501
|
+
export const DiscoveredChangelogPathSchema = z.object({
|
|
502
|
+
path: z.string(),
|
|
503
|
+
origin: z.enum(["root", "workspace", "override"]),
|
|
504
|
+
exists: z.boolean(),
|
|
505
|
+
});
|
|
506
|
+
|
|
462
507
|
/** Response for `POST /v1/sources/:slug/changelog/probe`. */
|
|
463
508
|
export const ChangelogProbeResponseSchema = z.object({
|
|
464
509
|
sourceId: z.string(),
|
|
465
510
|
sourceSlug: z.string(),
|
|
466
511
|
url: z.string(),
|
|
467
|
-
paths: z.array(
|
|
512
|
+
paths: z.array(DiscoveredChangelogPathSchema),
|
|
468
513
|
});
|
|
469
514
|
|
|
470
515
|
// ── Delete source (DELETE /sources/:slug) ──
|