@andesine/sdk 0.6.0 → 0.7.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/CHANGELOG.md +83 -0
- package/README.md +11 -3
- package/WEBHOOKS.md +7 -0
- package/dist/{client-DioqVOxl.js → client-D7hqNIEY.js} +74 -18
- package/dist/client-D7hqNIEY.js.map +1 -0
- package/dist/{client-C8wt7f3o.d.ts → client-TZ83eSln.d.ts} +91 -21
- package/dist/extensions.d.ts +1 -1
- package/dist/extensions.js +1 -1
- package/dist/{index-B5qefVRv.d.ts → index-BL6xmKO6.d.ts} +20 -8
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/{schema-CrquN_J6.d.ts → schema-D7yh5Hbs.d.ts} +3487 -592
- package/dist/streaming.d.ts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.js +1861 -959
- package/dist/webhooks.js.map +1 -1
- package/openapi.json +6386 -836
- package/package.json +1 -1
- package/dist/client-DioqVOxl.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- `auth.getIdentity` returns `client` (ID and registered name) for OAuth access tokens.
|
|
6
|
+
- Webhook events have an `actor`: the member, API key, extension, or app (OAuth client ID and
|
|
7
|
+
registered name, with the member) that made the change, or `null` for changes by Andesine.
|
|
8
|
+
- OAuth for MCP clients: the authorization server supports the authorization code flow with
|
|
9
|
+
PKCE and dynamic client registration, with the scopes `andesine:read` and `andesine:write`.
|
|
10
|
+
`auth.getIdentity` returns `scopes` and `workspaceID` for such tokens.
|
|
11
|
+
- `auth.getIdentity` returns the `permissions` of an API key, and `instance.get` returns
|
|
12
|
+
`appURL`, the web app URL (entries open at `{appURL}/{workspaceID}/{entryID}`).
|
|
13
|
+
- `versions.create` takes an optional `document`: ProseMirror JSON checked against the editor
|
|
14
|
+
and the entry's collection schema. The version has the new reason `import`, and the
|
|
15
|
+
current content does not change.
|
|
16
|
+
- `entries.get` returns `hash`, the hash of the current content, comparable to version hashes.
|
|
17
|
+
- Add `entries.move` and `collections.move` (API key permissions `entries` and
|
|
18
|
+
`collections`). Without `order`, a moved entry keeps its position in the same collection
|
|
19
|
+
and goes last in a new one.
|
|
20
|
+
- `entries.update` replaces the content with `document` and `expectedHash`, the hash of the
|
|
21
|
+
content it replaces. Only the changed blocks change, so live edits elsewhere are kept. A
|
|
22
|
+
different current hash returns the new error `CONTENT_HASH_MISMATCH` (409) with the current
|
|
23
|
+
`hash`. Current content without a version is first saved as an `auto` version, and `version`
|
|
24
|
+
also saves the new content as a `manual` version. Use `name` or `document`, not both. The
|
|
25
|
+
result is `{ hash, version }`; before, it was empty.
|
|
26
|
+
- `entries.create` takes an optional `document` in place of `name`; its title sets the name.
|
|
27
|
+
- MDX content: `entries.get`, `versions.get`, and `content.get` take `contentFormat: "mdx"`
|
|
28
|
+
and then also return the content as MDX in `mdx`: the title as H1, properties as
|
|
29
|
+
`<Property name value />` elements in their position, fragments as `<Fragment name>`
|
|
30
|
+
elements, and images as `asset:<assetID>` references (delivery URLs in published content).
|
|
31
|
+
`entries.create`, `entries.update`, and `versions.create` take `mdx` in place of `document`;
|
|
32
|
+
frontmatter properties are also read. Fields match by name or key. Node IDs and attributes
|
|
33
|
+
that MDX doesn't hold are kept from the current content. Invalid MDX returns the new error
|
|
34
|
+
`CONTENT_FORMAT_INVALID` (400) with the line and column.
|
|
35
|
+
- `Fragment` and `Property` are reserved element names; content writes with them return
|
|
36
|
+
`CONTENT_SCHEMA_INVALID`.
|
|
37
|
+
- `entries.update` takes `operations`, applied in order with all or none applied:
|
|
38
|
+
`replaceText` (`find` must occur once in one block's MDX), `replaceBlock`, `insertBlocks`
|
|
39
|
+
(`before`, `after`, or `position` with an optional `fragment`), `deleteBlocks`, and `setField`
|
|
40
|
+
(a property value, or MDX for a fragment). Anchors come from `entries.get` with
|
|
41
|
+
`contentFormat: "blocks"`, which returns the blocks with IDs, fragment keys, and MDX.
|
|
42
|
+
`expectedHash` is optional with operations; `name` can be combined with them. A missing or
|
|
43
|
+
ambiguous anchor returns the new error `CONTENT_OPERATION_INVALID` (409) with the operation
|
|
44
|
+
`index` and `reason`.
|
|
45
|
+
- Internal links: link marks can point to entries with `entry:<entryID>`, and an optional
|
|
46
|
+
`#anchor`. Content writes also accept `entry:<path>` (a path or slug path) and store the entry
|
|
47
|
+
ID; a link to a missing entry returns the new error `CONTENT_LINK_INVALID` (409).
|
|
48
|
+
`entries.get` and `versions.get` return `references`: the path, slug path, title, and
|
|
49
|
+
collection of each linked entry, or `available: false` for deleted and unreadable entries.
|
|
50
|
+
MDX and blocks show links by slug path. Published content resolves links to the entry's slug
|
|
51
|
+
path in the snapshot, and removes links to entries that aren't published (the text stays).
|
|
52
|
+
- Images follow content: content writes attach the `asset:<assetID>` images they newly
|
|
53
|
+
reference (the caller must be able to read each image and update the entry), so
|
|
54
|
+
`assets.attach` is no longer part of the public API. `entries.update` and `versions.create`
|
|
55
|
+
also import image URLs in MDX and wait until the images are ready; an image that fails or is
|
|
56
|
+
still processing returns the new error `CONTENT_IMAGE_UNAVAILABLE` (409). `entries.create`
|
|
57
|
+
accepts only `asset:` images. To remove an image, remove it from the content.
|
|
58
|
+
- `entries.create` takes `path`: a full name path. Missing parent collections are created with
|
|
59
|
+
exact names; a name already used at the path returns `CONTENT_NAME_CONFLICT`.
|
|
60
|
+
- Target collections by path: `entries.create` and `entries.move` take `collectionPath` or
|
|
61
|
+
`collectionSlugPath` in place of `collectionID`, `collections.create` takes `parentPath` or
|
|
62
|
+
`parentSlugPath`, `collections.move` takes `newParentPath` or `newParentSlugPath` (`/` is the
|
|
63
|
+
top level), and `publishing.getChannelContent` takes `collectionPath` or `collectionSlugPath`.
|
|
64
|
+
These select existing collections only.
|
|
65
|
+
- Preconditions: `expectedName` on `entries.update` and `collections.update`,
|
|
66
|
+
`expectedCollectionID` on `entries.move`, and `expectedParentID` on `collections.move`. A
|
|
67
|
+
different current value returns the new error `CONTENT_PRECONDITION_FAILED` (409) with the
|
|
68
|
+
current value.
|
|
69
|
+
- `Idempotency-Key` header on `entries.create`, `entries.update`, and `versions.create`: a
|
|
70
|
+
repeated request returns the first result for 24 hours (with `Idempotent-Replayed: true`).
|
|
71
|
+
The same key with a different body returns `IDEMPOTENCY_KEY_REUSED` (422), and a repeat while
|
|
72
|
+
the first request runs returns `IDEMPOTENCY_KEY_IN_PROGRESS` (409). The SDK sends a generated
|
|
73
|
+
key for these operations unless `headers` has one, and `retries` now also applies to them, including a retry that
|
|
74
|
+
arrives while the first attempt still runs (`IDEMPOTENCY_KEY_IN_PROGRESS`, with
|
|
75
|
+
`Retry-After: 1`).
|
|
76
|
+
- Add `entries.bulkUpdate`: the items of `entries.update` for many entries in one call (a name
|
|
77
|
+
without content is written as the title). All items are checked before any content changes,
|
|
78
|
+
and all or none apply; errors name the failing item. Supports `Idempotency-Key`.
|
|
79
|
+
- Add `entries.bulkMove`: the items of `entries.move` in one call, all or none. Moves that need a
|
|
80
|
+
schema migration aren't supported; use `entries.move` for them.
|
|
81
|
+
- Add `publishing.updateChannel`: renames a channel; its code doesn't change.
|
|
82
|
+
- `entries.create` takes `fields`: property values and fragment MDX by field name or key.
|
|
83
|
+
- `entries.get` returns the content of a document that is open in the editor, including edits
|
|
84
|
+
that aren't saved yet, so its `hash` matches what content writes compare against.
|
|
85
|
+
|
|
3
86
|
## 0.6.0
|
|
4
87
|
|
|
5
88
|
- No changes; versioned with the other public Andesine packages.
|
package/README.md
CHANGED
|
@@ -176,9 +176,17 @@ request execution, response reading, and retry delays. Supply `signal` per call
|
|
|
176
176
|
for cancellation.
|
|
177
177
|
|
|
178
178
|
Retries are **disabled by default**. Set `retries: 2` on the client or a call to
|
|
179
|
-
allow up to two retries of GET requests.
|
|
180
|
-
and HTTP 429, 500, 502, 503, and 504. They respect
|
|
181
|
-
increasing delays with jitter.
|
|
179
|
+
allow up to two retries of GET requests and idempotent writes. They retry network
|
|
180
|
+
`TypeError` failures and HTTP 429, 500, 502, 503, and 504. They respect
|
|
181
|
+
`Retry-After`, otherwise use increasing delays with jitter. Other writes are never
|
|
182
|
+
retried automatically.
|
|
183
|
+
|
|
184
|
+
Idempotent writes (`entries.create`, `entries.update`, and `versions.create`) send
|
|
185
|
+
an `Idempotency-Key` header, generated for each call unless you supply one in
|
|
186
|
+
`headers`. The server applies a key once and returns the first result for 24 hours,
|
|
187
|
+
so retries can't apply a write twice. A retry that arrives while the first attempt
|
|
188
|
+
still runs gets `IDEMPOTENCY_KEY_IN_PROGRESS` and is retried after `Retry-After`. Supply your own key to make a write safe
|
|
189
|
+
across separate calls, for example after a process restart.
|
|
182
190
|
|
|
183
191
|
```ts
|
|
184
192
|
await client.entries.get(
|
package/WEBHOOKS.md
CHANGED
|
@@ -275,6 +275,13 @@ or restricted resources can become unavailable. Publication snapshot references
|
|
|
275
275
|
can expire under the workspace policy; webhooks do not pin snapshots. Handle
|
|
276
276
|
missing references explicitly.
|
|
277
277
|
|
|
278
|
+
Each event has an `actor`: who made the change. It is a member (`memberID`), an
|
|
279
|
+
API key (`keyID`), an extension (`extensionID`), or an app such as an MCP client
|
|
280
|
+
(`clientID`, the `name` that the app registered, and the authorizing `memberID`).
|
|
281
|
+
It is `null` for changes by Andesine itself, and for editor saves that combine
|
|
282
|
+
several people's edits. App names are chosen by the app; don't use them for
|
|
283
|
+
access decisions. An extension's own webhooks don't receive its changes.
|
|
284
|
+
|
|
278
285
|
## Rotation, replay, and recovery
|
|
279
286
|
|
|
280
287
|
Normal rotation returns a new secret once and signs with both old and new keys
|
|
@@ -124,16 +124,6 @@ const createResources = (request) => ({
|
|
|
124
124
|
binary: false,
|
|
125
125
|
anonymous: false
|
|
126
126
|
}),
|
|
127
|
-
attach: operation(request, {
|
|
128
|
-
method: "post",
|
|
129
|
-
path: "/assets/{assetID}/attachments",
|
|
130
|
-
pathParams: ["assetID"],
|
|
131
|
-
queryParams: [],
|
|
132
|
-
body: true,
|
|
133
|
-
multipart: false,
|
|
134
|
-
binary: false,
|
|
135
|
-
anonymous: false
|
|
136
|
-
}),
|
|
137
127
|
register: operation(request, {
|
|
138
128
|
method: "post",
|
|
139
129
|
path: "/assets/uploads",
|
|
@@ -187,6 +177,7 @@ const createResources = (request) => ({
|
|
|
187
177
|
}) },
|
|
188
178
|
entries: {
|
|
189
179
|
create: operation(request, {
|
|
180
|
+
idempotent: true,
|
|
190
181
|
method: "post",
|
|
191
182
|
path: "/entries",
|
|
192
183
|
pathParams: [],
|
|
@@ -217,6 +208,7 @@ const createResources = (request) => ({
|
|
|
217
208
|
anonymous: false
|
|
218
209
|
}),
|
|
219
210
|
update: operation(request, {
|
|
211
|
+
idempotent: true,
|
|
220
212
|
method: "put",
|
|
221
213
|
path: "/entries/{id}",
|
|
222
214
|
pathParams: ["id"],
|
|
@@ -226,6 +218,37 @@ const createResources = (request) => ({
|
|
|
226
218
|
binary: false,
|
|
227
219
|
anonymous: false
|
|
228
220
|
}),
|
|
221
|
+
bulkUpdate: operation(request, {
|
|
222
|
+
idempotent: true,
|
|
223
|
+
method: "post",
|
|
224
|
+
path: "/entries/bulk/update",
|
|
225
|
+
pathParams: [],
|
|
226
|
+
queryParams: [],
|
|
227
|
+
body: true,
|
|
228
|
+
multipart: false,
|
|
229
|
+
binary: false,
|
|
230
|
+
anonymous: false
|
|
231
|
+
}),
|
|
232
|
+
move: operation(request, {
|
|
233
|
+
method: "post",
|
|
234
|
+
path: "/entries/{id}/move",
|
|
235
|
+
pathParams: ["id"],
|
|
236
|
+
queryParams: [],
|
|
237
|
+
body: true,
|
|
238
|
+
multipart: false,
|
|
239
|
+
binary: false,
|
|
240
|
+
anonymous: false
|
|
241
|
+
}),
|
|
242
|
+
bulkMove: operation(request, {
|
|
243
|
+
method: "post",
|
|
244
|
+
path: "/entries/bulk/move",
|
|
245
|
+
pathParams: [],
|
|
246
|
+
queryParams: [],
|
|
247
|
+
body: true,
|
|
248
|
+
multipart: false,
|
|
249
|
+
binary: false,
|
|
250
|
+
anonymous: false
|
|
251
|
+
}),
|
|
229
252
|
get: operation(request, {
|
|
230
253
|
method: "get",
|
|
231
254
|
path: "/entries/get",
|
|
@@ -234,7 +257,8 @@ const createResources = (request) => ({
|
|
|
234
257
|
"id",
|
|
235
258
|
"path",
|
|
236
259
|
"slugPath",
|
|
237
|
-
"expectedSchemaHash"
|
|
260
|
+
"expectedSchemaHash",
|
|
261
|
+
"contentFormat"
|
|
238
262
|
],
|
|
239
263
|
body: false,
|
|
240
264
|
multipart: false,
|
|
@@ -395,6 +419,16 @@ const createResources = (request) => ({
|
|
|
395
419
|
binary: false,
|
|
396
420
|
anonymous: false
|
|
397
421
|
}),
|
|
422
|
+
move: operation(request, {
|
|
423
|
+
method: "post",
|
|
424
|
+
path: "/collections/{id}/move",
|
|
425
|
+
pathParams: ["id"],
|
|
426
|
+
queryParams: [],
|
|
427
|
+
body: true,
|
|
428
|
+
multipart: false,
|
|
429
|
+
binary: false,
|
|
430
|
+
anonymous: false
|
|
431
|
+
}),
|
|
398
432
|
list: operation(request, {
|
|
399
433
|
method: "get",
|
|
400
434
|
path: "/collections/list",
|
|
@@ -494,7 +528,8 @@ const createResources = (request) => ({
|
|
|
494
528
|
"slugPath",
|
|
495
529
|
"expectedSchemaHash",
|
|
496
530
|
"channel",
|
|
497
|
-
"snapshotID"
|
|
531
|
+
"snapshotID",
|
|
532
|
+
"contentFormat"
|
|
498
533
|
],
|
|
499
534
|
body: false,
|
|
500
535
|
multipart: false,
|
|
@@ -1148,12 +1183,26 @@ const createResources = (request) => ({
|
|
|
1148
1183
|
method: "get",
|
|
1149
1184
|
path: "/publishing/channels/{channel}/content",
|
|
1150
1185
|
pathParams: ["channel"],
|
|
1151
|
-
queryParams: [
|
|
1186
|
+
queryParams: [
|
|
1187
|
+
"collectionID",
|
|
1188
|
+
"collectionPath",
|
|
1189
|
+
"collectionSlugPath"
|
|
1190
|
+
],
|
|
1152
1191
|
body: false,
|
|
1153
1192
|
multipart: false,
|
|
1154
1193
|
binary: false,
|
|
1155
1194
|
anonymous: false
|
|
1156
1195
|
}),
|
|
1196
|
+
updateChannel: operation(request, {
|
|
1197
|
+
method: "put",
|
|
1198
|
+
path: "/publishing/channels/{code}",
|
|
1199
|
+
pathParams: ["code"],
|
|
1200
|
+
queryParams: [],
|
|
1201
|
+
body: true,
|
|
1202
|
+
multipart: false,
|
|
1203
|
+
binary: false,
|
|
1204
|
+
anonymous: false
|
|
1205
|
+
}),
|
|
1157
1206
|
deleteChannel: operation(request, {
|
|
1158
1207
|
method: "delete",
|
|
1159
1208
|
path: "/publishing/channels/{code}",
|
|
@@ -1177,6 +1226,7 @@ const createResources = (request) => ({
|
|
|
1177
1226
|
}) },
|
|
1178
1227
|
versions: {
|
|
1179
1228
|
create: operation(request, {
|
|
1229
|
+
idempotent: true,
|
|
1180
1230
|
method: "post",
|
|
1181
1231
|
path: "/entries/{entryID}/versions",
|
|
1182
1232
|
pathParams: ["entryID"],
|
|
@@ -1200,7 +1250,7 @@ const createResources = (request) => ({
|
|
|
1200
1250
|
method: "get",
|
|
1201
1251
|
path: "/versions/{id}",
|
|
1202
1252
|
pathParams: ["id"],
|
|
1203
|
-
queryParams: ["expectedSchemaHash"],
|
|
1253
|
+
queryParams: ["expectedSchemaHash", "contentFormat"],
|
|
1204
1254
|
body: false,
|
|
1205
1255
|
multipart: false,
|
|
1206
1256
|
binary: false,
|
|
@@ -1274,6 +1324,10 @@ const retryDelay = (attempt, response) => {
|
|
|
1274
1324
|
if (Number.isFinite(date)) return Math.max(0, date - Date.now());
|
|
1275
1325
|
return Math.min(1e3 * 2 ** attempt, 3e4) + Math.random() * 250;
|
|
1276
1326
|
};
|
|
1327
|
+
const isInProgressWrite = async (request, response) => {
|
|
1328
|
+
if (response.status !== 409 || !request.headers.has("Idempotency-Key")) return false;
|
|
1329
|
+
return (await response.clone().json().catch(() => null))?.code === "IDEMPOTENCY_KEY_IN_PROGRESS";
|
|
1330
|
+
};
|
|
1277
1331
|
const fetchWithRetries = async (fetcher, request, retries) => {
|
|
1278
1332
|
const signal = request.signal;
|
|
1279
1333
|
for (let attempt = 0;; attempt += 1) {
|
|
@@ -1286,13 +1340,14 @@ const fetchWithRetries = async (fetcher, request, retries) => {
|
|
|
1286
1340
|
await wait(retryDelay(attempt), signal);
|
|
1287
1341
|
continue;
|
|
1288
1342
|
}
|
|
1289
|
-
|
|
1343
|
+
const retryable = [
|
|
1290
1344
|
429,
|
|
1291
1345
|
500,
|
|
1292
1346
|
502,
|
|
1293
1347
|
503,
|
|
1294
1348
|
504
|
|
1295
|
-
].includes(response.status)
|
|
1349
|
+
].includes(response.status) || await isInProgressWrite(request, response);
|
|
1350
|
+
if (attempt >= retries || !retryable) return response;
|
|
1296
1351
|
const delay = retryDelay(attempt, response);
|
|
1297
1352
|
await response.body?.cancel();
|
|
1298
1353
|
await wait(delay, signal);
|
|
@@ -1343,6 +1398,7 @@ const createClient = (config = {}) => {
|
|
|
1343
1398
|
new Headers(options.headers).forEach((value, key) => headers.set(key, value));
|
|
1344
1399
|
if (definition.streaming && !headers.has("Accept")) headers.set("Accept", "text/event-stream");
|
|
1345
1400
|
if (credential && !definition.anonymous) headers.set("Authorization", `Bearer ${credential}`);
|
|
1401
|
+
if (definition.idempotent && !headers.has("Idempotency-Key")) headers.set("Idempotency-Key", crypto.randomUUID());
|
|
1346
1402
|
const timer = timeout > 0 ? setTimeout(() => controller.abort(new DOMException("Request timed out", "TimeoutError")), timeout) : void 0;
|
|
1347
1403
|
let streamOwnsRequest = false;
|
|
1348
1404
|
try {
|
|
@@ -1361,7 +1417,7 @@ const createClient = (config = {}) => {
|
|
|
1361
1417
|
else if (item !== void 0) form.append(key, typeof item === "string" ? item : JSON.stringify(item));
|
|
1362
1418
|
return form;
|
|
1363
1419
|
} : void 0,
|
|
1364
|
-
fetch: (request) => fetchWithRetries(fetcher, request, definition.method === "get" ? retries : 0)
|
|
1420
|
+
fetch: (request) => fetchWithRetries(fetcher, request, definition.method === "get" || definition.idempotent ? retries : 0)
|
|
1365
1421
|
});
|
|
1366
1422
|
const response = result.response;
|
|
1367
1423
|
if (response.status === 304) {
|
|
@@ -1421,4 +1477,4 @@ const createClient = (config = {}) => {
|
|
|
1421
1477
|
//#endregion
|
|
1422
1478
|
export { paginate as n, paginatePages as r, createClient as t };
|
|
1423
1479
|
|
|
1424
|
-
//# sourceMappingURL=client-
|
|
1480
|
+
//# sourceMappingURL=client-D7hqNIEY.js.map
|