@onlyworlds/sdk
Advanced tools
+353
-307
@@ -42,309 +42,2 @@ "use strict"; | ||
| // src/token-resource.ts | ||
| var TokenResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get current token status for authenticated user | ||
| * | ||
| * Returns daily token allowance, usage, and availability. | ||
| * Matches base-tool's checkStatus() pattern. | ||
| * | ||
| * @returns Current token status | ||
| * @example | ||
| * ```typescript | ||
| * const status = await client.tokens.getStatus(); | ||
| * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`); | ||
| * console.log(`Used today: ${status.tokens_used_today}`); | ||
| * console.log(`Active sessions: ${status.sessions_active}`); | ||
| * ``` | ||
| */ | ||
| async getStatus() { | ||
| return this.client.request("GET", "/tokens/status/"); | ||
| } | ||
| /** | ||
| * Consume tokens for service usage | ||
| * | ||
| * Reports token consumption to track daily usage. Allows consumption even if | ||
| * exceeds available tokens (tracks as debt), but warns via error field. | ||
| * Matches base-tool's reportUsage() pattern. | ||
| * | ||
| * @param params - Token consumption parameters | ||
| * @returns Consumption result with updated balance | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.consume({ | ||
| * amount: 500, | ||
| * service: 'worldbuilding_tool', | ||
| * metadata: { | ||
| * feature: 'character_generation', | ||
| * model: 'gpt-4', | ||
| * prompt_tokens: 300, | ||
| * completion_tokens: 200 | ||
| * } | ||
| * }); | ||
| * | ||
| * if (result.error) { | ||
| * console.warn('Token warning:', result.error); | ||
| * } | ||
| * console.log(`${result.tokens_remaining} tokens remaining`); | ||
| * ``` | ||
| */ | ||
| async consume(params) { | ||
| return this.client.request("POST", "/tokens/consume/", { | ||
| body: { | ||
| amount: params.amount, | ||
| service: params.service || "sdk_client", | ||
| session_id: params.sessionId ?? null, | ||
| metadata: params.metadata ?? null | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Get encrypted OpenAI API key (advanced use case) | ||
| * | ||
| * Requires minimum 100 tokens available. Creates a 1-hour session for tracking. | ||
| * Returns encrypted key that must be decrypted client-side using Fernet. | ||
| * | ||
| * See base-tool/src/llm/token-service.ts:99-155 for full implementation example | ||
| * including client-side decryption with the 'fernet' npm package. | ||
| * | ||
| * @returns Encrypted access key and session info | ||
| * @throws Error if insufficient tokens (< 100) | ||
| * @example | ||
| * ```typescript | ||
| * // Get encrypted key | ||
| * const access = await client.tokens.getAccessKey(); | ||
| * | ||
| * // Decrypt using fernet library (see base-tool for full example) | ||
| * // 1. Derive key from world ID using SHA-256 | ||
| * // 2. Use 'fernet' npm package to decrypt | ||
| * // 3. Use decrypted OpenAI key for direct API calls | ||
| * // 4. Report usage with access.session_id | ||
| * | ||
| * console.log('Session:', access.session_id); | ||
| * console.log('Expires:', access.expires_at); | ||
| * ``` | ||
| */ | ||
| async getAccessKey() { | ||
| return this.client.request("GET", "/tokens/access-key/"); | ||
| } | ||
| /** | ||
| * Revoke a specific token session | ||
| * | ||
| * Invalidates the session ID obtained from getAccessKey(). | ||
| * Use when cleaning up or on logout. | ||
| * | ||
| * @param sessionId - Session ID to revoke | ||
| * @returns Revocation result | ||
| * @example | ||
| * ```typescript | ||
| * await client.tokens.revokeSession('session-id-here'); | ||
| * ``` | ||
| */ | ||
| async revokeSession(sessionId) { | ||
| return this.client.request( | ||
| "POST", | ||
| `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}` | ||
| ); | ||
| } | ||
| /** | ||
| * Revoke all active sessions (emergency use) | ||
| * | ||
| * Invalidates all token sessions for the authenticated user. | ||
| * Use for security cleanup or when sessions are stuck. | ||
| * | ||
| * @returns Revocation result with count of revoked sessions | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.revokeAllSessions(); | ||
| * console.log(`Revoked ${result.sessions_revoked} sessions`); | ||
| * ``` | ||
| */ | ||
| async revokeAllSessions() { | ||
| return this.client.request("POST", "/tokens/revoke-all-sessions/"); | ||
| } | ||
| /** | ||
| * Get public encryption info (no auth required) | ||
| * | ||
| * Returns algorithm details and example code for client-side decryption. | ||
| * Public endpoint - can be called without authentication. | ||
| * | ||
| * @returns Encryption algorithm and implementation details | ||
| * @example | ||
| * ```typescript | ||
| * const info = await client.tokens.getEncryptionInfo(); | ||
| * console.log('Algorithm:', info.algorithm); | ||
| * console.log('Key derivation:', info.key_derivation); | ||
| * console.log(info.javascript_example); | ||
| * ``` | ||
| */ | ||
| async getEncryptionInfo() { | ||
| return this.client.request("GET", "/tokens/encryption-info/"); | ||
| } | ||
| }; | ||
| // src/client.ts | ||
| var Resource = class { | ||
| constructor(client, elementType) { | ||
| this.client = client; | ||
| this.elementType = elementType; | ||
| } | ||
| async list(options) { | ||
| const response = await this.client.request("GET", `/${this.elementType}/`, { params: options }); | ||
| if (Array.isArray(response)) { | ||
| return { | ||
| count: response.length, | ||
| next: null, | ||
| previous: null, | ||
| results: response | ||
| }; | ||
| } | ||
| return response; | ||
| } | ||
| async get(id) { | ||
| return this.client.request("GET", `/${this.elementType}/${id}/`); | ||
| } | ||
| async create(data) { | ||
| const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data; | ||
| return this.client.request("POST", `/${this.elementType}/`, { body }); | ||
| } | ||
| async update(id, data) { | ||
| const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data; | ||
| return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body }); | ||
| } | ||
| async delete(id) { | ||
| return this.client.request("DELETE", `/${this.elementType}/${id}/`); | ||
| } | ||
| /** | ||
| * Round Pin coordinates to integers (API requirement) | ||
| * @private | ||
| */ | ||
| roundPinCoordinates(data) { | ||
| const rounded = { ...data }; | ||
| if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x); | ||
| if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y); | ||
| if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z); | ||
| return rounded; | ||
| } | ||
| }; | ||
| var WorldResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get the world associated with the current API key | ||
| * Returns the world directly (not wrapped in pagination) | ||
| */ | ||
| async get() { | ||
| return this.client.request("GET", "/world/"); | ||
| } | ||
| /** | ||
| * Update the current world | ||
| */ | ||
| async update(data) { | ||
| return this.client.request("PATCH", "/world/", { body: data }); | ||
| } | ||
| }; | ||
| var OnlyWorldsClient = class { | ||
| constructor(config) { | ||
| this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi"; | ||
| this.headers = { | ||
| "Content-Type": "application/json", | ||
| "API-Key": config.apiKey, | ||
| "API-Pin": config.apiPin | ||
| }; | ||
| this.worlds = new WorldResource(this); | ||
| this.tokens = new TokenResource(this); | ||
| this.abilities = new Resource(this, "ability"); | ||
| this.characters = new Resource(this, "character"); | ||
| this.collectives = new Resource(this, "collective"); | ||
| this.constructs = new Resource(this, "construct"); | ||
| this.creatures = new Resource(this, "creature"); | ||
| this.events = new Resource(this, "event"); | ||
| this.families = new Resource(this, "family"); | ||
| this.institutions = new Resource(this, "institution"); | ||
| this.languages = new Resource(this, "language"); | ||
| this.laws = new Resource(this, "law"); | ||
| this.locations = new Resource(this, "location"); | ||
| this.maps = new Resource(this, "map"); | ||
| this.markers = new Resource(this, "marker"); | ||
| this.narratives = new Resource(this, "narrative"); | ||
| this.objects = new Resource(this, "object"); | ||
| this.phenomena = new Resource(this, "phenomenon"); | ||
| this.pins = new Resource(this, "pin"); | ||
| this.relations = new Resource(this, "relation"); | ||
| this.species = new Resource(this, "species"); | ||
| this.titles = new Resource(this, "title"); | ||
| this.traits = new Resource(this, "trait"); | ||
| this.zones = new Resource(this, "zone"); | ||
| } | ||
| /** | ||
| * Make a request to the OnlyWorlds API | ||
| */ | ||
| async request(method, path, options) { | ||
| const url = new URL(`${this.baseUrl}${path}`); | ||
| if (options?.params) { | ||
| Object.entries(options.params).forEach(([key, value]) => { | ||
| if (value !== void 0 && value !== null) { | ||
| url.searchParams.append(key, String(value)); | ||
| } | ||
| }); | ||
| } | ||
| const fetchOptions = { | ||
| method, | ||
| headers: this.headers | ||
| }; | ||
| if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) { | ||
| fetchOptions.body = JSON.stringify(options.body); | ||
| } | ||
| const response = await fetch(url.toString(), fetchOptions); | ||
| if (!response.ok) { | ||
| let errorMessage = `API Error ${response.status}`; | ||
| try { | ||
| const errorText = await response.text(); | ||
| if (errorText) { | ||
| try { | ||
| const errorJson = JSON.parse(errorText); | ||
| if (Array.isArray(errorJson.detail)) { | ||
| const validationErrors = errorJson.detail.map((err) => { | ||
| const location = err.loc ? err.loc.join(".") : "unknown"; | ||
| return `${location}: ${err.msg}`; | ||
| }).join("; "); | ||
| errorMessage += `: ${validationErrors}`; | ||
| } else { | ||
| errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`; | ||
| } | ||
| } catch { | ||
| errorMessage += `: ${errorText}`; | ||
| } | ||
| } | ||
| } catch { | ||
| } | ||
| throw new Error(errorMessage); | ||
| } | ||
| if (response.status === 204) { | ||
| return void 0; | ||
| } | ||
| return response.json(); | ||
| } | ||
| /** | ||
| * Helper to convert nested objects to _id/_ids format | ||
| */ | ||
| static prepareInput(data) { | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| if (value && typeof value === "object" && "id" in value) { | ||
| delete result[key]; | ||
| result[`${key}_id`] = value.id; | ||
| } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| delete result[key]; | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| // src/types.ts | ||
@@ -1168,2 +861,355 @@ var ElementType = /* @__PURE__ */ ((ElementType2) => { | ||
| // src/token-resource.ts | ||
| var TokenResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get current token status for authenticated user | ||
| * | ||
| * Returns daily token allowance, usage, and availability. | ||
| * Matches base-tool's checkStatus() pattern. | ||
| * | ||
| * @returns Current token status | ||
| * @example | ||
| * ```typescript | ||
| * const status = await client.tokens.getStatus(); | ||
| * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`); | ||
| * console.log(`Used today: ${status.tokens_used_today}`); | ||
| * console.log(`Active sessions: ${status.sessions_active}`); | ||
| * ``` | ||
| */ | ||
| async getStatus() { | ||
| return this.client.request("GET", "/tokens/status/"); | ||
| } | ||
| /** | ||
| * Consume tokens for service usage | ||
| * | ||
| * Reports token consumption to track daily usage. Allows consumption even if | ||
| * exceeds available tokens (tracks as debt), but warns via error field. | ||
| * Matches base-tool's reportUsage() pattern. | ||
| * | ||
| * @param params - Token consumption parameters | ||
| * @returns Consumption result with updated balance | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.consume({ | ||
| * amount: 500, | ||
| * service: 'worldbuilding_tool', | ||
| * metadata: { | ||
| * feature: 'character_generation', | ||
| * model: 'gpt-4', | ||
| * prompt_tokens: 300, | ||
| * completion_tokens: 200 | ||
| * } | ||
| * }); | ||
| * | ||
| * if (result.error) { | ||
| * console.warn('Token warning:', result.error); | ||
| * } | ||
| * console.log(`${result.tokens_remaining} tokens remaining`); | ||
| * ``` | ||
| */ | ||
| async consume(params) { | ||
| return this.client.request("POST", "/tokens/consume/", { | ||
| body: { | ||
| amount: params.amount, | ||
| service: params.service || "sdk_client", | ||
| session_id: params.sessionId ?? null, | ||
| metadata: params.metadata ?? null | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Get encrypted OpenAI API key (advanced use case) | ||
| * | ||
| * Requires minimum 100 tokens available. Creates a 1-hour session for tracking. | ||
| * Returns encrypted key that must be decrypted client-side using Fernet. | ||
| * | ||
| * See base-tool/src/llm/token-service.ts:99-155 for full implementation example | ||
| * including client-side decryption with the 'fernet' npm package. | ||
| * | ||
| * @returns Encrypted access key and session info | ||
| * @throws Error if insufficient tokens (< 100) | ||
| * @example | ||
| * ```typescript | ||
| * // Get encrypted key | ||
| * const access = await client.tokens.getAccessKey(); | ||
| * | ||
| * // Decrypt using fernet library (see base-tool for full example) | ||
| * // 1. Derive key from world ID using SHA-256 | ||
| * // 2. Use 'fernet' npm package to decrypt | ||
| * // 3. Use decrypted OpenAI key for direct API calls | ||
| * // 4. Report usage with access.session_id | ||
| * | ||
| * console.log('Session:', access.session_id); | ||
| * console.log('Expires:', access.expires_at); | ||
| * ``` | ||
| */ | ||
| async getAccessKey() { | ||
| return this.client.request("GET", "/tokens/access-key/"); | ||
| } | ||
| /** | ||
| * Revoke a specific token session | ||
| * | ||
| * Invalidates the session ID obtained from getAccessKey(). | ||
| * Use when cleaning up or on logout. | ||
| * | ||
| * @param sessionId - Session ID to revoke | ||
| * @returns Revocation result | ||
| * @example | ||
| * ```typescript | ||
| * await client.tokens.revokeSession('session-id-here'); | ||
| * ``` | ||
| */ | ||
| async revokeSession(sessionId) { | ||
| return this.client.request( | ||
| "POST", | ||
| `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}` | ||
| ); | ||
| } | ||
| /** | ||
| * Revoke all active sessions (emergency use) | ||
| * | ||
| * Invalidates all token sessions for the authenticated user. | ||
| * Use for security cleanup or when sessions are stuck. | ||
| * | ||
| * @returns Revocation result with count of revoked sessions | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.revokeAllSessions(); | ||
| * console.log(`Revoked ${result.sessions_revoked} sessions`); | ||
| * ``` | ||
| */ | ||
| async revokeAllSessions() { | ||
| return this.client.request("POST", "/tokens/revoke-all-sessions/"); | ||
| } | ||
| /** | ||
| * Get public encryption info (no auth required) | ||
| * | ||
| * Returns algorithm details and example code for client-side decryption. | ||
| * Public endpoint - can be called without authentication. | ||
| * | ||
| * @returns Encryption algorithm and implementation details | ||
| * @example | ||
| * ```typescript | ||
| * const info = await client.tokens.getEncryptionInfo(); | ||
| * console.log('Algorithm:', info.algorithm); | ||
| * console.log('Key derivation:', info.key_derivation); | ||
| * console.log(info.javascript_example); | ||
| * ``` | ||
| */ | ||
| async getEncryptionInfo() { | ||
| return this.client.request("GET", "/tokens/encryption-info/"); | ||
| } | ||
| }; | ||
| // src/client.ts | ||
| var Resource = class { | ||
| constructor(client, elementType) { | ||
| this.client = client; | ||
| this.elementType = elementType; | ||
| } | ||
| async list(options) { | ||
| const response = await this.client.request("GET", `/${this.elementType}/`, { params: options }); | ||
| if (Array.isArray(response)) { | ||
| return { | ||
| count: response.length, | ||
| next: null, | ||
| previous: null, | ||
| results: response | ||
| }; | ||
| } | ||
| return response; | ||
| } | ||
| async get(id) { | ||
| return this.client.request("GET", `/${this.elementType}/${id}/`); | ||
| } | ||
| async create(data) { | ||
| let body = OnlyWorldsClient.prepareRelations(data, this.elementType); | ||
| if (this.elementType === "pin") body = this.roundPinCoordinates(body); | ||
| return this.client.request("POST", `/${this.elementType}/`, { body }); | ||
| } | ||
| async update(id, data) { | ||
| let body = OnlyWorldsClient.prepareRelations(data, this.elementType); | ||
| if (this.elementType === "pin") body = this.roundPinCoordinates(body); | ||
| return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body }); | ||
| } | ||
| async delete(id) { | ||
| return this.client.request("DELETE", `/${this.elementType}/${id}/`); | ||
| } | ||
| /** | ||
| * Round Pin coordinates to integers (API requirement) | ||
| * @private | ||
| */ | ||
| roundPinCoordinates(data) { | ||
| const rounded = { ...data }; | ||
| if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x); | ||
| if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y); | ||
| if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z); | ||
| return rounded; | ||
| } | ||
| }; | ||
| var WorldResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get the world associated with the current API key | ||
| * Returns the world directly (not wrapped in pagination) | ||
| */ | ||
| async get() { | ||
| return this.client.request("GET", "/world/"); | ||
| } | ||
| /** | ||
| * Update the current world | ||
| */ | ||
| async update(data) { | ||
| return this.client.request("PATCH", "/world/", { body: data }); | ||
| } | ||
| }; | ||
| var OnlyWorldsClient = class { | ||
| constructor(config) { | ||
| this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi"; | ||
| this.headers = { | ||
| "Content-Type": "application/json", | ||
| "API-Key": config.apiKey, | ||
| "API-Pin": config.apiPin | ||
| }; | ||
| this.worlds = new WorldResource(this); | ||
| this.tokens = new TokenResource(this); | ||
| this.abilities = new Resource(this, "ability"); | ||
| this.characters = new Resource(this, "character"); | ||
| this.collectives = new Resource(this, "collective"); | ||
| this.constructs = new Resource(this, "construct"); | ||
| this.creatures = new Resource(this, "creature"); | ||
| this.events = new Resource(this, "event"); | ||
| this.families = new Resource(this, "family"); | ||
| this.institutions = new Resource(this, "institution"); | ||
| this.languages = new Resource(this, "language"); | ||
| this.laws = new Resource(this, "law"); | ||
| this.locations = new Resource(this, "location"); | ||
| this.maps = new Resource(this, "map"); | ||
| this.markers = new Resource(this, "marker"); | ||
| this.narratives = new Resource(this, "narrative"); | ||
| this.objects = new Resource(this, "object"); | ||
| this.phenomena = new Resource(this, "phenomenon"); | ||
| this.pins = new Resource(this, "pin"); | ||
| this.relations = new Resource(this, "relation"); | ||
| this.species = new Resource(this, "species"); | ||
| this.titles = new Resource(this, "title"); | ||
| this.traits = new Resource(this, "trait"); | ||
| this.zones = new Resource(this, "zone"); | ||
| } | ||
| /** | ||
| * Make a request to the OnlyWorlds API | ||
| */ | ||
| async request(method, path, options) { | ||
| const url = new URL(`${this.baseUrl}${path}`); | ||
| if (options?.params) { | ||
| Object.entries(options.params).forEach(([key, value]) => { | ||
| if (value !== void 0 && value !== null) { | ||
| url.searchParams.append(key, String(value)); | ||
| } | ||
| }); | ||
| } | ||
| const fetchOptions = { | ||
| method, | ||
| headers: this.headers | ||
| }; | ||
| if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) { | ||
| fetchOptions.body = JSON.stringify(options.body); | ||
| } | ||
| const response = await fetch(url.toString(), fetchOptions); | ||
| if (!response.ok) { | ||
| let errorMessage = `API Error ${response.status}`; | ||
| try { | ||
| const errorText = await response.text(); | ||
| if (errorText) { | ||
| try { | ||
| const errorJson = JSON.parse(errorText); | ||
| if (Array.isArray(errorJson.detail)) { | ||
| const validationErrors = errorJson.detail.map((err) => { | ||
| const location = err.loc ? err.loc.join(".") : "unknown"; | ||
| return `${location}: ${err.msg}`; | ||
| }).join("; "); | ||
| errorMessage += `: ${validationErrors}`; | ||
| } else { | ||
| errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`; | ||
| } | ||
| } catch { | ||
| errorMessage += `: ${errorText}`; | ||
| } | ||
| } | ||
| } catch { | ||
| } | ||
| throw new Error(errorMessage); | ||
| } | ||
| if (response.status === 204) { | ||
| return void 0; | ||
| } | ||
| return response.json(); | ||
| } | ||
| /** | ||
| * Helper to convert nested objects to _id/_ids format (legacy method) | ||
| * @deprecated Use prepareRelations instead - it's called automatically in create/update | ||
| */ | ||
| static prepareInput(data) { | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| if (value && typeof value === "object" && !Array.isArray(value) && "id" in value) { | ||
| delete result[key]; | ||
| result[`${key}_id`] = value.id; | ||
| } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| delete result[key]; | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Convert relation fields to API format (_id/_ids suffix) | ||
| * | ||
| * The OnlyWorlds API expects: | ||
| * - single_link fields: fieldname_id (e.g., birthplace_id) | ||
| * - multi_link fields: fieldname_ids (e.g., species_ids) | ||
| * | ||
| * This method auto-converts based on FIELD_SCHEMA: | ||
| * - { species: ["id1", "id2"] } → { species_ids: ["id1", "id2"] } | ||
| * - { birthplace: "location-id" } → { birthplace_id: "location-id" } | ||
| * - { species: [{id: "id1", name: "X"}] } → { species_ids: ["id1"] } | ||
| * | ||
| * Called automatically by create() and update() methods. | ||
| */ | ||
| static prepareRelations(data, elementType) { | ||
| const schema = FIELD_SCHEMA[elementType]; | ||
| if (!schema) return data; | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| const fieldDef = schema[key]; | ||
| if (!fieldDef) continue; | ||
| if (fieldDef.type === "single_link") { | ||
| delete result[key]; | ||
| if (value && typeof value === "object" && "id" in value) { | ||
| result[`${key}_id`] = value.id; | ||
| } else if (typeof value === "string" || value === null) { | ||
| result[`${key}_id`] = value; | ||
| } | ||
| } else if (fieldDef.type === "multi_link") { | ||
| delete result[key]; | ||
| if (Array.isArray(value)) { | ||
| if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } else { | ||
| result[`${key}_ids`] = value; | ||
| } | ||
| } else { | ||
| result[`${key}_ids`] = []; | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| // src/token-types.ts | ||
@@ -1170,0 +1216,0 @@ var GameTier = /* @__PURE__ */ ((GameTier2) => { |
+353
-307
@@ -1,308 +0,1 @@ | ||
| // src/token-resource.ts | ||
| var TokenResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get current token status for authenticated user | ||
| * | ||
| * Returns daily token allowance, usage, and availability. | ||
| * Matches base-tool's checkStatus() pattern. | ||
| * | ||
| * @returns Current token status | ||
| * @example | ||
| * ```typescript | ||
| * const status = await client.tokens.getStatus(); | ||
| * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`); | ||
| * console.log(`Used today: ${status.tokens_used_today}`); | ||
| * console.log(`Active sessions: ${status.sessions_active}`); | ||
| * ``` | ||
| */ | ||
| async getStatus() { | ||
| return this.client.request("GET", "/tokens/status/"); | ||
| } | ||
| /** | ||
| * Consume tokens for service usage | ||
| * | ||
| * Reports token consumption to track daily usage. Allows consumption even if | ||
| * exceeds available tokens (tracks as debt), but warns via error field. | ||
| * Matches base-tool's reportUsage() pattern. | ||
| * | ||
| * @param params - Token consumption parameters | ||
| * @returns Consumption result with updated balance | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.consume({ | ||
| * amount: 500, | ||
| * service: 'worldbuilding_tool', | ||
| * metadata: { | ||
| * feature: 'character_generation', | ||
| * model: 'gpt-4', | ||
| * prompt_tokens: 300, | ||
| * completion_tokens: 200 | ||
| * } | ||
| * }); | ||
| * | ||
| * if (result.error) { | ||
| * console.warn('Token warning:', result.error); | ||
| * } | ||
| * console.log(`${result.tokens_remaining} tokens remaining`); | ||
| * ``` | ||
| */ | ||
| async consume(params) { | ||
| return this.client.request("POST", "/tokens/consume/", { | ||
| body: { | ||
| amount: params.amount, | ||
| service: params.service || "sdk_client", | ||
| session_id: params.sessionId ?? null, | ||
| metadata: params.metadata ?? null | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Get encrypted OpenAI API key (advanced use case) | ||
| * | ||
| * Requires minimum 100 tokens available. Creates a 1-hour session for tracking. | ||
| * Returns encrypted key that must be decrypted client-side using Fernet. | ||
| * | ||
| * See base-tool/src/llm/token-service.ts:99-155 for full implementation example | ||
| * including client-side decryption with the 'fernet' npm package. | ||
| * | ||
| * @returns Encrypted access key and session info | ||
| * @throws Error if insufficient tokens (< 100) | ||
| * @example | ||
| * ```typescript | ||
| * // Get encrypted key | ||
| * const access = await client.tokens.getAccessKey(); | ||
| * | ||
| * // Decrypt using fernet library (see base-tool for full example) | ||
| * // 1. Derive key from world ID using SHA-256 | ||
| * // 2. Use 'fernet' npm package to decrypt | ||
| * // 3. Use decrypted OpenAI key for direct API calls | ||
| * // 4. Report usage with access.session_id | ||
| * | ||
| * console.log('Session:', access.session_id); | ||
| * console.log('Expires:', access.expires_at); | ||
| * ``` | ||
| */ | ||
| async getAccessKey() { | ||
| return this.client.request("GET", "/tokens/access-key/"); | ||
| } | ||
| /** | ||
| * Revoke a specific token session | ||
| * | ||
| * Invalidates the session ID obtained from getAccessKey(). | ||
| * Use when cleaning up or on logout. | ||
| * | ||
| * @param sessionId - Session ID to revoke | ||
| * @returns Revocation result | ||
| * @example | ||
| * ```typescript | ||
| * await client.tokens.revokeSession('session-id-here'); | ||
| * ``` | ||
| */ | ||
| async revokeSession(sessionId) { | ||
| return this.client.request( | ||
| "POST", | ||
| `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}` | ||
| ); | ||
| } | ||
| /** | ||
| * Revoke all active sessions (emergency use) | ||
| * | ||
| * Invalidates all token sessions for the authenticated user. | ||
| * Use for security cleanup or when sessions are stuck. | ||
| * | ||
| * @returns Revocation result with count of revoked sessions | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.revokeAllSessions(); | ||
| * console.log(`Revoked ${result.sessions_revoked} sessions`); | ||
| * ``` | ||
| */ | ||
| async revokeAllSessions() { | ||
| return this.client.request("POST", "/tokens/revoke-all-sessions/"); | ||
| } | ||
| /** | ||
| * Get public encryption info (no auth required) | ||
| * | ||
| * Returns algorithm details and example code for client-side decryption. | ||
| * Public endpoint - can be called without authentication. | ||
| * | ||
| * @returns Encryption algorithm and implementation details | ||
| * @example | ||
| * ```typescript | ||
| * const info = await client.tokens.getEncryptionInfo(); | ||
| * console.log('Algorithm:', info.algorithm); | ||
| * console.log('Key derivation:', info.key_derivation); | ||
| * console.log(info.javascript_example); | ||
| * ``` | ||
| */ | ||
| async getEncryptionInfo() { | ||
| return this.client.request("GET", "/tokens/encryption-info/"); | ||
| } | ||
| }; | ||
| // src/client.ts | ||
| var Resource = class { | ||
| constructor(client, elementType) { | ||
| this.client = client; | ||
| this.elementType = elementType; | ||
| } | ||
| async list(options) { | ||
| const response = await this.client.request("GET", `/${this.elementType}/`, { params: options }); | ||
| if (Array.isArray(response)) { | ||
| return { | ||
| count: response.length, | ||
| next: null, | ||
| previous: null, | ||
| results: response | ||
| }; | ||
| } | ||
| return response; | ||
| } | ||
| async get(id) { | ||
| return this.client.request("GET", `/${this.elementType}/${id}/`); | ||
| } | ||
| async create(data) { | ||
| const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data; | ||
| return this.client.request("POST", `/${this.elementType}/`, { body }); | ||
| } | ||
| async update(id, data) { | ||
| const body = this.elementType === "pin" ? this.roundPinCoordinates(data) : data; | ||
| return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body }); | ||
| } | ||
| async delete(id) { | ||
| return this.client.request("DELETE", `/${this.elementType}/${id}/`); | ||
| } | ||
| /** | ||
| * Round Pin coordinates to integers (API requirement) | ||
| * @private | ||
| */ | ||
| roundPinCoordinates(data) { | ||
| const rounded = { ...data }; | ||
| if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x); | ||
| if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y); | ||
| if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z); | ||
| return rounded; | ||
| } | ||
| }; | ||
| var WorldResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get the world associated with the current API key | ||
| * Returns the world directly (not wrapped in pagination) | ||
| */ | ||
| async get() { | ||
| return this.client.request("GET", "/world/"); | ||
| } | ||
| /** | ||
| * Update the current world | ||
| */ | ||
| async update(data) { | ||
| return this.client.request("PATCH", "/world/", { body: data }); | ||
| } | ||
| }; | ||
| var OnlyWorldsClient = class { | ||
| constructor(config) { | ||
| this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi"; | ||
| this.headers = { | ||
| "Content-Type": "application/json", | ||
| "API-Key": config.apiKey, | ||
| "API-Pin": config.apiPin | ||
| }; | ||
| this.worlds = new WorldResource(this); | ||
| this.tokens = new TokenResource(this); | ||
| this.abilities = new Resource(this, "ability"); | ||
| this.characters = new Resource(this, "character"); | ||
| this.collectives = new Resource(this, "collective"); | ||
| this.constructs = new Resource(this, "construct"); | ||
| this.creatures = new Resource(this, "creature"); | ||
| this.events = new Resource(this, "event"); | ||
| this.families = new Resource(this, "family"); | ||
| this.institutions = new Resource(this, "institution"); | ||
| this.languages = new Resource(this, "language"); | ||
| this.laws = new Resource(this, "law"); | ||
| this.locations = new Resource(this, "location"); | ||
| this.maps = new Resource(this, "map"); | ||
| this.markers = new Resource(this, "marker"); | ||
| this.narratives = new Resource(this, "narrative"); | ||
| this.objects = new Resource(this, "object"); | ||
| this.phenomena = new Resource(this, "phenomenon"); | ||
| this.pins = new Resource(this, "pin"); | ||
| this.relations = new Resource(this, "relation"); | ||
| this.species = new Resource(this, "species"); | ||
| this.titles = new Resource(this, "title"); | ||
| this.traits = new Resource(this, "trait"); | ||
| this.zones = new Resource(this, "zone"); | ||
| } | ||
| /** | ||
| * Make a request to the OnlyWorlds API | ||
| */ | ||
| async request(method, path, options) { | ||
| const url = new URL(`${this.baseUrl}${path}`); | ||
| if (options?.params) { | ||
| Object.entries(options.params).forEach(([key, value]) => { | ||
| if (value !== void 0 && value !== null) { | ||
| url.searchParams.append(key, String(value)); | ||
| } | ||
| }); | ||
| } | ||
| const fetchOptions = { | ||
| method, | ||
| headers: this.headers | ||
| }; | ||
| if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) { | ||
| fetchOptions.body = JSON.stringify(options.body); | ||
| } | ||
| const response = await fetch(url.toString(), fetchOptions); | ||
| if (!response.ok) { | ||
| let errorMessage = `API Error ${response.status}`; | ||
| try { | ||
| const errorText = await response.text(); | ||
| if (errorText) { | ||
| try { | ||
| const errorJson = JSON.parse(errorText); | ||
| if (Array.isArray(errorJson.detail)) { | ||
| const validationErrors = errorJson.detail.map((err) => { | ||
| const location = err.loc ? err.loc.join(".") : "unknown"; | ||
| return `${location}: ${err.msg}`; | ||
| }).join("; "); | ||
| errorMessage += `: ${validationErrors}`; | ||
| } else { | ||
| errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`; | ||
| } | ||
| } catch { | ||
| errorMessage += `: ${errorText}`; | ||
| } | ||
| } | ||
| } catch { | ||
| } | ||
| throw new Error(errorMessage); | ||
| } | ||
| if (response.status === 204) { | ||
| return void 0; | ||
| } | ||
| return response.json(); | ||
| } | ||
| /** | ||
| * Helper to convert nested objects to _id/_ids format | ||
| */ | ||
| static prepareInput(data) { | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| if (value && typeof value === "object" && "id" in value) { | ||
| delete result[key]; | ||
| result[`${key}_id`] = value.id; | ||
| } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| delete result[key]; | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| // src/types.ts | ||
@@ -1126,2 +819,355 @@ var ElementType = /* @__PURE__ */ ((ElementType2) => { | ||
| // src/token-resource.ts | ||
| var TokenResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get current token status for authenticated user | ||
| * | ||
| * Returns daily token allowance, usage, and availability. | ||
| * Matches base-tool's checkStatus() pattern. | ||
| * | ||
| * @returns Current token status | ||
| * @example | ||
| * ```typescript | ||
| * const status = await client.tokens.getStatus(); | ||
| * console.log(`Available: ${status.tokens_available_today}/${status.token_rating}`); | ||
| * console.log(`Used today: ${status.tokens_used_today}`); | ||
| * console.log(`Active sessions: ${status.sessions_active}`); | ||
| * ``` | ||
| */ | ||
| async getStatus() { | ||
| return this.client.request("GET", "/tokens/status/"); | ||
| } | ||
| /** | ||
| * Consume tokens for service usage | ||
| * | ||
| * Reports token consumption to track daily usage. Allows consumption even if | ||
| * exceeds available tokens (tracks as debt), but warns via error field. | ||
| * Matches base-tool's reportUsage() pattern. | ||
| * | ||
| * @param params - Token consumption parameters | ||
| * @returns Consumption result with updated balance | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.consume({ | ||
| * amount: 500, | ||
| * service: 'worldbuilding_tool', | ||
| * metadata: { | ||
| * feature: 'character_generation', | ||
| * model: 'gpt-4', | ||
| * prompt_tokens: 300, | ||
| * completion_tokens: 200 | ||
| * } | ||
| * }); | ||
| * | ||
| * if (result.error) { | ||
| * console.warn('Token warning:', result.error); | ||
| * } | ||
| * console.log(`${result.tokens_remaining} tokens remaining`); | ||
| * ``` | ||
| */ | ||
| async consume(params) { | ||
| return this.client.request("POST", "/tokens/consume/", { | ||
| body: { | ||
| amount: params.amount, | ||
| service: params.service || "sdk_client", | ||
| session_id: params.sessionId ?? null, | ||
| metadata: params.metadata ?? null | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Get encrypted OpenAI API key (advanced use case) | ||
| * | ||
| * Requires minimum 100 tokens available. Creates a 1-hour session for tracking. | ||
| * Returns encrypted key that must be decrypted client-side using Fernet. | ||
| * | ||
| * See base-tool/src/llm/token-service.ts:99-155 for full implementation example | ||
| * including client-side decryption with the 'fernet' npm package. | ||
| * | ||
| * @returns Encrypted access key and session info | ||
| * @throws Error if insufficient tokens (< 100) | ||
| * @example | ||
| * ```typescript | ||
| * // Get encrypted key | ||
| * const access = await client.tokens.getAccessKey(); | ||
| * | ||
| * // Decrypt using fernet library (see base-tool for full example) | ||
| * // 1. Derive key from world ID using SHA-256 | ||
| * // 2. Use 'fernet' npm package to decrypt | ||
| * // 3. Use decrypted OpenAI key for direct API calls | ||
| * // 4. Report usage with access.session_id | ||
| * | ||
| * console.log('Session:', access.session_id); | ||
| * console.log('Expires:', access.expires_at); | ||
| * ``` | ||
| */ | ||
| async getAccessKey() { | ||
| return this.client.request("GET", "/tokens/access-key/"); | ||
| } | ||
| /** | ||
| * Revoke a specific token session | ||
| * | ||
| * Invalidates the session ID obtained from getAccessKey(). | ||
| * Use when cleaning up or on logout. | ||
| * | ||
| * @param sessionId - Session ID to revoke | ||
| * @returns Revocation result | ||
| * @example | ||
| * ```typescript | ||
| * await client.tokens.revokeSession('session-id-here'); | ||
| * ``` | ||
| */ | ||
| async revokeSession(sessionId) { | ||
| return this.client.request( | ||
| "POST", | ||
| `/tokens/revoke-session/?session_id=${encodeURIComponent(sessionId)}` | ||
| ); | ||
| } | ||
| /** | ||
| * Revoke all active sessions (emergency use) | ||
| * | ||
| * Invalidates all token sessions for the authenticated user. | ||
| * Use for security cleanup or when sessions are stuck. | ||
| * | ||
| * @returns Revocation result with count of revoked sessions | ||
| * @example | ||
| * ```typescript | ||
| * const result = await client.tokens.revokeAllSessions(); | ||
| * console.log(`Revoked ${result.sessions_revoked} sessions`); | ||
| * ``` | ||
| */ | ||
| async revokeAllSessions() { | ||
| return this.client.request("POST", "/tokens/revoke-all-sessions/"); | ||
| } | ||
| /** | ||
| * Get public encryption info (no auth required) | ||
| * | ||
| * Returns algorithm details and example code for client-side decryption. | ||
| * Public endpoint - can be called without authentication. | ||
| * | ||
| * @returns Encryption algorithm and implementation details | ||
| * @example | ||
| * ```typescript | ||
| * const info = await client.tokens.getEncryptionInfo(); | ||
| * console.log('Algorithm:', info.algorithm); | ||
| * console.log('Key derivation:', info.key_derivation); | ||
| * console.log(info.javascript_example); | ||
| * ``` | ||
| */ | ||
| async getEncryptionInfo() { | ||
| return this.client.request("GET", "/tokens/encryption-info/"); | ||
| } | ||
| }; | ||
| // src/client.ts | ||
| var Resource = class { | ||
| constructor(client, elementType) { | ||
| this.client = client; | ||
| this.elementType = elementType; | ||
| } | ||
| async list(options) { | ||
| const response = await this.client.request("GET", `/${this.elementType}/`, { params: options }); | ||
| if (Array.isArray(response)) { | ||
| return { | ||
| count: response.length, | ||
| next: null, | ||
| previous: null, | ||
| results: response | ||
| }; | ||
| } | ||
| return response; | ||
| } | ||
| async get(id) { | ||
| return this.client.request("GET", `/${this.elementType}/${id}/`); | ||
| } | ||
| async create(data) { | ||
| let body = OnlyWorldsClient.prepareRelations(data, this.elementType); | ||
| if (this.elementType === "pin") body = this.roundPinCoordinates(body); | ||
| return this.client.request("POST", `/${this.elementType}/`, { body }); | ||
| } | ||
| async update(id, data) { | ||
| let body = OnlyWorldsClient.prepareRelations(data, this.elementType); | ||
| if (this.elementType === "pin") body = this.roundPinCoordinates(body); | ||
| return this.client.request("PATCH", `/${this.elementType}/${id}/`, { body }); | ||
| } | ||
| async delete(id) { | ||
| return this.client.request("DELETE", `/${this.elementType}/${id}/`); | ||
| } | ||
| /** | ||
| * Round Pin coordinates to integers (API requirement) | ||
| * @private | ||
| */ | ||
| roundPinCoordinates(data) { | ||
| const rounded = { ...data }; | ||
| if (typeof rounded.x === "number") rounded.x = Math.round(rounded.x); | ||
| if (typeof rounded.y === "number") rounded.y = Math.round(rounded.y); | ||
| if (typeof rounded.z === "number") rounded.z = Math.round(rounded.z); | ||
| return rounded; | ||
| } | ||
| }; | ||
| var WorldResource = class { | ||
| constructor(client) { | ||
| this.client = client; | ||
| } | ||
| /** | ||
| * Get the world associated with the current API key | ||
| * Returns the world directly (not wrapped in pagination) | ||
| */ | ||
| async get() { | ||
| return this.client.request("GET", "/world/"); | ||
| } | ||
| /** | ||
| * Update the current world | ||
| */ | ||
| async update(data) { | ||
| return this.client.request("PATCH", "/world/", { body: data }); | ||
| } | ||
| }; | ||
| var OnlyWorldsClient = class { | ||
| constructor(config) { | ||
| this.baseUrl = config.baseUrl || "https://www.onlyworlds.com/api/worldapi"; | ||
| this.headers = { | ||
| "Content-Type": "application/json", | ||
| "API-Key": config.apiKey, | ||
| "API-Pin": config.apiPin | ||
| }; | ||
| this.worlds = new WorldResource(this); | ||
| this.tokens = new TokenResource(this); | ||
| this.abilities = new Resource(this, "ability"); | ||
| this.characters = new Resource(this, "character"); | ||
| this.collectives = new Resource(this, "collective"); | ||
| this.constructs = new Resource(this, "construct"); | ||
| this.creatures = new Resource(this, "creature"); | ||
| this.events = new Resource(this, "event"); | ||
| this.families = new Resource(this, "family"); | ||
| this.institutions = new Resource(this, "institution"); | ||
| this.languages = new Resource(this, "language"); | ||
| this.laws = new Resource(this, "law"); | ||
| this.locations = new Resource(this, "location"); | ||
| this.maps = new Resource(this, "map"); | ||
| this.markers = new Resource(this, "marker"); | ||
| this.narratives = new Resource(this, "narrative"); | ||
| this.objects = new Resource(this, "object"); | ||
| this.phenomena = new Resource(this, "phenomenon"); | ||
| this.pins = new Resource(this, "pin"); | ||
| this.relations = new Resource(this, "relation"); | ||
| this.species = new Resource(this, "species"); | ||
| this.titles = new Resource(this, "title"); | ||
| this.traits = new Resource(this, "trait"); | ||
| this.zones = new Resource(this, "zone"); | ||
| } | ||
| /** | ||
| * Make a request to the OnlyWorlds API | ||
| */ | ||
| async request(method, path, options) { | ||
| const url = new URL(`${this.baseUrl}${path}`); | ||
| if (options?.params) { | ||
| Object.entries(options.params).forEach(([key, value]) => { | ||
| if (value !== void 0 && value !== null) { | ||
| url.searchParams.append(key, String(value)); | ||
| } | ||
| }); | ||
| } | ||
| const fetchOptions = { | ||
| method, | ||
| headers: this.headers | ||
| }; | ||
| if (options?.body && ["POST", "PATCH", "PUT"].includes(method)) { | ||
| fetchOptions.body = JSON.stringify(options.body); | ||
| } | ||
| const response = await fetch(url.toString(), fetchOptions); | ||
| if (!response.ok) { | ||
| let errorMessage = `API Error ${response.status}`; | ||
| try { | ||
| const errorText = await response.text(); | ||
| if (errorText) { | ||
| try { | ||
| const errorJson = JSON.parse(errorText); | ||
| if (Array.isArray(errorJson.detail)) { | ||
| const validationErrors = errorJson.detail.map((err) => { | ||
| const location = err.loc ? err.loc.join(".") : "unknown"; | ||
| return `${location}: ${err.msg}`; | ||
| }).join("; "); | ||
| errorMessage += `: ${validationErrors}`; | ||
| } else { | ||
| errorMessage += `: ${errorJson.detail || errorJson.error || errorText}`; | ||
| } | ||
| } catch { | ||
| errorMessage += `: ${errorText}`; | ||
| } | ||
| } | ||
| } catch { | ||
| } | ||
| throw new Error(errorMessage); | ||
| } | ||
| if (response.status === 204) { | ||
| return void 0; | ||
| } | ||
| return response.json(); | ||
| } | ||
| /** | ||
| * Helper to convert nested objects to _id/_ids format (legacy method) | ||
| * @deprecated Use prepareRelations instead - it's called automatically in create/update | ||
| */ | ||
| static prepareInput(data) { | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| if (value && typeof value === "object" && !Array.isArray(value) && "id" in value) { | ||
| delete result[key]; | ||
| result[`${key}_id`] = value.id; | ||
| } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| delete result[key]; | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Convert relation fields to API format (_id/_ids suffix) | ||
| * | ||
| * The OnlyWorlds API expects: | ||
| * - single_link fields: fieldname_id (e.g., birthplace_id) | ||
| * - multi_link fields: fieldname_ids (e.g., species_ids) | ||
| * | ||
| * This method auto-converts based on FIELD_SCHEMA: | ||
| * - { species: ["id1", "id2"] } → { species_ids: ["id1", "id2"] } | ||
| * - { birthplace: "location-id" } → { birthplace_id: "location-id" } | ||
| * - { species: [{id: "id1", name: "X"}] } → { species_ids: ["id1"] } | ||
| * | ||
| * Called automatically by create() and update() methods. | ||
| */ | ||
| static prepareRelations(data, elementType) { | ||
| const schema = FIELD_SCHEMA[elementType]; | ||
| if (!schema) return data; | ||
| const result = { ...data }; | ||
| for (const [key, value] of Object.entries(result)) { | ||
| const fieldDef = schema[key]; | ||
| if (!fieldDef) continue; | ||
| if (fieldDef.type === "single_link") { | ||
| delete result[key]; | ||
| if (value && typeof value === "object" && "id" in value) { | ||
| result[`${key}_id`] = value.id; | ||
| } else if (typeof value === "string" || value === null) { | ||
| result[`${key}_id`] = value; | ||
| } | ||
| } else if (fieldDef.type === "multi_link") { | ||
| delete result[key]; | ||
| if (Array.isArray(value)) { | ||
| if (value.length > 0 && typeof value[0] === "object" && "id" in value[0]) { | ||
| result[`${key}_ids`] = value.map((item) => item.id); | ||
| } else { | ||
| result[`${key}_ids`] = value; | ||
| } | ||
| } else { | ||
| result[`${key}_ids`] = []; | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| }; | ||
| // src/token-types.ts | ||
@@ -1128,0 +1174,0 @@ var GameTier = /* @__PURE__ */ ((GameTier2) => { |
+1
-1
| { | ||
| "name": "@onlyworlds/sdk", | ||
| "version": "2.1.3", | ||
| "version": "2.2.0", | ||
| "description": "TypeScript SDK for the OnlyWorlds API - build world-building applications with type safety", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
304049
1.26%5547
2%5
66.67%