diff --git a/libSmartAttributes/0.0.1/index.d.ts b/libSmartAttributes/0.0.1/index.d.ts deleted file mode 100644 index 89d876972..000000000 --- a/libSmartAttributes/0.0.1/index.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -type AttributeType = "current" | "max"; -declare function getAttribute(characterId: string, name: string, type?: AttributeType): Promise; -type SetOptions = { - setWithWorker?: boolean; -}; -declare function setAttribute(characterId: string, name: string, value: unknown, type?: AttributeType, options?: SetOptions): Promise & { - setWithWorker: (attributes: Partial) => void; -})>; -declare function deleteAttribute(characterId: string, name: string): Promise; -declare const _default: { - getAttribute: typeof getAttribute; - setAttribute: typeof setAttribute; - deleteAttribute: typeof deleteAttribute; -}; -export default _default; diff --git a/libSmartAttributes/0.0.2/libSmartAttributes.js b/libSmartAttributes/0.0.2/libSmartAttributes.js new file mode 100644 index 000000000..27716de9c --- /dev/null +++ b/libSmartAttributes/0.0.2/libSmartAttributes.js @@ -0,0 +1,97 @@ +// libSmartAttributes v0.0.2 by GUD Team | libSmartAttributes provides an interface for managing beacon attributes in a slightly smarter way. +var libSmartAttributes = (function () { + 'use strict'; + + async function getAttribute(characterId, name, type = "current") { + // Try for legacy attribute first + const legacyAttr = findObjs({ + _type: "attribute", + _characterid: characterId, + name: name, + })[0]; + if (legacyAttr) { + return legacyAttr.get(type); + } + // Then try for the beacon computed + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); + if (beaconAttr !== null && beaconAttr !== undefined) { + return beaconAttr; + } + // Then try for the user attribute + const userAttr = await getSheetItem(characterId, `user.${beaconName}`); + if (userAttr !== null && userAttr !== undefined) { + return userAttr; + } + log(`Attribute ${beaconName} not found on character ${characterId}`); + return undefined; + } + async function setAttribute(characterId, name, value, type = "current", options) { + // Try for legacy attribute first + const legacyAttr = findObjs({ + _type: "attribute", + _characterid: characterId, + name: name, + })[0]; + if (legacyAttr && options?.setWithWorker) { + legacyAttr.setWithWorker({ [type]: value }); + return; + } + else if (legacyAttr) { + legacyAttr.set({ [type]: value }); + return; + } + // Then try for the beacon computed + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); + if (beaconAttr !== null && beaconAttr !== undefined) { + setSheetItem(characterId, beaconName, value); + return; + } + // Guard against creating user attributes if noCreate is set + if (options?.noCreate) { + log(`Attribute ${beaconName} not found on character ${characterId}, and noCreate option is set. Skipping creation.`); + return; + } + // Then default to a user attribute + setSheetItem(characterId, `user.${beaconName}`, value); + return; + } + async function deleteAttribute(characterId, name, type = "current") { + // Try for legacy attribute first + const legacyAttr = findObjs({ + _type: "attribute", + _characterid: characterId, + name: name, + })[0]; + if (legacyAttr) { + legacyAttr.remove(); + return; + } + // Then try for the beacon computed + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); + if (beaconAttr !== null && beaconAttr !== undefined) { + log(`Cannot delete beacon computed attribute ${name} on character ${characterId}. Setting to undefined instead`); + setSheetItem(characterId, name, undefined); + return; + } + // Then try for the user attribute + const userAttr = await getSheetItem(characterId, `user.${beaconName}`); + if (userAttr !== null && userAttr !== undefined) { + log(`Deleting user attribute ${name} on character ${characterId}`); + setSheetItem(characterId, `user.${beaconName}`, undefined); + return; + } + log(`Attribute ${beaconName} not found on character ${characterId}, nothing to delete`); + return; + } + var index = { + getAttribute, + setAttribute, + deleteAttribute, + }; + + return index; + +})(); diff --git a/libSmartAttributes/package.json b/libSmartAttributes/package.json index a5796f284..22d808e3e 100644 --- a/libSmartAttributes/package.json +++ b/libSmartAttributes/package.json @@ -2,8 +2,10 @@ "name": "lib-smart-attributes", "version": "1.0.0", "type": "module", - "main": "src/index.ts", - "types": "0.0.1/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./types": "./src/types.d.ts" + }, "scripts": { "lint": "eslint", "lint:fix": "eslint --fix", diff --git a/libSmartAttributes/rollup.config.ts b/libSmartAttributes/rollup.config.ts index 73a10ee09..70486abd1 100644 --- a/libSmartAttributes/rollup.config.ts +++ b/libSmartAttributes/rollup.config.ts @@ -9,17 +9,14 @@ export default defineConfig({ output: { file: `${json.version}/${json.name}.js`, - format: "iife", name: json.name, + format: "iife", sourcemap: false, banner: `// ${json.name} v${json.version} by ${json.authors} | ${json.description}`, }, plugins: [ del({ targets: `${json.version}/*`, runOnce: true }), - typescript({ - declaration: true, - declarationDir: `${json.version}`, - }), + typescript({}), ] }); \ No newline at end of file diff --git a/libSmartAttributes/script.json b/libSmartAttributes/script.json index e58c5306a..30bbb05c1 100644 --- a/libSmartAttributes/script.json +++ b/libSmartAttributes/script.json @@ -1,6 +1,6 @@ { "name": "libSmartAttributes", - "version": "0.0.1", + "version": "0.0.2", "description": "libSmartAttributes provides an interface for managing beacon attributes in a slightly smarter way.", "authors": "GUD Team", "roll20userid": "8705027", @@ -9,5 +9,7 @@ "conflicts": [], "script": "libSmartAttributes.js", "useroptions": [], - "previousversions": [] + "previousversions": [ + "0.0.1" + ] } \ No newline at end of file diff --git a/libSmartAttributes/src/index.ts b/libSmartAttributes/src/index.ts index 2ec6a5f1e..fb03c93ff 100644 --- a/libSmartAttributes/src/index.ts +++ b/libSmartAttributes/src/index.ts @@ -13,23 +13,25 @@ async function getAttribute(characterId: string, name: string, type: AttributeTy } // Then try for the beacon computed - const beaconAttr = await getSheetItem(characterId, name); + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); if (beaconAttr !== null && beaconAttr !== undefined) { return beaconAttr; } // Then try for the user attribute - const userAttr = await getSheetItem(characterId, `user.${name}`); + const userAttr = await getSheetItem(characterId, `user.${beaconName}`); if (userAttr !== null && userAttr !== undefined) { return userAttr; } - log(`Attribute ${name} not found on character ${characterId}`); + log(`Attribute ${beaconName} not found on character ${characterId}`); return undefined; }; type SetOptions = { setWithWorker?: boolean; + noCreate?: boolean; }; async function setAttribute(characterId: string, name: string, value: unknown, type: AttributeType = "current", options?: SetOptions) { @@ -41,24 +43,35 @@ async function setAttribute(characterId: string, name: string, value: unknown, t })[0]; if (legacyAttr && options?.setWithWorker) { - return legacyAttr.setWithWorker({ [type]: value }); + legacyAttr.setWithWorker({ [type]: value }); + return; } else if (legacyAttr) { - return legacyAttr.set({ [type]: value }); + legacyAttr.set({ [type]: value }); + return; } // Then try for the beacon computed - const beaconAttr = await getSheetItem(characterId, name); + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); if (beaconAttr !== null && beaconAttr !== undefined) { - return setSheetItem(characterId, name, value); + setSheetItem(characterId, beaconName, value); + return; + } + + // Guard against creating user attributes if noCreate is set + if (options?.noCreate) { + log(`Attribute ${beaconName} not found on character ${characterId}, and noCreate option is set. Skipping creation.`); + return; } // Then default to a user attribute - return setSheetItem(characterId, `user.${name}`, value); + setSheetItem(characterId, `user.${beaconName}`, value); + return; }; -async function deleteAttribute(characterId: string, name: string) { +async function deleteAttribute(characterId: string, name: string, type: AttributeType = "current") { // Try for legacy attribute first const legacyAttr = findObjs({ _type: "attribute", @@ -67,15 +80,29 @@ async function deleteAttribute(characterId: string, name: string) { })[0]; if (legacyAttr) { - return legacyAttr.remove(); + legacyAttr.remove(); + return; } // Then try for the beacon computed - const beaconAttr = await getSheetItem(characterId, name); + const beaconName = type === "current" ? name : `${name}_max`; + const beaconAttr = await getSheetItem(characterId, beaconName); if (beaconAttr !== null && beaconAttr !== undefined) { log(`Cannot delete beacon computed attribute ${name} on character ${characterId}. Setting to undefined instead`); - return setSheetItem(characterId, name, undefined); + setSheetItem(characterId, name, undefined); + return; } + + // Then try for the user attribute + const userAttr = await getSheetItem(characterId, `user.${beaconName}`); + if (userAttr !== null && userAttr !== undefined) { + log(`Deleting user attribute ${name} on character ${characterId}`); + setSheetItem(characterId, `user.${beaconName}`, undefined); + return; + } + + log(`Attribute ${beaconName} not found on character ${characterId}, nothing to delete`); + return; }; export default { diff --git a/libSmartAttributes/src/types.d.ts b/libSmartAttributes/src/types.d.ts new file mode 100644 index 000000000..b881d3d2a --- /dev/null +++ b/libSmartAttributes/src/types.d.ts @@ -0,0 +1,5 @@ +declare namespace SmartAttributes { + function getAttribute(characterId: string, name: string, type?: "current" | "max"): Promise; + function setAttribute(characterId: string, name: string, value: unknown, type?: "current" | "max", options?: { setWithWorker?: boolean, noCreate?: boolean }): Promise; + function deleteAttribute(characterId: string, name: string, type?: "current" | "max", options?: { setWithWorker?: boolean }): Promise; +} \ No newline at end of file diff --git a/libSmartAttributes/src/index.test.ts b/libSmartAttributes/tests/index.test.ts similarity index 86% rename from libSmartAttributes/src/index.test.ts rename to libSmartAttributes/tests/index.test.ts index b54e55ed3..20b97837f 100644 --- a/libSmartAttributes/src/index.test.ts +++ b/libSmartAttributes/tests/index.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import smartAttributes from "../src/index"; +import SmartAttributes from "../src/index"; // Mock Roll20 API functions const mockFindObjs = vi.fn(); @@ -19,7 +19,7 @@ vi.stubGlobal("getSheetItem", mockGetSheetItem); vi.stubGlobal("setSheetItem", mockSetSheetItem); vi.stubGlobal("log", mockLog); -describe("smartAttributes", () => { +describe("SmartAttributes", () => { beforeEach(() => { vi.clearAllMocks(); }); @@ -32,7 +32,7 @@ describe("smartAttributes", () => { const mockAttr = createMockAttribute("15"); mockFindObjs.mockReturnValue([mockAttr]); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(mockFindObjs).toHaveBeenCalledWith({ _type: "attribute", @@ -47,7 +47,7 @@ describe("smartAttributes", () => { const mockAttr = createMockAttribute("20"); mockFindObjs.mockReturnValue([mockAttr]); - const result = await smartAttributes.getAttribute(characterId, attributeName, "max"); + const result = await SmartAttributes.getAttribute(characterId, attributeName, "max"); expect(mockAttr.get).toHaveBeenCalledWith("max"); expect(result).toBe("20"); @@ -57,7 +57,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce("beacon-value"); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(mockFindObjs).toHaveBeenCalled(); expect(mockGetSheetItem).toHaveBeenCalledWith(characterId, attributeName); @@ -68,7 +68,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce(null).mockResolvedValueOnce("user-value"); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(mockGetSheetItem).toHaveBeenNthCalledWith(1, characterId, attributeName); expect(mockGetSheetItem).toHaveBeenNthCalledWith(2, characterId, `user.${attributeName}`); @@ -79,7 +79,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValue(null); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(mockLog).toHaveBeenCalledWith(`Attribute ${attributeName} not found on character ${characterId}`); expect(result).toBeUndefined(); @@ -89,7 +89,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce(0).mockResolvedValueOnce(null); // 0 is falsy, so code continues to user attr - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(result).toBeUndefined(); // Since user attr also returns null expect(mockGetSheetItem).toHaveBeenCalledTimes(2); @@ -100,7 +100,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce("").mockResolvedValueOnce(null); // '' is falsy, so code continues to user attr - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(result).toBeUndefined(); // Since user attr also returns null expect(mockGetSheetItem).toHaveBeenCalledTimes(2); @@ -118,7 +118,7 @@ describe("smartAttributes", () => { mockAttr.set.mockReturnValue(value); // Mock set to return the new value mockFindObjs.mockReturnValue([mockAttr]); - const result = await smartAttributes.setAttribute(characterId, attributeName, value); + const result = await SmartAttributes.setAttribute(characterId, attributeName, value); expect(mockFindObjs).toHaveBeenCalledWith({ _type: "attribute", @@ -134,7 +134,7 @@ describe("smartAttributes", () => { mockAttr.set.mockReturnValue(value); // Mock set to return the new value mockFindObjs.mockReturnValue([mockAttr]); - const result = await smartAttributes.setAttribute(characterId, attributeName, value, "max"); + const result = await SmartAttributes.setAttribute(characterId, attributeName, value, "max"); expect(mockAttr.set).toHaveBeenCalledWith({ max: value }); expect(result).toBe(value); @@ -145,7 +145,7 @@ describe("smartAttributes", () => { mockGetSheetItem.mockResolvedValue("existing-beacon-value"); mockSetSheetItem.mockResolvedValue("updated-value"); - const result = await smartAttributes.setAttribute(characterId, attributeName, value); + const result = await SmartAttributes.setAttribute(characterId, attributeName, value); expect(mockGetSheetItem).toHaveBeenCalledWith(characterId, attributeName); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, attributeName, value); @@ -157,7 +157,7 @@ describe("smartAttributes", () => { mockGetSheetItem.mockResolvedValue(null); mockSetSheetItem.mockResolvedValue("user-value"); - const result = await smartAttributes.setAttribute(characterId, attributeName, value); + const result = await SmartAttributes.setAttribute(characterId, attributeName, value); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, `user.${attributeName}`, value); expect(result).toBe("user-value"); @@ -169,7 +169,7 @@ describe("smartAttributes", () => { mockGetSheetItem.mockResolvedValue(null); mockSetSheetItem.mockResolvedValue(complexValue); - const result = await smartAttributes.setAttribute(characterId, attributeName, complexValue); + const result = await SmartAttributes.setAttribute(characterId, attributeName, complexValue); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, `user.${attributeName}`, complexValue); expect(result).toBe(complexValue); @@ -180,7 +180,7 @@ describe("smartAttributes", () => { mockGetSheetItem.mockResolvedValue(null); mockSetSheetItem.mockResolvedValue(null); - const result = await smartAttributes.setAttribute(characterId, attributeName, null); + const result = await SmartAttributes.setAttribute(characterId, attributeName, null); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, `user.${attributeName}`, null); expect(result).toBe(null); @@ -191,7 +191,7 @@ describe("smartAttributes", () => { mockGetSheetItem.mockResolvedValue(0); // falsy but valid existing value - code treats as falsy so goes to user path mockSetSheetItem.mockResolvedValue("updated"); - const result = await smartAttributes.setAttribute(characterId, attributeName, value); + const result = await SmartAttributes.setAttribute(characterId, attributeName, value); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, `user.${attributeName}`, value); expect(result).toBe("updated"); @@ -206,7 +206,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce(0).mockResolvedValueOnce("user-value"); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(result).toBe("user-value"); expect(mockGetSheetItem).toHaveBeenCalledWith(characterId, attributeName); @@ -217,7 +217,7 @@ describe("smartAttributes", () => { const mockAttr = createMockAttribute(42); mockFindObjs.mockReturnValue([mockAttr]); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(result).toBe(42); }); @@ -226,7 +226,7 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([]); mockGetSheetItem.mockResolvedValueOnce(true); - const result = await smartAttributes.getAttribute(characterId, attributeName); + const result = await SmartAttributes.getAttribute(characterId, attributeName); expect(result).toBe(true); }); @@ -241,12 +241,12 @@ describe("smartAttributes", () => { mockFindObjs.mockReturnValue([mockAttr]); // Get current value - const currentValue = await smartAttributes.getAttribute(characterId, attributeName); + const currentValue = await SmartAttributes.getAttribute(characterId, attributeName); expect(currentValue).toBe("10"); // Set new value mockAttr.set.mockReturnValue("15"); - const result = await smartAttributes.setAttribute(characterId, attributeName, "15"); + const result = await SmartAttributes.setAttribute(characterId, attributeName, "15"); expect(result).toBe("15"); }); @@ -256,11 +256,11 @@ describe("smartAttributes", () => { mockSetSheetItem.mockResolvedValue("beacon-15"); // Get current value - const currentValue = await smartAttributes.getAttribute(characterId, attributeName); + const currentValue = await SmartAttributes.getAttribute(characterId, attributeName); expect(currentValue).toBe("beacon-10"); // Set new value - const result = await smartAttributes.setAttribute(characterId, attributeName, "beacon-15"); + const result = await SmartAttributes.setAttribute(characterId, attributeName, "beacon-15"); expect(result).toBe("beacon-15"); }); @@ -270,11 +270,11 @@ describe("smartAttributes", () => { mockSetSheetItem.mockResolvedValue("new-value"); // Get returns undefined - const currentValue = await smartAttributes.getAttribute(characterId, attributeName); + const currentValue = await SmartAttributes.getAttribute(characterId, attributeName); expect(currentValue).toBeUndefined(); // But set still works by creating user attribute - const result = await smartAttributes.setAttribute(characterId, attributeName, "new-value"); + const result = await SmartAttributes.setAttribute(characterId, attributeName, "new-value"); expect(result).toBe("new-value"); expect(mockSetSheetItem).toHaveBeenCalledWith(characterId, `user.${attributeName}`, "new-value"); }); diff --git a/libSmartAttributes/tsconfig.json b/libSmartAttributes/tsconfig.json index ddf9ff2a6..57d37fd92 100644 --- a/libSmartAttributes/tsconfig.json +++ b/libSmartAttributes/tsconfig.json @@ -3,12 +3,12 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "sourceMap": false }, - "include": ["src"], - "exclude": ["node_modules", "dist", "**/*.test.ts"] + "include": ["src", "rollup.config.ts"], + "exclude": ["node_modules"] }