@buildinternet/releases-api-types 0.27.0 → 0.29.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buildinternet/releases-api-types",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
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.22.0",
20
+ "@buildinternet/releases-core": "^0.23.0",
21
21
  "zod": "^4.4.3"
22
22
  },
23
23
  "publishConfig": {
package/src/api-types.ts CHANGED
@@ -250,6 +250,7 @@ export {
250
250
  OverviewPageItemSchema,
251
251
  OverviewCitationSchema,
252
252
  CategorySchema,
253
+ NoticeSchema,
253
254
  } from "./schemas/shared.js";
254
255
  export {
255
256
  OrgListItemSchema,
@@ -636,7 +637,10 @@ export interface StuckSource {
636
637
  isHidden: boolean;
637
638
  /** Non-`dry_run` attempts examined in the window (all errors when stuck). */
638
639
  recentAttempts: number;
639
- /** Errors among the examined attempts (equals `recentAttempts` when stuck). */
640
+ /**
641
+ * Failed or degraded attempts (`error`, `crawl_timeout`, `blocked`) among the
642
+ * examined attempts (equals `recentAttempts` when stuck).
643
+ */
640
644
  recentErrors: number;
641
645
  /** Most-recent fetch attempt timestamp (ISO), or null. */
642
646
  lastAttemptAt: string | null;
@@ -1169,6 +1173,24 @@ export interface FetchLogEntry {
1169
1173
  createdAt: string;
1170
1174
  }
1171
1175
 
1176
+ /**
1177
+ * The managed-agent session currently fetching a source, surfaced read-side on
1178
+ * the fetch-log view so an operator/agent can tell "a fetch is in flight" from
1179
+ * "stuck/dead" without waiting for a terminal `fetch_log` row (#1360). Only
1180
+ * running, non-stale sessions are reported (the StatusHub DO drops sessions idle
1181
+ * past its staleness cutoff), so the presence of this object means a fetch is
1182
+ * live; `startedAt` says how long it has been running.
1183
+ */
1184
+ export interface ActiveFetchSession {
1185
+ sessionId: string;
1186
+ /** Always `running` today — only running sessions are surfaced. */
1187
+ status: string;
1188
+ /** Session start, epoch ms. */
1189
+ startedAt: number;
1190
+ /** Last session activity, epoch ms. */
1191
+ lastUpdatedAt: number;
1192
+ }
1193
+
1172
1194
  // ── Usage ──
1173
1195
 
1174
1196
  export interface UsageBreakdownRow {
@@ -38,6 +38,8 @@ export const LookupSourceSchema = z.looseObject({
38
38
  isHidden: z.boolean().nullable().optional(),
39
39
  discovery: z.enum(["curated", "agent", "on_demand"]).optional(),
40
40
  metadata: z.string().nullable(),
41
+ stars: z.number().int().min(0).nullable().optional(),
42
+ starsFetchedAt: z.string().nullable().optional(),
41
43
  createdAt: z.string().optional(),
42
44
  });
43
45
 
@@ -4,6 +4,7 @@ import {
4
4
  AppStoreSourceInfoSchema,
5
5
  CategorySchema,
6
6
  ListResponseSchema,
7
+ NoticeSchema,
7
8
  OverviewPageItemSchema,
8
9
  PaginationSchema,
9
10
  ReleaseItemSchema,
@@ -114,6 +115,7 @@ export const UpdateOrgBodySchema = z.object({
114
115
  featured: z.boolean().optional(),
115
116
  /** Admin-only: promote or demote the org's discovery status (curated/agent/on_demand). */
116
117
  discovery: z.enum(["curated", "agent", "on_demand"]).optional(),
118
+ notice: NoticeSchema.nullable().optional(),
117
119
  });
118
120
 
119
121
  // Org detail's products query selects a strict subset of `ProductListItem` —
@@ -455,4 +457,5 @@ export const OrgDetailSchema = z.object({
455
457
  sources: z.array(SourceListItemSchema),
456
458
  overview: OverviewPageItemSchema.nullable().optional(),
457
459
  playbook: OrgDetailPlaybookSchema.nullable().optional(),
460
+ notice: NoticeSchema.nullable().optional(),
458
461
  });
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { KIND_VALUES } from "@buildinternet/releases-core/kinds";
3
- import { CategorySchema, ListResponseSchema, ReleaseItemSchema } from "./shared.js";
3
+ import { CategorySchema, ListResponseSchema, NoticeSchema, ReleaseItemSchema } from "./shared.js";
4
4
  import { SourceTypeSchema, SourceFeedPaginationSchema } from "./sources.js";
5
5
 
6
6
  /**
@@ -89,6 +89,7 @@ export const ProductDetailSchema = ProductRowSchema.extend({
89
89
  releases: z.array(ReleaseItemSchema).optional(),
90
90
  /** Cursor pagination for the embedded release feed — set by the web format adapter (PR #1207), absent on bare product detail. */
91
91
  pagination: SourceFeedPaginationSchema.optional(),
92
+ notice: NoticeSchema.nullable().optional(),
92
93
  });
93
94
 
94
95
  /**
@@ -125,6 +126,7 @@ export const UpdateProductBodySchema = z.object({
125
126
  aliases: z.array(z.string()).optional(),
126
127
  kind: z.enum(KIND_VALUES).nullable().optional(),
127
128
  avatarUrl: z.string().nullable().optional(),
129
+ notice: NoticeSchema.nullable().optional(),
128
130
  });
129
131
 
130
132
  /**
@@ -60,6 +60,7 @@ export const SearchSourceHitSchema = z.object({
60
60
  orgSlug: z.string().nullable(),
61
61
  orgName: z.string().nullable(),
62
62
  productSlug: z.string().nullable(),
63
+ stars: z.number().int().min(0).nullable().optional(),
63
64
  });
64
65
 
65
66
  export const SearchReleaseHitSchema = z.object({
@@ -164,6 +165,7 @@ export const LookupResultPayloadSchema = z.object({
164
165
  name: z.string(),
165
166
  url: z.string(),
166
167
  discovery: z.enum(["curated", "agent", "on_demand"]),
168
+ stars: z.number().int().min(0).nullable().optional(),
167
169
  })
168
170
  .optional(),
169
171
  releases: z
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { NoticeSchema } from "./shared";
3
+
4
+ describe("NoticeSchema", () => {
5
+ it("accepts a message-only notice", () => {
6
+ expect(NoticeSchema.safeParse({ message: "Heads up" }).success).toBe(true);
7
+ });
8
+ it("accepts a message + internal coordinate", () => {
9
+ expect(
10
+ NoticeSchema.safeParse({
11
+ message: "Moved",
12
+ coordinate: "cognition/devin",
13
+ linkText: "View Devin",
14
+ }).success,
15
+ ).toBe(true);
16
+ });
17
+ it("accepts a message + external href", () => {
18
+ expect(NoticeSchema.safeParse({ message: "Moved", href: "https://devin.ai" }).success).toBe(
19
+ true,
20
+ );
21
+ });
22
+ it("rejects both coordinate and href", () => {
23
+ expect(
24
+ NoticeSchema.safeParse({ message: "x", coordinate: "a/b", href: "https://x.com" }).success,
25
+ ).toBe(false);
26
+ });
27
+ it("rejects an empty message and an over-long message", () => {
28
+ expect(NoticeSchema.safeParse({ message: "" }).success).toBe(false);
29
+ expect(NoticeSchema.safeParse({ message: "x".repeat(281) }).success).toBe(false);
30
+ });
31
+ it("rejects a malformed coordinate", () => {
32
+ expect(NoticeSchema.safeParse({ message: "x", coordinate: "/leading" }).success).toBe(false);
33
+ expect(NoticeSchema.safeParse({ message: "x", coordinate: "a/b/c" }).success).toBe(false);
34
+ expect(NoticeSchema.safeParse({ message: "x", coordinate: "" }).success).toBe(false);
35
+ });
36
+ });
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { RELEASE_TYPES } from "@buildinternet/releases-core/schema";
3
3
  import { CATEGORIES } from "@buildinternet/releases-core/categories";
4
+ import { isValidNoticeCoordinate } from "@buildinternet/releases-core/notice";
4
5
 
5
6
  export const CategorySchema = z.enum(CATEGORIES);
6
7
 
@@ -150,6 +151,26 @@ export const OverviewCitationSchema = z.object({
150
151
  releaseId: z.string().nullable().optional(),
151
152
  });
152
153
 
154
+ /**
155
+ * Entity notice — a small curator-set note on an org / product / source. Stored
156
+ * under the `notice` key of the entity's metadata JSON; mirrors the `Notice`
157
+ * type in @buildinternet/releases-core/notice. `coordinate` is an internal
158
+ * registry path ("org" / "org/slug"); `href` is an external URL; at most one.
159
+ */
160
+ export const NoticeSchema = z
161
+ .object({
162
+ message: z.string().min(1).max(280),
163
+ linkText: z.string().min(1).max(60).optional(),
164
+ coordinate: z.string().min(1).max(200).optional(),
165
+ href: z.url().max(500).optional(),
166
+ })
167
+ .refine((n) => !(n.coordinate && n.href), {
168
+ message: "notice may set coordinate or href, not both",
169
+ })
170
+ .refine((n) => !n.coordinate || isValidNoticeCoordinate(n.coordinate), {
171
+ message: "notice.coordinate must be 'org' or 'org/slug'",
172
+ });
173
+
153
174
  export const OverviewPageItemSchema = z.object({
154
175
  scope: z.enum(["org", "product"]),
155
176
  orgSlug: z.string().nullable().optional(),
@@ -5,7 +5,12 @@ import {
5
5
  SOURCE_FETCH_PRIORITIES,
6
6
  } from "@buildinternet/releases-core/source-enums";
7
7
  import { KIND_VALUES } from "@buildinternet/releases-core/kinds";
8
- import { ListResponseSchema, ReleaseItemSchema, ReleaseSummaryItemSchema } from "./shared.js";
8
+ import {
9
+ ListResponseSchema,
10
+ NoticeSchema,
11
+ ReleaseItemSchema,
12
+ ReleaseSummaryItemSchema,
13
+ } from "./shared.js";
9
14
 
10
15
  export const SourceTypeSchema = z.enum(SOURCE_TYPES);
11
16
  export const SourceDiscoverySchema = z.enum(SOURCE_DISCOVERY);
@@ -50,6 +55,11 @@ export const SourceListItemSchema = z.object({
50
55
  productName: z.string().nullable().optional(),
51
56
  productSlug: z.string().nullable().optional(),
52
57
  kind: z.enum(KIND_VALUES).nullable().optional(),
58
+ // GitHub stargazer count. `nullable().optional()` (the house idiom for these
59
+ // shapes): non-github sources and older responses omit it; a fetched row may
60
+ // carry `null`. Consumers treat null/undefined as "unknown".
61
+ stars: z.number().int().min(0).nullable().optional(),
62
+ starsFetchedAt: z.string().nullable().optional(),
53
63
  });
54
64
 
55
65
  /**
@@ -84,6 +94,11 @@ export const SourceWithOrgSchema = z.object({
84
94
  medianGapDays: z.number().nullable(),
85
95
  lastRetieredAt: z.string().nullable(),
86
96
  kind: z.enum(KIND_VALUES).nullable().optional(),
97
+ // GitHub stargazer count. `nullable().optional()` (the house idiom for these
98
+ // shapes): non-github sources and older responses omit it; a fetched row may
99
+ // carry `null`. Consumers treat null/undefined as "unknown".
100
+ stars: z.number().int().min(0).nullable().optional(),
101
+ starsFetchedAt: z.string().nullable().optional(),
87
102
  });
88
103
 
89
104
  export const SourceListResponseSchema = ListResponseSchema(SourceWithOrgSchema);
@@ -161,6 +176,12 @@ export const SourceDetailSchema = z.object({
161
176
  pagination: SourceFeedPaginationSchema,
162
177
  summaries: SourceDetailSummariesSchema,
163
178
  kind: z.enum(KIND_VALUES).nullable().optional(),
179
+ // GitHub stargazer count. `nullable().optional()` (the house idiom for these
180
+ // shapes): non-github sources and older responses omit it; a fetched row may
181
+ // carry `null`. Consumers treat null/undefined as "unknown".
182
+ stars: z.number().int().min(0).nullable().optional(),
183
+ starsFetchedAt: z.string().nullable().optional(),
184
+ notice: NoticeSchema.nullable().optional(),
164
185
  });
165
186
 
166
187
  /**
@@ -248,6 +269,7 @@ export const SourcePatchInputSchema = z.object({
248
269
  kind: z.enum(KIND_VALUES).nullable().optional(),
249
270
  /** Admin-only: promote or demote the source's discovery status (curated/agent/on_demand). */
250
271
  discovery: SourceDiscoverySchema.optional(),
272
+ notice: NoticeSchema.nullable().optional(),
251
273
  });
252
274
 
253
275
  /** Body accepted by `POST /v1/sources`. */