@axiapps/gw2-data 0.1.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/data/overrides.json +25 -0
- package/package.json +32 -0
- package/scripts/generate-fixtures.js +242 -0
- package/src/api/client.js +117 -0
- package/src/api/types.js +80 -0
- package/src/engine/attributes.js +525 -0
- package/src/engine/boons.js +156 -0
- package/src/engine/combos.js +103 -0
- package/src/engine/constants.js +298 -0
- package/src/engine/graph.js +24 -0
- package/src/engine/index.js +82 -0
- package/src/engine/modifiers.js +204 -0
- package/src/engine/overrides.js +13 -0
- package/src/engine/tooltips.js +59 -0
- package/src/facts/match.js +134 -0
- package/src/facts/merge.js +45 -0
- package/src/facts/normalize.js +27 -0
- package/src/index.js +60 -0
- package/src/wiki/cache.js +103 -0
- package/src/wiki/client.js +230 -0
- package/src/wiki/parser.js +599 -0
- package/src/wiki/relations.js +55 -0
- package/src/wiki/resolver.js +352 -0
- package/tests/api-client.test.js +138 -0
- package/tests/cache.test.js +108 -0
- package/tests/engine/attributes.test.js +252 -0
- package/tests/engine/boons.test.js +129 -0
- package/tests/engine/combos.test.js +76 -0
- package/tests/engine/constants.test.js +576 -0
- package/tests/engine/fixtures/berserker-thief.json +61 -0
- package/tests/engine/fixtures/berserker-warrior.json +113 -0
- package/tests/engine/fixtures/celestial-firebrand-wvw.json +94 -0
- package/tests/engine/fixtures/harrier-druid.json +119 -0
- package/tests/engine/fixtures/viper-mirage.json +104 -0
- package/tests/engine/graph.test.js +30 -0
- package/tests/engine/integration.test.js +111 -0
- package/tests/engine/modifiers.test.js +473 -0
- package/tests/engine/overrides.test.js +70 -0
- package/tests/engine/snapshot.test.js +53 -0
- package/tests/engine/test-utils.js +20 -0
- package/tests/engine/tooltips.test.js +62 -0
- package/tests/fixtures/capture.js +160 -0
- package/tests/fixtures/fixtures.json +839 -0
- package/tests/integration.test.js +100 -0
- package/tests/match.test.js +176 -0
- package/tests/merge.test.js +128 -0
- package/tests/normalize.test.js +78 -0
- package/tests/parser.test.js +506 -0
- package/tests/real-data.test.js +296 -0
- package/tests/relations.test.js +80 -0
- package/tests/resolver.test.js +721 -0
- package/tests/validate-live.js +191 -0
- package/tests/wiki-client.test.js +468 -0
- package/tests/wiki-integration.test.js +177 -0
- package/tests/wiki-live-validation.test.js +61 -0
- package/tests/wiki-snapshots.test.js +166 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"trait:1719": {
|
|
3
|
+
"implicitFury": true,
|
|
4
|
+
"description": "Roiling Mists: has fury crit bonus but no Buff(Fury) fact in API"
|
|
5
|
+
},
|
|
6
|
+
"trait:1016": {
|
|
7
|
+
"petStatOnly": true,
|
|
8
|
+
"description": "Fang and Claw: AttributeAdjust facts apply to pets, not player"
|
|
9
|
+
},
|
|
10
|
+
"trait:1765": {
|
|
11
|
+
"mightOverride": { "power": 40, "condi": 20 },
|
|
12
|
+
"description": "Notoriety: modifies Might per-stack values (+40P/+20CD instead of +30P/+30CD)"
|
|
13
|
+
},
|
|
14
|
+
"trait:2220": {
|
|
15
|
+
"allyTargeted": ["elixir"],
|
|
16
|
+
"description": "Twisted Medicine: elixir skills become ally-targeted"
|
|
17
|
+
},
|
|
18
|
+
"trait:1831": {
|
|
19
|
+
"description": "Primal Rage: grants access to Primal Bursts (no burst recharge reduction)"
|
|
20
|
+
},
|
|
21
|
+
"trait:2046": {
|
|
22
|
+
"berserkConditional": true,
|
|
23
|
+
"description": "Fatal Frenzy: stat bonuses only apply while in berserk mode"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@axiapps/gw2-data",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GW2 data library — wiki-sourced facts, GW2 API structural data, and stat computation",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./wiki": "./src/wiki/client.js",
|
|
9
|
+
"./api": "./src/api/client.js",
|
|
10
|
+
"./facts": "./src/facts/merge.js",
|
|
11
|
+
"./engine": "./src/engine/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "jest"
|
|
15
|
+
},
|
|
16
|
+
"jest": {
|
|
17
|
+
"testEnvironment": "node",
|
|
18
|
+
"testMatch": ["**/packages/gw2-data/tests/**/*.test.js"],
|
|
19
|
+
"clearMocks": true,
|
|
20
|
+
"testTimeout": 15000
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": ["gw2", "guild-wars-2", "wiki", "api", "build-editor"],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/darkharasho/axiforge",
|
|
27
|
+
"directory": "packages/gw2-data"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"jest": "^30.3.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { computeAttributes } = require("../src/engine/attributes");
|
|
6
|
+
const { hydrateCatalogs } = require("../tests/engine/test-utils");
|
|
7
|
+
|
|
8
|
+
const FIXTURE_DIR = path.join(__dirname, "../tests/engine/fixtures");
|
|
9
|
+
|
|
10
|
+
const fixtures = [
|
|
11
|
+
{
|
|
12
|
+
name: "Berserker Warrior",
|
|
13
|
+
description: "Heavy armor, 3-stat, full ascended, signets, Might+Fury assumed",
|
|
14
|
+
ctx: {
|
|
15
|
+
profession: "Warrior",
|
|
16
|
+
specializations: [
|
|
17
|
+
{ id: 4, majorChoices: { 1: 1444, 2: 1449, 3: 1437 } },
|
|
18
|
+
],
|
|
19
|
+
equipment: {
|
|
20
|
+
slots: {
|
|
21
|
+
head: "Berserker's", shoulders: "Berserker's", chest: "Berserker's",
|
|
22
|
+
gloves: "Berserker's", legs: "Berserker's", boots: "Berserker's",
|
|
23
|
+
mainhand1: "Berserker's", offhand1: "Berserker's",
|
|
24
|
+
back: "Berserker's", accessory1: "Berserker's", accessory2: "Berserker's",
|
|
25
|
+
amulet: "Berserker's", ring1: "Berserker's", ring2: "Berserker's",
|
|
26
|
+
},
|
|
27
|
+
weapons: { mainhand1: "greatsword" },
|
|
28
|
+
runes: {},
|
|
29
|
+
infusions: {},
|
|
30
|
+
enrichment: null,
|
|
31
|
+
food: null,
|
|
32
|
+
utility: null,
|
|
33
|
+
},
|
|
34
|
+
gameMode: "pve",
|
|
35
|
+
underwaterMode: false,
|
|
36
|
+
activeWeaponSet: 1,
|
|
37
|
+
skills: { healId: null, utilityIds: [9093], eliteId: null },
|
|
38
|
+
assumedBoons: { might: 25, fury: true },
|
|
39
|
+
sigilStacks: null,
|
|
40
|
+
},
|
|
41
|
+
catalogs: {
|
|
42
|
+
traits: [
|
|
43
|
+
{ id: 1444, facts: [{ type: "AttributeAdjust", target: "Power", value: 120 }] },
|
|
44
|
+
{ id: 1449, facts: [] },
|
|
45
|
+
{ id: 1437, facts: [] },
|
|
46
|
+
],
|
|
47
|
+
specializations: [{ id: 4, minorTraits: [] }],
|
|
48
|
+
skills: [],
|
|
49
|
+
runes: [],
|
|
50
|
+
foods: [],
|
|
51
|
+
utilities: [],
|
|
52
|
+
infusions: [],
|
|
53
|
+
enrichments: [],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "Viper Mirage",
|
|
58
|
+
description: "Medium armor, 4-stat, trait conversions, food + utility",
|
|
59
|
+
ctx: {
|
|
60
|
+
profession: "Mesmer",
|
|
61
|
+
specializations: [
|
|
62
|
+
{ id: 24, majorChoices: { 1: 700 } },
|
|
63
|
+
],
|
|
64
|
+
equipment: {
|
|
65
|
+
slots: {
|
|
66
|
+
head: "Viper's", shoulders: "Viper's", chest: "Viper's",
|
|
67
|
+
gloves: "Viper's", legs: "Viper's", boots: "Viper's",
|
|
68
|
+
mainhand1: "Viper's",
|
|
69
|
+
back: "Viper's", accessory1: "Viper's", accessory2: "Viper's",
|
|
70
|
+
amulet: "Viper's", ring1: "Viper's", ring2: "Viper's",
|
|
71
|
+
},
|
|
72
|
+
weapons: { mainhand1: "axe" },
|
|
73
|
+
runes: {},
|
|
74
|
+
infusions: {},
|
|
75
|
+
enrichment: null,
|
|
76
|
+
food: 91805,
|
|
77
|
+
utility: null,
|
|
78
|
+
},
|
|
79
|
+
gameMode: "pve",
|
|
80
|
+
underwaterMode: false,
|
|
81
|
+
activeWeaponSet: 1,
|
|
82
|
+
skills: { healId: null, utilityIds: [], eliteId: null },
|
|
83
|
+
assumedBoons: null,
|
|
84
|
+
sigilStacks: null,
|
|
85
|
+
},
|
|
86
|
+
catalogs: {
|
|
87
|
+
traits: [
|
|
88
|
+
{ id: 700, facts: [{ type: "BuffConversion", source: "Vitality", target: "ConditionDamage", percent: 10 }] },
|
|
89
|
+
],
|
|
90
|
+
specializations: [{ id: 24, minorTraits: [] }],
|
|
91
|
+
skills: [],
|
|
92
|
+
runes: [],
|
|
93
|
+
foods: [{ id: 91805, name: "Plate of Beef Rendang", buff: "+100 Expertise\n+70 Condition Damage" }],
|
|
94
|
+
utilities: [],
|
|
95
|
+
infusions: [],
|
|
96
|
+
enrichments: [],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "Celestial Firebrand WvW",
|
|
101
|
+
description: "Heavy armor, 9-stat, WvW Celestial exclusion, rune bonuses",
|
|
102
|
+
ctx: {
|
|
103
|
+
profession: "Guardian",
|
|
104
|
+
specializations: [],
|
|
105
|
+
equipment: {
|
|
106
|
+
slots: {
|
|
107
|
+
head: "Celestial", shoulders: "Celestial", chest: "Celestial",
|
|
108
|
+
gloves: "Celestial", legs: "Celestial", boots: "Celestial",
|
|
109
|
+
mainhand1: "Celestial",
|
|
110
|
+
back: "Celestial", accessory1: "Celestial", accessory2: "Celestial",
|
|
111
|
+
amulet: "Celestial", ring1: "Celestial", ring2: "Celestial",
|
|
112
|
+
},
|
|
113
|
+
weapons: { mainhand1: "axe" },
|
|
114
|
+
runes: {
|
|
115
|
+
head: 24836, shoulders: 24836, chest: 24836,
|
|
116
|
+
gloves: 24836, legs: 24836, boots: 24836,
|
|
117
|
+
},
|
|
118
|
+
infusions: {},
|
|
119
|
+
enrichment: null,
|
|
120
|
+
food: null,
|
|
121
|
+
utility: null,
|
|
122
|
+
},
|
|
123
|
+
gameMode: "wvw",
|
|
124
|
+
underwaterMode: false,
|
|
125
|
+
activeWeaponSet: 1,
|
|
126
|
+
skills: { healId: null, utilityIds: [], eliteId: null },
|
|
127
|
+
assumedBoons: null,
|
|
128
|
+
sigilStacks: null,
|
|
129
|
+
},
|
|
130
|
+
catalogs: {
|
|
131
|
+
traits: [],
|
|
132
|
+
specializations: [],
|
|
133
|
+
skills: [],
|
|
134
|
+
runes: [{ id: 24836, name: "Superior Rune of the Scholar", bonuses: ["+25 Power", "+35 Ferocity", "+50 Power", "+65 Ferocity", "+100 Power", "+125 Ferocity"] }],
|
|
135
|
+
foods: [],
|
|
136
|
+
utilities: [],
|
|
137
|
+
infusions: [],
|
|
138
|
+
enrichments: [],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "Harrier Druid",
|
|
143
|
+
description: "Medium armor, 3-stat healing, enrichment, infusions",
|
|
144
|
+
ctx: {
|
|
145
|
+
profession: "Ranger",
|
|
146
|
+
specializations: [],
|
|
147
|
+
equipment: {
|
|
148
|
+
slots: {
|
|
149
|
+
head: "Harrier's", shoulders: "Harrier's", chest: "Harrier's",
|
|
150
|
+
gloves: "Harrier's", legs: "Harrier's", boots: "Harrier's",
|
|
151
|
+
mainhand1: "Harrier's",
|
|
152
|
+
back: "Harrier's", accessory1: "Harrier's", accessory2: "Harrier's",
|
|
153
|
+
amulet: "Harrier's", ring1: "Harrier's", ring2: "Harrier's",
|
|
154
|
+
},
|
|
155
|
+
weapons: { mainhand1: "staff" },
|
|
156
|
+
runes: {},
|
|
157
|
+
infusions: {
|
|
158
|
+
head: [49432], shoulders: [49432], chest: [49432],
|
|
159
|
+
gloves: [49432], legs: [49432], boots: [49432],
|
|
160
|
+
},
|
|
161
|
+
enrichment: 78061,
|
|
162
|
+
food: null,
|
|
163
|
+
utility: null,
|
|
164
|
+
},
|
|
165
|
+
gameMode: "pve",
|
|
166
|
+
underwaterMode: false,
|
|
167
|
+
activeWeaponSet: 1,
|
|
168
|
+
skills: { healId: null, utilityIds: [], eliteId: null },
|
|
169
|
+
assumedBoons: null,
|
|
170
|
+
sigilStacks: null,
|
|
171
|
+
},
|
|
172
|
+
catalogs: {
|
|
173
|
+
traits: [],
|
|
174
|
+
specializations: [],
|
|
175
|
+
skills: [],
|
|
176
|
+
runes: [],
|
|
177
|
+
foods: [],
|
|
178
|
+
utilities: [],
|
|
179
|
+
infusions: [{ id: 49432, name: "+5 Healing Power Infusion", infixUpgrade: { attributes: [{ attribute: "Healing", modifier: 5 }] } }],
|
|
180
|
+
enrichments: [{ id: 78061, name: "+10 Concentration Enrichment", infixUpgrade: { attributes: [{ attribute: "BoonDuration", modifier: 10 }] } }],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "Berserker Thief",
|
|
185
|
+
description: "Medium armor, sparse gear (testing empty slots gracefully)",
|
|
186
|
+
ctx: {
|
|
187
|
+
profession: "Thief",
|
|
188
|
+
specializations: [],
|
|
189
|
+
equipment: {
|
|
190
|
+
slots: { chest: "Berserker's", legs: "Berserker's" },
|
|
191
|
+
weapons: {},
|
|
192
|
+
runes: {},
|
|
193
|
+
infusions: {},
|
|
194
|
+
enrichment: null,
|
|
195
|
+
food: null,
|
|
196
|
+
utility: null,
|
|
197
|
+
},
|
|
198
|
+
gameMode: "pve",
|
|
199
|
+
underwaterMode: false,
|
|
200
|
+
activeWeaponSet: 1,
|
|
201
|
+
skills: { healId: null, utilityIds: [], eliteId: null },
|
|
202
|
+
assumedBoons: null,
|
|
203
|
+
sigilStacks: null,
|
|
204
|
+
},
|
|
205
|
+
catalogs: {
|
|
206
|
+
traits: [],
|
|
207
|
+
specializations: [],
|
|
208
|
+
skills: [],
|
|
209
|
+
runes: [],
|
|
210
|
+
foods: [],
|
|
211
|
+
utilities: [],
|
|
212
|
+
infusions: [],
|
|
213
|
+
enrichments: [],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Generate fixtures
|
|
219
|
+
if (!fs.existsSync(FIXTURE_DIR)) {
|
|
220
|
+
fs.mkdirSync(FIXTURE_DIR, { recursive: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const fixture of fixtures) {
|
|
224
|
+
const catalogs = hydrateCatalogs(fixture.catalogs);
|
|
225
|
+
const result = computeAttributes(fixture.ctx, catalogs);
|
|
226
|
+
const output = {
|
|
227
|
+
name: fixture.name,
|
|
228
|
+
description: fixture.description,
|
|
229
|
+
ctx: fixture.ctx,
|
|
230
|
+
catalogs: fixture.catalogs,
|
|
231
|
+
expected: {
|
|
232
|
+
total: result.total,
|
|
233
|
+
derived: result.derived,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
const filename = fixture.name.toLowerCase().replace(/\s+/g, "-") + ".json";
|
|
237
|
+
const filepath = path.join(FIXTURE_DIR, filename);
|
|
238
|
+
fs.writeFileSync(filepath, JSON.stringify(output, null, 2) + "\n");
|
|
239
|
+
console.log(`Generated: ${filename}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.log("Done!");
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { MemoryCache } = require("../wiki/cache");
|
|
4
|
+
|
|
5
|
+
const GW2_API_ROOT = "https://api.guildwars2.com";
|
|
6
|
+
const MAX_IDS_PER_REQUEST = 180;
|
|
7
|
+
const MAX_RETRIES = 3;
|
|
8
|
+
const MAX_CONCURRENT = 3;
|
|
9
|
+
const RATE_LIMIT_DELAY_MS = 2000;
|
|
10
|
+
const USER_AGENT = "@axiapps/gw2-data (https://github.com/darkharasho/axiforge)";
|
|
11
|
+
|
|
12
|
+
class Gw2ApiClient {
|
|
13
|
+
/**
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {import('../wiki/cache').CacheAdapter} options.cache - Cache adapter
|
|
16
|
+
* @param {Function} [options.fetch] - Fetch implementation (defaults to global fetch)
|
|
17
|
+
* @param {string} [options.apiRoot] - GW2 API root URL
|
|
18
|
+
* @param {string} [options.lang] - Language code (default: "en")
|
|
19
|
+
*/
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this._cache = options.cache || new MemoryCache();
|
|
22
|
+
this._fetch = options.fetch || globalThis.fetch;
|
|
23
|
+
this._apiRoot = options.apiRoot || GW2_API_ROOT;
|
|
24
|
+
this._lang = options.lang || "en";
|
|
25
|
+
this._queue = [];
|
|
26
|
+
this._activeRequests = 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async fetchJson(url) {
|
|
30
|
+
let lastError;
|
|
31
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
32
|
+
const res = await this._enqueue(() =>
|
|
33
|
+
this._fetch(url, {
|
|
34
|
+
headers: { "User-Agent": USER_AGENT },
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
if (res.ok) {
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
if (res.status === 429) {
|
|
41
|
+
await this._delay(RATE_LIMIT_DELAY_MS);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
lastError = new Error(`HTTP ${res.status} ${res.statusText}: ${url}`);
|
|
45
|
+
if (res.status >= 500) {
|
|
46
|
+
await this._delay(500 * (attempt + 1));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
throw lastError;
|
|
50
|
+
}
|
|
51
|
+
throw lastError;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async fetchByIds(endpoint, ids, lang) {
|
|
55
|
+
const dedupedIds = [...new Set(ids)];
|
|
56
|
+
const chunks = this._chunk(dedupedIds, MAX_IDS_PER_REQUEST);
|
|
57
|
+
const langParam = lang || this._lang;
|
|
58
|
+
const results = [];
|
|
59
|
+
|
|
60
|
+
for (const chunk of chunks) {
|
|
61
|
+
const url = `${this._apiRoot}${endpoint}?ids=${chunk.join(",")}&lang=${langParam}`;
|
|
62
|
+
const data = await this.fetchJson(url);
|
|
63
|
+
results.push(...data);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async fetchCached(key, url, ttlMs) {
|
|
70
|
+
const cached = await this._cache.get(key);
|
|
71
|
+
if (cached !== null) return cached;
|
|
72
|
+
|
|
73
|
+
const data = await this.fetchJson(url);
|
|
74
|
+
await this._cache.set(key, data, ttlMs);
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_chunk(arr, size) {
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for (let i = 0; i < arr.length; i += size) {
|
|
81
|
+
chunks.push(arr.slice(i, i + size));
|
|
82
|
+
}
|
|
83
|
+
return chunks;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_delay(ms) {
|
|
87
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_enqueue(fn) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
this._queue.push({ fn, resolve, reject });
|
|
93
|
+
this._drain();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_drain() {
|
|
98
|
+
while (this._queue.length > 0 && this._activeRequests < MAX_CONCURRENT) {
|
|
99
|
+
const { fn, resolve, reject } = this._queue.shift();
|
|
100
|
+
this._activeRequests++;
|
|
101
|
+
fn()
|
|
102
|
+
.then(resolve)
|
|
103
|
+
.catch(reject)
|
|
104
|
+
.finally(() => {
|
|
105
|
+
this._activeRequests--;
|
|
106
|
+
this._drain();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
Gw2ApiClient,
|
|
114
|
+
GW2_API_ROOT,
|
|
115
|
+
MAX_IDS_PER_REQUEST,
|
|
116
|
+
USER_AGENT,
|
|
117
|
+
};
|
package/src/api/types.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {'pve'|'wvw'|'pvp'} GameMode
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} Fact
|
|
9
|
+
* @property {string} type - Fact type (Damage, Buff, AttributeAdjust, Recharge, etc.)
|
|
10
|
+
* @property {string} text - Display label
|
|
11
|
+
* @property {string} [icon] - Icon URL
|
|
12
|
+
* @property {number} [value] - Numeric value (AttributeAdjust, Number)
|
|
13
|
+
* @property {number} [duration] - Duration in seconds (Buff, Time)
|
|
14
|
+
* @property {number} [apply_count] - Stack count (Buff)
|
|
15
|
+
* @property {string} [status] - Buff/condition name (Buff)
|
|
16
|
+
* @property {number} [dmg_multiplier] - Damage coefficient (Damage)
|
|
17
|
+
* @property {number} [hit_count] - Number of hits (Damage)
|
|
18
|
+
* @property {number} [distance] - Distance/radius in units (Distance, Radius)
|
|
19
|
+
* @property {number} [percent] - Percentage value (Percent)
|
|
20
|
+
* @property {number} [coefficient] - Healing/barrier coefficient
|
|
21
|
+
* @property {string} [target] - Target attribute (AttributeAdjust, BuffConversion)
|
|
22
|
+
* @property {string} [source] - Source attribute (BuffConversion)
|
|
23
|
+
* @property {string} [finisher_type] - Combo finisher type (ComboFinisher)
|
|
24
|
+
* @property {string} [field_type] - Combo field type (ComboField)
|
|
25
|
+
* @property {boolean} [_splitFact] - Marked true when fact value comes from a balance split
|
|
26
|
+
* @property {boolean} [_traitedFact] - Marked true when fact is from traited_facts
|
|
27
|
+
* @property {boolean} [_newFact] - Marked true when fact was added by split (not in API)
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} ResolvedSkill
|
|
32
|
+
* @property {number} id - Skill ID
|
|
33
|
+
* @property {string} name - Skill name
|
|
34
|
+
* @property {string} description - Skill description
|
|
35
|
+
* @property {string} icon - Icon URL
|
|
36
|
+
* @property {string} [slot] - Slot type (Weapon_1-5, Heal, Utility, Elite, Profession_1-5)
|
|
37
|
+
* @property {number} [specialization] - Required specialization ID
|
|
38
|
+
* @property {string[]} [professions] - Professions that can use this skill
|
|
39
|
+
* @property {Fact[]} facts - Resolved facts for the requested game mode
|
|
40
|
+
* @property {Fact[]} [traited_facts] - Facts that change when specific traits are active
|
|
41
|
+
* @property {boolean} [hasSplit] - True if facts differ from PvE in this game mode
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Object} ResolvedTrait
|
|
46
|
+
* @property {number} id - Trait ID
|
|
47
|
+
* @property {string} name - Trait name
|
|
48
|
+
* @property {string} description - Trait description
|
|
49
|
+
* @property {string} icon - Icon URL
|
|
50
|
+
* @property {number} specialization - Specialization ID
|
|
51
|
+
* @property {number} tier - Trait tier (1=minor adept, 2=major adept, etc.)
|
|
52
|
+
* @property {number} order - Position in tier (0, 1, 2)
|
|
53
|
+
* @property {Fact[]} facts - Resolved facts for the requested game mode
|
|
54
|
+
* @property {Fact[]} [traited_facts] - Conditional facts
|
|
55
|
+
* @property {boolean} [hasSplit] - True if facts differ from PvE
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {Object} SplitEntry
|
|
60
|
+
* @property {Fact[]} facts - Facts for this game mode
|
|
61
|
+
* @property {boolean} [complete] - If true, this is the full fact set (not partial)
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {Object} WikiRelation
|
|
66
|
+
* @property {string} name - Related entity name
|
|
67
|
+
* @property {string} [icon] - Icon URL
|
|
68
|
+
* @property {string} [context] - Description of the relationship
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @typedef {Object} CacheAdapter
|
|
73
|
+
* @property {(key: string) => any|null} get
|
|
74
|
+
* @property {(key: string, value: any, ttlMs: number) => void} set
|
|
75
|
+
* @property {(key: string) => void} invalidate
|
|
76
|
+
* @property {() => void} clear
|
|
77
|
+
* @property {(key: string) => boolean} has
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
module.exports = {};
|