@onlyworlds/sdk 4.55.0 → 4.56.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
@@ -1,6 +1,6 @@
1
1
  # For AI agents using @onlyworlds/sdk
2
2
 
3
- **Current as of**: SDK **4.x** · schema-dist **v0.33.0-dist.45** (canonical 00.33.00).
3
+ **Current as of**: SDK **4.x** · schema-dist **v0.33.0-dist.46** (canonical 00.33.00).
4
4
  This line is asserted by `codegen:check` in CI — if the pin moves and this file is not
5
5
  re-read against it, the check fails rather than letting this document rot quietly.
6
6
 
@@ -109,6 +109,11 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
109
109
  `mcp`, `declared`, `api`, `v1`, `sync`, `server`), and a `/changes` delete carries
110
110
  `deleted_by` and `deleted_via`; `isAgentDoor(door)` is true for the first three. An agent
111
111
  seat's key is recorded as an agent without the option. Never set it in a tool people use.
112
+ - **Name your tool** (since 4.56.0): `new OwV2Client({ ..., client: 'atlas' })` sends
113
+ `X-OW-Client: atlas` on every element write (never on a read); left out, it sends `sdk`. The name
114
+ is lower case letters, digits and hyphens, 1 to 32 characters, else a TypeError at construction.
115
+ keel stores it as `changed_client` on the element and `deleted_client` on a `/changes` delete:
116
+ the client's claim, never proof, and null when none was sent.
112
117
  - A guest or player reads what its own Character's sheet links to as a **stub**, marked
113
118
  `known_of: true` (`isKnownOf(el)`): only `id`, `type`, `name`, `description`, `supertype`,
114
119
  `subtype`, `image_url`, the timestamps and the fields its type lists in `PUBLIC_FIELDS` are
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.56.0] - 2026-10-22
9
+
10
+ ### Added
11
+
12
+ - **The client's name** (keel #520, live 2026-10-22; Orrery's `client-name.md`, decision 2): a constructor option `client`, default `'sdk'`, sent as `X-OW-Client` on every element write (`create`, `upsert`, `patch`, `delete`, `editLinks`, `bulk`, `adopt`) and on no read. keel stores it on the element as `changed_client`, so a reader can tell which tool made the last change (`new OwV2Client({ ..., client: 'atlas' })`). The rule is keel's, exact here: lower case letters, digits and hyphens, 1 to 32 characters (`[a-z0-9-]{1,32}`). Anything else, a capital, a space, an empty string, 33 characters or a value that is not a string, is a TypeError at construction naming the option and the rule, and nothing is trimmed; keel's own fence for a stranger's header is to store null, never a 422. The name is the client's claim about itself, never proof; it changes no door and no permission, and `agent` still decides `X-OW-Agent`.
13
+ - **`changed_client`** on `OwElementBase` (`string | null`, server-managed, read-only, beside `changed_by` and `changed_via`): the name the last write gave, null when it gave none or one outside the rule, for keel's own writes, and for changes from before names were recorded. Sent back in a write it is ignored. **`deleted_client`** on a `/changes` delete (`OwChangeDelete`, beside `deleted_by` and `deleted_via`), the same for the delete; null on a `hidden` row.
14
+
15
+ ### Changed
16
+
17
+ - **The constructor refuses an unknown key by name**, as every options object has since 4.52.0: `new OwV2Client({ clinet: 'atlas' })` is a `TypeError` naming the key and the eight the constructor knows, where it fell back to the defaults silently before. Every known caller (Atlas, Lantern, the toolkit) passes known keys only.
18
+ - **Schema repinned to schema-dist v0.33.0-dist.46** (same canonical 00.33.00). The dist's example export (`example/world-export.json`) gains `changed_client: null` on its bodies, `walk/lint_folder.py`'s server-key set names `changed_client`, and the README's sentence on server keys names it; `VERSION` and `MANIFEST.json` carry the new serial; no schema file moved. Generated output moves its provenance lines and gains `changed_client`.
19
+
8
20
  ## [4.55.0] - 2026-10-22
9
21
 
10
22
  ### Added
package/SCHEMA.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OnlyWorlds Schema Reference
2
2
 
3
- **Source**: https://github.com/OnlyWorlds/schema-dist @ **v0.33.0-dist.45** — canonical schema **00.33.00**, published 2026-10-22.
3
+ **Source**: https://github.com/OnlyWorlds/schema-dist @ **v0.33.0-dist.46** — canonical schema **00.33.00**, published 2026-10-22.
4
4
 
5
5
  GENERATED from the canonical schema YAML — do not hand-edit (regenerate: `python codegen/generate_types.py`).
6
6
  Written for both humans and AI agents reading this package locally.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Every element carries these. The extension index signature admits namespaced
2
2
  * pass-through fields (atlas_* / shadow_* / x_*) returned verbatim by the server.
3
3
  * Derived from base_properties.yaml: `World` is dropped (the API rejects it in
4
- * bodies -- the key determines the world) and the seven server-managed fields are
4
+ * bodies -- the key determines the world) and the eight server-managed fields are
5
5
  * added, since they ride every wire body and appear in no element YAML; `known_of`
6
6
  * rides only a stub. */
7
7
  interface OwElementBase {
@@ -31,6 +31,8 @@ interface OwElementBase {
31
31
  changed_by?: string | null;
32
32
  /** The door of the last write (server-managed, read-only): `seat` (an agent seat), `mcp`, `declared` (a client that sent X-OW-Agent), then `api`, `v1`, `sync`, `server` (keel's own writes) or `site` (the owner signed in on onlyworlds.com; keel PR 160). The first three are agent-made. Null for anything last changed before writes recorded it. */
33
33
  changed_via?: 'seat' | 'mcp' | 'declared' | 'api' | 'v1' | 'sync' | 'server' | 'site' | null;
34
+ /** The name the client gave in the X-OW-Client header on the last write (server-managed, read-only): lower case letters, digits and hyphens, 1 to 32 characters, such as `sdk` or `atlas`. It is the client's claim about itself, never proof, and says nothing about whether an agent drove the write (that is `changed_via`). Null when the last write sent no name or one outside the rule, for keel's own writes, and for anything last changed before names were recorded. Sent in a write it is ignored. This SDK sends its `client` option, `sdk` by default. */
35
+ changed_client?: string | null;
34
36
  /** Present, and true, only when the world's owner or a co-builder hid this element and this reader still sees it (server-managed, read-only): the owner, a co-builder, a key the owner minted, or the member who made it; to every other reader the element does not exist. Absent otherwise, whatever `fields` asked; on the read, the list, the change feed's upserts and MCP, never in the download, world sync or v1. Sent in a write it is ignored. */
35
37
  hidden?: true;
36
38
  /** Present, and true, only on a stub (server-managed, read-only): an element a guest or player reaches only through its own Character's sheet. A stub keeps id, type, name, description, supertype, subtype, image_url, created_at, updated_at and change_seq, and the fields its type lists in PUBLIC_FIELDS with their values; every other field reads unset ("", null, []), created_by/changed_by/changed_via null, no extension fields, and its files are 404. Its empties are not values and not a deletion: never write a stub back. Absent: the element is whole. See isKnownOf(). */
@@ -1981,6 +1983,12 @@ interface OwChangeDelete {
1981
1983
  deleted_by?: string | null;
1982
1984
  /** The door the delete came through (see {@link OwWriteDoor}); null for deletes before writes recorded it. */
1983
1985
  deleted_via?: OwWriteDoor | null;
1986
+ /**
1987
+ * The name the deleting client gave in its `X-OW-Client` header (keel #520), as an element's
1988
+ * `changed_client`: the client's claim, never proof. Null when none was recorded, and on a
1989
+ * `hidden` row (a hide carries no client).
1990
+ */
1991
+ deleted_client?: string | null;
1984
1992
  /**
1985
1993
  * Why the element left this reader's view (keel #421): `deleted` (it is gone; a GET answers 410),
1986
1994
  * `hidden` (the world's owner or a co-builder hid it from this reader; it still exists, a GET answers
@@ -2812,6 +2820,18 @@ interface OwClientConfig {
2812
2820
  * as an agent without it.
2813
2821
  */
2814
2822
  agent?: boolean;
2823
+ /**
2824
+ * This client's own name, sent as `X-OW-Client` on every element write (create, upsert, patch,
2825
+ * delete, editLinks, bulk, adopt) and on no read (keel #520). keel stores it on the element as
2826
+ * `changed_client`, and on a /changes delete as `deleted_client`, so a reader can tell which tool
2827
+ * made a change: `'atlas'`, `'lantern'`, your app's name. Default `'sdk'`.
2828
+ *
2829
+ * The rule: lower case letters, digits and hyphens, 1 to 32 characters (`[a-z0-9-]{1,32}`), no
2830
+ * version (`'atlas'`, not `'atlas-1.76'`). Anything else is a TypeError when the client is
2831
+ * constructed, and nothing is trimmed. It is the client's claim about itself, not proof, and it
2832
+ * changes no permission and no door: whether an agent made a write is `agent`'s business.
2833
+ */
2834
+ client?: string;
2815
2835
  }
2816
2836
 
2817
2837
  /**
@@ -3158,6 +3178,8 @@ declare class OwV2Client {
3158
3178
  private readonly apiPin;
3159
3179
  private readonly fetchImpl;
3160
3180
  private readonly agent;
3181
+ /** The name sent as X-OW-Client on element writes (keel #520), checked at construction. */
3182
+ private readonly clientName;
3161
3183
  constructor(config?: OwClientConfig);
3162
3184
  /** GET /health -- unauthenticated liveness pulse. */
3163
3185
  health(): Promise<unknown>;
@@ -3763,8 +3785,10 @@ declare class OwV2Client {
3763
3785
  allowEmpty?: boolean;
3764
3786
  replayAware?: boolean;
3765
3787
  /** A `change_seq` to send as `If-Match` (keel #210); a whole number, else TypeError before sending. */
3766
- /** An element write (create, put, patch, delete, links, bulk): where an agent client sends X-OW-Agent. */
3788
+ /** A write keel's document lists X-OW-Agent on (the element writes, hide and unhide, the file ticket, setMyCharacter, mintWorldKey): where an agent client sends it. */
3767
3789
  write?: boolean;
3790
+ /** One of the seven element writes keel reads X-OW-Client on (keel #520: create, put, patch, delete, links, bulk, adopt): where the client's name goes. */
3791
+ elementWrite?: boolean;
3768
3792
  ifMatch?: number;
3769
3793
  }): Promise<T>;
3770
3794
  }
package/dist/index.js CHANGED
@@ -504,6 +504,8 @@ var DEFAULT_PAGE_SIZE = 100;
504
504
  var DEFAULT_CHANGES_PAGE_SIZE = 100;
505
505
  var OwV2Client = class {
506
506
  constructor(config = {}) {
507
+ only("new OwV2Client", config, CLIENT_CONFIG_KEYS);
508
+ this.clientName = clientNameOf(config.client);
507
509
  this.apiKey = config.apiKey ?? "";
508
510
  this.apiPin = config.apiPin || void 0;
509
511
  this.keyKind = config.apiKey ? detectKeyKind(config.apiKey) : "none";
@@ -649,6 +651,7 @@ var OwV2Client = class {
649
651
  }
650
652
  return this.request("POST", `/${type}/`, {
651
653
  write: true,
654
+ elementWrite: true,
652
655
  body,
653
656
  idempotencyKey: opts.idempotencyKey
654
657
  });
@@ -660,7 +663,7 @@ var OwV2Client = class {
660
663
  */
661
664
  async upsert(type, id, element) {
662
665
  refuseKnownOf(element, "upsert");
663
- return this.request("PUT", `/${type}/${id}/`, { write: true, body: sanitizePayload(element) });
666
+ return this.request("PUT", `/${type}/${id}/`, { write: true, elementWrite: true, body: sanitizePayload(element) });
664
667
  }
665
668
  /**
666
669
  * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
@@ -675,7 +678,7 @@ var OwV2Client = class {
675
678
  async patch(type, id, partial, opts = {}) {
676
679
  only("patch", opts, ["ifMatch"]);
677
680
  refuseKnownOf(partial, "patch");
678
- return this.request("PATCH", `/${type}/${id}/`, { write: true, body: sanitizePayload(partial), ifMatch: opts.ifMatch });
681
+ return this.request("PATCH", `/${type}/${id}/`, { write: true, elementWrite: true, body: sanitizePayload(partial), ifMatch: opts.ifMatch });
679
682
  }
680
683
  /**
681
684
  * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
@@ -683,7 +686,7 @@ var OwV2Client = class {
683
686
  * transaction -- no client-side unlink pass needed, ever.
684
687
  */
685
688
  async delete(type, id) {
686
- await this.request("DELETE", `/${type}/${id}/`, { write: true, allowEmpty: true });
689
+ await this.request("DELETE", `/${type}/${id}/`, { write: true, elementWrite: true, allowEmpty: true });
687
690
  }
688
691
  /**
689
692
  * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
@@ -695,7 +698,7 @@ var OwV2Client = class {
695
698
  async editLinks(type, id, field, edit, opts = {}) {
696
699
  only("editLinks", opts, ["ifMatch"]);
697
700
  only("editLinks", edit, ["add", "remove"], "The edit is { add?: string[], remove?: string[] }.");
698
- return this.request("POST", `/${type}/${id}/links/${field}`, { write: true, body: edit, ifMatch: opts.ifMatch });
701
+ return this.request("POST", `/${type}/${id}/links/${field}`, { write: true, elementWrite: true, body: edit, ifMatch: opts.ifMatch });
699
702
  }
700
703
  /**
701
704
  * POST /{type}/{id}/adopt with {member} -- change who made an element (keel #427, D141).
@@ -711,7 +714,7 @@ var OwV2Client = class {
711
714
  */
712
715
  async adopt(type, id, member) {
713
716
  if (typeof member !== "string" || member === "") throw new TypeError('adopt: member must be a membership id or "owner"');
714
- return this.request("POST", `/${type}/${id}/adopt`, { write: true, body: { member } });
717
+ return this.request("POST", `/${type}/${id}/adopt`, { write: true, elementWrite: true, body: { member } });
715
718
  }
716
719
  // -- Bulk ----------------------------------------------------------------
717
720
  /**
@@ -745,6 +748,7 @@ var OwV2Client = class {
745
748
  };
746
749
  return this.request("POST", "/bulk/", {
747
750
  write: true,
751
+ elementWrite: true,
748
752
  body,
749
753
  idempotencyKey: opts.idempotencyKey,
750
754
  replayAware: true
@@ -1511,6 +1515,7 @@ var OwV2Client = class {
1511
1515
  if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
1512
1516
  if (opts.ifMatch !== void 0) headers["If-Match"] = `"${opts.ifMatch}"`;
1513
1517
  if (this.agent && opts.write) headers["X-OW-Agent"] = "1";
1518
+ if (opts.elementWrite) headers["X-OW-Client"] = this.clientName;
1514
1519
  let res;
1515
1520
  try {
1516
1521
  res = await this.fetchImpl(url, {
@@ -1670,6 +1675,16 @@ function only(method, obj, keys, hint = "") {
1670
1675
  }
1671
1676
  }
1672
1677
  }
1678
+ var CLIENT_CONFIG_KEYS = ["apiKey", "apiPin", "baseUrl", "pageSize", "changesPageSize", "fetch", "agent", "client"];
1679
+ var CLIENT_NAME_RULE = /^[a-z0-9-]{1,32}$/;
1680
+ var DEFAULT_CLIENT_NAME = "sdk";
1681
+ function clientNameOf(value) {
1682
+ if (value === void 0) return DEFAULT_CLIENT_NAME;
1683
+ if (typeof value !== "string" || !CLIENT_NAME_RULE.test(value)) {
1684
+ throw new TypeError(`OwV2Client: client must be lower case letters, digits and hyphens, 1 to 32 characters ([a-z0-9-]{1,32}), such as 'atlas'; got ${JSON.stringify(value) ?? String(value)}. It is sent as X-OW-Client on writes; leave it out for '${DEFAULT_CLIENT_NAME}'.`);
1685
+ }
1686
+ return value;
1687
+ }
1673
1688
  function buildQuery(params) {
1674
1689
  const q = new URLSearchParams();
1675
1690
  for (const [k, v] of Object.entries(params)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.55.0",
3
+ "version": "4.56.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {