@mapletip/data 0.3.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 +172 -2
- package/client/contract-compatibility.mjs +10 -0
- package/client/items.d.ts +28 -5
- package/client/items.js +59 -4
- 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 +11 -3
- package/client/monster-client.js +44 -5
- package/client/monsters.d.ts +27 -6
- package/client/monsters.js +30 -4
- 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-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 +36 -2
package/README.md
CHANGED
|
@@ -82,11 +82,97 @@ digest. `getMonsterBrowse` still requests only the release manifest named by the
|
|
|
82
82
|
build index, the product manifest and the Monster catalog.
|
|
83
83
|
|
|
84
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 `2.1`. `@mapletip/data`
|
|
85
|
+
SDK reads both, and `hasMonsterSprites` narrows a catalog to revisions `2.1` or `2.2`. `@mapletip/data`
|
|
86
86
|
`0.2.0` and older reject a `2.1` catalog with a typed `SCHEMA_VALIDATION_ERROR` instead of
|
|
87
87
|
returning rows without sprites, so upgrade to `0.3.0` before adopting a pin that carries
|
|
88
88
|
it.
|
|
89
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
|
+
|
|
90
176
|
`pin` is the parsed `pin.json` published next to the release root by whoever assembled
|
|
91
177
|
the release: it names the manifest path, SHA-256 and byte length and is the caller's
|
|
92
178
|
trust anchor. The SDK never resolves a "current" release on its own and never fetches a
|
|
@@ -145,6 +231,66 @@ It rejects any content ID that is not a 64-character lowercase SHA-256 digest wi
|
|
|
145
231
|
`SCHEMA_VALIDATION_ERROR`, so a row's `icon.value` can be used without building a
|
|
146
232
|
make-believe descriptor.
|
|
147
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
|
+
|
|
148
294
|
## What each entry loads
|
|
149
295
|
|
|
150
296
|
Monster catalog validation and the release/manifest/index validators are imported
|
|
@@ -154,7 +300,7 @@ operation that needs it, as a separate on-demand chunk:
|
|
|
154
300
|
| Entry | Eager generated validators | Loaded on demand |
|
|
155
301
|
| --- | --- | --- |
|
|
156
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 |
|
|
157
|
-
| `/items` | product manifest only (every Item validator is on demand) | browse, catalog, detail, relationship and evidence packs, plus the release client behind `getItemBrowse` |
|
|
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` |
|
|
158
304
|
| `/equipment` | that domain's catalog, detail, relationship and evidence packs | — |
|
|
159
305
|
| `/quests` | none (Quest validators are always on demand) | catalog, browse, detail, relationship, auxiliary and evidence packs |
|
|
160
306
|
| `/media` | that domain's catalog, detail packs | — |
|
|
@@ -288,3 +434,27 @@ const evPage = await client.getEvidencePage('quest-ev-1000');
|
|
|
288
434
|
|
|
289
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.
|
|
290
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/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, resolveMediaContentUrl, } from './core.js';
|
|
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,6 +180,13 @@ 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
192
|
return resolveMediaContentUrl(baseUrl, contentId, 'Item icon');
|
|
@@ -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
|
+
}
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
import type { MonsterCatalog } from '../generated/monster-catalog.d.ts';
|
|
10
10
|
import type { MonsterCatalogV2 } from '../generated/monster-catalog-2.d.ts';
|
|
11
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';
|
|
12
17
|
import type { MonsterDetailRecord, PageDescriptor } from '../generated/monster-detail.d.ts';
|
|
13
18
|
import type { MonsterDetailRecordV2 } from '../generated/monster-detail-2.d.ts';
|
|
14
19
|
import type { MonsterEvidencePage } from '../generated/monster-evidence-pack.d.ts';
|
|
@@ -17,15 +22,18 @@ import type { MonsterLocationOccurrence } from '../generated/monster-relationshi
|
|
|
17
22
|
import type { MonsterCoreRelationshipPackV2 } from '../generated/monster-core-relationship-pack-2.d.ts';
|
|
18
23
|
import { type OpenProductOptions, type OperationOptions, type StaticProductManifest, type VerifiedProductContext } from './core.js';
|
|
19
24
|
/**
|
|
20
|
-
* A verified Monster catalog.
|
|
21
|
-
*
|
|
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.
|
|
22
27
|
*/
|
|
23
|
-
export type StaticMonsterCatalog = MonsterCatalog | MonsterCatalogV2 | MonsterCatalogV2_1;
|
|
28
|
+
export type StaticMonsterCatalog = MonsterCatalog | MonsterCatalogV2 | MonsterCatalogV2_1 | MonsterCatalogV2_2 | MonsterCatalogV2_3;
|
|
29
|
+
export type StaticMonsterLocationBrowse = MonsterLocationBrowseV1 | MonsterLocationBrowseV1_1;
|
|
24
30
|
export type StaticMonsterDetail = MonsterDetailRecord | MonsterDetailRecordV2;
|
|
25
31
|
export type StaticMonsterEvidencePage = MonsterEvidencePage | MonsterEvidencePageV2;
|
|
26
32
|
export interface StaticProductClient {
|
|
27
33
|
readonly manifest: StaticProductManifest;
|
|
28
34
|
loadCatalog(options?: OperationOptions): Promise<StaticMonsterCatalog>;
|
|
35
|
+
loadDropBrowse(options?: OperationOptions): Promise<MonsterDropBrowseV1>;
|
|
36
|
+
loadLocationBrowse(options?: OperationOptions): Promise<StaticMonsterLocationBrowse>;
|
|
29
37
|
getDetail(key: string, options?: OperationOptions): Promise<StaticMonsterDetail>;
|
|
30
38
|
getRelationshipPage(page: PageDescriptor, options?: OperationOptions): Promise<MonsterLocationOccurrence[]>;
|
|
31
39
|
getEvidencePage(page: PageDescriptor, options?: OperationOptions): Promise<StaticMonsterEvidencePage>;
|
package/client/monster-client.js
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import validateCatalog from '../generated/monster-catalog.validator.js';
|
|
2
2
|
import validateCatalogV2 from '../generated/monster-catalog-2.validator.js';
|
|
3
3
|
import validateCatalogV2_1 from '../generated/monster-catalog-2-1.validator.js';
|
|
4
|
-
import
|
|
4
|
+
import validateCatalogV2_2 from '../generated/monster-catalog-2-2.validator.js';
|
|
5
|
+
import validateCatalogV2_3 from '../generated/monster-catalog-2-3.validator.js';
|
|
6
|
+
import validateDropBrowse from '../generated/monster-drop-browse.validator.js';
|
|
7
|
+
import validateLocationBrowse from '../generated/monster-location-browse.validator.js';
|
|
8
|
+
import validateLocationBrowseV1_1 from '../generated/monster-location-browse-1-1.validator.js';
|
|
9
|
+
import { MissingPageError, MissingRecordError, SchemaValidationError, UnsupportedCapabilityError, UnsupportedProductError, assertEnvelopeVersion, assertSameScope, lazyValidator, memoized, openProductContext, rangeDescriptor, recordRangeDescriptor, } from './core.js';
|
|
5
10
|
/**
|
|
6
11
|
* Contract-2 catalog validator that picks the revision's strict schema from the document's
|
|
7
12
|
* own `contractVersion`. Unsupported revisions fail the 2.0 schema with a typed error.
|
|
8
13
|
*/
|
|
9
14
|
const validateCatalogRevision = Object.assign((data) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const version = data?.contractVersion;
|
|
16
|
+
const validator = version === '2.3'
|
|
17
|
+
? validateCatalogV2_3
|
|
18
|
+
: version === '2.2'
|
|
19
|
+
? validateCatalogV2_2
|
|
20
|
+
: version === '2.1'
|
|
21
|
+
? validateCatalogV2_1
|
|
22
|
+
: validateCatalogV2;
|
|
13
23
|
const valid = validator(data);
|
|
14
24
|
validateCatalogRevision.errors = validator.errors;
|
|
15
25
|
return valid;
|
|
@@ -23,9 +33,16 @@ const loadLocationsV2 = lazyValidator(() => import('../generated/monster-locatio
|
|
|
23
33
|
const loadCoreRelationshipsV2 = lazyValidator(() => import('../generated/monster-core-relationship-pack-2.validator.js'));
|
|
24
34
|
const loadEvidence = lazyValidator(() => import('../generated/monster-evidence-pack.validator.js'));
|
|
25
35
|
const loadEvidenceV2 = lazyValidator(() => import('../generated/monster-evidence-pack-2.validator.js'));
|
|
36
|
+
const validateLocationBrowseRevision = Object.assign((data) => {
|
|
37
|
+
const version = data?.contractVersion;
|
|
38
|
+
const validator = version === '1.1' ? validateLocationBrowseV1_1 : validateLocationBrowse;
|
|
39
|
+
const valid = validator(data);
|
|
40
|
+
validateLocationBrowseRevision.errors = validator.errors;
|
|
41
|
+
return valid;
|
|
42
|
+
}, { errors: undefined });
|
|
26
43
|
/** Build a Monster client over an already verified product manifest. */
|
|
27
44
|
export function monsterProductClient(context) {
|
|
28
|
-
const { manifest, contract2, fetchEnvelope } = context;
|
|
45
|
+
const { manifest, contract2, fetchJson, fetchEnvelope } = context;
|
|
29
46
|
if (manifest.productId !== 'monsters')
|
|
30
47
|
throw new UnsupportedProductError(manifest.productId);
|
|
31
48
|
let catalogPromise;
|
|
@@ -49,6 +66,26 @@ export function monsterProductClient(context) {
|
|
|
49
66
|
}
|
|
50
67
|
return catalog;
|
|
51
68
|
}
|
|
69
|
+
async function loadBrowseArtifact(descriptor, capability, validator, label, operation) {
|
|
70
|
+
if (!(manifest.requiredClientCapabilities ?? []).includes(capability)) {
|
|
71
|
+
throw new UnsupportedCapabilityError('Monster product manifest does not require ' + capability);
|
|
72
|
+
}
|
|
73
|
+
if (!descriptor) {
|
|
74
|
+
throw new UnsupportedCapabilityError('Monster product manifest does not provide ' + label);
|
|
75
|
+
}
|
|
76
|
+
const raw = await fetchJson(descriptor, 'browse', operation);
|
|
77
|
+
if (!validator(raw)) {
|
|
78
|
+
throw new SchemaValidationError(descriptor.path, JSON.stringify(validator.errors ?? []));
|
|
79
|
+
}
|
|
80
|
+
assertSameScope(raw.scope, manifest.scope, descriptor.path);
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
async function loadDropBrowse(operation) {
|
|
84
|
+
return loadBrowseArtifact(manifest.dropBrowse, 'monster-drop-browse-v1', validateDropBrowse, 'drop browse artifact', operation);
|
|
85
|
+
}
|
|
86
|
+
async function loadLocationBrowse(operation) {
|
|
87
|
+
return loadBrowseArtifact(manifest.locationBrowse, 'monster-location-browse-v1', validateLocationBrowseRevision, 'location browse artifact', operation);
|
|
88
|
+
}
|
|
52
89
|
async function getDetail(key, operation) {
|
|
53
90
|
const descriptor = recordRangeDescriptor(manifest.detailPacks, key, 'detail', 'Monster');
|
|
54
91
|
if (contract2) {
|
|
@@ -153,6 +190,8 @@ export function monsterProductClient(context) {
|
|
|
153
190
|
return {
|
|
154
191
|
manifest,
|
|
155
192
|
loadCatalog,
|
|
193
|
+
loadDropBrowse,
|
|
194
|
+
loadLocationBrowse,
|
|
156
195
|
getDetail,
|
|
157
196
|
getRelationshipPage,
|
|
158
197
|
getEvidencePage,
|