@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 +36 -0
- package/README.md +4 -1
- package/dist/index.d.ts +399 -405
- package/dist/index.js +46 -49
- package/package.json +63 -61
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
|
-
|
|
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
|
|
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:
|
|
34
|
-
* (first-party rendering
|
|
35
|
-
*
|
|
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:
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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: "
|
|
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: "
|
|
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
|
-
//
|
|
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.
|
|
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
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|