@buildinternet/releases-api-types 0.59.0 → 0.60.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.
Files changed (126) hide show
  1. package/README.md +14 -6
  2. package/dist/api-types.d.ts +1168 -0
  3. package/dist/api-types.d.ts.map +1 -0
  4. package/dist/api-types.js +134 -0
  5. package/dist/schemas/account-profile.d.ts +29 -0
  6. package/dist/schemas/account-profile.d.ts.map +1 -0
  7. package/dist/schemas/account-profile.js +24 -0
  8. package/dist/schemas/ai-models.d.ts +67 -0
  9. package/dist/schemas/ai-models.d.ts.map +1 -0
  10. package/{src/schemas/ai-models.ts → dist/schemas/ai-models.js} +25 -31
  11. package/dist/schemas/api-keys.d.ts +71 -0
  12. package/dist/schemas/api-keys.d.ts.map +1 -0
  13. package/dist/schemas/api-keys.js +32 -0
  14. package/dist/schemas/classifications.d.ts +134 -0
  15. package/dist/schemas/classifications.d.ts.map +1 -0
  16. package/dist/schemas/classifications.js +96 -0
  17. package/dist/schemas/collections.d.ts +605 -0
  18. package/dist/schemas/collections.d.ts.map +1 -0
  19. package/dist/schemas/collections.js +320 -0
  20. package/dist/schemas/datasets.d.ts +34 -0
  21. package/dist/schemas/datasets.d.ts.map +1 -0
  22. package/dist/schemas/datasets.js +11 -0
  23. package/dist/schemas/errors.d.ts +43 -0
  24. package/dist/schemas/errors.d.ts.map +1 -0
  25. package/dist/schemas/errors.js +61 -0
  26. package/dist/schemas/feedback.d.ts +134 -0
  27. package/dist/schemas/feedback.d.ts.map +1 -0
  28. package/dist/schemas/feedback.js +76 -0
  29. package/dist/schemas/follows.d.ts +154 -0
  30. package/dist/schemas/follows.d.ts.map +1 -0
  31. package/dist/schemas/follows.js +42 -0
  32. package/dist/schemas/listing.d.ts +374 -0
  33. package/dist/schemas/listing.d.ts.map +1 -0
  34. package/dist/schemas/listing.js +175 -0
  35. package/dist/schemas/lookups.d.ts +285 -0
  36. package/dist/schemas/lookups.d.ts.map +1 -0
  37. package/{src/schemas/lookups.ts → dist/schemas/lookups.js} +67 -80
  38. package/dist/schemas/marketing-classifier.d.ts +32 -0
  39. package/dist/schemas/marketing-classifier.d.ts.map +1 -0
  40. package/{src/schemas/marketing-classifier.ts → dist/schemas/marketing-classifier.js} +14 -18
  41. package/dist/schemas/orgs.d.ts +950 -0
  42. package/dist/schemas/orgs.d.ts.map +1 -0
  43. package/dist/schemas/orgs.js +473 -0
  44. package/dist/schemas/overviews.d.ts +205 -0
  45. package/dist/schemas/overviews.d.ts.map +1 -0
  46. package/{src/schemas/overviews.ts → dist/schemas/overviews.js} +41 -54
  47. package/dist/schemas/products.d.ts +595 -0
  48. package/dist/schemas/products.d.ts.map +1 -0
  49. package/{src/schemas/products.ts → dist/schemas/products.js} +113 -136
  50. package/dist/schemas/publish-tokens.d.ts +55 -0
  51. package/dist/schemas/publish-tokens.d.ts.map +1 -0
  52. package/{src/schemas/publish-tokens.ts → dist/schemas/publish-tokens.js} +18 -29
  53. package/dist/schemas/recommendations.d.ts +52 -0
  54. package/dist/schemas/recommendations.d.ts.map +1 -0
  55. package/dist/schemas/recommendations.js +58 -0
  56. package/dist/schemas/related.d.ts +331 -0
  57. package/dist/schemas/related.d.ts.map +1 -0
  58. package/dist/schemas/related.js +115 -0
  59. package/dist/schemas/releases.d.ts +623 -0
  60. package/dist/schemas/releases.d.ts.map +1 -0
  61. package/dist/schemas/releases.js +414 -0
  62. package/dist/schemas/resolve.d.ts +259 -0
  63. package/dist/schemas/resolve.d.ts.map +1 -0
  64. package/{src/schemas/resolve.ts → dist/schemas/resolve.js} +2 -5
  65. package/dist/schemas/search.d.ts +475 -0
  66. package/dist/schemas/search.d.ts.map +1 -0
  67. package/dist/schemas/search.js +258 -0
  68. package/dist/schemas/semantic-alert-summary.d.ts +54 -0
  69. package/dist/schemas/semantic-alert-summary.d.ts.map +1 -0
  70. package/dist/schemas/semantic-alert-summary.js +57 -0
  71. package/dist/schemas/semantic-alerts.d.ts +41 -0
  72. package/dist/schemas/semantic-alerts.d.ts.map +1 -0
  73. package/dist/schemas/semantic-alerts.js +42 -0
  74. package/dist/schemas/shared.d.ts +247 -0
  75. package/dist/schemas/shared.d.ts.map +1 -0
  76. package/dist/schemas/shared.js +248 -0
  77. package/dist/schemas/site-notice.d.ts +35 -0
  78. package/dist/schemas/site-notice.d.ts.map +1 -0
  79. package/{src/schemas/site-notice.ts → dist/schemas/site-notice.js} +14 -18
  80. package/dist/schemas/sitemap.d.ts +97 -0
  81. package/dist/schemas/sitemap.d.ts.map +1 -0
  82. package/dist/schemas/sitemap.js +107 -0
  83. package/dist/schemas/sources.d.ts +1145 -0
  84. package/dist/schemas/sources.d.ts.map +1 -0
  85. package/dist/schemas/sources.js +645 -0
  86. package/dist/schemas/stats.d.ts +82 -0
  87. package/dist/schemas/stats.d.ts.map +1 -0
  88. package/dist/schemas/stats.js +59 -0
  89. package/dist/schemas/taxonomy.d.ts +344 -0
  90. package/dist/schemas/taxonomy.d.ts.map +1 -0
  91. package/{src/schemas/taxonomy.ts → dist/schemas/taxonomy.js} +47 -58
  92. package/dist/schemas/webhooks.d.ts +618 -0
  93. package/dist/schemas/webhooks.d.ts.map +1 -0
  94. package/dist/schemas/webhooks.js +191 -0
  95. package/dist/schemas/well-known.d.ts +302 -0
  96. package/dist/schemas/well-known.d.ts.map +1 -0
  97. package/dist/schemas/well-known.js +192 -0
  98. package/dist/schemas/workspace-integrations.d.ts +27 -0
  99. package/dist/schemas/workspace-integrations.d.ts.map +1 -0
  100. package/dist/schemas/workspace-integrations.js +22 -0
  101. package/package.json +16 -5
  102. package/src/api-types.ts +0 -2229
  103. package/src/schemas/account-profile.ts +0 -28
  104. package/src/schemas/ai-models.test.ts +0 -29
  105. package/src/schemas/classifications.ts +0 -109
  106. package/src/schemas/collections.ts +0 -346
  107. package/src/schemas/datasets.ts +0 -31
  108. package/src/schemas/errors.test.ts +0 -55
  109. package/src/schemas/errors.ts +0 -81
  110. package/src/schemas/feedback.ts +0 -56
  111. package/src/schemas/listing.ts +0 -226
  112. package/src/schemas/orgs.ts +0 -533
  113. package/src/schemas/recommendations.ts +0 -39
  114. package/src/schemas/related.ts +0 -126
  115. package/src/schemas/releases.ts +0 -449
  116. package/src/schemas/search.ts +0 -274
  117. package/src/schemas/semantic-alert-summary.ts +0 -65
  118. package/src/schemas/shared.notice.test.ts +0 -36
  119. package/src/schemas/shared.ts +0 -267
  120. package/src/schemas/site-notice.test.ts +0 -67
  121. package/src/schemas/sitemap.ts +0 -117
  122. package/src/schemas/sources.ts +0 -674
  123. package/src/schemas/stats.ts +0 -62
  124. package/src/schemas/well-known.test.ts +0 -264
  125. package/src/schemas/well-known.ts +0 -252
  126. package/src/schemas/workspace-integrations.ts +0 -26
@@ -1,28 +0,0 @@
1
- import { z } from "zod";
2
-
3
- /** Response shared by user + workspace avatar upload routes. */
4
- export const UploadAvatarResponseSchema = z.object({
5
- avatarUrl: z.string(),
6
- key: z.string(),
7
- width: z.number(),
8
- height: z.number(),
9
- });
10
-
11
- export const WorkspaceProfileFieldsSchema = z.object({
12
- websiteUrl: z.string().nullable(),
13
- changelogUrl: z.string().nullable(),
14
- githubHandle: z.string().nullable(),
15
- });
16
-
17
- /** Body for `PATCH /v1/workspaces/:workspaceId/profile`. */
18
- export const PatchWorkspaceProfileBodySchema = z.object({
19
- websiteUrl: z.string().nullable().optional(),
20
- changelogUrl: z.string().nullable().optional(),
21
- githubHandle: z.string().nullable().optional(),
22
- });
23
-
24
- export const WorkspaceProfileResponseSchema = z.object({
25
- workspaceId: z.string(),
26
- logo: z.string().nullable(),
27
- profile: WorkspaceProfileFieldsSchema,
28
- });
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { AiLaneModelsPutSchema } from "./ai-models";
3
-
4
- describe("AiLaneModelsPutSchema", () => {
5
- it("accepts a partial map", () => {
6
- expect(
7
- AiLaneModelsPutSchema.safeParse({
8
- models: { summarize: "deepseek/deepseek-v4.1-flash" },
9
- }).success,
10
- ).toBe(true);
11
- });
12
-
13
- it("accepts null to clear an override", () => {
14
- expect(AiLaneModelsPutSchema.safeParse({ models: { extract: null } }).success).toBe(true);
15
- });
16
-
17
- it("rejects an empty models object", () => {
18
- expect(AiLaneModelsPutSchema.safeParse({ models: {} }).success).toBe(false);
19
- });
20
-
21
- it("rejects unknown keys", () => {
22
- expect(
23
- AiLaneModelsPutSchema.safeParse({
24
- models: { summarize: "x" },
25
- extra: true,
26
- }).success,
27
- ).toBe(false);
28
- });
29
- });
@@ -1,109 +0,0 @@
1
- import { z } from "zod";
2
-
3
- /**
4
- * Admin classification analytics (`GET /v1/admin/classifications/summary`
5
- * and `/recent`). Decisions live in Analytics Engine; these shapes are the
6
- * hydrated read model, not the positional point schema.
7
- */
8
-
9
- const ClassificationOriginSchema = z.enum(["ingest", "manual", "eval"]);
10
- const ClassificationOriginFilterSchema = z.enum(["ingest", "manual", "eval", "all"]);
11
- const ClassificationDispositionSchema = z.enum(["kept", "suppressed", "failed", "skipped"]);
12
-
13
- const ProbabilityBinSchema = z.object({
14
- /** Inclusive. Width is 0.1. See `probability.threshold` for the current
15
- * effective suppression threshold and which bin it falls in. */
16
- start: z.number(),
17
- /** Exclusive, except the last bin (`0.9`–`1`) which includes `1`. */
18
- end: z.number(),
19
- count: z.number(),
20
- });
21
-
22
- const ProbabilityHistogramSchema = z.object({
23
- bins: z.array(ProbabilityBinSchema),
24
- /** Sampled rows whose double is `< 0` (the absent sentinel). */
25
- missing: z.number(),
26
- });
27
-
28
- /** `GET /v1/admin/classifications/summary`. */
29
- export const ClassificationSummarySchema = z.object({
30
- after: z.string(),
31
- before: z.string(),
32
- bucket: z.enum(["hour", "day"]),
33
- origin: ClassificationOriginFilterSchema,
34
- totals: z.object({
35
- classified: z.number(),
36
- kept: z.number(),
37
- suppressed: z.number(),
38
- failed: z.number(),
39
- skipped: z.number(),
40
- costUsd: z.number(),
41
- /** `suppressed / (kept + suppressed)`, or null when the denominator is 0. */
42
- suppressionRate: z.number().nullable(),
43
- }),
44
- series: z.array(
45
- z.object({
46
- t: z.string(),
47
- kept: z.number(),
48
- suppressed: z.number(),
49
- failed: z.number(),
50
- skipped: z.number(),
51
- }),
52
- ),
53
- choices: z.array(z.object({ choice: z.string(), count: z.number() })),
54
- choiceSeries: z.array(
55
- z.object({
56
- t: z.string(),
57
- choices: z.record(z.string(), z.number()),
58
- }),
59
- ),
60
- models: z.array(
61
- z.object({
62
- provider: z.string(),
63
- model: z.string(),
64
- count: z.number(),
65
- costUsd: z.number(),
66
- }),
67
- ),
68
- probability: z.object({
69
- /** Current effective suppression threshold (operator override, else the
70
- * code default) — no longer a fixed literal now that it's editable via
71
- * `GET/PUT /v1/admin/marketing-classifier`. */
72
- threshold: z.number(),
73
- selected: ProbabilityHistogramSchema,
74
- confidence: ProbabilityHistogramSchema,
75
- }),
76
- meta: z.object({
77
- dataset: z.string(),
78
- retentionDays: z.literal(90),
79
- sampled: z.literal(true),
80
- }),
81
- });
82
-
83
- /** One sampled decision from `GET /v1/admin/classifications/recent`. */
84
- export const ClassificationRecentItemSchema = z.object({
85
- timestamp: z.string(),
86
- origin: ClassificationOriginSchema,
87
- sourceId: z.string().nullable(),
88
- sourceName: z.string().nullable(),
89
- sourceSlug: z.string().nullable(),
90
- orgSlug: z.string().nullable(),
91
- releaseId: z.string().nullable(),
92
- releaseTitle: z.string().nullable(),
93
- provider: z.string(),
94
- model: z.string(),
95
- choice: z.string(),
96
- selectedChoiceProbability: z.number().nullable(),
97
- providerConfidence: z.number().nullable(),
98
- disposition: ClassificationDispositionSchema,
99
- reason: z.string(),
100
- failureCategory: z.string(),
101
- costUsd: z.number().nullable(),
102
- durationMs: z.number().nullable(),
103
- });
104
-
105
- /** `GET /v1/admin/classifications/recent` — newest sampled rows, not weighted. */
106
- export const ClassificationRecentResponseSchema = z.object({
107
- items: z.array(ClassificationRecentItemSchema),
108
- nextCursor: z.string().nullable(),
109
- });
@@ -1,346 +0,0 @@
1
- import { z } from "zod";
2
- import { ImportanceScoreSchema, ReleaseCompositionSchema, ReleaseItemSchema } from "./shared.js";
3
-
4
- /**
5
- * One org as it appears inside a collection's preview list or detail page.
6
- * Joined through `organizations_public`, so soft-deleted / on_demand orgs
7
- * never leak via a collection.
8
- */
9
- export const CollectionMemberOrgSchema = z.object({
10
- slug: z.string(),
11
- name: z.string(),
12
- domain: z.string().nullable(),
13
- avatarUrl: z.string().nullable(),
14
- /** GitHub handle from `org_accounts`; lets the avatar fall back to github.com/<handle>.png. */
15
- githubHandle: z.string().nullable(),
16
- description: z.string().nullable(),
17
- });
18
-
19
- /**
20
- * Org context attached to a product member so the chip can render the parent
21
- * org's avatar / handle. Subset of `CollectionMemberOrg` — description is
22
- * intentionally omitted because the byline comes from the product itself.
23
- */
24
- export const ProductParentOrgSchema = z.object({
25
- slug: z.string(),
26
- name: z.string(),
27
- domain: z.string().nullable(),
28
- avatarUrl: z.string().nullable(),
29
- githubHandle: z.string().nullable(),
30
- });
31
-
32
- /**
33
- * One product as it appears inside a collection. Carries the parent org block
34
- * so the chip can render the org's avatar (products don't have their own).
35
- */
36
- export const CollectionMemberProductSchema = z.object({
37
- slug: z.string(),
38
- name: z.string(),
39
- description: z.string().nullable(),
40
- org: ProductParentOrgSchema,
41
- });
42
-
43
- /**
44
- * Mixed-kind member entry. `kind` discriminates: `org` carries the org block at
45
- * the top level; `product` carries the product fields plus a `org` parent block.
46
- * Used by `GET /v1/collections/:slug` (`members` field) and the new
47
- * `previewMembers` on `GET /v1/collections`.
48
- */
49
- export const CollectionMemberSchema = z.discriminatedUnion("kind", [
50
- CollectionMemberOrgSchema.extend({ kind: z.literal("org") }),
51
- CollectionMemberProductSchema.extend({ kind: z.literal("product") }),
52
- ]);
53
-
54
- /**
55
- * Row on `GET /v1/collections`. `previewMembers` (the new mixed-kind preview)
56
- * is the first few visible members so the list page can render inline avatars
57
- * without a second round trip. `previewOrgs` is the legacy org-only preview,
58
- * retained for back-compat — clients should migrate to `previewMembers`.
59
- */
60
- export const CollectionListItemSchema = z.object({
61
- slug: z.string(),
62
- name: z.string(),
63
- description: z.string().nullable(),
64
- memberCount: z.number().int().min(0),
65
- /** Whether the collection is promoted on the homepage. Filterable via `?featured=1`. */
66
- isFeatured: z.boolean(),
67
- /** Mixed-kind preview (org + product). Cap matches `previewOrgs`. */
68
- previewMembers: z.array(CollectionMemberSchema).optional(),
69
- /** Legacy org-only preview. Populated with the org-kind subset of `previewMembers`. */
70
- previewOrgs: z.array(CollectionMemberOrgSchema).optional(),
71
- });
72
-
73
- export const CollectionListResponseSchema = z.array(CollectionListItemSchema);
74
-
75
- /**
76
- * Returned by `GET /v1/collections/:slug`.
77
- *
78
- * `members` is the mixed-kind canonical list. `orgs` is the legacy org-only
79
- * field, populated with the org-kind subset of `members` for back-compat —
80
- * new clients should consume `members`.
81
- */
82
- export const CollectionDetailSchema = z.object({
83
- slug: z.string(),
84
- name: z.string(),
85
- description: z.string().nullable(),
86
- /** Whether the collection is promoted on the homepage. */
87
- isFeatured: z.boolean(),
88
- /** Per-collection enable toggle for the nightly daily-summary generation. */
89
- dailySummaryEnabled: z.boolean(),
90
- /** Whether the collection generates weekly digests (gates the email-subscribe toggle). */
91
- weeklyDigestEnabled: z.boolean(),
92
- members: z.array(CollectionMemberSchema),
93
- /** @deprecated Use `members`. Org-only subset, kept for back-compat. */
94
- orgs: z.array(CollectionMemberOrgSchema),
95
- });
96
-
97
- /**
98
- * Cross-member release feed row on `GET /v1/collections/:slug/releases`. Same
99
- * shape as `OrgReleaseItem` plus the origin `org` block (and optional
100
- * `product`) so the web's release card can render it identically. `source.type`
101
- * stays as `z.string()` because the SQL passes the column through unvalidated —
102
- * tightening to the SourceType enum would break pass-through code.
103
- */
104
- export const CollectionReleaseItemSchema = ReleaseItemSchema.extend({
105
- source: z.object({ slug: z.string(), name: z.string(), type: z.string() }),
106
- org: z.object({ slug: z.string(), name: z.string() }),
107
- /**
108
- * Product the release belongs to, if the source is bound to one. `null` for
109
- * orgs without products or standalone sources. Optional on the wire so older
110
- * workers mid-rollout (and hand-constructed test fixtures) don't trip the
111
- * typecheck — clients should treat missing and `null` identically.
112
- */
113
- product: z.object({ slug: z.string(), name: z.string() }).nullable().optional(),
114
- /**
115
- * Server-resolved grouping identity — `COALESCE(product.slug, source.slug)` /
116
- * `COALESCE(product.name, source.name)`. The web release feeds key and label
117
- * SDK/package-cluster rollups on these instead of reconstructing
118
- * `product ?? source` client-side. Optional on the wire: older workers omit
119
- * them, so clients must fall back to deriving from `product ?? source`. Never
120
- * null when present (`source` is always set). #1234
121
- */
122
- groupSlug: z.string().optional(),
123
- groupName: z.string().optional(),
124
- });
125
-
126
- /** Cursor pagination shape matches `/v1/orgs/:slug/releases`. */
127
- export const CollectionFeedPaginationSchema = z.object({
128
- nextCursor: z.string().nullable(),
129
- limit: z.number().int().min(1),
130
- });
131
-
132
- export const CollectionReleasesResponseSchema = z.object({
133
- releases: z.array(CollectionReleaseItemSchema),
134
- pagination: CollectionFeedPaginationSchema,
135
- });
136
-
137
- /**
138
- * Bare row returned by `POST /v1/collections` and `PATCH /v1/collections/:slug`.
139
- * Mirrors the `collections` table columns (no member rollup — that's a
140
- * separate detail call).
141
- */
142
- export const CollectionRowSchema = z.object({
143
- id: z.string(),
144
- slug: z.string(),
145
- name: z.string(),
146
- description: z.string().nullable(),
147
- isFeatured: z.boolean(),
148
- /** Per-collection enable toggle for the nightly daily-summary generation. */
149
- dailySummaryEnabled: z.boolean(),
150
- createdAt: z.string(),
151
- updatedAt: z.string(),
152
- });
153
-
154
- /**
155
- * Body for `POST /v1/collections`. `slug` derives from `name` via `toSlug()`
156
- * when omitted. Slug shape is lowercased alnum + hyphens, alnum-start, 2–64
157
- * chars — enforced server-side.
158
- */
159
- export const CreateCollectionRequestSchema = z.object({
160
- slug: z.string().optional(),
161
- name: z.string().min(1).max(200),
162
- description: z.string().max(2000).nullable().optional(),
163
- });
164
-
165
- /** Body for `PATCH /v1/collections/:slug`. All fields optional; slug rename allowed. */
166
- export const UpdateCollectionRequestSchema = z.object({
167
- slug: z.string().optional(),
168
- name: z.string().min(1).max(200).optional(),
169
- description: z.string().max(2000).nullable().optional(),
170
- /** Promote / demote the collection on the homepage. */
171
- isFeatured: z.boolean().optional(),
172
- /** Enable or disable the nightly daily-summary generation for this collection. */
173
- dailySummaryEnabled: z.boolean().optional(),
174
- /** Enable or disable the nightly weekly-digest generation for this collection (on by default for new collections). */
175
- weeklyDigestEnabled: z.boolean().optional(),
176
- });
177
-
178
- /**
179
- * One member entry accepted by the member-write endpoints.
180
- *
181
- * Exactly one of `orgId` / `orgSlug` / `productId` / `productSlug` must be
182
- * present. When both an id and a slug are supplied for the same kind, the id
183
- * wins. Mixing kinds (e.g. `orgId` + `productSlug`) is rejected by the handler.
184
- *
185
- * Product slug is resolved within an org — supply either `productId`
186
- * (`prod_…`, globally unique) or pair `productSlug` with `orgSlug`/`orgId`.
187
- * `productSlug` alone is rejected.
188
- */
189
- export const CollectionMemberInputSchema = z.object({
190
- orgId: z.string().optional(),
191
- orgSlug: z.string().optional(),
192
- productId: z.string().optional(),
193
- productSlug: z.string().optional(),
194
- /** Authoring position (default 0). For PUT, omit to use the array index. */
195
- position: z.number().int().optional(),
196
- });
197
-
198
- /** Body for `POST /v1/collections/:slug/members`. */
199
- export const AddCollectionMemberRequestSchema = CollectionMemberInputSchema;
200
-
201
- /**
202
- * Body for `PUT /v1/collections/:slug/members`. Replaces the full membership
203
- * atomically. The field is still named `orgs` for back-compat — it accepts
204
- * any `CollectionMemberInput`, org or product.
205
- */
206
- export const ReplaceCollectionMembersRequestSchema = z.object({
207
- orgs: z.array(CollectionMemberInputSchema),
208
- });
209
-
210
- /**
211
- * Resolved member returned by `PUT /v1/collections/:slug/members` and `POST
212
- * /v1/collections/:slug/members`. `kind` discriminates which id field is
213
- * populated. `orgId` is also set on legacy callers that only used org
214
- * membership — for clean dispatch, dispatch on `kind`.
215
- */
216
- export const ResolvedCollectionMemberSchema = z.object({
217
- kind: z.enum(["org", "product"]),
218
- orgId: z.string().optional(),
219
- productId: z.string().optional(),
220
- position: z.number().int(),
221
- });
222
-
223
- /** Response from `PUT /v1/collections/:slug/members`. */
224
- export const ReplaceCollectionMembersResponseSchema = z.object({
225
- collectionSlug: z.string(),
226
- members: z.array(ResolvedCollectionMemberSchema),
227
- });
228
-
229
- /**
230
- * Response from `POST /v1/collections/:slug/members`. `orgId` stays populated
231
- * for org-kind members so existing CLI callers don't break; `kind` +
232
- * `productId` are the canonical fields for new code.
233
- */
234
- export const AddCollectionMemberResponseSchema = z.object({
235
- collectionSlug: z.string(),
236
- kind: z.enum(["org", "product"]),
237
- orgId: z.string().optional(),
238
- productId: z.string().optional(),
239
- position: z.number().int(),
240
- });
241
-
242
- /** One daily summary row returned by `GET /v1/collections/:slug/daily-summaries`. */
243
- export const CollectionDailySummarySchema = z.object({
244
- /** Eastern calendar day, YYYY-MM-DD. */
245
- date: z.string(),
246
- title: z.string(),
247
- summary: z.string(),
248
- takeaways: z.array(z.string()),
249
- releaseCount: z.number().int().nonnegative(),
250
- });
251
-
252
- /** Response envelope for `GET /v1/collections/:slug/daily-summaries`. */
253
- export const CollectionDailySummariesResponseSchema = z.object({
254
- summaries: z.array(CollectionDailySummarySchema),
255
- });
256
-
257
- // ── Weekly digests ────────────────────────────────────────────────
258
-
259
- /** List row on `GET /v1/collections/:slug/digests` — newest-first, no body. */
260
- export const CollectionWeeklyDigestListItemSchema = z.object({
261
- id: z.string(),
262
- /** ET Monday starting the covered week, YYYY-MM-DD. */
263
- weekStart: z.string(),
264
- title: z.string(),
265
- intro: z.string(),
266
- releaseCount: z.number().int().nonnegative(),
267
- generatedAt: z.string(),
268
- });
269
-
270
- /** Cursor pagination shape matches the other paginated list surfaces. */
271
- export const CollectionWeeklyDigestsResponseSchema = z.object({
272
- digests: z.array(CollectionWeeklyDigestListItemSchema),
273
- pagination: CollectionFeedPaginationSchema,
274
- });
275
-
276
- /** Minimal, server-resolved release info for a digest's "Releases covered" list. */
277
- export const DigestCoveredReleaseSchema = z.object({
278
- id: z.string(),
279
- title: z.string(),
280
- /** Internal `/release/*` path — fallback link only (see `url`). */
281
- path: z.string(),
282
- /** Upstream source URL. Primary link target when http(s); optional for older servers. */
283
- url: z.string().nullable().optional(),
284
- org: z.object({
285
- slug: z.string(),
286
- name: z.string(),
287
- avatarUrl: z.string().nullable().optional(),
288
- githubHandle: z.string().nullable().optional(),
289
- }),
290
- /** Product the release's source belongs to, when it has one. */
291
- product: z.object({ slug: z.string(), name: z.string() }).nullable().optional(),
292
- /**
293
- * AI-scored importance 1–5. Null when unscored; optional for older
294
- * servers. Cheap to project on the existing hydrate — powers the flame
295
- * marker on the collapsed "Releases covered" list.
296
- */
297
- importance: ImportanceScoreSchema,
298
- /**
299
- * Per-category item counts from the AI release-content pass. Null when the
300
- * row hasn't been summarized; optional for older servers. Powers the
301
- * digest page's week-at-a-glance treemap.
302
- */
303
- composition: ReleaseCompositionSchema.nullable().optional(),
304
- /**
305
- * ISO publish timestamp, as stored. Null when the source gave none;
306
- * optional for older servers. A date-only source is stored at exactly
307
- * `T00:00:00.000Z`. Powers the digest week replay's day placement.
308
- */
309
- publishedAt: z.string().nullable().optional(),
310
- });
311
-
312
- /** One `###` section of a digest body, parsed server-side. */
313
- export const DigestSectionSchema = z.object({
314
- heading: z.string(),
315
- /** Slug the digest page renders as the heading's `id` — link target `#anchor`. */
316
- anchor: z.string(),
317
- /** First sentence of the section, plain text. */
318
- lede: z.string(),
319
- /** Cited release ids in first-seen order; resolve against the detail's `releases`. */
320
- releaseIds: z.array(z.string()),
321
- /**
322
- * The section's cited releases, resolved server-side in `releaseIds` order.
323
- * Ids that no longer resolve are dropped, so `releases.length` can be less
324
- * than `releaseIds.length`. Optional for older servers.
325
- */
326
- releases: z.array(DigestCoveredReleaseSchema).optional(),
327
- });
328
-
329
- /** Full row returned by `GET /v1/collections/:slug/digests/:weekStart` and
330
- * `GET /v1/collections/:slug/digests/latest`. */
331
- export const CollectionWeeklyDigestDetailSchema = z.object({
332
- id: z.string(),
333
- weekStart: z.string(),
334
- title: z.string(),
335
- intro: z.string(),
336
- body: z.string(),
337
- releaseIds: z.array(z.string()),
338
- releaseCount: z.number().int().nonnegative(),
339
- generatedAt: z.string(),
340
- /** Server-resolved minimal release info for every id in `releaseIds` that
341
- * still resolves — a release deleted/suppressed after generation is
342
- * dropped rather than surfaced as a dead link. */
343
- releases: z.array(DigestCoveredReleaseSchema),
344
- /** Parsed `###` sections. Optional for older servers. */
345
- sections: z.array(DigestSectionSchema).optional(),
346
- });
@@ -1,31 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const DatasetDescriptionSchema = z.object({
4
- name: z.string().trim().min(1).max(200),
5
- description: z.string().trim().min(50).max(5000),
6
- });
7
-
8
- /** Stored under source.metadata.dataset. Null removes the override. */
9
- export const SourceDatasetSettingsSchema = DatasetDescriptionSchema.partial()
10
- .extend({
11
- enabled: z.boolean().optional(),
12
- })
13
- .strict();
14
- export type SourceDatasetSettings = z.infer<typeof SourceDatasetSettingsSchema>;
15
- export type DatasetDescription = z.infer<typeof DatasetDescriptionSchema>;
16
- export interface AdminDatasetSource {
17
- id: string;
18
- slug: string;
19
- name: string;
20
- orgSlug: string | null;
21
- orgName: string | null;
22
- defaultEnabled: boolean;
23
- settings: SourceDatasetSettings | null;
24
- effective: DatasetDescription | null;
25
- blockedReasons: string[];
26
- }
27
- export interface AdminDatasetsResponse {
28
- sources: AdminDatasetSource[];
29
- pagination: { page: number; limit: number; hasMore: boolean };
30
- query: string;
31
- }
@@ -1,55 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { ERROR_TYPES } from "@buildinternet/releases-core/errors";
3
- import { errorEnvelopeSchema, decodeApiError, isApiError } from "./errors";
4
-
5
- test("a valid envelope parses", () => {
6
- const ok = errorEnvelopeSchema.safeParse({
7
- error: { code: "not_found", type: "not_found", message: "Nope" },
8
- });
9
- expect(ok.success).toBe(true);
10
- });
11
-
12
- test("malformed envelopes reject", () => {
13
- expect(errorEnvelopeSchema.safeParse({}).success).toBe(false);
14
- expect(
15
- errorEnvelopeSchema.safeParse({
16
- error: { code: 1, type: "not_found", message: "x" },
17
- }).success,
18
- ).toBe(false);
19
- });
20
-
21
- test("schema type enum matches the core union (anti-drift invariant 2)", () => {
22
- const options = errorEnvelopeSchema.shape.error.shape.type.options;
23
- expect([...options].sort()).toEqual([...ERROR_TYPES].sort());
24
- });
25
-
26
- test("decodeApiError normalizes an unknown type to internal, preserves unknown code", () => {
27
- const decoded = decodeApiError({
28
- error: { code: "brand_new_code", type: "teapot", message: "?" },
29
- });
30
- expect(decoded.type).toBe("internal");
31
- expect(decoded.code).toBe("brand_new_code");
32
- });
33
-
34
- test("decodeApiError degrades a malformed body without throwing", () => {
35
- expect(decodeApiError(null)).toEqual({
36
- code: "internal_error",
37
- type: "internal",
38
- message: "Unknown error",
39
- });
40
- });
41
-
42
- test("decodeApiError carries details through when present", () => {
43
- const decoded = decodeApiError({
44
- error: { code: "not_found", type: "not_found", message: "x", details: { id: "src_1" } },
45
- });
46
- expect(decoded.details).toEqual({ id: "src_1" });
47
- });
48
-
49
- test("isApiError matches shape and optional code", () => {
50
- const body = { error: { code: "not_found", type: "not_found", message: "x" } };
51
- expect(isApiError(body)).toBe(true);
52
- expect(isApiError(body, "not_found")).toBe(true);
53
- expect(isApiError(body, "conflict")).toBe(false);
54
- expect(isApiError({ nope: true })).toBe(false);
55
- });
@@ -1,81 +0,0 @@
1
- import { z } from "zod";
2
- import { ERROR_TYPES, type ErrorType, type ErrorCode } from "@buildinternet/releases-core/errors";
3
-
4
- /**
5
- * The one on-the-wire error shape. `type` is constrained (documents the
6
- * contract, powers OpenAPI); `code` is an open string so a new server code an
7
- * older client doesn't recognize still parses. The throwable hierarchy in
8
- * `@releases/lib/releases-error` serializes to this via `toWire()`.
9
- */
10
- export const errorEnvelopeSchema = z
11
- .object({
12
- error: z.object({
13
- code: z
14
- .string()
15
- .describe(
16
- "Stable, specific machine code (e.g. `not_found`, `rate_limited`). Never reworded once shipped, but open-ended — branch defensively on codes you don't recognize.",
17
- ),
18
- type: z
19
- .enum(ERROR_TYPES)
20
- .describe(
21
- "Coarse category from a fixed set; determines the HTTP status. Safe to switch on.",
22
- ),
23
- message: z.string().describe("Human-readable explanation. May change; do not parse."),
24
- details: z
25
- .unknown()
26
- .optional()
27
- .describe(
28
- "Optional structured context carried by select codes (e.g. `database_not_initialized`).",
29
- ),
30
- }),
31
- })
32
- .describe(
33
- "Standardized error response — every non-2xx response uses this shape. Switch on `error.type` (fixed set → HTTP status) or match `error.code` (specific, stable). See https://releases.sh/docs/api/errors.",
34
- );
35
-
36
- export type ErrorEnvelope = z.infer<typeof errorEnvelopeSchema>;
37
-
38
- /**
39
- * Lenient variant used ONLY for decode: `type` is left open so an unknown
40
- * category (older client, newer server) does not reject.
41
- */
42
- const lenientEnvelopeSchema = z.object({
43
- error: z.object({
44
- code: z.string(),
45
- type: z.string(),
46
- message: z.string(),
47
- details: z.unknown().optional(),
48
- }),
49
- });
50
-
51
- export interface DecodedApiError {
52
- code: string;
53
- type: ErrorType;
54
- message: string;
55
- details?: unknown;
56
- }
57
-
58
- /**
59
- * Parse an API error body into a typed, normalized shape. Forward-compatible: a
60
- * malformed body or an unknown `type` degrades to `internal` and never throws.
61
- */
62
- export function decodeApiError(body: unknown): DecodedApiError {
63
- const parsed = lenientEnvelopeSchema.safeParse(body);
64
- if (!parsed.success) {
65
- return { code: "internal_error", type: "internal", message: "Unknown error" };
66
- }
67
- const { code, type, message, details } = parsed.data.error;
68
- const normalizedType: ErrorType = (ERROR_TYPES as readonly string[]).includes(type)
69
- ? (type as ErrorType)
70
- : "internal";
71
- return details === undefined
72
- ? { code, type: normalizedType, message }
73
- : { code, type: normalizedType, message, details };
74
- }
75
-
76
- /** True if `body` is an API error envelope; if `code` is given, also matches it. */
77
- export function isApiError(body: unknown, code?: ErrorCode): boolean {
78
- const parsed = lenientEnvelopeSchema.safeParse(body);
79
- if (!parsed.success) return false;
80
- return code === undefined || parsed.data.error.code === code;
81
- }