@onlyworlds/sdk 2.1.3 → 2.2.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/dist/index.js CHANGED
@@ -23,7 +23,6 @@ __export(index_exports, {
23
23
  ELEMENT_ICONS: () => ELEMENT_ICONS,
24
24
  ELEMENT_LABELS: () => ELEMENT_LABELS,
25
25
  ELEMENT_SECTIONS: () => ELEMENT_SECTIONS,
26
- ELEMENT_UNICODE_ICONS: () => ELEMENT_UNICODE_ICONS,
27
26
  ElementType: () => ElementType,
28
27
  FIELD_SCHEMA: () => FIELD_SCHEMA,
29
28
  GameTier: () => GameTier,
@@ -34,318 +33,10 @@ __export(index_exports, {
34
33
  createElementIds: () => createElementIds,
35
34
  getElementIcon: () => getElementIcon,
36
35
  getElementLabel: () => getElementLabel,
37
- getElementSections: () => getElementSections,
38
- getElementUnicodeIcon: () => getElementUnicodeIcon
36
+ getElementSections: () => getElementSections
39
37
  });
40
38
  module.exports = __toCommonJS(index_exports);
41
39
 
42
- // src/token-resource.ts
43
- var TokenResource = class {
44
- constructor(client) {
45
- this.client = client;
46
- }
47
- /**
48
- * Get current token status for authenticated user
49
- *
50
- * Returns daily token allowance, usage, and availability.
51
- * Matches base-tool's checkStatus() pattern.
52
- *
53
- * @returns Current token status
54
- * @example
55
- * ```typescript
56
- * const status = await client.tokens.getStatus();
57
- * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`);
58
- * console.log(`Used today: ${status.tokens_used_today}`);
59
- * console.log(`Active sessions: ${status.sessions_active}`);
60
- * ```
61
- */
62
- async getStatus() {
63
- return this.client.request("GET", "/tokens/status/");
64
- }
65
- /**
66
- * Consume tokens for service usage
67
- *
68
- * Reports token consumption to track daily usage. Allows consumption even if
69
- * exceeds available tokens (tracks as debt), but warns via error field.
70
- * Matches base-tool's reportUsage() pattern.
71
- *
72
- * @param params - Token consumption parameters
73
- * @returns Consumption result with updated balance
74
- * @example
75
- * ```typescript
76
- * const result = await client.tokens.consume({
77
- * amount: 500,
78
- * service: 'worldbuilding_tool',
79
- * metadata: {
80
- * feature: 'character_generation',
81
- * model: 'gpt-4',
82
- * prompt_tokens: 300,
83
- * completion_tokens: 200
84
- * }
85
- * });
86
- *
87
- * if (result.error) {
88
- * console.warn('Token warning:', result.error);
89
- * }
90
- * console.log(`${result.tokens_remaining} tokens remaining`);
91
- * ```
92
- */
93
- async consume(params) {
94
- return this.client.request("POST", "/tokens/consume/", {
95
- body: {
96
- amount: params.amount,
97
- service: params.service || "sdk_client",
98
- session_id: params.sessionId ?? null,
99
- metadata: params.metadata ?? null
100
- }
101
- });
102
- }
103
- /**
104
- * Get encrypted OpenAI API key (advanced use case)
105
- *
106
- * Requires minimum 100 tokens available. Creates a 1-hour session for tracking.
107
- * Returns encrypted key that must be decrypted client-side using Fernet.
108
- *
109
- * See base-tool/src/llm/token-service.ts:99-155 for full implementation example
110
- * including client-side decryption with the 'fernet' npm package.
111
- *
112
- * @returns Encrypted access key and session info
113
- * @throws Error if insufficient tokens (< 100)
114
- * @example
115
- * ```typescript
116
- * // Get encrypted key
117
- * const access = await client.tokens.getAccessKey();
118
- *
119
- * // Decrypt using fernet library (see base-tool for full example)
120
- * // 1. Derive key from world ID using SHA-256
121
- * // 2. Use 'fernet' npm package to decrypt
122
- * // 3. Use decrypted OpenAI key for direct API calls
123
- * // 4. Report usage with access.session_id
124
- *
125
- * console.log('Session:', access.session_id);
126
- * console.log('Expires:', access.expires_at);
127
- * ```
128
- */
129
- async getAccessKey() {
130
- return this.client.request("GET", "/tokens/access-key/");
131
- }
132
- /**
133
- * Revoke a specific token session
134
- *
135
- * Invalidates the session ID obtained from getAccessKey().
136
- * Use when cleaning up or on logout.
137
- *
138
- * @param sessionId - Session ID to revoke
139
- * @returns Revocation result
140
- * @example
141
- * ```typescript
142
- * await client.tokens.revokeSession('session-id-here');
143
- * ```
144
- */
145
- async revokeSession(sessionId) {
146
- return this.client.request(
147
- "POST",
148
- `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
149
- );
150
- }
151
- /**
152
- * Revoke all active sessions (emergency use)
153
- *
154
- * Invalidates all token sessions for the authenticated user.
155
- * Use for security cleanup or when sessions are stuck.
156
- *
157
- * @returns Revocation result with count of revoked sessions
158
- * @example
159
- * ```typescript
160
- * const result = await client.tokens.revokeAllSessions();
161
- * console.log(`Revoked ${result.sessions_revoked} sessions`);
162
- * ```
163
- */
164
- async revokeAllSessions() {
165
- return this.client.request("POST", "/tokens/revoke-all-sessions/");
166
- }
167
- /**
168
- * Get public encryption info (no auth required)
169
- *
170
- * Returns algorithm details and example code for client-side decryption.
171
- * Public endpoint - can be called without authentication.
172
- *
173
- * @returns Encryption algorithm and implementation details
174
- * @example
175
- * ```typescript
176
- * const info = await client.tokens.getEncryptionInfo();
177
- * console.log('Algorithm:', info.algorithm);
178
- * console.log('Key derivation:', info.key_derivation);
179
- * console.log(info.javascript_example);
180
- * ```
181
- */
182
- async getEncryptionInfo() {
183
- return this.client.request("GET", "/tokens/encryption-info/");
184
- }
185
- };
186
-
187
- // src/client.ts
188
- var Resource = class {
189
- constructor(client, elementType) {
190
- this.client = client;
191
- this.elementType = elementType;
192
- }
193
- async list(options) {
194
- const response = await this.client.request("GET", `/${this.elementType}/`, { params: options });
195
- if (Array.isArray(response)) {
196
- return {
197
- count: response.length,
198
- next: null,
199
- previous: null,
200
- results: response
201
- };
202
- }
203
- return response;
204
- }
205
- async get(id) {
206
- return this.client.request("GET", `/${this.elementType}/${id}/`);
207
- }
208
- async create(data) {
209
- const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data;
210
- return this.client.request("POST", `/${this.elementType}/`, { body });
211
- }
212
- async update(id, data) {
213
- const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data;
214
- return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body });
215
- }
216
- async delete(id) {
217
- return this.client.request("DELETE", `/${this.elementType}/${id}/`);
218
- }
219
- /**
220
- * Round Pin coordinates to integers (API requirement)
221
- * @private
222
- */
223
- roundPinCoordinates(data) {
224
- const rounded = { ...data };
225
- if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x);
226
- if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y);
227
- if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z);
228
- return rounded;
229
- }
230
- };
231
- var WorldResource = class {
232
- constructor(client) {
233
- this.client = client;
234
- }
235
- /**
236
- * Get the world associated with the current API key
237
- * Returns the world directly (not wrapped in pagination)
238
- */
239
- async get() {
240
- return this.client.request("GET", "/world/");
241
- }
242
- /**
243
- * Update the current world
244
- */
245
- async update(data) {
246
- return this.client.request("PATCH", "/world/", { body: data });
247
- }
248
- };
249
- var OnlyWorldsClient = class {
250
- constructor(config) {
251
- this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi";
252
- this.headers = {
253
- "Content-Type": "application/json",
254
- "API-Key": config.apiKey,
255
- "API-Pin": config.apiPin
256
- };
257
- this.worlds = new WorldResource(this);
258
- this.tokens = new TokenResource(this);
259
- this.abilities = new Resource(this, "ability");
260
- this.characters = new Resource(this, "character");
261
- this.collectives = new Resource(this, "collective");
262
- this.constructs = new Resource(this, "construct");
263
- this.creatures = new Resource(this, "creature");
264
- this.events = new Resource(this, "event");
265
- this.families = new Resource(this, "family");
266
- this.institutions = new Resource(this, "institution");
267
- this.languages = new Resource(this, "language");
268
- this.laws = new Resource(this, "law");
269
- this.locations = new Resource(this, "location");
270
- this.maps = new Resource(this, "map");
271
- this.markers = new Resource(this, "marker");
272
- this.narratives = new Resource(this, "narrative");
273
- this.objects = new Resource(this, "object");
274
- this.phenomena = new Resource(this, "phenomenon");
275
- this.pins = new Resource(this, "pin");
276
- this.relations = new Resource(this, "relation");
277
- this.species = new Resource(this, "species");
278
- this.titles = new Resource(this, "title");
279
- this.traits = new Resource(this, "trait");
280
- this.zones = new Resource(this, "zone");
281
- }
282
- /**
283
- * Make a request to the OnlyWorlds API
284
- */
285
- async request(method, path, options) {
286
- const url = new URL(`${this.baseUrl}${path}`);
287
- if (options?.params) {
288
- Object.entries(options.params).forEach(([key, value]) => {
289
- if (value !== void 0 && value !== null) {
290
- url.searchParams.append(key, String(value));
291
- }
292
- });
293
- }
294
- const fetchOptions = {
295
- method,
296
- headers: this.headers
297
- };
298
- if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) {
299
- fetchOptions.body = JSON.stringify(options.body);
300
- }
301
- const response = await fetch(url.toString(), fetchOptions);
302
- if (!response.ok) {
303
- let errorMessage = `API Error ${response.status}`;
304
- try {
305
- const errorText = await response.text();
306
- if (errorText) {
307
- try {
308
- const errorJson = JSON.parse(errorText);
309
- if (Array.isArray(errorJson.detail)) {
310
- const validationErrors = errorJson.detail.map((err) => {
311
- const location = err.loc ? err.loc.join(".") : "unknown";
312
- return `${location}: ${err.msg}`;
313
- }).join("; ");
314
- errorMessage += `: ${validationErrors}`;
315
- } else {
316
- errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`;
317
- }
318
- } catch {
319
- errorMessage += `: ${errorText}`;
320
- }
321
- }
322
- } catch {
323
- }
324
- throw new Error(errorMessage);
325
- }
326
- if (response.status === 204) {
327
- return void 0;
328
- }
329
- return response.json();
330
- }
331
- /**
332
- * Helper to convert nested objects to _id/_ids format
333
- */
334
- static prepareInput(data) {
335
- const result = { ...data };
336
- for (const [key, value] of Object.entries(result)) {
337
- if (value && typeof value === "object" && "id" in value) {
338
- delete result[key];
339
- result[`${key}_id`] = value.id;
340
- } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
341
- delete result[key];
342
- result[`${key}_ids`] = value.map((item) => item.id);
343
- }
344
- }
345
- return result;
346
- }
347
- };
348
-
349
40
  // src/types.ts
350
41
  var ElementType = /* @__PURE__ */ ((ElementType2) => {
351
42
  ElementType2["Ability"] = "ability";
@@ -530,36 +221,6 @@ var ELEMENT_ICONS = {
530
221
  ["trait" /* Trait */]: "flaky",
531
222
  ["zone" /* Zone */]: "architecture"
532
223
  };
533
- var ELEMENT_UNICODE_ICONS = {
534
- ["ability" /* Ability */]: "\u2728",
535
- ["character" /* Character */]: "\u{1F464}",
536
- ["collective" /* Collective */]: "\u{1F465}",
537
- ["construct" /* Construct */]: "\u2699\uFE0F",
538
- ["creature" /* Creature */]: "\u{1F43E}",
539
- ["event" /* Event */]: "\u{1F4C5}",
540
- ["family" /* Family */]: "\u{1F468}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F466}",
541
- ["institution" /* Institution */]: "\u{1F3DB}\uFE0F",
542
- ["language" /* Language */]: "\u{1F4AC}",
543
- ["law" /* Law */]: "\u2696\uFE0F",
544
- ["location" /* Location */]: "\u{1F3F0}",
545
- ["map" /* Map */]: "\u{1F5FA}\uFE0F",
546
- ["marker" /* Marker */]: "\u{1F4CD}",
547
- ["narrative" /* Narrative */]: "\u{1F4D6}",
548
- ["object" /* Object */]: "\u{1F4E6}",
549
- ["phenomenon" /* Phenomenon */]: "\u26A1",
550
- ["pin" /* Pin */]: "\u{1F4CC}",
551
- ["relation" /* Relation */]: "\u{1F517}",
552
- ["species" /* Species */]: "\u{1F9EC}",
553
- ["title" /* Title */]: "\u{1F451}",
554
- ["trait" /* Trait */]: "\u2744\uFE0F",
555
- ["zone" /* Zone */]: "\u{1F5FA}\uFE0F"
556
- };
557
- function getElementIcon(elementType) {
558
- return ELEMENT_ICONS[elementType];
559
- }
560
- function getElementUnicodeIcon(elementType) {
561
- return ELEMENT_UNICODE_ICONS[elementType];
562
- }
563
224
  var FIELD_SCHEMA = {
564
225
  ability: {
565
226
  // Base fields (shared by all elements)
@@ -1154,15 +815,368 @@ var FIELD_SCHEMA = {
1154
815
  principles: { type: "multi_link", target: "construct" }
1155
816
  }
1156
817
  };
1157
- function createElementId(id) {
1158
- return id;
1159
- }
1160
- function createElementIds(ids) {
1161
- return ids;
1162
- }
1163
- function createAnyElementId(id) {
1164
- return id;
1165
- }
818
+ function createElementId(id) {
819
+ return id;
820
+ }
821
+ function createElementIds(ids) {
822
+ return ids;
823
+ }
824
+ function createAnyElementId(id) {
825
+ return id;
826
+ }
827
+
828
+ // src/token-resource.ts
829
+ var TokenResource = class {
830
+ constructor(client) {
831
+ this.client = client;
832
+ }
833
+ /**
834
+ * Get current token status for authenticated user
835
+ *
836
+ * Returns daily token allowance, usage, and availability.
837
+ * Matches base-tool's checkStatus() pattern.
838
+ *
839
+ * @returns Current token status
840
+ * @example
841
+ * ```typescript
842
+ * const status = await client.tokens.getStatus();
843
+ * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`);
844
+ * console.log(`Used today: ${status.tokens_used_today}`);
845
+ * console.log(`Active sessions: ${status.sessions_active}`);
846
+ * ```
847
+ */
848
+ async getStatus() {
849
+ return this.client.request("GET", "/tokens/status/");
850
+ }
851
+ /**
852
+ * Consume tokens for service usage
853
+ *
854
+ * Reports token consumption to track daily usage. Allows consumption even if
855
+ * exceeds available tokens (tracks as debt), but warns via error field.
856
+ * Matches base-tool's reportUsage() pattern.
857
+ *
858
+ * @param params - Token consumption parameters
859
+ * @returns Consumption result with updated balance
860
+ * @example
861
+ * ```typescript
862
+ * const result = await client.tokens.consume({
863
+ * amount: 500,
864
+ * service: 'worldbuilding_tool',
865
+ * metadata: {
866
+ * feature: 'character_generation',
867
+ * model: 'gpt-4',
868
+ * prompt_tokens: 300,
869
+ * completion_tokens: 200
870
+ * }
871
+ * });
872
+ *
873
+ * if (result.error) {
874
+ * console.warn('Token warning:', result.error);
875
+ * }
876
+ * console.log(`${result.tokens_remaining} tokens remaining`);
877
+ * ```
878
+ */
879
+ async consume(params) {
880
+ return this.client.request("POST", "/tokens/consume/", {
881
+ body: {
882
+ amount: params.amount,
883
+ service: params.service || "sdk_client",
884
+ session_id: params.sessionId ?? null,
885
+ metadata: params.metadata ?? null
886
+ }
887
+ });
888
+ }
889
+ /**
890
+ * Get encrypted OpenAI API key (advanced use case)
891
+ *
892
+ * Requires minimum 100 tokens available. Creates a 1-hour session for tracking.
893
+ * Returns encrypted key that must be decrypted client-side using Fernet.
894
+ *
895
+ * See base-tool/src/llm/token-service.ts:99-155 for full implementation example
896
+ * including client-side decryption with the 'fernet' npm package.
897
+ *
898
+ * @returns Encrypted access key and session info
899
+ * @throws Error if insufficient tokens (< 100)
900
+ * @example
901
+ * ```typescript
902
+ * // Get encrypted key
903
+ * const access = await client.tokens.getAccessKey();
904
+ *
905
+ * // Decrypt using fernet library (see base-tool for full example)
906
+ * // 1. Derive key from world ID using SHA-256
907
+ * // 2. Use 'fernet' npm package to decrypt
908
+ * // 3. Use decrypted OpenAI key for direct API calls
909
+ * // 4. Report usage with access.session_id
910
+ *
911
+ * console.log('Session:', access.session_id);
912
+ * console.log('Expires:', access.expires_at);
913
+ * ```
914
+ */
915
+ async getAccessKey() {
916
+ return this.client.request("GET", "/tokens/access-key/");
917
+ }
918
+ /**
919
+ * Revoke a specific token session
920
+ *
921
+ * Invalidates the session ID obtained from getAccessKey().
922
+ * Use when cleaning up or on logout.
923
+ *
924
+ * @param sessionId - Session ID to revoke
925
+ * @returns Revocation result
926
+ * @example
927
+ * ```typescript
928
+ * await client.tokens.revokeSession('session-id-here');
929
+ * ```
930
+ */
931
+ async revokeSession(sessionId) {
932
+ return this.client.request(
933
+ "POST",
934
+ `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}`
935
+ );
936
+ }
937
+ /**
938
+ * Revoke all active sessions (emergency use)
939
+ *
940
+ * Invalidates all token sessions for the authenticated user.
941
+ * Use for security cleanup or when sessions are stuck.
942
+ *
943
+ * @returns Revocation result with count of revoked sessions
944
+ * @example
945
+ * ```typescript
946
+ * const result = await client.tokens.revokeAllSessions();
947
+ * console.log(`Revoked ${result.sessions_revoked} sessions`);
948
+ * ```
949
+ */
950
+ async revokeAllSessions() {
951
+ return this.client.request("POST", "/tokens/revoke-all-sessions/");
952
+ }
953
+ /**
954
+ * Get public encryption info (no auth required)
955
+ *
956
+ * Returns algorithm details and example code for client-side decryption.
957
+ * Public endpoint - can be called without authentication.
958
+ *
959
+ * @returns Encryption algorithm and implementation details
960
+ * @example
961
+ * ```typescript
962
+ * const info = await client.tokens.getEncryptionInfo();
963
+ * console.log('Algorithm:', info.algorithm);
964
+ * console.log('Key derivation:', info.key_derivation);
965
+ * console.log(info.javascript_example);
966
+ * ```
967
+ */
968
+ async getEncryptionInfo() {
969
+ return this.client.request("GET", "/tokens/encryption-info/");
970
+ }
971
+ };
972
+
973
+ // src/client.ts
974
+ var Resource = class {
975
+ constructor(client, elementType) {
976
+ this.client = client;
977
+ this.elementType = elementType;
978
+ }
979
+ async list(options) {
980
+ const response = await this.client.request("GET", `/${this.elementType}/`, { params: options });
981
+ if (Array.isArray(response)) {
982
+ return {
983
+ count: response.length,
984
+ next: null,
985
+ previous: null,
986
+ results: response
987
+ };
988
+ }
989
+ return response;
990
+ }
991
+ async get(id) {
992
+ return this.client.request("GET", `/${this.elementType}/${id}/`);
993
+ }
994
+ async create(data) {
995
+ let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
996
+ if (this.elementType === "pin") body = this.roundPinCoordinates(body);
997
+ return this.client.request("POST", `/${this.elementType}/`, { body });
998
+ }
999
+ async update(id, data) {
1000
+ let body = OnlyWorldsClient.prepareRelations(data, this.elementType);
1001
+ if (this.elementType === "pin") body = this.roundPinCoordinates(body);
1002
+ return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body });
1003
+ }
1004
+ async delete(id) {
1005
+ return this.client.request("DELETE", `/${this.elementType}/${id}/`);
1006
+ }
1007
+ /**
1008
+ * Round Pin coordinates to integers (API requirement)
1009
+ * @private
1010
+ */
1011
+ roundPinCoordinates(data) {
1012
+ const rounded = { ...data };
1013
+ if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x);
1014
+ if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y);
1015
+ if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z);
1016
+ return rounded;
1017
+ }
1018
+ };
1019
+ var WorldResource = class {
1020
+ constructor(client) {
1021
+ this.client = client;
1022
+ }
1023
+ /**
1024
+ * Get the world associated with the current API key
1025
+ * Returns the world directly (not wrapped in pagination)
1026
+ */
1027
+ async get() {
1028
+ return this.client.request("GET", "/world/");
1029
+ }
1030
+ /**
1031
+ * Update the current world
1032
+ */
1033
+ async update(data) {
1034
+ return this.client.request("PATCH", "/world/", { body: data });
1035
+ }
1036
+ };
1037
+ var OnlyWorldsClient = class {
1038
+ constructor(config) {
1039
+ this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi";
1040
+ this.headers = {
1041
+ "Content-Type": "application/json",
1042
+ "API-Key": config.apiKey,
1043
+ "API-Pin": config.apiPin
1044
+ };
1045
+ this.worlds = new WorldResource(this);
1046
+ this.tokens = new TokenResource(this);
1047
+ this.abilities = new Resource(this, "ability");
1048
+ this.characters = new Resource(this, "character");
1049
+ this.collectives = new Resource(this, "collective");
1050
+ this.constructs = new Resource(this, "construct");
1051
+ this.creatures = new Resource(this, "creature");
1052
+ this.events = new Resource(this, "event");
1053
+ this.families = new Resource(this, "family");
1054
+ this.institutions = new Resource(this, "institution");
1055
+ this.languages = new Resource(this, "language");
1056
+ this.laws = new Resource(this, "law");
1057
+ this.locations = new Resource(this, "location");
1058
+ this.maps = new Resource(this, "map");
1059
+ this.markers = new Resource(this, "marker");
1060
+ this.narratives = new Resource(this, "narrative");
1061
+ this.objects = new Resource(this, "object");
1062
+ this.phenomena = new Resource(this, "phenomenon");
1063
+ this.pins = new Resource(this, "pin");
1064
+ this.relations = new Resource(this, "relation");
1065
+ this.species = new Resource(this, "species");
1066
+ this.titles = new Resource(this, "title");
1067
+ this.traits = new Resource(this, "trait");
1068
+ this.zones = new Resource(this, "zone");
1069
+ }
1070
+ /**
1071
+ * Make a request to the OnlyWorlds API
1072
+ */
1073
+ async request(method, path, options) {
1074
+ const url = new URL(`${this.baseUrl}${path}`);
1075
+ if (options?.params) {
1076
+ Object.entries(options.params).forEach(([key, value]) => {
1077
+ if (value !== void 0 && value !== null) {
1078
+ url.searchParams.append(key, String(value));
1079
+ }
1080
+ });
1081
+ }
1082
+ const fetchOptions = {
1083
+ method,
1084
+ headers: this.headers
1085
+ };
1086
+ if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) {
1087
+ fetchOptions.body = JSON.stringify(options.body);
1088
+ }
1089
+ const response = await fetch(url.toString(), fetchOptions);
1090
+ if (!response.ok) {
1091
+ let errorMessage = `API Error ${response.status}`;
1092
+ try {
1093
+ const errorText = await response.text();
1094
+ if (errorText) {
1095
+ try {
1096
+ const errorJson = JSON.parse(errorText);
1097
+ if (Array.isArray(errorJson.detail)) {
1098
+ const validationErrors = errorJson.detail.map((err) => {
1099
+ const location = err.loc ? err.loc.join(".") : "unknown";
1100
+ return `${location}: ${err.msg}`;
1101
+ }).join("; ");
1102
+ errorMessage += `: ${validationErrors}`;
1103
+ } else {
1104
+ errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`;
1105
+ }
1106
+ } catch {
1107
+ errorMessage += `: ${errorText}`;
1108
+ }
1109
+ }
1110
+ } catch {
1111
+ }
1112
+ throw new Error(errorMessage);
1113
+ }
1114
+ if (response.status === 204) {
1115
+ return void 0;
1116
+ }
1117
+ return response.json();
1118
+ }
1119
+ /**
1120
+ * Helper to convert nested objects to _id/_ids format (legacy method)
1121
+ * @deprecated Use prepareRelations instead - it's called automatically in create/update
1122
+ */
1123
+ static prepareInput(data) {
1124
+ const result = { ...data };
1125
+ for (const [key, value] of Object.entries(result)) {
1126
+ if (value && typeof value === "object" && !Array.isArray(value) && "id" in value) {
1127
+ delete result[key];
1128
+ result[`${key}_id`] = value.id;
1129
+ } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
1130
+ delete result[key];
1131
+ result[`${key}_ids`] = value.map((item) => item.id);
1132
+ }
1133
+ }
1134
+ return result;
1135
+ }
1136
+ /**
1137
+ * Convert relation fields to API format (_id/_ids suffix)
1138
+ *
1139
+ * The OnlyWorlds API expects:
1140
+ * - single_link fields: fieldname_id (e.g., birthplace_id)
1141
+ * - multi_link fields: fieldname_ids (e.g., species_ids)
1142
+ *
1143
+ * This method auto-converts based on FIELD_SCHEMA:
1144
+ * - { species: ["id1", "id2"] } → { species_ids: ["id1", "id2"] }
1145
+ * - { birthplace: "location-id" } → { birthplace_id: "location-id" }
1146
+ * - { species: [{id: "id1", name: "X"}] } → { species_ids: ["id1"] }
1147
+ *
1148
+ * Called automatically by create() and update() methods.
1149
+ */
1150
+ static prepareRelations(data, elementType) {
1151
+ const schema = FIELD_SCHEMA[elementType];
1152
+ if (!schema) return data;
1153
+ const result = { ...data };
1154
+ for (const [key, value] of Object.entries(result)) {
1155
+ const fieldDef = schema[key];
1156
+ if (!fieldDef) continue;
1157
+ if (fieldDef.type === "single_link") {
1158
+ delete result[key];
1159
+ if (value && typeof value === "object" && "id" in value) {
1160
+ result[`${key}_id`] = value.id;
1161
+ } else if (typeof value === "string" || value === null) {
1162
+ result[`${key}_id`] = value;
1163
+ }
1164
+ } else if (fieldDef.type === "multi_link") {
1165
+ delete result[key];
1166
+ if (Array.isArray(value)) {
1167
+ if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) {
1168
+ result[`${key}_ids`] = value.map((item) => item.id);
1169
+ } else {
1170
+ result[`${key}_ids`] = value;
1171
+ }
1172
+ } else {
1173
+ result[`${key}_ids`] = [];
1174
+ }
1175
+ }
1176
+ }
1177
+ return result;
1178
+ }
1179
+ };
1166
1180
 
1167
1181
  // src/token-types.ts
1168
1182
  var GameTier = /* @__PURE__ */ ((GameTier2) => {
@@ -1174,12 +1188,52 @@ var GameTier = /* @__PURE__ */ ((GameTier2) => {
1174
1188
  GameTier2["DELUXE"] = "deluxe";
1175
1189
  return GameTier2;
1176
1190
  })(GameTier || {});
1191
+
1192
+ // src/icon-utils.ts
1193
+ var PLURAL_TO_SINGULAR = {
1194
+ abilities: "ability" /* Ability */,
1195
+ characters: "character" /* Character */,
1196
+ collectives: "collective" /* Collective */,
1197
+ constructs: "construct" /* Construct */,
1198
+ creatures: "creature" /* Creature */,
1199
+ events: "event" /* Event */,
1200
+ families: "family" /* Family */,
1201
+ institutions: "institution" /* Institution */,
1202
+ languages: "language" /* Language */,
1203
+ laws: "law" /* Law */,
1204
+ locations: "location" /* Location */,
1205
+ maps: "map" /* Map */,
1206
+ markers: "marker" /* Marker */,
1207
+ narratives: "narrative" /* Narrative */,
1208
+ objects: "object" /* Object */,
1209
+ phenomena: "phenomenon" /* Phenomenon */,
1210
+ // irregular plural
1211
+ pins: "pin" /* Pin */,
1212
+ relations: "relation" /* Relation */,
1213
+ species: "species" /* Species */,
1214
+ // same singular/plural
1215
+ titles: "title" /* Title */,
1216
+ traits: "trait" /* Trait */,
1217
+ zones: "zone" /* Zone */
1218
+ };
1219
+ function getElementIcon(type) {
1220
+ const lower = type.toLowerCase();
1221
+ if (lower in PLURAL_TO_SINGULAR) {
1222
+ return ELEMENT_ICONS[PLURAL_TO_SINGULAR[lower]];
1223
+ }
1224
+ const singular = Object.values(ElementType).find(
1225
+ (et) => et.toLowerCase() === lower
1226
+ );
1227
+ if (singular) {
1228
+ return ELEMENT_ICONS[singular];
1229
+ }
1230
+ return "help_outline";
1231
+ }
1177
1232
  // Annotate the CommonJS export names for ESM import in node:
1178
1233
  0 && (module.exports = {
1179
1234
  ELEMENT_ICONS,
1180
1235
  ELEMENT_LABELS,
1181
1236
  ELEMENT_SECTIONS,
1182
- ELEMENT_UNICODE_ICONS,
1183
1237
  ElementType,
1184
1238
  FIELD_SCHEMA,
1185
1239
  GameTier,
@@ -1190,6 +1244,5 @@ var GameTier = /* @__PURE__ */ ((GameTier2) => {
1190
1244
  createElementIds,
1191
1245
  getElementIcon,
1192
1246
  getElementLabel,
1193
- getElementSections,
1194
- getElementUnicodeIcon
1247
+ getElementSections
1195
1248
  });