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