@andesine/sdk 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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +728 -0
- package/dist/answer-stream-7N_gLhVS.js +193 -0
- package/dist/answer-stream-7N_gLhVS.js.map +1 -0
- package/dist/index-DFEcYmbh.d.ts +23370 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1167 -0
- package/dist/index.js.map +1 -0
- package/dist/streaming.d.ts +41 -0
- package/dist/streaming.js +93 -0
- package/dist/streaming.js.map +1 -0
- package/openapi.json +27190 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
# @andesine/sdk
|
|
2
|
+
|
|
3
|
+
MIT-licensed TypeScript client for the public Andesine HTTP API. ESM only. Requires
|
|
4
|
+
Node.js 22 or later, or a browser/worker runtime with Fetch, Blob, FormData, and
|
|
5
|
+
AbortSignal.any. Keep private API keys in server code.
|
|
6
|
+
|
|
7
|
+
## Use
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @andesine/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createClient } from "@andesine/sdk";
|
|
15
|
+
|
|
16
|
+
const client = createClient({
|
|
17
|
+
baseURL: "https://api.andesine.app",
|
|
18
|
+
apiKey: process.env.ANDESINE_API_KEY
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const entry = await client.entries.get({ id: "ent_123" });
|
|
22
|
+
const published = await client.content.get({
|
|
23
|
+
entryID: entry.id,
|
|
24
|
+
channel: "published"
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`baseURL` is required. A trailing slash is optional; path prefixes are preserved.
|
|
29
|
+
Each client has its own configuration. You can supply `fetch` and default
|
|
30
|
+
`headers` for proxies, instrumentation, or other authentication arrangements.
|
|
31
|
+
|
|
32
|
+
Resource methods are generated from the checked-in OpenAPI document. They cover
|
|
33
|
+
assets, entries, collections, content delivery, roles, search, schemas, schema
|
|
34
|
+
migrations, schema versions, memberships, publishing, entry versions, and instance information. Internal
|
|
35
|
+
session-only operations are excluded. Published asset delivery does not require
|
|
36
|
+
an API key.
|
|
37
|
+
|
|
38
|
+
Methods accept one object containing path, query, and body fields. Operations
|
|
39
|
+
without required fields can be called without arguments. Date/time values remain
|
|
40
|
+
JSON strings. File inputs and binary outputs use `Blob`.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
await client.assets.upload({ assetID: "ast_123", file: new Blob([bytes]) });
|
|
44
|
+
const image = await client.assets.get({ assetID: "ast_123", entryID: entry.id });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The client does not add a content-save endpoint. Use converters to produce JSON;
|
|
48
|
+
storage remains a separate responsibility.
|
|
49
|
+
|
|
50
|
+
## Errors and responses
|
|
51
|
+
|
|
52
|
+
Methods return data directly. HTTP failures throw `AndesineAPIError`, with
|
|
53
|
+
`status`, `code`, `message`, `data`, `defined`, `hints`, and the Fetch `response`. Network failures and
|
|
54
|
+
cancellation retain their native errors.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { AndesineAPIError } from "@andesine/sdk";
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await client.entries.update({ id: "ent_123", name: "Installation" });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof AndesineAPIError) {
|
|
63
|
+
console.error(error.status, error.code, error.message, error.hints);
|
|
64
|
+
|
|
65
|
+
if (error.is("SCHEMA_MIGRATION_IN_PROGRESS")) {
|
|
66
|
+
// data is typed for this code.
|
|
67
|
+
console.log(error.data.migrationID);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (error.is("TOO_MANY_REQUESTS")) {
|
|
71
|
+
console.log(error.data?.retryAfterSeconds);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = await client.content.get(
|
|
77
|
+
{ entryID: "ent_123" },
|
|
78
|
+
{ response: "full", headers: { "If-None-Match": cachedETag } }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (result.notModified) {
|
|
82
|
+
// Reuse the cached content.
|
|
83
|
+
} else {
|
|
84
|
+
console.log(result.data, result.headers.get("ETag"));
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use full-response mode for conditional requests. A 304 response has
|
|
89
|
+
`notModified: true` and no data. A 304 in data mode causes a clear error because
|
|
90
|
+
the client does not store a response cache.
|
|
91
|
+
|
|
92
|
+
## Cancellation, timeouts, and retries
|
|
93
|
+
|
|
94
|
+
The default timeout is 30 seconds. Set `timeout: 0` to disable it. Timeouts include
|
|
95
|
+
request execution, response reading, and retry delays. Supply `signal` per call
|
|
96
|
+
for cancellation.
|
|
97
|
+
|
|
98
|
+
Retries are **disabled by default**. Set `retries: 2` on the client or a call to
|
|
99
|
+
allow up to two retries of GET requests. Reads retry network `TypeError` failures
|
|
100
|
+
and HTTP 429, 500, 502, 503, and 504. They respect `Retry-After`, otherwise use
|
|
101
|
+
increasing delays with jitter. Writes are never retried automatically.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
await client.entries.get(
|
|
105
|
+
{ id: "ent_123" },
|
|
106
|
+
{ signal: controller.signal, timeout: 10_000, retries: 2 }
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Snapshots and pagination
|
|
111
|
+
|
|
112
|
+
Pin related content reads to the snapshot returned by the first read:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const tree = await client.content.getTree({ collectionID: "col_123" });
|
|
116
|
+
const content = client.atSnapshot(tree.snapshotID);
|
|
117
|
+
const entry = await content.get({ entryID: "ent_123" });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`atSnapshot()` returns content resource methods. It rejects a conflicting channel
|
|
121
|
+
or snapshot selector. It does not pin unrelated management operations. Snapshot
|
|
122
|
+
image URLs can expire; copying images for archival export is the caller's task.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { paginate } from "@andesine/sdk";
|
|
126
|
+
|
|
127
|
+
for await (const entry of paginate((cursor) => client.entries.list({ cursor }))) {
|
|
128
|
+
console.log(entry.name);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Cursor lists default to 50 items and accept at most 100. `memberships.list()` and
|
|
133
|
+
`memberships.listInvites()` return `{ data, pagination }`. Migration status no longer
|
|
134
|
+
includes `contentLossEntries`; use `schemaMigrations.listContentLossEntries()` for
|
|
135
|
+
that paginated list. Mutable management lists are not snapshots: concurrent edits
|
|
136
|
+
can change their contents between requests.
|
|
137
|
+
|
|
138
|
+
Use `paginatePages()` to keep page metadata and save a cursor after processing a
|
|
139
|
+
whole page. Both iterators accept `{ cursor }` to resume. Save the same filters with
|
|
140
|
+
your cursor. A missing or repeated continuation cursor causes an error.
|
|
141
|
+
|
|
142
|
+
Published listings also require the snapshot ID on later pages. The first page
|
|
143
|
+
selects a channel; later pages select that exact snapshot:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { paginatePages } from "@andesine/sdk";
|
|
147
|
+
|
|
148
|
+
let snapshotID: string | undefined; // Restore this together with a saved cursor.
|
|
149
|
+
const savedCursor: string | undefined = undefined;
|
|
150
|
+
|
|
151
|
+
for await (const page of paginatePages(
|
|
152
|
+
(cursor) =>
|
|
153
|
+
client.content.listEntries({
|
|
154
|
+
...(snapshotID ? { snapshotID } : { channel: "published" }),
|
|
155
|
+
cursor,
|
|
156
|
+
limit: 100
|
|
157
|
+
}),
|
|
158
|
+
{ cursor: savedCursor }
|
|
159
|
+
)) {
|
|
160
|
+
snapshotID = page.snapshotID;
|
|
161
|
+
// Process page.data, then save snapshotID and page.pagination.nextCursor.
|
|
162
|
+
// A null nextCursor means the listing is complete.
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`content.listCollections()` uses the same format. Both return flat records ordered
|
|
167
|
+
by ID, with parent/collection IDs for links. Entry summaries include a version ID and
|
|
168
|
+
hash. Use `includeContent: true` for full entry records, or fetch one entry with
|
|
169
|
+
`client.atSnapshot(snapshotID).get()`. Snapshot expiry
|
|
170
|
+
still applies. If a saved snapshot is unavailable, start a new listing from its
|
|
171
|
+
channel; do not combine the old cursor with a new snapshot.
|
|
172
|
+
|
|
173
|
+
### Filtered full-content lists
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const page = await client.content.listEntries({
|
|
177
|
+
collectionPath: "/Tutorials",
|
|
178
|
+
descendants: true,
|
|
179
|
+
includeContent: true,
|
|
180
|
+
filters: [
|
|
181
|
+
{ kind: "text", key: "audience", values: ["beginner"] },
|
|
182
|
+
{ kind: "boolean", key: "enabled", value: true }
|
|
183
|
+
],
|
|
184
|
+
limit: 50
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
for (const entry of page.data) {
|
|
188
|
+
console.log(entry.path, entry.content, entry.properties, entry.schema);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (page.pagination.nextCursor) {
|
|
192
|
+
const next = await client.atSnapshot(page.snapshotID).listEntries({
|
|
193
|
+
collectionPath: "/Tutorials",
|
|
194
|
+
descendants: true,
|
|
195
|
+
includeContent: true,
|
|
196
|
+
filters: [
|
|
197
|
+
{ kind: "text", key: "audience", values: ["beginner"] },
|
|
198
|
+
{ kind: "boolean", key: "enabled", value: true }
|
|
199
|
+
],
|
|
200
|
+
cursor: page.pagination.nextCursor,
|
|
201
|
+
limit: 50
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`includeContent` defaults to false. A literal true gives full items; false or an
|
|
207
|
+
omitted option gives summaries. A runtime boolean gives their union. This also
|
|
208
|
+
works with full-response mode, `atSnapshot()`, and `paginatePages()`. Full items
|
|
209
|
+
add `content`, `properties`, `fragments`, `assets`, `schema`, and full version
|
|
210
|
+
metadata. Channel and snapshot metadata stay in the page envelope. The generated
|
|
211
|
+
`PublishedEntryContent` and `PublishedEntrySummary` types are public.
|
|
212
|
+
|
|
213
|
+
With a collection selector, the list returns direct entries by default.
|
|
214
|
+
`descendants: true` includes nested collections and requires a collection selector.
|
|
215
|
+
Use `collectionPath: "/"` with descendants for the complete snapshot tree. Without
|
|
216
|
+
a scope, the list includes all snapshot entries.
|
|
217
|
+
|
|
218
|
+
Filters use the same contract as search: text `any`/`all`/`none`, number and date
|
|
219
|
+
comparisons, and boolean equality. All filters must match. Text comparison ignores
|
|
220
|
+
case; missing or incompatible properties do not match negative operators. Filters
|
|
221
|
+
use the assigned version's property records, including fields between content
|
|
222
|
+
blocks. They do not use the current draft or the search index. The SDK encodes
|
|
223
|
+
nested filters automatically.
|
|
224
|
+
|
|
225
|
+
Keep the same scope and filters across pages. Limits remain 1–100 entries and up
|
|
226
|
+
to 20 filters, with the existing per-filter bounds. Assets are URLs and metadata.
|
|
227
|
+
The server validates each returned full document against its recorded schema. An
|
|
228
|
+
invalid document fails the page with `CONTENT_SCHEMA_INVALID`; it is not skipped.
|
|
229
|
+
Summary reads do not load or validate full documents.
|
|
230
|
+
|
|
231
|
+
### Structured content
|
|
232
|
+
|
|
233
|
+
Use `toStructuredContent()` to remove property and fragment wrappers:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { toStructuredContent } from "@andesine/sdk";
|
|
237
|
+
|
|
238
|
+
const page = await client.content.get({ path: "/Tutorials/Welcome" });
|
|
239
|
+
const tutorial = toStructuredContent(page);
|
|
240
|
+
|
|
241
|
+
console.log(tutorial.properties); // Each property's value, keyed by derived key.
|
|
242
|
+
console.log(tutorial.fragments); // Each fragment's content, keyed by derived key.
|
|
243
|
+
console.log(tutorial.schema, tutorial.path); // Other response fields are preserved.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The helper also accepts current full entries and full published list items:
|
|
247
|
+
`page.data.map(toStructuredContent)`. Property and fragment maps stay separate.
|
|
248
|
+
Values such as `null`, false, zero, empty strings, and empty arrays are preserved.
|
|
249
|
+
The helper is synchronous. It creates a new object and maps, does not change its
|
|
250
|
+
input, and retains references to values and other fields. It does not validate,
|
|
251
|
+
coerce, fetch, or generate collection-specific types. Types follow the supplied
|
|
252
|
+
input; API property values retain their generated union types. `StructuredContent<T>`
|
|
253
|
+
is available for explicit annotations. Collection type generation remains deferred.
|
|
254
|
+
|
|
255
|
+
## Publishing preconditions
|
|
256
|
+
|
|
257
|
+
Pass `expectedSnapshotID` to publish, unpublish, or delete a channel only if its
|
|
258
|
+
publication state still matches a prior read:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const reviewed = await client.content.listEntries({ channel: "published", limit: 1 });
|
|
262
|
+
|
|
263
|
+
await client.publishing.publishEntry({
|
|
264
|
+
entryID: "ent_123",
|
|
265
|
+
channel: "published",
|
|
266
|
+
expectedSnapshotID: reviewed.snapshotID
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The server checks the channel inside the mutation transaction. A mismatch returns
|
|
271
|
+
HTTP 409 with code `PUBLISHING_SNAPSHOT_CHANGED`, the expected and current snapshot
|
|
272
|
+
IDs, and a hint. Read the current state and review it before another write. Omitting
|
|
273
|
+
the precondition makes no comparison with your prior read. Draft edits are not
|
|
274
|
+
covered by this check.
|
|
275
|
+
|
|
276
|
+
`setCollection()` and `bulkSetCollections()` can affect multiple channels. Their
|
|
277
|
+
optional `expectedSnapshots` map checks each supplied channel before the change,
|
|
278
|
+
for example `{ published: "snp_123", preview: "snp_456" }`. Omitted channels are not
|
|
279
|
+
checked. `revertChanges()` continues to require its reviewed `snapshotID`.
|
|
280
|
+
|
|
281
|
+
Bulk inputs accept at most 100 selected items. Revert selections share that limit
|
|
282
|
+
across entries and collections. A selected collection can contain more descendants;
|
|
283
|
+
this is an input limit, not a limit on the number of affected records. Separate bulk
|
|
284
|
+
requests are separate transactions.
|
|
285
|
+
|
|
286
|
+
## Instance information
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
const instance = await client.instance.get();
|
|
290
|
+
console.log(instance.features.imageStorage, instance.features.semanticSearch);
|
|
291
|
+
console.log(instance.features.aiAnswers, instance.features.aiAnswerStreaming);
|
|
292
|
+
console.log(instance.limits.maxUploadBytes, instance.limits.assetStorageBytes);
|
|
293
|
+
console.log(instance.limits.maxPageSize, instance.limits.maxBulkItems);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
This authenticated call returns the API contract version, configured features,
|
|
297
|
+
and effective workspace limits, including the search result limit. Features do
|
|
298
|
+
not grant permissions or report live service health. Storage limits are capacity,
|
|
299
|
+
not remaining space. `aiAnswers` requires search, embedding, and answer-model
|
|
300
|
+
configuration. `aiAnswerStreaming` reports the configured streaming implementation;
|
|
301
|
+
the AI provider must support Chat Completions SSE. These flags do not report
|
|
302
|
+
remaining quota. The SDK makes no discovery request automatically.
|
|
303
|
+
|
|
304
|
+
## Types
|
|
305
|
+
|
|
306
|
+
Named types are generated from the public OpenAPI components. Import them directly
|
|
307
|
+
or through the type-only `Andesine` namespace:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import type { Entry, PropertyFilter, Andesine } from "@andesine/sdk";
|
|
311
|
+
|
|
312
|
+
const renderEntry = (entry: Entry): string => entry.name;
|
|
313
|
+
const renderAsset = (asset: Andesine.Asset): string => asset.filename;
|
|
314
|
+
const filter: PropertyFilter = {
|
|
315
|
+
kind: "text",
|
|
316
|
+
key: "category",
|
|
317
|
+
values: ["guides"]
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The exports cover entries and properties; assets, files, and analysis; collections;
|
|
322
|
+
entry and schema versions; schema fields, inheritance, and migrations; memberships,
|
|
323
|
+
invites, roles, and permissions; published content and trees; publishing channels;
|
|
324
|
+
search results and filters; and pagination.
|
|
325
|
+
|
|
326
|
+
`Entry` includes content, fragments, and properties. `EntrySummary` is the metadata
|
|
327
|
+
returned by entry lists and creation. `SchemaDefinition` describes a collection's
|
|
328
|
+
content schema; `CollectionSchema` contains its local and effective schema details.
|
|
329
|
+
Types describe the HTTP data: dates are strings and binary values are `Blob`.
|
|
330
|
+
Search filter types describe inputs, so fields with server defaults can be omitted.
|
|
331
|
+
|
|
332
|
+
Every public operation also has generated input, output, and error aliases. Their names
|
|
333
|
+
combine the resource and method with `Input`, `Output`, or `Error`:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import type { EntriesCreateInput, EntriesListOutput, Andesine } from "@andesine/sdk";
|
|
337
|
+
|
|
338
|
+
const input: EntriesCreateInput = { name: "Getting started" };
|
|
339
|
+
type EntryPage = EntriesListOutput;
|
|
340
|
+
type SearchRequest = Andesine.SearchCurrentInput;
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Operation inputs match the SDK's single argument object, including path, query,
|
|
344
|
+
and body fields. Outputs match the returned data, excluding the full-response
|
|
345
|
+
wrapper. All these exports, including `Andesine`, are types only and add no runtime
|
|
346
|
+
code. The generic helpers and raw `components`, `operations`, and `paths` types
|
|
347
|
+
remain available:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
import type { OperationInput, OperationOutput } from "@andesine/sdk";
|
|
351
|
+
|
|
352
|
+
type CreateEntry = OperationInput<"entries.create">;
|
|
353
|
+
type EntryPage = OperationOutput<"entries.list">;
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Content types are structurally compatible with `@andesine/converters`. Neither
|
|
357
|
+
public package imports the private editor or backend at runtime.
|
|
358
|
+
|
|
359
|
+
## Maintainer workflow
|
|
360
|
+
|
|
361
|
+
From the repository root:
|
|
362
|
+
|
|
363
|
+
```sh
|
|
364
|
+
pnpm --filter @andesine/backend openapi:export
|
|
365
|
+
pnpm --filter @andesine/sdk generate
|
|
366
|
+
pnpm --filter @andesine/sdk build
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The export calls oRPC's OpenAPI generator on the same contracts implemented by
|
|
370
|
+
the server. It imports no request handlers or service initialization and needs no
|
|
371
|
+
backend environment variables, database, queue, or running server. The export
|
|
372
|
+
uses the default upload-size limit; self-hosted instances can set a different
|
|
373
|
+
limit, which their own OpenAPI document reflects. The checked-in document's
|
|
374
|
+
server URL is a placeholder; consumers must supply their instance URL.
|
|
375
|
+
|
|
376
|
+
Commit `openapi.json` and `src/generated/` together. Do not edit generated files.
|
|
377
|
+
Public schema names are defined in the backend's
|
|
378
|
+
`src/contracts/schemas/public.ts` registry. Adding a component there generates its
|
|
379
|
+
named SDK export and its `Andesine` member; no SDK alias list needs to be updated.
|
|
380
|
+
The generator also creates input/output/error aliases for each public operation and
|
|
381
|
+
rejects invalid or duplicate public type names.
|
|
382
|
+
Generation is a maintainer operation, never an install-time operation. `prepack`
|
|
383
|
+
checks types, bundles ESM and declarations with Rolldown, and checks
|
|
384
|
+
package metadata with publint. These 0.x packages are pre-release; review
|
|
385
|
+
generated changes together with API changes.
|
|
386
|
+
|
|
387
|
+
See the [release guide](https://github.com/vriteio/andesine/blob/andesine/packages/public/README.md)
|
|
388
|
+
for archive checks, versioning, and npm publishing. This package uses the
|
|
389
|
+
[MIT license](./LICENSE).
|
|
390
|
+
|
|
391
|
+
### Generator choice
|
|
392
|
+
|
|
393
|
+
Both Hey API 0.99.0 and openapi-typescript 7.13.0 generated output from the Andesine
|
|
394
|
+
spec. This package uses openapi-typescript and openapi-fetch. A small generated
|
|
395
|
+
resource layer supplies the flat argument objects and instance methods; a shared
|
|
396
|
+
transport supplies errors, retries, multipart serialization, and full responses.
|
|
397
|
+
|
|
398
|
+
Hey API generated operation functions and a larger client layer, but still needed
|
|
399
|
+
adaptation for the selected public interface and multipart operation defaults.
|
|
400
|
+
The selected approach keeps these behaviors in one maintained transport and
|
|
401
|
+
keeps runtime dependencies small.
|
|
402
|
+
|
|
403
|
+
## Content names
|
|
404
|
+
|
|
405
|
+
Sibling entries and collections share names. Names are trimmed, normalized to
|
|
406
|
+
Unicode NFC, and compared case-sensitively. Names cannot contain `/` or equal
|
|
407
|
+
`.` or `..`. The limit is 300 JavaScript string units. The collection name `~`
|
|
408
|
+
is reserved for the internal workspace root. Root entries and root collections
|
|
409
|
+
share the same level.
|
|
410
|
+
|
|
411
|
+
Creation chooses an available name, adding suffixes such as ` (2)` when needed.
|
|
412
|
+
Use the returned name rather than assuming that your requested name was saved.
|
|
413
|
+
Rename, move, and restore operations reject conflicts. Deleted items release
|
|
414
|
+
names; a schema move temporarily reserves its source name for rollback.
|
|
415
|
+
|
|
416
|
+
`CONTENT_NAME_CONFLICT` (409) includes `name`, `parentID`, and optional `hints`.
|
|
417
|
+
Use `error.is("CONTENT_NAME_CONFLICT")` to access typed data. Renaming never
|
|
418
|
+
chooses a different name for you.
|
|
419
|
+
|
|
420
|
+
## Content paths
|
|
421
|
+
|
|
422
|
+
Read an entry by ID or by path. Use exactly one selector:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
const current = await client.entries.get({ path: "/Docs/Getting started" });
|
|
426
|
+
const tree = await client.content.getTree({ collectionPath: "/Docs" });
|
|
427
|
+
const content = client.atSnapshot(tree.snapshotID);
|
|
428
|
+
const page = await content.get({ path: "/Docs/Getting started" });
|
|
429
|
+
const definition = await content.getSchema({ path: page.path });
|
|
430
|
+
const activeSchema = await client.schemas.get({ collectionPath: "/Docs" });
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Paths use names from the tree, with `/` between levels. Names are case-sensitive;
|
|
434
|
+
each segment is trimmed and NFC-normalized. Pass decoded names to the SDK. It
|
|
435
|
+
encodes query parameters once, so a name containing the literal text `%2F` keeps
|
|
436
|
+
that text. Empty segments, trailing slashes, `.` and `..` are invalid.
|
|
437
|
+
|
|
438
|
+
An absolute path starts with `/`. An anchored path starts with a collection ID,
|
|
439
|
+
such as `coll_67bqzSOb9kct2DaVkTwT53/Getting started`. The anchor must exist in the
|
|
440
|
+
selected current tree or snapshot. A name that looks like an ID is still a name
|
|
441
|
+
when it occurs in an absolute path. A bare collection ID is also a valid
|
|
442
|
+
`collectionPath`. `/` selects the root. Neither selects an entry on its own.
|
|
443
|
+
|
|
444
|
+
Current entry and collection responses include their canonical absolute `path`.
|
|
445
|
+
Published content, trees, and list results use snapshot collection names and the
|
|
446
|
+
titles of assigned versions. Draft renames, moves, and deletions do not change
|
|
447
|
+
those snapshot paths. Full published content also includes `id` and `collectionID`.
|
|
448
|
+
Saved versions remain ID-based because a version alone has no snapshot location.
|
|
449
|
+
|
|
450
|
+
`content.getTree({ collectionPath: "/" })` returns a virtual root collection with
|
|
451
|
+
`id: null`, `name: ""`, and `path: "/"`. It contains top-level entries and collections.
|
|
452
|
+
|
|
453
|
+
`entries.list`, `collections.list`, `content.listEntries`, and
|
|
454
|
+
`content.listCollections` accept either `collectionID` or `collectionPath` to
|
|
455
|
+
select direct children. Keep the same scope while paging. With no scope,
|
|
456
|
+
`collections.list` selects root children; the other lists select all accessible
|
|
457
|
+
items. `collections.list` now uses these fields instead of `ancestorID`.
|
|
458
|
+
Search accepts the same collection selectors and includes descendants. Its `/`
|
|
459
|
+
scope covers the whole accessible tree. Search results include a canonical entry
|
|
460
|
+
`path` and an optional heading `anchor`.
|
|
461
|
+
|
|
462
|
+
HTTP reads use query selectors on `GET /entries/get`, `GET /content/entries/get`,
|
|
463
|
+
`GET /content/tree`, and `GET /schemas/collection`. The published schema route
|
|
464
|
+
remains `GET /content/entries/schema`. These replace the former ID-in-path GET
|
|
465
|
+
routes; mutation routes are unchanged. SDK method names are unchanged.
|
|
466
|
+
|
|
467
|
+
Publication checks names across the complete next snapshot, including retained
|
|
468
|
+
versions. `PUBLISHING_NAME_CONFLICT` (409) includes `name`, `parentID`, and `hints`.
|
|
469
|
+
Publish related replacements together or unpublish the conflict first. A draft
|
|
470
|
+
rename alone does not remove a name from an existing publication. A rejected
|
|
471
|
+
publication leaves the channel and its snapshot unchanged.
|
|
472
|
+
|
|
473
|
+
## Search and source links
|
|
474
|
+
|
|
475
|
+
Published search results include required `channel`, `snapshotID`, `versionID`,
|
|
476
|
+
and canonical entry `path` fields. `entryID` remains the stable identity.
|
|
477
|
+
An optional `anchor` identifies a heading in that version; a result before any
|
|
478
|
+
heading has no anchor. Current results have their indexed working path. Search
|
|
479
|
+
index updates are asynchronous, so current paths can briefly lag draft changes.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
const matches = await client.search.published({
|
|
483
|
+
channel: "published",
|
|
484
|
+
query: "installation"
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
for (const result of matches.results) {
|
|
488
|
+
const page = await client.atSnapshot(result.snapshotID).get({
|
|
489
|
+
entryID: result.entryID
|
|
490
|
+
});
|
|
491
|
+
const pathname = result.path.split("/").map(encodeURIComponent).join("/");
|
|
492
|
+
const link = pathname + (result.anchor ? `#${encodeURIComponent(result.anchor)}` : "");
|
|
493
|
+
// Map pathname to your docs site's route and render page with matching heading IDs.
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Paths and anchors are decoded data, not application URLs. Encode each path segment
|
|
498
|
+
when building a URL. Reading by `entryID` at the source snapshot preserves the
|
|
499
|
+
content version even if the channel advances. A retained snapshot can expire;
|
|
500
|
+
handle an unavailable snapshot with a new search. Search still uses the current
|
|
501
|
+
channel and does not support historical snapshot queries.
|
|
502
|
+
|
|
503
|
+
The converters' `getHeadingAnchors()` helper derives matching targets. HTML
|
|
504
|
+
conversion applies those IDs; other renderers must preserve AST IDs or apply the
|
|
505
|
+
helper themselves. Duplicate headings are numbered across all fragments. Existing
|
|
506
|
+
`headingPath` is retained for editor navigation. Answer sources use the same path,
|
|
507
|
+
snapshot, and anchor metadata, plus a numbered citation `id` and `relevance`.
|
|
508
|
+
|
|
509
|
+
## AI answers
|
|
510
|
+
|
|
511
|
+
Call these methods from your server with an API key. For published documentation,
|
|
512
|
+
grant `ai-answers` and `read:publishing`. For current workspace content, grant
|
|
513
|
+
`ai-answers`, `read:entries`, and `read:collections`. Write permissions also supply
|
|
514
|
+
the corresponding read access, but never imply `ai-answers`. The key editor has a
|
|
515
|
+
separate AI answers option.
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
import type { AnswerHistoryMessage, PublishedAnswerSource } from "@andesine/sdk";
|
|
519
|
+
|
|
520
|
+
const history: AnswerHistoryMessage[] = [];
|
|
521
|
+
const result = await client.search.askPublished({
|
|
522
|
+
channel: "published",
|
|
523
|
+
collectionPath: "/Docs",
|
|
524
|
+
question: "How do I install Andesine?",
|
|
525
|
+
history
|
|
526
|
+
});
|
|
527
|
+
const sources: PublishedAnswerSource[] = result.sources;
|
|
528
|
+
|
|
529
|
+
// [1] in result.answer refers to the source with id === 1.
|
|
530
|
+
// Use each source's path/anchor for links and snapshotID/entryID for content reads.
|
|
531
|
+
history.push(
|
|
532
|
+
{ role: "user", content: "How do I install Andesine?" },
|
|
533
|
+
{ role: "assistant", content: result.answer }
|
|
534
|
+
);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
`client.search.askCurrent({ question, collectionPath, history })` reads current
|
|
538
|
+
content with the caller's collection access rules. Both methods accept optional
|
|
539
|
+
property `filters` and either `collectionID` or `collectionPath`. Published scopes
|
|
540
|
+
resolve within the channel's current snapshot. Omit the collection selector to
|
|
541
|
+
search all content available to the caller.
|
|
542
|
+
|
|
543
|
+
Responses contain the complete `answer` and `sources`. The generated `Answer`,
|
|
544
|
+
`PublishedAnswer`, `AnswerSource`, `PublishedAnswerSource`, and
|
|
545
|
+
`AnswerHistoryMessage` types are also available through `Andesine`.
|
|
546
|
+
Both complete and streamed answers use the same inputs and source-selection rules.
|
|
547
|
+
|
|
548
|
+
The limits are 1,000 characters per question, 10 history messages of up to 4,000
|
|
549
|
+
characters each, and 20 property filters. History is supplied per request; the SDK
|
|
550
|
+
does not store it. Trim history before sending a later question. The existing Ask
|
|
551
|
+
AI rate limit is 10 requests per 60 seconds per credential. API usage metering
|
|
552
|
+
is unchanged: a successful complete response counts once when billing is enabled.
|
|
553
|
+
A rate-limit error includes `Retry-After` and `data.retryAfterSeconds`. Provider
|
|
554
|
+
failures return `SERVICE_UNAVAILABLE` with hints. Answer POST requests are never
|
|
555
|
+
automatically retried, even when read retries are enabled.
|
|
556
|
+
|
|
557
|
+
## Recorded content schemas
|
|
558
|
+
|
|
559
|
+
Full entry, version, and published-content responses include `schema`, containing
|
|
560
|
+
`revisionID` and `hash`, or `null` for content without a recorded schema. Reads and
|
|
561
|
+
publication validate the document against that recorded revision, including
|
|
562
|
+
inherited fields. They do not repair content or use the latest schema instead.
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
const page = await client.content.get({ entryID: "ent_123" });
|
|
566
|
+
const definition = await client.atSnapshot(page.snapshotID).getSchema({
|
|
567
|
+
entryID: page.id
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// Use a hash saved by your application when it requires a specific definition.
|
|
571
|
+
const checked = await client.content.get({
|
|
572
|
+
entryID: page.id,
|
|
573
|
+
snapshotID: page.snapshotID,
|
|
574
|
+
expectedSchemaHash: "YOUR_SAVED_64_CHARACTER_SHA256_HASH"
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
`content.getSchema()` uses published-content access. It returns the selected
|
|
579
|
+
entry's recorded definition or `null`. `schemas.getRevision({ revisionID })`
|
|
580
|
+
requires collection-read access and returns an exact revision, including inactive
|
|
581
|
+
revisions. Both return `{ revisionID, hash, definition }`. The definition combines
|
|
582
|
+
inherited fields without internal source metadata and matches the recorded hash.
|
|
583
|
+
|
|
584
|
+
`expectedSchemaHash` is also available on `entries.get()`, `versions.get()`, and
|
|
585
|
+
`publishing.getEntryVersion()`. A mismatch, including content without a schema,
|
|
586
|
+
returns `CONTENT_SCHEMA_MISMATCH` (409) with the expected hash and actual metadata.
|
|
587
|
+
Invalid stored content returns `CONTENT_SCHEMA_INVALID` (500) on delivery or (409)
|
|
588
|
+
on publication, with issue paths and available entry, version, and revision IDs.
|
|
589
|
+
`SCHEMA_FIELD_KEY_CONFLICT` (409) identifies conflicting derived field keys.
|
|
590
|
+
Use `error.is(code)` to read the generated error data types. Schema definitions
|
|
591
|
+
remain readable when their associated document is invalid.
|
|
592
|
+
|
|
593
|
+
## Operation documentation and error hints
|
|
594
|
+
|
|
595
|
+
All public methods include generated summaries, descriptions, API key permissions,
|
|
596
|
+
and input examples in editor tooltips. The same examples appear in OpenAPI parameters
|
|
597
|
+
and request bodies. IDs and file contents in examples are placeholders; replace them
|
|
598
|
+
with values from your instance. Role permissions differ from API key permissions.
|
|
599
|
+
|
|
600
|
+
Error declarations come from the backend contracts. `error.is(code)` narrows `data`
|
|
601
|
+
only when the server marks the error as defined by its contract. An undeclared error,
|
|
602
|
+
proxy response, or network failure does not become a typed domain error. Handle the
|
|
603
|
+
remaining error cases as well. `data` stays `unknown` until narrowed.
|
|
604
|
+
|
|
605
|
+
`APIErrorCode`, `APIErrorBody`, `APIErrorData<Code>`, and
|
|
606
|
+
`OperationError<"entries.create">` are available for application types. Operation
|
|
607
|
+
aliases such as `EntriesCreateError` are also available as
|
|
608
|
+
`Andesine.EntriesCreateError`. These types describe declared errors, not every
|
|
609
|
+
possible failure. TypeScript does not enforce the types of thrown errors.
|
|
610
|
+
|
|
611
|
+
`data.hints` is optional advice. `error.hints` returns an empty array when no hints
|
|
612
|
+
are present. Use stable codes and structured data for program logic; never parse
|
|
613
|
+
hint text. Hints do not trigger retries. Automatic retries remain disabled by default.
|
|
614
|
+
|
|
615
|
+
Common errors can include validation `issues`, `missingPermissions`, `requiredPlan`,
|
|
616
|
+
or `retryAfterSeconds`. Domain errors include `SCHEMA_MIGRATION_IN_PROGRESS`,
|
|
617
|
+
`PUBLISHING_SNAPSHOT_CHANGED`, `ROLE_NAME_DUPLICATE`, `ROLE_NAME_INVALID`,
|
|
618
|
+
`MEMBERSHIP_ALREADY_EXISTS`, and `INVITE_ALREADY_PENDING`. Migration and snapshot
|
|
619
|
+
conflicts now use their specific codes instead of the generic `CONFLICT` code.
|
|
620
|
+
|
|
621
|
+
Asset operation errors can also include the current `assetStatus`, expected and
|
|
622
|
+
actual file sizes and checksums, or storage `limitBytes`, `usedBytes`, and
|
|
623
|
+
`requiredBytes`. Storage reservations include processed image variants.
|
|
624
|
+
These fields are declared on the applicable operation error types, such as
|
|
625
|
+
`AssetsUploadError` and `AssetsRegisterError`; not every error with the same code
|
|
626
|
+
contains asset data. With `AndesineAPIError`, check for the field before using it.
|
|
627
|
+
Known rate-limit delays appear in both `data.retryAfterSeconds` and `Retry-After`.
|
|
628
|
+
Concurrency limits have no fixed delay and only supply a hint.
|
|
629
|
+
|
|
630
|
+
## Streaming AI answers
|
|
631
|
+
|
|
632
|
+
`search.askCurrentStream()` and `search.askPublishedStream()` return a promise of
|
|
633
|
+
an async iterator. They require the same permissions and inputs as their complete
|
|
634
|
+
answer methods. Published source types require `snapshotID`, `versionID`, and
|
|
635
|
+
`channel`. `AnswerEvent` and `PublishedAnswerEvent` are available directly and
|
|
636
|
+
through `Andesine`.
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
const events = await client.search.askPublishedStream(
|
|
641
|
+
{ channel: "published", collectionPath: "/Docs", question: "How do I install Andesine?" },
|
|
642
|
+
{ signal: controller.signal, timeout: 120_000 }
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
for await (const event of events) {
|
|
646
|
+
if (event.type === "sources") console.log(event.sources);
|
|
647
|
+
if (event.type === "textDelta") process.stdout.write(event.text);
|
|
648
|
+
if (event.type === "completed") console.log(event.answer);
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
The order is one `sources` event, non-empty `textDelta` events, then one
|
|
653
|
+
`completed` event. A no-source answer may complete without deltas. The completed
|
|
654
|
+
answer contains the final text and sources. Deltas contain plain text, including
|
|
655
|
+
any Markdown syntax; choose how to render it in your application.
|
|
656
|
+
|
|
657
|
+
Use `controller.abort()` to cancel, or `break` from the loop to stop early. If you
|
|
658
|
+
open a stream but do not iterate it, call `await events.return?.()` to close it.
|
|
659
|
+
The request timeout covers both opening and consuming the stream; `timeout: 0`
|
|
660
|
+
disables it. The SDK never reconnects or retries generation, even if read retries
|
|
661
|
+
are enabled. `response: "full"` returns the iterator in `data`, with the actual
|
|
662
|
+
response status and headers.
|
|
663
|
+
|
|
664
|
+
Errors before SSE headers are normal `AndesineAPIError` failures. A declared
|
|
665
|
+
error after headers throws `AndesineStreamError`, which extends `AndesineAPIError`
|
|
666
|
+
and supports `.is()`, `code`, `data`, and `hints`. Its `status` is the error status
|
|
667
|
+
from the frame; `response.status` remains the actual HTTP status, usually 200.
|
|
668
|
+
Malformed streams throw `AndesineStreamProtocolError` with `INVALID_STREAM`.
|
|
669
|
+
Missing completion or an interrupted body uses `INCOMPLETE_STREAM`. These are
|
|
670
|
+
local protocol errors, not API errors with an invented HTTP status. Abort and
|
|
671
|
+
timeout reasons are preserved.
|
|
672
|
+
|
|
673
|
+
A stream counts as one API call when generation starts, including generation that
|
|
674
|
+
later fails or is cancelled. Source preparation alone is not charged. The existing
|
|
675
|
+
Ask AI rate limit and quota checks run before opening the stream. Usage headers
|
|
676
|
+
show the allowance when the stream opens; chunks do not update headers or create
|
|
677
|
+
additional usage records.
|
|
678
|
+
|
|
679
|
+
### Forward a stream from your server
|
|
680
|
+
|
|
681
|
+
The optional `@andesine/sdk/streaming` entry has no framework dependency. It uses
|
|
682
|
+
standard Fetch types, preserves backpressure and cancellation, and forwards no
|
|
683
|
+
upstream headers or credentials.
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
import { toAnswerResponse } from "@andesine/sdk/streaming";
|
|
687
|
+
|
|
688
|
+
async function answerRequest(request: Request): Promise<Response> {
|
|
689
|
+
const { question } = await request.json();
|
|
690
|
+
const events = await client.search.askPublishedStream(
|
|
691
|
+
{ channel: "published", collectionPath: "/Docs", question },
|
|
692
|
+
{ signal: request.signal, timeout: 120_000 }
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
return toAnswerResponse(events);
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Keep the configured `client` and API key on your server. Handle errors before
|
|
700
|
+
`toAnswerResponse()` with your application's normal HTTP error handler. Once
|
|
701
|
+
streaming starts, the helper preserves typed API error frames and uses a generic
|
|
702
|
+
error frame for other failures. Its response disables caching and requests that
|
|
703
|
+
proxies do not buffer the stream. Your hosting platform must support streamed
|
|
704
|
+
responses.
|
|
705
|
+
|
|
706
|
+
A browser can read the forwarded response without an Andesine API key:
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
import { readAnswerStream } from "@andesine/sdk/streaming";
|
|
710
|
+
|
|
711
|
+
const controller = new AbortController();
|
|
712
|
+
const response = await fetch("/api/ask", {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({ question: "How do I install Andesine?" }),
|
|
716
|
+
signal: controller.signal
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
for await (const event of await readAnswerStream(response, { signal: controller.signal })) {
|
|
720
|
+
console.log(event);
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Both helpers use oRPC's SSE framing: `event: message` carries an answer event as
|
|
725
|
+
JSON; `event: error` carries the error envelope. Completion is an application
|
|
726
|
+
`completed` event. Comments are ignored, and a transport `done` event cannot
|
|
727
|
+
replace application completion. Event IDs and retry fields never trigger
|
|
728
|
+
reconnection.
|