@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.
Files changed (88) hide show
  1. package/NOTICE +52 -0
  2. package/README.md +209 -0
  3. package/client/contract-compatibility.d.mts +27 -0
  4. package/client/contract-compatibility.mjs +159 -0
  5. package/client/core.d.ts +150 -0
  6. package/client/core.js +359 -0
  7. package/client/equipment.d.ts +22 -0
  8. package/client/equipment.js +63 -0
  9. package/client/items.d.ts +22 -0
  10. package/client/items.js +63 -0
  11. package/client/media.d.ts +19 -0
  12. package/client/media.js +47 -0
  13. package/client/monster-client.d.ts +35 -0
  14. package/client/monster-client.js +155 -0
  15. package/client/monsters.d.ts +24 -0
  16. package/client/monsters.js +14 -0
  17. package/client/quests.d.ts +31 -0
  18. package/client/quests.js +124 -0
  19. package/client/release.d.ts +63 -0
  20. package/client/release.js +279 -0
  21. package/client/static-client.d.ts +40 -0
  22. package/client/static-client.js +44 -0
  23. package/client/static-domains.d.ts +48 -0
  24. package/client/static-domains.js +113 -0
  25. package/generated/contract-2-catalog-fact.d.ts +51 -0
  26. package/generated/equipment-catalog-2.d.ts +434 -0
  27. package/generated/equipment-catalog-2.validator.js +17 -0
  28. package/generated/equipment-detail-2.d.ts +350 -0
  29. package/generated/equipment-detail-2.validator.js +17 -0
  30. package/generated/equipment-evidence-pack-2.d.ts +243 -0
  31. package/generated/equipment-evidence-pack-2.validator.js +17 -0
  32. package/generated/equipment-pack-2.validator.js +17 -0
  33. package/generated/equipment-relationship-pack-2.d.ts +170 -0
  34. package/generated/equipment-relationship-pack-2.validator.js +17 -0
  35. package/generated/item-catalog-2.d.ts +366 -0
  36. package/generated/item-catalog-2.validator.js +17 -0
  37. package/generated/item-detail-2.d.ts +540 -0
  38. package/generated/item-detail-2.validator.js +17 -0
  39. package/generated/item-evidence-pack-2.d.ts +243 -0
  40. package/generated/item-evidence-pack-2.validator.js +17 -0
  41. package/generated/item-pack-2.validator.js +17 -0
  42. package/generated/item-relationship-pack-2.d.ts +170 -0
  43. package/generated/item-relationship-pack-2.validator.js +17 -0
  44. package/generated/media-catalog-2.d.ts +133 -0
  45. package/generated/media-catalog-2.validator.js +17 -0
  46. package/generated/media-detail-2.d.ts +125 -0
  47. package/generated/media-detail-2.validator.js +17 -0
  48. package/generated/media-pack-2.validator.js +17 -0
  49. package/generated/monster-catalog-2.d.ts +230 -0
  50. package/generated/monster-catalog-2.validator.js +17 -0
  51. package/generated/monster-catalog.d.ts +224 -0
  52. package/generated/monster-catalog.validator.js +1 -0
  53. package/generated/monster-core-relationship-pack-2.d.ts +208 -0
  54. package/generated/monster-core-relationship-pack-2.validator.js +17 -0
  55. package/generated/monster-detail-2.d.ts +372 -0
  56. package/generated/monster-detail-2.validator.js +17 -0
  57. package/generated/monster-detail.d.ts +799 -0
  58. package/generated/monster-detail.validator.js +17 -0
  59. package/generated/monster-evidence-pack-2.d.ts +378 -0
  60. package/generated/monster-evidence-pack-2.validator.js +17 -0
  61. package/generated/monster-evidence-pack.d.ts +269 -0
  62. package/generated/monster-evidence-pack.validator.js +17 -0
  63. package/generated/monster-location-pack-2.validator.js +1 -0
  64. package/generated/monster-pack-2.validator.js +17 -0
  65. package/generated/monster-pack.validator.js +17 -0
  66. package/generated/monster-relationship-pack.d.ts +126 -0
  67. package/generated/monster-relationship-pack.validator.js +1 -0
  68. package/generated/product-manifest.d.ts +188 -0
  69. package/generated/product-manifest.validator.js +1 -0
  70. package/generated/quest-auxiliary-pack-2.d.ts +391 -0
  71. package/generated/quest-auxiliary-pack-2.validator.js +17 -0
  72. package/generated/quest-browse-pack-2.d.ts +353 -0
  73. package/generated/quest-browse-pack-2.validator.js +17 -0
  74. package/generated/quest-catalog-2.d.ts +501 -0
  75. package/generated/quest-catalog-2.validator.js +17 -0
  76. package/generated/quest-evidence-pack-2.d.ts +269 -0
  77. package/generated/quest-evidence-pack-2.validator.js +17 -0
  78. package/generated/quest-pack-2.d.ts +410 -0
  79. package/generated/quest-pack-2.validator.js +17 -0
  80. package/generated/quest-relationship-pack-2.d.ts +868 -0
  81. package/generated/quest-relationship-pack-2.validator.js +17 -0
  82. package/generated/release-build-index.d.ts +42 -0
  83. package/generated/release-build-index.validator.js +1 -0
  84. package/generated/release-manifest.d.ts +24 -0
  85. package/generated/release-manifest.validator.js +1 -0
  86. package/generated/release-pin.d.ts +12 -0
  87. package/generated/release-pin.validator.js +1 -0
  88. package/package.json +155 -0
package/NOTICE ADDED
@@ -0,0 +1,52 @@
1
+ MapleTip public data SDK (@mapletip/data)
2
+ Copyright (C) 2026 Stephen Liang. All rights reserved.
3
+
4
+ This package is publicly downloadable from the npm registry, but it is proprietary
5
+ software, not open-source software: no licence is granted to modify it, redistribute it,
6
+ publish it, sublicense it, or incorporate it (in source or compiled form) into another
7
+ product. Any use beyond installing it as a dependency requires the prior written
8
+ permission of the copyright holder.
9
+
10
+ SPDX-License-Identifier: UNLICENSED
11
+
12
+ Third-party notices
13
+ -------------------
14
+ This package contains the following third-party material.
15
+
16
+ 1. Ajv runtime helper `ucs2length`
17
+
18
+ Generated standalone validators that enforce string length constraints inline
19
+ the `ucs2length` helper emitted by Ajv's standalone code generator, so that the
20
+ published package needs no runtime dependency on Ajv. Ajv is used as a
21
+ build-time dependency only; this inlined helper is the only Ajv code present in
22
+ the distributed files (27 of the 34 shipped validators; the others do not
23
+ enforce string lengths).
24
+
25
+ The MIT License (MIT)
26
+
27
+ Copyright (c) 2015-2021 Evgeny Poberezkin
28
+
29
+ Permission is hereby granted, free of charge, to any person obtaining a copy
30
+ of this software and associated documentation files (the "Software"), to deal
31
+ in the Software without restriction, including without limitation the rights
32
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33
+ copies of the Software, and to permit persons to whom the Software is
34
+ furnished to do so, subject to the following conditions:
35
+
36
+ The above copyright notice and this permission notice shall be included in all
37
+ copies or substantial portions of the Software.
38
+
39
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45
+ SOFTWARE.
46
+
47
+ Build-time dependencies that are not distributed with this package (Ajv,
48
+ ajv-formats, json-schema-to-typescript, TypeScript) remain under their own
49
+ licences; only the inlined helper above is present in the published files.
50
+
51
+ Canonical MapleTip datasets, game assets, media and trademarks are not part of
52
+ this package and are licensed separately.
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # MapleTip public data contracts
2
+
3
+ ## Install
4
+
5
+ ```sh
6
+ npm install @mapletip/data
7
+ ```
8
+
9
+ `@mapletip/data` is an ESM-only package with TypeScript declarations and no runtime
10
+ dependencies. It is one package with domain-scoped entry points, so a consumer only
11
+ bundles the domains it imports:
12
+
13
+ ```ts
14
+ import { getMonsterBrowse, openMonsterProduct } from '@mapletip/data/monsters';
15
+ import { openRelease, resolveTargetAvailability } from '@mapletip/data/release';
16
+ import { openItemProduct } from '@mapletip/data/items';
17
+ import { openEquipmentProduct } from '@mapletip/data/equipment';
18
+ import { openQuestProduct } from '@mapletip/data/quests';
19
+ import { openMediaProduct, resolveMediaUrl } from '@mapletip/data/media';
20
+ import { assertManifestCompatibility } from '@mapletip/data/contract-compatibility';
21
+
22
+ // Compatibility: the same implementations, all domains in one graph.
23
+ import { getMonsterBrowse, openRelease, openProduct } from '@mapletip/data';
24
+ import { openRelease } from '@mapletip/data/client';
25
+ ```
26
+
27
+ Every entry point shares one verified release/transport/integrity core. Importing a
28
+ domain never re-implements hashing, scope, capability or contract checks, and the
29
+ compatibility root is a view over the same modules rather than a second implementation.
30
+
31
+ ## Isomorphic Monster browse
32
+
33
+ `getMonsterBrowse` reads one build's verified Monster catalog in a single call:
34
+
35
+ ```ts
36
+ import { getMonsterBrowse } from '@mapletip/data/monsters';
37
+
38
+ const catalog = await getMonsterBrowse({
39
+ baseUrl: releaseRootUrl,
40
+ pin: releasePin,
41
+ buildId: 'cw-launch',
42
+ signal: abortController.signal,
43
+ });
44
+ ```
45
+
46
+ It is exactly `openRelease(options)` → `openBuild(options.buildId)` → `loadCatalog()`:
47
+ it adds no cache, no fallback build selection and no alternate data path, and it returns
48
+ the canonical Rust-owned catalog with its `known`/`unknown`/`absent`/`unsupported` fact
49
+ and collection states and build scope intact. The optional `fetch`, `signal`, `onRequest`
50
+ and validated `preloaded` options behave exactly as they do on `openRelease`, and every
51
+ existing typed failure (`UNKNOWN_BUILD`, `UNSUPPORTED_PRODUCT`, `SCOPE_ERROR`,
52
+ `INTEGRITY_ERROR`, `SCHEMA_VALIDATION_ERROR`, `UNSUPPORTED_CAPABILITY`,
53
+ `CONTRACT_VERSION_ERROR`, `AbortError`, …) propagates unchanged.
54
+
55
+ Supported builds are `cw-launch` (`classic-world`) and `gms-v83` (`gms`). When several
56
+ reads share one release, keep using `openRelease` and reuse the opened release client.
57
+
58
+ `pin` is the parsed `pin.json` published next to the release root by whoever assembled
59
+ the release: it names the manifest path, SHA-256 and byte length and is the caller's
60
+ trust anchor. The SDK never resolves a "current" release on its own and never fetches a
61
+ pin, so a consumer chooses exactly one immutable release and one explicit build.
62
+
63
+ The same import runs in Node (SSR, prerendering, scripts) and in browsers using standard
64
+ `fetch`, `URL`, `AbortSignal` and WebCrypto. Browser reads require a secure context
65
+ (HTTPS or localhost) because SHA-256 verification has no fallback. Rendering mode,
66
+ routing, caching and UI adaptation stay with the consumer.
67
+
68
+ ## What each entry loads
69
+
70
+ Monster catalog validation and the release/manifest/index validators are imported
71
+ eagerly: a browse cannot complete without them. Everything else loads on the first
72
+ operation that needs it, as a separate on-demand chunk:
73
+
74
+ | Entry | Eager generated validators | Loaded on demand |
75
+ | --- | --- | --- |
76
+ | `/monsters` | release pin, release manifest, build index, product manifest, Monster catalog 1.x and 2.0 | Monster detail, location/relationship, core-relationship and evidence packs |
77
+ | `/items`, `/equipment` | that domain's catalog, detail, relationship and evidence packs | — |
78
+ | `/quests` | none (Quest validators are always on demand) | catalog, browse, detail, relationship, auxiliary and evidence packs |
79
+ | `/media` | that domain's catalog, detail packs | — |
80
+ | `/release` | release pin, release manifest, build index, product manifest, Monster catalog (Monster is the release client's build selector) | Item, Equipment, Media, Quest and static-domain clients |
81
+ | `.` and `/client` | every domain's eager validators (compatibility view) | Quest and Monster detail/relationship/evidence validators |
82
+
83
+ A Monster-browse-only browser consumer therefore ships the release and Monster catalog
84
+ code and nothing else; the repository's `tools/benchmark-bundle.mjs` measures it against
85
+ the pre-modularization package.
86
+
87
+ Publication to the public npm registry is a manual, owner-approved step; the procedure
88
+ lives in `PUBLISHING.md` in the repository and is not shipped in the tarball.
89
+
90
+ ## License
91
+
92
+ `@mapletip/data` is **proprietary software**, published for public download on npm but
93
+ not open source (`"license": "UNLICENSED"` in `package.json`). The copyright holder
94
+ reserves all rights: no licence is granted to modify, redistribute, publish, sublicense or
95
+ incorporate the package into another product, and any use beyond installing it as a
96
+ dependency requires prior written permission, as stated in the `NOTICE` file shipped with
97
+ the package.
98
+
99
+ `NOTICE` also preserves the third-party attribution for the Ajv runtime helper inlined
100
+ into the generated validators.
101
+
102
+ Canonical MapleTip datasets, game assets, media and trademarks are **not** part of this
103
+ package and are licensed separately.
104
+
105
+ ## Compiler and product store
106
+
107
+ The `maplekit-public materialize-product` command stores local products under
108
+ `$XDG_DATA_HOME/mapletip/products` by default, falling back to
109
+ `$HOME/.local/share/mapletip/products`. Set `MAPLETIP_PRODUCT_STORE` or pass
110
+ `--store-root` to select another store.
111
+
112
+ `maplekit-public assemble-release` accepts exactly two `--product-id` values
113
+ from that product store: the completed CW Launch product with its historical
114
+ overlay and native GMS v83. It writes an immutable local static release under
115
+ `$XDG_DATA_HOME/mapletip/releases`, falling back to
116
+ `$HOME/.local/share/mapletip/releases`; `MAPLETIP_RELEASE_STORE` or
117
+ `--release-store` selects another store. The release manifest advertises
118
+ `release-manifest-v1`, `local-diagnostic`/partial for incomplete local output,
119
+ or `production`/complete when the exact hard-cutover roster is assembled. Its
120
+ separate `pin.json` names the content-addressed manifest path, SHA-256, and
121
+ byte length.
122
+
123
+ Products emitted by the D identity materializer advertise contract version `1.2` and require `product-input-identity-v1`. Generated wire schemas accept a well-formed minor version under major `1`; typed consumers must also enforce the minimum minor implied by each required capability and reject required capabilities they do not implement. `wire-1.1`, `evidence-pages-v1`, and `historical-drop-overlay-v1` require minor `1` or later; `product-input-identity-v1` and `release-manifest-v1` require minor `2` or later.
124
+
125
+ TypeScript consumers can import `assertManifestCompatibility` and `assertCompatibleContractVersion` from `@mapletip/data/contract-compatibility`. Validate the manifest first, then use its returned minimum minor when validating each child envelope.
126
+
127
+ The historical drop overlay is optional. A product advertises `historical-drop-overlay-v1` only when it contains that overlay. A consumer that does not implement the overlay can consume a native product that does not require the capability and can ignore unknown optional fields.
128
+
129
+ The generated validators at `ba9c525` pinned `contractVersion` to exactly `1.0`. They reject a new `1.1` product even when it has no historical overlay. This was a pre-existing schema defect, not a capability incompatibility. Regenerate or update consumers to use the major-compatible version pattern and check `requiredClientCapabilities` before consuming `1.1` products. Those previous validators cannot accept `1.1` until updated.
130
+
131
+ The cross-version test in `pnpm test` runs when `MAPLETIP_PREVIOUS_VALIDATOR_DIR` points to a temporary extraction of the generated validators from `ba9c525`; otherwise that historical comparison is skipped.
132
+
133
+ ## Static Monster client
134
+
135
+ The browser-safe ESM client is available from `@mapletip/data/client`. It consumes one product directory selected by the caller's immutable release pin and build scope:
136
+
137
+ ```ts
138
+ import { openProduct } from '@mapletip/data/client';
139
+
140
+ const client = await openProduct({
141
+ baseUrl: releaseProductBaseUrl,
142
+ manifest: releaseProductDescriptor,
143
+ expectedScope: { family_id: 'classic-world', build_id: 'cw-launch' },
144
+ onRequest: (entry) => requestLog.push(entry),
145
+ });
146
+
147
+ const catalog = await client.loadCatalog();
148
+ const detail = await client.getDetail(catalog.records[0].key);
149
+ const locations = await client.getRelationshipPage(detail.locations.pages[0]);
150
+ ```
151
+
152
+ `releaseProductDescriptor` supplies the manifest path, SHA-256, and byte length from the selected release/product index. The client verifies those manifest bytes and every selected catalog, detail, relationship, or evidence pack against its descriptor before decoding or parsing JSON. It validates each envelope with the generated Ajv validator, checks contract capabilities and exact build scope, and fetches only the pack that addresses the requested key or page. The request hook records the file kind, relative path, and resolved URL.
153
+
154
+ Digest verification requires a secure browser context with WebCrypto SHA-256 available. If `globalThis.isSecureContext` is `false` or `crypto.subtle` is unavailable, reads reject with `SecureContextRequiredError` (`SECURE_CONTEXT_REQUIRED`). There is no hashing fallback or unverified-data path. Serve the site over HTTPS, or use localhost during local development.
155
+
156
+ Detail records use generated TypeScript declarations. Optional `evidenceAttribution` on drop entries is exposed to callers as typed data. The client does not render it. The client supports `product-input-identity-v1` and enforces the capability's contract-version floor when opening a product manifest.
157
+
158
+ ## Static release client
159
+
160
+ Use `openRelease` when the application is pinned to an immutable multi-build release. The `baseUrl` is the release directory root, and `pin` is the parsed `pin.json` object:
161
+
162
+ ```ts
163
+ import { openRelease } from '@mapletip/data/client';
164
+
165
+ const release = await openRelease({
166
+ baseUrl: '/releases/release-a299223e/release/',
167
+ pin,
168
+ });
169
+
170
+ for (const { family, build, monsterProductDescriptor, questProductDescriptor } of release.builds) {
171
+ console.log(family, build, monsterProductDescriptor?.productManifest.path, questProductDescriptor?.productManifest.path);
172
+ }
173
+
174
+ const client = await release.openBuild('cw-launch'); // Monster client
175
+ const questClient = await release.openQuestBuild('cw-launch'); // Quest client
176
+ const mediaClient = await release.openMediaBuild('cw-launch'); // Media client
177
+ ```
178
+
179
+ The client validates the pin, verifies the release-manifest bytes against its pinned SHA-256 and byte length before parsing, validates the manifest with its generated schema, and requires the `release-manifest-v1` capability. It then verifies and validates the build index and checks that both identify the pinned release.
180
+
181
+ Build product independence: `openRelease` supports releases with any valid product roster (including Monster-less builds containing only Quest products). Each build entry in `release.builds` exposes `productCapabilities`, `monsterProductDescriptor`, `questProductDescriptor`, `mediaProductDescriptor`, and legacy alias `productDescriptor` (for Monster product). Calling `openBuild`, `openQuestBuild`, or `openMediaBuild` on a build missing that product throws `UnsupportedProductError`. Absent build IDs reject with `UnknownBuildError` and never fall back to another product.
182
+
183
+ ## Static Quest client
184
+
185
+ The Quest client is available via `openQuestProduct` or `openRelease`:
186
+
187
+ ```ts
188
+ import { openQuestProduct, isQuestProductClient } from '@mapletip/data/client';
189
+
190
+ const client = await openQuestProduct({
191
+ baseUrl: releaseProductBaseUrl,
192
+ manifest: questProductDescriptor,
193
+ expectedScope: { family_id: 'classic-world', build_id: 'cw-launch' },
194
+ onRequest: (entry) => requestLog.push(entry),
195
+ onValidatorLoad: (validatorName) => console.log('Validator loaded:', validatorName),
196
+ });
197
+
198
+ // Lazy per-operation validation: each operation loads only its required validator
199
+ const catalog = await client.loadCatalog();
200
+ const browsePacks = await client.loadBrowsePacks();
201
+ const browseRecord = await client.getBrowseRecord('quest-1000');
202
+ const detail = await client.getDetail('quest-1000');
203
+ const relPage = await client.getRelationshipPage('quest-rel-1000', { expectedOwnerKey: 'quest-1000' });
204
+ const auxPage = await client.getAuxiliaryPage('quest-aux-1000');
205
+ const evPage = await client.getEvidencePage('quest-ev-1000');
206
+ ```
207
+
208
+ 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.
209
+
@@ -0,0 +1,27 @@
1
+ import type { ConnectedProductManifest } from '../generated/product-manifest.d.ts';
2
+ import type { ReleaseManifest } from '../generated/release-manifest.d.ts';
3
+
4
+ export declare const SUPPORTED_CLIENT_CAPABILITIES: ReadonlySet<string>;
5
+
6
+ export interface ContractVersionFloor {
7
+ readonly major: number;
8
+ readonly minor: number;
9
+ }
10
+
11
+ export declare function assertCompatibleContractVersion(
12
+ version: string,
13
+ minimumVersion?: number | ContractVersionFloor,
14
+ ): void;
15
+
16
+ export declare function assertManifestCompatibility(
17
+ manifest: ConnectedProductManifest | ReleaseManifest,
18
+ supportedCapabilities?: ReadonlySet<string>,
19
+ ): number;
20
+
21
+ export declare function minimumPackContractMinor(
22
+ manifest: ConnectedProductManifest,
23
+ ): number;
24
+
25
+ export declare function minimumPackContractVersion(
26
+ manifest: ConnectedProductManifest | ReleaseManifest,
27
+ ): ContractVersionFloor;
@@ -0,0 +1,159 @@
1
+ const CONTRACT_VERSION_PATTERN = /^(1|2)\.(0|[1-9][0-9]*)$/;
2
+
3
+ const MINIMUM_VERSION_BY_CAPABILITY = new Map([
4
+ ['record-packs-v1', { major: 1, minor: 0 }],
5
+ ['wire-1.1', { major: 1, minor: 1 }],
6
+ ['evidence-pages-v1', { major: 1, minor: 1 }],
7
+ ['historical-drop-overlay-v1', { major: 1, minor: 1 }],
8
+ ['product-input-identity-v1', { major: 1, minor: 2 }],
9
+ ['release-manifest-v1', { major: 1, minor: 2 }],
10
+ ['monster-core-v1', { major: 2, minor: 0 }],
11
+ ['quests-v1', { major: 2, minor: 0 }],
12
+ ['items-v1', { major: 2, minor: 0 }],
13
+ ['equipment-v1', { major: 2, minor: 0 }],
14
+ ['media-v1', { major: 2, minor: 0 }],
15
+ ['maps-v1', { major: 2, minor: 0 }],
16
+ ['npcs-v1', { major: 2, minor: 0 }],
17
+ ['reactors-v1', { major: 2, minor: 0 }],
18
+ ['skills-v1', { major: 2, minor: 0 }],
19
+ ['jobs-v1', { major: 2, minor: 0 }],
20
+ ['recipes-v1', { major: 2, minor: 0 }],
21
+ ['cash-shop-v1', { major: 2, minor: 0 }],
22
+ ['avatars-v1', { major: 2, minor: 0 }],
23
+ ['evidence-pages-v2', { major: 2, minor: 0 }],
24
+ ['wire-2.0', { major: 2, minor: 0 }],
25
+ ]);
26
+ const MANIFEST_ONLY_CAPABILITIES = new Set(['product-input-identity-v1']);
27
+
28
+ const supportedClientCapabilities = new Set([
29
+ 'record-packs-v1',
30
+ 'wire-1.1',
31
+ 'evidence-pages-v1',
32
+ 'historical-drop-overlay-v1',
33
+ 'product-input-identity-v1',
34
+ 'release-manifest-v1',
35
+ 'monster-core-v1',
36
+ 'quests-v1',
37
+ 'items-v1',
38
+ 'equipment-v1',
39
+ 'media-v1',
40
+ 'maps-v1',
41
+ 'npcs-v1',
42
+ 'reactors-v1',
43
+ 'skills-v1',
44
+ 'jobs-v1',
45
+ 'recipes-v1',
46
+ 'cash-shop-v1',
47
+ 'avatars-v1',
48
+ 'evidence-pages-v2',
49
+ 'wire-2.0',
50
+ ]);
51
+
52
+ export const SUPPORTED_CLIENT_CAPABILITIES = new Proxy(supportedClientCapabilities, {
53
+ get(target, property) {
54
+ if (property === 'add' || property === 'delete' || property === 'clear') return undefined;
55
+ const value = Reflect.get(target, property, target);
56
+ return typeof value === 'function' ? value.bind(target) : value;
57
+ },
58
+ });
59
+
60
+ function parseContractVersion(version) {
61
+ const match = typeof version === 'string' ? CONTRACT_VERSION_PATTERN.exec(version) : null;
62
+ if (!match) throw new Error(`Unsupported contract version: ${String(version)}`);
63
+ return { major: Number(match[1]), minor: Number(match[2]) };
64
+ }
65
+
66
+ function isLessThan(actual, required) {
67
+ return actual.major < required.major
68
+ || (actual.major === required.major && actual.minor < required.minor);
69
+ }
70
+
71
+ function maximumVersion(left, right) {
72
+ return isLessThan(left, right) ? right : left;
73
+ }
74
+
75
+ function floorFromArgument(minimumVersion) {
76
+ if (typeof minimumVersion === 'number') return { major: 1, minor: minimumVersion };
77
+ if (minimumVersion && Number.isSafeInteger(minimumVersion.major)
78
+ && Number.isSafeInteger(minimumVersion.minor)
79
+ && minimumVersion.major >= 1 && minimumVersion.minor >= 0) {
80
+ return { major: minimumVersion.major, minor: minimumVersion.minor };
81
+ }
82
+ throw new Error('Invalid minimum contract version');
83
+ }
84
+
85
+ export function assertCompatibleContractVersion(version, minimumVersion = 0) {
86
+ const actual = parseContractVersion(version);
87
+ const required = floorFromArgument(minimumVersion);
88
+ if (typeof minimumVersion === 'number' && actual.major !== 1) {
89
+ throw new Error(`Unsupported contract version: ${version}`);
90
+ }
91
+ if (isLessThan(actual, required)) {
92
+ if (typeof minimumVersion === 'number') {
93
+ throw new Error(`Contract version ${version} is below required minor ${minimumVersion}`);
94
+ }
95
+ throw new Error(`Contract version ${version} is below required contract ${required.major}.${required.minor}`);
96
+ }
97
+ }
98
+
99
+ export function assertManifestCompatibility(manifest, supportedCapabilities = SUPPORTED_CLIENT_CAPABILITIES) {
100
+ let minimumVersion = { major: 1, minor: 0 };
101
+ const requiredCapabilities = manifest.requiredClientCapabilities ?? [];
102
+ for (const capability of requiredCapabilities) {
103
+ const capabilityMinimumVersion = MINIMUM_VERSION_BY_CAPABILITY.get(capability);
104
+ if (capabilityMinimumVersion === undefined) {
105
+ throw new Error(`Unknown required client capability: ${capability}`);
106
+ }
107
+ if (!supportedCapabilities.has(capability)) {
108
+ throw new Error(`Unsupported client capability: ${capability}`);
109
+ }
110
+ minimumVersion = maximumVersion(minimumVersion, capabilityMinimumVersion);
111
+ }
112
+
113
+ const actual = parseContractVersion(manifest.contractVersion);
114
+ if (actual.major === 2 && !requiredCapabilities.includes('wire-2.0')) {
115
+ throw new Error('Contract 2.0 requires the wire-2.0 client capability');
116
+ }
117
+ if (actual.major === 2) {
118
+ for (const required of ['evidence-pages-v2']) {
119
+ if (!requiredCapabilities.includes(required)) {
120
+ throw new Error(`Contract 2.0 requires the ${required} client capability`);
121
+ }
122
+ }
123
+ if (!requiredCapabilities.some((capability) => [
124
+ 'monster-core-v1', 'quests-v1', 'items-v1', 'equipment-v1', 'media-v1',
125
+ 'maps-v1', 'npcs-v1', 'reactors-v1', 'skills-v1', 'jobs-v1', 'recipes-v1',
126
+ 'cash-shop-v1', 'avatars-v1',
127
+ ].includes(capability))) {
128
+ throw new Error('Contract 2.0 requires a product capability');
129
+ }
130
+ }
131
+ assertCompatibleContractVersion(
132
+ manifest.contractVersion,
133
+ minimumVersion.major === 1 ? minimumVersion.minor : minimumVersion,
134
+ );
135
+ // Keep the legacy minor-only return value for 1.x callers. Version floors
136
+ // for pack envelopes use minimumPackContractVersion below.
137
+ return minimumVersion.major === 2 ? minimumVersion.major : minimumVersion.minor;
138
+ }
139
+
140
+ export function minimumPackContractVersion(manifest) {
141
+ let minimumVersion = { major: 1, minor: 0 };
142
+ for (const capability of manifest.requiredClientCapabilities ?? []) {
143
+ if (MANIFEST_ONLY_CAPABILITIES.has(capability)) continue;
144
+ const capabilityMinimumVersion = MINIMUM_VERSION_BY_CAPABILITY.get(capability);
145
+ if (capabilityMinimumVersion === undefined) {
146
+ throw new Error(`Unknown required client capability: ${capability}`);
147
+ }
148
+ if (!SUPPORTED_CLIENT_CAPABILITIES.has(capability)) {
149
+ throw new Error(`Unsupported client capability: ${capability}`);
150
+ }
151
+ minimumVersion = maximumVersion(minimumVersion, capabilityMinimumVersion);
152
+ }
153
+ return minimumVersion;
154
+ }
155
+
156
+ /** Backwards-compatible minor-only helper for contract-1.x consumers. */
157
+ export function minimumPackContractMinor(manifest) {
158
+ return minimumPackContractVersion(manifest).minor;
159
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Shared verified-data core: standard fetch/URL handling, SHA-256 and byte-length
3
+ * verification, manifest and pin validation, descriptor path safety, contract and
4
+ * capability compatibility, build/product scope checks, typed errors, request
5
+ * tracing and memoization.
6
+ *
7
+ * Every domain module imports this module; this module imports no domain module.
8
+ * Trust operations are implemented exactly once, here.
9
+ */
10
+ import type { ConnectedProductManifest, PackDescriptor } from '../generated/product-manifest.d.ts';
11
+ import type { ReleaseManifest } from '../generated/release-manifest.d.ts';
12
+ import { type ContractVersionFloor } from './contract-compatibility.mjs';
13
+ export type ProductScope = ConnectedProductManifest['scope'];
14
+ export type ProductIdentity = Readonly<NonNullable<ConnectedProductManifest['identity']>>;
15
+ export interface StaticProductManifest extends ConnectedProductManifest {
16
+ readonly identity?: ProductIdentity | null;
17
+ }
18
+ /** A manifest pin supplied by the immutable release/product index. */
19
+ export interface ProductManifestDescriptor {
20
+ readonly path: string;
21
+ readonly sha256: string;
22
+ readonly byteLength: number;
23
+ }
24
+ export interface IntegrityDescriptor {
25
+ readonly path: string;
26
+ readonly sha256: string;
27
+ readonly byteLength: number;
28
+ }
29
+ export type RequestKind = 'release-manifest' | 'build-index' | 'manifest' | 'catalog' | 'detail' | 'browse' | 'relationship' | 'core-relationship' | 'auxiliary' | 'evidence' | 'blob';
30
+ export interface RequestLogEntry {
31
+ readonly kind: RequestKind;
32
+ readonly path: string;
33
+ readonly url: string;
34
+ readonly source: 'network' | 'preloaded';
35
+ }
36
+ export type RequestLogHook = (entry: RequestLogEntry) => void;
37
+ export interface OpenProductOptions {
38
+ /** Directory URL containing the selected product's manifest and assets. */
39
+ readonly baseUrl: string | URL;
40
+ /** Immutable product-manifest pin from the release/product descriptor. */
41
+ readonly manifest: ProductManifestDescriptor;
42
+ /** Exact build expected from the caller's release pin. */
43
+ readonly expectedScope: ProductScope;
44
+ readonly signal?: AbortSignal;
45
+ readonly fetch?: typeof globalThis.fetch;
46
+ readonly onRequest?: RequestLogHook;
47
+ }
48
+ export interface OperationOptions {
49
+ readonly signal?: AbortSignal;
50
+ }
51
+ export interface GetRelationshipOptions extends OperationOptions {
52
+ readonly expectedOwnerKey?: string;
53
+ }
54
+ export interface ContractEnvelope {
55
+ readonly contractVersion: string;
56
+ readonly scope: ProductScope;
57
+ }
58
+ export interface RuntimeValidator<T> {
59
+ (data: unknown): data is T;
60
+ readonly errors?: readonly unknown[] | null;
61
+ }
62
+ /** A product manifest that has already passed pin, schema, capability and scope checks. */
63
+ export interface VerifiedProductContext {
64
+ readonly manifest: StaticProductManifest;
65
+ /** Contract floor implied by the product's required client capabilities. */
66
+ readonly minimumVersion: ContractVersionFloor;
67
+ /** True for contract-2.0 products, which carry identity metadata and 2.0 packs. */
68
+ readonly contract2: boolean;
69
+ /** Fetch, verify bytes and parse one descriptor without schema validation. */
70
+ fetchJson(descriptor: IntegrityDescriptor | PackDescriptor, kind: RequestKind, operation?: OperationOptions): Promise<unknown>;
71
+ /** Fetch, verify, parse, schema-validate and scope-check one envelope. */
72
+ fetchEnvelope<T extends ContractEnvelope>(descriptor: PackDescriptor, kind: Exclude<RequestKind, 'manifest'>, validator: RuntimeValidator<T>, operation?: OperationOptions): Promise<T>;
73
+ /** Fetch and verify raw bytes (media blobs). */
74
+ fetchBlob(descriptor: IntegrityDescriptor, operation?: OperationOptions): Promise<Uint8Array>;
75
+ }
76
+ export declare class StaticClientError extends Error {
77
+ readonly code: string;
78
+ constructor(code: string, message: string, options?: ErrorOptions);
79
+ }
80
+ export declare class SecureContextRequiredError extends StaticClientError {
81
+ constructor();
82
+ }
83
+ export declare class IntegrityError extends StaticClientError {
84
+ constructor(message: string);
85
+ }
86
+ export declare class RequestError extends StaticClientError {
87
+ constructor(message: string, options?: ErrorOptions);
88
+ }
89
+ export declare class InvalidPathError extends StaticClientError {
90
+ constructor(message: string);
91
+ }
92
+ export declare class InvalidJsonError extends StaticClientError {
93
+ constructor(path: string, options?: ErrorOptions);
94
+ }
95
+ export declare class SchemaValidationError extends StaticClientError {
96
+ constructor(path: string, details: string);
97
+ }
98
+ export declare class ContractVersionError extends StaticClientError {
99
+ constructor(message: string, options?: ErrorOptions);
100
+ }
101
+ export declare class UnsupportedCapabilityError extends StaticClientError {
102
+ constructor(message: string);
103
+ }
104
+ export declare class UnsupportedProductError extends StaticClientError {
105
+ constructor(productId: string);
106
+ }
107
+ export declare class UnknownBuildError extends StaticClientError {
108
+ constructor(buildId: string);
109
+ }
110
+ export declare class ScopeError extends StaticClientError {
111
+ constructor(message: string);
112
+ }
113
+ export declare class MissingRecordError extends StaticClientError {
114
+ constructor(key: string, domain?: string);
115
+ }
116
+ export declare class MissingPageError extends StaticClientError {
117
+ constructor(key: string, kind: string);
118
+ }
119
+ export declare function normalizedBaseUrl(value: string | URL): URL;
120
+ export declare function verifyBlob(bytes: Uint8Array, descriptor: IntegrityDescriptor): Promise<void>;
121
+ export declare function resolveMediaUrl(baseUrl: URL | string, descriptor: IntegrityDescriptor): string;
122
+ export declare function assertCompatibleManifest(manifest: ConnectedProductManifest | ReleaseManifest): number;
123
+ export declare function createJsonFetcher(options: {
124
+ readonly baseUrl: URL;
125
+ readonly fetcher: typeof globalThis.fetch;
126
+ readonly onRequest?: RequestLogHook;
127
+ readonly signal?: AbortSignal;
128
+ }): (descriptor: IntegrityDescriptor | PackDescriptor, kind: RequestKind, operation?: OperationOptions) => Promise<unknown>;
129
+ export declare function parsePreloadedJson(value: Uint8Array | string, descriptor: IntegrityDescriptor, kind: RequestKind, baseUrl: URL, onRequest?: RequestLogHook): Promise<unknown>;
130
+ export declare function assertEnvelopeVersion(version: string, minimumVersion: ContractVersionFloor, path: string): void;
131
+ export declare function assertSameScope(actual: ProductScope, expected: ProductScope, path: string): void;
132
+ export declare function rangeDescriptor(descriptors: readonly PackDescriptor[], key: string, kind: string): PackDescriptor;
133
+ export declare function recordRangeDescriptor(descriptors: readonly PackDescriptor[], key: string, kind: string, domain?: string): PackDescriptor;
134
+ export declare function memoized<T>(cache: Map<string, Promise<T>>, key: string, load: () => Promise<T>): Promise<T>;
135
+ /**
136
+ * Wrap a static `import()` of one generated validator in a memoized loader.
137
+ * The import specifier stays literal so bundlers keep the validator in its own
138
+ * on-demand chunk; a rejected load is evicted so the next call retries.
139
+ */
140
+ export declare function lazyValidator<T>(load: () => Promise<{
141
+ default: RuntimeValidator<T>;
142
+ }>): () => Promise<RuntimeValidator<T>>;
143
+ export declare function isStaticDomain(domain: string): boolean;
144
+ /** Product capability implied by a content-addressed product ID. */
145
+ export declare function productCapability(productId: string): string | undefined;
146
+ /**
147
+ * Fetch, verify and validate one product manifest, then hand back the verified
148
+ * context that domain clients build on. This is the single manifest trust gate.
149
+ */
150
+ export declare function openProductContext(options: OpenProductOptions): Promise<VerifiedProductContext>;