@buildinternet/releases-sdk 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,78 @@
1
- # Temporary Holding Version
1
+ # @buildinternet/releases-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript client for the [Releases](https://releases.sh) API at `api.releases.sh`. Request and response types come from [`@buildinternet/releases-api-types`](../api-types); the client calls only the operations in the public API surface ([`apps/api/src/api-surface.ts`](../../apps/api/src/api-surface.ts)). It runs anywhere `fetch` does: Node 18+, Bun, Deno, browsers and workers.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { Releases, ReleasesError } from "@buildinternet/releases-sdk";
9
+
10
+ const releases = new Releases({ apiKey: process.env.RELEASES_API_KEY });
11
+
12
+ // Public reads work without a key, at a lower rate limit.
13
+ const { releases: latest } = await releases.releases.latest({ org: "vercel", count: 10 });
14
+ const hits = await releases.search({ q: "edge functions", mode: "hybrid" });
15
+ const changelog = await releases.sources.changelog(
16
+ { org: "vercel", source: "next-js" },
17
+ { tokens: 2000 },
18
+ );
19
+
20
+ // Cursor pagination: pass `pagination.nextCursor` back as `cursor`.
21
+ let page = await releases.orgs.releases("vercel", { limit: 50 });
22
+ while (page.pagination.nextCursor) {
23
+ page = await releases.orgs.releases("vercel", { limit: 50, cursor: page.pagination.nextCursor });
24
+ }
25
+
26
+ try {
27
+ await releases.orgs.get("no-such-org");
28
+ } catch (err) {
29
+ if (err instanceof ReleasesError && err.type === "not_found") {
30
+ // err.status, err.code, err.message, err.details
31
+ }
32
+ }
33
+ ```
34
+
35
+ ### Publishing releases
36
+
37
+ A publish token (minted for one source) or a `write` token can publish:
38
+
39
+ ```ts
40
+ const publisher = new Releases({ apiKey: process.env.RELEASES_PUBLISH_TOKEN });
41
+ await publisher.sources.publish(
42
+ { org: "acme", source: "acme-cli" },
43
+ {
44
+ mode: "upsert-content",
45
+ releases: [
46
+ { title: "v2.1.0", version: "2.1.0", content: "…", url: "https://acme.dev/changelog#v2.1.0" },
47
+ ],
48
+ },
49
+ { idempotencyKey: crypto.randomUUID() },
50
+ );
51
+ ```
52
+
53
+ ### Account
54
+
55
+ With a user key (`relu_`) or OAuth token: `me.follows`, `me.feed`, `me.webhooks`, `apiKeys`, and `workspaceWebhooks(workspaceId)`.
56
+
57
+ Anything not wrapped yet is reachable through `request`, by its surface key:
58
+
59
+ ```ts
60
+ const overview = await releases.request("GET /products/:slug/overview", {
61
+ params: { slug: "next-js" },
62
+ });
63
+ ```
64
+
65
+ ## Options
66
+
67
+ | Option | Default | Notes |
68
+ | --------- | ------------------------- | ------------------------------------------- |
69
+ | `apiKey` | none | Sent as `Authorization: Bearer …`. |
70
+ | `baseUrl` | `https://api.releases.sh` | Use your own origin for staging or a fork. |
71
+ | `fetch` | global `fetch` | Inject for tests, proxies, or custom retry. |
72
+ | `headers` | none | Sent with every request. |
73
+
74
+ Every method also takes `{ signal, headers, idempotencyKey }` as its last argument.
75
+
76
+ ## License
77
+
78
+ MIT
@@ -0,0 +1,219 @@
1
+ import type { BatchReleasesBody, BatchReleasesResponse, CollectionDetail, CollectionListResponse, CollectionReleasesResponse, CreateUserApiKeyBody, CreateUserWebhookBody, CreateUserWebhookResponse, CreateWorkspaceWebhookBody, CreateWorkspaceWebhookResponse, CreatedUserApiKey, DomainLookupResponse, FollowMutationResponse, FollowTarget, FollowsListResponse, ListUserApiKeysResponse, OrgDetail, OrgListResponse, OrgOverviewResponse, OrgReleasesFeedResponse, PersonalizedFeedResponse, ProductDetail, ProductListResponse, ReleaseCoverageResponse, ReleaseDetailResponse, ReleaseLatestResponse, RevokeUserApiKeyResponse, RotateUserWebhookSecretResponse, SourceChangelogResponse, SourceDetail, SourceListResult, SourceReleasesResponse, StatsResponse, TestUserWebhookResponse, UnifiedSearchResponse, UpdateWebhookBody, UserWebhook, UserWebhookListResponse, WebhookDeliveriesResponse, WhatsChangedResponse, WorkspaceWebhook, WorkspaceWebhookListResponse } from "@buildinternet/releases-api-types";
2
+ import { type ClientOptions, type RequestOptions } from "./transport.js";
3
+ /** Entity kinds used by the `kind` filters. */
4
+ export type Kind = "platform" | "sdk" | "mobile" | "desktop" | "docs" | "integration" | "tool";
5
+ /** A source by slug or `src_…` ID, or by its org-scoped slug pair. */
6
+ export type SourceRef = string | {
7
+ org: string;
8
+ source: string;
9
+ };
10
+ /** A product by slug or `prod_…` ID, or by its org-scoped slug pair. */
11
+ export type ProductRef = string | {
12
+ org: string;
13
+ product: string;
14
+ };
15
+ export interface LatestReleasesQuery {
16
+ /** Number of releases to return. */
17
+ count?: number;
18
+ /** Source slug or ID. */
19
+ source?: string;
20
+ /** Org slug or ID. */
21
+ org?: string;
22
+ /** Comma-separated org slugs to leave out. */
23
+ exclude?: string;
24
+ /** ISO 8601 lower bound on `publishedAt`. */
25
+ since?: string;
26
+ /** ISO 8601 upper bound on `publishedAt`. */
27
+ until?: string;
28
+ /** Only releases with an AI-scored importance of at least this (1–5). */
29
+ minImportance?: number;
30
+ include_coverage?: boolean;
31
+ include_prereleases?: boolean;
32
+ }
33
+ export interface CursorQuery {
34
+ /** `pagination.nextCursor` from the previous page. */
35
+ cursor?: string;
36
+ limit?: number;
37
+ }
38
+ export interface OrgReleasesQuery extends CursorQuery {
39
+ source_type?: string;
40
+ kind?: Kind;
41
+ /** Product slug. */
42
+ product?: string;
43
+ q?: string;
44
+ since?: string;
45
+ until?: string;
46
+ include_coverage?: boolean;
47
+ include_prereleases?: boolean;
48
+ }
49
+ export interface OrgListQuery {
50
+ q?: string;
51
+ category?: string;
52
+ page?: number;
53
+ limit?: number;
54
+ featured?: boolean;
55
+ includeEmpty?: boolean;
56
+ }
57
+ export interface SearchQuery {
58
+ q: string;
59
+ limit?: number;
60
+ offset?: number;
61
+ mode?: "lexical" | "semantic" | "hybrid";
62
+ domain?: string;
63
+ product?: string;
64
+ category?: string;
65
+ collection?: string;
66
+ kind?: Kind;
67
+ since?: string;
68
+ until?: string;
69
+ include_coverage?: boolean;
70
+ include_content?: boolean;
71
+ include_empty?: boolean;
72
+ }
73
+ export interface ChangelogQuery {
74
+ /** Repo path of the file, from `files[].path`. Defaults to the root CHANGELOG. */
75
+ path?: string;
76
+ offset?: number;
77
+ /** Slice length in characters. */
78
+ limit?: number;
79
+ /** Slice to this many cl100k_base tokens instead of `limit` characters. */
80
+ tokens?: number;
81
+ }
82
+ export interface CollectionReleasesQuery extends CursorQuery {
83
+ /** Comma-separated org slugs. */
84
+ orgs?: string;
85
+ /** Comma-separated product slugs. */
86
+ products?: string;
87
+ source_type?: string;
88
+ include_prereleases?: boolean;
89
+ }
90
+ export interface WhatsChangedQuery {
91
+ package: string;
92
+ from: string;
93
+ to: string;
94
+ ecosystem?: "npm" | "pypi" | "github";
95
+ }
96
+ export interface DeliveriesQuery {
97
+ /** Only failed attempts. */
98
+ failed?: boolean;
99
+ /** 1–100, default 20. */
100
+ limit?: number;
101
+ }
102
+ /**
103
+ * Client for the Releases API.
104
+ *
105
+ * ```ts
106
+ * const releases = new Releases({ apiKey: process.env.RELEASES_API_KEY });
107
+ * const { releases: latest } = await releases.releases.latest({ org: "vercel", count: 10 });
108
+ * ```
109
+ */
110
+ export declare class Releases {
111
+ #private;
112
+ constructor(options?: ClientOptions);
113
+ /** Send any public operation by its surface key, e.g. `"GET /orgs/:slug"`. */
114
+ request<T = unknown>(operation: string, call?: {
115
+ params?: Record<string, string>;
116
+ query?: object;
117
+ body?: unknown;
118
+ } & RequestOptions): Promise<T>;
119
+ /** Lexical, semantic, or hybrid search across orgs, sources, and releases. */
120
+ search(query: SearchQuery, opts?: RequestOptions): Promise<UnifiedSearchResponse>;
121
+ /** Registry totals; `days` sets the recent-activity window. */
122
+ stats(query?: {
123
+ days?: number;
124
+ }, opts?: RequestOptions): Promise<StatsResponse>;
125
+ /** Release notes between two versions of a package. */
126
+ whatsChanged(query: WhatsChangedQuery, opts?: RequestOptions): Promise<WhatsChangedResponse>;
127
+ readonly releases: {
128
+ latest: (query?: LatestReleasesQuery, opts?: RequestOptions) => Promise<ReleaseLatestResponse>;
129
+ get: (id: string, opts?: RequestOptions) => Promise<ReleaseDetailResponse>;
130
+ coverage: (id: string, opts?: RequestOptions) => Promise<ReleaseCoverageResponse>;
131
+ };
132
+ readonly orgs: {
133
+ list: (query?: OrgListQuery, opts?: RequestOptions) => Promise<OrgListResponse>;
134
+ get: (slug: string, opts?: RequestOptions) => Promise<OrgDetail>;
135
+ releases: (slug: string, query?: OrgReleasesQuery, opts?: RequestOptions) => Promise<OrgReleasesFeedResponse>;
136
+ products: (slug: string, query?: {
137
+ kind?: Kind;
138
+ }, opts?: RequestOptions) => Promise<ProductListResponse>;
139
+ overview: (slug: string, opts?: RequestOptions) => Promise<OrgOverviewResponse>;
140
+ };
141
+ readonly products: {
142
+ list: (query?: {
143
+ orgId?: string;
144
+ kind?: Kind;
145
+ }, opts?: RequestOptions) => Promise<ProductListResponse>;
146
+ get: (ref: ProductRef, opts?: RequestOptions) => Promise<ProductDetail>;
147
+ };
148
+ readonly sources: {
149
+ list: (query?: {
150
+ kind?: Kind;
151
+ }, opts?: RequestOptions) => Promise<SourceListResult>;
152
+ get: (ref: SourceRef, opts?: RequestOptions) => Promise<SourceDetail>;
153
+ releases: (ref: SourceRef, opts?: RequestOptions) => Promise<SourceReleasesResponse>;
154
+ /** The source's tracked CHANGELOG file, sliced; page with `nextOffset`. */
155
+ changelog: (ref: SourceRef, query?: ChangelogQuery, opts?: RequestOptions) => Promise<SourceChangelogResponse>;
156
+ /**
157
+ * Publish releases to a source. Needs a `write` token or a publish token
158
+ * bound to the source. Releases match on `url`; pass
159
+ * `mode: "upsert-content"` to overwrite content on a match.
160
+ */
161
+ publish: (ref: SourceRef, body: BatchReleasesBody, opts?: RequestOptions) => Promise<BatchReleasesResponse>;
162
+ };
163
+ readonly collections: {
164
+ list: (query?: {
165
+ featured?: boolean;
166
+ }, opts?: RequestOptions) => Promise<CollectionListResponse>;
167
+ get: (slug: string, opts?: RequestOptions) => Promise<CollectionDetail>;
168
+ releases: (slug: string, query?: CollectionReleasesQuery, opts?: RequestOptions) => Promise<CollectionReleasesResponse>;
169
+ };
170
+ readonly lookups: {
171
+ /** The org, products, and sources registered for a domain. */
172
+ byDomain: (domain: string, opts?: RequestOptions) => Promise<DomainLookupResponse>;
173
+ };
174
+ /** The signed-in caller's account. Needs a user key (`relu_`) or OAuth token. */
175
+ readonly me: {
176
+ follows: {
177
+ list: (opts?: RequestOptions) => Promise<FollowsListResponse>;
178
+ /** Following an org includes its products in the feed. Idempotent. */
179
+ add: (targetType: FollowTarget, targetId: string, opts?: RequestOptions) => Promise<FollowMutationResponse>;
180
+ remove: (targetType: FollowTarget, targetId: string, opts?: RequestOptions) => Promise<FollowMutationResponse>;
181
+ };
182
+ /** Releases from everything the caller follows, newest first. */
183
+ feed: (query?: CursorQuery, opts?: RequestOptions) => Promise<PersonalizedFeedResponse>;
184
+ webhooks: {
185
+ list: (query?: {
186
+ enabled?: boolean;
187
+ }, opts?: RequestOptions) => Promise<UserWebhookListResponse>;
188
+ /** The response carries the signing key; it is shown only once. */
189
+ create: (body: CreateUserWebhookBody, opts?: RequestOptions) => Promise<CreateUserWebhookResponse>;
190
+ get: (id: string, opts?: RequestOptions) => Promise<UserWebhook>;
191
+ update: (id: string, body: UpdateWebhookBody, opts?: RequestOptions) => Promise<UserWebhook>;
192
+ delete: (id: string, opts?: RequestOptions) => Promise<void>;
193
+ deliveries: (id: string, query?: DeliveriesQuery, opts?: RequestOptions) => Promise<WebhookDeliveriesResponse>;
194
+ rotateSecret: (id: string, opts?: RequestOptions) => Promise<RotateUserWebhookSecretResponse>;
195
+ test: (id: string, opts?: RequestOptions) => Promise<TestUserWebhookResponse>;
196
+ };
197
+ };
198
+ /** Self-serve `relu_` keys. Keys minted here are capped at `read` scope. */
199
+ readonly apiKeys: {
200
+ list: (opts?: RequestOptions) => Promise<ListUserApiKeysResponse>;
201
+ /** The response carries the full key; it is shown only once. */
202
+ create: (body: CreateUserApiKeyBody, opts?: RequestOptions) => Promise<CreatedUserApiKey>;
203
+ revoke: (id: string, opts?: RequestOptions) => Promise<RevokeUserApiKeyResponse>;
204
+ };
205
+ /** Webhooks owned by a workspace. Members can read and test; owners and admins manage. */
206
+ workspaceWebhooks(workspaceId: string): {
207
+ list: (query?: {
208
+ enabled?: boolean;
209
+ }, opts?: RequestOptions) => Promise<WorkspaceWebhookListResponse>;
210
+ create: (body: CreateWorkspaceWebhookBody, opts?: RequestOptions) => Promise<CreateWorkspaceWebhookResponse>;
211
+ get: (id: string, opts?: RequestOptions) => Promise<WorkspaceWebhook>;
212
+ update: (id: string, body: UpdateWebhookBody, opts?: RequestOptions) => Promise<WorkspaceWebhook>;
213
+ delete: (id: string, opts?: RequestOptions) => Promise<void>;
214
+ deliveries: (id: string, query?: DeliveriesQuery, opts?: RequestOptions) => Promise<WebhookDeliveriesResponse>;
215
+ rotateSecret: (id: string, opts?: RequestOptions) => Promise<RotateUserWebhookSecretResponse>;
216
+ test: (id: string, opts?: RequestOptions) => Promise<TestUserWebhookResponse>;
217
+ };
218
+ }
219
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,141 @@
1
+ import { Transport } from "./transport.js";
2
+ /**
3
+ * Client for the Releases API.
4
+ *
5
+ * ```ts
6
+ * const releases = new Releases({ apiKey: process.env.RELEASES_API_KEY });
7
+ * const { releases: latest } = await releases.releases.latest({ org: "vercel", count: 10 });
8
+ * ```
9
+ */
10
+ export class Releases {
11
+ #t;
12
+ constructor(options = {}) {
13
+ this.#t = new Transport(options);
14
+ }
15
+ /** A bare-path operation for a slug/ID ref, its org-scoped twin for a slug pair. */
16
+ #source(ref, bare, scoped, call = {}) {
17
+ return typeof ref === "string"
18
+ ? this.#t.request(bare, { params: { slug: ref }, ...call })
19
+ : this.#t.request(scoped, {
20
+ params: { orgSlug: ref.org, sourceSlug: ref.source },
21
+ ...call,
22
+ });
23
+ }
24
+ /** Send any public operation by its surface key, e.g. `"GET /orgs/:slug"`. */
25
+ request(operation, call) {
26
+ return this.#t.request(operation, call);
27
+ }
28
+ /** Lexical, semantic, or hybrid search across orgs, sources, and releases. */
29
+ search(query, opts) {
30
+ return this.#t.request("GET /search", { query, ...opts });
31
+ }
32
+ /** Registry totals; `days` sets the recent-activity window. */
33
+ stats(query, opts) {
34
+ return this.#t.request("GET /stats", { query, ...opts });
35
+ }
36
+ /** Release notes between two versions of a package. */
37
+ whatsChanged(query, opts) {
38
+ return this.#t.request("GET /whats-changed", { query, ...opts });
39
+ }
40
+ releases = {
41
+ latest: (query, opts) => this.#t.request("GET /releases/latest", { query, ...opts }),
42
+ get: (id, opts) => this.#t.request("GET /releases/:id", { params: { id }, ...opts }),
43
+ coverage: (id, opts) => this.#t.request("GET /releases/:id/coverage", { params: { id }, ...opts }),
44
+ };
45
+ orgs = {
46
+ list: (query, opts) => this.#t.request("GET /orgs", { query, ...opts }),
47
+ get: (slug, opts) => this.#t.request("GET /orgs/:slug", { params: { slug }, ...opts }),
48
+ releases: (slug, query, opts) => this.#t.request("GET /orgs/:slug/releases", { params: { slug }, query, ...opts }),
49
+ products: (slug, query, opts) => this.#t.request("GET /orgs/:slug/products", { params: { slug }, query, ...opts }),
50
+ overview: (slug, opts) => this.#t.request("GET /orgs/:slug/overview", { params: { slug }, ...opts }),
51
+ };
52
+ products = {
53
+ list: (query, opts) => this.#t.request("GET /products", { query, ...opts }),
54
+ get: (ref, opts) => typeof ref === "string"
55
+ ? this.#t.request("GET /products/:identifier", { params: { identifier: ref }, ...opts })
56
+ : this.#t.request("GET /orgs/:orgSlug/products/:productSlug", {
57
+ params: { orgSlug: ref.org, productSlug: ref.product },
58
+ ...opts,
59
+ }),
60
+ };
61
+ sources = {
62
+ list: (query, opts) => this.#t.request("GET /sources", { query, ...opts }),
63
+ get: (ref, opts) => this.#source(ref, "GET /sources/:slug", "GET /orgs/:orgSlug/sources/:sourceSlug", opts),
64
+ releases: (ref, opts) => this.#source(ref, "GET /sources/:slug/releases", "GET /orgs/:orgSlug/sources/:sourceSlug/releases", opts),
65
+ /** The source's tracked CHANGELOG file, sliced; page with `nextOffset`. */
66
+ changelog: (ref, query, opts) => this.#source(ref, "GET /sources/:slug/changelog", "GET /orgs/:orgSlug/sources/:sourceSlug/changelog", { query, ...opts }),
67
+ /**
68
+ * Publish releases to a source. Needs a `write` token or a publish token
69
+ * bound to the source. Releases match on `url`; pass
70
+ * `mode: "upsert-content"` to overwrite content on a match.
71
+ */
72
+ publish: (ref, body, opts) => this.#source(ref, "POST /sources/:slug/releases/batch", "POST /orgs/:orgSlug/sources/:sourceSlug/releases/batch", { body, ...opts }),
73
+ };
74
+ collections = {
75
+ list: (query, opts) => this.#t.request("GET /collections", { query, ...opts }),
76
+ get: (slug, opts) => this.#t.request("GET /collections/:slug", { params: { slug }, ...opts }),
77
+ releases: (slug, query, opts) => this.#t.request("GET /collections/:slug/releases", { params: { slug }, query, ...opts }),
78
+ };
79
+ lookups = {
80
+ /** The org, products, and sources registered for a domain. */
81
+ byDomain: (domain, opts) => this.#t.request("GET /lookups/by-domain", { query: { domain }, ...opts }),
82
+ };
83
+ /** The signed-in caller's account. Needs a user key (`relu_`) or OAuth token. */
84
+ me = {
85
+ follows: {
86
+ list: (opts) => this.#t.request("GET /me/follows", opts),
87
+ /** Following an org includes its products in the feed. Idempotent. */
88
+ add: (targetType, targetId, opts) => this.#t.request("POST /me/follows", { body: { targetType, targetId }, ...opts }),
89
+ remove: (targetType, targetId, opts) => this.#t.request("DELETE /me/follows/:targetType/:targetId", {
90
+ params: { targetType, targetId },
91
+ ...opts,
92
+ }),
93
+ },
94
+ /** Releases from everything the caller follows, newest first. */
95
+ feed: (query, opts) => this.#t.request("GET /me/feed", { query, ...opts }),
96
+ webhooks: {
97
+ list: (query, opts) => this.#t.request("GET /me/webhooks", { query, ...opts }),
98
+ /** The response carries the signing key; it is shown only once. */
99
+ create: (body, opts) => this.#t.request("POST /me/webhooks", { body, ...opts }),
100
+ get: (id, opts) => this.#t.request("GET /me/webhooks/:id", { params: { id }, ...opts }),
101
+ update: (id, body, opts) => this.#t.request("PATCH /me/webhooks/:id", { params: { id }, body, ...opts }),
102
+ delete: (id, opts) => this.#t.request("DELETE /me/webhooks/:id", { params: { id }, ...opts }),
103
+ deliveries: (id, query, opts) => this.#t.request("GET /me/webhooks/:id/deliveries", { params: { id }, query, ...opts }),
104
+ rotateSecret: (id, opts) => this.#t.request("POST /me/webhooks/:id/rotate-secret", { params: { id }, ...opts }),
105
+ test: (id, opts) => this.#t.request("POST /me/webhooks/:id/test", { params: { id }, ...opts }),
106
+ },
107
+ };
108
+ /** Self-serve `relu_` keys. Keys minted here are capped at `read` scope. */
109
+ apiKeys = {
110
+ list: (opts) => this.#t.request("GET /api-keys", opts),
111
+ /** The response carries the full key; it is shown only once. */
112
+ create: (body, opts) => this.#t.request("POST /api-keys", { body, ...opts }),
113
+ revoke: (id, opts) => this.#t.request("DELETE /api-keys/:id", { params: { id }, ...opts }),
114
+ };
115
+ /** Webhooks owned by a workspace. Members can read and test; owners and admins manage. */
116
+ workspaceWebhooks(workspaceId) {
117
+ const t = this.#t;
118
+ const at = (id) => id === undefined ? { workspaceId } : { workspaceId, id };
119
+ return {
120
+ list: (query, opts) => t.request("GET /workspaces/:workspaceId/webhooks", { params: at(), query, ...opts }),
121
+ create: (body, opts) => t.request("POST /workspaces/:workspaceId/webhooks", { params: at(), body, ...opts }),
122
+ get: (id, opts) => t.request("GET /workspaces/:workspaceId/webhooks/:id", { params: at(id), ...opts }),
123
+ update: (id, body, opts) => t.request("PATCH /workspaces/:workspaceId/webhooks/:id", {
124
+ params: at(id),
125
+ body,
126
+ ...opts,
127
+ }),
128
+ delete: (id, opts) => t.request("DELETE /workspaces/:workspaceId/webhooks/:id", { params: at(id), ...opts }),
129
+ deliveries: (id, query, opts) => t.request("GET /workspaces/:workspaceId/webhooks/:id/deliveries", {
130
+ params: at(id),
131
+ query,
132
+ ...opts,
133
+ }),
134
+ rotateSecret: (id, opts) => t.request("POST /workspaces/:workspaceId/webhooks/:id/rotate-secret", {
135
+ params: at(id),
136
+ ...opts,
137
+ }),
138
+ test: (id, opts) => t.request("POST /workspaces/:workspaceId/webhooks/:id/test", { params: at(id), ...opts }),
139
+ };
140
+ }
141
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Every non-2xx response from the Releases API carries the envelope
3
+ * `{ error: { code, type, message, details? } }`. `type` is a fixed category
4
+ * tied to the HTTP status; `code` is specific and stable but open-ended, so
5
+ * branch defensively on codes you don't recognize.
6
+ */
7
+ export declare class ReleasesError extends Error {
8
+ /** HTTP status, or 0 when the request never got a response. */
9
+ readonly status: number;
10
+ /** Specific machine code, e.g. `not_found`, `rate_limited`. */
11
+ readonly code: string;
12
+ /** Coarse category, e.g. `not_found`, `validation`, `auth`, `internal`. */
13
+ readonly type: string;
14
+ readonly details?: unknown;
15
+ readonly method: string;
16
+ readonly path: string;
17
+ constructor(init: {
18
+ status: number;
19
+ code: string;
20
+ type: string;
21
+ message: string;
22
+ details?: unknown;
23
+ method: string;
24
+ path: string;
25
+ cause?: unknown;
26
+ });
27
+ }
28
+ /** Build a `ReleasesError` from a failed response's status and parsed body. */
29
+ export declare function errorFromResponse(status: number, statusText: string, body: unknown, method: string, path: string): ReleasesError;
30
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,IAAI,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,EASA;CACF;AAeD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACX,aAAa,CAaf"}
package/dist/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Every non-2xx response from the Releases API carries the envelope
3
+ * `{ error: { code, type, message, details? } }`. `type` is a fixed category
4
+ * tied to the HTTP status; `code` is specific and stable but open-ended, so
5
+ * branch defensively on codes you don't recognize.
6
+ */
7
+ export class ReleasesError extends Error {
8
+ /** HTTP status, or 0 when the request never got a response. */
9
+ status;
10
+ /** Specific machine code, e.g. `not_found`, `rate_limited`. */
11
+ code;
12
+ /** Coarse category, e.g. `not_found`, `validation`, `auth`, `internal`. */
13
+ type;
14
+ details;
15
+ method;
16
+ path;
17
+ constructor(init) {
18
+ super(init.message, init.cause === undefined ? undefined : { cause: init.cause });
19
+ this.name = "ReleasesError";
20
+ this.status = init.status;
21
+ this.code = init.code;
22
+ this.type = init.type;
23
+ this.details = init.details;
24
+ this.method = init.method;
25
+ this.path = init.path;
26
+ }
27
+ }
28
+ function isEnvelope(body) {
29
+ const error = body?.error;
30
+ return (typeof error === "object" &&
31
+ error !== null &&
32
+ typeof error.code === "string" &&
33
+ typeof error.type === "string" &&
34
+ typeof error.message === "string");
35
+ }
36
+ /** Build a `ReleasesError` from a failed response's status and parsed body. */
37
+ export function errorFromResponse(status, statusText, body, method, path) {
38
+ if (isEnvelope(body)) {
39
+ const { code, type, message, details } = body.error;
40
+ return new ReleasesError({ status, code, type, message, details, method, path });
41
+ }
42
+ return new ReleasesError({
43
+ status,
44
+ code: "unknown_error",
45
+ type: status >= 500 ? "internal" : "unknown",
46
+ message: statusText || `HTTP ${status}`,
47
+ method,
48
+ path,
49
+ });
50
+ }
@@ -0,0 +1,6 @@
1
+ export { Releases } from "./client.js";
2
+ export type { ChangelogQuery, CollectionReleasesQuery, CursorQuery, DeliveriesQuery, Kind, LatestReleasesQuery, OrgListQuery, OrgReleasesQuery, ProductRef, SearchQuery, SourceRef, WhatsChangedQuery, } from "./client.js";
3
+ export { ReleasesError } from "./errors.js";
4
+ export { DEFAULT_BASE_URL, type ClientOptions, type RequestOptions } from "./transport.js";
5
+ export type * from "@buildinternet/releases-api-types";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EACV,cAAc,EACd,uBAAuB,EACvB,WAAW,EACX,eAAe,EACf,IAAI,EACJ,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE3F,mBAAmB,mCAAmC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Releases } from "./client.js";
2
+ export { ReleasesError } from "./errors.js";
3
+ export { DEFAULT_BASE_URL } from "./transport.js";
@@ -0,0 +1,43 @@
1
+ export declare const DEFAULT_BASE_URL = "https://api.releases.sh";
2
+ export interface ClientOptions {
3
+ /**
4
+ * A `relu_` user key, a `relk_` API token, or a publish token. Optional:
5
+ * public reads work anonymously at a lower rate limit.
6
+ */
7
+ apiKey?: string;
8
+ /** API origin. Defaults to `https://api.releases.sh`. */
9
+ baseUrl?: string;
10
+ /** A `fetch` implementation. Defaults to the global `fetch`. */
11
+ fetch?: typeof fetch;
12
+ /** Headers sent with every request. */
13
+ headers?: Record<string, string>;
14
+ }
15
+ /** Per-call options accepted by every method. */
16
+ export interface RequestOptions {
17
+ signal?: AbortSignal;
18
+ headers?: Record<string, string>;
19
+ /**
20
+ * Sent as `Idempotency-Key` on writes. Retrying with the same key replays
21
+ * the first response instead of repeating the write.
22
+ */
23
+ idempotencyKey?: string;
24
+ }
25
+ export type QueryValue = string | number | boolean | null | undefined;
26
+ interface Call extends RequestOptions {
27
+ /** Values for the `:name` segments of the operation's path. */
28
+ params?: Record<string, string>;
29
+ query?: object;
30
+ body?: unknown;
31
+ }
32
+ /**
33
+ * Sends one operation. `operation` is the API surface key, `"GET /orgs/:slug"`:
34
+ * the same string `apps/api/src/api-surface.ts` lists, which a test checks so
35
+ * the SDK only calls public routes.
36
+ */
37
+ export declare class Transport {
38
+ #private;
39
+ constructor(options?: ClientOptions);
40
+ request<T>(operation: string, call?: Call): Promise<T>;
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEtE,UAAU,IAAK,SAAQ,cAAc;IACnC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,qBAAa,SAAS;;IAMpB,YAAY,OAAO,GAAE,aAAkB,EAKtC;IAEK,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,IAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAsC/D;CACF"}
@@ -0,0 +1,80 @@
1
+ import { errorFromResponse, ReleasesError } from "./errors.js";
2
+ export const DEFAULT_BASE_URL = "https://api.releases.sh";
3
+ /**
4
+ * Sends one operation. `operation` is the API surface key, `"GET /orgs/:slug"`:
5
+ * the same string `apps/api/src/api-surface.ts` lists, which a test checks so
6
+ * the SDK only calls public routes.
7
+ */
8
+ export class Transport {
9
+ #apiKey;
10
+ #baseUrl;
11
+ #fetch;
12
+ #headers;
13
+ constructor(options = {}) {
14
+ this.#apiKey = options.apiKey;
15
+ this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
16
+ this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
17
+ this.#headers = options.headers ?? {};
18
+ }
19
+ async request(operation, call = {}) {
20
+ const space = operation.indexOf(" ");
21
+ const method = operation.slice(0, space);
22
+ const path = buildPath(operation.slice(space + 1), call.params ?? {});
23
+ const url = `${this.#baseUrl}/v1${path}${buildQuery(call.query)}`;
24
+ const headers = { Accept: "application/json", ...this.#headers };
25
+ if (this.#apiKey)
26
+ headers.Authorization = `Bearer ${this.#apiKey}`;
27
+ if (call.body !== undefined)
28
+ headers["Content-Type"] = "application/json";
29
+ if (call.idempotencyKey)
30
+ headers["Idempotency-Key"] = call.idempotencyKey;
31
+ Object.assign(headers, call.headers);
32
+ let res;
33
+ try {
34
+ res = await this.#fetch(url, {
35
+ method,
36
+ headers,
37
+ body: call.body === undefined ? undefined : JSON.stringify(call.body),
38
+ signal: call.signal,
39
+ });
40
+ }
41
+ catch (cause) {
42
+ throw new ReleasesError({
43
+ status: 0,
44
+ code: "network_error",
45
+ type: "network",
46
+ message: `${method} ${path} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
47
+ method,
48
+ path,
49
+ cause,
50
+ });
51
+ }
52
+ if (!res.ok) {
53
+ const body = await res.json().catch(() => null);
54
+ throw errorFromResponse(res.status, res.statusText, body, method, path);
55
+ }
56
+ if (res.status === 204)
57
+ return undefined;
58
+ return (await res.json());
59
+ }
60
+ }
61
+ function buildPath(pattern, params) {
62
+ return pattern.replace(/:(\w+)/g, (_, name) => {
63
+ const value = params[name];
64
+ if (value === undefined || value === "")
65
+ throw new TypeError(`Missing path parameter "${name}"`);
66
+ return encodeURIComponent(value);
67
+ });
68
+ }
69
+ function buildQuery(query) {
70
+ if (!query)
71
+ return "";
72
+ const search = new URLSearchParams();
73
+ for (const [key, value] of Object.entries(query)) {
74
+ if (value === undefined || value === null)
75
+ continue;
76
+ search.set(key, String(value));
77
+ }
78
+ const qs = search.toString();
79
+ return qs ? `?${qs}` : "";
80
+ }
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@buildinternet/releases-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for the Releases API (api.releases.sh).",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/buildinternet/releases.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "scripts": {
26
+ "build": "tsc -p tsconfig.build.json",
27
+ "prepack": "bun ../../scripts/pack-dist.ts prepack ../core ../api-types",
28
+ "postpack": "bun ../../scripts/pack-dist.ts postpack"
29
+ },
30
+ "dependencies": {
31
+ "@buildinternet/releases-api-types": "0.59.0"
32
+ }
33
+ }