@buildinternet/releases-api-types 0.28.0 → 0.30.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.30.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
@@ -32,6 +32,8 @@ import type {
32
32
  OrgTagsResponseSchema,
33
33
  CreateOrgBodySchema,
34
34
  UpdateOrgBodySchema,
35
+ SetOrgAvatarBodySchema,
36
+ SetOrgAvatarResponseSchema,
35
37
  OrgDetailSchema,
36
38
  OrgCatalogItemSchema,
37
39
  OrgCatalogResponseSchema,
@@ -250,6 +252,7 @@ export {
250
252
  OverviewPageItemSchema,
251
253
  OverviewCitationSchema,
252
254
  CategorySchema,
255
+ NoticeSchema,
253
256
  } from "./schemas/shared.js";
254
257
  export {
255
258
  OrgListItemSchema,
@@ -259,6 +262,8 @@ export {
259
262
  OrgTagsResponseSchema,
260
263
  CreateOrgBodySchema,
261
264
  UpdateOrgBodySchema,
265
+ SetOrgAvatarBodySchema,
266
+ SetOrgAvatarResponseSchema,
262
267
  OrgDetailSchema,
263
268
  OrgCatalogItemSchema,
264
269
  OrgCatalogResponseSchema,
@@ -514,6 +519,8 @@ export type OrgAccountsResponse = z.infer<typeof OrgAccountsResponseSchema>;
514
519
  export type OrgTagsResponse = z.infer<typeof OrgTagsResponseSchema>;
515
520
  export type CreateOrgBody = z.infer<typeof CreateOrgBodySchema>;
516
521
  export type UpdateOrgBody = z.infer<typeof UpdateOrgBodySchema>;
522
+ export type SetOrgAvatarBody = z.infer<typeof SetOrgAvatarBodySchema>;
523
+ export type SetOrgAvatarResponse = z.infer<typeof SetOrgAvatarResponseSchema>;
517
524
  export type OrgDetail = z.infer<typeof OrgDetailSchema>;
518
525
 
519
526
  export type OrgCatalogItem = z.infer<typeof OrgCatalogItemSchema>;
@@ -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,20 @@ 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(),
119
+ });
120
+
121
+ /** Body for `POST /v1/orgs/:slug/avatar` — the remote image to mirror to R2 (#1406). */
122
+ export const SetOrgAvatarBodySchema = z.object({
123
+ sourceUrl: z.url(),
124
+ });
125
+
126
+ /** Response for `POST /v1/orgs/:slug/avatar` — the mirrored avatar's public URL + dims. */
127
+ export const SetOrgAvatarResponseSchema = z.object({
128
+ avatarUrl: z.string(),
129
+ key: z.string(),
130
+ width: z.number(),
131
+ height: z.number(),
117
132
  });
118
133
 
119
134
  // Org detail's products query selects a strict subset of `ProductListItem` —
@@ -455,4 +470,5 @@ export const OrgDetailSchema = z.object({
455
470
  sources: z.array(SourceListItemSchema),
456
471
  overview: OverviewPageItemSchema.nullable().optional(),
457
472
  playbook: OrgDetailPlaybookSchema.nullable().optional(),
473
+ notice: NoticeSchema.nullable().optional(),
458
474
  });
@@ -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`. */