@onlyworlds/sdk 4.0.0 → 4.0.1

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,42 @@
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.0.1] — 2026-07-29
7
+
8
+ **Metadata correction release.** No wire-path change: the client's reads and writes never
9
+ consulted `FIELD_SCHEMA`, and the generated interfaces carried the correct targets throughout.
10
+ The exposure is anything that builds UI or validation by **iterating `FIELD_SCHEMA`**.
11
+
12
+ ⚑ **One way this can surface as a compile error**: `relation.relations` is removed, so
13
+ `FIELD_SCHEMA.relation.relations` is now a TypeScript error rather than a value. That is the
14
+ intended outcome — the field does not exist in the standard and the API rejects it — but it can
15
+ break a build rather than only a behaviour.
16
+
17
+ ### Fixed
18
+ - **`FIELD_SCHEMA.collective.equipment` targeted `construct`; the standard says `object`.**
19
+ This is the founding case of the schema ruling table
20
+ (`collective-equipment-target`, ruled 2026-07-23): the v1 implementation used
21
+ Construct, v1 is decommissioned, and keel serves per YAML. The generated code path
22
+ in this repo was corrected the same week. The hand-maintained `FIELD_SCHEMA` copy of
23
+ the same fact, in the same package, kept shipping the decommissioned value on
24
+ `latest` — the fix went where someone happened to be looking.
25
+ - **`FIELD_SCHEMA.relation.relations` removed** — a `multi_link` to `relation` that does
26
+ not exist in `relation.yaml`. A phantom field, publicly exported, that consumers
27
+ building forms from this table would have sent to an API that 422s unknown keys.
28
+
29
+ ### Changed
30
+ - **`FIELD_SCHEMA` is now GENERATED** from the pinned schema distribution and gated by
31
+ `codegen:check`, joining `ELEMENT_ICONS` / `ELEMENT_SECTIONS` / `ELEMENT_FAMILIES`. It
32
+ was ~650 hand-maintained lines whose test compared nothing to the schema. Regenerating
33
+ it changed exactly 2 of 467 entries — the two above. Runtime shape and the deeply
34
+ readonly public types (`as const`) are unchanged.
35
+ Two declared deviations from a naive schema read are now stated in the generated file:
36
+ `pin.element` (a `generic-link`) splits into `element_type` + `element_id`, as the wire
37
+ serves it; and `integer_max` / `max` remain in the `FieldType` union unused, because
38
+ the walk does not surface the schema's `maximum:` constraint (41 across 17 types).
39
+ - `codegen/generate_types.py` imports the vendored schema walk instead of carrying its
40
+ own copy of it. Output byte-identical.
41
+
6
42
  ## [4.0.0] — 2026-07-23
7
43
 
8
44
  **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/dist/index.d.ts CHANGED
@@ -26,15 +26,17 @@ interface OwElementBase {
26
26
  }
27
27
  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
28
  declare const ELEMENT_TYPES: ElementType[];
29
- /** Canonical OnlyWorlds schema version. Source: canonical VERSION file, carried into keel schema/ by the refresh script (keel 492168c). */
29
+ /** Canonical OnlyWorlds schema version. Source: the `canonical:` value of the pinned
30
+ * distribution's VERSION file (see the provenance block at the top of this file). */
30
31
  declare const ONLYWORLDS_VERSION: "00.30.00";
31
32
  /** The four semantic families (colour carries the family; ELEMENT_ICONS carries the type). */
32
33
  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"). */
34
+ /** Per-type semantic family. Source: the distribution's `presentation.json` sidecar
35
+ * (first-party rendering DEFAULTS — NOT part of the council-governed OnlyWorlds
36
+ * standard, and explicitly overridable by any consumer). The colour values are
37
+ * NOT in the sidecar: FAMILY_COLORS is hand-authored here in src/v2/palette.ts. */
36
38
  declare const ELEMENT_FAMILIES: Record<ElementType, ElementFamily>;
37
- /** Material Symbols icon name per type. Source: keel's PRESENTATION-WRAPPER key `icon:` (keel 56c124a). */
39
+ /** Material Symbols icon name per type. Source: the distribution's `presentation.json` sidecar. */
38
40
  declare const ELEMENT_ICONS: Record<ElementType, string>;
39
41
  /** Field grouping for display. DERIVED from the canonical schema's own document
40
42
  * structure (top-level property groups, document order = display order). */
@@ -44,402 +46,15 @@ interface SectionInfo {
44
46
  fields: string[];
45
47
  }
46
48
  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
-
186
- /**
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.
189
- *
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.
195
- */
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);
49
+ /** Field type definitions for OnlyWorlds elements. */
50
+ type FieldType = 'text' | 'integer' | 'integer_max' | 'single_link' | 'multi_link';
51
+ /** Field metadata structure. */
52
+ interface FieldInfo {
53
+ type: FieldType;
54
+ target?: string;
55
+ max?: number;
56
+ required?: boolean;
221
57
  }
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
58
  declare const FIELD_SCHEMA: {
444
59
  readonly ability: {
445
60
  readonly name: {
@@ -681,7 +296,7 @@ declare const FIELD_SCHEMA: {
681
296
  };
682
297
  readonly equipment: {
683
298
  readonly type: "multi_link";
684
- readonly target: "construct";
299
+ readonly target: "object";
685
300
  };
686
301
  readonly activity: {
687
302
  readonly type: "text";
@@ -1892,10 +1507,6 @@ declare const FIELD_SCHEMA: {
1892
1507
  readonly type: "multi_link";
1893
1508
  readonly target: "family";
1894
1509
  };
1895
- readonly relations: {
1896
- readonly type: "multi_link";
1897
- readonly target: "relation";
1898
- };
1899
1510
  readonly titles: {
1900
1511
  readonly type: "multi_link";
1901
1512
  readonly target: "title";
@@ -2223,6 +1834,389 @@ declare const FIELD_SCHEMA: {
2223
1834
  };
2224
1835
  };
2225
1836
  };
1837
+
1838
+ /**
1839
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
1840
+ * wire-corrected against live staging fixtures 2026-07-18.
1841
+ *
1842
+ * OnlyWorlds v2 (keel) wire types -- the NON-generated, hand-owned wire shapes
1843
+ * (envelopes, pages, bulk, changes, config). Per-type element field typing is
1844
+ * generated (types.generated.ts). One-shape principle: a field reads the way it
1845
+ * writes -- links are UUID arrays (or UUID/null), same name both directions.
1846
+ * No `_ids` suffix in v2.
1847
+ */
1848
+
1849
+ /** An element as read from / written to the v2 API. Loose base + extension keys. */
1850
+ type OwElement = OwElementBase;
1851
+ /** Spatial types live under spatial/ in the OW Folder Format. */
1852
+ declare const SPATIAL_TYPES: readonly ElementType[];
1853
+ interface OwWorldMeta {
1854
+ id: string;
1855
+ name: string;
1856
+ updated_at?: string;
1857
+ public_read?: boolean;
1858
+ [field: string]: unknown;
1859
+ }
1860
+ /** List envelope: cursor-paginated. */
1861
+ interface OwPage<T = OwElement> {
1862
+ data: T[];
1863
+ has_more: boolean;
1864
+ next_cursor: string | null;
1865
+ }
1866
+ /** /changes feed -- discriminated union on `op`. Apply in order -> convergence. */
1867
+ type OwChange = {
1868
+ op: 'upsert';
1869
+ id: string;
1870
+ type: string;
1871
+ element: OwElement;
1872
+ updated_at: string;
1873
+ [k: string]: unknown;
1874
+ } | {
1875
+ op: 'delete';
1876
+ id: string;
1877
+ type: string;
1878
+ deleted_at: string;
1879
+ [k: string]: unknown;
1880
+ };
1881
+ /**
1882
+ * /changes response. Wire shape verified in keel source (core/changes.py) and
1883
+ * pinned here: {cursor, changes, has_more, head}.
1884
+ */
1885
+ interface OwChangesPage {
1886
+ /** Opaque compound cursor -- persist verbatim, never parse, never expires. */
1887
+ cursor: string;
1888
+ changes: OwChange[];
1889
+ has_more: boolean;
1890
+ /**
1891
+ * World's current change_seq. If a persisted cursor is ever AHEAD of head,
1892
+ * the server rewound (disaster restore) -- re-baseline from cursor zero
1893
+ * instead of assuming caught-up.
1894
+ */
1895
+ head: number;
1896
+ }
1897
+ interface OwBulkItem {
1898
+ type: ElementType | string;
1899
+ element: OwElement | Record<string, unknown>;
1900
+ }
1901
+ /**
1902
+ * One slot of a /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): `status` is
1903
+ * the NUMERIC HTTP status of that slot (201/400/...), success slots echo
1904
+ * created_at/updated_at, error slots carry an OwErrorBody under `error`.
1905
+ */
1906
+ interface OwBulkItemResult {
1907
+ status: number;
1908
+ id?: string;
1909
+ created_at?: string;
1910
+ updated_at?: string;
1911
+ error?: OwErrorBody;
1912
+ }
1913
+ /** The wire error envelope carried in error slots and thrown errors. */
1914
+ interface OwErrorBody {
1915
+ type?: string;
1916
+ code?: string;
1917
+ message?: string;
1918
+ param?: string | null;
1919
+ doc_url?: string;
1920
+ }
1921
+ /**
1922
+ * /bulk response. WIRE-CORRECTED (fixtures P2a/P2c): the array key is `items`,
1923
+ * not `results`. `wasReplay` is populated by the client from the (lowercase on
1924
+ * the wire) Idempotent-Replay response header (fixture P2b) -- not a wire field.
1925
+ */
1926
+ interface OwBulkResponse {
1927
+ errors: boolean;
1928
+ items: OwBulkItemResult[];
1929
+ /** Client-derived: true when the server replayed a prior Idempotency-Key. */
1930
+ wasReplay?: boolean;
1931
+ [k: string]: unknown;
1932
+ }
1933
+ interface OwLinkEdit {
1934
+ add?: string[];
1935
+ remove?: string[];
1936
+ }
1937
+ interface ListParams {
1938
+ limit?: number;
1939
+ cursor?: string;
1940
+ /** One-level stub expansion, e.g. ['friends', 'location']. */
1941
+ expand?: string[];
1942
+ /** Sparse include-set of field names. */
1943
+ fields?: string[];
1944
+ /**
1945
+ * Blessed Django-style filters: __icontains, __in, __gte, __lte, __isnull,
1946
+ * supertype/subtype equality. Unknown params 422 loudly server-side -- the
1947
+ * client passes them through and lets the platform name the typo.
1948
+ */
1949
+ filter?: Record<string, string | number | boolean>;
1950
+ }
1951
+ interface OwClientConfig {
1952
+ /** ow_w_ / ow_r_ / ow_a_ prefixed key, or grandfathered 10-digit legacy key. */
1953
+ apiKey: string;
1954
+ /**
1955
+ * Optional. Required for writes when the world has a PIN, and for legacy-key
1956
+ * reads of private worlds. Prefixed keys read PIN-less. String, not number --
1957
+ * '0123' !== 123.
1958
+ */
1959
+ apiPin?: string;
1960
+ /** Default: https://www.onlyworlds.com/api/v2 */
1961
+ baseUrl?: string;
1962
+ /**
1963
+ * Page size for element lists. Default 100 (server default; max 1000).
1964
+ * Deliberately visible in config: page size is a citizenship property.
1965
+ */
1966
+ pageSize?: number;
1967
+ /**
1968
+ * Page size for /changes pulls. Default 100. Live precedents: Obsidian 100,
1969
+ * Atlas 250, MCP 25. /changes is the platform's heaviest route -- be polite.
1970
+ */
1971
+ changesPageSize?: number;
1972
+ /** Injectable for tests / fake-keel harnesses. Defaults to globalThis.fetch. */
1973
+ fetch?: typeof globalThis.fetch;
1974
+ }
1975
+
1976
+ /**
1977
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
1978
+ * wire-corrected against live staging fixtures 2026-07-18.
1979
+ *
1980
+ * keel error envelope handling. The error contract is part of the contract:
1981
+ * envelopes carry a machine `code` and a `doc_url` fragment anchored at
1982
+ * onlyworlds.github.io/api/errors -- surface both, always. The live wire
1983
+ * envelope also carries `type` and `param` (fixtures P4a/P2c); both are
1984
+ * surfaced on the thrown error.
1985
+ */
1986
+ /** Auth codes are distinguishable by design; client recovery UX differs per code. */
1987
+ type OwAuthErrorCode = 'invalid_credentials' | 'key_revoked' | 'world_gone';
1988
+ declare class OwApiError extends Error {
1989
+ readonly status: number;
1990
+ /** Machine error code from the keel envelope, e.g. 'invalid_credentials'. */
1991
+ readonly code: string | null;
1992
+ /** Error family from the envelope, e.g. 'invalid_request', 'not_found'. */
1993
+ readonly type: string | null;
1994
+ /** Offending field/param named by the envelope (422/400), else null. */
1995
+ readonly param: string | null;
1996
+ /** Documentation link from the envelope -- show it to users/logs verbatim. */
1997
+ readonly docUrl: string | null;
1998
+ /** Raw parsed envelope (or body text when the body wasn't JSON). */
1999
+ readonly detail: unknown;
2000
+ constructor(status: number, code: string | null, message: string, docUrl: string | null, detail: unknown, type?: string | null, param?: string | null);
2001
+ get isAuthError(): boolean;
2002
+ /** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
2003
+ get isValidationError(): boolean;
2004
+ /** Same Idempotency-Key replayed with a different payload. */
2005
+ get isIdempotencyConflict(): boolean;
2006
+ }
2007
+ /** Network-level failure (fetch rejected) -- no envelope to parse. */
2008
+ declare class OwNetworkError extends Error {
2009
+ readonly cause2: unknown;
2010
+ constructor(message: string, cause: unknown);
2011
+ }
2012
+ /** Parse a wire envelope into OwApiError parts (exported for the error type-tests). */
2013
+ /** Parse the platform ERROR envelope into an OwApiError. (Renamed from parseEnvelope in 4.0 —
2014
+ * distinct from the world-export envelope, which is a different artifact entirely.) */
2015
+ declare function parseErrorEnvelope(status: number, body: unknown): OwApiError;
2016
+ /** Build an OwApiError from a non-2xx response, tolerating non-JSON bodies. */
2017
+ declare function errorFromResponse(res: Response): Promise<OwApiError>;
2018
+
2019
+ /**
2020
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
2021
+ * wire-corrected against live staging fixtures 2026-07-18.
2022
+ *
2023
+ * OnlyWorlds key-kind detection. Prefixes make leaked keys grep-scannable
2024
+ * (Stripe/GitHub precedent) -- and tell a client what auth shape to expect.
2025
+ */
2026
+ type OwKeyKind =
2027
+ /** ow_w_ -- world key, read + write. Writes need the world's PIN if it has one. */
2028
+ 'write'
2029
+ /** ow_r_ -- world key, read-only, works bare (no PIN). The share-with-players primitive. */
2030
+ | 'read'
2031
+ /** ow_a_ -- account Bearer token for /account/* routes; can mint world keys. */
2032
+ | 'account'
2033
+ /** Grandfathered 10-digit key. Needs PIN to read private worlds. */
2034
+ | 'legacy' | 'unknown';
2035
+ /** Demo range 0000000000-0000000009: read-only aliases, safe as live read gates. */
2036
+ declare function isDemoKey(key: string): boolean;
2037
+ declare function detectKeyKind(key: string): OwKeyKind;
2038
+ /** Can this key kind ever perform world writes? (PIN is a separate, per-world question.) */
2039
+ declare function kindCanWrite(kind: OwKeyKind): boolean;
2040
+ /**
2041
+ * Should a credential UI ask for a PIN with this key?
2042
+ * Prefixed keys read PIN-less; legacy keys may need it; writes on pinned
2043
+ * worlds always need it. 'optional' means: show the field, don't require it.
2044
+ */
2045
+ declare function pinExpectation(kind: OwKeyKind): 'never' | 'optional' | 'required-for-private-reads';
2046
+
2047
+ /**
2048
+ * keel v2 engine absorbed from Assembly's ow-v2-client v0.9.0 (Kael),
2049
+ * wire-corrected against live staging fixtures 2026-07-18.
2050
+ *
2051
+ * OwV2Client -- thin typed fetch client for the keel v2 API.
2052
+ *
2053
+ * Deliberately thin: no caching, no sync state, no retry policy -- those belong
2054
+ * to callers (sync engines, tools, games). What IS encoded here is the wire
2055
+ * contract and its safety rails: payload read-only-field stripping, opaque
2056
+ * cursors, idempotency headers, doc_url-bearing errors, polite page sizes, and
2057
+ * client-side UUID minting so idempotent retries are structurally safe.
2058
+ */
2059
+
2060
+ declare class OwV2Client {
2061
+ readonly baseUrl: string;
2062
+ readonly keyKind: OwKeyKind;
2063
+ readonly pageSize: number;
2064
+ readonly changesPageSize: number;
2065
+ private readonly apiKey;
2066
+ private readonly apiPin;
2067
+ private readonly fetchImpl;
2068
+ constructor(config: OwClientConfig);
2069
+ /** GET /health -- unauthenticated liveness pulse. */
2070
+ health(): Promise<unknown>;
2071
+ /**
2072
+ * GET /world -- world meta (name, calendar/time fields, public_read).
2073
+ * GOTCHA (by server design): world-meta edits do NOT appear in /changes and
2074
+ * do not bump change_seq. Poll getWorld().updated_at for meta freshness.
2075
+ */
2076
+ getWorld(): Promise<OwWorldMeta>;
2077
+ /** PATCH /world -- partial world-meta update. */
2078
+ patchWorld(partial: Record<string, unknown>): Promise<OwWorldMeta>;
2079
+ /** GET /{type}/ -- one cursor page. */
2080
+ list(type: ElementType | string, params?: ListParams): Promise<OwPage>;
2081
+ /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
2082
+ listAll(type: ElementType | string, params?: Omit<ListParams, 'cursor'>): AsyncGenerator<OwElement>;
2083
+ /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
2084
+ get(type: ElementType | string, id: string, opts?: Pick<ListParams, 'expand' | 'fields'>): Promise<OwElement>;
2085
+ /**
2086
+ * POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
2087
+ * caller omits one (design ruling D29d) so a retry carrying the same
2088
+ * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
2089
+ */
2090
+ create(type: ElementType | string, element: OwElement | Record<string, unknown>, opts?: {
2091
+ idempotencyKey?: string;
2092
+ }): Promise<OwElement>;
2093
+ /** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
2094
+ upsert(type: ElementType | string, id: string, element: OwElement | Record<string, unknown>): Promise<OwElement>;
2095
+ /**
2096
+ * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
2097
+ * replace wholesale, omitted fields stay untouched. For link arrays prefer
2098
+ * editLinks() -- atomic server-side merge, no read-before-write.
2099
+ */
2100
+ patch(type: ElementType | string, id: string, partial: Record<string, unknown>): Promise<OwElement>;
2101
+ /**
2102
+ * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
2103
+ * tombstone AND scrubs the id from every other element's links in the same
2104
+ * transaction -- no client-side unlink pass needed, ever.
2105
+ */
2106
+ delete(type: ElementType | string, id: string): Promise<void>;
2107
+ /**
2108
+ * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
2109
+ * Dedupes, tolerates already-present/already-absent ids. Returns the FULL
2110
+ * updated element (fixture P5). Use this for all relationship editing; it
2111
+ * retires the read-merge-PATCH dance.
2112
+ */
2113
+ editLinks(type: ElementType | string, id: string, field: string, edit: OwLinkEdit): Promise<OwElement>;
2114
+ /**
2115
+ * POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
2116
+ * always; inspect per-slot numeric `status` + top-level `errors` flag);
2117
+ * atomic:true for all-or-nothing. Link validation runs against batch U
2118
+ * database -- send in any order, cycles included; no client topo-sort.
2119
+ * Success slots echo server-authoritative timestamps: set your sync baseline
2120
+ * from this response alone. When an idempotencyKey is replayed, the returned
2121
+ * response carries wasReplay:true (read from the Idempotent-Replay header).
2122
+ */
2123
+ bulk(items: OwBulkItem[], opts?: {
2124
+ atomic?: boolean;
2125
+ idempotencyKey?: string;
2126
+ }): Promise<OwBulkResponse>;
2127
+ /**
2128
+ * GET /changes -- one page of the world's ordered change feed.
2129
+ * Cursor is OPAQUE and never expires: persist verbatim, never parse.
2130
+ * Zero/absent cursor = full export (byte-aligned with the Folder Format).
2131
+ * Rewind rule: if your persisted position is ahead of page.head, the server
2132
+ * was restored -- re-baseline from cursor zero; do not assume caught-up.
2133
+ * Citizenship: heaviest route on the platform; default page size is polite.
2134
+ */
2135
+ changes(opts?: {
2136
+ since?: string;
2137
+ limit?: number;
2138
+ }): Promise<OwChangesPage>;
2139
+ /**
2140
+ * Walk the feed from `since` (or from zero = full export) to the current
2141
+ * tail, yielding ops in order. Returns the final cursor via the generator's
2142
+ * return value; persist it for the next incremental pull.
2143
+ */
2144
+ changesAll(since?: string): AsyncGenerator<OwChange, {
2145
+ cursor: string;
2146
+ head: number;
2147
+ }>;
2148
+ /**
2149
+ * Raw authenticated request against this client's baseUrl. Public since 4.0
2150
+ * so auxiliary resources can ride the same transport — it structurally
2151
+ * satisfies `TokenTransport` (`new TokenResource(client)`). Prefer the typed
2152
+ * methods for element CRUD; this is the escape hatch, and it does NOT apply
2153
+ * sanitizePayload — callers own their body shape.
2154
+ */
2155
+ request<T = unknown>(method: string, path: string, opts?: {
2156
+ query?: string;
2157
+ body?: unknown;
2158
+ idempotencyKey?: string;
2159
+ auth?: boolean;
2160
+ allowEmpty?: boolean;
2161
+ replayAware?: boolean;
2162
+ }): Promise<T>;
2163
+ }
2164
+
2165
+ /**
2166
+ * Canonical element colour palette — four semantic families.
2167
+ *
2168
+ * Ruled by Captain 2026-07-22 after Skeld's measurement pass (Orrery
2169
+ * `product/schema/element-palette-measurements.md`): 22 mutually-separable
2170
+ * hues is structurally impossible; four families is the ceiling that passes
2171
+ * all-pairs CVD separation in both modes. **Colour carries the FAMILY; the
2172
+ * icon (`ELEMENT_ICONS`) carries the TYPE.** Dark-mode pairs land in the 6–8
2173
+ * CVD floor band, so secondary encoding (icon + label) is REQUIRED alongside
2174
+ * colour, not optional.
2175
+ *
2176
+ * The type→family map is GENERATED: `ELEMENT_FAMILIES` is emitted by
2177
+ * `codegen/generate_types.py` from the `family:` key in keel's schema YAML
2178
+ * (added 2026-07-23, keel c69366b) — a keel PRESENTATION-WRAPPER key
2179
+ * (first-party rendering metadata, not part of the council-governed
2180
+ * OnlyWorlds standard). It cannot drift from the schema; membership and hex
2181
+ * invariants stay test-gated in `test/palette.test.mjs`.
2182
+ *
2183
+ * The hexes below are design constants, hand-authored beside the generated
2184
+ * map. Do not change any value without re-running the CVD validation (every
2185
+ * brighter World green collides with Temporal amber for protan viewers — the
2186
+ * green is pinned BY the accessibility budget).
2187
+ *
2188
+ * Provenance: first proven live in atlas (`src/core/element-colors.ts`) and
2189
+ * council (`src/cosmos/element-families.ts`) — both become re-exports of this
2190
+ * module.
2191
+ */
2192
+
2193
+ /**
2194
+ * Family → validated hex per surface mode. `light` assumes near-white
2195
+ * surfaces, `dark` assumes near-black (measured against #0a0a0a).
2196
+ * World green is identical in both modes and sits at its low-contrast end
2197
+ * deliberately — see module header before "fixing" it.
2198
+ */
2199
+ declare const FAMILY_COLORS: Record<ElementFamily, {
2200
+ light: string;
2201
+ dark: string;
2202
+ }>;
2203
+ /** Semantic family for an element type slug. */
2204
+ declare function familyOf(type: ElementType): ElementFamily;
2205
+ /**
2206
+ * The convenience most callers want: canonical colour for an element type.
2207
+ * Name matches the live atlas/council implementations so their SDK swap is a
2208
+ * re-export, not a rename. Defaults to `dark` (both current consumers are
2209
+ * dark-surface).
2210
+ */
2211
+ declare function elementColor(type: ElementType, mode?: 'light' | 'dark'): string;
2212
+ /** All four families, in ruling order (the order IS the CVD-safety mechanism of the source palette). */
2213
+ declare const FAMILY_ORDER: readonly ElementFamily[];
2214
+
2215
+ /**
2216
+ * Current OnlyWorlds version
2217
+ * Synced with https://github.com/OnlyWorlds/OnlyWorlds/blob/main/VERSION
2218
+ */
2219
+ declare const ELEMENT_LABELS: Record<ElementType, string>;
2226
2220
  /**
2227
2221
  * Get Material Design icon name for an element type
2228
2222
  * Accepts multiple formats: 'character', 'characters', 'Character', etc.
package/dist/index.js CHANGED
@@ -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.0.1",
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
+ }