@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 +196 -2
- package/dist/client.d.ts +47 -0
- package/dist/errors.d.ts +35 -0
- package/dist/generated/resources.d.ts +1460 -0
- package/dist/generated/schema.d.ts +16240 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1926 -0
- package/dist/middleware.d.ts +15 -0
- package/dist/retry.d.ts +22 -0
- package/dist/transport.d.ts +15 -0
- package/dist/types.d.ts +9 -0
- package/dist/webhook-events.d.ts +99 -0
- package/dist/webhooks.d.ts +27 -0
- package/package.json +39 -4
package/README.md
CHANGED
|
@@ -1,3 +1,197 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @prism-draft/sdk
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
package/dist/client.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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
|
+
}
|