@buildinternet/releases-api-types 0.33.0 → 0.34.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 +2 -2
- package/src/api-types.ts +243 -3
- package/src/schemas/account-profile.ts +28 -0
- package/src/schemas/collections.ts +21 -0
- package/src/schemas/releases.ts +50 -0
- package/src/schemas/search.ts +9 -0
- package/src/schemas/site-notice.test.ts +67 -0
- package/src/schemas/site-notice.ts +45 -0
- package/src/schemas/taxonomy.ts +11 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buildinternet/releases-api-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@buildinternet/releases-core": "^0.
|
|
20
|
+
"@buildinternet/releases-core": "^0.24.0",
|
|
21
21
|
"zod": "^4.4.3"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
package/src/api-types.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type {
|
|
|
13
13
|
// Re-export above doesn't create local bindings; import for use in interfaces below.
|
|
14
14
|
import type { SourceType, SourceFetchPriority } from "@buildinternet/releases-core/source-enums";
|
|
15
15
|
import type { ApiScope } from "@buildinternet/releases-core/api-token";
|
|
16
|
+
import type { BreakingLevel } from "@buildinternet/releases-core/breaking";
|
|
16
17
|
import type {
|
|
17
18
|
MediaItemSchema,
|
|
18
19
|
PaginationSchema,
|
|
@@ -59,6 +60,12 @@ import type {
|
|
|
59
60
|
OrgRecentReleasesResponseSchema,
|
|
60
61
|
DeleteOrgAccountResponseSchema,
|
|
61
62
|
} from "./schemas/orgs.js";
|
|
63
|
+
import type {
|
|
64
|
+
UploadAvatarResponseSchema,
|
|
65
|
+
WorkspaceProfileFieldsSchema,
|
|
66
|
+
PatchWorkspaceProfileBodySchema,
|
|
67
|
+
WorkspaceProfileResponseSchema,
|
|
68
|
+
} from "./schemas/account-profile.js";
|
|
62
69
|
import type {
|
|
63
70
|
SourceListItemSchema,
|
|
64
71
|
SourceWithOrgSchema,
|
|
@@ -138,9 +145,13 @@ import type {
|
|
|
138
145
|
UpdateReleaseBodySchema,
|
|
139
146
|
ReleasePatchResponseSchema,
|
|
140
147
|
ReleaseDeleteResponseSchema,
|
|
148
|
+
ReleaseBatchDeleteBodySchema,
|
|
149
|
+
ReleaseBatchDeleteResponseSchema,
|
|
141
150
|
ReleaseSuppressResponseSchema,
|
|
142
151
|
ReleaseUnsuppressResponseSchema,
|
|
143
152
|
ReleaseSuppressBodySchema,
|
|
153
|
+
ReleaseBatchSuppressBodySchema,
|
|
154
|
+
ReleaseBatchSuppressResponseSchema,
|
|
144
155
|
ReleaseStreamMessageSchema,
|
|
145
156
|
} from "./schemas/releases.js";
|
|
146
157
|
import type {
|
|
@@ -225,6 +236,8 @@ import type {
|
|
|
225
236
|
ResolvedCollectionMemberSchema,
|
|
226
237
|
ReplaceCollectionMembersResponseSchema,
|
|
227
238
|
AddCollectionMemberResponseSchema,
|
|
239
|
+
CollectionDailySummarySchema,
|
|
240
|
+
CollectionDailySummariesResponseSchema,
|
|
228
241
|
} from "./schemas/collections.js";
|
|
229
242
|
import type {
|
|
230
243
|
OrgOverviewResponseSchema,
|
|
@@ -255,6 +268,7 @@ export {
|
|
|
255
268
|
CategorySchema,
|
|
256
269
|
NoticeSchema,
|
|
257
270
|
} from "./schemas/shared.js";
|
|
271
|
+
export { SiteNoticeSchema, SiteNoticeResponseSchema } from "./schemas/site-notice.js";
|
|
258
272
|
export {
|
|
259
273
|
OrgListItemSchema,
|
|
260
274
|
OrgListResponseSchema,
|
|
@@ -289,6 +303,12 @@ export {
|
|
|
289
303
|
OrgRecentReleasesResponseSchema,
|
|
290
304
|
DeleteOrgAccountResponseSchema,
|
|
291
305
|
} from "./schemas/orgs.js";
|
|
306
|
+
export {
|
|
307
|
+
UploadAvatarResponseSchema,
|
|
308
|
+
WorkspaceProfileFieldsSchema,
|
|
309
|
+
PatchWorkspaceProfileBodySchema,
|
|
310
|
+
WorkspaceProfileResponseSchema,
|
|
311
|
+
} from "./schemas/account-profile.js";
|
|
292
312
|
export {
|
|
293
313
|
ReleasesJsonConfigSchema,
|
|
294
314
|
ReleasesJsonProductSchema,
|
|
@@ -400,9 +420,13 @@ export {
|
|
|
400
420
|
UpdateReleaseBodySchema,
|
|
401
421
|
ReleasePatchResponseSchema,
|
|
402
422
|
ReleaseDeleteResponseSchema,
|
|
423
|
+
ReleaseBatchDeleteBodySchema,
|
|
424
|
+
ReleaseBatchDeleteResponseSchema,
|
|
403
425
|
ReleaseSuppressResponseSchema,
|
|
404
426
|
ReleaseUnsuppressResponseSchema,
|
|
405
427
|
ReleaseSuppressBodySchema,
|
|
428
|
+
ReleaseBatchSuppressBodySchema,
|
|
429
|
+
ReleaseBatchSuppressResponseSchema,
|
|
406
430
|
ReleaseStreamMessageSchema,
|
|
407
431
|
} from "./schemas/releases.js";
|
|
408
432
|
export {
|
|
@@ -462,6 +486,8 @@ export {
|
|
|
462
486
|
ResolvedCollectionMemberSchema,
|
|
463
487
|
ReplaceCollectionMembersResponseSchema,
|
|
464
488
|
AddCollectionMemberResponseSchema,
|
|
489
|
+
CollectionDailySummarySchema,
|
|
490
|
+
CollectionDailySummariesResponseSchema,
|
|
465
491
|
} from "./schemas/collections.js";
|
|
466
492
|
export {
|
|
467
493
|
OrgOverviewResponseSchema,
|
|
@@ -560,6 +586,10 @@ export type CreateOrgBody = z.infer<typeof CreateOrgBodySchema>;
|
|
|
560
586
|
export type UpdateOrgBody = z.infer<typeof UpdateOrgBodySchema>;
|
|
561
587
|
export type SetOrgAvatarBody = z.infer<typeof SetOrgAvatarBodySchema>;
|
|
562
588
|
export type SetOrgAvatarResponse = z.infer<typeof SetOrgAvatarResponseSchema>;
|
|
589
|
+
export type UploadAvatarResponse = z.infer<typeof UploadAvatarResponseSchema>;
|
|
590
|
+
export type WorkspaceProfileFields = z.infer<typeof WorkspaceProfileFieldsSchema>;
|
|
591
|
+
export type PatchWorkspaceProfileBody = z.infer<typeof PatchWorkspaceProfileBodySchema>;
|
|
592
|
+
export type WorkspaceProfileResponse = z.infer<typeof WorkspaceProfileResponseSchema>;
|
|
563
593
|
export type OrgDetail = z.infer<typeof OrgDetailSchema>;
|
|
564
594
|
|
|
565
595
|
export type OrgCatalogItem = z.infer<typeof OrgCatalogItemSchema>;
|
|
@@ -724,6 +754,44 @@ export type ReleaseType = z.infer<typeof ReleaseTypeSchema>;
|
|
|
724
754
|
export type ReleaseItem = z.infer<typeof ReleaseItemSchema>;
|
|
725
755
|
export type ReleaseComposition = z.infer<typeof ReleaseCompositionSchema>;
|
|
726
756
|
|
|
757
|
+
/**
|
|
758
|
+
* One entry in a {@link WhatsChangedResponse} — a release in the requested
|
|
759
|
+
* upgrade range; a slim, release-derived projection. `breaking`/`migrationNotes`
|
|
760
|
+
* come from #1696 (`"unknown"` until classified or for non-dev-facing kinds).
|
|
761
|
+
*/
|
|
762
|
+
export interface WhatsChangedEntry {
|
|
763
|
+
version: string | null;
|
|
764
|
+
publishedAt: string | null;
|
|
765
|
+
/** AI-generated title when present, else the raw release title. */
|
|
766
|
+
title: string | null;
|
|
767
|
+
summary: string | null;
|
|
768
|
+
breaking: BreakingLevel;
|
|
769
|
+
migrationNotes: string | null;
|
|
770
|
+
url: string | null;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Response of `GET /v1/whats-changed` (#1697, upgrade intelligence Phase 1) —
|
|
775
|
+
* the changelog entries in the half-open version range `(from, to]` for a
|
|
776
|
+
* package (`from` exclusive, `to` inclusive). `status: "unknown"` (still
|
|
777
|
+
* HTTP 200 — a valid answer, not an error) when the package can't be resolved
|
|
778
|
+
* read-only from the catalog (e.g. an npm/PyPI name absent until #1345).
|
|
779
|
+
* `entries` are ordered oldest→newest; a range wider than the token budget is
|
|
780
|
+
* truncated (newest entries kept), flagged by `truncated` + `truncatedAtTokens`.
|
|
781
|
+
*/
|
|
782
|
+
export interface WhatsChangedResponse {
|
|
783
|
+
status: "resolved" | "unknown";
|
|
784
|
+
package: string;
|
|
785
|
+
ecosystem: "npm" | "pypi" | "github" | null;
|
|
786
|
+
from: string;
|
|
787
|
+
to: string;
|
|
788
|
+
source: { sourceId: string; sourceSlug: string; orgSlug: string } | null;
|
|
789
|
+
entries: WhatsChangedEntry[];
|
|
790
|
+
count: number;
|
|
791
|
+
truncated: boolean;
|
|
792
|
+
truncatedAtTokens?: number;
|
|
793
|
+
}
|
|
794
|
+
|
|
727
795
|
export interface ReleaseDetail {
|
|
728
796
|
id: string;
|
|
729
797
|
sourceId: string;
|
|
@@ -741,6 +809,16 @@ export interface ReleaseDetail {
|
|
|
741
809
|
titleGenerated?: string | null;
|
|
742
810
|
/** AI-generated smart-brevity headline (#852, renamed in #860). Same fallback as `titleGenerated`. */
|
|
743
811
|
titleShort?: string | null;
|
|
812
|
+
/**
|
|
813
|
+
* Machine-readable breaking-change level (#1696). `.optional()` for mid-deploy
|
|
814
|
+
* / pinned-worker tolerance; absent or `"unknown"` means not classified (the
|
|
815
|
+
* fail-open default). Populated live at ingest for developer-facing source
|
|
816
|
+
* kinds; history is `"unknown"` pending a backfill. Route population + the web
|
|
817
|
+
* chip are #1696 follow-ups.
|
|
818
|
+
*/
|
|
819
|
+
breaking?: BreakingLevel;
|
|
820
|
+
/** Explicit upgrade/migration steps lifted from the body (#1696); null when the body gives none. */
|
|
821
|
+
migrationNotes?: string | null;
|
|
744
822
|
url: string | null;
|
|
745
823
|
media: MediaItem[];
|
|
746
824
|
publishedAt: string | null;
|
|
@@ -751,8 +829,17 @@ export interface ReleaseDetail {
|
|
|
751
829
|
/**
|
|
752
830
|
* Owning org. `avatarUrl` is the resolved `organizations.avatar_url`
|
|
753
831
|
* (nullable; `.optional()` for mid-deploy / pinned-worker tolerance).
|
|
832
|
+
* `discovery` and `isHidden` are additive — older servers omit them.
|
|
754
833
|
*/
|
|
755
|
-
org: {
|
|
834
|
+
org: {
|
|
835
|
+
slug: string;
|
|
836
|
+
name: string;
|
|
837
|
+
avatarUrl?: string | null;
|
|
838
|
+
/** How the org was created. Additive — older servers omit it. */
|
|
839
|
+
discovery?: "curated" | "agent" | "on_demand";
|
|
840
|
+
/** Whether the org is hidden from public listings. Additive — older servers omit it. */
|
|
841
|
+
isHidden?: boolean;
|
|
842
|
+
} | null;
|
|
756
843
|
/**
|
|
757
844
|
* Owning product, when the source is grouped under one (`sources.product_id`).
|
|
758
845
|
* `null` when ungrouped; `.optional()` so older servers that omit it still type.
|
|
@@ -766,6 +853,11 @@ export interface ReleaseDetail {
|
|
|
766
853
|
appStore?: { platform: "ios" | "macos"; iconUrl: string | null } | null;
|
|
767
854
|
/** Video provider tag, present only when `sourceType === "video"`. */
|
|
768
855
|
video?: { provider: "youtube" | "vimeo" | "wistia" } | null;
|
|
856
|
+
/**
|
|
857
|
+
* Whether the release's source is hidden from public listings. Additive —
|
|
858
|
+
* older servers omit it; treat `undefined` as `false`.
|
|
859
|
+
*/
|
|
860
|
+
sourceIsHidden?: boolean;
|
|
769
861
|
}
|
|
770
862
|
|
|
771
863
|
export type ReleaseCoverageSibling = z.infer<typeof ReleaseCoverageSiblingSchema>;
|
|
@@ -788,9 +880,13 @@ export type ReleaseDetailResponse = z.infer<typeof ReleaseDetailResponseSchema>;
|
|
|
788
880
|
export type UpdateReleaseBody = z.infer<typeof UpdateReleaseBodySchema>;
|
|
789
881
|
export type ReleasePatchResponse = z.infer<typeof ReleasePatchResponseSchema>;
|
|
790
882
|
export type ReleaseDeleteResponse = z.infer<typeof ReleaseDeleteResponseSchema>;
|
|
883
|
+
export type ReleaseBatchDeleteBody = z.infer<typeof ReleaseBatchDeleteBodySchema>;
|
|
884
|
+
export type ReleaseBatchDeleteResponse = z.infer<typeof ReleaseBatchDeleteResponseSchema>;
|
|
791
885
|
export type ReleaseSuppressResponse = z.infer<typeof ReleaseSuppressResponseSchema>;
|
|
792
886
|
export type ReleaseUnsuppressResponse = z.infer<typeof ReleaseUnsuppressResponseSchema>;
|
|
793
887
|
export type ReleaseSuppressBody = z.infer<typeof ReleaseSuppressBodySchema>;
|
|
888
|
+
export type ReleaseBatchSuppressBody = z.infer<typeof ReleaseBatchSuppressBodySchema>;
|
|
889
|
+
export type ReleaseBatchSuppressResponse = z.infer<typeof ReleaseBatchSuppressResponseSchema>;
|
|
794
890
|
export type ReleaseStreamMessage = z.infer<typeof ReleaseStreamMessageSchema>;
|
|
795
891
|
|
|
796
892
|
export type ReleaseSummaryItem = z.infer<typeof ReleaseSummaryItemSchema>;
|
|
@@ -849,11 +945,117 @@ export interface FeedTokenResponse {
|
|
|
849
945
|
token: FeedToken | null;
|
|
850
946
|
}
|
|
851
947
|
|
|
948
|
+
/** Cursor pagination shape for GET /v1/me/feed (same as other release feeds). */
|
|
949
|
+
export type PersonalizedFeedPagination = OrgFeedPagination;
|
|
950
|
+
|
|
852
951
|
/**
|
|
853
952
|
* GET /v1/me/feed response — the personalized release feed. Reuses the same item
|
|
854
|
-
* shape as /v1/releases/latest (ReleaseLatestItem)
|
|
953
|
+
* shape as /v1/releases/latest (ReleaseLatestItem); cursor-paginated like other
|
|
954
|
+
* feed-shaped surfaces.
|
|
855
955
|
*/
|
|
856
|
-
export
|
|
956
|
+
export interface PersonalizedFeedResponse {
|
|
957
|
+
items: ReleaseLatestItem[];
|
|
958
|
+
pagination: PersonalizedFeedPagination;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// ── User webhooks ──
|
|
962
|
+
|
|
963
|
+
/** High-level delivery posture derived from summary columns (no AE query). */
|
|
964
|
+
export type WebhookDeliveryHealth =
|
|
965
|
+
| "never_delivered"
|
|
966
|
+
| "healthy"
|
|
967
|
+
| "degraded"
|
|
968
|
+
| "failing"
|
|
969
|
+
| "paused"
|
|
970
|
+
| "auto_paused";
|
|
971
|
+
|
|
972
|
+
/** Self-serve webhook filter scope. */
|
|
973
|
+
export type UserWebhookScope = "org" | "follows";
|
|
974
|
+
|
|
975
|
+
/** Optional per-event filter on release taxonomy type. */
|
|
976
|
+
export type UserWebhookReleaseTypeFilter = "feature" | "rollup";
|
|
977
|
+
|
|
978
|
+
/** Webhook delivery output format. */
|
|
979
|
+
export type UserWebhookFormat = "json" | "slack";
|
|
980
|
+
|
|
981
|
+
/** A user-owned webhook subscription row (no signing secret). */
|
|
982
|
+
export interface UserWebhookSubscription {
|
|
983
|
+
id: string;
|
|
984
|
+
userId: string;
|
|
985
|
+
scope: UserWebhookScope;
|
|
986
|
+
orgId: string | null;
|
|
987
|
+
url: string;
|
|
988
|
+
sourceId: string | null;
|
|
989
|
+
productId: string | null;
|
|
990
|
+
releaseType: UserWebhookReleaseTypeFilter | null;
|
|
991
|
+
format: UserWebhookFormat;
|
|
992
|
+
enabled: boolean;
|
|
993
|
+
description: string | null;
|
|
994
|
+
secretVersion: number;
|
|
995
|
+
createdAt: string;
|
|
996
|
+
lastSuccessAt: string | null;
|
|
997
|
+
lastErrorAt: string | null;
|
|
998
|
+
lastErrorMsg: string | null;
|
|
999
|
+
consecutiveFailures: number;
|
|
1000
|
+
disabledReason: string | null;
|
|
1001
|
+
failureStreakStartedAt: string | null;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/** Delivery health fields returned on user webhook read paths. */
|
|
1005
|
+
export interface UserWebhookDeliveryHealth {
|
|
1006
|
+
deliveryHealth: WebhookDeliveryHealth;
|
|
1007
|
+
deliveryHealthSummary: string;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/** List item returned by GET /v1/me/webhooks — includes org/source display fields. */
|
|
1011
|
+
export interface UserWebhookListItem
|
|
1012
|
+
extends Omit<UserWebhookSubscription, "userId">, UserWebhookDeliveryHealth {
|
|
1013
|
+
orgSlug: string | null;
|
|
1014
|
+
orgName: string | null;
|
|
1015
|
+
sourceSlug: string | null;
|
|
1016
|
+
sourceName: string | null;
|
|
1017
|
+
productSlug: string | null;
|
|
1018
|
+
productName: string | null;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/** GET /v1/me/webhooks response. */
|
|
1022
|
+
export interface UserWebhookListResponse {
|
|
1023
|
+
subscriptions: UserWebhookListItem[];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/** POST /v1/me/webhooks response — signing key shown once at creation (omitted for slack format). */
|
|
1027
|
+
export interface CreateUserWebhookResponse
|
|
1028
|
+
extends UserWebhookSubscription, UserWebhookDeliveryHealth {
|
|
1029
|
+
orgSlug: string | null;
|
|
1030
|
+
orgName: string | null;
|
|
1031
|
+
signingKey?: string;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/** POST /v1/me/webhooks/:id/rotate-secret response. */
|
|
1035
|
+
export interface RotateUserWebhookSecretResponse {
|
|
1036
|
+
secretVersion: number;
|
|
1037
|
+
signingKey: string;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/** POST /v1/me/webhooks/:id/test response. */
|
|
1041
|
+
export interface TestUserWebhookResponse {
|
|
1042
|
+
enqueued: true;
|
|
1043
|
+
eventId: string;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** One Analytics Engine delivery-attempt row from GET …/webhooks/:id/deliveries. */
|
|
1047
|
+
export interface WebhookDeliveryRow {
|
|
1048
|
+
timestamp?: string;
|
|
1049
|
+
event_id?: string;
|
|
1050
|
+
error_message?: string | null;
|
|
1051
|
+
error_code?: string | null;
|
|
1052
|
+
outcome?: string;
|
|
1053
|
+
/** Delivery format — "json" (signed raw event) or "slack" (Block Kit). Empty on pre-format rows. */
|
|
1054
|
+
format?: UserWebhookFormat | "";
|
|
1055
|
+
http_status?: number;
|
|
1056
|
+
latency_ms?: number;
|
|
1057
|
+
attempt?: number;
|
|
1058
|
+
}
|
|
857
1059
|
|
|
858
1060
|
// ── Digest emails ──
|
|
859
1061
|
|
|
@@ -1208,6 +1410,10 @@ export type ReplaceCollectionMembersResponse = z.infer<
|
|
|
1208
1410
|
typeof ReplaceCollectionMembersResponseSchema
|
|
1209
1411
|
>;
|
|
1210
1412
|
export type AddCollectionMemberResponse = z.infer<typeof AddCollectionMemberResponseSchema>;
|
|
1413
|
+
export type CollectionDailySummary = z.infer<typeof CollectionDailySummarySchema>;
|
|
1414
|
+
export type CollectionDailySummariesResponse = z.infer<
|
|
1415
|
+
typeof CollectionDailySummariesResponseSchema
|
|
1416
|
+
>;
|
|
1211
1417
|
|
|
1212
1418
|
// ── Releases (enriched) ──
|
|
1213
1419
|
|
|
@@ -1242,6 +1448,25 @@ export interface LatestRelease {
|
|
|
1242
1448
|
publishedAt: string | null;
|
|
1243
1449
|
sourceName: string;
|
|
1244
1450
|
sourceSlug: string;
|
|
1451
|
+
/**
|
|
1452
|
+
* Source type (`github`/`scrape`/`feed`/`agent`/`appstore`/`video`). Lets the
|
|
1453
|
+
* live feed render the source-type icon for a freshly-arrived event. Additive —
|
|
1454
|
+
* older buffered events / pinned workers omit it.
|
|
1455
|
+
*/
|
|
1456
|
+
sourceType?: string;
|
|
1457
|
+
/**
|
|
1458
|
+
* Owning-org context, resolved at publish time so the live feed can render an
|
|
1459
|
+
* avatar + org name the instant an event arrives. Nested to match
|
|
1460
|
+
* {@link ReleaseDetail.org} and the build-event input. Additive/nullable:
|
|
1461
|
+
* absent for orphan sources or older buffered events. Avatar fallback chain:
|
|
1462
|
+
* `avatarUrl` → `github.com/<githubHandle>.png` → an initial.
|
|
1463
|
+
*/
|
|
1464
|
+
org?: {
|
|
1465
|
+
slug: string;
|
|
1466
|
+
name: string;
|
|
1467
|
+
avatarUrl: string | null;
|
|
1468
|
+
githubHandle: string | null;
|
|
1469
|
+
} | null;
|
|
1245
1470
|
/** AI-generated summary (#852, renamed in #860). Nullable — most rows unpopulated. */
|
|
1246
1471
|
summary: string | null;
|
|
1247
1472
|
/** AI-generated headline (#852, renamed in #860). See {@link ReleaseDetail.titleGenerated}. */
|
|
@@ -1526,6 +1751,21 @@ export interface TokenIdentity {
|
|
|
1526
1751
|
scopes: string[];
|
|
1527
1752
|
principalType: "internal" | "agent" | "user";
|
|
1528
1753
|
principalId?: string | null;
|
|
1754
|
+
/**
|
|
1755
|
+
* Stable internal principal id for consumption telemetry (#1719). Same value the
|
|
1756
|
+
* auth middleware attaches (`relk_` row id, `relu_${keyId}`, …) — never the raw
|
|
1757
|
+
* secret. Returned on self-introspection so MCP can distinguish `relu_` keys.
|
|
1758
|
+
*/
|
|
1759
|
+
tokenId?: string | null;
|
|
1760
|
+
/**
|
|
1761
|
+
* Owning user id for a user-scoped principal (`relu_` key) — the Better Auth
|
|
1762
|
+
* `apikey.referenceId`. Lets a consumer bucket all of an account's credentials
|
|
1763
|
+
* together. Null for the static root key and machine (`relk_`) tokens, which
|
|
1764
|
+
* have no owning user. Distinct from `principalId`, which carries an
|
|
1765
|
+
* agent/internal id for machine tokens. Used by the MCP worker to bucket
|
|
1766
|
+
* `relu_` keys per-account on the rate-limit account tier (#1729).
|
|
1767
|
+
*/
|
|
1768
|
+
userId?: string | null;
|
|
1529
1769
|
expiresAt?: string | null;
|
|
1530
1770
|
lastUsedAt?: string | null;
|
|
1531
1771
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/** Response shared by user + workspace avatar upload routes. */
|
|
4
|
+
export const UploadAvatarResponseSchema = z.object({
|
|
5
|
+
avatarUrl: z.string(),
|
|
6
|
+
key: z.string(),
|
|
7
|
+
width: z.number(),
|
|
8
|
+
height: z.number(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const WorkspaceProfileFieldsSchema = z.object({
|
|
12
|
+
websiteUrl: z.string().nullable(),
|
|
13
|
+
changelogUrl: z.string().nullable(),
|
|
14
|
+
githubHandle: z.string().nullable(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** Body for `PATCH /v1/workspaces/:workspaceId/profile`. */
|
|
18
|
+
export const PatchWorkspaceProfileBodySchema = z.object({
|
|
19
|
+
websiteUrl: z.string().nullable().optional(),
|
|
20
|
+
changelogUrl: z.string().nullable().optional(),
|
|
21
|
+
githubHandle: z.string().nullable().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const WorkspaceProfileResponseSchema = z.object({
|
|
25
|
+
workspaceId: z.string(),
|
|
26
|
+
logo: z.string().nullable(),
|
|
27
|
+
profile: WorkspaceProfileFieldsSchema,
|
|
28
|
+
});
|
|
@@ -85,6 +85,8 @@ export const CollectionDetailSchema = z.object({
|
|
|
85
85
|
description: z.string().nullable(),
|
|
86
86
|
/** Whether the collection is promoted on the homepage. */
|
|
87
87
|
isFeatured: z.boolean(),
|
|
88
|
+
/** Per-collection enable toggle for the nightly daily-summary generation. */
|
|
89
|
+
dailySummaryEnabled: z.boolean(),
|
|
88
90
|
members: z.array(CollectionMemberSchema),
|
|
89
91
|
/** @deprecated Use `members`. Org-only subset, kept for back-compat. */
|
|
90
92
|
orgs: z.array(CollectionMemberOrgSchema),
|
|
@@ -141,6 +143,8 @@ export const CollectionRowSchema = z.object({
|
|
|
141
143
|
name: z.string(),
|
|
142
144
|
description: z.string().nullable(),
|
|
143
145
|
isFeatured: z.boolean(),
|
|
146
|
+
/** Per-collection enable toggle for the nightly daily-summary generation. */
|
|
147
|
+
dailySummaryEnabled: z.boolean(),
|
|
144
148
|
createdAt: z.string(),
|
|
145
149
|
updatedAt: z.string(),
|
|
146
150
|
});
|
|
@@ -163,6 +167,8 @@ export const UpdateCollectionRequestSchema = z.object({
|
|
|
163
167
|
description: z.string().max(2000).nullable().optional(),
|
|
164
168
|
/** Promote / demote the collection on the homepage. */
|
|
165
169
|
isFeatured: z.boolean().optional(),
|
|
170
|
+
/** Enable or disable the nightly daily-summary generation for this collection. */
|
|
171
|
+
dailySummaryEnabled: z.boolean().optional(),
|
|
166
172
|
});
|
|
167
173
|
|
|
168
174
|
/**
|
|
@@ -228,3 +234,18 @@ export const AddCollectionMemberResponseSchema = z.object({
|
|
|
228
234
|
productId: z.string().optional(),
|
|
229
235
|
position: z.number().int(),
|
|
230
236
|
});
|
|
237
|
+
|
|
238
|
+
/** One daily summary row returned by `GET /v1/collections/:slug/daily-summaries`. */
|
|
239
|
+
export const CollectionDailySummarySchema = z.object({
|
|
240
|
+
/** Eastern calendar day, YYYY-MM-DD. */
|
|
241
|
+
date: z.string(),
|
|
242
|
+
title: z.string(),
|
|
243
|
+
summary: z.string(),
|
|
244
|
+
takeaways: z.array(z.string()),
|
|
245
|
+
releaseCount: z.number().int().nonnegative(),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/** Response envelope for `GET /v1/collections/:slug/daily-summaries`. */
|
|
249
|
+
export const CollectionDailySummariesResponseSchema = z.object({
|
|
250
|
+
summaries: z.array(CollectionDailySummarySchema),
|
|
251
|
+
});
|
package/src/schemas/releases.ts
CHANGED
|
@@ -29,6 +29,13 @@ export const ReleaseLatestSourceSchema = z.object({
|
|
|
29
29
|
/** Owning org's display name (e.g. "Cloudflare"), distinct from the source
|
|
30
30
|
* name. Null when the source has no org. Additive — older responses omit it. */
|
|
31
31
|
orgName: z.string().nullable().optional(),
|
|
32
|
+
/** Owning org's explicit avatar URL (`organizations.avatar_url`), when set.
|
|
33
|
+
* Null/absent when the org has no stored avatar — clients fall back to the
|
|
34
|
+
* GitHub handle below, then an initial. Additive — older responses omit it. */
|
|
35
|
+
orgAvatarUrl: z.string().nullable().optional(),
|
|
36
|
+
/** Owning org's GitHub handle, used to derive `github.com/<handle>.png` as the
|
|
37
|
+
* avatar fallback when `orgAvatarUrl` is unset. Additive — older responses omit it. */
|
|
38
|
+
orgGithubHandle: z.string().nullable().optional(),
|
|
32
39
|
});
|
|
33
40
|
|
|
34
41
|
export const ReleaseLatestProductSchema = z.object({
|
|
@@ -178,6 +185,17 @@ export const ReleaseDetailOrgSchema = z.object({
|
|
|
178
185
|
* related-rail feed.
|
|
179
186
|
*/
|
|
180
187
|
avatarUrl: z.string().nullable().optional(),
|
|
188
|
+
/**
|
|
189
|
+
* How the org was created. Additive — older servers omit it; treat
|
|
190
|
+
* `undefined` as `"curated"`. Used by the web to emit `noindex` on
|
|
191
|
+
* on-demand-materialized org pages.
|
|
192
|
+
*/
|
|
193
|
+
discovery: z.enum(["curated", "agent", "on_demand"]).optional(),
|
|
194
|
+
/**
|
|
195
|
+
* Whether the org is hidden from public listings. Additive — older servers
|
|
196
|
+
* omit it; treat `undefined` as `false`. Used by the web to emit `noindex`.
|
|
197
|
+
*/
|
|
198
|
+
isHidden: z.boolean().optional(),
|
|
181
199
|
});
|
|
182
200
|
|
|
183
201
|
/**
|
|
@@ -219,6 +237,12 @@ export const ReleaseDetailResponseSchema = z.object({
|
|
|
219
237
|
composition: ReleaseCompositionSchema.nullable(),
|
|
220
238
|
appStore: AppStoreSourceInfoSchema.nullable().optional(),
|
|
221
239
|
video: VideoSourceInfoSchema.nullable().optional(),
|
|
240
|
+
/**
|
|
241
|
+
* Whether the release's source is hidden from public listings. Additive —
|
|
242
|
+
* older servers omit it; treat `undefined` as `false`. Used by the web to
|
|
243
|
+
* emit `noindex` on pages whose source is an on-demand-materialized stub.
|
|
244
|
+
*/
|
|
245
|
+
sourceIsHidden: z.boolean().optional(),
|
|
222
246
|
});
|
|
223
247
|
|
|
224
248
|
/**
|
|
@@ -287,6 +311,32 @@ export const ReleaseDeleteResponseSchema = z.object({
|
|
|
287
311
|
deleted: z.literal(true),
|
|
288
312
|
});
|
|
289
313
|
|
|
314
|
+
/** Body accepted by `DELETE /v1/releases/batch`. */
|
|
315
|
+
export const ReleaseBatchDeleteBodySchema = z.object({
|
|
316
|
+
releaseIds: z.array(z.string()).min(1),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/** Response returned by `DELETE /v1/releases/batch`. */
|
|
320
|
+
export const ReleaseBatchDeleteResponseSchema = z.object({
|
|
321
|
+
deleted: z.number().int().min(0),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Body accepted by `POST /v1/releases/batch-suppress`. When `suppressed` is
|
|
326
|
+
* true the optional `reason` is stored in `suppressed_reason`; when false the
|
|
327
|
+
* reason is cleared.
|
|
328
|
+
*/
|
|
329
|
+
export const ReleaseBatchSuppressBodySchema = z.object({
|
|
330
|
+
releaseIds: z.array(z.string()).min(1),
|
|
331
|
+
suppressed: z.boolean(),
|
|
332
|
+
reason: z.string().optional(),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
/** Response returned by `POST /v1/releases/batch-suppress`. */
|
|
336
|
+
export const ReleaseBatchSuppressResponseSchema = z.object({
|
|
337
|
+
updated: z.number().int().min(0),
|
|
338
|
+
});
|
|
339
|
+
|
|
290
340
|
/** Response returned by `POST /v1/releases/:id/suppress`. */
|
|
291
341
|
export const ReleaseSuppressResponseSchema = z.object({
|
|
292
342
|
suppressed: z.literal(true),
|
package/src/schemas/search.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./shared.js";
|
|
9
9
|
import { SourceTypeSchema } from "./sources.js";
|
|
10
10
|
import { LookupStatusSchema } from "./lookups.js";
|
|
11
|
+
import { CollectionMemberSchema } from "./collections.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Org hit on the unified `/v1/search` response. The list is built either
|
|
@@ -130,6 +131,14 @@ export const SearchCollectionHitSchema = z.object({
|
|
|
130
131
|
score: z.number().optional(),
|
|
131
132
|
/** Result-set org slugs that triggered this rollup. Always present on `via=member`. */
|
|
132
133
|
matchedOrgSlugs: z.array(z.string()).optional(),
|
|
134
|
+
/**
|
|
135
|
+
* A small org preview (capped at 3) for an inline avatar facepile on the
|
|
136
|
+
* search card, mirroring the collections list page. Org-kind only — search's
|
|
137
|
+
* `memberCount` counts orgs, so the facepile and its "+N more" stay
|
|
138
|
+
* consistent. Attached after hit merge; optional on the wire so older workers
|
|
139
|
+
* mid-rollout don't trip the schema.
|
|
140
|
+
*/
|
|
141
|
+
previewMembers: z.array(CollectionMemberSchema).optional(),
|
|
133
142
|
});
|
|
134
143
|
|
|
135
144
|
/**
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { SiteNoticeSchema, SiteNoticeResponseSchema } from "./site-notice";
|
|
3
|
+
|
|
4
|
+
const valid = {
|
|
5
|
+
active: true,
|
|
6
|
+
message: "We shipped a new feed",
|
|
7
|
+
linkText: "See it",
|
|
8
|
+
href: "https://releases.sh/updates",
|
|
9
|
+
placement: "banner" as const,
|
|
10
|
+
color: "#0081e7",
|
|
11
|
+
dismissible: false,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("SiteNoticeSchema", () => {
|
|
15
|
+
it("accepts a fully-specified notice", () => {
|
|
16
|
+
expect(SiteNoticeSchema.safeParse(valid).success).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it("accepts a site-relative href and omitted link", () => {
|
|
19
|
+
const { href, linkText, ...rest } = valid;
|
|
20
|
+
expect(SiteNoticeSchema.safeParse({ ...rest, href: "/updates" }).success).toBe(true);
|
|
21
|
+
expect(SiteNoticeSchema.safeParse(rest).success).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it("rejects an over-length message", () => {
|
|
24
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, message: "x".repeat(281) }).success).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it("rejects an empty message", () => {
|
|
27
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, message: "" }).success).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it("rejects a bad placement", () => {
|
|
30
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, placement: "footer" }).success).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
it("rejects a non-hex color", () => {
|
|
33
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, color: "blue" }).success).toBe(false);
|
|
34
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, color: "#fff" }).success).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it("rejects an href that is neither absolute http(s) nor root-relative", () => {
|
|
37
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, href: "ftp://x.y" }).success).toBe(false);
|
|
38
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, href: "updates" }).success).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it("rejects linkText without an href (dead CTA)", () => {
|
|
41
|
+
const { href, ...noHref } = valid; // keeps linkText
|
|
42
|
+
expect(SiteNoticeSchema.safeParse(noHref).success).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("accepts href without linkText", () => {
|
|
45
|
+
const { linkText, ...noLinkText } = valid;
|
|
46
|
+
expect(SiteNoticeSchema.safeParse(noLinkText).success).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("rejects javascript:/data: schemes and protocol-relative hrefs", () => {
|
|
49
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, href: "javascript:alert(1)" }).success).toBe(
|
|
50
|
+
false,
|
|
51
|
+
);
|
|
52
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, href: "data:text/html,x" }).success).toBe(false);
|
|
53
|
+
expect(SiteNoticeSchema.safeParse({ ...valid, href: "//evil.com" }).success).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("SiteNoticeResponseSchema", () => {
|
|
58
|
+
it("accepts null", () => {
|
|
59
|
+
expect(SiteNoticeResponseSchema.safeParse({ notice: null }).success).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
it("accepts a stored notice with updatedAt", () => {
|
|
62
|
+
const r = SiteNoticeResponseSchema.safeParse({
|
|
63
|
+
notice: { ...valid, updatedAt: "2026-06-11T00:00:00.000Z" },
|
|
64
|
+
});
|
|
65
|
+
expect(r.success).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SITE_NOTICE_PLACEMENTS } from "@buildinternet/releases-core/site-notice";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Absolute http(s) URL or a site-relative path beginning with a single "/".
|
|
6
|
+
* Rejects other schemes (javascript:, data:, …) and protocol-relative "//host"
|
|
7
|
+
* so a stored href can never become an unexpected-scheme or off-site link.
|
|
8
|
+
*/
|
|
9
|
+
const HrefSchema = z
|
|
10
|
+
.string()
|
|
11
|
+
.max(500)
|
|
12
|
+
.refine((h) => /^https?:\/\//.test(h) || (h.startsWith("/") && !h.startsWith("//")), {
|
|
13
|
+
message: "href must be an absolute http(s) URL or a site-relative path",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Editable site-notice fields. Kept structurally in sync with the
|
|
18
|
+
* `SiteNotice` interface in `@buildinternet/releases-core/site-notice`. Split
|
|
19
|
+
* from the refined `SiteNoticeSchema` below so the response schema can `.extend()`
|
|
20
|
+
* it (a `.refine()` returns a non-extendable effect).
|
|
21
|
+
*/
|
|
22
|
+
const SiteNoticeFieldsSchema = z.object({
|
|
23
|
+
active: z.boolean(),
|
|
24
|
+
message: z.string().min(1).max(280),
|
|
25
|
+
linkText: z.string().min(1).max(60).optional(),
|
|
26
|
+
href: HrefSchema.optional(),
|
|
27
|
+
placement: z.enum(SITE_NOTICE_PLACEMENTS),
|
|
28
|
+
color: z.string().regex(/^#[0-9a-fA-F]{6}$/, "color must be a 6-digit hex like #0081e7"),
|
|
29
|
+
dismissible: z.boolean(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Editable site-notice payload. A bare `linkText` with no `href` renders nothing
|
|
34
|
+
* (the view only draws a link when there's a target), so require `href` whenever
|
|
35
|
+
* `linkText` is set.
|
|
36
|
+
*/
|
|
37
|
+
export const SiteNoticeSchema = SiteNoticeFieldsSchema.refine((n) => !(n.linkText && !n.href), {
|
|
38
|
+
message: "href is required when linkText is set",
|
|
39
|
+
path: ["href"],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/** GET /v1/site-notice response: the stored notice (+updatedAt) or null. */
|
|
43
|
+
export const SiteNoticeResponseSchema = z.object({
|
|
44
|
+
notice: SiteNoticeFieldsSchema.extend({ updatedAt: z.iso.datetime() }).nullable(),
|
|
45
|
+
});
|
package/src/schemas/taxonomy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CategorySchema } from "./shared.js";
|
|
3
|
-
import { CollectionReleaseItemSchema } from "./collections.js";
|
|
3
|
+
import { CollectionMemberSchema, CollectionReleaseItemSchema } from "./collections.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Org rollup row shared by category and tag detail responses. Slim by
|
|
@@ -38,6 +38,16 @@ export const CategoryListItemSchema = z.object({
|
|
|
38
38
|
aliases: z.array(z.string()),
|
|
39
39
|
orgCount: z.number().int().min(0),
|
|
40
40
|
productCount: z.number().int().min(0),
|
|
41
|
+
/**
|
|
42
|
+
* A small mixed-kind preview (capped at 3) of the orgs and products in this
|
|
43
|
+
* category, so the list page can render an inline avatar facepile without a
|
|
44
|
+
* second round trip — same shape and intent as a collection's
|
|
45
|
+
* `previewMembers`. Org entries are surfaced first (they carry the avatar);
|
|
46
|
+
* product entries fill any remaining slots and dedupe against orgs already
|
|
47
|
+
* shown. The list page derives "+N more" from `orgCount + productCount`.
|
|
48
|
+
* Optional on the wire so older workers mid-rollout don't trip the schema.
|
|
49
|
+
*/
|
|
50
|
+
previewMembers: z.array(CollectionMemberSchema).optional(),
|
|
41
51
|
});
|
|
42
52
|
|
|
43
53
|
export const CategoryListResponseSchema = z.array(CategoryListItemSchema);
|