@mapletip/data 0.2.0 → 0.5.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/README.md +204 -2
- package/client/contract-compatibility.mjs +10 -0
- package/client/core.d.ts +5 -0
- package/client/core.js +10 -0
- package/client/items.d.ts +28 -5
- package/client/items.js +60 -8
- package/client/map-geography.d.ts +4 -0
- package/client/map-geography.js +2 -0
- package/client/maps.d.ts +22 -0
- package/client/maps.js +25 -0
- package/client/monster-client.d.ts +14 -1
- package/client/monster-client.js +55 -3
- package/client/monsters.d.ts +37 -8
- package/client/monsters.js +51 -0
- package/client/static-client.d.ts +7 -5
- package/client/static-client.js +3 -2
- package/client/static-domains.d.ts +8 -1
- package/client/static-domains.js +27 -3
- package/generated/item-browse-pack-2.d.ts +83 -1
- package/generated/item-browse-pack-2.validator.js +2 -2
- package/generated/item-related-entity-browse-1.d.ts +48 -0
- package/generated/item-related-entity-browse-1.validator.js +1 -0
- package/generated/map-catalog-2-1.d.ts +103 -0
- package/generated/map-catalog-2-1.validator.d.ts +18 -0
- package/generated/map-catalog-2-1.validator.js +1 -0
- package/generated/map-geography.d.ts +79 -0
- package/generated/map-geography.validator.d.ts +18 -0
- package/generated/map-geography.validator.js +1 -0
- package/generated/monster-catalog-2-1.d.ts +253 -0
- package/generated/monster-catalog-2-1.validator.js +17 -0
- package/generated/monster-catalog-2-2.d.ts +332 -0
- package/generated/monster-catalog-2-2.validator.js +17 -0
- package/generated/monster-catalog-2-3.d.ts +339 -0
- package/generated/monster-catalog-2-3.validator.js +17 -0
- package/generated/monster-drop-browse.d.ts +123 -0
- package/generated/monster-drop-browse.validator.js +17 -0
- package/generated/monster-location-browse-1-1.d.ts +79 -0
- package/generated/monster-location-browse-1-1.validator.js +17 -0
- package/generated/monster-location-browse.d.ts +69 -0
- package/generated/monster-location-browse.validator.js +17 -0
- package/generated/product-manifest.d.ts +23 -0
- package/generated/product-manifest.validator.js +1 -1
- package/package.json +38 -2
package/README.md
CHANGED
|
@@ -55,6 +55,124 @@ existing typed failure (`UNKNOWN_BUILD`, `UNSUPPORTED_PRODUCT`, `SCOPE_ERROR`,
|
|
|
55
55
|
Supported builds are `cw-launch` (`classic-world`) and `gms-v83` (`gms`). When several
|
|
56
56
|
reads share one release, keep using `openRelease` and reuse the opened release client.
|
|
57
57
|
|
|
58
|
+
### Monster sprites
|
|
59
|
+
|
|
60
|
+
Monster catalog contract `2.1` gives every row a `sprite` fact: when `known`, its value
|
|
61
|
+
is the content ID (PNG SHA-256) of the `monster.sprite` image that the same build's Media
|
|
62
|
+
product selects. `monsterSpriteUrl` turns it into the blob URL under the release root, so
|
|
63
|
+
a browse page needs no Media catalog, Media detail pack or second product:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { getMonsterBrowse, hasMonsterSprites, monsterSpriteUrl } from '@mapletip/data/monsters';
|
|
67
|
+
|
|
68
|
+
const catalog = await getMonsterBrowse({ baseUrl: releaseRootUrl, pin: releasePin, buildId: 'gms-v83' });
|
|
69
|
+
if (!hasMonsterSprites(catalog)) throw new Error('this pin predates Monster sprites');
|
|
70
|
+
const rows = catalog.records.map((row) => ({
|
|
71
|
+
key: row.key,
|
|
72
|
+
spriteUrl: row.sprite.state === 'known' ? monsterSpriteUrl(releaseRootUrl, row.sprite.value) : undefined,
|
|
73
|
+
}));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `sprite` state mirrors Media exactly: `unknown` (for example, a monster with only
|
|
77
|
+
stub frames), `absent` with its coverage key, or `unsupported`. Never substitute a
|
|
78
|
+
placeholder or another build's image for a non-known state. Release assembly fails unless
|
|
79
|
+
every row agrees with Media and every known content ID names a blob in the release.
|
|
80
|
+
`monsterSpriteUrl` throws `SCHEMA_VALIDATION_ERROR` for anything but a lowercase 64-hex
|
|
81
|
+
digest. `getMonsterBrowse` still requests only the release manifest named by the pin, the
|
|
82
|
+
build index, the product manifest and the Monster catalog.
|
|
83
|
+
|
|
84
|
+
Releases built before revision `2.1` carry contract-`2.0` catalogs without `sprite`; this
|
|
85
|
+
SDK reads both, and `hasMonsterSprites` narrows a catalog to revisions `2.1` or `2.2`. `@mapletip/data`
|
|
86
|
+
`0.2.0` and older reject a `2.1` catalog with a typed `SCHEMA_VALIDATION_ERROR` instead of
|
|
87
|
+
returning rows without sprites, so upgrade to `0.3.0` before adopting a pin that carries
|
|
88
|
+
it.
|
|
89
|
+
|
|
90
|
+
### Monster drops and locations
|
|
91
|
+
|
|
92
|
+
Monster catalog `2.2` keeps these summaries on every Monster row. The catalog-level
|
|
93
|
+
`browseCoverage` carries the shared witnesses and conventions: `nativeDropsAbsentCoverageKey`
|
|
94
|
+
is present exactly when at least one native drop row is `absent`; `historicalDrops` says whether
|
|
95
|
+
the build has a historical overlay; `locationsAbsentCoverageKey` is present exactly when at
|
|
96
|
+
least one location row is `absent`; and `locationScope` is `client-placements`. An absent row
|
|
97
|
+
does not repeat the catalog coverage key.
|
|
98
|
+
|
|
99
|
+
`drops.native` describes evidence from the selected build. Its `lowerBound: true` marker
|
|
100
|
+
says a `known` count is a lower bound while native source coverage is partial.
|
|
101
|
+
`unknown` means the count could not be established; it does not mean zero.
|
|
102
|
+
`drops.historical` is present only when the build
|
|
103
|
+
advertises historical overlay support. CW Launch reports native data separately from
|
|
104
|
+
its historical entries, so a Monster can have no native data and still have historical
|
|
105
|
+
drop assertions. GMS v83 reports historical drops as unsupported.
|
|
106
|
+
|
|
107
|
+
The drop browse artifact stores each distinct item once in its sorted `items[]` table.
|
|
108
|
+
Each record entry's `item` is an index into that table. Native entries describe this
|
|
109
|
+
build; historical entries also carry `sourceBuild` and `confidence`. A resolved
|
|
110
|
+
Equipment category is `{ kind: "equip-slot", code }`; display labels for that code come
|
|
111
|
+
from the same build's Equipment product slot labels. Other resolved item categories use
|
|
112
|
+
`{ kind: "item-class", value }`, where the value is `ItemBrowseClass`. Unresolved item
|
|
113
|
+
references use `{ kind: "unknown" }`.
|
|
114
|
+
|
|
115
|
+
`detail.dropEvidence.state` keeps its existing native-only meaning. It describes the
|
|
116
|
+
native evidence collection for the Monster and is not changed by historical overlay
|
|
117
|
+
assertions. Read the compact historical assertions from the drop browse artifact:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { getMonsterDropBrowse } from '@mapletip/data/monsters';
|
|
121
|
+
|
|
122
|
+
const dropBrowse = await getMonsterDropBrowse({
|
|
123
|
+
baseUrl: releaseRootUrl,
|
|
124
|
+
pin: releasePin,
|
|
125
|
+
buildId: 'cw-launch',
|
|
126
|
+
});
|
|
127
|
+
const itemKey = 'item-0123456789abcdef';
|
|
128
|
+
const monstersWithItem = Object.entries(dropBrowse.records)
|
|
129
|
+
.filter(([, record]) => record?.entries.some((entry) => {
|
|
130
|
+
const item = dropBrowse.items[entry.item];
|
|
131
|
+
return item?.itemRef.state === 'resolved' && item.itemRef.key === itemKey;
|
|
132
|
+
}))
|
|
133
|
+
.map(([monsterKey]) => monsterKey);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Location summaries count client map placements, not live spawn rates. `known` carries
|
|
137
|
+
distinct map and placement counts; `absent` carries neither count and uses the
|
|
138
|
+
catalog-level coverage key; `unresolved` carries counts for the resolved portion when
|
|
139
|
+
some placements could not be attributed. Location browse entries carry the public
|
|
140
|
+
`mapKey`, map name and placement count, with no coordinates or media references.
|
|
141
|
+
`regionCode` is the raw source classification code, not a geographic region.
|
|
142
|
+
|
|
143
|
+
Filter by map key, then resolve that map's minimap through the same build's Media product.
|
|
144
|
+
The Media record key is the map key; its `map.minimap` descriptor resolves to a
|
|
145
|
+
content-addressed image URL:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { getMonsterLocationBrowse } from '@mapletip/data/monsters';
|
|
149
|
+
import { openRelease } from '@mapletip/data/release';
|
|
150
|
+
import { resolveMediaUrl } from '@mapletip/data/media';
|
|
151
|
+
|
|
152
|
+
const buildId = 'gms-v83';
|
|
153
|
+
const locationBrowse = await getMonsterLocationBrowse({
|
|
154
|
+
baseUrl: releaseRootUrl,
|
|
155
|
+
pin: releasePin,
|
|
156
|
+
buildId,
|
|
157
|
+
});
|
|
158
|
+
const mapKey = 'map-0123456789abcdef';
|
|
159
|
+
const monstersOnMap = Object.entries(locationBrowse.records)
|
|
160
|
+
.filter(([, record]) => record?.entries.some((entry) => entry.mapKey === mapKey))
|
|
161
|
+
.map(([monsterKey]) => monsterKey);
|
|
162
|
+
|
|
163
|
+
const release = await openRelease({ baseUrl: releaseRootUrl, pin: releasePin });
|
|
164
|
+
const media = await release.openMediaBuild(buildId);
|
|
165
|
+
const mapMedia = (await media.loadCatalog()).records.find((record) => record.key === mapKey);
|
|
166
|
+
const minimap = mapMedia?.media['map.minimap'];
|
|
167
|
+
const minimapUrl = minimap?.state === 'known'
|
|
168
|
+
? resolveMediaUrl(releaseRootUrl, minimap.value)
|
|
169
|
+
: undefined;
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`0.3.0` reads catalog revisions `2.0` and `2.1` but rejects `2.2` with a typed
|
|
173
|
+
`SCHEMA_VALIDATION_ERROR`. Use `0.4.0` before adopting a release with drop and location
|
|
174
|
+
browse summaries.
|
|
175
|
+
|
|
58
176
|
`pin` is the parsed `pin.json` published next to the release root by whoever assembled
|
|
59
177
|
the release: it names the manifest path, SHA-256 and byte length and is the caller's
|
|
60
178
|
trust anchor. The SDK never resolves a "current" release on its own and never fetches a
|
|
@@ -113,6 +231,66 @@ It rejects any content ID that is not a 64-character lowercase SHA-256 digest wi
|
|
|
113
231
|
`SCHEMA_VALIDATION_ERROR`, so a row's `icon.value` can be used without building a
|
|
114
232
|
make-believe descriptor.
|
|
115
233
|
|
|
234
|
+
### Player facts and related identities
|
|
235
|
+
|
|
236
|
+
Item Browse 2.1 adds sparse typed player facts. An omitted sparse fact means its value is
|
|
237
|
+
unknown, unless that field's per-build coverage says `unsupported`; it does not mean
|
|
238
|
+
zero. A recorded `sellPrice: 0` is a real zero. Use
|
|
239
|
+
`hasItemPlayerFacts` to narrow a browse result before relying on the 2.1 coverage block;
|
|
240
|
+
older pinned 2.0 products remain readable. `coverage.playerFacts.requirementJobs` is
|
|
241
|
+
`unsupported` in both current builds because neither has a normalized non-equipment job
|
|
242
|
+
requirement source. NPC shop purchase prices are deferred: sell price is not a shop price.
|
|
243
|
+
|
|
244
|
+
`requirement.level` is the item's direct use-level requirement. The separate
|
|
245
|
+
`levelRange` fact carries GMS Monster Crystal `monsterLevelMin`/`monsterLevelMax` and
|
|
246
|
+
Secret Crystal `requirementLevelDelta`; these values are never folded into
|
|
247
|
+
`requirement.level`. Recovery keeps flat `recovery.hp`/`mp` distinct from percentage
|
|
248
|
+
`recovery.hpPercent`/`mpPercent`. Buff effects use the closed `ItemBrowseBuffStat`
|
|
249
|
+
vocabulary, and unknown source `spec` keys fail the publication gate unless they have
|
|
250
|
+
an explicit exclusion.
|
|
251
|
+
|
|
252
|
+
Related identities are stored in one lazy roster artifact per relationship kind. The
|
|
253
|
+
Items manifest pins each artifact by digest and byte length. In a roster,
|
|
254
|
+
`targets` is the target index table and `records[itemKey]` contains indices into that
|
|
255
|
+
table. Load only the kind needed for the current view. For example, this resolves the
|
|
256
|
+
`Dropped by` names for one item without loading all seven rosters:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
getItemBrowse,
|
|
261
|
+
getItemRelatedEntities,
|
|
262
|
+
hasItemPlayerFacts,
|
|
263
|
+
} from '@mapletip/data/items';
|
|
264
|
+
import type { GetItemBrowseOptions } from '@mapletip/data/items';
|
|
265
|
+
|
|
266
|
+
declare const releaseRootUrl: GetItemBrowseOptions['baseUrl'];
|
|
267
|
+
declare const releasePin: GetItemBrowseOptions['pin'];
|
|
268
|
+
|
|
269
|
+
const browse = await getItemBrowse({
|
|
270
|
+
baseUrl: releaseRootUrl,
|
|
271
|
+
pin: releasePin,
|
|
272
|
+
buildId: 'gms-v83',
|
|
273
|
+
});
|
|
274
|
+
if (!hasItemPlayerFacts(browse)) throw new Error('this pin predates Item player facts');
|
|
275
|
+
|
|
276
|
+
const item = browse.records.find((row) => row.sourceId === '02022001');
|
|
277
|
+
if (!item) throw new Error('item is not present in this build');
|
|
278
|
+
const roster = await getItemRelatedEntities({
|
|
279
|
+
baseUrl: releaseRootUrl,
|
|
280
|
+
pin: releasePin,
|
|
281
|
+
buildId: 'gms-v83',
|
|
282
|
+
kind: 'monster-drop',
|
|
283
|
+
});
|
|
284
|
+
const droppedBy = (roster.records[item.key] ?? []).flatMap((targetIndex) => {
|
|
285
|
+
const target = roster.targets[targetIndex];
|
|
286
|
+
return target ? [{ key: target.key, name: target.displayName ?? target.key }] : [];
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The `droppedBy` result can be rendered as the row's `Dropped by` names. Target names may
|
|
291
|
+
be unknown, in which case the stable target key remains available. The roster read is
|
|
292
|
+
digest-verified, build-scoped, uncached and has no fallback to another build.
|
|
293
|
+
|
|
116
294
|
## What each entry loads
|
|
117
295
|
|
|
118
296
|
Monster catalog validation and the release/manifest/index validators are imported
|
|
@@ -121,8 +299,8 @@ operation that needs it, as a separate on-demand chunk:
|
|
|
121
299
|
|
|
122
300
|
| Entry | Eager generated validators | Loaded on demand |
|
|
123
301
|
| --- | --- | --- |
|
|
124
|
-
| `/monsters` | release pin, release manifest, build index, product manifest, Monster catalog 1.x and 2.
|
|
125
|
-
| `/items` | product manifest only (every Item validator is on demand) | browse, catalog, detail, relationship and evidence packs, plus the release client behind `getItemBrowse` |
|
|
302
|
+
| `/monsters` | release pin, release manifest, build index, product manifest, Monster catalog 1.x, 2.0 and 2.1 | Monster detail, location/relationship, core-relationship and evidence packs |
|
|
303
|
+
| `/items` | product manifest only (every Item validator is on demand) | browse, per-kind related-entity roster, catalog, detail, relationship and evidence packs, plus the release client behind `getItemBrowse` and `getItemRelatedEntities` |
|
|
126
304
|
| `/equipment` | that domain's catalog, detail, relationship and evidence packs | — |
|
|
127
305
|
| `/quests` | none (Quest validators are always on demand) | catalog, browse, detail, relationship, auxiliary and evidence packs |
|
|
128
306
|
| `/media` | that domain's catalog, detail packs | — |
|
|
@@ -256,3 +434,27 @@ const evPage = await client.getEvidencePage('quest-ev-1000');
|
|
|
256
434
|
|
|
257
435
|
The Quest client supports lazy per-operation validator loading via dynamic `import()` to ensure that only the validator required for a specific operation is fetched. Browse records and detail records are lazily addressed through bounded key-range descriptors without per-record eager loads or mandatory catalog fetching for direct detail lookups.
|
|
258
436
|
|
|
437
|
+
## Map geography
|
|
438
|
+
|
|
439
|
+
Map geography uses a build-scoped, digest-verified read model. `getMapGeography` reads that model from the `maps` product; `openMapProduct` returns a product with `loadGeography()`, and `release.openDomainBuild(buildId, 'maps')` provides the same operation when an opened release is reused. Map catalog rows carry compact geography keys and provenance, while the geography model stores the labels once.
|
|
440
|
+
|
|
441
|
+
The derivation layers run in order: reviewed placements, the build-family region allowlist, area names from Map marks (Hidden Street marks are excluded), then collection classification. Maps with no applicable layer have provenance `none`. Provenance values are `reviewed-overlay`, `source-region-allowlist`, `source-map-mark`, `source-classification`, and `none`.
|
|
442
|
+
|
|
443
|
+
Reviewed placement status `supported` means local witnesses support the entrance and placement. Citation register entries that could not be fetched remain inactive; they are not treated as verified external evidence. The carried-over placements in these builds use `supported` status.
|
|
444
|
+
|
|
445
|
+
Geography keys are stable filters: `region:<slug>`, `area:<region-slug>/<area-slug>`, and `collection:<slug>`. Monster catalog 2.3 rows contain the union of their location entries' region and location keys. A Region filter can use those keys directly:
|
|
446
|
+
|
|
447
|
+
```ts
|
|
448
|
+
import { getMonsterBrowse, hasMonsterGeography } from '@mapletip/data/monsters';
|
|
449
|
+
|
|
450
|
+
const catalog = await getMonsterBrowse({
|
|
451
|
+
baseUrl: releaseRootUrl,
|
|
452
|
+
pin: releasePin,
|
|
453
|
+
buildId: 'gms-v83',
|
|
454
|
+
});
|
|
455
|
+
if (!hasMonsterGeography(catalog)) throw new Error('This release predates Monster geography');
|
|
456
|
+
|
|
457
|
+
const regionKey = 'region:henesys';
|
|
458
|
+
const monstersInRegion = catalog.records.filter((row) => row.geography.regionKeys.includes(regionKey));
|
|
459
|
+
```
|
|
460
|
+
|
|
@@ -8,11 +8,16 @@ const MINIMUM_VERSION_BY_CAPABILITY = new Map([
|
|
|
8
8
|
['product-input-identity-v1', { major: 1, minor: 2 }],
|
|
9
9
|
['release-manifest-v1', { major: 1, minor: 2 }],
|
|
10
10
|
['monster-core-v1', { major: 2, minor: 0 }],
|
|
11
|
+
['monster-drop-browse-v1', { major: 2, minor: 0 }],
|
|
12
|
+
['monster-location-browse-v1', { major: 2, minor: 0 }],
|
|
13
|
+
['item-player-facts-v1', { major: 2, minor: 0 }],
|
|
14
|
+
['item-related-entities-v1', { major: 2, minor: 0 }],
|
|
11
15
|
['quests-v1', { major: 2, minor: 0 }],
|
|
12
16
|
['items-v1', { major: 2, minor: 0 }],
|
|
13
17
|
['equipment-v1', { major: 2, minor: 0 }],
|
|
14
18
|
['media-v1', { major: 2, minor: 0 }],
|
|
15
19
|
['maps-v1', { major: 2, minor: 0 }],
|
|
20
|
+
['map-geography-v1', { major: 2, minor: 0 }],
|
|
16
21
|
['npcs-v1', { major: 2, minor: 0 }],
|
|
17
22
|
['reactors-v1', { major: 2, minor: 0 }],
|
|
18
23
|
['skills-v1', { major: 2, minor: 0 }],
|
|
@@ -33,11 +38,16 @@ const supportedClientCapabilities = new Set([
|
|
|
33
38
|
'product-input-identity-v1',
|
|
34
39
|
'release-manifest-v1',
|
|
35
40
|
'monster-core-v1',
|
|
41
|
+
'monster-drop-browse-v1',
|
|
42
|
+
'monster-location-browse-v1',
|
|
43
|
+
'item-player-facts-v1',
|
|
44
|
+
'item-related-entities-v1',
|
|
36
45
|
'quests-v1',
|
|
37
46
|
'items-v1',
|
|
38
47
|
'equipment-v1',
|
|
39
48
|
'media-v1',
|
|
40
49
|
'maps-v1',
|
|
50
|
+
'map-geography-v1',
|
|
41
51
|
'npcs-v1',
|
|
42
52
|
'reactors-v1',
|
|
43
53
|
'skills-v1',
|
package/client/core.d.ts
CHANGED
|
@@ -120,6 +120,11 @@ export declare function normalizedBaseUrl(value: string | URL): URL;
|
|
|
120
120
|
export declare function verifyBlob(bytes: Uint8Array, descriptor: IntegrityDescriptor): Promise<void>;
|
|
121
121
|
export declare function resolveReleasePath(baseUrl: URL | string, path: string): string;
|
|
122
122
|
export declare function resolveMediaUrl(baseUrl: URL | string, descriptor: IntegrityDescriptor): string;
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a Media content ID (the PNG's lowercase SHA-256 digest) to its blob URL under
|
|
125
|
+
* the release root. `subject` names the caller's field in the validation error.
|
|
126
|
+
*/
|
|
127
|
+
export declare function resolveMediaContentUrl(baseUrl: URL | string, contentId: string, subject: string): string;
|
|
123
128
|
export declare function assertCompatibleManifest(manifest: ConnectedProductManifest | ReleaseManifest): number;
|
|
124
129
|
export declare function createJsonFetcher(options: {
|
|
125
130
|
readonly baseUrl: URL;
|
package/client/core.js
CHANGED
|
@@ -127,6 +127,16 @@ export function resolveReleasePath(baseUrl, path) {
|
|
|
127
127
|
export function resolveMediaUrl(baseUrl, descriptor) {
|
|
128
128
|
return resolveReleasePath(baseUrl, descriptor.path);
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Resolve a Media content ID (the PNG's lowercase SHA-256 digest) to its blob URL under
|
|
132
|
+
* the release root. `subject` names the caller's field in the validation error.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveMediaContentUrl(baseUrl, contentId, subject) {
|
|
135
|
+
if (!/^[0-9a-f]{64}$/.test(contentId)) {
|
|
136
|
+
throw new SchemaValidationError(contentId, `${subject} content ID must be a SHA-256 hex digest`);
|
|
137
|
+
}
|
|
138
|
+
return resolveReleasePath(baseUrl, `media/images/${contentId.slice(0, 2)}/${contentId}.png`);
|
|
139
|
+
}
|
|
130
140
|
function parseJson(bytes, path) {
|
|
131
141
|
try {
|
|
132
142
|
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
package/client/items.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* client is imported on demand, so a browse-only consumer never downloads catalog,
|
|
6
6
|
* detail, relationship, evidence validators or the Monster client.
|
|
7
7
|
*/
|
|
8
|
-
import type { ItemBrowsePack, ItemBrowseCoverage, ItemBrowseRecord } from '../generated/item-browse-pack-2.d.ts';
|
|
8
|
+
import type { ItemBrowsePack, ItemBrowseCoverage, ItemBrowsePlayerFactCoverage, ItemBrowseBuff, ItemBrowseBuffEffect, ItemBrowseBuffStat, ItemBrowseBuffUnit, ItemBrowseCoverageState, ItemBrowseJobFamily, ItemBrowseLevelRange, ItemBrowseRecovery, ItemBrowseRequirement, ItemBrowseRecord } from '../generated/item-browse-pack-2.d.ts';
|
|
9
|
+
import type { ItemBrowseRelationshipKind, ItemRelatedEntityBrowsePack, ItemRelatedEntityTarget } from '../generated/item-related-entity-browse-1.d.ts';
|
|
9
10
|
import type { ItemCatalog } from '../generated/item-catalog-2.d.ts';
|
|
10
11
|
import type { ItemDetailRecord } from '../generated/item-detail-2.d.ts';
|
|
11
12
|
import type { ItemRelationshipPage } from '../generated/item-relationship-pack-2.d.ts';
|
|
@@ -15,22 +16,37 @@ import { type GetRelationshipOptions, type OpenProductOptions, type OperationOpt
|
|
|
15
16
|
export type StaticItemCatalog = ItemCatalog;
|
|
16
17
|
export type StaticItemDetail = ItemDetailRecord;
|
|
17
18
|
export type { GetRelationshipOptions };
|
|
18
|
-
export type { ItemBrowsePack, ItemBrowseCoverage, ItemBrowseRecord };
|
|
19
|
+
export type { ItemBrowsePack, ItemBrowseCoverage, ItemBrowsePlayerFactCoverage, ItemBrowseBuff, ItemBrowseBuffEffect, ItemBrowseBuffStat, ItemBrowseBuffUnit, ItemBrowseCoverageState, ItemBrowseJobFamily, ItemBrowseLevelRange, ItemBrowseRecovery, ItemBrowseRequirement, ItemBrowseRecord, ItemBrowseRelationshipKind, ItemRelatedEntityBrowsePack, ItemRelatedEntityTarget, };
|
|
19
20
|
export type { OpenReleaseOptions };
|
|
20
21
|
/** One browse row: the compact record plus its stable public key. */
|
|
21
22
|
export type ItemBrowseRow = ItemBrowseRecord & {
|
|
22
23
|
readonly key: string;
|
|
23
24
|
};
|
|
24
|
-
/**
|
|
25
|
-
export interface
|
|
25
|
+
/** Verified Item Browse 2.0, retained for older pinned products. */
|
|
26
|
+
export interface StaticItemBrowseV2_0 {
|
|
27
|
+
readonly contractVersion: '2.0';
|
|
28
|
+
readonly scope: ProductScope;
|
|
29
|
+
readonly coverage: Omit<ItemBrowseCoverage, 'playerFacts'>;
|
|
30
|
+
readonly records: readonly ItemBrowseRow[];
|
|
31
|
+
}
|
|
32
|
+
/** Verified Item Browse 2.1 with its per-build player-fact coverage. */
|
|
33
|
+
export interface StaticItemBrowseV2_1 {
|
|
34
|
+
readonly contractVersion: '2.1';
|
|
26
35
|
readonly scope: ProductScope;
|
|
27
|
-
readonly coverage: ItemBrowseCoverage
|
|
36
|
+
readonly coverage: ItemBrowseCoverage & {
|
|
37
|
+
readonly playerFacts: ItemBrowsePlayerFactCoverage;
|
|
38
|
+
};
|
|
28
39
|
readonly records: readonly ItemBrowseRow[];
|
|
29
40
|
}
|
|
41
|
+
/** The whole verified browse roster of one Item build. */
|
|
42
|
+
export type StaticItemBrowse = StaticItemBrowseV2_0 | StaticItemBrowseV2_1;
|
|
43
|
+
/** Narrow an Item browse result to contract 2.1, which carries player-fact coverage. */
|
|
44
|
+
export declare function hasItemPlayerFacts(browse: StaticItemBrowse): browse is StaticItemBrowseV2_1;
|
|
30
45
|
export interface StaticItemProductClient {
|
|
31
46
|
readonly manifest: StaticProductManifest;
|
|
32
47
|
loadCatalog(options?: OperationOptions): Promise<StaticItemCatalog>;
|
|
33
48
|
loadBrowse(options?: OperationOptions): Promise<StaticItemBrowse>;
|
|
49
|
+
loadRelatedEntities(kind: ItemBrowseRelationshipKind, options?: OperationOptions): Promise<ItemRelatedEntityBrowsePack>;
|
|
34
50
|
getDetail(key: string, options?: OperationOptions): Promise<StaticItemDetail>;
|
|
35
51
|
getRelationshipPage(pageKey: string, options?: GetRelationshipOptions): Promise<ItemRelationshipPage>;
|
|
36
52
|
getEvidencePage(pageKey: string, options?: OperationOptions): Promise<ItemEvidencePage>;
|
|
@@ -54,5 +70,12 @@ export type GetItemBrowseOptions = OpenReleaseOptions & {
|
|
|
54
70
|
* client is imported on demand so the Item browse entry stays narrow.
|
|
55
71
|
*/
|
|
56
72
|
export declare function getItemBrowse(options: GetItemBrowseOptions): Promise<StaticItemBrowse>;
|
|
73
|
+
/** Options for one verified, one-shot related-entity roster read. */
|
|
74
|
+
export type GetItemRelatedEntitiesOptions = OpenReleaseOptions & {
|
|
75
|
+
readonly buildId: string;
|
|
76
|
+
readonly kind: ItemBrowseRelationshipKind;
|
|
77
|
+
};
|
|
78
|
+
/** Read one build's manifest-pinned roster for exactly one Item relationship kind. */
|
|
79
|
+
export declare function getItemRelatedEntities(options: GetItemRelatedEntitiesOptions): Promise<ItemRelatedEntityBrowsePack>;
|
|
57
80
|
/** Resolve one Item icon blob URL from its content-addressed media digest. */
|
|
58
81
|
export declare function itemIconUrl(baseUrl: URL | string, contentId: string): string;
|
package/client/items.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { MissingPageError, MissingRecordError, SchemaValidationError, ScopeError, UnsupportedCapabilityError, UnsupportedProductError, lazyValidator, memoized, openProductContext, rangeDescriptor, recordRangeDescriptor,
|
|
1
|
+
import { MissingPageError, MissingRecordError, SchemaValidationError, ScopeError, UnsupportedCapabilityError, UnsupportedProductError, assertSameScope, lazyValidator, memoized, openProductContext, rangeDescriptor, recordRangeDescriptor, resolveMediaContentUrl, } from './core.js';
|
|
2
|
+
/** Narrow an Item browse result to contract 2.1, which carries player-fact coverage. */
|
|
3
|
+
export function hasItemPlayerFacts(browse) {
|
|
4
|
+
return browse.contractVersion === '2.1' && browse.coverage.playerFacts !== undefined;
|
|
5
|
+
}
|
|
2
6
|
const loadBrowsePackValidator = lazyValidator(() => import('../generated/item-browse-pack-2.validator.js'));
|
|
7
|
+
const loadRelatedEntityBrowseValidator = lazyValidator(() => import('../generated/item-related-entity-browse-1.validator.js'));
|
|
3
8
|
const loadCatalogValidator = lazyValidator(() => import('../generated/item-catalog-2.validator.js'));
|
|
4
9
|
const loadDetailValidator = lazyValidator(() => import('../generated/item-detail-2.validator.js'));
|
|
5
10
|
const loadDetailPackValidator = lazyValidator(() => import('../generated/item-pack-2.validator.js'));
|
|
@@ -7,7 +12,7 @@ const loadRelationshipPackValidator = lazyValidator(() => import('../generated/i
|
|
|
7
12
|
const loadEvidencePackValidator = lazyValidator(() => import('../generated/item-evidence-pack-2.validator.js'));
|
|
8
13
|
/** Build an Item client over an already verified product manifest. */
|
|
9
14
|
export function itemProductClient(context) {
|
|
10
|
-
const { manifest, fetchEnvelope } = context;
|
|
15
|
+
const { manifest, fetchJson, fetchEnvelope } = context;
|
|
11
16
|
if (manifest.productId !== 'items')
|
|
12
17
|
throw new UnsupportedProductError(manifest.productId);
|
|
13
18
|
let catalogPromise;
|
|
@@ -37,9 +42,13 @@ export function itemProductClient(context) {
|
|
|
37
42
|
const rows = [];
|
|
38
43
|
let previousKey;
|
|
39
44
|
const coverage = packs[0].coverage;
|
|
45
|
+
const contractVersion = packs[0].contractVersion;
|
|
40
46
|
const coverageJson = JSON.stringify(coverage);
|
|
41
47
|
for (const [index, pack] of packs.entries()) {
|
|
42
48
|
const descriptor = descriptors[index];
|
|
49
|
+
if (pack.contractVersion !== contractVersion) {
|
|
50
|
+
throw new SchemaValidationError(descriptor.path, 'browse pack contract version does not match the first browse pack');
|
|
51
|
+
}
|
|
43
52
|
const keys = Object.keys(pack.records);
|
|
44
53
|
if (keys.length !== descriptor.count) {
|
|
45
54
|
throw new SchemaValidationError(descriptor.path, `browse pack has ${keys.length} records but its descriptor declares ${descriptor.count}`);
|
|
@@ -68,12 +77,51 @@ export function itemProductClient(context) {
|
|
|
68
77
|
if (rows.length !== manifest.totalEntityCount) {
|
|
69
78
|
throw new SchemaValidationError(manifest.catalog.path, `browse roster has ${rows.length} rows but the manifest declares ${manifest.totalEntityCount}`);
|
|
70
79
|
}
|
|
71
|
-
|
|
80
|
+
if (contractVersion === '2.1') {
|
|
81
|
+
const playerFacts = coverage.playerFacts;
|
|
82
|
+
if (!playerFacts) {
|
|
83
|
+
throw new SchemaValidationError(descriptors[0].path, 'Item Browse 2.1 is missing player-fact coverage');
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
contractVersion,
|
|
87
|
+
scope: manifest.scope,
|
|
88
|
+
coverage: { ...coverage, playerFacts },
|
|
89
|
+
records: rows,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (contractVersion === '2.0') {
|
|
93
|
+
if ((manifest.requiredClientCapabilities ?? []).includes('item-player-facts-v1')) {
|
|
94
|
+
throw new SchemaValidationError(descriptors[0].path, 'Item product requires player facts but the browse pack is contract 2.0');
|
|
95
|
+
}
|
|
96
|
+
const { playerFacts: _playerFacts, ...legacyCoverage } = coverage;
|
|
97
|
+
return { contractVersion, scope: manifest.scope, coverage: legacyCoverage, records: rows };
|
|
98
|
+
}
|
|
99
|
+
throw new SchemaValidationError(descriptors[0].path, `unsupported Item Browse contract ${contractVersion}`);
|
|
72
100
|
})();
|
|
73
101
|
void browsePromise.catch(() => { browsePromise = undefined; });
|
|
74
102
|
}
|
|
75
103
|
return browsePromise;
|
|
76
104
|
}
|
|
105
|
+
async function loadRelatedEntities(kind, operation) {
|
|
106
|
+
if (!(manifest.requiredClientCapabilities ?? []).includes('item-related-entities-v1')) {
|
|
107
|
+
throw new UnsupportedCapabilityError('Item product manifest does not require item-related-entities-v1');
|
|
108
|
+
}
|
|
109
|
+
const descriptor = manifest.relatedEntityBrowsePacks?.[kind];
|
|
110
|
+
if (!descriptor) {
|
|
111
|
+
throw new UnsupportedCapabilityError(`Item product manifest does not provide ${kind} related entities`);
|
|
112
|
+
}
|
|
113
|
+
const validator = await loadRelatedEntityBrowseValidator();
|
|
114
|
+
const raw = await fetchJson(descriptor, 'browse', operation);
|
|
115
|
+
if (!validator(raw)) {
|
|
116
|
+
throw new SchemaValidationError(descriptor.path, JSON.stringify(validator.errors ?? []));
|
|
117
|
+
}
|
|
118
|
+
const pack = raw;
|
|
119
|
+
assertSameScope(pack.scope, manifest.scope, descriptor.path);
|
|
120
|
+
if (pack.relationshipKind !== kind) {
|
|
121
|
+
throw new SchemaValidationError(descriptor.path, `related-entity roster kind ${pack.relationshipKind} does not match requested ${kind}`);
|
|
122
|
+
}
|
|
123
|
+
return pack;
|
|
124
|
+
}
|
|
77
125
|
async function getDetail(key, operation) {
|
|
78
126
|
const descriptor = recordRangeDescriptor(manifest.detailPacks, key, 'detail', 'Item');
|
|
79
127
|
const packValidator = await loadDetailPackValidator();
|
|
@@ -110,7 +158,7 @@ export function itemProductClient(context) {
|
|
|
110
158
|
throw new MissingPageError(pageKey, 'evidence');
|
|
111
159
|
return page;
|
|
112
160
|
}
|
|
113
|
-
return { manifest, loadCatalog, loadBrowse, getDetail, getRelationshipPage, getEvidencePage };
|
|
161
|
+
return { manifest, loadCatalog, loadBrowse, loadRelatedEntities, getDetail, getRelationshipPage, getEvidencePage };
|
|
114
162
|
}
|
|
115
163
|
/** Open one pinned Item product. */
|
|
116
164
|
export async function openItemProduct(options) {
|
|
@@ -132,10 +180,14 @@ export async function getItemBrowse(options) {
|
|
|
132
180
|
const items = await release.openItemBuild(options.buildId);
|
|
133
181
|
return items.loadBrowse({ signal: options.signal });
|
|
134
182
|
}
|
|
183
|
+
/** Read one build's manifest-pinned roster for exactly one Item relationship kind. */
|
|
184
|
+
export async function getItemRelatedEntities(options) {
|
|
185
|
+
const { openRelease } = await import('./release.js');
|
|
186
|
+
const release = await openRelease(options);
|
|
187
|
+
const items = await release.openItemBuild(options.buildId);
|
|
188
|
+
return items.loadRelatedEntities(options.kind, { signal: options.signal });
|
|
189
|
+
}
|
|
135
190
|
/** Resolve one Item icon blob URL from its content-addressed media digest. */
|
|
136
191
|
export function itemIconUrl(baseUrl, contentId) {
|
|
137
|
-
|
|
138
|
-
throw new SchemaValidationError(contentId, 'Item icon content ID must be a SHA-256 hex digest');
|
|
139
|
-
}
|
|
140
|
-
return resolveReleasePath(baseUrl, `media/images/${contentId.slice(0, 2)}/${contentId}.png`);
|
|
192
|
+
return resolveMediaContentUrl(baseUrl, contentId, 'Item icon');
|
|
141
193
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { MapGeographyFact, MapGeographyProvenance, MapGeographyReviewStatus, } from '../generated/map-catalog-2-1.d.ts';
|
|
2
|
+
export type { MapGeographyArea, MapGeographyCitation, MapGeographyCollection, MapGeographyEntryPoint, MapGeographyModel, MapGeographyRegion, MapGeographyReviewedPlacement, MapGeographyWitnessCount, } from '../generated/map-geography.d.ts';
|
|
3
|
+
export { default as validateMapCatalogV2_1 } from '../generated/map-catalog-2-1.validator.js';
|
|
4
|
+
export { default as validateMapGeography } from '../generated/map-geography.validator.js';
|
package/client/maps.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mapletip/data/maps` — verified Map geography and Map catalog reads.
|
|
3
|
+
*/
|
|
4
|
+
import { isMapGeographyModel, type MapGeographyModel, type StaticDomainProductClient } from './static-domains.js';
|
|
5
|
+
import { type OpenProductOptions, type StaticProductManifest } from './core.js';
|
|
6
|
+
import { type OpenReleaseOptions } from './release.js';
|
|
7
|
+
export { isMapGeographyModel };
|
|
8
|
+
export type { MapGeographyModel, StaticDomainProductClient };
|
|
9
|
+
export type StaticMapProductClient = Omit<StaticDomainProductClient, 'manifest'> & {
|
|
10
|
+
readonly manifest: StaticProductManifest & {
|
|
11
|
+
readonly productId: 'maps';
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** Open a pinned Maps domain product directly. */
|
|
15
|
+
export declare function openMapProduct(options: OpenProductOptions): Promise<StaticMapProductClient>;
|
|
16
|
+
export declare function isMapProductClient(product: StaticDomainProductClient): product is StaticMapProductClient;
|
|
17
|
+
/** Options for the one-shot, manifest-pinned geography model read. */
|
|
18
|
+
export type GetMapGeographyOptions = OpenReleaseOptions & {
|
|
19
|
+
readonly buildId: string;
|
|
20
|
+
};
|
|
21
|
+
/** Read the selected build's digest-verified Map geography model. */
|
|
22
|
+
export declare function getMapGeography(options: GetMapGeographyOptions): Promise<MapGeographyModel>;
|
package/client/maps.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mapletip/data/maps` — verified Map geography and Map catalog reads.
|
|
3
|
+
*/
|
|
4
|
+
import { isMapGeographyModel, } from './static-domains.js';
|
|
5
|
+
import { UnsupportedProductError, } from './core.js';
|
|
6
|
+
import { openStaticDomainProduct } from './static-domains.js';
|
|
7
|
+
import { openRelease } from './release.js';
|
|
8
|
+
export { isMapGeographyModel };
|
|
9
|
+
/** Open a pinned Maps domain product directly. */
|
|
10
|
+
export async function openMapProduct(options) {
|
|
11
|
+
const product = await openStaticDomainProduct(options);
|
|
12
|
+
if (product.manifest.productId !== 'maps') {
|
|
13
|
+
throw new UnsupportedProductError(product.manifest.productId);
|
|
14
|
+
}
|
|
15
|
+
return product;
|
|
16
|
+
}
|
|
17
|
+
export function isMapProductClient(product) {
|
|
18
|
+
return product.manifest.productId === 'maps';
|
|
19
|
+
}
|
|
20
|
+
/** Read the selected build's digest-verified Map geography model. */
|
|
21
|
+
export async function getMapGeography(options) {
|
|
22
|
+
const release = await openRelease(options);
|
|
23
|
+
const maps = await release.openDomainBuild(options.buildId, 'maps');
|
|
24
|
+
return maps.loadGeography({ signal: options.signal });
|
|
25
|
+
}
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { MonsterCatalog } from '../generated/monster-catalog.d.ts';
|
|
10
10
|
import type { MonsterCatalogV2 } from '../generated/monster-catalog-2.d.ts';
|
|
11
|
+
import type { MonsterCatalogV2_1 } from '../generated/monster-catalog-2-1.d.ts';
|
|
12
|
+
import type { MonsterCatalogV2_2 } from '../generated/monster-catalog-2-2.d.ts';
|
|
13
|
+
import type { MonsterCatalogV2_3 } from '../generated/monster-catalog-2-3.d.ts';
|
|
14
|
+
import type { MonsterDropBrowseV1 } from '../generated/monster-drop-browse.d.ts';
|
|
15
|
+
import type { MonsterLocationBrowseV1 } from '../generated/monster-location-browse.d.ts';
|
|
16
|
+
import type { MonsterLocationBrowseV1_1 } from '../generated/monster-location-browse-1-1.d.ts';
|
|
11
17
|
import type { MonsterDetailRecord, PageDescriptor } from '../generated/monster-detail.d.ts';
|
|
12
18
|
import type { MonsterDetailRecordV2 } from '../generated/monster-detail-2.d.ts';
|
|
13
19
|
import type { MonsterEvidencePage } from '../generated/monster-evidence-pack.d.ts';
|
|
@@ -15,12 +21,19 @@ import type { MonsterEvidencePageV2 } from '../generated/monster-evidence-pack-2
|
|
|
15
21
|
import type { MonsterLocationOccurrence } from '../generated/monster-relationship-pack.d.ts';
|
|
16
22
|
import type { MonsterCoreRelationshipPackV2 } from '../generated/monster-core-relationship-pack-2.d.ts';
|
|
17
23
|
import { type OpenProductOptions, type OperationOptions, type StaticProductManifest, type VerifiedProductContext } from './core.js';
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A verified Monster catalog. Contracts 2.1 and 2.2 carry the Media content ID fact
|
|
26
|
+
* for a monster's representative image; 2.0 and 1.x records have none.
|
|
27
|
+
*/
|
|
28
|
+
export type StaticMonsterCatalog = MonsterCatalog | MonsterCatalogV2 | MonsterCatalogV2_1 | MonsterCatalogV2_2 | MonsterCatalogV2_3;
|
|
29
|
+
export type StaticMonsterLocationBrowse = MonsterLocationBrowseV1 | MonsterLocationBrowseV1_1;
|
|
19
30
|
export type StaticMonsterDetail = MonsterDetailRecord | MonsterDetailRecordV2;
|
|
20
31
|
export type StaticMonsterEvidencePage = MonsterEvidencePage | MonsterEvidencePageV2;
|
|
21
32
|
export interface StaticProductClient {
|
|
22
33
|
readonly manifest: StaticProductManifest;
|
|
23
34
|
loadCatalog(options?: OperationOptions): Promise<StaticMonsterCatalog>;
|
|
35
|
+
loadDropBrowse(options?: OperationOptions): Promise<MonsterDropBrowseV1>;
|
|
36
|
+
loadLocationBrowse(options?: OperationOptions): Promise<StaticMonsterLocationBrowse>;
|
|
24
37
|
getDetail(key: string, options?: OperationOptions): Promise<StaticMonsterDetail>;
|
|
25
38
|
getRelationshipPage(page: PageDescriptor, options?: OperationOptions): Promise<MonsterLocationOccurrence[]>;
|
|
26
39
|
getEvidencePage(page: PageDescriptor, options?: OperationOptions): Promise<StaticMonsterEvidencePage>;
|