@onlyworlds/sdk 4.6.0 → 4.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/AGENTS.md CHANGED
@@ -21,7 +21,7 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
21
21
  - Known, deterministic operations (CRUD, sync, bulk) → **this SDK**. Typed calls, typed
22
22
  responses, far cheaper than tool-schema reasoning.
23
23
  - Live exploration of a user's world from a chat/agent context → the **MCP server** at
24
- `https://www.onlyworlds.com/mcp` (same `API-Key`/`API-Pin` headers, 11 tools).
24
+ `https://www.onlyworlds.com/mcp` (same `API-Key`/`API-Pin` headers, 12 tools).
25
25
 
26
26
  **Wire facts that bite** (full details in README):
27
27
  - Never send a `"world"` field in payloads — world identity comes from the API key (422 otherwise).
package/CHANGELOG.md CHANGED
@@ -5,13 +5,31 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
- ## [4.6.0] — 2026-10-06 (staged, not published)
8
+ ## [4.7.0] — 2026-10-08
9
+
10
+ ### Added
11
+ - **`OwApiError.isResyncRequired`**: 409 `resync_required` from `/changes`. Guests can follow the
12
+ feed since keel D79; when a guest's view or a key's role changes, its stored cursor is refused. Drop
13
+ it, walk from zero, and replace the local copy rather than merging.
14
+
15
+ ### Documented
16
+ - `uploadImage` accepts webp, png, jpeg and avif. 4.6.0's JSDoc and README also listed gif, which
17
+ the edge refuses (415 `unsupported_type`); checked against the Worker's type table.
18
+ - `changes()`: the old rewind rule compared a stored cursor with `head`, which an opaque cursor
19
+ cannot do; replaced with the reset rule (`isResyncRequired`).
20
+ - The doc comments of `OwClientConfig`, `create` and `uploadImage` are written for users: internal
21
+ ruling numbers and test-harness notes are gone, and each opens with what the option or method does.
22
+ The docs site's TypeScript reference is generated from these comments.
23
+
24
+ ## [4.6.0] — 2026-10-06
25
+
26
+ Published from `e198298` (tag `v4.6.0`); the tarball was checked against a build of that commit.
9
27
 
10
28
  Includes 4.5.1's documentation fixes: 4.5.1 was staged and never published.
11
29
 
12
30
  ### Added
13
- - **`uploadImage(image, { key?, ticket? })`**: upload one image (webp, png, jpeg, avif, gif;
14
- never SVG) and get its permanent public URL. Two requests: a ticket from keel
31
+ - **`uploadImage(image, { key?, ticket? })`**: upload one image (webp, png, jpeg, avif;
32
+ never SVG; 4.6.0's docs also said gif, which the edge refuses with 415) and get its permanent public URL. Two requests: a ticket from keel
15
33
  (`POST /media/ticket`), then the bytes straight to the edge at the ticket's `upload_url`
16
34
  (keel never sees them). Only the ticket goes to the edge, never the API key or PIN. An image
17
35
  larger than the ticket's `max_bytes` throws 413 `too_large` before anything is sent; the edge's
package/README.md CHANGED
@@ -98,8 +98,8 @@ await writer.patch('character', id, { image_url: image.url });
98
98
  ```
99
99
 
100
100
  Two requests: a single-use ticket from keel, then the bytes straight to the edge (keel never
101
- sees them, and the edge never sees your key). webp, png, jpeg, avif or gif, read from the
102
- bytes; never SVG. Each ticket counts toward the world's daily limit and the account's image
101
+ sees them, and the edge never sees your key). webp, png, jpeg or avif (never SVG or gif), read from the
102
+ bytes. Each ticket counts toward the world's daily limit and the account's image
103
103
  storage. To upload yourself (for a progress bar), take `createMediaTicket()` and POST the bytes to
104
104
  its `upload_url` with `Authorization: Bearer <ticket>`.
105
105
 
@@ -151,7 +151,7 @@ const status = await tokens.getStatus();
151
151
 
152
152
  For **known, deterministic operations** (CRUD, sync, bulk) use this SDK — typed calls, no
153
153
  tool-schema overhead. For **live exploration of a user's world from a chat/agent context**, use
154
- the MCP server at `https://www.onlyworlds.com/mcp` (same `API-Key`/`API-Pin` headers, 11 tools;
154
+ the MCP server at `https://www.onlyworlds.com/mcp` (same `API-Key`/`API-Pin` headers, 12 tools;
155
155
  unaffected by SDK versioning).
156
156
 
157
157
  ## License
package/dist/index.d.ts CHANGED
@@ -2000,27 +2000,26 @@ interface ListParams {
2000
2000
  filter?: Record<string, string | number | boolean>;
2001
2001
  }
2002
2002
  interface OwClientConfig {
2003
- /** ow_w_ / ow_r_ / ow_a_ prefixed key, or grandfathered 10-digit legacy key. */
2003
+ /** The key: an ow_w_ (write) or ow_r_ (read) world key, an ow_a_ account token (sent as a Bearer token, for the account routes), or a 10-digit legacy key. */
2004
2004
  apiKey: string;
2005
2005
  /**
2006
- * Optional. Required for writes when the world has a PIN, and for legacy-key
2007
- * reads of private worlds. Prefixed keys read PIN-less. String, not number --
2008
- * '0123' !== 123.
2006
+ * The PIN, needed for writes when the world has one, and for legacy-key reads of private worlds.
2007
+ * Prefixed keys read without a PIN. A string, not a number: '0123' !== 123.
2009
2008
  */
2010
2009
  apiPin?: string;
2011
- /** Default: https://www.onlyworlds.com/api/v2 */
2010
+ /** The API's base URL, default https://www.onlyworlds.com/api/v2. */
2012
2011
  baseUrl?: string;
2013
2012
  /**
2014
- * Page size for element lists. Default 100 (server default; max 1000).
2015
- * Deliberately visible in config: page size is a citizenship property.
2013
+ * Page size for element lists, default 100 (the server's default; at most 1000).
2014
+ * It is in the config on purpose: page size decides the load a client puts on the server.
2016
2015
  */
2017
2016
  pageSize?: number;
2018
2017
  /**
2019
- * Page size for /changes pulls. Default 100. Live precedents: Obsidian 100,
2020
- * Atlas 250, MCP 25. /changes is the platform's heaviest route -- be polite.
2018
+ * Page size for /changes pulls, default 100. Tools in use pick 25 to 250; the change feed
2019
+ * is the platform's heaviest route, so keep it modest.
2021
2020
  */
2022
2021
  changesPageSize?: number;
2023
- /** Injectable for tests / fake-keel harnesses. Defaults to globalThis.fetch. */
2022
+ /** A fetch implementation to use instead of globalThis.fetch (for tests and other runtimes). */
2024
2023
  fetch?: typeof globalThis.fetch;
2025
2024
  }
2026
2025
 
@@ -2079,6 +2078,13 @@ declare class OwApiError extends Error {
2079
2078
  * per-item slot `{status: 409, error: {code: 'id_conflict'}}` — check the slot.
2080
2079
  */
2081
2080
  get isIdConflict(): boolean;
2081
+ /**
2082
+ * `/changes` refused the cursor (409 `resync_required`): a guest's view of the world
2083
+ * changed, or the key's role did (a guest's cursor has another shape). Drop the stored
2084
+ * cursor, walk again from the start, and REPLACE the local copy rather than merging into
2085
+ * it: a guest never receives delete ops, so only the replace removes what it no longer sees.
2086
+ */
2087
+ get isResyncRequired(): boolean;
2082
2088
  /** keel's admission control turned the request away (503 `server_busy`); see retryAfter. */
2083
2089
  get isBusy(): boolean;
2084
2090
  /**
@@ -2175,11 +2181,11 @@ declare class OwV2Client {
2175
2181
  /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
2176
2182
  get(type: ElementType | string, id: string, opts?: Pick<ListParams, 'expand' | 'fields'>): Promise<OwElement>;
2177
2183
  /**
2178
- * POST /{type}/ -- create. Mints an RFC 9562 UUIDv7 for element.id when the
2179
- * caller omits one (design ruling D29d) so a retry carrying the same
2180
- * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
2184
+ * POST /{type}/ -- create one element, minting a UUIDv7 id on the client when you leave id out.
2185
+ * The client-side id makes a retry carrying the same Idempotency-Key safe: it rewrites
2186
+ * the same element instead of creating a second one. You may still supply your own id.
2181
2187
  * `name` is the one required field, and required means the key is present: `''` and
2182
- * `null` are accepted and stored as `''` (nameless Markers exist; keel ruling 2026-09-28).
2188
+ * `null` are accepted and stored as `''` (nameless Markers exist).
2183
2189
  */
2184
2190
  create(type: ElementType | string, element: OwElement | Record<string, unknown>, opts?: {
2185
2191
  idempotencyKey?: string;
@@ -2223,10 +2229,12 @@ declare class OwV2Client {
2223
2229
  }): Promise<OwBulkResponse>;
2224
2230
  /**
2225
2231
  * GET /changes -- one page of the world's ordered change feed.
2226
- * Cursor is OPAQUE and never expires: persist verbatim, never parse.
2232
+ * Cursor is OPAQUE: persist verbatim, never parse or build one (a guest's has
2233
+ * another shape than everyone else's).
2227
2234
  * Zero/absent cursor = full export (byte-aligned with the Folder Format).
2228
- * Rewind rule: if your persisted position is ahead of page.head, the server
2229
- * was restored -- re-baseline from cursor zero; do not assume caught-up.
2235
+ * Reset rule: a 409 `resync_required` (err.isResyncRequired) means the key's
2236
+ * view changed (a guest's, or its role): drop the cursor, walk from zero, and
2237
+ * replace the local copy rather than merging into it.
2230
2238
  * Citizenship: heaviest route on the platform; default page size is polite.
2231
2239
  */
2232
2240
  changes(opts?: {
@@ -2254,12 +2262,12 @@ declare class OwV2Client {
2254
2262
  */
2255
2263
  createMediaTicket(): Promise<OwMediaTicket>;
2256
2264
  /**
2257
- * Upload one image and get its permanent public URL: a ticket from keel, then the
2258
- * bytes straight to the edge (keel never sees them). Pass a ticket from
2265
+ * Upload one image and get its permanent public URL: a ticket from the API, then the
2266
+ * bytes straight to the media edge (the API never sees them). Pass a ticket from
2259
2267
  * createMediaTicket() to use it instead of fetching one. Returns the edge's 201;
2260
2268
  * set `url` as an element's `image_url` yourself (`patch(type, id, { image_url })`).
2261
2269
  *
2262
- * webp, png, jpeg, avif or gif, read from the bytes (never SVG). `key` names the
2270
+ * webp, png, jpeg or avif, read from the bytes (never SVG or gif). `key` names the
2263
2271
  * object (it must start with the ticket's `prefix`; without it the edge picks one).
2264
2272
  * An image larger than the ticket's `max_bytes` throws 413 `too_large` before any
2265
2273
  * byte is sent. The edge's refusals arrive as OwApiError with its code: 401
package/dist/index.js CHANGED
@@ -35,6 +35,15 @@ var OwApiError = class extends Error {
35
35
  get isIdConflict() {
36
36
  return this.status === 409 && this.code === "id_conflict";
37
37
  }
38
+ /**
39
+ * `/changes` refused the cursor (409 `resync_required`): a guest's view of the world
40
+ * changed, or the key's role did (a guest's cursor has another shape). Drop the stored
41
+ * cursor, walk again from the start, and REPLACE the local copy rather than merging into
42
+ * it: a guest never receives delete ops, so only the replace removes what it no longer sees.
43
+ */
44
+ get isResyncRequired() {
45
+ return this.status === 409 && this.code === "resync_required";
46
+ }
38
47
  /** keel's admission control turned the request away (503 `server_busy`); see retryAfter. */
39
48
  get isBusy() {
40
49
  return this.status === 503 && this.code === "server_busy";
@@ -188,11 +197,11 @@ var OwV2Client = class {
188
197
  return this.request("GET", `/${type}/${id}/`, { query });
189
198
  }
190
199
  /**
191
- * POST /{type}/ -- create. Mints an RFC 9562 UUIDv7 for element.id when the
192
- * caller omits one (design ruling D29d) so a retry carrying the same
193
- * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
200
+ * POST /{type}/ -- create one element, minting a UUIDv7 id on the client when you leave id out.
201
+ * The client-side id makes a retry carrying the same Idempotency-Key safe: it rewrites
202
+ * the same element instead of creating a second one. You may still supply your own id.
194
203
  * `name` is the one required field, and required means the key is present: `''` and
195
- * `null` are accepted and stored as `''` (nameless Markers exist; keel ruling 2026-09-28).
204
+ * `null` are accepted and stored as `''` (nameless Markers exist).
196
205
  */
197
206
  async create(type, element, opts = {}) {
198
207
  const body = sanitizePayload(element);
@@ -260,10 +269,12 @@ var OwV2Client = class {
260
269
  // -- Changes feed --------------------------------------------------------
261
270
  /**
262
271
  * GET /changes -- one page of the world's ordered change feed.
263
- * Cursor is OPAQUE and never expires: persist verbatim, never parse.
272
+ * Cursor is OPAQUE: persist verbatim, never parse or build one (a guest's has
273
+ * another shape than everyone else's).
264
274
  * Zero/absent cursor = full export (byte-aligned with the Folder Format).
265
- * Rewind rule: if your persisted position is ahead of page.head, the server
266
- * was restored -- re-baseline from cursor zero; do not assume caught-up.
275
+ * Reset rule: a 409 `resync_required` (err.isResyncRequired) means the key's
276
+ * view changed (a guest's, or its role): drop the cursor, walk from zero, and
277
+ * replace the local copy rather than merging into it.
267
278
  * Citizenship: heaviest route on the platform; default page size is polite.
268
279
  */
269
280
  async changes(opts = {}) {
@@ -300,12 +311,12 @@ var OwV2Client = class {
300
311
  return this.request("POST", "/media/ticket");
301
312
  }
302
313
  /**
303
- * Upload one image and get its permanent public URL: a ticket from keel, then the
304
- * bytes straight to the edge (keel never sees them). Pass a ticket from
314
+ * Upload one image and get its permanent public URL: a ticket from the API, then the
315
+ * bytes straight to the media edge (the API never sees them). Pass a ticket from
305
316
  * createMediaTicket() to use it instead of fetching one. Returns the edge's 201;
306
317
  * set `url` as an element's `image_url` yourself (`patch(type, id, { image_url })`).
307
318
  *
308
- * webp, png, jpeg, avif or gif, read from the bytes (never SVG). `key` names the
319
+ * webp, png, jpeg or avif, read from the bytes (never SVG or gif). `key` names the
309
320
  * object (it must start with the ticket's `prefix`; without it the edge picks one).
310
321
  * An image larger than the ticket's `max_bytes` throws 413 `too_large` before any
311
322
  * byte is sent. The edge's refusals arrive as OwApiError with its code: 401
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {