@buildinternet/releases-api-types 0.32.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 +39 -0
- package/src/schemas/releases.ts +9 -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
|
@@ -829,12 +829,47 @@ export interface FollowMutationResponse {
|
|
|
829
829
|
following: boolean;
|
|
830
830
|
}
|
|
831
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
|
+
|
|
832
852
|
/**
|
|
833
853
|
* GET /v1/me/feed response — the personalized release feed. Reuses the same item
|
|
834
854
|
* shape as /v1/releases/latest (ReleaseLatestItem) inside the standard list envelope.
|
|
835
855
|
*/
|
|
836
856
|
export type PersonalizedFeedResponse = ListResponse<ReleaseLatestItem>;
|
|
837
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
|
+
|
|
838
873
|
// ── Search ──
|
|
839
874
|
|
|
840
875
|
export type SearchOrgHit = z.infer<typeof SearchOrgHitSchema>;
|
|
@@ -845,6 +880,8 @@ export type SearchSourceHit = z.infer<typeof SearchSourceHitSchema>;
|
|
|
845
880
|
export interface RawSourceHit extends SearchSourceHit {
|
|
846
881
|
productName?: string;
|
|
847
882
|
productCategory?: string;
|
|
883
|
+
/** Owning org's avatar URL — folded onto the catalog hit for the byline avatar. */
|
|
884
|
+
orgAvatarUrl?: string | null;
|
|
848
885
|
/** Entity taxonomy kind from the `sources.kind` column. */
|
|
849
886
|
entityKind?: string | null;
|
|
850
887
|
}
|
|
@@ -870,6 +907,7 @@ export function foldSourcesIntoCatalog(
|
|
|
870
907
|
name: s.productName ?? s.name,
|
|
871
908
|
orgSlug: s.orgSlug,
|
|
872
909
|
orgName: s.orgName,
|
|
910
|
+
orgAvatarUrl: s.orgAvatarUrl ?? null,
|
|
873
911
|
category: s.productCategory ?? null,
|
|
874
912
|
entryType: "product",
|
|
875
913
|
kind: undefined,
|
|
@@ -881,6 +919,7 @@ export function foldSourcesIntoCatalog(
|
|
|
881
919
|
name: s.name,
|
|
882
920
|
orgSlug: s.orgSlug,
|
|
883
921
|
orgName: s.orgName,
|
|
922
|
+
orgAvatarUrl: s.orgAvatarUrl ?? null,
|
|
884
923
|
category: null,
|
|
885
924
|
entryType: "source",
|
|
886
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({
|
|
@@ -240,6 +243,12 @@ export const UpdateReleaseBodySchema = z
|
|
|
240
243
|
titleShort: z.string().nullable().optional(),
|
|
241
244
|
// `null` removes `$.composition` from metadata; an object replaces it.
|
|
242
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(),
|
|
243
252
|
})
|
|
244
253
|
.strict();
|
|
245
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) ──
|