@onlyworlds/sdk 3.0.0 → 4.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,112 +1,373 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
1
+ // src/v2/errors.ts
2
+ var OwApiError = class extends Error {
3
+ constructor(status, code, message, docUrl, detail, type = null, param = null) {
4
+ super(message);
5
+ this.name = "OwApiError";
6
+ this.status = status;
7
+ this.code = code;
8
+ this.type = type;
9
+ this.param = param;
10
+ this.docUrl = docUrl;
11
+ this.detail = detail;
12
+ }
13
+ get isAuthError() {
14
+ return this.code === "invalid_credentials" || this.code === "key_revoked" || this.code === "world_gone";
15
+ }
16
+ /** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
17
+ get isValidationError() {
18
+ return this.status === 422 || this.status === 400;
19
+ }
20
+ /** Same Idempotency-Key replayed with a different payload. */
21
+ get isIdempotencyConflict() {
22
+ return this.status === 409;
23
+ }
9
24
  };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ var OwNetworkError = class extends Error {
26
+ constructor(message, cause) {
27
+ super(message);
28
+ this.name = "OwNetworkError";
29
+ this.cause2 = cause;
15
30
  }
16
- return to;
17
31
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+ function parseEnvelope(status, body) {
33
+ const env = body && typeof body === "object" ? body : {};
34
+ const nested = typeof env.error === "object" && env.error !== null ? env.error : void 0;
35
+ const code = env.code ?? nested?.code ?? (typeof env.error === "string" ? env.error : null) ?? null;
36
+ const type = env.type ?? nested?.type ?? null;
37
+ const param = env.param ?? nested?.param ?? null;
38
+ const docUrl = env.doc_url ?? nested?.doc_url ?? null;
39
+ const message = env.message ?? nested?.message ?? (typeof env.detail === "string" ? env.detail : void 0) ?? `OnlyWorlds API error ${status}${code ? ` (${code})` : ""}`;
40
+ return new OwApiError(status, code, message, docUrl, body, type, param);
41
+ }
42
+ async function errorFromResponse(res) {
43
+ let body = null;
44
+ let text = "";
45
+ try {
46
+ text = await res.text();
47
+ body = text ? JSON.parse(text) : null;
48
+ } catch {
49
+ body = text || null;
50
+ }
51
+ return parseEnvelope(res.status, body);
52
+ }
19
53
 
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- ELEMENT_ICONS: () => ELEMENT_ICONS,
24
- ELEMENT_LABELS: () => ELEMENT_LABELS,
25
- ELEMENT_SECTIONS: () => ELEMENT_SECTIONS,
26
- ELEMENT_TYPES: () => ELEMENT_TYPES,
27
- ElementType: () => ElementType,
28
- FIELD_SCHEMA: () => FIELD_SCHEMA,
29
- GameTier: () => GameTier,
30
- ONLYWORLDS_VERSION: () => ONLYWORLDS_VERSION,
31
- OnlyWorldsClient: () => OnlyWorldsClient,
32
- OwApiError: () => OwApiError,
33
- OwNetworkError: () => OwNetworkError,
34
- OwV2Client: () => OwV2Client,
35
- SPATIAL_TYPES: () => SPATIAL_TYPES,
36
- createAnyElementId: () => createAnyElementId,
37
- createElementId: () => createElementId,
38
- createElementIds: () => createElementIds,
39
- detectKeyKind: () => detectKeyKind,
40
- errorFromResponse: () => errorFromResponse,
41
- getElementIcon: () => getElementIcon,
42
- getElementLabel: () => getElementLabel,
43
- getElementSections: () => getElementSections,
44
- isDemoKey: () => isDemoKey,
45
- kindCanWrite: () => kindCanWrite,
46
- parseEnvelope: () => parseEnvelope,
47
- pinExpectation: () => pinExpectation
48
- });
49
- module.exports = __toCommonJS(index_exports);
54
+ // src/v2/keys.ts
55
+ var LEGACY_RE = /^[0-9]{10}$/;
56
+ function isDemoKey(key) {
57
+ return LEGACY_RE.test(key) && key >= "0000000000" && key <= "0000000009";
58
+ }
59
+ function detectKeyKind(key) {
60
+ if (key.startsWith("ow_w_")) return "write";
61
+ if (key.startsWith("ow_r_")) return "read";
62
+ if (key.startsWith("ow_a_")) return "account";
63
+ if (LEGACY_RE.test(key)) return "legacy";
64
+ return "unknown";
65
+ }
66
+ function kindCanWrite(kind) {
67
+ return kind === "write" || kind === "legacy";
68
+ }
69
+ function pinExpectation(kind) {
70
+ switch (kind) {
71
+ case "read":
72
+ return "never";
73
+ case "account":
74
+ return "never";
75
+ case "write":
76
+ return "optional";
77
+ case "legacy":
78
+ return "required-for-private-reads";
79
+ default:
80
+ return "optional";
81
+ }
82
+ }
50
83
 
51
- // src/types.ts
52
- var ElementType = /* @__PURE__ */ ((ElementType2) => {
53
- ElementType2["Ability"] = "ability";
54
- ElementType2["Character"] = "character";
55
- ElementType2["Collective"] = "collective";
56
- ElementType2["Construct"] = "construct";
57
- ElementType2["Creature"] = "creature";
58
- ElementType2["Event"] = "event";
59
- ElementType2["Family"] = "family";
60
- ElementType2["Institution"] = "institution";
61
- ElementType2["Language"] = "language";
62
- ElementType2["Law"] = "law";
63
- ElementType2["Location"] = "location";
64
- ElementType2["Map"] = "map";
65
- ElementType2["Marker"] = "marker";
66
- ElementType2["Narrative"] = "narrative";
67
- ElementType2["Object"] = "object";
68
- ElementType2["Phenomenon"] = "phenomenon";
69
- ElementType2["Pin"] = "pin";
70
- ElementType2["Relation"] = "relation";
71
- ElementType2["Species"] = "species";
72
- ElementType2["Title"] = "title";
73
- ElementType2["Trait"] = "trait";
74
- ElementType2["Zone"] = "zone";
75
- return ElementType2;
76
- })(ElementType || {});
77
- var ELEMENT_LABELS = {
78
- ["ability" /* Ability */]: "Abilities",
79
- ["character" /* Character */]: "Characters",
80
- ["collective" /* Collective */]: "Collectives",
81
- ["construct" /* Construct */]: "Constructs",
82
- ["creature" /* Creature */]: "Creatures",
83
- ["event" /* Event */]: "Events",
84
- ["family" /* Family */]: "Families",
85
- ["institution" /* Institution */]: "Institutions",
86
- ["language" /* Language */]: "Languages",
87
- ["law" /* Law */]: "Laws",
88
- ["location" /* Location */]: "Locations",
89
- ["map" /* Map */]: "Maps",
90
- ["marker" /* Marker */]: "Markers",
91
- ["narrative" /* Narrative */]: "Narratives",
92
- ["object" /* Object */]: "Objects",
93
- ["phenomenon" /* Phenomenon */]: "Phenomena",
94
- ["pin" /* Pin */]: "Pins",
95
- ["relation" /* Relation */]: "Relations",
96
- ["species" /* Species */]: "Species",
97
- ["title" /* Title */]: "Titles",
98
- ["trait" /* Trait */]: "Traits",
99
- ["zone" /* Zone */]: "Zones"
84
+ // src/v2/client.ts
85
+ var DEFAULT_BASE_URL = "https://www.onlyworlds.com/api/v2";
86
+ var DEFAULT_PAGE_SIZE = 100;
87
+ var DEFAULT_CHANGES_PAGE_SIZE = 100;
88
+ var OwV2Client = class {
89
+ constructor(config) {
90
+ if (!config.apiKey) throw new Error("OwV2Client: apiKey is required");
91
+ this.apiKey = config.apiKey;
92
+ this.apiPin = config.apiPin || void 0;
93
+ this.keyKind = detectKeyKind(config.apiKey);
94
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
95
+ this.pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
96
+ this.changesPageSize = config.changesPageSize ?? DEFAULT_CHANGES_PAGE_SIZE;
97
+ this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
98
+ if (typeof this.fetchImpl !== "function") {
99
+ throw new Error("OwV2Client: no fetch available -- supply config.fetch");
100
+ }
101
+ }
102
+ // -- Health & world meta -------------------------------------------------
103
+ /** GET /health -- unauthenticated liveness pulse. */
104
+ async health() {
105
+ return this.request("GET", "/health/", { auth: false });
106
+ }
107
+ /**
108
+ * GET /world -- world meta (name, calendar/time fields, public_read).
109
+ * GOTCHA (by server design): world-meta edits do NOT appear in /changes and
110
+ * do not bump change_seq. Poll getWorld().updated_at for meta freshness.
111
+ */
112
+ async getWorld() {
113
+ return this.request("GET", "/world/");
114
+ }
115
+ /** PATCH /world -- partial world-meta update. */
116
+ async patchWorld(partial) {
117
+ return this.request("PATCH", "/world/", { body: sanitizePayload(partial) });
118
+ }
119
+ // -- Element CRUD --------------------------------------------------------
120
+ /** GET /{type}/ -- one cursor page. */
121
+ async list(type, params = {}) {
122
+ const query = buildQuery({
123
+ limit: params.limit ?? this.pageSize,
124
+ cursor: params.cursor,
125
+ expand: params.expand?.join(","),
126
+ fields: params.fields?.join(","),
127
+ ...params.filter
128
+ });
129
+ return this.request("GET", `/${type}/`, { query });
130
+ }
131
+ /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
132
+ async *listAll(type, params = {}) {
133
+ let cursor;
134
+ do {
135
+ const page = await this.list(type, { ...params, cursor });
136
+ for (const el of page.data) yield el;
137
+ cursor = page.has_more && page.next_cursor ? page.next_cursor : void 0;
138
+ } while (cursor);
139
+ }
140
+ /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
141
+ async get(type, id, opts = {}) {
142
+ const query = buildQuery({ expand: opts.expand?.join(","), fields: opts.fields?.join(",") });
143
+ return this.request("GET", `/${type}/${id}/`, { query });
144
+ }
145
+ /**
146
+ * POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
147
+ * caller omits one (design ruling D29d) so a retry carrying the same
148
+ * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
149
+ */
150
+ async create(type, element, opts = {}) {
151
+ const body = sanitizePayload(element);
152
+ if (body.id === void 0 || body.id === null || body.id === "") {
153
+ body.id = mintUuid();
154
+ }
155
+ return this.request("POST", `/${type}/`, {
156
+ body,
157
+ idempotencyKey: opts.idempotencyKey
158
+ });
159
+ }
160
+ /** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
161
+ async upsert(type, id, element) {
162
+ return this.request("PUT", `/${type}/${id}/`, { body: sanitizePayload(element) });
163
+ }
164
+ /**
165
+ * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
166
+ * replace wholesale, omitted fields stay untouched. For link arrays prefer
167
+ * editLinks() -- atomic server-side merge, no read-before-write.
168
+ */
169
+ async patch(type, id, partial) {
170
+ return this.request("PATCH", `/${type}/${id}/`, { body: sanitizePayload(partial) });
171
+ }
172
+ /**
173
+ * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
174
+ * tombstone AND scrubs the id from every other element's links in the same
175
+ * transaction -- no client-side unlink pass needed, ever.
176
+ */
177
+ async delete(type, id) {
178
+ await this.request("DELETE", `/${type}/${id}/`, { allowEmpty: true });
179
+ }
180
+ /**
181
+ * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
182
+ * Dedupes, tolerates already-present/already-absent ids. Returns the FULL
183
+ * updated element (fixture P5). Use this for all relationship editing; it
184
+ * retires the read-merge-PATCH dance.
185
+ */
186
+ async editLinks(type, id, field, edit) {
187
+ return this.request("POST", `/${type}/${id}/links/${field}`, { body: edit });
188
+ }
189
+ // -- Bulk ----------------------------------------------------------------
190
+ /**
191
+ * POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
192
+ * always; inspect per-slot numeric `status` + top-level `errors` flag);
193
+ * atomic:true for all-or-nothing. Link validation runs against batch U
194
+ * database -- send in any order, cycles included; no client topo-sort.
195
+ * Success slots echo server-authoritative timestamps: set your sync baseline
196
+ * from this response alone. When an idempotencyKey is replayed, the returned
197
+ * response carries wasReplay:true (read from the Idempotent-Replay header).
198
+ */
199
+ async bulk(items, opts = {}) {
200
+ const body = {
201
+ items: items.map((it) => ({ type: it.type, element: sanitizePayload(it.element) })),
202
+ atomic: opts.atomic ?? false
203
+ };
204
+ return this.request("POST", "/bulk/", {
205
+ body,
206
+ idempotencyKey: opts.idempotencyKey,
207
+ replayAware: true
208
+ });
209
+ }
210
+ // -- Changes feed --------------------------------------------------------
211
+ /**
212
+ * GET /changes -- one page of the world's ordered change feed.
213
+ * Cursor is OPAQUE and never expires: persist verbatim, never parse.
214
+ * Zero/absent cursor = full export (byte-aligned with the Folder Format).
215
+ * Rewind rule: if your persisted position is ahead of page.head, the server
216
+ * was restored -- re-baseline from cursor zero; do not assume caught-up.
217
+ * Citizenship: heaviest route on the platform; default page size is polite.
218
+ */
219
+ async changes(opts = {}) {
220
+ const query = buildQuery({ since: opts.since, limit: opts.limit ?? this.changesPageSize });
221
+ return this.request("GET", "/changes/", { query });
222
+ }
223
+ /**
224
+ * Walk the feed from `since` (or from zero = full export) to the current
225
+ * tail, yielding ops in order. Returns the final cursor via the generator's
226
+ * return value; persist it for the next incremental pull.
227
+ */
228
+ async *changesAll(since) {
229
+ let cursor = since;
230
+ let page;
231
+ do {
232
+ page = await this.changes({ since: cursor });
233
+ for (const op of page.changes) yield op;
234
+ cursor = page.cursor;
235
+ } while (page.has_more);
236
+ return { cursor: page.cursor, head: page.head };
237
+ }
238
+ // -- Core request machinery ----------------------------------------------
239
+ /**
240
+ * Raw authenticated request against this client's baseUrl. Public since 4.0
241
+ * so auxiliary resources can ride the same transport — it structurally
242
+ * satisfies `TokenTransport` (`new TokenResource(client)`). Prefer the typed
243
+ * methods for element CRUD; this is the escape hatch, and it does NOT apply
244
+ * sanitizePayload — callers own their body shape.
245
+ */
246
+ async request(method, path, opts = {}) {
247
+ const url = `${this.baseUrl}${path}${opts.query ? `?${opts.query}` : ""}`;
248
+ const headers = {};
249
+ if (opts.auth !== false) {
250
+ if (this.keyKind === "account") {
251
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
252
+ } else {
253
+ headers["API-Key"] = this.apiKey;
254
+ if (this.apiPin) headers["API-Pin"] = this.apiPin;
255
+ }
256
+ }
257
+ if (opts.body !== void 0) headers["Content-Type"] = "application/json";
258
+ if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
259
+ let res;
260
+ try {
261
+ res = await this.fetchImpl(url, {
262
+ method,
263
+ headers,
264
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
265
+ });
266
+ } catch (cause) {
267
+ throw new OwNetworkError(`OnlyWorlds request failed: ${method} ${path}`, cause);
268
+ }
269
+ if (!res.ok) throw await errorFromResponse(res);
270
+ if (res.status === 204 || opts.allowEmpty) {
271
+ const text = await res.text();
272
+ return text ? JSON.parse(text) : null;
273
+ }
274
+ const parsed = await res.json();
275
+ if (opts.replayAware && parsed && typeof parsed === "object") {
276
+ parsed.wasReplay = readReplayHeader(res.headers);
277
+ }
278
+ return parsed;
279
+ }
100
280
  };
101
- function getElementLabel(elementType) {
102
- return ELEMENT_LABELS[elementType];
281
+ var READ_ONLY_FIELDS = ["world", "type", "created_at", "updated_at", "change_seq"];
282
+ function sanitizePayload(payload) {
283
+ if (!payload || typeof payload !== "object") return payload;
284
+ const rest = { ...payload };
285
+ for (const field of READ_ONLY_FIELDS) delete rest[field];
286
+ return rest;
103
287
  }
288
+ function readReplayHeader(headers) {
289
+ const v = headers.get("Idempotent-Replay");
290
+ return v != null && v.toLowerCase() === "true";
291
+ }
292
+ function mintUuid() {
293
+ const c = globalThis.crypto;
294
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
295
+ const bytes = new Uint8Array(16);
296
+ if (c && typeof c.getRandomValues === "function") {
297
+ c.getRandomValues(bytes);
298
+ } else {
299
+ for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
300
+ }
301
+ bytes[6] = bytes[6] & 15 | 64;
302
+ bytes[8] = bytes[8] & 63 | 128;
303
+ const hex = [];
304
+ for (let i = 0; i < 256; i++) hex.push((i + 256).toString(16).slice(1));
305
+ return hex[bytes[0]] + hex[bytes[1]] + hex[bytes[2]] + hex[bytes[3]] + "-" + hex[bytes[4]] + hex[bytes[5]] + "-" + hex[bytes[6]] + hex[bytes[7]] + "-" + hex[bytes[8]] + hex[bytes[9]] + "-" + hex[bytes[10]] + hex[bytes[11]] + hex[bytes[12]] + hex[bytes[13]] + hex[bytes[14]] + hex[bytes[15]];
306
+ }
307
+ function buildQuery(params) {
308
+ const q = new URLSearchParams();
309
+ for (const [k, v] of Object.entries(params)) {
310
+ if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
311
+ }
312
+ return q.toString();
313
+ }
314
+
315
+ // src/v2/types.generated.ts
316
+ var ELEMENT_TYPES = ["ability", "character", "collective", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"];
317
+ var ELEMENT_FAMILIES = {
318
+ ability: "abstract",
319
+ character: "agents",
320
+ collective: "agents",
321
+ construct: "world",
322
+ creature: "agents",
323
+ event: "temporal",
324
+ family: "agents",
325
+ institution: "agents",
326
+ language: "abstract",
327
+ law: "abstract",
328
+ location: "world",
329
+ map: "world",
330
+ marker: "world",
331
+ narrative: "temporal",
332
+ object: "world",
333
+ phenomenon: "temporal",
334
+ pin: "world",
335
+ relation: "temporal",
336
+ species: "agents",
337
+ title: "abstract",
338
+ trait: "abstract",
339
+ zone: "world"
340
+ };
341
+ var ELEMENT_ICONS = {
342
+ ability: "auto_fix_normal",
343
+ character: "person",
344
+ collective: "groups_3",
345
+ construct: "api",
346
+ creature: "bug_report",
347
+ event: "saved_search",
348
+ family: "supervisor_account",
349
+ institution: "business",
350
+ language: "edit_road",
351
+ law: "gpp_bad",
352
+ location: "castle",
353
+ map: "map",
354
+ marker: "place",
355
+ narrative: "menu_book",
356
+ object: "webhook",
357
+ phenomenon: "thunderstorm",
358
+ pin: "push_pin",
359
+ relation: "link",
360
+ species: "crib",
361
+ title: "military_tech",
362
+ trait: "flaky",
363
+ zone: "architecture"
364
+ };
104
365
  var ELEMENT_SECTIONS = {
105
- ["ability" /* Ability */]: [
366
+ ability: [
106
367
  { name: "Mechanics", order: 1, fields: ["activation", "duration", "potency", "range", "effects", "challenges", "talents", "requisites"] },
107
368
  { name: "World", order: 2, fields: ["prevalence", "tradition", "source", "locus", "instruments", "systems"] }
108
369
  ],
109
- ["character" /* Character */]: [
370
+ character: [
110
371
  { name: "Constitution", order: 1, fields: ["physicality", "mentality", "height", "weight", "species", "traits", "abilities"] },
111
372
  { name: "Origins", order: 2, fields: ["background", "motivations", "birth_date", "birthplace", "languages"] },
112
373
  { name: "World", order: 3, fields: ["reputation", "location", "objects", "institutions"] },
@@ -114,43 +375,43 @@ var ELEMENT_SECTIONS = {
114
375
  { name: "Social", order: 5, fields: ["family", "friends", "rivals"] },
115
376
  { name: "TTRPG", order: 6, fields: ["level", "hit_points", "STR", "DEX", "CON", "INT", "WIS", "CHA"] }
116
377
  ],
117
- ["collective" /* Collective */]: [
378
+ collective: [
118
379
  { name: "Formation", order: 1, fields: ["composition", "count", "formation_date", "operator", "equipment"] },
119
380
  { name: "Dynamics", order: 2, fields: ["activity", "disposition", "state", "abilities", "symbolism"] },
120
381
  { name: "World", order: 3, fields: ["species", "characters", "creatures", "phenomena"] }
121
382
  ],
122
- ["construct" /* Construct */]: [
383
+ construct: [
123
384
  { name: "Nature", order: 1, fields: ["rationale", "history", "status", "reach", "start_date", "end_date", "founder", "custodian"] },
124
385
  { name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"] }
125
386
  ],
126
- ["creature" /* Creature */]: [
387
+ creature: [
127
388
  { name: "Biology", order: 1, fields: ["appearance", "weight", "height", "species"] },
128
- { name: "Behaviour", order: 2, fields: ["habits", "demeanor", "traits", "abilities", "languages"] },
389
+ { name: "Behavior", order: 2, fields: ["habits", "demeanor", "traits", "abilities", "languages"] },
129
390
  { name: "World", order: 3, fields: ["status", "birth_date", "location", "zone"] },
130
391
  { name: "TTRPG", order: 4, fields: ["challenge_rating", "hit_points", "armor_class", "speed", "actions"] }
131
392
  ],
132
- ["event" /* Event */]: [
393
+ event: [
133
394
  { name: "Nature", order: 1, fields: ["history", "challenges", "consequences", "start_date", "end_date", "triggers"] },
134
395
  { name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs"] }
135
396
  ],
136
- ["family" /* Family */]: [
397
+ family: [
137
398
  { name: "Identity", order: 1, fields: ["spirit", "history", "traditions", "traits", "abilities", "languages", "ancestors"] },
138
399
  { name: "World", order: 2, fields: ["reputation", "estates", "governs", "heirlooms", "creatures"] }
139
400
  ],
140
- ["institution" /* Institution */]: [
401
+ institution: [
141
402
  { name: "Foundation", order: 1, fields: ["doctrine", "founding_date", "parent_institution"] },
142
403
  { name: "Claims", order: 2, fields: ["zones", "objects", "creatures"] },
143
404
  { name: "World", order: 3, fields: ["status", "allies", "adversaries", "constructs"] }
144
405
  ],
145
- ["language" /* Language */]: [
406
+ language: [
146
407
  { name: "Structure", order: 1, fields: ["phonology", "grammar", "lexicon", "writing", "classification"] },
147
408
  { name: "World", order: 2, fields: ["status", "spread", "dialects"] }
148
409
  ],
149
- ["law" /* Law */]: [
410
+ law: [
150
411
  { name: "Code", order: 1, fields: ["declaration", "purpose", "date", "parent_law", "penalties"] },
151
412
  { name: "World", order: 2, fields: ["author", "locations", "zones", "prohibitions", "adjudicators", "enforcers"] }
152
413
  ],
153
- ["location" /* Location */]: [
414
+ location: [
154
415
  { name: "Setting", order: 1, fields: ["form", "function", "founding_date", "parent_location", "populations"] },
155
416
  { name: "Politics", order: 2, fields: ["political_climate", "primary_power", "governing_title", "secondary_powers", "zone", "rival", "partner"] },
156
417
  { name: "World", order: 3, fields: ["customs", "founders", "cults", "delicacies"] },
@@ -159,78 +420,95 @@ var ELEMENT_SECTIONS = {
159
420
  { name: "Construction", order: 6, fields: ["architecture", "buildings", "building_methods"] },
160
421
  { name: "Defense", order: 7, fields: ["defensibility", "elevation", "fighters", "defensive_objects"] }
161
422
  ],
162
- ["map" /* Map */]: [
423
+ map: [
163
424
  { name: "Details", order: 1, fields: ["background_color", "hierarchy", "width", "height", "depth", "parent_map", "location"] }
164
425
  ],
165
- ["marker" /* Marker */]: [
426
+ marker: [
166
427
  { name: "Details", order: 1, fields: ["map", "zone", "x", "y", "z", "order"] }
167
428
  ],
168
- ["narrative" /* Narrative */]: [
429
+ narrative: [
169
430
  { name: "Context", order: 1, fields: ["story", "consequences", "start_date", "end_date", "order", "parent_narrative", "protagonist", "antagonist", "narrator", "conservator"] },
170
431
  { name: "Involves", order: 2, fields: ["events", "characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "laws"] }
171
432
  ],
172
- ["object" /* Object */]: [
433
+ object: [
173
434
  { name: "Form", order: 1, fields: ["aesthetics", "weight", "amount", "parent_object", "materials", "technology"] },
174
435
  { name: "Function", order: 2, fields: ["utility", "effects", "abilities", "consumes"] },
175
436
  { name: "World", order: 3, fields: ["origins", "location", "language", "affinities"] }
176
437
  ],
177
- ["phenomenon" /* Phenomenon */]: [
438
+ phenomenon: [
178
439
  { name: "Mechanics", order: 1, fields: ["expression", "effects", "duration", "catalysts", "empowerments"] },
179
440
  { name: "World", order: 2, fields: ["mythology", "system", "triggers", "wielders", "environments"] }
180
441
  ],
181
- ["pin" /* Pin */]: [
182
- { name: "Details", order: 1, fields: ["map", "element_type", "element_id", "element", "x", "y", "z"] }
442
+ pin: [
443
+ { name: "Details", order: 1, fields: ["map", "element", "x", "y", "z"] }
183
444
  ],
184
- ["relation" /* Relation */]: [
445
+ relation: [
185
446
  { name: "Nature", order: 1, fields: ["background", "start_date", "end_date", "intensity", "actor", "events"] },
186
- { name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "relations", "titles", "constructs", "events", "narratives"] }
447
+ { name: "Involves", order: 2, fields: ["characters", "objects", "locations", "species", "creatures", "institutions", "traits", "collectives", "zones", "abilities", "phenomena", "languages", "families", "titles", "constructs", "events", "narratives"] }
187
448
  ],
188
- ["species" /* Species */]: [
449
+ species: [
189
450
  { name: "Biology", order: 1, fields: ["appearance", "life_span", "weight", "nourishment", "reproduction", "adaptations"] },
190
451
  { name: "Psychology", order: 2, fields: ["instincts", "sociality", "temperament", "communication", "aggression", "traits"] },
191
452
  { name: "World", order: 3, fields: ["role", "parent_species", "locations", "zones", "affinities"] }
192
453
  ],
193
- ["title" /* Title */]: [
454
+ title: [
194
455
  { name: "Mandate", order: 1, fields: ["authority", "eligibility", "grant_date", "revoke_date", "issuer", "body", "superior_title", "holders", "symbols"] },
195
456
  { name: "World", order: 2, fields: ["status", "history", "characters", "institutions", "families", "zones", "locations", "objects", "constructs", "laws", "collectives", "creatures", "phenomena", "species", "languages"] }
196
457
  ],
197
- ["trait" /* Trait */]: [
458
+ trait: [
198
459
  { name: "Qualitative", order: 1, fields: ["social_effects", "physical_effects", "functional_effects", "personality_effects", "behaviour_effects"] },
199
460
  { name: "Quantitative", order: 2, fields: ["charisma", "coercion", "competence", "compassion", "creativity", "courage"] },
200
461
  { name: "World", order: 3, fields: ["significance", "anti_trait", "empowered_abilities"] }
201
462
  ],
202
- ["zone" /* Zone */]: [
463
+ zone: [
203
464
  { name: "Scope", order: 1, fields: ["role", "start_date", "end_date", "phenomena", "linked_zones"] },
204
465
  { name: "World", order: 2, fields: ["context", "populations", "titles", "principles"] }
205
466
  ]
206
467
  };
207
- function getElementSections(elementType) {
208
- return ELEMENT_SECTIONS[elementType] || [];
468
+
469
+ // src/v2/types.ts
470
+ var SPATIAL_TYPES = ["map", "pin", "marker", "zone"];
471
+
472
+ // src/v2/palette.ts
473
+ var FAMILY_COLORS = {
474
+ agents: { light: "#2a78d6", dark: "#3987e5" },
475
+ world: { light: "#008300", dark: "#008300" },
476
+ abstract: { light: "#e87ba4", dark: "#d55181" },
477
+ temporal: { light: "#eda100", dark: "#c98500" }
478
+ };
479
+ function familyOf(type) {
480
+ return ELEMENT_FAMILIES[type];
209
481
  }
482
+ function elementColor(type, mode = "dark") {
483
+ return FAMILY_COLORS[ELEMENT_FAMILIES[type]][mode];
484
+ }
485
+ var FAMILY_ORDER = ["agents", "world", "abstract", "temporal"];
486
+
487
+ // src/v2/constants.ts
210
488
  var ONLYWORLDS_VERSION = "00.30.00";
211
- var ELEMENT_ICONS = {
212
- ["ability" /* Ability */]: "auto_fix_normal",
213
- ["character" /* Character */]: "person",
214
- ["collective" /* Collective */]: "groups_3",
215
- ["construct" /* Construct */]: "api",
216
- ["creature" /* Creature */]: "bug_report",
217
- ["event" /* Event */]: "saved_search",
218
- ["family" /* Family */]: "supervisor_account",
219
- ["institution" /* Institution */]: "business",
220
- ["language" /* Language */]: "edit_road",
221
- ["law" /* Law */]: "gpp_bad",
222
- ["location" /* Location */]: "castle",
223
- ["map" /* Map */]: "map",
224
- ["marker" /* Marker */]: "place",
225
- ["narrative" /* Narrative */]: "menu_book",
226
- ["object" /* Object */]: "webhook",
227
- ["phenomenon" /* Phenomenon */]: "thunderstorm",
228
- ["pin" /* Pin */]: "push_pin",
229
- ["relation" /* Relation */]: "link",
230
- ["species" /* Species */]: "crib",
231
- ["title" /* Title */]: "military_tech",
232
- ["trait" /* Trait */]: "flaky",
233
- ["zone" /* Zone */]: "architecture"
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"
234
512
  };
235
513
  var FIELD_SCHEMA = {
236
514
  ability: {
@@ -242,9 +520,9 @@ var FIELD_SCHEMA = {
242
520
  image_url: { type: "text", required: false },
243
521
  // Mechanics
244
522
  activation: { type: "text" },
245
- duration: { type: "number" },
246
- potency: { type: "number" },
247
- range: { type: "number" },
523
+ duration: { type: "integer" },
524
+ potency: { type: "integer" },
525
+ range: { type: "integer" },
248
526
  effects: { type: "multi_link", target: "phenomenon" },
249
527
  challenges: { type: "text" },
250
528
  talents: { type: "multi_link", target: "trait" },
@@ -267,15 +545,15 @@ var FIELD_SCHEMA = {
267
545
  // Constitution
268
546
  physicality: { type: "text" },
269
547
  mentality: { type: "text" },
270
- height: { type: "number" },
271
- weight: { type: "number" },
548
+ height: { type: "integer" },
549
+ weight: { type: "integer" },
272
550
  species: { type: "multi_link", target: "species" },
273
551
  traits: { type: "multi_link", target: "trait" },
274
552
  abilities: { type: "multi_link", target: "ability" },
275
553
  // Origins
276
554
  background: { type: "text" },
277
555
  motivations: { type: "text" },
278
- birth_date: { type: "number" },
556
+ birth_date: { type: "integer" },
279
557
  birthplace: { type: "single_link", target: "location" },
280
558
  languages: { type: "multi_link", target: "language" },
281
559
  // World
@@ -284,25 +562,25 @@ var FIELD_SCHEMA = {
284
562
  objects: { type: "multi_link", target: "object" },
285
563
  institutions: { type: "multi_link", target: "institution" },
286
564
  // Personality
287
- charisma: { type: "number" },
288
- coercion: { type: "number" },
289
- competence: { type: "number" },
290
- compassion: { type: "number" },
291
- creativity: { type: "number" },
292
- courage: { type: "number" },
565
+ charisma: { type: "integer" },
566
+ coercion: { type: "integer" },
567
+ competence: { type: "integer" },
568
+ compassion: { type: "integer" },
569
+ creativity: { type: "integer" },
570
+ courage: { type: "integer" },
293
571
  // Social
294
572
  family: { type: "multi_link", target: "family" },
295
573
  friends: { type: "multi_link", target: "character" },
296
574
  rivals: { type: "multi_link", target: "character" },
297
575
  // TTRPG
298
- level: { type: "number" },
299
- hit_points: { type: "number" },
300
- STR: { type: "number" },
301
- DEX: { type: "number" },
302
- CON: { type: "number" },
303
- INT: { type: "number" },
304
- WIS: { type: "number" },
305
- CHA: { type: "number" }
576
+ level: { type: "integer" },
577
+ hit_points: { type: "integer" },
578
+ STR: { type: "integer" },
579
+ DEX: { type: "integer" },
580
+ CON: { type: "integer" },
581
+ INT: { type: "integer" },
582
+ WIS: { type: "integer" },
583
+ CHA: { type: "integer" }
306
584
  },
307
585
  collective: {
308
586
  // Base fields (shared by all elements)
@@ -313,8 +591,8 @@ var FIELD_SCHEMA = {
313
591
  image_url: { type: "text", required: false },
314
592
  // Formation
315
593
  composition: { type: "text" },
316
- count: { type: "number" },
317
- formation_date: { type: "number" },
594
+ count: { type: "integer" },
595
+ formation_date: { type: "integer" },
318
596
  operator: { type: "single_link", target: "institution" },
319
597
  equipment: { type: "multi_link", target: "construct" },
320
598
  // Dynamics
@@ -341,8 +619,8 @@ var FIELD_SCHEMA = {
341
619
  history: { type: "text" },
342
620
  status: { type: "text" },
343
621
  reach: { type: "text" },
344
- start_date: { type: "number" },
345
- end_date: { type: "number" },
622
+ start_date: { type: "integer" },
623
+ end_date: { type: "integer" },
346
624
  founder: { type: "single_link", target: "character" },
347
625
  custodian: { type: "single_link", target: "institution" },
348
626
  // Involves
@@ -374,8 +652,8 @@ var FIELD_SCHEMA = {
374
652
  image_url: { type: "text", required: false },
375
653
  // Biology
376
654
  appearance: { type: "text" },
377
- weight: { type: "number" },
378
- height: { type: "number" },
655
+ weight: { type: "integer" },
656
+ height: { type: "integer" },
379
657
  species: { type: "multi_link", target: "species" },
380
658
  // Behaviour
381
659
  habits: { type: "text" },
@@ -385,14 +663,14 @@ var FIELD_SCHEMA = {
385
663
  languages: { type: "multi_link", target: "language" },
386
664
  // World
387
665
  status: { type: "text" },
388
- birth_date: { type: "number" },
666
+ birth_date: { type: "integer" },
389
667
  location: { type: "single_link", target: "location" },
390
668
  zone: { type: "single_link", target: "zone" },
391
669
  // TTRPG
392
- challenge_rating: { type: "number" },
393
- hit_points: { type: "number" },
394
- armor_class: { type: "number" },
395
- speed: { type: "number" },
670
+ challenge_rating: { type: "integer" },
671
+ hit_points: { type: "integer" },
672
+ armor_class: { type: "integer" },
673
+ speed: { type: "integer" },
396
674
  actions: { type: "multi_link", target: "ability" }
397
675
  },
398
676
  event: {
@@ -406,8 +684,8 @@ var FIELD_SCHEMA = {
406
684
  history: { type: "text" },
407
685
  challenges: { type: "text" },
408
686
  consequences: { type: "text" },
409
- start_date: { type: "number" },
410
- end_date: { type: "number" },
687
+ start_date: { type: "integer" },
688
+ end_date: { type: "integer" },
411
689
  triggers: { type: "multi_link", target: "event" },
412
690
  // Involves
413
691
  characters: { type: "multi_link", target: "character" },
@@ -458,7 +736,7 @@ var FIELD_SCHEMA = {
458
736
  image_url: { type: "text", required: false },
459
737
  // Foundation
460
738
  doctrine: { type: "text" },
461
- founding_date: { type: "number" },
739
+ founding_date: { type: "integer" },
462
740
  parent_institution: { type: "single_link", target: "institution" },
463
741
  // Claims
464
742
  zones: { type: "multi_link", target: "zone" },
@@ -498,7 +776,7 @@ var FIELD_SCHEMA = {
498
776
  // Code
499
777
  declaration: { type: "text" },
500
778
  purpose: { type: "text" },
501
- date: { type: "number" },
779
+ date: { type: "integer" },
502
780
  parent_law: { type: "single_link", target: "law" },
503
781
  penalties: { type: "multi_link", target: "construct" },
504
782
  // World
@@ -519,7 +797,7 @@ var FIELD_SCHEMA = {
519
797
  // Setting
520
798
  form: { type: "text" },
521
799
  function: { type: "text" },
522
- founding_date: { type: "number" },
800
+ founding_date: { type: "integer" },
523
801
  parent_location: { type: "single_link", target: "location" },
524
802
  populations: { type: "multi_link", target: "collective" },
525
803
  // Politics
@@ -551,7 +829,7 @@ var FIELD_SCHEMA = {
551
829
  building_methods: { type: "multi_link", target: "construct" },
552
830
  // Defense
553
831
  defensibility: { type: "text" },
554
- elevation: { type: "number" },
832
+ elevation: { type: "integer" },
555
833
  fighters: { type: "multi_link", target: "construct" },
556
834
  defensive_objects: { type: "multi_link", target: "object" }
557
835
  },
@@ -564,10 +842,10 @@ var FIELD_SCHEMA = {
564
842
  image_url: { type: "text", required: false },
565
843
  // Details
566
844
  background_color: { type: "text" },
567
- hierarchy: { type: "number" },
568
- width: { type: "number" },
569
- height: { type: "number" },
570
- depth: { type: "number" },
845
+ hierarchy: { type: "integer" },
846
+ width: { type: "integer" },
847
+ height: { type: "integer" },
848
+ depth: { type: "integer" },
571
849
  parent_map: { type: "single_link", target: "map" },
572
850
  location: { type: "single_link", target: "location" }
573
851
  },
@@ -581,10 +859,10 @@ var FIELD_SCHEMA = {
581
859
  // Details
582
860
  map: { type: "single_link", target: "map", required: true },
583
861
  zone: { type: "single_link", target: "zone", required: true },
584
- x: { type: "number", required: true },
585
- y: { type: "number", required: true },
586
- z: { type: "number" },
587
- order: { type: "number", required: true }
862
+ x: { type: "integer", required: true },
863
+ y: { type: "integer", required: true },
864
+ z: { type: "integer" },
865
+ order: { type: "integer", required: true }
588
866
  },
589
867
  narrative: {
590
868
  // Base fields (shared by all elements)
@@ -596,9 +874,9 @@ var FIELD_SCHEMA = {
596
874
  // Context
597
875
  story: { type: "text" },
598
876
  consequences: { type: "text" },
599
- start_date: { type: "number" },
600
- end_date: { type: "number" },
601
- order: { type: "number" },
877
+ start_date: { type: "integer" },
878
+ end_date: { type: "integer" },
879
+ order: { type: "integer" },
602
880
  parent_narrative: { type: "single_link", target: "narrative" },
603
881
  protagonist: { type: "single_link", target: "character" },
604
882
  antagonist: { type: "single_link", target: "character" },
@@ -633,8 +911,8 @@ var FIELD_SCHEMA = {
633
911
  image_url: { type: "text", required: false },
634
912
  // Form
635
913
  aesthetics: { type: "text" },
636
- weight: { type: "number" },
637
- amount: { type: "number" },
914
+ weight: { type: "integer" },
915
+ amount: { type: "integer" },
638
916
  parent_object: { type: "single_link", target: "object" },
639
917
  materials: { type: "multi_link", target: "construct" },
640
918
  technology: { type: "multi_link", target: "construct" },
@@ -659,7 +937,7 @@ var FIELD_SCHEMA = {
659
937
  // Mechanics
660
938
  expression: { type: "text" },
661
939
  effects: { type: "text" },
662
- duration: { type: "number" },
940
+ duration: { type: "integer" },
663
941
  catalysts: { type: "multi_link", target: "object" },
664
942
  empowerments: { type: "multi_link", target: "ability" },
665
943
  // World
@@ -682,9 +960,9 @@ var FIELD_SCHEMA = {
682
960
  // ElementType enum value; YAML 'element' generic-link is split into _type + _id
683
961
  element_id: { type: "single_link", target: "any", required: true },
684
962
  // Can reference any element
685
- x: { type: "number", required: true },
686
- y: { type: "number", required: true },
687
- z: { type: "number" }
963
+ x: { type: "integer", required: true },
964
+ y: { type: "integer", required: true },
965
+ z: { type: "integer" }
688
966
  },
689
967
  relation: {
690
968
  // Base fields (shared by all elements)
@@ -693,891 +971,359 @@ var FIELD_SCHEMA = {
693
971
  supertype: { type: "text", required: false },
694
972
  subtype: { type: "text", required: false },
695
973
  image_url: { type: "text", required: false },
696
- // Nature
697
- background: { type: "text" },
698
- start_date: { type: "number" },
699
- end_date: { type: "number" },
700
- intensity: { type: "number" },
701
- actor: { type: "single_link", target: "character" },
702
- events: { type: "multi_link", target: "event" },
703
- // Involves
704
- characters: { type: "multi_link", target: "character" },
705
- objects: { type: "multi_link", target: "object" },
706
- locations: { type: "multi_link", target: "location" },
707
- species: { type: "multi_link", target: "species" },
708
- creatures: { type: "multi_link", target: "creature" },
709
- institutions: { type: "multi_link", target: "institution" },
710
- traits: { type: "multi_link", target: "trait" },
711
- collectives: { type: "multi_link", target: "collective" },
712
- zones: { type: "multi_link", target: "zone" },
713
- abilities: { type: "multi_link", target: "ability" },
714
- phenomena: { type: "multi_link", target: "phenomenon" },
715
- languages: { type: "multi_link", target: "language" },
716
- families: { type: "multi_link", target: "family" },
717
- relations: { type: "multi_link", target: "relation" },
718
- titles: { type: "multi_link", target: "title" },
719
- constructs: { type: "multi_link", target: "construct" },
720
- narratives: { type: "multi_link", target: "narrative" }
721
- },
722
- species: {
723
- // Base fields (shared by all elements)
724
- name: { type: "text", required: true },
725
- description: { type: "text", required: false },
726
- supertype: { type: "text", required: false },
727
- subtype: { type: "text", required: false },
728
- image_url: { type: "text", required: false },
729
- // Biology
730
- appearance: { type: "text" },
731
- life_span: { type: "number" },
732
- weight: { type: "number" },
733
- nourishment: { type: "multi_link", target: "species" },
734
- reproduction: { type: "multi_link", target: "construct" },
735
- adaptations: { type: "multi_link", target: "ability" },
736
- // Psychology
737
- instincts: { type: "text" },
738
- sociality: { type: "text" },
739
- temperament: { type: "text" },
740
- communication: { type: "text" },
741
- aggression: { type: "number" },
742
- traits: { type: "multi_link", target: "trait" },
743
- // World
744
- role: { type: "text" },
745
- parent_species: { type: "single_link", target: "species" },
746
- locations: { type: "multi_link", target: "location" },
747
- zones: { type: "multi_link", target: "zone" },
748
- affinities: { type: "multi_link", target: "phenomenon" }
749
- },
750
- title: {
751
- // Base fields (shared by all elements)
752
- name: { type: "text", required: true },
753
- description: { type: "text", required: false },
754
- supertype: { type: "text", required: false },
755
- subtype: { type: "text", required: false },
756
- image_url: { type: "text", required: false },
757
- // Mandate
758
- authority: { type: "text" },
759
- eligibility: { type: "text" },
760
- grant_date: { type: "number" },
761
- revoke_date: { type: "number" },
762
- issuer: { type: "single_link", target: "institution" },
763
- body: { type: "single_link", target: "institution" },
764
- superior_title: { type: "single_link", target: "title" },
765
- holders: { type: "multi_link", target: "character" },
766
- symbols: { type: "multi_link", target: "object" },
767
- // World
768
- status: { type: "text" },
769
- history: { type: "text" },
770
- characters: { type: "multi_link", target: "character" },
771
- institutions: { type: "multi_link", target: "institution" },
772
- families: { type: "multi_link", target: "family" },
773
- zones: { type: "multi_link", target: "zone" },
774
- locations: { type: "multi_link", target: "location" },
775
- objects: { type: "multi_link", target: "object" },
776
- constructs: { type: "multi_link", target: "construct" },
777
- laws: { type: "multi_link", target: "law" },
778
- collectives: { type: "multi_link", target: "collective" },
779
- creatures: { type: "multi_link", target: "creature" },
780
- phenomena: { type: "multi_link", target: "phenomenon" },
781
- species: { type: "multi_link", target: "species" },
782
- languages: { type: "multi_link", target: "language" }
783
- },
784
- trait: {
785
- // Base fields (shared by all elements)
786
- name: { type: "text", required: true },
787
- description: { type: "text", required: false },
788
- supertype: { type: "text", required: false },
789
- subtype: { type: "text", required: false },
790
- image_url: { type: "text", required: false },
791
- // Qualitative
792
- social_effects: { type: "text" },
793
- physical_effects: { type: "text" },
794
- functional_effects: { type: "text" },
795
- personality_effects: { type: "text" },
796
- behaviour_effects: { type: "text" },
797
- // Quantitative
798
- charisma: { type: "number" },
799
- coercion: { type: "number" },
800
- competence: { type: "number" },
801
- compassion: { type: "number" },
802
- creativity: { type: "number" },
803
- courage: { type: "number" },
804
- // World
805
- significance: { type: "text" },
806
- anti_trait: { type: "single_link", target: "trait" },
807
- empowered_abilities: { type: "multi_link", target: "ability" }
974
+ // Nature
975
+ background: { type: "text" },
976
+ start_date: { type: "integer" },
977
+ end_date: { type: "integer" },
978
+ intensity: { type: "integer" },
979
+ actor: { type: "single_link", target: "character" },
980
+ events: { type: "multi_link", target: "event" },
981
+ // Involves
982
+ characters: { type: "multi_link", target: "character" },
983
+ objects: { type: "multi_link", target: "object" },
984
+ locations: { type: "multi_link", target: "location" },
985
+ species: { type: "multi_link", target: "species" },
986
+ creatures: { type: "multi_link", target: "creature" },
987
+ institutions: { type: "multi_link", target: "institution" },
988
+ traits: { type: "multi_link", target: "trait" },
989
+ collectives: { type: "multi_link", target: "collective" },
990
+ zones: { type: "multi_link", target: "zone" },
991
+ abilities: { type: "multi_link", target: "ability" },
992
+ phenomena: { type: "multi_link", target: "phenomenon" },
993
+ languages: { type: "multi_link", target: "language" },
994
+ families: { type: "multi_link", target: "family" },
995
+ relations: { type: "multi_link", target: "relation" },
996
+ titles: { type: "multi_link", target: "title" },
997
+ constructs: { type: "multi_link", target: "construct" },
998
+ narratives: { type: "multi_link", target: "narrative" }
808
999
  },
809
- zone: {
1000
+ species: {
810
1001
  // Base fields (shared by all elements)
811
1002
  name: { type: "text", required: true },
812
1003
  description: { type: "text", required: false },
813
1004
  supertype: { type: "text", required: false },
814
1005
  subtype: { type: "text", required: false },
815
1006
  image_url: { type: "text", required: false },
816
- // Scope
817
- role: { type: "text" },
818
- start_date: { type: "number" },
819
- end_date: { type: "number" },
820
- phenomena: { type: "multi_link", target: "phenomenon" },
821
- linked_zones: { type: "multi_link", target: "zone" },
1007
+ // Biology
1008
+ appearance: { type: "text" },
1009
+ life_span: { type: "integer" },
1010
+ weight: { type: "integer" },
1011
+ nourishment: { type: "multi_link", target: "species" },
1012
+ reproduction: { type: "multi_link", target: "construct" },
1013
+ adaptations: { type: "multi_link", target: "ability" },
1014
+ // Psychology
1015
+ instincts: { type: "text" },
1016
+ sociality: { type: "text" },
1017
+ temperament: { type: "text" },
1018
+ communication: { type: "text" },
1019
+ aggression: { type: "integer" },
1020
+ traits: { type: "multi_link", target: "trait" },
822
1021
  // World
823
- context: { type: "text" },
824
- populations: { type: "multi_link", target: "collective" },
825
- titles: { type: "multi_link", target: "title" },
826
- principles: { type: "multi_link", target: "construct" }
827
- }
828
- };
829
- function createElementId(id) {
830
- return id;
831
- }
832
- function createElementIds(ids) {
833
- return ids;
834
- }
835
- function createAnyElementId(id) {
836
- return id;
837
- }
838
-
839
- // src/token-resource.ts
840
- var TokenResource = class {
841
- constructor(client) {
842
- this.client = client;
843
- }
844
- /**
845
- * Get current token status for authenticated user
846
- *
847
- * Returns daily token allowance, usage, and availability.
848
- * Matches base-tool's checkStatus() pattern.
849
- *
850
- * @returns Current token status
851
- * @example
852
- * ```typescript
853
- * const status = await client.tokens.getStatus();
854
- * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`);
855
- * console.log(`Used today: ${status.tokens_used_today}`);
856
- * console.log(`Active sessions: ${status.sessions_active}`);
857
- * ```
858
- */
859
- async getStatus() {
860
- return this.client.request("GET", "/tokens/status/");
861
- }
862
- /**
863
- * Consume tokens for service usage
864
- *
865
- * Reports token consumption to track daily usage. Allows consumption even if
866
- * exceeds available tokens (tracks as debt), but warns via error field.
867
- * Matches base-tool's reportUsage() pattern.
868
- *
869
- * @param params - Token consumption parameters
870
- * @returns Consumption result with updated balance
871
- * @example
872
- * ```typescript
873
- * const result = await client.tokens.consume({
874
- * amount: 500,
875
- * service: 'worldbuilding_tool',
876
- * metadata: {
877
- * feature: 'character_generation',
878
- * model: 'gpt-4',
879
- * prompt_tokens: 300,
880
- * completion_tokens: 200
881
- * }
882
- * });
883
- *
884
- * if (result.error) {
885
- * console.warn('Token warning:', result.error);
886
- * }
887
- * console.log(`${result.tokens_remaining} tokens remaining`);
888
- * ```
889
- */
890
- async consume(params) {
891
- return this.client.request("POST", "/tokens/consume/", {
892
- body: {
893
- amount: params.amount,
894
- service: params.service || "sdk_client",
895
- session_id: params.sessionId ?? null,
896
- metadata: params.metadata ?? null
897
- }
898
- });
899
- }
900
- /**
901
- * Get encrypted OpenAI API key (advanced use case)
902
- *
903
- * Requires minimum 100 tokens available. Creates a 1-hour session for tracking.
904
- * Returns encrypted key that must be decrypted client-side using Fernet.
905
- *
906
- * See base-tool/src/llm/token-service.ts:99-155 for full implementation example
907
- * including client-side decryption with the 'fernet' npm package.
908
- *
909
- * @returns Encrypted access key and session info
910
- * @throws Error if insufficient tokens (< 100)
911
- * @example
912
- * ```typescript
913
- * // Get encrypted key
914
- * const access = await client.tokens.getAccessKey();
915
- *
916
- * // Decrypt using fernet library (see base-tool for full example)
917
- * // 1. Derive key from world ID using SHA-256
918
- * // 2. Use 'fernet' npm package to decrypt
919
- * // 3. Use decrypted OpenAI key for direct API calls
920
- * // 4. Report usage with access.session_id
921
- *
922
- * console.log('Session:', access.session_id);
923
- * console.log('Expires:', access.expires_at);
924
- * ```
925
- */
926
- async getAccessKey() {
927
- return this.client.request("GET", "/tokens/access-key/");
928
- }
929
- /**
930
- * Revoke a specific token session
931
- *
932
- * Invalidates the session ID obtained from getAccessKey().
933
- * Use when cleaning up or on logout.
934
- *
935
- * @param sessionId - Session ID to revoke
936
- * @returns Revocation result
937
- * @example
938
- * ```typescript
939
- * await client.tokens.revokeSession('session-id-here');
940
- * ```
941
- */
942
- async revokeSession(sessionId) {
943
- return this.client.request(
944
- "POST",
945
- `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
946
- );
947
- }
948
- /**
949
- * Revoke all active sessions (emergency use)
950
- *
951
- * Invalidates all token sessions for the authenticated user.
952
- * Use for security cleanup or when sessions are stuck.
953
- *
954
- * @returns Revocation result with count of revoked sessions
955
- * @example
956
- * ```typescript
957
- * const result = await client.tokens.revokeAllSessions();
958
- * console.log(`Revoked ${result.sessions_revoked} sessions`);
959
- * ```
960
- */
961
- async revokeAllSessions() {
962
- return this.client.request("POST", "/tokens/revoke-all-sessions/");
963
- }
964
- /**
965
- * Get public encryption info (no auth required)
966
- *
967
- * Returns algorithm details and example code for client-side decryption.
968
- * Public endpoint - can be called without authentication.
969
- *
970
- * @returns Encryption algorithm and implementation details
971
- * @example
972
- * ```typescript
973
- * const info = await client.tokens.getEncryptionInfo();
974
- * console.log('Algorithm:', info.algorithm);
975
- * console.log('Key derivation:', info.key_derivation);
976
- * console.log(info.javascript_example);
977
- * ```
978
- */
979
- async getEncryptionInfo() {
980
- return this.client.request("GET", "/tokens/encryption-info/");
981
- }
982
- };
983
-
984
- // src/client.ts
985
- var Resource = class {
986
- constructor(client, elementType) {
987
- this.client = client;
988
- this.elementType = elementType;
989
- }
990
- async list(options) {
991
- const response = await this.client.request("GET", `/${this.elementType}/`, { params: options });
992
- if (Array.isArray(response)) {
993
- return {
994
- count: response.length,
995
- next: null,
996
- previous: null,
997
- results: response
998
- };
999
- }
1000
- return response;
1001
- }
1002
- async get(id) {
1003
- return this.client.request("GET", `/${this.elementType}/${id}/`);
1004
- }
1005
- async create(data) {
1006
- let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
1007
- if (this.elementType === "pin") body = this.roundPinCoordinates(body);
1008
- return this.client.request("POST", `/${this.elementType}/`, { body });
1009
- }
1010
- async update(id, data) {
1011
- let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
1012
- if (this.elementType === "pin") body = this.roundPinCoordinates(body);
1013
- return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body });
1014
- }
1015
- async delete(id) {
1016
- return this.client.request("DELETE", `/${this.elementType}/${id}/`);
1017
- }
1018
- /**
1019
- * Round Pin coordinates to integers (API requirement)
1020
- * @private
1021
- */
1022
- roundPinCoordinates(data) {
1023
- const rounded = { ...data };
1024
- if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x);
1025
- if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y);
1026
- if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z);
1027
- return rounded;
1028
- }
1029
- };
1030
- var WorldResource = class {
1031
- constructor(client) {
1032
- this.client = client;
1033
- }
1034
- /**
1035
- * Get the world associated with the current API key
1036
- * Returns the world directly (not wrapped in pagination)
1037
- */
1038
- async get() {
1039
- return this.client.request("GET", "/world/");
1040
- }
1041
- /**
1042
- * Update the current world
1043
- */
1044
- async update(data) {
1045
- return this.client.request("PATCH", "/world/", { body: data });
1046
- }
1047
- };
1048
- var OnlyWorldsClient = class {
1049
- constructor(config) {
1050
- this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi";
1051
- this.headers = {
1052
- "Content-Type": "application/json",
1053
- "API-Key": config.apiKey,
1054
- "API-Pin": config.apiPin
1055
- };
1056
- this.worlds = new WorldResource(this);
1057
- this.tokens = new TokenResource(this);
1058
- this.abilities = new Resource(this, "ability");
1059
- this.characters = new Resource(this, "character");
1060
- this.collectives = new Resource(this, "collective");
1061
- this.constructs = new Resource(this, "construct");
1062
- this.creatures = new Resource(this, "creature");
1063
- this.events = new Resource(this, "event");
1064
- this.families = new Resource(this, "family");
1065
- this.institutions = new Resource(this, "institution");
1066
- this.languages = new Resource(this, "language");
1067
- this.laws = new Resource(this, "law");
1068
- this.locations = new Resource(this, "location");
1069
- this.maps = new Resource(this, "map");
1070
- this.markers = new Resource(this, "marker");
1071
- this.narratives = new Resource(this, "narrative");
1072
- this.objects = new Resource(this, "object");
1073
- this.phenomena = new Resource(this, "phenomenon");
1074
- this.pins = new Resource(this, "pin");
1075
- this.relations = new Resource(this, "relation");
1076
- this.species = new Resource(this, "species");
1077
- this.titles = new Resource(this, "title");
1078
- this.traits = new Resource(this, "trait");
1079
- this.zones = new Resource(this, "zone");
1080
- }
1081
- /**
1082
- * Make a request to the OnlyWorlds API
1083
- */
1084
- async request(method, path, options) {
1085
- const url = new URL(`${this.baseUrl}${path}`);
1086
- if (options?.params) {
1087
- Object.entries(options.params).forEach(([key, value]) => {
1088
- if (value !== void 0 && value !== null) {
1089
- url.searchParams.append(key, String(value));
1090
- }
1091
- });
1092
- }
1093
- const fetchOptions = {
1094
- method,
1095
- headers: this.headers
1096
- };
1097
- if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) {
1098
- fetchOptions.body = JSON.stringify(options.body);
1099
- }
1100
- const response = await fetch(url.toString(), fetchOptions);
1101
- if (!response.ok) {
1102
- let errorMessage = `API Error ${response.status}`;
1103
- try {
1104
- const errorText = await response.text();
1105
- if (errorText) {
1106
- try {
1107
- const errorJson = JSON.parse(errorText);
1108
- if (Array.isArray(errorJson.detail)) {
1109
- const validationErrors = errorJson.detail.map((err) => {
1110
- const location = err.loc ? err.loc.join(".") : "unknown";
1111
- return `${location}: ${err.msg}`;
1112
- }).join("; ");
1113
- errorMessage += `: ${validationErrors}`;
1114
- } else {
1115
- errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`;
1116
- }
1117
- } catch {
1118
- errorMessage += `: ${errorText}`;
1119
- }
1120
- }
1121
- } catch {
1122
- }
1123
- throw new Error(errorMessage);
1124
- }
1125
- if (response.status === 204) {
1126
- return void 0;
1127
- }
1128
- return response.json();
1129
- }
1130
- /**
1131
- * Helper to convert nested objects to _id/_ids format (legacy method)
1132
- * @deprecated Use prepareRelations instead - it's called automatically in create/update
1133
- */
1134
- static prepareInput(data) {
1135
- const result = { ...data };
1136
- for (const [key, value] of Object.entries(result)) {
1137
- if (value && typeof value === "object" && !Array.isArray(value) && "id" in value) {
1138
- delete result[key];
1139
- result[`${key}_id`] = value.id;
1140
- } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
1141
- delete result[key];
1142
- result[`${key}_ids`] = value.map((item) => item.id);
1143
- }
1144
- }
1145
- return result;
1146
- }
1147
- /**
1148
- * Convert relation fields to API format (_id/_ids suffix)
1149
- *
1150
- * The OnlyWorlds API expects:
1151
- * - single_link fields: fieldname_id (e.g., birthplace_id)
1152
- * - multi_link fields: fieldname_ids (e.g., species_ids)
1153
- *
1154
- * This method auto-converts based on FIELD_SCHEMA:
1155
- * - { species: ["id1", "id2"] } → { species_ids: ["id1", "id2"] }
1156
- * - { birthplace: "location-id" } → { birthplace_id: "location-id" }
1157
- * - { species: [{id: "id1", name: "X"}] } → { species_ids: ["id1"] }
1158
- *
1159
- * Called automatically by create() and update() methods.
1160
- */
1161
- static prepareRelations(data, elementType) {
1162
- const schema = FIELD_SCHEMA[elementType];
1163
- if (!schema) return data;
1164
- const result = { ...data };
1165
- for (const [key, value] of Object.entries(result)) {
1166
- const fieldDef = schema[key];
1167
- if (!fieldDef) continue;
1168
- if (fieldDef.type === "single_link") {
1169
- delete result[key];
1170
- if (value && typeof value === "object" && "id" in value) {
1171
- result[`${key}_id`] = value.id;
1172
- } else if (typeof value === "string" || value === null) {
1173
- result[`${key}_id`] = value;
1174
- }
1175
- } else if (fieldDef.type === "multi_link") {
1176
- delete result[key];
1177
- if (Array.isArray(value)) {
1178
- if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
1179
- result[`${key}_ids`] = value.map((item) => item.id);
1180
- } else {
1181
- result[`${key}_ids`] = value;
1182
- }
1183
- } else {
1184
- result[`${key}_ids`] = [];
1185
- }
1186
- }
1187
- }
1188
- return result;
1022
+ role: { type: "text" },
1023
+ parent_species: { type: "single_link", target: "species" },
1024
+ locations: { type: "multi_link", target: "location" },
1025
+ zones: { type: "multi_link", target: "zone" },
1026
+ affinities: { type: "multi_link", target: "phenomenon" }
1027
+ },
1028
+ title: {
1029
+ // Base fields (shared by all elements)
1030
+ name: { type: "text", required: true },
1031
+ description: { type: "text", required: false },
1032
+ supertype: { type: "text", required: false },
1033
+ subtype: { type: "text", required: false },
1034
+ image_url: { type: "text", required: false },
1035
+ // Mandate
1036
+ authority: { type: "text" },
1037
+ eligibility: { type: "text" },
1038
+ grant_date: { type: "integer" },
1039
+ revoke_date: { type: "integer" },
1040
+ issuer: { type: "single_link", target: "institution" },
1041
+ body: { type: "single_link", target: "institution" },
1042
+ superior_title: { type: "single_link", target: "title" },
1043
+ holders: { type: "multi_link", target: "character" },
1044
+ symbols: { type: "multi_link", target: "object" },
1045
+ // World
1046
+ status: { type: "text" },
1047
+ history: { type: "text" },
1048
+ characters: { type: "multi_link", target: "character" },
1049
+ institutions: { type: "multi_link", target: "institution" },
1050
+ families: { type: "multi_link", target: "family" },
1051
+ zones: { type: "multi_link", target: "zone" },
1052
+ locations: { type: "multi_link", target: "location" },
1053
+ objects: { type: "multi_link", target: "object" },
1054
+ constructs: { type: "multi_link", target: "construct" },
1055
+ laws: { type: "multi_link", target: "law" },
1056
+ collectives: { type: "multi_link", target: "collective" },
1057
+ creatures: { type: "multi_link", target: "creature" },
1058
+ phenomena: { type: "multi_link", target: "phenomenon" },
1059
+ species: { type: "multi_link", target: "species" },
1060
+ languages: { type: "multi_link", target: "language" }
1061
+ },
1062
+ trait: {
1063
+ // Base fields (shared by all elements)
1064
+ name: { type: "text", required: true },
1065
+ description: { type: "text", required: false },
1066
+ supertype: { type: "text", required: false },
1067
+ subtype: { type: "text", required: false },
1068
+ image_url: { type: "text", required: false },
1069
+ // Qualitative
1070
+ social_effects: { type: "text" },
1071
+ physical_effects: { type: "text" },
1072
+ functional_effects: { type: "text" },
1073
+ personality_effects: { type: "text" },
1074
+ behaviour_effects: { type: "text" },
1075
+ // Quantitative
1076
+ charisma: { type: "integer" },
1077
+ coercion: { type: "integer" },
1078
+ competence: { type: "integer" },
1079
+ compassion: { type: "integer" },
1080
+ creativity: { type: "integer" },
1081
+ courage: { type: "integer" },
1082
+ // World
1083
+ significance: { type: "text" },
1084
+ anti_trait: { type: "single_link", target: "trait" },
1085
+ empowered_abilities: { type: "multi_link", target: "ability" }
1086
+ },
1087
+ zone: {
1088
+ // Base fields (shared by all elements)
1089
+ name: { type: "text", required: true },
1090
+ description: { type: "text", required: false },
1091
+ supertype: { type: "text", required: false },
1092
+ subtype: { type: "text", required: false },
1093
+ image_url: { type: "text", required: false },
1094
+ // Scope
1095
+ role: { type: "text" },
1096
+ start_date: { type: "integer" },
1097
+ end_date: { type: "integer" },
1098
+ phenomena: { type: "multi_link", target: "phenomenon" },
1099
+ linked_zones: { type: "multi_link", target: "zone" },
1100
+ // World
1101
+ context: { type: "text" },
1102
+ populations: { type: "multi_link", target: "collective" },
1103
+ titles: { type: "multi_link", target: "title" },
1104
+ principles: { type: "multi_link", target: "construct" }
1189
1105
  }
1190
1106
  };
1191
-
1192
- // src/token-types.ts
1193
- var GameTier = /* @__PURE__ */ ((GameTier2) => {
1194
- GameTier2["FREE"] = "free";
1195
- GameTier2["SILVER"] = "silver";
1196
- GameTier2["GOLD"] = "gold";
1197
- GameTier2["PLATINUM"] = "platinum";
1198
- GameTier2["DIAMOND"] = "diamond";
1199
- GameTier2["DELUXE"] = "deluxe";
1200
- return GameTier2;
1201
- })(GameTier || {});
1202
-
1203
- // src/icon-utils.ts
1204
1107
  var PLURAL_TO_SINGULAR = {
1205
- abilities: "ability" /* Ability */,
1206
- characters: "character" /* Character */,
1207
- collectives: "collective" /* Collective */,
1208
- constructs: "construct" /* Construct */,
1209
- creatures: "creature" /* Creature */,
1210
- events: "event" /* Event */,
1211
- families: "family" /* Family */,
1212
- institutions: "institution" /* Institution */,
1213
- languages: "language" /* Language */,
1214
- laws: "law" /* Law */,
1215
- locations: "location" /* Location */,
1216
- maps: "map" /* Map */,
1217
- markers: "marker" /* Marker */,
1218
- narratives: "narrative" /* Narrative */,
1219
- objects: "object" /* Object */,
1220
- phenomena: "phenomenon" /* Phenomenon */,
1108
+ abilities: "ability",
1109
+ characters: "character",
1110
+ collectives: "collective",
1111
+ constructs: "construct",
1112
+ creatures: "creature",
1113
+ events: "event",
1114
+ families: "family",
1115
+ institutions: "institution",
1116
+ languages: "language",
1117
+ laws: "law",
1118
+ locations: "location",
1119
+ maps: "map",
1120
+ markers: "marker",
1121
+ narratives: "narrative",
1122
+ objects: "object",
1123
+ phenomena: "phenomenon",
1221
1124
  // irregular plural
1222
- pins: "pin" /* Pin */,
1223
- relations: "relation" /* Relation */,
1224
- species: "species" /* Species */,
1125
+ pins: "pin",
1126
+ relations: "relation",
1127
+ species: "species",
1225
1128
  // same singular/plural
1226
- titles: "title" /* Title */,
1227
- traits: "trait" /* Trait */,
1228
- zones: "zone" /* Zone */
1129
+ titles: "title",
1130
+ traits: "trait",
1131
+ zones: "zone"
1229
1132
  };
1230
1133
  function getElementIcon(type) {
1231
1134
  const lower = type.toLowerCase();
1232
1135
  if (lower in PLURAL_TO_SINGULAR) {
1233
1136
  return ELEMENT_ICONS[PLURAL_TO_SINGULAR[lower]];
1234
1137
  }
1235
- const singular = Object.values(ElementType).find(
1236
- (et) => et.toLowerCase() === lower
1237
- );
1138
+ const singular = ELEMENT_TYPES.find((et) => et.toLowerCase() === lower);
1238
1139
  if (singular) {
1239
1140
  return ELEMENT_ICONS[singular];
1240
1141
  }
1241
1142
  return "help_outline";
1242
1143
  }
1243
-
1244
- // src/v2/errors.ts
1245
- var OwApiError = class extends Error {
1246
- constructor(status, code, message, docUrl, detail, type = null, param = null) {
1247
- super(message);
1248
- this.name = "OwApiError";
1249
- this.status = status;
1250
- this.code = code;
1251
- this.type = type;
1252
- this.param = param;
1253
- this.docUrl = docUrl;
1254
- this.detail = detail;
1255
- }
1256
- get isAuthError() {
1257
- return this.code === "invalid_credentials" || this.code === "key_revoked" || this.code === "world_gone";
1258
- }
1259
- /** 422s/400s name the offending param/field -- typos error loudly platform-wide. */
1260
- get isValidationError() {
1261
- return this.status === 422 || this.status === 400;
1262
- }
1263
- /** Same Idempotency-Key replayed with a different payload. */
1264
- get isIdempotencyConflict() {
1265
- return this.status === 409;
1266
- }
1267
- };
1268
- var OwNetworkError = class extends Error {
1269
- constructor(message, cause) {
1270
- super(message);
1271
- this.name = "OwNetworkError";
1272
- this.cause2 = cause;
1273
- }
1274
- };
1275
- function parseEnvelope(status, body) {
1276
- const env = body && typeof body === "object" ? body : {};
1277
- const nested = typeof env.error === "object" && env.error !== null ? env.error : void 0;
1278
- const code = env.code ?? nested?.code ?? (typeof env.error === "string" ? env.error : null) ?? null;
1279
- const type = env.type ?? nested?.type ?? null;
1280
- const param = env.param ?? nested?.param ?? null;
1281
- const docUrl = env.doc_url ?? nested?.doc_url ?? null;
1282
- const message = env.message ?? nested?.message ?? (typeof env.detail === "string" ? env.detail : void 0) ?? `OnlyWorlds API error ${status}${code ? ` (${code})` : ""}`;
1283
- return new OwApiError(status, code, message, docUrl, body, type, param);
1284
- }
1285
- async function errorFromResponse(res) {
1286
- let body = null;
1287
- let text = "";
1288
- try {
1289
- text = await res.text();
1290
- body = text ? JSON.parse(text) : null;
1291
- } catch {
1292
- body = text || null;
1293
- }
1294
- return parseEnvelope(res.status, body);
1295
- }
1296
-
1297
- // src/v2/keys.ts
1298
- var LEGACY_RE = /^[0-9]{10}$/;
1299
- function isDemoKey(key) {
1300
- return LEGACY_RE.test(key) && key >= "0000000000" && key <= "0000000009";
1301
- }
1302
- function detectKeyKind(key) {
1303
- if (key.startsWith("ow_w_")) return "write";
1304
- if (key.startsWith("ow_r_")) return "read";
1305
- if (key.startsWith("ow_a_")) return "account";
1306
- if (LEGACY_RE.test(key)) return "legacy";
1307
- return "unknown";
1308
- }
1309
- function kindCanWrite(kind) {
1310
- return kind === "write" || kind === "legacy";
1311
- }
1312
- function pinExpectation(kind) {
1313
- switch (kind) {
1314
- case "read":
1315
- return "never";
1316
- case "account":
1317
- return "never";
1318
- case "write":
1319
- return "optional";
1320
- case "legacy":
1321
- return "required-for-private-reads";
1322
- default:
1323
- return "optional";
1324
- }
1144
+ function getElementLabel(elementType) {
1145
+ return ELEMENT_LABELS[elementType];
1325
1146
  }
1326
1147
 
1327
- // src/v2/client.ts
1328
- var DEFAULT_BASE_URL = "https://www.onlyworlds.com/api/v2";
1329
- var DEFAULT_PAGE_SIZE = 100;
1330
- var DEFAULT_CHANGES_PAGE_SIZE = 100;
1331
- var OwV2Client = class {
1332
- constructor(config) {
1333
- if (!config.apiKey) throw new Error("OwV2Client: apiKey is required");
1334
- this.apiKey = config.apiKey;
1335
- this.apiPin = config.apiPin || void 0;
1336
- this.keyKind = detectKeyKind(config.apiKey);
1337
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
1338
- this.pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
1339
- this.changesPageSize = config.changesPageSize ?? DEFAULT_CHANGES_PAGE_SIZE;
1340
- this.fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
1341
- if (typeof this.fetchImpl !== "function") {
1342
- throw new Error("OwV2Client: no fetch available -- supply config.fetch");
1343
- }
1344
- }
1345
- // -- Health & world meta -------------------------------------------------
1346
- /** GET /health -- unauthenticated liveness pulse. */
1347
- async health() {
1348
- return this.request("GET", "/health/", { auth: false });
1349
- }
1350
- /**
1351
- * GET /world -- world meta (name, calendar/time fields, public_read).
1352
- * GOTCHA (by server design): world-meta edits do NOT appear in /changes and
1353
- * do not bump change_seq. Poll getWorld().updated_at for meta freshness.
1354
- */
1355
- async getWorld() {
1356
- return this.request("GET", "/world/");
1357
- }
1358
- /** PATCH /world -- partial world-meta update. */
1359
- async patchWorld(partial) {
1360
- return this.request("PATCH", "/world/", { body: sanitizePayload(partial) });
1361
- }
1362
- // -- Element CRUD --------------------------------------------------------
1363
- /** GET /{type}/ -- one cursor page. */
1364
- async list(type, params = {}) {
1365
- const query = buildQuery({
1366
- limit: params.limit ?? this.pageSize,
1367
- cursor: params.cursor,
1368
- expand: params.expand?.join(","),
1369
- fields: params.fields?.join(","),
1370
- ...params.filter
1371
- });
1372
- return this.request("GET", `/${type}/`, { query });
1373
- }
1374
- /** Cursor-walk every page of a type. Politeness: uses config pageSize. */
1375
- async *listAll(type, params = {}) {
1376
- let cursor;
1377
- do {
1378
- const page = await this.list(type, { ...params, cursor });
1379
- for (const el of page.data) yield el;
1380
- cursor = page.has_more && page.next_cursor ? page.next_cursor : void 0;
1381
- } while (cursor);
1382
- }
1383
- /** GET /{type}/{id}/ -- optional one-level stub expansion / sparse fields. */
1384
- async get(type, id, opts = {}) {
1385
- const query = buildQuery({ expand: opts.expand?.join(","), fields: opts.fields?.join(",") });
1386
- return this.request("GET", `/${type}/${id}/`, { query });
1387
- }
1388
- /**
1389
- * POST /{type}/ -- create. Mints an RFC-4122 UUID for element.id when the
1390
- * caller omits one (design ruling D29d) so a retry carrying the same
1391
- * Idempotency-Key is structurally safe. Callers MAY still supply their own id.
1392
- */
1393
- async create(type, element, opts = {}) {
1394
- const body = sanitizePayload(element);
1395
- if (body.id === void 0 || body.id === null || body.id === "") {
1396
- body.id = mintUuid();
1397
- }
1398
- return this.request("POST", `/${type}/`, {
1399
- body,
1400
- idempotencyKey: opts.idempotencyKey
1401
- });
1402
- }
1403
- /** PUT /{type}/{id}/ -- upsert-by-client-id. The local-first write primitive. */
1404
- async upsert(type, id, element) {
1405
- return this.request("PUT", `/${type}/${id}/`, { body: sanitizePayload(element) });
1406
- }
1407
- /**
1408
- * PATCH /{type}/{id}/ -- partial update. DESTRUCTIVE on sent fields: arrays
1409
- * replace wholesale, omitted fields stay untouched. For link arrays prefer
1410
- * editLinks() -- atomic server-side merge, no read-before-write.
1411
- */
1412
- async patch(type, id, partial) {
1413
- return this.request("PATCH", `/${type}/${id}/`, { body: sanitizePayload(partial) });
1148
+ // src/token-resource.ts
1149
+ var TokenResource = class {
1150
+ constructor(client) {
1151
+ this.client = client;
1414
1152
  }
1415
1153
  /**
1416
- * DELETE /{type}/{id}/ -- idempotent (204 on absent). Server writes a
1417
- * tombstone AND scrubs the id from every other element's links in the same
1418
- * transaction -- no client-side unlink pass needed, ever.
1154
+ * Get current token status for authenticated user
1155
+ *
1156
+ * Returns daily token allowance, usage, and availability.
1157
+ * Matches base-tool's checkStatus() pattern.
1158
+ *
1159
+ * @returns Current token status
1160
+ * @example
1161
+ * ```typescript
1162
+ * const status = await client.tokens.getStatus();
1163
+ * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`);
1164
+ * console.log(`Used today: ${status.tokens_used_today}`);
1165
+ * console.log(`Active sessions: ${status.sessions_active}`);
1166
+ * ```
1419
1167
  */
1420
- async delete(type, id) {
1421
- await this.request("DELETE", `/${type}/${id}/`, { allowEmpty: true });
1168
+ async getStatus() {
1169
+ return this.client.request("GET", "/tokens/status/");
1422
1170
  }
1423
1171
  /**
1424
- * POST /{type}/{id}/links/{field} with {add, remove} -- atomic link merge.
1425
- * Dedupes, tolerates already-present/already-absent ids. Returns the FULL
1426
- * updated element (fixture P5). Use this for all relationship editing; it
1427
- * retires the read-merge-PATCH dance.
1172
+ * Consume tokens for service usage
1173
+ *
1174
+ * Reports token consumption to track daily usage. Allows consumption even if
1175
+ * exceeds available tokens (tracks as debt), but warns via error field.
1176
+ * Matches base-tool's reportUsage() pattern.
1177
+ *
1178
+ * @param params - Token consumption parameters
1179
+ * @returns Consumption result with updated balance
1180
+ * @example
1181
+ * ```typescript
1182
+ * const result = await client.tokens.consume({
1183
+ * amount: 500,
1184
+ * service: 'worldbuilding_tool',
1185
+ * metadata: {
1186
+ * feature: 'character_generation',
1187
+ * model: 'gpt-4',
1188
+ * prompt_tokens: 300,
1189
+ * completion_tokens: 200
1190
+ * }
1191
+ * });
1192
+ *
1193
+ * if (result.error) {
1194
+ * console.warn('Token warning:', result.error);
1195
+ * }
1196
+ * console.log(`${result.tokens_remaining} tokens remaining`);
1197
+ * ```
1428
1198
  */
1429
- async editLinks(type, id, field, edit) {
1430
- return this.request("POST", `/${type}/${id}/links/${field}`, { body: edit });
1199
+ async consume(params) {
1200
+ return this.client.request("POST", "/tokens/consume/", {
1201
+ body: {
1202
+ amount: params.amount,
1203
+ service: params.service || "sdk_client",
1204
+ session_id: params.sessionId ?? null,
1205
+ metadata: params.metadata ?? null
1206
+ }
1207
+ });
1431
1208
  }
1432
- // -- Bulk ----------------------------------------------------------------
1433
1209
  /**
1434
- * POST /bulk -- up to ~1000 items. Partial success by default (HTTP 200
1435
- * always; inspect per-slot numeric `status` + top-level `errors` flag);
1436
- * atomic:true for all-or-nothing. Link validation runs against batch U
1437
- * database -- send in any order, cycles included; no client topo-sort.
1438
- * Success slots echo server-authoritative timestamps: set your sync baseline
1439
- * from this response alone. When an idempotencyKey is replayed, the returned
1440
- * response carries wasReplay:true (read from the Idempotent-Replay header).
1210
+ * Get encrypted OpenAI API key (advanced use case)
1211
+ *
1212
+ * Requires minimum 100 tokens available. Creates a 1-hour session for tracking.
1213
+ * Returns encrypted key that must be decrypted client-side using Fernet.
1214
+ *
1215
+ * See base-tool/src/llm/token-service.ts:99-155 for full implementation example
1216
+ * including client-side decryption with the 'fernet' npm package.
1217
+ *
1218
+ * @returns Encrypted access key and session info
1219
+ * @throws Error if insufficient tokens (< 100)
1220
+ * @example
1221
+ * ```typescript
1222
+ * // Get encrypted key
1223
+ * const access = await client.tokens.getAccessKey();
1224
+ *
1225
+ * // Decrypt using fernet library (see base-tool for full example)
1226
+ * // 1. Derive key from world ID using SHA-256
1227
+ * // 2. Use 'fernet' npm package to decrypt
1228
+ * // 3. Use decrypted OpenAI key for direct API calls
1229
+ * // 4. Report usage with access.session_id
1230
+ *
1231
+ * console.log('Session:', access.session_id);
1232
+ * console.log('Expires:', access.expires_at);
1233
+ * ```
1441
1234
  */
1442
- async bulk(items, opts = {}) {
1443
- const body = {
1444
- items: items.map((it) => ({ type: it.type, element: sanitizePayload(it.element) })),
1445
- atomic: opts.atomic ?? false
1446
- };
1447
- return this.request("POST", "/bulk/", {
1448
- body,
1449
- idempotencyKey: opts.idempotencyKey,
1450
- replayAware: true
1451
- });
1235
+ async getAccessKey() {
1236
+ return this.client.request("GET", "/tokens/access-key/");
1452
1237
  }
1453
- // -- Changes feed --------------------------------------------------------
1454
1238
  /**
1455
- * GET /changes -- one page of the world's ordered change feed.
1456
- * Cursor is OPAQUE and never expires: persist verbatim, never parse.
1457
- * Zero/absent cursor = full export (byte-aligned with the Folder Format).
1458
- * Rewind rule: if your persisted position is ahead of page.head, the server
1459
- * was restored -- re-baseline from cursor zero; do not assume caught-up.
1460
- * Citizenship: heaviest route on the platform; default page size is polite.
1239
+ * Revoke a specific token session
1240
+ *
1241
+ * Invalidates the session ID obtained from getAccessKey().
1242
+ * Use when cleaning up or on logout.
1243
+ *
1244
+ * @param sessionId - Session ID to revoke
1245
+ * @returns Revocation result
1246
+ * @example
1247
+ * ```typescript
1248
+ * await client.tokens.revokeSession('session-id-here');
1249
+ * ```
1461
1250
  */
1462
- async changes(opts = {}) {
1463
- const query = buildQuery({ since: opts.since, limit: opts.limit ?? this.changesPageSize });
1464
- return this.request("GET", "/changes/", { query });
1251
+ async revokeSession(sessionId) {
1252
+ return this.client.request(
1253
+ "POST",
1254
+ `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
1255
+ );
1465
1256
  }
1466
1257
  /**
1467
- * Walk the feed from `since` (or from zero = full export) to the current
1468
- * tail, yielding ops in order. Returns the final cursor via the generator's
1469
- * return value; persist it for the next incremental pull.
1258
+ * Revoke all active sessions (emergency use)
1259
+ *
1260
+ * Invalidates all token sessions for the authenticated user.
1261
+ * Use for security cleanup or when sessions are stuck.
1262
+ *
1263
+ * @returns Revocation result with count of revoked sessions
1264
+ * @example
1265
+ * ```typescript
1266
+ * const result = await client.tokens.revokeAllSessions();
1267
+ * console.log(`Revoked ${result.sessions_revoked} sessions`);
1268
+ * ```
1470
1269
  */
1471
- async *changesAll(since) {
1472
- let cursor = since;
1473
- let page;
1474
- do {
1475
- page = await this.changes({ since: cursor });
1476
- for (const op of page.changes) yield op;
1477
- cursor = page.cursor;
1478
- } while (page.has_more);
1479
- return { cursor: page.cursor, head: page.head };
1270
+ async revokeAllSessions() {
1271
+ return this.client.request("POST", "/tokens/revoke-all-sessions/");
1480
1272
  }
1481
- // -- Core request machinery ----------------------------------------------
1482
- async request(method, path, opts = {}) {
1483
- const url = `${this.baseUrl}${path}${opts.query ? `?${opts.query}` : ""}`;
1484
- const headers = {};
1485
- if (opts.auth !== false) {
1486
- if (this.keyKind === "account") {
1487
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1488
- } else {
1489
- headers["API-Key"] = this.apiKey;
1490
- if (this.apiPin) headers["API-Pin"] = this.apiPin;
1491
- }
1492
- }
1493
- if (opts.body !== void 0) headers["Content-Type"] = "application/json";
1494
- if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
1495
- let res;
1496
- try {
1497
- res = await this.fetchImpl(url, {
1498
- method,
1499
- headers,
1500
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
1501
- });
1502
- } catch (cause) {
1503
- throw new OwNetworkError(`OnlyWorlds request failed: ${method} ${path}`, cause);
1504
- }
1505
- if (!res.ok) throw await errorFromResponse(res);
1506
- if (res.status === 204 || opts.allowEmpty) {
1507
- const text = await res.text();
1508
- return text ? JSON.parse(text) : null;
1509
- }
1510
- const parsed = await res.json();
1511
- if (opts.replayAware && parsed && typeof parsed === "object") {
1512
- parsed.wasReplay = readReplayHeader(res.headers);
1513
- }
1514
- return parsed;
1273
+ /**
1274
+ * Get public encryption info (no auth required)
1275
+ *
1276
+ * Returns algorithm details and example code for client-side decryption.
1277
+ * Public endpoint - can be called without authentication.
1278
+ *
1279
+ * @returns Encryption algorithm and implementation details
1280
+ * @example
1281
+ * ```typescript
1282
+ * const info = await client.tokens.getEncryptionInfo();
1283
+ * console.log('Algorithm:', info.algorithm);
1284
+ * console.log('Key derivation:', info.key_derivation);
1285
+ * console.log(info.javascript_example);
1286
+ * ```
1287
+ */
1288
+ async getEncryptionInfo() {
1289
+ return this.client.request("GET", "/tokens/encryption-info/");
1515
1290
  }
1516
1291
  };
1517
- var READ_ONLY_FIELDS = ["world", "type", "created_at", "updated_at", "change_seq"];
1518
- function sanitizePayload(payload) {
1519
- if (!payload || typeof payload !== "object") return payload;
1520
- const rest = { ...payload };
1521
- for (const field of READ_ONLY_FIELDS) delete rest[field];
1522
- return rest;
1523
- }
1524
- function readReplayHeader(headers) {
1525
- const v = headers.get("Idempotent-Replay");
1526
- return v != null && v.toLowerCase() === "true";
1527
- }
1528
- function mintUuid() {
1529
- const c = globalThis.crypto;
1530
- if (c && typeof c.randomUUID === "function") return c.randomUUID();
1531
- const bytes = new Uint8Array(16);
1532
- if (c && typeof c.getRandomValues === "function") {
1533
- c.getRandomValues(bytes);
1534
- } else {
1535
- for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);
1536
- }
1537
- bytes[6] = bytes[6] & 15 | 64;
1538
- bytes[8] = bytes[8] & 63 | 128;
1539
- const hex = [];
1540
- for (let i = 0; i < 256; i++) hex.push((i + 256).toString(16).slice(1));
1541
- return hex[bytes[0]] + hex[bytes[1]] + hex[bytes[2]] + hex[bytes[3]] + "-" + hex[bytes[4]] + hex[bytes[5]] + "-" + hex[bytes[6]] + hex[bytes[7]] + "-" + hex[bytes[8]] + hex[bytes[9]] + "-" + hex[bytes[10]] + hex[bytes[11]] + hex[bytes[12]] + hex[bytes[13]] + hex[bytes[14]] + hex[bytes[15]];
1542
- }
1543
- function buildQuery(params) {
1544
- const q = new URLSearchParams();
1545
- for (const [k, v] of Object.entries(params)) {
1546
- if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
1547
- }
1548
- return q.toString();
1549
- }
1550
1292
 
1551
- // src/v2/types.generated.ts
1552
- var ELEMENT_TYPES = ["ability", "character", "collective", "construct", "creature", "event", "family", "institution", "language", "law", "location", "map", "marker", "narrative", "object", "phenomenon", "pin", "relation", "species", "title", "trait", "zone"];
1553
-
1554
- // src/v2/types.ts
1555
- var SPATIAL_TYPES = ["map", "pin", "marker", "zone"];
1556
- // Annotate the CommonJS export names for ESM import in node:
1557
- 0 && (module.exports = {
1293
+ // src/token-types.ts
1294
+ var GameTier = /* @__PURE__ */ ((GameTier2) => {
1295
+ GameTier2["FREE"] = "free";
1296
+ GameTier2["SILVER"] = "silver";
1297
+ GameTier2["GOLD"] = "gold";
1298
+ GameTier2["PLATINUM"] = "platinum";
1299
+ GameTier2["DIAMOND"] = "diamond";
1300
+ GameTier2["DELUXE"] = "deluxe";
1301
+ return GameTier2;
1302
+ })(GameTier || {});
1303
+ export {
1304
+ ELEMENT_FAMILIES,
1558
1305
  ELEMENT_ICONS,
1559
1306
  ELEMENT_LABELS,
1560
1307
  ELEMENT_SECTIONS,
1561
1308
  ELEMENT_TYPES,
1562
- ElementType,
1309
+ FAMILY_COLORS,
1310
+ FAMILY_ORDER,
1563
1311
  FIELD_SCHEMA,
1564
1312
  GameTier,
1565
1313
  ONLYWORLDS_VERSION,
1566
- OnlyWorldsClient,
1567
1314
  OwApiError,
1568
1315
  OwNetworkError,
1569
1316
  OwV2Client,
1570
1317
  SPATIAL_TYPES,
1571
- createAnyElementId,
1572
- createElementId,
1573
- createElementIds,
1318
+ TokenResource,
1574
1319
  detectKeyKind,
1320
+ elementColor,
1575
1321
  errorFromResponse,
1322
+ familyOf,
1576
1323
  getElementIcon,
1577
1324
  getElementLabel,
1578
- getElementSections,
1579
1325
  isDemoKey,
1580
1326
  kindCanWrite,
1581
1327
  parseEnvelope,
1582
1328
  pinExpectation
1583
- });
1329
+ };