@prism-draft/sdk 0.0.0-stage → 0.2.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,197 @@
1
- # Temporary Holding Version
1
+ # @prism-draft/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
+ A typed TypeScript client for the PrismDraft API, with webhook verification. It is
4
+ generated from the API's OpenAPI document, so a route that changes shape changes the
5
+ types and the tests fail until the SDK is regenerated.
6
+
7
+ Runs on any runtime with global `fetch` and Web Crypto. The pack test installs the packed
8
+ tarball and runs a client, a request, a timeout and a webhook signature under Bun and Node
9
+ 20, 22 and 24 (Node 20.20, 22.23 and 24.21 were the versions run; `engines` says
10
+ `node >= 20`). It imports nothing from `node:` or `bun:`. A custom `fetch` must honour the
11
+ request's `signal`, which `timeoutMs` and cancellation rely on.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install @prism-draft/sdk
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ `apiKey` is a workspace API key (`pd_live_…`) created in the web app, under Settings, API
22
+ keys. It carries exactly the permissions of its role.
23
+
24
+ ```ts
25
+ import { PrismDraft } from "@prism-draft/sdk";
26
+
27
+ const pd = new PrismDraft({ apiKey: process.env.PRISMDRAFT_API_KEY! });
28
+
29
+ const { articles, total } = await pd.articles.list({ workspaceId, status: "PUBLISHED" });
30
+ console.log(`${articles.length} of ${total} published articles`);
31
+ ```
32
+
33
+ The example assumes `workspaceId` is in scope: the id after `/w/` in the web app's address.
34
+
35
+ ## Resources
36
+
37
+ One property per resource, one method per operation. Each method has `<Resource><Method>Input`
38
+ and `<Resource><Method>Output` types, exported from the package (`ArticlesListInput`).
39
+
40
+ | Property | Methods |
41
+ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
+ | `pd.articles` | `create` `list` `duplicate` `get` `update` `delete` `submit` `approve` `reject` `requestChanges` `publish` `unpublish` `transitionTranslations` `listReviews` `listVersions` `getVersion` `getSources` `restoreVersion` `listComments` `addComment` `resolveComment` `reopenComment` `checkDuplicates` `setHeroFromUrl` `setHeroFromFile` `detachHeroFromBody` |
43
+ | `pd.articleGroups` | `list` `get` `delete` `approve` `publish` `unpublish` |
44
+ | `pd.categories` | `list` `create` `update` `delete` `setForArticle` `suggestForArticle` `suggestForProject` `translate` |
45
+ | `pd.projects` | `list` `create` `get` `update` `delete` `archive` `restore` `listMembers` `addMember` `setMemberRole` `removeMember` |
46
+ | `pd.generation` | `estimate` `getOutline` `approveOutline` `aiEdit` `start` `getStatus` `cancel` `regenerateImage` `updateAsset` `deleteAsset` |
47
+ | `pd.translations` | `estimate` `create` |
48
+ | `pd.webhooks` | `list` `create` `delete` |
49
+ | `pd.integrations` | `list` `create` `update` `delete` |
50
+ | `pd.knowledge` | `list` `create` `uploadContent` `reingest` `delete` |
51
+ | `pd.brandVoices` | `list` `create` `update` `delete` |
52
+ | `pd.contentTemplates` | `list` `create` `update` `delete` |
53
+ | `pd.briefs` | `draft` `list` `create` `get` `update` `delete` |
54
+ | `pd.ideas` | `list` `create` `update` `delete` `generate` |
55
+ | `pd.calendar` | `list` `create` `update` `delete` |
56
+ | `pd.links` | `list` `generate` `decide` |
57
+ | `pd.social` | `generate` `list` |
58
+ | `pd.siteAnalyses` | `start` `get` `apply` |
59
+ | `pd.dashboard` | `getAnalytics` `getTrends` `getArticleCounts` `listJobs` `listAudit` `getCreditBurn` |
60
+ | `pd.usage` | `listTransactions` `get` `listPayments` `listQuotas` `setQuota` |
61
+ | `pd.billing` | `listCreditPackages` `getCredits` `adjustCredits` `listPlans` `getSubscription` `startSubscriptionCheckout` `openPortal` `startCheckout` |
62
+ | `pd.workspaces` | `list` `create` `get` `update` `listMembers` `addMember` `setMemberRoles` `removeMember` |
63
+ | `pd.roles` | `list` `create` `update` `delete` `duplicate` |
64
+ | `pd.permissions` | `list` |
65
+ | `pd.invitations` | `list` `revoke` `resend` |
66
+ | `pd.apiKeys` | `list` `create` `revoke` `rotate` |
67
+ | `pd.blog` | `listPosts` `listCategories` `getPost` `getPostEdit` (PrismDraft's public blog: the first three need no valid key) |
68
+
69
+ ## Documentation
70
+
71
+ The guides and the reference for every method are at <https://docs.prism-draft.com/sdk>.
72
+
73
+ - [Authentication](https://docs.prism-draft.com/sdk/guides/authentication): keys, roles, the workspace id, rotation.
74
+ - [Conventions](https://docs.prism-draft.com/sdk/guides/conventions): input, output, pagination, `pd.raw`.
75
+ - [Errors](https://docs.prism-draft.com/sdk/guides/errors): `PrismDraftError` and every error code.
76
+ - [Retries and timeouts](https://docs.prism-draft.com/sdk/guides/retries-and-timeouts)
77
+ - [Uploads](https://docs.prism-draft.com/sdk/guides/uploads)
78
+ - [Webhooks](https://docs.prism-draft.com/sdk/guides/webhooks): verifying a delivery and every event.
79
+ - [Versioning](https://docs.prism-draft.com/sdk/guides/versioning)
80
+ - [Recipes](https://docs.prism-draft.com/sdk/guides/recipes): publish an imported article, sync a site, paginate, bulk-update categories.
81
+
82
+ The constructor throws a `TypeError` for an empty `apiKey`, so an unset environment
83
+ variable fails at start-up instead of as a 401 later.
84
+
85
+ ## License
86
+
87
+ `UNLICENSED`: the package is public to install, and no permission is granted to copy or
88
+ redistribute its code. Using it to call the PrismDraft API is what it is for.
89
+
90
+ ## Releasing
91
+
92
+ Published from `main` by a maintainer who owns or belongs to the `prism-draft` npm
93
+ organisation and has a second factor that npm still accepts.
94
+
95
+ **One-time setup (done for `argus416`)**
96
+
97
+ 1. Create the organisation `prism-draft` at <https://www.npmjs.com/org/create> (the free
98
+ plan covers public packages). The scope `@prism-draft` exists only after this; before
99
+ it, `npm publish` fails with `Scope not found`.
100
+ 2. Enable two-factor authentication on the publishing account, set to "Authorization and
101
+ publishing". npm no longer accepts new authenticator-app (TOTP) enrolments
102
+ (`Adding a new TOTP 2FA is no longer supported`): add a passkey or security key at
103
+ `https://npmjs.com/settings/<user>/tfa`. Touch ID through the browser's passkey prompt is
104
+ enough. Without 2FA the registry answers `403 Two-factor authentication or granular access
105
+ token with bypass 2fa enabled is required to publish packages`.
106
+ 3. Log in: `npm login`, then check `npm whoami` and `npm org ls prism-draft`.
107
+
108
+ Tokens that bypass 2FA are being restricted by npm, so do not build the release around one.
109
+
110
+ **Each release**
111
+
112
+ ```sh
113
+ git switch main && git pull
114
+ # bump "version" in packages/sdk/package.json (0.x: minor for new API surface or
115
+ # SDK changes, patch for fixes), commit it, and merge it through a pull request
116
+ bun install # a stale install fails the build: "Cannot find module 'openapi-fetch'"
117
+ bun run --cwd apps/api openapi:export && bun run --cwd packages/sdk generate # must leave no diff
118
+ cd packages/sdk && bun --env-file=../../.env test
119
+ npm publish --dry-run # `prepublishOnly` builds; check the file list: dist/, README, package.json
120
+ npm publish --access public # prints a URL; approve it with the passkey
121
+ git tag sdk-v<version> && git push origin sdk-v<version>
122
+ ```
123
+
124
+ `bun publish --dry-run --access public` produces the same 14-file tarball (Bun 1.4.2 runs
125
+ `prepublishOnly`, `prepack` and `postpack`). Use `npm publish` for the real release: its
126
+ passkey approval flow is the one that has been exercised.
127
+
128
+ Afterwards, check `npm view @prism-draft/sdk version` and install it in an empty directory
129
+ (`bun add @prism-draft/sdk`, then import `PrismDraft`). The registry can take a minute to
130
+ show a new version.
131
+
132
+ A published version cannot be replaced. Fix forward with a patch release; `npm deprecate`
133
+ marks a bad one.
134
+
135
+ ## Development
136
+
137
+ The package exports its TypeScript source inside the workspace (`"exports":
138
+ "./src/index.ts"`) and `dist/` only in the packed tarball.
139
+
140
+ **What is generated.** `openapi.json` is a snapshot of the API's OpenAPI document,
141
+ filtered to what an API key can use. `src/generated/schema.ts` (types, by
142
+ `openapi-typescript`) and `src/generated/resources.ts` (one class per resource) come
143
+ from it. All three are committed. After any route change:
144
+
145
+ ```sh
146
+ bun run --cwd apps/api openapi:export
147
+ bun run --cwd packages/sdk generate
148
+ ```
149
+
150
+ Two tests fail on a stale file: `apps/api/test/openapi-snapshot.test.ts` (the snapshot
151
+ against the live document) and `packages/sdk/test/generated.test.ts` (the generated
152
+ files against the snapshot).
153
+
154
+ **Coverage gate.** `test/response-coverage.test.ts` requires every operation in the
155
+ surface to declare a JSON 2xx `response` schema (a 204-only operation is exempt) and an
156
+ `operationId` of the form `resource.method`, unique across the API. The
157
+ `resource` part becomes the class (`articleGroups` is `pd.articleGroups`) and
158
+ `method` the method. `bun run --cwd packages/sdk coverage [Tag …]` lists what is still
159
+ missing, read from the live app, not the snapshot.
160
+
161
+ **The surface is a denylist.** `scripts/surface.ts` lists `SDK_EXCLUDED_TAGS` and
162
+ `SDK_EXCLUDED_OPERATIONS` (`"METHOD /path"`, each with its reason) and drops every
163
+ `/events` stream and every untagged operation. Everything else is in the SDK by default,
164
+ so a new route needs its `operationId` and response schema or the gate fails. A route
165
+ that cannot be a typed method (a download, a redirect, a person's browser flow) goes on
166
+ the denylist, with a reason; `test/surface.test.ts` checks that every entry still exists.
167
+
168
+ **To add an operation:** declare `operationId` and the 2xx `response` schema in the
169
+ route, add a test that parses a real answer with that schema (see
170
+ `apps/api/test/sdk-responses-*.test.ts`), run the two commands above, commit the
171
+ regenerated files. A request body the document cannot describe (an upload read by a
172
+ content-type parser) goes in `RAW_BODY_OPERATIONS` in `scripts/generate.ts`.
173
+
174
+ **`typescript-classic`.** A devDependency, an alias of `typescript@5.9`, used only by
175
+ `scripts/generate.ts`. `openapi-typescript` builds its output with the classic compiler
176
+ API (`ts.factory`), which the workspace's TypeScript 7 (the Go compiler) does not
177
+ have; the generator registers the alias in place of `typescript` before importing it.
178
+ `tsc` everywhere else is still TypeScript 7. It is a devDependency, so it is not in the
179
+ tarball's dependencies.
180
+
181
+ **Build.** `bun run build` writes `dist/index.js` (one ESM file, not minified, with
182
+ `openapi-fetch` left as an import) and the `.d.ts` files. Three points about
183
+ TypeScript 7:
184
+
185
+ - Declarations are emitted by `tsc` (`tsconfig.build.json`, `emitDeclarationOnly`), which
186
+ works, but needs an explicit `rootDir` (TS5011 otherwise).
187
+ - `rewriteRelativeImportExtensions` rewrites `./x.ts` to `./x.js` in emitted JavaScript,
188
+ not in declarations, which kept the `.ts` specifiers. `scripts/build.ts` rewrites
189
+ them in `dist/**/*.d.ts` after `tsc` runs. The declarations are still the compiler's.
190
+ - npm and Bun ignore `publishConfig.exports` (pnpm applies it), so a plain `npm pack`
191
+ would export `./src/index.ts`, which the tarball does not contain. `prepack` and
192
+ `postpack` (`scripts/pack-manifest.ts`) swap the published `exports` in for the
193
+ pack and put the workspace form back, and drop `scripts` and `devDependencies`.
194
+
195
+ `test/pack.test.ts` builds, packs, installs the tarball into an empty project, typechecks a
196
+ consumer and every example in this README and in `docs/guides` with `tsc`, and imports it
197
+ under Bun and Node.
@@ -0,0 +1,47 @@
1
+ import { PrismDraftResources } from "./generated/resources.js";
2
+ import { type FetchLike } from "./retry.js";
3
+ import type { PrismDraftClient } from "./transport.js";
4
+ export declare const DEFAULT_BASE_URL = "https://api.prism-draft.com";
5
+ export interface PrismDraftOptions {
6
+ /** A workspace API key (`pd_live_…`). Must not be empty. */
7
+ apiKey: string;
8
+ /** An absolute `http(s)` URL; default `https://api.prism-draft.com`. */
9
+ baseUrl?: string;
10
+ /**
11
+ * Replaces the global `fetch`. A plain function is enough (no `preconnect`, which Bun's
12
+ * `typeof fetch` requires); the SDK calls it with a `Request`, and with `requestInit` after it.
13
+ */
14
+ fetch?: FetchLike;
15
+ /**
16
+ * Passed as the second argument of every `fetch` call, retries included: `{ cache: "no-store" }`
17
+ * for Next.js, a `dispatcher` for undici. `body`, `method`, `headers` and `signal` belong to
18
+ * the client and are ignored if present.
19
+ */
20
+ requestInit?: Omit<RequestInit, "body" | "method" | "headers" | "signal">;
21
+ /** Retries for GET/HEAD on network errors and 429/502/503/504. Default 2; 0 disables. */
22
+ maxRetries?: number;
23
+ /**
24
+ * Default for every call: an attempt that takes longer fails with a `PrismDraftTimeoutError`.
25
+ * Per attempt, so a retried read gets a fresh timer. Unset: no timeout. A finite number above 0;
26
+ * a call's `init.timeoutMs` overrides it.
27
+ */
28
+ timeoutMs?: number;
29
+ /**
30
+ * The longest a retry waits, in ms; default 8000. A `Retry-After` above it is not retried
31
+ * (the answer is returned), so 60000 rides out the API's rate-limit window. A finite number above 0.
32
+ */
33
+ maxRetryDelayMs?: number;
34
+ }
35
+ /**
36
+ * The API client: one property per resource (`pd.articles.list({ workspaceId })`),
37
+ * and `raw`, the underlying `openapi-fetch` client, for anything the methods do not cover.
38
+ * Every non-2xx answer throws a `PrismDraftError`.
39
+ */
40
+ export declare class PrismDraft extends PrismDraftResources {
41
+ readonly raw: PrismDraftClient;
42
+ /**
43
+ * @throws {TypeError} for an empty `apiKey`, a `baseUrl` that is not an absolute http(s) URL,
44
+ * or a `timeoutMs` or `maxRetryDelayMs` that is not a finite number above 0.
45
+ */
46
+ constructor(options: PrismDraftOptions);
47
+ }
@@ -0,0 +1,35 @@
1
+ /** The API's structured error (spec §71): `{ error: { code, message, details? } }`. */
2
+ export declare class PrismDraftError extends Error {
3
+ readonly status: number;
4
+ readonly code: string;
5
+ readonly details: Record<string, unknown> | undefined;
6
+ constructor(status: number, code: string, message: string, details?: Record<string, unknown>);
7
+ }
8
+ /**
9
+ * A request that did not finish within `timeoutMs` (the client option or the per-call one).
10
+ * Not a `PrismDraftError`: the API never answered. A call the caller cancelled with its own
11
+ * `signal` rejects with that signal's reason instead.
12
+ */
13
+ export declare class PrismDraftTimeoutError extends Error {
14
+ readonly timeoutMs: number;
15
+ constructor(timeoutMs: number);
16
+ }
17
+ /** A webhook delivery whose signature is missing, malformed, stale or wrong. */
18
+ export declare class WebhookSignatureError extends Error {
19
+ constructor(message?: string);
20
+ }
21
+ /**
22
+ * A delivery with a valid signature whose body is not what the API sends: `reason` is `not_json`
23
+ * or `not_envelope` (JSON, but not `{ event, data }`). A `WebhookSignatureError` too, so a caller
24
+ * that catches that class keeps working; test for this class first to answer 400 here and 401 for
25
+ * the plain signature error.
26
+ */
27
+ export declare class WebhookPayloadError extends WebhookSignatureError {
28
+ readonly reason: "not_json" | "not_envelope";
29
+ constructor(reason: WebhookPayloadError["reason"], message: string);
30
+ }
31
+ /** A signed delivery for an event this SDK version does not know. Answer 2xx and ignore it. */
32
+ export declare class UnknownWebhookEventError extends Error {
33
+ readonly event: string;
34
+ constructor(event: string);
35
+ }