@onlyworlds/sdk 4.3.0 → 4.5.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
@@ -44,6 +44,11 @@ against live data). The v1 surface (`OnlyWorldsClient`, the `ElementType` enum)
44
44
  - Under load keel answers 503 `server_busy` with `Retry-After` (`err.isBusy`, `err.retryAfter`
45
45
  in seconds). The client does not retry for you; back off and retry yourself. Works the same
46
46
  in browsers.
47
+ - `created_by` rides every element read: the id of the membership that created it, `null` for
48
+ the world's owner and for anything made before memberships existed. It is read-only; a write
49
+ body that carries it has it dropped, never an error. A **contributor** can change only what
50
+ they created: anything else is 403 `not_author` (`err.isNotAuthor`; retrying will not help).
51
+ `/bulk` reports it per slot, not as a throw.
47
52
  - A string holding an unpaired surrogate (text cut mid-emoji) is a 422 naming the field.
48
53
  Slice strings by code point, not by UTF-16 unit.
49
54
  - Colour carries the element's FAMILY (`elementColor(type, mode)`); the icon
package/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ earlier history lives in git log only.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.5.0] — 2026-10-04 (staged, not published)
9
+
10
+ What the staging write probes found on the wire (Python package, 2026-10-04): one new getter, and
11
+ three behaviours the server has always had that no client doc said. Additive; nothing is removed.
12
+
13
+ ### Added
14
+ - **`OwApiError.isOwnerOnly`**: 403 `owner_only`, a contributor attempting a world-level change
15
+ (`patchWorld`, publishing a show page). Owners and co-builders never see it. keel's OpenAPI
16
+ document does not name the code yet (Skeld has that to close); the wire returns it.
17
+
18
+ ### Documented
19
+ - **An atomic `/bulk` rollback still returns 201 slots** for the items that would have
20
+ succeeded, with the ids and timestamps they would have had (keel spec: "counterfactual 201s").
21
+ After `atomic: true` with `errors: true`, nothing was written. On `bulk()`, `OwBulkItemResult`
22
+ and the README.
23
+ - **`name` is required as a key only**: `''` and `null` are accepted and stored as `''` (keel
24
+ ruling 2026-09-28, spec §6; nameless Markers exist). On `create()` and the README.
25
+
26
+ ## [4.4.0] — 2026-10-04
27
+
28
+ Membership, as far as the server has shipped it: who created an element, and the refusal a
29
+ contributor meets on someone else's. Additive; nothing is removed or narrows.
30
+
31
+ ### Added
32
+ - **`created_by`** on `OwElementBase` (so on every element type): `string | null`, the id of the
33
+ membership that created the element, `null` for the world's owner and for anything made
34
+ before memberships existed (keel D72 phase 2, live 2026-09-28; keel spec §4). Read-only. The
35
+ schema does not carry it (like `created_at` it is server bookkeeping), so the generator adds
36
+ it to the wire base under the declared mapping. A write body that carries it has it dropped
37
+ by the server, so the client does not strip it and a read body still round-trips.
38
+ - **`OwApiError.isNotAuthor`**: 403 `not_author`, a contributor changing, replacing, relinking
39
+ or deleting an element someone else created. `/bulk` answers 200 and reports it in the item
40
+ slot, so check the slot (documented on `OwBulkItemResult`).
41
+
42
+ ### Fixed
43
+ - **`FIELD_SCHEMA` no longer marks `marker.map/zone/x/y/order` and `pin.map/element_type/element_id/x/y`
44
+ as `required: true`.** The wire requires `name` and nothing else (rulings.yaml `nullable-by-default`,
45
+ Captain 2026-07-28; keel's OpenAPI write schemas say `required: [name]`), but the schema files still
46
+ list those fields and the generator copied the lists, so a form built from the table demanded
47
+ coordinates the server never did. Found by comparing the SDK with keel's OpenAPI document (the
48
+ new `verify-sdk-wire` gate in Assembly). **If you read `required` from this table, marker and pin
49
+ fields now read as optional**, which is what the server accepts. The generator notes the lists it
50
+ ignores on every run and says when canonical stops carrying them.
51
+
52
+ ### Not yet
53
+ - `me()` and `members()` wait for the server routes (keel's M2b). Nothing in this release
54
+ guesses at their names.
55
+
8
56
  ## [4.3.0] — 2026-09-28
9
57
 
10
58
  Errors you can act on: the two 409s told apart, and the busy server's `Retry-After`, in Node and
package/README.md CHANGED
@@ -45,6 +45,8 @@ const location = await writer.create('location', {
45
45
  name: 'Dragon Peak',
46
46
  description: 'A treacherous mountain peak where dragons nest',
47
47
  }); // id minted client-side when omitted (retries stay idempotent)
48
+ // name is the one required field, and required means the key is present:
49
+ // '' and null are accepted and stored as '' (nameless Markers exist).
48
50
 
49
51
  const dragon = await writer.create('creature', {
50
52
  name: 'Vorrath the Ember-Scaled',
@@ -84,6 +86,8 @@ if (res.errors) {
84
86
  }
85
87
  }
86
88
  // Pass { atomic: true } for all-or-nothing instead. res.wasReplay flags idempotent replays.
89
+ // After a failed atomic batch NOTHING was written, but the slots that would have succeeded
90
+ // still say 201 (keel's counterfactual 201s): do not record those ids as created.
87
91
  ```
88
92
 
89
93
  ## Sync
package/SCHEMA.md CHANGED
@@ -6,7 +6,7 @@ GENERATED from the canonical schema YAML — do not hand-edit (regenerate: `pyth
6
6
  Written for both humans and AI agents reading this package locally.
7
7
 
8
8
  **The shape rules** (v2 wire dialect): every element carries `id` (UUID), `name`, optional
9
- `description`/`supertype`/`subtype`/`image_url`, server-managed `type`/`created_at`/`updated_at`/`change_seq`,
9
+ `description`/`supertype`/`subtype`/`image_url`, server-managed `type`/`created_at`/`updated_at`/`change_seq`/`created_by`,
10
10
  and namespaced extension fields (`x_*` etc.) returned verbatim. Link fields use ONE bare
11
11
  name in both read and write (no `_ids` suffix). Single links are `UUID | null`; multi links
12
12
  are `UUID[]`. **Links are owned one-way**: the type listed below owns the field (e.g.
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 four server-managed fields are
4
+ * bodies -- the key determines the world) and the five server-managed fields are
5
5
  * added, since they ride every wire body and appear in no element YAML. */
6
6
  interface OwElementBase {
7
7
  /** Element type slug (server-managed, read-only). */
@@ -24,6 +24,8 @@ interface OwElementBase {
24
24
  updated_at?: string;
25
25
  /** Per-world change cursor, stamped on every write (server-managed, read-only). */
26
26
  change_seq?: number;
27
+ /** The membership that created this element (server-managed, read-only); null for the world's owner and for anything created before memberships existed. */
28
+ created_by?: string | null;
27
29
  /** Namespaced extension fields (atlas_* / shadow_* / x_*), returned verbatim. */
28
30
  [ext: string]: unknown;
29
31
  }
@@ -1100,27 +1102,22 @@ declare const FIELD_SCHEMA: {
1100
1102
  readonly map: {
1101
1103
  readonly type: "single_link";
1102
1104
  readonly target: "map";
1103
- readonly required: true;
1104
1105
  };
1105
1106
  readonly zone: {
1106
1107
  readonly type: "single_link";
1107
1108
  readonly target: "zone";
1108
- readonly required: true;
1109
1109
  };
1110
1110
  readonly x: {
1111
1111
  readonly type: "integer";
1112
- readonly required: true;
1113
1112
  };
1114
1113
  readonly y: {
1115
1114
  readonly type: "integer";
1116
- readonly required: true;
1117
1115
  };
1118
1116
  readonly z: {
1119
1117
  readonly type: "integer";
1120
1118
  };
1121
1119
  readonly order: {
1122
1120
  readonly type: "integer";
1123
- readonly required: true;
1124
1121
  };
1125
1122
  };
1126
1123
  readonly narrative: {
@@ -1407,24 +1404,19 @@ declare const FIELD_SCHEMA: {
1407
1404
  readonly map: {
1408
1405
  readonly type: "single_link";
1409
1406
  readonly target: "map";
1410
- readonly required: true;
1411
1407
  };
1412
1408
  readonly element_type: {
1413
1409
  readonly type: "text";
1414
- readonly required: true;
1415
1410
  };
1416
1411
  readonly element_id: {
1417
1412
  readonly type: "single_link";
1418
1413
  readonly target: "any";
1419
- readonly required: true;
1420
1414
  };
1421
1415
  readonly x: {
1422
1416
  readonly type: "integer";
1423
- readonly required: true;
1424
1417
  };
1425
1418
  readonly y: {
1426
1419
  readonly type: "integer";
1427
- readonly required: true;
1428
1420
  };
1429
1421
  readonly z: {
1430
1422
  readonly type: "integer";
@@ -1918,6 +1910,11 @@ interface OwBulkItem {
1918
1910
  * One slot of a /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): `status` is
1919
1911
  * the NUMERIC HTTP status of that slot (201/400/...), success slots echo
1920
1912
  * created_at/updated_at, error slots carry an OwErrorBody under `error`.
1913
+ * A contributor's write on someone else's element fails in its slot as
1914
+ * `{status: 403, error: {code: 'not_author'}}` (keel D72), never as a thrown error.
1915
+ * After an `atomic: true` request with `errors` true NOTHING was written, yet the slots of
1916
+ * the items that would have succeeded still say 201 (keel's "counterfactual 201s"): do not
1917
+ * record their ids as created.
1921
1918
  */
1922
1919
  interface OwBulkItemResult {
1923
1920
  status: number;
@@ -2042,6 +2039,19 @@ declare class OwApiError extends Error {
2042
2039
  get isIdConflict(): boolean;
2043
2040
  /** keel's admission control turned the request away (503 `server_busy`); see retryAfter. */
2044
2041
  get isBusy(): boolean;
2042
+ /**
2043
+ * A contributor tried to change, replace, relink or delete an element someone else
2044
+ * created (403 `not_author`, keel D72 membership phase 2). Retrying will not help; the
2045
+ * element is not theirs. `/bulk` never throws for this: it answers 200 with a per-item
2046
+ * slot `{status: 403, error: {code: 'not_author'}}`. Owners and co-builders never see it.
2047
+ */
2048
+ get isNotAuthor(): boolean;
2049
+ /**
2050
+ * A contributor tried a world-level change: `patchWorld`, or publishing a show page
2051
+ * (403 `owner_only`, keel D72 membership phase 2). Retrying will not help; only the
2052
+ * owner may. Owners and co-builders never see it.
2053
+ */
2054
+ get isOwnerOnly(): boolean;
2045
2055
  }
2046
2056
  /** Network-level failure (fetch rejected) -- no envelope to parse. */
2047
2057
  declare class OwNetworkError extends Error {
@@ -2125,6 +2135,8 @@ declare class OwV2Client {
2125
2135
  * POST /{type}/ -- create. Mints an RFC 9562 UUIDv7 for element.id when the
2126
2136
  * caller omits one (design ruling D29d) so a retry carrying the same
2127
2137
  * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
2138
+ * `name` is the one required field, and required means the key is present: `''` and
2139
+ * `null` are accepted and stored as `''` (nameless Markers exist; keel ruling 2026-09-28).
2128
2140
  */
2129
2141
  create(type: ElementType | string, element: OwElement | Record<string, unknown>, opts?: {
2130
2142
  idempotencyKey?: string;
@@ -2158,6 +2170,9 @@ declare class OwV2Client {
2158
2170
  * Success slots echo server-authoritative timestamps: set your sync baseline
2159
2171
  * from this response alone. When an idempotencyKey is replayed, the returned
2160
2172
  * response carries wasReplay:true (read from the Idempotent-Replay header).
2173
+ * After an atomic:true request with errors:true NOTHING was written, yet the slots of
2174
+ * the items that would have succeeded still say 201 with the ids and timestamps they
2175
+ * would have had (keel's "counterfactual 201s"): do not record those ids as created.
2161
2176
  */
2162
2177
  bulk(items: OwBulkItem[], opts?: {
2163
2178
  atomic?: boolean;
package/dist/index.js CHANGED
@@ -39,6 +39,23 @@ var OwApiError = class extends Error {
39
39
  get isBusy() {
40
40
  return this.status === 503 && this.code === "server_busy";
41
41
  }
42
+ /**
43
+ * A contributor tried to change, replace, relink or delete an element someone else
44
+ * created (403 `not_author`, keel D72 membership phase 2). Retrying will not help; the
45
+ * element is not theirs. `/bulk` never throws for this: it answers 200 with a per-item
46
+ * slot `{status: 403, error: {code: 'not_author'}}`. Owners and co-builders never see it.
47
+ */
48
+ get isNotAuthor() {
49
+ return this.status === 403 && this.code === "not_author";
50
+ }
51
+ /**
52
+ * A contributor tried a world-level change: `patchWorld`, or publishing a show page
53
+ * (403 `owner_only`, keel D72 membership phase 2). Retrying will not help; only the
54
+ * owner may. Owners and co-builders never see it.
55
+ */
56
+ get isOwnerOnly() {
57
+ return this.status === 403 && this.code === "owner_only";
58
+ }
42
59
  };
43
60
  var OwNetworkError = class extends Error {
44
61
  constructor(message, cause) {
@@ -173,6 +190,8 @@ var OwV2Client = class {
173
190
  * POST /{type}/ -- create. Mints an RFC 9562 UUIDv7 for element.id when the
174
191
  * caller omits one (design ruling D29d) so a retry carrying the same
175
192
  * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
193
+ * `name` is the one required field, and required means the key is present: `''` and
194
+ * `null` are accepted and stored as `''` (nameless Markers exist; keel ruling 2026-09-28).
176
195
  */
177
196
  async create(type, element, opts = {}) {
178
197
  const body = sanitizePayload(element);
@@ -222,6 +241,9 @@ var OwV2Client = class {
222
241
  * Success slots echo server-authoritative timestamps: set your sync baseline
223
242
  * from this response alone. When an idempotencyKey is replayed, the returned
224
243
  * response carries wasReplay:true (read from the Idempotent-Replay header).
244
+ * After an atomic:true request with errors:true NOTHING was written, yet the slots of
245
+ * the items that would have succeeded still say 201 with the ids and timestamps they
246
+ * would have had (keel's "counterfactual 201s"): do not record those ids as created.
225
247
  */
226
248
  async bulk(items, opts = {}) {
227
249
  const body = {
@@ -846,12 +868,12 @@ var FIELD_SCHEMA = {
846
868
  subtype: { type: "text", required: false },
847
869
  image_url: { type: "text", required: false },
848
870
  // Details
849
- map: { type: "single_link", target: "map", required: true },
850
- zone: { type: "single_link", target: "zone", required: true },
851
- x: { type: "integer", required: true },
852
- y: { type: "integer", required: true },
871
+ map: { type: "single_link", target: "map" },
872
+ zone: { type: "single_link", target: "zone" },
873
+ x: { type: "integer" },
874
+ y: { type: "integer" },
853
875
  z: { type: "integer" },
854
- order: { type: "integer", required: true }
876
+ order: { type: "integer" }
855
877
  },
856
878
  narrative: {
857
879
  // Base fields (shared by all elements)
@@ -944,11 +966,11 @@ var FIELD_SCHEMA = {
944
966
  subtype: { type: "text", required: false },
945
967
  image_url: { type: "text", required: false },
946
968
  // Details
947
- map: { type: "single_link", target: "map", required: true },
948
- element_type: { type: "text", required: true },
949
- element_id: { type: "single_link", target: "any", required: true },
950
- x: { type: "integer", required: true },
951
- y: { type: "integer", required: true },
969
+ map: { type: "single_link", target: "map" },
970
+ element_type: { type: "text" },
971
+ element_id: { type: "single_link", target: "any" },
972
+ x: { type: "integer" },
973
+ y: { type: "integer" },
952
974
  z: { type: "integer" }
953
975
  },
954
976
  relation: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlyworlds/sdk",
3
- "version": "4.3.0",
3
+ "version": "4.5.0",
4
4
  "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
5
  "type": "module",
6
6
  "exports": {