@buildinternet/releases-api-types 0.16.0 → 0.18.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 +516 -516
- package/src/schemas/collections.ts +140 -0
- package/src/schemas/lookups.ts +123 -0
- package/src/schemas/orgs.ts +294 -4
- package/src/schemas/overviews.ts +138 -0
- package/src/schemas/products.ts +32 -4
- package/src/schemas/related.ts +107 -0
- package/src/schemas/releases.ts +250 -0
- package/src/schemas/search.ts +157 -0
- package/src/schemas/shared.ts +5 -0
- package/src/schemas/sitemap.ts +36 -0
- package/src/schemas/sources.ts +291 -0
- package/src/schemas/stats.ts +62 -0
- package/src/schemas/taxonomy.ts +110 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { 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
|
+
* Row on `GET /v1/collections`. `previewMembers` is the first few visible
|
|
21
|
+
* members (capped at 3) so the list page can render inline avatars without a
|
|
22
|
+
* second round trip.
|
|
23
|
+
*/
|
|
24
|
+
export const CollectionListItemSchema = z.object({
|
|
25
|
+
slug: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
description: z.string().nullable(),
|
|
28
|
+
memberCount: z.number().int().min(0),
|
|
29
|
+
previewMembers: z.array(CollectionMemberOrgSchema).optional(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const CollectionListResponseSchema = z.array(CollectionListItemSchema);
|
|
33
|
+
|
|
34
|
+
/** Returned by `GET /v1/collections/:slug`. */
|
|
35
|
+
export const CollectionDetailSchema = z.object({
|
|
36
|
+
slug: z.string(),
|
|
37
|
+
name: z.string(),
|
|
38
|
+
description: z.string().nullable(),
|
|
39
|
+
orgs: z.array(CollectionMemberOrgSchema),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Cross-org release feed row on `GET /v1/collections/:slug/releases`. Same
|
|
44
|
+
* shape as `OrgReleaseItem` plus the origin `org` block (and optional
|
|
45
|
+
* `product`) so the web's release card can render it identically. `source.type`
|
|
46
|
+
* stays as `z.string()` because the SQL passes the column through unvalidated —
|
|
47
|
+
* tightening to the SourceType enum would break pass-through code.
|
|
48
|
+
*/
|
|
49
|
+
export const CollectionReleaseItemSchema = ReleaseItemSchema.extend({
|
|
50
|
+
source: z.object({ slug: z.string(), name: z.string(), type: z.string() }),
|
|
51
|
+
org: z.object({ slug: z.string(), name: z.string() }),
|
|
52
|
+
/**
|
|
53
|
+
* Product the release belongs to, if the source is bound to one. `null` for
|
|
54
|
+
* orgs without products or standalone sources. Optional on the wire so older
|
|
55
|
+
* workers mid-rollout (and hand-constructed test fixtures) don't trip the
|
|
56
|
+
* typecheck — clients should treat missing and `null` identically.
|
|
57
|
+
*/
|
|
58
|
+
product: z.object({ slug: z.string(), name: z.string() }).nullable().optional(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** Cursor pagination shape matches `/v1/orgs/:slug/releases`. */
|
|
62
|
+
export const CollectionFeedPaginationSchema = z.object({
|
|
63
|
+
nextCursor: z.string().nullable(),
|
|
64
|
+
limit: z.number().int().min(1),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const CollectionReleasesResponseSchema = z.object({
|
|
68
|
+
releases: z.array(CollectionReleaseItemSchema),
|
|
69
|
+
pagination: CollectionFeedPaginationSchema,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Bare row returned by `POST /v1/collections` and `PATCH /v1/collections/:slug`.
|
|
74
|
+
* Mirrors the `collections` table columns (no member rollup — that's a
|
|
75
|
+
* separate detail call).
|
|
76
|
+
*/
|
|
77
|
+
export const CollectionRowSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
slug: z.string(),
|
|
80
|
+
name: z.string(),
|
|
81
|
+
description: z.string().nullable(),
|
|
82
|
+
createdAt: z.string(),
|
|
83
|
+
updatedAt: z.string(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Body for `POST /v1/collections`. `slug` derives from `name` via `toSlug()`
|
|
88
|
+
* when omitted. Slug shape is lowercased alnum + hyphens, alnum-start, 2–64
|
|
89
|
+
* chars — enforced server-side.
|
|
90
|
+
*/
|
|
91
|
+
export const CreateCollectionRequestSchema = z.object({
|
|
92
|
+
slug: z.string().optional(),
|
|
93
|
+
name: z.string().min(1).max(200),
|
|
94
|
+
description: z.string().max(2000).nullable().optional(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/** Body for `PATCH /v1/collections/:slug`. All fields optional; slug rename allowed. */
|
|
98
|
+
export const UpdateCollectionRequestSchema = z.object({
|
|
99
|
+
slug: z.string().optional(),
|
|
100
|
+
name: z.string().min(1).max(200).optional(),
|
|
101
|
+
description: z.string().max(2000).nullable().optional(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* One member entry accepted by the member-write endpoints. Either `orgId`
|
|
106
|
+
* (`org_…`) or `orgSlug` is required; when both are given, `orgId` wins.
|
|
107
|
+
*/
|
|
108
|
+
export const CollectionMemberInputSchema = z.object({
|
|
109
|
+
orgId: z.string().optional(),
|
|
110
|
+
orgSlug: z.string().optional(),
|
|
111
|
+
/** Authoring position (default 0). For PUT, omit to use the array index. */
|
|
112
|
+
position: z.number().int().optional(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** Body for `POST /v1/collections/:slug/members`. */
|
|
116
|
+
export const AddCollectionMemberRequestSchema = CollectionMemberInputSchema;
|
|
117
|
+
|
|
118
|
+
/** Body for `PUT /v1/collections/:slug/members`. Replaces the full membership atomically. */
|
|
119
|
+
export const ReplaceCollectionMembersRequestSchema = z.object({
|
|
120
|
+
orgs: z.array(CollectionMemberInputSchema),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** Resolved member returned by `PUT /v1/collections/:slug/members`. */
|
|
124
|
+
export const ResolvedCollectionMemberSchema = z.object({
|
|
125
|
+
orgId: z.string(),
|
|
126
|
+
position: z.number().int(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/** Response from `PUT /v1/collections/:slug/members`. */
|
|
130
|
+
export const ReplaceCollectionMembersResponseSchema = z.object({
|
|
131
|
+
collectionSlug: z.string(),
|
|
132
|
+
members: z.array(ResolvedCollectionMemberSchema),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/** Response from `POST /v1/collections/:slug/members`. */
|
|
136
|
+
export const AddCollectionMemberResponseSchema = z.object({
|
|
137
|
+
collectionSlug: z.string(),
|
|
138
|
+
orgId: z.string(),
|
|
139
|
+
position: z.number().int(),
|
|
140
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CategorySchema } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Status flags returned by `POST /v1/lookups`:
|
|
6
|
+
* - `indexed` — the source was just materialized and releases were ingested.
|
|
7
|
+
* - `existing` — the source already lives in the registry; we returned it as-is.
|
|
8
|
+
* - `empty` — the repo exists but has no releases or CHANGELOG to ingest. A
|
|
9
|
+
* hidden stub row is left behind so the next cron pass can pick it up.
|
|
10
|
+
* - `not_found` — the repo doesn't exist, is archived, or isn't reachable.
|
|
11
|
+
* - `deferred` — GitHub probe rate-limited or 5xx'd; the next cron pass retries.
|
|
12
|
+
*/
|
|
13
|
+
export const LookupStatusSchema = z.enum(["indexed", "existing", "empty", "not_found", "deferred"]);
|
|
14
|
+
|
|
15
|
+
/** Body accepted by `POST /v1/lookups`. v1 only supports GitHub coordinates. */
|
|
16
|
+
export const LookupBodySchema = z.object({
|
|
17
|
+
provider: z.literal("github"),
|
|
18
|
+
coordinate: z.string().regex(/^[^/\s]+\/[^/\s]+$/, {
|
|
19
|
+
message: "coordinate must match {org}/{repo}",
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Source row embedded in `LookupResponse`. The handler returns the full
|
|
25
|
+
* Drizzle row, so `z.looseObject(...)` lets stable fields stay typed while
|
|
26
|
+
* letting timestamps/counters that aren't load-bearing for callers ride
|
|
27
|
+
* along unmodelled. Same pattern as `SourceMutationResponseSchema`.
|
|
28
|
+
*/
|
|
29
|
+
export const LookupSourceSchema = z.looseObject({
|
|
30
|
+
id: z.string(),
|
|
31
|
+
slug: z.string(),
|
|
32
|
+
name: z.string(),
|
|
33
|
+
type: z.string(),
|
|
34
|
+
url: z.string(),
|
|
35
|
+
orgId: z.string().nullable(),
|
|
36
|
+
productId: z.string().nullable(),
|
|
37
|
+
isHidden: z.boolean().nullable().optional(),
|
|
38
|
+
discovery: z.enum(["curated", "agent", "on_demand"]).optional(),
|
|
39
|
+
metadata: z.string().nullable(),
|
|
40
|
+
createdAt: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** Release row embedded in `LookupResponse.releases`. Loose for the same reason as `LookupSourceSchema`. */
|
|
44
|
+
export const LookupReleaseSchema = z.looseObject({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
sourceId: z.string(),
|
|
47
|
+
version: z.string().nullable(),
|
|
48
|
+
title: z.string(),
|
|
49
|
+
url: z.string().nullable(),
|
|
50
|
+
publishedAt: z.string().nullable(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** "Did you mean" rail returned alongside not_found / empty / deferred outcomes. */
|
|
54
|
+
export const LookupRelatedOrgSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
org: z.object({ id: z.string(), slug: z.string(), name: z.string() }),
|
|
57
|
+
sources: z.array(
|
|
58
|
+
z.object({ id: z.string(), slug: z.string(), name: z.string(), url: z.string() }),
|
|
59
|
+
),
|
|
60
|
+
})
|
|
61
|
+
.nullable();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Response returned by `POST /v1/lookups`. The `source` and `releases` fields
|
|
65
|
+
* are populated for `indexed` / `existing` outcomes; `empty` populates only
|
|
66
|
+
* `source`; `not_found` / `deferred` populate neither.
|
|
67
|
+
*/
|
|
68
|
+
export const LookupResponseSchema = z.object({
|
|
69
|
+
status: LookupStatusSchema,
|
|
70
|
+
source: LookupSourceSchema.optional(),
|
|
71
|
+
releases: z.array(LookupReleaseSchema).optional(),
|
|
72
|
+
relatedOrg: LookupRelatedOrgSchema,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** Response from `GET /v1/lookups/source-by-slug?slug=…`. */
|
|
76
|
+
export const LookupSourceBySlugResponseSchema = z.object({
|
|
77
|
+
sourceId: z.string(),
|
|
78
|
+
sourceSlug: z.string(),
|
|
79
|
+
orgSlug: z.string(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/** Response from `GET /v1/lookups/product-by-slug?slug=…`. */
|
|
83
|
+
export const LookupProductBySlugResponseSchema = z.object({
|
|
84
|
+
productId: z.string(),
|
|
85
|
+
productSlug: z.string(),
|
|
86
|
+
orgSlug: z.string(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Response from `GET /v1/lookups/by-domain?domain=…`. Pure resolution: an
|
|
91
|
+
* unknown domain returns 404, never probes. Both an org and product list can
|
|
92
|
+
* be present when a product alias points at the same domain its parent org
|
|
93
|
+
* owns.
|
|
94
|
+
*/
|
|
95
|
+
export const DomainLookupOrgSchema = z.object({
|
|
96
|
+
id: z.string(),
|
|
97
|
+
slug: z.string(),
|
|
98
|
+
name: z.string(),
|
|
99
|
+
domain: z.string().nullable(),
|
|
100
|
+
description: z.string().nullable(),
|
|
101
|
+
category: z.string().nullable(),
|
|
102
|
+
avatarUrl: z.string().nullable(),
|
|
103
|
+
matchedVia: z.enum(["primary", "alias"]),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export const DomainLookupProductSchema = z.object({
|
|
107
|
+
id: z.string(),
|
|
108
|
+
slug: z.string(),
|
|
109
|
+
name: z.string(),
|
|
110
|
+
orgId: z.string(),
|
|
111
|
+
orgSlug: z.string(),
|
|
112
|
+
orgName: z.string(),
|
|
113
|
+
// Reuses the canonical `CategorySchema` enum because the products read
|
|
114
|
+
// path resolves through `resolveCategoryInput` before persisting — unlike
|
|
115
|
+
// the search catalog category, which passes the column through raw.
|
|
116
|
+
category: CategorySchema.nullable(),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export const DomainLookupResponseSchema = z.object({
|
|
120
|
+
domain: z.string(),
|
|
121
|
+
org: DomainLookupOrgSchema.nullable(),
|
|
122
|
+
products: z.array(DomainLookupProductSchema),
|
|
123
|
+
});
|
package/src/schemas/orgs.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CategorySchema,
|
|
4
|
+
ListResponseSchema,
|
|
5
|
+
OverviewPageItemSchema,
|
|
6
|
+
ReleaseItemSchema,
|
|
7
|
+
} from "./shared.js";
|
|
3
8
|
import { SourceListItemSchema } from "./sources.js";
|
|
4
9
|
import { ProductListItemSchema } from "./products.js";
|
|
10
|
+
import { CollectionListItemSchema } from "./collections.js";
|
|
5
11
|
|
|
6
12
|
export const OrgListItemSchema = z.object({
|
|
7
13
|
id: z.string(),
|
|
@@ -26,24 +32,49 @@ export const OrgAccountItemSchema = z.object({
|
|
|
26
32
|
handle: z.string(),
|
|
27
33
|
});
|
|
28
34
|
|
|
29
|
-
export const
|
|
35
|
+
export const OrgAccountsListResponseSchema = ListResponseSchema(OrgAccountItemSchema);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Response shape for `GET /v1/orgs/:slug/accounts`. Returns the paginated list
|
|
39
|
+
* by default; with `?platform=<name>` returns a single matching account or
|
|
40
|
+
* `null` when no row matches. The dual shape is preserved for compatibility
|
|
41
|
+
* with the OSS CLI which calls `?platform=` and expects a single row.
|
|
42
|
+
*/
|
|
43
|
+
export const OrgAccountsResponseSchema = z.union([
|
|
44
|
+
OrgAccountsListResponseSchema,
|
|
45
|
+
OrgAccountItemSchema,
|
|
46
|
+
z.null(),
|
|
47
|
+
]);
|
|
30
48
|
export const OrgTagsResponseSchema = ListResponseSchema(z.string());
|
|
31
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Body accepted by `POST /v1/orgs`. `category` is a non-empty string at the
|
|
52
|
+
* wire boundary so callers can pass either a canonical slug or one of its
|
|
53
|
+
* configured aliases (resolved server-side via `resolveCategoryInput`).
|
|
54
|
+
* Responses always carry the canonical slug. Empty strings are rejected so
|
|
55
|
+
* the truthy-guard around `resolveCategoryInput` in the handler can't be
|
|
56
|
+
* bypassed into persisting an invalid `""` value.
|
|
57
|
+
*/
|
|
32
58
|
export const CreateOrgBodySchema = z.object({
|
|
33
59
|
name: z.string().min(1),
|
|
34
60
|
slug: z.string().optional(),
|
|
35
61
|
domain: z.string().optional(),
|
|
36
62
|
description: z.string().optional(),
|
|
37
|
-
category:
|
|
63
|
+
category: z.string().min(1).optional(),
|
|
38
64
|
tags: z.array(z.string()).optional(),
|
|
39
65
|
});
|
|
40
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Body accepted by `PATCH /v1/orgs/:slug`. Same alias-allowed string rule
|
|
69
|
+
* as create, plus `null` to explicitly clear the overlay. Empty strings
|
|
70
|
+
* stay rejected so callers must pick between a slug/alias or `null`.
|
|
71
|
+
*/
|
|
41
72
|
export const UpdateOrgBodySchema = z.object({
|
|
42
73
|
name: z.string().optional(),
|
|
43
74
|
slug: z.string().optional(),
|
|
44
75
|
domain: z.string().nullable().optional(),
|
|
45
76
|
description: z.string().nullable().optional(),
|
|
46
|
-
category:
|
|
77
|
+
category: z.string().min(1).nullable().optional(),
|
|
47
78
|
tags: z.array(z.string()).optional(),
|
|
48
79
|
aliases: z.array(z.string()).optional(),
|
|
49
80
|
});
|
|
@@ -66,6 +97,265 @@ const OrgDetailPlaybookSchema = z.object({
|
|
|
66
97
|
updatedAt: z.string(),
|
|
67
98
|
});
|
|
68
99
|
|
|
100
|
+
// ── Org catalog (/orgs/:slug/catalog) ──
|
|
101
|
+
|
|
102
|
+
const OrgCatalogProductItemSchema = z.object({
|
|
103
|
+
kind: z.literal("product"),
|
|
104
|
+
id: z.string(),
|
|
105
|
+
slug: z.string(),
|
|
106
|
+
name: z.string(),
|
|
107
|
+
url: z.string().nullable(),
|
|
108
|
+
description: z.string().nullable(),
|
|
109
|
+
category: CategorySchema.nullable(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const OrgCatalogSourceItemSchema = z.object({
|
|
113
|
+
kind: z.literal("source"),
|
|
114
|
+
id: z.string(),
|
|
115
|
+
slug: z.string(),
|
|
116
|
+
name: z.string(),
|
|
117
|
+
type: z.string(),
|
|
118
|
+
url: z.string(),
|
|
119
|
+
productId: z.string().nullable(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Combined product + source catalog for `GET /v1/orgs/:slug/catalog`.
|
|
124
|
+
* `kind` discriminates between the two row shapes. Products carry
|
|
125
|
+
* `category` and `description`; sources carry `type` and `productId`.
|
|
126
|
+
*/
|
|
127
|
+
export const OrgCatalogItemSchema = z.discriminatedUnion("kind", [
|
|
128
|
+
OrgCatalogProductItemSchema,
|
|
129
|
+
OrgCatalogSourceItemSchema,
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
export const OrgCatalogResponseSchema = z.object({
|
|
133
|
+
org: z.object({ id: z.string(), slug: z.string(), name: z.string() }),
|
|
134
|
+
items: z.array(OrgCatalogItemSchema),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ── Org collections membership (/orgs/:slug/collections) ──
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* List of collections the org belongs to, returned by
|
|
141
|
+
* `GET /v1/orgs/:slug/collections`. Each item follows the same shape as
|
|
142
|
+
* `CollectionListItem` on `GET /v1/collections` — `previewMembers` is
|
|
143
|
+
* omitted here because the context (one org's collections) makes inline
|
|
144
|
+
* member previews redundant.
|
|
145
|
+
*/
|
|
146
|
+
export const OrgCollectionsResponseSchema = z.array(CollectionListItemSchema);
|
|
147
|
+
|
|
148
|
+
// ── Org accounts (/orgs/:slug/accounts) ──
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Body accepted by `POST /v1/orgs/:slug/accounts`.
|
|
152
|
+
* Both `platform` and `handle` are required.
|
|
153
|
+
*/
|
|
154
|
+
export const AddOrgAccountBodySchema = z.object({
|
|
155
|
+
platform: z.string().min(1),
|
|
156
|
+
handle: z.string().min(1),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ── Org ignored URLs (/orgs/:slug/ignored-urls) ──
|
|
160
|
+
|
|
161
|
+
export const IgnoredUrlItemSchema = z.object({
|
|
162
|
+
id: z.string(),
|
|
163
|
+
url: z.string(),
|
|
164
|
+
orgId: z.string(),
|
|
165
|
+
reason: z.string().nullable(),
|
|
166
|
+
ignoredAt: z.string(),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
export const OrgIgnoredUrlsListResponseSchema = ListResponseSchema(IgnoredUrlItemSchema);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Response shape for `GET /v1/orgs/:slug/ignored-urls`. Returns the paginated
|
|
173
|
+
* list by default; with `?url=<encoded>&single=1` returns the matching single
|
|
174
|
+
* row or `null` when not present.
|
|
175
|
+
*/
|
|
176
|
+
export const OrgIgnoredUrlsResponseSchema = z.union([
|
|
177
|
+
OrgIgnoredUrlsListResponseSchema,
|
|
178
|
+
IgnoredUrlItemSchema,
|
|
179
|
+
z.null(),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Body accepted by `POST /v1/orgs/:slug/ignored-urls`.
|
|
184
|
+
* `reason` is optional.
|
|
185
|
+
*/
|
|
186
|
+
export const AddIgnoredUrlBodySchema = z.object({
|
|
187
|
+
url: z.string().min(1),
|
|
188
|
+
reason: z.string().optional(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
/** Response shape for `POST /v1/orgs/:slug/ignored-urls`. */
|
|
192
|
+
export const AddIgnoredUrlResponseSchema = z.object({
|
|
193
|
+
ignored: z.literal(true),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/** Response shape for `DELETE /v1/orgs/:slug/ignored-urls/:url`. */
|
|
197
|
+
export const DeleteIgnoredUrlResponseSchema = z.object({
|
|
198
|
+
deleted: z.literal(true),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ── Org tags (/orgs/:slug/tags and PUT /DELETE) ──
|
|
202
|
+
|
|
203
|
+
/** Body accepted by `PUT` and `DELETE` `/v1/orgs/:slug/tags`. */
|
|
204
|
+
export const OrgTagsBodySchema = z.object({
|
|
205
|
+
tags: z.array(z.string()),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
/** Response shape returned by `PUT` and `DELETE` `/v1/orgs/:slug/tags`. */
|
|
209
|
+
export const OrgTagsMutationResponseSchema = z.object({
|
|
210
|
+
ok: z.literal(true),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ── POST /tags (global tag creation) ──
|
|
214
|
+
|
|
215
|
+
/** Body accepted by `POST /v1/tags`. */
|
|
216
|
+
export const CreateTagBodySchema = z.object({
|
|
217
|
+
name: z.string().min(1),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/** Row returned by `POST /v1/tags` (201 on create, 200 if already exists). */
|
|
221
|
+
export const TagRowSchema = z.object({
|
|
222
|
+
id: z.string(),
|
|
223
|
+
name: z.string(),
|
|
224
|
+
slug: z.string(),
|
|
225
|
+
createdAt: z.string(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// ── Org activity (/orgs/:slug/activity) ──
|
|
229
|
+
|
|
230
|
+
const OrgActivityWeeklyBucketSchema = z.object({
|
|
231
|
+
weekStart: z.string(),
|
|
232
|
+
count: z.number().int().min(0),
|
|
233
|
+
earliestVersion: z.string().nullable(),
|
|
234
|
+
latestVersion: z.string().nullable(),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const OrgActivitySourceSchema = z.object({
|
|
238
|
+
slug: z.string(),
|
|
239
|
+
name: z.string(),
|
|
240
|
+
releaseCount: z.number().int().min(0),
|
|
241
|
+
avgReleasesPerWeek: z.number(),
|
|
242
|
+
earliestVersion: z.string().nullable(),
|
|
243
|
+
latestVersion: z.string().nullable(),
|
|
244
|
+
latestDate: z.string().nullable(),
|
|
245
|
+
weeklyBuckets: z.array(OrgActivityWeeklyBucketSchema),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Response shape for `GET /v1/orgs/:slug/activity`. Returns per-source
|
|
250
|
+
* weekly release buckets plus an aggregate rollup — used for timeline
|
|
251
|
+
* visualization in the web org detail page.
|
|
252
|
+
*/
|
|
253
|
+
export const OrgActivityResponseSchema = z.object({
|
|
254
|
+
org: z.object({ slug: z.string(), name: z.string() }),
|
|
255
|
+
range: z.object({ from: z.string(), to: z.string() }),
|
|
256
|
+
sources: z.array(OrgActivitySourceSchema),
|
|
257
|
+
aggregateWeekly: z.array(z.object({ weekStart: z.string(), count: z.number().int().min(0) })),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ── Org heatmap (/orgs/:slug/heatmap) ──
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Response shape for `GET /v1/orgs/:slug/heatmap`. Returns daily release
|
|
264
|
+
* counts for the trailing 365 days — used for contribution-graph
|
|
265
|
+
* visualization in the web org detail page.
|
|
266
|
+
*/
|
|
267
|
+
export const OrgHeatmapResponseSchema = z.object({
|
|
268
|
+
org: z.object({ slug: z.string(), name: z.string() }),
|
|
269
|
+
range: z.object({ from: z.string(), to: z.string() }),
|
|
270
|
+
dailyCounts: z.array(z.object({ date: z.string(), count: z.number().int().min(0) })),
|
|
271
|
+
total: z.number().int().min(0),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// ── Org sparklines (/orgs/:slug/sparklines) ──
|
|
275
|
+
|
|
276
|
+
const SparklineSourceSchema = z.object({
|
|
277
|
+
slug: z.string(),
|
|
278
|
+
name: z.string(),
|
|
279
|
+
sparkline: z.array(z.number().int().min(0)).length(30),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Response shape for `GET /v1/orgs/:slug/sparklines`. Returns 30-day daily
|
|
284
|
+
* release counts per source and per product, plus an aggregate rollup.
|
|
285
|
+
*/
|
|
286
|
+
export const OrgSparklinesResponseSchema = z.object({
|
|
287
|
+
org: z.object({ slug: z.string(), name: z.string() }),
|
|
288
|
+
range: z.object({ from: z.string(), to: z.string() }),
|
|
289
|
+
aggregate: z.array(z.number().int().min(0)).length(30),
|
|
290
|
+
sources: z.array(SparklineSourceSchema),
|
|
291
|
+
products: z.array(SparklineSourceSchema),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ── Org releases feed (/orgs/:slug/releases) ──
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Release feed item on `GET /v1/orgs/:slug/releases`. Extends `ReleaseItem`
|
|
298
|
+
* with a `source` block identifying the originating source within the org.
|
|
299
|
+
*/
|
|
300
|
+
export const OrgReleaseItemSchema = ReleaseItemSchema.extend({
|
|
301
|
+
source: z.object({ slug: z.string(), name: z.string(), type: z.string() }),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/** Cursor pagination shape for the org releases feed. */
|
|
305
|
+
export const OrgFeedPaginationSchema = z.object({
|
|
306
|
+
nextCursor: z.string().nullable(),
|
|
307
|
+
limit: z.number().int().min(1),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Response shape for `GET /v1/orgs/:slug/releases`.
|
|
312
|
+
* Cursor-paginated; `nextCursor` is `null` when this is the last page.
|
|
313
|
+
*/
|
|
314
|
+
export const OrgReleasesFeedResponseSchema = z.object({
|
|
315
|
+
releases: z.array(OrgReleaseItemSchema),
|
|
316
|
+
pagination: OrgFeedPaginationSchema,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ── Org recent releases (/orgs/:slug/recent-releases) ──
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Row returned by `GET /v1/orgs/:slug/recent-releases`. This is the full
|
|
323
|
+
* `releasesVisible` row with `sourceName` and `sourceSlug` joined from
|
|
324
|
+
* `sourcesVisible` — intended for summarization / grouping by agents.
|
|
325
|
+
* Many fields are nullable because they may not be populated at ingest time.
|
|
326
|
+
*/
|
|
327
|
+
export const OrgRecentReleaseItemSchema = z.object({
|
|
328
|
+
id: z.string(),
|
|
329
|
+
sourceId: z.string(),
|
|
330
|
+
version: z.string().nullable(),
|
|
331
|
+
type: z.string().optional(),
|
|
332
|
+
title: z.string(),
|
|
333
|
+
content: z.string(),
|
|
334
|
+
summary: z.string().nullable(),
|
|
335
|
+
titleGenerated: z.string().nullable().optional(),
|
|
336
|
+
titleShort: z.string().nullable().optional(),
|
|
337
|
+
url: z.string().nullable(),
|
|
338
|
+
contentHash: z.string().nullable(),
|
|
339
|
+
metadata: z.string().nullable(),
|
|
340
|
+
media: z.string().nullable(),
|
|
341
|
+
publishedAt: z.string().nullable(),
|
|
342
|
+
suppressed: z.number().optional(),
|
|
343
|
+
suppressedReason: z.string().nullable().optional(),
|
|
344
|
+
fetchedAt: z.string().optional(),
|
|
345
|
+
embeddedAt: z.string().nullable().optional(),
|
|
346
|
+
sourceName: z.string(),
|
|
347
|
+
sourceSlug: z.string(),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
export const OrgRecentReleasesResponseSchema = z.array(OrgRecentReleaseItemSchema);
|
|
351
|
+
|
|
352
|
+
// ── Org accounts mutation responses ──
|
|
353
|
+
|
|
354
|
+
/** Response returned by `DELETE /v1/orgs/:slug/accounts/:platform/:handle`. */
|
|
355
|
+
export const DeleteOrgAccountResponseSchema = z.object({
|
|
356
|
+
deleted: z.literal(true),
|
|
357
|
+
});
|
|
358
|
+
|
|
69
359
|
export const OrgDetailSchema = z.object({
|
|
70
360
|
id: z.string().optional(),
|
|
71
361
|
slug: z.string(),
|