@buildinternet/releases-api-types 0.31.0 → 0.33.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 +89 -1
- package/src/schemas/releases.ts +23 -0
- package/src/schemas/search.ts +3 -0
- package/src/schemas/shared.ts +14 -0
- package/src/schemas/sources.ts +15 -1
package/package.json
CHANGED
package/src/api-types.ts
CHANGED
|
@@ -748,7 +748,16 @@ export interface ReleaseDetail {
|
|
|
748
748
|
sourceName: string;
|
|
749
749
|
sourceSlug: string;
|
|
750
750
|
sourceType: string;
|
|
751
|
-
|
|
751
|
+
/**
|
|
752
|
+
* Owning org. `avatarUrl` is the resolved `organizations.avatar_url`
|
|
753
|
+
* (nullable; `.optional()` for mid-deploy / pinned-worker tolerance).
|
|
754
|
+
*/
|
|
755
|
+
org: { slug: string; name: string; avatarUrl?: string | null } | null;
|
|
756
|
+
/**
|
|
757
|
+
* Owning product, when the source is grouped under one (`sources.product_id`).
|
|
758
|
+
* `null` when ungrouped; `.optional()` so older servers that omit it still type.
|
|
759
|
+
*/
|
|
760
|
+
product?: { slug: string; name: string } | null;
|
|
752
761
|
/** Release type. See {@link ReleaseType}. */
|
|
753
762
|
type?: ReleaseType;
|
|
754
763
|
/** Per-category item counts from the AI release-content pass. See {@link ReleaseComposition}. */
|
|
@@ -786,6 +795,81 @@ export type ReleaseStreamMessage = z.infer<typeof ReleaseStreamMessageSchema>;
|
|
|
786
795
|
|
|
787
796
|
export type ReleaseSummaryItem = z.infer<typeof ReleaseSummaryItemSchema>;
|
|
788
797
|
|
|
798
|
+
// ── Follows ──
|
|
799
|
+
|
|
800
|
+
/** What a user can follow. */
|
|
801
|
+
export type FollowTarget = "org" | "product";
|
|
802
|
+
|
|
803
|
+
/** A user's follow, enriched for rendering (returned by GET /v1/me/follows). */
|
|
804
|
+
export interface Follow {
|
|
805
|
+
targetType: FollowTarget;
|
|
806
|
+
targetId: string;
|
|
807
|
+
name: string;
|
|
808
|
+
slug: string;
|
|
809
|
+
avatarUrl: string | null;
|
|
810
|
+
/** Owning org slug for product follows (null for org follows). */
|
|
811
|
+
orgSlug: string | null;
|
|
812
|
+
createdAt: string;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** GET /v1/me/follows response. */
|
|
816
|
+
export interface FollowsListResponse {
|
|
817
|
+
follows: Follow[];
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** POST /v1/me/follows request body. */
|
|
821
|
+
export interface FollowRequest {
|
|
822
|
+
targetType: FollowTarget;
|
|
823
|
+
targetId: string;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** POST/DELETE /v1/me/follows response. */
|
|
827
|
+
export interface FollowMutationResponse {
|
|
828
|
+
success: true;
|
|
829
|
+
following: boolean;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* A user's personalized feed token, including the full re-revealable feed URL.
|
|
834
|
+
* The token is reversible (stored recoverably) because the feed serves only
|
|
835
|
+
* public release data and carries no PII — so the URL can be re-displayed and
|
|
836
|
+
* copied on any visit (see #1519 design, decision 6). One per user.
|
|
837
|
+
*/
|
|
838
|
+
export interface FeedToken {
|
|
839
|
+
/** Absolute, tokenized Atom URL — e.g. https://api.releases.sh/v1/feed/relf_…_….atom */
|
|
840
|
+
feedUrl: string;
|
|
841
|
+
/** Non-secret public handle (for masked display). */
|
|
842
|
+
lookupId: string;
|
|
843
|
+
createdAt: string;
|
|
844
|
+
lastUsedAt: string | null;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/** `GET /v1/me/feed/token` — the token, or null if the user has none yet. */
|
|
848
|
+
export interface FeedTokenResponse {
|
|
849
|
+
token: FeedToken | null;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* GET /v1/me/feed response — the personalized release feed. Reuses the same item
|
|
854
|
+
* shape as /v1/releases/latest (ReleaseLatestItem) inside the standard list envelope.
|
|
855
|
+
*/
|
|
856
|
+
export type PersonalizedFeedResponse = ListResponse<ReleaseLatestItem>;
|
|
857
|
+
|
|
858
|
+
// ── Digest emails ──
|
|
859
|
+
|
|
860
|
+
/** How often a user wants a digest email. `off` = no emails. */
|
|
861
|
+
export type DigestCadence = "off" | "daily" | "weekly";
|
|
862
|
+
|
|
863
|
+
/** GET /v1/me/digest response — the caller's current cadence. */
|
|
864
|
+
export interface DigestPrefsResponse {
|
|
865
|
+
cadence: DigestCadence;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** PUT /v1/me/digest request body. */
|
|
869
|
+
export interface DigestPrefsRequest {
|
|
870
|
+
cadence: DigestCadence;
|
|
871
|
+
}
|
|
872
|
+
|
|
789
873
|
// ── Search ──
|
|
790
874
|
|
|
791
875
|
export type SearchOrgHit = z.infer<typeof SearchOrgHitSchema>;
|
|
@@ -796,6 +880,8 @@ export type SearchSourceHit = z.infer<typeof SearchSourceHitSchema>;
|
|
|
796
880
|
export interface RawSourceHit extends SearchSourceHit {
|
|
797
881
|
productName?: string;
|
|
798
882
|
productCategory?: string;
|
|
883
|
+
/** Owning org's avatar URL — folded onto the catalog hit for the byline avatar. */
|
|
884
|
+
orgAvatarUrl?: string | null;
|
|
799
885
|
/** Entity taxonomy kind from the `sources.kind` column. */
|
|
800
886
|
entityKind?: string | null;
|
|
801
887
|
}
|
|
@@ -821,6 +907,7 @@ export function foldSourcesIntoCatalog(
|
|
|
821
907
|
name: s.productName ?? s.name,
|
|
822
908
|
orgSlug: s.orgSlug,
|
|
823
909
|
orgName: s.orgName,
|
|
910
|
+
orgAvatarUrl: s.orgAvatarUrl ?? null,
|
|
824
911
|
category: s.productCategory ?? null,
|
|
825
912
|
entryType: "product",
|
|
826
913
|
kind: undefined,
|
|
@@ -832,6 +919,7 @@ export function foldSourcesIntoCatalog(
|
|
|
832
919
|
name: s.name,
|
|
833
920
|
orgSlug: s.orgSlug,
|
|
834
921
|
orgName: s.orgName,
|
|
922
|
+
orgAvatarUrl: s.orgAvatarUrl ?? null,
|
|
835
923
|
category: null,
|
|
836
924
|
entryType: "source",
|
|
837
925
|
kind: (s.entityKind as SearchCatalogHit["kind"]) ?? undefined,
|
package/src/schemas/releases.ts
CHANGED
|
@@ -26,6 +26,9 @@ export const ReleaseLatestSourceSchema = z.object({
|
|
|
26
26
|
name: z.string(),
|
|
27
27
|
type: z.enum(SOURCE_TYPES),
|
|
28
28
|
orgSlug: z.string().nullable(),
|
|
29
|
+
/** Owning org's display name (e.g. "Cloudflare"), distinct from the source
|
|
30
|
+
* name. Null when the source has no org. Additive — older responses omit it. */
|
|
31
|
+
orgName: z.string().nullable().optional(),
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
export const ReleaseLatestProductSchema = z.object({
|
|
@@ -168,6 +171,13 @@ export const ReleasesWithMediaResponseSchema = z.array(ReleaseWithMediaRowSchema
|
|
|
168
171
|
export const ReleaseDetailOrgSchema = z.object({
|
|
169
172
|
slug: z.string(),
|
|
170
173
|
name: z.string(),
|
|
174
|
+
/**
|
|
175
|
+
* Resolved org avatar URL (the `organizations.avatar_url` column). `null`
|
|
176
|
+
* when the org has no stored avatar. Additive — older servers omit it, so
|
|
177
|
+
* treat `undefined` as `null`. Mirrors the `orgAvatarUrl` field on the
|
|
178
|
+
* related-rail feed.
|
|
179
|
+
*/
|
|
180
|
+
avatarUrl: z.string().nullable().optional(),
|
|
171
181
|
});
|
|
172
182
|
|
|
173
183
|
/**
|
|
@@ -199,6 +209,13 @@ export const ReleaseDetailResponseSchema = z.object({
|
|
|
199
209
|
sourceSlug: z.string().nullable(),
|
|
200
210
|
sourceType: z.enum(SOURCE_TYPES).nullable(),
|
|
201
211
|
org: ReleaseDetailOrgSchema.nullable(),
|
|
212
|
+
/**
|
|
213
|
+
* Owning product, when the release's source is grouped under a product
|
|
214
|
+
* (`sources.product_id`). `null` when the source has no product. Additive —
|
|
215
|
+
* older servers omit this field; treat `undefined` as `null`. Reuses the
|
|
216
|
+
* `{ slug, name }` shape from the latest feed.
|
|
217
|
+
*/
|
|
218
|
+
product: ReleaseLatestProductSchema.nullable().optional(),
|
|
202
219
|
composition: ReleaseCompositionSchema.nullable(),
|
|
203
220
|
appStore: AppStoreSourceInfoSchema.nullable().optional(),
|
|
204
221
|
video: VideoSourceInfoSchema.nullable().optional(),
|
|
@@ -226,6 +243,12 @@ export const UpdateReleaseBodySchema = z
|
|
|
226
243
|
titleShort: z.string().nullable().optional(),
|
|
227
244
|
// `null` removes `$.composition` from metadata; an object replaces it.
|
|
228
245
|
composition: ReleaseCompositionSchema.nullable().optional(),
|
|
246
|
+
// Replace the release's stored `media[]` wholesale (curator manual edit).
|
|
247
|
+
// Items whose `url` is not already an R2/`media.releases.sh` origin are run
|
|
248
|
+
// through the ingest R2 mirror (`processMediaForR2`) so posters/images land
|
|
249
|
+
// in `released-media` and get `r2Key`/`r2Url` stamped — same path as ingest.
|
|
250
|
+
// Reuses the shared `MediaItemSchema` (type/url/alt/r2Url/r2Key/linkUrl).
|
|
251
|
+
media: z.array(MediaItemSchema).optional(),
|
|
229
252
|
})
|
|
230
253
|
.strict();
|
|
231
254
|
|
package/src/schemas/search.ts
CHANGED
|
@@ -40,6 +40,9 @@ export const SearchCatalogHitSchema = z.object({
|
|
|
40
40
|
name: z.string(),
|
|
41
41
|
orgSlug: z.string().nullable(),
|
|
42
42
|
orgName: z.string().nullable(),
|
|
43
|
+
/** Owning org's avatar URL, for a small inline byline avatar. Products have
|
|
44
|
+
* no avatar of their own, so the web row anchors on the org's instead. */
|
|
45
|
+
orgAvatarUrl: z.string().nullable().optional(),
|
|
43
46
|
// Loose `z.string().nullable()` rather than the canonical `CategorySchema`
|
|
44
47
|
// enum because `foldSourcesIntoCatalog` and the catalog-search handlers
|
|
45
48
|
// pass `s.productCategory` / `o.category` through without validating
|
package/src/schemas/shared.ts
CHANGED
|
@@ -9,7 +9,21 @@ export const MediaItemSchema = z.object({
|
|
|
9
9
|
type: z.enum(["image", "video", "gif"]),
|
|
10
10
|
url: z.string(),
|
|
11
11
|
alt: z.string().optional(),
|
|
12
|
+
// R2 object key (`releases/<hash>.<ext>`) for media mirrored into the
|
|
13
|
+
// `released-media` bucket; read paths resolve it to a same-origin `r2Url`
|
|
14
|
+
// (`resolveR2Url`). Stamped by `processMediaForR2` at ingest / manual edit and
|
|
15
|
+
// part of the stored `media[]` JSON — surfaced on the wire so a curator round-
|
|
16
|
+
// tripping `media` through `PATCH /v1/releases/:id` can preserve an already-
|
|
17
|
+
// mirrored item without forcing a re-fetch.
|
|
18
|
+
r2Key: z.string().optional(),
|
|
12
19
|
r2Url: z.string().optional(),
|
|
20
|
+
// For `type: "video"` items promoted from a hosted-video link found inline in
|
|
21
|
+
// a release body (Wistia/Loom/Vimeo/YouTube), `url` holds the poster/thumbnail
|
|
22
|
+
// (mirrored to R2 like any image) and `linkUrl` holds the human watch URL the
|
|
23
|
+
// play-card links out to. Absent for ordinary image/gif media. See
|
|
24
|
+
// `detectInlineVideos` in `@releases/rendering/video-embed` and web's
|
|
25
|
+
// release-content video card.
|
|
26
|
+
linkUrl: z.string().optional(),
|
|
13
27
|
});
|
|
14
28
|
|
|
15
29
|
/**
|
package/src/schemas/sources.ts
CHANGED
|
@@ -475,13 +475,27 @@ export const CreateSourceSummaryResponseSchema = z.object({
|
|
|
475
475
|
// ── Source fetch trigger (POST /sources/:slug/fetch) ──
|
|
476
476
|
|
|
477
477
|
/**
|
|
478
|
-
* Response shape for `POST /v1/sources/:slug/fetch`.
|
|
478
|
+
* Response shape for `POST /v1/sources/:slug/fetch`. Three branches:
|
|
479
479
|
* - Feed/GitHub/scraped-with-feed sources: `{ fetched: true, releasesInserted, … }`
|
|
480
480
|
* - Scrape/agent sources (flagged for CLI pickup): `{ queued: true, type: "flagged" }`
|
|
481
|
+
* - Render dry-run probe (`?dryRun=true` on a client-rendered scrape source):
|
|
482
|
+
* `{ renderCheck: true, rendered, candidateCount, sampleUrls, … }`
|
|
481
483
|
*/
|
|
482
484
|
export const SourceFetchResponseSchema = z.union([
|
|
483
485
|
z.looseObject({ fetched: z.literal(true) }),
|
|
484
486
|
z.object({ queued: z.literal(true), type: z.string() }),
|
|
487
|
+
// Render dry-run probe (#1528): renders a client-rendered scrape source's
|
|
488
|
+
// index once and reports candidate links found, without the managed-agent
|
|
489
|
+
// extraction loop.
|
|
490
|
+
z.looseObject({
|
|
491
|
+
renderCheck: z.literal(true),
|
|
492
|
+
status: z.enum(["dry_run", "error"]),
|
|
493
|
+
rendered: z.boolean(),
|
|
494
|
+
candidateCount: z.number().int(),
|
|
495
|
+
sampleUrls: z.array(z.string()),
|
|
496
|
+
durationMs: z.number().int(),
|
|
497
|
+
error: z.string().optional(),
|
|
498
|
+
}),
|
|
485
499
|
]);
|
|
486
500
|
|
|
487
501
|
// ── Source content-hash check (POST /sources/:slug/content-hash) ──
|