@buildinternet/releases-api-types 0.28.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.28.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,
@@ -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
  /**
@@ -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);
@@ -176,6 +181,7 @@ export const SourceDetailSchema = z.object({
176
181
  // carry `null`. Consumers treat null/undefined as "unknown".
177
182
  stars: z.number().int().min(0).nullable().optional(),
178
183
  starsFetchedAt: z.string().nullable().optional(),
184
+ notice: NoticeSchema.nullable().optional(),
179
185
  });
180
186
 
181
187
  /**
@@ -263,6 +269,7 @@ export const SourcePatchInputSchema = z.object({
263
269
  kind: z.enum(KIND_VALUES).nullable().optional(),
264
270
  /** Admin-only: promote or demote the source's discovery status (curated/agent/on_demand). */
265
271
  discovery: SourceDiscoverySchema.optional(),
272
+ notice: NoticeSchema.nullable().optional(),
266
273
  });
267
274
 
268
275
  /** Body accepted by `POST /v1/sources`. */