@mpgd/platform 0.6.0 → 0.7.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.
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PlatformCapabilities, PlatformGateway, PlatformTarget } from './index.js';
|
|
2
|
+
export declare const platformCapabilityKeys: readonly ["nativeIap", "nativeAds", "rewardedAds", "interstitialAds", "nativeLeaderboard", "achievements", "cloudSave", "socialShare", "haptics", "localizedContent"];
|
|
3
|
+
export interface PlatformGatewayCapabilityConformanceTransition {
|
|
4
|
+
readonly update: () => Promise<void> | void;
|
|
5
|
+
readonly expectedCapabilities: PlatformCapabilities;
|
|
6
|
+
}
|
|
7
|
+
export interface PlatformGatewayCapabilityConformanceFixture {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly gateway: PlatformGateway;
|
|
10
|
+
readonly expectedTarget: PlatformTarget;
|
|
11
|
+
readonly expectedCapabilities: PlatformCapabilities;
|
|
12
|
+
/**
|
|
13
|
+
* Optional provider transition used to prove that capability reads are live
|
|
14
|
+
* rather than permanently cached by an adapter or target-config wrapper.
|
|
15
|
+
*/
|
|
16
|
+
readonly transition?: PlatformGatewayCapabilityConformanceTransition;
|
|
17
|
+
}
|
|
18
|
+
export interface RunPlatformGatewayCapabilityConformanceInput {
|
|
19
|
+
readonly fixtures: readonly PlatformGatewayCapabilityConformanceFixture[];
|
|
20
|
+
}
|
|
21
|
+
export interface PlatformGatewayCapabilityConformanceReport {
|
|
22
|
+
readonly passedFixtures: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs provider-neutral capability checks against real adapter gateways or
|
|
26
|
+
* target-configured wrappers. Every read must return a complete boolean
|
|
27
|
+
* snapshot, a fresh object, and the provider's latest state.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runPlatformGatewayCapabilityConformance(input: RunPlatformGatewayCapabilityConformanceInput): Promise<PlatformGatewayCapabilityConformanceReport>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const platformCapabilityKeys = Object.freeze([
|
|
2
|
+
'nativeIap',
|
|
3
|
+
'nativeAds',
|
|
4
|
+
'rewardedAds',
|
|
5
|
+
'interstitialAds',
|
|
6
|
+
'nativeLeaderboard',
|
|
7
|
+
'achievements',
|
|
8
|
+
'cloudSave',
|
|
9
|
+
'socialShare',
|
|
10
|
+
'haptics',
|
|
11
|
+
'localizedContent',
|
|
12
|
+
]);
|
|
13
|
+
/**
|
|
14
|
+
* Runs provider-neutral capability checks against real adapter gateways or
|
|
15
|
+
* target-configured wrappers. Every read must return a complete boolean
|
|
16
|
+
* snapshot, a fresh object, and the provider's latest state.
|
|
17
|
+
*/
|
|
18
|
+
export async function runPlatformGatewayCapabilityConformance(input) {
|
|
19
|
+
if (input.fixtures.length === 0) {
|
|
20
|
+
throw new Error('Platform gateway capability conformance requires at least one fixture.');
|
|
21
|
+
}
|
|
22
|
+
const fixtureNames = new Set();
|
|
23
|
+
const passedFixtures = [];
|
|
24
|
+
for (const fixture of input.fixtures) {
|
|
25
|
+
if (fixture.name.trim().length === 0) {
|
|
26
|
+
throw new Error('Platform capability conformance fixture names must not be empty.');
|
|
27
|
+
}
|
|
28
|
+
if (fixtureNames.has(fixture.name)) {
|
|
29
|
+
throw new Error(`Duplicate platform capability conformance fixture: ${fixture.name}.`);
|
|
30
|
+
}
|
|
31
|
+
fixtureNames.add(fixture.name);
|
|
32
|
+
try {
|
|
33
|
+
await runFixture(fixture);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw new Error(`Platform gateway capability conformance failed: ${fixture.name}.`, {
|
|
37
|
+
cause: error,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
passedFixtures.push(fixture.name);
|
|
41
|
+
}
|
|
42
|
+
return { passedFixtures };
|
|
43
|
+
}
|
|
44
|
+
async function runFixture(fixture) {
|
|
45
|
+
assertEqual(fixture.gateway.target, fixture.expectedTarget, 'gateway target must match the fixture target');
|
|
46
|
+
const first = await fixture.gateway.getCapabilities();
|
|
47
|
+
assertCapabilitySnapshot(first, fixture.expectedCapabilities);
|
|
48
|
+
tryMutateSnapshot(first);
|
|
49
|
+
const second = await fixture.gateway.getCapabilities();
|
|
50
|
+
assert(second !== first, 'getCapabilities must return a fresh snapshot object for every read');
|
|
51
|
+
assertCapabilitySnapshot(second, fixture.expectedCapabilities);
|
|
52
|
+
if (fixture.transition === undefined) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
assert(hasCapabilityDifference(fixture.expectedCapabilities, fixture.transition.expectedCapabilities), 'a transition fixture must change at least one capability');
|
|
56
|
+
await fixture.transition.update();
|
|
57
|
+
const updated = await fixture.gateway.getCapabilities();
|
|
58
|
+
assert(updated !== second, 'getCapabilities must return a fresh snapshot after a provider transition');
|
|
59
|
+
assertCapabilitySnapshot(updated, fixture.transition.expectedCapabilities);
|
|
60
|
+
}
|
|
61
|
+
function assertCapabilitySnapshot(actual, expected) {
|
|
62
|
+
const actualKeys = Object.keys(actual).sort();
|
|
63
|
+
const expectedKeys = [...platformCapabilityKeys].sort();
|
|
64
|
+
assertEqual(JSON.stringify(actualKeys), JSON.stringify(expectedKeys), 'capability snapshots must contain exactly the public capability keys');
|
|
65
|
+
for (const key of platformCapabilityKeys) {
|
|
66
|
+
assertEqual(typeof actual[key], 'boolean', `capability ${key} must be a boolean`);
|
|
67
|
+
assertEqual(actual[key], expected[key], `capability ${key} must match the expected provider state`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function tryMutateSnapshot(snapshot) {
|
|
71
|
+
try {
|
|
72
|
+
Reflect.set(snapshot, 'nativeIap', !snapshot.nativeIap);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Frozen snapshots already satisfy the isolation requirement.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function hasCapabilityDifference(first, second) {
|
|
79
|
+
return platformCapabilityKeys.some((key) => first[key] !== second[key]);
|
|
80
|
+
}
|
|
81
|
+
function assert(condition, message) {
|
|
82
|
+
if (!condition) {
|
|
83
|
+
throw new Error(message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function assertEqual(actual, expected, message) {
|
|
87
|
+
if (actual !== expected) {
|
|
88
|
+
throw new Error(`${message}. Expected ${String(expected)}, received ${String(actual)}.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PlatformTarget = 'browser' | 'android' | 'ios' | 'ait' | 'reddit' | 'telegram' | 'tauri';
|
|
1
|
+
export type PlatformTarget = 'browser' | 'android' | 'ios' | 'ait' | 'reddit' | 'verse8' | 'telegram' | 'tauri';
|
|
2
2
|
export type StarterLogicalProductId = 'COINS_100' | 'COINS_500' | 'REMOVE_ADS';
|
|
3
3
|
export type LogicalProductId = StarterLogicalProductId | (string & Record<never, never>);
|
|
4
4
|
export type StarterLogicalAdPlacementId = 'CONTINUE_AFTER_FAIL' | 'STAGE_END_INTERSTITIAL';
|
|
@@ -182,6 +182,7 @@ export interface StorageAdapter {
|
|
|
182
182
|
load(input: {
|
|
183
183
|
readonly key: string;
|
|
184
184
|
}): Promise<StorageLoadResult | null>;
|
|
185
|
+
/** Persist a JSON-serializable value or reject without replacing the prior value. */
|
|
185
186
|
save(input: {
|
|
186
187
|
readonly key: string;
|
|
187
188
|
readonly value: unknown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StorageAdapter } from './index.js';
|
|
2
|
+
export declare const storageAdapterConformanceScenarios: readonly ['missing-read', 'round-trip-and-overwrite', 'storage-scope-isolation', 'mutation-isolation', 'quota-rejection-preserves-value', 'save-failure-is-fail-closed', 'load-failure-is-fail-closed'];
|
|
3
|
+
export type StorageAdapterConformanceScenario = (typeof storageAdapterConformanceScenarios)[number];
|
|
4
|
+
export interface CreateStorageAdapterConformanceFixtureInput {
|
|
5
|
+
readonly scenario: StorageAdapterConformanceScenario;
|
|
6
|
+
}
|
|
7
|
+
export interface StorageAdapterConformanceFixture {
|
|
8
|
+
/** Storage area under test. */
|
|
9
|
+
readonly storage: StorageAdapter;
|
|
10
|
+
/**
|
|
11
|
+
* A distinct account, namespace, browser profile, or equivalent storage
|
|
12
|
+
* scope backed by the same kind of provider.
|
|
13
|
+
*/
|
|
14
|
+
readonly isolatedStorage: StorageAdapter;
|
|
15
|
+
/** A value that the fixture's configured provider quota must reject. */
|
|
16
|
+
readonly oversizedValue: unknown;
|
|
17
|
+
/** Fail the next primary-scope save before it becomes visible. */
|
|
18
|
+
readonly armNextSaveFailure: () => void;
|
|
19
|
+
/** Fail the next primary-scope load instead of reporting a missing value. */
|
|
20
|
+
readonly armNextLoadFailure: () => void;
|
|
21
|
+
readonly dispose?: () => Promise<void> | void;
|
|
22
|
+
}
|
|
23
|
+
export type CreateStorageAdapterConformanceFixture = (input: CreateStorageAdapterConformanceFixtureInput) => Promise<StorageAdapterConformanceFixture> | StorageAdapterConformanceFixture;
|
|
24
|
+
export interface RunStorageAdapterConformanceInput {
|
|
25
|
+
/** Return an isolated fixture, or a uniquely namespaced fixture, per scenario. */
|
|
26
|
+
readonly createFixture: CreateStorageAdapterConformanceFixture;
|
|
27
|
+
}
|
|
28
|
+
export interface StorageAdapterConformanceReport {
|
|
29
|
+
readonly passedScenarios: readonly StorageAdapterConformanceScenario[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Runs provider-neutral persistence checks against a StorageAdapter. The
|
|
33
|
+
* suite distinguishes a missing value from a provider failure, requires
|
|
34
|
+
* quota and fault rejection to preserve the last committed value, and checks
|
|
35
|
+
* JSON-style value isolation across the adapter boundary.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runStorageAdapterConformance(input: RunStorageAdapterConformanceInput): Promise<StorageAdapterConformanceReport>;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
export const storageAdapterConformanceScenarios = [
|
|
2
|
+
'missing-read',
|
|
3
|
+
'round-trip-and-overwrite',
|
|
4
|
+
'storage-scope-isolation',
|
|
5
|
+
'mutation-isolation',
|
|
6
|
+
'quota-rejection-preserves-value',
|
|
7
|
+
'save-failure-is-fail-closed',
|
|
8
|
+
'load-failure-is-fail-closed',
|
|
9
|
+
];
|
|
10
|
+
const scenarioRunners = {
|
|
11
|
+
'missing-read': runMissingReadScenario,
|
|
12
|
+
'round-trip-and-overwrite': runRoundTripAndOverwriteScenario,
|
|
13
|
+
'storage-scope-isolation': runStorageScopeIsolationScenario,
|
|
14
|
+
'mutation-isolation': runMutationIsolationScenario,
|
|
15
|
+
'quota-rejection-preserves-value': runQuotaRejectionPreservesValueScenario,
|
|
16
|
+
'save-failure-is-fail-closed': runSaveFailureIsFailClosedScenario,
|
|
17
|
+
'load-failure-is-fail-closed': runLoadFailureIsFailClosedScenario,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Runs provider-neutral persistence checks against a StorageAdapter. The
|
|
21
|
+
* suite distinguishes a missing value from a provider failure, requires
|
|
22
|
+
* quota and fault rejection to preserve the last committed value, and checks
|
|
23
|
+
* JSON-style value isolation across the adapter boundary.
|
|
24
|
+
*/
|
|
25
|
+
export async function runStorageAdapterConformance(input) {
|
|
26
|
+
const passedScenarios = [];
|
|
27
|
+
for (const scenario of storageAdapterConformanceScenarios) {
|
|
28
|
+
let fixture;
|
|
29
|
+
let scenarioError;
|
|
30
|
+
let scenarioFailed = false;
|
|
31
|
+
try {
|
|
32
|
+
fixture = await input.createFixture({ scenario });
|
|
33
|
+
await scenarioRunners[scenario](fixture);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
scenarioError = error;
|
|
37
|
+
scenarioFailed = true;
|
|
38
|
+
}
|
|
39
|
+
let cleanupError;
|
|
40
|
+
let cleanupFailed = false;
|
|
41
|
+
try {
|
|
42
|
+
await fixture?.dispose?.();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
cleanupError = error;
|
|
46
|
+
cleanupFailed = true;
|
|
47
|
+
}
|
|
48
|
+
if (scenarioFailed) {
|
|
49
|
+
throw new Error(`Storage adapter conformance failed: ${scenario}.`, {
|
|
50
|
+
cause: cleanupFailed
|
|
51
|
+
? new AggregateError([scenarioError, cleanupError], `Scenario and fixture cleanup both failed: ${scenario}.`)
|
|
52
|
+
: scenarioError,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (cleanupFailed) {
|
|
56
|
+
throw new Error(`Storage adapter conformance cleanup failed: ${scenario}.`, {
|
|
57
|
+
cause: cleanupError,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
passedScenarios.push(scenario);
|
|
61
|
+
}
|
|
62
|
+
return { passedScenarios };
|
|
63
|
+
}
|
|
64
|
+
async function runMissingReadScenario(fixture) {
|
|
65
|
+
const key = scenarioKey('missing-read');
|
|
66
|
+
assertEqual(await fixture.storage.load({ key }), null, 'missing values must return null');
|
|
67
|
+
}
|
|
68
|
+
async function runRoundTripAndOverwriteScenario(fixture) {
|
|
69
|
+
const key = scenarioKey('round-trip-and-overwrite');
|
|
70
|
+
const first = { revision: 1, progress: { stage: 2, coins: 10 } };
|
|
71
|
+
const second = { revision: 2, progress: { stage: 3, coins: 25 } };
|
|
72
|
+
await fixture.storage.save({ key, value: first });
|
|
73
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: first }, 'saved values must round-trip');
|
|
74
|
+
await fixture.storage.save({ key, value: second });
|
|
75
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: second }, 'later saves must replace the value for the same key');
|
|
76
|
+
await fixture.storage.save({ key, value: null });
|
|
77
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: null }, 'a stored top-level JSON null must remain distinguishable from a missing value');
|
|
78
|
+
}
|
|
79
|
+
async function runStorageScopeIsolationScenario(fixture) {
|
|
80
|
+
const key = scenarioKey('storage-scope-isolation');
|
|
81
|
+
const primaryValue = { owner: 'primary', coins: 10 };
|
|
82
|
+
const isolatedValue = { owner: 'isolated', coins: 20 };
|
|
83
|
+
await fixture.storage.save({ key, value: primaryValue });
|
|
84
|
+
assertEqual(await fixture.isolatedStorage.load({ key }), null, 'a distinct storage scope must not observe the primary value');
|
|
85
|
+
await fixture.isolatedStorage.save({ key, value: isolatedValue });
|
|
86
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: primaryValue }, 'writing an isolated scope must not replace the primary value');
|
|
87
|
+
assertJsonEqual(await fixture.isolatedStorage.load({ key }), { value: isolatedValue }, 'the isolated scope must retain its own value');
|
|
88
|
+
}
|
|
89
|
+
async function runMutationIsolationScenario(fixture) {
|
|
90
|
+
const key = scenarioKey('mutation-isolation');
|
|
91
|
+
const savedInput = {
|
|
92
|
+
revision: 1,
|
|
93
|
+
progress: { stage: 4, coins: 40 },
|
|
94
|
+
inventory: ['starter'],
|
|
95
|
+
};
|
|
96
|
+
const committedValue = {
|
|
97
|
+
revision: 1,
|
|
98
|
+
progress: { stage: 4, coins: 40 },
|
|
99
|
+
inventory: ['starter'],
|
|
100
|
+
};
|
|
101
|
+
await fixture.storage.save({ key, value: savedInput });
|
|
102
|
+
savedInput.progress.coins = 999;
|
|
103
|
+
savedInput.inventory.push('mutated-after-save');
|
|
104
|
+
const firstLoad = await fixture.storage.load({ key });
|
|
105
|
+
assertJsonEqual(firstLoad, { value: committedValue }, 'mutating the saved input must not mutate committed storage');
|
|
106
|
+
assert(firstLoad !== null, 'the committed value must remain readable');
|
|
107
|
+
const mutableLoadedValue = firstLoad.value;
|
|
108
|
+
try {
|
|
109
|
+
mutableLoadedValue.progress.coins = -1;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Immutable values are acceptable; the later read still proves isolation.
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
mutableLoadedValue.inventory.push('mutated-after-load');
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Immutable values are acceptable; each nested branch is probed independently.
|
|
119
|
+
}
|
|
120
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: committedValue }, 'mutating a loaded value must not mutate later reads');
|
|
121
|
+
}
|
|
122
|
+
async function runQuotaRejectionPreservesValueScenario(fixture) {
|
|
123
|
+
const key = scenarioKey('quota-rejection-preserves-value');
|
|
124
|
+
const committedValue = { revision: 1, state: 'committed-before-quota' };
|
|
125
|
+
await fixture.storage.save({ key, value: committedValue });
|
|
126
|
+
await assertRejects(() => fixture.storage.save({ key, value: fixture.oversizedValue }), 'an oversized value must reject instead of reporting success');
|
|
127
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: committedValue }, 'quota rejection must preserve the last committed value');
|
|
128
|
+
}
|
|
129
|
+
async function runSaveFailureIsFailClosedScenario(fixture) {
|
|
130
|
+
const key = scenarioKey('save-failure-is-fail-closed');
|
|
131
|
+
const committedValue = { revision: 1, state: 'committed-before-save-failure' };
|
|
132
|
+
await fixture.storage.save({ key, value: committedValue });
|
|
133
|
+
fixture.armNextSaveFailure();
|
|
134
|
+
await assertRejects(() => fixture.storage.save({ key, value: { revision: 2, state: 'must-not-commit' } }), 'provider save failures must reject instead of falling back or reporting success');
|
|
135
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: committedValue }, 'a failed save must preserve the last committed value');
|
|
136
|
+
}
|
|
137
|
+
async function runLoadFailureIsFailClosedScenario(fixture) {
|
|
138
|
+
const key = scenarioKey('load-failure-is-fail-closed');
|
|
139
|
+
const committedValue = { revision: 1, state: 'committed-before-load-failure' };
|
|
140
|
+
await fixture.storage.save({ key, value: committedValue });
|
|
141
|
+
fixture.armNextLoadFailure();
|
|
142
|
+
await assertRejects(() => fixture.storage.load({ key }), 'provider load failures must reject instead of returning a missing or fallback value');
|
|
143
|
+
assertJsonEqual(await fixture.storage.load({ key }), { value: committedValue }, 'the next load must recover the committed value after a transient failure');
|
|
144
|
+
}
|
|
145
|
+
function scenarioKey(scenario) {
|
|
146
|
+
return `storage-conformance:${scenario}`;
|
|
147
|
+
}
|
|
148
|
+
async function assertRejects(run, message) {
|
|
149
|
+
let rejected = false;
|
|
150
|
+
try {
|
|
151
|
+
await run();
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
rejected = true;
|
|
155
|
+
}
|
|
156
|
+
assert(rejected, message);
|
|
157
|
+
}
|
|
158
|
+
function assertJsonEqual(actual, expected, message) {
|
|
159
|
+
if (!jsonValuesEqual(actual, expected)) {
|
|
160
|
+
throw new Error(`${message}. Expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}.`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function jsonValuesEqual(actual, expected) {
|
|
164
|
+
if (actual === expected) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(actual) || Array.isArray(expected)) {
|
|
168
|
+
return (Array.isArray(actual) &&
|
|
169
|
+
Array.isArray(expected) &&
|
|
170
|
+
actual.length === expected.length &&
|
|
171
|
+
actual.every((value, index) => jsonValuesEqual(value, expected[index])));
|
|
172
|
+
}
|
|
173
|
+
if (!isJsonObject(actual) || !isJsonObject(expected)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const actualKeys = Object.keys(actual);
|
|
177
|
+
const expectedKeys = Object.keys(expected);
|
|
178
|
+
return (actualKeys.length === expectedKeys.length &&
|
|
179
|
+
actualKeys.every((key) => Object.hasOwn(expected, key) && jsonValuesEqual(actual[key], expected[key])));
|
|
180
|
+
}
|
|
181
|
+
function isJsonObject(value) {
|
|
182
|
+
return typeof value === 'object' && value !== null;
|
|
183
|
+
}
|
|
184
|
+
function assert(condition, message) {
|
|
185
|
+
if (!condition) {
|
|
186
|
+
throw new Error(message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function assertEqual(actual, expected, message) {
|
|
190
|
+
if (actual !== expected) {
|
|
191
|
+
throw new Error(`${message}. Expected ${String(expected)}, received ${String(actual)}.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Shared platform gateway, monetization, ads, leaderboard, identity, lifecycle, and storage contracts for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
".": {
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./capability-conformance": {
|
|
33
|
+
"types": "./dist/capability-conformance.d.ts",
|
|
34
|
+
"default": "./dist/capability-conformance.js"
|
|
35
|
+
},
|
|
36
|
+
"./storage-conformance": {
|
|
37
|
+
"types": "./dist/storage-conformance.d.ts",
|
|
38
|
+
"default": "./dist/storage-conformance.js"
|
|
31
39
|
}
|
|
32
40
|
},
|
|
33
41
|
"files": [
|
|
@@ -48,6 +56,6 @@
|
|
|
48
56
|
"lint": "ttsc --noEmit",
|
|
49
57
|
"format": "ttsc format",
|
|
50
58
|
"fix": "ttsc fix",
|
|
51
|
-
"test": "vitest run"
|
|
59
|
+
"test": "vitest run && node test/capability-conformance-dist-import.mjs && node test/storage-conformance-dist-import.mjs"
|
|
52
60
|
}
|
|
53
61
|
}
|