@mapletip/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/NOTICE +52 -0
- package/README.md +209 -0
- package/client/contract-compatibility.d.mts +27 -0
- package/client/contract-compatibility.mjs +159 -0
- package/client/core.d.ts +150 -0
- package/client/core.js +359 -0
- package/client/equipment.d.ts +22 -0
- package/client/equipment.js +63 -0
- package/client/items.d.ts +22 -0
- package/client/items.js +63 -0
- package/client/media.d.ts +19 -0
- package/client/media.js +47 -0
- package/client/monster-client.d.ts +35 -0
- package/client/monster-client.js +155 -0
- package/client/monsters.d.ts +24 -0
- package/client/monsters.js +14 -0
- package/client/quests.d.ts +31 -0
- package/client/quests.js +124 -0
- package/client/release.d.ts +63 -0
- package/client/release.js +279 -0
- package/client/static-client.d.ts +40 -0
- package/client/static-client.js +44 -0
- package/client/static-domains.d.ts +48 -0
- package/client/static-domains.js +113 -0
- package/generated/contract-2-catalog-fact.d.ts +51 -0
- package/generated/equipment-catalog-2.d.ts +434 -0
- package/generated/equipment-catalog-2.validator.js +17 -0
- package/generated/equipment-detail-2.d.ts +350 -0
- package/generated/equipment-detail-2.validator.js +17 -0
- package/generated/equipment-evidence-pack-2.d.ts +243 -0
- package/generated/equipment-evidence-pack-2.validator.js +17 -0
- package/generated/equipment-pack-2.validator.js +17 -0
- package/generated/equipment-relationship-pack-2.d.ts +170 -0
- package/generated/equipment-relationship-pack-2.validator.js +17 -0
- package/generated/item-catalog-2.d.ts +366 -0
- package/generated/item-catalog-2.validator.js +17 -0
- package/generated/item-detail-2.d.ts +540 -0
- package/generated/item-detail-2.validator.js +17 -0
- package/generated/item-evidence-pack-2.d.ts +243 -0
- package/generated/item-evidence-pack-2.validator.js +17 -0
- package/generated/item-pack-2.validator.js +17 -0
- package/generated/item-relationship-pack-2.d.ts +170 -0
- package/generated/item-relationship-pack-2.validator.js +17 -0
- package/generated/media-catalog-2.d.ts +133 -0
- package/generated/media-catalog-2.validator.js +17 -0
- package/generated/media-detail-2.d.ts +125 -0
- package/generated/media-detail-2.validator.js +17 -0
- package/generated/media-pack-2.validator.js +17 -0
- package/generated/monster-catalog-2.d.ts +230 -0
- package/generated/monster-catalog-2.validator.js +17 -0
- package/generated/monster-catalog.d.ts +224 -0
- package/generated/monster-catalog.validator.js +1 -0
- package/generated/monster-core-relationship-pack-2.d.ts +208 -0
- package/generated/monster-core-relationship-pack-2.validator.js +17 -0
- package/generated/monster-detail-2.d.ts +372 -0
- package/generated/monster-detail-2.validator.js +17 -0
- package/generated/monster-detail.d.ts +799 -0
- package/generated/monster-detail.validator.js +17 -0
- package/generated/monster-evidence-pack-2.d.ts +378 -0
- package/generated/monster-evidence-pack-2.validator.js +17 -0
- package/generated/monster-evidence-pack.d.ts +269 -0
- package/generated/monster-evidence-pack.validator.js +17 -0
- package/generated/monster-location-pack-2.validator.js +1 -0
- package/generated/monster-pack-2.validator.js +17 -0
- package/generated/monster-pack.validator.js +17 -0
- package/generated/monster-relationship-pack.d.ts +126 -0
- package/generated/monster-relationship-pack.validator.js +1 -0
- package/generated/product-manifest.d.ts +188 -0
- package/generated/product-manifest.validator.js +1 -0
- package/generated/quest-auxiliary-pack-2.d.ts +391 -0
- package/generated/quest-auxiliary-pack-2.validator.js +17 -0
- package/generated/quest-browse-pack-2.d.ts +353 -0
- package/generated/quest-browse-pack-2.validator.js +17 -0
- package/generated/quest-catalog-2.d.ts +501 -0
- package/generated/quest-catalog-2.validator.js +17 -0
- package/generated/quest-evidence-pack-2.d.ts +269 -0
- package/generated/quest-evidence-pack-2.validator.js +17 -0
- package/generated/quest-pack-2.d.ts +410 -0
- package/generated/quest-pack-2.validator.js +17 -0
- package/generated/quest-relationship-pack-2.d.ts +868 -0
- package/generated/quest-relationship-pack-2.validator.js +17 -0
- package/generated/release-build-index.d.ts +42 -0
- package/generated/release-build-index.validator.js +1 -0
- package/generated/release-manifest.d.ts +24 -0
- package/generated/release-manifest.validator.js +1 -0
- package/generated/release-pin.d.ts +12 -0
- package/generated/release-pin.validator.js +1 -0
- package/package.json +155 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import validateReleaseBuildIndex from '../generated/release-build-index.validator.js';
|
|
2
|
+
import validateReleaseManifest from '../generated/release-manifest.validator.js';
|
|
3
|
+
import validateReleasePin from '../generated/release-pin.validator.js';
|
|
4
|
+
import { minimumPackContractVersion } from './contract-compatibility.mjs';
|
|
5
|
+
import { ScopeError, SchemaValidationError, UnknownBuildError, UnsupportedCapabilityError, UnsupportedProductError, assertCompatibleManifest, assertEnvelopeVersion, createJsonFetcher, isStaticDomain, memoized, normalizedBaseUrl, parsePreloadedJson, productCapability, } from './core.js';
|
|
6
|
+
import { openMonsterProduct } from './monster-client.js';
|
|
7
|
+
function targetCapability(kind) {
|
|
8
|
+
switch (kind) {
|
|
9
|
+
case 'monster': return 'monster-core-v1';
|
|
10
|
+
case 'item': return 'items-v1';
|
|
11
|
+
case 'equipment': return 'equipment-v1';
|
|
12
|
+
case 'quest': return 'quests-v1';
|
|
13
|
+
case 'media': return 'media-v1';
|
|
14
|
+
case 'map': return 'maps-v1';
|
|
15
|
+
case 'npc': return 'npcs-v1';
|
|
16
|
+
case 'reactor': return 'reactors-v1';
|
|
17
|
+
case 'skill': return 'skills-v1';
|
|
18
|
+
case 'job': return 'jobs-v1';
|
|
19
|
+
case 'recipe': return 'recipes-v1';
|
|
20
|
+
case 'cash-shop': return 'cash-shop-v1';
|
|
21
|
+
case 'avatar': return 'avatars-v1';
|
|
22
|
+
default: return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Resolve a target against the verified product roster in its build envelope. */
|
|
26
|
+
export function resolveTargetAvailability(build, target) {
|
|
27
|
+
const capability = targetCapability(target.kind);
|
|
28
|
+
if (!capability)
|
|
29
|
+
return 'unknown';
|
|
30
|
+
return build.productCapabilities.includes(capability) ? 'available' : 'not-available';
|
|
31
|
+
}
|
|
32
|
+
export async function openRelease(options) {
|
|
33
|
+
if (!validateReleasePin(options.pin)) {
|
|
34
|
+
throw new SchemaValidationError('pin.json', JSON.stringify(validateReleasePin.errors ?? []));
|
|
35
|
+
}
|
|
36
|
+
const baseUrl = normalizedBaseUrl(options.baseUrl);
|
|
37
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
38
|
+
const fetchJson = createJsonFetcher({
|
|
39
|
+
baseUrl,
|
|
40
|
+
fetcher,
|
|
41
|
+
onRequest: options.onRequest,
|
|
42
|
+
signal: options.signal,
|
|
43
|
+
});
|
|
44
|
+
const rawManifest = options.preloaded
|
|
45
|
+
? await parsePreloadedJson(options.preloaded.releaseManifest, options.pin, 'release-manifest', baseUrl, options.onRequest)
|
|
46
|
+
: await fetchJson(options.pin, 'release-manifest');
|
|
47
|
+
if (!validateReleaseManifest(rawManifest)) {
|
|
48
|
+
throw new SchemaValidationError(options.pin.path, JSON.stringify(validateReleaseManifest.errors ?? []));
|
|
49
|
+
}
|
|
50
|
+
const manifest = rawManifest;
|
|
51
|
+
assertCompatibleManifest(manifest);
|
|
52
|
+
const minimumVersion = minimumPackContractVersion(manifest);
|
|
53
|
+
if (!manifest.requiredClientCapabilities.includes('release-manifest-v1')) {
|
|
54
|
+
throw new UnsupportedCapabilityError('Release manifest does not require release-manifest-v1');
|
|
55
|
+
}
|
|
56
|
+
if (manifest.releaseId !== options.pin.releaseId) {
|
|
57
|
+
throw new ScopeError(`Release manifest ID ${manifest.releaseId} does not match pin ID ${options.pin.releaseId}`);
|
|
58
|
+
}
|
|
59
|
+
const rawBuildIndex = options.preloaded
|
|
60
|
+
? await parsePreloadedJson(options.preloaded.buildIndex, manifest.buildIndex, 'build-index', baseUrl, options.onRequest)
|
|
61
|
+
: await fetchJson(manifest.buildIndex, 'build-index');
|
|
62
|
+
if (!validateReleaseBuildIndex(rawBuildIndex)) {
|
|
63
|
+
throw new SchemaValidationError(manifest.buildIndex.path, JSON.stringify(validateReleaseBuildIndex.errors ?? []));
|
|
64
|
+
}
|
|
65
|
+
const buildIndex = rawBuildIndex;
|
|
66
|
+
assertEnvelopeVersion(buildIndex.contractVersion, minimumVersion, manifest.buildIndex.path);
|
|
67
|
+
if (buildIndex.releaseId !== manifest.releaseId) {
|
|
68
|
+
throw new ScopeError(`Build index release ID ${buildIndex.releaseId} does not match manifest ID ${manifest.releaseId}`);
|
|
69
|
+
}
|
|
70
|
+
const builds = [];
|
|
71
|
+
const buildById = new Map();
|
|
72
|
+
for (const indexedBuild of buildIndex.builds) {
|
|
73
|
+
if (indexedBuild.products.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
const rosterCapabilities = [];
|
|
76
|
+
const seenCapabilities = new Set();
|
|
77
|
+
let productDescriptor;
|
|
78
|
+
let monsterProductDescriptor;
|
|
79
|
+
let itemProductDescriptor;
|
|
80
|
+
let equipmentProductDescriptor;
|
|
81
|
+
let mediaProductDescriptor;
|
|
82
|
+
let questProductDescriptor;
|
|
83
|
+
const domainProductDescriptors = {};
|
|
84
|
+
for (const descriptor of indexedBuild.products) {
|
|
85
|
+
if (descriptor.familyId !== indexedBuild.familyId
|
|
86
|
+
|| descriptor.buildId !== indexedBuild.buildId) {
|
|
87
|
+
throw new ScopeError(`Product descriptor scope does not match build index entry ${indexedBuild.familyId}/${indexedBuild.buildId}`);
|
|
88
|
+
}
|
|
89
|
+
const capability = productCapability(descriptor.productId);
|
|
90
|
+
if (!capability || (buildIndex.contractVersion === '2.0'
|
|
91
|
+
&& !descriptor.requiredClientCapabilities.includes(capability))) {
|
|
92
|
+
throw new SchemaValidationError(manifest.buildIndex.path, `Product roster has no matching domain capability for ${descriptor.productId}`);
|
|
93
|
+
}
|
|
94
|
+
if (seenCapabilities.has(capability)) {
|
|
95
|
+
throw new SchemaValidationError(manifest.buildIndex.path, `Product roster repeats capability ${capability} for ${indexedBuild.familyId}/${indexedBuild.buildId}`);
|
|
96
|
+
}
|
|
97
|
+
seenCapabilities.add(capability);
|
|
98
|
+
rosterCapabilities.push(capability);
|
|
99
|
+
if (capability === 'monster-core-v1') {
|
|
100
|
+
productDescriptor = descriptor;
|
|
101
|
+
monsterProductDescriptor = descriptor;
|
|
102
|
+
}
|
|
103
|
+
if (capability === 'items-v1')
|
|
104
|
+
itemProductDescriptor = descriptor;
|
|
105
|
+
if (capability === 'equipment-v1')
|
|
106
|
+
equipmentProductDescriptor = descriptor;
|
|
107
|
+
if (capability === 'media-v1')
|
|
108
|
+
mediaProductDescriptor = descriptor;
|
|
109
|
+
if (capability === 'quests-v1')
|
|
110
|
+
questProductDescriptor = descriptor;
|
|
111
|
+
const domain = descriptor.productId.slice(0, descriptor.productId.lastIndexOf('-'));
|
|
112
|
+
if (isStaticDomain(domain))
|
|
113
|
+
domainProductDescriptors[domain] = descriptor;
|
|
114
|
+
}
|
|
115
|
+
rosterCapabilities.sort();
|
|
116
|
+
if (buildIndex.contractVersion === '2.0' && indexedBuild.productCapabilities === undefined) {
|
|
117
|
+
throw new ScopeError(`Contract-2.0 build is missing product capabilities for ${indexedBuild.familyId}/${indexedBuild.buildId}`);
|
|
118
|
+
}
|
|
119
|
+
if (indexedBuild.productCapabilities !== undefined
|
|
120
|
+
&& (indexedBuild.productCapabilities.length !== rosterCapabilities.length
|
|
121
|
+
|| indexedBuild.productCapabilities.some((capability, index) => capability !== rosterCapabilities[index]))) {
|
|
122
|
+
throw new SchemaValidationError(manifest.buildIndex.path, `Build product capabilities do not match the shipped product roster for ${indexedBuild.familyId}/${indexedBuild.buildId}`);
|
|
123
|
+
}
|
|
124
|
+
const build = {
|
|
125
|
+
family: indexedBuild.familyId,
|
|
126
|
+
build: indexedBuild.buildId,
|
|
127
|
+
productDescriptor,
|
|
128
|
+
monsterProductDescriptor,
|
|
129
|
+
itemProductDescriptor,
|
|
130
|
+
equipmentProductDescriptor,
|
|
131
|
+
questProductDescriptor,
|
|
132
|
+
mediaProductDescriptor,
|
|
133
|
+
domainProductDescriptors,
|
|
134
|
+
productCapabilities: indexedBuild.productCapabilities ?? rosterCapabilities,
|
|
135
|
+
};
|
|
136
|
+
if (buildById.has(build.build)) {
|
|
137
|
+
throw new SchemaValidationError(manifest.buildIndex.path, `Duplicate build ID: ${build.build}`);
|
|
138
|
+
}
|
|
139
|
+
builds.push(build);
|
|
140
|
+
buildById.set(build.build, build);
|
|
141
|
+
}
|
|
142
|
+
function productOptions(build, descriptor) {
|
|
143
|
+
return {
|
|
144
|
+
baseUrl,
|
|
145
|
+
manifest: descriptor.productManifest,
|
|
146
|
+
expectedScope: { family_id: build.family, build_id: build.build },
|
|
147
|
+
signal: options.signal,
|
|
148
|
+
fetch: fetcher,
|
|
149
|
+
onRequest: options.onRequest,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function assertDescriptorIdentity(actualProductId, descriptor, label) {
|
|
153
|
+
if (!actualProductId || actualProductId !== descriptor.productId) {
|
|
154
|
+
throw new ScopeError(`${label} product manifest identity (${actualProductId ?? 'missing'}) does not match descriptor (${descriptor.productId})`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const itemClients = new Map();
|
|
158
|
+
async function openItemBuild(buildId) {
|
|
159
|
+
const build = buildById.get(buildId);
|
|
160
|
+
if (!build)
|
|
161
|
+
throw new UnknownBuildError(buildId);
|
|
162
|
+
const descriptor = build.itemProductDescriptor;
|
|
163
|
+
if (!descriptor) {
|
|
164
|
+
throw new UnsupportedProductError(`items for build ${buildId}`);
|
|
165
|
+
}
|
|
166
|
+
return memoized(itemClients, buildId, async () => {
|
|
167
|
+
const { openItemProduct } = await import('./items.js');
|
|
168
|
+
const client = await openItemProduct(productOptions(build, descriptor));
|
|
169
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
170
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Item');
|
|
171
|
+
}
|
|
172
|
+
return client;
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const equipmentClients = new Map();
|
|
176
|
+
async function openEquipmentBuild(buildId) {
|
|
177
|
+
const build = buildById.get(buildId);
|
|
178
|
+
if (!build)
|
|
179
|
+
throw new UnknownBuildError(buildId);
|
|
180
|
+
const descriptor = build.equipmentProductDescriptor;
|
|
181
|
+
if (!descriptor) {
|
|
182
|
+
throw new UnsupportedProductError(`equipment for build ${buildId}`);
|
|
183
|
+
}
|
|
184
|
+
return memoized(equipmentClients, buildId, async () => {
|
|
185
|
+
const { openEquipmentProduct } = await import('./equipment.js');
|
|
186
|
+
const client = await openEquipmentProduct(productOptions(build, descriptor));
|
|
187
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
188
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Equipment');
|
|
189
|
+
}
|
|
190
|
+
return client;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
const productClients = new Map();
|
|
194
|
+
async function openBuild(buildId) {
|
|
195
|
+
const build = buildById.get(buildId);
|
|
196
|
+
if (!build)
|
|
197
|
+
throw new UnknownBuildError(buildId);
|
|
198
|
+
const descriptor = build.monsterProductDescriptor ?? build.productDescriptor;
|
|
199
|
+
if (!descriptor) {
|
|
200
|
+
throw new UnsupportedProductError(`monsters for build ${buildId}`);
|
|
201
|
+
}
|
|
202
|
+
return memoized(productClients, buildId, async () => {
|
|
203
|
+
const client = await openMonsterProduct(productOptions(build, descriptor));
|
|
204
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
205
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Monster');
|
|
206
|
+
}
|
|
207
|
+
return client;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
const mediaClients = new Map();
|
|
211
|
+
async function openMediaBuild(buildId) {
|
|
212
|
+
const build = buildById.get(buildId);
|
|
213
|
+
if (!build)
|
|
214
|
+
throw new UnknownBuildError(buildId);
|
|
215
|
+
const descriptor = build.mediaProductDescriptor;
|
|
216
|
+
if (!descriptor) {
|
|
217
|
+
throw new UnsupportedProductError(`media for build ${buildId}`);
|
|
218
|
+
}
|
|
219
|
+
return memoized(mediaClients, buildId, async () => {
|
|
220
|
+
const { openMediaProduct } = await import('./media.js');
|
|
221
|
+
const client = await openMediaProduct(productOptions(build, descriptor));
|
|
222
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
223
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Media');
|
|
224
|
+
}
|
|
225
|
+
return client;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const questClients = new Map();
|
|
229
|
+
async function openQuestBuild(buildId) {
|
|
230
|
+
const build = buildById.get(buildId);
|
|
231
|
+
if (!build)
|
|
232
|
+
throw new UnknownBuildError(buildId);
|
|
233
|
+
const descriptor = build.questProductDescriptor;
|
|
234
|
+
if (!descriptor) {
|
|
235
|
+
throw new UnsupportedProductError(`quests for build ${buildId}`);
|
|
236
|
+
}
|
|
237
|
+
return memoized(questClients, buildId, async () => {
|
|
238
|
+
const { openQuestProduct } = await import('./quests.js');
|
|
239
|
+
const client = await openQuestProduct(productOptions(build, descriptor));
|
|
240
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
241
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Quest');
|
|
242
|
+
}
|
|
243
|
+
return client;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const domainClients = new Map();
|
|
247
|
+
async function openDomainBuild(buildId, domain) {
|
|
248
|
+
const build = buildById.get(buildId);
|
|
249
|
+
if (!build)
|
|
250
|
+
throw new UnknownBuildError(buildId);
|
|
251
|
+
if (!isStaticDomain(domain))
|
|
252
|
+
throw new UnsupportedProductError(`${domain} for build ${buildId}`);
|
|
253
|
+
const descriptor = build.domainProductDescriptors[domain];
|
|
254
|
+
if (!descriptor)
|
|
255
|
+
throw new UnsupportedProductError(`${domain} for build ${buildId}`);
|
|
256
|
+
const cacheKey = `${buildId}:${domain}`;
|
|
257
|
+
return memoized(domainClients, cacheKey, async () => {
|
|
258
|
+
const { openStaticDomainProduct } = await import('./static-domains.js');
|
|
259
|
+
const client = await openStaticDomainProduct(productOptions(build, descriptor));
|
|
260
|
+
if (client.manifest.productId !== domain) {
|
|
261
|
+
throw new UnsupportedProductError(String(client.manifest.productId));
|
|
262
|
+
}
|
|
263
|
+
if (client.manifest.contractVersion === '2.0' || descriptor.contractVersion === '2.0') {
|
|
264
|
+
assertDescriptorIdentity(client.manifest.identity?.productId, descriptor, 'Domain');
|
|
265
|
+
}
|
|
266
|
+
return client;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
manifest,
|
|
271
|
+
builds,
|
|
272
|
+
openBuild,
|
|
273
|
+
openItemBuild,
|
|
274
|
+
openEquipmentBuild,
|
|
275
|
+
openMediaBuild,
|
|
276
|
+
openQuestBuild,
|
|
277
|
+
openDomainBuild,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mapletip/data` and `@mapletip/data/client` — the broad compatibility entry.
|
|
3
|
+
*
|
|
4
|
+
* This barrel re-exports the same implementations that the domain subpaths expose and
|
|
5
|
+
* adds the legacy generic `openProduct` dispatcher. Consumers that only need one domain
|
|
6
|
+
* should import `@mapletip/data/monsters`, `/items`, `/equipment`, `/quests`,
|
|
7
|
+
* `/media` or `/release` instead, which keep the other domains out of the bundle.
|
|
8
|
+
*/
|
|
9
|
+
import { type OpenProductOptions } from './core.js';
|
|
10
|
+
import { type StaticProductClient } from './monster-client.js';
|
|
11
|
+
import { type StaticItemProductClient } from './items.js';
|
|
12
|
+
import { type StaticEquipmentProductClient } from './equipment.js';
|
|
13
|
+
import { type StaticMediaProductClient } from './media.js';
|
|
14
|
+
import { type StaticQuestProductClient } from './quests.js';
|
|
15
|
+
import { type StaticDomainProductClient } from './static-domains.js';
|
|
16
|
+
export { ContractVersionError, IntegrityError, InvalidJsonError, InvalidPathError, MissingPageError, MissingRecordError, RequestError, SchemaValidationError, ScopeError, SecureContextRequiredError, StaticClientError, UnknownBuildError, UnsupportedCapabilityError, UnsupportedProductError, resolveMediaUrl, verifyBlob, } from './core.js';
|
|
17
|
+
export type { GetRelationshipOptions, IntegrityDescriptor, OpenProductOptions, OperationOptions, ProductIdentity, ProductManifestDescriptor, ProductScope, RequestKind, RequestLogEntry, RequestLogHook, StaticProductManifest, } from './core.js';
|
|
18
|
+
export { isMonsterProductClient, openMonsterProduct } from './monster-client.js';
|
|
19
|
+
export type { StaticMonsterCatalog, StaticMonsterDetail, StaticMonsterEvidencePage, StaticProductClient, } from './monster-client.js';
|
|
20
|
+
export { isItemProductClient, openItemProduct } from './items.js';
|
|
21
|
+
export type { StaticItemCatalog, StaticItemDetail, StaticItemProductClient } from './items.js';
|
|
22
|
+
export { isEquipmentProductClient, openEquipmentProduct } from './equipment.js';
|
|
23
|
+
export type { StaticEquipmentCatalog, StaticEquipmentDetail, StaticEquipmentProductClient, } from './equipment.js';
|
|
24
|
+
export { isMediaProductClient, openMediaProduct } from './media.js';
|
|
25
|
+
export type { StaticMediaCatalog, StaticMediaDetail, StaticMediaProductClient } from './media.js';
|
|
26
|
+
export { isQuestProductClient, openQuestProduct } from './quests.js';
|
|
27
|
+
export type { QuestAuxiliaryPack, QuestAuxiliaryPage, QuestBrowsePack, QuestBrowseRecord, QuestCatalog, QuestDetailPack, QuestDetailRecord, QuestEvidencePack, QuestEvidencePage, QuestRelationshipPack, QuestRelationshipRecord, StaticQuestProductClient, } from './quests.js';
|
|
28
|
+
export type { StaticDomainCatalog, StaticDomainCatalogRecord, StaticDomainDetailRecord, StaticDomainFactState, StaticDomainProductClient, StaticDomainSourceIdentity, } from './static-domains.js';
|
|
29
|
+
export { openRelease, resolveTargetAvailability } from './release.js';
|
|
30
|
+
export type { OpenReleaseOptions, ReleaseProductDescriptor, StaticReleaseBuild, StaticReleaseClient, TargetAvailability, } from './release.js';
|
|
31
|
+
export { getMonsterBrowse } from './monsters.js';
|
|
32
|
+
export type { GetMonsterBrowseOptions } from './monsters.js';
|
|
33
|
+
export type AnyProductClient = StaticProductClient | StaticMediaProductClient | StaticQuestProductClient | StaticItemProductClient | StaticEquipmentProductClient | StaticDomainProductClient;
|
|
34
|
+
/**
|
|
35
|
+
* Open one pinned product of any supported domain.
|
|
36
|
+
*
|
|
37
|
+
* The manifest is fetched and verified once, then handed to the matching domain
|
|
38
|
+
* client. Prefer the domain-specific openers (or `openRelease`) for narrower bundles.
|
|
39
|
+
*/
|
|
40
|
+
export declare function openProduct(options: OpenProductOptions): Promise<AnyProductClient>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mapletip/data` and `@mapletip/data/client` — the broad compatibility entry.
|
|
3
|
+
*
|
|
4
|
+
* This barrel re-exports the same implementations that the domain subpaths expose and
|
|
5
|
+
* adds the legacy generic `openProduct` dispatcher. Consumers that only need one domain
|
|
6
|
+
* should import `@mapletip/data/monsters`, `/items`, `/equipment`, `/quests`,
|
|
7
|
+
* `/media` or `/release` instead, which keep the other domains out of the bundle.
|
|
8
|
+
*/
|
|
9
|
+
import { openProductContext, isStaticDomain } from './core.js';
|
|
10
|
+
import { monsterProductClient } from './monster-client.js';
|
|
11
|
+
import { itemProductClient } from './items.js';
|
|
12
|
+
import { equipmentProductClient } from './equipment.js';
|
|
13
|
+
import { mediaProductClient } from './media.js';
|
|
14
|
+
import { questProductClient } from './quests.js';
|
|
15
|
+
import { staticDomainProductClient } from './static-domains.js';
|
|
16
|
+
export { ContractVersionError, IntegrityError, InvalidJsonError, InvalidPathError, MissingPageError, MissingRecordError, RequestError, SchemaValidationError, ScopeError, SecureContextRequiredError, StaticClientError, UnknownBuildError, UnsupportedCapabilityError, UnsupportedProductError, resolveMediaUrl, verifyBlob, } from './core.js';
|
|
17
|
+
export { isMonsterProductClient, openMonsterProduct } from './monster-client.js';
|
|
18
|
+
export { isItemProductClient, openItemProduct } from './items.js';
|
|
19
|
+
export { isEquipmentProductClient, openEquipmentProduct } from './equipment.js';
|
|
20
|
+
export { isMediaProductClient, openMediaProduct } from './media.js';
|
|
21
|
+
export { isQuestProductClient, openQuestProduct } from './quests.js';
|
|
22
|
+
export { openRelease, resolveTargetAvailability } from './release.js';
|
|
23
|
+
export { getMonsterBrowse } from './monsters.js';
|
|
24
|
+
/**
|
|
25
|
+
* Open one pinned product of any supported domain.
|
|
26
|
+
*
|
|
27
|
+
* The manifest is fetched and verified once, then handed to the matching domain
|
|
28
|
+
* client. Prefer the domain-specific openers (or `openRelease`) for narrower bundles.
|
|
29
|
+
*/
|
|
30
|
+
export async function openProduct(options) {
|
|
31
|
+
const context = await openProductContext(options);
|
|
32
|
+
const productId = context.manifest.productId;
|
|
33
|
+
if (productId === 'items')
|
|
34
|
+
return itemProductClient(context);
|
|
35
|
+
if (productId === 'equipment')
|
|
36
|
+
return equipmentProductClient(context);
|
|
37
|
+
if (productId === 'media')
|
|
38
|
+
return mediaProductClient(context);
|
|
39
|
+
if (productId === 'quests')
|
|
40
|
+
return questProductClient(context);
|
|
41
|
+
if (isStaticDomain(productId))
|
|
42
|
+
return staticDomainProductClient(context);
|
|
43
|
+
return monsterProductClient(context);
|
|
44
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static normalized domain products (maps, NPCs, reactors, skills, jobs, recipes,
|
|
3
|
+
* cash-shop, avatars). These products carry no generated Ajv validators; their
|
|
4
|
+
* envelopes are checked against the same contract/scope rules as every other pack.
|
|
5
|
+
*/
|
|
6
|
+
import { type OpenProductOptions, type OperationOptions, type ProductScope, type StaticProductManifest, type VerifiedProductContext } from './core.js';
|
|
7
|
+
export type StaticDomainFactState = {
|
|
8
|
+
readonly state: 'known';
|
|
9
|
+
readonly value: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly state: 'unknown';
|
|
12
|
+
readonly reason?: string | null;
|
|
13
|
+
} | {
|
|
14
|
+
readonly state: 'unsupported';
|
|
15
|
+
readonly capability: string;
|
|
16
|
+
};
|
|
17
|
+
export interface StaticDomainSourceIdentity {
|
|
18
|
+
readonly namespace?: string;
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly discriminator?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface StaticDomainCatalogRecord {
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly sourceIdentity: StaticDomainSourceIdentity;
|
|
25
|
+
readonly name: StaticDomainFactState;
|
|
26
|
+
readonly facts: Readonly<Record<string, unknown>>;
|
|
27
|
+
}
|
|
28
|
+
export interface StaticDomainCatalog {
|
|
29
|
+
readonly format: 'mapletip.domain-catalog/1';
|
|
30
|
+
readonly contractVersion: string;
|
|
31
|
+
readonly scope: ProductScope;
|
|
32
|
+
readonly domain: string;
|
|
33
|
+
readonly coverage: 'partial';
|
|
34
|
+
readonly recordCount: number;
|
|
35
|
+
readonly records: readonly StaticDomainCatalogRecord[];
|
|
36
|
+
}
|
|
37
|
+
export interface StaticDomainDetailRecord extends StaticDomainCatalogRecord {
|
|
38
|
+
readonly related: Readonly<Record<string, readonly Readonly<Record<string, unknown>>[]>>;
|
|
39
|
+
}
|
|
40
|
+
export interface StaticDomainProductClient {
|
|
41
|
+
readonly manifest: StaticProductManifest;
|
|
42
|
+
loadCatalog(options?: OperationOptions): Promise<StaticDomainCatalog>;
|
|
43
|
+
getDetail(key: string, options?: OperationOptions): Promise<StaticDomainDetailRecord>;
|
|
44
|
+
}
|
|
45
|
+
/** Build a static domain client over an already verified product manifest. */
|
|
46
|
+
export declare function staticDomainProductClient(context: VerifiedProductContext): StaticDomainProductClient;
|
|
47
|
+
/** Open one pinned static domain product. */
|
|
48
|
+
export declare function openStaticDomainProduct(options: OpenProductOptions): Promise<StaticDomainProductClient>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static normalized domain products (maps, NPCs, reactors, skills, jobs, recipes,
|
|
3
|
+
* cash-shop, avatars). These products carry no generated Ajv validators; their
|
|
4
|
+
* envelopes are checked against the same contract/scope rules as every other pack.
|
|
5
|
+
*/
|
|
6
|
+
import { MissingRecordError, SchemaValidationError, UnsupportedProductError, assertEnvelopeVersion, assertSameScope, isStaticDomain, memoized, openProductContext, recordRangeDescriptor, } from './core.js';
|
|
7
|
+
function asObject(value) {
|
|
8
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function assertDomainCatalog(value, path, manifest) {
|
|
13
|
+
const object = asObject(value);
|
|
14
|
+
const records = object?.records;
|
|
15
|
+
const scope = asObject(object?.scope);
|
|
16
|
+
if (!object || object.format !== 'mapletip.domain-catalog/1'
|
|
17
|
+
|| object.contractVersion !== '2.0'
|
|
18
|
+
|| object.domain !== manifest.productId
|
|
19
|
+
|| object.coverage !== 'partial'
|
|
20
|
+
|| !scope || scope.family_id !== manifest.scope.family_id || scope.build_id !== manifest.scope.build_id
|
|
21
|
+
|| !Number.isSafeInteger(object.recordCount)
|
|
22
|
+
|| !Array.isArray(records)
|
|
23
|
+
|| object.recordCount !== records.length) {
|
|
24
|
+
throw new SchemaValidationError(path, 'invalid MapleKit normalized domain catalog envelope');
|
|
25
|
+
}
|
|
26
|
+
let previous = '';
|
|
27
|
+
for (const recordValue of records) {
|
|
28
|
+
const record = asObject(recordValue);
|
|
29
|
+
const source = asObject(record?.sourceIdentity);
|
|
30
|
+
const name = asObject(record?.name);
|
|
31
|
+
if (!record || typeof record.key !== 'string' || record.key.length === 0
|
|
32
|
+
|| record.key <= previous || !source || typeof source.id !== 'string'
|
|
33
|
+
|| source.id.length === 0 || !name || typeof record.facts !== 'object'
|
|
34
|
+
|| record.facts === null || Array.isArray(record.facts)) {
|
|
35
|
+
throw new SchemaValidationError(path, 'invalid or unordered MapleKit domain catalog record');
|
|
36
|
+
}
|
|
37
|
+
previous = record.key;
|
|
38
|
+
}
|
|
39
|
+
return object;
|
|
40
|
+
}
|
|
41
|
+
function assertDomainDetail(value, path, expectedKey) {
|
|
42
|
+
const record = asObject(value);
|
|
43
|
+
const source = asObject(record?.sourceIdentity);
|
|
44
|
+
const name = asObject(record?.name);
|
|
45
|
+
const related = asObject(record?.related);
|
|
46
|
+
if (!record || record.key !== expectedKey || !source || typeof source.id !== 'string'
|
|
47
|
+
|| !name || !related || typeof record.facts !== 'object'
|
|
48
|
+
|| record.facts === null || Array.isArray(record.facts)) {
|
|
49
|
+
throw new SchemaValidationError(path, 'invalid MapleKit normalized domain detail record');
|
|
50
|
+
}
|
|
51
|
+
for (const rows of Object.values(related)) {
|
|
52
|
+
if (!Array.isArray(rows) || rows.some((row) => !asObject(row))) {
|
|
53
|
+
throw new SchemaValidationError(path, 'invalid related row graph in MapleKit domain detail');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return record;
|
|
57
|
+
}
|
|
58
|
+
/** Build a static domain client over an already verified product manifest. */
|
|
59
|
+
export function staticDomainProductClient(context) {
|
|
60
|
+
const { manifest, fetchJson, minimumVersion } = context;
|
|
61
|
+
if (!isStaticDomain(manifest.productId))
|
|
62
|
+
throw new UnsupportedProductError(manifest.productId);
|
|
63
|
+
let catalogPromise;
|
|
64
|
+
const detailPackCache = new Map();
|
|
65
|
+
async function loadCatalog(operation) {
|
|
66
|
+
if (!catalogPromise) {
|
|
67
|
+
catalogPromise = (async () => {
|
|
68
|
+
const raw = await fetchJson(manifest.catalog, 'catalog', operation);
|
|
69
|
+
assertEnvelopeVersion(raw.contractVersion, minimumVersion, manifest.catalog.path);
|
|
70
|
+
const catalog = assertDomainCatalog(raw, manifest.catalog.path, manifest);
|
|
71
|
+
assertSameScope(catalog.scope, manifest.scope, manifest.catalog.path);
|
|
72
|
+
if (catalog.records.length !== catalog.recordCount) {
|
|
73
|
+
throw new SchemaValidationError(manifest.catalog.path, 'recordCount does not match records length');
|
|
74
|
+
}
|
|
75
|
+
return catalog;
|
|
76
|
+
})();
|
|
77
|
+
void catalogPromise.catch(() => { catalogPromise = undefined; });
|
|
78
|
+
}
|
|
79
|
+
return catalogPromise;
|
|
80
|
+
}
|
|
81
|
+
async function getDetail(key, operation) {
|
|
82
|
+
const descriptor = recordRangeDescriptor(manifest.detailPacks, key, 'detail', manifest.productId);
|
|
83
|
+
const pack = await memoized(detailPackCache, descriptor.path, async () => {
|
|
84
|
+
const raw = await fetchJson(descriptor, 'detail', operation);
|
|
85
|
+
const envelope = asObject(raw);
|
|
86
|
+
if (!envelope || envelope.format !== 'mapletip.domain-detail-pack/1'
|
|
87
|
+
|| envelope.contractVersion !== '2.0'
|
|
88
|
+
|| envelope.domain !== manifest.productId
|
|
89
|
+
|| !envelope.scope || JSON.stringify(envelope.scope) !== JSON.stringify(manifest.scope)) {
|
|
90
|
+
throw new SchemaValidationError(descriptor.path, 'invalid MapleKit normalized domain detail pack envelope');
|
|
91
|
+
}
|
|
92
|
+
assertEnvelopeVersion(envelope.contractVersion, minimumVersion, descriptor.path);
|
|
93
|
+
const records = asObject(envelope.records);
|
|
94
|
+
if (!records || Object.keys(records).length !== descriptor.count) {
|
|
95
|
+
throw new SchemaValidationError(descriptor.path, 'detail pack count does not match its descriptor');
|
|
96
|
+
}
|
|
97
|
+
const normalized = {};
|
|
98
|
+
for (const [recordKey, recordValue] of Object.entries(records)) {
|
|
99
|
+
normalized[recordKey] = assertDomainDetail(recordValue, descriptor.path, recordKey);
|
|
100
|
+
}
|
|
101
|
+
return normalized;
|
|
102
|
+
});
|
|
103
|
+
const detail = pack[key];
|
|
104
|
+
if (!detail)
|
|
105
|
+
throw new MissingRecordError(key, manifest.productId);
|
|
106
|
+
return detail;
|
|
107
|
+
}
|
|
108
|
+
return { manifest, loadCatalog, getDetail };
|
|
109
|
+
}
|
|
110
|
+
/** Open one pinned static domain product. */
|
|
111
|
+
export async function openStaticDomainProduct(options) {
|
|
112
|
+
return staticDomainProductClient(await openProductContext(options));
|
|
113
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* Generated automatically from Rust-owned wire definitions. Do not edit. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compact catalog projection of [`FactState`]. It keeps the state and a
|
|
5
|
+
* positively observed value, retains the coverage witness for `absent`, and
|
|
6
|
+
* omits source attribution and explanatory metadata.
|
|
7
|
+
*/
|
|
8
|
+
export type CatalogFact<T = RawScalar> =
|
|
9
|
+
| {
|
|
10
|
+
state: "known";
|
|
11
|
+
value: T;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
state: "unknown";
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
coverageKey: CoverageKey;
|
|
18
|
+
state: "absent";
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
state: "unsupported";
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* A scalar source observation that preserves the source value kind.
|
|
25
|
+
*/
|
|
26
|
+
export type RawScalar =
|
|
27
|
+
| {
|
|
28
|
+
kind: "integer";
|
|
29
|
+
value: SafeInt64;
|
|
30
|
+
}
|
|
31
|
+
| {
|
|
32
|
+
kind: "real";
|
|
33
|
+
value: number;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
kind: "text";
|
|
37
|
+
value: string;
|
|
38
|
+
}
|
|
39
|
+
| {
|
|
40
|
+
kind: "boolean";
|
|
41
|
+
value: boolean;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
kind: "bytes";
|
|
45
|
+
value: number[];
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
kind: "source-null";
|
|
49
|
+
};
|
|
50
|
+
export type SafeInt64 = number;
|
|
51
|
+
export type CoverageKey = string;
|