@onlyworlds/sdk 4.0.0 → 4.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 CHANGED
@@ -3,6 +3,97 @@
3
3
  All notable changes to `@onlyworlds/sdk`. Maintained from 3.1.0 onward (Kael, Assembly);
4
4
  earlier history lives in git log only.
5
5
 
6
+ ## [4.1.0] — 2026-07-29
7
+
8
+ Public-surface hygiene. Nothing breaks; one member is now marked for removal, and one
9
+ piece of long-standing speculation is retired by measurement.
10
+
11
+ ⚑ **4.0.2 was tagged in git and superseded before it reached npm.** Everything in it ships
12
+ here — the tag stays as a record rather than being moved or deleted.
13
+
14
+ ### Deprecated
15
+ - **`FieldType.integer_max` and `FieldInfo.max`** — removal scheduled for **5.0.0**. No
16
+ `FIELD_SCHEMA` entry has ever carried either, in this repository's entire history. They
17
+ existed to surface the schema's `maximum:` constraint, and that constraint is **advisory**:
18
+ keel declares no `MaxValueValidator`, and a `charisma: 9999` write against a `maximum: 100`
19
+ field returns 201 and stores it verbatim. The canonical schema walk therefore stays silent
20
+ on bounds permanently. There is no source to wire them to and no promise they could keep —
21
+ and a public type member meaning "hint the wire ignores" is one consumers read as
22
+ validation. Deprecating now rather than at the major so the signal arrives early; the
23
+ `@deprecated` tags surface in editors via the shipped `.d.ts`.
24
+
25
+ ### Changed
26
+ - **`TokenResource` is confirmed staying.** RFC-001 §5 asked the platform owner whether the
27
+ token routes were carried long-term or deprecated wire-side, and the question was never
28
+ answered in writing — so this package's own barrel carried "wire fate under review; may be
29
+ removed in a later 4.x" for months, on nobody's authority. Probed against production:
30
+ `GET /api/v2/tokens/status/` and `/tokens/encryption-info/` both return **200**, with a 404
31
+ control on a nonexistent route proving the check meant something. The wire carries it. The
32
+ speculation is retired and the comment now records the evidence instead.
33
+
34
+ ## [4.0.2] — 2026-07-29
35
+
36
+ Re-pinned to `v0.30.1-dist.13` (canonical **00.30.01**). No field shape changed and no
37
+ `FIELD_SCHEMA` entry changed — the schema walk is byte-identical between the two pins, so
38
+ nothing about decoding moved. Three things changed and nothing else.
39
+
40
+ ### Fixed
41
+ - **`construct.relations` and `event.languages` shipped with no description at all** — in
42
+ `types.generated.ts` and in `SCHEMA.md`. Their descriptions were nested one level too deep
43
+ inside `items:` in the canonical YAML, which made them invisible to every consumer that reads
44
+ field descriptions, this package included. `SCHEMA.md` is the package's AI-legibility artifact,
45
+ so the gap landed where it did the most harm.
46
+ - **Five description typos** corrected in published JSDoc and `SCHEMA.md`: `beapplied`,
47
+ `phyiscal`, `relating the`, `object grant`, `eventuated`. The rendered docs site had already
48
+ fixed all five by hand — the downstream copy was the correct one, and nobody noticed because
49
+ the fix went where it was visible rather than where it was true.
50
+
51
+ ### Changed
52
+ - `ONLYWORLDS_VERSION` `'00.30.00'` → `'00.30.01'`. It is a public `as const`, so its **literal
53
+ type** changes. Depending on that literal is pathological, but it is a type-level change and
54
+ should not be discovered rather than announced.
55
+ - The pin now carries canonical's numeric-bounds correction: the `maximum: 0` sentinel is gone
56
+ from 26 fields, 8 fields gained `minimum: 0`, and `rulings.yaml` carries the numeric-bounds
57
+ row. Nothing in this package consumes bounds — `maximum:` is **advisory** (keel does not
58
+ enforce it; a `charisma: 9999` write returns 201 and stores verbatim) and the walk stays
59
+ silent on bounds permanently. `integer_max` / `max` therefore remain declared no-ops here.
60
+
61
+ ## [4.0.1] — 2026-07-29
62
+
63
+ **Metadata correction release.** No wire-path change: the client's reads and writes never
64
+ consulted `FIELD_SCHEMA`, and the generated interfaces carried the correct targets throughout.
65
+ The exposure is anything that builds UI or validation by **iterating `FIELD_SCHEMA`**.
66
+
67
+ ⚑ **One way this can surface as a compile error**: `relation.relations` is removed, so
68
+ `FIELD_SCHEMA.relation.relations` is now a TypeScript error rather than a value. That is the
69
+ intended outcome — the field does not exist in the standard and the API rejects it — but it can
70
+ break a build rather than only a behaviour.
71
+
72
+ ### Fixed
73
+ - **`FIELD_SCHEMA.collective.equipment` targeted `construct`; the standard says `object`.**
74
+ This is the founding case of the schema ruling table
75
+ (`collective-equipment-target`, ruled 2026-07-23): the v1 implementation used
76
+ Construct, v1 is decommissioned, and keel serves per YAML. The generated code path
77
+ in this repo was corrected the same week. The hand-maintained `FIELD_SCHEMA` copy of
78
+ the same fact, in the same package, kept shipping the decommissioned value on
79
+ `latest` — the fix went where someone happened to be looking.
80
+ - **`FIELD_SCHEMA.relation.relations` removed** — a `multi_link` to `relation` that does
81
+ not exist in `relation.yaml`. A phantom field, publicly exported, that consumers
82
+ building forms from this table would have sent to an API that 422s unknown keys.
83
+
84
+ ### Changed
85
+ - **`FIELD_SCHEMA` is now GENERATED** from the pinned schema distribution and gated by
86
+ `codegen:check`, joining `ELEMENT_ICONS` / `ELEMENT_SECTIONS` / `ELEMENT_FAMILIES`. It
87
+ was ~650 hand-maintained lines whose test compared nothing to the schema. Regenerating
88
+ it changed exactly 2 of 467 entries — the two above. Runtime shape and the deeply
89
+ readonly public types (`as const`) are unchanged.
90
+ Two declared deviations from a naive schema read are now stated in the generated file:
91
+ `pin.element` (a `generic-link`) splits into `element_type` + `element_id`, as the wire
92
+ serves it; and `integer_max` / `max` remain in the `FieldType` union unused, because
93
+ the walk does not surface the schema's `maximum:` constraint (41 across 17 types).
94
+ - `codegen/generate_types.py` imports the vendored schema walk instead of carrying its
95
+ own copy of it. Output byte-identical.
96
+
6
97
  ## [4.0.0] — 2026-07-23
7
98
 
8
99
  **v2-native only.** See `docs/migrating-3-to-4.md`. 3.x stays published forever for
package/README.md CHANGED
@@ -5,7 +5,10 @@
5
5
 
6
6
  The canonical typed client for the [OnlyWorlds](https://onlyworlds.github.io) v2 API, plus the
7
7
  canonical constants (element types, icons, colour families, field schema) — generated from the
8
- same schema source the server runs on.
8
+ canonical OnlyWorlds schema, obtained through the public
9
+ [schema distribution](https://github.com/OnlyWorlds/schema-dist) at a pinned, hash-verified
10
+ tag. The generated files carry that tag and commit in their header, so what these types were
11
+ built from is checkable rather than asserted.
9
12
 
10
13
  **4.x is v2-native and ESM-only (Node 18+).** If you need the legacy v1 API dialect
11
14
  (`OnlyWorldsClient`) or CommonJS `require()`, stay on 3.x — it remains published and the v1 API
package/SCHEMA.md CHANGED
@@ -149,7 +149,7 @@ Families (colour semantics; icon carries the type): agents · world · abstract
149
149
  - `phenomena` (multi link → phenomenon) — Phenomena relevant to the construct
150
150
  - `languages` (multi link → language) — Languages relevant to the construct
151
151
  - `families` (multi link → family) — Families relevant to the construct
152
- - `relations` (multi link → relation)
152
+ - `relations` (multi link → relation) — Relations relevant to the construct
153
153
  - `titles` (multi link → title) — Titles relevant to the construct
154
154
  - `constructs` (multi link → construct) — Other constructs relevant to the construct
155
155
  - `events` (multi link → event) — Events relevant to the construct
@@ -200,7 +200,7 @@ Families (colour semantics; icon carries the type): agents · world · abstract
200
200
  - `consequences` (text) — Outcomes and impacts resulting from the event
201
201
  - `start_date` (integer) — Date on which the event began
202
202
  - `end_date` (integer) — Date on which the event concluded
203
- - `triggers` (multi link → event) — Events that eventuated the event
203
+ - `triggers` (multi link → event) — Events that precipitated this event
204
204
 
205
205
  ### Involves
206
206
 
@@ -215,7 +215,7 @@ Families (colour semantics; icon carries the type): agents · world · abstract
215
215
  - `zones` (multi link → zone) — Zones relevant to the event
216
216
  - `abilities` (multi link → ability) — Abilities relevant to the event
217
217
  - `phenomena` (multi link → phenomenon) — Natural or supernatural phenomena relevant to the event
218
- - `languages` (multi link → language)
218
+ - `languages` (multi link → language) — Languages relevant to the event
219
219
  - `families` (multi link → family) — Families relevant to the event
220
220
  - `relations` (multi link → relation) — Interpersonal or political relations relevant to the event
221
221
  - `titles` (multi link → title) — Titles relevant to the event
@@ -294,7 +294,7 @@ Families (colour semantics; icon carries the type): agents · world · abstract
294
294
  - `purpose` (text) — The intent, motivation, or justification for the law's creation
295
295
  - `date` (integer) — Date the law was formally established, in world TIME units
296
296
  - `parent_law` (single link → law) — A law that this law derives from, modifies, or enhances
297
- - `penalties` (multi link → construct) — Consequences intended to beapplied when the law is contravened
297
+ - `penalties` (multi link → construct) — Consequences intended to be applied when the law is contravened
298
298
 
299
299
  ### World
300
300
 
@@ -436,14 +436,14 @@ Families (colour semantics; icon carries the type): agents · world · abstract
436
436
  - `weight` (integer) — Approximate or exact mass of the object, defined by world MASS units
437
437
  - `amount` (integer) — The number of identical units in this object entry
438
438
  - `parent_object` (single link → object) — Larger object that this one is part of or contained within
439
- - `materials` (multi link → construct) — The phyiscal matter that constitutes the object
440
- - `technology` (multi link → construct) — Mechanisms relating the object's design or operation
439
+ - `materials` (multi link → construct) — The physical matter that constitutes the object
440
+ - `technology` (multi link → construct) — Mechanisms relating to the object's design or operation
441
441
 
442
442
  ### Function
443
443
 
444
444
  - `utility` (text) — Intended purpose or primary use of the object
445
445
  - `effects` (multi link → phenomenon) — Phenomena potentially triggered or emitted on object use
446
- - `abilities` (multi link → ability) — Abilities that the object grant or enables
446
+ - `abilities` (multi link → ability) — Abilities that the object grants or enables
447
447
  - `consumes` (multi link → construct) — What might be used or depleted on object use
448
448
 
449
449
  ### World
package/dist/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  /** Every element carries these. The extension index signature admits namespaced
2
- * pass-through fields (atlas_* / shadow_* / x_*) returned verbatim by the server. */
2
+ * pass-through fields (atlas_* / shadow_* / x_*) returned verbatim by the server.
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
5
+ * added, since they ride every wire body and appear in no element YAML. */
3
6
  interface OwElementBase {
4
7
  /** Element type slug (server-managed, read-only). */
5
8
  type: string;
6
- /** Unique identifier, uuidv7 format. */
9
+ /** Unique identifier for the element, uuidv7 format. */
7
10
  id: string;
8
11
  /** Name of the element. */
9
12
  name: string;
@@ -26,15 +29,17 @@ interface OwElementBase {
26
29
  }
27
30
  type ElementType = 'ability' | 'character' | 'collective' | 'construct' | 'creature' | 'event' | 'family' | 'institution' | 'language' | 'law' | 'location' | 'map' | 'marker' | 'narrative' | 'object' | 'phenomenon' | 'pin' | 'relation' | 'species' | 'title' | 'trait' | 'zone';
28
31
  declare const ELEMENT_TYPES: ElementType[];
29
- /** Canonical OnlyWorlds schema version. Source: canonical VERSION file, carried into keel schema/ by the refresh script (keel 492168c). */
30
- declare const ONLYWORLDS_VERSION: "00.30.00";
32
+ /** Canonical OnlyWorlds schema version. Source: the `canonical:` value of the pinned
33
+ * distribution's VERSION file (see the provenance block at the top of this file). */
34
+ declare const ONLYWORLDS_VERSION: "00.30.01";
31
35
  /** The four semantic families (colour carries the family; ELEMENT_ICONS carries the type). */
32
36
  type ElementFamily = 'agents' | 'world' | 'abstract' | 'temporal';
33
- /** Per-type semantic family. Source: keel's PRESENTATION-WRAPPER schema key `family:`
34
- * (first-party rendering metadata, keel-only — NOT part of the council-governed
35
- * OnlyWorlds standard; see keel/schema-pipeline.md "The wrapper layer"). */
37
+ /** Per-type semantic family. Source: the distribution's `presentation.json` sidecar
38
+ * (first-party rendering DEFAULTS — NOT part of the council-governed OnlyWorlds
39
+ * standard, and explicitly overridable by any consumer). The colour values are
40
+ * NOT in the sidecar: FAMILY_COLORS is hand-authored here in src/v2/palette.ts. */
36
41
  declare const ELEMENT_FAMILIES: Record<ElementType, ElementFamily>;
37
- /** Material Symbols icon name per type. Source: keel's PRESENTATION-WRAPPER key `icon:` (keel 56c124a). */
42
+ /** Material Symbols icon name per type. Source: the distribution's `presentation.json` sidecar. */
38
43
  declare const ELEMENT_ICONS: Record<ElementType, string>;
39
44
  /** Field grouping for display. DERIVED from the canonical schema's own document
40
45
  * structure (top-level property groups, document order = display order). */
@@ -44,402 +49,28 @@ interface SectionInfo {
44
49
  fields: string[];
45
50
  }
46
51
  declare const ELEMENT_SECTIONS: Record<ElementType, SectionInfo[]>;
47
-
48
- /**
49
- * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
50
- * wire-corrected against live staging fixtures 2026-07-18.
51
- *
52
- * OnlyWorlds v2 (keel) wire types -- the NON-generated, hand-owned wire shapes
53
- * (envelopes, pages, bulk, changes, config). Per-type element field typing is
54
- * generated (types.generated.ts). One-shape principle: a field reads the way it
55
- * writes -- links are UUID arrays (or UUID/null), same name both directions.
56
- * No `_ids` suffix in v2.
57
- */
58
-
59
- /** An element as read from / written to the v2 API. Loose base + extension keys. */
60
- type OwElement = OwElementBase;
61
- /** Spatial types live under spatial/ in the OW Folder Format. */
62
- declare const SPATIAL_TYPES: readonly ElementType[];
63
- interface OwWorldMeta {
64
- id: string;
65
- name: string;
66
- updated_at?: string;
67
- public_read?: boolean;
68
- [field: string]: unknown;
69
- }
70
- /** List envelope: cursor-paginated. */
71
- interface OwPage<T = OwElement> {
72
- data: T[];
73
- has_more: boolean;
74
- next_cursor: string | null;
75
- }
76
- /** /changes feed -- discriminated union on `op`. Apply in order -> convergence. */
77
- type OwChange = {
78
- op: 'upsert';
79
- id: string;
80
- type: string;
81
- element: OwElement;
82
- updated_at: string;
83
- [k: string]: unknown;
84
- } | {
85
- op: 'delete';
86
- id: string;
87
- type: string;
88
- deleted_at: string;
89
- [k: string]: unknown;
90
- };
91
- /**
92
- * /changes response. Wire shape verified in keel source (core/changes.py) and
93
- * pinned here: {cursor, changes, has_more, head}.
94
- */
95
- interface OwChangesPage {
96
- /** Opaque compound cursor -- persist verbatim, never parse, never expires. */
97
- cursor: string;
98
- changes: OwChange[];
99
- has_more: boolean;
100
- /**
101
- * World's current change_seq. If a persisted cursor is ever AHEAD of head,
102
- * the server rewound (disaster restore) -- re-baseline from cursor zero
103
- * instead of assuming caught-up.
104
- */
105
- head: number;
106
- }
107
- interface OwBulkItem {
108
- type: ElementType | string;
109
- element: OwElement | Record<string, unknown>;
110
- }
111
- /**
112
- * One slot of a /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): `status` is
113
- * the NUMERIC HTTP status of that slot (201/400/...), success slots echo
114
- * created_at/updated_at, error slots carry an OwErrorBody under `error`.
115
- */
116
- interface OwBulkItemResult {
117
- status: number;
118
- id?: string;
119
- created_at?: string;
120
- updated_at?: string;
121
- error?: OwErrorBody;
122
- }
123
- /** The wire error envelope carried in error slots and thrown errors. */
124
- interface OwErrorBody {
125
- type?: string;
126
- code?: string;
127
- message?: string;
128
- param?: string | null;
129
- doc_url?: string;
130
- }
131
- /**
132
- * /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): the array key is `items`,
133
- * not `results`. `wasReplay` is populated by the client from the (lowercase on
134
- * the wire) Idempotent-Replay response header (fixture P2b) -- not a wire field.
135
- */
136
- interface OwBulkResponse {
137
- errors: boolean;
138
- items: OwBulkItemResult[];
139
- /** Client-derived: true when the server replayed a prior Idempotency-Key. */
140
- wasReplay?: boolean;
141
- [k: string]: unknown;
142
- }
143
- interface OwLinkEdit {
144
- add?: string[];
145
- remove?: string[];
146
- }
147
- interface ListParams {
148
- limit?: number;
149
- cursor?: string;
150
- /** One-level stub expansion, e.g. ['friends', 'location']. */
151
- expand?: string[];
152
- /** Sparse include-set of field names. */
153
- fields?: string[];
154
- /**
155
- * Blessed Django-style filters: __icontains, __in, __gte, __lte, __isnull,
156
- * supertype/subtype equality. Unknown params 422 loudly server-side -- the
157
- * client passes them through and lets the platform name the typo.
158
- */
159
- filter?: Record<string, string | number | boolean>;
160
- }
161
- interface OwClientConfig {
162
- /** ow_w_ / ow_r_ / ow_a_ prefixed key, or grandfathered 10-digit legacy key. */
163
- apiKey: string;
164
- /**
165
- * Optional. Required for writes when the world has a PIN, and for legacy-key
166
- * reads of private worlds. Prefixed keys read PIN-less. String, not number --
167
- * '0123' !== 123.
168
- */
169
- apiPin?: string;
170
- /** Default: https://www.onlyworlds.com/api/v2 */
171
- baseUrl?: string;
172
- /**
173
- * Page size for element lists. Default 100 (server default; max 1000).
174
- * Deliberately visible in config: page size is a citizenship property.
175
- */
176
- pageSize?: number;
177
- /**
178
- * Page size for /changes pulls. Default 100. Live precedents: Obsidian 100,
179
- * Atlas 250, MCP 25. /changes is the platform's heaviest route -- be polite.
180
- */
181
- changesPageSize?: number;
182
- /** Injectable for tests / fake-keel harnesses. Defaults to globalThis.fetch. */
183
- fetch?: typeof globalThis.fetch;
184
- }
185
-
52
+ /** Field type definitions for OnlyWorlds elements. */
53
+ type FieldType = 'text' | 'integer'
186
54
  /**
187
- * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
188
- * wire-corrected against live staging fixtures 2026-07-18.
55
+ * @deprecated Emitted by nothing, and scheduled for removal in 5.0.0.
189
56
  *
190
- * keel error envelope handling. The error contract is part of the contract:
191
- * envelopes carry a machine `code` and a `doc_url` fragment anchored at
192
- * onlyworlds.github.io/api/errors -- surface both, always. The live wire
193
- * envelope also carries `type` and `param` (fixtures P4a/P2c); both are
194
- * surfaced on the thrown error.
57
+ * No `FIELD_SCHEMA` entry has ever carried this type, in the entire history of
58
+ * this repository. It was meant to surface the schema's `maximum:` constraint,
59
+ * and that constraint is **advisory**: keel declares no `MaxValueValidator` and
60
+ * the wire stores `charisma: 9999` against a `maximum: 100` field (201, verbatim).
61
+ * The canonical schema walk therefore stays silent on bounds permanently, so
62
+ * there is no source to wire this to and no promise it could keep. A public type
63
+ * member meaning "hint the wire ignores" is one consumers read as validation.
195
64
  */
196
- /** Auth codes are distinguishable by design; client recovery UX differs per code. */
197
- type OwAuthErrorCode = 'invalid_credentials' | 'key_revoked' | 'world_gone';
198
- declare class OwApiError extends Error {
199
- readonly status: number;
200
- /** Machine error code from the keel envelope, e.g. 'invalid_credentials'. */
201
- readonly code: string | null;
202
- /** Error family from the envelope, e.g. 'invalid_request', 'not_found'. */
203
- readonly type: string | null;
204
- /** Offending field/param named by the envelope (422/400), else null. */
205
- readonly param: string | null;
206
- /** Documentation link from the envelope -- show it to users/logs verbatim. */
207
- readonly docUrl: string | null;
208
- /** Raw parsed envelope (or body text when the body wasn't JSON). */
209
- readonly detail: unknown;
210
- constructor(status: number, code: string | null, message: string, docUrl: string | null, detail: unknown, type?: string | null, param?: string | null);
211
- get isAuthError(): boolean;
212
- /** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
213
- get isValidationError(): boolean;
214
- /** Same Idempotency-Key replayed with a different payload. */
215
- get isIdempotencyConflict(): boolean;
216
- }
217
- /** Network-level failure (fetch rejected) -- no envelope to parse. */
218
- declare class OwNetworkError extends Error {
219
- readonly cause2: unknown;
220
- constructor(message: string, cause: unknown);
65
+ | 'integer_max' | 'single_link' | 'multi_link';
66
+ /** Field metadata structure. */
67
+ interface FieldInfo {
68
+ type: FieldType;
69
+ target?: string;
70
+ /** @deprecated Never populated; removed in 5.0.0. See `FieldType.integer_max`. */
71
+ max?: number;
72
+ required?: boolean;
221
73
  }
222
- /** Parse a wire envelope into OwApiError parts (exported for the error type-tests). */
223
- /** Parse the platform ERROR envelope into an OwApiError. (Renamed from parseEnvelope in 4.0 —
224
- * distinct from the world-export envelope, which is a different artifact entirely.) */
225
- declare function parseErrorEnvelope(status: number, body: unknown): OwApiError;
226
- /** Build an OwApiError from a non-2xx response, tolerating non-JSON bodies. */
227
- declare function errorFromResponse(res: Response): Promise<OwApiError>;
228
-
229
- /**
230
- * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
231
- * wire-corrected against live staging fixtures 2026-07-18.
232
- *
233
- * OnlyWorlds key-kind detection. Prefixes make leaked keys grep-scannable
234
- * (Stripe/GitHub precedent) -- and tell a client what auth shape to expect.
235
- */
236
- type OwKeyKind =
237
- /** ow_w_ -- world key, read + write. Writes need the world's PIN if it has one. */
238
- 'write'
239
- /** ow_r_ -- world key, read-only, works bare (no PIN). The share-with-players primitive. */
240
- | 'read'
241
- /** ow_a_ -- account Bearer token for /account/* routes; can mint world keys. */
242
- | 'account'
243
- /** Grandfathered 10-digit key. Needs PIN to read private worlds. */
244
- | 'legacy' | 'unknown';
245
- /** Demo range 0000000000-0000000009: read-only aliases, safe as live read gates. */
246
- declare function isDemoKey(key: string): boolean;
247
- declare function detectKeyKind(key: string): OwKeyKind;
248
- /** Can this key kind ever perform world writes? (PIN is a separate, per-world question.) */
249
- declare function kindCanWrite(kind: OwKeyKind): boolean;
250
- /**
251
- * Should a credential UI ask for a PIN with this key?
252
- * Prefixed keys read PIN-less; legacy keys may need it; writes on pinned
253
- * worlds always need it. 'optional' means: show the field, don't require it.
254
- */
255
- declare function pinExpectation(kind: OwKeyKind): 'never' | 'optional' | 'required-for-private-reads';
256
-
257
- /**
258
- * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
259
- * wire-corrected against live staging fixtures 2026-07-18.
260
- *
261
- * OwV2Client -- thin typed fetch client for the keel v2 API.
262
- *
263
- * Deliberately thin: no caching, no sync state, no retry policy -- those belong
264
- * to callers (sync engines, tools, games). What IS encoded here is the wire
265
- * contract and its safety rails: payload read-only-field stripping, opaque
266
- * cursors, idempotency headers, doc_url-bearing errors, polite page sizes, and
267
- * client-side UUID minting so idempotent retries are structurally safe.
268
- */
269
-
270
- declare class OwV2Client {
271
- readonly baseUrl: string;
272
- readonly keyKind: OwKeyKind;
273
- readonly pageSize: number;
274
- readonly changesPageSize: number;
275
- private readonly apiKey;
276
- private readonly apiPin;
277
- private readonly fetchImpl;
278
- constructor(config: OwClientConfig);
279
- /** GET /health -- unauthenticated liveness pulse. */
280
- health(): Promise<unknown>;
281
- /**
282
- * GET /world -- world meta (name, calendar/time fields, public_read).
283
- * GOTCHA (by server design): world-meta edits do NOT appear in /changes and
284
- * do not bump change_seq. Poll getWorld().updated_at for meta freshness.
285
- */
286
- getWorld(): Promise<OwWorldMeta>;
287
- /** PATCH /world -- partial world-meta update. */
288
- patchWorld(partial: Record<string, unknown>): Promise<OwWorldMeta>;
289
- /** GET /{type}/ -- one cursor page. */
290
- list(type: ElementType | string, params?: ListParams): Promise<OwPage>;
291
- /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
292
- listAll(type: ElementType | string, params?: Omit<ListParams, 'cursor'>): AsyncGenerator<OwElement>;
293
- /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
294
- get(type: ElementType | string, id: string, opts?: Pick<ListParams, 'expand' | 'fields'>): Promise<OwElement>;
295
- /**
296
- * POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
297
- * caller omits one (design ruling D29d) so a retry carrying the same
298
- * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
299
- */
300
- create(type: ElementType | string, element: OwElement | Record<string, unknown>, opts?: {
301
- idempotencyKey?: string;
302
- }): Promise<OwElement>;
303
- /** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
304
- upsert(type: ElementType | string, id: string, element: OwElement | Record<string, unknown>): Promise<OwElement>;
305
- /**
306
- * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
307
- * replace wholesale, omitted fields stay untouched. For link arrays prefer
308
- * editLinks() -- atomic server-side merge, no read-before-write.
309
- */
310
- patch(type: ElementType | string, id: string, partial: Record<string, unknown>): Promise<OwElement>;
311
- /**
312
- * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
313
- * tombstone AND scrubs the id from every other element's links in the same
314
- * transaction -- no client-side unlink pass needed, ever.
315
- */
316
- delete(type: ElementType | string, id: string): Promise<void>;
317
- /**
318
- * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
319
- * Dedupes, tolerates already-present/already-absent ids. Returns the FULL
320
- * updated element (fixture P5). Use this for all relationship editing; it
321
- * retires the read-merge-PATCH dance.
322
- */
323
- editLinks(type: ElementType | string, id: string, field: string, edit: OwLinkEdit): Promise<OwElement>;
324
- /**
325
- * POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
326
- * always; inspect per-slot numeric `status` + top-level `errors` flag);
327
- * atomic:true for all-or-nothing. Link validation runs against batch U
328
- * database -- send in any order, cycles included; no client topo-sort.
329
- * Success slots echo server-authoritative timestamps: set your sync baseline
330
- * from this response alone. When an idempotencyKey is replayed, the returned
331
- * response carries wasReplay:true (read from the Idempotent-Replay header).
332
- */
333
- bulk(items: OwBulkItem[], opts?: {
334
- atomic?: boolean;
335
- idempotencyKey?: string;
336
- }): Promise<OwBulkResponse>;
337
- /**
338
- * GET /changes -- one page of the world's ordered change feed.
339
- * Cursor is OPAQUE and never expires: persist verbatim, never parse.
340
- * Zero/absent cursor = full export (byte-aligned with the Folder Format).
341
- * Rewind rule: if your persisted position is ahead of page.head, the server
342
- * was restored -- re-baseline from cursor zero; do not assume caught-up.
343
- * Citizenship: heaviest route on the platform; default page size is polite.
344
- */
345
- changes(opts?: {
346
- since?: string;
347
- limit?: number;
348
- }): Promise<OwChangesPage>;
349
- /**
350
- * Walk the feed from `since` (or from zero = full export) to the current
351
- * tail, yielding ops in order. Returns the final cursor via the generator's
352
- * return value; persist it for the next incremental pull.
353
- */
354
- changesAll(since?: string): AsyncGenerator<OwChange, {
355
- cursor: string;
356
- head: number;
357
- }>;
358
- /**
359
- * Raw authenticated request against this client's baseUrl. Public since 4.0
360
- * so auxiliary resources can ride the same transport — it structurally
361
- * satisfies `TokenTransport` (`new TokenResource(client)`). Prefer the typed
362
- * methods for element CRUD; this is the escape hatch, and it does NOT apply
363
- * sanitizePayload — callers own their body shape.
364
- */
365
- request<T = unknown>(method: string, path: string, opts?: {
366
- query?: string;
367
- body?: unknown;
368
- idempotencyKey?: string;
369
- auth?: boolean;
370
- allowEmpty?: boolean;
371
- replayAware?: boolean;
372
- }): Promise<T>;
373
- }
374
-
375
- /**
376
- * Canonical element colour palette — four semantic families.
377
- *
378
- * Ruled by Captain 2026-07-22 after Skeld's measurement pass (Orrery
379
- * `product/schema/element-palette-measurements.md`): 22 mutually-separable
380
- * hues is structurally impossible; four families is the ceiling that passes
381
- * all-pairs CVD separation in both modes. **Colour carries the FAMILY; the
382
- * icon (`ELEMENT_ICONS`) carries the TYPE.** Dark-mode pairs land in the 6–8
383
- * CVD floor band, so secondary encoding (icon + label) is REQUIRED alongside
384
- * colour, not optional.
385
- *
386
- * The type→family map is GENERATED: `ELEMENT_FAMILIES` is emitted by
387
- * `codegen/generate_types.py` from the `family:` key in keel's schema YAML
388
- * (added 2026-07-23, keel c69366b) — a keel PRESENTATION-WRAPPER key
389
- * (first-party rendering metadata, not part of the council-governed
390
- * OnlyWorlds standard). It cannot drift from the schema; membership and hex
391
- * invariants stay test-gated in `test/palette.test.mjs`.
392
- *
393
- * The hexes below are design constants, hand-authored beside the generated
394
- * map. Do not change any value without re-running the CVD validation (every
395
- * brighter World green collides with Temporal amber for protan viewers — the
396
- * green is pinned BY the accessibility budget).
397
- *
398
- * Provenance: first proven live in atlas (`src/core/element-colors.ts`) and
399
- * council (`src/cosmos/element-families.ts`) — both become re-exports of this
400
- * module.
401
- */
402
-
403
- /**
404
- * Family → validated hex per surface mode. `light` assumes near-white
405
- * surfaces, `dark` assumes near-black (measured against #0a0a0a).
406
- * World green is identical in both modes and sits at its low-contrast end
407
- * deliberately — see module header before "fixing" it.
408
- */
409
- declare const FAMILY_COLORS: Record<ElementFamily, {
410
- light: string;
411
- dark: string;
412
- }>;
413
- /** Semantic family for an element type slug. */
414
- declare function familyOf(type: ElementType): ElementFamily;
415
- /**
416
- * The convenience most callers want: canonical colour for an element type.
417
- * Name matches the live atlas/council implementations so their SDK swap is a
418
- * re-export, not a rename. Defaults to `dark` (both current consumers are
419
- * dark-surface).
420
- */
421
- declare function elementColor(type: ElementType, mode?: 'light' | 'dark'): string;
422
- /** All four families, in ruling order (the order IS the CVD-safety mechanism of the source palette). */
423
- declare const FAMILY_ORDER: readonly ElementFamily[];
424
-
425
- /**
426
- * Current OnlyWorlds version
427
- * Synced with https://github.com/OnlyWorlds/OnlyWorlds/blob/main/VERSION
428
- */
429
- /**
430
- * Field type definitions for OnlyWorlds elements
431
- */
432
- type FieldType = 'text' | 'integer' | 'integer_max' | 'single_link' | 'multi_link';
433
- /**
434
- * Field metadata structure
435
- */
436
- interface FieldInfo {
437
- type: FieldType;
438
- target?: string;
439
- max?: number;
440
- required?: boolean;
441
- }
442
- declare const ELEMENT_LABELS: Record<ElementType, string>;
443
74
  declare const FIELD_SCHEMA: {
444
75
  readonly ability: {
445
76
  readonly name: {
@@ -681,7 +312,7 @@ declare const FIELD_SCHEMA: {
681
312
  };
682
313
  readonly equipment: {
683
314
  readonly type: "multi_link";
684
- readonly target: "construct";
315
+ readonly target: "object";
685
316
  };
686
317
  readonly activity: {
687
318
  readonly type: "text";
@@ -1892,10 +1523,6 @@ declare const FIELD_SCHEMA: {
1892
1523
  readonly type: "multi_link";
1893
1524
  readonly target: "family";
1894
1525
  };
1895
- readonly relations: {
1896
- readonly type: "multi_link";
1897
- readonly target: "relation";
1898
- };
1899
1526
  readonly titles: {
1900
1527
  readonly type: "multi_link";
1901
1528
  readonly target: "title";
@@ -2223,6 +1850,389 @@ declare const FIELD_SCHEMA: {
2223
1850
  };
2224
1851
  };
2225
1852
  };
1853
+
1854
+ /**
1855
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
1856
+ * wire-corrected against live staging fixtures 2026-07-18.
1857
+ *
1858
+ * OnlyWorlds v2 (keel) wire types -- the NON-generated, hand-owned wire shapes
1859
+ * (envelopes, pages, bulk, changes, config). Per-type element field typing is
1860
+ * generated (types.generated.ts). One-shape principle: a field reads the way it
1861
+ * writes -- links are UUID arrays (or UUID/null), same name both directions.
1862
+ * No `_ids` suffix in v2.
1863
+ */
1864
+
1865
+ /** An element as read from / written to the v2 API. Loose base + extension keys. */
1866
+ type OwElement = OwElementBase;
1867
+ /** Spatial types live under spatial/ in the OW Folder Format. */
1868
+ declare const SPATIAL_TYPES: readonly ElementType[];
1869
+ interface OwWorldMeta {
1870
+ id: string;
1871
+ name: string;
1872
+ updated_at?: string;
1873
+ public_read?: boolean;
1874
+ [field: string]: unknown;
1875
+ }
1876
+ /** List envelope: cursor-paginated. */
1877
+ interface OwPage<T = OwElement> {
1878
+ data: T[];
1879
+ has_more: boolean;
1880
+ next_cursor: string | null;
1881
+ }
1882
+ /** /changes feed -- discriminated union on `op`. Apply in order -> convergence. */
1883
+ type OwChange = {
1884
+ op: 'upsert';
1885
+ id: string;
1886
+ type: string;
1887
+ element: OwElement;
1888
+ updated_at: string;
1889
+ [k: string]: unknown;
1890
+ } | {
1891
+ op: 'delete';
1892
+ id: string;
1893
+ type: string;
1894
+ deleted_at: string;
1895
+ [k: string]: unknown;
1896
+ };
1897
+ /**
1898
+ * /changes response. Wire shape verified in keel source (core/changes.py) and
1899
+ * pinned here: {cursor, changes, has_more, head}.
1900
+ */
1901
+ interface OwChangesPage {
1902
+ /** Opaque compound cursor -- persist verbatim, never parse, never expires. */
1903
+ cursor: string;
1904
+ changes: OwChange[];
1905
+ has_more: boolean;
1906
+ /**
1907
+ * World's current change_seq. If a persisted cursor is ever AHEAD of head,
1908
+ * the server rewound (disaster restore) -- re-baseline from cursor zero
1909
+ * instead of assuming caught-up.
1910
+ */
1911
+ head: number;
1912
+ }
1913
+ interface OwBulkItem {
1914
+ type: ElementType | string;
1915
+ element: OwElement | Record<string, unknown>;
1916
+ }
1917
+ /**
1918
+ * One slot of a /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): `status` is
1919
+ * the NUMERIC HTTP status of that slot (201/400/...), success slots echo
1920
+ * created_at/updated_at, error slots carry an OwErrorBody under `error`.
1921
+ */
1922
+ interface OwBulkItemResult {
1923
+ status: number;
1924
+ id?: string;
1925
+ created_at?: string;
1926
+ updated_at?: string;
1927
+ error?: OwErrorBody;
1928
+ }
1929
+ /** The wire error envelope carried in error slots and thrown errors. */
1930
+ interface OwErrorBody {
1931
+ type?: string;
1932
+ code?: string;
1933
+ message?: string;
1934
+ param?: string | null;
1935
+ doc_url?: string;
1936
+ }
1937
+ /**
1938
+ * /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): the array key is `items`,
1939
+ * not `results`. `wasReplay` is populated by the client from the (lowercase on
1940
+ * the wire) Idempotent-Replay response header (fixture P2b) -- not a wire field.
1941
+ */
1942
+ interface OwBulkResponse {
1943
+ errors: boolean;
1944
+ items: OwBulkItemResult[];
1945
+ /** Client-derived: true when the server replayed a prior Idempotency-Key. */
1946
+ wasReplay?: boolean;
1947
+ [k: string]: unknown;
1948
+ }
1949
+ interface OwLinkEdit {
1950
+ add?: string[];
1951
+ remove?: string[];
1952
+ }
1953
+ interface ListParams {
1954
+ limit?: number;
1955
+ cursor?: string;
1956
+ /** One-level stub expansion, e.g. ['friends', 'location']. */
1957
+ expand?: string[];
1958
+ /** Sparse include-set of field names. */
1959
+ fields?: string[];
1960
+ /**
1961
+ * Blessed Django-style filters: __icontains, __in, __gte, __lte, __isnull,
1962
+ * supertype/subtype equality. Unknown params 422 loudly server-side -- the
1963
+ * client passes them through and lets the platform name the typo.
1964
+ */
1965
+ filter?: Record<string, string | number | boolean>;
1966
+ }
1967
+ interface OwClientConfig {
1968
+ /** ow_w_ / ow_r_ / ow_a_ prefixed key, or grandfathered 10-digit legacy key. */
1969
+ apiKey: string;
1970
+ /**
1971
+ * Optional. Required for writes when the world has a PIN, and for legacy-key
1972
+ * reads of private worlds. Prefixed keys read PIN-less. String, not number --
1973
+ * '0123' !== 123.
1974
+ */
1975
+ apiPin?: string;
1976
+ /** Default: https://www.onlyworlds.com/api/v2 */
1977
+ baseUrl?: string;
1978
+ /**
1979
+ * Page size for element lists. Default 100 (server default; max 1000).
1980
+ * Deliberately visible in config: page size is a citizenship property.
1981
+ */
1982
+ pageSize?: number;
1983
+ /**
1984
+ * Page size for /changes pulls. Default 100. Live precedents: Obsidian 100,
1985
+ * Atlas 250, MCP 25. /changes is the platform's heaviest route -- be polite.
1986
+ */
1987
+ changesPageSize?: number;
1988
+ /** Injectable for tests / fake-keel harnesses. Defaults to globalThis.fetch. */
1989
+ fetch?: typeof globalThis.fetch;
1990
+ }
1991
+
1992
+ /**
1993
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
1994
+ * wire-corrected against live staging fixtures 2026-07-18.
1995
+ *
1996
+ * keel error envelope handling. The error contract is part of the contract:
1997
+ * envelopes carry a machine `code` and a `doc_url` fragment anchored at
1998
+ * onlyworlds.github.io/api/errors -- surface both, always. The live wire
1999
+ * envelope also carries `type` and `param` (fixtures P4a/P2c); both are
2000
+ * surfaced on the thrown error.
2001
+ */
2002
+ /** Auth codes are distinguishable by design; client recovery UX differs per code. */
2003
+ type OwAuthErrorCode = 'invalid_credentials' | 'key_revoked' | 'world_gone';
2004
+ declare class OwApiError extends Error {
2005
+ readonly status: number;
2006
+ /** Machine error code from the keel envelope, e.g. 'invalid_credentials'. */
2007
+ readonly code: string | null;
2008
+ /** Error family from the envelope, e.g. 'invalid_request', 'not_found'. */
2009
+ readonly type: string | null;
2010
+ /** Offending field/param named by the envelope (422/400), else null. */
2011
+ readonly param: string | null;
2012
+ /** Documentation link from the envelope -- show it to users/logs verbatim. */
2013
+ readonly docUrl: string | null;
2014
+ /** Raw parsed envelope (or body text when the body wasn't JSON). */
2015
+ readonly detail: unknown;
2016
+ constructor(status: number, code: string | null, message: string, docUrl: string | null, detail: unknown, type?: string | null, param?: string | null);
2017
+ get isAuthError(): boolean;
2018
+ /** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
2019
+ get isValidationError(): boolean;
2020
+ /** Same Idempotency-Key replayed with a different payload. */
2021
+ get isIdempotencyConflict(): boolean;
2022
+ }
2023
+ /** Network-level failure (fetch rejected) -- no envelope to parse. */
2024
+ declare class OwNetworkError extends Error {
2025
+ readonly cause2: unknown;
2026
+ constructor(message: string, cause: unknown);
2027
+ }
2028
+ /** Parse a wire envelope into OwApiError parts (exported for the error type-tests). */
2029
+ /** Parse the platform ERROR envelope into an OwApiError. (Renamed from parseEnvelope in 4.0 —
2030
+ * distinct from the world-export envelope, which is a different artifact entirely.) */
2031
+ declare function parseErrorEnvelope(status: number, body: unknown): OwApiError;
2032
+ /** Build an OwApiError from a non-2xx response, tolerating non-JSON bodies. */
2033
+ declare function errorFromResponse(res: Response): Promise<OwApiError>;
2034
+
2035
+ /**
2036
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
2037
+ * wire-corrected against live staging fixtures 2026-07-18.
2038
+ *
2039
+ * OnlyWorlds key-kind detection. Prefixes make leaked keys grep-scannable
2040
+ * (Stripe/GitHub precedent) -- and tell a client what auth shape to expect.
2041
+ */
2042
+ type OwKeyKind =
2043
+ /** ow_w_ -- world key, read + write. Writes need the world's PIN if it has one. */
2044
+ 'write'
2045
+ /** ow_r_ -- world key, read-only, works bare (no PIN). The share-with-players primitive. */
2046
+ | 'read'
2047
+ /** ow_a_ -- account Bearer token for /account/* routes; can mint world keys. */
2048
+ | 'account'
2049
+ /** Grandfathered 10-digit key. Needs PIN to read private worlds. */
2050
+ | 'legacy' | 'unknown';
2051
+ /** Demo range 0000000000-0000000009: read-only aliases, safe as live read gates. */
2052
+ declare function isDemoKey(key: string): boolean;
2053
+ declare function detectKeyKind(key: string): OwKeyKind;
2054
+ /** Can this key kind ever perform world writes? (PIN is a separate, per-world question.) */
2055
+ declare function kindCanWrite(kind: OwKeyKind): boolean;
2056
+ /**
2057
+ * Should a credential UI ask for a PIN with this key?
2058
+ * Prefixed keys read PIN-less; legacy keys may need it; writes on pinned
2059
+ * worlds always need it. 'optional' means: show the field, don't require it.
2060
+ */
2061
+ declare function pinExpectation(kind: OwKeyKind): 'never' | 'optional' | 'required-for-private-reads';
2062
+
2063
+ /**
2064
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
2065
+ * wire-corrected against live staging fixtures 2026-07-18.
2066
+ *
2067
+ * OwV2Client -- thin typed fetch client for the keel v2 API.
2068
+ *
2069
+ * Deliberately thin: no caching, no sync state, no retry policy -- those belong
2070
+ * to callers (sync engines, tools, games). What IS encoded here is the wire
2071
+ * contract and its safety rails: payload read-only-field stripping, opaque
2072
+ * cursors, idempotency headers, doc_url-bearing errors, polite page sizes, and
2073
+ * client-side UUID minting so idempotent retries are structurally safe.
2074
+ */
2075
+
2076
+ declare class OwV2Client {
2077
+ readonly baseUrl: string;
2078
+ readonly keyKind: OwKeyKind;
2079
+ readonly pageSize: number;
2080
+ readonly changesPageSize: number;
2081
+ private readonly apiKey;
2082
+ private readonly apiPin;
2083
+ private readonly fetchImpl;
2084
+ constructor(config: OwClientConfig);
2085
+ /** GET /health -- unauthenticated liveness pulse. */
2086
+ health(): Promise<unknown>;
2087
+ /**
2088
+ * GET /world -- world meta (name, calendar/time fields, public_read).
2089
+ * GOTCHA (by server design): world-meta edits do NOT appear in /changes and
2090
+ * do not bump change_seq. Poll getWorld().updated_at for meta freshness.
2091
+ */
2092
+ getWorld(): Promise<OwWorldMeta>;
2093
+ /** PATCH /world -- partial world-meta update. */
2094
+ patchWorld(partial: Record<string, unknown>): Promise<OwWorldMeta>;
2095
+ /** GET /{type}/ -- one cursor page. */
2096
+ list(type: ElementType | string, params?: ListParams): Promise<OwPage>;
2097
+ /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
2098
+ listAll(type: ElementType | string, params?: Omit<ListParams, 'cursor'>): AsyncGenerator<OwElement>;
2099
+ /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
2100
+ get(type: ElementType | string, id: string, opts?: Pick<ListParams, 'expand' | 'fields'>): Promise<OwElement>;
2101
+ /**
2102
+ * POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
2103
+ * caller omits one (design ruling D29d) so a retry carrying the same
2104
+ * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
2105
+ */
2106
+ create(type: ElementType | string, element: OwElement | Record<string, unknown>, opts?: {
2107
+ idempotencyKey?: string;
2108
+ }): Promise<OwElement>;
2109
+ /** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
2110
+ upsert(type: ElementType | string, id: string, element: OwElement | Record<string, unknown>): Promise<OwElement>;
2111
+ /**
2112
+ * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
2113
+ * replace wholesale, omitted fields stay untouched. For link arrays prefer
2114
+ * editLinks() -- atomic server-side merge, no read-before-write.
2115
+ */
2116
+ patch(type: ElementType | string, id: string, partial: Record<string, unknown>): Promise<OwElement>;
2117
+ /**
2118
+ * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
2119
+ * tombstone AND scrubs the id from every other element's links in the same
2120
+ * transaction -- no client-side unlink pass needed, ever.
2121
+ */
2122
+ delete(type: ElementType | string, id: string): Promise<void>;
2123
+ /**
2124
+ * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
2125
+ * Dedupes, tolerates already-present/already-absent ids. Returns the FULL
2126
+ * updated element (fixture P5). Use this for all relationship editing; it
2127
+ * retires the read-merge-PATCH dance.
2128
+ */
2129
+ editLinks(type: ElementType | string, id: string, field: string, edit: OwLinkEdit): Promise<OwElement>;
2130
+ /**
2131
+ * POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
2132
+ * always; inspect per-slot numeric `status` + top-level `errors` flag);
2133
+ * atomic:true for all-or-nothing. Link validation runs against batch U
2134
+ * database -- send in any order, cycles included; no client topo-sort.
2135
+ * Success slots echo server-authoritative timestamps: set your sync baseline
2136
+ * from this response alone. When an idempotencyKey is replayed, the returned
2137
+ * response carries wasReplay:true (read from the Idempotent-Replay header).
2138
+ */
2139
+ bulk(items: OwBulkItem[], opts?: {
2140
+ atomic?: boolean;
2141
+ idempotencyKey?: string;
2142
+ }): Promise<OwBulkResponse>;
2143
+ /**
2144
+ * GET /changes -- one page of the world's ordered change feed.
2145
+ * Cursor is OPAQUE and never expires: persist verbatim, never parse.
2146
+ * Zero/absent cursor = full export (byte-aligned with the Folder Format).
2147
+ * Rewind rule: if your persisted position is ahead of page.head, the server
2148
+ * was restored -- re-baseline from cursor zero; do not assume caught-up.
2149
+ * Citizenship: heaviest route on the platform; default page size is polite.
2150
+ */
2151
+ changes(opts?: {
2152
+ since?: string;
2153
+ limit?: number;
2154
+ }): Promise<OwChangesPage>;
2155
+ /**
2156
+ * Walk the feed from `since` (or from zero = full export) to the current
2157
+ * tail, yielding ops in order. Returns the final cursor via the generator's
2158
+ * return value; persist it for the next incremental pull.
2159
+ */
2160
+ changesAll(since?: string): AsyncGenerator<OwChange, {
2161
+ cursor: string;
2162
+ head: number;
2163
+ }>;
2164
+ /**
2165
+ * Raw authenticated request against this client's baseUrl. Public since 4.0
2166
+ * so auxiliary resources can ride the same transport — it structurally
2167
+ * satisfies `TokenTransport` (`new TokenResource(client)`). Prefer the typed
2168
+ * methods for element CRUD; this is the escape hatch, and it does NOT apply
2169
+ * sanitizePayload — callers own their body shape.
2170
+ */
2171
+ request<T = unknown>(method: string, path: string, opts?: {
2172
+ query?: string;
2173
+ body?: unknown;
2174
+ idempotencyKey?: string;
2175
+ auth?: boolean;
2176
+ allowEmpty?: boolean;
2177
+ replayAware?: boolean;
2178
+ }): Promise<T>;
2179
+ }
2180
+
2181
+ /**
2182
+ * Canonical element colour palette — four semantic families.
2183
+ *
2184
+ * Ruled by Captain 2026-07-22 after Skeld's measurement pass (Orrery
2185
+ * `product/schema/element-palette-measurements.md`): 22 mutually-separable
2186
+ * hues is structurally impossible; four families is the ceiling that passes
2187
+ * all-pairs CVD separation in both modes. **Colour carries the FAMILY; the
2188
+ * icon (`ELEMENT_ICONS`) carries the TYPE.** Dark-mode pairs land in the 6–8
2189
+ * CVD floor band, so secondary encoding (icon + label) is REQUIRED alongside
2190
+ * colour, not optional.
2191
+ *
2192
+ * The type→family map is GENERATED: `ELEMENT_FAMILIES` is emitted by
2193
+ * `codegen/generate_types.py` from the `family:` key in keel's schema YAML
2194
+ * (added 2026-07-23, keel c69366b) — a keel PRESENTATION-WRAPPER key
2195
+ * (first-party rendering metadata, not part of the council-governed
2196
+ * OnlyWorlds standard). It cannot drift from the schema; membership and hex
2197
+ * invariants stay test-gated in `test/palette.test.mjs`.
2198
+ *
2199
+ * The hexes below are design constants, hand-authored beside the generated
2200
+ * map. Do not change any value without re-running the CVD validation (every
2201
+ * brighter World green collides with Temporal amber for protan viewers — the
2202
+ * green is pinned BY the accessibility budget).
2203
+ *
2204
+ * Provenance: first proven live in atlas (`src/core/element-colors.ts`) and
2205
+ * council (`src/cosmos/element-families.ts`) — both become re-exports of this
2206
+ * module.
2207
+ */
2208
+
2209
+ /**
2210
+ * Family → validated hex per surface mode. `light` assumes near-white
2211
+ * surfaces, `dark` assumes near-black (measured against #0a0a0a).
2212
+ * World green is identical in both modes and sits at its low-contrast end
2213
+ * deliberately — see module header before "fixing" it.
2214
+ */
2215
+ declare const FAMILY_COLORS: Record<ElementFamily, {
2216
+ light: string;
2217
+ dark: string;
2218
+ }>;
2219
+ /** Semantic family for an element type slug. */
2220
+ declare function familyOf(type: ElementType): ElementFamily;
2221
+ /**
2222
+ * The convenience most callers want: canonical colour for an element type.
2223
+ * Name matches the live atlas/council implementations so their SDK swap is a
2224
+ * re-export, not a rename. Defaults to `dark` (both current consumers are
2225
+ * dark-surface).
2226
+ */
2227
+ declare function elementColor(type: ElementType, mode?: 'light' | 'dark'): string;
2228
+ /** All four families, in ruling order (the order IS the CVD-safety mechanism of the source palette). */
2229
+ declare const FAMILY_ORDER: readonly ElementFamily[];
2230
+
2231
+ /**
2232
+ * Current OnlyWorlds version
2233
+ * Synced with https://github.com/OnlyWorlds/OnlyWorlds/blob/main/VERSION
2234
+ */
2235
+ declare const ELEMENT_LABELS: Record<ElementType, string>;
2226
2236
  /**
2227
2237
  * Get Material Design icon name for an element type
2228
2238
  * Accepts multiple formats: 'character', 'characters', 'Character', etc.
package/dist/index.js CHANGED
@@ -314,7 +314,7 @@ function buildQuery(params) {
314
314
 
315
315
  // src/v2/types.generated.ts
316
316
  var ELEMENT_TYPES = ["ability", "character", "collective", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"];
317
- var ONLYWORLDS_VERSION = "00.30.00";
317
+ var ONLYWORLDS_VERSION = "00.30.01";
318
318
  var ELEMENT_FAMILIES = {
319
319
  ability: "abstract",
320
320
  character: "agents",
@@ -466,50 +466,6 @@ var ELEMENT_SECTIONS = {
466
466
  { name: "World", order: 2, fields: ["context", "populations", "titles", "principles"] }
467
467
  ]
468
468
  };
469
-
470
- // src/v2/types.ts
471
- var SPATIAL_TYPES = ["map", "pin", "marker", "zone"];
472
-
473
- // src/v2/palette.ts
474
- var FAMILY_COLORS = {
475
- agents: { light: "#2a78d6", dark: "#3987e5" },
476
- world: { light: "#008300", dark: "#008300" },
477
- abstract: { light: "#e87ba4", dark: "#d55181" },
478
- temporal: { light: "#eda100", dark: "#c98500" }
479
- };
480
- function familyOf(type) {
481
- return ELEMENT_FAMILIES[type];
482
- }
483
- function elementColor(type, mode = "dark") {
484
- return FAMILY_COLORS[ELEMENT_FAMILIES[type]][mode];
485
- }
486
- var FAMILY_ORDER = ["agents", "world", "abstract", "temporal"];
487
-
488
- // src/v2/constants.ts
489
- var ELEMENT_LABELS = {
490
- ability: "Abilities",
491
- character: "Characters",
492
- collective: "Collectives",
493
- construct: "Constructs",
494
- creature: "Creatures",
495
- event: "Events",
496
- family: "Families",
497
- institution: "Institutions",
498
- language: "Languages",
499
- law: "Laws",
500
- location: "Locations",
501
- map: "Maps",
502
- marker: "Markers",
503
- narrative: "Narratives",
504
- object: "Objects",
505
- phenomenon: "Phenomena",
506
- pin: "Pins",
507
- relation: "Relations",
508
- species: "Species",
509
- title: "Titles",
510
- trait: "Traits",
511
- zone: "Zones"
512
- };
513
469
  var FIELD_SCHEMA = {
514
470
  ability: {
515
471
  // Base fields (shared by all elements)
@@ -594,7 +550,7 @@ var FIELD_SCHEMA = {
594
550
  count: { type: "integer" },
595
551
  formation_date: { type: "integer" },
596
552
  operator: { type: "single_link", target: "institution" },
597
- equipment: { type: "multi_link", target: "construct" },
553
+ equipment: { type: "multi_link", target: "object" },
598
554
  // Dynamics
599
555
  activity: { type: "text" },
600
556
  disposition: { type: "text" },
@@ -655,7 +611,7 @@ var FIELD_SCHEMA = {
655
611
  weight: { type: "integer" },
656
612
  height: { type: "integer" },
657
613
  species: { type: "multi_link", target: "species" },
658
- // Behaviour
614
+ // Behavior
659
615
  habits: { type: "text" },
660
616
  demeanor: { type: "text" },
661
617
  traits: { type: "multi_link", target: "trait" },
@@ -957,9 +913,7 @@ var FIELD_SCHEMA = {
957
913
  // Details
958
914
  map: { type: "single_link", target: "map", required: true },
959
915
  element_type: { type: "text", required: true },
960
- // ElementType enum value; YAML 'element' generic-link is split into _type + _id
961
916
  element_id: { type: "single_link", target: "any", required: true },
962
- // Can reference any element
963
917
  x: { type: "integer", required: true },
964
918
  y: { type: "integer", required: true },
965
919
  z: { type: "integer" }
@@ -992,7 +946,6 @@ var FIELD_SCHEMA = {
992
946
  phenomena: { type: "multi_link", target: "phenomenon" },
993
947
  languages: { type: "multi_link", target: "language" },
994
948
  families: { type: "multi_link", target: "family" },
995
- relations: { type: "multi_link", target: "relation" },
996
949
  titles: { type: "multi_link", target: "title" },
997
950
  constructs: { type: "multi_link", target: "construct" },
998
951
  narratives: { type: "multi_link", target: "narrative" }
@@ -1104,6 +1057,50 @@ var FIELD_SCHEMA = {
1104
1057
  principles: { type: "multi_link", target: "construct" }
1105
1058
  }
1106
1059
  };
1060
+
1061
+ // src/v2/types.ts
1062
+ var SPATIAL_TYPES = ["map", "pin", "marker", "zone"];
1063
+
1064
+ // src/v2/palette.ts
1065
+ var FAMILY_COLORS = {
1066
+ agents: { light: "#2a78d6", dark: "#3987e5" },
1067
+ world: { light: "#008300", dark: "#008300" },
1068
+ abstract: { light: "#e87ba4", dark: "#d55181" },
1069
+ temporal: { light: "#eda100", dark: "#c98500" }
1070
+ };
1071
+ function familyOf(type) {
1072
+ return ELEMENT_FAMILIES[type];
1073
+ }
1074
+ function elementColor(type, mode = "dark") {
1075
+ return FAMILY_COLORS[ELEMENT_FAMILIES[type]][mode];
1076
+ }
1077
+ var FAMILY_ORDER = ["agents", "world", "abstract", "temporal"];
1078
+
1079
+ // src/v2/constants.ts
1080
+ var ELEMENT_LABELS = {
1081
+ ability: "Abilities",
1082
+ character: "Characters",
1083
+ collective: "Collectives",
1084
+ construct: "Constructs",
1085
+ creature: "Creatures",
1086
+ event: "Events",
1087
+ family: "Families",
1088
+ institution: "Institutions",
1089
+ language: "Languages",
1090
+ law: "Laws",
1091
+ location: "Locations",
1092
+ map: "Maps",
1093
+ marker: "Markers",
1094
+ narrative: "Narratives",
1095
+ object: "Objects",
1096
+ phenomenon: "Phenomena",
1097
+ pin: "Pins",
1098
+ relation: "Relations",
1099
+ species: "Species",
1100
+ title: "Titles",
1101
+ trait: "Traits",
1102
+ zone: "Zones"
1103
+ };
1107
1104
  var PLURAL_TO_SINGULAR = {
1108
1105
  abilities: "ability",
1109
1106
  characters: "character",
package/package.json CHANGED
@@ -1,61 +1,63 @@
1
- {
2
- "name": "@onlyworlds/sdk",
3
- "version": "4.0.0",
4
- "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "types": "./dist/index.d.ts",
9
- "import": "./dist/index.js"
10
- },
11
- "./package.json": "./package.json"
12
- },
13
- "types": "dist/index.d.ts",
14
- "sideEffects": false,
15
- "files": [
16
- "dist",
17
- "README.md",
18
- "AGENTS.md",
19
- "CHANGELOG.md",
20
- "SCHEMA.md"
21
- ],
22
- "scripts": {
23
- "build": "tsup src/index.ts --format esm --dts --clean",
24
- "dev": "tsup src/index.ts --format esm --dts --watch",
25
- "pretest": "npm run build",
26
- "test": "node --test \"test/**/*.test.mjs\"",
27
- "codegen": "python codegen/generate_types.py",
28
- "codegen:check": "python codegen/generate_types.py --check",
29
- "prepublishOnly": "npm run build"
30
- },
31
- "keywords": [
32
- "onlyworlds",
33
- "worldbuilding",
34
- "api",
35
- "sdk",
36
- "typescript",
37
- "rpg",
38
- "ttrpg",
39
- "game-development"
40
- ],
41
- "author": "OnlyWorlds",
42
- "license": "MIT",
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/OnlyWorlds/sdk.git"
46
- },
47
- "homepage": "https://onlyworlds.github.io/",
48
- "bugs": {
49
- "url": "https://github.com/OnlyWorlds/sdk/issues"
50
- },
51
- "devDependencies": {
52
- "tsup": "^8.0.1",
53
- "typescript": "^5.3.3"
54
- },
55
- "peerDependencies": {
56
- "typescript": ">=4.5.0"
57
- },
58
- "engines": {
59
- "node": ">=18.0.0"
60
- }
61
- }
1
+ {
2
+ "name": "@onlyworlds/sdk",
3
+ "version": "4.1.0",
4
+ "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./package.json": "./package.json"
12
+ },
13
+ "types": "dist/index.d.ts",
14
+ "sideEffects": false,
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "AGENTS.md",
19
+ "CHANGELOG.md",
20
+ "SCHEMA.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup src/index.ts --format esm --dts --clean",
24
+ "dev": "tsup src/index.ts --format esm --dts --watch",
25
+ "pretest": "npm run build",
26
+ "test": "node --test",
27
+ "codegen": "python codegen/generate_types.py",
28
+ "codegen:check": "python codegen/generate_types.py --check",
29
+ "schema:verify": "python codegen/verify_dist.py",
30
+ "schema:check": "npm run schema:verify && npm run codegen:check",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "keywords": [
34
+ "onlyworlds",
35
+ "worldbuilding",
36
+ "api",
37
+ "sdk",
38
+ "typescript",
39
+ "rpg",
40
+ "ttrpg",
41
+ "game-development"
42
+ ],
43
+ "author": "OnlyWorlds",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/OnlyWorlds/sdk.git"
48
+ },
49
+ "homepage": "https://onlyworlds.github.io/",
50
+ "bugs": {
51
+ "url": "https://github.com/OnlyWorlds/sdk/issues"
52
+ },
53
+ "devDependencies": {
54
+ "tsup": "^8.0.1",
55
+ "typescript": "^5.3.3"
56
+ },
57
+ "peerDependencies": {
58
+ "typescript": ">=4.5.0"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ }
63
+ }